fmdtools 2.5.1__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.1 → fmdtools-2.5.2}/LICENSE +1 -1
  2. {fmdtools-2.5.1/fmdtools.egg-info → fmdtools-2.5.2}/PKG-INFO +9 -6
  3. {fmdtools-2.5.1 → fmdtools-2.5.2}/README.md +7 -4
  4. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/multirotor_drone/test_multirotor.py +1 -1
  5. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/test_rover.py +4 -3
  6. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/model_main.py +8 -6
  7. {fmdtools-2.5.1 → fmdtools-2.5.2/fmdtools.egg-info}/PKG-INFO +9 -6
  8. {fmdtools-2.5.1 → fmdtools-2.5.2}/fmdtools.egg-info/SOURCES.txt +24 -1
  9. {fmdtools-2.5.1 → fmdtools-2.5.2}/pyproject.toml +1 -1
  10. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/__init__.py +1 -1
  11. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/common.py +27 -18
  12. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/history.py +12 -3
  13. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/phases.py +7 -3
  14. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/result.py +32 -18
  15. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/tabulate.py +3 -1
  16. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/architecture/geom.py +4 -4
  17. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/base.py +10 -7
  18. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/container/base.py +2 -2
  19. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/container/rand.py +25 -2
  20. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/container/time.py +1 -1
  21. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/environment.py +6 -7
  22. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/object/base.py +67 -43
  23. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/object/coords.py +2 -2
  24. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/object/geom.py +60 -19
  25. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/sim/propagate.py +7 -3
  26. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/sim/sample.py +16 -12
  27. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/sim/scenario.py +3 -3
  28. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/sim/search.py +2 -2
  29. fmdtools-2.5.2/tests/test_array_random_states.py +182 -0
  30. fmdtools-2.5.2/tests/test_calc_metric_ci_constant_outputs.py +218 -0
  31. fmdtools-2.5.2/tests/test_comparison_groups.py +162 -0
  32. fmdtools-2.5.2/tests/test_confidence_history_clipping.py +181 -0
  33. fmdtools-2.5.2/tests/test_error_history_plots.py +197 -0
  34. fmdtools-2.5.2/tests/test_fault_sample_without_nominal.py +231 -0
  35. fmdtools-2.5.2/tests/test_fault_time_without_traces.py +141 -0
  36. fmdtools-2.5.2/tests/test_history_standard_deviation.py +95 -0
  37. fmdtools-2.5.2/tests/test_join_phasemaps.py +94 -0
  38. fmdtools-2.5.2/tests/test_joint_fault_weights.py +116 -0
  39. fmdtools-2.5.2/tests/test_nested_nominal_groups.py +200 -0
  40. fmdtools-2.5.2/tests/test_nesting_custom_separators.py +151 -0
  41. fmdtools-2.5.2/tests/test_parameter_sample_seeds.py +104 -0
  42. fmdtools-2.5.2/tests/test_parameter_subsampling_types.py +147 -0
  43. fmdtools-2.5.2/tests/test_phase_timestep.py +73 -0
  44. fmdtools-2.5.2/tests/test_pruned_fault_times.py +163 -0
  45. fmdtools-2.5.2/tests/test_result.py +173 -0
  46. fmdtools-2.5.2/tests/test_sample.py +165 -0
  47. fmdtools-2.5.2/tests/test_scenario.py +111 -0
  48. fmdtools-2.5.2/tests/test_scenario_defaults.py +168 -0
  49. fmdtools-2.5.2/tests/test_table_factor_indices.py +154 -0
  50. fmdtools-2.5.2/tests/test_time.py +101 -0
  51. fmdtools-2.5.2/tests/test_uniform_density.py +83 -0
  52. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/__init__.py +0 -0
  53. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airport_taxiway/model_asset.py +0 -0
  54. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airport_taxiway/model_atc.py +0 -0
  55. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airport_taxiway/model_common.py +0 -0
  56. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airport_taxiway/model_main.py +0 -0
  57. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airport_taxiway/test_asset.py +0 -0
  58. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airport_taxiway/test_model.py +0 -0
  59. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/__init__.py +0 -0
  60. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/__init__.py +0 -0
  61. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/aircraft.py +0 -0
  62. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/aircraftarchitecture.py +0 -0
  63. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/arch/__init__.py +0 -0
  64. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/arch/aviate.py +0 -0
  65. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/arch/controlflight.py +0 -0
  66. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/arch/flows.py +0 -0
  67. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/arch/holdpayload.py +0 -0
  68. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/arch/perceiveenvironment.py +0 -0
  69. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/arch/storeee.py +0 -0
  70. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/parameter.py +0 -0
  71. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/base/state.py +0 -0
  72. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/contingency_management/__init__.py +0 -0
  73. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/contingency_management/model_aircraft.py +0 -0
  74. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/contingency_management/model_environment.py +0 -0
  75. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/contingency_management/model_flightplanner.py +0 -0
  76. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/__init__.py +0 -0
  77. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/model_drone.py +0 -0
  78. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/model_environment.py +0 -0
  79. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/model_main.py +0 -0
  80. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/water_rescue/model_responder.py +0 -0
  81. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/wildfire_response/__init__.py +0 -0
  82. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/wildfire_response/model_aircraft.py +0 -0
  83. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/wildfire_response/model_environment.py +0 -0
  84. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/airspacelib/wildfire_response/model_main.py +0 -0
  85. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/cooling_tank/__init__.py +0 -0
  86. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/cooling_tank/model_main.py +0 -0
  87. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/cooling_tank/model_optimization.py +0 -0
  88. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/cooling_tank/test_tank.py +0 -0
  89. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/electric_power_system/__init__.py +0 -0
  90. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/electric_power_system/model_main.py +0 -0
  91. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/electric_power_system/test_model.py +0 -0
  92. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/human_hazard_mitigation/__init__.py +0 -0
  93. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/human_hazard_mitigation/model_main.py +0 -0
  94. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/multirotor_drone/__init__.py +0 -0
  95. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/multirotor_drone/model_dynamic.py +0 -0
  96. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/multirotor_drone/model_hierarchical.py +0 -0
  97. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/multirotor_drone/model_rural.py +0 -0
  98. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/multirotor_drone/model_static.py +0 -0
  99. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/multirotor_drone/model_urban.py +0 -0
  100. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/multirotor_drone/script_opt_model_rural.py +0 -0
  101. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/__init__.py +0 -0
  102. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/model_degradation.py +0 -0
  103. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/model_human.py +0 -0
  104. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/model_main.py +0 -0
  105. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/model_troupe.py +0 -0
  106. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/script_ea_analysis.py +0 -0
  107. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/script_mode_space.py +0 -0
  108. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/navigating_rover/script_search_rover.py +0 -0
  109. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/state_communication/__init__.py +0 -0
  110. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/state_communication/model_main.py +0 -0
  111. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/state_communication/test_multiflow.py +0 -0
  112. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/__init__.py +0 -0
  113. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/model_indiv.py +0 -0
  114. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/model_stochastic.py +0 -0
  115. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/script_jointfaults.py +0 -0
  116. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/script_parallelism.py +0 -0
  117. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/test_pump.py +0 -0
  118. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/test_pump_example.py +0 -0
  119. {fmdtools-2.5.1 → fmdtools-2.5.2}/examples/water_pump/test_pump_stochastic.py +0 -0
  120. {fmdtools-2.5.1 → fmdtools-2.5.2}/fmdtools.egg-info/dependency_links.txt +0 -0
  121. {fmdtools-2.5.1 → fmdtools-2.5.2}/fmdtools.egg-info/requires.txt +0 -0
  122. {fmdtools-2.5.1 → fmdtools-2.5.2}/fmdtools.egg-info/top_level.txt +0 -0
  123. {fmdtools-2.5.1 → fmdtools-2.5.2}/setup.cfg +0 -0
  124. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/__init__.py +0 -0
  125. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/__init__.py +0 -0
  126. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/base.py +0 -0
  127. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/label.py +0 -0
  128. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/model.py +0 -0
  129. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/analyze/graph/style.py +0 -0
  130. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/__init__.py +0 -0
  131. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/architecture/__init__.py +0 -0
  132. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/architecture/action.py +0 -0
  133. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/architecture/base.py +0 -0
  134. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/architecture/component.py +0 -0
  135. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/architecture/function.py +0 -0
  136. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/block/__init__.py +0 -0
  137. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/block/action.py +0 -0
  138. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/block/base.py +0 -0
  139. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/block/component.py +0 -0
  140. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/block/function.py +0 -0
  141. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/container/__init__.py +0 -0
  142. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/container/mode.py +0 -0
  143. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/container/parameter.py +0 -0
  144. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/container/state.py +0 -0
  145. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/flow/__init__.py +0 -0
  146. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/flow/base.py +0 -0
  147. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/flow/commsflow.py +0 -0
  148. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/flow/multiflow.py +0 -0
  149. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/object/__init__.py +0 -0
  150. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/object/timer.py +0 -0
  151. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/define/pathplan.py +0 -0
  152. {fmdtools-2.5.1 → fmdtools-2.5.2}/src/fmdtools/sim/__init__.py +0 -0
  153. {fmdtools-2.5.1 → fmdtools-2.5.2}/tests/__init__.py +0 -0
  154. {fmdtools-2.5.1 → fmdtools-2.5.2}/tests/common.py +0 -0
  155. {fmdtools-2.5.1 → fmdtools-2.5.2}/tests/test_geom.py +0 -0
  156. {fmdtools-2.5.1 → fmdtools-2.5.2}/tests/test_graph.py +0 -0
  157. {fmdtools-2.5.1 → fmdtools-2.5.2}/tests/test_hierarchical.py +0 -0
  158. {fmdtools-2.5.1 → fmdtools-2.5.2}/tests/test_packages.py +0 -0
  159. {fmdtools-2.5.1 → fmdtools-2.5.2}/tests/test_pathplan.py +0 -0
