From b5f72e74eb4316fe5e58616ad080693961a5b871 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 28 Jul 2025 17:35:03 -0400 Subject: [PATCH 1/7] added Bubbler block + test --- src/custom_pathsim_blocks.py | 75 ++++++++++++++++++++++++++++++++++++ test/test_custom_blocks.py | 29 ++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 test/test_custom_blocks.py diff --git a/src/custom_pathsim_blocks.py b/src/custom_pathsim_blocks.py index a3f9c7fe..d65353a8 100644 --- a/src/custom_pathsim_blocks.py +++ b/src/custom_pathsim_blocks.py @@ -1,4 +1,6 @@ from pathsim.blocks import Block, ODE +import pathsim.blocks +from pathsim import Subsystem, Interface, Connection import numpy as np @@ -35,3 +37,76 @@ def update(self, t): u = self.inputs[0] # mult by fractions and update outputs self.outputs.update_from_array(self.fractions * u) + + +# BUBBLER SYSTEM + + +class Bubbler(Subsystem): + vial_efficiency: float + conversion_efficiency: float + n_soluble_vials: float + n_insoluble_vials: float + + def __init__( + self, + conversion_efficiency=0.9, + vial_efficiency=0.9, + ): + self.n_soluble_vials = 2 + self.n_insoluble_vials = 2 + self.vial_efficiency = vial_efficiency + col_eff1 = Splitter(n=2, fractions=[vial_efficiency, 1 - vial_efficiency]) + vial_1 = pathsim.blocks.Integrator() + col_eff2 = Splitter(n=2, fractions=[vial_efficiency, 1 - vial_efficiency]) + vial_2 = pathsim.blocks.Integrator() + + conversion_eff = Splitter( + n=2, fractions=[conversion_efficiency, 1 - conversion_efficiency] + ) + + col_eff3 = Splitter(n=2, fractions=[vial_efficiency, 1 - vial_efficiency]) + vial_3 = pathsim.blocks.Integrator() + col_eff4 = Splitter(n=2, fractions=[vial_efficiency, 1 - vial_efficiency]) + vial_4 = pathsim.blocks.Integrator() + + add1 = pathsim.blocks.Adder() + add2 = pathsim.blocks.Adder() + + interface = Interface() + + blocks = [ + vial_1, + col_eff1, + vial_2, + col_eff2, + conversion_eff, + vial_3, + col_eff3, + vial_4, + col_eff4, + add1, + add2, + interface, + ] + connections = [ + Connection(interface[0], col_eff1), + Connection(col_eff1[0], vial_1), + Connection(col_eff1[1], col_eff2), + Connection(col_eff2[0], vial_2), + Connection(col_eff2[1], conversion_eff), + Connection(conversion_eff[0], add1[0]), + Connection(conversion_eff[1], add2[0]), + Connection(interface[1], add1[1]), + Connection(add1, col_eff3), + Connection(col_eff3[0], vial_3), + Connection(col_eff3[1], col_eff4), + Connection(col_eff4[0], vial_4), + Connection(col_eff4[1], add2[1]), + Connection(vial_1, interface[0]), + Connection(vial_2, interface[1]), + Connection(vial_3, interface[2]), + Connection(vial_4, interface[3]), + Connection(add2, interface[4]), + ] + super().__init__(blocks, connections) diff --git a/test/test_custom_blocks.py b/test/test_custom_blocks.py new file mode 100644 index 00000000..08083b75 --- /dev/null +++ b/test/test_custom_blocks.py @@ -0,0 +1,29 @@ +import pathsim.blocks +from pathsim import Simulation, Connection +from src.custom_pathsim_blocks import Bubbler + + +def test_bubbler(): + my_bubbler = Bubbler() + + source_soluble = pathsim.blocks.Constant(1) + source_insoluble = pathsim.blocks.Constant(0.5) + environment = pathsim.blocks.Integrator() + sco = pathsim.blocks.Scope( + labels=["Vial 1", "Vial 2", "Vial 3", "Vial 4", "Environment"], + ) + + blocks = [source_soluble, source_insoluble, my_bubbler, environment, sco] + + connections = [ + Connection(source_soluble, my_bubbler[0]), + Connection(source_insoluble, my_bubbler[1]), + Connection(my_bubbler[0], sco[0]), + Connection(my_bubbler[1], sco[1]), + Connection(my_bubbler[2], sco[2]), + Connection(my_bubbler[3], sco[3]), + Connection(my_bubbler[4], environment), + ] + + sim = Simulation(blocks, connections) + sim.run(20) From aa017175ee1b5ab1af2aebf3d8d21ff6baac96d8 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 28 Jul 2025 17:51:41 -0400 Subject: [PATCH 2/7] added replacement times --- src/custom_pathsim_blocks.py | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/custom_pathsim_blocks.py b/src/custom_pathsim_blocks.py index d65353a8..a895be88 100644 --- a/src/custom_pathsim_blocks.py +++ b/src/custom_pathsim_blocks.py @@ -52,7 +52,9 @@ def __init__( self, conversion_efficiency=0.9, vial_efficiency=0.9, + replacement_times=None, ): + self.reset_times = replacement_times or [] self.n_soluble_vials = 2 self.n_insoluble_vials = 2 self.vial_efficiency = vial_efficiency @@ -75,6 +77,8 @@ def __init__( interface = Interface() + self.vials = [vial_1, vial_2, vial_3, vial_4] + blocks = [ vial_1, col_eff1, @@ -110,3 +114,36 @@ def __init__( Connection(add2, interface[4]), ] super().__init__(blocks, connections) + + def create_reset_events_one_vial( + self, block, reset_times + ) -> list[pathsim.blocks.Schedule]: + events = [] + + def reset_itg(_): + block.reset() + + for t in reset_times: + events.append( + pathsim.blocks.Schedule(t_start=t, t_end=t, func_act=reset_itg) + ) + return events + + def create_reset_events(self) -> list[pathsim.blocks.Schedule]: + reset_times = self.reset_times + events = [] + # if reset_times is a single list use it for all vials + if isinstance(reset_times, (int, float)): + reset_times = [reset_times] + # if it's a flat list use it for all vials + elif isinstance(reset_times, list) and all( + isinstance(t, (int, float)) for t in reset_times + ): + reset_times = [reset_times] * len(self.vials) + elif isinstance(reset_times, list) and len(reset_times) != len(self.vials): + raise ValueError( + "reset_times must be a single value or a list with the same length as the number of vials" + ) + for i, vial in enumerate(self.vials): + events.extend(self.create_reset_events_one_vial(vial, reset_times[i])) + return events From b17c30475a9130aba13659002ff6268526247d6a Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 28 Jul 2025 20:34:56 -0400 Subject: [PATCH 3/7] added BubblerNode --- src/App.jsx | 4 ++++ src/BubblerNode.jsx | 32 ++++++++++++++++++++++++++++++ src/backend.py | 48 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/BubblerNode.jsx diff --git a/src/App.jsx b/src/App.jsx index d3367d46..25a518d4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -26,6 +26,7 @@ import DefaultNode from './DefaultNode'; import { makeEdge } from './CustomEdge'; import MultiplierNode from './MultiplierNode'; import { Splitter2Node, Splitter3Node } from './Splitters'; +import BubblerNode from './BubblerNode'; // Add nodes as a node type for this script const nodeTypes = { @@ -46,6 +47,7 @@ const nodeTypes = { pid: DefaultNode, splitter2: Splitter2Node, splitter3: Splitter3Node, + bubbler: BubblerNode, }; // Defining initial nodes and edges. In the data section, we have label, but also parameters specific to the node. @@ -466,6 +468,8 @@ export default function App() { case 'splitter3': nodeData = { ...nodeData, f1: '1/3', f2: '1/3', f3: '1/3' }; break; + case 'bubbler': + nodeData = { ...nodeData, conversion_efficiency: '0.95', vial_efficiency: '0.9', replacement_time: '' }; default: // For any other types, just use basic data break; diff --git a/src/BubblerNode.jsx b/src/BubblerNode.jsx new file mode 100644 index 00000000..d6003192 --- /dev/null +++ b/src/BubblerNode.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Handle } from '@xyflow/react'; + +export default function BubblerNode({ data }) { + return ( +
+
{data.label}
+ + + + + + + + + + +
+ ); +} diff --git a/src/backend.py b/src/backend.py index 8aa9a7b9..5f5f725c 100644 --- a/src/backend.py +++ b/src/backend.py @@ -30,7 +30,7 @@ PID, Schedule, ) -from custom_pathsim_blocks import Process, Splitter +from custom_pathsim_blocks import Process, Splitter, Bubbler NAME_TO_SOLVER = { "SSPRK22": pathsim.solvers.SSPRK22, @@ -137,6 +137,24 @@ def reset_itg(_): return block, events +def create_bubbler(node: dict) -> Bubbler: + """ + Create a Bubbler block based on the node data. + """ + # Extract parameters from node data + block = Bubbler( + conversion_efficiency=eval(node["data"]["conversion_efficiency"]), + vial_efficiency=eval(node["data"]["vial_efficiency"]), + replacement_times=eval(node["data"]["replacement_time"]) + if node["data"].get("replacement_time") != "" + else None, + ) + + events = block.create_reset_events() + + return block, events + + # TODO refactor this function... # Function to convert graph to pathsim and run simulation @app.route("/run-pathsim", methods=["POST"]) @@ -398,6 +416,9 @@ def func(x): else 0 ), ) + elif node["type"] == "bubbler": + block, events_bubbler = create_bubbler(node) + events.extend(events_bubbler) else: raise ValueError(f"Unknown node type: {node['type']}") block.id = node["id"] @@ -440,12 +461,37 @@ def func(x): raise ValueError( f"Invalid source handle '{edge['sourceHandle']}' for {edge}." ) + elif isinstance(block, Bubbler): + if edge["sourceHandle"] == "vial1": + output_index = 0 + elif edge["sourceHandle"] == "vial2": + output_index = 1 + elif edge["sourceHandle"] == "vial3": + output_index = 2 + elif edge["sourceHandle"] == "vial4": + output_index = 3 + elif edge["sourceHandle"] == "sample_out": + output_index = 4 + else: + raise ValueError( + f"Invalid source handle '{edge['sourceHandle']}' for {edge}." + ) else: output_index = 0 if isinstance(target_block, Scope): source_node = find_node_by_id(edge["source"]) input_index = target_block._source_nodes_order.index(source_node) + + elif isinstance(target_block, Bubbler): + if edge["targetHandle"] == "sample_in_soluble": + input_index = 0 + elif edge["targetHandle"] == "sample_in_insoluble": + input_index = 1 + else: + raise ValueError( + f"Invalid target handle '{edge['targetHandle']}' for {edge}." + ) else: input_index = block_to_input_index[target_block] From ae542f66f21ebfa44aa3747c956a70fad569029a Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Mon, 28 Jul 2025 21:09:53 -0400 Subject: [PATCH 4/7] documentation + better styling --- src/BubblerNode.jsx | 91 +++++++++++++++++++++++++++++++++--- src/custom_pathsim_blocks.py | 29 ++++++++++-- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/src/BubblerNode.jsx b/src/BubblerNode.jsx index d6003192..21e5a656 100644 --- a/src/BubblerNode.jsx +++ b/src/BubblerNode.jsx @@ -5,8 +5,8 @@ export default function BubblerNode({ data }) { return (
-
{data.label}
+
{data.label}
+ + {/* Labels for sample in handles */} + +
+ Sample in +
+
+ soluble +
+
+ insoluble +
- - +
+ Vials 1 +
+
2
+ +
+ 3 +
+
+ 4 +
+ + + - + + + +
+ out +
diff --git a/src/custom_pathsim_blocks.py b/src/custom_pathsim_blocks.py index a895be88..a7784c08 100644 --- a/src/custom_pathsim_blocks.py +++ b/src/custom_pathsim_blocks.py @@ -43,6 +43,8 @@ def update(self, t): class Bubbler(Subsystem): + """Subsystem representing a tritium bubbling system with 4 vials.""" + vial_efficiency: float conversion_efficiency: float n_soluble_vials: float @@ -54,7 +56,16 @@ def __init__( vial_efficiency=0.9, replacement_times=None, ): - self.reset_times = replacement_times or [] + """ + Args: + conversion_efficiency: Conversion efficiency from insoluble to soluble (between 0 and 1). + vial_efficiency: collection efficiency of each vial (between 0 and 1). + replacement_times: List of times at which each vial is replaced. If None, no replacement + events are created. If a single value is provided, it is used for all vials. + If a single list of floats is provided, it will be used for all vials. + If a list of lists is provided, each sublist corresponds to the replacement times for each vial. + """ + self.reset_times = replacement_times self.n_soluble_vials = 2 self.n_insoluble_vials = 2 self.vial_efficiency = vial_efficiency @@ -115,7 +126,7 @@ def __init__( ] super().__init__(blocks, connections) - def create_reset_events_one_vial( + def _create_reset_events_one_vial( self, block, reset_times ) -> list[pathsim.blocks.Schedule]: events = [] @@ -130,9 +141,19 @@ def reset_itg(_): return events def create_reset_events(self) -> list[pathsim.blocks.Schedule]: + """Create reset events for all vials based on the replacement times. + + Raises: + ValueError: If reset_times is not valid. + + Returns: + list of reset events. + """ reset_times = self.reset_times events = [] # if reset_times is a single list use it for all vials + if reset_times is None: + return events if isinstance(reset_times, (int, float)): reset_times = [reset_times] # if it's a flat list use it for all vials @@ -140,10 +161,12 @@ def create_reset_events(self) -> list[pathsim.blocks.Schedule]: isinstance(t, (int, float)) for t in reset_times ): reset_times = [reset_times] * len(self.vials) + elif isinstance(reset_times, np.ndarray) and reset_times.ndim == 1: + reset_times = [reset_times.tolist()] * len(self.vials) elif isinstance(reset_times, list) and len(reset_times) != len(self.vials): raise ValueError( "reset_times must be a single value or a list with the same length as the number of vials" ) for i, vial in enumerate(self.vials): - events.extend(self.create_reset_events_one_vial(vial, reset_times[i])) + events.extend(self._create_reset_events_one_vial(vial, reset_times[i])) return events From b13ff6d6be1f41990c9655780c2203628667d5cb Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 08:54:32 -0400 Subject: [PATCH 5/7] updated BABY --- saved_graphs/baby.json | 643 +++++++++++++++++------------------------ 1 file changed, 259 insertions(+), 384 deletions(-) diff --git a/saved_graphs/baby.json b/saved_graphs/baby.json index 09ce123f..d1d70d0e 100644 --- a/saved_graphs/baby.json +++ b/saved_graphs/baby.json @@ -46,8 +46,8 @@ "dragging": false, "id": "4", "measured": { - "height": 86, - "width": 136 + "height": 80, + "width": 90 }, "position": { "x": 508, @@ -98,8 +98,8 @@ "dragging": false, "id": "7", "measured": { - "height": 86, - "width": 136 + "height": 80, + "width": 90 }, "position": { "x": 912, @@ -118,7 +118,7 @@ "dragging": false, "id": "8", "measured": { - "height": 48, + "height": 120, "width": 200 }, "position": { @@ -130,255 +130,150 @@ }, { "data": { - "initial_value": "", - "label": "IV vial 1" - }, - "dragging": false, - "id": "9", - "measured": { - "height": 48, - "width": 200 - }, - "position": { - "x": 1418, - "y": 554 - }, - "selected": false, - "type": "integrator" - }, - { - "data": { - "f1": "0.9", - "f2": "0.1", - "label": "Collection eff." + "label": "IV vial activity" }, "dragging": false, - "id": "11", + "id": "21", "measured": { - "height": 120, + "height": 140, "width": 120 }, "position": { - "x": 1305, - "y": 698 - }, - "selected": false, - "type": "splitter2" - }, - { - "data": { - "initial_value": "", - "label": "IV vial 2" - }, - "dragging": false, - "id": "12", - "measured": { - "height": 48, - "width": 200 - }, - "position": { - "x": 1542, - "y": 691 + "x": 1949.5229665105167, + "y": 563.941606900507 }, "selected": false, - "type": "integrator" + "type": "scope" }, { "data": { - "f1": "0.9", - "f2": "0.1", - "label": "Collection eff." + "f1": "0.01", + "f2": "0.99", + "label": "soluble vs insoluble" }, "dragging": false, - "id": "13", + "id": "1", "measured": { "height": 120, "width": 120 }, "position": { - "x": 1312, - "y": 871 + "x": 1394.0782538373574, + "y": 743.9802025169162 }, "selected": false, "type": "splitter2" }, { - "data": { - "f1": "0.99", - "f2": "0.01", - "label": "HT -> HTO eff." - }, - "dragging": false, - "id": "14", - "measured": { - "height": 120, - "width": 120 - }, + "id": "23", + "type": "bubbler", "position": { - "x": 1353.9258928782547, - "y": 1099.0441878253396 + "x": 1595, + "y": 722 }, - "selected": false, - "type": "splitter2" - }, - { "data": { - "initial_value": "", - "label": "IV vial 3" + "label": "IV bubbler", + "conversion_efficiency": "0.95", + "vial_efficiency": "0.9", + "replacement_time": "np.arange(5, 50, step=3)" }, - "dragging": false, - "id": "15", "measured": { - "height": 48, - "width": 200 - }, - "position": { - "x": 1872, - "y": 593 + "width": 230, + "height": 160 }, "selected": false, - "type": "integrator" + "dragging": false }, { - "data": { - "f1": "0.9", - "f2": "0.1", - "label": "Collection eff." - }, - "dragging": false, - "id": "16", - "measured": { - "height": 120, - "width": 120 - }, + "id": "24", + "type": "splitter2", "position": { - "x": 1659, - "y": 771 + "x": 1193, + "y": 851 }, - "selected": false, - "type": "splitter2" - }, - { "data": { - "initial_value": "", - "label": "IV vial 4" + "label": "IV vs OV", + "f1": "0.8", + "f2": "0.2" }, - "dragging": false, - "id": "17", "measured": { - "height": 48, - "width": 200 - }, - "position": { - "x": 1865, - "y": 669 + "width": 120, + "height": 120 }, "selected": false, - "type": "integrator" + "dragging": false }, { - "data": { - "f1": "0.9", - "f2": "0.1", - "label": "Collection eff." - }, - "dragging": false, - "id": "18", - "measured": { - "height": 120, - "width": 120 - }, + "id": "25", + "type": "splitter2", "position": { - "x": 1667, - "y": 951 + "x": 1410.747877541758, + "y": 965.2139451372932 }, - "selected": false, - "type": "splitter2" - }, - { "data": { - "initial_value": "", - "label": "ambient air" + "label": "soluble vs insoluble", + "f1": "1", + "f2": "0" }, - "dragging": false, - "id": "19", "measured": { - "height": 48, - "width": 200 - }, - "position": { - "x": 2040, - "y": 913 + "width": 120, + "height": 120 }, "selected": false, - "type": "integrator" + "dragging": false }, { - "data": { - "label": "adder 20" - }, - "dragging": false, - "id": "20", - "measured": { - "height": 64, - "width": 64 - }, + "id": "26", + "type": "bubbler", "position": { - "x": 1769, - "y": 1185 + "x": 1602.4670834180595, + "y": 970.2206053879095 }, - "selected": false, - "type": "adder" - }, - { "data": { - "label": "Inventories" + "label": "OV bubbler", + "conversion_efficiency": "0.95", + "vial_efficiency": "0.9", + "replacement_time": "np.arange(5, 50, step=3)" }, - "dragging": false, - "id": "21", "measured": { - "height": 140, - "width": 120 - }, - "position": { - "x": 1822.4225842692827, - "y": 260.99001087345584 + "width": 230, + "height": 160 }, "selected": true, - "type": "scope" + "dragging": false }, { - "id": "0", - "type": "adder", + "id": "27", + "type": "integrator", "position": { - "x": 1517.8462568023597, - "y": 978.8349758222805 + "x": 2059.579367811878, + "y": 919.8031067000171 }, "data": { - "label": "adder 0" + "label": "environment", + "initial_value": "", + "reset_times": "" }, "measured": { - "width": 64, - "height": 64 + "width": 200, + "height": 48 }, "selected": false, "dragging": false }, { - "id": "1", - "type": "splitter2", + "id": "28", + "type": "scope", "position": { - "x": 865.0782538373575, - "y": 719.9802025169162 + "x": 1923.4902343023584, + "y": 1097.1334152526842 }, "data": { - "label": "soluble vs insoluble", - "f1": "0.01", - "f2": "0.99" + "label": "OV vial activity" }, "measured": { "width": 120, - "height": 120 + "height": 140 }, "selected": false, "dragging": false @@ -501,240 +396,201 @@ "type": "smoothstep" }, { - "data": {}, - "id": "e11-9-from_source1", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "11", + "id": "e24-1-from_source1", + "source": "24", + "target": "1", "sourceHandle": "source1", - "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 - }, - "target": "9", "targetHandle": null, - "type": "smoothstep" - }, - { + "type": "smoothstep", "data": {}, - "id": "e11-13-from_source2", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "11", - "sourceHandle": "source2", "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "13", - "targetHandle": null, - "type": "smoothstep" - }, - { - "data": {}, - "id": "e13-12-from_source1", "markerEnd": { - "color": "#ECDFCC", - "height": 20, "type": "arrowclosed", - "width": 20 - }, - "source": "13", - "sourceHandle": "source1", - "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 - }, - "target": "12", - "targetHandle": null, - "type": "smoothstep" + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e1-23-from_source1-to_sample_in_soluble", + "source": "1", + "target": "23", + "sourceHandle": "source1", + "targetHandle": "sample_in_soluble", + "type": "smoothstep", "data": {}, - "id": "e13-14-from_source2", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "13", - "sourceHandle": "source2", "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "14", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e1-23-from_source2-to_sample_in_insoluble", + "source": "1", + "target": "23", + "sourceHandle": "source2", + "targetHandle": "sample_in_insoluble", + "type": "smoothstep", "data": {}, - "id": "e16-15-from_source1", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "selected": false, - "source": "16", - "sourceHandle": "source1", "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "15", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e23-21-from_vial1", + "source": "23", + "target": "21", + "sourceHandle": "vial1", + "targetHandle": null, + "type": "smoothstep", "data": {}, - "id": "e16-18-from_source2", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "16", - "sourceHandle": "source2", "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "18", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e23-21-from_vial2", + "source": "23", + "target": "21", + "sourceHandle": "vial2", + "targetHandle": null, + "type": "smoothstep", "data": {}, - "id": "e18-17-from_source1", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "18", - "sourceHandle": "source1", "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "17", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e23-21-from_vial3", + "source": "23", + "target": "21", + "sourceHandle": "vial3", + "targetHandle": null, + "type": "smoothstep", "data": {}, - "id": "e14-20-from_source2", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "14", - "sourceHandle": "source2", "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "20", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e23-21-from_vial4", + "source": "23", + "target": "21", + "sourceHandle": "vial4", + "targetHandle": null, + "type": "smoothstep", "data": {}, - "id": "e18-20-from_source2", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "18", - "sourceHandle": "source2", "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "20", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e8-24-from_mass_flow_rate", + "source": "8", + "target": "24", + "sourceHandle": "mass_flow_rate", + "targetHandle": null, + "type": "smoothstep", "data": {}, - "id": "e20-19", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "20", - "sourceHandle": null, "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "19", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e24-25-from_source2", + "source": "24", + "target": "25", + "sourceHandle": "source2", + "targetHandle": null, + "type": "smoothstep", "data": {}, - "id": "e9-21", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "9", - "sourceHandle": null, "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "21", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { + "id": "e25-26-from_source1-to_sample_in_soluble", + "source": "25", + "target": "26", + "sourceHandle": "source1", + "targetHandle": "sample_in_soluble", + "type": "smoothstep", "data": {}, - "id": "e12-21", - "markerEnd": { - "color": "#ECDFCC", - "height": 20, - "type": "arrowclosed", - "width": 20 - }, - "source": "12", - "sourceHandle": null, "style": { - "stroke": "#ECDFCC", - "strokeWidth": 2 + "strokeWidth": 2, + "stroke": "#ECDFCC" }, - "target": "21", - "targetHandle": null, - "type": "smoothstep" + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } }, { - "id": "e8-1-from_mass_flow_rate", - "source": "8", - "target": "1", - "sourceHandle": "mass_flow_rate", - "targetHandle": null, + "id": "e25-26-from_source2-to_sample_in_insoluble", + "source": "25", + "target": "26", + "sourceHandle": "source2", + "targetHandle": "sample_in_insoluble", "type": "smoothstep", "data": {}, "style": { @@ -749,10 +605,10 @@ } }, { - "id": "e1-11-from_source1", - "source": "1", - "target": "11", - "sourceHandle": "source1", + "id": "e23-27-from_sample_out", + "source": "23", + "target": "27", + "sourceHandle": "sample_out", "targetHandle": null, "type": "smoothstep", "data": {}, @@ -768,10 +624,10 @@ } }, { - "id": "e1-0-from_source2", - "source": "1", - "target": "0", - "sourceHandle": "source2", + "id": "e26-27-from_sample_out", + "source": "26", + "target": "27", + "sourceHandle": "sample_out", "targetHandle": null, "type": "smoothstep", "data": {}, @@ -787,10 +643,10 @@ } }, { - "id": "e0-16", - "source": "0", - "target": "16", - "sourceHandle": null, + "id": "e26-28-from_vial1", + "source": "26", + "target": "28", + "sourceHandle": "vial1", "targetHandle": null, "type": "smoothstep", "data": {}, @@ -806,10 +662,10 @@ } }, { - "id": "e14-0-from_source1", - "source": "14", - "target": "0", - "sourceHandle": "source1", + "id": "e26-28-from_vial2", + "source": "26", + "target": "28", + "sourceHandle": "vial2", "targetHandle": null, "type": "smoothstep", "data": {}, @@ -825,10 +681,10 @@ } }, { - "id": "e15-21", - "source": "15", - "target": "21", - "sourceHandle": null, + "id": "e26-28-from_vial3", + "source": "26", + "target": "28", + "sourceHandle": "vial3", "targetHandle": null, "type": "smoothstep", "data": {}, @@ -844,10 +700,10 @@ } }, { - "id": "e17-21", - "source": "17", - "target": "21", - "sourceHandle": null, + "id": "e26-28-from_vial4", + "source": "26", + "target": "28", + "sourceHandle": "vial4", "targetHandle": null, "type": "smoothstep", "data": {}, @@ -863,5 +719,24 @@ } } ], - "nodeCounter": 23 + "nodeCounter": 29, + "solverParams": { + "dt": "0.01", + "dt_min": "1e-6", + "dt_max": "1.0", + "Solver": "SSPRK22", + "tolerance_fpi": "1e-6", + "iterations_max": "100", + "log": "true", + "simulation_duration": "50.0", + "extra_params": "{}" + }, + "globalVariables": [ + { + "id": "1753792090540", + "name": "", + "value": "", + "nameError": false + } + ] } \ No newline at end of file From b34bd1721f03ba25fd8f7d0a8890c1b98ff0cdd1 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 12:12:49 -0400 Subject: [PATCH 6/7] update BABY --- saved_graphs/baby.json | 88 ++++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 20 deletions(-) diff --git a/saved_graphs/baby.json b/saved_graphs/baby.json index d1d70d0e..bd3b72df 100644 --- a/saved_graphs/baby.json +++ b/saved_graphs/baby.json @@ -3,7 +3,7 @@ { "data": { "amplitude": "1", - "delay": "1", + "delay": "2", "label": "stepsource 2" }, "dragging": false, @@ -13,10 +13,10 @@ "width": 120 }, "position": { - "x": 339, - "y": 435 + "x": 398.37785598478024, + "y": 489.09982434168865 }, - "selected": false, + "selected": true, "type": "stepsource" }, { @@ -41,7 +41,7 @@ { "data": { "gain": "-1", - "label": "amplifier 4" + "label": "x (-1)" }, "dragging": false, "id": "4", @@ -67,7 +67,7 @@ "width": 120 }, "position": { - "x": 875, + "x": 859.1659050707252, "y": 126 }, "selected": false, @@ -111,8 +111,8 @@ { "data": { "initial_value": "", - "label": "process 8", - "residence_time": "10", + "label": "BABY", + "residence_time": "(k_IV + k_OV)/baby_vol", "source_term": "" }, "dragging": false, @@ -122,8 +122,8 @@ "width": 200 }, "position": { - "x": 1102, - "y": 571 + "x": 1020.1905095320806, + "y": 573.6390158215457 }, "selected": false, "type": "process" @@ -193,8 +193,8 @@ }, "data": { "label": "IV vs OV", - "f1": "0.8", - "f2": "0.2" + "f1": "k_IV/(k_IV + k_OV)", + "f2": "k_OV/(k_IV + k_OV)" }, "measured": { "width": 120, @@ -212,8 +212,8 @@ }, "data": { "label": "soluble vs insoluble", - "f1": "1", - "f2": "0" + "f1": "0.01", + "f2": "0.99" }, "measured": { "width": 120, @@ -233,13 +233,13 @@ "label": "OV bubbler", "conversion_efficiency": "0.95", "vial_efficiency": "0.9", - "replacement_time": "np.arange(5, 50, step=3)" + "replacement_time": "np.arange(5, 50, step=5)" }, "measured": { "width": 230, "height": 160 }, - "selected": true, + "selected": false, "dragging": false }, { @@ -277,6 +277,23 @@ }, "selected": false, "dragging": false + }, + { + "id": "30", + "type": "scope", + "position": { + "x": 916, + "y": 786 + }, + "data": { + "label": "BABY inventory" + }, + "measured": { + "width": 120, + "height": 140 + }, + "selected": false, + "dragging": false } ], "edges": [ @@ -717,9 +734,28 @@ "height": 20, "color": "#ECDFCC" } + }, + { + "id": "e8-30-from_inv", + "source": "8", + "target": "30", + "sourceHandle": "inv", + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } } ], - "nodeCounter": 29, + "nodeCounter": 31, "solverParams": { "dt": "0.01", "dt_min": "1e-6", @@ -729,13 +765,25 @@ "iterations_max": "100", "log": "true", "simulation_duration": "50.0", - "extra_params": "{}" + "extra_params": "{\"tolerance_lte_rel\":1e-4, \"tolerance_lte_abs\":1e-9}" }, "globalVariables": [ { "id": "1753792090540", - "name": "", - "value": "", + "name": "baby_vol", + "value": "1", + "nameError": false + }, + { + "id": "1753797344108", + "name": "k_IV", + "value": "5", + "nameError": false + }, + { + "id": "1753797380350", + "name": "k_OV", + "value": "1", "nameError": false } ] From de3c78cf16b689c1602e3c9e29679cff891fd58e Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Wed, 30 Jul 2025 21:46:12 -0400 Subject: [PATCH 7/7] adapted convert to python --- src/convert_to_python.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/convert_to_python.py b/src/convert_to_python.py index 9c8561d8..29b45b75 100644 --- a/src/convert_to_python.py +++ b/src/convert_to_python.py @@ -6,6 +6,7 @@ from .custom_pathsim_blocks import ( Process, Splitter, + Bubbler, ) from .pathsim_utils import ( map_str_to_object, @@ -146,6 +147,21 @@ def make_edge_data(data: dict) -> list[dict]: raise ValueError( f"Invalid source handle '{edge['sourceHandle']}' for {edge}." ) + elif isinstance(block, Bubbler): + if edge["sourceHandle"] == "vial1": + output_index = 0 + elif edge["sourceHandle"] == "vial2": + output_index = 1 + elif edge["sourceHandle"] == "vial3": + output_index = 2 + elif edge["sourceHandle"] == "vial4": + output_index = 3 + elif edge["sourceHandle"] == "sample_out": + output_index = 4 + else: + raise ValueError( + f"Invalid source handle '{edge['sourceHandle']}' for {edge}." + ) else: output_index = 0