Simulator backends¶
openlithohub.simulators is the vendor-neutral interface for lithography
forward simulation. The bundled HopkinsSimulator is the reference
implementation; CalibreSimulator and TachyonSimulator are
config-validated stubs that activate when the corresponding toolchain is
on PATH and licensed.
The module also re-exports load_source_intensity,
load_zernike_coefficients, and zernike_phase_map so callers can drive
the Hopkins forward model with measured-source maps and Zernike-pupil
aberrations without reaching into _utils.optics.
Registry¶
openlithohub.simulators.registry
¶
Open registry of simulator backends, keyed by string name.
get_simulator(name, config=None)
¶
Construct a simulator by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Registered backend name (e.g. |
required |
config
|
SimulatorConfig | None
|
Optional :class: |
None
|
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
Source code in src/openlithohub/simulators/registry.py
list_simulators()
¶
register_simulator(name, cls)
¶
Register a simulator class under name.
Overwrites any previous registration to keep the API simple — users
that want defensiveness can guard with name in list_simulators().
Source code in src/openlithohub/simulators/registry.py
describe_simulators()
¶
Return (name, class) pairs for every registered simulator, sorted by name.
Used by the CLI to print human-readable backend listings without
reaching into _REGISTRY from the outside.
Source code in src/openlithohub/simulators/registry.py
Base interface¶
openlithohub.simulators.base
¶
Simulator base class and result/config dataclasses.
BaseSimulator
¶
Bases: ABC
Abstract simulator backend.
A simulator maps mask -> SimulatorResult. The ABC makes no
assumption about differentiability; callers that need gradients
should pick a backend that documents support (e.g.
:class:HopkinsSimulator).
Implementations should be cheap to construct — heavy state (kernel
caches, tool licenses) belongs in :meth:prepare so that callers
can decide when to pay the cost.
Source code in src/openlithohub/simulators/base.py
prepare()
¶
Eagerly initialise backend state (kernels, tool sessions).
Default no-op. Override when there is meaningful setup cost so that callers can amortise it across many simulate() calls.
with_config(config)
¶
Return a sibling simulator using config, sharing cached state where possible.
Default builds a fresh instance via type(self)(config). Subclasses
with expensive per-config setup (SOCS kernels, vendor sessions) should
override to clone cheaply when the new config only differs in fields
the cached state does not depend on (typically dose / threshold).
Source code in src/openlithohub/simulators/base.py
simulate(mask)
abstractmethod
¶
Simulate the aerial image (and resist contour, if available).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mask
|
Tensor
|
Real-valued mask. Shape |
required |
Returns:
| Name | Type | Description |
|---|---|---|
A |
SimulatorResult
|
class: |
SimulatorResult
|
|
Source code in src/openlithohub/simulators/base.py
SimulatorConfig
dataclass
¶
Vendor-neutral simulator configuration.
Backends are free to ignore fields they don't model and to read
extra options from :attr:extra. We deliberately keep the surface
small — fields here must mean the same thing across every backend.
Attributes:
| Name | Type | Description |
|---|---|---|
wavelength_nm |
float
|
Exposure wavelength. 193 = ArF, 13.5 = EUV. |
na |
float
|
Numerical aperture (image-side). |
sigma |
float
|
Outer partial-coherence factor. |
sigma_inner |
float
|
Inner sigma for annular/dipole/quasar (0 = circular). |
pixel_size_nm |
float
|
Physical size of one mask pixel. |
defocus_nm |
float
|
Defocus offset. |
dose |
float
|
Linear dose multiplier. |
threshold |
float
|
Resist intensity threshold for binarization (0–1
relative to |
extra |
dict[str, Any]
|
Backend-specific options. Treated as opaque by the ABC. |
Source code in src/openlithohub/simulators/base.py
SimulatorResult
dataclass
¶
Output of a simulator forward pass.
Attributes:
| Name | Type | Description |
|---|---|---|
aerial |
Tensor
|
Aerial intensity image, same spatial shape as mask. |
resist |
Tensor | None
|
Optional binarized resist contour (0/1) at
:attr: |
backend |
str
|
Name of the simulator that produced the result. |
metadata |
dict[str, Any]
|
Free-form per-backend metadata (e.g. license info, vendor version, kernel count). Treated as opaque. |
Source code in src/openlithohub/simulators/base.py
Hopkins reference adapter¶
openlithohub.simulators.hopkins_sim
¶
Hopkins/SOCS simulator adapter — the bundled reference backend.
HopkinsSimulator
¶
Bases: BaseSimulator
Differentiable Hopkins/SOCS simulator.
Wraps :func:openlithohub._utils.hopkins.simulate_aerial_image_hopkins.
The full forward pass is auto-differentiable, so this adapter is the
right choice when used as a training-loop oracle for ILT or AI-OPC.
Backend-specific options read from config.extra:
illumination(circular|annular|dipole|quasar) — source shape, defaultcircular.num_kernels(int) — SOCS truncation order, default 24 (per Yang2023_LithoBench §3.2 / Table II — the reference SOCS decomposition for ICCAD16 evaluation).dipole_angle_deg,pole_opening_deg— pole geometry for dipole/quasar.
Source code in src/openlithohub/simulators/hopkins_sim.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
with_config(config)
¶
Clone, reusing cached SOCS kernels when only dose/threshold changed.
SOCS kernel construction depends on optical fields
(wavelength/NA/sigma/illumination/defocus/pixel_size_nm/num_kernels).
When those are unchanged, we can hand the new sibling our pre-built
:class:HopkinsParams instead of recomputing.
Source code in src/openlithohub/simulators/hopkins_sim.py
Vendor stubs¶
openlithohub.simulators.calibre
¶
Calibre nmOPC simulator adapter — stub.
Real Calibre integration requires the Mentor/Siemens EDA Calibre
toolchain on PATH plus a valid license file. We do not bundle
either, and we do not ship any code path that calls Calibre internals.
This stub exists so that:
- Downstream code can target
CalibreSimulatorsymbolically (e.g. in leaderboard config) without conditionally importing it. - The expected config schema is documented in one place.
- A user with Calibre access can subclass and override
:meth:
simulatewithout rewriting the surrounding wiring.
Track the integration RFC at
docs/rfcs/0003-commercial-simulator-hooks.md (TBD).
CalibreSimulator
¶
Bases: BaseSimulator
Stub adapter for Calibre nmOPC.
config.extra should carry:
calibre_home(str) — install root containingbin/calibre.runset(str) — path to the SVRF runset to execute.layer_map(dict[str, int]) — maps OpenLithoHub layer names to Calibre layer numbers.license_server(str, optional) — for non-default flexlm setups.
Source code in src/openlithohub/simulators/calibre.py
openlithohub.simulators.tachyon
¶
Tachyon simulator adapter — stub.
Real Tachyon integration requires the ASML Brion Tachyon toolchain on
PATH plus a license. We do not bundle either. See
:mod:openlithohub.simulators.calibre for the same policy and rationale.
TachyonSimulator
¶
Bases: BaseSimulator
Stub adapter for ASML Brion Tachyon.
config.extra should carry:
tachyon_home(str) — install root.recipe(str) — path to the.tclrecipe to execute.layer_map(dict[str, int]) — OpenLithoHub layer → Tachyon layer.
Source code in src/openlithohub/simulators/tachyon.py
Measured-source / Zernike-pupil I/O¶
openlithohub._utils.optics
¶
Standardized I/O for measured sources and pupil aberrations.
Hopkins / SOCS already accepts parametric sources (circular, annular, dipole, quasar) and a clean defocus-only pupil. Real production OPC needs more:
- Measured / freeform source intensity — a TIFF or PNG dumped from
the scanner illuminator, or a custom shape designed by SMO. This is
loaded as a 2-D
[N, N]array on normalized pupil coordinates(σx, σy) ∈ [-1, 1]. - Pupil aberrations as Zernike coefficients — typically tens of Zernikes (Z2..Z37 or higher) measured on the scanner. We parse a small text/JSON/CSV file mapping Noll-indexed terms → coefficients in waves of OPD, and synthesize a phase map on the same normalized pupil grid.
Both outputs are plain torch.Tensor so they can be plugged into a
custom Hopkins / SOCS run without further conversion. These three
functions are re-exported from :mod:openlithohub.simulators for
discoverability — the bundled :class:HopkinsSimulator does not
yet consume them through SimulatorConfig.extra; users who need
measured-source or Zernike-pupil pathing currently do so by calling
:func:openlithohub._utils.hopkins.simulate_aerial_image_hopkins
directly with the loaded tensor injected into a custom params object.
Wiring through the public HopkinsSimulator is roadmap (item #65 in
the bug-triage list once the API knobs are settled).
Coordinate convention: normalized pupil coordinates run from -1 to +1
across the diameter, with the unit circle being the NA-defined edge.
The center pixel is at (σx, σy) = (0, 0). For a square N x N
grid this places the center at index (N-1) / 2.
load_source_intensity(path, *, grid_size=None, normalize=True, device='cpu')
¶
Load a measured source intensity image as a square [N, N] tensor.
Supports any format Pillow can read: TIFF (16-bit OK), PNG, BMP, etc.
The image is converted to float32 in the range [0, ∞), optionally
resized to grid_size (bilinear), and optionally normalized so the
sum is 1 (which is what Hopkins J(f) expects).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Image file. |
required |
grid_size
|
int | None
|
If given, resize to |
None
|
normalize
|
bool
|
If True (default), rescale so the source sums to 1. |
True
|
device
|
str | device
|
Torch device for the output tensor. |
'cpu'
|
Returns:
| Type | Description |
|---|---|
Tensor
|
Float32 tensor on |
Tensor
|
pupil-coordinate layout (origin at the geometric center). |
Source code in src/openlithohub/_utils/optics.py
load_zernike_coefficients(path)
¶
Parse a Zernike coefficient file → {noll_index: coeff_in_waves}.
Three formats are supported, dispatched by extension:
.json— flat object{"4": 0.05, "11": -0.02, ...}or a nested form{"zernikes": {"4": 0.05, ...}}..csv— header row required, columnsnollandcoeff(case-insensitive). Other columns are ignored..txt— whitespace-separatednoll coeffpairs,#comments.
Coefficients are interpreted as waves of OPD (the scanner / Synopsys convention). Z1 (piston) is silently dropped — it has no optical effect and many vendor tools include it as a sanity column.
Source code in src/openlithohub/_utils/optics.py
zernike_phase_map(coeffs, grid_size, *, device='cpu')
¶
Synthesize a pupil OPD map from Noll-indexed Zernike coefficients.
Returns a real [N, N] tensor of optical path difference in waves
(multiply by 2π to get phase in radians, or by the wavelength to
get OPD in nm). Outside the unit pupil the map is set to 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coeffs
|
dict[int, float]
|
|
required |
grid_size
|
int
|
Output grid edge |
required |
device
|
str | device
|
Torch device. |
'cpu'
|