cngf-pf

continuum model for granular flows with pore-pressure dynamics (renamed from 1d_fd_simple_shear)
git clone git://src.adamsgaard.dk/cngf-pf # fast
git clone https://src.adamsgaard.dk/cngf-pf.git # slow
Log | Files | Refs | README | LICENSE Back to index

commit 92fb592d0bd18bef9855ce1b5a596107c0fa40bb
parent 3dbb7bbe713574ed78923bcc2bddc1a3046139bc
Author: Anders Damsgaard <anders@adamsgaard.dk>
Date:   Fri,  3 Jul 2026 11:24:54 +0200

fix(simulation): harden fluid BC, origo depth, and solver error handling

Prepare cngf-pf for use as an embedded per-column till solver:

- Correct the origo_z offset in all four depth terms (hydrostatic,
  lithostatic, critical-state friction, fused transient friction) so
  physical depth below the top surface is independent of origo_z; the
  ice-till interface node now equals p_f_top.
- Enforce the top Dirichlet pressure as an identity row in the Darcy
  TDMA system and use top porosity phi[nz-1] in the top ghost correction.
- Return error codes instead of exiting from library solver paths:
  darcy_solver_1d and temporal_increment on NaN/inf, and
  coupled_shear_solver (11 Darcy, 12 Poisson, 13 temporal increment);
  prepare_arrays and check_simulation_parameters now return int. CLI
  drivers translate codes back to exit/errx.
- Add reset_column() for in-place per-column reuse without reallocation.
- Reindent simulation.c and fluid.c to tabs (UseTab: ForIndentation).

Diffstat:
Mcngf-pf.c | 12++++++++----
Mdeborah.c | 8++++++--
Mfluid.c | 425++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mmax_depth_simple_shear.c | 8++++++--
Msimulation.c | 1554+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msimulation.h | 263+++++++++++++++++++++++++++++++++++++++++--------------------------------------
6 files changed, 1176 insertions(+), 1094 deletions(-)

