Deco models

Model contract

Decompression model base classes.

Each deco model defines its own DecoState subclass — the typed, copyable snapshot of everything the model knows at an instant (tissue tensions, ceiling, …). Models are generic over their state type, so ZHL16C.integrate_segment(...) returns a ZHL16State, not a bare dict. State snapshots are what the result/report layer stores at checkpoints and what counterfactual queries (TTS at time t) resume from.

class diveplan.models.base.BaseDecoModel(*args, **kwargs)[source]

Bases: ABC, Generic

Base class for all decompression models.

Generic over the model’s DecoState subclass: each model type processes and returns its own state type.

Parameters:
property name: str

Display name of this model instance, conservatism included.

Defaults to the registry NAME. Models with a conservatism setting (gradient factors, VPM conservatism level) append it, so the name alone says how a schedule was computed — e.g. "zhl16c GF 30/70" or "vpmb +3".

integrate_segment(segment)[source]

Integrate a dive segment into the model and return the state after it.

Numerical scheme: rectangle rule — each sample step is integrated at the pressure of its end point over the actual elapsed dt (the final step may be shorter than the sample rate). Models with an analytic solution for linear pressure change (Schreiner) should override this.

Parameters:

segment (DiveSegment)

Return type:

TypeVar(StateT, bound= DecoState)

get_state()[source]

Snapshot the current model state (public accessor).

Return type:

TypeVar(StateT, bound= DecoState)

integrate(pressure, gas, dt)[source]

Advance the model by a single step at the given pressure and gas.

Public stepwise entry point for consumers that drive their own sample loop (result layer, planner); integrate_segment remains the segment-level API.

Parameters:
Return type:

None

abstractmethod get_ceiling()[source]

Return the current (conservative) ceiling.

Anything the ceiling depends on beyond tissue state — gradient factors, conservatism level — is instance configuration, baked in at construction. Overrides keep this exact signature.

Return type:

Pressure

get_ascent_ceiling(target, first_stop=None)[source]

Ceiling for testing an ascent to target during staged deco.

The ascent planner calls this — never a model-specific API — so models whose tolerance evolves over the ascent can express that here: Bühlmann interpolates its gradient factor toward target, and VPM-B’s Boyle/CVA compensation will land here too. first_stop is the first (deepest) stop of the ascent being planned, or None while it is not yet known.

Default: the plain get_ceiling(), which is correct for any model whose tolerance does not depend on ascent context.

Parameters:
Return type:

Pressure

abstractmethod set_state(state)[source]

Restore the model to a previously snapshotted state (lossless).

Together with copy(), this is the checkpointing contract the result layer relies on for state-at-time and counterfactual (TTS) queries.

Parameters:

state (TypeVar(StateT, bound= DecoState))

Return type:

None

abstractmethod copy()[source]

Independent clone with identical configuration and current state.

Return type:

Self

class diveplan.models.base.DecoState[source]

Bases: object

Base class for model-specific decompression state snapshots.

Subclasses should be immutable value objects (frozen dataclass or __slots__ with guards) so they can be checkpointed, compared, and resumed from without defensive copying.

Bühlmann engine and helpers

Generic Bühlmann decompression engine.

All Bühlmann-family models share the same algorithm — parallel Haldane compartments integrated toward alveolar inert-gas pressures, with a ceiling from the a/b M-value line (optionally scaled by Baker gradient factors). Variants differ only in their coefficient tables: number of compartments, half-times, and a/b values.

BuhlmannModel implements the whole algorithm; a concrete model (e.g. ZHL16C) supplies only NAME and the six class-level tables. Table consistency is validated at class definition time. Running without gradient factors is the default (Gradient(1.0, 1.0) — raw Bühlmann); pass a Gradient for GF.

class diveplan.models.buhlmann.model.BuhlmannModel(gradient=None)[source]

Bases: BaseDecoModel[BuhlmannState]

Bühlmann algorithm over a subclass-supplied coefficient table.

Subclasses define NAME and the six tables (half-times in minutes, a in bar, b dimensionless), all of equal length:

class ZHL16C(BuhlmannModel):
    NAME = "zhl16c"
    N2_HALF_TIMES = (5.0, 8.0, ...)
    ...

Fresh instances start at surface equilibrium on air. The sample rate is read from DiveConfig.current().planning.sample_rate_s at construction.

Parameters:

gradient (Gradient | str | None) – GF pair — a Gradient or the usual string notation ("30/70", percentages). Defaults to raw Bühlmann (GF 100/100).

property name: str

Registry name plus gradient factors — e.g. "zhl16c GF 30/70".

property compartment_count: int

Number of tissue compartments in this model’s table.

get_ceiling()[source]

Ceiling at the instance’s GF-low — the conservative bound.

A ceiling above surface pressure means decompression stops are required. The gradient pair is instance configuration; ceilings under a different GF come from an instance built with it, not a parameter. Ascent-context interpolation is get_ascent_ceiling().

Return type:

Pressure

get_ascent_ceiling(target, first_stop=None)[source]

Ceiling with the gradient factor interpolated at target.

GF-low anchors at first_stop (the deepest stop of the ascent being planned), GF-high at the surface. Until the first stop is known the conservative GF-low applies.

Parameters:
Return type:

Pressure

set_state(state)[source]

Restore tissue tensions from a snapshot (exact, lossless).

Raises:

ValueError – If the snapshot’s compartment count does not match this model’s table.

Parameters:

state (BuhlmannState)

Return type:

None

copy()[source]

Independent clone with the same gradient and tissue tensions — for hypothetical ascents (TTS queries) without disturbing the run.

Subclasses that change the __init__ signature must override.

Return type:

Self

class diveplan.models.buhlmann.model.BuhlmannState(tissues)[source]

Bases: DecoState

Frozen snapshot of (ppn2, pphe) tissue tensions in float mbar.

Shared by all Bühlmann-family models; the tuple length equals the model’s compartment count.

Parameters:

tissues (tuple[tuple[float, float], ...])

Shared Bühlmann machinery: gradient factors and Haldane tissue compartments.

Unit conventions

  • Bühlmann a coefficients are published in bar; they are converted to mbar internally. b coefficients are dimensionless.

  • Tissue tensions are tracked as float mbar internally, not integer-mbar Pressure: at fine sample rates (1 s) a slow compartment’s per-step change is far below 1 mbar, and integer quantization would silently freeze it. Pressure remains the boundary type for all inputs and outputs.

  • Alveolar (inspired) inert-gas pressure subtracts water vapor at body temperature: (P_amb 62.7 mbar) · fraction (Bühlmann’s 0.0627 bar).

Gradient factors follow Erik Baker’s convention: GF-low applies at the first (deepest) stop, GF-high at the surface, interpolated linearly in ambient pressure between the two. Factors are fractions (Gradient(0.3, 0.85) is GF 30/85); Gradient(1.0, 1.0) is raw Bühlmann.

class diveplan.models.buhlmann.common.Gradient(gf_low, gf_high)[source]

Bases: object

A gradient-factor pair (Baker GF low/high), as fractions.

GF-low is the allowed fraction of the Bühlmann M-value gradient at the first (deepest) stop; GF-high applies at the surface. factor() interpolates linearly in ambient pressure between the two.

Parameters:
classmethod from_str(s)[source]

Parse the usual GF notation: "30/70", "GF 30/70", "85/85".

Numbers are percentages (the way divers write them); "100/100" is raw Bühlmann.

Raises:

ValueError – If the string is not two /-separated numbers.

Parameters:

s (str)

Return type:

Gradient

factor(pressure, first_stop_pressure)[source]

Gradient factor applicable at pressure.

Returns gf_low at (and below) the first stop, gf_high at (and above) the surface, linear in ambient pressure in between. Continuous over the whole range. Reads the current surface pressure from config.

Parameters:
Return type:

float

class diveplan.models.buhlmann.common.Compartment(*, ht_n2, ht_he, a_n2, a_he, b_n2, b_he, ppn2=None, pphe=None)[source]

Bases: object

One Haldane tissue compartment with Bühlmann a/b coefficients.

Holds the compartment constants (N2/He half-times in minutes, a in bar, b dimensionless) and the current inert-gas tensions. Fresh compartments default to surface equilibrium on air: N2 at (P_surface P_H2O) · 0.79, He at zero.

Parameters:
property ppn2: Pressure

Current N2 tension (rounded to integer mbar for display/compare).

property pphe: Pressure

Current He tension (rounded to integer mbar for display/compare).

property tensions_mbar: tuple[float, float]

Exact (ppn2, pphe) tensions in float mbar — for state snapshots.

set_tensions_mbar(ppn2_mbar, pphe_mbar)[source]

Restore exact tensions from a state snapshot.

Parameters:
Return type:

None

copy()[source]

Independent copy with the same constants and current tensions.

Return type:

Compartment

integrate(pressure, gas, duration)[source]

Haldane exponential update toward the alveolar inert pressures.

