Dive profile

Dive profile: the geometric plan of a dive.

A DiveProfile is a validated sequence of DiveSegment — pure input geometry, deliberately free of model results. It offers three ways of working:

  • building — fluent methods (descend_to("40 m"), stay, switch_gas("ean50")…) and explicit segment surgery, governed by a ProfileBuilderPolicy;

  • validation & repair — problems are returned as pure-data ProfileValidationError descriptors; canonical repairs live on fix/fix_all;

  • timeline — address the profile by runtime (pressure_at(23)), and sample it for integration via iter_samples (the single sampling authority used by models and visualization).

class diveplan.dive.dive_profile.DiveProfile(builder_policy=ProfileBuilderPolicy.RAISE_BAD_PROFILE)[source]

Bases: object

A dive profile consisting of a sequence of dive segments.

Provides builder methods to construct a profile and validation methods to ensure it is continuous and well-formed.

  • A profile should be continuous (pressure continuity, plus gas continuity except across explicit gas switches).

  • A profile should be as simple as possible (no redundant mergeable segments) — not strictly required; enforce with .simplify_profile().

  • Validation strictness during construction is controlled by builder_policy (see ProfileBuilderPolicy). Use .validate_profile() to check the whole profile after building with validation disabled.

Repairs: errors returned by .validate_profile() are pure descriptors. Apply a single repair with .fix(error), or repair everything with .fix_all().

Parameters:

builder_policy (ProfileBuilderPolicy)

property builder_policy: ProfileBuilderPolicy

The active profile builder policy.

copy(*, override_policy=None)[source]

Create a copy of the dive profile.

Segments are immutable value objects, so copying the segment list (a shallow list copy) is sufficient — the segments themselves are shared safely.

Return type:

DiveProfile

Returns:

A new DiveProfile with a fresh segment list and the same (or overridden) builder policy.

Parameters:

override_policy (ProfileBuilderPolicy | None)

property segments: list[DiveSegment]

The list of segments in the profile.

property segment_count: int

The number of segments in the profile.

property runtime: timedelta

Total duration of the profile (sum of all segment durations).

start_time_of_segment(index)[source]

Elapsed runtime at which the segment at index begins.

Raises:

IndexError – If the index is out of range.

Parameters:

index (int)

Return type:

timedelta

segment_index_at(t)[source]

Index of the segment active at runtime t (minutes or timedelta).

Raises:
Parameters:

t (timedelta | float)

Return type:

int

segment_at(t)[source]

The segment active at runtime t (minutes or timedelta).

Parameters:

t (timedelta | float)

Return type:

DiveSegment

pressure_at(t)[source]

Ambient pressure at runtime t (minutes or timedelta), interpolated linearly within the active segment.

Parameters:

t (timedelta | float)

Return type:

Pressure

gas_at(t)[source]

Breathing gas at runtime t (minutes or timedelta).

Parameters:

t (timedelta | float)

Return type:

Gas

iter_samples(interval)[source]

Yield integration steps over the whole profile timeline.

The single sampling authority for model integration and visualization: steps never cross a segment boundary (the last step of each segment is shortened as needed, so boundaries are hit exactly — checkpoints depend on this), zero-duration gas-switch segments yield no step, and each step’s dt sums exactly to the total runtime.

Parameters:

interval (timedelta | float) – Sample step — minutes as a number, or a timedelta.

Raises:

ValueError – If the interval is not strictly positive.

Return type:

Iterator[ProfileSample]

add_segment(segment)[source]

Append a segment to the profile.

Raises:

ProfileValidationError – Under RAISE policy, if the new segment is not continuous with the current last segment.

Parameters:

segment (DiveSegment)

Return type:

DiveProfile

add_segments(segments)[source]

Append multiple segments to the profile (each via add_segment).

Parameters:

segments (list[DiveSegment])

Return type:

DiveProfile

remove_segment_at_index(index)[source]

Remove a segment at a specific index.

Raises:
  • IndexError – If the index is out of range.

  • ProfileValidationError – Under RAISE policy, if removing an interior segment would break continuity at the resulting seam.

Parameters:

index (int)

Return type:

DiveProfile

remove_segment_at_indices(indices)[source]

Remove multiple segments at specific indices.

Parameters:

indices (list[int])

Return type:

DiveProfile

remove_segment(segment)[source]

Remove a segment by value.

Parameters:

segment (DiveSegment)

Return type:

DiveProfile

remove_segments(segments)[source]

Remove multiple segments by value.

Parameters:

segments (list[DiveSegment])

Return type:

DiveProfile

remove_last_segment()[source]

Remove the final segment from the profile.

Return type:

DiveProfile

clear_profile()[source]

Clear all segments from the profile.

Return type:

DiveProfile

insert_segment_at_index(index, segment)[source]

Insert a segment at a specific index.

Raises:

ProfileValidationError – Under RAISE policy, if the insertion would create a discontinuous seam.

Parameters:
Return type:

DiveProfile

insert_segments_at_index(index, segments)[source]

Insert multiple segments at a specific index, in order.

