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 'solvers/_solver.py'
|
|
4
|
+
##
|
|
5
|
+
## Milan Rother 2024
|
|
6
|
+
##
|
|
7
|
+
########################################################################################
|
|
8
|
+
|
|
9
|
+
# IMPORTS ==============================================================================
|
|
10
|
+
|
|
11
|
+
import unittest
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from pathsim.solvers._solver import (
|
|
16
|
+
Solver,
|
|
17
|
+
ExplicitSolver,
|
|
18
|
+
ImplicitSolver)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# HELPER FUNCTIONS =====================================================================
|
|
22
|
+
|
|
23
|
+
def simple_func(x, u, t):
|
|
24
|
+
return u - x
|
|
25
|
+
|
|
26
|
+
def simple_jac(x, u, t):
|
|
27
|
+
return -1
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# TESTS ================================================================================
|
|
31
|
+
|
|
32
|
+
class TestBaseSolver(unittest.TestCase):
|
|
33
|
+
"""
|
|
34
|
+
Test the implementation of the base 'Solver' class
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def setUp(self):
|
|
38
|
+
self.solver = Solver(initial_value=1.0, func=simple_func, jac=simple_jac)
|
|
39
|
+
|
|
40
|
+
def test_init(self):
|
|
41
|
+
self.assertEqual(self.solver.x, 1.0)
|
|
42
|
+
self.assertEqual(self.solver.x_0, 1.0)
|
|
43
|
+
self.assertEqual(self.solver.initial_value, 1.0)
|
|
44
|
+
self.assertEqual(self.solver.func, simple_func)
|
|
45
|
+
self.assertEqual(self.solver.jac, simple_jac)
|
|
46
|
+
self.assertFalse(self.solver.is_adaptive)
|
|
47
|
+
|
|
48
|
+
def test_str(self):
|
|
49
|
+
self.assertEqual(str(self.solver), "Solver")
|
|
50
|
+
|
|
51
|
+
def test_stages(self):
|
|
52
|
+
stages = list(self.solver.stages(0, 1))
|
|
53
|
+
self.assertEqual(stages, [0])
|
|
54
|
+
|
|
55
|
+
def test_get_set(self):
|
|
56
|
+
self.solver.set(2.0)
|
|
57
|
+
self.assertEqual(self.solver.get(), 2.0)
|
|
58
|
+
|
|
59
|
+
def test_reset(self):
|
|
60
|
+
self.solver.set(2.0)
|
|
61
|
+
self.solver.reset()
|
|
62
|
+
self.assertEqual(self.solver.get(), 1.0)
|
|
63
|
+
|
|
64
|
+
def test_buffer(self):
|
|
65
|
+
self.solver.x = 2.0
|
|
66
|
+
self.solver.buffer()
|
|
67
|
+
self.assertEqual(self.solver.x_0, 2.0)
|
|
68
|
+
|
|
69
|
+
def test_change(self):
|
|
70
|
+
new_solver = self.solver.change(ExplicitSolver)
|
|
71
|
+
self.assertIsInstance(new_solver, ExplicitSolver)
|
|
72
|
+
self.assertEqual(new_solver.get(), self.solver.get())
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ExplicitSolverTest(unittest.TestCase):
|
|
76
|
+
"""
|
|
77
|
+
Test the implementation of the 'ExplicitSolver' base class
|
|
78
|
+
"""
|
|
79
|
+
def setUp(self):
|
|
80
|
+
self.solver = ExplicitSolver(initial_value=1.0, func=simple_func, jac=simple_jac)
|
|
81
|
+
|
|
82
|
+
def test_init(self):
|
|
83
|
+
self.assertTrue(self.solver.is_explicit)
|
|
84
|
+
self.assertFalse(self.solver.is_implicit)
|
|
85
|
+
|
|
86
|
+
def test_integrate_singlestep(self):
|
|
87
|
+
success, error, scale = self.solver.integrate_singlestep(time=0, dt=0.1)
|
|
88
|
+
self.assertTrue(success)
|
|
89
|
+
self.assertEqual(error, 0.0)
|
|
90
|
+
self.assertEqual(scale, 1.0)
|
|
91
|
+
|
|
92
|
+
def test_integrate(self):
|
|
93
|
+
times, states = self.solver.integrate(time_start=0, time_end=1, dt=0.1)
|
|
94
|
+
self.assertEqual(len(times), len(states))
|
|
95
|
+
self.assertGreater(len(times), 1)
|
|
96
|
+
self.assertEqual(times[0], 0)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ImplicitSolverTest(unittest.TestCase):
|
|
100
|
+
"""
|
|
101
|
+
Test the implementation of the 'ImplicitSolver' base class
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def setUp(self):
|
|
105
|
+
self.solver = ImplicitSolver(initial_value=1.0, func=simple_func, jac=simple_jac)
|
|
106
|
+
|
|
107
|
+
def test_init(self):
|
|
108
|
+
self.assertFalse(self.solver.is_explicit)
|
|
109
|
+
self.assertTrue(self.solver.is_implicit)
|
|
110
|
+
|
|
111
|
+
def test_solve(self):
|
|
112
|
+
error = self.solver.solve(0, 0, 0.1)
|
|
113
|
+
self.assertEqual(error, 0.0)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
117
|
+
|
|
118
|
+
if __name__ == '__main__':
|
|
119
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/ssprk22.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.ssprk22 import SSPRK22
|
|
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 TestSSPRK22(unittest.TestCase):
|
|
49
|
+
"""
|
|
50
|
+
Test the implementation of the 'SSPRK22' solver class
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def test_init(self):
|
|
54
|
+
|
|
55
|
+
#test default initializtion
|
|
56
|
+
solver = SSPRK22()
|
|
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_explicit)
|
|
65
|
+
self.assertFalse(solver.is_implicit)
|
|
66
|
+
|
|
67
|
+
#test specific initialization
|
|
68
|
+
solver = SSPRK22(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 = SSPRK22()
|
|
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 = SSPRK22()
|
|
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
|
+
self.assertTrue(success)
|
|
102
|
+
self.assertEqual(err, 0.0)
|
|
103
|
+
self.assertEqual(scale, 1.0)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_integrate_fixed(self):
|
|
107
|
+
|
|
108
|
+
#integrate test problem and assess convergence order
|
|
109
|
+
|
|
110
|
+
timesteps = np.logspace(-0.4, 0, 20)
|
|
111
|
+
|
|
112
|
+
for problem in reference_problems:
|
|
113
|
+
|
|
114
|
+
solver = SSPRK22(problem.x0, problem.func, problem.jac)
|
|
115
|
+
|
|
116
|
+
errors = []
|
|
117
|
+
|
|
118
|
+
for dt in timesteps:
|
|
119
|
+
|
|
120
|
+
solver.reset()
|
|
121
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=3.0, dt=dt, adaptive=False)
|
|
122
|
+
|
|
123
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
124
|
+
|
|
125
|
+
#test if errors are monotonically decreasing
|
|
126
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
127
|
+
|
|
128
|
+
#test convergence order, expected 2
|
|
129
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
130
|
+
self.assertEqual(round(p), 2)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
134
|
+
|
|
135
|
+
if __name__ == '__main__':
|
|
136
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/ssprk33.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.ssprk33 import SSPRK33
|
|
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 TestSSPRK33(unittest.TestCase):
|
|
49
|
+
"""
|
|
50
|
+
Test the implementation of the 'SSPRK33' solver class
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def test_init(self):
|
|
54
|
+
|
|
55
|
+
#test default initializtion
|
|
56
|
+
solver = SSPRK33()
|
|
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_explicit)
|
|
65
|
+
self.assertFalse(solver.is_implicit)
|
|
66
|
+
|
|
67
|
+
#test specific initialization
|
|
68
|
+
solver = SSPRK33(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 = SSPRK33()
|
|
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 = SSPRK33()
|
|
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
|
+
self.assertTrue(success)
|
|
102
|
+
self.assertEqual(err, 0.0)
|
|
103
|
+
self.assertEqual(scale, 1.0)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_integrate_fixed(self):
|
|
107
|
+
|
|
108
|
+
#integrate test problem and assess convergence order
|
|
109
|
+
|
|
110
|
+
timesteps = np.logspace(-0.4, 0, 20)
|
|
111
|
+
|
|
112
|
+
for problem in reference_problems:
|
|
113
|
+
|
|
114
|
+
solver = SSPRK33(problem.x0, problem.func, problem.jac)
|
|
115
|
+
|
|
116
|
+
errors = []
|
|
117
|
+
|
|
118
|
+
for dt in timesteps:
|
|
119
|
+
|
|
120
|
+
solver.reset()
|
|
121
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=3.0, dt=dt, adaptive=False)
|
|
122
|
+
|
|
123
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
124
|
+
|
|
125
|
+
#test if errors are monotonically decreasing
|
|
126
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
127
|
+
|
|
128
|
+
#test convergence order, expected 3
|
|
129
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
130
|
+
self.assertEqual(round(p), 3)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
134
|
+
|
|
135
|
+
if __name__ == '__main__':
|
|
136
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'solvers/ssprk34.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.ssprk34 import SSPRK34
|
|
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 TestSSPRK34(unittest.TestCase):
|
|
49
|
+
"""
|
|
50
|
+
Test the implementation of the 'SSPRK34' solver class
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def test_init(self):
|
|
54
|
+
|
|
55
|
+
#test default initializtion
|
|
56
|
+
solver = SSPRK34()
|
|
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_explicit)
|
|
65
|
+
self.assertFalse(solver.is_implicit)
|
|
66
|
+
|
|
67
|
+
#test specific initialization
|
|
68
|
+
solver = SSPRK34(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 = SSPRK34()
|
|
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 = SSPRK34()
|
|
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
|
+
self.assertTrue(success)
|
|
102
|
+
self.assertEqual(err, 0.0)
|
|
103
|
+
self.assertEqual(scale, 1.0)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_integrate_fixed(self):
|
|
107
|
+
|
|
108
|
+
#integrate test problem and assess convergence order
|
|
109
|
+
|
|
110
|
+
timesteps = np.logspace(-0.4, 0, 20)
|
|
111
|
+
|
|
112
|
+
for problem in reference_problems:
|
|
113
|
+
|
|
114
|
+
solver = SSPRK34(problem.x0, problem.func, problem.jac)
|
|
115
|
+
|
|
116
|
+
errors = []
|
|
117
|
+
|
|
118
|
+
for dt in timesteps:
|
|
119
|
+
|
|
120
|
+
solver.reset()
|
|
121
|
+
time, numerical_solution = solver.integrate(time_start=0.0, time_end=3.0, dt=dt, adaptive=False)
|
|
122
|
+
|
|
123
|
+
errors.append(np.linalg.norm(numerical_solution - problem.solution(time)))
|
|
124
|
+
|
|
125
|
+
#test if errors are monotonically decreasing
|
|
126
|
+
self.assertTrue(np.all(np.diff(errors)>0))
|
|
127
|
+
|
|
128
|
+
#test convergence order, expected 3
|
|
129
|
+
p, _ = np.polyfit(np.log10(timesteps), np.log10(errors), deg=1)
|
|
130
|
+
self.assertEqual(round(p), 3)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
134
|
+
|
|
135
|
+
if __name__ == '__main__':
|
|
136
|
+
unittest.main(verbosity=2)
|
tests/test_connection.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'connection.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from pathsim.connection import Connection
|
|
16
|
+
from pathsim.blocks._block import Block
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# TESTS ================================================================================
|
|
20
|
+
|
|
21
|
+
class TestConnection(unittest.TestCase):
|
|
22
|
+
"""
|
|
23
|
+
Test the implementation of the 'Connection' class
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def test_init_single(self):
|
|
27
|
+
|
|
28
|
+
B1, B2 = Block(), Block()
|
|
29
|
+
|
|
30
|
+
#default
|
|
31
|
+
C = Connection(B1, B2)
|
|
32
|
+
|
|
33
|
+
#test if ports assigned correctly
|
|
34
|
+
self.assertEqual(C.source, (B1, 0))
|
|
35
|
+
self.assertEqual(C.targets, [(B2, 0)])
|
|
36
|
+
|
|
37
|
+
#mixed
|
|
38
|
+
C1 = Connection(B1, (B2, 2))
|
|
39
|
+
C2 = Connection((B1, 3), B2)
|
|
40
|
+
|
|
41
|
+
#test if ports assigned correctly
|
|
42
|
+
self.assertEqual(C1.source, (B1, 0))
|
|
43
|
+
self.assertEqual(C1.targets, [(B2, 2)])
|
|
44
|
+
|
|
45
|
+
#test if ports assigned correctly
|
|
46
|
+
self.assertEqual(C2.source, (B1, 3))
|
|
47
|
+
self.assertEqual(C2.targets, [(B2, 0)])
|
|
48
|
+
|
|
49
|
+
#all
|
|
50
|
+
C = Connection((B1, 4), (B2, 1))
|
|
51
|
+
|
|
52
|
+
#test if ports assigned correctly
|
|
53
|
+
self.assertEqual(C.source, (B1, 4))
|
|
54
|
+
self.assertEqual(C.targets, [(B2, 1)])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_init_multi(self):
|
|
58
|
+
|
|
59
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
60
|
+
|
|
61
|
+
#default
|
|
62
|
+
C = Connection(B1, B2, B3)
|
|
63
|
+
|
|
64
|
+
#test if ports assigned correctly
|
|
65
|
+
self.assertEqual(C.source, (B1, 0))
|
|
66
|
+
self.assertEqual(C.targets, [(B2, 0), (B3, 0)])
|
|
67
|
+
|
|
68
|
+
#mixed
|
|
69
|
+
C1 = Connection(B1, (B2, 2), B3)
|
|
70
|
+
C2 = Connection((B1, 3), B2, (B3, 1))
|
|
71
|
+
|
|
72
|
+
#test if ports assigned correctly
|
|
73
|
+
self.assertEqual(C1.source, (B1, 0))
|
|
74
|
+
self.assertEqual(C1.targets, [(B2, 2), (B3, 0)])
|
|
75
|
+
|
|
76
|
+
#test if ports assigned correctly
|
|
77
|
+
self.assertEqual(C2.source, (B1, 3))
|
|
78
|
+
self.assertEqual(C2.targets, [(B2, 0), (B3, 1)])
|
|
79
|
+
|
|
80
|
+
#all
|
|
81
|
+
C = Connection((B1, 4), (B2, 1), (B3, 2))
|
|
82
|
+
|
|
83
|
+
#test if ports assigned correctly
|
|
84
|
+
self.assertEqual(C.source, (B1, 4))
|
|
85
|
+
self.assertEqual(C.targets, [(B2, 1), (B3, 2)])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_overwrites(self):
|
|
89
|
+
|
|
90
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
91
|
+
|
|
92
|
+
C1 = Connection(B1, B2)
|
|
93
|
+
C2 = Connection(B1, B3)
|
|
94
|
+
C3 = Connection(B2, B3)
|
|
95
|
+
|
|
96
|
+
self.assertFalse(C1.overwrites(C2))
|
|
97
|
+
self.assertFalse(C2.overwrites(C1))
|
|
98
|
+
self.assertTrue(C3.overwrites(C2))
|
|
99
|
+
self.assertTrue(C2.overwrites(C3))
|
|
100
|
+
|
|
101
|
+
C1 = Connection(B1, B2, B3)
|
|
102
|
+
C2 = Connection(B1, B3)
|
|
103
|
+
|
|
104
|
+
self.assertTrue(C1.overwrites(C2))
|
|
105
|
+
self.assertTrue(C2.overwrites(C1))
|
|
106
|
+
|
|
107
|
+
C1 = Connection(B1, (B2, 1))
|
|
108
|
+
C2 = Connection(B1, (B2, 2))
|
|
109
|
+
|
|
110
|
+
self.assertFalse(C1.overwrites(C2))
|
|
111
|
+
self.assertFalse(C2.overwrites(C1))
|
|
112
|
+
|
|
113
|
+
C1 = Connection((B1, 1), B3)
|
|
114
|
+
C2 = Connection((B1, 2), B3)
|
|
115
|
+
|
|
116
|
+
self.assertTrue(C1.overwrites(C2))
|
|
117
|
+
self.assertTrue(C2.overwrites(C1))
|
|
118
|
+
|
|
119
|
+
C1 = Connection((B1, 1), B3)
|
|
120
|
+
C2 = Connection((B1, 2), (B3, 0))
|
|
121
|
+
|
|
122
|
+
self.assertTrue(C1.overwrites(C2))
|
|
123
|
+
self.assertTrue(C2.overwrites(C1))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_update_single(self):
|
|
127
|
+
|
|
128
|
+
B1, B2 = Block(), Block()
|
|
129
|
+
|
|
130
|
+
#test data transfer with default ports
|
|
131
|
+
C = Connection(B1, B2)
|
|
132
|
+
B1.outputs[0] = 3
|
|
133
|
+
C.update()
|
|
134
|
+
self.assertEqual(B2.inputs[0], 3)
|
|
135
|
+
|
|
136
|
+
#test data transfer with specific ports
|
|
137
|
+
C = Connection((B1, 2), (B2, 2))
|
|
138
|
+
B1.outputs[2] = 3
|
|
139
|
+
C.update()
|
|
140
|
+
self.assertEqual(B2.inputs[2], 3)
|
|
141
|
+
|
|
142
|
+
#test data transfer with mixed ports
|
|
143
|
+
C = Connection(B1, (B2, 2))
|
|
144
|
+
B1.outputs[0] = 3
|
|
145
|
+
C.update()
|
|
146
|
+
self.assertEqual(B2.inputs[2], 3)
|
|
147
|
+
|
|
148
|
+
C = Connection((B1, 2), B2)
|
|
149
|
+
B1.outputs[2] = 3
|
|
150
|
+
C.update()
|
|
151
|
+
self.assertEqual(B2.inputs[0], 3)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_update_multi(self):
|
|
155
|
+
|
|
156
|
+
B1, B2, B3 = Block(), Block(), Block()
|
|
157
|
+
|
|
158
|
+
#test data transfer with default ports
|
|
159
|
+
C = Connection(B1, B2, B3)
|
|
160
|
+
B1.outputs[0] = 3
|
|
161
|
+
C.update()
|
|
162
|
+
self.assertEqual(B2.inputs[0], 3)
|
|
163
|
+
self.assertEqual(B3.inputs[0], 3)
|
|
164
|
+
|
|
165
|
+
#test data transfer with specific ports
|
|
166
|
+
C = Connection((B1, 2), (B2, 2), (B3, 1))
|
|
167
|
+
B1.outputs[2] = 3
|
|
168
|
+
C.update()
|
|
169
|
+
self.assertEqual(B2.inputs[2], 3)
|
|
170
|
+
self.assertEqual(B3.inputs[1], 3)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
174
|
+
|
|
175
|
+
if __name__ == '__main__':
|
|
176
|
+
unittest.main(verbosity=2)
|