Skip to content

Commit ba5ae24

Browse files
committed
wip calibration dilution
1 parent 7aac8f2 commit ba5ae24

File tree

6 files changed

+336
-92
lines changed

6 files changed

+336
-92
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* @copyright Copyright (c) 2024-2025 Ronan LE MEILLAT
3+
* @license AGPL-3.0-or-later
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as
7+
* published by the Free Software Foundation, either version 3 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
import type React from "react";
19+
20+
import {
21+
AGA8wasm,
22+
PropertiesGERGResult,
23+
R,
24+
type GasMixtureExt,
25+
} from "@sctg/aga8-js";
26+
import { useEffect } from "react";
27+
28+
import { OrificeSelector } from "./OrificeSelector";
29+
import { PressureSlider } from "./PressureSlider";
30+
import { ConcentrationSelector } from "./ConcentrationSelector";
31+
import { Cd } from "@/config/site";
32+
import { logSonicNozzleFlowCalculation } from "@/utilities";
33+
34+
export type FlowData = {
35+
massFlow: number; // kg/s
36+
p_crit: number; // kPa
37+
A: number; // area of the orifice in m²
38+
properties: PropertiesGERGResult; // Gas properties
39+
molarMass: number; // g/mol
40+
Rs: number; // J/(kg·K)
41+
rho: number; // kg/m³
42+
rho_out: number; // kg/m³
43+
};
44+
interface CalibrationInletProps {
45+
label: string;
46+
pressure: number;
47+
selectedGas: GasMixtureExt;
48+
selectedCalibrationConcentration: number;
49+
selectedOrifice: number;
50+
temperature: number;
51+
onPressureChange: (pressure: number) => void;
52+
onCalibrationConcentrationChange: (concentration: number) => void;
53+
onOrificeChange: (orifice: number) => void;
54+
onFlowDataChange: (flowData: FlowData) => void;
55+
}
56+
57+
/**
58+
* Compute the gas flow function of pressure
59+
* @param temperature - Temperature in K
60+
* @param pressure - Inlet pressure in kPa
61+
* @param outletPressure - Outlet pressure in kPa
62+
* @param gas - Gas mixture
63+
* @param orifice - Orifice diameter in mm
64+
* @returns the mass flow rate in kg/s and the critical pressure in kPa
65+
*/
66+
async function computeGasFlowFunctionOfPressure(
67+
temperature: number,
68+
pressure: number,
69+
outletPressure: number,
70+
gas: GasMixtureExt,
71+
orifice: number,
72+
): Promise<FlowData> {
73+
// Initialize GERG-2008 module
74+
const AGA8 = await AGA8wasm();
75+
76+
AGA8.SetupGERG();
77+
78+
const A = Math.PI * Math.pow(orifice / 2000, 2); // A - Area of the orifice
79+
80+
// Calculate gas properties
81+
const molarMass = AGA8.MolarMassGERG(gas.gasMixture); // g/mol
82+
const { D } = AGA8.DensityGERG(0, temperature, pressure, gas.gasMixture); // mol/L
83+
const { D: D_out } = AGA8.DensityGERG(
84+
0,
85+
temperature,
86+
outletPressure,
87+
gas.gasMixture,
88+
); // mol/L
89+
const properties = AGA8.PropertiesGERG(temperature, D, gas.gasMixture);
90+
const molarMassSI = molarMass / 1000; // kg/mol (SI units)
91+
const densitySI = D * 1000; // mol/m³ (SI units)
92+
// Extract critical flow factor (Cf)
93+
const Cf = properties.Cf;
94+
95+
/** Specific gas constant */
96+
const Rs = R / molarMassSI; // J/(kg·K)
97+
98+
// Maximal outlet pressure (critical flow)
99+
const p_crit = pressure * Cf; // kPa
100+
// Calculate mass flow rate
101+
// Q = Cd * Cf * A * P / sqrt(Rs * T)
102+
const massFlow =
103+
(Cd * Cf * A * (pressure * 1000)) / Math.sqrt(Rs * temperature); // kg/s
104+
105+
const rho = densitySI * molarMassSI; // kg/m³
106+
const rho_out = D_out * 1000 * molarMassSI; // kg/m³
107+
108+
const _flowData = {
109+
massFlow: massFlow,
110+
p_crit: p_crit,
111+
A: A,
112+
properties: properties,
113+
molarMass: molarMass,
114+
Rs: Rs,
115+
rho: rho,
116+
rho_out: rho_out,
117+
};
118+
119+
// Output results
120+
logSonicNozzleFlowCalculation(
121+
gas,
122+
temperature,
123+
pressure,
124+
outletPressure,
125+
orifice,
126+
_flowData,
127+
);
128+
129+
return _flowData;
130+
}
131+
132+
export const CalibrationInlet: React.FC<CalibrationInletProps> = ({
133+
label,
134+
pressure,
135+
selectedGas,
136+
selectedCalibrationConcentration,
137+
selectedOrifice,
138+
temperature,
139+
onPressureChange,
140+
onCalibrationConcentrationChange,
141+
onOrificeChange,
142+
onFlowDataChange,
143+
}) => {
144+
useEffect(() => {
145+
const updateFlow = async () => {
146+
const newFlowData = await computeGasFlowFunctionOfPressure(
147+
temperature,
148+
pressure,
149+
101.325,
150+
selectedGas,
151+
selectedOrifice,
152+
);
153+
154+
onFlowDataChange(newFlowData);
155+
};
156+
157+
updateFlow();
158+
}, [temperature, pressure, selectedGas, selectedOrifice, onFlowDataChange]);
159+
160+
return (
161+
<div>
162+
<PressureSlider
163+
label={`${label} Pressure`}
164+
value={pressure}
165+
onChange={onPressureChange}
166+
/>
167+
<div className="my-4">
168+
<ConcentrationSelector
169+
label={`${label} concentration`}
170+
selectedConcentration={selectedCalibrationConcentration}
171+
onConcentrationChange={onCalibrationConcentrationChange}
172+
/>
173+
</div>
174+
<OrificeSelector
175+
label={`Orifice ${label}`}
176+
selectedOrifice={selectedOrifice}
177+
onOrificeChange={onOrificeChange}
178+
/>
179+
</div>
180+
);
181+
};
182+

src/components/GasInlet.tsx

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ import type React from "react";
2020
import {
2121
AGA8wasm,
2222
PropertiesGERGResult,
23+
R,
2324
type GasMixtureExt,
2425
} from "@sctg/aga8-js";
2526
import { useEffect } from "react";
2627

2728
import { GasSelector } from "./GasSelector";
2829
import { OrificeSelector } from "./OrificeSelector";
2930
import { PressureSlider } from "./PressureSlider";
31+
import { logSonicNozzleFlowCalculation } from "@/utilities";
32+
import { Cd } from "@/config/site";
3033

3134
export type FlowData = {
3235
massFlow: number; // kg/s
@@ -50,17 +53,6 @@ interface GasInletProps {
5053
onFlowDataChange: (flowData: FlowData) => void;
5154
}
5255

53-
// Constants for toroidal nozzle
54-
const Re_thoroidal_max = 3.2e7; // Maximal Reynolds number for toroidal nozzle
55-
const Re_thoroidal_min = 2.1e4; // Minimal Reynolds number for toroidal nozzle
56-
const Cd_a = 0.9959; // Constant for toroidal nozzle
57-
const Cd_b = 2.72; // Reynolds number factor for toroidal nozzle
58-
const Cd_n = 0.5; // Reynolds number exponent for toroidal nozzle
59-
const Cd_max = Cd_a - Cd_b * Re_thoroidal_min ** (Cd_n * -1); // Typical discharge coefficient for toroidal sonic nozzle
60-
const Cd_min = Cd_a - Cd_b * Re_thoroidal_max ** (Cd_n * -1); // Typical discharge coefficient for toroidal sonic nozzle
61-
const Cd_geometric_mean = Math.sqrt(Cd_max * Cd_min); // Geometric mean of discharge coefficients
62-
const Cd = Cd_geometric_mean; // Use geometric mean of discharge coefficients
63-
6456
/**
6557
* Compute the gas flow function of pressure
6658
* @param temperature - Temperature in K
@@ -98,7 +90,6 @@ async function computeGasFlowFunctionOfPressure(
9890
const densitySI = D * 1000; // mol/m³ (SI units)
9991
// Extract critical flow factor (Cf)
10092
const Cf = properties.Cf;
101-
const R = 8.31446261815324; // Universal gas constant in J/(mol·K)
10293
/** Specific gas constant */
10394
const Rs = R / molarMassSI; // J/(kg·K)
10495

@@ -186,44 +177,3 @@ export const GasInlet: React.FC<GasInletProps> = ({
186177
);
187178
};
188179

189-
function logSonicNozzleFlowCalculation(
190-
gas: GasMixtureExt,
191-
temperature: number,
192-
pressure: number,
193-
outletPressure: number,
194-
orifice: number,
195-
flowData: FlowData,
196-
// eslint-disable-next-line no-console
197-
target = console.log,
198-
) {
199-
const { A, molarMass, Rs, rho, p_crit, properties, rho_out, massFlow } =
200-
flowData;
201-
202-
target(`Sonic Nozzle Flow Calculation (ISO 9300:2022) for ${gas.name}
203-
\tInput conditions:
204-
\t\tTemperature: ${(temperature - 273.15).toPrecision(2)}°C
205-
\t\tInlet pressure: ${pressure.toPrecision(3)} kPa (${(pressure / 100).toPrecision(3)} bar)
206-
\t\tOutlet pressure: ${outletPressure.toPrecision(3)} kPa (${(outletPressure / 100).toPrecision(3)} bar)
207-
\t\tThroat diameter: ${orifice.toPrecision(4)} mm
208-
\t\tThroat area: ${(A * 1e6).toPrecision(4)}mm² (${A.toPrecision(4)} m²)
209-
\t\tMaximal discharge coefficient: ${Cd_max.toPrecision(4)}
210-
\t\tMinimal discharge coefficient: ${Cd_min.toPrecision(4)}
211-
\t\tUsed discharge coefficients: ${Cd.toPrecision(4)}
212-
213-
\tGas properties at inlet conditions:
214-
\t\tMolar mass: ${molarMass.toPrecision(4)} g/mol
215-
\t\tSpecific gas constant: ${Rs.toPrecision(4)} J/(kg·K)
216-
\t\tDensity: ${rho.toPrecision(4)} kg/m³
217-
\t\tCritical flow factor (Cf): ${properties.Cf.toPrecision(6)}
218-
\t\tHeat capacity ratio (κ): ${properties.Kappa.toPrecision(6)}
219-
220-
\tGas properties at outlet conditions:
221-
\t\tDensity: ${rho_out.toPrecision(4)} kg/m³
222-
223-
\tResults:
224-
\t\tOutlet pressure must be : <${p_crit.toPrecision(2)} kPa ${p_crit > outletPressure ? "✅" : "❌"}
225-
\t\tMass flow rate: ${massFlow.toPrecision(4)} kg/s
226-
\t\tMass flow rate: ${(massFlow * 1000 * 3600).toPrecision(4)} g/h
227-
\t\tMass flow rate: ${(massFlow * 1000 * 60).toPrecision(4)} g/min
228-
\t\tVolume flow at outlet: ${(massFlow / rho_out).toPrecision(4)} m³/s (${((massFlow / rho_out) * 1000 * 3600).toPrecision(4)} L/h)`);
229-
}

src/config/site.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,14 @@ export const siteConfig = {
104104
{ concentration: 1e-3, name: "1000 ppm" },
105105
],
106106
};
107+
108+
// Constants for toroidal nozzle
109+
export const Re_thoroidal_max = 3.2e7; // Maximal Reynolds number for toroidal nozzle
110+
export const Re_thoroidal_min = 2.1e4; // Minimal Reynolds number for toroidal nozzle
111+
export const Cd_a = 0.9959; // Constant for toroidal nozzle
112+
export const Cd_b = 2.72; // Reynolds number factor for toroidal nozzle
113+
export const Cd_n = 0.5; // Reynolds number exponent for toroidal nozzle
114+
export const Cd_max = Cd_a - Cd_b * Re_thoroidal_min ** (Cd_n * -1); // Typical discharge coefficient for toroidal sonic nozzle
115+
export const Cd_min = Cd_a - Cd_b * Re_thoroidal_max ** (Cd_n * -1); // Typical discharge coefficient for toroidal sonic nozzle
116+
export const Cd_geometric_mean = Math.sqrt(Cd_max * Cd_min); // Geometric mean of discharge coefficients
117+
export const Cd = Cd_geometric_mean; // Use geometric mean of discharge coefficients

0 commit comments

Comments
 (0)