1+ """
2+ Utilities for converting graph-based representations to PathSim simulations.
3+
4+ This module provides functionality to convert visual graph representations of simulation
5+ models into executable PathSim simulations. It handles the creation of blocks, connections,
6+ events, and solver configurations from JSON-like graph data structures.
7+
8+ The main workflow involves:
9+ 1. Processing global variables and solver parameters
10+ 2. Creating blocks from node data
11+ 3. Establishing connections between blocks based on edges
12+ 4. Setting up events and custom Python code execution
13+ 5. Building the complete PathSim simulation model
14+
15+ Key mappings are provided for:
16+ - Block types (map_str_to_object): Maps string identifiers to PathSim block classes
17+ - Event types (map_str_to_event): Maps string identifiers to PathSim event classes
18+ - Solver types (NAME_TO_SOLVER): Maps string identifiers to PathSim solver classes
19+ """
120import math
221import numpy as np
322from pathsim import Simulation , Connection
146165
147166
148167def find_node_by_id (node_id : str , nodes : list [dict ]) -> dict :
168+ """
169+ Find a node by its ID in a list of nodes.
170+
171+ Args:
172+ node_id: The ID of the node to find.
173+ nodes: A list of node dictionaries to search through.
174+
175+ Returns:
176+ The node dictionary with the matching ID, or None if not found.
177+ """
149178 return next ((node for node in nodes if node ["id" ] == node_id ), None )
150179
151180
152181def find_block_by_id (block_id : str , blocks : list [Block ]) -> Block :
182+ """
183+ Find a block by its ID in a list of blocks.
184+
185+ Args:
186+ block_id: The ID of the block to find.
187+ blocks: A list of Block objects to search through.
188+
189+ Returns:
190+ The Block object with the matching ID, or None if not found.
191+ """
153192 return next ((block for block in blocks if block .id == block_id ), None )
154193
155194
156195def make_global_variables (global_vars ):
196+ """
197+ Validate and execute global variable definitions to make them usable in the simulation.
198+
199+ Args:
200+ global_vars: A list of dictionaries containing variable definitions, where each
201+ dictionary has 'name' and 'value' keys.
202+
203+ Returns:
204+ dict: A namespace dictionary containing the global variables.
205+
206+ Raises:
207+ ValueError: If a variable name is invalid, is a Python keyword, or if there's
208+ an error evaluating the variable value.
209+ """
157210 # Validate and exec global variables so that they are usable later in this script.
158211 # Return a namespace dictionary containing the global variables
159212 global_namespace = globals ().copy ()
@@ -193,6 +246,23 @@ def make_global_variables(global_vars):
193246
194247
195248def make_solver_params (solver_prms , eval_namespace = None ):
249+ """
250+ Process and validate solver parameters from the graph data.
251+
252+ Args:
253+ solver_prms: Dictionary containing solver parameters including Solver type,
254+ simulation_duration, and other solver-specific parameters.
255+ eval_namespace: Optional namespace for evaluating parameter expressions.
256+
257+ Returns:
258+ tuple: A tuple containing:
259+ - solver_prms (dict): Processed solver parameters
260+ - extra_params (dict): Additional parameters for the solver
261+ - duration (float): Simulation duration
262+
263+ Raises:
264+ ValueError: If invalid parameter values are provided or if solver type is unknown.
265+ """
196266 extra_params = solver_prms .pop ("extra_params" , "" )
197267 if extra_params == "" :
198268 extra_params = {}
@@ -295,6 +365,21 @@ def auto_event_construction(event_data: dict, eval_namespace: dict = None) -> Ev
295365def get_parameters_for_event_class (
296366 event_class : type , event_data : dict , eval_namespace : dict = None
297367):
368+ """
369+ Extract and process parameters for an event class from event data.
370+
371+ Args:
372+ event_class: The event class type to create parameters for.
373+ event_data: Dictionary containing the event configuration data.
374+ eval_namespace: Optional namespace for evaluating expressions and executing functions.
375+
376+ Returns:
377+ dict: A dictionary of parameters ready to be passed to the event class constructor.
378+
379+ Raises:
380+ ValueError: If required parameters are missing, if function code execution fails,
381+ or if parameter evaluation fails.
382+ """
298383 parameters_for_class = inspect .signature (event_class .__init__ ).parameters
299384
300385 # Create a local namespace for executing the event functions
@@ -345,6 +430,20 @@ def get_parameters_for_event_class(
345430
346431
347432def get_parameters_for_block_class (block_class , node , eval_namespace ):
433+ """
434+ Extract and process parameters for a block class from node data.
435+
436+ Args:
437+ block_class: The block class type to create parameters for.
438+ node: Dictionary containing the node configuration data.
439+ eval_namespace: Namespace for evaluating parameter expressions.
440+
441+ Returns:
442+ dict: A dictionary of parameters ready to be passed to the block class constructor.
443+
444+ Raises:
445+ ValueError: If required parameters are missing or if parameter evaluation fails.
446+ """
348447 parameters_for_class = inspect .signature (block_class .__init__ ).parameters
349448 parameters = {}
350449 for k , value in parameters_for_class .items ():
@@ -374,6 +473,18 @@ def get_parameters_for_block_class(block_class, node, eval_namespace):
374473def make_blocks (
375474 nodes : list [dict ], eval_namespace : dict = None
376475) -> tuple [list [Block ], list [Event ]]:
476+ """
477+ Create Block objects from node data and collect any associated events.
478+
479+ Args:
480+ nodes: List of node dictionaries containing block configuration data.
481+ eval_namespace: Optional namespace for evaluating expressions.
482+
483+ Returns:
484+ tuple: A tuple containing:
485+ - blocks (list[Block]): List of created Block objects
486+ - events (list[Event]): List of events created by blocks (e.g., reset events)
487+ """
377488 blocks , events = [], []
378489
379490 for node in nodes :
@@ -393,11 +504,16 @@ def get_input_index(block: Block, edge: dict, block_to_input_index: dict) -> int
393504 Get the input index for a block based on the edge data.
394505
395506 Args:
396- block: The block object.
507+ block: The block object to get the input index for .
397508 edge: The edge dictionary containing source and target information.
509+ block_to_input_index: Dictionary mapping blocks to their current input index count.
398510
399511 Returns:
400- The input index for the block.
512+ int: The input index for the block.
513+
514+ Raises:
515+ AssertionError: If the target block has multiple input ports but the connection
516+ method hasn't been implemented for that block type.
401517 """
402518
403519 if edge ["targetHandle" ] is not None :
@@ -422,11 +538,16 @@ def get_output_index(block: Block, edge: dict) -> int:
422538 Get the output index for a block based on the edge data.
423539
424540 Args:
425- block: The block object.
541+ block: The block object to get the output index for .
426542 edge: The edge dictionary containing source and target information.
427543
428544 Returns:
429- The output index for the block.
545+ int: The output index for the block.
546+
547+ Raises:
548+ ValueError: If an invalid source handle is provided for a Splitter block.
549+ AssertionError: If the source block has multiple output ports but the connection
550+ method hasn't been implemented for that block type.
430551 """
431552 if edge ["sourceHandle" ] is not None :
432553 if block ._port_map_out :
@@ -456,6 +577,23 @@ def get_output_index(block: Block, edge: dict) -> int:
456577
457578
458579def make_connections (nodes , edges , blocks ) -> list [Connection ]:
580+ """
581+ Create PathSim Connection objects from nodes, edges, and blocks data.
582+
583+ This function processes the graph structure to create proper connections between blocks,
584+ handling special cases for scopes and different block types with multiple inputs/outputs.
585+
586+ Args:
587+ nodes: List of node dictionaries containing block information.
588+ edges: List of edge dictionaries defining connections between nodes.
589+ blocks: List of Block objects that have been created from the nodes.
590+
591+ Returns:
592+ list[Connection]: List of PathSim Connection objects linking block inputs and outputs.
593+
594+ Note:
595+ This function also handles labeling for Scope and Spectrum blocks automatically.
596+ """
459597 # Create connections based on the sorted edges to match beta order
460598 connections_pathsim = []
461599
@@ -526,6 +664,21 @@ def make_events(events_data: list[dict], eval_namespace: dict = None) -> list[Ev
526664
527665
528666def make_default_scope (nodes , blocks ) -> tuple [Scope , list [Connection ]]:
667+ """
668+ Create a default Scope block that connects to all other blocks in the simulation.
669+
670+ This function creates a default scope when no explicit scope exists in the graph,
671+ ensuring that all block outputs are captured for visualization.
672+
673+ Args:
674+ nodes: List of node dictionaries containing block information (used for labels).
675+ blocks: List of Block objects to connect to the default scope.
676+
677+ Returns:
678+ tuple: A tuple containing:
679+ - scope_default (Scope): The created default Scope block
680+ - connections_pathsim (list[Connection]): List of connections from blocks to the scope
681+ """
529682 scope_default = Scope (
530683 labels = [node ["data" ]["label" ] for node in nodes ],
531684 )
@@ -577,6 +730,31 @@ def make_var_name(node: dict) -> str:
577730
578731
579732def make_pathsim_model (graph_data : dict ) -> tuple [Simulation , float ]:
733+ """
734+ Create a complete PathSim simulation model from graph data.
735+
736+ This is the main function that orchestrates the creation of a PathSim simulation
737+ from a graph representation. It processes nodes, edges, solver parameters, global
738+ variables, events, and custom Python code to build a complete simulation model.
739+
740+ Args:
741+ graph_data: Dictionary containing the complete graph representation with keys:
742+ - nodes: List of node dictionaries representing blocks
743+ - edges: List of edge dictionaries representing connections
744+ - solverParams: Dictionary of solver configuration parameters
745+ - globalVariables: Dictionary of global variable definitions
746+ - events: List of event dictionaries (optional)
747+ - pythonCode: Custom Python code to execute (optional)
748+
749+ Returns:
750+ tuple: A tuple containing:
751+ - simulation (Simulation): The configured PathSim Simulation object
752+ - duration (float): The simulation duration
753+
754+ Raises:
755+ ValueError: If there are errors in processing any component of the graph data.
756+ Exception: If custom Python code execution fails.
757+ """
580758 nodes = graph_data .get ("nodes" , [])
581759 edges = graph_data .get ("edges" , [])
582760 solver_prms = graph_data .get ("solverParams" , {})
0 commit comments