diff --git a/cngf-pf.c b/cngf-pf.c @@ -247,7 +247,8 @@ main(int argc, char *argv[]) if (sim.nz < 1) sim.nz = (int) ceil(sim.L_z / sim.d); - prepare_arrays(&sim); + if (prepare_arrays(&sim)) + errx(1, "prepare_arrays failed"); if (!isnan(new_phi)) for (i = 0; i < sim.nz; ++i) @@ -279,15 +280,18 @@ main(int argc, char *argv[]) sim.dt = sim.t_end; compute_effective_stress(&sim); - check_simulation_parameters(&sim); + if (check_simulation_parameters(&sim)) { + free_arrays(&sim); + exit(1); + } filetimeclock = 0.0; iter = 0; t_begin = clock(); do { - if (coupled_shear_solver(&sim, max_iter, rtol)) { + if ((ret = coupled_shear_solver(&sim, max_iter, rtol))) { free_arrays(&sim); - exit(10); + exit(ret); } if ((filetimeclock >= sim.file_dt || iter == 1) && diff --git a/deborah.c b/deborah.c @@ -103,7 +103,8 @@ main(int argc, char *argv[]) usage(); sim.nz = 2; - prepare_arrays(&sim); + if (prepare_arrays(&sim)) + errx(1, "prepare_arrays failed"); if (!isnan(new_phi)) for (i = 0; i < sim.nz; ++i) @@ -112,7 +113,10 @@ main(int argc, char *argv[]) for (i = 0; i < sim.nz; ++i) sim.k[i] = new_k; - check_simulation_parameters(&sim); + if (check_simulation_parameters(&sim)) { + free_arrays(&sim); + exit(1); + } #ifdef BENCHMARK_PERFORMANCE t_begin = clock(); diff --git a/fluid.c b/fluid.c @@ -1,23 +1,23 @@ #include "fluid.h" #include "arrays.h" #include "simulation.h" -#include <err.h> #include <math.h> #include <stdlib.h> void hydrostatic_fluid_pressure_distribution(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->p_f_ghost[i + 1] = sim->p_f_top + sim->phi[i] * sim->rho_f * sim->G * - (sim->L_z - sim->z[i]); + for (i = 0; i < sim->nz; ++i) + sim->p_f_ghost[i + 1] = + sim->p_f_top + sim->phi[i] * sim->rho_f * sim->G * + (sim->origo_z + sim->L_z - sim->z[i]); } static double diffusivity(struct simulation *sim, int i) { - if (sim->D > 0.0) - return sim->D; - else - return sim->k[i] / ((sim->alpha + sim->phi[i] * sim->beta_f) * sim->mu_f); + if (sim->D > 0.0) + return sim->D; + else + return sim->k[i] / ((sim->alpha + sim->phi[i] * sim->beta_f) * sim->mu_f); } /* Determines the largest time step for the current simulation state. Note @@ -25,229 +25,234 @@ static double diffusivity(struct simulation *sim, int i) { * diffusivities (i.e., permeabilities, porosities, viscosities, or * compressibilities) change. The safety factor should be in ]0;1] */ int set_largest_fluid_timestep(struct simulation *sim, const double safety) { - int i; - double dx_min, diff, diff_max, *dx; - - dx = spacing(sim->z, sim->nz); - dx_min = INFINITY; - for (i = 0; i < sim->nz - 1; ++i) { - if (dx[i] < 0.0) { - fprintf(stderr, "error: cell spacing negative (%g) in cell %d\n", dx[i], - i); - free(dx); - return 1; - } - if (dx[i] < dx_min) - dx_min = dx[i]; - } - free(dx); - - diff_max = -INFINITY; - for (i = 0; i < sim->nz; ++i) { - diff = diffusivity(sim, i); - if (diff > diff_max) - diff_max = diff; - } - - sim->dt = safety * 0.5 * dx_min * dx_min / diff_max; - if (sim->file_dt < sim->dt) - sim->dt = sim->file_dt; - - return 0; + int i; + double dx_min, diff, diff_max, *dx; + + dx = spacing(sim->z, sim->nz); + dx_min = INFINITY; + for (i = 0; i < sim->nz - 1; ++i) { + if (dx[i] < 0.0) { + fprintf(stderr, "error: cell spacing negative (%g) in cell %d\n", dx[i], + i); + free(dx); + return 1; + } + if (dx[i] < dx_min) + dx_min = dx[i]; + } + free(dx); + + diff_max = -INFINITY; + for (i = 0; i < sim->nz; ++i) { + diff = diffusivity(sim, i); + if (diff > diff_max) + diff_max = diff; + } + + sim->dt = safety * 0.5 * dx_min * dx_min / diff_max; + if (sim->file_dt < sim->dt) + sim->dt = sim->file_dt; + + return 0; } static double sine_wave(const double time, const double ampl, const double freq, const double phase) { - return ampl * sin(2.0 * PI * freq * time + phase); + return ampl * sin(2.0 * PI * freq * time + phase); } static double triangular_pulse(const double time, const double peak_ampl, const double freq, const double peak_time) { - if (peak_time - 1.0 / (2.0 * freq) < time && time <= peak_time) - return peak_ampl * 2.0 * freq * (time - peak_time) + peak_ampl; - else if (peak_time < time && time < peak_time + 1.0 / (2.0 * freq)) - return peak_ampl * 2.0 * freq * (peak_time - time) + peak_ampl; - else - return 0.0; + if (peak_time - 1.0 / (2.0 * freq) < time && time <= peak_time) + return peak_ampl * 2.0 * freq * (time - peak_time) + peak_ampl; + else if (peak_time < time && time < peak_time + 1.0 / (2.0 * freq)) + return peak_ampl * 2.0 * freq * (peak_time - time) + peak_ampl; + else + return 0.0; } static double square_pulse(const double time, const double peak_ampl, const double freq, const double peak_time) { - if (peak_time - 1.0 / (2.0 * freq) < time && - time < peak_time + 1.0 / (2.0 * freq)) - return peak_ampl; - else - return 0.0; + if (peak_time - 1.0 / (2.0 * freq) < time && + time < peak_time + 1.0 / (2.0 * freq)) + return peak_ampl; + else + return 0.0; } static void set_fluid_bcs(double *p_f_ghost, struct simulation *sim, const double p_f_top) { - /* correct ghost node at top BC for hydrostatic pressure gradient */ - set_bc_dirichlet(p_f_ghost, sim->nz, +1, - p_f_top - sim->phi[0] * sim->rho_f * sim->G * sim->dz); - /* p_f_ghost[sim->nz] = p_f_top; */ /* REMOVED: Do not pin top physical node, - let solver handle it */ - set_bc_neumann(p_f_ghost, sim->nz, -1, sim->phi[0] * sim->rho_f * sim->G, - sim->dz); + /* correct ghost node at top BC for hydrostatic pressure gradient (uses top + porosity phi[nz-1] at the ice-till interface) */ + set_bc_dirichlet(p_f_ghost, sim->nz, +1, + p_f_top - + sim->phi[sim->nz - 1] * sim->rho_f * sim->G * sim->dz); + p_f_ghost[sim->nz] = p_f_top; /* pin top physical node */ + set_bc_neumann(p_f_ghost, sim->nz, -1, sim->phi[0] * sim->rho_f * sim->G, + sim->dz); } int darcy_solver_1d(struct simulation *sim) { - int i, solved = 0; - double p_f_top; - double *a = sim->tdma_a; - double *b = sim->tdma_b; - double *c = sim->tdma_c; - double *d = sim->tdma_d; - double *x = sim->tdma_x; - double *k_n = sim->darcy_k_n; - double *phi_n = sim->darcy_phi_n; - - for (i = 0; i < sim->nz; ++i) - sim->p_f_dot_impl[i] = sim->p_f_dot_expl[i] = 0.0; - - if (isnan(sim->p_f_mod_pulse_time)) - p_f_top = sim->p_f_top + sine_wave(sim->t, sim->p_f_mod_ampl, - sim->p_f_mod_freq, sim->p_f_mod_phase); - else if (sim->p_f_mod_pulse_shape == 1) - p_f_top = - sim->p_f_top + square_pulse(sim->t, sim->p_f_mod_ampl, - sim->p_f_mod_freq, sim->p_f_mod_pulse_time); - else - p_f_top = sim->p_f_top + triangular_pulse(sim->t, sim->p_f_mod_ampl, - sim->p_f_mod_freq, - sim->p_f_mod_pulse_time); - - /* set fluid BCs (1 of 2) */ - set_fluid_bcs(sim->p_f_ghost, sim, p_f_top); - set_fluid_bcs(sim->p_f_next_ghost, sim, p_f_top); - - /* implicit solution with TDMA */ - { + int i, solved = 0; + double p_f_top; + double *a = sim->tdma_a; + double *b = sim->tdma_b; + double *c = sim->tdma_c; + double *d = sim->tdma_d; + double *x = sim->tdma_x; + double *k_n = sim->darcy_k_n; + double *phi_n = sim->darcy_phi_n; + + for (i = 0; i < sim->nz; ++i) + sim->p_f_dot_impl[i] = sim->p_f_dot_expl[i] = 0.0; + + if (isnan(sim->p_f_mod_pulse_time)) + p_f_top = sim->p_f_top + sine_wave(sim->t, sim->p_f_mod_ampl, + sim->p_f_mod_freq, sim->p_f_mod_phase); + else if (sim->p_f_mod_pulse_shape == 1) + p_f_top = + sim->p_f_top + square_pulse(sim->t, sim->p_f_mod_ampl, + sim->p_f_mod_freq, sim->p_f_mod_pulse_time); + else + p_f_top = sim->p_f_top + triangular_pulse(sim->t, sim->p_f_mod_ampl, + sim->p_f_mod_freq, + sim->p_f_mod_pulse_time); + + /* set fluid BCs (1 of 2) */ + set_fluid_bcs(sim->p_f_ghost, sim, p_f_top); + set_fluid_bcs(sim->p_f_next_ghost, sim, p_f_top); + + /* implicit solution with TDMA */ + { #ifdef DEBUG - printf("\nIMPLICIT SOLVER IN %s\n", __func__); + printf("\nIMPLICIT SOLVER IN %s\n", __func__); #endif - /* Predictor step for nonlinear coefficients */ - if (sim->transient) - for (i = 0; i < sim->nz; ++i) { - phi_n[i] = sim->phi[i] + sim->dt * sim->phi_dot[i]; - k_n[i] = kozeny_carman(sim->d, phi_n[i]); - } - else - for (i = 0; i < sim->nz; ++i) { - phi_n[i] = sim->phi[i]; - k_n[i] = sim->k[i]; - } - - /* Build Tridiagonal System */ - for (i = 0; i < sim->nz; ++i) { - if (sim->D > 0.0) { - /* Constant diffusivity mode */ - double coeff = sim->D * sim->dt / (sim->dz * sim->dz); - a[i] = -coeff; - b[i] = 1.0 + 2.0 * coeff; - c[i] = -coeff; - /* RHS is just p_old (plus potential source terms if applicable, - but explicit function for D > 0 uses only diffusion term) */ - d[i] = sim->p_f_ghost[i + 1]; - } else { - /* Coefficients calculation matches the discretized equation */ - double k_i = k_n[i]; - double k_zn, k_zp; - - if (i == 0) - k_zn = k_i; - else - k_zn = k_n[i - 1]; - - if (i == sim->nz - 1) - k_zp = k_i; - else - k_zp = k_n[i + 1]; - - /* Harmonic means for conductivity at faces */ - double k_harm_p = 2.0 * k_zp * k_i / fmax(k_zp + k_i, 1e-30); - double k_harm_n = 2.0 * k_zn * k_i / fmax(k_zn + k_i, 1e-30); - - /* Diffusion terms */ - double coupling = - 1.0 / ((sim->alpha + sim->beta_f * phi_n[i]) * sim->mu_f); - double porosity_term = - -1.0 / ((sim->alpha + sim->beta_f * phi_n[i]) * (1.0 - phi_n[i])); - - /* Matrix coefficients (LHS) */ - /* term: - dt * coupling * (k_n * (p_i - p_{i-1}) / dz^2) */ - double alpha_i = -sim->dt * coupling * k_harm_n / - (sim->dz * sim->dz); // coeff for p_{i-1} - double gamma_i = -sim->dt * coupling * k_harm_p / - (sim->dz * sim->dz); // coeff for p_{i+1} - double beta_i = - 1.0 - - (alpha_i + gamma_i); // coeff for p_i (sum of abs(off-diags) + 1) - - a[i] = alpha_i; - b[i] = beta_i; - c[i] = gamma_i; - - /* RHS: p_old + source term */ - d[i] = - sim->p_f_ghost[i + 1] + sim->dt * porosity_term * sim->phi_dot[i]; - } - } - - /* Apply Boundary Conditions to Linear System */ - /* Bottom (i=0): Neumann. p_{-1} = p_0 + C. */ - /* Bottom (i=0): Neumann. p_{-1} = p_0 + C. */ - double bc_neumann_val = sim->phi[0] * sim->rho_f * sim->G * sim->dz; - - b[0] += a[0]; - d[0] -= a[0] * bc_neumann_val; - a[0] = 0.0; - - /* Top (i=nz-1): Dirichlet. p_{n} = p_ghost_top. - Term c[n-1] * p_{n} becomes c[n-1] * p_ghost_top. - Subtract from d[n-1]. - FIX: Use sim->p_f_ghost[sim->nz + 1] which has correct BC correction. - */ - d[sim->nz - 1] -= c[sim->nz - 1] * sim->p_f_ghost[sim->nz + 1]; - c[sim->nz - 1] = 0.0; - - /* Solve */ - tridiagonal_solver(x, a, b, c, d, sim->tdma_c_prime, sim->tdma_d_prime, - sim->nz); - - /* Store result in p_f_dot (rate) */ - for (i = 0; i < sim->nz; ++i) { - sim->p_f_dot[i] = (x[i] - sim->p_f_ghost[i + 1]) / sim->dt; - /* Store in impl array too for consistency/debug */ - sim->p_f_dot_impl[i] = sim->p_f_dot[i]; - } - - add_darcy_iters(1); - solved = 1; - } - - for (i = 0; i < sim->nz; ++i) - sim->p_f_next_ghost[i + 1] = - sim->p_f_dot[i] * sim->dt + sim->p_f_ghost[i + 1]; - - set_fluid_bcs(sim->p_f_ghost, sim, p_f_top); - set_fluid_bcs(sim->p_f_next_ghost, sim, p_f_top); + /* Predictor step for nonlinear coefficients */ + if (sim->transient) + for (i = 0; i < sim->nz; ++i) { + phi_n[i] = sim->phi[i] + sim->dt * sim->phi_dot[i]; + k_n[i] = kozeny_carman(sim->d, phi_n[i]); + } + else + for (i = 0; i < sim->nz; ++i) { + phi_n[i] = sim->phi[i]; + k_n[i] = sim->k[i]; + } + + /* Build Tridiagonal System */ + for (i = 0; i < sim->nz; ++i) { + if (sim->D > 0.0) { + /* Constant diffusivity mode */ + double coeff = sim->D * sim->dt / (sim->dz * sim->dz); + a[i] = -coeff; + b[i] = 1.0 + 2.0 * coeff; + c[i] = -coeff; + /* RHS is just p_old (plus potential source terms if applicable, + but explicit function for D > 0 uses only diffusion term) */ + d[i] = sim->p_f_ghost[i + 1]; + } else { + /* Coefficients calculation matches the discretized equation */ + double k_i = k_n[i]; + double k_zn, k_zp; + + if (i == 0) + k_zn = k_i; + else + k_zn = k_n[i - 1]; + + if (i == sim->nz - 1) + k_zp = k_i; + else + k_zp = k_n[i + 1]; + + /* Harmonic means for conductivity at faces */ + double k_harm_p = 2.0 * k_zp * k_i / fmax(k_zp + k_i, 1e-30); + double k_harm_n = 2.0 * k_zn * k_i / fmax(k_zn + k_i, 1e-30); + + /* Diffusion terms */ + double coupling = + 1.0 / ((sim->alpha + sim->beta_f * phi_n[i]) * sim->mu_f); + double porosity_term = + -1.0 / ((sim->alpha + sim->beta_f * phi_n[i]) * (1.0 - phi_n[i])); + + /* Matrix coefficients (LHS) */ + /* term: - dt * coupling * (k_n * (p_i - p_{i-1}) / dz^2) */ + double alpha_i = -sim->dt * coupling * k_harm_n / + (sim->dz * sim->dz); // coeff for p_{i-1} + double gamma_i = -sim->dt * coupling * k_harm_p / + (sim->dz * sim->dz); // coeff for p_{i+1} + double beta_i = + 1.0 - + (alpha_i + gamma_i); // coeff for p_i (sum of abs(off-diags) + 1) + + a[i] = alpha_i; + b[i] = beta_i; + c[i] = gamma_i; + + /* RHS: p_old + source term */ + d[i] = + sim->p_f_ghost[i + 1] + sim->dt * porosity_term * sim->phi_dot[i]; + } + } + + /* Apply Boundary Conditions to Linear System */ + /* Bottom (i=0): Neumann. p_{-1} = p_0 + C. */ + /* Bottom (i=0): Neumann. p_{-1} = p_0 + C. */ + double bc_neumann_val = sim->phi[0] * sim->rho_f * sim->G * sim->dz; + + b[0] += a[0]; + d[0] -= a[0] * bc_neumann_val; + a[0] = 0.0; + + /* Top (i=nz-1): Dirichlet at the physical top node (ice-till interface): + p[nz-1] = p_f_top(t). Identity row so the solved top-node pressure + equals the imposed value exactly. */ + a[sim->nz - 1] = 0.0; + b[sim->nz - 1] = 1.0; + c[sim->nz - 1] = 0.0; + d[sim->nz - 1] = p_f_top; + + /* Solve */ + tridiagonal_solver(x, a, b, c, d, sim->tdma_c_prime, sim->tdma_d_prime, + sim->nz); + + /* Store result in p_f_dot (rate) */ + for (i = 0; i < sim->nz; ++i) { + sim->p_f_dot[i] = (x[i] - sim->p_f_ghost[i + 1]) / sim->dt; + /* Store in impl array too for consistency/debug */ + sim->p_f_dot_impl[i] = sim->p_f_dot[i]; + } + + add_darcy_iters(1); + solved = 1; + } + + for (i = 0; i < sim->nz; ++i) + sim->p_f_next_ghost[i + 1] = + sim->p_f_dot[i] * sim->dt + sim->p_f_ghost[i + 1]; + + set_fluid_bcs(sim->p_f_ghost, sim, p_f_top); + set_fluid_bcs(sim->p_f_next_ghost, sim, p_f_top); #ifdef DEBUG - puts(".. p_f_dot_expl:"); - print_array(sim->p_f_dot_expl, sim->nz); - puts(".. p_f_dot_impl:"); - print_array(sim->p_f_dot_impl, sim->nz); + puts(".. p_f_dot_expl:"); + print_array(sim->p_f_dot_expl, sim->nz); + puts(".. p_f_dot_impl:"); + print_array(sim->p_f_dot_impl, sim->nz); #endif - for (i = 0; i < sim->nz; ++i) - if (isnan(sim->p_f_dot_expl[i]) || isinf(sim->p_f_dot_expl[i])) - errx(1, "invalid: sim->p_f_dot_expl[%d] = %g (t = %g s)", i, - sim->p_f_dot_expl[i], sim->t); - - for (i = 0; i < sim->nz; ++i) - if (isnan(sim->p_f_dot_impl[i]) || isinf(sim->p_f_dot_impl[i])) - errx(1, "invalid: sim->p_f_dot_impl[%d] = %g (t = %g s)", i, - sim->p_f_dot_impl[i], sim->t); - - return solved - 1; + for (i = 0; i < sim->nz; ++i) + if (isnan(sim->p_f_dot_expl[i]) || isinf(sim->p_f_dot_expl[i])) { + fprintf(stderr, "invalid: sim->p_f_dot_expl[%d] = %g (t = %g s)\n", i, + sim->p_f_dot_expl[i], sim->t); + return 1; + } + + for (i = 0; i < sim->nz; ++i) + if (isnan(sim->p_f_dot_impl[i]) || isinf(sim->p_f_dot_impl[i])) { + fprintf(stderr, "invalid: sim->p_f_dot_impl[%d] = %g (t = %g s)\n", i, + sim->p_f_dot_impl[i], sim->t); + return 1; + } + + return solved - 1; } diff --git a/max_depth_simple_shear.c b/max_depth_simple_shear.c @@ -229,7 +229,8 @@ main(int argc, char *argv[]) usage(); sim.nz = 2; - prepare_arrays(&sim); + if (prepare_arrays(&sim)) + errx(1, "prepare_arrays failed"); if (!isnan(new_phi)) for (i = 0; i < sim.nz; ++i) @@ -238,7 +239,10 @@ main(int argc, char *argv[]) for (i = 0; i < sim.nz; ++i) sim.k[i] = new_k; - check_simulation_parameters(&sim); + if (check_simulation_parameters(&sim)) { + free_arrays(&sim); + exit(1); + } depth = 0.0; d_s = skin_depth(&sim); diff --git a/simulation.c b/simulation.c @@ -5,6 +5,7 @@ #include <math.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> /* iteration limits for solvers */ #define MAX_ITER_GRANULAR 100000 @@ -15,11 +16,11 @@ /* solver statistics for benchmarking */ struct solver_stats { - long poisson_iters; - long darcy_iters; - long coupled_iters; - long stress_iters; - long timesteps; + long poisson_iters; + long darcy_iters; + long coupled_iters; + long stress_iters; + long timesteps; }; static struct solver_stats g_stats = {0, 0, 0, 0, 0}; @@ -29,910 +30,961 @@ static struct solver_stats g_stats = {0, 0, 0, 0, 0}; /* Simulation settings */ void init_sim(struct simulation *sim) { - int ret; - - ret = snprintf(sim->name, sizeof(sim->name), DEFAULT_SIMULATION_NAME); - if (ret < 0 || (size_t)ret == sizeof(sim->name)) - err(1, "%s: could not write simulation name", __func__); - - sim->G = 9.81; - sim->P_wall = 120e3; - sim->mu_wall = 0.45; - sim->v_x_bot = 0.0; - sim->v_x_fix = NAN; - sim->v_x_limit = NAN; - sim->nz = -1; /* cell size equals grain size if negative */ - - sim->A = 0.40; /* Loose fit to Damsgaard et al 2013 */ - sim->b = 0.9377; /* Henann and Kamrin 2016 */ - /* sim->mu_s = 0.3819; */ /* Henann and Kamrin 2016 */ - /* sim->C = 0.0; */ /* Henann and Kamrin 2016 */ - sim->mu_s = tan(DEG2RAD(22.0)); /* Damsgaard et al 2013 */ - sim->C = 0.0; /* Damsgaard et al 2013 */ - sim->phi = initval(0.25, 1); - sim->d = 0.04; /* Damsgaard et al 2013 */ - sim->transient = 0; - sim->phi_min = 0.20; - sim->phi_max = 0.55; - sim->dilatancy_constant = 4.09; /* Pailha & Pouliquen 2009 */ - - /* Iverson et al 1997, 1998: Storglaciaren till */ - /* sim->mu_s = tan(DEG2RAD(26.3)); */ - /* sim->C = 5.0e3; */ - /* sim->phi = initval(0.22, 1); */ - /* sim->d = ??; */ - - /* Iverson et al 1997, 1998: Two Rivers till */ - /* sim->mu_s = tan(DEG2RAD(17.8)); */ - /* sim->C = 14.0e3; */ - /* sim->phi = initval(0.37, 1); */ - /* sim->d = ??; */ - - /* Tulaczyk et al 2000a: Upstream B till */ - /* sim->mu_s = tan(DEG2RAD(23.9)); */ - /* sim->C = 3.0e3; */ - /* sim->phi = initval(0.35, 1); */ - /* sim->d = ??; */ - - sim->rho_s = 2.6e3; /* Damsgaard et al 2013 */ - sim->origo_z = 0.0; - sim->L_z = 1.0; - sim->t = 0.0; - sim->dt = 1.0; - sim->t_end = 1.0; - sim->file_dt = 1.0; - sim->n_file = 0; - sim->fluid = 0; - sim->rho_f = 1e3; - - /* Water at 20 deg C */ - /* sim->beta_f = 4.5e-10; */ /* Goren et al 2011 */ - /* sim->mu_f = 1.0-3; */ /* Goren et al 2011 */ - - /* Water at 0 deg C */ - sim->beta_f = 3.9e-10; /* doi:10.1063/1.1679903 */ - sim->mu_f = 1.787e-3; /* Cuffey and Paterson 2010 */ - - sim->alpha = 1e-8; - sim->D = -1.0; /* disabled when negative */ - - sim->k = initval(1.9e-15, 1); /* Damsgaard et al 2015 */ - - /* Iverson et al 1994: Storglaciaren */ - /* sim->k = initval(1.3e-14, 1); */ - - /* Engelhardt et al 1990: Upstream B */ - /* sim->k = initval(2.0e-16, 1); */ - - /* Leeman et al 2016: Upstream B till */ - /* sim->k = initval(4.9e-17, 1); */ - - /* no fluid-pressure variations */ - sim->p_f_top = 0.0; - sim->p_f_mod_ampl = 0.0; - sim->p_f_mod_freq = 1.0; - sim->p_f_mod_phase = 0.0; - sim->p_f_mod_pulse_time = NAN; - sim->p_f_mod_pulse_shape = 0; -} - -void prepare_arrays(struct simulation *sim) { - if (sim->nz < 2) { - fprintf(stderr, "error: grid size (nz) must be at least 2 but is %d\n", - sim->nz); - exit(1); - } - free(sim->phi); - free(sim->k); - - sim->z = linspace(sim->origo_z, sim->origo_z + sim->L_z, sim->nz); - sim->dz = sim->z[1] - sim->z[0]; - sim->mu = zeros(sim->nz); - sim->mu_c = zeros(sim->nz); - sim->sigma_n_eff = zeros(sim->nz); - sim->sigma_n = zeros(sim->nz); - sim->p_f_ghost = zeros(sim->nz + 2); - sim->p_f_next_ghost = zeros(sim->nz + 2); - sim->p_f_dot = zeros(sim->nz); - sim->p_f_dot_expl = zeros(sim->nz); - sim->p_f_dot_impl = zeros(sim->nz); - sim->phi = zeros(sim->nz); - sim->phi_c = zeros(sim->nz); - sim->phi_dot = zeros(sim->nz); - sim->k = zeros(sim->nz); - sim->xi = zeros(sim->nz); - sim->gamma_dot_p = zeros(sim->nz); - sim->v_x = zeros(sim->nz); - sim->d_x = zeros(sim->nz); - sim->g_local = zeros(sim->nz); - sim->g_ghost = zeros(sim->nz + 2); - sim->g_r_norm = zeros(sim->nz); - sim->I = zeros(sim->nz); - sim->tan_psi = zeros(sim->nz); - sim->old_val = empty(sim->nz); - sim->tdma_a = empty(sim->nz); - sim->tdma_b = empty(sim->nz); - sim->tdma_c = empty(sim->nz); - sim->tdma_d = empty(sim->nz); - sim->tdma_x = empty(sim->nz); - sim->tdma_c_prime = empty(sim->nz); - sim->tdma_d_prime = empty(sim->nz); - sim->darcy_k_n = empty(sim->nz); - sim->darcy_phi_n = empty(sim->nz); + int ret; + + ret = snprintf(sim->name, sizeof(sim->name), DEFAULT_SIMULATION_NAME); + if (ret < 0 || (size_t)ret == sizeof(sim->name)) + err(1, "%s: could not write simulation name", __func__); + + sim->G = 9.81; + sim->P_wall = 120e3; + sim->mu_wall = 0.45; + sim->v_x_bot = 0.0; + sim->v_x_fix = NAN; + sim->v_x_limit = NAN; + sim->nz = -1; /* cell size equals grain size if negative */ + + sim->A = 0.40; /* Loose fit to Damsgaard et al 2013 */ + sim->b = 0.9377; /* Henann and Kamrin 2016 */ + /* sim->mu_s = 0.3819; */ /* Henann and Kamrin 2016 */ + /* sim->C = 0.0; */ /* Henann and Kamrin 2016 */ + sim->mu_s = tan(DEG2RAD(22.0)); /* Damsgaard et al 2013 */ + sim->C = 0.0; /* Damsgaard et al 2013 */ + sim->phi = initval(0.25, 1); + sim->d = 0.04; /* Damsgaard et al 2013 */ + sim->transient = 0; + sim->phi_min = 0.20; + sim->phi_max = 0.55; + sim->dilatancy_constant = 4.09; /* Pailha & Pouliquen 2009 */ + + /* Iverson et al 1997, 1998: Storglaciaren till */ + /* sim->mu_s = tan(DEG2RAD(26.3)); */ + /* sim->C = 5.0e3; */ + /* sim->phi = initval(0.22, 1); */ + /* sim->d = ??; */ + + /* Iverson et al 1997, 1998: Two Rivers till */ + /* sim->mu_s = tan(DEG2RAD(17.8)); */ + /* sim->C = 14.0e3; */ + /* sim->phi = initval(0.37, 1); */ + /* sim->d = ??; */ + + /* Tulaczyk et al 2000a: Upstream B till */ + /* sim->mu_s = tan(DEG2RAD(23.9)); */ + /* sim->C = 3.0e3; */ + /* sim->phi = initval(0.35, 1); */ + /* sim->d = ??; */ + + sim->rho_s = 2.6e3; /* Damsgaard et al 2013 */ + sim->origo_z = 0.0; + sim->L_z = 1.0; + sim->t = 0.0; + sim->dt = 1.0; + sim->t_end = 1.0; + sim->file_dt = 1.0; + sim->n_file = 0; + sim->fluid = 0; + sim->rho_f = 1e3; + + /* Water at 20 deg C */ + /* sim->beta_f = 4.5e-10; */ /* Goren et al 2011 */ + /* sim->mu_f = 1.0-3; */ /* Goren et al 2011 */ + + /* Water at 0 deg C */ + sim->beta_f = 3.9e-10; /* doi:10.1063/1.1679903 */ + sim->mu_f = 1.787e-3; /* Cuffey and Paterson 2010 */ + + sim->alpha = 1e-8; + sim->D = -1.0; /* disabled when negative */ + + sim->k = initval(1.9e-15, 1); /* Damsgaard et al 2015 */ + + /* Iverson et al 1994: Storglaciaren */ + /* sim->k = initval(1.3e-14, 1); */ + + /* Engelhardt et al 1990: Upstream B */ + /* sim->k = initval(2.0e-16, 1); */ + + /* Leeman et al 2016: Upstream B till */ + /* sim->k = initval(4.9e-17, 1); */ + + /* no fluid-pressure variations */ + sim->p_f_top = 0.0; + sim->p_f_mod_ampl = 0.0; + sim->p_f_mod_freq = 1.0; + sim->p_f_mod_phase = 0.0; + sim->p_f_mod_pulse_time = NAN; + sim->p_f_mod_pulse_shape = 0; +} + +int prepare_arrays(struct simulation *sim) { + if (sim->nz < 2) { + fprintf(stderr, "error: grid size (nz) must be at least 2 but is %d\n", + sim->nz); + return 1; + } + free(sim->phi); + free(sim->k); + + sim->z = linspace(sim->origo_z, sim->origo_z + sim->L_z, sim->nz); + sim->dz = sim->z[1] - sim->z[0]; + sim->mu = zeros(sim->nz); + sim->mu_c = zeros(sim->nz); + sim->sigma_n_eff = zeros(sim->nz); + sim->sigma_n = zeros(sim->nz); + sim->p_f_ghost = zeros(sim->nz + 2); + sim->p_f_next_ghost = zeros(sim->nz + 2); + sim->p_f_dot = zeros(sim->nz); + sim->p_f_dot_expl = zeros(sim->nz); + sim->p_f_dot_impl = zeros(sim->nz); + sim->phi = zeros(sim->nz); + sim->phi_c = zeros(sim->nz); + sim->phi_dot = zeros(sim->nz); + sim->k = zeros(sim->nz); + sim->xi = zeros(sim->nz); + sim->gamma_dot_p = zeros(sim->nz); + sim->v_x = zeros(sim->nz); + sim->d_x = zeros(sim->nz); + sim->g_local = zeros(sim->nz); + sim->g_ghost = zeros(sim->nz + 2); + sim->g_r_norm = zeros(sim->nz); + sim->I = zeros(sim->nz); + sim->tan_psi = zeros(sim->nz); + sim->old_val = empty(sim->nz); + sim->tdma_a = empty(sim->nz); + sim->tdma_b = empty(sim->nz); + sim->tdma_c = empty(sim->nz); + sim->tdma_d = empty(sim->nz); + sim->tdma_x = empty(sim->nz); + sim->tdma_c_prime = empty(sim->nz); + sim->tdma_d_prime = empty(sim->nz); + sim->darcy_k_n = empty(sim->nz); + sim->darcy_phi_n = empty(sim->nz); + + return 0; } void free_arrays(struct simulation *sim) { - free(sim->z); - free(sim->mu); - free(sim->mu_c); - free(sim->sigma_n_eff); - free(sim->sigma_n); - free(sim->p_f_ghost); - free(sim->p_f_next_ghost); - free(sim->p_f_dot); - free(sim->p_f_dot_expl); - free(sim->p_f_dot_impl); - free(sim->k); - free(sim->phi); - free(sim->phi_c); - free(sim->phi_dot); - free(sim->xi); - free(sim->gamma_dot_p); - free(sim->v_x); - free(sim->d_x); - free(sim->g_local); - free(sim->g_ghost); - free(sim->g_r_norm); - free(sim->I); - free(sim->tan_psi); - free(sim->old_val); - free(sim->tdma_a); - free(sim->tdma_b); - free(sim->tdma_c); - free(sim->tdma_d); - free(sim->tdma_x); - free(sim->tdma_c_prime); - free(sim->tdma_d_prime); - free(sim->darcy_k_n); - free(sim->darcy_phi_n); + free(sim->z); + free(sim->mu); + free(sim->mu_c); + free(sim->sigma_n_eff); + free(sim->sigma_n); + free(sim->p_f_ghost); + free(sim->p_f_next_ghost); + free(sim->p_f_dot); + free(sim->p_f_dot_expl); + free(sim->p_f_dot_impl); + free(sim->k); + free(sim->phi); + free(sim->phi_c); + free(sim->phi_dot); + free(sim->xi); + free(sim->gamma_dot_p); + free(sim->v_x); + free(sim->d_x); + free(sim->g_local); + free(sim->g_ghost); + free(sim->g_r_norm); + free(sim->I); + free(sim->tan_psi); + free(sim->old_val); + free(sim->tdma_a); + free(sim->tdma_b); + free(sim->tdma_c); + free(sim->tdma_d); + free(sim->tdma_x); + free(sim->tdma_c_prime); + free(sim->tdma_d_prime); + free(sim->darcy_k_n); + free(sim->darcy_phi_n); +} + +void reset_column(struct simulation *sim) { + int i; + const int nz = sim->nz; + const size_t nz_bytes = (size_t)nz * sizeof(double); + const size_t ghost_bytes = (size_t)(nz + 2) * sizeof(double); + + /* zero accumulated state fields (size nz) */ + memset(sim->mu, 0, nz_bytes); + memset(sim->mu_c, 0, nz_bytes); + memset(sim->sigma_n_eff, 0, nz_bytes); + memset(sim->sigma_n, 0, nz_bytes); + memset(sim->p_f_dot, 0, nz_bytes); + memset(sim->p_f_dot_expl, 0, nz_bytes); + memset(sim->p_f_dot_impl, 0, nz_bytes); + memset(sim->phi_c, 0, nz_bytes); + memset(sim->phi_dot, 0, nz_bytes); + memset(sim->xi, 0, nz_bytes); + memset(sim->gamma_dot_p, 0, nz_bytes); + memset(sim->v_x, 0, nz_bytes); + memset(sim->d_x, 0, nz_bytes); + memset(sim->g_local, 0, nz_bytes); + memset(sim->g_r_norm, 0, nz_bytes); + memset(sim->I, 0, nz_bytes); + memset(sim->tan_psi, 0, nz_bytes); + + /* zero ghost fields (size nz + 2) */ + memset(sim->p_f_ghost, 0, ghost_bytes); + memset(sim->p_f_next_ghost, 0, ghost_bytes); + memset(sim->g_ghost, 0, ghost_bytes); + + /* restore init_sim() defaults for porosity and permeability */ + for (i = 0; i < nz; ++i) { + sim->phi[i] = 0.25; /* init_sim default */ + sim->k[i] = 1.9e-15; /* init_sim default */ + } + + /* reset per-column scalars to init_sim() defaults */ + sim->t = 0.0; + sim->mu_wall = 0.45; + sim->v_x_bot = 0.0; } static void warn_parameter_value(const char message[], const double value, int *return_status) { - fprintf(stderr, "check_simulation_parameters: %s (%.17g)\n", message, value); - *return_status = 1; + fprintf(stderr, "check_simulation_parameters: %s (%.17g)\n", message, value); + *return_status = 1; } static void check_float(const char name[], const double value, int *return_status) { - int ret; - char message[100]; + int ret; + char message[100]; #ifdef SHOW_PARAMETERS - printf("%30s: %.17g\n", name, value); + printf("%30s: %.17g\n", name, value); #endif - if (isnan(value)) { - ret = snprintf(message, sizeof(message), "%s is NaN", name); - if (ret < 0 || (size_t)ret >= sizeof(message)) - err(1, "%s: message parsing", __func__); - warn_parameter_value(message, value, return_status); - } else if (isinf(value)) { - ret = snprintf(message, sizeof(message), "%s is infinite", name); - if (ret < 0 || (size_t)ret >= sizeof(message)) - err(1, "%s: message parsing", __func__); - warn_parameter_value(message, value, return_status); - } -} - -void check_simulation_parameters(struct simulation *sim) { - int return_status = 0; - - check_float("sim->G", sim->G, &return_status); - if (sim->G < 0.0) - warn_parameter_value("sim->G is negative", sim->G, &return_status); - - check_float("sim->P_wall", sim->P_wall, &return_status); - if (sim->P_wall < 0.0) - warn_parameter_value("sim->P_wall is negative", sim->P_wall, - &return_status); - - check_float("sim->v_x_bot", sim->v_x_bot, &return_status); - - check_float("sim->mu_wall", sim->mu_wall, &return_status); - if (sim->mu_wall < 0.0) - warn_parameter_value("sim->mu_wall is negative", sim->mu_wall, - &return_status); - - check_float("sim->A", sim->A, &return_status); - if (sim->A < 0.0) - warn_parameter_value("sim->A is negative", sim->A, &return_status); - - check_float("sim->b", sim->b, &return_status); - if (sim->b < 0.0) - warn_parameter_value("sim->b is negative", sim->b, &return_status); - - check_float("sim->mu_s", sim->mu_s, &return_status); - if (sim->mu_s < 0.0) - warn_parameter_value("sim->mu_s is negative", sim->mu_s, &return_status); - - check_float("sim->C", sim->C, &return_status); - - check_float("sim->d", sim->d, &return_status); - if (sim->d <= 0.0) - warn_parameter_value("sim->d is not a positive number", sim->d, - &return_status); - - check_float("sim->rho_s", sim->rho_s, &return_status); - if (sim->rho_s <= 0.0) - warn_parameter_value("sim->rho_s is not a positive number", sim->rho_s, - &return_status); - - if (sim->nz <= 0) - warn_parameter_value("sim->nz is not a positive number", sim->nz, - &return_status); - - check_float("sim->origo_z", sim->origo_z, &return_status); - check_float("sim->L_z", sim->L_z, &return_status); - if (sim->L_z <= sim->origo_z) - warn_parameter_value("sim->L_z is smaller or equal to sim->origo_z", - sim->L_z, &return_status); - - if (sim->nz <= 0) - warn_parameter_value("sim->nz is not a positive number", sim->nz, - &return_status); - - check_float("sim->dz", sim->dz, &return_status); - if (sim->dz <= 0.0) - warn_parameter_value("sim->dz is not a positive number", sim->dz, - &return_status); - - check_float("sim->t", sim->t, &return_status); - if (sim->t < 0.0) - warn_parameter_value("sim->t is a negative number", sim->t, &return_status); - - check_float("sim->t_end", sim->t_end, &return_status); - if (sim->t > sim->t_end) - warn_parameter_value("sim->t_end is smaller than sim->t", sim->t, - &return_status); - - check_float("sim->dt", sim->dt, &return_status); - if (sim->dt < 0.0) - warn_parameter_value("sim->dt is less than zero", sim->dt, &return_status); - - check_float("sim->file_dt", sim->file_dt, &return_status); - if (sim->file_dt < 0.0) - warn_parameter_value("sim->file_dt is a negative number", sim->file_dt, - &return_status); - - check_float("sim->phi[0]", sim->phi[0], &return_status); - if (sim->phi[0] < 0.0 || sim->phi[0] > 1.0) - warn_parameter_value("sim->phi[0] is not within [0;1]", sim->phi[0], - &return_status); - - check_float("sim->phi_min", sim->phi_min, &return_status); - if (sim->phi_min < 0.0 || sim->phi_min > 1.0) - warn_parameter_value("sim->phi_min is not within [0;1]", sim->phi_min, - &return_status); - - check_float("sim->phi_max", sim->phi_max, &return_status); - if (sim->phi_max < 0.0 || sim->phi_max > 1.0) - warn_parameter_value("sim->phi_max is not within [0;1]", sim->phi_max, - &return_status); - - check_float("sim->dilatancy_constant", sim->dilatancy_constant, - &return_status); - if (sim->dilatancy_constant < 0.0 || sim->dilatancy_constant > 100.0) - warn_parameter_value("sim->dilatancy_constant is not within [0;100]", - sim->dilatancy_constant, &return_status); - - if (sim->fluid != 0 && sim->fluid != 1) - warn_parameter_value("sim->fluid has an invalid value", (double)sim->fluid, - &return_status); - - if (sim->transient != 0 && sim->transient != 1) - warn_parameter_value("sim->transient has an invalid value", - (double)sim->transient, &return_status); - - if (sim->fluid) { - check_float("sim->p_f_mod_ampl", sim->p_f_mod_ampl, &return_status); - if (sim->p_f_mod_ampl < 0.0) - warn_parameter_value("sim->p_f_mod_ampl is not a zero or positive", - sim->p_f_mod_ampl, &return_status); - - check_float("sim->p_f_mod_freq", sim->p_f_mod_freq, &return_status); - if (sim->p_f_mod_freq < 0.0) - warn_parameter_value("sim->p_f_mod_freq is not a zero or positive", - sim->p_f_mod_freq, &return_status); - - check_float("sim->beta_f", sim->beta_f, &return_status); - if (sim->beta_f <= 0.0) - warn_parameter_value("sim->beta_f is not positive", sim->beta_f, - &return_status); - - check_float("sim->alpha", sim->alpha, &return_status); - if (sim->alpha <= 0.0) - warn_parameter_value("sim->alpha is not positive", sim->alpha, - &return_status); - - check_float("sim->mu_f", sim->mu_f, &return_status); - if (sim->mu_f <= 0.0) - warn_parameter_value("sim->mu_f is not positive", sim->mu_f, - &return_status); - - check_float("sim->rho_f", sim->rho_f, &return_status); - if (sim->rho_f <= 0.0) - warn_parameter_value("sim->rho_f is not positive", sim->rho_f, - &return_status); - - check_float("sim->k[0]", sim->k[0], &return_status); - if (sim->k[0] <= 0.0) - warn_parameter_value("sim->k[0] is not positive", sim->k[0], - &return_status); - - check_float("sim->D", sim->D, &return_status); - if (sim->transient && sim->D > 0.0) - warn_parameter_value("constant diffusivity does not work in " - "transient simulations", - sim->D, &return_status); - } - - if (return_status != 0) { - fprintf(stderr, "error: aborting due to invalid parameter choices\n"); - exit(return_status); - } + if (isnan(value)) { + ret = snprintf(message, sizeof(message), "%s is NaN", name); + if (ret < 0 || (size_t)ret >= sizeof(message)) + err(1, "%s: message parsing", __func__); + warn_parameter_value(message, value, return_status); + } else if (isinf(value)) { + ret = snprintf(message, sizeof(message), "%s is infinite", name); + if (ret < 0 || (size_t)ret >= sizeof(message)) + err(1, "%s: message parsing", __func__); + warn_parameter_value(message, value, return_status); + } +} + +int check_simulation_parameters(struct simulation *sim) { + int return_status = 0; + + check_float("sim->G", sim->G, &return_status); + if (sim->G < 0.0) + warn_parameter_value("sim->G is negative", sim->G, &return_status); + + check_float("sim->P_wall", sim->P_wall, &return_status); + if (sim->P_wall < 0.0) + warn_parameter_value("sim->P_wall is negative", sim->P_wall, + &return_status); + + check_float("sim->v_x_bot", sim->v_x_bot, &return_status); + + check_float("sim->mu_wall", sim->mu_wall, &return_status); + if (sim->mu_wall < 0.0) + warn_parameter_value("sim->mu_wall is negative", sim->mu_wall, + &return_status); + + check_float("sim->A", sim->A, &return_status); + if (sim->A < 0.0) + warn_parameter_value("sim->A is negative", sim->A, &return_status); + + check_float("sim->b", sim->b, &return_status); + if (sim->b < 0.0) + warn_parameter_value("sim->b is negative", sim->b, &return_status); + + check_float("sim->mu_s", sim->mu_s, &return_status); + if (sim->mu_s < 0.0) + warn_parameter_value("sim->mu_s is negative", sim->mu_s, &return_status); + + check_float("sim->C", sim->C, &return_status); + + check_float("sim->d", sim->d, &return_status); + if (sim->d <= 0.0) + warn_parameter_value("sim->d is not a positive number", sim->d, + &return_status); + + check_float("sim->rho_s", sim->rho_s, &return_status); + if (sim->rho_s <= 0.0) + warn_parameter_value("sim->rho_s is not a positive number", sim->rho_s, + &return_status); + + if (sim->nz <= 0) + warn_parameter_value("sim->nz is not a positive number", sim->nz, + &return_status); + + check_float("sim->origo_z", sim->origo_z, &return_status); + check_float("sim->L_z", sim->L_z, &return_status); + if (sim->L_z <= sim->origo_z) + warn_parameter_value("sim->L_z is smaller or equal to sim->origo_z", + sim->L_z, &return_status); + + if (sim->nz <= 0) + warn_parameter_value("sim->nz is not a positive number", sim->nz, + &return_status); + + check_float("sim->dz", sim->dz, &return_status); + if (sim->dz <= 0.0) + warn_parameter_value("sim->dz is not a positive number", sim->dz, + &return_status); + + check_float("sim->t", sim->t, &return_status); + if (sim->t < 0.0) + warn_parameter_value("sim->t is a negative number", sim->t, &return_status); + + check_float("sim->t_end", sim->t_end, &return_status); + if (sim->t > sim->t_end) + warn_parameter_value("sim->t_end is smaller than sim->t", sim->t, + &return_status); + + check_float("sim->dt", sim->dt, &return_status); + if (sim->dt < 0.0) + warn_parameter_value("sim->dt is less than zero", sim->dt, &return_status); + + check_float("sim->file_dt", sim->file_dt, &return_status); + if (sim->file_dt < 0.0) + warn_parameter_value("sim->file_dt is a negative number", sim->file_dt, + &return_status); + + check_float("sim->phi[0]", sim->phi[0], &return_status); + if (sim->phi[0] < 0.0 || sim->phi[0] > 1.0) + warn_parameter_value("sim->phi[0] is not within [0;1]", sim->phi[0], + &return_status); + + check_float("sim->phi_min", sim->phi_min, &return_status); + if (sim->phi_min < 0.0 || sim->phi_min > 1.0) + warn_parameter_value("sim->phi_min is not within [0;1]", sim->phi_min, + &return_status); + + check_float("sim->phi_max", sim->phi_max, &return_status); + if (sim->phi_max < 0.0 || sim->phi_max > 1.0) + warn_parameter_value("sim->phi_max is not within [0;1]", sim->phi_max, + &return_status); + + check_float("sim->dilatancy_constant", sim->dilatancy_constant, + &return_status); + if (sim->dilatancy_constant < 0.0 || sim->dilatancy_constant > 100.0) + warn_parameter_value("sim->dilatancy_constant is not within [0;100]", + sim->dilatancy_constant, &return_status); + + if (sim->fluid != 0 && sim->fluid != 1) + warn_parameter_value("sim->fluid has an invalid value", (double)sim->fluid, + &return_status); + + if (sim->transient != 0 && sim->transient != 1) + warn_parameter_value("sim->transient has an invalid value", + (double)sim->transient, &return_status); + + if (sim->fluid) { + check_float("sim->p_f_mod_ampl", sim->p_f_mod_ampl, &return_status); + if (sim->p_f_mod_ampl < 0.0) + warn_parameter_value("sim->p_f_mod_ampl is not a zero or positive", + sim->p_f_mod_ampl, &return_status); + + check_float("sim->p_f_mod_freq", sim->p_f_mod_freq, &return_status); + if (sim->p_f_mod_freq < 0.0) + warn_parameter_value("sim->p_f_mod_freq is not a zero or positive", + sim->p_f_mod_freq, &return_status); + + check_float("sim->beta_f", sim->beta_f, &return_status); + if (sim->beta_f <= 0.0) + warn_parameter_value("sim->beta_f is not positive", sim->beta_f, + &return_status); + + check_float("sim->alpha", sim->alpha, &return_status); + if (sim->alpha <= 0.0) + warn_parameter_value("sim->alpha is not positive", sim->alpha, + &return_status); + + check_float("sim->mu_f", sim->mu_f, &return_status); + if (sim->mu_f <= 0.0) + warn_parameter_value("sim->mu_f is not positive", sim->mu_f, + &return_status); + + check_float("sim->rho_f", sim->rho_f, &return_status); + if (sim->rho_f <= 0.0) + warn_parameter_value("sim->rho_f is not positive", sim->rho_f, + &return_status); + + check_float("sim->k[0]", sim->k[0], &return_status); + if (sim->k[0] <= 0.0) + warn_parameter_value("sim->k[0] is not positive", sim->k[0], + &return_status); + + check_float("sim->D", sim->D, &return_status); + if (sim->transient && sim->D > 0.0) + warn_parameter_value("constant diffusivity does not work in " + "transient simulations", + sim->D, &return_status); + } + + if (return_status != 0) + fprintf(stderr, "error: aborting due to invalid parameter choices\n"); + + return return_status; } void lithostatic_pressure_distribution(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->sigma_n[i] = sim->P_wall + (1.0 - sim->phi[i]) * sim->rho_s * sim->G * - (sim->L_z - sim->z[i]); + for (i = 0; i < sim->nz; ++i) + sim->sigma_n[i] = sim->P_wall + (1.0 - sim->phi[i]) * sim->rho_s * sim->G * + (sim->origo_z + sim->L_z - sim->z[i]); } inline static double inertia_number(double gamma_dot_p, double d, double sigma_n_eff, double rho_s) { - return fabs(gamma_dot_p) * d / sqrt(sigma_n_eff / rho_s); + return fabs(gamma_dot_p) * d / sqrt(sigma_n_eff / rho_s); } void compute_inertia_number(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->I[i] = - inertia_number(sim->gamma_dot_p[i], sim->d, - fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->rho_s); + for (i = 0; i < sim->nz; ++i) + sim->I[i] = + inertia_number(sim->gamma_dot_p[i], sim->d, + fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->rho_s); } void compute_critical_state_porosity(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->phi_c[i] = sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i]; + for (i = 0; i < sim->nz; ++i) + sim->phi_c[i] = sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i]; } void compute_critical_state_friction(struct simulation *sim) { - int i; + int i; - if (sim->fluid) - for (i = 0; i < sim->nz; ++i) - sim->mu_c[i] = - sim->mu_wall / (fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN) / - (sim->P_wall - sim->p_f_top)); - else - for (i = 0; i < sim->nz; ++i) - sim->mu_c[i] = - sim->mu_wall / (1.0 + (1.0 - sim->phi[i]) * sim->rho_s * sim->G * - (sim->L_z - sim->z[i]) / sim->P_wall); + if (sim->fluid) + for (i = 0; i < sim->nz; ++i) + sim->mu_c[i] = + sim->mu_wall / (fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN) / + (sim->P_wall - sim->p_f_top)); + else + for (i = 0; i < sim->nz; ++i) + sim->mu_c[i] = + sim->mu_wall / + (1.0 + (1.0 - sim->phi[i]) * sim->rho_s * sim->G * + (sim->origo_z + sim->L_z - sim->z[i]) / sim->P_wall); } static void compute_friction(struct simulation *sim) { - int i; + int i; - if (sim->transient) - for (i = 0; i < sim->nz; ++i) - sim->mu[i] = sim->mu_c[i] + sim->tan_psi[i]; - else - for (i = 0; i < sim->nz; ++i) - sim->mu[i] = sim->mu_c[i]; + if (sim->transient) + for (i = 0; i < sim->nz; ++i) + sim->mu[i] = sim->mu_c[i] + sim->tan_psi[i]; + else + for (i = 0; i < sim->nz; ++i) + sim->mu[i] = sim->mu_c[i]; } static void compute_tan_dilatancy_angle(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->tan_psi[i] = sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]); + for (i = 0; i < sim->nz; ++i) + sim->tan_psi[i] = sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]); } static void compute_transient_fields(struct simulation *sim) { - int i; + int i; - /* Fused loop: compute I, phi_c, and tan_psi in single pass */ - for (i = 0; i < sim->nz; ++i) { - /* Eq. 1: Inertia number */ - sim->I[i] = - inertia_number(sim->gamma_dot_p[i], sim->d, - fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->rho_s); + /* Fused loop: compute I, phi_c, and tan_psi in single pass */ + for (i = 0; i < sim->nz; ++i) { + /* Eq. 1: Inertia number */ + sim->I[i] = + inertia_number(sim->gamma_dot_p[i], sim->d, + fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->rho_s); - /* Eq. 2: Critical state porosity */ - sim->phi_c[i] = sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i]; + /* Eq. 2: Critical state porosity */ + sim->phi_c[i] = sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i]; - /* Eq. 5: Dilatancy angle */ - sim->tan_psi[i] = sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]); - } + /* Eq. 5: Dilatancy angle */ + sim->tan_psi[i] = sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]); + } } static void compute_porosity_change(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->phi_dot[i] = sim->tan_psi[i] * sim->gamma_dot_p[i] * sim->phi[i]; + for (i = 0; i < sim->nz; ++i) + sim->phi_dot[i] = sim->tan_psi[i] * sim->gamma_dot_p[i] * sim->phi[i]; } double kozeny_carman(const double diameter, const double porosity) { - return (diameter * diameter) / 180.0 * (porosity * porosity * porosity) / - ((1.0 - porosity) * (1.0 - porosity)); + return (diameter * diameter) / 180.0 * (porosity * porosity * porosity) / + ((1.0 - porosity) * (1.0 - porosity)); } static void compute_permeability(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->k[i] = kozeny_carman(sim->d, sim->phi[i]); + for (i = 0; i < sim->nz; ++i) + sim->k[i] = kozeny_carman(sim->d, sim->phi[i]); } static double shear_strain_rate_plastic(const double fluidity, const double friction) { - return fluidity * friction; + return fluidity * friction; } static void compute_shear_strain_rate_plastic(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->gamma_dot_p[i] = - shear_strain_rate_plastic(sim->g_ghost[i + 1], sim->mu[i]); + for (i = 0; i < sim->nz; ++i) + sim->gamma_dot_p[i] = + shear_strain_rate_plastic(sim->g_ghost[i + 1], sim->mu[i]); } static void compute_shear_velocity(struct simulation *sim) { - int i; + int i; - /* TODO: implement iterative solver for v_x from gamma_dot_p field */ - /* Dirichlet BC at bottom */ - sim->v_x[0] = sim->v_x_bot; + /* TODO: implement iterative solver for v_x from gamma_dot_p field */ + /* Dirichlet BC at bottom */ + sim->v_x[0] = sim->v_x_bot; - for (i = 1; i < sim->nz; ++i) - sim->v_x[i] = sim->v_x[i - 1] + sim->gamma_dot_p[i] * sim->dz; + for (i = 1; i < sim->nz; ++i) + sim->v_x[i] = sim->v_x[i - 1] + sim->gamma_dot_p[i] * sim->dz; } void compute_effective_stress(struct simulation *sim) { - int i; + int i; - if (sim->fluid) - for (i = 0; i < sim->nz; ++i) { - /* use implicit (next-step) pressure for tighter coupling */ - sim->sigma_n_eff[i] = sim->sigma_n[i] - sim->p_f_next_ghost[i + 1]; - if (sim->sigma_n_eff[i] < 0) - warnx("%s: sigma_n_eff[%d] is negative with value %g", __func__, i, - sim->sigma_n_eff[i]); - } - else - for (i = 0; i < sim->nz; ++i) - sim->sigma_n_eff[i] = sim->sigma_n[i]; + if (sim->fluid) + for (i = 0; i < sim->nz; ++i) { + /* use implicit (next-step) pressure for tighter coupling */ + sim->sigma_n_eff[i] = sim->sigma_n[i] - sim->p_f_next_ghost[i + 1]; + if (sim->sigma_n_eff[i] < 0) + warnx("%s: sigma_n_eff[%d] is negative with value %g", __func__, i, + sim->sigma_n_eff[i]); + } + else + for (i = 0; i < sim->nz; ++i) + sim->sigma_n_eff[i] = sim->sigma_n[i]; } static double cooperativity_length(const double A, const double d, const double mu, const double p, const double mu_s, const double C) { - double denom = fmax(fabs((mu - C / p) - mu_s), 1e-10); - return A * d / sqrt(denom); + double denom = fmax(fabs((mu - C / p) - mu_s), 1e-10); + return A * d / sqrt(denom); } static void compute_cooperativity_length(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->xi[i] = cooperativity_length( - sim->A, sim->d, sim->mu[i], fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), - sim->mu_s, sim->C); + for (i = 0; i < sim->nz; ++i) + sim->xi[i] = cooperativity_length( + sim->A, sim->d, sim->mu[i], fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), + sim->mu_s, sim->C); } static double local_fluidity(const double p, const double mu, const double mu_s, const double C, const double b, const double rho_s, const double d) { - if (mu - C / p <= mu_s) - return 0.0; - else - return sqrt(p / (rho_s * d * d)) * ((mu - C / p) - mu_s) / (b * mu); + if (mu - C / p <= mu_s) + return 0.0; + else + return sqrt(p / (rho_s * d * d)) * ((mu - C / p) - mu_s) / (b * mu); } static void compute_local_fluidity(struct simulation *sim) { - int i; + int i; - for (i = 0; i < sim->nz; ++i) - sim->g_local[i] = - local_fluidity(fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->mu[i], - sim->mu_s, sim->C, sim->b, sim->rho_s, sim->d); + for (i = 0; i < sim->nz; ++i) + sim->g_local[i] = + local_fluidity(fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->mu[i], + sim->mu_s, sim->C, sim->b, sim->rho_s, sim->d); } void set_bc_neumann(double *a, const int nz, const int boundary, const double df, const double dx) { - if (boundary == -1) - a[0] = a[1] + df * dx; - else if (boundary == +1) - a[nz + 1] = a[nz] - df * dx; - else - errx(1, "%s: Unknown boundary %d\n", __func__, boundary); + if (boundary == -1) + a[0] = a[1] + df * dx; + else if (boundary == +1) + a[nz + 1] = a[nz] - df * dx; + else + errx(1, "%s: Unknown boundary %d\n", __func__, boundary); } void set_bc_dirichlet(double *a, const int nz, const int boundary, const double value) { - if (boundary == -1) - a[0] = value; - else if (boundary == +1) - a[nz + 1] = value; - else - errx(1, "%s: Unknown boundary %d\n", __func__, boundary); + if (boundary == -1) + a[0] = value; + else if (boundary == +1) + a[nz + 1] = value; + else + errx(1, "%s: Unknown boundary %d\n", __func__, boundary); } double residual(double new_val, double old_val) { - return (new_val - old_val) / (fmax(fabs(old_val), fabs(new_val)) + 1e-16); + return (new_val - old_val) / (fmax(fabs(old_val), fabs(new_val)) + 1e-16); } void tridiagonal_solver(double *x, const double *a, const double *b, const double *c, const double *d, double *c_prime, double *d_prime, int n) { - /* - * TDMA (Thomas Algorithm) solver for tridiagonal system - * a: sub-diagonal (means a[i] is coeff for x[i-1]) - * b: main diagonal (means b[i] is coeff for x[i]) - * c: super-diagonal (means c[i] is coeff for x[i+1]) - * d: right hand side - * x: solution vector - * n: system size - * - * Note: This implementation assumes 0-indexed arrays, where: - * Eq i (0 <= i < n): a[i]*x[i-1] + b[i]*x[i] + c[i]*x[i+1] = d[i] - * Boundary conditions: a[0] = 0 and c[n-1] = 0 (assumed to be handled by - * caller or zeroed) - */ - int i; - - /* Forward sweep */ - c_prime[0] = c[0] / b[0]; - d_prime[0] = d[0] / b[0]; - - for (i = 1; i < n; i++) { - double temp = 1.0 / (b[i] - a[i] * c_prime[i - 1]); - c_prime[i] = c[i] * temp; - d_prime[i] = (d[i] - a[i] * d_prime[i - 1]) * temp; - } - - /* Back substitution */ - x[n - 1] = d_prime[n - 1]; - for (i = n - 2; i >= 0; i--) { - x[i] = d_prime[i] - c_prime[i] * x[i + 1]; - } + /* + * TDMA (Thomas Algorithm) solver for tridiagonal system + * a: sub-diagonal (means a[i] is coeff for x[i-1]) + * b: main diagonal (means b[i] is coeff for x[i]) + * c: super-diagonal (means c[i] is coeff for x[i+1]) + * d: right hand side + * x: solution vector + * n: system size + * + * Note: This implementation assumes 0-indexed arrays, where: + * Eq i (0 <= i < n): a[i]*x[i-1] + b[i]*x[i] + c[i]*x[i+1] = d[i] + * Boundary conditions: a[0] = 0 and c[n-1] = 0 (assumed to be handled by + * caller or zeroed) + */ + int i; + + /* Forward sweep */ + c_prime[0] = c[0] / b[0]; + d_prime[0] = d[0] / b[0]; + + for (i = 1; i < n; i++) { + double temp = 1.0 / (b[i] - a[i] * c_prime[i - 1]); + c_prime[i] = c[i] * temp; + d_prime[i] = (d[i] - a[i] * d_prime[i - 1]) * temp; + } + + /* Back substitution */ + x[n - 1] = d_prime[n - 1]; + for (i = n - 2; i >= 0; i--) { + x[i] = d_prime[i] - c_prime[i] * x[i + 1]; + } } static int implicit_1d_sor_poisson_solver(struct simulation *sim) { - /* - * Replaced SOR solver with direct TDMA solver. - * System: -0.5*g[i-1] + (1 + C)*g[i] - 0.5*g[i+1] = C*g_local[i] - * where C = dz^2 / (2 * xi^2) - */ - int i; - int n = sim->nz; - double *a = sim->tdma_a; - double *b = sim->tdma_b; - double *c = sim->tdma_c; - double *d = sim->tdma_d; - double *x = sim->tdma_x; - - /* Set up TDMA arrays */ - for (i = 0; i < n; i++) { - double coorp_term = sim->dz * sim->dz / (2.0 * sim->xi[i] * sim->xi[i]); - - a[i] = -0.5; - b[i] = 1.0 + coorp_term; - c[i] = -0.5; - d[i] = coorp_term * sim->g_local[i]; - } - - /* Boundary conditions adjustment */ - /* g[-1] = 0 (sim->g_ghost[0]) -> a[0]*0 term vanishes */ - /* g[n] = 0 (sim->g_ghost[n+1]) -> c[n-1]*0 term vanishes */ - /* But TDMA solver assumes a[0] and c[n-1] are coefficients in the matrix, - which should be 0 for the first and last row if we strictly follow standard - TDMA for isolated systems. However, our equation for i=0 is: -0.5*g{-1} + - b[0]*g{0} + c[0]*g{1} = d[0] Since g{-1}=0, the term -0.5*g{-1} is 0. So - effectively a[0]=0 in the matrix sense. Same for i=n-1: c[n-1]*g{n} is 0. - */ - a[0] = 0.0; - c[n - 1] = 0.0; - - tridiagonal_solver(x, a, b, c, d, sim->tdma_c_prime, sim->tdma_d_prime, n); - - /* Copy result back to ghost array (indices 1 to n) */ - set_bc_dirichlet(sim->g_ghost, sim->nz, -1, 0.0); - set_bc_dirichlet(sim->g_ghost, sim->nz, +1, 0.0); - for (i = 0; i < n; i++) { - sim->g_ghost[i + 1] = x[i]; - } - - g_stats.poisson_iters += 1; /* Count as 1 iteration for stats */ - return 0; + /* + * Replaced SOR solver with direct TDMA solver. + * System: -0.5*g[i-1] + (1 + C)*g[i] - 0.5*g[i+1] = C*g_local[i] + * where C = dz^2 / (2 * xi^2) + */ + int i; + int n = sim->nz; + double *a = sim->tdma_a; + double *b = sim->tdma_b; + double *c = sim->tdma_c; + double *d = sim->tdma_d; + double *x = sim->tdma_x; + + /* Set up TDMA arrays */ + for (i = 0; i < n; i++) { + double coorp_term = sim->dz * sim->dz / (2.0 * sim->xi[i] * sim->xi[i]); + + a[i] = -0.5; + b[i] = 1.0 + coorp_term; + c[i] = -0.5; + d[i] = coorp_term * sim->g_local[i]; + } + + /* Boundary conditions adjustment */ + /* g[-1] = 0 (sim->g_ghost[0]) -> a[0]*0 term vanishes */ + /* g[n] = 0 (sim->g_ghost[n+1]) -> c[n-1]*0 term vanishes */ + /* But TDMA solver assumes a[0] and c[n-1] are coefficients in the matrix, + which should be 0 for the first and last row if we strictly follow standard + TDMA for isolated systems. However, our equation for i=0 is: -0.5*g{-1} + + b[0]*g{0} + c[0]*g{1} = d[0] Since g{-1}=0, the term -0.5*g{-1} is 0. So + effectively a[0]=0 in the matrix sense. Same for i=n-1: c[n-1]*g{n} is 0. + */ + a[0] = 0.0; + c[n - 1] = 0.0; + + tridiagonal_solver(x, a, b, c, d, sim->tdma_c_prime, sim->tdma_d_prime, n); + + /* Copy result back to ghost array (indices 1 to n) */ + set_bc_dirichlet(sim->g_ghost, sim->nz, -1, 0.0); + set_bc_dirichlet(sim->g_ghost, sim->nz, +1, 0.0); + for (i = 0; i < n; i++) { + sim->g_ghost[i + 1] = x[i]; + } + + g_stats.poisson_iters += 1; /* Count as 1 iteration for stats */ + return 0; } void write_output_file(struct simulation *sim, const int normalize) { - int ret; - char outfile[200]; - FILE *fp; + int ret; + char outfile[200]; + FILE *fp; - ret = snprintf(outfile, sizeof(outfile), "%s.output%05d.txt", sim->name, - sim->n_file++); - if (ret < 0 || (size_t)ret >= sizeof(outfile)) - err(1, "%s: outfile snprintf", __func__); + ret = snprintf(outfile, sizeof(outfile), "%s.output%05d.txt", sim->name, + sim->n_file++); + if (ret < 0 || (size_t)ret >= sizeof(outfile)) + err(1, "%s: outfile snprintf", __func__); - if ((fp = fopen(outfile, "w")) != NULL) { - print_output(sim, fp, normalize); - fclose(fp); - } else { - fprintf(stderr, "could not open output file: %s", outfile); - exit(1); - } + if ((fp = fopen(outfile, "w")) != NULL) { + print_output(sim, fp, normalize); + fclose(fp); + } else { + fprintf(stderr, "could not open output file: %s", outfile); + exit(1); + } } void print_output(struct simulation *sim, FILE *fp, const int norm) { - int i; - double *v_x_out; + int i; + double *v_x_out; - if (norm) - v_x_out = normalize(sim->v_x, sim->nz); - else - v_x_out = copy(sim->v_x, sim->nz); + if (norm) + v_x_out = normalize(sim->v_x, sim->nz); + else + v_x_out = copy(sim->v_x, sim->nz); - for (i = 0; i < sim->nz; ++i) - fprintf(fp, - "%.17g\t%.17g\t%.17g\t" - "%.17g\t%.17g\t%.17g\t" - "%.17g\t%.17g\t%.17g\t%.17g" - "\n", - sim->z[i], v_x_out[i], sim->sigma_n_eff[i], sim->p_f_ghost[i + 1], - sim->mu[i], sim->gamma_dot_p[i], sim->phi[i], sim->I[i], - sim->mu[i] * fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), - sim->d_x[i]); + for (i = 0; i < sim->nz; ++i) + fprintf(fp, + "%.17g\t%.17g\t%.17g\t" + "%.17g\t%.17g\t%.17g\t" + "%.17g\t%.17g\t%.17g\t%.17g" + "\n", + sim->z[i], v_x_out[i], sim->sigma_n_eff[i], sim->p_f_ghost[i + 1], + sim->mu[i], sim->gamma_dot_p[i], sim->phi[i], sim->I[i], + sim->mu[i] * fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), + sim->d_x[i]); - free(v_x_out); + free(v_x_out); } -static void temporal_increment(struct simulation *sim) { - int i; +static int temporal_increment(struct simulation *sim) { + int i; - if (sim->transient) - for (i = 0; i < sim->nz; ++i) - sim->phi[i] += sim->phi_dot[i] * sim->dt; + if (sim->transient) + for (i = 0; i < sim->nz; ++i) + sim->phi[i] += sim->phi_dot[i] * sim->dt; - if (sim->fluid) - for (i = 0; i < sim->nz; ++i) { - if (isnan(sim->p_f_dot[i])) { - errx(1, "encountered NaN at sim->p_f_dot[%d] (t = %g s)", i, sim->t); - } else { - sim->p_f_ghost[i + 1] += sim->p_f_dot[i] * sim->dt; - } - } + if (sim->fluid) + for (i = 0; i < sim->nz; ++i) { + if (isnan(sim->p_f_dot[i])) { + fprintf(stderr, "encountered NaN at sim->p_f_dot[%d] (t = %g s)\n", i, + sim->t); + return 1; + } else { + sim->p_f_ghost[i + 1] += sim->p_f_dot[i] * sim->dt; + } + } - for (i = 0; i < sim->nz; ++i) - sim->d_x[i] += sim->v_x[i] * sim->dt; - sim->t += sim->dt; + for (i = 0; i < sim->nz; ++i) + sim->d_x[i] += sim->v_x[i] * sim->dt; + sim->t += sim->dt; + + return 0; } int coupled_shear_solver(struct simulation *sim, const int max_iter, const double rel_tol) { - int i, coupled_iter, stress_iter = 0; - double r_norm_max, vel_res_norm = NAN, mu_wall_orig = sim->mu_wall; - - copy_values(sim->p_f_ghost, sim->p_f_next_ghost, sim->nz + 2); - compute_effective_stress(sim); /* Eq. 9 */ - - do { /* stress iterations */ - coupled_iter = 0; - do { /* coupled iterations */ - - if (sim->transient) { - copy_values(sim->phi_dot, sim->old_val, sim->nz); - - /* Fused loop for Eqs. 1-6 */ - for (i = 0; i < sim->nz; ++i) { - /* Eq. 1: Inertia number */ - sim->I[i] = inertia_number(sim->gamma_dot_p[i], sim->d, - fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), - sim->rho_s); - - /* Eq. 2: Critical state porosity */ - sim->phi_c[i] = - sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i]; - - /* Eq. 5: Dilatancy angle */ - sim->tan_psi[i] = - sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]); - - /* Eq. 7: Critical state friction */ - if (sim->fluid) - sim->mu_c[i] = - sim->mu_wall / (fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN) / - (sim->P_wall - sim->p_f_top)); - else - sim->mu_c[i] = sim->mu_wall / - (1.0 + (1.0 - sim->phi[i]) * sim->rho_s * sim->G * - (sim->L_z - sim->z[i]) / sim->P_wall); - - /* Eq. 3: Porosity change */ - sim->phi_dot[i] = sim->tan_psi[i] * sim->gamma_dot_p[i] * sim->phi[i]; - - /* Eq. 6: Permeability */ - sim->k[i] = kozeny_carman(sim->d, sim->phi[i]); - - /* Eq. 4: Friction */ - sim->mu[i] = sim->mu_c[i] + sim->tan_psi[i]; - } - } else { - /* Non-transient case */ - compute_critical_state_friction(sim); - compute_friction(sim); - } - - /* step 5, Eq. 13 */ - if (sim->fluid && (sim->t > 0)) - if (darcy_solver_1d(sim)) - exit(11); - - /* step 6 */ - compute_effective_stress(sim); /* Eq. 9 */ - - /* step 7 */ - compute_local_fluidity(sim); /* Eq. 10 */ - compute_cooperativity_length(sim); /* Eq. 12 */ - - /* step 8, Eq. 11 */ - if (implicit_1d_sor_poisson_solver(sim)) - exit(12); - - /* step 9 */ - compute_shear_strain_rate_plastic(sim); /* Eq. 8 */ - compute_inertia_number(sim); /* Eq. 1 */ - compute_shear_velocity(sim); + int i, coupled_iter, stress_iter = 0; + double r_norm_max, vel_res_norm = NAN, mu_wall_orig = sim->mu_wall; + + copy_values(sim->p_f_ghost, sim->p_f_next_ghost, sim->nz + 2); + compute_effective_stress(sim); /* Eq. 9 */ + + do { /* stress iterations */ + coupled_iter = 0; + do { /* coupled iterations */ + + if (sim->transient) { + copy_values(sim->phi_dot, sim->old_val, sim->nz); + + /* Fused loop for Eqs. 1-6 */ + for (i = 0; i < sim->nz; ++i) { + /* Eq. 1: Inertia number */ + sim->I[i] = inertia_number(sim->gamma_dot_p[i], sim->d, + fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), + sim->rho_s); + + /* Eq. 2: Critical state porosity */ + sim->phi_c[i] = + sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i]; + + /* Eq. 5: Dilatancy angle */ + sim->tan_psi[i] = + sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]); + + /* Eq. 7: Critical state friction */ + if (sim->fluid) + sim->mu_c[i] = + sim->mu_wall / (fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN) / + (sim->P_wall - sim->p_f_top)); + else + sim->mu_c[i] = + sim->mu_wall / + (1.0 + (1.0 - sim->phi[i]) * sim->rho_s * sim->G * + (sim->origo_z + sim->L_z - sim->z[i]) / sim->P_wall); + + /* Eq. 3: Porosity change */ + sim->phi_dot[i] = sim->tan_psi[i] * sim->gamma_dot_p[i] * sim->phi[i]; + + /* Eq. 6: Permeability */ + sim->k[i] = kozeny_carman(sim->d, sim->phi[i]); + + /* Eq. 4: Friction */ + sim->mu[i] = sim->mu_c[i] + sim->tan_psi[i]; + } + } else { + /* Non-transient case */ + compute_critical_state_friction(sim); + compute_friction(sim); + } + + /* step 5, Eq. 13 */ + if (sim->fluid && (sim->t > 0)) + if (darcy_solver_1d(sim)) + return 11; + + /* step 6 */ + compute_effective_stress(sim); /* Eq. 9 */ + + /* step 7 */ + compute_local_fluidity(sim); /* Eq. 10 */ + compute_cooperativity_length(sim); /* Eq. 12 */ + + /* step 8, Eq. 11 */ + if (implicit_1d_sor_poisson_solver(sim)) + return 12; + + /* step 9 */ + compute_shear_strain_rate_plastic(sim); /* Eq. 8 */ + compute_inertia_number(sim); /* Eq. 1 */ + compute_shear_velocity(sim); #ifdef DEBUG - /* for (i = 0; i < sim->nz; ++i) { */ - for (i = sim->nz - 1; i < sim->nz; ++i) { - printf("\nsim->t = %g s\n", sim->t); - printf("sim->I[%d] = %g\n", i, sim->I[i]); - printf("sim->phi_c[%d] = %g\n", i, sim->phi_c[i]); - printf("sim->tan_psi[%d] = %g\n", i, sim->tan_psi[i]); - printf("sim->mu_c[%d] = %g\n", i, sim->mu_c[i]); - printf("sim->phi[%d] = %g\n", i, sim->phi[i]); - printf("sim->phi_dot[%d] = %g\n", i, sim->phi_dot[i]); - printf("sim->k[%d] = %g\n", i, sim->k[i]); - printf("sim->mu[%d] = %g\n", i, sim->mu[i]); - } + /* for (i = 0; i < sim->nz; ++i) { */ + for (i = sim->nz - 1; i < sim->nz; ++i) { + printf("\nsim->t = %g s\n", sim->t); + printf("sim->I[%d] = %g\n", i, sim->I[i]); + printf("sim->phi_c[%d] = %g\n", i, sim->phi_c[i]); + printf("sim->tan_psi[%d] = %g\n", i, sim->tan_psi[i]); + printf("sim->mu_c[%d] = %g\n", i, sim->mu_c[i]); + printf("sim->phi[%d] = %g\n", i, sim->phi[i]); + printf("sim->phi_dot[%d] = %g\n", i, sim->phi_dot[i]); + printf("sim->k[%d] = %g\n", i, sim->k[i]); + printf("sim->mu[%d] = %g\n", i, sim->mu[i]); + } #endif - /* stable porosity change field == coupled solution converged */ - if (sim->transient) { - for (i = 0; i < sim->nz; ++i) - sim->g_r_norm[i] = fabs(residual(sim->phi_dot[i], sim->old_val[i])); - r_norm_max = max_with_threshold(sim->g_r_norm, sim->nz, rel_tol); - if (r_norm_max <= rel_tol && coupled_iter > 0) - break; - if (coupled_iter++ >= max_iter) { - fprintf(stderr, "coupled_shear_solver: "); - fprintf(stderr, - "Transient solution did not converge " - "after %d iterations\n", - coupled_iter); - fprintf(stderr, ".. Residual normalized error: %g\n", r_norm_max); - return 1; - } - } - - } while (sim->transient); - if (!isnan(sim->v_x_limit) || !isnan(sim->v_x_fix)) { - if (!isnan(sim->v_x_limit)) { - double v_ref = - fmax(fabs(sim->v_x_limit), fabs(sim->v_x[sim->nz - 1])) + 1e-12; - vel_res_norm = (sim->v_x_limit - sim->v_x[sim->nz - 1]) / v_ref; - if (vel_res_norm > 0.0) - vel_res_norm = 0.0; - } else { - double v_ref = - fmax(fabs(sim->v_x_fix), fabs(sim->v_x[sim->nz - 1])) + 1e-12; - vel_res_norm = (sim->v_x_fix - sim->v_x[sim->nz - 1]) / v_ref; - } - sim->mu_wall *= 1.0 + (vel_res_norm * 1e-3); - } - if (++stress_iter > max_iter) { - fprintf(stderr, "error: stress solution did not converge:\n"); - fprintf(stderr, - "v_x=%g, v_x_fix=%g, v_x_limit=%g, " - "vel_res_norm=%g, mu_wall=%g\n", - sim->v_x[sim->nz - 1], sim->v_x_fix, sim->v_x_limit, vel_res_norm, - sim->mu_wall); - return 10; - } - } while ((!isnan(sim->v_x_fix) || !isnan(sim->v_x_limit)) && - fabs(vel_res_norm) > RTOL_VELOCITY); - - if (!isnan(sim->v_x_limit) || !isnan(sim->v_x_fix)) - sim->mu_wall = mu_wall_orig; - - temporal_increment(sim); - - g_stats.coupled_iters += coupled_iter; - g_stats.stress_iters += stress_iter; - g_stats.timesteps++; - - return 0; + /* stable porosity change field == coupled solution converged */ + if (sim->transient) { + for (i = 0; i < sim->nz; ++i) + sim->g_r_norm[i] = fabs(residual(sim->phi_dot[i], sim->old_val[i])); + r_norm_max = max_with_threshold(sim->g_r_norm, sim->nz, rel_tol); + if (r_norm_max <= rel_tol && coupled_iter > 0) + break; + if (coupled_iter++ >= max_iter) { + fprintf(stderr, "coupled_shear_solver: "); + fprintf(stderr, + "Transient solution did not converge " + "after %d iterations\n", + coupled_iter); + fprintf(stderr, ".. Residual normalized error: %g\n", r_norm_max); + return 1; + } + } + + } while (sim->transient); + if (!isnan(sim->v_x_limit) || !isnan(sim->v_x_fix)) { + if (!isnan(sim->v_x_limit)) { + double v_ref = + fmax(fabs(sim->v_x_limit), fabs(sim->v_x[sim->nz - 1])) + 1e-12; + vel_res_norm = (sim->v_x_limit - sim->v_x[sim->nz - 1]) / v_ref; + if (vel_res_norm > 0.0) + vel_res_norm = 0.0; + } else { + double v_ref = + fmax(fabs(sim->v_x_fix), fabs(sim->v_x[sim->nz - 1])) + 1e-12; + vel_res_norm = (sim->v_x_fix - sim->v_x[sim->nz - 1]) / v_ref; + } + sim->mu_wall *= 1.0 + (vel_res_norm * 1e-3); + } + if (++stress_iter > max_iter) { + fprintf(stderr, "error: stress solution did not converge:\n"); + fprintf(stderr, + "v_x=%g, v_x_fix=%g, v_x_limit=%g, " + "vel_res_norm=%g, mu_wall=%g\n", + sim->v_x[sim->nz - 1], sim->v_x_fix, sim->v_x_limit, vel_res_norm, + sim->mu_wall); + return 10; + } + } while ((!isnan(sim->v_x_fix) || !isnan(sim->v_x_limit)) && + fabs(vel_res_norm) > RTOL_VELOCITY); + + if (!isnan(sim->v_x_limit) || !isnan(sim->v_x_fix)) + sim->mu_wall = mu_wall_orig; + + if (temporal_increment(sim)) + return 13; + + g_stats.coupled_iters += coupled_iter; + g_stats.stress_iters += stress_iter; + g_stats.timesteps++; + + return 0; } double find_flux(const struct simulation *sim) { - int i; - double flux = 0.0; + int i; + double flux = 0.0; - for (i = 1; i < sim->nz; ++i) - flux += (sim->v_x[i] + sim->v_x[i - 1]) / 2.0 * sim->dz; + for (i = 1; i < sim->nz; ++i) + flux += (sim->v_x[i] + sim->v_x[i - 1]) / 2.0 * sim->dz; - return flux; + return flux; } void set_coupled_fluid_transient_timestep(struct simulation *sim, const double safety) { - double max_gamma_dot, mu, xi, max_I, dt; + double max_gamma_dot, mu, xi, max_I, dt; - /* max expected strain rate */ - max_gamma_dot = 1.0 / sim->d; - if (!isnan(sim->v_x_fix)) - max_gamma_dot = sim->v_x_fix / sim->dz; - if (!isnan(sim->v_x_limit)) - max_gamma_dot = sim->v_x_limit / sim->dz; + /* max expected strain rate */ + max_gamma_dot = 1.0 / sim->d; + if (!isnan(sim->v_x_fix)) + max_gamma_dot = sim->v_x_fix / sim->dz; + if (!isnan(sim->v_x_limit)) + max_gamma_dot = sim->v_x_limit / sim->dz; - /* estimate for shear friction */ - mu = (sim->mu_wall / ((sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl) / - (sim->P_wall - sim->p_f_top))) + - sim->dilatancy_constant * sim->phi[sim->nz - 1]; + /* estimate for shear friction */ + mu = (sim->mu_wall / ((sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl) / + (sim->P_wall - sim->p_f_top))) + + sim->dilatancy_constant * sim->phi[sim->nz - 1]; - /* estimate for cooperativity length */ - xi = cooperativity_length(sim->A, sim->d, mu, - (sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl), - sim->mu_s, sim->C); + /* estimate for cooperativity length */ + xi = cooperativity_length(sim->A, sim->d, mu, + (sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl), + sim->mu_s, sim->C); - /* max expected inertia number */ - max_I = - inertia_number(max_gamma_dot, sim->d, - sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl, sim->rho_s); + /* max expected inertia number */ + max_I = + inertia_number(max_gamma_dot, sim->d, + sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl, sim->rho_s); - dt = xi * (sim->alpha + sim->phi[sim->nz - 1] * sim->beta_f) * sim->mu_f / - (sim->phi[sim->nz - 1] * sim->phi[sim->nz - 1] * sim->phi[sim->nz - 1] * - sim->L_z * max_I); + dt = xi * (sim->alpha + sim->phi[sim->nz - 1] * sim->beta_f) * sim->mu_f / + (sim->phi[sim->nz - 1] * sim->phi[sim->nz - 1] * sim->phi[sim->nz - 1] * + sim->L_z * max_I); - if (sim->dt > safety * dt) - sim->dt = safety * dt; + if (sim->dt > safety * dt) + sim->dt = safety * dt; } void print_solver_stats(FILE *fp) { - fprintf(fp, - "solver_stats: timesteps=%ld poisson_iters=%ld " - "darcy_iters=%ld coupled_iters=%ld stress_iters=%ld\n", - g_stats.timesteps, g_stats.poisson_iters, g_stats.darcy_iters, - g_stats.coupled_iters, g_stats.stress_iters); + fprintf(fp, + "solver_stats: timesteps=%ld poisson_iters=%ld " + "darcy_iters=%ld coupled_iters=%ld stress_iters=%ld\n", + g_stats.timesteps, g_stats.poisson_iters, g_stats.darcy_iters, + g_stats.coupled_iters, g_stats.stress_iters); } void reset_solver_stats(void) { - g_stats.poisson_iters = 0; - g_stats.darcy_iters = 0; - g_stats.coupled_iters = 0; - g_stats.stress_iters = 0; - g_stats.timesteps = 0; + g_stats.poisson_iters = 0; + g_stats.darcy_iters = 0; + g_stats.coupled_iters = 0; + g_stats.stress_iters = 0; + g_stats.timesteps = 0; } void add_darcy_iters(int iters) { g_stats.darcy_iters += iters; } diff --git a/simulation.h b/simulation.h @@ -12,137 +12,147 @@ extern struct simulation sim; /* Simulation settings */ struct simulation { - /* simulation name to use for output files */ - char name[100]; + /* simulation name to use for output files */ + char name[100]; - /* gravitational acceleration magnitude [m/s^2] */ - double G; + /* gravitational acceleration magnitude [m/s^2] */ + double G; - /* normal stress from the top wall [Pa] */ - double P_wall; - - /* optionally fix top shear velocity to this value [m/s] */ - double v_x_fix; - - /* optionally fix top shear velocity to this value [m/s] */ - double v_x_limit; - - /* bottom velocity along x [m/s] */ - double v_x_bot; - - /* stress ratio at top wall */ - double mu_wall; - - /* nonlocal amplitude [-] */ - double A; - - /* rate dependence beyond yield [-] */ - double b; - - /* bulk and critical state static yield friction coefficient [-] */ - double mu_s; - - /* material cohesion [Pa] */ - double C; - - /* representative grain size [m] */ - double d; /* ohlala */ - - /* grain material density [kg/m^3] */ - double rho_s; - - /* nodes along z */ - int nz; - - /* origo of axis [m] */ - double origo_z; - - /* length of domain [m] */ - double L_z; - - /* array of cell coordinates */ - double *z; - - /* cell spacing [m] */ - double dz; - - /* current time [s] */ - double t; - - /* end time [s] */ - double t_end; - - /* time step length [s] */ - double dt; - - /* interval between output files [s] */ - double file_dt; - - /* output file number */ - int n_file; - - double transient; - double phi_min; - double phi_max; - double dilatancy_constant; - - /* Fluid parameters */ - int fluid; /* flag to switch fluid on (1) or off (0) */ - double p_f_top; /* fluid pressure at the top [Pa] */ - double p_f_mod_ampl; /* amplitude of fluid pressure variations [Pa] */ - double p_f_mod_freq; /* frequency of fluid pressure variations [s^-1] */ - double p_f_mod_phase; /* phase of fluid pressure variations [s^-1] */ - double p_f_mod_pulse_time; /* single pressure pulse at this time [s] */ - int p_f_mod_pulse_shape; /* waveform for fluid-pressure pulse */ - double beta_f; /* adiabatic fluid compressibility [Pa^-1] */ - double alpha; /* adiabatic grain compressibility [Pa^-1] */ - double mu_f; /* fluid dynamic viscosity [Pa*s] */ - double rho_f; /* fluid density [kg/m^3] */ - double D; /* diffusivity [m^2/s], overrides k, beta_f, alpha, mu_f */ - - /* arrays */ - double *mu; /* static yield friction [-] */ - double *mu_c; /* critical-state static yield friction [-] */ - double *sigma_n_eff; /* effective normal pressure [Pa] */ - double *sigma_n; /* normal stress [Pa] */ - double *p_f_ghost; /* fluid pressure [Pa] */ - double *p_f_next_ghost; /* fluid pressure for next iteration [Pa] */ - double *p_f_dot; /* fluid pressure change [Pa/s] */ - double *p_f_dot_expl; /* fluid pressure change (explicit solution) [Pa/s] */ - double *p_f_dot_impl; /* fluid pressure change (implicit solution) [Pa/s] */ - - double *k; /* hydraulic permeability [m^2] */ - double *phi; /* porosity [-] */ - double *phi_c; /* critical-state porosity [-] */ - double *phi_dot; /* porosity change [s^-1] */ - double *xi; /* cooperativity length */ - double *gamma_dot_p; /* plastic shear strain rate [s^-1] */ - double *v_x; /* shear velocity [m/s] */ - double *d_x; /* cumulative shear displacement [m] */ - double *g_local; /* local fluidity */ - double *g_ghost; /* fluidity with ghost nodes */ - double *g_r_norm; /* normalized residual of fluidity field */ - double *I; /* inertia number [-] */ - double *tan_psi; /* tan(dilatancy_angle) [-] */ - double *old_val; /* temporary storage for iterative solvers */ - - /* Persistent solver workspace (size nz unless noted otherwise), allocated - * once in prepare_arrays() and reused by Darcy/Poisson TDMA paths. */ - double *tdma_a; /* shared TDMA sub-diagonal coefficients */ - double *tdma_b; /* shared TDMA diagonal coefficients */ - double *tdma_c; /* shared TDMA super-diagonal coefficients */ - double *tdma_d; /* shared TDMA RHS coefficients */ - double *tdma_x; /* shared TDMA solution vector */ - double *tdma_c_prime; /* shared TDMA forward-sweep scratch */ - double *tdma_d_prime; /* shared TDMA forward-sweep scratch */ - double *darcy_k_n; /* Darcy predictor permeability workspace */ - double *darcy_phi_n; /* Darcy predictor porosity workspace */ + /* normal stress from the top wall [Pa] */ + double P_wall; + + /* optionally fix top shear velocity to this value [m/s] */ + double v_x_fix; + + /* optionally fix top shear velocity to this value [m/s] */ + double v_x_limit; + + /* bottom velocity along x [m/s] */ + double v_x_bot; + + /* stress ratio at top wall */ + double mu_wall; + + /* nonlocal amplitude [-] */ + double A; + + /* rate dependence beyond yield [-] */ + double b; + + /* bulk and critical state static yield friction coefficient [-] */ + double mu_s; + + /* material cohesion [Pa] */ + double C; + + /* representative grain size [m] */ + double d; /* ohlala */ + + /* grain material density [kg/m^3] */ + double rho_s; + + /* nodes along z */ + int nz; + + /* origo of axis [m] */ + double origo_z; + + /* length of domain [m] */ + double L_z; + + /* array of cell coordinates */ + double *z; + + /* cell spacing [m] */ + double dz; + + /* current time [s] */ + double t; + + /* end time [s] */ + double t_end; + + /* time step length [s] */ + double dt; + + /* interval between output files [s] */ + double file_dt; + + /* output file number */ + int n_file; + + double transient; + double phi_min; + double phi_max; + double dilatancy_constant; + + /* Fluid parameters */ + int fluid; /* flag to switch fluid on (1) or off (0) */ + double p_f_top; /* fluid pressure at the top [Pa] */ + double p_f_mod_ampl; /* amplitude of fluid pressure variations [Pa] */ + double p_f_mod_freq; /* frequency of fluid pressure variations [s^-1] */ + double p_f_mod_phase; /* phase of fluid pressure variations [s^-1] */ + double p_f_mod_pulse_time; /* single pressure pulse at this time [s] */ + int p_f_mod_pulse_shape; /* waveform for fluid-pressure pulse */ + double beta_f; /* adiabatic fluid compressibility [Pa^-1] */ + double alpha; /* adiabatic grain compressibility [Pa^-1] */ + double mu_f; /* fluid dynamic viscosity [Pa*s] */ + double rho_f; /* fluid density [kg/m^3] */ + double D; /* diffusivity [m^2/s], overrides k, beta_f, alpha, mu_f */ + + /* arrays */ + double *mu; /* static yield friction [-] */ + double *mu_c; /* critical-state static yield friction [-] */ + double *sigma_n_eff; /* effective normal pressure [Pa] */ + double *sigma_n; /* normal stress [Pa] */ + double *p_f_ghost; /* fluid pressure [Pa] */ + double *p_f_next_ghost; /* fluid pressure for next iteration [Pa] */ + double *p_f_dot; /* fluid pressure change [Pa/s] */ + double *p_f_dot_expl; /* fluid pressure change (explicit solution) [Pa/s] */ + double *p_f_dot_impl; /* fluid pressure change (implicit solution) [Pa/s] */ + + double *k; /* hydraulic permeability [m^2] */ + double *phi; /* porosity [-] */ + double *phi_c; /* critical-state porosity [-] */ + double *phi_dot; /* porosity change [s^-1] */ + double *xi; /* cooperativity length */ + double *gamma_dot_p; /* plastic shear strain rate [s^-1] */ + double *v_x; /* shear velocity [m/s] */ + double *d_x; /* cumulative shear displacement [m] */ + double *g_local; /* local fluidity */ + double *g_ghost; /* fluidity with ghost nodes */ + double *g_r_norm; /* normalized residual of fluidity field */ + double *I; /* inertia number [-] */ + double *tan_psi; /* tan(dilatancy_angle) [-] */ + double *old_val; /* temporary storage for iterative solvers */ + + /* Persistent solver workspace (size nz unless noted otherwise), allocated + * once in prepare_arrays() and reused by Darcy/Poisson TDMA paths. */ + double *tdma_a; /* shared TDMA sub-diagonal coefficients */ + double *tdma_b; /* shared TDMA diagonal coefficients */ + double *tdma_c; /* shared TDMA super-diagonal coefficients */ + double *tdma_d; /* shared TDMA RHS coefficients */ + double *tdma_x; /* shared TDMA solution vector */ + double *tdma_c_prime; /* shared TDMA forward-sweep scratch */ + double *tdma_d_prime; /* shared TDMA forward-sweep scratch */ + double *darcy_k_n; /* Darcy predictor permeability workspace */ + double *darcy_phi_n; /* Darcy predictor porosity workspace */ }; void init_sim(struct simulation *sim); -void prepare_arrays(struct simulation *sim); + +/* returns: 0 ok, 1 grid size (nz) is less than 2 */ +int prepare_arrays(struct simulation *sim); + void free_arrays(struct simulation *sim); -void check_simulation_parameters(struct simulation *sim); + +/* Reset per-column state (stresses, velocities, fluidity, porosity, time) + * without reallocating; geometry (nz, L_z, z, dz) and workspaces are kept. + * For reuse of one prepared simulation across many independent columns. */ +void reset_column(struct simulation *sim); + +/* returns: 0 ok, 1 one or more parameters are invalid */ +int check_simulation_parameters(struct simulation *sim); void lithostatic_pressure_distribution(struct simulation *sim); void compute_effective_stress(struct simulation *sim); @@ -158,6 +168,9 @@ double kozeny_carman(const double diameter, const double porosity); void write_output_file(struct simulation *sim, const int normalize); void print_output(struct simulation *sim, FILE *fp, const int normalize); +/* returns: 0 ok, 1 transient solution not converged, 10 stress solution not + * converged, 11 fluid (Darcy) solver failed, 12 fluidity (Poisson) solver + * failed, 13 NaN in temporal increment */ int coupled_shear_solver(struct simulation *sim, const int max_iter, const double rel_tol);