fmdtools 2.5.2__tar.gz → 2.5.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 (177) hide show
  1. {fmdtools-2.5.2 → fmdtools-2.5.3}/LICENSE +1 -1
  2. {fmdtools-2.5.2/fmdtools.egg-info → fmdtools-2.5.3}/PKG-INFO +5 -5
  3. {fmdtools-2.5.2 → fmdtools-2.5.3}/README.md +3 -3
  4. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/multirotor_drone/model_urban.py +4 -0
  5. {fmdtools-2.5.2 → fmdtools-2.5.3/fmdtools.egg-info}/PKG-INFO +5 -5
  6. {fmdtools-2.5.2 → fmdtools-2.5.3}/fmdtools.egg-info/SOURCES.txt +19 -1
  7. {fmdtools-2.5.2 → fmdtools-2.5.3}/pyproject.toml +1 -1
  8. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/__init__.py +1 -1
  9. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/common.py +20 -18
  10. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/history.py +4 -1
  11. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/phases.py +9 -3
  12. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/result.py +25 -16
  13. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/base.py +2 -1
  14. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/block/base.py +33 -15
  15. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/container/rand.py +108 -40
  16. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/container/time.py +7 -1
  17. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/sim/sample.py +4 -3
  18. fmdtools-2.5.3/tests/test_array_differences.py +153 -0
  19. fmdtools-2.5.3/tests/test_array_result_equality.py +167 -0
  20. fmdtools-2.5.3/tests/test_bootstrap_statistic_arguments.py +196 -0
  21. fmdtools-2.5.3/tests/test_exact_nominal_expectation.py +166 -0
  22. fmdtools-2.5.3/tests/test_explicit_result_file_formats.py +176 -0
  23. fmdtools-2.5.3/tests/test_exponential_rayleigh_draw_sizes.py +239 -0
  24. fmdtools-2.5.3/tests/test_gamma_sampling_density.py +233 -0
  25. fmdtools-2.5.3/tests/test_history_phase_identifiers.py +204 -0
  26. fmdtools-2.5.3/tests/test_independent_single_replicate_seeds.py +201 -0
  27. fmdtools-2.5.3/tests/test_individual_result_paths.py +165 -0
  28. fmdtools-2.5.3/tests/test_integer_draw_mass.py +266 -0
  29. fmdtools-2.5.3/tests/test_json_result_arrays.py +163 -0
  30. fmdtools-2.5.3/tests/test_named_metric_rounding.py +152 -0
  31. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_packages.py +1 -1
  32. fmdtools-2.5.3/tests/test_pareto_density.py +203 -0
  33. fmdtools-2.5.3/tests/test_result_folder_selection.py +191 -0
  34. fmdtools-2.5.3/tests/test_standard_t_density.py +192 -0
  35. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_time.py +4 -4
  36. fmdtools-2.5.3/tests/test_unit_random_density.py +176 -0
  37. fmdtools-2.5.3/tests/test_vonmises_density.py +222 -0
  38. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/__init__.py +0 -0
  39. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airport_taxiway/model_asset.py +0 -0
  40. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airport_taxiway/model_atc.py +0 -0
  41. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airport_taxiway/model_common.py +0 -0
  42. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airport_taxiway/model_main.py +0 -0
  43. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airport_taxiway/test_asset.py +0 -0
  44. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airport_taxiway/test_model.py +0 -0
  45. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/__init__.py +0 -0
  46. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/__init__.py +0 -0
  47. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/aircraft.py +0 -0
  48. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/aircraftarchitecture.py +0 -0
  49. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/arch/__init__.py +0 -0
  50. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/arch/aviate.py +0 -0
  51. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/arch/controlflight.py +0 -0
  52. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/arch/flows.py +0 -0
  53. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/arch/holdpayload.py +0 -0
  54. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/arch/perceiveenvironment.py +0 -0
  55. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/arch/storeee.py +0 -0
  56. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/parameter.py +0 -0
  57. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/base/state.py +0 -0
  58. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/contingency_management/__init__.py +0 -0
  59. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/contingency_management/model_aircraft.py +0 -0
  60. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/contingency_management/model_environment.py +0 -0
  61. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/contingency_management/model_flightplanner.py +0 -0
  62. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/__init__.py +0 -0
  63. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/model_drone.py +0 -0
  64. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/model_environment.py +0 -0
  65. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/model_main.py +0 -0
  66. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/model_responder.py +0 -0
  67. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/wildfire_response/__init__.py +0 -0
  68. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/wildfire_response/model_aircraft.py +0 -0
  69. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/wildfire_response/model_environment.py +0 -0
  70. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/airspacelib/wildfire_response/model_main.py +0 -0
  71. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/cooling_tank/__init__.py +0 -0
  72. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/cooling_tank/model_main.py +0 -0
  73. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/cooling_tank/model_optimization.py +0 -0
  74. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/cooling_tank/test_tank.py +0 -0
  75. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/electric_power_system/__init__.py +0 -0
  76. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/electric_power_system/model_main.py +0 -0
  77. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/electric_power_system/test_model.py +0 -0
  78. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/human_hazard_mitigation/__init__.py +0 -0
  79. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/human_hazard_mitigation/model_main.py +0 -0
  80. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/multirotor_drone/__init__.py +0 -0
  81. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/multirotor_drone/model_dynamic.py +0 -0
  82. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/multirotor_drone/model_hierarchical.py +0 -0
  83. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/multirotor_drone/model_rural.py +0 -0
  84. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/multirotor_drone/model_static.py +0 -0
  85. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/multirotor_drone/script_opt_model_rural.py +0 -0
  86. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/multirotor_drone/test_multirotor.py +0 -0
  87. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/__init__.py +0 -0
  88. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/model_degradation.py +0 -0
  89. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/model_human.py +0 -0
  90. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/model_main.py +0 -0
  91. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/model_troupe.py +0 -0
  92. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/script_ea_analysis.py +0 -0
  93. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/script_mode_space.py +0 -0
  94. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/script_search_rover.py +0 -0
  95. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/navigating_rover/test_rover.py +0 -0
  96. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/state_communication/__init__.py +0 -0
  97. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/state_communication/model_main.py +0 -0
  98. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/state_communication/test_multiflow.py +0 -0
  99. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/__init__.py +0 -0
  100. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/model_indiv.py +0 -0
  101. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/model_main.py +0 -0
  102. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/model_stochastic.py +0 -0
  103. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/script_jointfaults.py +0 -0
  104. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/script_parallelism.py +0 -0
  105. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/test_pump.py +0 -0
  106. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/test_pump_example.py +0 -0
  107. {fmdtools-2.5.2 → fmdtools-2.5.3}/examples/water_pump/test_pump_stochastic.py +0 -0
  108. {fmdtools-2.5.2 → fmdtools-2.5.3}/fmdtools.egg-info/dependency_links.txt +0 -0
  109. {fmdtools-2.5.2 → fmdtools-2.5.3}/fmdtools.egg-info/requires.txt +0 -0
  110. {fmdtools-2.5.2 → fmdtools-2.5.3}/fmdtools.egg-info/top_level.txt +0 -0
  111. {fmdtools-2.5.2 → fmdtools-2.5.3}/setup.cfg +0 -0
  112. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/__init__.py +0 -0
  113. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/__init__.py +0 -0
  114. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/base.py +0 -0
  115. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/label.py +0 -0
  116. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/model.py +0 -0
  117. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/style.py +0 -0
  118. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/analyze/tabulate.py +0 -0
  119. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/__init__.py +0 -0
  120. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/architecture/__init__.py +0 -0
  121. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/architecture/action.py +0 -0
  122. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/architecture/base.py +0 -0
  123. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/architecture/component.py +0 -0
  124. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/architecture/function.py +0 -0
  125. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/architecture/geom.py +0 -0
  126. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/block/__init__.py +0 -0
  127. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/block/action.py +0 -0
  128. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/block/component.py +0 -0
  129. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/block/function.py +0 -0
  130. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/container/__init__.py +0 -0
  131. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/container/base.py +0 -0
  132. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/container/mode.py +0 -0
  133. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/container/parameter.py +0 -0
  134. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/container/state.py +0 -0
  135. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/environment.py +0 -0
  136. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/flow/__init__.py +0 -0
  137. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/flow/base.py +0 -0
  138. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/flow/commsflow.py +0 -0
  139. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/flow/multiflow.py +0 -0
  140. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/object/__init__.py +0 -0
  141. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/object/base.py +0 -0
  142. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/object/coords.py +0 -0
  143. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/object/geom.py +0 -0
  144. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/object/timer.py +0 -0
  145. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/define/pathplan.py +0 -0
  146. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/sim/__init__.py +0 -0
  147. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/sim/propagate.py +0 -0
  148. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/sim/scenario.py +0 -0
  149. {fmdtools-2.5.2 → fmdtools-2.5.3}/src/fmdtools/sim/search.py +0 -0
  150. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/__init__.py +0 -0
  151. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/common.py +0 -0
  152. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_array_random_states.py +0 -0
  153. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_calc_metric_ci_constant_outputs.py +0 -0
  154. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_comparison_groups.py +0 -0
  155. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_confidence_history_clipping.py +0 -0
  156. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_error_history_plots.py +0 -0
  157. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_fault_sample_without_nominal.py +0 -0
  158. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_fault_time_without_traces.py +0 -0
  159. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_geom.py +0 -0
  160. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_graph.py +0 -0
  161. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_hierarchical.py +0 -0
  162. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_history_standard_deviation.py +0 -0
  163. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_join_phasemaps.py +0 -0
  164. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_joint_fault_weights.py +0 -0
  165. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_nested_nominal_groups.py +0 -0
  166. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_nesting_custom_separators.py +0 -0
  167. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_parameter_sample_seeds.py +0 -0
  168. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_parameter_subsampling_types.py +0 -0
  169. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_pathplan.py +0 -0
  170. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_phase_timestep.py +0 -0
  171. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_pruned_fault_times.py +0 -0
  172. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_result.py +0 -0
  173. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_sample.py +0 -0
  174. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_scenario.py +0 -0
  175. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_scenario_defaults.py +0 -0
  176. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_table_factor_indices.py +0 -0
  177. {fmdtools-2.5.2 → fmdtools-2.5.3}/tests/test_uniform_density.py +0 -0
@@ -1,4 +1,4 @@
1
- fmdtools version 2.5.2
1
+ fmdtools version 2.5.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.5.2
3
+ Version: 2.5.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.5.2
7
+ License: fmdtools version 2.5.3
8
8
  ---------------------------
9
9
  Apache License
10
10
  Version 2.0, January 2004
@@ -394,7 +394,7 @@ More guidance on development installations is provided in [How to Contribute](ht
394
394
  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`` in the context of a development install you can checkout the tag corresponding to the version you want, e.g.:
395
395
 
396
396
  ```
397
- git checkout v2.5.2
397
+ git checkout v2.5.3
398
398
  ```
399
399
 
400
400
 
@@ -548,8 +548,8 @@ To cite a particular version of the fmdtools, you may use:
548
548
  author = {{NASA}},
549
549
  title = {fmdtools},
550
550
  url = {https://github.com/nasa/fmdtools},
551
- version = {2.5.2},
552
- date = {2026-9-28},
551
+ version = {2.5.3},
552
+ date = {2026-9-30},
553
553
  }
554
554
  ```
555
555
 
@@ -123,7 +123,7 @@ More guidance on development installations is provided in [How to Contribute](ht
123
123
  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`` in the context of a development install you can checkout the tag corresponding to the version you want, e.g.:
124
124
 
125
125
  ```
126
- git checkout v2.5.2
126
+ git checkout v2.5.3
127
127
  ```
128
128
 
129
129
 
@@ -277,8 +277,8 @@ To cite a particular version of the fmdtools, you may use:
277
277
  author = {{NASA}},
278
278
  title = {fmdtools},
279
279
  url = {https://github.com/nasa/fmdtools},
280
- version = {2.5.2},
281
- date = {2026-9-28},
280
+ version = {2.5.3},
281
+ date = {2026-9-30},
282
282
  }
283
283
  ```
284
284
 
@@ -322,6 +322,7 @@ class Drone(DroneRural):
322
322
  ('taxi', 12, 20)),
