Source code for geotecha.constitutive_models.void_ratio_stress

# 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 void-ratio and effective stress.

"""


from __future__ import print_function, division
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import functools
from scipy.optimize import fixed_point
from scipy.integrate import odeint
import geotecha.piecewise.piecewise_linear_1d as pwise

[docs]class OneDimensionalVoidRatioEffectiveStress(object): """Base class for defining 1D void ratio-effective stress relationships"""
[docs] def e_from_stress(self, estress, **kwargs): """Void ratio from effective stress""" raise NotImplementedError("e_from_stress must be implemented")
[docs] def stress_from_e(self, e, **kwargs): """Effective stress from void ratio""" raise NotImplementedError("stress_from_e must be implemented")
[docs] def e_and_stress_for_plotting(self, **kwargs): """Void ratio and stress values that plot the method""" # should return a tuple of x and y values raise NotImplementedError("e_and_stress_for_plotting must be " "implemented")
[docs] def av_from_stress(self, estress, **kwargs): """Slope of void ratio from effective stress""" raise NotImplementedError("av_from_stress must be implemented")
[docs] def plot_model(self, **kwargs): """plot the void ratio-stress points""" ax = kwargs.pop('ax', plt.gca()) x, y = self.e_and_stress_for_plotting(**kwargs) ax.plot(x, y) return
[docs]class AvSoilModel(OneDimensionalVoidRatioEffectiveStress): """Linear void ratio-effective stress relationship Parameters ---------- av : float Slope of compression line. siga, ea : float Effective stress and void ratio specifying point on av line. """ def __init__(self, av, siga, ea): self.av = av self.siga = siga self.ea = ea
[docs] def e_from_stress(self, estress, **kwargs): """Void ratio from effective stress Parameters ---------- estress : float Current effective stress. Returns ------- e : float Void ratio corresponding to current stress state. Examples -------- >>> a = AvSoilModel(av=1.5, siga=19, ea=4) >>> a.e_from_stress(20) 2.5 Array inputs: >>> a = AvSoilModel(av=1.5, siga=19, ea=4) >>> a.e_from_stress(np.array([20, 21])) array([2.5, 1. ]) """ return self.ea - self.av * (estress - self.siga)
[docs] def stress_from_e(self, e, **kwargs): """Effective stress from void ratio Parameters ---------- e : float Current void ratio. Returns ------- estress : float Effective stress corresponding to current void ratio Examples -------- >>> a = AvSoilModel(av=1.5, siga=19, ea=4) >>> a.stress_from_e(1) 21.0 Array inputs: >>> a = AvSoilModel(av=1.5, siga=19, ea=4) >>> a.stress_from_e(np.array([1, 2.5])) array([21., 20.]) """ estress = self.siga + (self.ea - e) / self.av return estress
[docs] def e_and_stress_for_plotting(self, **kwargs): """Void ratio and stress values that plot the model 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=100 Returns ------- x, y : 1d ndarray `npts` stress, and void ratio values between `xmin` and `xmax`. """ npts = kwargs.get('npts', 100) xmin, xmax = kwargs.get('xmin', 1.0), kwargs.get('xmax', 100) x = np.linspace(xmin, xmax, npts) y = self.e_from_stress(x) return x, y
[docs] def av_from_stress(self, *args, **kwargs): """Slope of void ratio from effective stress""" return self.av
[docs]class CcCrSoilModel(OneDimensionalVoidRatioEffectiveStress): """Semi-log void ratio-effective stress realationship Parameters ---------- Cc : float Compressibility index, slope of e-log(sig) line. Cr : float Recompression index, slope of e-log(sig) line. siga, ea : float Point on compression line fixing it in effective stress-void ratio space. """ def __init__(self, Cc, Cr, siga, ea): self.Cc = Cc self.Cr = Cr self.siga = siga self.ea = ea
[docs] def e_from_stress(self, estress, **kwargs): """Void ratio from effective stress Parameters ---------- estress : float Current effective stress. pstress : float, optional Preconsolidation stress. Default pstress=estress i.e. normally consolidated. Returns ------- e : float Void ratio corresponding to current stress state. Examples -------- On recompression line: >>> a = CcCrSoilModel(Cc=3, Cr=0.5, siga=10, ea=5) >>> a.e_from_stress(estress=40, pstress=50) 2.95154... On compression line: >>> a = CcCrSoilModel(Cc=3, Cr=0.5, siga=10, ea=5) >>> a.e_from_stress(estress=60, pstress=50) 2.66554... Array inputs: >>> a = CcCrSoilModel(Cc=3, Cr=0.5, siga=10, ea=5) >>> a.e_from_stress(estress=np.array([40, 60]), ... pstress=np.array([50, 55])) array([2.95154499, 2.66554625]) Normally consolidated (pstress not specified): >>> a = CcCrSoilModel(Cc=3.0, Cr=0.5, siga=10, ea=5) >>> a.e_from_stress(estress=10) 5.0 """ pstress = kwargs.get('pstress', estress) ea = self.ea Cc = self.Cc Cr = self.Cr siga = self.siga max_past = np.maximum(pstress, estress) # void ratio at preconsolidation pressure e = ea - Cc * np.log10(max_past / siga) # void ratio at current effetive stress e += Cr * np.log10(max_past / estress) return e
[docs] def stress_from_e(self, e, **kwargs): """Effective stress from void ratio Parameters ---------- e : float Current void ratio. pstress : float, optional Preconsolidation stress. Default pstress=estress i.e. normally consolidated. Returns ------- estress : float Effective stress corresponding to current void ratio. Examples -------- On recompression line: >>> a = CcCrSoilModel(Cc=3.0, Cr=0.5, siga=10, ea=5) >>> a.stress_from_e(e=2.95154499, pstress=50) 40... On compression line: >>> a = CcCrSoilModel(Cc=3, Cr=0.5, siga=10, ea=5) >>> a.stress_from_e(e=2.66554625, pstress=50) 59.999... Array inputs: >>> a = CcCrSoilModel(Cc=3, Cr=0.5, siga=10, ea=5) >>> a.stress_from_e(e=np.array([2.95154499, 2.66554625]), pstress=50) array([40.0..., 59.99...]) Normally consolidated: >>> a = CcCrSoilModel(Cc=3.0, Cr=0.5, siga=10, ea=5) >>> a.stress_from_e(e=5) 10.0 """ pstress = kwargs.get('pstress', None) ea = self.ea Cc = self.Cc Cr = self.Cr siga = self.siga fact = 2.3025850929940459 # 10**(x)==exp(fact*x) if pstress is None: # Normally consolidated estress = siga * np.exp(fact * (ea - e) / Cc) return estress # void ratio at preconsolidation pressure ep = ea - Cc * np.log10(pstress / siga) # stress change from (pstress, ep) if on extended Cc line dpCc = pstress * (np.exp(fact * (ep - e) / Cc) - 1.0) # stress change from (pstress, ep) if on extended Cr line dpCr = pstress * (np.exp(fact * (ep - e) / Cr) - 1.0) estress = pstress + np.minimum(dpCc, dpCr) return estress
[docs] def e_and_stress_for_plotting(self, **kwargs): """Void ratio and stress values that plot the model Parameters ---------- pstress : float, optional Preconsolidation stress. Default behaviour is normally consolidated. 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=100 Returns ------- x, y : 1d ndarray `npts` stress, and void ratio values between `xmin` and `xmax`. """ npts = kwargs.get('npts', 100) xmin, xmax = kwargs.get('xmin', 1.0), kwargs.get('xmax', 100) x = np.linspace(xmin, xmax, npts) pstress = kwargs.get('pstress', x) i = np.searchsorted(x, pstress) x[i] = pstress y = self.e_from_stress(x, pstress=pstress) return x, y
[docs] def av_from_stress(self, estress, **kwargs): """Slope of void ratio from effective stress Parameters ---------- estress : float Current effective stress. pstress : float, optional Preconsolidation stress. Default pstress=estress i.e. normally consolidated. Returns ------- av : float Slope of void-ratio vs effective stress plot at current stress state. Examples -------- On recompression line: >>> a = CcCrSoilModel(Cc=3.0, Cr=0.5, siga=10, ea=5) >>> a.av_from_stress(estress=40, pstress=50) 0.00542868... On compression line: >>> a = CcCrSoilModel(Cc=3.0, Cr=0.5, siga=10, ea=5) >>> a.av_from_stress(estress=60, pstress=50) 0.02171472... Array inputs: >>> a = CcCrSoilModel(Cc=3.0, Cr=0.5, siga=10, ea=5) >>> a.av_from_stress(estress=np.array([40, 60.0]), ... pstress=np.array([50, 55.0])) array([0.00542868, 0.02171472]) """ pstress = kwargs.get('pstress', estress) Cc = self.Cc Cr = self.Cr chooser = np.array((Cc, Cc, Cr), dtype=float) # appropriate value of Cc or Cr Cx = chooser[np.array(np.sign(estress - pstress), dtype=int)] av = 0.43429448190325182 * Cx / estress return av
[docs]class PwiseLinearSoilModel(OneDimensionalVoidRatioEffectiveStress): """Pwise linear void ratio-effective stress realationship x and y data can be interpolated natural-natural, natural-log10, log10-natural, or log10, log10. Parameters ---------- siga, ea : 1d array Effective stress values and void ratio values defining a one-to-one relationship. Slope of void ratio-effectinve stress plot should never fall below `Cr`. Cr : float Precompression index, slope of void rato-effective stress line. Note that `Cr` is the slope in whatever scales of slog and ylog that have been chosen. xlog, ylog : True/False, Optional If True then interpolation on each axis is assumed to be logarithmic with base 10. Default xlog=ylog=False """ def __init__(self, siga, ea, Cr, xlog=False, ylog=False): self.siga = np.asarray(siga, dtype=float) self.ea = np.asarray(ea, dtype=float) self.xlog = xlog self.ylog = ylog self.Cr = Cr #TODO: adjust for different logarithm bases. self.siga_slice = slice(None) self.ea_slice = slice(None) if np.any(np.diff(self.siga) <= 0): # siga is in decreasing order # reverse the slice for e_from_stress interpolation self.siga_slice = slice(None, None, -1) if np.any(np.diff(self.ea) <= 0): # ea is in decreasing order # reverse the slice for stress_from_e interpolation self.ea_slice = slice(None, None, -1) # raise ValueError("'ea' must be in monotomically decreasng order.") if len(siga) != len(ea): raise IndexError("'siga' and 'ea' must be the same length.") self.log_siga = np.log10(self.siga) self.log_ea = np.log10(self.ea)
[docs] def e_from_stress(self, estress, **kwargs): """Void ratio from effective stress Parameters ---------- estress : float Current effective stress. etress must be within the range of the soil model points. pstress : float, optional Preconsolidation stress. Default pstress=estress i.e. normally consolidated. pstress must be in the range of the soil model points. Returns ------- e : float Void ratio corresponding to current stress state. Examples -------- On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.2) >>> a.e_from_stress(estress=1.25, pstress=2.25) 2.95... On compression line: >>> a.e_from_stress(estress=2.25, pstress=2) 2.75... Normally consolidated (pstress not specified): >>> a.e_from_stress(estress=2.25) 2.75... Array inputs: >>> a.e_from_stress(estress=np.array([1.25, 2.25]), pstress=2.25) array([2.95, 2.75]) Logarithmic effective stress scale: On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.2, xlog=True) >>> a.e_from_stress(estress=1.25, pstress=2.25) 2.75930... On compression line: >>> a.e_from_stress(estress=2.25, pstress=2) 2.70824... Normally consolidated (pstress not specified): >>> a.e_from_stress(estress=2.25) 2.70824... Array inputs: >>> a.e_from_stress(estress=np.array([1.25, 2.25]), pstress=2.25) array([2.75930..., 2.70824...]) Logarithmic void ratio scale On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1, ylog=True) >>> a.e_from_stress(estress=1.25, pstress=2.25) 3.330803... On compression line: >>> a.e_from_stress(estress=2.25, pstress=2) 2.64575... Normally consolidated (pstress not specified): >>> a.e_from_stress(estress=2.25) 2.64575... Array inputs: >>> a.e_from_stress(estress=np.array([1.25, 2.25]), pstress=2.25) array([3.330803..., 2.64575...]) Logarithmic effective stress and void ratio scales On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1, xlog=True, ylog=True) >>> a.e_from_stress(estress=1.25, pstress=2.25) 2.76255... On compression line: >>> a.e_from_stress(estress=2.25, pstress=2) 2.604857... Normally consolidated (pstress not specified): >>> a.e_from_stress(estress=2.25) 2.604857... Array inputs: >>> a.e_from_stress(estress=np.array([1.25, 2.25]), pstress=2.25) array([2.76255..., 2.604857...]) Increasing vs decreasing inputs >>> ea = np.arange(1,10) >>> siga = 3 * ea >>> np.isclose(PwiseLinearSoilModel(siga, ea, ... Cr=0.1).e_from_stress(7.2), ... PwiseLinearSoilModel(siga[::-1], ea[::-1], ... Cr=0.1).e_from_stress(7.2)) True """ pstress = kwargs.get('pstress', estress) Cr = self.Cr max_past = np.maximum(pstress, estress) #transform x data if needed if self.xlog: siga = self.log_siga # np.log10(max_past, out=max_past) # doesn't work for single values max_past = np.log10(max_past) estress = np.log10(estress) else: siga = self.siga #transform y data if needed if self.ylog: ea = self.log_ea else: ea = self.ea # void ratio at preconsolidation pressure e = np.interp(max_past, siga[self.siga_slice], ea[self.siga_slice]) # void ratio at current effetive stress e += Cr * (max_past - estress) # transform y back if needed if self.ylog: # np.power(10, e, out=e) # doesn't work for single values e = np.power(10, e) return e
[docs] def stress_from_e(self, e, **kwargs): """Effective stress from void ratio Parameters ---------- e : float Current void ratio. pstress : float, optional Preconsolidation stress. Default pstress=estress i.e. normally consolidated. Returns ------- estress : float Effective stress corresponding to current void ratio. Examples -------- On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1) >>> a.stress_from_e(e=2.8, pstress=2.25) 1.75... On compression line: >>> a.stress_from_e(e=2.65, pstress=2) 2.28333... Normally consolidated (pstress not specified): >>> a.stress_from_e(e=2.65) 2.28333... Array inputs: >>> a.stress_from_e(e=np.array([2.8, 2.65]), ... pstress=np.array([2.25, 2.0])) array([1.75..., 2.28333...]) Logarithmic effective stress scale On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1, xlog=True) >>> a.stress_from_e(e=2.73, pstress=2.25) 1.363494... On compression line: >>> a.stress_from_e(e=2.73, pstress=2) 2.2427... Normally consolidated (pstress not specified): >>> a.stress_from_e(e=2.73) 2.2427... Array inputs: >>> a.stress_from_e(e=2.73, pstress=np.array([2.25, 2.])) array([1.363494..., 2.2427...]) Logarithmic void ratio scale On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1, ylog=True) >>> a.stress_from_e(e=2.75, pstress=2.25) 2.082163... On compression line: >>> a.stress_from_e(e=2.65, pstress=2) 2.24856... Normally consolidated (pstress not specified): >>> a.stress_from_e(e=2.65) 2.24856... Array inputs: >>> a.stress_from_e(e=np.array([2.75, 2.65]), ... pstress=np.array([2.25, 2])) array([2.082163..., 2.24856...]) Logarithmic effective stress and void ratio scales On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1, xlog=True, ylog=True) >>> a.stress_from_e(e=2.65, pstress=2.25) 1.8948013... On compression line: >>> a.stress_from_e(e=2.65, pstress=2) 2.23463... Normally consolidated (pstress not specified): >>> a.stress_from_e(e=2.65) 2.23463... Array inputs: >>> a.stress_from_e(e=2.65, pstress=np.array([2.25, 2])) array([1.8948013..., 2.23463...]) Increasing vs decreasing inputs >>> ea = np.arange(1,10) >>> siga = 3 * ea >>> np.isclose(PwiseLinearSoilModel(siga, ea, Cr=0.1).stress_from_e(3.0, pstress=4.0), ... PwiseLinearSoilModel(siga[::-1], ea[::-1], Cr=0.1).stress_from_e(3.0, pstress=4.0)) True """ #transform x data if needed if self.xlog: siga = self.log_siga # estress = np.log10(estress) else: siga = self.siga #transform y data if needed if self.ylog: ea = self.log_ea e = np.log10(e) else: ea = self.ea pstress = kwargs.get('pstress', None) #fact = 2.3025850929940459 # 10**(x)==exp(fact*x) if pstress is None: # Normally consolidated estress = np.interp(e, ea[self.ea_slice], siga[self.ea_slice]) if self.xlog: estress = np.power(10.0, estress) # estress *= fact # np.exp(estress, out=estress) return estress if self.xlog: pstress = np.log10(pstress) Cr = self.Cr # void ratio at preconsolidation pressure ep = np.interp(pstress, siga[self.siga_slice], ea[self.siga_slice]) # stress change from (pstress, ep) if on pwise line dp_interp = np.interp(e, ea[self.ea_slice], siga[self.ea_slice]) - pstress # stress change from (pstress, ep) if on extended Cr line dpCr = (ep - e) / Cr # effective stress at current void ratio estress = pstress + np.minimum(dp_interp, dpCr) # transform x back if needed if self.xlog: estress = np.power(10.0, estress) return estress
[docs] def e_and_stress_for_plotting(self, **kwargs): """Void ratio and stress values that plot the model Parameters ---------- pstress : float, optional Preconsolidation stress. Default behaviour is normally consolidated. 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 minumum of model `siga`. Returns ------- x, y : 1d ndarray `npts` stress, and void ratio values between `xmin` and `xmax`. """ npts = kwargs.get('npts', 100) xmin, xmax = np.min(self.siga), np.max(self.siga) x = np.linspace(xmin, xmax, npts) pstress = kwargs.get('pstress', x) y = self.e_from_stress(x, pstress=pstress) return x, y
[docs] def av_from_stress(self, estress, **kwargs): """Slope of void ratio from effective stress Parameters ---------- estress : float Current effective stress. pstress : float, optional Preconsolidation stress. Default pstress=estress i.e. normally consolidated. Returns ------- av : float Slope of void-ratio vs effective stress plot at current stress state. Examples -------- On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1) >>> a.av_from_stress(estress=1.25, pstress=2.25) 0.1... On compression line: >>> a.av_from_stress(estress=2.25, pstress=2) 3.0... Normally consolidated (pstress not specified): >>> a.av_from_stress(estress=2.25) 3.0... Array inputs: >>> a.av_from_stress(estress=np.array([1.25, 2.25]), ... pstress=np.array([2.25, 2.])) array([0.1, 3. ]) Logarithmic effective stress scale On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1, xlog=True) >>> a.av_from_stress(estress=1.25, pstress=2.25) 0.034743... On compression line: >>> a.av_from_stress(estress=2.25, pstress=2) 2.987... Normally consolidated (pstress not specified): >>> a.av_from_stress(estress=2.25) 2.987... Array inputs: >>> a.av_from_stress(estress=np.array([1.25, 2.25]), ... pstress=np.array([2.25, 2.])) array([0.034743..., 2.987...]) Logarithmic void ratio scale On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1, ylog=True) >>> a.av_from_stress(estress=1.25, pstress=2.25) 0.76694... On compression line: >>> a.av_from_stress(estress=2.25, pstress=2) 2.9612... Normally consolidated (pstress not specified): >>> a.av_from_stress(estress=2.25) 2.9612... Array inputs: >>> a.av_from_stress(estress=np.array([1.25, 2.25]), pstress=2.25) array([0.76694..., 2.9612...]) Logarithmic effective stress and void ratio scales On recompression line: >>> x, y = np.array([1,2,2.5]), np.array([4, 3.5, 2]) >>> a = PwiseLinearSoilModel(siga=x, ea=y, Cr=0.1, xlog=True, ylog=True) >>> a.av_from_stress(estress=1.25, pstress=2.25) 0.2210045... On compression line: >>> a.av_from_stress(estress=2.25, pstress=2) 2.9034... Normally consolidated (pstress not specified): >>> a.av_from_stress(estress=2.25) 2.9034... Array inputs: >>> a.av_from_stress(estress=np.array([1.25, 2.25]), pstress=2.25) array([0.2210045..., 2.9034...]) Increasing vs decreasing inputs >>> ea = np.arange(1,10) >>> siga = 3 * ea >>> np.isclose(PwiseLinearSoilModel(siga, ea, Cr=0.1).av_from_stress(7.2), ... PwiseLinearSoilModel(siga[::-1], ea[::-1], Cr=0.1).av_from_stress(7.2)) True """ pstress = kwargs.get('pstress', estress) Cr = self.Cr max_past = np.maximum(pstress, estress) #transform x data if needed if self.xlog: siga = self.log_siga[self.siga_slice] # np.log10(max_past, out=max_past) # doesn't work for single values max_past = np.log10(max_past) estress = np.log10(estress) else: siga = self.siga[self.siga_slice] #transform y data if needed if self.ylog: ea = self.log_ea[self.siga_slice] else: ea = self.ea[self.siga_slice] # interval at preconsolidatio stress i = np.searchsorted(siga, max_past) Cc = (ea[i - 1] - ea[i]) / (siga[i] - siga[i - 1]) # void ratio at preconsolidation pressure e = ea[i-1] - Cc * (max_past - siga[i - 1]) # void ratio at current effetive stress e += Cr * (max_past - estress) Cx = np.where(max_past > estress, Cr, Cc) # may need float comparison # modify Cx slope for log axes fact = 2.3025850929940459 #fact = log(10) dx, dy = 1.0, 1.0 # transform y back if needed if self.ylog: dy = fact * np.power(10.0, e) if self.xlog: dx = fact * np.power(10.0, estress) av = Cx * dy / dx return av
[docs]class FunctionSoilModel(OneDimensionalVoidRatioEffectiveStress): """Functional definition of void-ratio stress relationship User provides python functions. Parameters ---------- fn_e_from_stress: callable object Function to obtain void ratio from stress. fn_e_from_stress should be the inverse function of fn_stress_from_e. fn_stress_from_e : callable object Function to obtain stress from void ratio. fn_stress_from_e should be the inverse function of fn_e_from_stress. fn_av_from_stress: callable object Function to obtain slope of void ratio-stress relationship. fn_av_from_stress should be negative the derivative of fn_e_from_stress w.r.t. stress be the inverse function of fn_k_from_e. *args, **kwargs : anything Positional and keyword arguments to be passed to the fn_e_from_stress, fn_stress_from_e, fn_av_from_stress functions. Note that any additional args and kwargs passed to the functions will be appended to the args, and kwargs. You may get into a mess for example with e_from_stress because normally the first postional argument passed to such fucntions is estress. If you add your own positional arguments, then k will be after you own arguments. Just be aware that the usual way to call methods of the base object `OneDimensionalVoidRatioEffectiveStress` a single positonal arguement, e.g. estress, e, followed by any required keyword arguments. Notes ----- Any function should be able to accept additonal keywords. Examples -------- >>> def efs(s, b=2): ... return s * b >>> def sfe(e, b=2): ... return e / b >>> def afs(s, b=2): ... return -b >>> a = FunctionSoilModel(efs, sfe, afs, b=5) >>> a.e_from_stress(3) 15 >>> a.stress_from_e(15) 3.0 >>> a.av_from_stress(3) -5 Prconsolidation stress: >>> def efs2(s, pstress=None, b=2): ... if pstress is None: ... pstress=8 ... return s * b + pstress >>> a = FunctionSoilModel(efs2, sfe, afs, b=5) >>> a.e_from_stress(3) 23 """ def __init__(self, fn_e_from_stress, fn_stress_from_e, fn_av_from_stress, *args, **kwargs): self.fn_e_from_stress = fn_e_from_stress self.fn_stress_from_e = fn_stress_from_e self.fn_av_from_stress = fn_av_from_stress self.args = args self.kwargs = kwargs self.e_from_stress = functools.partial(fn_e_from_stress, *args, **kwargs) self.stress_from_e = functools.partial(fn_stress_from_e, *args, **kwargs) self.av_from_stress = functools.partial(fn_av_from_stress, *args, **kwargs) return
[docs]class YinAndGrahamSoilModel(OneDimensionalVoidRatioEffectiveStress): """Yin and Graham creep void ratio-effective stress realationship This model uses the integral void ratio form of the Yin and Graham (1996) [1]_ soil model as described by Hu et al. (2014) [2]_. Basically you step through time keeping track of the stress state and a cumulatlive integral representing creep strain. Parameters ---------- lam : float Compressibility index, slope of the referece time line e-ln(sig) line. kap : float Recompression index, slope of e-ln(sig) line. psi : float Creep parameter. Slope of the void ratio-ln(time) line. siga, ea : float Point on reference time line fixing it in effective stress-void ratio space. ta : float Reference time corresponding to the reference time line. e0 : float, optional Initial void ratio. Default e0=None. If specifying initial conditions then only use two of the four available (e0, estress0, pstress0, t0). estress0 : float, optional Initial effective stress. Default estress=None. If specifying initial conditions then only use two of the four available (e0, estress0, pstress0, t0). pstress0 : float, optional Initial 'preconsolidation` stress on the reference time line. Default pstress0=None. If specifying initial conditions then only use two of the four available (e0, estress0, pstress0, t0). t0 : float, optional Initial equivalent time at the initial conditon. Default t0=None. If specifying initial conditions then only use two of the four available (e0, estress0, pstress0, t0). Attributes ---------- _alpha : float Composite material parameter (lam - kap / psi) _psi_zero_model : CcCrSoilModel soil model when psi=0. i.e. CcCrSoilModel following instant time line and reference time line. _igral : float Current value of the material model integration w.r.t. time. _igral = integrate[1/t0 * (estress/etress0)**alpha, t,0,t] _inc : float Increment to add to _igral. Helper array to speed up stress_from_e. Only present after initial conditions have been initialized. _inc = integrate[1/t0 * (estress/etress0)**alpha, t,(t),(t + dt)] _es : float Temp effectve stress. Helper array to speed up stress_from_e calc. Only present after initial conditions have been initialized. _estress : float Current value of effective stress. Only present after initial conditions have been initialized. Basically when you call method e_from_stress or method stress_from_e with parameter dt, then _estress and _igral correspond to effective stress and the time integral at the start of the increment; the integration increment _inc will be calculated and then _igral and _estress will be updated to reflect values at the end of dt time increment. Examples -------- Combos of initial conditions give the same results >>> fixed_param = dict(lam=0.2, kap=0.04, psi=0.01, siga=20, ea=1, ta=1) >>> oparam = dict(e0=0.95, estress0=18, pstress0=28.06637954, ... t0=1220.73731) >>> a = YinAndGrahamSoilModel(e0=oparam['e0'], ... estress0=oparam['estress0'], **fixed_param) >>> [a.e0, a.estress0, a.pstress0, a.t0] [0.95..., 18..., 28.0..., 1220.737...] >>> a = YinAndGrahamSoilModel(e0=oparam['e0'], ... pstress0=oparam['pstress0'], **fixed_param) >>> [a.e0, a.estress0, a.pstress0, a.t0] [0.95..., 18..., 28.0..., 1220.737...] >>> a = YinAndGrahamSoilModel(e0=oparam['e0'], ... t0=oparam['t0'], **fixed_param) >>> [a.e0, a.estress0, a.pstress0, a.t0] [0.95..., 18..., 28.0..., 1220.737...] >>> a = YinAndGrahamSoilModel(estress0=oparam['estress0'], ... pstress0=oparam['pstress0'], **fixed_param) >>> [a.e0, a.estress0, a.pstress0, a.t0] [0.95..., 18..., 28.0..., 1220.737...] >>> a = YinAndGrahamSoilModel(estress0=oparam['estress0'], ... t0=oparam['t0'], **fixed_param) >>> [a.e0, a.estress0, a.pstress0, a.t0] [0.95..., 18..., 28.0..., 1220.737...] >>> a = YinAndGrahamSoilModel(pstress0=oparam['pstress0'], ... t0=oparam['t0'], **fixed_param) >>> [a.e0, a.estress0, a.pstress0, a.t0] [0.95..., 18..., 28.0..., 1220.737...] References ---------- .. [1] Yin, J.-H., and Graham, J. (1996). 'Elastic visco-plastic modelling of one-dimensional consolidation. Geotechnique, 46(3), 515-527. .. [2] Hu, Y.-Y., Zhou, W.-H., and Cai, Y.-Q. (2014). 'Large-strain elastic viscoplastic consolidation analysis of very soft clay layers with vertical drains under preloading. Canadian Geotechnical Journal, 51(2), 144-157. Notes ----- The void ratio stress relationship is: .. math:: e=e0-\\kappa\\ln\\left({\\frac{\\sigma^{\\prime}_z}{\\sigma^{\\prime}_{z}}}\\right) -\\psi\\ln\\left[{ \\int_{0}^{t}{ \\frac{1}{t_0} \\left({ \\frac{\\sigma^{\\prime}_z} {\\sigma^{\\prime}_{0}}}\\right)^{\\alpha}\\,dt }+1 }\\right] To calculate the integral we keep track of the effective stress and the integration at time t and then add the integration increment between :math:`t` and :math:`t+\\bigtriangleup t`: .. math:: e=e0-\\kappa\\ln\\left({\\frac{\\sigma^{\\prime}_{z,t+\\bigtriangleup t}}{\\sigma^{\\prime}_{z}}}\\right) -\\psi\\ln\\left[{ \\int_{0}^{t}{ \\frac{1}{t_0} \\left({ \\frac{\\sigma^{\\prime}_z} {\\sigma^{\\prime}_{0}}}\\right)^{\\alpha}\\,dt } + \\int_{t}^{t+\\bigtriangleup t}{ \\frac{1}{t_0} \\left({ \\frac{\\sigma^{\\prime}_z} {\\sigma^{\\prime}_{0}}}\\right)^{\\alpha}\\,dt } +1}\\right] Consider a time increment of length :math:`\\bigtriangleup t` in which the effective stress changes from :math:`\\sigma^{\\prime}_{t}` to :math:`\\sigma^{\\prime}_{t + \\bigtriangleup t}`. Assume that the stress changes in a linear fashion over :math:`\\bigtriangleup t`: .. math:: \\sigma^{\\prime}\\left({t}\\right) = \\sigma^{\\prime}_{t} + t \\frac{\\sigma^{\\prime}_{t+ \\bigtriangleup t} -\\sigma^{\\prime}_{t}} {\\bigtriangleup t} The time integral becomes: .. math:: \\frac{1}{t_0}\\int_{0}^{t}{ \\left({ \\frac{\\sigma^{\\prime}_z} {\\sigma^{\\prime}_{z0}}}\\right)^{\\alpha}\\,dt } + \\frac{1}{t_0}\\int_{0}^{\\bigtriangleup t} {\\left({\\frac{\\sigma^{\\prime}_{t} + t \\frac{\\sigma^{\\prime}_{t+ \\bigtriangleup t} -\\sigma^{\\prime}_{t}} {\\bigtriangleup t}}{\\sigma^{\\prime}_0}}\\right)^\\alpha\\,dt} The increment that is added to the main integral is: .. math:: \\frac{1}{t_0}\\int_{0}^{\\bigtriangleup t} {\\left({\\frac{\\sigma^{\\prime}_{t} + t \\frac{\\sigma^{\\prime}_{t+ \\bigtriangleup t} -\\sigma^{\\prime}_{t}} {\\bigtriangleup t}}{\\sigma^{\\prime}_0}}\\right)^\\alpha\\,dt} = \\frac{1}{t_0} \\frac{\\sigma^{\\prime}_{0} \\bigtriangleup t} {\\left({\\alpha+1}\\right) \\left({\\sigma^{\\prime}_{t+ \\bigtriangleup t} -\\sigma^{\\prime}_{t}}\\right)} \\left({ \\left({\\frac{\\sigma^{\\prime}_{t+ \\bigtriangleup t}}{\\sigma^{\\prime}_0}}\\right)^{\\alpha+1}- \\left({\\frac{\\sigma^{\\prime}_{t}}{\\sigma^{\\prime}_0}}\\right)^{\\alpha+1} }\\right) When :math:`\\sigma^{\\prime}_{t+ \\bigtriangleup t}=\\sigma^{\\prime}_{t}` then the increment is: .. math:: \\frac{1}{t_0}\\int_{0}^{\\bigtriangleup t} {\\left({\\frac{\\sigma^{\\prime}_{t} + t \\frac{\\sigma^{\\prime}_{t+ \\bigtriangleup t} -\\sigma^{\\prime}_{t}} {\\bigtriangleup t}}{\\sigma^{\\prime}_0}}\\right)^\\alpha\\,dt} = \\frac{1}{t_0}\\bigtriangleup t \\left({\\frac{\\sigma^{\\prime}_{t}}{\\sigma^{\\prime}_0}}\\right)^{\\alpha+1} The value of the integral and the effective stress is stored within the YinAndGrahamSoilModel object and updated after each call to `e_from_stress` or `stress_from_e`. **e_from_stress** `e_from_stress` uses the above equations directly (knowing all material parameters, the value of the integral at the start of the increment, and the efffective stresses at the beginning and end of time incremetn dt, then e can be calculated directly). **stress_from_e** `stress_from_e` recasts the equations as estress=f(estress) and uses fixed point iteration to determine the effective stress at the end of the increment. **initial_conditions** If two of the following four parameters describing the intial state of the soil are known, then the equations below can be combined to solve for the remaining two unknowns: initial void ratio :math:`e_0`, initial stress :math:`\\sigma^{\\prime}_0`, pseudo initial preconsolidation stress :math:`\\sigma^{\\prime}_p` and equivalent time corresponding to initial state :math:`t_0`. On the reference time line running through point :math:`(\\sigma^{\\prime}_a, e_a)`, the psuedo preconsolidation pressure :math:`\\sigma^{\\prime}_p` corresponds to void ratio :math:`e_p`: .. math:: e_p = e_a - \\lambda \\ln\\left({ \\frac{\\sigma^{\\prime}_p} {\\sigma^{\\prime}_a} }\\right) The instant time line running through point :math:`(\\sigma^{\\prime}_0, e_0)` meets the reference time line at :math:`\\sigma^{\\prime}_p` and void ratio :math:`e_p`: .. math:: e_p = e_0 - \\kappa \\ln\\left({ \\frac{\\sigma^{\\prime}_p} {\\sigma^{\\prime}_0} }\\right) The initial state :math:`(\\sigma^{\\prime}_0, e_0)` can be reached from the reference time line by either a) loading to :math:`\\sigma^{\\prime}_p` and then unloading to :math:`\\sigma^{\\prime}_0` or b) creeping from time :math:`t_a` to time :math:`t_0` at constant stress :math:`\\sigma^{\\prime}_0`. equating the two void ratio changes gives: .. math:: \\left({\\lambda-\\kappa}\\right) \\ln\\left({ \\frac{\\sigma^{\\prime}_p}{\\sigma^{\\prime}_0} }\\right) = \\psi \\ln\\left({ \\frac{t_0}{t_a} }\\right) YinAndGrahamSoilModel() """ def __init__(self, lam, kap, psi, siga, ea, ta, e0=None, estress0=None, pstress0=None, t0=None): self.lam = lam self.kap = kap self.psi = psi self.siga = siga self.ea = ea self.ta = ta self._alpha = (self.lam - self.kap) / self.psi self._psi_zero_model = CcCrSoilModel(Cc=np.log(10.0)*self.lam, Cr=np.log(10.0)*self.kap, siga=self.siga, ea=self.ea) self.e0, self.estress0, self.pstress0, self.t0 = ( self.initial_conditions(e0=e0, estress0=estress0, pstress0=pstress0, t0=t0)) self._igral = 0#np.zeros(len(np.asarray(self.e0)), dtype=float)
[docs] def initial_conditions(self, e0=None, estress0=None, pstress0=None, t0=None): """Calculate the initial conditions Only use two of the parameters, the other two will be calculated. Parameters ---------- e0 : float, optional Initial void ratio. Default e0=None. If specifying initial conditions then only use two of the four available (e0, estress0, pstress0, t0). estress0 : float, optional Initial effective stress. Default estress=None. If specifying initial conditions then only use two of the four available (e0, estress0, pstress0, t0). pstress0 : float, optional Initial 'preconsolidation` stress on the reference time line. Default pstress0=None. If specifying initial conditions then only use two of the four available (e0, estress0, pstress0, t0). t0 : float, optional Initial equivalent time at the initial conditon. Default t0=None. If specifying initial conditions then only use two of the four available (e0, estress0, pstress0, t0). Returns ------- e0 : float Initial void ratio estress0 : float Initial effective stress pstress0 : float Inital preconsolidation stress t0 : float Initial equivalent time. """ d=dict() try: d['e0']=e0.copy() except AttributeError: d['e0']=e0 try: d['estress0']=estress0.copy() except AttributeError: d['estress0']=estress0 try: d['pstress0']=pstress0.copy() except AttributeError: d['pstress0']=pstress0 try: d['t0']=t0.copy() except AttributeError: d['t0']=t0 # d = dict(e0=e0, estress0=estress0, pstress0=pstress0, t0=t0) ic_list = ['e0', 'estress0', 'pstress0','t0'] ic_given = [v for v in ic_list if not d.get(v, None) is None] if len(ic_given)==0: #no initial conditions set return None, None, None, None elif len(ic_given)==2: #2 of the 4 initial condition parameters are given, calc the others e0 = d.get('e0', None) estress0 = d.get('estress0', None) pstress0 = d.get('pstress0', None) t0 = d.get('t0', None) ea = self.ea siga = self.siga alpha = self._alpha kap = self.kap lam = self.lam psi = self.psi ta = self.ta if ic_given==['e0', 'estress0']: numer = (ea - e0 - kap * np.log(estress0) + lam * np.log(siga)) denom = (lam - kap) pstress0 = np.exp(numer / denom) t0 = (pstress0 / estress0)**(alpha) * ta elif ic_given==['e0', 'pstress0']: numer = (ea - e0- (lam-kap) * np.log(pstress0) + lam * np.log(siga)) denom = (kap) estress0 = np.exp(numer / denom) t0 = (pstress0 / estress0)**(alpha) * ta elif ic_given==['e0', 't0']: OCR = (t0/ta)**(1/alpha) numer = (ea - e0 + lam * np.log(siga) + kap * np.log(OCR)) denom = (lam) pstress0 = np.exp(numer / denom) estress0 = pstress0 / OCR elif ic_given==['estress0', 'pstress0']: OCR = pstress0 / estress0 t0 = (OCR)**alpha * ta e0 = (ea - lam * np.log(pstress0 / siga) + kap * np.log(OCR)) elif ic_given==['estress0', 't0']: OCR = (t0/ta)**(1/alpha) pstress0 = OCR * estress0 e0 = ea - lam * np.log(pstress0 / siga) + kap * np.log(OCR) elif ic_given==['pstress0', 't0']: OCR = (t0/ta)**(1/alpha) estress0 = pstress0 / OCR e0 = (ea - lam * np.log(pstress0 / siga) + kap * np.log(OCR)) #make some temp arrays to speed calcs self._inc = np.zeros_like(estress0, dtype=float) self._es = np.zeros_like(estress0, dtype=float) self._estress = np.zeros_like(estress0, dtype=float) self._inc = np.atleast_1d(self._inc) self._es = np.atleast_1d(self._es) self._estress = np.atleast_1d(self._estress) self._estress[:] = estress0 return e0, estress0, pstress0, t0 else: #too many initial conditoin parameters given. raise ValueError("If specifying initial conditions ({}) then only " "two and only two can be specified. " "You have {}.".format(ic_list, ",".join(ic_given)))
[docs] def e_from_stress(self, estress, **kwargs): """Void ratio from effective stress When dt is None then then self.e0, self.estress0, self.t0 and self.pstress0 will be initialized from estress and pstress; self.e0 will be returned. Parameters ---------- estress : float Current effective stress. estress is assumed to be the effective stress at the end of an increment of length dt. i.e. at time t+dt. dt : float, optional Time increment. Default dt=None. When dt is None, the void ratio is calculted assuming psi=0. i.e. along the instant time line and then down the reference time line. pstress : float, optional Preconsolidation stress. This is only relevant if dt is not None. Default pstress=estress i.e. on the reference time line. Returns ------- e : float Void ratio corresponding to current stress state. Notes ----- self._igral and self._estress and self._inc will be updated! """ dt = kwargs.get('dt', None) pstress = kwargs.get('pstress', estress) if dt is None: # assume calculating initial conditions from stress state self.e0, self.estress0, self.pstress0, self.t0 = ( self.initial_conditions(estress0=estress, pstress0=pstress)) # e = self._psi_zero_model.e_from_stress(estress, pstress) return self.e0 else: # estress is assumed to be the effective stress at the end # of an increment of length dt. e0 = self.e0 kap = self.kap lam = self.lam psi = self.psi estress0 = self.estress0 self._inc_from_stress(estress, dt) self._igral += self._inc self._estress[:] = estress # inc = dt/self.t0 * (estress / estress0) ** self._alpha # self._igral += inc e = (e0 - kap * np.log(estress / estress0) - psi * np.log(self._igral + 1)) return e
def _reset_to_initial_conditions(self): """Reset parameters to initial conditions""" self._igral = 0 self._estress[:] = self.estress0 def _inc_from_stress(self, estress, dt): """Integration increment As well as returning the integration increment self._inc will be updated. Parameters ---------- estress : 1d array of float effective stress at end of dt increment. dt : float Lenght of time increment Returns ------- inc : 1darray of float Integration increment. Stress is assumed to vary from self._estress to estress over a time incrementn dt. Notes ----- self._inc will be updated! """ self._inc[:] = dt self._inc /= self._alpha + 1 self._inc *= self.estress0 self._inc /= self.t0 self._inc /= (estress - self._estress) self._inc *= ((estress / self.estress0) ** (self._alpha + 1) - (self._estress / self.estress0) ** (self._alpha + 1)) nans = np.isnan(self._inc) # Following expression gives errors when t0, estress0 or _estress are # not arrays so I did it differently (may be slower): # self._inc[nans] = dt / self.t0[nans] * (self._estress[nans] / self.estress0[nans]) ** (self._alpha + 1) if np.isscalar(self._estress): self._inc[nans] = self._estress else: self._inc[nans] = self._estress[nans] if np.isscalar(self.estress0): self._inc[nans] /= self.estress0 else: self._inc[nans] /= self.estress0[nans] np.power(self._inc[nans],(self._alpha + 1),out=self._inc[nans]) if np.isscalar(self.t0): self._inc[nans] /= self.t0 else: self._inc[nans] /= self.t0[nans] return self._inc
[docs] def stress_from_e(self, e, **kwargs): """Effective stress from void ratio If `stress_from_e` is called when self.estress0 is not initialized then the initial conditions will be initialized assumming the stress state is on the reference time line. Parameters ---------- e : float Current void ratio. e is assumed to be the effective stress at the end of an increment of length dt. i.e. at time t+dt. dt : float, optional Time increment. Default dt=0. Returns ------- estress : float Effective stress corresponding to current void ratio. Notes ----- self._igral and self._estress and self._inc will be updated! """ if self.estress0 is None: # estress is on reference line # assume calculating initial conditions from void ratio state self.e0, self.estress0, self.pstress0, self.t0 = ( self.initial_conditions(e0=e, t0=self.ta)) return self.estress0 dt = kwargs.get('dt', 0) def fn(estress): self._inc_from_stress(estress, dt) self._es[:] = 1.0 self._es += self._inc self._es += self._igral np.log(self._es, out=self._es) self._es *= self.psi np.negative(self._es, out=self._es) self._es += self.e0 self._es -= e self._es /= self.kap np.exp(self._es, out=self._es) self._es *= self.estress0 return self._es # fn(0) # self._es *= 0.95 fixed_point(fn, self._estress, xtol=1e-3, maxiter=500) #update the properties at end of dt time increment self._igral += self._inc self._estress[:] = self._es return self._es
# def e_and_stress_for_plotting(self, **kwargs): # """Void ratio and stress values that plot the model # # Parameters # ---------- # pstress : float, optional # Preconsolidation stress. Default behaviour is normally # consolidated. # 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=100 # # # Returns # ------- # x, y : 1d ndarray # `npts` stress, and void ratio values between `xmin` and `xmax`. # # """ # # npts = kwargs.get('npts', 100) # xmin, xmax = kwargs.get('xmin', 1.0), kwargs.get('xmax', 100) # # x = np.linspace(xmin, xmax, npts) # pstress = kwargs.get('pstress', x) # i = np.searchsorted(x, pstress) # x[i] = pstress # y = self.e_from_stress(x, pstress=pstress) # # return x, y # #
[docs] def av_from_stress(self, estress, **kwargs): """Slope of void ratio from effective stress When `estressdot` is provided as a kwarg then av is a true representation of de/destress at the current time (i.e. using the current value of self._igral). When `estressdot` is not provided then a conservative lower value of av based on self.kap and the current estress is calculated. Parameters ---------- estress : float Current effective stress. estressdot: float, optional Rate of change of effective stress w.r.t. time. Default estressdot=None i.e. av is calculated using self.kap and current effective stress. Returns ------- av : float Slope of void-ratio vs effective stress plot at current stress state, assuming psi=0. Examples -------- >>> a = YinAndGrahamSoilModel(lam=0.2, kap=0.04, psi=0.01, siga=20, ... ea=1, ta=1, e0=0.90, estress0=18) >>> a._igral=3.1 #artificially set igral. Usually calculted advancing through time using e_from_stress or stress_from_e >>> a.av_from_stress(estress=10) 0.004 >>> a.av_from_stress(estress=40, estressdot=1.5) 0.00417... #todo: write up av equations """ estressdot = kwargs.get('estressdot', None) if estressdot is None: av = self.kap / estress else: av = (self.kap / estress + self.psi / self.t0 / np.abs(estressdot) / (self._igral + 1) * (estress / self.estress0)**self._alpha) return av
[docs] def CRSN(self, tvals, edot, **kwargs): """Constant rate of strain simulation Note that the rate of chage of void ratio is specified. Strain can be back caluclated using strain = (e0-e) / (1 + e0). Parameters ---------- tvals : 1d array of float Time values for piecewise linear strain-rate vs time. edot : 1d array of float Time rate of change of void ratio corresponding to tvals. Note that you will need a negative edot for void ratio to decrease. Be careful because strain in geotech is usually positive for a decrease in void ratio. method : ['step', 'ode'] optional Describes which method to use. 'step' will step through time using the stress_from_e function. 'odeint' will reframe the constitutive model in terms of stress-rate and use odeint to solve for effective stress. Both methods should produce the same results; the 'odeint' method is really just a validity check of the the 'step' method. Default method='step'. **kwargs : various Arguments (other than method) will be passed to the subdivide_x_into_segments function. If not given then the following values will be used, dx = (tvals[-1]-tvals[0])/200, min_segments=50. You may have to fiddle around with kwargs to get meaningful results; a small dx value will give good results but could take a long time to compute. Returns ------- tvals_ : 1d array of float Time values estress : 1d array of float Effective stress at tvals_. e : 1d array of float Void ratio at time tvals_. edot_ : 1d array of float. Time rate of change of void ratio at tvals_ See Also -------- geotecha.piecewise.piecewsie_linear_1d.subdivide_x_into_segments : used to determine time intervals. """ igral0 = self._igral # remember _igral value before CRSN simulation method = kwargs.pop('method', 'step').lower() kwargs['dx']= kwargs.get('dx', (tvals[-1] -tvals[0]) / 200) kwargs['min_segments']= kwargs.get('min_segments', 50) tvals_ = pwise.subdivide_x_into_segments(tvals, **kwargs) if method == "odeint": def func(x, tt): """Effective stress rate This is based on Parameters ---------- x : float Effective stress. tt : float Time. Returns ------- y : float Effective stress rate. """ erate = np.interp(tt, tvals, edot) evalue = self.e0 + pwise.integrate_x_y_between_xi_xj(tvals, edot, 0, tt) return (-x / self.kap * ( erate + self.psi/self.ta * np.exp((evalue-self.ea)/self.psi) *(x / self.siga)**(self.lam / self.psi) )) estress = odeint(func, self.estress0, tvals_)[:,0] edot_ = np.interp(tvals_, tvals, edot) e = self.e0 + pwise.integrate_x_y_between_xi_xj(tvals, edot, np.zeros_like(tvals_), tvals_) else: e = np.zeros_like(tvals_) e[0] = self.e0 estress = np.zeros_like(tvals_) estress[0] = self.estress0 edot_ = np.interp(tvals_, tvals, edot) dt = np.diff(tvals_) e[1:] = edot_[0:-1] * dt np.cumsum(e, out=e) for i, dt_ in enumerate(dt): estress[i + 1] = self.stress_from_e(e[i+1], dt=dt_) self._igral = igral0 # reset _igral to value before CRSN return tvals_, estress, e, edot_
[docs] def CRSS(self, tvals, estressdot, **kwargs): """Constant rate of stress simulation Note that the output is void ratio not strain. Strain can be back caluclated using strain = (e0-e) / (1 + e0). Parameters ---------- tvals : 1d array of float Time values for piecewise linear strain-rate vs time. estressdot : 1d array of float Time rate of change of effective stress corresponding to tvals. method : ['step', 'ode'] optional Describes which method to use. 'step' will step through time using the e_from_stress function. 'odeint' will reframe the constitutive model in terms of strain-rate and use odeint to solve for effective stress. Both methods should produce the same results; the 'odeint' method is really just a validity check of the the 'step' method. Default method='step'. **kwargs : various Arguments (other than method) will be passed to the subdivide_x_into_segments function. If not given then the following values will be used, dx = (tvals[-1]-tvals[0])/200, min_segments=50. You may have to fiddle around with kwargs to get meaningful results; a small dx value will give good results but could take a long time to compute. Returns ------- tvals_ : 1d array of float Time values estress : 1d array of float Effective stress at tvals_. e : 1d array of float Void ratio at time tvals_. estressdot_ : 1d array of float. Time rate of change of stress at tvals_ See Also -------- geotecha.piecewise.piecewsie_linear_1d.subdivide_x_into_segments : used to determine time intervals. """ igral0 = self._igral # remember _igral value before CRSS simulation method = kwargs.pop('method', 'step').lower() kwargs['dx']= kwargs.get('dx', (tvals[-1] -tvals[0]) / 200) kwargs['min_segments']= kwargs.get('min_segments', 50) tvals_ = pwise.subdivide_x_into_segments(tvals, **kwargs) if method == "odeint": def func(x, tt): """time rate of change in void ratio This is based on Parameters ---------- x : float void ratio. tt : float Time. Returns ------- y : float Rate of void ratio change """ estressrate = np.interp(tt, tvals, estressdot) estressvalue = self.estress0 + pwise.integrate_x_y_between_xi_xj(tvals, estressdot, 0, tt) return (-self.kap * estressrate/estressvalue -self.psi/self.ta * np.exp((x-self.ea)/self.psi) *(estressvalue / self.siga)**(self.lam / self.psi)) e = odeint(func, self.e0, tvals_)[:,0] estressdot_ = np.interp(tvals_, tvals, estressdot) estress = self.estress0 + pwise.integrate_x_y_between_xi_xj(tvals, estressdot, np.zeros_like(tvals_), tvals_) else: e = np.zeros_like(tvals_) e[0] = self.e0 estress = np.zeros_like(tvals_) estress[0] = self.estress0 estressdot_ = np.interp(tvals_, tvals, estressdot) dt = np.diff(tvals_) estress[1:] = estressdot_[0:-1] * dt np.cumsum(estress, out=estress) for i, dt_ in enumerate(dt): e[i + 1] = self.e_from_stress(estress[i+1], dt=dt_) self._igral = igral0 # reset _igral to value before CRSS return tvals_, estress, e, estressdot_
if __name__ == '__main__': # print(CcCr_estress_from_e(2.95154499, 50, 3, 0.5, 10, 5)) import nose nose.runmodule(argv=['nose', '--verbosity=3', '--with-doctest', '--doctest-options=+ELLIPSIS']) if 0: # a = YinAndGraham(lam=0.2, kap=0.04, psi=0.001, siga=20, ea=1, ta=1, # e0=1.5) fixed_param = dict(lam=0.2, kap=0.04, psi=0.01, siga=20, ea=1, ta=1) oparam = dict(e0=0.95, estress0=18) oparam = dict(e0=np.array([0.95]), estress0=np.array([18])) a = YinAndGrahamSoilModel(e0=oparam['e0'], estress0=oparam['estress0'], **fixed_param) b=a._psi_zero_model.plot_model() plt.gca().plot(a.siga, a.ea , marker="o", ms=10) plt.gca().plot(a.estress0, a.e0, marker = 's', ms=5) plt.gca().plot(a.pstress0, a.ea-a.lam*np.log(a.pstress0/a.siga), marker = '^', ms=5) # print([a.e0, a.estress0, a.pstress0, a.t0]) # bb = a.stress_from_e(a.e0*0.9, dt=10000000) # print(bb) # # plt.gca().plot(bb, a.e0*.9, marker = 'h', ms=5) # plt.show() # # plt.show() tt = np.array([0.0, 3000]) edot = -np.array([1e-4, 1e-4]) tt = np.array([0.0, 1000, 1001, 1e4]) edot = -np.array([1e-4, 1e-4, 1e-5, 1e-5]) # kwargs = dict(logx=True, dx=0.01) kwargs=dict() tvals, estress, e, edot_ = a.CRSN(tt, edot, method='odeint', **kwargs) plt.plot(estress, e, label='odeint', marker='s', ms=2) tvals, estress, e, edot_ = a.CRSN(tt, edot, method='step', **kwargs) plt.plot(estress, e, label='step', marker='o', ms=2) plt.gca().set_xscale('log') leg = plt.gca().legend() leg.draggable() plt.show()