Source code for geotecha.consolidation.shanetal2012

# 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.

"""
Shan et al (2012) "Exact solutions for one-dimensional consolidation of
single-layer unsaturated soil".


"""
from __future__ import print_function, division

import numpy as np
from matplotlib import pyplot as plt
import sympy

def _integral_for_homogenous_case_linear_initial_condition():
    """equ 52 from shan et al 2012 for depth-linear initial conditon"""
    z, H, M, utop, ubot = sympy.symbols('z, H, M, utop, ubot')


    f1 = utop + z * (ubot - utop) / H
    sn = sympy.sin(M * z / H)

    a = 2/H * sympy.integrate(f1 * sn, (z, 0,H))
    print(a)
    return


def _integral_for_exp_loading_dt():
    """equ 75 from shan et al 2012 for exponential loading diff wrt time"""

    #q(t) = q0 * exp[-b * t]
    M, eig, tau, t, q0, b= sympy.symbols('M, eig, tau, t, q0, b')

    f = q0 * (1-sympy.exp(-b * tau))
    f2 = sympy.exp(eig * M**2 * (t - tau))

    igral = sympy.integrate(sympy.diff(f, tau) * f2, (tau, 0, t), risch=False, conds='none')


    #wolfram:Integrate[Exp[g M**2 * t] * q0 * b * Exp[-b x] * Exp[-g * M**2 * x)], {x,0,t}]

    print('calculated', igral)

    print('manual', "-(b*q0*(exp(-b*t)-exp(eigs*M**2*t)))/(b+eigs*M**2)")

    return


def _integral_for_sin_loading_dt():
    """equ 75 from shan et al 2012 for sin loading diff wrt time"""

    #q(t) = q0 * sin(omega * t)
    r, tau, t, q0, omega= sympy.symbols('r, tau, t, q0, omega')

    f = q0 * sympy.sin(omega * tau)
    f2 = sympy.exp(-r * tau)

    igral = sympy.integrate(sympy.diff(f, tau) * f2, (tau, 0, t), risch=False, conds='none')

    #wolfram: Integrate[Exp[g M**2 * t] * q0 * D[Sin[k*x], x] * Exp[-g * M**2 * x)], {x,0,t}]
    print('calculated', igral)
    print('manual', "(omega*q0*(-eigs*M**2*cos(omega*t)+eigs*M**2*exp(eigs*M**2*t)+omega*sin(omega*t)))/(eigs**2*M**4+omega**2)")
    return


def _integral_for_exp_loading():
    """equ 75 from shan et al 2012 for exponential loading"""

    #q(t) = q0 * exp[-b * t]
    M, eig, tau, t, q0, b= sympy.symbols('M, eig, tau, t, q0, b')

    f = q0 * (1-sympy.exp(-b * tau))
    f2 = sympy.exp(eig * M**2 * (t - tau))

    igral = sympy.integrate(f * f2, (tau, 0, t), risch=False, conds='none')


    #wolfram:Integrate[Exp[g M**2 * t] * q0 *(1 - Exp[-b x]) * Exp[-g * M**2 * x)], {x,0,t}]

    print('calculated', igral)

    print('manual', "(q0*(exp(-b*t)-exp(eigs*M**2*t)))/(b+eigs*M**2)+(q0*(exp(eigs*M**2*t)-1))/(eigs*M**2)")

    return


def _integral_for_sin_loading():
    """equ 75 from shan et al 2012 for sin loading"""

    #q(t) = q0 * sin(omega * t)
    r, tau, t, q0, omega= sympy.symbols('r, tau, t, q0, omega')

    f = q0 * sympy.sin(omega * tau)
    f2 = sympy.exp(-r * tau)

    igral = sympy.integrate(f, tau * f2, (tau, 0, t), risch=False, conds='none')

    #wolfram: Integrate[Exp[g M**2 * t] * q0 * Sin[k*x] * Exp[-g * M**2 * x)], {x,0,t}]
    print('calculated', igral)
    print('manual', "-(q0*(-omega*exp(eigs*M**2*t)+eigs*M**2*sin(omega*t)+omega*cos(omega*t)))/(eigs**2*M**4+omega**2)")
    return



