fmdtools 2.1.2__tar.gz → 2.1.3__tar.gz

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.
Files changed (104) hide show
  1. {fmdtools-2.1.2 → fmdtools-2.1.3}/LICENSE +1 -1
  2. {fmdtools-2.1.2 → fmdtools-2.1.3}/PKG-INFO +5 -5
  3. {fmdtools-2.1.2 → fmdtools-2.1.3}/README.md +3 -3
  4. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/rover/rover_model.py +8 -3
  5. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/__init__.py +1 -1
  6. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/history.py +14 -6
  7. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/result.py +1 -1
  8. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/architecture/base.py +4 -6
  9. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/architecture/geom.py +33 -5
  10. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/container/base.py +3 -2
  11. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/environment.py +15 -8
  12. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/object/base.py +4 -1
  13. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/object/geom.py +147 -74
  14. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools.egg-info/PKG-INFO +5 -5
  15. {fmdtools-2.1.2 → fmdtools-2.1.3}/pyproject.toml +1 -1
  16. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/__init__.py +0 -0
  17. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/asg_demo/__init__.py +0 -0
  18. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/asg_demo/demo_model.py +0 -0
  19. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/eps/__init__.py +0 -0
  20. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/eps/eps.py +0 -0
  21. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/eps/test_eps.py +0 -0
  22. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multiflow_demo/__init__.py +0 -0
  23. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multiflow_demo/multiflow_demo.py +0 -0
  24. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multiflow_demo/test_multiflow.py +0 -0
  25. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multirotor/__init__.py +0 -0
  26. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multirotor/drone_mdl_dynamic.py +0 -0
  27. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multirotor/drone_mdl_hierarchical.py +0 -0
  28. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multirotor/drone_mdl_rural.py +0 -0
  29. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multirotor/drone_mdl_static.py +0 -0
  30. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multirotor/drone_mdl_urban.py +0 -0
  31. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multirotor/opt_drone_rural.py +0 -0
  32. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/multirotor/test_multirotor.py +0 -0
  33. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/__init__.py +0 -0
  34. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/ex_pump.py +0 -0
  35. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/jointfaults_ex.py +0 -0
  36. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/parallelism_methods.py +0 -0
  37. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/pump_indiv.py +0 -0
  38. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/pump_stochastic.py +0 -0
  39. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/test_pump.py +0 -0
  40. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/test_pump_example.py +0 -0
  41. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/pump/test_pump_stochastic.py +0 -0
  42. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/rover/__init__.py +0 -0
  43. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/rover/optimization/ea_analysis.py +0 -0
  44. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/rover/optimization/search_rover.py +0 -0
  45. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/rover/rover_degradation.py +0 -0
  46. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/rover/rover_mode_space.py +0 -0
  47. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/rover/rover_model_human.py +0 -0
  48. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/rover/test_rover.py +0 -0
  49. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/tank/__init__.py +0 -0
  50. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/tank/tank_model.py +0 -0
  51. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/tank/tank_optimization_model.py +0 -0
  52. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/tank/test_tank.py +0 -0
  53. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/taxiway/ATC.py +0 -0
  54. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/taxiway/asset.py +0 -0
  55. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/taxiway/common.py +0 -0
  56. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/taxiway/model.py +0 -0
  57. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/taxiway/test_asset.py +0 -0
  58. {fmdtools-2.1.2 → fmdtools-2.1.3}/examples/taxiway/test_model.py +0 -0
  59. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/__init__.py +0 -0
  60. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/common.py +0 -0
  61. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/graph/__init__.py +0 -0
  62. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/graph/base.py +0 -0
  63. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/graph/label.py +0 -0
  64. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/graph/model.py +0 -0
  65. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/graph/style.py +0 -0
  66. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/phases.py +0 -0
  67. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/analyze/tabulate.py +0 -0
  68. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/__init__.py +0 -0
  69. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/architecture/__init__.py +0 -0
  70. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/architecture/action.py +0 -0
  71. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/architecture/component.py +0 -0
  72. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/architecture/function.py +0 -0
  73. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/base.py +0 -0
  74. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/block/__init__.py +0 -0
  75. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/block/action.py +0 -0
  76. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/block/base.py +0 -0
  77. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/block/component.py +0 -0
  78. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/block/function.py +0 -0
  79. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/container/__init__.py +0 -0
  80. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/container/mode.py +0 -0
  81. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/container/parameter.py +0 -0
  82. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/container/rand.py +0 -0
  83. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/container/state.py +0 -0
  84. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/container/time.py +0 -0
  85. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/flow/__init__.py +0 -0
  86. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/flow/base.py +0 -0
  87. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/flow/commsflow.py +0 -0
  88. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/flow/multiflow.py +0 -0
  89. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/object/__init__.py +0 -0
  90. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/object/coords.py +0 -0
  91. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/define/object/timer.py +0 -0
  92. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/sim/__init__.py +0 -0
  93. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/sim/propagate.py +0 -0
  94. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/sim/sample.py +0 -0
  95. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/sim/scenario.py +0 -0
  96. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools/sim/search.py +0 -0
  97. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools.egg-info/SOURCES.txt +0 -0
  98. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools.egg-info/dependency_links.txt +0 -0
  99. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools.egg-info/requires.txt +0 -0
  100. {fmdtools-2.1.2 → fmdtools-2.1.3}/fmdtools.egg-info/top_level.txt +0 -0
  101. {fmdtools-2.1.2 → fmdtools-2.1.3}/setup.cfg +0 -0
  102. {fmdtools-2.1.2 → fmdtools-2.1.3}/tests/test_graph.py +0 -0
  103. {fmdtools-2.1.2 → fmdtools-2.1.3}/tests/test_hierarchical.py +0 -0
  104. {fmdtools-2.1.2 → fmdtools-2.1.3}/tests/test_packages.py +0 -0
