OpenSees Cloud

OpenSees AMI

Cargo Cult OpenSeesing

Original Post - 16 Jul 2023 - Michael H. Scott

Visit Structural Analysis Is Simple on Substack.


In recent conversation, a colleague in Eastchester mentioned “cargo cult science”, a term with which I was previously unfamiliar. A cargo cult scientist attempts to produce outcomes without understanding the actual processes that lead to those outcomes.

Although the associated cultural behaviors have been around for centuries, the popular term “cargo cult” was coined during World War II when the US military set up airstrips and cargo facilities on remote South Pacific islands. Cargo cultism is kinda like believing “correlation is causation”, but without understanding what causes what in the causation.

In addition to science, the “cargo cult” label has been applied to many disciplines, often derisively. For example, “cargo cult programming” describes an inexperienced programmer who writes code without taking the time to understand the underlying data structures, memory management, and performance issues of either the code or the language in which they are coding.

Cargo cult programming is similar to copy/paste programming–a legitimate style of programming when credit is given to the original programmer–but misses the important final steps of modification and adaptation for the new use case. The programmer is attempting to “fake it until they make it”.

Cargo cult programming is also in the same ballpark as shotgun programming, where the programmer “shoots first, asks questions later” through vigorous trial and error.

However you triangulate it, cargo cult programming usually results from bad advice or running with the wrong solution to your programming problem–there’s a lot more wrong answers than right answers online.

In OpenSees, a “cargo cult programmer” can also refer to a user who does not take the time to understand the underlying element formulations and analysis theories of their model. This leads to modeling decisions that have no discernible effect on the analysis results. An example I often see is using more than two integration points in the dispBeamColumn element.

Whether a developer or a user, a cargo cult OpenSeeser will leave behind tell-tale signs of coding inefficiency. For a developer, the resulting C++ code can be difficult to maintain and can cause memory leaks. Here are a couple developer examples, stripped of readily identifiable information.

Using an Array for a Single Pointer

A model uses an array of UniaxialMaterial pointers to track fiber stress-strain response. The array is allocated dynamically, getting a copy of an input material.

// Allocation
UniaxialMaterial **materials = new UniaxialMaterial*[Nfibers];
for (int i = 0; i < Nfibers; i++)
   materials[i] = inputMaterial.getCopy();

And the array is deallocated properly.

// Deallocation
for (int i = 0; i < Nfibers; i++)
   delete materials[i];
delete [] materials;

This is all fine coding. But at some point the developers added to the model another mode of deformation that required only one UniaxialMaterial pointer, whose allocation and deallocation was handled via an array, just like the fibers.

// Allocation
UniaxialMaterial **other = new UniaxialMaterial*[1];
for (int i = 0; i < 1; i++)
   other[i] = inputOther.getCopy();

// Deallocation
for (int i = 0; i < 1; i++)
   delete other[i];
delete [] other;

Yeah, the code works, but an array of pointers that always has one entry? You can accomplish the same objective with a single pointer like this.

// Allocation
UniaxialMaterial *other = inputOther.getCopy();

// Dallocation
delete other;

The array of one pointer is not a big deal, but there is a performance hit because the state determination and commits also use a loop to iterate over the one material. While a good compiler might catch the issue and unroll the loop, the code is unnecessarily complicated.

Storing Temporary Variables as Private Data

While I can kinda understand the previous example, another instance of cargo cult OpenSeesing still has me scratching my head. And I’ve seen this implementation practice in the source code of multiple models from different OpenSees programmers.

Suppose a temporary variable, let’s call it c, is required in computing the tangent for Concrete23. So, the developer defined c as private data for the class.

class Concrete23 : public UniaxialMaterial
{
  public:
   Concrete23(int tag, double fc, double epsc, ...);
   //
   // Other methods
   //
  private:
   double fc;
   double epsc;
   // Other private data
   double c;
};

Then, the implementation of getTangent() is the only place c is used.

double
Concrete23::getTangent()
{
   //
   // Do some stuff to compute tangent
   //

   c = 0.5*fc/espc;
   if (tangent < c) {
      opserr << "Spit out a warning" << endln;
      // Or whatever
   }

   return tangent;
}

But c does not need to be private data if its only use is as a temporary local variable in the getTangent() method. Instead you can remove c from the private data in the header file.

  private:
   double fc;
   double epsc;
};

And declare c as a local variable in the definition of getTangent().

   double c = 0.5*fc/espc;
   if (tangent < c) {
      opserr << "Spit out a warning" << endln;
      // Or whatever
   }

What’s the big deal, c is just used as a local variable? Well, private data is persistent over the lifetime of an object. If Concrete23 has 36 different temporary variables declared as private data, and there are 10,000,000 Concrete23 objects instantiated for your IDA of 3D RC frames with fiber sections, you may run out of RAM by draining the heap of 360,000,000 doubles at runtime. When the 36 temporary data variables are defined as local variables, you only increase the static size of the program, determined at compile time, by 36 doubles.



Don’t get me wrong. I have cargo cult programmed. Everyone has.