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 +1 -1
- pybounds/observability.py +28 -13
- pybounds/util.py +79 -0
- {pybounds-0.0.12.dist-info → pybounds-0.0.13.dist-info}/METADATA +1 -1
- pybounds-0.0.13.dist-info/RECORD +10 -0
- pybounds-0.0.12.dist-info/RECORD +0 -10
- {pybounds-0.0.12.dist-info → pybounds-0.0.13.dist-info}/LICENSE +0 -0
- {pybounds-0.0.12.dist-info → pybounds-0.0.13.dist-info}/WHEEL +0 -0
- {pybounds-0.0.12.dist-info → pybounds-0.0.13.dist-info}/top_level.txt +0 -0
pybounds/__init__.py
CHANGED
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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.
|
|
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
|
|
@@ -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,,
|
pybounds-0.0.12.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|