Skip to content

Commit ea1198b

Browse files
dmarek-flexdaquinteroflex
authored andcommitted
add selective simulation capabilities to TerminalComponentModeler
refactored common code into AbstractComponentModeler add toggle for controlling how s matrix is calculated added validation of run_only and element_mappings for modelers schema update
1 parent f3d0f8e commit ea1198b

File tree

8 files changed

+414
-151
lines changed

8 files changed

+414
-151
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- 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.
12+
1013
### Changed
1114
- Validate mode solver object for large number of grid points on the modal plane.
1215

schemas/TerminalComponentModeler.json

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"title": "TerminalComponentModeler",
3-
"description": "Tool for modeling two-terminal multiport devices and computing port parameters\nwith lumped and wave ports.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsimulation : Simulation\n Simulation describing the device without any sources present.\nports : Tuple[Union[LumpedPort, CoaxialLumpedPort, WavePort], ...] = ()\n Collection of lumped and wave ports associated with the network. For each port, one simulation will be run with a source that is associated with the port.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1]]\n [units = Hz]. Array or list of frequencies at which to compute port parameters.\nremove_dc_component : bool = True\n Whether to remove the DC component in the Gaussian pulse spectrum. If ``True``, the Gaussian pulse is modified at low frequencies to zero out the DC component, which is usually desirable so that the fields will decay. However, for broadband simulations, it may be better to have non-vanishing source power near zero frequency. Setting this to ``False`` results in an unmodified Gaussian pulse spectrum which can have a nonzero DC component.\nfolder_name : str = default\n Name of the folder for the tasks on web.\nverbose : bool = False\n Whether the :class:`.AbstractComponentModeler` should print status and progressbars.\ncallback_url : Optional[str] = None\n Http PUT url to receive simulation finish event. The body content is a json file with fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.\npath_dir : str = .\n Base directory where data and batch will be downloaded.\nsolver_version : Optional[str] = None\n batch_cached : Optional[Batch] = None\n Optional field to specify ``batch``. Only used as a workaround internally so that ``batch`` is written when ``.to_file()`` and then the proper batch is loaded from ``.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.\nradiation_monitors : Tuple[DirectivityMonitor, ...] = ()\n Facilitates the calculation of figures-of-merit for antennas. These monitor will be included in every simulation and record the radiated fields. ",
3+
"description": "Tool for modeling two-terminal multiport devices and computing port parameters\nwith lumped and wave ports.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsimulation : Simulation\n Simulation describing the device without any sources present.\nports : Tuple[Union[LumpedPort, CoaxialLumpedPort, WavePort], ...] = ()\n Collection of lumped and wave ports associated with the network. For each port, one simulation will be run with a source that is associated with the port.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1]]\n [units = Hz]. Array or list of frequencies at which to compute port parameters.\nremove_dc_component : bool = True\n Whether to remove the DC component in the Gaussian pulse spectrum. If ``True``, the Gaussian pulse is modified at low frequencies to zero out the DC component, which is usually desirable so that the fields will decay. However, for broadband simulations, it may be better to have non-vanishing source power near zero frequency. Setting this to ``False`` results in an unmodified Gaussian pulse spectrum which can have a nonzero DC component.\nfolder_name : str = default\n Name of the folder for the tasks on web.\nverbose : bool = False\n Whether the :class:`.AbstractComponentModeler` should print status and progressbars.\ncallback_url : Optional[str] = None\n Http PUT url to receive simulation finish event. The body content is a json file with fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.\npath_dir : str = .\n Base directory where data and batch will be downloaded.\nsolver_version : Optional[str] = None\n batch_cached : Optional[Batch] = None\n Optional field to specify ``batch``. Only used as a workaround internally so that ``batch`` is written when ``.to_file()`` and then the proper batch is loaded from ``.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.\nrun_only : Optional[Tuple[IndexType, ...]] = None\n Set of matrix indices that define the simulations to run. If ``None``, simulations will be run for all indices in the scattering matrix. If a tuple is given, simulations will be run only for the given matrix indices.\nelement_mappings : Tuple[tuple[~ElementType, ~ElementType, Union[tidy3d.components.types.tidycomplex, tidy3d.components.types.ComplexNumber]], ...] = ()\n Tuple of S matrix element mappings, each described by a tuple of (input_element, output_element, coefficient), where the coefficient is the element_mapping coefficient describing the relationship between the input and output matrix element. If all elements of a given column of the scattering matrix are defined by ``element_mappings``, the simulation corresponding to this column is skipped automatically.\nradiation_monitors : Tuple[DirectivityMonitor, ...] = ()\n Facilitates the calculation of figures-of-merit for antennas. These monitor will be included in every simulation and record the radiated fields. \nassume_ideal_excitation : bool = False\n If ``True``, only the excited port is assumed to have incident power, so the vector of incident power wave amplitudes (a) is assumed to be all zeros except for the entry associated with the excited port. This choice simplifies the calculation of the scattering matrix. If ``False``, every entry in the vector of incident power wave amplitudes (a) is calculated explicitly. This choice requires a matrix inversion when calculating the scattering matrix, but may lead to more accurate scattering parameters when there are reflections from simulation boundaries. ",
44
"type": "object",
55
"properties": {
66
"attrs": {
@@ -96,6 +96,53 @@
9696
}
9797
]
9898
},
99+
"run_only": {
100+
"title": "Run Only",
101+
"description": "Set of matrix indices that define the simulations to run. If ``None``, simulations will be run for all indices in the scattering matrix. If a tuple is given, simulations will be run only for the given matrix indices.",
102+
"type": "array",
103+
"items": {}
104+
},
105+
"element_mappings": {
106+
"title": "Element Mappings",
107+
"description": "Tuple of S matrix element mappings, each described by a tuple of (input_element, output_element, coefficient), where the coefficient is the element_mapping coefficient describing the relationship between the input and output matrix element. If all elements of a given column of the scattering matrix are defined by ``element_mappings``, the simulation corresponding to this column is skipped automatically.",
108+
"default": [],
109+
"type": "array",
110+
"items": {
111+
"type": "array",
112+
"minItems": 3,
113+
"maxItems": 3,
114+
"items": [
115+
{},
116+
{},
117+
{
118+
"anyOf": [
119+
{
120+
"title": "ComplexNumber",
121+
"description": "Complex number with a well defined schema.",
122+
"type": "object",
123+
"properties": {
124+
"real": {
125+
"title": "Real",
126+
"type": "number"
127+
},
128+
"imag": {
129+
"title": "Imag",
130+
"type": "number"
131+
}
132+
},
133+
"required": [
134+
"real",
135+
"imag"
136+
]
137+
},
138+
{
139+
"$ref": "#/definitions/ComplexNumber"
140+
}
141+
]
142+
}
143+
]
144+
}
145+
},
99146
"type": {
100147
"title": "Type",
101148
"default": "TerminalComponentModeler",
@@ -112,6 +159,12 @@
112159
"items": {
113160
"$ref": "#/definitions/DirectivityMonitor"
114161
}
162+
},
163+
"assume_ideal_excitation": {
164+
"title": "Assume Ideal Excitation",
165+
"description": "If ``True``, only the excited port is assumed to have incident power, so the vector of incident power wave amplitudes (a) is assumed to be all zeros except for the entry associated with the excited port. This choice simplifies the calculation of the scattering matrix. If ``False``, every entry in the vector of incident power wave amplitudes (a) is calculated explicitly. This choice requires a matrix inversion when calculating the scattering matrix, but may lead to more accurate scattering parameters when there are reflections from simulation boundaries. ",
166+
"default": false,
167+
"type": "boolean"
115168
}
116169
},
117170
"required": [

tests/test_plugins/smatrix/terminal_component_modeler_def.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePor
276276
inner_diameter=2 * Rinner,
277277
normal_axis=2,
278278
direction=direction,
279-
name=name,
279+
name="coax" + name,
280280
num_grid_cells=port_cells,
281281
impedance=reference_impedance,
282282
)
@@ -311,7 +311,7 @@ def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePor
311311
center=center,
312312
size=[2 * Router, 2 * Router, 0],
313313
direction=direction,
314-
name=name,
314+
name="wave" + name,
315315
mode_spec=td.ModeSpec(num_modes=1),
316316
mode_index=0,
317317
voltage_integral=voltage_integral,
@@ -321,9 +321,9 @@ def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePor
321321
return port
322322

