subsurface/core/gas.cpp
Michael Keller db516b6d4e Profile: Fix the Initial Gasmix.
Fix the initial gasmix that is shown in the tank bar of the profile.

Also add a meaningful gas name for gases with negative values for
percentages.

@bstoeger: This is a side effect of the `event_loop` functionality
introduced as part of #4198. In the case of an `event_loop("gasmix")`
this does not take into account the edge case where there is no
gaschange event at the very beginning of the dive, and the first gasmix
is implicitly used as the starting gasmix. This happens for planned and
manually added dives, but also for some dive computers.
We are using the
same kind of loop in a number of other places in `core/profile.cpp`,
`core/dive.cpp`, `core/gaspressures.cpp`, and `profile-widget/tankitem.cpp`,
and I am wondering if we should be converting these to use
`gasmix_loop` instead to avoid being bit by this special case?

Signed-off-by: Michael Keller <github@ike.ch>
2024-09-03 18:19:44 +12:00

200 lines
6.7 KiB
C++

// SPDX-License-Identifier: GPL-2.0
#include "gas.h"
#include "pref.h"
#include "errorhelper.h"
#include "format.h"
#include "gettext.h"
#include <stdio.h>
#include <string.h>
#include <QtGlobal> // for QT_TRANSLATE_NOOP
/* Perform isobaric counterdiffusion calculations for gas changes in trimix dives.
* Here we use the rule-of-fifths where, during a change involving trimix gas, the increase in nitrogen
* should not exceed one fifth of the decrease in helium.
* Parameters: 1) pointers to two gas mixes, the gas being switched from and the gas being switched to.
* 2) a pointer to an icd_data structure.
* Output: i) The icd_data stucture is filled with the delta_N2 and delta_He numbers (as permille).
* ii) Function returns a boolean indicating an exceeding of the rule-of-fifths. False = no icd problem.
*/
bool isobaric_counterdiffusion(struct gasmix oldgasmix, struct gasmix newgasmix, struct icd_data *results)
{
if (!prefs.show_icd) {
results->dN2 = results->dHe = 0;
return false;
}
results->dN2 = get_n2(newgasmix) - get_n2(oldgasmix);
results->dHe = get_he(newgasmix) - get_he(oldgasmix);
return get_he(oldgasmix) > 0 && results->dN2 > 0 && results->dHe < 0 && get_he(oldgasmix) && results->dN2 > 0 && 5 * results->dN2 > -results->dHe;
}
bool gasmix_is_invalid(struct gasmix mix)
{
return mix.o2.permille < 0;
}
int same_gasmix(struct gasmix a, struct gasmix b)
{
if (gasmix_is_invalid(a) || gasmix_is_invalid(b))
return 0;
if (gasmix_is_air(a) && gasmix_is_air(b))
return 1;
return get_o2(a) == get_o2(b) && get_he(a) == get_he(b);
}
void sanitize_gasmix(struct gasmix &mix)
{
unsigned int o2, he;
o2 = get_o2(mix);
he = get_he(mix);
/* Regular air: leave empty */
if (!he) {
if (!o2)
return;
/* 20.8% to 21% O2 is just air */
if (gasmix_is_air(mix)) {
mix.o2.permille = 0;
return;
}
}
/* Sane mix? */
if (o2 <= 1000 && he <= 1000 && o2 + he <= 1000)
return;
report_info("Odd gasmix: %u O2 %u He", o2, he);
mix = gasmix_air;
}
int gasmix_distance(struct gasmix a, struct gasmix b)
{
int a_o2 = get_o2(a), b_o2 = get_o2(b);
int a_he = get_he(a), b_he = get_he(b);
int delta_o2 = a_o2 - b_o2, delta_he = a_he - b_he;
delta_he = delta_he * delta_he;
delta_o2 = delta_o2 * delta_o2;
return delta_he + delta_o2;
}
bool gasmix_is_air(struct gasmix gasmix)
{
int o2 = get_o2(gasmix);
int he = get_he(gasmix);
return (he == 0) && (o2 == 0 || ((o2 >= O2_IN_AIR - 1) && (o2 <= O2_IN_AIR + 1)));
}
fraction_t make_fraction(int i)
{
fraction_t res;
res.permille = i;
return res;
}
fraction_t get_gas_component_fraction(struct gasmix mix, enum gas_component component)
{
switch (component) {
case O2: return make_fraction(get_o2(mix));
case N2: return make_fraction(get_n2(mix));
case HE: return make_fraction(get_he(mix));
default: return make_fraction(0);
}
}
// O2 pressure in mbar according to the steady state model for the PSCR
// NB: Ambient pressure comes in bar!
int pscr_o2(const double amb_pressure, struct gasmix mix)
{
int o2 = (int)(get_o2(mix) * amb_pressure -
(1.0 - get_o2(mix) / 1000.0) * prefs.o2consumption / (prefs.bottomsac * prefs.pscr_ratio) * 1000000);
if (o2 < 0.0) // He's dead, Jim.
o2 = 0.0;
return o2;
}
/* fill_pressures(): Compute partial gas pressures in bar from gasmix and ambient pressures, possibly for OC or CCR, to be
* extended to PSCT. This function does the calculations of gas pressures applicable to a single point on the dive profile.
* The structure "pressures" is used to return calculated gas pressures to the calling software.
* Call parameters: po2 = po2 value applicable to the record in calling function
* amb_pressure = ambient pressure applicable to the record in calling function
* *mix = structure containing cylinder gas mixture information.
* divemode = the dive mode pertaining to this point in the dive profile.
* This function called by: calculate_gas_information_new() in profile.cpp; add_segment() in deco.cpp.
*/
gas_pressures fill_pressures(const double amb_pressure, struct gasmix mix, double po2, enum divemode_t divemode)
{
struct gas_pressures pressures;
if ((divemode != OC) && po2) { // This is a rebreather dive where pressures.o2 is defined
if (po2 >= amb_pressure) {
pressures.o2 = amb_pressure;
pressures.n2 = pressures.he = 0.0;
} else {
pressures.o2 = po2;
if (get_o2(mix) == 1000) {
pressures.he = pressures.n2 = 0;
} else {
pressures.he = (amb_pressure - pressures.o2) * (double)get_he(mix) / (1000 - get_o2(mix));
pressures.n2 = amb_pressure - pressures.o2 - pressures.he;
}
}
} else {
if (divemode == PSCR) { /* The steady state approximation should be good enough */
pressures.o2 = pscr_o2(amb_pressure, mix) / 1000.0;
if (get_o2(mix) != 1000) {
pressures.he = (amb_pressure - pressures.o2) * get_he(mix) / (1000.0 - get_o2(mix));
pressures.n2 = (amb_pressure - pressures.o2) * get_n2(mix) / (1000.0 - get_o2(mix));
} else {
pressures.he = pressures.n2 = 0;
}
} else {
// Open circuit dives: no gas pressure values available, they need to be calculated
pressures.o2 = get_o2(mix) / 1000.0 * amb_pressure; // These calculations are also used if the CCR calculation above..
pressures.he = get_he(mix) / 1000.0 * amb_pressure; // ..returned a po2 of zero (i.e. o2 sensor data not resolvable)
pressures.n2 = get_n2(mix) / 1000.0 * amb_pressure;
}
}
return pressures;
}
enum gastype gasmix_to_type(struct gasmix mix)
{
if (gasmix_is_air(mix))
return GASTYPE_AIR;
if (get_o2(mix) >= 980)
return GASTYPE_OXYGEN;
if (get_he(mix) == 0)
return get_o2(mix) >= 230 ? GASTYPE_NITROX : GASTYPE_AIR;
if (get_o2(mix) <= 180)
return GASTYPE_HYPOXIC_TRIMIX;
return get_o2(mix) <= 230 ? GASTYPE_NORMOXIC_TRIMIX : GASTYPE_HYPEROXIC_TRIMIX;
}
static const char *gastype_names[] = {
QT_TRANSLATE_NOOP("gettextFromC", "Air"),
QT_TRANSLATE_NOOP("gettextFromC", "Nitrox"),
QT_TRANSLATE_NOOP("gettextFromC", "Hypoxic Trimix"),
QT_TRANSLATE_NOOP("gettextFromC", "Normoxic Trimix"),
QT_TRANSLATE_NOOP("gettextFromC", "Hyperoxic Trimix"),
QT_TRANSLATE_NOOP("gettextFromC", "Oxygen")
};
const char *gastype_name(enum gastype type)
{
if (type < 0 || type >= GASTYPE_COUNT)
return "";
return translate("gettextFromC", gastype_names[type]);
}
std::string gasmix::name() const
{
if (get_he(*this) < 0 || get_o2(*this) < 0)
return translate("gettextFromC", "invalid gas");
else if (gasmix_is_air(*this))
return translate("gettextFromC", "air");
else if (get_he(*this) == 0 && get_o2(*this) < 1000)
return format_string_std(translate("gettextFromC", "EAN%d"), (get_o2(*this) + 5) / 10);
else if (get_he(*this) == 0 && get_o2(*this) == 1000)
return translate("gettextFromC", "oxygen");
else
return format_string_std("(%d/%d)", (get_o2(*this) + 5) / 10, (get_he(*this) + 5) / 10);
}