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