Parameters:
Return type:

DiveProfile

replace_segment_at_index(index, segment)[source]

Replace a segment at a specific index.

Raises:
Parameters:
Return type:

DiveProfile

get_segment(index)[source]

Retrieve a segment by its index.

Raises:

IndexError – If the index is out of bounds.

Parameters:

index (int)

Return type:

DiveSegment

get_last_segment()[source]

Get the last segment in the profile.

Return type:

DiveSegment

get_first_segment()[source]

Get the first segment in the profile.

Return type:

DiveSegment

get_segment_index(segment)[source]

Find the index of a specific segment.

Parameters:

segment (DiveSegment)

Return type:

int

descend_to(depth, *, rate=None, gas=None)[source]

Append a descent from the current position to depth.

Parameters:
  • depth (Pressure | str | float) – Target as a Pressure, a parseable string (“40 m”, “5 bar”), or a bare number in metres.

  • rate (float | None) – Descent rate in m/min. Defaults to the configured planning.descent_rate.

  • gas (Gas | str | None) – Gas for the descent (Gas or name like “ean32”). Defaults to the current gas (air on an empty profile).

Raises:

ValueError – If depth is not below the current position.

Return type:

DiveProfile

ascend_to(depth, *, rate=None, gas=None, kind=Ascent.FORCED_ASCENT)[source]

Append an ascent from the current position to depth.

Parameters:
  • depth (Pressure | str | float) – Target as a Pressure, a parseable string (“21 m”, “2 bar”), or a bare number in metres.

  • rate (float | None) – Ascent rate in m/min. Defaults to the configured planning.ascent_rate.

  • gas (Gas | str | None) – Gas for the ascent (Gas or name). Defaults to the current gas.

  • kind (Ascent) – Ascent kind. Defaults to SegmentKind.ASCENT (forced ascent).

Raises:

ValueError – If depth is not above the current position.

Return type:

DiveProfile

stay(duration, *, gas=None, kind=Constant.BOTTOM)[source]

Append a constant-depth segment at the current position.

Parameters:
  • duration (timedelta | float) – Time to stay — minutes as a number, or a timedelta.

  • gas (Gas | str | None) – Gas for the segment (Gas or name). Defaults to the current gas.

  • kind (Constant) – Constant kind, e.g. SegmentKind.Constant.STOP for a deco stop. Defaults to SegmentKind.CONSTANT (bottom time).

Return type:

DiveProfile

switch_gas(gas)[source]

Append a gas switch at the current position.

The switch takes the configured gas.gas_switch_minutes (zero = instant switch). Switching to the gas already in use is a no-op.

Parameters:

gas (Gas | str) – The new gas (Gas or name like “ean50”).

Return type:

DiveProfile

surface()[source]

Append an ascent from the current position to the surface at the configured ascent rate (alias for add_end_surface_segment).

Raises:

ProfileEmptyError – If the profile has no segments.

Return type:

DiveProfile

property is_valid: bool

Whether the profile is continuous and gas-continuous.

Skips simplicity and start/end-surface checks — an in-progress profile is allowed to be non-simple and to not yet return to the surface.

validate_profile(*, skip_simplicity=False, skip_start_end_segments=True)[source]

Validate the whole profile and return all problems found.

Parameters:
  • skip_simplicity (bool) – If True, do not report mergeable (redundant) seams.

  • skip_start_end_segments (bool) – If True, do not require the profile to start and end at the surface.

Return type:

tuple[ProfileValidationError, ...]

Returns:

A tuple of validation errors, empty if the profile is valid.

fix(error)[source]

Apply the canonical repair for a single validation error.

Repair strategies are dispatched on the error type. Errors flagged fixable = False (empty / too-short profiles) have no repair and raise.

Parameters:

error (ProfileValidationError) – An error returned by .validate_profile().

Return type:

DiveProfile

Returns:

The profile instance (for chaining).

Raises:

ValueError – If the error type is not auto-fixable.

fix_all()[source]

Repeatedly validate and repair until no fixable error remains.

Fixes are applied one at a time and the profile re-validated each pass, because a single repair shifts segment indices and invalidates the indices captured by later errors.

Return type:

DiveProfile

Returns:

The profile instance (for chaining).

static make_transition_segment(segment_a, segment_b)[source]

Create a segment bridging a pressure gap between two segments.

The transition travels from the end of segment_a to the start of segment_b at the configured ascent/descent rate, carrying segment_a’s gas. A gas mismatch between the two segments is a separate concern resolved by a gas switch — it does not block a transition.

Raises:

ValueError – If the segments are already pressure-continuous.

Parameters:
Return type:

DiveSegment

static make_gas_switch_segment(segment_a, segment_b)[source]

Create a gas switch segment between two pressure-continuous segments.

Raises:

ValueError – If the segments are already gas continuous, or are not pressure-continuous (a gas switch happens at a single depth).

Parameters:
Return type:

DiveSegment

simplify_profile()[source]

Merge fully continuous adjacent segments to simplify the profile.

Return type:

DiveProfile

Returns:

