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

simulation.c (30862B)


      1 #include "simulation.h"
      2 #include "arrays.h"
      3 #include "fluid.h"
      4 #include <err.h>
      5 #include <math.h>
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 
     10 /* iteration limits for solvers */
     11 #define MAX_ITER_GRANULAR 100000
     12 #define MAX_ITER_DARCY 1000000
     13 
     14 /* tolerance criteria when in velocity driven or velocity limited mode */
     15 #define RTOL_VELOCITY 1e-3
     16 
     17 /* solver statistics for benchmarking */
     18 struct solver_stats {
     19 	long poisson_iters;
     20 	long darcy_iters;
     21 	long coupled_iters;
     22 	long stress_iters;
     23 	long timesteps;
     24 };
     25 
     26 /* per-process counters; not thread-safe if columns run on multiple threads */
     27 static struct solver_stats g_stats = {0, 0, 0, 0, 0};
     28 
     29 /* lower limit for effective normal stress sigma_n_eff for granular solver */
     30 #define SIGMA_N_EFF_MIN 1e-1
     31 
     32 /* defaults shared by init_sim() and reset_column() */
     33 #define DEFAULT_MU_WALL 0.45
     34 #define DEFAULT_PHI 0.25       /* Damsgaard et al 2013 */
     35 #define DEFAULT_K 1.9e-15      /* Damsgaard et al 2015 */
     36 
     37 /* Simulation settings */
     38 void init_sim(struct simulation *sim) {
     39 	int ret;
     40 
     41 	ret = snprintf(sim->name, sizeof(sim->name), DEFAULT_SIMULATION_NAME);
     42 	if (ret < 0 || (size_t)ret == sizeof(sim->name))
     43 		err(1, "%s: could not write simulation name", __func__);
     44 
     45 	sim->G = 9.81;
     46 	sim->P_wall = 120e3;
     47 	sim->mu_wall = DEFAULT_MU_WALL;
     48 	sim->v_x_bot = 0.0;
     49 	sim->v_x_fix = NAN;
     50 	sim->v_x_limit = NAN;
     51 	sim->nz = -1; /* cell size equals grain size if negative */
     52 
     53 	sim->A = 0.40;                  /* Loose fit to Damsgaard et al 2013 */
     54 	sim->b = 0.9377;                /* Henann and Kamrin 2016 */
     55 	/* sim->mu_s = 0.3819; */       /* Henann and Kamrin 2016 */
     56 	/* sim->C = 0.0;       */       /* Henann and Kamrin 2016 */
     57 	sim->mu_s = tan(DEG2RAD(22.0)); /* Damsgaard et al 2013 */
     58 	sim->C = 0.0;                   /* Damsgaard et al 2013 */
     59 	sim->phi = initval(DEFAULT_PHI, 1);
     60 	sim->d = 0.04; /* Damsgaard et al 2013 */
     61 	sim->transient = 0;
     62 	sim->phi_min = 0.20;
     63 	sim->phi_max = 0.55;
     64 	sim->dilatancy_constant = 4.09; /* Pailha & Pouliquen 2009 */
     65 
     66 	/* Iverson et al 1997, 1998: Storglaciaren till */
     67 	/* sim->mu_s = tan(DEG2RAD(26.3)); */
     68 	/* sim->C = 5.0e3; */
     69 	/* sim->phi = initval(0.22, 1); */
     70 	/* sim->d = ??; */
     71 
     72 	/* Iverson et al 1997, 1998: Two Rivers till */
     73 	/* sim->mu_s = tan(DEG2RAD(17.8)); */
     74 	/* sim->C = 14.0e3; */
     75 	/* sim->phi = initval(0.37, 1); */
     76 	/* sim->d = ??; */
     77 
     78 	/* Tulaczyk et al 2000a: Upstream B till */
     79 	/* sim->mu_s = tan(DEG2RAD(23.9)); */
     80 	/* sim->C = 3.0e3; */
     81 	/* sim->phi = initval(0.35, 1); */
     82 	/* sim->d = ??; */
     83 
     84 	sim->rho_s = 2.6e3; /* Damsgaard et al 2013 */
     85 	sim->origo_z = 0.0;
     86 	sim->L_z = 1.0;
     87 	sim->t = 0.0;
     88 	sim->dt = 1.0;
     89 	sim->t_end = 1.0;
     90 	sim->file_dt = 1.0;
     91 	sim->n_file = 0;
     92 	sim->fluid = 0;
     93 	sim->rho_f = 1e3;
     94 
     95 	/* Water at 20 deg C */
     96 	/* sim->beta_f = 4.5e-10; */ /* Goren et al 2011 */
     97 	/* sim->mu_f = 1.0-3; */     /* Goren et al 2011 */
     98 
     99 	/* Water at 0 deg C */
    100 	sim->beta_f = 3.9e-10; /* doi:10.1063/1.1679903 */
    101 	sim->mu_f = 1.787e-3;  /* Cuffey and Paterson 2010 */
    102 
    103 	sim->alpha = 1e-8;
    104 	sim->D = -1.0; /* disabled when negative */
    105 
    106 	sim->k = initval(DEFAULT_K, 1);
    107 
    108 	/* Iverson et al 1994: Storglaciaren */
    109 	/* sim->k = initval(1.3e-14, 1); */
    110 
    111 	/* Engelhardt et al 1990: Upstream B */
    112 	/* sim->k = initval(2.0e-16, 1); */
    113 
    114 	/* Leeman et al 2016: Upstream B till */
    115 	/* sim->k = initval(4.9e-17, 1); */
    116 
    117 	/* no fluid-pressure variations */
    118 	sim->p_f_top = 0.0;
    119 	sim->p_f_mod_ampl = 0.0;
    120 	sim->p_f_mod_freq = 1.0;
    121 	sim->p_f_mod_phase = 0.0;
    122 	sim->p_f_mod_pulse_time = NAN;
    123 	sim->p_f_mod_pulse_shape = 0;
    124 }
    125 
    126 int prepare_arrays(struct simulation *sim) {
    127 	if (sim->nz < 2) {
    128 		fprintf(stderr, "error: grid size (nz) must be at least 2 but is %d\n",
    129 		        sim->nz);
    130 		return 1;
    131 	}
    132 	free(sim->phi);
    133 	free(sim->k);
    134 
    135 	sim->z = linspace(sim->origo_z, sim->origo_z + sim->L_z, sim->nz);
    136 	sim->dz = sim->z[1] - sim->z[0];
    137 	sim->mu = zeros(sim->nz);
    138 	sim->mu_c = zeros(sim->nz);
    139 	sim->sigma_n_eff = zeros(sim->nz);
    140 	sim->sigma_n = zeros(sim->nz);
    141 	sim->p_f_ghost = zeros(sim->nz + 2);
    142 	sim->p_f_next_ghost = zeros(sim->nz + 2);
    143 	sim->p_f_dot = zeros(sim->nz);
    144 	sim->p_f_dot_impl = zeros(sim->nz);
    145 	sim->phi = zeros(sim->nz);
    146 	sim->phi_c = zeros(sim->nz);
    147 	sim->phi_dot = zeros(sim->nz);
    148 	sim->k = zeros(sim->nz);
    149 	sim->xi = zeros(sim->nz);
    150 	sim->gamma_dot_p = zeros(sim->nz);
    151 	sim->v_x = zeros(sim->nz);
    152 	sim->d_x = zeros(sim->nz);
    153 	sim->g_local = zeros(sim->nz);
    154 	sim->g_ghost = zeros(sim->nz + 2);
    155 	sim->g_r_norm = zeros(sim->nz);
    156 	sim->I = zeros(sim->nz);
    157 	sim->tan_psi = zeros(sim->nz);
    158 	sim->old_val = empty(sim->nz);
    159 	sim->tdma_a = empty(sim->nz);
    160 	sim->tdma_b = empty(sim->nz);
    161 	sim->tdma_c = empty(sim->nz);
    162 	sim->tdma_d = empty(sim->nz);
    163 	sim->tdma_x = empty(sim->nz);
    164 	sim->tdma_c_prime = empty(sim->nz);
    165 	sim->tdma_d_prime = empty(sim->nz);
    166 	sim->darcy_k_n = empty(sim->nz);
    167 	sim->darcy_phi_n = empty(sim->nz);
    168 
    169 	return 0;
    170 }
    171 
    172 void free_arrays(struct simulation *sim) {
    173 	free(sim->z);
    174 	free(sim->mu);
    175 	free(sim->mu_c);
    176 	free(sim->sigma_n_eff);
    177 	free(sim->sigma_n);
    178 	free(sim->p_f_ghost);
    179 	free(sim->p_f_next_ghost);
    180 	free(sim->p_f_dot);
    181 	free(sim->p_f_dot_impl);
    182 	free(sim->k);
    183 	free(sim->phi);
    184 	free(sim->phi_c);
    185 	free(sim->phi_dot);
    186 	free(sim->xi);
    187 	free(sim->gamma_dot_p);
    188 	free(sim->v_x);
    189 	free(sim->d_x);
    190 	free(sim->g_local);
    191 	free(sim->g_ghost);
    192 	free(sim->g_r_norm);
    193 	free(sim->I);
    194 	free(sim->tan_psi);
    195 	free(sim->old_val);
    196 	free(sim->tdma_a);
    197 	free(sim->tdma_b);
    198 	free(sim->tdma_c);
    199 	free(sim->tdma_d);
    200 	free(sim->tdma_x);
    201 	free(sim->tdma_c_prime);
    202 	free(sim->tdma_d_prime);
    203 	free(sim->darcy_k_n);
    204 	free(sim->darcy_phi_n);
    205 }
    206 
    207 void reset_column(struct simulation *sim) {
    208 	int i;
    209 	const int nz = sim->nz;
    210 	const size_t nz_bytes = (size_t)nz * sizeof(double);
    211 	const size_t ghost_bytes = (size_t)(nz + 2) * sizeof(double);
    212 
    213 	/* zero accumulated state fields (size nz) */
    214 	memset(sim->mu, 0, nz_bytes);
    215 	memset(sim->mu_c, 0, nz_bytes);
    216 	memset(sim->sigma_n_eff, 0, nz_bytes);
    217 	memset(sim->sigma_n, 0, nz_bytes);
    218 	memset(sim->p_f_dot, 0, nz_bytes);
    219 	memset(sim->p_f_dot_impl, 0, nz_bytes);
    220 	memset(sim->phi_c, 0, nz_bytes);
    221 	memset(sim->phi_dot, 0, nz_bytes);
    222 	memset(sim->xi, 0, nz_bytes);
    223 	memset(sim->gamma_dot_p, 0, nz_bytes);
    224 	memset(sim->v_x, 0, nz_bytes);
    225 	memset(sim->d_x, 0, nz_bytes);
    226 	memset(sim->g_local, 0, nz_bytes);
    227 	memset(sim->g_r_norm, 0, nz_bytes);
    228 	memset(sim->I, 0, nz_bytes);
    229 	memset(sim->tan_psi, 0, nz_bytes);
    230 
    231 	/* zero ghost fields (size nz + 2) */
    232 	memset(sim->p_f_ghost, 0, ghost_bytes);
    233 	memset(sim->p_f_next_ghost, 0, ghost_bytes);
    234 	memset(sim->g_ghost, 0, ghost_bytes);
    235 
    236 	/* restore init_sim() defaults for porosity and permeability */
    237 	for (i = 0; i < nz; ++i) {
    238 		sim->phi[i] = DEFAULT_PHI;
    239 		sim->k[i] = DEFAULT_K;
    240 	}
    241 
    242 	/* reset per-column scalars to init_sim() defaults */
    243 	sim->t = 0.0;
    244 	sim->mu_wall = DEFAULT_MU_WALL;
    245 	sim->v_x_bot = 0.0;
    246 	sim->n_file = 0;
    247 }
    248 
    249 static void warn_parameter_value(const char message[], const double value,
    250                                  int *return_status) {
    251 	fprintf(stderr, "check_simulation_parameters: %s (%.17g)\n", message, value);
    252 	*return_status = 1;
    253 }
    254 
    255 static void check_float(const char name[], const double value,
    256                         int *return_status) {
    257 	int ret;
    258 	char message[100];
    259 
    260 #ifdef SHOW_PARAMETERS
    261 	printf("%30s: %.17g\n", name, value);
    262 #endif
    263 	if (isnan(value)) {
    264 		ret = snprintf(message, sizeof(message), "%s is NaN", name);
    265 		if (ret < 0 || (size_t)ret >= sizeof(message))
    266 			err(1, "%s: message parsing", __func__);
    267 		warn_parameter_value(message, value, return_status);
    268 	} else if (isinf(value)) {
    269 		ret = snprintf(message, sizeof(message), "%s is infinite", name);
    270 		if (ret < 0 || (size_t)ret >= sizeof(message))
    271 			err(1, "%s: message parsing", __func__);
    272 		warn_parameter_value(message, value, return_status);
    273 	}
    274 }
    275 
    276 int check_simulation_parameters(struct simulation *sim) {
    277 	int return_status = 0;
    278 
    279 	check_float("sim->G", sim->G, &return_status);
    280 	if (sim->G < 0.0)
    281 		warn_parameter_value("sim->G is negative", sim->G, &return_status);
    282 
    283 	check_float("sim->P_wall", sim->P_wall, &return_status);
    284 	if (sim->P_wall < 0.0)
    285 		warn_parameter_value("sim->P_wall is negative", sim->P_wall,
    286 		                     &return_status);
    287 
    288 	check_float("sim->v_x_bot", sim->v_x_bot, &return_status);
    289 
    290 	check_float("sim->mu_wall", sim->mu_wall, &return_status);
    291 	if (sim->mu_wall < 0.0)
    292 		warn_parameter_value("sim->mu_wall is negative", sim->mu_wall,
    293 		                     &return_status);
    294 
    295 	check_float("sim->A", sim->A, &return_status);
    296 	if (sim->A < 0.0)
    297 		warn_parameter_value("sim->A is negative", sim->A, &return_status);
    298 
    299 	check_float("sim->b", sim->b, &return_status);
    300 	if (sim->b < 0.0)
    301 		warn_parameter_value("sim->b is negative", sim->b, &return_status);
    302 
    303 	check_float("sim->mu_s", sim->mu_s, &return_status);
    304 	if (sim->mu_s < 0.0)
    305 		warn_parameter_value("sim->mu_s is negative", sim->mu_s, &return_status);
    306 
    307 	check_float("sim->C", sim->C, &return_status);
    308 
    309 	check_float("sim->d", sim->d, &return_status);
    310 	if (sim->d <= 0.0)
    311 		warn_parameter_value("sim->d is not a positive number", sim->d,
    312 		                     &return_status);
    313 
    314 	check_float("sim->rho_s", sim->rho_s, &return_status);
    315 	if (sim->rho_s <= 0.0)
    316 		warn_parameter_value("sim->rho_s is not a positive number", sim->rho_s,
    317 		                     &return_status);
    318 
    319 	if (sim->nz <= 0)
    320 		warn_parameter_value("sim->nz is not a positive number", sim->nz,
    321 		                     &return_status);
    322 
    323 	check_float("sim->origo_z", sim->origo_z, &return_status);
    324 	check_float("sim->L_z", sim->L_z, &return_status);
    325 	if (sim->L_z <= sim->origo_z)
    326 		warn_parameter_value("sim->L_z is smaller or equal to sim->origo_z",
    327 		                     sim->L_z, &return_status);
    328 
    329 	if (sim->nz <= 0)
    330 		warn_parameter_value("sim->nz is not a positive number", sim->nz,
    331 		                     &return_status);
    332 
    333 	check_float("sim->dz", sim->dz, &return_status);
    334 	if (sim->dz <= 0.0)
    335 		warn_parameter_value("sim->dz is not a positive number", sim->dz,
    336 		                     &return_status);
    337 
    338 	check_float("sim->t", sim->t, &return_status);
    339 	if (sim->t < 0.0)
    340 		warn_parameter_value("sim->t is a negative number", sim->t, &return_status);
    341 
    342 	check_float("sim->t_end", sim->t_end, &return_status);
    343 	if (sim->t > sim->t_end)
    344 		warn_parameter_value("sim->t_end is smaller than sim->t", sim->t,
    345 		                     &return_status);
    346 
    347 	check_float("sim->dt", sim->dt, &return_status);
    348 	if (sim->dt < 0.0)
    349 		warn_parameter_value("sim->dt is less than zero", sim->dt, &return_status);
    350 
    351 	check_float("sim->file_dt", sim->file_dt, &return_status);
    352 	if (sim->file_dt < 0.0)
    353 		warn_parameter_value("sim->file_dt is a negative number", sim->file_dt,
    354 		                     &return_status);
    355 
    356 	check_float("sim->phi[0]", sim->phi[0], &return_status);
    357 	if (sim->phi[0] < 0.0 || sim->phi[0] > 1.0)
    358 		warn_parameter_value("sim->phi[0] is not within [0;1]", sim->phi[0],
    359 		                     &return_status);
    360 
    361 	check_float("sim->phi_min", sim->phi_min, &return_status);
    362 	if (sim->phi_min < 0.0 || sim->phi_min > 1.0)
    363 		warn_parameter_value("sim->phi_min is not within [0;1]", sim->phi_min,
    364 		                     &return_status);
    365 
    366 	check_float("sim->phi_max", sim->phi_max, &return_status);
    367 	if (sim->phi_max < 0.0 || sim->phi_max > 1.0)
    368 		warn_parameter_value("sim->phi_max is not within [0;1]", sim->phi_max,
    369 		                     &return_status);
    370 
    371 	check_float("sim->dilatancy_constant", sim->dilatancy_constant,
    372 	            &return_status);
    373 	if (sim->dilatancy_constant < 0.0 || sim->dilatancy_constant > 100.0)
    374 		warn_parameter_value("sim->dilatancy_constant is not within [0;100]",
    375 		                     sim->dilatancy_constant, &return_status);
    376 
    377 	if (sim->fluid != 0 && sim->fluid != 1)
    378 		warn_parameter_value("sim->fluid has an invalid value", (double)sim->fluid,
    379 		                     &return_status);
    380 
    381 	if (sim->transient != 0 && sim->transient != 1)
    382 		warn_parameter_value("sim->transient has an invalid value",
    383 		                     (double)sim->transient, &return_status);
    384 
    385 	if (sim->fluid) {
    386 		check_float("sim->p_f_mod_ampl", sim->p_f_mod_ampl, &return_status);
    387 		if (sim->p_f_mod_ampl < 0.0)
    388 			warn_parameter_value("sim->p_f_mod_ampl is not a zero or positive",
    389 			                     sim->p_f_mod_ampl, &return_status);
    390 
    391 		check_float("sim->p_f_mod_freq", sim->p_f_mod_freq, &return_status);
    392 		if (sim->p_f_mod_freq < 0.0)
    393 			warn_parameter_value("sim->p_f_mod_freq is not a zero or positive",
    394 			                     sim->p_f_mod_freq, &return_status);
    395 
    396 		check_float("sim->beta_f", sim->beta_f, &return_status);
    397 		if (sim->beta_f <= 0.0)
    398 			warn_parameter_value("sim->beta_f is not positive", sim->beta_f,
    399 			                     &return_status);
    400 
    401 		check_float("sim->alpha", sim->alpha, &return_status);
    402 		if (sim->alpha <= 0.0)
    403 			warn_parameter_value("sim->alpha is not positive", sim->alpha,
    404 			                     &return_status);
    405 
    406 		check_float("sim->mu_f", sim->mu_f, &return_status);
    407 		if (sim->mu_f <= 0.0)
    408 			warn_parameter_value("sim->mu_f is not positive", sim->mu_f,
    409 			                     &return_status);
    410 
    411 		check_float("sim->rho_f", sim->rho_f, &return_status);
    412 		if (sim->rho_f <= 0.0)
    413 			warn_parameter_value("sim->rho_f is not positive", sim->rho_f,
    414 			                     &return_status);
    415 
    416 		check_float("sim->k[0]", sim->k[0], &return_status);
    417 		if (sim->k[0] <= 0.0)
    418 			warn_parameter_value("sim->k[0] is not positive", sim->k[0],
    419 			                     &return_status);
    420 
    421 		check_float("sim->D", sim->D, &return_status);
    422 		if (sim->transient && sim->D > 0.0)
    423 			warn_parameter_value("constant diffusivity does not work in "
    424 			                     "transient simulations",
    425 			                     sim->D, &return_status);
    426 	}
    427 
    428 	if (return_status != 0)
    429 		fprintf(stderr, "error: aborting due to invalid parameter choices\n");
    430 
    431 	return return_status;
    432 }
    433 
    434 void lithostatic_pressure_distribution(struct simulation *sim) {
    435 	int i;
    436 
    437 	for (i = 0; i < sim->nz; ++i)
    438 		sim->sigma_n[i] = sim->P_wall + (1.0 - sim->phi[i]) * sim->rho_s * sim->G *
    439 		                                    (sim->origo_z + sim->L_z - sim->z[i]);
    440 }
    441 
    442 inline static double inertia_number(double gamma_dot_p, double d,
    443                                     double sigma_n_eff, double rho_s) {
    444 	return fabs(gamma_dot_p) * d / sqrt(sigma_n_eff / rho_s);
    445 }
    446 
    447 void compute_inertia_number(struct simulation *sim) {
    448 	int i;
    449 
    450 	for (i = 0; i < sim->nz; ++i)
    451 		sim->I[i] =
    452 		    inertia_number(sim->gamma_dot_p[i], sim->d,
    453 		                   fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->rho_s);
    454 }
    455 
    456 void compute_critical_state_porosity(struct simulation *sim) {
    457 	int i;
    458 
    459 	for (i = 0; i < sim->nz; ++i)
    460 		sim->phi_c[i] = sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i];
    461 }
    462 
    463 void compute_critical_state_friction(struct simulation *sim) {
    464 	int i;
    465 
    466 	if (sim->fluid)
    467 		for (i = 0; i < sim->nz; ++i)
    468 			sim->mu_c[i] =
    469 			    sim->mu_wall / (fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN) /
    470 			                    (sim->P_wall - sim->p_f_top));
    471 	else
    472 		for (i = 0; i < sim->nz; ++i)
    473 			sim->mu_c[i] =
    474 			    sim->mu_wall /
    475 			    (1.0 + (1.0 - sim->phi[i]) * sim->rho_s * sim->G *
    476 			               (sim->origo_z + sim->L_z - sim->z[i]) / sim->P_wall);
    477 }
    478 
    479 static void compute_friction(struct simulation *sim) {
    480 	int i;
    481 
    482 	if (sim->transient)
    483 		for (i = 0; i < sim->nz; ++i)
    484 			sim->mu[i] = sim->mu_c[i] + sim->tan_psi[i];
    485 	else
    486 		for (i = 0; i < sim->nz; ++i)
    487 			sim->mu[i] = sim->mu_c[i];
    488 }
    489 
    490 static void compute_tan_dilatancy_angle(struct simulation *sim) {
    491 	int i;
    492 
    493 	for (i = 0; i < sim->nz; ++i)
    494 		sim->tan_psi[i] = sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]);
    495 }
    496 
    497 static void compute_transient_fields(struct simulation *sim) {
    498 	int i;
    499 
    500 	/* Fused loop: compute I, phi_c, and tan_psi in single pass */
    501 	for (i = 0; i < sim->nz; ++i) {
    502 		/* Eq. 1: Inertia number */
    503 		sim->I[i] =
    504 		    inertia_number(sim->gamma_dot_p[i], sim->d,
    505 		                   fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->rho_s);
    506 
    507 		/* Eq. 2: Critical state porosity */
    508 		sim->phi_c[i] = sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i];
    509 
    510 		/* Eq. 5: Dilatancy angle */
    511 		sim->tan_psi[i] = sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]);
    512 	}
    513 }
    514 
    515 static void compute_porosity_change(struct simulation *sim) {
    516 	int i;
    517 
    518 	for (i = 0; i < sim->nz; ++i)
    519 		sim->phi_dot[i] = sim->tan_psi[i] * sim->gamma_dot_p[i] * sim->phi[i];
    520 }
    521 
    522 double kozeny_carman(const double diameter, const double porosity) {
    523 	return (diameter * diameter) / 180.0 * (porosity * porosity * porosity) /
    524 	       ((1.0 - porosity) * (1.0 - porosity));
    525 }
    526 
    527 static void compute_permeability(struct simulation *sim) {
    528 	int i;
    529 
    530 	for (i = 0; i < sim->nz; ++i)
    531 		sim->k[i] = kozeny_carman(sim->d, sim->phi[i]);
    532 }
    533 
    534 static double shear_strain_rate_plastic(const double fluidity,
    535                                         const double friction) {
    536 	return fluidity * friction;
    537 }
    538 
    539 static void compute_shear_strain_rate_plastic(struct simulation *sim) {
    540 	int i;
    541 
    542 	for (i = 0; i < sim->nz; ++i)
    543 		sim->gamma_dot_p[i] =
    544 		    shear_strain_rate_plastic(sim->g_ghost[i + 1], sim->mu[i]);
    545 }
    546 
    547 static void compute_shear_velocity(struct simulation *sim) {
    548 	int i;
    549 
    550 	/* TODO: implement iterative solver for v_x from gamma_dot_p field */
    551 	/* Dirichlet BC at bottom */
    552 	sim->v_x[0] = sim->v_x_bot;
    553 
    554 	for (i = 1; i < sim->nz; ++i)
    555 		sim->v_x[i] = sim->v_x[i - 1] + sim->gamma_dot_p[i] * sim->dz;
    556 }
    557 
    558 void compute_effective_stress(struct simulation *sim) {
    559 	int i;
    560 
    561 	if (sim->fluid)
    562 		for (i = 0; i < sim->nz; ++i) {
    563 			/* use implicit (next-step) pressure for tighter coupling */
    564 			sim->sigma_n_eff[i] = sim->sigma_n[i] - sim->p_f_next_ghost[i + 1];
    565 			if (sim->sigma_n_eff[i] < 0)
    566 				warnx("%s: sigma_n_eff[%d] is negative with value %g", __func__, i,
    567 				      sim->sigma_n_eff[i]);
    568 		}
    569 	else
    570 		for (i = 0; i < sim->nz; ++i)
    571 			sim->sigma_n_eff[i] = sim->sigma_n[i];
    572 }
    573 
    574 static double cooperativity_length(const double A, const double d,
    575                                    const double mu, const double p,
    576                                    const double mu_s, const double C) {
    577 	double denom = fmax(fabs((mu - C / p) - mu_s), 1e-10);
    578 	return A * d / sqrt(denom);
    579 }
    580 
    581 static void compute_cooperativity_length(struct simulation *sim) {
    582 	int i;
    583 
    584 	for (i = 0; i < sim->nz; ++i)
    585 		sim->xi[i] = cooperativity_length(
    586 		    sim->A, sim->d, sim->mu[i], fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN),
    587 		    sim->mu_s, sim->C);
    588 }
    589 
    590 static double local_fluidity(const double p, const double mu, const double mu_s,
    591                              const double C, const double b, const double rho_s,
    592                              const double d) {
    593 	if (mu - C / p <= mu_s)
    594 		return 0.0;
    595 	else
    596 		return sqrt(p / (rho_s * d * d)) * ((mu - C / p) - mu_s) / (b * mu);
    597 }
    598 
    599 static void compute_local_fluidity(struct simulation *sim) {
    600 	int i;
    601 
    602 	for (i = 0; i < sim->nz; ++i)
    603 		sim->g_local[i] =
    604 		    local_fluidity(fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN), sim->mu[i],
    605 		                   sim->mu_s, sim->C, sim->b, sim->rho_s, sim->d);
    606 }
    607 
    608 void set_bc_neumann(double *a, const int nz, const int boundary,
    609                     const double df, const double dx) {
    610 	if (boundary == -1)
    611 		a[0] = a[1] + df * dx;
    612 	else if (boundary == +1)
    613 		a[nz + 1] = a[nz] - df * dx;
    614 	else
    615 		errx(1, "%s: Unknown boundary %d\n", __func__, boundary);
    616 }
    617 
    618 void set_bc_dirichlet(double *a, const int nz, const int boundary,
    619                       const double value) {
    620 	if (boundary == -1)
    621 		a[0] = value;
    622 	else if (boundary == +1)
    623 		a[nz + 1] = value;
    624 	else
    625 		errx(1, "%s: Unknown boundary %d\n", __func__, boundary);
    626 }
    627 
    628 double residual(double new_val, double old_val) {
    629 	return (new_val - old_val) / (fmax(fabs(old_val), fabs(new_val)) + 1e-16);
    630 }
    631 
    632 void tridiagonal_solver(double *x, const double *a, const double *b,
    633                         const double *c, const double *d, double *c_prime,
    634                         double *d_prime, int n) {
    635 	/*
    636 	 * TDMA (Thomas Algorithm) solver for tridiagonal system
    637 	 * a: sub-diagonal (means a[i] is coeff for x[i-1])
    638 	 * b: main diagonal (means b[i] is coeff for x[i])
    639 	 * c: super-diagonal (means c[i] is coeff for x[i+1])
    640 	 * d: right hand side
    641 	 * x: solution vector
    642 	 * n: system size
    643 	 *
    644 	 * Note: This implementation assumes 0-indexed arrays, where:
    645 	 * Eq i (0 <= i < n): a[i]*x[i-1] + b[i]*x[i] + c[i]*x[i+1] = d[i]
    646 	 * Boundary conditions: a[0] = 0 and c[n-1] = 0 (assumed to be handled by
    647 	 * caller or zeroed)
    648 	 */
    649 	int i;
    650 
    651 	/* Forward sweep */
    652 	c_prime[0] = c[0] / b[0];
    653 	d_prime[0] = d[0] / b[0];
    654 
    655 	for (i = 1; i < n; i++) {
    656 		double temp = 1.0 / (b[i] - a[i] * c_prime[i - 1]);
    657 		c_prime[i] = c[i] * temp;
    658 		d_prime[i] = (d[i] - a[i] * d_prime[i - 1]) * temp;
    659 	}
    660 
    661 	/* Back substitution */
    662 	x[n - 1] = d_prime[n - 1];
    663 	for (i = n - 2; i >= 0; i--) {
    664 		x[i] = d_prime[i] - c_prime[i] * x[i + 1];
    665 	}
    666 }
    667 
    668 static int implicit_1d_sor_poisson_solver(struct simulation *sim) {
    669 	/*
    670 	 * Replaced SOR solver with direct TDMA solver.
    671 	 * System: -0.5*g[i-1] + (1 + C)*g[i] - 0.5*g[i+1] = C*g_local[i]
    672 	 * where C = dz^2 / (2 * xi^2)
    673 	 */
    674 	int i;
    675 	int n = sim->nz;
    676 	double *a = sim->tdma_a;
    677 	double *b = sim->tdma_b;
    678 	double *c = sim->tdma_c;
    679 	double *d = sim->tdma_d;
    680 	double *x = sim->tdma_x;
    681 
    682 	/* Set up TDMA arrays */
    683 	for (i = 0; i < n; i++) {
    684 		double coorp_term = sim->dz * sim->dz / (2.0 * sim->xi[i] * sim->xi[i]);
    685 
    686 		a[i] = -0.5;
    687 		b[i] = 1.0 + coorp_term;
    688 		c[i] = -0.5;
    689 		d[i] = coorp_term * sim->g_local[i];
    690 	}
    691 
    692 	/* Boundary conditions adjustment */
    693 	/* g[-1] = 0 (sim->g_ghost[0]) -> a[0]*0 term vanishes */
    694 	/* g[n] = 0 (sim->g_ghost[n+1]) -> c[n-1]*0 term vanishes */
    695 	/* But TDMA solver assumes a[0] and c[n-1] are coefficients in the matrix,
    696 	   which should be 0 for the first and last row if we strictly follow standard
    697 	   TDMA for isolated systems. However, our equation for i=0 is: -0.5*g{-1} +
    698 	   b[0]*g{0} + c[0]*g{1} = d[0] Since g{-1}=0, the term -0.5*g{-1} is 0. So
    699 	   effectively a[0]=0 in the matrix sense. Same for i=n-1: c[n-1]*g{n} is 0.
    700 	*/
    701 	a[0] = 0.0;
    702 	c[n - 1] = 0.0;
    703 
    704 	tridiagonal_solver(x, a, b, c, d, sim->tdma_c_prime, sim->tdma_d_prime, n);
    705 
    706 	/* Copy result back to ghost array (indices 1 to n) */
    707 	set_bc_dirichlet(sim->g_ghost, sim->nz, -1, 0.0);
    708 	set_bc_dirichlet(sim->g_ghost, sim->nz, +1, 0.0);
    709 	for (i = 0; i < n; i++) {
    710 		sim->g_ghost[i + 1] = x[i];
    711 	}
    712 
    713 	g_stats.poisson_iters += 1; /* Count as 1 iteration for stats */
    714 	return 0;
    715 }
    716 
    717 void write_output_file(struct simulation *sim, const int normalize) {
    718 	int ret;
    719 	char outfile[200];
    720 	FILE *fp;
    721 
    722 	ret = snprintf(outfile, sizeof(outfile), "%s.output%05d.txt", sim->name,
    723 	               sim->n_file++);
    724 	if (ret < 0 || (size_t)ret >= sizeof(outfile))
    725 		err(1, "%s: outfile snprintf", __func__);
    726 
    727 	if ((fp = fopen(outfile, "w")) != NULL) {
    728 		print_output(sim, fp, normalize);
    729 		fclose(fp);
    730 	} else {
    731 		fprintf(stderr, "could not open output file: %s", outfile);
    732 		exit(1);
    733 	}
    734 }
    735 
    736 void print_output(struct simulation *sim, FILE *fp, const int norm) {
    737 	int i;
    738 	double *v_x_out;
    739 
    740 	if (norm)
    741 		v_x_out = normalize(sim->v_x, sim->nz);
    742 	else
    743 		v_x_out = copy(sim->v_x, sim->nz);
    744 
    745 	for (i = 0; i < sim->nz; ++i)
    746 		fprintf(fp,
    747 		        "%.17g\t%.17g\t%.17g\t"
    748 		        "%.17g\t%.17g\t%.17g\t"
    749 		        "%.17g\t%.17g\t%.17g\t%.17g"
    750 		        "\n",
    751 		        sim->z[i], v_x_out[i], sim->sigma_n_eff[i], sim->p_f_ghost[i + 1],
    752 		        sim->mu[i], sim->gamma_dot_p[i], sim->phi[i], sim->I[i],
    753 		        sim->mu[i] * fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN),
    754 		        sim->d_x[i]);
    755 
    756 	free(v_x_out);
    757 }
    758 
    759 static int temporal_increment(struct simulation *sim) {
    760 	int i;
    761 
    762 	if (sim->transient)
    763 		for (i = 0; i < sim->nz; ++i)
    764 			sim->phi[i] += sim->phi_dot[i] * sim->dt;
    765 
    766 	if (sim->fluid)
    767 		for (i = 0; i < sim->nz; ++i) {
    768 			if (isnan(sim->p_f_dot[i])) {
    769 				fprintf(stderr, "encountered NaN at sim->p_f_dot[%d] (t = %g s)\n", i,
    770 				        sim->t);
    771 				return 1;
    772 			} else {
    773 				sim->p_f_ghost[i + 1] += sim->p_f_dot[i] * sim->dt;
    774 			}
    775 		}
    776 
    777 	for (i = 0; i < sim->nz; ++i)
    778 		sim->d_x[i] += sim->v_x[i] * sim->dt;
    779 	sim->t += sim->dt;
    780 
    781 	return 0;
    782 }
    783 
    784 int coupled_shear_solver(struct simulation *sim, const int max_iter,
    785                          const double rel_tol) {
    786 	int i, coupled_iter, stress_iter = 0;
    787 	double r_norm_max, vel_res_norm = NAN, mu_wall_orig = sim->mu_wall;
    788 
    789 	copy_values(sim->p_f_ghost, sim->p_f_next_ghost, sim->nz + 2);
    790 	compute_effective_stress(sim); /* Eq. 9 */
    791 
    792 	do { /* stress iterations */
    793 		coupled_iter = 0;
    794 		do { /* coupled iterations */
    795 
    796 			if (sim->transient) {
    797 				copy_values(sim->phi_dot, sim->old_val, sim->nz);
    798 
    799 				/* Fused loop for Eqs. 1-6 */
    800 				for (i = 0; i < sim->nz; ++i) {
    801 					/* Eq. 1: Inertia number */
    802 					sim->I[i] = inertia_number(sim->gamma_dot_p[i], sim->d,
    803 					                           fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN),
    804 					                           sim->rho_s);
    805 
    806 					/* Eq. 2: Critical state porosity */
    807 					sim->phi_c[i] =
    808 					    sim->phi_min + (sim->phi_max - sim->phi_min) * sim->I[i];
    809 
    810 					/* Eq. 5: Dilatancy angle */
    811 					sim->tan_psi[i] =
    812 					    sim->dilatancy_constant * (sim->phi_c[i] - sim->phi[i]);
    813 
    814 					/* Eq. 7: Critical state friction */
    815 					if (sim->fluid)
    816 						sim->mu_c[i] =
    817 						    sim->mu_wall / (fmax(sim->sigma_n_eff[i], SIGMA_N_EFF_MIN) /
    818 						                    (sim->P_wall - sim->p_f_top));
    819 					else
    820 						sim->mu_c[i] =
    821 						    sim->mu_wall /
    822 						    (1.0 + (1.0 - sim->phi[i]) * sim->rho_s * sim->G *
    823 						               (sim->origo_z + sim->L_z - sim->z[i]) / sim->P_wall);
    824 
    825 					/* Eq. 3: Porosity change */
    826 					sim->phi_dot[i] = sim->tan_psi[i] * sim->gamma_dot_p[i] * sim->phi[i];
    827 
    828 					/* Eq. 6: Permeability */
    829 					sim->k[i] = kozeny_carman(sim->d, sim->phi[i]);
    830 
    831 					/* Eq. 4: Friction */
    832 					sim->mu[i] = sim->mu_c[i] + sim->tan_psi[i];
    833 				}
    834 			} else {
    835 				/* Non-transient case */
    836 				compute_critical_state_friction(sim);
    837 				compute_friction(sim);
    838 			}
    839 
    840 			/* step 5, Eq. 13 */
    841 			if (sim->fluid && (sim->t > 0))
    842 				if (darcy_solver_1d(sim)) {
    843 					sim->mu_wall = mu_wall_orig;
    844 					return 11;
    845 				}
    846 
    847 			/* step 6 */
    848 			compute_effective_stress(sim); /* Eq. 9 */
    849 
    850 			/* step 7 */
    851 			compute_local_fluidity(sim);       /* Eq. 10 */
    852 			compute_cooperativity_length(sim); /* Eq. 12 */
    853 
    854 			/* step 8, Eq. 11 */
    855 			if (implicit_1d_sor_poisson_solver(sim)) {
    856 				sim->mu_wall = mu_wall_orig;
    857 				return 12;
    858 			}
    859 
    860 			/* step 9 */
    861 			compute_shear_strain_rate_plastic(sim); /* Eq. 8 */
    862 			compute_inertia_number(sim);            /* Eq. 1 */
    863 			compute_shear_velocity(sim);
    864 
    865 #ifdef DEBUG
    866 			/* for (i = 0; i < sim->nz; ++i) { */
    867 			for (i = sim->nz - 1; i < sim->nz; ++i) {
    868 				printf("\nsim->t = %g s\n", sim->t);
    869 				printf("sim->I[%d] = %g\n", i, sim->I[i]);
    870 				printf("sim->phi_c[%d] = %g\n", i, sim->phi_c[i]);
    871 				printf("sim->tan_psi[%d] = %g\n", i, sim->tan_psi[i]);
    872 				printf("sim->mu_c[%d] = %g\n", i, sim->mu_c[i]);
    873 				printf("sim->phi[%d] = %g\n", i, sim->phi[i]);
    874 				printf("sim->phi_dot[%d] = %g\n", i, sim->phi_dot[i]);
    875 				printf("sim->k[%d] = %g\n", i, sim->k[i]);
    876 				printf("sim->mu[%d] = %g\n", i, sim->mu[i]);
    877 			}
    878 #endif
    879 
    880 			/* stable porosity change field == coupled solution converged */
    881 			if (sim->transient) {
    882 				for (i = 0; i < sim->nz; ++i)
    883 					sim->g_r_norm[i] = fabs(residual(sim->phi_dot[i], sim->old_val[i]));
    884 				r_norm_max = max_with_threshold(sim->g_r_norm, sim->nz, rel_tol);
    885 				if (r_norm_max <= rel_tol && coupled_iter > 0)
    886 					break;
    887 				if (coupled_iter++ >= max_iter) {
    888 					fprintf(stderr, "coupled_shear_solver: ");
    889 					fprintf(stderr,
    890 					        "Transient solution did not converge "
    891 					        "after %d iterations\n",
    892 					        coupled_iter);
    893 					fprintf(stderr, ".. Residual normalized error: %g\n", r_norm_max);
    894 					sim->mu_wall = mu_wall_orig;
    895 					return 1;
    896 				}
    897 			}
    898 
    899 		} while (sim->transient);
    900 		if (!isnan(sim->v_x_limit) || !isnan(sim->v_x_fix)) {
    901 			if (!isnan(sim->v_x_limit)) {
    902 				double v_ref =
    903 				    fmax(fabs(sim->v_x_limit), fabs(sim->v_x[sim->nz - 1])) + 1e-12;
    904 				vel_res_norm = (sim->v_x_limit - sim->v_x[sim->nz - 1]) / v_ref;
    905 				if (vel_res_norm > 0.0)
    906 					vel_res_norm = 0.0;
    907 			} else {
    908 				double v_ref =
    909 				    fmax(fabs(sim->v_x_fix), fabs(sim->v_x[sim->nz - 1])) + 1e-12;
    910 				vel_res_norm = (sim->v_x_fix - sim->v_x[sim->nz - 1]) / v_ref;
    911 			}
    912 			sim->mu_wall *= 1.0 + (vel_res_norm * 1e-3);
    913 		}
    914 		if (++stress_iter > max_iter) {
    915 			fprintf(stderr, "error: stress solution did not converge:\n");
    916 			fprintf(stderr,
    917 			        "v_x=%g, v_x_fix=%g, v_x_limit=%g, "
    918 			        "vel_res_norm=%g, mu_wall=%g\n",
    919 			        sim->v_x[sim->nz - 1], sim->v_x_fix, sim->v_x_limit, vel_res_norm,
    920 			        sim->mu_wall);
    921 			sim->mu_wall = mu_wall_orig;
    922 			return 10;
    923 		}
    924 	} while ((!isnan(sim->v_x_fix) || !isnan(sim->v_x_limit)) &&
    925 	         fabs(vel_res_norm) > RTOL_VELOCITY);
    926 
    927 	if (!isnan(sim->v_x_limit) || !isnan(sim->v_x_fix))
    928 		sim->mu_wall = mu_wall_orig;
    929 
    930 	if (temporal_increment(sim))
    931 		return 13;
    932 
    933 	g_stats.coupled_iters += coupled_iter;
    934 	g_stats.stress_iters += stress_iter;
    935 	g_stats.timesteps++;
    936 
    937 	return 0;
    938 }
    939 
    940 double find_flux(const struct simulation *sim) {
    941 	int i;
    942 	double flux = 0.0;
    943 
    944 	for (i = 1; i < sim->nz; ++i)
    945 		flux += (sim->v_x[i] + sim->v_x[i - 1]) / 2.0 * sim->dz;
    946 
    947 	return flux;
    948 }
    949 
    950 void set_coupled_fluid_transient_timestep(struct simulation *sim,
    951                                           const double safety) {
    952 	double max_gamma_dot, mu, xi, max_I, dt;
    953 
    954 	/* max expected strain rate */
    955 	max_gamma_dot = 1.0 / sim->d;
    956 	if (!isnan(sim->v_x_fix))
    957 		max_gamma_dot = sim->v_x_fix / sim->dz;
    958 	if (!isnan(sim->v_x_limit))
    959 		max_gamma_dot = sim->v_x_limit / sim->dz;
    960 
    961 	/* estimate for shear friction */
    962 	mu = (sim->mu_wall / ((sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl) /
    963 	                      (sim->P_wall - sim->p_f_top))) +
    964 	     sim->dilatancy_constant * sim->phi[sim->nz - 1];
    965 
    966 	/* estimate for cooperativity length */
    967 	xi = cooperativity_length(sim->A, sim->d, mu,
    968 	                          (sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl),
    969 	                          sim->mu_s, sim->C);
    970 
    971 	/* max expected inertia number */
    972 	max_I =
    973 	    inertia_number(max_gamma_dot, sim->d,
    974 	                   sim->sigma_n[sim->nz - 1] - sim->p_f_mod_ampl, sim->rho_s);
    975 
    976 	dt = xi * (sim->alpha + sim->phi[sim->nz - 1] * sim->beta_f) * sim->mu_f /
    977 	     (sim->phi[sim->nz - 1] * sim->phi[sim->nz - 1] * sim->phi[sim->nz - 1] *
    978 	      sim->L_z * max_I);
    979 
    980 	if (sim->dt > safety * dt)
    981 		sim->dt = safety * dt;
    982 }
    983 
    984 void print_solver_stats(FILE *fp) {
    985 	fprintf(fp,
    986 	        "solver_stats: timesteps=%ld poisson_iters=%ld "
    987 	        "darcy_iters=%ld coupled_iters=%ld stress_iters=%ld\n",
    988 	        g_stats.timesteps, g_stats.poisson_iters, g_stats.darcy_iters,
    989 	        g_stats.coupled_iters, g_stats.stress_iters);
    990 }
    991 
    992 void reset_solver_stats(void) {
    993 	g_stats.poisson_iters = 0;
    994 	g_stats.darcy_iters = 0;
    995 	g_stats.coupled_iters = 0;
    996 	g_stats.stress_iters = 0;
    997 	g_stats.timesteps = 0;
    998 }
    999 
   1000 void add_darcy_iters(int iters) { g_stats.darcy_iters += iters; }