Skip to content

Repository files navigation

Adamantium designing a 90 degree waveguide bend: the material mask resolves from a blob into a curved guide while the field routes around the corner

Adamantium β

test status tests python backends license

Inverse design and verification for integrated photonics, on the laptop you already own.


Adamantium designs a photonic device by gradient descent on material density, then scores it against the corners it will actually meet. Two decisions define it. Transmission is referenced to a separate incident-flux solve, so the objective cannot be improved by reflecting light back at the source. And the optimizer minimizes the worst corner the verifier will later measure, not the nominal design point. The rest is engineering.

It is a scalar 2D TM approximation and beta research software. It is not foundry sign-off.

The animation is a recorded run, not a rendering. Frames come from the optimizer's own per-step callback via scripts/make_demo_gif.py, in the workbench's own palette.

Results

Left: throughput against optimization step, converging from -11.5 dB to -0.44 dB, with the verified worst corner at -1.16 dB. Right: throughput against wavelength for all 25 fabrication and spectral corners.

One adamantium bend --steps 16 --verify, seed 0, from its evidence.json.

Measurement Number Against
As-drawn 90° bend −8.41 dB, R 30.4% incident flux of the launch structure
Designed −0.41 dB, R 0.0% same reference
After a one-cell etch −0.51 dB, R 3.2% same reference
Worst of 25 corners −1.16 dB at etch−, 1.600 µm same reference
Independent JAX cross-check −0.51273 dB vs MLX −0.51273 dB Δ 2.0×10⁻⁵ dB, separate stack
Solver residual 2.7×10⁻⁵ nominal, 1.9×10⁻⁴ worst corner limit 2×10⁻⁴

The convergence trace is the optimizer's own readout of the continuous design; the table re-solves the projected and snapped structure, which is why −0.44 and −0.41 both appear.

The gap between −0.41 dB and −1.16 dB is the entire point. The first is what a demo quotes. The second is what the device has to survive.

Transmission you cannot game

Every device carries a cached reference solve of its launch structure alone. Transmission is always T = P_port / P_inc against that incident flux, and the same-solve input flux only sets R = 1 − P_in / P_inc.

The obvious alternative, P_out / P_in at the same solve, is worse than merely noisy: it is actively perverse. Reflection shrinks the denominator, so a device that throws light back at the source scores better while performing worse, and gradient descent will find that. When the objective was corrected, rescoring the same bend moved its worst corner from −3.5 dB to −1.16 dB. The optimizer had been buying most of a decibel with a measurement artifact.

Robust by construction

The loss minimizes a softmax-weighted (k=16) worst case over the corners the verifier will later score:

  • three projection fields, eroded, nominal and dilated, where the η shift reproduces the verifier's ceil(bias/dx) one-cell etch morphology exactly
  • three wavelengths, the band edges and center

The η shift matching the verifier's morphology is not a detail. If the optimizer's notion of an eroded structure differs from the verifier's by a single cell, it hardens against a device that will never be fabricated.

On Apple silicon all nine corner problems solve as one batched Metal dispatch with per-corner ω, PML and , so a robust step costs roughly what a nominal step costs. The JAX path solves the same cases in sequence with identical semantics.

Speed

Left: cold, p50 and p95 timings for one 64x64 Helmholtz solve on JAX and MLX. Right: wall clock split between design and verification, totalling about 43 seconds.

Design, 25-corner verification and the independent JAX cross-check finish in about 43 seconds in one process on an M-series laptop. That is the number worth quoting, because it is what stands between an idea and verified evidence.

Solver tolerance tightens in three tiers as β hardens, since gradient accuracy only has to grow as the projection sharpens: 1e-3/12 chunks below β 8, 5e-4/16 through β 12, then 2e-4/24. That cut the same run from 53 s to 41 s with an identical verified result.

Treat the solver ratio in the left panel as a snapshot, not a spec. It was 1.4× on the run that generated the chart and 2.65× on a cold machine, because Metal throttles under sustained load while the CPU reference does not. adamantium bench re-measures it on your hardware. The number that does not move is the agreement between the two stacks: 2.7×10⁻⁵ relative field delta, every run.

Install

Python 3.10+. Apple silicon gets the Metal path automatically; everything else falls back to JAX.

python3 -m venv .venv && source .venv/bin/activate
pip install ".[dev]"

Wheels and an sdist are attached to each release.

Use

adamantium bend --steps 16 --verify      # design, verify 25 corners, cross-check, export
adamantium serve                         # workbench at http://127.0.0.1:8765
adamantium bench                         # cold, p50, p95, backend parity
from adamantium.devices import bend
from adamantium.run import optimize_device

