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.
- dstf/__init__.py +41 -0
- dstf/__main__.py +10 -0
- dstf/authoring/__init__.py +13 -0
- dstf/authoring/builtin/__init__.py +32 -0
- dstf/authoring/builtin/accept.py +205 -0
- dstf/authoring/builtin/edit_criteria.py +479 -0
- dstf/authoring/builtin/edit_family.py +228 -0
- dstf/authoring/builtin/edit_on_plot.py +1354 -0
- dstf/authoring/builtin/edit_tree.py +1399 -0
- dstf/authoring/builtin/enqueue.py +144 -0
- dstf/authoring/capabilities.py +463 -0
- dstf/authoring/errors.py +49 -0
- dstf/authoring/family_rows.py +526 -0
- dstf/authoring/forms.py +631 -0
- dstf/authoring/layout.py +104 -0
- dstf/authoring/live_rows.py +337 -0
- dstf/authoring/live_runs.py +385 -0
- dstf/authoring/needle.py +127 -0
- dstf/authoring/picker_rows.py +92 -0
- dstf/authoring/queue_rows.py +354 -0
- dstf/authoring/session.py +3016 -0
- dstf/authoring/shell/__init__.py +9 -0
- dstf/authoring/shell/actions.py +702 -0
- dstf/authoring/shell/app.py +53 -0
- dstf/authoring/shell/drawer_controls.py +190 -0
- dstf/authoring/shell/family_fold.py +468 -0
- dstf/authoring/shell/figure_hook.py +249 -0
- dstf/authoring/shell/grips.js +1000 -0
- dstf/authoring/shell/header.py +783 -0
- dstf/authoring/shell/live_view.py +93 -0
- dstf/authoring/shell/picker.py +267 -0
- dstf/authoring/shell/placing.py +122 -0
- dstf/authoring/shell/plot_card.py +1171 -0
- dstf/authoring/shell/queue_panel.py +433 -0
- dstf/authoring/shell/rail.py +593 -0
- dstf/authoring/shell/shell.py +528 -0
- dstf/authoring/shell/stats_card.py +501 -0
- dstf/authoring/shell/strip.py +204 -0
- dstf/authoring/shell/style.py +721 -0
- dstf/authoring/shell/suite.py +277 -0
- dstf/authoring/shell/suite_grid.js +414 -0
- dstf/authoring/shell/suite_grid.py +373 -0
- dstf/authoring/shell/tab_theme.py +110 -0
- dstf/authoring/shell/tabstate.py +146 -0
- dstf/authoring/shell/test_list.py +179 -0
- dstf/authoring/shell/test_page.py +1404 -0
- dstf/authoring/shell/tree_card.py +1171 -0
- dstf/authoring/shell/variables_drawer.py +589 -0
- dstf/authoring/shell/widgets.py +188 -0
- dstf/authoring/situation.py +287 -0
- dstf/authoring/spec_document.py +215 -0
- dstf/authoring/spec_preview.py +83 -0
- dstf/authoring/suite_rows.py +99 -0
- dstf/authoring/tree_move.py +516 -0
- dstf/authoring/tree_pointer.py +33 -0
- dstf/authoring/tree_text.py +61 -0
- dstf/authoring/typed_number.js +64 -0
- dstf/authoring/typed_number.py +76 -0
- dstf/authoring/unscorable.py +141 -0
- dstf/authoring/variable_rows.py +216 -0
- dstf/backends/__init__.py +283 -0
- dstf/backends/base.py +1117 -0
- dstf/backends/common/__init__.py +1 -0
- dstf/backends/common/json_driver.py +484 -0
- dstf/backends/common/live_mat.py +369 -0
- dstf/backends/common/mat_reader.py +632 -0
- dstf/backends/common/proc_kill.py +238 -0
- dstf/backends/common/proc_output.py +20 -0
- dstf/backends/common/result_files.py +109 -0
- dstf/backends/common/result_variables.py +329 -0
- dstf/backends/common/statistics.py +39 -0
- dstf/backends/cross_backend.py +193 -0
- dstf/backends/dymola/__init__.py +5 -0
- dstf/backends/dymola/backend.py +776 -0
- dstf/backends/dymola/interface_loader.py +241 -0
- dstf/backends/dymola/log_parser.py +396 -0
- dstf/backends/dymola/persistent_backend.py +949 -0
- dstf/backends/fmpy/__init__.py +11 -0
- dstf/backends/fmpy/backend.py +443 -0
- dstf/backends/julia/__init__.py +27 -0
- dstf/backends/julia/backend.py +156 -0
- dstf/backends/julia/persistent_backend.py +541 -0
- dstf/backends/julia/run_persistent.jl +167 -0
- dstf/backends/julia/run_test.jl +146 -0
- dstf/backends/openmodelica/__init__.py +21 -0
- dstf/backends/openmodelica/backend.py +368 -0
- dstf/backends/openmodelica/init_xml.py +76 -0
- dstf/backends/openmodelica/log_parser.py +119 -0
- dstf/backends/openmodelica/mos_generator.py +228 -0
- dstf/backends/openmodelica/persistent_backend.py +1045 -0
- dstf/backends/openmodelica/session_loader.py +63 -0
- dstf/backends/persistent.py +1811 -0
- dstf/backends/pool.py +602 -0
- dstf/backends/progress.py +395 -0
- dstf/backends/python/__init__.py +11 -0
- dstf/backends/python/backend.py +141 -0
- dstf/backends/python/run_test.py +141 -0
- dstf/backends/run_metadata.py +103 -0
- dstf/backends/types.py +310 -0
- dstf/cli/__init__.py +12 -0
- dstf/cli/author_cmd.py +61 -0
- dstf/cli/baseline_cmds.py +341 -0
- dstf/cli/common.py +231 -0
- dstf/cli/diag_cmds.py +350 -0
- dstf/cli/main.py +561 -0
- dstf/cli/review.py +398 -0
- dstf/cli/run_cmds.py +589 -0
- dstf/cli/spec_cmds.py +123 -0
- dstf/comparison/__init__.py +13 -0
- dstf/comparison/algorithms.py +2127 -0
- dstf/comparison/builtins.py +291 -0
- dstf/comparison/comparator.py +1189 -0
- dstf/comparison/geometry.py +164 -0
- dstf/comparison/handles.py +1194 -0
- dstf/comparison/metric_tree.py +332 -0
- dstf/comparison/mode_schema.py +176 -0
- dstf/comparison/modes.py +788 -0
- dstf/comparison/registry.py +230 -0
- dstf/comparison/seeds.py +281 -0
- dstf/comparison/sides.py +158 -0
- dstf/comparison/snap.py +62 -0
- dstf/comparison/strict_keys.py +246 -0
- dstf/comparison/summaries.py +354 -0
- dstf/comparison/tree_eval.py +868 -0
- dstf/comparison/tree_spec.py +678 -0
- dstf/comparison/types.py +194 -0
- dstf/comparison/validator.py +232 -0
- dstf/config.py +815 -0
- dstf/discovery/__init__.py +0 -0
- dstf/discovery/json_recognizer.py +702 -0
- dstf/discovery/library_state.py +248 -0
- dstf/discovery/mo_parser.py +794 -0
- dstf/discovery/patch_apply.py +308 -0
- dstf/discovery/patch_envelope.py +63 -0
- dstf/discovery/plots.py +318 -0
- dstf/discovery/recognizer.py +103 -0
- dstf/discovery/scan_cache.py +154 -0
- dstf/discovery/spec_parser.py +538 -0
- dstf/discovery/spec_shape.py +122 -0
- dstf/discovery/spec_update.py +493 -0
- dstf/discovery/test_registry.py +488 -0
- dstf/discovery/unit_tests.py +95 -0
- dstf/figures/__init__.py +14 -0
- dstf/figures/assemble.py +48 -0
- dstf/figures/decimate.py +150 -0
- dstf/figures/families.py +289 -0
- dstf/figures/palette.py +170 -0
- dstf/figures/plotly.py +1413 -0
- dstf/figures/view.py +233 -0
- dstf/pipeline.py +435 -0
- dstf/plugins.py +104 -0
- dstf/reporting/__init__.py +0 -0
- dstf/reporting/console_report.py +187 -0
- dstf/reporting/context_builder.py +979 -0
- dstf/reporting/dashboard_render.py +534 -0
- dstf/reporting/junit_report.py +192 -0
- dstf/reporting/overlay_loader.py +708 -0
- dstf/reporting/report_suite.py +1059 -0
- dstf/reporting/rows.py +458 -0
- dstf/reporting/schema_export.py +413 -0
- dstf/reporting/stats_rows.py +321 -0
- dstf/reporting/status.py +388 -0
- dstf/reporting/templates/axes.js +61 -0
- dstf/reporting/templates/dashboard.html +284 -0
- dstf/reporting/templates/dashboard.js +604 -0
- dstf/reporting/templates/filters.js +100 -0
- dstf/reporting/templates/interactive.html +420 -0
- dstf/reporting/templates/report.js +116 -0
- dstf/reporting/templates/vendor/plotly.min.js +8 -0
- dstf/reporting/theme.py +324 -0
- dstf/reporting/tree_rows.py +73 -0
- dstf/reporting/verdicts.py +124 -0
- dstf/storage/__init__.py +0 -0
- dstf/storage/accept.py +216 -0
- dstf/storage/baseline.py +43 -0
- dstf/storage/batch_manifest.py +204 -0
- dstf/storage/ref_index.py +295 -0
- dstf/storage/reference_store.py +661 -0
- dstf/storage/roles.py +576 -0
- dstf/storage/run_stats.py +85 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/METADATA +266 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/RECORD +185 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/WHEEL +4 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/entry_points.txt +2 -0
- 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)])
|