From 48027ef2891213f62b714eeaab1e6de640467b16 Mon Sep 17 00:00:00 2001 From: Tyler Hughes Date: Fri, 30 Aug 2024 08:07:15 -0400 Subject: [PATCH] make ComponentModeler.batch_data property, simplify internals, deprecate passing path_dir to methods --- CHANGELOG.md | 2 + tests/test_plugins/smatrix/__init__.py | 0 .../terminal_component_modeler_def.py | 0 .../{ => smatrix}/test_component_modeler.py | 67 +++++++++---------- .../test_terminal_component_modeler.py | 14 ++-- .../smatrix/component_modelers/base.py | 39 +++++------ .../smatrix/component_modelers/modal.py | 5 +- .../smatrix/component_modelers/terminal.py | 4 +- 8 files changed, 60 insertions(+), 71 deletions(-) create mode 100644 tests/test_plugins/smatrix/__init__.py rename tests/test_plugins/{ => smatrix}/terminal_component_modeler_def.py (100%) rename tests/test_plugins/{ => smatrix}/test_component_modeler.py (87%) rename tests/test_plugins/{ => smatrix}/test_terminal_component_modeler.py (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94dfbeffd..9fea06af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,10 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added plotting functionality to voltage and current path integrals in the `microwave` plugin. - Added convenience functions `from_terminal_positions` and `from_circular_path` to simplify setup of `VoltageIntegralAxisAligned` and `CustomCurrentIntegral2D`, respectively. - Added `axial_ratio` to `DirectivityData.axial_ratio` for the `DirectivityMonitor`, defined as the ratio of the major axis to the minor axis of the polarization ellipse. +`ComponentModeler.batch_data` convenience property to access the `BatchData` corresponding to the component modeler run. ### Changed - Priority is given to `snapping_points` in `GridSpec` when close to structure boundaries, which reduces the chance of them being skipped. - Gradients for autograd are computed server-side by default. They can be computed locally (requiring more data download) by passing `local_gradient=True` to the `web.run()` and related functions. +- Passing `path_dir` to `ComponentModeler` methods is deprecated in favor of setting `ComponentModeler.path_dir` and will result in an error if the two don't match. ### Fixed - Significant speedup for field projection computations. diff --git a/tests/test_plugins/smatrix/__init__.py b/tests/test_plugins/smatrix/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_plugins/terminal_component_modeler_def.py b/tests/test_plugins/smatrix/terminal_component_modeler_def.py similarity index 100% rename from tests/test_plugins/terminal_component_modeler_def.py rename to tests/test_plugins/smatrix/terminal_component_modeler_def.py diff --git a/tests/test_plugins/test_component_modeler.py b/tests/test_plugins/smatrix/test_component_modeler.py similarity index 87% rename from tests/test_plugins/test_component_modeler.py rename to tests/test_plugins/smatrix/test_component_modeler.py index ca4534ccc..ff7609c83 100644 --- a/tests/test_plugins/test_component_modeler.py +++ b/tests/test_plugins/smatrix/test_component_modeler.py @@ -11,7 +11,7 @@ ) from tidy3d.web.api.container import Batch -from ..utils import run_emulated +from ...utils import run_emulated # Waveguide height wg_height = 0.22 @@ -191,22 +191,15 @@ def make_component_modeler(**kwargs): def run_component_modeler(monkeypatch, modeler: ComponentModeler): - # values = dict( - # simulation=modeler.simulation, - # ports=modeler.ports, - # freqs=modeler.freqs, - # run_only=modeler.run_only, - # element_mappings=modeler.element_mappings, - # ) sim_dict = modeler.sim_dict batch_data = {task_name: run_emulated(sim) for task_name, sim in sim_dict.items()} - monkeypatch.setattr(ComponentModeler, "_run_sims", lambda self, path_dir: batch_data) - s_matrix = modeler.run(path_dir=modeler.path_dir) + monkeypatch.setattr(ComponentModeler, "batch_data", property(lambda self: batch_data)) + s_matrix = modeler._construct_smatrix() return s_matrix -def test_validate_no_sources(tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) +def test_validate_no_sources(): + modeler = make_component_modeler() source = td.PointDipole( source_time=td.GaussianPulse(freq0=2e14, fwidth=1e14), polarization="Ex" ) @@ -215,21 +208,21 @@ def test_validate_no_sources(tmp_path): _ = modeler.copy(update=dict(simulation=sim_w_source)) -def test_element_mappings_none(tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) +def test_element_mappings_none(): + modeler = make_component_modeler() modeler = modeler.updated_copy(ports=[], element_mappings=()) _ = modeler.matrix_indices_run_sim -def test_no_port(tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) +def test_no_port(): + modeler = make_component_modeler() _ = modeler.ports with pytest.raises(Tidy3dKeyError): modeler.get_port_by_name(port_name="NOT_A_PORT") -def test_ports_too_close_boundary(tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) +def test_ports_too_close_boundary(): + modeler = make_component_modeler() grid_boundaries = modeler.simulation.grid.boundaries.to_list[0] way_outside = grid_boundaries[0] - 1000 xmin = grid_boundaries[1] @@ -252,30 +245,30 @@ def test_validate_batch_supplied(tmp_path): ) -def test_plot_sim(tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) +def test_plot_sim(): + modeler = make_component_modeler() modeler.plot_sim(z=0) plt.close() -def test_plot_sim_eps(tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) +def test_plot_sim_eps(): + modeler = make_component_modeler() modeler.plot_sim_eps(z=0) plt.close() -def test_make_component_modeler(tmp_path): - _ = make_component_modeler(path_dir=str(tmp_path)) +def test_make_component_modeler(): + _ = make_component_modeler() -def test_run(monkeypatch, tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) - monkeypatch.setattr(ComponentModeler, "run", lambda self, path_dir: None) - modeler.run(path_dir=str(tmp_path)) +def test_run(monkeypatch): + modeler = make_component_modeler() + monkeypatch.setattr(ComponentModeler, "run", lambda self, path_dir=None: None) + modeler.run() -def test_run_component_modeler(monkeypatch, tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) +def test_run_component_modeler(monkeypatch): + modeler = make_component_modeler() s_matrix = run_component_modeler(monkeypatch, modeler) for port_in in modeler.ports: @@ -338,17 +331,17 @@ def _test_mappings(element_mappings, s_matrix): ), "mapping not applied correctly." -def test_run_component_modeler_mappings(monkeypatch, tmp_path): +def test_run_component_modeler_mappings(monkeypatch): element_mappings = ( ((("left_bot", 0), ("right_bot", 0)), (("left_top", 0), ("right_top", 0)), -1j), ((("left_bot", 0), ("right_top", 0)), (("left_top", 0), ("right_bot", 0)), +1), ) - modeler = make_component_modeler(element_mappings=element_mappings, path_dir=str(tmp_path)) + modeler = make_component_modeler(element_mappings=element_mappings) s_matrix = run_component_modeler(monkeypatch, modeler) _test_mappings(element_mappings, s_matrix) -def test_mapping_exclusion(monkeypatch, tmp_path): +def test_mapping_exclusion(monkeypatch): """Make sure that source indices are skipped if totally covered by element mapping.""" _ = make_coupler() @@ -369,7 +362,7 @@ def test_mapping_exclusion(monkeypatch, tmp_path): mapping = ((("right_bot", 1), ("right_bot", 1)), (EXCLUDE_INDEX, EXCLUDE_INDEX), +1) element_mappings.append(mapping) - modeler = make_component_modeler(element_mappings=element_mappings, path_dir=str(tmp_path)) + modeler = make_component_modeler(element_mappings=element_mappings) run_sim_indices = modeler.matrix_indices_run_sim assert EXCLUDE_INDEX not in run_sim_indices, "mapping didnt exclude row properly" @@ -379,7 +372,7 @@ def test_mapping_exclusion(monkeypatch, tmp_path): def test_batch_filename(tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) + modeler = make_component_modeler() path = modeler._batch_path assert path @@ -388,8 +381,8 @@ def test_import_smatrix_smatrix(): from tidy3d.plugins.smatrix.smatrix import ComponentModeler, Port # noqa: F401 -def test_to_from_file_batch(monkeypatch, tmp_path): - modeler = make_component_modeler(path_dir=str(tmp_path)) +def test_to_from_file_batch(tmp_path, monkeypatch): + modeler = make_component_modeler() _ = run_component_modeler(monkeypatch, modeler) batch = td.web.Batch(simulations=dict()) diff --git a/tests/test_plugins/test_terminal_component_modeler.py b/tests/test_plugins/smatrix/test_terminal_component_modeler.py similarity index 97% rename from tests/test_plugins/test_terminal_component_modeler.py rename to tests/test_plugins/smatrix/test_terminal_component_modeler.py index 8e842d5b4..010e47437 100644 --- a/tests/test_plugins/test_terminal_component_modeler.py +++ b/tests/test_plugins/smatrix/test_terminal_component_modeler.py @@ -17,26 +17,22 @@ ) from tidy3d.plugins.smatrix.ports.base_lumped import AbstractLumpedPort -from ..utils import run_emulated +from ...utils import run_emulated from .terminal_component_modeler_def import make_coaxial_component_modeler, make_component_modeler def run_component_modeler(monkeypatch, modeler: TerminalComponentModeler): sim_dict = modeler.sim_dict batch_data = {task_name: run_emulated(sim) for task_name, sim in sim_dict.items()} - monkeypatch.setattr(TerminalComponentModeler, "_run_sims", lambda self, path_dir: batch_data) - # for the random data, the power wave matrix might be singular, leading to an error - # during inversion, so monkeypatch the inv method so that it operates on a random matrix - monkeypatch.setattr( - AbstractComponentModeler, "inv", lambda matrix: np.random.rand(*matrix.shape) + 1e-4 - ) - # Need to do something similar when computing F + monkeypatch.setattr(AbstractComponentModeler, "batch_data", property(lambda self: batch_data)) + monkeypatch.setattr(TerminalComponentModeler, "batch_data", property(lambda self: batch_data)) + monkeypatch.setattr(AbstractComponentModeler, "inv", lambda matrix: np.eye(len(modeler.ports))) monkeypatch.setattr( TerminalComponentModeler, "_compute_F", lambda matrix: 1.0 / (2.0 * np.sqrt(np.abs(matrix) + 1e-4)), ) - s_matrix = modeler.run(path_dir=modeler.path_dir) + s_matrix = modeler._construct_smatrix() return s_matrix diff --git a/tidy3d/plugins/smatrix/component_modelers/base.py b/tidy3d/plugins/smatrix/component_modelers/base.py index adbcaa1e7..240df16aa 100644 --- a/tidy3d/plugins/smatrix/component_modelers/base.py +++ b/tidy3d/plugins/smatrix/component_modelers/base.py @@ -15,7 +15,6 @@ from ....components.types import FreqArray from ....constants import HERTZ from ....exceptions import SetupError, Tidy3dKeyError -from ....log import log from ....web.api.container import Batch, BatchData from ..ports.coaxial_lumped import CoaxialLumpedPort from ..ports.modal import Port @@ -172,22 +171,23 @@ def batch(self) -> Batch: @cached_property def batch_path(self) -> str: """Path to the batch saved to file.""" + return self.batch._batch_path(path_dir=self.path_dir) - return self.batch._batch_path(path_dir=DEFAULT_DATA_DIR) + @cached_property + def batch_data(self) -> BatchData: + """The ``BatchData`` associated with the simulations run for this component modeler.""" + return self.batch.run(path_dir=self.path_dir) def get_path_dir(self, path_dir: str) -> None: """Check whether the supplied 'path_dir' matches the internal field value.""" - if path_dir not in (DEFAULT_DATA_DIR, self.path_dir): - log.warning( - f"'ComponentModeler' method was supplied a 'path_dir' of '{path_dir}' " - f"when its internal 'path_dir' field was set to '{self.path_dir}'. " - "The passed value will be deprecated in later versions. " - "Please set the internal 'path_dir' field to the desired value and " - "remove the 'path_dir' from the method argument. " - f"Using supplied '{path_dir}'." + if path_dir != self.path_dir: + raise ValueError( + f"'path_dir' of '{path_dir}' passed, but 'ComponentModeler.path_dir' is " + f"{self.path_dir}. Moving forward, only the 'ComponentModeler.path_dir' will be " + "used internally, please update your scripts accordingly to avoid passing this " + "value to methods. " ) - return path_dir return self.path_dir @@ -198,10 +198,9 @@ def _batch_path(self) -> str: def _run_sims(self, path_dir: str = DEFAULT_DATA_DIR) -> BatchData: """Run :class:`Simulations` for each port and return the batch after saving.""" - batch = self.batch - - batch_data = batch.run(path_dir=path_dir) - batch.to_file(self._batch_path) + _ = self.get_path_dir(path_dir) + self.batch.to_file(self._batch_path) + batch_data = self.batch_data return batch_data def get_port_by_name(self, port_name: str) -> Port: @@ -217,16 +216,12 @@ def _construct_smatrix(self, batch_data: BatchData) -> DataArray: def run(self, path_dir: str = DEFAULT_DATA_DIR) -> DataArray: """Solves for the scattering matrix of the system.""" - path_dir = self.get_path_dir(path_dir) - batch_data = self._run_sims(path_dir=path_dir) - return self._construct_smatrix(batch_data=batch_data) + _ = self.get_path_dir(path_dir) + return self._construct_smatrix() def load(self, path_dir: str = DEFAULT_DATA_DIR) -> DataArray: """Load a scattering matrix from saved :class:`BatchData` object.""" - path_dir = self.get_path_dir(path_dir) - - batch_data = BatchData.load(path_dir=path_dir) - return self._construct_smatrix(batch_data=batch_data) + return self.run(path_dir=path_dir) @staticmethod def inv(matrix: DataArray): diff --git a/tidy3d/plugins/smatrix/component_modelers/modal.py b/tidy3d/plugins/smatrix/component_modelers/modal.py index 18e6c3ee2..dd494f272 100644 --- a/tidy3d/plugins/smatrix/component_modelers/modal.py +++ b/tidy3d/plugins/smatrix/component_modelers/modal.py @@ -17,7 +17,6 @@ from ....components.types import Ax, Complex from ....components.viz import add_ax_if_none, equal_aspect from ....exceptions import SetupError -from ....web.api.container import BatchData from ..ports.modal import ModalPortDataArray, Port from .base import FWIDTH_FRAC, AbstractComponentModeler @@ -256,9 +255,11 @@ def get_max_mode_indices(matrix_elements: Tuple[str, int]) -> int: return max_mode_index_out, max_mode_index_in - def _construct_smatrix(self, batch_data: BatchData) -> ModalPortDataArray: + def _construct_smatrix(self) -> ModalPortDataArray: """Post process `BatchData` to generate scattering matrix.""" + batch_data = self.batch_data + max_mode_index_out, max_mode_index_in = self.max_mode_index num_modes_out = max_mode_index_out + 1 num_modes_in = max_mode_index_in + 1 diff --git a/tidy3d/plugins/smatrix/component_modelers/terminal.py b/tidy3d/plugins/smatrix/component_modelers/terminal.py index 80c067c90..c723696c6 100644 --- a/tidy3d/plugins/smatrix/component_modelers/terminal.py +++ b/tidy3d/plugins/smatrix/component_modelers/terminal.py @@ -182,9 +182,11 @@ def _source_time(self): freq0=freq0, fwidth=fwidth, remove_dc_component=self.remove_dc_component ) - def _construct_smatrix(self, batch_data: BatchData) -> TerminalPortDataArray: + def _construct_smatrix(self) -> TerminalPortDataArray: """Post process ``BatchData`` to generate scattering matrix.""" + batch_data = self.batch_data + port_names = [port.name for port in self.ports] values = np.zeros(