Commit ·
528346c
1
Parent(s): a900118
validation: use pinned public Hugging Face fixture
Browse files- data/fixtures/testing/ttH_NLO_64.root +0 -0
- docs/end_to_end_validation.md +9 -0
- tests/integration/test_real_root_model.py +5 -1
- tests/integration/test_real_root_sample.py +3 -3
- tests/unit/validation/test_fixture.py +19 -0
- validation/README.md +36 -16
- validation/extract_common.py +1 -1
- validation/fixture.py +111 -0
- validation/golden/testing/legacy/artifact.npz +3 -0
- validation/golden/testing/legacy/manifest.json +33 -0
- validation/manifests/dataset.json +1 -1
- validation/run_public_validation.py +77 -0
data/fixtures/testing/ttH_NLO_64.root
ADDED
|
Binary file (15.1 kB). View file
|
|
|
docs/end_to_end_validation.md
CHANGED
|
@@ -16,6 +16,15 @@ The captured runtime versions and seed policy are recorded in
|
|
| 16 |
`validation/manifests/environments.json`; the HF composite fixture provenance
|
| 17 |
is recorded in `validation/manifests/multiclass_fixture.json`.
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
Strict parity applies to preprocessing, topology, fixed forward, loss, and
|
| 20 |
one-step CPU updates. Multi-epoch, GPU and DDP comparisons are scientific:
|
| 21 |
compare curves, metrics, distributions and event-level correlations. Named
|
|
|
|
| 16 |
`validation/manifests/environments.json`; the HF composite fixture provenance
|
| 17 |
is recorded in `validation/manifests/multiclass_fixture.json`.
|
| 18 |
|
| 19 |
+
For the portable public regression, run:
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
uv run python -m validation.run_public_validation
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
This downloads or verifies the pinned `HWresearch/Delphes` 64-event fixture
|
| 26 |
+
and compares the current rewrite against the checked-in legacy golden output.
|
| 27 |
+
|
| 28 |
Strict parity applies to preprocessing, topology, fixed forward, loss, and
|
| 29 |
one-step CPU updates. Multi-epoch, GPU and DDP comparisons are scientific:
|
| 30 |
compare curves, metrics, distributions and event-level correlations. Named
|
tests/integration/test_real_root_model.py
CHANGED
|
@@ -34,7 +34,11 @@ FEATURE_SCALES = [0.1, 1, 1, 0.1, 1, 1, 1]
|
|
| 34 |
|
| 35 |
def _fixture_path() -> Path:
|
| 36 |
configured = os.environ.get("GNN4COLLIDERS_ROOT_FIXTURE")
|
| 37 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
|
| 40 |
@pytest.fixture(scope="module")
|
|
|
|
| 34 |
|
| 35 |
def _fixture_path() -> Path:
|
| 36 |
configured = os.environ.get("GNN4COLLIDERS_ROOT_FIXTURE")
|
| 37 |
+
return (
|
| 38 |
+
Path(configured)
|
| 39 |
+
if configured
|
| 40 |
+
else Path("data/fixtures/testing/ttH_NLO_64.root")
|
| 41 |
+
)
|
| 42 |
|
| 43 |
|
| 44 |
@pytest.fixture(scope="module")
|
tests/integration/test_real_root_sample.py
CHANGED
|
@@ -34,7 +34,7 @@ def _fixture_path() -> Path:
|
|
| 34 |
configured = os.environ.get("GNN4COLLIDERS_ROOT_FIXTURE")
|
| 35 |
if configured:
|
| 36 |
return Path(configured)
|
| 37 |
-
return Path("data/
|
| 38 |
|
| 39 |
|
| 40 |
@pytest.fixture(scope="module")
|
|
@@ -42,8 +42,8 @@ def root_tree():
|
|
| 42 |
path = _fixture_path()
|
| 43 |
if not path.exists():
|
| 44 |
pytest.skip(
|
| 45 |
-
f"ROOT sample fixture is absent: {path};
|
| 46 |
-
"
|
| 47 |
)
|
| 48 |
root_file = uproot.open(path)
|
| 49 |
tree = root_file["output"]
|
|
|
|
| 34 |
configured = os.environ.get("GNN4COLLIDERS_ROOT_FIXTURE")
|
| 35 |
if configured:
|
| 36 |
return Path(configured)
|
| 37 |
+
return Path("data/fixtures/testing/ttH_NLO_64.root")
|
| 38 |
|
| 39 |
|
| 40 |
@pytest.fixture(scope="module")
|
|
|
|
| 42 |
path = _fixture_path()
|
| 43 |
if not path.exists():
|
| 44 |
pytest.skip(
|
| 45 |
+
f"ROOT sample fixture is absent: {path}; run the public validation "
|
| 46 |
+
"command to download it"
|
| 47 |
)
|
| 48 |
root_file = uproot.open(path)
|
| 49 |
tree = root_file["output"]
|
tests/unit/validation/test_fixture.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import shutil
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
from validation.fixture import TESTING_FIXTURE, ensure_fixture, verify_fixture
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_checked_in_public_fixture_has_pinned_identity():
|
| 10 |
+
path = ensure_fixture("data/fixtures/testing/ttH_NLO_64.root", download=False)
|
| 11 |
+
assert path.stat().st_size == TESTING_FIXTURE.size_bytes
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_fixture_checksum_mismatch_is_rejected(tmp_path):
|
| 15 |
+
path = tmp_path / "fixture.root"
|
| 16 |
+
shutil.copyfile("data/fixtures/testing/ttH_NLO_64.root", path)
|
| 17 |
+
path.write_bytes(path.read_bytes() + b"corruption")
|
| 18 |
+
with pytest.raises(ValueError, match=r"fixture (size|SHA-256) mismatch"):
|
| 19 |
+
verify_fixture(path)
|
validation/README.md
CHANGED
|
@@ -7,38 +7,58 @@ flattened storage plus offsets. Exact fields (IDs, labels, folds, offsets,
|
|
| 7 |
topology) are exact-compared; floating fields use `tolerances.yaml`.
|
| 8 |
|
| 9 |
```bash
|
| 10 |
-
uv run python -m validation.
|
| 11 |
-
uv run python -m validation.run_full_validation \
|
| 12 |
-
--legacy-artifact validation_output/legacy \
|
| 13 |
-
--rewrite-artifact validation_output/rewrite \
|
| 14 |
-
--output validation_output/reports
|
| 15 |
```
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
| 20 |
generated output out of Git.
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
The
|
|
|
|
| 25 |
|
| 26 |
```bash
|
| 27 |
-
ROOT=/
|
| 28 |
HF_SHA=89d69eae9cd4d28a5d414d77bbc07185ea5b7de03481fdafe28899e2f3a09dec
|
| 29 |
|
| 30 |
conda run -n dgl env PYTHONPATH=.:legacy/root_gnn_dgl:src \
|
| 31 |
-
python validation/extract_legacy.py
|
| 32 |
--source-sha256 "$HF_SHA"
|
| 33 |
|
| 34 |
PYTHONPATH=.:src uv run python validation/extract_rewrite.py "$ROOT" \
|
| 35 |
validation_output/rewrite --source-sha256 "$HF_SHA"
|
| 36 |
```
|
| 37 |
|
| 38 |
-
The
|
| 39 |
-
RNTuple directly. Convert the fixture to a temporary TTree with a modern
|
| 40 |
-
Uproot process; this changes only the ROOT container representation, not event
|
| 41 |
-
values. The manifest retains the original HF SHA-256. Then run `forward.py`
|
| 42 |
with `model_epoch_71.pt`, `train_step.py`, `compare_step.py`, and
|
| 43 |
`compare_tasks.py` to generate fixed-forward, one-step, and task reports.
|
| 44 |
|
|
|
|
| 7 |
topology) are exact-compared; floating fields use `tolerances.yaml`.
|
| 8 |
|
| 9 |
```bash
|
| 10 |
+
uv run python -m validation.run_public_validation
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
```
|
| 12 |
|
| 13 |
+
The command verifies the local fixture, downloads it from the pinned Hugging
|
| 14 |
+
Face revision when necessary, extracts the rewrite, compares it with the
|
| 15 |
+
checked-in legacy golden artifact, writes JSON and Markdown reports, and exits
|
| 16 |
+
nonzero on a scientific mismatch. Use `--no-download` for offline runs. Keep
|
| 17 |
generated output out of Git.
|
| 18 |
|
| 19 |
+
The lower-level artifact tooling remains available for regenerating references
|
| 20 |
+
in the historical legacy environment:
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
uv run python -m validation.run_full_validation --smoke
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
## Current public HF fixture
|
| 27 |
+
|
| 28 |
+
The normal public regression path requires only the current environment. It
|
| 29 |
+
verifies the pinned public fixture, downloads it when missing, extracts the
|
| 30 |
+
rewrite, and compares it with the legacy golden artifact:
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
uv run python -m validation.run_public_validation
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
The fixture identity is:
|
| 37 |
+
|
| 38 |
+
```text
|
| 39 |
+
repository: HWresearch/Delphes (dataset)
|
| 40 |
+
revision: 76a6c362bfba8e766ba7255a5c08a55257f78d0e
|
| 41 |
+
path: testing/ttH_NLO_64.root
|
| 42 |
+
sha256: 89d69eae9cd4d28a5d414d77bbc07185ea5b7de03481fdafe28899e2f3a09dec
|
| 43 |
+
events: 64
|
| 44 |
+
```
|
| 45 |
|
| 46 |
+
The historical extraction commands below are retained only for regenerating or
|
| 47 |
+
auditing the golden artifact in a legacy environment:
|
| 48 |
|
| 49 |
```bash
|
| 50 |
+
ROOT=data/fixtures/testing/ttH_NLO_64.root
|
| 51 |
HF_SHA=89d69eae9cd4d28a5d414d77bbc07185ea5b7de03481fdafe28899e2f3a09dec
|
| 52 |
|
| 53 |
conda run -n dgl env PYTHONPATH=.:legacy/root_gnn_dgl:src \
|
| 54 |
+
python validation/extract_legacy.py "$ROOT" validation_output/legacy \
|
| 55 |
--source-sha256 "$HF_SHA"
|
| 56 |
|
| 57 |
PYTHONPATH=.:src uv run python validation/extract_rewrite.py "$ROOT" \
|
| 58 |
validation_output/rewrite --source-sha256 "$HF_SHA"
|
| 59 |
```
|
| 60 |
|
| 61 |
+
The manifest retains the original HF SHA-256. Then run `forward.py`
|
|
|
|
|
|
|
|
|
|
| 62 |
with `model_epoch_71.pt`, `train_step.py`, `compare_step.py`, and
|
| 63 |
`compare_tasks.py` to generate fixed-forward, one-step, and task reports.
|
| 64 |
|
validation/extract_common.py
CHANGED
|
@@ -136,7 +136,7 @@ def extract_root(
|
|
| 136 |
"events": event_count,
|
| 137 |
"sha256": file_sha256(path),
|
| 138 |
"source_sha256": source_sha256 or file_sha256(path),
|
| 139 |
-
"input_path":
|
| 140 |
"feature_schema": [
|
| 141 |
"pt",
|
| 142 |
"eta",
|
|
|
|
| 136 |
"events": event_count,
|
| 137 |
"sha256": file_sha256(path),
|
| 138 |
"source_sha256": source_sha256 or file_sha256(path),
|
| 139 |
+
"input_path": path.name,
|
| 140 |
"feature_schema": [
|
| 141 |
"pt",
|
| 142 |
"eta",
|
validation/fixture.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pinned public validation-fixture discovery, download, and verification."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import hashlib
|
| 6 |
+
import os
|
| 7 |
+
import tempfile
|
| 8 |
+
import urllib.request
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass(frozen=True)
|
| 14 |
+
class PublicFixture:
|
| 15 |
+
repository: str
|
| 16 |
+
revision: str
|
| 17 |
+
repository_path: str
|
| 18 |
+
sha256: str
|
| 19 |
+
size_bytes: int
|
| 20 |
+
|
| 21 |
+
@property
|
| 22 |
+
def url(self) -> str:
|
| 23 |
+
return (
|
| 24 |
+
f"https://huggingface.co/datasets/{self.repository}/resolve/"
|
| 25 |
+
f"{self.revision}/{self.repository_path}?download=true"
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
TESTING_FIXTURE = PublicFixture(
|
| 30 |
+
repository="HWresearch/Delphes",
|
| 31 |
+
revision="76a6c362bfba8e766ba7255a5c08a55257f78d0e",
|
| 32 |
+
repository_path="testing/ttH_NLO_64.root",
|
| 33 |
+
sha256="89d69eae9cd4d28a5d414d77bbc07185ea5b7de03481fdafe28899e2f3a09dec",
|
| 34 |
+
size_bytes=15050,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def default_fixture_path(root: str | Path = ".") -> Path:
|
| 39 |
+
return Path(root) / "data" / "fixtures" / "testing" / "ttH_NLO_64.root"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def file_sha256(path: str | Path) -> str:
|
| 43 |
+
digest = hashlib.sha256()
|
| 44 |
+
with Path(path).open("rb") as stream:
|
| 45 |
+
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
| 46 |
+
digest.update(chunk)
|
| 47 |
+
return digest.hexdigest()
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def verify_fixture(path: str | Path, fixture: PublicFixture = TESTING_FIXTURE) -> Path:
|
| 51 |
+
"""Verify size and SHA-256 before allowing a fixture into validation."""
|
| 52 |
+
|
| 53 |
+
path = Path(path)
|
| 54 |
+
if not path.is_file():
|
| 55 |
+
raise FileNotFoundError(f"validation fixture is missing: {path}")
|
| 56 |
+
actual_size = path.stat().st_size
|
| 57 |
+
if actual_size != fixture.size_bytes:
|
| 58 |
+
raise ValueError(
|
| 59 |
+
f"fixture size mismatch for {path}: expected {fixture.size_bytes}, "
|
| 60 |
+
f"got {actual_size}"
|
| 61 |
+
)
|
| 62 |
+
actual_sha256 = file_sha256(path)
|
| 63 |
+
if actual_sha256 != fixture.sha256:
|
| 64 |
+
raise ValueError(
|
| 65 |
+
f"fixture SHA-256 mismatch for {path}: expected {fixture.sha256}, "
|
| 66 |
+
f"got {actual_sha256}"
|
| 67 |
+
)
|
| 68 |
+
return path
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def download_fixture(
|
| 72 |
+
path: str | Path,
|
| 73 |
+
fixture: PublicFixture = TESTING_FIXTURE,
|
| 74 |
+
*,
|
| 75 |
+
timeout: float = 60.0,
|
| 76 |
+
) -> Path:
|
| 77 |
+
"""Download a pinned fixture atomically and verify it before returning."""
|
| 78 |
+
|
| 79 |
+
path = Path(path)
|
| 80 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 81 |
+
fd, temporary_name = tempfile.mkstemp(
|
| 82 |
+
prefix=f".{path.name}.", suffix=".download", dir=path.parent
|
| 83 |
+
)
|
| 84 |
+
os.close(fd)
|
| 85 |
+
temporary = Path(temporary_name)
|
| 86 |
+
try:
|
| 87 |
+
with urllib.request.urlopen(fixture.url, timeout=timeout) as response:
|
| 88 |
+
with temporary.open("wb") as output:
|
| 89 |
+
while chunk := response.read(1024 * 1024):
|
| 90 |
+
output.write(chunk)
|
| 91 |
+
verify_fixture(temporary, fixture)
|
| 92 |
+
os.replace(temporary, path)
|
| 93 |
+
finally:
|
| 94 |
+
temporary.unlink(missing_ok=True)
|
| 95 |
+
return path
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def ensure_fixture(
|
| 99 |
+
path: str | Path | None = None,
|
| 100 |
+
*,
|
| 101 |
+
download: bool = True,
|
| 102 |
+
fixture: PublicFixture = TESTING_FIXTURE,
|
| 103 |
+
) -> Path:
|
| 104 |
+
"""Return a verified local fixture, downloading it when necessary."""
|
| 105 |
+
|
| 106 |
+
path = Path(path) if path is not None else default_fixture_path()
|
| 107 |
+
if path.is_file():
|
| 108 |
+
return verify_fixture(path, fixture)
|
| 109 |
+
if not download:
|
| 110 |
+
raise FileNotFoundError(f"validation fixture is missing: {path}")
|
| 111 |
+
return download_fixture(path, fixture)
|
validation/golden/testing/legacy/artifact.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cc5164e3eda8f88991fc87a70e13e766b1ae2ef3604a614273d9f6c52cdcf792
|
| 3 |
+
size 40151
|
validation/golden/testing/legacy/manifest.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"arrays": [
|
| 3 |
+
"edge_dst_flat",
|
| 4 |
+
"edge_features_flat",
|
| 5 |
+
"edge_offsets",
|
| 6 |
+
"edge_src_flat",
|
| 7 |
+
"folds",
|
| 8 |
+
"globals",
|
| 9 |
+
"labels",
|
| 10 |
+
"node_features_flat",
|
| 11 |
+
"node_offsets",
|
| 12 |
+
"sample_id",
|
| 13 |
+
"weights"
|
| 14 |
+
],
|
| 15 |
+
"event_count": 64,
|
| 16 |
+
"events": 64,
|
| 17 |
+
"feature_schema": [
|
| 18 |
+
"pt",
|
| 19 |
+
"eta",
|
| 20 |
+
"phi",
|
| 21 |
+
"energy",
|
| 22 |
+
"btag",
|
| 23 |
+
"charge",
|
| 24 |
+
"node_type"
|
| 25 |
+
],
|
| 26 |
+
"input_path": "ttH_NLO_64.root",
|
| 27 |
+
"repository_path": "testing/ttH_NLO_64.root",
|
| 28 |
+
"schema_version": 1,
|
| 29 |
+
"sha256": "89d69eae9cd4d28a5d414d77bbc07185ea5b7de03481fdafe28899e2f3a09dec",
|
| 30 |
+
"source": "huggingface://datasets/HWresearch/Delphes",
|
| 31 |
+
"source_sha256": "89d69eae9cd4d28a5d414d77bbc07185ea5b7de03481fdafe28899e2f3a09dec",
|
| 32 |
+
"tree": "output"
|
| 33 |
+
}
|
validation/manifests/dataset.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"source": "https://huggingface.co/datasets/HWresearch/Delphes",
|
| 3 |
-
"revision": "
|
| 4 |
"files": [
|
| 5 |
{
|
| 6 |
"repository_path": "testing/ttH_NLO_64.root",
|
|
|
|
| 1 |
{
|
| 2 |
"source": "https://huggingface.co/datasets/HWresearch/Delphes",
|
| 3 |
+
"revision": "76a6c362bfba8e766ba7255a5c08a55257f78d0e",
|
| 4 |
"files": [
|
| 5 |
{
|
| 6 |
"repository_path": "testing/ttH_NLO_64.root",
|
validation/run_public_validation.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Run rewrite-vs-golden validation using the pinned public HF fixture."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
from validation.compare import compare_artifacts, write_reports
|
| 11 |
+
from validation.extract_common import (
|
| 12 |
+
FEATURE_BRANCHES,
|
| 13 |
+
OBJECT_TYPES,
|
| 14 |
+
SCALES,
|
| 15 |
+
extract_root,
|
| 16 |
+
)
|
| 17 |
+
from validation.fixture import TESTING_FIXTURE, ensure_fixture
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _extract_rewrite(root_file: Path, output: Path) -> None:
|
| 21 |
+
from gnn4colliders.features import build_node_features
|
| 22 |
+
|
| 23 |
+
def build(event):
|
| 24 |
+
features, _ = build_node_features(event, FEATURE_BRANCHES, OBJECT_TYPES, SCALES)
|
| 25 |
+
return features
|
| 26 |
+
|
| 27 |
+
extract_root(
|
| 28 |
+
root_file,
|
| 29 |
+
output,
|
| 30 |
+
build_features=build,
|
| 31 |
+
sample_name=TESTING_FIXTURE.repository_path,
|
| 32 |
+
source_sha256=TESTING_FIXTURE.sha256,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def main() -> int:
|
| 37 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 38 |
+
parser.add_argument("--fixture", type=Path)
|
| 39 |
+
parser.add_argument("--output", type=Path, default=Path("validation_output/public"))
|
| 40 |
+
parser.add_argument(
|
| 41 |
+
"--no-download",
|
| 42 |
+
action="store_true",
|
| 43 |
+
help="fail instead of downloading a missing fixture",
|
| 44 |
+
)
|
| 45 |
+
args = parser.parse_args()
|
| 46 |
+
|
| 47 |
+
fixture = ensure_fixture(args.fixture, download=not args.no_download)
|
| 48 |
+
golden = Path(__file__).parent / "golden" / "testing" / "legacy"
|
| 49 |
+
if not (golden / "manifest.json").is_file():
|
| 50 |
+
raise FileNotFoundError(f"golden reference is missing: {golden}")
|
| 51 |
+
rewrite = args.output / "rewrite"
|
| 52 |
+
_extract_rewrite(fixture, rewrite)
|
| 53 |
+
report = compare_artifacts(golden, rewrite)
|
| 54 |
+
report["fixture"] = {
|
| 55 |
+
"repository": TESTING_FIXTURE.repository,
|
| 56 |
+
"revision": TESTING_FIXTURE.revision,
|
| 57 |
+
"repository_path": TESTING_FIXTURE.repository_path,
|
| 58 |
+
"sha256": TESTING_FIXTURE.sha256,
|
| 59 |
+
"size_bytes": TESTING_FIXTURE.size_bytes,
|
| 60 |
+
}
|
| 61 |
+
report_dir = args.output / "report"
|
| 62 |
+
write_reports(report, report_dir)
|
| 63 |
+
print(
|
| 64 |
+
json.dumps(
|
| 65 |
+
{
|
| 66 |
+
"overall_status": report["overall_status"],
|
| 67 |
+
"fixture": str(fixture),
|
| 68 |
+
"report": str(report_dir),
|
| 69 |
+
},
|
| 70 |
+
indent=2,
|
| 71 |
+
)
|
| 72 |
+
)
|
| 73 |
+
return 0 if report["overall_status"] == "PASS" else 1
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
if __name__ == "__main__":
|
| 77 |
+
raise SystemExit(main())
|