Add a New UniaxialMaterial C++: Difference between revisions

From OpenSeesWiki
Jump to navigation Jump to search
(Created page with 'To add a new Uniaxial Material module using the C++ language, the developer must: # provide a new C++ subclass of the UniaxialMaterial class # provide an interface function that ...')
 
No edit summary
 
(3 intermediate revisions by the same user not shown)
Line 67: Line 67:
The methods with =0; are methods that you must implement for the class to link successfully with OpenSees. The other classes are optional.
The methods with =0; are methods that you must implement for the class to link successfully with OpenSees. The other classes are optional.


The setTriaStrain() is the method called by an element when a new strain in the material is to be set. Subsequent calls to getTangent() and getStress() are to return thecorresponding tangent and stress values for that stress.
The setTriaStrain() is the method called by an element when a new strain in the material is to be set. Subsequent calls to getTangent() and getStress() are to return thecorresponding tangent and stress values for that stress. setTrialStrain() is invoked as the solution algorithm tries a number of trial solution steps as it goes from one commited solution to the next on the solution path.


The commit() method is invoked when a solution has been obtained on the solution path. It is the responsibility of the material to be able to back track to that solution if a revertToLastCOmmit() is invoked. This will happen if the algorithm fails to find a solution on the solution path.  
The commitState() method is invoked when a trial solution has been determined to be on the solution path. It is the responsibility of the material to be able to back track to that solution if a revertToLastCOmmit() is invoked. This will happen if the algorithm fails to find a solution on the solution path.  


The getCopy() method is invoked by an element in the elements constructor. The material is to return a unique copy of itself to the element. This way different elements can use the same material type with the same properties, with each element having it's own unique copy.
The getCopy() method is invoked by an element in the elements constructor. The material is to return a unique copy of itself to the element. This way different elements can use the same material type with the same properties, with each element having it's own unique copy.
Line 80: Line 80:
The commit() method is what is called
The commit() method is what is called


=== Example - Truss2D ===
=== Example - ElasticPPecpp ===


In the following section we will provide all necessary code to add a new 2d planar truss element into an OpenSees interpreter. To demonstrate the power of object-oriented programming, the stress-strain relationship will be provided by a UniaxialMaterial object.
In the following section we will provide all necessary code to add a new elastic perfectly plastic material into an OpenSees interpreter.  


==== Header ====
==== Header ====


The header for thew new class, which we will call Truss2D is as follows:
The header for thew new class, which we will call Truss2D is as follows:


<source lang="cpp">
<source lang="cpp">
// include directives
#ifndef ElasticPPcpp_h
#include <Element.h>
#define ElasticPPcpp_h
#include <Matrix.h>
#include <Vector.h>


// forward declarations
#include <UniaxialMaterial.h>
class UniaxialMaterial;


class Truss2D : public Element
class ElasticPPcpp : public UniaxialMaterial
{
{
   public:
   public:
     // constructors                                                                 
     ElasticPPcpp(int tag, double E, double eyp);
    Truss2D(int tag,
    ElasticPPcpp();
            int Nd1, int Nd2,
            UniaxialMaterial &theMaterial,
            double A);


     Truss2D();
     ~ElasticPPcpp();


     // destructor                                                                   
     int setTrialStrain(double strain, double strainRate = 0.0);
     ~Truss2D();
    double getStrain(void);
    double getStress(void);
     double getTangent(void);


    // initialization
    int setDomain(Domain *theDomain);


     // public methods to obtain inforrmation about dof & connectivity               
     double getInitialTangent(void) {return E;};
    int getNumExternalNodes(void) const;
    const ID &getExternalNodes(void);
    Node **getNodePtrs(void);
    int getNumDOF(void);


    // public methods to set the state of the element                               
     int commitState(void);
     int commitState(void);
     int revertToLastCommit(void);
     int revertToLastCommit(void);
     int revertToStart(void);
     int revertToStart(void);
    int update(void);


     // public methods to obtain stiffness 
     UniaxialMaterial *getCopy(void);
    const Matrix &getTangentStiff(void);
    const Matrix &getInitialStiff(void);


     // public method to obtain resisting force
     int sendSelf(int commitTag, Channel &theChannel);
     const Vector &getResistingForce(void);
     int recvSelf(int commitTag, Channel &theChannel,
                FEM_ObjectBroker &theBroker);


    // method for obtaining information specific to an element
     void Print(OPS_Stream &s, int flag =0);
     void Print(OPS_Stream &s, int flag =0);
    Response *setResponse(const char **argv, int argc, OPS_Stream &s);
    int getResponse(int responseID, Information &eleInformation);


    // public methods for database/parallel processing                                                     
    int sendSelf(int commitTag, Channel &theChannel);
    int recvSelf(int commitTag, Channel &theChannel, FEM_ObjectBroker &theBroker);
    void Print(OPS_Stream &s, int flag =0);


  protected:
protected:


   private:
   private:
    // private member functions - only available to objects of the class             
     double fyp, fyn;   // positive and negative yield stress                                                                                 
     double computeCurrentStrain(void) const;
     double ezero;      // initial strain                                                                                                     
 
     double E;          // elastic modulus                                                                                                   
    // private attributes - a copy for each object of the class                     
     double ep;         // plastic strain at last commit                                                                                     
     UniaxialMaterial *theMaterial;      // pointer to a material                   
    ID  externalNodes;                  // contains the id's of end nodes           
    Matrix trans;      // hold the transformation matrix                     
     double L;          // length of truss (undeformed configuration)                                                                             
     double A;          // area of truss                                             
    Node *theNodes[2]; // node pointers                                             


     // static data - single copy for all objects of the class                       
     double trialStrain; // trial strain                                                                                                       
     static Matrix trussK;   // class wide matrix for returning stiffness                           
    double trialStress;      // current trial stress                                                                                         
     static Vector trussR;   // class wide vector for returning residual             
     double trialTangent;     // current trial tangent                                                                                         
};
     double commitStrain;     // last commited strain                                                                                         
#endif
    double commitStress;     // last commited stress                                                                                         
    double commitTangent;    // last committed  tangent       
</source>
</source>


The header file defines the interface and variables for the class Truss2D. It defines the new class to be a sublass of the Element class. In the public interface, are two constructors and a destructor in addition to minimal set of methods we showed for the Element class.  
The header file defines the interface and variables for the class ElasticPPcpp. It defines the new class to be a sublass of the UniaxialMaterial class. In the public interface, are two constructors and a destructor in addition to minimal set of methods we showed for the UniaxialMaterial class.  
There are no protected data or methods as we do not expect this class to be subclassed. In the private section, we define one private method, computeCurrentStrain(), and we define a number of private variables and a number of static variables.
There are no protected data or methods as we do not expect this class to be subclassed. In the private section we define a number of private variables and a number of variables. Some of these are parameter variable which do not change with each commit, e.g. E, and some state variable which do change, e.g. ep, fyp, and fyn.
 
The header has a number of #include directives, one is needed for the base class and every class used as a variable in the list of data (except those that are used as pointers). For those classes that only appear as pointers in the header file (Node, UniaxialMaterial) a forward declaration is all that is needed (the include could also have been used, but using the forward declaration simplifies dependencies and reduces the amount of code that ha to be recompiled later if changes are made).


==== Implementation ====
==== Implementation ====


It another file, Truss2D.cpp, we place the code that details what the constructors, destructor and methods do. In addition we provide one additional procedure OPS_Truss2D() (NOTE it has the same name as the class with an OPS_ prefix). We will go through each part of the file.
It another file, ElasticPPcpp.cpp, we place the code that details what the constructors, destructor and methods do. In addition we provide one additional procedure OPS_ElasticPPcpp() (NOTE it has the same name as the class with an OPS_ prefix). We will go through each part of the file.


===== Include Directives =====
===== Include Directives =====
Line 178: Line 152:


<source lang="cpp">
<source lang="cpp">
#include "Truss2D.h"
#include "ElasticPPcpp.h"


#include <elementAPI.h>
#include <elementAPI.h>
#include <G3Globals.h>
#include <Vector.h>
#include <Information.h>
#include <Domain.h>
#include <Node.h>
#include <Channel.h>
#include <Channel.h>
#include <Message.h>
#include <FEM_ObjectBroker.h>
#include <UniaxialMaterial.h>
#include <Renderer.h>
#include <ElementResponse.h>
#include <math.h>
#include <math.h>
#include <stdlib.h>
#include <float.h>
#include <string.h>
</source>
</source>


Line 200: Line 164:
===== Static Variables =====
===== Static Variables =====


Next, we initialize the static variables. For this example we are using 2 static-variables (objects shared by each Truss2D object that is created), one to return the tangent matrix and the other to return the resisting force.
Next, we initialize the static variables. For this example we are using 1 static-variables to keep track of the number of times the external procedure to parse and create such an object is called.


<source lang="cpp">
<source lang="cpp">
// initialise the class wide variables                                               
static int numElasticPPcpp = 0;
Matrix Truss2D::trussK(4,4);
Vector Truss2D::trussR(4);
</source>
</source>


===== Constructors =====
===== Constructors =====


After the list of includes, we provide the 2 constructors. The constructors are rather simple. They just initialize all the data variables defined in the header. Note it is very important to set all pointer values to 0.
After the list of includes, we provide the 2 constructors. The constructors are rather simple. They just initialize all the data variables defined in the header. Note it is very important to set all pointer values to 0 if you use pointers in your class. We will use none here.  
 
The first constructor is the one most typically used. The arguments provide the elements tag, the tags of the two end nodes, the element's area and a copy of the element's material. The code in the constructor does the following:
# The elements tag and a 0 are passed to the Element constructor.
# The matreial pointer, theMaterial, is set to a copy of the material obtained from the material that is passed in the arguments.
# The externalNodes array is set to be an array of size 2 and it's values are set to the nodal tags of the 2 nodes.  
# The theNodes array components are set to be 0.
It should be noted that the static variables dealing with length, transformations, and nodes are set to 0 in the constructors. They will be filled in when the setDomain() method is invoked on the object.
 


The first constructor is the one most typically used. The arguments provide the materials tag, youngs modulus and initial yield point strain values. material. The code in the constructor simply computes the positive and negative yield stress based on the input provided.


<source lang="cpp">                                                               
<source lang="cpp">                                                               
Truss2D::Truss2D(int tag,
ElasticPPcpp::ElasticPPcpp(int tag, double e, double eyp)
                int Nd1, int Nd2,
:UniaxialMaterial(tag, 0),
                UniaxialMaterial &theMat,
  ezero(0.0), E(e), ep(0.0),
                double a)
  trialStrain(0.0), trialStress(0.0), trialTangent(E),
:Element(tag, 0),
commitStrain(0.0), commitStress(0.0), commitTangent(E)
  externalNodes(2),
  trans(1,4), L(0.0), A(a)
{
{
   // get a copy of the material object for our own use                               
   fyp = E*eyp;
  theMaterial = theMat.getCopy();
   fyn = -fyp;
   if (theMaterial == 0) {
}
  opserr << "FATAL TrussCPP::TrussCPP() - out of memory, could not get a copy of the Material\n";
</source>
  exit(-1);
  }


  // fill in the ID containing external node info with node id's                    
The second constructor is called when paralell processing or the database feature of the OpenSees application is used. It's purpose is to create a blank TElasticPPcpp object, that will be filled in when the recvSelf() method is invoked on the object.
  if (externalNodes.Size() != 2) {
    opserr << "FATAL TrussCPP::TrussCPP() - out of memory, could not create an ID of size 2\n";
    exit(-1);
  }


  externalNodes(0) = Nd1;
<source lang="cpp">
  externalNodes(1) = Nd2;
ElasticPPcpp::ElasticPPcpp()
:UniaxialMaterial(0, 0),
fyp(0.0), fyn(0.0), ezero(0.0), E(0.0), ep(0.0),
trialStrain(0.0), trialStress(0.0), trialTangent(E),
commitStrain(0.0), commitStress(0.0), commitTangent(E)
{


  theNodes[0] = 0;
  theNodes[1] = 0;
}
}
</source>
</source>


The second constructor is called when paralell processing or the database feature of the OpenSees application is used. It's pupose is to create a blank Truss2D object, that will be filled in when the recvSelf() method is invoked on the object.
===== Destructor =====
 
The we provide the destructor. In the destructor all memory that the the object created or was passed to it in the constructor must be destroyed. For this example we have no such memory. We could have left the destructor out entirely. Hoowever, it is good practice to leave it in your source code.


<source lang="cpp">
<source lang="cpp">  
Truss2D::Truss2D()
ElasticPPcpp::~ElasticPPcpp()
:Element(0, 0),
theMaterial(0),
externalNodes(2),
trans(1,4), L(0.0), A(0.0)
{
{
   theNodes[0] = 0;
   // does nothing                                                                                                                             
  theNodes[1] = 0;
}
}
</source>
</source>


===== Destructor =====
===== getCopy() Method =====
 
This is the method called by each element or section to get unique copies of a material.


The we provide the destructor. In the destructor all memory that the Truss2D created or was passed to it in the constructor must be destroyed.
<source lang="cpp"> 
For our example, we need to invoke the destructor on the copy of the material object.
UniaxialMaterial *
ElasticPPcpp::getCopy(void)
{
  ElasticPPcpp *theCopy =
    new ElasticPPcpp(this->getTag(),E,fyp/E);
  theCopy->ep = this->ep;


<source lang="cpp">    
   return theCopy;
Truss2D::~Truss2D()
{                                     
    if (theMaterial != 0)
        delete theMaterial;
}
}
</source>
</source>


===== setDomain() Initialization Method


The setDomain() method is invoked when the truss element is being added to the Domain. It is in this method that most of the private variables of the object are determined. The method returns 0 if successfull, a negative number if not. In the method we obtain pointers to the end nodes, nodal coordinates are obtained and the elements length and transformation matrix is set once the coordinates have been obtained.
===== setTrialStrain() Method =====
 
This, as mentioned, is the method called when the element has computed a nw strain for the element. The element will make subsequent calls to getStress() and getTangent() to obtain new values of these for the new strain. This is typically the most complicated method to write and to determine the theory for before you even write the code. ALl subsequent methods are trivial.
 


<source lang="cpp">
<source lang="cpp">
void
int
Truss2D::setDomain(Domain *theDomain)
ElasticPPcpp::setTrialStrain(double strain, double strainRate)
{
{
     // check Domain is not null - invoked when object removed from a domain                                             
    if (fabs(trialStrain - strain) < DBL_EPSILON)
     if (theDomain == 0) {
      return 0;
        return;
 
     }
    trialStrain = strain;
 
    double sigtrial;    // trial stress                                                                                                       
    double f;          // yield function                                                                                                     
 
     // compute trial stress                                                                                                                   
    sigtrial = E * ( trialStrain - ezero - ep );
 
     //sigtrial  = E * trialStrain;                                                                                                           
    //sigtrial -= E * ezero;                                                                                                                  
     //sigtrial -= E *  ep;                                                                                                                   


     // first ensure nodes exist in Domain and set the node pointers                                                     
     // evaluate yield function                                                                                                               
     Node *end1Ptr, *end2Ptr;
     if ( sigtrial >= 0.0 )
    int Nd1 = externalNodes(0);
         f = sigtrial - fyp;
    int Nd2 = externalNodes(1);
     else
    end1Ptr = theDomain->getNode(Nd1);
         f = -sigtrial + fyn;
    end2Ptr = theDomain->getNode(Nd2);
    if (end1Ptr == 0) {
      opserr << "WARNING Truss2D::setDomain() - at truss " << this->getTag() << " node " <<
         Nd1 << " does not exist in domain\n";
        return; // don't go any further - otherwise segemntation fault                                                 
     }
    if (end2Ptr == 0) {
      opserr << "WARNING Truss2D::setDomain() - at truss " << this->getTag() << " node " <<
         Nd2 << "  does not exist in domain\n";
        return;  // don't go any further - otherwise segemntation fault                                                 
    }
    theNodes[0] = end1Ptr;
    theNodes[1] = end2Ptr;
    // call the DomainComponent class method THIS IS VERY IMPORTANT                                                     
    this->DomainComponent::setDomain(theDomain);


     // ensure connected nodes have correct number of dof's                                                             
     double fYieldSurface = - E * DBL_EPSILON;
    int dofNd1 = end1Ptr->getNumberDOF();
     if ( f <= fYieldSurface ) {
    int dofNd2 = end2Ptr->getNumberDOF();
     if ((dofNd1 != 2) || (dofNd2 != 2)) {
      opserr << "Truss2D::setDomain(): 2 dof required at nodes\n";
      return;
    }


    // now determine the length & transformation matrix                                                                 
      // elastic                                                                                                                             
    const Vector &end1Crd = end1Ptr->getCrds();
      trialStress = sigtrial;
    const Vector &end2Crd = end2Ptr->getCrds();
      trialTangent = E;


     double dx = end2Crd(0)-end1Crd(0);
     } else {
    double dy = end2Crd(1)-end1Crd(1);


    L = sqrt(dx*dx + dy*dy);
      // plastic                                                                                                                             
      if ( sigtrial > 0.0 ) {
        trialStress = fyp;
      } else {
        trialStress = fyn;
      }


    if (L == 0.0) {
      trialTangent = 0.0;
      opserr << "WARNING Truss2D::setDomain() - Truss2D " << this->getTag() <<
        " has zero length\n";
      return; // don't go any further - otherwise divide by 0 error                                                   
     }
     }


     double cs = dx/L;
     return 0;
    double sn = dy/L;
}
 


    trans(0,0) = -cs;
    trans(0,1) = -sn;
    trans(0,2) = cs;
    trans(0,3) = sn;
}
</source>
</source>


===== Methods Dealing With Nodes =====
===== Trivial Methods =====


Next comes 4 rather simple methods that return basic information about the elements nodes. These are one line methods that should not need any explanation!
Next comes 3 rather simple methods that return basic information computed in the setTrialStrain(). You do of course have the option to ignore the setTrialStrain() method and compute the stress and tangent quantities again in the interests of saving memory.  
<source lang="cpp">
<source lang="cpp">
int
double
Truss2D::getNumExternalNodes(void) const
ElasticPPcpp::getStrain(void)
{
{
    return 2;
  return trialStrain;
}
}


const ID &
double
Truss2D::getExternalNodes(void)
ElasticPPcpp::getStress(void)
{
{
   return externalNodes;
   return trialStress;
}
}


Node **
 
Truss2D::getNodePtrs(void)
double
ElasticPPcpp::getTangent(void)
{
{
   return theNodes;
   return trialTangent;
}
}


int
Truss2D::getNumDOF(void) {
    return 4;
}
</source>
</source>




===== Methods Dealing With Current State =====
===== Methods Dealing With Current State =====
As mentioned, when the algorithm finds a solution state as it goes from one converged solution to the next. As it attempts to find these solutions it goes through a number of trial steps (each setTrialStrain() is invoked in each of these steps). Once it finds a trial step that is on the solution path it will stop and invoke commitState() on the material. Any state variables that the material uses needs to be updated at this time. Should the algorithm fail to find a solution it may return to the last converged step or indeed the start. You the developer must provide code so that your mateial can indeed go back to these states and report correct getTangent() and getStress() values for subsequent analysis atte,pts.


<source lang="cpp">
<source lang="cpp">
int
int
Truss2D::commitState()
ElasticPPcpp::commitState(void)
{
    return theMaterial->commitState();
}
 
int
Truss2D::revertToLastCommit()
{
    return theMaterial->revertToLastCommit();
}
 
int
Truss2D::revertToStart()
{
    return theMaterial->revertToStart();
}
 
int
Truss2D::update()
{
{
  // determine the current strain given trial displacements at nodes                 
    double sigtrial;    // trial stress                                                                                                       
  double strain = this->computeCurrentStrain();
    double f;           // yield function                                                                                                     
 
  // set the strain in the materials                                                 
  theMaterial->setTrialStrain(strain);
 
  return 0;
}
</source>


===== Methods To Return Tangent Matrix =====
    // compute trial stress                                                                                                                   
    sigtrial = E * ( trialStrain - ezero - ep );


In both methods, we obtain the appropriate tangent from the material and use this to return the transformed matrix.
    // evaluate yield function                                                                                                               
    if ( sigtrial >= 0.0 )
        f =  sigtrial - fyp;
    else
        f = -sigtrial + fyn;


<source lang="cpp">
    double fYieldSurface = - E * DBL_EPSILON;
const Matrix &
    if ( f > fYieldSurface ) {
Truss2D::getTangentStiff(void)
      // plastic                                                                                                                             
{
      if ( sigtrial > 0.0 ) {
    if (L == 0.0) { // length = zero - problem in setDomain() warning message already printed                                         
         ep += f / E;
         trussK.Zero();
      } else {
         return trussK;
         ep -= f / E;
      }
     }
     }


     // get the current E from the material for the last updated strain                                                               
     commitStrain = trialStrain;
     double E = theMaterial->getTangent();
    commitTangent=trialTangent;
     commitStress = trialStress;


     // form the tangent stiffness matrix                                                                                             
     return 0;
    trussK = trans^trans;
    trussK *= A*E/L;
 
    // return the matrix                                                                                                             
    return trussK;
}
}


const Matrix &
int
Truss2D::getInitialStiff(void)
ElasticPPcpp::revertToLastCommit(void)
{
{
    if (L == 0.0) { // length = zero - problem in setDomain() warning message already printed                                         
  trialStrain = commitStrain;
        trussK.Zero();
  trialTangent = commitTangent;
        return trussK;
  trialStress = commitStress;
    }


    // get the current E from the material for the last updated strain                                                               
  return 0;
    double E = theMaterial->getInitialTangent();
 
    // form the tangent stiffness matrix                                                                                             
    trussK = trans^trans;
    trussK *= A*E/L;
 
    // return the matrix                                                                                                             
    return trussK;
}
}
</source>


===== Methods To Return Resisting Force =====


In this method we obtain the stress from the material and use this to return the transformed force vector.
int
 
ElasticPPcpp::revertToStart(void)
<source lang="cpp">
const Vector &
Truss2D::getResistingForce()
{
{
    if (L == 0.0) { // if length == 0, problem in setDomain()                                                                         
  trialStrain = commitStrain = 0.0;
        trussR.Zero();
  trialTangent = commitTangent = E;
        return trussR;
  trialStress = commitStress = 0.0;
    }


    // want: R = Ku - Pext                                                                                                           
  ep = 0.0;


    // force = F * transformation                                                                                                     
  return 0;
    double force = A*theMaterial->getStress();
}
    for (int i=0; i<4; i++)
        trussR(i) = trans(0,i)*force;


    return trussR;
}
</source>
</source>


Line 483: Line 380:
<source lang="cpp">
<source lang="cpp">
void
void
Truss2D::Print(OPS_Stream &s, int flag)
ElasticPPcpp::Print(OPS_Stream &s, int flag)
{
{
   s << "Element: " << this->getTag();
   s << "ElasticPPcpp tag: " << this->getTag() << endln;
   s << " type: Truss2D iNode: " << externalNodes(0);
   s << "  E: " << E << endln;
   s << " jNode: " << externalNodes(1);
   s << " ep: " << ep << endln;
   s << " Area: " << A;
   s << " stress: " << trialStress << " tangent: " << trialTangent << endln;
  s << " \t Material: " << *theMaterial;
}
}
</source>
</source>
Line 498: Line 394:


<source lang="cpp">
<source lang="cpp">
Response *
Truss2D::setResponse(const char **argv, int argc, OPS_Stream &output)
{
  Response *theResponse = 0;


  output.tag("ElementOutput");
</source>
  output.attr("eleType",this->getClassType());
 
  output.attr("eleTag",this->getTag());
===== Methods Dealing With Databases/Parallel Processing =====
  int numNodes = this->getNumExternalNodes();
 
  const ID &nodes = this->getExternalNodes();
There are two methods provided which are required is the user uses to use the database or parallel procesing features of the OpenSees applications. If neither are to be used, the developer need simply return a negative value in both methods. The idea is that the material must pack up it's information using Vector and ID objects and send it off to a Channel object. On the flip side, the receiving blank element must receive the same Vector and ID data, unpack it and set the variables.
  static char nodeData[32];


  for (int i=0; i<numNodes; i++) {
    sprintf(nodeData,"node%d",i+1);
    output.attr(nodeData,nodes(i));
  }


  if (strcmp(argv[0],"force") == 0 || strcmp(argv[0],"forces") == 0 ||
<source lang="cpp">
      strcmp(argv[0],"globalForce") == 0 || strcmp(argv[0],"globalForces") == 0) {
int
    const Vector &force = this->getResistingForce();
ElasticPPcpp::sendSelf(int cTag, Channel &theChannel)
    int size = force.Size();
{
    for (int i=0; i<size; i++) {
  int res = 0;
      sprintf(nodeData,"P%d",i+1);
  static Vector data(9);
      output.tag("ResponseType",nodeData);
  data(0) = this->getTag();
    }
  data(1) = ep;
    theResponse = new ElementResponse(this, 1, this->getResistingForce());
  data(2) = E;
   }
  data(3) = ezero;
  data(4) = fyp;
  data(5) = fyn;
  data(6) = commitStrain;
  data(7) = commitStress;
   data(8) = commitTangent;


   else if (strcmp(argv[0],"dampingForce") == 0 || strcmp(argv[0],"dampingForces") == 0) {
   res = theChannel.sendVector(this->getDbTag(), cTag, data);
    const Vector &force = this->getResistingForce();
   if (res < 0)
    int size = force.Size();
    opserr << "ElasticPPcpp::sendSelf() - failed to send data\n";
    for (int i=0; i<size; i++) {
      sprintf(nodeData,"P%d",i+1);
      output.tag("ResponseType",nodeData);
    }
    theResponse = new ElementResponse(this, 2, this->getResistingForce());
   } else if (strcmp(argv[0],"axialForce") ==0)
      return new ElementResponse(this, 3, 0.0);


  output.endTag();
   return res;
   return theResponse;
}
}


int
int
Truss2D::getResponse(int responseID, Information &eleInfo)
ElasticPPcpp::recvSelf(int cTag, Channel &theChannel,
                                FEM_ObjectBroker &theBroker)
{
{
double strain;
  int res = 0;
  static Vector data(9);
  res = theChannel.recvVector(this->getDbTag(), cTag, data);
  if (res < 0)
    opserr << "ElasticPPcpp::recvSelf() - failed to recv data\n";
  else {
    this->setTag(data(0));
    ep    = data(1);
    E    = data(2);
    ezero = data(3);
    fyp  = data(4);
    fyn  = data(5);
    commitStrain=data(6);
    commitStress=data(7);
    commitTangent=data(8);
    trialStrain = commitStrain;
    trialTangent = commitTangent;
    trialStress = commitStress;
  }


   switch (responseID) {
   return res;
  case -1:
    return -1;
  case 1: // global forces                                                                                             
    return eleInfo.setVector(this->getResistingForce());
  case 2:
    return eleInfo.setVector(this->getRayleighDampingForces());
  case 3:
    theMaterial->setTrialStrain(strain);
    return eleInfo.setDouble(A * theMaterial->getStress());
  default:
    return 0;
  }
}
}
</source>
</source>


===== Methods Dealing With Databases/Parallel Processing =====
==== External Procedure ====
 
This is the all importat extenal procedure that the interpreter will parse when it comes accross your element on the command line. You need to parse the command line, create a material using the command line arguments you parsed and then return this material. The name of the procedure must be OPS_YourClassName (no exceptions). If this procedure is missing or the name is incorrect, your material will fail to load.


There are two methods provided which are required is the user uses to use the database or parallel procesing features of the OpenSees applications. If neither are to be used, the developer need simply return a negative value in both methods. The idea is that the element must pack up it's information using Vector and ID objects and send it off to a Channel object. On the flip side, the receiving blank element must receive the same Vector and ID data, unpack it and set the variables.
NOTE: parsing the command line is easy with some other procedures that are defined in the elementAPI.h file. In the example we show how to get integer and double values from the command line. Other options such as character strings and obtaining the number of input arguments are also available.


The #ifdef stuff at the start is required for different operating systems.


<source lang="cpp">
<source lang="cpp">
int
Truss2D::sendSelf(int commitTag, Channel &theChannel)
{
    int res;


    // note: we don't check for dataTag == 0 for Element                                                               
#ifdef _USRDLL
    // objects as that is taken care of in a commit by the Domain                                                       
#define OPS_Export extern "C" _declspec(dllexport)
    // object - don't want to have to do the check if sending data                                                     
#elif _MACOSX
    int dataTag = this->getDbTag();
#define OPS_Export extern "C" __attribute__((visibility("default")))
 
#else
    // Truss2D packs it's data into a Vector and sends this to theChannel                                               
#define OPS_Export extern "C"
    // along with it's dbTag and the commitTag passed in the arguments                                                 
#endif
 
    Vector data(5);
    data(0) = this->getTag();
    data(1) = A;
    data(2) = theMaterial->getClassTag();
    int matDbTag = theMaterial->getDbTag();
    // NOTE: we do have to ensure that the material has a database                                                     
    // tag if we are sending to a database channel.                                                                     
    if (matDbTag == 0) {
        matDbTag = theChannel.getDbTag();
        if (matDbTag != 0)
            theMaterial->setDbTag(matDbTag);
    }
    data(3) = matDbTag;
 
    res = theChannel.sendVector(dataTag, commitTag, data);
    if (res < 0) {
      opserr << "WARNING Truss2D::sendSelf() - failed to send Vector\n";
      return -1;
    }
 
    // Truss2D then sends the tags of it's two end nodes                                                               
    res = theChannel.sendID(dataTag, commitTag, externalNodes);
    if (res < 0) {
      opserr << "WARNING Truss2D::sendSelf() - failed to send ID\n";
      return -2;
    }
 
    // finally Truss2D asks it's material object to send itself                                                         
    res = theMaterial->sendSelf(commitTag, theChannel);
    if (res < 0) {
      opserr << "WARNING Truss2D::sendSelf() - failed to send the Material\n";
      return -3;
    }


  return 0;
}


int
OPS_Export void *
Truss2D::recvSelf(int commitTag, Channel &theChannel, FEM_ObjectBroker &theBroker)
OPS_ElasticPPcpp()
{
{
     int res;
  // print out some KUDO's                                                                                                                   
     int dataTag = this->getDbTag();
  if (numElasticPPcpp == 0) {
     opserr << "ElasticPPcpp unaxial material - Written by fmk UC Berkeley Copyright 2008 - Use at your Own Peril\n";
     numElasticPPcpp =1;
  }


    // Truss2D creates a Vector, receives the Vector and then sets the                                                 
  // Pointer to a uniaxial material that will be returned                                                                                     
    // internal data with the data in the Vector                                                                       
  UniaxialMaterial *theMaterial = 0;


    Vector data(5);
  //                                                                                                                                         
    res = theChannel.recvVector(dataTag, commitTag, data);
  // parse the input line for the material parameters                                                                                         
    if (res < 0) {
  //                                                                                                                                         
      opserr << "WARNING Truss2D::recvSelf() - failed to receive Vector\n";
      return -1;
    }


    this->setTag((int)data(0));
  int   iData[1];
    A = data(1);
  double dData[2];
    // Truss2D now receives the tags of it's two external nodes                                                         
  int numData;
    res = theChannel.recvID(dataTag, commitTag, externalNodes);
  numData = 1;
    if (res < 0) {
  if (OPS_GetIntInput(&numData, iData) != 0) {
      opserr << "WARNING Truss2D::recvSelf() - failed to receive ID\n";
    opserr << "WARNING invalid uniaxialMaterial ElasticPP tag" << endln;
      return -2;
    return 0;
    }
  }


    // we create a material object of the correct type,                                                                
  numData = 2;
     // sets its database tag and asks this new object to recveive itself.                                               
  if (OPS_GetDoubleInput(&numData, dData) != 0) {
     int matClass = data(2);
     opserr << "WARNING invalid E & ep\n";
    int matDb = data(3);
     return 0;
  }


    theMaterial = theBroker.getNewUniaxialMaterial(matClass);
  //                                                                                                                                         
    if (theMaterial == 0) {
  // create a new material                                                                                                                   
      opserr << "WARNING Truss2D::recvSelf() - failed to create a Material\n";
  //                                                                                                                                         
      return -3;
    }


    // we set the dbTag before we receive the material  - this is important                                             
  theMaterial = new ElasticPPcpp(iData[0], dData[0], dData[1]);
    theMaterial->setDbTag(matDb);
    res = theMaterial->recvSelf(commitTag, theChannel, theBroker);
    if (res < 0) {
      opserr << "WARNING Truss2D::recvSelf() - failed to receive the Material\n";
      return -3;
    }


  if (theMaterial == 0) {
    opserr << "WARNING could not create uniaxialMaterial of type ElasticPPCpp\n";
     return 0;
     return 0;
  }
  // return the material                                                                                                                     
  return theMaterial;
}
}
</source>

Latest revision as of 20:31, 11 September 2013

To add a new Uniaxial Material module using the C++ language, the developer must:

