Source code for struphy.io.options

import logging
import os
from dataclasses import dataclass, fields
from typing import Any, Callable, Literal

from struphy.utils.utils import (
    __class_with_params_repr_no_defaults__,
    __dataclass_repr_all_stacked__,
    __dataclass_repr_no_defaults__,
    all_class_params_are_default,
    check_option,
)

logger = logging.getLogger("struphy")


@dataclass
class OptionsBase:
    def to_dict(self) -> dict:
        """Convert dataclass instance to dictionary."""
        return {field.name: getattr(self, field.name) for field in fields(type(self)) if field.init}

    @classmethod
    def from_dict(cls, dct) -> "Any":
        """Create dataclass instance from dictionary."""
        valid_fields = {field.name for field in fields(cls) if field.init}
        return cls(**{key: value for key, value in dct.items() if key in valid_fields})

    def __repr__(self):
        return __dataclass_repr_all_stacked__(self)


@dataclass
class LiteralOptions:
    """
    (String) options for parameters in launch files, including:

    * Time stepping
    * Derham finite element spaces
    * Background/equilibrium types
    * Model types
    * Initial conditions / perturbations
    * Linear, nonlinear, explicit (Butcher) and saddle-point solvers
    * Drawing of markers, type of PIC methods
    * Noise filtering in PIC
    * Smoothing kernels for SPH methods
    * Particle binning in phase space
    """

    # time
    SplitAlgos = Literal["LieTrotter", "Strang"]

    # derham
    OptsFEECSpace = Literal["H1", "Hcurl", "Hdiv", "L2", "H1vec"]
    OptsVecSpace = Literal["Hcurl", "Hdiv", "H1vec"]
    OptsNonTrivialBoundaryCondition = Literal["free", "dirichlet"]

    # fields background
    BackgroundTypes = Literal["LogicalConst", "FluidEquilibrium"]
    KineticDimensionsToPlot = Literal["e1", "e2", "e3", "v1", "v2", "v3"]

    # models
    ModelTypes = Literal["Toy", "Kinetic", "Fluid", "Hybrid"]

    # perturbations
    NoiseDirections = Literal["e1", "e2", "e3", "e1e2", "e1e3", "e2e3", "e1e2e3"]
    GivenInBasis = Literal["0", "1", "2", "3", "v", "physical", "physical_at_eta", "norm", None]

    # solvers
    OptsSymmSolver = Literal["pcg", "cg"]
    OptsGenSolver = Literal["pbicgstab", "bicgstab", "gmres"]
    OptsMassPrecond = Literal["MassMatrixPreconditioner", "MassMatrixDiagonalPreconditioner", None]
    OptsSaddlePointSolver = Literal["uzawa"]
    OptsDirectSolver = Literal["SparseSolver", "ScipySparse", "InexactNPInverse", "DirectNPInverse"]
    OptsNonlinearSolver = Literal["Picard", "Newton"]
    OptsButcher = Literal["rk4", "forward_euler", "heun2", "rk2", "heun3", "3/8 rule"]

    # markers
    OptsPICSpace = Literal["Particles6D", "DeltaFParticles6D", "Particles5D", "Particles3D"]
    OptsMarkerBC = Literal["periodic", "reflect", "remove", "refill"]
    OptsRecontructBC = Literal["periodic", "mirror", "fixed", "noslip"]
    OptsLoading = Literal[
        "pseudo_random",
        "sobol_standard",
        "sobol_antithetic",
        "external",
        "restart",
        "tesselation",
    ]
    OptsSpatialLoading = Literal["uniform", "disc"]
    OptsMPIsort = Literal["each", "last", None]

    # filters
    OptsFilter = Literal["fourier_in_tor", "hybrid", "three_point", None]

    # sph
    OptsKernel = Literal[
        "trigonometric_1d",
        "gaussian_1d",
        "linear_1d",
        "trigonometric_2d",
        "gaussian_2d",
        "linear_2d",
        "trigonometric_3d",
        "gaussian_3d",
        "linear_isotropic_3d",
        "linear_3d",
    ]

    # Create new Literal to determine type of binning output
    BinningQuantity = Literal[
        "density",
        "current_1",
        "current_2",
        "current_3",
        "energy_tensor_11",
        "energy_tensor_22",
        "energy_tensor_33",
        "energy_tensor_12",
        "energy_tensor_13",
        "energy_tensor_23",
        "heat_flux_1",
        "heat_flux_2",
        "heat_flux_3",
    ]


