pySimBlocks 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- examples/advanced/hardware/block_diagram.py +162 -0
- examples/advanced/hardware/camera.py +93 -0
- examples/advanced/hardware/gui_emio.py +231 -0
- examples/advanced/hardware/lmi.py +69 -0
- examples/advanced/hardware/main.py +57 -0
- examples/advanced/hardware/parameters.py +54 -0
- examples/advanced/qp_problem/params.py +10 -0
- examples/advanced/qp_problem/run.py +14 -0
- examples/advanced/sofa/gui/finger/Finger.py +108 -0
- examples/advanced/sofa/gui/finger/FingerController.py +53 -0
- examples/advanced/sofa/gui/finger/parameters_auto.py +22 -0
- examples/advanced/sofa/gui/sofa_plant/run.py +14 -0
- examples/advanced/sofa/python/Finger.py +107 -0
- examples/advanced/sofa/python/FingerController.py +48 -0
- examples/advanced/sofa/python/closedloop.py +64 -0
- examples/advanced/sofa/python/openloop.py +58 -0
- examples/basics/algebraic_function/g.py +7 -0
- examples/basics/algebraic_function/run.py +14 -0
- examples/basics/dc_motor/gui/run.py +14 -0
- examples/basics/dc_motor/multiple_order.py +91 -0
- examples/basics/dc_motor/openloop.py +62 -0
- examples/basics/dc_motor/pi.py +99 -0
- examples/basics/dc_motor/pid.py +118 -0
- examples/basics/dc_motor/pid_block.py +82 -0
- examples/basics/dc_motor/pid_block_vs_manual.py +143 -0
- examples/basics/external/parameters.py +4 -0
- examples/basics/external/run.py +14 -0
- examples/basics/file_sources/temp.py +16 -0
- examples/basics/observer.py +76 -0
- examples/basics/state_feedback/gui/parameters.py +9 -0
- examples/basics/state_feedback/gui/run.py +14 -0
- examples/basics/state_feedback/state_feedback.py +102 -0
- examples/basics/state_space/linear/gui/run.py +14 -0
- examples/basics/state_space/linear/ss_block.py +89 -0
- examples/basics/state_space/linear/ss_manual.py +105 -0
- examples/basics/state_space/non_linear/nlss.py +47 -0
- examples/basics/state_space/non_linear/run.py +14 -0
- examples/basics/state_space/polytope/params.py +39 -0
- examples/basics/state_space/polytope/weights.py +12 -0
- examples/comparison/simulink/convert.py +29 -0
- examples/comparison/simulink/openloop.py +127 -0
- examples/comparison/simulink/pi.py +148 -0
- examples/comparison/simulink/pid.py +154 -0
- examples/comparison/simulink/pid_backward.py +155 -0
- examples/quick_start/filter.py +35 -0
- examples/quick_start/gui/run.py +14 -0
- examples/tutorials/tutorial_1_python/main.py +99 -0
- examples/tutorials/tutorial_3_sofa/finger/Finger.py +120 -0
- examples/tutorials/tutorial_3_sofa/finger/FingerController.py +37 -0
- pySimBlocks/__init__.py +35 -0
- pySimBlocks/blocks/__init__.py +68 -0
- pySimBlocks/blocks/controllers/__init__.py +27 -0
- pySimBlocks/blocks/controllers/pid.py +250 -0
- pySimBlocks/blocks/controllers/state_feedback.py +141 -0
- pySimBlocks/blocks/interfaces/__init__.py +27 -0
- pySimBlocks/blocks/interfaces/external_input.py +107 -0
- pySimBlocks/blocks/interfaces/external_output.py +113 -0
- pySimBlocks/blocks/observers/__init__.py +23 -0
- pySimBlocks/blocks/observers/luenberger.py +175 -0
- pySimBlocks/blocks/operators/__init__.py +47 -0
- pySimBlocks/blocks/operators/algebraic_function.py +258 -0
- pySimBlocks/blocks/operators/dead_zone.py +186 -0
- pySimBlocks/blocks/operators/delay.py +254 -0
- pySimBlocks/blocks/operators/demux.py +123 -0
- pySimBlocks/blocks/operators/discrete_derivator.py +237 -0
- pySimBlocks/blocks/operators/discrete_integrator.py +265 -0
- pySimBlocks/blocks/operators/gain.py +294 -0
- pySimBlocks/blocks/operators/mux.py +131 -0
- pySimBlocks/blocks/operators/product.py +194 -0
- pySimBlocks/blocks/operators/rate_limiter.py +214 -0
- pySimBlocks/blocks/operators/saturation.py +178 -0
- pySimBlocks/blocks/operators/sum.py +188 -0
- pySimBlocks/blocks/operators/zero_order_hold.py +141 -0
- pySimBlocks/blocks/optimizers/__init__.py +23 -0
- pySimBlocks/blocks/optimizers/quadratic_program.py +326 -0
- pySimBlocks/blocks/sources/__init__.py +37 -0
- pySimBlocks/blocks/sources/chirp.py +168 -0
- pySimBlocks/blocks/sources/constant.py +82 -0
- pySimBlocks/blocks/sources/file_source.py +294 -0
- pySimBlocks/blocks/sources/function_source.py +197 -0
- pySimBlocks/blocks/sources/ramp.py +99 -0
- pySimBlocks/blocks/sources/sinusoidal.py +113 -0
- pySimBlocks/blocks/sources/step.py +138 -0
- pySimBlocks/blocks/sources/white_noise.py +107 -0
- pySimBlocks/blocks/systems/__init__.py +27 -0
- pySimBlocks/blocks/systems/linear_state_space.py +180 -0
- pySimBlocks/blocks/systems/non_linear_state_space.py +309 -0
- pySimBlocks/blocks/systems/polytopic_state_space.py +193 -0
- pySimBlocks/blocks/systems/sofa/__init__.py +30 -0
- pySimBlocks/blocks/systems/sofa/sofa_controller.py +472 -0
- pySimBlocks/blocks/systems/sofa/sofa_exchange_i_o.py +120 -0
- pySimBlocks/blocks/systems/sofa/sofa_plant.py +306 -0
- pySimBlocks/cli.py +127 -0
- pySimBlocks/core/__init__.py +32 -0
- pySimBlocks/core/block.py +167 -0
- pySimBlocks/core/block_source.py +94 -0
- pySimBlocks/core/config.py +90 -0
- pySimBlocks/core/fixed_time_manager.py +46 -0
- pySimBlocks/core/model.py +250 -0
- pySimBlocks/core/scheduler.py +30 -0
- pySimBlocks/core/simulator.py +329 -0
- pySimBlocks/core/task.py +73 -0
- pySimBlocks/docs/blocks/controllers/pid.md +78 -0
- pySimBlocks/docs/blocks/controllers/state_feedback.md +60 -0
- pySimBlocks/docs/blocks/interfaces/external_input.md +39 -0
- pySimBlocks/docs/blocks/interfaces/external_output.md +39 -0
- pySimBlocks/docs/blocks/observers/luenberger.md +70 -0
- pySimBlocks/docs/blocks/operators/algebraic_function.md +76 -0
- pySimBlocks/docs/blocks/operators/dead_zone.md +67 -0
- pySimBlocks/docs/blocks/operators/delay.md +76 -0
- pySimBlocks/docs/blocks/operators/demux.md +64 -0
- pySimBlocks/docs/blocks/operators/discrete_derivator.md +61 -0
- pySimBlocks/docs/blocks/operators/discrete_integrator.md +87 -0
- pySimBlocks/docs/blocks/operators/gain.md +97 -0
- pySimBlocks/docs/blocks/operators/mux.md +64 -0
- pySimBlocks/docs/blocks/operators/product.md +51 -0
- pySimBlocks/docs/blocks/operators/rate_limiter.md +70 -0
- pySimBlocks/docs/blocks/operators/saturation.md +62 -0
- pySimBlocks/docs/blocks/operators/sum.md +49 -0
- pySimBlocks/docs/blocks/operators/zero_order_hold.md +61 -0
- pySimBlocks/docs/blocks/optimizers/quadratic_program.md +98 -0
- pySimBlocks/docs/blocks/sources/chirp.md +102 -0
- pySimBlocks/docs/blocks/sources/constant.md +53 -0
- pySimBlocks/docs/blocks/sources/file_source.md +51 -0
- pySimBlocks/docs/blocks/sources/function_source.md +52 -0
- pySimBlocks/docs/blocks/sources/ramp.md +58 -0
- pySimBlocks/docs/blocks/sources/sinusoidal.md +60 -0
- pySimBlocks/docs/blocks/sources/step.md +63 -0
- pySimBlocks/docs/blocks/sources/white_noise.md +58 -0
- pySimBlocks/docs/blocks/systems/linear_state_space.md +66 -0
- pySimBlocks/docs/blocks/systems/non_linear_state_space.md +90 -0
- pySimBlocks/docs/blocks/systems/polytopic_state_space.md +72 -0
- pySimBlocks/docs/blocks/systems/sofa/sofa_exchange_i_o.md +63 -0
- pySimBlocks/docs/blocks/systems/sofa/sofa_plant.md +62 -0
- pySimBlocks/gui/__init__.py +20 -0
- pySimBlocks/gui/addons/__init__.py +19 -0
- pySimBlocks/gui/addons/sofa/__init__.py +19 -0
- pySimBlocks/gui/addons/sofa/sofa_dialog.py +190 -0
- pySimBlocks/gui/addons/sofa/sofa_service.py +209 -0
- pySimBlocks/gui/blocks/block_dialog_session.py +47 -0
- pySimBlocks/gui/blocks/block_meta.py +408 -0
- pySimBlocks/gui/blocks/controllers/pid.py +116 -0
- pySimBlocks/gui/blocks/controllers/state_feedback.py +80 -0
- pySimBlocks/gui/blocks/interfaces/external_input.py +64 -0
- pySimBlocks/gui/blocks/interfaces/external_output.py +63 -0
- pySimBlocks/gui/blocks/observers/luenberger.py +108 -0
- pySimBlocks/gui/blocks/operators/algebraic_function.py +232 -0
- pySimBlocks/gui/blocks/operators/dead_zone.py +79 -0
- pySimBlocks/gui/blocks/operators/delay.py +79 -0
- pySimBlocks/gui/blocks/operators/demux.py +98 -0
- pySimBlocks/gui/blocks/operators/discrete_derivator.py +67 -0
- pySimBlocks/gui/blocks/operators/discrete_integrator.py +80 -0
- pySimBlocks/gui/blocks/operators/gain.py +79 -0
- pySimBlocks/gui/blocks/operators/mux.py +100 -0
- pySimBlocks/gui/blocks/operators/product.py +109 -0
- pySimBlocks/gui/blocks/operators/rate_limiter.py +79 -0
- pySimBlocks/gui/blocks/operators/saturation.py +72 -0
- pySimBlocks/gui/blocks/operators/sum.py +91 -0
- pySimBlocks/gui/blocks/operators/zero_order_hold.py +64 -0
- pySimBlocks/gui/blocks/optimizers/quadratic_program.py +128 -0
- pySimBlocks/gui/blocks/parameter_meta.py +34 -0
- pySimBlocks/gui/blocks/port_meta.py +31 -0
- pySimBlocks/gui/blocks/sources/chirp.py +146 -0
- pySimBlocks/gui/blocks/sources/constant.py +63 -0
- pySimBlocks/gui/blocks/sources/file_source.py +196 -0
- pySimBlocks/gui/blocks/sources/function_source.py +202 -0
- pySimBlocks/gui/blocks/sources/ramp.py +73 -0
- pySimBlocks/gui/blocks/sources/sinusoidal.py +81 -0
- pySimBlocks/gui/blocks/sources/step.py +83 -0
- pySimBlocks/gui/blocks/sources/white_noise.py +70 -0
- pySimBlocks/gui/blocks/systems/linear_state_space.py +102 -0
- pySimBlocks/gui/blocks/systems/non_linear_state_space.py +233 -0
- pySimBlocks/gui/blocks/systems/polytopic_state_space.py +109 -0
- pySimBlocks/gui/blocks/systems/sofa/sofa_exchange_i_o.py +221 -0
- pySimBlocks/gui/blocks/systems/sofa/sofa_plant.py +220 -0
- pySimBlocks/gui/dialogs/__init__.py +19 -0
- pySimBlocks/gui/dialogs/block_dialog.py +125 -0
- pySimBlocks/gui/dialogs/display_yaml_dialog.py +89 -0
- pySimBlocks/gui/dialogs/help_dialog.py +39 -0
- pySimBlocks/gui/dialogs/plot_dialog.py +230 -0
- pySimBlocks/gui/dialogs/settings/plots.py +187 -0
- pySimBlocks/gui/dialogs/settings/project.py +146 -0
- pySimBlocks/gui/dialogs/settings/simulation.py +137 -0
- pySimBlocks/gui/dialogs/settings_dialog.py +88 -0
- pySimBlocks/gui/dialogs/unsaved_dialog.py +92 -0
- pySimBlocks/gui/editor.py +49 -0
- pySimBlocks/gui/graphics/__init__.py +29 -0
- pySimBlocks/gui/graphics/block_item.py +289 -0
- pySimBlocks/gui/graphics/connection_item.py +266 -0
- pySimBlocks/gui/graphics/port_item.py +153 -0
- pySimBlocks/gui/graphics/theme.py +119 -0
- pySimBlocks/gui/main_window.py +162 -0
- pySimBlocks/gui/models/__init__.py +31 -0
- pySimBlocks/gui/models/block_instance.py +114 -0
- pySimBlocks/gui/models/connection_instance.py +43 -0
- pySimBlocks/gui/models/port_instance.py +52 -0
- pySimBlocks/gui/models/project_simulation_params.py +50 -0
- pySimBlocks/gui/models/project_state.py +114 -0
- pySimBlocks/gui/project_controller.py +302 -0
- pySimBlocks/gui/services/__init__.py +20 -0
- pySimBlocks/gui/services/project_loader.py +381 -0
- pySimBlocks/gui/services/project_saver.py +68 -0
- pySimBlocks/gui/services/simulation_runner.py +66 -0
- pySimBlocks/gui/services/yaml_tools.py +261 -0
- pySimBlocks/gui/widgets/__init__.py +19 -0
- pySimBlocks/gui/widgets/block_list.py +131 -0
- pySimBlocks/gui/widgets/diagram_view.py +312 -0
- pySimBlocks/gui/widgets/toolbar_view.py +161 -0
- pySimBlocks/project/__init__.py +35 -0
- pySimBlocks/project/build_model.py +92 -0
- pySimBlocks/project/generate_run_script.py +109 -0
- pySimBlocks/project/generate_sofa_controller.py +215 -0
- pySimBlocks/project/load_project_config.py +197 -0
- pySimBlocks/project/load_simulation_config.py +166 -0
- pySimBlocks/project/load_simulator.py +45 -0
- pySimBlocks/project/plot_from_config.py +174 -0
- pySimBlocks/project/pySimBlocks_blocks_index.yaml +106 -0
- pySimBlocks/real_time/__init__.py +23 -0
- pySimBlocks/real_time/real_time_runner.py +148 -0
- pySimBlocks/tools/__init__.py +19 -0
- pySimBlocks/tools/blocks_registry.py +141 -0
- pySimBlocks/tools/generate_blocks_index.py +124 -0
- pysimblocks-0.1.0.dist-info/METADATA +180 -0
- pysimblocks-0.1.0.dist-info/RECORD +262 -0
- pysimblocks-0.1.0.dist-info/WHEEL +5 -0
- pysimblocks-0.1.0.dist-info/entry_points.txt +2 -0
- pysimblocks-0.1.0.dist-info/licenses/LICENSE.md +165 -0
- pysimblocks-0.1.0.dist-info/top_level.txt +3 -0
- tests/blocks/controllers/test_pid.py +143 -0
- tests/blocks/controllers/test_state_feedback.py +193 -0
- tests/blocks/observers/test_luenberger.py +225 -0
- tests/blocks/operators/test_algebraic_function.py +230 -0
- tests/blocks/operators/test_dead_zone.py +106 -0
- tests/blocks/operators/test_delay.py +167 -0
- tests/blocks/operators/test_demux.py +111 -0
- tests/blocks/operators/test_discrete_derivator.py +160 -0
- tests/blocks/operators/test_discrete_integrator.py +149 -0
- tests/blocks/operators/test_gain.py +182 -0
- tests/blocks/operators/test_mux.py +101 -0
- tests/blocks/operators/test_product.py +171 -0
- tests/blocks/operators/test_rate_limiter.py +166 -0
- tests/blocks/operators/test_saturation.py +157 -0
- tests/blocks/operators/test_sum.py +183 -0
- tests/blocks/operators/test_zero_order_hold.py +106 -0
- tests/blocks/sources/test_chirp.py +157 -0
- tests/blocks/sources/test_constant.py +55 -0
- tests/blocks/sources/test_file_source.py +185 -0
- tests/blocks/sources/test_function_source.py +135 -0
- tests/blocks/sources/test_ramp.py +110 -0
- tests/blocks/sources/test_sinusoidal.py +113 -0
- tests/blocks/sources/test_step.py +76 -0
- tests/blocks/sources/test_white_noise.py +108 -0
- tests/blocks/systems/test_linear_state_space.py +135 -0
- tests/blocks/systems/test_polytopic_state_space.py +133 -0
- tests/core/test_core_errors_and_determinism.py +82 -0
- tests/core/test_core_multirate_tasks.py +88 -0
- tests/core/test_core_two_phase_and_propagation.py +132 -0
- tests/gui/main_window_open_test.py +162 -0
- tests/gui/test_block_meta_param_cache.py +16 -0
- tests/gui/test_file_source_meta.py +17 -0
- tests/gui/test_function_source_meta.py +28 -0
- tests/regression/test_simulink_dc_motor.py +217 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from typing import Dict, List
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
|
|
25
|
+
from pySimBlocks.core.config import SimulationConfig
|
|
26
|
+
from pySimBlocks.core.fixed_time_manager import FixedStepTimeManager
|
|
27
|
+
from pySimBlocks.core.model import Model
|
|
28
|
+
from pySimBlocks.core.scheduler import Scheduler
|
|
29
|
+
from pySimBlocks.core.task import Task
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Simulator:
|
|
33
|
+
"""
|
|
34
|
+
Discrete-time simulator with strict Simulink-like semantics:
|
|
35
|
+
|
|
36
|
+
For each step k:
|
|
37
|
+
|
|
38
|
+
1) PHASE 1: output_update(t)
|
|
39
|
+
Blocks compute outputs y[k] from x[k] and u[k].
|
|
40
|
+
|
|
41
|
+
2) Propagate all outputs to downstream inputs.
|
|
42
|
+
|
|
43
|
+
3) PHASE 2: state_update(t, dt)
|
|
44
|
+
Blocks compute x[k+1] from x[k] and u[k].
|
|
45
|
+
|
|
46
|
+
4) Commit x[k+1] -> x[k].
|
|
47
|
+
|
|
48
|
+
This guarantees:
|
|
49
|
+
- Proper separation of outputs and state transitions.
|
|
50
|
+
- Correct causal behavior for feedback loops.
|
|
51
|
+
- Algebraic loop detection through the Model's topo ordering.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
model: Model,
|
|
57
|
+
sim_cfg: SimulationConfig,
|
|
58
|
+
verbose: bool = False,
|
|
59
|
+
):
|
|
60
|
+
|
|
61
|
+
self.model = model
|
|
62
|
+
self.sim_cfg = sim_cfg
|
|
63
|
+
self.verbose = verbose
|
|
64
|
+
self.model.verbose = verbose
|
|
65
|
+
|
|
66
|
+
self.sim_cfg.validate()
|
|
67
|
+
self._compile()
|
|
68
|
+
|
|
69
|
+
self.logs: Dict[str, List[np.ndarray]] = {"time": []}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# --------------------------------------------------------------------------
|
|
73
|
+
# Public methods
|
|
74
|
+
# --------------------------------------------------------------------------
|
|
75
|
+
def initialize(self, t0: float = 0.0):
|
|
76
|
+
"""Initialize all blocks and propagate initial outputs."""
|
|
77
|
+
self.t = float(t0)
|
|
78
|
+
self.t_step = float(t0)
|
|
79
|
+
self.logs = {"time": []}
|
|
80
|
+
self._log_shapes: Dict[str, tuple[int, int]] = {}
|
|
81
|
+
|
|
82
|
+
# Initialisation bloc par bloc + propagation
|
|
83
|
+
for block in self.output_order:
|
|
84
|
+
try:
|
|
85
|
+
block.initialize(self.t)
|
|
86
|
+
self._propagate_from(block)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
raise RuntimeError(
|
|
89
|
+
f"Error during initialization of block '{block.name}': {e}"
|
|
90
|
+
) from e
|
|
91
|
+
for task in self.tasks:
|
|
92
|
+
task.update_state_blocks()
|
|
93
|
+
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
def step(self, dt_override: float | None = None) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Perform one simulation step.
|
|
98
|
+
|
|
99
|
+
If dt_override is provided, the simulator time advance is driven by this
|
|
100
|
+
external dt (real-time clock). Otherwise, dt is provided by the internal
|
|
101
|
+
time manager (fixed-step).
|
|
102
|
+
"""
|
|
103
|
+
# 0) Choose dt for this tick
|
|
104
|
+
if self.sim_cfg.clock == "external":
|
|
105
|
+
if dt_override is None:
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
"[Simulator] dt_override must be provided when using external clock."
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
dt_scheduler = float(dt_override)
|
|
111
|
+
if dt_scheduler <= 0.0:
|
|
112
|
+
raise ValueError(f"[Simulator] dt_override must be > 0. Got {dt_scheduler}")
|
|
113
|
+
else: # internal clock
|
|
114
|
+
if dt_override is not None:
|
|
115
|
+
raise RuntimeError(
|
|
116
|
+
"[Simulator] dt_override should not be provided when using internal clock."
|
|
117
|
+
)
|
|
118
|
+
dt_scheduler = self.time_manager.next_dt(self.t)
|
|
119
|
+
|
|
120
|
+
active_tasks = self.scheduler.active_tasks(self.t)
|
|
121
|
+
|
|
122
|
+
# PHASE 1 — outputs
|
|
123
|
+
for task in active_tasks:
|
|
124
|
+
dt_task = task.get_dt(self.t)
|
|
125
|
+
for block in task.output_blocks:
|
|
126
|
+
block.output_update(self.t, dt_task)
|
|
127
|
+
self._propagate_from(block)
|
|
128
|
+
|
|
129
|
+
# PHASE 2 — states
|
|
130
|
+
for task in active_tasks:
|
|
131
|
+
dt_task = task.get_dt(self.t)
|
|
132
|
+
for block in task.state_blocks:
|
|
133
|
+
block.state_update(self.t, dt_task)
|
|
134
|
+
|
|
135
|
+
for task in active_tasks:
|
|
136
|
+
for block in task.state_blocks:
|
|
137
|
+
block.commit_state()
|
|
138
|
+
|
|
139
|
+
for task in active_tasks:
|
|
140
|
+
task.advance()
|
|
141
|
+
|
|
142
|
+
self.t_step = self.t
|
|
143
|
+
self.t += dt_scheduler
|
|
144
|
+
|
|
145
|
+
# ------------------------------------------------------------------
|
|
146
|
+
def run(
|
|
147
|
+
self,
|
|
148
|
+
T: float | None = None,
|
|
149
|
+
t0: float | None = None,
|
|
150
|
+
logging: list[str] | None = None,
|
|
151
|
+
):
|
|
152
|
+
"""Run the simulation from t0 to T.
|
|
153
|
+
If T, t0 or logging are not provided, use the simulator's config.
|
|
154
|
+
Returns:
|
|
155
|
+
logs (Dict[str, List[np.ndarray]]): Logged variables over time.
|
|
156
|
+
"""
|
|
157
|
+
if self.sim_cfg.clock == "external":
|
|
158
|
+
raise RuntimeError("Simulator.run() is not supported with external clock. Use step(dt_override=...)")
|
|
159
|
+
|
|
160
|
+
sim_duration = T if T is not None else self.sim_cfg.T
|
|
161
|
+
t0_run = t0 if t0 is not None else self.sim_cfg.t0
|
|
162
|
+
logging_run = logging if logging is not None else self.sim_cfg.logging
|
|
163
|
+
|
|
164
|
+
self.initialize(t0_run)
|
|
165
|
+
|
|
166
|
+
# Main loop (with a small epsilon to avoid floating-point issues)
|
|
167
|
+
eps = 1e-12
|
|
168
|
+
while self.t_step < sim_duration - eps:
|
|
169
|
+
self.step()
|
|
170
|
+
self._log(logging_run)
|
|
171
|
+
|
|
172
|
+
if self.verbose:
|
|
173
|
+
print(f"\nTime: {self.t_step}/{sim_duration}")
|
|
174
|
+
for variable in logging_run:
|
|
175
|
+
print(f"{variable}: {self.logs[variable][-1]}")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
for block in self.model.blocks.values():
|
|
180
|
+
try:
|
|
181
|
+
block.finalize()
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f"[WARNING] finalize() failed for block {block.name}: {e}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
return self.logs
|
|
187
|
+
|
|
188
|
+
# ------------------------------------------------------------------
|
|
189
|
+
def get_data(self,
|
|
190
|
+
variable: str | None = None,
|
|
191
|
+
block:str | None = None,
|
|
192
|
+
port: str | None = None) -> np.ndarray:
|
|
193
|
+
"""Retrieve logged data for a specific variable, block output, or state.
|
|
194
|
+
Provide either:
|
|
195
|
+
- variable: the full variable name as logged (e.g., "BlockName.outputs.Port)
|
|
196
|
+
- block and port: to specify an output variable (e.g., block="BlockName", port="Port")
|
|
197
|
+
"""
|
|
198
|
+
if variable is not None:
|
|
199
|
+
var_name = variable
|
|
200
|
+
elif block is not None and port is not None:
|
|
201
|
+
var_name = f"{block}.outputs.{port}"
|
|
202
|
+
else:
|
|
203
|
+
raise ValueError("Either variable or (block, port) must be provided.")
|
|
204
|
+
|
|
205
|
+
if var_name not in self.logs:
|
|
206
|
+
raise ValueError(f"Variable '{var_name}' is not logged. Available logs: {list(self.logs.keys())}")
|
|
207
|
+
|
|
208
|
+
data = self.logs.get(var_name)
|
|
209
|
+
if data is None:
|
|
210
|
+
raise ValueError(f"No data found for variable '{var_name}'.")
|
|
211
|
+
length = len(data)
|
|
212
|
+
if length == 0:
|
|
213
|
+
raise ValueError(f"Log for variable '{var_name}' is empty.")
|
|
214
|
+
shape = data[0].shape
|
|
215
|
+
try:
|
|
216
|
+
data_array = np.array(data).reshape(length, *shape)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
raise ValueError(f"Failed to convert log data for variable '{var_name}' to numpy array: {e}") from e
|
|
219
|
+
|
|
220
|
+
return data_array
|
|
221
|
+
|
|
222
|
+
# --------------------------------------------------------------------------
|
|
223
|
+
# Private methods
|
|
224
|
+
# --------------------------------------------------------------------------
|
|
225
|
+
def _compile(self):
|
|
226
|
+
"""Prepare the simulator for execution.
|
|
227
|
+
- Build execution order.
|
|
228
|
+
- Group blocks into tasks by sample time.
|
|
229
|
+
- Initialize the scheduler and time manager.
|
|
230
|
+
"""
|
|
231
|
+
self.output_order = self.model.build_execution_order()
|
|
232
|
+
self.model.resolve_sample_times(self.sim_cfg.dt)
|
|
233
|
+
self.model._rebuild_downstream_map()
|
|
234
|
+
sample_times = [b._effective_sample_time for b in self.model.blocks.values()]
|
|
235
|
+
|
|
236
|
+
# regroup blocks by sample time
|
|
237
|
+
tasks_by_ts = {}
|
|
238
|
+
for b in self.model.blocks.values():
|
|
239
|
+
sample_time = b._effective_sample_time
|
|
240
|
+
tasks_by_ts.setdefault(sample_time, []).append(b)
|
|
241
|
+
|
|
242
|
+
self.tasks = [
|
|
243
|
+
Task(sample_time, blocks, self.output_order)
|
|
244
|
+
for sample_time, blocks in tasks_by_ts.items()
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
self._single_task = (len(self.tasks) == 1)
|
|
248
|
+
if self._single_task:
|
|
249
|
+
self._task0 = self.tasks[0]
|
|
250
|
+
|
|
251
|
+
self.scheduler = Scheduler(self.tasks)
|
|
252
|
+
|
|
253
|
+
if self.sim_cfg.solver == "fixed":
|
|
254
|
+
self.time_manager = FixedStepTimeManager(
|
|
255
|
+
dt_base=self.sim_cfg.dt,
|
|
256
|
+
sample_times=list(set(sample_times))
|
|
257
|
+
)
|
|
258
|
+
elif self.sim_cfg.solver == "variable":
|
|
259
|
+
raise NotImplementedError(
|
|
260
|
+
"Variable-step simulation is not implemented yet."
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
raise ValueError(
|
|
264
|
+
f"Unknown simulation mode '{self.sim_cfg.solver}'. "
|
|
265
|
+
"Supported modes are: 'fixed', 'variable'."
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ------------------------------------------------------------------
|
|
270
|
+
def _propagate_from(self, block):
|
|
271
|
+
"""
|
|
272
|
+
Propagate outputs of `block` to its direct downstream blocks.
|
|
273
|
+
"""
|
|
274
|
+
blocks = self.model.blocks
|
|
275
|
+
for (src, dst) in self.model.downstream_of(block.name):
|
|
276
|
+
_, src_port = src
|
|
277
|
+
dst_block, dst_port = dst
|
|
278
|
+
value = block.outputs[src_port]
|
|
279
|
+
if value is not None:
|
|
280
|
+
blocks[dst_block].inputs[dst_port] = value
|
|
281
|
+
|
|
282
|
+
# ------------------------------------------------------------------
|
|
283
|
+
def _log(self, variables_to_log):
|
|
284
|
+
"""Log specified variables at the current time step.
|
|
285
|
+
|
|
286
|
+
Enforces:
|
|
287
|
+
- logged values must be 2D numpy arrays
|
|
288
|
+
- shape must stay constant over time for each logged variable
|
|
289
|
+
"""
|
|
290
|
+
for var in variables_to_log:
|
|
291
|
+
block_name, container, key = var.split(".")
|
|
292
|
+
block = self.model.blocks[block_name]
|
|
293
|
+
|
|
294
|
+
if container == "outputs":
|
|
295
|
+
value = block.outputs[key]
|
|
296
|
+
elif container == "state":
|
|
297
|
+
value = block.state[key]
|
|
298
|
+
else:
|
|
299
|
+
raise ValueError(f"Unknown container '{container}' in '{var}'.")
|
|
300
|
+
|
|
301
|
+
if value is None:
|
|
302
|
+
raise RuntimeError(
|
|
303
|
+
f"[Simulator] Cannot log '{var}' at t={self.t_step}: value is None."
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
arr = np.asarray(value)
|
|
307
|
+
|
|
308
|
+
if arr.ndim != 2:
|
|
309
|
+
raise RuntimeError(
|
|
310
|
+
f"[Simulator] Cannot log '{var}' at t={self.t_step}: expected a 2D array, "
|
|
311
|
+
f"got ndim={arr.ndim} with shape {arr.shape}."
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Enforce constant shape over time for this variable
|
|
315
|
+
if var not in self._log_shapes:
|
|
316
|
+
self._log_shapes[var] = arr.shape
|
|
317
|
+
else:
|
|
318
|
+
expected_shape = self._log_shapes[var]
|
|
319
|
+
if arr.shape != expected_shape:
|
|
320
|
+
raise RuntimeError(
|
|
321
|
+
f"[Simulator] Logged signal '{var}' changed shape over time at t={self.t_step}: "
|
|
322
|
+
f"expected {expected_shape}, got {arr.shape}."
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
if var not in self.logs:
|
|
326
|
+
self.logs[var] = []
|
|
327
|
+
self.logs[var].append(np.copy(arr))
|
|
328
|
+
|
|
329
|
+
self.logs["time"].append(np.array([self.t_step]))
|
pySimBlocks/core/task.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
class Task:
|
|
22
|
+
"""A task represents a group of blocks that share the same sample time."""
|
|
23
|
+
def __init__(self, sample_time, blocks, global_output_order):
|
|
24
|
+
self.sample_time = sample_time
|
|
25
|
+
self.next_activation = 0.0
|
|
26
|
+
self.last_activation = None
|
|
27
|
+
|
|
28
|
+
self.output_blocks = [
|
|
29
|
+
b for b in global_output_order
|
|
30
|
+
if b in blocks
|
|
31
|
+
]
|
|
32
|
+
self.state_blocks = []
|
|
33
|
+
|
|
34
|
+
def update_state_blocks(self):
|
|
35
|
+
"""Update the list of blocks with state within this task."""
|
|
36
|
+
self.state_blocks = [
|
|
37
|
+
b for b in self.output_blocks
|
|
38
|
+
if b.has_state
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
def should_run(self, t, eps=1e-12):
|
|
42
|
+
"""Check if the task should run at time t."""
|
|
43
|
+
return t + eps >= self.next_activation
|
|
44
|
+
|
|
45
|
+
def get_dt(self, t):
|
|
46
|
+
"""Get the time step for this task at time t."""
|
|
47
|
+
if self.last_activation is None:
|
|
48
|
+
return self.sample_time
|
|
49
|
+
return t - self.last_activation
|
|
50
|
+
|
|
51
|
+
def advance(self):
|
|
52
|
+
"""Advance the task's activation times."""
|
|
53
|
+
self.last_activation = self.next_activation
|
|
54
|
+
self.next_activation += self.sample_time
|
|
55
|
+
|
|
56
|
+
def run_outputs(self, t: float, dt: float, propagate_cb):
|
|
57
|
+
for block in self.output_blocks:
|
|
58
|
+
block.output_update(t, dt)
|
|
59
|
+
propagate_cb(block)
|
|
60
|
+
|
|
61
|
+
def run_states(self, t: float, dt: float):
|
|
62
|
+
for block in self.state_blocks:
|
|
63
|
+
block.state_update(t, dt)
|
|
64
|
+
|
|
65
|
+
def commit_states(self):
|
|
66
|
+
for block in self.state_blocks:
|
|
67
|
+
block.commit_state()
|
|
68
|
+
|
|
69
|
+
def tick(self, t: float, dt: float, propagate_cb):
|
|
70
|
+
# Optionnel : si tu veux une API unique, mais attention :
|
|
71
|
+
# tu ne peux PAS commit ici si tu veux respecter le "all state_update before any commit"
|
|
72
|
+
self.run_outputs(t, dt, propagate_cb)
|
|
73
|
+
self.run_states(t, dt)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# PID Controller
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
The PID block implements a discrete-time proportional–integral–derivative
|
|
6
|
+
controller in parallel form. It is strictly single-input single-output (SISO)
|
|
7
|
+
and follows Simulink-like execution semantics.
|
|
8
|
+
|
|
9
|
+
The controller structure can be configured to enable or disable individual
|
|
10
|
+
terms (P, I, D), and the integration method can be selected.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Mathematical Formulation
|
|
15
|
+
|
|
16
|
+
The control law is given by:
|
|
17
|
+
|
|
18
|
+
$$
|
|
19
|
+
u[k] = K_p e[k] + x_i[k] + K_d \frac{e[k] - e[k-1]}{dt}
|
|
20
|
+
$$
|
|
21
|
+
|
|
22
|
+
where the integral state evolves according to:
|
|
23
|
+
|
|
24
|
+
### Euler forward integration
|
|
25
|
+
|
|
26
|
+
$$
|
|
27
|
+
x_i[k+1] = x_i[k] + K_i e[k] \, dt
|
|
28
|
+
$$
|
|
29
|
+
|
|
30
|
+
### Euler backward integration
|
|
31
|
+
|
|
32
|
+
$$
|
|
33
|
+
x_i[k+1] = u[k] - \left( K_p e[k] + K_d \frac{e[k] - e[k-1]}{dt} \right)
|
|
34
|
+
$$
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Parameters
|
|
39
|
+
|
|
40
|
+
| Name | Type | Description | Optional |
|
|
41
|
+
|------------|-------------|-------------|-------------|
|
|
42
|
+
| `controller` | string | Controller structure: `P`, `I`, `PI`, `PD`, or `PID`. | False |
|
|
43
|
+
| `Kp` | scalar | Proportional gain. | False |
|
|
44
|
+
| `Ki` | scalar | Integral gain. | False |
|
|
45
|
+
| `Kd` | scalar | Derivative gain. | False |
|
|
46
|
+
| `integration_method` | string | Numerical integration scheme for the integral term: `euler forward` (default) or `euler backward`. | True |
|
|
47
|
+
| `u_min` | scalar | Minimum output saturation bound. | True |
|
|
48
|
+
| `u_max` | scalar | Maximum output saturation bound. | True |
|
|
49
|
+
| `sample_time` | float | Block sample time. If omitted, the global simulation time step is used. | True |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Inputs
|
|
54
|
+
|
|
55
|
+
| Port | Description |
|
|
56
|
+
|------|------------|
|
|
57
|
+
| `e` | Control error. |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Outputs
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
| Port | Description |
|
|
65
|
+
|------|------------|
|
|
66
|
+
| `u` | Control command. |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Notes
|
|
71
|
+
|
|
72
|
+
- The block is strictly SISO.
|
|
73
|
+
- Dimension checks are enforced at runtime.
|
|
74
|
+
- Direct feedthrough depends on the controller structure and integration method.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
© 2026 Université de Lille & INRIA – Licensed under LGPL-3.0-or-later
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# StateFeedback
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
The **StateFeedback** block implements a discrete-time static state-feedback
|
|
6
|
+
controller combining reference feedforward and state feedback.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Mathematical definition
|
|
11
|
+
|
|
12
|
+
The control law is defined as:
|
|
13
|
+
|
|
14
|
+
$$
|
|
15
|
+
u[k] = G r[k] - K x[k]
|
|
16
|
+
$$
|
|
17
|
+
|
|
18
|
+
where:
|
|
19
|
+
- $r[k]$ is the reference vector,
|
|
20
|
+
- $x[k]$ is the measured state vector,
|
|
21
|
+
- $u[k]$ is the control input.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Parameters
|
|
26
|
+
|
|
27
|
+
| Name | Type | Description | Optional |
|
|
28
|
+
|------------|-------------|-------------|-------------|
|
|
29
|
+
| `K` | array | State feedback gain matrix. | False |
|
|
30
|
+
| `G` | array | Reference feedforward gain matrix. | False |
|
|
31
|
+
| `sample_time` | float | Block sample time. If omitted, the global simulation time step is used. | True |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Inputs
|
|
36
|
+
|
|
37
|
+
| Port | Description |
|
|
38
|
+
|------|------------|
|
|
39
|
+
| `r` | Reference vector. |
|
|
40
|
+
| `x` | State measurement vector. |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Outputs
|
|
45
|
+
|
|
46
|
+
| Port | Description |
|
|
47
|
+
|------|------------|
|
|
48
|
+
| `u` | Control input vector. |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
|
|
54
|
+
- The controller has no internal state.
|
|
55
|
+
- The control law is evaluated at each simulation step.
|
|
56
|
+
- Both reference and state inputs must be connected.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
© 2026 Université de Lille & INRIA – Licensed under LGPL-3.0-or-later
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# External Input Block
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
The External Input block represents a data input point in a simulation model. It allows external signals to be fed into the model during simulation. The block outputs the value of the external signal at each simulation time step.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Parameters
|
|
10
|
+
|
|
11
|
+
| Name | Type | Description | Optional |
|
|
12
|
+
|------------|-------------|-------------|-------------|
|
|
13
|
+
| `sample_time` | float | Block sample time. If omitted, the global simulation time step is used. | True |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Inputs
|
|
18
|
+
|
|
19
|
+
| Port | Description |
|
|
20
|
+
|------|------------|
|
|
21
|
+
| `in` | External data signal $u$. |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Outputs
|
|
26
|
+
|
|
27
|
+
| Port | Description |
|
|
28
|
+
|------|------------|
|
|
29
|
+
| `out` | Model data signal. |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
- The External Input block is **stateless**.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
© 2026 Université de Lille & INRIA – Licensed under LGPL-3.0-or-later
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# External Output Block
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
The External Output block represents a data output point in a simulation model. It allows the model to send signals to an external environment during simulation. The block outputs the value of the internal signal at each simulation time step.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Parameters
|
|
10
|
+
|
|
11
|
+
| Name | Type | Description | Optional |
|
|
12
|
+
|------------|-------------|-------------|-------------|
|
|
13
|
+
| `sample_time` | float | Block sample time. If omitted, the global simulation time step is used. | True |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Inputs
|
|
18
|
+
|
|
19
|
+
| Port | Description |
|
|
20
|
+
|------|------------|
|
|
21
|
+
| `in` | Model data signal $u$. |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Outputs
|
|
26
|
+
|
|
27
|
+
| Port | Description |
|
|
28
|
+
|------|------------|
|
|
29
|
+
| `out` | External data signal. |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
- The External Output block is **stateless**.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
© 2026 Université de Lille & INRIA – Licensed under LGPL-3.0-or-later
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Luenberger
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
The **Luenberger** block implements a discrete-time state observer that
|
|
6
|
+
estimates the system state from input and output measurements.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Mathematical definition
|
|
11
|
+
|
|
12
|
+
The observer is defined by the equations:
|
|
13
|
+
|
|
14
|
+
$$
|
|
15
|
+
x̂[k+1] = A x̂[k] + B u[k] + L (y[k] - C x̂[k])
|
|
16
|
+
$$
|
|
17
|
+
|
|
18
|
+
$$
|
|
19
|
+
ŷ[k] = C x̂[k]
|
|
20
|
+
$$
|
|
21
|
+
|
|
22
|
+
where:
|
|
23
|
+
- $x̂[k]$ is the estimated state,
|
|
24
|
+
- $u[k]$ is the control input,
|
|
25
|
+
- $y[k]$ is the measured output,
|
|
26
|
+
- $ŷ[k]$ is the estimated output.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Parameters
|
|
31
|
+
|
|
32
|
+
| Name | Type | Description | Optional |
|
|
33
|
+
|------------|-------------|-------------|-------------|
|
|
34
|
+
| `A` | array | System state matrix. | False |
|
|
35
|
+
| `B` | array | Input matrix. | False |
|
|
36
|
+
| `C` | array | Output matrix. | False |
|
|
37
|
+
| `L` | array | Observer gain matrix. | False |
|
|
38
|
+
| `x0` | array | Initial estimated state. If omitted, the estimate is initialized to zero. | True |
|
|
39
|
+
| `sample_time` | float | Block sample time. If omitted, the global simulation time step is used. | True |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Inputs
|
|
44
|
+
|
|
45
|
+
| Port | Description |
|
|
46
|
+
|------|------------|
|
|
47
|
+
| `u` | Control input. |
|
|
48
|
+
| `y` | Measured output. |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Outputs
|
|
53
|
+
|
|
54
|
+
| Port | Description |
|
|
55
|
+
|------|------------|
|
|
56
|
+
| `x_hat` | Estimated state. |
|
|
57
|
+
| `y_hat` | Estimated output. |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Notes
|
|
62
|
+
|
|
63
|
+
- The block has internal state.
|
|
64
|
+
- The observer has no direct feedthrough.
|
|
65
|
+
- Matrix $D$ is intentionally not supported to avoid algebraic loops.
|
|
66
|
+
- The state estimate is updated once per simulation step.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
© 2026 Université de Lille & INRIA – Licensed under LGPL-3.0-or-later
|