fmdtools 2.5.1__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.1 → fmdtools-2.5.3}/LICENSE +1 -1
  2. {fmdtools-2.5.1/fmdtools.egg-info → fmdtools-2.5.3}/PKG-INFO +9 -6
  3. {fmdtools-2.5.1 → fmdtools-2.5.3}/README.md +7 -4
  4. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/multirotor_drone/model_urban.py +4 -0
  5. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/multirotor_drone/test_multirotor.py +1 -1
  6. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/test_rover.py +4 -3
  7. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/model_main.py +8 -6
  8. {fmdtools-2.5.1 → fmdtools-2.5.3/fmdtools.egg-info}/PKG-INFO +9 -6
  9. {fmdtools-2.5.1 → fmdtools-2.5.3}/fmdtools.egg-info/SOURCES.txt +42 -1
  10. {fmdtools-2.5.1 → fmdtools-2.5.3}/pyproject.toml +1 -1
  11. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/__init__.py +1 -1
  12. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/common.py +46 -35
  13. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/history.py +16 -4
  14. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/phases.py +16 -6
  15. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/result.py +57 -34
  16. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/tabulate.py +3 -1
  17. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/architecture/geom.py +4 -4
  18. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/base.py +12 -8
  19. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/block/base.py +33 -15
  20. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/container/base.py +2 -2
  21. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/container/rand.py +132 -41
  22. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/container/time.py +8 -2
  23. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/environment.py +6 -7
  24. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/object/base.py +67 -43
  25. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/object/coords.py +2 -2
  26. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/object/geom.py +60 -19
  27. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/sim/propagate.py +7 -3
  28. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/sim/sample.py +20 -15
  29. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/sim/scenario.py +3 -3
  30. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/sim/search.py +2 -2
  31. fmdtools-2.5.3/tests/test_array_differences.py +153 -0
  32. fmdtools-2.5.3/tests/test_array_random_states.py +182 -0
  33. fmdtools-2.5.3/tests/test_array_result_equality.py +167 -0
  34. fmdtools-2.5.3/tests/test_bootstrap_statistic_arguments.py +196 -0
  35. fmdtools-2.5.3/tests/test_calc_metric_ci_constant_outputs.py +218 -0
  36. fmdtools-2.5.3/tests/test_comparison_groups.py +162 -0
  37. fmdtools-2.5.3/tests/test_confidence_history_clipping.py +181 -0
  38. fmdtools-2.5.3/tests/test_error_history_plots.py +197 -0
  39. fmdtools-2.5.3/tests/test_exact_nominal_expectation.py +166 -0
  40. fmdtools-2.5.3/tests/test_explicit_result_file_formats.py +176 -0
  41. fmdtools-2.5.3/tests/test_exponential_rayleigh_draw_sizes.py +239 -0
  42. fmdtools-2.5.3/tests/test_fault_sample_without_nominal.py +231 -0
  43. fmdtools-2.5.3/tests/test_fault_time_without_traces.py +141 -0
  44. fmdtools-2.5.3/tests/test_gamma_sampling_density.py +233 -0
  45. fmdtools-2.5.3/tests/test_history_phase_identifiers.py +204 -0
  46. fmdtools-2.5.3/tests/test_history_standard_deviation.py +95 -0
  47. fmdtools-2.5.3/tests/test_independent_single_replicate_seeds.py +201 -0
  48. fmdtools-2.5.3/tests/test_individual_result_paths.py +165 -0
  49. fmdtools-2.5.3/tests/test_integer_draw_mass.py +266 -0
  50. fmdtools-2.5.3/tests/test_join_phasemaps.py +94 -0
  51. fmdtools-2.5.3/tests/test_joint_fault_weights.py +116 -0
  52. fmdtools-2.5.3/tests/test_json_result_arrays.py +163 -0
  53. fmdtools-2.5.3/tests/test_named_metric_rounding.py +152 -0
  54. fmdtools-2.5.3/tests/test_nested_nominal_groups.py +200 -0
  55. fmdtools-2.5.3/tests/test_nesting_custom_separators.py +151 -0
  56. {fmdtools-2.5.1 → fmdtools-2.5.3}/tests/test_packages.py +1 -1
  57. fmdtools-2.5.3/tests/test_parameter_sample_seeds.py +104 -0
  58. fmdtools-2.5.3/tests/test_parameter_subsampling_types.py +147 -0
  59. fmdtools-2.5.3/tests/test_pareto_density.py +203 -0
  60. fmdtools-2.5.3/tests/test_phase_timestep.py +73 -0
  61. fmdtools-2.5.3/tests/test_pruned_fault_times.py +163 -0
  62. fmdtools-2.5.3/tests/test_result.py +173 -0
  63. fmdtools-2.5.3/tests/test_result_folder_selection.py +191 -0
  64. fmdtools-2.5.3/tests/test_sample.py +165 -0
  65. fmdtools-2.5.3/tests/test_scenario.py +111 -0
  66. fmdtools-2.5.3/tests/test_scenario_defaults.py +168 -0
  67. fmdtools-2.5.3/tests/test_standard_t_density.py +192 -0
  68. fmdtools-2.5.3/tests/test_table_factor_indices.py +154 -0
  69. fmdtools-2.5.3/tests/test_time.py +101 -0
  70. fmdtools-2.5.3/tests/test_uniform_density.py +83 -0
  71. fmdtools-2.5.3/tests/test_unit_random_density.py +176 -0
  72. fmdtools-2.5.3/tests/test_vonmises_density.py +222 -0
  73. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/__init__.py +0 -0
  74. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airport_taxiway/model_asset.py +0 -0
  75. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airport_taxiway/model_atc.py +0 -0
  76. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airport_taxiway/model_common.py +0 -0
  77. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airport_taxiway/model_main.py +0 -0
  78. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airport_taxiway/test_asset.py +0 -0
  79. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airport_taxiway/test_model.py +0 -0
  80. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/__init__.py +0 -0
  81. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/__init__.py +0 -0
  82. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/aircraft.py +0 -0
  83. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/aircraftarchitecture.py +0 -0
  84. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/arch/__init__.py +0 -0
  85. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/arch/aviate.py +0 -0
  86. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/arch/controlflight.py +0 -0
  87. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/arch/flows.py +0 -0
  88. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/arch/holdpayload.py +0 -0
  89. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/arch/perceiveenvironment.py +0 -0
  90. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/arch/storeee.py +0 -0
  91. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/parameter.py +0 -0
  92. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/base/state.py +0 -0
  93. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/contingency_management/__init__.py +0 -0
  94. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/contingency_management/model_aircraft.py +0 -0
  95. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/contingency_management/model_environment.py +0 -0
  96. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/contingency_management/model_flightplanner.py +0 -0
  97. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/__init__.py +0 -0
  98. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/model_drone.py +0 -0
  99. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/model_environment.py +0 -0
  100. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/model_main.py +0 -0
  101. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/water_rescue/model_responder.py +0 -0
  102. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/wildfire_response/__init__.py +0 -0
  103. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/wildfire_response/model_aircraft.py +0 -0
  104. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/wildfire_response/model_environment.py +0 -0
  105. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/airspacelib/wildfire_response/model_main.py +0 -0
  106. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/cooling_tank/__init__.py +0 -0
  107. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/cooling_tank/model_main.py +0 -0
  108. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/cooling_tank/model_optimization.py +0 -0
  109. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/cooling_tank/test_tank.py +0 -0
  110. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/electric_power_system/__init__.py +0 -0
  111. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/electric_power_system/model_main.py +0 -0
  112. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/electric_power_system/test_model.py +0 -0
  113. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/human_hazard_mitigation/__init__.py +0 -0
  114. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/human_hazard_mitigation/model_main.py +0 -0
  115. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/multirotor_drone/__init__.py +0 -0
  116. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/multirotor_drone/model_dynamic.py +0 -0
  117. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/multirotor_drone/model_hierarchical.py +0 -0
  118. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/multirotor_drone/model_rural.py +0 -0
  119. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/multirotor_drone/model_static.py +0 -0
  120. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/multirotor_drone/script_opt_model_rural.py +0 -0
  121. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/__init__.py +0 -0
  122. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/model_degradation.py +0 -0
  123. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/model_human.py +0 -0
  124. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/model_main.py +0 -0
  125. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/model_troupe.py +0 -0
  126. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/script_ea_analysis.py +0 -0
  127. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/script_mode_space.py +0 -0
  128. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/navigating_rover/script_search_rover.py +0 -0
  129. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/state_communication/__init__.py +0 -0
  130. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/state_communication/model_main.py +0 -0
  131. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/state_communication/test_multiflow.py +0 -0
  132. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/__init__.py +0 -0
  133. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/model_indiv.py +0 -0
  134. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/model_stochastic.py +0 -0
  135. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/script_jointfaults.py +0 -0
  136. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/script_parallelism.py +0 -0
  137. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/test_pump.py +0 -0
  138. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/test_pump_example.py +0 -0
  139. {fmdtools-2.5.1 → fmdtools-2.5.3}/examples/water_pump/test_pump_stochastic.py +0 -0
  140. {fmdtools-2.5.1 → fmdtools-2.5.3}/fmdtools.egg-info/dependency_links.txt +0 -0
  141. {fmdtools-2.5.1 → fmdtools-2.5.3}/fmdtools.egg-info/requires.txt +0 -0
  142. {fmdtools-2.5.1 → fmdtools-2.5.3}/fmdtools.egg-info/top_level.txt +0 -0
  143. {fmdtools-2.5.1 → fmdtools-2.5.3}/setup.cfg +0 -0
  144. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/__init__.py +0 -0
  145. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/__init__.py +0 -0
  146. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/base.py +0 -0
  147. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/label.py +0 -0
  148. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/model.py +0 -0
  149. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/analyze/graph/style.py +0 -0
  150. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/__init__.py +0 -0
  151. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/architecture/__init__.py +0 -0
  152. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/architecture/action.py +0 -0
  153. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/architecture/base.py +0 -0
  154. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/architecture/component.py +0 -0
  155. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/architecture/function.py +0 -0
  156. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/block/__init__.py +0 -0
  157. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/block/action.py +0 -0
  158. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/block/component.py +0 -0
  159. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/block/function.py +0 -0
  160. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/container/__init__.py +0 -0
  161. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/container/mode.py +0 -0
  162. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/container/parameter.py +0 -0
  163. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/container/state.py +0 -0
  164. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/flow/__init__.py +0 -0
  165. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/flow/base.py +0 -0
  166. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/flow/commsflow.py +0 -0
  167. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/flow/multiflow.py +0 -0
  168. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/object/__init__.py +0 -0
  169. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/object/timer.py +0 -0
  170. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/define/pathplan.py +0 -0
  171. {fmdtools-2.5.1 → fmdtools-2.5.3}/src/fmdtools/sim/__init__.py +0 -0
  172. {fmdtools-2.5.1 → fmdtools-2.5.3}/tests/__init__.py +0 -0
  173. {fmdtools-2.5.1 → fmdtools-2.5.3}/tests/common.py +0 -0
  174. {fmdtools-2.5.1 → fmdtools-2.5.3}/tests/test_geom.py +0 -0
  175. {fmdtools-2.5.1 → fmdtools-2.5.3}/tests/test_graph.py +0 -0
  176. {fmdtools-2.5.1 → fmdtools-2.5.3}/tests/test_hierarchical.py +0 -0
  177. {fmdtools-2.5.1 → fmdtools-2.5.3}/tests/test_pathplan.py +0 -0
@@ -1,4 +1,4 @@
1
- fmdtools version 2.5.1
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.1
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.1
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.1
397
+ git checkout v2.5.3
398
398
  ```
399
399
 
400
400
 
@@ -499,7 +499,10 @@ Once fmdtools is installed, use the following to get acquainted with how to use
499
499
  Decent (but **very imperfect**) results can be achieved using LLMs for model development using the prompt:
500
500
 
501
501
  ```prompt
502
- You are an experienced software engineer learning a new Python library for the first time. Make an fmdtools simulation of racecar using the latest syntax from `https://github.com/nasa/fmdtools/tree/v2.5.1` for all modeling (`fmdtools.define`), simulation (`fmdtools.sim`), and analysis (`fmdtools.analyze`) classes. The output should be a bare bones but well-commented .py file with simple behaviors for each class and a small script at the bottom leveraging the built-in capabilities in the `fmdtools.analyze` package instead of directly using external libraries like matplotlib. As you are going, explain the structure of each class, the usage of each function call and how they each leverage the correct fmdtools v2.5.1 syntax.
502
+ You are an experienced software engineer learning a new Python library for the first time.
503
+ Make an fmdtools simulation of a <enter your system here> using the latest syntax from `https://github.com/nasa/fmdtools/tree/v2.5.1` for all modeling (`fmdtools.define`), simulation (`fmdtools.sim`), and analysis (`fmdtools.analyze`) classes.
504
+ The output should be a bare bones but well-commented .py file with simple behaviors for each class and a small script at the bottom leveraging the built-in capabilities in the `fmdtools.analyze` package instead of directly using external libraries like matplotlib.
505
+ As you are going, explain the structure of each class, the usage of each function call and how they each leverage the correct fmdtools v2.5.1 syntax.
503
506
  ```
504
507
 
505
508
  The important aspects of this prompt are specifying the URL and version--otherwise the LLM is likely to hallucinate usage or recall an earlier version of fmdtools. Do not expect perfect output from this--you may have to spend some time identifying the problems so they can be fixed!
@@ -545,8 +548,8 @@ To cite a particular version of the fmdtools, you may use:
545
548
  author = {{NASA}},
546
549
  title = {fmdtools},
547
550
  url = {https://github.com/nasa/fmdtools},
548
- version = {2.5.1},
549
- date = {2026-9-22},
551
+ version = {2.5.3},
552
+ date = {2026-9-30},
550
553
  }
551
554
  ```
552
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.1
126
+ git checkout v2.5.3
127
127
  ```
128
128
 
129
129
 
@@ -228,7 +228,10 @@ Once fmdtools is installed, use the following to get acquainted with how to use
228
228
  Decent (but **very imperfect**) results can be achieved using LLMs for model development using the prompt:
229
229
 
230
230
  ```prompt
231
- You are an experienced software engineer learning a new Python library for the first time. Make an fmdtools simulation of racecar using the latest syntax from `https://github.com/nasa/fmdtools/tree/v2.5.1` for all modeling (`fmdtools.define`), simulation (`fmdtools.sim`), and analysis (`fmdtools.analyze`) classes. The output should be a bare bones but well-commented .py file with simple behaviors for each class and a small script at the bottom leveraging the built-in capabilities in the `fmdtools.analyze` package instead of directly using external libraries like matplotlib. As you are going, explain the structure of each class, the usage of each function call and how they each leverage the correct fmdtools v2.5.1 syntax.
231
+ You are an experienced software engineer learning a new Python library for the first time.
232
+ Make an fmdtools simulation of a <enter your system here> using the latest syntax from `https://github.com/nasa/fmdtools/tree/v2.5.1` for all modeling (`fmdtools.define`), simulation (`fmdtools.sim`), and analysis (`fmdtools.analyze`) classes.
233
+ The output should be a bare bones but well-commented .py file with simple behaviors for each class and a small script at the bottom leveraging the built-in capabilities in the `fmdtools.analyze` package instead of directly using external libraries like matplotlib.
234
+ As you are going, explain the structure of each class, the usage of each function call and how they each leverage the correct fmdtools v2.5.1 syntax.
232
235
  ```
233
236
 
234
237
  The important aspects of this prompt are specifying the URL and version--otherwise the LLM is likely to hallucinate usage or recall an earlier version of fmdtools. Do not expect perfect output from this--you may have to spend some time identifying the problems so they can be fixed!
@@ -274,8 +277,8 @@ To cite a particular version of the fmdtools, you may use:
274
277
  author = {{NASA}},
275
278
  title = {fmdtools},
276
279
  url = {https://github.com/nasa/fmdtools},
277
- version = {2.5.1},
278
- date = {2026-9-22},
280
+ version = {2.5.3},
281
+ date = {2026-9-30},
279
282
  }
280
283
  ```
