Skip to content

Commit 61d5146

Browse files
committed
feat: Adding gaussian monitors
1 parent 7e2fe59 commit 61d5146

File tree

3 files changed

+226
-2
lines changed

3 files changed

+226
-2
lines changed

tests/test_components/test_monitor.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,14 @@ def test_diffraction_validators():
344344
FREQS = np.array([1, 2, 3]) * 1e12
345345

346346

347+
def test_gaussian_overlap_monitors_basic():
348+
g = td.GaussianOverlapMonitor(size=(1, 1, 0), name="g", freqs=FREQS)
349+
a = td.AstigmaticGaussianOverlapMonitor(size=(1, 1, 0), name="a", freqs=FREQS)
350+
for m in (g, a):
351+
s = m.storage_size(num_cells=10, tmesh=[0.0, 1.0])
352+
assert isinstance(s, int) and s > 0
353+
354+
347355
def test_monitor():
348356
size = (1, 2, 3)
349357
center = (1, 2, 3)
@@ -381,10 +389,14 @@ def test_monitor():
381389
m10 = td.PermittivityMonitor(size=size, center=center, freqs=FREQS, name="perm")
382390
m11 = td.AuxFieldTimeMonitor(size=size, center=center, name="aux_field_time", fields=("Nfx",))
383391
m12 = td.MediumMonitor(size=size, center=center, freqs=FREQS, name="mat")
392+
m13 = td.GaussianOverlapMonitor(size=(1, 1, 0), center=center, freqs=FREQS, name="gauss")
393+
m14 = td.AstigmaticGaussianOverlapMonitor(
394+
size=(1, 1, 0), center=center, freqs=FREQS, name="astigauss"
395+
)
384396

385397
tmesh = np.linspace(0, 1, 10)
386398

387-
for m in [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m12]:
399+
for m in [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14]:
388400
m.storage_size(num_cells=100, tmesh=tmesh)
389401

390402
for m in [m2, m4]:

tidy3d/components/monitor.py

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,79 @@ def normal_axis(self) -> Axis:
330330
return self.size.index(0.0)
331331

332332

333-
class AbstractModeMonitor(PlanarMonitor, FreqMonitor):
333+
class AbstractOverlapMonitor(PlanarMonitor, FreqMonitor):
334+
""":class:`Monitor` that projects fields onto a specified basis and stores overlap amplitudes.
335+
336+
This base is shared by ModeMonitor and Gaussian-overlap monitors.
337+
"""
338+
339+
store_fields_direction: Direction = pydantic.Field(
340+
None,
341+
title="Store Fields",
342+
description="Propagation direction for the field profiles stored from overlap calculation.",
343+
)
344+
345+
colocate: bool = pydantic.Field(
346+
True,
347+
title="Colocate Fields",
348+
description="Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes).",
349+
)
350+
351+
conjugated_dot_product: bool = pydantic.Field(
352+
True,
353+
title="Conjugated Dot Product",
354+
description="Use conjugated or non-conjugated dot product for overlap/decomposition.",
355+
)
356+
357+
def plot(
358+
self,
359+
x: Optional[float] = None,
360+
y: Optional[float] = None,
361+
z: Optional[float] = None,
362+
ax: Ax = None,
363+
**patch_kwargs,
364+
) -> Ax:
365+
"""Plot this overlap monitor with an arrow indicating propagation direction."""
366+
ax = super().plot(x=x, y=y, z=z, ax=ax, **patch_kwargs)
367+
368+
kwargs_alpha = patch_kwargs.get("alpha")
369+
arrow_alpha = ARROW_ALPHA if kwargs_alpha is None else kwargs_alpha
370+
371+
ax = self._plot_arrow(
372+
x=x,
373+
y=y,
374+
z=z,
375+
ax=ax,
376+
direction=self._dir_arrow,
377+
bend_radius=None,
378+
bend_axis=None,
379+
color=ARROW_COLOR_MONITOR,
380+
alpha=arrow_alpha,
381+
both_dirs=True,
382+
)
383+
return ax
384+
385+
@cached_property
386+
def _dir_arrow(self) -> tuple[float, float, float]:
387+
"""Direction vector from angles used for overlap (children must define angles)."""
388+
theta, phi = self._angles
389+
dx = np.cos(phi) * np.sin(theta)
390+
dy = np.sin(phi) * np.sin(theta)
391+
dz = np.cos(theta)
392+
return self.unpop_axis(dz, (dx, dy), axis=self.normal_axis)
393+
394+
@property
395+
def _angles(self) -> tuple[float, float]:
396+
"""Angle tuple (theta, phi) in radians. Children override to supply values."""
397+
return (0.0, 0.0)
398+
399+
def _storage_size_solver(self, num_cells: int, tmesh: ArrayFloat1D) -> int:
400+
"""Default size of intermediate data; store all fields on plane for overlap."""
401+
num_sample = len(getattr(self, "freqs", [0]))
402+
return BYTES_COMPLEX * num_cells * num_sample * 6
403+
404+
405+
class AbstractModeMonitor(AbstractOverlapMonitor):
334406
""":class:`Monitor` that records mode-related data."""
335407

