Robert,
After a sleep I can think (I hope) more clearly. I attach a function
from gaspressures.c (not the whole of gasspressures.c). It is possible
to use a switch between the cylinder_use types as shown in this example.
But its utility is limited. The interpolation requires knowledge about
the specific cylinder that is used for a particular point on the dive
profile. Think in terms of cylinder changes during the dive.
Now, the cylinder_use constant (i.e. oxygen, diluent, bailout) gives
information about the VARIABLES to be used for the interpolation, not
about the RULES to be used for interpolation. So, like in the attached
code, we can use a C-language switch (cylinder_use constants) to
initialise some variables. But the rest of the algorithm requires the
specific cylinder index values. And there is no getting away from using
explicit cylinder index values using this algorithm. So there is not an
immediate way of getting away from cylinder indices or "magic numbers"
as you call them. For that reason, the switch (cylinder_use) construct
of 12 lines of code can efficiently be achievd by the single line of
code that is commented out underneath each of the two switches. But the
issue of code maintainability remains.
The cylinder_use constants(oxyge, diluent, bailout) do not give any
information about the ID of particular cylinders when there is more than
one cylinder of a particular cylinder_use class. For instance, take the
hypothetical case of a CCR diver that takes a bailout cylinder as well
as a stage cylinder. Both of these additional cylinders would be classed
as cylinder_use = bailout. This touches on the topic of being gas-aware
versus being cylinder-aware. There is a growing need for being
cylinder-aware, reflected by at least one recent patch from Linus. The
question is how to address each physical cylinder in an efficient way.
At the moment magic numbers is probably the only way.
So what have we achieved with the creation of cylinder_use constants
(oxygen, diluent, bailout)? We created the possibility to use any of the
possible physical cylinders for any of these three cylinder use
categories. This is what I still refused to understand when hard-coding
a cylinder index into file.c: my code change totally defeated the object
of cylinder_use constants.
Every time you have articulated a pretty convincing argument about these
things. So I would really like to have your comment.
Kind regards,
willem
/* gaspressures.c
* ---------------
* This file contains the routines to calculate the gas pressures in the cylinders.
* The functions below support the code in profile.c.
* The high-level function is populate_pressure_information(), called by function
* create_plot_info_new() in profile.c. The other functions below are, in turn,
* called by populate_pressure_information(). The calling sequence is as follows:
*
* populate_pressure_information() -> calc_pressure_time()
* -> fill_missing_tank_pressures() -> fill_missing_segment_pressures()
* -> get_pr_interpolate_data()
*
* The pr_track_t related functions below implement a linked list that is used by
* the majority of the functions below. The linked list covers a part of the dive profile
* for which there are no cylinder pressure data. Each element in the linked list
* represents a segment between two consecutive points on the dive profile.
* pr_track_t is defined in gaspressures.h
*/
#include "dive.h"
#include "display.h"
#include "profile.h"
#include "gaspressures.h"
#define DEBUG_PR_TRACK 1
static pr_track_t *pr_track_alloc(int start, int t_start)
{
pr_track_t *pt = malloc(sizeof(pr_track_t));
pt->start = start;
pt->end = 0;
pt->t_start = pt->t_end = t_start;
pt->pressure_time = 0;
pt->next = NULL;
return pt;
}
/* poor man's linked list */
static pr_track_t *list_last(pr_track_t *list)
{
pr_track_t *tail = list;
if (!tail)
return NULL;
while (tail->next) {
tail = tail->next;
}
return tail;
}
static pr_track_t *list_add(pr_track_t *list, pr_track_t *element)
{
pr_track_t *tail = list_last(list);
if (!tail)
return element;
tail->next = element;
return list;
}
static void list_free(pr_track_t *list)
{
if (!list)
return;
list_free(list->next);
free(list);
}
#ifdef DEBUG_PR_TRACK
static void dump_pr_track(pr_track_t **track_pr)
{
int cyl;
pr_track_t *list;
for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) {
list = track_pr[cyl];
while (list) {
printf("cyl%d: start %d end %d t_start %d t_end %d pt %d\n", cyl,
list->start, list->end, list->t_start, list->t_end, list->pressure_time);
list = list->next;
}
}
}
#endif
/*
* This looks at the pressures for one cylinder, and
* calculates any missing beginning/end pressures for
* each segment by taking the over-all SAC-rate into
* account for that cylinder.
*
* NOTE! Many segments have full pressure information
* (both beginning and ending pressure). But if we have
* switched away from a cylinder, we will have the
* beginning pressure for the first segment with a
* missing end pressure. We may then have one or more
* segments without beginning or end pressures, until
* we finally have a segment with an end pressure.
*
* We want to spread out the pressure over these missing
* segments according to how big of a time_pressure area
* they have.
*/
static void fill_missing_segment_pressures(pr_track_t *list)
{
while (list) {
int start = list->start, end;
pr_track_t *tmp = list;
int pt_sum = 0, pt = 0;
for (;;) {
pt_sum += tmp->pressure_time;
end = tmp->end;
if (end)
break;
end = start;
if (!tmp->next)
break;
tmp = tmp->next;
}
if (!start)
start = end;
/*
* Now 'start' and 'end' contain the pressure values
* for the set of segments described by 'list'..'tmp'.
* pt_sum is the sum of all the pressure-times of the
* segments.
*
* Now dole out the pressures relative to pressure-time.
*/
list->start = start;
tmp->end = end;
for (;;) {
int pressure;
pt += list->pressure_time;
pressure = start;
if (pt_sum)
pressure -= (start - end) * (double)pt / pt_sum;
list->end = pressure;
if (list == tmp)
break;
list = list->next;
list->start = pressure;
}
/* Ok, we've done that set of segments */
list = list->next;
}
}
#ifdef DEBUG_PR_INTERPOLATE
void dump_pr_interpolate(int i, pr_interpolate_t interpolate_pr)
{
printf("Interpolate for entry %d: start %d - end %d - pt %d - acc_pt %d\n", i,
interpolate_pr.start, interpolate_pr.end, interpolate_pr.pressure_time, interpolate_pr.acc_pressure_time);
}
#endif
static struct pr_interpolate_struct get_pr_interpolate_data(pr_track_t *segment, struct plot_info *pi, int cur, int diluent_flag)
{ // cur = index to pi->entry corresponding to t_end of segment; diluent_flag=1 indicates diluent cylinder
struct pr_interpolate_struct interpolate;
int i;
struct plot_data *entry;
int pressure;
interpolate.start = segment->start;
interpolate.end = segment->end;
interpolate.acc_pressure_time = 0;
interpolate.pressure_time = 0;
for (i = 0; i < pi->nr; i++) {
entry = pi->entry + i;
if (diluent_flag)
pressure = DILUENT_PRESSURE(entry);
else
pressure = SENSOR_PRESSURE(entry);
/* This function goes through the list of tank pressures, either SENSOR_PRESSURE(entry) or DILUENT_PRESSURE(entry),
* within structure plot_info for the dive profile where each item in the list corresponds to one point (node) of the
* profile. It finds values for which there are no tank pressures (pressure==0). For each stretch of the dive log
* for which there are no pressures, it creates a pr_track_t structure which contains 1) the tank pressure at the
* start of the gap and at the end of the gap; 2) the times at the beginning and end of the gap; 3) the summed
* time-pressure values (based on depth) for all the segments in the gap with no cylinder pressure data. These pr_track_t
* structures ultimately allow for filling the missing tank pressure values on the dive profile using the depth_pressures
* of the dive. If diluent_flag = 1, then DILUENT_PRESSURE(entry) is used instead of SENSOR_PRESSURE.
* This function is called by create_plot_info_new() in profile.c
*/
void populate_pressure_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, int diluent_flag)
{
int i, cylinderid, cylinderindex = -1;
pr_track_t *track_pr[MAX_CYLINDERS] = { NULL, };
pr_track_t *current = NULL;
bool missing_pr = false;
enum dive_comp_type dc_type = dc->dctype;
/* This lookup table allows lookup of appropriate cylinder IDs. It uses dc->dctype and diluent_flag as
* indices and asumes that the mumeric assignments of these two variables will not change. The table
* avoids the need for complex if-else constructs.*/
int cyl_index_lookup[2][2] = { { 0, 0 }, { get_cylinder_use(dive, oxygen) , get_cylinder_use(dive, diluent)} };
// int (*lookup_cyl_ptr)[2][2] = &cyl_index_lookup;
for (i = 0; i < pi->nr; i++) {
struct plot_data *entry = pi->entry + i;
unsigned pressure;
cyl_index_lookup[0][0] = entry->cylinderindex;
switch (dive->cylinder[entry->cylinderindex].cylinder_use) {
case oxygen :
cylinderid = get_cylinder_use(dive, oxygen);
break;
case diluent :
cylinderid = get_cylinder_use(dive, diluent);
break;
case bailout :
cylinderid = entry->cylinderindex;
break;
default ;
}
// cylinderid = cyl_index_lookup[dc_type][diluent_flag];
if (diluent_flag)
pressure = DILUENT_PRESSURE(entry);
else
pressure = SENSOR_PRESSURE(entry);
/* If track_pr structure already exists, then update it: */
/* discrete integration of pressure over time to get the SAC rate equivalent */
if (current) {
entry->pressure_time = calc_pressure_time(dive, dc, entry - 1, entry);
current->pressure_time += entry->pressure_time;
current->t_end = entry->sec; // Store the pressure-time data
}
/* If 1st record or different cylinder: Create a new track_pr structure */
/* ..and initialise the start pressure and time for this new linked list*/
if (cylinderid != cylinderindex) {
switch (dive->cylinder[entry->cylinderindex].cylinder_use) {
case oxygen :
cylinderindex = get_cylinder_use(dive, oxygen);
break;
case diluent :
cylinderindex = get_cylinder_use(dive, diluent);
break;
case bailout :
cylinderindex = entry->cylinderindex;
break;
default ;
}
// cylinderindex = cyl_index_lookup[dc_type][diluent_flag];
current = pr_track_alloc(pressure, entry->sec);
track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current);
continue;
}
if (!pressure) { // If no pressure data then do not add another struct to linked list
missing_pr = 1; // ..and proceed to next sample
continue;
}
current->end = pressure;
/* Are the pressure data continuous? */
if (diluent_flag) {
if (DILUENT_PRESSURE(entry - 1)) // in the case of CCR diluent pressure
continue;
}
else if (SENSOR_PRESSURE(entry - 1)) // for all other cylinders
continue;
/* If there is a gap in the pressure data recorded into the track_pr structures */
current = pr_track_alloc(pressure, entry->sec); // add a new track_pr struct to the list
track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current);
}
if (missing_pr) { // if there are missing pressures then do interpolation
fill_missing_tank_pressures(dive, pi, track_pr, diluent_flag);
}
#ifdef PRINT_PRESSURES_DEBUG
debug_print_pressures(pi);
#endif
for (i = 0; i < MAX_CYLINDERS; i++)
list_free(track_pr[i]);
}
_______________________________________________
subsurface mailing list
[email protected]
http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface