moju 2.3.2__tar.gz → 2.4.0__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.0}/PKG-INFO +27 -1
- {moju-2.3.2 → moju-2.4.0}/README.md +26 -0
- {moju-2.3.2 → moju-2.4.0}/moju/__init__.py +1 -1
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/__init__.py +14 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/auditor.py +184 -12
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/evaluate_batch.py +26 -1
- moju-2.4.0/moju/monitor/evaluation_coverage.py +338 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/monitor_log_export.py +61 -8
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/report.py +12 -2
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/tiers.py +30 -4
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/visualize_components.py +16 -2
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/visualize_plotly.py +22 -10
- {moju-2.3.2 → moju-2.4.0}/moju.egg-info/PKG-INFO +27 -1
- {moju-2.3.2 → moju-2.4.0}/moju.egg-info/SOURCES.txt +2 -0
- {moju-2.3.2 → moju-2.4.0}/pyproject.toml +1 -1
- moju-2.4.0/tests/test_evaluation_coverage.py +353 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_imports.py +6 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_monitor_log_export.py +45 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_report_schema.py +3 -1
- {moju-2.3.2 → moju-2.4.0}/LICENSE +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/analytic_flows.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/audit_meta.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/boundary_closures.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/closure_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/config.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/constitutive_closure_summary.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/constitutive_closures.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/curvilinear.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/derivative_error.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/derivative_keys.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/derivative_metadata.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/derivative_provenance.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/derived_state_chain.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/eval_mask.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/fd_stencils.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/implied_scoring.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/key_declarations.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/law_fd_recipes.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/law_group_inference.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/law_implied_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/law_scale_recipes.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/model_derived_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/nondim_inference.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/nondim_scales_parse.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/path_b_derivatives.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/path_b_spectral.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/regions.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/resolution_sensor.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/scaling_closures.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/spatial_rnorm_panels.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/state_builder.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/state_ref.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/term_breakdown.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/types.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/visualize_constitutive.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/visualize_export.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/visualize_labels.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/visualize_theme.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/weighted_norms.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/monitor/weighted_safeguard.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/piratio/__init__.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/piratio/groups.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/piratio/laws.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/piratio/models.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/piratio/nondim.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/piratio/operators.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/registry.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/__init__.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/_closure.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/_derived.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/_engine.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/_fd_stencils.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/_implied_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/_nondim.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/_path_b.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch/_r_eff.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju/torch_interop.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju.egg-info/dependency_links.txt +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju.egg-info/requires.txt +0 -0
- {moju-2.3.2 → moju-2.4.0}/moju.egg-info/top_level.txt +0 -0
- {moju-2.3.2 → moju-2.4.0}/setup.cfg +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_analytic_ns_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_audit_meta.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_auditor.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_builtin_law_evaluate_batch.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_closure_debug_sidecar.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_compat_2_0_defaults.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_config_forms.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_constitutive_closure_summary.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_custom_law_hooks.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_derivative_provenance.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_derived_state_chain.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_evaluate.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_evaluate_optin_batched.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_evaluate_regions_batched.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_examples_implied_cookbooks.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_examples_path_b_fd_cookbooks.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_examples_turbulence_cookbooks.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_fd_stencils.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_fornberg_and_dual_spacing.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_groups.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_implied_robust_scoring.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_implied_viscosity_ls.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_jax_torch_fd_parity.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_law_fd_recipes.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_law_group_inference.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_law_implied_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_law_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_law_scale_recipes.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_laws.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_minimal_input_flow.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_model_derived_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_models.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_nondim.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_nondim_inference.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_not_identifiable_tier.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_notebooks_media.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_operators.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_paper_goldens.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_path_b_derivatives.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_path_b_spectral.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_phase_a2_derivative_layer.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_phase_a5_periodic.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_readme_examples.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_readme_pypi_links.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_region_aware_scoring.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_safeguard_diagnostics.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_scoring_and_bounds.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_spatial_rnorm_panels.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_split_admissibility.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_state_ref.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_state_ref_hdf5.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_state_ref_meshio.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_studio_auto_config.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_studio_dependency_planner.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_studio_implied_groups.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_studio_io.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_studio_law_fd_hints.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_studio_model_derived_registry.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_studio_pi_gating.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_studio_plots.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_torch_engine.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_torch_interop.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_visualize_components.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_visualize_constitutive.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_visualize_labels.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/tests/test_visualize_parity.py +0 -0
- {moju-2.3.2 → moju-2.4.0}/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.0
|
|
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_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).
|
|
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_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).
|
|
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,103 @@ 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
|
+
)
|
|
1032
|
+
per_key = report.get("per_key") or {}
|
|
1033
|
+
incomplete_keys: List[str] = []
|
|
1034
|
+
for k, row in per_key.items():
|
|
1035
|
+
if not isinstance(row, dict):
|
|
1036
|
+
continue
|
|
1037
|
+
cov = cov_map.get(k)
|
|
1038
|
+
apply_nonfinite_policy_to_key(row, cov, policy=policy)
|
|
1039
|
+
if is_incomplete_key(row):
|
|
1040
|
+
incomplete_keys.append(str(k))
|
|
1041
|
+
if cov_map:
|
|
1042
|
+
report["evaluation_coverage"] = {
|
|
1043
|
+
"per_key": cov_map,
|
|
1044
|
+
"aggregate": aggregate_coverage(cov_map),
|
|
1045
|
+
}
|
|
1046
|
+
report["coverage_status"] = report["evaluation_coverage"]["aggregate"]["coverage_status"]
|
|
1047
|
+
report["nonfinite_policy"] = policy
|
|
1048
|
+
if policy == "strict" and incomplete_keys:
|
|
1049
|
+
report["overall_admissibility_score"] = None
|
|
1050
|
+
report["overall_admissibility_level"] = TIER_INCOMPLETE
|
|
1051
|
+
report["incomplete_keys"] = incomplete_keys
|
|
1052
|
+
# Drop incomplete keys from category numeric rollup already reflected via null scores;
|
|
1053
|
+
# recompute categories excluding Incomplete / Not identifiable.
|
|
1054
|
+
cats: Dict[str, float] = {}
|
|
1055
|
+
buckets: Dict[str, List[float]] = {
|
|
1056
|
+
"laws": [],
|
|
1057
|
+
"constitutive": [],
|
|
1058
|
+
"scaling": [],
|
|
1059
|
+
"data": [],
|
|
1060
|
+
}
|
|
1061
|
+
for k, row in per_key.items():
|
|
1062
|
+
if not isinstance(row, dict):
|
|
1063
|
+
continue
|
|
1064
|
+
if is_incomplete_key(row):
|
|
1065
|
+
continue
|
|
1066
|
+
if (
|
|
1067
|
+
row.get("admissibility_level") == "Not identifiable"
|
|
1068
|
+
or row.get("identifiability_status") == "not_identifiable"
|
|
1069
|
+
):
|
|
1070
|
+
continue
|
|
1071
|
+
if "/" not in k:
|
|
1072
|
+
continue
|
|
1073
|
+
prefix = k.split("/", 1)[0]
|
|
1074
|
+
if prefix not in buckets:
|
|
1075
|
+
continue
|
|
1076
|
+
try:
|
|
1077
|
+
vf = float(row.get("admissibility_score"))
|
|
1078
|
+
except (TypeError, ValueError):
|
|
1079
|
+
continue
|
|
1080
|
+
if not math.isfinite(vf):
|
|
1081
|
+
continue
|
|
1082
|
+
buckets[prefix].append(vf)
|
|
1083
|
+
for cat, vals in buckets.items():
|
|
1084
|
+
if not vals:
|
|
1085
|
+
continue
|
|
1086
|
+
if cat == "constitutive":
|
|
1087
|
+
cats[cat] = _min_admissibility(vals)
|
|
1088
|
+
else:
|
|
1089
|
+
cats[cat] = _geom_mean_admissibility(vals)
|
|
1090
|
+
report["per_category"] = cats
|
|
1091
|
+
|
|
1092
|
+
|
|
985
1093
|
def audit(
|
|
986
1094
|
log: List[Dict[str, Any]],
|
|
987
1095
|
r_ref: Optional[Dict[str, float]] = None,
|
|
@@ -999,6 +1107,8 @@ def audit(
|
|
|
999
1107
|
regions: Any = None,
|
|
1000
1108
|
region_names: Optional[Mapping[Any, str]] = None,
|
|
1001
1109
|
region_min_points: Optional[int] = None,
|
|
1110
|
+
nonfinite_policy: Optional[str] = None,
|
|
1111
|
+
eval_mask_label: Optional[str] = None,
|
|
1002
1112
|
) -> Dict[str, Any]:
|
|
1003
1113
|
"""
|
|
1004
1114
|
Physics admissibility from logged **R_eff** (field ``rms``) and scales.
|
|
@@ -1226,6 +1336,27 @@ def audit(
|
|
|
1226
1336
|
|
|
1227
1337
|
report["audit_meta"] = audit_meta(log, r_ref=r_ref)
|
|
1228
1338
|
|
|
1339
|
+
_apply_evaluation_coverage_policy(
|
|
1340
|
+
report,
|
|
1341
|
+
log[-1],
|
|
1342
|
+
policy=nonfinite_policy,
|
|
1343
|
+
last_residual_dict=last_residual_dict,
|
|
1344
|
+
eval_mask_label=eval_mask_label,
|
|
1345
|
+
)
|
|
1346
|
+
# Keep log entry aligned with the returned report (strict may null scores).
|
|
1347
|
+
if log:
|
|
1348
|
+
log[-1]["overall_admissibility_score"] = report.get("overall_admissibility_score")
|
|
1349
|
+
log[-1]["overall_admissibility_level"] = report.get("overall_admissibility_level")
|
|
1350
|
+
if report.get("coverage_status") is not None:
|
|
1351
|
+
log[-1]["coverage_status"] = report.get("coverage_status")
|
|
1352
|
+
if report.get("nonfinite_policy") is not None:
|
|
1353
|
+
log[-1]["nonfinite_policy"] = report.get("nonfinite_policy")
|
|
1354
|
+
adm_map = log[-1].get("admissibility_score")
|
|
1355
|
+
if isinstance(adm_map, dict):
|
|
1356
|
+
for k, row in (report.get("per_key") or {}).items():
|
|
1357
|
+
if isinstance(row, dict) and row.get("admissibility_level") == "Incomplete":
|
|
1358
|
+
adm_map[k] = None
|
|
1359
|
+
|
|
1229
1360
|
if export_dir:
|
|
1230
1361
|
import zipfile
|
|
1231
1362
|
from pathlib import Path
|
|
@@ -1424,11 +1555,12 @@ def _worst_keys_table_rows(
|
|
|
1424
1555
|
continue
|
|
1425
1556
|
level = str(rep.get("admissibility_level") or "")
|
|
1426
1557
|
is_ni = level == "Not identifiable" or rep.get("identifiability_status") == "not_identifiable"
|
|
1558
|
+
is_incomplete = level == "Incomplete" or rep.get("coverage_incomplete") is True
|
|
1427
1559
|
try:
|
|
1428
1560
|
rn_f = float(rep.get("r_norm"))
|
|
1429
1561
|
except (TypeError, ValueError):
|
|
1430
1562
|
rn_f = float("nan")
|
|
1431
|
-
if not is_ni and not math.isfinite(rn_f):
|
|
1563
|
+
if not is_ni and not is_incomplete and not math.isfinite(rn_f):
|
|
1432
1564
|
continue
|
|
1433
1565
|
try:
|
|
1434
1566
|
r_eff = float(rep.get("rms"))
|
|
@@ -1445,7 +1577,8 @@ def _worst_keys_table_rows(
|
|
|
1445
1577
|
else:
|
|
1446
1578
|
sk_f = float("nan")
|
|
1447
1579
|
try:
|
|
1448
|
-
|
|
1580
|
+
adm_raw = rep.get("admissibility_score")
|
|
1581
|
+
adm_f = float(adm_raw) if adm_raw is not None else float("nan")
|
|
1449
1582
|
except (TypeError, ValueError):
|
|
1450
1583
|
adm_f = float("nan")
|
|
1451
1584
|
prefix = k.split("/", 1)[0] if "/" in str(k) else "other"
|
|
@@ -1455,9 +1588,9 @@ def _worst_keys_table_rows(
|
|
|
1455
1588
|
"r_eff": r_eff,
|
|
1456
1589
|
"scale_k": sk_f,
|
|
1457
1590
|
"r_norm": rn_f,
|
|
1458
|
-
"admissibility": adm_f,
|
|
1459
|
-
"admissibility_score": adm_f,
|
|
1460
|
-
"admissibility_level": level or admissibility_level(adm_f),
|
|
1591
|
+
"admissibility": adm_f if math.isfinite(adm_f) else None,
|
|
1592
|
+
"admissibility_score": adm_f if math.isfinite(adm_f) else None,
|
|
1593
|
+
"admissibility_level": level or admissibility_level(adm_f if math.isfinite(adm_f) else None),
|
|
1461
1594
|
"category": prefix,
|
|
1462
1595
|
}
|
|
1463
1596
|
if rep.get("implied_metric") is not None:
|
|
@@ -1477,6 +1610,11 @@ def _worst_keys_table_rows(
|
|
|
1477
1610
|
if rep.get("identifiability_reason"):
|
|
1478
1611
|
row["identifiability_reason"] = rep.get("identifiability_reason")
|
|
1479
1612
|
not_ident_rows.append(row)
|
|
1613
|
+
elif is_incomplete:
|
|
1614
|
+
row["admissibility_level"] = "Incomplete"
|
|
1615
|
+
row["admissibility"] = None
|
|
1616
|
+
row["admissibility_score"] = None
|
|
1617
|
+
rows.append(row)
|
|
1480
1618
|
else:
|
|
1481
1619
|
rows.append(row)
|
|
1482
1620
|
rows.sort(key=lambda r: r["r_norm"], reverse=True)
|
|
@@ -1609,7 +1747,14 @@ def _build_visualize_bundle(
|
|
|
1609
1747
|
"bar_display": bar_display,
|
|
1610
1748
|
"bar_values": bar_values_arr,
|
|
1611
1749
|
"bar_values_eff": bar_values_eff_arr,
|
|
1612
|
-
"overall_adm": [
|
|
1750
|
+
"overall_adm": [
|
|
1751
|
+
(
|
|
1752
|
+
float(metrics[i]["overall_admissibility_score"])
|
|
1753
|
+
if metrics[i].get("overall_admissibility_score") is not None
|
|
1754
|
+
else float("nan")
|
|
1755
|
+
)
|
|
1756
|
+
for i in range(n)
|
|
1757
|
+
],
|
|
1613
1758
|
"cats_fin": cats_fin,
|
|
1614
1759
|
"cat_colors": cat_colors,
|
|
1615
1760
|
"spatial": spatial_parsed,
|
|
@@ -2560,6 +2705,7 @@ class ResidualEngine:
|
|
|
2560
2705
|
nondim_scales: Any = None,
|
|
2561
2706
|
derivative_metadata: Optional[Dict[str, Any]] = None,
|
|
2562
2707
|
eval_mask: Any = None,
|
|
2708
|
+
eval_mask_label: Optional[str] = None,
|
|
2563
2709
|
state_pred_alt: Optional[Dict[str, Any]] = None,
|
|
2564
2710
|
estimate_derivative_error: Optional[bool] = None,
|
|
2565
2711
|
derivative_error_threshold: Optional[float] = None,
|
|
@@ -2610,6 +2756,8 @@ class ResidualEngine:
|
|
|
2610
2756
|
- ``derivative_metadata``: optional per-key metadata for consistency warnings.
|
|
2611
2757
|
- ``eval_mask``: boolean array (True = keep). When set, RMS/max for this call use
|
|
2612
2758
|
masked points only.
|
|
2759
|
+
- ``eval_mask_label``: optional string recorded on evaluation-coverage records for
|
|
2760
|
+
this mask (provenance only; does not change scores).
|
|
2613
2761
|
- ``state_pred_alt`` / ``estimate_derivative_error``: opt-in derivative-error estimates.
|
|
2614
2762
|
- ``point_weights`` / ``weighted_norms`` (Moju 2.0 default ``True``): area-weighted
|
|
2615
2763
|
R_eff for scoring, with a percentile safeguard (see ``weighted_safeguard``). Use
|
|
@@ -2641,6 +2789,7 @@ class ResidualEngine:
|
|
|
2641
2789
|
nondim_scales=nondim_scales,
|
|
2642
2790
|
derivative_metadata=derivative_metadata,
|
|
2643
2791
|
eval_mask=eval_mask,
|
|
2792
|
+
eval_mask_label=eval_mask_label,
|
|
2644
2793
|
state_pred_alt=state_pred_alt,
|
|
2645
2794
|
estimate_derivative_error=estimate_derivative_error,
|
|
2646
2795
|
derivative_error_threshold=derivative_error_threshold,
|
|
@@ -2681,6 +2830,7 @@ class ResidualEngine:
|
|
|
2681
2830
|
nondim_scales: Any = None,
|
|
2682
2831
|
derivative_metadata: Optional[Dict[str, Any]] = None,
|
|
2683
2832
|
eval_mask: Any = None,
|
|
2833
|
+
eval_mask_label: Optional[str] = None,
|
|
2684
2834
|
state_pred_alt: Optional[Dict[str, Any]] = None,
|
|
2685
2835
|
estimate_derivative_error: Optional[bool] = None,
|
|
2686
2836
|
derivative_error_threshold: Optional[float] = None,
|
|
@@ -3224,12 +3374,22 @@ class ResidualEngine:
|
|
|
3224
3374
|
}
|
|
3225
3375
|
|
|
3226
3376
|
flat = _flatten_residual_dict(residuals)
|
|
3377
|
+
unmasked_flat = flat
|
|
3227
3378
|
mask_meta: Dict[str, Any] = {}
|
|
3228
3379
|
if eval_mask is not None:
|
|
3229
3380
|
from moju.monitor.eval_mask import mask_flat_residuals, mask_provenance
|
|
3230
3381
|
|
|
3231
3382
|
flat = mask_flat_residuals(flat, eval_mask)
|
|
3232
3383
|
mask_meta = mask_provenance(eval_mask)
|
|
3384
|
+
from moju.monitor.evaluation_coverage import coverage_map_from_flat
|
|
3385
|
+
|
|
3386
|
+
evaluation_coverage = coverage_map_from_flat(
|
|
3387
|
+
flat,
|
|
3388
|
+
eval_mask=eval_mask,
|
|
3389
|
+
eval_mask_label=eval_mask_label,
|
|
3390
|
+
mask_meta=mask_meta or None,
|
|
3391
|
+
unmasked_flat=unmasked_flat,
|
|
3392
|
+
)
|
|
3233
3393
|
rms_per_key = _rms_per_key(flat, to_python=log_to_python)
|
|
3234
3394
|
r_max_per_key = _max_per_key(flat, to_python=log_to_python, scoring=self._scoring_by_key)
|
|
3235
3395
|
|
|
@@ -3649,6 +3809,8 @@ class ResidualEngine:
|
|
|
3649
3809
|
entry["derivative_error"] = derivative_error_report
|
|
3650
3810
|
if mask_meta:
|
|
3651
3811
|
entry["eval_mask"] = mask_meta
|
|
3812
|
+
if evaluation_coverage:
|
|
3813
|
+
entry["evaluation_coverage"] = evaluation_coverage
|
|
3652
3814
|
if rms_weighted is not None:
|
|
3653
3815
|
entry["rms_weighted"] = rms_weighted
|
|
3654
3816
|
entry["weighted_norms"] = {
|
|
@@ -3857,6 +4019,8 @@ def evaluate(
|
|
|
3857
4019
|
region_names: Optional[Mapping[Any, str]] = None,
|
|
3858
4020
|
region_min_points: Optional[int] = None,
|
|
3859
4021
|
evaluate_debug_loop: bool = False,
|
|
4022
|
+
nonfinite_policy: Optional[str] = None,
|
|
4023
|
+
eval_mask_label: Optional[str] = None,
|
|
3860
4024
|
**compute_kwargs: Any,
|
|
3861
4025
|
) -> List[Dict[str, Any]]:
|
|
3862
4026
|
"""
|
|
@@ -3953,6 +4117,8 @@ def evaluate(
|
|
|
3953
4117
|
implied_percentile=implied_percentile,
|
|
3954
4118
|
implied_noise_normalization=implied_noise_normalization,
|
|
3955
4119
|
require_batch=require_batch,
|
|
4120
|
+
nonfinite_policy=nonfinite_policy,
|
|
4121
|
+
eval_mask_label=eval_mask_label,
|
|
3956
4122
|
)
|
|
3957
4123
|
if batched is not None:
|
|
3958
4124
|
for report in batched:
|
|
@@ -3973,7 +4139,13 @@ def evaluate(
|
|
|
3973
4139
|
**compute_kwargs,
|
|
3974
4140
|
)
|
|
3975
4141
|
entry["index"] = i
|
|
3976
|
-
report = audit(
|
|
4142
|
+
report = audit(
|
|
4143
|
+
[entry],
|
|
4144
|
+
r_ref=r_ref,
|
|
4145
|
+
last_residual_dict=residuals if return_residuals else residuals,
|
|
4146
|
+
nonfinite_policy=nonfinite_policy,
|
|
4147
|
+
eval_mask_label=eval_mask_label,
|
|
4148
|
+
)
|
|
3977
4149
|
if return_residuals:
|
|
3978
4150
|
report["residuals"] = residuals
|
|
3979
4151
|
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)
|
|
@@ -558,6 +576,11 @@ def _score_one(
|
|
|
558
576
|
flat = _flat(residuals)
|
|
559
577
|
rms = {k: r_eff(v) for k, v in flat.items()}
|
|
560
578
|
rmax = {k: r_max(v) for k, v in flat.items()}
|
|
579
|
+
from moju.monitor.evaluation_coverage import coverage_counts_arrays
|
|
580
|
+
|
|
581
|
+
coverage_nums: Dict[str, Dict[str, Any]] = {
|
|
582
|
+
k: coverage_counts_arrays(v) for k, v in flat.items()
|
|
583
|
+
}
|
|
561
584
|
dynamic_scale: Dict[str, Any] = {}
|
|
562
585
|
recipe_ok: Dict[str, Any] = {}
|
|
563
586
|
for key, recipe, spec, _is_user in plan.auto_scale:
|
|
@@ -582,6 +605,8 @@ def _score_one(
|
|
|
582
605
|
"scale": dynamic_scale,
|
|
583
606
|
"recipe_ok": recipe_ok,
|
|
584
607
|
}
|
|
608
|
+
if coverage_nums:
|
|
609
|
+
out["coverage_nums"] = coverage_nums
|
|
585
610
|
if implied_id:
|
|
586
611
|
out["implied_id"] = implied_id
|
|
587
612
|
|