System#
Connection#
Connection definitions for co-simulation components.
This module defines data classes for representing connections between components in a co-simulation environment.
- Classes:
Connection: Standard data connection between two components.
EventConnection: Event-based connection for event handling.
- class syssimx.system.connection.Connection[source]
Bases:
objectDefines a connection between two components in a co-simulation environment.
- src_comp
Name of the source component.
- Type:
- src_port
Name of the output port on the source component.
- Type:
- dst_comp
Name of the destination component.
- Type:
- dst_port
Name of the input port on the destination component.
- Type:
- src_comp: str
- src_port: str
- dst_comp: str
- dst_port: str
- class syssimx.system.connection.EventConnection[source]
Bases:
objectDefines an event connection between a source (event producer) and target (event listener).
When added to a System via add_event_connection(): - The system automatically subscribes the target to the event - No manual Event object creation or subscribe_event() call required
- src_comp
Name of the component that produces the event (has event indicator)
- Type:
- src_port
Name of the event (must match event indicator name on source)
- Type:
- dst_comp
Name of the component that handles the event
- Type:
- dst_port
Name of the event input port on the target (for visualization)
- Type:
- src_comp: str
- src_port: str
- dst_comp: str
- dst_port: str
- property event_name: str
Name of the event (same as src_port).
- property target_comp: str
Name of the target component.
System Class#
Co-simulation system orchestration and management.
This module provides the System class, which is the central orchestrator
for heterogeneous co-simulation. It manages components, connections, execution
order computation, and delegates simulation stepping to pluggable algorithms.
- Key Responsibilities:
Component Management: Register and organize
CoSimComponentinstancesConnection Validation: Type and unit compatibility checking between ports
Graph Analysis: Build dependency graphs and detect algebraic loops
Execution Ordering: Compute parallelizable generations using topological sort
Algorithm Delegation: Step simulation using Jacobi, Gauss-Seidel, or Hybrid algorithms
Event Routing: Dispatch events between components via event connections
History Aggregation: Collect time-series data from all components
- Typical Usage:
Building and running a co-simulation:
from syssimx import System, Connection from syssimx.components import FMUComponent # Create system system = System(name="ControlledPendulum") # Add components pendulum = FMUComponent("Pendulum", fmu_path="Pendulum.fmu") controller = FMUComponent("PID", fmu_path="Controller.fmu") system.add_component(pendulum) system.add_component(controller) # Define connections system.add_connection(Connection( src_comp="Pendulum", src_port="angle", dst_comp="PID", dst_port="measurement" )) system.add_connection(Connection( src_comp="PID", src_port="control", dst_comp="Pendulum", dst_port="torque" )) # Initialize and run system.initialize(t0=0.0) system.run(t0=0.0, tf=10.0, dt=0.001) # Retrieve results history = system.get_history()
See also
Connection: Signal connections between component ports
EventConnection: Event routing between components
syssimx.system.algorithms: Stepping algorithm implementations
syssimx.system.graph: Graph analysis utilities
- class syssimx.system.system.System[source]#
Bases:
objectCentral orchestrator for heterogeneous co-simulation systems.
The
Systemclass manages a collection of interconnectedCoSimComponentinstances, validates their connections, computes execution order, and coordinates simulation stepping through a pluggable algorithm.- components#
Registered components by name.
- Type:
- graph#
Complete connection graph (including delayed).
- Type:
nx.MultiDiGraph
- groups#
Components organized by group.
- Type:
- execution_order#
Topologically sorted generations. Each generation contains components that can execute in parallel.
- history#
Aggregated time-series data from all components.
- Type:
Example
Creating a simple two-component system:
>>> system = System("Feedback") >>> system.add_component(plant) >>> system.add_component(controller) >>> system.add_connection(Connection( >>> src_comp="plant", src_port="y", >>> dst_comp="controller", dst_port="measurement" >>> )) >>> system.initialize(t0=0.0) >>> system.run(t0=0.0, tf=10.0, dt=0.01)
See also
Connection: Signal connection specificationAlgorithm: Base class for stepping algorithms- __init__(name)[source]#
Initialize a new co-simulation system.
- Parameters:
name (str) – Identifier for this system. Used in logging and history tracking.
Example
>>> system = System("ControlledPendulum") >>> system.name 'ControlledPendulum'
- set_algorithm(algorithm)[source]#
Set the co-simulation stepping algorithm.
The algorithm determines how components are stepped during simulation. Available algorithms include:
GaussSeidelAlgorithm: Sequential stepping (default)JacobiAlgorithm: Parallel stepping with delayed inputsHybridAlgorithm: Event-driven with bisection localization
- Parameters:
algorithm (Algorithm) – An instance implementing the
Algorithminterface.- Raises:
TypeError – If
algorithmdoesn’t implementAlgorithm.- Return type:
None
Example
>>> from syssimx.system.algorithms import JacobiAlgorithm >>> system.set_algorithm(JacobiAlgorithm())
Note
If components with event indicators are detected during
initialize(), the algorithm is automatically upgraded toHybridAlgorithm.
- add_component(component)[source]#
Register a component with the system.
Components must be added before connections can reference them. Each component’s history is automatically registered with the system’s history tracker.
- Parameters:
component (CoSimComponent) – A
CoSimComponentinstance to add. Itsnameattribute must be unique within this system.- Raises:
RuntimeError – If called after
initialize().ValueError – If a component with the same name already exists.
Example
>>> pendulum = FMUComponent("Pendulum", fmu_path="model.fmu") >>> system.add_component(pendulum) >>> "Pendulum" in system.components True
Note
If the component has a
groupattribute set, it will be added tosystem.groups[group]for visual organization.
- add_connection(connection)[source]#
Add a signal connection between two components.
Validates port existence, type compatibility, and unit compatibility before registering the connection.
- Parameters:
connection (Connection) – A
Connectionspecifying source and destination component/port pairs.- Raises:
ValueError – If source or destination component not in system, or if connection is a duplicate.
KeyError – If specified ports don’t exist on the components.
TypeError – If port types or units are incompatible.
- Return type:
None
Example
>>> system.add_connection(Connection( ... src_comp="Sensor", src_port="value", ... dst_comp="Controller", dst_port="measurement" ... ))
See also
Connection: Connection specification classadd_event_connection(): For event-based connections
- _validate_event_connection(connection)[source]#
Validate an event connection before registration.
Performs the following checks:
Source component exists in the system
Target component exists in the system
Event indicator is registered on the source component
No duplicate connections exist
- Parameters:
connection (EventConnection) – The
EventConnectionto validate.- Raises:
ValueError – If source or target component not in system, or if connection is a duplicate.
KeyError – If the event indicator is not registered on the source component.
- Return type:
None
Note
Event subscription on the target is handled automatically by
add_event_connection(), not during validation.
- add_event_connection(connection)[source]#
Add an event connection with automatic target subscription.
Registers an event connection and automatically subscribes the target component to receive the event. This eliminates the need for manual
Eventobject creation andsubscribe_event()calls.- Parameters:
connection (EventConnection) –
EventConnectionspecifying the source component’s event indicator and the target component to notify.- Raises:
ValueError – If source/target not in system or duplicate connection.
KeyError – If event indicator not registered on source component.
- Return type:
None
Example
>>> # Ball emits 'bounce' event, floor handles it >>> system.add_event_connection(EventConnection( ... src_comp="Ball", event_name="bounce", ... dst_comp="Floor" ... ))
Note
The target component must implement
_handle_events_internal()to respond to the event when it fires.See also
EventConnection: Event connection specificationdispatch_event(): Manual event dispatching
- get_event_targets(source_comp, event_name)[source]#
Get component names that receive a specific event.
- dispatch_event(event, t, notify=True)[source]#
Dispatch an event to all subscribed components.
Resolves event listeners based on registered event connections and optionally notifies them by calling their
handle_event()method.- Parameters:
- Returns:
List of component names that are subscribed to this event.
- Raises:
ValueError – If the event source is not in the system.
- Return type:
Example
>>> event = Event(name="threshold", source="Sensor") >>> targets = system.dispatch_event(event, t=1.5) >>> print(targets) ['Controller', 'Logger']
- build_graphs()[source]#
Build dependency graphs from registered connections.
Constructs two graph representations:
self.graph: CompleteMultiDiGraphwith all connections, including delayed connections (for visualization)self._dag:DiGraphwith only zero-delay, direct-feedthrough connections (for execution ordering)
Also detects algebraic loops as strongly connected components (SCCs) with more than one node on the direct-feedthrough graph.
Note
Called automatically during
initialize(). The detected algebraic loops are stored inself.algebraic_loops.See also
syssimx.system.graph: Graph construction utilities- Return type:
None
- compute_execution_order()[source]#
Compute parallelizable execution order from the dependency graph.
Performs a topological sort on the zero-delay dependency DAG to produce generations of components. Components within the same generation have no dependencies on each other and can execute in parallel.
The result is stored in
self.execution_orderas a list of lists, where each inner list contains component names in one generation.Example
After calling:
system.execution_order = [ ['Source', 'Reference'], # Generation 0 ['Controller'], # Generation 1 ['Plant'], # Generation 2 ['Sensor'] # Generation 3 ]
See also
syssimx.system.graph: Execution order computation- Return type:
None
- classify_components()[source]#
Classify components by their hybrid simulation capabilities.
Categorizes all components based on whether they have event indicators, event subscriptions, or neither. This classification is used to determine if hybrid simulation is needed.
- Returns:
"event_sources": Components with event indicators (require rollback support for bisection)"event_listeners": Components subscribed to events (will receive event notifications)"continuous_only": Components without any hybrid capabilities (pure continuous dynamics)
- Return type:
Dictionary with the following keys
Note
A component can be both an event source and an event listener. The categorization is stored in instance attributes
self.event_sources,self.event_listeners, andself.continuous_only.
- initialize(t0)[source]#
Initialize the system and all components at start time.
Performs the complete initialization sequence:
Classifies components and auto-selects
HybridAlgorithmif event sources are detectedCreates port state objects for all components so that feedthrough detection and input propagation can work
Detects direct feedthrough for pure-Python components via perturbation (FMU components already have this from their model description)
Builds dependency graphs and computes execution order; algebraic loops are identified from zero-delay edges
Iterates over generations in execution order:
Propagates initial input values from upstream outputs into the generation’s input port states
Initializes components (FMUs apply stored port values via
_apply_input_startsduring init mode)Solves algebraic loops within the generation
Performs a zero-step (dt=0) to establish consistent initial outputs for downstream generations
This ordering ensures that each generation receives consistent initial values from already-initialized upstream components before entering FMU initialization mode.
- Parameters:
t0 (float) – Initial simulation time in seconds.
- Return type:
None
Example
>>> system.add_component(plant) >>> system.add_component(controller) >>> system.add_connection(connection) >>> system.initialize(t0=0.0) >>> system.is_initialized True
Note
Must be called after all components and connections are added, but before
run(). Callinginitialize()locks the system against further component additions.
- _set_inputs_for_generation(gen, t)[source]#
Set input values for all components in a generation.
For each component in the generation, retrieves values from connected source ports and sets them as inputs. This propagates signal values through the connection graph.
If a component is not yet initialized (e.g. during the generation-based initialization sequence), values are written directly to the
PortStateobjects so that they are available when the component’sinitialize()is called later. For already-initialized components the regularset_inputs()path is used, which also pushes values into the underlying solver (e.g. an FMU instance).- Parameters:
- Return type:
None
Note
Only non-None source values are propagated. Components with no incoming connections or all-None sources receive no updates.
- run(t0, tf, dt)[source]#
Run the simulation from start time to end time.
Advances the simulation by repeatedly calling the algorithm’s
step()method with the specified time step size.- Parameters:
Example
>>> system.initialize(t0=0.0) >>> system.run(t0=0.0, tf=10.0, dt=0.001) >>> # Simulation complete, retrieve history >>> history = system.get_history()
Note
The algorithm may take sub-steps during event localization (Hybrid algorithm) or iteration (IJCSA for algebraic loops). The
dtparameter controls the macro step size.
- get_history()[source]#
Retrieve time-series history from all components.
Collects the recorded output history from every component in the system, plus any event history records.
- Returns:
Keys are component names, values are tuples of
(time_array, values_dict)fromget_history_arrays()Special key
"Events"contains event occurrence records
- Return type:
Dictionary with the following structure
Example
>>> history = system.get_history() >>> t, values = history["Pendulum"] >>> plt.plot(t, values["angle"]) >>> # Access events >>> events = history["Events"]
See also
CoSimComponent.get_history_arrays(): Component history format
- reset()[source]#
Reset the system and all components to uninitialized state.
Clears all component states, histories, and internal flags, allowing the system to be re-initialized from scratch.
Example
>>> system.initialize(t0=0.0) >>> system.run(t0=0.0, tf=10.0, dt=0.01) >>> system.reset() >>> system.is_initialized False
- Return type:
None