Adapters
Export a Maxwell Scene to a Tidy3D Simulation, and import or export extruded polygon geometry through GDS files.
This page covers the interoperability adapters in witwin.maxwell.adapters. Use them to cross-check a Maxwell scene in Tidy3D, or to move planar layout geometry between Maxwell and a GDS file.
The adapter functions are not re-exported from witwin.maxwell. Import them from their modules:
from witwin.maxwell.adapters.tidy3d import scene_to_tidy3d
from witwin.maxwell.adapters.gds import from_gds, to_gds_fileEach adapter needs an optional package that Maxwell does not install for you:
| Adapter | Package | Install |
|---|---|---|
witwin.maxwell.adapters.tidy3d | tidy3d | pip install tidy3d |
witwin.maxwell.adapters.gds | gdstk | pip install gdstk |
The package is imported only when you call a function. If it is missing, the call raises ImportError with the install command.
For a task-oriented walkthrough, see Interoperability.
scene_to_tidy3d#
scene_to_tidy3d(
scene,
*,
frequencies=None,
run_time=None,
length_scale=1e6,
**kwargs,
) -> tidy3d.SimulationConverts a Maxwell Scene to a tidy3d.Simulation. Scene.to_tidy3d(...) calls this function with the same arguments.
Scene to convert. It must have a Domain.
NoneFrequencies in Hz used for frequency-domain monitors, port drives, and some material fits. Required when a frequency-domain monitor has no frequencies of its own, when the scene has a WavePort or LumpedPort, or when a material is a lossy metal.
NoneTidy3D run time in seconds. None uses 10 light-crossing times of the largest domain extent.
1e6Factor from Maxwell lengths (metres) to Tidy3D lengths. The default converts metres to micrometres, the Tidy3D convention. Frequencies stay in Hz.
Any other keyword argument is forwarded to tidy3d.Simulation and overrides the converted value. A courant keyword is also used to compute the time step for time-domain monitors (0.99 when absent).
Returns a tidy3d.Simulation.
Raises ImportError when tidy3d is not installed, ValueError when required frequencies or a domain are missing, and NotImplementedError for scene features that have no Tidy3D equivalent (listed below).
import witwin.maxwell as mw
td_sim = scene.to_tidy3d(frequencies=1e9)
# or, without the Scene method:
from witwin.maxwell.adapters.tidy3d import scene_to_tidy3d
td_sim = scene_to_tidy3d(scene, frequencies=[0.6e9, 1.0e9, 1.6e9], run_time=1e-9)The function only builds the Tidy3D object. Running it (for example with tidy3d.web.run) is up to you and your Tidy3D account.
What the export maps#
| Maxwell | Tidy3D |
|---|---|
Box, Sphere, Cylinder | Box, Sphere, Cylinder |
Cone | Cylinder with a sidewall angle (rotated cones become a TriangleMesh) |
Ellipsoid | Sphere when all radii are equal, otherwise TriangleMesh |
PolySlab | PolySlab when unrotated, at the origin, and vertical; otherwise TriangleMesh |
| Other geometry with a surface mesh | TriangleMesh |
| PEC material | PECMedium |
| Non-dispersive material | Medium |
| Dispersive material | PoleResidue |
PointDipole, PlaneWave, GaussianBeam, AstigmaticGaussianBeam, ModeSource, UniformCurrentSource, CustomFieldSource, CustomCurrentSource | Source of the same name (a TFSF plane wave or beam maps to a TFSF source) |
CW / GaussianPulse / RickerWavelet | ContinuousWave / GaussianPulse / GaussianPulse |
WavePort / LumpedPort | Reference mode or current source plus receive monitors |
PointMonitor, PlaneMonitor, FinitePlaneMonitor | FieldMonitor, or FluxMonitor when compute_flux=True |
FieldTimeMonitor, FluxTimeMonitor | FieldTimeMonitor, FluxTimeMonitor |
DiffractionMonitor, PermittivityMonitor, ModeMonitor | Monitor of the same name |
| PML, periodic, PEC, PMC, Bloch, mixed boundaries | Matching BoundarySpec ("none" faces become PEC) |
Port drive convention: when the scene has wave ports, the first WavePort drives the export; otherwise the first LumpedPort does. Every wave port gets a receive mode monitor. TerminalPort is not mapped.
Symmetry exports only when it is equivalent to Tidy3D's center-based symmetry. Along each symmetry axis, every structure and MaterialRegion must be a Box that spans the full domain, and every source must be a PlaneWave with no direction component along that axis. Otherwise the export raises NotImplementedError. The folded face (low or high) is dropped.
What raises NotImplementedError#
The export fails instead of approximating when the scene uses:
- Magnetic materials (
mu_rother than 1, magnetic conductivity, magnetic dispersion, gyromagnetic ferrites, anisotropic permeability). - A full off-diagonal permittivity tensor combined with dispersion.
- Second-order (chi2) nonlinearity.
- Time modulation that Tidy3D cannot represent, or a spatially varying modulation profile.
- A spatially varying custom pole or perturbation medium.
- A generic
SurfaceImpedanceMedium. - A
MaterialRegionthat is not aBox, has a density filter, has non-uniform density, or has permeability other than 1. - A source or monitor type that is not in the table above.
- Automatic Bloch wavevectors that were not resolved by
Simulation.prepare()(raisesValueError).
from_gds#
from_gds(
gds_path,
*,
layer,
bounds,
datatype=0,
cell=None,
axis="z",
sidewall_angle=0.0,
reference_plane="middle",
length_scale=None,
) -> list[PolySlab]Reads polygons from one layer of a GDS file and returns them as extruded PolySlab geometries. Cell references are flattened (repetitions applied) and paths are converted to polygons. Each GDS polygon becomes one PolySlab; holes drawn as separate loops import as separate slabs.
Path to the .gds file.
GDS layer number to import.
(min, max) extrusion extent along axis, in metres.
0GDS datatype number to import.
NoneCell name to read. None selects the unique top-level cell.
'z'Extrusion axis: "x", "y", "z", or 0, 1, 2.
0.0PolySlab sidewall angle in radians.
'middle'PolySlab reference plane: "bottom", "middle", or "top".
NoneMetres per GDS user unit. None uses the file's library.unit (typically 1e-6), so vertices land in metres.
Returns one PolySlab per polygon on (layer, datatype). See Geometry.
Raises ImportError when gdstk is missing. Raises ValueError when the file does not exist, the cell is not found, there is more than one top-level cell and cell is None, or the layer has no polygons.
import witwin.maxwell as mw
from witwin.maxwell.adapters.gds import from_gds
slabs = from_gds("layout.gds", layer=1, bounds=(0.0, 220e-9))
silicon = mw.Material(eps_r=12.0)
for index, slab in enumerate(slabs):
scene.add_structure(mw.Structure(name=f"wg{index}", geometry=slab, material=silicon))to_gds_file#
to_gds_file(
geometries,
gds_path,
*,
cell_name="TOP",
layer=0,
datatype=0,
length_scale=1e6,
)Writes the 2D cross-sections of PolySlab geometries to a GDS file. The in-plane part of each position is applied. Extrusion bounds, sidewall_angle, and reference_plane have no GDS representation and are dropped; the reference-plane cross-section is written. Multi-loop geometry (a ComplexPolySlab with holes) is folded with XOR into simple polygons so holes stay holes.
One PolySlab or ComplexPolySlab, or an iterable of them.
Output .gds file path.
'TOP'Name of the written cell.
0GDS layer number for all polygons.
0GDS datatype number for all polygons.
1e6GDS user units per metre. The default writes micrometre user units; the library unit is 1 / length_scale.
Returns None.
Raises ImportError when gdstk is missing. Raises ValueError for geometry that is not a PolySlab or has a non-identity rotation.
from witwin.maxwell.adapters.gds import from_gds, to_gds_file
slabs = from_gds("layout.gds", layer=1, bounds=(0.0, 220e-9))
to_gds_file(slabs, "layout_out.gds", layer=1)