aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/schedule.c
diff options
context:
space:
mode:
authorGravatar Tim Segers <tsegers@pm.me>2022-09-29 19:12:32 +0200
committerGravatar Tim Segers <tsegers@pm.me>2022-09-30 11:35:37 +0200
commitb5dfedd376c2ac89f7c3d14a6f5db0b5ca1d994d (patch)
treedebac6d237969a0d2fa09fb5b0544714d902760c /schedule.c
parent98d9fc18c782d8fe78e4cd45d807da10ffd0e15d (diff)
downloadopendeco-b5dfedd376c2ac89f7c3d14a6f5db0b5ca1d994d.tar.gz
Initial commitv0.1
Diffstat (limited to 'schedule.c')
-rw-r--r--schedule.c227
1 files changed, 227 insertions, 0 deletions
diff --git a/schedule.c b/schedule.c
new file mode 100644
index 0000000..5bfc1d5
--- /dev/null
+++ b/schedule.c
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: MIT-0 */
+
+#include <assert.h>
+#include <math.h>
+
+#include "schedule.h"
+
+#define SAFETY_STOP_DEPTH (abs_depth(msw_to_bar(6)))
+#define SWITCH_INTERMEDIATE 1
+
+const gas_t *best_gas(const double depth, const gas_t *gasses, const int nof_gasses)
+{
+ const gas_t *best = NULL;
+ double mod_best = -1;
+
+ for (int i = 0; i < nof_gasses; i++) {
+ double mod = gas_mod(&gasses[i]);
+
+ if (depth <= mod && (mod_best == -1 || mod < mod_best)) {
+ best = &gasses[i];
+ mod_best = mod;
+ }
+ }
+
+ return best;
+}
+
+const gas_t *next_gas(const double depth, const gas_t *gasses, const int nof_gasses)
+{
+ const gas_t *next = NULL;
+ double mod_best = 0;
+
+ for (int i = 0; i < nof_gasses; i++) {
+ double mod = gas_mod(&gasses[i]);
+
+ if (depth > mod && mod > mod_best) {
+ next = &gasses[i];
+ mod_best = mod;
+ }
+ }
+
+ return next;
+}
+
+int direct_ascent(const decostate_t *ds, const double depth, const double time, const gas_t *gas)
+{
+ decostate_t ds_ = *ds;
+ assert(ds_.firststop == -1);
+
+ add_segment_ascdec(&ds_, depth, SURFACE_PRESSURE, time, gas);
+
+ return ceiling(&ds_, ds_.gfhi) <= SURFACE_PRESSURE;
+}
+
+void simulate_dive(decostate_t *ds, waypoint_t *waypoints, const int nof_waypoints, segment_callback_t seg_cb)
+{
+ double depth = SURFACE_PRESSURE;
+ double runtime = 0;
+
+ for (int i = 0; i < nof_waypoints; i++) {
+ double d = waypoints[i].depth;
+ double t = waypoints[i].time;
+ const gas_t *g = waypoints[i].gas;
+
+ if (d != depth) {
+ runtime += add_segment_ascdec(ds, depth, d, t, g);
+ depth = d;
+ } else {
+ runtime += add_segment_const(ds, d, t, g);
+ }
+
+ seg_cb((waypoint_t){.depth = d, .time = t, .gas = g}, SEG_DIVE);
+ }
+}
+
+double calc_ndl(decostate_t *ds, const double depth, const double ascrate, const gas_t *gas)
+{
+ decostate_t ds_ = *ds;
+ double ndl = 0;
+
+ while (ndl < 360) {
+ double tmp = add_segment_const(&ds_, depth, 1, gas);
+
+ if (!direct_ascent(&ds_, depth, gauge_depth(depth) / ascrate, gas))
+ break;
+
+ ndl += tmp;
+ }
+
+ return ndl;
+}
+
+void extend_to_ndl(decostate_t *ds, const double depth, const double ascrate, const gas_t *gas,
+ segment_callback_t seg_cb)
+{
+ double ndl = calc_ndl(ds, depth, ascrate, gas);
+
+ /* add segment to reach ndl */
+ if (ndl) {
+ add_segment_const(ds, depth, ndl, gas);
+ seg_cb((waypoint_t){.depth = depth, .time = ndl, .gas = gas}, SEG_NDL);
+ }
+
+ /* either ascend directly or make a safety stop */
+ if (depth < SAFETY_STOP_DEPTH || ds->max_depth < abs_depth(msw_to_bar(10))) {
+ /* surface */
+ add_segment_ascdec(ds, depth, SURFACE_PRESSURE, gauge_depth(depth) / ascrate, gas);
+ seg_cb((waypoint_t){.depth = SURFACE_PRESSURE, .time = gauge_depth(depth) / ascrate, .gas = gas}, SEG_SURFACE);
+ } else {
+ /* ascend to safety stop */
+ add_segment_ascdec(ds, depth, SAFETY_STOP_DEPTH, (depth - SAFETY_STOP_DEPTH) / ascrate, gas);
+ seg_cb((waypoint_t){.depth = SAFETY_STOP_DEPTH, .time = (depth - SAFETY_STOP_DEPTH) / ascrate, .gas = gas},
+ SEG_TRAVEL);
+
+ /* stop for 3 minutes */
+ add_segment_const(ds, SAFETY_STOP_DEPTH, 3, gas);
+ seg_cb((waypoint_t){.depth = SAFETY_STOP_DEPTH, .time = 3, .gas = gas}, SEG_SAFETY_STOP);
+
+ /* surface */
+ add_segment_ascdec(ds, SAFETY_STOP_DEPTH, SURFACE_PRESSURE, gauge_depth(SAFETY_STOP_DEPTH) / ascrate, gas);
+ seg_cb((waypoint_t){.depth = SURFACE_PRESSURE, .time = gauge_depth(SAFETY_STOP_DEPTH) / ascrate, .gas = gas},
+ SEG_SURFACE);
+ }
+}
+
+decoinfo_t calc_deco(decostate_t *ds, const double start_depth, const gas_t *start_gas, const gas_t *deco_gasses,
+ const int nof_gasses, segment_callback_t seg_cb)
+{
+ decoinfo_t ret = {.tts = 0, .ndl = 0};
+
+ /* setup start parameters */
+ double depth = start_depth;
+ const gas_t *gas = start_gas;
+
+ const double asc_per_min = msw_to_bar(9);
+
+ /* check if direct ascent is possible */
+ if (direct_ascent(ds, depth, gauge_depth(depth) / asc_per_min, gas)) {
+ ret.ndl = calc_ndl(ds, depth, asc_per_min, gas);
+ return ret;
+ }
+
+ /* determine first stop */
+ double current_gf = get_gf(ds, depth);
+
+ if (ds->firststop == -1)
+ ds->firststop = ceiling(ds, current_gf);
+
+ /* switch to best deco gas if there is one available */
+ const gas_t *best = best_gas(depth, deco_gasses, nof_gasses);
+
+ if (best)
+ gas = best;
+
+ /* alternate between ascending and stopping */
+ for (;;) {
+ /* ascend */
+ for (;;) {
+ double stopdep = ceiling(ds, current_gf);
+ const gas_t *next = next_gas(depth, deco_gasses, nof_gasses);
+
+ if (SWITCH_INTERMEDIATE && next) {
+ /* determine switch depth */
+ double switch_depth = round_ceiling(ds, next->mod) - ds->ceil_multiple;
+ assert(switch_depth <= next->mod);
+
+ if (switch_depth > stopdep) {
+ /* ascend to switch depth */
+ ret.tts += add_segment_ascdec(ds, depth, switch_depth, (depth - switch_depth) / asc_per_min, gas);
+ seg_cb(
+ (waypoint_t){.depth = switch_depth, .time = (depth - switch_depth) / asc_per_min, .gas = gas},
+ SEG_TRAVEL);
+
+ depth = switch_depth;
+ current_gf = get_gf(ds, depth);
+
+ /* switch gas */
+ gas = next;
+
+ add_segment_const(ds, switch_depth, 1, gas);
+ seg_cb((waypoint_t){.depth = depth, .time = 1, .gas = gas}, SEG_GAS_SWITCH);
+
+ continue;
+ }
+ }
+
+ ret.tts += add_segment_ascdec(ds, depth, stopdep, (depth - stopdep) / asc_per_min, gas);
+
+ if (stopdep <= SURFACE_PRESSURE)
+ seg_cb((waypoint_t){.depth = stopdep, .time = (depth - stopdep) / asc_per_min, .gas = gas},
+ SEG_SURFACE);
+ else
+ seg_cb((waypoint_t){.depth = stopdep, .time = (depth - stopdep) / asc_per_min, .gas = gas},
+ SEG_TRAVEL);
+
+ depth = stopdep;
+ current_gf = get_gf(ds, depth);
+
+ /* if the ceiling moved while we ascended, keep ascending */
+ if (depth > ceiling(ds, current_gf))
+ continue;
+
+ break;
+ }
+
+ /* terminate if we surfaced */
+ if (depth <= SURFACE_PRESSURE)
+ break;
+
+ /* switch to better gas if available */
+ const gas_t *best = best_gas(depth, deco_gasses, nof_gasses);
+
+ if (best)
+ gas = best;
+
+ /* stop */
+ double stoplen = 0;
+
+ while (ceiling(ds, current_gf) == depth)
+ stoplen += add_segment_const(ds, depth, 1, gas);
+
+ ret.tts += stoplen;
+ seg_cb((waypoint_t){.depth = depth, .time = stoplen, .gas = gas}, SEG_DECO_STOP);
+ }
+
+ return ret;
+}