pybounds 0.0.1__py3-none-any.whl → 0.0.2__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,20 +11,20 @@ from util import LatexStates
11
11
 
12
12
 
13
13
  class EmpiricalObservabilityMatrix:
14
- def __init__(self, simulator, x0, t_sim, u_sim, eps=1e-5, parallel=False):
14
+ def __init__(self, simulator, x0, time, u, eps=1e-5, parallel=False):
15
15
  """ Construct an empirical observability matrix O.
16
16
 
17
- Inputs
18
- simulator: simulator object: y = simulator(x0, t_sim, u_sim)
19
- x0: initial state
20
- t_sim: simulation time
21
- u_sim: simulation inputs
22
- eps: amount to perturb initial states
17
+ :param callable Simulator: Simulator object : y = simulator(x0, u, **kwargs)
18
+ y is (w x p) array. w is the number of time-steps and p is the number of measurements
19
+ :param dict/list/np.array x0: initial state for Simulator
20
+ :param dict/np.array u: inputs array
21
+ :param float eps: epsilon value for perturbations to construct O, should be small number
22
+ :param bool parallel: if True, run the perturbations in parallel
23
23
  """
24
24
 
25
25
  # Store inputs
26
26
  self.simulator = simulator
27
- self.t_sim = t_sim.copy()
27
+ self.time = time.copy()
28
28
  self.eps = eps
29
29
  self.parallel = parallel
30
30
 
@@ -33,35 +33,49 @@ class EmpiricalObservabilityMatrix:
33
33
  else:
34
34
  self.x0 = np.array(x0).squeeze()
35
35
 
36
- if isinstance(u_sim, dict):
37
- self.u_sim = np.vstack(list(u_sim.values())).T
36
+ if isinstance(u, dict):
37
+ self.u = np.vstack(list(u.values())).T
38
38
  else:
39
- self.u_sim = np.array(u_sim)
39
+ self.u = np.array(u)
40
+
41
+ # Number of states
42
+ self.n = self.x0.shape[0]
40
43
 
41
44
  # Simulate once for nominal trajectory
42
- self.y_nominal = self.simulator.simulate(self.x0, self.u_sim)
43
- self.x_nominal = self.simulator.x.copy()
44
- self.u_nominal = self.simulator.u.copy()
45
- # self.sim_data_nominal = self.simulator.sim_data.copy()
45
+ self.y_nominal = self.simulator.simulate(self.x0, self.u)
46
+
47
+ # Number of outputs
48
+ self.p = self.y_nominal.shape[1]
49
+
50
+ # Check for state/measurement names
51
+ if hasattr(self.simulator, 'state_names'):
52
+ self.state_names = self.simulator.state_names
53
+ else:
54
+ self.state_names = ['x_' + str(n) for n in range(self.n)]
55
+
56
+ if hasattr(self.simulator, 'measurement_names'):
57
+ self.measurement_names = self.simulator.measurement_names
58
+ else:
59
+ self.measurement_names = ['y_' + str(p) for p in range(self.p)]
46
60
 
47
61
  # Perturbation amounts
48
- self.w = len(t_sim) # of points in time window
49
- self.delta_x = eps * np.eye(self.simulator.n) # perturbation amount for each state
50
- self.delta_y = np.zeros((self.simulator.p, self.simulator.n, self.w)) # preallocate delta_y
51
- self.y_plus = np.zeros((self.w, self.simulator.n, self.simulator.p))
52
- self.y_minus = np.zeros((self.w, self.simulator.n, self.simulator.p))
62
+ self.w = len(self.time) # of points in time window
63
+ self.delta_x = eps * np.eye(self.n) # perturbation amount for each state
64
+ self.delta_y = np.zeros((self.p, self.n, self.w)) # preallocate delta_y
65
+ self.y_plus = np.zeros((self.w, self.n, self.p))
66
+ self.y_minus = np.zeros((self.w, self.n, self.p))
53
67
 
54
68
  # Observability matrix
55
- self.O = np.nan * np.zeros((self.simulator.p * self.w, self.simulator.n))
69
+ self.O = np.nan * np.zeros((self.p * self.w, self.n))
56
70
  self.O_df = pd.DataFrame(self.O)
