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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/esdirk43.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.esdirk43 import ESDIRK43
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# TEST PROBLEMS ========================================================================
|
|
20
|
+
|
|
21
|
+
class Problem:
|
|
22
|
+
def __init__(self, name, func, jac, x0, solution):
|
|
23
|
+
self.name = name
|
|
24
|
+
self.func = func
|
|
25
|
+
self.jac = jac
|
|
26
|
+
self.x0 = x0
|
|
27
|
+
self.solution = solution
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
#create some reference problems for testing
|
|
31
|
+
reference_problems = [
|
|
32
|
+
Problem(name="linear_feedback",
|
|
33
|
+
func=lambda x, u, t: -x,
|
|
34
|
+
jac=lambda x, u, t: -1,
|
|
35
|
+
x0=1.0,
|
|
36
|
+
solution=lambda t: np.exp(-t)
|
|
37
|
+
),
|
|
38
|
+
Problem(name="logistic",
|
|
39
|
+
func=lambda x, u, t: x*(1-x),
|
|
40
|
+
jac=lambda x, u, t: 1-2*x,
|
|
41
|
+
x0=0.5,
|
|
42
|
+
solution=lambda t: 1/(1 + np.exp(-t))
|
|
43
|
+
)
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# TESTS ================================================================================
|
|
48
|
+
|
|
49
|
+
class TestESDIRK43(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'ESDIRK43' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = ESDIRK43()
|
|
58
|
+
|
|
59
|
+
self.assertTrue(callable(solver.func))
|
|
60
|
+
self.assertEqual(solver.jac, None)
|
|
61
|
+
self.assertEqual(solver.initial_value, 0)
|
|
62
|
+
|
|
63
|
+
self.assertEqual(solver.stage, 0)
|
|
64
|
+
self.assertTrue(solver.is_adaptive)
|
|
65
|
+
self.assertTrue(solver.is_implicit)
|
|
66
|
+
self.assertFalse(solver.is_explicit)
|
|
67
|
+
|
|
68
|
+
#test specific initialization
|
|
69
|
+
solver = ESDIRK43(initial_value=1,
|
|
70
|
+
func=lambda x, u, t: -x,
|
|
71
|
+
jac=lambda x, u, t: -1,
|
|
72
|
+
tolerance_lte=1e-6)
|
|
73
|
+
|
|
74
|
+
self.assertEqual(solver.func(2, 0, 0), -2)
|
|
75
|
+
self.assertEqual(solver.jac(2, 0, 0), -1)
|
|
76
|
+
self.assertEqual(solver.initial_value, 1)
|
|
77
|
+
self.assertEqual(solver.tolerance_lte, 1e-6)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_stages(self):
|
|
81
|
+
|
|
82
|
+
solver = ESDIRK43()
|
|
83
|
+
|
|
84
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
85
|
+
|
|
86
|
+
#test the stage iterator
|
|
87
|
+
self.assertEqual(t, solver.eval_stages[i])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_step(self):
|
|
91
|
+
|
|
92
|
+
solver = ESDIRK43()
|
|
93
|
+
|
|
94
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
95
|
+
|
|
96
|
+
#test if stage incrementation works
|
|
97
|
+
self.assertEqual(solver.stage, i)
|
|
98
|
+
|
|
99
|
+
_ = solver.solve(0.0, t, 1) #needed for implicit solvers to get slope
|
|
100
|
+
success, err, scale = solver.step(0.0, t, 1)
|
|
101
|
+
|
|
102
|
+
#test if expected return at intermediate stages
|
|
103
|
+
if i < len(solver.eval_stages)-1:
|
|
104
|
+
self.assertTrue(success)
|
|
105
|
+
self.assertEqual(err, 0.0)
|
|
106
|
+
self.assertEqual(scale, 1.0)
|
|
107
|
+
|
|
108
|
+
#test if expected return at final stage
|
|
109
|
+
self.assertNotEqual(err, 0.0)
|
|
110
|
+
self.assertNotEqual(scale, 1.0)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_integrate_fixed(self):
|
|
114
|
+
|
|
115
|
+
#integrate test problem and assess convergence order
|
|
116
|
+
|
|
117
|
+
timesteps = np.logspace(-0.4, 0, 10)
|
|
118
|
+
|
|
119
|
+
for problem in reference_problems:
|
|
120
|
+
|
|
121
|
+
solver = ESDIRK43(problem.x0, problem.func, problem.jac)
|
|
122
|
+
|
|
123
|
+
errors = []
|
|
124
|
+
|
|
125
|
+
for dt in timesteps:
|
|
126
|
+
|
|
127
|
+
solver.reset()
|
|
128
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=3.0, dt=dt, adaptive=False)
|
|
129
|
+
|
|
130
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
131
|
+
|
|
132
|
+
#test if errors are monotonically decreasing
|
|
133
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
134
|
+
|
|
135
|
+
#test convergence order, expected 3 or 4
|
|
136
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
137
|
+
self.assertEqual(round(p), 4)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_integrate_adaptive(self):
|
|
141
|
+
|
|
142
|
+
#test the error control for each reference problem
|
|
143
|
+
|
|
144
|
+
for problem in reference_problems:
|
|
145
|
+
|
|
146
|
+
solver = ESDIRK43(problem.x0, problem.func, problem.jac, tolerance_lte=1e-6)
|
|
147
|
+
|
|
148
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=2.0, dt=1, adaptive=True)
|
|
149
|
+
error = np.linalg.norm(numerical_solution - problem.solution(time))
|
|
150
|
+
|
|
151
|
+
#test if error control was successful (same OOM, since global error)
|
|
152
|
+
self.assertLess(error, solver.tolerance_lte*3)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
156
|
+
|
|
157
|
+
if __name__ == '__main__':
|
|
158
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/esdirk54.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.esdirk54 import ESDIRK54
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# TEST PROBLEMS ========================================================================
|
|
20
|
+
|
|
21
|
+
class Problem:
|
|
22
|
+
def __init__(self, name, func, jac, x0, solution):
|
|
23
|
+
self.name = name
|
|
24
|
+
self.func = func
|
|
25
|
+
self.jac = jac
|
|
26
|
+
self.x0 = x0
|
|
27
|
+
self.solution = solution
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
#create some reference problems for testing
|
|
31
|
+
reference_problems = [
|
|
32
|
+
Problem(name="linear_feedback",
|
|
33
|
+
func=lambda x, u, t: -x,
|
|
34
|
+
jac=lambda x, u, t: -1,
|
|
35
|
+
x0=1.0,
|
|
36
|
+
solution=lambda t: np.exp(-t)
|
|
37
|
+
),
|
|
38
|
+
Problem(name="logistic",
|
|
39
|
+
func=lambda x, u, t: x*(1-x),
|
|
40
|
+
jac=lambda x, u, t: 1-2*x,
|
|
41
|
+
x0=0.5,
|
|
42
|
+
solution=lambda t: 1/(1 + np.exp(-t))
|
|
43
|
+
)
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# TESTS ================================================================================
|
|
48
|
+
|
|
49
|
+
class TestESDIRK54(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'ESDIRK54' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = ESDIRK54()
|
|
58
|
+
|
|
59
|
+
self.assertTrue(callable(solver.func))
|
|
60
|
+
self.assertEqual(solver.jac, None)
|
|
61
|
+
self.assertEqual(solver.initial_value, 0)
|
|
62
|
+
|
|
63
|
+
self.assertEqual(solver.stage, 0)
|
|
64
|
+
self.assertTrue(solver.is_adaptive)
|
|
65
|
+
self.assertTrue(solver.is_implicit)
|
|
66
|
+
self.assertFalse(solver.is_explicit)
|
|
67
|
+
|
|
68
|
+
#test specific initialization
|
|
69
|
+
solver = ESDIRK54(initial_value=1,
|
|
70
|
+
func=lambda x, u, t: -x,
|
|
71
|
+
jac=lambda x, u, t: -1,
|
|
72
|
+
tolerance_lte=1e-6)
|
|
73
|
+
|
|
74
|
+
self.assertEqual(solver.func(2, 0, 0), -2)
|
|
75
|
+
self.assertEqual(solver.jac(2, 0, 0), -1)
|
|
76
|
+
self.assertEqual(solver.initial_value, 1)
|
|
77
|
+
self.assertEqual(solver.tolerance_lte, 1e-6)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_stages(self):
|
|
81
|
+
|
|
82
|
+
solver = ESDIRK54()
|
|
83
|
+
|
|
84
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
85
|
+
|
|
86
|
+
#test the stage iterator
|
|
87
|
+
self.assertEqual(t, solver.eval_stages[i])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_step(self):
|
|
91
|
+
|
|
92
|
+
solver = ESDIRK54()
|
|
93
|
+
|
|
94
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
95
|
+
|
|
96
|
+
#test if stage incrementation works
|
|
97
|
+
self.assertEqual(solver.stage, i)
|
|
98
|
+
|
|
99
|
+
_ = solver.solve(0.0, t, 1) #needed for implicit solvers to get slope
|
|
100
|
+
success, err, scale = solver.step(0.0, t, 1)
|
|
101
|
+
|
|
102
|
+
#test if expected return at intermediate stages
|
|
103
|
+
if i < len(solver.eval_stages)-1:
|
|
104
|
+
self.assertTrue(success)
|
|
105
|
+
self.assertEqual(err, 0.0)
|
|
106
|
+
self.assertEqual(scale, 1.0)
|
|
107
|
+
|
|
108
|
+
#test if expected return at final stage
|
|
109
|
+
self.assertNotEqual(err, 0.0)
|
|
110
|
+
self.assertNotEqual(scale, 1.0)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_integrate_fixed(self):
|
|
114
|
+
|
|
115
|
+
#integrate test problem and assess convergence order
|
|
116
|
+
|
|
117
|
+
timesteps = np.logspace(-0.4, 0, 10)
|
|
118
|
+
|
|
119
|
+
for problem in reference_problems:
|
|
120
|
+
|
|
121
|
+
solver = ESDIRK54(problem.x0, problem.func, problem.jac)
|
|
122
|
+
|
|
123
|
+
errors = []
|
|
124
|
+
|
|
125
|
+
for dt in timesteps:
|
|
126
|
+
|
|
127
|
+
solver.reset()
|
|
128
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=3.0, dt=dt, adaptive=False)
|
|
129
|
+
|
|
130
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
131
|
+
|
|
132
|
+
#test if errors are monotonically decreasing
|
|
133
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
134
|
+
|
|
135
|
+
#test convergence order, expected 4 or 5
|
|
136
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
137
|
+
self.assertGreater(p, 4)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_integrate_adaptive(self):
|
|
141
|
+
|
|
142
|
+
#test the error control for each reference problem
|
|
143
|
+
|
|
144
|
+
for problem in reference_problems:
|
|
145
|
+
|
|
146
|
+
solver = ESDIRK54(problem.x0, problem.func, problem.jac, tolerance_lte=1e-6)
|
|
147
|
+
|
|
148
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=1, adaptive=True)
|
|
149
|
+
error = np.linalg.norm(numerical_solution - problem.solution(time))
|
|
150
|
+
|
|
151
|
+
#test if error control was successful
|
|
152
|
+
self.assertLess(error, solver.tolerance_lte)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
158
|
+
|
|
159
|
+
if __name__ == '__main__':
|
|
160
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/esdirk85.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.esdirk85 import ESDIRK85
|
|
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
|
+
# TESTS ================================================================================
|
|
47
|
+
|
|
48
|
+
class TestESDIRK85(unittest.TestCase):
|
|
49
|
+
"""
|
|
50
|
+
Test the implementation of the 'ESDIRK85' solver class
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def test_init(self):
|
|
54
|
+
|
|
55
|
+
#test default initializtion
|
|
56
|
+
solver = ESDIRK85()
|
|
57
|
+
|
|
58
|
+
self.assertTrue(callable(solver.func))
|
|
59
|
+
self.assertEqual(solver.jac, None)
|
|
60
|
+
self.assertEqual(solver.initial_value, 0)
|
|
61
|
+
|
|
62
|
+
self.assertEqual(solver.stage, 0)
|
|
63
|
+
self.assertTrue(solver.is_adaptive)
|
|
64
|
+
self.assertTrue(solver.is_implicit)
|
|
65
|
+
self.assertFalse(solver.is_explicit)
|
|
66
|
+
|
|
67
|
+
#test specific initialization
|
|
68
|
+
solver = ESDIRK85(initial_value=1,
|
|
69
|
+
func=lambda x, u, t: -x,
|
|
70
|
+
jac=lambda x, u, t: -1,
|
|
71
|
+
tolerance_lte=1e-6)
|
|
72
|
+
|
|
73
|
+
self.assertEqual(solver.func(2, 0, 0), -2)
|
|
74
|
+
self.assertEqual(solver.jac(2, 0, 0), -1)
|
|
75
|
+
self.assertEqual(solver.initial_value, 1)
|
|
76
|
+
self.assertEqual(solver.tolerance_lte, 1e-6)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_stages(self):
|
|
80
|
+
|
|
81
|
+
solver = ESDIRK85()
|
|
82
|
+
|
|
83
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
84
|
+
|
|
85
|
+
#test the stage iterator
|
|
86
|
+
self.assertEqual(t, solver.eval_stages[i])
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_step(self):
|
|
90
|
+
|
|
91
|
+
solver = ESDIRK85()
|
|
92
|
+
|
|
93
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
94
|
+
|
|
95
|
+
#test if stage incrementation works
|
|
96
|
+
self.assertEqual(solver.stage, i)
|
|
97
|
+
|
|
98
|
+
_ = solver.solve(0.0, t, 1) #needed for implicit solvers to get slope
|
|
99
|
+
success, err, scale = solver.step(0.0, t, 1)
|
|
100
|
+
|
|
101
|
+
#test if expected return at intermediate stages
|
|
102
|
+
if i < len(solver.eval_stages)-1:
|
|
103
|
+
self.assertTrue(success)
|
|
104
|
+
self.assertEqual(err, 0.0)
|
|
105
|
+
self.assertEqual(scale, 1.0)
|
|
106
|
+
|
|
107
|
+
#test if expected return at final stage
|
|
108
|
+
self.assertNotEqual(err, 0.0)
|
|
109
|
+
self.assertNotEqual(scale, 1.0)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_integrate_fixed(self):
|
|
113
|
+
|
|
114
|
+
#integrate test problem and assess convergence order
|
|
115
|
+
|
|
116
|
+
timesteps = np.logspace(-0.4, 0, 10)
|
|
117
|
+
|
|
118
|
+
for problem in reference_problems:
|
|
119
|
+
|
|
120
|
+
solver = ESDIRK85(problem.x0, problem.func, problem.jac)
|
|
121
|
+
|
|
122
|
+
errors = []
|
|
123
|
+
|
|
124
|
+
for dt in timesteps:
|
|
125
|
+
|
|
126
|
+
solver.reset()
|
|
127
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=3.0, dt=dt, adaptive=False)
|
|
128
|
+
|
|
129
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
130
|
+
|
|
131
|
+
#test if errors are monotonically decreasing
|
|
132
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
133
|
+
|
|
134
|
+
#test convergence order, expected 7 or 8
|
|
135
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
136
|
+
self.assertGreaterEqual(round(p), 7)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_integrate_adaptive(self):
|
|
140
|
+
|
|
141
|
+
#test the error control for each reference problem
|
|
142
|
+
|
|
143
|
+
for problem in reference_problems:
|
|
144
|
+
|
|
145
|
+
solver = ESDIRK85(problem.x0, problem.func, problem.jac, tolerance_lte=1e-6)
|
|
146
|
+
|
|
147
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=1, adaptive=True)
|
|
148
|
+
error = np.linalg.norm(numerical_solution - problem.solution(time))
|
|
149
|
+
|
|
150
|
+
#test if error control was successful
|
|
151
|
+
self.assertLess(error, solver.tolerance_lte)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
155
|
+
|
|
156
|
+
if __name__ == '__main__':
|
|
157
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/euler.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2023/24
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from pathsim.solvers.euler import EUF, EUB
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# TEST PROBLEMS ========================================================================
|
|
20
|
+
|
|
21
|
+
class Problem:
|
|
22
|
+
def __init__(self, name, func, jac, x0, solution):
|
|
23
|
+
self.name = name
|
|
24
|
+
self.func = func
|
|
25
|
+
self.jac = jac
|
|
26
|
+
self.x0 = x0
|
|
27
|
+
self.solution = solution
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
#create some reference problems for testing
|
|
31
|
+
reference_problems = [
|
|
32
|
+
Problem(name="linear_feedback",
|
|
33
|
+
func=lambda x, u, t: -x,
|
|
34
|
+
jac=lambda x, u, t: -1,
|
|
35
|
+
x0=1.0,
|
|
36
|
+
solution=lambda t: np.exp(-t)
|
|
37
|
+
),
|
|
38
|
+
Problem(name="logistic",
|
|
39
|
+
func=lambda x, u, t: x*(1-x),
|
|
40
|
+
jac=lambda x, u, t: 1-2*x,
|
|
41
|
+
x0=0.5,
|
|
42
|
+
solution=lambda t: 1/(1 + np.exp(-t))
|
|
43
|
+
)
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# TESTS ================================================================================
|
|
48
|
+
|
|
49
|
+
class TestEUF(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'EUF' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = EUF()
|
|
58
|
+
|
|
59
|
+
self.assertTrue(callable(solver.func))
|
|
60
|
+
self.assertEqual(solver.jac, None)
|
|
61
|
+
self.assertEqual(solver.initial_value, 0)
|
|
62
|
+
|
|
63
|
+
self.assertEqual(solver.stage, 0)
|
|
64
|
+
self.assertFalse(solver.is_adaptive)
|
|
65
|
+
self.assertTrue(solver.is_explicit)
|
|
66
|
+
self.assertFalse(solver.is_implicit)
|
|
67
|
+
|
|
68
|
+
#test specific initialization
|
|
69
|
+
solver = EUF(initial_value=1,
|
|
70
|
+
func=lambda x, u, t: -x,
|
|
71
|
+
jac=lambda x, u, t: -1,
|
|
72
|
+
tolerance_lte=1e-6)
|
|
73
|
+
|
|
74
|
+
self.assertEqual(solver.func(2, 0, 0), -2)
|
|
75
|
+
self.assertEqual(solver.jac(2, 0, 0), -1)
|
|
76
|
+
self.assertEqual(solver.initial_value, 1)
|
|
77
|
+
self.assertEqual(solver.tolerance_lte, 1e-6)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_stages(self):
|
|
81
|
+
|
|
82
|
+
solver = EUF()
|
|
83
|
+
|
|
84
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
85
|
+
|
|
86
|
+
#test the stage iterator
|
|
87
|
+
self.assertEqual(t, solver.eval_stages[i])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_step(self):
|
|
91
|
+
|
|
92
|
+
solver = EUF()
|
|
93
|
+
|
|
94
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
95
|
+
|
|
96
|
+
#test if stage incrementation works
|
|
97
|
+
self.assertEqual(solver.stage, i)
|
|
98
|
+
|
|
99
|
+
success, err, scale = solver.step(0.0, t, 1)
|
|
100
|
+
|
|
101
|
+
#test if expected return at intermediate stages
|
|
102
|
+
self.assertTrue(success)
|
|
103
|
+
self.assertEqual(err, 0.0)
|
|
104
|
+
self.assertEqual(scale, 1.0)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_integrate_fixed(self):
|
|
108
|
+
|
|
109
|
+
#integrate test problem and assess convergence order
|
|
110
|
+
|
|
111
|
+
timesteps = np.logspace(-0.4, 0, 20)
|
|
112
|
+
|
|
113
|
+
for problem in reference_problems:
|
|
114
|
+
|
|
115
|
+
solver = EUF(problem.x0, problem.func, problem.jac)
|
|
116
|
+
|
|
117
|
+
errors = []
|
|
118
|
+
|
|
119
|
+
for dt in timesteps:
|
|
120
|
+
|
|
121
|
+
solver.reset()
|
|
122
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=3.0, dt=dt, adaptive=False)
|
|
123
|
+
|
|
124
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
125
|
+
|
|
126
|
+
#test if errors are monotonically decreasing
|
|
127
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
128
|
+
|
|
129
|
+
#test convergence order, expected 1
|
|
130
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
131
|
+
self.assertEqual(round(p), 1)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TestEUB(unittest.TestCase):
|
|
136
|
+
"""
|
|
137
|
+
Test the implementation of the 'EUB' solver class
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def test_init(self):
|
|
141
|
+
|
|
142
|
+
#test default initializtion
|
|
143
|
+
solver = EUB()
|
|
144
|
+
|
|
145
|
+
self.assertTrue(callable(solver.func))
|
|
146
|
+
self.assertEqual(solver.jac, None)
|
|
147
|
+
self.assertEqual(solver.initial_value, 0)
|
|
148
|
+
|
|
149
|
+
self.assertEqual(solver.stage, 0)
|
|
150
|
+
self.assertFalse(solver.is_adaptive)
|
|
151
|
+
self.assertTrue(solver.is_implicit)
|
|
152
|
+
self.assertFalse(solver.is_explicit)
|
|
153
|
+
|
|
154
|
+
#test specific initialization
|
|
155
|
+
solver = EUB(initial_value=1,
|
|
156
|
+
func=lambda x, u, t: -x,
|
|
157
|
+
jac=lambda x, u, t: -1,
|
|
158
|
+
tolerance_lte=1e-6)
|
|
159
|
+
|
|
160
|
+
self.assertEqual(solver.func(2, 0, 0), -2)
|
|
161
|
+
self.assertEqual(solver.jac(2, 0, 0), -1)
|
|
162
|
+
self.assertEqual(solver.initial_value, 1)
|
|
163
|
+
self.assertEqual(solver.tolerance_lte, 1e-6)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_stages(self):
|
|
167
|
+
|
|
168
|
+
solver = EUB()
|
|
169
|
+
|
|
170
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
171
|
+
|
|
172
|
+
#test the stage iterator
|
|
173
|
+
self.assertEqual(t, solver.eval_stages[i])
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_step(self):
|
|
177
|
+
|
|
178
|
+
solver = EUB()
|
|
179
|
+
|
|
180
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
181
|
+
|
|
182
|
+
#test if stage incrementation works
|
|
183
|
+
self.assertEqual(solver.stage, i)
|
|
184
|
+
|
|
185
|
+
success, err, scale = solver.step(0.0, t, 1)
|
|
186
|
+
|
|
187
|
+
#test if expected return at intermediate stages
|
|
188
|
+
self.assertTrue(success)
|
|
189
|
+
self.assertEqual(err, 0.0)
|
|
190
|
+
self.assertEqual(scale, 1.0)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_integrate_fixed(self):
|
|
194
|
+
|
|
195
|
+
#integrate test problem and assess convergence order
|
|
196
|
+
|
|
197
|
+
timesteps = np.logspace(-2, -1, 10)
|
|
198
|
+
|
|
199
|
+
for problem in reference_problems:
|
|
200
|
+
|
|
201
|
+
solver = EUB(problem.x0, problem.func, problem.jac)
|
|
202
|
+
|
|
203
|
+
errors = []
|
|
204
|
+
|
|
205
|
+
for dt in timesteps:
|
|
206
|
+
|
|
207
|
+
solver.reset()
|
|
208
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=dt, adaptive=False)
|
|
209
|
+
|
|
210
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
211
|
+
|
|
212
|
+
#test if errors are monotonically decreasing
|
|
213
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
214
|
+
|
|
215
|
+
#test convergence order, expected 1
|
|
216
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
217
|
+
self.assertGreater(p, 0.5)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
221
|
+
|
|
222
|
+
if __name__ == '__main__':
|
|
223
|
+
unittest.main(verbosity=2)
|