P(t+Δt) = P(t) + (P_alv P(t)) · (1 2^(−Δt/ht)) per inert gas, with P_alv = (P_amb P_H2O) · fraction.

Parameters:
Return type:

None

tolerated_ambient_pressure(gradient_factor=1.0)[source]

Minimum ambient pressure this compartment tolerates (Baker formula).

P_amb_tol = (P_t a·GF) / (GF/b + 1 GF) with tension-weighted a/b for the N2/He mix. At GF = 1 this reduces to Bühlmann’s (P_t a) · b. Clamped at zero (a fully desaturated compartment tolerates any ambient pressure).

Parameters:

gradient_factor (float)

Return type:

Pressure

ZHL-16 variants

Bühlmann ZHL-16 variants — coefficient tables over the shared engine.

The whole algorithm lives in BuhlmannModel; a variant is nothing but NAME plus its published tables (half-times in minutes, a in bar, b dimensionless). ZHL-16A/16B can be added the same way once their tables are transcribed and verified.

class diveplan.models.buhlmann.zhl16.ZHL16C(gradient=None)[source]

Bases: BuhlmannModel

Bühlmann ZHL-16C, 16 compartments (1b first-compartment variant).

Sources: Bühlmann, Tauchmedizin; identical values are used by Subsurface/OSTC.

Parameters:

gradient (Gradient | str | None)

class diveplan.models.buhlmann.zhl16.BuhlmannState(tissues)[source]

Bases: DecoState

Frozen snapshot of (ppn2, pphe) tissue tensions in float mbar.

Shared by all Bühlmann-family models; the tuple length equals the model’s compartment count.

Parameters:

tissues (tuple[tuple[float, float], ...])

VPM-B (bubble model)

VPM-B decompression model (Varying Permeability Model, revision B).

A dual-phase (bubble) model: gas loading uses the same Haldane compartments and ZHL-16 half-times as Bühlmann, but the ceiling comes from bubble-nuclei mechanics instead of M-values — the allowable supersaturation gradient of a compartment is set by the size of its gas nuclei, which are crushed smaller by descent (raising the allowed gradient) and regenerate over weeks.

Scope

This class implements the VPM-B core: tissue loading, crushing-pressure tracking (permeable and impermeable regimes), nuclear regeneration, and the ceiling from initial allowable gradients (Baker’s vpmb_start_gradient stage). The Critical Volume Algorithm and Boyle-law stop compensation operate on a full ascent schedule and therefore live in the ascent planner, not here; until the planner applies them, ceilings from this model are the conservative pre-CVA values. CRIT_VOLUME_LAMBDA_BAR_MIN is exported for that later stage.

Constants and formulas follow the Subsurface implementation of Erik Baker’s VPM-B (core/deco.cpp), which uses bar/µm units: gamma quantities are the Yount surface-tension values pre-scaled so that 2·γ/r with r in µm yields bar.

class diveplan.models.vpm.model.VpmB(conservatism=0)[source]

Bases: BaseDecoModel[VpmState]

VPM-B core model (pre-CVA ceilings; see module docstring for scope).

Gas loading runs on the ZHL-16 half-times (the standard VPM-B choice), reusing the Bühlmann Compartment — its a/b coefficients are inert here; the ceiling comes from bubble mechanics.

Parameters:

conservatism (int) – 0 (nominal) … 4 — scales the initial critical radii by (1.0, 1.05, 1.12, 1.22, 1.35).

property name: str

Registry name plus conservatism level — e.g. "vpmb +3".

property crit_radius_n2_um: float

Initial N2 critical radius after conservatism scaling.

property crit_radius_he_um: float

Initial He critical radius after conservatism scaling.

get_ceiling()[source]

Minimum tolerated ambient pressure across compartments (pre-CVA).

Per compartment: total inert tension (+ fixed other-gases pressure) minus the tension-weighted (N2, He) allowable gradient.

Return type:

Pressure

set_state(state)[source]

Restore tissue tensions and bubble bookkeeping from a snapshot.

Parameters:

state (VpmState)

Return type:

None

copy()[source]

Independent clone (same conservatism, tensions, crushing history).

Return type:

Self

class diveplan.models.vpm.model.VpmState(compartments, runtime_min)[source]

Bases: DecoState

Frozen VPM-B snapshot.

Per compartment: (ppn2, pphe, max_crush_n2, max_crush_he, onset_tension) — tensions in float mbar, crushing pressures and onset tension in bar (the bubble-mechanics unit). runtime_min drives nuclear regeneration.

Parameters: