\(

 \renewcommand{\b}[1]{\mathbf{#1}}

\) $$\b{a}_{\rm{i}} \rm{b} b$$

Implementation of a linear plane stress triangle

This tutorial will describe the implementation of a standard plane stress triangle with linear approximation of the displacement field. For quasi-static problems the following set of equations needs to be solved $$\mathbf{f}_{\mathrm{int}} = \mathbf{f}_{\mathrm{ext}}$$ with the internal and external force vectors respectively

$$\b{f}_{\rm{int}} = \int_V \b{B}^{\rm{T}} \b{\sigma} \ \rm{d}V $$

$$\b{f}_{\rm{ext}} = \int_V \b{N}^{\rm{T}} \b{b} \ \rm{d}V + \int_{\Gamma} \b{N}^{\rm{T}} \b{t} \ \rm{d}\Gamma $$

The default solution procedure for solving the equations are a Newton-Rapshon scheme and for this the tangent stiffness matrix is needed, computed as $$\mathbf{K} = \int_V \mathbf{B}^{\mathrm{T}} \mathbf{D} \mathbf{B} \ \mathrm{d}V $$

Since the FE equations for all standard continuum elements (2D plane stress/strain, 3D, etc.) are equal, much of the element implementations are placed in the base class NonLinStructuralElement (or StructuralElement for purely linear problems). For example, this base class implements methods to compute the internal and external load vector together with the tangent stiffnes matrix (all as defined above). The main question that arises is then: what must a new element implement? In short it is everything that is element specific, such as the following:

In addition to the above several auxilary methods needs to be implemented such as:

All these topics will be considered in this tutorial.

Differential volume element - computeVolumeAround Differential boundary element

double 
BasicElement :: computeVolumeAround(GaussPoint *gp)
{
    // Computes the volume element dV associated with the given gp.
 
    double weight = gp->giveWeight();
    FloatArray *lCoords = gp->giveNaturalCoordinates(); // local/natural coords of the gp (parent domain)
    double detJ = fabs( this->interp.giveTransformationJacobian( *lCoords, FEIElementGeometryWrapper(this) ) );
    double thickness = this->giveCrossSection()->give(CS_Thickness, gp); // the cross section keeps track of the thickness
 
    return detJ * thickness * weight; // dV
}

Differential boundary element - computeEdgeVolumeAround

double
BasicElement :: computeEdgeVolumeAround(GaussPoint *gp, int iEdge)
{
    /* Returns the line element ds associated with the given gp on the specific edge.
     * Note: The name is misleading since there is no volume to speak of in this case. 
     * The returned value is used for integration of a line integral (external forces).
     */
    double detJ = this->interp.edgeGiveTransformationJacobian( iEdge, * gp->giveNaturalCoordinates(), FEIElementGeometryWrapper(this) );
    return detJ * gp->giveWeight();
}

Computation of the \(\mathbf{B}\)-matrix: This method is called computeBmatrixAt

The derivatives of the shape functions dNdx are evaluated using the elements' interpolator class

this->interp.evaldNdx( dNdx, * gp->giveNaturalCoordinates(), FEIElementGeometryWrapper(this) );

$$\frac{d \mathbf{N}}{d \mathbf{x}} = \begin{pmatrix} \frac{\mathrm{d}N_1}{\mathrm{d}x_1} & \frac{\mathrm{d}N_1}{\mathrm{d}x_2}
\frac{\mathrm{d}N_2}{\mathrm{d}x_1} & \frac{\mathrm{d}N_2}{\mathrm{d}x_2}
\frac{\mathrm{d}N_3}{\mathrm{d}x_1} & \frac{\mathrm{d}N_3}{\mathrm{d}x_2} \end{pmatrix} $$

From these derivatives the \(\mathbf{B}\)-matrix can be constructed which for a three noded triangle with two dofs per node looks like $$\mathbf{B} = \begin{pmatrix} \frac{\mathrm{d}N_1}{\mathrm{d}x_1} & 0 & \frac{\mathrm{d}N_2}{\mathrm{d}x_1} & 0 & \frac{\mathrm{d}N_3}{\mathrm{d}x_1} & 0
\frac{\mathrm{d}N_1}{\mathrm{d}x_1} & 0 & \frac{\mathrm{d}N_2}{\mathrm{d}x_1} & 0 & \frac{\mathrm{d}N_3}{\mathrm{d}x_1} & 0
\frac{\mathrm{d}N_1}{\mathrm{d}x_2} & \frac{\mathrm{d}N_1}{\mathrm{d}x_1} & \frac{\mathrm{d}N_2}{\mathrm{d}x_2} & \frac{\mathrm{d}N_2}{\mathrm{d}x_1} & \frac{\mathrm{d}N_3}{\mathrm{d}x_2} & \frac{\mathrm{d}N_3}{\mathrm{d}x_1}
\end{pmatrix} $$

    // Construct the B-matrix
    answer.resize(3, 6);
 
    answer.at(1, 1) = dNdx.at(1, 1);
    answer.at(1, 3) = dNdx.at(2, 1);
    answer.at(1, 5) = dNdx.at(3, 1);
 
    answer.at(2, 2) = dNdx.at(1, 2);
    answer.at(2, 4) = dNdx.at(2, 2);
    answer.at(2, 6) = dNdx.at(3, 2);
 
    answer.at(3, 1) = dNdx.at(1, 2);
    answer.at(3, 2) = dNdx.at(1, 1);
    answer.at(3, 3) = dNdx.at(2, 2);
    answer.at(3, 4) = dNdx.at(2, 1);
    answer.at(3, 5) = dNdx.at(3, 2);
    answer.at(3, 6) = dNdx.at(3, 1);

Auxilary metods that needs to be overloaded:

    /// Constructor
    BasicElement(int n, Domain * d);    
    /// Destructor.
    virtual ~BasicElement() { }         
 
    virtual FEInterpolation *giveInterpolation() const;
    virtual int computeNumberOfDofs() { return 6; }
    virtual void giveDofManDofIDMask(int inode, IntArray &) const;
    virtual double computeVolumeAround(GaussPoint *gp);
 
 
    virtual const char *giveInputRecordName() const { return _IFT_BasicElement_Name; }
    virtual const char *giveClassName() const { return "BasicElement"; }
    virtual IRResultType initializeFrom(InputRecord *ir);
    virtual MaterialMode giveMaterialMode() { return _PlaneStress; }
    virtual int testElementExtension(ElementExtension ext) { return ( ( ext == Element_EdgeLoadSupport ) ? 1 : 0 ); }
BasicElement.C

BasicElement.h

Problem representation - Engineering model

The concept “engineering model” is an abstraction for the problem under consideration. It represents the type of analysis to be performed (e.g. static structural, transient heat flow, etc.). The base class EngngModel declares and implements the basic services for assembling characteristic components and services for starting the solution step and its termination. Derived classes ``know'' the form of governing equation and the physical meaning of particular components. They are responsible for forming the governing equation for each solution step, usually by summing contributions from particular elements and nodes.ecific load type dependent services and implement all necessary services.