57
71
 
58
72
  # Set measurement names
59
73
  self.measurement_labels = []
60
74
  self.time_labels = []
61
75
  for w in range(self.w):
62
- tl = (w * np.ones(self.simulator.p)).astype(int)
76
+ tl = (w * np.ones(self.p)).astype(int)
63
77
  self.time_labels.append(tl)
64
- self.measurement_labels = self.measurement_labels + list(self.simulator.output_mode)
78
+ self.measurement_labels = self.measurement_labels + list(self.measurement_names)
65
79
 
66
80
  self.time_labels = np.hstack(self.time_labels)
67
81
 
@@ -76,7 +90,7 @@ class EmpiricalObservabilityMatrix:
76
90
  self.parallel = parallel
77
91
 
78
92
  # Run simulations for perturbed initial conditions
79
- state_index = np.arange(0, self.simulator.n).tolist()
93
+ state_index = np.arange(0, self.n).tolist()
80
94
  if self.parallel: # multiprocessing
81
95
  with Pool(4) as pool:
82
96
  results = pool.map(self.simulate, state_index)
@@ -95,18 +109,18 @@ class EmpiricalObservabilityMatrix:
95
109
  self.y_minus[:, n, :] = y_minus
96
110
 
97
111
  # Construct O by stacking the 3rd dimension of delta_y along the 1st dimension, O is a (p*w x n) matrix
98
- self.O = np.zeros((self.simulator.p * self.w, self.simulator.n))
112
+ self.O = np.zeros((self.p * self.w, self.n))
99
113
  for w in range(self.w):
100
114
  if w == 0:
101
115
  start_index = 0
102
116
  else:
103
- start_index = int(w * self.simulator.p)
117
+ start_index = int(w * self.p)
104
118
 
105
- end_index = start_index + self.simulator.p
119
+ end_index = start_index + self.p
106
120
  self.O[start_index:end_index] = self.delta_y[:, :, w]
107
121
 
108
122
  # Make O into a data-frame for interpretability
109
- self.O_df = pd.DataFrame(self.O, columns=self.simulator.state_names, index=self.measurement_labels)
123
+ self.O_df = pd.DataFrame(self.O, columns=self.state_names, index=self.measurement_labels)
110
124
  self.O_df['time_step'] = self.time_labels
111
125
  self.O_df = self.O_df.set_index('time_step', append=True)
112
126
  self.O_df.index.names = ['sensor', 'time_step']
@@ -120,8 +134,8 @@ class EmpiricalObservabilityMatrix:
120
134
  x0_minus = self.x0 - self.delta_x[:, n]
121
135
 
122
136
  # Simulate measurements from perturbed initial conditions
123
- y_plus = self.simulator.simulate(x0=x0_plus, u=self.u_sim)
124
- y_minus = self.simulator.simulate(x0=x0_minus, u=self.u_sim)
137
+ y_plus = self.simulator.simulate(x0=x0_plus, u=self.u)
138
+ y_minus = self.simulator.simulate(x0=x0_minus, u=self.u)
125
139
 
126
140
  # Calculate the numerical Jacobian & normalize by 2x the perturbation amount
127
141
  delta_y = np.array(y_plus - y_minus).T / (2 * self.eps)
@@ -130,23 +144,35 @@ class EmpiricalObservabilityMatrix:
130
144
 
131
145
 
132
146
  class SlidingEmpiricalObservabilityMatrix:
