Source code for maxwelllink.cavity.vacuum

# --------------------------------------------------------------------------------------#
# Copyright (c) 2026 MaxwellLink                                                        #
# This file is part of MaxwellLink. Repository: https://github.com/TaoELi/MaxwellLink   #
# If you use this code, always credit and cite arXiv:2512.06173.                        #
# See AGENTS.md and README.md for details.                                              #
# --------------------------------------------------------------------------------------#

"""
Free space: an empty FDTD cell with absorbing (PML) boundaries.
"""

import meep as mp

from .dummy_cavity import DummyCavity, CYLINDRICAL


[docs] class Vacuum(DummyCavity): """ An empty FDTD cell with absorbing (PML) boundaries, aka free space. Useful for spontaneous-emission and free-propagation tests. All placement and measurement methods are inherited from ``DummyCavity`` unchanged. Notes ----- With ``dimensions=mxl.CYLINDRICAL`` the cell is an (r, z) half plane: the axis sits at r = 0, the allowed region spans r in [0, size_r] and z in [-size_z/2, +size_z/2]. Since the field of a z-polarized dipole on the axis has full rotational symmetry, an m = 0 cylindrical run (``make_simulation(m=0)``) reproduces 3D free-space physics at 2D cost. An on-axis x- or y-polarized dipole can instead use one complex m = +1 or m = -1 sector. Examples -------- >>> from maxwelllink.cavity import Vacuum >>> cav = Vacuum(size_nm=4000.0, omega_ref=2320.0, units="cm-1", dimensions=1) >>> mol = cav.place_molecule(driver="tls", driver_kwargs=dict( ... omega=0.0106, # driver parameters are in a.u.; ~2326 cm^-1 ... mu12=187.0, orientation=2, pe_initial=1e-4)) >>> sim = cav.make_simulation(molecules=[mol]) >>> sim.run(until=200) """
[docs] def __init__( self, size_nm, omega_ref: float, units: str = "cm-1", dimensions: int = 1, resolution: float = None, pml_nm: float = None, ): """ Initialize the parameters of an empty FDTD cell (free space). Parameters ---------- size_nm : float or sequence of floats Interior size (nm) of the allowed region, *excluding* the PML that is added outside. A scalar gives an equal extent along every active axis; a sequence must have one entry per active axis such as (x,), (x, y), (x, y, z), or (r, z) for cylindrical cells. omega_ref : float Reference frequency (or wavelength) that sets the default resolution and PML thickness. units : str, default: "cm-1" Units of ``omega_ref``: "cm-1", "eV", "au", "nm", or "um". dimensions : int, default: 1 1, 2, 3, or mxl.CYLINDRICAL. resolution : float or None, optional Meep resolution (pixels per Meep length unit). Default: 20 pixels per reference wavelength (the DummyCavity default). pml_nm : float or None, optional PML thickness in nm. Default: one reference wavelength. """ # default attributes (units, grid, hotspot, ...), then resize below super().__init__(omega=omega_ref, units=units, dimensions=dimensions) lam = self.nm_to_meep(self.wavelength_nm) # reference wavelength in um # one interior extent per active axis, in Meep units (um) axes = self._active_axes() # "x", "xy", "xyz", or "xz" (cylindrical) if not hasattr(size_nm, "__len__"): # a single number interior = [self.nm_to_meep(size_nm)] * len(axes) else: if len(size_nm) != len(axes): raise ValueError( f"size_nm must be a scalar or a sequence of {len(axes)} " f"entries for this cell (active axes: {', '.join(axes)})." ) interior = [self.nm_to_meep(v) for v in size_nm] if resolution is not None: self.resolution = float(resolution) # the PML is added outside the requested interior on every boundary # (default thickness: one reference wavelength) self.pml_thickness = self.nm_to_meep(pml_nm) if pml_nm is not None else lam if self.dimensions == CYLINDRICAL: # the axis at r = 0 is not a boundary: the interior spans # r in [0, size_r], with PML at the outer r edge and both z ends self.cell_size = mp.Vector3( interior[0] + self.pml_thickness, 0.0, interior[1] + 2.0 * self.pml_thickness, ) self.allowed_bounds = { "x": (0.0, interior[0]), # x plays the role of r "z": (-0.5 * interior[1], 0.5 * interior[1]), } else: cell = [0.0, 0.0, 0.0] for i in range(self.dimensions): cell[i] = interior[i] + 2.0 * self.pml_thickness self.cell_size = mp.Vector3(*cell) self.allowed_bounds = { axis: (-0.5 * interior[i], 0.5 * interior[i]) for i, axis in enumerate(axes) } self.boundary_layers = [mp.PML(thickness=self.pml_thickness)] self.predicted = { "wavelength_ref_nm": self.wavelength_nm, "omega_ref_cminv": 1.0e7 / self.wavelength_nm, } self._warn_if_coarse()