Add A New Recorder: Difference between revisions
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 | 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:
- 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.
- 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:
- 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.)
- send/recvSelf() - are two methods called in parallel applications. When invoked the recorders send/recv information about what they are recording.
- 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:
- Zeros the vector which will contain the final sum
- If the time stamp is needed, it places it at the first location in the vector.
- Loops over all valid elements adding their resting force to the vector.
- Send the vector to the output handler to be written.
- 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:
- set the pointer for the enclosing domain object.
- allocate space from memoory for our array of ponters and our data vector.
- initialize the array components to be 0 or point to an element given by the eleID.
- determine the size of the vector that will be used to store the sum of the forces.
- 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;
}