Source code for fol.solvers.adjoint_fe_solver
"""
Authors: Reza Najian Asl, https://github.com/RezaNajian
Date: January, 2025
License: FOL/LICENSE
"""
import jax.numpy as jnp
from .fe_solver import FiniteElementSolver
from fol.tools.decoration_functions import *
from fol.responses.fe_response import FiniteElementResponse
[docs]class AdjointFiniteElementSolver(FiniteElementSolver):
"""
Adjoint finite element solver for gradient and sensitivity analysis.
This class solves the adjoint system associated with a forward finite
element problem. The adjoint formulation is typically used in
PDE-constrained optimization, inverse problems, and sensitivity analysis
to efficiently compute gradients of a scalar objective (loss) function
with respect to control variables.
The adjoint system is constructed using a
:class:`FiniteElementResponse` object, which provides the loss functional,
the adjoint Jacobian matrix, and the adjoint right-hand side vector.
The class inherits from :class:`FiniteElementSolver` and reuses its
linear system solution infrastructure.
Mathematical Formulation
------------------------
Let the forward problem be defined as:
R(u, m) = 0
where:
u : state (degrees of freedom),
m : control variables.
Let the scalar objective (loss) be:
J(u, m)
The adjoint variable λ satisfies the adjoint equation:
(∂R/∂u)ᵀ λ = - ∂J/∂u
where:
∂R/∂u : Jacobian of the residual with respect to the state,
∂J/∂u : gradient of the loss with respect to the state.
Boundary conditions are assumed to be applied directly to the adjoint
Jacobian and right-hand side prior to solving the system.
Parameters
----------
adj_fe_solver_name : str
Name of the adjoint finite element solver instance.
fe_response : FiniteElementResponse
Object responsible for defining the loss functional and assembling
the adjoint Jacobian matrix and right-hand side vector.
adj_fe_solver_settings : dict, optional
Dictionary containing solver-specific settings such as linear solver
type, tolerances, or preconditioning options. Default is an empty
dictionary.
Attributes
----------
fe_response : FiniteElementResponse
Reference to the finite element response object used to assemble
adjoint quantities.
"""
@print_with_timestamp_and_execution_time
def __init__(self, adj_fe_solver_name: str, fe_response: FiniteElementResponse, adj_fe_solver_settings:dict={}) -> None:
"""
Initialize the adjoint finite element solver.
This constructor initializes the base finite element solver using
the loss functional provided by the finite element response object
and stores a reference to the response for adjoint assembly.
Parameters
----------
adj_fe_solver_name : str
Name identifier for the adjoint solver.
fe_response : FiniteElementResponse
Finite element response object containing the loss definition
and adjoint assembly routines.
adj_fe_solver_settings : dict, optional
Solver configuration dictionary. Default is an empty dictionary.
"""
super().__init__(adj_fe_solver_name,fe_response.fe_loss,adj_fe_solver_settings)
self.fe_response = fe_response
[docs] @print_with_timestamp_and_execution_time
def Solve(self,current_control_vars:jnp.array,current_dofs:jnp.array,current_adjoint_dofs:jnp.array):
"""
Assemble and solve the adjoint linear system.
This method constructs the adjoint Jacobian matrix and right-hand
side vector based on the current control variables and forward
solution. The resulting linear system is then solved for the
adjoint degrees of freedom.
The right-hand side vector is multiplied by ``-1`` to ensure
consistency with the solver sign convention used in
:meth:`LinearSolve`.
Parameters
----------
current_control_vars : jnp.array
Current values of the control variables.
current_dofs : jnp.array
Current forward solution degrees of freedom.
current_adjoint_dofs : jnp.array
Initial guess or previous values of the adjoint degrees of
freedom.
Returns
-------
jnp.array
The updated adjoint degrees of freedom obtained by solving
the adjoint linear system.
"""
BC_applied_jac,BC_applied_rhs = self.fe_response.ComputeAdjointJacobianMatrixAndRHSVector(current_control_vars,current_dofs)
# here we need to multiply by -1 since the solver later mutiplies by -1
BC_applied_rhs *= -1
return self.LinearSolve(BC_applied_jac,BC_applied_rhs,current_adjoint_dofs)