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, progress=None)[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:
t0 (float) – Start time in seconds (should match
initialize(t0)).tf (float) – End time in seconds.
dt (float) – Fixed time step size in seconds. The final step may be smaller to land exactly on
tf.progress (Callable[[float, float], None] | None) – Optional callback invoked after every completed macro step with
(t, tf), wheretis the simulation time reached. Use it to drive progress bars or live plots, e.g.progress=lambda t, tf: bar.update(...).
- Returns:
A
SimulationResultwith the recorded component histories, the event log, and run metadata (time span, macro step, wall time, algorithm).- Return type:
Example
>>> system.initialize(t0=0.0) >>> result = system.run(t0=0.0, tf=10.0, dt=0.001) >>> df = result.to_dataframe(component="Pendulum")
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
- describe()[source]#
Return a human-readable report of the system’s structure.
Summarizes the components, connections, execution order (generations), detected algebraic loops, direct-feedthrough relations, and the configured master algorithm. The structural metadata is computed during
initialize(); callingdescribe()before initialization reports on whatever has been assembled so far.- Returns:
A multi-line report string, ready for
print().- Return type:
Example
>>> system.initialize(t0=0.0) >>> print(system.describe())
- 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
Simulation Results#
Simulation result container for SysSimX runs.
SimulationResult wraps the per-component output histories recorded during
a run together with run metadata (time span, macro step, wall time, algorithm)
and provides convenient post-processing accessors: pandas DataFrame
conversion (tidy long format or per-component wide format), CSV export, and
the recorded event log.
A result is returned by syssimx.system.system.System.run() and can also
be built from an already-run system via SimulationResult.from_system().
- class syssimx.system.results.SimulationResult[source]#
Bases:
objectContainer for the outputs and metadata of one simulation run.
- system_name#
Name of the simulated system.
- t0#
Start time of the run in seconds.
- tf#
End time of the run in seconds.
- dt#
Macro communication step size in seconds.
- wall_time#
Wall-clock duration of the run in seconds.
- algorithm#
Name of the master algorithm used.
- histories#
Mapping
component name -> (time_array, {port: values})exactly as returned byCoSimComponent.get_history_arrays().
- events#
Recorded event occurrences, as stored by the system history.
- classmethod from_system(system, t0, tf, dt, wall_time=nan)[source]#
Build a result from an already-run system.
- to_dataframe(component=None)[source]#
Convert recorded histories to a pandas DataFrame.
- Parameters:
component (str | None) – When given, return a wide DataFrame for that single component: one
timecolumn plus one column per output port. Without it, return a tidy long-format DataFrame with columnscomponent,port,time,valuecovering every component (components may have different time grids, which the long format represents without padding).- Returns:
The requested DataFrame. Values that are pint quantities are reduced to their magnitudes.
- Return type:
DataFrame
Declarative System Descriptions#
Declarative system descriptions: build a System from YAML/JSON.
A system description is a mapping with the following shape (YAML shown; JSON with the same structure is equally supported):
system:
name: Quickstart
components:
- name: Source
class: "my_package.components:LinearSource" # "module:ClassName"
args: {a: 1.0, b: 0.0} # constructor kwargs
parameters: {gain: 2.0} # set_parameters(...) after construction
connections:
- src: Source.y # "Component.port" (split at the first dot) ...
dst: Integrator.u
- src: {component: A, port: out} # ... or explicit mapping form
dst: {component: B, port: in}
event_connections: # optional
- src: Plant.wall_hit
dst: Controller.reset
algorithm: # optional (default gauss_seidel)
type: gauss_seidel # jacobi | gauss_seidel | hybrid | ijcsa
run: # optional defaults for run_from_config/CLI
t0: 0.0
tf: 5.0
dt: 0.1
Component classes are resolved by import path, so any installed
CoSimComponent subclass — including user-defined ones — can be used
without registration. Use build_system() to assemble a System and
run_from_config() to assemble, initialize, and run it in one call.
- exception syssimx.system.loader.ConfigError[source]#
Bases:
ValueErrorRaised when a system description is invalid.
- syssimx.system.loader.load_config(path)[source]#
Load a system description from a YAML or JSON file.
The format is chosen by file extension:
.jsonis parsed as JSON, everything else (.yaml/.yml) as YAML.
- syssimx.system.loader.build_system(config)[source]#
Assemble a
Systemfrom a description.- Parameters:
config (dict[str, Any] | str | Path) – A parsed configuration mapping, or a path to a YAML/JSON file (which is loaded via
load_config()).- Returns:
The assembled (not yet initialized) system.
- Raises:
ConfigError – On any invalid or missing configuration entry.
- Return type:
- syssimx.system.loader.run_from_config(config, t0=None, tf=None, dt=None, progress=None)[source]#
Assemble, initialize, and run a system from a description.
Run settings are taken from the description’s
runsection; thet0/tf/dtarguments override individual entries.- Parameters:
config (dict[str, Any] | str | Path) – Configuration mapping or path to a YAML/JSON file.
t0 (float | None) – Override for the start time.
tf (float | None) – Override for the end time.
dt (float | None) – Override for the macro step size.
progress (Any) – Optional progress callback forwarded to
System.run.
- Returns:
The
SimulationResultof the run.- Raises:
ConfigError – If any of
t0/tf/dtis neither configured nor provided as an argument.- Return type:
- syssimx.system.loader._build_component(entry)[source]#
Instantiate one component from its description entry.
- Parameters:
- Return type:
- syssimx.system.loader._resolve_class(spec)[source]#
Resolve a
"module.path:ClassName"import spec to a class.
Graph Analysis#
Graph construction and execution ordering helpers for System.
This module builds annotated connection graphs for a System, detects algebraic loops based on zero-delay direct-feedthrough dependencies, and computes a parallelizable execution order. It also provides utilities for identifying delayed producers and collecting zero-delay interface unknowns.
- The helpers in this module mutate the following System fields:
graph: full connection graph with edge port annotations.
_dag: zero-delay direct-feedthrough dependency graph (directed acyclic graph).
algebraic_loops: list of strongly connected components in _dag.
_scc_index: component name to SCC id mapping for the _dag condensation.
_incoming_by_dst: incoming connection list keyed by destination component.
_input_sources: mapping of destination ports to their driving connections.
execution_order: list of component generations for execution.
execution_idx: component name to generation index mapping.
- Typical usage:
build_graphs(system) compute_execution_order(system) interface_inputs, driver_map = collect_global_interface_unknowns(system)
- syssimx.system.graph.collect_active_outputs(system)[source]#
Collect output ports that are used by outgoing connections.
- syssimx.system.graph.build_graphs(system)[source]#
Build annotated connection graphs and detect algebraic loops.
Populates full-connection and zero-delay dependency graphs, then computes algebraic loops (SCCs) on the zero-delay dependency graph.
- Parameters:
system (System) – System to populate with graph structures.
- Raises:
RuntimeError – If multiple connections drive the same input port.
- Return type:
None
- syssimx.system.graph.compute_execution_order(system)[source]#
Compute a parallelizable execution order from zero-delay dependencies.
Condenses the zero-delay graph into SCCs, orders those SCCs in topological generations, and then expands each generation back to component names.
- Parameters:
system (System) – System to populate with execution ordering data.
- Return type:
None
- syssimx.system.graph.is_delayed_producer(system, name)[source]#
Check whether a component is a delayed producer.
A delayed producer has no zero-delay incident edges in the _dag, but still feeds into at least one component that participates in zero-delay structure. These are often actuator-like components whose outputs influence the closed loop only through downstream state.
- syssimx.system.graph.move_delayed_producers_to_last_generation(system)[source]#
Move delayed producers to a final execution generation.
Removes delayed producers from their current generations and appends them as a final, sorted generation in the execution order.
- Parameters:
system (System) – System whose execution order is modified.
- Return type:
None
- syssimx.system.graph.collect_global_interface_unknowns(system)[source]#
Collect interface inputs that participate in zero-delay couplings.
- Parameters:
system (System) – System containing the zero-delay dependency graph.
- Returns:
interface_inputs: list of (dst_comp_name, dst_port_name).
driver_map: mapping (dst_comp, dst_port) -> (src_comp, src_port).
- Return type:
A tuple containing