-
Notifications
You must be signed in to change notification settings - Fork 66
poc: adjoint surface monitors #2740
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,10 +22,13 @@ | |
GroupIndexDataArray, | ||
ModeDispersionDataArray, | ||
ModeIndexDataArray, | ||
SampleWeightDataArray, | ||
ScalarFieldDataArray, | ||
ScalarFieldTimeDataArray, | ||
ScalarModeFieldCylindricalDataArray, | ||
ScalarModeFieldDataArray, | ||
SurfaceScalarDataArray, | ||
SurfaceVectorDataArray, | ||
TimeDataArray, | ||
TriangleMeshDataArray, | ||
) | ||
|
@@ -675,6 +678,71 @@ class TriangleMeshDataset(Dataset): | |
) | ||
|
||
|
||
class SurfaceSamplesDataset(Dataset): | ||
"""Geometry surface samples and associated geometric metadata. | ||
|
||
- ``points``: Cartesian coordinates of each surface sample. | ||
- ``normals``: Outward-pointing unit normals at each sample. | ||
- ``perp1`` and ``perp2``: Optional orthonormal tangents spanning the local tangent plane. | ||
If not provided, we can construct a consistent basis from ``normals``. | ||
- ``weights``: Quadrature weights (e.g., differential area per sample). | ||
""" | ||
|
||
points: SurfaceVectorDataArray = pd.Field( | ||
..., title="Sample points", description="Sample coordinates (x,y,z) per surface sample." | ||
) | ||
normals: SurfaceVectorDataArray = pd.Field( | ||
..., title="Normals", description="Outward surface normals at each sample." | ||
) | ||
weights: SampleWeightDataArray = pd.Field( | ||
..., title="Weights", description="Quadrature weight or area per surface sample." | ||
) | ||
perp1: SurfaceVectorDataArray = pd.Field( | ||
None, | ||
title="Tangent 1", | ||
description="Optional first tangent vector per sample; orthonormal to the normal.", | ||
) | ||
perp2: SurfaceVectorDataArray = pd.Field( | ||
None, | ||
title="Tangent 2", | ||
description="Optional second tangent vector; completes an orthonormal basis.", | ||
) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we may want distance from points to the geometry edge - this would be useful for 2D PEC singularity correction in the gradient |
||
|
||
class AdjointDielectricSurfaceComponents(Dataset): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to support a more flexible integration scheme, instead of having surface components for a single type of material like this, we would want to have surface components specific to each type of interface (dielectric <-> dielectric, PEC <-> dielectric, lossy metal <-> dielectric, PEC <-> PEC (this one so we would know to ignore the integration here)). This would allow a dielectric geometry that was inside a PEC background to be integrated in the adjoint instead of assuming there was dielectric outside of it for example. With overlapping structures, we could potentially have multiple of these types of interfaces for a single geometry and each geometric edge could have different interfaces on it. So I would think that we could have something like
and then the samples would be for a single edge where an edge is a run of continuous interface. This edge maps to a geometric edge (i.e. - one of the sides of a |
||
"""Projected components recorded for dielectric adjoint surface VJP.""" | ||
|
||
samples: SurfaceSamplesDataset = pd.Field( | ||
..., title="Surface samples", description="Geometry surface samples and metadata." | ||
) | ||
Et1: SurfaceScalarDataArray = pd.Field( | ||
None, title="Et1", description="First tangential component of E at samples." | ||
) | ||
Et2: SurfaceScalarDataArray = pd.Field( | ||
None, title="Et2", description="Second tangential component of E at samples." | ||
) | ||
Dn: SurfaceScalarDataArray = pd.Field( | ||
None, title="Dn", description="Normal component of D at samples." | ||
) | ||
|
||
|
||
class AdjointPECSurfaceComponents(Dataset): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LossyMetal would look similar to this although we would include the Et1 and Et2 as well. |
||
"""Projected components recorded for PEC adjoint surface VJP.""" | ||
|
||
samples: SurfaceSamplesDataset = pd.Field( | ||
..., title="Surface samples", description="Geometry surface samples and metadata." | ||
) | ||
En: SurfaceScalarDataArray = pd.Field( | ||
None, title="En", description="Normal component of E at samples (outside PEC)." | ||
) | ||
Ht1: SurfaceScalarDataArray = pd.Field( | ||
None, title="Ht1", description="First tangential component of H at samples (outside PEC)." | ||
) | ||
Ht2: SurfaceScalarDataArray = pd.Field( | ||
None, title="Ht2", description="Second tangential component of H at samples (outside PEC)." | ||
) | ||
|
||
|
||
class TimeDataset(Dataset): | ||
"""Dataset for storing a function of time.""" | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1543,6 +1543,57 @@ def _storage_size_solver(self, num_cells: int, tmesh: ArrayFloat1D) -> int: | |
return BYTES_COMPLEX * num_cells * len(self.freqs) * 6 | ||
|
||
|
||
class AdjointSurfaceMonitor(FreqMonitor): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above - but I think a general |
||
"""Base class for adjoint shape-optimization surface monitors. | ||
|
||
Records boundary field samples on geometry surfaces within the monitor bounding box. | ||
Concrete subclasses define exactly which components are recorded. | ||
|
||
Parameters | ||
---------- | ||
sides : tuple['inside'|'outside', ...] | ||
Which sides of the surface to record. Concrete subclasses may constrain this | ||
(e.g., PEC outside-only). | ||
""" | ||
|
||
sides: tuple[Literal["inside", "outside"], ...] = pydantic.Field( | ||
("inside", "outside"), | ||
title="Sides to record", | ||
description="Which surface sides to record. Concrete monitors may constrain this.", | ||
) | ||
|
||
colocate: Literal[True] = pydantic.Field( | ||
True, | ||
title="Colocate Fields", | ||
description="Surface sampling is colocated to boundary locations by design.", | ||
) | ||
|
||
def storage_size(self, num_cells: int, tmesh: ArrayFloat1D) -> int: | ||
"""Conservative storage estimate: 3 complex components × freqs × sides × samples. | ||
|
||
Notes | ||
----- | ||
'num_cells' is treated as the number of surface samples (post-discretization). | ||
Concrete subclasses may adjust this if needed. | ||
""" | ||
nfreq = len(self.freqs) | ||
nsides = len(self.sides) | ||
ncomp = 3 | ||
return BYTES_COMPLEX * num_cells * nfreq * ncomp * nsides | ||
|
||
|
||
class AdjointDielectricSurfaceMonitor(AdjointSurfaceMonitor): | ||
"""Convenience class for dielectric adjoint surface recording.""" | ||
|
||
sides: tuple[Literal["inside", "outside"], ...] = ("inside", "outside") | ||
|
||
|
||
class AdjointPECSurfaceMonitor(AdjointSurfaceMonitor): | ||
"""Convenience class for PEC adjoint surface recording.""" | ||
|
||
sides: tuple[Literal["outside"], ...] = ("outside",) | ||
|
||
|
||
# types of monitors that are accepted by simulation | ||
MonitorType = Union[ | ||
FieldMonitor, | ||
|
@@ -1558,4 +1609,7 @@ def _storage_size_solver(self, num_cells: int, tmesh: ArrayFloat1D) -> int: | |
FieldProjectionKSpaceMonitor, | ||
DiffractionMonitor, | ||
DirectivityMonitor, | ||
AdjointSurfaceMonitor, | ||
AdjointDielectricSurfaceMonitor, | ||
AdjointPECSurfaceMonitor, | ||
] |
Uh oh!
There was an error while loading. Please reload this page.
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.
ultimately, to connect to the gradient, we will need to associate each of these points with the geometric parameter we are getting the gradient for. For PolySlab, for example, we will need a good way to sort these points as belonging to different edges for example. I wonder if possibly, we want the surface sample points broken down by edge. This would work well, I think, for Box and PolySlab, but it may not be as straightforward if we had something like a level set geometry in the future. See below as well - I think we may need each geometric edge to correspond to a list of interface edges where each interface edge is a continuous run of inside and outside materials being the same combination.