diff options
-rw-r--r-- | deco.c | 35 | ||||
-rw-r--r-- | dive.h | 3 | ||||
-rw-r--r-- | planner.c | 31 | ||||
-rw-r--r-- | pref.h | 4 | ||||
-rw-r--r-- | profile.c | 2 | ||||
-rw-r--r-- | qt-ui/diveplanner.cpp | 6 | ||||
-rw-r--r-- | qt-ui/mainwindow.cpp | 9 | ||||
-rw-r--r-- | qt-ui/mainwindow.h | 1 | ||||
-rw-r--r-- | qt-ui/plannerSettings.ui | 3 | ||||
-rw-r--r-- | qthelper.cpp | 14 | ||||
-rw-r--r-- | qthelper.h | 2 | ||||
-rw-r--r-- | tests/testplan.cpp | 75 | ||||
-rw-r--r-- | tests/testplan.h | 1 |
13 files changed, 160 insertions, 26 deletions
@@ -346,16 +346,35 @@ double solve_cubic(double A, double B, double C) } +// Solve another cubic equation, this time +// x^3 - B x - C == 0 +// Use trigonometric formula for negative discriminants (see Wikipedia for details) + +double solve_cubic2(double B, double C) +{ + double discriminant = 27 * C * C - 4 * cube(B); + if (discriminant < 0.0) { + return 2.0 * sqrt(B / 3.0) * cos(acos(3.0 * C * sqrt(3.0 / B) / (2.0 * B)) / 3.0); + } + + double denominator = pow(9 * C + sqrt(3 * discriminant), 1 / 3.0); + + return pow(2.0 / 3.0, 1.0 / 3.0) * B / denominator + denominator / pow(18.0, 1.0 / 3.0); +} + +// This is a simplified formula avoiding radii. It uses the fact that Boyle's law says +// pV = (G + P_amb) / G^3 is constant to solve for the new gradient G. + double update_gradient(double next_stop_pressure, double first_gradient) { - double first_radius = 2.0 * vpmb_config.surface_tension_gamma / first_gradient; - double A = next_stop_pressure; - double B = -2.0 * vpmb_config.surface_tension_gamma; - double C = (first_stop_pressure.mbar / 1000.0 + 2.0 * vpmb_config.surface_tension_gamma / first_radius) * cube(first_radius); + double B = cube(first_gradient) / (first_stop_pressure.mbar / 1000.0 + first_gradient); + double C = next_stop_pressure * B; - double next_radius = solve_cubic(A, B, C); + double new_gradient = solve_cubic2(B, C); - return 2.0 * vpmb_config.surface_tension_gamma / next_radius; + if (new_gradient < 0.0) + report_error("Negative gradient encountered!"); + return new_gradient; } void boyles_law(double next_stop_pressure) @@ -469,7 +488,6 @@ double add_segment(double pressure, const struct gasmix *gasmix, int period_in_s return tissue_tolerance_calc(dive); } -#ifdef DECO_CALC_DEBUG void dump_tissues() { int ci; @@ -481,7 +499,6 @@ void dump_tissues() printf(" %6.3e", tissue_he_sat[ci]); printf("\n"); } -#endif void clear_deco(double surface_pressure) { @@ -491,6 +508,8 @@ void clear_deco(double surface_pressure) tissue_he_sat[ci] = 0.0; max_n2_crushing_pressure[ci] = 0.0; max_he_crushing_pressure[ci] = 0.0; + n2_regen_radius[ci] = get_crit_radius_N2(); + he_regen_radius[ci] = get_crit_radius_He(); } gf_low_pressure_this_dive = surface_pressure; if (!buehlmann_config.gf_low_at_maxdepth) @@ -830,6 +830,9 @@ struct divedatapoint *create_dp(int time_incr, int depth, struct gasmix gasmix, void dump_plan(struct diveplan *diveplan); #endif bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer); +void calc_crushing_pressure(double pressure); +void vpmb_start_gradient(); + void delete_single_dive(int idx); struct event *get_next_event(struct event *event, const char *name); @@ -939,6 +939,7 @@ bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool int *decostoplevels; int decostoplevelcount; unsigned int *stoplevels = NULL; + int vpmb_first_stop; bool stopping = false; bool pendinggaschange = false; bool clear_to_ascend; @@ -996,6 +997,13 @@ bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool create_dive_from_plan(diveplan, is_planner); return(false); } + calc_crushing_pressure(depth_to_mbar(depth, &displayed_dive) / 1000.0); + nuclear_regeneration(clock); + clear_deco(displayed_dive.surface_pressure.mbar / 1000.0); + vpmb_start_gradient(); + previous_deco_time = 100000000; + deco_time = 10000000; + tissue_tolerance = tissue_at_end(&displayed_dive, cached_datap); displayed_dive.surface_pressure.mbar = diveplan->surface_pressure; @@ -1100,6 +1108,22 @@ bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool bottom_gi = gi; bottom_gas = gas; bottom_stopidx = stopidx; + + // Find first stop used for VPM-B Boyle's law compensation + if (prefs.deco_mode == VPMB) { + vpmb_first_stop = deco_allowed_depth(tissue_tolerance, diveplan->surface_pressure / 1000, &displayed_dive, 1); + if (vpmb_first_stop > 0) { + while (stoplevels[stopidx] > vpmb_first_stop) { + stopidx--; + } + stopidx++; + vpmb_first_stop = stoplevels[stopidx]; + } + first_stop_pressure.mbar = depth_to_mbar(vpmb_first_stop, &displayed_dive); + } else { + first_stop_pressure.mbar = 0; + } + //CVA do { is_final_plan = (prefs.deco_mode == BUEHLMANN) || (previous_deco_time - deco_time < 10); // CVA time converges @@ -1107,7 +1131,7 @@ bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool vpmb_next_gradient(deco_time, diveplan->surface_pressure / 1000.0); previous_deco_time = deco_time; - restore_deco_state(bottom_cache); + tissue_tolerance = restore_deco_state(bottom_cache); depth = bottom_depth; gi = bottom_gi; @@ -1119,7 +1143,6 @@ bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool breaktime = -1; breakcylinder = 0; o2time = 0; - first_stop_pressure.mbar = 0; last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); @@ -1160,8 +1183,6 @@ bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool stopping = true; // Boyles Law compensation - if (first_stop_pressure.mbar == 0) - first_stop_pressure.mbar = depth_to_mbar(depth, &displayed_dive); boyles_law(depth_to_mbar(stoplevels[stopidx], &displayed_dive) / 1000.0); /* Check we need to change cylinder. @@ -1215,8 +1236,6 @@ bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool stopping = true; // Boyles Law compensation - if (first_stop_pressure.mbar == 0) - first_stop_pressure.mbar = depth_to_mbar(depth, &displayed_dive); boyles_law(depth_to_mbar(stoplevels[stopidx], &displayed_dive) / 1000.0); } @@ -75,7 +75,7 @@ struct preferences { short tankbar; short save_userid_local; char *userid; - int ascrate75; + int ascrate75; // All rates in mm / sec int ascrate50; int ascratestops; int ascratelast6m; @@ -90,7 +90,7 @@ struct preferences { char *proxy_pass; bool doo2breaks; bool drop_stone_mode; - bool last_stop; + bool last_stop; // At 6m? bool verbatim_plan; bool display_runtime; bool display_duration; @@ -1209,7 +1209,7 @@ static void plot_string(struct plot_info *pi, struct plot_data *entry, struct me for (k = 0; k < 16; k++) { if (entry->ceilings[k]) { depthvalue = get_depth_units(entry->ceilings[k], NULL, &depth_unit); - put_format(b, translate("gettextFromC", "Tissue %.0fmin: %.0f%s\n"), buehlmann_N2_t_halflife[k], depthvalue, depth_unit); + put_format(b, translate("gettextFromC", "Tissue %.0fmin: %.1f%s\n"), buehlmann_N2_t_halflife[k], depthvalue, depth_unit); } } } diff --git a/qt-ui/diveplanner.cpp b/qt-ui/diveplanner.cpp index 82ecb05dc..7f0d129a2 100644 --- a/qt-ui/diveplanner.cpp +++ b/qt-ui/diveplanner.cpp @@ -228,6 +228,8 @@ void PlannerSettingsWidget::disableDecoElements(int mode) ui.decopo2->setDisabled(true); ui.reserve_gas->setDisabled(false); ui.conservatism_lvl->setDisabled(true); + ui.switch_at_req_stop->setDisabled(true); + ui.min_switch_duration->setDisabled(true); } else if (mode == VPMB) { ui.gflow->setDisabled(true); @@ -238,6 +240,8 @@ void PlannerSettingsWidget::disableDecoElements(int mode) ui.decopo2->setDisabled(false); ui.reserve_gas->setDisabled(true); ui.conservatism_lvl->setDisabled(false); + ui.switch_at_req_stop->setDisabled(false); + ui.min_switch_duration->setDisabled(false); } else if (mode == BUEHLMANN) { ui.gflow->setDisabled(false); @@ -248,6 +252,8 @@ void PlannerSettingsWidget::disableDecoElements(int mode) ui.decopo2->setDisabled(false); ui.reserve_gas->setDisabled(true); ui.conservatism_lvl->setDisabled(true); + ui.switch_at_req_stop->setDisabled(false); + ui.min_switch_duration->setDisabled(false); } } diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index dec550389..4d416d2be 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -1731,10 +1731,10 @@ void MainWindow::setApplicationState(const QByteArray& state) { if (!applicationState.keys().contains(state)) return; - if (currentApplicationState == state) + if (getCurrentAppState() == state) return; - currentApplicationState = state; + setCurrentAppState(state); #define SET_CURRENT_INDEX( X ) \ if (applicationState[state].X) { \ @@ -1762,8 +1762,3 @@ void MainWindow::setApplicationState(const QByteArray& state) { } #undef SET_CURRENT_INDEX } - -bool MainWindow::inPlanner() -{ - return (currentApplicationState == "PlanDive" || currentApplicationState == "EditPlannedDive"); -} diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h index 2d2ea8847..226e9b6ee 100644 --- a/qt-ui/mainwindow.h +++ b/qt-ui/mainwindow.h @@ -243,7 +243,6 @@ private: QHash<QByteArray, WidgetForQuadrant> applicationState; QHash<QByteArray, PropertiesForQuadrant> stateProperties; - QByteArray currentApplicationState; WindowTitleUpdate *wtu; }; diff --git a/qt-ui/plannerSettings.ui b/qt-ui/plannerSettings.ui index 4ebc868af..54ea5762c 100644 --- a/qt-ui/plannerSettings.ui +++ b/qt-ui/plannerSettings.ui @@ -491,6 +491,9 @@ <property name="text"> <string>Conservatism level</string> </property> + <property name="indent"> + <number>25</number> + </property> </widget> </item> <item row="12" column="2"> diff --git a/qthelper.cpp b/qthelper.cpp index 44bb8a33a..de1d893df 100644 --- a/qthelper.cpp +++ b/qthelper.cpp @@ -1274,7 +1274,19 @@ extern "C" void parse_display_units(char *line) qDebug() << line; } +static QByteArray currentApplicationState; + +QByteArray getCurrentAppState() +{ + return currentApplicationState; +} + +void setCurrentAppState(QByteArray state) +{ + currentApplicationState = state; +} + extern "C" bool in_planner() { - return MainWindow::instance()->inPlanner(); + return (currentApplicationState == "PlanDive" || currentApplicationState == "EditPlannedDive"); } diff --git a/qthelper.h b/qthelper.h index 674f036d3..073010d9f 100644 --- a/qthelper.h +++ b/qthelper.h @@ -36,6 +36,8 @@ fraction_t string_to_fraction(const char *str); int getCloudURL(QString &filename); void loadPreferences(); bool parseGpsText(const QString &gps_text, double *latitude, double *longitude); +QByteArray getCurrentAppState(); +void setCurrentAppState(QByteArray state); extern "C" bool in_planner(); #endif // QTHELPER_H diff --git a/tests/testplan.cpp b/tests/testplan.cpp index 88f136602..b506779ad 100644 --- a/tests/testplan.cpp +++ b/tests/testplan.cpp @@ -2,6 +2,7 @@ #include "testplan.h" #include "planner.h" #include "units.h" +#include "qthelper.h" #include <QDebug> #define DEBUG 1 @@ -19,6 +20,18 @@ void setupPrefs() prefs.last_stop = true; } +void setupPrefsVpmb() +{ + prefs = default_prefs; + prefs.ascrate50 = 10000 / 60; + prefs.ascrate75 = prefs.ascrate50; + prefs.ascratestops = prefs.ascrate50; + prefs.ascratelast6m = prefs.ascrate50; + prefs.descrate = 99000 / 60; + prefs.last_stop = false; + prefs.deco_mode = VPMB; +} + void setupPlan(struct diveplan *dp) { dp->salinity = 10300; @@ -45,6 +58,31 @@ void setupPlan(struct diveplan *dp) plan_add_segment(dp, 0, gas_mod(&oxygen, po2, &displayed_dive, M_OR_FT(3,10)).mm, oxygen, 0, 1); } +void setupPlanVpmb(struct diveplan *dp) +{ + dp->salinity = 10300; + dp->surface_pressure = 1013; + dp->bottomsac = 0; + dp->decosac = 0; + + struct gasmix bottomgas = { {180}, {450} }; + struct gasmix ean50 = { {500}, {0} }; + struct gasmix oxygen = { {1000}, {0} }; + pressure_t po2 = { 1600 }; + displayed_dive.cylinder[0].gasmix = bottomgas; + displayed_dive.cylinder[1].gasmix = ean50; + displayed_dive.cylinder[2].gasmix = oxygen; + displayed_dive.surface_pressure.mbar = 1013; + reset_cylinders(&displayed_dive, true); + free_dps(dp); + + int droptime = M_OR_FT(100, 330) * 60 / M_OR_FT(99, 330); + plan_add_segment(dp, droptime, M_OR_FT(100, 330), bottomgas, 0, 1); + plan_add_segment(dp, 60*60 - droptime, M_OR_FT(100, 330), bottomgas, 0, 1); + plan_add_segment(dp, 0, gas_mod(&ean50, po2, &displayed_dive, M_OR_FT(3,10)).mm, ean50, 0, 1); + plan_add_segment(dp, 0, gas_mod(&oxygen, po2, &displayed_dive, M_OR_FT(3,10)).mm, oxygen, 0, 1); +} + void TestPlan::testMetric() { char *cache = NULL; @@ -117,4 +155,41 @@ void TestPlan::testImperial() QCOMPARE(displayed_dive.dc.duration.seconds, 110u * 60u - 2u); } +void TestPlan::testVpmbMetric() +{ + char *cache = NULL; + + setupPrefsVpmb(); + prefs.unit_system = METRIC; + prefs.units.length = units::METERS; + + struct diveplan testPlan = { 0 }; + setupPlanVpmb(&testPlan); + setCurrentAppState("PlanDive"); + + plan(&testPlan, &cache, 1, 0); + +#if DEBUG + free(displayed_dive.notes); + displayed_dive.notes = NULL; + save_dive(stdout, &displayed_dive); +#endif + + // check first gas change to EAN50 at 21m + struct event *ev = displayed_dive.dc.events; + QVERIFY(ev != NULL); + QCOMPARE(ev->gas.index, 1); + QCOMPARE(ev->value, 50); + QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 21000); + // check second gas change to Oxygen at 6m + ev = ev->next; + QVERIFY(ev != NULL); + QCOMPARE(ev->gas.index, 2); + QCOMPARE(ev->value, 100); + QCOMPARE(get_depth_at_time(&displayed_dive.dc, ev->time.seconds), 6000); + // check expected run time of 105 minutes + QCOMPARE(displayed_dive.dc.duration.seconds, 18980u); +} + + QTEST_MAIN(TestPlan) diff --git a/tests/testplan.h b/tests/testplan.h index b35cd75c6..7822129d3 100644 --- a/tests/testplan.h +++ b/tests/testplan.h @@ -8,6 +8,7 @@ class TestPlan : public QObject { private slots: void testImperial(); void testMetric(); + void testVpmbMetric(); }; #endif // TESTPLAN_H |