drbx 2.0.0.dev0__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.
- drbx/__init__.py +20 -0
- drbx/__main__.py +5 -0
- drbx/cli.py +586 -0
- drbx/config/__init__.py +20 -0
- drbx/config/boutinp.py +556 -0
- drbx/config/model.py +34 -0
- drbx/config/normalization.py +66 -0
- drbx/data/atomic_rates/__init__.py +1 -0
- drbx/data/atomic_rates/iz_AMJUEL_H.x_2.1.5.json +213 -0
- drbx/data/atomic_rates/iz_AMJUEL_H.x_2.3.9a.json +213 -0
- drbx/data/atomic_rates/rec_AMJUEL_H.x_2.1.8.json +213 -0
- drbx/data/atomic_rates/rec_AMJUEL_H.x_2.3.13a.json +213 -0
- drbx/geometry/__init__.py +207 -0
- drbx/geometry/embedding.py +56 -0
- drbx/geometry/essos_import.py +1385 -0
- drbx/geometry/fci_geometry.py +4622 -0
- drbx/geometry/fci_maps.py +85 -0
- drbx/geometry/island_divertor.py +291 -0
- drbx/geometry/metric_tensor.py +99 -0
- drbx/geometry/open_slab.py +150 -0
- drbx/geometry/rotating_ellipse.py +253 -0
- drbx/geometry/shifted_torus.py +225 -0
- drbx/geometry/stellarator.py +287 -0
- drbx/geometry/vmec_extender_import.py +499 -0
- drbx/geometry/vmec_jax_import.py +306 -0
- drbx/linear/__init__.py +37 -0
- drbx/linear/dispersion.py +138 -0
- drbx/linear/eigen.py +91 -0
- drbx/native/__init__.py +224 -0
- drbx/native/array_backend.py +64 -0
- drbx/native/deck_runner.py +779 -0
- drbx/native/electromagnetic.py +250 -0
- drbx/native/expression.py +173 -0
- drbx/native/fci.py +295 -0
- drbx/native/fci_2_field_rhs.py +182 -0
- drbx/native/fci_4_field_rhs.py +1267 -0
- drbx/native/fci_boundaries.py +2494 -0
- drbx/native/fci_differentiable_case.py +304 -0
- drbx/native/fci_drb_EB_rhs.py +1243 -0
- drbx/native/fci_drb_rhs.py +190 -0
- drbx/native/fci_halo.py +1575 -0
- drbx/native/fci_helpers.py +350 -0
- drbx/native/fci_model.py +294 -0
- drbx/native/fci_neutral.py +139 -0
- drbx/native/fci_operators.py +4081 -0
- drbx/native/fci_sharding.py +597 -0
- drbx/native/fci_sheath_recycling.py +206 -0
- drbx/native/fci_time_integrator.py +96 -0
- drbx/native/fci_vorticity.py +198 -0
- drbx/native/fluid_1d.py +330 -0
- drbx/native/hasegawa_wakatani.py +196 -0
- drbx/native/limiters.py +57 -0
- drbx/native/mesh.py +238 -0
- drbx/native/metrics.py +234 -0
- drbx/native/neutrals/__init__.py +58 -0
- drbx/native/neutrals/atomic_rates.py +134 -0
- drbx/native/neutrals/detachment_sol_model.py +221 -0
- drbx/native/neutrals/reactions.py +164 -0
- drbx/native/neutrals/recycling_sol_model.py +197 -0
- drbx/native/sol_flux_tube.py +133 -0
- drbx/native/stellarator_turbulence.py +343 -0
- drbx/native/transport.py +134 -0
- drbx/native/units.py +32 -0
- drbx/native/vorticity.py +252 -0
- drbx/runtime/__init__.py +53 -0
- drbx/runtime/artifacts.py +161 -0
- drbx/runtime/memory.py +144 -0
- drbx/runtime/output.py +374 -0
- drbx/runtime/paths.py +9 -0
- drbx/runtime/performance.py +161 -0
- drbx/runtime/run_config.py +184 -0
- drbx/runtime/scheduler.py +99 -0
- drbx/runtime/state.py +40 -0
- drbx/validation/__init__.py +424 -0
- drbx/validation/autodiff_diffusion.py +329 -0
- drbx/validation/autodiff_diffusion_uncertainty.py +235 -0
- drbx/validation/diverted_tokamak_movie.py +558 -0
- drbx/validation/essos_fieldline_import_campaign.py +181 -0
- drbx/validation/essos_imported_artifact_audit.py +535 -0
- drbx/validation/essos_imported_drb_movie_campaign.py +2826 -0
- drbx/validation/essos_imported_fci_campaign.py +5241 -0
- drbx/validation/essos_imported_pytree_campaign.py +406 -0
- drbx/validation/essos_vmec_closed_field_campaign.py +314 -0
- drbx/validation/essos_vmec_closed_field_transient_campaign.py +629 -0
- drbx/validation/essos_vmec_fieldline_surface_campaign.py +620 -0
- drbx/validation/fluid_1d_mms_convergence.py +250 -0
- drbx/validation/geometry_lineouts.py +136 -0
- drbx/validation/geometry_slices.py +178 -0
- drbx/validation/publication_plotting.py +91 -0
- drbx/validation/stellarator_drb_pytree_campaign.py +621 -0
- drbx/validation/stellarator_fci_geometry_campaign.py +200 -0
- drbx/validation/stellarator_fci_operator_campaign.py +304 -0
- drbx/validation/stellarator_fci_suite_campaign.py +264 -0
- drbx/validation/stellarator_metric_mms_campaign.py +289 -0
- drbx/validation/stellarator_neutral_physics_campaign.py +255 -0
- drbx/validation/stellarator_sheath_recycling_campaign.py +331 -0
- drbx/validation/stellarator_sol_showcase.py +628 -0
- drbx/validation/stellarator_vorticity_campaign.py +304 -0
- drbx/validation/vmec_extender_edge_field_campaign.py +260 -0
- drbx/validation/vmec_extender_sol_smoke_campaign.py +365 -0
- drbx-2.0.0.dev0.dist-info/METADATA +380 -0
- drbx-2.0.0.dev0.dist-info/RECORD +106 -0
- drbx-2.0.0.dev0.dist-info/WHEEL +5 -0
- drbx-2.0.0.dev0.dist-info/entry_points.txt +2 -0
- drbx-2.0.0.dev0.dist-info/licenses/LICENSE +21 -0
- drbx-2.0.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .essos_imported_fci_campaign import (
|
|
10
|
+
_IMPORTED_FCI_DIAGNOSTIC_SCHEMA,
|
|
11
|
+
_IMPORTED_FCI_REQUIRED_REPORT_FIELDS,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_IMPORTED_DRB_MOVIE_REQUIRED_REPORT_FIELDS = (
|
|
16
|
+
"case",
|
|
17
|
+
"source",
|
|
18
|
+
"map_source",
|
|
19
|
+
"claim_scope",
|
|
20
|
+
"publication_ready",
|
|
21
|
+
"movie_evidence_role",
|
|
22
|
+
"movie_promotion_rejection_reasons",
|
|
23
|
+
"required_publication_gates",
|
|
24
|
+
"geometry",
|
|
25
|
+
"movie_physics_grid",
|
|
26
|
+
"movie_render_coordinate_model",
|
|
27
|
+
"frames",
|
|
28
|
+
"substeps_per_frame",
|
|
29
|
+
"dt",
|
|
30
|
+
"execute_seconds",
|
|
31
|
+
"endpoint_fraction",
|
|
32
|
+
"magnetic_field_modulation",
|
|
33
|
+
"connection_length_mean",
|
|
34
|
+
"final_min_density",
|
|
35
|
+
"initial_fluctuation_rms",
|
|
36
|
+
"final_fluctuation_rms",
|
|
37
|
+
"max_fluctuation_rms",
|
|
38
|
+
"final_ion_density_mean",
|
|
39
|
+
"final_neutral_density_mean",
|
|
40
|
+
"final_vorticity_rms",
|
|
41
|
+
"final_potential_residual_l2",
|
|
42
|
+
"radial_flux_proxy",
|
|
43
|
+
"radial_flux_abs_mean",
|
|
44
|
+
"radial_flux_rms",
|
|
45
|
+
"radial_flux_peak_abs",
|
|
46
|
+
"radial_flux_cancellation_ratio",
|
|
47
|
+
"radial_flux_positive_fraction",
|
|
48
|
+
"low_mode_spectral_power_fraction",
|
|
49
|
+
"spectral_poloidal_mode_count",
|
|
50
|
+
"spectral_toroidal_mode_count",
|
|
51
|
+
"spectral_centroid_poloidal_index",
|
|
52
|
+
"spectral_centroid_toroidal_index",
|
|
53
|
+
"spectral_centroid_poloidal_fraction",
|
|
54
|
+
"spectral_centroid_toroidal_fraction",
|
|
55
|
+
"spectral_edge_band_power_fraction",
|
|
56
|
+
"low_mode_window_covers_grid",
|
|
57
|
+
"dominant_poloidal_mode_index",
|
|
58
|
+
"dominant_toroidal_mode_index",
|
|
59
|
+
"total_target_heat_load",
|
|
60
|
+
"total_particle_loss",
|
|
61
|
+
"total_ionisation",
|
|
62
|
+
"total_recombination",
|
|
63
|
+
"total_charge_exchange",
|
|
64
|
+
"particle_recycling_relative_error",
|
|
65
|
+
"current_balance_relative_error",
|
|
66
|
+
"neutral_particle_relative_error",
|
|
67
|
+
"neutral_momentum_relative_error",
|
|
68
|
+
"movie_audit_passed",
|
|
69
|
+
"movie_frame_count",
|
|
70
|
+
"movie_frame_size",
|
|
71
|
+
"movie_file_size_bytes",
|
|
72
|
+
"movie_bbox_unique_count",
|
|
73
|
+
"movie_frame_rms_min",
|
|
74
|
+
"movie_frame_rms_median",
|
|
75
|
+
"movie_frame_rms_max",
|
|
76
|
+
"passed",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def audit_essos_imported_artifact_report(
|
|
81
|
+
report_json_path: str | Path,
|
|
82
|
+
*,
|
|
83
|
+
artifact_kind: str = "auto",
|
|
84
|
+
) -> dict[str, Any]:
|
|
85
|
+
"""Audit an imported-field validation report against the current schema.
|
|
86
|
+
|
|
87
|
+
The audit is intentionally lightweight: it reads a committed JSON report and
|
|
88
|
+
checks whether the report still contains the fields produced by the current
|
|
89
|
+
validation code. It does not rerun ESSOS, VMEC, or DRBX simulations.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
path = Path(report_json_path)
|
|
93
|
+
base_report: dict[str, Any] = {
|
|
94
|
+
"report_json_path": str(path),
|
|
95
|
+
"artifact_kind": str(artifact_kind),
|
|
96
|
+
"exists": path.exists(),
|
|
97
|
+
"loaded": False,
|
|
98
|
+
"schema_passed": False,
|
|
99
|
+
"report_passed": None,
|
|
100
|
+
"missing_report_fields": [],
|
|
101
|
+
"missing_diagnostic_fields": {},
|
|
102
|
+
"stale": True,
|
|
103
|
+
}
|
|
104
|
+
if not path.exists():
|
|
105
|
+
base_report["reason"] = "missing_report_json"
|
|
106
|
+
return base_report
|
|
107
|
+
try:
|
|
108
|
+
report = json.loads(path.read_text(encoding="utf-8"))
|
|
109
|
+
except json.JSONDecodeError as exc:
|
|
110
|
+
base_report["reason"] = f"invalid_json:{exc.msg}"
|
|
111
|
+
return base_report
|
|
112
|
+
if not isinstance(report, Mapping):
|
|
113
|
+
base_report["reason"] = "report_json_is_not_an_object"
|
|
114
|
+
return base_report
|
|
115
|
+
|
|
116
|
+
resolved_kind = _resolve_essos_imported_artifact_kind(report, artifact_kind)
|
|
117
|
+
required_fields = _required_report_fields_for_kind(resolved_kind)
|
|
118
|
+
diagnostic_schema = _diagnostic_schema_for_kind(resolved_kind)
|
|
119
|
+
missing_fields = tuple(field for field in required_fields if field not in report)
|
|
120
|
+
missing_diagnostics = _missing_nested_diagnostic_fields(report, diagnostic_schema)
|
|
121
|
+
schema_passed = not missing_fields and not missing_diagnostics
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
**base_report,
|
|
125
|
+
"artifact_kind": resolved_kind,
|
|
126
|
+
"loaded": True,
|
|
127
|
+
"schema_passed": bool(schema_passed),
|
|
128
|
+
"report_passed": report.get("passed"),
|
|
129
|
+
"missing_report_fields": list(missing_fields),
|
|
130
|
+
"missing_diagnostic_fields": {
|
|
131
|
+
key: list(value) for key, value in missing_diagnostics.items()
|
|
132
|
+
},
|
|
133
|
+
"stale": not bool(schema_passed),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def audit_essos_imported_artifact_reports(
|
|
138
|
+
report_json_paths: Sequence[str | Path],
|
|
139
|
+
*,
|
|
140
|
+
artifact_kind: str = "auto",
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
"""Audit a sequence of imported-field report JSON files."""
|
|
143
|
+
|
|
144
|
+
reports = tuple(
|
|
145
|
+
audit_essos_imported_artifact_report(path, artifact_kind=artifact_kind)
|
|
146
|
+
for path in report_json_paths
|
|
147
|
+
)
|
|
148
|
+
stale_reports = tuple(item for item in reports if item["stale"])
|
|
149
|
+
return {
|
|
150
|
+
"report_count": len(reports),
|
|
151
|
+
"stale_report_count": len(stale_reports),
|
|
152
|
+
"schema_passed": not stale_reports,
|
|
153
|
+
"reports": list(reports),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def audit_hybrid_open_sol_promotion_evidence(
|
|
158
|
+
*,
|
|
159
|
+
fci_report_json_path: str | Path,
|
|
160
|
+
stationarity_report_json_path: str | Path,
|
|
161
|
+
refinement_summary_json_path: str | Path,
|
|
162
|
+
media_manifest_json_path: str | Path,
|
|
163
|
+
) -> dict[str, Any]:
|
|
164
|
+
"""Audit the release-backed hybrid open-SOL promotion evidence bundle.
|
|
165
|
+
|
|
166
|
+
The hybrid QA lane is promoted only when several independently generated
|
|
167
|
+
reports agree: imported FCI/source accounting, long-window stationarity,
|
|
168
|
+
grid/time refinement, and visual-QA/media provenance. This audit is a
|
|
169
|
+
lightweight manifest-level gate; it intentionally does not rerun ESSOS,
|
|
170
|
+
VMEC, DRBX transients, or media rendering.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
paths = {
|
|
174
|
+
"fci_source_profile": Path(fci_report_json_path),
|
|
175
|
+
"stationarity": Path(stationarity_report_json_path),
|
|
176
|
+
"grid_time_refinement": Path(refinement_summary_json_path),
|
|
177
|
+
"media_manifest": Path(media_manifest_json_path),
|
|
178
|
+
}
|
|
179
|
+
loaded = {name: _load_json_mapping(path) for name, path in paths.items()}
|
|
180
|
+
stage_reports = (
|
|
181
|
+
_audit_hybrid_fci_stage(loaded["fci_source_profile"]),
|
|
182
|
+
_audit_hybrid_stationarity_stage(loaded["stationarity"]),
|
|
183
|
+
_audit_hybrid_refinement_stage(loaded["grid_time_refinement"]),
|
|
184
|
+
_audit_hybrid_media_stage(loaded["media_manifest"]),
|
|
185
|
+
)
|
|
186
|
+
promotion_rejection_reasons = sorted(
|
|
187
|
+
{
|
|
188
|
+
reason
|
|
189
|
+
for stage in stage_reports
|
|
190
|
+
if not stage["passed"]
|
|
191
|
+
for reason in stage["reasons"]
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
promotion_ready = not promotion_rejection_reasons
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"diagnostic": "essos_hybrid_open_sol_promotion_evidence_audit",
|
|
198
|
+
"claim_scope": (
|
|
199
|
+
"report-only audit of hybrid VMEC/coil open-SOL FCI, source, "
|
|
200
|
+
"stationarity, grid/time-refinement, media, and visual-QA evidence"
|
|
201
|
+
),
|
|
202
|
+
"map_source": "hybrid",
|
|
203
|
+
"evidence_paths": {name: str(path) for name, path in paths.items()},
|
|
204
|
+
"stage_reports": list(stage_reports),
|
|
205
|
+
"promotion_ready": bool(promotion_ready),
|
|
206
|
+
"promotion_rejection_reasons": promotion_rejection_reasons,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _resolve_essos_imported_artifact_kind(
|
|
211
|
+
report: Mapping[str, Any],
|
|
212
|
+
artifact_kind: str,
|
|
213
|
+
) -> str:
|
|
214
|
+
normalized = str(artifact_kind or "auto").strip().lower().replace("-", "_")
|
|
215
|
+
if normalized in {"fci", "imported_fci"}:
|
|
216
|
+
return "fci"
|
|
217
|
+
if normalized in {"movie", "drb_movie", "imported_drb_movie"}:
|
|
218
|
+
return "movie"
|
|
219
|
+
if normalized != "auto":
|
|
220
|
+
raise ValueError(f"Unsupported imported artifact kind {artifact_kind!r}.")
|
|
221
|
+
if "movie_frame_count" in report or "movie_render_coordinate_model" in report:
|
|
222
|
+
return "movie"
|
|
223
|
+
return "fci"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _required_report_fields_for_kind(kind: str) -> tuple[str, ...]:
|
|
227
|
+
if kind == "fci":
|
|
228
|
+
return tuple(_IMPORTED_FCI_REQUIRED_REPORT_FIELDS)
|
|
229
|
+
if kind == "movie":
|
|
230
|
+
return tuple(_IMPORTED_DRB_MOVIE_REQUIRED_REPORT_FIELDS)
|
|
231
|
+
raise ValueError(f"Unsupported imported artifact kind {kind!r}.")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _diagnostic_schema_for_kind(kind: str) -> Mapping[str, Sequence[str]]:
|
|
235
|
+
if kind == "fci":
|
|
236
|
+
return _IMPORTED_FCI_DIAGNOSTIC_SCHEMA
|
|
237
|
+
if kind == "movie":
|
|
238
|
+
return {}
|
|
239
|
+
raise ValueError(f"Unsupported imported artifact kind {kind!r}.")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _load_json_mapping(path: Path) -> dict[str, Any]:
|
|
243
|
+
result: dict[str, Any] = {
|
|
244
|
+
"path": str(path),
|
|
245
|
+
"exists": path.exists(),
|
|
246
|
+
"loaded": False,
|
|
247
|
+
"data": None,
|
|
248
|
+
"reasons": [],
|
|
249
|
+
}
|
|
250
|
+
if not path.exists():
|
|
251
|
+
result["reasons"] = ["missing_json"]
|
|
252
|
+
return result
|
|
253
|
+
try:
|
|
254
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
255
|
+
except json.JSONDecodeError as exc:
|
|
256
|
+
result["reasons"] = [f"invalid_json:{exc.msg}"]
|
|
257
|
+
return result
|
|
258
|
+
if not isinstance(payload, Mapping):
|
|
259
|
+
result["reasons"] = ["json_is_not_an_object"]
|
|
260
|
+
return result
|
|
261
|
+
result["loaded"] = True
|
|
262
|
+
result["data"] = payload
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _audit_hybrid_fci_stage(loaded: Mapping[str, Any]) -> dict[str, Any]:
|
|
267
|
+
report = _loaded_report_or_none(loaded)
|
|
268
|
+
reasons = _loading_reasons("fci", loaded)
|
|
269
|
+
if report is not None:
|
|
270
|
+
if report.get("map_source") != "hybrid":
|
|
271
|
+
reasons.append("fci_map_source_not_hybrid")
|
|
272
|
+
if report.get("passed") is not True:
|
|
273
|
+
reasons.append("fci_report_not_passed")
|
|
274
|
+
if report.get("connection_length_resolution_passed") is not True:
|
|
275
|
+
reasons.append("fci_connection_length_resolution_not_passed")
|
|
276
|
+
if report.get("map_diagnostics_passed") is not True:
|
|
277
|
+
reasons.append("fci_map_diagnostics_not_passed")
|
|
278
|
+
if not _finite_greater_than(report.get("target_fraction"), 0.0):
|
|
279
|
+
reasons.append("fci_target_fraction_not_positive")
|
|
280
|
+
for key in (
|
|
281
|
+
"particle_recycling_relative_error",
|
|
282
|
+
"neutral_particle_relative_error",
|
|
283
|
+
"current_balance_relative_error",
|
|
284
|
+
"neutral_momentum_relative_error",
|
|
285
|
+
):
|
|
286
|
+
if not _finite_abs_at_most(report.get(key), 1.0e-10):
|
|
287
|
+
reasons.append(f"fci_{key}_above_tolerance")
|
|
288
|
+
return _hybrid_stage_report(
|
|
289
|
+
"hybrid_fci_source_profile",
|
|
290
|
+
loaded,
|
|
291
|
+
reasons,
|
|
292
|
+
summary={
|
|
293
|
+
"target_fraction": None if report is None else report.get("target_fraction"),
|
|
294
|
+
"magnetic_field_modulation": None
|
|
295
|
+
if report is None
|
|
296
|
+
else report.get("magnetic_field_modulation"),
|
|
297
|
+
},
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _audit_hybrid_stationarity_stage(loaded: Mapping[str, Any]) -> dict[str, Any]:
|
|
302
|
+
report = _loaded_report_or_none(loaded)
|
|
303
|
+
reasons = _loading_reasons("stationarity", loaded)
|
|
304
|
+
if report is not None:
|
|
305
|
+
if report.get("map_source") != "hybrid":
|
|
306
|
+
reasons.append("stationarity_map_source_not_hybrid")
|
|
307
|
+
if report.get("publication_ready") is not True:
|
|
308
|
+
reasons.append("stationarity_publication_ready_not_true")
|
|
309
|
+
if report.get("stationarity_passed") is not True:
|
|
310
|
+
reasons.append("stationarity_not_passed")
|
|
311
|
+
if report.get("underlying_movie_passed") is not True:
|
|
312
|
+
reasons.append("stationarity_underlying_movie_not_passed")
|
|
313
|
+
if not _integer_at_least(report.get("frames"), 12):
|
|
314
|
+
reasons.append("stationarity_too_few_frames")
|
|
315
|
+
if report.get("potential_preconditioner") != "jacobi":
|
|
316
|
+
reasons.append("stationarity_potential_preconditioner_not_jacobi")
|
|
317
|
+
if not _finite_abs_below(report.get("potential_tail_max"), 1.0e-8):
|
|
318
|
+
reasons.append("stationarity_potential_tail_too_large")
|
|
319
|
+
if not _finite_greater_than(report.get("min_density_tail"), 0.0):
|
|
320
|
+
reasons.append("stationarity_min_density_tail_not_positive")
|
|
321
|
+
return _hybrid_stage_report(
|
|
322
|
+
"hybrid_stationarity",
|
|
323
|
+
loaded,
|
|
324
|
+
reasons,
|
|
325
|
+
summary={
|
|
326
|
+
"frames": None if report is None else report.get("frames"),
|
|
327
|
+
"potential_tail_max": None
|
|
328
|
+
if report is None
|
|
329
|
+
else report.get("potential_tail_max"),
|
|
330
|
+
},
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _audit_hybrid_refinement_stage(loaded: Mapping[str, Any]) -> dict[str, Any]:
|
|
335
|
+
report = _loaded_report_or_none(loaded)
|
|
336
|
+
reasons = _loading_reasons("refinement", loaded)
|
|
337
|
+
if report is not None:
|
|
338
|
+
if report.get("publication_ready") is not True:
|
|
339
|
+
reasons.append("refinement_publication_ready_not_true")
|
|
340
|
+
if report.get("grid_refinement_passed") is not True:
|
|
341
|
+
reasons.append("grid_refinement_not_passed")
|
|
342
|
+
if report.get("time_refinement_passed") is not True:
|
|
343
|
+
reasons.append("time_refinement_not_passed")
|
|
344
|
+
for key in ("grid_refinement_diagnostics", "time_refinement_diagnostics"):
|
|
345
|
+
diagnostics = report.get(key)
|
|
346
|
+
if not isinstance(diagnostics, Mapping):
|
|
347
|
+
reasons.append(f"{key}_missing")
|
|
348
|
+
continue
|
|
349
|
+
prefix = "grid" if key.startswith("grid") else "time"
|
|
350
|
+
if diagnostics.get("passed") is not True:
|
|
351
|
+
reasons.append(f"{prefix}_refinement_diagnostics_not_passed")
|
|
352
|
+
if diagnostics.get("all_reports_passed") is not True:
|
|
353
|
+
reasons.append(f"{prefix}_refinement_reports_not_all_passed")
|
|
354
|
+
if diagnostics.get("map_source") != "hybrid":
|
|
355
|
+
reasons.append(f"{prefix}_refinement_map_source_not_hybrid")
|
|
356
|
+
if diagnostics.get("map_source_consistent") is not True:
|
|
357
|
+
reasons.append(f"{prefix}_refinement_map_source_not_consistent")
|
|
358
|
+
if diagnostics.get("spectral_resolution_passed") is not True:
|
|
359
|
+
reasons.append(f"{prefix}_refinement_spectral_resolution_not_passed")
|
|
360
|
+
if not _finite_abs_at_most(
|
|
361
|
+
diagnostics.get("max_relative_metric_change"),
|
|
362
|
+
report.get("relative_tolerance", 0.3),
|
|
363
|
+
):
|
|
364
|
+
reasons.append(f"{prefix}_refinement_metric_change_above_tolerance")
|
|
365
|
+
return _hybrid_stage_report(
|
|
366
|
+
"hybrid_grid_time_refinement",
|
|
367
|
+
loaded,
|
|
368
|
+
reasons,
|
|
369
|
+
summary={
|
|
370
|
+
"grid_max_relative_metric_change": _nested_get(
|
|
371
|
+
report,
|
|
372
|
+
"grid_refinement_diagnostics",
|
|
373
|
+
"max_relative_metric_change",
|
|
374
|
+
),
|
|
375
|
+
"time_max_relative_metric_change": _nested_get(
|
|
376
|
+
report,
|
|
377
|
+
"time_refinement_diagnostics",
|
|
378
|
+
"max_relative_metric_change",
|
|
379
|
+
),
|
|
380
|
+
},
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _audit_hybrid_media_stage(loaded: Mapping[str, Any]) -> dict[str, Any]:
|
|
385
|
+
report = _loaded_report_or_none(loaded)
|
|
386
|
+
reasons = _loading_reasons("media", loaded)
|
|
387
|
+
if report is not None:
|
|
388
|
+
qa = report.get("qa")
|
|
389
|
+
files = report.get("files")
|
|
390
|
+
release_assets = report.get("release_assets")
|
|
391
|
+
file_paths = (
|
|
392
|
+
[str(item.get("path", "")) for item in files if isinstance(item, Mapping)]
|
|
393
|
+
if isinstance(files, Sequence) and not isinstance(files, (str, bytes))
|
|
394
|
+
else []
|
|
395
|
+
)
|
|
396
|
+
if report.get("map_source") != "hybrid":
|
|
397
|
+
reasons.append("media_map_source_not_hybrid")
|
|
398
|
+
if not isinstance(qa, Mapping):
|
|
399
|
+
reasons.append("media_qa_missing")
|
|
400
|
+
else:
|
|
401
|
+
if qa.get("visual_qa") != "passed_local_frame_contact_sheet":
|
|
402
|
+
reasons.append("media_visual_qa_not_passed")
|
|
403
|
+
if qa.get("camera_stability") != "passed":
|
|
404
|
+
reasons.append("media_camera_stability_not_passed")
|
|
405
|
+
if qa.get("non_axisymmetric_geometry_visible") is not True:
|
|
406
|
+
reasons.append("media_non_axisymmetric_geometry_not_visible")
|
|
407
|
+
if qa.get("opened_radial_toroidal_sector_visible") is not True:
|
|
408
|
+
reasons.append("media_opened_sector_not_visible")
|
|
409
|
+
if not isinstance(release_assets, Sequence) or isinstance(
|
|
410
|
+
release_assets,
|
|
411
|
+
(str, bytes),
|
|
412
|
+
):
|
|
413
|
+
reasons.append("media_release_assets_missing")
|
|
414
|
+
else:
|
|
415
|
+
urls = [str(url) for url in release_assets]
|
|
416
|
+
if not urls:
|
|
417
|
+
reasons.append("media_release_assets_empty")
|
|
418
|
+
if any(
|
|
419
|
+
not url.startswith(
|
|
420
|
+
"https://github.com/uwplasma/drbx/releases/download/"
|
|
421
|
+
)
|
|
422
|
+
for url in urls
|
|
423
|
+
):
|
|
424
|
+
reasons.append("media_release_asset_url_not_project_release")
|
|
425
|
+
required_file_fragments = {
|
|
426
|
+
"gif": ".gif",
|
|
427
|
+
"diagnostics": "diagnostics",
|
|
428
|
+
"poster": "poster",
|
|
429
|
+
"snapshots": "snapshots",
|
|
430
|
+
"contact_sheet": "contact_sheet",
|
|
431
|
+
}
|
|
432
|
+
for label, fragment in required_file_fragments.items():
|
|
433
|
+
if not any(fragment in path for path in file_paths):
|
|
434
|
+
reasons.append(f"media_{label}_file_missing")
|
|
435
|
+
return _hybrid_stage_report(
|
|
436
|
+
"hybrid_media_manifest",
|
|
437
|
+
loaded,
|
|
438
|
+
reasons,
|
|
439
|
+
summary={
|
|
440
|
+
"file_count": None
|
|
441
|
+
if report is None or not isinstance(report.get("files"), Sequence)
|
|
442
|
+
else len(report.get("files", ())),
|
|
443
|
+
"release_asset_count": None
|
|
444
|
+
if report is None or not isinstance(report.get("release_assets"), Sequence)
|
|
445
|
+
else len(report.get("release_assets", ())),
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _hybrid_stage_report(
|
|
451
|
+
stage: str,
|
|
452
|
+
loaded: Mapping[str, Any],
|
|
453
|
+
reasons: Sequence[str],
|
|
454
|
+
*,
|
|
455
|
+
summary: Mapping[str, Any] | None = None,
|
|
456
|
+
) -> dict[str, Any]:
|
|
457
|
+
return {
|
|
458
|
+
"stage": stage,
|
|
459
|
+
"path": loaded["path"],
|
|
460
|
+
"exists": bool(loaded["exists"]),
|
|
461
|
+
"loaded": bool(loaded["loaded"]),
|
|
462
|
+
"passed": not reasons,
|
|
463
|
+
"reasons": list(reasons),
|
|
464
|
+
"summary": dict(summary or {}),
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _loaded_report_or_none(loaded: Mapping[str, Any]) -> Mapping[str, Any] | None:
|
|
469
|
+
data = loaded.get("data")
|
|
470
|
+
return data if isinstance(data, Mapping) else None
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _loading_reasons(prefix: str, loaded: Mapping[str, Any]) -> list[str]:
|
|
474
|
+
return [f"{prefix}_{reason}" for reason in loaded.get("reasons", ())]
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _finite_abs_at_most(value: Any, limit: float) -> bool:
|
|
478
|
+
return (
|
|
479
|
+
_finite_number(value)
|
|
480
|
+
and _finite_number(limit)
|
|
481
|
+
and abs(float(value)) <= float(limit)
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _finite_abs_below(value: Any, limit: float) -> bool:
|
|
486
|
+
return (
|
|
487
|
+
_finite_number(value)
|
|
488
|
+
and _finite_number(limit)
|
|
489
|
+
and abs(float(value)) < float(limit)
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _finite_greater_than(value: Any, threshold: float) -> bool:
|
|
494
|
+
return (
|
|
495
|
+
_finite_number(value)
|
|
496
|
+
and _finite_number(threshold)
|
|
497
|
+
and float(value) > float(threshold)
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _integer_at_least(value: Any, threshold: int) -> bool:
|
|
502
|
+
return isinstance(value, int) and not isinstance(value, bool) and value >= threshold
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _finite_number(value: Any) -> bool:
|
|
506
|
+
return (
|
|
507
|
+
isinstance(value, (int, float))
|
|
508
|
+
and not isinstance(value, bool)
|
|
509
|
+
and math.isfinite(float(value))
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _nested_get(report: Mapping[str, Any] | None, parent: str, child: str) -> Any:
|
|
514
|
+
if report is None:
|
|
515
|
+
return None
|
|
516
|
+
parent_value = report.get(parent)
|
|
517
|
+
if not isinstance(parent_value, Mapping):
|
|
518
|
+
return None
|
|
519
|
+
return parent_value.get(child)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _missing_nested_diagnostic_fields(
|
|
523
|
+
report: Mapping[str, Any],
|
|
524
|
+
diagnostic_schema: Mapping[str, Sequence[str]],
|
|
525
|
+
) -> dict[str, tuple[str, ...]]:
|
|
526
|
+
missing: dict[str, tuple[str, ...]] = {}
|
|
527
|
+
for parent_key, child_keys in diagnostic_schema.items():
|
|
528
|
+
parent = report.get(parent_key)
|
|
529
|
+
if not isinstance(parent, Mapping):
|
|
530
|
+
missing[parent_key] = tuple(child_keys)
|
|
531
|
+
continue
|
|
532
|
+
child_missing = tuple(child for child in child_keys if child not in parent)
|
|
533
|
+
if child_missing:
|
|
534
|
+
missing[parent_key] = child_missing
|
|
535
|
+
return missing
|