Engineering Notes

Lesson learned OpenSees N.º 07

My OpenSees database would not reproduce: the material remembered the previous model

Abstract. I reran 20 analyses with the same code and the same inputs, and none matched. The cause was a material that keeps memory between models when they run in the same Python process. The fix is one line.

Follow a recipe to the letter twice and you expect the same cake. Computation works the same way: same code, same inputs, same result. Everything else rests on that. If you cannot repeat an analysis, you cannot check it, fix it, or defend it to a reviewer.

That is why I was so uneasy about what I found in a database of several hundred nonlinear dynamic analyses in OpenSees, using a concrete material that cracks. To check one result I reran 20 cases as they were. None matched the first run. Most moved by a couple of percent, but one changed by almost 90% and another went from “no damage” to “cracked”.

In this note I tell how I looked for the cause, what it was, and how to fix it. If you run many models in parallel with Python, it may be happening to you without you knowing.

Finding the cause by elimination

When something does not repeat, the temptation is to blame “numerical noise” and move on. I preferred to rule out causes one at a time, each with its own check script:

  1. Inputs: ground motions and parameters were bit-identical.
  2. Mesh and materials: the vibration period matched to the last digit.
  3. Code: identical to the version that built the database.
  4. Libraries: same OpenSeesPy and NumPy versions.
  5. Numerical chaos: changing an input by one part in a billion changed the result by the same tiny amount. The problem did not amplify small differences.
  6. Randomness: the same case, run twice in a row, gave exactly the same thing.
  7. Leftovers from the previous model after ops.wipe(): none, or so it seemed.
  8. Convergence tolerance: tested separately.

The test that explained it

I ran one case three ways:

How I ran itResult
Alone, in a fresh Python processreference
After another model, same process≈ 6% higher
Twice in a row, aloneidentical

There it was. The result depended on which model had run before in the same process. The original database was built with 14 parallel processes, each with about 57 cases in line. Every result depended, without anyone knowing, on its place in line.

Not the tolerance

My first suspect was the convergence tolerance. I tightened it a hundred times, then ten thousand times. The gap between the two versions did not move: still almost 6%, and each version was stable to the ninth decimal.

So there was no “good” result and “bad” result. There were two well-solved answers to two different problems. Something in the material survived from one model to the next, despite ops.wipe(). One more hint: the material prints its banner only once per process, even if you build a hundred models.

The fix

Give each model a fresh process with no memory:

from multiprocessing import Pool

with Pool(processes=14, maxtasksperchild=1) as pool:
    results = pool.map(run_case, cases)

maxtasksperchild=1 tells Python to close each process after one case and open a clean one for the next. For one-off checks I run each case in a subprocess. Opening processes costs some time; in exchange, every row in the database can be repeated.

What I do now

  • Before using a database, I rerun 10 to 20 cases in clean processes and compare.
  • I store library versions and the exact command next to the results.
  • If a material or element holds global state, each model gets its own process.

“Same code and same inputs” is not enough. It also has to start from scratch.

About the author

Yordan Rocio Maldonado

Structural engineer, seismic and bridge specialist. CIP 215845

Educational, reference-only content. Opinions are my own and do not represent any employer. On a real project, the engineer of record and the governing code decide.

← Back to notes