diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index fc2783636..3980899a7 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -415,11 +415,16 @@ registry lookup. For field-coupled finite-difference JVPs, the exact boundary evaluation level must also equal the active Program resource level before the perturbed field solve or the frozen-field restoration is allowed to dispatch. A fine-level caller therefore cannot forge a coarse point and reuse level 0. -This identity guard does not create a fine-level tangent-field solve. Supporting a boundary JVP that -reads solved fields requires a provider contract that materializes the field tangent from the state -direction on every participating level, couples those tangents across CompositeFAC when requested, -and restores the frozen primal field transactionally. Reusing the primal field pointer would omit -the derivative of the field solve and is therefore not a valid fallback. +The generated finite-difference route materializes that derivative by re-solving the exact prepared +provider from the perturbed state on both levels of the proved 2D ratio-2 L0/L1 hierarchy, evaluating +the complete residual, and restoring the frozen primal field transactionally; it never reuses the +unperturbed field pointer as a tangent. A two-rank native consensus/numerical oracle covers this +algebra, but the generated-Program route has no installed MPI end-to-end proof. The public capability +row therefore advertises only host single-process execution. A partially refined CompositeFAC +hierarchy with a dynamic physical boundary remains a separate explicit refusal until its correction +owns a level-qualified homogeneous/JVP boundary operator. +The public report exposes that unsupported subcase separately as +`amr:composite_dynamic_boundary`; it is not hidden behind the available level-qualified JVP row. Linear and nonlinear field routes both retain the accepted warm start until their `SolveReport` is consumed; an invalid boundary evaluation or iteration limit restores that value and cannot update the published aux channel. diff --git a/python/pops/_capabilities_report.py b/python/pops/_capabilities_report.py index 2d7d47e93..1ee7e84b6 100644 --- a/python/pops/_capabilities_report.py +++ b/python/pops/_capabilities_report.py @@ -373,6 +373,8 @@ def _python_contract_rows(flags: Any, source: str) -> list[Any]: mpi = bool(_flag_value(flags, "supports_mpi")) gpu = bool(_flag_value(flags, "supports_gpu")) + amr_status = _status_from_flag(flags, "supports_amr") + composite_boundary_status = "partial" if amr_status == "available" else amr_status return [ _row( "boundary:prepared_transport", @@ -494,9 +496,7 @@ def _python_contract_rows(flags: Any, source: str) -> list[Any]: "solver outcome, block counter, or restart metadata; a typed candidate failure " "rejects the step and never substitutes another solver" ), - requested=( - "prepared Riemann recovery chain with requested/used solver diagnostics" - ), + requested=("prepared Riemann recovery chain with requested/used solver diagnostics"), available_route=( "one explicitly selected Riemann solver with typed rejection and transactional " "rollback" @@ -574,19 +574,41 @@ def _python_contract_rows(flags: Any, source: str) -> list[Any]: _row( "amr:field_coupled_rhs_jacvec", layout="amr", - backend="none", + backend="production", platform="host", - mpi=mpi, - gpu=gpu, - status="unavailable", + mpi=False, + gpu=False, + status=amr_status, + limitation=( + "field-coupled finite-difference rhs_jacvec re-solves the exact prepared field " + "provider from the perturbed state on both levels of the proved 2D ratio-2 L0/L1 " + "hierarchy, then restores the frozen primal publication transactionally; a " + "two-rank native consensus/numerical oracle exists, but the generated-Program " + "route has no installed MPI end-to-end proof and therefore advertises only host " + "single-process execution" + ), + source=source, + ), + _row( + "amr:composite_dynamic_boundary", + layout="amr", + backend="production", + platform="host", + mpi=False, + gpu=False, + status=composite_boundary_status, limitation=( - "field-coupled rhs_jacvec has no level-qualified tangent-field provider ABI " - "for AMR level > 0" + "the proved fully refined 2D ratio-2 L0/L1 hierarchy passes the exact finest-level " + "logical time, state dependencies and nonlinear/JVP context to its dynamic field " + "boundary; a partially refined CompositeFAC hierarchy is refused because its " + "coarse-fine correction lacks a level-qualified homogeneous/JVP boundary operator" + ), + available_route=( + "fully refined 2D ratio-2 L0/L1 host single-process CompositeFAC hierarchy" ), - requested="field_coupled rhs_jacvec on AMR level > 0", - available_route="field_coupled rhs_jacvec on AMR level 0", alternative=( - "use the level-0 route or implement a level-qualified tangent-field provider ABI" + "use a fully refined hierarchy or implement the level-qualified homogeneous/JVP " + "coarse-fine correction boundary" ), source=source, ), diff --git a/tests/python/integration/amr/test_amr_runtime_inspect.py b/tests/python/integration/amr/test_amr_runtime_inspect.py index aa15cedd4..aea86ecea 100644 --- a/tests/python/integration/amr/test_amr_runtime_inspect.py +++ b/tests/python/integration/amr/test_amr_runtime_inspect.py @@ -294,11 +294,20 @@ def test_inspect_before_build_reports_unbuilt_patches_honestly(): assert report.regrid.frozen is True -def test_inspect_no_longer_lists_the_served_fine_level_field_jacvec_as_a_limitation(): +def test_inspect_separates_served_field_jacvec_from_partial_composite_boundary(): report = AmrSystem(n=16, L=1.0, periodicity=(True, True)).amr.inspect() - rows = [row for row in report.limitations if row["feature"] == "amr:field_coupled_rhs_jacvec"] + field_rows = [ + row for row in report.limitations if row["feature"] == "amr:field_coupled_rhs_jacvec" + ] + boundary_rows = [ + row for row in report.limitations if row["feature"] == "amr:composite_dynamic_boundary" + ] - assert rows == [] + assert field_rows == [] + assert len(boundary_rows) == 1 + assert boundary_rows[0]["status"] == "partial" + assert "partially refined CompositeFAC hierarchy is refused" in boundary_rows[0]["reason"] + assert "level-qualified homogeneous/JVP boundary operator" in boundary_rows[0]["reason"] # --- compiled static delegation ------------------------------------------------ diff --git a/tests/python/unit/codegen/test_fail_closed_reports.py b/tests/python/unit/codegen/test_fail_closed_reports.py index 6773f344d..e01c2b7cb 100644 --- a/tests/python/unit/codegen/test_fail_closed_reports.py +++ b/tests/python/unit/codegen/test_fail_closed_reports.py @@ -91,6 +91,50 @@ def test_mpi_world_route_reports_only_proved_native_availability(supports_mpi, e assert external_amr.available_route == ( "external FieldSolver@2 on one uniform host/serial level" ) + field_jacvec = routes["amr:field_coupled_rhs_jacvec"] + assert field_jacvec.status == "available" + assert field_jacvec.layout == "amr" + assert field_jacvec.backend == "production" + assert field_jacvec.mpi is False + assert field_jacvec.gpu is False + assert "proved 2D ratio-2 L0/L1 hierarchy" in field_jacvec.limitation + assert "restores the frozen primal publication transactionally" in field_jacvec.limitation + assert "two-rank native consensus/numerical oracle exists" in field_jacvec.limitation + assert ( + "generated-Program route has no installed MPI end-to-end proof" in field_jacvec.limitation + ) + assert "host single-process execution" in field_jacvec.limitation + assert field_jacvec.available_route == "" + assert field_jacvec.alternative == "" + composite_boundary = routes["amr:composite_dynamic_boundary"] + assert composite_boundary.status == "partial" + assert composite_boundary.layout == "amr" + assert composite_boundary.backend == "production" + assert composite_boundary.mpi is False + assert composite_boundary.gpu is False + assert "fully refined 2D ratio-2 L0/L1 hierarchy" in composite_boundary.limitation + assert "partially refined CompositeFAC hierarchy is refused" in composite_boundary.limitation + assert "level-qualified homogeneous/JVP boundary operator" in composite_boundary.limitation + assert composite_boundary.available_route == ( + "fully refined 2D ratio-2 L0/L1 host single-process CompositeFAC hierarchy" + ) + assert "coarse-fine correction boundary" in composite_boundary.alternative + + +def test_amr_field_jacvec_routes_follow_the_artifact_amr_flag(): + report = capability_reports.native_capability_report( + flags={"supports_mpi": True, "supports_gpu": False, "supports_amr": False}, + source="uniform-test-manifest", + ) + routes = {row.feature: row for row in report.routes} + + field_jacvec = routes["amr:field_coupled_rhs_jacvec"] + assert field_jacvec.status == "unavailable" + assert field_jacvec.error_message + + composite_boundary = routes["amr:composite_dynamic_boundary"] + assert composite_boundary.status == "unavailable" + assert composite_boundary.error_message def test_transport_boundary_routes_report_exact_supported_envelope_and_missing_kernels(): @@ -152,9 +196,7 @@ def test_transport_boundary_routes_report_exact_supported_envelope_and_missing_k flags={"supports_mpi": True, "supports_gpu": True, "supports_amr": True}, source="test-gpu-manifest", ) - gpu_post_riemann = { - row.feature: row for row in gpu_report.routes - }["boundary:post_riemann_flux"] + gpu_post_riemann = {row.feature: row for row in gpu_report.routes}["boundary:post_riemann_flux"] assert gpu_post_riemann.gpu is False