pathsim 0.2.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.
- pathsim/__init__.py +3 -0
- pathsim/blocks/__init__.py +14 -0
- pathsim/blocks/_block.py +209 -0
- pathsim/blocks/adder.py +30 -0
- pathsim/blocks/amplifier.py +34 -0
- pathsim/blocks/delay.py +70 -0
- pathsim/blocks/differentiator.py +70 -0
- pathsim/blocks/function.py +82 -0
- pathsim/blocks/integrator.py +66 -0
- pathsim/blocks/lti.py +155 -0
- pathsim/blocks/multiplier.py +30 -0
- pathsim/blocks/ode.py +86 -0
- pathsim/blocks/rf/__init__.py +4 -0
- pathsim/blocks/rf/filters.py +169 -0
- pathsim/blocks/rf/noise.py +218 -0
- pathsim/blocks/rf/sources.py +163 -0
- pathsim/blocks/rf/wienerhammerstein.py +338 -0
- pathsim/blocks/rng.py +57 -0
- pathsim/blocks/scope.py +224 -0
- pathsim/blocks/sources.py +71 -0
- pathsim/blocks/spectrum.py +316 -0
- pathsim/connection.py +112 -0
- pathsim/simulation.py +652 -0
- pathsim/solvers/__init__.py +25 -0
- pathsim/solvers/_solver.py +403 -0
- pathsim/solvers/bdf.py +240 -0
- pathsim/solvers/dirk2.py +101 -0
- pathsim/solvers/dirk3.py +86 -0
- pathsim/solvers/esdirk32.py +131 -0
- pathsim/solvers/esdirk4.py +99 -0
- pathsim/solvers/esdirk43.py +139 -0
- pathsim/solvers/esdirk54.py +141 -0
- pathsim/solvers/esdirk85.py +200 -0
- pathsim/solvers/euler.py +81 -0
- pathsim/solvers/rk4.py +61 -0
- pathsim/solvers/rkbs32.py +101 -0
- pathsim/solvers/rkck54.py +108 -0
- pathsim/solvers/rkdp54.py +111 -0
- pathsim/solvers/rkdp87.py +116 -0
- pathsim/solvers/rkf45.py +102 -0
- pathsim/solvers/rkf78.py +111 -0
- pathsim/solvers/rkv65.py +103 -0
- pathsim/solvers/ssprk22.py +62 -0
- pathsim/solvers/ssprk33.py +65 -0
- pathsim/solvers/ssprk34.py +74 -0
- pathsim/subsystem.py +267 -0
- pathsim/utils/__init__.py +0 -0
- pathsim/utils/adaptivebuffer.py +87 -0
- pathsim/utils/anderson.py +180 -0
- pathsim/utils/funcs.py +205 -0
- pathsim/utils/gilbert.py +110 -0
- pathsim/utils/progresstracker.py +90 -0
- pathsim/utils/realtimeplotter.py +230 -0
- pathsim/utils/statespacerealizations.py +116 -0
- pathsim/utils/waveforms.py +36 -0
- pathsim-0.2.0.dist-info/LICENSE.txt +21 -0
- pathsim-0.2.0.dist-info/METADATA +149 -0
- pathsim-0.2.0.dist-info/RECORD +109 -0
- pathsim-0.2.0.dist-info/WHEEL +5 -0
- pathsim-0.2.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/blocks/__init__.py +0 -0
- tests/blocks/test_adder.py +85 -0
- tests/blocks/test_amplifier.py +66 -0
- tests/blocks/test_block.py +138 -0
- tests/blocks/test_delay.py +122 -0
- tests/blocks/test_differentiator.py +102 -0
- tests/blocks/test_function.py +165 -0
- tests/blocks/test_integrator.py +92 -0
- tests/blocks/test_lti.py +162 -0
- tests/blocks/test_multiplier.py +87 -0
- tests/blocks/test_ode.py +125 -0
- tests/blocks/test_rng.py +109 -0
- tests/blocks/test_scope.py +196 -0
- tests/blocks/test_sources.py +119 -0
- tests/blocks/test_spectrum.py +119 -0
- tests/solvers/__init__.py +0 -0
- tests/solvers/test_bdf.py +364 -0
- tests/solvers/test_dirk2.py +138 -0
- tests/solvers/test_dirk3.py +137 -0
- tests/solvers/test_esdirk32.py +158 -0
- tests/solvers/test_esdirk4.py +138 -0
- tests/solvers/test_esdirk43.py +158 -0
- tests/solvers/test_esdirk54.py +160 -0
- tests/solvers/test_esdirk85.py +157 -0
- tests/solvers/test_euler.py +223 -0
- tests/solvers/test_rk4.py +138 -0
- tests/solvers/test_rkbs32.py +159 -0
- tests/solvers/test_rkck54.py +157 -0
- tests/solvers/test_rkdp54.py +159 -0
- tests/solvers/test_rkdp87.py +157 -0
- tests/solvers/test_rkf45.py +159 -0
- tests/solvers/test_rkf78.py +160 -0
- tests/solvers/test_rkv65.py +160 -0
- tests/solvers/test_solver.py +119 -0
- tests/solvers/test_ssprk22.py +136 -0
- tests/solvers/test_ssprk33.py +136 -0
- tests/solvers/test_ssprk34.py +136 -0
- tests/test_connection.py +176 -0
- tests/test_simulation.py +271 -0
- tests/test_subsystem.py +182 -0
- tests/utils/__init__.py +0 -0
- tests/utils/test_adaptivebuffer.py +111 -0
- tests/utils/test_anderson.py +142 -0
- tests/utils/test_funcs.py +143 -0
- tests/utils/test_gilbert.py +108 -0
- tests/utils/test_progresstracker.py +144 -0
- tests/utils/test_realtimeplotter.py +122 -0
- tests/utils/test_statespacerealizations.py +107 -0
tests/test_simulation.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'simulation.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from pathsim.simulation import Simulation
|
|
16
|
+
|
|
17
|
+
#for testing
|
|
18
|
+
from pathsim.blocks._block import Block
|
|
19
|
+
from pathsim.connection import Connection
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# TESTS ================================================================================
|
|
24
|
+
|
|
25
|
+
class TestSimulation(unittest.TestCase):
|
|
26
|
+
"""
|
|
27
|
+
Test the implementation of the 'Simulation' class
|
|
28
|
+
|
|
29
|
+
only very minimal functonality
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def test_init(self):
|
|
33
|
+
|
|
34
|
+
#test default initialization
|
|
35
|
+
Sim = Simulation(log=False)
|
|
36
|
+
self.assertEqual(Sim.blocks, [])
|
|
37
|
+
self.assertEqual(Sim.connections, [])
|
|
38
|
+
self.assertEqual(Sim.dt, 0.01)
|
|
39
|
+
self.assertEqual(Sim.dt_min, 0.0)
|
|
40
|
+
self.assertEqual(Sim.dt_max, None)
|
|
41
|
+
self.assertEqual(str(Sim.Solver()), "SSPRK22")
|
|
42
|
+
self.assertEqual(Sim.tolerance_fpi, 1e-12)
|
|
43
|
+
self.assertEqual(Sim.tolerance_lte, 1e-8)
|
|
44
|
+
self.assertEqual(Sim.iterations_min, 1) # <-- determined from internal path length
|
|
45
|
+
self.assertEqual(Sim.iterations_max, 200)
|
|
46
|
+
self.assertFalse(Sim.log)
|
|
47
|
+
|
|
48
|
+
#test specific initialization
|
|
49
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
50
|
+
C1 = Connection(B1, B2)
|
|
51
|
+
C2 = Connection(B2, B3)
|
|
52
|
+
C3 = Connection(B3, B1)
|
|
53
|
+
Sim = Simulation(blocks=[B1, B2, B3],
|
|
54
|
+
connections=[C1, C2, C3],
|
|
55
|
+
dt=0.02,
|
|
56
|
+
dt_min=0.001,
|
|
57
|
+
dt_max=0.1,
|
|
58
|
+
tolerance_fpi=1e-9,
|
|
59
|
+
tolerance_lte=1e-6,
|
|
60
|
+
iterations_min=None,
|
|
61
|
+
iterations_max=100,
|
|
62
|
+
log=False)
|
|
63
|
+
self.assertEqual(len(Sim.blocks), 3)
|
|
64
|
+
self.assertEqual(len(Sim.connections), 3)
|
|
65
|
+
self.assertEqual(Sim.dt, 0.02)
|
|
66
|
+
self.assertEqual(Sim.dt_min, 0.001)
|
|
67
|
+
self.assertEqual(Sim.dt_max, 0.1)
|
|
68
|
+
self.assertEqual(Sim.tolerance_fpi, 1e-9)
|
|
69
|
+
self.assertEqual(Sim.tolerance_lte, 1e-6)
|
|
70
|
+
self.assertEqual(Sim.iterations_min, 3) # <-- determined from internal path length
|
|
71
|
+
self.assertEqual(Sim.iterations_max, 100)
|
|
72
|
+
|
|
73
|
+
#test specific initialization with connection override
|
|
74
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
75
|
+
C1 = Connection(B1, B2)
|
|
76
|
+
C2 = Connection(B2, B3)
|
|
77
|
+
C3 = Connection(B3, B2) # <-- overrides B2
|
|
78
|
+
with self.assertRaises(ValueError):
|
|
79
|
+
Sim = Simulation(blocks=[B1, B2, B3],
|
|
80
|
+
connections=[C1, C2, C3],
|
|
81
|
+
log=False)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_add_block(self):
|
|
85
|
+
|
|
86
|
+
Sim = Simulation(log=False)
|
|
87
|
+
|
|
88
|
+
self.assertEqual(Sim.blocks, [])
|
|
89
|
+
|
|
90
|
+
#test adding a block
|
|
91
|
+
B1 = Block()
|
|
92
|
+
Sim.add_block(B1)
|
|
93
|
+
self.assertEqual(Sim.blocks, [B1])
|
|
94
|
+
|
|
95
|
+
#test adding the same block again
|
|
96
|
+
with self.assertRaises(ValueError):
|
|
97
|
+
Sim.add_block(B1)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_add_connection(self):
|
|
101
|
+
|
|
102
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
103
|
+
C1 = Connection(B1, B2)
|
|
104
|
+
|
|
105
|
+
Sim = Simulation(blocks=[B1, B2, B3],
|
|
106
|
+
connections=[C1],
|
|
107
|
+
log=False)
|
|
108
|
+
|
|
109
|
+
self.assertEqual(Sim.connections, [C1])
|
|
110
|
+
|
|
111
|
+
#test adding a connection
|
|
112
|
+
C2 = Connection(B2, B3)
|
|
113
|
+
Sim.add_connection(C2)
|
|
114
|
+
self.assertEqual(Sim.connections, [C1, C2])
|
|
115
|
+
|
|
116
|
+
#test adding the same connection again
|
|
117
|
+
with self.assertRaises(ValueError):
|
|
118
|
+
Sim.add_connection(C2)
|
|
119
|
+
self.assertEqual(Sim.connections, [C1, C2])
|
|
120
|
+
|
|
121
|
+
#test adding a connection that overrides B3
|
|
122
|
+
C3 = Connection(B1, B3)
|
|
123
|
+
with self.assertRaises(ValueError):
|
|
124
|
+
Sim.add_connection(C3)
|
|
125
|
+
self.assertEqual(Sim.connections, [C1, C2])
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_set_solver(self): pass
|
|
129
|
+
def test_update(self): pass
|
|
130
|
+
def test_step(self): pass
|
|
131
|
+
def test_run(self): pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class TestSimulationIVP(unittest.TestCase):
|
|
135
|
+
"""
|
|
136
|
+
special test case:
|
|
137
|
+
linear feedback initial value problem with default solver (SSPRK22)
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def setUp(self):
|
|
141
|
+
|
|
142
|
+
#modules from pathsim for test case
|
|
143
|
+
from pathsim.blocks import (
|
|
144
|
+
Integrator, Scope,
|
|
145
|
+
Amplifier, Adder
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
#blocks that define the system
|
|
149
|
+
self.Int = Integrator(1.0)
|
|
150
|
+
self.Amp = Amplifier(-1)
|
|
151
|
+
self.Add = Adder()
|
|
152
|
+
self.Sco = Scope(labels=["response"])
|
|
153
|
+
|
|
154
|
+
blocks = [self.Int, self.Amp, self.Add, self.Sco]
|
|
155
|
+
|
|
156
|
+
#the connections between the blocks
|
|
157
|
+
connections = [
|
|
158
|
+
Connection(self.Amp, self.Add[1]),
|
|
159
|
+
Connection(self.Add, self.Int),
|
|
160
|
+
Connection(self.Int, self.Amp, self.Sco)
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
#initialize simulation with the blocks, connections, timestep and logging enabled
|
|
164
|
+
self.Sim = Simulation(blocks, connections, dt=0.02, log=False)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_init(self):
|
|
168
|
+
|
|
169
|
+
from pathsim.solvers import SSPRK22
|
|
170
|
+
|
|
171
|
+
#test initialization of simulation
|
|
172
|
+
self.assertEqual(len(self.Sim.blocks), 4)
|
|
173
|
+
self.assertEqual(len(self.Sim.connections), 3)
|
|
174
|
+
self.assertEqual(self.Sim.dt, 0.02)
|
|
175
|
+
self.assertEqual(self.Sim.iterations_min, 2)
|
|
176
|
+
self.assertTrue(isinstance(self.Sim.engine, SSPRK22))
|
|
177
|
+
self.assertTrue(self.Sim.is_explicit)
|
|
178
|
+
self.assertFalse(self.Sim.is_adaptive)
|
|
179
|
+
|
|
180
|
+
#test if engine setup was correct
|
|
181
|
+
self.assertTrue(isinstance(self.Int.engine, SSPRK22)) # <-- only the Integrator needs an engine
|
|
182
|
+
self.assertTrue(self.Amp.engine is None)
|
|
183
|
+
self.assertTrue(self.Add.engine is None)
|
|
184
|
+
self.assertTrue(self.Sco.engine is None)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_step(self):
|
|
188
|
+
|
|
189
|
+
#reset first
|
|
190
|
+
self.Sim.reset()
|
|
191
|
+
|
|
192
|
+
#check if reset was sucecssful
|
|
193
|
+
self.assertEqual(self.Sim.time, 0.0)
|
|
194
|
+
self.assertEqual(self.Int.get(0), self.Int.initial_value)
|
|
195
|
+
|
|
196
|
+
#step using global timestep
|
|
197
|
+
success, err, scl, te, ts = self.Sim.step()
|
|
198
|
+
self.assertEqual(self.Sim.time, self.Sim.dt)
|
|
199
|
+
self.assertEqual(err, 0.0) #fixed solver
|
|
200
|
+
self.assertEqual(scl, 1.0) #fixed solver
|
|
201
|
+
self.assertEqual(ts, 0) #no implicit solver
|
|
202
|
+
self.assertGreaterEqual(te, self.Sim.iterations_min)
|
|
203
|
+
|
|
204
|
+
#step again using custom timestep
|
|
205
|
+
self.Sim.step(dt=2.2*self.Sim.dt)
|
|
206
|
+
self.assertEqual(self.Sim.time, 3.2*self.Sim.dt)
|
|
207
|
+
self.assertLess(self.Int.get(0), self.Int.initial_value)
|
|
208
|
+
|
|
209
|
+
#test if scope recorded correctly
|
|
210
|
+
time, data = self.Sco.read()
|
|
211
|
+
for a, b in zip(time, [self.Sim.dt, 3.2*self.Sim.dt]):
|
|
212
|
+
self.assertEqual(a, b)
|
|
213
|
+
|
|
214
|
+
#reset again
|
|
215
|
+
self.Sim.reset()
|
|
216
|
+
|
|
217
|
+
#check if reset was sucecssful
|
|
218
|
+
self.assertEqual(self.Sim.time, 0.0)
|
|
219
|
+
self.assertEqual(self.Int.get(0), self.Int.initial_value)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_run(self):
|
|
223
|
+
|
|
224
|
+
#reset first
|
|
225
|
+
self.Sim.reset()
|
|
226
|
+
|
|
227
|
+
#check if reset was sucecssful
|
|
228
|
+
self.assertEqual(self.Sim.time, 0.0)
|
|
229
|
+
self.assertEqual(self.Int.get(0), self.Int.initial_value)
|
|
230
|
+
|
|
231
|
+
#test running for some time
|
|
232
|
+
self.Sim.run(duration=2, reset=True)
|
|
233
|
+
self.assertEqual(self.Sim.time, 2)
|
|
234
|
+
|
|
235
|
+
time, data = self.Sco.read()
|
|
236
|
+
_time = np.arange(0, 2.02, 0.02)
|
|
237
|
+
|
|
238
|
+
#time recording matches and solution decays
|
|
239
|
+
self.assertLess(np.linalg.norm(time - _time), 1e-13)
|
|
240
|
+
self.assertTrue(np.all(np.diff(data) < 0.0))
|
|
241
|
+
|
|
242
|
+
#test running for some time with reset
|
|
243
|
+
self.Sim.run(duration=1, reset=True)
|
|
244
|
+
self.assertEqual(self.Sim.time, 1)
|
|
245
|
+
|
|
246
|
+
time, data = self.Sco.read()
|
|
247
|
+
_time = np.arange(0, 1.02, 0.02)
|
|
248
|
+
|
|
249
|
+
#time recording matches and solution decays
|
|
250
|
+
self.assertLess(np.linalg.norm(time - _time), 1e-13)
|
|
251
|
+
|
|
252
|
+
#test running for some time without reset
|
|
253
|
+
self.Sim.run(duration=2, reset=False)
|
|
254
|
+
self.assertEqual(self.Sim.time, 3)
|
|
255
|
+
|
|
256
|
+
time, data = self.Sco.read()
|
|
257
|
+
_time = np.arange(0, 3.02, 0.02)
|
|
258
|
+
|
|
259
|
+
#time recording matches and solution decays
|
|
260
|
+
self.assertLess(np.linalg.norm(time - _time), 1e-13)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
269
|
+
|
|
270
|
+
if __name__ == '__main__':
|
|
271
|
+
unittest.main(verbosity=2)
|
tests/test_subsystem.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'subsystem.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from pathsim.subsystem import Subsystem, Interface
|
|
16
|
+
|
|
17
|
+
#for testing
|
|
18
|
+
from pathsim.blocks._block import Block
|
|
19
|
+
from pathsim.connection import Connection
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# TESTS ================================================================================
|
|
23
|
+
|
|
24
|
+
class TestInterface(unittest.TestCase):
|
|
25
|
+
"""
|
|
26
|
+
Test the implementation of the 'Interface' class
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def test_set_output(self):
|
|
30
|
+
I = Interface()
|
|
31
|
+
|
|
32
|
+
#test that outputs are zero
|
|
33
|
+
self.assertEqual(I.outputs, {0: 0.0})
|
|
34
|
+
|
|
35
|
+
#test if output is correctly set
|
|
36
|
+
I.set_output(0, 0.2)
|
|
37
|
+
I.set_output(1, 1.3)
|
|
38
|
+
I.set_output(2, 2.4)
|
|
39
|
+
I.set_output(3, 3.5)
|
|
40
|
+
|
|
41
|
+
self.assertEqual(I.get(0), 0.2)
|
|
42
|
+
self.assertEqual(I.get(1), 1.3)
|
|
43
|
+
self.assertEqual(I.get(2), 2.4)
|
|
44
|
+
self.assertEqual(I.get(3), 3.5)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_get_input(self):
|
|
48
|
+
|
|
49
|
+
I = Interface()
|
|
50
|
+
|
|
51
|
+
#test that inputs are zero
|
|
52
|
+
self.assertEqual(I.inputs, {0: 0.0})
|
|
53
|
+
|
|
54
|
+
#test default retrieval
|
|
55
|
+
self.assertEqual(I.get_input(2), 0.0)
|
|
56
|
+
|
|
57
|
+
#test if input is correctly retrieved
|
|
58
|
+
I.set(0, 0.2)
|
|
59
|
+
I.set(1, 1.3)
|
|
60
|
+
I.set(2, 2.4)
|
|
61
|
+
I.set(3, 3.5)
|
|
62
|
+
|
|
63
|
+
self.assertEqual(I.get_input(0), 0.2)
|
|
64
|
+
self.assertEqual(I.get_input(1), 1.3)
|
|
65
|
+
self.assertEqual(I.get_input(2), 2.4)
|
|
66
|
+
self.assertEqual(I.get_input(3), 3.5)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TestSubsystem(unittest.TestCase):
|
|
70
|
+
"""
|
|
71
|
+
test implementation of the 'Subsystem' class
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def test_init(self):
|
|
75
|
+
|
|
76
|
+
#test default initialization
|
|
77
|
+
with self.assertRaises(ValueError):
|
|
78
|
+
S = Subsystem()
|
|
79
|
+
|
|
80
|
+
#test initialization without interface
|
|
81
|
+
with self.assertRaises(ValueError):
|
|
82
|
+
S = Subsystem(blocks=[Block(), Block()])
|
|
83
|
+
|
|
84
|
+
#test specific initialization with interface
|
|
85
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
86
|
+
I1 = Interface()
|
|
87
|
+
C1 = Connection(I1, B1, B2, B3)
|
|
88
|
+
C2 = Connection(B1, I1)
|
|
89
|
+
S = Subsystem(blocks=[B1, B2, B3, I1], connections=[C1, C2])
|
|
90
|
+
self.assertEqual(len(S.blocks), 3)
|
|
91
|
+
self.assertEqual(len(S.connections), 2)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_check_connections(self):
|
|
95
|
+
|
|
96
|
+
#test specific initialization with connecion override
|
|
97
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
98
|
+
I1 = Interface()
|
|
99
|
+
C1 = Connection(I1, B1, B2, B3)
|
|
100
|
+
C2 = Connection(B1, I1)
|
|
101
|
+
C3 = Connection(B2, B3) # <-- this one overrides B3
|
|
102
|
+
with self.assertRaises(ValueError):
|
|
103
|
+
S = Subsystem(blocks=[B1, B2, B3, I1], connections=[C1, C2, C3])
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_len(self):
|
|
107
|
+
|
|
108
|
+
#test the len method for internal signal path estimation
|
|
109
|
+
|
|
110
|
+
I1 = Interface()
|
|
111
|
+
S = Subsystem(blocks=[I1],)
|
|
112
|
+
self.assertEqual(len(S), 1)
|
|
113
|
+
|
|
114
|
+
B1 = Block()
|
|
115
|
+
I1 = Interface()
|
|
116
|
+
C1 = Connection(I1, B1)
|
|
117
|
+
S = Subsystem(blocks=[I1, B1], connections=[C1])
|
|
118
|
+
self.assertEqual(len(S), 2)
|
|
119
|
+
|
|
120
|
+
B1, B2 = Block(), Block()
|
|
121
|
+
I1 = Interface()
|
|
122
|
+
C1 = Connection(I1, B1)
|
|
123
|
+
C2 = Connection(B1, B2)
|
|
124
|
+
S = Subsystem(blocks=[I1, B1, B2], connections=[C1, C2])
|
|
125
|
+
self.assertEqual(len(S), 3)
|
|
126
|
+
|
|
127
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
128
|
+
I1 = Interface()
|
|
129
|
+
C1 = Connection(I1, B1, B2, B3)
|
|
130
|
+
C2 = Connection(B1, B2[1])
|
|
131
|
+
C3 = Connection(B2, B1[1], B3[1])
|
|
132
|
+
C4 = Connection(B3, I1)
|
|
133
|
+
S = Subsystem(blocks=[I1, B1, B2, B3], connections=[C1, C2, C3, C4])
|
|
134
|
+
self.assertEqual(len(S), 5)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_set(self):
|
|
138
|
+
|
|
139
|
+
B1 = Block()
|
|
140
|
+
I1 = Interface()
|
|
141
|
+
C1 = Connection(I1, B1, I1)
|
|
142
|
+
S = Subsystem(blocks=[I1, B1], connections=[C1])
|
|
143
|
+
|
|
144
|
+
self.assertEqual(S.interface.outputs, {0:0.0})
|
|
145
|
+
|
|
146
|
+
S.set(0, 1.1)
|
|
147
|
+
S.set(1, 2.2)
|
|
148
|
+
S.set(2, 3.3)
|
|
149
|
+
|
|
150
|
+
self.assertEqual(S.interface.outputs, {0:1.1, 1:2.2, 2:3.3})
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_get(self):
|
|
154
|
+
|
|
155
|
+
B1 = Block()
|
|
156
|
+
I1 = Interface()
|
|
157
|
+
C1 = Connection(I1, B1, I1)
|
|
158
|
+
S = Subsystem(blocks=[I1, B1], connections=[C1])
|
|
159
|
+
|
|
160
|
+
S.interface.inputs = {0:1.1, 1:2.2, 2:3.3}
|
|
161
|
+
|
|
162
|
+
self.assertEqual(S.get(0), 1.1)
|
|
163
|
+
self.assertEqual(S.get(1), 2.2)
|
|
164
|
+
self.assertEqual(S.get(2), 3.3)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_update(self):
|
|
168
|
+
|
|
169
|
+
B1 = Block()
|
|
170
|
+
I1 = Interface()
|
|
171
|
+
C1 = Connection(I1, B1)
|
|
172
|
+
S = Subsystem(blocks=[I1, B1], connections=[C1])
|
|
173
|
+
|
|
174
|
+
err = S.update(0)
|
|
175
|
+
|
|
176
|
+
self.assertEqual(err, 0.0)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
180
|
+
|
|
181
|
+
if __name__ == '__main__':
|
|
182
|
+
unittest.main(verbosity=2)
|
tests/utils/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'utils/adaptivebuffer.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from pathsim.utils.adaptivebuffer import (
|
|
16
|
+
AdaptiveBuffer
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# TESTS ================================================================================
|
|
21
|
+
|
|
22
|
+
class TestAdaptiveBuffer(unittest.TestCase):
|
|
23
|
+
"""
|
|
24
|
+
test the implementation of the 'AdaptiveBuffer' class
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def test_init(self):
|
|
28
|
+
|
|
29
|
+
d = 100
|
|
30
|
+
|
|
31
|
+
buffer = AdaptiveBuffer(d)
|
|
32
|
+
|
|
33
|
+
#test initialization
|
|
34
|
+
self.assertEqual(buffer.delay, d)
|
|
35
|
+
self.assertEqual(len(buffer.buffer), 0)
|
|
36
|
+
self.assertEqual(buffer.counter, 0)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_add(self):
|
|
40
|
+
|
|
41
|
+
d = 100
|
|
42
|
+
|
|
43
|
+
buffer = AdaptiveBuffer(d)
|
|
44
|
+
|
|
45
|
+
for i in range(10*d):
|
|
46
|
+
|
|
47
|
+
#test the counter
|
|
48
|
+
self.assertLessEqual(buffer.counter, buffer.clean_every+1)
|
|
49
|
+
|
|
50
|
+
buffer.add(i, i)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_get_empty(self):
|
|
54
|
+
|
|
55
|
+
d = 100
|
|
56
|
+
|
|
57
|
+
buffer = AdaptiveBuffer(d)
|
|
58
|
+
|
|
59
|
+
#test default empty buffer
|
|
60
|
+
self.assertEqual(buffer.get(123), 0)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_get(self):
|
|
64
|
+
|
|
65
|
+
d = 100
|
|
66
|
+
|
|
67
|
+
buffer = AdaptiveBuffer(d)
|
|
68
|
+
|
|
69
|
+
for i in range(10*d):
|
|
70
|
+
|
|
71
|
+
buffer.add(i, i)
|
|
72
|
+
|
|
73
|
+
#test buffer readout
|
|
74
|
+
self.assertEqual(buffer.get(i), 0 if i < d else i-d)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_get_too_large(self):
|
|
78
|
+
|
|
79
|
+
d = 100
|
|
80
|
+
|
|
81
|
+
buffer = AdaptiveBuffer(d)
|
|
82
|
+
|
|
83
|
+
for i in range(10*d):
|
|
84
|
+
|
|
85
|
+
buffer.add(i, i)
|
|
86
|
+
|
|
87
|
+
#test buffer readout out of bounds (most recent value)
|
|
88
|
+
self.assertEqual(buffer.get(100*d), i)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_clear(self):
|
|
92
|
+
|
|
93
|
+
d = 100
|
|
94
|
+
|
|
95
|
+
buffer = AdaptiveBuffer(d)
|
|
96
|
+
|
|
97
|
+
for i in range(10*d):
|
|
98
|
+
|
|
99
|
+
buffer.add(i, i)
|
|
100
|
+
|
|
101
|
+
buffer.clear()
|
|
102
|
+
|
|
103
|
+
#test buffer clearing
|
|
104
|
+
self.assertEqual(len(buffer.buffer), 0)
|
|
105
|
+
self.assertEqual(buffer.counter, 0)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
109
|
+
|
|
110
|
+
if __name__ == '__main__':
|
|
111
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'utils/anderson.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from pathsim.utils.anderson import (
|
|
16
|
+
AndersonAcceleration,
|
|
17
|
+
NewtonAndersonAcceleration
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# TESTS ================================================================================
|
|
22
|
+
|
|
23
|
+
class TestAndersonAcceleration(unittest.TestCase):
|
|
24
|
+
"""
|
|
25
|
+
test the implementation of the 'AndersonAcceleration' class
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def test_init(self):
|
|
29
|
+
m = 5
|
|
30
|
+
aa = AndersonAcceleration(m)
|
|
31
|
+
# test initialization
|
|
32
|
+
self.assertEqual(aa.m, m)
|
|
33
|
+
self.assertTrue(aa.restart)
|
|
34
|
+
self.assertEqual(len(aa.x_buffer), 0)
|
|
35
|
+
self.assertEqual(len(aa.f_buffer), 0)
|
|
36
|
+
self.assertEqual(aa.counter, 0)
|
|
37
|
+
|
|
38
|
+
def test_reset(self):
|
|
39
|
+
aa = AndersonAcceleration(5)
|
|
40
|
+
aa.x_buffer = [1, 2, 3]
|
|
41
|
+
aa.f_buffer = [4, 5, 6]
|
|
42
|
+
aa.counter = 10
|
|
43
|
+
aa.reset()
|
|
44
|
+
# test reset
|
|
45
|
+
self.assertEqual(len(aa.x_buffer), 0)
|
|
46
|
+
self.assertEqual(len(aa.f_buffer), 0)
|
|
47
|
+
self.assertEqual(aa.counter, 0)
|
|
48
|
+
|
|
49
|
+
def test_step_scalar(self):
|
|
50
|
+
aa = AndersonAcceleration(2)
|
|
51
|
+
x, g = 1.0, 2.0
|
|
52
|
+
result, residual = aa.step(x, g)
|
|
53
|
+
# test scalar step
|
|
54
|
+
self.assertEqual(result, g)
|
|
55
|
+
self.assertEqual(residual, abs(g - x))
|
|
56
|
+
|
|
57
|
+
def test_step_vector(self):
|
|
58
|
+
aa = AndersonAcceleration(2)
|
|
59
|
+
x = np.array([1.0, 2.0])
|
|
60
|
+
g = np.array([2.0, 3.0])
|
|
61
|
+
result, residual = aa.step(x, g)
|
|
62
|
+
# test vector step
|
|
63
|
+
np.testing.assert_array_equal(result, g)
|
|
64
|
+
self.assertAlmostEqual(residual, np.linalg.norm(g - x))
|
|
65
|
+
|
|
66
|
+
def test_solve_scalar_equation(self):
|
|
67
|
+
# Solve x = cos(x)
|
|
68
|
+
aa = AndersonAcceleration(m=3)
|
|
69
|
+
x = 0.0
|
|
70
|
+
for _ in range(100):
|
|
71
|
+
g = np.cos(x)
|
|
72
|
+
x, residual = aa.step(x, g)
|
|
73
|
+
if residual < 1e-8:
|
|
74
|
+
break
|
|
75
|
+
self.assertAlmostEqual(x, 0.7390851332151607, places=7)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_solve_vector_equation(self):
|
|
79
|
+
# Solve the system:
|
|
80
|
+
# x^2 + y^2 = 1
|
|
81
|
+
aa = AndersonAcceleration(m=3)
|
|
82
|
+
x = np.array([1.0, 0.0]) # Start from a point on the circle
|
|
83
|
+
for _ in range(100):
|
|
84
|
+
g = x / np.linalg.norm(x) # Project back onto the unit circle
|
|
85
|
+
x, residual = aa.step(x, g)
|
|
86
|
+
if residual < 1e-8:
|
|
87
|
+
break
|
|
88
|
+
# Check if the solution lies on the unit circle
|
|
89
|
+
self.assertAlmostEqual(np.linalg.norm(x), 1.0, places=7)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestNewtonAndersonAcceleration(unittest.TestCase):
|
|
93
|
+
"""
|
|
94
|
+
test the implementation of the 'NewtonAndersonAcceleration' class
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def test_init(self):
|
|
98
|
+
m = 5
|
|
99
|
+
naa = NewtonAndersonAcceleration(m)
|
|
100
|
+
# test initialization
|
|
101
|
+
self.assertEqual(naa.m, m)
|
|
102
|
+
self.assertTrue(naa.restart)
|
|
103
|
+
self.assertEqual(len(naa.x_buffer), 0)
|
|
104
|
+
self.assertEqual(len(naa.f_buffer), 0)
|
|
105
|
+
self.assertEqual(naa.counter, 0)
|
|
106
|
+
|
|
107
|
+
def test_step_no_jacobian(self):
|
|
108
|
+
naa = NewtonAndersonAcceleration(2)
|
|
109
|
+
x = np.array([1.0, 2.0])
|
|
110
|
+
g = np.array([2.0, 3.0])
|
|
111
|
+
result, residual = naa.step(x, g)
|
|
112
|
+
# test step without jacobian (should be same as AndersonAcceleration)
|
|
113
|
+
np.testing.assert_array_equal(result, g)
|
|
114
|
+
self.assertAlmostEqual(residual, np.linalg.norm(g - x))
|
|
115
|
+
|
|
116
|
+
def test_step_with_jacobian(self):
|
|
117
|
+
naa = NewtonAndersonAcceleration(2)
|
|
118
|
+
x = np.array([1.0, 2.0])
|
|
119
|
+
g = np.array([2.0, 3.0])
|
|
120
|
+
jac = np.array([[2.0, 0.0], [0.0, 2.0]])
|
|
121
|
+
result, residual = naa.step(x, g, jac)
|
|
122
|
+
# test step with jacobian
|
|
123
|
+
self.assertIsNotNone(result)
|
|
124
|
+
self.assertIsNotNone(residual)
|
|
125
|
+
|
|
126
|
+
def test_solve_scalar_equation(self):
|
|
127
|
+
# Solve x = cos(x)
|
|
128
|
+
naa = NewtonAndersonAcceleration(m=5)
|
|
129
|
+
x = 0.0
|
|
130
|
+
for i in range(100):
|
|
131
|
+
g = np.cos(x)
|
|
132
|
+
jac = -np.sin(x)
|
|
133
|
+
x, residual = naa.step(x, g, jac)
|
|
134
|
+
if residual < 1e-8:
|
|
135
|
+
break
|
|
136
|
+
self.assertAlmostEqual(x, 0.7390851332151607, places=7)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
140
|
+
|
|
141
|
+
if __name__ == '__main__':
|
|
142
|
+
unittest.main(verbosity=2)
|