Skip to content

Commit e5c8f08

Browse files
Gregory Robertsyaugenst-flex
authored andcommitted
change[autograd]: set minimum discretization wavelength when computing autograd derivatives of cylinders and make adaptive spacing relative to minimum free space wavelength
1 parent 7aac129 commit e5c8f08

File tree

5 files changed

+115
-10
lines changed

5 files changed

+115
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
### Changed
1616
- Validate mode solver object for large number of grid points on the modal plane.
17+
- Adaptive minimum spacing for `PolySlab` integration is now wavelength relative and a minimum discretization is set for computing gradients for cylinders.
1718

1819
### Fixed
1920
- Fixed missing amplitude factor and handling of negative normal direction case when making adjoint sources from `DiffractionMonitor`.

tests/test_components/test_autograd.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919

2020
import tidy3d as td
2121
import tidy3d.web as web
22-
from tidy3d.components.autograd.constants import MAX_NUM_TRACED_STRUCTURES
22+
from tidy3d.components.autograd.constants import (
23+
MAX_NUM_TRACED_STRUCTURES,
24+
MIN_WVL_FRACTION_CYLINDER_DISCRETIZE,
25+
MINIMUM_SPACING_FRACTION,
26+
)
2327
from tidy3d.components.autograd.derivative_utils import DerivativeInfo
2428
from tidy3d.components.autograd.utils import is_tidy_box
2529
from tidy3d.components.data.data_array import DataArray
@@ -1638,6 +1642,70 @@ def f(eps_inf, poles):
16381642
assert np.isclose(grads_computed[field_path], grad_poles[i][j])
16391643

16401644

1645+
@pytest.mark.parametrize("eps_real", [1e6, -1e8])
1646+
def test_adaptive_spacing(eps_real):
1647+
freq = 5e9
1648+
1649+
info = DerivativeInfo(
1650+
paths={},
1651+
E_der_map={},
1652+
D_der_map={},
1653+
E_fwd={},
1654+
D_fwd={},
1655+
E_adj={},
1656+
D_adj={},
1657+
eps_data={},
1658+
eps_in=eps_real,
1659+
eps_out=1.0,
1660+
frequencies=[freq],
1661+
bounds=((-1, -1, -1), (1, 1, 1)),
1662+
eps_no_structure={},
1663+
eps_inf_structure={},
1664+
bounds_intersect=((-1, -1, -1), (1, 1, 1)),
1665+
)
1666+
1667+
with AssertLogLevel("WARNING", contains_str="Based on the material, the adaptive spacing"):
1668+
expected_vjp_spacing = info.wavelength_min * MINIMUM_SPACING_FRACTION
1669+
vjp_spacing = info.adaptive_vjp_spacing()
1670+
1671+
assert np.isclose(expected_vjp_spacing, vjp_spacing), "Unexpected adaptive vjp spacing!"
1672+
1673+
1674+
@pytest.mark.parametrize("eps_real", [1e6, -1e8])
1675+
def test_cylinder_discretization(eps_real):
1676+
freq = 5e9
1677+
1678+
info = DerivativeInfo(
1679+
paths={},
1680+
E_der_map={},
1681+
D_der_map={},
1682+
E_fwd={},
1683+
D_fwd={},
1684+
E_adj={},
1685+
D_adj={},
1686+
eps_data={},
1687+
eps_in=eps_real,
1688+
eps_out=1.0,
1689+
frequencies=[freq],
1690+
bounds=((-1, -1, -1), (1, 1, 1)),
1691+
eps_no_structure={},
1692+
eps_inf_structure={},
1693+
bounds_intersect=((-1, -1, -1), (1, 1, 1)),
1694+
)
1695+
1696+
with AssertLogLevel(
1697+
"WARNING", contains_str="The minimum wavelength inside the cylinder material"
1698+
):
1699+
cylinder = td.Cylinder(axis=2, length=info.wavelength_min, radius=2 * info.wavelength_min)
1700+
1701+
expected_wvl_mat = info.wavelength_min * MIN_WVL_FRACTION_CYLINDER_DISCRETIZE
1702+
wvl_mat = cylinder._discretization_wavelength(derivative_info=info)
1703+
1704+
assert np.isclose(expected_wvl_mat, wvl_mat), (
1705+
"Unexpected wavelength for discretizing cylinder!"
1706+
)
1707+
1708+
16411709
def test_custom_pole_residue(monkeypatch):
16421710
"""Test that computed pole residue derivatives match."""
16431711

tidy3d/components/autograd/constants.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import numpy as np
44

5+
# minimum fraction of minimum free space wavelength for discretizing cylinder in autograd derivative
6+
MIN_WVL_FRACTION_CYLINDER_DISCRETIZE = 5e-2
57
# default number of points per wvl in material for discretizing cylinder in autograd derivative
68
PTS_PER_WVL_MAT_CYLINDER_DISCRETIZE = 10
79

@@ -19,7 +21,8 @@
1921
AUTOGRAD_MONITOR_INTERVAL_SPACE_CUSTOM = (1, 1, 1)
2022

2123
DEFAULT_WAVELENGTH_FRACTION = 0.1
22-
MINIMUM_SPACING = 1e-2
24+
# minimum fraction of minimum free space wavelength to be used when computing adaptive spacing
25+
MINIMUM_SPACING_FRACTION = 1e-2
2326

2427
EDGE_CLIP_TOLERANCE = 1e-9
2528

tidy3d/components/autograd/derivative_utils.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
from tidy3d.components.data.data_array import FreqDataArray, ScalarFieldDataArray
1212
from tidy3d.components.types import ArrayLike, Bound, tidycomplex
1313
from tidy3d.constants import C_0, LARGE_NUMBER
14+
from tidy3d.log import log
1415

1516
from .constants import (
1617
DEFAULT_WAVELENGTH_FRACTION,
1718
GRADIENT_DTYPE_COMPLEX,
1819
GRADIENT_DTYPE_FLOAT,
19-
MINIMUM_SPACING,
20+
MINIMUM_SPACING_FRACTION,
2021
)
2122
from .types import PathType
2223
from .utils import get_static
@@ -430,7 +431,7 @@ def _project_in_basis(
430431
def adaptive_vjp_spacing(
431432
self,
432433
wl_fraction: float = DEFAULT_WAVELENGTH_FRACTION,
433-
min_allowed_spacing: float = MINIMUM_SPACING,
434+
min_allowed_spacing_fraction: float = MINIMUM_SPACING_FRACTION,
434435
) -> float:
435436
"""Compute adaptive spacing for finite-difference gradient evaluation.
436437
@@ -441,8 +442,9 @@ def adaptive_vjp_spacing(
441442
----------
442443
wl_fraction : float = 0.1
443444
Fraction of wavelength/skin depth to use as spacing.
444-
min_allowed_spacing : float = 1e-2
445-
Minimum allowed spacing to prevent numerical issues.
445+
min_allowed_spacing_fraction : float = 1e-2
446+
Minimum allowed spacing fraction of free space wavelength to
447+
prevent numerical issues.
446448
447449
Returns
448450
-------
@@ -471,7 +473,17 @@ def adaptive_vjp_spacing(
471473
delta_min = C_0 / (omega * np.sqrt(np.abs(eps_neg).max()))
472474
dx_candidates.append(wl_fraction * delta_min)
473475

474-
return max(min(dx_candidates), min_allowed_spacing)
476+
computed_spacing = min(dx_candidates)
477+
min_allowed_spacing = self.wavelength_min * min_allowed_spacing_fraction
478+
479+
if computed_spacing < min_allowed_spacing:
480+
log.warning(
481+
f"Based on the material, the adaptive spacing for integrating the polyslab surface "
482+
f"would be {computed_spacing:.3e} μm. The spacing has been clipped to {min_allowed_spacing:.3e} μm "
483+
f"to prevent a performance degradation.",
484+
log_once=True,
485+
)
486+
return max(computed_spacing, min_allowed_spacing)
475487

476488
@property
477489
def wavelength_min(self) -> float:

tidy3d/components/geometry/primitives.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@
1111
import shapely
1212

1313
from tidy3d.components.autograd import AutogradFieldMap, TracedSize1D
14-
from tidy3d.components.autograd.constants import PTS_PER_WVL_MAT_CYLINDER_DISCRETIZE
14+
from tidy3d.components.autograd.constants import (
15+
MIN_WVL_FRACTION_CYLINDER_DISCRETIZE,
16+
PTS_PER_WVL_MAT_CYLINDER_DISCRETIZE,
17+
)
1518
from tidy3d.components.autograd.derivative_utils import DerivativeInfo
1619
from tidy3d.components.base import cached_property, skip_if_fields_missing
1720
from tidy3d.components.types import Axis, Bound, Coordinate, MatrixReal4x4, Shapely
1821
from tidy3d.constants import LARGE_NUMBER, MICROMETER
1922
from tidy3d.exceptions import SetupError, ValidationError
23+
from tidy3d.log import log
2024
from tidy3d.packaging import verify_packages_import
2125

2226
from . import base
@@ -278,12 +282,29 @@ def _points_unit_circle(
278282
ys = np.sin(angles)
279283
return np.stack((xs, ys), axis=0)
280284

285+
def _discretization_wavelength(self, derivative_info: DerivativeInfo) -> float:
286+
"""Choose a reference wavelength for discretizing the cylinder into a `PolySlab`."""
287+
wvl0_min = derivative_info.wavelength_min
288+
wvl_mat = wvl0_min / np.max([1.0, np.max(np.sqrt(abs(derivative_info.eps_in)))])
289+
290+
min_wvl_mat = MIN_WVL_FRACTION_CYLINDER_DISCRETIZE * wvl0_min
291+
if wvl_mat < min_wvl_mat:
292+
log.warning(
293+
f"The minimum wavelength inside the cylinder material is {wvl_mat:.3e} μm, which would "
294+
f"create a large number of discretization points for computing the gradient. "
295+
f"To prevent performance degradation, the discretization wavelength has "
296+
f"been clipped to {min_wvl_mat:.3e} μm.",
297+
log_once=True,
298+
)
299+
wvl_mat = max(wvl_mat, min_wvl_mat)
300+
301+
return wvl_mat
302+
281303
def _compute_derivatives(self, derivative_info: DerivativeInfo) -> AutogradFieldMap:
282304
"""Compute the adjoint derivatives for this object."""
283305

284306
# compute circumference discretization
285-
wvl0_min = derivative_info.wavelength_min
286-
wvl_mat = wvl0_min / np.max([1.0, np.max(np.sqrt(abs(derivative_info.eps_in)))])
307+
wvl_mat = self._discretization_wavelength(derivative_info=derivative_info)
287308

288309
circumference = 2 * np.pi * self.radius
289310
wvls_in_circumference = circumference / wvl_mat

0 commit comments

Comments
 (0)