Skip to content

Generator contract, shared evaluation layer, and HF-hosted leaderboard - #75

Merged
mkeeler43 merged 24 commits into
mainfrom
feat/generator-contract-overhaul
Sep 9, 2026
Merged

mkeeler43 merged 24 commits into
mainfrom
feat/generator-contract-overhaul

Conversation

@mkeeler43

@mkeeler43 mkeeler43 commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Generator contract, shared evaluation layer, and HF-hosted leaderboard

Replaces 13 near-duplicate evaluate_*.py scripts with one adapter per model
and a shared evaluation package, and makes HuggingFace the single home for
checkpoints, metrics, and the leaderboard.

Large, but split into four commits meant to be reviewed one at a time.
Commit 1 is a pure file move you can skim. The design fits in commit 2.


Why

Every model had a training script and an evaluation script. The evaluation
scripts were ~79% identical — evaluate_cgan_cnn_2d.py and
evaluate_gan_cnn_2d.py differed by 26 lines out of 123, and those 26 lines
were only ever four things: which network class to import, how to build it from
run config, whether to pass conditions, and a model-id string.

So adding a metric meant editing 13 files, and adding a model meant copying one.
A leaderboard can't be built on that.

Training scripts are deliberately untouched in structure. Their variation is
real — 340 to 1894 lines of genuinely different algorithms — so unifying them
would relocate complexity rather than concentrate it, and would cost the
single-file readability the repo is built around.


Review guide

1 · 646617d — Move model packages under engiopt/generators/ (68 files, skim)

Mirrors engibench/problems/<name>/. Pure move: file contents are unchanged
apart from the import paths the move forces (engiopt.<model> →
engiopt.generators.<model>). Git detects all 47 renames, so git log --follow
still works.

Nothing to review here beyond "yes, that's a move."

2 · 35f1041 — What defines a "generator" (5 files, read this one)

The whole design is here. Today a model is defined implicitly, by whatever its
evaluation script happened to do. This commit makes it explicit: a generator is
six facts and two actions.

The six facts a model states about itself. These are plain values, not code:

Fact Example What it buys you
algo_id "gan_2d" The name it appears under everywhere — its folder, its leaderboard row, its HuggingFace repo. One name, so those can never drift apart.
conditional False Whether it listens to the design requirements it is given. Unconditional models are still given them, so a metric can measure that they ignored them.
design_kinds ("2d",) Which problems it can serve. Pointing a 2D model at a 3D problem now fails immediately with a clear message, instead of producing garbage.
checkpoint_files ("generator.pth",) Which files must be present to load it. Missing files fail at load, not halfway through an evaluation.
primary_state_key "generator" Where the weights sit inside the saved file.
output_clip (1e-3, 1.0) The range its designs must stay in, because several simulators are unstable at exactly zero.

The two actions a model must implement:

  • build — given a downloaded checkpoint, reconstruct me. The model
    receives the training run's saved settings and local file paths, and returns
    a working network.
  • _sample — given design requirements, produce designs. This is the only
    genuinely model-specific step, and it is where models really differ: one line
    for a GAN, a 1000-step loop for diffusion, one forward pass per pixel for
    PixelCNN++.

Everything else is done once, centrally: finding and downloading the
checkpoint, picking CPU/GPU, choosing which hyperparameter configuration to
load, seeding for reproducibility, timing how long sampling took, reshaping
outputs, and clamping them to the valid range.

The outcome: a new model is ~45 lines, of which ~10 are unique to it. Before
this, adding a model meant copying a 123-line evaluation script and editing
four scattered places in it. The contract mirrors engibench.core.Problem, so
the two halves of the stack are learned once.

engiopt/utils/all_generators.py finds models by walking the folder, exactly as
engibench/utils/all_problems.py finds problems — BUILTIN_GENERATORS sits
alongside BUILTIN_PROBLEMS.

Checkpoint storage also changes here. Each training run is filed under a
short hash of its own hyperparameters:

{problem}/cfg_{fingerprint}/seed_{n}/     one per configuration
{problem}/seed_{n}/                        the "standard" one

The outcome: a 50-configuration sweep produces 50 separately loadable models
instead of 50 runs overwriting one folder. The plain {problem}/seed_{n} path
is claimed only by a run that used the training script's default settings —
so no amount of hyperparameter searching can change what a bare model name
means. W&B is no longer a checkpoint source at all.

3 · a796025 — Shared evaluation layer (28 files, −1942 lines)

Deletes the 13 eval scripts. Adds:

  • engiopt/evaluation/ — a metric registry declaring each metric's family,
    cost, and ranking direction; an EvaluationContext that computes the
    expensive optimizer pass once for every performance metric (iog, cog,
    fog, viol share one sweep instead of running four); an Evaluator; and
    the leaderboard, published to HF by merging rather than overwriting.
  • engiopt/specs/<problem>/v1.json — frozen, hash-verified evaluation
    contracts, so rows are comparable across people and across time.
  • python -m engiopt.evaluate replaces the deleted scripts.
  • engiopt/generators/_template/ — copy-me starting point for new models.

Metrics are registered functions, not model methods — a metric compares a
generated set against a reference set under a problem, so it belongs to the
comparison:

@register_metric("mmd", family="distribution", cost="cheap", higher_is_better=False)
def mmd(ctx) -> float: ...

Declared cost means a cheap run provably cannot start a simulator.

4 · 2b9c58d — W&B decoupling + four correctness fixes (29 files, scrutinise this one)

These are behaviour changes, not refactors.

Training / W&B. 10 of 14 scripts only saved checkpoints when --track was
on, so training without W&B silently produced nothing on HuggingFace. VQGAN
additionally gated early stopping — real training logic — behind it, so under
default settings its transformer checkpoint was never written at all. Found by a
live run, not by tests.

iog was not an optimality gap. It stored the raw simulated objective while
cog/fog measured against the reference optimum. Verified with an oracle
generator that returns the reference design itself: it now scores iog ≈ 3e-07
where it previously reported the design's raw compliance.

Condition sampling couldn't serve half the problems. photonics2d declares
solver settings that aren't dataset columns; thermoelastic2d encodes boundary
conditions as 65×65 matrices. Scalar conditions now feed the model tensor while
the simulator still receives every condition — the physics is unchanged.

Multi-objective scores leaked. thermoelastic2d has a per-sample weight
spanning 0.0–1.0. Summing or averaging its three objectives meant a purely
structural sample was scored mostly on thermal compliance:

weight sum mean weighted
1.0 (structural only) 42.31 14.10 0.30 ← correct
0.0 (thermal only) 42.31 14.10 42.00

Scalarization is now declared in the eval spec, and the evaluator refuses to
guess
when it's absent.

Gaps ignored objective direction. photonics2d maximizes total_overlap,
so a design that beat the reference scored +0.3 and ranked last. Gaps are now
signed by ObjectiveDirection.

Also removes metrics.metrics() and simulate_failure_ratio() — both dead once
the evaluator landed. (This was the only remaining mypy error in the codebase.)


Verification

76 tests. Metric correctness is asserted by properties that must hold for any
correct implementation — mmd(X, X) == 0, collapsed generators score below
varied ones, a purely structural sample is unaffected by thermal compliance —
rather than by pinning historical numbers.

Verified live against HuggingFace with W&B disabled: trained two
hyperparameter configurations, confirmed they landed in separate packages,
loaded each back by fingerprint, evaluated, pushed the leaderboard incrementally,
and confirmed the first model's row was untouched. VQGAN was exercised
separately as the multi-file, multi-stage case. All test repos deleted after.

Ruff clean; mypy clean.


Migration notes for reviewers

  • The lvae branch will need adapting. Its evaluate_*.py scripts call the
    removed metrics.metrics() and pass W&B artifact arguments that no longer
    exist. That was accepted deliberately rather than maintaining two evaluation
    paths.
  • All checkpoints must be retrained, which was already the plan — the storage
    layout changed and there is no W&B read-fallback.
  • surrogate_model/ is untouched and sits outside the contract; it's neither a
    generator nor evaluated.

Known limitations

  • cog sums across steps and objectives while fog averages objectives — a
    pre-existing inconsistency, left alone rather than changed silently. Worth
    settling alongside the metric-suite work.
  • dpp underflows toward zero on collapsed generators (a 1-epoch GAN scored
    8.9e-22), so it may lose ordering at the bottom of a real zoo. A log-domain
    form would fix it.
  • Leaderboard submissions are internal for now. The Generator contract is
    the submission interface, so opening it later is a policy change, not a
    rewrite.

Update — review round 1 addressed (commits 3a2568e, 810529f)

All 13 of @SoheylM's comments are fixed, each with a threaded reply. Summary comment
below has the full breakdown; the two things worth knowing before re-reading the diff:

