pybounds 0.0.4__tar.gz → 0.0.5__tar.gz
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.
Potentially problematic release.
This version of pybounds might be problematic. Click here for more details.
- {pybounds-0.0.4 → pybounds-0.0.5}/PKG-INFO +6 -10
- {pybounds-0.0.4 → pybounds-0.0.5}/README.md +5 -9
- {pybounds-0.0.4 → pybounds-0.0.5}/pybounds/observability.py +19 -10
- pybounds-0.0.5/pybounds/simulator.py +422 -0
- {pybounds-0.0.4 → pybounds-0.0.5}/pybounds/util.py +4 -1
- {pybounds-0.0.4 → pybounds-0.0.5}/pybounds.egg-info/PKG-INFO +6 -10
- {pybounds-0.0.4 → pybounds-0.0.5}/setup.py +1 -1
- pybounds-0.0.4/pybounds/simulator.py +0 -260
- {pybounds-0.0.4 → pybounds-0.0.5}/LICENSE +0 -0
- {pybounds-0.0.4 → pybounds-0.0.5}/pybounds/__init__.py +0 -0
- {pybounds-0.0.4 → pybounds-0.0.5}/pybounds.egg-info/SOURCES.txt +0 -0
- {pybounds-0.0.4 → pybounds-0.0.5}/pybounds.egg-info/dependency_links.txt +0 -0
- {pybounds-0.0.4 → pybounds-0.0.5}/pybounds.egg-info/top_level.txt +0 -0
- {pybounds-0.0.4 → pybounds-0.0.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pybounds
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Bounding Observability for Uncertain Nonlinear Dynamics Systems (BOUNDS)
|
|
5
5
|
Home-page: https://pypi.org/project/pybounds/
|
|
6
6
|
Author: Ben Cellini, Burak Boyacioglu, Floris van Breugel
|
|
@@ -16,16 +16,9 @@ License-File: LICENSE
|
|
|
16
16
|
|
|
17
17
|
Python implementation of BOUNDS: Bounding Observability for Uncertain Nonlinear Dynamic Systems.
|
|
18
18
|
|
|
19
|
-
<p align="center">
|
|
20
|
-
<a href="https://pynumdiff.readthedocs.io/en/master/" target="_blank" >
|
|
21
|
-
|
|
22
|
-
[//]: # ( <img alt="Python for Numerical Differentiation of noisy time series data" src="docs/source/_static/logo_PyNumDiff.png" width="300" height="200" />)
|
|
23
|
-
</a>
|
|
24
|
-
</p>
|
|
25
|
-
|
|
26
19
|
<p align="center">
|
|
27
20
|
<a href="https://pypi.org/project/pybounds/">
|
|
28
|
-
<img src="https://badge.fury.io/py/
|
|
21
|
+
<img src="https://badge.fury.io/py/pybounds.svg" alt="PyPI version" height="18"></a>
|
|
29
22
|
</p>
|
|
30
23
|
|
|
31
24
|
## Introduction
|
|
@@ -42,9 +35,12 @@ pip install pybounds
|
|
|
42
35
|
```
|
|
43
36
|
|
|
44
37
|
## Notebook examples
|
|
45
|
-
|
|
38
|
+
For a simple system
|
|
46
39
|
* Monocular camera with optic fow measurements: [mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
|
|
47
40
|
|
|
41
|
+
For a more complex system
|
|
42
|
+
* Fly-wind: [fly_wind_example.ipynb](examples%2Ffly_wind_example.ipynb)[mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
|
|
43
|
+
|
|
48
44
|
## Citation
|
|
49
45
|
|
|
50
46
|
If you use the code or methods from this package, please cite the following paper:
|
|
@@ -2,16 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Python implementation of BOUNDS: Bounding Observability for Uncertain Nonlinear Dynamic Systems.
|
|
4
4
|
|
|
5
|
-
<p align="center">
|
|
6
|
-
<a href="https://pynumdiff.readthedocs.io/en/master/" target="_blank" >
|
|
7
|
-
|
|
8
|
-
[//]: # ( <img alt="Python for Numerical Differentiation of noisy time series data" src="docs/source/_static/logo_PyNumDiff.png" width="300" height="200" />)
|
|
9
|
-
</a>
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
5
|
<p align="center">
|
|
13
6
|
<a href="https://pypi.org/project/pybounds/">
|
|
14
|
-
<img src="https://badge.fury.io/py/
|
|
7
|
+
<img src="https://badge.fury.io/py/pybounds.svg" alt="PyPI version" height="18"></a>
|
|
15
8
|
</p>
|
|
16
9
|
|
|
17
10
|
## Introduction
|
|
@@ -28,9 +21,12 @@ pip install pybounds
|
|
|
28
21
|
```
|
|
29
22
|
|
|
30
23
|
## Notebook examples
|
|
31
|
-
|
|
24
|
+
For a simple system
|
|
32
25
|
* Monocular camera with optic fow measurements: [mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
|
|
33
26
|
|
|
27
|
+
For a more complex system
|
|
28
|
+
* Fly-wind: [fly_wind_example.ipynb](examples%2Ffly_wind_example.ipynb)[mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
|
|
29
|
+
|
|
34
30
|
## Citation
|
|
35
31
|
|
|
36
32
|
If you use the code or methods from this package, please cite the following paper:
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
2
1
|
import numpy as np
|
|
3
2
|
import pandas as pd
|
|
4
|
-
from multiprocessing import Pool
|
|
3
|
+
# from multiprocessing import Pool
|
|
4
|
+
# from pathos.multiprocessing import ProcessingPool as Pool
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
6
|
import warnings
|
|
6
|
-
import matplotlib as mpl
|
|
7
|
+
# import matplotlib as mpl
|
|
7
8
|
import matplotlib.pyplot as plt
|
|
8
9
|
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
|
|
9
10
|
import sympy as sp
|
|
@@ -92,8 +93,11 @@ class EmpiricalObservabilityMatrix:
|
|
|
92
93
|
# Run simulations for perturbed initial conditions
|
|
93
94
|
state_index = np.arange(0, self.n).tolist()
|
|
94
95
|
if self.parallel: # multiprocessing
|
|
95
|
-
with Pool(4) as pool:
|
|
96
|
-
|
|
96
|
+
# with Pool(4) as pool:
|
|
97
|
+
# results = pool.map(self.simulate, state_index)
|
|
98
|
+
|
|
99
|
+
with ThreadPoolExecutor() as executor:
|
|
100
|
+
results = list(executor.map(self.simulate, state_index))
|
|
97
101
|
|
|
98
102
|
for n, r in enumerate(results):
|
|
99
103
|
delta_y, y_plus, y_minus = r
|
|
@@ -147,7 +151,7 @@ class SlidingEmpiricalObservabilityMatrix:
|
|
|
147
151
|
def __init__(self, simulator, t_sim, x_sim, u_sim, w=None, eps=1e-5,
|
|
148
152
|
parallel_sliding=False, parallel_perturbation=False):
|
|
149
153
|
""" Construct empirical observability matrix O in sliding windows along a trajectory.
|
|
150
|
-
|
|
154
|
+
|
|
151
155
|
:param callable simulator: Simulator object : y = simulator(x0, u, **kwargs)
|
|
152
156
|
y is (w x p) array. w is the number of time-steps and p is the number of measurements
|
|
153
157
|
:param np.array t_sim: time vector size N
|
|
@@ -201,7 +205,7 @@ class SlidingEmpiricalObservabilityMatrix:
|
|
|
201
205
|
raise ValueError('window size must be smaller than trajectory length')
|
|
202
206
|
|
|
203
207
|
# All the indices to calculate O
|
|
204
|
-
self.O_index = np.arange(0, self.N - self.w + 1,
|
|
208
|
+
self.O_index = np.arange(0, self.N - self.w + 1, step=1) # indices to compute O
|
|
205
209
|
self.O_time = self.t_sim[self.O_index] # times to compute O
|
|
206
210
|
self.n_point = len(self.O_index) # # of times to calculate O
|
|
207
211
|
|
|
@@ -229,8 +233,12 @@ class SlidingEmpiricalObservabilityMatrix:
|
|
|
229
233
|
# Construct O's
|
|
230
234
|
n_point_range = np.arange(0, self.n_point).astype(int)
|
|
231
235
|
if self.parallel_sliding: # multiprocessing
|
|
232
|
-
with Pool(4) as pool:
|
|
233
|
-
|
|
236
|
+
# with Pool(4) as pool:
|
|
237
|
+
# results = pool.map(self.construct, n_point_range)
|
|
238
|
+
|
|
239
|
+
with ThreadPoolExecutor(max_workers=12) as executor:
|
|
240
|
+
results = list(executor.map(self.construct, n_point_range))
|
|
241
|
+
|
|
234
242
|
for r in results:
|
|
235
243
|
self.O_sliding.append(r[0])
|
|
236
244
|
self.O_df_sliding.append(r[1])
|
|
@@ -262,7 +270,8 @@ class SlidingEmpiricalObservabilityMatrix:
|
|
|
262
270
|
u_win = self.u_sim[win, :] # inputs in window
|
|
263
271
|
|
|
264
272
|
# Calculate O for window
|
|
265
|
-
EOM = EmpiricalObservabilityMatrix(self.simulator, x0, t_win0, u_win, eps=self.eps,
|
|
273
|
+
EOM = EmpiricalObservabilityMatrix(self.simulator, x0, t_win0, u_win, eps=self.eps,
|
|
274
|
+
parallel=self.parallel_perturbation)
|
|
266
275
|
self.EOM = EOM
|
|
267
276
|
|
|
268
277
|
# Store data
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
|
|
2
|
+
import warnings
|
|
3
|
+
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import do_mpc
|
|
6
|
+
from .util import FixedKeysDict, SetDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Simulator(object):
|
|
10
|
+
def __init__(self, f, h, dt=0.01, n=None, m=None,
|
|
11
|
+
state_names=None, input_names=None, measurement_names=None,
|
|
12
|
+
params_simulator=None, mpc_horizon=10):
|
|
13
|
+
|
|
14
|
+
""" Simulator.
|
|
15
|
+
|
|
16
|
+
:param callable f: dynamics function f(X, U, t)
|
|
17
|
+
:param callable h: measurement function h(X, U, t)
|
|
18
|
+
:param float dt: sampling time in seconds
|
|
19
|
+
:param int n: number of states, optional but cannot be set if state_names is set
|
|
20
|
+
:param int m: number of inputs, optional but cannot be set if input_names is set
|
|
21
|
+
:param list state_names: names of states, optional but cannot be set if n is set
|
|
22
|
+
:param list input_names: names of inputs, optional but cannot be set if m is set
|
|
23
|
+
:param list measurement_names: names of measurements, optional
|
|
24
|
+
:param dict params_simulator: simulation parameters, optional
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
self.f = f
|
|
28
|
+
self.h = h
|
|
29
|
+
self.dt = dt
|
|
30
|
+
|
|
31
|
+
# Set state names
|
|
32
|
+
if state_names is None: # default state names
|
|
33
|
+
if n is None:
|
|
34
|
+
raise ValueError('must set state_names or n')
|
|
35
|
+
else:
|
|
36
|
+
self.n = int(n)
|
|
37
|
+
|
|
38
|
+
self.state_names = ['x_' + str(n) for n in range(self.n)]
|
|
39
|
+
else: # state names given
|
|
40
|
+
if n is not None:
|
|
41
|
+
raise ValueError('cannot set state_names and n')
|
|
42
|
+
|
|
43
|
+
self.state_names = list(state_names)
|
|
44
|
+
self.n = len(self.state_names)
|
|
45
|
+
# if len(self.state_names) != self.n:
|
|
46
|
+
# raise ValueError('state_names must have length equal to x0')
|
|
47
|
+
|
|
48
|
+
# Set input names
|
|
49
|
+
if input_names is None: # default input names
|
|
50
|
+
if m is None:
|
|
51
|
+
raise ValueError('must set input_names or m')
|
|
52
|
+
else:
|
|
53
|
+
self.m = int(m)
|
|
54
|
+
|
|
55
|
+
self.input_names = ['u_' + str(m) for m in range(self.m)]
|
|
56
|
+
else: # input names given
|
|
57
|
+
if m is not None:
|
|
58
|
+
raise ValueError('cannot set in and n')
|
|
59
|
+
|
|
60
|
+
self.input_names = list(input_names)
|
|
61
|
+
self.m = len(self.input_names)
|
|
62
|
+
# if len(self.input_names) != self.m:
|
|
63
|
+
# raise ValueError('input_names must have length equal to u0')
|
|
64
|
+
|
|
65
|
+
# Run measurement function to get measurement size
|
|
66
|
+
x0 = np.ones(self.n)
|
|
67
|
+
u0 = np.ones(self.m)
|
|
68
|
+
y = self.h(x0, u0)
|
|
69
|
+
self.p = len(y) # number of measurements
|
|
70
|
+
|
|
71
|
+
# Set measurement names
|
|
72
|
+
if measurement_names is None: # default measurement names
|
|
73
|
+
self.measurement_names = ['y_' + str(p) for p in range(self.p)]
|
|
74
|
+
else:
|
|
75
|
+
self.measurement_names = measurement_names
|
|
76
|
+
if len(self.measurement_names) != self.p:
|
|
77
|
+
raise ValueError('measurement_names must have length equal to y')
|
|
78
|
+
|
|
79
|
+
# Initialize time vector
|
|
80
|
+
self.w = 11 # initialize for w time-steps, but this can change later
|
|
81
|
+
self.time = np.arange(0, self.w * self.dt, step=self.dt) # time vector
|
|
82
|
+
|
|
83
|
+
# Define initial states & initialize state time-series
|
|
84
|
+
self.x0 = {}
|
|
85
|
+
self.x = {}
|
|
86
|
+
for n, state_name in enumerate(self.state_names):
|
|
87
|
+
self.x0[state_name] = x0[n]
|
|
88
|
+
self.x[state_name] = x0[n] * np.ones(self.w)
|
|
89
|
+
|
|
90
|
+
self.x0 = FixedKeysDict(self.x0)
|
|
91
|
+
self.x = FixedKeysDict(self.x)
|
|
92
|
+
|
|
93
|
+
# Initialize input time-series
|
|
94
|
+
self.u = {}
|
|
95
|
+
for m, input_name in enumerate(self.input_names):
|
|
96
|
+
self.u[input_name] = u0[m] * np.ones(self.w)
|
|
97
|
+
|
|
98
|
+
self.u = FixedKeysDict(self.u)
|
|
99
|
+
|
|
100
|
+
# Initialize measurement time-series
|
|
101
|
+
self.y = {}
|
|
102
|
+
for p, measurement_name in enumerate(self.measurement_names):
|
|
103
|
+
self.y[measurement_name] = 0.0 * np.ones(self.w)
|
|
104
|
+
|
|
105
|
+
self.y = FixedKeysDict(self.y)
|
|
106
|
+
|
|
107
|
+
# Initialize state set-points
|
|
108
|
+
self.setpoint = {}
|
|
109
|
+
for n, state_name in enumerate(self.state_names):
|
|
110
|
+
self.setpoint[state_name] = 0.0 * np.ones(self.w)
|
|
111
|
+
|
|
112
|
+
self.setpoint = FixedKeysDict(self.setpoint)
|
|
113
|
+
|
|
114
|
+
# Define continuous-time MPC model
|
|
115
|
+
self.model = do_mpc.model.Model('continuous')
|
|
116
|
+
|
|
117
|
+
# Define state variables
|
|
118
|
+
X = []
|
|
119
|
+
for n, state_name in enumerate(self.state_names):
|
|
120
|
+
x = self.model.set_variable(var_type='_x', var_name=state_name, shape=(1, 1))
|
|
121
|
+
X.append(x)
|
|
122
|
+
|
|
123
|
+
# Define input variables
|
|
124
|
+
U = []
|
|
125
|
+
for m, input_name in enumerate(self.input_names):
|
|
126
|
+
u = self.model.set_variable(var_type='_u', var_name=input_name, shape=(1, 1))
|
|
127
|
+
U.append(u)
|
|
128
|
+
|
|
129
|
+
# Define dynamics
|
|
130
|
+
Xdot = self.f(X, U)
|
|
131
|
+
for n, state_name in enumerate(self.state_names):
|
|
132
|
+
self.model.set_rhs(state_name, Xdot[n])
|
|
133
|
+
|
|
134
|
+
# Add time-varying set-point variables for later use with MPC
|
|
135
|
+
for n, state_name in enumerate(self.state_names):
|
|
136
|
+
x = self.model.set_variable(var_type='_tvp', var_name=state_name + str('_set'), shape=(1, 1))
|
|
137
|
+
|
|
138
|
+
# Build model
|
|
139
|
+
self.model.setup()
|
|
140
|
+
|
|
141
|
+
# Define simulator & simulator parameters
|
|
142
|
+
self.simulator = do_mpc.simulator.Simulator(self.model)
|
|
143
|
+
|
|
144
|
+
# Set simulation parameters
|
|
145
|
+
if params_simulator is None:
|
|
146
|
+
self.params_simulator = {
|
|
147
|
+
'integration_tool': 'idas', # cvodes, idas
|
|
148
|
+
'abstol': 1e-8,
|
|
149
|
+
'reltol': 1e-8,
|
|
150
|
+
't_step': self.dt
|
|
151
|
+
}
|
|
152
|
+
else:
|
|
153
|
+
self.params_simulator = params_simulator
|
|
154
|
+
|
|
155
|
+
self.simulator.set_param(**self.params_simulator)
|
|
156
|
+
|
|
157
|
+
# Setup MPC
|
|
158
|
+
self.mpc = do_mpc.controller.MPC(self.model)
|
|
159
|
+
self.mpc_horizon = mpc_horizon
|
|
160
|
+
setup_mpc = {
|
|
161
|
+
'n_horizon': self.mpc_horizon,
|
|
162
|
+
'n_robust': 0,
|
|
163
|
+
'open_loop': 0,
|
|
164
|
+
't_step': self.dt,
|
|
165
|
+
'state_discretization': 'collocation',
|
|
166
|
+
'collocation_type': 'radau',
|
|
167
|
+
'collocation_deg': 2,
|
|
168
|
+
'collocation_ni': 1,
|
|
169
|
+
'store_full_solution': False,
|
|
170
|
+
|
|
171
|
+
# Use MA27 linear solver in ipopt for faster calculations:
|
|
172
|
+
'nlpsol_opts': {'ipopt.linear_solver': 'mumps', # mumps, MA27
|
|
173
|
+
'ipopt.print_level': 0,
|
|
174
|
+
'ipopt.sb': 'yes',
|
|
175
|
+
'print_time': 0,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
self.mpc.set_param(**setup_mpc)
|
|
180
|
+
|
|
181
|
+
# Get template's for MPC time-varying parameters
|
|
182
|
+
self.mpc_tvp_template = self.mpc.get_tvp_template()
|
|
183
|
+
self.simulator_tvp_template = self.simulator.get_tvp_template()
|
|
184
|
+
|
|
185
|
+
# Set time-varying set-point functions
|
|
186
|
+
self.mpc.set_tvp_fun(self.mpc_tvp_function)
|
|
187
|
+
self.simulator.set_tvp_fun(self.simulator_tvp_function)
|
|
188
|
+
|
|
189
|
+
# Setup simulator
|
|
190
|
+
self.simulator.setup()
|
|
191
|
+
|
|
192
|
+
def simulator_tvp_function(self, t):
|
|
193
|
+
""" Set the set-point function for MPC simulator.
|
|
194
|
+
:param t: current time
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
mpc_horizon = self.mpc._settings.n_horizon
|
|
198
|
+
|
|
199
|
+
# Set current step index
|
|
200
|
+
k_step = int(np.round(t / self.dt))
|
|
201
|
+
if k_step >= mpc_horizon: # point is beyond end of input data
|
|
202
|
+
k_step = mpc_horizon - 1 # set point beyond input data to last point
|
|
203
|
+
|
|
204
|
+
# Update current set-point
|
|
205
|
+
for n, state_name in enumerate(self.state_names):
|
|
206
|
+
self.simulator_tvp_template[state_name + '_set'] = self.setpoint[state_name][k_step]
|
|
207
|
+
|
|
208
|
+
return self.simulator_tvp_template
|
|
209
|
+
|
|
210
|
+
def mpc_tvp_function(self, t):
|
|
211
|
+
""" Set the set-point function for MPC optimizer.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
mpc_horizon = self.mpc._settings.n_horizon
|
|
215
|
+
|
|
216
|
+
# Set current step index
|
|
217
|
+
k_step = int(np.round(t / self.dt))
|
|
218
|
+
|
|
219
|
+
# Update set-point time horizon
|
|
220
|
+
for k in range(mpc_horizon + 1):
|
|
221
|
+
k_set = k_step + k
|
|
222
|
+
if k_set >= self.w: # horizon is beyond end of input data
|
|
223
|
+
k_set = self.w - 1 # set part of horizon beyond input data to last point
|
|
224
|
+
|
|
225
|
+
# Update each set-point over time horizon
|
|
226
|
+
for n, state_name in enumerate(self.state_names):
|
|
227
|
+
self.mpc_tvp_template['_tvp', k, state_name + '_set'] = self.setpoint[state_name][k_set]
|
|
228
|
+
|
|
229
|
+
return self.mpc_tvp_template
|
|
230
|
+
|
|
231
|
+
def set_initial_state(self, x0):
|
|
232
|
+
""" Update the initial state.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
if x0 is not None: # initial state given
|
|
236
|
+
if isinstance(x0, dict): # in dict format
|
|
237
|
+
SetDict().set_dict_with_overwrite(self.x0, x0) # update only the states in the dict given
|
|
238
|
+
elif isinstance(x0, list) or isinstance(x0, tuple) or (
|
|
239
|
+
x0, np.ndarray): # list, tuple, or numpy array format
|
|
240
|
+
x0 = np.array(x0).squeeze()
|
|
241
|
+
for n, key in enumerate(self.x0.keys()): # each state
|
|
242
|
+
self.x0[key] = x0[n]
|
|
243
|
+
else:
|
|
244
|
+
raise Exception('x0 must be either a dict, tuple, list, or numpy array')
|
|
245
|
+
|
|
246
|
+
def update_dict(self, data=None, name=None):
|
|
247
|
+
""" Update.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
update = getattr(self, name)
|
|
251
|
+
|
|
252
|
+
if data is not None: # data given
|
|
253
|
+
if isinstance(data, dict): # in dict format
|
|
254
|
+
SetDict().set_dict_with_overwrite(update, data) # update only the inputs in the dict given
|
|
255
|
+
elif isinstance(data, list) or isinstance(data, tuple): # list or tuple format, each input vector in each element
|
|
256
|
+
for n, k in enumerate(update.keys()): # each state
|
|
257
|
+
update[k] = data[n]
|
|
258
|
+
elif isinstance(data, np.ndarray): # numpy array format given as matrix where columns are the different inputs
|
|
259
|
+
if len(data.shape) <= 1: # given as 1d array, so convert to column vector
|
|
260
|
+
data = np.atleast_2d(data).T
|
|
261
|
+
|
|
262
|
+
for n, key in enumerate(update.keys()): # each input
|
|
263
|
+
update[key] = data[:, n]
|
|
264
|
+
|
|
265
|
+
else:
|
|
266
|
+
raise Exception(name + ' must be either a dict, tuple, list, or numpy array')
|
|
267
|
+
|
|
268
|
+
# Make sure inputs are the same size
|
|
269
|
+
points = np.array([update[key].shape[0] for key in update.keys()])
|
|
270
|
+
points_check = points == points[0]
|
|
271
|
+
if not np.all(points_check):
|
|
272
|
+
raise Exception(name + ' not the same size')
|
|
273
|
+
|
|
274
|
+
def simulate(self, x0=None, u=None, mpc=False, return_full_output=False):
|
|
275
|
+
"""
|
|
276
|
+
Simulate the system.
|
|
277
|
+
|
|
278
|
+
:params x0: initial state dict or array
|
|
279
|
+
:params u: input dict or array
|
|
280
|
+
:params return_full_output: boolean to run (time, x, u, y) instead of y
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
if (mpc is True) and (u is not None):
|
|
284
|
+
raise Exception('u must be None if running MPC')
|
|
285
|
+
|
|
286
|
+
if (mpc is False) and (u is None):
|
|
287
|
+
warnings.warn('not running MPC or setting u directly')
|
|
288
|
+
|
|
289
|
+
# Update the initial state
|
|
290
|
+
if x0 is None:
|
|
291
|
+
if mpc: # set the initial state to start at set-point if running MPC
|
|
292
|
+
x0 = {}
|
|
293
|
+
for state_name in self.state_names:
|
|
294
|
+
x0[state_name] = self.setpoint[state_name][0]
|
|
295
|
+
|
|
296
|
+
self.set_initial_state(x0=x0)
|
|
297
|
+
else:
|
|
298
|
+
self.set_initial_state(x0=x0)
|
|
299
|
+
|
|
300
|
+
# Update the inputs
|
|
301
|
+
self.update_dict(u, name='u')
|
|
302
|
+
|
|
303
|
+
# Concatenate the inputs, where rows are individual inputs and columns are time-steps
|
|
304
|
+
if mpc:
|
|
305
|
+
self.w = np.vstack(list(self.setpoint.values())).shape[1]
|
|
306
|
+
u_sim = np.zeros((self.w, self.m)) # preallocate input array
|
|
307
|
+
else:
|
|
308
|
+
self.w = np.vstack(list(self.u.values())).shape[1]
|
|
309
|
+
u_sim = np.vstack(list(self.u.values())).T
|
|
310
|
+
|
|
311
|
+
# Update time vector
|
|
312
|
+
T = (self.w - 1) * self.dt
|
|
313
|
+
self.time = np.linspace(0, T, num=self.w)
|
|
314
|
+
|
|
315
|
+
# Set array to store simulated states, where rows are individual states and columns are time-steps
|
|
316
|
+
x_step = np.array(list(self.x0.values())) # initialize state
|
|
317
|
+
x = np.nan * np.zeros((self.w, self.n))
|
|
318
|
+
x[0, :] = x_step.copy()
|
|
319
|
+
|
|
320
|
+
# Initialize the simulator
|
|
321
|
+
self.simulator.t0 = self.time[0]
|
|
322
|
+
self.simulator.x0 = x_step.copy()
|
|
323
|
+
self.simulator.set_initial_guess()
|
|
324
|
+
|
|
325
|
+
# Initialize MPC
|
|
326
|
+
if mpc:
|
|
327
|
+
self.mpc.setup()
|
|
328
|
+
self.mpc.t0 = self.time[0]
|
|
329
|
+
self.mpc.x0 = x_step.copy()
|
|
330
|
+
self.mpc.u0 = np.zeros((self.m, 1))
|
|
331
|
+
self.mpc.set_initial_guess()
|
|
332
|
+
|
|
333
|
+
# Run simulation
|
|
334
|
+
for k in range(1, self.w):
|
|
335
|
+
# Set input
|
|
336
|
+
if mpc: # run MPC step
|
|
337
|
+
u_step = self.mpc.make_step(x_step)
|
|
338
|
+
else: # use inputs directly
|
|
339
|
+
u_step = u_sim[k - 1:k, :].T
|
|
340
|
+
|
|
341
|
+
# Store inputs
|
|
342
|
+
u_sim[k - 1, :] = u_step.squeeze()
|
|
343
|
+
|
|
344
|
+
# Simulate one time step given current inputs
|
|
345
|
+
x_step = self.simulator.make_step(u_step)
|
|
346
|
+
|
|
347
|
+
# Store new states
|
|
348
|
+
x[k, :] = x_step.squeeze()
|
|
349
|
+
|
|
350
|
+
# Last input has no effect, so keep it the same as previous time-step
|
|
351
|
+
if mpc:
|
|
352
|
+
u_sim[-1, :] = u_sim[-2, :]
|
|
353
|
+
|
|
354
|
+
# Update the inputs
|
|
355
|
+
self.update_dict(u_sim, name='u')
|
|
356
|
+
|
|
357
|
+
# Update state trajectory
|
|
358
|
+
self.update_dict(x, name='x')
|
|
359
|
+
|
|
360
|
+
# Calculate measurements
|
|
361
|
+
x_list = list(self.x.values())
|
|
362
|
+
u_list = list(self.u.values())
|
|
363
|
+
y = self.h(x_list, u_list)
|
|
364
|
+
|
|
365
|
+
# Set measurements
|
|
366
|
+
self.update_dict(y, name='y')
|
|
367
|
+
|
|
368
|
+
# Return the measurements in array format
|
|
369
|
+
y_array = np.vstack(list(self.y.values())).T
|
|
370
|
+
|
|
371
|
+
if return_full_output:
|
|
372
|
+
return self.time.copy(), self.x.copy(), self.u.copy(), self.u.copy()
|
|
373
|
+
else:
|
|
374
|
+
return y_array
|
|
375
|
+
|
|
376
|
+
def get_time_states_inputs_measurements(self):
|
|
377
|
+
return self.time.copy(), self.x.copy(), self.u.copy(), self.u.copy()
|
|
378
|
+
|
|
379
|
+
def plot(self, name='x', dpi=150, plot_kwargs=None):
|
|
380
|
+
""" Plot states, inputs.
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
if plot_kwargs is None:
|
|
384
|
+
plot_kwargs = {
|
|
385
|
+
'color': 'black',
|
|
386
|
+
'linewidth': 2.0,
|
|
387
|
+
'linestyle': '-',
|
|
388
|
+
'marker': '.',
|
|
389
|
+
'markersize': 0
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if name == 'x':
|
|
393
|
+
plot_kwargs['color'] = 'firebrick'
|
|
394
|
+
elif name == 'u':
|
|
395
|
+
plot_kwargs['color'] = 'royalblue'
|
|
396
|
+
elif name == 'y':
|
|
397
|
+
plot_kwargs['color'] = 'seagreen'
|
|
398
|
+
elif name == 'setpoint':
|
|
399
|
+
plot_kwargs['color'] = 'gray'
|
|
400
|
+
|
|
401
|
+
plot_dict = getattr(self, name)
|
|
402
|
+
plot_data = np.array(list(plot_dict.values()))
|
|
403
|
+
n = plot_data.shape[0]
|
|
404
|
+
|
|
405
|
+
fig, ax = plt.subplots(n, 1, figsize=(4, n * 1.5), dpi=dpi, sharex=True)
|
|
406
|
+
ax = np.atleast_1d(ax)
|
|
407
|
+
|
|
408
|
+
for n, key in enumerate(plot_dict.keys()):
|
|
409
|
+
ax[n].plot(self.time, plot_dict[key], label='set-point', **plot_kwargs)
|
|
410
|
+
ax[n].set_ylabel(key, fontsize=7)
|
|
411
|
+
|
|
412
|
+
# Also plot the states if plotting setpoint
|
|
413
|
+
if name == 'setpoint':
|
|
414
|
+
ax[n].plot(self.time, self.x[key], label=key, color='firebrick', linestyle='-', linewidth=0.5)
|
|
415
|
+
ax[n].legend(fontsize=6)
|
|
416
|
+
|
|
417
|
+
ax[-1].set_xlabel('time', fontsize=7)
|
|
418
|
+
ax[0].set_title(name, fontsize=8, fontweight='bold')
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
for a in ax.flat:
|
|
422
|
+
a.tick_params(axis='both', labelsize=6)
|
|
@@ -74,6 +74,7 @@ class LatexStates:
|
|
|
74
74
|
'v_perp': r'$v_{\perp}$',
|
|
75
75
|
'phi': r'$\phi$',
|
|
76
76
|
'phidot': r'$\dot{\phi}$',
|
|
77
|
+
'phi_dot': r'$\dot{\phi}$',
|
|
77
78
|
'phiddot': r'$\ddot{\phi}$',
|
|
78
79
|
'w': r'$w$',
|
|
79
80
|
'zeta': r'$\zeta$',
|
|
@@ -97,7 +98,9 @@ class LatexStates:
|
|
|
97
98
|
'v_para_dot_ratio': r'$\frac{\Delta v_{\parallel}}{v_{\parallel}}$',
|
|
98
99
|
'sigma': r'$\sigma$',
|
|
99
100
|
'rho': r'$\rho$',
|
|
100
|
-
'beta': r'$\beta$'
|
|
101
|
+
'beta': r'$\beta$',
|
|
102
|
+
'lambda': r'$\lambda',
|
|
103
|
+
'delta': r'$\delta'
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
if dict is not None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pybounds
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Bounding Observability for Uncertain Nonlinear Dynamics Systems (BOUNDS)
|
|
5
5
|
Home-page: https://pypi.org/project/pybounds/
|
|
6
6
|
Author: Ben Cellini, Burak Boyacioglu, Floris van Breugel
|
|
@@ -16,16 +16,9 @@ License-File: LICENSE
|
|
|
16
16
|
|
|
17
17
|
Python implementation of BOUNDS: Bounding Observability for Uncertain Nonlinear Dynamic Systems.
|
|
18
18
|
|
|
19
|
-
<p align="center">
|
|
20
|
-
<a href="https://pynumdiff.readthedocs.io/en/master/" target="_blank" >
|
|
21
|
-
|
|
22
|
-
[//]: # ( <img alt="Python for Numerical Differentiation of noisy time series data" src="docs/source/_static/logo_PyNumDiff.png" width="300" height="200" />)
|
|
23
|
-
</a>
|
|
24
|
-
</p>
|
|
25
|
-
|
|
26
19
|
<p align="center">
|
|
27
20
|
<a href="https://pypi.org/project/pybounds/">
|
|
28
|
-
<img src="https://badge.fury.io/py/
|
|
21
|
+
<img src="https://badge.fury.io/py/pybounds.svg" alt="PyPI version" height="18"></a>
|
|
29
22
|
</p>
|
|
30
23
|
|
|
31
24
|
## Introduction
|
|
@@ -42,9 +35,12 @@ pip install pybounds
|
|
|
42
35
|
```
|
|
43
36
|
|
|
44
37
|
## Notebook examples
|
|
45
|
-
|
|
38
|
+
For a simple system
|
|
46
39
|
* Monocular camera with optic fow measurements: [mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
|
|
47
40
|
|
|
41
|
+
For a more complex system
|
|
42
|
+
* Fly-wind: [fly_wind_example.ipynb](examples%2Ffly_wind_example.ipynb)[mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
|
|
43
|
+
|
|
48
44
|
## Citation
|
|
49
45
|
|
|
50
46
|
If you use the code or methods from this package, please cite the following paper:
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="pybounds", # Replace with your own username
|
|
8
|
-
version="0.0.
|
|
8
|
+
version="0.0.5",
|
|
9
9
|
author="Ben Cellini, Burak Boyacioglu, Floris van Breugel",
|
|
10
10
|
author_email="bcellini00@gmail.com",
|
|
11
11
|
description="Bounding Observability for Uncertain Nonlinear Dynamics Systems (BOUNDS)",
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import numpy as np
|
|
3
|
-
import do_mpc
|
|
4
|
-
from .util import FixedKeysDict, SetDict
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Simulator(object):
|
|
8
|
-
def __init__(self, f, h, dt=0.01, n=None, m=None,
|
|
9
|
-
state_names=None, input_names=None, measurement_names=None,
|
|
10
|
-
params_simulator=None):
|
|
11
|
-
|
|
12
|
-
""" Simulator.
|
|
13
|
-
|
|
14
|
-
:param callable f: dynamics function f(X, U, t)
|
|
15
|
-
:param callable h: measurement function h(X, U, t)
|
|
16
|
-
:param float dt: sampling time in seconds
|
|
17
|
-
:param int n: number of states, optional but cannot be set if state_names is set
|
|
18
|
-
:param int m: number of inputs, optional but cannot be set if input_names is set
|
|
19
|
-
:param list state_names: names of states, optional but cannot be set if n is set
|
|
20
|
-
:param list input_names: names of inputs, optional but cannot be set if m is set
|
|
21
|
-
:param list measurement_names: names of measurements, optional
|
|
22
|
-
:param dict params_simulator: simulation parameters, optional
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
self.f = f
|
|
26
|
-
self.h = h
|
|
27
|
-
self.dt = dt
|
|
28
|
-
|
|
29
|
-
# Set state names
|
|
30
|
-
if state_names is None: # default state names
|
|
31
|
-
if n is None:
|
|
32
|
-
raise ValueError('must set state_names or n')
|
|
33
|
-
else:
|
|
34
|
-
self.n = int(n)
|
|
35
|
-
|
|
36
|
-
self.state_names = ['x_' + str(n) for n in range(self.n)]
|
|
37
|
-
else: # state names given
|
|
38
|
-
if n is not None:
|
|
39
|
-
raise ValueError('cannot set state_names and n')
|
|
40
|
-
|
|
41
|
-
self.state_names = list(state_names)
|
|
42
|
-
self.n = len(self.state_names)
|
|
43
|
-
# if len(self.state_names) != self.n:
|
|
44
|
-
# raise ValueError('state_names must have length equal to x0')
|
|
45
|
-
|
|
46
|
-
# Set input names
|
|
47
|
-
if input_names is None: # default input names
|
|
48
|
-
if m is None:
|
|
49
|
-
raise ValueError('must set input_names or m')
|
|
50
|
-
else:
|
|
51
|
-
self.m = int(m)
|
|
52
|
-
|
|
53
|
-
self.input_names = ['u_' + str(m) for m in range(self.m)]
|
|
54
|
-
else: # input names given
|
|
55
|
-
if m is not None:
|
|
56
|
-
raise ValueError('cannot set in and n')
|
|
57
|
-
|
|
58
|
-
self.input_names = list(input_names)
|
|
59
|
-
self.m = len(self.input_names)
|
|
60
|
-
# if len(self.input_names) != self.m:
|
|
61
|
-
# raise ValueError('input_names must have length equal to u0')
|
|
62
|
-
|
|
63
|
-
# Run measurement function to get measurement size
|
|
64
|
-
x0 = np.ones(self.n)
|
|
65
|
-
u0 = np.ones(self.m)
|
|
66
|
-
y = self.h(x0, u0, 0)
|
|
67
|
-
self.p = len(y) # number of measurements
|
|
68
|
-
|
|
69
|
-
# Set measurement names
|
|
70
|
-
if measurement_names is None: # default measurement names
|
|
71
|
-
self.measurement_names = ['y_' + str(p) for p in range(self.p)]
|
|
72
|
-
else:
|
|
73
|
-
self.measurement_names = measurement_names
|
|
74
|
-
if len(self.measurement_names) != self.p:
|
|
75
|
-
raise ValueError('measurement_names must have length equal to y')
|
|
76
|
-
|
|
77
|
-
# Initialize time vector
|
|
78
|
-
w = 10 # initialize for w time-steps, but this can change later
|
|
79
|
-
self.time = np.arange(0, w * self.dt + self.dt / 2, step=self.dt) # time vector
|
|
80
|
-
|
|
81
|
-
# Define initial states & initialize state time-series
|
|
82
|
-
self.x0 = {}
|
|
83
|
-
self.x = {}
|
|
84
|
-
for n, state_name in enumerate(self.state_names):
|
|
85
|
-
self.x0[state_name] = x0[n]
|
|
86
|
-
self.x[state_name] = x0[n] * np.ones(w)
|
|
87
|
-
|
|
88
|
-
self.x0 = FixedKeysDict(self.x0)
|
|
89
|
-
|
|
90
|
-
# Initialize input time-series
|
|
91
|
-
self.u = {}
|
|
92
|
-
for m, input_name in enumerate(self.input_names):
|
|
93
|
-
self.u[input_name] = u0[m] * np.ones(w)
|
|
94
|
-
|
|
95
|
-
self.u = FixedKeysDict(self.u)
|
|
96
|
-
|
|
97
|
-
# Initialize measurement time-series
|
|
98
|
-
self.y = {}
|
|
99
|
-
for p, measurement_name in enumerate(self.measurement_names):
|
|
100
|
-
self.y[measurement_name] = 0.0 * np.ones(w)
|
|
101
|
-
|
|
102
|
-
self.y = FixedKeysDict(self.y)
|
|
103
|
-
|
|
104
|
-
# Define continuous-time MPC model
|
|
105
|
-
self.model = do_mpc.model.Model('continuous')
|
|
106
|
-
|
|
107
|
-
# Define state variables
|
|
108
|
-
X = []
|
|
109
|
-
for n, state_name in enumerate(self.state_names):
|
|
110
|
-
x = self.model.set_variable(var_type='_x', var_name=state_name, shape=(1, 1))
|
|
111
|
-
X.append(x)
|
|
112
|
-
|
|
113
|
-
# Define input variables
|
|
114
|
-
U = []
|
|
115
|
-
for m, input_name in enumerate(self.input_names):
|
|
116
|
-
u = self.model.set_variable(var_type='_u', var_name=input_name, shape=(1, 1))
|
|
117
|
-
U.append(u)
|
|
118
|
-
|
|
119
|
-
# Define dynamics
|
|
120
|
-
Xdot = self.f(X, U, 0)
|
|
121
|
-
for n, state_name in enumerate(self.state_names):
|
|
122
|
-
self.model.set_rhs(state_name, Xdot[n])
|
|
123
|
-
|
|
124
|
-
# Build model
|
|
125
|
-
self.model.setup()
|
|
126
|
-
|
|
127
|
-
# Define simulator & simulator parameters
|
|
128
|
-
self.simulator = do_mpc.simulator.Simulator(self.model)
|
|
129
|
-
|
|
130
|
-
# Set simulation parameters
|
|
131
|
-
if params_simulator is None:
|
|
132
|
-
self.params_simulator = {
|
|
133
|
-
'integration_tool': 'idas', # cvodes, idas
|
|
134
|
-
'abstol': 1e-8,
|
|
135
|
-
'reltol': 1e-8,
|
|
136
|
-
't_step': self.dt
|
|
137
|
-
}
|
|
138
|
-
else:
|
|
139
|
-
self.params_simulator = params_simulator
|
|
140
|
-
|
|
141
|
-
self.simulator.set_param(**self.params_simulator)
|
|
142
|
-
self.simulator.setup()
|
|
143
|
-
|
|
144
|
-
def set_initial_state(self, x0):
|
|
145
|
-
""" Update the initial state.
|
|
146
|
-
"""
|
|
147
|
-
|
|
148
|
-
if x0 is not None: # initial state given
|
|
149
|
-
if isinstance(x0, dict): # in dict format
|
|
150
|
-
SetDict().set_dict_with_overwrite(self.x0, x0) # update only the states in the dict given
|
|
151
|
-
elif isinstance(x0, list) or isinstance(x0, tuple) or (
|
|
152
|
-
x0, np.ndarray): # list, tuple, or numpy array format
|
|
153
|
-
x0 = np.array(x0).squeeze()
|
|
154
|
-
for n, key in enumerate(self.x0.keys()): # each state
|
|
155
|
-
self.x0[key] = x0[n]
|
|
156
|
-
else:
|
|
157
|
-
raise Exception('x0 must be either a dict, tuple, list, or numpy array')
|
|
158
|
-
|
|
159
|
-
def set_inputs(self, u):
|
|
160
|
-
""" Update the inputs.
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
if u is not None: # inputs given
|
|
164
|
-
if isinstance(u, dict): # in dict format
|
|
165
|
-
SetDict().set_dict_with_overwrite(self.u, u) # update only the inputs in the dict given
|
|
166
|
-
elif isinstance(u, list) or isinstance(u, tuple): # list or tuple format, each input vector in each element
|
|
167
|
-
for n, k in enumerate(self.u.keys()): # each input
|
|
168
|
-
self.u[k] = u[n]
|
|
169
|
-
elif isinstance(u, np.ndarray): # numpy array format given as matrix where columns are the different inputs
|
|
170
|
-
if len(u.shape) <= 1: # given as 1d array, so convert to column vector
|
|
171
|
-
u = np.atleast_2d(u).T
|
|
172
|
-
|
|
173
|
-
for m, key in enumerate(self.u.keys()): # each input
|
|
174
|
-
self.u[key] = u[:, m]
|
|
175
|
-
|
|
176
|
-
else:
|
|
177
|
-
raise Exception('u must be either a dict, tuple, list, or numpy array')
|
|
178
|
-
|
|
179
|
-
# Make sure inputs are the same size
|
|
180
|
-
points = np.array([self.u[key].shape[0] for key in self.u.keys()])
|
|
181
|
-
points_check = points == points[0]
|
|
182
|
-
if not np.all(points_check):
|
|
183
|
-
raise Exception('inputs are not the same size')
|
|
184
|
-
|
|
185
|
-
def simulate(self, x0=None, u=None, return_full_output=False):
|
|
186
|
-
"""
|
|
187
|
-
Simulate the system.
|
|
188
|
-
|
|
189
|
-
:params x0: initial state dict or array
|
|
190
|
-
:params u: input dict or array
|
|
191
|
-
:params return_full_output: boolean to run (time, x, u, y) instead of y
|
|
192
|
-
"""
|
|
193
|
-
|
|
194
|
-
# Update the initial state
|
|
195
|
-
self.set_initial_state(x0=x0.copy())
|
|
196
|
-
|
|
197
|
-
# Update the inputs
|
|
198
|
-
self.set_inputs(u=u)
|
|
199
|
-
|
|
200
|
-
# Concatenate the inputs, where rows are individual inputs and columns are time-steps
|
|
201
|
-
u_sim = np.vstack(list(self.u.values())).T
|
|
202
|
-
n_point = u_sim.shape[0]
|
|
203
|
-
|
|
204
|
-
# Update time vector
|
|
205
|
-
T = (n_point - 1) * self.dt
|
|
206
|
-
self.time = np.linspace(0, T, num=n_point)
|
|
207
|
-
|
|
208
|
-
# Set array to store simulated states, where rows are individual states and columns are time-steps
|
|
209
|
-
x_step = np.array(list(self.x0.values())) # initialize state
|
|
210
|
-
x_sim = np.nan * np.zeros((n_point, self.n))
|
|
211
|
-
x_sim[0, :] = x_step.copy()
|
|
212
|
-
|
|
213
|
-
# Initialize the simulator
|
|
214
|
-
self.simulator.t0 = self.time[0]
|
|
215
|
-
self.simulator.x0 = x_step.copy()
|
|
216
|
-
self.simulator.set_initial_guess()
|
|
217
|
-
|
|
218
|
-
# Run simulation
|
|
219
|
-
for k in range(1, n_point):
|
|
220
|
-
# Set input
|
|
221
|
-
u_step = u_sim[k - 1:k, :].T
|
|
222
|
-
|
|
223
|
-
# Store inputs
|
|
224
|
-
u_sim[k - 1, :] = u_step.squeeze()
|
|
225
|
-
|
|
226
|
-
# Simulate one time step given current inputs
|
|
227
|
-
x_step = self.simulator.make_step(u_step)
|
|
228
|
-
|
|
229
|
-
# Store new states
|
|
230
|
-
x_sim[k, :] = x_step.squeeze()
|
|
231
|
-
|
|
232
|
-
# Update the inputs
|
|
233
|
-
self.set_inputs(u=u_sim)
|
|
234
|
-
|
|
235
|
-
# Update state trajectory
|
|
236
|
-
for n, key in enumerate(self.x.keys()):
|
|
237
|
-
self.x[key] = x_sim[:, n]
|
|
238
|
-
|
|
239
|
-
# Calculate measurements
|
|
240
|
-
x_list = list(self.x.values())
|
|
241
|
-
u_list = list(self.u.values())
|
|
242
|
-
y = self.h(x_list, u_list, 0)
|
|
243
|
-
|
|
244
|
-
# Set outputs
|
|
245
|
-
self.y = {}
|
|
246
|
-
for p, measurement_name in enumerate(self.measurement_names):
|
|
247
|
-
self.y[measurement_name] = y[p]
|
|
248
|
-
|
|
249
|
-
# Return the outputs in array format
|
|
250
|
-
y_array = np.vstack(list(self.y.values())).T
|
|
251
|
-
|
|
252
|
-
if return_full_output:
|
|
253
|
-
return self.time.copy(), self.x.copy(), self.u.copy(), self.u.copy()
|
|
254
|
-
else:
|
|
255
|
-
return y_array
|
|
256
|
-
|
|
257
|
-
def get_time_states_input_measurements(self):
|
|
258
|
-
return self.time.copy(), self.x.copy(), self.u.copy(), self.u.copy()
|
|
259
|
-
|
|
260
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|