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.
Files changed (109) hide show
  1. pathsim/__init__.py +3 -0
  2. pathsim/blocks/__init__.py +14 -0
  3. pathsim/blocks/_block.py +209 -0
  4. pathsim/blocks/adder.py +30 -0
  5. pathsim/blocks/amplifier.py +34 -0
  6. pathsim/blocks/delay.py +70 -0
  7. pathsim/blocks/differentiator.py +70 -0
  8. pathsim/blocks/function.py +82 -0
  9. pathsim/blocks/integrator.py +66 -0
  10. pathsim/blocks/lti.py +155 -0
  11. pathsim/blocks/multiplier.py +30 -0
  12. pathsim/blocks/ode.py +86 -0
  13. pathsim/blocks/rf/__init__.py +4 -0
  14. pathsim/blocks/rf/filters.py +169 -0
  15. pathsim/blocks/rf/noise.py +218 -0
  16. pathsim/blocks/rf/sources.py +163 -0
  17. pathsim/blocks/rf/wienerhammerstein.py +338 -0
  18. pathsim/blocks/rng.py +57 -0
  19. pathsim/blocks/scope.py +224 -0
  20. pathsim/blocks/sources.py +71 -0
  21. pathsim/blocks/spectrum.py +316 -0
  22. pathsim/connection.py +112 -0
  23. pathsim/simulation.py +652 -0
  24. pathsim/solvers/__init__.py +25 -0
  25. pathsim/solvers/_solver.py +403 -0
  26. pathsim/solvers/bdf.py +240 -0
  27. pathsim/solvers/dirk2.py +101 -0
  28. pathsim/solvers/dirk3.py +86 -0
  29. pathsim/solvers/esdirk32.py +131 -0
  30. pathsim/solvers/esdirk4.py +99 -0
  31. pathsim/solvers/esdirk43.py +139 -0
  32. pathsim/solvers/esdirk54.py +141 -0
  33. pathsim/solvers/esdirk85.py +200 -0
  34. pathsim/solvers/euler.py +81 -0
  35. pathsim/solvers/rk4.py +61 -0
  36. pathsim/solvers/rkbs32.py +101 -0
  37. pathsim/solvers/rkck54.py +108 -0
  38. pathsim/solvers/rkdp54.py +111 -0
  39. pathsim/solvers/rkdp87.py +116 -0
  40. pathsim/solvers/rkf45.py +102 -0
  41. pathsim/solvers/rkf78.py +111 -0
  42. pathsim/solvers/rkv65.py +103 -0
  43. pathsim/solvers/ssprk22.py +62 -0
  44. pathsim/solvers/ssprk33.py +65 -0
  45. pathsim/solvers/ssprk34.py +74 -0
  46. pathsim/subsystem.py +267 -0
  47. pathsim/utils/__init__.py +0 -0
  48. pathsim/utils/adaptivebuffer.py +87 -0
  49. pathsim/utils/anderson.py +180 -0
  50. pathsim/utils/funcs.py +205 -0
  51. pathsim/utils/gilbert.py +110 -0
  52. pathsim/utils/progresstracker.py +90 -0
  53. pathsim/utils/realtimeplotter.py +230 -0
  54. pathsim/utils/statespacerealizations.py +116 -0
  55. pathsim/utils/waveforms.py +36 -0
  56. pathsim-0.2.0.dist-info/LICENSE.txt +21 -0
  57. pathsim-0.2.0.dist-info/METADATA +149 -0
  58. pathsim-0.2.0.dist-info/RECORD +109 -0
  59. pathsim-0.2.0.dist-info/WHEEL +5 -0
  60. pathsim-0.2.0.dist-info/top_level.txt +2 -0
  61. tests/__init__.py +0 -0
  62. tests/blocks/__init__.py +0 -0
  63. tests/blocks/test_adder.py +85 -0
  64. tests/blocks/test_amplifier.py +66 -0
  65. tests/blocks/test_block.py +138 -0
  66. tests/blocks/test_delay.py +122 -0
  67. tests/blocks/test_differentiator.py +102 -0
  68. tests/blocks/test_function.py +165 -0
  69. tests/blocks/test_integrator.py +92 -0
  70. tests/blocks/test_lti.py +162 -0
  71. tests/blocks/test_multiplier.py +87 -0
  72. tests/blocks/test_ode.py +125 -0
  73. tests/blocks/test_rng.py +109 -0
  74. tests/blocks/test_scope.py +196 -0
  75. tests/blocks/test_sources.py +119 -0
  76. tests/blocks/test_spectrum.py +119 -0
  77. tests/solvers/__init__.py +0 -0
  78. tests/solvers/test_bdf.py +364 -0
  79. tests/solvers/test_dirk2.py +138 -0
  80. tests/solvers/test_dirk3.py +137 -0
  81. tests/solvers/test_esdirk32.py +158 -0
  82. tests/solvers/test_esdirk4.py +138 -0
  83. tests/solvers/test_esdirk43.py +158 -0
  84. tests/solvers/test_esdirk54.py +160 -0
  85. tests/solvers/test_esdirk85.py +157 -0
  86. tests/solvers/test_euler.py +223 -0
  87. tests/solvers/test_rk4.py +138 -0
  88. tests/solvers/test_rkbs32.py +159 -0
  89. tests/solvers/test_rkck54.py +157 -0
  90. tests/solvers/test_rkdp54.py +159 -0
  91. tests/solvers/test_rkdp87.py +157 -0
  92. tests/solvers/test_rkf45.py +159 -0
  93. tests/solvers/test_rkf78.py +160 -0
  94. tests/solvers/test_rkv65.py +160 -0
  95. tests/solvers/test_solver.py +119 -0
  96. tests/solvers/test_ssprk22.py +136 -0
  97. tests/solvers/test_ssprk33.py +136 -0
  98. tests/solvers/test_ssprk34.py +136 -0
  99. tests/test_connection.py +176 -0
  100. tests/test_simulation.py +271 -0
  101. tests/test_subsystem.py +182 -0
  102. tests/utils/__init__.py +0 -0
  103. tests/utils/test_adaptivebuffer.py +111 -0
  104. tests/utils/test_anderson.py +142 -0
  105. tests/utils/test_funcs.py +143 -0
  106. tests/utils/test_gilbert.py +108 -0
  107. tests/utils/test_progresstracker.py +144 -0
  108. tests/utils/test_realtimeplotter.py +122 -0
  109. tests/utils/test_statespacerealizations.py +107 -0
@@ -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)
@@ -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)
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)