New Jersey Scuba Diving
In addition to its value for debugging the program, I find the program trace fascinating in its own right. Each line is generated by a function call. The first two numbers are k and j, the counters for user (input) points and (program) waypoints respectively. The next two numbers are depth and run-time at the end of the previous function ( equivalent to the beginning of the current function. ) After that is a note if the profile is in DECOmpression or if one of the CONServative settings is in force. The function name is displayed in bold, followed by some interesting note or result.
There are five levels of tracing, each showing progressively more detail. The last level also displays many intermediate results. You can watch compartment values, partial pressures, ceilings, etc change as you move through the profile.
If a function is indented below the previous one, that means the second one was called by the first. Functions at the same indent level were all called in the order shown by the same parent. A lot of basic bookkeeping-type functions are suppressed from the display to minimize clutter. There is a function Peek() that serves only to display results at interesting points in the program. Watch for it.
The program begins with Start(). This is little more than a bookend for the Trace() function, although it does do a little setting-up. Then comes a big one - Input(). Input reads the big control form ( which is itself a program ) and sets up all program parameters and user points, using defaults for any missing inputs. It generates additional points as needed, for example, a final ascent to the surface. Input() expands the waypoints you enter into program User Points, with additional bookend User points as necessary. User Points combine your dive waypoints with the equipment and environment factors selected.
The next function is DecoModel(), which sets up the parameters for the selected decompression model. Most of this function is just data arrays for 16 different models. Altitude is determined by PBaro(). After that, the MainLoop() function is called. This is where the simulation really begins. MainLoop() processes the user point list, creating program waypoints as it goes. Some user points translate into just one waypoint, while others may spawn many. All waypoints are saved for later use, unlike many decompression programs which constantly operate on just one data point. All of that saved data is made use of by the table generator.
MainLoop() initializes a new dive profile with NewProfile() whenever commanded - setting all states to pre-dive, and doing some bounds-checking and bookkeeping. Then it moves on to the dive simulation. Mainloop() reads in each User Point that was generated by Input(), and creates a Program Waypoint from it. A Program Waypoint contains all the information from the User Point, plus a lot more: pressures, ceilings, etc, that are calculated as the point is processed.
There are three main functions inside MainLoop() that correspond to phases of a dive: Descend(), Level(), and Ascend(). Each of these has two modes: Predict and Compute. In Predict mode, the current program state ( waypoint and compartments ) is saved while temporary calculations are made. Whatever results that were sought are saved, and then the original program state is restored. In Compute mode, the same function updates the program state, committing its calculations to the current waypoint. In the program trace, Predict mode is indicated by gray text, while Compute mode is indicated by blue text.
Descend() is called first - without that you are not diving. Descend() calls Shreiner() - the Shreiner equation - to calculate gas loadings, and then Ceilings() to determine the resulting decompression ceiling. It may do this once, or twice if the Descent Split Depth is crossed. Descending is simple, there is no need to test if you can go down, so Descend() is only ever called in Compute mode.
A descent is followed by a Level(). Level() uses the Haldane equation to calculate gas loadings for the specified time. Level() is used in both Predict and Compute modes.
Ascend() is the reverse of Descend(), using the Shreiner equation in much the same way. However, that is too dumb. On its own, Ascend() will take you directly to whatever depth is specified. In real life, this can get you BENT, and so too in the program. So Ascend() is never called directly, but is invoked by a controlling function - Decompress(), even if it turns out that no decompression is needed. Ascend() calls Shreiner(), and then Ceilings() to calculate the new decompression ceilings that result from its own use.
Decompress() uses Ascend() and Level() in Predict mode to determine the ceiling that you can ascend to, based on gas loadings and conservatism settings. Decompress() first calls StopDepth(), which is one half of the real brains of the algorithm. By comparison, SteppedDepth() is a dumb little function that just rounds-up the decompression ceiling to the next deeper stop depth.
StopDepth() begins with a stepped deco depth based on the current ceiling, and then calls Ascend() in predict mode as often as necessary to determine if it can be reached, or even exceeded, restoring the initial conditions between tests. This is necessary because the ceilings change during an ascent, you can't predict what they will be when you get there without actually doing it. When all this is done, StopDepth() then returns the shallowest stop depth that it was able to reach, and restores the initial conditions for the next stage.
Decompress() then calls Ascend() to reach the stop depth. It then determines the next stop depth, and calls StopTime() to determine the decompression time required to reach it. StopTime() is the other half of the brains of the program. StopTime() is actually where the program spends most of its time. StopTime() calls Level() and Ascend() repeatedly to determine the necessary decompression time to ascend to the next stop or the surface. For long decompressions, this can take many iterations.
In order to head off very long computing times, rather than simply starting at zero minutes and going up one by one, StopTime() begins with a search algorithm, with limits determined by the depth of the stop and the length of the dive profile. The search algorithm determines a good starting point, typically 1 minute short of the actual required time, and feeds it to the incremental algorithm, which then finds the solution in two steps. StopTime() can determine most decompression times in six to ten iterations. While very short stops may actually take longer to determine this way, very long stops are calculated much faster than they would be otherwise, and it is those that really blow up the program execution time. If a very short stop is expected, then the search algorithm is bypassed.
For extremely long decompressions ( say, for saturation diving ) StopTime() begins with one minute increments for the first sixty minutes, and increases the increment as the stop time gets longer, allowing it to calculate very long stop times. In one test I ran, StopTime() determined the final 10 foot decompression stop time from full saturation on air at 100 feet in approximately 150 iterations. The iteration limit is ten times that, so the program should be capable of solving almost any situation, including ones that are not realistic.
Once it has the stop time, Decompress() calls Level() and Ascend() once more in Compute mode to commit the calculation. I could probably be more clever and just keep the last temporary calculations without repeating them, but it is simpler to just do it one more time. The whole process repeats until the target ascent depth is reached. Each time Ascend() is called in Compute mode, it spawns a new waypoint, so the entire process may create many waypoints from a single user point, and require dozens or even hundreds or Predict mode computations.
Ceilings() is used by many other functions to determine the decompression ceiling at a given point in the profile. By default, Ceilings() only generates the ( non-conservative ) model ceiling, but if conservatism is in use, Ceilings() calls ConsCeilings() to determine the additional deeper conservative ceilings.
D-Plan has no limitations on dive profile - you can go down and up and back down as much as you like. Ascents do not need to be to the surface, ascents and descents may be to any depth, even with zero time. Yo-Yo profiles are no problem for computation, although they may be a problem in real life.
A dive is considered concluded if you touch the surface - zero depth - either by a user-inputted point or a program-generated point. This will cause a number of profile parameters to reset, and sums to be calculated. You can set a zero-time surface interval and go right back down, but that will still be a new dive.
That covers the basic decompression algorithm. But D-Plan does a lot more. At the end of each stage, and sometimes twice, it calculates gas volumes: Volume(), and oxygen toxicity: OxyToxCNS() and OxyToxOTU(). This functionality is implemented in a modular way, so I can turn it off completely if I need to simplify matters for debugging, but normally it is enabled.
D-Plan also implements several forms of conservatism and deep stops. These are mostly encoded in ConsCeilings(), which is called from Ceilings() as needed. ConsCeilings() does extra ceiling calculations based on user conservatism settings, and then replaces the base ceiling with the worst-case conservative ceiling. Again, this is all modular, the base algorithm runs with or without conservatism.
Gradient Factor conservatism is implemented in two functions: ConsInit(), which is called at the maximum depth of a dive, and ConsGF(), which calculates the Gradient Factor prior to every ascent, be it a Predict or a Compute. Pyle stops are invoked at the beginning of Decompress(). The PyleStops() function inserts however many deep stops that it calculates as new waypoints. ZH-L17TS deep stops are part of the model, and are invoked when the model is set up at the beginning of the program. Pressure Ratio conservatism is implemented in ConsPR(). No-decompression safety stops are implemented in SafetyStop(), also called at the beginning of Decompress().
Decompression gas switches are implemented in DecoGasSwitch(). If Deco gases are enabled, this function is called at the beginning of Level() and Ascend(), to set up the next stage.
The final addition to the base decompression algorithm is rebreathers. Rebreathers are implemented entirely in Rebreather(). It turns out that simulating a rebreather is mostly a matter of adjusting the gas fractions according to the logic of the device. This is very similar to Deco gas switching. Rebreathers are admittedly poorly modeled during ascents and descents, but I think this has little impact on the overall results.
DecoGasSwitch() will override Rebreather() if both are in use. In fact, realizing just how similar these two functions are is what got me to add rebreathers to the program in the first place, and now there are three different types! Rebreather functions can be removed easily, but deco gas switching is in the core of the program.
TimeToFly() is an accurate prediction of time to fly, based on decompression modeling to altitude. It is surprising how short the real time-to-fly can be, especially after a single dive.
If you only look at the blue sections of the trace, you can see the dive profile, and if you look at the gray sections, you can see all the thrashing the program went through to calculate that profile. Remember, a lot of basic operations are not shown, and every operation is really as many as 17 separate operations, one for each tissue compartment. There was a time when something like this might have had a noticeable execution time, but nowadays computers are so fast that any profile I try is generated almost instantaneously. Still, it would be all but impossible to do these calculations by hand.
I make no claim as to the accuracy, validity, or appropriateness of any information found in this website. I will not be responsible for the consequences of any action that is based upon information found here. Scuba diving is an adventure sport, and as always, you alone are responsible for your own safety and well being.
Copyright © 1996-2016 Rich Galiano
unless otherwise noted