Experiment definitions

Grid, Execution, Forcing and Experiment are definitions you can import directly from veris. They run in Python on the host, the computer that controls the calculation, and cannot change in place. They check inputs and combine supplied values with defaults from the model’s named tables of settings and physical constants. They do not create model arrays or choose a JAX backend, the CPU or GPU system that will run the calculation:

from veris import Execution, Experiment, Forcing, Grid

experiment = Experiment(
    grid=Grid(nx=6, ny=8),
    settings={"dtype": "float32", "deltatDyn": 600, "deltatTherm": 600},
    constants={"rhoIce": 910},
    initial_conditions={"hIceMean": 1.0, "hSnowMean": 0.05, "Area": 0.8},
    forcing=Forcing(
        constant={"uWind": 5.0, "ocSalt": 34.7},
        series={"Qnet": [80.0, 100.0, 120.0]},
    ),
    execution=Execution(backend="cpu"),
    steps=3,
)

Use veris.initialize_experiment() to create model arrays and the Simulation lifecycle interface to control runs from Python. Initialization and presets describes preset providers, which supply starting fields, and lower-level array creation. These operations run on the host. Enable JAX x64 before requesting float64 arrays (see Model settings).

Authored and resolved controls

settings, constants, scenario and forcing keep the inputs you wrote, without allowing changes in place. configuration, physical_constants, resolved_settings, resolved_scenario and resolved_forcing expose the checked values that the model will use after applying defaults and overrides. Their priority is shared registry defaults < setup preset/profile < experiment configuration < explicit Python/CLI overrides. Values later in this order win. Veris keeps track of which values you supplied and which came from a preset, so it can update inherited defaults when the experiment changes:

updated = Experiment.builtin("island").with_overrides({
    "scenario": {"islandTimeStep": 300, "islandCooling": 25},
    "settings": {"dtype": "float64"},
})

Mappings, or dictionaries, merge by key. Arrays, lists, single values and allowed None values replace whole values. Explicit numerical settings or forcing, the prescribed external inputs, continue to override scenario defaults. Changing numerical precision re-evaluates inherited defaults from their original values. A rounded value that you supplied explicitly keeps that value.

Declarative files

Experiment.from_file(path, overrides=...) reads version 1 or 2 JSON data files. If the version is omitted, it means 1. Unsupported versions and duplicate or unknown keys produce clear errors. The definition starts with preset defaults, then applies file values and finally explicit Python overrides, using the merge rules above. You can write a runnable definition as:

{
  "schema_version": 1,
  "preset": "island",
  "grid": {"nx": 6, "ny": 8},
  "settings": {"dtype": "float32", "nEVPsteps": 2},
  "scenario": {"islandWindSpeed": 4.0},
  "forcing": {"series": {"Qnet": [80.0, 100.0, 120.0]}},
  "steps": 3
}

Output durations use {"microseconds": integer}. Files you write by hand can also use {"seconds": number} if the duration is an exact number of microseconds. A calendar origin is its starting date and time. Gregorian origins use ISO-format dates without a timezone. Fixed-calendar origins use objects with integer year, month, day and optional time components. See Export modes for exports of supplied inputs and resolved values.

Dimensions, units and precedence

Grid.nx and Grid.ny count global physical cells, the cells in the full modeled area, excluding all halos. Halos are extra cells around each grid piece that hold boundary or neighboring values. Constant and initial-condition arrays are single values or (nx, ny) fields. Forcing series have shape (time,) or (time, nx, ny). A staggered grid places different fields at cell centers, faces and corners. All these fields use the same extents with (x, y) ordering. The numerical State uses two halo cells at every partition boundary, where a partition is a grid piece. For example, Grid(12, 8) on partitions=(3, 2) has local physical extents (4, 4) and packed storage shape (24, 16). Packed storage includes every partition’s halos.

