# Global Ocean Dynamic Adjustment date: 2026/03/22 Contributors: Xylar Asay-Davis, Codex, Claude ## Summary This design document describes a new Polaris capability for dynamic adjustment of global-ocean initial conditions. The intent is to port the core idea from Compass' `compass/ocean/tests/global_ocean/dynamic_adjustment` workflow: a sequence of short MPAS-Ocean forward runs with stage-specific time steps, damping, and restart/output intervals that dissipate fast waves generated by the initial condition before a longer production simulation begins. The new Polaris capability should support the same qualitative workflow for MPAS-Ocean or Omega, selected by the `[ocean] model` config option. A developer should be able to describe a staged adjustment schedule over several days to roughly a month, with progressively less damping and typically increasing time steps, and Polaris should turn that schedule into an inspectable sequence of model steps. Each stage should produce restart files and optional diagnostics so the developer can verify that the solution is settling down and can continue from the final relaxed state. The primary software challenge is not the numerical method itself but the workflow abstraction: Polaris needs a reusable way to describe and execute a chain of forward model stages while mapping common stage settings onto model-specific MPAS-Ocean and Omega configuration options. This design is successful if Polaris can express the existing Compass-style schedules in a compact configuration file, execute them as a restart chain for the configured model, and leave a clean path for model-specific tuning and validation as the capability matures. The capability has landed for MPAS-Ocean and Omega, with schedules for all five unified meshes, and both models have run it end to end on all five; Omega's damped stages need an Omega build with Rayleigh damping. The parts of this document that the implementation changed are the validation strategy — there is deliberately no automated settling criterion — and the schedule format, which became a delta on the forward-run config options rather than a self-contained description of a run. Both are recorded in the sections below. ## Requirements ### Requirement: A staged dynamic-adjustment workflow can be created from a global initial condition Date last modified: 2026/03/22 Contributors: Xylar Asay-Davis, Codex Polaris shall provide a workflow that begins from a global-ocean initial condition and runs a sequence of forward-model stages intended to dissipate fast external and internal waves introduced by initialization. The workflow shall support schedules spanning from a few days to roughly a month, depending on mesh resolution and model stability needs. Each stage shall support its own run duration and restart/output cadence and shall allow stage-specific control over time stepping and damping. ### Requirement: The same conceptual workflow supports either MPAS-Ocean or Omega Date last modified: 2026/06/14 Contributors: Xylar Asay-Davis, Codex, Claude Polaris shall support dynamic adjustment for either MPAS-Ocean or Omega through a common workflow description rather than two unrelated task designs. The target model is selected by the `[ocean] model` config option; a given task targets exactly one model and consumes that model's initial condition. The common description shall allow Polaris to represent concepts such as stage duration, time-step selection, damping strength, and restart chaining even if the exact model configuration options differ between MPAS-Ocean and Omega. ### Requirement: Dynamic adjustment is decomposed into inspectable restart stages Date last modified: 2026/03/22 Contributors: Xylar Asay-Davis, Codex The workflow shall be organized into individual Polaris steps or substeps, one per stage of the dynamic-adjustment schedule. Each stage shall consume either the original initial condition or the restart from the preceding stage and shall produce a restart suitable for the next stage. The final stage shall produce a relaxed restart that can be used for subsequent simulation workflows. ### Requirement: Adjustment schedules are reusable but easy to tune Date last modified: 2026/03/22 Contributors: Xylar Asay-Davis, Codex Polaris shall support reusable default schedules for common global meshes while also allowing users or developers to modify durations, damping values, and time steps without changing Python source code. The design shall make it straightforward to port existing Compass schedules into Polaris and later refine them as model behavior or stability limits change. ### Requirement: The workflow includes basic validation that the adjustment remained well behaved Date last modified: 2026/03/22 Contributors: Xylar Asay-Davis, Codex Polaris shall provide at least basic validation that the dynamic-adjustment sequence completed successfully and did not produce obviously unphysical results. This validation does not need to prove that the schedule is scientifically optimal, but it should detect clear failures such as numerical blow-up, missing restart chaining, or gross excursions in key diagnostics. ## Algorithm Design ### Algorithm Design: A staged dynamic-adjustment workflow can be created from a global initial condition Date last modified: 2026/08/11 Contributors: Xylar Asay-Davis, Codex, Claude The workflow is described by an ordered schedule in YAML, containing a small number of shared options plus a list of stages. Each stage may contain: 1. A stage name (the mapping key). 2. A run duration. 3. An output interval. 4. A restart interval. 5. Time-step information, either explicit (`dt`/`btr_dt`) or derived from mesh resolution (`dt_per_km`/`btr_dt_per_km`). 6. Damping settings, typically decreasing from one stage to the next. Every schedule key maps directly onto a field of the reusable, model-agnostic `ForwardStage` object introduced by the `realistic_global` forward workflow, so the schedule parser is a thin adapter rather than a second configuration system. The keys use Polaris-native names (`stages`, `damping`) rather than the Compass names, but the mental model is the same: a sequence of several `damped_adjustment_*` stages followed by a final `simulation` stage, which has proven useful across several global meshes. The relationship between the schedule and the config options turned out to be the important design decision, and it went further than "maps onto `ForwardStage`". A stage *is* a forward run, so it starts from the `[realistic_global_forward]` config options for its mesh — including the per-mesh overrides — and the schedule supplies only what varies from stage to stage. The horizontal mixing coefficients, the eddy parameterizations and the time integrators are therefore the same as they would be for a `forward` run on that mesh, and no schedule mentions them. Keeping the schedule a delta is what stops it from becoming a second configuration system that has to track `ForwardStage` field by field. The corollary is that a schedule key must name a real forward-run setting. A key that names nothing is an error at setup rather than a silent fall back to the config value, so a schedule cannot quietly go stale when an option is renamed. Damping runs the other way round from every other option: it is off in config and turned on per stage, so the final `simulation` stage gets an undamped run simply by omitting it. ### Algorithm Design: The same conceptual workflow supports either MPAS-Ocean or Omega Date last modified: 2026/10/26 Contributors: Xylar Asay-Davis, Codex, Claude The schedule should be model-agnostic. Polaris should interpret the schedule into a common internal representation and then map each stage into model configuration options appropriate for the configured ocean model. For MPAS-Ocean, this mapping will likely include familiar options such as `config_dt`, `config_btr_dt`, restart flags, and the Rayleigh damping controls used in Compass. For Omega, Polaris should provide a model-specific translator that applies the closest available controls for time stepping, restart behavior, output cadence, and damping. The initial implementation does not need to guarantee that MPAS-Ocean and Omega use identical low-level damping controls. The requirement is instead that either model can participate in the same staged-adjustment workflow abstraction and that model-specific differences are isolated in one place. That isolation landed in the forward workflow rather than here: the mapping is `ForwardStage.model_replacements` and the `mpaso_to_omega.yaml` translation the model-neutral template already goes through, so this workflow contributes no model-specific code of its own. Damping is the one control that is not a translation: Omega's `RayleighDamping` applies the same drag in every layer, in the implicit vertical-mixing solve, but MPAS-Ocean turns its damping on through the bottom-drag type rather than a switch of its own. The forward step adds the group to a damped stage's Omega config directly, and leaves it out of an undamped one, so that only damped stages need an Omega build that has it. ### Algorithm Design: Dynamic adjustment is decomposed into inspectable restart stages Date last modified: 2026/03/22 Contributors: Xylar Asay-Davis, Codex Polaris should create one model-running step per adjustment stage. The first stage should start from the initial condition produced by the corresponding global-ocean initialization workflow. Each later stage should start from the restart written by the previous stage. This stepwise structure makes the workflow easy to inspect and debug. If a particular stage is unstable or clearly overdamped, the user can examine that stage's configuration and outputs without having to reason about a single large monolithic run script. The workflow should also compute each stage's start and stop time from the ordered schedule so restart filenames and model start times are deterministic and do not need to be entered redundantly by hand. ### Algorithm Design: Adjustment schedules are reusable but easy to tune Date last modified: 2026/08/11 Contributors: Xylar Asay-Davis, Codex, Claude The most practical near-term design is to store default schedules in small YAML files associated with a mesh, task, or mesh family, following the same pattern Compass already uses. Polaris should support two complementary modes: 1. A default mesh-specific schedule that can be checked into the repository. 2. User overrides through Polaris config options or an alternate schedule file. This keeps the common case simple while avoiding hard-coded stage definitions in Python. Both landed: a checked-in `.yaml` when the mesh has one and `default.yaml` otherwise, with a `schedule` config option naming an alternate file. The one constraint worth stating is that the set of steps depends on the schedule, and a Polaris step graph is fixed at setup. So a schedule change takes effect at `polaris setup`, not at run time, and the task rebuilds its stages during `configure()` — after the user's config has been merged — because a stage whose name survived a change would otherwise come back from the shared-step cache still carrying its old run duration. ### Algorithm Design: The workflow includes basic validation that the adjustment remained well behaved Date last modified: 2026/08/11 Contributors: Xylar Asay-Davis, Codex, Claude Validation focuses on clear failure modes rather than exhaustive physical assessment. The strategy as implemented is: 1. Confirm that each stage completed and wrote the restart the next stage consumes (enforced by the Polaris dependency graph and each stage's declared input/output restart files). 2. Bound the maximum `temperature`, the maximum `salinity` and the maximum CFL number reached at any point in each stage, to catch numerical blow-up and a time step too long for the flow the stage produced. 3. Collect a per-stage diagnostics table and plot the underlying time series, so that whether the adjustment settled is read by a person rather than asserted by a threshold. 4. Compare a small set of final-state variables against a baseline for regression testing. Item 4 of the earlier strategy — requiring the maximum cell kinetic energy to flatten over the last few stages — was removed. It was rewritten three times (kinetic-energy levels, then the fractional change stage over stage, then that change per unit time) and **every** revision was forced by a healthy run failing it. The quantity is not comparable across stages: a schedule's stages differ in length by up to 70x, the final stage switches the damping off so its energy rises by design, and a stage whose energy happens to dip makes the next ordinary rise read as an acceleration. Three samples cannot separate any of that from a run that is genuinely running away. Whether an adjustment settled is now read from the diagnostics table and the figure, which show the whole series; the checks that remain are bounds on quantities with real failure modes behind them. An automated settling criterion should not be re-added without evidence that it survives schedules whose stages differ in length and damping. The other change is where the numbers come from. Rather than reading `output.nc`, the checks read each stage's global-statistics file wherever the configured model reports the quantity, because the model has already reduced it over the whole domain at the statistics cadence — so an excursion in the middle of a stage is visible, which it is not in an end-of-stage field. `output.nc` is the fallback, and only for maxima and minima, which mean the same thing however they are obtained. A volume-weighted mean does not, so a metric the model does not report is left blank rather than quietly replaced with an unweighted one. Two exclusions make the bounds mean what they say. A stage is never judged on the sample written before its first time step: for the first stage that sample is the initial condition, which dynamic adjustment did not produce, and for every later stage it is the predecessor's final state, which that stage's own check already covered. And the tracer bounds ignore a configurable window at the start of the *sequence*, over which the initial condition's artifacts are still mixing away. The window is measured from the sequence rather than from each stage deliberately: what it skips belongs to the initial condition, and a per-stage window would instead stop watching just after every damping change, which is where a schedule that steps the damping down too fast would show itself. For the same reason the CFL bound does not get the window at all. ## Implementation ### Implementation: A staged dynamic-adjustment workflow can be created from a global initial condition Date last modified: 2026/09/24 Contributors: Xylar Asay-Davis, Codex, Claude Polaris adds a `realistic_global` dynamic-adjustment task that consumes the outputs of the shared `realistic_global/init` steps. The task reads a schedule file and instantiates one forward model step per stage, reusing the `realistic_global` `Forward` step and its `ForwardStage` settings object unchanged. The schedule uses Polaris-native keys that name `ForwardStage` fields, which is to say `[realistic_global_forward]` config options. A `shared` block supplies per-stage defaults; each stage overrides what it needs. The `u-oi240-lr240` schedule, shortened: ```yaml dynamic_adjustment: shared: output_interval: 10_00:00:00 restart_interval: 10_00:00:00 start_time: 0001-01-01_00:00:00 stages: damped_adjustment_1: run_duration: 10_00:00:00 dt: 02:00:00 btr_dt: 00:06:00 damping: 1.0e-4 simulation: run_duration: 10_00:00:00 dt: 02:00:00 btr_dt: 00:06:00 # damping omitted => the config default, which is no Rayleigh damping ``` Nothing about the physics or the time integrators appears here: those come from `[realistic_global_forward]` and the mesh's own overrides, and `load_schedule_stages` applies the schedule on top with `dataclasses.replace`. Time steps may instead be given per km of mesh minimum resolution (`dt_per_km`/`btr_dt_per_km`), matching the existing `ForwardStage` support. The parser computes each stage's cumulative start/stop time and the shared `restarts/rst..nc` filenames, so the restart chain is deterministic and not entered by hand. This port renames Compass' `steps` to `stages` and `Rayleigh_damping_coeff` to `damping`, and drops `land_ice_flux_mode`, which `realistic_global` does not yet model. Four rules keep a schedule honest, all enforced at setup: * Keys are validated against `SCHEDULE_FIELDS` — the `ForwardStage` fields minus the ones the restart chain owns — and coerced to each field's declared type. A renamed or misspelled option raises instead of falling back to the config value, and `do_restart`, `restart_in` and `restart_out` cannot be set by a stage at all. `start_time` is accepted only in the `shared` block, since it is where the chain begins and each stage's own start time follows from the durations before it. * `run_duration` is the one key a stage must set, since the chain's timing follows from it. * `restart_interval` defaults to the stage's own `run_duration` rather than to the config value, because every stage has to write the restart the next one reads. * A `restart_interval` must put the stage's stop time on MPAS-Ocean's restart alarm, which is measured from a fixed reference time and not from where the stage starts, and a `stats_interval` must be no longer than the stage. Otherwise the restart the next stage needs is never written, or the statistics hold only the record written at startup — and both fail only after the model has run. `output_interval` is deliberately not checked: an interval longer than the stage is a legitimate way to say "no 3-D output during the damped stages", which is what the ported Compass schedules do. Such a stage still writes one record, its initial state for MPAS-Ocean and its final state for Omega, which cannot write on startup. ### Implementation: The same conceptual workflow supports either MPAS-Ocean or Omega Date last modified: 2026/10/28 Contributors: Xylar Asay-Davis, Codex, Claude Implementation builds on Polaris' existing model-abstraction machinery, especially `OceanModelStep` and the mapping between common YAML-driven configuration and model-specific MPAS-Ocean or Omega settings. The dynamic-adjustment code separates: 1. Schedule parsing and restart sequencing. 2. Common stage metadata such as duration and cadence (`ForwardStage`). 3. Model-specific translation of time-step and damping options (`ForwardStage.model_replacements` / `bottom_drag_options` / `omega_damping_replacements`). This separation keeps the workflow portable even though Omega needs different config names and a different subset of controls. Since each task targets a single configured model, no within-task branching is required. The implementation wires the restart chain end-to-end for **MPAS-Ocean**, where the restart stream is both an input and an output stream, so one `filename_template` serves both directions and the read side is just `config_do_restart` and `config_start_time`. The start time is stated explicitly rather than through a restart-pointer file. The schedule and stage abstraction remain model-agnostic. For Omega, each stage reads its predecessor's restart through a `StartType` and a pointer file, as described in the forward framework's design; the chain has been run on all five unified meshes with an Omega test merge that adds Rayleigh damping. Omega runs the adjustment with its split-explicit integrator, `SplitExplicitRK2`, on the schedules' split time steps; RK4 would have to advance on the barotropic step. ### Implementation: Dynamic adjustment is decomposed into inspectable restart stages Date last modified: 2026/10/21 Contributors: Xylar Asay-Davis, Codex, Claude Each stage lives in its own work directory under `spherical/realistic_global/{mesh_name}/dynamic_adjustment/{stage_name}` and declares: 1. The restart or initial-condition input it consumes. 2. The restart file it produces for the next stage. 3. A stage-local output file for diagnostics or later validation. The stages share a `restarts` directory beside them, so a break in the chain is caught by Polaris before the model launches. The Python driver computes restart times from cumulative run durations, so the user does not have to provide both a stage duration and an absolute stop time. The whole chain — the shared `init` steps, one `Forward` step per stage, a `StageCheck` after each, `summary` and `viz` — is built by `get_realistic_dynamic_adjustment_steps(component, mesh_name, include_viz)` through `Component.get_or_create_shared_step`, following the same pattern as `get_realistic_init_steps`. It returns the stages alongside the steps and config, because which stage hands off the adjusted state, and what that restart file is called, both depend on the schedule: `stages[-1].restart_out` is what a downstream workflow is after. `e3sm/init`'s component inputs is such a workflow, and gets the same step instances rather than a second copy of a chain that costs days. Two details are specific to this workflow. Consecutive stages are linked with `Step.add_dependency` rather than through their input/output files alone, because a stage's restart filename comes from the schedule rather than from the step, so the wiring is not available inside a step's constructor the way the `init` steps do it; re-adding the same dependency is a no-op, which is what makes calling the accessor again safe. And each stage's check is a step of its own, run immediately after that stage, rather than work the forward step does after the model exits: a Polaris step is sized for its MPI call, so folding the check in would hold the model's whole allocation open to compare a few scalars — and a stage that is already out of bounds stops the sequence instead of costing the rest of the job. ### Implementation: Adjustment schedules are reusable but easy to tune Date last modified: 2026/09/24 Contributors: Xylar Asay-Davis, Codex, Claude The schedules live with the task package, which makes them version-controlled, easy to review, and easy to tune for a specific resolution family. Six are checked in: one for each of the five unified meshes, and `default.yaml` for everything else. The `u-oi30-lr10`, `u-oi6to18-lr12p5`, `u-oi6to18-lr25` and `u-oi-so12to30-lr10` schedules are ported from Compass' `ec30to60`, `rrs6to18` (for both `u-oi6to18` meshes) and `so12to30`; `u-oi240-lr240` follows the same damping ramp on ten-day stages, so that the shape the finer meshes use is exercised on the cheapest mesh that has a realistic initial condition. `default.yaml`, which the coarse base meshes such as `icos240km` and `qu240km` fall back to, is two stages. That is deliberate: it is a quick check that a mesh runs at all rather than an adjustment worth keeping, and a mesh that deserves a real one deserves a schedule of its own. No automatic tuning procedure was invented, as this section intended. A user retunes by editing the checked-in YAML or by pointing the `schedule` config option at another file before setup. ### Implementation: The workflow includes basic validation that the adjustment remained well behaved Date last modified: 2026/10/21 Contributors: Xylar Asay-Davis, Codex, Claude Validation ended up split between a per-stage `StageCheck` step and a final `Summary` step, plus the built-in baseline comparison: 1. A baseline comparison of the final `simulation/output.nc` (`temperature`, `salinity`, `normalVelocity`), handled by the forward step's built-in `validate_class='state'` mechanism, as in the forward tasks. 2. Four thresholds per stage, applied by `StageCheck` immediately after that stage: the maximum `temperature` below `temperature_max`, the minimum above `temperature_min`, the maximum `salinity` below `salinity_max`, and the maximum reported CFL number below `cfl_max`. Each reports *when* the extreme occurred as well as how large it was, which is usually most of the diagnosis, and each is skipped with a log line when the configured model does not report the quantity. 3. `Summary` applies no checks at all. It collects one row of diagnostics per stage, writes `dynamic_adjustment_stats.csv` and logs the same table, so the summary a user reads and the checks that passed are one calculation rather than two. Its step is named `summary` rather than `validate` because a Polaris `validate` step is expected to compare and fail. The settling heuristic this section listed as item 3 was removed, for the reasons given in the corresponding Algorithm Design section above. The diagnostics are defined by a `METRICS` list, each naming the global-statistics variable it reads in MPAS-Ocean naming, how to reduce that variable's time series over the stage, and the `output.nc` field to fall back to when the model does not report it. Omega's statistics are renamed to those names by `open_stage_stats`, using the names `polaris.ocean.global_stats_names` builds for each model, since `mpaso_to_omega.yaml` no longer maps statistics pair by pair; Omega's pseudo-thickness stands in for layer thickness, as it did in that map. Metrics with no honest fallback are left blank: a maximum means the same thing computed either way, a volume-weighted mean does not. Two derived columns, the mean temperature and salinity change per day, are a conservation check rather than a settling one — these runs are forced by wind alone with no surface fluxes, so both should be zero to within machine noise, and `u-oi30-lr10` reaches -4e-17 °C/day. Omega's `GlobalStats` covers temperature, salinity, layer thickness, normal velocity and kinetic energy but has no CFL number or volume-weighted sums. The reported-CFL column is therefore blank and its threshold check is skipped for Omega. Polaris separately estimates a conservative horizontal CFL using the largest absolute normal-velocity extrema, the stage timestep and the smallest horizontal mesh edge. Because the velocity and edge-length extrema need not be at the same location, this estimate is an upper bound, not Omega's CFL, and is reported for inspection rather than checked against `cfl_max`. Minimum layer thickness remains a separate diagnostic; it is not used as a horizontal CFL length scale. The log says which source each metric came from. The two tracer thresholds are blow-up detectors, not plausibility bounds, and they are set above the WOA23 source artifacts rather than above the physics. NOAA's January WOA23 analysis contains one bad cell off Sumatra that reaches 35.9 to 36.1 °C in the `u-oi6to18` initial conditions and erodes slowly enough to still be present when the third stage begins, and a Red Sea salinity blob at 1150 m that settles near 42.1 PSU for the rest of the adjustment. A bound placed at the physics would fail the opening stages of a healthy run; a real blow-up produces hundreds of degrees or NaN, so there is ample room between the two. This is also what the sequence-wide startup window exists for. A `viz` step plots the same statistics as time series — kinetic energy, maximum normal velocity, CFL number, tracer extremes, mean tracer change and minimum layer thickness on one continuous axis, with stage boundaries marked and each stage labelled with its damping. A summary row cannot distinguish a quantity that was flat through a stage from one that spiked and recovered, and those call for different changes to the schedule; the figure can. It belongs to the standalone task: a workflow that only wants the relaxed restart is not asking about a completed adjustment. A second figure answers how far the adjustment moved the ocean from its initial condition, with volume-weighted histograms of the change in temperature and salinity from the initial state to the end of the last stage. The common concept remains "each stage completed and wrote its restart, and the final adjusted state looks reasonable" — with the second half now answered by a person reading a table and a figure, rather than by a threshold pretending to. ## Testing ### Testing and Validation: A staged dynamic-adjustment workflow can be created from a global initial condition Date last modified: 2026/09/24 Contributors: Xylar Asay-Davis, Codex, Claude Unit tests cover schedule parsing, cumulative restart-time calculation, and error handling for malformed schedules — a missing `dynamic_adjustment` key, a missing `stages` block, a stage that is not a mapping, a missing `run_duration`, an unknown key in a stage or in `shared`, a chain-owned option a stage tried to set, and `start_time` outside `shared`. Others cover the semantics the format depends on: that a schedule value overrides the config option of the same name, that a blank value clears an optional field, that `restart_interval` defaults to the stage duration, and that stages inherit the per-mesh overrides, the physics options and the time integrators without mentioning them. The two cadence rules get tests of their own, since both otherwise fail only after the model has run: a `restart_interval` that misses the stage end raises, one measured from the fixed reference rather than the stage start is accepted, and a `stats_interval` longer than the stage raises while an `output_interval` longer than the stage does not. Running a short multi-stage adjustment on a coarse global mesh remains the integration test, and is what `default.yaml` and the `u-oi240-lr240` schedule are for. It is not something a suite can run. ### Testing and Validation: The same conceptual workflow supports either MPAS-Ocean or Omega Date last modified: 2026/10/26 Contributors: Xylar Asay-Davis, Codex, Claude Model-specific tests confirm that the same staged schedule renders for either configured model, even though the resulting low-level options are not identical: the diagnostics summary is built for both, a blow-up is caught for both, and each model's stages declare the restart files that model writes. The differences that must *not* pass silently get tests of their own — a damped stage gives Omega its `RayleighDamping` and an undamped one leaves the group out, and the statistics filename each model actually writes is pinned, since Omega's moves with the statistics interval and a name that is not found reads as a stage that wrote nothing. ### Testing and Validation: Dynamic adjustment is decomposed into inspectable restart stages Date last modified: 2026/08/11 Contributors: Xylar Asay-Davis, Codex, Claude Tests verify that the restart chain is consistent — each stage's `restart_out` is the next stage's `restart_in`, all in one shared `restarts` directory — that a stage in a chain declares both ends of it while a lone stage declares neither, and that the last stage names the restart a downstream consumer is after. Because these steps are shared, a second group of tests covers what sharing means: that two consumers asking for the same mesh get the same step instances, that every step is registered on the component, that the shared config is reused rather than rebuilt, that the `viz` step is created but returned only when asked, and that `configure()` rebuilds the stages only when the schedule actually changed. ### Testing and Validation: Adjustment schedules are reusable but easy to tune Date last modified: 2026/08/11 Contributors: Xylar Asay-Davis, Codex, Claude Every checked-in schedule is exercised, not just one, so that a change to the parser or to the model-option mapping cannot silently break any of them. The tests assert the properties that make a schedule an adjustment rather than an arbitrary sequence of runs: each unified mesh has the expected number of stages, the damping ramps monotonically down, the final stage is undamped, and every stage writes statistics within its own duration. A mesh with no file of its own falls back to `default.yaml`, and that fallback chains correctly too. ### Testing and Validation: The workflow includes basic validation that the adjustment remained well behaved Date last modified: 2026/10/21 Contributors: Xylar Asay-Davis, Codex, Claude Regression testing includes loose threshold checks and a baseline comparison on a few key fields, to catch obvious failures such as numerical instability, missing restart chaining, or grossly unphysical state variables. The tests around those thresholds are mostly about *which samples* a stage is judged on, because that is where the checks are easy to get wrong: that the sample written before a stage's first time step is excluded, that an extreme after it is still caught, that the startup window skips only the stages it actually reaches, that a sample landing exactly on the window's edge is excluded, that a stage left with nothing to judge is reported rather than silently passed, and that the CFL check deliberately watches inside the window the tracer checks skip. A further pair confirms that a blow-up in the middle of a stage is caught from the statistics — the case an end-of-stage field would miss — and that the summary applies the same windows the checks do, so the CSV and the checks cannot disagree. This section previously asked for a test that the last few stages show tapering behavior in a kinetic-energy metric. There is instead a test asserting that `Summary` does *not* judge the kinetic energy, for the reasons in the Algorithm Design section: three samples of a quantity that is not comparable across stages cannot distinguish a healthy adjustment from a runaway, and every attempt to make them failed healthy runs. Whether a schedule is settling is read from the diagnostics table and the `viz` figure, whose own tests confirm that the series is plotted, that stages without statistics are skipped, and that stage labels stay legible when a schedule opens with stages far shorter than the axis.