Add A New Recorder

From OpenSeesWiki
Jump to navigation Jump to search

A Recorder in the interpreted OpenSees applications is used to obtain information from the model during the analysis. To add a new Recorder option into the interpreted applications, the developer must provide a new C++ subclass of the Recorder class and an interface function that will be used to parse the input and create the new recorder.

Recorder Class

The Recorder class itself is an abstract base class. It inherits from both the TaggedObject class and the MovableObject class. The class has a minimal interface, which is as shown below:

The Recorder Class:

class Recorder: public MovableObject, public TaggedObject
{
  public:
    Recorder(int classTag);
    virtual ~Recorder();

    virtual int record(int commitTag, double timeStamp) =0;

    virtual int restart(void);
    virtual int domainChanged(void);
    virtual int setDomain(Domain &theDomain);
    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);


  protected:
  protected:

  private:
    static int lastRecorderTag;
};

The most important methods in the interface are:

  1. setDomain() - this is the method that is called when the new recorder object is first added to the domain. It is inside this method that all data, typically memory and pointer values, need to be initialized for subsequent record commands.
  2. record() - this is the method that is called when the recorder is called upon to record/save information. The method is called with a tag that will be unique and the current time in the domain.

Other Important methods are:

  1. domainChanged() - this is a method called when something major has happened in the Domain, ie. a new element, node, constraint and/or load pattern has been added to the domain or removed from the domain. It is necessasry for the Recorder to check in this call if it's pointers are still valid (i.e. if an element it was recording info for has been removed from the domain, it wuill have been deleted and it's old pointer information will no longer be valid.)
  2. send/recvSelf() - are two methods called in parallel applications. When invoked the recorders send/recv information about what they are recording.
  3. restart() - this method is called if restart() is invoked on the Domain. What the recorder does is up to you the developer.


Example - SumElementForcesRecorder

In the following section we will provide all necessary code to add a new recorder. The purpose of this recorder will be to sum the forces obtained from the list of inputted elements. The recorder will use the getResistingForce() method in the elements to obtain these forces. A similar class exists in the framework, which uses the setResponse()/getResponse() methods in the element interface. To demonstrate some of the output file options, the result will go to either the screen, a text file, or a binary file. More output options are of course available and the developer should look at existing recorder options.


Header

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

#ifndef SumElementForcesRecorder_h
#define SumElementForcesRecorder_h
                                                                         
#include <Recorder.h>
#include <Information.h>
#include <ID.h>

class Domain;
class Vector;
class Matrix;
class Element;
class Response;
class FE_Datastore;

class SumElementForcesRecorder: public Recorder
{
  public:
    // constructors
    SumElementForcesRecorder();
    SumElementForcesRecorder(const ID eleID,
                             bool echoTime,
                             OPS_Stream *theOutputHandler);
    // destructors
    ~SumElementForcesRecorder();

    // public methods
    int record(int commitTag, double timeStamp);
    int restart(void);
    int domainChanged(void);
    int setDomain(Domain &theDomain);
    const char *getClassType(void) const;
    int sendSelf(int commitTag, Channel &theChannel);
    int recvSelf(int commitTag, Channel &theChannel, FEM_ObjectBroker &theBroker);

  protected:

  private:
    int numEle;           // the number of elements                                                                              
    Element **theElements;// pointer to array of element pointers                                                                
    ID eleID;             // ID (integer list) of element tags to record                                                          

    Domain *theDomain;    // pointer to domain holding elements                                                                  
    OPS_Stream *theOutput;// pointer to output location                                                                          
    bool echoTimeFlag;    // flag indicating if pseudo time to be printed                                                        
    Vector *data;         // Vector (double array) to store sum of element forces                                                
};


#endif

The header file defines the interface and variables for the class SumElementForceRecorder. It defines the new class to be a sublass of the Recorder class. In the public interface are 2 constructors and 1 destructor in addition to all the methods defined for the Recorder class. There are no protected data or methods as we do not expect this class to be subclassed. In the private section we store data that will be used by the SumElementForceRecorder objects. 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 (Domain, Vector, Element, OPS_Stream) 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

