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,68 @@
|
|
|
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 pySimBlocks.blocks.controllers import Pid, StateFeedback
|
|
22
|
+
from pySimBlocks.blocks.interfaces import ExternalInput, ExternalOutput
|
|
23
|
+
from pySimBlocks.blocks.observers import Luenberger
|
|
24
|
+
from pySimBlocks.blocks.operators import (
|
|
25
|
+
DeadZone, Delay, DiscreteDerivator, DiscreteIntegrator,
|
|
26
|
+
Gain, Mux, Product, RateLimiter, Saturation, Sum, ZeroOrderHold
|
|
27
|
+
)
|
|
28
|
+
from pySimBlocks.blocks.sources import (
|
|
29
|
+
Constant,
|
|
30
|
+
FunctionSource,
|
|
31
|
+
Ramp,
|
|
32
|
+
Sinusoidal,
|
|
33
|
+
Step,
|
|
34
|
+
WhiteNoise,
|
|
35
|
+
)
|
|
36
|
+
from pySimBlocks.blocks.systems import LinearStateSpace, PolytopicStateSpace
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"Pid",
|
|
40
|
+
"StateFeedback",
|
|
41
|
+
|
|
42
|
+
"ExternalInput",
|
|
43
|
+
"ExternalOutput",
|
|
44
|
+
|
|
45
|
+
"Luenberger",
|
|
46
|
+
|
|
47
|
+
"DeadZone",
|
|
48
|
+
"Delay",
|
|
49
|
+
"DiscreteDerivator",
|
|
50
|
+
"DiscreteIntegrator",
|
|
51
|
+
"Gain",
|
|
52
|
+
"Mux",
|
|
53
|
+
"Product",
|
|
54
|
+
"RateLimiter",
|
|
55
|
+
"Saturation",
|
|
56
|
+
"Sum",
|
|
57
|
+
"ZeroOrderHold",
|
|
58
|
+
|
|
59
|
+
"Constant",
|
|
60
|
+
"FunctionSource",
|
|
61
|
+
"Ramp",
|
|
62
|
+
"Step",
|
|
63
|
+
"Sinusoidal",
|
|
64
|
+
"WhiteNoise",
|
|
65
|
+
|
|
66
|
+
"LinearStateSpace",
|
|
67
|
+
"PolytopicStateSpace",
|
|
68
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
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 pySimBlocks.blocks.controllers.pid import Pid
|
|
22
|
+
from pySimBlocks.blocks.controllers.state_feedback import StateFeedback
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Pid",
|
|
26
|
+
"StateFeedback"
|
|
27
|
+
]
|
|
@@ -0,0 +1,250 @@
|
|
|
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
|
+
import warnings
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
from numpy.typing import ArrayLike
|
|
25
|
+
|
|
26
|
+
from pySimBlocks.core.block import Block
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Pid(Block):
|
|
30
|
+
"""
|
|
31
|
+
Discrete-time PID controller block.
|
|
32
|
+
|
|
33
|
+
Summary:
|
|
34
|
+
Implements a single-input single-output discrete PID controller,
|
|
35
|
+
similar to the Simulink PID block. The controller computes a control
|
|
36
|
+
command from an error signal using proportional, integral and/or
|
|
37
|
+
derivative actions, depending on the selected control mode.
|
|
38
|
+
|
|
39
|
+
Parameters (overview):
|
|
40
|
+
controller : str
|
|
41
|
+
Control mode. One of {"P", "PI", "PD", "PID"}.
|
|
42
|
+
Kp : float
|
|
43
|
+
Proportional gain.
|
|
44
|
+
Ki : float
|
|
45
|
+
Integral gain.
|
|
46
|
+
Kd : float
|
|
47
|
+
Derivative gain.
|
|
48
|
+
u_min : float, optional
|
|
49
|
+
Minimum output saturation.
|
|
50
|
+
u_max : float, optional
|
|
51
|
+
Maximum output saturation.
|
|
52
|
+
sample_time : float, optional
|
|
53
|
+
Controller sampling period.
|
|
54
|
+
|
|
55
|
+
I/O:
|
|
56
|
+
Inputs:
|
|
57
|
+
e : error signal.
|
|
58
|
+
Outputs:
|
|
59
|
+
u : control command.
|
|
60
|
+
|
|
61
|
+
Notes:
|
|
62
|
+
- The block is strictly SISO.
|
|
63
|
+
- Integral action introduces internal state.
|
|
64
|
+
- If a parameter is not provided, the block falls back to its
|
|
65
|
+
internal default value.
|
|
66
|
+
- Output saturation is applied only if u_min and/or u_max are defined.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
name: str,
|
|
72
|
+
controller: str = "PID",
|
|
73
|
+
Kp: ArrayLike = 0.0,
|
|
74
|
+
Ki: ArrayLike = 0.0,
|
|
75
|
+
Kd: ArrayLike = 0.0,
|
|
76
|
+
u_min: ArrayLike | None = None,
|
|
77
|
+
u_max: ArrayLike | None = None,
|
|
78
|
+
integration_method: str = "euler forward",
|
|
79
|
+
sample_time: float | None = None,
|
|
80
|
+
):
|
|
81
|
+
super().__init__(name, sample_time)
|
|
82
|
+
|
|
83
|
+
controller = controller.upper()
|
|
84
|
+
allowed_types = {"P", "I", "PI", "PD", "PID"}
|
|
85
|
+
if controller not in allowed_types:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"[{self.name}] Invalid controller type '{controller}'. Allowed: {allowed_types}"
|
|
88
|
+
)
|
|
89
|
+
self.controller = controller
|
|
90
|
+
|
|
91
|
+
self.integration_method = integration_method.lower()
|
|
92
|
+
allowed = ("euler forward", "euler backward")
|
|
93
|
+
if self.integration_method not in allowed:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"[{self.name}] Unsupported method '{self.integration_method}'. Allowed: {allowed}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Gains (SISO, (1,1))
|
|
99
|
+
self.Kp = self._to_siso("Kp", Kp)
|
|
100
|
+
self.Ki = self._to_siso("Ki", Ki)
|
|
101
|
+
self.Kd = self._to_siso("Kd", Kd)
|
|
102
|
+
|
|
103
|
+
self.u_min = None if u_min is None else self._to_siso("u_min", u_min)
|
|
104
|
+
self.u_max = None if u_max is None else self._to_siso("u_max", u_max)
|
|
105
|
+
|
|
106
|
+
if self.u_min is not None and self.u_max is not None:
|
|
107
|
+
if float(self.u_min[0, 0]) > float(self.u_max[0, 0]):
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"[{self.name}] u_min ({self.u_min.item()}) must be <= u_max ({self.u_max.item()})."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Warnings
|
|
113
|
+
self._validate_gains()
|
|
114
|
+
|
|
115
|
+
# Direct feedthrough policy
|
|
116
|
+
has_p = "P" in self.controller
|
|
117
|
+
has_d = "D" in self.controller
|
|
118
|
+
has_i = "I" in self.controller
|
|
119
|
+
|
|
120
|
+
if has_p or has_d:
|
|
121
|
+
self.direct_feedthrough = True
|
|
122
|
+
else:
|
|
123
|
+
# I-only
|
|
124
|
+
self.direct_feedthrough = (self.integration_method == "euler backward")
|
|
125
|
+
|
|
126
|
+
# Ports
|
|
127
|
+
self.inputs["e"] = None
|
|
128
|
+
self.outputs["u"] = None
|
|
129
|
+
|
|
130
|
+
# State (SISO)
|
|
131
|
+
self.state["x_i"] = np.zeros((1, 1), dtype=float)
|
|
132
|
+
self.state["e_prev"] = np.zeros((1, 1), dtype=float)
|
|
133
|
+
self.next_state["x_i"] = np.zeros((1, 1), dtype=float)
|
|
134
|
+
self.next_state["e_prev"] = np.zeros((1, 1), dtype=float)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# --------------------------------------------------------------------------
|
|
138
|
+
# Public methods
|
|
139
|
+
# --------------------------------------------------------------------------
|
|
140
|
+
def initialize(self, t0: float):
|
|
141
|
+
# Start at zero command, keep internal states at zero.
|
|
142
|
+
self.outputs["u"] = np.zeros((1, 1), dtype=float)
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
def output_update(self, t: float, dt: float):
|
|
146
|
+
e_in = self.inputs["e"]
|
|
147
|
+
if e_in is None:
|
|
148
|
+
raise RuntimeError(f"[{self.name}] Missing input 'e'.")
|
|
149
|
+
|
|
150
|
+
e = self._to_siso("e", e_in)
|
|
151
|
+
|
|
152
|
+
x_i = self.state["x_i"]
|
|
153
|
+
e_prev = self.state["e_prev"]
|
|
154
|
+
|
|
155
|
+
has_p = "P" in self.controller
|
|
156
|
+
has_i = "I" in self.controller
|
|
157
|
+
has_d = "D" in self.controller
|
|
158
|
+
|
|
159
|
+
P = self.Kp * e if has_p else np.zeros((1, 1), dtype=float)
|
|
160
|
+
|
|
161
|
+
if has_i:
|
|
162
|
+
if self.integration_method == "euler forward":
|
|
163
|
+
I = x_i
|
|
164
|
+
else:
|
|
165
|
+
I = x_i + self.Ki * e * dt
|
|
166
|
+
else:
|
|
167
|
+
I = np.zeros((1, 1), dtype=float)
|
|
168
|
+
|
|
169
|
+
D = (self.Kd * (e - e_prev) / dt) if has_d else np.zeros((1, 1), dtype=float)
|
|
170
|
+
|
|
171
|
+
u = P + I + D
|
|
172
|
+
|
|
173
|
+
# Saturation
|
|
174
|
+
if self.u_min is not None:
|
|
175
|
+
u = np.maximum(u, self.u_min)
|
|
176
|
+
if self.u_max is not None:
|
|
177
|
+
u = np.minimum(u, self.u_max)
|
|
178
|
+
|
|
179
|
+
self.outputs["u"] = u
|
|
180
|
+
|
|
181
|
+
# ------------------------------------------------------------------
|
|
182
|
+
def state_update(self, t: float, dt: float):
|
|
183
|
+
e_in = self.inputs["e"]
|
|
184
|
+
if e_in is None:
|
|
185
|
+
raise RuntimeError(f"[{self.name}] Missing input 'e'.")
|
|
186
|
+
|
|
187
|
+
e = self._to_siso("e", e_in)
|
|
188
|
+
|
|
189
|
+
has_i = "I" in self.controller
|
|
190
|
+
|
|
191
|
+
# Integrator update only if I term is enabled
|
|
192
|
+
if has_i:
|
|
193
|
+
x_i_next = self.state["x_i"] + self.Ki * e * dt
|
|
194
|
+
else:
|
|
195
|
+
x_i_next = self.state["x_i"].copy()
|
|
196
|
+
|
|
197
|
+
# Anti-windup (clamp integral state if saturation is defined)
|
|
198
|
+
if self.u_min is not None:
|
|
199
|
+
x_i_next = np.maximum(x_i_next, self.u_min)
|
|
200
|
+
if self.u_max is not None:
|
|
201
|
+
x_i_next = np.minimum(x_i_next, self.u_max)
|
|
202
|
+
|
|
203
|
+
self.next_state["x_i"] = x_i_next
|
|
204
|
+
self.next_state["e_prev"] = e.copy()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# --------------------------------------------------------------------------
|
|
208
|
+
# Private methods
|
|
209
|
+
# --------------------------------------------------------------------------
|
|
210
|
+
def _to_siso(self, name: str, value: ArrayLike) -> np.ndarray:
|
|
211
|
+
"""
|
|
212
|
+
Normalize scalar-like into (1,1). Reject anything else (strict SISO).
|
|
213
|
+
"""
|
|
214
|
+
if np.isscalar(value):
|
|
215
|
+
return np.array([[float(value)]], dtype=float)
|
|
216
|
+
|
|
217
|
+
arr = np.asarray(value, dtype=float)
|
|
218
|
+
|
|
219
|
+
if arr.shape == ():
|
|
220
|
+
return np.array([[float(arr)]], dtype=float)
|
|
221
|
+
if arr.shape == (1,):
|
|
222
|
+
return arr.reshape(1, 1)
|
|
223
|
+
if arr.shape == (1, 1):
|
|
224
|
+
return arr
|
|
225
|
+
|
|
226
|
+
raise ValueError(
|
|
227
|
+
f"[{self.name}] '{name}' must be scalar-like ((), (1,), or (1,1)). Got shape {arr.shape}."
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# ------------------------------------------------------------------
|
|
231
|
+
def _validate_gains(self) -> None:
|
|
232
|
+
kp = float(self.Kp[0, 0])
|
|
233
|
+
ki = float(self.Ki[0, 0])
|
|
234
|
+
kd = float(self.Kd[0, 0])
|
|
235
|
+
|
|
236
|
+
if "P" in self.controller and kp == 0.0:
|
|
237
|
+
warnings.warn(
|
|
238
|
+
f"[{self.name}] Kp=0 while controller '{self.controller}' includes a P term.",
|
|
239
|
+
UserWarning,
|
|
240
|
+
)
|
|
241
|
+
if "I" in self.controller and ki == 0.0:
|
|
242
|
+
warnings.warn(
|
|
243
|
+
f"[{self.name}] Ki=0 while controller '{self.controller}' includes an I term.",
|
|
244
|
+
UserWarning,
|
|
245
|
+
)
|
|
246
|
+
if "D" in self.controller and kd == 0.0:
|
|
247
|
+
warnings.warn(
|
|
248
|
+
f"[{self.name}] Kd=0 while controller '{self.controller}' includes a D term.",
|
|
249
|
+
UserWarning,
|
|
250
|
+
)
|
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
import numpy as np
|
|
22
|
+
from pySimBlocks.core.block import Block
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class StateFeedback(Block):
|
|
26
|
+
"""
|
|
27
|
+
Discrete-time state-feedback controller block.
|
|
28
|
+
|
|
29
|
+
Summary:
|
|
30
|
+
Implements a static discrete-time state-feedback control law:
|
|
31
|
+
u = G @ r - K @ x
|
|
32
|
+
|
|
33
|
+
Parameters:
|
|
34
|
+
K : array-like, shape (m, n)
|
|
35
|
+
State feedback gain matrix.
|
|
36
|
+
G : array-like, shape (m, p)
|
|
37
|
+
Reference feedforward gain matrix.
|
|
38
|
+
sample_time : float, optional
|
|
39
|
+
Block execution period.
|
|
40
|
+
|
|
41
|
+
Inputs:
|
|
42
|
+
r : array (p, 1)
|
|
43
|
+
Reference vector.
|
|
44
|
+
x : array (n, 1)
|
|
45
|
+
State vector.
|
|
46
|
+
|
|
47
|
+
Outputs:
|
|
48
|
+
u : array (m, 1)
|
|
49
|
+
Control vector.
|
|
50
|
+
|
|
51
|
+
Notes:
|
|
52
|
+
- Stateless block.
|
|
53
|
+
- This block intentionally enforces column-vector inputs.
|
|
54
|
+
- No implicit flattening is performed.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
direct_feedthrough = True
|
|
58
|
+
|
|
59
|
+
def __init__(self, name: str, K, G, sample_time: float | None = None):
|
|
60
|
+
super().__init__(name, sample_time)
|
|
61
|
+
|
|
62
|
+
self.K = np.asarray(K, dtype=float)
|
|
63
|
+
self.G = np.asarray(G, dtype=float)
|
|
64
|
+
|
|
65
|
+
if self.K.ndim != 2:
|
|
66
|
+
raise ValueError(f"[{self.name}] K must be a 2D array (m,n). Got shape {self.K.shape}.")
|
|
67
|
+
if self.G.ndim != 2:
|
|
68
|
+
raise ValueError(f"[{self.name}] G must be a 2D array (m,p). Got shape {self.G.shape}.")
|
|
69
|
+
|
|
70
|
+
m, n = self.K.shape
|
|
71
|
+
m2, p = self.G.shape
|
|
72
|
+
|
|
73
|
+
if m != m2:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"[{self.name}] Inconsistent dimensions: "
|
|
76
|
+
f"K is {self.K.shape} while G is {self.G.shape} (first dimension must match)."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# cached expected sizes for input validation
|
|
80
|
+
self._m = m
|
|
81
|
+
self._n = n
|
|
82
|
+
self._p = p
|
|
83
|
+
|
|
84
|
+
# Ports
|
|
85
|
+
self.inputs["r"] = None
|
|
86
|
+
self.inputs["x"] = None
|
|
87
|
+
self.outputs["u"] = None
|
|
88
|
+
|
|
89
|
+
# freeze input shapes once seen (optional but consistent)
|
|
90
|
+
self._input_shapes = {}
|
|
91
|
+
|
|
92
|
+
# --------------------------------------------------------------------------
|
|
93
|
+
# Public methods
|
|
94
|
+
# --------------------------------------------------------------------------
|
|
95
|
+
def initialize(self, t0: float):
|
|
96
|
+
r = self.inputs["r"]
|
|
97
|
+
x = self.inputs["x"]
|
|
98
|
+
if r is None or x is None:
|
|
99
|
+
self.outputs["u"] = np.zeros((self._m, 1))
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
r = self._require_col_vector("r", self._p)
|
|
104
|
+
x = self._require_col_vector("x", self._n)
|
|
105
|
+
self.outputs["u"] = self.G @ r - self.K @ x
|
|
106
|
+
except Exception as _:
|
|
107
|
+
self.outputs["u"] = np.zeros((self._m, 1))
|
|
108
|
+
|
|
109
|
+
# ------------------------------------------------------------------
|
|
110
|
+
def output_update(self, t: float, dt: float):
|
|
111
|
+
r = self._require_col_vector("r", self._p)
|
|
112
|
+
x = self._require_col_vector("x", self._n)
|
|
113
|
+
|
|
114
|
+
self.outputs["u"] = self.G @ r - self.K @ x
|
|
115
|
+
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
def state_update(self, t: float, dt: float):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# --------------------------------------------------------------------------
|
|
122
|
+
# Private methods
|
|
123
|
+
# --------------------------------------------------------------------------
|
|
124
|
+
def _require_col_vector(self, port: str, expected_rows: int) -> np.ndarray:
|
|
125
|
+
u = self.inputs[port]
|
|
126
|
+
if u is None:
|
|
127
|
+
raise RuntimeError(f"[{self.name}] Input '{port}' is not connected or not set.")
|
|
128
|
+
|
|
129
|
+
arr = np.asarray(u, dtype=float)
|
|
130
|
+
|
|
131
|
+
if arr.ndim != 2 or arr.shape[1] != 1:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
f"[{self.name}] Input '{port}' must be a column vector (n,1). Got shape {arr.shape}."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if arr.shape[0] != expected_rows:
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f"[{self.name}] Input '{port}' has wrong dimension: expected ({expected_rows},1), got {arr.shape}."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return arr
|
|
@@ -0,0 +1,27 @@
|
|
|
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 pySimBlocks.blocks.interfaces.external_input import ExternalInput
|
|
22
|
+
from pySimBlocks.blocks.interfaces.external_output import ExternalOutput
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"ExternalInput",
|
|
26
|
+
"ExternalOutput"
|
|
27
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
import numpy as np
|
|
22
|
+
from pySimBlocks.core.block import Block
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ExternalInput(Block):
|
|
26
|
+
"""
|
|
27
|
+
External input interface block.
|
|
28
|
+
|
|
29
|
+
Summary:
|
|
30
|
+
Pass-through block for external real-time measurements.
|
|
31
|
+
|
|
32
|
+
Parameters:
|
|
33
|
+
sample_time: float, optional
|
|
34
|
+
Execution period of the block. If not specified, the simulator time
|
|
35
|
+
step is used.
|
|
36
|
+
|
|
37
|
+
I/O:
|
|
38
|
+
Input:
|
|
39
|
+
in: array (n,1)
|
|
40
|
+
External measurement value. Scalar, (n,), (n,1) accepted.
|
|
41
|
+
Output:
|
|
42
|
+
out: array (n,1)
|
|
43
|
+
Measurement forwarded to the model as a column vector (n,1).
|
|
44
|
+
Policy:
|
|
45
|
+
- Accepts scalar, (n,), (n,1)
|
|
46
|
+
- Outputs strict (n,1)
|
|
47
|
+
- Once shape is known, it is frozen (cannot change)
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
direct_feedthrough = True
|
|
51
|
+
|
|
52
|
+
def __init__(self, name: str, sample_time: float | None = None):
|
|
53
|
+
super().__init__(name, sample_time)
|
|
54
|
+
self.inputs["in"] = None
|
|
55
|
+
self.outputs["out"] = None
|
|
56
|
+
self._resolved_shape: tuple[int, int] | None = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# --------------------------------------------------------------------------
|
|
60
|
+
# Public methods
|
|
61
|
+
# --------------------------------------------------------------------------
|
|
62
|
+
def initialize(self, t0: float) -> None:
|
|
63
|
+
u = self.inputs["in"]
|
|
64
|
+
if u is None:
|
|
65
|
+
self.outputs["out"] = np.zeros((1, 1))
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
self.outputs["out"] = self._to_col_vec(u)
|
|
69
|
+
|
|
70
|
+
# ------------------------------------------------------------------
|
|
71
|
+
def output_update(self, t: float, dt: float) -> None:
|
|
72
|
+
u = self.inputs["in"]
|
|
73
|
+
if u is None:
|
|
74
|
+
raise RuntimeError(f"[{self.name}] Missing input 'in'.")
|
|
75
|
+
self.outputs["out"] = self._to_col_vec(u)
|
|
76
|
+
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
def state_update(self, t: float, dt: float) -> None:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --------------------------------------------------------------------------
|
|
83
|
+
# Private methods
|
|
84
|
+
# --------------------------------------------------------------------------
|
|
85
|
+
def _to_col_vec(self, value) -> np.ndarray:
|
|
86
|
+
arr = np.asarray(value, dtype=float)
|
|
87
|
+
|
|
88
|
+
if arr.ndim == 0:
|
|
89
|
+
arr = arr.reshape(1, 1)
|
|
90
|
+
elif arr.ndim == 1:
|
|
91
|
+
arr = arr.reshape(-1, 1)
|
|
92
|
+
elif arr.ndim == 2 and arr.shape[1] == 1:
|
|
93
|
+
pass
|
|
94
|
+
else:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"[{self.name}] Input 'in' must be scalar, (n,), or (n,1). Got shape {arr.shape}."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Freeze shape
|
|
100
|
+
if self._resolved_shape is None:
|
|
101
|
+
self._resolved_shape = arr.shape
|
|
102
|
+
elif arr.shape != self._resolved_shape:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"[{self.name}] Input 'in' shape changed: expected {self._resolved_shape}, got {arr.shape}."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return arr
|