@@ -1,4 +1,4 @@
1
- fmdtools version 2.1.2
1
+ fmdtools version 2.1.3
2
2
  ---------------------------
3
3
  Apache License
4
4
  Version 2.0, January 2004
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmdtools
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: System resilience modelling, simulation, and assessment in Python
5
5
  Author: fmdtools Developers
6
6
  Maintainer-email: Daniel Hulse <daniel.e.hulse@nasa.gov>
7
- License: fmdtools version 2.1.2
7
+ License: fmdtools version 2.1.3
8
8
  ---------------------------
9
9
  Apache License
10
10
  Version 2.0, January 2004
@@ -367,7 +367,7 @@ This may then be installed using:
367
367
  Note that this version reflects the latest version on the ``main`` git branch along with any local changes. To use a particular version of ``fmdtools`` you can checkout the tag corresponding to the version you want, e.g.:
368
368
 
369
369
  ```
370
- git checkout v2.1.2
370
+ git checkout v2.1.3
371
371
  ```
372
372
 
373
373
 
@@ -505,8 +505,8 @@ To cite a particular version of the fmdtools, you may use:
505
505
  author = {{NASA}},
506
506
  title = {fmdtools},
507
507
  url = {https://github.com/nasa/fmdtools},
508
- version = {2.1.2},
509
- date = {2025-5-19},
508
+ version = {2.1.3},
509
+ date = {2025-6-6},
510
510
  }
511
511
  ```
512
512
 
@@ -106,7 +106,7 @@ This may then be installed using:
106
106
  Note that this version reflects the latest version on the ``main`` git branch along with any local changes. To use a particular version of ``fmdtools`` you can checkout the tag corresponding to the version you want, e.g.:
107
107
 
108
108
  ```
109
- git checkout v2.1.2
109
+ git checkout v2.1.3
110
110
  ```
111
111
 
112
112
 
@@ -244,8 +244,8 @@ To cite a particular version of the fmdtools, you may use:
244
244
  author = {{NASA}},
245
245
  title = {fmdtools},
246
246
  url = {https://github.com/nasa/fmdtools},
247
- version = {2.1.2},
248
- date = {2025-5-19},
247
+ version = {2.1.3},
248
+ date = {2025-6-6},
249
249
  }
250
250
  ```
251
251
 
@@ -227,6 +227,7 @@ class DestState(State):
227
227
  class Dest(GeomPoint):
228
228
  """Start/end/point."""
229
229
 
230
+ __slots__ = ()
230
231
  container_p = DestParam
231
232
  container_s = DestState
232
233
 
@@ -252,9 +253,11 @@ class GroundGeomArch(GeomArchitecture):
252
253
 
253
254
 
254
255
  class GroundState(State):
255
- """state defining if rover is in bound"""
256
+ """State defining if rover is in bounds."""
257
+
256
258
  in_bound: bool = True
257
259
 
260
+
258
261
  class Ground(Environment):
259
262
  """Ground environment of the rover."""
260
263
 
@@ -272,7 +275,8 @@ class Ground(Environment):
272
275
 
273
276
  def end_dist(self, pos_state):
274
277
  """Calculate minimum distance between the end point and the rover."""
275
- return self.ga.points['end'].on.distance(Point(pos_state.get("x", "y")))
278
+ on_shape = self.ga.points['end'].get_shape('on')
279
+ return on_shape.distance(Point(pos_state.get("x", "y")))
276
280
 
277
281
  def max_line_dist(self, pos_hist):
278
282
  """Calculate the maximum distance the rover has deviated from its path."""
@@ -280,7 +284,7 @@ class Ground(Environment):
280
284
  yhist = pos_hist.y
281
285
  max_dist = 0.0
282
286
  for i, x in enumerate(xhist):
283
- dist = self.ga.lines['line'].shape.distance(Point(x, yhist[i]))
287
+ dist = self.ga.lines['line'].get_shape().distance(Point(x, yhist[i]))
284
288
  if dist > max_dist:
285
289
  max_dist = dist
286
290
  return max_dist
@@ -289,6 +293,7 @@ class Ground(Environment):
289
293
  class PathLine(GeomLine):
290
294
  """Rover Line."""
291
295
 
296
+ __slots__ = ()
292
297
  container_p = PathParam
293
298
 
294
299
 
@@ -22,6 +22,6 @@ import fmdtools.sim as sim
22
22
  import fmdtools.analyze as analyze
23
23
 
24
24
  __all__ = ['analyze', 'sim', 'define']
25
- __version__ = '2.1.2'
25
+ __version__ = '2.1.3'
26
26
  __copyright__ = "Copyright 2024 United States Government"
27
27
  __license__ = "Apache-2.0"
@@ -268,7 +268,7 @@ class History(Result):
268
268
  raise Exception(obj_str + "Value too large to represent: "
269
269
  + att + "=" + str(val)) from e
270
270
 