Settings and constants use the defaults and units listed in Model settings and Physical constants. Supplied overrides take precedence over table defaults. Constants inherit the model dtype, its numerical data type. A conflicting constants dtype is rejected. Use Grid for dimensions and Execution for sharding, which splits arrays across devices. Those controls cannot also appear in settings. The resolved objects are available as experiment.configuration and experiment.physical_constants. Simulation binds them for its trajectory; pure advance also accepts dynamic physical-parameter continuations. Their numerical grid extents remain local to each partition.

Initial conditions and forcing use the units in State fields: thickness in meters, temperatures in kelvin, the absolute temperature scale, concentration between zero and one, and heat fluxes in W/m² with Qnet and Qsw positive upward. Concentration is the fraction of a cell covered by ice. A heat flux is heat transferred per unit area per unit time. Initial conditions accept ice fields that evolve during a run. Loads computed from other fields, grid geometry, distances and areas are not independent initial conditions. Forcing accepts prescribed atmospheric and ocean fields rather than evolving ice fields. Definitions copy input arrays and mappings and prevent changes in place, so editing the original inputs does not change the definition.

The accepted field names below are generated from the input sets used by the code. Inputs must contain finite real values, with no infinities or undefined numerical values. Ice/snow thickness, wind speed and salinity are nonnegative. Temperatures in kelvin are positive, and concentration Area lies in [0, 1]. Initial conditions and constant forcing are single values or physical 2D arrays. Series put time in the first dimension, as described above. load_fields accepts any of these names from serial records sampled at a single instant. Geometry, masks and independent grid distances and areas require initialization.

Initial conditions

Name

Units

Description

Area

1

Sea ice cover fraction

TSurf

K

Ice/ snow surface temperature

hIceMean

m

Mean ice thickness

hSnowMean

m

Mean snow thickness

sigma1

N m-1

Stress trace sigma11 + sigma22

sigma12

N m-1

Stress tensor component

sigma2

N m-1

Normal-stress difference sigma11 - sigma22

uIce

m s-1

Zonal ice velocity

vIce

m s-1

Meridional ice velocity

Prescribed forcing

Name

Units

Description

ATemp

K

Atmospheric temperature

LWdown

W m-2

Downward longwave radiation

Qnet

W m-2

Net heat flux out of the ocean

Qsw

W m-2

Surface shortwave heatflux (+ = upwards)

SWdown

W m-2

Downward shortwave radiation

aqh

kg kg-1

Atmospheric specific humidity

evap

m s-1

Evaporation rate over open ocean (freshwater flux, <0 increases salinity)

ocSalt

g kg-1

Ocean surface salinity

precip

m s-1

Precipitation rate (freshwater flux)

runoff

m s-1

Runoff into ocean

snowfall

m s-1

Snowfall rate

ssh_an

m

Sea surface height anomaly

surfPress

Pa

Surface pressure

theta

K

Ocean surface temperature

uOcean

m s-1

Zonal ocean surface velocity

uWind

m s-1

Zonal wind velocity

vOcean

m s-1

Meridional ocean surface velocity

vWind

m s-1

Meridional wind velocity

wSpeed

m s-1

Total wind speed

User-supplied OceanGeometry uses physical arrays without halos. maskT, maskU and maskV have shape (nx, ny, z). The last dimension must be nonempty, and the last level holds the surface masks. Masks mark wet cells with values in [0, 1]; zero means dry. dxt/dxu are positive x-spacing vectors of length nx. dyt/dyu are positive y-spacing vectors of length ny. All spacings are in meters. area_t, area_u and area_v are positive (nx, ny) areas in m². ht is the existing signed bottom elevation in meters; coriolis_t is a frequency in s⁻¹. Initialization uses the established equations for ocean grid distances and areas, closes blocked velocity faces, and computes load and inverse thickness with regularization to handle thin ice. Fractional wet masks retain supplied ice amounts averaged over each cell. Explicit TSurf initial conditions override the grid’s default temperature. A custom runnable experiment requires geometry. Creating arrays from defaults alone does not define one.

Builtins are island, dynamics and growth. Their scenario controls use the maintained setup tables. Grid supplies dimensions; scenario does not also accept nx, ny, steps or dtype. Island initialization keeps initial Qnet/Qsw coupling outputs at zero and queues prescribed heat for each timestep. Growth feeds computed heat fluxes into later steps unless heat is explicitly prescribed. Explicit winds determine wSpeed unless it is supplied separately.

Series override constants with the same field name. forcing.at(0) selects the first step’s prescribed inputs without advancing a position tracker through the series. A missing field keeps its current State value, allowing computed heat fluxes to feed into later steps. Veris checks spatial shapes, series lengths and configured duration before execution. Requesting a position beyond the series raises an exhaustion error.

Physics modes are coupled, dynamics and thermodynamics. Dynamics computes ice motion and transport; thermodynamics computes freezing and melting. Coupled mode combines both and requires equal dynamics and thermodynamic timesteps. Model time uses the active timestep and microsecond precision on the host. Unsupported sub-microsecond values and clock overflow produce clear errors. backend=None honors the caller’s JAX selection. cpu and gpu request resources; initialization determines which devices are available.

Explicit meshes

All builtins and custom geometry, initial fields and forcing use the same public initializer for arrays split across devices. For example, start a fresh Python process with JAX_NUM_CPU_DEVICES=2 before importing JAX, then run:

import jax
from veris import Execution, Experiment, Grid, Simulation

mesh = jax.make_mesh((2, 1), ("x", "y"), devices=jax.devices("cpu"))
experiment = Experiment.builtin(
    "island", grid=Grid(12, 8), steps=2,
    settings={"dtype": "float32", "nEVPsteps": 2},
    execution=Execution(backend="cpu", partitions=(2, 1)),
)
with Simulation.initialize(experiment, mesh=mesh) as simulation:
    result = simulation.run()
    simulation.snapshot("sharded-final.nc", variables=("hIceMean", "Area"))

Input arrays keep their full-grid physical shapes. Each process initializes only the packed State regions it can access. Spatial series split only the spatial dimensions; their time axis stays whole. The caller keeps ownership of the mesh. For multiple processes, initialize JAX distributed services before listing devices and keep responsibility for closing those services. See Parallel dynamics. Complete distributed restart is unsupported. Checks of local CPU sharding do not establish correctness on GPUs or across multiple computers; those need their own verification.

Definitions are constructed only on the host. The pure numerical API consists of veris.advance for one timestep and veris.step() to repeat timesteps without Python side effects. See Scan rollouts and checkpointing for JIT, which compiles functions for execution, and differentiation, which measures how results change with inputs. JAX can compute derivatives with respect to initial State and supplied JAX forcing arrays. Fixed definitions, settings, constants and host input checks stay outside that path.

class veris.model.experiment.Grid(nx: int = 8, ny: int = 12)[source]

Bases: object

Global physical grid extents in (x, y) order, excluding all halos.

Staggered center/face/corner fields share the same storage lengths. Existing Configuration nx/ny still describe local physical cells per partition.

local_shape(partitions: tuple[int, int] = (1, 1)) → tuple[int, int][source]

Return local physical extents after divisibility/minimum checks.

storage_shape(partitions: tuple[int, int] = (1, 1)) → tuple[int, int][source]

Return packed global storage with two halos per partition edge.

class veris.model.experiment.Execution(backend: str | None = None, partitions: tuple[int, int] = (1, 1), distributed: DistributedExecution | None = None, all_devices: bool = False)[source]

Bases: object

Execution resource request, separate from static numerical settings.

backend=None honors the caller’s JAX choice; cpu/gpu requests that backend. Partitions specify the explicit x/y mesh shape, with (1,1) serial by default. Construction never initializes distributed JAX or enumerates devices.

