Source code for pygeon.discretizations.discretization

"""Module for the discretization class."""

from __future__ import annotations

import abc
from typing import Callable, Type

import numpy as np
import scipy.sparse as sps

import pygeon as pg


[docs] class Discretization(abc.ABC): """ Abstract class for PyGeoN discretization methods. """ poly_order: int """Polynomial degree of the basis functions""" tensor_order: int """Tensor order of the basis functions"""
[docs] def __init__(self, keyword: str = pg.UNITARY_DATA) -> None: """ Initialize the Discretization object. Args: keyword (str): The keyword used to identify the discretization method. Default is pg.UNITARY_DATA. Returns: None """ self.keyword = keyword
[docs] def __repr__(self) -> str: """ Return a string representation of the Discretization object. The string includes the type of the discretization and, if available, the keyword associated with it. Returns: str: A string representation of the Discretization object. """ s = f"Discretization of type {self.__class__.__name__}" if hasattr(self, "keyword"): s += f" with keyword {self.keyword}" return s
[docs] @abc.abstractmethod def ndof(self, sd: pg.Grid) -> int: """ Returns the number of degrees of freedom associated to the method. Args: sd: Grid, or a subclass. Returns: ndof: the number of degrees of freedom. """
[docs] @abc.abstractmethod def assemble_mass_matrix( self, sd: pg.Grid, data: dict | None = None ) -> sps.csc_array: """ Assembles the mass matrix Args: sd (pg.Grid): Grid object or a subclass. data (dict | None): Dictionary with physical parameters for scaling. Returns: sps.csc_array: The mass matrix. """
[docs] def assemble_lumped_matrix( self, sd: pg.Grid, data: dict | None = None ) -> sps.csc_array: """ Assembles the lumped mass matrix given by the row sums on the diagonal. Args: sd (pg.Grid): Grid object or a subclass. data (dict | None): Dictionary with physical parameters for scaling. Returns: sps.csc_array: The lumped mass matrix. """ diag_mass = self.assemble_mass_matrix(sd, data).sum(axis=0) return sps.diags_array(np.asarray(diag_mass).flatten()).tocsc()
[docs] @abc.abstractmethod def assemble_diff_matrix(self, sd: pg.Grid) -> sps.csc_array: """ Assembles the matrix corresponding to the differential operator. Args: sd (pg.Grid): Grid object or a subclass. Returns: sps.csc_array: The differential matrix. """
[docs] def assemble_stiff_matrix( self, sd: pg.Grid, data: dict | None = None ) -> sps.csc_array: """ Assembles the stiffness matrix. This method takes a grid object `sd` and an optional data dictionary `data` as input. It first calls the `assemble_diff_matrix` method to obtain the differential matrix `B`. Then, it creates an instance of the range discretization class using the `dim` attribute of `sd`. Next, it calls the `assemble_mass_matrix` method of the range discretization class to obtain the mass matrix `A`. Finally, it returns the product of the transpose of `B`, `A`, and `B`. Args: sd (pg.Grid): Grid object or a subclass. data (dict | None): Optional data dictionary. Defaults to None. Returns: sps.csc_array: The stiffness matrix. """ B = self.assemble_diff_matrix(sd) discr = self.get_range_discr_class(sd.dim)(self.keyword) A = discr.assemble_mass_matrix(sd, data) return (B.T @ A @ B).tocsc()
[docs] @abc.abstractmethod def interpolate( self, sd: pg.Grid, func: Callable[[np.ndarray], np.ndarray] ) -> np.ndarray: """ Interpolates a function onto the finite element space Args: sd (pg.Grid): Grid, or a subclass. func (Callable): A function that returns the function values at coordinates. Returns: np.ndarray: The values of the degrees of freedom """
[docs] def eval_at_cell_centers(self, sd: pg.Grid) -> sps.csc_array: """ Assembles the matrix for evaluating the discretization at the cell centers. Args: sd (pg.Grid): Grid object or a subclass. Returns: sps.csc_array: The evaluation matrix. """ Pi = self.proj_to_PwPolynomials(sd) Poly_space = pg.get_PwPolynomials(self.poly_order, self.tensor_order)( self.keyword ) return Poly_space.eval_at_cell_centers(sd) @ Pi
[docs] def source_term( self, sd: pg.Grid, func: Callable[[np.ndarray], np.ndarray] ) -> np.ndarray: """ Assembles the source term by interpolating the given function and multiplying by the mass matrix Args: sd (pg.Grid): Grid object or a subclass. func (Callable): A function that returns the function values at coordinates. Returns: np.ndarray: The evaluation matrix. """ return self.assemble_mass_matrix(sd) @ self.interpolate(sd, func)
[docs] @abc.abstractmethod def assemble_nat_bc( self, sd: pg.Grid, func: Callable[[np.ndarray], np.ndarray], b_faces: np.ndarray ) -> np.ndarray: """ Assembles the natural boundary condition term (Tr q, p)_Gamma Args: sd (pg.Grid): The grid object. func (Callable): The function representing the natural boundary condition. b_faces (np.ndarray): The array of boundary faces. Returns: np.ndarray: The assembled natural boundary condition term. """
[docs] @abc.abstractmethod def get_range_discr_class(self, dim: int) -> Type[pg.Discretization]: """ Returns the discretization class that contains the range of the differential Args: dim (int): The dimension of the range. Returns: pg.Discretization: The discretization class containing the range of the differential """
[docs] @abc.abstractmethod def proj_to_PwPolynomials(self, sd: pg.Grid) -> sps.csc_array: """ Returns the inclusion matrix that projects the finite element space onto the lowest order piecewise polynomial space without loss of information. Args: sd (pg.Grid): The grid. Returns: sps.csc_array: The inclusion matrix. """
[docs] def error_l2( self, sd: pg.Grid, num_sol: np.ndarray, ana_sol: Callable[[np.ndarray], np.ndarray], relative: bool = True, poly_order: int | None = None, data: dict | None = None, ) -> float: """ Returns the l2 error computed against an analytical solution given as a function. NOTE: The default implementation uses interpolation which may result in unexpected superconvergence. In that case, we advise specifying poly_order. Args: sd (pg.Grid): Grid, or a subclass. num_sol (np.ndarray): Vector of the numerical solution. ana_sol (Callable): Function that represents the analytical solution. relative (bool, optional): Compute the relative error or not. Defaults to True. poly_order (int, optional): The default is to compare the numerical solution to the interpolation of the given solution. If poly_order is specified, the error is evaluated in a piecewise polynomial space of that order. Returns: float: The computed error. """ # Default case in which we interpolate the solution and compare if poly_order is None: int_sol = self.interpolate(sd, ana_sol) mass = self.assemble_mass_matrix(sd, data) norm = (int_sol @ mass @ int_sol.T) if relative else 1 diff = num_sol - int_sol return np.sqrt(diff @ mass @ diff.T / norm) # If poly_order is specified, we compare the solutions in a piecewise polynomial # space else: poly_space = pg.get_PwPolynomials(poly_order, self.tensor_order)( self.keyword ) proj_sol = pg.proj_to_PwPolynomials(self, sd, poly_order) @ num_sol return poly_space.error_l2(sd, proj_sol, ana_sol, relative, data=data)