Source code for tidy3d.plugins.smatrix.run

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from tidy3d.components.data.index import SimulationDataMap
from tidy3d.exceptions import AdjointError
from tidy3d.log import log
from tidy3d.plugins.smatrix.component_modelers.modal import ModalComponentModeler
from tidy3d.plugins.smatrix.component_modelers.terminal import TerminalComponentModeler
from tidy3d.plugins.smatrix.data.modal import ModalComponentModelerData
from tidy3d.plugins.smatrix.data.terminal import TerminalComponentModelerData
from tidy3d.web import Batch
from tidy3d.web.api.autograd.types import NumericalStructureConfig

if TYPE_CHECKING:
    from collections.abc import Mapping

    from tidy3d.plugins.smatrix.component_modelers.types import ComponentModelerType
    from tidy3d.plugins.smatrix.data.types import ComponentModelerDataType
    from tidy3d.web import BatchData
    from tidy3d.web.api.autograd.types import CustomVJPSpec, SetupRunResult

DEFAULT_DATA_DIR = "."


[docs] def compose_modeler_data( modeler: ModalComponentModeler | TerminalComponentModeler, indexed_sim_data: SimulationDataMap, ) -> ComponentModelerDataType: """Create a modeler data object from a modeler and indexed simulation data. This function acts as a dispatcher, creating either a `ModalComponentModelerData` or `TerminalComponentModelerData` object based on the type of the input `modeler`. Parameters ---------- modeler : ModalComponentModeler | TerminalComponentModeler The component modeler for which to create the data object. indexed_sim_data : SimulationDataMap A map of simulation data indexed by port names. Returns ------- ComponentModelerDataType The appropriate data object containing the simulation results. Raises ------ TypeError If the provided `modeler` is not a recognized type. """ if isinstance(modeler, ModalComponentModeler): modeler_data = ModalComponentModelerData(modeler=modeler, data=indexed_sim_data) elif isinstance(modeler, TerminalComponentModeler): modeler_data = TerminalComponentModelerData(modeler=modeler, data=indexed_sim_data) else: raise TypeError(f"Unsupported modeler type: {type(modeler).__name__}") return modeler_data
[docs] def compose_modeler_data_from_batch_data( modeler: ComponentModelerType, batch_data: BatchData, ) -> ComponentModelerDataType: """Select the correct composer based on modeler type and create the data object. This method acts as a dispatcher, inspecting the type of `modeler` to determine which composer function to invoke. It populates a `ComponentModelerData` object with results from a `BatchData` object. Parameters ---------- modeler : ComponentModelerType The component modeler, which can be either a `ModalComponentModeler` or a `TerminalComponentModeler`. batch_data : BatchData The results obtained from running the simulation `Batch`. Returns ------- ComponentModelerDataType The appropriate data object containing the simulation results. Raises ------ TypeError If the provided `modeler` is not a recognized type. """ port_simulation_data = SimulationDataMap( keys=tuple(batch_data.keys()), values=tuple(batch_data.values()) ) return compose_modeler_data(modeler=modeler, indexed_sim_data=port_simulation_data)
[docs] def create_batch( modeler: ComponentModelerType, **kwargs: Any, ) -> Batch: """Create a simulation Batch from a component modeler. Parameters ---------- modeler : ComponentModelerType The component modeler that defines the set of simulations. **kwargs Additional keyword arguments passed to the `Batch` constructor. Returns ------- Batch The configured `Batch` object ready for execution. """ batch = Batch( simulations=modeler.sim_dict, **kwargs, ) return batch
def _run_local( modeler: ComponentModelerType, path_dir: str = DEFAULT_DATA_DIR, numerical_structures: NumericalStructureConfig | tuple[NumericalStructureConfig, ...] | None = None, custom_vjp: CustomVJPSpec | None = None, setup_results: Mapping[str, SetupRunResult] | None = None, **kwargs: Any, ) -> ComponentModelerDataType: """Execute the full simulation workflow for a given component modeler. This function orchestrates the end-to-end process: 1. Creates a `Batch` of simulations from the `modeler`. 2. Submits the `Batch` for execution and waits for results. 3. Composes the results into a structured `ComponentModelerDataType` object. Parameters ---------- modeler : ComponentModelerType The component modeler defining the simulations to be run. path_dir : str, optional The directory where the batch file will be saved. Defaults to ".". numerical_structures : Union[NumericalStructureConfig, tuple[NumericalStructureConfig, ...]] = None Specification of additional structures to add to the base simulation that can be traced via autograd. This can be a single structure or multiple structures specified in a tuple. custom_vjp : Union[CustomVJPConfig, tuple[CustomVJPConfig, ...]] = None Specification of alternate gradient function for certain structures in the simulation. This can be a single vjp configuration or multiple specified in a tuple. **kwargs Extra keyword arguments propagated to the Batch creation. Returns ------- ComponentModelerDataType An object containing the processed simulation data, ready for S-parameter extraction and analysis. """ # autograd path if any sim is valid for autograd from tidy3d.web.api.autograd import autograd as web_ag sims = modeler.sim_dict if isinstance(numerical_structures, NumericalStructureConfig): numerical_structures = (numerical_structures,) if numerical_structures: web_ag.validate_numerical_structure_parameters(numerical_structures) traced_numerical_structures = numerical_structures and web_ag.has_traced_numerical_structures( numerical_structures ) modeler_numerical_structures = ( dict.fromkeys(sims, numerical_structures) if numerical_structures else None ) if setup_results is None: ( needs_autograd, expanded_custom_vjp_dict, setup_results, ) = web_ag._prepare_simulation_mapping_for_autograd( sims, custom_vjp=custom_vjp, numerical_structures=modeler_numerical_structures, ) else: needs_autograd = tuple(setup_results[name].needs_autograd for name in sims) expanded_custom_vjp_dict = web_ag._expand_custom_vjp_for_simulations( sims, custom_vjp, ) should_use_autograd = traced_numerical_structures or any(needs_autograd) if should_use_autograd: if len(modeler.element_mappings) > 0: log.warning( "Element mappings are used to populate S-matrix values, but autograd gradients " "are computed only for simulated elements. Gradients for mapped elements are not " "included. For optimization with autograd, prefer enforcing symmetry in geometry/" "objective functions and use 'run_only' to select unique sources.", log_once=True, ) from tidy3d.web.api.autograd.autograd import _run_async kwargs.setdefault("folder_name", "default") kwargs.setdefault("simulation_type", "tidy3d_autograd_async") kwargs.setdefault("path_dir", path_dir) local_gradient = kwargs.get("local_gradient", True) if not local_gradient: if custom_vjp is not None: raise AdjointError("custom_vjp specified for a remote gradient not supported.") if traced_numerical_structures: raise AdjointError( "ComponentModeler autograd with traced numerical structures requires local_gradient=True." ) if numerical_structures: numerical_structures = modeler_numerical_structures sim_data_map = _run_async( simulations=sims, numerical_structures=numerical_structures, custom_vjp=expanded_custom_vjp_dict, setup_results=setup_results, **kwargs, ) return compose_modeler_data_from_batch_data(modeler=modeler, batch_data=sim_data_map) if numerical_structures is not None: modeler = modeler.updated_copy( simulation=web_ag.insert_numerical_structures_static( simulation=modeler.simulation, numerical_structures=numerical_structures ) ) # Filter kwargs to only include valid Batch parameters batch_kwargs = { k: v for k, v in kwargs.items() if k in { "solver_version", "folder_name", "verbose", "callback_url", "simulation_type", "parent_tasks", "num_workers", "reduce_simulation", "pay_type", } } batch = create_batch(modeler=modeler, **batch_kwargs) run_kwargs: dict[str, Any] = {"path_dir": path_dir} priority = kwargs.get("priority") if priority is not None: run_kwargs["priority"] = priority vgpu_allocation = kwargs.get("vgpu_allocation") if vgpu_allocation is not None: run_kwargs["vgpu_allocation"] = vgpu_allocation ignore_memory_limit = kwargs.get("ignore_memory_limit") if ignore_memory_limit is not None: run_kwargs["ignore_memory_limit"] = ignore_memory_limit batch_data = batch.run(**run_kwargs) return compose_modeler_data_from_batch_data(modeler=modeler, batch_data=batch_data)