fix(photonics2d): consistent simulate/optimize (v1)#264
Open
mkeeler43 wants to merge 6 commits into
Open
Conversation
v0 applied the density projection (blur + tanh smoothed-Heaviside) inside both simulate and optimize at differing strengths, so an optimized design did not reproduce its reported objective when re-simulated (the gap grew with blur_radius, up to ~80%). v1 confines filtering/projection to optimize and evaluates designs as-is everywhere else, so simulate(optimize(x)[0]) == history[-1] and history[0] == simulate(x). - backend.py: add design_to_epsr (scale-only) and filter_and_project (blur+project), purely additive; v0 functions unchanged and frozen. - v1.py: subclass of v0 with an explicit Adam loop + beta continuation, rho clipped to [0,1] each step; optimizer knobs promoted to validated Config fields; bounds checked via check_constraints. - __init__: export v1 as the default Photonics2D. - dataset_slurm_v1.py: SLURM dataset generator that collects results and pushes to IDEALLab/photonics_2d_120_120_v1. - tests/test_photonics2d.py: round-trip, step-0, bounds, dtype, backend units. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The intermediate job/result files defaulted into a temp dir under $SCRATCH, which HPC clusters purge on a rolling basis; a run left un-assembled for weeks loses its results. --work-dir lets the intermediate files be written to persistent storage so a delayed run is recoverable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add a Versions section and v1 dataset fields to the problem docs, including how to revert to v0 (`from engibench.problems.photonics2d.v0 import Photonics2D`). - Remove the one-off SLURM dataset-generation script from the branch; the v1 dataset is already published and the script remains in git history. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lity - Merge origin/main (adds the reference-value regression test). - Add tests/reference/simulate/problems.photonics2d.v1.Photonics2D.json and drop the orphaned v0 reference (v0 is no longer the exported/tested class). - v1.py: drop the autograd `ArrayBox` from type annotations (it evaluated against a Mock during the docs/doctest builds and crashed them; it is also autograd-internal jargon), and rewrite the module docstring in plain language so blur/projection/beta continuation are understandable without prior v0 knowledge. - docs: document the fixed warm-start used to generate the v1 dataset. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
simulateandoptimizedisagree in Photonics2D v0: the objectiveoptimizereports for a designis not reproduced when you
simulatethe design it returned. On the published v0 dataset,re-simulating a stored
optimal_designdiffers from its stored objective by a median of ~130% andup to 37×. This PR fixes it by adding Photonics2D v1; v0 is unchanged and still importable.
Photonics background (what's specific to this problem)
Unlike the structural/thermal problems, a photonics design is a continuous density field
rho ∈ [0, 1]that is converted to a permittivity map and scored by an FDFD solve(frequency-domain Maxwell). The device is a wavelength demultiplexer, and the objective
("total overlap") measures how well two wavelengths reach their respective output ports.
Gradient-based topology optimization here uses two operators (from the ceviche tutorial this problem
is refactored from), applied to
rhobefore it becomes permittivity:operator_blur) — a convolution imposing a minimum feature size; without it the optimizerproduces unmanufacturable single-pixel features.
operator_proj) — a tanh smoothed-Heaviside that pushesrhotoward 0/1 so thefinal design is near-binary. Its sharpness is
beta; a continuation schedule rampsbetafromsoft to hard over the run so early-iteration gradients stay informative.
Both are optimization machinery — they only make sense while searching for a design, not while
scoring a finished one.
The bug (v0)
v0 ran blur + projection inside the shared
epsr_parameterization, which was called by bothoptimizeandsimulate— andsimulateran it atbeta = max_betawhileoptimizerampedbeta. Two consequences:simulate(design)silently re-blurs and re-projects the design before scoring it, so it does notscore the design it was handed.
optimizereturned a design that was projected once and then binarized withnp.rint, whichdiffers again from what its final recorded objective was computed on.
So the stored/returned design is not the one that produced the reported number, and the gap grows
with
blur_radius(more projection → larger mismatch) — up to 37× on the dataset.The fix (v1)
Blur + projection live only inside
optimize;simulatescores designs as given.rho ∈ [0, 1](blur + projection already applied).simulate(design)validatesrho ∈ [0, 1](check_constraints) and converts it to permittivity byscaling only (
design_to_epsr) — no blur, no projection — then runs FDFD.optimize(start):history[0] == simulate(start).betarampinginitial_beta → max_betain an explicitAdam loop, clipping
rhoto [0, 1] each step. (The tanh projection only maps [0,1]→[0,1], sokeeping
rhobounded is what makes it well-behaved — this is why the projection does not need tobe a sigmoid.)
max_beta, sosimulate(optimize(x)[0]) == history[-1].epsr_parameterizationis split intodesign_to_epsr(scale only, used bysimulate) andfilter_and_project(blur + projection, used byoptimize). Both are additive inbackend.py;no v0 function is modified.
penalty_weight,step_size,eta,initial_beta,max_beta) are now validatedConfigfields. The trailingnp.rintbinarization and the beta-schedulingadam_optimizecallback are gone — the explicit loop updates
betadirectly.Fixes # (simulate vs optimization-history inconsistency issue — please link)
Reverting to v0
from engibench.problems.photonics2d import Photonics2D→ v1 (…_v1dataset).from engibench.problems.photonics2d.v0 import Photonics2D→ v0 (…_v0, unchanged).Dataset
Regenerated on Euler, reusing v0's exact conditions and train/val/test split (1594 / 199 / 200) so the
two are directly comparable; all 1993 optimizations succeeded. Published to
IDEALLab/photonics_2d_120_120_v1. Warm start is fixed across all rows(
random_design(noise=0.001), seed 0), documented on the problem page.Validation
Re-
simulateeach storedoptimal_designand compare to its stored objective:Type of change
docs/problems/photonics2d.md)Checklist
ruff checkandruff formatmypytests/test_photonics2d.py) plus reference values