Integrals
A central operation in any Finite Element application is to integrate a
function over a physical domain. In Nutils, integration starts with the
topology, in particular the integral()
method.
The integral method takes a nutils.function.Array
function as first argument
and the degree as keyword argument. The function should contain the Jacobian of
the geometry against which the function should be integrated, using either
nutils.function.J
or dV
in a namespace expression (assuming the jacobian
has been added to the namespace using ns.define_for(..., jacobians=('dV', 'dS'))
). For example, the following integrates 1
against geometry x
:
I = topo.integral('1 dV' @ ns, degree=0)
# Array<>
The resulting nutils.function.Array
object is a representation of the
integral, as yet unevaluated. To compute the actual numbers, use the
function.eval()
function:
function.eval(I)
# 1.0
Be careful with including the Jacobian in your integrands. The following two integrals are different:
function.eval(topo.integral('(1 + 1) dV' @ ns, degree=0))
# 2.0
function.eval(topo.integral('1 + 1 dV' @ ns, degree=0))
# 5.0
Like any other nutils.function.Array
, the integrals can be added or
subtracted:
J = topo.integral('x_0 dV' @ ns, degree=1)
function.eval(I+J)
# 1.5
Recall that a topology boundary is also a nutils.topology.Topology
object,
and hence it supports integration. For example, to integrate the geometry x
over the entire boundary, write
function.eval(topo.boundary.integral('x_0 dS' @ ns, degree=1))
# 1.0
To limit the integral to the right boundary, write
function.eval(topo.boundary['right'].integral('x_0 dS' @ ns, degree=1))
# 1.0
Note that this boundary is simply a point and the integral a point evaluation.
Back to our discrete solution
We observed earlier that we cannot evaluate our discrete solution because of lacking a specification of topological points -- or in Nutils jargon: leaving a space unbound. Integrals provide this information by defining the set of quadrature points that the function will be evaluated in (followed by a contraction with quadrature weights). This means we could aim to evaluate the following function:
f = topo.integral('∇_k(u) ∇_k(u) dV' @ ns, degree=1)
However, to do so we do need to provide the weights for argument 'u'. We do this as follows:
function.eval(f, arguments={'u': [1,2,0,5,4]})
Since integrals are nutils.function.Array
objects, integrals involving
arguments support derivatives. Taking the derivative once results in a 1D
function, which evaluates to a numpy.ndarray
:
function.eval(f.derivative('u'), arguments={'u': [1,2,0,5,4]})
# array([0.125, 0.25 , 0.25 , 0.25 , 0.125])
Since our function f
is quadratic in 'u', the second derivative becomes
constant (i.e. independent of arguments) and can be evaluated without any
argument specification:
M = f.derivative('u').derivative('u')
function.eval(M)
# array([[ 4., -4., 0., 0., 0.],
# [-4., 8., -4., 0., 0.],
# [ 0., -4., 8., -4., 0.],
# [ 0., 0., -4., 8., -4.],
# [ 0., 0., 0., -4., 4.]])
Observing that this matrix is sparse, it would be more efficient to evaluate
only the nonzero entries and the corresponding indices, so that these can be
used to form an appropriate sparse matric object. For this, the
nutils.function
module provides the two modifiers as_coo
and as_csr
.
The former is valid for objects of any dimension, and return the nonzero values
followed by a number of index vectors equal to the dimension of the array:
function.eval(function.as_coo(M))
# (array([ 8., -8., -8., 16., -8., -8., 16., -8., -8., 16., -8., -8., 8.]),
# array([0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4]),
# array([0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4]))
The latter of specific to 2D arrays and returns the sparsity structure in compressed sparse row format:
function.eval(function.as_csr(M))
# (array([ 8., -8., -8., 16., -8., -8., 16., -8., -8., 16., -8., -8., 8.]),
# array([ 0, 2, 5, 8, 11, 13]),
# array([0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4]))
Either of these can be used to form a Nutils sparse matrix using the
nutils.matrix.assemble_coo
or nutils.matrix.assemble_csr
functions, or
any third party sparse matrix of preference.