133
- def __init__(self, simulator, t_sim, x_sim, u_sim, w=None, eps=1e-5, parallel=False):
134
- """ Construct an empirical observability matrix O in sliding windows along a trajectory.
135
-
136
- Inputs
137
- simulator: simulator object
138
- t_sim: simulation time along trajectory
139
- u_sim: simulation inputs along trajectory
140
- x_sim: state trajectory
141
- w: simulation window size for each calculation of O
142
- eps: amount to perturb initial state
147
+ def __init__(self, simulator, t_sim, x_sim, u_sim, w=None, eps=1e-5,
148
+ parallel_sliding=False, parallel_perturbation=False):
149
+ """ Construct empirical observability matrix O in sliding windows along a trajectory.
150
+
151
+ :param callable simulator: Simulator object : y = simulator(x0, u, **kwargs)
152
+ y is (w x p) array. w is the number of time-steps and p is the number of measurements
153
+ :param np.array t_sim: time vector size N
154
+ :param np.array x_sim: state trajectory array (N, n), can also be dict
155
+ :param np.array u_sim: input array (N, m), can also be dict
156
+ :param np.array w: window size for O calculations, will automatically set how many windows to compute
157
+ :params float eps: tolerance for sliding windows
158
+ :param dict/np.array u: inputs array
159
+ :param float eps: epsilon value for perturbations to construct O's, should be small number
160
+ :param bool parallel_sliding: if True, run the sliding windows in parallel
161
+ :param bool parallel_perturbation: if True, run the perturbations in parallel
143
162
  """
144
163
 
145
164
  self.simulator = simulator
146
- self.t_sim = t_sim.copy()
147
165
  self.eps = eps
148
- self.parallel = parallel
166
+ self.parallel_sliding = parallel_sliding
167
+ self.parallel_perturbation = parallel_perturbation
168
+
169
+ # Set time vector
170
+ self.t_sim = np.array(t_sim)
149
171
 
172
+ # Number of points
173
+ self.N = self.t_sim.shape[0]
174
+
175
+ # Make x_sim & u_sim arrays
150
176
  if isinstance(x_sim, dict):
151
177
  self.x_sim = np.vstack((list(x_sim.values()))).T
152
178
  else:
@@ -157,16 +183,22 @@ class SlidingEmpiricalObservabilityMatrix:
157
183
  else:
158
184
  self.u_sim = np.array(u_sim).squeeze()
159
185
 
160
- # self.dt = np.round(np.mean(np.diff(self.t_sim)), 6)
161
- self.N = self.t_sim.shape[0]
186
+ # Check sizes
187
+ if self.N != self.x_sim.shape[0]:
188
+ raise ValueError('t_sim & x_sim must have same number of rows')
189
+ elif self.N != self.u_sim.shape[0]:
190
+ raise ValueError('t_sim & u_sim must have same number of rows')
191
+ elif self.x_sim.shape[0] != self.u_sim.shape[0]:
192
+ raise ValueError('x_sim & u_sim must have same number of rows')
162
193
 
194
+ # Set time-window to calculate O's
163
195
  if w is None: # set window size to full time-series size
164
196
  self.w = self.N
165
197
  else:
166
198
  self.w = w
167
199
 
168
200
  if self.w > self.N:
169
- raise ValueError('Window size must be smaller than trajectory length')
201
+ raise ValueError('window size must be smaller than trajectory length')
170
202
 
171
203
  # All the indices to calculate O
172
204
  self.O_index = np.arange(0, self.N - self.w + 1, step=1) # indices to compute O
@@ -174,7 +206,7 @@ class SlidingEmpiricalObservabilityMatrix:
174
206
  self.n_point = len(self.O_index) # # of times to calculate O
175
207
 
176
208
  # Where to store sliding window trajectory data & O's
177
- self.window_data = {'t': [], 'u': [], 'x': [], 'y': [], 'y_plus': [], 'y_minus': []}
209
+ self.window_data = {}
178
210
  self.O_sliding = []
179
211
  self.O_df_sliding = []
180
212
 
@@ -182,21 +214,21 @@ class SlidingEmpiricalObservabilityMatrix:
182
214
  self.EOM = None
183
215
  self.run()
184
216
 
185
- def run(self, parallel=None):
217
+ def run(self, parallel_sliding=None):
186
218
  """ Run.
187
219
  """
188
220
 
189
- if parallel is not None:
190
- self.parallel = parallel
221
+ if parallel_sliding is not None:
222
+ self.parallel_sliding = parallel_sliding
191
223
 
192
224
  # Where to store sliding window trajectory data & O's
193
- self.window_data = {'t': [], 'u': [], 'x': [], 'y': [], 'y_plus': [], 'y_minus': []}
225
+ self.window_data = {'t': [], 'u': [], 'y': [], 'y_plus': [], 'y_minus': []}
194
226
  self.O_sliding = []
195
227
  self.O_df_sliding = []
196
228
 
197
229
  # Construct O's
198
230
  n_point_range = np.arange(0, self.n_point).astype(int)
199
- if self.parallel: # multiprocessing
231
+ if self.parallel_sliding: # multiprocessing
200
232
  with Pool(4) as pool:
201
233
  results = pool.map(self.construct, n_point_range)
202
234
  for r in results:
@@ -230,7 +262,7 @@ class SlidingEmpiricalObservabilityMatrix:
230
262
  u_win = self.u_sim[win, :] # inputs in window
231
263
 
232
264
  # Calculate O for window
233
- EOM = EmpiricalObservabilityMatrix(self.simulator, x0, t_win0, u_win, eps=self.eps, parallel=False)
265
+ EOM = EmpiricalObservabilityMatrix(self.simulator, x0, t_win0, u_win, eps=self.eps, parallel=self.parallel_perturbation)
234
266
  self.EOM = EOM
235
267
 
236
268
  # Store data
@@ -239,23 +271,30 @@ class SlidingEmpiricalObservabilityMatrix:
239
271
 
240
272
  window_data = {'t': t_win.copy(),
241
273
  'u': u_win.copy(),
242
- 'x': EOM.x_nominal.copy(),
243
274
  'y': EOM.y_nominal.copy(),
244
275
  'y_plus': EOM.y_plus.copy(),
245
276
  'y_minus': EOM.y_minus.copy()}
246
277
 
247
278
  return O_sliding, O_df_sliding, window_data
248
279
 
280
+ def get_observability_matrix(self):
281
+ return self.O_df_sliding.copy()
282
+
249
283
 
250
284
  class FisherObservability:
251
- def __init__(self, O, R=None, sensor_noise_dict=None, sigma=None):
285
+ def __init__(self, O, R=None, sensor_noise_dict=None, lam=None):
252
286
  """ Evaluate the observability of a state variable(s) using the Fisher Information Matrix.
253
287
 
254
- Inputs
255
- O: observability matrix. Can be numpy array or pandas data-frame (pxw x n)
256
- R: measurement noise covariance matrix (p x p)
257
- beta: reconstruction error bound for binary observability
258
- epsilon: F = F + epsilon*I if not None
288
+ :param np.array O: observability matrix (w*p, n)
289
+ w is the number of time-steps, p is the number of measurements, and n in the number of states
290
+ can also be set as pd.DataFrame where columns set the state names & a multilevel index sets the
291
+ measurement names: O.index names must be ('sensor', 'time_step')
292
+ :param np.array R: measurement noise covariance matrix (w*p x w*p)
293
+ can also be set as pd.DataFrame where R.index = R.columns = O.index
294
+ can also be a scaler where R = R * I
295
+ :param dict sensor_noise_dict: constructs R by setting the noise levels for each sensor across time-steps
296
+ keys must correspond to the 'sensor' index in O data-frame, can only be set if R is None
297
+ :param float lam: lamda parameter, if lam='limit' compute F^-1 symbolically, otherwise use Chernoff inverse
259
298
  """
260
299
 
261
300
  # Make O a data-frame
@@ -282,21 +321,21 @@ class FisherObservability:
282
321
  self.F = pd.DataFrame(self.F, index=O.columns, columns=O.columns)
283
322
 
284
323
  # Set sigma
285
- if sigma is None:
324
+ if lam is None:
286
325
  # np.linalg.eig(self.F)
287
- self.sigma = 0.0
326
+ self.lam = 0.0
288
327
  else:
289
- self.sigma = sigma
328
+ self.lam = lam
290
329
 
291
330
  # Invert F
292
- if self.sigma == 'limit': # calculate limit with symbolic sigma
331
+ if self.lam == 'limit': # calculate limit with symbolic sigma
293
332
  sigma_sym = sp.symbols('sigma')
294
333
  F_hat = self.F.values + sp.Matrix(sigma_sym * np.eye(self.n))
295
334
  F_hat_inv = F_hat.inv()
296
335
  F_hat_inv_limit = F_hat_inv.applyfunc(lambda elem: sp.limit(elem, sigma_sym, 0))
297
336
  self.F_inv = np.array(F_hat_inv_limit, dtype=np.float64)
298
337
  else: # numeric sigma
299
- F_epsilon = self.F.values + (self.sigma * np.eye(self.n))
338
+ F_epsilon = self.F.values + (self.lam * np.eye(self.n))
300
339
  self.F_inv = np.linalg.inv(F_epsilon)
301
340
 
302
341
  self.F_inv = pd.DataFrame(self.F_inv, index=O.columns, columns=O.columns)
@@ -346,17 +385,27 @@ class FisherObservability:
346
385
 
347
386
  self.R_inv = pd.DataFrame(self.R_inv, index=self.R.index, columns=self.R.index)
348
387
 
388
+ def get_fisher_information(self):
389
+ return self.F.copy(), self.F_inv.copy(), self.R.copy()
390
+
349
391
 
350
392
  class SlidingFisherObservability:
351
- def __init__(self, O_list, time=None, sigma=1e6, R=None, sensor_noise_dict=None,
393
+ def __init__(self, O_list, time=None, lam=1e6, R=None, sensor_noise_dict=None,
352
394
  states=None, sensors=None, time_steps=None, w=None):
353
395
 
354
396
  """ Compute the Fisher information matrix & inverse in sliding windows and pull put the minimum error variance.