323
323
  end_time=30,
324
324
  units='min',
325
+ use_local=False,
325
326
  dt=0.1)
326
327
 
327
328
  def init_architecture(self, **kwargs):
@@ -499,6 +500,9 @@ if __name__ == "__main__":
499
500
  # UrbanDroneEnvironment("a")
500
501
  # PlanPath._init_environment("a")
501
502
  # p = PlanPath("test", {})
503
+ mdl = Drone(sp={'end_condition': 'scenario_finished', 'dt': 0.1}, t={'local_dt': 0.1})
504
+ results_fault, hist_fault = propagate.one_fault(mdl, "affect_dof.ca.comps.lr", "mechbreak", time=1.4)
505
+ fig, ax = plot_env_with_traj_z(hist_fault, mdl, title = "Urban Drone Trajectory")
502
506
 
503
507
  e = UrbanDroneEnvironment("env")
504
508
  e.c.show({"height": {}})
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmdtools
3
- Version: 2.5.2
3
+ Version: 2.5.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.5.2
7
+ License: fmdtools version 2.5.3
8
8
  ---------------------------
9
9
  Apache License
10
10
  Version 2.0, January 2004
@@ -394,7 +394,7 @@ More guidance on development installations is provided in [How to Contribute](ht
394
394
  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`` in the context of a development install you can checkout the tag corresponding to the version you want, e.g.:
395
395
 
396
396
  ```
397
- git checkout v2.5.2
397
+ git checkout v2.5.3
398
398
  ```
399
399
 
400
400
 
@@ -548,8 +548,8 @@ To cite a particular version of the fmdtools, you may use:
548
548
  author = {{NASA}},
549
549
  title = {fmdtools},
550
550
  url = {https://github.com/nasa/fmdtools},
551
- version = {2.5.2},
552
- date = {2026-9-28},
551
+ version = {2.5.3},
552
+ date = {2026-9-30},
553
553
  }
554
554
  ```
555
555
 
@@ -127,31 +127,49 @@ src/fmdtools/sim/scenario.py
127
127
  src/fmdtools/sim/search.py
128
128
  tests/__init__.py
129
129
  tests/common.py
130
+ tests/test_array_differences.py
130
131
  tests/test_array_random_states.py
132
+ tests/test_array_result_equality.py
133
+ tests/test_bootstrap_statistic_arguments.py
131
134
  tests/test_calc_metric_ci_constant_outputs.py
132
135
  tests/test_comparison_groups.py
133
136
  tests/test_confidence_history_clipping.py
134
137
  tests/test_error_history_plots.py
138
+ tests/test_exact_nominal_expectation.py
139
+ tests/test_explicit_result_file_formats.py
140
+ tests/test_exponential_rayleigh_draw_sizes.py
135
141
  tests/test_fault_sample_without_nominal.py
136
142
  tests/test_fault_time_without_traces.py
143
+ tests/test_gamma_sampling_density.py
137
144
  tests/test_geom.py
138
145
  tests/test_graph.py
139
146
  tests/test_hierarchical.py
147
+ tests/test_history_phase_identifiers.py
140
148
  tests/test_history_standard_deviation.py
149
+ tests/test_independent_single_replicate_seeds.py
150
+ tests/test_individual_result_paths.py
151
+ tests/test_integer_draw_mass.py
141
152
  tests/test_join_phasemaps.py
142
153
  tests/test_joint_fault_weights.py
154
+ tests/test_json_result_arrays.py
155
+ tests/test_named_metric_rounding.py
143
156
  tests/test_nested_nominal_groups.py
144
157
  tests/test_nesting_custom_separators.py
145
158
  tests/test_packages.py
146
159
  tests/test_parameter_sample_seeds.py
147
160
  tests/test_parameter_subsampling_types.py
161
+ tests/test_pareto_density.py
148
162
  tests/test_pathplan.py
149
163
  tests/test_phase_timestep.py
150
164
  tests/test_pruned_fault_times.py
151
165
  tests/test_result.py
166
+ tests/test_result_folder_selection.py
152
167
  tests/test_sample.py
153
168
  tests/test_scenario.py
154
169
  tests/test_scenario_defaults.py
170
+ tests/test_standard_t_density.py
155
171
  tests/test_table_factor_indices.py
156
172
  tests/test_time.py
157
- tests/test_uniform_density.py
173
+ tests/test_uniform_density.py
174
+ tests/test_unit_random_density.py
175
+ tests/test_vonmises_density.py
@@ -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.5.2"
21
+ version="2.5.3"
22
22
  readme = "README.md"
23
23
  requires-python = ">=3.12"
24
24
 
@@ -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.5.2'
25
+ __version__ = '2.5.3'
26
26
  __copyright__ = "Copyright 2024 United States Government"
27
27
  __license__ = "Apache-2.0"
@@ -42,6 +42,7 @@ from matplotlib import rcParams
42
42
  import inspect
43
43
  from scipy.stats import bootstrap
44
44
  from fmdtools.define.base import filter_kwargs, round_float
45
+ from functools import partial
45
46
 
46
47
 
47
48
  plt.rcParams['pdf.fonttype'] = 42
@@ -180,11 +181,11 @@ def create_indiv_filename(filename, indiv_id, splitchar='_'):
180
181
  --------
181
182
  >>> create_indiv_filename("hi.csv", "4")
182
183
  'hi_4.csv'
184
+ >>> create_indiv_filename("runs.v1/results.v2.csv", "4", splitchar="/")
185
+ 'runs.v1/results.v2/4.csv'
183
186
  """
184
- filename_parts = filename.split(".")
185
- filename_parts.insert(1, '.')
186
- filename_parts.insert(1, splitchar+indiv_id)
187
- return "".join(filename_parts)
187
+ stem, extension = os.path.splitext(filename)
188
+ return stem + splitchar + indiv_id + extension
188
189
 
189
190
 
190
191
  def load_folder(folder, filetype):
@@ -198,20 +199,17 @@ def load_folder(folder, filetype):
198
199
  folder : str
199
200
  Name of the folder. Must be in the current directory
200
201
  filetype : str
201
- Type of files in the folder ('pickle', 'csv', or 'json')
202
+ Type of files in the folder ('npz', 'csv', or 'json'). Other extensions
203
+ and subdirectories are ignored; matching files are not searched recursively.
202
204
 
203
205
  Returns
204
206
  -------
205
207
  files_to_read : list
206
208
  files to load for results/mdlhists.
207
209
  """
208
- files = os.listdir(folder)
209
- files_toread = []
210
- for file in files:
211
- read_filetype = auto_filetype(file)
212
- if read_filetype == filetype:
213
- files_toread.append(file)
214
- return files_toread
210
+ return [file for file in os.listdir(folder)
211
+ if file.endswith('.' + filetype)
212
+ and os.path.isfile(os.path.join(folder, file))]
215
213
 
216
214
 
217
215
  def metric_preamble(data, dtype=None, rates=None, r_dtype=None, r_norm=False):
@@ -255,7 +253,8 @@ def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
255
253
  r_norm : bool, optional
256
254
  Whether to normalize rates. The default is False.
257
255
  round_value : bool, optional
258
- Whether to round the value. Default is True.
256
+ Whether to round the final value, including named metrics.
257
+ Applied once after evaluation. Default is True.
259
258
  res : float
260
259
  Resolution to round to. Default is 1e-6.
261
260
  min_r : int
@@ -283,8 +282,9 @@ def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
283
282
  """
284
283
  if isinstance(method, str):
285
284
  method = eval("calc_"+method)
285
+ # Named helpers call calc_metric again; round only the final result.
286
286
  metric = method(data, args=args, axis=axis, dtype=dtype, rates=rates,
287
- r_dtype=r_dtype, r_norm=r_norm, **kwargs)
287
+ r_dtype=r_dtype, r_norm=r_norm, round_value=False, **kwargs)
288
288
  else:
289
289
  vals = metric_preamble(data, dtype, rates, r_dtype, r_norm)
290
290
  metric = method(vals, *args, **filter_kwargs(method, **kwargs, axis=axis))
@@ -309,7 +309,8 @@ def calc_metric_ci(data, method=np.average, return_anyway=False, interval=None,
309
309
  axis : int
310
310
  Axis along which to calculate the confidence interval(s). The default is 0.
311
311
  **kwargs : kwargs
312
- Keyword arguments to calc_metric or scipy.bootstrap.
312
+ Keyword arguments to the statistic, preprocessing, or scipy.bootstrap.
313
+ Statistic arguments are used for both the point estimate and resamples.
313
314
 
314
315
  Returns
315
316
  -------
@@ -339,15 +340,16 @@ def calc_metric_ci(data, method=np.average, return_anyway=False, interval=None,
339
340
  val = np.take(vals, [0], axis=axis)
340
341
  if "weights" in kwargs and kwargs['weights'] is not None:
341
342
  raise Exception("Weights not able to be used w- bootstrap--use rates instead.")
342
- met_val = method(vals, axis=axis, **filter_kwargs(method, **kwargs))
343
+ statistic = partial(method, **filter_kwargs(method, **kwargs))
344
+ met_val = statistic(vals, axis=axis)
343
345
  vals_vary = vals == val
344
346
  # Preserve the existing policy for globally identical data.
345
347
  if not np.all(vals == vals.flat[0]):
346
348
  if np.any(np.all(vals_vary, axis=axis)):
347
349
  # use more robust/basic algorithm if some indices don't vary
348
- bs = bootstrap([vals], method, axis=axis, method="basic", **bs_kwar)
350
+ bs = bootstrap([vals], statistic, axis=axis, method="basic", **bs_kwar)
349
351
  else:
350
- bs = bootstrap([vals], method, axis=axis, **bs_kwar)
352
+ bs = bootstrap([vals], statistic, axis=axis, **bs_kwar)
351
353
  return met_val, bs.confidence_interval.low, bs.confidence_interval.high
352
354
  elif return_anyway:
353
355
  return met_val, met_val, met_val
@@ -182,6 +182,9 @@ class History(Result):
182
182
  """
183
183
  Find the values of two histories which are different.
184
184
 
185
+ Array-valued entries are retained when any element of their difference
186
+ is nonzero; the full difference array is returned.
187
+
185
188
  Parameters
186
189
  ----------
187
190
  other : History
@@ -195,7 +198,7 @@ class History(Result):
195
198
  """
196
199
  diff = self-other
197
200
  different = self.__class__()
198
- different.data = {k: v for k, v in diff.items() if any(v)}
201
+ different.data = {k: v for k, v in diff.items() if np.any(v)}
199
202
  return different
200
203
 
201
204
  def copy(self):
@@ -388,9 +388,9 @@ def from_hist(hist, fxn_modephases='all', dt=1.0):
388
388
  Dictionary of distict phases that the system functions pass through,
389
389
  of the form: {'fxn': PhaseMap} where each phase is defined by its
390
390
  corresponding mode in the modelhist.
391
- Phases are numbered mode, mode1, mode2 for multiple modes and given a
392
- corresponding phasemap {mode: {mode, mode1, mode2}} mapping modes to
393
- phases for future sampling.
391
+ Repeated phases are numbered mode, mode1, mode2, skipping suffixes that
392
+ would collide with actual mode names or previously generated phase names.
393
+ The modephase mapping groups each mode's intervals for future sampling.
394
394
 
395
395
  Examples
396
396
  --------
@@ -411,6 +411,8 @@ def from_hist(hist, fxn_modephases='all', dt=1.0):
411
411
  fxn = k[k.index('m')-1]
412
412
  if len(modehist) != 0:
413
413
  modes = OrderedSet(modehist)
414
+ # Reserve actual mode names before generating repeated-phase suffixes.
415
+ used_phaseids = set(modes)
414
416
  modephases = dict.fromkeys(modes)
415
417
  phases_unsorted = dict()
416
418
  for mode in modes:
@@ -427,6 +429,10 @@ def from_hist(hist, fxn_modephases='all', dt=1.0):
427
429
  startind = modeinds[i+1]
428
430
  phasenum += 1
429
431
  phaseid = mode+str(phasenum)
432
+ while phaseid in used_phaseids:
433
+ phasenum += 1
434
+ phaseid = mode+str(phasenum)
435
+ used_phaseids.add(phaseid)
430
436
  phases = dict(sorted(phases_unsorted.items(),
431
437
  key=lambda item: item[1][0]))
432
438
  if fxn_modephases == 'all' or fxn in fxn_modephases:
@@ -239,7 +239,8 @@ class Result(UserDict):
239
239
  Check that the two values of the dictionary are equal.
240
240
 
241
241
  Enables the syntax result1 == result2, which returns True/False depending on if
242
- the keys/values are the same.
242
+ the keys/values are the same. Array values must have the same shape and
243
+ elements, without broadcasting or tolerances. NaN values remain unequal.
243
244
 
244
245
  Parameters
245
246
  ----------
@@ -274,10 +275,11 @@ class Result(UserDict):
274
275
  if self.keys() != other.keys():
275
276
  return False
276
277
  else:
277
- return all([all(v == other.data.get(k, None))
278
- if isinstance(v, np.ndarray)
279
- else v == other.data.get(k, None)
280
- for k, v in self.data.items()])
278
+ return all(np.array_equal(v, other.data[k])
279
+ if isinstance(v, np.ndarray) or
280
+ isinstance(other.data[k], np.ndarray)
281
+ else v == other.data[k]
282
+ for k, v in self.data.items())
281
283
 
282
284
  def __sub__(self, other):
283
285
  """
@@ -324,6 +326,9 @@ class Result(UserDict):
324
326
  """
325
327
  Find the values of two results which are different.
326
328
 
329
+ Array-valued entries are retained when any element of their difference
330
+ is nonzero; the full difference array is returned.
331
+
327
332
  Parameters
328
333
  ----------
329
334
  other : Result
@@ -337,7 +342,7 @@ class Result(UserDict):
337
342
  """
338
343
  diff = self-other
339
344
  different = self.__class__()
340
- different.data = {k: v for k, v in diff.items() if v}
345
+ different.data = {k: v for k, v in diff.items() if np.any(v)}
341
346
  return different
342
347
 
343
348
  def keys(self):
@@ -466,7 +471,7 @@ class Result(UserDict):
466
471
 
467
472
  def load(filename, filetype="", renest_dict=False, indiv=False):
468
473
  """Load as Result using :func:`load'."""
469
- inputdict = load(filename, filetype="", renest_dict=renest_dict,
474
+ inputdict = load(filename, filetype=filetype, renest_dict=renest_dict,
470
475
  indiv=indiv, Rclass=Result)
471
476
  return fromdict(Result, inputdict)
472
477
 
@@ -709,15 +714,18 @@ class Result(UserDict):
709
714
  """
710
715
  Save a given result variable (result or history) to a file filename.
711
716
 
712
- Files can be saved as npz, csv, or json.
717
+ Files can be saved as npz, csv, or json. JSON converts arrays to
718
+ nested lists of Python values (or a scalar for zero-dimensional arrays).
719
+ Array dtype and shape metadata are not stored in JSON.
713
720
 
714
721
  Parameters
715
722
  ----------
716
723
  filename : str
717
724
  File name for the file. Can be nested in a folder if desired.
718
725
  filetype : str, optional
719
- Optional specifier of file type (if not included in filename).
720
- The default is "".
726
+ Format to write, overriding the filename extension when supplied.
727
+ The default "" infers the format from the extension. The exact
728
+ filename is used for all formats; no extension is appended.
721
729
  overwrite : bool, optional
722
730
  Whether to overwrite existing files with this name.
723
731
  The default is False.
@@ -741,8 +749,8 @@ class Result(UserDict):
741
749
  else:
742
750
  res_to_save = self
743
751
  res_to_save = res_to_save.flatten()
744
- np.savez(filename, **res_to_save)
745
- elif filename[-4:] == '.csv':
752
+ np.savez(file_handle, **res_to_save)
753
+ elif filetype == 'csv':
746
754
  # add support for nested dict mdlhist using flatten_hist?
747
755
  variable = variable.flatten()
748
756
  with open(filename, 'w', newline='') as file_handle:
@@ -754,13 +762,13 @@ class Result(UserDict):
754
762
  writer.writerows(zip(*variable.values()))
755
763
  else:
756
764
  writer.writerow([*variable.values()])
757
- elif filename[-5:] == '.json':
765
+ elif filetype == 'json':
758
766
  with open(filename, 'w', encoding='utf8') as file_handle:
759
767
  variable = variable.flatten()
760
768
  new_variable = {}
761
769
  for key in variable:
762
770
  if isinstance(variable[key], np.ndarray):
763
- new_variable[str(key)] = [var.item() for var in variable[key]]
771
+ new_variable[str(key)] = variable[key].tolist()
764
772
  else:
765
773
  new_variable[str(key)] = variable[key]
766
774
  if result_id:
@@ -798,7 +806,8 @@ class Result(UserDict):
798
806
  app : SampleApproach, optional
799
807
  Approach to use for weights (via rates). The default is [].
800
808
  with_nominal : bool, optional
801
- Whether to include the nominal scenario in the expectation.
809
+ Whether to include the scenario named exactly 'nominal' in the
810
+ expectation. Other names containing 'nominal' remain included.
802
811
  The default is False.
803
812
  difference_from_nominal : bool, optional
804
813
  Whether to calculated the difference of the expectation from nominal.
@@ -816,7 +825,7 @@ class Result(UserDict):
816
825
 
817
826
  nomhist = {k: v for k, v in mh.nominal.items() if is_numeric(v)}
818
827
  newhists = {k: hist for k, hist in mh.items()
819
- if not ('nominal' in k and not (with_nominal))}
828
+ if with_nominal or k != 'nominal'}
820
829
  if app:
821
830
  scenario_rates = {scen.name: scen.rate for scen in app.scenarios()}
822
831
  if with_nominal:
@@ -442,7 +442,8 @@ def nan_to_x(metric, x=0.0):
442
442
 
443
443
  def gen_timerange(start_time, end_time, dt=1.0, min_r=7):
444
444
  """Generate the times in a given interval given the timestep dt."""
445
- return np.round(np.arange(start_time, end_time + np.power(10.0, -min_r), dt), min_r)
445
+ return round_float(np.arange(start_time, end_time + np.power(10.0, -min_r), dt),
446
+ res=dt, min_r=min_r)
446
447
 
447
448
 
448
449
  def get_code_attrs(obj):
@@ -23,7 +23,7 @@ specific language governing permissions and limitations under the License.
23
23
  """
24
24
 
25
25
  from fmdtools.define.base import gen_timerange, is_iter, get_var, filter_kwargs
26
- from fmdtools.define.base import copy_dict_objs
26
+ from fmdtools.define.base import copy_dict_objs, round_float
27
27
  from fmdtools.define.object.base import BaseObject
28
28
  from fmdtools.define.container.parameter import Parameter
29
29
  from fmdtools.define.container.time import Time
@@ -35,6 +35,8 @@ from fmdtools.analyze.graph.base import Graph
35
35
  import itertools
36
36
  import warnings
37
37
  import numpy as np
38
+ import math
39
+ from decimal import Decimal
38
40
 
39
41
 
40
42
  class SimParam(Parameter, readonly=True):
@@ -114,7 +116,7 @@ class SimParam(Parameter, readonly=True):
114
116
  ": " + str(self.phases) +
115
117
  " Ensure max of each phase < min of each other phase")
116
118
 
117
- def get_timerange(self, start_time=None, end_time=None, min_r=7):
119
+ def get_timerange(self, start_time=None, end_time=None, dt=None, min_r=7):
118
120
  """
119
121
  Generate the timerange to simulate over.
120
122
 
@@ -127,7 +129,9 @@ class SimParam(Parameter, readonly=True):
127
129
  start_time = self.start_time
128
130
  if end_time is None:
129
131
  end_time = self.end_time
130
- return gen_timerange(start_time, end_time, self.dt, min_r)
132
+ if dt is None:
133
+ dt = self.dt
134
+ return gen_timerange(start_time, end_time, dt, min_r)
131
135
 
132
136
  def get_histrange(self, start_time=0.0, end_time=None):
133
137
  """Get the history range associated with the SimParam."""
@@ -140,32 +144,45 @@ class SimParam(Parameter, readonly=True):
140
144
  histrange = self.track_times[1]
141
145
  return histrange
142
146
 
143
- def get_hist_ind(self, t_ind, t):
147
+ def get_hist_ind(self, t_ind, t, local_dt=None):
144
148
  """
145
149
  Get the index of the history given the simulation time/index and shift.
146
150
 
151
+ Returns log as true if the index of the history and simulation line up, otherwise
152
+ the simulation is not to be logged.
153
+
147
154
  Examples
148
155
  --------
149
156
  >>> SimParam().get_hist_ind(2, 2.0)
150
- 2
157
+ (True, 2)
151
158
  >>> SimParam(track_times=('interval', 2)).get_hist_ind(4, 4.0)
152
- 2
153
- """
159
+ (True, 2)
160
+ >>> SimParam().get_hist_ind(2, 2.0, 0.1)
161
+ (False, 1)
162
+ >>> SimParam().get_hist_ind(10, 2.0, 0.1)
163
+ (True, 1)
164
+ """
165
+ log = True
166
+ if local_dt is not None:
167
+ t_dec = Decimal(str(round_float(t_ind*local_dt, res=local_dt)))
168
+ dt = Decimal(str(self.dt))
169
+ log = t_dec % dt == 0.0
170
+ t_ind = math.ceil(t_dec/dt)
171
+
154
172
  if self.track_times[0] == 'all':
155
173
  t_ind_rec = t_ind
156
174
  elif self.track_times[0] == 'interval':
175
+ log = t_ind % self.track_times[1] == 0
157
176
  t_ind_rec = t_ind//self.track_times[1]
158
177
  elif self.track_times[0] == 'times':
159
178
  t_ind_rec = self.track_times[1].index(t)
160
179
  else:
161
180
  raise Exception("Invalid argument, track_times=" + str(self.track_times))
162
- return t_ind_rec
181
+ return bool(log), t_ind_rec
163
182
 
164
183
  def get_sub_kwargs(self):
165
184
  """Get keyword arguments for contained sim from larger sim."""
166
185
  kwargs = self.asdict()
167
- if not self.use_local:
168
- kwargs.pop("dt")
169
186
  kwargs['end_condition'] = ""
170
187
  return kwargs
171
188
 
@@ -278,8 +295,8 @@ class Simulable(BaseObject):
278
295
 
279
296
  Returns
280
297
  -------
281
- result : TYPE
282
- DESCRIPTION.
298
+ result : Result
299
+ Result to return given to_return input.
283
300
  """
284
301
  result = Result()
285
302
  self.get_endclass(to_return=to_return, result=result, nomresult=nomresult,
@@ -762,7 +779,7 @@ class Simulable(BaseObject):
762
779
  if time == 'end':
763
780
  time = self.sp.end_time
764
781
  start_time, end_time = self.t.get_sim_times(time)
765
- sim_times = self.sp.get_timerange(start_time, end_time)
782
+ sim_times = self.sp.get_timerange(start_time, end_time, dt=self.t.dt)
766
783
  for t in sim_times:
767
784
  if t == end_time:
768
785
  if copy:
@@ -778,8 +795,9 @@ class Simulable(BaseObject):
778
795
  self.set_sub_faults()
779
796
  if inc_at == "all" or (inc_at == "time" and t == time):
780
797
  self.inc_sim_time()
781
- t_ind = self.sp.get_hist_ind(self.t.t_ind, self.t.time)
782
- self.h.log(self, t_ind, self.t.time)
798
+ log, t_ind = self.sp.get_hist_ind(self.t.t_ind, self.t.time, self.t.dt)
799
+ if log:
800
+ self.h.log(self, t_ind, self.t.time)
783
801
  if self.sp.end_condition and not copy:
784
802
  if get_var(self, self.sp.end_condition)():
785
803
  break