pybounds 0.0.3__py3-none-any.whl → 0.0.5__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
@@ -1,9 +1,10 @@
1
-
2
1
  import numpy as np
3
2
  import pandas as pd
4
- from multiprocessing import Pool
3
+ # from multiprocessing import Pool
4
+ # from pathos.multiprocessing import ProcessingPool as Pool
5
+ from concurrent.futures import ThreadPoolExecutor
5
6
  import warnings
6
- import matplotlib as mpl
7
+ # import matplotlib as mpl
7
8
  import matplotlib.pyplot as plt
8
9
  from mpl_toolkits.axes_grid1.inset_locator import inset_axes
9
10
  import sympy as sp
@@ -92,8 +93,11 @@ class EmpiricalObservabilityMatrix:
92
93
  # Run simulations for perturbed initial conditions
93
94
  state_index = np.arange(0, self.n).tolist()
94
95
  if self.parallel: # multiprocessing
95
- with Pool(4) as pool:
96
- results = pool.map(self.simulate, state_index)
96
+ # with Pool(4) as pool:
97
+ # results = pool.map(self.simulate, state_index)
98
+
99
+ with ThreadPoolExecutor() as executor:
100
+ results = list(executor.map(self.simulate, state_index))
97
101
 
98
102
  for n, r in enumerate(results):
99
103
  delta_y, y_plus, y_minus = r
@@ -147,7 +151,7 @@ class SlidingEmpiricalObservabilityMatrix:
147
151
  def __init__(self, simulator, t_sim, x_sim, u_sim, w=None, eps=1e-5,
148
152
  parallel_sliding=False, parallel_perturbation=False):
149
153
  """ Construct empirical observability matrix O in sliding windows along a trajectory.
150
-
154
+
151
155
  :param callable simulator: Simulator object : y = simulator(x0, u, **kwargs)
152
156
  y is (w x p) array. w is the number of time-steps and p is the number of measurements
153
157
  :param np.array t_sim: time vector size N
@@ -201,7 +205,7 @@ class SlidingEmpiricalObservabilityMatrix:
201
205
  raise ValueError('window size must be smaller than trajectory length')
202
206
 
203
207
  # All the indices to calculate O
204
- self.O_index = np.arange(0, self.N - self.w + 1, step=1) # indices to compute O
208
+ self.O_index = np.arange(0, self.N - self.w + 1, step=1) # indices to compute O
205
209
  self.O_time = self.t_sim[self.O_index] # times to compute O
206
210
  self.n_point = len(self.O_index) # # of times to calculate O
207
211
 
@@ -229,8 +233,12 @@ class SlidingEmpiricalObservabilityMatrix:
229
233
  # Construct O's
230
234
  n_point_range = np.arange(0, self.n_point).astype(int)
231
235
  if self.parallel_sliding: # multiprocessing
232
- with Pool(4) as pool:
233
- results = pool.map(self.construct, n_point_range)
236
+ # with Pool(4) as pool:
237
+ # results = pool.map(self.construct, n_point_range)
238
+
239
+ with ThreadPoolExecutor(max_workers=12) as executor:
240
+ results = list(executor.map(self.construct, n_point_range))
241
+
234
242
  for r in results:
235
243
  self.O_sliding.append(r[0])
236
244
  self.O_df_sliding.append(r[1])
@@ -262,7 +270,8 @@ class SlidingEmpiricalObservabilityMatrix:
262
270
  u_win = self.u_sim[win, :] # inputs in window
263
271
 
264
272
  # Calculate O for window
265
- EOM = EmpiricalObservabilityMatrix(self.simulator, x0, t_win0, u_win, eps=self.eps, parallel=self.parallel_perturbation)
273
+ EOM = EmpiricalObservabilityMatrix(self.simulator, x0, t_win0, u_win, eps=self.eps,
274
+ parallel=self.parallel_perturbation)
266
275
  self.EOM = EOM
267
276
 
268
277
  # Store data
pybounds/simulator.py CHANGED
@@ -1,5 +1,7 @@
1
1
 
2
+ import warnings
2
3
  import numpy as np
4
+ import matplotlib.pyplot as plt
3
5
  import do_mpc
4
6
  from .util import FixedKeysDict, SetDict
5
7
 
@@ -7,7 +9,7 @@ from .util import FixedKeysDict, SetDict
7
9
  class Simulator(object):
8
10
  def __init__(self, f, h, dt=0.01, n=None, m=None,
9
11
  state_names=None, input_names=None, measurement_names=None,
10
- params_simulator=None):
12
+ params_simulator=None, mpc_horizon=10):
11
13
 
12
14
  """ Simulator.
