Source code for prismo.solvers.base

"""
Abstract base class for electromagnetic solvers.

This module defines the unified interface that all solvers (FDTD, FEM, MEEP)
must implement, allowing solver-agnostic simulation orchestration.
"""

from abc import ABC, abstractmethod
from typing import Optional

import numpy as np

from prismo.core.fields import ElectromagneticFields
from prismo.core.grid import YeeGrid


[docs] class SolverBase(ABC): """ Abstract base class for all electromagnetic solvers. This class defines the unified interface that all solvers must implement, allowing the Simulation class to work with any solver type transparently. """
[docs] def __init__(self, grid: YeeGrid): """ Initialize the solver. Parameters ---------- grid : YeeGrid The simulation grid. """ self.grid = grid self.time = 0.0 self.step_count = 0
[docs] @abstractmethod def step(self, fields: Optional[ElectromagneticFields] = None) -> None: """ Perform a single time step. Parameters ---------- fields : ElectromagneticFields, optional Fields to update. If None, uses internal fields. """ pass
[docs] @abstractmethod def get_fields(self) -> ElectromagneticFields: """ Get the current electromagnetic fields. Returns ------- ElectromagneticFields Current field state. """ pass
[docs] @abstractmethod def reset(self) -> None: """Reset simulation to initial state.""" pass
[docs] @abstractmethod def get_time_step(self) -> float: """ Get the time step used by the solver. Returns ------- float Time step in seconds. """ pass
[docs] def get_simulation_info(self) -> dict: """ Get information about the current simulation state. Returns ------- dict Dictionary with simulation information. """ return { "time": self.time, "step_count": self.step_count, "dt": self.get_time_step(), "grid_dimensions": self.grid.dimensions, "is_2d": self.grid.is_2d, }
[docs] def __repr__(self) -> str: """String representation.""" info = self.get_simulation_info() return ( f"{self.__class__.__name__}(t={info['time']:.2e}s, " f"steps={info['step_count']})" )
[docs] class TimeDomainSolver(SolverBase): """ Base class for time-domain solvers (FDTD, MEEP). Time-domain solvers advance fields in time using time-stepping. """
[docs] @abstractmethod def run(self, total_time: float, callback: Optional[callable] = None) -> None: """ Run simulation for specified time. Parameters ---------- total_time : float Total simulation time in seconds. callback : callable, optional Function called after each time step. """ pass
[docs] @abstractmethod def run_steps(self, num_steps: int, callback: Optional[callable] = None) -> None: """ Run simulation for specified number of steps. Parameters ---------- num_steps : int Number of time steps to run. callback : callable, optional Function called after each time step. """ pass
[docs] class FrequencyDomainSolver(SolverBase): """ Base class for frequency-domain solvers (FEM). Frequency-domain solvers solve for fields at specific frequencies. """
[docs] @abstractmethod def solve(self, frequency: float) -> ElectromagneticFields: """ Solve for fields at a specific frequency. Parameters ---------- frequency : float Frequency in Hz. Returns ------- ElectromagneticFields Solution fields at the specified frequency. """ pass
[docs] @abstractmethod def solve_eigenvalue(self, num_modes: int = 1) -> tuple[np.ndarray, list]: """ Solve eigenvalue problem (e.g., for waveguide modes). Parameters ---------- num_modes : int Number of modes to compute. Returns ------- tuple (eigenvalues, eigenmodes) where eigenmodes is a list of fields. """ pass