Source code for tidy3d.plugins.design.design

"""Defines design space specification for tidy3d."""
from __future__ import annotations

from typing import Tuple, Callable, Dict, Any, List, Union
import inspect

import pydantic.v1 as pd

from ...components.base import Tidy3dBaseModel, cached_property
from ...components.simulation import Simulation
from ...components.data.sim_data import SimulationData
from ...web.api.container import BatchData

from .method import MethodType
from .parameter import ParameterType
from .result import Result


[docs] class DesignSpace(Tidy3dBaseModel): """Specification of a design problem / combination of several parameters + algorithm. Example ------- >>> import tidy3d.plugins.design as tdd >>> param = tdd.ParameterFloat(name="x", span=(0,1)) >>> method = tdd.MethodMonteCarlo(num_points=10) >>> design_space = tdd.DesignSpace(parameters=[param], method=method) >>> fn = lambda x: x**2 >>> result = design_space.run(fn) >>> df = result.to_dataframe() >>> im = df.plot() """ parameters: Tuple[ParameterType, ...] = pd.Field( (), title="Parameters", description="Set of parameters defining the dimensions and allowed values for the design space.", ) method: MethodType = pd.Field( ..., title="Search Type", description="Specifications for the procedure used to explore the parameter space.", ) name: str = pd.Field(None, title="Name", description="Optional name for the design space.") @cached_property def dims(self) -> Tuple[str]: """dimensions defined by the design parameter names.""" return tuple(param.name for param in self.parameters) @cached_property def design_parameter_dict(self) -> Dict[str, ParameterType]: """Mapping of design parameter name to design parameter.""" return dict(zip(self.dims, self.parameters)) def _package_run_results( self, fn_args: Dict[str, tuple], fn_values: List[Any], fn_source: str, task_ids: Tuple[str] = None, batch_data: BatchData = None, ) -> Result: """How to package results from ``method.run`` and ``method.run_batch``""" fn_args_coords = tuple(fn_args.values()) fn_args_coords_T = list(map(list, zip(*fn_args_coords))) return Result( dims=self.dims, values=fn_values, coords=fn_args_coords_T, fn_source=fn_source, task_ids=task_ids, batch_data=batch_data, )
[docs] @staticmethod def get_fn_source(function: Callable) -> str: """Get the function source as a string, return ``None`` if not available.""" try: return inspect.getsource(function) except (TypeError, OSError): return None
[docs] def run(self, function: Callable, **kwargs) -> Result: """Run the design problem on a user defined function of the design parameters. Parameters ---------- function : Callable Function accepting arguments that correspond to the ``.name`` fields of the ``DesignSpace.parameters``. Returns ------- :class:`.Result` Object containing the results of the design space exploration. Can be converted to ``pandas.DataFrame`` with ``.to_dataframe()``. """ # run the function from the method fn_args, fn_values = self.method.run(parameters=self.parameters, fn=function) fn_source = self.get_fn_source(function) # package the result return self._package_run_results(fn_args=fn_args, fn_values=fn_values, fn_source=fn_source)
@staticmethod def _make_batch_fn_source(fn_source_pre: str, fn_source_post: str) -> str: """How to make the full function source from the pre and post functions.""" if (fn_source_pre is None) and (fn_source_post is None): return None return str(fn_source_pre) + "\n\n" + str(fn_source_post)
[docs] def run_batch( self, fn_pre: Callable[Any, Union[Simulation, List[Simulation], Dict[str, Simulation]]], fn_post: Callable[ Union[SimulationData, List[SimulationData], Dict[str, SimulationData]], Any ], path_dir: str = None, **batch_kwargs, ) -> Result: """Run a design problem where the function is split into pre and post processing steps. Parameters ---------- fn_pre : Callable[Any, Union[Simulation, List[Simulation]]] Function accepting arguments that correspond to the ``.name`` fields of the ``DesignSpace.parameters``. Returns either a :class:`.Simulation`, `list` of :class:`.Simulation`s or a `dict` of :class:`.Simulation`s to be run in a batch. fn_pre : Callable[Union[SimulationData, List[SimulationData], Dict[str, SimulationData]], Any] Function accepting the :class:`.SimulationData` object(s) corresponding to the ``fn_pre`` and returning the result of the parameter sweep function. If ``fn_pre`` returns a single simulation, it will be passed as a single argument to ``fn_post``. If ``fn_pre`` returns a list of simulations, their data will be passed as ``*args``. If ``fn_pre`` returns a dict of simulations, their data will be passed as ``**kwargs`` with the keys corresponding to the argument names. path_dir : str = None Optional directory in which to store the batch results. Returns ------- :class:`.Result` Object containing the results of the design space exploration. Can be converted to ``pandas.DataFrame`` with ``.to_dataframe()``. """ # run the functions using the `method.run_batch` fn_args, fn_values, task_ids, batch_data = self.method.run_batch( parameters=self.parameters, fn_pre=fn_pre, fn_post=fn_post, path_dir=path_dir, **batch_kwargs, ) # store the pre and post functions in a single source code fn_source_pre = self.get_fn_source(fn_pre) fn_source_post = self.get_fn_source(fn_post) fn_source = self._make_batch_fn_source(fn_source_pre, fn_source_post) # package the result return self._package_run_results( fn_args=fn_args, fn_values=fn_values, fn_source=fn_source, task_ids=task_ids, batch_data=batch_data, )