diff --git a/CHANGELOG.md b/CHANGELOG.md index b3e851eb2..ea1e44e84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Indent for the json string of Tidy3D models has been changed to `None` when used internally; kept as `indent=4` for writing to `json` and `yaml` files. - API for specifying one or more nonlinear models via `NonlinearSpec.models`. +- `freqs` and `direction` are optional in `ModeSolver` methods converting to monitor and source, respectively. If not supplied, uses the values from the `ModeSolver` instance calling the method. ### Fixed - Fixed the duplication of log messages in Jupyter when `set_logging_file` is used. diff --git a/tests/test_plugins/test_mode_solver.py b/tests/test_plugins/test_mode_solver.py index 76fc74fd7..dc303cfe4 100644 --- a/tests/test_plugins/test_mode_solver.py +++ b/tests/test_plugins/test_mode_solver.py @@ -653,3 +653,56 @@ def test_mode_solver_nan_pol_fraction(): md = ms.solve() assert list(np.where(np.isnan(md.pol_fraction.te))[1]) == [8, 9] + + +def test_mode_solver_method_defaults(): + """Test that changes to mode solver default values in methods work.""" + + simulation = td.Simulation( + medium=td.Medium(permittivity=2), + size=SIM_SIZE, + grid_spec=td.GridSpec.auto(wavelength=1.55, min_steps_per_wvl=15), + run_time=1e-12, + symmetry=(0, 0, 1), + boundary_spec=td.BoundarySpec.all_sides(boundary=td.Periodic()), + sources=[SRC], + ) + + mode_spec = td.ModeSpec( + num_modes=10, + target_neff=3.48, + filter_pol="tm", + precision="single", + track_freq="central", + ) + + freqs = [td.C_0 / 1.55] + + ms = ModeSolver( + simulation=simulation, + plane=td.Box(center=(0, 0, 0), size=(2, 0, 1.1)), + mode_spec=mode_spec, + freqs=freqs, + direction="-", + ) + + # test defaults + st = td.GaussianPulse(freq0=1.0, fwidth=1.0) + + src = ms.to_source(source_time=st) + assert src.direction == ms.direction + + src = ms.to_source(source_time=st, direction="+") + assert src.direction != ms.direction + + mnt = ms.to_monitor(name="mode_mnt") + assert np.allclose(mnt.freqs, ms.freqs) + + mnt = ms.to_monitor(name="mode_mnt", freqs=[2e14]) + assert not np.allclose(mnt.freqs, ms.freqs) + + sim = ms.sim_with_source(source_time=st) + assert sim.sources[-1].direction == ms.direction + + sim = ms.sim_with_monitor(name="test") + assert np.allclose(sim.monitors[-1].freqs, ms.freqs) diff --git a/tidy3d/plugins/mode/mode_solver.py b/tidy3d/plugins/mode/mode_solver.py index b2425955c..686d27f07 100644 --- a/tidy3d/plugins/mode/mode_solver.py +++ b/tidy3d/plugins/mode/mode_solver.py @@ -573,7 +573,7 @@ def _grid_correction( def to_source( self, source_time: SourceTime, - direction: Direction, + direction: Direction = None, mode_index: pydantic.NonNegativeInt = 0, ) -> ModeSource: """Creates :class:`.ModeSource` from a :class:`ModeSolver` instance plus additional @@ -583,8 +583,9 @@ def to_source( ---------- source_time: :class:`.SourceTime` Specification of the source time-dependence. - direction : Direction + direction : Direction = None Whether source will inject in ``"+"`` or ``"-"`` direction relative to plane normal. + If not specified, uses the direction from the mode solver. mode_index : int = 0 Index into the list of modes returned by mode solver to use in source. @@ -595,6 +596,9 @@ def to_source( inputs. """ + if direction is None: + direction = self.direction + return ModeSource( center=self.plane.center, size=self.plane.size, @@ -604,7 +608,7 @@ def to_source( direction=direction, ) - def to_monitor(self, freqs: List[float], name: str) -> ModeMonitor: + def to_monitor(self, freqs: List[float] = None, name: str = None) -> ModeMonitor: """Creates :class:`ModeMonitor` from a :class:`ModeSolver` instance plus additional specifications. @@ -612,6 +616,7 @@ def to_monitor(self, freqs: List[float], name: str) -> ModeMonitor: ---------- freqs : List[float] Frequencies to include in Monitor (Hz). + If not specified, passes ``self.freqs``. name : str Required name of monitor. @@ -622,6 +627,15 @@ def to_monitor(self, freqs: List[float], name: str) -> ModeMonitor: inputs. """ + if freqs is None: + freqs = self.freqs + + if name is None: + raise ValueError( + "A 'name' must be passed to 'ModeSolver.to_monitor'. " + "The default value of 'None' is for backwards compatibility and is not accepted." + ) + return ModeMonitor( center=self.plane.center, size=self.plane.size, @@ -663,7 +677,7 @@ def to_mode_solver_monitor(self, name: str, colocate: bool = None) -> ModeSolver def sim_with_source( self, source_time: SourceTime, - direction: Direction, + direction: Direction = None, mode_index: pydantic.NonNegativeInt = 0, ) -> Simulation: """Creates :class:`Simulation` from a :class:`ModeSolver`. Creates a copy of @@ -674,8 +688,9 @@ def sim_with_source( ---------- source_time: :class:`.SourceTime` Specification of the source time-dependence. - direction : Direction + direction : Direction = None Whether source will inject in ``"+"`` or ``"-"`` direction relative to plane normal. + If not specified, uses the direction from the mode solver. mode_index : int = 0 Index into the list of modes returned by mode solver to use in source. @@ -685,6 +700,7 @@ def sim_with_source( Copy of the simulation with a :class:`.ModeSource` with specifications taken from the ModeSolver instance and the method inputs. """ + mode_source = self.to_source( mode_index=mode_index, direction=direction, source_time=source_time ) @@ -694,8 +710,8 @@ def sim_with_source( def sim_with_monitor( self, - freqs: List[float], - name: str, + freqs: List[float] = None, + name: str = None, ) -> Simulation: """Creates :class:`.Simulation` from a :class:`ModeSolver`. Creates a copy of the ModeSolver's original simulation with a mode monitor added corresponding to @@ -703,8 +719,9 @@ def sim_with_monitor( Parameters ---------- - freqs : List[float] + freqs : List[float] = None Frequencies to include in Monitor (Hz). + If not specified, uses the frequencies from the mode solver. name : str Required name of monitor. @@ -714,6 +731,7 @@ def sim_with_monitor( Copy of the simulation with a :class:`.ModeMonitor` with specifications taken from the ModeSolver instance and the method inputs. """ + mode_monitor = self.to_monitor(freqs=freqs, name=name) new_monitors = list(self.simulation.monitors) + [mode_monitor] new_sim = self.simulation.updated_copy(monitors=new_monitors)