Source code for EasyFEA.fem._forms
# Copyright (C) 2021-2025 Université Gustave Eiffel.
# This file is part of the EasyFEA project.
# EasyFEA is distributed under the terms of the GNU General Public License v3, see LICENSE.txt and CREDITS.md for more information.
"""Module containing the BiLinearForm and LinearForm classes used to construct arbitrary fem matrices."""
from abc import ABC, abstractmethod
from typing import Callable, TYPE_CHECKING
import numpy as np
from scipy.sparse import csr_matrix
# from fem
from ._linalg import FeArray
if TYPE_CHECKING:
from ._field import Field
class _Form(ABC):
"""Form class from which BilinearForm and LinearForm are derived."""
def __init__(self, form: Callable[..., FeArray.FeArrayALike]):
self._form = form
def __call__(self, *args, **kwds):
return self._form(*args, **kwds)
@abstractmethod
def Integrate_e(self, field: "Field") -> np.ndarray:
"""Integrates de form with the field on elements.
Parameters
----------
field : Field
field
Returns
-------
np.ndarray
the integrated (Ne, ...) numpy array
"""
pass
@abstractmethod
def Assemble(self, field: "Field") -> csr_matrix:
pass
[docs]
class BiLinearForm(_Form):
[docs]
def Integrate_e(self, field):
# get field data
dof_n = field.dof_n
groupElem = field.groupElem
nPe = groupElem.nPe
# init data array
data = np.zeros((groupElem.Ne, nPe * dof_n, nPe * dof_n), dtype=float)
# get form function
form = self._form
# set u and v field
u = field
v = field.copy()
# get dofs and nodes
dofs = np.arange(nPe * dof_n)
nodes = np.arange(nPe).reshape(nPe, 1).repeat(dof_n, axis=1).ravel()
# get dX to integrate
dX_e_pg = groupElem.Get_weightedJacobian_e_pg(field.matrixType)
# loop over u dofs
for i in dofs:
# activate node and dof for u
u._Set_current_active_node(nodes[i])
u._Set_current_active_dof(i % dof_n)
# loop over v dofs
for j in dofs:
# activate node and dof for v
v._Set_current_active_node(nodes[j])
v._Set_current_active_dof(j % dof_n)
# get (Ne, nPg) array
values_e_pg = form(u, v)
# sum on gauss points
values_e = (values_e_pg * dX_e_pg).sum(axis=1)
# add data
data[:, i, j] = values_e
return data
[docs]
def Assemble(self, field) -> csr_matrix:
"""Assemble de form with the field.
Parameters
----------
field : Field
field
Returns
-------
csr_matrix
the assembled (Nn * dof_n, Nn * dof_n) sparse matrix.
"""
# get field data
dof_n = field.dof_n
groupElem = field.groupElem
# get values
values = self.Integrate_e(field=field).ravel()
rows = groupElem.Get_rowsVector_e(dof_n).ravel()
columns = groupElem.Get_columnsVector_e(dof_n).ravel()
# get shape
Ndof = groupElem.Nn * dof_n
shape = (Ndof, Ndof)
assert values.size == rows.size, f"Not enough data to fill a {shape} matrix."
matrix = csr_matrix((values.ravel(), (rows, columns)), shape=shape)
return matrix
[docs]
class LinearForm(_Form):
[docs]
def Integrate_e(self, field):
# get field data
dof_n = field.dof_n
groupElem = field.groupElem
nPe = groupElem.nPe
# init data array
data = np.zeros((groupElem.Ne, nPe * dof_n, 1), dtype=float)
# get form function
form = self._form
# get v field
v = field
# get dofs and nodes
dofs = np.arange(nPe * dof_n)
nodes = np.arange(nPe).reshape(nPe, 1).repeat(dof_n, axis=1).ravel()
# get dX to integrate
dX_e_pg = groupElem.Get_weightedJacobian_e_pg(field.matrixType)
# loop over u dofs
for i in dofs:
# activate node and dof for v
v._Set_current_active_node(nodes[i])
v._Set_current_active_dof(i % dof_n)
# get (Ne, nPg) array
values_e_pg = form(v)
# sum on gauss points
values_e = (values_e_pg * dX_e_pg).sum(axis=1)
# add data
data[:, i] = values_e
return data
[docs]
def Assemble(self, field) -> csr_matrix:
"""Assemble de form with the field.
Parameters
----------
field : Field
field
Returns
-------
csr_matrix
the assembled (Nn * dof_n, 1) sparse vector.
"""
# get field data
dof_n = field.dof_n
groupElem = field.groupElem
# get values
values = self.Integrate_e(field=field).ravel()
rows = groupElem.Get_rowsVector_e(dof_n).ravel()
columns = np.ones_like(rows)
# get shape
Ndof = groupElem.Nn * dof_n
shape = (Ndof, 1)
assert values.size == rows.size, f"Not enough data to fill a {shape} vector."
matrix = csr_matrix((values.ravel(), (rows, columns)), shape=shape)
return matrix