pyyeti icon indicating copy to clipboard operation
pyyeti copied to clipboard

Reading OP2 files

Open djagodzinski opened this issue 3 months ago • 6 comments

Hi @twmacro,

I am having trouble reading in data from OP2 files, such as elastic strain energy (EKE). It appears pyyeti has functionality built in, where I could read this data without having to use the DMAP. Does pyyeti support reading in generic data from OP2 files without the DMAP? Below is a summary of what I am seeing.

>>> o2 = op2.OP2("<file path>.op2")
>>> print(o2.dbdct.keys())
dict_keys(['PVT0', 'CASECC', 'GEOM1S', 'GEOM2S', 'GEOM4S', 'EPTS', 'MPTS', 'DYNAMICS', 'BGPDTS', 'EQEXINS', 'OGPWG', 'EFMFSMS', 'EFMASSS', 'RBMASSS', 'EFMFACS', 'MPFACS', 'MEFMASS', 'MEFWTS', 'LAMA', 'OUGV1', 'ONRGY1', 'OEKE1', 'ONRGY2'])
>>>
>>> o2.set_position('ONRGY1')
45800
>>> name, trailer, rectype = o2.rdop2nt()
>>> temp = o2.rdop2matrix(trailer)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<...>\venv\Lib\site-packages\pyyeti\nastran\op2.py", line 559, in rdop2matrix
    matrix[r : r + n, col] = struct.unpack(
    ~~~~~~^^^^^^^^^^^^^^^^
ValueError: could not broadcast input array from shape (72,) into shape (0,)

I can see why rdop2matrix() gets tripped up because the trailer entry has a 0 for its 3rd entry (index 2).

>>> o2.dbdct['ONRGY1'][0]         
namespace(name='ONRGY1', start=45800, stop=58880, nbytes=13079, dbtype=0, size=(0, 0), trailer=(101, 10, 0, 0, 0, 2, 0), headers=[[(22, 18, 1197994693), 584], [(1012, 1150488744, 1073585648), 592], [(22, 18, 1212777119), 584], [(1012, 1167000431, 1074655500), 592], [(22, 18, 1212777119), 584], [(1012, 1176212073, 1083860973), 592], [(22, 18, 1217449303), 584], [(1012, 1166939440, 1069825658), 592], [(22, 18, 1231060748), 584], [(1012, 1190678199, 1079850632), 592], [(22, 18, 1233267819), 584], [(1012, 1195726125, 1083676561), 592], [(22, 18, 1236752431), 584], [(1012, 1191443824, 1074789111), 592], [(22, 18, 1236752431), 584], [(1012, 1200825604, 1084261114), 592], [(22, 18, 1238470447), 584], [(1012, 1187995039, 1069934606), 592], [(22, 18, 1238470447), 584], [(1012, 1192067592, 1074204042), 592]])

Thank you, Daniel

djagodzinski avatar Sep 18 '25 19:09 djagodzinski

Hi @djagodzinski!

I'll happily look into this when I get back from vacation. I'm sure I'll have some questions for you, but I'll poke around a little first and get back to you.

Cheers, Tim

twmacro avatar Sep 24 '25 22:09 twmacro

Sounds good. Here is an OP2 file of a small example, if that helps: https://drive.google.com/file/d/1XVPV4aLPtrFYV9eryuBQP4Nw69-qlNzI/view?usp=sharing

Thanks! -Daniel

djagodzinski avatar Sep 26 '25 16:09 djagodzinski

Hi Daniel!

That op2 file was very helpful for me to understand the format of this data table. I wrote the following little script that reads the relevant data -- at least, what I assumed was most relevant :-) -- into a dictionary of Pandas DataFrames. Here is the script so you can test it out:

import sys
import numpy as np
import pandas as pd
from pyyeti.nastran.op2 import OP2


if __name__ == "__main__":

    dbname = "ONRGY1"
    # dbname = "ONRGY2"

    with OP2("modes.op2") as self:
        self.prtdir()  # temp
        try:
            self.set_position(dbname)
        except KeyError:
            sys.exit()
            # return None
        self.rdop2nt()
        output = {}

        while True:
            ident = self.rdop2record(form="int")
            if ident is None:
                break
            data = self.rdop2record(form="single")
            acode = ident[0] // 10
            tcode = ident[1] // 1000
            fcode = ident[8]
            numwde = ident[9]
            assert data.shape[0] % numwde == 0

            if acode != 2 or tcode != 0 or fcode != 1:
                msg = "Unsupported data format in ONRGY[12]: {}"
                raise ValueError(msg.format((tcode, acode, fcode)))

            mode_num = ident[4]
            n_elements = data.shape[0] // numwde
            element_ids = np.frombuffer(data, self._endian + "i4")[::numwde] // 10
            data.shape = (n_elements, numwde)
            strains = data[:, 1:]
            output[mode_num] = pd.DataFrame(
                strains, index=element_ids, columns=["ESE", "%TSE", "EED"]
            )
        # return output

Please let me know if that gives you good, usable data or if we need to do something else. I also have a couple questions to help me understand better:

  1. What are these two data blocks? Especially ONRGY2.
  2. How was this data requested? Mainly, I'm wondering if this output should be added to rdparampost or if it should be a separate standalone routine.

Thank you! Tim

twmacro avatar Sep 27 '25 23:09 twmacro

Hi @twmacro,

Thanks for the quick turnaround. I'll give this a try later today but wanted to answer your questions first:

  1. ONRGY1 is strain energy and ONRGY2 is kinetic energy (these were output from a SOL 103 run, i.e., normal modes)
  2. The data was written by specifying ESE and EKE in the Control block, so I don't think it should be part of the rdparampost routine since this data should be written to another .op2 file other than the one written by PARAM,POST,-1.

Thanks again, Daniel

djagodzinski avatar Sep 28 '25 02:09 djagodzinski

Hi @twmacro,

Finally had the chance to try out your code and it is returning what I am after :). In general, it seems you'd have to understand the structure of the data you want to pull in order to use those methods properly. Is there some kind of reference that goes over things like how to properly use form= in rdop2record() or np.frombuffer()? I wanted to see if I could extract complex responses from a SOL 111 run.

Thank you, Daniel

djagodzinski avatar Sep 28 '25 23:09 djagodzinski

Hi @djagodzinski!

Awesome! The primary source of info I use regularly for understanding the format of data blocks in the OP2 format is the NDDL listing from a Nastran run. I use something like this to get the listing:

NASTRAN NLINES=99999
ACQUIRE NDDL $
COMPILE NDDL=NDDL,SOUIN=MSCSOU,LIST,NOREF,NODECK
CEND
BEGIN BULK
ENDDATA

I hope this helps, and keep me posted! Let me know too if you develop some code that would be good to be part of pyYeti! :)

Regards, Tim

twmacro avatar Oct 01 '25 14:10 twmacro