Source code for fol.loss_functions.phase_field

"""
Authors: Yusuke Yamazaki; Reza Najian Asl (https://github.com/RezaNajian)
Date: April, 2024
License: FOL/LICENSE
"""
from  .fe_loss import FiniteElementLoss
import jax
import jax.numpy as jnp
from functools import partial
from fol.tools.fem_utilities import *
from fol.tools.decoration_functions import *
from fol.mesh_input_output.mesh import Mesh

[docs]class AllenCahnLoss(FiniteElementLoss): """ Allen–Cahn phase-field energy functional with implicit time discretization. This class defines an energy-based loss functional for Allen–Cahn type phase-field evolution problems. The total loss value represents the total discrete free energy of the system and is assembled by summing element-level energy contributions over all finite elements in the mesh. For each element, a scalar energy contribution is computed using Gaussian quadrature based on the current and previous phase-field values. The total energy of the system is obtained by accumulating these element energies across the computational domain. In addition to the scalar energy contribution, the class provides the element residual vector and Jacobian matrix corresponding to the discrete nonlinear system derived from the energy functional. These quantities are used for implicit time integration and Newton-based solution procedures. Material and time-integration parameters are provided via ``loss_settings["material_dict"]``. The interface width is controlled by ``epsilon``, and the time step size is given by ``dt``. The formulation supports both two-dimensional and three-dimensional problems. Args: name (str): Name identifier for the loss instance. loss_settings (dict): Dictionary containing problem settings. Must include ``material_dict`` with keys ``"rho"``, ``"cp"``, ``"dt"``, and ``"epsilon"``. Element discretization settings (dimension, element type, ordered DOFs) are typically provided by the specialized subclasses. fe_mesh (Mesh): Finite element mesh over which the energy functional is defined. Attributes: rho (float): Density parameter from ``material_dict``. cp (float): Heat-capacity-like parameter from ``material_dict``. dt (float): Time step size used for implicit time discretization. epsilon (float): Interface-width parameter controlling the phase-field thickness. body_force (jax.numpy.ndarray): Body force vector with shape ``(2, 1)`` in 2D or ``(3, 1)`` in 3D. """
[docs] def Initialize(self) -> None: super().Initialize() if "material_dict" not in self.loss_settings.keys(): fol_error("material_dict should provided in the loss settings !") if self.dim == 2: self.rho = self.loss_settings["material_dict"]["rho"] self.cp = self.loss_settings["material_dict"]["cp"] self.dt = self.loss_settings["material_dict"]["dt"] self.epsilon = self.loss_settings["material_dict"]["epsilon"] self.body_force = jnp.zeros((2,1)) if "body_foce" in self.loss_settings: self.body_force = jnp.array(self.loss_settings["body_foce"]) else: self.rho = self.loss_settings["material_dict"]["rho"] self.cp = self.loss_settings["material_dict"]["cp"] self.dt = self.loss_settings["material_dict"]["dt"] self.epsilon = self.loss_settings["material_dict"]["epsilon"] self.body_force = jnp.zeros((3,1)) if "body_foce" in self.loss_settings: self.body_force = jnp.array(self.loss_settings["body_foce"])
[docs] def ComputeElement(self,xyze,phi_e_c,phi_e_n,body_force=0): """ Compute element-level energy, residual, and Jacobian contributions. This method evaluates the discrete Allen–Cahn free energy contribution of a single finite element using Gaussian quadrature. The returned scalar value represents the energy contribution of this element to the total system energy. The total energy of the problem is obtained by summing these element energy contributions over all elements in the mesh. In addition to the scalar energy, the method computes the element residual vector and the element Jacobian matrix corresponding to the discrete nonlinear system derived from the energy functional. These quantities are used in implicit time integration and Newton-based solution procedures. The formulation depends on the phase-field values at the current time step and the previous time step. The time-discrete contribution enforces temporal consistency through the time step size ``dt``. Args: xyze: Element nodal coordinates. phi_e_c: Element phase-field degrees of freedom at the previous time step (committed state). Expected shape is compatible with the element node count. phi_e_n: Element phase-field degrees of freedom at the current time step (unknown state). Expected shape is compatible with the element node count. body_force: Optional element source term. This argument is included for extensibility and defaults to 0. Returns: Tuple[jax.numpy.ndarray, jax.numpy.ndarray, jax.numpy.ndarray]: - Scalar element free energy contribution. - Element residual vector for the discrete Allen–Cahn system. - Element Jacobian matrix associated with the residual. Raises: ValueError: If invalid numerical values are encountered during the evaluation of the element energy or residual. """ phi_e_c = phi_e_c.reshape(-1,1) phi_e_n = phi_e_n.reshape(-1,1) def compute_at_gauss_point(gp_point,gp_weight): N_vec = self.fe_element.ShapeFunctionsValues(gp_point) DN_DX = self.fe_element.ShapeFunctionsLocalGradients(gp_point) J = self.fe_element.Jacobian(xyze,gp_point) detJ = jnp.linalg.det(J) invJ = jnp.linalg.inv(J) B_mat = jnp.dot(invJ,DN_DX.T) phi_at_gauss_n = jnp.dot(N_vec.reshape(1,-1), phi_e_n) phi_at_gauss_c = jnp.dot(N_vec.reshape(1,-1), phi_e_c) source_term = 0.25*(phi_at_gauss_n*phi_at_gauss_n - 1)**2 Dsource_term = (phi_at_gauss_n*phi_at_gauss_n - 1)*phi_at_gauss_n gp_stiffness = B_mat.T@B_mat * detJ * gp_weight gp_mass = jnp.outer(N_vec, N_vec) * detJ * gp_weight gp_f_res = N_vec.reshape(-1,1)*Dsource_term * detJ * gp_weight gp_f = source_term * detJ * gp_weight gp_t = 0.5/(self.dt)*gp_weight * detJ *(phi_at_gauss_n-phi_at_gauss_c)**2 gp_Df = jnp.outer(N_vec, N_vec) * (3 * phi_at_gauss_n**2 - 1) * detJ * gp_weight return gp_stiffness,gp_mass, gp_f, gp_f_res, gp_t, gp_Df gp_points,gp_weights = self.fe_element.GetIntegrationData() k_gps,m_gps,f_gps,f_res_gps,t_gps, df_gps = jax.vmap(compute_at_gauss_point,in_axes=(0,0))(gp_points,gp_weights) Se = jnp.sum(k_gps, axis=0) Me = jnp.sum(m_gps, axis=0) Fe = jnp.sum(f_gps) Fe_res = jnp.sum(f_res_gps,axis=0) Te = jnp.sum(t_gps) dFe = jnp.sum(df_gps, axis=0) return 0.5*phi_e_n.T@Se@phi_e_n + 1/(self.epsilon**2)*Fe + Te, ((Me+self.dt*Se)@phi_e_n - (Me@phi_e_c- 1/(self.epsilon**2)*self.dt*Fe_res)), (Me+self.dt*Se - self.dt/(self.epsilon**2)*dFe)
[docs]class AllenCahnLoss2DQuad(AllenCahnLoss): """ Allen–Cahn loss for 2D quadrilateral finite elements. This class configures :class:`AllenCahnLoss` for two-dimensional problems discretized with quadrilateral elements. The phase-field has a single DOF per node (``Phi``), and element contributions are assembled using the base formulation. If the number of Gauss points is not specified in the loss settings, a default value of ``num_gp = 2`` is used. Args: name (str): Name identifier for the loss instance. loss_settings (dict): Dictionary containing ``material_dict`` and optional settings such as integration parameters. fe_mesh (Mesh): Finite element mesh associated with the loss. """ def __init__(self, name: str, loss_settings: dict, fe_mesh: Mesh): if not "num_gp" in loss_settings.keys(): loss_settings["num_gp"] = 2 super().__init__(name,{**loss_settings,"compute_dims":2, "ordered_dofs": ["Phi"], "element_type":"quad"},fe_mesh)
[docs]class AllenCahnLoss2DTri(AllenCahnLoss): """ Allen–Cahn loss for 2D triangular finite elements. This class configures :class:`AllenCahnLoss` for two-dimensional problems discretized with triangular elements. The phase-field has a single DOF per node (``Phi``), and element contributions are assembled using the base formulation. Args: name (str): Name identifier for the loss instance. loss_settings (dict): Dictionary containing ``material_dict`` and optional settings. fe_mesh (Mesh): Finite element mesh associated with the loss. """ def __init__(self, name: str, loss_settings: dict, fe_mesh: Mesh): super().__init__(name,{**loss_settings,"compute_dims":2, "ordered_dofs": ["Phi"], "element_type":"triangle"},fe_mesh)
[docs]class AllenCahnLoss3DHexa(AllenCahnLoss): """ Allen–Cahn loss for 3D hexahedral finite elements. This class configures :class:`AllenCahnLoss` for three-dimensional problems discretized with hexahedral elements. The phase-field has a single DOF per node (``Phi``), and element contributions are assembled using the base formulation. If the number of Gauss points is not specified in the loss settings, a default value of ``num_gp = 2`` is used. Args: name (str): Name identifier for the loss instance. loss_settings (dict): Dictionary containing ``material_dict`` and optional settings such as integration parameters. fe_mesh (Mesh): Finite element mesh associated with the loss. """ def __init__(self, name: str, loss_settings: dict, fe_mesh: Mesh): if not "num_gp" in loss_settings.keys(): loss_settings["num_gp"] = 2 super().__init__(name,{**loss_settings,"compute_dims":3, "ordered_dofs": ["Phi"], "element_type":"hexahedron"},fe_mesh)