Inverse Design#
End-to-end topology optimization pipeline. Build a device, run multi-phase optimization on cloud GPU, then clean up and export to GDS locally.
Device Configuration#
- hyperwave_community.build_device(layers, grid, wavelength, nx, ny=None, vertical_radius=2)#
Build a device configuration from layer specifications.
- Parameters:
layers (
List[Dict[str,Any]]) –List of layer dicts. Each dict has: name (str): Layer name, e.g. “etch”, “box”, “W1”. thickness (float): Physical thickness in um. index (float): Refractive index of the material. design (bool): If True, this is an optimizable layer.
Default False.
- initial_value (float): Initial theta for design layers (0-1).
Default 0.5.
- density_radius (int): Conic filter radius in theta pixels.
Required for design layers. The theta grid is 2x finer than the FDTD grid, so R=8 at 17.5nm theta pixel gives min feature = 8 * 17.5nm = 140nm.
- density_eta (float): Projection threshold (0.45-0.75).
- Controls solid/void balance:
0.50 = balanced (default, safe) 0.55-0.60 = wider gaps (for larger min gap specs)
Default 0.5.
- waveguide_width (float): Waveguide width in um (optional).
Used by get_source_config() to compute mode solve bounds.
- waveguide_y_center (float): Waveguide center Y position in um
(optional). Defaults to center of the Y dimension.
grid (
float) – FDTD grid spacing in um (permittivity voxel size). E.g. 0.035 for 35nm. Theta grid = grid/2.wavelength (
float) – Operating wavelength in um.nx (
int) – X dimension in theta pixels (2x FDTD grid).ny (
Optional[int]) – Y dimension in theta pixels. Defaults to nx.vertical_radius (
int) – Vertical blur radius at layer interfaces (pixels). Smooths permittivity transitions between layers. Default 2.
- Return type:
- Returns:
DeviceConfig to pass to optimize().
- class hyperwave_community.DeviceConfig(layers, shape, design_layers_info, recipe_params, freq_band, pixel_size, grid, vertical_radius=2)#
Result of build_device(). Pass to optimize().
- get_source_config(layer_name, waveguide_y_center=None, waveguide_y_width=None, x_position=None, mode_pad=15)#
Compute mode source bounds from device geometry.
Returns a dict with perpendicular_bounds, z_bounds, and source_position ready to pass to create_mode_source().
- Parameters:
layer_name (
str) – Design layer name (e.g. “sin”, “etch”).waveguide_y_center (
Optional[float]) – Waveguide center in um. If None, uses the center stored in the layer spec from build_device().waveguide_y_width (
Optional[float]) – Waveguide width in um. If None, uses the width stored in the layer spec from build_device().x_position (
Optional[int]) – Source x position in permittivity pixels. If None, defaults to 5 pixels past the absorber width.mode_pad (
int) – Padding around the waveguide for mode solve (pixels).
- Returns:
source_position, perpendicular_bounds, z_bounds
- Return type:
Dict with
Waveguide Mode Solver#
- hyperwave_community.solve_waveguide_mode(grid, waveguide_width, waveguide_height, n_core, n_clad, wavelength, mode_number=0, propagation_axis=0, cross_section_size=80)#
Solve for a waveguide eigenmode.
Builds a waveguide cross-section permittivity, solves the eigenvalue problem, and returns the mode field and effective index.
- Parameters:
grid (
float) – FDTD grid spacing in um.waveguide_width (
float) – Waveguide width in um.waveguide_height (
float) – Waveguide height in um (total device layer).n_core (
float) – Core refractive index.n_clad (
float) – Cladding refractive index.wavelength (
float) – Operating wavelength in um.mode_number (
int) – Which mode to solve for. For rectangular waveguides: 0 = TE0 (fundamental), 1 = TM0, 2 = TE1, 3 = TM1, etc. The exact ordering depends on waveguide geometry.propagation_axis (
int) – Propagation direction (0=x, 1=y, 2=z).cross_section_size (
int) – Size of the cross-section grid in pixels.
- Return type:
- Returns:
(mode_field, n_eff) where mode_field has shape (1, 6, 1, Ny, Nz) for x-propagation and n_eff is the effective refractive index.
Optimization#
- hyperwave_community.optimize(device, source, mode, objective=None, phase='freeform', n_steps=100, initial_design=None, input_power=1.0, mode_cross_power=None, beta_init=None, beta_max=None, learning_rate=None, disk_radius=3, gap_radius=3, fab_eta_lo=None, fab_eta_hi=None, gpu_type='B200', api_key=None, **kwargs)#
Run an optimization phase on cloud GPU.
Runs the optimization loop, prints progress per step, and returns the final result. Press Ctrl+C to cancel early – the GPU job is stopped and you only pay for completed steps. The partial result is still returned.
- Usage:
- result = hwc.optimize(device, source, mode,
phase=”freeform”, n_steps=100)
# Prints: Step 1/100: efficiency=0.00% time=604s # Step 2/100: efficiency=1.44% time=603s # … # Press Ctrl+C to stop early
result.design # Design object for next phase result.history # per-step metrics result.save(“./checkpoints/my_run”)
- Parameters:
device (
Any) – GridInfo from LayerStack.build(), or a dict with design_layers, freq_band, source_offset, recipe_params, etc.source (
ndarray) – Source field array.mode (
ndarray) – Target mode field array.objective (
Any) – Objective expression tree (from hwc.objectives). If None, uses mode coupling with the provided mode field.phase (
str) – “freeform”, “binarize”, “dfm”, or “recovery”.n_steps (
int) – Number of optimization steps.per-layer ((density_radius is)
specs) (set in build_device layer)
initial_design (
Optional[Design]) – Design from a previous phase.input_power (
float) – Source input power for normalization.mode_cross_power (
Optional[float]) – Mode self-overlap power.disk_radius (
int) – Morphological disk radius for dfm/recovery.gap_radius (
int) – Gap disk radius for dfm/recovery.fab_eta_lo (
Optional[float]) – Override solid detection eta.gpu_type (
str) – GPU type (default “B200”).api_key (
Optional[str]) – API key (overrides configured key).
- Return type:
- Returns:
OptimizationResult with .design, .history, .save().
Surgery#
- hyperwave_community.surgery(design, min_feature_size=0.105, pixel_size=0.0175)#
Remove small features and fill small holes.
Non-differentiable cleanup step. Runs locally on CPU, no credits.
DRC Check#
- hyperwave_community.check_drc(design, disk_radius=3, pixel_size=0.0175, fab_rules=None)#
Run design rule check via morphological opening.
Runs locally on CPU, no credits charged.
- Single-layer (simple):
drc = check_drc(design, disk_radius=3) drc.passed # True/False
- Multi-layer with per-layer rules:
- drc = check_drc(design, fab_rules={
“W1”: {“cd_radius”: 4, “gap_radius”: 4}, “W2”: {“cd_radius”: 4, “gap_radius”: 6},
}) drc[“W1”].passed drc[“W2”].gap_pct
- Parameters:
design (
Design) – Design to check.disk_radius (
int) – Default morphological disk radius (used when fab_rules not provided, or as fallback for missing layers).pixel_size (
float) – Physical pixel size in um.fab_rules (
Optional[Dict[str,Dict[str,int]]]) – Per-layer fab rules. Dict of layer_name -> dict with optional keys: cd_radius, gap_radius (default to disk_radius).
- Returns:
DrcReport if single-layer or no fab_rules. Dict[str, DrcReport] if fab_rules provided (keyed by layer name).
GDS Export#
- hyperwave_community.export_gds(design, filename='output.gds', layer=(1, 0), pixel_size=0.0175, layer_name=None, beta=64.0, eta=None, smooth_nm=None)#
Export design to GDSII file.
Uses the subpixel-accurate pipeline validated by Jon Roth: theta -> conic filter -> tanh Heaviside projection -> marching squares contour -> optional KLayout smoothing.
The tanh projection at beta=64 keeps the density mostly binary but leaves a thin gradient ring at each contour, giving marching squares sub-pixel interpolation accuracy. This produces smooth contours without pixel laddering artifacts.
Runs locally on CPU, no credits charged.
- Parameters:
design (
Design) – Design to export.filename (
str) – Output GDS filename.layer (
Tuple[int,int]) – GDS layer tuple (layer_number, datatype).pixel_size (
float) – Physical pixel size in um.layer_name (
Optional[str]) – Which design layer to export. Defaults to first layer.beta (
float) – Tanh Heaviside projection sharpness. Default 64.0 (matches typical optimization final beta). Higher = sharper edges.eta (
Optional[float]) – Projection threshold. If None, uses the layer’s density_eta from build_device(). Controls solid/void boundary position.smooth_nm (
Optional[float]) – Optional KLayout smoothing distance in nm. Removes remaining staircase vertices. 5nm works well for 12.5nm grids. None = no smoothing (raw marching squares output).
- Return type:
- Returns:
Absolute path to the generated GDS file.