/* Let's try to do some deco calculations. */ void calculate_deco_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool print_mode) { int i, count_iteration = 0; double surface_pressure = (dc->surface_pressure.mbar ? dc->surface_pressure.mbar : get_surface_pressure_in_mbar(dive, true)) / 1000.0; int last_ndl_tts_calc_time = 0; int first_ceiling = 0; bool first_iteration = true; int final_tts = 0 , time_clear_ceiling = 0, time_deep_ceiling = 0, deco_time = 0, prev_deco_time = 10000000; char *cache_data_initial = NULL; /* For VPM-B outside the planner, cache the initial deco state for CVA iterations */ if (prefs.deco_mode == VPMB && !in_planner()) cache_deco_state(&cache_data_initial); /* For VPM-B outside the planner, iterate until deco time converges (usually one or two iterations after the initial) * Set maximum number of iterations to 10 just in case */ while ((abs(prev_deco_time - deco_time) >= 30) && (count_iteration < 10)) { for (i = 1; i < pi->nr; i++) { struct plot_data *entry = pi->entry + i; int j, t0 = (entry - 1)->sec, t1 = entry->sec; int time_stepsize = 20; entry->ambpressure = depth_to_bar(entry->depth, dive); entry->gfline = MAX((double)prefs.gflow, (entry->ambpressure - surface_pressure) / (gf_low_pressure_this_dive - surface_pressure) * (prefs.gflow - prefs.gfhigh) + prefs.gfhigh) * (100.0 - AMB_PERCENTAGE) / 100.0 + AMB_PERCENTAGE; if (t0 > t1) { fprintf(stderr, "non-monotonous dive stamps %d %d\n", t0, t1); int xchg = t1; t1 = t0; t0 = xchg; } if (t0 != t1 && t1 - t0 < time_stepsize) time_stepsize = t1 - t0; for (j = t0 + time_stepsize; j <= t1; j += time_stepsize) { int depth = interpolate(entry[-1].depth, entry[0].depth, j - t0, t1 - t0); add_segment(depth_to_bar(depth, dive), &dive->cylinder[entry->cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, entry->sac); if ((t1 - j < time_stepsize) && (j < t1)) time_stepsize = t1 - j; } if (t0 == t1) { entry->ceiling = (entry - 1)->ceiling; } else { /* Keep updating the VPM-B gradients until the start of the ascent phase of the dive. */ if (prefs.deco_mode == VPMB && !in_planner() && (entry - 1)->ceiling >= first_ceiling && first_iteration == true) { nuclear_regeneration(t1); vpmb_start_gradient(); /* For CVA calculations, start by guessing deco time = dive time remaining */ deco_time = pi->maxtime - t1; vpmb_next_gradient(deco_time, surface_pressure / 1000.0); } entry->ceiling = deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, !prefs.calcceiling3m); /* If using VPM-B outside the planner, take first_ceiling_pressure as the deepest ceiling */ if (prefs.deco_mode == VPMB && !in_planner()) { if (entry->ceiling >= first_ceiling) { time_deep_ceiling = t1; first_ceiling = entry->ceiling; first_ceiling_pressure.mbar = depth_to_mbar(first_ceiling, dive); if (first_iteration) { nuclear_regeneration(t1); vpmb_start_gradient(); /* For CVA calculations, start by guessing deco time = dive time remaining */ deco_time = pi->maxtime - t1; vpmb_next_gradient(deco_time, surface_pressure / 1000.0); } } // Use the point where the ceiling clears as the end of deco phase for CVA calculations if (entry->ceiling > 0) time_clear_ceiling = 0; else if (time_clear_ceiling == 0) time_clear_ceiling = t1; } } for (j = 0; j < 16; j++) { double m_value = buehlmann_inertgas_a[j] + entry->ambpressure / buehlmann_inertgas_b[j]; entry->ceilings[j] = deco_allowed_depth(tolerated_by_tissue[j], surface_pressure, dive, 1); entry->percentages[j] = tissue_inertgas_saturation[j] < entry->ambpressure ? tissue_inertgas_saturation[j] / entry->ambpressure * AMB_PERCENTAGE : AMB_PERCENTAGE + (tissue_inertgas_saturation[j] - entry->ambpressure) / (m_value - entry->ambpressure) * (100.0 - AMB_PERCENTAGE); } /* should we do more calculations? * We don't for print-mode because this info doesn't show up there * If the ceiling hasn't cleared by the last data point, we need tts for VPM-B CVA calculation * It is not necessary to do these calculation on the first VPMB iteration, except for the last data point */ if ((prefs.calcndltts && !print_mode && (prefs.deco_mode != VPMB || in_planner() || !first_iteration)) || (prefs.deco_mode == VPMB && !in_planner() && i == pi->nr - 1)) { /* only calculate ndl/tts on every 30 seconds */ if ((entry->sec - last_ndl_tts_calc_time) < 30 && i != pi->nr - 1) { struct plot_data *prev_entry = (entry - 1); entry->stoptime_calc = prev_entry->stoptime_calc; entry->stopdepth_calc = prev_entry->stopdepth_calc; entry->tts_calc = prev_entry->tts_calc; entry->ndl_calc = prev_entry->ndl_calc; continue; } last_ndl_tts_calc_time = entry->sec; /* We are going to mess up deco state, so store it for later restore */ char *cache_data = NULL; cache_deco_state(&cache_data); calculate_ndl_tts(entry, dive, surface_pressure); if (prefs.deco_mode == VPMB && !in_planner() && i == pi->nr - 1) final_tts = entry->tts_calc; /* Restore "real" deco state for next real time step */ restore_deco_state(cache_data); free(cache_data); } } if (prefs.deco_mode == VPMB && !in_planner()) { prev_deco_time = deco_time; // Do we need to update deco_time? if (final_tts > 0) deco_time = pi->maxtime + final_tts - time_deep_ceiling; else if (time_clear_ceiling > 0) deco_time = time_clear_ceiling - time_deep_ceiling; vpmb_next_gradient(deco_time, surface_pressure / 1000.0); final_tts = 0; last_ndl_tts_calc_time = 0; first_ceiling = 0; first_iteration = false; count_iteration ++; restore_deco_state(cache_data_initial); } else { // With Buhlmann, or not in planner, iterating isn't needed. This makes the while condition false. prev_deco_time = deco_time = 0; } } free(cache_data_initial); #if DECO_CALC_DEBUG & 1 dump_tissues(); #endif }
bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer) { int bottom_depth; int bottom_gi; int bottom_stopidx; bool is_final_plan = true; int deco_time; int previous_deco_time; char *bottom_cache = NULL; struct sample *sample; int po2; int transitiontime, gi; int current_cylinder; unsigned int stopidx; int depth; double tissue_tolerance = 0.0; struct gaschanges *gaschanges = NULL; int gaschangenr; int *decostoplevels; int decostoplevelcount; unsigned int *stoplevels = NULL; int vpmb_first_stop; bool stopping = false; bool pendinggaschange = false; bool clear_to_ascend; int clock, previous_point_time; int avg_depth, max_depth, bottom_time = 0; int last_ascend_rate; int best_first_ascend_cylinder; struct gasmix gas, bottom_gas; int o2time = 0; int breaktime = -1; int breakcylinder = 0; int error = 0; bool decodive = false; set_gf(diveplan->gflow, diveplan->gfhigh, prefs.gf_low_at_maxdepth); if (!diveplan->surface_pressure) diveplan->surface_pressure = SURFACE_PRESSURE; create_dive_from_plan(diveplan, is_planner); // Do we want deco stop array in metres or feet? if (prefs.units.length == METERS ) { decostoplevels = decostoplevels_metric; decostoplevelcount = sizeof(decostoplevels_metric) / sizeof(int); } else { decostoplevels = decostoplevels_imperial; decostoplevelcount = sizeof(decostoplevels_imperial) / sizeof(int); } /* If the user has selected last stop to be at 6m/20', we need to get rid of the 3m/10' stop. * Otherwise reinstate the last stop 3m/10' stop. */ if (prefs.last_stop) *(decostoplevels + 1) = 0; else *(decostoplevels + 1) = M_OR_FT(3,10); /* Let's start at the last 'sample', i.e. the last manually entered waypoint. */ sample = &displayed_dive.dc.sample[displayed_dive.dc.samples - 1]; get_gas_at_time(&displayed_dive, &displayed_dive.dc, sample->time, &gas); po2 = sample->setpoint.mbar; if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); current_cylinder = 0; } depth = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].depth.mm; average_max_depth(diveplan, &avg_depth, &max_depth); last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); /* if all we wanted was the dive just get us back to the surface */ if (!is_planner) { transitiontime = depth / 75; /* this still needs to be made configurable */ plan_add_segment(diveplan, transitiontime, 0, gas, po2, false); create_dive_from_plan(diveplan, is_planner); return(false); } calc_crushing_pressure(depth_to_mbar(depth, &displayed_dive) / 1000.0); nuclear_regeneration(clock); clear_deco(displayed_dive.surface_pressure.mbar / 1000.0); vpmb_start_gradient(); previous_deco_time = 100000000; deco_time = 10000000; tissue_tolerance = tissue_at_end(&displayed_dive, cached_datap); displayed_dive.surface_pressure.mbar = diveplan->surface_pressure; #if DEBUG_PLAN & 4 printf("gas %s\n", gasname(&gas)); printf("depth %5.2lfm \n", depth / 1000.0); #endif best_first_ascend_cylinder = current_cylinder; /* Find the gases available for deco */ if (po2) { // Don't change gas in CCR mode gaschanges = NULL; gaschangenr = 0; } else { gaschanges = analyze_gaslist(diveplan, &gaschangenr, depth, &best_first_ascend_cylinder); } /* Find the first potential decostopdepth above current depth */ for (stopidx = 0; stopidx < decostoplevelcount; stopidx++) if (*(decostoplevels + stopidx) >= depth) break; if (stopidx > 0) stopidx--; /* Stoplevels are either depths of gas changes or potential deco stop depths. */ stoplevels = sort_stops(decostoplevels, stopidx + 1, gaschanges, gaschangenr); stopidx += gaschangenr; /* Keep time during the ascend */ bottom_time = clock = previous_point_time = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].time.seconds; gi = gaschangenr - 1; if(prefs.deco_mode == RECREATIONAL) { bool safety_stop = prefs.safetystop && max_depth >= 10000; track_ascent_gas(depth, &displayed_dive.cylinder[current_cylinder], avg_depth, bottom_time, safety_stop); // How long can we stay at the current depth and still directly ascent to the surface? while (trial_ascent(depth, 0, avg_depth, bottom_time, tissue_tolerance, &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0) && enough_gas(current_cylinder)) { tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, DECOTIMESTEP, po2, &displayed_dive, prefs.bottomsac); update_cylinder_pressure(&displayed_dive, depth, depth, DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false); clock += DECOTIMESTEP; } clock -= DECOTIMESTEP; plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, true); previous_point_time = clock; do { /* Ascend to surface */ int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) { plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); } if (depth - deltad < 0) deltad = depth; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, TIMESTEP, po2, &displayed_dive, prefs.decosac); clock += TIMESTEP; depth -= deltad; if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) { plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false); previous_point_time = clock; clock += 180; plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false); previous_point_time = clock; safety_stop = false; } } while (depth > 0); plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false); create_dive_from_plan(diveplan, is_planner); add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error); fixup_dc_duration(&displayed_dive.dc); free(stoplevels); free(gaschanges); return(false); } if (best_first_ascend_cylinder != current_cylinder) { stopping = true; current_cylinder = best_first_ascend_cylinder; gas = displayed_dive.cylinder[current_cylinder].gasmix; #if DEBUG_PLAN & 16 printf("switch to gas %d (%d/%d) @ %5.2lfm\n", best_first_ascend_cylinder, (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[best_first_ascend_cylinder].depth / 1000.0); #endif } // VPM-B or Buehlmann Deco nuclear_regeneration(clock); vpmb_start_gradient(); previous_deco_time = 100000000; deco_time = 10000000; cache_deco_state(tissue_tolerance, &bottom_cache); // Lets us make several iterations bottom_depth = depth; bottom_gi = gi; bottom_gas = gas; bottom_stopidx = stopidx; // Find first stop used for VPM-B Boyle's law compensation if (prefs.deco_mode == VPMB) { vpmb_first_stop = deco_allowed_depth(tissue_tolerance, diveplan->surface_pressure / 1000, &displayed_dive, 1); if (vpmb_first_stop > 0) { while (stoplevels[stopidx] > vpmb_first_stop) { stopidx--; } stopidx++; vpmb_first_stop = stoplevels[stopidx]; } first_stop_pressure.mbar = depth_to_mbar(vpmb_first_stop, &displayed_dive); } else { first_stop_pressure.mbar = 0; } //CVA do { is_final_plan = (prefs.deco_mode == BUEHLMANN) || (previous_deco_time - deco_time < 10); // CVA time converges if (deco_time != 10000000) vpmb_next_gradient(deco_time, diveplan->surface_pressure / 1000.0); previous_deco_time = deco_time; tissue_tolerance = restore_deco_state(bottom_cache); depth = bottom_depth; gi = bottom_gi; clock = previous_point_time = bottom_time; gas = bottom_gas; stopping = false; decodive = false; stopidx = bottom_stopidx; breaktime = -1; breakcylinder = 0; o2time = 0; last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); current_cylinder = 0; } while (1) { /* We will break out when we hit the surface */ do { /* Ascend to next stop depth */ int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) { if (is_final_plan) plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = false; last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); } if (depth - deltad < stoplevels[stopidx]) deltad = depth - stoplevels[stopidx]; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, TIMESTEP, po2, &displayed_dive, prefs.decosac); clock += TIMESTEP; depth -= deltad; } while (depth > 0 && depth > stoplevels[stopidx]); if (depth <= 0) break; /* We are at the surface */ if (gi >= 0 && stoplevels[stopidx] <= gaschanges[gi].depth) { /* We have reached a gas change. * Record this in the dive plan */ if (is_final_plan) plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = true; // Boyles Law compensation boyles_law(depth_to_mbar(stoplevels[stopidx], &displayed_dive) / 1000.0); /* Check we need to change cylinder. * We might not if the cylinder was chosen by the user * or user has selected only to switch only at required stops. * If current gas is hypoxic, we want to switch asap */ if (current_cylinder != gaschanges[gi].gasidx) { if (!prefs.switch_at_req_stop || !trial_ascent(depth, stoplevels[stopidx - 1], avg_depth, bottom_time, tissue_tolerance, &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0) || get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) < 160) { current_cylinder = gaschanges[gi].gasidx; gas = displayed_dive.cylinder[current_cylinder].gasmix; #if DEBUG_PLAN & 16 printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi].gasidx, (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi].depth / 1000.0); #endif /* Stop for the minimum duration to switch gas */ tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); clock += prefs.min_switch_duration; if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) o2time += prefs.min_switch_duration; } else { /* The user has selected the option to switch gas only at required stops. * Remember that we are waiting to switch gas */ pendinggaschange = true; } } gi--; } --stopidx; /* Save the current state and try to ascend to the next stopdepth */ while (1) { /* Check if ascending to next stop is clear, go back and wait if we hit the ceiling on the way */ if (trial_ascent(depth, stoplevels[stopidx], avg_depth, bottom_time, tissue_tolerance, &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0)) break; /* We did not hit the ceiling */ /* Add a minute of deco time and then try again */ decodive = true; if (!stopping) { /* The last segment was an ascend segment. * Add a waypoint for start of this deco stop */ if (is_final_plan) plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = true; // Boyles Law compensation boyles_law(depth_to_mbar(stoplevels[stopidx], &displayed_dive) / 1000.0); } /* Are we waiting to switch gas? * Occurs when the user has selected the option to switch only at required stops */ if (pendinggaschange) { current_cylinder = gaschanges[gi + 1].gasidx; gas = displayed_dive.cylinder[current_cylinder].gasmix; #if DEBUG_PLAN & 16 printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi + 1].gasidx, (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi + 1].depth / 1000.0); #endif /* Stop for the minimum duration to switch gas */ tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); clock += prefs.min_switch_duration; if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) o2time += prefs.min_switch_duration; pendinggaschange = false; } /* Deco stop should end when runtime is at a whole minute */ int this_decotimestep; this_decotimestep = DECOTIMESTEP - clock % DECOTIMESTEP; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, this_decotimestep, po2, &displayed_dive, prefs.decosac); clock += this_decotimestep; /* Finish infinite deco */ if(clock >= 48 * 3600 && depth >= 6000) { error = LONGDECO; break; } if (prefs.doo2breaks) { /* The backgas breaks option limits time on oxygen to 12 minutes, followed by 6 minutes on * backgas (first defined gas). This could be customized if there were demand. */ if (get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) { o2time += DECOTIMESTEP; if (o2time >= 12 * 60) { breaktime = 0; breakcylinder = current_cylinder; if (is_final_plan) plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; current_cylinder = 0; gas = displayed_dive.cylinder[current_cylinder].gasmix; } } else { if (breaktime >= 0) { breaktime += DECOTIMESTEP; if (breaktime >= 6 * 60) { o2time = 0; if (is_final_plan) plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; current_cylinder = breakcylinder; gas = displayed_dive.cylinder[current_cylinder].gasmix; breaktime = -1; } } } } } if (stopping) { /* Next we will ascend again. Add a waypoint if we have spend deco time */ if (is_final_plan) plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = false; } } deco_time = clock - bottom_time; } while (!is_final_plan); plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false); create_dive_from_plan(diveplan, is_planner); add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error); fixup_dc_duration(&displayed_dive.dc); free(stoplevels); free(gaschanges); return decodive; }