OpenSees Cloud

OpenSees AMI

Set and Get Concrete23 Response

Original Post - 13 Oct 2024 - Michael H. Scott

Visit Structural Analysis Is Simple on Substack.


When I wrote Concrete23, I copied Concrete01 then tweaked the unloading and reloading rules. I also added a couple of bells and whistles and it was music to my ears. But I wanted to record those sounds during an analysis.

Recorders in OpenSees use the setResponse and getResponse methods to identify and obtain element, section, and material response. Admittedly, working with setResponse and getResponse is a little convoluted, but it beats polluting interfaces with dozens of methods like getRing(), getDing(), and getDong() for all the bells in all the element, section, and material models.

This post describes the preferred approach to record the sounds of Concrete23 without having to pollute an interface or unnecessarily duplicate code. The focus is UniaxialMaterial, but the same ideas apply to recorders for Element and SectionForceDeformation objects.

First, the base UniaxialMaterial class implements setResponse and getResponse for common response quantities like stress and tangent as well as plastic strain. Below is an abridged implementation of the base setResponse method, which doesn’t really set anything, but is more of an identify and set up operation.

Response *
UniaxialMaterial::setResponse(const char **argv, int argc,
			                  OPS_Stream &theOutput)
{
   if (argc < 1)
      return 0;

   if (strcmp(argv[0],"stress") == 0)
      return new MaterialResponse(this, 1, 0.0);
   if (strcmp(argv[0],"tangent") == 0)
      return new MaterialResponse(this, 2, 0.0);
   if (strcmp(argv[0],"plasticStrain") == 0)
      return new MaterialResponse(this, 3, 0.0);

   return 0;
}

The MaterialResponse object takes a pointer to the material object (this), a unique integer identifier, and a data type–here just passing a double because stress, tangent, and plastic strain are scalars. The value of the double passed to the MaterialResponse constructor doesn’t matter–the constructor just needs to know the size and type of data to be recorded.

And don’t worry, we’re not going to leak memory with the new MaterialResponse statements–the calling object will invoke delete.

The corresponding abridged implementation of getResponse is shown below.

int
UniaxialMaterial::getResponse(int responseID, Information &matInfo)
{
   if (responseID == 1) {
      matInfo.setDouble(this->getStress());
      return 0;
   }
   if (responseID == 2) {
      matInfo.setDouble(this->getTangent());
      return 0;
   }
   if (responseID == 3) {
      double strain = this->getStrain();
      double stress = this->getStress();
      double kinit = this->getInitialTangent();
      // elastic unloading
      matInfo.setDouble(strain-stress/kinit);
      return 0;
   }

   return 0;
}

Note that the plastic strain is approximated by elastic unloading, subtracting the elastic component of strain from the total strain. Most implementations of UniaxialMaterial do a better job of tracking plastic strain, but this base class recorder option is adequate.

Moving on, the subclass can leverage the base class setResponse and getResponse methods. First check for ring, ding, and dong (the special sounds of Concrete23) then invoke setResponse on the base class if nothing hits.

Response *
Concrete23::setResponse(const char **argv, int argc,
			            OPS_Stream &theOutput)
{
   if (argc < 1)
      return 0;

   if (strcmp(argv[0],"ring") == 0)
      return new MaterialResponse(this, 100, 0.0);
   if (strcmp(argv[0],"ding") == 0)
      return new MaterialResponse(this, 101, 0.0);
   if (strcmp(argv[0],"dong") == 0)
      return new MaterialResponse(this, 102, 0.0);

   return UniaxialMaterial::setResponse(argv, argc, theOutput);
}

The base UniaxialMaterial implementation of setResponse uses identifiers 1, 2, 3…, thus the 101, 102, 103 in Concrete23. I should change the base class to use negative identifiers to mitigate conflicts. The identifiers can be the same between Concrete23 and Concrete24, but have to be different from the base class. This approach is not quite as dumb as the combination to Lord Helmet’s luggage, but look at the identifiers in setResponse of UniaxialMaterial before you implement recorders for your materials.

For the subclass getResponse, check the identifiers then pass ring, ding, or dong (private data of Concrete23) to the Information object. If none of the identifiers match, call the base class getResponse.

int
Concrete23::getResponse(int responseID, Information &matInfo)
{
   if (responseID == 100) {
      matInfo.setDouble(ring);
      return 0;
   }
   if (responseID == 101) {
      matInfo.setDouble(ding);
      return 0;
   }
   if (responseID == 102) {
      matInfo.setDouble(dong);
      return 0;
   }

   return UniaxialMaterial::getResponse(responseID, matInfo);
}

Depending on how you implemented your material, you can also override the default setResponse and getResponse methods for stress, tangent, or plastic strain. These responses may be cached as private variables and the default behavior of calling getStress and getTangent may launch state determination that unnecessarily slows down the simulation, e.g., if your return mapping algorithm is implemented in getStress.

Or perhaps you have the true plastic strain and don’t want to record the elastic unloading approximation implemented in the base class. In this case, add another if-statement in the subclass setResponse to catch “plasticStrain” before the base class setResponse is called, give an integer identifier that makes sense for your subclass, then record the plastic strain in your getResponse before the base class getResponse is called.