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
25 changes: 20 additions & 5 deletions saved_graphs/baby.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@
},
{
"data": {
"label": "Neutron source"
"label": "Neutron source",
"labels": "",
"sampling_rate": "",
"t_wait": ""
},
"dragging": false,
"id": "5",
Expand All @@ -75,7 +78,10 @@
},
{
"data": {
"label": "Neutron rate"
"label": "Neutron rate",
"labels": "",
"sampling_rate": "",
"t_wait": ""
},
"dragging": false,
"id": "6",
Expand Down Expand Up @@ -130,7 +136,10 @@
},
{
"data": {
"label": "IV vial activity"
"label": "IV vial activity",
"labels": "",
"sampling_rate": "",
"t_wait": ""
},
"dragging": false,
"id": "21",
Expand Down Expand Up @@ -269,7 +278,10 @@
"y": 1097.1334152526842
},
"data": {
"label": "OV vial activity"
"label": "OV vial activity",
"labels": "",
"sampling_rate": "",
"t_wait": ""
},
"measured": {
"width": 120,
Expand All @@ -286,7 +298,10 @@
"y": 786
},
"data": {
"label": "BABY inventory"
"label": "BABY inventory",
"labels": "",
"sampling_rate": "",
"t_wait": ""
},
"measured": {
"width": 120,
Expand Down
205 changes: 105 additions & 100 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Background,
useNodesState,
useEdgesState,
addEdge,

Check failure on line 9 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

'addEdge' is defined but never used. Allowed unused vars must match /^[A-Z_]/u

Check failure on line 9 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

'addEdge' is defined but never used. Allowed unused vars must match /^[A-Z_]/u
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import './App.css';
Expand Down Expand Up @@ -93,6 +93,24 @@

// Global variables state
const [globalVariables, setGlobalVariables] = useState([]);
const [defaultValues, setDefaultValues] = useState({});

// Function to fetch default values for a node type
const fetchDefaultValues = async (nodeType) => {
try {
const response = await fetch(`http://localhost:8000/default-values/${nodeType}`);
if (response.ok) {
const defaults = await response.json();
return defaults;
} else {
console.error('Failed to fetch default values');
return {};
}
} catch (error) {
console.error('Error fetching default values:', error);
return {};
}
};

// Function to save a graph to computer with "Save As" dialog
const saveGraph = async () => {
Expand Down Expand Up @@ -584,7 +602,7 @@
);
};
// Function to add a new node to the graph
const addNode = () => {
const addNode = async () => {
// Get available node types from nodeTypes object
const availableTypes = Object.keys(nodeTypes);

Expand All @@ -611,78 +629,24 @@

const selectedType = availableTypes[choiceIndex];
const newNodeId = nodeCounter.toString();

// Create appropriate data based on node type

// Fetch default values for this node type
const defaults = await fetchDefaultValues(selectedType);

// Store default values for this node type
setDefaultValues(prev => ({
...prev,
[selectedType]: defaults
}));

// Create node data with label and initialize all expected fields as empty strings
let nodeData = { label: `${selectedType} ${newNodeId}` };

// Add type-specific default parameters
switch (selectedType) {
case 'process':
nodeData = { ...nodeData, residence_time: '', source_term: '', initial_value: '' };
break;
case 'process_horizontal':
nodeData = { ...nodeData, residence_time: '', source_term: '', initial_value: '' };
break;
case 'constant':
nodeData = { ...nodeData, value: '' };
break;
case 'stepsource':
nodeData = { ...nodeData, amplitude: '1', delay: '1' };
break;
case 'pulsesource':
nodeData = { ...nodeData, amplitude: '1', T: '1', t_rise: '0.0', t_fall: '0.0', tau: '0.0', duty: '0.5' };
break;
case 'amplifier':
nodeData = { ...nodeData, gain: '' };
break;
case 'amplifier_reverse':
nodeData = { ...nodeData, gain: '' };
break;
case 'multiplier':
break;
case 'integrator':
nodeData = { ...nodeData, initial_value: '', reset_times: '' };
break;
case 'adder':
break;
case 'scope':
nodeData = { ...nodeData };
break;
case 'function':
nodeData = { ...nodeData, expression: '' };
break;
case 'delay':
nodeData = { ...nodeData, tau: '' };
break;
case 'rng':
nodeData = { ...nodeData, sampling_rate: '' };
break;
case 'pid':
nodeData = { ...nodeData, Kp: '', Ki: '', Kd: '', f_max: '' };
break;
case 'splitter2':
nodeData = { ...nodeData, f1: '0.5', f2: '0.5' };
break;
case 'splitter3':
nodeData = { ...nodeData, f1: '1/3', f2: '1/3', f3: '1/3' };
break;
case 'wall':
nodeData = { ...nodeData, thickness: '', surface_area: '1', temperature: '', D_0: '1', E_D: '0', n_vertices: '100' };
break;
case 'bubbler':
nodeData = { ...nodeData, conversion_efficiency: '0.95', vial_efficiency: '0.9', replacement_times: '' };
break;
case 'white_noise':
nodeData = { ...nodeData, spectral_density: '1', sampling_rate: '' };
break;
case 'pink_noise':
nodeData = { ...nodeData, spectral_density: '1', num_octaves: '16', sampling_rate: '' };
break;
default:
// For any other types, just use basic data
break;
}


// Initialize all expected parameters as empty strings
Object.keys(defaults).forEach(key => {
nodeData[key] = '';
});

const newNode = {
id: newNodeId,
type: selectedType,
Expand Down Expand Up @@ -839,7 +803,7 @@
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [selectedEdge, selectedNode, copiedNode, duplicateNode, setCopyFeedback]);

Check warning on line 806 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

React Hook useEffect has missing dependencies: 'deleteSelectedEdge' and 'deleteSelectedNode'. Either include them or remove the dependency array

Check warning on line 806 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

React Hook useEffect has missing dependencies: 'deleteSelectedEdge' and 'deleteSelectedNode'. Either include them or remove the dependency array

return (
<div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
Expand Down Expand Up @@ -1132,37 +1096,78 @@
}}
>
<h3>{selectedNode.data.label}</h3>
{Object.entries(selectedNode.data)
.map(([key, value]) => (
<div key={key} style={{ marginBottom: '10px' }}>
<label>{key}:</label>
<input
type="text"
value={value}
onChange={(e) => {
const newValue = e.target.value;
const updatedNode = {
...selectedNode,
data: { ...selectedNode.data, [key]: newValue },
};

setNodes((nds) =>
nds.map((node) =>
node.id === selectedNode.id ? updatedNode : node
)
);
setSelectedNode(updatedNode);

}}

style={{ width: '100%', marginTop: 4 }}
/>
</div>
))}
{(() => {
// Get default values for this node type
const nodeDefaults = defaultValues[selectedNode.type] || {};

// Create a list of all possible parameters (both current data and defaults)
const allParams = new Set([
...Object.keys(selectedNode.data),
...Object.keys(nodeDefaults)
]);

return Array.from(allParams)
.map(key => {
const currentValue = selectedNode.data[key] || '';
const defaultValue = nodeDefaults[key];
const placeholder = defaultValue !== undefined && defaultValue !== null ?
String(defaultValue) : '';

return (
<div key={key} style={{ marginBottom: '10px' }}>
<label style={{
color: '#ffffff',
display: 'block',
marginBottom: '4px',
fontSize: '14px'
}}>
{key}:
</label>
<input
type="text"
value={currentValue}
placeholder={placeholder}
onChange={(e) => {
const newValue = e.target.value;
const updatedNode = {
...selectedNode,
data: { ...selectedNode.data, [key]: newValue },
};

setNodes((nds) =>
nds.map((node) =>
node.id === selectedNode.id ? updatedNode : node
)
);
setSelectedNode(updatedNode);
}}
style={{
width: '100%',
marginTop: 4,
padding: '8px',
borderRadius: '4px',
border: '1px solid #555',
backgroundColor: '#2a2a3e',
color: '#ffffff',
fontSize: '14px'
}}
/>
</div>
);
});
})()}

<br />
<button
style={{ marginTop: 10 }}
style={{
marginTop: 10,
padding: '8px 16px',
backgroundColor: '#666',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
onClick={() => setSelectedNode(null)}
>
Close
Expand Down
27 changes: 26 additions & 1 deletion src/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
from plotly.subplots import make_subplots
import plotly
import json as plotly_json
import inspect

from .convert_to_python import convert_graph_to_python
from .pathsim_utils import make_pathsim_model
from .pathsim_utils import make_pathsim_model, map_str_to_object
from pathsim.blocks import Scope

# Configure Flask app for Cloud Run
Expand Down Expand Up @@ -62,6 +63,30 @@ def health_check():
), 200


# returns default values for parameters of a node
@app.route("/default-values/<string:node_type>", methods=["GET"])
def get_default_values(node_type):
try:
if node_type not in map_str_to_object:
return jsonify({"error": f"Unknown node type: {node_type}"}), 400

block_class = map_str_to_object[node_type]
parameters_for_class = inspect.signature(block_class.__init__).parameters
default_values = {}
for param in parameters_for_class:
if param != "self": # Skip 'self' parameter
default_value = parameters_for_class[param].default
if default_value is inspect._empty:
default_values[param] = None # Handle empty defaults
else:
default_values[param] = default_value
return jsonify(default_values)
except Exception as e:
return jsonify(
{"error": f"Could not get default values for {node_type}: {str(e)}"}
), 400


# Function to save graphs
@app.route("/save", methods=["POST"])
def save_graph():
Expand Down
38 changes: 38 additions & 0 deletions src/custom_pathsim_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,44 @@ def __init__(self, f1, f2, f3):
super().__init__(n=3, fractions=[f1, f2, f3])


class Integrator(pathsim.blocks.Integrator):
"""Integrator block with a reset method."""

def __init__(self, initial_value=0.0, reset_times=None):
"""
Args:
initial_value: Initial value of the integrator.
reset_times: List of times at which the integrator is reset. If None, no reset events are created.
"""
super().__init__(initial_value=initial_value)
self.reset_times = reset_times

def create_reset_events(self):
"""Create reset events for the integrator based on the reset times.

Raises:
ValueError: If reset_times is not valid.

Returns:
list of reset events.
"""
if self.reset_times is None:
return []
if isinstance(self.reset_times, (int, float)):
reset_times = [self.reset_times]
elif isinstance(self.reset_times, list) and all(
isinstance(t, (int, float)) for t in self.reset_times
):
reset_times = self.reset_times
else:
raise ValueError("reset_times must be a single value or a list of times")

return [
pathsim.blocks.Schedule(t_start=t, t_end=t, func_act=self.reset)
for t in reset_times
]


# BUBBLER SYSTEM


Expand Down
Loading
Loading