1. Evaluation now needs EngiBench from source, and CI pins a commit.
The two specs that would not resolve for Soheyl were not a case of datasets drifting
upstream — they were frozen against EngiBench main, whose photonics2d and
thermoelastic2d read the v1 datasets, while release 0.2.0 still points those two at
v0. On 0.2.0 they sample different data entirely. Specs now record the problem
definition they were frozen against, so that case reports itself instead of surfacing as
a hash mismatch. CI installs EngiBench at a pinned commit; the README says so.

2. Four changes are in the diff that nobody asked for. Flagged so they are not a
surprise: the EngiBench pin above; two extra bug fixes inside the feasibility work
(check_constraints raised on thermoelastic2d, and float64 designs were judged
infeasible against a float32 design space on dtype alone); a pytest job in CI, since the
repo ran only ruff and mypy and the requested spec test had nowhere to live; and the
deletion of two dead helpers this PR had itself added.

Also removed the sweep YAMLs and the per-algorithm notebook that should never have been
committed.

Testing: 136 tests, up from 111. CI green on all four checks.

Known limitation — image conditions

thermoelastic2d models are conditioned on 3 scalars while the simulator receives all 7
conditions, because its four 65x65 boundary masks cannot travel in a dense condition
tensor. Scoring is correct — simulate and optimize get the full conditions — but a
model cannot currently see where the part is held, loaded, or cooled unless it reads
ConditionBatch.dataset itself, as vqgan does for its own preprocessing. First-class
support is being prepared as a follow-up PR; it is purely additive to this contract
(new optional fields, no adapter changes), so it does not block this one.


Update — review round 2: making the board survive being public

All seven remaining comments are fixed, with a threaded reply on each. Two of
them turned out to be the tip of a larger problem, so this round also closes the
gap that would have stopped this leaderboard working as a public one.

The board was a self-report system

A row recorded a checkpoint hash but never a repo, and every default
pointed at IDEALLab. An outside contributor could neither publish weights nor
produce a row anyone could trace back to them, so every number was an assertion
nobody could falsify.

Rows now carry checkpoint_repo / checkpoint_path / checkpoint_revision /
checkpoint_hash — an address, not just a fingerprint — and
python -m engiopt.verify follows it: fetch the package at the recorded
revision
, confirm it still hashes to what was scored, rebuild through the
registered adapter, score it again. Rows land verified=false and unranked
until a runner reproduces them.

Verification publishes its own numbers, not a pass/fail on the submitted
ones. Sampling from one seed on different hardware genuinely produces different
designs — CUDA and CPU draw different values from the same generator state — so
any tolerance loose enough for honest variation is loose enough for real
fudging. Re-scoring sidesteps that; the submitted value becomes a claim reported
as corroborated or not.

The runner is not privileged. Because the address is public, python -m engiopt.verify with no credentials and no --publish is an audit anyone can
run, which is what keeps the runner honest too.

The protocol is public, so three metrics are copyable by construction

The spec names the scored conditions, and the dataset supplies the optimal
design for each. mmd, iog and fog are therefore defined as closeness to
designs anyone can look up. Measured on the real beams2d spec, against the
trained models in this repo:

mmd viol copy_rate cond_sens
lookup table 0.000 0.00 1.00 0.0 flagged memorized
cgan_cnn_2d 0.561 0.92 0.00 0.171
gan_cnn_2d 1.049 1.00 0.00 0.0 unconditional, correctly unflagged
vqgan 0.007 0.46 0.02 0.386

A lookup table beats every trained model on both headline metrics. That is not
an implementation flaw and it cannot be fixed by hiding which rows are scored,
since the whole dataset is public. So the board measures retrieval instead:
novelty / copy_rate catch it, and cond_sens catches a model that ignores
conditions it declares it uses.

Both are diagnostic, with no ranking direction, and that is the load-bearing
choice. Ranking on novelty would seat pure noise in first place — zero means
retrieval, but large means only "unlike the data", which a broken model also
achieves. Closing one gaming vector by opening another is not progress. Flagged
rows are published and left out of the ordering: deleting a submission is
moderation, declining to rank one is a statement about what its number measures,
and only the second scales.

cond_sens is not a new feature so much as a claim this PR had already made —
registry.py declared a conditions metric family and core.py referred three
times to "conditional-adherence metrics", with no such metric existing. The
alternative was deleting the claims.

Other ways a score could drift from the thing scored

  • required_seeds. An entry must cover seeds 1, 2, 3 to be ranked. A count
    would not do: it still permits running twenty and publishing the best three,
    which turns a median into a maximum while every individual row stays honest.
  • ROW_KEY gains checkpoint_repo. Two contributors who both train
    cgan_cnn_2d on default hyperparameters and seed 1 produced the same key and
    different weights, so either one's push would silently replace the other's
    verified row.
  • A silent skip. --generators gan_cnn_2d vqgan --config-fingerprints gan_cnn_2d:6293adb3 scored gan_cnn_2d and never mentioned vqgan — the empty
    fingerprint list meant the loop body never ran, producing no row, no load
    attempt and no error. Found by running it, not by a test.

Verification

255 tests, up from 201; CI green on all four checks with no skips.

Exercised against the live Hub rather than fakes: a lookup table scored on the
real beams2d spec (copy_rate=1.00); four published checkpoints loaded and
scored through the contract, including VQGAN's multi-file case; the documented
diffusion_2d_cond --seeds 1 2 3 run produced three rows collapsing into one
entry with n_seeds=3; all four committed specs still reproduce their frozen
digests; and 143 published packages were checked to confirm the new
incomplete-package refusal rejects none of them.

New docs: LEADERBOARD.md — the trust model, the flags, and
the design of the v2 spec that would actually close the copying hole rather
than price it (scoring on off-dataset conditions, which needs an optimizer run
per condition and so is not built here).

Still open, deliberately

Migration scope. Ten of the fourteen registered generators have no published
checkpoint. --list-generators --check-availability now reports that per
generator instead of implying all fourteen are usable — but reporting is not
choosing between migrating them, keeping a fallback, and narrowing the registry.
That is a project decision, so #73 should stay open.

🤖 Generated with Claude Code

mkeeler43 and others added 5 commits July 24, 2026 13:56
Mirrors engibench/problems/<name>/ so the two halves of the stack are laid out
the same way. Pure move: file contents are unchanged apart from the import
paths the move forces (engiopt.<model> -> engiopt.generators.<model>).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ackend

engiopt/core.py defines what a model must provide to be evaluated: a few
declarative attributes plus build() and _sample(). Mirrors engibench/core.py.
engiopt/utils/all_generators.py mirrors engibench/utils/all_problems.py.

checkpoint_store drops the W&B read-fallback and the write-only run-scoped
path, which no loader ever read. Checkpoints are now filed per hyperparameter
configuration at {problem}/cfg_{fingerprint}/seed_{n}, with the canonical
{problem}/seed_{n} claimed only by default-hyperparameter runs so a sweep
cannot redefine what a bare model name means. Evaluation metrics can be
attached to a package, keeping a checkpoint self-describing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 13 evaluate_*.py scripts were ~79% identical; they differed only in which
network to import, how to build it, whether to pass conditions, and a model id
string. Each model now supplies a ~45-line adapter and everything else is
shared:

- engiopt/evaluation/: a metric registry declaring each metric's family, cost,
  and ranking direction; an EvaluationContext that computes the expensive
  optimizer pass once for every performance metric; an Evaluator; and the
  leaderboard, published to HuggingFace by merging rather than overwriting.
- engiopt/specs/<problem>/v1.json: frozen, hash-verified evaluation contracts
  so rows are comparable across people and across time.
- python -m engiopt.evaluate replaces the deleted scripts.
- engiopt/generators/_template/ is the copy-me starting point for new models.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Training scripts:
- 10 of 14 only saved checkpoints when --track was on, so training without W&B
  silently produced nothing on HuggingFace. VQGAN additionally gated early
  stopping -- real training logic -- behind it, so under default settings its
  transformer checkpoint was never written at all.
- Each run is now filed under its own hyperparameter configuration via
  checkpoint_identity(args).

Evaluation correctness:
- iog stored the raw simulated objective rather than a gap against the
  reference optimum, unlike cog and fog. An oracle generator returning the
  reference design now scores ~0 where it previously reported raw compliance.
- Condition sampling could not serve photonics2d (whose conditions include
  solver settings absent from the dataset) or thermoelastic2d (whose boundary
  conditions are 65x65 matrices). Scalar conditions now feed the model tensor
  while the simulator still receives every condition.
