Skip to content

Inverse Design

COMPASS includes a built-in optimization module for inverse design of pixel structures. Starting from a baseline pixel configuration, the optimizer automatically searches for geometry parameters (microlens shape, BARL thicknesses, color filter thickness, etc.) that maximize quantum efficiency, minimize crosstalk, or achieve other user-defined targets.

Overview

The optimization workflow has three parts:

  1. Parameter Space -- Choose which physical dimensions to optimize and set their bounds.
  2. Objective Function -- Define what "better" means: higher QE, lower crosstalk, or a weighted combination.
  3. Optimizer -- Select an algorithm (Nelder-Mead, L-BFGS-B, differential evolution, etc.) that iteratively evaluates COMPASS simulations to find the optimum.

Under the hood, each optimizer iteration runs a full COMPASS simulation with updated parameters, evaluates the objective, and records the result in an optimization history.

Quick Example

python
import copy
from compass.optimization import (
    PixelOptimizer,
    ParameterSpace,
    MicrolensHeight,
    MicrolensSquareness,
    MaximizeQE,
    MinimizeCrosstalk,
    CompositeObjective,
)

# Start from your baseline config dict
base_config = {
    "pixel": {
        "pitch": 1.0,
        "unit_cell": [2, 2],
        "layers": {
            "microlens": {"height": 0.6, "profile": {"n": 2.5}},
            "color_filter": {"thickness": 0.6},
        },
    },
    "solver": {"name": "meent"},
    "source": {"wavelength": {"mode": "sweep", "sweep": {"start": 0.4, "stop": 0.7, "step": 0.02}}},
    "compute": {"backend": "cpu"},
}

# 1. Define parameter space
params = ParameterSpace([
    MicrolensHeight(base_config, min_val=0.2, max_val=1.2),
    MicrolensSquareness(base_config, min_val=1.5, max_val=5.0),
])

# 2. Define objective
objective = CompositeObjective([
    (1.0, MaximizeQE(wavelength_range=(0.4, 0.7))),
    (0.5, MinimizeCrosstalk()),
])

# 3. Run optimization
optimizer = PixelOptimizer(
    base_config=base_config,
    parameter_space=params,
    objective=objective,
    solver_name="meent",
    method="nelder-mead",
    max_iterations=50,
)
result = optimizer.optimize()

print(f"Best QE objective: {result.best_objective:.4f}")
print(f"Best parameters: {result.best_params}")
print(f"Evaluations: {result.n_evaluations}")
print(f"Converged: {result.converged}")

# Save optimization history
result.history.save("outputs/optimization_history.json")

Using YAML Configuration

You can also drive optimization from a Hydra YAML config:

yaml
# configs/experiment/optimize_microlens.yaml
optimization:
  solver: meent
  method: nelder-mead
  max_iterations: 50
  parameters:
    - type: microlens_height
      bounds: [0.2, 1.2]
    - type: microlens_squareness
      bounds: [1.5, 5.0]
  objective:
    type: composite
    components:
      - weight: 1.0
        type: maximize_qe
        wavelength_range: [0.4, 0.7]
      - weight: 0.5
        type: minimize_crosstalk

Available Parameters

ParameterClassSizeDescription
microlens_heightMicrolensHeight1Microlens sag height (um)
microlens_squarenessMicrolensSquareness1Superellipse exponent n
microlens_radiiMicrolensRadii2Lens semi-axes (radius_x, radius_y)
barl_thicknessesBARLThicknessesNOne value per BARL layer
color_filter_thicknessColorFilterThickness1Color filter layer thickness (um)

Each parameter class accepts min_val and max_val arguments for bound constraints.

Objective Functions

All objectives return a scalar to be minimized. Maximization objectives (QE, peak QE) are internally negated.

ObjectiveDescription
MaximizeQENegative mean QE across pixels and wavelengths. Supports target_pixels, wavelength_range, and weights.
MinimizeCrosstalkMean off-diagonal QE fraction (crosstalk). Supports target_wavelength_range.
MaximizePeakQENegative peak QE for a specific color channel ("R", "G", or "B").
EnergyBalanceRegularizerQuadratic penalty when R+T+A deviates from 1 beyond a tolerance.
CompositeObjectiveWeighted sum of multiple objectives.

CompositeObjective Example

python
objective = CompositeObjective([
    (1.0, MaximizeQE()),
    (0.5, MinimizeCrosstalk()),
    (0.1, EnergyBalanceRegularizer(tolerance=0.02, penalty_weight=10.0)),
])

Energy Balance: R + T + A = 1

Adjust silicon thickness and BARL quality to see how reflection, transmission, and absorption partition the incident light across the visible spectrum.

Max error:0.00e+0
Mean error:0.00e+0
Validation:✓ Pass
Peak absorption:380 nm
0.00.20.40.60.81.0400450500550600650700750Wavelength (nm)FractionReflection (R)Transmission (T)Absorption (A)

Optimization Methods

MethodTypeBest For
nelder-meadGradient-free simplexRobust default, 1--5 parameters
powellGradient-free directionalSmooth landscapes, bounded
l-bfgs-bQuasi-Newton with boundsSmooth, differentiable solvers
differential-evolutionGlobal stochasticMulti-modal landscapes, avoids local minima

Tips

  • Start with Nelder-Mead for up to ~5 parameters. It is robust and does not require gradients.
  • Use differential-evolution when you suspect multiple local minima or have more than 5 parameters.
  • Add EnergyBalanceRegularizer as a soft constraint to steer the optimizer away from unphysical configurations.
  • Save the history after each run with result.history.save(path) so you can visualize convergence later.
  • Use a coarse wavelength sweep during optimization (e.g. step=0.02 um) to speed up each evaluation, then verify with a fine sweep at the end.
  • Choose meent as the solver for optimization since it is a fast, differentiable RCWA backend.
  • Each evaluation runs a full COMPASS simulation, so wall time scales linearly with the number of evaluations.