355
- :param O_list: list of observability matrices O
356
- :param time: time vector the same size as O_list
357
- :param states: list of states to use from O's
358
- :param sensors: list of sensors to use from O's
359
- :param time_steps: list of time steps to use from O's
397
+ :param list O_list: list of observability matrices O (stored as pd.DataFrame)
398
+ :param np.array time: time vector the same size as O_list
399
+ :param np.array lam: lamda parameter, if lam='limit' compute F^-1 symbolically, otherwise use Chernoff inverse
400
+ :param np.array R: measurement noise covariance matrix (w*p x w*p)
401
+ can also be set as pd.DataFrame where R.index = R.columns = O.index
402
+ can also be a scaler where R = R * I
403
+ :param dict sensor_noise_dict: constructs R by setting the noise levels for each sensor across time-steps
404
+ keys must correspond to the 'sensor' index in O data-frame, can only be set if R is None
405
+ :param list states: list of states to use from O's. ex: ['g', 'd']
406
+ :param list sensors: list of sensors to use from O's, ex: ['r']
407
+ :param np.array time_steps: array of time steps to use from O's, ex: np.array([0, 1, 2])
408
+ :param np.array w: window size to use from O's, if None then just grab it from O
360
409
  """
361
410
 
362
411
  self.O_list = O_list
@@ -366,7 +415,7 @@ class SlidingFisherObservability:
366
415
  if time is None:
367
416
  self.time = np.arange(0, self.n_window, step=1)
368
417
  else:
369
- self.time = time
418
+ self.time = np.array(time)
370
419
 
371
420
  self.dt = np.mean(np.diff(self.time))
372
421
 
@@ -395,7 +444,7 @@ class SlidingFisherObservability:
395
444
  if time_steps is None:
396
445
  self.time_steps = O.index.get_level_values('time_step')
397
446
  else:
398
- self.time_steps = time_steps
447
+ self.time_steps = np.array(time_steps)
399
448
 
400
449
  # Compute Fisher information matrix & inverse for each sliding window
401
450
  self.EV = [] # collect error variance data for each state over time
@@ -410,7 +459,7 @@ class SlidingFisherObservability:
410
459
  O_subset = O.loc[(self.sensors, self.time_steps), self.states].sort_values(['time_step', 'sensor'])
411
460
 
412
461
  # Compute Fisher information & inverse
413
- FO = FisherObservability(O_subset, sensor_noise_dict=sensor_noise_dict, R=R, sigma=sigma)
462
+ FO = FisherObservability(O_subset, sensor_noise_dict=sensor_noise_dict, R=R, lam=lam)
414
463
  self.FO.append(FO)
415
464
 
416
465
  # Collect error variance data
@@ -424,6 +473,9 @@ class SlidingFisherObservability:
424
473
  time_df = pd.DataFrame(np.atleast_2d(self.time).T, columns=['time'])
425
474
  self.EV_aligned = pd.concat((time_df, self.EV), axis=1)
426
475
 
476
+ def get_minimum_error_variance(self):
477
+ return self.EV_aligned.copy()
478
+
427
479
 
428
480
  class ObservabilityMatrixImage:
429
481
  def __init__(self, O, state_names=None, sensor_names=None, vmax_percentile=100, vmin_ratio=1.0, cmap='bwr'):
pybounds/simulator.py CHANGED
@@ -7,42 +7,66 @@ from util import FixedKeysDict, SetDict
7
7
 
8
8
 
9
9
  class Simulator(object):
10
- def __init__(self, f, h, x0, u0, dt=0.01,
10
+ def __init__(self, f, h, dt=0.01, n=None, m=None,
11
11
  state_names=None, input_names=None, measurement_names=None,
12
12
  params_simulator=None):
13
+
13
14
  """ Simulator.