281
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": {}})
@@ -150,7 +150,7 @@ class DroneDisturbanceTest(unittest.TestCase):
150
150
  amt = self.mdl.fxns['store_ee'].ca.comps['s1p1'].p.amt
151
151
  soc_expected = soc_set - 100/amt
152
152
  soc_res = self.edp.f2(soc_set)
153
- self.assertEqual(soc_expected, soc_res)
153
+ self.assertAlmostEqual(soc_expected, soc_res)
154
154
 
155
155
  # make sure the disturbance wasn't set permanently
156
156
  soc_later = self.edp.f1(soc_set)
@@ -18,6 +18,7 @@ specific language governing permissions and limitations under the License.
18
18
  from fmdtools_tests.common import CommonTests
19
19
  from fmdtools_examples.navigating_rover.script_search_rover import line_dist, line_dist_faster
20
20
  import unittest
21
+ import numpy as np
21
22
 
22
23
 
23
24
  class RoverTests(unittest.TestCase, CommonTests):
@@ -29,9 +30,9 @@ class RoverTests(unittest.TestCase, CommonTests):
29
30
  dist_int, enddist_int, endpt_int = line_dist_faster(testvalue)
30
31
  dist, enddist, endpt = line_dist(testvalue)
31
32
 
32
- self.assertEqual(dist, dist_int)
33
- self.assertEqual(enddist, enddist_int)
34
- self.assertEqual(endpt, endpt_int)
33
+ self.assertTrue(np.isclose(dist, dist_int))
34
+ self.assertTrue(np.isclose(enddist, enddist_int))
35
+ self.assertTrue(np.isclose(endpt, endpt_int).all())
35
36
 
36
37
 
37
38
  if __name__ == '__main__':
@@ -632,14 +632,14 @@ def script_sample_faults(track='all', **kwargs):
632
632
 
633
633
  gh = mdlhists.get_comp_groups('flows.ee_1.s.current')
634
634
 
635
- results, mdlhists_staged = propagate.fault_sample(mdl, faultapp,
636
- staged=True, track='all')
635
+ # results, mdlhists_staged = propagate.fault_sample(mdl, faultapp,
636
+ # staged=True, track='all')
637
637
 
638
- tab = an.tabulate.result_summary_fmea(
639
- results, mdlhists, *mdl.fxns, *mdl.flows)
638
+ # tab = an.tabulate.result_summary_fmea(
639
+ # results, mdlhists, *mdl.fxns, *mdl.flows)
640
640
 
641
641
  h = mdlhists.get_expected(app=faultapp, with_nominal=True)
642
- ec = results.get_expected()
642
+ # ec = results.get_expected()
643
643
 
644
644
  # degsumm = h.get_summary(*mdl.fxns, *mdl.flows)
645
645
 
@@ -658,7 +658,7 @@ def script_sample_faults(track='all', **kwargs):
658
658
  fmea = an.tabulate.FMEA(results, faultapp)
