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,157 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/rkdp87.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.rkdp87 import RKDP87
|
|
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 TestRKDP87(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'RKDP87' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = RKDP87()
|
|
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 = RKDP87(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 = RKDP87()
|
|
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 = RKDP87()
|
|
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(-0.4, 0, 20)
|
|
117
|
+
|
|
118
|
+
for problem in reference_problems:
|
|
119
|
+
|
|
120
|
+
solver = RKDP87(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 8
|
|
135
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
136
|
+
self.assertGreater(p, 8)
|
|
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 = RKDP87(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,159 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/rkf45.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.rkf45 import RKF45
|
|
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 TestRKF45(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'RKF45' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = RKF45()
|
|
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 = RKF45(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 = RKF45()
|
|
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 = RKF45()
|
|
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, -1, 20)
|
|
117
|
+
|
|
118
|
+
for problem in reference_problems:
|
|
119
|
+
|
|
120
|
+
solver = RKF45(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=1.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 4
|
|
135
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
136
|
+
self.assertEqual(np.round(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 = RKF45(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)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/rkf78.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.rkf78 import RKF78
|
|
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 TestRKF78(unittest.TestCase):
|
|
52
|
+
"""
|
|
53
|
+
Test the implementation of the 'RKF78' solver class
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def test_init(self):
|
|
57
|
+
|
|
58
|
+
#test default initializtion
|
|
59
|
+
solver = RKF78()
|
|
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.assertTrue(solver.is_adaptive)
|
|
67
|
+
self.assertTrue(solver.is_explicit)
|
|
68
|
+
self.assertFalse(solver.is_implicit)
|
|
69
|
+
|
|
70
|
+
#test specific initialization
|
|
71
|
+
solver = RKF78(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 = RKF78()
|
|
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_step(self):
|
|
93
|
+
|
|
94
|
+
solver = RKF78()
|
|
95
|
+
|
|
96
|
+
for i, t in enumerate(solver.stages(0, 1)):
|
|
97
|
+
|
|
98
|
+
#test if stage incrementation works
|
|
99
|
+
self.assertEqual(solver.stage, i)
|
|
100
|
+
|
|
101
|
+
success, err, scale = solver.step(0.0, t, 1)
|
|
102
|
+
|
|
103
|
+
#test if expected return at intermediate stages
|
|
104
|
+
if i < len(solver.eval_stages)-1:
|
|
105
|
+
self.assertTrue(success)
|
|
106
|
+
self.assertEqual(err, 0.0)
|
|
107
|
+
self.assertEqual(scale, 1.0)
|
|
108
|
+
|
|
109
|
+
#test if expected return at final stage
|
|
110
|
+
self.assertNotEqual(err, 0.0)
|
|
111
|
+
self.assertNotEqual(scale, 1.0)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_integrate_fixed(self):
|
|
115
|
+
|
|
116
|
+
#integrate test problem and assess convergence order
|
|
117
|
+
|
|
118
|
+
timesteps = np.logspace(-1, 0, 20)
|
|
119
|
+
|
|
120
|
+
for problem in reference_problems:
|
|
121
|
+
|
|
122
|
+
solver = RKF78(problem.x0, problem.func, problem.jac)
|
|
123
|
+
|
|
124
|
+
errors = []
|
|
125
|
+
|
|
126
|
+
for dt in timesteps:
|
|
127
|
+
|
|
128
|
+
solver.reset()
|
|
129
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=dt, adaptive=False)
|
|
130
|
+
|
|
131
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
132
|
+
|
|
133
|
+
#test if errors are monotonically decreasing
|
|
134
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
135
|
+
|
|
136
|
+
#test convergence order, expected 7 or 8
|
|
137
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
138
|
+
self.assertGreater(p, 6.5)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_integrate_adaptive(self):
|
|
142
|
+
|
|
143
|
+
#test the error control for each reference problem
|
|
144
|
+
|
|
145
|
+
for problem in reference_problems:
|
|
146
|
+
|
|
147
|
+
solver = RKF78(problem.x0, problem.func, problem.jac, tolerance_lte=1e-6)
|
|
148
|
+
|
|
149
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=1.0, dt=1, adaptive=True)
|
|
150
|
+
error = np.linalg.norm(numerical_solution - problem.solution(time))
|
|
151
|
+
|
|
152
|
+
#test if error control was successful
|
|
153
|
+
self.assertLess(error, solver.tolerance_lte)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
158
|
+
|
|
159
|
+
if __name__ == '__main__':
|
|
160
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/rkv65.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.rkv65 import RKV65
|
|
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 TestRKV65(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'RKV65' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = RKV65()
|
|
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 = RKV65(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 = RKV65()
|
|
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 = RKV65()
|
|
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(-1, -0.2, 20)
|
|
117
|
+
|
|
118
|
+
for problem in reference_problems:
|
|
119
|
+
|
|
120
|
+
solver = RKV65(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=1.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 4
|
|
135
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
136
|
+
self.assertEqual(np.round(p), 6)
|
|
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 = RKV65(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
|
+
|
|
157
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
158
|
+
|
|
159
|
+
if __name__ == '__main__':
|
|
160
|
+
unittest.main(verbosity=2)
|