- Multi-objective gaps were summed or averaged, letting an objective a sample
  does not prioritize dominate its score. Scalarization is declared in the eval
  spec, and the evaluator refuses to guess when it is absent.
- Gaps now respect ObjectiveDirection, so photonics2d -- which maximizes
  total_overlap -- is no longer ranked in reverse.

metrics.metrics() and simulate_failure_ratio() are removed: both became dead
once the evaluator landed, and a second evaluation path only invites drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two failures on the PR, both surfaced only by CI because it runs the whole
repository with the latest ruff while local runs used 0.11.

- surrogate_model/run_pe_optimization.py passed active_wandb_run to
  resolve_checkpoint_reference, which lost that parameter when W&B stopped
  being a checkpoint source. Caught by mypy; the loader now takes only a
  model reference.
- ruff 0.16 formats Python inside Markdown fences, which reformatted the two
  new docs, and select = ["ALL"] means new rules enable themselves on every
  upgrade. PLR0917 is ignored for the same reason PLR0913 already is, and
  PLC0415 because lazy imports are used deliberately to keep module import
  cost down and avoid cycles. The stale PLR0913 noqa directives those rules
  made redundant are removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mkeeler43
mkeeler43 requested a review from SoheylM July 24, 2026 12:37

@SoheylM SoheylM left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generator interface and shared evaluator make sense to me, and removing the duplicated evaluation scripts is a good change. I am requesting changes because several parts of the documented evaluation flow do not work end to end yet. I have left the concrete code issues inline.

There are four cross-cutting points as well:

  • Checkpoint migration: Issue #73 says to remove the migration playbook after the W&B migration is complete, but the issue is still open and this PR removes both the playbook and W&B loading. On 2026-08-03, IDEALLab/engiopt-cgan-cnn-2d contained only .gitattributes and README.md; IDEALLab/engiopt-vqgan and IDEALLab/engiopt-gan-cnn-2d did not exist. The README's cGAN evaluation command cannot currently load beams2d/seed_1. Please publish and verify the required HF packages before removing the old path, or keep the migration support until that is done.
  • Documentation: The README still describes separate training and evaluation scripts, says the power-electronics optimizer accepts legacy W&B artifacts, and links to the deleted migration playbook. run_pe_optimization.py still uses W&B references in its example and argument descriptions, while Generator.resolve_checkpoint and the evaluation CLI help still list wandb as a model source. These should match the final migration decision.
  • Lint CI: With Ruff 0.15.11, ruff check . reports 20 I001 errors on this commit. The GitHub job is green because it runs ruff check --fix in the temporary checkout without checking whether files changed. Please commit the import fixes and run plain ruff check . in CI. Pinning Ruff would also make the check stable.
  • Workshop integration: The open DCC workshop branch still imports engiopt.cgan_cnn_2d... in notebook_helpers.py, which is used by the participant notebooks. Please update that branch before these changes reach participants, or keep a temporary import alias if the old path must remain supported.

Verification on the PR head: 76 tests passed with 3 warnings; two of four committed specs failed to load; the thermoelastic adapter shape error and mixed-ranking result were reproduced; the documented cGAN evaluation loaded no model and exited with status 0; git diff --check passed.

Comment thread engiopt/evaluation/spec.py Outdated
Comment thread engiopt/evaluation/spec.py Outdated
Comment thread engiopt/core.py Outdated
Comment thread engiopt/evaluation/leaderboard.py Outdated
Comment thread engiopt/evaluation/leaderboard.py Outdated
Comment thread engiopt/evaluate.py Outdated
Comment thread engiopt/core.py
Comment thread engiopt/evaluation/context.py Outdated
Comment thread engiopt/checkpoint_store.py Outdated
Comment thread engiopt/generators/cgan_cnn_2d/sweep_hp_tuning_beams2d.yaml Outdated
mkeeler43 and others added 2 commits August 5, 2026 15:30
…d integrity

Resolves the thirteen review comments on the generator-contract PR. Three
were correctness bugs; the rest close gaps that would have corrupted a
shared leaderboard once more than one person wrote to it.

Condition schema. `n_conds` counted every entry of `problem.conditions_keys`,
including the array-valued and solver-only ones that never reach a dense
condition tensor. A cGAN built for thermoelastic2d's seven declared conditions
received three columns and failed on reshape. `transforms.condition_keys` is
now the single definition of what a model consumes, used by training,
loading, and sampling alike, and each checkpoint records the schema it was
trained under so a model keeps loading after a problem gains a condition.
Handing a model conditions from a different schema now raises rather than
silently conditioning on the wrong numbers.

Eval specs. The digest covered only the drawn indices, so an upstream edit to
a condition value changed every score without tripping the check; it now
covers the condition names and values too. Specs also record and load a
pinned dataset revision, since an EngiBench version does not identify a
dataset. All four specs are re-frozen; photonics2d and thermoelastic2d, which
no longer resolved, now do.

Feasibility. `viol` only existed for problems with a `volfrac` or `volume`
condition, returned NaN for photonics2d, and skipped a legitimate target of
0.0. It is now `problem.check_constraints` plus a volume budget the spec
names explicitly. Two further defects surfaced while implementing it:
`check_constraints` raised on thermoelastic2d, whose boundary matrices arrive
from HuggingFace as lists, and float64 designs were judged infeasible against
a float32 design space on dtype alone -- which marked that problem's own
dataset optima as violations.

Leaderboard integrity. Ranking pooled different hyperparameter configurations
and different spec versions into one score; it now groups by both. A failed
download read as an empty board, so a transient error could turn the next
publish into a deletion; only genuine not-found cases are caught now.
Publishing is conditional on the revision it read and re-merges on conflict,
rather than overwriting a concurrent publisher. Rows carry the checkpoint
revision, a hash of the weights themselves, and the code version, so a score
is traceable to the model that earned it.

Usability. `local_model_dir` reaches `from_pretrained` and the CLI, so the
advertised local source is usable. The CLI exits non-zero when nothing could
be evaluated, instead of reporting an empty batch as success. Sample timing
synchronises the device before stopping the clock. Each uploaded checkpoint
package carries metadata describing itself rather than a path the run may not
have written.

Testing. The repository ran only ruff and mypy, so a pytest job is added
along with tests for the condition schema, publishing races, local package
loading, and a network-marked check that every committed spec still resolves
against its pinned dataset.

Also removes the sweep configs and the per-algorithm notebook that should not
have been committed, and two dead helpers this branch had added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review-response commit, addressing what CI surfaced.

The two specs Soheyl reported as broken were never a case of the dataset
drifting upstream. They were frozen against EngiBench main, whose photonics2d
and thermoelastic2d read the v1 datasets, while the 0.2.0 release on PyPI
still points those two at v0. Sampling a different dataset is why the digests
could not match, and why CI could not reproduce them either.

So the specs now record the problem definition they were frozen against, not
just the dataset: the full condition list plus the EngiBench commit. That is
checked before the digest, which turns the failure from two hashes that will
never agree into a message naming the conditions and datasets that differ. CI
installs EngiBench from git at a pinned commit, since no release carries the
v1 problems yet, and the README says so for anyone running an evaluation.

Two of my own breaks are fixed here too. A Python block inside
CONTRIBUTING_A_MODEL.md was misformatted, which the newer ruff in CI checks and
my older local one does not. And the leaderboard publishing tests constructed
huggingface_hub errors in a way that only works on older releases, where
`response` is optional and unread; they now build a real requests.Response,
which every version accepts. Verified against hub 1.26.0 as well as 0.34.4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mkeeler43

Copy link
Copy Markdown
Contributor Author

Thanks for this — the review caught one genuine correctness bug (the condition count) and
several things that would have quietly corrupted the leaderboard once more than one person
was writing to it. All 13 are addressed, and each has a threaded reply above.

Fixes for your comments

Correctness

Leaderboard integrity

Usability

Changes beyond your comments, and why

