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