Note

This page renders committed notebook outputs. The Read the Docs build does not execute notebook code.

Portability round-trips with empirical revalidation#

Current surface: V0.29.

Purpose#

Show that portable generator payloads should be revalidated against the current field and residual target after export/import.

What you will learn#

  • How canonical GeneratorFamily manifests preserve payload meaning.

  • How strict payload normalization differs from compatibility paths.

  • How validate_symmetry_candidate(...) turns a round-tripped payload into an empirical report.

  • How malformed or unsupported payloads fail with typed validation errors.

Required extras#

Core install is enough.

Expected runtime#

Under 1 minute.

Out of scope#

No external detector training, no callable candidates, no formula-string execution, no broad interchange standard.

These notebooks are tutorials, not API contracts. Example outputs are runtime summaries, not canonical paper artifacts.

[1]:
from pathlib import Path
import sys

ROOT = Path.cwd()
if not (ROOT / "pyproject.toml").exists():
    ROOT = ROOT.parent
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))
from notebooks._tutorial_utils import confidence_card, print_cards, pretty_json
from pdelie import SchemaValidationError
from pdelie.data import generate_heat_1d_field_batch
from pdelie.portability import coerce_generator_family, export_generator_family_manifest, import_generator_family_manifest
from pdelie.reporting import summarize_generator_fit_diagnostics, summarize_generator_family
from pdelie.residuals import HeatResidualEvaluator
from pdelie.symmetry import fit_translation_generator, validate_symmetry_candidate

CONFIG = {"fit_epsilon": 1e-4, "source_candidate_id": "round_trip_heat_translation"}
CONFIG

[1]:
{'fit_epsilon': 0.0001, 'source_candidate_id': 'round_trip_heat_translation'}

1. Fit once, then package meaning explicitly#

[2]:
field = generate_heat_1d_field_batch(batch_size=4, num_times=33, num_points=64, seed=630)
evaluator = HeatResidualEvaluator()
generator = fit_translation_generator(field, evaluator, epsilon=CONFIG["fit_epsilon"])
fit_summary = summarize_generator_fit_diagnostics(generator)
family_summary = summarize_generator_family(generator)
print(pretty_json({"family": family_summary, "fit": fit_summary}, max_chars=2500))

