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,143 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR 'utils/funcs.py'
|
|
4
|
+
##
|
|
5
|
+
## Milan Rother 2023/24
|
|
6
|
+
##
|
|
7
|
+
########################################################################################
|
|
8
|
+
|
|
9
|
+
# IMPORTS ==============================================================================
|
|
10
|
+
|
|
11
|
+
import unittest
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from pathsim.utils.funcs import (
|
|
15
|
+
dict_to_array,
|
|
16
|
+
array_to_dict,
|
|
17
|
+
abs_error,
|
|
18
|
+
rel_error,
|
|
19
|
+
max_error,
|
|
20
|
+
max_error_dicts,
|
|
21
|
+
max_rel_error,
|
|
22
|
+
max_rel_error_dicts,
|
|
23
|
+
numerical_jacobian,
|
|
24
|
+
auto_jacobian
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# TESTS ================================================================================
|
|
29
|
+
|
|
30
|
+
class TestUtilsFuncs(unittest.TestCase):
|
|
31
|
+
"""
|
|
32
|
+
test all the array-dict conversions and functions for error calculation
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def test_abs_error(self):
|
|
36
|
+
|
|
37
|
+
self.assertEqual(abs_error(1.0, 2.0), 1.0)
|
|
38
|
+
self.assertEqual(abs_error(2.0, 1.0), 1.0)
|
|
39
|
+
self.assertEqual(abs_error(2.0, 0.0), 2.0)
|
|
40
|
+
self.assertEqual(abs_error(0.0, 1.0), 1.0)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_rel_error(self):
|
|
44
|
+
|
|
45
|
+
#test fallback to abs error
|
|
46
|
+
self.assertEqual(rel_error(0.0, 1), 1)
|
|
47
|
+
self.assertEqual(rel_error(0.0, 0.1), 0.1)
|
|
48
|
+
|
|
49
|
+
#test a<b
|
|
50
|
+
self.assertEqual(rel_error(1.0, 2.0), 1.0)
|
|
51
|
+
|
|
52
|
+
#test a>b
|
|
53
|
+
self.assertEqual(rel_error(2.0, 1.0), 0.5)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_max_error(self):
|
|
57
|
+
self.assertEqual(max_error([0.00139, 2.4, 87, 1, 97.8, 4.35],
|
|
58
|
+
[1.00139, 1.4, 86, 2, 98.2, 33.35]),
|
|
59
|
+
29)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_max_rel_error(self):
|
|
63
|
+
self.assertEqual(max_rel_error([0.00139, 2.4, 87, 1, 97.8, 4.35],
|
|
64
|
+
[1.00139, 1.4, 86, 2, 98.2, 33.35]),
|
|
65
|
+
1/0.00139)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_dict_to_array(self):
|
|
69
|
+
|
|
70
|
+
#test conversion
|
|
71
|
+
self.assertEqual(np.sum(np.abs(dict_to_array({0:12, 1:3.2, 2:31.0})-np.array([12, 3.2, 31.0]))), 0.0)
|
|
72
|
+
|
|
73
|
+
#test key sorting
|
|
74
|
+
self.assertEqual(np.sum(np.abs(dict_to_array({0:12, 2:3.2, 1:31.0}) - np.array([12, 31.0, 3.2]))), 0.0)
|
|
75
|
+
self.assertEqual(np.sum(np.abs(dict_to_array({1:12, 2:3.2, 3:31.0})- np.array([12, 3.2, 31.0]))), 0.0)
|
|
76
|
+
|
|
77
|
+
#test non uniform keys
|
|
78
|
+
self.assertEqual(np.sum(np.abs(dict_to_array({0:12, 2:3.2, 3:31.0}) - np.array([12, 3.2, 31.0]))), 0.0)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_array_to_dict(self):
|
|
82
|
+
|
|
83
|
+
#test scalar input
|
|
84
|
+
self.assertEqual(array_to_dict(4), {0:4})
|
|
85
|
+
|
|
86
|
+
#test array input
|
|
87
|
+
self.assertEqual(array_to_dict(np.array([12, 3.2, 31.0])), {0:12, 1:3.2, 2:31.0})
|
|
88
|
+
self.assertEqual(array_to_dict(np.array([2.0])), {0:2.0})
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_max_rel_error_dicts(self):
|
|
92
|
+
self.assertEqual(max_rel_error_dicts({0:0.00139, 1:2.4, 2:87, 3:1, 4:97.8, 5:4.35},
|
|
93
|
+
{0:1.00139, 1:1.4, 2:86, 3:2, 4:98.2, 5:33.35}),
|
|
94
|
+
1/0.00139)
|
|
95
|
+
|
|
96
|
+
def test_max_error_dicts(self):
|
|
97
|
+
self.assertEqual(max_error_dicts({0:0.00139, 1:2.4, 2:87, 3:1, 4:97.8, 5:4.35},
|
|
98
|
+
{0:1.00139, 1:1.4, 2:86, 3:2, 4:98.2, 5:33.35}),
|
|
99
|
+
29)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestJacobian(unittest.TestCase):
|
|
103
|
+
"""
|
|
104
|
+
testing of numerical jacobian calculation
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def test_numerical_jacobian(self):
|
|
108
|
+
|
|
109
|
+
#test scalar case
|
|
110
|
+
def _f(x): return x**2
|
|
111
|
+
def _df(x): return 2*x
|
|
112
|
+
self.assertAlmostEqual(numerical_jacobian(_f, 3), _df(3), 4)
|
|
113
|
+
self.assertAlmostEqual(numerical_jacobian(_f, -6.0), _df(-6.0), 4)
|
|
114
|
+
self.assertAlmostEqual(numerical_jacobian(_f, 100), _df(100), 3)
|
|
115
|
+
|
|
116
|
+
#test vectorial case
|
|
117
|
+
def _f(x): return np.array([np.cos(x[0]), np.sin(x[1])])
|
|
118
|
+
def _df(x): return np.array([[-np.sin(x[0]), 0.0], [0.0, np.cos(x[1])]])
|
|
119
|
+
self.assertAlmostEqual(np.sum(np.abs(numerical_jacobian(_f, np.ones(2)) - _df(np.ones(2)))), 0.0, 6)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_auto_jacobian(self):
|
|
123
|
+
|
|
124
|
+
#test scalar case
|
|
125
|
+
def _f(x): return x**2
|
|
126
|
+
def _df(x): return 2*x
|
|
127
|
+
_j = auto_jacobian(_f)
|
|
128
|
+
self.assertAlmostEqual(_j(3), _df(3), 6)
|
|
129
|
+
self.assertAlmostEqual(_j(-6.0), _df(-6.0), 6)
|
|
130
|
+
self.assertAlmostEqual(_j(100), _df(100), 3)
|
|
131
|
+
|
|
132
|
+
#test vectorial case
|
|
133
|
+
def _f(x): return np.array([np.cos(x[0]), np.sin(x[1])])
|
|
134
|
+
def _df(x): return np.array([[-np.sin(x[0]), 0.0], [0.0, np.cos(x[1])]])
|
|
135
|
+
_j = auto_jacobian(_f)
|
|
136
|
+
self.assertAlmostEqual(np.sum(np.abs(_j(np.ones(2)) - _df(np.ones(2)))), 0.0, 6)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
141
|
+
|
|
142
|
+
if __name__ == '__main__':
|
|
143
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'utils/gilbert.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import numpy as np
|
|
14
|
+
from pathsim.utils.gilbert import gilbert_realization
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# HELPER FUNCTIONS =====================================================================
|
|
18
|
+
|
|
19
|
+
def evaluate_statespace(s, A, B, C, D):
|
|
20
|
+
n = A.shape[0]
|
|
21
|
+
return np.dot(C, np.linalg.solve(s*np.eye(n) - A, B)) + D
|
|
22
|
+
|
|
23
|
+
def evaluate_poleresidue(s, Poles, Residues, Const):
|
|
24
|
+
return np.sum([r/(s - p) for p, r in zip(Poles, Residues)], axis=0) + Const
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# TESTS ================================================================================
|
|
28
|
+
|
|
29
|
+
class TestGilbertRealization(unittest.TestCase):
|
|
30
|
+
"""
|
|
31
|
+
Test the implementation of the gilbert statespace realization
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def assertArrayAlmostEqual(self, first, second, places=7, msg=None):
|
|
35
|
+
np.testing.assert_array_almost_equal(first, second, decimal=places, err_msg=msg)
|
|
36
|
+
|
|
37
|
+
def test_helpers(self):
|
|
38
|
+
A, B, C, D = np.array([[1]]), np.array([[1]]), np.array([[1]]), np.array([[1]])
|
|
39
|
+
self.assertArrayAlmostEqual(evaluate_statespace(0, A, B, C, D), np.array([[0]]))
|
|
40
|
+
self.assertArrayAlmostEqual(evaluate_statespace(1j, A, B, C, D), np.array([[1+1/(1j-1)]]))
|
|
41
|
+
|
|
42
|
+
Poles, Residues, Const = [1], [np.array([1])], np.array([[1]])
|
|
43
|
+
self.assertArrayAlmostEqual(evaluate_poleresidue(0, Poles, Residues, Const), np.array([[0]]))
|
|
44
|
+
self.assertArrayAlmostEqual(evaluate_poleresidue(1j, Poles, Residues, Const), np.array([[1+1/(1j-1)]]))
|
|
45
|
+
|
|
46
|
+
def test_siso_real_poles(self):
|
|
47
|
+
Poles = [-1.0, -2.0, -3.0]
|
|
48
|
+
Residues = [1.0, 2.0, 3.0]
|
|
49
|
+
Const = 0.5
|
|
50
|
+
A, B, C, D = gilbert_realization(Poles, Residues, Const)
|
|
51
|
+
|
|
52
|
+
for s in [0, 1j, 10j, 100j]:
|
|
53
|
+
ss_eval = evaluate_statespace(s, A, B, C, D)
|
|
54
|
+
pr_eval = evaluate_poleresidue(s, Poles, Residues, Const)
|
|
55
|
+
self.assertArrayAlmostEqual(ss_eval, pr_eval, places=12)
|
|
56
|
+
|
|
57
|
+
def test_siso_complex_poles(self):
|
|
58
|
+
Poles = [-1+1j, -1-1j, -2]
|
|
59
|
+
Residues = [1-0.5j, 1+0.5j, 2]
|
|
60
|
+
Const = 0.1
|
|
61
|
+
A, B, C, D = gilbert_realization(Poles, Residues, Const)
|
|
62
|
+
|
|
63
|
+
for s in [0, 1j, 10j, 100j]:
|
|
64
|
+
ss_eval = evaluate_statespace(s, A, B, C, D)
|
|
65
|
+
pr_eval = evaluate_poleresidue(s, Poles, Residues, Const)
|
|
66
|
+
self.assertArrayAlmostEqual(ss_eval, pr_eval, places=12)
|
|
67
|
+
|
|
68
|
+
def test_mimo_2x2(self):
|
|
69
|
+
Poles = [-1, -2, -3]
|
|
70
|
+
Residues = [np.array([[1, 2], [3, 4]]),
|
|
71
|
+
np.array([[2, 3], [4, 5]]),
|
|
72
|
+
np.array([[3, 4], [5, 6]])]
|
|
73
|
+
Const = np.array([[0.1, 0.2], [0.3, 0.4]])
|
|
74
|
+
A, B, C, D = gilbert_realization(Poles, Residues, Const)
|
|
75
|
+
|
|
76
|
+
for s in [0, 1j, 10j, 100j]:
|
|
77
|
+
ss_eval = evaluate_statespace(s, A, B, C, D)
|
|
78
|
+
pr_eval = evaluate_poleresidue(s, Poles, Residues, Const)
|
|
79
|
+
self.assertArrayAlmostEqual(ss_eval, pr_eval, places=12)
|
|
80
|
+
|
|
81
|
+
def test_mimo_3x2(self):
|
|
82
|
+
Poles = [-1+1j, -1-1j, -2]
|
|
83
|
+
Residues = [np.array([[1, 2], [3, 4], [5, 6]]),
|
|
84
|
+
np.array([[1, 2], [3, 4], [5, 6]]),
|
|
85
|
+
np.array([[2, 3], [4, 5], [6, 7]])]
|
|
86
|
+
Const = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])
|
|
87
|
+
A, B, C, D = gilbert_realization(Poles, Residues, Const)
|
|
88
|
+
|
|
89
|
+
for s in [0, 1j, 10j, 100j]:
|
|
90
|
+
ss_eval = evaluate_statespace(s, A, B, C, D)
|
|
91
|
+
pr_eval = evaluate_poleresidue(s, Poles, Residues, Const)
|
|
92
|
+
self.assertArrayAlmostEqual(ss_eval, pr_eval, places=12)
|
|
93
|
+
|
|
94
|
+
def test_input_validation(self):
|
|
95
|
+
|
|
96
|
+
# Empty poles and residues
|
|
97
|
+
with self.assertRaises(ValueError):
|
|
98
|
+
gilbert_realization([], [], 0)
|
|
99
|
+
|
|
100
|
+
# Mismatched poles and residues
|
|
101
|
+
with self.assertRaises(ValueError):
|
|
102
|
+
gilbert_realization([1, 2], [np.array([1])], 0)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
106
|
+
|
|
107
|
+
if __name__ == '__main__':
|
|
108
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'utils/progresstracker.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2023/24
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from pathsim.utils.progresstracker import (
|
|
16
|
+
ProgressTracker
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# TESTS ================================================================================
|
|
21
|
+
|
|
22
|
+
class TestProgressTracker(unittest.TestCase):
|
|
23
|
+
"""
|
|
24
|
+
test the implementation of the 'ProgressTracker' class
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_iter_successful_5(self):
|
|
29
|
+
|
|
30
|
+
#tracker with log interval 5/100 %
|
|
31
|
+
tracker = ProgressTracker(log_interval=5)
|
|
32
|
+
|
|
33
|
+
#test if display percentages are correctly computed
|
|
34
|
+
self.assertEqual(tracker.display_percentages,
|
|
35
|
+
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100])
|
|
36
|
+
|
|
37
|
+
i, n = 0, 100
|
|
38
|
+
|
|
39
|
+
#iterate the tracker
|
|
40
|
+
for _ in tracker:
|
|
41
|
+
i += 1
|
|
42
|
+
|
|
43
|
+
#test tracker iteration condition
|
|
44
|
+
self.assertTrue(tracker.condition)
|
|
45
|
+
|
|
46
|
+
#check progress
|
|
47
|
+
tracker.check(progress=i/n, success=True)
|
|
48
|
+
|
|
49
|
+
#test tracker steps
|
|
50
|
+
self.assertEqual(tracker.steps, i)
|
|
51
|
+
|
|
52
|
+
#check successful steps tracker
|
|
53
|
+
self.assertEqual(tracker.successful_steps, i)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_iter_successful_10(self):
|
|
57
|
+
|
|
58
|
+
#tracker with log interval 10/100 %
|
|
59
|
+
tracker = ProgressTracker(log_interval=10)
|
|
60
|
+
|
|
61
|
+
#test if display percentages are correctly computed
|
|
62
|
+
self.assertEqual(tracker.display_percentages,
|
|
63
|
+
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
|
64
|
+
|
|
65
|
+
i, n = 0, 100
|
|
66
|
+
|
|
67
|
+
#iterate the tracker
|
|
68
|
+
for _ in tracker:
|
|
69
|
+
i += 1
|
|
70
|
+
|
|
71
|
+
#test tracker iteration condition
|
|
72
|
+
self.assertTrue(tracker.condition)
|
|
73
|
+
|
|
74
|
+
#check progress
|
|
75
|
+
tracker.check(progress=i/n, success=True)
|
|
76
|
+
|
|
77
|
+
#test tracker steps
|
|
78
|
+
self.assertEqual(tracker.steps, i)
|
|
79
|
+
|
|
80
|
+
#check successful steps tracker
|
|
81
|
+
self.assertEqual(tracker.successful_steps, i)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_iter_mixed_success_5(self):
|
|
85
|
+
|
|
86
|
+
#tracker with log interval 5/100 %
|
|
87
|
+
tracker = ProgressTracker(log_interval=5)
|
|
88
|
+
|
|
89
|
+
#test if display percentages are correctly computed
|
|
90
|
+
self.assertEqual(tracker.display_percentages,
|
|
91
|
+
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100])
|
|
92
|
+
|
|
93
|
+
i, j, n = 0, 50, 100
|
|
94
|
+
|
|
95
|
+
#iterate the tracker
|
|
96
|
+
for _ in tracker:
|
|
97
|
+
i += 1
|
|
98
|
+
|
|
99
|
+
#test tracker iteration condition
|
|
100
|
+
self.assertTrue(tracker.condition)
|
|
101
|
+
|
|
102
|
+
#check progress
|
|
103
|
+
tracker.check(progress=i/n, success=i>j)
|
|
104
|
+
|
|
105
|
+
#test tracker steps
|
|
106
|
+
self.assertEqual(tracker.steps, i)
|
|
107
|
+
|
|
108
|
+
#check successful steps tracker
|
|
109
|
+
self.assertEqual(tracker.successful_steps, max(0, i-j))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_iter_mixed_success_10(self):
|
|
113
|
+
|
|
114
|
+
#tracker with log interval 10/100 %
|
|
115
|
+
tracker = ProgressTracker(log_interval=10)
|
|
116
|
+
|
|
117
|
+
#test if display percentages are correctly computed
|
|
118
|
+
self.assertEqual(tracker.display_percentages,
|
|
119
|
+
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
|
120
|
+
|
|
121
|
+
i, j, n = 0, 50, 100
|
|
122
|
+
|
|
123
|
+
#iterate the tracker
|
|
124
|
+
for _ in tracker:
|
|
125
|
+
i += 1
|
|
126
|
+
|
|
127
|
+
#test tracker iteration condition
|
|
128
|
+
self.assertTrue(tracker.condition)
|
|
129
|
+
|
|
130
|
+
#check progress
|
|
131
|
+
tracker.check(progress=i/n, success=i>j)
|
|
132
|
+
|
|
133
|
+
#test tracker steps
|
|
134
|
+
self.assertEqual(tracker.steps, i)
|
|
135
|
+
|
|
136
|
+
#check successful steps tracker
|
|
137
|
+
self.assertEqual(tracker.successful_steps, max(0, i-j))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
142
|
+
|
|
143
|
+
if __name__ == '__main__':
|
|
144
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR
|
|
4
|
+
## 'utils/realtimeplotter.py'
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
from unittest.mock import patch, MagicMock
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from pathsim.utils.realtimeplotter import RealtimePlotter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# TESTS ================================================================================
|
|
20
|
+
|
|
21
|
+
class TestRealtimePlotter(unittest.TestCase):
|
|
22
|
+
"""
|
|
23
|
+
test the implementation of the 'RealtimePlotter' class
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def setUp(self):
|
|
27
|
+
self.max_samples = 100
|
|
28
|
+
self.update_interval = 0.5
|
|
29
|
+
self.labels = ['Test 1', 'Test 2']
|
|
30
|
+
self.x_label = 'X Axis'
|
|
31
|
+
self.y_label = 'Y Axis'
|
|
32
|
+
|
|
33
|
+
@patch('pathsim.utils.realtimeplotter.plt')
|
|
34
|
+
def test_init(self, mock_plt):
|
|
35
|
+
mock_fig = MagicMock()
|
|
36
|
+
mock_ax = MagicMock()
|
|
37
|
+
mock_plt.subplots.return_value = (mock_fig, mock_ax)
|
|
38
|
+
|
|
39
|
+
plotter = RealtimePlotter(
|
|
40
|
+
max_samples=self.max_samples,
|
|
41
|
+
update_interval=self.update_interval,
|
|
42
|
+
labels=self.labels,
|
|
43
|
+
x_label=self.x_label,
|
|
44
|
+
y_label=self.y_label
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self.assertEqual(plotter.max_samples, self.max_samples)
|
|
48
|
+
self.assertEqual(plotter.update_interval, self.update_interval)
|
|
49
|
+
self.assertEqual(plotter.labels, self.labels)
|
|
50
|
+
self.assertEqual(plotter.x_label, self.x_label)
|
|
51
|
+
self.assertEqual(plotter.y_label, self.y_label)
|
|
52
|
+
|
|
53
|
+
mock_plt.subplots.assert_called_once()
|
|
54
|
+
mock_ax.set_xlabel.assert_called_once_with(self.x_label)
|
|
55
|
+
mock_ax.set_ylabel.assert_called_once_with(self.y_label)
|
|
56
|
+
mock_ax.grid.assert_called_once_with(True)
|
|
57
|
+
|
|
58
|
+
@patch('pathsim.utils.realtimeplotter.plt')
|
|
59
|
+
@patch('pathsim.utils.realtimeplotter.time')
|
|
60
|
+
def test_update_all(self, mock_time, mock_plt):
|
|
61
|
+
mock_fig = MagicMock()
|
|
62
|
+
mock_ax = MagicMock()
|
|
63
|
+
mock_line = MagicMock()
|
|
64
|
+
mock_ax.plot.return_value = [mock_line]
|
|
65
|
+
mock_plt.subplots.return_value = (mock_fig, mock_ax)
|
|
66
|
+
mock_time.time.return_value = 0
|
|
67
|
+
|
|
68
|
+
plotter = RealtimePlotter(labels=self.labels)
|
|
69
|
+
|
|
70
|
+
x = np.array([1, 2, 3])
|
|
71
|
+
y = np.array([[1, 2, 3], [4, 5, 6]])
|
|
72
|
+
|
|
73
|
+
result = plotter.update_all(x, y)
|
|
74
|
+
|
|
75
|
+
self.assertTrue(result)
|
|
76
|
+
self.assertTrue(hasattr(plotter, 'data'))
|
|
77
|
+
self.assertTrue(hasattr(plotter, 'lines'))
|
|
78
|
+
|
|
79
|
+
@patch('pathsim.utils.realtimeplotter.plt')
|
|
80
|
+
@patch('pathsim.utils.realtimeplotter.time')
|
|
81
|
+
def test_update(self, mock_time, mock_plt):
|
|
82
|
+
mock_fig = MagicMock()
|
|
83
|
+
mock_ax = MagicMock()
|
|
84
|
+
mock_line = MagicMock()
|
|
85
|
+
mock_ax.plot.return_value = [mock_line]
|
|
86
|
+
mock_plt.subplots.return_value = (mock_fig, mock_ax)
|
|
87
|
+
mock_time.time.return_value = 0
|
|
88
|
+
|
|
89
|
+
plotter = RealtimePlotter(max_samples=5)
|
|
90
|
+
|
|
91
|
+
for i in range(10):
|
|
92
|
+
result = plotter.update(i, i)
|
|
93
|
+
self.assertTrue(result)
|
|
94
|
+
|
|
95
|
+
self.assertTrue(hasattr(plotter, 'data'))
|
|
96
|
+
self.assertTrue(hasattr(plotter, 'lines'))
|
|
97
|
+
|
|
98
|
+
@patch('pathsim.utils.realtimeplotter.plt')
|
|
99
|
+
def test_on_close(self, mock_plt):
|
|
100
|
+
mock_fig = MagicMock()
|
|
101
|
+
mock_ax = MagicMock()
|
|
102
|
+
mock_plt.subplots.return_value = (mock_fig, mock_ax)
|
|
103
|
+
|
|
104
|
+
plotter = RealtimePlotter()
|
|
105
|
+
plotter.on_close(None)
|
|
106
|
+
self.assertFalse(plotter.is_running)
|
|
107
|
+
|
|
108
|
+
@patch('pathsim.utils.realtimeplotter.plt')
|
|
109
|
+
def test_show(self, mock_plt):
|
|
110
|
+
mock_fig = MagicMock()
|
|
111
|
+
mock_ax = MagicMock()
|
|
112
|
+
mock_plt.subplots.return_value = (mock_fig, mock_ax)
|
|
113
|
+
|
|
114
|
+
plotter = RealtimePlotter()
|
|
115
|
+
plotter.show()
|
|
116
|
+
mock_plt.show.assert_called_with(block=False)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
120
|
+
|
|
121
|
+
if __name__ == '__main__':
|
|
122
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TESTS FOR 'utils/statespacerealizations.py'
|
|
4
|
+
##
|
|
5
|
+
## Milan Rother 2024
|
|
6
|
+
##
|
|
7
|
+
########################################################################################
|
|
8
|
+
|
|
9
|
+
# IMPORTS ==============================================================================
|
|
10
|
+
|
|
11
|
+
import unittest
|
|
12
|
+
import numpy as np
|
|
13
|
+
from pathsim.utils.statespacerealizations import gilbert_realization
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# HELPER FUNCTIONS =====================================================================
|
|
17
|
+
|
|
18
|
+
def evaluate_statespace(s, A, B, C, D):
|
|
19
|
+
n = A.shape[0]
|
|
20
|
+
return np.dot(C, np.linalg.solve(s*np.eye(n) - A, B)) + D
|
|
21
|
+
|
|
22
|
+
def evaluate_poleresidue(s, Poles, Residues, Const):
|
|
23
|
+
return np.sum([r/(s - p) for p, r in zip(Poles, Residues)], axis=0) + Const
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# TESTS ================================================================================
|
|
27
|
+
|
|
28
|
+
class GilbertRealizationTest(unittest.TestCase):
|
|
29
|
+
"""
|
|
30
|
+
Test the implementation of the gilbert statespace realization
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def assertArrayAlmostEqual(self, first, second, places=7, msg=None):
|
|
34
|
+
np.testing.assert_array_almost_equal(first, second, decimal=places, err_msg=msg)
|
|
35
|
+
|
|
36
|
+
def test_helpers(self):
|
|
37
|
+
A, B, C, D = np.array([[1]]), np.array([[1]]), np.array([[1]]), np.array([[1]])
|
|
38
|
+
self.assertArrayAlmostEqual(evaluate_statespace(0, A, B, C, D), np.array([[0]]))
|
|
39
|
+
self.assertArrayAlmostEqual(evaluate_statespace(1j, A, B, C, D), np.array([[1+1/(1j-1)]]))
|
|
40
|
+
|
|
41
|
+
Poles, Residues, Const = [1], [np.array([1])], np.array([[1]])
|
|
42
|
+
self.assertArrayAlmostEqual(evaluate_poleresidue(0, Poles, Residues, Const), np.array([[0]]))
|
|
43
|
+
self.assertArrayAlmostEqual(evaluate_poleresidue(1j, Poles, Residues, Const), np.array([[1+1/(1j-1)]]))
|
|
44
|
+
|
|
45
|
+
def test_siso_real_poles(self):
|
|
46
|
+
Poles = [-1.0, -2.0, -3.0]
|
|
47
|
+
Residues = [1.0, 2.0, 3.0]
|
|
48
|
+
Const = 0.5
|
|
49
|
+
A, B, C, D = gilbert_realization(Poles, Residues, Const)
|
|
50
|
+
|
|
51
|
+
for s in [0, 1j, 10j, 100j]:
|
|
52
|
+
ss_eval = evaluate_statespace(s, A, B, C, D)
|
|
53
|
+
pr_eval = evaluate_poleresidue(s, Poles, Residues, Const)
|
|
54
|
+
self.assertArrayAlmostEqual(ss_eval, pr_eval, places=12)
|
|
55
|
+
|
|
56
|
+
def test_siso_complex_poles(self):
|
|
57
|
+
Poles = [-1+1j, -1-1j, -2]
|
|
58
|
+
Residues = [1-0.5j, 1+0.5j, 2]
|
|
59
|
+
Const = 0.1
|
|
60
|
+
A, B, C, D = gilbert_realization(Poles, Residues, Const)
|
|
61
|
+
|
|
62
|
+
for s in [0, 1j, 10j, 100j]:
|
|
63
|
+
ss_eval = evaluate_statespace(s, A, B, C, D)
|
|
64
|
+
pr_eval = evaluate_poleresidue(s, Poles, Residues, Const)
|
|
65
|
+
self.assertArrayAlmostEqual(ss_eval, pr_eval, places=12)
|
|
66
|
+
|
|
67
|
+
def test_mimo_2x2(self):
|
|
68
|
+
Poles = [-1, -2, -3]
|
|
69
|
+
Residues = [np.array([[1, 2], [3, 4]]),
|
|
70
|
+
np.array([[2, 3], [4, 5]]),
|
|
71
|
+
np.array([[3, 4], [5, 6]])]
|
|
72
|
+
Const = np.array([[0.1, 0.2], [0.3, 0.4]])
|
|
73
|
+
A, B, C, D = gilbert_realization(Poles, Residues, Const)
|
|
74
|
+
|
|
75
|
+
for s in [0, 1j, 10j, 100j]:
|
|
76
|
+
ss_eval = evaluate_statespace(s, A, B, C, D)
|
|
77
|
+
pr_eval = evaluate_poleresidue(s, Poles, Residues, Const)
|
|
78
|
+
self.assertArrayAlmostEqual(ss_eval, pr_eval, places=12)
|
|
79
|
+
|
|
80
|
+
def test_mimo_3x2(self):
|
|
81
|
+
Poles = [-1+1j, -1-1j, -2]
|
|
82
|
+
Residues = [np.array([[1, 2], [3, 4], [5, 6]]),
|
|
83
|
+
np.array([[1, 2], [3, 4], [5, 6]]),
|
|
84
|
+
np.array([[2, 3], [4, 5], [6, 7]])]
|
|
85
|
+
Const = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])
|
|
86
|
+
A, B, C, D = gilbert_realization(Poles, Residues, Const)
|
|
87
|
+
|
|
88
|
+
for s in [0, 1j, 10j, 100j]:
|
|
89
|
+
ss_eval = evaluate_statespace(s, A, B, C, D)
|
|
90
|
+
pr_eval = evaluate_poleresidue(s, Poles, Residues, Const)
|
|
91
|
+
self.assertArrayAlmostEqual(ss_eval, pr_eval, places=12)
|
|
92
|
+
|
|
93
|
+
def test_input_validation(self):
|
|
94
|
+
|
|
95
|
+
# Empty poles and residues
|
|
96
|
+
with self.assertRaises(ValueError):
|
|
97
|
+
gilbert_realization([], [], 0)
|
|
98
|
+
|
|
99
|
+
# Mismatched poles and residues
|
|
100
|
+
with self.assertRaises(ValueError):
|
|
101
|
+
gilbert_realization([1, 2], [np.array([1])], 0)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# RUN TESTS LOCALLY ====================================================================
|
|
105
|
+
|
|
106
|
+
if __name__ == '__main__':
|
|
107
|
+
unittest.main(verbosity=2)
|