323323
center_src1 = [0, 0, -length / 2]
324-
port_1 = make_port(center_src1, direction="+", type=port_types[0], name="coax_port_1")
324+
port_1 = make_port(center_src1, direction="+", type=port_types[0], name="_1")
325325
center_src2 = [0, 0, length / 2]
326-
port_2 = make_port(center_src2, direction="-", type=port_types[1], name="coax_port_2")
326+
port_2 = make_port(center_src2, direction="-", type=port_types[1], name="_2")
327327
ports = [port_1, port_2]
328328
freqs = np.linspace(freq_start, freq_stop, 100)
329329

tests/test_plugins/smatrix/test_component_modeler.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,35 @@ def test_mapping_exclusion(monkeypatch):
374374
_test_mappings(element_mappings, s_matrix)
375375

376376

377+
def test_mapping_with_run_only():
378+
"""Make sure that the Modeler is correctly validated when both run_only and
379+
element_mappings are provided."""
380+
ports = make_ports()
381+
382+
EXCLUDE_INDEX = ("right_bot", 0)
383+
element_mappings = []
384+
run_only = []
385+
# add a mapping to each element in the row of EXCLUDE_INDEX
386+
for port in ports:
387+
for mode_index in range(port.mode_spec.num_modes):
388+
row_index = (port.name, mode_index)
389+
run_only.append(row_index)
390+
if row_index != EXCLUDE_INDEX:
391+
mapping = ((row_index, row_index), (row_index, EXCLUDE_INDEX), +1)
392+
element_mappings.append(mapping)
393+
394+
# add the self-self coupling element to complete row
395+
mapping = ((("right_bot", 1), ("right_bot", 1)), (EXCLUDE_INDEX, EXCLUDE_INDEX), +1)
396+
element_mappings.append(mapping)
397+
398+
# Will pass, since run_only covers all source indices in element_mapping
399+
_ = make_component_modeler(element_mappings=element_mappings, run_only=run_only)
400+
401+
run_only.remove(EXCLUDE_INDEX)
402+
with pytest.raises(pydantic.ValidationError):
403+
_ = make_component_modeler(element_mappings=element_mappings, run_only=run_only)
404+
405+
377406
def test_batch_filename(tmp_path):
378407
modeler = make_component_modeler()
379408
path = modeler._batch_path

tests/test_plugins/smatrix/test_terminal_component_modeler.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,3 +900,44 @@ def test_get_combined_antenna_parameters_data(monkeypatch, tmp_path):
900900
assert not np.allclose(
901901
antenna_params.radiation_efficiency, single_port_params.radiation_efficiency
902902
)
903+
904+
905+
def test_run_only_and_element_mappings(monkeypatch, tmp_path):
906+
"""Checks the terminal component modeler works when running with a subset of excitations."""
907+
z_grid = td.UniformGrid(dl=1 * 1e3)
908+
xy_grid = td.UniformGrid(dl=0.1 * 1e3)
909+
grid_spec = td.GridSpec(grid_x=xy_grid, grid_y=xy_grid, grid_z=z_grid)
910+
modeler = make_coaxial_component_modeler(
911+
path_dir=str(tmp_path), port_types=(CoaxialLumpedPort, WavePort), grid_spec=grid_spec
912+
)
913+
port0_idx = modeler.network_index(modeler.ports[0])
914+
port1_idx = modeler.network_index(modeler.ports[1])
915+
modeler_run1 = modeler.updated_copy(run_only=(port0_idx,))
916+
917+
# Make sure the smatrix and impedance calculations work for reduced simulations
918+
s_matrix = run_component_modeler(monkeypatch, modeler_run1)
919+
with pytest.raises(ValueError):
920+
TerminalComponentModeler._validate_square_matrix(s_matrix, "test_method")
921+
_ = modeler_run1.port_reference_impedances
922+
923+
assert len(modeler_run1.sim_dict) == 1
924+
S11 = (port0_idx, port0_idx)
925+
S21 = (port1_idx, port0_idx)
926+
S12 = (port0_idx, port1_idx)
927+
S22 = (port1_idx, port1_idx)
928+
element_mappings = ((S11, S22, 1),)
929+
modeler_with_mappings = modeler.updated_copy(element_mappings=element_mappings)
930+
assert len(modeler_with_mappings.sim_dict) == 2
931+
932+
# Column 1 is mapped to column 2, resulting in one simulation
933+
element_mappings = ((S11, S22, 1), (S21, S12, 1))
934+
modeler_with_mappings = modeler.updated_copy(element_mappings=element_mappings)
935+
s_matrix = run_component_modeler(monkeypatch, modeler_with_mappings)
936+
assert np.all(s_matrix.values[:, 0, 0] == s_matrix.values[:, 1, 1])
937+
assert np.all(s_matrix.values[:, 0, 1] == s_matrix.values[:, 1, 0])
938+
assert len(modeler_with_mappings.sim_dict) == 1
939+
940+
# Mapping is incomplete, so two simulations are run
941+
element_mappings = ((S11, S22, 1), (S12, S21, 1))
942+
modeler_with_mappings = modeler.updated_copy(element_mappings=element_mappings)
943+
assert len(modeler_with_mappings.sim_dict) == 2

0 commit comments

Comments
 (0)