271
- def cut(self, end_ind=None, start_ind=None, newcopy=False):
271
+ def cut(self, end_ind=None, start_ind=None, newcopy=False, reshape_to=None):
272
272
  """
273
273
  Cut the history to a given index.
274
274
 
@@ -283,6 +283,9 @@ class History(Result):
283
283
  newcopy: bool, optional
284
284
  Tells whether to creat a new history variable with the information what was
285
285
  cut or cut the original history variable. Default is False.
286
+ reshape_to: int, optional
287
+ Size of array to cut from, if different from current size. Default is None,
288
+ which does not reshape the array.
286
289
 
287
290
  Examples
288
291
  --------
@@ -306,15 +309,20 @@ class History(Result):
306
309
  hist = self
307
310
  for name, att in hist.items():
308
311
  if isinstance(att, History):
309
- hist[name] = hist[name].cut(end_ind, start_ind, newcopy=False)
312
+ hist[name] = hist[name].cut(end_ind, start_ind,
313
+ newcopy=False, reshape_to=reshape_to)
310
314
  else:
311
315
  try:
316
+ if reshape_to and reshape_to != att.shape[0]:
317
+ new_shape = tuple([reshape_to, *att.shape[1:]])
318
+ att = np.resize(att, new_shape)
312
319
  if end_ind is None:
313
- hist[name] = att[start_ind:]
314
- elif start_ind is None:
315
- hist[name] = att[:end_ind+1]
320
+ ei = len(att)
316
321
  else:
317
- hist[name] = att[start_ind:end_ind+1]
322
+ ei = end_ind+1
323
+ if start_ind is None:
324
+ start_ind = 0
325
+ hist[name] = att[start_ind:ei]
318
326
  except TypeError as e:
319
327
  raise Exception("Invalid history for name " + name +
320
328
  " in history "+str(hist.data)) from e
@@ -40,7 +40,7 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the
40
40
  specific language governing permissions and limitations under the License.
41
41
  """
42
42
 
43
- from fmdtools.define.base import t_key, nest_dict, is_numeric, is_bool, nan_to_x
43
+ from fmdtools.define.base import t_key, nest_dict, is_numeric, is_bool
44
44
  from fmdtools.analyze.common import to_include_keys
45
45
  from fmdtools.analyze.common import calc_metric, calc_metric_ci, join_key
46
46
  from fmdtools.analyze.common import get_sub_include, unpack_plot_values
@@ -167,6 +167,8 @@ class Architecture(Simulable):
167
167
  # sync rands, if present
168
168
  if hasattr(self, 'r') and hasattr(objclass, "container_r"):
169
169
  kwargs = {**{'r': {"seed": self.r.seed}}, **kwargs}
170
+ if hasattr(objclass, "container_sp"):
171
+ kwargs = {**{'sp': self.sp.asdict()}, **kwargs}
170
172
  obj = init_obj(name=name, objclass=objclass, track=track,
171
173
  as_copy=as_copy, root=self.get_full_name()+"."+flex_role,
172
174
  **kwargs)
@@ -241,14 +243,10 @@ class Architecture(Simulable):
241
243
  Flows, dicts for non-default values to p, s, etc.
242
244
  """
243
245
  flows = self.get_flows(*flownames, all_if_empty=False)
244
- fkwargs = {**{'sp': self.sp.asdict()}, **kwargs}
245
246
  if not self.sp.use_local:
246
- fkwargs = {**{'t': {'dt': self.sp.dt}}, **fkwargs}
247
+ kwargs = {**{'t': {'dt': self.sp.dt}}, **kwargs}
247
248
 
248
- self.add_flex_role_obj(flex_role, name, objclass=simclass, flows=flows, **fkwargs)
249
-
250
- # add modes to overall mode dict
251
- sim = self.get_flex_role_objs(flex_role)[name]
249
+ self.add_flex_role_obj(flex_role, name, objclass=simclass, flows=flows, **kwargs)
252
250
 
253
251
  def init_architecture(self, *args, **kwargs):
254
252
  """Use to initialize architecture."""
@@ -51,13 +51,16 @@ class GeomArchitecture(Architecture):
51
51
 
52
52
  >>> ega = ExGeomArch()
53
53
  >>> ega.geoms()['ex_point'].s
54
- ExGeomState(occupied=False)
54
+ ExGeomState(occupied=False, buffer_around=1.0)
55
55
  >>> ega.h
56
56
  points.ex_point.s.occupied: array(101)
57
+ points.ex_point.s.buffer_around: array(101)
57
58
  lines.ex_line.s.occupied: array(101)
59
+ lines.ex_line.s.buffer_around: array(101)
58
60
  polys.ex_poly.s.occupied: array(101)
61
+ polys.ex_poly.s.buffer_around: array(101)
59
62
  >>> ega.return_mutables()
60
- ((False,), (False,), (False,), (-0.1, 0, 0.0, 1))
63
+ ((False, 1.0), (False, 1.0), (False, 1.0), (-0.1, 0, 0.0, 1))
61
64
  """
62
65
 
63
66
  container_p = Parameter
@@ -153,11 +156,11 @@ class GeomArchitecture(Architecture):
153
156
  --------
154
157
  >>> exga = ExGeomArch()
155
158
  >>> exga.all_at(1.0, 1.0)
156
- {'ex_point': ['shape', 'on'], 'ex_line': ['shape', 'on'], 'ex_poly': ['shape']}
159
+ {'ex_point': ['shape', 'on', 'around'], 'ex_line': ['shape', 'on', 'around'], 'ex_poly': ['shape', 'around']}
157
160
  >>> exga.all_at(0.0, 0.0)
158
- {'ex_line': ['shape', 'on'], 'ex_poly': ['shape']}
161
+ {'ex_line': ['shape', 'on', 'around'], 'ex_poly': ['shape', 'around']}
159
162
  >>> exga.all_at(0.4, 0.3)
