fmdtools 2.0.0__tar.gz → 2.0.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 (107) hide show
  1. {fmdtools-2.0.0 → fmdtools-2.0.3}/LICENSE +1 -1
  2. {fmdtools-2.0.0/fmdtools.egg-info → fmdtools-2.0.3}/PKG-INFO +21 -8
  3. {fmdtools-2.0.0 → fmdtools-2.0.3}/README.md +18 -5
  4. fmdtools-2.0.3/examples/asg_demo/__init__.py +16 -0
  5. fmdtools-2.0.3/examples/asg_demo/demo_model.py +212 -0
  6. fmdtools-2.0.3/examples/eps/__init__.py +6 -0
  7. fmdtools-2.0.3/examples/eps/eps.py +515 -0
  8. fmdtools-2.0.3/examples/eps/test_eps.py +134 -0
  9. fmdtools-2.0.3/examples/multiflow_demo/__init__.py +6 -0
  10. fmdtools-2.0.3/examples/multiflow_demo/multiflow_demo.py +140 -0
  11. fmdtools-2.0.3/examples/multiflow_demo/test_multiflow.py +92 -0
  12. fmdtools-2.0.3/examples/multirotor/__init__.py +16 -0
  13. fmdtools-2.0.3/examples/multirotor/drone_mdl_dynamic.py +549 -0
  14. fmdtools-2.0.3/examples/multirotor/drone_mdl_hierarchical.py +334 -0
  15. fmdtools-2.0.3/examples/multirotor/drone_mdl_rural.py +961 -0
  16. fmdtools-2.0.3/examples/multirotor/drone_mdl_static.py +809 -0
  17. fmdtools-2.0.3/examples/multirotor/drone_mdl_urban.py +549 -0
  18. fmdtools-2.0.3/examples/multirotor/opt_drone_rural.py +203 -0
  19. fmdtools-2.0.3/examples/multirotor/test_multirotor.py +154 -0
  20. fmdtools-2.0.3/examples/pump/__init__.py +6 -0
  21. fmdtools-2.0.3/examples/pump/ex_pump.py +687 -0
  22. fmdtools-2.0.3/examples/pump/jointfaults_ex.py +44 -0
  23. fmdtools-2.0.3/examples/pump/parallelism_methods.py +200 -0
  24. fmdtools-2.0.3/examples/pump/pump_indiv.py +83 -0
  25. fmdtools-2.0.3/examples/pump/pump_stochastic.py +231 -0
  26. fmdtools-2.0.3/examples/pump/test_pump.py +335 -0
  27. fmdtools-2.0.3/examples/pump/test_pump_example.py +208 -0
  28. fmdtools-2.0.3/examples/pump/test_pump_stochastic.py +297 -0
  29. fmdtools-2.0.3/examples/rover/__init__.py +16 -0
  30. fmdtools-2.0.3/examples/rover/opt_rover.py +75 -0
  31. fmdtools-2.0.3/examples/rover/optimization/ea_analysis.py +35 -0
  32. fmdtools-2.0.3/examples/rover/optimization/search_rover.py +841 -0
  33. fmdtools-2.0.3/examples/rover/optimization/untitled2.py +7 -0
  34. fmdtools-2.0.3/examples/rover/rover_degradation.py +347 -0
  35. fmdtools-2.0.3/examples/rover/rover_mode_space.py +81 -0
  36. fmdtools-2.0.3/examples/rover/rover_model.py +1275 -0
  37. fmdtools-2.0.3/examples/rover/rover_model_human.py +547 -0
  38. fmdtools-2.0.3/examples/rover/test_rover.py +39 -0
  39. fmdtools-2.0.3/examples/tank/__init__.py +16 -0
  40. fmdtools-2.0.3/examples/tank/tank_model.py +477 -0
  41. fmdtools-2.0.3/examples/tank/tank_optimization_model.py +275 -0
  42. fmdtools-2.0.3/examples/tank/test_tank.py +276 -0
  43. fmdtools-2.0.3/examples/taxiway/ATC.py +183 -0
  44. fmdtools-2.0.3/examples/taxiway/asset.py +591 -0
  45. fmdtools-2.0.3/examples/taxiway/common.py +859 -0
  46. fmdtools-2.0.3/examples/taxiway/model.py +235 -0
  47. fmdtools-2.0.3/examples/taxiway/test_asset.py +309 -0
  48. fmdtools-2.0.3/examples/taxiway/test_model.py +95 -0
  49. fmdtools-2.0.3/fmdtools/analyze/__init__.py +25 -0
  50. fmdtools-2.0.3/fmdtools/analyze/common.py +521 -0
  51. fmdtools-2.0.3/fmdtools/analyze/graph/__init__.py +23 -0
  52. fmdtools-2.0.3/fmdtools/analyze/graph/base.py +998 -0
  53. fmdtools-2.0.3/fmdtools/analyze/graph/label.py +243 -0
  54. fmdtools-2.0.3/fmdtools/analyze/graph/model.py +358 -0
  55. fmdtools-2.0.3/fmdtools/analyze/graph/style.py +603 -0
  56. fmdtools-2.0.3/fmdtools/analyze/history.py +1182 -0
  57. fmdtools-2.0.3/fmdtools/analyze/phases.py +691 -0
  58. fmdtools-2.0.3/fmdtools/analyze/result.py +1143 -0
  59. fmdtools-2.0.3/fmdtools/analyze/tabulate.py +869 -0
  60. fmdtools-2.0.3/fmdtools/define/__init__.py +26 -0
  61. fmdtools-2.0.3/fmdtools/define/architecture/__init__.py +24 -0
  62. fmdtools-2.0.3/fmdtools/define/architecture/action.py +422 -0
  63. fmdtools-2.0.3/fmdtools/define/architecture/base.py +424 -0
  64. fmdtools-2.0.3/fmdtools/define/architecture/component.py +56 -0
  65. fmdtools-2.0.3/fmdtools/define/architecture/function.py +932 -0
  66. fmdtools-2.0.3/fmdtools/define/architecture/geom.py +222 -0
  67. fmdtools-2.0.3/fmdtools/define/base.py +303 -0
  68. fmdtools-2.0.3/fmdtools/define/block/__init__.py +23 -0
  69. fmdtools-2.0.3/fmdtools/define/block/action.py +101 -0
  70. fmdtools-2.0.3/fmdtools/define/block/base.py +685 -0
  71. fmdtools-2.0.3/fmdtools/define/block/component.py +29 -0
  72. fmdtools-2.0.3/fmdtools/define/block/function.py +262 -0
  73. fmdtools-2.0.3/fmdtools/define/container/__init__.py +24 -0
  74. fmdtools-2.0.3/fmdtools/define/container/base.py +474 -0
  75. fmdtools-2.0.3/fmdtools/define/container/mode.py +619 -0
  76. fmdtools-2.0.3/fmdtools/define/container/parameter.py +227 -0
  77. fmdtools-2.0.3/fmdtools/define/container/rand.py +408 -0
  78. fmdtools-2.0.3/fmdtools/define/container/state.py +365 -0
  79. fmdtools-2.0.3/fmdtools/define/container/time.py +177 -0
  80. fmdtools-2.0.3/fmdtools/define/environment.py +146 -0
  81. fmdtools-2.0.3/fmdtools/define/flow/__init__.py +22 -0
  82. fmdtools-2.0.3/fmdtools/define/flow/base.py +106 -0
  83. fmdtools-2.0.3/fmdtools/define/flow/commsflow.py +283 -0
  84. fmdtools-2.0.3/fmdtools/define/flow/multiflow.py +236 -0
  85. fmdtools-2.0.3/fmdtools/define/object/__init__.py +23 -0
  86. fmdtools-2.0.3/fmdtools/define/object/base.py +946 -0
  87. fmdtools-2.0.3/fmdtools/define/object/coords.py +1364 -0
  88. fmdtools-2.0.3/fmdtools/define/object/geom.py +518 -0
  89. fmdtools-2.0.3/fmdtools/define/object/timer.py +158 -0
  90. fmdtools-2.0.3/fmdtools/sim/__init__.py +23 -0
  91. fmdtools-2.0.3/fmdtools/sim/propagate.py +1240 -0
  92. fmdtools-2.0.3/fmdtools/sim/sample.py +1976 -0
  93. fmdtools-2.0.3/fmdtools/sim/scenario.py +421 -0
  94. fmdtools-2.0.3/fmdtools/sim/search.py +1830 -0
  95. {fmdtools-2.0.0 → fmdtools-2.0.3/fmdtools.egg-info}/PKG-INFO +21 -8
  96. fmdtools-2.0.3/fmdtools.egg-info/SOURCES.txt +103 -0
  97. {fmdtools-2.0.0 → fmdtools-2.0.3}/fmdtools.egg-info/requires.txt +1 -1
  98. {fmdtools-2.0.0 → fmdtools-2.0.3}/pyproject.toml +3 -3
  99. fmdtools-2.0.0/fmdtools.egg-info/SOURCES.txt +0 -13
  100. fmdtools-2.0.0/setup.py +0 -3
  101. {fmdtools-2.0.0 → fmdtools-2.0.3}/examples/__init__.py +0 -0
  102. {fmdtools-2.0.0 → fmdtools-2.0.3}/fmdtools/__init__.py +0 -0
  103. {fmdtools-2.0.0 → fmdtools-2.0.3}/fmdtools.egg-info/dependency_links.txt +0 -0
  104. {fmdtools-2.0.0 → fmdtools-2.0.3}/fmdtools.egg-info/top_level.txt +0 -0
  105. {fmdtools-2.0.0 → fmdtools-2.0.3}/setup.cfg +0 -0
  106. {fmdtools-2.0.0 → fmdtools-2.0.3}/tests/test_graph.py +0 -0
  107. {fmdtools-2.0.0 → fmdtools-2.0.3}/tests/test_packages.py +0 -0
@@ -1,4 +1,4 @@
1
- fmdtools version 2.0.0
1
+ fmdtools version 2.0.3
2
2
  ---------------------------
3
3
  Apache License
4
4
  Version 2.0, January 2004
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fmdtools
3
- Version: 2.0.0
3
+ Version: 2.0.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.0.0
7
+ License: fmdtools version 2.0.3
8
8
  ---------------------------
9
9
  Apache License
10
10
  Version 2.0, January 2004
@@ -251,7 +251,7 @@ Requires-Dist: matplotlib>=3.7.0
251
251
  Requires-Dist: pandas
252
252
  Requires-Dist: ordered-set
253
253
  Requires-Dist: dill
254
- Requires-Dist: recordclass>=0.21.1
254
+ Requires-Dist: recordclass==0.21.1
255
255
  Requires-Dist: pytest
256
256
  Requires-Dist: graphviz
257
257
  Requires-Dist: shapely>=2.0
@@ -314,21 +314,34 @@ graphviz
314
314
  # install from: https://anaconda.org/anaconda/graphviz
315
315
  ```
316
316
 
317
+ Note: some versions `fmdtools` dependencies may additionally require `Microsoft Studio Build tools <https://visualstudio.microsoft.com/license-terms/vs2022-ga-diagnosticbuildtools/>`_ to build properly, so it may be helpful to install it also.
318
+
319
+
317
320
  ### Install fmdtools