[docs]def shanetal2012(z, t, H, Cw, Cvw, Ca, Cva, drn=1, Csw=0, Csa=0, uwi=(0, 0), uai=(0, 0), nterms=100, f=None, f1=None, f2=None, f3=None, f4=None): """1D unsaturated consolidation Features: - Unsaturated soil. - Vertical flow. - Soil properties constant with time. - Initial pore pressure distribution is linear with depth. Load is uniform with depth but can be sinusoidal with time, or exponential with time. - Drainage boundaries in air and water phase can be pervious, impervious or piecewise linear with time or sinusoidal with time or exponential with time. - Pore pressure vs depth in air and water at various times. Parameters ---------- z : float or 1d array/list of float Depth values for output. t : float or 1d array/list of float Time values for output H : float Drainage path length. Cw : float Cw = (1 - (m2w/m1kw)) / (m2w - m1kw) Cvw : float Cvw = kw / (gamw * m2w) Ca : float Ca = (m2a/m1ka) / (1 - m2a/m1ka - n(1-S)/(ua_*m1ka)) Cva : float Cva = ka / {(wa/(R*T)) * m1ka*ua_*[1 - m2a/m1ka - n(1-S)/(ua_*m1ka)]} where wa=molecular mass of air= 28.966e-3 kg/mol for air, R=universal gas constant=8.31432 J/(mol.K), T = absolute temperature in Kelvin=273.16+t0 (K), t0=temperature in celsius, ua_= absolute air pressure=ua+uatm (kPa), ua=guage air pressure, uatm= atmospheric air pressure=101 kPa. When ua is small or rapidly dissipates during consolidation ua_ can be considered a constant; so let ua_=uatm drn : int, optional Drainage condition. drn=0 is PTPB, drn=1 is PTIB. Default drn=1. Csw : float, optional Csw = m1kw/m2w default=0. Csa : float, optional Csa = (m2a/m1ka) / (1 - m2a/m1ka - n(1-S)/(ua_*m1ka)) Default=0, uwi : 2-element tuple, optioanl Initial pore water pressure at top and bottom of soil. Initial pore water pressure within soil is assumed to vary linearly between the top and bottom values. Default uwi=(0,0). uai : 2-element tuple, optioanl Initial pore air pressure at top and bottom of soil. Initial pore air pressure within soil is assumed to vary linearly between the top and bottom values. Default uai=(0,0). nterms : int, optional Number of terms to use in solution. Default nterms=100. f : dict, optional Ditionary describing a loading function. Default f=None i.e. no load e.g. f = {'type': 'exp', 'q0': 100, 'b': 0.00005} is a load described by q(t) = q0 * exp[-b * t]. f = {'type': 'sin', 'q0': 100, 'omega': 2*np.pi/1e8} is a load described by q(t) = q0 * sin(omega*t). f1, f2 : dict, optional dict describing loading function for uwtop and uatop. Default f1=f2==None i.e. no load. f3, f4 : dict, optional dict describing loading function for uwbot and uabot. Default f3=f4==None i.e. no load. Returns ------- porw, pora : 2d array of float Pore pressure at depth and time in water and air phase por is an array of size (len(z), len(t)). References ---------- .. [1] Shan, Zhendong, Daosheng Ling, and Haojiang Ding. 2012. 'Exact Solutions for One-dimensional Consolidation of Single-layer Unsaturated Soil'. International Journal for Numerical and Analytical Methods in Geomechanics 36 (6): 708-22. doi:10.1002/nag.1026. """ def E_exp_load_dt(q0,b, t, M, eigs): exp=np.exp return -(b*q0*(exp(-b*t)-exp(eigs*M**2*t)))/(b+eigs*M**2) def E_sin_load_dt(q0,omega, t, M, eigs): exp=np.exp sin = np.sin cos = np.cos return ((omega*q0*(-eigs*M**2*cos(omega*t)+eigs*M**2*exp(eigs*M**2*t)+ omega*sin(omega*t)))/(eigs**2*M**4+omega**2)) def E_exp_load(q0,b, t, M, eigs): exp=np.exp return ((q0*(exp(-b*t)-exp(eigs*M**2*t)))/(b+eigs*M**2)+ (q0*(exp(eigs*M**2*t)-1))/(eigs*M**2)) def E_sin_load(q0,omega, t, M, eigs): exp=np.exp sin = np.sin cos = np.cos return (-(q0*(-omega*exp(eigs*M**2*t)+eigs*M**2*sin(omega*t)+ omega*cos(omega*t)))/(eigs**2*M**4+omega**2)) permitted_loads = ['exp', 'sin'] permitted_parameters={'exp': ['q0','b'], 'sin': ['q0', 'omega']} f_list = ['f','f1','f2','f3','f4'] for fname, ff in zip(f_list,[f,f1,f2,f3,f4]): if not ff is None: if not ff['type'] in permitted_loads: raise ValueError("{} load function must be one " "of type {}; one of yours is " "'{}'".format(fname, permitted_loads, ff['type'])) for p in permitted_parameters[ff['type']]: if not p in ff: raise ValueError("'{}' is missing from the " "{} '{}' loading " "function dict".format(p, fname, ff['type'])) Cmat = np.array([[1, Cw], [Ca, 1]], dtype=float) Kmat = np.array([[Cvw, 0], [0, Cva]], dtype=float) a1 = np.sqrt(Cva**2 - 2 * Cva * Cvw + Cvw**2 + 4 * Ca * Cw * Cva * Cvw) r1 = 0.5 * (Cva + Cvw - a1)/ (1-Ca*Cw) r2 = 0.5 * (Cva + Cvw + a1)/ (1-Ca*Cw) sin = np.sin cos = np.cos if drn==1: M = ((2 * np.arange(nterms) + 1) / 2 * np.pi) elif drn==0: M = (np.arange(1,nterms+1) * np.pi) ICmat = np.linalg.inv(Cmat) Nmat = np.dot(ICmat,Kmat) eigs, v = np.linalg.eig(Nmat) v = np.asarray(v) if 0: #this is just to demonstrate that it is possible to do the #eigenvalues and eigenvectors analytically. a1 = np.sqrt(Cva**2 - 2 * Cva * Cvw + Cvw**2 + 4 * Ca * Cw * Cva * Cvw) r1 = 0.5 * (Cva + Cvw - a1)/ (1-Ca*Cw) r2 = 0.5 * (Cva + Cvw + a1)/ (1-Ca*Cw) eigs = np.array([r1,r2]) v = np.array([[r1-Nmat[1,1], r2 - Nmat[1,1]], [Nmat[1,0], Nmat[1,0]]]) #initial condition gw = 2 / H * (-H*uwi[1]*cos(M)/M + H*uwi[0]/M + H*uwi[1]*sin(M)/M**2 - H*uwi[0]*sin(M)/M**2) ga = 2 / H * (-H*uai[1]*cos(M)/M + H*uai[0]/M + H*uai[1]*sin(M)/M**2 - H*uai[0]*sin(M)/M**2) X0 = np.empty((1, 1, nterms, 2)) X0[0, 0, :, 0] = gw X0[0, 0, :, 1] = ga #z, t, M, uw & ua Iv = np.linalg.inv(v) Iv_X0 = np.einsum('lp,ijkp', Iv, X0) E = np.empty((1, len(t), nterms, 2), dtype=float) E[0, :, :, :] = np.exp( M[None, None, :, None] **2 / H**2 * eigs[None, None, None, :] * t[None, :, None, None]) v_E_Iv_X0 = np.einsum('lp,ijkp', v, E * Iv_X0) S = np.sin(M[None, None, :,None] * z[:,None, None, None]/H) u = np.sum(S * v_E_Iv_X0, axis=2) #dsig E = np.zeros((1, len(t), nterms, 2), dtype=float) if not f is None: Csig = np.array([Csw, Csa], dtype=float) ICmat_Csig = np.dot(ICmat, Csig) igral_S = 2 * (1 - np.cos(M)) / M Hk = igral_S[None, None, :, None] * ICmat_Csig[None, None, None, :] Iv_X = np.einsum('lp,ijkp', Iv, Hk) if f['type']=='exp': q0 = f['q0'] b = f['b'] E[0, :, :, :] = E_exp_load_dt(q0, b, t[None, :, None, None], M[None, None, :, None]/H, eigs[None, None, None, :]) elif f['type']=='sin': q0 = f['q0'] omega = f['omega'] E[0, :, :, :] = E_sin_load_dt(q0, omega, t[None, :, None, None], M[None, None, :, None] / H, eigs[None, None, None, :]) v_E_Iv_X = np.einsum('lp,ijkp', v, E * Iv_X) u += np.sum(S * v_E_Iv_X, axis=2) #f1 diff wrt t. i.e f1*(h-z)/h, water top if not f1 is None: ff=f1 Csig = np.array([-1, -Ca], dtype=float) ICmat_Csig = np.dot(ICmat, Csig) if drn==0: igral_S = (2*(M-sin(M)))/M**2 #2/H * Integrate[Sin[M*x / H] * (H-x)/H, {x, 0, H}] elif drn==1: igral_S = 2 * (1 - np.cos(M)) / M Hk = igral_S[None, None, :, None] * ICmat_Csig[None, None, None, :] Iv_X = np.einsum('lp,ijkp', Iv, Hk) if ff['type']=='exp': q0 = ff['q0'] b = ff['b'] E[0, :, :, :] = E_exp_load_dt(q0, b, t[None, :, None, None], M[None, None, :, None]/H, eigs[None, None, None, :]) #add in homogenizing term if drn==0: u[:,:,0] += (q0 * (1 - np.exp(-b * t[None, :])) * (1 - z[:, None]/H)) elif drn==1: u[:,:,0] += q0 * (1 - np.exp(-b * t[None, :])) * (1) elif ff['type']=='sin': q0 = ff['q0'] omega = ff['omega'] E[0, :, :, :] = E_sin_load_dt(q0, omega, t[None, :, None, None], M[None, None, :, None] / H, eigs[None, None, None, :]) #add in homogenizing term if drn==0: u[:,:,0] += q0 * np.sin(omega * t[None, :]) * (1-z[:, None]/H) elif drn==1: u[:,:,0] += q0 * np.sin(omega * t[None, :]) * (1) v_E_Iv_X = np.einsum('lp,ijkp', v, E * Iv_X) u += np.sum(S * v_E_Iv_X, axis=2) #f2 diff wrt t. i.e f2*(h-z)/h, air top if not f2 is None: ff=f2 Csig = np.array([-Cw, -1], dtype=float) ICmat_Csig = np.dot(ICmat, Csig) if drn==0: igral_S = (2*(M-sin(M)))/M**2 #2/H * Integrate[Sin[M*x / H] * (H-x)/H, {x, 0, H}] elif drn==1: igral_S = 2 * (1 - np.cos(M)) / M Hk = igral_S[None, None, :, None] * ICmat_Csig[None, None, None, :] Iv_X = np.einsum('lp,ijkp', Iv, Hk) if ff['type']=='exp': q0 = ff['q0'] b = ff['b'] E[0, :, :, :] = E_exp_load_dt(q0, b, t[None, :, None, None], M[None, None, :, None]/H, eigs[None, None, None, :]) #add in homogenizing term if drn==0: u[:,:,1] += (q0 * (1 - np.exp(-b * t[None, :])) * (1 - z[:, None]/H)) elif drn==1: u[:,:,1] += q0 * (1 - np.exp(-b * t[None, :])) * (1) elif ff['type']=='sin': q0 = ff['q0'] omega = ff['omega'] E[0, :, :, :] = E_sin_load_dt(q0, omega, t[None, :, None, None], M[None, None, :, None] / H, eigs[None, None, None, :]) #add in homogenizing term if drn==0: u[:,:,1] += q0 * np.sin(omega * t[None, :]) * (1-z[:, None]/H) elif drn==1: u[:,:,1] += q0 * np.sin(omega * t[None, :]) * (1) v_E_Iv_X = np.einsum('lp,ijkp', v, E * Iv_X) u += np.sum(S * v_E_Iv_X, axis=2) #f3 diff wrt t. i.e f3*z/H, water bot if not f3 is None: ff=f3 Csig = np.array([-1, -Ca], dtype=float) ICmat_Csig = np.dot(ICmat, Csig) if drn==0: igral_S = (2*(sin(M)-M*cos(M)))/M**2 #2/H * Integrate[Sin[M*x / H] * (x)/H, {x, 0, H}] elif drn==1: igral_S = (2*H*(sin(M)-M*cos(M)))/M**2 #2/H * Integrate[Sin[M*x / H] * x, {x, 0, H}] Hk = igral_S[None, None, :, None] * ICmat_Csig[None, None, None, :] Iv_X = np.einsum('lp,ijkp', Iv, Hk) if ff['type']=='exp': q0 = ff['q0'] b = ff['b'] E[0, :, :, :] = E_exp_load_dt(q0, b, t[None, :, None, None], M[None, None, :, None]/H, eigs[None, None, None, :]) #add in homogenizing term if drn==0: u[:,:,0] += (q0 * (1 - np.exp(-b * t[None, :])) * (z[:, None]/H)) elif drn==1: u[:,:,0] += q0 * (1 - np.exp(-b * t[None, :])) * (z[:, None]) elif ff['type']=='sin': q0 = ff['q0'] omega = ff['omega'] E[0, :, :, :] = E_sin_load_dt(q0, omega, t[None, :, None, None], M[None, None, :, None] / H, eigs[None, None, None, :]) #add in homogenizing term if drn==0: u[:,:,0] += q0 * np.sin(omega * t[None, :]) * (z[:, None]/H) elif drn==1: u[:,:,0] += q0 * np.sin(omega * t[None, :]) * (z[:, None]) v_E_Iv_X = np.einsum('lp,ijkp', v, E * Iv_X) u += np.sum(S * v_E_Iv_X, axis=2) #f4 diff wrt t. i.e f4*z, air bot if not f4 is None: ff=f4 Csig = np.array([-Cw, -1], dtype=float) ICmat_Csig = np.dot(ICmat, Csig) if drn==0: igral_S = (2*(sin(M)-M*cos(M)))/M**2 #2/H * Integrate[Sin[M*x / H] * (x)/H, {x, 0, H}] elif drn==1: igral_S = (2*H*(sin(M)-M*cos(M)))/M**2 #2/H * Integrate[Sin[M*x / H] * x, {x, 0, H}] Hk = igral_S[None, None, :, None] * ICmat_Csig[None, None, None, :] Iv_X = np.einsum('lp,ijkp', Iv, Hk) if ff['type']=='exp': q0 = ff['q0'] b = ff['b'] E[0, :, :, :] = E_exp_load_dt(q0, b, t[None, :, None, None], M[None, None, :, None]/H, eigs[None, None, None, :]) #add in homogenizing term if drn==0: u[:,:,1] += (q0 * (1 - np.exp(-b * t[None, :])) * (z[:, None]/H)) elif drn==1: u[:,:,1] += q0 * (1 - np.exp(-b * t[None, :])) * (z[:, None]) elif ff['type']=='sin': q0 = ff['q0'] omega = ff['omega'] E[0, :, :, :] = E_sin_load_dt(q0, omega, t[None, :, None, None], M[None, None, :, None] / H, eigs[None, None, None, :]) #add in homogenizing term if drn==0: u[:,:,1] += q0 * np.sin(omega * t[None, :]) * (z[:, None]/H) elif drn==1: u[:,:,1] += q0 * np.sin(omega * t[None, :]) * (z[:, None]) v_E_Iv_X = np.einsum('lp,ijkp', v, E * Iv_X) u += np.sum(S * v_E_Iv_X, axis=2) uw = u[:,:,0] ua = u[:,:,1] return uw, ua
######################### if __name__ == '__main__': kw = 1e-10 ka = 10 * kw H=10 Cw=-0.75 Cvw=-5e-8 Ca = -0.0775134 Cva=-64504.4 * ka drn=1 Csw=0.25 Csa=0.155027 uwi=(40, 40) uai=(20, 20) nterms=200 f=f1=f2=f3=f4=None # f = {'type':'exp', 'q0':100.0, 'b':0.00005} # f = {'type':'sin', 'q0':100.0, 'omega':2*np.pi / 1e8} f3 = {'type':'sin', 'q0':100.0, 'omega':2*np.pi / 1e9} z = np.array([0, 3.0, 5.0, 8.0, 10.0]) t = np.array([1e6,3e6, 1e8,3e8, 1e9, 2e9 ]) z = np.linspace(0, H, 51) # z = np.array([4]) # t = np.logspace(2,10,400) # t = np.array([0, 1e6]) porw, pora = shanetal2012(z, t, H, Cw, Cvw, Ca, Cva, drn, Csw, Csa, uwi, uai, nterms, f=f, f1=f1, f2=f2, f3=f3, f4=f4) if 1: print('\nporw', repr(porw)) print('\npora', repr(pora)) labels = ['water pore pressure', 'Air pore pressure'] title = 'drn={}, ka/kw={:.3g}'.format(drn, ka/kw) for p, lab in zip([porw, pora], labels): fig=plt.figure() ax = fig.add_subplot(1,1,1) for j, t_ in enumerate(t): ax.plot(p[:,j], z, marker='o', label="t={0:.3g}".format(t[j])) ax.set_xlabel(lab) ax.set_ylabel('Depth') ax.invert_yaxis() # ax.set_xlim(0,1) ax.grid() ax.set_title(title) leg = plt.legend(loc=3 ) leg.draggable() fig = plt.figure() ax=fig.add_subplot('111') ax.plot(t, porw[len(z)//2], 'bs', ls='-', ms=3,label='water') ax.plot(t, pora[len(z)//2], 'g*', ls='-', ms=3,label='air') ax.set_xlabel('Time') ax.set_ylabel('Pore pressure at z={}'.format(z[len(z)//2])) ax.set_xscale('log') ax.set_xlim(0) ax.grid() ax.set_title(title) leg = plt.legend(loc=3 ) leg.draggable() # fig=plt.figure() # ax = fig.add_subplot(1,1,1) # ax.plot(t, avp, marker='o',) # ax.set_xlabel('time') # ax.set_ylabel('Overall average pore pressure') # ax.invert_yaxis() ## ax.set_xlim(0,1) # ax.grid() # # fig=plt.figure() # ax = fig.add_subplot(1,1,1) # ax.plot(t, settle, marker='o',) # ax.set_xlabel('time') # ax.set_ylabel('settlement') # ax.invert_yaxis() ## ax.set_xlim(0,1) # ax.grid() plt.show()