Skip to content

Commit

Permalink
More options in DispersionFitter and fit constant loss tangent model
Browse files Browse the repository at this point in the history
  • Loading branch information
weiliangjin2021 committed Apr 30, 2024
1 parent 4180e20 commit d2cfb5e
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 0 deletions.
32 changes: 32 additions & 0 deletions tests/test_plugins/test_dispersion_fitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ def test_lossless_dispersion(random_data, mock_remote_api):
fitter = FastDispersionFitter(wvl_um=wvl_um.tolist(), n_data=tuple(n_data))
medium, rms = fitter.fit(advanced_param=advanced_param)

# from permittivity data
fitter = FastDispersionFitter.from_complex_permittivity(wvl_um, n_data**2)
medium2, rms2 = fitter.fit(advanced_param=advanced_param)


@responses.activate
def test_lossy_dispersion(random_data, mock_remote_api):
Expand All @@ -106,6 +110,34 @@ def test_lossy_dispersion(random_data, mock_remote_api):
fitter = FastDispersionFitter(wvl_um=wvl_um.tolist(), n_data=n_data, k_data=k_data)
medium, rms = fitter.fit(advanced_param=advanced_param)

# from permittivity data
eps_complex = (n_data + 1j * k_data) ** 2
fitter = FastDispersionFitter.from_complex_permittivity(
wvl_um, eps_complex.real, eps_complex.imag
)
medium2, rms2 = fitter.fit(advanced_param=advanced_param)

# from loss tangent
fitter = FastDispersionFitter.from_loss_tangent(
wvl_um, eps_complex.real, eps_complex.imag / eps_complex.real
)
medium3, rms3 = fitter.fit(advanced_param=advanced_param)


def test_constant_loss_tangent():
"""perform fitting on constant loss tangent material"""

eps_real = 2.5
loss_tangent = 1e-2
frequency_range = (1e9, 6e9)
mat = FastDispersionFitter.constant_loss_tangent_model(eps_real, loss_tangent, frequency_range)

# validate
sampling_frequency = np.linspace(frequency_range[0], frequency_range[1], 29)
eps_out, loss_tangent_out = mat.loss_tangent_model(sampling_frequency)
assert np.max(np.abs(eps_out - eps_real)) < 2e-2
assert np.max(np.abs(loss_tangent_out - loss_tangent)) / loss_tangent < 2e-2


@responses.activate
def test_dispersion_load_url():
Expand Down
50 changes: 50 additions & 0 deletions tidy3d/components/medium.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,21 @@ def nk_model(self, frequency: float) -> Tuple[float, float]:
eps_complex = self.eps_model(frequency=frequency)
return self.eps_complex_to_nk(eps_complex)

def loss_tangent_model(self, frequency: float) -> Tuple[float, float]:
"""Permittivity and loss tangent as a function of frequency.
Parameters
----------
frequency : float
Frequency to evaluate permittivity at (Hz).
Returns
-------
Tuple[float, float]
Real part of permittivity and loss tangent.
"""
eps_complex = self.eps_model(frequency=frequency)
return self.eps_complex_to_eps_loss_tangent(eps_complex)

@ensure_freq_in_range
def eps_diagonal(self, frequency: float) -> Tuple[complex, complex, complex]:
"""Main diagonal of the complex-valued permittivity tensor as a function of frequency.
Expand Down Expand Up @@ -963,6 +978,41 @@ def eps_complex_to_eps_sigma(eps_complex: complex, freq: float) -> Tuple[float,
sigma = omega * eps_imag * EPSILON_0
return eps_real, sigma

@staticmethod
def eps_complex_to_eps_loss_tangent(eps_complex: complex) -> Tuple[float, float]:
"""Convert complex permittivity to permittivity and loss tangent.
Parameters
----------
eps_complex : complex
Complex-valued relative permittivity.
Returns
-------
Tuple[float, float]
Real part of relative permittivity & loss tangent
"""
eps_real, eps_imag = eps_complex.real, eps_complex.imag
return eps_real, eps_imag / eps_real

@staticmethod
def eps_loss_tangent_to_eps_complex(eps_real: float, loss_tangent: float) -> complex:
"""Convert permittivity and loss tangent to complex permittivity.
Parameters
----------
eps_real : float
Real part of relative permittivity
loss_tangent : float
Loss tangent
Returns
-------
eps_complex : complex
Complex-valued relative permittivity.
"""
return eps_real * (1 + 1j * loss_tangent)

@ensure_freq_in_range
def sigma_model(self, freq: float) -> complex:
"""Complex-valued conductivity as a function of frequency.
Expand Down
60 changes: 60 additions & 0 deletions tidy3d/plugins/dispersion/fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,3 +697,63 @@ def from_file(cls, fname: str, **loadtxt_kwargs):
else:
wvl_um, n_data, k_data = data.T
return cls(wvl_um=wvl_um, n_data=n_data, k_data=k_data)

