diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b1e7a3b3..27e6a6bcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- ``Selmeier.from_dispersion()`` method to quickly make a single-pole fit for lossless weakly dispersive materials. - Stable dispersive material fits via webservice. - Validates simulation based on discretized size. diff --git a/tests/test_components.py b/tests/test_components.py index 66d82e865..fbc80a3dc 100644 --- a/tests/test_components.py +++ b/tests/test_components.py @@ -449,6 +449,25 @@ def test_medium_dispersion_create(): struct = Structure(geometry=Box(size=(1, 1, 1)), medium=medium) +def test_sellmeier_from_dispersion(): + n = 3.5 + wvl = 0.5 + freq = C_0 / wvl + dn_dwvl = -0.1 + with pytest.raises(ValidationError) as e: + # Check that postivie dispersion raises an error + medium = Sellmeier.from_dispersion(n=n, freq=freq, dn_dwvl=-dn_dwvl) + + # Check that medium properties are as epected + medium = Sellmeier.from_dispersion(n=n, freq=freq, dn_dwvl=dn_dwvl) + epses = [medium.eps_model(f) for f in [0.99 * freq, freq, 1.01 * freq]] + ns = np.sqrt(epses) + dn_df = (ns[2] - ns[0]) / 0.02 / freq + + assert np.allclose(ns[1], n) + assert np.allclose(-dn_df * C_0 / wvl ** 2, dn_dwvl) + + def eps_compare(medium: Medium, expected: Dict, tol: float = 1e-5): for freq, val in expected.items(): diff --git a/tidy3d/components/medium.py b/tidy3d/components/medium.py index fa515115e..dcd21c356 100644 --- a/tidy3d/components/medium.py +++ b/tidy3d/components/medium.py @@ -13,7 +13,7 @@ from .validators import validate_name_str from ..constants import C_0, pec_val, EPSILON_0, HERTZ, CONDUCTIVITY, PERMITTIVITY, RADPERSEC -from ..log import log +from ..log import log, ValidationError def ensure_freq_in_range(eps_model: Callable[[float], complex]) -> Callable[[float], complex]: @@ -268,7 +268,7 @@ def from_nk(cls, n: float, k: float, freq: float): Real part of refractive index. k : float = 0 Imaginary part of refrative index. - frequency : float + freq : float Frequency to evaluate permittivity at (Hz). Returns @@ -507,6 +507,39 @@ def pole_residue(self): name=self.name, ) + @classmethod + def from_dispersion(cls, n: float, freq: float, dn_dwvl: float = 0): + """Convert ``n`` and wavelength dispersion ``dn_dwvl`` values at frequency ``freq`` to + a single-pole :class:`Sellmeier` medium. + + Parameters + ---------- + n : float + Real part of refractive index. Must be larger than or equal to one. + dn_dwvl : float = 0 + Derivative of the refractive index with wavelength (1/um). Must be negative. + frequency : float + Frequency to evaluate permittivity at (Hz). + + Returns + ------- + :class:`Medium` + medium containing the corresponding ``permittivity`` and ``conductivity``. + """ + + if dn_dwvl >= 0: + raise ValidationError("Dispersion ``dn_dwvl`` must be smaller than zero.") + if n < 1: + raise ValidationError("Refractive index ``n`` cannot be smaller than one.") + + wvl = C_0 / freq + nsqm1 = n ** 2 - 1 + c_coeff = -(wvl ** 3) * n * dn_dwvl / (nsqm1 - wvl * n * dn_dwvl) + b_coeff = (wvl ** 2 - c_coeff) / wvl ** 2 * nsqm1 + coeffs = [(b_coeff, c_coeff)] + + return cls(coeffs=coeffs) + class Lorentz(DispersiveMedium): """A dispersive medium described by the Lorentz model. diff --git a/tidy3d/log.py b/tidy3d/log.py index 2388cf3f9..bcf836c45 100644 --- a/tidy3d/log.py +++ b/tidy3d/log.py @@ -43,7 +43,7 @@ class Tidy3dKeyError(Tidy3dError): class ValidationError(Tidy3dError): - """eError when constructing Tidy3d components.""" + """Error when constructing Tidy3d components.""" class SetupError(Tidy3dError):