Dangling Recorders

Because I don’t perform very large or time-consuming OpenSees analyses, I use recorders rather infrequently, instead preferring commands like nodeDisp and eleResponse.

An issue with node and element recorders is they can segmentation fault after the node or element to which they point is removed from a model–a common scenario for progressive collapse simulations.

A potential segmentation fault is not why I don’t use recorders. It’s more that OpenSeesPy mitigates the need to pump data from OpenSees.exe to MATLAB or Excel or whatever for post-processing.

But run the following script and see what happens–hopefully not the blue screen of death.

import openseespy.opensees as ops

ops.wipe()
ops.model('basic','-ndm',1,'-ndf',1)

ops.node(0,0); ops.fix(0,1)
ops.node(1,0)
ops.node(2,0)

ops.uniaxialMaterial('Elastic',1,600)

ops.element('zeroLength',1,0,1,'-mat',1,'-dir',1)
ops.element('zeroLength',2,1,2,'-mat',1,'-dir',1)
ops.element('zeroLength',3,0,2,'-mat',1,'-dir',1)

ops.timeSeries('Linear',1)
ops.pattern('Plain',1,1)
ops.load(1,300)

ops.recorder('Element','-file','ele1force.out','-ele',1,'force')
ops.recorder('Element','-file','ele2force.out','-ele',2,'force')
ops.recorder('Element','-file','ele2defo.out','-ele',2,'deformation')

ops.analysis('Static','-noWarnings')
ops.analyze(1)

ops.remove('element',2)

ops.analyze(1)

After element 2 is removed from the model–and deleted from memory–the recorders that once pointed to element 2 now point to some unused data segment (dangling pointers). Then, when a subsequent analyze command hits the recording phase, there will be a segmentation fault.

Within OpenSees, as far as I know, there is no easy fix to this issue. But fortunately, the recorder command returns a tag, so there are some easy scripting workarounds.

One workaround is to define a dictionary whose keys are element tags and whose values are lists of recorder tags that point to the element.

When you define a recorder, append its tag to the list for that element. Then, when you remove an element, iterate through the list of recorder tags and remove the removed element’s recorders before you go to the next analysis step.

#
# Define your model
#

recorderTags = dict()
for ele in ops.getEleTags():
    recorderTags[ele] = []

recorderTags[1].append(ops.recorder('Element','-file','ele1force.out','-ele',1,'force'))
recorderTags[2].append(ops.recorder('Element','-file','ele2force.out','-ele',2,'force'))
recorderTags[2].append(ops.recorder('Element','-file','ele2defo.out','-ele',2,'deformation'))

ops.analysis('Static','-noWarnings')
ops.analyze(1)

ops.remove('element',2)
for recorder in recorderTags[2]:
    ops.remove('recorder',recorder)

ops.analyze(1)

Other workarounds are possible once you are aware that the recorder command returns a tag. And not just element recorders, node recorders also return a tag. Envelope recorders too. All recorders return a tag.

5 thoughts on “Dangling Recorders

  1. Great post as always!

    A possible solution on the opensees side would be to call the domainChanged() function on the recorders (node recorder overloads it, but does nothing, element recorder doesn’t overload it) and then check that the pointers to the nodes/elements are still valid, setting them to zero otherwise. No need to remove the tag from the list of nodes/elements as the object could re-appear in the future (e.g. staged construction analysis for some geotech applications), although there is an argument to be made to removing it from that list too.

    A more modern idea would be to use weak pointers from the recorder class. Though this entails using smart pointers which would require a larger overhaul elsewhere in the code.

    Saludos,

    Liked by 2 people

    1. Thank you, Jose! domainChanged() is a good idea, but it’s still not clear how a recorder would know that the element or node it points to has been deleted. The node or element would have to pass a message to the recorder before they are deleted. Or there’s some mapping of recorders to domain objects, but that gets messy.

      Like

  2. Nice post! I remember we had that issue with one of my students a while ago. I’m curious, why is that you mention that recorders are preferred over output commands for large analyses?. Two years ago when we started a project we performed some testing about recorders vs output commands and found that the latter speed up the analyses, particularly when you have mechanical drives. Moreover, if your PC happens to have some of today’s cheap SSD drives, I found that their endurance may be compromised if used frequently for analyses that use recorders.

    The solution we found for OpenSeesPy was storing the data in serialized files (pickle, feather or parquet). As long as you have enough RAM, you can create the file, and if you don’t have enough, you can split the results in multiple files. The advantage of this process is that results are stored in RAM while running and you avoid the slowest process in a PC, which is writing data to the storage drive.

    Like

  3. I believe the “recorder tag” is not “cleared” using ops.wipe().
    I performed an analysis with three recorders, and it returned tag=1, tag=2 and tag=3.

    I ran the analysis again (keeping ops.wipe() at the beginning of the code), and it returned tag=4, tag=5 and tag=6.

    Try running this code twice.
    The printed tags will be different.

    import openseespy.opensees as ops

    ops.wipe()
    ops.model(‘basic’,’-ndm’,1,’-ndf’,1)

    ops.node(0,0); ops.fix(0,1)
    ops.node(1,0)
    ops.node(2,0)

    ops.uniaxialMaterial(‘Elastic’,1,600)

    ops.element(‘zeroLength’,1,0,1,’-mat’,1,’-dir’,1)
    ops.element(‘zeroLength’,2,1,2,’-mat’,1,’-dir’,1)
    ops.element(‘zeroLength’,3,0,2,’-mat’,1,’-dir’,1)

    ops.timeSeries(‘Linear’,1)
    ops.pattern(‘Plain’,1,1)
    ops.load(1,300)

    recorderTags = dict()
    for ele in ops.getEleTags():
    recorderTags[ele] = []

    recorderTags[1].append(ops.recorder(‘Element’,’-file’,’ele1force.out’,’-ele’,1,’force’))
    recorderTags[2].append(ops.recorder(‘Element’,’-file’,’ele2force.out’,’-ele’,2,’force’))
    recorderTags[2].append(ops.recorder(‘Element’,’-file’,’ele2defo.out’,’-ele’,2,’deformation’))

    print(recorderTags[1])
    print(recorderTags[2])

    ops.analysis(‘Static’,’-noWarnings’)
    ops.analyze(1)

    Like

Leave a reply to Jose A. Abell M. Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.