13
15
 
@@ -63,7 +65,7 @@ class Simulator(object):
63
65
  # Run measurement function to get measurement size
64
66
  x0 = np.ones(self.n)
65
67
  u0 = np.ones(self.m)
66
- y = self.h(x0, u0, 0)
68
+ y = self.h(x0, u0)
67
69
  self.p = len(y) # number of measurements
68
70
 
69
71
  # Set measurement names
@@ -75,32 +77,40 @@ class Simulator(object):
75
77
  raise ValueError('measurement_names must have length equal to y')
76
78
 
77
79
  # Initialize time vector
78
- w = 10 # initialize for w time-steps, but this can change later
79
- self.time = np.arange(0, w * self.dt + self.dt / 2, step=self.dt) # time vector
80
+ self.w = 11 # initialize for w time-steps, but this can change later
81
+ self.time = np.arange(0, self.w * self.dt, step=self.dt) # time vector
80
82
 
81
83
  # Define initial states & initialize state time-series
82
84
  self.x0 = {}
83
85
  self.x = {}
84
86
  for n, state_name in enumerate(self.state_names):
85
87
  self.x0[state_name] = x0[n]
86
- self.x[state_name] = x0[n] * np.ones(w)
88
+ self.x[state_name] = x0[n] * np.ones(self.w)
87
89
 
88
90
  self.x0 = FixedKeysDict(self.x0)
91
+ self.x = FixedKeysDict(self.x)
89
92
 
90
93
  # Initialize input time-series
91
94
  self.u = {}
92
95
  for m, input_name in enumerate(self.input_names):
93
- self.u[input_name] = u0[m] * np.ones(w)
96
+ self.u[input_name] = u0[m] * np.ones(self.w)
94
97
 
95
98
  self.u = FixedKeysDict(self.u)
96
99
 
97
100
  # Initialize measurement time-series
98
101
  self.y = {}
99
102
  for p, measurement_name in enumerate(self.measurement_names):
100
- self.y[measurement_name] = 0.0 * np.ones(w)
103
+ self.y[measurement_name] = 0.0 * np.ones(self.w)
101
104
 
102
105
  self.y = FixedKeysDict(self.y)
103
106
 
107
+ # Initialize state set-points
108
+ self.setpoint = {}
109
+ for n, state_name in enumerate(self.state_names):
110
+ self.setpoint[state_name] = 0.0 * np.ones(self.w)
111
+
112
+ self.setpoint = FixedKeysDict(self.setpoint)
113
+
104
114
  # Define continuous-time MPC model
105
115
  self.model = do_mpc.model.Model('continuous')
106
116
 
@@ -117,10 +127,14 @@ class Simulator(object):
117
127
  U.append(u)
118
128
 
119
129
  # Define dynamics
120
- Xdot = self.f(X, U, 0)
130
+ Xdot = self.f(X, U)
121
131
  for n, state_name in enumerate(self.state_names):
122
132
  self.model.set_rhs(state_name, Xdot[n])
123
133
 
134
+ # Add time-varying set-point variables for later use with MPC
135
+ for n, state_name in enumerate(self.state_names):
136
+ x = self.model.set_variable(var_type='_tvp', var_name=state_name + str('_set'), shape=(1, 1))
137
+
124
138
  # Build model
125
139
  self.model.setup()
126
140
 
@@ -139,8 +153,81 @@ class Simulator(object):
139
153
  self.params_simulator = params_simulator
140
154
 
141
155
  self.simulator.set_param(**self.params_simulator)
