OOF2: The Manual

Name

EqnProperty — A Property that contributes to an Equation

Synopsis

C++ Synopsis

#include "engine/property.h" 
class EqnProperty: , public PhysicalProperty {
  virtual void* begin_point(const FEMeshmesh,
                            const Elementelement,
                            const Equationequation,
                            const MasterPositionposition,
                            double time) const;

  virtual void end_point(const FEMeshmesh,
                         const Elementelement,
                         const Equationequation,
                         const MasterPositionposition,
                         double time,
                         void* data) const;

  virtual void time_deriv_matrices(const FEMeshmesh,
                                   const Elementelement,
                                   const ElementFuncNodeIteratornode,
                                   const Equationequation,
                                   const MasterPositionposition,
                                   double time,
                                   void* data,
                                   SmallSystemlinsys) const;

  virtual void force_value(const FEMeshmesh,
                           const Elementelement,
                           const Equationequation,
                           const MasterPositionposition,
                           double time,
                           void* data,
                           SmallSystemlinsys) const;

  virtual void force_deriv_matrix(const FEMeshmesh,
                                  const Elementelement,
                                  const Equationequation,
                                  const ElementFuncNodeIteratornode,
                                  const MasterPositionposition,
                                  double time,
                                  void* data,
                                  SmallSystemlinsys) const;

}

Python Synopsis

