Normalize 2D diffusion designs to DDPM sample scale#68
Conversation
|
I wanted to add a bit more context on why I changed the 2D diffusion scaling/sampling path here. The main issue I found after the previous diffusion fixes was that the 2D sampler could still drift outside the scale of the design problem during reverse diffusion. For Beams2D, the old sampling path could produce raw values far outside the valid design range, which then showed up as washed backgrounds, ghost structures, or collapsed samples. This PR makes the scale contract explicit:
I also ran a short Beams2D sanity training run with the new path. This was not meant to be a final paper-quality model, just a check that the implementation behaves sensibly:
Qualitatively, the 50-epoch samples are not perfect, but they are much better behaved than the previous unstable sampler path: the generated designs stayed in range before the defensive final evaluation-time bounds clip was needed, and several Beams2D structures are recognizable under test-set conditions. I see this as a better implementation because it uses the EngiBench problem definition as the scale source of truth and keeps the DDPM sampler internally consistent, rather than relying on accidental dataset extrema or a final-output clip to rescue out-of-range samples. Validation I ran locally:
One note: this still uses the local EngiOpt DDPM sampler rather than switching fully to Hugging Face |
|
Adding the qualitative sanity-check figure from the short Beams2D run so the numerical results above have visual context. What this shows:
This is not intended as a final paper-quality model. It is a quick qualitative sanity check that the implementation is better behaved: generated designs remain inside the valid design bounds after inverse transform, several structures are recognizable, and the sampler no longer depends on the final evaluation-time bounds clip to rescue out-of-range samples. There is still a defensive final clip in evaluation, but in this sanity run the measured clipped fraction was 0.0; the point is that the reverse process itself stayed inside the intended normalized scale. The remaining washed backgrounds / imperfect members are model-quality issues to keep investigating, but the scale contract in this PR is much cleaner and more problem-consistent. |
Mirror PR #68 fixes from diffusion_2d_cond into the image-conditioned variant so the two scripts produce equivalent designs (modulo image condition handling): - Add DIFFUSION_SAMPLE_MIN/MAX constants and the three module-level helpers (normalize_designs_to_diffusion_range, denormalize_designs_from_diffusion_range, get_design_bounds). - DiffusionSampler now precomputes posterior_mean_coef1/2 and exposes _posterior_mean, which reconstructs the predicted clean x0, clips it to [-1, 1], and uses it for the posterior mean. Both diffusion_step_sample and sample_timestep (which still concatenates image conditions as extra UNet channels) now use _posterior_mean. - Training designs are normalized from problem.design_space bounds (or the data min/max fallback) to [-1, 1] before being fed to the UNet. - The train/val visualization grids denormalize sampled designs back to the original problem scale before rendering with imshow. - Checkpoints now save design_min/design_max plus the diffusion sample bounds. evaluate_diffusion_2d_cond_imgcond denormalizes and applies bounds-aware clipping when those keys are present, with a legacy [1e-3, 1.0] fallback for older checkpoints. Verified end-to-end with an offline beams2d smoke run: both train_designs and val_designs grids render, val split loads, and the sampler+denormalize path runs without errors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…issues Normalize 2D diffusion designs to DDPM sample scale
Summary
I added a follow-up fix for the 2D conditional diffusion sampler/training scale after checking the qualitative Beams2D outputs from the previous diffusion fixes.
What changed:
[-1, 1]instead of assuming the model should train directly on[0, 1].x0, clip that prediction in normalized[-1, 1], and use it to compute the posterior mean.design_minanddesign_maxin the checkpoint so evaluation can inverse-transform generated samples back to the original problem scale.x0posterior step.Why:
[0, 1], and outputs are mapped back to the original bounds for metrics.Validation:
conda run -n engibench python -m pytest -qconda run -n engibench ruff check engiopt/diffusion_2d_cond/diffusion_2d_cond.py engiopt/diffusion_2d_cond/evaluate_diffusion_2d_cond.py tests/test_diffusion_fixes.pyconda run -n engibench ruff format --check engiopt/diffusion_2d_cond/diffusion_2d_cond.py engiopt/diffusion_2d_cond/evaluate_diffusion_2d_cond.py tests/test_diffusion_fixes.pygit diff --checkconda run -n engibench python -m compileall -q engiopt/diffusion_2d_cond tests