pybounds 0.0.11__py3-none-any.whl → 0.0.13__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/__init__.py CHANGED
@@ -10,4 +10,4 @@ from .observability import transform_states
10
10
 
11
11
  from .jacobian import SymbolicJacobian
12
12
 
13
- from .util import colorline
13
+ from .util import colorline, plot_heatmap_log_timeseries
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,20 +329,26 @@ 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, force_R_scalar=False,
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)
322
337
  w is the number of time-steps, p is the number of measurements, and n in the number of states
323
338
  can also be set as pd.DataFrame where columns set the state names & a multilevel index sets the
324
339
  measurement names: O.index names must be ('sensor', 'time_step')
325
- :param None | np.array | float| dict R: measurement noise covariance matrix (w*p x w*p)
340
+ :param None | np.array | float | dict R: measurement noise covariance matrix (w*p x w*p)
326
341
  can also be set as pd.DataFrame where R.index = R.columns = O.index
327
342
  can also be a scaler where R = R * I_(nxn)
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 bool force_R_scalar: force R to be a scalar, useful when the resulting R matrix is too big to fit in memory
347
+ :param None | tuple | list states: list of states to use from O's. ex: ['g', 'd']
348
+ :param None | tuple | list sensors: list of sensors to use from O's, ex: ['r']
349
+ :param None | tuple | list | np.array time_steps: array of time steps to use from O's, ex: np.array([0, 1, 2])
350
+ :param None | tuple | list | np.array w: window size to use from O's,
351
+ if None then just grab it from O as the maximum window size """
332
352
 
333
353
  # Make O a data-frame
334
354
  self.pw = O.shape[0] # number of sensors * time-steps
@@ -344,14 +364,57 @@ class FisherObservability:
344
364
  else:
345
365
  raise TypeError('O is not a pandas data-frame or numpy array')
346
366
 
347
- # Set measurement noise covariance matrix
348
- self.R = pd.DataFrame(np.eye(self.pw), index=self.O.index, columns=self.O.index)
349
- self.R_inv = pd.DataFrame(np.eye(self.pw), index=self.O.index, columns=self.O.index)
350
- self.set_noise_covariance(R=R)
367
+ # Set window size
368
+ if w is None: # set automatically
369
+ self.w = np.max(np.array(self.O.index.get_level_values('time_step'))) + 1
370
+ else:
371
+ self.w = w
372
+
373
+ # Set the states to use
374
+ if states is None:
375
+ self.states = self.O.columns
376
+ else:
377
+ self.states = states
378
+
379
+ # Set the sensors to use
380
+ if sensors is None:
381
+ self.sensors = self.O.index.get_level_values('sensor')
382
+ else:
383
+ self.sensors = sensors
384
+
385
+ # Set the time-steps to use
386
+ if time_steps is None:
387
+ self.time_steps = self.O.index.get_level_values('time_step')
388
+ else:
389
+ self.time_steps = np.array(time_steps)
390
+
391
+ # Get subset of O
392
+ self.O = O.loc[(self.sensors, self.time_steps), self.states].sort_values(['time_step', 'sensor'])
393
+
394
+ # Reset the size of O
395
+ self.pw = self.O.shape[0] # number of sensors * time-steps
396
+ self.n = self.O.shape[1] # number of states
351
397
 
352
- # Calculate Fisher Information Matrix
353
- 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)
398
+ # Set measurement noise covariance matrix & calculate Fisher Information Matrix
399
+ if force_R_scalar and np.isscalar(R): # scalar R
400
+ self.R = pd.DataFrame({'R': {'index': float(R)}})
401
+ self.R_inv = pd.DataFrame({'R_inv': {'index': 1 / self.R.values.squeeze()}})
402
+
403
+ # Calculate Fisher Information Matrix for scalar R
404
+ self.F = self.R_inv.values.squeeze() * (self.O.values.T @ self.O.values)
405
+
406
+ elif force_R_scalar and not np.isscalar(R):
407
+ raise Exception('R must be a scalar')
408
+
409
+ else: # non-scalar R
410
+ self.R = pd.DataFrame(np.eye(self.pw), index=self.O.index, columns=self.O.index)
411
+ self.R_inv = pd.DataFrame(np.eye(self.pw), index=self.O.index, columns=self.O.index)
412
+ self.set_noise_covariance(R=R)
413
+
414
+ # Calculate Fisher Information Matrix for non-scalar R
415
+ self.F = self.O.values.T @ self.R_inv.values.squeeze() @ self.O.values
416
+
417
+ self.F = pd.DataFrame(self.F, index=self.O.columns, columns=self.O.columns)
355
418
 
356
419
  # Set sigma
357
420
  if lam is None:
@@ -371,7 +434,7 @@ class FisherObservability:
371
434
  F_epsilon = self.F.values + (self.lam * np.eye(self.n))
372
435
  self.F_inv = np.linalg.inv(F_epsilon)
373
436
 
374
- self.F_inv = pd.DataFrame(self.F_inv, index=O.columns, columns=O.columns)
437
+ self.F_inv = pd.DataFrame(self.F_inv, index=self.O.columns, columns=self.O.columns)
375
438
 
376
439
  # Pull out diagonal elements
377
440
  self.error_variance = pd.DataFrame(np.diag(self.F_inv), index=self.O.columns).T
@@ -421,18 +484,19 @@ class FisherObservability:
421
484
 
422
485
 
423
486
  class SlidingFisherObservability:
424
- def __init__(self, O_list, time=None, lam=1e6, R=None,
487
+ def __init__(self, O_list, R=None, lam=1e6, time=None,
425
488
  states=None, sensors=None, time_steps=None, w=None):
426
489
 
427
490
  """ Compute the Fisher information matrix & inverse in sliding windows and pull put the minimum error variance.
