OOF2: The Manual
Synopses
C++ Synopsis
#include "engine/property.h"
class FluxProperty: , public PhysicalProperty {virtual void* begin_point(const FEMesh* mesh,
const Element element,
const Flux* flux,
const MasterPosition& position,
double time) const;virtual void end_point(const FEMesh* mesh,
const Element* element,
const Flux* flux,
const MasterPosition& position,
double time,
void* data) const;virtual void flux_matrix(const FEMesh* mesh,
const Element* element,
const ElementFuncNodeIterator& node,
const Flux* flux,
const MasterPosition& position,
double time,
void* data,
SmallSystem* linsys) const;virtual void flux_offset(const FEMesh* mesh,
const Element* element,
const Flux* flux,
const MasterPosition& position,
double time,
void* data,
SmallSystem* linsys);virtual void flux_value(const FEMesh* mesh,
const Element* element,
const Flux* flux,
const MasterPosition& position,
double time,
void* data,
SmallSystem* linsys) const;virtual void static_flux_value(const FEMesh* mesh,
const Element* element,
const Flux* flux,
const MasterPosition& position,
double time,
void* data,
SmallSystem* linsys) const;
}
Python Synopsis
PyFluxProperty is a swigged C++ class
that is derived (in C++) from
FluxProperty. It is used as a base
class for FluxProperties that are
defined in Python. When the methods listed below are called
from C++, they use the Python C API to invoke methods in the
Python derived class.
from ooflib.SWIG.engine import pypropertywrapper
class PyFluxProperty(FluxProperty):def begin_point(self, mesh, element, flux, position, time)def end_point(self, mesh, element, flux, position, time, data)def flux_matrix(self, mesh, element, node, flux, position, time, data, linsys)def flux_offset(self, mesh, element, flux, position, time, data, linsys)def flux_value(self, mesh, element, flux, position, time, data, linsys)def static_flux_value(self, mesh, element, flux, position, time, data, linsys)
Source Files
-
SRC/engine/property.C: C++ code -
SRC/engine/property.h: C++ header -
SRC/engine/property.swg: SWIG source code -
SRC/engine/property.swg: Python code included in the SWIG output -
SRC/engine/pypropertywrapper.C: C++ code forPropertiesimplemented in Python -
SRC/engine/pypropertywrapper.h: C++ header -
SRC/engine/pypropertywrapper.swg: SWIG source code -
SRC/engine/pypropertywrapper.spy: Python code inserted into the SWIG output.
Overview
A FluxProperty is Property that
contributes to a Flux. For example, Elasticity
contributes to Stress, and ThermalConductivity
contributes to Heat_Flux. The principal
job of the FluxProperty is to compute the
coefficients of the Fields and their time derivatives in a
linear expansion of a Flux, in terms of a Field or
Fields, , and its spatial and time derivatives:
is an field-independent offset, to first order. The
coefficients are written as functions of position, , but
, and may be nonlinear functions of the
Fields and their derivatives. A future version of OOF2 may
allow nonlinearities in and as well.
A new subclass of FluxProperty must
redefine at least one of the following four virtual methods:
-
FluxProperty::flux_matrix()computes the linearization of theFluxat a given point in anElement, as a function of the relevantFields, their gradients, and possibly their time derivatives. That is, it determines the coefficients , , , and in Eqn. (187). -
FluxProperty::flux_offset()computes the value of theFluxat a given point in anElementwhen theFieldsand their gradients are zero. This is in (187). -
FluxProperty::flux_value()computes the actual value of theFluxat a given point in anElement. If thePropertyis linear in theFields,flux_value()does not need to be redefined, since theFluxvalue can be deduced fromflux_matrix()andflux_offset(). -
static_flux_value()computes the part of theFluxvalue that depends only on theFieldsand not on their time derivatives, if theFluxdepends on theFieldderivatives.
The functions listed above all have a SmallSystem* argument, which represents a
small part of the LinearizedSystem
that is currently being built.
SmallSystem methods handle all of the
issues related to matrix indexing, element order, and so forth.
The derived FluxProperty methods (and the
developer) do not need to know anything about what equations are
being solved or how they are discretized by the finite element
machinery. Details on all of the functions are provided below.
In addition, two virtual utility functions, begin_point()
and end_point()
can be redefined to facilitate passing data between the other
routines.
Constructor
The C++ and Python constructors for
FluxProperty are the same as the base class
constructors.
Methods
void* begin_point(...) const
void* begin_point(const FEMesh* mesh, const Element* element, const Flux* flux, const MasterPosition& position, double time) const
When building the finite element matrices and vectors, OOF2
loops over the Gauss points within each Element. At each Gauss
point, and for each active Flux, it calls
FluxProperty methods for each
Property that contributes to the Flux, beginning with
begin_point(), followed by flux_matrix(),
flux_offset(),
and (sometimes) flux_value(),
and ending with end_point().
To avoid recomputing quantities that are required by more than
one of these methods, computations can be done in
begin_point() and their results stored in
a data structure. begin_point() should
return this structure (as a void* pointer in C++
or an object in Python). The data structure will be passed
through to the other FluxProperty
methods. If the data object was allocated in C++,
end_point() should delete it.
The data returned by begin_point() is not
stored or transmitted to any parts of OOF2 other than the
FluxProperty methods discussed here.
If no data needs to be transmitted,
begin_point() should return
nullptr in C++ or None in Python.
The arguments to begin_point() are:
const FEMesh* mesh-
The
FEMeshcurrently being solved. const Element* element-
The
Elementcontaining the current point. const Flux* flux-
The
Fluxthat thisPropertycomputes. If thePropertycomputes more than oneFlux,begin_pointwill be called once for eachFluxat each point. const MasterPosition& point-
The position within the
Elementwhere thePropertyis being evaluated. The position is given in master coordinates, which can be converted to physical coordinates byElement::from_master()if necessary. double time-
The time at which the
Propertyis being evaluated.
The default implementation of
begin_point() simply returns
nullptr in C++ and
None in Python.
void end_point(...) const
void end_point(const FEMesh* mesh, const Element* element, const Flux* flux, const MasterPosition& position, double time, void* data) const
end_point() is just like begin_point(),
except that it's called after OOF2 is
done computing the contribution at a point. The
end_point() method can be used to clean
up any temporary objects allocated by begin_point.
The arguments for end_point() are the same
as the arguments for begin_point(),
with the addition of a void* which points to the
data returned by begin_point.
The default implementation of end_point()
does nothing.
void flux_matrix(...) const
void flux_matrix(const FEMesh* mesh, const Element* element, const ElementFuncNodeIterator& node, const Flux* flux, const MasterPosition& position, double time, void* data, SmallSystem* linsys) const
FluxProperty subclasses that represent
the , , or terms in the constitutive
equation (187) must redefine the virtual
function FluxProperty::flux_matrix().
flux_matrix() computes a matrix that,
when multiplied by a vector of degrees of freedom and/or their
time derivatives, produces a vector containing the components
of a Flux. Explaining this properly requires a brief review
of the finite element machinery.
Let be the component of a field at position . If the field values are known at nodes , which are at positions , then the finite element shape functions can be used to approximate the field at :[77]
where is the shape function that is 1 at node and 0 at all other nodes, and
is the component of the field at node . Similarly, the gradient of the field is
Because OOF2 does its computations in 2D, gradients include only x and y derivatives, unless explicitly stated otherwise.
Temporarily ignoring the terms, a constitutive
relation connects a Field and its gradient,
, to a Flux , through moduli and
, and an offset, :
This is the linearization of a more general relation. , and may all be functions of , , , or time. ( must not have any linear dependence on or , though, because then it would be part of or .)
(191) can be written as a matrix equation:
(repeated indices are summed). The index is a flux component, is a space component, and is a field component. Inserting (188) and (190) into (192)
shows that the vector of flux values at is related to the vector of field values at the nodes by a matrix we call the flux matrix:
and an offset . The columns of correspond to field values and the rows to flux components . is a part of what is generally called the stiffness matrix.
Computing (194) is the
purpose of FluxProperty::flux_matrix().
On each call, flux_matrix() must make
contributions to for all of the components of a
given flux, all of the components of the relevant fields
(and their out-of-plane parts), and one given node
.
The SmallSystem
object that is passed in to flux_matrix
is a container for the various matrices that Properties
compute. flux_matrix() contributes to
by calling SmallSystem::stiffness_matrix_element(),
as shown in the example below.
Similarly, if the Flux depends on the time derivative of the
Field, , or its gradient, , through
moduli and respectively, then
flux_matrix() must also compute a
damping matrix:
It stores its results by calling SmallSystem::damping_matrix_element().
OOF2 currently does not allow nonlinearities in the time
derivative terms, so and cannot depend on the
Fields, but they can be functions of position.
Note that it is not necessary for the
flux_matrix() routine (or its author) to
know anything about the following:
-
The topology or number of nodes in the element.
-
How nodal degrees of freedom are mapped into columns of the matrix.
-
How flux components are mapped into rows of the matrix.
-
What equations are being solved.
The arguments to flux_matrix() are:
const FEMesh* mesh-
The finite element mesh that's being solved.
flux_matrix()probably doesn't have to use themeshobject directly, but it does need to pass it through to other functions. const Element* element-
The finite element under consideration. This shouldn't be explicitly needed except in cases in which the material parameters depend on physical space coordinates, or if history-dependent fields are stored in the element.
const ElementFuncNodeIterator& node-
This is the node referred to in the discussion above. It's passed in in the form of an iterator, which can iterate over all of the nodes of the element, although it should be thought of here simply as a way of accessing the node's indices and shape functions.[78] The node's shape function and its derivatives can be obtained from
ElementFuncNodeIterator::shapefunction()andElementFuncNodeIterator::dshapefunction(). const Flux* flux-
The
Flux. Properties that contribute to more than oneFluxneed to check this variable to know which flux they're computing now. const MasterPosition& position-
This is the position in (193).
positionis a point inelement'sElement's master coordinate space. It is often a Gauss integration point, but not always. Master coordinates can be converted to physical coordinates by callingElement::from_master(). double time-
The time at which the flux matrix is being evaluated.
void* data-
The data object, if any, returned by
begin_point(). -
SmallSystem* linsys -
The
SmallSystemclass stores the portion of the the actual flux matrix and damping matrix that are currently being computed, along with other flux data.[79]
Example of flux_matrix()
For example, for thermal conductivity the field is the temperature T, and the heat flux is the vector :
The z component in (196) has been separated out because,
as explained in Section 2.5.5,
the z derivatives of Fields are
treated differently from the x and
y derivatives in OOF2.
is treated as a separate scalar Field, and is only present
if Temperature is not in-plane. Because
gradients of
don't appear in (196), it is
treated as part of the term in (194).
Expanding the fields and derivatives in terms of the shape functions via (188) and (190),
from which we get
for and
for . Note that there
is no index because
Temperature is a scalar.
Here is the fluxmatrix routine for
the HeatConductivity class, from
SRC/engine/properties/heatconductivity/heatconductivity.C
in the OOF2 source code:
void HeatConductivity::flux_matrix(const FEMesh *mesh,
const Element *el,
const ElementFuncNodeIterator &nu,
const Flux *flux,
const MasterPosition &position,
double time,
void* data,
SmallSystem *linsys) const
{
if (*flux != *heat_flux) {
throw ErrProgrammingError("Unexpected flux", __FILE__, __LINE__);
}
double sf = nu.shapefunction(position);
double dsf0 = nu.dshapefunction(0, position);
double dsf1 = nu.dshapefunction(1, position);
const SymmMatrix3 cond(conductivitytensor(mesh, el, position));
for(IndexP i : *flux->components(ALL_INDICES)) {
// in-plane temperature gradient contributions
linsys->stiffness_matrix_element(i, temperature, nu) -=
cond(i, 0) * dsf0 + 
cond(i, 1) * dsf1; 
// out-of-plane temperature gradient contribution
if(!temperature->in_plane(mesh))
linsys->stiffness_matrix_element(i, temperature->out_of_plane(), nu)
-= cond(i, 2) * sf; 
}
}
|
We don't really need to do this. It's a sanity check to
make sure that we got the |
|
|
|
|
|
Here is where the shape functions are evaluated. This is . Shape function values are precomputed and cached at the Gauss integration points, so the evaluation here is fast. |
|
|
These are the derivatives of the shape functions. The
first argument is the component of the
gradient. |
|
|
|
|
|
Note that this loop is over all components of the
|
|
|
If the field weren't a scalar, there would be a loop over its components here. |
|
|
|
|
|
|
|
|
Here the |
|
|
This is (198), summing over . |
|
|
This checks to see if the temperature field has an out-of-plane part. If it does, its contribution to the stress is computed. |
|
|
This retrieves a reference to the matrix element
coupling the out-of-plane
part of the Temperature |
|
|
This is (199). |
void flux_offset(...) const
void flux_offset(const FEMesh* mesh, const Element* element, const Flux* flux, const MasterPosition& position, double time, void* data, SmallSystem *linysys) const
FluxProperty::flux_offset()
computes the term in (187).
It is the value of the linearized Flux when the Fields
are zero. It is similar in purpose to FluxProperty::flux_matrix(),
but generally simpler to implement.
Because is not proportional to a Field or its
gradient, flux_offset() does not have
to worry about expansions and shape functions. It just
computes the value of and stores it in the given
SmallSystem.[80]
As an example, consider thermal expansion. The flux (stress) is
is the thermal expansion coefficient and
is the temperature at which the thermal stress vanishes.
The
term in (200) is handled by
ThermalExpansion::flux_matrix()
because of its field dependencies, but
makes a field independent contribution
to the flux, and needs to be handled by
flux_offset().
The arguments to
FluxProperty::flux_offset() are:
const FEMesh* mesh-
The finite element mesh that's being solved.
flux_offset()probably doesn't have to use themeshobject directly, but it does have to pass it through to other functions. const Element* element-
The finite element under consideration. This shouldn't be explicitly needed except in cases in which the material parameters depend on physical space coordinates or in which history-dependent fields are stored in the element.
const Flux* flux-
The
Flux. Properties that contribute to more than oneFluxneed to check this variable to know which flux they're computing now. const MasterPosition& position-
The flux offset is evaluated at a physical coordinate (often a Gauss integration point, but not always).
positionis the point in theElement's master coordinate space. double time-
The time at which the flux offset is being evaluated.
void* data-
The data object, if any, returned by
begin_point(). -
SmallSystem* linsys -
The
SmallSystemclass stores the portion of the finite element matrices and vectors that are currently being computed. The results of theflux_offset()calculation are stored here.
Example of flux_offset
Here is the flux_offset routine for
the ThermalExpansion class, from
SRC/engine/properties/thermalexpansion/thermalexpansion.C:
void ThermalExpansion::flux_offset(const FEMesh* mesh,
const Element* element,
const Flux* flux,
const MasterPosition& x,
double time,
void* data,
SmallSystem* linsys)
const
{
if(*flux!=*stress_flux) {
throw ErrProgrammingError("Unexpected flux.", __FILE__, __LINE__);
}
const Cijkl modulus = elasticity->cijkl(mesh, element, x);
SymmMatrix3 expten = expansiontensor(mesh, element, x);
linsys->offsetVector() += modulus*expten*T0;
} |
This is the object returned by |
|
|
This is optional here, to check that we got the |
|
|
This extracts the elastic modulus from the |
|
|
This is in
(200).
|
|
|
is stored as a vector even though the Stress
is a tensor. It's treated as a list of values in an
order determined by the |
The example above uses C++ vectors and tensors. An equivalent way of writing it explicitly loops over their components:
void ThermalExpansion::flux_offset(const FEMesh* mesh,
const Element* element,
const Flux* flux,
const MasterPosition& x,
double time,
void* data,
SmallSystem* linsys) const {
if(*flux != *stress_flux) {
throw ErrProgrammingError("Unexpected flux.", __FILE__, __LINE__);
}
const Cijkl modulus = elasticity->cijkl(mesh, element, x);
SymmMatrix3 expten = expansiontensor(mesh, element, x);
for(IndexP ij : *flux->components(ALL_INDICES)) {
double &offset_el = linsys->offset_vector_element(ij);
for(SymTensorIndex kl : symTensorIJComponents) {
if(kl.diagonal()) {
offset_el += modulus(ij,kl)*expten[kl]*T0;
}
else {
offset_el += 2.0*modulus(ij,kl)*expten[kl]*T0;
}
}
}
}
|
This loop is over all components of the stress, even if the problem is being solved in plane stress. The out-of-plane components are used in the constraint equations. Looping over components is discussed in Section 8.4. |
|
|
|
|
|
This loops over all components of the stress. The line could have been written
for(IndexP kl : *flux->components(ALL_INDICES)) { ... }
but the generic |
|
|
The loop over the components of the symmetric tensor flux hits each independent component once, but when multiplying tensors, the off-diagonal components occur twice, leading to a factor of two in the off-diagonal terms. This complication can be avoided by using the tensor multiplication operators directly, as in the simpler example above. |
void flux_value(...) const
void flux_value(const FEMesh* mesh, const Element* element, const Flux* flux, const MasterPosition& position, double time, void* data, SmallSystem *linysys) const
flux_value() computes the value of a
given Flux, at a given point in an Element, as in (187). It is used when solving nonlinear
equations and when computing flux-dependent Outputs.
The default base class version of
flux_value() uses flux_property(),
flux_offset(),
and the Field values to evaluate (187). A subclass of
FluxProperty should redefine
flux_value() if for some reason it is
faster or more accurate to compute the Flux directly than
it is to use (187). If the Flux is
a nonlinear function of the Fields, it should always
define flux_value().
The arguments to flux_value() are:
const FEMesh* mesh-
The
FEMeshcurrently being solved or evaluated. const Element* element-
The
Elementcontaining the current point. const Flux* flux-
The
Fluxthat is being computed. const MasterPosition& position-
The position within the
Elementwhere thePropertyis being evaluated. The position is given in master coordinates, which can be converted to physical coordinates byElement::from_master()if necessary. double time-
The time at which the
Fluxis being computed. void* data-
The data object, if any, returned by
begin_point(). -
SmallSystem* linsys -
The
SmallSystemclass stores the portion of the finite element matrices and vectors that are currently being computed. The results of theflux_value()calculation are stored here.
Example of flux_value()
Here is the flux_value() routine for
the thermal conductivity property, from
SRC/engine/properties/heatconductivity/heatconductivity.C
in the OOF2 source code. (Note that it could have been
omitted, since in this case the heat flux can be computed
from flux_matrix alone.)
void HeatConductivity::flux_value(
const FEMesh *mesh,
const Element *element,
const Flux *flux,
const MasterPosition &position,
double time,
void* data,
SmallSystem *fluxdata)
const
{
DoubleVec fieldGradient = temperature->gradient(mesh, element, pt);
const SymmMatrix3 cond(conductivitytensor(mesh, element, pt));
linsys->fluxVector() -= cond*fieldGradient;
}
|
|
|
|
|
|
|
|
void static_flux_value(...) const
void static_flux_value(const FEMesh* mesh, const Element* element, const Flux* flux, const MasterPosition& position, double time, void* data, SmallSystem *linysys) const
static_flux_value() computes the part
of the Flux that depends only on the Fields and not on
their time derivatives. It only needs to be defined if the
Flux depends on the time derivatives of the Fields and
is nonlinear. Its arguments are the same as the arguments
to flux_value().
In the base class, static_flux_value()
simply calls flux_value().
[78]
It's not possible to use a Node
instead of an ElementFuncNodeIterator
here. Nodes are unable to
evaluate shape functions because they don't know which
Element
is being computed.
ElementFuncNodeIterators know
which Element they're looping
over.
[79] This data can't be stored
directly in the Flux object because one Flux is
shared among many FEMeshes. The SmallSystem
object is local to this computation.
[80]
The values of the flux offset are stored in the SmallSystem
as a vector, for all types of Flux. This would be completely
invisible to the user, if the name of the
SmallSystem method for accessing
the offset weren't offsetVector().
[81] It would look like
for(IndexP kl : *flux->components(ALL_INDICES)) {
const SymTensorIndex* kayell = dynamic_cast<const SymTensorIndex*>(kl.fieldindex());
if(kayell->diagonal()) {
offset_el -= modulus(ij,kl)*expten[kl]*T0;
}
else {
offset_el -= 2.0*modulus(ij,kl)*expten[kl]*T0;
}
} 


