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/dirk2.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.dirk2 import DIRK2
|
|
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 TestDIRK2(unittest.TestCase):
|
|
50
|
+
"""
|
|
51
|
+
Test the implementation of the 'DIRK2' solver class
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def test_init(self):
|
|
55
|
+
|
|
56
|
+
#test default initializtion
|
|
57
|
+
solver = DIRK2()
|
|
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_implicit)
|
|
66
|
+
self.assertFalse(solver.is_explicit)
|
|
67
|
+
|
|
68
|
+
#test specific initialization
|
|
69
|
+
solver = DIRK2(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 = DIRK2()
|
|
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 = DIRK2()
|
|
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
|
+
self.assertTrue(success)
|
|
104
|
+
self.assertEqual(err, 0.0)
|
|
105
|
+
self.assertEqual(scale, 1.0)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_integrate_fixed(self):
|
|
109
|
+
|
|
110
|
+
#integrate test problem and assess convergence order
|
|
111
|
+
|
|
112
|
+
timesteps = np.logspace(-1, -0.2, 10)
|
|
113
|
+
|
|
114
|
+
for problem in reference_problems:
|
|
115
|
+
|
|
116
|
+
solver = DIRK2(problem.x0, problem.func, problem.jac)
|
|
117
|
+
|
|
118
|
+
errors = []
|
|
119
|
+
|
|
120
|
+
for dt in timesteps:
|
|
121
|
+
|
|
122
|
+
solver.reset()
|
|
123
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=3.0, dt=dt, adaptive=False)
|
|
124
|
+
|
|
125
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
126
|
+
|
|
127
|
+
#test if errors are monotonically decreasing
|
|
128
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
129
|
+
|
|
130
|
+
#test convergence order, expected 2 or 3
|
|
131
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
132
|
+
self.assertEqual(round(p), 2)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
136
|
+
|
|
137
|
+
if __name__ == '__main__':
|
|
138
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/dirk3.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.dirk3 import DIRK3
|
|
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 TestDIRK3(unittest.TestCase):
|
|
49
|
+
"""
|
|
50
|
+
Test the implementation of the 'DIRK3' solver class
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def test_init(self):
|
|
54
|
+
|
|
55
|
+
#test default initializtion
|
|
56
|
+
solver = DIRK3()
|
|
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.assertFalse(solver.is_adaptive)
|
|
64
|
+
self.assertTrue(solver.is_implicit)
|
|
65
|
+
self.assertFalse(solver.is_explicit)
|
|
66
|
+
|
|
67
|
+
#test specific initialization
|
|
68
|
+
solver = DIRK3(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 = DIRK3()
|
|
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 = DIRK3()
|
|
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
|
+
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(-1, -0.2, 10)
|
|
112
|
+
|
|
113
|
+
for problem in reference_problems:
|
|
114
|
+
|
|
115
|
+
solver = DIRK3(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 2 or 3
|
|
130
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
131
|
+
self.assertGreater(p, 2.4)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
135
|
+
|
|
136
|
+
if __name__ == '__main__':
|
|
137
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/esdirk32.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.esdirk32 import ESDIRK32
|
|
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 TestESDIRK32(unittest.TestCase):
|
|
49
|
+
"""
|
|
50
|
+
Test the implementation of the 'ESDIRK32' solver class
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def test_init(self):
|
|
54
|
+
|
|
55
|
+
#test default initializtion
|
|
56
|
+
solver = ESDIRK32()
|
|
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 = ESDIRK32(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 = ESDIRK32()
|
|
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 = ESDIRK32()
|
|
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(-1, -0.5, 10)
|
|
117
|
+
|
|
118
|
+
for problem in reference_problems:
|
|
119
|
+
|
|
120
|
+
solver = ESDIRK32(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 2 or 3
|
|
135
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
136
|
+
self.assertGreater(p, 2.3)
|
|
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 = ESDIRK32(problem.x0, problem.func, problem.jac, tolerance_lte=1e-6)
|
|
146
|
+
|
|
147
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=2.0, dt=1, adaptive=True)
|
|
148
|
+
error = np.linalg.norm(numerical_solution - problem.solution(time))
|
|
149
|
+
|
|
150
|
+
#test if error control was successful (same OOM, since global error)
|
|
151
|
+
self.assertLess(error, solver.tolerance_lte*3)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
156
|
+
|
|
157
|
+
if __name__ == '__main__':
|
|
158
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/esdirk4.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.esdirk4 import ESDIRK4
|
|
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 TestESDIRK4(unittest.TestCase):
|
|
49
|
+
"""
|
|
50
|
+
Test the implementation of the 'ESDIRK4' solver class
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def test_init(self):
|
|
54
|
+
|
|
55
|
+
#test default initializtion
|
|
56
|
+
solver = ESDIRK4()
|
|
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.assertFalse(solver.is_adaptive)
|
|
64
|
+
self.assertTrue(solver.is_implicit)
|
|
65
|
+
self.assertFalse(solver.is_explicit)
|
|
66
|
+
|
|
67
|
+
#test specific initialization
|
|
68
|
+
solver = ESDIRK4(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 = ESDIRK4()
|
|
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 = ESDIRK4()
|
|
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
|
+
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.5, -0.1, 10)
|
|
112
|
+
|
|
113
|
+
for problem in reference_problems:
|
|
114
|
+
|
|
115
|
+
solver = ESDIRK4(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 3 or 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)
|