Source code for tidy3d.components.index

"""
This module defines the SimulationMap, a specialized container for managing
a set of Tidy3D simulations, allowing them to be accessed by name.
"""

from __future__ import annotations

from collections.abc import Mapping
from typing import TYPE_CHECKING, Any

from pydantic import Field, model_validator

from tidy3d.components.base import Tidy3dBaseModel
from tidy3d.components.types.base import discriminated_union
from tidy3d.components.types.simulation import SimulationType

if TYPE_CHECKING:
    from collections.abc import Iterator

    from tidy3d.compat import Self


class ValueMap(Tidy3dBaseModel, Mapping[str, Any]):
    """An immutable dictionary-like container for objects.

    Notes
    -----
        This class maps unique string keys to corresponding value objects.
        By inheriting from `collections.abc.Mapping`, it provides standard dictionary
        behaviors like item access (`my_dict["my_key"]`), iteration (`for name in my_dict`), and
        length checking (`len(my_dict)`).

        It automatically validates that the `keys` and `values`
        tuples have matching lengths upon instantiation.

    Attributes
    ----------
    keys : tuple[str, ...]
        A tuple of unique string identifiers for each simulation.
    values : tuple[Any, ...]
        A tuple of `Any`-type objects, each corresponding to a key at the
        same index. Should be overwritten by the subclass instantiation
    """

    keys_tuple: tuple[str, ...] = Field(
        description="A tuple of unique string identifiers for each simulation.", alias="keys"
    )
    values_tuple: tuple[Any, ...] = Field(
        description=(
            "A tuple of `Simulation` objects, each corresponding to a key at the same index."
        ),
        alias="values",
    )

    @model_validator(mode="after")
    def _validate_lengths_match(self) -> Self:
        """Ensure 'keys' and 'values' have the same length.

        Returns
        -------
        Self
            The validated instance.

        Raises
        ------
        ValueError
            If the lengths of the 'keys' and 'values' tuples are not equal.
        """
        keys, values = self.keys_tuple, self.values_tuple
        if len(keys) != len(values):
            raise ValueError("Length of 'keys' and 'values' must be the same.")
        return self

    def __getitem__(self, key: str) -> Any:
        """Retrieves a `Simulation` object by its corresponding key.

        This allows for dictionary-style access, e.g., `my_map["my_key"]`.

        Parameters
        ----------
        key : str
            The string name of the item to retrieve.

        Returns
        -------
        SimulationType
            The object corresponding to the given key.

        Raises
        ------
        KeyError
            If no object with the given key name is found in the map.
        """
        for i, current_key in enumerate(self.keys_tuple):
            if current_key == key:
                return self.values_tuple[i]
        raise KeyError(f"Key '{key}' not found in the {type(self)}.")

    def __iter__(self) -> Iterator[str]:
        """Returns an iterator over the string keys of the map.

        This allows for standard iteration, e.g., `for key in my_map:`.

        Yields
        ------
        str
            The next key in the map.
        """
        return iter(self.keys_tuple)

    def __len__(self) -> int:
        """Returns the number of key-value pairs stored in the map.

        This allows for using the built-in `len()` function, e.g., `len(my_map)`.

        Returns
        -------
        int
            The total number of items in the map.
        """
        return len(self.keys_tuple)


[docs] class SimulationMap(ValueMap, Mapping[str, SimulationType]): """An immutable dictionary-like container for simulations. Notes ----- This class maps unique string keys to corresponding `Simulation` objects. By inheriting from `collections.abc.Mapping`, it provides standard dictionary behaviors like item access (`sims["sim_A"]`), iteration (`for name in sims`), and length checking (`len(sims)`). It automatically validates that the `keys` and `values` tuples have matching lengths upon instantiation. Attributes ---------- keys : tuple[str, ...] A tuple of unique string identifiers for each simulation. values : tuple[SimulationType, ...] A tuple of `Simulation` objects, each corresponding to a key at the same index. Example ------- >>> from tidy3d import ( ... Simulation, ... SimulationMap, ... Structure, ... Box, ... Medium, ... UniformCurrentSource, ... GaussianPulse, ... FieldMonitor, ... GridSpec, ... BoundarySpec, ... PML, ... ) >>> # Simple minimal simulation >>> sim1 = Simulation( ... size=(4, 3, 3), ... grid_spec=GridSpec.auto(min_steps_per_wvl=25), ... structures=[ ... Structure( ... geometry=Box(size=(1, 1, 1), center=(0, 0, 0)), ... medium=Medium(permittivity=2.0), ... ), ... ], ... sources=[ ... UniformCurrentSource( ... size=(0, 0, 0), ... center=(0, 0.5, 0), ... polarization="Hx", ... source_time=GaussianPulse(freq0=2e14, fwidth=4e13), ... current_amplitude_definition="total", ... ) ... ], ... monitors=[ ... FieldMonitor( ... size=(1, 1, 0), ... center=(0, 0, 0), ... freqs=[2e14], ... name='field' ... ), ... ], ... run_time=1e-12, ... boundary_spec=BoundarySpec.all_sides(boundary=PML()), ... ) >>> sim2 = sim1.updated_copy(run_time=2e-12) >>> >>> # Instantiate the map >>> simulation_map = SimulationMap( ... keys=("sim_1", "sim_2"), ... values=(sim1, sim2), ... ) >>> >>> # Access a simulation like a dictionary >>> # print(simulation_map["sim_1"]) """ keys_tuple: tuple[str, ...] = Field( description="A tuple of unique string identifiers for each simulation.", alias="keys" ) values_tuple: tuple[discriminated_union(SimulationType), ...] = Field( description=( "A tuple of `Simulation` objects, each corresponding to a key at the same index." ), alias="values", )