14
- :param float f: callable dynamics function f(X, U, t)
15
- :param float h: callable measurement function h(X, U, t)
15
+
16
+ :param callable f: dynamics function f(X, U, t)
17
+ :param callable h: measurement function h(X, U, t)
16
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
17
25
  """
18
26
 
19
27
  self.f = f
20
28
  self.h = h
21
29
  self.dt = dt
22
30
 
23
- # Set state & input sizes
24
- self.n = len(x0) # number of states
25
- self.m = len(u0) # number of inputs
26
-
27
- # Run measurement function to get measurement size
28
- y = self.h(x0, u0, 0)
29
- self.p = len(y) # number of measurements
30
-
31
31
  # Set state names
32
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
+
33
38
  self.state_names = ['x_' + str(n) for n in range(self.n)]
34
- else:
35
- self.state_names = state_names
36
- if len(self.state_names) != self.n:
37
- raise ValueError('state_names must have length equal to x0')
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')
38
47
 
39
48
  # Set input names
40
- if input_names is None: # default measurement 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
+
41
55
  self.input_names = ['u_' + str(m) for m in range(self.m)]
42
- else:
43
- self.input_names = input_names
44
- if len(self.input_names) != self.m:
45
- raise ValueError('input_names must have length equal to u0')
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, 0)
69
+ self.p = len(y) # number of measurements
46
70
 
47
71
  # Set measurement names
48
72
  if measurement_names is None: # default measurement names
@@ -52,8 +76,6 @@ class Simulator(object):
52
76
  if len(self.measurement_names) != self.p:
53
77
  raise ValueError('measurement_names must have length equal to y')
54
78
 
55
- self.output_mode = self.measurement_names
56
-
57
79
  # Initialize time vector
58
80
  w = 10 # initialize for w time-steps, but this can change later
59
81
  self.time = np.arange(0, w * self.dt + self.dt / 2, step=self.dt) # time vector
@@ -168,8 +190,7 @@ class Simulator(object):
168
190
 
169
191
  :params x0: initial state dict or array
170
192
  :params u: input dict or array
171
- :params output_mode: array of strings containing variable names of outputs
172
- :params run_mpc: boolean to run MPC controller
193
+ :params return_full_output: boolean to run (time, x, u, y) instead of y
173
194
  """
174
195
 
