pybounds 0.0.4__py3-none-any.whl → 0.0.5__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.
Potentially problematic release.
This version of pybounds might be problematic. Click here for more details.
- pybounds/observability.py +19 -10
- pybounds/simulator.py +208 -46
- pybounds/util.py +4 -1
- {pybounds-0.0.4.dist-info → pybounds-0.0.5.dist-info}/METADATA +6 -10
- pybounds-0.0.5.dist-info/RECORD +9 -0
- pybounds-0.0.4.dist-info/RECORD +0 -9
- {pybounds-0.0.4.dist-info → pybounds-0.0.5.dist-info}/LICENSE +0 -0
- {pybounds-0.0.4.dist-info → pybounds-0.0.5.dist-info}/WHEEL +0 -0
- {pybounds-0.0.4.dist-info → pybounds-0.0.5.dist-info}/top_level.txt +0 -0
pybounds/observability.py
CHANGED
|
@@ -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
|
pybounds/simulator.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
|
|
2
|
+
import warnings
|
|
2
3
|
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
3
5
|
import do_mpc
|
|
4
6
|
from .util import FixedKeysDict, SetDict
|
|
5
7
|
|
|
@@ -7,7 +9,7 @@ from .util import FixedKeysDict, SetDict
|
|
|
7
9
|
class Simulator(object):
|
|
8
10
|
def __init__(self, f, h, dt=0.01, n=None, m=None,
|
|
9
11
|
state_names=None, input_names=None, measurement_names=None,
|
|
10
|
-
params_simulator=None):
|
|
12
|
+
params_simulator=None, mpc_horizon=10):
|
|
11
13
|
|
|
12
14
|
""" Simulator.
|
|
13
15
|
|
|
@@ -63,7 +65,7 @@ class Simulator(object):
|
|
|
63
65
|
# Run measurement function to get measurement size
|
|
64
66
|
x0 = np.ones(self.n)
|
|
65
67
|
u0 = np.ones(self.m)
|
|
66
|
-
y = self.h(x0, u0
|
|
68
|
+
y = self.h(x0, u0)
|
|
67
69
|
self.p = len(y) # number of measurements
|
|
68
70
|
|
|
69
71
|
# Set measurement names
|
|
@@ -75,32 +77,40 @@ class Simulator(object):
|
|
|
75
77
|
raise ValueError('measurement_names must have length equal to y')
|
|
76
78
|
|
|
77
79
|
# Initialize time vector
|
|
78
|
-
w =
|
|
79
|
-
self.time = np.arange(0, w * self.dt
|
|
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
|
|
80
82
|
|
|
81
83
|
# Define initial states & initialize state time-series
|
|
82
84
|
self.x0 = {}
|
|
83
85
|
self.x = {}
|
|
84
86
|
for n, state_name in enumerate(self.state_names):
|
|
85
87
|
self.x0[state_name] = x0[n]
|
|
86
|
-
self.x[state_name] = x0[n] * np.ones(w)
|
|
88
|
+
self.x[state_name] = x0[n] * np.ones(self.w)
|
|
87
89
|
|
|
88
90
|
self.x0 = FixedKeysDict(self.x0)
|
|
91
|
+
self.x = FixedKeysDict(self.x)
|
|
89
92
|
|
|
90
93
|
# Initialize input time-series
|
|
91
94
|
self.u = {}
|
|
92
95
|
for m, input_name in enumerate(self.input_names):
|
|
93
|
-
self.u[input_name] = u0[m] * np.ones(w)
|
|
96
|
+
self.u[input_name] = u0[m] * np.ones(self.w)
|
|
94
97
|
|
|
95
98
|
self.u = FixedKeysDict(self.u)
|
|
96
99
|
|
|
97
100
|
# Initialize measurement time-series
|
|
98
101
|
self.y = {}
|
|
99
102
|
for p, measurement_name in enumerate(self.measurement_names):
|
|
100
|
-
self.y[measurement_name] = 0.0 * np.ones(w)
|
|
103
|
+
self.y[measurement_name] = 0.0 * np.ones(self.w)
|
|
101
104
|
|
|
102
105
|
self.y = FixedKeysDict(self.y)
|
|
103
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
|
+
|
|
104
114
|
# Define continuous-time MPC model
|
|
105
115
|
self.model = do_mpc.model.Model('continuous')
|
|
106
116
|
|
|
@@ -117,10 +127,14 @@ class Simulator(object):
|
|
|
117
127
|
U.append(u)
|
|
118
128
|
|
|
119
129
|
# Define dynamics
|
|
120
|
-
Xdot = self.f(X, U
|
|
130
|
+
Xdot = self.f(X, U)
|
|
121
131
|
for n, state_name in enumerate(self.state_names):
|
|
122
132
|
self.model.set_rhs(state_name, Xdot[n])
|
|
123
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
|
+
|
|
124
138
|
# Build model
|
|
125
139
|
self.model.setup()
|
|
126
140
|
|
|
@@ -139,8 +153,81 @@ class Simulator(object):
|
|
|
139
153
|
self.params_simulator = params_simulator
|
|
140
154
|
|
|
141
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
|
|
142
190
|
self.simulator.setup()
|
|
143
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
|
+
|
|
144
231
|
def set_initial_state(self, x0):
|
|
145
232
|
""" Update the initial state.
|
|
146
233
|
"""
|
|
@@ -156,33 +243,35 @@ class Simulator(object):
|
|
|
156
243
|
else:
|
|
157
244
|
raise Exception('x0 must be either a dict, tuple, list, or numpy array')
|
|
158
245
|
|
|
159
|
-
def
|
|
160
|
-
""" Update
|
|
246
|
+
def update_dict(self, data=None, name=None):
|
|
247
|
+
""" Update.
|
|
161
248
|
"""
|
|
162
249
|
|
|
163
|
-
|
|
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
|
|
250
|
+
update = getattr(self, name)
|
|
172
251
|
|
|
173
|
-
|
|
174
|
-
|
|
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]
|
|
175
264
|
|
|
176
265
|
else:
|
|
177
|
-
raise Exception('
|
|
266
|
+
raise Exception(name + ' must be either a dict, tuple, list, or numpy array')
|
|
178
267
|
|
|
179
268
|
# Make sure inputs are the same size
|
|
180
|
-
points = np.array([
|
|
269
|
+
points = np.array([update[key].shape[0] for key in update.keys()])
|
|
181
270
|
points_check = points == points[0]
|
|
182
271
|
if not np.all(points_check):
|
|
183
|
-
raise Exception('
|
|
272
|
+
raise Exception(name + ' not the same size')
|
|
184
273
|
|
|
185
|
-
def simulate(self, x0=None, u=None, return_full_output=False):
|
|
274
|
+
def simulate(self, x0=None, u=None, mpc=False, return_full_output=False):
|
|
186
275
|
"""
|
|
187
276
|
Simulate the system.
|
|
188
277
|
|
|
@@ -191,34 +280,63 @@ class Simulator(object):
|
|
|
191
280
|
:params return_full_output: boolean to run (time, x, u, y) instead of y
|
|
192
281
|
"""
|
|
193
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
|
+
|
|
194
289
|
# Update the initial state
|
|
195
|
-
|
|
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)
|
|
196
299
|
|
|
197
300
|
# Update the inputs
|
|
198
|
-
self.
|
|
301
|
+
self.update_dict(u, name='u')
|
|
199
302
|
|
|
200
303
|
# Concatenate the inputs, where rows are individual inputs and columns are time-steps
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
203
310
|
|
|
204
311
|
# Update time vector
|
|
205
|
-
T = (
|
|
206
|
-
self.time = np.linspace(0, T, num=
|
|
312
|
+
T = (self.w - 1) * self.dt
|
|
313
|
+
self.time = np.linspace(0, T, num=self.w)
|
|
207
314
|
|
|
208
315
|
# Set array to store simulated states, where rows are individual states and columns are time-steps
|
|
209
316
|
x_step = np.array(list(self.x0.values())) # initialize state
|
|
210
|
-
|
|
211
|
-
|
|
317
|
+
x = np.nan * np.zeros((self.w, self.n))
|
|
318
|
+
x[0, :] = x_step.copy()
|
|
212
319
|
|
|
213
320
|
# Initialize the simulator
|
|
214
321
|
self.simulator.t0 = self.time[0]
|
|
215
322
|
self.simulator.x0 = x_step.copy()
|
|
216
323
|
self.simulator.set_initial_guess()
|
|
217
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
|
+
|
|
218
333
|
# Run simulation
|
|
219
|
-
for k in range(1,
|
|
334
|
+
for k in range(1, self.w):
|
|
220
335
|
# Set input
|
|
221
|
-
|
|
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
|
|
222
340
|
|
|
223
341
|
# Store inputs
|
|
224
342
|
u_sim[k - 1, :] = u_step.squeeze()
|
|
@@ -227,26 +345,27 @@ class Simulator(object):
|
|
|
227
345
|
x_step = self.simulator.make_step(u_step)
|
|
228
346
|
|
|
229
347
|
# Store new states
|
|
230
|
-
|
|
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, :]
|
|
231
353
|
|
|
232
354
|
# Update the inputs
|
|
233
|
-
self.
|
|
355
|
+
self.update_dict(u_sim, name='u')
|
|
234
356
|
|
|
235
357
|
# Update state trajectory
|
|
236
|
-
|
|
237
|
-
self.x[key] = x_sim[:, n]
|
|
358
|
+
self.update_dict(x, name='x')
|
|
238
359
|
|
|
239
360
|
# Calculate measurements
|
|
240
361
|
x_list = list(self.x.values())
|
|
241
362
|
u_list = list(self.u.values())
|
|
242
|
-
y = self.h(x_list, u_list
|
|
363
|
+
y = self.h(x_list, u_list)
|
|
243
364
|
|
|
244
|
-
# Set
|
|
245
|
-
self.y =
|
|
246
|
-
for p, measurement_name in enumerate(self.measurement_names):
|
|
247
|
-
self.y[measurement_name] = y[p]
|
|
365
|
+
# Set measurements
|
|
366
|
+
self.update_dict(y, name='y')
|
|
248
367
|
|
|
249
|
-
# Return the
|
|
368
|
+
# Return the measurements in array format
|
|
250
369
|
y_array = np.vstack(list(self.y.values())).T
|
|
251
370
|
|
|
252
371
|
if return_full_output:
|
|
@@ -254,7 +373,50 @@ class Simulator(object):
|
|
|
254
373
|
else:
|
|
255
374
|
return y_array
|
|
256
375
|
|
|
257
|
-
def
|
|
376
|
+
def get_time_states_inputs_measurements(self):
|
|
258
377
|
return self.time.copy(), self.x.copy(), self.u.copy(), self.u.copy()
|
|
259
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
|
+
|
|
260
420
|
|
|
421
|
+
for a in ax.flat:
|
|
422
|
+
a.tick_params(axis='both', labelsize=6)
|
pybounds/util.py
CHANGED
|
@@ -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:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pybounds/__init__.py,sha256=so9LuRNw2V8MGsDs-RPstGGgJtBFp2YGolQbS0rVfhw,348
|
|
2
|
+
pybounds/observability.py,sha256=MUdwufFf200SYseP42JCP978O4F7GbeBVtm0yGjGzOE,27490
|
|
3
|
+
pybounds/simulator.py,sha256=-CrQVpNfiBDFECd6H7FU5has4sYGW1gyS2RhOgXUqZg,15858
|
|
4
|
+
pybounds/util.py,sha256=l6S9G88S-OZO9mi7F_58bVAPSr3PGQKcbHUghgni4JY,5956
|
|
5
|
+
pybounds-0.0.5.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
|
|
6
|
+
pybounds-0.0.5.dist-info/METADATA,sha256=sQnyiQHrQHFzXHh_mr4ymujDAh7MZved-QnSKC08JTM,2226
|
|
7
|
+
pybounds-0.0.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
8
|
+
pybounds-0.0.5.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
|
|
9
|
+
pybounds-0.0.5.dist-info/RECORD,,
|
pybounds-0.0.4.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
pybounds/__init__.py,sha256=so9LuRNw2V8MGsDs-RPstGGgJtBFp2YGolQbS0rVfhw,348
|
|
2
|
-
pybounds/observability.py,sha256=OqJ7fZmflvgZ9dQprIkZ3aahmvwLKVa-5RRHGpnv_gI,27058
|
|
3
|
-
pybounds/simulator.py,sha256=GiQHDGkRtiUsMiiNB8tLBXbG2d5UQJwa7WvXXalFtXg,9748
|
|
4
|
-
pybounds/util.py,sha256=xxmXmpLR3yK923X6wAPKp_5w814cO3m9OqF1XIt9S8c,5818
|
|
5
|
-
pybounds-0.0.4.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
|
|
6
|
-
pybounds-0.0.4.dist-info/METADATA,sha256=u32cRGxW05dWQFSAAKk_oQ-WYpXki6CBMSBvU-j3jT0,2376
|
|
7
|
-
pybounds-0.0.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
8
|
-
pybounds-0.0.4.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
|
|
9
|
-
pybounds-0.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|