156
+
157
+ # Setup MPC
158
+ self.mpc = do_mpc.controller.MPC(self.model)
159
+ self.mpc_horizon = mpc_horizon
160
+ setup_mpc = {
161
+ 'n_horizon': self.mpc_horizon,
162
+ 'n_robust': 0,
163
+ 'open_loop': 0,
164
+ 't_step': self.dt,
165
+ 'state_discretization': 'collocation',
166
+ 'collocation_type': 'radau',
167
+ 'collocation_deg': 2,
168
+ 'collocation_ni': 1,
169
+ 'store_full_solution': False,
170
+
171
+ # Use MA27 linear solver in ipopt for faster calculations:
172
+ 'nlpsol_opts': {'ipopt.linear_solver': 'mumps', # mumps, MA27
173
+ 'ipopt.print_level': 0,
174
+ 'ipopt.sb': 'yes',
175
+ 'print_time': 0,
176
+ }
177
+ }
178
+
179
+ self.mpc.set_param(**setup_mpc)
180
+
181
+ # Get template's for MPC time-varying parameters
182
+ self.mpc_tvp_template = self.mpc.get_tvp_template()
183
+ self.simulator_tvp_template = self.simulator.get_tvp_template()
184
+
185
+ # Set time-varying set-point functions
186
+ self.mpc.set_tvp_fun(self.mpc_tvp_function)
187
+ self.simulator.set_tvp_fun(self.simulator_tvp_function)
188
+
189
+ # Setup simulator
142
190
  self.simulator.setup()
143
191
 
192
+ def simulator_tvp_function(self, t):
193
+ """ Set the set-point function for MPC simulator.
194
+ :param t: current time
195
+ """
196
+
197
+ mpc_horizon = self.mpc._settings.n_horizon
198
+
199
+ # Set current step index
200
+ k_step = int(np.round(t / self.dt))
201
+ if k_step >= mpc_horizon: # point is beyond end of input data
202
+ k_step = mpc_horizon - 1 # set point beyond input data to last point
203
+
204
+ # Update current set-point
205
+ for n, state_name in enumerate(self.state_names):
206
+ self.simulator_tvp_template[state_name + '_set'] = self.setpoint[state_name][k_step]
207
+
208
+ return self.simulator_tvp_template
209
+
210
+ def mpc_tvp_function(self, t):
211
+ """ Set the set-point function for MPC optimizer.
212
+ """
213
+
214
+ mpc_horizon = self.mpc._settings.n_horizon
215
+
216
+ # Set current step index
217
+ k_step = int(np.round(t / self.dt))
218
+
219
+ # Update set-point time horizon
220
+ for k in range(mpc_horizon + 1):
221
+ k_set = k_step + k
222
+ if k_set >= self.w: # horizon is beyond end of input data
223
+ k_set = self.w - 1 # set part of horizon beyond input data to last point
224
+
225
+ # Update each set-point over time horizon
226
+ for n, state_name in enumerate(self.state_names):
227
+ self.mpc_tvp_template['_tvp', k, state_name + '_set'] = self.setpoint[state_name][k_set]
228
+
229
+ return self.mpc_tvp_template
230
+
144
231
  def set_initial_state(self, x0):
145
232
  """ Update the initial state.
146
233
  """
@@ -156,33 +243,35 @@ class Simulator(object):
156
243
  else:
157
244
  raise Exception('x0 must be either a dict, tuple, list, or numpy array')
158
245
 
159
- def set_inputs(self, u):
160
- """ Update the inputs.
246
+ def update_dict(self, data=None, name=None):
247
+ """ Update.
161
248
  """
162
249
 
163
- if u is not None: # inputs given
164
- if isinstance(u, dict): # in dict format
165
- SetDict().set_dict_with_overwrite(self.u, u) # update only the inputs in the dict given
166
- elif isinstance(u, list) or isinstance(u, tuple): # list or tuple format, each input vector in each element
167
- for n, k in enumerate(self.u.keys()): # each input
168
- self.u[k] = u[n]
169
- elif isinstance(u, np.ndarray): # numpy array format given as matrix where columns are the different inputs
170
- if len(u.shape) <= 1: # given as 1d array, so convert to column vector
171
- u = np.atleast_2d(u).T
250
+ update = getattr(self, name)
172
251
 
173
- for m, key in enumerate(self.u.keys()): # each input
174
- self.u[key] = u[:, m]
252
+ if data is not None: # data given
253
+ if isinstance(data, dict): # in dict format
254
+ SetDict().set_dict_with_overwrite(update, data) # update only the inputs in the dict given
255
+ elif isinstance(data, list) or isinstance(data, tuple): # list or tuple format, each input vector in each element
256
+ for n, k in enumerate(update.keys()): # each state
257
+ update[k] = data[n]
258
+ elif isinstance(data, np.ndarray): # numpy array format given as matrix where columns are the different inputs
259
+ if len(data.shape) <= 1: # given as 1d array, so convert to column vector
260
+ data = np.atleast_2d(data).T
261
+
262
+ for n, key in enumerate(update.keys()): # each input
263
+ update[key] = data[:, n]
175
264
 
176
265
  else:
177
- raise Exception('u must be either a dict, tuple, list, or numpy array')
266
+ raise Exception(name + ' must be either a dict, tuple, list, or numpy array')
178
267
 
179
268
  # Make sure inputs are the same size
180
- points = np.array([self.u[key].shape[0] for key in self.u.keys()])
269
+ points = np.array([update[key].shape[0] for key in update.keys()])
181
270
  points_check = points == points[0]
182
271
  if not np.all(points_check):
183
- raise Exception('inputs are not the same size')
272
+ raise Exception(name + ' not the same size')
184
273
 
185
- def simulate(self, x0=None, u=None, return_full_output=False):
274
+ def simulate(self, x0=None, u=None, mpc=False, return_full_output=False):
186
275
  """
187
276
  Simulate the system.
188
277
 
@@ -191,34 +280,63 @@ class Simulator(object):
191
280
  :params return_full_output: boolean to run (time, x, u, y) instead of y
192
281
  """
193
282
 
283
+ if (mpc is True) and (u is not None):
284
+ raise Exception('u must be None if running MPC')
285
+
286
+ if (mpc is False) and (u is None):
287
+ warnings.warn('not running MPC or setting u directly')
288
+
194
289
  # Update the initial state
195
- self.set_initial_state(x0=x0.copy())
290
+ if x0 is None:
291
+ if mpc: # set the initial state to start at set-point if running MPC
292
+ x0 = {}
293
+ for state_name in self.state_names:
294
+ x0[state_name] = self.setpoint[state_name][0]
295
+
296
+ self.set_initial_state(x0=x0)
297
+ else:
298
+ self.set_initial_state(x0=x0)
196
299
 
197
300
  # Update the inputs
198
- self.set_inputs(u=u)
301
+ self.update_dict(u, name='u')
199
302
 
200
303
  # Concatenate the inputs, where rows are individual inputs and columns are time-steps
201
- u_sim = np.vstack(list(self.u.values())).T
202
- n_point = u_sim.shape[0]
304
+ if mpc:
305
+ self.w = np.vstack(list(self.setpoint.values())).shape[1]
306
+ u_sim = np.zeros((self.w, self.m)) # preallocate input array
307
+ else:
308
+ self.w = np.vstack(list(self.u.values())).shape[1]
309
+ u_sim = np.vstack(list(self.u.values())).T
203
310
 
204
311
  # Update time vector
205
- T = (n_point - 1) * self.dt
206
- self.time = np.linspace(0, T, num=n_point)
312
+ T = (self.w - 1) * self.dt
313
+ self.time = np.linspace(0, T, num=self.w)
207
314
 
208
315
  # Set array to store simulated states, where rows are individual states and columns are time-steps
209
316
  x_step = np.array(list(self.x0.values())) # initialize state
210
- x_sim = np.nan * np.zeros((n_point, self.n))
211
- x_sim[0, :] = x_step.copy()
317
+ x = np.nan * np.zeros((self.w, self.n))
318
+ x[0, :] = x_step.copy()
212
319
 
213
320
  # Initialize the simulator
214
321
  self.simulator.t0 = self.time[0]
215
322
  self.simulator.x0 = x_step.copy()
216
323
  self.simulator.set_initial_guess()
217
324
 
325
+ # Initialize MPC
326
+ if mpc:
327
+ self.mpc.setup()
328
+ self.mpc.t0 = self.time[0]
329
+ self.mpc.x0 = x_step.copy()
330
+ self.mpc.u0 = np.zeros((self.m, 1))
331
+ self.mpc.set_initial_guess()
332
+
218
333
  # Run simulation
219
- for k in range(1, n_point):
334
+ for k in range(1, self.w):
220
335
  # Set input
221
- u_step = u_sim[k - 1:k, :].T
336
+ if mpc: # run MPC step
337
+ u_step = self.mpc.make_step(x_step)
338
+ else: # use inputs directly
339
+ u_step = u_sim[k - 1:k, :].T
222
340
 
223
341
  # Store inputs
224
342
  u_sim[k - 1, :] = u_step.squeeze()
@@ -227,26 +345,27 @@ class Simulator(object):
227
345
  x_step = self.simulator.make_step(u_step)
228
346
 
229
347
  # Store new states
230
- x_sim[k, :] = x_step.squeeze()
348
+ x[k, :] = x_step.squeeze()
349
+
350
+ # Last input has no effect, so keep it the same as previous time-step
351
+ if mpc:
352
+ u_sim[-1, :] = u_sim[-2, :]
231
353
 
232
354
  # Update the inputs
233
- self.set_inputs(u=u_sim)
355
+ self.update_dict(u_sim, name='u')
234
356
 
235
357
  # Update state trajectory
236
- for n, key in enumerate(self.x.keys()):
237
- self.x[key] = x_sim[:, n]
358
+ self.update_dict(x, name='x')
238
359
 
239
360
  # Calculate measurements
240
361
  x_list = list(self.x.values())
241
362
  u_list = list(self.u.values())
242
- y = self.h(x_list, u_list, 0)
363
+ y = self.h(x_list, u_list)
243
364
 
244
- # Set outputs
245
- self.y = {}
246
- for p, measurement_name in enumerate(self.measurement_names):
247
- self.y[measurement_name] = y[p]
365
+ # Set measurements
366
+ self.update_dict(y, name='y')
248
367
 
249
- # Return the outputs in array format
368
+ # Return the measurements in array format
250
369
  y_array = np.vstack(list(self.y.values())).T
251
370
 
252
371
  if return_full_output:
@@ -254,7 +373,50 @@ class Simulator(object):
254
373
  else:
255
374
  return y_array
256
375
 
257
- def get_time_states_input_measurements(self):
376
+ def get_time_states_inputs_measurements(self):
258
377
  return self.time.copy(), self.x.copy(), self.u.copy(), self.u.copy()
259
378
 
379
+ def plot(self, name='x', dpi=150, plot_kwargs=None):
380
+ """ Plot states, inputs.
381
+ """
382
+
383
+ if plot_kwargs is None:
384
+ plot_kwargs = {
385
+ 'color': 'black',
386
+ 'linewidth': 2.0,
387
+ 'linestyle': '-',
388
+ 'marker': '.',
389
+ 'markersize': 0
390
+ }
391
+
392
+ if name == 'x':
393
+ plot_kwargs['color'] = 'firebrick'
394
+ elif name == 'u':
395
+ plot_kwargs['color'] = 'royalblue'
396
+ elif name == 'y':
397
+ plot_kwargs['color'] = 'seagreen'
398
+ elif name == 'setpoint':
399
+ plot_kwargs['color'] = 'gray'
400
+
401
+ plot_dict = getattr(self, name)
402
+ plot_data = np.array(list(plot_dict.values()))
403
+ n = plot_data.shape[0]
404
+
405
+ fig, ax = plt.subplots(n, 1, figsize=(4, n * 1.5), dpi=dpi, sharex=True)
406
+ ax = np.atleast_1d(ax)
407
+
408
+ for n, key in enumerate(plot_dict.keys()):
409
+ ax[n].plot(self.time, plot_dict[key], label='set-point', **plot_kwargs)
410
+ ax[n].set_ylabel(key, fontsize=7)
411
+
412
+ # Also plot the states if plotting setpoint
413
+ if name == 'setpoint':
414
+ ax[n].plot(self.time, self.x[key], label=key, color='firebrick', linestyle='-', linewidth=0.5)
415
+ ax[n].legend(fontsize=6)
416
+
417
+ ax[-1].set_xlabel('time', fontsize=7)
418
+ ax[0].set_title(name, fontsize=8, fontweight='bold')
419
+
260
420
 
421
+ for a in ax.flat:
422
+ a.tick_params(axis='both', labelsize=6)
pybounds/util.py CHANGED
@@ -74,6 +74,7 @@ class LatexStates:
74
74
  'v_perp': r'$v_{\perp}$',
75
75
  'phi': r'$\phi$',
76
76
  'phidot': r'$\dot{\phi}$',
77
+ 'phi_dot': r'$\dot{\phi}$',
77
78
  'phiddot': r'$\ddot{\phi}$',
78
79
  'w': r'$w$',
79
80
  'zeta': r'$\zeta$',
@@ -97,7 +98,9 @@ class LatexStates:
97
98
  'v_para_dot_ratio': r'$\frac{\Delta v_{\parallel}}{v_{\parallel}}$',
98
99
  'sigma': r'$\sigma$',
99
100
  'rho': r'$\rho$',
100
- 'beta': r'$\beta$'
101
+ 'beta': r'$\beta$',
102
+ 'lambda': r'$\lambda',
103
+ 'delta': r'$\delta'
101
104
  }
102
105
 
103
106
  if dict is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pybounds
3
- Version: 0.0.3
3
+ Version: 0.0.5
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
@@ -8,7 +8,7 @@ Author-email: bcellini00@gmail.com
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.9
11
+ Requires-Python: >=3.0
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
 
@@ -16,16 +16,9 @@ License-File: LICENSE
16
16
 
17
17
  Python implementation of BOUNDS: Bounding Observability for Uncertain Nonlinear Dynamic Systems.
18
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
19
  <p align="center">
27
20
  <a href="https://pypi.org/project/pybounds/">
28
- <img src="https://badge.fury.io/py/pynumdiff.svg" alt="PyPI version" height="18"></a>
21
+ <img src="https://badge.fury.io/py/pybounds.svg" alt="PyPI version" height="18"></a>
29
22
  </p>
30
23
 
31
24
  ## Introduction
@@ -42,9 +35,12 @@ pip install pybounds
42
35
  ```
43
36
 
44
37
  ## Notebook examples
45
- There is currently one simple example notebook. More to come.
38
+ For a simple system
46
39
  * Monocular camera with optic fow measurements: [mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
47
40
 
41
+ For a more complex system
42
+ * Fly-wind: [fly_wind_example.ipynb](examples%2Ffly_wind_example.ipynb)[mono_camera_example.ipynb](examples%2Fmono_camera_example.ipynb)
43
+
48
44
  ## Citation
49
45
 
50
46
  If you use the code or methods from this package, please cite the following paper:
@@ -0,0 +1,9 @@
1
+ pybounds/__init__.py,sha256=so9LuRNw2V8MGsDs-RPstGGgJtBFp2YGolQbS0rVfhw,348
2
+ pybounds/observability.py,sha256=MUdwufFf200SYseP42JCP978O4F7GbeBVtm0yGjGzOE,27490
3
+ pybounds/simulator.py,sha256=-CrQVpNfiBDFECd6H7FU5has4sYGW1gyS2RhOgXUqZg,15858
4
+ pybounds/util.py,sha256=l6S9G88S-OZO9mi7F_58bVAPSr3PGQKcbHUghgni4JY,5956
5
+ pybounds-0.0.5.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
6
+ pybounds-0.0.5.dist-info/METADATA,sha256=sQnyiQHrQHFzXHh_mr4ymujDAh7MZved-QnSKC08JTM,2226
7
+ pybounds-0.0.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
8
+ pybounds-0.0.5.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
9
+ pybounds-0.0.5.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- pybounds/__init__.py,sha256=so9LuRNw2V8MGsDs-RPstGGgJtBFp2YGolQbS0rVfhw,348
2
- pybounds/observability.py,sha256=OqJ7fZmflvgZ9dQprIkZ3aahmvwLKVa-5RRHGpnv_gI,27058
3
- pybounds/simulator.py,sha256=GiQHDGkRtiUsMiiNB8tLBXbG2d5UQJwa7WvXXalFtXg,9748
4
- pybounds/util.py,sha256=xxmXmpLR3yK923X6wAPKp_5w814cO3m9OqF1XIt9S8c,5818
5
- pybounds-0.0.3.dist-info/LICENSE,sha256=kqeyRXtRGgBVZdXYeIX4zR9l2KZ2rqIBVEiPMTjxjcI,1093
6
- pybounds-0.0.3.dist-info/METADATA,sha256=4P-nOKxKtvARRB68s2FeExrHp773u0Fok9GIgG3ualg,2376
7
- pybounds-0.0.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
8
- pybounds-0.0.3.dist-info/top_level.txt,sha256=V-ofnWE3m_UkXTXJwNRD07n14m5R6sc6l4NadaCCP_A,9
9
- pybounds-0.0.3.dist-info/RECORD,,