diff --git a/CHANGELOG.md b/CHANGELOG.md index b6cfda5301..e9e7cd2363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `tidy3d.plugins.design.DesignSpace.run(..., fn_post=...)` now accepts a `priority` keyword to propagate vGPU queue priority to all automatically batched simulations. - Introduced `BroadbandPulse` for exciting simulations across a wide frequency spectrum. - Added `interp_spec` in `ModeSpec` to allow downsampling and interpolation of waveguide modes in frequency. +- Added warning if port mesh refinement is incompatible with the `GridSpec` in the `TerminalComponentModeler`. ### Breaking Changes - Edge singularity correction at PEC and lossy metal edges defaults to `True`. diff --git a/tests/test_plugins/smatrix/terminal_component_modeler_def.py b/tests/test_plugins/smatrix/terminal_component_modeler_def.py index 8e500875be..806e667e5a 100644 --- a/tests/test_plugins/smatrix/terminal_component_modeler_def.py +++ b/tests/test_plugins/smatrix/terminal_component_modeler_def.py @@ -267,8 +267,10 @@ def make_coaxial_component_modeler( def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePort]: if type is CoaxialLumpedPort: port_cells = None + enable_snapping_points = False if port_refinement: port_cells = 21 + enable_snapping_points = True port = CoaxialLumpedPort( center=center, outer_diameter=2 * Router, @@ -278,6 +280,7 @@ def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePor name="coax" + name, num_grid_cells=port_cells, impedance=reference_impedance, + enable_snapping_points=enable_snapping_points, ) else: mean_radius = (Router + Rinner) / 2 diff --git a/tests/test_plugins/smatrix/test_component_modeler_autograd.py b/tests/test_plugins/smatrix/test_component_modeler_autograd.py index faeb0acda1..f7b476c891 100644 --- a/tests/test_plugins/smatrix/test_component_modeler_autograd.py +++ b/tests/test_plugins/smatrix/test_component_modeler_autograd.py @@ -197,6 +197,8 @@ def build_terminal_modeler(scale: float) -> TerminalComponentModeler: voltage_axis=1, name="lp1", impedance=50.0, + num_grid_cells=None, + enable_snapping_points=False, ) p2 = RectLumpedPort( center=(0.0, 0.0, 1.5), @@ -204,6 +206,8 @@ def build_terminal_modeler(scale: float) -> TerminalComponentModeler: voltage_axis=1, name="lp2", impedance=50.0, + num_grid_cells=None, + enable_snapping_points=False, ) freqs = [2.0e14] diff --git a/tests/test_plugins/smatrix/test_terminal_component_modeler.py b/tests/test_plugins/smatrix/test_terminal_component_modeler.py index 898dcdd9fc..4bb4649cee 100644 --- a/tests/test_plugins/smatrix/test_terminal_component_modeler.py +++ b/tests/test_plugins/smatrix/test_terminal_component_modeler.py @@ -29,7 +29,7 @@ from tidy3d.plugins.smatrix.ports.base_lumped import AbstractLumpedPort from tidy3d.plugins.smatrix.utils import s_to_z, validate_square_matrix -from ...utils import run_emulated +from ...utils import AssertLogLevel, run_emulated from .terminal_component_modeler_def import ( make_basic_filter_terminals, make_coaxial_component_modeler, @@ -521,14 +521,16 @@ def test_ab_to_s_component_modeler(): assert np.isclose(S_matrix, b_matrix).all() -def test_port_snapping(tmp_path): +def test_port_snapping(): """Make sure that the snapping behavior of the load resistor is mirrored by all other components in the modeler simulations with rectangular ports. """ y_z_grid = td.UniformGrid(dl=0.1 * 1e3) x_grid = td.UniformGrid(dl=11 * 1e3) grid_spec = td.GridSpec(grid_x=x_grid, grid_y=y_z_grid, grid_z=y_z_grid) - modeler = make_component_modeler(planar_pec=True, port_refinement=False, grid_spec=grid_spec) + modeler = make_component_modeler( + planar_pec=True, port_refinement=False, port_snapping=False, grid_spec=grid_spec + ) check_lumped_port_components_snapped_correctly(modeler=modeler) @@ -548,7 +550,7 @@ def test_coaxial_port_source_size(axis): assert np.isclose(source.size[axis], 0) -def test_coarse_grid_at_port(monkeypatch, tmp_path): +def test_coarse_grid_at_port(monkeypatch): modeler = make_component_modeler(planar_pec=True, port_refinement=False, port_snapping=False) # Without port refinement the grid is much too coarse for these port sizes with pytest.raises(SetupError): @@ -823,8 +825,7 @@ def test_make_coaxial_component_modeler_with_wave_ports(tmp_path): xy_grid = td.UniformGrid(dl=0.1 * 1e3) grid_spec = td.GridSpec(grid_x=xy_grid, grid_y=xy_grid, grid_z=z_grid) _ = make_coaxial_component_modeler( - port_types=(WavePort, WavePort), - grid_spec=grid_spec, + port_types=(WavePort, WavePort), grid_spec=grid_spec, port_refinement=False ) @@ -844,6 +845,7 @@ def test_run_coaxial_component_modeler_with_wave_ports( grid_spec=grid_spec, use_voltage=voltage_enabled, use_current=current_enabled, + port_refinement=False, ) return @@ -852,6 +854,7 @@ def test_run_coaxial_component_modeler_with_wave_ports( grid_spec=grid_spec, use_voltage=voltage_enabled, use_current=current_enabled, + port_refinement=False, ) s_matrix = get_terminal_port_data_array(monkeypatch, modeler) @@ -895,7 +898,9 @@ def test_run_mixed_component_modeler_with_wave_ports(monkeypatch, tmp_path): xy_grid = td.UniformGrid(dl=0.1 * 1e3) grid_spec = td.GridSpec(grid_x=xy_grid, grid_y=xy_grid, grid_z=z_grid) modeler = make_coaxial_component_modeler( - port_types=(CoaxialLumpedPort, WavePort), grid_spec=grid_spec + port_types=(CoaxialLumpedPort, WavePort), + grid_spec=grid_spec, + port_refinement=False, ) s_matrix = get_terminal_port_data_array(monkeypatch, modeler) @@ -1363,7 +1368,9 @@ def test_run_only_and_element_mappings(monkeypatch, tmp_path): xy_grid = td.UniformGrid(dl=0.1 * 1e3) grid_spec = td.GridSpec(grid_x=xy_grid, grid_y=xy_grid, grid_z=z_grid) modeler = make_coaxial_component_modeler( - port_types=(CoaxialLumpedPort, CoaxialLumpedPort), grid_spec=grid_spec + port_types=(CoaxialLumpedPort, CoaxialLumpedPort), + grid_spec=grid_spec, + port_refinement=False, ) port0_idx = modeler.network_index(modeler.ports[0]) port1_idx = modeler.network_index(modeler.ports[1]) @@ -1677,7 +1684,11 @@ def test_S_parameter_deembedding(monkeypatch, tmp_path): z_grid = td.UniformGrid(dl=1 * 1e3) xy_grid = td.UniformGrid(dl=0.1 * 1e3) grid_spec = td.GridSpec(grid_x=xy_grid, grid_y=xy_grid, grid_z=z_grid) - modeler = make_coaxial_component_modeler(port_types=(WavePort, WavePort), grid_spec=grid_spec) + modeler = make_coaxial_component_modeler( + port_types=(WavePort, WavePort), + grid_spec=grid_spec, + port_refinement=False, + ) # Make sure the smatrix and impedance calculations work for reduced simulations modeler_data = run_component_modeler(monkeypatch, modeler) @@ -1977,7 +1988,11 @@ def test_validate_run_only_with_wave_ports(): z_grid = td.UniformGrid(dl=1 * 1e3) xy_grid = td.UniformGrid(dl=0.1 * 1e3) grid_spec = td.GridSpec(grid_x=xy_grid, grid_y=xy_grid, grid_z=z_grid) - modeler = make_coaxial_component_modeler(port_types=(WavePort, WavePort), grid_spec=grid_spec) + modeler = make_coaxial_component_modeler( + port_types=(WavePort, WavePort), + grid_spec=grid_spec, + port_refinement=False, + ) port0_idx = modeler.network_index(modeler.ports[0], 0) port1_idx = modeler.network_index(modeler.ports[1], 0) @@ -2212,6 +2227,7 @@ def test_wave_port_mode_index_with_modeler(): mode_spec=mode_spec, direction="+", mode_selection=(0, 2), # Only modes 0 and 2 + num_grid_cells=None, ) # Verify the port has only the selected modes @@ -2232,6 +2248,8 @@ def test_wave_port_mode_index_with_modeler(): size=(1, 1, 0), voltage_axis=0, # x-direction since port is in x-y plane name="lumped_port", + num_grid_cells=None, + enable_snapping_points=False, ) modeler = TerminalComponentModeler( @@ -2306,3 +2324,12 @@ def test_get_task_name(): # Test with wave port (no mode_index) - should work task_name = TerminalComponentModeler.get_task_name(port=wave_port) assert task_name == "wave@1" + + +def test_validate_port_refinement_with_uniform_grid(): + """Test that port refinement options raise error with uniform grid.""" + uniform_grid = td.GridSpec.uniform(dl=0.5 * mm) + with AssertLogLevel("WARNING", contains_str="mesh refinement options enabled"): + make_coaxial_component_modeler( + port_types=(CoaxialLumpedPort, WavePort), grid_spec=uniform_grid + ) diff --git a/tidy3d/plugins/smatrix/component_modelers/terminal.py b/tidy3d/plugins/smatrix/component_modelers/terminal.py index ccc2de5f22..f865286c4e 100644 --- a/tidy3d/plugins/smatrix/component_modelers/terminal.py +++ b/tidy3d/plugins/smatrix/component_modelers/terminal.py @@ -742,6 +742,31 @@ def _validate_3d_simulation(cls, val): ) return val + @pd.validator("ports") + @skip_if_fields_missing(["simulation"]) + def _validate_port_refinement_usage(cls, val, values): + """Warn if port refinement options are enabled, but the supplied simulation + does not contain a grid type that will make use of them.""" + + sim: Simulation = values.get("simulation") + # If grid spec is using AutoGrid + # then set up is acceptable + if sim.grid_spec.auto_grid_used: + return val + + for port in val: + if port._is_using_mesh_refinement: + log.warning( + f"A port with name '{port.name}' has mesh refinement options enabled, but the " + "'Simulation' passed to the 'TerminalComponentModeler' was setup with a 'GridSpec' which " + "does not support mesh refinement. For accurate simulations, please setup the " + "'Simulation' to use an 'AutoGrid'. To suppress this warning, please explicitly disable " + "mesh refinement options in the port, which are by default enabled. For example, set " + "the 'enable_snapping_points=False' and 'num_grid_cells=None' for lumped ports." + ) + + return val + @pd.validator("radiation_monitors") @skip_if_fields_missing(["freqs"]) def _validate_radiation_monitors(cls, val, values): diff --git a/tidy3d/plugins/smatrix/ports/base_lumped.py b/tidy3d/plugins/smatrix/ports/base_lumped.py index 0e4516aa69..4582773128 100644 --- a/tidy3d/plugins/smatrix/ports/base_lumped.py +++ b/tidy3d/plugins/smatrix/ports/base_lumped.py @@ -94,3 +94,11 @@ def to_monitors( @abstractmethod def _check_grid_size(self, yee_grid: YeeGrid) -> None: """Raises :class:`SetupError` if the grid is too coarse at port locations.""" + + @property + def _is_using_mesh_refinement(self) -> bool: + """Check if this lumped port is using any mesh refinement options. + + Returns ``True`` if snapping points are enabled or custom grid cell count is specified. + """ + return self.enable_snapping_points or self.num_grid_cells is not None diff --git a/tidy3d/plugins/smatrix/ports/wave.py b/tidy3d/plugins/smatrix/ports/wave.py index aea67192bb..8b160c3bdc 100644 --- a/tidy3d/plugins/smatrix/ports/wave.py +++ b/tidy3d/plugins/smatrix/ports/wave.py @@ -365,3 +365,11 @@ def _validate_mode_index(cls, val, values): f"'mode_spec.num_modes' ({num_modes}). Valid range is 0 to {num_modes - 1}." ) return val + + @property + def _is_using_mesh_refinement(self) -> bool: + """Check if this wave port is using mesh refinement options. + + Returns ``True`` if a custom grid cell count is specified. + """ + return self.num_grid_cells is not None