  1. provide a new C++ subclass of the UniaxialMaterial class
  2. provide an interface function that will be used to parse the input and create the new material.

Unlike the C and Fortran modules, no information about the state of the model is passed as arguments to the material methods. It is the responsibility of the material to okeep whatever information it needs. This information will include both parameters (information needed to define the material) and state variables or history variables (information needed by the material to remember it's current state for the computation of the stress and tangent)

NOTE: This document assumes the reader is familiar with the C++ programming language.

UniaxialMaterial Class

The Uniaxial class itself is an abstract base class. It inherits from both the Material class, which is itself a subclass of TaggedObject class and the MovableObject class. The class has a large number of methods defined in the interface, not all these methods need to be included in a new UniaxialMaterial class. The following is the minimal interface that should be considered:

The UniaxialMaterial Class:

#ifndef ElasticPPcpp_h
#define ElasticPPcpp_h

// Written: fmk                                                                                                                                
//                                                                                                                                             
// Description: This file contains the class definition for                                                                                    
// ElasticPPcpp. ElasticPPcpp provides the abstraction                                                                                         
// of an elastic perfectly plastic uniaxial material,                                                                                          
//                                                                                                                                             
// What: "@(#) ElasticPPcpp.h, revA"                                                                                                           

#include <UniaxialMaterial.h>
class UniaxialMaterial : public Material
{
  public:
    UniaxialMaterial (int tag, int classTag);
    virtual ~UniaxialMaterial();

    virtual int setTrialStrain (double strain, double strainRate =0) =0;

    virtual double getStrain (void) = 0;
    virtual double getStrainRate (void);
    virtual double getStress (void) = 0;
    virtual double getTangent (void) = 0;
    virtual double getInitialTangent (void) = 0;

    virtual int commitState (void) = 0;
    virtual int revertToLastCommit (void) = 0;
    virtual int revertToStart (void) = 0;

    virtual UniaxialMaterial *getCopy (void) = 0;

    virtual Response *setResponse (const char **argv, int argc,
                                   OPS_Stream &theOutputStream);
    virtual int getResponse (int responseID, Information &matInformation);

    virtual int sendSelf(int commitTag, Channel &theChannel);
    virtual int recvSelf(int commitTag, Channel &theChannel,
                 FEM_ObjectBroker &theBroker);

    virtual void Print(OPS_Stream &s, int flag =0);

  protected:

  private:
}

The methods with =0; are methods that you must implement for the class to link successfully with OpenSees. The other classes are optional.

The setTriaStrain() is the method called by an element when a new strain in the material is to be set. Subsequent calls to getTangent() and getStress() are to return thecorresponding tangent and stress values for that stress. setTrialStrain() is invoked as the solution algorithm tries a number of trial solution steps as it goes from one commited solution to the next on the solution path.

The commitState() method is invoked when a trial solution has been determined to be on the solution path. It is the responsibility of the material to be able to back track to that solution if a revertToLastCOmmit() is invoked. This will happen if the algorithm fails to find a solution on the solution path.

The getCopy() method is invoked by an element in the elements constructor. The material is to return a unique copy of itself to the element. This way different elements can use the same material type with the same properties, with each element having it's own unique copy.

The setResponse()/getResponse() typically do not have to be provided. These are the methods called by a recorder after a commit(). If you are appy with the existing responses fro a UniaxialMaterial which responds to "stress", "strain", "tangent", "stressANDstrain" you do not have to implement these methods. The example below shows them just for those ew who want additional info out of their materials.

The sendSelf()/recvSelf() methods are used in parallel processing with OpenSeesSP and when using the database command. If you don't envision using the material in these situations you can again ignore these methods. Again I am only showing them in the code for those even fewer who would do this.


The commit() method is what is called

Example - ElasticPPecpp

In the following section we will provide all necessary code to add a new elastic perfectly plastic material into an OpenSees interpreter.

Header

The header for thew new class, which we will call Truss2D is as follows:

#ifndef ElasticPPcpp_h
#define ElasticPPcpp_h

#include <UniaxialMaterial.h>

class ElasticPPcpp : public UniaxialMaterial
{
  public:
    ElasticPPcpp(int tag, double E, double eyp);
    ElasticPPcpp();

    ~ElasticPPcpp();

    int setTrialStrain(double strain, double strainRate = 0.0);
    double getStrain(void);
    double getStress(void);
    double getTangent(void);


    double getInitialTangent(void) {return E;};

    int commitState(void);
    int revertToLastCommit(void);
    int revertToStart(void);

    UniaxialMaterial *getCopy(void);

    int sendSelf(int commitTag, Channel &theChannel);
    int recvSelf(int commitTag, Channel &theChannel,
                 FEM_ObjectBroker &theBroker);

    void Print(OPS_Stream &s, int flag =0);


 protected:

  private:
    double fyp, fyn;    // positive and negative yield stress                                                                                  
    double ezero;       // initial strain                                                                                                      
    double E;           // elastic modulus                                                                                                     
    double ep;          // plastic strain at last commit                                                                                       

    double trialStrain; // trial strain                                                                                                        
    double trialStress;      // current trial stress                                                                                           
    double trialTangent;     // current trial tangent                                                                                          
    double commitStrain;     // last commited strain                                                                                           
    double commitStress;     // last commited stress                                                                                           
    double commitTangent;    // last committed  tangent

The header file defines the interface and variables for the class ElasticPPcpp. It defines the new class to be a sublass of the UniaxialMaterial class. In the public interface, are two constructors and a destructor in addition to minimal set of methods we showed for the UniaxialMaterial class. There are no protected data or methods as we do not expect this class to be subclassed. In the private section we define a number of private variables and a number of variables. Some of these are parameter variable which do not change with each commit, e.g. E, and some state variable which do change, e.g. ep, fyp, and fyn.

Implementation

It another file, ElasticPPcpp.cpp, we place the code that details what the constructors, destructor and methods do. In addition we provide one additional procedure OPS_ElasticPPcpp() (NOTE it has the same name as the class with an OPS_ prefix). We will go through each part of the file.

Include Directives

The first part of the file contains the list of includes. It is necessary to have an #include directive for each class and api file that is used within the .cpp file and is not included in the header.

#include "ElasticPPcpp.h"

#include <elementAPI.h>
#include <Vector.h>
#include <Channel.h>
#include <math.h>
#include <float.h>


Static Variables

Next, we initialize the static variables. For this example we are using 1 static-variables to keep track of the number of times the external procedure to parse and create such an object is called.

static int numElasticPPcpp = 0;
Constructors

After the list of includes, we provide the 2 constructors. The constructors are rather simple. They just initialize all the data variables defined in the header. Note it is very important to set all pointer values to 0 if you use pointers in your class. We will use none here.

The first constructor is the one most typically used. The arguments provide the materials tag, youngs modulus and initial yield point strain values. material. The code in the constructor simply computes the positive and negative yield stress based on the input provided.

                                                               
ElasticPPcpp::ElasticPPcpp(int tag, double e, double eyp)
:UniaxialMaterial(tag, 0),
 ezero(0.0), E(e), ep(0.0),
 trialStrain(0.0), trialStress(0.0), trialTangent(E),
 commitStrain(0.0), commitStress(0.0), commitTangent(E)
{
  fyp = E*eyp;
  fyn = -fyp;
}

The second constructor is called when paralell processing or the database feature of the OpenSees application is used. It's purpose is to create a blank TElasticPPcpp object, that will be filled in when the recvSelf() method is invoked on the object.

ElasticPPcpp::ElasticPPcpp()
:UniaxialMaterial(0, 0),
 fyp(0.0), fyn(0.0), ezero(0.0), E(0.0), ep(0.0),
 trialStrain(0.0), trialStress(0.0), trialTangent(E),
 commitStrain(0.0), commitStress(0.0), commitTangent(E)
{

}
Destructor

The we provide the destructor. In the destructor all memory that the the object created or was passed to it in the constructor must be destroyed. For this example we have no such memory. We could have left the destructor out entirely. Hoowever, it is good practice to leave it in your source code.

   
ElasticPPcpp::~ElasticPPcpp()
{
  // does nothing                                                                                                                              
}
getCopy() Method

This is the method called by each element or section to get unique copies of a material.

  
UniaxialMaterial *
ElasticPPcpp::getCopy(void)
{
  ElasticPPcpp *theCopy =
    new ElasticPPcpp(this->getTag(),E,fyp/E);
  theCopy->ep = this->ep;

  return theCopy;
}


setTrialStrain() Method

This, as mentioned, is the method called when the element has computed a nw strain for the element. The element will make subsequent calls to getStress() and getTangent() to obtain new values of these for the new strain. This is typically the most complicated method to write and to determine the theory for before you even write the code. ALl subsequent methods are trivial.


int
ElasticPPcpp::setTrialStrain(double strain, double strainRate)
{
    if (fabs(trialStrain - strain) < DBL_EPSILON)
      return 0;

    trialStrain = strain;

    double sigtrial;    // trial stress                                                                                                        
    double f;           // yield function                                                                                                      

    // compute trial stress                                                                                                                    
    sigtrial = E * ( trialStrain - ezero - ep );

    //sigtrial  = E * trialStrain;                                                                                                             
    //sigtrial -= E * ezero;                                                                                                                   
    //sigtrial -= E *  ep;                                                                                                                     

    // evaluate yield function                                                                                                                 
    if ( sigtrial >= 0.0 )
        f =  sigtrial - fyp;
    else
        f = -sigtrial + fyn;

    double fYieldSurface = - E * DBL_EPSILON;
    if ( f <= fYieldSurface ) {

      // elastic                                                                                                                               
      trialStress = sigtrial;
      trialTangent = E;

    } else {

      // plastic                                                                                                                               
      if ( sigtrial > 0.0 ) {
        trialStress = fyp;
      } else {
        trialStress = fyn;
      }

      trialTangent = 0.0;
    }

    return 0;
}
Trivial Methods

Next comes 3 rather simple methods that return basic information computed in the setTrialStrain(). You do of course have the option to ignore the setTrialStrain() method and compute the stress and tangent quantities again in the interests of saving memory.

double
ElasticPPcpp::getStrain(void)
{
  return trialStrain;
}

double
ElasticPPcpp::getStress(void)
{
  return trialStress;
}


double
ElasticPPcpp::getTangent(void)
{
  return trialTangent;
}


Methods Dealing With Current State

As mentioned, when the algorithm finds a solution state as it goes from one converged solution to the next. As it attempts to find these solutions it goes through a number of trial steps (each setTrialStrain() is invoked in each of these steps). Once it finds a trial step that is on the solution path it will stop and invoke commitState() on the material. Any state variables that the material uses needs to be updated at this time. Should the algorithm fail to find a solution it may return to the last converged step or indeed the start. You the developer must provide code so that your mateial can indeed go back to these states and report correct getTangent() and getStress() values for subsequent analysis atte,pts.

int
ElasticPPcpp::commitState(void)
{
    double sigtrial;    // trial stress                                                                                                        
    double f;           // yield function                                                                                                      

    // compute trial stress                                                                                                                    
    sigtrial = E * ( trialStrain - ezero - ep );

    // evaluate yield function                                                                                                                 
    if ( sigtrial >= 0.0 )
        f =  sigtrial - fyp;
    else
        f = -sigtrial + fyn;

    double fYieldSurface = - E * DBL_EPSILON;
    if ( f > fYieldSurface ) {
      // plastic                                                                                                                               
      if ( sigtrial > 0.0 ) {
        ep += f / E;
      } else {
        ep -= f / E;
      }
    }

    commitStrain = trialStrain;
    commitTangent=trialTangent;
    commitStress = trialStress;

    return 0;
}

int
ElasticPPcpp::revertToLastCommit(void)
{
  trialStrain = commitStrain;
  trialTangent = commitTangent;
  trialStress = commitStress;

  return 0;
}


int
ElasticPPcpp::revertToStart(void)
{
  trialStrain = commitStrain = 0.0;
  trialTangent = commitTangent = E;
  trialStress = commitStress = 0.0;

  ep = 0.0;

  return 0;
}
Methods Dealing With Output

Information is obtained by the user when the print command is invoked by the user and also when the user issues the recorder command. When the print command is invoked the Print method is invoked. This method simply prints information about the element, and then asks the material to do likewise.

void
ElasticPPcpp::Print(OPS_Stream &s, int flag)
{
  s << "ElasticPPcpp tag: " << this->getTag() << endln;
  s << "  E: " << E << endln;
  s << "  ep: " << ep << endln;
  s << "  stress: " << trialStress << " tangent: " << trialTangent << endln;
}

There are two methods used by the element recorders.

  1. The first method, setResponse(), is called when the recorder is created. The element informs the recorder that it can respond to a request of that type, if it cannot respond to the request it returns a 0, otherwise it returns an Response object. The response object includes a pointer to the element, an integer flag used to id the response when the getResponse() method is called, and a Vector detailing the size of the response.
  2. The second method, getReponse(), is called by the recorder when it is actually recording the information.
Methods Dealing With Databases/Parallel Processing

There are two methods provided which are required is the user uses to use the database or parallel procesing features of the OpenSees applications. If neither are to be used, the developer need simply return a negative value in both methods. The idea is that the material must pack up it's information using Vector and ID objects and send it off to a Channel object. On the flip side, the receiving blank element must receive the same Vector and ID data, unpack it and set the variables.


int
ElasticPPcpp::sendSelf(int cTag, Channel &theChannel)
{
  int res = 0;
  static Vector data(9);
  data(0) = this->getTag();
  data(1) = ep;
  data(2) = E;
  data(3) = ezero;
  data(4) = fyp;
  data(5) = fyn;
  data(6) = commitStrain;
  data(7) = commitStress;
  data(8) = commitTangent;

  res = theChannel.sendVector(this->getDbTag(), cTag, data);
  if (res < 0)
    opserr << "ElasticPPcpp::sendSelf() - failed to send data\n";

  return res;
}

int
ElasticPPcpp::recvSelf(int cTag, Channel &theChannel,
                                 FEM_ObjectBroker &theBroker)
{
  int res = 0;
  static Vector data(9);
  res = theChannel.recvVector(this->getDbTag(), cTag, data);
  if (res < 0)
    opserr << "ElasticPPcpp::recvSelf() - failed to recv data\n";
  else {
    this->setTag(data(0));
    ep    = data(1);
    E     = data(2);
    ezero = data(3);
    fyp   = data(4);
    fyn   = data(5);
    commitStrain=data(6);
    commitStress=data(7);
    commitTangent=data(8);
    trialStrain = commitStrain;
    trialTangent = commitTangent;
    trialStress = commitStress;
  }

  return res;
}

External Procedure

This is the all importat extenal procedure that the interpreter will parse when it comes accross your element on the command line. You need to parse the command line, create a material using the command line arguments you parsed and then return this material. The name of the procedure must be OPS_YourClassName (no exceptions). If this procedure is missing or the name is incorrect, your material will fail to load.

NOTE: parsing the command line is easy with some other procedures that are defined in the elementAPI.h file. In the example we show how to get integer and double values from the command line. Other options such as character strings and obtaining the number of input arguments are also available.

The #ifdef stuff at the start is required for different operating systems.

#ifdef _USRDLL
#define OPS_Export extern "C" _declspec(dllexport)
#elif _MACOSX
#define OPS_Export extern "C" __attribute__((visibility("default")))
#else
#define OPS_Export extern "C"
#endif


OPS_Export void *
OPS_ElasticPPcpp()
{
  // print out some KUDO's                                                                                                                     
  if (numElasticPPcpp == 0) {
    opserr << "ElasticPPcpp unaxial material - Written by fmk UC Berkeley Copyright 2008 - Use at your Own Peril\n";
    numElasticPPcpp =1;
  }

  // Pointer to a uniaxial material that will be returned                                                                                      
  UniaxialMaterial *theMaterial = 0;

  //                                                                                                                                           
  // parse the input line for the material parameters                                                                                          
  //                                                                                                                                           

  int    iData[1];
  double dData[2];
  int numData;
  numData = 1;
  if (OPS_GetIntInput(&numData, iData) != 0) {
    opserr << "WARNING invalid uniaxialMaterial ElasticPP tag" << endln;
    return 0;
  }

  numData = 2;
  if (OPS_GetDoubleInput(&numData, dData) != 0) {
    opserr << "WARNING invalid E & ep\n";
    return 0;
  }

  //                                                                                                                                           
  // create a new material                                                                                                                     
  //                                                                                                                                           

  theMaterial = new ElasticPPcpp(iData[0], dData[0], dData[1]);

  if (theMaterial == 0) {
    opserr << "WARNING could not create uniaxialMaterial of type ElasticPPCpp\n";
    return 0;
  }

  // return the material                                                                                                                       
  return theMaterial;
}