Last active
September 7, 2022 21:40
-
-
Save mCzolko/a7b7d9179be8f44b55d5a77ac828b3b8 to your computer and use it in GitHub Desktop.
CNS calculation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kotlin.math.abs | |
import kotlin.math.log | |
import kotlin.math.max | |
import kotlin.math.min | |
/** | |
* Shortcuts for the most common operations: | |
* AAP = absolute atmospheric pressure | |
* ppO2 = partial pressure of oxygen | |
* fO2 = oxygen fraction | |
*/ | |
val cnsTable = mapOf( | |
0.5..0.6 to Pair(-1800, 1800), | |
0.6..0.7 to Pair(-1500, 1620), | |
0.7..0.8 to Pair(-1200, 1410), | |
0.8..0.9 to Pair(-900, 1170), | |
0.9..1.1 to Pair(-600, 900), | |
1.1..1.5 to Pair(-300, 570), | |
1.5..1.6 to Pair(-750, 1245) | |
) | |
val po2lo = cnsTable.keys.map { it.start } | |
val po2hi = cnsTable.keys.map { it.endInclusive } | |
val limslp = cnsTable.values.map { it.first } | |
val limint = cnsTable.values.map { it.second } | |
fun findCnsTableEntry(ppO2: Double): Map.Entry<ClosedFloatingPointRange<Double>, Pair<Int, Int>>? = | |
cnsTable.entries.firstOrNull { (range, _) -> ppO2 in range } | |
fun findCnsTableValue(ppO2: Double): Pair<Int, Int>? { | |
val found = findCnsTableEntry(ppO2) | |
if (found != null) { | |
return found.value | |
} | |
return null | |
} | |
/** | |
* @param ppO2 partial pressure of oxygen | |
* @param minutes minutes of dive | |
*/ | |
fun cnsFlat(ppO2: Double, minutes: Double): Double { | |
if (ppO2 < 0.5) { | |
return 0.0 | |
} | |
val (slope, intercept) = findCnsTableValue(ppO2) ?: error("ppO2 is too high!") | |
val timeLimit = slope * ppO2 + intercept | |
return minutes / timeLimit | |
} | |
/** | |
* @param fO2 oxygen fraction | |
* @param depth depth in meters | |
* @param minutes minutes of dive | |
*/ | |
fun cnsFlat(fO2: Double, depth: Double, minutes: Double): Double { | |
val aap = (depth / 10) + 1 | |
val ppO2 = fO2 * aap | |
return cnsFlat(ppO2, minutes) | |
} | |
/** | |
* @param fO2 oxygen fraction | |
* @param startDepth starting depth in meters | |
* @param endDepth end depth in meters | |
* @param rate rate of ascent or descend speed in meters per minute | |
*/ | |
fun cnsDifference(fO2: Double, startDepth: Double, endDepth: Double, rate: Double): Double { | |
val sgTime = (endDepth - startDepth) / rate | |
val startAap = (startDepth / 10) + 1 | |
val endAap = (endDepth / 10) + 1 | |
val maxAap = max(startAap, endAap) | |
val minAap = min(startAap, endAap) | |
val maxPpO2 = maxAap * fO2 | |
val minPpO2 = minAap * fO2 | |
if (maxPpO2 <= .5) { | |
return .0 | |
} | |
val lowPpO2 = if (minPpO2 < .5) .5 else minPpO2 | |
val o2time = sgTime * (maxPpO2 - lowPpO2) / (maxPpO2 - minPpO2) | |
val oTime = mutableMapOf<Int, Double>() | |
val segPpO2 = mutableListOf<Double>() | |
val ppO2o = mutableListOf<Double>() | |
val ppO2f = mutableListOf<Double>() | |
for (i in 0..6) { | |
if ((maxPpO2 > po2lo[i]) && (lowPpO2 <= po2hi[i])) { | |
if ((maxPpO2 >= po2hi[i]) && (lowPpO2 < po2lo[i])) { | |
if (startDepth > endDepth) { | |
ppO2o.add(i, po2hi[i]) | |
ppO2f.add(i, po2lo[i]) | |
} else { | |
ppO2o.add(i, po2lo[i]) | |
ppO2f.add(i, po2hi[i]) | |
} | |
} else if ((maxPpO2 < po2hi[i]) && (lowPpO2 <= po2lo[i])) { | |
if (startDepth > endDepth) { | |
ppO2o.add(i, maxPpO2) | |
ppO2f.add(i, po2lo[i]) | |
} else { | |
ppO2o.add(i, po2lo[i]) | |
ppO2f.add(i, maxPpO2) | |
} | |
} else if ((lowPpO2 > po2lo[i]) && (maxPpO2 >= po2hi[i])) { | |
if (startDepth > endDepth) { | |
ppO2o.add(i, po2hi[i]) | |
ppO2f.add(i, lowPpO2) | |
} else { | |
ppO2o.add(i, lowPpO2) | |
ppO2f.add(i, po2hi[i]) | |
} | |
} else { | |
if (startDepth > endDepth) { | |
ppO2o.add(i, maxPpO2) | |
ppO2f.add(i, lowPpO2) | |
} else { | |
ppO2o.add(i, lowPpO2) | |
ppO2f.add(i, maxPpO2) | |
} | |
} | |
segPpO2.add(i, ppO2f[i] - ppO2o[i]) | |
oTime[i] = o2time * abs(segPpO2[i]) / (maxPpO2 - lowPpO2) | |
} | |
} | |
return oTime.map { | |
val value = it.value | |
if (value == .0) { | |
return@map value | |
} | |
val i = it.key | |
val tLimit = limslp[i] * ppO2o[i] + limint[i] | |
val mk = limslp[i] * (segPpO2[i] / value) | |
1.0 / mk * (log(abs(tLimit + mk * value), 2.0) - log(abs(tLimit), 2.0)) | |
}.sumOf { abs(it) } | |
} | |
fun main() { | |
val fO2 = 0.32 | |
val bottomDepth = 33.5 | |
val list = listOf( | |
cnsDifference(fO2, .0, bottomDepth, 11.0), // descend | |
cnsFlat(fO2, bottomDepth, 22.0), // bottom | |
cnsDifference(fO2, bottomDepth, .0, 1.1), // ascend | |
) | |
println("pO2: $fO2, depth: 0 -> ${bottomDepth}m, speed: 11.0 m/m -> CNS: ${list[0]}") | |
println("pO2: $fO2, depth: ${bottomDepth}m, minutes: 22.0 -> CNS: ${list[1]}") | |
println("pO2: $fO2, depth: ${bottomDepth}m -> 0, speed: 1.1 m/m -> CNS: ${list[2]}") | |
println("Total CNS: ${list.sum() * 100}%") // result is 27.624661704700294 % | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment