fmdtools 2.2.1__tar.gz → 2.2.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.2.1 → fmdtools-2.2.3}/LICENSE +1 -1
  2. {fmdtools-2.2.1/fmdtools.egg-info → fmdtools-2.2.3}/PKG-INFO +7 -7
  3. {fmdtools-2.2.1 → fmdtools-2.2.3}/README.md +4 -4
  4. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/eps/eps.py +6 -5
  5. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/eps/test_eps.py +1 -1
  6. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multiflow_demo/multiflow_demo.py +6 -4
  7. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multirotor/drone_mdl_dynamic.py +11 -10
  8. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multirotor/drone_mdl_hierarchical.py +163 -115
  9. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multirotor/drone_mdl_rural.py +62 -52
  10. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multirotor/drone_mdl_static.py +25 -21
  11. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multirotor/drone_mdl_urban.py +9 -9
  12. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multirotor/opt_drone_rural.py +1 -1
  13. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/ex_pump.py +49 -35
  14. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/jointfaults_ex.py +3 -3
  15. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/parallelism_methods.py +9 -9
  16. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/pump_stochastic.py +6 -6
  17. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/test_pump_stochastic.py +2 -2
  18. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/rover/optimization/search_rover.py +1 -1
  19. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/rover/rover_degradation.py +26 -26
  20. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/rover/rover_model.py +53 -53
  21. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/rover/rover_model_human.py +9 -7
  22. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/tank/tank_model.py +19 -17
  23. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/tank/tank_optimization_model.py +6 -6
  24. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/taxiway/asset.py +2 -2
  25. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/taxiway/common.py +17 -17
  26. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/taxiway/model.py +3 -3
  27. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/__init__.py +1 -1
  28. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/common.py +2 -2
  29. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/phases.py +7 -7
  30. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/result.py +9 -5
  31. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/tabulate.py +47 -31
  32. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/architecture/action.py +2 -2
  33. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/architecture/base.py +21 -5
  34. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/architecture/component.py +6 -6
  35. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/architecture/function.py +11 -11
  36. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/architecture/geom.py +2 -2
  37. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/base.py +3 -1
  38. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/block/action.py +6 -5
  39. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/block/base.py +6 -5
  40. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/block/component.py +5 -5
  41. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/block/function.py +6 -6
  42. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/container/base.py +9 -3
  43. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/container/mode.py +5 -5
  44. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/container/parameter.py +9 -9
  45. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/container/rand.py +18 -10
  46. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/container/state.py +5 -3
  47. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/container/time.py +9 -8
  48. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/environment.py +26 -6
  49. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/flow/base.py +4 -4
  50. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/flow/commsflow.py +12 -10
  51. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/flow/multiflow.py +21 -14
  52. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/object/base.py +15 -10
  53. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/object/coords.py +5 -4
  54. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/object/geom.py +1 -1
  55. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/sim/propagate.py +20 -10
  56. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/sim/sample.py +2 -2
  57. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/sim/search.py +4 -4
  58. {fmdtools-2.2.1 → fmdtools-2.2.3/fmdtools.egg-info}/PKG-INFO +7 -7
  59. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools.egg-info/requires.txt +1 -1
  60. {fmdtools-2.2.1 → fmdtools-2.2.3}/pyproject.toml +2 -2
  61. {fmdtools-2.2.1 → fmdtools-2.2.3}/tests/test_graph.py +1 -1
  62. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/__init__.py +0 -0
  63. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/asg_demo/__init__.py +0 -0
  64. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/asg_demo/demo_model.py +0 -0
  65. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/eps/__init__.py +0 -0
  66. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multiflow_demo/__init__.py +0 -0
  67. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multiflow_demo/test_multiflow.py +0 -0
  68. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multirotor/__init__.py +0 -0
  69. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/multirotor/test_multirotor.py +0 -0
  70. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/__init__.py +0 -0
  71. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/pump_indiv.py +0 -0
  72. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/test_pump.py +0 -0
  73. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/pump/test_pump_example.py +0 -0
  74. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/rover/__init__.py +0 -0
  75. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/rover/optimization/ea_analysis.py +0 -0
  76. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/rover/rover_mode_space.py +0 -0
  77. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/rover/test_rover.py +0 -0
  78. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/tank/__init__.py +0 -0
  79. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/tank/test_tank.py +0 -0
  80. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/taxiway/ATC.py +0 -0
  81. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/taxiway/test_asset.py +0 -0
  82. {fmdtools-2.2.1 → fmdtools-2.2.3}/examples/taxiway/test_model.py +0 -0
  83. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/__init__.py +0 -0
  84. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/graph/__init__.py +0 -0
  85. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/graph/base.py +0 -0
  86. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/graph/label.py +0 -0
  87. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/graph/model.py +0 -0
  88. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/graph/style.py +0 -0
  89. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/analyze/history.py +0 -0
  90. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/__init__.py +0 -0
  91. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/architecture/__init__.py +0 -0
  92. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/block/__init__.py +0 -0
  93. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/container/__init__.py +0 -0
  94. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/flow/__init__.py +0 -0
  95. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/object/__init__.py +0 -0
  96. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/define/object/timer.py +0 -0
  97. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/sim/__init__.py +0 -0
  98. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools/sim/scenario.py +0 -0
  99. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools.egg-info/SOURCES.txt +0 -0
  100. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools.egg-info/dependency_links.txt +0 -0
  101. {fmdtools-2.2.1 → fmdtools-2.2.3}/fmdtools.egg-info/top_level.txt +0 -0
  102. {fmdtools-2.2.1 → fmdtools-2.2.3}/setup.cfg +0 -0
  103. {fmdtools-2.2.1 → fmdtools-2.2.3}/tests/test_hierarchical.py +0 -0
  104. {fmdtools-2.2.1 → fmdtools-2.2.3}/tests/test_packages.py +0 -0
@@ -1,4 +1,4 @@
1
- fmdtools version 2.2.1
1
+ fmdtools version 2.2.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.2.1
3
+ Version: 2.2.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.2.1
7
+ License: fmdtools version 2.2.3
8
8
  ---------------------------
9
9
  Apache License
10
10
  Version 2.0, January 2004
@@ -250,7 +250,7 @@ Requires-Dist: tqdm
250
250
  Requires-Dist: networkx
251
251
  Requires-Dist: numpy>=2.0
252
252
  Requires-Dist: matplotlib>=3.7.0
253
- Requires-Dist: pandas
253
+ Requires-Dist: pandas[all]
254
254
  Requires-Dist: ordered-set
255
255
  Requires-Dist: dill
256
256
  Requires-Dist: recordclass==0.23.1
@@ -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.2.1
370
+ git checkout v2.2.3
371
371
  ```
372
372
 
373
373
 
@@ -386,7 +386,7 @@ numpy < 2.0
386
386
  # license: (BSD) https://numpy.org/doc/stable/license.html
387
387
  matplotlib
388
388
  # license: (mixed) https://matplotlib.org/stable/users/license.html
389
- pandas
389
+ pandas[all]
390
390
  # license: (BSD 3-clause) https://pandas.pydata.org/pandas-docs/stable/getting_started/overview.html#license
391
391
  ordered-set
392
392
  # license: (MIT) https://github.com/rspeer/ordered-set/blob/master/MIT-LICENSE
@@ -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.2.1},
509
- date = {2025-9-30},
508
+ version = {2.2.3},
509
+ date = {2025-12-4},
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.2.1
109
+ git checkout v2.2.3
110
110
  ```
111
111
 
112
112
 
@@ -125,7 +125,7 @@ numpy < 2.0
125
125
  # license: (BSD) https://numpy.org/doc/stable/license.html
126
126
  matplotlib
127
127
  # license: (mixed) https://matplotlib.org/stable/users/license.html
128
- pandas
128
+ pandas[all]
129
129
  # license: (BSD 3-clause) https://pandas.pydata.org/pandas-docs/stable/getting_started/overview.html#license
130
130
  ordered-set
131
131
  # license: (MIT) https://github.com/rspeer/ordered-set/blob/master/MIT-LICENSE
@@ -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.2.1},
248
- date = {2025-9-30},
247
+ version = {2.2.3},
248
+ date = {2025-12-4},
249
249
  }
