zhl16 0.1.0

A no_std Rust implementation of core Bühlmann ZHL-16 tissue loading primitives.
Documentation
# zhl16

`zhl16` is a small `#![no_std]` Rust crate for core Bühlmann ZHL-16 tissue loading primitives.

The crate currently provides:

- 16-compartment nitrogen and helium tissue loading.
- ZHL-16C tissue constants loaded from `data/zhl16c.csv` at compile time.
- Gas mixture handling for oxygen, helium, and derived nitrogen fractions.
- Maximum operating pressure and equivalent narcotic pressure helpers.
- Per-compartment safe ambient pressure calculation from a gradient factor.
- Lightweight unit wrappers for pressure, time, pressure rate, and frequency.

This is not a complete dive computer. It does not currently provide dive profile planning, ascent scheduling, decompression stop generation, CNS/OTU tracking, gas switching, validation against dive-computer reference profiles, or safety policy around user input.

## Installation

Add the crate to your `Cargo.toml`:

```toml
[dependencies]
zhl16 = "0.1"
```

## Example

```rust
use zhl16::{BodyState, GasMixture, Pressure, PressureUnit, Time, TimeUnit};

let mut body = BodyState::new(
    Pressure::new(0.79, PressureUnit::Bar).expect("valid pressure"),
    Pressure::zero(),
);

let trimix_10_20 = GasMixture::new(0.10, 0.20).expect("valid gas mixture");

body.evolve(
    Pressure::new(2.0, PressureUnit::Bar).expect("valid pressure"),
    &trimix_10_20,
    Time::new(60.0, TimeUnit::Seconds).expect("valid time"),
)
.expect("positive time step");

let first_compartment = &body.compartments()[0];
let n2 = first_compartment
    .get_nitrogen_pressure()
    .to(PressureUnit::Bar);
let he = first_compartment
    .get_helium_pressure()
    .to(PressureUnit::Bar);
let safe_pressure = first_compartment
    .safe_ambient_pressure(0.85)
    .expect("valid gradient factor")
    .to(PressureUnit::Bar);

assert!(n2 > 0.79);
assert!(he > 0.0);
let _ = safe_pressure;
```

## Gas Mixtures

`GasMixture::new(oxygen_fraction, helium_fraction)` stores oxygen and helium fractions. Nitrogen is derived as:

```text
1.0 - oxygen_fraction - helium_fraction
```

The constructor returns `Result<GasMixture, GasMixtureError>` if either supplied fraction is not finite, oxygen is zero, either fraction is negative, or oxygen plus helium is greater than `1.0`.

```rust
use zhl16::{Gas, GasMixture, GasMixtureError, Pressure, PressureUnit};

fn main() -> Result<(), GasMixtureError> {
    let nitrox_32 = GasMixture::new(0.32, 0.0)?;
    assert_eq!(nitrox_32.helium_fraction(), 0.0);
    assert!((nitrox_32.inert_fraction(Gas::Nitrogen) - 0.68).abs() < 1e-12);

    let mod_pressure = nitrox_32
        .max_operational_pressure(
            Pressure::new(1.4, PressureUnit::Bar).expect("valid pressure"),
        )?
        .to(PressureUnit::Bar);

    assert!((mod_pressure - 4.375).abs() < 1e-12);

    Ok(())
}
```

## Tissue Constants

The ZHL-16-C constants live in `data/zhl16c.csv`. A build script validates that file and generates the Rust `TISSUE_CONSTANTS` array at compile time.

This keeps the runtime crate `no_std`; the CSV parsing happens only during compilation.

## Units

The crate uses simple typed quantity wrappers:

- `Pressure`
- `Time`
- `PressureRate`
- `Frequency`

Each quantity stores a base-unit `f64` internally and converts through unit enums such as `PressureUnit::Bar` and `TimeUnit::Seconds`.

Public quantity constructors return `Result<_, QuantityError>` and reject `NaN` values.

## Safety

Do not use this crate as the sole basis for real dive planning or life-support decisions. The API is still early and intentionally exposes low-level model primitives rather than a validated decompression algorithm.

## License

Licensed under MIT license