device = bend()
result = optimize_device(device, steps=16, seed=0)

result["metrics"]["out"]["dB"]      # as designed           -0.414
result["metrics_fab"]["out"]["dB"]  # after a one-cell etch -0.513
result["metrics"]["R"]              # reflection, 1 - P_in / P_inc

Every T there is P_port / P_inc against the device's own reference solve, so the numbers mean the same thing in Python, in the CLI and in the browser.

adamantium bend --steps 16 --verify \
  --bandwidth 0.10 --spectral-points 5 \
  --etch-bias 0.02 --index-delta 0.02

adamantium sweep \
  --devices bend,lens --wavelengths 1.31,1.55 \
  --core-indices 2.6,2.8 --seeds 0,1 --steps 16 --dry-run

A run writes a spec manifest, NumPy mask and fields, a restart, probe CSVs, a live .iter monitor, evidence.json and VTK. Verification adds the worst-case spectrum, the JAX cross-check and a goal-weighted Trust Field.

Architecture

ρ ──filter──▶ ρ̃ ──project(β)──▶ ε ──▶ FDFD solve ──▶ Ez ──▶ port flux / P_inc
   2.2 px          3 η fields                                      │
                        ×                                          │
                  3 wavelengths ─────▶ 9 corners ──softmax(16)──▶ worst-case loss
                                           │                            │
                                  one batched Metal            implicit adjoint,
                                     dispatch                  one extra solve

verify ──▶ 5 wavelengths × 5 fabrication corners = 25 solves ──▶ worst dB
       └─▶ independent JAX solve ─────────────────────────────▶ Δ dB
  • mlx. A fused reusable Metal stencil, Jacobi-preconditioned compiled BiCGSTAB chunks with breakdown recovery, unified memory, isolated warm starts. Batch flux reads use slices only; integer indexing puts a complex64 scatter in the VJP that the MLX GPU path rejects.
  • jax. The independent CPU reference in float64/complex128, and the reason any of the Metal numbers are believable.
  • Adjoint. One implicit solve, not an unrolled iteration tape.
  • Interface. The browser keeps the plain two-view workbench by default. Strict verification and the LAVA-inspired instrument (lab +) stay collapsed and opt-in. The aesthetic is locked by docs/VISUAL_CONTRACT.md.

Oracles

tests/test_oracles.py pins the physics layer to closed-form identities rather than to its own past output.

Oracle Holds to
Flux is conserved along a straight guide 5% between two planes
T → 1 and R → 0 when a device is its own reference 5%
Projection fields nest: eroded ≤ nominal ≤ dilated exact
η shift reproduces the verifier's ceil(bias/dx) morphology exact
Design cases land on the band edges and center 10⁻⁹
The filter kernel is a partition of unity 10⁻¹²

Limitations

  • Scalar 2D TM. No 3D radiation, no polarization coupling, no dispersion beyond the index you set. A fast design loop, not a tape-out.
  • The verifier is the oracle. Robustness only covers the corners you declare. A device hardened against ±1 cell of etch across a 0.1 µm band says nothing outside that envelope.
  • Coarse-grid acceleration does not exist here. Below roughly 6 points per material wavelength the Helmholtz operator turns indefinite enough that Jacobi-BiCGSTAB stalls near residual 0.4. A half-resolution stage will not solve to tolerance at all.
  • Two accelerations were measured and rejected, recorded so they are not retried: a 5/8-resolution warm start improved the nominal number while costing 0.6 to 0.9 dB of verified worst corner, and warm-starting the adjoint saved no wall clock and cost 0.2 dB.
  • Filter radius 2.2 px. 1.4 px could not survive the verifier's one-cell erosion.
  • Metal needs Apple silicon. ADAMANTIUM_BACKEND=jax is the portable path. CI runs Metal on macOS arm64 and JAX on Linux.
  • Packaging. Web assets install through [tool.setuptools.data-files], which setuptools has deprecated. It works, and find_web_root also resolves a source checkout, but the layout has to move before setuptools drops the feature.

Run it

pytest                                       # 77 passed
ADAMANTIUM_BACKEND=jax pytest                # same suite on the reference path

pip install ".[docs]"                        # matplotlib + pillow, for the figures
python scripts/make_charts.py --steps 16     # regenerate the charts above
python scripts/make_demo_gif.py --steps 24   # regenerate the animation

Provenance

Framework and code © Joe Pecoraro, Apache-2.0. The methods below are cited prior art, not vendored, and are not claimed to originate here:

License

Apache License 2.0. See LICENSE and NOTICE. Release history in CHANGELOG.md.

About

Apple-silicon-native inverse design and verification for integrated photonics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages