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.
Files changed (262) hide show
  1. examples/advanced/hardware/block_diagram.py +162 -0
  2. examples/advanced/hardware/camera.py +93 -0
  3. examples/advanced/hardware/gui_emio.py +231 -0
  4. examples/advanced/hardware/lmi.py +69 -0
  5. examples/advanced/hardware/main.py +57 -0
  6. examples/advanced/hardware/parameters.py +54 -0
  7. examples/advanced/qp_problem/params.py +10 -0
  8. examples/advanced/qp_problem/run.py +14 -0
  9. examples/advanced/sofa/gui/finger/Finger.py +108 -0
  10. examples/advanced/sofa/gui/finger/FingerController.py +53 -0
  11. examples/advanced/sofa/gui/finger/parameters_auto.py +22 -0
  12. examples/advanced/sofa/gui/sofa_plant/run.py +14 -0
  13. examples/advanced/sofa/python/Finger.py +107 -0
  14. examples/advanced/sofa/python/FingerController.py +48 -0
  15. examples/advanced/sofa/python/closedloop.py +64 -0
  16. examples/advanced/sofa/python/openloop.py +58 -0
  17. examples/basics/algebraic_function/g.py +7 -0
  18. examples/basics/algebraic_function/run.py +14 -0
  19. examples/basics/dc_motor/gui/run.py +14 -0
  20. examples/basics/dc_motor/multiple_order.py +91 -0
  21. examples/basics/dc_motor/openloop.py +62 -0
  22. examples/basics/dc_motor/pi.py +99 -0
  23. examples/basics/dc_motor/pid.py +118 -0
  24. examples/basics/dc_motor/pid_block.py +82 -0
  25. examples/basics/dc_motor/pid_block_vs_manual.py +143 -0
  26. examples/basics/external/parameters.py +4 -0
  27. examples/basics/external/run.py +14 -0
  28. examples/basics/file_sources/temp.py +16 -0
  29. examples/basics/observer.py +76 -0
  30. examples/basics/state_feedback/gui/parameters.py +9 -0
  31. examples/basics/state_feedback/gui/run.py +14 -0
  32. examples/basics/state_feedback/state_feedback.py +102 -0
  33. examples/basics/state_space/linear/gui/run.py +14 -0
  34. examples/basics/state_space/linear/ss_block.py +89 -0
  35. examples/basics/state_space/linear/ss_manual.py +105 -0
  36. examples/basics/state_space/non_linear/nlss.py +47 -0
  37. examples/basics/state_space/non_linear/run.py +14 -0
  38. examples/basics/state_space/polytope/params.py +39 -0
  39. examples/basics/state_space/polytope/weights.py +12 -0
  40. examples/comparison/simulink/convert.py +29 -0
  41. examples/comparison/simulink/openloop.py +127 -0
  42. examples/comparison/simulink/pi.py +148 -0
  43. examples/comparison/simulink/pid.py +154 -0
  44. examples/comparison/simulink/pid_backward.py +155 -0
  45. examples/quick_start/filter.py +35 -0
  46. examples/quick_start/gui/run.py +14 -0
  47. examples/tutorials/tutorial_1_python/main.py +99 -0
  48. examples/tutorials/tutorial_3_sofa/finger/Finger.py +120 -0
  49. examples/tutorials/tutorial_3_sofa/finger/FingerController.py +37 -0
  50. pySimBlocks/__init__.py +35 -0
  51. pySimBlocks/blocks/__init__.py +68 -0
  52. pySimBlocks/blocks/controllers/__init__.py +27 -0
  53. pySimBlocks/blocks/controllers/pid.py +250 -0
  54. pySimBlocks/blocks/controllers/state_feedback.py +141 -0
  55. pySimBlocks/blocks/interfaces/__init__.py +27 -0
  56. pySimBlocks/blocks/interfaces/external_input.py +107 -0
  57. pySimBlocks/blocks/interfaces/external_output.py +113 -0
  58. pySimBlocks/blocks/observers/__init__.py +23 -0
  59. pySimBlocks/blocks/observers/luenberger.py +175 -0
  60. pySimBlocks/blocks/operators/__init__.py +47 -0
  61. pySimBlocks/blocks/operators/algebraic_function.py +258 -0
  62. pySimBlocks/blocks/operators/dead_zone.py +186 -0
  63. pySimBlocks/blocks/operators/delay.py +254 -0
  64. pySimBlocks/blocks/operators/demux.py +123 -0
  65. pySimBlocks/blocks/operators/discrete_derivator.py +237 -0
  66. pySimBlocks/blocks/operators/discrete_integrator.py +265 -0
  67. pySimBlocks/blocks/operators/gain.py +294 -0
  68. pySimBlocks/blocks/operators/mux.py +131 -0
  69. pySimBlocks/blocks/operators/product.py +194 -0
  70. pySimBlocks/blocks/operators/rate_limiter.py +214 -0
  71. pySimBlocks/blocks/operators/saturation.py +178 -0
  72. pySimBlocks/blocks/operators/sum.py +188 -0
  73. pySimBlocks/blocks/operators/zero_order_hold.py +141 -0
  74. pySimBlocks/blocks/optimizers/__init__.py +23 -0
  75. pySimBlocks/blocks/optimizers/quadratic_program.py +326 -0
  76. pySimBlocks/blocks/sources/__init__.py +37 -0
  77. pySimBlocks/blocks/sources/chirp.py +168 -0
  78. pySimBlocks/blocks/sources/constant.py +82 -0
  79. pySimBlocks/blocks/sources/file_source.py +294 -0
  80. pySimBlocks/blocks/sources/function_source.py +197 -0
  81. pySimBlocks/blocks/sources/ramp.py +99 -0
  82. pySimBlocks/blocks/sources/sinusoidal.py +113 -0
  83. pySimBlocks/blocks/sources/step.py +138 -0
  84. pySimBlocks/blocks/sources/white_noise.py +107 -0
  85. pySimBlocks/blocks/systems/__init__.py +27 -0
  86. pySimBlocks/blocks/systems/linear_state_space.py +180 -0
  87. pySimBlocks/blocks/systems/non_linear_state_space.py +309 -0
  88. pySimBlocks/blocks/systems/polytopic_state_space.py +193 -0
  89. pySimBlocks/blocks/systems/sofa/__init__.py +30 -0
  90. pySimBlocks/blocks/systems/sofa/sofa_controller.py +472 -0
  91. pySimBlocks/blocks/systems/sofa/sofa_exchange_i_o.py +120 -0
  92. pySimBlocks/blocks/systems/sofa/sofa_plant.py +306 -0
  93. pySimBlocks/cli.py +127 -0
  94. pySimBlocks/core/__init__.py +32 -0
  95. pySimBlocks/core/block.py +167 -0
  96. pySimBlocks/core/block_source.py +94 -0
  97. pySimBlocks/core/config.py +90 -0
  98. pySimBlocks/core/fixed_time_manager.py +46 -0
  99. pySimBlocks/core/model.py +250 -0
  100. pySimBlocks/core/scheduler.py +30 -0
  101. pySimBlocks/core/simulator.py +329 -0
  102. pySimBlocks/core/task.py +73 -0
  103. pySimBlocks/docs/blocks/controllers/pid.md +78 -0
  104. pySimBlocks/docs/blocks/controllers/state_feedback.md +60 -0
  105. pySimBlocks/docs/blocks/interfaces/external_input.md +39 -0
  106. pySimBlocks/docs/blocks/interfaces/external_output.md +39 -0
  107. pySimBlocks/docs/blocks/observers/luenberger.md +70 -0
  108. pySimBlocks/docs/blocks/operators/algebraic_function.md +76 -0
  109. pySimBlocks/docs/blocks/operators/dead_zone.md +67 -0
  110. pySimBlocks/docs/blocks/operators/delay.md +76 -0
  111. pySimBlocks/docs/blocks/operators/demux.md +64 -0
  112. pySimBlocks/docs/blocks/operators/discrete_derivator.md +61 -0
  113. pySimBlocks/docs/blocks/operators/discrete_integrator.md +87 -0
  114. pySimBlocks/docs/blocks/operators/gain.md +97 -0
  115. pySimBlocks/docs/blocks/operators/mux.md +64 -0
  116. pySimBlocks/docs/blocks/operators/product.md +51 -0
  117. pySimBlocks/docs/blocks/operators/rate_limiter.md +70 -0
  118. pySimBlocks/docs/blocks/operators/saturation.md +62 -0
  119. pySimBlocks/docs/blocks/operators/sum.md +49 -0
  120. pySimBlocks/docs/blocks/operators/zero_order_hold.md +61 -0
  121. pySimBlocks/docs/blocks/optimizers/quadratic_program.md +98 -0
  122. pySimBlocks/docs/blocks/sources/chirp.md +102 -0
  123. pySimBlocks/docs/blocks/sources/constant.md +53 -0
  124. pySimBlocks/docs/blocks/sources/file_source.md +51 -0
  125. pySimBlocks/docs/blocks/sources/function_source.md +52 -0
  126. pySimBlocks/docs/blocks/sources/ramp.md +58 -0
  127. pySimBlocks/docs/blocks/sources/sinusoidal.md +60 -0
  128. pySimBlocks/docs/blocks/sources/step.md +63 -0
  129. pySimBlocks/docs/blocks/sources/white_noise.md +58 -0
  130. pySimBlocks/docs/blocks/systems/linear_state_space.md +66 -0
  131. pySimBlocks/docs/blocks/systems/non_linear_state_space.md +90 -0
  132. pySimBlocks/docs/blocks/systems/polytopic_state_space.md +72 -0
  133. pySimBlocks/docs/blocks/systems/sofa/sofa_exchange_i_o.md +63 -0
  134. pySimBlocks/docs/blocks/systems/sofa/sofa_plant.md +62 -0
  135. pySimBlocks/gui/__init__.py +20 -0
  136. pySimBlocks/gui/addons/__init__.py +19 -0
  137. pySimBlocks/gui/addons/sofa/__init__.py +19 -0
  138. pySimBlocks/gui/addons/sofa/sofa_dialog.py +190 -0
  139. pySimBlocks/gui/addons/sofa/sofa_service.py +209 -0
  140. pySimBlocks/gui/blocks/block_dialog_session.py +47 -0
  141. pySimBlocks/gui/blocks/block_meta.py +408 -0
  142. pySimBlocks/gui/blocks/controllers/pid.py +116 -0
  143. pySimBlocks/gui/blocks/controllers/state_feedback.py +80 -0
  144. pySimBlocks/gui/blocks/interfaces/external_input.py +64 -0
  145. pySimBlocks/gui/blocks/interfaces/external_output.py +63 -0
  146. pySimBlocks/gui/blocks/observers/luenberger.py +108 -0
  147. pySimBlocks/gui/blocks/operators/algebraic_function.py +232 -0
  148. pySimBlocks/gui/blocks/operators/dead_zone.py +79 -0
  149. pySimBlocks/gui/blocks/operators/delay.py +79 -0
  150. pySimBlocks/gui/blocks/operators/demux.py +98 -0
  151. pySimBlocks/gui/blocks/operators/discrete_derivator.py +67 -0
  152. pySimBlocks/gui/blocks/operators/discrete_integrator.py +80 -0
  153. pySimBlocks/gui/blocks/operators/gain.py +79 -0
  154. pySimBlocks/gui/blocks/operators/mux.py +100 -0
  155. pySimBlocks/gui/blocks/operators/product.py +109 -0
  156. pySimBlocks/gui/blocks/operators/rate_limiter.py +79 -0
  157. pySimBlocks/gui/blocks/operators/saturation.py +72 -0
  158. pySimBlocks/gui/blocks/operators/sum.py +91 -0
  159. pySimBlocks/gui/blocks/operators/zero_order_hold.py +64 -0
  160. pySimBlocks/gui/blocks/optimizers/quadratic_program.py +128 -0
  161. pySimBlocks/gui/blocks/parameter_meta.py +34 -0
  162. pySimBlocks/gui/blocks/port_meta.py +31 -0
  163. pySimBlocks/gui/blocks/sources/chirp.py +146 -0
  164. pySimBlocks/gui/blocks/sources/constant.py +63 -0
  165. pySimBlocks/gui/blocks/sources/file_source.py +196 -0
  166. pySimBlocks/gui/blocks/sources/function_source.py +202 -0
  167. pySimBlocks/gui/blocks/sources/ramp.py +73 -0
  168. pySimBlocks/gui/blocks/sources/sinusoidal.py +81 -0
  169. pySimBlocks/gui/blocks/sources/step.py +83 -0
  170. pySimBlocks/gui/blocks/sources/white_noise.py +70 -0
  171. pySimBlocks/gui/blocks/systems/linear_state_space.py +102 -0
  172. pySimBlocks/gui/blocks/systems/non_linear_state_space.py +233 -0
  173. pySimBlocks/gui/blocks/systems/polytopic_state_space.py +109 -0
  174. pySimBlocks/gui/blocks/systems/sofa/sofa_exchange_i_o.py +221 -0
  175. pySimBlocks/gui/blocks/systems/sofa/sofa_plant.py +220 -0
  176. pySimBlocks/gui/dialogs/__init__.py +19 -0
  177. pySimBlocks/gui/dialogs/block_dialog.py +125 -0
  178. pySimBlocks/gui/dialogs/display_yaml_dialog.py +89 -0
  179. pySimBlocks/gui/dialogs/help_dialog.py +39 -0
  180. pySimBlocks/gui/dialogs/plot_dialog.py +230 -0
  181. pySimBlocks/gui/dialogs/settings/plots.py +187 -0
  182. pySimBlocks/gui/dialogs/settings/project.py +146 -0
  183. pySimBlocks/gui/dialogs/settings/simulation.py +137 -0
  184. pySimBlocks/gui/dialogs/settings_dialog.py +88 -0
  185. pySimBlocks/gui/dialogs/unsaved_dialog.py +92 -0
  186. pySimBlocks/gui/editor.py +49 -0
  187. pySimBlocks/gui/graphics/__init__.py +29 -0
  188. pySimBlocks/gui/graphics/block_item.py +289 -0
  189. pySimBlocks/gui/graphics/connection_item.py +266 -0
  190. pySimBlocks/gui/graphics/port_item.py +153 -0
  191. pySimBlocks/gui/graphics/theme.py +119 -0
  192. pySimBlocks/gui/main_window.py +162 -0
  193. pySimBlocks/gui/models/__init__.py +31 -0
  194. pySimBlocks/gui/models/block_instance.py +114 -0
  195. pySimBlocks/gui/models/connection_instance.py +43 -0
  196. pySimBlocks/gui/models/port_instance.py +52 -0
  197. pySimBlocks/gui/models/project_simulation_params.py +50 -0
  198. pySimBlocks/gui/models/project_state.py +114 -0
  199. pySimBlocks/gui/project_controller.py +302 -0
  200. pySimBlocks/gui/services/__init__.py +20 -0
  201. pySimBlocks/gui/services/project_loader.py +381 -0
  202. pySimBlocks/gui/services/project_saver.py +68 -0
  203. pySimBlocks/gui/services/simulation_runner.py +66 -0
  204. pySimBlocks/gui/services/yaml_tools.py +261 -0
  205. pySimBlocks/gui/widgets/__init__.py +19 -0
  206. pySimBlocks/gui/widgets/block_list.py +131 -0
  207. pySimBlocks/gui/widgets/diagram_view.py +312 -0
  208. pySimBlocks/gui/widgets/toolbar_view.py +161 -0
  209. pySimBlocks/project/__init__.py +35 -0
  210. pySimBlocks/project/build_model.py +92 -0
  211. pySimBlocks/project/generate_run_script.py +109 -0
  212. pySimBlocks/project/generate_sofa_controller.py +215 -0
  213. pySimBlocks/project/load_project_config.py +197 -0
  214. pySimBlocks/project/load_simulation_config.py +166 -0
  215. pySimBlocks/project/load_simulator.py +45 -0
  216. pySimBlocks/project/plot_from_config.py +174 -0
  217. pySimBlocks/project/pySimBlocks_blocks_index.yaml +106 -0
  218. pySimBlocks/real_time/__init__.py +23 -0
  219. pySimBlocks/real_time/real_time_runner.py +148 -0
  220. pySimBlocks/tools/__init__.py +19 -0
  221. pySimBlocks/tools/blocks_registry.py +141 -0
  222. pySimBlocks/tools/generate_blocks_index.py +124 -0
  223. pysimblocks-0.1.0.dist-info/METADATA +180 -0
  224. pysimblocks-0.1.0.dist-info/RECORD +262 -0
  225. pysimblocks-0.1.0.dist-info/WHEEL +5 -0
  226. pysimblocks-0.1.0.dist-info/entry_points.txt +2 -0
  227. pysimblocks-0.1.0.dist-info/licenses/LICENSE.md +165 -0
  228. pysimblocks-0.1.0.dist-info/top_level.txt +3 -0
  229. tests/blocks/controllers/test_pid.py +143 -0
  230. tests/blocks/controllers/test_state_feedback.py +193 -0
  231. tests/blocks/observers/test_luenberger.py +225 -0
  232. tests/blocks/operators/test_algebraic_function.py +230 -0
  233. tests/blocks/operators/test_dead_zone.py +106 -0
  234. tests/blocks/operators/test_delay.py +167 -0
  235. tests/blocks/operators/test_demux.py +111 -0
  236. tests/blocks/operators/test_discrete_derivator.py +160 -0
  237. tests/blocks/operators/test_discrete_integrator.py +149 -0
  238. tests/blocks/operators/test_gain.py +182 -0
  239. tests/blocks/operators/test_mux.py +101 -0
  240. tests/blocks/operators/test_product.py +171 -0
  241. tests/blocks/operators/test_rate_limiter.py +166 -0
  242. tests/blocks/operators/test_saturation.py +157 -0
  243. tests/blocks/operators/test_sum.py +183 -0
  244. tests/blocks/operators/test_zero_order_hold.py +106 -0
  245. tests/blocks/sources/test_chirp.py +157 -0
  246. tests/blocks/sources/test_constant.py +55 -0
  247. tests/blocks/sources/test_file_source.py +185 -0
  248. tests/blocks/sources/test_function_source.py +135 -0
  249. tests/blocks/sources/test_ramp.py +110 -0
  250. tests/blocks/sources/test_sinusoidal.py +113 -0
  251. tests/blocks/sources/test_step.py +76 -0
  252. tests/blocks/sources/test_white_noise.py +108 -0
  253. tests/blocks/systems/test_linear_state_space.py +135 -0
  254. tests/blocks/systems/test_polytopic_state_space.py +133 -0
  255. tests/core/test_core_errors_and_determinism.py +82 -0
  256. tests/core/test_core_multirate_tasks.py +88 -0
  257. tests/core/test_core_two_phase_and_propagation.py +132 -0
  258. tests/gui/main_window_open_test.py +162 -0
  259. tests/gui/test_block_meta_param_cache.py +16 -0
  260. tests/gui/test_file_source_meta.py +17 -0
  261. tests/gui/test_function_source_meta.py +28 -0
  262. 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)))