diff --git a/glc.py b/glc.py new file mode 100644 index 0000000..eed17ca --- /dev/null +++ b/glc.py @@ -0,0 +1,145 @@ +import pathsim +from pathsim import Simulation, Connection +import numpy as np +import matplotlib.pyplot as plt +import pathview + +# Create global variables +import numpy as np + +c_T_inlet = 3.09e-2 # mol/m^3 (c_T(L+)), Inlet tritium concentration in liquid just before inlet +y_T2_in = 0.0 # Inlet tritium molar fraction in gas (0 = pure purge gas) +P_0 = 5e5 # Pa, Gas total pressure at inlet +ρ_l = 9000 # kg/m^3, Liquid density +K_s = 2e-6 # Tritium Sievert's constant in liquid + +L = 3.0 # m, Height of the bubble column +D = 0.5 # m, Column diameter +ε_g = 0.04 # Gas phase fraction + +Q_l = 0.01 # m^3/s, Volumetric flow rate of liquid phase +Q_g = 0.0005 # m^3/s, Volumetric flow rate of gas phase at inlet + +a = 20 # m^-1, Specific liquid-gas interfacial area + +E_g = 0.05 # m^2/s, Effective axial dispersion coefficient, gas phase +E_l = 0.01 # m^2/s, Effective axial dispersion coefficient, liquid phase + +h_l = 1e-4 # m/s, Mass transfer coefficient, tritium liquid - gas + +g = 9.81 # m/s^2, Gravitational acceleration + +# Calculated parameters +ε_l = 1 - ε_g # Liquid phase fraction + +A = np.pi * (D / 2) ** 2 # m^2, Cross-sectional area of the column +A_l = A * ε_l # m^2, Cross-sectional area of the liquid phase +A_g = A * ε_g # m^2, Cross-sectional area of the gas phase + +u_l = Q_l / A_l # m/s, superficial liquid inlet velocity (positive for downward flow) +u_g0 = Q_g / A_g # m/s, gas inlet velocity (positive for upward flow) +R = 8.314 +T = 623 +N_A = 6.0221408e23 + +# Create blocks +blocks, events = [], [] + +p_t2_inlet_5 = pathsim.blocks.sources.Constant(value=y_T2_in) +blocks.append(p_t2_inlet_5) + +blanket_6 = pathview.custom_pathsim_blocks.Process( + residence_time=2, initial_value=1, source_term=0 +) +blocks.append(blanket_6) + +storage_7 = pathview.custom_pathsim_blocks.Process(residence_time=0, source_term=0) +blocks.append(storage_7) + +to_concentration_14 = pathsim.blocks.amplifier.Amplifier(gain=Q_l**-1) +blocks.append(to_concentration_14) + +inventories_16 = pathsim.blocks.scope.Scope( + labels=["blanket (inv)", "storage (inv)", "adder 20"] +) +blocks.append(inventories_16) + +all_t_flow_rates_18 = pathsim.blocks.scope.Scope(labels=["blanket (mass_flow_rate)"]) +blocks.append(all_t_flow_rates_18) + +adder_20_20 = pathsim.blocks.adder.Adder() +blocks.append(adder_20_20) + +glc_21_21 = pathview.custom_pathsim_blocks.GLC( + P_outlet=5e5, + L=L, + u_l=u_l, + u_g0=u_g0, + ε_g=ε_g, + ε_l=ε_l, + E_g=E_g, + E_l=E_l, + a=a, + h_l=h_l, + ρ_l=ρ_l, + K_s=K_s, + Q_l=Q_l, + Q_g=Q_g, + D=D, + T=T, + initial_nb_of_elements=20, +) +blocks.append(glc_21_21) + + +# Create events + + +# Create connections + +connections = [ + Connection(blanket_6["mass_flow_rate"], to_concentration_14[0]), + Connection(blanket_6["inv"], inventories_16[0]), + Connection(storage_7["inv"], inventories_16[1]), + Connection(blanket_6["mass_flow_rate"], all_t_flow_rates_18[0]), + Connection(blanket_6["inv"], adder_20_20[0]), + Connection(storage_7["inv"], adder_20_20[1]), + Connection(adder_20_20[0], inventories_16[2]), + Connection(to_concentration_14[0], glc_21_21["c_T_inlet"]), + Connection(p_t2_inlet_5[0], glc_21_21["y_T2_in"]), + Connection(glc_21_21["T_out_gas"], storage_7[0]), + Connection(glc_21_21["T_out_liquid"], blanket_6[0]), +] + +# Create simulation +my_simulation = Simulation( + blocks, + connections, + events=events, + Solver=pathsim.solvers.SSPRK22, + dt=1, + dt_min=1e-16, + iterations_max=200, + log=True, + tolerance_fpi=1e-10, + **{}, +) + +if __name__ == "__main__": + my_simulation.run(200) + + # Optional: Plotting results + scopes = [block for block in blocks if isinstance(block, pathsim.blocks.Scope)] + fig, axs = plt.subplots( + nrows=len(scopes), sharex=True, figsize=(10, 5 * len(scopes)) + ) + for i, scope in enumerate(scopes): + plt.sca(axs[i] if len(scopes) > 1 else axs) + time, data = scope.read() + # plot the recorded data + for p, d in enumerate(data): + lb = scope.labels[p] if p < len(scope.labels) else f"port {p}" + plt.plot(time, d, label=lb) + plt.legend() + plt.xlabel("Time") + plt.show() diff --git a/pyproject.toml b/pyproject.toml index f16925b..738d537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ requires-python = ">=3.8" dependencies = [ "pathsim>=0.8.2", - "pathsim-chem", + "pathsim-chem@git+https://github.com/pathsim/pathsim-chem@glc", "matplotlib>=3.7.0", "numpy>=1.24.0", "plotly>=6.0", diff --git a/src/components/nodes/BubblerNode.jsx b/src/components/nodes/BubblerNode.jsx index 2caa387..0b5fb74 100644 --- a/src/components/nodes/BubblerNode.jsx +++ b/src/components/nodes/BubblerNode.jsx @@ -20,29 +20,29 @@ export default function BubblerNode({ data }) { {/* Labels for sample in handles */} -
Sample in
-
soluble
-
@@ -52,52 +52,52 @@ export default function BubblerNode({ data }) { -
Vials 1
-
2
-
3
-
4
- + -
+
{data.label}
+ + +
+ c_T_in +
+ +
+ flow_l +
+ +
+ y_T2_in +
+ +
+ flow_g +
+ + + + + + +
+ c_T_out +
+
+ efficiency +
+ +
+ Q_l +
+ +
+ n_T_out_liquid +
+ +
+ y_T2_out +
+ +
+ P_out +
+ +
+ Q_g_out +
+ +
+ n_T_out_gas +
+ + + + + + + + + +
+ ); +} \ No newline at end of file diff --git a/src/nodeConfig.js b/src/nodeConfig.js index d2e8a8f..41d2024 100644 --- a/src/nodeConfig.js +++ b/src/nodeConfig.js @@ -16,6 +16,7 @@ import BubblerNode from './components/nodes/BubblerNode'; import WallNode from './components/nodes/WallNode'; import { DynamicHandleNode } from './components/nodes/DynamicHandleNode'; import SwitchNode from './components/nodes/SwitchNode'; +import { GLCNode } from './components/nodes/GLCNode'; // Node types mapping export const nodeTypes = { @@ -64,6 +65,7 @@ export const nodeTypes = { ode: DynamicHandleNode, interface: DynamicHandleNode, switch: SwitchNode, + glc: GLCNode, }; export const nodeMathTypes = { @@ -117,7 +119,7 @@ export const nodeCategories = { description: 'Filter and flow control nodes' }, 'Fuel Cycle': { - nodes: ['process', 'process_horizontal', 'bubbler', 'wall'], + nodes: ['process', 'process_horizontal', 'bubbler', 'wall', 'glc'], description: 'Fuel cycle specific nodes' }, 'Others': { @@ -194,6 +196,7 @@ export const getNodeDisplayName = (nodeType) => { 'samplehold': 'Sample Hold', 'comparator': 'Comparator', 'interface': 'Interface', + 'glc': 'Gas-Liquid Contactor', }; return displayNames[nodeType] || nodeType.charAt(0).toUpperCase() + nodeType.slice(1); diff --git a/src/python/custom_pathsim_blocks.py b/src/python/custom_pathsim_blocks.py index 223c160..ec8bd15 100644 --- a/src/python/custom_pathsim_blocks.py +++ b/src/python/custom_pathsim_blocks.py @@ -37,7 +37,10 @@ def update(self, t): outputs = [None, None] outputs[self._port_map_out["inv"]] = x outputs[self._port_map_out["mass_flow_rate"]] = mass_rate - # update the outputs + + # make sure an array of size two is returned + outputs = [float(o) for o in outputs] + self.outputs.update_from_array(outputs) diff --git a/src/python/pathsim_utils.py b/src/python/pathsim_utils.py index ab30c21..2cb9274 100644 --- a/src/python/pathsim_utils.py +++ b/src/python/pathsim_utils.py @@ -52,7 +52,7 @@ FestimWall, Integrator, ) -from pathsim_chem import Bubbler4, Splitter +from pathsim_chem import Bubbler4, Splitter, GLC import inspect NAME_TO_SOLVER = { @@ -133,6 +133,7 @@ "fir": pathsim.blocks.FIR, "interface": pathsim.subsystem.Interface, "switch": pathsim.blocks.Switch, + "glc": GLC, } math_blocks = { @@ -279,7 +280,6 @@ def make_solver_params(solver_prms: dict, eval_namespace=None): # TODO get the default from pathsim._constants prms[k] = None else: - print(v, type(v)) prms[k] = eval(v, eval_namespace) elif k == "log": if v == "true":