@classmethod
def from_complex_permittivity(
cls,
wvl_um: ArrayFloat1D,
eps_real: ArrayFloat1D,
eps_imag: ArrayFloat1D = None,
wvl_range: Tuple[Optional[float], Optional[float]] = (None, None),
):
"""Loads :class:`DispersionFitter` from wavelength and complex permittivity data
Parameters
----------
wvl_um : ArrayFloat1D
Wavelength data in micrometers.
eps_real : ArrayFloat1D
Real parts of permittivity data
eps_imag : Optional[ArrayFloat1D]
Imageinary parts of permittivity data; `None` for lossless medium.
wvg_range : Tuple[Optional[float], Optional[float]]
Wavelength range [wvl_min,wvl_max] for fitting.
Returns
-------
:class:`DispersionFitter`
A :class:`DispersionFitter` instance.
"""
if eps_imag is None:
n, _ = AbstractMedium.eps_complex_to_nk(eps_real + 0j)
return cls(wvl_um=wvl_um, n_data=n, wvl_range=wvl_range)
n, k = AbstractMedium.eps_complex_to_nk(eps_real + eps_imag * 1j)
return cls(wvl_um=wvl_um, n_data=n, k_data=k, wvl_range=wvl_range)

@classmethod
def from_loss_tangent(
cls,
wvl_um: ArrayFloat1D,
eps_real: ArrayFloat1D,
loss_tangent: ArrayFloat1D,
wvl_range: Tuple[Optional[float], Optional[float]] = (None, None),
):
"""Loads :class:`DispersionFitter` from wavelength and loss tangent data.
Parameters
----------
wvl_um : ArrayFloat1D
Wavelength data in micrometers.
eps_real : ArrayFloat1D
Real parts of permittivity data
loss_tangent : Optional[ArrayFloat1D]
Loss tangent data, defined as the ratio of imaginary and real parts of permittivity.
wvg_range : Tuple[Optional[float], Optional[float]]
Wavelength range [wvl_min,wvl_max] for fitting.
Returns
-------
:class:`DispersionFitter`
A :class:`DispersionFitter` instance.
"""
eps_complex = AbstractMedium.eps_loss_tangent_to_eps_complex(eps_real, loss_tangent)
n, k = AbstractMedium.eps_complex_to_nk(eps_complex)
return cls(wvl_um=wvl_um, n_data=n, k_data=k, wvl_range=wvl_range)
46 changes: 46 additions & 0 deletions tidy3d/plugins/dispersion/fit_fast.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ...components.medium import PoleResidue, LOSS_CHECK_MIN, LOSS_CHECK_MAX, LOSS_CHECK_NUM
from ...components.types import ArrayFloat1D, ArrayComplex1D, ArrayFloat2D, ArrayComplex2D
from ...exceptions import ValidationError
from ...constants import C_0

# numerical tolerance for pole relocation for fast fitter
TOL = 1e-8
Expand Down Expand Up @@ -816,3 +817,48 @@ def make_configs():
)

return best_model.pole_residue, best_model.rms_error

@classmethod
def constant_loss_tangent_model(
cls,
eps_real: float,
loss_tangent: float,
frequency_range: Tuple[float, float],
max_num_poles: PositiveInt = DEFAULT_MAX_POLES,
number_sampling_frequency: PositiveInt = 10,
tolerance_rms: NonNegativeFloat = DEFAULT_TOLERANCE_RMS,
) -> PoleResidue:
"""Fit a constant loss tangent material model.
Parameters
----------
eps_real : float
Real part of permittivity
loss_tangent : float
Loss tangent.
frequency_range : Tuple[float, float]
Freqquency range for the material to exhibit constant loss tangent response.
max_num_poles : PositiveInt, optional
Maximum number of poles in the model.
number_sampling_frequency : PositiveInt, optional
Number of sampling frequencies to compute RMS error for fitting.
tolerance_rms : float, optional
Weighted RMS error below which the fit is successful and the result is returned.
Returns
-------
:class:`.PoleResidue
Best results of multiple fits.
"""
if number_sampling_frequency < 2:
frequencies = np.array([np.mean(frequency_range)])
else:
frequencies = np.linspace(
frequency_range[0], frequency_range[1], number_sampling_frequency
)
wvl_um = C_0 / frequencies
eps_real_array = np.ones_like(frequencies) * eps_real
loss_tangent_array = np.ones_like(frequencies) * loss_tangent
fitter = cls.from_loss_tangent(wvl_um, eps_real_array, loss_tangent_array)
material, _ = fitter.fit(max_num_poles=max_num_poles, tolerance_rms=tolerance_rms)
return material

0 comments on commit d2cfb5e

Please sign in to comment.