491
+
428
492
  :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
493
  :param None | np.array | float| dict R: measurement noise covariance matrix (w*p x w*p)
432
494
  can also be set as pd.DataFrame where R.index = R.columns = O.index
433
495
  can also be a scaler where R = R * I_(nxn)
434
496
  can also be dict where keys must correspond to the 'sensor' index in O data-frame
435
497
  if None, then R = I_(nxn)
498
+ :param float | np.array lam: lamda parameter, if lam='limit' compute F^-1 symbolically, otherwise use Chernoff inverse
499
+ :param None | np.array time: time vector the same size as O_list
436
500
  :param None | tuple | list states: list of states to use from O's. ex: ['g', 'd']
437
501
  :param None | tuple | list sensors: list of sensors to use from O's, ex: ['r']
438
502
  :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 +522,6 @@ class SlidingFisherObservability:
458
522
  else: # default is time-step of 1
459
523
  self.dt = 1
460
524
 
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
525
  # Compute Fisher information matrix & inverse for each sliding window
489
526
  self.EV = [] # collect error variance data for each state over windows
490
527
  self.FO = [] # collect FisherObservability objects over windows
@@ -492,11 +529,8 @@ class SlidingFisherObservability:
492
529
  # Get full O
493
530
  O = self.O_list[k]
494
531
 
495
- # Get subset of O
496
- O_subset = O.loc[(self.sensors, self.time_steps), self.states].sort_values(['time_step', 'sensor'])
497
-
498
532
  # Compute Fisher information & inverse
499
- FO = FisherObservability(O_subset, R=R, lam=lam)
533
+ FO = FisherObservability(O, R=R, lam=lam, states=states, sensors=sensors, time_steps=time_steps, w=w)
500
534
  self.FO.append(FO)
501
535
 
502
536
  # Collect error variance data
@@ -505,7 +539,7 @@ class SlidingFisherObservability:
505
539
  self.EV.append(ev)
506
540
 
507
541
  # Concatenate error variance & make same size as simulation data
508
- self.shift_index = int(np.round((1 / 2) * self.w))
542
+ self.shift_index = int(np.round((1 / 2) * float(FO.w)))
509
543
  self.shift_time = self.shift_index * self.dt # shift the time forward by half the window size
510
544
  self.EV = pd.concat(self.EV, axis=0, ignore_index=True)
511
545
  if self.n_window > 1: # more than 1 window
@@ -598,8 +632,10 @@ class ObservabilityMatrixImage:
598
632
 
599
633
  # Default sensor names based on data-frame 'sensor' index
600
634
  sensor_names_all = list(np.unique(O.index.get_level_values('sensor')))
601
- self.sensor_names_default = list(O.index.get_level_values('sensor')[0:len(sensor_names_all)])
602
-
635
+ self.sensors = list(O.index.get_level_values('sensor'))
636
+ self.time_steps = np.array(O.index.get_level_values('time_step'))
637
+ self.sensor_names_default = self.sensors[0:len(sensor_names_all)]
638
+ self.time_steps_default = np.unique(self.time_steps)
603
639
  else: # numpy matrix
604
640
  raise TypeError('n-sensor must be an integer value when O is given as a numpy matrix')
605
641
 
@@ -629,7 +665,7 @@ class ObservabilityMatrixImage:
629
665
  self.measurement_names = []
630
666
  for w in range(self.n_time_step):
631
667
  for p in range(self.n_sensor):
632
- m = '$' + self.sensor_names[p] + ',_{' + 'k=' + str(w) + '}$'
668
+ m = '$' + self.sensor_names[p] + ',_{' + 'k=' + str(self.time_steps_default[w]) + '}$'
633
669
  self.measurement_names.append(m)
634
670
 
635
671
  elif len(sensor_names) == 1:
@@ -638,7 +674,7 @@ class ObservabilityMatrixImage:
638
674
  self.measurement_names = []
639
675
  for w in range(self.n_time_step):
640
676
  for p in range(self.n_sensor):
641
- m = '$' + sensor_names[0] + '_{' + str(p) + ',k=' + str(w) + '}$'
677
+ m = '$' + sensor_names[0] + '_{' + str(p) + ',k=' + str(self.time_steps_default[w]) + '}$'
642
678
  self.measurement_names.append(m)
643
679
  else:
644
680
  raise TypeError('sensor_names must be of length p or length 1')
@@ -649,7 +685,7 @@ class ObservabilityMatrixImage:
649
685
  self.measurement_names = []
650
686
  for w in range(self.n_time_step):
651
687
  for p in range(self.n_sensor):
652
- m = '$' + self.sensor_names[p] + '_{' + ',k=' + str(w) + '}$'
688
+ m = '$' + self.sensor_names[p] + '_{' + ',k=' + str(self.time_steps_default[w]) + '}$'
653
689
  self.measurement_names.append(m)
654
690
 
655
691
  def plot(self, vmax_percentile=100, vmin_ratio=0.0, vmax_override=None, cmap='bwr', grid=True, scale=1.0, dpi=150,
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')
pybounds/util.py CHANGED
@@ -1,4 +1,6 @@
1
1
  import numpy as np
2
+ import scipy
3
+ import matplotlib as mpl
2
4
  import matplotlib.pyplot as plt
3
5
  import matplotlib.collections as mcoll
4
6
  import matplotlib.patheffects as path_effects
@@ -198,3 +200,80 @@ def colorline(x, y, z, ax=None, cmap=plt.get_cmap('copper'), norm=None, linewidt
198
200
  ax.add_collection(lc)
199
201
 
200
202
  return lc
203
+
204
+ def plot_heatmap_log_timeseries(data, ax=None, log_ticks=None, data_labels=None,
205
+ cmap='inferno_r', y_label=None,
206
+ aspect=0.25, interpolation=False):
207
+ """ Plot log-scale time-series as heatmap.
208
+ """
209
+
210
+ n_label = data.shape[1]
211
+
212
+ # Set ticks
213
+ if log_ticks is None:
214
+ log_tick_low = int(np.floor(np.log10(np.min(data))))
215
+ log_tick_high = int(np.ceil(np.log10(np.max(data))))
216
+ else:
217
+ log_tick_low = log_ticks[0]
218
+ log_tick_high = log_ticks[1]
219
+
220
+ log_ticks = np.logspace(log_tick_low, log_tick_high, log_tick_high - log_tick_low + 1)
221
+
222
+ # Set color normalization
223
+ cnorm = mpl.colors.LogNorm(10 ** log_tick_low, 10 ** log_tick_high)
224
+
225
+ # Set labels
226
+ if data_labels is None:
227
+ data_labels = np.arange(0, n_label).tolist()
228
+ data_labels = [str(x) for x in data_labels]
229
+
230
+ # Make figure/axis
231
+ if ax is None:
232
+ fig, ax = plt.subplots(1, 1, figsize=(5 * 1, 4 * 1), dpi=150)
233
+ else:
234
+ # ax = plt.gca()
235
+ fig = plt.gcf()
236
+
237
+ # Plot heatmap
238
+ if interpolation:
239
+ data = 10**scipy.ndimage.zoom(np.log10(data), (interpolation, 1), order=1)
240
+ aspect = aspect / interpolation
241
+
242
+ ax.imshow(data, norm=cnorm, aspect=aspect, cmap=cmap, interpolation='none')
243
+
244
+ # Set axis properties
245
+ ax.grid(True, axis='x')
246
+ ax.tick_params(axis='both', which='both', labelsize=6, top=False, labeltop=True, bottom=False, labelbottom=False,
247
+ color='gray')
248
+
249
+ # Set x-ticks
250
+ LatexConverter = LatexStates()
251
+ data_labels_latex = LatexConverter.convert_to_latex(data_labels)
252
+ ax.set_xticks(np.arange(0, len(data_labels)) - 0.5)
253
+ ax.set_xticklabels(data_labels_latex)
254
+
255
+ # Set labels
256
+ ax.set_ylabel('time steps', fontsize=7, fontweight='bold')
257
+ ax.set_xlabel('states', fontsize=7, fontweight='bold')
258
+ ax.xaxis.set_label_position('top')
259
+
260
+ # Set x-ticks
261
+ xticks = ax.get_xticklabels()
262
+ for tick in xticks:
263
+ tick.set_ha('left')
264
+ tick.set_va('center')
265
+ # tick.set_rotation(0)
266
+ # tick.set_transform(tick.get_transform() + transforms.ScaledTranslation(6 / 72, 0, ax.figure.dpi_scale_trans))
267
+
268
+ # Colorbar
269
+ if y_label is None:
270
+ y_label = 'values'
271
+
272
+ cax = ax.inset_axes((1.03, 0.0, 0.04, 1.0))
273
+ cbar = fig.colorbar(mpl.cm.ScalarMappable(norm=cnorm, cmap=cmap), cax=cax, ticks=log_ticks)
274
+ cbar.set_label(y_label, rotation=270, fontsize=7, labelpad=8)
275
+ cbar.ax.tick_params(labelsize=6)
276
+
277
+ ax.spines[['bottom', 'top', 'left', 'right']].set_color('gray')
278
+
279
+ return cnorm, cmap, log_ticks
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pybounds
3
- Version: 0.0.11
3
+ Version: 0.0.13
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=mGTxRNwJmvRhRG5M8pVX2QmPV9a6L-bP0DAAM6pAX-A,462
2
+ pybounds/jacobian.py,sha256=hqDOwwqZMdnlTECz0Rx6txCd4VuZ4iZHPaj62PTkKvA,2057
3
+ pybounds/observability.py,sha256=IIC-iaHIZSmKBnxcYKhn5Q18bBX9rwebz0F1twQHE0Y,33922
4
+ pybounds/simulator.py,sha256=Qx9QGFYuuFnyuq1z5dcub5T_Lz8ISWcKLLrHup9UY0A,16702
5
+ pybounds/util.py,sha256=_nWwDLY07Aj8UlzK-xjy-hO_8btUwqDjsiwQmNTkRug,10136
6
+ pybounds-0.0.13.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
7
+ pybounds-0.0.13.dist-info/METADATA,sha256=fEdYfbP1PU3V2aKA-ce4nt-D5uWWGr3StZR0osVBgz4,2202
8
+ pybounds-0.0.13.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
9
+ pybounds-0.0.13.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
10
+ pybounds-0.0.13.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,,