Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ComponentModeler.batch_data and pathdir enhancements #1932

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't exactly understand, do you mean pass path_dir when initializing ComponentModeler?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok: ComponentModeler(..., path_dir=actual_path_dir)
will warn: ComponentModeler.run(path_dir=different_path_dir)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and will use actual_path_dir:

ComponentModeler.run()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, yeah, that's what I thought.


### Fixed
- Significant speedup for field projection computations.
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
)
Expand All @@ -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]
Expand All @@ -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:
Expand Down Expand Up @@ -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()
Expand All @@ -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"
Expand All @@ -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

Expand All @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
39 changes: 17 additions & 22 deletions tidy3d/plugins/smatrix/component_modelers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand All @@ -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):
Expand Down
5 changes: 3 additions & 2 deletions tidy3d/plugins/smatrix/component_modelers/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion tidy3d/plugins/smatrix/component_modelers/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading