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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pybounds
3
- Version: 0.0.4
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/pynumdiff.svg" alt="PyPI version" height="18"></a>
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
- There is currently one simple example notebook. More to come.
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/pynumdiff.svg" alt="PyPI version" height="18"></a>
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
- There is currently one simple example notebook. More to come.
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
- results = pool.map(self.simulate, state_index)
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, step=1) # indices to compute O
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
- results = pool.map(self.construct, n_point_range)
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, parallel=self.parallel_perturbation)
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.4
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/pynumdiff.svg" alt="PyPI version" height="18"></a>
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
- There is currently one simple example notebook. More to come.
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.4",
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