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,228 @@
1
+ """Edit family (D152): the FAMILY_CRITERION placement — the Indices field
2
+ of a folded criteria row's editor. The row is one criterion on several
3
+ elements of an array; the field says which, in the syntax the add form's
4
+ Indices takes (`families.parse_slice`), and applying it removes the leaf of
5
+ every element left out and adds one, with the fold's criterion, for every
6
+ element new to it. One gesture stages ONE whole-tree op
7
+ (`family_rows.as_one`). The fields beside it are Edit criteria's.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from ..capabilities import Panel, Patch, Placement, plural
15
+ from ..family_rows import NOT_A_FOLD, Family, Reindexed, as_one, fold_key, reindex
16
+ from ..session import no_tree_error
17
+ from ..tree_move import path_map
18
+ from ..tree_pointer import node_or_none
19
+ from ..unscorable import Refused
20
+ from .edit_tree import _family
21
+
22
+ if TYPE_CHECKING:
23
+ from ..session import SessionView
24
+
25
+ # NO module-level `nicegui` import: `import dstf.authoring` runs
26
+ # register_builtins(), and the Session must import without the extra.
27
+
28
+ #: An Indices text that names nothing. The fold is the criterion: taking
29
+ #: every element away is removing it, and that is the rows' own `−`.
30
+ EMPTY = (
31
+ "Indices is empty. To remove the criterion, unfold this row and remove "
32
+ "its criteria instead."
33
+ )
34
+
35
+
36
+ def _variables(tree: dict | None, paths: tuple[str, ...]) -> list[str] | None:
37
+ """The members' variables, in ``paths``' order — None when a path no
38
+ longer names a criterion (an edit staged elsewhere moved the tree)."""
39
+ nodes = [node_or_none(tree, path) for path in paths]
40
+ if not nodes or any(node is None or "metric" not in node for node in nodes):
41
+ return None
42
+ return [str(node["variable"]) for node in nodes if node is not None]
43
+
44
+
45
+ def planned(
46
+ s: SessionView,
47
+ model_id: str,
48
+ tree: dict,
49
+ paths: tuple[str, ...],
50
+ variables: tuple[str, ...],
51
+ text: str,
52
+ family: Family | None = None,
53
+ ) -> Reindexed:
54
+ """The Indices edit `EditFamily.act` stages, with where the members
55
+ are afterwards — what a panel counts as the text is typed and reveals
56
+ once its ``submit`` went through. ``variables`` are the members as the
57
+ panel read them: a path whose leaf is no longer that member's — an
58
+ edit staged elsewhere moved the tree under the row — refuses the whole
59
+ edit, or it would remove another criterion's leaf.
60
+
61
+ ``family`` is the members' (`_family`), read here when not given: every
62
+ element the last run's result file has and every name the test scores
63
+ (`Session.variable_names` lists both), so a member that run did not
64
+ record is an element all the same (D142: the strip asks for a rerun).
65
+
66
+ `Refused` with the sentence to show — a refusal reaches the person as
67
+ that sentence and logs nothing; as a plain ``ValueError``, which a
68
+ bug's also is, each was logged with a traceback: nothing named
69
+ (`EMPTY`), a text that is no slice, an index the family does not have,
70
+ an element with a criterion of its own (`reindex`), members gone
71
+ stale."""
72
+ if not text.replace(",", "").strip():
73
+ raise Refused(EMPTY)
74
+ if not variables or _variables(tree, paths) != list(variables):
75
+ raise Refused(NOT_A_FOLD)
76
+ if family is None:
77
+ family = _family(s, model_id, variables[0])
78
+ if family is None:
79
+ raise Refused(NOT_A_FOLD)
80
+ return reindex(model_id, tree, paths, family.chosen(text))
81
+
82
+
83
+ def count_text(plan: Reindexed) -> str:
84
+ """What an Indices text would do, said beside it as it is typed:
85
+ ``→ 5 elements (removes 15)``. `1:20:5` is start:step:end — ONE element
86
+ — and Enter on it took nineteen criteria away with nothing said."""
87
+ did = [
88
+ f"{verb} {len(names)}"
89
+ for verb, names in (("removes", plan.removed), ("adds", plan.added))
90
+ if names
91
+ ]
92
+ after = plural(len(plan.paths), "element")
93
+ return f"→ {after} ({', '.join(did) or 'no change'})"
94
+
95
+
96
+ class EditFamily:
97
+ """Which elements of its array a folded criterion is on."""
98
+
99
+ name = "edit-family"
100
+ label = "Edit family"
101
+ placements = frozenset({Placement.FAMILY_CRITERION})
102
+
103
+ def caption(self, s: SessionView) -> str:
104
+ # FAMILY_CRITERION is inline: no button reads a caption there.
105
+ return self.label
106
+
107
+ def available(self, s: SessionView) -> bool | str:
108
+ sel = s.selection
109
+ model_id = next(iter(sel.tests)) if len(sel.tests) == 1 else None
110
+ if model_id is None or len(sel.leaf_paths) < 2:
111
+ return "select one folded row"
112
+ # Edit criteria's sentence for a tree the Session will not edit:
113
+ # the host says one sentence once.
114
+ _tree, why = s.tree_or_reason(model_id)
115
+ return True if why is None else why
116
+
117
+ def panel(self, s: SessionView, ui_panel: Panel) -> None:
118
+ from nicegui import ui
119
+
120
+ sel = s.selection
121
+ model_id = next(iter(sel.tests))
122
+ paths = sel.leaf_paths
123
+ named = _variables(s.tree(model_id), paths)
124
+ family = None if named is None else _family(s, model_id, named[0])
125
+ if named is None or family is None:
126
+ # The panel is rebuilt after edits made elsewhere.
127
+ ui.label(NOT_A_FOLD).classes("dstf-why")
128
+ return
129
+ variables = tuple(named)
130
+ applied = family.indices_of(variables)
131
+ #: The text a large removal was asked to confirm, once.
132
+ asked: dict[str, str | None] = {"text": None}
133
+
134
+ def plan_of(text: str) -> Reindexed:
135
+ tree = s.tree(model_id)
136
+ if tree is None:
137
+ raise Refused(NOT_A_FOLD)
138
+ return planned(s, model_id, tree, paths, variables, text, family)
139
+
140
+ def count() -> None:
141
+ """Beside the field, as it is typed: what Enter would do."""
142
+ try:
143
+ said = count_text(plan_of(str(field.value or "")))
144
+ except ValueError as e:
145
+ said = str(e)
146
+ counted.set_text(said)
147
+ counted.classes(remove="text-negative")
148
+
149
+ def apply() -> None:
150
+ text = str(field.value or "")
151
+ tree = s.tree(model_id)
152
+ try:
153
+ # Planned BEFORE the submit: afterwards the paths name the
154
+ # new tree.
155
+ plan = plan_of(text)
156
+ except ValueError: # the submit raises it again, and says it
157
+ plan = None
158
+ if (
159
+ plan is not None
160
+ and 2 * len(plan.removed) > len(paths)
161
+ and asked["text"] != text
162
+ ):
163
+ # More than half of the criteria would go: asked once,
164
+ # inline, for this very text.
165
+ asked["text"] = text
166
+ counted.set_text(
167
+ f"Removes {len(plan.removed)} of {len(paths)} criteria — "
168
+ "Apply again to confirm"
169
+ )
170
+ counted.classes(add="text-negative")
171
+ return
172
+ if not ui_panel.submit(
173
+ model_id=model_id, paths=paths, variables=variables, indices=text
174
+ ):
175
+ return
176
+ if tree is None or plan is None or not plan.ops:
177
+ # Nothing moved: the field reads what the fold holds.
178
+ field.set_value(applied)
179
+ return
180
+ ui_panel.edited(path_map(tree, plan.ops))
181
+ # The rows are redrawn (leaves came or went): the fold's editor
182
+ # opens again on what it holds now — or, one element left, that
183
+ # criterion's own.
184
+ first = plan.paths[0]
185
+ ui_panel.reveal(fold_key(first) if len(plan.paths) > 1 else first)
186
+
187
+ with ui.row().classes("dstf-fields items-start gap-3 wrap w-full"):
188
+ field = (
189
+ ui.input(label="Indices", value=applied, on_change=count)
190
+ .props("dense")
191
+ .classes("dstf-field")
192
+ # Wide enough for its hint on one line: at a field's 140 px
193
+ # the sentence broke over three (the LOOK, in Chromium).
194
+ .style("min-width: 44ch")
195
+ .mark("edit-family:indices")
196
+ )
197
+ field.props["hint"] = (
198
+ f"Elements of {family.label}. Enter applies, Esc puts back."
199
+ )
200
+ field.on("keydown.enter", apply)
201
+ # Esc: the text applied last, as the fold holds it.
202
+ field.on("keydown.escape", lambda: field.set_value(applied))
203
+ ui.button("Apply", on_click=apply).props("flat dense no-caps").mark(
204
+ "edit-family:apply"
205
+ )
206
+ counted = (
207
+ ui.label().classes("dstf-why self-center").mark("edit-family:count")
208
+ )
209
+ count()
210
+
211
+ def act(self, s: SessionView, **inputs: Any) -> Patch | None:
212
+ model_id, paths = inputs["model_id"], tuple(inputs["paths"])
213
+ tree, why = s.tree_or_reason(model_id)
214
+ if tree is None:
215
+ assert why is not None # `tree` is None
216
+ raise no_tree_error(model_id, why)
217
+ plan = planned(
218
+ s,
219
+ model_id,
220
+ tree,
221
+ paths,
222
+ tuple(inputs.get("variables") or ()),
223
+ str(inputs["indices"]),
224
+ )
225
+ if not plan.ops:
226
+ # The elements it already holds: nothing happened, nothing staged.
227
+ return None
228
+ return Patch(ops=[as_one(model_id, tree, plan.ops, plan.notes)])