Skip to content

Minimum-lengthscale constraints in Python - #12

Merged
smartalecH merged 3 commits into
mainfrom
python/lengthscale-constraints
Sep 18, 2026
Merged

smartalecH merged 3 commits into
mainfrom
python/lengthscale-constraints

Conversation

@smartalecH

Copy link
Copy Markdown
Collaborator

Stacked on #11 (base branch is docs/supported-features) — review that one first.

Ports the geometric minimum-lengthscale constraints from the Julia package to ssp_topopt, closing one of the gaps recorded in docs/features.md.

What's here

ssp_topopt.constraints adds constraint_solid and constraint_void, following the Julia implementation in src/julia/SSP/src/constrain.jl:

  • the geometric constraints of Zhou et al. (2015), with the hyperparameter-free thresholds derived in Arrieta, Romano & Johnson, arXiv:2507.16108 — decay rate c = 64 R^2, threshold eps = 1e-8, and the conic-filter threshold points eta_e / eta_d of Qian & Sigmund (2013) (eqs. 7-12 of that paper);
  • the gradient of the filtered density is obtained from the same differentiable cubic interpolation (utils.gradient) that ssp2 uses;
  • the return value is normalized by the constraint threshold, matching the Julia API, so it can go straight into an nlopt inequality constraint: nonpositive means feasible.

Any SSP order works. The constraints only take rho_filtered and rho_projected, so they compose with ssp1_bilinear, ssp2, or a plain tanh_projection; a test exercises all three end to end.

Example

examples/python/ssp_constrained_example.py has two parts:

  1. A sweep over a stripe geometry, which shows the solid constraint crossing the threshold right at the target lengthscale (qualitatively fig. 1c of the paper).
  2. A two-stage optimization against a target pattern with bars thinner than the target lengthscale. Stage 1 is unconstrained and reproduces the sub-lengthscale bars (constraints at ~1e6 times the threshold); stage 2 turns the constraints on and repairs the design (both constraints negative, i.e. feasible) at a cost of FOM 1.2e-4 -> 8.7e-2.

Testing

tests/python/test_constraints.py (14 tests, 20 subtests; the full suite is 21 passing):

  • threshold functions against the closed-form values, plus the eta_e + eta_d = 1 invariant;
  • thin stripes/gaps violate the constraint and thick ones satisfy it, with the physical width measured from the projected design rather than assumed;
  • reverse-mode gradients w.r.t. both rho_filtered and rho_projected match central finite differences to ~1e-7 (in float64 — the normalized constraint is O(1/eps), so single-precision differencing is pure roundoff);
  • composition with each projection, jit parity, and argument validation.

As an extra check outside the test suite, I compared against the Julia implementation on identical arrays: solid 1.4147e6 (Python) vs 1.4174e6 (Julia) and void 1.0382e6 vs 1.0388e6 — 0.2% and 0.06%, consistent with the two packages using different cubic interpolation schemes (interpax cubic2 vs FastInterpolations CubicFit) for the density gradient.

Not included

Dilation/erosion and cubic-interpolation SSP1 remain Julia-only; docs/features.md is updated to reflect only what this PR adds.

Base automatically changed from docs/supported-features to main August 31, 2026 19:03
@smartalecH
smartalecH requested a review from romanodev August 31, 2026 20:28
@smartalecH
smartalecH marked this pull request as ready for review August 31, 2026 20:28

@romanodev romanodev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. Just a few cosmetic changes.

Comment thread src/python/ssp_topopt/constraints.py
Comment thread src/python/ssp_topopt/constraints.py Outdated
Comment thread examples/python/ssp_constrained_example.py
smartalecH and others added 2 commits September 18, 2026 14:30
Ports the geometric lengthscale constraints from the Julia package:
constraint_solid and constraint_void, with the hyperparameter-free
thresholds of Arrieta et al. (arXiv:2507.16108). Both are written in
terms of rho_filtered and rho_projected only, so they compose with any
projection order (ssp1_bilinear, ssp2, or a plain tanh projection).

Includes a test suite covering the threshold functions, lengthscale
detection on stripe/gap geometries, gradients against finite differences,
composition with each projection, jit, and argument validation, plus a
worked example that sweeps the constraint over feature widths and runs a
two-stage constrained optimization.
Cite the published version of Arrieta et al. (Struct. Multidisc. Optim.
69, 210, 2026) in place of the arXiv preprint, use the paper's ρ̃ / ρ̄
notation for the filtered and projected densities in the documentation,
and report the figure of merit and constraints recomputed at the design
nlopt returns rather than at the last design it happened to evaluate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@smartalecH
smartalecH force-pushed the python/lengthscale-constraints branch from 2bb82ed to 75edbcb Compare September 18, 2026 21:33
The top-level reference list still pointed at the arXiv preprint for the
paper the constraints are based on, which appeared as Struct. Multidisc.
Optim. 69, 210 (September 2026). Link the journal DOI instead, matching
the style of the Optics Express entry above it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@smartalecH
smartalecH merged commit 913c39a into main Sep 18, 2026
3 checks passed
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