Skip to content

Commit cb3e2d0

Browse files
feat(rf): DirectivityMonitorSpec
1 parent 0ed42f3 commit cb3e2d0

File tree

9 files changed

+758
-29
lines changed

9 files changed

+758
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
- Introduced a profile-based configuration manager with TOML persistence and runtime overrides exposed via `tidy3d.config`.
2828
- Added support of `os.PathLike` objects as paths like `pathlib.Path` alongside `str` paths in all path-related functions.
2929
- Added configurable local simulation result caching with checksum validation, eviction limits, and per-call overrides across `web.run`, `web.load`, and job workflows.
30+
- Added `DirectivityMonitorSpec` for automated creation and configuration of directivity radiation monitors in `TerminalComponentModeler`.
3031

3132
### Changed
3233
- Improved performance of antenna metrics calculation by utilizing cached wave amplitude calculations instead of recomputing wave amplitudes for each port excitation in the `TerminalComponentModelerData`.

docs/api/microwave/radiation_scattering.rst

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Radiation & Scattering
88
:template: module.rst
99

1010
tidy3d.DirectivityMonitor
11+
tidy3d.plugins.smatrix.DirectivityMonitorSpec
1112
tidy3d.plugins.microwave.RectangularAntennaArrayCalculator
1213
tidy3d.plugins.microwave.LobeMeasurer
1314
tidy3d.AntennaMetricsData
@@ -30,19 +31,25 @@ When modeling antennas or scattering problems, it is vital to analyze the radiat
3031
phi=my_phi,
3132
theta=my_theta,
3233
name='My radiation monitor',
33-
far_field_approx=True,
3434
)
3535
36-
The :class:`.DirectivityMonitor` should completely surround the structure of interest. The ``far_field_approx`` flag can be used to set whether the far-field approximation is used (default ``True``).
36+
The :class:`.DirectivityMonitor` should completely surround the structure of interest.
3737

38-
Once the monitor is defined, it should be added to the ``radiation_monitors`` option of the :class:`.TerminalComponentModeler`.
38+
Alternatively, a :class:`.DirectivityMonitorSpec` can be used to create a specification for automatic generation of a :class:`.DirectivityMonitor` in the :class:`.TerminalComponentModeler`.
39+
40+
.. code-block:: python
41+
42+
# Define directivity monitor spec
43+
my_directivity_monitor_spec = DirectivityMonitorSpec()
44+
45+
Once the monitor or monitor spec is defined, it should be added to the ``radiation_monitors`` option of the :class:`.TerminalComponentModeler`.
3946

4047
.. code-block:: python
4148
4249
# Add directivity monitor to simulation
4350
my_tcm = TerminalComponentModeler(
4451
...,
45-
radiation_monitors=[my_directivity_monitor],
52+
radiation_monitors=[my_directivity_monitor, my_directivity_monitor_spec],
4653
)
4754
4855
Once the simulation is completed, the ``get_antenna_metrics_data()`` method of the :class:`.TerminalComponentModelerData` object is used to obtain the radiation metrics.

