moju 2.3.2__tar.gz → 2.4.1__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.
- {moju-2.3.2 → moju-2.4.1}/PKG-INFO +27 -1
- {moju-2.3.2 → moju-2.4.1}/README.md +26 -0
- {moju-2.3.2 → moju-2.4.1}/moju/__init__.py +1 -1
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/__init__.py +14 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/auditor.py +188 -12
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/evaluate_batch.py +57 -1
- moju-2.4.1/moju/monitor/evaluation_coverage.py +547 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/law_implied_diagnostics.py +15 -3
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/monitor_log_export.py +61 -8
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/report.py +12 -2
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/tiers.py +30 -4
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/visualize_components.py +16 -2
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/visualize_plotly.py +22 -10
- {moju-2.3.2 → moju-2.4.1}/moju.egg-info/PKG-INFO +27 -1
- {moju-2.3.2 → moju-2.4.1}/moju.egg-info/SOURCES.txt +2 -0
- {moju-2.3.2 → moju-2.4.1}/pyproject.toml +1 -1
- moju-2.4.1/tests/test_evaluation_coverage.py +552 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_imports.py +6 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_monitor_log_export.py +45 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_paper_goldens.py +19 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_report_schema.py +3 -1
- {moju-2.3.2 → moju-2.4.1}/LICENSE +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/analytic_flows.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/audit_meta.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/boundary_closures.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/closure_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/config.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/constitutive_closure_summary.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/constitutive_closures.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/curvilinear.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/derivative_error.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/derivative_keys.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/derivative_metadata.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/derivative_provenance.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/derived_state_chain.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/eval_mask.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/fd_stencils.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/implied_scoring.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/key_declarations.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/law_fd_recipes.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/law_group_inference.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/law_scale_recipes.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/model_derived_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/nondim_inference.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/nondim_scales_parse.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/path_b_derivatives.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/path_b_spectral.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/regions.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/resolution_sensor.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/scaling_closures.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/spatial_rnorm_panels.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/state_builder.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/state_ref.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/term_breakdown.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/types.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/visualize_constitutive.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/visualize_export.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/visualize_labels.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/visualize_theme.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/weighted_norms.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/monitor/weighted_safeguard.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/piratio/__init__.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/piratio/groups.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/piratio/laws.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/piratio/models.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/piratio/nondim.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/piratio/operators.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/registry.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/__init__.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/_closure.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/_derived.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/_engine.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/_fd_stencils.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/_implied_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/_nondim.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/_path_b.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch/_r_eff.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju/torch_interop.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju.egg-info/dependency_links.txt +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju.egg-info/requires.txt +0 -0
- {moju-2.3.2 → moju-2.4.1}/moju.egg-info/top_level.txt +0 -0
- {moju-2.3.2 → moju-2.4.1}/setup.cfg +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_analytic_ns_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_audit_meta.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_auditor.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_builtin_law_evaluate_batch.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_closure_debug_sidecar.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_compat_2_0_defaults.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_config_forms.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_constitutive_closure_summary.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_custom_law_hooks.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_derivative_provenance.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_derived_state_chain.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_evaluate.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_evaluate_optin_batched.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_evaluate_regions_batched.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_examples_implied_cookbooks.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_examples_path_b_fd_cookbooks.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_examples_turbulence_cookbooks.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_fd_stencils.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_fornberg_and_dual_spacing.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_groups.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_implied_robust_scoring.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_implied_viscosity_ls.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_jax_torch_fd_parity.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_law_fd_recipes.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_law_group_inference.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_law_implied_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_law_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_law_scale_recipes.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_laws.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_minimal_input_flow.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_model_derived_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_models.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_nondim.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_nondim_inference.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_not_identifiable_tier.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_notebooks_media.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_operators.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_path_b_derivatives.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_path_b_spectral.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_phase_a2_derivative_layer.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_phase_a5_periodic.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_readme_examples.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_readme_pypi_links.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_region_aware_scoring.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_safeguard_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_scoring_and_bounds.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_spatial_rnorm_panels.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_split_admissibility.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_state_ref.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_state_ref_hdf5.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_state_ref_meshio.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_studio_auto_config.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_studio_dependency_planner.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_studio_implied_groups.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_studio_io.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_studio_law_fd_hints.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_studio_model_derived_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_studio_pi_gating.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_studio_plots.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_torch_engine.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_torch_interop.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_visualize_components.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_visualize_constitutive.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_visualize_labels.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_visualize_parity.py +0 -0
- {moju-2.3.2 → moju-2.4.1}/tests/test_visualize_theme.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moju
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Physics admissibility auditing for ML surrogates and simulation data: scores how well predicted fields satisfy governing laws and constitutive relations, with implied-parameter checks and admissibility tiers, for JAX and PyTorch.
|
|
5
5
|
Author-email: "Ifimo Lab (Ifimo Global Solutions LLC)" <lab@ifimoanalytics.com>
|
|
6
6
|
Maintainer-email: "Ifimo Global Solutions LLC (dba Ifimo Lab)" <info@ifimoanalytics.com>
|
|
@@ -80,6 +80,32 @@ Moju is an open-source physics admissibility auditing library for scientific ML
|
|
|
80
80
|
|
|
81
81
|
A tier is computed from an admissibility score in `[0, 1]`: High at `≥ 0.909`, Moderate at `≥ 0.667`, Low at `≥ 0.5`, and Non-Admissible below that (see `moju.monitor.auditor.admissibility_level`). A passing tier indicates consistency with the stated physics within the audited conditions: the laws, constitutive models, sample points, and scales you supplied. It is not a guarantee of correctness.
|
|
82
82
|
|
|
83
|
+
### Evaluation coverage
|
|
84
|
+
|
|
85
|
+
Coverage counts residual elements, including trailing vector/tensor components.
|
|
86
|
+
An evaluation mask excludes all components at each excluded spatial point.
|
|
87
|
+
Audit-scope monitor-log exports preserve the non-finite policy recorded by
|
|
88
|
+
`audit`, including incomplete per-key verdicts and null scores in JSON series.
|
|
89
|
+
|
|
90
|
+
NaN-aware reductions may score on the finite subset of a residual. Every scored key and the aggregate now carry an evaluation-coverage record (`n_total`, `n_masked`, `n_not_identifiable`, `n_selected`, `n_finite`, `n_nonfinite`, `coverage_status`). Default `nonfinite_policy="flag"` keeps scores and tiers unchanged; set `"strict"` to treat incomplete or empty coverage as tier **Incomplete** (not assessable — distinct from **Not identifiable**). Under `"flag"`, a NaN residual is dropped from the score while an infinite residual enters the RMS and drives the score to 0; both are reported as `coverage_status = "incomplete"`. `"strict"` treats NaN and inf identically (null score, Incomplete). Points excluded by implied-audit identifiability (finite conditioning that fails the scoring-path thresholds) are counted as `n_not_identifiable` (not `n_nonfinite`), so `n_selected = n_total − n_masked − n_not_identifiable`. Non-finite inputs or non-finite conditioning at a point count as `n_nonfinite`, never as not identifiable.
|
|
91
|
+
|
|
92
|
+
**Known limitation.** Coverage counts non-finite values in the residual array. A required input that is non-finite while the law's residual stays finite (for example, a law that applies `jnp.where` or `zeros_like` to that input) is currently reported as `coverage_status = "ok"`. Counting non-finite inputs per sample needs a mapping from input samples to residual samples that holds for scalars, parameters and broadcast shapes; this is planned for a follow-up release.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import jax.numpy as jnp
|
|
96
|
+
from moju.monitor import ResidualEngine, LawSpec, Scoring, audit
|
|
97
|
+
|
|
98
|
+
eng = ResidualEngine(
|
|
99
|
+
laws=[LawSpec(name="unit_law", fn=lambda r: r, state_map={"r": "r"},
|
|
100
|
+
scoring=Scoring("rms", dimensionless=True))],
|
|
101
|
+
law_implied_audits=False,
|
|
102
|
+
)
|
|
103
|
+
eng.compute_residuals({"r": jnp.array([0.0, float("nan")])}, run_mode="eval")
|
|
104
|
+
report = audit(eng.log) # default flag: High score, coverage_status="incomplete"
|
|
105
|
+
print(report["coverage_status"])
|
|
106
|
+
print(report["per_key"]["laws/unit_law"]["evaluation_coverage"])
|
|
107
|
+
```
|
|
108
|
+
|
|
83
109
|
## What Moju Does
|
|
84
110
|
|
|
85
111
|
- Builds residuals from composable `Laws`, `Groups`, and `Models`.
|
|
@@ -31,6 +31,32 @@ Moju is an open-source physics admissibility auditing library for scientific ML
|
|
|
31
31
|
|
|
32
32
|
A tier is computed from an admissibility score in `[0, 1]`: High at `≥ 0.909`, Moderate at `≥ 0.667`, Low at `≥ 0.5`, and Non-Admissible below that (see `moju.monitor.auditor.admissibility_level`). A passing tier indicates consistency with the stated physics within the audited conditions: the laws, constitutive models, sample points, and scales you supplied. It is not a guarantee of correctness.
|
|
33
33
|
|
|
34
|
+
### Evaluation coverage
|
|
35
|
+
|
|
36
|
+
Coverage counts residual elements, including trailing vector/tensor components.
|
|
37
|
+
An evaluation mask excludes all components at each excluded spatial point.
|
|
38
|
+
Audit-scope monitor-log exports preserve the non-finite policy recorded by
|
|
39
|
+
`audit`, including incomplete per-key verdicts and null scores in JSON series.
|
|
40
|
+
|
|
41
|
+
NaN-aware reductions may score on the finite subset of a residual. Every scored key and the aggregate now carry an evaluation-coverage record (`n_total`, `n_masked`, `n_not_identifiable`, `n_selected`, `n_finite`, `n_nonfinite`, `coverage_status`). Default `nonfinite_policy="flag"` keeps scores and tiers unchanged; set `"strict"` to treat incomplete or empty coverage as tier **Incomplete** (not assessable — distinct from **Not identifiable**). Under `"flag"`, a NaN residual is dropped from the score while an infinite residual enters the RMS and drives the score to 0; both are reported as `coverage_status = "incomplete"`. `"strict"` treats NaN and inf identically (null score, Incomplete). Points excluded by implied-audit identifiability (finite conditioning that fails the scoring-path thresholds) are counted as `n_not_identifiable` (not `n_nonfinite`), so `n_selected = n_total − n_masked − n_not_identifiable`. Non-finite inputs or non-finite conditioning at a point count as `n_nonfinite`, never as not identifiable.
|
|
42
|
+
|
|
43
|
+
**Known limitation.** Coverage counts non-finite values in the residual array. A required input that is non-finite while the law's residual stays finite (for example, a law that applies `jnp.where` or `zeros_like` to that input) is currently reported as `coverage_status = "ok"`. Counting non-finite inputs per sample needs a mapping from input samples to residual samples that holds for scalars, parameters and broadcast shapes; this is planned for a follow-up release.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import jax.numpy as jnp
|
|
47
|
+
from moju.monitor import ResidualEngine, LawSpec, Scoring, audit
|
|
48
|
+
|
|
49
|
+
eng = ResidualEngine(
|
|
50
|
+
laws=[LawSpec(name="unit_law", fn=lambda r: r, state_map={"r": "r"},
|
|
51
|
+
scoring=Scoring("rms", dimensionless=True))],
|
|
52
|
+
law_implied_audits=False,
|
|
53
|
+
)
|
|
54
|
+
eng.compute_residuals({"r": jnp.array([0.0, float("nan")])}, run_mode="eval")
|
|
55
|
+
report = audit(eng.log) # default flag: High score, coverage_status="incomplete"
|
|
56
|
+
print(report["coverage_status"])
|
|
57
|
+
print(report["per_key"]["laws/unit_law"]["evaluation_coverage"])
|
|
58
|
+
```
|
|
59
|
+
|
|
34
60
|
## What Moju Does
|
|
35
61
|
|
|
36
62
|
- Builds residuals from composable `Laws`, `Groups`, and `Models`.
|
|
@@ -45,6 +45,12 @@ from moju.monitor.implied_scoring import (
|
|
|
45
45
|
resolve_implied_scoring_options,
|
|
46
46
|
score_from_r_max,
|
|
47
47
|
)
|
|
48
|
+
from moju.monitor.evaluation_coverage import (
|
|
49
|
+
DEFAULT_NONFINITE_POLICY,
|
|
50
|
+
TIER_INCOMPLETE,
|
|
51
|
+
coverage_from_residual,
|
|
52
|
+
validate_nonfinite_policy,
|
|
53
|
+
)
|
|
48
54
|
from moju.monitor.regions import (
|
|
49
55
|
DEFAULT_REGION_MIN_POINTS,
|
|
50
56
|
normalize_regions,
|
|
@@ -101,9 +107,11 @@ from moju.monitor.visualize_labels import pretty_category_name, pretty_residual_
|
|
|
101
107
|
from moju.monitor.tiers import (
|
|
102
108
|
TIER_CUTOFFS,
|
|
103
109
|
TIER_DEFINITION,
|
|
110
|
+
TIER_NON_SCORED,
|
|
104
111
|
TIER_ORDER,
|
|
105
112
|
tier_definition,
|
|
106
113
|
tier_for_score,
|
|
114
|
+
tier_rank,
|
|
107
115
|
)
|
|
108
116
|
from moju.monitor.types import (
|
|
109
117
|
REPORT_SCHEMA_VERSION,
|
|
@@ -154,6 +162,10 @@ __all__ = [
|
|
|
154
162
|
"parse_implied_scoring_options",
|
|
155
163
|
"resolve_implied_scoring_options",
|
|
156
164
|
"score_from_r_max",
|
|
165
|
+
"DEFAULT_NONFINITE_POLICY",
|
|
166
|
+
"TIER_INCOMPLETE",
|
|
167
|
+
"coverage_from_residual",
|
|
168
|
+
"validate_nonfinite_policy",
|
|
157
169
|
"DEFAULT_REGION_MIN_POINTS",
|
|
158
170
|
"normalize_regions",
|
|
159
171
|
"percentile_tail_adequacy",
|
|
@@ -176,9 +188,11 @@ __all__ = [
|
|
|
176
188
|
"merge_implied_groups_first",
|
|
177
189
|
"TIER_CUTOFFS",
|
|
178
190
|
"TIER_DEFINITION",
|
|
191
|
+
"TIER_NON_SCORED",
|
|
179
192
|
"TIER_ORDER",
|
|
180
193
|
"tier_definition",
|
|
181
194
|
"tier_for_score",
|
|
195
|
+
"tier_rank",
|
|
182
196
|
"REPORT_SCHEMA_VERSION",
|
|
183
197
|
"AuditReport",
|
|
184
198
|
"BoundCheck",
|
|
@@ -381,7 +381,7 @@ def _r_eff_scalar(x: jnp.ndarray) -> jnp.ndarray:
|
|
|
381
381
|
return rms_r * jnp.power(q, p)
|
|
382
382
|
|
|
383
383
|
|
|
384
|
-
def admissibility_level(score: float) -> str:
|
|
384
|
+
def admissibility_level(score: Optional[float]) -> str:
|
|
385
385
|
"""
|
|
386
386
|
Map admissibility score in ``[0, 1]`` to a qualitative label (``Unknown`` if non-finite).
|
|
387
387
|
|
|
@@ -389,14 +389,25 @@ def admissibility_level(score: float) -> str:
|
|
|
389
389
|
``scale_k`` (:data:`ADM_HIGH_THRESHOLD`, :data:`ADM_MODERATE_THRESHOLD`,
|
|
390
390
|
:data:`ADM_LOW_THRESHOLD`). High requires ``score >= ADM_HIGH_THRESHOLD`` (~90.9% at ±0.1%
|
|
391
391
|
fractional RMS); Moderate and Low use inclusive lower bounds at ~66.7% and 50%.
|
|
392
|
+
|
|
393
|
+
``None`` maps to ``Incomplete`` (not assessable under ``nonfinite_policy=\"strict\"``;
|
|
394
|
+
distinct from ``Not identifiable``).
|
|
392
395
|
"""
|
|
393
|
-
if
|
|
396
|
+
if score is None:
|
|
397
|
+
from moju.monitor.evaluation_coverage import TIER_INCOMPLETE
|
|
398
|
+
|
|
399
|
+
return TIER_INCOMPLETE
|
|
400
|
+
try:
|
|
401
|
+
s = float(score)
|
|
402
|
+
except (TypeError, ValueError):
|
|
394
403
|
return "Unknown"
|
|
395
|
-
if
|
|
404
|
+
if not math.isfinite(s):
|
|
405
|
+
return "Unknown"
|
|
406
|
+
if s >= ADM_HIGH_THRESHOLD:
|
|
396
407
|
return "High Admissibility"
|
|
397
|
-
if
|
|
408
|
+
if s >= ADM_MODERATE_THRESHOLD:
|
|
398
409
|
return "Moderate Admissibility"
|
|
399
|
-
if
|
|
410
|
+
if s >= ADM_LOW_THRESHOLD:
|
|
400
411
|
return "Low Admissibility"
|
|
401
412
|
return "Non-Admissible"
|
|
402
413
|
|
|
@@ -982,6 +993,106 @@ def _apply_implied_scoring_to_entry(
|
|
|
982
993
|
entry["implied_scoring"] = implied_scoring
|
|
983
994
|
|
|
984
995
|
|
|
996
|
+
def _apply_evaluation_coverage_policy(
|
|
997
|
+
report: Dict[str, Any],
|
|
998
|
+
entry: Mapping[str, Any],
|
|
999
|
+
*,
|
|
1000
|
+
policy: str,
|
|
1001
|
+
last_residual_dict: Optional[Dict[str, Any]] = None,
|
|
1002
|
+
eval_mask_label: Optional[str] = None,
|
|
1003
|
+
) -> None:
|
|
1004
|
+
"""Attach coverage records and apply ``nonfinite_policy`` to per-key / overall."""
|
|
1005
|
+
from moju.monitor.evaluation_coverage import (
|
|
1006
|
+
TIER_INCOMPLETE,
|
|
1007
|
+
aggregate_coverage,
|
|
1008
|
+
apply_nonfinite_policy_to_key,
|
|
1009
|
+
coverage_map_from_flat,
|
|
1010
|
+
is_incomplete_key,
|
|
1011
|
+
validate_nonfinite_policy,
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
policy = validate_nonfinite_policy(policy)
|
|
1015
|
+
cov_map: Dict[str, Any] = {}
|
|
1016
|
+
raw = entry.get("evaluation_coverage")
|
|
1017
|
+
if isinstance(raw, dict) and raw:
|
|
1018
|
+
cov_map = {str(k): dict(v) for k, v in raw.items() if isinstance(v, dict)}
|
|
1019
|
+
elif last_residual_dict is not None:
|
|
1020
|
+
flat: Dict[str, Any] = {}
|
|
1021
|
+
for cat, content in last_residual_dict.items():
|
|
1022
|
+
if cat == "closure_debug" or not isinstance(content, dict):
|
|
1023
|
+
continue
|
|
1024
|
+
for name, arr in content.items():
|
|
1025
|
+
flat[f"{cat}/{name}"] = arr
|
|
1026
|
+
cov_map = coverage_map_from_flat(
|
|
1027
|
+
flat,
|
|
1028
|
+
eval_mask_label=eval_mask_label
|
|
1029
|
+
or (entry.get("eval_mask") or {}).get("label"),
|
|
1030
|
+
mask_meta=entry.get("eval_mask") if isinstance(entry.get("eval_mask"), dict) else None,
|
|
1031
|
+
closure_debug=last_residual_dict.get("closure_debug")
|
|
1032
|
+
if isinstance(last_residual_dict.get("closure_debug"), dict)
|
|
1033
|
+
else None,
|
|
1034
|
+
)
|
|
1035
|
+
per_key = report.get("per_key") or {}
|
|
1036
|
+
incomplete_keys: List[str] = []
|
|
1037
|
+
for k, row in per_key.items():
|
|
1038
|
+
if not isinstance(row, dict):
|
|
1039
|
+
continue
|
|
1040
|
+
cov = cov_map.get(k)
|
|
1041
|
+
apply_nonfinite_policy_to_key(row, cov, policy=policy)
|
|
1042
|
+
if is_incomplete_key(row):
|
|
1043
|
+
incomplete_keys.append(str(k))
|
|
1044
|
+
if cov_map:
|
|
1045
|
+
report["evaluation_coverage"] = {
|
|
1046
|
+
"per_key": cov_map,
|
|
1047
|
+
"aggregate": aggregate_coverage(cov_map),
|
|
1048
|
+
}
|
|
1049
|
+
report["coverage_status"] = report["evaluation_coverage"]["aggregate"]["coverage_status"]
|
|
1050
|
+
report["nonfinite_policy"] = policy
|
|
1051
|
+
if policy == "strict" and incomplete_keys:
|
|
1052
|
+
report["overall_admissibility_score"] = None
|
|
1053
|
+
report["overall_admissibility_level"] = TIER_INCOMPLETE
|
|
1054
|
+
report["incomplete_keys"] = incomplete_keys
|
|
1055
|
+
# Drop incomplete keys from category numeric rollup already reflected via null scores;
|
|
1056
|
+
# recompute categories excluding Incomplete / Not identifiable.
|
|
1057
|
+
cats: Dict[str, float] = {}
|
|
1058
|
+
buckets: Dict[str, List[float]] = {
|
|
1059
|
+
"laws": [],
|
|
1060
|
+
"constitutive": [],
|
|
1061
|
+
"scaling": [],
|
|
1062
|
+
"data": [],
|
|
1063
|
+
}
|
|
1064
|
+
for k, row in per_key.items():
|
|
1065
|
+
if not isinstance(row, dict):
|
|
1066
|
+
continue
|
|
1067
|
+
if is_incomplete_key(row):
|
|
1068
|
+
continue
|
|
1069
|
+
if (
|
|
1070
|
+
row.get("admissibility_level") == "Not identifiable"
|
|
1071
|
+
or row.get("identifiability_status") == "not_identifiable"
|
|
1072
|
+
):
|
|
1073
|
+
continue
|
|
1074
|
+
if "/" not in k:
|
|
1075
|
+
continue
|
|
1076
|
+
prefix = k.split("/", 1)[0]
|
|
1077
|
+
if prefix not in buckets:
|
|
1078
|
+
continue
|
|
1079
|
+
try:
|
|
1080
|
+
vf = float(row.get("admissibility_score"))
|
|
1081
|
+
except (TypeError, ValueError):
|
|
1082
|
+
continue
|
|
1083
|
+
if not math.isfinite(vf):
|
|
1084
|
+
continue
|
|
1085
|
+
buckets[prefix].append(vf)
|
|
1086
|
+
for cat, vals in buckets.items():
|
|
1087
|
+
if not vals:
|
|
1088
|
+
continue
|
|
1089
|
+
if cat == "constitutive":
|
|
1090
|
+
cats[cat] = _min_admissibility(vals)
|
|
1091
|
+
else:
|
|
1092
|
+
cats[cat] = _geom_mean_admissibility(vals)
|
|
1093
|
+
report["per_category"] = cats
|
|
1094
|
+
|
|
1095
|
+
|
|
985
1096
|
def audit(
|
|
986
1097
|
log: List[Dict[str, Any]],
|
|
987
1098
|
r_ref: Optional[Dict[str, float]] = None,
|
|
@@ -999,6 +1110,8 @@ def audit(
|
|
|
999
1110
|
regions: Any = None,
|
|
1000
1111
|
region_names: Optional[Mapping[Any, str]] = None,
|
|
1001
1112
|
region_min_points: Optional[int] = None,
|
|
1113
|
+
nonfinite_policy: Optional[str] = None,
|
|
1114
|
+
eval_mask_label: Optional[str] = None,
|
|
1002
1115
|
) -> Dict[str, Any]:
|
|
1003
1116
|
"""
|
|
1004
1117
|
Physics admissibility from logged **R_eff** (field ``rms``) and scales.
|
|
@@ -1226,6 +1339,27 @@ def audit(
|
|
|
1226
1339
|
|
|
1227
1340
|
report["audit_meta"] = audit_meta(log, r_ref=r_ref)
|
|
1228
1341
|
|
|
1342
|
+
_apply_evaluation_coverage_policy(
|
|
1343
|
+
report,
|
|
1344
|
+
log[-1],
|
|
1345
|
+
policy=nonfinite_policy,
|
|
1346
|
+
last_residual_dict=last_residual_dict,
|
|
1347
|
+
eval_mask_label=eval_mask_label,
|
|
1348
|
+
)
|
|
1349
|
+
# Keep log entry aligned with the returned report (strict may null scores).
|
|
1350
|
+
if log:
|
|
1351
|
+
log[-1]["overall_admissibility_score"] = report.get("overall_admissibility_score")
|
|
1352
|
+
log[-1]["overall_admissibility_level"] = report.get("overall_admissibility_level")
|
|
1353
|
+
if report.get("coverage_status") is not None:
|
|
1354
|
+
log[-1]["coverage_status"] = report.get("coverage_status")
|
|
1355
|
+
if report.get("nonfinite_policy") is not None:
|
|
1356
|
+
log[-1]["nonfinite_policy"] = report.get("nonfinite_policy")
|
|
1357
|
+
adm_map = log[-1].get("admissibility_score")
|
|
1358
|
+
if isinstance(adm_map, dict):
|
|
1359
|
+
for k, row in (report.get("per_key") or {}).items():
|
|
1360
|
+
if isinstance(row, dict) and row.get("admissibility_level") == "Incomplete":
|
|
1361
|
+
adm_map[k] = None
|
|
1362
|
+
|
|
1229
1363
|
if export_dir:
|
|
1230
1364
|
import zipfile
|
|
1231
1365
|
from pathlib import Path
|
|
@@ -1424,11 +1558,12 @@ def _worst_keys_table_rows(
|
|
|
1424
1558
|
continue
|
|
1425
1559
|
level = str(rep.get("admissibility_level") or "")
|
|
1426
1560
|
is_ni = level == "Not identifiable" or rep.get("identifiability_status") == "not_identifiable"
|
|
1561
|
+
is_incomplete = level == "Incomplete" or rep.get("coverage_incomplete") is True
|
|
1427
1562
|
try:
|
|
1428
1563
|
rn_f = float(rep.get("r_norm"))
|
|
1429
1564
|
except (TypeError, ValueError):
|
|
1430
1565
|
rn_f = float("nan")
|
|
1431
|
-
if not is_ni and not math.isfinite(rn_f):
|
|
1566
|
+
if not is_ni and not is_incomplete and not math.isfinite(rn_f):
|
|
1432
1567
|
continue
|
|
1433
1568
|
try:
|
|
1434
1569
|
r_eff = float(rep.get("rms"))
|
|
@@ -1445,7 +1580,8 @@ def _worst_keys_table_rows(
|
|
|
1445
1580
|
else:
|
|
1446
1581
|
sk_f = float("nan")
|
|
1447
1582
|
try:
|
|
1448
|
-
|
|
1583
|
+
adm_raw = rep.get("admissibility_score")
|
|
1584
|
+
adm_f = float(adm_raw) if adm_raw is not None else float("nan")
|
|
1449
1585
|
except (TypeError, ValueError):
|
|
1450
1586
|
adm_f = float("nan")
|
|
1451
1587
|
prefix = k.split("/", 1)[0] if "/" in str(k) else "other"
|
|
@@ -1455,9 +1591,9 @@ def _worst_keys_table_rows(
|
|
|
1455
1591
|
"r_eff": r_eff,
|
|
1456
1592
|
"scale_k": sk_f,
|
|
1457
1593
|
"r_norm": rn_f,
|
|
1458
|
-
"admissibility": adm_f,
|
|
1459
|
-
"admissibility_score": adm_f,
|
|
1460
|
-
"admissibility_level": level or admissibility_level(adm_f),
|
|
1594
|
+
"admissibility": adm_f if math.isfinite(adm_f) else None,
|
|
1595
|
+
"admissibility_score": adm_f if math.isfinite(adm_f) else None,
|
|
1596
|
+
"admissibility_level": level or admissibility_level(adm_f if math.isfinite(adm_f) else None),
|
|
1461
1597
|
"category": prefix,
|
|
1462
1598
|
}
|
|
1463
1599
|
if rep.get("implied_metric") is not None:
|
|
@@ -1477,6 +1613,11 @@ def _worst_keys_table_rows(
|
|
|
1477
1613
|
if rep.get("identifiability_reason"):
|
|
1478
1614
|
row["identifiability_reason"] = rep.get("identifiability_reason")
|
|
1479
1615
|
not_ident_rows.append(row)
|
|
1616
|
+
elif is_incomplete:
|
|
1617
|
+
row["admissibility_level"] = "Incomplete"
|
|
1618
|
+
row["admissibility"] = None
|
|
1619
|
+
row["admissibility_score"] = None
|
|
1620
|
+
rows.append(row)
|
|
1480
1621
|
else:
|
|
1481
1622
|
rows.append(row)
|
|
1482
1623
|
rows.sort(key=lambda r: r["r_norm"], reverse=True)
|
|
@@ -1609,7 +1750,14 @@ def _build_visualize_bundle(
|
|
|
1609
1750
|
"bar_display": bar_display,
|
|
1610
1751
|
"bar_values": bar_values_arr,
|
|
1611
1752
|
"bar_values_eff": bar_values_eff_arr,
|
|
1612
|
-
"overall_adm": [
|
|
1753
|
+
"overall_adm": [
|
|
1754
|
+
(
|
|
1755
|
+
float(metrics[i]["overall_admissibility_score"])
|
|
1756
|
+
if metrics[i].get("overall_admissibility_score") is not None
|
|
1757
|
+
else float("nan")
|
|
1758
|
+
)
|
|
1759
|
+
for i in range(n)
|
|
1760
|
+
],
|
|
1613
1761
|
"cats_fin": cats_fin,
|
|
1614
1762
|
"cat_colors": cat_colors,
|
|
1615
1763
|
"spatial": spatial_parsed,
|
|
@@ -2560,6 +2708,7 @@ class ResidualEngine:
|
|
|
2560
2708
|
nondim_scales: Any = None,
|
|
2561
2709
|
derivative_metadata: Optional[Dict[str, Any]] = None,
|
|
2562
2710
|
eval_mask: Any = None,
|
|
2711
|
+
eval_mask_label: Optional[str] = None,
|
|
2563
2712
|
state_pred_alt: Optional[Dict[str, Any]] = None,
|
|
2564
2713
|
estimate_derivative_error: Optional[bool] = None,
|
|
2565
2714
|
derivative_error_threshold: Optional[float] = None,
|
|
@@ -2610,6 +2759,8 @@ class ResidualEngine:
|
|
|
2610
2759
|
- ``derivative_metadata``: optional per-key metadata for consistency warnings.
|
|
2611
2760
|
- ``eval_mask``: boolean array (True = keep). When set, RMS/max for this call use
|
|
2612
2761
|
masked points only.
|
|
2762
|
+
- ``eval_mask_label``: optional string recorded on evaluation-coverage records for
|
|
2763
|
+
this mask (provenance only; does not change scores).
|
|
2613
2764
|
- ``state_pred_alt`` / ``estimate_derivative_error``: opt-in derivative-error estimates.
|
|
2614
2765
|
- ``point_weights`` / ``weighted_norms`` (Moju 2.0 default ``True``): area-weighted
|
|
2615
2766
|
R_eff for scoring, with a percentile safeguard (see ``weighted_safeguard``). Use
|
|
@@ -2641,6 +2792,7 @@ class ResidualEngine:
|
|
|
2641
2792
|
nondim_scales=nondim_scales,
|
|
2642
2793
|
derivative_metadata=derivative_metadata,
|
|
2643
2794
|
eval_mask=eval_mask,
|
|
2795
|
+
eval_mask_label=eval_mask_label,
|
|
2644
2796
|
state_pred_alt=state_pred_alt,
|
|
2645
2797
|
estimate_derivative_error=estimate_derivative_error,
|
|
2646
2798
|
derivative_error_threshold=derivative_error_threshold,
|
|
@@ -2681,6 +2833,7 @@ class ResidualEngine:
|
|
|
2681
2833
|
nondim_scales: Any = None,
|
|
2682
2834
|
derivative_metadata: Optional[Dict[str, Any]] = None,
|
|
2683
2835
|
eval_mask: Any = None,
|
|
2836
|
+
eval_mask_label: Optional[str] = None,
|
|
2684
2837
|
state_pred_alt: Optional[Dict[str, Any]] = None,
|
|
2685
2838
|
estimate_derivative_error: Optional[bool] = None,
|
|
2686
2839
|
derivative_error_threshold: Optional[float] = None,
|
|
@@ -3224,12 +3377,23 @@ class ResidualEngine:
|
|
|
3224
3377
|
}
|
|
3225
3378
|
|
|
3226
3379
|
flat = _flatten_residual_dict(residuals)
|
|
3380
|
+
unmasked_flat = flat
|
|
3227
3381
|
mask_meta: Dict[str, Any] = {}
|
|
3228
3382
|
if eval_mask is not None:
|
|
3229
3383
|
from moju.monitor.eval_mask import mask_flat_residuals, mask_provenance
|
|
3230
3384
|
|
|
3231
3385
|
flat = mask_flat_residuals(flat, eval_mask)
|
|
3232
3386
|
mask_meta = mask_provenance(eval_mask)
|
|
3387
|
+
from moju.monitor.evaluation_coverage import coverage_map_from_flat
|
|
3388
|
+
|
|
3389
|
+
evaluation_coverage = coverage_map_from_flat(
|
|
3390
|
+
flat,
|
|
3391
|
+
eval_mask=eval_mask,
|
|
3392
|
+
eval_mask_label=eval_mask_label,
|
|
3393
|
+
mask_meta=mask_meta or None,
|
|
3394
|
+
unmasked_flat=unmasked_flat,
|
|
3395
|
+
closure_debug=closure_debug or None,
|
|
3396
|
+
)
|
|
3233
3397
|
rms_per_key = _rms_per_key(flat, to_python=log_to_python)
|
|
3234
3398
|
r_max_per_key = _max_per_key(flat, to_python=log_to_python, scoring=self._scoring_by_key)
|
|
3235
3399
|
|
|
@@ -3649,6 +3813,8 @@ class ResidualEngine:
|
|
|
3649
3813
|
entry["derivative_error"] = derivative_error_report
|
|
3650
3814
|
if mask_meta:
|
|
3651
3815
|
entry["eval_mask"] = mask_meta
|
|
3816
|
+
if evaluation_coverage:
|
|
3817
|
+
entry["evaluation_coverage"] = evaluation_coverage
|
|
3652
3818
|
if rms_weighted is not None:
|
|
3653
3819
|
entry["rms_weighted"] = rms_weighted
|
|
3654
3820
|
entry["weighted_norms"] = {
|
|
@@ -3857,6 +4023,8 @@ def evaluate(
|
|
|
3857
4023
|
region_names: Optional[Mapping[Any, str]] = None,
|
|
3858
4024
|
region_min_points: Optional[int] = None,
|
|
3859
4025
|
evaluate_debug_loop: bool = False,
|
|
4026
|
+
nonfinite_policy: Optional[str] = None,
|
|
4027
|
+
eval_mask_label: Optional[str] = None,
|
|
3860
4028
|
**compute_kwargs: Any,
|
|
3861
4029
|
) -> List[Dict[str, Any]]:
|
|
3862
4030
|
"""
|
|
@@ -3953,6 +4121,8 @@ def evaluate(
|
|
|
3953
4121
|
implied_percentile=implied_percentile,
|
|
3954
4122
|
implied_noise_normalization=implied_noise_normalization,
|
|
3955
4123
|
require_batch=require_batch,
|
|
4124
|
+
nonfinite_policy=nonfinite_policy,
|
|
4125
|
+
eval_mask_label=eval_mask_label,
|
|
3956
4126
|
)
|
|
3957
4127
|
if batched is not None:
|
|
3958
4128
|
for report in batched:
|
|
@@ -3973,7 +4143,13 @@ def evaluate(
|
|
|
3973
4143
|
**compute_kwargs,
|
|
3974
4144
|
)
|
|
3975
4145
|
entry["index"] = i
|
|
3976
|
-
report = audit(
|
|
4146
|
+
report = audit(
|
|
4147
|
+
[entry],
|
|
4148
|
+
r_ref=r_ref,
|
|
4149
|
+
last_residual_dict=residuals if return_residuals else residuals,
|
|
4150
|
+
nonfinite_policy=nonfinite_policy,
|
|
4151
|
+
eval_mask_label=eval_mask_label,
|
|
4152
|
+
)
|
|
3977
4153
|
if return_residuals:
|
|
3978
4154
|
report["residuals"] = residuals
|
|
3979
4155
|
report["execution"] = "loop"
|
|
@@ -43,6 +43,8 @@ def evaluate_batched(
|
|
|
43
43
|
implied_percentile: Optional[float] = None,
|
|
44
44
|
implied_noise_normalization: Optional[bool] = None,
|
|
45
45
|
require_batch: bool = False,
|
|
46
|
+
nonfinite_policy: Optional[str] = None,
|
|
47
|
+
eval_mask_label: Optional[str] = None,
|
|
46
48
|
) -> Optional[List[Dict[str, Any]]]:
|
|
47
49
|
"""
|
|
48
50
|
Score every candidate in one compiled call.
|
|
@@ -294,8 +296,24 @@ def evaluate_batched(
|
|
|
294
296
|
compact[sk] = jnp.asarray(arr)[i]
|
|
295
297
|
id_map[str(bn)] = materialize_identifiability_meta(compact)
|
|
296
298
|
entry["implied_identifiability"] = id_map
|
|
299
|
+
if "coverage_nums" in out:
|
|
300
|
+
from moju.monitor.evaluation_coverage import materialize_coverage_counts
|
|
301
|
+
|
|
302
|
+
cov_map: Dict[str, Any] = {}
|
|
303
|
+
for k, nums in out["coverage_nums"].items():
|
|
304
|
+
host_nums = {sk: jnp.asarray(arr)[i] for sk, arr in nums.items()}
|
|
305
|
+
cov_map[str(k)] = materialize_coverage_counts(
|
|
306
|
+
host_nums, eval_mask_label=eval_mask_label
|
|
307
|
+
)
|
|
308
|
+
entry["evaluation_coverage"] = cov_map
|
|
297
309
|
entry["monitor_settings"] = ms
|
|
298
|
-
report = audit(
|
|
310
|
+
report = audit(
|
|
311
|
+
[entry],
|
|
312
|
+
r_ref=r_ref,
|
|
313
|
+
last_residual_dict=residuals,
|
|
314
|
+
nonfinite_policy=nonfinite_policy,
|
|
315
|
+
eval_mask_label=eval_mask_label,
|
|
316
|
+
)
|
|
299
317
|
if return_residuals:
|
|
300
318
|
report["residuals"] = residuals
|
|
301
319
|
reports.append(report)
|
|
@@ -452,6 +470,10 @@ def _score_one(
|
|
|
452
470
|
implied_debug: Dict[str, Dict[str, Any]] = {}
|
|
453
471
|
# Tracer-safe identifiability scalars (status strings materialised on the host).
|
|
454
472
|
implied_id: Dict[str, Dict[str, Any]] = {}
|
|
473
|
+
# Pointwise NI masks / row-NI flags for coverage (keyed by residual basename).
|
|
474
|
+
batch_id_masks: Dict[str, Any] = {}
|
|
475
|
+
batch_ni_masks: Dict[str, Any] = {}
|
|
476
|
+
batch_id_row_ni: Dict[str, Any] = {}
|
|
455
477
|
for audit_spec in plan.audits:
|
|
456
478
|
spec = audit_spec["spec"]
|
|
457
479
|
reg = audit_spec["reg"]
|
|
@@ -513,6 +535,19 @@ def _score_one(
|
|
|
513
535
|
pack[sk] = jnp.asarray(dbg[sk])
|
|
514
536
|
if pack:
|
|
515
537
|
implied_id[str(base)] = pack
|
|
538
|
+
if dbg.get("identifiable_mask") is not None:
|
|
539
|
+
batch_id_masks[str(base)] = jnp.asarray(dbg["identifiable_mask"])
|
|
540
|
+
if dbg.get("not_identifiable_mask") is not None:
|
|
541
|
+
batch_ni_masks[str(base)] = jnp.asarray(
|
|
542
|
+
dbg["not_identifiable_mask"]
|
|
543
|
+
)
|
|
544
|
+
# Row-level wipe (tracer-safe 0-d bool): entire δ is NaN for scoring.
|
|
545
|
+
if "identifiable" in dbg:
|
|
546
|
+
batch_id_row_ni[str(base)] = jnp.logical_not(
|
|
547
|
+
jnp.asarray(dbg["identifiable"])
|
|
548
|
+
)
|
|
549
|
+
elif dbg.get("status") == "not_identifiable":
|
|
550
|
+
batch_id_row_ni[str(base)] = jnp.asarray(True)
|
|
516
551
|
if (
|
|
517
552
|
dbg.get("noise_rhs") is not None
|
|
518
553
|
and dbg.get("noise_basis") is not None
|
|
@@ -558,6 +593,25 @@ def _score_one(
|
|
|
558
593
|
flat = _flat(residuals)
|
|
559
594
|
rms = {k: r_eff(v) for k, v in flat.items()}
|
|
560
595
|
rmax = {k: r_max(v) for k, v in flat.items()}
|
|
596
|
+
from moju.monitor.evaluation_coverage import coverage_counts_arrays
|
|
597
|
+
|
|
598
|
+
coverage_nums: Dict[str, Dict[str, Any]] = {}
|
|
599
|
+
for k, v in flat.items():
|
|
600
|
+
id_mask = None
|
|
601
|
+
ni_mask = None
|
|
602
|
+
row_ni: Any = False
|
|
603
|
+
if str(k).startswith("constitutive/") and str(k).endswith("/implied_delta"):
|
|
604
|
+
bn = str(k)[len("constitutive/") : -len("/implied_delta")]
|
|
605
|
+
id_mask = batch_id_masks.get(bn)
|
|
606
|
+
ni_mask = batch_ni_masks.get(bn)
|
|
607
|
+
if bn in batch_id_row_ni:
|
|
608
|
+
row_ni = batch_id_row_ni[bn]
|
|
609
|
+
coverage_nums[k] = coverage_counts_arrays(
|
|
610
|
+
v,
|
|
611
|
+
identifiable_mask=id_mask,
|
|
612
|
+
not_identifiable_mask=ni_mask,
|
|
613
|
+
row_not_identifiable=row_ni,
|
|
614
|
+
)
|
|
561
615
|
dynamic_scale: Dict[str, Any] = {}
|
|
562
616
|
recipe_ok: Dict[str, Any] = {}
|
|
563
617
|
for key, recipe, spec, _is_user in plan.auto_scale:
|
|
@@ -582,6 +636,8 @@ def _score_one(
|
|
|
582
636
|
"scale": dynamic_scale,
|
|
583
637
|
"recipe_ok": recipe_ok,
|
|
584
638
|
}
|
|
639
|
+
if coverage_nums:
|
|
640
|
+
out["coverage_nums"] = coverage_nums
|
|
585
641
|
if implied_id:
|
|
586
642
|
out["implied_id"] = implied_id
|
|
587
643
|
|