318
321
 
319
- The latest public version of fmdtools can be downloaded from the [fmdtools github repository](https://github.com/nasa/fmdtools/) e.g., using:
322
+ For use of fmdtools as an external library, the toolkit can be installed directly from the [PyPI package repository](https://pypi.org/project/fmdtools/) using ``pip install fmdtools``.
323
+
324
+ #### Editable Development Install
325
+
326
+ For development as well as interactive use of tutorials and models, we reccomend creating an editable development install.
327
+
328
+ For this installation, fmdtools can be downloaded from the [fmdtools github repository](https://github.com/nasa/fmdtools/) using:
320
329
 
321
330
  ```
322
331
  git clone https://github.com/nasa/fmdtools.git
323
332
  ```
324
-
325
- For development and use of this version (e.g., for tutorials and models), we recommended installing this package using `pip`:
333
+
334
+ This may then be installed using:
326
335
 
327
336
  ```
328
337
  pip install -e "/path/to/fmdtools"
329
338
  ```
330
339
 
331
- A version of the fmdtools toolkit can also be installed directly from the [PyPI package repository](https://pypi.org/project/fmdtools/) using ``pip install fmdtools``.
340
+ 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.:
341
+
342
+ ```
343
+ git checkout v2.0.3
344
+ ```
332
345
 
333
346
 
334
347
  #### Dependencies
@@ -465,7 +478,7 @@ To cite a particular version of the fmdtools, you may use:
465
478
  author = {{NASA}},
466
479
  title = {fmdtools},
467
480
  url = {https://github.com/nasa/fmdtools},
468
- version = {2.0.0}, # <- replace with your version number
481
+ version = {2.0.3}, # <- replace with your version number
469
482
  date = {2024-08-22},
470
483
  }
471
484
  ```
@@ -56,21 +56,34 @@ graphviz
56
56
  # install from: https://anaconda.org/anaconda/graphviz
57
57
  ```
58
58
 
59
+ Note: some versions `fmdtools` dependencies may additionally require `Microsoft Studio Build tools <https://visualstudio.microsoft.com/license-terms/vs2022-ga-diagnosticbuildtools/>`_ to build properly, so it may be helpful to install it also.
60
+
61
+
59
62
  ### Install fmdtools
60
63
 
61
- The latest public version of fmdtools can be downloaded from the [fmdtools github repository](https://github.com/nasa/fmdtools/) e.g., using:
64
+ For use of fmdtools as an external library, the toolkit can be installed directly from the [PyPI package repository](https://pypi.org/project/fmdtools/) using ``pip install fmdtools``.
65
+
66
+ #### Editable Development Install
67
+
68
+ For development as well as interactive use of tutorials and models, we reccomend creating an editable development install.
69
+
70
+ For this installation, fmdtools can be downloaded from the [fmdtools github repository](https://github.com/nasa/fmdtools/) using:
62
71
 
63
72
  ```
64
73
  git clone https://github.com/nasa/fmdtools.git
65
74
  ```
66
-
67
- For development and use of this version (e.g., for tutorials and models), we recommended installing this package using `pip`:
75
+
76
+ This may then be installed using:
68
77
 
69
78
  ```
70
79
  pip install -e "/path/to/fmdtools"
71
80
  ```
72
81
 
73
- A version of the fmdtools toolkit can also be installed directly from the [PyPI package repository](https://pypi.org/project/fmdtools/) using ``pip install fmdtools``.
82
+ 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.:
83
+
84
+ ```
85
+ git checkout v2.0.3
86
+ ```
74
87
 
75
88
 
76
89
  #### Dependencies
@@ -207,7 +220,7 @@ To cite a particular version of the fmdtools, you may use:
207
220
  author = {{NASA}},
208
221
  title = {fmdtools},
209
222
  url = {https://github.com/nasa/fmdtools},
210
- version = {2.0.0}, # <- replace with your version number
223
+ version = {2.0.3}, # <- replace with your version number
211
224
  date = {2024-08-22},
212
225
  }
213
226
  ```
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Copyright © 2024, United States Government, as represented by the Administrator
5
+ of the National Aeronautics and Space Administration. All rights reserved.
6
+
7
+ The “"Fault Model Design tools - fmdtools version 2"” software is licensed
8
+ under the Apache License, Version 2.0 (the "License"); you may not use this
9
+ file except in compliance with the License. You may obtain a copy of the
10
+ License at http://www.apache.org/licenses/LICENSE-2.0.
11
+
12
+ Unless required by applicable law or agreed to in writing, software distributed
13
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
15
+ specific language governing permissions and limitations under the License.
16
+ """
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Copyright © 2024, United States Government, as represented by the Administrator
5
+ of the National Aeronautics and Space Administration. All rights reserved.
6
+
7
+ The “"Fault Model Design tools - fmdtools version 2"” software is licensed
8
+ under the Apache License, Version 2.0 (the "License"); you may not use this
9
+ file except in compliance with the License. You may obtain a copy of the
10
+ License at http://www.apache.org/licenses/LICENSE-2.0.
11
+
12
+ Unless required by applicable law or agreed to in writing, software distributed
13
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
15
+ specific language governing permissions and limitations under the License.
16
+ """
17
+
18
+ from fmdtools.define.container.mode import Mode
19
+ from fmdtools.define.block.function import Function
20
+ from fmdtools.define.block.action import Action
21
+ from fmdtools.define.architecture.action import ActionArchitecture
22
+ from fmdtools.define.architecture.function import FunctionArchitecture
23
+ from fmdtools.define.container.state import State
24
+ from fmdtools.define.flow.base import Flow
25
+
26
+
27
+ class OutcomeStates(State):
28
+ """State tracking the number of actions and perceptions performed."""
29
+
30
+ num_perceptions: int = 0
31
+ num_actions: int = 0
32
+
33
+
34
+ class Outcome(Flow):
35
+
36
+ __slots__ = ()
37
+ container_s = OutcomeStates
38
+
39
+
40
+ class HazardState(State):
41
+ """Whether a hazard is present, percieved, or mitigated by the human."""
42
+
43
+ present: bool = False
44
+ percieved: bool = False
45
+ mitigated: bool = False
46
+
47
+
48
+ class Hazard(Flow):
49
+
50
+ __slots__ = ()
51
+ container_s = HazardState
52
+
53
+
54
+ class ActionMode(Mode):
55
+ fm_args = ('failed', 'unable')
56
+ mode: str = 'nominal'
57
+ exclusive = True
58
+
59
+
60
+ class Perceive(Action):
61
+ """A user's perception abilities/behaviors for percieving the hazard."""
62
+
63
+ __slots__ = ('hazard', 'outcome')
64
+ container_m = ActionMode
65
+ flow_hazard = Hazard
66
+ flow_outcome = Outcome
67
+
68
+ def behavior(self, time):
69
+ if not self.m.in_mode('failed', 'unable'):
70
+ self.hazard.s.percieved = self.hazard.s.present
71
+ self.outcome.s.num_perceptions += self.hazard.s.percieved
72
+ else:
73
+ self.hazard.s.percieved = False
74
+ self.m.remove_fault('failed', 'nom')
75
+
76
+ def percieved(self):
77
+ return self.hazard.s.percieved
78
+
79
+
80
+ class Act(Action):
81
+ """User actions to mitigate the hazard."""
82
+
83
+ __slots__ = ('hazard', 'outcome')
84
+ container_m = ActionMode
85
+ flow_hazard = Hazard
86
+ flow_outcome = Outcome
87
+
88
+ def behavior(self, time):
89
+ if not self.m.in_mode('failed', 'unable'):
90
+ self.outcome.s.num_actions += 1
91
+ self.hazard.s.mitigated = True
92
+ elif self.m.in_mode('failed'):
93
+ self.hazard.s.mitigated = False
94
+ self.m.remove_fault('failed', 'nom')
95
+ else:
96
+ self.hazard.s.mitigated = False
97
+
98
+ def acted(self):
99
+ return not self.m.in_mode('failed')
100
+
101
+
102
+ class Done(Action):
103
+ """User state after performing the action."""
104
+
105
+ __slots__ = ('hazard')
106
+ flow_hazard = Hazard
107
+
108
+ def behavior(self, time):
109
+ if not self.hazard.s.present:
110
+ self.hazard.s.mitigated = False
111
+
112
+ def ready(self):
113
+ return not self.hazard.s.present
114
+
115
+
116
+ class Human(ActionArchitecture):
117
+ """Overall human action sequence graph specifying what the user will do when."""
118
+ initial_action = "perceive"
119
+
120
+ def init_architecture(self, *args, **kwargs):
121
+ # flows from external fxn/model can be defined here,
122
+ self.add_flow("hazard", Hazard)
123
+ # along with flows internal to the ASG class
124
+ self.add_flow("outcome", fclass=Outcome)
125
+
126
+ self.add_act("perceive", Perceive, "outcome", "hazard")
127
+ self.add_act("act", Act, "outcome", "hazard")
128
+ self.add_act("done", Done, "hazard")
129
+
130
+ self.add_cond("perceive", "act", "percieved", self.acts['perceive'].percieved)
131
+ self.add_cond("act", "done", "acted", self.acts['act'].acted)
132
+ self.add_cond("done", "perceive", "ready", self.acts['done'].ready)
133
+
134
+
135
+ class DetectHazard(Function):
136
+ """Function containing the human."""
137
+
138
+ __slots__ = ('hazard')
139
+ container_m = Mode
140
+ arch_aa = Human
141
+ flow_hazard = Hazard
142
+
143
+
144
+ class ProduceHazard(Function):
145
+ """Function producing Hazards."""
146
+
147
+ __slots__ = ('hazard',)
148
+ flow_hazard = Hazard
149
+
150
+ def dynamic_behavior(self, time):
151
+ if not time % 4:
152
+ self.hazard.s.present = True
153
+ else:
154
+ self.hazard.s.present = False
155
+
156
+
157
+ class PassStates(State):
158
+ """Whether or not the hazard is ultimately passed or mitigated."""
159
+
160
+ hazards_mitigated: int = 0
161
+ hazards_propagated: int = 0
162
+
163
+
164
+ class PassHazard(Function):
165
+ """Accumulates total hazards/mitigations."""
166
+
167
+ __slots__ = ('hazard',)
168
+ container_s = PassStates
169
+ flow_hazard = Hazard
170
+
171
+ def dynamic_behavior(self, time):
172
+ if self.hazard.s.present and self.hazard.s.mitigated:
173
+ self.s.hazards_mitigated += 1
174
+ elif self.hazard.s.present and not self.hazard.s.mitigated:
175
+ self.s.hazards_propagated += 1
176
+
177
+
178
+ class HazardModel(FunctionArchitecture):
179
+ """Overall model of the human in context."""
180
+
181
+ __slots__ = ()
182
+ default_sp = dict(end_time=60, dt=1.0)
183
+
184
+ def init_architecture(self, **kwargs):
185
+ self.add_flow("hazard", Hazard)
186
+ self.add_fxn("produce_hazard", ProduceHazard, 'hazard')
187
+ self.add_fxn("detect_hazard", DetectHazard, 'hazard')
188
+ self.add_fxn("pass_hazard", PassHazard, 'hazard')
189
+
190
+
191
+ if __name__ == '__main__':
192
+ import fmdtools.sim.propagate as prop
193
+ mdl = HazardModel()
194
+ result_fault, mdlhist_fault = prop.one_fault(mdl, 'detect_hazard',
195
+ 'act_unable', time=4,
196
+ desired_result='graph')
197
+ result_fault, mdlhist_fault = prop.one_fault(mdl, 'detect_hazard',
198
+ 'perceive_failed', time=4,
199
+ desired_result='graph')
200
+
201
+ result_fault.graph.draw()
202
+ ex_fxn = DetectHazard('detect_hazard')
203
+ result_indiv, hist_indiv = prop.nominal(ex_fxn,
204
+ disturbances={5:{'aa.flows.hazard.s.present':True}})
205
+ fig, axs = hist_indiv.plot_line('aa.flows.hazard.s.present',
206
+ 'aa.flows.hazard.s.percieved',
207
+ 'aa.flows.hazard.s.mitigated', figsize=(10,5))
208
+
209
+ result_fault, mdlhist_fault = prop.one_fault(mdl, 'detect_hazard',
210
+ 'perceive_failed', time=4,
211
+ desired_result='graph.fxns.detect_hazard.aa')
212
+ result_fault.graph.fxns.detect_hazard.aa.draw_graphviz(layout='dot')
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Tue May 16 12:44:04 2023
4
+
5
+ @author: dhulse
6
+ """