-
Notifications
You must be signed in to change notification settings - Fork 65
feat(adjoint): sidewall angle gradients #2747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK! A few comments and suggestions. Thanks @yaugenst-flex
5560c04
to
1265b8e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good to me, small note on testing!
tests/test_components/test_autograd_polyslab_sidewall_numerical.py
Outdated
Show resolved
Hide resolved
0e7cbc6
to
73c16e8
Compare
@groberts-flex @tylerflex ok polished it up, i think this is ready to go. waiting for dario's merge so i can run the numerical tests but other than that i think it's done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
10 files reviewed, 1 comment
73c16e8
to
757d4cd
Compare
Diff CoverageDiff: origin/develop...HEAD, staged and unstaged changes
Summary
tidy3d/components/geometry/polyslab.pyLines 1519-1527 1519
1520 z0 = max(self.slab_bounds[0], sim_min[self.axis])
1521 z1 = min(self.slab_bounds[1], sim_max[self.axis])
1522 if z1 <= z0:
! 1523 return np.array([], dtype=GRADIENT_DTYPE_FLOAT), 0.0, z0, z1
1524
1525 n_z = max(1, int(np.ceil((z1 - z0) / dx)))
1526 dz = (z1 - z0) / n_z
1527 z_centers = np.linspace(z0 + dz / 2, z1 - dz / 2, n_z, dtype=GRADIENT_DTYPE_FLOAT) Lines 1553-1561 1553 if t_start >= t_end:
1554 return None
1555
1556 if t_end <= t_start + EDGE_CLIP_TOLERANCE:
! 1557 return None
1558
1559 return (t_start, t_end)
1560
1561 @staticmethod Lines 1626-1634 1626 z_centers, dz, z0, z1 = self._z_slices(sim_min, sim_max, is_2d=is_2d, dx=dx * cos_th)
1627
1628 # early exit: no slices
1629 if (not is_2d) and len(z_centers) == 0:
! 1630 return {
1631 "centers": np.empty((0, 3), dtype=GRADIENT_DTYPE_FLOAT),
1632 "normals": np.empty((0, 3), dtype=GRADIENT_DTYPE_FLOAT),
1633 "perps1": np.empty((0, 3), dtype=GRADIENT_DTYPE_FLOAT),
1634 "perps2": np.empty((0, 3), dtype=GRADIENT_DTYPE_FLOAT), Lines 1680-1688 1680 for ei, (v0, v1) in enumerate(zip(vertices, next_v)):
1681 edge_vec = v1 - v0
1682 L = np.linalg.norm(edge_vec)
1683 if np.isclose(L, 0.0):
! 1684 continue
1685
1686 # constant along edge: unit tangent in 3D (no axis component)
1687 t_edge = basis["perp1"][ei]
1688 # outward in-plane normal from canonical basis normal (axis-consistent) Lines 1692-1701 1692 if not np.isclose(nrm, 0.0):
1693 n2d = n2d / nrm
1694 else:
1695 # fallback to right-handed construction if degenerate
! 1696 tmp = np.cross(axis_vec, t_edge)
! 1697 n2d = tmp / (np.linalg.norm(tmp) + 1e-20)
1698
1699 for zc in z_centers:
1700 # offset at this slice along the in-plane outward normal
1701 d = -(zc - z_ref) * tan_th Lines 1717-1725 1717
1718 # densify tangentially as |theta| grows: ds_phys scales with cos(theta)
1719 s_list, w_list = self._adaptive_edge_samples(L, denom_edge, t0, t1)
1720 if len(s_list) == 0:
! 1721 continue
1722
1723 pts2d = v0 + np.outer(s_list, edge_vec)
1724 xyz = (
1725 self.unpop_axis_vect( Lines 1836-1844 1836 is_2d=False,
1837 dx=dx,
1838 )
1839 if patch["centers"].shape[0] == 0:
! 1840 return 0.0
1841
1842 # Shape-derivative factors:
1843 # - Offset: d(z) = -(z - z_ref) * tan(theta)
1844 # - Tangential rate: dd/dtheta = -(z - z_ref) * sec(theta)^2 tidy3d/components/geometry/primitives.pyLines 320-330 320 # build PolySlab derivative paths based on requested Cylinder paths
321 ps_paths = set()
322 for path in derivative_info.paths:
323 if path == ("length",):
! 324 ps_paths.update({("slab_bounds", 0), ("slab_bounds", 1)})
325 elif path == ("radius",):
! 326 ps_paths.add(("vertices",))
327 elif "center" in path:
328 _, center_index = path
329 _, (index_x, index_y) = self.pop_axis((0, 1, 2), axis=self.axis)
330 if center_index in (index_x, index_y): Lines 329-339 329 _, (index_x, index_y) = self.pop_axis((0, 1, 2), axis=self.axis)
330 if center_index in (index_x, index_y):
331 ps_paths.add(("vertices",))
332 else:
! 333 ps_paths.update({("slab_bounds", 0), ("slab_bounds", 1)})
! 334 elif path == ("sidewall_angle",):
! 335 ps_paths.add(("sidewall_angle",))
336
337 # pass interpolators to PolySlab if available to avoid redundant conversions
338 update_kwargs = {
339 "paths": list(ps_paths), Lines 347-368 347
348 vjps = {}
349 for path in derivative_info.paths:
350 if path == ("length",):
! 351 vjp_top = vjps_polyslab.get(("slab_bounds", 0), 0.0)
! 352 vjp_bot = vjps_polyslab.get(("slab_bounds", 1), 0.0)
353 vjps[path] = vjp_top - vjp_bot
354
355 elif path == ("radius",):
356 # transform polyslab vertices derivatives into radius derivative
! 357 xs_, ys_ = self._points_unit_circle(num_pts_circumference=num_pts_circumference)
! 358 if ("vertices",) not in vjps_polyslab:
! 359 vjps[path] = 0.0
360 else:
! 361 vjps_vertices_xs, vjps_vertices_ys = vjps_polyslab[("vertices",)].T
! 362 vjp_xs = np.sum(xs_ * vjps_vertices_xs)
! 363 vjp_ys = np.sum(ys_ * vjps_vertices_ys)
! 364 vjps[path] = vjp_xs + vjp_ys
365
366 elif "center" in path:
367 _, center_index = path
368 _, (index_x, index_y) = self.pop_axis((0, 1, 2), axis=self.axis) Lines 367-375 367 _, center_index = path
368 _, (index_x, index_y) = self.pop_axis((0, 1, 2), axis=self.axis)
369 if center_index == index_x:
370 if ("vertices",) not in vjps_polyslab:
! 371 vjps[path] = 0.0
372 else:
373 vjps_vertices_xs = vjps_polyslab[("vertices",)][:, 0]
374 vjps[path] = np.sum(vjps_vertices_xs)
375 elif center_index == index_y: Lines 373-392 373 vjps_vertices_xs = vjps_polyslab[("vertices",)][:, 0]
374 vjps[path] = np.sum(vjps_vertices_xs)
375 elif center_index == index_y:
376 if ("vertices",) not in vjps_polyslab:
! 377 vjps[path] = 0.0
378 else:
379 vjps_vertices_ys = vjps_polyslab[("vertices",)][:, 1]
380 vjps[path] = np.sum(vjps_vertices_ys)
381 else:
! 382 vjp_top = vjps_polyslab.get(("slab_bounds", 0), 0.0)
! 383 vjp_bot = vjps_polyslab.get(("slab_bounds", 1), 0.0)
384 vjps[path] = vjp_top + vjp_bot
385
! 386 elif path == ("sidewall_angle",):
387 # direct mapping: cylinder angle equals polyslab angle
! 388 vjps[path] = vjps_polyslab.get(("sidewall_angle",), 0.0)
389
390 else:
391 raise NotImplementedError(
392 f"Differentiation with respect to 'Cylinder' '{path}' field not supported. " |
fa5db23
to
3dec410
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for adding this! it looks good to me if numerical tests are looking good!
bd6935b
to
6afd711
Compare
6afd711
to
6392f93
Compare
Greptile Summary
Updated On: 2025-09-04 13:18:06 UTC
This PR implements sidewall angle gradients for automatic differentiation (adjoint optimization) in PolySlab and Cylinder geometries. The changes enable users to compute gradients with respect to sidewall angles, expanding Tidy3D's inverse design capabilities beyond existing vertex and bounds optimization.
The implementation consists of several key architectural changes:
Core Infrastructure: The
sidewall_angle
field in thePlanar
base class is upgraded fromfloat
toTracedFloat
, enabling autograd tracking. A newreference_axis_pos
property is added to standardize reference point calculations across different reference plane configurations.Gradient Computation: A comprehensive
_compute_derivative_sidewall_angle()
method is implemented in PolySlab that calculates Vector-Jacobian Products (VJP) through surface integration over sidewall patches. The mathematical approach correctly handles the relationship between sidewall angle changes and surface normal velocities, with proper area element corrections for slanted surfaces.Cylinder Support: The Cylinder class is enhanced to map sidewall angle gradients to its underlying PolySlab representation, with defensive programming to handle edge cases where derivative paths may not exist.
Web Platform Integration: New batch category types (
'tidy3d_autograd'
,'tidy3d_autograd_async'
,'autograd_fwd'
,'autograd_bwd'
) are added to properly categorize and route different phases of autograd computations.Validation Compatibility: Existing validation functions are updated to handle autograd-traced sidewall angles using
get_static()
andgetval()
functions, ensuring validation logic remains functional while preserving autograd traceability.The feature integrates seamlessly with Tidy3D's existing adjoint framework, following established patterns for parameter tracing and gradient computation. The implementation includes extensive mathematical handling of sidewall geometry transformations and proper numerical integration techniques.
Important Files Changed
Changed Files
tidy3d/web/api/container.py
tests/test_components/autograd/test_autograd.py
CHANGELOG.md
tidy3d/components/geometry/primitives.py
tidy3d/components/geometry/base.py
tidy3d/components/geometry/utils.py
tidy3d/components/geometry/polyslab.py
tests/test_components/autograd/numerical/test_autograd_polyslab_sidewall_numerical.py
tests/test_components/autograd/test_autograd_polyslab_sidewall.py
tests/test_components/autograd/test_sidewall_edge_cases.py
Confidence score: 4/5
Sequence Diagram