160
- {'ex_point': ['on'], 'ex_line': ['on']}
163
+ {'ex_point': ['on', 'around'], 'ex_line': ['on', 'around'], 'ex_poly': ['around']}
161
164
  """
162
165
  all_at = {}
163
166
  for geomname, geom in self.geoms().items():
@@ -205,6 +208,31 @@ class GeomArchitecture(Architecture):
205
208
  fig, ax = self.geoms()[geomname].show(ax=ax, fig=fig, z=z, **local_kwargs)
206
209
  return fig, ax
207
210
 
211
+ def show_from(self, hist, t, **kwargs):
212
+ """
213
+ Show the GeomArch at the given time in the provided history.
214
+
215
+ Parameters
216
+ ----------
217
+ hist : History
218
+ History of states for the GeomArchitecture.
219
+ t : int
220
+ Time in the history to show the state of the GeomArchitecture at.
221
+ **kwargs : kwargs
222
+ Keyword arguments to GeomArchitecture.show().
223
+
224
+ Returns
225
+ -------
226
+ fig : figure
227
+ Matplotlib figure object
228
+ ax : axis
229
+ Corresponding matplotlib axis
230
+ """
231
+ for flex_role in self.flexible_roles:
232
+ for geomname, geom in self.get_flex_role_objs(flex_role).items():
233
+ geom.assign_from(hist.get(flex_role+"."+geomname), t)
234
+ return self.show(**kwargs)
235
+
208
236
 
209
237
  class ExGeomArch(GeomArchitecture):
210
238
  """Example Geometric Architecture for testing etc."""
@@ -22,6 +22,7 @@ from fmdtools.analyze.common import get_sub_include
22
22
  from fmdtools.analyze.history import History
23
23
 
24
24
  from recordclass import dataobject, astuple, asdict
25
+ from collections import UserDict
25
26
  import copy
26
27
  import pickle
27
28
  import numpy as np
@@ -218,13 +219,13 @@ class BaseContainer(dataobject, mapping=True, iterable=True, copy_default=True):
218
219
  else:
219
220
  # if kwarg states provided, assign keys to values
220
221
  fielddict = {k: getattr(obj, v) for k, v in fielddict.items()}
221
- elif isinstance(obj, dict):
222
+ elif isinstance(obj, dict) or isinstance(obj, UserDict):
222
223
  if not fielddict:
223
224
  fielddict = {s: obj[s] for s in fields if s in obj}
224
225
  else:
225
226
  fielddict = {k: obj[v] for k, v in fielddict.items() if v in obj}
226
227
  else:
227
- raise Exception("Invalid type to assign from: "+obj.__class__)
228
+ raise Exception("Invalid type to assign from: "+str(obj.__class__))
228
229
  return fielddict
229
230
 
230
231
  def assign(self, obj, *fields, as_copy=True, **fielddict):
@@ -19,6 +19,7 @@ specific language governing permissions and limitations under the License.
19
19
 
20
20
  from fmdtools.define.container.rand import Rand
21
21
  from fmdtools.define.flow.commsflow import CommsFlow
22
+ from fmdtools.define.block.base import SimParam
22
23
  from fmdtools.define.object.coords import Coords, ExampleCoords
23
24
  from fmdtools.define.architecture.geom import GeomArchitecture, ExGeomArch
24
25
 
@@ -51,32 +52,37 @@ class Environment(CommsFlow):
51
52
  >>> env.create_hist([1.0])
52
53
  c.r.probdens: array(1)
53
54
  c.st: array(1)
54
- ga.points.ex_point: array(1)
55
- ga.lines.ex_line: array(1)
56
- ga.polys.ex_poly: array(1)
55
+ ga.points.ex_point.s.occupied: array(1)
56
+ ga.points.ex_point.s.buffer_around: array(1)
57
+ ga.lines.ex_line.s.occupied: array(1)
58
+ ga.lines.ex_line.s.buffer_around: array(1)
59
+ ga.polys.ex_poly.s.occupied: array(1)
60
+ ga.polys.ex_poly.s.buffer_around: array(1)
57
61
  """
58
62
 
59
63
  slots = ["c", "r", "ga"]
60
64
  container_r = Rand
65
+ container_sp = SimParam
66
+ default_sp = {}
61
67
  coords_c = Coords
62
68
  arch_ga = GeomArchitecture
63
69
  roletypes = ['container', 'coords', 'arch']
64
70
  default_track = ('s', 'i', 'c', 'ga')
65
71
  all_possible = ('s', 'i', 'c', 'r', 'ga')
66
72
 
