Mercurial > public > ostc4
view Discovery/Src/vpm.c @ 942:06aaccaf2e02 Evo_2_23
Power down gnss module during dive:
The gnss modul will now be send to powerdown at the start of the dive. After end of dive the module returns to normal operation.
For development / test purpose a new simulated dive profile has been added.
author | Ideenmodellierer |
---|---|
date | Mon, 16 Dec 2024 19:09:00 +0100 |
parents | 46a21ff3f5ab |
children |
line wrap: on
line source
/////////////////////////////////////////////////////////////////////////////// /// -*- coding: UTF-8 -*- /// /// \file Discovery/Src/vpm.c /// \brief critical_volume comment by hw /// \author Heinrichs Weikamp, Erik C. Baker /// \date 19-April-2014 /// /// \details /// /// $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/>. ////////////////////////////////////////////////////////////////////////////// /// \par Varying Permeability Model (VPM) Decompression Program in c (converted from FORTRAN) /// /// Author: Erik C. Baker /// /// "DISTRIBUTE FREELY - CREDIT THE AUTHORS" /// /// This program extends the 1986 VPM algorithm (Yount & Hoffman) to include /// mixed gas, repetitive, and altitude diving. Developments to the algorithm /// were made by David E. Yount, Eric B. Maiken, and Erik C. Baker over a /// period from 1999 to 2001. This work is dedicated in remembrance of /// Professor David E. Yount who passed away on April 27, 2000. /// /// Notes: /// 1. This program uses the sixteen (16) half-time compartments of the /// Buhlmann ZH-L16 model. The optional Compartment 1b is used here with /// half-times of 1.88 minutes for helium and 5.0 minutes for nitrogen. /// /// 2. This program uses various DEC, IBM, and Microsoft extensions which /// may not be supported by all FORTRAN compilers. Comments are made with /// a capital "C" in the first column or an exclamation point "!" placed /// in a line after code. An asterisk "*" in column 6 is a continuation /// of the previous line. All code, except for line numbers, starts in /// column 7. /// /// 3. Comments and suggestions for improvements are welcome. Please /// respond by e-mail to: EBaker@se.aeieng.com /// /// Acknowledgment: Thanks to Kurt Spaugh for recommendations on how to clean /// up the code. /// =============================================================================== /// Converted to vpmdeco.c using f2c; R.McGinnis (CABER Swe) 5/01 /// =============================================================================== /// /// ************************ Heirichs Weipkamp ************************************** /// /// The original Yount & Baker code has been adjusted for real life calculation. /// /// 1) The original main function has been split in several functions /// /// 2) When the deco zone is reached (while ascending) the gradient factors are kept fix /// and critical volume algorithm is switched of. maxfirststopdepth is kept fix /// to make shure Boeyls Law algorithm works correctly /// /// 4) gas_loadings_ascent_descend heeds all gaschanges and CCR support has been added /// #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <time.h> #include "vpm.h" #include "decom.h" #define GAS_N2 0 #define GAS_HE 1 static const _Bool buehlmannSafety = true; /* Common Block Declarations */ extern const float SURFACE_TENSION_GAMMA; //!Adj. Range: 0.015 to 0.065 N/m extern const float SKIN_COMPRESSION_GAMMAC; //!Adj. Range: 0.160 to 0.290 N/m extern const float UNITS_FACTOR; extern const float WATER_VAPOR_PRESSURE; // (Schreiner value) based on respiratory quotien extern const float CRIT_VOLUME_PARAMETER_LAMBDA; //!Adj. Range: 6500 to 8300 fsw-min //extern const float GRADIENT_ONSET_OF_IMPERM_ATM; //!Adj. Range: 5.0 to 10.0 atm extern const float REGENERATION_TIME_CONSTANT; //!Adj. Range: 10080 to 51840 min //extern const float PRESSURE_OTHER_GASES_MMHG; //!Constant value for PO2 up to 2 atm extern const float CONSTANT_PRESSURE_OTHER_GASES; // PRESSURE_OTHER_GASES_MMHG / 760. * UNITS_FACTOR; extern const float HELIUM_TIME_CONSTANT[]; extern const float NITROGEN_TIME_CONSTANT[]; static float minimum_deco_stop_time; static float run_time, run_time_first_stop; static float segment_time; static short mix_number; static float barometric_pressure; static _Bool altitude_dive_algorithm_off; static _Bool units_equal_fsw, units_equal_msw; /* by hw 11.06.2015 to allow */ static float gCNS_VPM; static float helium_pressure[16], nitrogen_pressure[16]; static float surface_phase_volume_time[16]; static float regenerated_radius_he[16], regenerated_radius_n2[16]; static float allowable_gradient_he[16], allowable_gradient_n2[16]; //_Bool deco_zone_reached; static _Bool critical_volume_algorithm_off; static float max_first_stop_depth; static float max_deco_ceiling_depth; //Boylslaw compensation static float deco_gradient_he[16]; static float deco_gradient_n2[16]; static int vpm_calc_what; static int count_critical_volume_iteration; static short number_of_changes; static float depth_change[11]; static float step_size_change[11]; static float rate_change[11]; static short mix_change[11]; static const _Bool vpm_b = true; static SvpmTableState vpmTableState = VPM_TABLE_INIT; static SDecoinfo vpmTable; extern const float float_buehlmann_N2_factor_expositon_20_seconds[]; extern const float float_buehlmann_He_factor_expositon_20_seconds[]; extern const float float_buehlmann_N2_factor_expositon_one_minute[]; extern const float float_buehlmann_He_factor_expositon_one_minute[]; extern const float float_buehlmann_N2_factor_expositon_five_minutes[]; extern const float float_buehlmann_He_factor_expositon_five_minutes[]; extern const float float_buehlmann_N2_factor_expositon_one_hour[]; extern const float float_buehlmann_He_factor_expositon_one_hour[]; static float depth_start_of_deco_calc; static float depth_start_of_deco_zone; static float first_stop_depth; static float run_time_start_of_deco_zone; static float r_nint(float *x); static float r_int(float *x); static _Bool nullzeit_unter60; static int vpm_calc_status; static _Bool buehlmann_wait_exceeded = false; static SLifeData* pInput = NULL; static SVpm* pVpm = NULL; static SDecoinfo* pDecoInfo = NULL; static SDiveSettings* pDiveSettings = NULL; static float r_nint(float *x) { return( (*x)>=0 ? floorf(*x + 0.5f) : -floorf(0.5f - *x) ); } static float r_int(float *x) { return( (*x>0.0) ? floorf(*x) : -floorf(- *x) ); } /** private functions */ extern int radius_root_finder (float *a, float *b, float *c,float *low_bound, float *high_bound, float *ending_radius); static int nuclear_regeneration(float *dive_time);// clock_(); static int calc_deco_ceiling(float *deco_ceiling_depth,_Bool fallowablw); static int critical_volume(float *deco_phase_volume_time); ; static int calc_start_of_deco_zone(float *starting_depth, float *rate, float *depth_start_of_deco_zone); static int calc_initial_allowable_gradient(void); static void decompression_stop(float *deco_stop_depth, float *step_size, _Bool final_deco_calculation); static int gas_loadings_ascent_descen(float* helium_pressure, float* nitrogen_pressure, float starting_depth,float ending_depth, float rate,_Bool check_gas_change); static int calc_surface_phase_volume_time(void); static int calc_max_actual_gradient(float *deco_stop_depth); static int projected_ascent(float *starting_depth, float *rate, float *deco_stop_depth, float *step_size); static void vpm_calc_deco(void); static int vpm_calc_critcal_volume(_Bool begin,_Bool calc_nulltime); static int vpm_check_converged(_Bool calc_nulltime); static int vpm_calc_final_deco(_Bool begin); static void BOYLES_LAW_COMPENSATION (float* First_Stop_Depth,float * Deco_Stop_Depth,float* Step_Size); static int vpm_calc_ndl(void); static void vpm_init_1(void); static void vpm_calc_deco_ceiling(void); uint8_t vpm_get_decozone(void); static void vpm_init_1(void) { units_equal_msw = true; units_equal_fsw = false; altitude_dive_algorithm_off= true; //!Options: ON or OFF minimum_deco_stop_time=1.0; //!Options: float positive number critical_volume_algorithm_off= false; //!Options: ON or OFF run_time = 0.; //barometric_pressure = dive_data.surface * 10; //mix_number = dive_data.selected_gas + 1; max_first_stop_depth = 0; max_deco_ceiling_depth = 0; //deco_zone_reached = false; depth_start_of_deco_calc = 0; depth_start_of_deco_zone = 0; first_stop_depth = 0; run_time_start_of_deco_zone = 0; gCNS_VPM = 0; } float vpm_get_CNS(void) { return gCNS_VPM; } void vpm_maintainTable(SLifeData* pLifeData,SDecoinfo* pDecoInfo) { static uint32_t lastDiveSecond = 0; uint8_t actual_deco_stop = 0; int8_t index = 0; uint8_t decreaseStopTime = 1; if(lastDiveSecond < pLifeData->dive_time_seconds) { lastDiveSecond = pLifeData->dive_time_seconds; actual_deco_stop = decom_get_actual_deco_stop((SDiveState*)stateUsed); pDecoInfo->output_time_to_surface_seconds = 0; for(index = DECOINFO_STRUCT_MAX_STOPS -1 ;index >= 0; index--) { if(pDecoInfo->output_stop_length_seconds[index] > 0) { if(decreaseStopTime) { if((pLifeData->depth_meter > (float)(actual_deco_stop - 1.5)) && (pLifeData->depth_meter < (float)actual_deco_stop + 1.5)) { pDecoInfo->output_stop_length_seconds[index]--; decreaseStopTime = 0; } else if (pLifeData->depth_meter < (float)(actual_deco_stop - 1.5)) /* missed deco stop */ { vpmTableState = VPM_TABLE_MISSED; pDecoInfo->output_stop_length_seconds[index] = 0; decreaseStopTime = 0; } } pDecoInfo->output_time_to_surface_seconds += pDecoInfo->output_stop_length_seconds[index]; } } pDecoInfo->output_time_to_surface_seconds += pLifeData->depth_meter / 10.0 * 60.0; } else if(lastDiveSecond > pLifeData->dive_time_seconds) { lastDiveSecond = pLifeData->dive_time_seconds; } } int vpm_calc(SLifeData* pINPUT, SDiveSettings* pSettings, SVpm* pVPM, SDecoinfo* pDECOINFO, int calc_what) { static uint8_t vpmTableActive = 0; vpm_init_1(); //decom_CreateGasChangeList(pSettings, pINPUT); vpm_calc_what = calc_what; /**clear decoInfo*/ if((vpmTableActive) && (vpm_calc_what == DECOSTOPS)) { memcpy(&vpmTable, pDECOINFO, sizeof(SDecoinfo)); /* save changes done by e.g. the simulator */ } pDECOINFO->output_time_to_surface_seconds = 0; pDECOINFO->output_ndl_seconds = 0; pDECOINFO->output_ceiling_meter = 0; pDECOINFO->super_saturation = 0; uint8_t tmp_calc_status; for(int i=0;i<DECOINFO_STRUCT_MAX_STOPS;i++) { pDECOINFO->output_stop_length_seconds[i] = 0; } if(pINPUT->dive_time_seconds_without_surface_time < 60) { vpm_calc_status = CALC_NDL; return vpm_calc_status; } pVpm = pVPM; pInput = pINPUT; pDecoInfo = pDECOINFO; pDiveSettings = pSettings; if(vpm_calc_status == CALC_NDL) { tmp_calc_status = vpm_calc_ndl(); } else { tmp_calc_status = CALC_BEGIN; } //Normal Deco calculation if(tmp_calc_status != CALC_NDL) { max_first_stop_depth = pVpm->max_first_stop_depth_save; run_time_start_of_deco_zone = pVpm->run_time_start_of_deco_zone_save; depth_start_of_deco_zone = pVpm->depth_start_of_deco_zone_save; for (int i = 0; i < 16; ++i) { helium_pressure[i] = pInput->tissue_helium_bar[i] * 10; nitrogen_pressure[i] = pInput->tissue_nitrogen_bar[i] * 10; } vpm_calc_deco(); tmp_calc_status = vpm_calc_critcal_volume(true,false); if(vpm_calc_what == DECOSTOPS) { pVpm->max_first_stop_depth_save = max_first_stop_depth; pVpm->run_time_start_of_deco_zone_save = run_time_start_of_deco_zone; pVpm->depth_start_of_deco_zone_save = depth_start_of_deco_zone; } } //Only Decostops not futute stops if(vpm_calc_what == DECOSTOPS) { vpm_calc_status = tmp_calc_status; if(pSettings->vpm_tableMode) /* store the most conservative deco plan and stick to it. */ { if((int16_t)(pDECOINFO->output_time_to_surface_seconds - vpmTable.output_time_to_surface_seconds) > 60) { memcpy(&vpmTable, pDECOINFO, sizeof(SDecoinfo)); vpmTableActive = 1; if(pVpm->deco_zone_reached) /* table should not change after deco zone was entered */ { if(vpmTableState != VPM_TABLE_MISSED) { vpmTableState = VPM_TABLE_WARNING; } } } else { if(vpmTable.output_time_to_surface_seconds > 0) { vpm_maintainTable(pINPUT, &vpmTable); vpmTable.output_ceiling_meter = pDECOINFO->output_ceiling_meter; memcpy(pDECOINFO, &vpmTable, sizeof(SDecoinfo)); } } } } return vpm_calc_status; } void vpm_saturation_after_ascent(SLifeData* input) { int i = 0; for (i = 0; i < 16; ++i) { pInput->tissue_helium_bar[i] = helium_pressure[i] / 10; pInput->tissue_nitrogen_bar[i] = nitrogen_pressure[i] / 10; } pInput->pressure_ambient_bar = pInput->pressure_surface_bar; } /* =============================================================================== */ /* 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 */ /* =============================================================================== */ /* =============================================================================== */ /* SUBROUTINE GAS_LOADINGS_ASCENT_DESCENT */ /* Purpose: This subprogram applies the Schreiner equation to update the */ /* gas loadings (partial pressures of helium and nitrogen) in the half-time */ /* compartments due to a linear ascent or descent segment at a constant rate. */ /* =============================================================================== */ static int gas_loadings_ascent_descen(float* helium_pressure, float* nitrogen_pressure, float starting_depth, float ending_depth, float rate,_Bool check_gas_change) { short i; float initial_inspired_n2_pressure, initial_inspired_he_pressure, nitrogen_rate, last_run_time, starting_ambient_pressure, ending_ambient_pressure; float initial_helium_pressure[16]; float initial_nitrogen_pressure[16]; float helium_rate; float fraction_helium_begin; float fraction_helium_end; float fraction_nitrogen_begin; float fraction_nitrogen_end; float ending_depth_tmp = ending_depth; float segment_time_tmp = 0; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* =============================================================================== */ segment_time = (ending_depth_tmp - starting_depth) / rate; last_run_time = run_time; run_time = last_run_time + segment_time; do { ending_depth_tmp = ending_depth; if (starting_depth > ending_depth && check_gas_change && number_of_changes > 1) { for (i = 1; i < number_of_changes; ++i) { if (depth_change[i] < starting_depth && depth_change[i] > ending_depth) { ending_depth_tmp = depth_change[i]; break; } } for (i = 1; i < number_of_changes; ++i) { if (depth_change[i] >= starting_depth) { mix_number = mix_change[i]; } } } segment_time_tmp = (ending_depth_tmp - starting_depth) / rate; ending_ambient_pressure = ending_depth_tmp + barometric_pressure; starting_ambient_pressure = starting_depth + barometric_pressure; decom_get_inert_gases( starting_ambient_pressure / 10, (&pDiveSettings->decogaslist[mix_number]), &fraction_nitrogen_begin, &fraction_helium_begin ); decom_get_inert_gases( ending_ambient_pressure / 10, (&pDiveSettings->decogaslist[mix_number]), &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 = *rate * fraction_helium[mix_number - 1]; helium_rate = ((ending_ambient_pressure - WATER_VAPOR_PRESSURE)* fraction_helium_end - initial_inspired_he_pressure)/segment_time_tmp; //nitrogen_rate2 = *rate * fraction_nitrogen[mix_number - 1]; nitrogen_rate = ((ending_ambient_pressure - WATER_VAPOR_PRESSURE)* fraction_nitrogen_end - initial_inspired_n2_pressure)/segment_time_tmp; decom_oxygen_calculate_cns_stage_SchreinerStyle(segment_time_tmp,&pDiveSettings->decogaslist[mix_number],starting_ambient_pressure/10,ending_ambient_pressure/10,&gCNS_VPM); //if(fabs(nitrogen_rate - nitrogen_rate2) > 0.000001) //return -2; for (i = 1; i <= 16; ++i) { initial_helium_pressure[i - 1] = helium_pressure[i - 1]; initial_nitrogen_pressure[i - 1] = nitrogen_pressure[i - 1]; helium_pressure[i - 1] = schreiner_equation__2(&initial_inspired_he_pressure, &helium_rate, &segment_time, &HELIUM_TIME_CONSTANT[i - 1], &initial_helium_pressure[i - 1]); nitrogen_pressure[i - 1] = schreiner_equation__2(&initial_inspired_n2_pressure, &nitrogen_rate, &segment_time, &NITROGEN_TIME_CONSTANT[i - 1], &initial_nitrogen_pressure[i - 1]); //nextround??? } starting_depth = ending_depth_tmp; } while(ending_depth_tmp > ending_depth); return 0; } /* gas_loadings_ascent_descen */ static float last_phase_volume_time[16]; static float n2_pressure_start_of_deco_zone[16]; static float he_pressure_start_of_deco_zone[16]; static float phase_volume_time[16]; static float n2_pressure_start_of_ascent[16]; static float he_pressure_start_of_ascent[16]; static float run_time_start_of_deco_calc; static float starting_depth; static float last_run_time; static float deco_phase_volume_time; static float run_time_start_of_ascent; static float rate; static float step_size; static _Bool vpm_violates_buehlmann; static void vpm_calc_deco(void) { /* System generated locals */ //float deepest_possible_stop_depth; // altitude_of_dive, short i; int j = 0; // float rounding_operation; /* =============================================================================== */ /* INPUT PARAMETERS TO BE USED FOR STAGED DECOMPRESSION AND SAVE IN ARRAYS. */ /* ASSIGN INITAL PARAMETERS TO BE USED AT START OF ASCENT */ /* The user has the ability to change mix, ascent rate, and step size in any */ /* combination at any depth during the ascent. */ /* =============================================================================== */ run_time = ((float)pInput->dive_time_seconds )/ 60; count_critical_volume_iteration = 0; number_of_changes = 1; barometric_pressure = pInput->pressure_surface_bar * 10; depth_change[0] =(pInput->pressure_ambient_bar - pInput->pressure_surface_bar)* 10; mix_change[0] = 0; rate_change[0 ] = -10;// neu 160215 hw, zuvor: -12; step_size_change[0] = 3; vpm_violates_buehlmann = false; for (i = 1; i < BUEHLMANN_STRUCT_MAX_GASES; i++) { depth_change[i] = 0; mix_change[i] = 0; } j = 0; for (i = 1; i < BUEHLMANN_STRUCT_MAX_GASES; i++) { if((pDiveSettings->decogaslist[i].change_during_ascent_depth_meter_otherwise_zero >= depth_change[0] + 1) && (pDiveSettings->gas[pDiveSettings->decogaslist[i].GasIdInSettings].note.ub.decocalc)) continue; if((pDiveSettings->decogaslist[i].change_during_ascent_depth_meter_otherwise_zero <= 0) || (pDiveSettings->gas[pDiveSettings->decogaslist[i].GasIdInSettings].note.ub.decocalc == 0)) break; j++; number_of_changes ++; depth_change[j] = pDiveSettings->decogaslist[i].change_during_ascent_depth_meter_otherwise_zero ; mix_change[j] = i; rate_change[j] = -10;// neu 160215 hw, zuvor: -12; step_size_change[j] = 3; } starting_depth = depth_change[0] ; mix_number = mix_change[0] ; rate = rate_change[0]; step_size = step_size_change[0]; for (i = 0; i < 16; ++i) { he_pressure_start_of_ascent[i ] = helium_pressure[i]; n2_pressure_start_of_ascent[i] = nitrogen_pressure[i]; } run_time_start_of_ascent = run_time; if(starting_depth <= depth_start_of_deco_zone && vpm_calc_what == DECOSTOPS) { pVpm->deco_zone_reached = true; depth_start_of_deco_calc = starting_depth; critical_volume_algorithm_off = true; } else { //if(deco_zone_reached) //{ pVpm->deco_zone_reached = false; critical_volume_algorithm_off = false; //max_first_stop_depth = 0; //max_first_stop_depth_save = 0; //} /* =============================================================================== */ /* BEGIN PROCESS OF ASCENT AND DECOMPRESSION */ /* First, calculate the regeneration of critical radii that takes place over */ /* the dive time. The regeneration time constant has a time scale of weeks */ /* so this will have very little impact on dives of normal length, but will */ /* have major impact for saturation dives. */ /* =============================================================================== */ nuclear_regeneration(&run_time); /* =============================================================================== */ /* CALCULATE INITIAL ALLOWABLE GRADIENTS FOR ASCENT */ /* This is based on the maximum effective crushing pressure on critical radii */ /* in each compartment achieved during the dive profile. */ /* =============================================================================== */ calc_initial_allowable_gradient(); /* =============================================================================== */ /* SAVE VARIABLES AT START OF ASCENT (END OF BOTTOM TIME) SINCE THESE WILL */ /* BE USED LATER TO COMPUTE THE FINAL ASCENT PROFILE THAT IS WRITTEN TO THE */ /* OUTPUT FILE. */ /* The VPM uses an iterative process to compute decompression schedules so */ /* there will be more than one pass through the decompression loop. */ /* =============================================================================== */ /* =============================================================================== */ /* CALCULATE THE DEPTH WHERE THE DECOMPRESSION ZONE BEGINS FOR THIS PROFILE */ /* BASED ON THE INITIAL ASCENT PARAMETERS AND WRITE THE DEEPEST POSSIBLE */ /* DECOMPRESSION STOP DEPTH TO THE OUTPUT FILE */ /* Knowing where the decompression zone starts is very important. Below */ /* that depth there is no possibility for bubble formation because there */ /* will be no supersaturation gradients. Deco stops should never start */ /* below the deco zone. The deepest possible stop deco stop depth is */ /* defined as the next "standard" stop depth above the point where the */ /* leading compartment enters the deco zone. Thus, the program will not */ /* base this calculation on step sizes larger than 10 fsw or 3 msw. The */ /* deepest possible stop depth is not used in the program, per se, rather */ /* it is information to tell the diver where to start putting on the brakes */ /* during ascent. This should be prominently displayed by any deco program. */ /* =============================================================================== */ calc_start_of_deco_zone(&starting_depth, &rate, &depth_start_of_deco_zone); /* =============================================================================== */ /* TEMPORARILY ASCEND PROFILE TO THE START OF THE DECOMPRESSION ZONE, SAVE */ /* VARIABLES AT THIS POINT, AND INITIALIZE VARIABLES FOR CRITICAL VOLUME LOOP */ /* The iterative process of the VPM Critical Volume Algorithm will operate */ /* only in the decompression zone since it deals with excess gas volume */ /* released as a result of supersaturation gradients (not possible below the */ /* decompression zone). */ /* =============================================================================== */ gas_loadings_ascent_descen(helium_pressure,nitrogen_pressure, starting_depth, depth_start_of_deco_zone, rate, true); run_time_start_of_deco_zone = run_time; depth_start_of_deco_calc = depth_start_of_deco_zone; for (i = 0; i < 16; ++i) { pVpm->max_actual_gradient[i] = 0.; } } for (i = 0; i < 16; ++i) { surface_phase_volume_time[i] = 0.; last_phase_volume_time[i] = 0.; he_pressure_start_of_deco_zone[i] = helium_pressure[i]; n2_pressure_start_of_deco_zone[i] = nitrogen_pressure[i]; //pVpm->max_actual_gradient[i] = 0.; } run_time_start_of_deco_calc = run_time; } /* =============================================================================== */ /* START OF CRITICAL VOLUME LOOP */ /* This loop operates between Lines 50 and 100. If the Critical Volume */ /* Algorithm is toggled "off" in the program settings, there will only be */ /* one pass through this loop. Otherwise, there will be two or more passes */ /* through this loop until the deco schedule is "converged" - that is when a */ /* comparison between the phase volume time of the present iteration and the */ /* last iteration is less than or equal to one minute. This implies that */ /* the volume of released gas in the most recent iteration differs from the */ /* "critical" volume limit by an acceptably small amount. The critical */ /* volume limit is set by the Critical Volume Parameter Lambda in the program */ /* settings (default setting is 7500 fsw-min with adjustability range from */ /* from 6500 to 8300 fsw-min according to Bruce Wienke). */ /* =============================================================================== */ /* L50: */ static float deco_stop_depth; static int vpm_calc_critcal_volume(_Bool begin, _Bool calc_nulltime) { /* loop will run continuous there is an exit stateme */ short i; float rounding_operation2; //float ending_depth; float deco_ceiling_depth; //float deco_time; int count = 0; _Bool first_stop; int dp = 0; float tissue_He_saturation[16]; float tissue_N2_saturation[16]; float vpm_buehlmann_safety_gradient = 1.0f - (((float)pDiveSettings->vpm_conservatism) / 40); /* =============================================================================== */ /* CALCULATE CURRENT DECO CEILING BASED ON ALLOWABLE SUPERSATURATION */ /* GRADIENTS AND SET FIRST DECO STOP. CHECK TO MAKE SURE THAT SELECTED STEP */ /* SIZE WILL NOT ROUND UP FIRST STOP TO A DEPTH THAT IS BELOW THE DECO ZONE. */ /* =============================================================================== */ if(begin) { if(depth_start_of_deco_calc < max_first_stop_depth ) { if(vpm_b) { BOYLES_LAW_COMPENSATION(&max_first_stop_depth, &depth_start_of_deco_calc, &step_size); } calc_deco_ceiling(&deco_ceiling_depth, false); } else calc_deco_ceiling(&deco_ceiling_depth, true); if (deco_ceiling_depth <= 0.0f) { deco_stop_depth = 0.0f; } else { rounding_operation2 = deco_ceiling_depth / step_size + ( float)0.5f; deco_stop_depth = r_nint(&rounding_operation2) * step_size; } // buehlmann safety if(buehlmannSafety) { for (i = 0; i < 16; i++) { tissue_He_saturation[i] = helium_pressure[i] / 10.0; tissue_N2_saturation[i] = nitrogen_pressure[i] / 10.0; } if(!decom_tissue_test_tolerance(tissue_N2_saturation, tissue_He_saturation, vpm_buehlmann_safety_gradient, (deco_stop_depth / 10.0f) + pInput->pressure_surface_bar)) { vpm_violates_buehlmann = true; do { deco_stop_depth += 3.0; } while (!decom_tissue_test_tolerance(tissue_N2_saturation, tissue_He_saturation, vpm_buehlmann_safety_gradient, (deco_stop_depth / 10.0f) + pInput->pressure_surface_bar)); } } /* =============================================================================== */ /* PERFORM A SEPARATE "PROJECTED ASCENT" OUTSIDE OF THE MAIN PROGRAM TO MAKE */ /* SURE THAT AN INCREASE IN GAS LOADINGS DURING ASCENT TO THE FIRST STOP WILL */ /* NOT CAUSE A VIOLATION OF THE DECO CEILING. IF SO, ADJUST THE FIRST STOP */ /* DEEPER BASED ON STEP SIZE UNTIL A SAFE ASCENT CAN BE MADE. */ /* Note: this situation is a possibility when ascending from extremely deep */ /* dives or due to an unusual gas mix selection. */ /* CHECK AGAIN TO MAKE SURE THAT ADJUSTED FIRST STOP WILL NOT BE BELOW THE */ /* DECO ZONE. */ /* =============================================================================== */ if (deco_stop_depth < depth_start_of_deco_calc) { projected_ascent(&depth_start_of_deco_calc, &rate, &deco_stop_depth, &step_size); } /*if (deco_stop_depth > depth_start_of_deco_zone) { printf("\t\n"); printf(fmt_905); printf(fmt_900); printf("\nPROGRAM TERMINATED\n"); exit(1); }*/ /* =============================================================================== */ /* HANDLE THE SPECIAL CASE WHEN NO DECO STOPS ARE REQUIRED - ASCENT CAN BE */ /* MADE DIRECTLY TO THE SURFACE */ /* Write ascent data to output file and exit the Critical Volume Loop. */ /* =============================================================================== */ if (deco_stop_depth == 0.0f) { if(calc_nulltime) { return CALC_END; } if(pVpm->deco_zone_reached) { for(dp = 0;dp < DECOINFO_STRUCT_MAX_STOPS;dp++) { pDecoInfo->output_stop_length_seconds[dp] = 0; } pDecoInfo->output_ndl_seconds = 0; } return CALC_NDL; /* exit the critical volume l */ } /* =============================================================================== */ /* ASSIGN VARIABLES FOR ASCENT FROM START OF DECO ZONE TO FIRST STOP. SAVE */ /* FIRST STOP DEPTH FOR LATER USE WHEN COMPUTING THE FINAL ASCENT PROFILE */ /* =============================================================================== */ deco_stop_depth = fmaxf(deco_stop_depth,(float)pDiveSettings->last_stop_depth_bar * 10); starting_depth = depth_start_of_deco_calc; first_stop_depth = deco_stop_depth; first_stop = true; } /* =============================================================================== */ /* DECO STOP LOOP BLOCK WITHIN CRITICAL VOLUME LOOP */ /* This loop computes a decompression schedule to the surface during each */ /* iteration of the critical volume loop. No output is written from this */ /* loop, rather it computes a schedule from which the in-water portion of the */ /* total phase volume time (Deco_Phase_Volume_Time) can be extracted. Also, */ /* the gas loadings computed at the end of this loop are used the subroutine */ /* which computes the out-of-water portion of the total phase volume time */ /* (Surface_Phase_Volume_Time) for that schedule. */ /* Note that exit is made from the loop after last ascent is made to a deco */ /* stop depth that is less than or equal to zero. A final deco stop less */ /* than zero can happen when the user makes an odd step size change during */ /* ascent - such as specifying a 5 msw step size change at the 3 msw stop! */ /* =============================================================================== */ while(true) /* loop will run continuous there is an break statement */ { if(starting_depth > deco_stop_depth ) gas_loadings_ascent_descen(helium_pressure, nitrogen_pressure, starting_depth, deco_stop_depth, rate,first_stop); first_stop = false; if (deco_stop_depth <= 0.0f) { break; } if (number_of_changes > 1) { int i1 = number_of_changes; for (i = 2; i <= i1; ++i) { if (depth_change[i - 1] >= deco_stop_depth) { mix_number = mix_change[i - 1]; rate = rate_change[i - 1]; step_size = step_size_change[i - 1]; } } } if(vpm_b) { float fist_stop_depth2 = fmaxf(first_stop_depth,max_first_stop_depth); BOYLES_LAW_COMPENSATION(&fist_stop_depth2, &deco_stop_depth, &step_size); } decompression_stop(&deco_stop_depth, &step_size, false); starting_depth = deco_stop_depth; if(deco_stop_depth == (float)pDiveSettings->last_stop_depth_bar * 10) deco_stop_depth = 0; else { deco_stop_depth = deco_stop_depth - step_size; deco_stop_depth = fmaxf(deco_stop_depth,(float)pDiveSettings->last_stop_depth_bar * 10); } count++; //if(count > 14) //return CALC_CRITICAL2; /* L60: */ } return vpm_check_converged(calc_nulltime); } /* =============================================================================== */ /* COMPUTE TOTAL PHASE VOLUME TIME AND MAKE CRITICAL VOLUME COMPARISON */ /* The deco phase volume time is computed from the run time. The surface */ /* phase volume time is computed in a subroutine based on the surfacing gas */ /* loadings from previous deco loop block. Next the total phase volume time */ /* (in-water + surface) for each compartment is compared against the previous */ /* total phase volume time. The schedule is converged when the difference is */ /* less than or equal to 1 minute in any one of the 16 compartments. */ /* Note: the "phase volume time" is somewhat of a mathematical concept. */ /* It is the time divided out of a total integration of supersaturation */ /* gradient x time (in-water and surface). This integration is multiplied */ /* by the excess bubble number to represent the amount of free-gas released */ /* as a result of allowing a certain number of excess bubbles to form. */ /* =============================================================================== */ /* end of deco stop loop */ static int vpm_check_converged(_Bool calc_nulltime) { short i; float critical_volume_comparison; float r1; _Bool schedule_converged = false; deco_phase_volume_time = run_time - run_time_start_of_deco_zone; calc_surface_phase_volume_time(); for (i = 1; i <= 16; ++i) { phase_volume_time[i - 1] = deco_phase_volume_time + surface_phase_volume_time[i - 1]; critical_volume_comparison = (r1 = phase_volume_time[i - 1] - last_phase_volume_time[i - 1], fabs(r1)); if (critical_volume_comparison <= 1.0f) { schedule_converged = true; } } /* =============================================================================== */ /* CRITICAL VOLUME DECISION TREE BETWEEN LINES 70 AND 99 */ /* There are two options here. If the Critical Volume Agorithm setting is */ /* "on" and the schedule is converged, or the Critical Volume Algorithm */ /* setting was "off" in the first place, the program will re-assign variables */ /* to their values at the start of ascent (end of bottom time) and process */ /* a complete decompression schedule once again using all the same ascent */ /* parameters and first stop depth. This decompression schedule will match */ /* the last iteration of the Critical Volume Loop and the program will write */ /* the final deco schedule to the output file. */ /* Note: if the Critical Volume Agorithm setting was "off", the final deco */ /* schedule will be based on "Initial Allowable Supersaturation Gradients." */ /* If it was "on", the final schedule will be based on "Adjusted Allowable */ /* Supersaturation Gradients" (gradients that are "relaxed" as a result of */ /* the Critical Volume Algorithm). */ /* If the Critical Volume Agorithm setting is "on" and the schedule is not */ /* converged, the program will re-assign variables to their values at the */ /* start of the deco zone and process another trial decompression schedule. */ /* =============================================================================== */ /* L70: */ //Not more than 4 iteration allowed count_critical_volume_iteration++; if(count_critical_volume_iteration > 4) { //return CALC_FINAL_DECO; if(calc_nulltime) return CALC_FINAL_DECO; else return vpm_calc_final_deco(true); } if (schedule_converged || critical_volume_algorithm_off) { //return CALC_FINAL_DECO; if(calc_nulltime) return CALC_FINAL_DECO; else return vpm_calc_final_deco(true); /* final deco schedule */ /* exit critical volume l */ /* =============================================================================== */ /* IF SCHEDULE NOT CONVERGED, COMPUTE RELAXED ALLOWABLE SUPERSATURATION */ /* GRADIENTS WITH VPM CRITICAL VOLUME ALGORITHM AND PROCESS ANOTHER */ /* ITERATION OF THE CRITICAL VOLUME LOOP */ /* =============================================================================== */ } else { critical_volume(&deco_phase_volume_time); deco_phase_volume_time = 0.; run_time = run_time_start_of_deco_calc; starting_depth = depth_start_of_deco_calc; mix_number = mix_change[0]; rate = rate_change[0]; step_size = step_size_change[0]; for (i = 1; i <= 16; ++i) { last_phase_volume_time[i - 1] = phase_volume_time[i - 1]; helium_pressure[i - 1] = he_pressure_start_of_deco_zone[i - 1]; nitrogen_pressure[i - 1] = n2_pressure_start_of_deco_zone[i - 1]; } if(calc_nulltime) return CALC_CRITICAL; else return vpm_calc_critcal_volume(true, false); } /* end of critical volume decision */ /* L100: */ // }/* end of critical vol loop */ } static void vpm_calc_deco_ceiling(void) { short i; // hw 1601209 float r1; // hw 1601209 float stop_time; // hw 1601209 int count = 0; //static int dp_max; //static float surfacetime; // _Bool first_stop = false; float tissue_He_saturation[16]; float tissue_N2_saturation[16]; float vpm_buehlmann_safety_gradient = 1.0f - (((float)pDiveSettings->vpm_conservatism) / 40); //max_first_stop_depth = fmaxf(first_stop_depth,max_first_stop_depth); /** CALC DECO Ceiling ******************************************************************/ /** Not when Future stops */ if(vpm_calc_what == DECOSTOPS) { for (i = 1; i <= 16; ++i) { helium_pressure[i - 1] = he_pressure_start_of_deco_zone[i - 1]; nitrogen_pressure[i - 1] = n2_pressure_start_of_deco_zone[i - 1]; } run_time = run_time_start_of_ascent;// run_time_start_of_ascent; starting_depth = depth_change[0]; mix_number = mix_change[0]; rate = rate_change[0]; //gas_loadings_ascent_descen(helium_pressure,nitrogen_pressure, starting_depth, depth_start_of_deco_calc, rate, true); float deco_ceiling_depth = 0.0f; if(depth_start_of_deco_calc > max_deco_ceiling_depth) { calc_deco_ceiling(&deco_ceiling_depth, true); } if(buehlmannSafety) { for (i = 0; i < 16; i++) { tissue_He_saturation[i] = helium_pressure[i] / 10.0; tissue_N2_saturation[i] = nitrogen_pressure[i] / 10.0; } if(!decom_tissue_test_tolerance(tissue_N2_saturation, tissue_He_saturation, vpm_buehlmann_safety_gradient, (deco_ceiling_depth / 10.0f) + pInput->pressure_surface_bar)) { vpm_violates_buehlmann = true; do { deco_ceiling_depth += 0.1f; } while (!decom_tissue_test_tolerance(tissue_N2_saturation, tissue_He_saturation, vpm_buehlmann_safety_gradient, (deco_ceiling_depth / 10.0f) + pInput->pressure_surface_bar)); } } if (deco_ceiling_depth < depth_start_of_deco_calc) { projected_ascent(&depth_start_of_deco_calc, &rate, &deco_ceiling_depth, &step_size); } max_deco_ceiling_depth = fmaxf(max_deco_ceiling_depth,deco_ceiling_depth); if(depth_start_of_deco_calc > deco_ceiling_depth) { gas_loadings_ascent_descen(helium_pressure,nitrogen_pressure, depth_start_of_deco_calc,deco_ceiling_depth, rate, true); //surfacetime += segment_time; } if(vpm_b) { BOYLES_LAW_COMPENSATION(&max_deco_ceiling_depth, &deco_ceiling_depth, &step_size); } calc_deco_ceiling(&deco_ceiling_depth, false); // buehlmann safety if(vpm_violates_buehlmann) { for (i = 0; i < 16; i++) { tissue_He_saturation[i] = helium_pressure[i] / 10.0; tissue_N2_saturation[i] = nitrogen_pressure[i] / 10.0; } if(!decom_tissue_test_tolerance(tissue_N2_saturation, tissue_He_saturation, vpm_buehlmann_safety_gradient, (deco_ceiling_depth / 10.0f) + pInput->pressure_surface_bar)) { vpm_violates_buehlmann = true; do { deco_ceiling_depth += 0.1f; } while (!decom_tissue_test_tolerance(tissue_N2_saturation, tissue_He_saturation, vpm_buehlmann_safety_gradient, (deco_ceiling_depth / 10.0f) + pInput->pressure_surface_bar)); } } // output_ceiling_meter if(deco_ceiling_depth > first_stop_depth) deco_ceiling_depth = first_stop_depth; pDecoInfo->output_ceiling_meter = deco_ceiling_depth ; } else { pDecoInfo->output_ceiling_meter = 0.0; } // fix hw 160627 if(pDecoInfo->output_ceiling_meter < 0.0) pDecoInfo->output_ceiling_meter = 0.0; /*** End CALC ceiling ***************************************************/ } /* =============================================================================== */ /* DECO STOP LOOP BLOCK FOR FINAL DECOMPRESSION SCHEDULE */ /* =============================================================================== */ static int vpm_calc_final_deco(_Bool begin) { short i; float r1; float stop_time; int count = 0; static int dp_max; static float surfacetime; _Bool first_stop = false; float roundingValue = 0.0; uint16_t stop_time_seconds; max_first_stop_depth = fmaxf(first_stop_depth,max_first_stop_depth); if(begin) { gCNS_VPM = 0; dp_max = 0; for (i = 1; i <= 16; ++i) { helium_pressure[i - 1] = he_pressure_start_of_ascent[i - 1]; nitrogen_pressure[i - 1] = n2_pressure_start_of_ascent[i - 1]; } run_time = run_time_start_of_ascent;// run_time_start_of_ascent; starting_depth = depth_change[0]; mix_number = mix_change[0]; rate = rate_change[0]; step_size = step_size_change[0]; deco_stop_depth = first_stop_depth; max_first_stop_depth = fmaxf(first_stop_depth,max_first_stop_depth); last_run_time = 0.; /* =============================================================================== */ /* DECO STOP LOOP BLOCK FOR FINAL DECOMPRESSION SCHEDULE */ /* =============================================================================== */ surfacetime = 0; first_stop = true; } while(true) /* loop will run continuous until there is an break statement */ { if(starting_depth > deco_stop_depth) { gas_loadings_ascent_descen(helium_pressure,nitrogen_pressure, starting_depth,deco_stop_depth, rate, first_stop); surfacetime += segment_time; } /* =============================================================================== */ /* DURING FINAL DECOMPRESSION SCHEDULE PROCESS, COMPUTE MAXIMUM ACTUAL */ /* SUPERSATURATION GRADIENT RESULTING IN EACH COMPARTMENT */ /* If there is a repetitive dive, this will be used later in the VPM */ /* Repetitive Algorithm to adjust the values for critical radii. */ /* =============================================================================== */ if(vpm_calc_what == DECOSTOPS) calc_max_actual_gradient(&deco_stop_depth); if (deco_stop_depth <= 0.0f) { break; } if (number_of_changes > 1) { int i1 = number_of_changes; for (i = 2; i <= i1; ++i) { if (depth_change[i - 1] >= deco_stop_depth) { mix_number = mix_change[i - 1]; rate = rate_change[i - 1]; step_size = step_size_change[i - 1]; } } } if(first_stop) { run_time_first_stop = run_time; first_stop = false; } if(vpm_b) { BOYLES_LAW_COMPENSATION(&max_first_stop_depth, &deco_stop_depth, &step_size); } decompression_stop(&deco_stop_depth, &step_size, true); /* =============================================================================== */ /* This next bit justs rounds up the stop time at the first stop to be in */ /* whole increments of the minimum stop time (to make for a nice deco table). */ /* =============================================================================== */ if (last_run_time == 0.0f) { r1 = segment_time / minimum_deco_stop_time + 0.5f; stop_time = r_int(&r1) * minimum_deco_stop_time; } else { stop_time = run_time - last_run_time; } stop_time = segment_time; surfacetime += stop_time; if((vpm_calc_what == DECOSTOPS) || (vpm_calc_what == BAILOUTSTOPS)) { int dp = 0; if(deco_stop_depth == (float)pDiveSettings->last_stop_depth_bar * 10) { dp = 0; } else { roundingValue = (deco_stop_depth - (pDiveSettings->input_second_to_last_stop_depth_bar * 10.0)) / step_size; dp = 1 + r_nint(&roundingValue); } //dp_max = (int)fmaxf(dp_max,dp); if(dp > dp_max) { dp_max = dp; } if(dp < DECOINFO_STRUCT_MAX_STOPS) { stop_time_seconds = (uint16_t)(fminf((999.9 * 60.0), (stop_time *60.0))); // //if(vpm_calc_what == DECOSTOPS) pDecoInfo->output_stop_length_seconds[dp] = stop_time_seconds; //else //decostop_bailout[dp] = (unsigned short)stop_time_seconds; } } /* =============================================================================== */ /* DURING FINAL DECOMPRESSION SCHEDULE, IF MINIMUM STOP TIME PARAMETER IS A */ /* WHOLE NUMBER (i.e. 1 minute) THEN WRITE DECO SCHEDULE USING short */ /* NUMBERS (looks nicer). OTHERWISE, USE DECIMAL NUMBERS. */ /* Note: per the request of a noted exploration diver(!), program now allows */ /* a minimum stop time of less than one minute so that total ascent time can */ /* be minimized on very long dives. In fact, with step size set at 1 fsw or */ /* 0.2 msw and minimum stop time set at 0.1 minute (6 seconds), a near */ /* continuous decompression schedule can be computed. */ /* =============================================================================== */ starting_depth = deco_stop_depth; if(deco_stop_depth == (float)pDiveSettings->last_stop_depth_bar * 10) deco_stop_depth = 0; else { deco_stop_depth = deco_stop_depth - step_size; deco_stop_depth = fmaxf(deco_stop_depth,(float)pDiveSettings->last_stop_depth_bar * 10); } last_run_time = run_time; count++; //if(count > 14) //return CALC_FINAL_DECO2; /* L80: */ } /* for final deco sche */ if( (vpm_calc_what == DECOSTOPS) || (vpm_calc_what == BAILOUTSTOPS)) { for(int dp = dp_max +1;dp < DECOINFO_STRUCT_MAX_STOPS;dp++) { //if(vpm_calc_what == DECOSTOPS) pDecoInfo->output_stop_length_seconds[dp] = 0; //else //decostop_bailout[dp] = 0; } } pDecoInfo->output_time_to_surface_seconds = (int)(surfacetime * 60.0); pDecoInfo->output_ndl_seconds = 0; vpm_calc_deco_ceiling(); /* end of deco stop lo */ return CALC_END; } /* =============================================================================== */ /* SUBROUTINE NUCLEAR_REGENERATION */ /* Purpose: This subprogram calculates the regeneration of VPM critical */ /* radii that takes place over the dive time. The regeneration time constant */ /* has a time scale of weeks so this will have very little impact on dives of */ /* normal length, but will have a major impact for saturation dives. */ /* =============================================================================== */ static int nuclear_regeneration(float *dive_time) { /* Local variables */ float crush_pressure_adjust_ratio_he, ending_radius_n2, ending_radius_he; short i; float crushing_pressure_pascals_n2, crushing_pressure_pascals_he, adj_crush_pressure_n2_pascals, adj_crush_pressure_he_pascals, crush_pressure_adjust_ratio_n2; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* First convert the maximum crushing pressure obtained for each compartment */ /* to Pascals. Next, compute the ending radius for helium and nitrogen */ /* critical nuclei in each compartment. */ /* =============================================================================== */ for (i = 1; i <= 16; ++i) { crushing_pressure_pascals_he = pVpm->max_crushing_pressure_he[i - 1] / UNITS_FACTOR * 101325.0f; crushing_pressure_pascals_n2 = pVpm->max_crushing_pressure_n2[i - 1] / UNITS_FACTOR * 101325.0f; ending_radius_he = 1.0f / (crushing_pressure_pascals_he / ((SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) * 2.0f) + 1.0f / pVpm->adjusted_critical_radius_he[i - 1]); ending_radius_n2 = 1.0f / (crushing_pressure_pascals_n2 / ((SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) * 2.0f) + 1.0f / pVpm->adjusted_critical_radius_n2[i - 1]); /* =============================================================================== */ /* A "regenerated" radius for each nucleus is now calculated based on the */ /* regeneration time constant. This means that after application of */ /* crushing pressure and reduction in radius, a nucleus will slowly grow */ /* back to its original initial radius over a period of time. This */ /* phenomenon is probabilistic in nature and depends on absolute temperature. */ /* It is independent of crushing pressure. */ /* =============================================================================== */ regenerated_radius_he[i - 1] = pVpm->adjusted_critical_radius_he[i - 1] + (ending_radius_he - pVpm->adjusted_critical_radius_he[i - 1]) * expf(-(*dive_time) / REGENERATION_TIME_CONSTANT); regenerated_radius_n2[i - 1] = pVpm->adjusted_critical_radius_n2[i - 1] + (ending_radius_n2 - pVpm->adjusted_critical_radius_n2[i - 1]) * expf(-(*dive_time) / REGENERATION_TIME_CONSTANT); /* =============================================================================== */ /* In order to preserve reference back to the initial critical radii after */ /* regeneration, an "adjusted crushing pressure" for the nuclei in each */ /* compartment must be computed. In other words, this is the value of */ /* crushing pressure that would have reduced the original nucleus to the */ /* to the present radius had regeneration not taken place. The ratio */ /* for adjusting crushing pressure is obtained from algebraic manipulation */ /* of the standard VPM equations. The adjusted crushing pressure, in lieu */ /* of the original crushing pressure, is then applied in the VPM Critical */ /* Volume Algorithm and the VPM Repetitive Algorithm. */ /* =============================================================================== */ crush_pressure_adjust_ratio_he = ending_radius_he * (pVpm->adjusted_critical_radius_he[i - 1] - regenerated_radius_he[i - 1]) / (regenerated_radius_he[i - 1] * (pVpm->adjusted_critical_radius_he[i - 1] - ending_radius_he)); crush_pressure_adjust_ratio_n2 = ending_radius_n2 * (pVpm->adjusted_critical_radius_n2[i - 1] - regenerated_radius_n2[i - 1]) / (regenerated_radius_n2[i - 1] * (pVpm->adjusted_critical_radius_n2[i - 1] - ending_radius_n2)); adj_crush_pressure_he_pascals = crushing_pressure_pascals_he * crush_pressure_adjust_ratio_he; adj_crush_pressure_n2_pascals = crushing_pressure_pascals_n2 * crush_pressure_adjust_ratio_n2; pVpm->adjusted_crushing_pressure_he[i - 1] = adj_crush_pressure_he_pascals / 101325.0f * UNITS_FACTOR; pVpm->adjusted_crushing_pressure_n2[i - 1] = adj_crush_pressure_n2_pascals / 101325.0f * UNITS_FACTOR; } return 0; } /* nuclear_regeneration */ /* =============================================================================== */ /* SUBROUTINE CALC_INITIAL_ALLOWABLE_GRADIENT */ /* Purpose: This subprogram calculates the initial allowable gradients for */ /* helium and nitrogren in each compartment. These are the gradients that */ /* will be used to set the deco ceiling on the first pass through the deco */ /* loop. If the Critical Volume Algorithm is set to "off", then these */ /* gradients will determine the final deco schedule. Otherwise, if the */ /* Critical Volume Algorithm is set to "on", these gradients will be further */ /* "relaxed" by the Critical Volume Algorithm subroutine. The initial */ /* allowable gradients are referred to as "PssMin" in the papers by Yount */ /* and colleauges, i.e., the minimum supersaturation pressure gradients */ /* that will probe bubble formation in the VPM nuclei that started with the */ /* designated minimum initial radius (critical radius). */ /* The initial allowable gradients are computed directly from the */ /* "regenerated" radii after the Nuclear Regeneration subroutine. These */ /* gradients are tracked separately for helium and nitrogen. */ /* =============================================================================== */ static int calc_initial_allowable_gradient() { float initial_allowable_grad_n2_pa, initial_allowable_grad_he_pa; short i; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* The initial allowable gradients are computed in Pascals and then converted */ /* to the diving pressure units. Two different sets of arrays are used to */ /* save the calculations - Initial Allowable Gradients and Allowable */ /* Gradients. The Allowable Gradients are assigned the values from Initial */ /* Allowable Gradients however the Allowable Gradients can be changed later */ /* by the Critical Volume subroutine. The values for the Initial Allowable */ /* Gradients are saved in a global array for later use by both the Critical */ /* Volume subroutine and the VPM Repetitive Algorithm subroutine. */ /* =============================================================================== */ for (i = 1; i <= 16; ++i) { initial_allowable_grad_n2_pa = SURFACE_TENSION_GAMMA * 2.0f * (SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) / (regenerated_radius_n2[i - 1] * SKIN_COMPRESSION_GAMMAC); initial_allowable_grad_he_pa = SURFACE_TENSION_GAMMA * 2.0f * (SKIN_COMPRESSION_GAMMAC - SURFACE_TENSION_GAMMA) / (regenerated_radius_he[i - 1] * SKIN_COMPRESSION_GAMMAC); pVpm->initial_allowable_gradient_n2[i - 1] = initial_allowable_grad_n2_pa / 101325.0f * UNITS_FACTOR; pVpm->initial_allowable_gradient_he[i - 1] = initial_allowable_grad_he_pa / 101325.0f * UNITS_FACTOR; allowable_gradient_he[i - 1] = pVpm->initial_allowable_gradient_he[i - 1]; allowable_gradient_n2[i - 1] = pVpm->initial_allowable_gradient_n2[i - 1]; } return 0; } /* calc_initial_allowable_gradient */ /* =============================================================================== */ /* SUBROUTINE CALC_DECO_CEILING */ /* Purpose: This subprogram calculates the deco ceiling (the safe ascent */ /* depth) in each compartment, based on the allowable gradients, and then */ /* finds the deepest deco ceiling across all compartments. This deepest */ /* value (Deco Ceiling Depth) is then used by the Decompression Stop */ /* subroutine to determine the actual deco schedule. */ /* =============================================================================== */ static int calc_deco_ceiling(float *deco_ceiling_depth,_Bool fallowable) { /* System generated locals */ float r1, r2; /* Local variables */ float weighted_allowable_gradient; short i; float compartment_deco_ceiling[16], gas_loading, tolerated_ambient_pressure; float gradient_he, gradient_n2; if(!vpm_b) fallowable = true; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* Since there are two sets of allowable gradients being tracked, one for */ /* helium and one for nitrogen, a "weighted allowable gradient" must be */ /* computed each time based on the proportions of helium and nitrogen in */ /* each compartment. This proportioning follows the methodology of */ /* Buhlmann/Keller. If there is no helium and nitrogen in the compartment, */ /* such as after extended periods of oxygen breathing, then the minimum value */ /* across both gases will be used. It is important to note that if a */ /* compartment is empty of helium and nitrogen, then the weighted allowable */ /* gradient formula cannot be used since it will result in division by zero. */ /* =============================================================================== */ for (i = 1; i <= 16; ++i) { // abfrage raus und pointer stattdessen if(fallowable){ gradient_he = allowable_gradient_he[i-1]; gradient_n2 = allowable_gradient_n2[i-1]; } else{ gradient_he = deco_gradient_he[i-1]; gradient_n2 = deco_gradient_n2[i-1]; } gas_loading = helium_pressure[i - 1] + nitrogen_pressure[i - 1]; if (gas_loading > 0) { weighted_allowable_gradient = (gradient_he * helium_pressure[i - 1] + gradient_n2 * nitrogen_pressure[i - 1]) / (helium_pressure[i - 1] + nitrogen_pressure[i - 1]); tolerated_ambient_pressure = gas_loading + CONSTANT_PRESSURE_OTHER_GASES - weighted_allowable_gradient; } else { /* Computing MIN */ r1 = gradient_he; r2 = gradient_n2; weighted_allowable_gradient = fminf(r1,r2); tolerated_ambient_pressure = CONSTANT_PRESSURE_OTHER_GASES - weighted_allowable_gradient; } /* =============================================================================== */ /* The tolerated ambient pressure cannot be less than zero absolute, i.e., */ /* the vacuum of outer space! */ /* =============================================================================== */ if (tolerated_ambient_pressure < 0.0) { tolerated_ambient_pressure = 0.0; } compartment_deco_ceiling[i - 1] = tolerated_ambient_pressure - barometric_pressure; } /* =============================================================================== */ /* The Deco Ceiling Depth is computed in a loop after all of the individual */ /* compartment deco ceilings have been calculated. It is important that the */ /* Deco Ceiling Depth (max deco ceiling across all compartments) only be */ /* extracted from the compartment values and not be compared against some */ /* initialization value. For example, if MAX(Deco_Ceiling_Depth . .) was */ /* compared against zero, this could cause a program lockup because sometimes */ /* the Deco Ceiling Depth needs to be negative (but not less than zero */ /* absolute ambient pressure) in order to decompress to the last stop at zero */ /* depth. */ /* =============================================================================== */ *deco_ceiling_depth = compartment_deco_ceiling[0]; for (i = 2; i <= 16; ++i) { /* Computing MAX */ r1 = *deco_ceiling_depth; r2 = compartment_deco_ceiling[i - 1]; *deco_ceiling_depth = fmaxf(r1,r2); } return 0; } /* calc_deco_ceiling */ /* =============================================================================== */ /* SUBROUTINE CALC_MAX_ACTUAL_GRADIENT */ /* Purpose: This subprogram calculates the actual supersaturation gradient */ /* obtained in each compartment as a result of the ascent profile during */ /* decompression. Similar to the concept with crushing pressure, the */ /* supersaturation gradients are not cumulative over a multi-level, staged */ /* ascent. Rather, it will be the maximum value obtained in any one discrete */ /* step of the overall ascent. Thus, the program must compute and store the */ /* maximum actual gradient for each compartment that was obtained across all */ /* steps of the ascent profile. This subroutine is invoked on the last pass */ /* through the deco stop loop block when the final deco schedule is being */ /* generated. */ /* */ /* The max actual gradients are later used by the VPM Repetitive Algorithm to */ /* determine if adjustments to the critical radii are required. If the max */ /* actual gradient did not exceed the initial alllowable gradient, then no */ /* adjustment will be made. However, if the max actual gradient did exceed */ /* the intitial allowable gradient, such as permitted by the Critical Volume */ /* Algorithm, then the critical radius will be adjusted (made larger) on the */ /* repetitive dive to compensate for the bubbling that was allowed on the */ /* previous dive. The use of the max actual gradients is intended to prevent */ /* the repetitive algorithm from being overly conservative. */ /* =============================================================================== */ static int calc_max_actual_gradient(float *deco_stop_depth) { /* System generated locals */ float r1; /* Local variables */ short i; float compartment_gradient; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* Note: negative supersaturation gradients are meaningless for this */ /* application, so the values must be equal to or greater than zero. */ /* =============================================================================== */ for (i = 1; i <= 16; ++i) { compartment_gradient = helium_pressure[i - 1] + nitrogen_pressure[i - 1] + CONSTANT_PRESSURE_OTHER_GASES - (*deco_stop_depth + barometric_pressure); if (compartment_gradient <= 0.0f) { compartment_gradient = 0.0f; } /* Computing MAX */ r1 = pVpm->max_actual_gradient[i - 1]; pVpm->max_actual_gradient[i - 1] = fmaxf(r1, compartment_gradient); } return 0; } /* calc_max_actual_gradient */ /* =============================================================================== */ /* SUBROUTINE CALC_SURFACE_PHASE_VOLUME_TIME */ /* Purpose: This subprogram computes the surface portion of the total phase */ /* volume time. This is the time factored out of the integration of */ /* supersaturation gradient x time over the surface interval. The VPM */ /* considers the gradients that allow bubbles to form or to drive bubble */ /* growth both in the water and on the surface after the dive. */ /* This subroutine is a new development to the VPM algorithm in that it */ /* computes the time course of supersaturation gradients on the surface */ /* when both helium and nitrogen are present. Refer to separate write-up */ /* for a more detailed explanation of this algorithm. */ /* =============================================================================== */ static int calc_surface_phase_volume_time() { /* Local variables */ float decay_time_to_zero_gradient; short i; float integral_gradient_x_time, surface_inspired_n2_pressure; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* =============================================================================== */ surface_inspired_n2_pressure = (barometric_pressure - WATER_VAPOR_PRESSURE) * 0.79f; for (i = 1; i <= 16; ++i) { if (nitrogen_pressure[i - 1] > surface_inspired_n2_pressure) { surface_phase_volume_time[i - 1] = (helium_pressure[i - 1] / HELIUM_TIME_CONSTANT[i - 1] + (nitrogen_pressure[i - 1] - surface_inspired_n2_pressure) / NITROGEN_TIME_CONSTANT[i - 1]) / (helium_pressure[i - 1] + nitrogen_pressure[i - 1] - surface_inspired_n2_pressure); } else if (nitrogen_pressure[i - 1] <= surface_inspired_n2_pressure && helium_pressure[i - 1] + nitrogen_pressure[i - 1] >= surface_inspired_n2_pressure) { decay_time_to_zero_gradient = 1.0f / (NITROGEN_TIME_CONSTANT[i - 1] - HELIUM_TIME_CONSTANT[i - 1]) * log((surface_inspired_n2_pressure - nitrogen_pressure[i - 1]) / helium_pressure[i - 1]); integral_gradient_x_time = helium_pressure[i - 1] / HELIUM_TIME_CONSTANT[i - 1] * (1.0f - expf(-HELIUM_TIME_CONSTANT[i - 1] * decay_time_to_zero_gradient)) + (nitrogen_pressure[i - 1] - surface_inspired_n2_pressure) / NITROGEN_TIME_CONSTANT[i - 1] * (1.0f - expf(-NITROGEN_TIME_CONSTANT[i - 1] * decay_time_to_zero_gradient)); surface_phase_volume_time[i - 1] = integral_gradient_x_time / (helium_pressure[i - 1] + nitrogen_pressure[i - 1] - surface_inspired_n2_pressure); } else { surface_phase_volume_time[i - 1] = 0.0f; } } return 0; } /* calc_surface_phase_volume_time */ /* =============================================================================== */ /* SUBROUTINE CRITICAL_VOLUME */ /* Purpose: This subprogram applies the VPM Critical Volume Algorithm. This */ /* algorithm will compute "relaxed" gradients for helium and nitrogen based */ /* on the setting of the Critical Volume Parameter Lambda. */ /* =============================================================================== */ static int critical_volume(float *deco_phase_volume_time) { /* System generated locals */ float r1; /* Local variables */ float initial_allowable_grad_n2_pa, initial_allowable_grad_he_pa, parameter_lambda_pascals, b, c; short i; float new_allowable_grad_n2_pascals, phase_volume_time[16], new_allowable_grad_he_pascals, adj_crush_pressure_n2_pascals, adj_crush_pressure_he_pascals; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* Note: Since the Critical Volume Parameter Lambda was defined in units of */ /* fsw-min in the original papers by Yount and colleauges, the same */ /* convention is retained here. Although Lambda is adjustable only in units */ /* of fsw-min in the program settings (range from 6500 to 8300 with default */ /* 7500), it will convert to the proper value in Pascals-min in this */ /* subroutine regardless of which diving pressure units are being used in */ /* the main program - feet of seawater (fsw) or meters of seawater (msw). */ /* The allowable gradient is computed using the quadratic formula (refer to */ /* separate write-up posted on the Deco List web site). */ /* =============================================================================== */ /** ****************************************************************************** * @brief critical_volume comment by hw * @version V0.0.1 * @date 19-April-2014 * @retval global: allowable_gradient_he[i], allowable_gradient_n2[i] ****************************************************************************** */ parameter_lambda_pascals = CRIT_VOLUME_PARAMETER_LAMBDA / 33.0f * 101325.0f; for (i = 1; i <= 16; ++i) { phase_volume_time[i - 1] = *deco_phase_volume_time + surface_phase_volume_time[i - 1]; } for (i = 1; i <= 16; ++i) { adj_crush_pressure_he_pascals = pVpm->adjusted_crushing_pressure_he[i - 1] / UNITS_FACTOR * 101325.0f; initial_allowable_grad_he_pa = pVpm->initial_allowable_gradient_he[i - 1] / UNITS_FACTOR * 101325.0f; b = initial_allowable_grad_he_pa + parameter_lambda_pascals * SURFACE_TENSION_GAMMA / ( SKIN_COMPRESSION_GAMMAC * phase_volume_time[i - 1]); c = SURFACE_TENSION_GAMMA * ( SURFACE_TENSION_GAMMA * ( parameter_lambda_pascals * adj_crush_pressure_he_pascals)) / (SKIN_COMPRESSION_GAMMAC * (SKIN_COMPRESSION_GAMMAC * phase_volume_time[i - 1])); /* Computing 2nd power */ r1 = b; new_allowable_grad_he_pascals = (b + sqrtf(r1 * r1 - c * 4.0f)) / 2.0f; /* modify global variable */ allowable_gradient_he[i - 1] = new_allowable_grad_he_pascals / 101325.0f * UNITS_FACTOR; } for (i = 1; i <= 16; ++i) { adj_crush_pressure_n2_pascals = pVpm->adjusted_crushing_pressure_n2[i - 1] / UNITS_FACTOR * 101325.0f; initial_allowable_grad_n2_pa = pVpm->initial_allowable_gradient_n2[i - 1] / UNITS_FACTOR * 101325.0f; b = initial_allowable_grad_n2_pa + parameter_lambda_pascals * SURFACE_TENSION_GAMMA / ( SKIN_COMPRESSION_GAMMAC * phase_volume_time[i - 1]); c = SURFACE_TENSION_GAMMA * (SURFACE_TENSION_GAMMA * (parameter_lambda_pascals * adj_crush_pressure_n2_pascals)) / (SKIN_COMPRESSION_GAMMAC * (SKIN_COMPRESSION_GAMMAC * phase_volume_time[i - 1])); /* Computing 2nd power */ r1 = b; new_allowable_grad_n2_pascals = (b + sqrtf(r1 * r1 - c * 4.0f)) / 2.0f; /* modify global variable */ allowable_gradient_n2[i - 1] = new_allowable_grad_n2_pascals / 101325.0f * UNITS_FACTOR; } return 0; } /* critical_volume */ /* =============================================================================== */ /* SUBROUTINE CALC_START_OF_DECO_ZONE */ /* Purpose: This subroutine uses the Bisection Method to find the depth at */ /* which the leading compartment just enters the decompression zone. */ /* Source: "Numerical Recipes in Fortran 77", Cambridge University Press, */ /* 1992. */ /* =============================================================================== */ static int calc_start_of_deco_zone(float *starting_depth, float *rate, float *depth_start_of_deco_zone) { /* Local variables */ float last_diff_change, initial_helium_pressure, mid_range_nitrogen_pressure; short i, j; float initial_inspired_n2_pressure, cpt_depth_start_of_deco_zone, 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, starting_ambient_pressure, initial_nitrogen_pressure, function_at_high_bound; float time_to_start_of_deco_zone, high_bound_helium_pressure, helium_rate, differential_change; float fraction_helium_begin; float fraction_helium_end; float fraction_nitrogen_begin; float fraction_nitrogen_end; float ending_ambient_pressure; float time_test; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* First initialize some variables */ /* =============================================================================== */ *depth_start_of_deco_zone = 0.0f; starting_ambient_pressure = *starting_depth + barometric_pressure; //>>>>>>>>>>>>>>>>>>>> //Test depth to calculate helium_rate and nitrogen_rate ending_ambient_pressure = starting_ambient_pressure/2; time_test = (ending_ambient_pressure - starting_ambient_pressure) / *rate; decom_get_inert_gases(starting_ambient_pressure / 10.0, (&pDiveSettings->decogaslist[mix_number]), &fraction_nitrogen_begin, &fraction_helium_begin ); decom_get_inert_gases(ending_ambient_pressure / 10.0, (&pDiveSettings->decogaslist[mix_number]), &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)/time_test; nitrogen_rate = ((ending_ambient_pressure - WATER_VAPOR_PRESSURE)* fraction_nitrogen_end - initial_inspired_n2_pressure)/time_test; //>>>>>>>>>>>>>>>>>>>>> /*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];*/ /* =============================================================================== */ /* ESTABLISH THE BOUNDS FOR THE ROOT SEARCH USING THE BISECTION METHOD */ /* AND CHECK TO MAKE SURE THAT THE ROOT WILL BE WITHIN BOUNDS. PROCESS */ /* EACH COMPARTMENT INDIVIDUALLY AND FIND THE MAXIMUM DEPTH ACROSS ALL */ /* COMPARTMENTS (LEADING COMPARTMENT) */ /* In this case, we are solving for time - the time when the gas tension in */ /* the compartment will be equal to ambient pressure. The low bound for time */ /* is set at zero and the high bound is set at the time it would take to */ /* ascend to zero ambient pressure (absolute). Since the ascent rate is */ /* negative, a multiplier of -1.0 is used to make the time positive. The */ /* desired point when gas tension equals ambient pressure is found at a time */ /* 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. */ /* =============================================================================== */ low_bound = 0.0; high_bound = starting_ambient_pressure / *rate * -1.0f; for (i = 1; i <= 16; ++i) { initial_helium_pressure = helium_pressure[i - 1]; initial_nitrogen_pressure = nitrogen_pressure[i - 1]; function_at_low_bound = initial_helium_pressure + initial_nitrogen_pressure + CONSTANT_PRESSURE_OTHER_GASES - starting_ambient_pressure; high_bound_helium_pressure = schreiner_equation__2(&initial_inspired_he_pressure, &helium_rate, &high_bound, &HELIUM_TIME_CONSTANT[i - 1], &initial_helium_pressure); high_bound_nitrogen_pressure = schreiner_equation__2(&initial_inspired_n2_pressure, &nitrogen_rate, &high_bound, &NITROGEN_TIME_CONSTANT[i - 1], &initial_nitrogen_pressure); function_at_high_bound = high_bound_helium_pressure + high_bound_nitrogen_pressure + CONSTANT_PRESSURE_OTHER_GASES; 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_to_start_of_deco_zone = low_bound; differential_change = high_bound - low_bound; } else { time_to_start_of_deco_zone = 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_to_start_of_deco_zone + differential_change; mid_range_helium_pressure = schreiner_equation__2(&initial_inspired_he_pressure, &helium_rate, &mid_range_time, &HELIUM_TIME_CONSTANT[i - 1], &initial_helium_pressure); mid_range_nitrogen_pressure = schreiner_equation__2(&initial_inspired_n2_pressure, &nitrogen_rate, &mid_range_time, &NITROGEN_TIME_CONSTANT[i - 1], &initial_nitrogen_pressure); function_at_mid_range = mid_range_helium_pressure + mid_range_nitrogen_pressure + CONSTANT_PRESSURE_OTHER_GASES - (starting_ambient_pressure + *rate * mid_range_time); if (function_at_mid_range <= 0.0f) { time_to_start_of_deco_zone = mid_range_time; } if( fabs(differential_change) < 0.001f || function_at_mid_range == 0.0f) { goto L170; } /* L150: */ } printf("\nERROR! ROOT SEARCH EXCEEDED MAXIMUM ITERATIONS"); //pause(); /* =============================================================================== */ /* When a solution with the desired accuracy is found, the program jumps out */ /* of the loop to Line 170 and assigns the solution value for the individual */ /* compartment. */ /* =============================================================================== */ L170: cpt_depth_start_of_deco_zone = starting_ambient_pressure + *rate * time_to_start_of_deco_zone - barometric_pressure; /* =============================================================================== */ /* The overall solution will be the compartment with the maximum depth where */ /* gas tension equals ambient pressure (leading compartment). */ /* =============================================================================== */ *depth_start_of_deco_zone = fmaxf(*depth_start_of_deco_zone, cpt_depth_start_of_deco_zone); /* L200: */ } return 0; } /* calc_start_of_deco_zone */ /* =============================================================================== */ /* SUBROUTINE PROJECTED_ASCENT */ /* Purpose: This subprogram performs a simulated ascent outside of the main */ /* program to ensure that a deco ceiling will not be violated due to unusual */ /* gas loading during ascent (on-gassing). If the deco ceiling is violated, */ /* the stop depth will be adjusted deeper by the step size until a safe */ /* ascent can be made. */ /* =============================================================================== */ static int projected_ascent(float *starting_depth, float *rate, float *deco_stop_depth, float *step_size) { /* Local variables */ float weighted_allowable_gradient, ending_ambient_pressure, temp_gas_loading[16]; int i; float allowable_gas_loading[16]; float temp_nitrogen_pressure[16]; float temp_helium_pressure[16]; float run_time_save = 0; /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* =============================================================================== */ L665: ending_ambient_pressure = *deco_stop_depth + barometric_pressure; for (i = 1; i <= 16; ++i) { temp_helium_pressure[i - 1] = helium_pressure[i - 1]; temp_nitrogen_pressure[i - 1] = nitrogen_pressure[i - 1]; } run_time_save = run_time; gas_loadings_ascent_descen(temp_helium_pressure, temp_nitrogen_pressure, *starting_depth,*deco_stop_depth,*rate,true); run_time = run_time_save; for (i = 1; i <= 16; ++i) { temp_gas_loading[i - 1] = temp_helium_pressure[i - 1] + temp_nitrogen_pressure[i - 1]; if (temp_gas_loading[i - 1] > 0.0f) { weighted_allowable_gradient = (allowable_gradient_he[i - 1] * temp_helium_pressure[i - 1] + allowable_gradient_n2[i - 1] * temp_nitrogen_pressure[i - 1]) / temp_gas_loading[i - 1]; } else { /* Computing MIN */ weighted_allowable_gradient = fminf(allowable_gradient_he[i - 1],allowable_gradient_n2[i - 1]); } allowable_gas_loading[i - 1] = ending_ambient_pressure + weighted_allowable_gradient - CONSTANT_PRESSURE_OTHER_GASES; /* L670: */ } for (i = 1; i <= 16; ++i) { if (temp_gas_loading[i - 1] > allowable_gas_loading[i - 1]) { *deco_stop_depth += *step_size; goto L665; } /* L671: */ } return 0; } /* projected_ascent */ /* =============================================================================== */ /* SUBROUTINE DECOMPRESSION_STOP */ /* Purpose: This subprogram calculates the required time at each */ /* decompression stop. */ /* =============================================================================== */ static void decompression_stop(float *deco_stop_depth, float *step_size, _Bool final_deco_calculation) { /* Local variables */ float inspired_nitrogen_pressure; // short last_segment_number; // float weighted_allowable_gradient; float initial_helium_pressure[16]; /* by hw */ float initial_CNS = gCNS_VPM; //static float time_counter; short i; float ambient_pressure; float inspired_helium_pressure, next_stop; //last_run_time, //temp_segment_time; float deco_ceiling_depth, initial_nitrogen_pressure[16]; //round_up_operation; float fraction_helium_begin; float fraction_nitrogen_begin; int count = 0; _Bool buehlmann_wait = false; float tissue_He_saturation[16]; float tissue_N2_saturation[16]; float vpm_buehlmann_safety_gradient = 1.0f - (((float)pDiveSettings->vpm_conservatism) / 40); /* loop */ /* =============================================================================== */ /* CALCULATIONS */ /* =============================================================================== */ segment_time = 0; // temp_segment_time = segment_time; ambient_pressure = *deco_stop_depth + barometric_pressure; //ending_ambient_pressure = ambient_pressure; decom_get_inert_gases(ambient_pressure / 10, (&pDiveSettings->decogaslist[mix_number]), &fraction_nitrogen_begin, &fraction_helium_begin ); if(*deco_stop_depth == (float)(pDiveSettings->last_stop_depth_bar * 10)) next_stop = 0; else { next_stop = *deco_stop_depth - *step_size; next_stop = fmaxf(next_stop,(float)pDiveSettings->last_stop_depth_bar * 10); } inspired_helium_pressure = (ambient_pressure - WATER_VAPOR_PRESSURE) * fraction_helium_begin; inspired_nitrogen_pressure = (ambient_pressure - WATER_VAPOR_PRESSURE) * fraction_nitrogen_begin; /* =============================================================================== */ /* Check to make sure that program won't lock up if unable to decompress */ /* to the next stop. If so, write error message and terminate program. */ /* =============================================================================== */ //deco_ceiling_depth = next_stop +1; //deco_ceiling_depth = next_stop + 1; if(!vpm_violates_buehlmann) { calc_deco_ceiling(&deco_ceiling_depth, false); //weg, weil auf jeden Fall schleife für safety und so konservativer } else { deco_ceiling_depth = next_stop + 1.0; } if(deco_ceiling_depth > next_stop) { while (deco_ceiling_depth > next_stop) { segment_time += 60.0; if(segment_time >= 999.0 ) { segment_time = 999.0 ; run_time += segment_time; return; } //goto L700; initial_CNS = gCNS_VPM; decom_oxygen_calculate_cns_exposure(60*60,&pDiveSettings->decogaslist[mix_number],ambient_pressure/10,&gCNS_VPM); for (i = 0; i < 16; i++) { initial_helium_pressure[i] = helium_pressure[i]; initial_nitrogen_pressure[i] = nitrogen_pressure[i]; helium_pressure[i] += (inspired_helium_pressure - helium_pressure[i]) * float_buehlmann_He_factor_expositon_one_hour[i]; nitrogen_pressure[i] += (inspired_nitrogen_pressure - nitrogen_pressure[i]) * float_buehlmann_N2_factor_expositon_one_hour[i]; } calc_deco_ceiling(&deco_ceiling_depth, false); } if(deco_ceiling_depth < next_stop) { segment_time -= 60.0; gCNS_VPM = initial_CNS; for (i = 0; i < 16; i++) { helium_pressure[i] = initial_helium_pressure[i]; nitrogen_pressure[i] = initial_nitrogen_pressure[i]; } deco_ceiling_depth = next_stop +1; } count = 0; while (deco_ceiling_depth > next_stop && count < 13) { count++; segment_time += 5; //goto L700; initial_CNS = gCNS_VPM; decom_oxygen_calculate_cns_exposure(60*5,&pDiveSettings->decogaslist[mix_number],ambient_pressure/10,&gCNS_VPM); for (i = 0; i < 16; i++) { initial_helium_pressure[i] = helium_pressure[i]; initial_nitrogen_pressure[i] = nitrogen_pressure[i]; helium_pressure[i] += (inspired_helium_pressure - helium_pressure[i]) * float_buehlmann_He_factor_expositon_five_minutes[i]; nitrogen_pressure[i] += (inspired_nitrogen_pressure - nitrogen_pressure[i]) * float_buehlmann_N2_factor_expositon_five_minutes[i]; } calc_deco_ceiling(&deco_ceiling_depth, false); } if(deco_ceiling_depth < next_stop) { segment_time -= 5; gCNS_VPM = initial_CNS; for (i = 0; i < 16; i++) { helium_pressure[i] = initial_helium_pressure[i]; nitrogen_pressure[i] = initial_nitrogen_pressure[i]; } deco_ceiling_depth = next_stop +1; } buehlmann_wait = false; while (buehlmann_wait || (deco_ceiling_depth > next_stop)) { //time_counter = temp_segment_time; segment_time += 1.0; if(segment_time >= 999.0 ) { segment_time = 999.0 ; run_time += segment_time; return; } //goto L700; initial_CNS = gCNS_VPM; decom_oxygen_calculate_cns_exposure(60*1,&pDiveSettings->decogaslist[mix_number],ambient_pressure/10,&gCNS_VPM); for (i = 0; i < 16; i++) { initial_helium_pressure[i] = helium_pressure[i]; initial_nitrogen_pressure[i] = nitrogen_pressure[i]; helium_pressure[i] += (inspired_helium_pressure - helium_pressure[i]) * float_buehlmann_He_factor_expositon_one_minute[i]; nitrogen_pressure[i] += (inspired_nitrogen_pressure - nitrogen_pressure[i]) * float_buehlmann_N2_factor_expositon_one_minute[i]; } if(!buehlmann_wait) calc_deco_ceiling(&deco_ceiling_depth, false); if(buehlmannSafety && final_deco_calculation && !(deco_ceiling_depth > next_stop)) { for (i = 0; i < 16; i++) { tissue_He_saturation[i] = helium_pressure[i] / 10; tissue_N2_saturation[i] = nitrogen_pressure[i] / 10; } if( (fabsf(nitrogen_pressure[15] - inspired_nitrogen_pressure) < 0.00001f) && (fabsf(helium_pressure[15] - inspired_helium_pressure) < 0.00001f) && (fabsf(nitrogen_pressure[0] - inspired_nitrogen_pressure) < 0.00001f) && (fabsf(helium_pressure[0] - inspired_helium_pressure) < 0.00001f)) { buehlmann_wait_exceeded = true; break; } if(decom_tissue_test_tolerance(tissue_N2_saturation, tissue_He_saturation, vpm_buehlmann_safety_gradient, (next_stop / 10.0f) + pInput->pressure_surface_bar)) break; buehlmann_wait = true; } } if(buehlmann_wait) { vpm_violates_buehlmann = true; } if(!buehlmann_wait) { if(deco_ceiling_depth < next_stop) { segment_time -= 1; gCNS_VPM = initial_CNS; for (i = 0; i < 16; i++) { helium_pressure[i] = initial_helium_pressure[i]; nitrogen_pressure[i] = initial_nitrogen_pressure[i]; } deco_ceiling_depth = next_stop +1; } while (deco_ceiling_depth > next_stop) { //time_counter = temp_segment_time; segment_time += (float) 1.0f / 3.0f; //goto L700; initial_CNS = gCNS_VPM; decom_oxygen_calculate_cns_exposure(20,&pDiveSettings->decogaslist[mix_number],ambient_pressure/10,&gCNS_VPM); for (i = 0; i < 16; i++) { helium_pressure[i] += (inspired_helium_pressure - helium_pressure[i]) * float_buehlmann_He_factor_expositon_20_seconds[i]; nitrogen_pressure[i] += (inspired_nitrogen_pressure - nitrogen_pressure[i]) * float_buehlmann_N2_factor_expositon_20_seconds[i]; } calc_deco_ceiling(&deco_ceiling_depth, false); } } } /*float pressure_save =dive_data.pressure; dive_data.pressure = ambient_pressure/10; tissues_exposure_stage(st_deco_test,(int)(segment_time * 60), &dive_data, &gaslist); dive_data.pressure = pressure_save;*/ run_time += segment_time; return; } /* decompression_stop */ /* =============================================================================== */ // SUROUTINE BOYLES_LAW_COMPENSATION // Purpose: This subprogram calculates the reduction in allowable gradients // with decreasing ambient pressure during the decompression profile based // on Boyle's Law considerations. //=============================================================================== static void BOYLES_LAW_COMPENSATION (float* First_Stop_Depth, float* Deco_Stop_Depth, float* Step_Size) { short i; float Next_Stop; float Ambient_Pressure_First_Stop, Ambient_Pressure_Next_Stop; float Amb_Press_First_Stop_Pascals, Amb_Press_Next_Stop_Pascals; float A, B, C, Low_Bound, High_Bound, Ending_Radius; float Deco_Gradient_Pascals; float Allow_Grad_First_Stop_He_Pa, Radius_First_Stop_He; float Allow_Grad_First_Stop_N2_Pa, Radius_First_Stop_N2; //=============================================================================== // LO//AL ARRAYS //=============================================================================== // float Radius1_He[16], Radius2_He[16]; // float Radius1_N2[16], Radius2_N2[16]; float root_factor; //=============================================================================== // CALCULATIONS //=============================================================================== Next_Stop = *Deco_Stop_Depth - *Step_Size; Ambient_Pressure_First_Stop = *First_Stop_Depth + barometric_pressure; Ambient_Pressure_Next_Stop = Next_Stop + barometric_pressure; Amb_Press_First_Stop_Pascals = (Ambient_Pressure_First_Stop/UNITS_FACTOR) * 101325.0f; Amb_Press_Next_Stop_Pascals = (Ambient_Pressure_Next_Stop/UNITS_FACTOR) * 101325.0f; root_factor = powf(Amb_Press_First_Stop_Pascals/Amb_Press_Next_Stop_Pascals,1.0f / 3.0f); for( i = 0; i < 16;i++) { Allow_Grad_First_Stop_He_Pa = (allowable_gradient_he[i]/UNITS_FACTOR) * 101325.0f; Radius_First_Stop_He = (2.0f * SURFACE_TENSION_GAMMA) / Allow_Grad_First_Stop_He_Pa; // Radius1_He[i] = Radius_First_Stop_He; A = Amb_Press_Next_Stop_Pascals; B = -2.0f * SURFACE_TENSION_GAMMA; C = (Amb_Press_First_Stop_Pascals + (2.0f * SURFACE_TENSION_GAMMA)/ Radius_First_Stop_He)* Radius_First_Stop_He* (Radius_First_Stop_He*(Radius_First_Stop_He)); Low_Bound = Radius_First_Stop_He; High_Bound = Radius_First_Stop_He * root_factor; //*pow(Amb_Press_First_Stop_Pascals/Amb_Press_Next_Stop_Pascals,1.0/3.0); //*(Amb_Press_First_Stop_Pascals/Amb_Press_Next_Stop_Pascals)**(1.0/3.0); radius_root_finder(&A,&B,&C, &Low_Bound, &High_Bound, &Ending_Radius); // Radius2_He[i] = Ending_Radius; Deco_Gradient_Pascals = (2.0f * SURFACE_TENSION_GAMMA) / Ending_Radius; deco_gradient_he[i] = (Deco_Gradient_Pascals / 101325.0f)* UNITS_FACTOR; } for( i = 0; i < 16;i++) { Allow_Grad_First_Stop_N2_Pa = (allowable_gradient_n2[i]/UNITS_FACTOR) * 101325.0f; Radius_First_Stop_N2 = (2.0f * SURFACE_TENSION_GAMMA) / Allow_Grad_First_Stop_N2_Pa; // Radius1_N2[i] = Radius_First_Stop_N2; A = Amb_Press_Next_Stop_Pascals; B = -2.0f * SURFACE_TENSION_GAMMA; C = (Amb_Press_First_Stop_Pascals + (2.0f * SURFACE_TENSION_GAMMA)/ Radius_First_Stop_N2)* Radius_First_Stop_N2* (Radius_First_Stop_N2*(Radius_First_Stop_N2)); Low_Bound = Radius_First_Stop_N2; High_Bound = Radius_First_Stop_N2* root_factor;//pow(Amb_Press_First_Stop_Pascals/Amb_Press_Next_Stop_Pascals,1.0/3.0); //High_Bound = Radius_First_Stop_N2*exp(log(Amb_Press_First_Stop_Pascals/Amb_Press_Next_Stop_Pascals)/3); radius_root_finder(&A,&B,&C, &Low_Bound, &High_Bound, &Ending_Radius); // Radius2_N2[i] = Ending_Radius; Deco_Gradient_Pascals = (2.0f * SURFACE_TENSION_GAMMA) / Ending_Radius; deco_gradient_n2[i] = (Deco_Gradient_Pascals / 101325.0f)* UNITS_FACTOR; } } /* =============================================================================== */ // vpm_calc_ndl // Purpose: This function computes NDL (time where no decostops are needed) //=============================================================================== #define MAX_NDL 240 static int vpm_calc_ndl(void) { static float future_helium_pressure[16]; static float future_nitrogen_pressure[16]; static int temp_segment_time; static int mix_number; static float inspired_helium_pressure; static float inspired_nitrogen_pressure; float previous_helium_pressure[16]; float previous_nitrogen_pressure[16]; float ambient_pressure; float fraction_helium_begin; float fraction_nitrogen_begin; int i = 0; int count = 0; int status = CALC_END; for(i = 0; i < 16;i++) { future_helium_pressure[i] = pInput->tissue_helium_bar[i] * 10.0;//tissue_He_saturation[st_dive][i] * 10; future_nitrogen_pressure[i] = pInput->tissue_nitrogen_bar[i] * 10.0; } temp_segment_time = 0; mix_number = 0; ambient_pressure = pInput->pressure_ambient_bar * 10; decom_get_inert_gases( ambient_pressure / 10, (&pDiveSettings->decogaslist[mix_number]) , &fraction_nitrogen_begin, &fraction_helium_begin ); inspired_helium_pressure =(ambient_pressure - WATER_VAPOR_PRESSURE) * fraction_helium_begin; inspired_nitrogen_pressure =(ambient_pressure - WATER_VAPOR_PRESSURE) *fraction_nitrogen_begin; status = CALC_END; while (status == CALC_END) { count++; temp_segment_time += 60; if(temp_segment_time >= MAX_NDL) { pDecoInfo->output_ndl_seconds = temp_segment_time * 60; return CALC_NDL; } run_time += 60; //goto L700; for (i = 1; i <= 16; ++i) { previous_helium_pressure[i-1] = future_helium_pressure[i - 1]; previous_nitrogen_pressure[i - 1] = future_nitrogen_pressure[i - 1]; future_helium_pressure[i - 1] = future_helium_pressure[i - 1] + (inspired_helium_pressure - future_helium_pressure[i - 1]) * float_buehlmann_He_factor_expositon_one_hour[i-1]; future_nitrogen_pressure[i - 1] = future_nitrogen_pressure[i - 1] + (inspired_nitrogen_pressure - future_nitrogen_pressure[i - 1]) * float_buehlmann_N2_factor_expositon_one_hour[i-1]; helium_pressure[i - 1] = future_helium_pressure[i - 1]; nitrogen_pressure[i - 1] = future_nitrogen_pressure[i - 1]; } vpm_calc_deco(); while((status = vpm_calc_critcal_volume(true,true)) == CALC_CRITICAL); } temp_segment_time -= 60; run_time -= 60; for (i = 1; i <= 16; ++i) { future_helium_pressure[i - 1] = previous_helium_pressure[i-1]; future_nitrogen_pressure[i - 1] = previous_nitrogen_pressure[i - 1]; } status = CALC_END; if(temp_segment_time < 60) nullzeit_unter60 = true; while (status == CALC_END) { temp_segment_time += 5; if(temp_segment_time >= MAX_NDL) { pDecoInfo->output_ndl_seconds = temp_segment_time * 60; return CALC_NDL; } if(nullzeit_unter60 && temp_segment_time > 60) { nullzeit_unter60 = false; return CALC_NDL; } run_time += 5; //goto L700; for (i = 1; i <= 16; ++i) { previous_helium_pressure[i-1] = future_helium_pressure[i - 1]; previous_nitrogen_pressure[i - 1] = future_nitrogen_pressure[i - 1]; future_helium_pressure[i - 1] = future_helium_pressure[i - 1] + (inspired_helium_pressure - future_helium_pressure[i - 1]) * float_buehlmann_He_factor_expositon_five_minutes[i-1]; future_nitrogen_pressure[i - 1] = future_nitrogen_pressure[i - 1] + (inspired_nitrogen_pressure - future_nitrogen_pressure[i - 1]) * float_buehlmann_N2_factor_expositon_five_minutes[i-1]; helium_pressure[i - 1] = future_helium_pressure[i - 1]; nitrogen_pressure[i - 1] = future_nitrogen_pressure[i - 1]; } vpm_calc_deco(); while((status =vpm_calc_critcal_volume(true,true)) == CALC_CRITICAL); } temp_segment_time -= 5; run_time -= 5; for (i = 1; i <= 16; ++i) { future_helium_pressure[i - 1] = previous_helium_pressure[i-1]; future_nitrogen_pressure[i - 1] = previous_nitrogen_pressure[i - 1]; } status = CALC_END; if(temp_segment_time <= 20) { while (status == CALC_END) { temp_segment_time += minimum_deco_stop_time; run_time += minimum_deco_stop_time; //goto L700; for (i = 1; i <= 16; ++i) { future_helium_pressure[i - 1] = future_helium_pressure[i - 1] + (inspired_helium_pressure - future_helium_pressure[i - 1]) * float_buehlmann_He_factor_expositon_one_minute[i-1]; future_nitrogen_pressure[i - 1] = future_nitrogen_pressure[i - 1] + (inspired_nitrogen_pressure - future_nitrogen_pressure[i - 1]) * float_buehlmann_N2_factor_expositon_one_minute[i-1]; helium_pressure[i - 1] = future_helium_pressure[i - 1]; nitrogen_pressure[i - 1] =future_nitrogen_pressure[i - 1]; } vpm_calc_deco(); while((status =vpm_calc_critcal_volume(true,true)) == CALC_CRITICAL); } } else temp_segment_time += 5; pDecoInfo->output_ndl_seconds = temp_segment_time * 60; if(temp_segment_time > 1) return CALC_NDL; else return CALC_BEGIN; } void vpm_table_init() { vpmTable.output_time_to_surface_seconds = 0; vpmTableState = VPM_TABLE_INIT; } uint8_t vpm_get_decozone(void) { return((uint8_t)pVpm->depth_start_of_deco_zone_save); } SvpmTableState vpm_get_TableState(void) { return vpmTableState; }