fmdtools 2.5.0__tar.gz → 2.5.2__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 (159) hide show
  1. {fmdtools-2.5.0 → fmdtools-2.5.2}/LICENSE +1 -1
  2. {fmdtools-2.5.0 → fmdtools-2.5.2}/PKG-INFO +26 -9
  3. {fmdtools-2.5.0 → fmdtools-2.5.2}/README.md +24 -7
  4. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/model_responder.py +7 -8
  5. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/multirotor_drone/test_multirotor.py +1 -1
  6. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/test_rover.py +4 -3
  7. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/model_main.py +8 -6
  8. {fmdtools-2.5.0 → fmdtools-2.5.2}/fmdtools.egg-info/PKG-INFO +26 -9
  9. {fmdtools-2.5.0 → fmdtools-2.5.2}/fmdtools.egg-info/SOURCES.txt +25 -1
  10. {fmdtools-2.5.0 → fmdtools-2.5.2}/pyproject.toml +1 -1
  11. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/__init__.py +1 -1
  12. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/common.py +27 -18
  13. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/history.py +12 -3
  14. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/phases.py +7 -3
  15. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/result.py +32 -18
  16. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/tabulate.py +3 -1
  17. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/architecture/component.py +0 -2
  18. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/architecture/geom.py +4 -4
  19. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/base.py +10 -7
  20. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/container/base.py +2 -2
  21. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/container/rand.py +25 -2
  22. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/container/time.py +1 -1
  23. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/environment.py +6 -7
  24. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/object/base.py +67 -43
  25. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/object/coords.py +35 -12
  26. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/object/geom.py +109 -26
  27. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/sim/propagate.py +7 -3
  28. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/sim/sample.py +16 -12
  29. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/sim/scenario.py +3 -3
  30. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/sim/search.py +2 -2
  31. fmdtools-2.5.2/tests/test_array_random_states.py +182 -0
  32. fmdtools-2.5.2/tests/test_calc_metric_ci_constant_outputs.py +218 -0
  33. fmdtools-2.5.2/tests/test_comparison_groups.py +162 -0
  34. fmdtools-2.5.2/tests/test_confidence_history_clipping.py +181 -0
  35. fmdtools-2.5.2/tests/test_error_history_plots.py +197 -0
  36. fmdtools-2.5.2/tests/test_fault_sample_without_nominal.py +231 -0
  37. fmdtools-2.5.2/tests/test_fault_time_without_traces.py +141 -0
  38. fmdtools-2.5.2/tests/test_geom.py +139 -0
  39. fmdtools-2.5.2/tests/test_history_standard_deviation.py +95 -0
  40. fmdtools-2.5.2/tests/test_join_phasemaps.py +94 -0
  41. fmdtools-2.5.2/tests/test_joint_fault_weights.py +116 -0
  42. fmdtools-2.5.2/tests/test_nested_nominal_groups.py +200 -0
  43. fmdtools-2.5.2/tests/test_nesting_custom_separators.py +151 -0
  44. fmdtools-2.5.2/tests/test_parameter_sample_seeds.py +104 -0
  45. fmdtools-2.5.2/tests/test_parameter_subsampling_types.py +147 -0
  46. {fmdtools-2.5.0 → fmdtools-2.5.2}/tests/test_pathplan.py +0 -2
  47. fmdtools-2.5.2/tests/test_phase_timestep.py +73 -0
  48. fmdtools-2.5.2/tests/test_pruned_fault_times.py +163 -0
  49. fmdtools-2.5.2/tests/test_result.py +173 -0
  50. fmdtools-2.5.2/tests/test_sample.py +165 -0
  51. fmdtools-2.5.2/tests/test_scenario.py +111 -0
  52. fmdtools-2.5.2/tests/test_scenario_defaults.py +168 -0
  53. fmdtools-2.5.2/tests/test_table_factor_indices.py +154 -0
  54. fmdtools-2.5.2/tests/test_time.py +101 -0
  55. fmdtools-2.5.2/tests/test_uniform_density.py +83 -0
  56. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/__init__.py +0 -0
  57. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airport_taxiway/model_asset.py +0 -0
  58. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airport_taxiway/model_atc.py +0 -0
  59. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airport_taxiway/model_common.py +0 -0
  60. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airport_taxiway/model_main.py +0 -0
  61. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airport_taxiway/test_asset.py +0 -0
  62. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airport_taxiway/test_model.py +0 -0
  63. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/__init__.py +0 -0
  64. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/__init__.py +0 -0
  65. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/aircraft.py +0 -0
  66. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/aircraftarchitecture.py +0 -0
  67. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/arch/__init__.py +0 -0
  68. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/arch/aviate.py +0 -0
  69. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/arch/controlflight.py +0 -0
  70. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/arch/flows.py +0 -0
  71. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/arch/holdpayload.py +0 -0
  72. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/arch/perceiveenvironment.py +0 -0
  73. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/arch/storeee.py +0 -0
  74. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/parameter.py +0 -0
  75. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/base/state.py +0 -0
  76. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/contingency_management/__init__.py +0 -0
  77. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/contingency_management/model_aircraft.py +0 -0
  78. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/contingency_management/model_environment.py +0 -0
  79. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/contingency_management/model_flightplanner.py +0 -0
  80. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/__init__.py +0 -0
  81. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/model_drone.py +0 -0
  82. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/model_environment.py +0 -0
  83. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/model_main.py +0 -0
  84. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/wildfire_response/__init__.py +0 -0
  85. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/wildfire_response/model_aircraft.py +0 -0
  86. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/wildfire_response/model_environment.py +0 -0
  87. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/airspacelib/wildfire_response/model_main.py +0 -0
  88. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/cooling_tank/__init__.py +0 -0
  89. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/cooling_tank/model_main.py +0 -0
  90. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/cooling_tank/model_optimization.py +0 -0
  91. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/cooling_tank/test_tank.py +0 -0
  92. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/electric_power_system/__init__.py +0 -0
  93. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/electric_power_system/model_main.py +0 -0
  94. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/electric_power_system/test_model.py +0 -0
  95. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/human_hazard_mitigation/__init__.py +0 -0
  96. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/human_hazard_mitigation/model_main.py +0 -0
  97. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/multirotor_drone/__init__.py +0 -0
  98. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/multirotor_drone/model_dynamic.py +0 -0
  99. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/multirotor_drone/model_hierarchical.py +0 -0
  100. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/multirotor_drone/model_rural.py +0 -0
  101. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/multirotor_drone/model_static.py +0 -0
  102. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/multirotor_drone/model_urban.py +0 -0
  103. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/multirotor_drone/script_opt_model_rural.py +0 -0
  104. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/__init__.py +0 -0
  105. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/model_degradation.py +0 -0
  106. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/model_human.py +0 -0
  107. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/model_main.py +0 -0
  108. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/model_troupe.py +0 -0
  109. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/script_ea_analysis.py +0 -0
  110. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/script_mode_space.py +0 -0
  111. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/navigating_rover/script_search_rover.py +0 -0
  112. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/state_communication/__init__.py +0 -0
  113. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/state_communication/model_main.py +0 -0
  114. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/state_communication/test_multiflow.py +0 -0
  115. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/__init__.py +0 -0
  116. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/model_indiv.py +0 -0
  117. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/model_stochastic.py +0 -0
  118. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/script_jointfaults.py +0 -0
  119. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/script_parallelism.py +0 -0
  120. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/test_pump.py +0 -0
  121. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/test_pump_example.py +0 -0
  122. {fmdtools-2.5.0 → fmdtools-2.5.2}/examples/water_pump/test_pump_stochastic.py +0 -0
  123. {fmdtools-2.5.0 → fmdtools-2.5.2}/fmdtools.egg-info/dependency_links.txt +0 -0
  124. {fmdtools-2.5.0 → fmdtools-2.5.2}/fmdtools.egg-info/requires.txt +0 -0
  125. {fmdtools-2.5.0 → fmdtools-2.5.2}/fmdtools.egg-info/top_level.txt +0 -0
  126. {fmdtools-2.5.0 → fmdtools-2.5.2}/setup.cfg +0 -0
  127. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/__init__.py +0 -0
  128. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/__init__.py +0 -0
  129. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/base.py +0 -0
  130. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/label.py +0 -0
  131. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/model.py +0 -0
  132. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/style.py +0 -0
  133. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/__init__.py +0 -0
  134. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/architecture/__init__.py +0 -0
  135. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/architecture/action.py +0 -0
  136. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/architecture/base.py +0 -0
  137. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/architecture/function.py +0 -0
  138. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/block/__init__.py +0 -0
  139. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/block/action.py +0 -0
  140. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/block/base.py +0 -0
  141. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/block/component.py +0 -0
  142. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/block/function.py +0 -0
  143. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/container/__init__.py +0 -0
  144. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/container/mode.py +0 -0
  145. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/container/parameter.py +0 -0
  146. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/container/state.py +0 -0
  147. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/flow/__init__.py +0 -0
  148. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/flow/base.py +0 -0
  149. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/flow/commsflow.py +0 -0
  150. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/flow/multiflow.py +0 -0
  151. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/object/__init__.py +0 -0
  152. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/object/timer.py +0 -0
  153. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/define/pathplan.py +0 -0
  154. {fmdtools-2.5.0 → fmdtools-2.5.2}/src/fmdtools/sim/__init__.py +0 -0
  155. {fmdtools-2.5.0 → fmdtools-2.5.2}/tests/__init__.py +0 -0
  156. {fmdtools-2.5.0 → fmdtools-2.5.2}/tests/common.py +0 -0
  157. {fmdtools-2.5.0 → fmdtools-2.5.2}/tests/test_graph.py +0 -0
  158. {fmdtools-2.5.0 → fmdtools-2.5.2}/tests/test_hierarchical.py +0 -0
  159. {fmdtools-2.5.0 → fmdtools-2.5.2}/tests/test_packages.py +0 -0
@@ -1,4 +1,4 @@
1
- fmdtools version 2.5.0
1
+ fmdtools version 2.5.2
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.0
3
+ Version: 2.5.2
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.0
7
+ License: fmdtools version 2.5.2
8
8
  ---------------------------
9
9
  Apache License
10
10
  Version 2.0, January 2004
@@ -306,8 +306,8 @@ The fmdtools library provides the computational support needed to perform a simu
306
306
 
307
307
  <img align="left" width="100" height="100" src="/docs-source/figures/powerpoint/flexible.svg">
308
308
 
309
- ### Flexible Modelling Paradigm
310
- Models in fmdtools use a consistent and composable representation of system structure and behavior. Whether you want to model a simple component, a complex system-of-systems, or both, fmdtools can help.
309
+ ### Flexible, AI-Ready Modelling Paradigm
310
+ Models in fmdtools use a straightforward and composable code-based representation of system structure and behavior. Since fmdtools models are based on open-source Python, models can be readily generated from your AI of choice with the right prompting. Whether you want to model a simple component, a complex system-of-systems, or both, fmdtools can help.
311
311
 
312
312
  <img align="left" width="100" height="100" src="/docs-source/figures/powerpoint/powerful.svg">
313
313
 
@@ -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.0
397
+ git checkout v2.5.2
398
398
  ```
399
399
 
400
400
 
@@ -490,10 +490,27 @@ Once fmdtools is installed, use the following to get acquainted with how to use
490
490
 
491
491
  - Explore more [examples](https://nasa.github.io/fmdtools/examples/Examples.html) of particular use-cases by going through the [examples folder](https://github.com/nasa/fmdtools/tree/main/examples)
492
492
 
493
- - Read about contributions and model development best practices by perusing the [Development Guide](https://nasa.github.io/fmdtools/docs-source/Development%20Guide.html#).
493
+ - Read about contributions and model development best practices by perusing the [Development Guide](https://nasa.github.io/fmdtools/docs-source/development-guide.html).
494
494
 
495
495
  - Explore the searchable [module reference](https://nasa.github.io/fmdtools/docs-source/fmdtools.html) for syntax and usage documentation.
496
496
 
497
+ ### AI/LLM Tips
498
+
499
+ Decent (but **very imperfect**) results can be achieved using LLMs for model development using the prompt:
500
+
501
+ ```prompt
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.
506
+ ```
507
+
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!
509
+
510
+ For somewhat better results, we recommend using an on-machine coding "agent" such as OpenCode/Claude Code/Codex.
511
+
512
+ More elaborate prompts can be found in our [AGENTS.md](AGENTS.md) and repository skills at `.agents/skills`.
513
+
497
514
  ## Contributions
498
515
  fmdtools is developed primarily by researchers at NASA Ames Research Center. External contributions are welcome under a Contributor License Agreement:
499
516
 
@@ -527,12 +544,12 @@ To cite fmdtools in general, you may cite our explanatory publication:
527
544
  To cite a particular version of the fmdtools, you may use:
528
545
 
529
546
  ```
