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,119 @@
1
+ ########################################################################################
2
+ ##
3
+ ## TESTS FOR
4
+ ## 'blocks.sources.py'
5
+ ##
6
+ ## Milan Rother 2024
7
+ ##
8
+ ########################################################################################
9
+
10
+ # IMPORTS ==============================================================================
11
+
12
+ import unittest
13
+ import numpy as np
14
+
15
+ from pathsim.blocks.sources import Source, Constant
16
+
17
+
18
+ # TESTS ================================================================================
19
+
20
+ class TestConstant(unittest.TestCase):
21
+ """
22
+ Test the implementation of the 'Constant' block class
23
+ """
24
+
25
+ def test_init(self):
26
+
27
+ C = Constant(value=5)
28
+
29
+ self.assertEqual(C.value, 5)
30
+ self.assertEqual(C.get(0), 5)
31
+
32
+
33
+ def test_str(self):
34
+
35
+ C = Constant(value=5)
36
+
37
+ #test default str method
38
+ self.assertEqual(str(C), "Constant")
39
+
40
+
41
+ def test_reset(self):
42
+
43
+ C = Constant(value=5)
44
+
45
+ C.reset()
46
+
47
+ #test if output remains after reset
48
+ self.assertEqual(C.get(0), 5)
49
+
50
+
51
+ class TestSource(unittest.TestCase):
52
+ """
53
+ Test the implementation of the 'Source' block class
54
+ """
55
+
56
+ def test_init(self):
57
+
58
+ def f(t):
59
+ return np.sin(t)
60
+
61
+ S = Source(func=f)
62
+
63
+ #test if function works
64
+ self.assertEqual(S.func(1), f(1))
65
+ self.assertEqual(S.func(2), f(2))
66
+ self.assertEqual(S.func(3), f(3))
67
+
68
+ #test input validation
69
+ with self.assertRaises(ValueError):
70
+ S = Source(func=2)
71
+
72
+
73
+ def test_str(self):
74
+
75
+ S = Source()
76
+
77
+ #test default str method
78
+ self.assertEqual(str(S), "Source")
79
+
80
+
81
+ def test_update(self):
82
+
83
+ def f(t):
84
+ return np.sin(t)
85
+
86
+ S = Source(func=f)
87
+
88
+ #update block
89
+ err = S.update(1)
90
+
91
+ #test if update was correct
92
+ self.assertEqual(S.get(0), f(1))
93
+
94
+ #test if error is allways 0
95
+ self.assertEqual(err, 0)
96
+
97
+ #update block
98
+ err = S.update(2)
99
+
100
+ #test if update was correct
101
+ self.assertEqual(S.get(0), f(2))
102
+
103
+ #test if error is allways 0
104
+ self.assertEqual(err, 0)
105
+
106
+ #update block
107
+ err = S.update(3)
108
+
109
+ #test if update was correct
110
+ self.assertEqual(S.get(0), f(3))
111
+
112
+ #test if error is allways 0
113
+ self.assertEqual(err, 0)
114
+
115
+
116
+ # RUN TESTS LOCALLY ====================================================================
117
+
118
+ if __name__ == '__main__':
119
+ unittest.main(verbosity=2)
@@ -0,0 +1,119 @@
1
+ ########################################################################################
2
+ ##
3
+ ## TESTS FOR
4
+ ## 'blocks.spectrum.py'
5
+ ##
6
+ ## Milan Rother 2024
7
+ ##
8
+ ########################################################################################
9
+
10
+ # IMPORTS ==============================================================================
11
+
12
+ import unittest
13
+ import numpy as np
14
+
15
+ from pathsim.blocks.spectrum import Spectrum
16
+
17
+ #base solver for testing
18
+ from pathsim.solvers._solver import Solver
19
+
20
+
21
+ # TESTS ================================================================================
22
+
23
+ class TestSpectrum(unittest.TestCase):
24
+ """
25
+ Test the implementation of the 'Spectrum' block class
26
+ """
27
+
28
+ def test_init(self):
29
+
30
+ #test default initialization
31
+ S = Spectrum()
32
+ self.assertEqual(S.time, 0.0)
33
+ self.assertEqual(S.t_wait, 0.0)
34
+ self.assertEqual(S.alpha, 0.0)
35
+ self.assertEqual(S.labels, [])
36
+ self.assertEqual(len(S.freq), 0)
37
+ self.assertEqual(len(S.omega), 0)
38
+
39
+ #test specific initialization
40
+ _freq = np.linspace(0, 10, 100)
41
+ S = Spectrum(freq=_freq, t_wait=20, alpha=0.01, labels=["1", "2"])
42
+ self.assertEqual(S.time, 0.0)
43
+ self.assertEqual(S.t_wait, 20)
44
+ self.assertEqual(S.alpha, 0.01)
45
+ self.assertEqual(S.labels, ["1", "2"])
46
+ self.assertTrue(np.all(S.freq == _freq))
47
+ self.assertTrue(np.all(S.omega == 2*np.pi*_freq))
48
+
49
+
50
+ def test_len(self):
51
+
52
+ S = Spectrum()
53
+
54
+ #no direct passthrough
55
+ self.assertEqual(len(S), 0)
56
+
57
+
58
+ def test_str(self):
59
+
60
+ S = Spectrum()
61
+
62
+ #test default str method
63
+ self.assertEqual(str(S), "Spectrum")
64
+
65
+
66
+ def test_set_solver(self):
67
+
68
+ S = Spectrum()
69
+
70
+ #test that no solver is initialized
71
+ self.assertEqual(S.engine, None)
72
+
73
+ S.set_solver(Solver, tolerance_lte=1e-6)
74
+
75
+ #test that solver is now available
76
+ self.assertTrue(isinstance(S.engine, Solver))
77
+ self.assertEqual(S.engine.tolerance_lte, 1e-6)
78
+ self.assertEqual(S.engine.initial_value, 0.0)
79
+
80
+ S.set_solver(Solver, tolerance_lte=1e-3)
81
+
82
+ #test that solver tolerance is changed
83
+ self.assertEqual(S.engine.tolerance_lte, 1e-3)
84
+
85
+
86
+ def test_read(self):
87
+
88
+ #test read for no engine and default initialization
89
+ S = Spectrum()
90
+
91
+ freq, spec = S.read()
92
+
93
+ self.assertEqual(len(freq), 0)
94
+ self.assertEqual(len(spec), 0)
95
+
96
+ #test read for no engine and specific initialization
97
+ _freq = np.linspace(0, 10, 100)
98
+ S = Spectrum(freq=_freq)
99
+
100
+ freq, spec = S.read()
101
+
102
+ self.assertTrue(np.all(freq == _freq))
103
+ self.assertTrue(np.all(spec == np.zeros(100)))
104
+
105
+ #test read for engine and specific initialization
106
+ _freq = np.linspace(0, 10, 100)
107
+ S = Spectrum(freq=_freq)
108
+ S.set_solver(Solver)
109
+
110
+ freq, spec = S.read()
111
+
112
+ self.assertTrue(np.all(freq == _freq))
113
+ self.assertTrue(np.all(spec == np.zeros(100)))
114
+
115
+
116
+ # RUN TESTS LOCALLY ====================================================================
117
+
118
+ if __name__ == '__main__':
119
+ unittest.main(verbosity=2)
File without changes
@@ -0,0 +1,364 @@
1
+ ########################################################################################
2
+ ##
3
+ ## TESTS FOR
4
+ ## 'solvers/bdf.py'
5
+ ##
6
+ ## Milan Rother 2024
7
+ ##
8
+ ########################################################################################
9
+
10
+ # IMPORTS ==============================================================================
11
+
12
+ import unittest
13
+ import numpy as np
14
+
15
+ from pathsim.solvers.bdf import BDF2, BDF3, BDF4
16
+
17
+
18
+ # TEST PROBLEMS ========================================================================
19
+
20
+ class Problem:
21
+ def __init__(self, name, func, jac, x0, solution):
22
+ self.name = name
23
+ self.func = func
24
+ self.jac = jac
25
+ self.x0 = x0
26
+ self.solution = solution
27
+
28
+
29
+ #create some reference problems for testing
30
+ reference_problems = [
31
+ Problem(name="linear_feedback",
32
+ func=lambda x, u, t: -x,
33
+ jac=lambda x, u, t: -1,
34
+ x0=1.0,
35
+ solution=lambda t: np.exp(-t)
36
+ ),
37
+ Problem(name="logistic",
38
+ func=lambda x, u, t: x*(1-x),
39
+ jac=lambda x, u, t: 1-2*x,
40
+ x0=0.5,
41
+ solution=lambda t: 1/(1 + np.exp(-t))
42
+ )
43
+ ]
44
+
45
+
46
+
47
+
48
+
49
+ # TESTS ================================================================================
50
+
51
+ class TestBDF2(unittest.TestCase):
52
+ """
53
+ Test the implementation of the 'BDF2' solver class
54
+ """
55
+
56
+ def test_init(self):
57
+
58
+ #test default initializtion
59
+ solver = BDF2()
60
+
61
+ self.assertTrue(callable(solver.func))
62
+ self.assertEqual(solver.jac, None)
63
+ self.assertEqual(solver.initial_value, 0)
64
+
65
+ self.assertEqual(solver.stage, 0)
66
+ self.assertFalse(solver.is_adaptive)
67
+ self.assertTrue(solver.is_implicit)
68
+ self.assertFalse(solver.is_explicit)
69
+
70
+ #test specific initialization
71
+ solver = BDF2(initial_value=1,
72
+ func=lambda x, u, t: -x,
73
+ jac=lambda x, u, t: -1,
74
+ tolerance_lte=1e-6)
75
+
76
+ self.assertEqual(solver.func(2, 0, 0), -2)
77
+ self.assertEqual(solver.jac(2, 0, 0), -1)
78
+ self.assertEqual(solver.initial_value, 1)
79
+ self.assertEqual(solver.tolerance_lte, 1e-6)
80
+
81
+
82
+ def test_stages(self):
83
+
84
+ solver = BDF2()
85
+
86
+ for i, t in enumerate(solver.stages(0, 1)):
87
+
88
+ #test the stage iterator
89
+ self.assertEqual(t, solver.eval_stages[i])
90
+
91
+
92
+ def test_buffer(self):
93
+
94
+ solver = BDF2()
95
+
96
+ #perform some steps
97
+ for k in range(10):
98
+
99
+ #test bdf buffer length
100
+ buffer_length = len(solver.B)
101
+ self.assertEqual(buffer_length, k+1 if k < 2 else 2)
102
+
103
+ #make one step
104
+ for i, t in enumerate(solver.stages(0, 1)):
105
+ success, err, scale = solver.step(0.0, t, 1)
106
+
107
+
108
+ def test_step(self):
109
+
110
+ solver = BDF2()
111
+
112
+ for i, t in enumerate(solver.stages(0, 1)):
113
+
114
+ #test if stage incrementation works
115
+ self.assertEqual(solver.stage, i)
116
+
117
+ success, err, scale = solver.step(0.0, t, 1)
118
+
119
+ #test if expected return at intermediate stages
120
+ self.assertTrue(success)
121
+ self.assertEqual(err, 0.0)
122
+ self.assertEqual(scale, 1.0)
123
+
124
+
125
+ def test_integrate_fixed(self):
126
+
127
+ #integrate test problem and assess convergence order
128
+
129
+ timesteps = np.logspace(-2, -1, 10)
130
+
131
+ for problem in reference_problems:
132
+
133
+ solver = BDF2(problem.x0, problem.func, problem.jac)
134
+
135
+ errors = []
136
+
137
+ for dt in timesteps:
138
+
139
+ solver.reset()
140
+ time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=dt, adaptive=False)
141
+
142
+ errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
143
+
144
+ #test if errors are monotonically decreasing
145
+ self.assertTrue(np.all(np.diff(errors)>0))
146
+
147
+ #test convergence order, expected 1
148
+ p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
149
+ self.assertGreater(p, 1)
150
+
151
+
152
+
153
+ class TestBDF3(unittest.TestCase):
154
+ """
155
+ Test the implementation of the 'BDF3' solver class
156
+ """
157
+
158
+ def test_init(self):
159
+
160
+ #test default initializtion
161
+ solver = BDF3()
162
+
163
+ self.assertTrue(callable(solver.func))
164
+ self.assertEqual(solver.jac, None)
165
+ self.assertEqual(solver.initial_value, 0)
166
+
167
+ self.assertEqual(solver.stage, 0)
168
+ self.assertFalse(solver.is_adaptive)
169
+ self.assertTrue(solver.is_implicit)
170
+ self.assertFalse(solver.is_explicit)
171
+
172
+ #test specific initialization
173
+ solver = BDF3(initial_value=1,
174
+ func=lambda x, u, t: -x,
175
+ jac=lambda x, u, t: -1,
176
+ tolerance_lte=1e-6)
177
+
178
+ self.assertEqual(solver.func(2, 0, 0), -2)
179
+ self.assertEqual(solver.jac(2, 0, 0), -1)
180
+ self.assertEqual(solver.initial_value, 1)
181
+ self.assertEqual(solver.tolerance_lte, 1e-6)
182
+
183
+
184
+ def test_stages(self):
185
+
186
+ solver = BDF3()
187
+
188
+ for i, t in enumerate(solver.stages(0, 1)):
189
+
190
+ #test the stage iterator
191
+ self.assertEqual(t, solver.eval_stages[i])
192
+
193
+
194
+ def test_buffer(self):
195
+
196
+ solver = BDF3()
197
+
198
+ #perform some steps
199
+ for k in range(10):
200
+
201
+ #test bdf buffer length
202
+ buffer_length = len(solver.B)
203
+ self.assertEqual(buffer_length, k+1 if k < 3 else 3)
204
+
205
+ #make one step
206
+ for i, t in enumerate(solver.stages(0, 1)):
207
+ success, err, scale = solver.step(0.0, t, 1)
208
+
209
+
210
+ def test_step(self):
211
+
212
+ solver = BDF3()
213
+
214
+ for i, t in enumerate(solver.stages(0, 1)):
215
+
216
+ #test if stage incrementation works
217
+ self.assertEqual(solver.stage, i)
218
+
219
+ success, err, scale = solver.step(0.0, t, 1)
220
+
221
+ #test if expected return at intermediate stages
222
+ self.assertTrue(success)
223
+ self.assertEqual(err, 0.0)
224
+ self.assertEqual(scale, 1.0)
225
+
226
+
227
+ def test_integrate_fixed(self):
228
+
229
+ #integrate test problem and assess convergence order
230
+
231
+ timesteps = np.logspace(-2, -1, 10)
232
+
233
+ for problem in reference_problems:
234
+
235
+ solver = BDF3(problem.x0, problem.func, problem.jac)
236
+
237
+ errors = []
238
+
239
+ for dt in timesteps:
240
+
241
+ solver.reset()
242
+ time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=dt, adaptive=False)
243
+
244
+ errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
245
+
246
+ #test if errors are monotonically decreasing
247
+ self.assertTrue(np.all(np.diff(errors)>0))
248
+
249
+ #test convergence order, expected 1
250
+ p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
251
+ self.assertGreater(p, 1)
252
+
253
+
254
+
255
+ class TestBDF4(unittest.TestCase):
256
+ """
257
+ Test the implementation of the 'BDF4' solver class
258
+ """
259
+
260
+ def test_init(self):
261
+
262
+ #test default initializtion
263
+ solver = BDF4()
264
+
265
+ self.assertTrue(callable(solver.func))
266
+ self.assertEqual(solver.jac, None)
267
+ self.assertEqual(solver.initial_value, 0)
268
+
269
+ self.assertEqual(solver.stage, 0)
270
+ self.assertFalse(solver.is_adaptive)
271
+ self.assertTrue(solver.is_implicit)
272
+ self.assertFalse(solver.is_explicit)
273
+
274
+ #test specific initialization
275
+ solver = BDF4(initial_value=1,
276
+ func=lambda x, u, t: -x,
277
+ jac=lambda x, u, t: -1,
278
+ tolerance_lte=1e-6)
279
+
280
+ self.assertEqual(solver.func(2, 0, 0), -2)
281
+ self.assertEqual(solver.jac(2, 0, 0), -1)
282
+ self.assertEqual(solver.initial_value, 1)
283
+ self.assertEqual(solver.tolerance_lte, 1e-6)
284
+
285
+
286
+ def test_stages(self):
287
+
288
+ solver = BDF4()
289
+
290
+ for i, t in enumerate(solver.stages(0, 1)):
291
+
292
+ #test the stage iterator
293
+ self.assertEqual(t, solver.eval_stages[i])
294
+
295
+
296
+ def test_buffer(self):
297
+
298
+ solver = BDF4()
299
+
300
+ #perform some steps
301
+ for k in range(10):
302
+
303
+ #test bdf buffer length
304
+ buffer_length = len(solver.B)
305
+ self.assertEqual(buffer_length, k+1 if k < 4 else 4)
306
+
307
+ #make one step
308
+ for i, t in enumerate(solver.stages(0, 1)):
309
+ success, err, scale = solver.step(0.0, t, 1)
310
+
311
+
312
+ def test_step(self):
313
+
314
+ solver = BDF4()
315
+
316
+ for i, t in enumerate(solver.stages(0, 1)):
317
+
318
+ #test if stage incrementation works
319
+ self.assertEqual(solver.stage, i)
320
+
321
+ success, err, scale = solver.step(0.0, t, 1)
322
+
323
+ #test if expected return at intermediate stages
324
+ self.assertTrue(success)
325
+ self.assertEqual(err, 0.0)
326
+ self.assertEqual(scale, 1.0)
327
+
328
+
329
+ def test_integrate_fixed(self):
330
+
331
+ #integrate test problem and assess convergence order
332
+
333
+ timesteps = np.logspace(-2, -1, 10)
334
+
335
+ for problem in reference_problems:
336
+
337
+ solver = BDF4(problem.x0, problem.func, problem.jac)
338
+
339
+ errors = []
340
+
341
+ for dt in timesteps:
342
+
343
+ solver.reset()
344
+ time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=dt, adaptive=False)
345
+
346
+ errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
347
+
348
+ #test if errors are monotonically decreasing
349
+ self.assertTrue(np.all(np.diff(errors)>0))
350
+
351
+ #test convergence order, expected 1
352
+ p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
353
+ self.assertGreater(p, 1)
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+ # RUN TESTS LOCALLY ====================================================================
362
+
363
+ if __name__ == '__main__':
364
+ unittest.main(verbosity=2)