2013-01-03 05:21:36 +00:00
|
|
|
/* calculate deco values
|
|
|
|
* based on Bühlmann ZHL-16b
|
|
|
|
* based on an implemention by heinrichs weikamp for the DR5
|
2013-01-08 17:29:07 +00:00
|
|
|
* the original file was given to Subsurface under the GPLv2
|
|
|
|
* by Matthias Heinrichs
|
2013-01-03 05:21:36 +00:00
|
|
|
*
|
2013-01-08 17:29:07 +00:00
|
|
|
* The implementation below is a fairly complete rewrite since then
|
|
|
|
* (C) Robert C. Helling 2013 and released under the GPLv2
|
2013-01-03 05:21:36 +00:00
|
|
|
*
|
2013-01-08 17:29:07 +00:00
|
|
|
* add_segment() - add <seconds> at the given pressure, breathing gasmix
|
|
|
|
* deco_allowed_depth() - ceiling based on lead tissue, surface pressure, 3m increments or smooth
|
|
|
|
* set_gf() - set Buehlmann gradient factors
|
|
|
|
* clear_deco()
|
|
|
|
* cache_deco_state()
|
|
|
|
* restore_deco_state()
|
|
|
|
* dump_tissues()
|
2013-01-03 05:21:36 +00:00
|
|
|
*/
|
2013-01-04 04:45:20 +00:00
|
|
|
#include <math.h>
|
2013-01-06 19:13:46 +00:00
|
|
|
#include <string.h>
|
2013-01-03 05:21:36 +00:00
|
|
|
#include "dive.h"
|
2014-10-24 14:40:21 +00:00
|
|
|
#include <assert.h>
|
2015-08-12 10:06:52 +00:00
|
|
|
#include <planner.h>
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2015-08-19 21:12:30 +00:00
|
|
|
extern bool in_planner();
|
|
|
|
|
2015-08-27 12:27:45 +00:00
|
|
|
extern pressure_t first_ceiling_pressure;
|
2015-08-19 21:14:00 +00:00
|
|
|
|
2013-01-03 05:21:36 +00:00
|
|
|
//! Option structure for Buehlmann decompression.
|
|
|
|
struct buehlmann_config {
|
2014-02-28 04:09:57 +00:00
|
|
|
double satmult; //! safety at inert gas accumulation as percentage of effect (more than 100).
|
|
|
|
double desatmult; //! safety at inert gas depletion as percentage of effect (less than 100).
|
|
|
|
unsigned int last_deco_stop_in_mtr; //! depth of last_deco_stop.
|
|
|
|
double gf_high; //! gradient factor high (at surface).
|
|
|
|
double gf_low; //! gradient factor low (at bottom/start of deco calculation).
|
|
|
|
double gf_low_position_min; //! gf_low_position below surface_min_shallow.
|
|
|
|
bool gf_low_at_maxdepth; //! if true, gf_low applies at max depth instead of at deepest ceiling.
|
2013-01-03 05:21:36 +00:00
|
|
|
};
|
2015-08-17 11:52:14 +00:00
|
|
|
|
|
|
|
struct buehlmann_config buehlmann_config = {
|
|
|
|
.satmult = 1.0,
|
|
|
|
.desatmult = 1.01,
|
|
|
|
.last_deco_stop_in_mtr = 0,
|
|
|
|
.gf_high = 0.75,
|
|
|
|
.gf_low = 0.35,
|
|
|
|
.gf_low_position_min = 1.0,
|
|
|
|
.gf_low_at_maxdepth = false
|
|
|
|
};
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2015-07-03 19:30:53 +00:00
|
|
|
//! Option structure for VPM-B decompression.
|
|
|
|
struct vpmb_config {
|
|
|
|
double crit_radius_N2; //! Critical radius of N2 nucleon (microns).
|
|
|
|
double crit_radius_He; //! Critical radius of He nucleon (microns).
|
2015-08-15 13:55:46 +00:00
|
|
|
double crit_volume_lambda; //! Constant corresponding to critical gas volume (bar * min).
|
2015-07-07 10:25:51 +00:00
|
|
|
double gradient_of_imperm; //! Gradient after which bubbles become impermeable (bar).
|
2015-08-29 11:34:19 +00:00
|
|
|
double surface_tension_gamma; //! Nucleons surface tension constant (N / bar = m2).
|
|
|
|
double skin_compression_gammaC; //! Skin compression gammaC (N / bar = m2).
|
2015-07-07 10:25:51 +00:00
|
|
|
double regeneration_time; //! Time needed for the bubble to regenerate to the start radius (min).
|
|
|
|
double other_gases_pressure; //! Always present pressure of other gasses in tissues (bar).
|
2015-07-03 19:30:53 +00:00
|
|
|
};
|
2015-08-17 11:52:14 +00:00
|
|
|
|
|
|
|
struct vpmb_config vpmb_config = {
|
|
|
|
.crit_radius_N2 = 0.55,
|
|
|
|
.crit_radius_He = 0.45,
|
2015-08-18 14:06:39 +00:00
|
|
|
.crit_volume_lambda = 199.58,
|
2015-08-17 11:52:14 +00:00
|
|
|
.gradient_of_imperm = 8.2,
|
2015-08-29 11:34:19 +00:00
|
|
|
.surface_tension_gamma = 0.18137175, // = 0.0179 N/msw
|
|
|
|
.skin_compression_gammaC = 2.6040525, // = 0.257 N/msw
|
2015-08-17 11:52:14 +00:00
|
|
|
.regeneration_time = 20160.0,
|
|
|
|
.other_gases_pressure = 0.1359888
|
|
|
|
};
|
2015-07-03 19:30:53 +00:00
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
const double buehlmann_N2_a[] = { 1.1696, 1.0, 0.8618, 0.7562,
|
|
|
|
0.62, 0.5043, 0.441, 0.4,
|
|
|
|
0.375, 0.35, 0.3295, 0.3065,
|
|
|
|
0.2835, 0.261, 0.248, 0.2327 };
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
const double buehlmann_N2_b[] = { 0.5578, 0.6514, 0.7222, 0.7825,
|
|
|
|
0.8126, 0.8434, 0.8693, 0.8910,
|
|
|
|
0.9092, 0.9222, 0.9319, 0.9403,
|
|
|
|
0.9477, 0.9544, 0.9602, 0.9653 };
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
const double buehlmann_N2_t_halflife[] = { 5.0, 8.0, 12.5, 18.5,
|
|
|
|
27.0, 38.3, 54.3, 77.0,
|
|
|
|
109.0, 146.0, 187.0, 239.0,
|
|
|
|
305.0, 390.0, 498.0, 635.0 };
|
2013-01-03 05:21:36 +00:00
|
|
|
|
|
|
|
const double buehlmann_N2_factor_expositon_one_second[] = {
|
|
|
|
2.30782347297664E-003, 1.44301447809736E-003, 9.23769302935806E-004, 6.24261986779007E-004,
|
|
|
|
4.27777107246730E-004, 3.01585140931371E-004, 2.12729727268379E-004, 1.50020603047807E-004,
|
|
|
|
1.05980191127841E-004, 7.91232600646508E-005, 6.17759153688224E-005, 4.83354552742732E-005,
|
2014-02-28 04:09:57 +00:00
|
|
|
3.78761777920511E-005, 2.96212356654113E-005, 2.31974277413727E-005, 1.81926738960225E-005
|
|
|
|
};
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
const double buehlmann_He_a[] = { 1.6189, 1.383, 1.1919, 1.0458,
|
|
|
|
0.922, 0.8205, 0.7305, 0.6502,
|
|
|
|
0.595, 0.5545, 0.5333, 0.5189,
|
|
|
|
0.5181, 0.5176, 0.5172, 0.5119 };
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
const double buehlmann_He_b[] = { 0.4770, 0.5747, 0.6527, 0.7223,
|
|
|
|
0.7582, 0.7957, 0.8279, 0.8553,
|
|
|
|
0.8757, 0.8903, 0.8997, 0.9073,
|
|
|
|
0.9122, 0.9171, 0.9217, 0.9267 };
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
const double buehlmann_He_t_halflife[] = { 1.88, 3.02, 4.72, 6.99,
|
|
|
|
10.21, 14.48, 20.53, 29.11,
|
|
|
|
41.20, 55.19, 70.69, 90.34,
|
|
|
|
115.29, 147.42, 188.24, 240.03 };
|
2013-01-03 05:21:36 +00:00
|
|
|
|
|
|
|
const double buehlmann_He_factor_expositon_one_second[] = {
|
|
|
|
6.12608039419837E-003, 3.81800836683133E-003, 2.44456078654209E-003, 1.65134647076792E-003,
|
|
|
|
1.13084424730725E-003, 7.97503165599123E-004, 5.62552521860549E-004, 3.96776399429366E-004,
|
|
|
|
2.80360036664540E-004, 2.09299583354805E-004, 1.63410794820518E-004, 1.27869320250551E-004,
|
2014-02-28 04:09:57 +00:00
|
|
|
1.00198406028040E-004, 7.83611475491108E-005, 6.13689891868496E-005, 4.81280465299827E-005
|
|
|
|
};
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2015-08-15 12:36:07 +00:00
|
|
|
const double conservatism_lvls[] = { 1.0, 1.05, 1.12, 1.22, 1.35 };
|
|
|
|
|
2015-08-29 11:43:32 +00:00
|
|
|
/* Inspired gas loading equations depend on the partial pressure of inert gas in the alveolar.
|
|
|
|
* P_alv = (P_amb - P_H2O + (1 - Rq) / Rq * P_CO2) * f
|
|
|
|
* where:
|
|
|
|
* P_alv alveolar partial pressure of inert gas
|
|
|
|
* P_amb ambient pressure
|
|
|
|
* P_H2O water vapour partial pressure = ~0.0627 bar
|
|
|
|
* P_CO2 carbon dioxide partial pressure = ~0.0534 bar
|
|
|
|
* Rq respiratory quotient (O2 consumption / CO2 production)
|
|
|
|
* f fraction of inert gas
|
|
|
|
*
|
|
|
|
* In our calculations, we simplify this to use an effective water vapour pressure
|
|
|
|
* WV = P_H20 - (1 - Rq) / Rq * P_CO2
|
|
|
|
*
|
|
|
|
* Buhlmann ignored the contribution of CO2 (i.e. Rq = 1.0), whereas Schreiner adopted Rq = 0.8.
|
|
|
|
* WV_Buhlmann = PP_H2O = 0.0627 bar
|
|
|
|
* WV_Schreiner = 0.0627 - (1 - 0.8) / Rq * 0.0534 = 0.0493 bar
|
|
|
|
|
|
|
|
* Buhlmann calculations use the Buhlmann value, VPM-B calculations use the Schreiner value.
|
|
|
|
*/
|
|
|
|
#define WV_PRESSURE 0.0627 // water vapor pressure in bar, based on respiratory quotient Rq = 1.0 (Buhlmann value)
|
|
|
|
#define WV_PRESSURE_SCHREINER 0.0493 // water vapor pressure in bar, based on respiratory quotient Rq = 0.8 (Schreiner value)
|
|
|
|
|
2013-01-08 14:37:41 +00:00
|
|
|
#define DECO_STOPS_MULTIPLIER_MM 3000.0
|
2015-08-15 12:28:44 +00:00
|
|
|
#define NITROGEN_FRACTION 0.79
|
2013-01-08 14:37:41 +00:00
|
|
|
|
2013-01-03 05:21:36 +00:00
|
|
|
double tissue_n2_sat[16];
|
|
|
|
double tissue_he_sat[16];
|
|
|
|
int ci_pointing_to_guiding_tissue;
|
2013-01-08 14:37:41 +00:00
|
|
|
double gf_low_pressure_this_dive;
|
2013-01-06 19:13:46 +00:00
|
|
|
#define TISSUE_ARRAY_SZ sizeof(tissue_n2_sat)
|
2013-01-03 07:22:07 +00:00
|
|
|
|
2013-05-30 18:56:00 +00:00
|
|
|
double tolerated_by_tissue[16];
|
2014-09-15 12:09:00 +00:00
|
|
|
double tissue_inertgas_saturation[16];
|
|
|
|
double buehlmann_inertgas_a[16], buehlmann_inertgas_b[16];
|
2013-05-30 18:56:00 +00:00
|
|
|
|
2015-07-03 20:10:12 +00:00
|
|
|
double max_n2_crushing_pressure[16];
|
|
|
|
double max_he_crushing_pressure[16];
|
|
|
|
|
|
|
|
double crushing_onset_tension[16]; // total inert gas tension in the t* moment
|
|
|
|
double n2_regen_radius[16]; // rs
|
|
|
|
double he_regen_radius[16];
|
|
|
|
double max_ambient_pressure; // last moment we were descending
|
|
|
|
|
2015-07-03 21:19:57 +00:00
|
|
|
double allowable_n2_gradient[16];
|
|
|
|
double allowable_he_gradient[16];
|
|
|
|
double total_gradient[16];
|
|
|
|
|
2015-08-15 12:03:08 +00:00
|
|
|
double bottom_n2_gradient[16];
|
|
|
|
double bottom_he_gradient[16];
|
|
|
|
|
|
|
|
double initial_n2_gradient[16];
|
|
|
|
double initial_he_gradient[16];
|
2015-07-03 20:10:12 +00:00
|
|
|
|
2015-08-15 12:36:07 +00:00
|
|
|
double get_crit_radius_He()
|
|
|
|
{
|
|
|
|
if (prefs.conservatism_level <= 4)
|
|
|
|
return vpmb_config.crit_radius_He * conservatism_lvls[prefs.conservatism_level];
|
|
|
|
return vpmb_config.crit_radius_He;
|
|
|
|
}
|
|
|
|
|
|
|
|
double get_crit_radius_N2()
|
|
|
|
{
|
|
|
|
if (prefs.conservatism_level <= 4)
|
|
|
|
return vpmb_config.crit_radius_N2 * conservatism_lvls[prefs.conservatism_level];
|
|
|
|
return vpmb_config.crit_radius_N2;
|
|
|
|
}
|
2015-07-03 20:10:12 +00:00
|
|
|
|
2013-01-08 14:37:41 +00:00
|
|
|
static double tissue_tolerance_calc(const struct dive *dive)
|
2013-01-03 05:21:36 +00:00
|
|
|
{
|
|
|
|
int ci = -1;
|
2013-01-04 07:56:10 +00:00
|
|
|
double ret_tolerance_limit_ambient_pressure = 0.0;
|
2013-01-08 14:37:41 +00:00
|
|
|
double gf_high = buehlmann_config.gf_high;
|
|
|
|
double gf_low = buehlmann_config.gf_low;
|
2014-01-15 18:54:41 +00:00
|
|
|
double surface = get_surface_pressure_in_mbar(dive, true) / 1000.0;
|
Deco artefacts with low GFlow
In a dive, when you choose a very low GFlow (like 5 or 9) and a trimix
with quite some He (12/48 in the example) and descend fast, the ceiling
seems to do strange things in the first minutes of the dive (very very
deep for example or jumping around).
To understand what is going on we have to recall what gradient factors do
in detail: Plain Buehlmann gives you for each tissue a maximal inert gas
pressure that is a straight line when plotted against the ambient
pressure. So for each depth (=ambient pressure) there is a maximally
allowed over-pressure.
The idea of gradient factors is that one does not use all the possible
over-pressure that Buehlmann gives us but only a depth dependent fraction.
GFhigh is the fraction of the possible over-pressure at the surface while
GFlow is the fraction at the first deco stop. In between, the fraction is
linearly interpolated. As the Buehlmann over-pressure is increasing with
depth and typically also the allowed overpressure after applications of
gradient factors increases with depth or said differently: the tissue
saturation has to be lower if the diver wants to ascent.
The main problem is: What is the first stop (where to apply GFlow)? In a
planned dive, we could take the first deco stop, but in a real dive from a
dive computer download it is impossible to say what constitutes a stop and
what is only a slow ascent?
What I have used so far is not exactly the first stop but rather the first
theoretical stop: During all of the dive, I have calculated the ceiling
under the assumption that GFlow applies everywhere (and not just at a
single depth). The deepest of these ceilings I have used as the “first
stop depth”, the depth at which GFlow applies.
Even more, I only wanted to use the information that a diver has during
the dive, so I actually only considered the ceilings in the past (and not
in the future of a given sample).
But this brings with it the problem that early in the dive, in particular
during the descent the lowest ceiling so far is very shallow (as not much
gas has built up in the body so far).
This problem now interferes with a second one: If at the start of the dive
when the all compartments have 790mbar N2 the diver starts breathing a
He-heavy mix (like 12/48) and descents fast the He builds up in the
tissues before the N2 can diffuse out. So right at the start, we already
encounter high tissue loadings.
If now we have a large difference between GFhigh and GFlow but they apply
at very similar depth (the surface and a very shallow depth of the deepest
ceiling (which for a non-decompression dive would be theoretically at
negative depth) so far) it can happen that the linear interpolation as
opposite slope then in the typical case above: The allowed over-pressure
is degreasing with depth, shallower depth do not require lower gas loading
in the tissue (i.e. can be reached after further off-gasing) but but
tolerate higher loadings. In that situation the ceiling disappears (or is
rather a floor).
So far, I got rid of that problem, by stating that the minimum depth for
GFlow was 20m (after all, GFlow is about deep stops, so it should better
not be too shallow). Now the dive reported in ticket #549 takes values to
an extreme in such away that 20m (which is determined by
buehlmann_config.gf_low_position_min in deco.c) was not enough to prevent
this inversion problem (or in a milder form that the interpolation of
gradient factors is in fact an extrapolation with quite extreme values).
This patch that gets rid of the problem for the dive described above but
still it is possible to find (more extreme) parameter choices that lead to
non-realistic ceilings.
Let me close by pointing out that all this is only about the descent, as
it is about too shallow depth for GFlow. So no real deco (i.e. later part
of the dive) is inflicted. This is only about a theoretical ceiling
displayed possibly in the first minutes of a dive. So this is more an
aesthetically than a practical problem.
Fixes #549
Signed-off-by: Robert C. Helling <helling@atdotde.de>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-06-18 15:11:54 +00:00
|
|
|
double lowest_ceiling = 0.0;
|
|
|
|
double tissue_lowest_ceiling[16];
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2015-08-19 21:12:30 +00:00
|
|
|
if (prefs.deco_mode != VPMB || !in_planner()) {
|
2015-08-12 10:06:52 +00:00
|
|
|
for (ci = 0; ci < 16; ci++) {
|
|
|
|
tissue_inertgas_saturation[ci] = tissue_n2_sat[ci] + tissue_he_sat[ci];
|
|
|
|
buehlmann_inertgas_a[ci] = ((buehlmann_N2_a[ci] * tissue_n2_sat[ci]) + (buehlmann_He_a[ci] * tissue_he_sat[ci])) / tissue_inertgas_saturation[ci];
|
|
|
|
buehlmann_inertgas_b[ci] = ((buehlmann_N2_b[ci] * tissue_n2_sat[ci]) + (buehlmann_He_b[ci] * tissue_he_sat[ci])) / tissue_inertgas_saturation[ci];
|
2013-02-08 10:42:34 +00:00
|
|
|
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2015-08-12 10:06:52 +00:00
|
|
|
/* tolerated = (tissue_inertgas_saturation - buehlmann_inertgas_a) * buehlmann_inertgas_b; */
|
2013-01-08 14:37:41 +00:00
|
|
|
|
2015-08-12 10:06:52 +00:00
|
|
|
tissue_lowest_ceiling[ci] = (buehlmann_inertgas_b[ci] * tissue_inertgas_saturation[ci] - gf_low * buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci]) /
|
|
|
|
((1.0 - buehlmann_inertgas_b[ci]) * gf_low + buehlmann_inertgas_b[ci]);
|
|
|
|
if (tissue_lowest_ceiling[ci] > lowest_ceiling)
|
|
|
|
lowest_ceiling = tissue_lowest_ceiling[ci];
|
|
|
|
if (!buehlmann_config.gf_low_at_maxdepth) {
|
|
|
|
if (lowest_ceiling > gf_low_pressure_this_dive)
|
|
|
|
gf_low_pressure_this_dive = lowest_ceiling;
|
|
|
|
}
|
2013-11-20 15:11:22 +00:00
|
|
|
}
|
2015-08-12 10:06:52 +00:00
|
|
|
for (ci = 0; ci < 16; ci++) {
|
|
|
|
double tolerated;
|
|
|
|
|
|
|
|
if ((surface / buehlmann_inertgas_b[ci] + buehlmann_inertgas_a[ci] - surface) * gf_high + surface <
|
|
|
|
(gf_low_pressure_this_dive / buehlmann_inertgas_b[ci] + buehlmann_inertgas_a[ci] - gf_low_pressure_this_dive) * gf_low + gf_low_pressure_this_dive)
|
|
|
|
tolerated = (-buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci] * (gf_high * gf_low_pressure_this_dive - gf_low * surface) -
|
|
|
|
(1.0 - buehlmann_inertgas_b[ci]) * (gf_high - gf_low) * gf_low_pressure_this_dive * surface +
|
|
|
|
buehlmann_inertgas_b[ci] * (gf_low_pressure_this_dive - surface) * tissue_inertgas_saturation[ci]) /
|
|
|
|
(-buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci] * (gf_high - gf_low) +
|
|
|
|
(1.0 - buehlmann_inertgas_b[ci]) * (gf_low * gf_low_pressure_this_dive - gf_high * surface) +
|
|
|
|
buehlmann_inertgas_b[ci] * (gf_low_pressure_this_dive - surface));
|
|
|
|
else
|
|
|
|
tolerated = ret_tolerance_limit_ambient_pressure;
|
|
|
|
|
|
|
|
|
|
|
|
tolerated_by_tissue[ci] = tolerated;
|
|
|
|
|
|
|
|
if (tolerated >= ret_tolerance_limit_ambient_pressure) {
|
|
|
|
ci_pointing_to_guiding_tissue = ci;
|
|
|
|
ret_tolerance_limit_ambient_pressure = tolerated;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// VPM-B ceiling
|
|
|
|
for (ci = 0; ci < 16; ci++) {
|
|
|
|
double tolerated = tissue_n2_sat[ci] + tissue_he_sat[ci] + vpmb_config.other_gases_pressure - total_gradient[ci];
|
|
|
|
if (tolerated >= ret_tolerance_limit_ambient_pressure) {
|
|
|
|
ci_pointing_to_guiding_tissue = ci;
|
|
|
|
ret_tolerance_limit_ambient_pressure = tolerated;
|
|
|
|
}
|
|
|
|
tolerated_by_tissue[ci] = tolerated;
|
2013-01-03 05:21:36 +00:00
|
|
|
}
|
|
|
|
}
|
2013-01-04 07:56:10 +00:00
|
|
|
return ret_tolerance_limit_ambient_pressure;
|
2013-01-03 05:21:36 +00:00
|
|
|
}
|
|
|
|
|
2013-09-26 03:42:19 +00:00
|
|
|
/*
|
|
|
|
* Return buelman factor for a particular period and tissue index.
|
|
|
|
*
|
|
|
|
* We cache the last factor, since we commonly call this with the
|
|
|
|
* same values... We have a special "fixed cache" for the one second
|
|
|
|
* case, although I wonder if that's even worth it considering the
|
|
|
|
* more general-purpose cache.
|
|
|
|
*/
|
|
|
|
struct factor_cache {
|
|
|
|
int last_period;
|
|
|
|
double last_factor;
|
|
|
|
};
|
|
|
|
|
|
|
|
double n2_factor(int period_in_seconds, int ci)
|
|
|
|
{
|
|
|
|
static struct factor_cache cache[16];
|
|
|
|
|
|
|
|
if (period_in_seconds == 1)
|
|
|
|
return buehlmann_N2_factor_expositon_one_second[ci];
|
|
|
|
|
|
|
|
if (period_in_seconds != cache[ci].last_period) {
|
|
|
|
cache[ci].last_period = period_in_seconds;
|
2014-02-28 04:09:57 +00:00
|
|
|
cache[ci].last_factor = 1 - pow(2.0, -period_in_seconds / (buehlmann_N2_t_halflife[ci] * 60));
|
2013-09-26 03:42:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return cache[ci].last_factor;
|
|
|
|
}
|
|
|
|
|
|
|
|
double he_factor(int period_in_seconds, int ci)
|
|
|
|
{
|
|
|
|
static struct factor_cache cache[16];
|
|
|
|
|
|
|
|
if (period_in_seconds == 1)
|
|
|
|
return buehlmann_He_factor_expositon_one_second[ci];
|
|
|
|
|
|
|
|
if (period_in_seconds != cache[ci].last_period) {
|
|
|
|
cache[ci].last_period = period_in_seconds;
|
2014-02-28 04:09:57 +00:00
|
|
|
cache[ci].last_factor = 1 - pow(2.0, -period_in_seconds / (buehlmann_He_t_halflife[ci] * 60));
|
2013-09-26 03:42:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return cache[ci].last_factor;
|
|
|
|
}
|
|
|
|
|
2015-08-15 12:28:44 +00:00
|
|
|
double calc_surface_phase(double surface_pressure, double he_pressure, double n2_pressure, double he_time_constant, double n2_time_constant)
|
|
|
|
{
|
2015-08-29 11:43:32 +00:00
|
|
|
double inspired_n2 = (surface_pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * NITROGEN_FRACTION;
|
2015-08-15 12:28:44 +00:00
|
|
|
|
|
|
|
if (n2_pressure > inspired_n2)
|
|
|
|
return (he_pressure / he_time_constant + (n2_pressure - inspired_n2) / n2_time_constant) / (he_pressure + n2_pressure - inspired_n2);
|
|
|
|
|
|
|
|
if (he_pressure + n2_pressure >= inspired_n2){
|
|
|
|
double gradient_decay_time = 1.0 / (n2_time_constant - he_time_constant) * log ((inspired_n2 - n2_pressure) / he_pressure);
|
|
|
|
double gradients_integral = he_pressure / he_time_constant * (1.0 - exp(-he_time_constant * gradient_decay_time)) + (n2_pressure - inspired_n2) / n2_time_constant * (1.0 - exp(-n2_time_constant * gradient_decay_time));
|
|
|
|
return gradients_integral / (he_pressure + n2_pressure - inspired_n2);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-03 21:19:57 +00:00
|
|
|
void vpmb_start_gradient()
|
|
|
|
{
|
|
|
|
int ci;
|
|
|
|
double gradient_n2, gradient_he;
|
|
|
|
|
|
|
|
for (ci = 0; ci < 16; ++ci) {
|
2015-08-15 12:03:08 +00:00
|
|
|
initial_n2_gradient[ci] = bottom_n2_gradient[ci] = allowable_n2_gradient[ci] = 2.0 * (vpmb_config.surface_tension_gamma / vpmb_config.skin_compression_gammaC) * ((vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma) / n2_regen_radius[ci]);
|
|
|
|
initial_he_gradient[ci] = bottom_he_gradient[ci] = allowable_he_gradient[ci] = 2.0 * (vpmb_config.surface_tension_gamma / vpmb_config.skin_compression_gammaC) * ((vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma) / he_regen_radius[ci]);
|
2015-07-03 21:19:57 +00:00
|
|
|
|
|
|
|
total_gradient[ci] = ((allowable_n2_gradient[ci] * tissue_n2_sat[ci]) + (allowable_he_gradient[ci] * tissue_he_sat[ci])) / (tissue_n2_sat[ci] + tissue_he_sat[ci]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-15 12:28:44 +00:00
|
|
|
void vpmb_next_gradient(double deco_time, double surface_pressure)
|
2015-07-03 21:59:22 +00:00
|
|
|
{
|
|
|
|
int ci;
|
|
|
|
double gradient_n2, gradient_he;
|
|
|
|
double n2_b, n2_c;
|
|
|
|
double he_b, he_c;
|
2015-08-15 12:28:44 +00:00
|
|
|
double desat_time;
|
|
|
|
deco_time /= 60.0;
|
2015-07-03 21:59:22 +00:00
|
|
|
|
|
|
|
for (ci = 0; ci < 16; ++ci) {
|
2015-08-15 12:28:44 +00:00
|
|
|
desat_time = deco_time + calc_surface_phase(surface_pressure, tissue_he_sat[ci], tissue_n2_sat[ci], log(2.0) / buehlmann_He_t_halflife[ci], log(2.0) / buehlmann_N2_t_halflife[ci]);
|
|
|
|
|
2015-08-15 12:03:08 +00:00
|
|
|
n2_b = initial_n2_gradient[ci] + (vpmb_config.crit_volume_lambda * vpmb_config.surface_tension_gamma) / (vpmb_config.skin_compression_gammaC * desat_time);
|
|
|
|
he_b = initial_he_gradient[ci] + (vpmb_config.crit_volume_lambda * vpmb_config.surface_tension_gamma) / (vpmb_config.skin_compression_gammaC * desat_time);
|
2015-07-03 21:59:22 +00:00
|
|
|
|
|
|
|
n2_c = vpmb_config.surface_tension_gamma * vpmb_config.surface_tension_gamma * vpmb_config.crit_volume_lambda * max_n2_crushing_pressure[ci];
|
2015-08-15 12:03:08 +00:00
|
|
|
n2_c = n2_c / (vpmb_config.skin_compression_gammaC * vpmb_config.skin_compression_gammaC * desat_time);
|
2015-07-03 21:59:22 +00:00
|
|
|
he_c = vpmb_config.surface_tension_gamma * vpmb_config.surface_tension_gamma * vpmb_config.crit_volume_lambda * max_he_crushing_pressure[ci];
|
2015-08-15 12:03:08 +00:00
|
|
|
he_c = he_c / (vpmb_config.skin_compression_gammaC * vpmb_config.skin_compression_gammaC * desat_time);
|
|
|
|
|
|
|
|
bottom_n2_gradient[ci] = allowable_n2_gradient[ci] = 0.5 * ( n2_b + sqrt(n2_b * n2_b - 4.0 * n2_c));
|
|
|
|
bottom_he_gradient[ci] = allowable_he_gradient[ci] = 0.5 * ( he_b + sqrt(he_b * he_b - 4.0 * he_c));
|
2015-07-03 21:59:22 +00:00
|
|
|
|
2015-08-15 12:03:08 +00:00
|
|
|
total_gradient[ci] = ((allowable_n2_gradient[ci] * tissue_n2_sat[ci]) + (allowable_he_gradient[ci] * tissue_he_sat[ci])) / (tissue_n2_sat[ci] + tissue_he_sat[ci]);
|
|
|
|
}
|
|
|
|
}
|
2015-07-03 21:59:22 +00:00
|
|
|
|
2015-08-17 19:36:50 +00:00
|
|
|
// A*r^3 - B*r^2 - C == 0
|
|
|
|
// Solved with the help of mathematica
|
|
|
|
|
|
|
|
#define cube(x) (x * x * x)
|
|
|
|
double solve_cubic(double A, double B, double C)
|
|
|
|
{
|
|
|
|
double BA = B/A;
|
|
|
|
double CA = C/A;
|
|
|
|
|
|
|
|
double discriminant = CA * (4 * cube(BA) + 27 * CA);
|
|
|
|
|
|
|
|
// Let's make sure we have a real solution:
|
|
|
|
if (discriminant < 0.0) {
|
|
|
|
// This should better not happen
|
|
|
|
report_error("Complex solution for inner pressure encountered!\n A=%f\tB=%f\tC=%f\n", A, B, C);
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
double denominator = pow(cube(BA) + 1.5 * (9 * CA + sqrt(3.0) * sqrt(discriminant)), 1/3.0);
|
|
|
|
return (BA + BA * BA / denominator + denominator) / 3.0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-08-23 08:48:38 +00:00
|
|
|
// 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.
|
|
|
|
|
2015-08-19 21:14:00 +00:00
|
|
|
double update_gradient(double next_stop_pressure, double first_gradient)
|
2015-08-15 12:03:08 +00:00
|
|
|
{
|
2015-08-27 12:27:45 +00:00
|
|
|
double B = cube(first_gradient) / (first_ceiling_pressure.mbar / 1000.0 + first_gradient);
|
2015-08-23 08:48:38 +00:00
|
|
|
double C = next_stop_pressure * B;
|
2015-08-17 19:36:50 +00:00
|
|
|
|
2015-08-23 08:48:38 +00:00
|
|
|
double new_gradient = solve_cubic2(B, C);
|
2015-08-15 12:03:08 +00:00
|
|
|
|
2015-08-23 08:48:38 +00:00
|
|
|
if (new_gradient < 0.0)
|
|
|
|
report_error("Negative gradient encountered!");
|
|
|
|
return new_gradient;
|
2015-08-15 12:03:08 +00:00
|
|
|
}
|
|
|
|
|
2015-08-19 21:14:00 +00:00
|
|
|
void boyles_law(double next_stop_pressure)
|
2015-08-15 12:03:08 +00:00
|
|
|
{
|
|
|
|
int ci;
|
2015-08-19 21:14:00 +00:00
|
|
|
|
2015-08-21 15:22:07 +00:00
|
|
|
if (!in_planner() || prefs.deco_mode != VPMB)
|
|
|
|
return;
|
|
|
|
|
2015-08-21 15:19:32 +00:00
|
|
|
// This should be a tautology but prevents a numerical instability.
|
2015-08-27 12:27:45 +00:00
|
|
|
if (IS_FP_SAME(next_stop_pressure, first_ceiling_pressure.mbar / 1000.0))
|
2015-08-21 15:19:32 +00:00
|
|
|
return;
|
|
|
|
|
2015-08-27 12:27:45 +00:00
|
|
|
if (!first_ceiling_pressure.mbar)
|
2015-08-19 21:14:00 +00:00
|
|
|
return;
|
2015-08-15 12:03:08 +00:00
|
|
|
for (ci = 0; ci < 16; ++ci) {
|
2015-08-19 21:14:00 +00:00
|
|
|
allowable_n2_gradient[ci] = update_gradient(next_stop_pressure, bottom_n2_gradient[ci]);
|
|
|
|
allowable_he_gradient[ci] = update_gradient(next_stop_pressure, bottom_he_gradient[ci]);
|
2015-07-03 21:59:22 +00:00
|
|
|
|
|
|
|
total_gradient[ci] = ((allowable_n2_gradient[ci] * tissue_n2_sat[ci]) + (allowable_he_gradient[ci] * tissue_he_sat[ci])) / (tissue_n2_sat[ci] + tissue_he_sat[ci]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-03 20:45:29 +00:00
|
|
|
void nuclear_regeneration(double time)
|
|
|
|
{
|
|
|
|
time /= 60.0;
|
|
|
|
int ci;
|
|
|
|
double crushing_radius_N2, crushing_radius_He;
|
|
|
|
for (ci = 0; ci < 16; ++ci) {
|
|
|
|
//rm
|
2015-08-15 12:36:07 +00:00
|
|
|
crushing_radius_N2 = 1.0 / (max_n2_crushing_pressure[ci] / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / get_crit_radius_N2());
|
|
|
|
crushing_radius_He = 1.0 / (max_he_crushing_pressure[ci] / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / get_crit_radius_He());
|
2015-07-03 20:45:29 +00:00
|
|
|
//rs
|
2015-08-15 12:36:07 +00:00
|
|
|
n2_regen_radius[ci] = crushing_radius_N2 + (get_crit_radius_N2() - crushing_radius_N2) * (1.0 - exp (-time / vpmb_config.regeneration_time));
|
|
|
|
he_regen_radius[ci] = crushing_radius_He + (get_crit_radius_He() - crushing_radius_He) * (1.0 - exp (-time / vpmb_config.regeneration_time));
|
2015-07-03 20:45:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-17 19:36:50 +00:00
|
|
|
|
2015-07-03 19:50:39 +00:00
|
|
|
// Calculates the nucleons inner pressure during the impermeable period
|
|
|
|
double calc_inner_pressure(double crit_radius, double onset_tension, double current_ambient_pressure)
|
|
|
|
{
|
2015-07-01 10:27:42 +00:00
|
|
|
double onset_radius = 1.0 / (vpmb_config.gradient_of_imperm / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / crit_radius);
|
2015-07-03 19:50:39 +00:00
|
|
|
|
|
|
|
|
2015-07-01 10:27:42 +00:00
|
|
|
double A = current_ambient_pressure - vpmb_config.gradient_of_imperm + (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) / onset_radius;
|
|
|
|
double B = 2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma);
|
|
|
|
double C = onset_tension * pow(onset_radius, 3);
|
2015-07-03 19:50:39 +00:00
|
|
|
|
2015-08-17 19:36:50 +00:00
|
|
|
double current_radius = solve_cubic(A, B, C);
|
2015-07-01 10:27:42 +00:00
|
|
|
|
|
|
|
return onset_tension * onset_radius * onset_radius * onset_radius / (current_radius * current_radius * current_radius);
|
2015-07-03 19:50:39 +00:00
|
|
|
}
|
|
|
|
|
2015-07-03 20:10:12 +00:00
|
|
|
// Calculates the crushing pressure in the given moment. Updates crushing_onset_tension and critical radius if needed
|
|
|
|
void calc_crushing_pressure(double pressure)
|
|
|
|
{
|
|
|
|
int ci;
|
|
|
|
double gradient;
|
|
|
|
double gas_tension;
|
|
|
|
double n2_crushing_pressure, he_crushing_pressure;
|
|
|
|
double n2_inner_pressure, he_inner_pressure;
|
|
|
|
|
|
|
|
for (ci = 0; ci < 16; ++ci) {
|
|
|
|
gas_tension = tissue_n2_sat[ci] + tissue_he_sat[ci] + vpmb_config.other_gases_pressure;
|
|
|
|
gradient = pressure - gas_tension;
|
|
|
|
|
|
|
|
if (gradient <= vpmb_config.gradient_of_imperm) { // permeable situation
|
|
|
|
n2_crushing_pressure = he_crushing_pressure = gradient;
|
|
|
|
crushing_onset_tension[ci] = gas_tension;
|
|
|
|
}
|
|
|
|
else { // impermeable
|
|
|
|
if (max_ambient_pressure >= pressure)
|
|
|
|
return;
|
|
|
|
|
2015-08-15 12:36:07 +00:00
|
|
|
n2_inner_pressure = calc_inner_pressure(get_crit_radius_N2(), crushing_onset_tension[ci], pressure);
|
|
|
|
he_inner_pressure = calc_inner_pressure(get_crit_radius_He(), crushing_onset_tension[ci], pressure);
|
2015-07-03 20:10:12 +00:00
|
|
|
|
|
|
|
n2_crushing_pressure = pressure - n2_inner_pressure;
|
|
|
|
he_crushing_pressure = pressure - he_inner_pressure;
|
|
|
|
}
|
|
|
|
max_n2_crushing_pressure[ci] = MAX(max_n2_crushing_pressure[ci], n2_crushing_pressure);
|
|
|
|
max_he_crushing_pressure[ci] = MAX(max_he_crushing_pressure[ci], he_crushing_pressure);
|
|
|
|
}
|
|
|
|
max_ambient_pressure = MAX(pressure, max_ambient_pressure);
|
|
|
|
}
|
|
|
|
|
2013-01-08 17:29:07 +00:00
|
|
|
/* add period_in_seconds at the given pressure and gas to the deco calculation */
|
2014-10-24 14:40:21 +00:00
|
|
|
double add_segment(double pressure, const struct gasmix *gasmix, int period_in_seconds, int ccpo2, const struct dive *dive, int sac)
|
2013-01-03 05:21:36 +00:00
|
|
|
{
|
|
|
|
int ci;
|
2014-09-15 12:55:20 +00:00
|
|
|
struct gas_pressures pressures;
|
|
|
|
|
2015-08-29 11:43:32 +00:00
|
|
|
fill_pressures(&pressures, pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE),
|
|
|
|
gasmix, (double) ccpo2 / 1000.0, dive->dc.divemode);
|
2013-01-03 05:21:36 +00:00
|
|
|
|
2013-11-20 15:11:22 +00:00
|
|
|
if (buehlmann_config.gf_low_at_maxdepth && pressure > gf_low_pressure_this_dive)
|
2013-05-28 18:21:27 +00:00
|
|
|
gf_low_pressure_this_dive = pressure;
|
2013-01-08 14:37:41 +00:00
|
|
|
|
2013-09-26 03:42:19 +00:00
|
|
|
for (ci = 0; ci < 16; ci++) {
|
2014-09-15 12:55:20 +00:00
|
|
|
double pn2_oversat = pressures.n2 - tissue_n2_sat[ci];
|
|
|
|
double phe_oversat = pressures.he - tissue_he_sat[ci];
|
2013-09-26 03:42:19 +00:00
|
|
|
double n2_f = n2_factor(period_in_seconds, ci);
|
|
|
|
double he_f = he_factor(period_in_seconds, ci);
|
2014-06-22 14:41:44 +00:00
|
|
|
double n2_satmult = pn2_oversat > 0 ? buehlmann_config.satmult : buehlmann_config.desatmult;
|
|
|
|
double he_satmult = phe_oversat > 0 ? buehlmann_config.satmult : buehlmann_config.desatmult;
|
2013-09-26 03:42:19 +00:00
|
|
|
|
2014-06-22 14:41:44 +00:00
|
|
|
tissue_n2_sat[ci] += n2_satmult * pn2_oversat * n2_f;
|
|
|
|
tissue_he_sat[ci] += he_satmult * phe_oversat * he_f;
|
2013-01-03 05:21:36 +00:00
|
|
|
}
|
2015-07-03 20:10:12 +00:00
|
|
|
calc_crushing_pressure(pressure);
|
2013-01-08 14:37:41 +00:00
|
|
|
return tissue_tolerance_calc(dive);
|
2013-01-03 05:21:36 +00:00
|
|
|
}
|
|
|
|
|
2013-01-04 04:45:20 +00:00
|
|
|
void dump_tissues()
|
|
|
|
{
|
|
|
|
int ci;
|
|
|
|
printf("N2 tissues:");
|
|
|
|
for (ci = 0; ci < 16; ci++)
|
|
|
|
printf(" %6.3e", tissue_n2_sat[ci]);
|
|
|
|
printf("\nHe tissues:");
|
|
|
|
for (ci = 0; ci < 16; ci++)
|
|
|
|
printf(" %6.3e", tissue_he_sat[ci]);
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear_deco(double surface_pressure)
|
2013-01-03 05:21:36 +00:00
|
|
|
{
|
|
|
|
int ci;
|
|
|
|
for (ci = 0; ci < 16; ci++) {
|
2015-08-29 11:43:32 +00:00
|
|
|
tissue_n2_sat[ci] = (surface_pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * N2_IN_AIR / 1000;
|
2013-01-03 05:21:36 +00:00
|
|
|
tissue_he_sat[ci] = 0.0;
|
2015-07-03 20:18:41 +00:00
|
|
|
max_n2_crushing_pressure[ci] = 0.0;
|
|
|
|
max_he_crushing_pressure[ci] = 0.0;
|
2015-08-23 14:04:59 +00:00
|
|
|
n2_regen_radius[ci] = get_crit_radius_N2();
|
|
|
|
he_regen_radius[ci] = get_crit_radius_He();
|
2013-01-03 05:21:36 +00:00
|
|
|
}
|
Deco artefacts with low GFlow
In a dive, when you choose a very low GFlow (like 5 or 9) and a trimix
with quite some He (12/48 in the example) and descend fast, the ceiling
seems to do strange things in the first minutes of the dive (very very
deep for example or jumping around).
To understand what is going on we have to recall what gradient factors do
in detail: Plain Buehlmann gives you for each tissue a maximal inert gas
pressure that is a straight line when plotted against the ambient
pressure. So for each depth (=ambient pressure) there is a maximally
allowed over-pressure.
The idea of gradient factors is that one does not use all the possible
over-pressure that Buehlmann gives us but only a depth dependent fraction.
GFhigh is the fraction of the possible over-pressure at the surface while
GFlow is the fraction at the first deco stop. In between, the fraction is
linearly interpolated. As the Buehlmann over-pressure is increasing with
depth and typically also the allowed overpressure after applications of
gradient factors increases with depth or said differently: the tissue
saturation has to be lower if the diver wants to ascent.
The main problem is: What is the first stop (where to apply GFlow)? In a
planned dive, we could take the first deco stop, but in a real dive from a
dive computer download it is impossible to say what constitutes a stop and
what is only a slow ascent?
What I have used so far is not exactly the first stop but rather the first
theoretical stop: During all of the dive, I have calculated the ceiling
under the assumption that GFlow applies everywhere (and not just at a
single depth). The deepest of these ceilings I have used as the “first
stop depth”, the depth at which GFlow applies.
Even more, I only wanted to use the information that a diver has during
the dive, so I actually only considered the ceilings in the past (and not
in the future of a given sample).
But this brings with it the problem that early in the dive, in particular
during the descent the lowest ceiling so far is very shallow (as not much
gas has built up in the body so far).
This problem now interferes with a second one: If at the start of the dive
when the all compartments have 790mbar N2 the diver starts breathing a
He-heavy mix (like 12/48) and descents fast the He builds up in the
tissues before the N2 can diffuse out. So right at the start, we already
encounter high tissue loadings.
If now we have a large difference between GFhigh and GFlow but they apply
at very similar depth (the surface and a very shallow depth of the deepest
ceiling (which for a non-decompression dive would be theoretically at
negative depth) so far) it can happen that the linear interpolation as
opposite slope then in the typical case above: The allowed over-pressure
is degreasing with depth, shallower depth do not require lower gas loading
in the tissue (i.e. can be reached after further off-gasing) but but
tolerate higher loadings. In that situation the ceiling disappears (or is
rather a floor).
So far, I got rid of that problem, by stating that the minimum depth for
GFlow was 20m (after all, GFlow is about deep stops, so it should better
not be too shallow). Now the dive reported in ticket #549 takes values to
an extreme in such away that 20m (which is determined by
buehlmann_config.gf_low_position_min in deco.c) was not enough to prevent
this inversion problem (or in a milder form that the interpolation of
gradient factors is in fact an extrapolation with quite extreme values).
This patch that gets rid of the problem for the dive described above but
still it is possible to find (more extreme) parameter choices that lead to
non-realistic ceilings.
Let me close by pointing out that all this is only about the descent, as
it is about too shallow depth for GFlow. So no real deco (i.e. later part
of the dive) is inflicted. This is only about a theoretical ceiling
displayed possibly in the first minutes of a dive. So this is more an
aesthetically than a practical problem.
Fixes #549
Signed-off-by: Robert C. Helling <helling@atdotde.de>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-06-18 15:11:54 +00:00
|
|
|
gf_low_pressure_this_dive = surface_pressure;
|
|
|
|
if (!buehlmann_config.gf_low_at_maxdepth)
|
|
|
|
gf_low_pressure_this_dive += buehlmann_config.gf_low_position_min;
|
2015-07-03 20:18:41 +00:00
|
|
|
max_ambient_pressure = 0.0;
|
2013-01-03 05:21:36 +00:00
|
|
|
}
|
2013-01-03 07:22:07 +00:00
|
|
|
|
2013-01-06 19:13:46 +00:00
|
|
|
void cache_deco_state(double tissue_tolerance, char **cached_datap)
|
|
|
|
{
|
|
|
|
char *data = *cached_datap;
|
|
|
|
|
|
|
|
if (!data) {
|
2013-02-08 10:42:34 +00:00
|
|
|
data = malloc(2 * TISSUE_ARRAY_SZ + 2 * sizeof(double) + sizeof(int));
|
2013-01-06 19:13:46 +00:00
|
|
|
*cached_datap = data;
|
|
|
|
}
|
|
|
|
memcpy(data, tissue_n2_sat, TISSUE_ARRAY_SZ);
|
|
|
|
data += TISSUE_ARRAY_SZ;
|
|
|
|
memcpy(data, tissue_he_sat, TISSUE_ARRAY_SZ);
|
|
|
|
data += TISSUE_ARRAY_SZ;
|
2013-01-08 14:37:41 +00:00
|
|
|
memcpy(data, &gf_low_pressure_this_dive, sizeof(double));
|
2013-01-06 19:13:46 +00:00
|
|
|
data += sizeof(double);
|
|
|
|
memcpy(data, &tissue_tolerance, sizeof(double));
|
|
|
|
data += sizeof(double);
|
|
|
|
memcpy(data, &ci_pointing_to_guiding_tissue, sizeof(int));
|
|
|
|
}
|
|
|
|
|
|
|
|
double restore_deco_state(char *data)
|
|
|
|
{
|
|
|
|
double tissue_tolerance;
|
|
|
|
|
|
|
|
memcpy(tissue_n2_sat, data, TISSUE_ARRAY_SZ);
|
|
|
|
data += TISSUE_ARRAY_SZ;
|
|
|
|
memcpy(tissue_he_sat, data, TISSUE_ARRAY_SZ);
|
|
|
|
data += TISSUE_ARRAY_SZ;
|
2013-01-08 14:37:41 +00:00
|
|
|
memcpy(&gf_low_pressure_this_dive, data, sizeof(double));
|
2013-01-06 19:13:46 +00:00
|
|
|
data += sizeof(double);
|
|
|
|
memcpy(&tissue_tolerance, data, sizeof(double));
|
|
|
|
data += sizeof(double);
|
|
|
|
memcpy(&ci_pointing_to_guiding_tissue, data, sizeof(int));
|
|
|
|
|
|
|
|
return tissue_tolerance;
|
|
|
|
}
|
|
|
|
|
2013-10-05 07:29:09 +00:00
|
|
|
unsigned int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth)
|
2013-01-03 07:22:07 +00:00
|
|
|
{
|
2013-01-08 14:37:41 +00:00
|
|
|
unsigned int depth;
|
|
|
|
double pressure_delta;
|
|
|
|
|
2013-01-08 17:29:07 +00:00
|
|
|
/* Avoid negative depths */
|
|
|
|
pressure_delta = tissues_tolerance > surface_pressure ? tissues_tolerance - surface_pressure : 0.0;
|
2013-01-08 14:37:41 +00:00
|
|
|
|
|
|
|
depth = rel_mbar_to_depth(pressure_delta * 1000, dive);
|
|
|
|
|
2013-01-29 21:10:46 +00:00
|
|
|
if (!smooth)
|
2013-01-08 17:29:07 +00:00
|
|
|
depth = ceil(depth / DECO_STOPS_MULTIPLIER_MM) * DECO_STOPS_MULTIPLIER_MM;
|
2013-01-08 14:37:41 +00:00
|
|
|
|
2013-01-29 21:10:46 +00:00
|
|
|
if (depth > 0 && depth < buehlmann_config.last_deco_stop_in_mtr * 1000)
|
2013-01-08 17:29:07 +00:00
|
|
|
depth = buehlmann_config.last_deco_stop_in_mtr * 1000;
|
2013-01-03 07:22:07 +00:00
|
|
|
|
|
|
|
return depth;
|
|
|
|
}
|
2013-01-04 05:31:22 +00:00
|
|
|
|
2013-11-20 15:11:22 +00:00
|
|
|
void set_gf(short gflow, short gfhigh, bool gf_low_at_maxdepth)
|
2013-01-04 05:31:22 +00:00
|
|
|
{
|
2013-05-28 18:21:27 +00:00
|
|
|
if (gflow != -1)
|
|
|
|
buehlmann_config.gf_low = (double)gflow / 100.0;
|
|
|
|
if (gfhigh != -1)
|
|
|
|
buehlmann_config.gf_high = (double)gfhigh / 100.0;
|
2013-11-20 15:11:22 +00:00
|
|
|
buehlmann_config.gf_low_at_maxdepth = gf_low_at_maxdepth;
|
2013-01-04 05:31:22 +00:00
|
|
|
}
|