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,138 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/rk4.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.rk4 import RK4
|
|
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 TestRK4(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'RK4' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = RK4()
|
|
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 = RK4(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 = RK4()
|
|
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 = RK4()
|
|
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 = RK4(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 4
|
|
130
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
131
|
+
self.assertEqual(round(p), 4)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
136
|
+
|
|
137
|
+
if __name__ == '__main__':
|
|
138
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/rkbs32.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.rkbs32 import RKBS32
|
|
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
|
+
# TESTS ================================================================================
|
|
49
|
+
|
|
50
|
+
class TestRKBS32(unittest.TestCase):
|
|
51
|
+
"""
|
|
52
|
+
Test the implementation of the 'RKBS32' solver class
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def test_init(self):
|
|
56
|
+
|
|
57
|
+
#test default initializtion
|
|
58
|
+
solver = RKBS32()
|
|
59
|
+
|
|
60
|
+
self.assertTrue(callable(solver.func))
|
|
61
|
+
self.assertEqual(solver.jac, None)
|
|
62
|
+
self.assertEqual(solver.initial_value, 0)
|
|
63
|
+
|
|
64
|
+
self.assertEqual(solver.stage, 0)
|
|
65
|
+
self.assertTrue(solver.is_adaptive)
|
|
66
|
+
self.assertTrue(solver.is_explicit)
|
|
67
|
+
self.assertFalse(solver.is_implicit)
|
|
68
|
+
|
|
69
|
+
#test specific initialization
|
|
70
|
+
solver = RKBS32(initial_value=1,
|
|
71
|
+
func=lambda x, u, t: -x,
|
|
72
|
+
jac=lambda x, u, t: -1,
|
|
73
|
+
tolerance_lte=1e-6)
|
|
74
|
+
|
|
75
|
+
self.assertEqual(solver.func(2, 0, 0), -2)
|
|
76
|
+
self.assertEqual(solver.jac(2, 0, 0), -1)
|
|
77
|
+
self.assertEqual(solver.initial_value, 1)
|
|
78
|
+
self.assertEqual(solver.tolerance_lte, 1e-6)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_stages(self):
|
|
82
|
+
|
|
83
|
+
solver = RKBS32()
|
|
84
|
+
|
|
85
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
86
|
+
|
|
87
|
+
#test the stage iterator
|
|
88
|
+
self.assertEqual(t, solver.eval_stages[i])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_step(self):
|
|
92
|
+
|
|
93
|
+
solver = RKBS32()
|
|
94
|
+
|
|
95
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
96
|
+
|
|
97
|
+
#test if stage incrementation works
|
|
98
|
+
self.assertEqual(solver.stage, i)
|
|
99
|
+
|
|
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, 20)
|
|
118
|
+
|
|
119
|
+
for problem in reference_problems:
|
|
120
|
+
|
|
121
|
+
solver = RKBS32(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
|
|
136
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
137
|
+
self.assertEqual(round(p), 3)
|
|
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 = RKBS32(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=0.1, adaptive=True)
|
|
149
|
+
error = np.linalg.norm(numerical_solution - problem.solution(time))
|
|
150
|
+
|
|
151
|
+
#test if error control was successful (same OOM for global error -> < 1e-5)
|
|
152
|
+
self.assertLess(error, solver.tolerance_lte*10)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
157
|
+
|
|
158
|
+
if __name__ == '__main__':
|
|
159
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/rkck54.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.rkck54 import RKCK54
|
|
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 TestRKCK54(unittest.TestCase):
|
|
49
|
+
"""
|
|
50
|
+
Test the implementation of the 'RKCK54' solver class
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def test_init(self):
|
|
54
|
+
|
|
55
|
+
#test default initializtion
|
|
56
|
+
solver = RKCK54()
|
|
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_explicit)
|
|
65
|
+
self.assertFalse(solver.is_implicit)
|
|
66
|
+
|
|
67
|
+
#test specific initialization
|
|
68
|
+
solver = RKCK54(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 = RKCK54()
|
|
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 = RKCK54()
|
|
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
|
+
success, err, scale = solver.step(0.0, t, 1)
|
|
99
|
+
|
|
100
|
+
#test if expected return at intermediate stages
|
|
101
|
+
if i < len(solver.eval_stages)-1:
|
|
102
|
+
self.assertTrue(success)
|
|
103
|
+
self.assertEqual(err, 0.0)
|
|
104
|
+
self.assertEqual(scale, 1.0)
|
|
105
|
+
|
|
106
|
+
#test if expected return at final stage
|
|
107
|
+
self.assertNotEqual(err, 0.0)
|
|
108
|
+
self.assertNotEqual(scale, 1.0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_integrate_fixed(self):
|
|
112
|
+
|
|
113
|
+
#integrate test problem and assess convergence order
|
|
114
|
+
|
|
115
|
+
timesteps = np.logspace(-2, -1, 20)
|
|
116
|
+
|
|
117
|
+
for problem in reference_problems:
|
|
118
|
+
|
|
119
|
+
solver = RKCK54(problem.x0, problem.func, problem.jac)
|
|
120
|
+
|
|
121
|
+
errors = []
|
|
122
|
+
|
|
123
|
+
for dt in timesteps:
|
|
124
|
+
|
|
125
|
+
solver.reset()
|
|
126
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=dt, adaptive=False)
|
|
127
|
+
|
|
128
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
129
|
+
|
|
130
|
+
#test if errors are monotonically decreasing
|
|
131
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
132
|
+
|
|
133
|
+
#test convergence order, expected 5
|
|
134
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
135
|
+
self.assertEqual(np.round(p), 5)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_integrate_adaptive(self):
|
|
139
|
+
|
|
140
|
+
#test the error control for each reference problem
|
|
141
|
+
|
|
142
|
+
for problem in reference_problems:
|
|
143
|
+
|
|
144
|
+
solver = RKCK54(problem.x0, problem.func, problem.jac, tolerance_lte=1e-6)
|
|
145
|
+
|
|
146
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=1, adaptive=True)
|
|
147
|
+
error = np.linalg.norm(numerical_solution - problem.solution(time))
|
|
148
|
+
|
|
149
|
+
#test if error control was successful
|
|
150
|
+
self.assertLess(error, solver.tolerance_lte)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
155
|
+
|
|
156
|
+
if __name__ == '__main__':
|
|
157
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/rkdp54.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.rkdp54 import RKDP54
|
|
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
|
+
# TESTS ================================================================================
|
|
48
|
+
|
|
49
|
+
class TestRKDP54(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'RKDP54' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = RKDP54()
|
|
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_explicit)
|
|
66
|
+
self.assertFalse(solver.is_implicit)
|
|
67
|
+
|
|
68
|
+
#test specific initialization
|
|
69
|
+
solver = RKDP54(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 = RKDP54()
|
|
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 = RKDP54()
|
|
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
|
+
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(-2, -0.2, 20)
|
|
117
|
+
|
|
118
|
+
for problem in reference_problems:
|
|
119
|
+
|
|
120
|
+
solver = RKDP54(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 5
|
|
135
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
136
|
+
self.assertGreater(p, 4)
|
|
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 = RKDP54(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
|
+
|
|
155
|
+
|
|
156
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
157
|
+
|
|
158
|
+
if __name__ == '__main__':
|
|
159
|
+
unittest.main(verbosity=2)
|