dive-deco
Version:
A TypeScript implementation of decompression calculation algorithms for scuba diving, featuring Bühlmann ZH-L16C algorithm with gradient factors, gas management, and oxygen toxicity tracking.
148 lines (147 loc) • 6.91 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Compartment = void 0;
const depth_1 = require("./depth");
class Compartment {
no;
minTolerableAmbPressure;
heIp;
n2Ip;
totalIp;
mValueRaw;
mValueCalc;
params;
modelConfig;
constructor(no, params, modelConfig) {
this.no = no;
this.params = params;
this.modelConfig = modelConfig;
// Initialize with air at surface
const initGasCompoundPressures = this.getAirInspiredPartialPressures(depth_1.Depth.zero(), modelConfig.surfacePressure());
this.n2Ip = initGasCompoundPressures.n2;
this.heIp = initGasCompoundPressures.he;
this.totalIp = this.heIp + this.n2Ip;
// Calculate initial values
const [, gfHigh] = modelConfig.gradientFactors();
this.mValueRaw = this.mValue(depth_1.Depth.zero(), modelConfig.surfacePressure(), 100);
this.mValueCalc = this.mValueRaw;
this.minTolerableAmbPressure = this.minTolerableAmbPressureCalc(gfHigh);
}
getAirInspiredPartialPressures(depth, surfacePressure) {
// Air composition: 21% O2, 79% N2, 0% He
const ambientPressure = surfacePressure / 1000 + depth.asMeters() / 10;
const waterVaporPressure = 0.0627; // bar at 37°C
const inspiredPressure = ambientPressure - waterVaporPressure;
return {
o2: 0.21 * inspiredPressure,
n2: 0.79 * inspiredPressure,
he: 0.0 * inspiredPressure
};
}
// Recalculate tissue inert gases saturation and tolerable pressure
recalculate(record, maxGf, surfacePressure) {
const [heInertPressure, n2InertPressure] = this.compartmentInertPressure(record, surfacePressure);
this.heIp = heInertPressure;
this.n2Ip = n2InertPressure;
this.totalIp = heInertPressure + n2InertPressure;
this.mValueRaw = this.mValue(record.depth, surfacePressure, 100);
this.mValueCalc = this.mValue(record.depth, surfacePressure, maxGf);
this.minTolerableAmbPressure = this.minTolerableAmbPressureCalc(maxGf);
}
// Tissue ceiling as depth
ceiling() {
let ceil = (this.minTolerableAmbPressure - (this.modelConfig.surfacePressure() / 1000.0)) * 10.0;
// Cap ceiling at 0 if min tolerable leading compartment pressure depth equivalent negative
if (ceil < 0.0) {
ceil = 0.0;
}
return depth_1.Depth.fromMeters(ceil);
}
// Tissue supersaturation (gf99, surface gf)
supersaturation(surfacePressure, depth) {
const pSurf = surfacePressure / 1000.0;
const pAmb = pSurf + (depth.asMeters() / 10.0);
const mValue = this.mValueRaw;
const mValueSurf = this.mValue(depth_1.Depth.zero(), surfacePressure, 100);
const gf99 = ((this.totalIp - pAmb) / (mValue - pAmb)) * 100.0;
const gfSurf = ((this.totalIp - pSurf) / (mValueSurf - pSurf)) * 100.0;
return { gf99, gfSurf };
}
mValue(depth, surfacePressure, maxGf) {
const weightedZhlParams = this.weightedZhlParams(this.heIp, this.n2Ip);
const [, aCoeffAdjusted, bCoeffAdjusted] = this.maxGfAdjustedZhlParams(weightedZhlParams, maxGf);
const pSurf = surfacePressure / 1000.0;
const pAmb = pSurf + (depth.asMeters() / 10.0);
return aCoeffAdjusted + (pAmb / bCoeffAdjusted);
}
// Tissue inert gases pressure after record
compartmentInertPressure(record, surfacePressure) {
const { depth, time, gas } = record;
const partialPressures = gas.inspiredPartialPressures(depth, surfacePressure);
// Partial pressure of inert gases in inspired gas (adjusted alveoli water vapor pressure)
const heInspiredPp = partialPressures.he;
const n2Inspired = partialPressures.n2;
// Tissue saturation pressure change for inert gases
const hePCompDelta = this.compartmentPressureDeltaHaldane('helium', heInspiredPp, time, this.params.heht);
const n2PCompDelta = this.compartmentPressureDeltaHaldane('nitrogen', n2Inspired, time, this.params.n2ht);
// Inert gases pressures after applying delta P
const heFinal = this.heIp + hePCompDelta;
const n2Final = this.n2Ip + n2PCompDelta;
return [heFinal, n2Final];
}
// Compartment pressure change for inert gas (Haldane equation)
compartmentPressureDeltaHaldane(inertGas, gasInspiredP, time, halfTime) {
const inertGasLoad = inertGas === 'helium' ? this.heIp : this.n2Ip;
// (Pi - Po)(1 - e^(-0.693t/half-time))
const factor = 1.0 - Math.pow(2.0, -(time.asMinutes()) / halfTime);
return (gasInspiredP - inertGasLoad) * factor;
}
// Tissue tolerable ambient pressure using GF slope, weighted Buhlmann ZHL params based on tissue inert gases saturation proportions
minTolerableAmbPressureCalc(maxGf) {
const weightedZhlParams = this.weightedZhlParams(this.heIp, this.n2Ip);
const [, aCoefficientAdjusted, bCoefficientAdjusted] = this.maxGfAdjustedZhlParams(weightedZhlParams, maxGf);
return (this.totalIp - aCoefficientAdjusted) * bCoefficientAdjusted;
}
// Weighted ZHL params (half time, a coefficient, b coefficient) based on N2 and He params and inert gases proportions in tissue
weightedZhlParams(hePp, n2Pp) {
const weightedParam = (heParam, hePp, n2Param, n2Pp) => {
return ((heParam * hePp) + (n2Param * n2Pp)) / (hePp + n2Pp);
};
return [
weightedParam(this.params.heht, hePp, this.params.n2ht, n2Pp),
weightedParam(this.params.hea, hePp, this.params.n2a, n2Pp),
weightedParam(this.params.heb, hePp, this.params.n2b, n2Pp),
];
}
// Adjust zhl params based on max gf
maxGfAdjustedZhlParams(params, maxGf) {
const [halfTime, aCoeff, bCoeff] = params;
const maxGfFraction = maxGf / 100.0;
const aCoefficientAdjusted = aCoeff * maxGfFraction;
const bCoefficientAdjusted = bCoeff / (maxGfFraction - (maxGfFraction * bCoeff) + bCoeff);
return [halfTime, aCoefficientAdjusted, bCoefficientAdjusted];
}
// Get current tissue pressures
get tissurePressures() {
return {
n2: this.n2Ip,
he: this.heIp,
total: this.totalIp,
};
}
zhlParams() {
return this.params;
}
// Clone compartment for simulation purposes
clone() {
const cloned = new Compartment(this.no, this.params, this.modelConfig);
cloned.n2Ip = this.n2Ip;
cloned.heIp = this.heIp;
cloned.totalIp = this.totalIp;
cloned.mValueRaw = this.mValueRaw;
cloned.mValueCalc = this.mValueCalc;
cloned.minTolerableAmbPressure = this.minTolerableAmbPressure;
return cloned;
}
}
exports.Compartment = Compartment;