[docs] @dataclass(repr=False) class Time(OptionsBase): """Set options for time stepping in parameter/launch files. Parameters ---------- dt : float Time step. Tend : float End time. split_algo : LiteralOptions.SplitAlgos Splitting algorithm (the order of the propagators is defined in the model). """ dt: float = 0.01 Tend: float = 0.03 split_algo: LiteralOptions.SplitAlgos = "LieTrotter" def __post_init__(self): check_option(self.split_algo, LiteralOptions.SplitAlgos) def __repr_no_defaults__(self): return __dataclass_repr_no_defaults__(self) @property def is_default(self): return all_class_params_are_default(self)
[docs] @dataclass class BaseUnits(OptionsBase): """Set base units in parameter/launch files from which other units are derived. See :ref:`normalization`. Parameters ---------- x : float Unit of length in meters. B : float Unit of magnetic field in Tesla. n : float Unit of particle number density in 1e20/m^3. kBT : float, optional Unit of internal energy in keV. Only in effect if the velocity scale is set to 'thermal'. """ x: float = 1.0 B: float = 1.0 n: float = 1.0 kBT: float = None def __repr_no_defaults__(self): return __dataclass_repr_no_defaults__(self) @property def is_default(self): return all_class_params_are_default(self)
NonTrivialBC = LiteralOptions.OptsNonTrivialBoundaryCondition
[docs] @dataclass(repr=False) class DerhamOptions(OptionsBase): """Set options for the 3D discrete de Rham spaces in parameter/launch files. This dataclass controls spline degrees, boundary conditions and quadrature settings used to build the FEEC de Rham sequence. See :ref:`geomFE`. Parameters ---------- degree : tuple[int, int, int] Spline degree in each logical direction ``(eta_1, eta_2, eta_3)``. bcs : tuple[None | tuple[NonTrivialBC, NonTrivialBC], None | tuple[NonTrivialBC, NonTrivialBC], None | tuple[NonTrivialBC, NonTrivialBC]] Boundary condition selector for each direction. Use ``None`` in a direction for periodic boundaries, or a tuple ``(left, right)`` with entries in ``{"free", "dirichlet"}`` for non-periodic boundaries. nquads : tuple[int, int, int] | None Number of Gauss-Legendre quadrature points per direction for cell integrals. If ``None``, backend defaults are used. nquads_proj : tuple[int, int, int] | None Number of Gauss-Legendre quadrature points per direction for geometric projector/histopolation integrals. If ``None``, backend defaults are used. polar_splines : bool Smoothness at a possible polar singularity at ``eta_1 = 0``. ``False`` gives standard tensor-product splines, ``True`` gives C1 polar splines. local_projectors : bool Whether to build local commuting projectors based on quasi-inter-/histopolation. """ degree: tuple[int, int, int] = (1, 1, 1) bcs: tuple[ None | tuple[NonTrivialBC, NonTrivialBC], None | tuple[NonTrivialBC, NonTrivialBC], None | tuple[NonTrivialBC, NonTrivialBC], ] = (None, None, None) nquads: tuple[int, int, int] | None = None nquads_proj: tuple[int, int, int] | None = None polar_splines: bool = False local_projectors: bool = False def __post_init__(self): for bc in self.bcs: if bc is not None: assert isinstance(bc, tuple) and len(bc) == 2, ( "Boundary conditions must be given as a tuple (left, right) for each direction." ) check_option(bc[0], LiteralOptions.OptsNonTrivialBoundaryCondition) check_option(bc[1], LiteralOptions.OptsNonTrivialBoundaryCondition) # def __str__(self): # for k, v in self.__dict__.items(): # logger.info(f"{k + ':':<20}{v}") # return "" def __repr_no_defaults__(self): return __dataclass_repr_no_defaults__(self) def __repr_all_stacked__(self): return __dataclass_repr_all_stacked__(self) @property def is_default(self): return all_class_params_are_default(self)
[docs] @dataclass(repr=False) class FieldsBackground(OptionsBase): """Set options for static fluid backgrounds/equilibria in parameter/launch files. Parameters ---------- type : BackgroundTypes Type of background. values : tuple[float] Values for LogicalConst on the unit cube. Can be length 1 for scalar functions; must be length 3 for vector-valued functions. variable : str Name of the method in :class:`~struphy.fields_background.base.FluidEquilibrium` that should be the background. """ type: LiteralOptions.BackgroundTypes = "LogicalConst" values: tuple = (1.5, 0.7, 2.4) variable: str = None def __post_init__(self): check_option(self.type, LiteralOptions.BackgroundTypes) def __repr_no_defaults__(self): return __dataclass_repr_no_defaults__(self) @property def is_default(self): return all_class_params_are_default(self)
[docs] @dataclass(repr=False) class EnvironmentOptions(OptionsBase): """Set environment options for launching run on current architecture (these options do not influence the simulation result). Parameters ---------- out_folders : str Absolute path to directory for ``sim_folder``. sim_folder : str Folder in ``out_folders/`` for the current simulation (default= ``sim_1/`` ). Will create the folder if it does not exist OR cleans the folder for new runs. restart : bool Whether to restart a run (default=False). max_runtime : int Maximum run time of simulation in minutes. Will finish the time integration once this limit is reached (default=300). save_step : int When to save data output: every time step (save_step=1), every second time step (save_step=2), etc (default=1). sort_step: int, optional Sort markers in memory every N time steps (default=0, which means markers are sorted only at the start of simulation) num_clones: int, optional Number of domain clones (default=1) profiling_activated: bool, optional Activate profiling with scope-profiler (default=False) profiling_trace: bool, optional Save time-trace of each profiling region (default=False) """ out_folders: str = os.getcwd() sim_folder: str = "sim_1" restart: bool = False max_runtime: int = 300 save_step: int = 1 sort_step: int = 0 num_clones: int = 1 profiling_activated: bool = False profiling_trace: bool = False def __post_init__(self): self.path_out: str = os.path.join(self.out_folders, self.sim_folder) # def __str__(self): # for k, v in self.__dict__.items(): # logger.info(f"{k + ':':<20}{v}") # return "" def __repr_no_defaults__(self): return __dataclass_repr_no_defaults__(self) def __repr_all_stacked__(self): return __dataclass_repr_all_stacked__(self) @property def is_default(self): return all_class_params_are_default(self)