Add A New Recorder: Difference between revisions

From OpenSeesWiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 4: Line 4:
=== Recorder Class ===
=== Recorder Class ===


The Recorder class itself is an abstract base class. It inherits from both the tagged object class and the MovableObject 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 class has a minimal interface, which is as shown below:



Revision as of 00:35, 21 May 2010

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.

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 the 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:
    SumElementForcesRecorder();
    SumElementForcesRecorder(const ID eleID,
                             bool echoTime,
                             OPS_Stream *theOutputHandler);

    ~SumElementForcesRecorder();

    int record(int commitTag, double timeStamp);
    int restart(void);
    int domainChanged(void);
    int setDomain(Domain &theDomain);

    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 defines the class SumElementForceRecorder 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. Their is no protected data or methods. 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.

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;
}