GNN4Colliders / tasks /task3.md
ho22joshua's picture
rewriting codebase (#7)
916755e
|
Raw History Blame Contribute Delete
12.5 kB
# Task 3: Characterize Legacy Data and Graph Behavior
Build a deterministic characterization/parity test suite for the active legacy ROOT-GNN preprocessing path.
The ROOT-GNN development environment is now expected to include working PyTorch and DGL support.
Do not implement the new production data pipeline yet.
Before making changes, read:
```text
AGENTS.md
docs/architecture.md
docs/migration.md
```
Then inspect the relevant legacy implementation under:
```text
legacy/root_gnn_dgl/
```
Focus only on the active data/graph behavior required by the standard ROOT-GNN workflow.
---
# Goal
Capture the externally observable behavior of the legacy preprocessing stack so that the new implementation can later be rewritten against explicit parity tests.
The relevant flow is:
```text
ROOT/Awkward event data
->
node feature construction
->
graph topology
->
edge feature construction
->
labels / tracking / global features
```
The tests must characterize what the legacy implementation **actually does**.
Do not encode assumptions solely because they appear in documentation.
If documentation, previous expectations, and observed legacy behavior disagree, treat the running legacy implementation as the behavior to characterize and document the discrepancy.
---
# Environment validation
Before writing or modifying parity tests, verify that the ROOT-GNN environment is functional.
Run:
```bash
uv sync --extra root-gnn
uv run python -c "import torch; print(torch.__version__)"
uv run python -c "import dgl; print(dgl.__version__)"
```
DGL-dependent parity tests must not silently skip because DGL is absent.
If DGL cannot import, stop and report the environment failure rather than treating skipped tests as successful characterization.
---
# 1. Node feature characterization
Characterize the active legacy node-feature construction.
Verify:
* feature count
* feature ordering
* feature values
* dtype
* shape
* concatenation across object types where applicable
* feature scaling behavior
The active schema is expected to include:
```text
pt
eta
phi
energy
btag
charge
node_type
```
But verify this against the legacy implementation rather than assuming it.
Characterize derived behavior including:
```text
CALC_E = pt * cosh(eta)
```
if that is what the active legacy path actually implements.
Also verify:
* constant-valued branches
* `NODE_TYPE`
* scale application
* object ordering when multiple object types are combined
Use explicit expected arrays where practical.
---
# 2. Graph topology characterization
Characterize the graph produced by the active legacy graph builder.
Verify:
* whether the graph is directed
* whether self-loops are present
* number of nodes
* number of edges
* source indices
* destination indices
* edge ordering if deterministic
* behavior for small graph sizes
Do not assume a two-node fully connected directed graph has four edges.
Explicitly characterize the observed behavior.
For example, if the legacy implementation produces:
```text
0 -> 1
1 -> 0
```
for a two-node graph, encode that exact behavior.
If the topology is fully connected without self-loops, verify the expected edge count:
```text
N * (N - 1)
```
for multiple values of `N`.
At minimum test:
```text
N = 1
N = 2
N = 3
```
where supported by the legacy implementation.
For edge ordering:
* assert exact ordering only if it is deterministic and observable
* otherwise test topology independent of ordering and document the ambiguity
---
# 3. Edge feature characterization
Characterize the active legacy edge-feature construction.
Verify that edge features correspond to:
```text
deta
dphi
dR
```
in the actual stored order.
Test at least one case where phi wrapping matters.
For example, use objects near:
```text
phi = +pi
phi = -pi
```
so that naive subtraction differs from wrapped angular distance.
Verify:
```text
dR = sqrt(deta^2 + dphi^2)
```
if that is the actual legacy behavior.
Important:
Expected edge-feature arrays must correspond exactly to the graph edges produced by the legacy implementation.
Do not duplicate expectations for edges that do not exist.
Where ordering is deterministic, verify the feature associated with each `(src, dst)` pair.
---
# 4. Tracking semantics
Characterize tracking information.
Verify at minimum the observed semantics of:
```text
tracking[:, 0]
tracking[:, 1]
```
Expected meanings from the architecture documentation are:
```text
column 0 -> fold identifier
column 1 -> event weight
```
Confirm those semantics against the legacy implementation.
Also characterize:
* dtype
* shape
* whether tracking information is mutated during dataset processing
* behavior of negative or non-unit weights where easily testable
Do not expand into training-weight semantics yet.
---
# 5. Labels
Characterize labels produced by the active standard dataset path.
Verify:
* shape
* dtype
* scalar vs vector representation
* binary-task representation where applicable
* multiclass representation where practical
Focus on the active configurations.
Do not characterize every experimental loss or task variant.
---
# 6. Global features
Characterize global-feature behavior in the active path.
Verify:
* whether globals are present
* shape
* dtype
* empty/default behavior when global features are unused
Do not invent a new global-feature abstraction.
The purpose is only to record legacy behavior.
---
# 7. Fixtures
Use deterministic fixtures under:
```text
tests/fixtures/
```
Prefer, in order:
1. small in-memory NumPy/Awkward objects
2. a very small ROOT file only where actual ROOT I/O must be tested
3. compact stored expected outputs only when inline arrays become unwieldy
Avoid large external datasets.
Fixtures should exercise meaningful cases such as:
* one object
* two objects
* three or more objects
* multiple object types if relevant
* nontrivial eta values
* phi wrapping across `-pi/pi`
* different node types
* non-unit event weights
Test empty object collections only if the active legacy path supports them meaningfully.
Do not invent impossible legacy inputs just to increase coverage.
---
# 8. Test organization
Place characterization tests under:
```text
tests/parity/
```
A reasonable organization is:
```text
tests/parity/
conftest.py
test_legacy_node_features.py
test_legacy_graph_and_edges.py
test_legacy_tracking.py
test_legacy_labels_and_globals.py
```
Use fewer files if that produces clearer tests.
Shared legacy import/setup helpers may live in:
```text
tests/parity/conftest.py
```
Do not copy production functions from the legacy tree into tests.
Tests should invoke the legacy implementation directly.
---
# 9. Legacy imports
Avoid permanently modifying Python import paths outside the test process.
If the legacy package requires path adjustment because it is not installable, isolate that behavior in test setup.
Do not modify:
```text
legacy/root_gnn_dgl/
```
to make imports easier.
Do not introduce repository-wide `sys.path` hacks into production code.
---
# 10. Expected outputs
Prefer readable explicit expectations.
For example:
```python
expected_features = torch.tensor(
[
[...],
[...],
],
dtype=torch.float32,
)
```
For topology:
```python
expected_src = torch.tensor([...])
expected_dst = torch.tensor([...])
```
Use exact equality for:
* shapes
* integer IDs
* node-type codes
* edge endpoints
* fold values
where deterministic.
Use numerical comparison for floating-point values.
Avoid opaque snapshot testing unless there is a strong reason.
---
# 11. Floating-point tolerances
Use explicit tolerances for numerical parity.
For normal float32 feature calculations, prefer something approximately like:
```python
torch.testing.assert_close(
actual,
expected,
rtol=1e-5,
atol=1e-6,
)
```
unless observed legacy behavior requires a different tolerance.
Do not use unnecessarily loose tolerances.
If a different tolerance is required, explain why.
---
# 12. Characterization vs specification
These tests are characterization tests.
They answer:
> What does the legacy system currently do?
They do not automatically answer:
> What should the redesigned system do forever?
When unusual behavior is discovered, classify it as one of:
```text
compatibility requirement
legacy observation
ambiguous behavior
likely bug requiring later design decision
```
Do not silently "fix" questionable behavior in this task.
---
# 13. Known graph expectation correction
A previous test incorrectly expected four edges for a two-node graph.
The running legacy implementation produced two edges.
Investigate and characterize this behavior explicitly.
If confirmed, the likely topology is:
```text
directed fully connected graph
without self-loops
```
which gives:
```text
num_edges = N * (N - 1)
```
Do not modify legacy code to make it match the old test expectation.
Update the parity test to match actual observed behavior.
Also verify the actual `(src, dst)` pairs and corresponding edge features.
---
# 14. Documentation of ambiguities
Record meaningful discoveries in:
```text
docs/migration.md
```
or a concise dedicated section if appropriate.
Potential ambiguities include:
* exact edge ordering
* single-node graph behavior
* empty graph behavior
* missing ROOT branches
* phi wrapping convention
* negative weights
* object concatenation ordering
* shape behavior for unused global features
Only document ambiguities actually encountered.
Do not expand documentation unnecessarily.
---
# Out of scope
Do not implement or modify:
```text
src/gnn4colliders/data/
src/gnn4colliders/features/
src/gnn4colliders/graphs/
src/gnn4colliders/models/
src/gnn4colliders/training/
src/gnn4colliders/inference/
```
except for existing nonfunctional scaffolding if absolutely required for test discovery, and preferably not at all.
Do not implement:
* new data readers
* new feature builders
* new graph builders
* model code
* training code
* losses
* metrics
* checkpoint handling
* Hydra configuration behavior
* runtime optimizations
Do not refactor legacy code.
Do not modernize legacy code.
Do not run Ruff against the frozen legacy tree.
---
# Validation
Run:
```bash
uv run python -c "import torch, dgl; print(torch.__version__, dgl.__version__)"
uv run pytest tests/parity -v
uv run ruff check tests
uv run ruff format --check tests
```
Then run the full current suite:
```bash
uv run pytest
```
The expected result for Task 3 is:
```text
all characterization tests pass
no DGL tests skipped because DGL is unavailable
```
If a test fails because its expectation disagrees with running legacy behavior:
1. investigate the legacy implementation
2. determine the observed behavior
3. fix the test expectation if the test was wrong
4. document ambiguity if appropriate
Do not modify legacy behavior to satisfy a characterization test.
---
# Git inspection
Before committing, inspect:
```bash
git status
git diff
```
Verify that:
* legacy files are unchanged
* no new production implementation exists
* fixtures are small
* no caches are tracked
* no model checkpoints were added
* no generated DGL files were added unintentionally
* no unrelated changes are included
---
# Completion criteria
Task 3 is complete when:
1. PyTorch and DGL import successfully.
2. Active node-feature behavior is characterized.
3. Graph topology is characterized.
4. Edge features and phi wrapping are characterized.
5. Tracking semantics are characterized.
6. Labels and global-feature behavior are characterized to the extent exercised by the active path.
7. Important ambiguities are documented.
8. All parity tests pass.
9. No DGL-dependent parity tests are skipped because of missing dependencies.
10. Legacy production code remains untouched.
---
# Completion report
Report:
1. files created
2. files modified
3. fixtures created
4. behaviors characterized
5. graph topology observed
6. edge ordering observed
7. numerical tolerances used
8. ambiguities discovered
9. behavior classified as compatibility requirement vs observation
10. validation commands and results
11. anything intentionally deferred
After validation succeeds, create one Git commit containing only Task 3 changes.
Use:
```text
test: characterize legacy data and graph behavior
```
Before committing, inspect the final diff and ensure no unrelated files are included.