Mercurial > public > ostc4
diff Common/Src/calc_crush.c @ 38:5f11787b4f42
include in ostc4 repository
author | heinrichsweikamp |
---|---|
date | Sat, 28 Apr 2018 11:52:34 +0200 |
parents | |
children | 8f8ea3a32e82 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Common/Src/calc_crush.c Sat Apr 28 11:52:34 2018 +0200 @@ -0,0 +1,878 @@ +/////////////////////////////////////////////////////////////////////////////// +/// -*- coding: UTF-8 -*- +/// +/// \file Common/Src/calc_crush.c +/// \brief VPM Desaturation code +/// \author Heinrichs Weikamp +/// \date 2018 +/// +/// $Id$ +/////////////////////////////////////////////////////////////////////////////// +/// \par Copyright (c) 2014-2018 Heinrichs Weikamp gmbh +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// +/// This program is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with this program. If not, see <http://www.gnu.org/licenses/>. +////////////////////////////////////////////////////////////////////////////// + +#include "calc_crush.h" + +#include "decom.h" +#include "math.h" +#include "vpm.h" + +/* Common Block Declarations */ +//#pragma warning(disable:1035) + +const float SURFACE_TENSION_GAMMA = 0.0179f; //!Adj. Range: 0.015 to 0.065 N/m +const float SKIN_COMPRESSION_GAMMAC = 0.257f; //!Adj. Range: 0.160 to 0.290 N/m +const float UNITS_FACTOR = 10.1325f; +const float WATER_VAPOR_PRESSURE = 0.493f; // (Schreiner value) based on respiratory quotien +const float CRIT_VOLUME_PARAMETER_LAMBDA = 7500.0f; //!Adj. Range: 6500 to 8300 fsw-min +const float GRADIENT_ONSET_OF_IMPERM_ATM = 8.2f; //!Adj. Range: 5.0 to 10.0 atm +const float REGENERATION_TIME_CONSTANT = 20160.0f; //!Adj. Range: 10080 to 51840 min +const float PRESSURE_OTHER_GASES_MMHG = 102.0f; //!Constant value for PO2 up to 2 atm +const float CONSTANT_PRESSURE_OTHER_GASES = 102.0f * 10.1325f / 760.0f; // PRESSURE_OTHER_GASES_MMHG / 760. * UNITS_FACTOR; + +const float HELIUM_TIME_CONSTANT[16] = {3.68695308808482E-001f, + 2.29518933960247E-001f, + 1.46853216220327E-001f, + 9.91626867753856E-002f, + 6.78890480470074E-002f, + 4.78692804254106E-002f, + 3.37626488338989E-002f, + 2.38113081607676E-002f, + 1.68239606932026E-002f, + 1.25592893741610E-002f, + 9.80544886914621E-003f, + 7.67264977374303E-003f, + 6.01220557342307E-003f, + 4.70185307665137E-003f, + 3.68225234041620E-003f, + 2.88775228329769E-003f}; + + const float NITROGEN_TIME_CONSTANT[16] = {1.38629436111989E-001f, + 8.66433975699932E-002f, + 5.54517744447956E-002f, + 3.74674151654024E-002f, + 2.56721177985165E-002f, + 1.80978376125312E-002f, + 1.27651414467762E-002f, + 9.00191143584345E-003f, + 6.35914844550409E-003f, + 4.74758342849278E-003f, + 3.70666941475907E-003f, + 2.90019740820061E-003f, + 2.27261370675392E-003f, + 1.77730046297422E-003f, + 1.39186180835330E-003f, + 1.09157036308653E-003f}; + +int onset_of_impermeability(SGas* pGas, float *starting_ambient_pressure, +float *ending_ambient_pressure, +float *rate, +float* amb_pressure_onset_of_imperm, +float* gas_tension_onset_of_imperm, +float* initial_helium_pressure, +float* initial_nitrogen_pressure, +short i); +int radius_root_finder (float *a, float *b, float *c, float *low_bound, float *high_bound, float *ending_radius); +//void get_inert_gases_(SBuehlmann* input, ,short gas_id, float ambient_pressure_bar, float* fraction_nitrogen,float* fraction_helium ); +int vpm_repetitive_algorithm(SVpm* pVpm, float *surface_interval_time, float* initial_critical_radius_he, float* initial_critical_radius_n2); + + + +/* =============================================================================== */ +/* NOTE ABOUT PRESSURE UNITS USED IN CALCULATIONS: */ +/* It is the convention in decompression calculations to compute all gas */ +/* loadings, absolute pressures, partial pressures, etc., in the units of */ +/* depth pressure that you are diving - either feet of seawater (fsw) or */ +/* meters of seawater (msw). This program follows that convention with the */ +/* the exception that all VPM calculations are performed in SI units (by */ +/* necessity). Accordingly, there are several conversions back and forth */ +/* between the diving pressure units and the SI units. */ +/* =============================================================================== */ +/* =============================================================================== */ +/* FUNCTION SUBPROGRAM FOR GAS LOADING CALCULATIONS - ASCENT AND DESCENT */ +/* =============================================================================== */ + +float schreiner_equation__2(float *initial_inspired_gas_pressure, +float *rate_change_insp_gas_pressure, +float *interval_time_minutes, +const float *gas_time_constant, +float *initial_gas_pressure) +{ + /* System generated locals */ + float ret_val; + float time_null_pressure = 0.0f; + float time_rest = 0.0f; + float time = *interval_time_minutes; + /* =============================================================================== */ + /* Note: The Schreiner equation is applied when calculating the uptake or */ + /* elimination of compartment gases during linear ascents or descents at a */ + /* constant rate. For ascents, a negative number for rate must be used. */ + /* =============================================================================== */ + if( *rate_change_insp_gas_pressure < 0.0f) + { + time_null_pressure = -1.0f * *initial_inspired_gas_pressure / *rate_change_insp_gas_pressure; + if(time > time_null_pressure ) + { + time_rest = time - time_null_pressure; + time = time_null_pressure; + } + } + ret_val = + *initial_inspired_gas_pressure + + *rate_change_insp_gas_pressure * + (time - 1.f / *gas_time_constant) - + (*initial_inspired_gas_pressure - + *initial_gas_pressure - + *rate_change_insp_gas_pressure / *gas_time_constant) * + expf(-(*gas_time_constant) * time); + + if(time_rest > 0.0f) + { + ret_val = ret_val * expf(-(*gas_time_constant) * time_rest); + } + + + return ret_val; +}; /* schreiner_equation__2 */ + +/* =============================================================================== */ +/* SUBROUTINE CALC_CRUSHING_PRESSURE */ +/* Purpose: Compute the effective "crushing pressure" in each compartment as */ +/* a result of descent segment(s). The crushing pressure is the gradient */ +/* (difference in pressure) between the outside ambient pressure and the */ +/* gas tension inside a VPM nucleus (bubble seed). This gradient acts to */ +/* reduce (shrink) the radius smaller than its initial value at the surface. */ +/* This phenomenon has important ramifications because the smaller the radius */ +/* of a VPM nucleus, the greater the allowable supersaturation gradient upon */ +/* ascent. Gas loading (uptake) during descent, especially in the fast */ +/* compartments, will reduce the magnitude of the crushing pressure. The */ +/* crushing pressure is not cumulative over a multi-level descent. It will */ +/* be the maximum value obtained in any one discrete segment of the overall */ +/* descent. Thus, the program must compute and store the maximum crushing */ +/* pressure for each compartment that was obtained across all segments of */ +/* the descent profile. */ + +/* The calculation of crushing pressure will be different depending on */ +/* whether or not the gradient is in the VPM permeable range (gas can diffuse */ +/* across skin of VPM nucleus) or the VPM impermeable range (molecules in */ +/* skin of nucleus are squeezed together so tight that gas can no longer */ +/* diffuse in or out of nucleus; the gas becomes trapped and further resists */ +/* the crushing pressure). The solution for crushing pressure in the VPM */ +/* permeable range is a simple linear equation. In the VPM impermeable */ +/* range, a cubic equation must be solved using a numerical method. */ + +/* Separate crushing pressures are tracked for helium and nitrogen because */ +/* they can have different critical radii. The crushing pressures will be */ +/* the same for helium and nitrogen in the permeable range of the model, but */ +/* they will start to diverge in the impermeable range. This is due to */ +/* the differences between starting radius, radius at the onset of */ +/* impermeability, and radial compression in the impermeable range. */ +/* =============================================================================== */ +int calc_crushing_pressure(SLifeData* lifeData, SVpm* vpm, float * initial_helium_pressure, float * initial_nitrogen_pressure, float starting_ambient_pressure, +float rate ) +{ + /* System generated locals */ +static float r1, r2; + +static float low_bound_n2, + ending_radius_n2, + gradient_onset_of_imperm_pa; +static float low_bound_he, + ending_radius_he, + high_bound_n2, + crushing_pressure_n2; + short i; +static float crushing_pressure_pascals_n2, + gradient_onset_of_imperm, + starting_gas_tension, + high_bound_he, + crushing_pressure_he, + amb_press_onset_of_imperm_pa, + crushing_pressure_pascals_he, + radius_onset_of_imperm_n2, + starting_gradient, + radius_onset_of_imperm_he, + ending_gas_tension; + +static float ending_ambient_pressure_pa, + a_n2, + b_n2, + c_n2, + ending_gradient, + gas_tension_onset_of_imperm_pa, + a_he, + b_he, c_he; +static float amb_pressure_onset_of_imperm[16]; +static float gas_tension_onset_of_imperm[16]; + +static float helium_pressure_crush[16]; +static float nitrogen_pressure_crush[16]; + + +static float ending_ambient_pressure = 0; + + ending_ambient_pressure = lifeData->pressure_ambient_bar * 10; + for( i = 0; i < 16; i++) + { + helium_pressure_crush[i] = lifeData->tissue_helium_bar[i] * 10; + nitrogen_pressure_crush[i] = lifeData->tissue_nitrogen_bar[i] * 10; + } + + + + + + /* loop */ + /* =============================================================================== */ + /* CALCULATIONS */ + /* First, convert the Gradient for Onset of Impermeability from units of */ + /* atmospheres to diving pressure units (either fsw or msw) and to Pascals */ + /* (SI units). The reason that the Gradient for Onset of Impermeability is */ + /* given in the program settings in units of atmospheres is because that is */ + /* how it was reported in the original research papers by Yount and */ + /* colleauges. */ + /* =============================================================================== */ + + gradient_onset_of_imperm = GRADIENT_ONSET_OF_IMPERM_ATM * UNITS_FACTOR; + gradient_onset_of_imperm_pa = GRADIENT_ONSET_OF_IMPERM_ATM * 101325.0f; + + /* =============================================================================== */ + /* Assign values of starting and ending ambient pressures for descent segment */ + /* =============================================================================== */ + + //starting_ambient_pressure = *starting_depth; + //ending_ambient_pressure = *ending_depth; + + /* =============================================================================== */ + /* MAIN LOOP WITH NESTED DECISION TREE */ + /* For each compartment, the program computes the starting and ending */ + /* gas tensions and gradients. The VPM is different than some dissolved gas */ + /* algorithms, Buhlmann for example, in that it considers the pressure due to */ + /* oxygen, carbon dioxide, and water vapor in each compartment in addition to */ + /* the inert gases helium and nitrogen. These "other gases" are included in */ + /* the calculation of gas tensions and gradients. */ + /* =============================================================================== */ + + crushing_pressure_he = 0.0f; + crushing_pressure_n2 = 0.0f; + + for (i = 0; i < 16; ++i) { + starting_gas_tension = initial_helium_pressure[i] + initial_nitrogen_pressure[i] + CONSTANT_PRESSURE_OTHER_GASES; + starting_gradient = starting_ambient_pressure - starting_gas_tension; + ending_gas_tension = helium_pressure_crush[i] + nitrogen_pressure_crush[i] + CONSTANT_PRESSURE_OTHER_GASES; + ending_gradient = ending_ambient_pressure - ending_gas_tension; + + /* =============================================================================== */ + /* Compute radius at onset of impermeability for helium and nitrogen */ + /* critical radii */ + /* =============================================================================== */ + + radius_onset_of_imperm_he = 1.0f / ( + gradient_onset_of_imperm_pa / + ((SKIN_COMPRESSION_GAMMAC - + SURFACE_TENSION_GAMMA) * 2.0f) + + 1.0f / vpm->adjusted_critical_radius_he[i]); + radius_onset_of_imperm_n2 = 1.0f / ( + gradient_onset_of_imperm_pa / + ((SKIN_COMPRESSION_GAMMAC - + SURFACE_TENSION_GAMMA) * 2.0f) + + 1.0f / vpm->adjusted_critical_radius_n2[i]); + + /* =============================================================================== */ + /* FIRST BRANCH OF DECISION TREE - PERMEABLE RANGE */ + /* Crushing pressures will be the same for helium and nitrogen */ + /* =============================================================================== */ + + if (ending_gradient <= gradient_onset_of_imperm) { + crushing_pressure_he = ending_ambient_pressure - ending_gas_tension; + crushing_pressure_n2 = ending_ambient_pressure - ending_gas_tension; + } + + /* =============================================================================== */ + /* SECOND BRANCH OF DECISION TREE - IMPERMEABLE RANGE */ + /* Both the ambient pressure and the gas tension at the onset of */ + /* impermeability must be computed in order to properly solve for the ending */ + /* radius and resultant crushing pressure. The first decision block */ + /* addresses the special case when the starting gradient just happens to be */ + /* equal to the gradient for onset of impermeability (not very likely!). */ + /* =============================================================================== */ + + if (ending_gradient > gradient_onset_of_imperm) { + if (starting_gradient == gradient_onset_of_imperm) { + amb_pressure_onset_of_imperm[i] = starting_ambient_pressure; + gas_tension_onset_of_imperm[i] = starting_gas_tension; + } + + /* =============================================================================== */ + /* In most cases, a subroutine will be called to find these values using a */ + /* numerical method. */ + /* =============================================================================== */ + + if (starting_gradient < gradient_onset_of_imperm) { + onset_of_impermeability(&(lifeData->actualGas), &starting_ambient_pressure, &ending_ambient_pressure, &rate, + amb_pressure_onset_of_imperm, gas_tension_onset_of_imperm, + initial_helium_pressure, initial_nitrogen_pressure, i); + } + + /* =============================================================================== */ + /* Next, using the values for ambient pressure and gas tension at the onset */ + /* of impermeability, the equations are set up to process the calculations */ + /* through the radius root finder subroutine. This subprogram will find the */ + /* root (solution) to the cubic equation using a numerical method. In order */ + /* to do this efficiently, the equations are placed in the form */ + /* Ar^3 - Br^2 - C = 0, where r is the ending radius after impermeable */ + /* compression. The coefficients A, B, and C for helium and nitrogen are */ + /* computed and passed to the subroutine as arguments. The high and low */ + /* bounds to be used by the numerical method of the subroutine are also */ + /* computed (see separate page posted on Deco List ftp site entitled */ + /* "VPM: Solving for radius in the impermeable regime"). The subprogram */ + /* will return the value of the ending radius and then the crushing */ + /* pressures for helium and nitrogen can be calculated. */ + /* =============================================================================== */ + + ending_ambient_pressure_pa = ending_ambient_pressure / UNITS_FACTOR * 101325.0f; + amb_press_onset_of_imperm_pa = amb_pressure_onset_of_imperm[i] / UNITS_FACTOR * 101325.0f; + gas_tension_onset_of_imperm_pa = gas_tension_onset_of_imperm[i] / UNITS_FACTOR * 101325.0f; + b_he = (SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) * 2.0f; + a_he = ending_ambient_pressure_pa - amb_press_onset_of_imperm_pa + gas_tension_onset_of_imperm_pa + + (SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) * 2.0f / radius_onset_of_imperm_he; + /* Computing 3rd power */ + r1 = radius_onset_of_imperm_he; + c_he = gas_tension_onset_of_imperm_pa * (r1 * (r1 * r1)); + high_bound_he = radius_onset_of_imperm_he; + low_bound_he = b_he / a_he; + radius_root_finder(&a_he, &b_he, &c_he, &low_bound_he, &high_bound_he, &ending_radius_he); + /* Computing 3rd power */ + r1 = radius_onset_of_imperm_he; + /* Computing 3rd power */ + r2 = ending_radius_he; + crushing_pressure_pascals_he = + gradient_onset_of_imperm_pa + + ending_ambient_pressure_pa - + amb_press_onset_of_imperm_pa + + gas_tension_onset_of_imperm_pa * + (1.0f - r1 * (r1 * r1) / (r2 * (r2 * r2))); + crushing_pressure_he = + crushing_pressure_pascals_he / 101325.0f * UNITS_FACTOR; + b_n2 = (SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) * 2.0f; + a_n2 = ending_ambient_pressure_pa - + amb_press_onset_of_imperm_pa + + gas_tension_onset_of_imperm_pa + + (SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) * + 2.0f / radius_onset_of_imperm_n2; + /* Computing 3rd power */ + r1 = radius_onset_of_imperm_n2; + c_n2 = gas_tension_onset_of_imperm_pa * (r1 * (r1 * r1)); + high_bound_n2 = radius_onset_of_imperm_n2; + low_bound_n2 = b_n2 / a_n2; + radius_root_finder(&a_n2, + &b_n2, + &c_n2, + &low_bound_n2, + &high_bound_n2, + &ending_radius_n2); + + /* Computing 3rd power */ + r1 = radius_onset_of_imperm_n2; + /* Computing 3rd power */ + r2 = ending_radius_n2; + crushing_pressure_pascals_n2 = + gradient_onset_of_imperm_pa + + ending_ambient_pressure_pa - + amb_press_onset_of_imperm_pa + + gas_tension_onset_of_imperm_pa * (1.0f - r1 * + (r1 * r1) / (r2 * (r2 * r2))); + crushing_pressure_n2 = crushing_pressure_pascals_n2 / 101325.0f * UNITS_FACTOR; + } + + /* =============================================================================== */ + /* UPDATE VALUES OF MAX CRUSHING PRESSURE IN GLOBAL ARRAYS */ + /* =============================================================================== */ + + /* Computing MAX */ + r1 = vpm->max_crushing_pressure_he[i]; + vpm->max_crushing_pressure_he[i] = fmaxf(r1, crushing_pressure_he); + /* Computing MAX */ + r1 = vpm->max_crushing_pressure_n2[i]; + vpm->max_crushing_pressure_n2[i] = fmaxf(r1, crushing_pressure_n2); + } + return 0; +} /* calc_crushing_pressure */ + +/* =============================================================================== */ +/* SUBROUTINE ONSET_OF_IMPERMEABILITY */ +/* Purpose: This subroutine uses the Bisection Method to find the ambient */ +/* pressure and gas tension at the onset of impermeability for a given */ +/* compartment. Source: "Numerical Recipes in Fortran 77", */ +/* Cambridge University Press, 1992. */ +/* =============================================================================== */ + +int onset_of_impermeability(SGas* pGas, float *starting_ambient_pressure, +float *ending_ambient_pressure, +float *rate, +float* amb_pressure_onset_of_imperm, +float* gas_tension_onset_of_imperm, +float* initial_helium_pressure, +float* initial_nitrogen_pressure, +short i) +{ + /* Local variables */ + float time, last_diff_change, mid_range_nitrogen_pressure; + short j; + float gas_tension_at_mid_range, + initial_inspired_n2_pressure, + gradient_onset_of_imperm, + starting_gas_tension, + low_bound, + initial_inspired_he_pressure, + high_bound_nitrogen_pressure, + nitrogen_rate, + function_at_mid_range, + function_at_low_bound, + high_bound, + mid_range_helium_pressure, + mid_range_time, + ending_gas_tension, + function_at_high_bound; + + float mid_range_ambient_pressure, + high_bound_helium_pressure, + helium_rate, + differential_change; + float fraction_helium_begin; + float fraction_helium_end; + float fraction_nitrogen_begin; + float fraction_nitrogen_end; + /* loop */ + /* =============================================================================== */ + /* CALCULATIONS */ + /* First convert the Gradient for Onset of Impermeability to the diving */ + /* pressure units that are being used */ + /* =============================================================================== */ + + gradient_onset_of_imperm = GRADIENT_ONSET_OF_IMPERM_ATM * UNITS_FACTOR; + + /* =============================================================================== */ + /* ESTABLISH THE BOUNDS FOR THE ROOT SEARCH USING THE BISECTION METHOD */ + /* In this case, we are solving for time - the time when the ambient pressure */ + /* minus the gas tension will be equal to the Gradient for Onset of */ + /* Impermeabliity. The low bound for time is set at zero and the high */ + /* bound is set at the elapsed time (segment time) it took to go from the */ + /* starting ambient pressure to the ending ambient pressure. The desired */ + /* ambient pressure and gas tension at the onset of impermeability will */ + /* be found somewhere between these endpoints. The algorithm checks to */ + /* make sure that the solution lies in between these bounds by first */ + /* computing the low bound and high bound function values. */ + /* =============================================================================== */ + + /*initial_inspired_he_pressure = + (*starting_ambient_pressure - water_vapor_pressure) * fraction_helium[mix_number - 1]; + initial_inspired_n2_pressure = + (*starting_ambient_pressure - water_vapor_pressure) * fraction_nitrogen[mix_number - 1]; + helium_rate = *rate * fraction_helium[mix_number - 1]; + nitrogen_rate = *rate * fraction_nitrogen[mix_number - 1];*/ + low_bound = 0.; + high_bound = (*ending_ambient_pressure - *starting_ambient_pressure) / *rate; + + //New + decom_get_inert_gases( *starting_ambient_pressure / 10.0f, pGas, &fraction_nitrogen_begin, &fraction_helium_begin ); + decom_get_inert_gases(*ending_ambient_pressure / 10.0f, pGas, &fraction_nitrogen_end, &fraction_helium_end ); + initial_inspired_he_pressure = (*starting_ambient_pressure - WATER_VAPOR_PRESSURE) * fraction_helium_begin; + initial_inspired_n2_pressure = (*starting_ambient_pressure - WATER_VAPOR_PRESSURE) * fraction_nitrogen_begin; + helium_rate = ((*ending_ambient_pressure - WATER_VAPOR_PRESSURE)* fraction_helium_end - initial_inspired_he_pressure)/high_bound; + nitrogen_rate = ((*ending_ambient_pressure - WATER_VAPOR_PRESSURE)* fraction_nitrogen_end - initial_inspired_n2_pressure)/high_bound; + + starting_gas_tension = + initial_helium_pressure[i] + + initial_nitrogen_pressure[i] + + CONSTANT_PRESSURE_OTHER_GASES; + function_at_low_bound = + *starting_ambient_pressure - + starting_gas_tension - + gradient_onset_of_imperm; + high_bound_helium_pressure = + schreiner_equation__2(&initial_inspired_he_pressure, + &helium_rate, + &high_bound, + &HELIUM_TIME_CONSTANT[i], + &initial_helium_pressure[i]); + high_bound_nitrogen_pressure = + schreiner_equation__2(&initial_inspired_n2_pressure, + &nitrogen_rate, + &high_bound, + &NITROGEN_TIME_CONSTANT[i], + &initial_nitrogen_pressure[i]); + ending_gas_tension = + high_bound_helium_pressure + + high_bound_nitrogen_pressure + + CONSTANT_PRESSURE_OTHER_GASES; + function_at_high_bound = + *ending_ambient_pressure - + ending_gas_tension - + gradient_onset_of_imperm; + if (function_at_high_bound * function_at_low_bound >= 0.0f) { + //printf("\nERROR! ROOT IS NOT WITHIN BRACKETS"); + } + + /* =============================================================================== */ + /* APPLY THE BISECTION METHOD IN SEVERAL ITERATIONS UNTIL A SOLUTION WITH */ + /* THE DESIRED ACCURACY IS FOUND */ + /* Note: the program allows for up to 100 iterations. Normally an exit will */ + /* be made from the loop well before that number. If, for some reason, the */ + /* program exceeds 100 iterations, there will be a pause to alert the user. */ + /* =============================================================================== */ + + if (function_at_low_bound < 0.0f) { + time = low_bound; + differential_change = high_bound - low_bound; + } else { + time = high_bound; + differential_change = low_bound - high_bound; + } + for (j = 1; j <= 100; ++j) { + last_diff_change = differential_change; + differential_change = last_diff_change * 0.5f; + mid_range_time = time + differential_change; + mid_range_ambient_pressure = *starting_ambient_pressure + *rate * mid_range_time; + mid_range_helium_pressure = + schreiner_equation__2(&initial_inspired_he_pressure, + &helium_rate, + &mid_range_time, + &HELIUM_TIME_CONSTANT[i], + &initial_helium_pressure[i]); + mid_range_nitrogen_pressure = + schreiner_equation__2(&initial_inspired_n2_pressure, + &nitrogen_rate, + &mid_range_time, + &NITROGEN_TIME_CONSTANT[i], + &initial_nitrogen_pressure[i]); + gas_tension_at_mid_range = + mid_range_helium_pressure + + mid_range_nitrogen_pressure + + CONSTANT_PRESSURE_OTHER_GASES; + function_at_mid_range = + mid_range_ambient_pressure - + gas_tension_at_mid_range - + gradient_onset_of_imperm; + if (function_at_mid_range <= 0.0f) { + time = mid_range_time; + } + if (fabs(differential_change) < .001f || + function_at_mid_range == 0.0f) { + goto L100; + } + } + //printf("\nERROR! ROOT SEARCH EXCEEDED MAXIMUM ITERATIONS"); + + /* =============================================================================== */ + /* When a solution with the desired accuracy is found, the program jumps out */ + /* of the loop to Line 100 and assigns the solution values for ambient */ + /* pressure and gas tension at the onset of impermeability. */ + /* =============================================================================== */ + + L100: + amb_pressure_onset_of_imperm[i] = mid_range_ambient_pressure; + gas_tension_onset_of_imperm[i] = gas_tension_at_mid_range; + return 0; +} /* onset_of_impermeability */ + + +/* =============================================================================== */ +/* SUBROUTINE RADIUS_ROOT_FINDER */ +/* Purpose: This subroutine is a "fail-safe" routine that combines the */ +/* Bisection Method and the Newton-Raphson Method to find the desired root. */ +/* This hybrid algorithm takes a bisection step whenever Newton-Raphson would */ +/* take the solution out of bounds, or whenever Newton-Raphson is not */ +/* converging fast enough. Source: "Numerical Recipes in Fortran 77", */ +/* Cambridge University Press, 1992. */ +/* =============================================================================== */ + +int radius_root_finder (float *a, +float *b, +float *c, +float *low_bound, +float *high_bound, +float *ending_radius) +{ + /* System generated locals */ + float r1, r2; + + /* Local variables */ + float radius_at_low_bound, + last_diff_change, + function, + radius_at_high_bound; + short i; + float function_at_low_bound, + last_ending_radius, + function_at_high_bound, + derivative_of_function, + differential_change; + + /* loop */ + /* =============================================================================== */ + /* BEGIN CALCULATIONS BY MAKING SURE THAT THE ROOT LIES WITHIN BOUNDS */ + /* In this case we are solving for radius in a cubic equation of the form, */ + /* Ar^3 - Br^2 - C = 0. The coefficients A, B, and C were passed to this */ + /* subroutine as arguments. */ + /* =============================================================================== */ + + function_at_low_bound = + *low_bound * (*low_bound * (*a * *low_bound - *b)) - *c; + function_at_high_bound = + *high_bound * (*high_bound * (*a * *high_bound - *b)) - *c; + if (function_at_low_bound > 0.0f && function_at_high_bound > 0.0f) { +// printf("\nERROR! ROOT IS NOT WITHIN BRACKETS"); + + } + + /* =============================================================================== */ + /* Next the algorithm checks for special conditions and then prepares for */ + /* the first bisection. */ + /* =============================================================================== */ + + if (function_at_low_bound < 0.0f && function_at_high_bound < 0.0f) { + //printf("\nERROR! ROOT IS NOT WITHIN BRACKETS"); + + } + if (function_at_low_bound == 0.0f) { + *ending_radius = *low_bound; + return 0; + } else if (function_at_high_bound == 0.0f) { + *ending_radius = *high_bound; + return 0; + } else if (function_at_low_bound < 0.0f) { + radius_at_low_bound = *low_bound; + radius_at_high_bound = *high_bound; + } else { + radius_at_high_bound = *low_bound; + radius_at_low_bound = *high_bound; + } + *ending_radius = (*low_bound + *high_bound) * .5f; + last_diff_change = (r1 = *high_bound - *low_bound, fabs(r1)); + differential_change = last_diff_change; + + /* =============================================================================== */ + /* At this point, the Newton-Raphson Method is applied which uses a function */ + /* and its first derivative to rapidly converge upon a solution. */ + /* Note: the program allows for up to 100 iterations. Normally an exit will */ + /* be made from the loop well before that number. If, for some reason, the */ + /* program exceeds 100 iterations, there will be a pause to alert the user. */ + /* When a solution with the desired accuracy is found, exit is made from the */ + /* loop by returning to the calling program. The last value of ending */ + /* radius has been assigned as the solution. */ + /* =============================================================================== */ + + function = + *ending_radius * (*ending_radius * (*a * *ending_radius - *b)) - *c; + derivative_of_function = + *ending_radius * (*ending_radius * 3.0f * *a - *b * 2.0f); + for (i = 1; i <= 100; ++i) { + if (((*ending_radius - radius_at_high_bound) * derivative_of_function - function) * + ((*ending_radius - radius_at_low_bound) * derivative_of_function - function) >= 0.0f + || (r1 = function * 2.0f, fabs(r1)) > + (r2 = last_diff_change * derivative_of_function, fabs(r2))) { + last_diff_change = differential_change; + differential_change = + (radius_at_high_bound - radius_at_low_bound) * .5f; + *ending_radius = radius_at_low_bound + differential_change; + if (radius_at_low_bound == *ending_radius) { + return 0; + } + } else { + last_diff_change = differential_change; + differential_change = function / derivative_of_function; + last_ending_radius = *ending_radius; + *ending_radius -= differential_change; + if (last_ending_radius == *ending_radius) { + return 0; + } + } + if (fabs(differential_change) < 1e-12) { + return 0; + } + function = + *ending_radius * (*ending_radius * (*a * *ending_radius - *b)) - *c; + derivative_of_function = + *ending_radius * (*ending_radius * 3.0f * *a - *b * 2.0f); + if (function < 0.0f) { + radius_at_low_bound = *ending_radius; + } else { + radius_at_high_bound = *ending_radius; + } + } +// printf("\nERROR! ROOT SEARCH EXCEEDED MAXIMUM ITERATIONS"); + return 0; +} /* radius_root_finder */ + + + + +void vpm_init(SVpm* pVpm, short conservatism, short repetitive_dive, long seconds_since_last_dive) +{ + + float critical_radius_n2_microns, + critical_radius_he_microns; + float initial_critical_radius_n2[16]; + float initial_critical_radius_he[16]; + int i = 0; + float surface_time = seconds_since_last_dive / 60; + pVpm->repetitive_variables_not_valid = !repetitive_dive; + //pVpm->vpm_conservatism = conservatism; + switch(conservatism) + { + case 0: + critical_radius_n2_microns=0.55; //!Adj. Range: 0.2 to 1.35 microns + critical_radius_he_microns=0.45; //!Adj. Range: 0.2 to 1.35 microns + break; + case 1: + critical_radius_n2_microns=0.58; + critical_radius_he_microns=0.48; + break; + case 2: + critical_radius_n2_microns=0.62; + critical_radius_he_microns=0.52; + break; + case 3: + critical_radius_n2_microns=0.68; + critical_radius_he_microns=0.58; + break; + case 4: + critical_radius_n2_microns=0.75; + critical_radius_he_microns=0.65; + break; + case 5: + critical_radius_n2_microns=0.82; + critical_radius_he_microns=0.72; + break; + } + + for (i = 0; i < 16; ++i) { + initial_critical_radius_n2[i] = critical_radius_n2_microns * 1e-6f; + initial_critical_radius_he[i] = critical_radius_he_microns * 1e-6f; + } + + + + if( (surface_time > 0) + && (!pVpm->repetitive_variables_not_valid) ) + //&& (pVpm->decomode_vpm_plus_conservatism_last_dive > 0) + //&& (pVpm->decomode_vpm_plus_conservatism_last_dive - 1 == pVpm->vpm_conservatism)) + { + vpm_repetitive_algorithm(pVpm, &surface_time,initial_critical_radius_he, initial_critical_radius_n2); + } + else + { + //Kein gültiger Wiederholungstauchgang + for (i = 0; i < 16; ++i) { + pVpm->adjusted_critical_radius_n2[i] = initial_critical_radius_n2[i]; + pVpm->adjusted_critical_radius_he[i] = initial_critical_radius_he[i]; + } + pVpm->repetitive_variables_not_valid = 0; + } + for (i = 0; i < 16; ++i) { + pVpm->max_crushing_pressure_he[i] = 0.0f; + pVpm->max_crushing_pressure_n2[i] = 0.0f; + pVpm->max_actual_gradient[i] = 0.0f; + pVpm->adjusted_crushing_pressure_he[i] = 0.0f; + pVpm->adjusted_crushing_pressure_n2[i] = 0.0f; + pVpm->initial_allowable_gradient_he[i] = 0.0f; + pVpm->initial_allowable_gradient_n2[i] = 0.0f; + } + pVpm->max_first_stop_depth_save = 0; + pVpm->depth_start_of_deco_zone_save = 0; + pVpm->run_time_start_of_deco_zone_save = 0; + pVpm->deco_zone_reached = 0; +} + +/* =============================================================================== */ +/* SUBROUTINE VPM_REPETITIVE_ALGORITHM */ +/* Purpose: This subprogram implements the VPM Repetitive Algorithm that was */ +/* envisioned by Professor David E. Yount only months before his passing. */ +/* =============================================================================== */ +int vpm_repetitive_algorithm(SVpm* pVpm, float *surface_interval_time, float* initial_critical_radius_he, float* initial_critical_radius_n2) +{ + /* Local variables */ + static float max_actual_gradient_pascals; + //static float initial_allowable_grad_n2_pa, initial_allowable_grad_he_pa; + static short i; + static float adj_crush_pressure_n2_pascals, + new_critical_radius_n2, + adj_crush_pressure_he_pascals, + new_critical_radius_he; + + /* loop */ + /* =============================================================================== */ + /* CALCULATIONS */ + + /* by hw 160215 */ + /* IN: */ + /* pVpm->max_actual_gradient[i] */ + /* pVpm->initial_allowable_gradient_n2[i] */ + /* pVpm->initial_allowable_gradient_he[i] */ + /* pVpm->adjusted_crushing_pressure_he[i] */ + /* pVpm->adjusted_crushing_pressure_n2[i] */ + /* OUT: */ + /* pVpm->adjusted_critical_radius_n2[i] */ + /* pVpm->adjusted_critical_radius_he[i] */ + /* =============================================================================== */ + + for (i = 0; i < 16; ++i) { + max_actual_gradient_pascals = pVpm->max_actual_gradient[i] / UNITS_FACTOR * 101325.0f; + adj_crush_pressure_he_pascals = pVpm->adjusted_crushing_pressure_he[i] / UNITS_FACTOR * 101325.0f; + adj_crush_pressure_n2_pascals = pVpm->adjusted_crushing_pressure_n2[i] / UNITS_FACTOR * 101325.0f; +/* + initial_allowable_grad_he_pa = + pVpm->initial_allowable_gradient_he[i] / UNITS_FACTOR * 101325.0f; + initial_allowable_grad_n2_pa = + pVpm->initial_allowable_gradient_n2[i] / UNITS_FACTOR * 101325.0f; +*/ + if (pVpm->max_actual_gradient[i] > pVpm->initial_allowable_gradient_n2[i]) + { + new_critical_radius_n2 = + SURFACE_TENSION_GAMMA * 2.0f * + (SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) / + (max_actual_gradient_pascals * SKIN_COMPRESSION_GAMMAC - + SURFACE_TENSION_GAMMA * adj_crush_pressure_n2_pascals); + + pVpm->adjusted_critical_radius_n2[i] = + initial_critical_radius_n2[i] + + (initial_critical_radius_n2[i] - new_critical_radius_n2) + * exp(-(*surface_interval_time) / REGENERATION_TIME_CONSTANT); + + } else { + pVpm->adjusted_critical_radius_n2[i] = + initial_critical_radius_n2[i]; + } + if (pVpm->max_actual_gradient[i] > pVpm->initial_allowable_gradient_he[i]) + { + new_critical_radius_he = + SURFACE_TENSION_GAMMA * 2.0f * + (SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) / + (max_actual_gradient_pascals * SKIN_COMPRESSION_GAMMAC - + SURFACE_TENSION_GAMMA * adj_crush_pressure_he_pascals); + + pVpm->adjusted_critical_radius_he[i] = + initial_critical_radius_he[i] + + ( initial_critical_radius_he[i] - new_critical_radius_he) + * exp(-(*surface_interval_time) / REGENERATION_TIME_CONSTANT); + } else { + pVpm->adjusted_critical_radius_he[i] = + initial_critical_radius_he[i]; + } + } + return 0; +} /* vpm_repetitive_algorithm */