@@ -1,4 +1,4 @@
1
- fmdtools version 2.5.1
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.1
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.1
7
+ License: fmdtools version 2.5.2
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.2
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.2},
552
+ date = {2026-9-28},
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.2
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.2},
281
+ date = {2026-9-28},
279
282
  }
280
283
  ```
281
284
 
@@ -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.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.1
7
+ License: fmdtools version 2.5.2
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.2
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.2},
552
+ date = {2026-9-28},
550
553
  }
551
554
  ```
552
555
 
@@ -127,8 +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
130
137
  tests/test_geom.py
131
138
  tests/test_graph.py
132
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
133
145
  tests/test_packages.py
134
- 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.1"
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.1'
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
@@ -41,6 +41,7 @@ specific language governing permissions and limitations under the License.
41
41
  """
42
42
 
43
43
  from fmdtools.define.base import t_key, nest_dict, is_numeric, is_bool, is_iter
44
+ from fmdtools.define.base import round_float
44
45
  from fmdtools.analyze.common import to_include_keys, create_indiv_filename
45
46
  from fmdtools.analyze.common import calc_metric, calc_metric_ci, join_key
46
47
  from fmdtools.analyze.common import get_sub_include, unpack_plot_values
@@ -551,7 +552,9 @@ class Result(UserDict):
551
552
  *values : str
552
553
  Values to get (e.g. `fxns.fxnname.s.val`)
553
554
  **groups : list
554
- Sets of scenarios to group (e.g. set_1=['scen1', 'scen2'...])
555
+ Sets of scenarios to group (e.g. set_1=['scen1', 'scen2'...]).
556
+ Scenario names match at dot-separated path boundaries; selecting a
557
+ parent scenario also includes its nested scenarios.
555
558
 
556
559
  Returns
557
560
  -------
@@ -569,7 +572,7 @@ class Result(UserDict):
569
572
  elif isinstance(scens, str):
570
573
  scens = [scens]
571
574
  k_vs = [k for k in self.keys() for scen in scens for v in values
572
- if k.startswith(scen) and k.endswith(v) and '.t.' not in k]
575
+ if k.startswith(scen+'.') and k.endswith(v) and '.t.' not in k]
573
576
  if len(k_vs) > 0 and (group not in group_hist):
574
577
  group_hist[group] = self.__class__()
575
578
  for k in k_vs:
@@ -587,22 +590,26 @@ class Result(UserDict):
587
590
 
588
591
  def get_default_comp_groups(self):
589
592
  """
