Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
key: ${{ runner.os }}-${{ steps.os.outputs.release }}-petsc-63b72503
fail-on-cache-miss: true
- name: Install Python requirements
run: pip install -r py/requirements.txt pytest pytest-timeout ruff gcovr 'proteus-mpl>=1.4.0'
run: pip install -r py/requirements.txt pytest pytest-timeout ruff gcovr 'proteus-mpl>=1.5.0'
- name: Build with coverage instrumentation
run: |
make -j CFLAGS_EXTRA="--coverage"
Expand Down Expand Up @@ -159,7 +159,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install Python requirements
run: pip install -r py/requirements.txt pytest pytest-timeout 'proteus-mpl>=1.4.0'
run: pip install -r py/requirements.txt pytest pytest-timeout 'proteus-mpl>=1.5.0'
- name: Run installer
run: ./tools/get_spider.sh
- name: Check the installer produced a runnable binary
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
./configure --with-fc=0 --with-cxx=0 --with-cc=gcc --download-sundials2 --download-mpich --download-f2cblaslapack PETSC_ARCH=arch-test
make all
- name: Install Python requirements
run: pip install -r py/requirements.txt pytest pytest-timeout gcovr 'proteus-mpl>=1.4.0'
run: pip install -r py/requirements.txt pytest pytest-timeout gcovr 'proteus-mpl>=1.5.0'
- name: Build with coverage instrumentation
run: |
make -j CFLAGS_EXTRA="--coverage"
Expand Down
37 changes: 28 additions & 9 deletions docs/Validation/atmosphere.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
# atmosphere.c: Surface energy balance

**Source under test**: `atmosphere.c`
## What this page checks

## Cases
This page checks the surface radiative energy balance in `atmosphere.c`, which models heat exchange.
The atmosphere module calculates the thermal flux, which is exchanged between the planetary surface and space.
Validation means that we test the grey-body radiation routine against the analytical Stefan-Boltzmann law, which provides an exact reference.
If the surface radiation is wrong, the cooling rate of the magma ocean is incorrect.
The section below details the validation check, its tolerance, and its test command.

### Grey-body flux matches Stefan-Boltzmann limit
## Grey-body flux matches Stefan-Boltzmann limit

**Reference-pinned test**: `tests/test_atmosphere.py::test_grey_body_flux_matches_stefan_boltzmann_limit`
**Why this check exists.** Numerical implementations of radiative flux can suffer from incorrect temperature exponents, or from inverted signs. A bug in the surface flux calculation leads to unphysical planetary cooling rates, without raising a runtime error.

**Reference**: Analytical limit: the grey-body flux F = emissivity sigma (T_surf^4 - T_eqm^4) with emissivity 1 and T_eqm = 273 K (Stefan-Boltzmann).
**What it tests.** The surface flux $F$ (W/m$^2$) of a grey body depends on the emissivity $\epsilon$ (0 to 1), the Stefan-Boltzmann constant $\sigma$ (W m$^{-2}$ K$^{-4}$), the surface temperature $T_\mathrm{surf}$ and the equilibrium temperature $T_\mathrm{eqm}$ (both in K). The test evaluates $F = \epsilon\sigma(T_\mathrm{surf}^4 - T_\mathrm{eqm}^4)$ with $\epsilon$ = 1 and $T_\mathrm{eqm}$ = 273 K, the values of the blackbody50 run (the standard 50-node test run, `tests/opts/blackbody50.opts`). It compares the reported surface flux against the Stefan-Boltzmann law, evaluated at the reported surface temperature. The test guards against exponent errors, where a T^3 exponent slip changes the flux by orders of magnitude at magma-ocean temperatures. The flux must remain positive (outward), and bounded to the 1e2 to 1e9 W/m2 regime.

**Tolerance**: rel 1e-6 against the reported surface temperature, using the code constant sigma = 5.670367e-8 W m-2 K-4.
**How we know the right answer.** The grey-body Stefan-Boltzmann law provides an exact analytical reference for thermal radiation, which governs ideal emitters. An exponent slip to third power changes the flux by orders of magnitude, which easily violates the tolerance of rel 1e-6.

**Command**: `make`, then `pytest -o addopts= tests/test_atmosphere.py::test_grey_body_flux_matches_stefan_boltzmann_limit`
**Why it matters.** The atmosphere module computes the surface energy balance, which drives planetary mantle cooling. PROTEUS reads the surface flux `Fatm` as `F_int`, and surface temperature `temperature_surface` as `T_magma`, to couple the interior to the atmosphere.

**Discrimination guards**: A T^3 exponent slip changes the flux by orders of magnitude at magma-ocean temperatures; the flux is positive (outward) and bounded to the 1e2 to 1e9 W/m2 regime; monotone decline of flux and surface temperature over the first outputs.
**Known limit.** The test uses its own copy of the value in `constants.c`, sigma = 5.670367e-8 W m-2 K-4. A change of the constant in the code larger than the tolerance fails the test, but a value that is wrong in both places does not. PROTEUS uses this grey-body branch only with `surface_bc_mode = 'grey_body'`; its default is a prescribed flux (the SPIDER option `SURFACE_BC 4`).

The atmosphere module computes the surface energy balance that drives the cooling. The grey-body branch is pinned against the Stefan-Boltzmann law evaluated from the same output state, and the secular cooling trend is asserted across output times.
![Surface flux of the blackbody50 run at 200 yr on the grey-body law](../assets/validation/atmosphere_grey_body_flux.svg#only-light){ width="80%" }
![Surface flux of the blackbody50 run at 200 yr on the grey-body law](../assets/validation/atmosphere_grey_body_flux_dark.svg#only-dark){ width="80%" }

**How to read the plot.** The x-axis is the surface temperature in K and the y-axis the surface flux in W/m$^2$ on a log scale. The line is the grey-body law $\epsilon\sigma(T^4 - T_\mathrm{eqm}^4)$, with emissivity $\epsilon$ = 1 and equilibrium temperature $T_\mathrm{eqm}$ = 273 K. Here $\sigma$ is the Stefan-Boltzmann constant, and the dashed line is the $T^3$ form, which the test rejects. The circle is the asserted surface flux Fatm of the blackbody50 run at 200 yr, at its reported surface temperature. Its values are shown to 3 significant digits, because the run output moves by about 1e-6 between builds of the PETSc library. A pass means that the circle lies on the line; the test asserts relative 1e-6 on the unrounded values.

??? info "Test details"
- Reference: Analytical limit: the grey-body flux F = emissivity sigma (T_surf^4 - T_eqm^4) with emissivity 1 and T_eqm = 273 K (Stefan-Boltzmann).
- Tolerance: rel 1e-6 on the flux, computed from the reported surface temperature using the test's copy of the code value sigma = 5.670367e-8 W m-2 K-4.
- Test id: `tests/test_atmosphere.py::test_grey_body_flux_matches_stefan_boltzmann_limit`
- Command: `make`, then `pytest -o addopts= tests/test_atmosphere.py::test_grey_body_flux_matches_stefan_boltzmann_limit` (add `--validation-plots` to write the plot pair)
- Companion test: `tests/test_atmosphere.py::test_surface_cools_and_flux_decays_across_macro_steps` confirms monotone decline of flux and surface temperature over outputs.

## Related code

- `atmosphere.c`: `set_atmosphere_emissivity_and_flux`.
- `tests/test_atmosphere.py`: `test_grey_body_flux_matches_stefan_boltzmann_limit`, `test_surface_cools_and_flux_decays_across_macro_steps`.
61 changes: 46 additions & 15 deletions docs/Validation/bc.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,62 @@
# bc.c: Boundary conditions

**Source under test**: `bc.c`
## What this page checks

## Cases
This page checks the boundary condition (BC) implementations in `bc.c`, which control surface and bottom boundaries.
The boundary condition routines specify how energy and entropy enter, or leave, the planetary mantle.
Validation means that we test boundary condition options against analytical identities, and against prescribed inputs.
If boundary condition dispatch fails, energy conservation is violated at the surface, or at the core-mantle boundary (CMB). The SPIDER options `SURFACE_BC` and `CORE_BC` select the boundary condition at the surface and at the CMB.
The sections below detail two validation cases, their tolerances, and their test commands.

### Prescribed-flux BC echoes value and inverts emissivity
## Constant-entropy BC pins the initial surface

**Reference-pinned test**: `tests/test_bc.py::test_prescribed_flux_bc_echoes_value_and_inverts_emissivity`
**Why this check exists.** Fixed boundary values can be overwritten by initial rebalancing steps, or by stencil operations. If the surface boundary assignment fails, the mantle profile starts from an uncalibrated thermodynamic state.

**Reference**: Analytical identity: with SURFACE_BC 4 the surface flux equals the prescribed value, and the reported emissivity is its grey-body inversion.
**What it tests.** The test evaluates the constant-entropy boundary option, which writes `ic_surface_entropy` (2550 J/kg/K) onto the surface basic node (the outermost cell boundary). This value is preserved through the steady-state energy solve into the initial output. The test discriminates this setting against the default configuration surface value (2599.7 J/kg/K from the adiabat, the constant-entropy profile, at 2600), which sits about 50 J/kg/K away. Sibling test `test_prescribed_core_flux_slows_cmb_cooling` checks CORE_BC dispatch, CMB entropy difference, and doubled CMB heat flux.

**Tolerance**: rel 1e-9 on the flux echo; rel 1e-6 on the emissivity inversion.
**How we know the right answer.** Preserving a specified boundary value is an exact identity. Because the value is assigned directly, it matches to rel 1e-10.

**Command**: `make`, then `pytest -o addopts= tests/test_bc.py::test_prescribed_flux_bc_echoes_value_and_inverts_emissivity`
**Why it matters.** Setting fixed surface or core entropies allows controlled numerical experiments and comparisons with simplified interior profiles.

**Discrimination guards**: The grey-body flux at the same surface temperature differs from the prescribed value by more than 10 percent (dispatch guard), and the emissivity differs from 1 by more than 0.5; the flux is positive and between 1e2 and 1e9 W/m^2. The sibling test `test_mantle_cools_from_the_top_between_surface_and_cmb` (same file) asserts the temperature ordering across the mantle (CMB hotter than surface) and the near-zero surface pressure on the default configuration.
**Known limit.** For constant-entropy boundary conditions, the surface value is assigned directly rather than determined by a physical flux balance.

The boundary-condition dispatch is pinned through the prescribed-flux pathway PROTEUS uses in coupled runs: the value handed in through the option must reappear identically in the output, with the emissivity consistently inverted from it.
![Initial entropy profiles with the constant-entropy boundary and with the default boundary](../assets/validation/bc_constant_entropy_surface.svg#only-light){ width="80%" }
![Initial entropy profiles with the constant-entropy boundary and with the default boundary](../assets/validation/bc_constant_entropy_surface_dark.svg#only-dark){ width="80%" }

### Constant-entropy BC pins the initial surface
**How to read the plot.** The x-axis is the radius and the y-axis the entropy at the basic nodes (the cell boundaries of the mesh) at $t$ = 0. The solid line is the asserted profile of the run with a constant-entropy surface boundary. The dashed line is the default run (context); its surface entropy, 2599.7 J kg$^{-1}$ K$^{-1}$, must differ from 2550 by more than 40. The circle is the asserted surface node, and the hollow square the value 2550 J kg$^{-1}$ K$^{-1}$ of the option `ic_surface_entropy`. A pass means that the circle sits in the square (relative 1e-10) and the deep end of the solid line lies between 2400 and 2700.

**Reference-pinned test**: `tests/test_bc.py::test_constant_entropy_bc_pins_the_initial_surface`
??? info "Test details"
- Reference: Analytical identity: the constant-entropy boundary writes ic_surface_entropy (2550 J/kg/K) onto the surface basic node, preserved through the steady-state energy solve into the initial output.
- Tolerance: rel 1e-10 (assigned, not solved); the core-side entropy between 2400 and 2700 J/kg/K.
- Test id: `tests/test_bc.py::test_constant_entropy_bc_pins_the_initial_surface`
- Command: `make`, then `pytest -o addopts= tests/test_bc.py::test_constant_entropy_bc_pins_the_initial_surface` (add `--validation-plots` to write the plot pair)
- Companion test: `tests/test_bc.py::test_prescribed_core_flux_slows_cmb_cooling` verifies that core heat flux slows cooling at the core-mantle boundary.

**Reference**: Analytical identity: the constant-entropy boundary writes ic_surface_entropy (2550 J/kg/K) onto the surface basic node, preserved through the steady-state energy solve into the initial output.
## Prescribed-flux BC echoes value and inverts emissivity

**Tolerance**: rel 1e-10 (assigned, not solved).
**Why this check exists.** With a prescribed surface flux, the reported flux must equal the prescribed value and the emissivity must be its grey-body inversion. A wrong boundary-condition (BC) branch or a wrong inversion gives a wrong reported flux or emissivity without an error message.

**Command**: `make`, then `pytest -o addopts= tests/test_bc.py::test_constant_entropy_bc_pins_the_initial_surface`
**What it tests.** The test evaluates boundary condition SURFACE_BC 4, where the surface flux equals the prescribed value. The reported emissivity must equal the grey-body inversion value. The test guards against wrong dispatch, confirming that the grey-body flux at the surface temperature differs from the prescribed value by more than 10 percent, and the emissivity differs from 1 by more than 0.5. The flux must be positive, and lie between 1e2 and 1e9 W/m^2. Sibling test `test_mantle_cools_from_the_top_between_surface_and_cmb` asserts temperature ordering across the mantle, and near-zero surface pressure.

**Discrimination guards**: The default configuration's surface value (2599.7 J/kg/K from the 2600 adiabat) sits about 50 J/kg/K away; positivity and table-range bounds on the core-side value after the steady rebalance. The sibling test `test_prescribed_core_flux_slows_cmb_cooling` (same file) compares against the core-cooling baseline and discriminates the CORE_BC dispatch by the sign of the CMB entropy difference and the doubled CMB heat flux.
**How we know the right answer.** Under SURFACE_BC 4, reproducing the prescribed flux is an analytical identity. The test checks the flux echo to rel 1e-9, and the inverted emissivity to rel 1e-6.

**Why it matters.** PROTEUS passes the atmospheric radiative flux into SPIDER using option `-SURFACE_BC 4` and `-surface_bc_value`, so SPIDER must echo this flux exactly.

**Known limit.** The test reads only the reported `Fatm` and emissivity; it does not check that the interior total flux at the surface (`Jtot` at node 0, set in `bc.c`) uses the prescribed value. `SURFACE_BC` is read in `atmosphere.c`, `bc.c` and `energy.c`.

![Reported surface flux of the prescribed-flux run against the grey-body law with emissivity 1 and with the reported emissivity](../assets/validation/bc_prescribed_flux.svg#only-light){ width="80%" }
![Reported surface flux of the prescribed-flux run against the grey-body law with emissivity 1 and with the reported emissivity](../assets/validation/bc_prescribed_flux_dark.svg#only-dark){ width="80%" }

**How to read the plot.** The x-axis is the surface temperature and the y-axis the surface flux on a log scale. The dotted line is the prescribed flux, 1e5 W/m$^2$, and the circle the asserted surface flux Fatm (relative 1e-9) at the reported surface temperature. The solid line is the grey-body law with emissivity 1; the test requires it to be more than 10 % away at that temperature. The dashed line is the grey-body law with the inverted emissivity, about 0.12, asserted to relative 1e-6. A pass means that the circle sits on the dotted and the dashed line, far below the solid one.

??? info "Test details"
- Reference: Analytical identity: with SURFACE_BC 4 the surface flux equals the prescribed value, and the reported emissivity is its grey-body inversion.
- Tolerance: rel 1e-9 on the flux echo; rel 1e-6 on the emissivity inversion.
- Test id: `tests/test_bc.py::test_prescribed_flux_bc_echoes_value_and_inverts_emissivity`
- Command: `make`, then `pytest -o addopts= tests/test_bc.py::test_prescribed_flux_bc_echoes_value_and_inverts_emissivity` (add `--validation-plots` to write the plot pair)
- Companion test: `tests/test_bc.py::test_mantle_cools_from_the_top_between_surface_and_cmb` verifies that the core-mantle boundary remains hotter than the surface.

## Related code

- `bc.c`: `set_surface_flux_from_atmosphere`, `set_surface_entropy_constant`, `set_cmb_entropy_constant`.
- `tests/test_bc.py`: `test_prescribed_flux_bc_echoes_value_and_inverts_emissivity`, `test_constant_entropy_bc_pins_the_initial_surface`.
Loading
Loading