Source code for felupe.assembly._cartesian
# -*- coding: utf-8 -*-
"""
This file is part of FElupe.
FElupe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FElupe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FElupe. If not, see <http://www.gnu.org/licenses/>.
"""
import numpy as np
try:
from einsumt import einsumt
except ModuleNotFoundError:
from numpy import einsum as einsumt
from scipy.sparse import csr_matrix as sparsematrix
[docs]
class IntegralFormCartesian:
r"""Single-field integral form constructed by a function result ``fun``, a test
field ``v``, differential volumes ``dV`` and optionally a trial field ``u``. For
both fields ``v`` and ``u`` gradients may be passed by setting ``grad_v`` and
``grad_u`` to True (default is False for both fields).
Parameters
----------
fun : array
The pre-evaluated function array. If the array is not contiguous, a C-contiguous
copy is made.
v : Field
The test field.
dV : array
The differential volumes.
u : Field, optional
If a field is passed, a bilinear form is created (default is None).
grad_v : bool, optional
Flag to activate the gradient on the test field ``v`` (default is False).
grad_u : bool, optional
Flag to activate the gradient on the trial field ``u`` (default is False).
Notes
-----
Linearform
~~~~~~~~~~
A linear form is either defined by the dot product of a given (vector-valued)
function :math:`\boldsymbol{f}` and the (vector) field :math:`\boldsymbol{v}`
.. math::
L(\boldsymbol{v}) = \int_\Omega \boldsymbol{f} \cdot \boldsymbol{v} ~ dV
or by the double-dot product of a (matrix-valued) function :math:`\boldsymbol{f}`
and the gradient of the field values :math:`\boldsymbol{\nabla v}`.
.. math::
L(\boldsymbol{\nabla v}) =
\int_\Omega \boldsymbol{f} : \boldsymbol{\nabla v} ~ dV
Bilinearform
~~~~~~~~~~~~
A bilinear form is either defined by the dot products of a given (matrix-valued)
function :math:`\boldsymbol{f}` and the (vector) fields :math:`\boldsymbol{v}` and
:math:`\boldsymbol{u}`
.. math::
a(\boldsymbol{v}, \boldsymbol{u}) =
\int_\Omega \boldsymbol{v} \cdot \boldsymbol{f} \cdot \boldsymbol{u} ~ dV
or by the double-dot products of a (tensor-valued) function :math:`\boldsymbol{f}`
and the field values :math:`\boldsymbol{v}` and :math:`\boldsymbol{u}` or their
gradients :math:`\boldsymbol{\nabla v}` and :math:`\boldsymbol{\nabla u}`.
.. math::
a(\boldsymbol{\nabla v}, \boldsymbol{u}) &=
\int_\Omega \boldsymbol{\nabla v} : \boldsymbol{f} \cdot \boldsymbol{u} ~ dV
a(\boldsymbol{v}, \boldsymbol{\nabla u}) &=
\int_\Omega \boldsymbol{v} \cdot \boldsymbol{f} : \boldsymbol{\nabla u} ~ dV
a(\boldsymbol{\nabla v}, \boldsymbol{\nabla u}) &= \int_\Omega
\boldsymbol{\nabla v} : \boldsymbol{f} : \boldsymbol{\nabla u} ~ dV
See Also
--------
felupe.IntegralForm : Mixed-field integral form container with methods for integration and assembly.
felupe.assembly.IntegralFormAxisymmetric : An Integral Form for axisymmetric fields.
"""
def __init__(self, fun, v, dV, u=None, grad_v=False, grad_u=False):
self.fun = np.ascontiguousarray(fun)
self.dV = dV
self.v = v
self.grad_v = grad_v
self.u = u
self.grad_u = grad_u
# init indices
# # linear form
if not self.u:
self.indices = self.v.indices.ai
self.shape = self.v.indices.shape
# # bilinear form
else:
cai = self.v.indices.cai
cbk = self.u.indices.cai
caibk0 = np.repeat(cai, cbk.shape[1] * self.u.dim)
caibk1 = np.tile(cbk, (1, cai.shape[1] * self.v.dim, 1)).ravel()
self.indices = (caibk0, caibk1)
self.shape = (self.v.indices.shape[0], self.u.indices.shape[0])
[docs]
def assemble(self, values=None, parallel=False, out=None):
"Assembly of sparse region vectors or matrices."
if values is None:
values = self.integrate(parallel=parallel, out=out)
permute = np.append(len(values.shape) - 1, range(len(values.shape) - 1)).astype(
int
)
# broadcast values of a uniform grid mesh
if values.size < self.indices[0].size:
new_shape = (*values.shape[:-1], self.v.region.mesh.ncells)
values = np.broadcast_to(values, new_shape)
res = sparsematrix(
(values.transpose(permute).ravel(), self.indices), shape=self.shape
)
return res
[docs]
def integrate(self, parallel=False, out=None):
"Return evaluated (but not assembled) integrals."
grad_v, grad_u = self.grad_v, self.grad_u
v, u = self.v, self.u
dV = self.dV
fun = self.fun
# plane strain
# trim 3d vector-valued functions to the dimension of the field
function_dimension = len(fun.shape) - 2
function_is_vector = function_dimension >= 1
function_is_3d = len(fun) == 3
field_is_2d = v.dim == 2
if function_is_vector and function_is_3d and field_is_2d:
fun = fun[tuple([slice(2)] * function_dimension)]
if parallel:
einsum = einsumt
else:
einsum = np.einsum
if not grad_v:
vb = v.region.h
else:
vb = v.region.dhdX
if u is not None:
if not grad_u:
ub = u.region.h
else:
ub = u.region.dhdX
if u is None:
if not grad_v:
return einsum(
"aqc,...qc,qc->a...c", vb, fun, dV, optimize=True, out=out
)
else:
return einsum(
"aJqc,...Jqc,qc->a...c", vb, fun, dV, optimize=True, out=out
)
else:
if not grad_v and not grad_u:
res = einsum(
"aqc,...qc,bqc,qc->a...bc", vb, fun, ub, dV, optimize=True, out=out
)
if len(res.shape) == 5:
return einsum("aijbc->aibjc", res, out=out)
else:
return res
elif grad_v and not grad_u:
return einsum(
"aJqc,iJ...qc,bqc,qc->aib...c",
vb,
fun,
ub,
dV,
optimize=True,
out=out,
)
elif not grad_v and grad_u:
return einsum(
"a...qc,...kLqc,bLqc,qc->a...bkc",
vb,
fun,
ub,
dV,
optimize=True,
out=out,
)
else: # grad_v and grad_u
return einsum(
"aJqc,iJkLqc,bLqc,qc->aibkc",
vb,
fun,
ub,
dV,
optimize=True,
out=out,
)