336408
mode_spec: ModeSpec = pydantic.Field(
@@ -393,6 +465,10 @@ def plot(
393465
)
394466
return ax
395467

468+
@cached_property
469+
def _angles(self) -> tuple[float, float]:
470+
return (self.mode_spec.angle_theta, self.mode_spec.angle_phi)
471+
396472
@cached_property
397473
def _dir_arrow(self) -> tuple[float, float, float]:
398474
"""Source direction normal vector in cartesian coordinates."""
@@ -433,6 +509,138 @@ def _storage_size_solver(self, num_cells: int, tmesh: ArrayFloat1D) -> int:
433509
return bytes_single
434510

435511

512+
class AbstractGaussianOverlapMonitor(AbstractOverlapMonitor):
513+
""":class:`Monitor` that records amplitudes from decomposition onto a Gaussian-like beam.
514+
515+
Common fields and behavior shared by GaussianOverlapMonitor and
516+
AstigmaticGaussianOverlapMonitor.
517+
"""
518+
519+
angle_theta: float = pydantic.Field(
520+
0.0,
521+
title="Polar Angle",
522+
description="Polar angle of propagation direction.",
523+
units=RADIAN,
524+
)
525+
526+
angle_phi: float = pydantic.Field(
527+
0.0,
528+
title="Azimuth Angle",
529+
description="Azimuth angle of propagation direction.",
530+
units=RADIAN,
531+
)
532+
533+
pol_angle: float = pydantic.Field(
534+
0,
535+
title="Polarization Angle",
536+
description="Specifies the angle between the electric field polarization of the "
537+
"source and the plane defined by the injection axis and the propagation axis (rad). "
538+
"``pol_angle=0`` (default) specifies P polarization, "
539+
"while ``pol_angle=np.pi/2`` specifies S polarization. "
540+
"At normal incidence when S and P are undefined, ``pol_angle=0`` defines: "
541+
"- ``Ey`` polarization for propagation along ``x``."
542+
"- ``Ex`` polarization for propagation along ``y``."
543+
"- ``Ex`` polarization for propagation along ``z``.",
544+
units=RADIAN,
545+
)
546+
547+
@property
548+
def _angles(self) -> tuple[float, float]:
549+
return (self.angle_theta, self.angle_phi)
550+
551+
def storage_size(self, num_cells: int, tmesh: ArrayFloat1D) -> int:
552+
"""Size of monitor storage given the number of points after discretization."""
553+
# store complex amplitudes for +/- directions
554+
num_dirs = 2
555+
return BYTES_COMPLEX * len(self.freqs) * num_dirs
556+
557+
558+
class GaussianOverlapMonitor(AbstractGaussianOverlapMonitor):
559+
""":class:`Monitor` that records amplitudes from decomposition onto a Gaussian beam.
560+
561+
Example
562+
-------
563+
>>> gauss = GaussianOverlapMonitor(
564+
... size=(0,3,3),
565+
... source_time=pulse,
566+
... pol_angle=np.pi / 2,
567+
... waist_radius=1.0)
568+
569+
Notes
570+
--------
571+
If one wants the focus 'in front' of the source, a negative value of ``waist_distance`` is needed.
572+
573+
.. image:: ../../_static/img/beam_waist.png
574+
:width: 30%
575+
:align: center
576+
"""
577+
578+
waist_radius: pydantic.PositiveFloat = pydantic.Field(
579+
1.0,
580+
title="Waist Radius",
581+
description="Radius of the beam at the waist.",
582+
units=MICROMETER,
583+
)
584+
585+
waist_distance: float = pydantic.Field(
586+
0.0,
587+
title="Waist Distance",
588+
description="Distance from the beam waist along the propagation direction. "
589+
"A positive value means the waist is positioned behind the source, considering the propagation direction. "
590+
"For example, for a beam propagating in the ``+`` direction, a positive value of ``beam_distance`` "
591+
"means the beam waist is positioned in the ``-`` direction (behind the source). "
592+
"A negative value means the beam waist is in the ``+`` direction (in front of the source). "
593+
"For an angled source, the distance is defined along the rotated propagation direction.",
594+
units=MICROMETER,
595+
)
596+
597+
598+
class AstigmaticGaussianOverlapMonitor(AbstractGaussianOverlapMonitor):
599+
""":class:`Monitor` that records amplitudes from decomposition onto an astigmatic Gaussian beam.
600+
601+
The simple astigmatic Gaussian distribution allows
602+
both an elliptical intensity profile and different waist locations for the two principal axes
603+
of the ellipse. When equal waist sizes and equal waist distances are specified in the two
604+
directions, this source becomes equivalent to :class:`GaussianBeam`.
605+
606+
Notes
607+
-----
608+
609+
This class implements the simple astigmatic Gaussian beam described in _`[1]`.
610+
611+
**References**:
612+
613+
.. [1] Kochkina et al., Applied Optics, vol. 52, issue 24, 2013.
614+
615+
Example
616+
-------
617+
>>> gauss = AstigmaticGaussianOverlapMonitor(
618+
... size=(0,3,3),
619+
... pol_angle=np.pi / 2,
620+
... waist_sizes=(1.0, 2.0),
621+
... waist_distances = (3.0, 4.0))
622+
"""
623+
624+
waist_sizes: tuple[pydantic.PositiveFloat, pydantic.PositiveFloat] = pydantic.Field(
625+
(1.0, 1.0),
626+
title="Waist sizes",
627+
description="Size of the beam at the waist in the local x and y directions.",
628+
units=MICROMETER,
629+
)
630+
631+
waist_distances: tuple[float, float] = pydantic.Field(
632+
(0.0, 0.0),
633+
title="Waist distances",
634+
description="Distance to the beam waist along the propagation direction "
635+
"for the waist sizes in the local x and y directions. "
636+
"When ``direction`` is ``+`` and ``waist_distances`` are positive, the waist "
637+
"is on the ``-`` side (behind) the source plane. When ``direction`` is ``+`` and "
638+
"``waist_distances`` are negative, the waist is on the ``+`` side (in front) of "
639+
"the source plane.",
640+
units=MICROMETER,
641+
)
642+
643+
436644
class FieldMonitor(AbstractFieldMonitor, FreqMonitor):
437645
""":class:`Monitor` that records electromagnetic fields in the frequency domain.
438646

tidy3d/components/types/monitor.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
ModeMonitor,
2121
ModeSolverMonitor,
2222
PermittivityMonitor,
23+
GaussianOverlapMonitor,
24+
AstigmaticGaussianOverlapMonitor,
2325
)
2426

2527
# types of monitors that are accepted by simulation
@@ -40,4 +42,6 @@
4042
DirectivityMonitor,
4143
MicrowaveModeMonitor,
4244
MicrowaveModeSolverMonitor,
45+
GaussianOverlapMonitor,
46+
AstigmaticGaussianOverlapMonitor,
4347
]

0 commit comments

Comments
 (0)