590
- Get a dict of nominal and faulty scenario keys from the Result.
593
+ Get nominal and faulty keys for single or nested scenario samples.
594
+
595
+ Nested samples retain their parent prefix (e.g., ``sample.nominal``).
596
+ Only a scenario named exactly ``nominal`` belongs to the nominal group.
591
597
 
592
598
  Returns
593
599
  -------
594
600
  comp_groups : dict
595
601
  Dict with structure {'nominal': [list of nominal scenarios], 'faulty': [list of faulty scenarios]}.
596
- If no nominal or faulty, returns an empty dict {}.
602
+ If no nominal scenario is found, returns {'default': 'default'}.
597
603
  """
598
- nest = self.nest(1)
599
- nest2 = self.nest(2)
604
+ flat = self.flatten()
605
+ nest = flat.nest(1)
606
+ nest2 = flat.nest(1, skip=1)
600
607
  if 'nominal' in nest.keys():
601
608
  comp_groups = {'nominal': 'nominal',
602
609
  'faulty': [f for f in nest.keys() if f != 'nominal']}
603
- elif any(['nominal' in k for k in nest2.keys()]):
604
- comp_groups = {'nominal': [f for f in nest2.keys() if 'nominal' not in f],
605
- 'faulty': [f for f in nest2.keys() if 'nominal' not in f]}
610
+ elif any(k.endswith('.nominal') for k in nest2):
611
+ comp_groups = {'nominal': [f for f in nest2 if f.endswith('.nominal')],
612
+ 'faulty': [f for f in nest2 if not f.endswith('.nominal')]}
606
613
  else:
607
614
  comp_groups = {'default': 'default'}
608
615
  return comp_groups
@@ -781,7 +788,8 @@ class Result(UserDict):
781
788
  else:
782
789
  return tab.loc[:, metrics]
783
790
 
784
- def get_expected(self, app=[], with_nominal=False, difference_from_nominal=False):
791
+ def get_expected(self, app=[], with_nominal=False, difference_from_nominal=False,
792
+ round_value=True, res=1e-6, **kwargs):
785
793
  """