A new simplified DiveProfile instance.

fix_continuity()[source]

Insert transition segments to resolve pressure discontinuities (in place).

Return type:

DiveProfile

fix_gas_continuity()[source]

Insert gas switch segments to resolve gas discontinuities (in place).

Return type:

DiveProfile

add_start_surface_segment()[source]

Ensure the profile starts with a descent from the surface.

Raises:

ProfileEmptyError – If the profile has no segments.

Return type:

DiveProfile

add_end_surface_segment()[source]

Ensure the profile ends with an ascent to the surface.

Raises:

ProfileEmptyError – If the profile has no segments.

Return type:

DiveProfile

add_surface_segments()[source]

Ensure the profile both starts and ends at the surface.

Raises:

ProfileEmptyError – If the profile has no segments.

Return type:

DiveProfile

to_dict()[source]

Serialize to a JSON-compatible dict (builder policy + segments).

Return type:

dict[str, Any]

classmethod from_dict(data)[source]

Reconstruct a DiveProfile from to_dict() output.

Segments are loaded exactly as saved, without re-validating seams — a stored in-progress profile must round-trip even if it is not (yet) continuous. Call .validate_profile() after loading if you need the guarantees of the RAISE policy.

Raises:
  • KeyError – If the “segments” field is missing, or an unknown builder policy name is given.

  • ValueError – If a segment entry is malformed.

Parameters:

data (Mapping[str, Any])

Return type:

DiveProfile

to_json(path=None, indent=2)[source]

Serialize to a JSON string, optionally writing to a file.

Parameters:
Return type:

str

classmethod from_json(data=None, path=None)[source]

Deserialize from a JSON string or file path (see from_dict()).

Parameters:
Return type:

DiveProfile

class diveplan.dive.dive_profile.ProfileSample(time, dt, pressure, gas, segment_index)[source]

Bases: NamedTuple

One integration step on the profile’s global timeline.

Represents the half-open interval (time - dt, time]: pressure is the ambient pressure at the end of the step (rectangle rule, matching BaseDecoModel), gas the breathing gas throughout it.

Parameters:
time: timedelta

Alias for field number 0

dt: timedelta

Alias for field number 1

pressure: Pressure

Alias for field number 2

gas: Gas

Alias for field number 3

segment_index: int

Alias for field number 4

exception diveplan.dive.dive_profile.ProfileValidationError(message, *, segment_index=None, segments=())[source]

Bases: ValueError

Base descriptor for a dive profile validation problem.

message

Human-readable description of the problem.

segment_index

Index of the first segment involved, if applicable.

segments

The offending segment(s), if applicable.

fixable

Whether DiveProfile.fix() knows how to repair this error.

Parameters:
exception diveplan.dive.dive_profile.ProfileContinuityError(message, segment_index, segment_a, segment_b)[source]

Bases: ProfileValidationError

Base for problems at the seam between two adjacent segments.

Parameters:
exception diveplan.dive.dive_profile.ProfileSimplicityError(segment_index, segment_a, segment_b)[source]

Bases: ProfileContinuityError

Adjacent segments are fully continuous and could be merged. Fixable via merge.

Parameters:
Return type:

None

exception diveplan.dive.dive_profile.ProfileStartEndError(first_segment, last_segment)[source]

Bases: ProfileValidationError

The profile does not start and end at the surface. Fixable via add_surface_segments().

Parameters:
Return type:

None

exception diveplan.dive.dive_profile.ProfileDepthContinuityError(segment_index, segment_a, segment_b)[source]

Bases: ProfileContinuityError

The end pressure of one segment does not match the start of the next. Fixable via a transition segment.

Parameters:
Return type:

None

exception diveplan.dive.dive_profile.ProfileGasContinuityError(segment_index, segment_a, segment_b)[source]

Bases: ProfileContinuityError

Adjacent segments use different gases without a gas switch. Fixable via a gas switch segment.

Parameters:
Return type:

None

exception diveplan.dive.dive_profile.ProfileEmptyError[source]

Bases: ProfileValidationError

The profile has no segments. Not auto-fixable.

Return type:

None

exception diveplan.dive.dive_profile.ProfileTooShortError(segment_count)[source]

Bases: ProfileValidationError

The profile has fewer than 2 segments (surface → dive → surface). Not auto-fixable.

Parameters:

segment_count (int)

Return type:

None

class diveplan.dive.dive_profile.ProfileBuilderPolicy(*values)[source]

Bases: Enum

Policies for handling validation during dive profile construction.

RAISE_BAD_PROFILE: Each mutating builder call validates only the seam(s)

it touches (O(1)) and raises the specific ProfileValidationError on the first problem. Start/end-surface and simplicity are not enforced here — an in-progress profile is allowed to not yet return to the surface.

ALLOW_BAD_PROFILE: No validation during construction. Call .validate_profile()

and .fix()/.fix_all() before handing the profile to the planner.

AUTOFIX_BAD_PROFILE: After each mutation, transitions and gas switches are

inserted automatically. Convenient, but can produce unexpected geometry.