175
196
  # Update the initial state
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.1
2
+ Name: pybounds
3
+ Version: 0.0.2
4
+ Summary: Bounding Observability for Uncertain Nonlinear Dynamics Systems (BOUNDS)
5
+ Home-page: https://pypi.org/project/pybounds/
6
+ Author: Ben Cellini, Burak Boyacioglu, Floris van Breugel
7
+ Author-email: bcellini00@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+
15
+ # pybounds
16
+
17
+ Python implementation of BOUNDS: Bounding Observability for Uncertain Nonlinear Dynamic Systems.
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
+ <p align="center">
27
+ <a href="https://pypi.org/project/pybounds/">
28
+ <img src="https://badge.fury.io/py/pynumdiff.svg" alt="PyPI version" height="18"></a>
29
+ </p>
30
+
31
+ ## Introduction
32
+
33
+ This repository provides a minimal working example demonstrating how to empirically calculate the observability level of individual states for a nonlinear (partially observable) system, and accounts for sensor noise.
34
+
35
+ ## Installing
36
+
37
+ The package can be installed by cloning the repo and running python setup.py install from inside the home pybounds directory.
38
+
39
+ Alternatively using pip
40
+ ```bash
41
+ pip install pybounds
42
+ ```
43
+
44
+ ## Notebook examples
45
+ There is currently one simple example notebook. More to come.
46
+ * Monocular camera with optic fow measurements: [mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
47
+
48
+ ## Citation
49
+
50
+ If you use the code or methods from this package, please cite the following paper:
51
+
52
+ Benjamin Cellini, Burak Boyacıoğlu, Stanley David Stupski, and Floris van Breugel. Discovering and exploiting active sensing motifs for estimation with empirical observability. (2024) bioRxiv.
53
+
54
+ ## Related packages
55
+ This repository is the evolution of the EISO repo (https://github.com/BenCellini/EISO), and is intended as a companion to the repository directly associated with the paper above.
56
+
57
+ ## License
58
+
59
+ This project utilizes the [MIT LICENSE](LICENSE.txt).
60
+ 100% open-source, feel free to utilize the code however you like.
@@ -0,0 +1,9 @@
1
+ pybounds/__init__.py,sha256=so9LuRNw2V8MGsDs-RPstGGgJtBFp2YGolQbS0rVfhw,348
2
+ pybounds/observability.py,sha256=_A6HEy6wMEMhRoxXojMZYeximhz3PPR3MdLTaihJR7U,27057
3
+ pybounds/simulator.py,sha256=p1O17cvbr_otjGTsDTreuugeF3lEI7NpwII6uaoCkaQ,9780
4
+ pybounds/util.py,sha256=xxmXmpLR3yK923X6wAPKp_5w814cO3m9OqF1XIt9S8c,5818
5
+ pybounds-0.0.2.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
6
+ pybounds-0.0.2.dist-info/METADATA,sha256=Zeh9auhV9fqq0Le2paSPD1-hrMN0AsWJ_zKZd5Symc4,2376
7
+ pybounds-0.0.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
8
+ pybounds-0.0.2.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
9
+ pybounds-0.0.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,15 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: pybounds
3
- Version: 0.0.1
4
- Summary: Bounding Observability for Uncertain Nonlinear Dynamics Systems (BOUNDS)
5
- Home-page: https://github.com/vanbreugel-lab/pybounds
6
- Author: Ben Cellini, Burak Boyacioglu, Floris van Breugel
7
- Author-email: bcellini00@gmail.com
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.9
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
-
15
- # pybounds
@@ -1,9 +0,0 @@
1
- pybounds/__init__.py,sha256=so9LuRNw2V8MGsDs-RPstGGgJtBFp2YGolQbS0rVfhw,348
2
- pybounds/observability.py,sha256=tiJdR5tS72SGn1df8Vnz303nDPzhOFaQWSYJoY1o8Qg,24055
3
- pybounds/simulator.py,sha256=-XpPKQUcwNubw40wNfoqSfqmohbGpBjSQSNWctyfRkQ,8837
4
- pybounds/util.py,sha256=xxmXmpLR3yK923X6wAPKp_5w814cO3m9OqF1XIt9S8c,5818
5
- pybounds-0.0.1.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
6
- pybounds-0.0.1.dist-info/METADATA,sha256=Il4j0WbOUWga2nq69YD-fjh3OtElrZ-XA4TBSKMUUMs,539
7
- pybounds-0.0.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
8
- pybounds-0.0.1.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
9
- pybounds-0.0.1.dist-info/RECORD,,