Skip to content

Commit 3a8d240

Browse files
feat(tidy3d): FXC-3693-triangle-mesh-support-for-adjoint
1 parent bad66c6 commit 3a8d240

File tree

9 files changed

+1253
-34
lines changed

9 files changed

+1253
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3737
- Introduced `BroadbandPulse` for exciting simulations across a wide frequency spectrum.
3838
- Added `interp_spec` in `ModeSpec` to allow downsampling and interpolation of waveguide modes in frequency.
3939
- Added warning if port mesh refinement is incompatible with the `GridSpec` in the `TerminalComponentModeler`.
40+
- Added support of `TriangleMesh` for autograd.
4041

4142
### Breaking Changes
4243
- Edge singularity correction at PEC and lossy metal edges defaults to `True`.

docs/api/geometry.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,5 +295,6 @@ Use the ``from_stl()`` class method to import from an external STL file, or ``fr
295295
+ `Importing STL files <../notebooks/STLImport.html>`_
296296
+ `Defining complex geometries using trimesh <../notebooks/CreatingGeometryUsingTrimesh.html>`_
297297

298-
~~~~
298+
Shape gradients for ``TriangleMesh`` geometries are supported through the autograd workflow. When a mesh participates in an adjoint optimization, boundary sensitivities are evaluated on the triangle faces. The cost of the surface integral scales with the number of mesh faces; very fine meshes may require additional sampling to converge gradients, so consider simplifying or coarsening meshes when possible, or adjusting the autograd configuration to trade off accuracy and runtime.
299299

300+
~~~~

tests/test_components/autograd/numerical/test_autograd_box_polyslab_numerical.py

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212

1313
import tidy3d as td
1414
import tidy3d.web as web
15+
from tidy3d import config
16+
17+
config.local_cache.enabled = True
1518

1619
WL_UM = 0.65
1720
FREQ0 = td.C_0 / WL_UM
@@ -41,6 +44,29 @@
4144
sys.stdout = sys.stderr
4245

4346

47+
def angled_overlap_deg(v1, v2):
48+
# zero-out tiny vectors elementwise
49+
thresh = 1e-6
50+
v1 = np.where(np.abs(v1) < thresh, 0.0, v1)
51+
v2 = np.where(np.abs(v2) < thresh, 0.0, v2)
52+
53+
norm_v1 = np.linalg.norm(v1)
54+
norm_v2 = np.linalg.norm(v2)
55+
56+
# handle degenerate vectors
57+
if norm_v1 < thresh or norm_v2 < thresh:
58+
# one is effectively zero, the other is not → undefined angle
59+
if not (norm_v1 < thresh and norm_v2 < thresh):
60+
return np.inf
61+
62+
return 0.0
63+
64+
dot = np.minimum(1.0, np.sum((v1 / np.linalg.norm(v1)) * (v2 / np.linalg.norm(v2))))
65+
angle_deg = np.arccos(dot) * 180.0 / np.pi
66+
67+
return angle_deg
68+
69+
4470
def case_identifier(is_3d: bool, infinite_dim_2d: int | None, shift_box_center: bool) -> str:
4571
geometry_tag = "3d" if is_3d else f"2d_infinite_dim_{infinite_dim_2d}"
4672
shift_tag = "shifted" if shift_box_center else "centered"
@@ -213,12 +239,12 @@ def run_parameter_simulations(
213239
medium=td.Medium(permittivity=PERMITTIVITY),
214240
)
215241

216-
sim = base_sim.updated_copy(structures=[structure])
242+
sim = base_sim.updated_copy(structures=[structure], validate=False)
217243
simulation_dict[f"sim_{idx}"] = sim
218244

219245
if len(simulation_dict) == 1:
220246
key, sim = next(iter(simulation_dict.items()))
221-
result_path = output_dir / f"{key}.hdf5"
247+
result_path = output_dir / f"{sim._hash_self()}.hdf5"
222248
sim_data = web.run(
223249
sim,
224250
task_name=key,
@@ -422,21 +448,6 @@ def test_box_and_polyslab_gradients_match(
422448
)
423449
np.savez(npz_path, **test_data)
424450

425-
def angled_overlap_deg(v1, v2):
426-
norm_v1 = np.linalg.norm(v1)
427-
norm_v2 = np.linalg.norm(v2)
428-
429-
if np.isclose(norm_v1, 0.0) or np.isclose(norm_v2, 0.0):
430-
if not (np.isclose(norm_v1, 0.0) and np.isclose(norm_v2, 0.0)):
431-
return np.inf
432-
433-
return 0.0
434-
435-
dot = np.minimum(1.0, np.sum((v1 / np.linalg.norm(v1)) * (v2 / np.linalg.norm(v2))))
436-
angle_deg = np.arccos(dot) * 180.0 / np.pi
437-
438-
return angle_deg
439-
440451
box_polyslab_overlap_deg = angled_overlap_deg(box_grad_filtered, polyslab_grad_filtered)
441452
fd_overlap_deg = angled_overlap_deg(fd_box, fd_polyslab)
442453
box_fd_adj_overlap_deg = angled_overlap_deg(box_grad_filtered, fd_box)
@@ -448,17 +459,14 @@ def angled_overlap_deg(v1, v2):
448459
print(f"Box Finite Difference vs. Adjoint: {box_fd_adj_overlap_deg}")
449460
print(f"PolySlab Finite Difference vs. Adjoint: {polyslab_fd_adj_overlap_deg}")
450461

451-
assert box_polyslab_overlap_deg < ANGLE_OVERLAP_THRESH_DEG, (
452-
"Autograd gradients for Box and PolySlab disagree"
453-
)
454-
assert fd_overlap_deg < ANGLE_OVERLAP_THRESH_DEG, (
455-
"Finite-difference gradients for Box and PolySlab disagree"
456-
)
457-
458462
if COMPARE_TO_FINITE_DIFFERENCE:
459463
assert box_fd_adj_overlap_deg < ANGLE_OVERLAP_FD_ADJ_THRESH_DEG, (
460-
"Autograd and finite-difference gradients for the Box geometry disagree"
464+
"Autograd and finite-difference gradients for the Box geometry disagree: "
465+
f"angle = {box_fd_adj_overlap_deg:.2f} deg, "
466+
f"threshold = {ANGLE_OVERLAP_FD_ADJ_THRESH_DEG:.2f} deg"
461467
)
462468
assert polyslab_fd_adj_overlap_deg < ANGLE_OVERLAP_FD_ADJ_THRESH_DEG, (
463-
"Autograd and finite-difference gradients for the PolySlab geometry disagree"
469+
"Autograd and finite-difference gradients for the PolySlab geometry disagree: "
470+
f"angle = {polyslab_fd_adj_overlap_deg:.2f} deg, "
471+
f"threshold = {ANGLE_OVERLAP_FD_ADJ_THRESH_DEG:.2f} deg"
464472
)

0 commit comments

Comments
 (0)