class veris.model.experiment.Experiment(grid: Grid = _DefaultInput.SHARED, settings: Mapping[str, ~typing.Any]=<factory>, constants: Mapping[str, ~typing.Any]=<factory>, physics: str = _DefaultInput.SHARED, geometry: OceanGeometry | None = None, initial_conditions: Mapping[str, ~typing.Any]=<factory>, forcing: Forcing = <factory>, execution: Execution = _DefaultInput.SHARED, steps: int = _DefaultInput.SHARED, output: OutputSettings | None = _DefaultInput.SHARED, preset: str | None = None, profile: str | None = None, scenario: Mapping[str, ~typing.Any]=<factory>)[source]

Bases: object

One experiment’s independent numerical, physical, input and host controls.

settings, constants, scenario and forcing retain authored inputs. Inherited defaults and dtype-normalized values live in configuration, physical_constants, resolved_settings, resolved_scenario and resolved_forcing. with_overrides merges authored values before resolving them again, preserving shared defaults < preset < configuration < explicit overrides precedence. Grid owns global nx/ny; Execution owns use_sharding. Physical constants inherit the numerical dtype. Initial conditions and forcing use global physical arrays without halos, in VARIABLES units. Geometry is host input to runnable initialization, rather than an array default allocation.

physics selects coupled, dynamics or thermodynamics. Coupled execution requires equal dynamics and thermodynamic timesteps; subcycling is not implied. steps specifies the total requested trajectory. output=None disables scheduled output. Definition construction allocates no model/device arrays.

classmethod builtin(name: str, **options: Any) → Experiment[source]

Define a registered scientific preset using its authoritative defaults.

Grid defaults come from the public Grid registry, except the growth column uses its scenario’s 2 by 2 grid. Explicit options override preset settings; scenario contains only experiment-specific coefficients.

classmethod from_file(path: str | Path, *, overrides: Mapping[str, Any] | None = None) → Experiment[source]

Read versioned JSON with registry < file < Python override precedence.

to_dict(*, mode: str = 'resolved') → dict[str, Any][source]

Export pinned resolved values or sparse authored controls as fresh JSON data.

with_overrides(overrides: Mapping[str, Any]) → Experiment[source]

Merge explicit grouped overrides into authored inputs and revalidate.

Shared defaults < preset < authored configuration < these overrides. Nested mappings merge by key; arrays, sequences, None and scalar values replace whole values. The preceding definition remains unchanged.

property step_duration: timedelta

Active timestep rounded only within model precision to microseconds.

Sub-microsecond timesteps and values not representable by the host clock fail clearly; insignificant float32 scalar normalization is accepted.

class veris.model.forcing.Forcing(constant: Mapping[str, ~typing.Any]=<factory>, series: Mapping[str, ~typing.Any]=<factory>)[source]

Bases: object

Immutable constant and timestep-indexed prescribed field values.

Constants accept scalars or (nx, ny) arrays. Series accept (time,) or (time, nx, ny) arrays and override constants of the same name. All series share a nonzero length. Fields absent from both groups keep their current State value, allowing legacy recursive heat fluxes. Values use VARIABLES units, including kelvin and positive-upward Qnet/Qsw in W m-2.

property length: int | None

Number of available steps, or None for unlimited constant forcing.

validate_shape(shape: tuple[int, int]) → None[source]

Check every spatial input against global physical (nx, ny).

at(step: int) → dict[str, Any][source]

Select an absolute zero-based timestep without advancing a cursor.

The returned mapping is a fresh host dictionary; its arrays remain immutable. This method is for host preparation, outside JAX transforms.

Export modes

experiment.to_dict() defaults to mode="resolved" and fixes all values that the model will use, including the profile identity. mode="authored" keeps only the caller’s supplied inputs, leaving omitted groups and values omitted. Reading authored data and then changing dtype/scenario recomputes inherited defaults. Resolved data keeps the deliberately fixed literal values. Both return independent data suitable for JSON. Derived single values are reconstructed from the stored inputs. Returned arrays and containers do not share storage with the definition. Schema version 1 remains supported; profile/distributed/all-device declarations use version 2. Use veris inspect to inspect or export from the command line without selecting devices.