659
659
  fmea.as_table()
660
660
  fmea.sort_by_metric("expected_cost")
661
- fmea.as_plot("expected_cost", color_factor="function")
661
+ fmea.as_plot("expected_cost", color_factor="obj")
662
662
 
663
663
  # test cases for multiplot legend/axis sharing
664
664
  mdlhists.plot_line("flows.ee_1.s.current", "flows.sig_1.s.power",
@@ -679,6 +679,8 @@ if __name__ == "__main__":
679
679
  mdl = Pump()
680
680
  res, hist = propagate.nominal(mdl, to_return = {'graph': FunctionArchitectureGraph})
681
681
 
682
+ # script_sample_faults()
683
+
682
684
  """
683
685
  endfaults, mdlhist = propagate.one_fault(mdl, 'export_water', 'block',
684
686
  time=10, to_return='faults')
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmdtools
3
- Version: 2.5.1
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.1
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.1
397
+ git checkout v2.5.3
398
398
  ```
399
399
 
400
400
 
@@ -499,7 +499,10 @@ Once fmdtools is installed, use the following to get acquainted with how to use
499
499
  Decent (but **very imperfect**) results can be achieved using LLMs for model development using the prompt:
500
500
 
501
501
  ```prompt
502
- You are an experienced software engineer learning a new Python library for the first time. Make an fmdtools simulation of racecar using the latest syntax from `https://github.com/nasa/fmdtools/tree/v2.5.1` for all modeling (`fmdtools.define`), simulation (`fmdtools.sim`), and analysis (`fmdtools.analyze`) classes. The output should be a bare bones but well-commented .py file with simple behaviors for each class and a small script at the bottom leveraging the built-in capabilities in the `fmdtools.analyze` package instead of directly using external libraries like matplotlib. As you are going, explain the structure of each class, the usage of each function call and how they each leverage the correct fmdtools v2.5.1 syntax.
502
+ You are an experienced software engineer learning a new Python library for the first time.
503
+ Make an fmdtools simulation of a <enter your system here> using the latest syntax from `https://github.com/nasa/fmdtools/tree/v2.5.1` for all modeling (`fmdtools.define`), simulation (`fmdtools.sim`), and analysis (`fmdtools.analyze`) classes.
504
+ The output should be a bare bones but well-commented .py file with simple behaviors for each class and a small script at the bottom leveraging the built-in capabilities in the `fmdtools.analyze` package instead of directly using external libraries like matplotlib.
505
+ As you are going, explain the structure of each class, the usage of each function call and how they each leverage the correct fmdtools v2.5.1 syntax.
503
506
  ```
504
507
 
505
508
  The important aspects of this prompt are specifying the URL and version--otherwise the LLM is likely to hallucinate usage or recall an earlier version of fmdtools. Do not expect perfect output from this--you may have to spend some time identifying the problems so they can be fixed!
@@ -545,8 +548,8 @@ To cite a particular version of the fmdtools, you may use:
545
548
  author = {{NASA}},
546
549
  title = {fmdtools},
547
550
  url = {https://github.com/nasa/fmdtools},
548
- version = {2.5.1},
549
- date = {2026-9-22},
551
+ version = {2.5.3},
552
+ date = {2026-9-30},
550
553
  }
551
554
  ```
552
555
 
@@ -127,8 +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
131
+ tests/test_array_random_states.py
132
+ tests/test_array_result_equality.py
133
+ tests/test_bootstrap_statistic_arguments.py
134
+ tests/test_calc_metric_ci_constant_outputs.py
135
+ tests/test_comparison_groups.py
136
+ tests/test_confidence_history_clipping.py
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
141
+ tests/test_fault_sample_without_nominal.py
142
+ tests/test_fault_time_without_traces.py
143
+ tests/test_gamma_sampling_density.py
130
144
  tests/test_geom.py
131
145
  tests/test_graph.py
132
146
  tests/test_hierarchical.py
147
+ tests/test_history_phase_identifiers.py
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
152
+ tests/test_join_phasemaps.py
153
+ tests/test_joint_fault_weights.py
154
+ tests/test_json_result_arrays.py
155
+ tests/test_named_metric_rounding.py
156
+ tests/test_nested_nominal_groups.py
157
+ tests/test_nesting_custom_separators.py
133
158
  tests/test_packages.py
134
- tests/test_pathplan.py
159
+ tests/test_parameter_sample_seeds.py
160
+ tests/test_parameter_subsampling_types.py
161
+ tests/test_pareto_density.py
162
+ tests/test_pathplan.py
163
+ tests/test_phase_timestep.py
164
+ tests/test_pruned_fault_times.py
165
+ tests/test_result.py
166
+ tests/test_result_folder_selection.py
167
+ tests/test_sample.py
168
+ tests/test_scenario.py
169
+ tests/test_scenario_defaults.py
170
+ tests/test_standard_t_density.py
171
+ tests/test_table_factor_indices.py
172
+ tests/test_time.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.1"
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.1'
25
+ __version__ = '2.5.3'
26
26
  __copyright__ = "Copyright 2024 United States Government"
27
27
  __license__ = "Apache-2.0"
@@ -41,7 +41,8 @@ import matplotlib.pyplot as plt
41
41
  from matplotlib import rcParams
42
42
  import inspect
43
43
  from scipy.stats import bootstrap
44
- from fmdtools.define.base import filter_kwargs
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):
@@ -229,7 +227,8 @@ def metric_preamble(data, dtype=None, rates=None, r_dtype=None, r_norm=False):
229
227
 
230
228
 
231
229
  def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
232
- rates=None, r_dtype=None, r_norm=False, **kwargs):
230
+ rates=None, r_dtype=None, r_norm=False, round_value=True,
231
+ res=1e-6, min_r=7, **kwargs):
233
232
  """
234
233
  Calculate a metric from data.
235
234
 
@@ -253,12 +252,19 @@ def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
253
252
  Datatype to preprocess the rates over. The default is None.
254
253
  r_norm : bool, optional
255
254
  Whether to normalize rates. The default is False.
255
+ round_value : bool, optional
256
+ Whether to round the final value, including named metrics.
257
+ Applied once after evaluation. Default is True.
258
+ res : float
259
+ Resolution to round to. Default is 1e-6.
260
+ min_r : int
261
+ Maximum number of digits to round to (see round_float).
256
262
  **kwargs : kwargs
257
- Keyword arguments to method.
263
+ Keyword arguments to method (see round_float).
258
264
 
259
265
  Returns
260
266
  -------
261
- metric: float
267
+ metric: np.float64/np.int64
262
268
  Metric calculated by method over data.
263
269
 
264
270
  Examples
@@ -266,7 +272,7 @@ def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
266
272
  >>> calc_metric([1,2,3]) # simple average
267
273
  np.float64(2.0)
268
274
  >>> calc_metric([1,2,3], rates=[0.1, 0.1, 0.0], method=np.sum) # weighted sum
269
- np.float64(0.30000000000000004)
275
+ np.float64(0.3)
270
276
  >>> calc_metric([0, 20, 30], dtype=bool, rates=[0.1, 0.1, 0.1], method=np.sum) # rate of nonzero event
271
277
  np.float64(0.2)
272
278
  >>> calc_metric([0, 1, 2], "total")
@@ -276,11 +282,16 @@ def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
276
282
  """
277
283
  if isinstance(method, str):
278
284
  method = eval("calc_"+method)
279
- return method(data, args=args, axis=axis, dtype=dtype, rates=rates,
280
- r_dtype=r_dtype, r_norm=r_norm, **kwargs)
285
+ # Named helpers call calc_metric again; round only the final result.
286
+ metric = method(data, args=args, axis=axis, dtype=dtype, rates=rates,
287
+ r_dtype=r_dtype, r_norm=r_norm, round_value=False, **kwargs)
281
288
  else:
282
289
  vals = metric_preamble(data, dtype, rates, r_dtype, r_norm)
283
- return method(vals, *args, **filter_kwargs(method, **kwargs, axis=axis))
290
+ metric = method(vals, *args, **filter_kwargs(method, **kwargs, axis=axis))
291
+ if round_value and (isinstance(metric, np.floating) or isinstance(metric, float)):
292
+ return round_float(metric, res=res, min_r=min_r)
293
+ else:
294
+ return metric
284
295
 
285
296
 
286
297
  def calc_metric_ci(data, method=np.average, return_anyway=False, interval=None,
@@ -298,7 +309,8 @@ def calc_metric_ci(data, method=np.average, return_anyway=False, interval=None,
298
309
  axis : int
299
310
  Axis along which to calculate the confidence interval(s). The default is 0.
300
311
  **kwargs : kwargs
301
- 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.
302
314
 
303
315
  Returns
304
316
  -------
@@ -325,20 +337,19 @@ def calc_metric_ci(data, method=np.average, return_anyway=False, interval=None,
325
337
  bs_kwar['confidence_level'] = interval*0.01
326
338
 
327
339
  vals = metric_preamble(data, **filter_kwargs(metric_preamble, **kwargs))
328
- if len(np.shape(vals)) > 1:
329
- val = vals[axis, 0]
330
- else:
331
- val = np.array(vals).flatten()[0]
340
+ val = np.take(vals, [0], axis=axis)
332
341
  if "weights" in kwargs and kwargs['weights'] is not None:
333
342
  raise Exception("Weights not able to be used w- bootstrap--use rates instead.")
334
- 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)
335
345
  vals_vary = vals == val
336
- if not np.all(vals_vary):
346
+ # Preserve the existing policy for globally identical data.
347
+ if not np.all(vals == vals.flat[0]):
337
348
  if np.any(np.all(vals_vary, axis=axis)):
338
349
  # use more robust/basic algorithm if some indices don't vary
339
- bs = bootstrap([vals], method, axis=axis, method="basic", **bs_kwar)
350
+ bs = bootstrap([vals], statistic, axis=axis, method="basic", **bs_kwar)
340
351
  else:
341
- bs = bootstrap([vals], method, axis=axis, **bs_kwar)
352
+ bs = bootstrap([vals], statistic, axis=axis, **bs_kwar)
342
353
  return met_val, bs.confidence_interval.low, bs.confidence_interval.high
343
354
  elif return_anyway:
344
355
  return met_val, met_val, met_val
@@ -353,7 +364,7 @@ def calc_rate(data, rates=None, weights=None, **kwargs):
353
364
  Examples
354
365
  --------
355
366
  >>> calc_rate([0, 10, 0]) # defaults to equal rate
356
- np.float64(0.3333333333333333)
367
+ np.float64(0.333333)
357
368
  >>> calc_rate([0, 10, 100], [0.1, 0.1, 0.1]) # provided rates
358
369
  np.float64(0.2)
359
370
  """
@@ -370,7 +381,7 @@ def calc_percent(data, weights=None, rates=None, **kwargs):
370
381
  Examples
371
382
  --------
372
383
  >>> calc_percent([0, 10, 0])
373
- np.float64(0.3333333333333333)
384
+ np.float64(0.333333)
374
385
  """
375
386
  return calc_metric(data, **{**kwargs, 'dtype': bool})
376
387
 
@@ -386,7 +397,7 @@ def calc_total(data, weights=None, **kwargs):
386
397
  >>> calc_total([0, 10, 100])
387
398
  np.int64(2)
388
399
  """
389
- return calc_metric(data, **{**kwargs, 'method': np.sum, 'dtype': bool})
400
+ return np.int64(calc_metric(data, **{**kwargs, 'method': np.sum, 'dtype': bool}))
390
401
 
391
402
 
392
403
  def calc_expected(data, rates=None, weights=None, **kwargs):
@@ -519,8 +530,8 @@ def plot_err_hist(err_hist, ax=None, fig=None, figsize=(6, 4), boundtype='fill',
519
530
  fig : mpl figure
520
531
  ax :mpl, axis
521
532
  """
522
- fig, ax = setup_plot(fig, ax, figsize)
523
- ax.plot(err_hist['stat'], **kwargs)
533
+ fig, ax = setup_plot(fig=fig, ax=ax, figsize=figsize)
534
+ ax.plot(err_hist[time], err_hist['stat'], **kwargs)
524
535
  if boundtype == 'fill':
525
536
  col = ax.lines[-1].get_color()
526
537
  ax.fill_between(err_hist[time], err_hist['low'], err_hist['high'],
@@ -582,7 +593,7 @@ def plot_err_lines(times, lows, highs, ax=None, fig=None, figsize=(6, 4), **kwar
582
593
  **kwargs : kwargs
583
594
  kwargs for the line
584
595
  """
585
- fig, ax = setup_plot(ax, fig, figsize)
596
+ fig, ax = setup_plot(fig=fig, ax=ax, figsize=figsize)
586
597
  ax.plot(times, highs, **kwargs)
587
598
  ax.plot(times, lows, **kwargs)
588
599
  return fig, ax
@@ -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):
@@ -360,6 +363,9 @@ class History(Result):
360
363
  """
361
364
  Get the time a fault is present in the system.
362
365
 
366
+ Only recorded fault traces are considered. Without recorded faults,
367
+ returns zero for ``total`` and ``nan`` for the position metrics.
368
+
363
369
  Parameters
364
370
  ----------
365
371
  metric : 'earliest','latest','total', 'times', optional
@@ -373,6 +379,8 @@ class History(Result):
373
379
 
374
380
  Examples
375
381
  --------
382
+ >>> History(time=[0, 1, 2]).get_fault_time()
383
+ nan
376
384
  >>> History({'m.faults.fault1': [False, False, False]}).get_fault_time()
377
385
  nan
378
386
  >>> History({'m.faults.fault1': [False, False, True]}).get_fault_time()
@@ -383,7 +391,7 @@ class History(Result):
383
391
  if metric == 'total':
384
392
  return np.sum(all_faults_hist >= 1)
385
393
  else:
386
- times = np.where(all_faults_hist >= 1)[0]
394
+ times = np.where(np.atleast_1d(all_faults_hist) >= 1)[0]
387
395
  if times.size == 0:
388
396
  return np.nan
389
397
  elif metric == 'times':
@@ -809,6 +817,8 @@ class History(Result):
809
817
  """
810
818
  Get aggregated err_hist of means surrounded by std deviation.
811
819
 
820
+ Calculate the spread across histories separately at each time sample.
821
+
812
822
  Parameters
813
823
  ----------
814
824
  value : str
@@ -829,7 +839,7 @@ class History(Result):
829
839
  hist = History()
830
840
  hist[time] = self.get_metric(time, axis=0)
831
841
  hist['stat'] = self.get_metric(value, np.mean, axis=0)
832
- std_dev = self.get_metric(value, np.std)
842
+ std_dev = self.get_metric(value, np.std, axis=0)
833
843
  hist['high'] = hist['stat']+std_dev/2
834
844
  hist['low'] = hist['stat']-std_dev/2
835
845
  return hist
@@ -851,7 +861,8 @@ class History(Result):
851
861
  ci : float
852
862
  Fraction for confidence interval. Default is 0.95.
853
863
  max_ind : str/int
854
- Max index of time to clip to. Default is 'max'.
864
+ Exclusive slice endpoint for both timestamps and values. Default is
865
+ 'max', which uses the shortest history length.
855
866
 
856
867
  Returns
857
868
  -------
@@ -874,6 +885,7 @@ class History(Result):
874
885
  hist[time] = self.get_metric(time, axis=0)
875
886
  if max_ind == 'max':
876
887
  max_ind = min([len(h) for h in self.values()])
888
+ hist[time] = hist[time][:max_ind]
877
889
  vals = np.array([*self.get_values(value).values()])[:, :max_ind]
878
890
  boot_stats = calc_metric_ci(vals, confidence_level=ci, axis=0, **kwargs)
879
891
  hist['stat'] = boot_stats[0]
@@ -80,7 +80,7 @@ class PhaseMap(object):
80
80
  def __repr__(self):
81
81
  return 'PhaseMap(' + str(self.phases) + ', ' + str(self.modephases) + ')'
82
82
 
83
- def find_phase(self, time, dt=1.0):
83
+ def find_phase(self, time, dt=None):
84
84
  """
85
85
  Find the phase that a time occurs in.
86
86
 
@@ -88,12 +88,17 @@ class PhaseMap(object):
88
88
  ----------
89
89
  time : float
90
90
  Occurence time.
91
+ dt : float, optional
92
+ Width of the final sample in each phase. Defaults to this map's dt.
93
+ An explicit value overrides the configured timestep for this lookup.
91
94
 
92
95
  Returns
93
96
  -------
94
97
  phase : str
95
98
  Name of the phase time occurs in.
96
99
  """
100
+ if dt is None:
101
+ dt = self.dt
97
102
  for phase, times in self.phases.items():
98
103
  if times[0] <= time < times[1]+dt:
99
104
  return phase
@@ -383,9 +388,9 @@ def from_hist(hist, fxn_modephases='all', dt=1.0):
383
388
  Dictionary of distict phases that the system functions pass through,
384
389
  of the form: {'fxn': PhaseMap} where each phase is defined by its
385
390
  corresponding mode in the modelhist.
386
- Phases are numbered mode, mode1, mode2 for multiple modes and given a
387
- corresponding phasemap {mode: {mode, mode1, mode2}} mapping modes to
388
- 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.
389
394
 
390
395
  Examples
391
396
  --------
@@ -406,6 +411,8 @@ def from_hist(hist, fxn_modephases='all', dt=1.0):
406
411
  fxn = k[k.index('m')-1]
407
412
  if len(modehist) != 0:
408
413
  modes = OrderedSet(modehist)
414
+ # Reserve actual mode names before generating repeated-phase suffixes.
415
+ used_phaseids = set(modes)
409
416
  modephases = dict.fromkeys(modes)
410
417
  phases_unsorted = dict()
411
418
  for mode in modes:
@@ -422,6 +429,10 @@ def from_hist(hist, fxn_modephases='all', dt=1.0):
422
429
  startind = modeinds[i+1]
423
430
  phasenum += 1
424
431
  phaseid = mode+str(phasenum)
432
+ while phaseid in used_phaseids:
433
+ phasenum += 1
434
+ phaseid = mode+str(phasenum)
435
+ used_phaseids.add(phaseid)
425
436
  phases = dict(sorted(phases_unsorted.items(),
426
437
  key=lambda item: item[1][0]))
427
438
  if fxn_modephases == 'all' or fxn in fxn_modephases:
@@ -677,8 +688,7 @@ def join_phasemaps(*phasemaps):
677
688
  joint_phases = {}
678
689
  all_combos = [*itertools.product(*[phasemap.phases for phasemap in phasemaps])]
679
690
  for combo in all_combos:
680
- phases = {c: phasemaps[i].phases[c] for i, c in enumerate(combo)}
681
- intervals = [i for i in phases.values()]
691
+ intervals = [phasemaps[i].phases[c] for i, c in enumerate(combo)]
682
692
  joined_interval = find_interval_overlap(*intervals)
683
693
  if joined_interval:
684
694
  joint_phases[combo] = joined_interval