Chapter 6 - Compaction and its inherent length scale¶
The compaction-press problem¶
%matplotlib inline
import numpy as np
import scipy.sparse as sps
import scipy.sparse.linalg as spla
from scipy.optimize import brentq
import matplotlib.pyplot as plt
from matplotlib import animation
import warnings
warnings.filterwarnings('ignore')
plt.rcParams["animation.html"] = "jshtml"
plt.ioff();
The solution to the Filter Press problem was given as
def compaction_rate(z):
return -np.exp(z)
The compact rate function, equation \(\eqref{eq:filterpress-cmprate}\), is plotted in Figure 6.1 below.
fig, ax = plt.subplots()
fig.set_size_inches(12.0, 4.5)
z = np.linspace(-8, 0.0, 1000)
C = compaction_rate(z)
ax.plot([0., 0], [-2., 2], '--k', linewidth=1)
ax.plot([-8.0, 1.0], [0., 0], ':k', linewidth=1)
ax.plot(z, C, '-k', linewidth=3)
ax.set_xlabel('$z/\delta_0$', fontsize=20)
ax.set_ylabel('$\mathcal{C}\,\delta_0/W_0$', fontsize=20)
ax.text(-7.0, -0.2, '$W_0 \Rightarrow$', fontsize=20)
ax.set_xlim(-8.0, 0.3)
ax.set_ylim(-1.1, 0.1)
ax.tick_params(axis='both', which='major', labelsize=13)
fig.text(0.5, -0.1, "Figure 6.1", fontsize=20, ha='center')
plt.show()
The permeability-step problem¶
The porosity is given by the piece-wise constant function,
where \(f_i\) (\(i=p,m\)) are constants that multiply the reference porosity, chosen such that \(f_i-\vert 1\vert\ll 1\).
The solution of the permeability-step problem was given as
def cmprate(fp, fm, f0, n, z):
cmp = (
np.power(fm, n) * (1.0 - fm * f0) - np.power(fp, n) * (1.0 - fp * f0)
)/(
np.power(fm, 0.5*n) + np.power(fp, 0.5*n)
)
return np.asarray([
cmp * np.exp(-z_ * np.power(fp, -0.5 * n))
if z_ > 0.0 else cmp * np.exp(z_ * np.power(fm, 0.5 * n))
for z_ in z
])
The one-dimensional segregation flux \(q \equiv \phi(w-W)\) is given by
def segflux(fp, fm, f0, n, z):
cmp = (
np.power(fm, n) * (1. - fm * f0) - np.power(fp, n) * (1. - fp * f0)
)/(
np.power(fm, 0.5 * n) + np.power(fp, 0.5 * n)
)
return np.asarray([
np.power(fp, n) * (1. - fp * f0) +
cmp * np.power(fp, 0.5 * n) * np.exp(-z_ * np.power(fp, -0.5 * n))
if z_ > 0.0 else
np.power(fm, n)*(1.0 - fm * f0) -
cmp * np.power(fm, 0.5 * n)*np.exp(z_ * np.power(fm, -0.5 * n))
for z_ in z
])
Figure 6.2 plots the porosity, compaction rate, and segregation flux for the permeability-step problem. (a) A porosity increase with \(z\): \(f_m=0.75\), \(f_p=1.25\). (b) Scaled compaction rate and segregation flux for the porosity increase. (c) A porosity decrease with \(z\): \(f_m=1.25\), \(f_p=0.75\). (d) Scaled compaction rate and segregation flux for the porosity decrease.
fig, ax = plt.subplots(2, 2)
fig.set_size_inches(12., 18.)
# Figure 6.2a
fm, fp = 0.85, 1.15
z = np.linspace(-4., 4., 1000)
f = np.asarray([fm if z_ < 0.0 else fp for z_ in z])
ax[0, 0].plot(f, z, '-k', linewidth=2)
ax[0, 0].plot(np.ones(1000), z, ':k', linewidth=1)
ax[0, 0].set_ylabel(r'$z/\delta_0$', fontsize=20)
ax[0, 0].set_xlabel(r'$\phi/\phi_0$', fontsize=20)
ax[0, 0].tick_params(axis='both', which='major', labelsize=13)
ax[0, 0].text(
1.13, -4., '(a)', fontsize=18,
verticalalignment='bottom', horizontalalignment='left'
)
# Figure 6.2b
zmax = 4
n = 3
f0 = 0.01
z = np.linspace(-4., 4., 1000)
C = cmprate(fp, fm, f0, n, z)
fwmW = segflux(fp, fm, f0, n, z)
ax[0, 1].plot(
C, z, '-k', linewidth=2, label='$\mathcal{C}(z)\delta_0/(\phi_0w_0)$'
)
ax[0, 1].plot(fwmW, z,'--k', linewidth=2, label='$q(z)/(\phi_0w_0)$')
ax[0, 1].legend(fontsize=15, loc='upper center')
ax[0, 1].tick_params(axis='both', which='major', labelsize=13)
ax[0, 1].text(
1.37, -4., '(b)', fontsize=18,
verticalalignment='bottom', horizontalalignment='left'
)
# Figure 6.2c
fm, fp = 1.15, 0.85
f = np.asarray([fm if z_ < 0.0 else fp for z_ in z])
ax[1, 0].plot(f, z, '-k', linewidth=2)
ax[1, 0].plot(np.ones(1000), z, ':k', linewidth=1)
ax[1, 0].set_xticks((0.85, 1.0, 1.15))
ax[1, 0].set_ylabel(r'$z/\delta_0$', fontsize=20)
ax[1, 0].set_xlabel(r'$\phi/\phi_0$', fontsize=20)
ax[1, 0].tick_params(axis='both', which='major', labelsize=13)
ax[1, 0].text(
1.13, 4., '(c)', fontsize=18,
verticalalignment='bottom', horizontalalignment='left'
)
# Figure 6.2d
zmax = 4
n = 3
f0 = 0.01
z = np.linspace(-4., 4., 1000)
C = cmprate(fp, fm, f0, n, z)
fwmW = segflux(fp, fm, f0, n, z)
ax[1, 1].plot(C, z, '-k', linewidth=2, label='$\mathcal{C}(z)\delta_0/(\phi_0w_0)$')
ax[1, 1].plot(fwmW, z,'--k', linewidth=2, label='$q(z)/(\phi_0w_0)$')
ax[1, 1].tick_params(axis='both', which='major', labelsize=13)
ax[1, 1].legend(fontsize=15, loc='lower center')
ax[1, 1].text(
1.4, 4., '(d)', fontsize=18,
verticalalignment='bottom', horizontalalignment='left'
)
fig.supxlabel("Figure 6.2", fontsize=20)
plt.show()
Propagation of small porosity disturbances¶
The phase (\(c_p\)) and group (\(c_g\)) velocities are given as
The phase (\(c_p\)) and group (\(c_g\)) velocities are plotted in Figure 6.3 below as a function of the wavelength \(\lambda=2\pi/k\).
lambdas = np.logspace(-2., 3., 1000)
k = 2 * np.pi / lambdas
n = 2.
cp_2 = n/(k ** 2 + 1)
cg_2 = cp_2 - 2. * n * (k**2) / ((k**2 + 1.0) ** 2)
n = 3.
cp_3 = n/(k**2 + 1)
cg_3 = cp_3 - 2. * n * (k**2) / ((k**2 + 1.) ** 2)
fig, ax = plt.subplots()
fig.set_size_inches(9., 9.)
ax.semilogx(lambdas, cp_2, '--k', linewidth=3, label='$c_p, n=2$')
ax.semilogx(lambdas, cg_2, '--k', linewidth=1, label='$c_g, n=2$')
ax.semilogx(lambdas, cp_3, '-k', linewidth=3, label='$c_p, n=3$')
ax.semilogx(lambdas, cg_3, '-k', linewidth=1, label='$c_g, n=3$')
ax.plot([10**(-5), 10**5], [0., 0.], '-', color=[0.75, 0.75, 0.75])
ax.plot([2. * np.pi, 2. * np.pi],[-10., 10.],'-', color=[0.75, 0.75, 0.75])
ax.set_xlabel(r'$\lambda$', fontsize=20)
ax.set_ylabel(r'$c$', fontsize=20)
ax.set_ylim(-0.5, 3.1)
ax.set_xlim(10**(-2), 10**3)
ax.tick_params(axis='both', which='major', labelsize=13)
fig.supxlabel("Figure 6.3", fontsize=20)
plt.legend(fontsize=20)
plt.show()
Magmatic solitary waves¶
The solitary wave speed \(v\) is computed as
The dimensionless solitary wave speed \(\upsilon\) computed with the equation above as a function of wave amplitude \(\Lambda^*\) relative to the background porosity is plotted in Figure 6.4 below.
fig, ax = plt.subplots()
fig.set_size_inches(9., 9.)
lambdas = np.linspace(1.001, 4., 1000)
c2 = (lambdas - 1.0)**2 / (lambdas * np.log(lambdas) - lambdas + 1.)
c3 = 2. * lambdas + 1.
ax.plot(lambdas, c2, '--k', linewidth=2, label='$n=2$')
ax.plot(lambdas, c3, '-k', linewidth=2, label='$n=3$')
ax.set_xlim(1., 4.)
ax.set_ylim(0., 7.)
ax.set_xlabel(r'$\Lambda^*$', fontsize=20)
ax.set_ylabel(r'$\upsilon$', fontsize=20)
ax.tick_params(axis='both', which='major', labelsize=13)
ax.legend(fontsize=20)
fig.supxlabel("Figure 6.4", fontsize=20)
plt.show()
The normalized porosity profile is obtained through numerical inversion of
which is implemented in Python as
def porosity(f, z, A):
sqrtAf = np.sqrt(A - f)
sqrtAm1 = np.sqrt(A - 1.)
return z + np.sqrt(A + 0.5) * (
-2. * sqrtAf +
np.log(
(sqrtAm1 - sqrtAf)/(sqrtAm1 + sqrtAf)
) / sqrtAm1
)
Profiles of normalised porosity perturbation for solitary waves of various amplitude are plotted in Figures 6.5a below. We set \(n=3\) in all cases.
fig, ax = plt.subplots()
fig.set_size_inches(15., 9.)
zmax = 25.
zs = np.linspace(0., zmax, 100)
zm = 0.5 * (zs[1:] + zs[:-1])
AsLS = {2.: '-.', 4.: '-', 6.:'--', 8.: ':'}
phi = {}
for A, ls in AsLS.items():
phi[A] = np.asarray(
[brentq(lambda f: porosity(f, z, A), 1.000000001, A) for z in zs]
)
for A, ls in AsLS.items():
ax.plot(
np.concatenate((-zs[::-1], zs), axis=0),
np.concatenate((phi[A][::-1], phi[A]), axis=0),
'k', label='$\Lambda^* = '+str(int(A))+'$',
linestyle=ls, linewidth=2
)
ax.set_xlim(-zmax, zmax)
ax.set_ylim(0., 9.)
ax.grid()
ax.set_xlabel(r'$Z$, compaction lengths', fontsize=20)
ax.set_ylabel(r'$\Lambda$', fontsize=20)
ax.tick_params(axis='both', which='major', labelsize=15)
ax.legend(fontsize=20)
fig.supxlabel("Figure 6.5a", fontsize=20)
plt.show()
Profiles of compaction rate for solitary waves of various amplitude. \(n=3\) in all cases. The gravity vector \(\gravity\) points to the left, as indicated in the Figure 6.5b below.
fig, ax = plt.subplots()
fig.set_size_inches(15., 9.)
zmax = 25.0
zs = np.linspace(0., zmax, 100)
zm = 0.5 * (zs[1:] + zs[:-1])
AsLS = {2: '-.', 4: '-', 6:'--', 8: ':'}
C = {}
for A, ls in AsLS.items():
# phi is defined in the previous cell
C[A] = -(2. * A + 1.) * (phi[8][1:] - phi[8][0:-1]) / (zs[1] - zs[0])
for A, ls in AsLS.items():
plt.plot(
np.concatenate((-zm[::-1], zm), axis=0),
np.concatenate((-C[A][::-1], C[A]), axis=0),
'k', linestyle=ls, linewidth=2
)
ax.set_xlim(-zmax, zmax)
ax.set_ylim(-10., 10.)
ax.grid()
ax.set_xlabel(r'Z, compaction lengths', fontsize=20)
ax.set_ylabel(r'$\lambda$', fontSize=20)
ax.tick_params(axis='both', which='major', labelsize=15)
fig.supxlabel("Figure 6.5b", fontsize=20)
plt.show()
Solitary-wave trains¶
The equations below admit a nonlinear solitary wave solution:
which is plotted below:
def get_compaction_rate_dirichlet(phi, n, dz, phi0):
n_ = len(phi)
perm = np.sqrt((phi[:-1] ** n) * (phi[1:] ** n))
# rhs
b = np.zeros(n_, dtype=float)
b[1:-1] = phi0 * dz * (perm[1:] - perm[:-1])
# matrix
offsets = np.array([0, -1, 1])
data = np.zeros(3 * n_).reshape(3, n_)
data[0, 0] = data[0, -1] = 1
data[0, 1:-1] = -(perm[:-1] + perm[1:] + dz * dz) # diagonal
data[1, 0:-2] = perm[:-1] # sub-diagonal
data[2, 2:] = perm[1:] # sup-diagonal
mtx = sps.dia_matrix((data, offsets), shape=(n_, n_))
mtx = mtx.tocsr()
# solution of linear system
Cmp = spla.dsolve.spsolve(mtx, b)
return Cmp
def solitary_wave_update_porosity(PhiOld, n, phi0, dz, dt):
Cmp = get_compaction_rate_dirichlet(PhiOld, n, dz, phi0)
PhiNew = PhiOld + dt * Cmp / phi0
Cmp = 0.5 * (Cmp + get_compaction_rate_dirichlet(PhiNew, n, dz, phi0))
PhiNew = PhiOld + dt * Cmp / phi0
return PhiNew
fig, ax = plt.subplots(figsize=(4.5, 9.0))
ln, = plt.plot([], [], 'k')
phi0 = 0.05 # background porosity
A = 1.5 # amplitude of step
zmax = 150. # total size of domain
z0 = zmax / 5. # location of step
zw = 10. # width of step
n = 3. # permeability exponent
Nz = 1000 # number of grid points
cfl = 1. # courant limit on time-step
tmax = 70. # maximum time
# initial condition
z = np.linspace(0.0, zmax, Nz)
f = 1. - (A - 1.) * (1 + np.tanh((z - z0) / zw)) / 2.
# derived parameters
dz = z[1] - z[0]
V = (1. - phi0 ** n) / (1. - phi0)
dt = cfl * dz / V
Nt = int(np.ceil(tmax / dt))
def init():
ax.set_xlim(0.4, 1.5)
ax.set_ylim(0., zmax)
ax.tick_params(axis='both', which='major', labelsize=13)
return ln,
def update(frame):
global f
f = solitary_wave_update_porosity(f, n, phi0, dz, dt)
ln.set_data(f, z)
return ln,
animation.FuncAnimation(
fig, update, frames=np.linspace(0, tmax, Nt), init_func=init, blit=True
)