Source code for geotecha.constitutive_models.velocity_hydraulic_gradient

# geotecha - A software suite for geotechncial engineering
# Copyright (C) 2018  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.

"""Relationships between velocity and hydraulic gradient"""


from __future__ import print_function, division

import numpy as np
from numpy import (isscalar, asarray)
import matplotlib.pyplot as plt
import matplotlib
import scipy.optimize
from collections import OrderedDict

from geotecha.mathematics import root_finding

[docs]class OneDimensionalFlowRelationship(object): """Base class for defining velocity vs hydraulic gradient relationships Attributes ---------- non_Darcy: True A class variable indicating if model has a non-Darcian flow relationship. Can be overridden in subclasses. Basically the Darcian flow equations are simple and it might be simpler to bypass the use of a OneDimensionalFlowRelationship. """ non_Darcy = True
[docs] def v_from_i(self, hyd_grad, **kwargs): """Velocity from hydraulic gradient""" #Note this should work for +ve and -ve hydraulic gradients raise NotImplementedError("v_from_i must be implemented.")
[docs] def i_from_v(self, velocity, **kwargs): """Hydraulic gradient from velocity """ #Note this should work for +ve and -ve velocities raise NotImplementedError("i_from_v must be implemented.")
[docs] def dv_di(self, hyd_grad, **kwargs): """Slope of velocity vs hydraulic gradient relationship""" #Note this should work for +ve and -ve hydraulic gradients raise NotImplementedError("dv_di must be implemented.")
[docs] def vdrain_strain_rate(self, eta, head, **kwargs): """Vertical drain strain rate as based on the eta method""" raise NotImplementedError("vdrain_strain_rate must be implemented.")
[docs] def v_and_i_for_plotting(self, **kwargs): """Velocity and hydraulic gradient that that plot the relationship""" # should return a tuple of x and y values # x-values are hydraulic gradient, y-values are velocity. raise NotImplementedError("v_and_i_for_plotting must be implemented.")
[docs] def plot_model(self, **kwargs): """Plot the velocity-hydraulic gradient relationship""" ax = kwargs.pop('ax', plt.gca()) x, y = self.v_and_i_for_plotting(**kwargs) # label = "k={:g}".format(self.k) label=None ax.plot(x, y, label=label) ax.set_xlabel('Hydraulic gradient, i') ax.set_ylabel('Flow velocity, v') return
def __str__(self): """Need to define self._attribute_list, a lsit of attribute names""" #self._attribute_list is a list str containing names of attributes attribute_list = getattr(self, '_attribute_list', []) attribute_string = "\n".join(["{} = {:g}".format(v, getattr(self, v,"not defined.")) for v in attribute_list]) return attribute_string
[docs]class DarcyFlowModel(OneDimensionalFlowRelationship): """Darcian flow model Parameters ---------- k : float, optional Darcian permeability. Default k=1. Notes ----- Darcian flow is described by: .. math:: v = ki """ non_Darcy = False def __init__(self, k=1.0): self._attribute_list = ['k'] self.k = k
[docs] def v_from_i(self, hyd_grad, **kwargs): """Velocity from hydraulic gradient Parameters ---------- hyd_grad : float Hydraulic gradient. **kwargs : any Any keyword arguments are ignored. Returns ------- v : float Flow velocity. Examples -------- >>> a = DarcyFlowModel(k=3) >>> a.v_from_i(8.0) 24.0 >>> a.v_from_i(-8.0) -24.0 >>> a.v_from_i(np.array([5.0, 8.0])) array([15., 24.]) """ return self.k * hyd_grad
[docs] def i_from_v(self, velocity, **kwargs): """Hydraulic gradient from velocity Parameters ---------- v : float Flow velocity. **kwargs : any Any keyword arguments are ignored. Returns ------- hyd_grad : float Hydraulic gradient. Examples -------- >>> a = DarcyFlowModel(k=3) >>> a.i_from_v(24.0) 8.0 >>> a.i_from_v(-24.0) -8.0 >>> a.i_from_v(np.array([15, 24])) array([5., 8.]) """ return velocity / self.k
[docs] def dv_di(self, hyd_grad, **kwargs): """Slope of velocity vs hydraulic gradient relationship Parameters ---------- hyd_grad : float Hydraulic gradient. **kwargs : any Any keyword arguments are ignored. Returns ------- slope : float slope of velocity-hydraulic gradient relationship at `hyd_grad`. Examples -------- >>> a = DarcyFlowModel(k=3) >>> a.dv_di(8.0) 3.0 >>> a.dv_di(-8.0) -3.0 >>> a.dv_di(np.array([5.0, 8.0])) array([3., 3.]) """ return np.ones_like(hyd_grad, dtype=float) * self.k * np.sign(hyd_grad)
[docs] def vdrain_strain_rate(self, eta, head, **kwargs): """Vertical drain strain rate as based on the eta method"" [strain rate] = head * self.k * eta Parameters ---------- eta : float Value of vertical drain geometry, peremability parameter. This value should be calculated based on Darcy's law (see geotecha.consolidation.smearzones.drain_eta) head : float Hydraulic head driving the flow. For vertical drains this is usually the difference between the average head in the soil and the head in the drain. **kwargs : any Any additional keyword arguments are ignored. Returns ------- strain_rate : float Strain rate based on eta method. See Also -------- geotecha.consolidation.smearzones : Functions to determine eta. Examples -------- >>> a = DarcyFlowModel(k=3) >>> a.vdrain_strain_rate(eta=2.5, head=4) 30.0 """ return head * self.k * eta
[docs] def v_and_i_for_plotting(self, **kwargs): """Velocity and hydraulic gradient that plot the relationship Parameters ---------- npts : int, optional Number of points to return. Default npts=100. xmin, xmax : float, optional Range of x (i.e. hydraulic gradient) values from which to return points. Default xmin=0, xmax=50. 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', 0), kwargs.get('xmax', 50) x = np.linspace(xmin, xmax, npts) y = self.v_from_i(x) return x, y
[docs]class HansboNonDarcianFlowModel(OneDimensionalFlowRelationship): """Hansbo non-darcian flow model Various combinations of parameters can be used to specify the model. Basically three parameters are required, one of which must be kstar or klinear. If n==1 or i0=0, or iL=0 then the model will reduce to Darcy flow model with peremability equal to klinear (or kstar if klinear is not defined). Parameters ---------- klinear : float, optional This is the slope of the linear portion of the v-i relationship for i>iL. klinear = kstar * n * iL**(n-1). Default klinear=None. `klinear` and `kstar` cannot both be None, you must use one or the other. kstar : float, optional Permability coefficient in the power law, v=kstar*i**n, flow section, i<iL. Default kstar=None. `klinear` and `kstar` cannot both be None. n : float, optional Exponent for non-Darcian portion of flow. n must be greater than or equal to 1. Default n=None. i0 : float, optional x-axis intercept of the linear portion of the flow law. Default i0=None. i0 must be greater or equal to zero. iL : float, optional Limiting hydraulic gradient, beyond which v-i relationship is linear. iL = i0 * n / (n -1). Default iL=None. Notes ----- Hansbo's Non-darcian flow relationship is defined by: .. math:: v=\\left\\{\\begin{array}{lr} k^{\\ast}i^{n} & i<i_{L} \\\\ k_{\\rm{linear}} \\left({i-i_0}\\right) & i\\geq i_{L} \\end{array}\\right. where, .. math:: k_{\\rm{linear}} = k^{\\ast}ni_L^\\left(n-1\\right) .. math:: i_L=\\frac{i_0 n}{\\left(n-1\\right)} Examples -------- These examples show the various ways to define the model: >>> p = dict(kstar=2, n=1.3, iL=16.2402611294, i0=3.7477525683, klinear=6) >>> a = HansboNonDarcianFlowModel(kstar=p['kstar'], n=p['n'], iL=p['iL']) >>> print(a) kstar = 2 n = 1.3 iL = 16.240... i0 = 3.747... klinear = 6 >>> a = HansboNonDarcianFlowModel(kstar=p['kstar'], n=p['n'], i0=p['i0']) >>> print(a) kstar = 2 n = 1.3 iL = 16.240... i0 = 3.747... klinear = 6 >>> a = HansboNonDarcianFlowModel(kstar=p['kstar'], iL=p['iL'], i0=p['i0']) >>> print(a) kstar = 2 n = 1.3 iL = 16.240... i0 = 3.747... klinear = 6 >>> a = HansboNonDarcianFlowModel(kstar=p['kstar'], iL=p['iL'], klinear=p['klinear']) >>> print(a) kstar = 2 n = 1.3 iL = 16.240... i0 = 3.747... >>> a = HansboNonDarcianFlowModel(kstar=p['kstar'], i0=p['i0'], klinear=p['klinear']) >>> print(a) kstar = 2 n = 1.3 iL = 16.240... i0 = 3.747... klinear = 6 >>> a = HansboNonDarcianFlowModel(n=p['n'], iL=p['iL'], klinear=p['klinear']) >>> print(a) kstar = 2 n = 1.3 iL = 16.240... i0 = 3.747... klinear = 6 >>> a = HansboNonDarcianFlowModel(n=p['n'], i0=p['i0'], klinear=p['klinear']) >>> print(a) kstar = 2 n = 1.3 iL = 16.240... i0 = 3.747... klinear = 6 >>> a = HansboNonDarcianFlowModel(iL=p['iL'], i0=p['i0'], klinear=p['klinear']) >>> print(a) kstar = 2 n = 1.3 iL = 16.240... i0 = 3.747... klinear = 6 If the behaviour is not as you expect when i0=0, iL=0, n==1 then use very small values of i0 or iL, n just greater than 1. """ non_Darcy = True def __init__(self, klinear=None, kstar=None, n=None, i0=None, iL=None): d = OrderedDict() d['kstar']=kstar d['n']=n d['iL']=iL d['i0']=i0 d['klinear'] = klinear self._attribute_list = list(d.keys()) params = [v for v in d if not d[v] is None] # self._attribute_list = ('kstar', 'n', 'iL', 'i0', 'klinear') # params = [v for v in self._attribute_list if not eval(v) is None] if len(params) != 3 or (kstar is None and klinear is None): raise TypeError("Wrong number of parameters to define model.'" "Need three only of ['kstar', 'n', 'iL', " "i0', 'klinear'] at least one of which is " "kstar or klinear. You have {}.".format(params)) if len([v for v in (kstar, n, iL) if not v is None]) == 3: if n == 1 or iL == 0: self.kstar = kstar self.n = 1 self.iL = 0 self.i0 = 0 self.k_linear = kstar else: self.kstar = kstar self.n = n self.iL = iL self.i0 = (n - 1) / n * iL self.klinear = kstar * n * iL**(n - 1) elif len([v for v in (kstar, n, i0) if not v is None]) == 3: if n == 1 or i0 == 0: self.kstar = kstar self.n = 1 self.iL = 0 self.i0 = 0 self.klinear = kstar else: self.kstar = kstar self.n = n self.iL = i0 * n / (n - 1) self.i0 = i0 self.klinear = kstar * n * self.iL**(n - 1) elif len([v for v in (kstar, n, klinear) if not v is None]) == 3: if n == 1: self.kstar = klinear self.n = 1 self.iL = 0 self.i0 = 0 self.klinear = klinear else: self.kstar = kstar self.n = n self.iL = (klinear / (kstar * n)) ** (1/(n - 1)) self.i0 = self.iL * (n - 1) / n self.klinear = klinear elif len([v for v in (kstar, iL, i0) if not v is None]) == 3: if iL == i0: self.kstar = kstar self.n = 1 self.iL = 0 self.i0 = 0 self.klinear = kstar else: self.kstar = kstar self.n = iL / (iL - i0) self.iL = iL self.i0 = i0 self.klinear = kstar * self.n * iL **(self.n - 1) elif len([v for v in (kstar, iL, klinear) if not v is None]) == 3: if iL == 0: self.kstar = klinear self.n = 1 self.iL = 0 self.i0 = 0 self.klinear = klinear else: self.kstar = kstar def fn(_n, _klinear, _kstar, _iL): return np.log(_klinear/_kstar/_n) / np.log(_iL) + 1 # self.n = scipy.optimize.fixed_point(fn, # 1.0001, # args=(klinear, kstar, iL)) # note as at 20151106 scipy.optimize.fixed_point may not # converge for this function because convergence acceleration # is used. Use my modified version instead: self.n = root_finding.fixed_point_no_accel(fn, 1.00001, args=(klinear, kstar, iL)) # self.n = klinear / kstar / iL**(n - 1) self.iL = iL self.i0 = iL * (self.n - 1) / self.n self.klinear = klinear elif len([v for v in (kstar, i0, klinear) if not v is None]) == 3: if i0 == 0: self.kstar = klinear self.n = 1 self.iL = 0 self.i0 = 0 self.klinear = klinear else: self.kstar = kstar def fn(_n, _klinear, _kstar, _i0): return np.log(_klinear/_kstar/_n) / np.log((_i0 *_n/(_n - 1))) + 1 # self.n = scipy.optimize.fixed_point(fn, # 1.00001, # args=(klinear, kstar, i0)) # note as at 20151106 scipy.optimize.fixed_point may not # converge for this function because convergence acceleration # is used. Use my modified version instead: self.n = root_finding.fixed_point_no_accel(fn, 1.00001, args=(klinear, kstar, i0)) self.iL = i0 * self.n / (self.n - 1) self.i0 = i0 self.klinear = klinear #elif len([v for v in (n, iL, i0) if not v is None]) == 3: # #need kstar or klinear elif len([v for v in (n, iL, klinear) if not v is None]) == 3: if n == 1 or iL == 0: self.n = 1 self.iL = 0 self.i0 = 0 self.klinear = klinear self.kstar = klinear else: self.n = n self.iL = iL self.i0 = iL * (n - 1) / n self.klinear = klinear self.kstar = klinear / (n * iL**(n - 1)) elif len([v for v in (n, i0, klinear) if not v is None]) == 3: if n == 1 or i0 == 0: self.n = 1 self.iL = 0 self.i0 = 0 self.klinear = klinear self.kstar = klinear else: self.n = n self.iL = i0 * n / (n - 1) self.i0 = i0 self.klinear = klinear self.kstar = klinear / (self.n * self.iL**(self.n - 1)) elif len([v for v in (iL, i0, klinear) if not v is None]) == 3: self.n = iL / (iL - i0) self.iL = iL self.i0 = i0 self.klinear = klinear self.kstar = klinear / (self.n * iL**(self.n - 1)) if self.iL < self.i0: raise ValueError('iL must be greater than i0.') if self.iL <= self.i0 and self.i0 > 0: raise ValueError('iL must be greater than i0.') if self.n < 1: raise ValueError('n must be greater than 0.') self.vL = self.klinear * (self.iL - self.i0)
[docs] def v_from_i(self, hyd_grad, **kwargs): """Velocity from hydraulic gradient Parameters ---------- hyd_grad : float Hydraulic gradient. **kwargs : any Any keyword arguments are ignored. Returns ------- v : float Flow velocity. Examples -------- >>> a = HansboNonDarcianFlowModel(kstar=2, n=1.3, iL=16.2402611294) >>> a.v_from_i(8.0) 29.857... >>> a.v_from_i(-8.0) -29.857... >>> a.v_from_i(np.array([8.0, 20.0])) array([29.857..., 97.513...]) """ abs_hyd_grad = abs(hyd_grad) return np.where(abs_hyd_grad >= self.iL, self.klinear * (abs_hyd_grad - self.i0), self.kstar * abs_hyd_grad**self.n)*np.sign(hyd_grad)*1
[docs] def i_from_v(self, velocity, **kwargs): """Hydraulic gradient from velocity Parameters ---------- v : float Flow velocity. **kwargs : any Any keyword arguments are ignored. Returns ------- velocity : float Flow velocity. Examples -------- >>> a = HansboNonDarcianFlowModel(kstar=2, n=1.3, iL=16.2402611294) >>> a.i_from_v(29.858) 8.0... >>> a.i_from_v(-29.858) -8.0... >>> a.i_from_v(np.array([29.858, 97.514])) array([ 8.0..., 20.0...]) """ absvelocity = np.abs(velocity) return np.where(absvelocity >= self.vL, absvelocity / self.klinear + self.i0, (absvelocity / self.kstar)**(1/self.n)) * np.sign(velocity)*1
[docs] def dv_di(self, hyd_grad, **kwargs): """Slope of velocity vs hydraulic gradient relationship Parameters ---------- hyd_grad : float Hydraulic gradient. **kwargs : any Any keyword arguments are ignored. Returns ------- slope : float slope of velocity-hydraulic gradient relationship at `hyd_grad`. Examples -------- >>> a = HansboNonDarcianFlowModel(kstar=2, n=1.3, iL=16.2402611294) >>> a.dv_di(8.0) 4.851... >>> a.dv_di(-8.0) -4.851... >>> a.dv_di(np.array([8.0, 20.0])) array([4.851..., 6...]) """ abs_hyd_grad = abs(hyd_grad) return np.where(abs_hyd_grad >= self.iL, self.klinear, self.kstar * self.n * abs_hyd_grad**(self.n - 1)) * np.sign(hyd_grad)*1
[docs] def vdrain_strain_rate(self, eta, head, **kwargs): """Vertical drain strain rate as based on the eta method"" [strain rate] = head**self.nflow * self.klinear * gamw**(nflow - 1) * eta Note that `vdrain_strain_rate` only uses the exponential portion of the Non-Darcian flow relationship. If hydraulic gradients are greater than the limiting value iL then the flow rates will be overestimated. Parameters ---------- eta : float Value of vertical drain geometry, peremability parameter. This value should be calculated based on Hansbo's non-Darcian flow model (see geotecha.consolidation.smearzones.non_darcy_drain_eta) head : float Hydraulic head driving the flow. For vertical drains this is usually the difference between the average head in the soil and the head in the drain. gamw : float, optional Unit weight of water. Note that this gamw must be consistent with the value used to determine eta. Default gamw=10. **kwargs : any Any additional keyword arguments, other than 'gamw', are ignored. Returns ------- strain_rate : float Strain rate based on eta method. See Also -------- geotecha.consolidation.smearzones : Functions to determine eta. Examples -------- >>> a = HansboNonDarcianFlowModel(klinear=2, n=1.3, iL=16.2402611294) >>> a.vdrain_strain_rate(eta=0.1, head=4, gamw=10) 2.419... """ gamw = kwargs.get('gamw', 10) return head**self.n * self.klinear * gamw**(self.n - 1) * eta
[docs] def v_and_i_for_plotting(self, **kwargs): """Velocity and hydraulic gradient that plot the relationship Parameters ---------- npts : int, optional Number of points to return. Default npts=100. xmin, xmax : float, optional Range of x (i.e. hydraulic gradient) values from which to return points. Default xmin=0, xmax=50. 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', 0), kwargs.get('xmax', 50) x = np.linspace(xmin, xmax, npts) y = self.v_from_i(x) return x, y
if __name__ == "__main__": import nose nose.runmodule(argv=['nose', '--verbosity=3', '--with-doctest', '--doctest-options=+ELLIPSIS']) pass if 1: p = dict(kstar=2, n=1.3, iL=16.2402611294, i0=3.7477525683, klinear=6) a = HansboNonDarcianFlowModel(kstar=p['kstar'], iL=p['iL'], klinear=p['klinear']) print(a) # a.plot_model() # plt.show() b = HansboNonDarcianFlowModel(kstar=p['kstar'], i0=p['i0'], klinear=p['klinear']) print(b)