It another file, SumElementForcesRecorder.cpp, we place the code that details what the constructors, destructor and methods do. In addition we provide one additional procedure OPS_SumElementForcesRecorder() (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 "SumElementForcesRecorder.h"
#include <elementAPI.h>

#include <Domain.h>
#include <Element.h>
#include <ElementIter.h>
#include <Matrix.h>
#include <Vector.h>
#include <ID.h>
#include <string.h>
#include <Response.h>
#include <Message.h>
#include <Channel.h>
#include <FEM_ObjectBroker.h>

#include <StandardStream.h>
#include <BinaryFileStream.h>
#include <DataFileStream.h>

#include <elementAPI.h>


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.

SumElementForcesRecorder::SumElementForcesRecorder()
:Recorder(-1),
 numEle(0), theElements(0), eleID(0),
 theDomain(0), theOutput(0),
 echoTimeFlag(true), data(0)
{

}

SumElementForcesRecorder::SumElementForcesRecorder(const ID ele,
                                                   bool echoTime,
                                                   OPS_Stream *theoutput)
 :Recorder(-1),
 numEle(0), theElements(0), eleID(ele),
 theDomain(0), theOutput(theoutput),
 echoTimeFlag(echoTime), data(0)
{
  // set numEle                                                                                                                  
  numEle = eleID.Size();
  if (numEle == 0) {
    opserr << "WARNING SumElementForcesRecorder::SumElementForcesRecorder() - no elements tags passed in input!\n";
  }
}


Destructor

The we provide the destructor. In the destructor all memory that the Recorder created or was passed to it in the constructor must be destroyed. Failing to delete this memory, will result in memory leaks.

SumElementForcesRecorder::~SumElementForcesRecorder()
{
  if (theElements != 0)
    delete [] theElements;

  if (data != 0)
    delete data;

  if (theOutput != 0)
    delete theOutput;
}


record() Method

After the destructor, we provide the code for the record() method. It does the following operations:

  1. Zeros the vector which will contain the final sum
  2. If the time stamp is needed, it places it at the first location in the vector.
  3. Loops over all valid elements adding their resting force to the vector.
  4. Send the vector to the output handler to be written.
  5. Returns success.
int
SumElementForcesRecorder::record(int commitTag, double timeStamp)
{
  // check for initialization                                                                                                    
  if (data == 0) {
    opserr << "SumElementForcesRecorder::record() - setDomain() has not been called\n";
    return -1;
  }

  // zero the data vector                                                                                                        
  data->Zero();

  int forceSize = data->Size();
  int startLoc = 0;


  // write the time if echTimeFlag set                                                                                           
  if (echoTimeFlag == true) {
    (*data)(0) = timeStamp;
    forceSize -= 1;
    startLoc = 1;
  }

  //                                                                                                                             
  // for each element that has been added to theElements add force contribution                                                  
  //                                                                                                                             

  for (int i=0; i< numEle; i++) {
    if (theElements[i] != 0) {
      int loc = startLoc;
      const Vector &force = theElements[i]->getResistingForce();
      int forceSize = force.Size();
      for (int j=0; j<forceSize; j++, loc++)
          (*data)(loc) += force(j);
    }
  }

  //                                                                                                                             
  // send the response vector to the output handler for o/p                                                                      
  //                                                                                                                             

  if (theOutput != 0)
    theOutput->write(*data);

  // succesfull completion - return 0                                                                                            
  return 0;
}


restart() and domainChanged() methods

Afte the record() method, we have the two simple short methods restart() and domainChanged(). restart does nothing and domainChanged simply calls the objects own setDomain() method.

int
SumElementForcesRecorder::restart(void)
{
  return 0;
}

int
SumElementForcesRecorder::domainChanged(void)
{
  if (theDomain != 0)
    this->setDomain(*theDomain);
}


setDomain() Method

The setDomain() method follows. In this method we perform the following:

  1. set the pointer for the enclosing domain object.
  2. allocate space from memoory for our array of ponters and our data vector.
  3. initialize the array components to be 0 or point to an element given by the eleID.
  4. determine the size of the vector that will be used to store the sum of the forces.
  5. allocate space for the vector.
int
SumElementForcesRecorder::setDomain(Domain &theDom)
{
  theDomain = &theDom;

  // set numEle                                                                                                                  
  if (numEle == 0) {
    opserr << "WARNING SumElementForcesRecorder::initialize() - no elements tags passed in input!\n";
    return 0;
  }

  // create theElements, an array of pointers to elements                                                                        
  theElements = new Element *[numEle];
  if (theElements == 0) {
    opserr << "WARNING SumElementForcesRecorder::initialize() - out of memory\n";
    numEle = 0; // set numEle = 0, in case record() still called                                                                 
    return -1;
  }

  //                                                                                                                             
  // loop over the list of elements,                                                                                             
  //    if element exists add it's pointer o the array                                                                           
  //    get its resisting force, check size to determine compatable with others                                                  
  //                                                                                                                             

  int sizeArray = -1;

  for (int i=0; i<numEle; i++) {
    int eleTag = eleID(i);
    Element *theEle = theDomain->getElement(eleTag);

    if (theEle != 0) {

      const Vector &force = theEle->getResistingForce();
      int forceSize = force.Size();
      if (sizeArray == -1) {
        sizeArray = forceSize;
        theElements[i] = theEle;
      } else if (sizeArray != forceSize) {
        opserr << "WARNING: forces mismatch - element: " << eleTag << " will not be included\n";
        theElements[i] = 0;
      } else {
        theElements[i] = theEle;
      }
    } else {
      theElements[i] = 0;
    }
  }

  // if echTimeFlag is set, add room for the time to be output                                                                   
  if (echoTimeFlag == true)
    sizeArray++;

  // create the vector to hold the data                                                                                          
  data = new Vector(sizeArray);

  if (data == 0 || data->Size() != sizeArray) {
    opserr << "SumElementForcesRecorder::initialize() - out of memory\n";
    delete [] theElements;
    theElements = 0;
    numEle = 0;
  }

  return 0;
}


sendSelf() and recvSelf() methods

These methods only need be provided if the object will be used in a parallel program. We provide their implementation for completeness, though typicall developers are interested in running the code in a sequential application and should just return -1.

static char myClassType[] = {"SumElementForcesRecorder"};

const char *
SumElementForcesRecorder::getClassType(void) const
{
  return myClassType;
}

int
SumElementForcesRecorder::sendSelf(int commitTag, Channel &theChannel)
{
  // send in an ID (integar array) to the receiving object the following:                                                        
  //   recorder tag                                                                                                              
  //   size of eleID                                                                                                             
  //   class tag of handler                                                                                                      
  //   echoTimeFlag                                                                                                              

  static ID idData(5);
  idData(0) = this->getTag();;
  idData(1) = eleID.Size();
  idData(2) = theOutput->getClassTag();
  if (echoTimeFlag == true)
    idData(3) = 1;
  else
    idData(3) = 0;

  if (theChannel.sendID(0, commitTag, idData) < 0) {
    opserr << "SumElementForcesRecorder::recvSelf() - failed to recv idData\n";
    return -1;
  }

  // send eleID  to receiving object                                                                                             
  if (theChannel.sendID(0, commitTag, eleID) < 0) {
    opserr << "SumElementForcesRecorder::sendSelf() - failed to send idData\n";
    return -1;
  }

  // send theOutput to receiving object                                                                                          
  if (theOutput->sendSelf(commitTag, theChannel) < 0) {
    opserr << "SumElementForcesRecorder::sendSelf() - failed to send theOutput\n";
    return -1;
  }

  return 0;
}
int
SumElementForcesRecorder::recvSelf(int commitTag, Channel &theChannel, FEM_ObjectBroker &theBroker)
{
  // receive from the sending object the ID                                                                                      
  static ID idData(5);
  if (theChannel.recvID(0, commitTag, idData) < 0) {
    opserr << "SumElementForcesRecorder::recvSelf() - failed to recv idData\n";
    return -1;
  }

  // with the data received                                                                                                      
  //   setTag                                                                                                                    
  //   resize the eleID array                                                                                                    
  //   set echoTimeFlag                                                                                                          
  //   get an outputHandler                                                                                                      

  this->setTag(idData(0));
  eleID.resize(idData(1));
  idData(2) = theOutput->getClassTag();
  if (idData(3)  == 0)
    echoTimeFlag = true;
  else
    echoTimeFlag = false;

  if (theOutput != 0 && theOutput->getClassTag() != idData(4))
    delete theOutput;

  theOutput = theBroker.getPtrNewStream(idData(4));
  if (theOutput == 0) {
    opserr << "SumElementForcesRecorder::recvSelf() - failed to get Output of correct type\n";
    return -1;
  }

  // receive eleID                                                                                                               
  if (theChannel.recvID(0, commitTag, eleID) < 0) {
    opserr << "SumElementForcesRecorder::recvSelf() - failed to recv eleID\n";
    return -1;
  }

  // get theOutput to receive data                                                                                               
  if (theOutput->recvSelf(commitTag, theChannel, theBroker) < 0) {
    opserr << "SumElementForcesRecorder::sendSelf() - failed to send theOutput\n";
    return -1;
  }


  return 0;
}


Interface Function

At the end of the implementation file is the interface function. This function is required by all new classes. It is a function which will use the api to

  1. parse the input
  2. based on the input create objects
  3. create a recorder object of the correct type, and return it to the calling function.

The interface function is the function that is called when the interpreter comes across the command telling it to create a SumElementForcesRecorder.

#ifdef _USRDLL
#include <windows.h>
#define OPS_Export extern "C" _declspec(dllexport)
#elif _MACOSX
#define OPS_Export extern "C" __attribute__((visibility("default")))
#else
#define OPS_Export extern "C"
#endif

static int numSumElementForcesREcorder = 0;

OPS_Export void *
OPS_SumElementForcesRecorder()
{
  Recorder *theRecorder = 0;

  int numRemainingArgs = OPS_GetNumRemainingInputArgs();

  // check for quick return, possibly parallel case                                                                 
  if (numRemainingArgs == 0) {
    Recorder *theRecorder = new SumElementForcesRecorder();
  }

  //                                                                                                                
  // parse args                                                                                                     
  //                                                                                                                

  int numEle = 0, eleTag;
  ID eleID(0);

  OPS_Stream *theOutputStream = 0;
  int outMode = 0;    // standard stream                                                                            
  bool echoTime = false;

  bool doneParsingArgs = false;
  char data[100];
  char outputName[200];
  char **eleArgs = 0;
  int numEleArgs = 0;
  while (numRemainingArgs > 0) {
    if (OPS_GetString(data,100) < 0)
      return 0;

    // output to standard file                                                                                      
    if (strcmp(data,"-file") == 0)  {
      outMode = 1;
      if (OPS_GetString(outputName,200) < 0)
        return 0;
      numRemainingArgs -= 2;
    }

    // output to binary file                                                                                        
    else if (strcmp(data,"-binary") == 0) {
      outMode = 2;
      if (OPS_GetString(outputName,200) < 0)
        return 0;
      numRemainingArgs -= 2;
    }

    // echo domain time stamp in output                                                                             
    else if (strcmp(data,"-time") == 0) {
      echoTime = true;
      numRemainingArgs -= 1;
    }

    // read the list of elements & place in an ID                                                                   
    else if ((strcmp(data,"-ele") == 0) ||
             (strcmp(data,"-eles") == 0) ||
             (strcmp(data,"-element") == 0)) {

      numRemainingArgs --;
      int one = 1;
      while (numRemainingArgs > 0 && OPS_GetIntInput(&one, &eleTag) == 0) {
        eleID[numEle] = eleTag;
        numEle++;
        numRemainingArgs--;
      }
      doneParsingArgs = true;
    }

 //                                                                                                                
  // create the output handler                                                                                      
  //                                                                                                                

  if (outMode == 0)
    theOutputStream = new StandardStream();
  if (outMode == 1)
    theOutputStream = new DataFileStream(outputName);
  else if (outMode == 2)
    theOutputStream = new BinaryFileStream(outputName);

  //                                                                                                                
  // create the recorder                                                                                            
  //                                                                                                                

  theRecorder = new SumElementForcesRecorder(eleID,
                                             echoTime,
                                             theOutputStream);

  // return it                                                                                                      
  return theRecorder;
}


Example Script

(OpenSeesDeveloper/recorder/example1.tcl)

An example OpenSees tcl input file for this new recorder is:

#create the model                                                                                    
model basic -ndm 2 -ndf 2                                                                     
node 1   0.0  0.0
node 2 144.0  0.0
node 3 168.0  0.0
node 4  72.0 96.0                                                      
uniaxialMaterial Elastic 1 3000                                                
element truss 1 1 4 10.0 1
element truss 2 2 4 5.0 1
element truss 3 3 4 5.0 1                                              
fix 1 1 1
fix 2 1 1
fix 3 1 1

pattern Plain 1 Linear {
    # apply the load - command: load nodeID xForce yForce                                                           
    load 4 100 -50
}

# Create the analysis                                                                      
system ProfileSPD
constraints Plain
integrator LoadControl 1.0
algorithm Linear
numberer RCM
analysis Static                                                  
recorder Element -file a.out -time -ele 1 2 3  forces
recorder SumElementForcesRecorder -file b.out -time -ele 1 2 3                                                   

# perform the analysis                                                                                              
analyze 10


Example Output

The output shows that the model is in equilibrium, and that at node 4 the node the element resisting forces are equal to the applied forces.

1 -100 50 100 -50
2 -200 100 200 -100
3 -300 150 300 -150
4 -400 200 400 -200
5 -500 250 500 -250
6 -600 300 600 -300
7 -700 350 700 -350
8 -800 400 800 -400
9 -900 450 900 -450
10 -1000 500 1000 -500