Source code for matrix_toolkit.pde.config
"""
Configuration classes for PDE matrix generation
"""
from dataclasses import dataclass, field
from typing import Optional, Tuple, Union, List, Dict, Any
from enum import Enum
import numpy as np
[docs]
class BoundaryCondition(Enum):
"""Boundary condition types"""
DIRICHLET = "dirichlet" # u = 0 on boundary
NEUMANN = "neumann" # du/dn = 0 on boundary
PERIODIC = "periodic" # periodic BC
ROBIN = "robin" # alpha*u + beta*du/dn = 0
MIXED = "mixed" # different BC on different sides
[docs]
class DiscretizationType(Enum):
"""Discretization method types"""
FINITE_DIFFERENCE = "fd" # Finite differences
FINITE_ELEMENT = "fe" # Finite elements (basic)
FINITE_VOLUME = "fv" # Finite volumes
[docs]
class StencilType(Enum):
"""Finite difference stencil types"""
SECOND_ORDER = 2 # Second order accurate
FOURTH_ORDER = 4 # Fourth order accurate
SIXTH_ORDER = 6 # Sixth order accurate
[docs]
@dataclass
class PDEConfig:
"""Configuration for PDE matrix generation"""
# Domain configuration
dimension: int = 2 # 1D, 2D, or 3D
domain: Tuple[float, ...] = (0.0, 1.0) # Domain bounds per dimension
# Mesh configuration
mesh_size: Union[int, Tuple[int, ...]] = 32 # Grid points per dimension
mesh_type: str = "uniform" # uniform, stretched, random
# Discretization
discretization: DiscretizationType = DiscretizationType.FINITE_DIFFERENCE
stencil_order: StencilType = StencilType.SECOND_ORDER
# Boundary conditions
boundary_condition: Union[BoundaryCondition, List[BoundaryCondition]] = BoundaryCondition.DIRICHLET
# PDE parameters
coefficients: Dict[str, Any] = field(default_factory=dict)
# Random parameter generation
random_params: bool = False
random_seed: Optional[int] = None
param_ranges: Dict[str, Tuple[float, float]] = field(default_factory=dict)
# Output format
backend: str = "scipy" # scipy, cupy, jax, torch, numpy
format: str = "csr" # csr, csc, coo, dense, etc.
dtype: str = "float64" # float32, float64, complex64, complex128
# Additional options
include_rhs: bool = False # Include right-hand side vector
include_exact_solution: bool = False # Include exact solution if known
normalize: bool = False # Normalize matrix
[docs]
def __post_init__(self):
"""Validate and process configuration"""
# Ensure domain is tuple
if isinstance(self.domain, (int, float)):
self.domain = (0.0, float(self.domain))
# Ensure mesh_size matches dimension
if isinstance(self.mesh_size, int):
self.mesh_size = tuple([self.mesh_size] * self.dimension)
elif len(self.mesh_size) != self.dimension:
raise ValueError(
f"mesh_size length ({len(self.mesh_size)}) "
f"must match dimension ({self.dimension})"
)
# Reshape domain to match dimension
if len(self.domain) == 2 and self.dimension > 1:
# Assume same bounds for all dimensions
self.domain = tuple(self.domain * self.dimension)
elif len(self.domain) != 2 * self.dimension:
raise ValueError(
f"domain must have {2 * self.dimension} values "
f"for {self.dimension}D problem"
)
[docs]
def get_mesh_spacing(self) -> Tuple[float, ...]:
"""Calculate mesh spacing for each dimension"""
spacings = []
for i in range(self.dimension):
x_min = self.domain[2*i]
x_max = self.domain[2*i + 1]
n = self.mesh_size[i]
h = (x_max - x_min) / (n - 1)
spacings.append(h)
return tuple(spacings)
[docs]
def total_dofs(self) -> int:
"""Calculate total degrees of freedom"""
return int(np.prod(self.mesh_size))