67
- def __init__(self, name='', root='', glob=[], p={}, s={}, r={}, c={}, ga={},
73
+ def __init__(self, name='', root='', glob=[], p={}, s={}, r={}, sp={}, c={}, ga={},
68
74
  track='default'):
69
- super().__init__(name=name, root=root, glob=glob, p=p, s=s, track=track)
75
+ super().__init__(name=name, root=root, glob=glob, p=p, s=s, sp=sp, track=track)
70
76
  # NOTE: p and s also init here because if not, they are overritten
71
77
  # may need to change in the future
72
- self.init_roletypes('container', "coords", "arch", r=r, p=p, s=s)
78
+ self.init_roletypes('container', "coords", "arch", r=r, p=p, s=s, sp=sp)
73
79
  if 'p' not in c and getattr(self.coords_c, 'container_p', None) == getattr(self, 'container_p', None):
74
80
  c = {**c, 'p': p}
75
81
  if 'p' not in ga and getattr(self.arch_ga, 'container_p', None) == getattr(self, 'container_p', None):
76
82
  ga = {**ga, 'p': p}
77
83
  r_kwargs = {'run_stochastic': self.r.run_stochastic, 'seed': self.r.seed}
78
84
  c = {**{'r': r_kwargs}, **c}
79
- ga = {**{'r': r_kwargs}, **ga}
85
+ ga = {**{'r': r_kwargs, 'sp': self.sp}, **ga}
80
86
  self.init_roletypes('coords', 'arch', c=c, ga=ga)
81
87
 
82
88
  def base_type(self):
@@ -115,7 +121,8 @@ class Environment(CommsFlow):
115
121
  cop = super().copy(glob=glob, p=p, s=s)
116
122
  cop.r.assign(self.r)
117
123
  cop.c = self.c.copy()
118
- cop.ga = self.ga.copy()
124
+ cop.sp = self.sp
125
+ cop.ga = self.ga.copy(sp=self.sp)
119
126
  if hasattr(self, 'h'):
120
127
  cop.h = self.h.copy()
121
128
  return cop
@@ -644,7 +644,10 @@ class BaseObject(object):
644
644
  hist : History
645
645
  A history of each recorded block property over the given timerange.
646
646
  """
647
- if hasattr(self, 'h') and self.h and len([*self.h.values()][0]) == len(timerange):
647
+ if hasattr(self, 'h') and self.h:
648
+ len_timerange = len(timerange)
649
+ if len_timerange != len([*self.h.values()][0]):
650
+ self.h = self.h.cut(reshape_to=len_timerange)
648
651
  return self.h
649
652
  else:
650
653
  track = self.track
@@ -48,19 +48,18 @@ class Geom(BaseObject):
48
48
  """
49
49
  Base class for defining geometries.
50
50
 
51
- Geometry objects are essentially wrappers around various shapely classes used to
51
+ Geometry objects are essentially interfaces for various shapely classes used to
52
52
  convey the spacial properties of a model.
53
53
 
54
- ...
55
-
56
54
  Roles
57
55
  -----
58
56
  s : State
59
- State defining mutable geom properties (e.g., allocation, color, etc)
57
+ State defining mutable properties (e.g., allocations, shapely class inputs)
60
58
  p : Param
61
- Parameter defining immutable geom characteristics (e.g., shapely inputs, buffer)
59
+ Parameter defining immutable properties (e.g., shapely inputs, buffer)
62
60
  """
63
61
 
62
+ __slots__ = ('p', 's', 'shapenames', )
64
63
  container_s = State
65
64
  container_p = Parameter
66
65
  default_track = ['s']
@@ -70,22 +69,53 @@ class Geom(BaseObject):
70
69
 
71
70
  def __init__(self, *args, s={}, p={}, track='default', **kwargs):
72
71
  super().__init__(s=s, p=p, track=[], **kwargs)
73
- self.shape = self.shapely_class(*self.p.as_args())
74
- # set buffer shapes
75
- self.init_role_dict("buffer")
76
- for buffer, rad in self.buffers.items():
77
- setattr(self, buffer, self.shape.buffer(rad))
72
+ self.shapenames = ['shape',
73
+ *self.p.get_pref_attrs('buffer'),
74
+ *self.s.get_pref_attrs('buffer')]
78
75
  self.init_track(track=track)
79
76
 
80
- def at(self, pt, buffername='shape'):
77
+ def get_shape(self, shapename='shape'):
81
78
  """
82
- Determine whether the point x, y is within the buffer 'buffername'.
79
+ Get the shapely object defining the class and buffers.
80
+
81
+ Parameters
82
+ ----------
83
+ shapename : str, optional
84
+ Name of the shape. The default is 'shape', which produces the base shape,
85
+ other names return the buffer shapes for that shape.
86
+
87
+ Returns
88
+ -------
89
+ shapely.geometry
90
+ Shapely object for the shape.
91
+ """
92
+ shape = self.shapely_class(*self.get_args())
93
+ if shapename == 'shape':
94
+ return shape
95
+ else:
96
+ argname = 'buffer_'+shapename
97
+ if hasattr(self.p, argname):
98
+ arg = self.p[argname]
99
+ elif hasattr(self.s, argname):
100
+ arg = self.s[argname]
101
+ else:
102
+ raise Exception(argname+" not in "+"Geom's "+str(self.s.__class__)
103
+ + " or "+str(self.p.__class__))
104
+ return shape.buffer(arg)
105
+
106
+ def get_paramstates(self):
107
+ """Create dict of parameters and states."""
108
+ return {**self.p.asdict(), **self.s.asdict()}
109
+
110
+ def at(self, pt, shapename='shape'):
111
+ """
112
+ Determine whether the point x, y is within the buffer 'shapename'.
83
113
 
84
114
  Parameters
85
115
  ----------
86
116
  *pt : tuple
87
117
  Locations of x, y, z coordinates.
88
- buffername : str
118
+ shapename : str
89
119
  Name of buffer property
90
120
 
91
121
  Returns
@@ -93,8 +123,8 @@ class Geom(BaseObject):
93
123
  at : bool
94
124
  Whether x,y is within the buffer.
95
125
  """
96
- buffer = getattr(self, buffername)
97
- return buffer.covers(Point(*pt))
126
+ shape = self.get_shape(shapename)
127
+ return shape.covers(Point(*pt))
98
128
 
99
129
  def all_at(self, *pt):
100
130
  """
@@ -114,15 +144,14 @@ class Geom(BaseObject):
114
144
  --------
115
145
  >>> exp = ExPoint()
116
146
  >>> exp.all_at(1.0, 1.0)
117
- ['shape', 'on']
147
+ ['shape', 'on', 'around']
118
148
  >>> exp.all_at(1.0, 0.1)
119
- ['on']
149
+ ['on', 'around']
120
150
  >>> exp.all_at(0.0, 0.0)
121
151
  []
122
152
  """
123
- buffernames = ['shape', *self.buffers]
124
153
  all_at = []
125
- for bname in buffernames:
154
+ for bname in self.shapenames:
126
155
  if self.at(pt, bname):
127
156
  all_at.append(bname)
128
157
  return all_at
@@ -140,7 +169,7 @@ class Geom(BaseObject):
140
169
  def return_mutables(self):
141
170
  return astuple(self.s)
142
171
 
143
- def vect_to_shape(self, pt, buffername='shape'):
172
+ def vect_to_shape(self, pt, shapename='shape'):
144
173
  """
145
174
  Gets the vector (x, y) to a given shape.
146
175
 
@@ -148,7 +177,7 @@ class Geom(BaseObject):
148
177
  ----------
149
178
  pt : tuple/list
150
179
  Point to get vector from
151
- buffername : str
180
+ shapename : str
152
181
  Name of shape/buffer. Default is 'shape'.
153
182
 
154
183
  Examples
@@ -164,13 +193,13 @@ class Geom(BaseObject):
164
193
  array([[0.],
165
194
  [0.]])
166
195
  """
167
- buffer = getattr(self, buffername)
196
+ shape = self.get_shape(shapename)
168
197
  geom_pt = Point(pt)
169
- geom_c, close_pt = nearest_points(geom_pt, buffer)
198
+ geom_c, close_pt = nearest_points(geom_pt, shape)
170
199
  vect_to_shape = np.array(close_pt.xy) - np.array(geom_pt.xy)
171
200
  return vect_to_shape
172
201
 
173
- def vect_at_shape(self, pt, buffername='shape', dist_forward=0.1):
202
+ def vect_at_shape(self, pt, shapename='shape', dist_forward=0.1):
174
203
  """
175
204
  Get the vector (x, y) at a given shape (e.g., direction of a line at pt).
176
205
 
@@ -178,7 +207,7 @@ class Geom(BaseObject):
178
207
  ----------
179
208
  pt : tuple/lost
180
209
  Point closest to shape
181
- buffername : str
210
+ shapename : str
182
211
  Name of shape/buffer. Default is 'shape'.
183
212
  dist_forward : float
184
213
  Distance forward along line segment to project. Give a negative number to
@@ -194,11 +223,11 @@ class Geom(BaseObject):
194
223
  array([[ 0. ],
195
224
  [-0.1]])
196
225
  """
197
- buffer = getattr(self, buffername)
226
+ shape = self.get_shape(shapename)
198
227
  geom_pt = Point(pt)
199
- geom_c, close_pt = nearest_points(geom_pt, buffer)
200
- line_dist = buffer.line_locate_point(geom_pt)
201
- next_pt = buffer.line_interpolate_point(line_dist + dist_forward)
228
+ geom_c, close_pt = nearest_points(geom_pt, shape)
229
+ line_dist = shape.line_locate_point(geom_pt)
230
+ next_pt = shape.line_interpolate_point(line_dist + dist_forward)
202
231
  vect_at_shape = np.array(next_pt.xy) - np.array(close_pt.xy)
203
232
  return vect_at_shape
204
233
 
@@ -236,33 +265,37 @@ class Geom(BaseObject):
236
265
  if not ax:
237
266
  fig, ax = setup_plot(z=z, figsize=figsize)
238
267
  if 'all' in shapes:
239
- shapes = {'shape': {}, **{v: {} for v in self.buffers}}
268
+ shapes = {s: {} for s in self.shapenames}
240
269
  if type(z) in (int, float):
241
270
  plot_kwargs = {'zs': z, 'zdir': 'z', **kwargs}
242
271
  else:
243
272
  plot_kwargs = kwargs
244
- for shape, shape_kwargs in shapes.items():
273
+ for shapename, shape_kwargs in shapes.items():
245
274
  if geomlabel:
246
- shape_label = geomlabel + "." + shape
275
+ shape_label = geomlabel + "." + shapename
247
276
  else:
248
- shape_label = shape
277
+ shape_label = shapename
249
278
  local_kwargs = {**plot_kwargs, 'label': shape_label, **shape_kwargs}
250
- shap = getattr(self, shape)
251
- if isinstance(shap, Point):
252
- ax.scatter(shap.x, shap.y, **local_kwargs)
253
- elif isinstance(shap, LineString):
254
- linecoords = np.array([*shap.coords])
279
+ shape = self.get_shape(shapename)
280
+ if isinstance(shape, Point):
281
+ ax.scatter(shape.x, shape.y, **local_kwargs)
282
+ elif isinstance(shape, LineString):
283
+ linecoords = np.array([*shape.coords])
255
284
  ax.plot(linecoords[:, 0], linecoords[:, 1], **local_kwargs)
256
- elif isinstance(shap, Polygon):
257
- ax.plot(*shap.exterior.xy, **local_kwargs)
285
+ elif isinstance(shape, Polygon):
286
+ ax.plot(*shape.exterior.xy, **local_kwargs)
258
287
  ax.axis('equal')
259
288
  consolidate_legend(ax, **kwargs)
260
289
  return fig, ax
261
290
 
291
+ def assign_from(self, hist, t, *states):
292
+ """Update Geom state from a given history and time."""
293
+ self.s.assign(hist.s.get_slice(t), *states)
294
+
262
295
 
263
296
  class PointParam(Parameter):
264
297
  """
265
- Parameter defining points. May be extended with buffers.
298
+ Parameter defining points. Extend with 'buffer_att' to create buffer shapes.
266
299
 
267
300
  Fields
268
301
  ------
@@ -271,6 +304,7 @@ class PointParam(Parameter):
271
304
  y : float
272
305
  y-location of point.
273
306
 
307
+
274
308
  Examples
275
309
  --------
276
310
  a point with center at 1.0, 1.0 and radius of 1.0 defining whether something
@@ -281,18 +315,13 @@ class PointParam(Parameter):
281
315
  ... y: float = 1.0
282
316
  ... buffer_on: float = 1.0
283
317
 
284
- >>> expa = ExPointParam()
285
- >>> expa.as_args()
286
- ([1.0, 1.0],)
318
+ >>> ExPointParam()
319
+ ExPointParam(x=1.0, y=1.0, buffer_on=1.0)
287
320
  """
288
321
 
289
322
  x: ClassVar[float] = 0.0
290
323
  y: ClassVar[float] = 0.0
291
324
 
292
- def as_args(self):
293
- """Create arguments for shapely Point class based on fields."""
294
- return ([self[i] for i in ['x', 'y', 'z'] if i in dir(self)], )
295
-
296
325
 
297
326
  class GeomPoint(Geom):
298
327
  """
@@ -304,6 +333,7 @@ class GeomPoint(Geom):
304
333
  Examples
305
334
  --------
306
335
  >>> class ExPoint(GeomPoint):
336
+ ... __slots__ = ()
307
337
  ... container_p = ExPointParam
308
338
  ... container_s = ExGeomState
309
339
  >>> exp = ExPoint()
@@ -314,20 +344,42 @@ class GeomPoint(Geom):
314
344
  >>> exp.at((1.0, 0.1), "on")
315
345
  True
316
346
 
317
- Additionally, note the underlying shapely attribute at .shape:
347
+ Additionally, note the underlying shapely attributes returned by get_shape:
318
348
 
319
- >>> type(exp.shape)
349
+ >>> type(exp.get_shape())
320
350
  <class 'shapely.geometry.point.Point'>
321
351
 
322
352
  as well as the buffer (on):
323
353
 
324
- >>> type(exp.on)
354
+ >>> type(exp.get_shape('on'))
325
355
  <class 'shapely.geometry.polygon.Polygon'>
356
+
357
+ Finally, note how geom characteristics defined as states can change:
358
+
359
+ >>> exp.at((3.0, 1.0), 'around') # outside the default area of `around` buffer
360
+ False
361
+ >>> exp.s.buffer_around = 2.0 # set to a larger radius to capture the point
362
+ >>> exp.at((3.0, 1.0), 'around')
363
+ True
326
364
  """
327
365
 
366
+ __slots__ = ()
328
367
  container_p = PointParam
329
368
  shapely_class = Point
330
369
 
370
+ def get_args(self):
371
+ """
372
+ Get shape arguments from Point parameter/state.
373
+
374
+ Examples
375
+ --------
376
+ >>> exp = ExPoint()
377
+ >>> exp.get_args()
378
+ (1.0, 1.0)
379
+ """
380
+ combodict = self.get_paramstates()
381
+ return tuple([combodict[i] for i in ['x', 'y', 'z'] if i in combodict])
382
+
331
383
 
332
384
  class ExGeomState(State):
333
385
  """
@@ -337,6 +389,7 @@ class ExGeomState(State):
337
389
  """
338
390
 
339
391
  occupied: bool = False
392
+ buffer_around: float = 1.0
340
393
 
341
394
 
342
395
  class ExPointParam(PointParam):
@@ -354,6 +407,7 @@ class ExPointParam(PointParam):
354
407
  class ExPoint(GeomPoint):
355
408
  """Example point for testing."""
356
409
 
410
+ __slots__ = ()
357
411
  container_p = ExPointParam
358
412
  container_s = ExGeomState
359
413
 
@@ -377,17 +431,12 @@ class LineParam(Parameter):
377
431
  >>> class ExLineParam(LineParam):
378
432
  ... xys: tuple = ((0.0, 0.0), (1.0, 1.0))
379
433
  ... buffer_on: float = 1.0
380
- >>> exlp = ExLineParam()
381
- >>> exlp.as_args()
382
- (((0.0, 0.0), (1.0, 1.0)),)
434
+ >>> ExLineParam()
435
+ ExLineParam(xys=((0.0, 0.0), (1.0, 1.0)), buffer_on=1.0)
383
436
  """
384
437
 
385
438
  xys: ClassVar[tuple] = ()
386
439
 
387
- def as_args(self):
388
- """Create arguments for shapely LineString class based on fields."""
389
- return (self.xys, )
390
-
391
440
 
392
441
  class ExLineParam(LineParam):
393
442
  """Example parameter defining a line with a given buffer 'on'."""
@@ -405,6 +454,7 @@ class GeomLine(Geom):
405
454
  Examples
406
455
  --------
407
456
  >>> class ExLine(GeomLine):
457
+ ... __slots__ = ()
408
458
  ... container_p = ExLineParam
409
459
  ... container_s = ExGeomState
410
460
  >>> exp = ExLine()
@@ -415,24 +465,39 @@ class GeomLine(Geom):
415
465
  >>> exp.at((2.0, 2.0), "on")
416
466
  False
417
467
 
418
- Additionally, note the underlying shapely attribute at .shape:
468
+ Additionally, note the underlying shapely objects returned by get_shape():
419
469
 
420
- >>> type(exp.shape)
470
+ >>> type(exp.get_shape())
421
471
  <class 'shapely.geometry.linestring.LineString'>
422
472
 
423
473
  As well as the buffer (on):
424
474
 
425
- >>> type(exp.on)
475
+ >>> type(exp.get_shape('on'))
426
476
  <class 'shapely.geometry.polygon.Polygon'>
427
477
  """
428
478
 
479
+ __slots__ = ()
429
480
  container_p = LineParam
430
481
  shapely_class = LineString
431
482
 
483
+ def get_args(self):
484
+ """
485
+ Create arguments for shapely LineString class based on fields.
486
+
487
+ Examples
488
+ --------
489
+ >>> exl = ExLine()
490
+ >>> exl.get_args()
491
+ (((0.0, 0.0), (1.0, 1.0)),)
492
+ """
493
+ combodict = self.get_paramstates()
494
+ return (combodict['xys'], )
495
+
432
496
 
433
497
  class ExLine(GeomLine):
434
498
  """Example GeomLine to use in testing."""
435
499
 
500
+ __slots__ = ()
436
501
  container_p = ExLineParam
437
502
  container_s = ExGeomState
438
503
 
@@ -441,8 +506,6 @@ class PolyParam(Parameter):
441
506
  """
442
507
  Parameter defining polygons. May be extended with buffers.
443
508
 
444
- ...
445
-
446
509
  Fields
447
510
  ------
448
511
  shell : tuple
@@ -455,20 +518,16 @@ class PolyParam(Parameter):
455
518
  The following PolyParam defines a hollow right triangle:
456
519
 
457
520
  >>> class ExPolyParam(PolyParam):
458
- ... shell: tuple = ((0.0, 0.0), (1.0, 0.0), (1.0, 1.0))
459
- ... holes: tuple = (((0.3, 0.2), (0.6, 0.2), (0.6, 0.5)), )
460
- >>> expp = ExPolyParam()
461
- >>> expp.as_args()
462
- (((0.0, 0.0), (1.0, 0.0), (1.0, 1.0)), (((0.3, 0.2), (0.6, 0.2), (0.6, 0.5)),))
521
+ ... __slots__ = ()
522
+ ... shell: tuple = ((0.0, 0.0), (1.0, 0.0), (1.0, 1.0))
523
+ ... holes: tuple = (((0.3, 0.2), (0.6, 0.2), (0.6, 0.5)), )
524
+ >>> ExPolyParam()
525
+ ExPolyParam(shell=((0.0, 0.0), (1.0, 0.0), (1.0, 1.0)), holes=(((0.3, 0.2), (0.6, 0.2), (0.6, 0.5)),))
463
526
  """
464
527
 
465
528
  shell: ClassVar[tuple] = ()
466
529
  holes: ClassVar[tuple] = ()
467
530
 
468
- def as_args(self):
469
- """Create arguments for shapely Polygon class based on fields."""
470
- return self.shell, self.holes
471
-
472
531
 
473
532
  class ExPolyParam(PolyParam):
474
533
  """Example polygon parameter defining a basic triangle for use in testing."""
@@ -487,6 +546,7 @@ class GeomPoly(Geom):
487
546
  Examples
488
547
  --------
489
548
  >>> class ExPoly(GeomPoly):
549
+ ... __slots__ = ()
490
550
  ... container_p = ExPolyParam
491
551
  ... container_s = ExGeomState
492
552
  >>> egp = ExPoly()
@@ -495,19 +555,32 @@ class GeomPoly(Geom):
495
555
  >>> egp.at((0.4, 0.3))
496
556
  False
497
557
 
498
- Additionally, note the underlying shapely attribute at .shape:
558
+ Additionally, note the underlying shapely objects returned by get_shape():
499
559
 
500
- >>> type(egp.shape)
560
+ >>> type(egp.get_shape())
501
561
  <class 'shapely.geometry.polygon.Polygon'>
502
562
  """
503
563
 
564
+ __slots__ = ()
504
565
  container_p = PolyParam
505
566
  shapely_class = Polygon
506
567
 
568
+ def get_args(self):
569
+ """Create arguments for shapely Polygon class based on fields.
570
+
571
+ Examples
572
+ --------
573
+ >>> ExPoly().get_args()
574
+ (((0.0, 0.0), (1.0, 0.0), (1.0, 1.0)), (((0.3, 0.2), (0.6, 0.2), (0.6, 0.5)),))
575
+ """
576
+ combodict = self.get_paramstates()
577
+ return combodict['shell'], combodict['holes']
578
+
507
579
 
508
580
  class ExPoly(GeomPoly):
509
581
  """Example Polygon for use in testing."""
510
582
 
583
+ __slots__ = ()
511
584
  container_p = ExPolyParam
512
585
  container_s = ExGeomState
513
586
 
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmdtools
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: System resilience modelling, simulation, and assessment in Python
5
5
  Author: fmdtools Developers
6
6
  Maintainer-email: Daniel Hulse <daniel.e.hulse@nasa.gov>
7
- License: fmdtools version 2.1.2
7
+ License: fmdtools version 2.1.3
8
8
  ---------------------------
9
9
  Apache License
10
10
  Version 2.0, January 2004
@@ -367,7 +367,7 @@ This may then be installed using:
367
367
  Note that this version reflects the latest version on the ``main`` git branch along with any local changes. To use a particular version of ``fmdtools`` you can checkout the tag corresponding to the version you want, e.g.:
368
368
 
369
369
  ```
370
- git checkout v2.1.2
370
+ git checkout v2.1.3
371
371
  ```
372
372
 
373
373
 
@@ -505,8 +505,8 @@ To cite a particular version of the fmdtools, you may use:
505
505
  author = {{NASA}},
506
506
  title = {fmdtools},
507
507
  url = {https://github.com/nasa/fmdtools},
508
- version = {2.1.2},
509
- date = {2025-5-19},
508
+ version = {2.1.3},
509
+ date = {2025-6-6},
510
510
  }
511
511
  ```
512
512
 
@@ -18,7 +18,7 @@ classifiers = [
18
18
  description = "System resilience modelling, simulation, and assessment in Python"
19
19
  license = {file="LICENSE"}
20
20
  name = "fmdtools"
21
- version="2.1.2"
21
+ version="2.1.3"
22
22
  readme = "README.md"
23
23
  requires-python = ">=3.10"
24
24
 
File without changes
File without changes
File without changes
File without changes