Generator contract, shared evaluation layer, and HF-hosted leaderboard - #75
Conversation
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>
SoheylM
left a comment
There was a problem hiding this comment.
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-2dcontained only.gitattributesandREADME.md;IDEALLab/engiopt-vqganandIDEALLab/engiopt-gan-cnn-2ddid not exist. The README's cGAN evaluation command cannot currently loadbeams2d/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.pystill uses W&B references in its example and argument descriptions, whileGenerator.resolve_checkpointand the evaluation CLI help still listwandbas a model source. These should match the final migration decision. - Lint CI: With Ruff 0.15.11,
ruff check .reports 20I001errors on this commit. The GitHub job is green because it runsruff check --fixin the temporary checkout without checking whether files changed. Please commit the import fixes and run plainruff check .in CI. Pinning Ruff would also make the check stable. - Workshop integration: The open DCC workshop branch still imports
engiopt.cgan_cnn_2d...innotebook_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.
…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>
|
Thanks for this — the review caught one genuine correctness bug (the condition count) and Fixes for your commentsCorrectness
Leaderboard integrity
Usability
Changes beyond your comments, and whyFour things are in this branch that you did not ask for. Flagging them so nothing in the
Known limitation, not addressed herethermoelastic2d models are conditioned on 3 scalars while the simulator receives all 7 |
SoheylM
left a comment
There was a problem hiding this comment.
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.
…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>
|
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 Four of these were defects the previous round introduced (C, F, G, and A itself). Two of
Where I would push back — B, second half only. Recording the EngiBench revision is 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>
|
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 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 |
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>
|
Thanks — all four of the latest round are addressed, each in its own thread. Summary here, The four inline items
Cross-cuttingCheckpoint migration / issue #73. The precondition you set is now met, and I checked
Lint CI. Confirmed your diagnosis exactly: Documentation. README no longer advertises W&B artifact refs or links the deleted DCC workshop branch. Here I would push back on the remedy rather than the observation. Three findings from verifying against the pool1. Nothing in a sweep writes the canonical path — the documented command is broken. Two changes: the resolution error now lists what the repo actually holds instead of a bare 2. A correction to something I said earlier. On 2026-08-06 I reported that VQGAN's The 4 that genuinely did not finish are still the reason the completeness marker is worth 3.
All 61 predate this PR (63 on the previous head, so this branch reduces it by 2). Fixing it Also worth flagging, deliberately not changed
I had a VerificationCI on |
SoheylM
left a comment
There was a problem hiding this comment.
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.
…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>
Round 2 — all seven comments answered, plus the change that lets this board be publicThreaded reply on each of the seven; short version below, with the two that
Two of these were symptoms of a bigger gap. A row recorded a checkpoint Full write-up in the PR description, and the trust model in On the earlier threads~21 threads from 3 and 6 Aug are still open in the UI. I re-verified each Still open, and I don't think it's mine to closeMigration scope. Ten of fourteen registered generators have no published I also did not publish a canonical |
|
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: More importantly, the submitted artifact is not yet safe for unattended execution. Verification reaches 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:
Existing IDEALLab 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 |
|
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 |
|
The migration playbook is being removed before #73 meets its removal criteria. Issue #73 says I do not think this PR needs to migrate every historical checkpoint. However, could we either:
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 |
`--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>
Round 3 — two comments fixed, one split into #78Pushed as
The availability report was wrong in the way you said, and slightly worseYou called it as 7 of 14 fitting
Seven fewer Hub requests too, since those repos cannot hold #73 — taking your option 2Closed, with the decision written into The submission contract — agreed on the mechanism, split on the scopeYour analysis is right and I have stopped implying otherwise in the docs. A What is in this PR, because it is free and this branch is what makes a remote package the
What is in #78, and why not here. The argument for landing the format now is that I also fixed the contradiction underneath your Community-PR point rather than building the VerificationRun in a clean worktree, because my working tree has untracked generator directories that
One limit worth stating: that check covered one package per published generator on Still open, deliberatelyMigration scope. Unchanged in substance, but the reporting is now honest about it: |
|
Thanks, Matthew. I completed another review of 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. |
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>
|
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:
The README's live claims were re-verified against the Hub today: Verification, in a clean worktree: 256 passed / 4 deselected ( |
SoheylM
left a comment
There was a problem hiding this comment.
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.
Generator contract, shared evaluation layer, and HF-hosted leaderboard
Replaces 13 near-duplicate
evaluate_*.pyscripts with one adapter per modeland 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.pyandevaluate_gan_cnn_2d.pydiffered by 26 lines out of 123, and those 26 lineswere 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 underengiopt/generators/(68 files, skim)Mirrors
engibench/problems/<name>/. Pure move: file contents are unchangedapart from the import paths the move forces (
engiopt.<model>→engiopt.generators.<model>). Git detects all 47 renames, sogit log --followstill 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:
algo_id"gan_2d"conditionalFalsedesign_kinds("2d",)checkpoint_files("generator.pth",)primary_state_key"generator"output_clip(1e-3, 1.0)The two actions a model must implement:
build— given a downloaded checkpoint, reconstruct me. The modelreceives the training run's saved settings and local file paths, and returns
a working network.
_sample— given design requirements, produce designs. This is the onlygenuinely 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, sothe two halves of the stack are learned once.
engiopt/utils/all_generators.pyfinds models by walking the folder, exactly asengibench/utils/all_problems.pyfinds problems —BUILTIN_GENERATORSsitsalongside
BUILTIN_PROBLEMS.Checkpoint storage also changes here. Each training run is filed under a
short hash of its own hyperparameters:
The outcome: a 50-configuration sweep produces 50 separately loadable models
instead of 50 runs overwriting one folder. The plain
{problem}/seed_{n}pathis 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
EvaluationContextthat computes theexpensive optimizer pass once for every performance metric (
iog,cog,fog,violshare one sweep instead of running four); anEvaluator; andthe leaderboard, published to HF by merging rather than overwriting.
engiopt/specs/<problem>/v1.json— frozen, hash-verified evaluationcontracts, so rows are comparable across people and across time.
python -m engiopt.evaluatereplaces 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:
Declared
costmeans 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
--trackwason, 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.
iogwas not an optimality gap. It stored the raw simulated objective whilecog/fogmeasured against the reference optimum. Verified with an oraclegenerator that returns the reference design itself: it now scores
iog ≈ 3e-07where it previously reported the design's raw compliance.
Condition sampling couldn't serve half the problems.
photonics2ddeclaressolver settings that aren't dataset columns;
thermoelastic2dencodes boundaryconditions 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.
thermoelastic2dhas a per-sampleweightspanning 0.0–1.0. Summing or averaging its three objectives meant a purely
structural sample was scored mostly on thermal compliance:
Scalarization is now declared in the eval spec, and the evaluator refuses to
guess when it's absent.
Gaps ignored objective direction.
photonics2dmaximizestotal_overlap,so a design that beat the reference scored
+0.3and ranked last. Gaps are nowsigned by
ObjectiveDirection.Also removes
metrics.metrics()andsimulate_failure_ratio()— both dead oncethe 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 belowvaried 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
lvaebranch will need adapting. Itsevaluate_*.pyscripts call theremoved
metrics.metrics()and pass W&B artifact arguments that no longerexist. That was accepted deliberately rather than maintaining two evaluation
paths.
layout changed and there is no W&B read-fallback.
surrogate_model/is untouched and sits outside the contract; it's neither agenerator nor evaluated.
Known limitations
cogsums across steps and objectives whilefogaverages objectives — apre-existing inconsistency, left alone rather than changed silently. Worth
settling alongside the metric-suite work.
dppunderflows toward zero on collapsed generators (a 1-epoch GAN scored8.9e-22), so it may lose ordering at the bottom of a real zoo. A log-domainform would fix it.
Generatorcontract isthe 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, whosephotonics2dandthermoelastic2dread thev1datasets, while release 0.2.0 still points those two atv0. On 0.2.0 they sample different data entirely. Specs now record the problemdefinition 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_constraintsraised on thermoelastic2d, and float64 designs were judgedinfeasible 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
thermoelastic2dmodels are conditioned on 3 scalars while the simulator receives all 7conditions, because its four 65x65 boundary masks cannot travel in a dense condition
tensor. Scoring is correct —
simulateandoptimizeget the full conditions — but amodel cannot currently see where the part is held, loaded, or cooled unless it reads
ConditionBatch.datasetitself, asvqgandoes for its own preprocessing. First-classsupport 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 norproduce 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 — andpython -m engiopt.verifyfollows it: fetch the package at the recordedrevision, confirm it still hashes to what was scored, rebuild through the
registered adapter, score it again. Rows land
verified=falseand unrankeduntil 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.verifywith no credentials and no--publishis an audit anyone canrun, 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,iogandfogare therefore defined as closeness todesigns anyone can look up. Measured on the real beams2d spec, against the
trained models in this repo:
memorizedA 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_ratecatch it, andcond_senscatches a model that ignoresconditions 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_sensis not a new feature so much as a claim this PR had already made —registry.pydeclared aconditionsmetric family andcore.pyreferred threetimes 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 countwould 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_KEYgainscheckpoint_repo. Two contributors who both traincgan_cnn_2don default hyperparameters and seed 1 produced the same key anddifferent weights, so either one's push would silently replace the other's
verified row.
--generators gan_cnn_2d vqgan --config-fingerprints gan_cnn_2d:6293adb3scored gan_cnn_2d and never mentioned vqgan — the emptyfingerprint 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 andscored through the contract, including VQGAN's multi-file case; the documented
diffusion_2d_cond --seeds 1 2 3run produced three rows collapsing into oneentry with
n_seeds=3; all four committed specs still reproduce their frozendigests; 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-availabilitynow reports that pergenerator 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