250
250
  ```
251
251
 
@@ -35,11 +35,12 @@ from fmdtools.define.container.mode import Mode
35
35
  from fmdtools.define.architecture.function import FunctionArchitecture
36
36
  from fmdtools.define.container.state import State
37
37
  from fmdtools.define.flow.base import Flow
38
+ import numpy as np
38
39
 
39
40
 
40
41
  class GenericState(State):
41
- rate: float = 1.0
42
- effort: float = 1.0
42
+ rate: np.float64 = 1.0
43
+ effort: np.float64 = 1.0
43
44
 
44
45
 
45
46
  class GenericFlow(Flow):
@@ -48,7 +49,7 @@ class GenericFlow(Flow):
48
49
 
49
50
 
50
51
  class SigState(State):
51
- value: float = 1.0
52
+ value: np.float64 = 1.0
52
53
 
53
54
 
54
55
  class Signal(Flow):
@@ -482,14 +483,14 @@ if __name__ == "__main__":
482
483
  to_return="graph")
483
484
 
484
485
  result.get_faulty().tend.graph.draw()
485
- # endclasses, mdlhists = propagate.single_faults(mdl)
486
+ # results, hists = propagate.single_faults(mdl)
486
487
 
487
488
  # resgraph, mdlhists = propagate.one_fault(mdl, 'ee_to_me', 'toohigh_torque', to_return="fxnflowgraph")
488
489
  # result.graph.draw()
489
490
  ks = [*mdl.get_roles_as_dict("fxn", "flow", flex_prefixes=True)]
490
491
 
491
492
  summary = mdlhists.get_fault_degradation_summary(*ks)
492
- # endclasses, mdlhists = propagate.single_faults(mdl)
493
+ # results, hists = propagate.single_faults(mdl)
493
494
  degradation = mdlhists.get_degraded_hist(*ks)
494
495
 
495
496
  degtimemap = degradation.get_summary(operator=np.sum)
@@ -139,4 +139,4 @@ if __name__ == "__main__":
139
139
  # mdl_nom = EPS()
140
140
  # endresults_nom, resgraph_nom, mdlhist_nom = propagate.nominal(mdl_nom)
141
141
 
142
- # endclasses, reshists = propagate.single_faults(mdl)
142
+ # results, hists = propagate.single_faults(mdl)
@@ -24,10 +24,12 @@ from fmdtools.define.flow.multiflow import MultiFlow
24
24
  from fmdtools.define.flow.commsflow import CommsFlow
25
25
  from fmdtools.define.container.state import State
26
26
 
27
+ import numpy as np
28
+
27
29
 
28
30
  class LocationState(State):
29
- x: float = 0.0
30
- y: float = 0.0
31
+ x: np.float64 = 0.0
32
+ y: np.float64 = 0.0
31
33
 
32
34
 
33
35
  class Communications(CommsFlow):
@@ -39,8 +41,8 @@ class Location(MultiFlow):
39
41
 
40
42
 
41
43
  class MoveParam(Parameter):
42
- x_up: float = 0.0
43
- y_up: float = 0.0
44
+ x_up: np.float64 = 0.0
45
+ y_up: np.float64 = 0.0
44
46
 
45
47
 
46
48
  class Mover(Function):
@@ -60,9 +60,9 @@ class DroneEnvironmentGridParam(CoordsParam):
60
60
  Type of environment risk profile (urban or rural).
61
61
  """
62
62
 
63
- x_size: int = 16
64
- y_size: int = 16
65
- blocksize: float = 10.0
63
+ x_size: np.int32 = 16
64
+ y_size: np.int32 = 16
65
+ blocksize: np.float64 = 10.0
66
66
  loc: str = 'rural'
67
67
 
68
68
 
@@ -240,10 +240,10 @@ class PlanPath(Function):
240
240
  e.g.::
241
241
  >>> p = PlanPath()
242
242
  >>> p.des_traj.s
243
- DesTrajState(dx=np.float64(1.0), dy=np.float64(0.0), dz=np.float64(0.0), power=np.float64(1.0))
243
+ DesTrajState(dx=1.0, dy=0.0, dz=0.0, power=1.0)
244
244
  >>> p.assign_vectdist_to_goal()
245
245
  >>> p.des_traj.s
246
- DesTrajState(dx=np.float64(0.0), dy=np.float64(0.0), dz=np.float64(50.0), power=np.float64(1.0))
246
+ DesTrajState(dx=0.0, dy=0.0, dz=50.0, power=1.0)
247
247
  """
248
248
  loc = self.dofs.s.get('x', 'y', 'z')
249
249
  vd = vectdist(self.s.goal, loc)
@@ -354,13 +354,13 @@ class AffectDOF(AffectDOFStatic):
354
354
  >>> a.dofs.s.put(x=0.0, y=0.0, z=100.0, planvel=1.0, vertvel=0.0)
355
355
  >>> a.inc_pos()
356
356
  >>> a.dofs.s
357
- DOFstate(vertvel=np.float64(0.0), planvel=np.float64(1.0), planpwr=np.float64(1.0), uppwr=np.float64(1.0), x=np.float64(1.0), y=np.float64(0.0), z=np.float64(100.0))
357
+ DOFstate(vertvel=0.0, planvel=1.0, planpwr=1.0, uppwr=1.0, x=1.0, y=0.0, z=100.0)
358
358
 
359
359
  or, for climbing::
360
360
  >>> a.dofs.s.put(vertvel=1.0, planvel=0.0)
361
361
  >>> a.inc_pos()
362
362
  >>> a.dofs.s
363
- DOFstate(vertvel=np.float64(1.0), planvel=np.float64(0.0), planpwr=np.float64(1.0), uppwr=np.float64(1.0), x=np.float64(1.0), y=np.float64(0.0), z=np.float64(101.0))
363
+ DOFstate(vertvel=1.0, planvel=0.0, planpwr=1.0, uppwr=1.0, x=1.0, y=0.0, z=101.0)
364
364
  """
365
365
  # increment x,y,z
366
366
  vec_factor = self.des_traj.s.dist2d()
@@ -428,7 +428,7 @@ class Drone(FunctionArchitecture):
428
428
 
429
429
  Returns
430
430
  -------
431
- endclass : dict
431
+ result : dict
432
432
  Rate, cost, and expected cost for scenario.
433
433
  """
434
434
  if -5 > self.h.flows.dofs.s.x[-1] or 5 < self.h.flows.dofs.s.x[-1]:
@@ -439,7 +439,7 @@ class Drone(FunctionArchitecture):
439
439
  lostcost = 50000
440
440
  else:
441
441
  lostcost = 0
442
- a = 1
442
+
443
443
  if any(self.h.fxns.hold_payload.m.faults['break']):
444
444
  crashcost = 100000
445
445
  else:
@@ -451,7 +451,8 @@ class Drone(FunctionArchitecture):
451
451
  totcost = repcost + crashcost + lostcost
452
452
  rate = scen.rate
453
453
  expcost = totcost*rate*1e5
454
- return {'rate': rate, 'cost': totcost, 'expected_cost': expcost}
454
+ return {'rate': rate, 'cost': totcost, 'expected_cost': expcost,
455
+ 'lost_cost': lostcost, 'crashcost': crashcost}
455
456
 
456
457
 
457
458
  def finddist(p1, p2):
@@ -16,7 +16,7 @@ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
16
16
  CONDITIONS OF ANY KIND, either express or implied. See the License for the
17
17
  specific language governing permissions and limitations under the License.
18
18
  """
19
- from examples.multirotor.drone_mdl_static import m2to1, DistEE, BaseLine
19
+ from examples.multirotor.drone_mdl_static import m2to1, DistEE, BaseLine, ControlState
20
20
  from examples.multirotor.drone_mdl_static import Force, EE, Control, DOFs, DesTraj
21
21
  from examples.multirotor.drone_mdl_static import AffectDOFMode, AffectDOFState
22
22
  from examples.multirotor.drone_mdl_dynamic import StoreEE, CtlDOF, PlanPath, HoldPayload
@@ -25,16 +25,15 @@ from examples.multirotor.drone_mdl_dynamic import Drone as DynDrone
25
25
  from examples.multirotor.drone_mdl_dynamic import AffectDOF as AffectDOFDynamic
26
26
 
27
27
  from fmdtools.define.container.parameter import Parameter
28
- from fmdtools.define.container.state import State
29
28
  from fmdtools.define.block.component import Component
30
29
  from fmdtools.define.architecture.component import ComponentArchitecture
31
- from fmdtools.define.architecture.function import FunctionArchitecture
30
+ from fmdtools.define.flow.multiflow import MultiFlow
32
31
 
33
32
  import fmdtools.sim as fs
34
33
 
35
34
  import numpy as np
36
35
 
37
- class OverallAffectDOFState(State):
36
+ class OverallAffectDOFState(AffectDOFState):
38
37
  """
39
38
  Overall states for the multirotor AffectDOF architecture.
40
39
 
@@ -48,9 +47,8 @@ class OverallAffectDOFState(State):
48
47
  Amplification factor (for fault recovery)
49
48
  """
50
49
 
51
- lrstab: float = 0.0
52
- frstab: float = 0.0
53
- amp_factor: float = 1.0
50
+ lrstab: np.float64 = 0.0
51
+ frstab: np.float64 = 0.0
54
52
 
55
53
 
56
54
  class LineArchParam(Parameter):
@@ -117,144 +115,194 @@ class LineArchParam(Parameter):
117
115
  lr_dict=lr_dict, fr_dict=fr_dict, opposite=opposite)
118
116
  super().__init__(*args, strict_immutability=False)
119
117
 
120
- class LineArchState(State):
121
- """
122
- States of the line architecture.
118
+ def get_forward_factors(self):
119
+ if self.archtype == 'quad':
120
+ return {'rf': 0.5, 'lf': 0.5, 'lr': -0.5, 'rr': -0.5}
121
+ elif self.archtype == 'hex':
122
+ return {'rf': 0.5, 'lf': 0.5, 'lr': -0.5, 'rr': -0.5, 'r': -0.75, 'f': 0.75}
123
+ elif self.archtype == 'oct':
124
+ return {'rf': 0.5, 'lf': 0.5, 'lr': -0.5, 'rr': -0.5,
125
+ 'rf2': 0.5, 'lf2': 0.5, 'lr2': -0.5, 'rr2': -0.5}
123
126
 
124
- Fields
125
- ------
126
- forward : dict
127
- Correction factors for moving forward (based on which rotors are in front).
128
- upward: dict
129
- Correction factors for moving upward (1.0 unless in a recovery mode).
130
- """
131
- forward: dict = dict()
132
- upward: dict = dict()
127
+
128
+ class MultiControl(MultiFlow):
129
+ container_s = ControlState
133
130
 
134
131
 
135
132
  class AffectDOFArch(ComponentArchitecture):
133
+ """
134
+ Drone line architecture (quad/hex/octorotor).
135
+
136
+ Architecture reconfigures rotors when a fault is present, see:
137
+
138
+ Examples
139
+ --------
140
+ >>> adf = AffectDOFArch()
141
+ >>> adf.flows['ctl_in'].s.put(forward=1.0, upward=1.0)
142
+ >>> adf.static_behavior()
143
+ >>> adf.flows['ctl']
144
+ ctl MultiControl
145
+ - s=ControlState(forward=1.0, upward=1.0)
146
+ LOCALS:
147
+ - lf=(s=(forward=0.5, upward=1.0))
148
+ - lf_factors=(s=(forward=0.5, upward=1.0))
149
+ - lr=(s=(forward=-0.5, upward=1.0))
150
+ - lr_factors=(s=(forward=-0.5, upward=1.0))
151
+ - rf=(s=(forward=0.5, upward=1.0))
152
+ - rf_factors=(s=(forward=0.5, upward=1.0))
153
+ - rr=(s=(forward=-0.5, upward=1.0))
154
+ - rr_factors=(s=(forward=-0.5, upward=1.0))
155
+ >>> adf.comps['lf'].m.add_fault("mechbreak") # if one is broken, should adjust ctl
156
+ >>> adf.static_behavior()
157
+ >>> adf.flows['ctl']
158
+ ctl MultiControl
159
+ - s=ControlState(forward=1.0, upward=1.0)
160
+ LOCALS:
161
+ - lf=(s=(forward=0.0, upward=0.0))
162
+ - lf_factors=(s=(forward=0.0, upward=0.0))
163
+ - lr=(s=(forward=-1.0, upward=2.0))
164
+ - lr_factors=(s=(forward=-1.0, upward=2.0))
165
+ - rf=(s=(forward=1.0, upward=2.0))
166
+ - rf_factors=(s=(forward=1.0, upward=2.0))
167
+ - rr=(s=(forward=0.0, upward=0.0))
168
+ - rr_factors=(s=(forward=0.0, upward=0.0))
169
+ """
170
+
136
171
  container_p = LineArchParam
137
- container_s = LineArchState
138
172
 
139
173
  def init_architecture(self, **kwargs):
140
- # add lines
141
- for compname in self.p.components:
142
- self.add_comp(compname, Line)
174
+ self.add_flow("ee_in", EE)
175
+ self.add_flow("force", Force)
176
+ self.add_flow("ctl_in", Control)
177
+ self.add_flow("ctl", MultiControl)
143
178
  # add state configuration - relative throttle for each line
144
- if self.p.archtype == 'quad':
145
- self.s.forward.update({'rf': 0.5, 'lf': 0.5, 'lr': -0.5, 'rr': -0.5})
146
- elif self.p.archtype == 'hex':
147
- self.s.forward.update({'rf': 0.5, 'lf': 0.5, 'lr': -0.5,
148
- 'rr': -0.5, 'r': -0.75, 'f': 0.75})
149
- elif self.p.archtype == 'oct':
150
- self.s.forward.update({'rf': 0.5, 'lf': 0.5, 'lr': -0.5, 'rr': -0.5,
151
- 'rf2': 0.5, 'lf2': 0.5, 'lr2': -0.5, 'rr2': -0.5})
152
- self.s.upward = {c: 1.0 for c in self.p.components}
179
+ forward = self.p.get_forward_factors()
180
+ # add lines
181
+ for cname in self.p.components:
182
+ self.add_comp(cname, Line, 'ee_in', 'ctl', 'force')
183
+ s={'forward': forward[cname], 'upward': 1.0}
184
+ self.flows['ctl'].create_local(cname+"_factors", s=s)
185
+
186
+ def static_behavior(self):
187
+ """Trigger reconfiguration during static behavior step."""
188
+ if self.p.opposite and self.get_faults(with_base_faults=False):
189
+ self.reconfig_faults()
190
+ self.send_local_control_signals()
191
+
192
+ def send_local_control_signals(self):
193
+ """Calc and send local control signals to lines."""
194
+ for cname in self.comps:
195
+ ctl = self.flows['ctl'].get_view(cname)
196
+ facts = self.flows['ctl'].get_view(cname+"_factors")
197
+ ctl.s.put(forward=facts.s.forward*self.flows['ctl_in'].s.forward,
198
+ upward=facts.s.upward*self.flows['ctl_in'].s.upward)
199
+
200
+ def reconfig_faults(self):
201
+ """
202
+ Correct for individual line faultmodes.
203
+
204
+ Turns off the opposite rotor and upping the throttle (amp_factor).
205
+ """
206
+ empty_comps = [c for c, comp in self.comps.items()
207
+ if comp.m.faults or self.comps[self.p.opposite[c]].m.faults]
208
+ num_remaining = len(self.comps)-len(empty_comps)
209
+ if num_remaining:
210
+ amp_factor = len(self.comps)/num_remaining
211
+ else:
212
+ amp_factor = 0.0
213
+ forward = self.p.get_forward_factors()
214
+ for cname, comp in self.comps.items():
215
+ if cname in empty_comps:
216
+ new_facts = dict(forward=0.0, upward=0.0)
217
+ else:
218
+ new_facts = dict(forward=forward[cname]*amp_factor, upward=amp_factor)
219
+ self.flows['ctl'].get_view(cname+'_factors').s.put(**new_facts)
153
220
 
154
221
 
155
222
  class AffectDOF(AffectDOFDynamic):
156
- """Rotor locomotion (multi-component extension)."""
223
+ """
224
+ Multirotor locomotion (multi-component extension).
157
225
 
158
- container_s = OverallAffectDOFState
159
- arch_ca = AffectDOFArch
226
+ Each rotor is simulated individually, producing current and air values that are
227
+ aggregated in the power output calculation.
160
228
 
161
- def static_behavior(self):
162
- """Rotor dynamic behavior with architecture-base recovery."""
163
- if self.ca.p.opposite:
164
- self.reconfig_faults()
165
- self.calc_pwr()
229
+ Examples
230
+ --------
231
+ >>> a = AffectDOF()
232
+ >>> a.dofs.s.put(z=100.0)
233
+ >>> a.ctl_in.s.put(forward=0.0, upward=1.0) # ascent - only going up
234
+ >>> a.ca() # simulate component arch
235
+ >>> a.calc_pwr()
236
+ >>> a.dofs.s
237
+ DOFstate(vertvel=1.0, planvel=1.0, planpwr=-0.0, uppwr=1.0, x=0.0, y=0.0, z=100.0)
238
+ >>> a.ctl_in.s.put(forward=1.0, upward=1.0) # ascent and forward flight - both
239
+ >>> a.ca() # simulate component arch
240
+ >>> a.calc_pwr()
241
+ >>> a.dofs.s
242
+ DOFstate(vertvel=1.0, planvel=1.0, planpwr=1.0, uppwr=1.0, x=0.0, y=0.0, z=100.0)
243
+ """
166
244
 
167
- def reconfig_faults(self):
168
- """Corrects for individual line faultmodes by turning off the opposite rotor
169
- and upping the throttle (amp_factor)"""
170
- if self.get_faults(with_base_faults=False, only_present=True):
171
- for cname, comp in self.ca.comps.items():
172
- if comp.m.faults:
173
- oname = self.ca.p.opposite[cname]
174
- if self.ca.s.forward[cname] != 0.0:
175
- self.ca.s.forward[cname] = 0.0
176
- self.ca.s.upward[cname] = 0.0
177
- if self.ca.s.forward[oname] != 0.0:
178
- self.ca.s.forward[oname] = 0.0
179
- self.ca.s.upward[oname] = 0.0
180
- tot_comps = len(self.ca.comps)
181
- empty_comps = len([c for c in self.ca.s.forward if self.ca.s.forward[c] == 0.0])
182
- try:
183
- self.s.amp_factor = tot_comps / (tot_comps - empty_comps)
184
- except ZeroDivisionError:
185
- self.s.amp_factor = 1.0
245
+ container_s = OverallAffectDOFState
246
+ arch_ca = AffectDOFArch
186
247
 
187
248
  def calc_pwr(self):
188
249
  """
189
- Calculates overall power and stability based on individual rotor output.
190
-
191
- e.g., ::
192
- >>> a = AffectDOF()
193
- >>> a.dofs.s.put(z=100.0)
194
- >>> a.ctl_in.s.put(forward=0.0, upward=1.0)
195
- >>> a.calc_pwr()
196
- >>> a.dofs.s
197
- DOFstate(vertvel=np.float64(1.0), planvel=np.float64(1.0), planpwr=np.float64(-0.0), uppwr=np.float64(1.0), x=np.float64(0.0), y=np.float64(0.0), z=np.float64(100.0))
198
- >>> a.ctl_in.s.put(forward=1.0, upward=1.0)
199
- >>> a.calc_pwr()
200
- >>> a.dofs.s
201
- DOFstate(vertvel=np.float64(1.0), planvel=np.float64(1.0), planpwr=np.float64(1.0), uppwr=np.float64(1.0), x=np.float64(0.0), y=np.float64(0.0), z=np.float64(100.0))
250
+ Calculate overall power and stability based on individual rotor output.
251
+
252
+ Aggregates current and air from contained component architecture lines to
253
+ calculate high-level output to dofs.
202
254
  """
203
- air, ee_in = {}, {}
204
- # injects faults into lines
205
- for linname, lin in self.ca.comps.items():
206
- a, ee = lin.behavior(self.ee_in.s.effort,
207
- self.ctl_in,
208
- self.ca.s.upward[linname] * self.s.amp_factor,
209
- self.ca.s.forward[linname] * self.s.amp_factor,
210
- self.force.s.support)
211
- air[lin.name] = a
212
- ee_in[lin.name] = ee
213
-
214
- if any(value >= 10 for value in ee_in.values()):
255
+ over_state = self.s.mul("e_to", "e_ti", "ct", "mt", "pt")
256
+ airs = {cname: c.s.air for cname, c in self.ca.comps.items()}
257
+ currents = {cname: c.ee.s.rate for cname, c in self.ca.comps.items()}
258
+
259
+ if any(value >= 10 for value in currents.values()):
215
260
  self.ee_in.s.rate = 10
216
- elif any(value != 0.0 for value in ee_in.values()):
217
- self.ee_in.s.rate = sum(ee_in.values()) / \
218
- len(ee_in) # should it really be max?
261
+ elif any(value != 0.0 for value in currents.values()):
262
+ self.ee_in.s.rate = sum(currents.values()) / len(currents) # should it really be max?
219
263
  else:
220
264
  self.ee_in.s.rate = 0.0
221
265
 
222
- self.s.lrstab = (sum([air[comp] for comp in self.ca.p.lr_dict['l']]) -
223
- sum([air[comp] for comp in self.ca.p.lr_dict['r']]))/len(air)
224
- self.s.frstab = (sum([air[comp] for comp in self.ca.p.fr_dict['r']]) -
225
- sum([air[comp] for comp in self.ca.p.fr_dict['f']]))/len(air)
266
+ self.s.lrstab = (sum([airs[comp] for comp in self.ca.p.lr_dict['l']]) -
267
+ sum([airs[comp] for comp in self.ca.p.lr_dict['r']]))/len(airs)
268
+ self.s.frstab = (sum([airs[comp] for comp in self.ca.p.fr_dict['r']]) -
269
+ sum([airs[comp] for comp in self.ca.p.fr_dict['f']]))/len(airs)
226
270
  if abs(self.s.lrstab) >= 0.4 or abs(self.s.frstab) >= 0.75:
227
271
  self.dofs.s.put(uppwr=0.0, planpwr=0.0)
228
272
  else:
229
- airs = list(air.values())
230
- self.dofs.s.uppwr = np.mean(airs)
231
- self.dofs.s.planpwr = -2*self.s.frstab
273
+ self.dofs.s.uppwr = np.mean(list(airs.values())) * over_state
274
+ self.dofs.s.planpwr = -2*self.s.frstab * over_state
275
+
232
276
 
277
+ class LineState(AffectDOFState):
278
+ """AffectDOFState with air output for individual rotor."""
279
+
280
+ air : np.float64 = 0.0
233
281
 
234
282
  class Line(Component, BaseLine):
235
283
  """Individual version of a line (extends BaseLine in static model)."""
236
284
 
237
- container_s = AffectDOFState
285
+ container_s = LineState
238
286
  container_m = AffectDOFMode
287
+ flow_ee_in = EE
288
+ flow_ee = EE
289
+ flow_ctl = MultiControl
290
+ flow_force = Force
291
+
292
+ def init_block(self, **kwargs):
293
+ self.ctl = self.ctl.create_local(self.name)
239
294
 
240
- def behavior(self, ee_in, ctlin, u_fact, f_fact, force):
241
- """Calculate air, ee out based on inputs and modes."""
242
- if force <= 0.0:
295
+ def static_behavior(self):
296
+ if self.force.s.support <= 0.0:
243
297
  self.m.add_fault('mechbreak', 'propbreak')
244
- elif force <= 0.5:
298
+ elif self.force.s.support <= 0.5:
245
299
  self.m.add_fault('mechfriction')
246
-
247
300
  self.calc_faults()
248
-
249
- pwr = ctlin.s.upward * u_fact + ctlin.s.forward * f_fact
250
- airout = m2to1([ee_in,
251
- self.s.e_ti,
252
- pwr,
253
- self.s.ct,
254
- self.s.mt,
255
- self.s.pt])
256
- ee_in = m2to1([ee_in, self.s.e_to, pwr])
257
- return airout, ee_in
301
+ pwr = self.ctl.s.upward + self.ctl.s.forward
302
+ self.ee.s.effort = self.ee_in.s.effort
303
+ self.ee.s.rate = m2to1([self.ee_in.s.effort, self.s.e_to, pwr])
304
+ self.s.air = m2to1([self.ee_in.s.effort, self.s.e_ti, pwr, self.s.ct, self.s.mt,
305
+ self.s.pt])
258
306
 
259
307
 
260
308
  class DroneParam(Parameter, readonly=True):
@@ -303,11 +351,11 @@ if __name__ == "__main__":
303
351
 
304
352
  # check rf_mechbreack fault propagation in quad architecture:
305
353
  hierarchical_model = Drone(p=DroneParam(arch='quad'))
306
- # endclass, mdlhist = fs.propagate.one_fault(hierarchical_model,
307
- # 'affect_dof.ca.comps.rf',
308
- # 'mechbreak',
309
- # time=5)
310
- # mdlhist.plot_line('flows.dofs.s.x', 'flows.dofs.s.y', 'flows.dofs.s.z', 'fxns.store_ee.s.soc')
354
+ result, mdlhist = fs.propagate.one_fault(hierarchical_model,
355
+ 'affect_dof.ca.comps.rf',
356
+ 'mechbreak',
357
+ time=5)
358
+ mdlhist.plot_line('flows.dofs.s.x', 'flows.dofs.s.y', 'flows.dofs.s.z', 'fxns.store_ee.s.soc')
311
359
 
312
360
  # check rr2_propstuck fault in oct architecture over several times:
313
361
  mdl = Drone(p=DroneParam(arch='oct'))