Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions Algorithms/Gas.cs

This file was deleted.

3 changes: 0 additions & 3 deletions CLI/Program.cs

This file was deleted.

15 changes: 15 additions & 0 deletions docs/UMLDiagrams/ZHL-16Diagrams/Step1.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@startuml Step1_Initialise_Tissues
title Step 1 – Initialise the Model (Initialise_Tissues Function)

start
:Call Initialise_Tissues();
repeat
:For tissue i (1 to 16):
:Set Pn[i] = 0.79 * (ATM - WATERVAPOUR);
:Set Ph[i] = 0;
:Set Pt[i] = Pn[i] + Ph[i];
repeat while (more tissues remain)
:Return tissue arrays (Pn, Ph, Pt);
stop

@enduml
43 changes: 43 additions & 0 deletions docs/UMLDiagrams/ZHL-16Diagrams/Step2.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@startuml Step2_Descent_Gas_Loadings
title Step 2 – Descent Gas Loading (Descent_Gas_Loadings Function)

start
:Input Parameters;
note right
Depth, DESCENT_RATE, fN2, fHe, WATERVAPOUR
end note

:Calculate descent time t = Depth / DESCENT_RATE;

repeat
:For each tissue i (1 to 16);

partition "For Nitrogen" {
note right
Use Schreiner Equation:
P = Pio + R*(t - 1/k) - (Pio - Po - R/k)*exp(-k*t)
end note
:Set Po = Pn[i];
:Set Pio = (Depth - WATERVAPOUR) * fN2;
:Set R = DESCENT_RATE * fN2;
:Calculate k = ln(2) / (nitrogen half-time[i]);
:Update Pn[i] = Pio + R*(t - 1/k) - (Pio - Po - R/k)*exp(-k*t);
}

partition "For Helium"
note right
Use Schreiner Equation:
P = Pio + R*(t - 1/k) - (Pio - Po - R/k)*exp(-k*t)
end note
:Set Po = Ph[i];
:Set Pio = (Depth - WATERVAPOUR) * fHe;
:Set R = DESCENT_RATE * fHe;
:Calculate k = ln(2) / (helium half-time[i]);
:Update Ph[i] = Pio + R*(t - 1/k) - (Pio - Po - R/k)*exp(-k*t);
}
repeat while (more tissues remain)

:Return updated tissue arrays (Pn, Ph);
stop

@enduml
33 changes: 33 additions & 0 deletions docs/UMLDiagrams/ZHL-16Diagrams/Step3.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@startuml Step3_Dive_Loadings
title Step 3 – Dive Loadings (Dive_Loadings Function)

start

repeat :For each tissue i (1 to 16):
partition "For Nitrogen" {
note right
Use Instantaneous Equation:
P = Po + (Pi - Po)*(1 - 2^(-t/half-time))
end note
:Set Po = Pn[i];
:Calculate Pi = (Depth - WATERVAPOUR) * fN2;
:Set t = Bottom Time;
:Update Pn[i] = Po + (Pi - Po) * (1 - 2^(-t / (nitrogen half-time[i])));
}

partition "For Helium" {
note right
Use Instantaneous Equation:
P = Po + (Pi - Po)*(1 - 2^(-t/half-time))
end note
:Set Po = Ph[i];
:Calculate Pi = (Depth - WATERVAPOUR) * fHe;
:Set t = Bottom Time;
:Update Ph[i] = Po + (Pi - Po) * (1 - 2^(-t / (helium half-time[i])));
}
repeat while (more tissues remain)

:Return updated tissue arrays (Pn, Ph);
stop

@enduml
12 changes: 12 additions & 0 deletions docs/UMLDiagrams/ZHL-16Diagrams/Step3a.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@startuml
start
repeat :For each Tissue;
:Calculate A = ((an2 * Pn) + (ahe * Ph)) / (Pn + Ph);
:Calculate B = ((bn2 * Pn) + (bhe * Ph)) / (Pn + Ph);
:Compute TissueCeiling = ((Pn + Ph) - A) * B;
:Store TissueCeiling value;
repeat while (more tissues remain?)
:Find the deepest (highest) TissueCeiling;
:Set Critical Ascent Ceiling = Highest Value;
stop
@enduml
24 changes: 24 additions & 0 deletions docs/UMLDiagrams/ZHL-16Diagrams/Step4.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@startuml
start

:Set currentStop = FirstStop;
:Set LastStop = 3msw or the desired last stop depth;

repeat
:Get DecoGas for current stop;
:Set StopLength = 0;

repeat
:CalculateAtDepth|
:Depth = currentStop;
:Bottom Time = 1 min;
:GasMix = DecoGas;
:Increment StopLength by 1;
repeat while (AscentCeiling != (currentStop - 3msw))

:Total stop time = StopLength minutes;
:Move up to next stop depth (currentStop = currentStop - 3msw);


stop
@enduml
40 changes: 40 additions & 0 deletions src/.github/workflows/qodana_code_quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#-------------------------------------------------------------------------------#
# Discover additional configuration options in our documentation #
# https://www.jetbrains.com/help/qodana/github.html #
#-------------------------------------------------------------------------------#

name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- dev
- master

jobs:
qodana:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2025.2
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
with:
# When pr-mode is set to true, Qodana analyzes only the files that have been changed
pr-mode: false
use-caches: true
post-pr-comment: true
use-annotations: true
# Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job
upload-result: false
# quick-fixes available in Ultimate and Ultimate Plus plans
push-fixes: 'none'
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Algorithms</RootNamespace>
</PropertyGroup>


</Project>
66 changes: 66 additions & 0 deletions src/DecoBox.Algorithms/DiveLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace Algorithms;

/// <summary>
/// Represents a level in a dive profile.
/// </summary>
public class DiveLevel
{
/// <summary>
/// Represents the depth of the dive level in bar.
/// </summary>
public float Depth;

/// <summary>
/// Represents the time spent at this dive level in minutes.
/// </summary>
public int Time;

/// <summary>
/// Represents the gas used at this dive level. Uses a Gas object.
/// </summary>
public Gas LevelGas;

/// <summary>
/// Initializes a new instance of the <see cref="DiveLevel"/> class with the specified parameters
/// </summary>
/// <param name="depth">Depth of the dive level in bar.</param>
/// <param name="time">Time spent at depth in metres.</param>
/// <param name="levelGas">The gas used in the dive level. See <see cref="Gas"/>.</param>
/// <exception cref="ArgumentException">
/// Thrown when any of the following are true:
/// <list type="bullet">
/// <item>Depth is less than or equal to 0 bar.</item>
/// <item>Time is less than or equal to 0 minutes.</item>
/// </list>
/// </exception>
/// <exception cref="ArgumentNullException">Thrown when no gas is supplied.</exception>
public DiveLevel(float depth, int time, Gas levelGas)
{
if (depth <= 0f) { throw new ArgumentException("Error: Depth must be greater than 0 bar."); }
if (time <= 0) { throw new ArgumentException("Error: Time must be at least 1 minute."); }
ArgumentNullException.ThrowIfNull(levelGas);

Depth = depth;
Time = time;
LevelGas = levelGas;
}

/// <summary>
/// Returns a string representation of the DiveLevel.
/// </summary>
/// <returns>A human-readable representation of the dive level</returns>
public override string ToString()
{
return $"Depth: {Depth} bar, Time: {Time} min, Gas: {LevelGas.Name}";
}

/// <summary>
/// Converts the depth from bar to meters.
/// </summary>
/// <returns>The depth of the DiveLevel instance in metres.</returns>
public double GetDepthInMeters()
{
return (Depth - 1) * 10;
}
}

77 changes: 77 additions & 0 deletions src/DecoBox.Algorithms/Gas.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
namespace Algorithms;

/// <summary>
/// Rrepresents a breathing gas mixture used in diving.
/// </summary>
/// <remarks>
/// The gas is defined by it's oxygen (<see cref="O2"/>) and helium (<see cref="He"/>) fractions. The nitrogen (<see cref="N2"/>) is calcuated from the remaining percent.
/// </remarks>
public class Gas
{
/// <summary>
/// The name of the tank for identification purposes.
/// </summary>
public string Name { get; }

/// <summary>
/// The percentage of oxygen in the gas mixture. Expressed as a fraction (e.g., 0.21 for 21%).
/// </summary>
public float O2 { get; }
/// <summary>
/// The percentage of helium in the gas mixture. Expressed as a fraction (e.g., 0.35 for 35%).
public float He { get; }
/// <summary>
/// The percentage of nitrogen in the gas mixture. Calculated as the remainder after accounting for oxygen and helium. Expressed as a fraction (e.g., 0.79 for 79%).
/// </summary>
public float N2 { get; }
/// <summary>
/// The tank size in liters.
/// </summary>
public int TankSize { get; }
/// <summary>
/// The pressure of the gas in the tank, measured in bar.
/// </summary>
public int Pressure { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Gas"/> class with the specified parameters.
/// </summary>
/// <param name="name">Name of the tank for ID purposes.</param>
/// <param name="o2">The percentage of oxygen expressed as a fraction.</param>
/// <param name="he">The percentage of helium expressed as a fraction.</param>
/// <param name="tankSize">The size of the tank in litres.</param>
/// <param name="pressure">The pressure of the tank in bar.</param>
/// <exception cref="ArgumentException">
/// Thrown when any of the following are true:
/// <list type="bullet">
/// <item>O2 is less than or equal to 0.</item>
/// <item>The sum of O2 and He exceeds 1.0.</item>
/// <item>Tank size is less than or equal to 0.</item>
/// <item>Pressure is less than or equal to 0.</item>
/// </list>
/// </exception>
public Gas(string name, float o2, float he, int tankSize, int pressure)
{
if (o2 <= 0) { throw new ArgumentException($"Invalid gas composition: O2 cannot be 0, was {o2}"); }
if (o2 + he > 1) { throw new ArgumentException($"Invalid gas composition: O2 + He must not exceed 1.0, was {o2 + he}"); }
if (tankSize <= 0) { throw new ArgumentException($"Invalid tank size: Tank size must be greater than 0, was {tankSize}"); }
if (pressure <= 0) { throw new ArgumentException($"Invalid pressure: Pressure must be greater than 0, was {pressure}"); }

Name = name;
O2 = o2;
He = he;
N2 = 1 - (o2 + he);
TankSize = tankSize;
Pressure = pressure;
}

/// <summary>
/// Returns the maximum operating depth (MOD) for the gas at a given partial pressure of oxygen (PO2).
/// </summary>
/// <param name="PO2">The partial pressure of the tank (e.g., 1.4)</param>
/// <returns>A float of the maximum operating depth of the tank.</returns>
public float CalculateMOD(float PO2)
{
return ((PO2 / O2) - 1) * 10;
}
}
Loading
Loading