Four things are in this branch that you did not ask for. Flagging them so nothing in the
diff is a surprise:

  1. EngiBench must now come from git, and CI pins a commit. Not a preference — no PyPI
    release has the photonics2d/thermoelastic2d v1 problems, so on 0.2.0 those two specs
    cannot resolve at all. This is the root cause of what you reported in Add GAN 1D implementation #1. See that reply;
    it is the change most worth a second opinion.
  2. Two extra bug fixes inside made algos and metrics compatible with eval_model.py files #11. Implementing feasibility surfaced that
    check_constraints raised on thermoelastic2d (HuggingFace returns its boundary
    matrices as lists, which EngiBench's bound checks cannot compare), and that float64
    designs were judged infeasible against a float32 design space on dtype alone — which
    marked that problem's own dataset optima as violations. Both are fixed and tested here
    because viol is wrong without them. The list/array crash may deserve a fix in EngiBench
    too.
  3. A pytest job in CI. The repo ran only ruff and mypy, so the spec test you asked for
    in Add GAN 1D implementation #1 had nowhere to live. 136 tests now run on every push (111 before).
  4. Two dead helpers deleted — _discover_reference_files (a near-duplicate of live code,
    no callers) and problem_id_of (public, undocumented, unused). Both were added by this
    PR, so this is removing our own dead weight rather than unrelated tidying.

Known limitation, not addressed here

thermoelastic2d models are conditioned on 3 scalars while the simulator receives all 7
conditions, because the four 65x65 boundary matrices cannot travel in a dense condition
tensor. Scoring is correct — simulate and optimize get the full conditions — but a
model cannot currently see where the part is bolted or heated unless it reads
ConditionBatch.dataset itself, as vqgan does for its own preprocessing. Giving image
conditions first-class support in the contract is worth a follow-up; it is a modelling
capability rather than a bug, and it predates this PR.

@SoheylM SoheylM left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the detailed follow-up. Several original issues are fixed, including local loading, exit status, timing, checkpoint metadata, and grouping configurations into separate entries. The remaining inline findings still affect reproducibility or leaderboard correctness, so I am keeping Changes Requested.

The migration and repository cleanup also remain unfinished. Issue #73 is open, the public HF repositories still do not provide the documented checkpoint packages, and the README links to the deleted migration playbook while retaining obsolete W&B source instructions. ruff check . reports 20 import-order errors; CI stays green because it runs with --fix. The DCC workshop branch also still imports the old pre-move module path.

Verification on 810529f: the local suite reports 134 passed and 2 skipped with PyPI EngiBench; all four spec-resolution tests pass when the actual pinned EngiBench source is imported; git diff --check passes; the worktree is clean.

Comment thread tests/test_specs.py Outdated
Comment thread engiopt/evaluation/spec.py Outdated
Comment thread engiopt/core.py Outdated
Comment thread engiopt/generators/vqgan/adapter.py Outdated
Comment thread engiopt/generators/vqgan/adapter.py
Comment thread engiopt/evaluation/leaderboard.py Outdated
Comment thread engiopt/evaluation/leaderboard.py Outdated
Comment thread engiopt/evaluate.py
Comment thread engiopt/evaluation/context.py
…time conditions

Nine follow-up comments, eight of them correct and four describing defects the
previous round introduced.

The important one: CI was green because it skipped the two specs the job exists
to check. The pinned EngiBench install was a no-op -- the git build reports the
same version string as the PyPI one, so pip considered the requirement already
satisfied and kept the wrong copy. It is now a --force-reinstall with an
explicit assertion that the pin took effect, and a spec frozen against a
different EngiBench definition fails rather than skipping, since skipping would
go green exactly when the pin has broken. `freeze()` also captures the EngiBench
source revision itself, so it reproduces the values already committed in the
specs instead of relying on how they were written by hand.

Conditions are now presented to a model the way its training data looked. The
evaluator supplies every scalar condition, unscaled; a model that trained on a
subset, a different order, or a rescaling gets that view reconstructed from what
its checkpoint recorded. Previously the contract demanded exact equality, which
rejected VQGAN outright -- it drops constant columns during training -- and
VQGAN worked around it by re-deriving its preprocessing from the evaluation
sample. That was silently wrong: fifty evaluation rows do not have the training
split's mean, and "columns that never vary" is a different set in fifty rows
than in forty thousand. Training now records the statistics it fitted, sampling
replays them, and the adapter's bespoke preprocessing is deleted rather than
patched. Any two-stage model that preprocesses conditions gets the same
treatment without writing code for it.

Feasibility no longer rides along with the optimizer pass. It describes the
design as generated, so it is judged before any solver runs -- which means it
still reports when the optimizer refuses to start from an invalid design, the
case where the answer matters most, and asking for `viol` alone now costs a
constraint check instead of a full optimization. It is a cheap metric as a
result.

Smaller corrections: ranks restart within each problem and spec version rather
than running across the whole table; a private repo is no longer mistaken for an
empty leaderboard, and a repo whose leaderboard file is merely absent keeps its
revision so two first publishers cannot overwrite each other; `--skip-existing`
compares the checkpoint hash, so retrained weights are re-scored; and the
package hash covers every weight file present rather than only the declared
ones, since VQGAN loads a condition encoder it cannot declare.

Not adopted: pinning the EngiBench revision as a hard requirement at resolution,
or adding it to leaderboard ranking identity. Recording it is right and is done;
enforcing it would break every contributor whose EngiBench differs by any
commit, and ranking on it would partition the board per build so that two models
could not be compared unless scored on byte-identical EngiBench.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mkeeler43

Copy link
Copy Markdown
Contributor Author

All nine addressed; eight adopted, one half-declined with reasoning (B).

The one that mattered most is A: you were right that CI was green because it skipped
the two specs it exists to verify. My EngiBench pin was a no-op — the git build reports the
same version string as the PyPI build, so pip kept the wrong copy. I had reported this PR
as "CI green" on that basis, which was wrong. It is now a --force-reinstall with an
explicit assertion that the pin took effect, and a spec mismatch fails rather than skips.

Four of these were defects the previous round introduced (C, F, G, and A itself). Two of
the fixes made the code smaller rather than larger:

  • viol is now cheap — decoupled from the optimizer pass, so it survives an optimizer
    that rejects an invalid design, and costs a constraint check rather than a full run.
  • VQGAN's bespoke preprocessing is deleted, not patched. The contract now replays
    whatever preprocessing a checkpoint recorded, so any two-stage model gets the pathway
    without writing code for it. This also fixed a silent correctness bug: VQGAN was
    refitting its condition normalization on the 50 evaluation rows and discarding the
    training statistics, so the network saw a scale it was never trained on.

Where I would push back — B, second half only. Recording the EngiBench revision is
right and is done. Enforcing it at resolution would refuse to evaluate for any contributor
whose EngiBench differs by a commit, and putting it in ranking identity would partition the
board per build, so two models could not be compared unless scored on byte-identical
EngiBench. Traceability is what the concern justifies, and the revision is recorded in the
spec with code_version already on every row. Happy to add engibench_version to the
provenance columns as well if you want it visible per row — that is the useful half
without the cost. Say the word and I will.

153 tests, up from 136.

…ion set

Projecting the evaluator's conditions onto a checkpoint's recorded schema is
right when the dropped column is constant -- it carries no information, which is
why training dropped it. beams2d's overhang_constraint is 0 across all fifty
test cases, so VQGAN loses nothing the other models have.

That is a property of today's data, not a guarantee. A column constant in a
training split can vary in a future evaluation set, and then the model is blind
to a requirement its competitors can see while nothing says so. Not an error: a
model is allowed to ignore conditions, and the conditional-adherence metrics
exist to expose exactly that. But it should never be silent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread engiopt/checkpoint_store.py Outdated
Comment thread engiopt/generators/cgan_1d/adapter.py
Comment thread engiopt/evaluation/leaderboard.py Outdated
@SoheylM

SoheylM commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

I rechecked the latest head. The condition handling, VQGAN preprocessing, feasibility, ranking, Hub publishing, and CI spec verification now look correct. The pinned suite passes all 155 tests without skips.

One documentation cleanup from the previous review summary remains. This PR removes the per-model evaluation scripts, removes W&B as a checkpoint source, and deletes docs/checkpoint_migration_playbook.md, but the README still says there are two scripts per algorithm, still lists legacy W&B checkpoint references, and still links to the deleted playbook. run_pe_optimization.py and one Generator.resolve_checkpoint docstring retain the removed W&B source as well. Please update those references. W&B experiment tracking and dashboard documentation can remain; only the checkpoint-loading claims are obsolete.

The documented default HF repository also still has no checkpoint package. Before presenting the evaluation command as runnable, please either publish that package, retain the migration path until issue #73 is complete, or state clearly that pretrained checkpoints are not available yet.

The remaining inline comments are limited to checkpoint identity, replaying training-fitted normalizers, hashless --skip-existing behavior, and runtime EngiBench provenance. I am not requesting further VQGAN work, a visual leaderboard, repository-wide Ruff cleanup, or workshop-branch changes in this PR.

mkeeler43 and others added 5 commits August 10, 2026 13:56
Three ways a package could misrepresent itself, all of which reach the
leaderboard:

`metrics.json` was inside the content hash. It is written by
`publish_checkpoint_metrics` into the package it describes, so attaching a
score changed the identity of the thing scored: the next `--skip-existing`
run would see a new hash, re-evaluate, rewrite the metrics, and change the
hash again. No package has metrics attached yet, so this was latent. It is
now in DESCRIPTIVE_FILES alongside run_config.json and metadata.json, which
also stops it being served back as a loadable checkpoint file.

A multi-stage model that dies partway leaves a package that looks complete.
VQGAN uploads after each of its three stages so a crash does not lose the
earlier work, which means an interrupted run publishes a run_config.json and
a metadata.json with no usable weights -- indistinguishable, from the Hub,
from a finished run. 4 of the pool's 102 VQGAN runs ended this way, holding
vqgan.pth but no transformer.pth. `package_complete` is the distinction, and
the load error now says which stage it reached and what the package holds.

The same gap applies to the canonical path, which an intermediate stage of a
default-hyperparameter run could claim. That publishes the bare model name
pointing at something unloadable for as long as the remaining stages take --
hours, for VQGAN -- and permanently if the run dies first. Only a finished
run may claim it now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VQGAN records the statistics it normalized its conditions by, and the
contract replays them. cgan_1d, cgan_bezier, gan_1d, gan_bezier, and
diffusion_1d did not: they rebuilt their normalizers from whatever
`problem.dataset["train"]` currently holds, at load time. cgan_1d's docstring
said so outright.

Those bounds are as much a part of the model as its weights -- a design
decoded against different bounds is a different design -- but `Normalizer` is
a plain class rather than an nn.Module, so nothing ever put them in the state
dict. The result is that the same checkpoint, with the same content hash,
produces different designs after a dataset revision, with nothing to show it
happened. The eval spec's pinned revision does not help: it governs the test
conditions, not the training split these read.

`normalizer_state` / `load_normalizer_state` record and restore the fitted
bounds, duck-typed on min_val/max_val/eps so the five identical copies of
`Normalizer` do not have to be unified first -- that is a change to training
code this does not need. A checkpoint with nothing recorded is left to fit
from the dataset exactly as before, which is what those runs really did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`already_evaluated` treated a row with no `checkpoint_hash` as a match. You
are right that it should not: an absent hash means the identity of those
weights is unknown, and unknown is not equal. One hashless row could suppress
every future evaluation of that configuration and seed, retrained weights
included, until somebody deleted it by hand. Since this schema has never
shipped on `main` there is no historical board whose re-evaluation cost the
leniency was protecting, so the legacy-row test is updated rather than kept.

Rows now also record `engibench_version`: the EngiBench that ran the
evaluation, as opposed to the one the spec was frozen against. This is the
compromise from the earlier thread -- recorded, not enforced, and not part of
ranking identity, so contributors on a slightly different commit can still be
compared while the difference stays visible.

Deriving that version had the bug you predicted. `git -C` searches parent
directories, so a wheel unpacked into a virtualenv inside a checkout would
report the *enclosing* repository's sha as EngiBench's -- a wrong sha, worse
than none. It now reads PEP 610 `direct_url.json` first, which is where a
non-editable `pip install git+...` records the commit and is the case CI
hits, and only accepts a git answer when the repository root is the package's
own directory and the path is not an installed-package path.

Separately, `--config-fingerprints` was applied as a cross product over
generators. A fingerprint hashes one algorithm's hyperparameters, so
evaluating two models against a flat list asked for packages that were never
going to exist and buried the run in load failures that were not failures.
`algo:fingerprint` scopes an entry to its owner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`{problem_id}/seed_{seed}` is what a bare model name resolves to: what
`from_pretrained(problem, seed=1)` reads and what the README's evaluation
command uses. Only a run using the training script's default hyperparameters
writes it, and a sweep has no such run, because every arm sets at least one
flag.

The consequence is currently live on the Hub. cgan_cnn_2d and gan_cnn_2d have
138 published packages each and not one canonical package for any problem, so
the documented `--generators cgan_cnn_2d --seeds 1` fails with a bare "not
found". vqgan and diffusion_2d_cond do have canonical packages, but only
because their swept center configuration happens to coincide with the script
defaults -- luck rather than design, and precisely what makes the bare model
name unreliable.

Two changes. A failed canonical resolution now lists the configurations that
do exist, so the reader learns the package was never written rather than
suspecting a broken path. And `python -m engiopt.promote_checkpoint` copies a
chosen configuration to the canonical path, so an existing sweep arm can
become the default without spending a few hundred GPU-hours retraining a
model we already have. The promoted package keeps its own
`config_fingerprint`, so a leaderboard row still records which configuration
earned the score; only the address changes. An incomplete package is refused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… mean it

The README still advertised W&B artifact refs for the power-electronics
optimizer and linked docs/checkpoint_migration_playbook.md, which this branch
deletes; `run_pe_optimization.py` still described its arguments as W&B
artifacts and used one in its usage example; and `Generator.resolve_checkpoint`
still listed `wandb` as a model source. None of those paths exist.

The README also documented no leaderboard workflow at all -- `--push-to`,
`--skip-existing`, and `--attach-metrics` were undocumented, as was the fact
that a sweep leaves the canonical path empty. Both now have a section, along
with how an outside contributor submits a model.

The ruff job ran `ruff check --fix`, which repairs violations inside the
runner and then reports success, so the job was green on code that does not
pass. Dropping `--fix` makes it mean what it says, and the 20 pre-existing
I001 violations it was hiding are fixed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mkeeler43

Copy link
Copy Markdown
Contributor Author

Thanks — all four of the latest round are addressed, each in its own thread. Summary here,
plus the cross-cutting items, plus three findings from verifying against the finished
checkpoint pool that you should know before re-reviewing.

The four inline items

Item Outcome
1 metrics.json inside the checkpoint hash Fixed. Also found a second instance in _discover_package_files
2 Five adapters refitting normalizers at load Fixed for all five; bounds recorded and replayed
3 Hashless rows treated as a match Took your version; legacy-row test updated
4 Runtime engibench_version, and the git -C bug Both done; the sha logic now uses PEP 610 direct_url.json first

Cross-cutting

Checkpoint migration / issue #73. The precondition you set is now met, and I checked
rather than assuming. All four architectures load from HF and score end to end:

beams2d  vqgan        mmd=0.0103  viol=0.36    (98 of 102 runs complete)
beams2d  cgan_cnn_2d  mmd=0.0277  viol=0.38

IDEALLab/engiopt-cgan-cnn-2d and -gan-cnn-2d hold 138 packages each,
-diffusion-2d-cond 139, -vqgan 102, -constrained-plvae-2d 54. So the W&B path is no
longer load-bearing for anything, and I think #73 can close with this PR rather than block
it. If you would rather keep it open until the leaderboard is actually published, that is
reasonable too — but the checkpoints themselves are there and verified.

Lint CI. Confirmed your diagnosis exactly: ruff check . reported 20 I001 on the
old head, and the job was green only because it ran --fix inside the runner. --fix is
gone and the 20 violations are committed. ruff check . is now clean at the un-fixed
setting CI uses.

Documentation. README no longer advertises W&B artifact refs or links the deleted
migration playbook; run_pe_optimization.py's argument docs and usage example are updated;
resolve_checkpoint no longer lists wandb as a source. The README also gained a
leaderboard section, which was previously undocumented — --push-to, --skip-existing,
--attach-metrics, and how an outside contributor submits a model.

DCC workshop branch. Here I would push back on the remedy rather than the observation.
The import break is real, but that branch is being overhauled separately and its notebooks
are changing substantially for IDETC, so a temporary import alias would be dead weight
added to main to serve a branch that will not use it by the time participants see it.
I would rather fix the import as part of that overhaul, and I am happy for that to be a
stated dependency of the workshop rather than of this PR.

Three findings from verifying against the pool

1. Nothing in a sweep writes the canonical path — the documented command is broken.
{problem}/seed_{seed} is what --seeds 1 resolves, and only a default-hyperparameter run
writes it. Every sweep arm sets at least one flag, so cgan_cnn_2d and gan_cnn_2d have
138 packages each and zero canonical packages. The README command fails for both.
(vqgan and diffusion_2d_cond do have them, but only because their center configuration
coincidentally equals the script defaults — luck, not design.)

Two changes: the resolution error now lists what the repo actually holds instead of a bare
"not found", and python -m engiopt.promote_checkpoint promotes an existing arm to the
canonical path rather than spending a few hundred GPU-hours retraining a model we have.

2. A correction to something I said earlier. On 2026-08-06 I reported that VQGAN's
packages held nothing but cvqgan.pth and attributed it to walltime exhaustion. That was
wrong: I sampled the Hub mid-sweep, and VQGAN uploads after each of its three stages, so a
run still training is indistinguishable from one that died. The pool finished at the
original epoch settings, 98 of 102 complete. I had staged an epoch-budget change on the
back of that diagnosis and have dropped it.

The 4 that genuinely did not finish are still the reason the completeness marker is worth
having: they hold vqgan.pth and no transformer.pth, and without package_complete they
look exactly like finished packages until you try to load one.

3. mypy in CI is passing vacuously. Not fixed here, flagging it because it is the
same shape as the two you already caught. The job installs only mypy numpy pytest, so
with torch/gymnasium/diffusers absent the ignore_missing_imports = true overrides turn
every library type into Any. Reproduced both ways:

  • CI's exact environment: Success: no issues found in 74 source files
  • Same commit with the real dependency stack: 61 errors in 10 files

All 61 predate this PR (63 on the previous head, so this branch reduces it by 2). Fixing it
means either installing the stack in that job or committing to burning down 61 pre-existing
errors, so I have deliberately left it rather than expand this PR further. Worth its own
issue.

Also worth flagging, deliberately not changed

dpp is closer to its floor than it looks. The determinant is a product of n eigenvalues
below 1, so it decays exponentially: on the two real checkpoints above it returned 1.3e-29
and 8.4e-15, and on Gaussian designs at sigma=10 it hits exactly 0.0 from around
n=200, at which point every model ties while the column still looks like a score. Two
training scripts already work around this — cgan_cnn_3d and cgan_vae both log
np.log(max(final_dpp, tiny)).

I had a log_dpp metric written and dropped it from this PR: at the n=50 the specs draw,
dpp is nonzero and ordinally valid, so nothing here is actually broken by it, and adding a
metric no spec references would have shipped dead code into an already large PR. Suggest a
spec v2 adopting it as a separate change — happy to open that if you agree.

Verification

CI on e6231bc: 201 passed, 0 skipped — counts rather than the badge, since the skip
was the failure mode last round; the four spec-resolution tests run for real. Locally:
ruff check . and ruff format --check clean without --fix, pre-commit including pyright
green, worktree clean. Every claim above about the Hub was checked against the live repos
rather than inferred.

@SoheylM SoheylM left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the substantial follow-up. The earlier concerns around fitted preprocessing, null checkpoint hashes, EngiBench provenance, Ruff CI, and obsolete W&B documentation have been addressed. I reran the complete test and quality suite against the pinned EngiBench revision: all 201 tests pass, and Ruff, formatting, pre-commit, and pyright are clean. I also tested the documented evaluation path and checkpoint-package behavior directly.

The remaining inline comments concern the current checkpoint publishing and loading contract, reproducibility metadata, spec creation, and documentation. One cross-cutting migration point also remains: the shared registry exposes 14 generators, but the default HF repositories currently exist for only cgan_cnn_2d, diffusion_2d_cond, gan_cnn_2d, and vqgan. Because this PR removes W&B checkpoint loading repository-wide, please either migrate the remaining registered generators, retain a fallback for the unmigrated ones, or explicitly narrow what the shared evaluator advertises as available. Issue #73 should remain open until the chosen scope is complete.

The README sentence saying that each algorithm usually has separate training and evaluation scripts should also be removed, since evaluation is now shared. I am treating the missing device override on Apple silicon as a follow-up rather than a blocker for this PR.

Comment thread engiopt/checkpoint_store.py
Comment thread engiopt/checkpoint_store.py
Comment thread engiopt/checkpoint_store.py
Comment thread engiopt/evaluation/spec.py
Comment thread engiopt/evaluation/evaluator.py Outdated
Comment thread README.md Outdated
Comment thread CONTRIBUTING_A_MODEL.md Outdated
mkeeler43 and others added 5 commits August 11, 2026 14:40
…ieval

An internal leaderboard can assume its rows were produced in good faith by
someone with no reason to fabricate them. A public one cannot assume either,
and the two failure modes need separate answers.

Fabrication. A row recorded a checkpoint hash but never a repo, and every
default pointed at IDEALLab, so a contributor could neither publish weights nor
produce a row anyone could trace. Rows now carry repo, path, revision and hash;
`python -m engiopt.verify` follows that address, checks the package still
hashes to what was scored, and re-scores it. Rows land unverified and unranked
until a runner reproduces them, and because the address is public the runner is
just the first auditor rather than a trusted oracle. Verification publishes its
own numbers instead of a pass/fail, since one seed on different hardware draws
different designs and any tolerance loose enough for that is loose enough for
fudging.

Gaming. The protocol is public: the spec names the scored conditions and the
dataset supplies each optimal design, so `mmd`, `iog` and `fog` are by
definition maximised by returning them. Measured on beams2d, a lookup table
scores mmd=0.000 and viol=0.00 against the trained cGAN's 0.561 and 0.92 -- it
wins every headline metric. `novelty`/`copy_rate` catch it (copy_rate=1.00),
and `cond_sens` catches a model that ignores conditions it declares. Both are
diagnostic, not ranked: ranking novelty would seat pure noise in first place,
which is closing one vector by opening another. Flagged rows are published and
left out of the ordering, because deleting a submission is moderation and
declining to rank one is a statement about what its number measures.

Also closes the ways a score could drift from the thing scored:
`required_seeds` stops a best-three-of-twenty median; `ROW_KEY` gains
`checkpoint_repo` so two contributors' identically-configured models cannot
overwrite each other; the content hash now covers fitted preprocessing, which
changes outputs but left the hash untouched; a complete upload clears stale
files, while a staged one deliberately does not; an incomplete package is
refused before file discovery rather than while explaining a missing file; and
`code_version` stops reporting an enclosing repository's sha as its own.

Docs: LEADERBOARD.md for the trust model and the copying hole that a v2 spec
scored on off-dataset conditions would actually close. README and
CONTRIBUTING corrected -- the documented commands now name packages that exist,
`--list-generators --check-availability` reports which of the fourteen
registered models have published weights, and the guidance to re-fit
preprocessing from `conditions.dataset` is replaced by recording it in the
checkpoint.

51 new tests. Verified against the live Hub: a real cgan_cnn_2d checkpoint
scores end to end with its address recorded and cond_sens=0.171, and all four
committed specs still reproduce their frozen digests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… does

Two things the live run turned up.

`--generators gan_cnn_2d vqgan --config-fingerprints gan_cnn_2d:6293adb3`
scored gan_cnn_2d and never mentioned vqgan. Scoping the only entry to another
algorithm left vqgan with an empty fingerprint list, so the loop body never
ran: no row, no load attempt, no error. A leaderboard quietly missing an
entrant is worse than one reporting a load failure, so a model no entry is
scoped to now falls back to its canonical checkpoint.

Ruff 0.16 formats Python blocks inside Markdown and CI runs it repo-wide,
while the local check here was scoped to engiopt/ and tests/. Reformatted, and
the lesson is the general one: run the command CI runs, not a subset of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cheap` in this registry means "cannot reach a simulator", which cond_sens
honours -- but it draws the batch twice, so selecting it doubles generation
cost. Free for a GAN, noticeable for diffusion, and unpleasant for a model that
samples a pixel at a time. Documented alongside the --metrics escape hatch,
which was already supported and unmentioned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ResolvedCheckpoint.reference` was added alongside repo_id/package_path on the
assumption verification would want a formatted hf:// string; it builds that
inline instead. An unused accessor on a core dataclass is a maintenance claim
with no payer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`FLAG_INCOMPLETE_SEEDS` was declared and never raised: seed coverage is a
property of the entry, so `eligible_rows` drops those rows during ranking
rather than flagging them individually. A flag constant nobody emits reads like
a feature that exists.

`_as_float` had been written twice, once per module, for the same job with
slightly different blank handling. Promoted to `as_metric_value` in submission,
where the NaN rule belongs -- a metric that did not run must be neither flagged
as a zero nor reported as a claim the verifier failed to reproduce.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps in my own response to review, found while writing the replies.

The CLI's --include-expensive help still listed feasibility as simulator-backed.
That was half of Soheyl's point and I had only fixed the other half in
CONTRIBUTING; viol has been a constraint check since the earlier round.

`freeze_spec` gained parameters for every contract field but no test, and he
asked for one. Two now: that the values reach the frozen spec, and that the
CLI's defaults are the dataclass's rather than a second copy that can drift
without either side failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mkeeler43

Copy link
Copy Markdown
Contributor Author

Round 2 — all seven comments answered, plus the change that lets this board be public

Threaded reply on each of the seven; short version below, with the two that
opened into something larger called out.

Comment Fix
package_complete only checked while building the message Checked before file discovery; the old branch became unreachable and was deleted rather than left as dead defensive code
upload_folder retains stale files Deletes when the upload is the whole package, and deliberately not for a staged one — a blanket delete would have destroyed VQGAN's stage 1
Preprocessing excluded from content_hash Folded in via identity_metadata, as a denylist so a future output-affecting field cannot be silently missed
freeze_spec cannot set volume_condition / volfrac_tol Every contract field now settable; you asked for tests and I had missed them — added
code_version has the parent-repository bug Shared source_checkout_commit(...) helper, used by both sides
README command still fails Reproduced first; your 825831f6 confirmed and both commands now run verbatim
CONTRIBUTING conditions.dataset guidance + viol Rewritten; the viol half was only half-done until af6b018

Two of these were symptoms of a bigger gap. A row recorded a checkpoint
hash but never a repo, and every default pointed at IDEALLab — so an
outside contributor could neither publish weights nor produce a row anyone could
trace. Every number was an assertion nobody could falsify. Rows now carry a full
address and python -m engiopt.verify re-runs from it; nothing is ranked until a
runner reproduces it. Separately, the protocol being public means a lookup table
scores mmd=0.000 and viol=0.00 on the real beams2d spec — better than every
trained model here — so the board now measures retrieval (novelty/copy_rate)
and condition-blindness (cond_sens) and declines to rank what trips them.

Full write-up in the PR description, and the trust model in
LEADERBOARD.md.

On the earlier threads

~21 threads from 3 and 6 Aug are still open in the UI. I re-verified each
against current code rather than assuming your summary covered them, and they
are all fixed — spot-checking the ones I trusted least: sweep YAMLs deleted, the
VQGAN adapter no longer refits on the evaluation sample, hash_package_contents
covers cvqgan.pth by listing the directory, local_model_dir reaches both
from_pretrained and the CLI, the device is synchronised before the sample timer
stops, and feasibility is computed independently of optimization. The two you
flagged as hidden by skips now genuinely run: 255 collected, 255 passed, zero
skipped. Happy to bulk-resolve those, or leave them for you.

Still open, and I don't think it's mine to close

Migration scope. Ten of fourteen registered generators have no published
checkpoint. --list-generators --check-availability now reports that per
generator instead of implying all fourteen are usable, but reporting is not
choosing between migrating them, keeping a fallback, and narrowing the registry.
#73 should stay open.

I also did not publish a canonical cgan_cnn_2d package. That is a
checkpoint-publishing decision that overlaps the above, so it seemed yours.

@mkeeler43
mkeeler43 requested a review from SoheylM August 11, 2026 15:29
@SoheylM

SoheylM commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The public submission and verification boundary is not complete yet.

The documentation now describes a public leaderboard where outside contributors publish checkpoint-backed rows and an “official runner” later downloads and re-scores them. However, this PR does not define or deploy that runner: ideallab-ci is currently only a value stored in verified_by. There is no workflow, schedule, queue, or specified execution environment. We should not assume any particular future infrastructure.

More importantly, the submitted artifact is not yet safe for unattended execution. Verification reaches generator_cls.build(...), after which adapters deserialize participant-controlled .pth files using torch.load; VQGAN explicitly uses weights_only=False. The revision and content hash establish which bytes were evaluated, but they do not make those bytes safe to load.

I think this PR should establish a submission contract that a future official runner can consume safely, even if deploying that runner remains follow-up work:

  • Require safetensors for new external submissions and reject pickle-based .pth/.pt files on the public path.
  • Make EngiOpt export safetensors automatically so this does not become a manual participant step.
  • Keep reconstruction settings and preprocessing state in validated JSON.
  • Accept only registered EngiOpt adapters. New executable model code must still enter through a reviewed code PR.
  • Before loading weights, validate the configuration and safetensors header against adapter-specific expectations: tensor names, shapes, supported dtypes, file size, tensor count and total element count.
  • Pin the checkpoint repository revision and content hash, as this PR already does.
  • Provide a submission mechanism that does not require IDEALLab write access. The current --push-to IDEALLab/engiopt-leaderboard performs a direct upload, despite LEADERBOARD.md saying outside contributors need no IDEALLab access. A pending manifest through a Hugging Face Community PR (create_pr=True) would be one straightforward option.
  • Keep participant results verified=false; only the eventual official runner should publish verified scores.

Existing IDEALLab .pth checkpoints can temporarily use a separate, explicit allowlisted legacy path, with a follow-up issue to migrate them. A user-controlled repository or URL must not be able to select that path.

Isolation and runtime controls belong to the eventual runner: no credentials, restricted network access, and CPU/GPU, memory and time limits. Those controls do not have to be deployed in this PR, but they should be tracked before unattended verification is enabled.

Deploying and operating the official runner may remain follow-up work, but the safe submission contract cannot. Every external submission accepted after this PR should already contain safetensors weights, validated JSON configuration and preprocessing metadata, a registered adapter identifier, and an immutable repository revision and content hash. Participant-computed metrics may remain visible as verified=false until the official runner is available, but the submitted package must be directly consumable by that runner without conversion, migration, or resubmission.

@SoheylM

SoheylM commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

README cleanup: The sentence “Usually, we provide two scripts per algorithm: one to train the model, and one to evaluate it” describes the architecture removed by this PR. Each generator still has its own training script and adapter, but evaluation is now handled centrally by python -m engiopt.evaluate. Could we replace it with: “Each generator provides its own training script and adapter; evaluation is handled through the shared python -m engiopt.evaluate command.”

@SoheylM

SoheylM commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The migration playbook is being removed before #73 meets its removal criteria.

Issue #73 says docs/checkpoint_migration_playbook.md should be deleted after the historical W&B checkpoint migration is complete. This PR deletes the file while leaving #73 open, and the PR description explicitly says migration scope remains unresolved.

I do not think this PR needs to migrate every historical checkpoint. However, could we either:

  1. Restore and update the playbook for the new package layout, leaving Remove checkpoint migration playbook after W&B checkpoint migration is complete #73 open until migration is complete; or
  2. Record an explicit decision not to migrate the remaining checkpoints, update the supported-generator documentation accordingly, and close or replace Remove checkpoint migration playbook after W&B checkpoint migration is complete #73.

Deleting the migration instructions while the migration decision remains open leaves no documented route for completing the work.

When documenting availability, please also distinguish missing checkpoints from incompatible generators. For beams2d, only seven registered generators support the problem kind; at the time of this review four have published checkpoints and three compatible families are missing. Counting all fourteen makes unrelated 1D/3D generators look like failed migrations.

mkeeler43 and others added 2 commits September 2, 2026 13:03
`--list-generators --check-availability` queried every registered generator
regardless of design kind, so on beams2d it reported ten missing checkpoints and
told the reader "they can be trained and evaluated". Seven of those ten cannot
serve a 2D problem at all -- `evaluator.py` refuses the pairing -- so the count
invented seven migrations nobody owes and the sentence was false for each of
them. The real number is three.

`generators_for` already answered this and the listing path never called it.
It does now, which also drops seven pointless Hub requests, and the third state
prints as "does not fit beams2d" rather than being folded into the missing set.

Records the decision behind the number: W&B-era checkpoints are not being
migrated, because a migrated checkpoint arrives without the fingerprint and
content hash that make a package canonically addressable and a row verifiable.
Retraining costs less than a provenance story nobody can check.

Also drops the README sentence promising two scripts per algorithm, which this
branch stopped being true when it deleted the thirteen evaluation scripts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A `.pth` file is pickled Python, and unpickling executes instructions carried in
the file, so `torch.load` on a checkpoint from someone else's account runs their
code. The recorded revision and content hash say which bytes were loaded, not
that loading them was safe -- and this branch is what makes a remote package the
only way to reach a checkpoint at all.

All sixteen load sites now pass `weights_only=True`. Thirteen simply never set
it; VQGAN opted into the unrestricted form at three sites and did not need to.
Verified against the Hub rather than assumed: all nine weight files across the
four published packages -- cgan_cnn_2d, diffusion_2d_cond, gan_cnn_2d, vqgan --
load unchanged under the restriction, holding nothing but state dicts, tensors
and scalars. A test pins this across `core.py` and every registered adapter, so
a new model cannot quietly reintroduce it.

This is not enough for unattended verification and LEADERBOARD.md now says so
instead of implying otherwise: safetensors, validated JSON for reconstruction
and preprocessing state, header checks before allocation, and an isolated runner
are all still missing. They are deliberately not here, because nothing outside
IDEALLab can submit yet -- so there is no package that would need converting,
and a submission format built before the runner that consumes it is a guess.

Corrects the claim that submission needs no IDEALLab access, which was true of
the checkpoints and false of `--push-to`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mkeeler43

Copy link
Copy Markdown
Contributor Author

Round 3 — two comments fixed, one split into #78

Pushed as 963fc02 and b087001.

Comment Outcome
README still describes two scripts per algorithm Fixed — replaced with your wording
Playbook deleted while #73 is open; availability counts unrelated 1D/3D generators Fixed — decision recorded, listing made kind-aware, #73 closed
Safe submission contract (safetensors, header validation, Community PR, isolation) Split: the free half is in, the subsystem is #78

The availability report was wrong in the way you said, and slightly worse

You called it as 7 of 14 fitting beams2d, so 3 missing rather than 10. Confirmed, and the
old message compounded it: having counted seven inapplicable models as unpublished, it then
reassured the reader that "they can be trained and evaluated" — false for exactly those
seven, since evaluator.py:161 refuses the pairing outright.

generators_for already answered this and the listing path never called it. Now:

14 generators registered, 7 of them fit beams2d:

  cgan_1d              1d/dict    conditional      does not fit beams2d
  cgan_cnn_2d          2d         conditional      46 package(s)
  cgan_2d              2d         conditional      no published checkpoints
  ...
3 of the 7 generators that fit beams2d have no published checkpoint under IDEALLab:
cgan_2d, gan_2d, pixel_cnn_pp_2d.

Seven fewer Hub requests too, since those repos cannot hold beams2d packages.

#73 — taking your option 2

Closed, with the decision written into docs/checkpoint_layout.md: W&B-era checkpoints are
not being migrated. A migrated checkpoint would arrive without a config fingerprint or a
content hash, so it could be stored but never canonically addressed or verified — a package
no row can be ranked against. Retraining costs less than a provenance story nobody can check.

The submission contract — agreed on the mechanism, split on the scope

Your analysis is right and I have stopped implying otherwise in the docs. A .pth is pickled
Python, unpickling executes what the file carries, and the revision and hash establish which
bytes ran, not that running them was safe.

What is in this PR, because it is free and this branch is what makes a remote package the
only route to a checkpoint: all sixteen load sites now pass weights_only=True. Thirteen
never set it; VQGAN opted into weights_only=False at three sites and did not need to —
its checkpoints hold nothing but state dicts, tensors and scalars. Verified against the Hub
rather than assumed: all nine weight files across the four published packages load unchanged
under the restriction. A test pins it across core.py and every registered adapter.

LEADERBOARD.md gained a Loading someone else's weights section saying what that does
not cover — resource exhaustion, tensors that are not the model, participant-controlled
config feeding build(), and the runner's own blast radius.

What is in #78, and why not here. The argument for landing the format now is that
packages accepted after this PR would otherwise need converting later. That is a migration
cost, and it is currently zero: there are no external submissions, and there cannot be until
a no-IDEALLab-access route exists. The set that would need converting is empty and stays
empty until the board opens. Against that, items 1–5 and 7–8 are a subsystem — safetensors
export across 14 training scripts, per-adapter header validation, a schema layer, a
submission flow, an allowlist — on a PR at 120 files and three rounds. And a submission
format built before the runner that consumes it is a guess at what the runner needs. They
are one piece of work; #78 is that piece, gated on opening the board.

I also fixed the contradiction underneath your Community-PR point rather than building the
flow: LEADERBOARD.md claimed "Nothing here needs write access to IDEALLab" while
--push-to does a direct upload_file to the board. True of the checkpoints, false of the
row. The doc now says which is which, and names the self-service route as tracked in #78.

Verification

Run in a clean worktree, because my working tree has untracked generator directories that
fail test_every_generator_directory_has_an_adapter locally; all of them have zero tracked
files, so CI never sees them.

  • 260 passed, 0 failed, 0 skipped — up from 255, five new tests. Counts, not the badge.
  • ruff check . and ruff format --check clean, verbatim as CI runs them, no --fix.
  • pre-commit including pyright green on both commits.
  • mypy with the real dependency stack: 64 errors before, 64 after, none introduced. Still
    pre-existing and still passing vacuously in CI, as flagged last round.
  • --list-generators --check-availability run against the live Hub; output above.
  • The weights_only claim checked by downloading and loading real packages.

One limit worth stating: that check covered one package per published generator on beams2d,
not all 189. I read the save side of every training script — all dicts of scalars and
state_dict()s — but did not load every package.

Still open, deliberately

Migration scope. Unchanged in substance, but the reporting is now honest about it:
3 generators fit beams2d and lack weights, not 10, and the non-migration decision is
written down rather than implied. Narrowing the registry further is still a project call.

Comment thread README.md Outdated
Comment thread docs/checkpoint_layout.md Outdated
@SoheylM

SoheylM commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Thanks, Matthew. I completed another review of b087001. The previous correctness, checkpoint integrity, availability reporting, and CI concerns now look addressed. I also reran the full suite locally: 260 tests pass, and Ruff and the formatting check are clean.

Splitting the safe external-submission contract into #78 is reasonable now that this PR clearly keeps submission internal and #78 is explicitly required before accepting external checkpoints.

I have left two final documentation comments: one correcting who can currently publish, and one clarifying the reason for not migrating historical W&B checkpoints. Once those are addressed, I think we can resolve the remaining review threads and approve the PR.

mkeeler43 and others added 2 commits September 8, 2026 16:16
Two documentation corrections from review.

README claimed "Anyone may publish", contradicting LEADERBOARD.md, which
says in two places that `--push-to` needs write access to the board. The
checkpoints need no access; the row does. README now says which, and names
#78 as the self-service route.

The non-migration rationale was wrong on the facts: a migrated package can
be hashed, and a fingerprint can be derived when the run config is
recoverable. The real reason is the manual work of recovering and
validating that metadata across historical W&B runs, and the incomplete
provenance it would still leave.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pass over every document this branch ships, checking each claim against
the code and against the other documents.

Contradictions with the code:
- CONTRIBUTING's build() example loaded a checkpoint without
  weights_only=True, which every adapter now passes and a test pins.
- The same example used checkpoint_identity without importing it.
- The PR template still asked for evaluate_<algo>.py and for restoring
  models from W&B artifacts; both are gone. It now asks for the adapter
  and the HuggingFace package.
- cgan_cnn_3d's README listed --lr_enc. That model has no encoder.
- "a metric is added once rather than thirteen times": there are fourteen.

Merge artifacts:
- LEADERBOARD.md said "the mechanism this PR adds".
- README cited #78 bare, which does not link from a README; LEADERBOARD.md
  described the self-service route without naming the issue at all.
- Two README sentences read as migration notes ("now use", "no longer")
  rather than as documentation.

Spelling: American throughout, including docstrings and test comments
this branch added (defence, behaviour, honour, modelling).

Housekeeping: one dash style per document; engiopt/.DS_Store untracked
and ignored; the EngiBench typo and the broken \" escape in the citation;
trailing slashes on two table links.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mkeeler43

Copy link
Copy Markdown
Contributor Author

Both documentation comments are addressed in dfeb373.

32670ba is a consistency pass over every document this branch ships, so the second commit touches more files than the two comments alone would. Each item was checked against the code before changing the text:

  • CONTRIBUTING_A_MODEL.md's build() example loaded a checkpoint without weights_only=True, contradicting what every adapter now does and what tests/ pins; and it used checkpoint_identity without importing it.
  • The PR template still asked for evaluate_<algo>.py and for restoring models from W&B artifacts. It now asks for the Generator adapter and the HuggingFace package.
  • LEADERBOARD.md said "the mechanism this PR adds", and described the self-service route without naming Safe submission contract before the leaderboard accepts external checkpoints #78. Both fixed.
  • cgan_cnn_3d/README.md listed --lr_enc; that model has no encoder.
  • README: "thirteen times" (the registry has fourteen), two sentences that read as migration notes, and the EngiBenc typo.
  • American spelling in the new docstrings and test comments; one dash style per document; engiopt/.DS_Store untracked and ignored.

The README's live claims were re-verified against the Hub today: engiopt-cgan-cnn-2d holds 46 beams2d packages and no canonical path; diffusion_2d_cond and vqgan do have canonical packages.

Verification, in a clean worktree: 256 passed / 4 deselected (-m "not network"; nothing network-facing changed), ruff format --check and pre-commit including pyright clean, git diff --check clean. No code logic changed in either commit.

@SoheylM SoheylM left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Matthew. I reviewed the latest head (32670ba). Both final documentation comments are addressed, and the additional consistency updates match the current implementation.

The earlier correctness, checkpoint integrity, availability reporting, migration scope, and CI concerns are now resolved. The safe external-submission contract is clearly gated in #78 before external checkpoints can be accepted.

I reran the complete local suite: 260 tests pass, and Ruff, formatting, pre-commit, and pyright are clean. All GitHub checks are also green. I have no further changes to request and am approving the PR.

@mkeeler43
mkeeler43 merged commit 7a98ba5 into main Sep 9, 2026
4 checks passed
@mkeeler43
mkeeler43 deleted the feat/generator-contract-overhaul branch September 9, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants