From 7bfd25e57e251f2d3e316722be7eba60f0d3094d Mon Sep 17 00:00:00 2001 From: Qiming Date: Thu, 12 Sep 2024 17:31:22 -0400 Subject: [PATCH] Fixed wrong projection monitor error for out-of-domain structures --- CHANGELOG.md | 3 ++ tests/test_components/test_simulation.py | 32 +++++++++++++++ tidy3d/components/simulation.py | 52 +++++++++++++++++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bce65683f..c0a30f2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Stopped raising error when field projection monitor intersecting structures outside of the simulation domain. + ## [2.7.3] - 2024-09-12 ### Added diff --git a/tests/test_components/test_simulation.py b/tests/test_components/test_simulation.py index b68dea733..8b194074b 100644 --- a/tests/test_components/test_simulation.py +++ b/tests/test_components/test_simulation.py @@ -2977,3 +2977,35 @@ def test_validate_sources_monitors_in_bounds(): grid_spec=td.GridSpec(wavelength=1.0), monitors=[mode_monitor], ) + + +def test_2d_projection_monitor_out_sim_domain(): + # set up a structure interset with far field monitor outside the computational domain + cylinder = td.Structure( + geometry=td.Cylinder(axis=2, radius=0.5, length=2), medium=td.PECMedium() + ) + + num_phi = 100 + theta = np.pi / 2 + phis = np.linspace(0, np.pi, num_phi) + monitor_far = td.FieldProjectionAngleMonitor( + size=[2, 2, 0.5], + freqs=[1e12], + name="far_field", + theta=theta, + phi=phis, + ) + + sim2d = td.Simulation( + size=(4, 4, 0), + run_time=1e-12, + grid_spec=td.GridSpec.uniform(dl=0.025), + sources=[], + structures=[cylinder], + monitors=[monitor_far], + boundary_spec=td.BoundarySpec( + x=td.Boundary.pml(), + y=td.Boundary.pml(), + z=td.Boundary.periodic(), + ), + ) diff --git a/tidy3d/components/simulation.py b/tidy3d/components/simulation.py index b64c3f26e..1086920f8 100644 --- a/tidy3d/components/simulation.py +++ b/tidy3d/components/simulation.py @@ -136,6 +136,12 @@ # height of the PML plotting boxes along any dimensions where sim.size[dim] == 0 PML_HEIGHT_FOR_0_DIMS = inf +# fraction of structure size to monitor along 0 dim in 2D simulations +FRACTION = 0.9 + +# Minimum size of monitor +MIN_MONITOR_SIZE = 1e-12 + class AbstractYeeGridSimulation(AbstractSimulation, ABC): """ @@ -2756,7 +2762,51 @@ def _projection_monitors_homogeneous(cls, val, values): with log as consolidated_logger: for monitor_ind, monitor in enumerate(val): if isinstance(monitor, (AbstractFieldProjectionMonitor, DiffractionMonitor)): - mediums = Scene.intersecting_media(monitor, total_structures) + if monitor.volume() != 0.0: + if not ( + all( + monitor_min >= sim_min + for monitor_min, sim_min in zip( + monitor.bounds[0], structure_bg.geometry.bounds[0] + ) + ) + and all( + monitor_max <= sim_max + for monitor_max, sim_max in zip( + monitor.bounds[1], structure_bg.geometry.bounds[1] + ) + ) + ): + exclude_surfaces = [] + dimensions = ["x", "y", "z"] + for i, dim in enumerate(dimensions): + if monitor.bounds[0][i] <= structure_bg.geometry.bounds[0][i]: + exclude_surfaces.append(f"{dim}-") + if monitor.bounds[1][i] >= structure_bg.geometry.bounds[1][i]: + exclude_surfaces.append(f"{dim}+") + new_min = np.maximum(monitor.bounds[0], structure_bg.geometry.bounds[0]) + new_max = np.minimum(monitor.bounds[1], structure_bg.geometry.bounds[1]) + new_size = tuple(new_max - new_min) + # Add a minimum value to the monitor along 0-dim in 2D simulations + new_size = tuple( + MIN_MONITOR_SIZE if num == 0 else num for num in new_size + ) + new_center = tuple((new_max + new_min) / 2) + monitor_test = monitor.updated_copy(center=new_center, size=new_size) + monitor_dict = monitor_test.dict() + # Append exclude surfaces for 2D simulation to existing ones + existing_exclude_surfaces = monitor_dict.get("exclude_surfaces") or [] + updated_exclude_surfaces = list( + set(existing_exclude_surfaces + exclude_surfaces) + ) + monitor_dict["exclude_surfaces"] = updated_exclude_surfaces + surfaces = monitor_test.surfaces_with_exclusion(**monitor_dict) + mediums = set() + for surface in surfaces: + _mediums = Scene.intersecting_media(surface, total_structures) + mediums.update(_mediums) + else: + mediums = Scene.intersecting_media(monitor, total_structures) # make sure there is no more than one medium in the returned list if len(mediums) > 1: raise SetupError(