PyEqnProperty is a swigged C++ class that is derived (in C++) from EqnProperty. It is used as a base class for EqnProperties 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 PyEqnProperty(EqnProperty):
  def begin_point (self, mesh, element, equation, position)
  def end_point(self, mesh, element, equation, position, time, data)
  def time_deriv_matrices(self, mesh, element, node, equation, position, time, data, linsys)
  def force_value(self, mesh, element, equation, position, time, data, linsys)
  def force_deriv_matrix(self, mesh, element, node, equation, 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 for Properties implemented 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

An EqnProperty is a Property that makes a direct contribution to an Equation without being part of a Flux. For instance Mechanical:MassDensity:ConstantMassDensity makes a contribution to the second time derivative in the Force_Balance Equation but is not part of the Stress Flux. Similarly, Mechanical:Damping:Isotropic makes a contribution to the first time derivative, and Mechanical:ForceDensity:ConstantForceDensity contributes to the force term.

A new subclass of EqnProperty must define at least one of following two virtual methods:

In addition, if force_value() is nonlinear, the new subclass must define:

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 EqnProperty 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 EqnProperty are the same as the base class constructors.

Methods

void* begin_point(...) const

void *begin_point(const FEMesh* mesh,
                  const Element* element,
                  const Equation* equation,
                  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, for each active Equation, it calls EqnProperty methods for each Property that contributes the Equation, beginning with begin_point(), followed by time_deriv_matrices(), force_deriv_matrix() (for nonlinear properties), and finally force_value(). 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 arguments to begin_point() are:

const FEMesh* mesh

The FEMesh currently being solved.

const Element* element

The Element containing the current point.

const Equation*

The Equation being solved. If the EqnProperty contributes to more than one Equation, begin_point() will be called once for each Equation and this value should be checked to determine which one is current.

const MasterPosition

The position within the Element where the Property is being evaluated. The position is given in master coordinates, which can be converted to physical coordinates by Element::from_master() if necessary.

double time

The time at which the Property is 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 Equation* equation,
               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 time_deriv_matrices(...) const

void time_deriv_matrices(const FEMesh* mesh,
                         const Element* element,
                         const ElementFuncNodeIterator& node,
                         const Equation* equation,
                         const MasterPosition& position,
                         double time,
                         void *data,
                         SmallSystem* linsys) const 

EqnProperty classes that contribute to 𝐂 or 𝐌 , the coefficients of the first and second time derivatives of the Fields in the time-dependent divergence equation (2.9), need to define time_deriv_matrices(). It is analogous to FluxProperty::flux_matrix(), but generally much simpler.

As in FluxProperty::flux_matrix(), the derivation begins by expanding the Field components in terms of the shape functions:[77]

φk(𝐱)=Nν(𝐱)φkν
(201)

where Nν(𝐱) is the shape function that is 1 at node ν and 0 at all other nodes, and

φkνφk(𝐱ν)
(202)

is the kth component of the field at node ν . OOF2 stores the values of φkν for all nodes in a vector, where kν should be understood as a single index.

Because φkν is a vector, Equation (2.9) is a matrix equation, so the first derivative term is

[𝐂φt(𝐱)]i=𝐂ikφkt(𝐱)
(203)

where i is an equation index. Using Eq. (201), this becomes

[𝐂φt(𝐱)]i=[Nν(𝐱)𝐂ik]φkνt
(204)

The derivation for 𝐌 is identical, except that the second time derivative is used instead of the first:

[𝐌2φt2(𝐱)]i=[Nν(𝐱)𝐂ik]2φkνt2
(205)

The job of time_deriv_matrices() is to compute the terms in square brackets on the right hand sides of (204) and (205).

The arguments to time_deriv_matrices() are:

const FEMesh* mesh

The finite element mesh that's being solved. time_deriv_matrices() probably doesn't have to use the mesh object 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() and ElementFuncNodeIterator::dshapefunction().

Equation* equation

The Equation being computed. Properties that contribute to more than one Equation may need to check this variable to know which one they're being asked to compute.

const MasterPosition& position

This is the position 𝐱 in (204). position is a point in element's Element's master coordinate space. It is often a Gauss integration point, but not always. Master coordinates can be converted to physical coordinates by calling Element::from_master().

double time

The time at which the coefficients are being evaluated.

void* data

The data object, if any, returned by begin_point().

SmallSystem* linsys

Storage for the portion of the the actual 𝐂 and 𝐌 that are currently being computed, along with other data. SmallSystem knows how to convert local Element and Node indices into global FEMesh indices.

The default version of time_deriv_matrices() does nothing.

Example of time_deriv_matrices() for first derivatives

For example, here is the first_time_deriv_matrix() method for the Mechanical:Damping:Isotropic Property in SRC/engine/properties/damping/damping.h. The Property adds a

d𝐮t
(206)

term to the x and y components of the Displacement u in the force balance equation (6.120).[82]

void IsotropicDampingProp::time_deriv_matrices(
					   const FEMesh* mesh,
					   const Element* element,
					   const ElementFuncNodeIterator& node, 1
					   const Equation* eqn,
					   const MasterPosition& mpos,
					   double time,
					   void *data, 
					   SmallSystem* linsys)
  const
{
  if(*eqn != force_balance) 2 3
     throw ErrProgrammingError("Unexpected equation.", __FILE__, __LINE__);
  double shapeFuncVal = node.shapefunction(mpos); 4
  for(IndexP component : *eqn->components()) { 5
    eqndata->damping_matrix_element(component, displacement, component, node) 6
      += coeff*shapeFuncVal; 7
  }
} 

1

This is the node ν in Eq. (204).

2

force_balance is an Equation* that is set when IsotropicDamping::precompute() calls Equation::getEquation(). It is stored in the class instance. Similiarly, dispacement is a Field* set by calling Field::getField(). They could just as well have been set in the IsotropicDampingProp constructor.

3

Checking the equation is optional here. It verifies that the equation eqn is actually the Force_Balance equation. If the Property contributed to more than one Equation, a check like this would determine which one to compute.

4

This is Nν(𝐱) in Eq. (206).

5

See Section 8.4 for a discussion about looping over components of an Equation. OOF2 divergence equations only have in-plane components, and this loops over all of them.

6

SmallSystem::damping_matrix_element() returns a reference to the finite element matrix element that is being computed. The row is determined by the Equation component, and the column by the component of the displacement field at the Node node. Since the damping coefficient is a scalar, aka a diagonal matrix, both indices are component.

7

This is where Eq. (206) is added to the finite element matrix. It uses += instead of = just in case some other Property has already made a contribution to that matrix element.

coeff is the damping coefficient. It is a parameter defined in the Property's PropertyRegistration and stored in the Property by its constructor.

Example of time_deriv_matrices() for second derivatives

Here is the time_deriv_matrices() method for the Mechanical:MassDensity:ConstantMassDensity Property in SRC/engine/properties/massdensity/massdensity.C, which adds a term

m2ut2
(207)

to the force balance equation (6.120). It is essentially the same as the damping example above, but it calls SmallSystem::mass_matrix_element() instead of SmallSystem::damping_matrix_element().

void MassDensityProp::time_deriv_matrices(
				       const FEMesh* mesh,
				       const Element* element,
				       const ElementFuncNodeIterator& node,
				       const Equation *eqn,
				       const MasterPosition& mpos,
				       double time,
				       void*,
				       SmallSystem *eqdata) const
{
  double shapeFuncVal = node.shapefunction(mpos);
  for(IndexP eqncomp : *eqn->components()) {
    eqdata->mass_matrix_element(component, disp, component, node) 1
      += rho_ * shapeFuncVal;
  }
} 

1

SmallSystem::mass_matrix_element() returns a reference to an entry in the finite element matrix element that is being computed. The row is determined by the Equation component eqncomp, and the column by the k component of the displacement field at the Node node.

void force_value(...) const

void force_value(const FEMesh* mesh,
                 const Element* element,
                 const Equation* equation,
                 const MasterPosition& position,
                 double time,
                 void* data,
                 SmallSystem* linsys) const 

A Property that makes a direct contribution to the force term in Eq. (2.9) must redefine force_value(), which must use SmallSystem::forceVector() to set the value of the force at the given point in the given Element. The arguments to force_value() are[83]:

const FEMesh* mesh

The FEMesh that's currently being solved.

const Element* element

The Element containing the current point.

const Equation*

The Equation being solved. If the EqnProperty contributes to more than one Equation, begin_point() will be called once for each Equation and this value should be checked to determine which one is current.

const MasterPosition

The position within the Element where the Property is being evaluated. The position is given in master coordinates, which can be converted to physical coordinates by Element::from_master() if necessary.

double time

The time at which the Property is being evaluated.

void* data

The data object, if any, returned by begin_point().

The default version of force_value() does nothing.

Example of force_value()

Here is the force_value() method for Mechanical:ForceDensity:ConstantForceDensity. It contributes a constant force density given by parameters gx and gy, which are stored in the Property as a ??? g.

void ForceDensity::force_value(
                       const FEMesh *mesh,
                       const Element *element,
                       const Equation *eqn,
                       const MasterPosition &x,
                       double time, void *data,
                       SmallSystem *linsys) const
{
  linsys->forceVector() += g; 1
} 

1

This line adds all components of g to the right hand side being constructed by linsys. The indexing is handled by the SmallSystem class.

void force_deriv_matrix(...) const

 void force_deriv_matrix(const FEMesh* mesh,
                         const Element* element,
                         const ElementFuncNodeIterator& node,
                         const Equation* equation,
                         const MasterPosition& position,
                         double time,
                         void* data, 
                         SmallSystem* linsys) const 

force_deriv_matrix() needs to be defined for any EqnProperty that defines a nonlinear force_value().[84] It computes the derivatives of the force with respect to the Fields. That is, it computes a ???

Jij=fiφj
(208)

where fi is a force component and φj is a Field component. The result is stored in the given SmallSystem by calling SmallSystem::force_deriv_matrix_element(), which handles all of the indexing.

The arguments to force_deriv_matrix() are the same as the arguments to time_deriv_matrices().

The default version of force_deriv_matrix() does nothing.

Example of force_deriv_matrix()

Here is a template for a force_deriv_matrix() in a Property that depends nonlinearly on the Displacement Field:

void NonlinearForceDensity::force_deriv_matrix(const FEMesh *mesh,
					       const Element *element,
					       const ElementFuncNodeIterator &node,
					       const Equation *eqn,
					       const MasterPosition &point,
					       double time, void*,
					       SmallSystem *linsys) const
{
  DoubleVec fieldVal = displacement->values(mesh, element, point); 1
  Coord coord = element->from_master(point); 2
  SmallMatrix forceDeriv = nonlin_force_density_deriv(coord, time, fieldVal); 3
  
  for(IndexP eqncomp : *eqn->components()) { 4
    for (IndexP fieldcomp : *displacement->components(ALL_INDICES)) { 5

      linsys->force_deriv_matrix_element(eqncomp, displacement, fieldcomp, node)
        += forceDeriv(eqno, fieldno); 6
    }
  }
} 

1

displacement is a TwoVectorField that was set in the Property's constructor like this:

displacement = dynamic_cast<TwoVectorField*>(Field::getField("Displacement")); 

2

coord is the real space position of the point at which the derivatives are being evaluated. Element::from_master() converts the given ??? to laboratory coordinates.

4

This is the loop over the rows of Jij .

5

This is the loop over the columns of Jij .

3

nonlin_force_density_deriv() is the function that computes the matrix Jij .

6

This adds the components of the forceDeriv matrix to the appropriate entries in the linearized system, linsys.



[82] The damping does not represent physical damping, because it acts on the velocity in the lab frame, not the strain rate, but it can be useful to provide numerical stability.

[83] Note that unlike the other EqnProperty methods, force_value() does not take a ElementFuncNodeIterator argument. It doesn't multiply a field or field derivative that is being expanded in shape functions.

[84] Technically, it only needs to be defined if the nonlinear solver needs to compute the Jacobian.