schemas/TerminalComponentModeler.json

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6096,6 +6096,69 @@
60966096
],
60976097
"type": "object"
60986098
},
6099+
"DirectivityMonitorSpec": {
6100+
"additionalProperties": false,
6101+
"properties": {
6102+
"attrs": {
6103+
"default": {},
6104+
"type": "object"
6105+
},
6106+
"buffer": {
6107+
"default": 2,
6108+
"minimum": 0,
6109+
"type": "integer"
6110+
},
6111+
"custom_origin": {
6112+
"default": [
6113+
0,
6114+
0,
6115+
0
6116+
],
6117+
"items": [
6118+
{
6119+
"type": "number"
6120+
},
6121+
{
6122+
"type": "number"
6123+
},
6124+
{
6125+
"type": "number"
6126+
}
6127+
],
6128+
"maxItems": 3,
6129+
"minItems": 3,
6130+
"type": "array"
6131+
},
6132+
"freqs": {
6133+
"items": {
6134+
"minimum": 0,
6135+
"type": "integer"
6136+
},
6137+
"type": "array"
6138+
},
6139+
"name": {
6140+
"type": "string"
6141+
},
6142+
"num_phi_points": {
6143+
"default": 200,
6144+
"minimum": 0,
6145+
"type": "integer"
6146+
},
6147+
"num_theta_points": {
6148+
"default": 100,
6149+
"minimum": 0,
6150+
"type": "integer"
6151+
},
6152+
"type": {
6153+
"default": "DirectivityMonitorSpec",
6154+
"enum": [
6155+
"DirectivityMonitorSpec"
6156+
],
6157+
"type": "string"
6158+
}
6159+
},
6160+
"type": "object"
6161+
},
60996162
"DistributedGeneration": {
61006163
"additionalProperties": false,
61016164
"properties": {
@@ -18304,7 +18367,21 @@
1830418367
"radiation_monitors": {
1830518368
"default": [],
1830618369
"items": {
18307-
"$ref": "#/definitions/DirectivityMonitor"
18370+
"discriminator": {
18371+
"mapping": {
18372+
"DirectivityMonitor": "#/definitions/DirectivityMonitor",
18373+
"DirectivityMonitorSpec": "#/definitions/DirectivityMonitorSpec"
18374+
},
18375+
"propertyName": "type"
18376+
},
18377+
"oneOf": [
18378+
{
18379+
"$ref": "#/definitions/DirectivityMonitor"
18380+
},
18381+
{
18382+
"$ref": "#/definitions/DirectivityMonitorSpec"
18383+
}
18384+
]
1830818385
},
1830918386
"type": "array"
1831018387
},

tests/test_plugins/smatrix/terminal_component_modeler_def.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,225 @@ def make_differential_stripline_modeler():
474474
)
475475

476476
return tcm
477+
478+
479+
def make_patch_antenna_simulation(padding: tuple[float, float, float] = (0.25, 0.25, 0.25)):
480+
"""Create a rectangular patch antenna simulation on a dielectric substrate.
481+
482+
Closely follows the patch antenna design from AntennaCharacteristics.ipynb,
483+
based on: Sheen, D.M., Ali, S.M., Abouzahra, M.D. and Kong, J.A., 1990.
484+
Application of the three-dimensional finite-difference time-domain method
485+
to the analysis of planar microstrip circuits.
486+
487+
Parameters
488+
----------
489+
padding : tuple[float, float, float]
490+
Padding between substrate and simulation domain boundaries in x, y, z directions.
491+
492+
Returns
493+
-------
494+
td.Simulation
495+
Patch antenna simulation object.
496+
"""
497+
# Frequency setup for patch antenna (5-11 GHz range for resonances at 7.5 and 10 GHz)
498+
freq_start_antenna = 5e9
499+
freq_stop_antenna = 11e9
500+
freq0 = (freq_start_antenna + freq_stop_antenna) / 2
501+
wavelength0 = td.C_0 / freq0
502+
503+
# Metal thickness
504+
th = 0.05 * mm
505+
506+
# Substrate parameters
507+
sub_x = 23.34 * mm
508+
sub_y = 40 * mm
509+
sub_z = 0.794 * mm
510+
511+
# Patch parameters
512+
patch_x = 12.45 * mm
513+
patch_y = 16 * mm
514+
515+
# Feed line parameters
516+
feed_x = 2.46 * mm
517+
feed_y = 20 * mm
518+
feed_offset = 2.09 * mm
519+
520+
# Materials
521+
medium_sub = td.Medium(permittivity=2.2, name="Substrate")
522+
medium_metal = td.PECMedium()
523+
524+
# Create structures for grid refinement calculation
525+
ground_plane_temp = td.Structure(
526+
geometry=td.Box(center=[0, 0, -(sub_z + th) / 2], size=[sub_x, sub_y, th]),
527+
medium=medium_metal,
528+
name="Ground",
529+
)
530+
531+
feed_line_temp = td.Structure(
532+
geometry=td.Box.from_bounds(
533+
rmin=[-patch_x / 2 + feed_offset, -sub_y / 2, sub_z / 2],
534+
rmax=[-patch_x / 2 + feed_offset + feed_x, -sub_y / 2 + feed_y, sub_z / 2 + th],
535+
),
536+
medium=medium_metal,
537+
name="Feed line",
538+
)
539+
540+
patch_temp = td.Structure(
541+
geometry=td.Box.from_bounds(
542+
rmin=[-patch_x / 2, -sub_y / 2 + feed_y, sub_z / 2],
543+
rmax=[patch_x / 2, -sub_y / 2 + feed_y + patch_y, sub_z / 2 + th],
544+
),
545+
medium=medium_metal,
546+
name="Patch",
547+
)
548+
549+
# Create LayerRefinementSpec helper
550+
def create_lr_spec(structures_list):
551+
"""Returns LayerRefinementSpec applied to the bounding box of the input structure list"""
552+
lr_spec = td.LayerRefinementSpec.from_structures(
553+
structures=structures_list,
554+
axis=2, # Layer normal is oriented along the z-axis
555+
bounds_snapping="bounds", # Snap grid lines to layer bounds in normal direction
556+
bounds_refinement=td.GridRefinement(
557+
dl=th, num_cells=2
558+
), # cell size and num cells at layer boundaries in normal direction
559+
corner_refinement=td.GridRefinement(
560+
dl=0.2 * mm, num_cells=2
561+
), # cell size and num cells around in-plane metal corners
562+
)
563+
return lr_spec
564+
565+
# Layer refinement for the ground layer
566+
lr_spec_1 = create_lr_spec([ground_plane_temp])
567+
568+
# Layer refinement for the patch antenna layer
569+
lr_spec_2 = create_lr_spec([feed_line_temp, patch_temp])
570+
571+
# Define overall grid specification
572+
grid_spec = td.GridSpec.auto(
573+
wavelength=wavelength0, min_steps_per_wvl=25, layer_refinement_specs=[lr_spec_1, lr_spec_2]
574+
)
575+
576+
# Create substrate
577+
substrate = td.Structure(
578+
geometry=td.Box(center=[0, 0, 0], size=[sub_x, sub_y, sub_z]),
579+
medium=medium_sub,
580+
name="Substrate",
581+
)
582+
583+
# Create ground plane
584+
ground_plane = td.Structure(
585+
geometry=td.Box(center=[0, 0, -(sub_z + th) / 2], size=[sub_x, sub_y, th]),
586+
medium=medium_metal,
587+
name="Ground",
588+
)
589+
590+
# Create feed line
591+
feed_line = td.Structure(
592+
geometry=td.Box.from_bounds(
593+
rmin=[-patch_x / 2 + feed_offset, -sub_y / 2, sub_z / 2],
594+
rmax=[-patch_x / 2 + feed_offset + feed_x, -sub_y / 2 + feed_y, sub_z / 2 + th],
595+
),
596+
medium=medium_metal,
597+
name="Feed line",
598+
)
599+
600+
# Create patch antenna
601+
patch = td.Structure(
602+
geometry=td.Box.from_bounds(
603+
rmin=[-patch_x / 2, -sub_y / 2 + feed_y, sub_z / 2],
604+
rmax=[patch_x / 2, -sub_y / 2 + feed_y + patch_y, sub_z / 2 + th],
605+
),
606+
medium=medium_metal,
607+
name="Patch",
608+
)
609+
610+
# Structures list (dielectric first, then metal/PEC)
611+
structures_list = [substrate, ground_plane, feed_line, patch]
612+
613+
# Padding distance
614+
padding_um = td.C_0 / freq_start_antenna * np.array(padding)
615+
616+
# Simulation size
617+
sim_x = sub_x + 2 * padding_um[0]
618+
sim_y = sub_y + 2 * padding_um[1]
619+
sim_z = sub_z + 2 * padding_um[2]
620+
621+
# Define PMLs on all sides
622+
boundary_spec = td.BoundarySpec.pml(x=True, y=False, z=True)
623+
624+
# Create simulation object
625+
sim = td.Simulation(
626+
center=[0, 0, 0],
627+
size=[sim_x, sim_y, sim_z],
628+
structures=structures_list,
629+
sources=[],
630+
monitors=[],
631+
boundary_spec=boundary_spec,
632+
grid_spec=grid_spec,
633+
run_time=5e-9,
634+
shutoff=1e-4,
635+
plot_length_units="mm",
636+
)
637+
638+
return sim
639+
640+
641+
def make_patch_antenna_modeler(padding: tuple[float, float, float] = (0.25, 0.25, 0.25)):
642+
"""Create a TerminalComponentModeler for a rectangular patch antenna.
643+
644+
Closely follows the antenna setup from AntennaCharacteristics.ipynb.
645+
646+
Returns
647+
-------
648+
TerminalComponentModeler
649+
Component modeler for the patch antenna.
650+
"""
651+
# Frequency setup for patch antenna
652+
freq_start_antenna = 5e9
653+
freq_stop_antenna = 11e9
654+
655+
sim = make_patch_antenna_simulation(padding=padding)
656+
657+
# Patch antenna dimensions
658+
patch_x = 12.45 * mm
659+
feed_x = 2.46 * mm
660+
feed_offset = 2.09 * mm
661+
sub_z = 0.794 * mm
662+
sub_y = 40 * mm
663+
664+
# Create lumped port excitation at feed line
665+
port = LumpedPort(
666+
name="lumped_port",
667+
center=[-patch_x / 2 + feed_offset + feed_x / 2, -sub_y / 2, 0],
668+
size=[feed_x, 0, sub_z],
669+
voltage_axis=2,
670+
impedance=50,
671+
)
672+
673+
# Frequency sweep (201 points as in notebook)
674+
freqs = np.linspace(freq_start_antenna, freq_stop_antenna, 201)
675+
676+
# Target frequencies for field monitor
677+
freqs_target = [7.5e9, 10e9]
678+
679+
# Field monitor
680+
monitor_field = td.FieldMonitor(
681+
center=(0, 0, sub_z / 2),
682+
size=(td.inf, td.inf, 0),
683+
freqs=freqs_target,
684+
name="field",
685+
)
686+
687+
# Update simulation with field monitor
688+
sim = sim.updated_copy(monitors=[monitor_field])
689+
690+
modeler = TerminalComponentModeler(
691+
simulation=sim,
692+
ports=[port],
693+
freqs=freqs,
694+
remove_dc_component=False,
695+
radiation_monitors=[],
696+
)
697+
698+
return modeler

0 commit comments

Comments
 (0)