Skip to content

Commit f1007a7

Browse files
--amend
1 parent 8fcbe0b commit f1007a7

File tree

3 files changed

+23
-107
lines changed

3 files changed

+23
-107
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11-
- Add automatic structure extrusion for waveports defined on boundaries, controlled by the `extrude_structures` field in `WavePort`.
12-
- The extrusion method, implemented in `TerminalComponentModeler`, ensures that mode sources, absorbers, and PEC frames are fully contained within the extruded structures; extrusion occurs only when `extrude_structures` is set to `True`.
11+
- Added optional automatic extrusion of boundary waveport structures via `WavePort.extrude_structures`, ensuring mode sources, absorbers, and PEC frames are fully contained.
1312
- Added rectangular and radial taper support to `RectangularAntennaArrayCalculator` for phased array amplitude weighting; refactored array factor calculation for improved clarity and performance.
1413
- Selective simulation capabilities to `TerminalComponentModeler` via `run_only` and `element_mappings` fields, allowing users to run fewer simulations and extract only needed scattering matrix elements.
1514
- Added KLayout plugin, with DRC functionality for running design rule checks in `plugins.klayout.drc`. Supports running DRC on GDS files as well as `Geometry`, `Structure`, and `Simulation` objects.

schemas/TerminalComponentModeler.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16108,6 +16108,10 @@
1610816108
],
1610916109
"type": "string"
1611016110
},
16111+
"extrude_structures": {
16112+
"default": false,
16113+
"type": "boolean"
16114+
},
1611116115
"frame": {
1611216116
"allOf": [
1611316117
{

tidy3d/plugins/smatrix/component_modelers/terminal.py

Lines changed: 18 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -446,102 +446,6 @@ def get_radiation_monitor_by_name(self, monitor_name: str) -> DirectivityMonitor
446446
return monitor
447447
raise Tidy3dKeyError(f"No radiation monitor named '{monitor_name}'.")
448448

449-
def get_antenna_metrics_data(
450-
self,
451-
port_amplitudes: Optional[dict[str, complex]] = None,
452-
monitor_name: Optional[str] = None,
453-
) -> AntennaMetricsData:
454-
"""Calculate antenna parameters using superposition of fields from multiple port excitations.
455-
456-
The method computes the radiated far fields and port excitation power wave amplitudes
457-
for a superposition of port excitations, which can be used to analyze antenna radiation
458-
characteristics.
459-
460-
Parameters
461-
----------
462-
port_amplitudes : dict[str, complex] = None
463-
Dictionary mapping port names to their desired excitation amplitudes, ``a``. For each port,
464-
:math:`\\frac{1}{2}|a|^2` represents the incident power from that port into the system.
465-
If ``None``, uses only the first port without any scaling of the raw simulation data.
466-
When ``None`` is passed as a port amplitude, the raw simulation data is used for that port.
467-
Note that in this method ``a`` represents the incident wave amplitude
468-
using the power wave definition in [2].
469-
monitor_name : str = None
470-
Name of the :class:`.DirectivityMonitor` to use for calculating far fields.
471-
If None, uses the first monitor in `radiation_monitors`.
472-
473-
Returns
474-
-------
475-
:class:`.AntennaMetricsData`
476-
Container with antenna parameters including directivity, gain, and radiation efficiency,
477-
computed from the superposition of fields from all excited ports.
478-
"""
479-
# Use the first port as default if none specified
480-
if port_amplitudes is None:
481-
port_amplitudes = {self.ports[0].name: None}
482-
483-
# Check port names, and create map from port to amplitude
484-
port_dict = {}
485-
for key in port_amplitudes.keys():
486-
port, _ = self.network_dict[key]
487-
port_dict[port] = port_amplitudes[key]
488-
# Get the radiation monitor, use first as default
489-
# if none specified
490-
if monitor_name is None:
491-
rad_mon = self.radiation_monitors[0]
492-
else:
493-
rad_mon = self.get_radiation_monitor_by_name(monitor_name)
494-
495-
# Create data arrays for holding the superposition of all port power wave amplitudes
496-
f = list(rad_mon.freqs)
497-
coords = {"f": f, "port": list(self.matrix_indices_monitor)}
498-
a_sum = PortDataArray(
499-
np.zeros((len(f), len(self.matrix_indices_monitor)), dtype=complex), coords=coords
500-
)
501-
b_sum = a_sum.copy()
502-
# Retrieve associated simulation data
503-
combined_directivity_data = None
504-
for port, amplitude in port_dict.items():
505-
if amplitude == 0.0:
506-
continue
507-
sim_data_port = self.batch_data[self._task_name(port=port)]
508-
radiation_data = sim_data_port[rad_mon.name]
509-
510-
a, b = self.compute_wave_amplitudes_at_each_port(
511-
self.port_reference_impedances, sim_data_port, s_param_def="power"
512-
)
513-
# Select a possible subset of frequencies
514-
a = a.sel(f=f)
515-
b = b.sel(f=f)
516-
a_raw = a.sel(port=self.network_index(port))
517-
518-
if amplitude is None:
519-
# No scaling performed when amplitude is None
520-
scaled_directivity_data = sim_data_port[rad_mon.name]
521-
scale_factor = 1.0
522-
else:
523-
scaled_directivity_data = self._monitor_data_at_port_amplitude(
524-
port, sim_data_port, radiation_data, amplitude
525-
)
526-
scale_factor = amplitude / a_raw
527-
a = scale_factor * a
528-
b = scale_factor * b
529-
530-
# Combine the possibly scaled directivity data and the power wave amplitudes
531-
if combined_directivity_data is None:
532-
combined_directivity_data = scaled_directivity_data
533-
else:
534-
combined_directivity_data = combined_directivity_data + scaled_directivity_data
535-
a_sum += a
536-
b_sum += b
537-
538-
# Compute and add power measures to results
539-
power_incident = np.real(0.5 * a_sum * np.conj(a_sum)).sum(dim="port")
540-
power_reflected = np.real(0.5 * b_sum * np.conj(b_sum)).sum(dim="port")
541-
return AntennaMetricsData.from_directivity_data(
542-
combined_directivity_data, power_incident, power_reflected
543-
)
544-
545449
def _extrude_port_structures(self, sim: Simulation) -> Simulation:
546450
"""
547451
Extrude structures intersecting a port plane when a wave port lies on a structure boundary.
@@ -634,14 +538,19 @@ def _clip(i, lo, hi):
634538
# loop over structures and extrude those that intersect a waveport plane
635539
for structure in sim.structures:
636540
# get geometries that intersect the plane on which the waveport is defined
637-
left_geom = slice_plane_left.intersections_with(structure.geometry)
638-
right_geom = slice_plane_right.intersections_with(structure.geometry)
639-
shapely_geom = left_geom or right_geom or []
640541

641-
new_geoms = []
542+
if direction == "+":
543+
shapely_geom = slice_plane_right.intersections_with(structure.geometry)
544+
else:
545+
shapely_geom = slice_plane_left.intersections_with(structure.geometry)
642546

547+
polygon_list = []
548+
for geom in shapely_geom:
549+
polygon_list = polygon_list + ClipOperation.to_polygon_list(geom)
550+
551+
new_geoms = []
643552
# loop over identified geometries and extrude them
644-
for polygon in shapely_geom:
553+
for polygon in polygon_list:
645554
# construct outer shell of an extruded geometry first
646555
exterior_vertices = np.array(polygon.exterior.coords)
647556
outer_shell = PolySlab(
@@ -676,9 +585,13 @@ def _clip(i, lo, hi):
676585
)
677586
new_structures.append(new_struct)
678587

679-
# return simulation with added extruded structures
680-
return sim.updated_copy(grid_spec=GridSpec.from_grid(sim.grid), structures=new_structures)
681-
588+
if not mode_sources:
589+
return sim
590+
else:
591+
# return simulation with added extruded structures
592+
return sim.updated_copy(
593+
grid_spec=GridSpec.from_grid(sim.grid), structures=new_structures
594+
)
595+
682596

683597
TerminalComponentModeler.update_forward_refs()
684-

0 commit comments

Comments
 (0)