# geotecha - A software suite for geotechncial engineering
# Copyright (C) 2015 Rohan T. Walker (rtrwalker@gmail.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/gpl.html.
"""Some soil water characteristic curves (SWCC) for unsaturated soil"""
from __future__ import print_function, division
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
from scipy.optimize import fsolve
import sympy
[docs]class SWCC(object):
"""Base class for defining soil water characteristic curve"""
[docs] def w_from_psi(self, psi, **kwargs):
"""Water content from suction"""
raise NotImplementedError("w_from_psi must be implemented")
[docs] def psi_from_w(self, w, **kwargs):
"""Suction from water content"""
raise NotImplementedError("w_from_psi must be implemented")
[docs] def dw_dpsi(self, psi, **kwargs):
"""Slope of SWCC dw/dpsi"""
raise NotImplementedError("dw_dpsi must be implemented")
[docs] def psi_and_w_for_plotting(self, **kwargs):
"""Suction and water content for plotting that plot the method"""
# should return a tuple of x and y values
# x-values are peremability, y-values are void ratio.
raise NotImplementedError("psi_and_w_for_plotting must be implemented")
[docs] def plot_model(self, **kwargs):
"""Plot the void ratio-permeability"""
ax = kwargs.pop('ax', plt.gca())
x, y = self.psi_and_w_for_plotting(**kwargs)
ax.plot(x, y)
return
[docs]class SWCC_FredlundAndXing1994(SWCC):
"""Soil water characteristic curve from Fredlund and Xing 1994
Parameters
----------
a : float
Fitting parameter corresponding to soil suction at the inflection
point.
n : float
Fitting parameter designating th rate of desaturation.
m : float
Third fitting parameter
ws : float, optional
Saturated water content. Default ws=1.0 i.e. basically a degree of
saturation.
psir : float, optional
Residual suction. Default psir=1e6.
correction : float, optional
Manual correction factor. Default correction=None i.e. correction
factor is caclulated.
References
----------
.. [1] Fredlund, D.G., Anqing Xing, and Shangyan Huang.
"Predicting the Permeability Function for Unsaturated Soils
Using the Soil-Water Characteristic Curve."
Canadian Geotechnical Journal 31, no. 4 (1994): 533-46.
doi:10.1139/t94-062.
Notes
-----
The equation for the soil water charcteristic curve is given by:
.. math:: w(\\psi) = C(\\psi)
\\frac{w_s}
{\\left[{\\ln\\left[{\\exp(1)+
\\left({\\psi/a}\\right)^n}\\right]}\\right]^m}
where the correction factor is given by:
.. math:: C(\\psi) = \\left[{1 - \\frac{\\ln\\left({1+\\psi/\\psi_r}\\right)}
{\\ln\\left({1+10^6/\\psi_r}\\right)}}\\right]
SWCC_FredlundAndXing1994()
"""
def __init__(self, a, n, m, ws=1.0, psir=1.0e6, correction=None):
self.a = a
self.n = n
self.m = m
self.ws = ws
self.psir = psir
self.correction = correction
[docs] def w_from_psi(self, psi, **kwargs):
"""water content from suction
Parameters
----------
psi : float
Suction
Returns
-------
w : float
Suction corresponding to psi
Examples
--------
>>> a = SWCC_FredlundAndXing1994(a=3000, n=1.5, m=1, ws=60, correction=1)
>>> a.w_from_psi(2000)
50.73...
>>> b = SWCC_FredlundAndXing1994(a=100, n=2, m=4, correction=1)
>>> b.w_from_psi(90)
0.395...
>>> c = SWCC_FredlundAndXing1994(a=427, n=0.794, m=0.613, psir=3000)
>>> c.w_from_psi(10000)
0.40...
"""
psir = self.psir
a = self.a
n = self.n
m = self.m
ws = self.ws
if self.correction is None:
corr = (1.0 - np.log(1 + psi / psir) /
np.log(1.0 + 1.0e6 / psir) )
else:
corr = self.correction
return (corr * ws /
np.log(np.exp(1.0) + (psi / a)**n)**m)
def _derive_dw_dpsi():
"""Derive the slope dw_dpsi with sympy
Returns
-------
dw_dpsi1 : sympy expression
Derivative including correction factor.
dw_dpsi2 : sympy expression
Derivative with correction factor equal to one.
"""
psi, a, n, m, psir, ws = sympy.symbols('psi, a, n, m, psir, ws')
from sympy import log, exp
C1 = (1 - log(1 + psi / psir) /
log(1.0 + 1.0e6 / psir))
C2 = 1
w = C1 * ws / log(exp(1) + (psi / a)**n)**m
dw_dpsi1 = sympy.diff(w, psi)
w = C2 * ws / log(exp(1) + (psi / a)**n)**m
dw_dpsi2 = sympy.diff(w, psi)
return dw_dpsi1, dw_dpsi2
def _numerical_check_of_dw_dpsi(**kwargs):
"""Produce plot of dw_dpsi, and check against numerical slope
only needs to be close
Parameters
----------
ax : matplotlib.Axes object, optional
Axes to plot on.
"""
if 'ax' in kwargs:
ax=kwargs['ax']
else:
ax=plt.gca()
a = SWCC_FredlundAndXing1994(a=427, n=0.794, m=0.613, psir=3000)
b = SWCC_FredlundAndXing1994(a=200, n=2, m=1, ws=40, correction=1)
text = ('a={a:g}, n={n:g}, m={m:g}, psir={psir:g}, '
'ws={ws:g}, correction={correction}')
labels = [text.format(a=v.a, n=v.n, m=v.m, psir=v.psir, ws=v.ws,
correction=v.correction) for v in [a, b]]
for v, label in zip([a, b], labels):
x, y = v.psi_and_w_for_plotting(npts=100)
dx = np.diff(x)
dy = np.diff(y)
xm = x[:-1] + dx
slope_n = dy / dx
slope_a = v.dw_dpsi(xm)
ax.plot(xm, slope_n, marker='o', ms=2, ls='.',
label="numerical: " + label)
ax.plot(xm, slope_a,
label="analytical: " + label)
plt.gca().set_xscale('log')
ax.set_xlabel('$\\psi$')
ax.set_ylabel('$dw/d\\psi$')
leg = ax.legend(loc=4)
leg.draggable()
# print(np.isclose(slope_a, slope_n))
# print(slope_a-slope_n)
plt.show()
[docs] def dw_dpsi(self, psi, **kwargs):
"""Slope of SWCC dw/dpsi
Parameters
----------
psi : float
Suction
Returns
-------
dw_dpsi : float
Suction corresponding to psi
Examples
--------
>>> a = SWCC_FredlundAndXing1994(a=3000, n=1.5, m=1, ws=60, correction=1)
>>> a.dw_dpsi(2000)
-0.00536...
>>> b = SWCC_FredlundAndXing1994(a=100, n=2, m=4, correction=1)
>>> b.dw_dpsi(90)
-0.00640...
>>> c = SWCC_FredlundAndXing1994(a=427, n=0.794, m=0.613, psir=3000)
>>> c.dw_dpsi(10000)
-1.317...e-05
"""
psir = self.psir
a = self.a
n = self.n
m = self.m
ws = self.ws
E = np.exp(1.0)
log = np.log
if self.correction is None:
return (-m*n*ws*(psi/a)**n*(1 - log(psi/psir + 1)/
log(1.0 + 1000000.0/psir))*log((psi/a)**n + E)**(-m)/
(psi*((psi/a)**n + E)*log((psi/a)**n + E)) -
ws*log((psi/a)**n + E)**(-m)/(psir*(psi/psir + 1)*
log(1.0 + 1000000.0/psir)))
else:
return self.correction * (-m*n*ws*(psi/a)**n*
log((psi/a)**n + E)**(-m)/(psi*((psi/a)**n + E)*
log((psi/a)**n + E)))
[docs] def psi_and_w_for_plotting(self, **kwargs):
"""Suction and water content for plotting
Parameters
----------
npts : int, optional
Number of points to return. Default npts=100.
xmin, xmax : float, optional
Range of x (i.e. effective stress) values from which
to return points. Default xmin=1, xmax=1e6.
Returns
-------
x, y : 1d ndarray
`npts` permeability, and void ratio values between
`xmin` and `xmax`.
"""
npts = kwargs.get('npts', 100)
xmin, xmax = kwargs.get('xmin', 1.0), kwargs.get('xmax', 1e6)
x = np.logspace(np.log10(xmin), np.log10(xmax), npts)
y = self.w_from_psi(x)
return x, y
[docs] def k_from_psi(self, psi, **kwargs):
"""Relative permeability from suction
Parameters
---------_
psi : float
Soil suction. Can be a 1d array.
aev : float, optional
Air entry soil suction. Default aev=1.0
npts : int, optional
Numper of intervals to break integral into. Default npts=500.
Returns
-------
k : float
Relative permeability.
Examples
--------
>>> b = SWCC_FredlundAndXing1994(a=2.77, n=11.2, m=0.45, psir=300)
>>> b.k_from_psi(4)
0.0520...
Notes
-----
The permability relative to the saturated value is given by:
.. math:: k_r(\\psi) = \\left.
\\int_{\\ln(\\psi)}^{b}
{\\frac{\\theta(e^y) - \\theta(\\psi)}{e^y} \\theta^{\\prime}(e^y)\\,dy}
\\middle/
\\int_{\\ln(\\psi_{\\textrm{aev}})}^{b}
{\\frac{\\theta(e^y) - \\theta_s}{e^y} \\theta^{\\prime}(e^y)\\,dy}
\\right.
where, :math:`b=\\ln(10^6)`, :math:`\\theta(\\psi)` is the volumetric
water content at a given soil suction. :math:`\\psi_{\\textrm{aev}}`
is the air entry value of soil suction (must be positive for the
log integral to work). Each integral is performed by dividing the
integration inteval into N sections, evaluating the integrand at the
mid point of each inteval, then summing the areas of each section.
If you enter a single elment array you will get a scalar returned.
References
----------
.. [1] Fredlund, D.G., Anqing Xing, and Shangyan Huang.
"Predicting the Permeability Function for Unsaturated
Soils Using the Soil-Water Characteristic Curve.
Canadian Geotechnical Journal 31, no. 4 (1994): 533-46.
doi:10.1139/t94-062.
"""
aev = kwargs.get('aev', 1.0)
npts = kwargs.get('npts', 500)
psi = np.atleast_1d(psi)
b = np.log(1.0e6) #conceivably this could be the suction at residual
a1 = np.log(psi)
dy1 = (b - a1) / npts
y1 = a1[:, None] + dy1[:, None]*(np.arange(npts)[None, :] + 0.5)
ey1 = np.exp(y1)
f1 = (self.w_from_psi(ey1)-self.w_from_psi(psi[:,None])) * self.dw_dpsi(ey1) / ey1
numer = np.sum(f1, axis=1)
a2 = np.log(aev)
dy2 = (b - a2) / npts
y2 = a2 + dy2*(np.arange(npts) + 0.5)
ey2 = np.exp(y2)
f2 = (self.w_from_psi(ey2) - self.ws) * self.dw_dpsi(ey2) / ey2
# ws_numerical = self.w_from_psi(aev)
# f2 = (self.w_from_psi(ey2) - ws_numerical) * self.dw_dpsi(ey2) / ey2
denom = np.sum(f2)
k = (numer * dy1) / (denom * dy2)
if k.size==1:
return k[0]
else:
return k
return k
[docs]class SWCC_PhamAndFredlund2008(SWCC):
"""Soil water characteristic curve from Fredlund and Xing 2008
Be careful interpreting results when suction is less than 1.
At low suction values we expect wc approx equal to wsat-wr, but the
s1 * log10(psi) term gives large negative numbers for psi<<1,
whereas at psi=1 the term dissapears as expected.
Parameters
----------
a : float
Curve fitting parameter.
b : float
Curve fitting parameter.
wr : float
Residual gravimetric water content.
s1 : float
Initial slope of SWCC (i.e. Cc/Gs)
ws : float
Saturated water content. Default ws=1.0 i.e. basically a degree of
saturation.
psir : float, optional
Residual suction. Default psir=None, i.e. psir = (2.7*a) **(1/b).
correction : float, optional
Manual correction factor. Default correction=None i.e. correction
factor is caclulated.
References
----------
.. [1] Pham, Hung Q., and Delwyn G. Fredlund. "Equations for the
Entire Soil-Water Characteristic Curve of a Volume Change
Soil." Canadian Geotechnical Journal 45, no. 4
(April 1, 2008): 443-53.
doi:10.1139/T07-117.
Notes
-----
The equation for the soil water charcteristic curve is given by:
.. math:: w(\\psi) = C(\\psi)
\\left[{\\left({w_{sat} - S_l \\log(\\psi) -w_r}\\right)
\\frac{a}{\\psi^{b}+a} + w_r}\\right]
where the correction factor is given by:
.. math:: C(\\psi) = \\left[{1 - \\frac{\\ln\\left({1+\\psi/\\psi_r}\\right)}
{\\ln\\left({1+10^6/\\psi_r}\\right)}}\\right]
SWCC_PhamAndFredlund2008()
"""
def __init__(self, ws, a, b, wr, s1, psir=None, correction=None):
self.ws = ws
self.a = a
self.b = b
self.wr = wr
self.s1 = s1
if psir is None:
self.psir = (2.7*self.a)**(1/self.b)
else:
self.psir = psir
# self.psir = psir
self.correction = correction
[docs] def w_from_psi(self, psi, **kwargs):
"""water content from suction
Parameters
----------
psi : float
Suction
Returns
-------
w : float
Suction corresponding to psi
# Examples
# --------
# >>> a = SWCC_FredlundAndXing1994(a=3000, n=1.5, m=1, ws=60, correction=1)
# >>> a.w_from_psi(2000)
# 50.73...
# >>> b = SWCC_FredlundAndXing1994(a=100, n=2, m=4, correction=1)
# >>> b.w_from_psi(90)
# 0.395...
# >>> c = SWCC_FredlundAndXing1994(a=427, n=0.794, m=0.613, psir=3000)
# >>> c.w_from_psi(10000)
# 0.40...
"""
psir = self.psir
ws = self.ws
a = self.a
b = self.b
wr = self.wr
s1 = self.s1
if self.correction is None:
corr = (1.0 - np.log(1. + psi / psir) /
np.log(1.0 + 1.0e6 / psir) )
else:
corr = self.correction
return (corr * ((ws - s1 * np.log10(psi) - wr) *
a / (psi**b + a) + wr))
def _derive_dw_dpsi():
"""Derive the slope dw_dpsi with sympy
Returns
-------
dw_dpsi1 : sympy expression
Derivative including correction factor.
dw_dpsi2 : sympy expression
Derivative with correction factor equal to one.
"""
psi, a, n, m, psir, ws = sympy.symbols('psi, a, n, m, psir, ws')
psi, ws, a, b, wr, s1, psir = sympy.symbols('psi, ws, a, b, wr, s1, psir')
from sympy import log, exp
C1 = (1 - log(1 + psi / psir) /
log(1.0 + 1.0e6 / psir))
C2 = 1
l10 = sympy.symbols('l10')
w = C1 * ((ws - s1 * log(psi)/l10 - wr) * a / (psi**b + a) + wr)
dw_dpsi1 = sympy.diff(w, psi)
w = C2 * ((ws - s1 * log(psi)/l10 - wr) * a / (psi**b + a) + wr)
dw_dpsi2 = sympy.diff(w, psi)
return dw_dpsi1, dw_dpsi2
def _numerical_check_of_dw_dpsi(**kwargs):
"""Produce plot of dw_dpsi, and check against numerical slope
only needs to be close
Parameters
----------
ax : matplotlib.Axes object, optional
Axes to plot on.
"""
if 'ax' in kwargs:
ax=kwargs['ax']
else:
ax=plt.gca()
a = SWCC_PhamAndFredlund2008(a = 3.1e6,
b = 3.377,
wr = 0.128,
s1 = 0.115,
ws=0.262,
psir=3000)
b = SWCC_PhamAndFredlund2008(
a = 1.1e6,
b = 2.,
wr = 0.128,
s1 = 0.1,
ws=0.262,
correction=1)
text = ('ws={ws:g}, a={a:g}, b={b:g}, wr={wr:g}, s1={s1:g}, '
'psir={psir:g}, correction={correction}')
labels = [text.format(ws=v.ws, a=v.a, b=v.b, wr=v.wr,
s1=v.s1, psir=v.psir,
correction=v.correction) for v in [a, b]]
for v, label in zip([a, b], labels):
x, y = v.psi_and_w_for_plotting(npts=100)
dx = np.diff(x)
dy = np.diff(y)
xm = x[:-1] + dx
slope_n = dy / dx
slope_a = v.dw_dpsi(xm)
ax.plot(xm, slope_n, marker='o', ms=2, ls='.',
label="numerical: " + label)
ax.plot(xm, slope_a,
label="analytical: " + label)
plt.gca().set_xscale('log')
ax.set_xlabel('$\\psi$')
ax.set_ylabel('$dw/d\\psi$')
leg = ax.legend(loc=4)
leg.draggable()
# print(np.isclose(slope_a, slope_n))
# print(slope_a-slope_n)
plt.show()
[docs] def dw_dpsi(self, psi, **kwargs):
"""Slope of SWCC dw/dpsi
Parameters
----------
psi : float
Suction
Returns
-------
dw_dpsi : float
Suction corresponding to psi
# Examples
# --------
# >>> a = SWCC_FredlundAndXing1994(a=3000, n=1.5, m=1, ws=60, correction=1)
# >>> a.dw_dpsi(2000)
# -0.00536...
# >>> b = SWCC_FredlundAndXing1994(a=100, n=2, m=4, correction=1)
# >>> b.dw_dpsi(90)
# -0.00640...
# >>> c = SWCC_FredlundAndXing1994(a=427, n=0.794, m=0.613, psir=3000)
# >>> c.dw_dpsi(10000)
# -1.317...e-05
"""
psir = self.psir
ws = self.ws
a = self.a
b = self.b
wr = self.wr
s1 = self.s1
E = np.exp(1.0)
log = np.log
l10 = np.log(10.)
if self.correction is None:
return ((1 - log(psi/psir + 1)/log(1.0 + 1000000.0/psir))*
(-a*b*psi**b*(-wr + ws - s1*log(psi)/l10)/(psi*(a + psi**b)**2)
- a*s1/(l10*psi*(a + psi**b))) - (a*(-wr + ws -
s1*log(psi)/l10)/(a + psi**b) + wr)/(psir*(psi/psir + 1)*
log(1.0 + 1000000.0/psir)))
else:
return self.correction * (-a*b*psi**b*(-wr + ws -
s1*log(psi)/l10)/(psi*(a + psi**b)**2) -
a*s1/(l10*psi*(a + psi**b)))
[docs] def psi_and_w_for_plotting(self, **kwargs):
"""Suction and water content for plotting
Parameters
----------
npts : int, optional
Number of points to return. Default npts=100.
xmin, xmax : float, optional
Range of x (i.e. effective stress) values from which
to return points. Default xmin=1, xmax=1e6.
Returns
-------
x, y : 1d ndarray
`npts` permeability, and void ratio values between
`xmin` and `xmax`.
"""
npts = kwargs.get('npts', 100)
xmin, xmax = kwargs.get('xmin', 1.0), kwargs.get('xmax', 1e6)
x = np.logspace(np.log10(xmin), np.log10(xmax), npts)
y = self.w_from_psi(x)
return x, y
[docs] def k_from_psi(self, psi, **kwargs):
"""Relative permeability from suction
If k_from_psi returns a number greater than zero then try reducing
aev.
Parameters
---------
psi : float
Soil suction. Can be a 1d array.
aev : float, optional
Air entry soil suction. Default aev=0.001
npts : int, optional
Numper of intervals to break integral into. Default npts=500.
Returns
-------
k : float
Relative permeability.
# Examples
# --------
# >>> b = SWCC_FredlundAndXing1994(a=2.77, n=11.2, m=0.45, psir=300)
# >>> b.k_from_psi(4)
# 0.0520...
Notes
-----
The permability relative to the saturated value is given by:
.. math:: k_r(\\psi) = \\left.
\\int_{\\ln(\\psi)}^{b}
{\\frac{\\theta(e^y) - \\theta(\\psi)}{e^y} \\theta^{\\prime}(e^y)\\,dy}
\\middle/
\\int_{\\ln(\\psi_{\\textrm{aev}})}^{b}
{\\frac{\\theta(e^y) - \\theta_s}{e^y} \\theta^{\\prime}(e^y)\\,dy}
\\right.
where, :math:`b=\\ln(10^6)`, :math:`\\theta(\\psi)` is the volumetric
water content at a given soil suction. :math:`\\psi_{\\textrm{aev}}`
is the air entry value of soil suction (must be positive for the
log integral to work). Each integral is performed by dividing the
integration inteval into N sections, evaluating the integrand at the
mid point of each inteval, then summing the areas of each section.
If you enter a single element array you will get a scalar returned.
References
----------
.. [1] Fredlund, D.G., Anqing Xing, and Shangyan Huang.
"Predicting the Permeability Function for Unsaturated
Soils Using the Soil-Water Characteristic Curve.
Canadian Geotechnical Journal 31, no. 4 (1994): 533-46.
doi:10.1139/t94-062.
"""
aev = kwargs.get('aev', 0.001)
npts = kwargs.get('npts', 500)
psi = np.atleast_1d(psi)
b = np.log(1.0e6) #conceivably this could be the suction at residual
a1 = np.log(psi)
dy1 = (b - a1) / npts
y1 = a1[:, None] + dy1[:, None]*(np.arange(npts)[None, :] + 0.5)
ey1 = np.exp(y1)
f1 = (self.w_from_psi(ey1)-self.w_from_psi(psi[:,None])) * self.dw_dpsi(ey1) / ey1
numer = np.sum(f1, axis=1)
a2 = np.log(aev)
dy2 = (b - a2) / npts
y2 = a2 + dy2*(np.arange(npts) + 0.5)
ey2 = np.exp(y2)
# f2 = (self.w_from_psi(ey2) - self.ws) * self.dw_dpsi(ey2) / ey2
ws_numerical = self.w_from_psi(aev)
f2 = (self.w_from_psi(ey2) - ws_numerical) * self.dw_dpsi(ey2) / ey2
denom = np.sum(f2)
k = (numer*dy1) / (denom*dy2)
if k.size==1:
return k[0]
else:
return k
return k
[docs]def kwrel_from_discrete_swcc(psi, psi_swcc, vw_swcc):
"""Relative permeability from integrating discrete soil water
characteristic curve.
Parameters
----------
psi: float
Suction values to calculate relative permeability at. Suction is
positve.
psi_swcc : 1d array of float
Suction values defining soil water charteristic curve.
vw_swcc : 1d array of float
Volumetric water content corresponding to psi_swcc
Returns
-------
krel : 1d array of float
relative permeability values corresponding to psi.
Notes
-----
This integrates the whole SWCC. Initial slope of SWCC is non-zero then
peremability reduction with suction will commence straight from first
value of psi_swcc.
Uses _[1] method but does it assuming taht you already have all
the SWCC points (_[1] can calculate volumetric water content from suction).
Main differnce is that in _[1] vw and dvw/dpsi at the midpoint are
calculated analytically, whereas here midpoint value is half the
segment endpoint values, and slope is approx value at segmetn endpoint.
Will return scalar if scalar or single elemtn array is input.
If you get some sort of index error then probably your pis input is
beyond your data range.
Examples
--------
>>> b = SWCC_FredlundAndXing1994(a=2.77, n=11.2, m=0.45, psir=300)
>>> x = np.logspace(-3,6,500)
>>> y = b.w_from_psi(x)
>>> kwrel_from_discrete_swcc(4, x, y)
0.0550...
References
----------
.. [1] Fredlund, D.G., Anqing Xing, and Shangyan Huang.
"Predicting the Permeability Function for Unsaturated
Soils Using the Soil-Water Characteristic Curve.
Canadian Geotechnical Journal 31, no. 4 (1994): 533-46.
doi:10.1139/t94-062.
"""
dlogx = np.log10(psi_swcc[1:]) - np.log10(psi_swcc[:-1]) #interval in log10-space
xbar = psi_swcc[:-1]*np.power(10, 0.5*dlogx) # x value at midpoint
ybar = 0.5 * (vw_swcc[:-1] + vw_swcc[1:]) # y value at interval
logslope = (vw_swcc[1:] - vw_swcc[:-1]) / dlogx #interval slope in log10-space
slopebar = logslope/np.log(10)/xbar #slope at interval midpoint in normal space
idx = np.searchsorted(psi_swcc, psi, side="right")-1
# if index error then chances are
#TODO: limit what values of idx, to be within range
f1 = dlogx[:,None] * (ybar[:,None] - ybar[None,idx]) / xbar[:,None] * slopebar[:,None]
#note in f1 each row is an interval, each column is a psi value
row, col = np.indices(f1.shape)
f1[row<idx] = 0.0 #ignore values before psi
numer = np.sum(f1, axis=0)
denom = np.sum(dlogx[:]*(ybar[:] - ybar[0])/xbar[:] * slopebar[:])
krel = numer / denom
if krel.size==1:
return krel[0]
else:
return krel
[docs]def karel_air_from_saturation(Sr, qfit=0.5):
"""Relative air peremability (w.r.t. dry_ka)
krel = (1-Sr)**0.5*(1-Sr**(1 / qfit))**(2*qfit)
Parameters
----------
Sr : 1d array of float
Degree of saturation at which to calc relative permeability of air.
qfit : float, optional
Fitting parameter. Generally between between 0 and 1.
Default qfit=1
References
----------
.. [1] Ba-Te, B., Limin Zhang, and Delwyn G. Fredlund. "A General
Air-Phase Permeability Function for Airflow through Unsaturated
Soils." In Proceedings of Geofrontiers 2005 Cngress,
2961-85. Austin, Tx: ASCE, 2005. doi:10.1061/40787(166)29.
"""
return (1 - Sr)**0.5*(1 - Sr**(1 / qfit))**(2 * qfit)
if __name__ == "__main__":
import nose
nose.runmodule(argv=['nose', '--verbosity=3', '--with-doctest', '--doctest-options=+ELLIPSIS'])
if 0:
a = SWCC_FredlundAndXing1994(a=3000, n=1.5, m=1, ws=60, correction=1)
b = SWCC_FredlundAndXing1994(a=200, n=2, m=1, ws=40, correction=1)
c = SWCC_FredlundAndXing1994(a=10, n=4, m=1, ws=30, correction=1)
[v.plot_model() for v in [a, b, c]]
plt.gca().set_xscale('log')
plt.gca().set_ylim([0,70])
plt.gca().grid(which='major', linestyle='-')
plt.gca().grid(which='minor', linestyle='--')
plt.show()
if 0:
b = SWCC_FredlundAndXing1994(a=100, n=2, m=4)
b = SWCC_FredlundAndXing1994(a=427, n=0.794, m=0.613, psir=3000)
b.plot_model()
plt.gca().set_xscale('log')
# plt.gca().set_ylim([0,70])
plt.gca().grid(which='major', linestyle='-')
plt.gca().grid(which='minor', linestyle='--')
plt.show()
if 0:
a, b=SWCC_FredlundAndXing1994._derive_dw_dpsi()
print(a)
print(b)
if 0:
SWCC_FredlundAndXing1994._numerical_check_of_dw_dpsi()
# b = SWCC_FredlundAndXing1994(a=427, n=0.794, m=0.613, psir=3000)
# b = SWCC_FredlundAndXing1994(a=200, n=2, m=1, ws=40, correction=1)
# x, y = b.psi_and_w_for_plotting(npts=10000)
# dx = np.diff(x)
# dy = np.diff(y)
# xm = x[:-1] + dx
# slope_n = dy / dx
# slope_a = b.dw_dpsi(xm)
# plt.plot(xm, slope_n, marker='o', ms=2, ls='.')
# plt.plot(xm, slope_a, color='red')
# plt.gca().set_xscale('log')
# print(np.isclose(slope_a, slope_n))
# print(slope_a-slope_n)
# plt.show()
if 0:
# b = SWCC_FredlundAndXing1994(a=427, n=0.794, m=0.613, psir=3000)
# b = SWCC_FredlundAndXing1994(a=8.34, n=9.9, m=0.44, psir=30)
# b = SWCC_FredlundAndXing1994(a=6.01, n=11.86, m=0.36, psir=30)
b = SWCC_FredlundAndXing1994(a=2.77, n=11.2, m=0.45, psir=300)
# b = SWCC_FredlundAndXing1994(a=2.7, n=2.05, m=0.36, psir=100)
# b = SWCC_FredlundAndXing1994(a=200, n=2, m=1, ws=40, correction=1)
x, y = b.psi_and_w_for_plotting(npts=100)
k = b.k_from_psi(x, aev=1)
fig = plt.figure()
ax = fig.add_subplot('211')
ax.plot(x, y)
ax.set_xlabel('$psi$')
ax.set_ylabel('w')
ax.set_xscale('log')
ax.grid(which='major', linestyle='-')
ax.grid(which='minor', linestyle='--')
ax = fig.add_subplot('212', sharex=ax)
ax.plot(x, k)
ax.set_xlabel('$psi$')
ax.set_ylabel('$k/k_{sat}$')
ax.set_yscale('log')
ax.set_ylim((0.001, 1))
ax.set_ylim((0.001, 10))
ax.grid(which='major', linestyle='-')
ax.grid(which='minor', linestyle='--')
plt.show()
if 0:
a, b=SWCC_PhamAndFredlund2008._derive_dw_dpsi()
print(a)
print()
print(b)
if 0:
SWCC_PhamAndFredlund2008._numerical_check_of_dw_dpsi()
if 0:
b = SWCC_PhamAndFredlund2008(a = 50000.,
b = 2.,
wr = 0.12,
s1 = 0.04,
ws=0.5,
correction=None)
# b = SWCC_PhamAndFredlund2008(a = 25000.,
# b = 3.,
# wr = 0.12,
# s1 = 0.04,
# ws=0.5,
# correction=None)
# b = SWCC_PhamAndFredlund2008(a = 50000.,
# b = 1.5,
# wr = 0.12,
# s1 = 0.04,
# ws=0.5,
# correction=None)
x, y = b.psi_and_w_for_plotting(npts=100)
k = b.k_from_psi(x, aev=1)
fig = plt.figure()
ax = fig.add_subplot('211')
ax.plot(x, y)
ax.set_xlabel('$psi$')
ax.set_ylabel('w')
ax.set_xscale('log')
ax.grid(which='major', linestyle='-')
ax.grid(which='minor', linestyle='--')
ax = fig.add_subplot('212', sharex=ax)
ax.plot(x, k)
ax.set_xlabel('$psi$')
ax.set_ylabel('$k/k_{sat}$')
ax.set_yscale('log')
ax.set_ylim((0.001, 1))
ax.set_ylim((0.001, 10))
ax.grid(which='major', linestyle='-')
ax.grid(which='minor', linestyle='--')
plt.show()
if 1:
b = SWCC_FredlundAndXing1994(a=2.77, n=11.2, m=0.45, psir=300)
x = np.logspace(-3,6, 500)
y = b.w_from_psi(x)
x2 = x[0:-1]*10**(0.5*(np.log10(x[1]) - np.log10(x[0])))
# print(x)
# print(x2)
k_obj = b.k_from_psi(x)
np.logspace
krel = kwrel_from_discrete_swcc(x2[:-2], x, y)
# krel = kwrel_from_discrete_swcc(x[:-1], x, y)
fig, ax = plt.subplots()
ax.plot(x, k_obj, label="expected")
ax.plot(x2[:-2], krel, label="numerical", ls="None", marker='o')
# ax.plot(x[:-1], krel, label="numerical", ls="None", marker='o')
ax.set_xscale('log')
ax.set_xlabel('psi')
ax.set_ylabel('krel')
plt.show()