Skip to content

Commit 8e5977f

Browse files
schiffman1D.m
1 parent d3e8999 commit 8e5977f

1 file changed

Lines changed: 386 additions & 0 deletions

File tree

examples/matlab/schiffman1D.m

Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
% -------------------------------------------------------------------------
2+
% Schiffman One-Dimensional Consolidation Model (Time-Dependent Loading)
3+
%
4+
% This simulation models the transient pore pressure response in a saturated
5+
% porous medium under a time-varying compressive face load.
6+
%
7+
% A linearly increasing load P(t) is applied at the left boundary (x = 0 m),
8+
% ramping from 0 to P0 = 10 MPa over a duration t0 (e.g., 1 hour), and
9+
% remaining constant at P0 thereafter.
10+
%
11+
% The porous matrix is fully saturated and deformation is driven by both
12+
% pore pressure diffusion and volumetric strain coupling, consistent with
13+
% Biot’s theory. The model includes a source term proportional to dP/dt.
14+
%
15+
% Zero displacement is enforced at the right boundary (x = L = 25 m),
16+
% representing a fixed support. Fluid drainage is allowed only at the
17+
% loaded boundary (x = 0 m), where the pressure is prescribed as p(0, t) = P(t).
18+
%
19+
% The MOLE Laplacian operator is used to compute the spatial pressure gradient.
20+
% The governing PDE includes a time-dependent source term:
21+
%
22+
% ∂p/∂t = Cv ∂²p/∂x² + [α / (Ss*Ks + α²)] * dP(t)/dt
23+
%
24+
% where Cv is the consolidation coefficient, α is the Biot coefficient,
25+
% Ss is the specific storage, and Ks is the bulk modulus of the solid matrix.
26+
%
27+
% This model generalizes Terzaghi’s classical formulation by accommodating
28+
% dynamic loading. The numerical results are compared to an analytical
29+
% benchmark solution under static loading for verification.
30+
% -------------------------------------------------------------------------
31+
32+
%%
33+
clc;
34+
close all;
35+
36+
addpath('../../src/matlab'); % MOLE operator path
37+
38+
%% Parameters
39+
P0 = 10e6; % Face load in Pascals
40+
cf = 1e-4; % Diffusion constant
41+
l = 25; % Domain length in meters
42+
k = 2; % MOLE operator order
43+
m = 50; % Number of cells
44+
45+
% Spatial discretization
46+
a = 0; % Left boundary of the domain [m]
47+
b = l; % Right boundary of the domain [m]
48+
dx = (b - a)/m; % Cell width (uniform grid spacing) [m]
49+
xgrid = [a a+dx/2 : dx : b-dx/2 b]; % Staggered grid with ghost nodes at boundaries
50+
% xgrid has m+2 points:
51+
% - ghost cell at a (Dirichlet BC)
52+
% - m internal nodes
53+
% - ghost cell at b (Neumann BC)
54+
55+
% Times to evaluate (in hours)
56+
times_hr = [1, 10, 40, 70]; % Time snapshots in hours for comparison
57+
times_sec = times_hr * 3600; % Convert time points to seconds for simulation
58+
59+
% Time-dependent face load: P(t)
60+
P0 = 10e6; % Final face load in Pascals
61+
t0 = 3600; % Ramp time [s]
62+
P_face = @(t) (t < t0) .* (P0 * t / t0) + (t >= t0) .* P0;
63+
dPdt = @(t) (t > 0 & t < t0) .* (P0 / t0);
64+
65+
% Fluid properties
66+
K = 1e-12; % Permeability [m^2]
67+
mu = 1e-3; % Dynamic viscosity [Pa·s]
68+
rho = 1000; % Fluid density [kg/m^3]
69+
g = 9.81; % Gravity [m/s^2]
70+
Ks = 1e8; % Bulk modulus [Pa]
71+
alpha = 1; % Biot coefficient
72+
Ss = 1e-5; % [1/Pa] Specific storage coefficient
73+
A_src = alpha / (Ss * Ks + alpha^2); % Coefficient for dP/dt source term
74+
%% Numerical (MOLE) Solution
75+
L = lap(k, m, dx); % Mimetic Laplacian operator for diffusion
76+
G = grad(k, m, dx); % Mimetic gradient operator for Darcy flux
77+
78+
% Boundary modifications to Laplacian
79+
L(1,:) = 0; L(end,:) = 0;
80+
81+
p_numerical = zeros(length(xgrid), length(times_sec)); % Pressure field [Pa]
82+
q_numerical = zeros(m+1, length(times_sec)); % Darcy flux [m/s] (size = m+1)
83+
e_numerical = zeros(length(xgrid), length(times_sec)); % Strain field
84+
u_numerical = zeros(length(xgrid), length(times_sec)); % Displacement field
85+
86+
% Loop over each specified final time
87+
for i = 1:length(times_sec)
88+
t_final = times_sec(i);
89+
dt = 1.0;
90+
nsteps = round(t_final / dt);
91+
92+
% Uniform Initial Condition : p(x,0) = P0
93+
p = zeros(size(xgrid))';
94+
95+
% Time integration using Forward Euler
96+
for step = 1:nsteps
97+
t_current = (step - 1) * dt;
98+
dP_term = A_src * dPdt(t_current);
99+
source = dP_term * ones(size(p)); % Uniform source term
100+
p = p + dt * (cf * (L * p) + source); % Updated Euler step with source
101+
p(1) = P_face(t_current); % Time-varying Dirichlet BC at x = 0
102+
p(end) = p(end-1); % Neumann BC at x = L
103+
end
104+
105+
% Compute strain and displacement from final pressure
106+
e = (alpha * p - P0) / Ks;
107+
u = cumtrapz(xgrid, e); %cumulative integral using the trapezoidal rule.
108+
109+
% Store results
110+
e_numerical(:,i) = e;
111+
u_numerical(:,i) = u;
112+
p_numerical(:,i) = p;
113+
114+
% Compute darcy flux
115+
dpdx = G * p;
116+
q = - (K / mu) * (dpdx - rho * g);
117+
q_numerical(:,i) = q(1:m+1);
118+
119+
end
120+
121+
%% Mass Conservation Residual Evaluation
122+
123+
% Compute dp/dt and de/dt using backward differences
124+
dpdt = zeros(size(p_numerical));
125+
dedt = zeros(size(e_numerical));
126+
127+
for i = 2:length(times_sec)
128+
dt_local = times_sec(i) - times_sec(i-1);
129+
dpdt(:,i) = (p_numerical(:,i) - p_numerical(:,i-1)) / dt_local;
130+
dedt(:,i) = (e_numerical(:,i) - e_numerical(:,i-1)) / dt_local;
131+
end
132+
133+
% Compute divergence of q using MOLE div()
134+
divq = zeros(size(p_numerical)); % same shape as pressure field
135+
for i = 1:length(times_sec)
136+
qx = q_numerical(:,i); % q has m+1 values
137+
divq(:,i) = div(k, m, dx) * qx; % returns m+2 values (staggered grid)
138+
end
139+
140+
% Compute full mass balance residual
141+
residual = Ss * dpdt + alpha * dedt - divq;
142+
143+
%% Combined Numerical Output
144+
for i = 1:length(times_sec)
145+
fprintf('\nNumerical results at t = %.2f hr:\n', times_hr(i));
146+
fprintf('| x (m) | Numerical p [Pa] | Darcy Flux [m/s] | Residual [1/s] |\n');
147+
fprintf('|---------------|------------------|------------------|------------------|\n');
148+
for j = 1:m+1
149+
if i == 1
150+
fprintf('| %13.6f | %16.6e | %16.6e | %16s |\n', ...
151+
xgrid(j), p_numerical(j,i), q_numerical(j,i), 'N/A');
152+
else
153+
fprintf('| %13.6f | %16.6e | %16.6e | %16.6e |\n', ...
154+
xgrid(j), p_numerical(j,i), q_numerical(j,i), residual(j,i));
155+
end
156+
end
157+
end
158+
159+
160+
%% Analytical Solution
161+
N_max = 100; % Number of Fourier series terms
162+
p_analytical = zeros(length(xgrid), length(times_sec)); % Pressure field [Pa]
163+
q_analytical = zeros(m+1, length(times_sec)); % Darcy flux [m/s] (size = m+1)
164+
e_analytical = zeros(length(xgrid), length(times_sec)); % Strain field
165+
u_analytical = zeros(length(xgrid), length(times_sec)); % Displacement field
166+
residual_analytical = zeros(length(xgrid), length(times_sec));
167+
168+
% Loop over all time snapshots
169+
for i = 1:length(times_sec)
170+
t = times_sec(i);
171+
p = zeros(size(xgrid));
172+
173+
% Compute analytical pressure using Fourier series
174+
for N = 0:N_max
175+
n = 2*N + 1;
176+
term = (1/n) * sin(n*pi*xgrid/(2*l)) .* exp(-(n^2)*(pi^2)*cf*t/(4*l^2));
177+
p = p + term;
178+
end
179+
p = (4/pi) * P0 * p;
180+
p_analytical(:,i) = p;
181+
182+
% Compute Darcy flux
183+
dpdx = gradient(p, dx);
184+
q = - (K / mu) * (dpdx - rho * g);
185+
q_analytical(:,i) = q(1:m+1);
186+
187+
% Compute analytical strain and displacement
188+
e = (alpha * p - P0) / Ks;
189+
u = cumtrapz(xgrid, e);
190+
e_analytical(:,i) = e;
191+
u_analytical(:,i) = u;
192+
193+
194+
% Compute mass conservation residual (analytical)
195+
if i == 1
196+
residual_analytical(:,i) = NaN(size(p_analytical(:,i))); % undefined at first time step
197+
else
198+
dt_local = times_sec(i) - times_sec(i-1);
199+
dpdt_ana = (p_analytical(:,i) - p_analytical(:,i-1)) / dt_local;
200+
dedt_ana = (e_analytical(:,i) - e_analytical(:,i-1)) / dt_local;
201+
divq_ana = div(k, m, dx) * q_analytical(:,i);
202+
residual_analytical(:,i) = Ss * dpdt_ana + alpha * dedt_ana - divq_ana;
203+
end
204+
205+
206+
% Print combined analytical output
207+
fprintf('\nAnalytical results at t = %.2f hr:\n', times_hr(i));
208+
fprintf('| x (m) | Analytical p [Pa] | Darcy Flux [m/s] | Residual [1/s] |\n');
209+
fprintf('|---------------|-------------------|------------------|------------------|\n');
210+
for j = 1:m+1
211+
if i == 1
212+
fprintf('| %13.6f | %18.6e | %16.6e | %16s |\n', ...
213+
xgrid(j), p_analytical(j,i), q_analytical(j,i), 'N/A');
214+
else
215+
fprintf('| %13.6f | %18.6e | %16.6e | %16.6e |\n', ...
216+
xgrid(j), p_analytical(j,i), q_analytical(j,i), residual_analytical(j,i));
217+
end
218+
end
219+
220+
if i > 1
221+
max_res = max(abs(residual_analytical(2:end-1,i)));
222+
fprintf('| Max Residual (interior) at t = %.2f hr: %e |\n', times_hr(i), max_res);
223+
end
224+
end
225+
226+
%% Combined Plot
227+
figure('Name','Schiffman one-dimensional consolidation');
228+
set(gcf,'Color','white');
229+
230+
% Top subplot: MOLE numerical
231+
subplot(2,1,1);
232+
hold on;
233+
for i = 1:length(times_sec)
234+
semilogy(xgrid, p_numerical(:,i)/1e6, '-o', 'DisplayName', [num2str(times_hr(i)) ' hr']);
235+
end
236+
title('MOLE Numerical Solution');
237+
ylabel('Excess Pore Pressure p(x,t) [MPa]');
238+
axis([0 l 1e-3 10]);
239+
legend('Location', 'southeast');
240+
grid on;
241+
242+
% Bottom subplot: Analytical
243+
subplot(2,1,2);
244+
hold on;
245+
for i = 1:length(times_sec)
246+
semilogy(xgrid, p_analytical(:,i)/1e6, '--s', 'DisplayName', [num2str(times_hr(i)) ' hr']);
247+
end
248+
title('Analytical Benchmark Solution');
249+
xlabel('x (m)');
250+
ylabel('Excess Pore Pressure p(x,t) [MPa]');
251+
axis([0 l 1e-3 10]);
252+
legend('Location', 'southeast');
253+
grid on;
254+
255+
256+
257+
%% Combined Displacement Plot (Numerical vs Analytical)
258+
figure('Name','Displacement Field (Numerical vs Analytical)');
259+
set(gcf,'Color','white');
260+
261+
% Top subplot: Numerical displacement
262+
subplot(2,1,1);
263+
hold on;
264+
for i = 1:length(times_sec)
265+
plot(xgrid, u_numerical(:,i), '-o', 'DisplayName', [num2str(times_hr(i)) ' hr']);
266+
end
267+
xlabel('x (m)');
268+
ylabel('Displacement u(x,t) [m]');
269+
title('Numerical Displacement Field');
270+
legend('Location','southwest');
271+
grid on;
272+
273+
% Bottom subplot: Analytical displacement
274+
subplot(2,1,2);
275+
hold on;
276+
for i = 1:length(times_sec)
277+
plot(xgrid, u_analytical(:,i), '--s', 'DisplayName', [num2str(times_hr(i)) ' hr']);
278+
end
279+
xlabel('x (m)');
280+
ylabel('Displacement u(x,t) [m]');
281+
title('Analytical Displacement Field');
282+
legend('Location','southwest');
283+
grid on;
284+
285+
286+
%% Residual Plot (Numerical vs Analytical)
287+
figure('Name','Mass Conservation Residuals (Numerical vs Analytical)');
288+
set(gcf,'Color','white');
289+
290+
% Top subplot: Numerical residual
291+
subplot(2,1,1);
292+
hold on;
293+
for i = 2:length(times_sec)
294+
plot(xgrid(2:end-1), residual(2:end-1,i), '-o', 'DisplayName', [num2str(times_hr(i)) ' hr']);
295+
end
296+
title('Numerical Mass Conservation Residual');
297+
xlabel('x (m)');
298+
ylabel('Residual [1/s]');
299+
legend('Location','northeast');
300+
grid on;
301+
302+
% Bottom subplot: Analytical residual
303+
subplot(2,1,2);
304+
hold on;
305+
for i = 2:length(times_sec)
306+
plot(xgrid(2:end-1), residual_analytical(2:end-1,i), '--s', 'DisplayName', [num2str(times_hr(i)) ' hr']);
307+
end
308+
title('Analytical Mass Conservation Residual');
309+
xlabel('x (m)');
310+
ylabel('Residual [1/s]');
311+
legend('Location','northeast');
312+
grid on;
313+
314+
315+
%% Print relative L2 errors
316+
fprintf('\nRelative L2 Errors (Numerical vs Analytical):\n');
317+
fprintf('| Time [hr] | Pressure Error | Darcy Flux Error | Strain Error | Displacement Error | Residual Error |\n');
318+
fprintf('|------------------|----------------|------------------|--------------|--------------------|----------------|\n');
319+
320+
for i = 1:length(times_hr)
321+
% L2 error for pressure
322+
rel_err_p = norm(p_numerical(:,i) - p_analytical(:,i)) / norm(p_analytical(:,i));
323+
324+
% L2 error for flux
325+
rel_err_q = norm(q_numerical(:,i) - q_analytical(:,i)) / norm(q_analytical(:,i));
326+
327+
% L2 error for strain
328+
rel_err_e = norm(e_numerical(:,i) - e_analytical(:,i)) / norm(e_analytical(:,i));
329+
330+
% L2 error for displacement
331+
rel_err_u = norm(u_numerical(:,i) - u_analytical(:,i)) / norm(u_analytical(:,i));
332+
333+
% L2 error for mass conservation residual
334+
if i == 1
335+
rel_err_r = NaN; % Not defined for first time step
336+
else
337+
rel_err_r = norm(residual(:,i) - residual_analytical(:,i)) / norm(residual_analytical(:,i));
338+
end
339+
340+
% Print in formatted row
341+
if i == 1
342+
fprintf('| %16.2f | %14.6e | %16.6e | %12.6e | %18.6e | %14s |\n', ...
343+
times_hr(i), rel_err_p, rel_err_q, rel_err_e, rel_err_u, 'N/A');
344+
else
345+
fprintf('| %16.2f | %14.6e | %16.6e | %12.6e | %18.6e | %14.6e |\n', ...
346+
times_hr(i), rel_err_p, rel_err_q, rel_err_e, rel_err_u, rel_err_r);
347+
end
348+
end
349+
350+
%% Cumulative Surface Plots
351+
% Prepare time matrix for plotting
352+
[X, T] = meshgrid(xgrid, times_sec / 3600); % Time in hours
353+
354+
% 1. Pressure surface plot
355+
figure('Name','Pressure Evolution Surface');
356+
surf(X, T, p_numerical'); % p_numerical': size (space x time)
357+
xlabel('x (m)');
358+
ylabel('Time (hr)');
359+
zlabel('Pressure p(x,t) [Pa]');
360+
title('Pore Pressure Evolution');
361+
shading interp; view(135, 30); colorbar;
362+
363+
% 2. Displacement surface plot
364+
figure('Name','Displacement Evolution Surface');
365+
surf(X, T, u_numerical');
366+
xlabel('x (m)');
367+
ylabel('Time (hr)');
368+
zlabel('Displacement u(x,t) [m]');
369+
title('Displacement Evolution');
370+
shading interp; view(135, 30); colorbar;
371+
372+
% 3. Mass balance residual surface plot (excluding ghost nodes)
373+
x_interior = xgrid(2:end-1); % length 50
374+
[Xr, Tr] = meshgrid(x_interior, times_sec / 3600); % size: (4 × 50)
375+
376+
% Plot residual surface
377+
figure('Name','Mass Conservation Residual Surface');
378+
surf(Xr, Tr, residual(2:end-1,:)');
379+
xlabel('x (m)');
380+
ylabel('Time (hr)');
381+
zlabel('Residual [1/s]');
382+
title('Mass Conservation Residual Over Time');
383+
shading interp; view(135, 30); colorbar;
384+
385+
386+

0 commit comments

Comments
 (0)