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,157 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.core import Model, Simulator, SimulationConfig
|
|
5
|
+
from pySimBlocks.blocks.sources.constant import Constant
|
|
6
|
+
from pySimBlocks.blocks.sources.step import Step
|
|
7
|
+
from pySimBlocks.blocks.operators.saturation import Saturation
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ------------------------------------------------------------
|
|
11
|
+
# Helper
|
|
12
|
+
# ------------------------------------------------------------
|
|
13
|
+
def run_sim(src_block, sat_block, dt=0.1, T=0.2):
|
|
14
|
+
m = Model()
|
|
15
|
+
m.add_block(src_block)
|
|
16
|
+
m.add_block(sat_block)
|
|
17
|
+
m.connect(src_block.name, "out", sat_block.name, "in")
|
|
18
|
+
|
|
19
|
+
sim_cfg = SimulationConfig(dt, T, logging=[f"{sat_block.name}.outputs.out"])
|
|
20
|
+
sim = Simulator(m, sim_cfg)
|
|
21
|
+
logs = sim.run()
|
|
22
|
+
return logs[f"{sat_block.name}.outputs.out"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ------------------------------------------------------------
|
|
26
|
+
# 1) Scalar saturation
|
|
27
|
+
# ------------------------------------------------------------
|
|
28
|
+
def test_saturation_scalar():
|
|
29
|
+
src = Constant("src", 5.0) # -> (1,1)
|
|
30
|
+
sat = Saturation("sat", u_min=0.0, u_max=3.0)
|
|
31
|
+
|
|
32
|
+
logs = run_sim(src, sat)
|
|
33
|
+
assert np.allclose(logs[0], [[3.0]])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ------------------------------------------------------------
|
|
37
|
+
# 2) No saturation when bounds are wide
|
|
38
|
+
# ------------------------------------------------------------
|
|
39
|
+
def test_saturation_no_effect():
|
|
40
|
+
src = Constant("src", 2.0)
|
|
41
|
+
sat = Saturation("sat", u_min=-10.0, u_max=10.0)
|
|
42
|
+
|
|
43
|
+
logs = run_sim(src, sat)
|
|
44
|
+
assert np.allclose(logs[0], [[2.0]])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ------------------------------------------------------------
|
|
48
|
+
# 3) Lower bound only
|
|
49
|
+
# ------------------------------------------------------------
|
|
50
|
+
def test_saturation_lower_only():
|
|
51
|
+
src = Constant("src", -5.0)
|
|
52
|
+
sat = Saturation("sat", u_min=-2.0)
|
|
53
|
+
|
|
54
|
+
logs = run_sim(src, sat)
|
|
55
|
+
assert np.allclose(logs[0], [[-2.0]])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ------------------------------------------------------------
|
|
59
|
+
# 4) Upper bound only
|
|
60
|
+
# ------------------------------------------------------------
|
|
61
|
+
def test_saturation_upper_only():
|
|
62
|
+
src = Constant("src", 5.0)
|
|
63
|
+
sat = Saturation("sat", u_max=1.5)
|
|
64
|
+
|
|
65
|
+
logs = run_sim(src, sat)
|
|
66
|
+
assert np.allclose(logs[0], [[1.5]])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------
|
|
70
|
+
# 5) Vector bounds on vector input (m,1)
|
|
71
|
+
# ------------------------------------------------------------
|
|
72
|
+
def test_saturation_vector():
|
|
73
|
+
src = Constant("src", [[5.0], [-5.0]]) # (2,1)
|
|
74
|
+
sat = Saturation("sat", u_min=[-1.0, -2.0], u_max=[3.0, -1.0]) # -> (2,1)
|
|
75
|
+
|
|
76
|
+
logs = run_sim(src, sat)
|
|
77
|
+
assert np.allclose(logs[0], [[3.0], [-2.0]])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ------------------------------------------------------------
|
|
81
|
+
# 6) Matrix input with scalar bounds
|
|
82
|
+
# ------------------------------------------------------------
|
|
83
|
+
def test_saturation_matrix_scalar_bounds():
|
|
84
|
+
src = Constant("src", [[5.0, -5.0],
|
|
85
|
+
[2.0, 10.0]]) # (2,2)
|
|
86
|
+
sat = Saturation("sat", u_min=0.0, u_max=3.0) # scalar -> broadcast to (2,2)
|
|
87
|
+
|
|
88
|
+
logs = run_sim(src, sat)
|
|
89
|
+
expected = np.array([[3.0, 0.0],
|
|
90
|
+
[2.0, 3.0]])
|
|
91
|
+
assert np.allclose(logs[0], expected)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ------------------------------------------------------------
|
|
95
|
+
# 7) Matrix input with vector bounds (broadcast across columns)
|
|
96
|
+
# ------------------------------------------------------------
|
|
97
|
+
def test_saturation_matrix_vector_bounds_broadcast_columns():
|
|
98
|
+
src = Constant("src", [[10.0, -10.0, 2.0],
|
|
99
|
+
[5.0, 6.0, -7.0]]) # (2,3)
|
|
100
|
+
|
|
101
|
+
# bounds as (2,1) -> broadcast across columns
|
|
102
|
+
sat = Saturation("sat", u_min=[0.0, -2.0], u_max=[3.0, 1.0])
|
|
103
|
+
|
|
104
|
+
logs = run_sim(src, sat)
|
|
105
|
+
|
|
106
|
+
# row1 clipped to [0,3], row2 clipped to [-2,1]
|
|
107
|
+
expected = np.array([[3.0, 0.0, 2.0],
|
|
108
|
+
[1.0, 1.0, -2.0]])
|
|
109
|
+
assert np.allclose(logs[0], expected)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ------------------------------------------------------------
|
|
113
|
+
# 8) Passthrough with no bounds
|
|
114
|
+
# ------------------------------------------------------------
|
|
115
|
+
def test_saturation_no_bounds():
|
|
116
|
+
src = Step("src", value_before=0.0, value_after=10.0, start_time=0.0)
|
|
117
|
+
sat = Saturation("sat")
|
|
118
|
+
|
|
119
|
+
logs = run_sim(src, sat)
|
|
120
|
+
assert np.allclose(logs[0], [[10.0]])
|
|
121
|
+
|
|
122
|
+
# ------------------------------------------------------------
|
|
123
|
+
# 9) Invalid bounds (after resolution)
|
|
124
|
+
# ------------------------------------------------------------
|
|
125
|
+
def test_saturation_bounds_componentwise_ok():
|
|
126
|
+
s = Saturation("sat", u_min=[1.0, 5.0], u_max=[2.0, 8.0])
|
|
127
|
+
s.inputs["in"] = np.array([[0.0], [10.0]])
|
|
128
|
+
s.initialize(0.0)
|
|
129
|
+
assert np.allclose(s.outputs["out"], [[1.0], [8.0]])
|
|
130
|
+
|
|
131
|
+
# ------------------------------------------------------------
|
|
132
|
+
# 10) Invalid bounds (after resolution)
|
|
133
|
+
# ------------------------------------------------------------
|
|
134
|
+
def test_saturation_invalid_bounds_scalar():
|
|
135
|
+
with pytest.raises(ValueError):
|
|
136
|
+
s = Saturation("sat", u_min=2.0, u_max=1.0)
|
|
137
|
+
s.inputs["in"] = np.array([[0.0]])
|
|
138
|
+
s.initialize(0.0)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ------------------------------------------------------------
|
|
142
|
+
# 11) Missing input at initialization
|
|
143
|
+
# ------------------------------------------------------------
|
|
144
|
+
def test_saturation_missing_input():
|
|
145
|
+
sat = Saturation("sat", u_min=0.0, u_max=1.0)
|
|
146
|
+
m = Model()
|
|
147
|
+
m.add_block(sat)
|
|
148
|
+
|
|
149
|
+
sim_cfg = SimulationConfig(0.1, 0.1)
|
|
150
|
+
sim = Simulator(m, sim_cfg)
|
|
151
|
+
|
|
152
|
+
with pytest.raises(RuntimeError):
|
|
153
|
+
sim.initialize()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
test_saturation_invalid_bounds_scalar()
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.core.model import Model
|
|
5
|
+
from pySimBlocks.core.simulator import Simulator
|
|
6
|
+
from pySimBlocks.core.config import SimulationConfig
|
|
7
|
+
from pySimBlocks.blocks.sources.constant import Constant
|
|
8
|
+
from pySimBlocks.blocks.operators.sum import Sum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ------------------------------------------------------------
|
|
12
|
+
# Helper
|
|
13
|
+
# ------------------------------------------------------------
|
|
14
|
+
def run_two_inputs(v1, v2, signs="++", dt=0.1):
|
|
15
|
+
m = Model()
|
|
16
|
+
|
|
17
|
+
s1 = Constant("s1", v1)
|
|
18
|
+
s2 = Constant("s2", v2)
|
|
19
|
+
|
|
20
|
+
m.add_block(s1)
|
|
21
|
+
m.add_block(s2)
|
|
22
|
+
|
|
23
|
+
sm = Sum("S", signs=signs)
|
|
24
|
+
m.add_block(sm)
|
|
25
|
+
|
|
26
|
+
m.connect("s1", "out", "S", "in1")
|
|
27
|
+
m.connect("s2", "out", "S", "in2")
|
|
28
|
+
|
|
29
|
+
sim_cfg = SimulationConfig(dt, dt, logging=["S.outputs.out"])
|
|
30
|
+
sim = Simulator(m, sim_cfg)
|
|
31
|
+
logs = sim.run()
|
|
32
|
+
return logs["S.outputs.out"][-1]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ------------------------------------------------------------
|
|
36
|
+
# 1) Basic sum: u1 + u2
|
|
37
|
+
# ------------------------------------------------------------
|
|
38
|
+
def test_sum_basic():
|
|
39
|
+
out = run_two_inputs([[1.0]], [[2.0]])
|
|
40
|
+
assert np.allclose(out, [[3.0]])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ------------------------------------------------------------
|
|
44
|
+
# 2) Subtraction: u1 - u2
|
|
45
|
+
# ------------------------------------------------------------
|
|
46
|
+
def test_sum_subtraction():
|
|
47
|
+
out = run_two_inputs([[5.0]], [[3.0]], signs="+-")
|
|
48
|
+
assert np.allclose(out, [[2.0]])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------
|
|
52
|
+
# 3) Automatic default signs = ++
|
|
53
|
+
# ------------------------------------------------------------
|
|
54
|
+
def test_sum_default_signs():
|
|
55
|
+
sm = Sum("S")
|
|
56
|
+
assert sm.signs == [1.0, 1.0]
|
|
57
|
+
assert sm.num_inputs == 2
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ------------------------------------------------------------
|
|
61
|
+
# 4) Matrix sum supported
|
|
62
|
+
# ------------------------------------------------------------
|
|
63
|
+
def test_sum_matrix_supported():
|
|
64
|
+
u1 = np.array([[1.0, 2.0],
|
|
65
|
+
[3.0, 4.0]])
|
|
66
|
+
u2 = np.array([[10.0, 20.0],
|
|
67
|
+
[30.0, 40.0]])
|
|
68
|
+
out = run_two_inputs(u1, u2, signs="++")
|
|
69
|
+
assert np.allclose(out, u1 + u2)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ------------------------------------------------------------
|
|
73
|
+
# 5) Scalar broadcast to matrix supported (only (1,1) allowed)
|
|
74
|
+
# ------------------------------------------------------------
|
|
75
|
+
def test_sum_scalar_broadcast_to_matrix():
|
|
76
|
+
u1 = np.array([[1.0, 2.0],
|
|
77
|
+
[3.0, 4.0]])
|
|
78
|
+
u2 = np.array([[5.0]]) # scalar (1,1) broadcast
|
|
79
|
+
out = run_two_inputs(u1, u2, signs="++")
|
|
80
|
+
assert np.allclose(out, u1 + 5.0)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ------------------------------------------------------------
|
|
84
|
+
# 6) Shape mismatch among non-scalars must raise RuntimeError
|
|
85
|
+
# (sim wraps ValueError into RuntimeError)
|
|
86
|
+
# ------------------------------------------------------------
|
|
87
|
+
def test_sum_shape_mismatch_non_scalars():
|
|
88
|
+
m = Model()
|
|
89
|
+
|
|
90
|
+
s1 = Constant("s1", np.zeros((2, 2)))
|
|
91
|
+
s2 = Constant("s2", np.zeros((2, 3))) # mismatch
|
|
92
|
+
|
|
93
|
+
sm = Sum("S", "++")
|
|
94
|
+
m.add_block(s1)
|
|
95
|
+
m.add_block(s2)
|
|
96
|
+
m.add_block(sm)
|
|
97
|
+
|
|
98
|
+
m.connect("s1", "out", "S", "in1")
|
|
99
|
+
m.connect("s2", "out", "S", "in2")
|
|
100
|
+
|
|
101
|
+
dt = 0.1
|
|
102
|
+
sim_cfg = SimulationConfig(dt, dt, logging=["S.outputs.out"])
|
|
103
|
+
sim = Simulator(m, sim_cfg)
|
|
104
|
+
|
|
105
|
+
with pytest.raises(RuntimeError) as err:
|
|
106
|
+
sim.run(T=0.1)
|
|
107
|
+
|
|
108
|
+
assert "Inconsistent input shapes for Sum" in str(err.value)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------------------------
|
|
112
|
+
# 7) Vector-to-matrix implicit broadcast is NOT allowed
|
|
113
|
+
# ------------------------------------------------------------
|
|
114
|
+
def test_sum_vector_to_matrix_not_allowed():
|
|
115
|
+
m = Model()
|
|
116
|
+
|
|
117
|
+
s1 = Constant("s1", np.zeros((2, 2))) # non-scalar target shape
|
|
118
|
+
s2 = Constant("s2", np.zeros((2, 1))) # non-scalar but different shape (not scalar)
|
|
119
|
+
|
|
120
|
+
sm = Sum("S", "++")
|
|
121
|
+
m.add_block(s1)
|
|
122
|
+
m.add_block(s2)
|
|
123
|
+
m.add_block(sm)
|
|
124
|
+
|
|
125
|
+
m.connect("s1", "out", "S", "in1")
|
|
126
|
+
m.connect("s2", "out", "S", "in2")
|
|
127
|
+
|
|
128
|
+
dt = 0.1
|
|
129
|
+
sim_cfg = SimulationConfig(dt, dt, logging=["S.outputs.out"])
|
|
130
|
+
sim = Simulator(m, sim_cfg)
|
|
131
|
+
|
|
132
|
+
with pytest.raises(RuntimeError) as err:
|
|
133
|
+
sim.run(T=0.1)
|
|
134
|
+
|
|
135
|
+
# depending on where it fails, message can be either "Inconsistent input shapes..."
|
|
136
|
+
# or "Only scalar (1,1) inputs can be broadcast."
|
|
137
|
+
assert (
|
|
138
|
+
"Inconsistent input shapes for Sum" in str(err.value)
|
|
139
|
+
or "Only scalar (1,1) inputs can be broadcast" in str(err.value)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ------------------------------------------------------------
|
|
144
|
+
# 8) Invalid signs list
|
|
145
|
+
# ------------------------------------------------------------
|
|
146
|
+
def test_sum_invalid_signs():
|
|
147
|
+
with pytest.raises(ValueError):
|
|
148
|
+
Sum("S", signs="+/") # invalid character
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ------------------------------------------------------------
|
|
152
|
+
# 9) Inferred num_inputs from signs only
|
|
153
|
+
# ------------------------------------------------------------
|
|
154
|
+
def test_sum_infer_num_inputs_from_signs():
|
|
155
|
+
sm = Sum("S", signs="+")
|
|
156
|
+
assert sm.num_inputs == 1
|
|
157
|
+
assert sm.signs == [1.0]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ------------------------------------------------------------
|
|
161
|
+
# 10) Check output initialization (inputs connected)
|
|
162
|
+
# ------------------------------------------------------------
|
|
163
|
+
def test_sum_initial_output():
|
|
164
|
+
m = Model()
|
|
165
|
+
|
|
166
|
+
s1 = Constant("s1", [[1.0]])
|
|
167
|
+
s2 = Constant("s2", [[2.0]])
|
|
168
|
+
|
|
169
|
+
sm = Sum("S", signs="++")
|
|
170
|
+
|
|
171
|
+
m.add_block(s1)
|
|
172
|
+
m.add_block(s2)
|
|
173
|
+
m.add_block(sm)
|
|
174
|
+
|
|
175
|
+
m.connect("s1", "out", "S", "in1")
|
|
176
|
+
m.connect("s2", "out", "S", "in2")
|
|
177
|
+
|
|
178
|
+
dt = 0.1
|
|
179
|
+
sim_cfg = SimulationConfig(dt, dt, logging=["S.outputs.out"])
|
|
180
|
+
sim = Simulator(m, sim_cfg)
|
|
181
|
+
sim.initialize(0.0)
|
|
182
|
+
|
|
183
|
+
assert np.allclose(sm.outputs["out"], [[3.0]])
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.core import Model, Simulator, SimulationConfig
|
|
5
|
+
from pySimBlocks.blocks.sources.sinusoidal import Sinusoidal
|
|
6
|
+
from pySimBlocks.blocks.sources.constant import Constant
|
|
7
|
+
from pySimBlocks.blocks.operators.zero_order_hold import ZeroOrderHold
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_sim(src, block, dt=0.01, T=0.1):
|
|
11
|
+
model = Model("test")
|
|
12
|
+
model.add_block(src)
|
|
13
|
+
model.add_block(block)
|
|
14
|
+
model.connect(src.name, "out", block.name, "in")
|
|
15
|
+
|
|
16
|
+
sim_cfg = SimulationConfig(dt, T, logging=[f"{block.name}.outputs.out"])
|
|
17
|
+
sim = Simulator(model, sim_cfg)
|
|
18
|
+
logs = sim.run()
|
|
19
|
+
return logs[f"{block.name}.outputs.out"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
def test_zoh_passthrough_first_sample():
|
|
25
|
+
src = Constant("src", 3.0)
|
|
26
|
+
zoh = ZeroOrderHold("zoh", sample_time=0.1)
|
|
27
|
+
|
|
28
|
+
logs = run_sim(src, zoh, dt=0.01, T=0.03)
|
|
29
|
+
|
|
30
|
+
assert np.allclose(logs[0], [[3.0]])
|
|
31
|
+
assert np.allclose(logs[1], [[3.0]])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
def test_zoh_hold_behavior():
|
|
37
|
+
"""
|
|
38
|
+
sample_time=0.05s, dt=0.01s -> update expected at k=0,5,10...
|
|
39
|
+
We check:
|
|
40
|
+
- values constant between k=0..4
|
|
41
|
+
- change occurs at k=5 (very likely for sinusoidal)
|
|
42
|
+
"""
|
|
43
|
+
src = Sinusoidal("src", amplitude=1.0, frequency=1.0, offset=0.0, phase=0.0)
|
|
44
|
+
zoh = ZeroOrderHold("zoh", sample_time=0.05)
|
|
45
|
+
|
|
46
|
+
logs = run_sim(src, zoh, dt=0.01, T=0.11)
|
|
47
|
+
|
|
48
|
+
# held constant over [0..4]
|
|
49
|
+
for k in range(1, 5):
|
|
50
|
+
assert np.allclose(logs[k], logs[0])
|
|
51
|
+
|
|
52
|
+
# at k=5, new sample should be taken (may still coincidentally match,
|
|
53
|
+
# but extremely unlikely; we keep a strict "not allclose" check)
|
|
54
|
+
assert not np.allclose(logs[5], logs[0])
|
|
55
|
+
|
|
56
|
+
# held constant over [5..9]
|
|
57
|
+
for k in range(6, 10):
|
|
58
|
+
assert np.allclose(logs[k], logs[5])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
def test_zoh_vector_signal():
|
|
64
|
+
src = Constant("src", [[1.0], [2.0]])
|
|
65
|
+
zoh = ZeroOrderHold("zoh", sample_time=0.05)
|
|
66
|
+
|
|
67
|
+
logs = run_sim(src, zoh, dt=0.01, T=0.03)
|
|
68
|
+
|
|
69
|
+
assert np.allclose(logs[0], [[1.0], [2.0]])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def test_zoh_matrix_signal():
|
|
75
|
+
src = Constant("src", [[1.0, 2.0],
|
|
76
|
+
[3.0, 4.0]])
|
|
77
|
+
zoh = ZeroOrderHold("zoh", sample_time=0.05)
|
|
78
|
+
|
|
79
|
+
logs = run_sim(src, zoh, dt=0.01, T=0.03)
|
|
80
|
+
|
|
81
|
+
assert np.allclose(logs[0], [[1.0, 2.0], [3.0, 4.0]])
|
|
82
|
+
assert logs[0].shape == (2, 2)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
def test_zoh_shape_change_raises():
|
|
88
|
+
# block-only: ZOH must reject shape changes once initialized
|
|
89
|
+
zoh = ZeroOrderHold("zoh", sample_time=0.05)
|
|
90
|
+
|
|
91
|
+
zoh.inputs["in"] = np.array([[1.0]]) # (1,1)
|
|
92
|
+
zoh.initialize(0.0)
|
|
93
|
+
|
|
94
|
+
zoh.inputs["in"] = np.array([[1.0, 2.0],
|
|
95
|
+
[3.0, 4.0]]) # (2,2)
|
|
96
|
+
with pytest.raises(ValueError) as err:
|
|
97
|
+
zoh.output_update(0.01, 0.01)
|
|
98
|
+
|
|
99
|
+
assert "shape" in str(err.value).lower()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
def test_zoh_invalid_sample_time():
|
|
105
|
+
with pytest.raises(ValueError):
|
|
106
|
+
ZeroOrderHold("zoh", sample_time=0.0)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.blocks.sources.chirp import Chirp
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# ----------------------------------------------------------
|
|
8
|
+
# 1) Scalar parameters
|
|
9
|
+
# ----------------------------------------------------------
|
|
10
|
+
def test_chirp_scalar_at_start():
|
|
11
|
+
c = Chirp(
|
|
12
|
+
"c",
|
|
13
|
+
amplitude=2.0,
|
|
14
|
+
f0=1.0,
|
|
15
|
+
f1=5.0,
|
|
16
|
+
duration=2.0,
|
|
17
|
+
offset=0.5,
|
|
18
|
+
phase=0.0,
|
|
19
|
+
)
|
|
20
|
+
c.initialize(0.0)
|
|
21
|
+
# tau = 0 -> sin(0)=0
|
|
22
|
+
assert np.allclose(c.outputs["out"], [[0.5]])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_chirp_scalar_mid_sweep():
|
|
26
|
+
c = Chirp("c", 1.0, 1.0, 3.0, 2.0)
|
|
27
|
+
c.output_update(1.0, 0.1)
|
|
28
|
+
|
|
29
|
+
# expected phase manually
|
|
30
|
+
k = (3.0 - 1.0) / 2.0
|
|
31
|
+
tau = 1.0
|
|
32
|
+
phi = 2 * np.pi * (1.0 * tau + 0.5 * k * tau**2)
|
|
33
|
+
expected = np.sin(phi)
|
|
34
|
+
|
|
35
|
+
assert np.allclose(c.outputs["out"], [[expected]])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ----------------------------------------------------------
|
|
39
|
+
# 2) After sweep duration (constant frequency f1)
|
|
40
|
+
# ----------------------------------------------------------
|
|
41
|
+
def test_chirp_after_duration_continuity():
|
|
42
|
+
c = Chirp("c", 1.0, 1.0, 3.0, 2.0)
|
|
43
|
+
|
|
44
|
+
c.output_update(2.0, 0.1) # end of sweep
|
|
45
|
+
y_end = c.outputs["out"].copy()
|
|
46
|
+
|
|
47
|
+
c.output_update(3.0, 0.1) # after sweep
|
|
48
|
+
y_after = c.outputs["out"]
|
|
49
|
+
|
|
50
|
+
# just verify finite and defined (continuity handled in block)
|
|
51
|
+
assert np.isfinite(y_end).all()
|
|
52
|
+
assert np.isfinite(y_after).all()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ----------------------------------------------------------
|
|
56
|
+
# 3) Vector parameters
|
|
57
|
+
# ----------------------------------------------------------
|
|
58
|
+
def test_chirp_vector_parameters():
|
|
59
|
+
c = Chirp(
|
|
60
|
+
"c",
|
|
61
|
+
amplitude=[1.0, 2.0],
|
|
62
|
+
f0=[1.0, 2.0],
|
|
63
|
+
f1=[3.0, 4.0],
|
|
64
|
+
duration=[2.0, 2.0],
|
|
65
|
+
offset=[0.0, 10.0],
|
|
66
|
+
phase=[0.0, 0.0],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
c.output_update(0.0, 0.1)
|
|
70
|
+
|
|
71
|
+
# tau=0 -> sin(0)=0
|
|
72
|
+
assert np.allclose(c.outputs["out"], [[0.0], [10.0]])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ----------------------------------------------------------
|
|
76
|
+
# 4) Broadcasting scalar (allowed only (1,1))
|
|
77
|
+
# ----------------------------------------------------------
|
|
78
|
+
def test_chirp_broadcast_scalar_duration():
|
|
79
|
+
c = Chirp(
|
|
80
|
+
"c",
|
|
81
|
+
amplitude=[1.0, 1.0, 1.0],
|
|
82
|
+
f0=[1.0, 2.0, 3.0],
|
|
83
|
+
f1=[2.0, 3.0, 4.0],
|
|
84
|
+
duration=2.0, # scalar broadcast
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
c.output_update(0.5, 0.1)
|
|
88
|
+
assert c.outputs["out"].shape == (3, 1)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ----------------------------------------------------------
|
|
92
|
+
# 5) Matrix parameters (Option B)
|
|
93
|
+
# ----------------------------------------------------------
|
|
94
|
+
def test_chirp_matrix_same_shape():
|
|
95
|
+
A = np.array([[1.0, 2.0],
|
|
96
|
+
[3.0, 4.0]])
|
|
97
|
+
F0 = np.array([[1.0, 1.0],
|
|
98
|
+
[1.0, 1.0]])
|
|
99
|
+
F1 = np.array([[2.0, 2.0],
|
|
100
|
+
[2.0, 2.0]])
|
|
101
|
+
D = np.array([[2.0, 2.0],
|
|
102
|
+
[2.0, 2.0]])
|
|
103
|
+
|
|
104
|
+
c = Chirp("c", amplitude=A, f0=F0, f1=F1, duration=D)
|
|
105
|
+
c.output_update(0.0, 0.1)
|
|
106
|
+
|
|
107
|
+
assert np.allclose(c.outputs["out"], np.zeros((2, 2)))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_chirp_matrix_broadcast_scalar_offset():
|
|
111
|
+
A = np.array([[1.0, 2.0],
|
|
112
|
+
[3.0, 4.0]])
|
|
113
|
+
F0 = np.ones((2, 2))
|
|
114
|
+
F1 = 2 * np.ones((2, 2))
|
|
115
|
+
D = 2 * np.ones((2, 2))
|
|
116
|
+
|
|
117
|
+
c = Chirp("c", amplitude=A, f0=F0, f1=F1, duration=D, offset=5.0)
|
|
118
|
+
c.output_update(0.0, 0.1)
|
|
119
|
+
|
|
120
|
+
assert np.allclose(c.outputs["out"], np.full((2, 2), 5.0))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ----------------------------------------------------------
|
|
124
|
+
# 6) Inconsistent shapes among non-scalars -> error
|
|
125
|
+
# ----------------------------------------------------------
|
|
126
|
+
def test_chirp_inconsistent_shapes():
|
|
127
|
+
with pytest.raises(ValueError):
|
|
128
|
+
Chirp(
|
|
129
|
+
"c",
|
|
130
|
+
amplitude=np.zeros((2, 2)),
|
|
131
|
+
f0=np.zeros((2, 3)), # mismatch
|
|
132
|
+
f1=1.0,
|
|
133
|
+
duration=1.0,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ----------------------------------------------------------
|
|
138
|
+
# 7) Illegal ndim (>2) -> error
|
|
139
|
+
# ----------------------------------------------------------
|
|
140
|
+
def test_chirp_illegal_ndim():
|
|
141
|
+
with pytest.raises(ValueError):
|
|
142
|
+
Chirp(
|
|
143
|
+
"c",
|
|
144
|
+
amplitude=np.zeros((2, 2, 2)),
|
|
145
|
+
f0=1.0,
|
|
146
|
+
f1=2.0,
|
|
147
|
+
duration=1.0,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ----------------------------------------------------------
|
|
152
|
+
# 8) Invalid duration (<=0) -> error
|
|
153
|
+
# ----------------------------------------------------------
|
|
154
|
+
def test_chirp_invalid_duration():
|
|
155
|
+
with pytest.raises(ValueError):
|
|
156
|
+
Chirp("c", 1.0, 1.0, 2.0, duration=0.0)
|
|
157
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pySimBlocks.blocks.sources.constant import Constant
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# ------------------------------------------------------------
|
|
8
|
+
# 1) Test valeur simple (scalaire)
|
|
9
|
+
# ------------------------------------------------------------
|
|
10
|
+
def test_constant_scalar():
|
|
11
|
+
c = Constant("c", 5.0)
|
|
12
|
+
c.initialize(0.0)
|
|
13
|
+
assert np.allclose(c.outputs["out"], [[5.0]])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ------------------------------------------------------------
|
|
17
|
+
# 2) Test vecteur colonne (n,1)
|
|
18
|
+
# ------------------------------------------------------------
|
|
19
|
+
def test_constant_vector():
|
|
20
|
+
c = Constant("c", [[1.0], [2.0], [3.0]])
|
|
21
|
+
c.initialize(0.0)
|
|
22
|
+
assert np.allclose(c.outputs["out"], [[1.0], [2.0], [3.0]])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ------------------------------------------------------------
|
|
26
|
+
# 3) Test reshape depuis vecteur ligne ou liste 1D
|
|
27
|
+
# ------------------------------------------------------------
|
|
28
|
+
def test_constant_1d_list():
|
|
29
|
+
c = Constant("c", [10.0, 20.0])
|
|
30
|
+
c.initialize(0.0)
|
|
31
|
+
assert np.allclose(c.outputs["out"], [[10.0], [20.0]])
|
|
32
|
+
|
|
33
|
+
# ------------------------------------------------------------
|
|
34
|
+
# 4) Test matrice (m,n)
|
|
35
|
+
# ------------------------------------------------------------
|
|
36
|
+
def test_constant_matrix():
|
|
37
|
+
c = Constant("c", [[1.0, 2.0], [3.0, 4.0]])
|
|
38
|
+
c.initialize(0.0)
|
|
39
|
+
assert np.allclose(c.outputs["out"], [[1.0, 2.0], [3.0, 4.0]])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ------------------------------------------------------------
|
|
43
|
+
# 5) Test erreur : valeur non numérique
|
|
44
|
+
# ------------------------------------------------------------
|
|
45
|
+
def test_constant_invalid_type():
|
|
46
|
+
with pytest.raises(TypeError):
|
|
47
|
+
Constant("c", value="not numeric")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ------------------------------------------------------------
|
|
51
|
+
# 6) Test erreur : mauvaise forme (simulateur ne joue pas de rôle ici)
|
|
52
|
+
# ------------------------------------------------------------
|
|
53
|
+
def test_constant_bad_shape():
|
|
54
|
+
with pytest.raises(ValueError):
|
|
55
|
+
Constant("c", value=np.zeros((2, 3, 2)))
|