pybounds 0.0.11__py3-none-any.whl → 0.0.12__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 CHANGED
@@ -11,7 +11,7 @@ from .jacobian import SymbolicJacobian
11
11
 
12
12
 
13
13
  class EmpiricalObservabilityMatrix:
14
- def __init__(self, simulator, x0, u, eps=1e-5, parallel=False,
14
+ def __init__(self, simulator, x0, u, aux=None, eps=1e-5, parallel=False,
15
15
  z_function=None, z_state_names=None):
16
16
  """ Construct an empirical observability matrix O.
17
17
 
@@ -19,18 +19,20 @@ class EmpiricalObservabilityMatrix:
19
19
  y is (w x p) array. w is the number of time-steps and p is the number of measurements
20
20
  :param dict/list/np.array x0: initial state for Simulator
21
21
  :param dict/np.array u: inputs array
22
+ :param aux: auxiliary input that can be passed to Simulator class
22
23
  :param float eps: epsilon value for perturbations to construct O, should be small number
23
24
  :param bool parallel: if True, run the perturbations in parallel
24
25
  :param callable z_function: function that transforms coordinates from original to new states
25
26
  must be of the form z = z_function(x), where x & z are the same size
26
27
  should use sympy functions wherever possible
27
28
  leave as None to maintain original coordinates
28
- :param list | tuple z_state_names: (optional) names of states in new coordinates.
29
+ :param list | tuple | None z_state_names: (optional) names of states in new coordinates.
29
30
  will only have an effect if O is a data-frame & z_function is not None
30
31
  """
31
32
 
32
33
  # Store inputs
33
34
  self.simulator = simulator
35
+ self.aux = aux
34
36
  self.eps = eps
35
37
  self.parallel = parallel
36
38
 
@@ -48,7 +50,7 @@ class EmpiricalObservabilityMatrix:
48
50
  self.n = self.x0.shape[0]
49
51
 
50
52
  # Simulate once for nominal trajectory
51
- self.y_nominal = self.simulator.simulate(self.x0, self.u)
53
+ self.y_nominal = self.simulator.simulate(x0=self.x0, u=self.u, aux=self.aux)
52
54
 
53
55
  # Number of outputs
54
56
  self.p = self.y_nominal.shape[1]
@@ -158,8 +160,8 @@ class EmpiricalObservabilityMatrix:
158
160
  x0_minus = self.x0 - self.delta_x[:, n]
159
161
 
160
162
  # Simulate measurements from perturbed initial conditions
161
- y_plus = self.simulator.simulate(x0=x0_plus, u=self.u)
162
- y_minus = self.simulator.simulate(x0=x0_minus, u=self.u)
163
+ y_plus = self.simulator.simulate(x0=x0_plus, u=self.u, aux=self.aux)
164
+ y_minus = self.simulator.simulate(x0=x0_minus, u=self.u, aux=self.aux)
163
165
 
164
166
  # Calculate the numerical Jacobian & normalize by 2x the perturbation amount
165
167
  delta_y = np.array(y_plus - y_minus).T / (2 * self.eps)
@@ -168,7 +170,7 @@ class EmpiricalObservabilityMatrix:
168
170
 
169
171
 
170
172
  class SlidingEmpiricalObservabilityMatrix:
171
- def __init__(self, simulator, t_sim, x_sim, u_sim, w=None, eps=1e-5,
173
+ def __init__(self, simulator, t_sim, x_sim, u_sim, aux_list=None, w=None, eps=1e-5,
172
174
  parallel_sliding=False, parallel_perturbation=False,
173
175
  z_function=None, z_state_names=None):
174
176
  """ Construct empirical observability matrix O in sliding windows along a trajectory.
@@ -178,6 +180,7 @@ class SlidingEmpiricalObservabilityMatrix:
178
180
  :param np.array t_sim: time values along state trajectory array (N, 1)
179
181
  :param np.array x_sim: state trajectory array (N, n), can also be dict
180
182
  :param np.array u_sim: input array (N, m), can also be dict
183
+ :param aux_list: auxiliary input that can be passed to Simulator class
181
184
  :param np.array w: window size for O calculations, will automatically set how many windows to compute
182
185
  :params float eps: tolerance for sliding windows
183
186
  :param float eps: epsilon value for perturbations to construct O's, should be small number
@@ -207,7 +210,7 @@ class SlidingEmpiricalObservabilityMatrix:
207
210
  if isinstance(u_sim, dict):
208
211
  self.u_sim = np.vstack(list(u_sim.values())).T
209
212
  else:
210
- self.u_sim = np.array(u_sim).squeeze()
213
+ self.u_sim = np.array(u_sim)
211
214
 
212
215
  # Check sizes
213
216
  if self.N != self.x_sim.shape[0]:
@@ -217,6 +220,15 @@ class SlidingEmpiricalObservabilityMatrix:
217
220
  elif self.x_sim.shape[0] != self.u_sim.shape[0]:
218
221
  raise ValueError('x_sim & u_sim must have same number of rows')
219
222
 
223
+ # Set aux inputs
224
+ if aux_list is None:
225
+ self.aux_list = [None for k in range(self.N)]
226
+ else:
227
+ self.aux_list = aux_list
228
+
229
+ if len(self.aux_list) != self.N:
230
+ raise ValueError('aux_list must have same number of elements as t_sim')
231
+
220
232
  # Set time-window to calculate O's
221
233
  if w is None: # set window size to full time-series size
222
234
  self.w = self.N
@@ -292,7 +304,9 @@ class SlidingEmpiricalObservabilityMatrix:
292
304
  u_win = self.u_sim[win, :] # inputs in window
293
305
 
294
306
  # Calculate O for window
295
- EOM = EmpiricalObservabilityMatrix(self.simulator, x0, u_win, eps=self.eps,
307
+ EOM = EmpiricalObservabilityMatrix(self.simulator, x0, u_win,
308
+ aux=self.aux_list[n],
309
+ eps=self.eps,
296
310
  parallel=self.parallel_perturbation,
297
311
  z_function=self.z_function,
298
312
  z_state_names=self.z_state_names)
@@ -315,7 +329,8 @@ class SlidingEmpiricalObservabilityMatrix:
315
329
 
316
330
 
317
331
  class FisherObservability:
318
- def __init__(self, O, R=None, lam=None):
332
+ def __init__(self, O, R=None, lam=None,
333
+ states=None, sensors=None, time_steps=None, w=None):
319
334
  """ Evaluate the observability of a state variable(s) using the Fisher Information Matrix.
320
335
 
321
336
  :param np.array O: observability matrix (w*p, n)
@@ -328,7 +343,11 @@ class FisherObservability:
328
343
  can also be dict where keys must correspond to the 'sensor' index in O data-frame
329
344
  if None, then R = I_(nxn)
330
345
  :param float lam: lamda parameter, if lam='limit' compute F^-1 symbolically, otherwise use Chernoff inverse
331
- """
346
+ :param None | tuple | list states: list of states to use from O's. ex: ['g', 'd']
347
+ :param None | tuple | list sensors: list of sensors to use from O's, ex: ['r']
348
+ :param None | tuple | list | np.array time_steps: array of time steps to use from O's, ex: np.array([0, 1, 2])
349
+ :param None | tuple | list | np.array w: window size to use from O's,
350
+ if None then just grab it from O as the maximum window size """
332
351
 
333
352
  # Make O a data-frame
334
353
  self.pw = O.shape[0] # number of sensors * time-steps
@@ -344,6 +363,37 @@ class FisherObservability:
344
363
  else:
345
364
  raise TypeError('O is not a pandas data-frame or numpy array')
346
365
 
366
+ # Set window size
367
+ if w is None: # set automatically
368
+ self.w = np.max(np.array(self.O.index.get_level_values('time_step'))) + 1
369
+ else:
370
+ self.w = w
371
+
372
+ # Set the states to use
373
+ if states is None:
374
+ self.states = self.O.columns
375
+ else:
376
+ self.states = states
377
+
378
+ # Set the sensors to use
379
+ if sensors is None:
380
+ self.sensors = self.O.index.get_level_values('sensor')
381
+ else:
382
+ self.sensors = sensors
383
+
384
+ # Set the time-steps to use
385
+ if time_steps is None:
386
+ self.time_steps = self.O.index.get_level_values('time_step')
387
+ else:
388
+ self.time_steps = np.array(time_steps)
389
+
390
+ # Get subset of O
391
+ self.O = O.loc[(self.sensors, self.time_steps), self.states].sort_values(['time_step', 'sensor'])
392
+
393
+ # Reset the size of O
394
+ self.pw = self.O.shape[0] # number of sensors * time-steps
395
+ self.n = self.O.shape[1] # number of states
396
+
347
397
  # Set measurement noise covariance matrix
348
398
  self.R = pd.DataFrame(np.eye(self.pw), index=self.O.index, columns=self.O.index)
349
399
  self.R_inv = pd.DataFrame(np.eye(self.pw), index=self.O.index, columns=self.O.index)
@@ -351,7 +401,7 @@ class FisherObservability:
351
401
 
352
402
  # Calculate Fisher Information Matrix
353
403
  self.F = self.O.values.T @ self.R_inv.values @ self.O.values
354
- self.F = pd.DataFrame(self.F, index=O.columns, columns=O.columns)
404
+ self.F = pd.DataFrame(self.F, index=self.O.columns, columns=self.O.columns)
355
405
 
356
406
  # Set sigma
357
407
  if lam is None:
@@ -371,7 +421,7 @@ class FisherObservability:
371
421
  F_epsilon = self.F.values + (self.lam * np.eye(self.n))
372
422
  self.F_inv = np.linalg.inv(F_epsilon)
373
423
 
374
- self.F_inv = pd.DataFrame(self.F_inv, index=O.columns, columns=O.columns)
424
+ self.F_inv = pd.DataFrame(self.F_inv, index=self.O.columns, columns=self.O.columns)
375
425
 
376
426
  # Pull out diagonal elements
377
427
  self.error_variance = pd.DataFrame(np.diag(self.F_inv), index=self.O.columns).T
@@ -421,18 +471,19 @@ class FisherObservability:
421
471
 
422
472
 
423
473
  class SlidingFisherObservability:
424
- def __init__(self, O_list, time=None, lam=1e6, R=None,
474
+ def __init__(self, O_list, R=None, lam=1e6, time=None,
425
475
  states=None, sensors=None, time_steps=None, w=None):
426
476
 
427
477
  """ Compute the Fisher information matrix & inverse in sliding windows and pull put the minimum error variance.
478
+
428
479
  :param list O_list: list of observability matrices O (stored as pd.DataFrame)
429
- :param None | np.array time: time vector the same size as O_list
430
- :param float | np.array lam: lamda parameter, if lam='limit' compute F^-1 symbolically, otherwise use Chernoff inverse
431
480
  :param None | np.array | float| dict R: measurement noise covariance matrix (w*p x w*p)
432
481
  can also be set as pd.DataFrame where R.index = R.columns = O.index
433
482
  can also be a scaler where R = R * I_(nxn)
434
483
  can also be dict where keys must correspond to the 'sensor' index in O data-frame
435
484
  if None, then R = I_(nxn)
485
+ :param float | np.array lam: lamda parameter, if lam='limit' compute F^-1 symbolically, otherwise use Chernoff inverse
486
+ :param None | np.array time: time vector the same size as O_list
436
487
  :param None | tuple | list states: list of states to use from O's. ex: ['g', 'd']
437
488
  :param None | tuple | list sensors: list of sensors to use from O's, ex: ['r']
438
489
  :param None | tuple | list | np.array time_steps: array of time steps to use from O's, ex: np.array([0, 1, 2])
@@ -458,33 +509,6 @@ class SlidingFisherObservability:
458
509
  else: # default is time-step of 1
459
510
  self.dt = 1
460
511
 
461
- # Get single O
462
- O = O_list[0]
463
-
464
- # Set window size
465
- if w is None: # set automatically
466
- self.w = np.max(np.array(O.index.get_level_values('time_step'))) + 1
467
- else:
468
- self.w = w
469
-
470
- # Set the states to use
471
- if states is None:
472
- self.states = O.columns
473
- else:
474
- self.states = states
475
-
476
- # Set the sensors to use
477
- if sensors is None:
478
- self.sensors = O.index.get_level_values('sensor')
479
- else:
480
- self.sensors = sensors
481
-
482
- # Set the time-steps to use
483
- if time_steps is None:
484
- self.time_steps = O.index.get_level_values('time_step')
485
- else:
486
- self.time_steps = np.array(time_steps)
487
-
488
512
  # Compute Fisher information matrix & inverse for each sliding window
489
513
  self.EV = [] # collect error variance data for each state over windows
490
514
  self.FO = [] # collect FisherObservability objects over windows
@@ -492,11 +516,8 @@ class SlidingFisherObservability:
492
516
  # Get full O
493
517
  O = self.O_list[k]
494
518
 
495
- # Get subset of O
496
- O_subset = O.loc[(self.sensors, self.time_steps), self.states].sort_values(['time_step', 'sensor'])
497
-
498
519
  # Compute Fisher information & inverse
499
- FO = FisherObservability(O_subset, R=R, lam=lam)
520
+ FO = FisherObservability(O, R=R, lam=lam, states=states, sensors=sensors, time_steps=time_steps, w=w)
500
521
  self.FO.append(FO)
501
522
 
502
523
  # Collect error variance data
@@ -505,7 +526,7 @@ class SlidingFisherObservability:
505
526
  self.EV.append(ev)
506
527
 
507
528
  # Concatenate error variance & make same size as simulation data
508
- self.shift_index = int(np.round((1 / 2) * self.w))
529
+ self.shift_index = int(np.round((1 / 2) * float(FO.w)))
509
530
  self.shift_time = self.shift_index * self.dt # shift the time forward by half the window size
510
531
  self.EV = pd.concat(self.EV, axis=0, ignore_index=True)
511
532
  if self.n_window > 1: # more than 1 window
pybounds/simulator.py CHANGED
@@ -2,6 +2,7 @@
2
2
  import warnings
3
3
  import numpy as np
4
4
  import matplotlib.pyplot as plt
5
+ import casadi
5
6
  import do_mpc
6
7
  from .util import FixedKeysDict, SetDict
7
8
 
@@ -129,7 +130,7 @@ class Simulator(object):
129
130
  # Define dynamics
130
131
  Xdot = self.f(X, U)
131
132
  for n, state_name in enumerate(self.state_names):
132
- self.model.set_rhs(state_name, Xdot[n])
133
+ self.model.set_rhs(state_name, casadi.SX(Xdot[n]))
133
134
 
134
135
  # Add time-varying set-point variables for later use with MPC
135
136
  for n, state_name in enumerate(self.state_names):
@@ -271,12 +272,14 @@ class Simulator(object):
271
272
  if not np.all(points_check):
272
273
  raise Exception(name + ' not the same size')
273
274
 
274
- def simulate(self, x0=None, u=None, mpc=False, return_full_output=False):
275
+ def simulate(self, x0=None, u=None, aux=None, mpc=False, return_full_output=False):
275
276
  """
276
277
  Simulate the system.
277
278
 
278
279
  :params x0: initial state dict or array
279
- :params u: input dict or array
280
+ :params u: input dict or array, if True then mpc must be None
281
+ :params aux: auxiliary input
282
+ :params mpc: boolean to run MPC, if True then u must be None
280
283
  :params return_full_output: boolean to run (time, x, u, y) instead of y
281
284
  """
282
285
 
@@ -420,14 +423,16 @@ class Simulator(object):
420
423
  ax[n].legend(fontsize=6)
421
424
 
422
425
  y = self.x[key]
423
- y_min = np.min(y)
424
- y_max = np.max(y)
425
- delta = y_max - y_min
426
- if np.abs(delta) < 0.01:
427
- margin = 0.1
428
- ax[n].set_ylim(y_min - margin, y_max + margin)
429
- else:
430
- margin = 0.0
426
+ else:
427
+ y = plot_dict[key]
428
+
429
+ # Set y-axis limits
430
+ y_min = np.min(y)
431
+ y_max = np.max(y)
432
+ delta = y_max - y_min
433
+ if np.abs(delta) < 0.01:
434
+ margin = 0.1
435
+ ax[n].set_ylim(y_min - margin, y_max + margin)
431
436
 
432
437
  ax[-1].set_xlabel('time', fontsize=7)
433
438
  ax[0].set_title(name, fontsize=8, fontweight='bold')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pybounds
3
- Version: 0.0.11
3
+ Version: 0.0.12
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
@@ -41,6 +41,9 @@ For a simple system
41
41
  For a more complex system
42
42
  * Fly-wind: [fly_wind_example.ipynb](examples%2Ffly_wind_example.ipynb)
43
43
 
44
+ ## Guides
45
+ * Creating a custom simulator:
46
+
44
47
  ## Citation
45
48
 
46
49
  If you use the code or methods from this package, please cite the following paper:
@@ -0,0 +1,10 @@
1
+ pybounds/__init__.py,sha256=e4SQdDBQzqr7_x5lcQsuSoWqfBg258i7CDrhhvnmCe4,433
2
+ pybounds/jacobian.py,sha256=hqDOwwqZMdnlTECz0Rx6txCd4VuZ4iZHPaj62PTkKvA,2057
3
+ pybounds/observability.py,sha256=AZgUHk781C0C81aibNAaQSanbhY_0WMbmuCiHT82Onk,32926
4
+ pybounds/simulator.py,sha256=Qx9QGFYuuFnyuq1z5dcub5T_Lz8ISWcKLLrHup9UY0A,16702
5
+ pybounds/util.py,sha256=Gs0UgqgLXTJI9FZww90iJhqU02iJ31bXBURjGiq3YzM,7401
6
+ pybounds-0.0.12.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
7
+ pybounds-0.0.12.dist-info/METADATA,sha256=9u0pmrau2uJorcTcjznL56LxkOTshpA-uQ7GNTwUqJU,2202
8
+ pybounds-0.0.12.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
9
+ pybounds-0.0.12.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
10
+ pybounds-0.0.12.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
-
2
- # from .observability import EmpiricalObservabilityMatrix
3
- #
4
- #
5
- # class DroneSimulator(EmpiricalObservabilityMatrix):
6
- # def __init__(self, dt=0.1, mpc_horizon=10, r_u=1e-2, input_mode='direct', control_mode='velocity_body_level'):
7
- # self.dynamics = DroneModel()
8
- # super().__init__(self.dynamics.f, self.dynamics.h, dt=dt, mpc_horizon=mpc_horizon,
9
- # state_names=self.dynamics.state_names,
10
- # input_names=self.dynamics.input_names,
11
- # measurement_names=self.dynamics.measurement_names)
@@ -1,11 +0,0 @@
1
- pybounds/__init__.py,sha256=e4SQdDBQzqr7_x5lcQsuSoWqfBg258i7CDrhhvnmCe4,433
2
- pybounds/jacobian.py,sha256=hqDOwwqZMdnlTECz0Rx6txCd4VuZ4iZHPaj62PTkKvA,2057
3
- pybounds/observability.py,sha256=GmWrPADr-vCbBJIv0Cjk3Gj9JJoCuOizKrF8lzX3ncs,31553
4
- pybounds/observability_transform.py,sha256=YibApe7OzwrZT44BmlZwetJ1JLHOIgMkpVYggWz5Myo,585
5
- pybounds/simulator.py,sha256=nKLtMQs_HgNYKIJ1YIg63lW2v-r7sT7WxLyouOV07wA,16518
6
- pybounds/util.py,sha256=Gs0UgqgLXTJI9FZww90iJhqU02iJ31bXBURjGiq3YzM,7401
7
- pybounds-0.0.11.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
8
- pybounds-0.0.11.dist-info/METADATA,sha256=Evpk9-4sk6w_czjxjX-CYC1Y9eFuRGuYRcYrNlMQqjg,2156
9
- pybounds-0.0.11.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10
- pybounds-0.0.11.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
11
- pybounds-0.0.11.dist-info/RECORD,,