786
794
  Take the expectation of numeric metrics in the result over given scenarios.
787
795
 
@@ -795,6 +803,8 @@ class Result(UserDict):
795
803
  difference_from_nominal : bool, optional
796
804
  Whether to calculated the difference of the expectation from nominal.
797
805
  The default is False.
806
+ **kwargs
807
+ kwargs to round_float
798
808
 
799
809
  Returns
800
810
  -------
@@ -808,21 +818,25 @@ class Result(UserDict):
808
818
  newhists = {k: hist for k, hist in mh.items()
809
819
  if not ('nominal' in k and not (with_nominal))}
810
820
  if app:
811
- weights = [w.rate for w in app.scenarios()]
821
+ scenario_rates = {scen.name: scen.rate for scen in app.scenarios()}
812
822
  if with_nominal:
813
- weights.append(1)
823
+ scenario_rates['nominal'] = 1
824
+ weights = [scenario_rates[name] for name in newhists]
814
825
  else:
815
826
  weights = [1 for k in newhists]
816
827
 
817
828
  expres = self.__class__()
818
829
  for k in nomhist.keys():
819
830
  if difference_from_nominal:
820
- expres[k] = np.average([nomhist[k]-hist[k]
821
- for hist in newhists.values()],
822
- axis=0, weights=weights)
831
+ value = np.average([nomhist[k]-hist[k] for hist in newhists.values()],
832
+ axis=0, weights=weights)
833
+ else:
834
+ value = np.average([hist[k] for hist in newhists.values()],
835
+ axis=0, weights=weights)
836
+ if round_value:
837
+ expres[k] = round_float(value, res=res, **kwargs)
823
838
  else:
824
- expres[k] = np.average([hist[k] for hist in newhists.values()],
825
- axis=0, weights=weights)
839
+ expres[k] = value
826
840
  return expres
827
841
 
828
842
  def get_metric(self, value, method=np.average, rates=None, weights=None, prefix="",
@@ -856,7 +870,7 @@ class Result(UserDict):
856
870
  >>> r.get_metric("b", method="expected", rates="a")
857
871
  np.float64(0.005)
858
872
  >>> r.get_metric("b", "expected", rates={"t1": 1, "t2": 2})
859
- np.float64(0.21000000000000002)
873
+ np.float64(0.21)
860
874
  """
861
875
  vals, rates, weights = self.get_vals(value, prefix=prefix,
862
876
  rates=rates, weights=weights)
@@ -179,13 +179,15 @@ class BaseTab(UserDict):
179
179
  Parameters
180
180
  ----------
181
181
  factor : str/int
182
- Name or index of factor to sort by.
182
+ Factor name or zero-based tuple index to sort by. Negative indices
183
+ follow the usual Python indexing convention.
183
184
  reverse : bool, optional
184
185
  Whether to sort in descending order. The default is False.
185
186
  """
186
187
  metric = [*self.keys()][0]
187
188
  keys = [k for k in self[metric].keys()]
188
189
 
190
+ value = factor
189
191
  if hasattr(self, 'factors') and isinstance(factor, str):
190
192
  value = self.factors.index(factor)
191
193
 
@@ -147,7 +147,7 @@ class GeomArchitecture(Architecture):
147
147
  if not isinstance(gclass, baseclass):
148
148
  raise Exception("gclass "+gclass+" not a "+baseclass.__name__)
149
149
 
150
- def add_geom(self, name, pclass=GeomJSON, **kwargs):
150
+ def add_geom(self, name, gclass=GeomJSON, **kwargs):
151
151
  """
152
152
  Add/instantiate an individual geom to the architecture.
153
153
 
@@ -158,13 +158,13 @@ class GeomArchitecture(Architecture):
158
158
  ----------
159
159
  name : str
160
160
  Name for the geom.
161
- pclass : BaseGeom, optional
161
+ gclass : BaseGeom, optional
162
162
  Class to instantiate. The default is GeomJSON.
163
163
  **kwargs : kwargs
164
164
  Keyword arguments to the class.
165
165
  """
166
- self.check_geom_class(pclass, BaseGeom)
167
- self.add_flex_role_obj("geoms", name, objclass=pclass, **kwargs)
166
+ self.check_geom_class(gclass, BaseGeom)
167
+ self.add_flex_role_obj("geoms", name, objclass=gclass, **kwargs)
168
168
 
169
169
  def all_at(self, *pt):
170
170
  """