view Common/Src/calc_crush.c @ 563:d97f0e395058

Refine o2 sensor diagnostic functions: Changed implementation to ignore connection errors if analog interface is selected. Because the HUD is using cBars, the code has been modified to be compatible with the Bar units provided by the analog interface. Behavior (outofbounds) should be identical to the previous implementation.
author Ideenmodellierer
date Sun, 15 Nov 2020 20:01:56 +0100
parents 8f8ea3a32e82
children
line wrap: on
line source

///////////////////////////////////////////////////////////////////////////////
/// -*- 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 = 0.82;		 /* be conservative in case of an unexpected parameter value */
	float critical_radius_he_microns = 0.72;
	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;
		default:
			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 */