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,167 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.core import Model, Simulator, SimulationConfig
|
|
5
|
+
from pySimBlocks.blocks.sources.step import Step
|
|
6
|
+
from pySimBlocks.blocks.operators.delay import Delay
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ------------------------------------------------------------
|
|
10
|
+
def run_sim(value_before, value_after, delay_block, dt=0.1, T_mult=3):
|
|
11
|
+
m = Model()
|
|
12
|
+
|
|
13
|
+
src = Step("src", start_time=0.1, value_before=value_before, value_after=value_after)
|
|
14
|
+
m.add_block(src)
|
|
15
|
+
m.add_block(delay_block)
|
|
16
|
+
m.connect("src", "out", delay_block.name, "in")
|
|
17
|
+
|
|
18
|
+
sim_cfg = SimulationConfig(dt, T_mult * dt, logging=[f"{delay_block.name}.outputs.out"])
|
|
19
|
+
sim = Simulator(m, sim_cfg)
|
|
20
|
+
logs = sim.run()
|
|
21
|
+
return logs[f"{delay_block.name}.outputs.out"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ------------------------------------------------------------
|
|
25
|
+
def test_delay_scalar_1_step():
|
|
26
|
+
d = Delay("D", num_delays=1)
|
|
27
|
+
logs = run_sim([[0.0]], [[5.0]], d)
|
|
28
|
+
|
|
29
|
+
assert np.allclose(logs[0], [[0.0]])
|
|
30
|
+
assert np.allclose(logs[1], [[0.0]])
|
|
31
|
+
assert np.allclose(logs[2], [[5.0]])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ------------------------------------------------------------
|
|
35
|
+
def test_delay_scalar_2_steps():
|
|
36
|
+
d = Delay("D", num_delays=2)
|
|
37
|
+
logs = run_sim([[0.0]], [[1.0]], d)
|
|
38
|
+
|
|
39
|
+
assert np.allclose(logs[0], [[0.0]])
|
|
40
|
+
assert np.allclose(logs[1], [[0.0]])
|
|
41
|
+
assert np.allclose(logs[2], [[0.0]])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------
|
|
45
|
+
def test_delay_with_initial_output_scalar():
|
|
46
|
+
# initial_output scalar is allowed and should be used at t=0
|
|
47
|
+
d = Delay("D", num_delays=2, initial_output=[[7.0]])
|
|
48
|
+
logs = run_sim([[0.0]], [[1.0]], d)
|
|
49
|
+
|
|
50
|
+
assert np.allclose(logs[0], [[7.0]])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ------------------------------------------------------------
|
|
54
|
+
def test_delay_initial_output_scalar_broadcast_to_matrix():
|
|
55
|
+
# initial_output scalar can be broadcast to match matrix input shape
|
|
56
|
+
d = Delay("D", num_delays=1, initial_output=[[2.0]])
|
|
57
|
+
|
|
58
|
+
value_before = [[0.0, 0.0],
|
|
59
|
+
[0.0, 0.0]]
|
|
60
|
+
value_after = [[5.0, 6.0],
|
|
61
|
+
[7.0, 8.0]]
|
|
62
|
+
|
|
63
|
+
logs = run_sim(value_before, value_after, d)
|
|
64
|
+
|
|
65
|
+
# buffer at t=0 uses scalar init broadcasted to (2,2)
|
|
66
|
+
assert np.allclose(logs[0], np.full((2, 2), 2.0))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------
|
|
70
|
+
def test_delay_with_initial_output_matrix():
|
|
71
|
+
init = [[1.0, 2.0],
|
|
72
|
+
[3.0, 4.0]]
|
|
73
|
+
d = Delay("D", num_delays=2, initial_output=init)
|
|
74
|
+
|
|
75
|
+
value_before = init
|
|
76
|
+
value_after = [[10.0, 20.0],
|
|
77
|
+
[30.0, 40.0]]
|
|
78
|
+
|
|
79
|
+
logs = run_sim(value_before, value_after, d)
|
|
80
|
+
|
|
81
|
+
assert np.allclose(logs[0], np.array(init))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ------------------------------------------------------------
|
|
85
|
+
def test_delay_dimension_mismatch_initial_output_non_scalar_vs_input():
|
|
86
|
+
# initial_output non-scalar fixes buffer shape; if input shape differs -> error
|
|
87
|
+
d = Delay("D", num_delays=1, initial_output=[[1.0], [2.0]]) # shape (2, 1)
|
|
88
|
+
|
|
89
|
+
m = Model()
|
|
90
|
+
src = Step("src", start_time=0.1, value_before=[[0.0], [0.0], [0.]],
|
|
91
|
+
value_after=[[1.0], [1.0], [1.]]) # shape (3, 1)
|
|
92
|
+
m.add_block(src)
|
|
93
|
+
m.add_block(d)
|
|
94
|
+
m.connect("src", "out", "D", "in")
|
|
95
|
+
|
|
96
|
+
sim_cfg = SimulationConfig(0.1, 0.2, logging=["D.outputs.out"])
|
|
97
|
+
sim = Simulator(m, sim_cfg)
|
|
98
|
+
|
|
99
|
+
with pytest.raises(RuntimeError) as err:
|
|
100
|
+
sim.run()
|
|
101
|
+
|
|
102
|
+
assert "shape" in str(err.value) and "expected" in str(err.value)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ------------------------------------------------------------
|
|
106
|
+
def test_delay_uninitialized_buffer_error():
|
|
107
|
+
m = Model()
|
|
108
|
+
d = Delay("D", num_delays=1)
|
|
109
|
+
m.add_block(d)
|
|
110
|
+
|
|
111
|
+
sim_cfg = SimulationConfig(0.1, 0.1)
|
|
112
|
+
sim = Simulator(m, sim_cfg)
|
|
113
|
+
|
|
114
|
+
with pytest.raises(RuntimeError):
|
|
115
|
+
sim.run(T=0.1)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ------------------------------------------------------------
|
|
119
|
+
def test_delay_vector_1_step():
|
|
120
|
+
value_before = [[0.0], [0.0]]
|
|
121
|
+
value_after = [[5.0], [10.0]]
|
|
122
|
+
|
|
123
|
+
d = Delay("D", num_delays=1)
|
|
124
|
+
logs = run_sim(value_before, value_after, d)
|
|
125
|
+
|
|
126
|
+
expected0 = np.array([[0.0], [0.0]])
|
|
127
|
+
expected1 = np.array([[0.0], [0.0]])
|
|
128
|
+
expected2 = np.array([[5.0], [10.0]])
|
|
129
|
+
|
|
130
|
+
assert np.allclose(logs[0], expected0)
|
|
131
|
+
assert np.allclose(logs[1], expected1)
|
|
132
|
+
assert np.allclose(logs[2], expected2)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ------------------------------------------------------------
|
|
136
|
+
def test_delay_matrix_1_step():
|
|
137
|
+
value_before = [[0.0, 0.0],
|
|
138
|
+
[0.0, 0.0]]
|
|
139
|
+
value_after = [[1.0, 2.0],
|
|
140
|
+
[3.0, 4.0]]
|
|
141
|
+
|
|
142
|
+
d = Delay("D", num_delays=1)
|
|
143
|
+
logs = run_sim(value_before, value_after, d)
|
|
144
|
+
|
|
145
|
+
expected0 = np.array(value_before)
|
|
146
|
+
expected1 = np.array(value_before)
|
|
147
|
+
expected2 = np.array(value_after)
|
|
148
|
+
|
|
149
|
+
assert np.allclose(logs[0], expected0)
|
|
150
|
+
assert np.allclose(logs[1], expected1)
|
|
151
|
+
assert np.allclose(logs[2], expected2)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ------------------------------------------------------------
|
|
155
|
+
def test_delay_input_shape_change_over_time_raises():
|
|
156
|
+
# First phase: scalar, second phase: matrix (shape change) -> should raise
|
|
157
|
+
d = Delay("D", num_delays=1)
|
|
158
|
+
|
|
159
|
+
d.initialize(0.0)
|
|
160
|
+
d.inputs["in"] = np.array([[1.0]]) # scalar input
|
|
161
|
+
d.state_update(0.0, 0.1)
|
|
162
|
+
d.inputs["in"] = np.array([[1.0, 2.0], # shape change to (2,2)
|
|
163
|
+
[3.0, 4.0]])
|
|
164
|
+
with pytest.raises(ValueError) as err:
|
|
165
|
+
d.state_update(0.1, 0.1)
|
|
166
|
+
|
|
167
|
+
assert "shape" in str(err.value) and "expected" in str(err.value)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.blocks.operators.demux import Demux
|
|
5
|
+
from pySimBlocks.blocks.sources.constant import Constant
|
|
6
|
+
from pySimBlocks.core import Model, SimulationConfig, Simulator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_sim(value, num_outputs):
|
|
10
|
+
m = Model()
|
|
11
|
+
|
|
12
|
+
c = Constant("c", value)
|
|
13
|
+
dmx = Demux("D", num_outputs=num_outputs)
|
|
14
|
+
|
|
15
|
+
m.add_block(c)
|
|
16
|
+
m.add_block(dmx)
|
|
17
|
+
m.connect("c", "out", "D", "in")
|
|
18
|
+
|
|
19
|
+
logs = [f"D.outputs.out{i+1}" for i in range(num_outputs)]
|
|
20
|
+
sim_cfg = SimulationConfig(0.1, 0.1, logging=logs)
|
|
21
|
+
sim = Simulator(m, sim_cfg)
|
|
22
|
+
data = sim.run()
|
|
23
|
+
return [data[key][-1] for key in logs]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_demux_basic_column_vector():
|
|
27
|
+
outputs = run_sim([[1.0], [2.0], [3.0]], num_outputs=3)
|
|
28
|
+
assert np.allclose(outputs[0], [[1.0]])
|
|
29
|
+
assert np.allclose(outputs[1], [[2.0]])
|
|
30
|
+
assert np.allclose(outputs[2], [[3.0]])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_demux_equal_split_when_divisible():
|
|
34
|
+
outputs = run_sim([[1.0], [2.0], [3.0], [4.0]], num_outputs=2)
|
|
35
|
+
assert np.allclose(outputs[0], [[1.0], [2.0]])
|
|
36
|
+
assert np.allclose(outputs[1], [[3.0], [4.0]])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_demux_remainder_split_rule():
|
|
40
|
+
# n=5, p=3 => q=1, m=2
|
|
41
|
+
# out1/out2 have 2 elems, out3 has 1 elem
|
|
42
|
+
outputs = run_sim([[1.0], [2.0], [3.0], [4.0], [5.0]], num_outputs=3)
|
|
43
|
+
assert np.allclose(outputs[0], [[1.0], [2.0]])
|
|
44
|
+
assert np.allclose(outputs[1], [[3.0], [4.0]])
|
|
45
|
+
assert np.allclose(outputs[2], [[5.0]])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_demux_invalid_parameter():
|
|
49
|
+
with pytest.raises(ValueError) as err:
|
|
50
|
+
Demux("D", num_outputs=0)
|
|
51
|
+
assert "num_outputs must be a positive integer" in str(err.value)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_demux_missing_input_raises():
|
|
55
|
+
m = Model()
|
|
56
|
+
dmx = Demux("D", num_outputs=2)
|
|
57
|
+
m.add_block(dmx)
|
|
58
|
+
|
|
59
|
+
sim_cfg = SimulationConfig(0.1, 0.1)
|
|
60
|
+
sim = Simulator(m, sim_cfg)
|
|
61
|
+
|
|
62
|
+
with pytest.raises(RuntimeError) as err:
|
|
63
|
+
sim.run(T=0.1)
|
|
64
|
+
|
|
65
|
+
assert "not connected or not set" in str(err.value)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_demux_rejects_matrix_input():
|
|
69
|
+
m = Model()
|
|
70
|
+
c = Constant("c", [[1.0, 2.0], [3.0, 4.0]])
|
|
71
|
+
dmx = Demux("D", num_outputs=4)
|
|
72
|
+
|
|
73
|
+
m.add_block(c)
|
|
74
|
+
m.add_block(dmx)
|
|
75
|
+
m.connect("c", "out", "D", "in")
|
|
76
|
+
|
|
77
|
+
sim_cfg = SimulationConfig(0.1, 0.1)
|
|
78
|
+
sim = Simulator(m, sim_cfg)
|
|
79
|
+
|
|
80
|
+
with pytest.raises(RuntimeError) as err:
|
|
81
|
+
sim.run(T=0.1)
|
|
82
|
+
|
|
83
|
+
assert "must be a column vector (n,1)" in str(err.value)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_demux_rejects_1d_input():
|
|
87
|
+
dmx = Demux("D", num_outputs=1)
|
|
88
|
+
dmx.inputs["in"] = np.array([5.0])
|
|
89
|
+
|
|
90
|
+
with pytest.raises(ValueError) as err:
|
|
91
|
+
dmx.output_update(t=0.0, dt=0.1)
|
|
92
|
+
|
|
93
|
+
assert "must be a column vector (n,1)" in str(err.value)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_demux_rejects_more_outputs_than_input_length():
|
|
97
|
+
m = Model()
|
|
98
|
+
c = Constant("c", [[1.0], [2.0]])
|
|
99
|
+
dmx = Demux("D", num_outputs=3)
|
|
100
|
+
|
|
101
|
+
m.add_block(c)
|
|
102
|
+
m.add_block(dmx)
|
|
103
|
+
m.connect("c", "out", "D", "in")
|
|
104
|
+
|
|
105
|
+
sim_cfg = SimulationConfig(0.1, 0.1)
|
|
106
|
+
sim = Simulator(m, sim_cfg)
|
|
107
|
+
|
|
108
|
+
with pytest.raises(RuntimeError) as err:
|
|
109
|
+
sim.run(T=0.1)
|
|
110
|
+
|
|
111
|
+
assert "must be <= input vector length" in str(err.value)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.core import Model, Simulator, SimulationConfig
|
|
5
|
+
from pySimBlocks.blocks.sources.step import Step
|
|
6
|
+
from pySimBlocks.blocks.sources.constant import Constant
|
|
7
|
+
from pySimBlocks.blocks.operators.discrete_derivator import DiscreteDerivator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ------------------------------------------------------------
|
|
11
|
+
def run_sim(src_block, der_block, dt=0.1, T=0.3):
|
|
12
|
+
m = Model()
|
|
13
|
+
m.add_block(src_block)
|
|
14
|
+
m.add_block(der_block)
|
|
15
|
+
m.connect(src_block.name, "out", der_block.name, "in")
|
|
16
|
+
|
|
17
|
+
sim_cfg = SimulationConfig(dt, T, logging=[f"{der_block.name}.outputs.out"])
|
|
18
|
+
sim = Simulator(m, sim_cfg)
|
|
19
|
+
logs = sim.run()
|
|
20
|
+
return logs[f"{der_block.name}.outputs.out"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ------------------------------------------------------------
|
|
24
|
+
def test_derivator_scalar_basic_step():
|
|
25
|
+
"""
|
|
26
|
+
Step: value_before=0 until t=0.1, then 1.
|
|
27
|
+
dt=0.1
|
|
28
|
+
|
|
29
|
+
Expected derivative (backward difference):
|
|
30
|
+
first output_update: 0 (placeholder)
|
|
31
|
+
then at t=0.1: (1-0)/0.1 = 10
|
|
32
|
+
then: 0
|
|
33
|
+
"""
|
|
34
|
+
src = Step("src", start_time=0.1, value_before=[[0.0]], value_after=[[1.0]])
|
|
35
|
+
D = DiscreteDerivator("D")
|
|
36
|
+
|
|
37
|
+
logs = run_sim(src, D, dt=0.1, T=0.3)
|
|
38
|
+
|
|
39
|
+
assert np.allclose(logs[0], [[0.0]])
|
|
40
|
+
assert np.allclose(logs[1], [[10.0]])
|
|
41
|
+
assert np.allclose(logs[2], [[0.0]])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------
|
|
45
|
+
def test_derivator_vector_step():
|
|
46
|
+
src = Step(
|
|
47
|
+
"src",
|
|
48
|
+
start_time=0.1,
|
|
49
|
+
value_before=[[0.0], [0.0]],
|
|
50
|
+
value_after=[[2.0], [4.0]],
|
|
51
|
+
)
|
|
52
|
+
D = DiscreteDerivator("D")
|
|
53
|
+
|
|
54
|
+
logs = run_sim(src, D, dt=0.1, T=0.3)
|
|
55
|
+
|
|
56
|
+
assert np.allclose(logs[0], [[0.0], [0.0]])
|
|
57
|
+
assert np.allclose(logs[1], [[20.0], [40.0]])
|
|
58
|
+
assert np.allclose(logs[2], [[0.0], [0.0]])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------
|
|
62
|
+
def test_derivator_matrix_constant_is_zero():
|
|
63
|
+
src = Constant("src", [[1.0, 2.0], [3.0, 4.0]])
|
|
64
|
+
D = DiscreteDerivator("D")
|
|
65
|
+
|
|
66
|
+
logs = run_sim(src, D, dt=0.1, T=0.3)
|
|
67
|
+
|
|
68
|
+
# first call: 0 placeholder upgraded to matrix on first non-scalar input
|
|
69
|
+
assert logs[0].shape == (2, 2)
|
|
70
|
+
assert np.allclose(logs[0], np.zeros((2, 2)))
|
|
71
|
+
|
|
72
|
+
# constant input -> derivative zero
|
|
73
|
+
assert np.allclose(logs[1], np.zeros((2, 2)))
|
|
74
|
+
assert np.allclose(logs[2], np.zeros((2, 2)))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ------------------------------------------------------------
|
|
78
|
+
def test_derivator_initial_output_freezes_shape():
|
|
79
|
+
"""
|
|
80
|
+
initial_output fixes the shape (including scalar).
|
|
81
|
+
If input later has a different shape -> error.
|
|
82
|
+
"""
|
|
83
|
+
D = DiscreteDerivator("D", initial_output=[[5.0]]) # freezes to (1,1)
|
|
84
|
+
src = Constant("src", [[1.0, 2.0], [3.0, 4.0]]) # (2,2) -> mismatch
|
|
85
|
+
|
|
86
|
+
m = Model()
|
|
87
|
+
m.add_block(src)
|
|
88
|
+
m.add_block(D)
|
|
89
|
+
m.connect("src", "out", "D", "in")
|
|
90
|
+
|
|
91
|
+
sim_cfg = SimulationConfig(0.1, 0.2, logging=["D.outputs.out"])
|
|
92
|
+
sim = Simulator(m, sim_cfg)
|
|
93
|
+
|
|
94
|
+
with pytest.raises(RuntimeError) as err:
|
|
95
|
+
sim.run()
|
|
96
|
+
|
|
97
|
+
assert "shape" in str(err.value).lower()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------
|
|
101
|
+
def test_derivator_missing_input_never_none_output():
|
|
102
|
+
"""
|
|
103
|
+
If the block is alone / never receives input, it must not propagate None.
|
|
104
|
+
It will just keep (1,1) zeros (placeholder).
|
|
105
|
+
"""
|
|
106
|
+
D = DiscreteDerivator("D")
|
|
107
|
+
m = Model()
|
|
108
|
+
m.add_block(D)
|
|
109
|
+
|
|
110
|
+
sim_cfg = SimulationConfig(0.1, 0.2, logging=["D.outputs.out"])
|
|
111
|
+
sim = Simulator(m, sim_cfg)
|
|
112
|
+
|
|
113
|
+
logs = sim.run()
|
|
114
|
+
assert np.allclose(logs["D.outputs.out"][0], [[0.0]])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ------------------------------------------------------------
|
|
118
|
+
def test_derivator_shape_upgrade_from_placeholder_unit():
|
|
119
|
+
"""
|
|
120
|
+
Unit test without simulator:
|
|
121
|
+
- Start with no input -> placeholder output (1,1)
|
|
122
|
+
- Then feed matrix -> shape freezes and u_prev set to avoid spike
|
|
123
|
+
"""
|
|
124
|
+
D = DiscreteDerivator("D")
|
|
125
|
+
D.initialize(0.0)
|
|
126
|
+
|
|
127
|
+
# First output_update with missing input -> should keep placeholder, not crash
|
|
128
|
+
D.inputs["in"] = None
|
|
129
|
+
D.output_update(0.0, 0.1)
|
|
130
|
+
assert np.allclose(D.outputs["out"], [[0.0]])
|
|
131
|
+
|
|
132
|
+
# Now provide a matrix; first call after upgrade keeps 0 (but shape becomes (2,2))
|
|
133
|
+
D.inputs["in"] = np.array([[1.0, 2.0], [3.0, 4.0]])
|
|
134
|
+
D.output_update(0.1, 0.1)
|
|
135
|
+
assert D.outputs["out"].shape == (2, 2)
|
|
136
|
+
assert np.allclose(D.outputs["out"], np.zeros((2, 2)))
|
|
137
|
+
|
|
138
|
+
# Second call with same matrix -> derivative zero
|
|
139
|
+
D.state_update(0.1, 0.1)
|
|
140
|
+
D.state["u_prev"] = D.next_state["u_prev"].copy()
|
|
141
|
+
|
|
142
|
+
D.output_update(0.2, 0.1)
|
|
143
|
+
assert np.allclose(D.outputs["out"], np.zeros((2, 2)))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ------------------------------------------------------------
|
|
147
|
+
def test_derivator_shape_change_after_freeze_raises_unit():
|
|
148
|
+
D = DiscreteDerivator("D")
|
|
149
|
+
D.initialize(0.0)
|
|
150
|
+
|
|
151
|
+
# First: provide matrix -> freezes
|
|
152
|
+
D.inputs["in"] = np.array([[1.0, 2.0], [3.0, 4.0]])
|
|
153
|
+
D.output_update(0.0, 0.1)
|
|
154
|
+
|
|
155
|
+
# Now attempt incompatible shape
|
|
156
|
+
D.inputs["in"] = np.array([[1.0], [2.0], [3.0]])
|
|
157
|
+
with pytest.raises(ValueError) as err:
|
|
158
|
+
D.output_update(0.1, 0.1)
|
|
159
|
+
|
|
160
|
+
assert "shape" in str(err.value).lower()
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.core import Model, Simulator, SimulationConfig
|
|
5
|
+
from pySimBlocks.blocks.sources.step import Step
|
|
6
|
+
from pySimBlocks.blocks.sources.constant import Constant
|
|
7
|
+
from pySimBlocks.blocks.operators.discrete_integrator import DiscreteIntegrator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_sim(src_block, integrator_block, dt=0.1, T=0.3):
|
|
11
|
+
m = Model()
|
|
12
|
+
m.add_block(src_block)
|
|
13
|
+
m.add_block(integrator_block)
|
|
14
|
+
m.connect(src_block.name, "out", integrator_block.name, "in")
|
|
15
|
+
|
|
16
|
+
sim_cfg = SimulationConfig(dt, T, logging=[f"{integrator_block.name}.outputs.out"])
|
|
17
|
+
sim = Simulator(m, sim_cfg)
|
|
18
|
+
logs = sim.run()
|
|
19
|
+
return logs[f"{integrator_block.name}.outputs.out"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ----------------------------------------------------------------------
|
|
23
|
+
# 1) FORWARD EULER — SCALAR
|
|
24
|
+
# ----------------------------------------------------------------------
|
|
25
|
+
def test_integrator_scalar_forward():
|
|
26
|
+
src = Step("src", start_time=0.1, value_before=0.0, value_after=1.0)
|
|
27
|
+
I = DiscreteIntegrator("I", method="euler forward")
|
|
28
|
+
|
|
29
|
+
logs = run_sim(src, I, dt=0.1, T=0.3)
|
|
30
|
+
|
|
31
|
+
# forward: y[k] = x[k], x[k+1] = x[k] + dt*u[k]
|
|
32
|
+
# u: 0,1,1 -> y: 0,0,0.1
|
|
33
|
+
assert np.allclose(logs[0], [[0.0]])
|
|
34
|
+
assert np.allclose(logs[1], [[0.0]])
|
|
35
|
+
assert np.allclose(logs[2], [[0.1]])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ----------------------------------------------------------------------
|
|
39
|
+
# 2) FORWARD EULER — VECTOR
|
|
40
|
+
# ----------------------------------------------------------------------
|
|
41
|
+
def test_integrator_vector_forward():
|
|
42
|
+
src = Step(
|
|
43
|
+
"src",
|
|
44
|
+
start_time=0.1,
|
|
45
|
+
value_before=[[0.0], [0.0]],
|
|
46
|
+
value_after=[[1.0], [2.0]],
|
|
47
|
+
)
|
|
48
|
+
I = DiscreteIntegrator("I", method="euler forward")
|
|
49
|
+
|
|
50
|
+
logs = run_sim(src, I, dt=0.1, T=0.3)
|
|
51
|
+
|
|
52
|
+
assert np.allclose(logs[0], [[0.0], [0.0]])
|
|
53
|
+
assert np.allclose(logs[1], [[0.0], [0.0]])
|
|
54
|
+
assert np.allclose(logs[2], [[0.1], [0.2]])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ----------------------------------------------------------------------
|
|
58
|
+
# 3) BACKWARD EULER — SCALAR
|
|
59
|
+
# ----------------------------------------------------------------------
|
|
60
|
+
def test_integrator_scalar_backward():
|
|
61
|
+
src = Step("src", start_time=0.1, value_before=0.0, value_after=1.0)
|
|
62
|
+
I = DiscreteIntegrator("I", method="euler backward")
|
|
63
|
+
|
|
64
|
+
logs = run_sim(src, I, dt=0.1, T=0.3)
|
|
65
|
+
|
|
66
|
+
# backward output: y = x + dt*u (with x updated in state_update)
|
|
67
|
+
# u: 0,1,1
|
|
68
|
+
# y0 = 0 + 0.1*0 = 0
|
|
69
|
+
# y1 = 0 + 0.1*1 = 0.1
|
|
70
|
+
# y2 = 0.1 + 0.1*1 = 0.2
|
|
71
|
+
assert np.allclose(logs[0], [[0.0]])
|
|
72
|
+
assert np.allclose(logs[1], [[0.1]])
|
|
73
|
+
assert np.allclose(logs[2], [[0.2]])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ----------------------------------------------------------------------
|
|
77
|
+
# 4) INITIAL STATE — FREEZES SHAPE (NON-SCALAR)
|
|
78
|
+
# ----------------------------------------------------------------------
|
|
79
|
+
def test_integrator_initial_state_matrix_freezes_shape():
|
|
80
|
+
src = Constant("src", [[1.0, 2.0], [3.0, 4.0]])
|
|
81
|
+
I = DiscreteIntegrator("I", initial_state=np.zeros((2, 2)), method="euler forward")
|
|
82
|
+
|
|
83
|
+
logs = run_sim(src, I, dt=0.1, T=0.2)
|
|
84
|
+
|
|
85
|
+
# forward: y = x, with x0 fixed
|
|
86
|
+
assert np.allclose(logs[0], np.zeros((2, 2)))
|
|
87
|
+
assert np.allclose(logs[1], 0.1 * np.array([[1.0, 2.0], [3.0, 4.0]]))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ----------------------------------------------------------------------
|
|
91
|
+
# 5) PLACEHOLDER (1,1) MUST NOT FREEZE; UPGRADE ON FIRST NON-SCALAR
|
|
92
|
+
# ----------------------------------------------------------------------
|
|
93
|
+
def test_integrator_placeholder_upgrades_to_matrix_on_first_non_scalar_input():
|
|
94
|
+
# Step goes from scalar -> matrix; scalar should NOT freeze the integrator if no initial_state
|
|
95
|
+
src = Step(
|
|
96
|
+
"src",
|
|
97
|
+
start_time=0.1,
|
|
98
|
+
value_before=[[0.0]], # scalar placeholder
|
|
99
|
+
value_after=[[1.0, 2.0], [3.0, 4.0]], # matrix
|
|
100
|
+
)
|
|
101
|
+
I = DiscreteIntegrator("I", method="euler forward")
|
|
102
|
+
|
|
103
|
+
logs = run_sim(src, I, dt=0.1, T=0.3)
|
|
104
|
+
|
|
105
|
+
# At t=0: output placeholder (1,1) is fine
|
|
106
|
+
assert np.allclose(logs[0], [[0.0]])
|
|
107
|
+
|
|
108
|
+
# After step: integrator should upgrade and integrate matrix
|
|
109
|
+
# forward y1 is still x0 (after upgrade, still zero matrix)
|
|
110
|
+
# y2 is dt*u1
|
|
111
|
+
assert logs[1].shape == (2, 2)
|
|
112
|
+
assert np.allclose(logs[1], np.zeros((2, 2)))
|
|
113
|
+
assert np.allclose(logs[2], 0.1 * np.array([[1.0, 2.0], [3.0, 4.0]]))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ----------------------------------------------------------------------
|
|
117
|
+
# 6) SHAPE CHANGE AFTER FREEZE MUST RAISE
|
|
118
|
+
# ----------------------------------------------------------------------
|
|
119
|
+
def test_integrator_shape_change_after_freeze_raises():
|
|
120
|
+
# Freeze to (2,2) via initial_state, then feed (3,1)
|
|
121
|
+
src = Constant("src", [[1.0], [2.0], [3.0]])
|
|
122
|
+
I = DiscreteIntegrator("I", initial_state=np.zeros((2, 2)), method="euler backward")
|
|
123
|
+
|
|
124
|
+
m = Model()
|
|
125
|
+
m.add_block(src)
|
|
126
|
+
m.add_block(I)
|
|
127
|
+
m.connect("src", "out", "I", "in")
|
|
128
|
+
|
|
129
|
+
sim_cfg = SimulationConfig(0.1, 0.2, logging=["I.outputs.out"])
|
|
130
|
+
sim = Simulator(m, sim_cfg)
|
|
131
|
+
|
|
132
|
+
with pytest.raises(ValueError) as err:
|
|
133
|
+
sim.run()
|
|
134
|
+
|
|
135
|
+
assert "shape" in str(err.value).lower()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ----------------------------------------------------------------------
|
|
139
|
+
# 7) SCALAR INPUT (1,1) MUST BROADCAST WHEN SHAPE IS FROZEN TO MATRIX
|
|
140
|
+
# ----------------------------------------------------------------------
|
|
141
|
+
def test_integrator_scalar_broadcast_into_frozen_matrix_shape():
|
|
142
|
+
src = Constant("src", [[2.0]]) # scalar (1,1)
|
|
143
|
+
I = DiscreteIntegrator("I", initial_state=np.zeros((2, 2)), method="euler backward")
|
|
144
|
+
|
|
145
|
+
logs = run_sim(src, I, dt=0.1, T=0.2)
|
|
146
|
+
|
|
147
|
+
# backward: y = x + dt*u, with u broadcast to 2x2
|
|
148
|
+
assert logs[0].shape == (2, 2)
|
|
149
|
+
assert np.allclose(logs[0], 0.1 * 2.0 * np.ones((2, 2)))
|