pybounds 0.0.12__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
@@ -329,7 +329,7 @@ class SlidingEmpiricalObservabilityMatrix:
329
329
 
330
330
 
331
331
  class FisherObservability:
332
- def __init__(self, O, R=None, lam=None,
332
+ def __init__(self, O, R=None, lam=None, force_R_scalar=False,
333
333
  states=None, sensors=None, time_steps=None, w=None):
334
334
  """ Evaluate the observability of a state variable(s) using the Fisher Information Matrix.
335
335
 
@@ -337,12 +337,13 @@ class FisherObservability:
337
337
  w is the number of time-steps, p is the number of measurements, and n in the number of states
338
338
  can also be set as pd.DataFrame where columns set the state names & a multilevel index sets the
339
339
  measurement names: O.index names must be ('sensor', 'time_step')
340
- :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)
341
341
  can also be set as pd.DataFrame where R.index = R.columns = O.index
342
342
  can also be a scaler where R = R * I_(nxn)
343
343
  can also be dict where keys must correspond to the 'sensor' index in O data-frame
344
344
  if None, then R = I_(nxn)
345
345
  :param float lam: lamda parameter, if lam='limit' compute F^-1 symbolically, otherwise use Chernoff inverse
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
346
347
  :param None | tuple | list states: list of states to use from O's. ex: ['g', 'd']
347
348
  :param None | tuple | list sensors: list of sensors to use from O's, ex: ['r']
348
349
  :param None | tuple | list | np.array time_steps: array of time steps to use from O's, ex: np.array([0, 1, 2])
@@ -394,13 +395,25 @@ class FisherObservability:
394
395
  self.pw = self.O.shape[0] # number of sensors * time-steps
395
396
  self.n = self.O.shape[1] # number of states
396
397
 
397
- # Set measurement noise covariance matrix
398
- self.R = pd.DataFrame(np.eye(self.pw), index=self.O.index, columns=self.O.index)
399
- self.R_inv = pd.DataFrame(np.eye(self.pw), index=self.O.index, columns=self.O.index)
400
- self.set_noise_covariance(R=R)
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
401
416
 
402
- # Calculate Fisher Information Matrix
403
- self.F = self.O.values.T @ self.R_inv.values @ self.O.values
404
417
  self.F = pd.DataFrame(self.F, index=self.O.columns, columns=self.O.columns)
405
418
 
406
419
  # Set sigma
@@ -619,8 +632,10 @@ class ObservabilityMatrixImage:
619
632
 
620
633
  # Default sensor names based on data-frame 'sensor' index
621
634
  sensor_names_all = list(np.unique(O.index.get_level_values('sensor')))
622
- self.sensor_names_default = list(O.index.get_level_values('sensor')[0:len(sensor_names_all)])
623
-
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)
624
639
  else: # numpy matrix
625
640
  raise TypeError('n-sensor must be an integer value when O is given as a numpy matrix')
626
641
 
@@ -650,7 +665,7 @@ class ObservabilityMatrixImage:
650
665
  self.measurement_names = []
651
666
  for w in range(self.n_time_step):
652
667
  for p in range(self.n_sensor):
653
- m = '$' + self.sensor_names[p] + ',_{' + 'k=' + str(w) + '}$'
668
+ m = '$' + self.sensor_names[p] + ',_{' + 'k=' + str(self.time_steps_default[w]) + '}$'
654
669
  self.measurement_names.append(m)
655
670
 
656
671
  elif len(sensor_names) == 1:
@@ -659,7 +674,7 @@ class ObservabilityMatrixImage:
659
674
  self.measurement_names = []
660
675
  for w in range(self.n_time_step):
661
676
  for p in range(self.n_sensor):
662
- m = '$' + sensor_names[0] + '_{' + str(p) + ',k=' + str(w) + '}$'
677
+ m = '$' + sensor_names[0] + '_{' + str(p) + ',k=' + str(self.time_steps_default[w]) + '}$'
663
678
  self.measurement_names.append(m)
664
679
  else:
665
680
  raise TypeError('sensor_names must be of length p or length 1')
@@ -670,7 +685,7 @@ class ObservabilityMatrixImage:
670
685
  self.measurement_names = []
671
686
  for w in range(self.n_time_step):
672
687
  for p in range(self.n_sensor):
673
- m = '$' + self.sensor_names[p] + '_{' + ',k=' + str(w) + '}$'
688
+ m = '$' + self.sensor_names[p] + '_{' + ',k=' + str(self.time_steps_default[w]) + '}$'
674
689
  self.measurement_names.append(m)
675
690
 
676
691
  def plot(self, vmax_percentile=100, vmin_ratio=0.0, vmax_override=None, cmap='bwr', grid=True, scale=1.0, dpi=150,
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.12
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
@@ -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,10 +0,0 @@
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,,