{
  "family": {
    "coefficient_shape": [
      1,
      4
    ],
    "coefficients": [
      [
        0.9999999999359518,
        1.0133800116507564e-05,
        -2.0831610610531808e-08,
        5.04006296149618e-06
      ]
    ],
    "diagnostics": {
      "basis": [
        "1",
        "t",
        "x",
        "u"
      ],
      "basis_delta_norms": {
        "1": 0.0037811302102881963,
        "t": 156.86688686928323,
        "u": 111.92166664831211,
        "x": 3893.915837793674
      },
      "condition_number": 1149085.046098012,
      "design_column_norms": {
        "1": 0.0037811302102881963,
        "t": 156.86688686928323,
        "u": 111.92166664831211,
        "x": 3893.915837793674
      },
      "evidence_label": "direct_svd_in_tolerance",
      "fallback_reason": null,
      "fit_mode": "svd",
      "fit_residual": 0.0033887146157163994,
      "min_delta_basis": "1",
      "reference_fallback_used": false,
      "selected_coefficients": [
        0.9999999999359518,
        1.0133800116507564e-05,
        -2.0831610610531808e-08,
        5.04006296149618e-06
      ],
      "selected_span_distance": 1.1317975676651596e-05,
      "singular_values": [
        3893.9212904134856,
        156.89414953360733,
        111.69351544041169,
        0.0033887146157163994
      ],
      "svd_coefficients": [
        0.9999999999359518,
        1.0133800116507564e-05,
        -2.0831610610531808e-08,
        5.04006296149618e-06
      ],
      "svd_span_distance": 1.1317975676651596e-05,
      "training_epsilon": 0.0001
    },
    "fallback_reason": null,
    "fit_mode": "svd",
    "generator_names": null,
    "normalization": "l2_unit",
    "parameterization": "polynomial_translation_affine",
    "reference_fallback_used": false,
    "summary_schema_version": "0.1",
    "summary_type": "generator_family",
    "translation_span_distance": 1.1317975676651596e-05
  },
  "fit": {
    "basis": [
      "1",
      "t",
      "x",
      "u"
    ],
    "basis_delta_norms": {
      "1": 0.0037811302102881963,
      "t": 156.86688686928323,
      "u": 111.92166664831211,
      "x": 3893.915837793674
    },
    "condition_number": 1149085.046098012,
    "design_column_norms": {
      "1": 0.0037811302102881963,
      "t": 156.86688686928323,
      "u": 111.92166664831211,
      "x": 3893.915837793674
    },
    "evidence_label": "direct_svd_in_tolerance",
    "fallback_reason": null,
    "fit_mode": "svd",
    "fit_residual": 0.0033887146157163994,
    "min
... <truncated 777 chars>

2. Manifest round-trip preserves the canonical generator payload#

[3]:
manifest = export_generator_family_manifest(
    generator,
    pdelie_version="tutorial-v0.28",
    provenance={"notebook": "03_portability_round_trips"},
)
imported = import_generator_family_manifest(manifest)
coerced = coerce_generator_family(manifest)
print(pretty_json({
    "manifest_type": manifest["manifest_type"],
    "imported_equal": imported.to_dict() == generator.to_dict(),
    "coerced_equal": coerced.to_dict() == generator.to_dict(),
}))

{
  "coerced_equal": true,
  "imported_equal": true,
  "manifest_type": "pdelie.generator_family_export"
}

3. Revalidate after import#

This is the v0.16 upgrade: an external or round-tripped candidate should be validated under the current field, residual evaluator, thresholds, and optional reference.

[4]:
validation = validate_symmetry_candidate(
    field,
    imported,
    residual_evaluator=evaluator,
    source_candidate_id=CONFIG["source_candidate_id"],
)
card = confidence_card(label="round-tripped generator", fit=fit_summary, validation=validation)
print_cards([card])
print(pretty_json(validation, max_chars=3500))

[
  {
    "candidate_kind": "generator_family",
    "condition_number": 1149085.046098012,
    "evidence_label": "direct_svd_in_tolerance",
    "fit_mode": "svd",
    "label": "round-tripped generator",
    "reference_fallback_used": false,
    "selected_span_distance": 1.1317975676651596e-05,
    "singular_value_count": 4,
    "svd_span_distance": 1.1317975676651596e-05,
    "validation_conclusion": "validated"
  }
]
{
  "candidate_kind": "generator_family",
  "candidate_summary": {
    "coefficient_shape": [
      1,
      4
    ],
    "coefficients": [
      [
        0.9999999999359518,
        1.0133800116507564e-05,
        -2.0831610610531808e-08,
        5.04006296149618e-06
      ]
    ],
    "diagnostics": {
      "basis": [
        "1",
        "t",
        "x",
        "u"
      ],
      "basis_delta_norms": {
        "1": 0.0037811302102881963,
        "t": 156.86688686928323,
        "u": 111.92166664831211,
        "x": 3893.915837793674
      },
      "condition_number": 1149085.046098012,
      "design_column_norms": {
        "1": 0.0037811302102881963,
        "t": 156.86688686928323,
        "u": 111.92166664831211,
        "x": 3893.915837793674
      },
      "evidence_label": "direct_svd_in_tolerance",
      "fallback_reason": null,
      "fit_mode": "svd",
      "fit_residual": 0.0033887146157163994,
      "min_delta_basis": "1",
      "reference_fallback_used": false,
      "selected_coefficients": [
        0.9999999999359518,
        1.0133800116507564e-05,
        -2.0831610610531808e-08,
        5.04006296149618e-06
      ],
      "selected_span_distance": 1.1317975676651596e-05,
      "singular_values": [
        3893.9212904134856,
        156.89414953360733,
        111.69351544041169,
        0.0033887146157163994
      ],
      "svd_coefficients": [
        0.9999999999359518,
        1.0133800116507564e-05,
        -2.0831610610531808e-08,
        5.04006296149618e-06
      ],
      "svd_span_distance": 1.1317975676651596e-05,
      "training_epsilon": 0.0001
    },
    "fallback_reason": null,
    "fit_mode": "svd",
    "generator_names": null,
    "normalization": "l2_unit",
    "parameterization": "polynomial_translation_affine",
    "reference_fallback_used": false,
    "summary_schema_version": "0.1",
    "summary_type": "generator_family",
    "translation_span_distance": 1.1317975676651596e-05
  },
  "check_reports": {
    "finite_transform_verification": {
      "report": {
        "classification": "exact",
        "diagnostics": {
          "batch_errors": [
            [
              4.517438082004002e-09,
              1.353233245421291e-09,
              2.3268666014623027e-09,
              3.82848018646044e-09
            ],
            [
              1.4285392566444045e-08,
              4.279297660259322e-09,
              7.358197057645651e-09,
              1.210671752275741e-08
            ],
            [
              4.5174362945667467e-08,
              1.3532322619707901e-08,
              2.3268653770340417e-08,
              3.8284788951846757e-08
            ],
            [
              1.428533957488756e-07,
              4.279281848829128e-08,
              7.358170221721434e-08,
              1.2106673250724294e-07
            ],
            [
              4.517268706859453e-07,
              1.3531826537297098e-07,
              2.3267817036313517e-07,
              3.828339691538713e-07
            ],
            [
              1.4280042110990248e-06,
              4.277713700327642e-07,
              7.355526036720196e-07,
              1.2102272040723458e-06
            ],
            [
              4.5005338798622695e-06,
              1.3482287971829435e-06,
              2.3184288082321497e-06,
              3.8144362696403565e-06
            ]
          ],
          "heldout_initial_conditions": 4,
          "span_distance": 1.1317975676651596e-05,
          "span_tolera
... <truncated 2007 chars>

4. Malformed payloads should fail before scientific interpretation#

External interchange should be strict. A malformed mapping is not a weak candidate; it is not a candidate at all.

[5]:
malformed_payload = {"schema_version": "0.2", "parameterization": "polynomial_translation_affine"}
try:
    validate_symmetry_candidate(
        field,
        malformed_payload,
        residual_evaluator=evaluator,
        source_candidate_id="malformed_payload",
    )
except SchemaValidationError as exc:
    print(type(exc).__name__, str(exc))
SchemaValidationError GeneratorFamily candidate payload is malformed.

Recap#

Export/import preserves payloads, while candidate validation checks whether the imported object is credible for the current field and residual evaluator.

Common pitfalls#

  • Assuming deserialization means scientific validity.

  • Mixing canonical payloads with unsupported legacy or partial mappings.

  • Skipping revalidation after changing fields, residual evaluators, or thresholds.

Extension ideas#

  • Attach provenance to manifests from an external detector.

  • Validate the same payload against Heat and Fisher-KPP to see equation-specific behavior.

  • Add stricter checks in your own workflow before downstream acceptance.

What to read/run next#

Run 04_discovered_vs_known_translation_generators.ipynb for side-by-side candidate comparison.