src-local/params.h
params.h
Runtime parameter management for the bursting-bubble simulation.
Defines a single configuration structure plus helpers
to set defaults, parse a key=value
parameter file (case.params), apply
key=value command-line overrides, fall back
to the legacy positional CLI, validate, and print the
resolved configuration.
This is the C-side analogue of the shell
parse_params.sh layer: the shell layer
orchestrates case folders and sweeps, while this header
lets the simulation read its own configuration directly
from case.params. Adding a new tunable knob
requires only (1) a struct field, (2) a default in
set_default_params, and (3) a line in
apply_param_kv — which is what makes the
adaptive time/space resolution upgrade a configuration
change rather than a code edit.
Author
Vatsal Sanjay ([email protected]) CoMPhy Lab, Durham University
#ifndef PARAMS_H
#define PARAMS_H25
#include <ctype.h> // isspace()
#include <sys/stat.h> // mkdir()
#include <errno.h> // errnoSimulationParams
All runtime parameters in one structure. Grouped into physical, geometry, adaptive-space, adaptive-time, and time-control blocks.
struct SimulationParams {
// Case identification
int CaseNo;< Case number for folder naming (4-digit: 1000-9999)
// Physical parameters (dimensionless numbers)
double Oh;< Ohnesorge number (liquid): mu/sqrt(rhosigmaR)
double Bond;< Bond number: rhogR^2/sigma
double OhRatio;< Gas/liquid Ohnesorge ratio; Oha = OhRatio*Oh
// Geometry
double zWall;< Distance from bubble south pole to bottom wall
// Adaptive SPACE resolution
int MAXlevel;< Maximum refinement level (2^MAXlevel cells at finest)
int MINlevel;< Minimum refinement level (far-field coarsening floor)
int init_grid_level;< Initial uniform grid level (2^init_grid_level cells)
double fErr;< Wavelet error tolerance on the VOF field f
double VelErr;< Wavelet error tolerance on velocity components
double KErr;< Wavelet error tolerance on interface curvature
// Adaptive TIME resolution
double CFL;< Advective CFL number
double dtmax;< Ceiling on the timestep; surface tension reduces it to the capillary-wave limit each step (adaptive dt)
double TOLERANCE;< Poisson/viscous solver convergence tolerance
// Time control
double tmax;< Maximum simulation time (capillary units)
double tsnap;< Snapshot/restart dump interval
};set_default_params()
Populate the parameter structure with defaults
representative of a water-like bursting bubble. These
reproduce the historical hard-coded configuration,
except that dtmax is now a generous ceiling
(surface tension sets the actual adaptive step) and
MINlevel is an explicit far-field floor
(previously the implicit MAXlevel-6).
static inline void set_default_params(struct SimulationParams *p) {
// Case identification
p->CaseNo = 1000;
// Physical (water-air-like)
p->Oh = 1.0e-2;
p->Bond = 1.0e-3;
p->OhRatio = 2.0e-2;
// Geometry
p->zWall = 0.05;
// Adaptive space
p->MAXlevel = 10;
p->MINlevel = 4;
p->init_grid_level = 5;
p->fErr = 1.0e-3;
p->VelErr = 1.0e-3;
p->KErr = 1.0e-6;
// Adaptive time
p->CFL = 0.1;
p->dtmax = 1.0e-2;
p->TOLERANCE = 1.0e-4;
// Time control
p->tmax = 1.0;
p->tsnap = 1.0e-2;
}apply_param_kv()
Apply a single key/value
assignment to the parameter structure.
Shared by file parsing and command-line override parsing so the key dispatch lives in exactly one place.
Returns
1if the key was recognised0for an unknown key (caller may warn)
static inline int apply_param_kv(const char *key, const char *value,
struct SimulationParams *p) {
if (strcmp(key, "CaseNo") == 0) p->CaseNo = atoi(value);
else if (strcmp(key, "Oh") == 0) p->Oh = atof(value);
else if (strcmp(key, "Bond") == 0) p->Bond = atof(value);
else if (strcmp(key, "OhRatio") == 0) p->OhRatio = atof(value);
else if (strcmp(key, "zWall") == 0) p->zWall = atof(value);
else if (strcmp(key, "MAXlevel") == 0) p->MAXlevel = atoi(value);
else if (strcmp(key, "MINlevel") == 0) p->MINlevel = atoi(value);
else if (strcmp(key, "init_grid_level") == 0) p->init_grid_level = atoi(value);
else if (strcmp(key, "fErr") == 0) p->fErr = atof(value);
else if (strcmp(key, "VelErr") == 0) p->VelErr = atof(value);
else if (strcmp(key, "KErr") == 0) p->KErr = atof(value);
else if (strcmp(key, "CFL") == 0) p->CFL = atof(value);
else if (strcmp(key, "dtmax") == 0) p->dtmax = atof(value);
else if (strcmp(key, "TOLERANCE") == 0) p->TOLERANCE = atof(value);
else if (strcmp(key, "tmax") == 0) p->tmax = atof(value);
else if (strcmp(key, "tsnap") == 0) p->tsnap = atof(value);
else return 0;
return 1;
}trim_inplace()
Strip leading/trailing whitespace from a string in place and return a pointer to the trimmed start.
static inline char *trim_inplace(char *s) {
while (*s && isspace((unsigned char)*s)) s++;
if (*s == '\0') return s;
char *end = s + strlen(s) - 1;
while (end > s && isspace((unsigned char)*end)) *end-- = '\0';
return s;
}parse_params_from_file()
Parse parameters from a key=value
configuration file. Comments (#), inline
comments, and blank lines are ignored. Unknown keys
produce a warning but do not abort (forward
compatibility with the shell layer).
Returns
0on success-1if the file cannot be opened
static inline int parse_params_from_file(const char *filename,
struct SimulationParams *p) {
FILE *fp = fopen(filename, "r");
if (!fp) {
fprintf(stderr, "ERROR: Cannot open parameter file: %s\n", filename);
return -1;
}
char line[512];
int line_num = 0;
while (fgets(line, sizeof(line), fp)) {
line_num++;
// Strip inline comments
char *hash = strchr(line, '#');
if (hash) *hash = '\0';
// Skip blank lines
char *start = trim_inplace(line);
if (*start == '\0') continue;
// Split on first '='
char *eq = strchr(start, '=');
if (!eq) continue;
*eq = '\0';
char *key = trim_inplace(start);
char *value = trim_inplace(eq + 1);
if (*key == '\0' || *value == '\0') continue;
if (!apply_param_kv(key, value, p))
fprintf(stderr, "WARNING: Unknown parameter '%s' at %s:%d\n",
key, filename, line_num);
}
fclose(fp);
return 0;
}apply_cli_overrides()
Apply key=value tokens from
argv[start..argc) on top of the already
parsed configuration. Enables stage-specific overrides
such as
./burstingBubble case.params tmax=0.10 for
the Stage 1 restart run.
static inline void apply_cli_overrides(int argc, char **argv, int start,
struct SimulationParams *p) {
for (int k = start; k < argc; k++) {
char buf[256];
strncpy(buf, argv[k], sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char *eq = strchr(buf, '=');
if (!eq) {
fprintf(stderr, "WARNING: Ignoring malformed override '%s' (expected key=value)\n",
argv[k]);
continue;
}
*eq = '\0';
char *key = trim_inplace(buf);
char *value = trim_inplace(eq + 1);
if (!apply_param_kv(key, value, p))
fprintf(stderr, "WARNING: Unknown override key '%s'\n", key);
}
}parse_params_from_cli()
Legacy positional fallback so historical invocations keep working:
./burstingBubble <MAXlevel> <Oh> <Bond> <tmax> <zWall>
New adaptive-resolution knobs take their defaults in
this mode. Prefer the case.params file mode
for full control.
Returns
0on success-1if too few arguments
static inline int parse_params_from_cli(int argc, char **argv,
struct SimulationParams *p) {
if (argc < 6) {
fprintf(stderr, "ERROR: Insufficient command line arguments\n");
fprintf(stderr, "Legacy form: %s <MAXlevel> <Oh> <Bond> <tmax> <zWall>\n", argv[0]);
fprintf(stderr, "Preferred: %s <case.params> [key=value ...]\n", argv[0]);
return -1;
}
p->MAXlevel = atoi(argv[1]);
p->Oh = atof(argv[2]);
p->Bond = atof(argv[3]);
p->tmax = atof(argv[4]);
p->zWall = atof(argv[5]);
return 0;
}params_init()
Single entry point. Detects file mode versus legacy positional mode:
- File mode (preferred):
argv[1]is a readable file or ends in.params. The file is parsed, then any furtherkey=valuetokens override it. - Legacy mode: otherwise the first five positional arguments are read.
Always starts from set_default_params,
so unspecified knobs are defaulted.
Returns
0on success-1on error (caller should abort the run)
static inline int params_init(int argc, char **argv,
struct SimulationParams *p) {
set_default_params(p);
if (argc < 2) {
fprintf(stderr, "ERROR: Missing arguments\n");
fprintf(stderr, "Usage: %s <case.params> [key=value ...]\n", argv[0]);
fprintf(stderr, " or: %s <MAXlevel> <Oh> <Bond> <tmax> <zWall> (legacy)\n", argv[0]);
return -1;
}
const char *a1 = argv[1];
int is_file = 0;
FILE *probe = fopen(a1, "r");
if (probe) { fclose(probe); is_file = 1; }
if (!is_file) {
size_t n = strlen(a1);
if (n >= 7 && strcmp(a1 + n - 7, ".params") == 0) is_file = 1;
}
if (is_file) {
if (parse_params_from_file(a1, p) != 0) return -1;
apply_cli_overrides(argc, argv, 2, p);
} else {
if (parse_params_from_cli(argc, argv, p) != 0) return -1;
}
return 0;
}validate_params()
Check physical and numerical consistency.
Returns
1if valid0if invalid
static inline int validate_params(const struct SimulationParams *p) {
int valid = 1;
if (p->CaseNo < 1000 || p->CaseNo > 9999) {
fprintf(stderr, "ERROR: CaseNo must be 4-digit (1000-9999), got %d\n", p->CaseNo);
valid = 0;
}
if (p->Oh <= 0) {
fprintf(stderr, "ERROR: Oh must be positive (Oh = %g)\n", p->Oh);
valid = 0;
}
if (p->OhRatio <= 0) {
fprintf(stderr, "ERROR: OhRatio must be positive (OhRatio = %g)\n", p->OhRatio);
valid = 0;
}
if (p->Bond < 0) {
fprintf(stderr, "ERROR: Bond must be non-negative (Bond = %g)\n", p->Bond);
valid = 0;
}
if (p->MAXlevel < p->MINlevel) {
fprintf(stderr, "ERROR: MAXlevel (%d) must be >= MINlevel (%d)\n",
p->MAXlevel, p->MINlevel);
valid = 0;
}
if (p->MINlevel < 2) {
fprintf(stderr, "ERROR: MINlevel (%d) must be >=\n", p->MINlevel);
valid = 0;
}
if (p->init_grid_level < p->MINlevel || p->init_grid_level > p->MAXlevel) {
fprintf(stderr, "WARNING: init_grid_level (%d) outside [MINlevel, MAXlevel] = [%d, %d]\n",
p->init_grid_level, p->MINlevel, p->MAXlevel);
}
if (p->MAXlevel > 15) {
fprintf(stderr, "WARNING: Very high MAXlevel (%d) may exhaust memory\n", p->MAXlevel);
}
if (p->fErr <= 0 || p->VelErr <= 0 || p->KErr <= 0) {
fprintf(stderr, "ERROR: Wavelet error tolerances must be positive\n");
valid = 0;
}
if (p->CFL <= 0 || p->CFL > 1) {
fprintf(stderr, "ERROR: CFL must be in (0, 1] (CFL = %g)\n", p->CFL);
valid = 0;
}
if (p->dtmax <= 0) {
fprintf(stderr, "ERROR: dtmax must be positive (dtmax = %g)\n", p->dtmax);
valid = 0;
}
if (p->TOLERANCE <= 0) {
fprintf(stderr, "ERROR: TOLERANCE must be positive (TOLERANCE = %g)\n", p->TOLERANCE);
valid = 0;
}
if (p->tmax <= 0) {
fprintf(stderr, "ERROR: tmax must be positive (tmax = %g)\n", p->tmax);
valid = 0;
}
if (p->tsnap <= 0 || p->tsnap > p->tmax) {
fprintf(stderr, "ERROR: Invalid tsnap (tsnap = %g, tmax = %g)\n", p->tsnap, p->tmax);
valid = 0;
}
return valid;
}print_params()
Print a formatted summary for logging and reproducibility.
static inline void print_params(const struct SimulationParams *p, FILE *fp) {
fprintf(fp, "\n========================================\n");
fprintf(fp, "Bursting Bubble Simulation Configuration\n");
fprintf(fp, "========================================\n");
fprintf(fp, "Case Number: %04d\n", p->CaseNo);
fprintf(fp, "Physical Parameters:\n");
fprintf(fp, " Ohnesorge (liquid): %g\n", p->Oh);
fprintf(fp, " Ohnesorge (gas): %g (OhRatio=%g)\n", p->OhRatio * p->Oh, p->OhRatio);
fprintf(fp, " Bond number: %g\n", p->Bond);
fprintf(fp, "Geometry:\n");
fprintf(fp, " zWall: %g\n", p->zWall);
fprintf(fp, "Adaptive Space:\n");
fprintf(fp, " Levels (min/max): %d / %d\n", p->MINlevel, p->MAXlevel);
fprintf(fp, " Initial grid level: %d (2^%d = %d cells)\n",
p->init_grid_level, p->init_grid_level, 1 << p->init_grid_level);
fprintf(fp, " Error tol (f/Vel/K): %g / %g / %g\n", p->fErr, p->VelErr, p->KErr);
fprintf(fp, "Adaptive Time:\n");
fprintf(fp, " CFL: %g\n", p->CFL);
fprintf(fp, " dtmax (ceiling): %g\n", p->dtmax);
fprintf(fp, " Solver TOLERANCE: %g\n", p->TOLERANCE);
fprintf(fp, "Time Control:\n");
fprintf(fp, " tmax: %g\n", p->tmax);
fprintf(fp, " tsnap: %g\n", p->tsnap);
fprintf(fp, "========================================\n\n");
fflush(fp);
}
#endif // PARAMS_H