dynamic-systems-testing-framework 1.0.1__py3-none-any.whl

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 (185) hide show
  1. dstf/__init__.py +41 -0
  2. dstf/__main__.py +10 -0
  3. dstf/authoring/__init__.py +13 -0
  4. dstf/authoring/builtin/__init__.py +32 -0
  5. dstf/authoring/builtin/accept.py +205 -0
  6. dstf/authoring/builtin/edit_criteria.py +479 -0
  7. dstf/authoring/builtin/edit_family.py +228 -0
  8. dstf/authoring/builtin/edit_on_plot.py +1354 -0
  9. dstf/authoring/builtin/edit_tree.py +1399 -0
  10. dstf/authoring/builtin/enqueue.py +144 -0
  11. dstf/authoring/capabilities.py +463 -0
  12. dstf/authoring/errors.py +49 -0
  13. dstf/authoring/family_rows.py +526 -0
  14. dstf/authoring/forms.py +631 -0
  15. dstf/authoring/layout.py +104 -0
  16. dstf/authoring/live_rows.py +337 -0
  17. dstf/authoring/live_runs.py +385 -0
  18. dstf/authoring/needle.py +127 -0
  19. dstf/authoring/picker_rows.py +92 -0
  20. dstf/authoring/queue_rows.py +354 -0
  21. dstf/authoring/session.py +3016 -0
  22. dstf/authoring/shell/__init__.py +9 -0
  23. dstf/authoring/shell/actions.py +702 -0
  24. dstf/authoring/shell/app.py +53 -0
  25. dstf/authoring/shell/drawer_controls.py +190 -0
  26. dstf/authoring/shell/family_fold.py +468 -0
  27. dstf/authoring/shell/figure_hook.py +249 -0
  28. dstf/authoring/shell/grips.js +1000 -0
  29. dstf/authoring/shell/header.py +783 -0
  30. dstf/authoring/shell/live_view.py +93 -0
  31. dstf/authoring/shell/picker.py +267 -0
  32. dstf/authoring/shell/placing.py +122 -0
  33. dstf/authoring/shell/plot_card.py +1171 -0
  34. dstf/authoring/shell/queue_panel.py +433 -0
  35. dstf/authoring/shell/rail.py +593 -0
  36. dstf/authoring/shell/shell.py +528 -0
  37. dstf/authoring/shell/stats_card.py +501 -0
  38. dstf/authoring/shell/strip.py +204 -0
  39. dstf/authoring/shell/style.py +721 -0
  40. dstf/authoring/shell/suite.py +277 -0
  41. dstf/authoring/shell/suite_grid.js +414 -0
  42. dstf/authoring/shell/suite_grid.py +373 -0
  43. dstf/authoring/shell/tab_theme.py +110 -0
  44. dstf/authoring/shell/tabstate.py +146 -0
  45. dstf/authoring/shell/test_list.py +179 -0
  46. dstf/authoring/shell/test_page.py +1404 -0
  47. dstf/authoring/shell/tree_card.py +1171 -0
  48. dstf/authoring/shell/variables_drawer.py +589 -0
  49. dstf/authoring/shell/widgets.py +188 -0
  50. dstf/authoring/situation.py +287 -0
  51. dstf/authoring/spec_document.py +215 -0
  52. dstf/authoring/spec_preview.py +83 -0
  53. dstf/authoring/suite_rows.py +99 -0
  54. dstf/authoring/tree_move.py +516 -0
  55. dstf/authoring/tree_pointer.py +33 -0
  56. dstf/authoring/tree_text.py +61 -0
  57. dstf/authoring/typed_number.js +64 -0
  58. dstf/authoring/typed_number.py +76 -0
  59. dstf/authoring/unscorable.py +141 -0
  60. dstf/authoring/variable_rows.py +216 -0
  61. dstf/backends/__init__.py +283 -0
  62. dstf/backends/base.py +1117 -0
  63. dstf/backends/common/__init__.py +1 -0
  64. dstf/backends/common/json_driver.py +484 -0
  65. dstf/backends/common/live_mat.py +369 -0
  66. dstf/backends/common/mat_reader.py +632 -0
  67. dstf/backends/common/proc_kill.py +238 -0
  68. dstf/backends/common/proc_output.py +20 -0
  69. dstf/backends/common/result_files.py +109 -0
  70. dstf/backends/common/result_variables.py +329 -0
  71. dstf/backends/common/statistics.py +39 -0
  72. dstf/backends/cross_backend.py +193 -0
  73. dstf/backends/dymola/__init__.py +5 -0
  74. dstf/backends/dymola/backend.py +776 -0
  75. dstf/backends/dymola/interface_loader.py +241 -0
  76. dstf/backends/dymola/log_parser.py +396 -0
  77. dstf/backends/dymola/persistent_backend.py +949 -0
  78. dstf/backends/fmpy/__init__.py +11 -0
  79. dstf/backends/fmpy/backend.py +443 -0
  80. dstf/backends/julia/__init__.py +27 -0
  81. dstf/backends/julia/backend.py +156 -0
  82. dstf/backends/julia/persistent_backend.py +541 -0
  83. dstf/backends/julia/run_persistent.jl +167 -0
  84. dstf/backends/julia/run_test.jl +146 -0
  85. dstf/backends/openmodelica/__init__.py +21 -0
  86. dstf/backends/openmodelica/backend.py +368 -0
  87. dstf/backends/openmodelica/init_xml.py +76 -0
  88. dstf/backends/openmodelica/log_parser.py +119 -0
  89. dstf/backends/openmodelica/mos_generator.py +228 -0
  90. dstf/backends/openmodelica/persistent_backend.py +1045 -0
  91. dstf/backends/openmodelica/session_loader.py +63 -0
  92. dstf/backends/persistent.py +1811 -0
  93. dstf/backends/pool.py +602 -0
  94. dstf/backends/progress.py +395 -0
  95. dstf/backends/python/__init__.py +11 -0
  96. dstf/backends/python/backend.py +141 -0
  97. dstf/backends/python/run_test.py +141 -0
  98. dstf/backends/run_metadata.py +103 -0
  99. dstf/backends/types.py +310 -0
  100. dstf/cli/__init__.py +12 -0
  101. dstf/cli/author_cmd.py +61 -0
  102. dstf/cli/baseline_cmds.py +341 -0
  103. dstf/cli/common.py +231 -0
  104. dstf/cli/diag_cmds.py +350 -0
  105. dstf/cli/main.py +561 -0
  106. dstf/cli/review.py +398 -0
  107. dstf/cli/run_cmds.py +589 -0
  108. dstf/cli/spec_cmds.py +123 -0
  109. dstf/comparison/__init__.py +13 -0
  110. dstf/comparison/algorithms.py +2127 -0
  111. dstf/comparison/builtins.py +291 -0
  112. dstf/comparison/comparator.py +1189 -0
  113. dstf/comparison/geometry.py +164 -0
  114. dstf/comparison/handles.py +1194 -0
  115. dstf/comparison/metric_tree.py +332 -0
  116. dstf/comparison/mode_schema.py +176 -0
  117. dstf/comparison/modes.py +788 -0
  118. dstf/comparison/registry.py +230 -0
  119. dstf/comparison/seeds.py +281 -0
  120. dstf/comparison/sides.py +158 -0
  121. dstf/comparison/snap.py +62 -0
  122. dstf/comparison/strict_keys.py +246 -0
  123. dstf/comparison/summaries.py +354 -0
  124. dstf/comparison/tree_eval.py +868 -0
  125. dstf/comparison/tree_spec.py +678 -0
  126. dstf/comparison/types.py +194 -0
  127. dstf/comparison/validator.py +232 -0
  128. dstf/config.py +815 -0
  129. dstf/discovery/__init__.py +0 -0
  130. dstf/discovery/json_recognizer.py +702 -0
  131. dstf/discovery/library_state.py +248 -0
  132. dstf/discovery/mo_parser.py +794 -0
  133. dstf/discovery/patch_apply.py +308 -0
  134. dstf/discovery/patch_envelope.py +63 -0
  135. dstf/discovery/plots.py +318 -0
  136. dstf/discovery/recognizer.py +103 -0
  137. dstf/discovery/scan_cache.py +154 -0
  138. dstf/discovery/spec_parser.py +538 -0
  139. dstf/discovery/spec_shape.py +122 -0
  140. dstf/discovery/spec_update.py +493 -0
  141. dstf/discovery/test_registry.py +488 -0
  142. dstf/discovery/unit_tests.py +95 -0
  143. dstf/figures/__init__.py +14 -0
  144. dstf/figures/assemble.py +48 -0
  145. dstf/figures/decimate.py +150 -0
  146. dstf/figures/families.py +289 -0
  147. dstf/figures/palette.py +170 -0
  148. dstf/figures/plotly.py +1413 -0
  149. dstf/figures/view.py +233 -0
  150. dstf/pipeline.py +435 -0
  151. dstf/plugins.py +104 -0
  152. dstf/reporting/__init__.py +0 -0
  153. dstf/reporting/console_report.py +187 -0
  154. dstf/reporting/context_builder.py +979 -0
  155. dstf/reporting/dashboard_render.py +534 -0
  156. dstf/reporting/junit_report.py +192 -0
  157. dstf/reporting/overlay_loader.py +708 -0
  158. dstf/reporting/report_suite.py +1059 -0
  159. dstf/reporting/rows.py +458 -0
  160. dstf/reporting/schema_export.py +413 -0
  161. dstf/reporting/stats_rows.py +321 -0
  162. dstf/reporting/status.py +388 -0
  163. dstf/reporting/templates/axes.js +61 -0
  164. dstf/reporting/templates/dashboard.html +284 -0
  165. dstf/reporting/templates/dashboard.js +604 -0
  166. dstf/reporting/templates/filters.js +100 -0
  167. dstf/reporting/templates/interactive.html +420 -0
  168. dstf/reporting/templates/report.js +116 -0
  169. dstf/reporting/templates/vendor/plotly.min.js +8 -0
  170. dstf/reporting/theme.py +324 -0
  171. dstf/reporting/tree_rows.py +73 -0
  172. dstf/reporting/verdicts.py +124 -0
  173. dstf/storage/__init__.py +0 -0
  174. dstf/storage/accept.py +216 -0
  175. dstf/storage/baseline.py +43 -0
  176. dstf/storage/batch_manifest.py +204 -0
  177. dstf/storage/ref_index.py +295 -0
  178. dstf/storage/reference_store.py +661 -0
  179. dstf/storage/roles.py +576 -0
  180. dstf/storage/run_stats.py +85 -0
  181. dynamic_systems_testing_framework-1.0.1.dist-info/METADATA +266 -0
  182. dynamic_systems_testing_framework-1.0.1.dist-info/RECORD +185 -0
  183. dynamic_systems_testing_framework-1.0.1.dist-info/WHEEL +4 -0
  184. dynamic_systems_testing_framework-1.0.1.dist-info/entry_points.txt +2 -0
  185. dynamic_systems_testing_framework-1.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,291 @@
1
+ """Registration of the built-in metrics and combinators.
2
+
3
+ Imported for its side effect by the package ``__init__`` — the same shape
4
+ ``backends`` uses for its backends. Each ``build`` here reproduces the
5
+ per-mode construction that used to live in ``modes.resolve_mode``'s
6
+ if/elif chain, so it is the single place that knows how an override dict
7
+ turns into a configured mode.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from ..backends.types import DatasetType
13
+ from .handles import (
14
+ edit_events_handle,
15
+ edit_peaks_handle,
16
+ edit_points_handle,
17
+ edit_range_handle,
18
+ edit_scalar_handle,
19
+ edit_tube_handle,
20
+ place_events_row,
21
+ place_peaks_row,
22
+ place_points_row,
23
+ place_tube_row,
24
+ tube_row_noun,
25
+ )
26
+ from .metric_tree import (
27
+ AndCombinator,
28
+ KOfNCombinator,
29
+ OrCombinator,
30
+ WarnCombinator,
31
+ WeightedCombinator,
32
+ )
33
+ from .modes import (
34
+ _TUBE_KEYS,
35
+ DominantFrequencyConfig,
36
+ DominantFrequencyMode,
37
+ EventTimingConfig,
38
+ EventTimingMode,
39
+ NrmseConfig,
40
+ NrmseMode,
41
+ PointsConfig,
42
+ PointsMode,
43
+ RangeConfig,
44
+ RangeMode,
45
+ ScalarConfig,
46
+ ScalarMode,
47
+ TubeConfig,
48
+ TubeMode,
49
+ )
50
+ from .registry import (
51
+ DEFAULT_METRIC,
52
+ CombinatorDescriptor,
53
+ MetricDescriptor,
54
+ metric,
55
+ register_combinator,
56
+ register_metric,
57
+ )
58
+ from .seeds import seed_dominant_frequency, seed_range, seed_scalar, seed_tube
59
+ from .sides import Sides, parse_number, parse_sides
60
+ from .summaries import (
61
+ summarize_dominant_frequency,
62
+ summarize_event_timing,
63
+ summarize_nrmse,
64
+ summarize_points,
65
+ summarize_range,
66
+ summarize_scalar,
67
+ summarize_tube,
68
+ )
69
+
70
+
71
+ def _build_nrmse(override: dict, tolerance: Sides) -> NrmseMode:
72
+ if not tolerance.equal:
73
+ raise ValueError("nrmse has one ceiling: tolerance must be a number")
74
+ return NrmseMode(NrmseConfig(tolerance=tolerance.upper))
75
+
76
+
77
+ def _build_tube(override: dict, tolerance: Sides) -> TubeMode:
78
+ tube_kwargs = {k: override[k] for k in _TUBE_KEYS if k in override}
79
+ for key in ("tube_abs", "tube_rel"):
80
+ if key not in tube_kwargs:
81
+ continue
82
+ if tube_kwargs[key] is None:
83
+ # ``null`` is "no width", as it was when a width was a number
84
+ # (a falsy one was left out): absent, not a config error.
85
+ del tube_kwargs[key]
86
+ else:
87
+ tube_kwargs[key] = parse_sides(tube_kwargs[key], key)
88
+ # The floor has no sides: one number, read here so that anything else
89
+ # is a ``ValueError`` where ``resolve_mode``'s callers catch it — a
90
+ # pair or a string went on to the scorer, which compared it with zero
91
+ # and raised a ``TypeError`` nothing caught. ``null`` is "no floor".
92
+ if tube_kwargs.get("tube_min_width") is None:
93
+ tube_kwargs.pop("tube_min_width", None)
94
+ else:
95
+ tube_kwargs["tube_min_width"] = parse_number(
96
+ tube_kwargs["tube_min_width"], "tube_min_width"
97
+ )
98
+ return TubeMode(TubeConfig(**tube_kwargs))
99
+
100
+
101
+ def _build_points(override: dict, tolerance: Sides) -> PointsMode:
102
+ return PointsMode(
103
+ PointsConfig(
104
+ points=override.get("points"),
105
+ tolerance=tolerance,
106
+ )
107
+ )
108
+
109
+
110
+ def _bound(override: dict, key: str) -> float | None:
111
+ """A ``min_value`` / ``max_value`` as it is written: one number, or
112
+ none (absent, or ``null``). Checked here, as a tube's floor is, so that
113
+ anything else is a ``ValueError`` where ``resolve_mode``'s callers catch
114
+ it: a string, or an integer too large for a float, went on to the
115
+ scorer, which subtracted it from the samples and raised what nothing
116
+ caught."""
117
+ value = override.get(key)
118
+ if value is not None:
119
+ parse_number(value, key)
120
+ return value
121
+
122
+
123
+ def _build_range(override: dict, tolerance: Sides) -> RangeMode:
124
+ # ``min_value`` / ``max_value`` only — the names the ``RangeConfig``
125
+ # dataclass, the auto-derived UI and MetricTree leaf params all use.
126
+ return RangeMode(
127
+ RangeConfig(
128
+ min_value=_bound(override, "min_value"),
129
+ max_value=_bound(override, "max_value"),
130
+ )
131
+ )
132
+
133
+
134
+ def _build_event_timing(override: dict, tolerance: Sides) -> EventTimingMode:
135
+ return EventTimingMode(
136
+ EventTimingConfig(
137
+ time_tolerance=parse_sides(
138
+ override.get("time_tolerance", 1e-3), "time_tolerance"
139
+ ),
140
+ count_must_match=override.get("count_must_match", True),
141
+ events=override.get("events"),
142
+ )
143
+ )
144
+
145
+
146
+ def _build_dominant_frequency(
147
+ override: dict, tolerance: Sides
148
+ ) -> DominantFrequencyMode:
149
+ return DominantFrequencyMode(
150
+ DominantFrequencyConfig(
151
+ peaks=override.get("peaks"),
152
+ )
153
+ )
154
+
155
+
156
+ def _build_scalar(override: dict, tolerance: Sides) -> ScalarMode:
157
+ return ScalarMode(
158
+ ScalarConfig(
159
+ quantity=override.get("quantity"),
160
+ tolerance=tolerance,
161
+ tolerance_mode=override.get("tolerance_mode", "abs"),
162
+ min_value=_bound(override, "min_value"),
163
+ max_value=_bound(override, "max_value"),
164
+ )
165
+ )
166
+
167
+
168
+ def register_builtins() -> None:
169
+ """Register the seven built-in metrics and five combinators.
170
+
171
+ Registration order is the canonical presentation order (schema export
172
+ ``$defs``, the reporter's mode-schema map, the "valid: ..." list in
173
+ :func:`~.modes.resolve_mode`'s error message).
174
+
175
+ Idempotent: ``importlib.reload(dstf.comparison)`` re-executes the
176
+ package init, and a second registration pass must not trip the
177
+ duplicate-name guard (B1 review finding 3).
178
+ """
179
+ if metric(DEFAULT_METRIC) is not None:
180
+ return
181
+ register_metric(
182
+ MetricDescriptor(
183
+ name="nrmse",
184
+ config_cls=NrmseConfig,
185
+ build=_build_nrmse,
186
+ score_direction="lower",
187
+ summarize=summarize_nrmse,
188
+ )
189
+ )
190
+ register_metric(
191
+ MetricDescriptor(
192
+ name="tube",
193
+ config_cls=TubeConfig,
194
+ build=_build_tube,
195
+ # Tube's score is the fraction of samples inside the envelope.
196
+ score_direction="higher",
197
+ summarize=summarize_tube,
198
+ edit_handle=edit_tube_handle,
199
+ seed=seed_tube,
200
+ place_row=place_tube_row,
201
+ row_noun=tube_row_noun,
202
+ )
203
+ )
204
+ register_metric(
205
+ MetricDescriptor(
206
+ name="points",
207
+ config_cls=PointsConfig,
208
+ build=_build_points,
209
+ score_direction="lower",
210
+ summarize=summarize_points,
211
+ edit_handle=edit_points_handle,
212
+ place_row=place_points_row,
213
+ row_noun=lambda _leaf: "a point",
214
+ )
215
+ )
216
+ register_metric(
217
+ MetricDescriptor(
218
+ name="range",
219
+ config_cls=RangeConfig,
220
+ build=_build_range,
221
+ score_direction="lower",
222
+ summarize=summarize_range,
223
+ edit_handle=edit_range_handle,
224
+ drawn_by_default=False,
225
+ seed=seed_range,
226
+ )
227
+ )
228
+ register_metric(
229
+ MetricDescriptor(
230
+ name="event-timing",
231
+ config_cls=EventTimingConfig,
232
+ build=_build_event_timing,
233
+ score_direction="lower",
234
+ summarize=summarize_event_timing,
235
+ edit_handle=edit_events_handle,
236
+ place_row=place_events_row,
237
+ row_noun=lambda _leaf: "an event",
238
+ )
239
+ )
240
+ register_metric(
241
+ MetricDescriptor(
242
+ name="dominant-frequency",
243
+ config_cls=DominantFrequencyConfig,
244
+ build=_build_dominant_frequency,
245
+ score_direction="lower",
246
+ summarize=summarize_dominant_frequency,
247
+ edit_handle=edit_peaks_handle,
248
+ seed=seed_dominant_frequency,
249
+ place_row=place_peaks_row,
250
+ row_noun=lambda _leaf: "a peak",
251
+ )
252
+ )
253
+ register_metric(
254
+ MetricDescriptor(
255
+ name="scalar",
256
+ config_cls=ScalarConfig,
257
+ build=_build_scalar,
258
+ # The one metric that scores a number rather than a trajectory:
259
+ # it works on a scalars-only dataset as well as on a time series
260
+ # it reduces itself.
261
+ accepted_datasets=frozenset({DatasetType.TIME_SERIES, DatasetType.SCALARS}),
262
+ score_direction="lower",
263
+ # The only builtin whose ``quantity`` names its data source.
264
+ accepts_quantity=True,
265
+ summarize=summarize_scalar,
266
+ edit_handle=edit_scalar_handle,
267
+ drawn_by_default=False,
268
+ seed=seed_scalar,
269
+ )
270
+ )
271
+
272
+ register_combinator(
273
+ CombinatorDescriptor(name="and", build=lambda spec: AndCombinator())
274
+ )
275
+ register_combinator(
276
+ CombinatorDescriptor(name="or", build=lambda spec: OrCombinator())
277
+ )
278
+ register_combinator(
279
+ CombinatorDescriptor(name="k-of-n", build=lambda spec: KOfNCombinator(spec.k))
280
+ )
281
+ register_combinator(
282
+ CombinatorDescriptor(name="warn", build=lambda spec: WarnCombinator())
283
+ )
284
+ register_combinator(
285
+ CombinatorDescriptor(
286
+ name="weighted",
287
+ build=lambda spec: WeightedCombinator(
288
+ spec.weights, spec.threshold, spec.direction
289
+ ),
290
+ )
291
+ )