530
- @software{nasa2025fmdtools,
547
+ @software{nasa2026fmdtools,
531
548
  author = {{NASA}},
532
549
  title = {fmdtools},
533
550
  url = {https://github.com/nasa/fmdtools},
534
- version = {2.5.0},
535
- date = {2026-8-13},
551
+ version = {2.5.2},
552
+ date = {2026-9-28},
536
553
  }
537
554
  ```
538
555
 
@@ -35,8 +35,8 @@ The fmdtools library provides the computational support needed to perform a simu
35
35
 
36
36
  <img align="left" width="100" height="100" src="/docs-source/figures/powerpoint/flexible.svg">
37
37
 
38
- ### Flexible Modelling Paradigm
39
- Models in fmdtools use a consistent and composable representation of system structure and behavior. Whether you want to model a simple component, a complex system-of-systems, or both, fmdtools can help.
38
+ ### Flexible, AI-Ready Modelling Paradigm
39
+ Models in fmdtools use a straightforward and composable code-based representation of system structure and behavior. Since fmdtools models are based on open-source Python, models can be readily generated from your AI of choice with the right prompting. Whether you want to model a simple component, a complex system-of-systems, or both, fmdtools can help.
40
40
 
41
41
  <img align="left" width="100" height="100" src="/docs-source/figures/powerpoint/powerful.svg">
42
42
 
@@ -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.0
126
+ git checkout v2.5.2
127
127
  ```
128
128
 
129
129
 
@@ -219,10 +219,27 @@ Once fmdtools is installed, use the following to get acquainted with how to use
219
219
 
220
220
  - Explore more [examples](https://nasa.github.io/fmdtools/examples/Examples.html) of particular use-cases by going through the [examples folder](https://github.com/nasa/fmdtools/tree/main/examples)
221
221
 
222
- - Read about contributions and model development best practices by perusing the [Development Guide](https://nasa.github.io/fmdtools/docs-source/Development%20Guide.html#).
222
+ - Read about contributions and model development best practices by perusing the [Development Guide](https://nasa.github.io/fmdtools/docs-source/development-guide.html).
223
223
 
224
224
  - Explore the searchable [module reference](https://nasa.github.io/fmdtools/docs-source/fmdtools.html) for syntax and usage documentation.
225
225
 
226
+ ### AI/LLM Tips
227
+
228
+ Decent (but **very imperfect**) results can be achieved using LLMs for model development using the prompt:
229
+
230
+ ```prompt
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.
235
+ ```
236
+
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!
238
+
239
+ For somewhat better results, we recommend using an on-machine coding "agent" such as OpenCode/Claude Code/Codex.
240
+
241
+ More elaborate prompts can be found in our [AGENTS.md](AGENTS.md) and repository skills at `.agents/skills`.
242
+
226
243
  ## Contributions
227
244
  fmdtools is developed primarily by researchers at NASA Ames Research Center. External contributions are welcome under a Contributor License Agreement:
228
245
 
@@ -256,12 +273,12 @@ To cite fmdtools in general, you may cite our explanatory publication:
256
273
  To cite a particular version of the fmdtools, you may use:
257
274
 
258
275
  ```
259
- @software{nasa2025fmdtools,
276
+ @software{nasa2026fmdtools,
260
277
  author = {{NASA}},
261
278
  title = {fmdtools},
262
279
  url = {https://github.com/nasa/fmdtools},
263
- version = {2.5.0},
264
- date = {2026-8-13},
280
+ version = {2.5.2},
281
+ date = {2026-9-28},
265
282
  }
266
283
  ```
267
284
 
@@ -88,16 +88,12 @@ class ResponderGeomArchitecture(GeomArchitecture):
88
88
  self.add_geom('peripheral', PeripheralZone)
89
89
  self.add_geom('paracentral', ParacentralZone)
90
90
 
91
-
92
- ga = ResponderGeomArchitecture()
93
-
94
-
95
91
  def check_vision_zones(ga, point):
96
92
  """Return {zone_name: bool} indicating which vision zones contain `point`."""
97
93
  return {name: geom.at(point) for name, geom in ga.geoms.items()}
98
94
 
99
95
 
100
- print(check_vision_zones(ga, (400, 10)))
96
+
101
97
 
102
98
 
103
99
  class ResponderState(State):
@@ -302,6 +298,9 @@ def plot_combined_response_from(time, history={}, mdl=None, fig=None, ax=None,
302
298
  return fig, ax
303
299
 
304
300
 
305
- responder = Responder()
306
- print(responder.environment.c.find_all_prop("person_to_rescue"))
307
- #print(responder.create_rescue_path(4))
301
+ if __name__ == "__main__":
302
+ ga = ResponderGeomArchitecture()
303
+ print(check_vision_zones(ga, (400, 10)))
304
+ responder = Responder()
305
+ print(responder.environment.c.find_all_prop("person_to_rescue"))
306
+ #print(responder.create_rescue_path(4))
@@ -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.0
3
+ Version: 2.5.2
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.0
7
+ License: fmdtools version 2.5.2
8
8
  ---------------------------
9
9
  Apache License
10
10
  Version 2.0, January 2004
@@ -306,8 +306,8 @@ The fmdtools library provides the computational support needed to perform a simu
306
306
 
307
307
  <img align="left" width="100" height="100" src="/docs-source/figures/powerpoint/flexible.svg">
308
308
 
309
- ### Flexible Modelling Paradigm
310
- Models in fmdtools use a consistent and composable representation of system structure and behavior. Whether you want to model a simple component, a complex system-of-systems, or both, fmdtools can help.
309
+ ### Flexible, AI-Ready Modelling Paradigm
310
+ Models in fmdtools use a straightforward and composable code-based representation of system structure and behavior. Since fmdtools models are based on open-source Python, models can be readily generated from your AI of choice with the right prompting. Whether you want to model a simple component, a complex system-of-systems, or both, fmdtools can help.
311
311
 
312
312
  <img align="left" width="100" height="100" src="/docs-source/figures/powerpoint/powerful.svg">
313
313
 
@@ -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.0
397
+ git checkout v2.5.2
398
398
  ```
399
399
 
400
400
 
@@ -490,10 +490,27 @@ Once fmdtools is installed, use the following to get acquainted with how to use
490
490
 
491
491
  - Explore more [examples](https://nasa.github.io/fmdtools/examples/Examples.html) of particular use-cases by going through the [examples folder](https://github.com/nasa/fmdtools/tree/main/examples)
492
492
 
493
- - Read about contributions and model development best practices by perusing the [Development Guide](https://nasa.github.io/fmdtools/docs-source/Development%20Guide.html#).
493
+ - Read about contributions and model development best practices by perusing the [Development Guide](https://nasa.github.io/fmdtools/docs-source/development-guide.html).
494
494
 
495
495
  - Explore the searchable [module reference](https://nasa.github.io/fmdtools/docs-source/fmdtools.html) for syntax and usage documentation.
496
496
 
497
+ ### AI/LLM Tips
498
+
499
+ Decent (but **very imperfect**) results can be achieved using LLMs for model development using the prompt:
500
+
501
+ ```prompt
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.
506
+ ```
507
+
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!
509
+
510
+ For somewhat better results, we recommend using an on-machine coding "agent" such as OpenCode/Claude Code/Codex.
511
+
512
+ More elaborate prompts can be found in our [AGENTS.md](AGENTS.md) and repository skills at `.agents/skills`.
513
+
497
514
  ## Contributions
498
515
  fmdtools is developed primarily by researchers at NASA Ames Research Center. External contributions are welcome under a Contributor License Agreement:
499
516
 
@@ -527,12 +544,12 @@ To cite fmdtools in general, you may cite our explanatory publication:
527
544
  To cite a particular version of the fmdtools, you may use:
528
545
 
529
546
  ```
530
- @software{nasa2025fmdtools,
547
+ @software{nasa2026fmdtools,
531
548
  author = {{NASA}},
532
549
  title = {fmdtools},
533
550
  url = {https://github.com/nasa/fmdtools},
534
- version = {2.5.0},
535
- date = {2026-8-13},
551
+ version = {2.5.2},
552
+ date = {2026-9-28},
536
553
  }
537
554
  ```
538
555
 
@@ -127,7 +127,31 @@ 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_random_states.py
131
+ tests/test_calc_metric_ci_constant_outputs.py
132
+ tests/test_comparison_groups.py
133
+ tests/test_confidence_history_clipping.py
134
+ tests/test_error_history_plots.py
135
+ tests/test_fault_sample_without_nominal.py
136
+ tests/test_fault_time_without_traces.py
137
+ tests/test_geom.py
130
138
  tests/test_graph.py
131
139
  tests/test_hierarchical.py
140
+ tests/test_history_standard_deviation.py
141
+ tests/test_join_phasemaps.py
142
+ tests/test_joint_fault_weights.py
143
+ tests/test_nested_nominal_groups.py
144
+ tests/test_nesting_custom_separators.py
132
145
  tests/test_packages.py
133
- tests/test_pathplan.py
146
+ tests/test_parameter_sample_seeds.py
147
+ tests/test_parameter_subsampling_types.py
148
+ tests/test_pathplan.py
149
+ tests/test_phase_timestep.py
150
+ tests/test_pruned_fault_times.py
151
+ tests/test_result.py
152
+ tests/test_sample.py
153
+ tests/test_scenario.py
154
+ tests/test_scenario_defaults.py
155
+ tests/test_table_factor_indices.py
156
+ tests/test_time.py
157
+ tests/test_uniform_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.0"
21
+ version="2.5.2"
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.0'
25
+ __version__ = '2.5.2'
26
26
  __copyright__ = "Copyright 2024 United States Government"
27
27
  __license__ = "Apache-2.0"
@@ -41,7 +41,7 @@ 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
45
 
46
46
 
47
47
  plt.rcParams['pdf.fonttype'] = 42
@@ -229,7 +229,8 @@ def metric_preamble(data, dtype=None, rates=None, r_dtype=None, r_norm=False):
229
229
 
230
230
 
231
231
  def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
232
- rates=None, r_dtype=None, r_norm=False, **kwargs):
232
+ rates=None, r_dtype=None, r_norm=False, round_value=True,
233
+ res=1e-6, min_r=7, **kwargs):
233
234
  """
234
235
  Calculate a metric from data.
235
236
 
@@ -253,12 +254,18 @@ def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
253
254
  Datatype to preprocess the rates over. The default is None.
254
255
  r_norm : bool, optional
255
256
  Whether to normalize rates. The default is False.
257
+ round_value : bool, optional
258
+ Whether to round the value. Default is True.
259
+ res : float
260
+ Resolution to round to. Default is 1e-6.
261
+ min_r : int
262
+ Maximum number of digits to round to (see round_float).
256
263
  **kwargs : kwargs
257
- Keyword arguments to method.
264
+ Keyword arguments to method (see round_float).
258
265
 
259
266
  Returns
260
267
  -------
261
- metric: float
268
+ metric: np.float64/np.int64
262
269
  Metric calculated by method over data.
263
270
 
264
271
  Examples
@@ -266,7 +273,7 @@ def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
266
273
  >>> calc_metric([1,2,3]) # simple average
267
274
  np.float64(2.0)
268
275
  >>> calc_metric([1,2,3], rates=[0.1, 0.1, 0.0], method=np.sum) # weighted sum
269
- np.float64(0.30000000000000004)
276
+ np.float64(0.3)
270
277
  >>> calc_metric([0, 20, 30], dtype=bool, rates=[0.1, 0.1, 0.1], method=np.sum) # rate of nonzero event
271
278
  np.float64(0.2)
272
279
  >>> calc_metric([0, 1, 2], "total")
@@ -276,11 +283,15 @@ def calc_metric(data, method=np.average, args=(), axis=None, dtype=None,
276
283
  """
277
284
  if isinstance(method, str):
278
285
  method = eval("calc_"+method)
279
- return method(data, args=args, axis=axis, dtype=dtype, rates=rates,
286
+ metric = method(data, args=args, axis=axis, dtype=dtype, rates=rates,
280
287
  r_dtype=r_dtype, r_norm=r_norm, **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,
@@ -325,15 +336,13 @@ def calc_metric_ci(data, method=np.average, return_anyway=False, interval=None,
325
336
  bs_kwar['confidence_level'] = interval*0.01
326
337
 
327
338
  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]
339
+ val = np.take(vals, [0], axis=axis)
332
340
  if "weights" in kwargs and kwargs['weights'] is not None:
333
341
  raise Exception("Weights not able to be used w- bootstrap--use rates instead.")
334
342
  met_val = method(vals, axis=axis, **filter_kwargs(method, **kwargs))
335
343
  vals_vary = vals == val
336
- if not np.all(vals_vary):
344
+ # Preserve the existing policy for globally identical data.
345
+ if not np.all(vals == vals.flat[0]):
337
346
  if np.any(np.all(vals_vary, axis=axis)):
338
347
  # use more robust/basic algorithm if some indices don't vary
339
348
  bs = bootstrap([vals], method, axis=axis, method="basic", **bs_kwar)
@@ -353,7 +362,7 @@ def calc_rate(data, rates=None, weights=None, **kwargs):
353
362
  Examples
354
363
  --------
355
364
  >>> calc_rate([0, 10, 0]) # defaults to equal rate
356
- np.float64(0.3333333333333333)
365
+ np.float64(0.333333)
357
366
  >>> calc_rate([0, 10, 100], [0.1, 0.1, 0.1]) # provided rates
358
367
  np.float64(0.2)
359
368
  """
@@ -370,7 +379,7 @@ def calc_percent(data, weights=None, rates=None, **kwargs):
370
379
  Examples
371
380
  --------
372
381
  >>> calc_percent([0, 10, 0])
373
- np.float64(0.3333333333333333)
382
+ np.float64(0.333333)
374
383
  """
375
384
  return calc_metric(data, **{**kwargs, 'dtype': bool})
376
385
 
@@ -386,7 +395,7 @@ def calc_total(data, weights=None, **kwargs):
386
395
  >>> calc_total([0, 10, 100])
387
396
  np.int64(2)
388
397
  """
389
- return calc_metric(data, **{**kwargs, 'method': np.sum, 'dtype': bool})
398
+ return np.int64(calc_metric(data, **{**kwargs, 'method': np.sum, 'dtype': bool}))
390
399
 
391
400
 
392
401
  def calc_expected(data, rates=None, weights=None, **kwargs):
@@ -519,8 +528,8 @@ def plot_err_hist(err_hist, ax=None, fig=None, figsize=(6, 4), boundtype='fill',
519
528
  fig : mpl figure
520
529
  ax :mpl, axis
521
530
  """
522
- fig, ax = setup_plot(fig, ax, figsize)
523
- ax.plot(err_hist['stat'], **kwargs)
531
+ fig, ax = setup_plot(fig=fig, ax=ax, figsize=figsize)
532
+ ax.plot(err_hist[time], err_hist['stat'], **kwargs)
524
533
  if boundtype == 'fill':
525
534
  col = ax.lines[-1].get_color()
526
535
  ax.fill_between(err_hist[time], err_hist['low'], err_hist['high'],
@@ -582,7 +591,7 @@ def plot_err_lines(times, lows, highs, ax=None, fig=None, figsize=(6, 4), **kwar
582
591
  **kwargs : kwargs
583
592
  kwargs for the line
584
593
  """
585
- fig, ax = setup_plot(ax, fig, figsize)
594
+ fig, ax = setup_plot(fig=fig, ax=ax, figsize=figsize)
586
595
  ax.plot(times, highs, **kwargs)
587
596
  ax.plot(times, lows, **kwargs)
588
597
  return fig, ax
@@ -360,6 +360,9 @@ class History(Result):
360
360
  """
361
361
  Get the time a fault is present in the system.
362
362
 
363
+ Only recorded fault traces are considered. Without recorded faults,
364
+ returns zero for ``total`` and ``nan`` for the position metrics.
365
+
363
366
  Parameters
364
367
  ----------
365
368
  metric : 'earliest','latest','total', 'times', optional
@@ -373,6 +376,8 @@ class History(Result):
373
376
 
374
377
  Examples
375
378
  --------
379
+ >>> History(time=[0, 1, 2]).get_fault_time()
380
+ nan
376
381
  >>> History({'m.faults.fault1': [False, False, False]}).get_fault_time()
377
382
  nan
378
383
  >>> History({'m.faults.fault1': [False, False, True]}).get_fault_time()
@@ -383,7 +388,7 @@ class History(Result):
383
388
  if metric == 'total':
384
389
  return np.sum(all_faults_hist >= 1)
385
390
  else:
386
- times = np.where(all_faults_hist >= 1)[0]
391
+ times = np.where(np.atleast_1d(all_faults_hist) >= 1)[0]
387
392
  if times.size == 0:
388
393
  return np.nan
389
394
  elif metric == 'times':
@@ -809,6 +814,8 @@ class History(Result):
809
814
  """
810
815
  Get aggregated err_hist of means surrounded by std deviation.
811
816
 
817
+ Calculate the spread across histories separately at each time sample.
818
+
812
819
  Parameters
813
820
  ----------
814
821
  value : str
@@ -829,7 +836,7 @@ class History(Result):
829
836
  hist = History()
830
837
  hist[time] = self.get_metric(time, axis=0)
831
838
  hist['stat'] = self.get_metric(value, np.mean, axis=0)
832
- std_dev = self.get_metric(value, np.std)
839
+ std_dev = self.get_metric(value, np.std, axis=0)
833
840
  hist['high'] = hist['stat']+std_dev/2
834
841
  hist['low'] = hist['stat']-std_dev/2
835
842
  return hist
@@ -851,7 +858,8 @@ class History(Result):
851
858
  ci : float
852
859
  Fraction for confidence interval. Default is 0.95.
853
860
  max_ind : str/int
854
- Max index of time to clip to. Default is 'max'.
861
+ Exclusive slice endpoint for both timestamps and values. Default is
862
+ 'max', which uses the shortest history length.
855
863
 
856
864
  Returns
857
865
  -------
@@ -874,6 +882,7 @@ class History(Result):
874
882
  hist[time] = self.get_metric(time, axis=0)
875
883
  if max_ind == 'max':
876
884
  max_ind = min([len(h) for h in self.values()])
885
+ hist[time] = hist[time][:max_ind]
877
886
  vals = np.array([*self.get_values(value).values()])[:, :max_ind]
878
887
  boot_stats = calc_metric_ci(vals, confidence_level=ci, axis=0, **kwargs)
879
888
  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
@@ -677,8 +682,7 @@ def join_phasemaps(*phasemaps):
677
682
  joint_phases = {}
678
683
  all_combos = [*itertools.product(*[phasemap.phases for phasemap in phasemaps])]
679
684
  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()]
685
+ intervals = [phasemaps[i].phases[c] for i, c in enumerate(combo)]
682
686
  joined_interval = find_interval_overlap(*intervals)
683
687
  if joined_interval:
684
688
  joint_phases[combo] = joined_interval