Source code for tdgl.solver.options

from dataclasses import dataclass
from enum import Enum
from typing import Union


class SolverOptionsError(ValueError):
    pass


[docs]class SparseSolver(Enum): """Supported sparse linear solvers.""" SUPERLU: str = "superlu" UMFPACK: str = "umfpack" PARDISO: str = "pardiso" CUPY: str = "cupy"
[docs]@dataclass class SolverOptions: """Options for the TDGL solver. Args: solve_time: Total simulation time, after any thermalization. skip_time: Amount of 'thermalization' time to simulate before recording data. dt_init: Initial time step. dt_max: Maximum adaptive time step. adaptive: Whether to use an adpative time step. Setting ``dt_init = dt_max`` is equivalent to setting ``adaptive = False``. adaptive_window: Number of most recent solve steps to consider when computing the time step adaptively. max_solve_retries: The maximum number of times to reduce the time step in a given solve iteration before giving up. adaptive_time_step_multiplier: The factor by which to multiple the time step ``dt`` for each adaptive solve retry. terminal_psi: Fixed value for the order parameter in current terminals. output_file: Path to an HDF5 file in which to save the data. If the file name already exists, a unique name will be generated. If ``output_file`` is ``None``, the solver results will not be saved to disk. gpu: Use the GPU via CuPy. This option requires a GPU and the CuPy Python package, which can be installed via pip. sparse_solver: One of ``"superlu"``, ``"umfpack"``, ``"pardiso"``, or ``"cupy"``. ``"umfpack"`` requires suitesparse, which can be installed via conda, and scikit-umfpack, which can be installed via pip. ``"pardiso"`` requires an Intel CPU and the pypardiso package, which can be installed via pip or conda. ``"cupy"`` requires a GPU and the CuPy Python package, which can be installed via pip. field_units: The units for magnetic fields. current_units: The units for currents. pause_on_interrupt: Pause the simulation in the event of a ``KeyboardInterrupt``. save_every: Save interval in units of solve steps. progress_interval: Minimum number of solve steps between progress bar updates. monitor: Plot data in real time as the simulation is running. monitor_update_interval: The monitor update interval in seconds. include_screening: Whether to include screening in the simulation. max_iterations_per_step: The maximum number of screening iterations per solve step. screening_tolerance: Relative tolerance for the induced vector potential, used to evaluate convergence of the screening calculation within a single time step. screening_step_size: Step size :math:`\\alpha` for Polyak's method. screening_step_drag: Drag parameter :math:`\\beta` for Polyak's method. """ solve_time: float skip_time: float = 0.0 dt_init: float = 1e-6 dt_max: float = 1e-1 adaptive: bool = True adaptive_window: int = 10 max_solve_retries: int = 10 adaptive_time_step_multiplier: float = 0.25 output_file: Union[str, None] = None terminal_psi: Union[float, complex, None] = 0.0 gpu: bool = False sparse_solver: Union[SparseSolver, str] = SparseSolver.SUPERLU pause_on_interrupt: bool = True save_every: int = 100 progress_interval: int = 0 monitor: bool = False monitor_update_interval: float = 1.0 field_units: str = "mT" current_units: str = "uA" include_screening: bool = False max_iterations_per_step: int = 1000 screening_tolerance: float = 1e-3 screening_step_size: float = 0.1 screening_step_drag: float = 0.5 def validate(self) -> None: if self.dt_init > self.dt_max: raise SolverOptionsError("dt_init must be less than or equal to dt_max.") if self.terminal_psi is not None and not (0 <= abs(self.terminal_psi) <= 1): raise SolverOptionsError( "terminal_psi must be None or have absolute value in [0, 1]" f" (got {self.terminal_psi})." ) if not (0 < self.adaptive_time_step_multiplier < 1): raise SolverOptionsError( "adaptive_time_step_multiplier must be in (0, 1)" f" (got {self.adaptive_time_step_multiplier})." ) if not (0 < self.screening_step_drag <= 1): raise SolverOptionsError( "screening_step_drag must be in (0, 1]" f" (got {self.screening_step_drag})." ) if self.screening_step_size <= 0: raise SolverOptionsError( "screening_step_size must be in > 0" f" (got {self.screening_step_size})." ) if self.screening_tolerance <= 0: raise SolverOptionsError( "screening_tolerance must be in > 0" f" (got {self.screening_tolerance})." ) if self.gpu: try: import cupy # type: ignore # noqa: F401 except ImportError: raise SolverOptionsError( "GPU option requires a GPU and the CuPy Python package." ) solver = self.sparse_solver if isinstance(solver, str): try: solver = SparseSolver[solver.upper()] except KeyError: valid_solvers = list(SparseSolver.__members__.keys()) if solver not in valid_solvers: raise SolverOptionsError( f"sparse solver must be one of {valid_solvers!r}, got {solver}." ) self.sparse_solver = solver if self.sparse_solver is SparseSolver.UMFPACK: try: from scikits import umfpack # type: ignore # noqa: F401 except ImportError: raise SolverOptionsError( "SparseSolver.UMFPACK requires suitesparse and the" " scikit-umfpack Python package." ) if self.sparse_solver is SparseSolver.PARDISO: try: import pypardiso # type: ignore # noqa: F401 except ImportError: raise SolverOptionsError( "SparseSolver.CUPY requires an Intel CPU" " and the pypardiso Python package." ) if self.sparse_solver is SparseSolver.CUPY: if not self.gpu: raise SolverOptionsError( "SparseSolver.CUPY requires SolverOptions.gpu = True," " and therefore requires a GPU and the CuPy Python package." )