1

i have inp file that needs to read from python data.inp

*Heading
** Job name: inp6_1 Model name: Model-1
*Node
      1,          50.,          20.,          40.
      2,         100.,          20.,          40.
      3,         100.,          20.,           0.
      4,          50.,          20.,           0.
      5,         100.,           0.,          40.
      6,         100.,           0.,           0.
      7,          50.,           0.,           0.
      8,          50.,           0.,          40.
      9,           0.,          40.,          40.
*Element, type=C3D8
  1,  82, 336, 712, 294,   1,  15, 168,  46
  2, 336, 337, 713, 712,  15,  16, 169, 168
  3, 337, 338, 714, 713,  16,  17, 170, 169
*Elset, elset=Set-1, instance=Part-1-1, generate
 321,  951,   10
*End Assembly

the purpose is to store all numbers between "*Node" and "*Element" in inp file. Below is my current code, it is workable, but pretty lengthy because i used with open(filepath, 'r') as file: twice, first time is to get line numer, second time is to read from line and store to numpy array. I tried to put 2 for loops under with, but it is not working where i only get one line of number from inp file.

my working code:

def findNodes(filepath):
    with open(filepath, 'r') as file:
        for num, line in enumerate(file,1):
            if '*Node' in line:
                nodeLineStart = num
            if '*Element' in line:
                nodeLineEnd = num

    xx = np.empty(shape=[1, 4])
    with open(filepath, 'r') as file:
        for num, lin in enumerate(file, 1):
            if nodeLineStart+1 <= num <= nodeLineEnd-1:
                text = lin.replace(" ", "")
                lines = np.genfromtxt(StringIO(text), delimiter=",").reshape(1, 4)
                xx = np.append(xx, lines, axis=0)
    return xx

ndarray = findNodes('data.inp')
3
  • once you know the start and stop lines you should be able to read them with one genfromtxt call. Check its docs. You could also usw readlines to read the whole thing. Just give genfromtxt a slice of that list. Commented Sep 17, 2022 at 22:58
  • @hpaulj genfromtxt is there to cover the case of missing data: numpy.loadtxt is an equivalent function when no data is missing. Commented Sep 18, 2022 at 14:10
  • @Claudio, here either would work. The OP is using genfromtxt. gen is also more useful when loading columns that vary in dtype, i.e. returning a structured array. With the latest version, loadtxt has a speed advantage. Commented Sep 18, 2022 at 18:19

5 Answers 5

2

You can just read split the entire string instead of reading it line by line:

# Read as single string
with open(filepath, 'r') as file:
    contents = file.read()

# find *Node and *Element and get substring in between
first = "*Node"
second = "*Element"
numbers = contents[contents.find(first)+len(first):contents.find(second)]

# Remove commas, split string at whitespace characters and convert numbers to floats
numbers = [float(x) for x in numbers.replace(',', '').split()]

Or using your basic structure and numpy array as return type:

def findNodes(filepath, first="*Node", second="*Element"):
    with open(filepath, 'r') as file:
        contents = file.read()
    numbers = contents[contents.find(first)+len(first):contents.find(second)]
    return np.array([float(x) for x in numbers.replace(',', '').split()])

findNodes("data.inp")

Due to the comments pointing out that the resulting array is a 1d-array, I made a modification to split the found substring into lines first and then into its elements, which are subsequently converted to floats. This yields the desired 2d-array

def findNodes(filepath, first="*Node", second="*Element"):
    with open(filepath, 'r') as file:
        contents = file.read()
    numbers = contents[contents.find(first)+len(first):contents.find(second)]
    return np.array( \
            [[float(el) for el in line.split(',')] for line in numbers.splitlines()[1:]] \
                   )
Sign up to request clarification or add additional context in comments.

5 Comments

Notice that the in your nice short solution the returned numpy array is a flat one (has not the required 9 x 4 shape).
Your methold is clear, but it will be great if not all numbers in one line in numpy array. I mean the array's row will be same number of lines.
Yes, you're right. I just edited my answer. The last code snippet should yield the desired result.
Try ncol = numbers.count('\n')-1 instead of .split() and .next()
@Claudio I think your approach is better than the one is used. I am not sure, though, that this is safe and OS-independent. So I edited my answer again, now using the splitlines method, which considers several newline characters. Another advantage is that there is no need to count lines and reshape the array anymore because the array can be initialized directly with a nested list comprehension, see my edited answer.
0

BernieD already gave a good answer, but if you do want to read the file line by line, you could use an indicator variable that keeps track of whether the current line is in between the start and stop keywords or not:

def findNodes(filepath):
    datalist = []
    datastream = False
    
    with open(filepath, 'r') as file:
        for line in file:
            if '*Element' in line:
                datastream = False
            if datastream:
                datalist.append([float(n) for n in 
                                 line.replace(' ', '').replace('\n', '').split(',')])
            if '*Node' in line:
                datastream = True

    return np.array(datalist)


ndarray = findNodes('data.inp')

1 Comment

indicator variable is smart!
0

This is from a script I wrote to parse inp files:

def read_input(ifn):
    """Read an Abaqus INP file, read its sections.
    Return the section headings and the lines.
    """
    with open(ifn) as inf:
        lines = [ln.strip() for ln in inf.readlines()]
    # Remove comments
    lines = [ln for ln in lines if not ln.startswith("**")]
    # Find section headers
    headings = [(ln[1:], n) for n, ln in enumerate(lines) if ln.startswith("*")]
    # Filter the headings so that every heading has a start-of-data and
    # end-of-data index.
    headings.append(("end", -1))
    ln = [h[1] for h in headings]
    headings = [
        (name, start + 1, end) for (name, start), end in zip(headings[:-1], ln[1:])
    ]
    return headings, lines


def retrieve_nodes(headings, lines):
    """Extract the nodes out of lines.
    Return a dict of nodes, indexed by the node number.
    A node is a 3-tuple of coordinate strings.
    The node coordinates are *not* converted to floats, so as to not lose precision.

    Arguments:
        headings (list): list of (name, start, end) tuples.
        lines (list): list of lines.

    Returns:
        A dict of nodes (x,y,z)-tuples indexed by the node number.
    """
    nodes = {}
    for h in headings:
        if h[0].lower().startswith("node"):
            for ln in lines[h[1]:h[2]]:
                idx, x, y, z = ln.split(",")
                nodes[int(idx)] = (x.strip(), y.strip(), z.strip())
            # Assuming there is only one NODE section.
            break
    return nodes

Comments

0

Try following simplification (creates the array using numpy.loadtxt()):

import numpy as np
from io import StringIO
def findNodes(filepath, s='*Node\n', e='\n*Element'): 
    with open(filepath,'r') as f: r = f.read()
    return np.loadtxt(StringIO(r[r.find(s)+len(s):r.find(e)]),delimiter=',')

The same but using regular expression looks like:

import re
def findNodes(filepath, s=r'\*Node\n', e=r'\n\*Element'): 
    with open(filepath,'r') as f: r = f.read()
    rs = re.search(r"(?<="+s+")[\d.,\s\n]+(?="+e+")", r)[0]
    return np.loadtxt(StringIO(rs),delimiter=',')

Below the result of print(findNodes('data.inp')):

[[  1.  50.  20.  40.]
 [  2. 100.  20.  40.]
 [  3. 100.  20.   0.]
 [  4.  50.  20.   0.]
 [  5. 100.   0.  40.]
 [  6. 100.   0.   0.]
 [  7.  50.   0.   0.]
 [  8.  50.   0.  40.]
 [  9.   0.  40.  40.]]

P.S. Both of the used additional imports ( io StringIO and re are available in the standard installation of Python. The regex finds the appropriate text section using positive look behind and positive look forward.

Comments

0

There are various ways of reading and finding the desired block of lines. Something that's quite close to what you are already doing is:

Do one read to get the limits:

In [125]: with open('test.csv', 'r') as file:
     ...:         for num, line in enumerate(file,1):
     ...:             if '*Node' in line:
     ...:                 nodeLineStart = num
     ...:             if '*Element' in line:
     ...:                 nodeLineEnd = num
     ...:                 

In [126]: nodeLineStart, nodeLineEnd
Out[126]: (3, 13)

Then one genfromtxt call using the line limit parameters:

In [128]: data = np.genfromtxt('test.csv', delimiter=',', skip_header=nodeLineStart, max_rows=nodeLineEnd-nodeLineStart-1)

In [129]: data
Out[129]: 
array([[  1.,  50.,  20.,  40.],
       [  2., 100.,  20.,  40.],
       [  3., 100.,  20.,   0.],
       [  4.,  50.,  20.,   0.],
       [  5., 100.,   0.,  40.],
       [  6., 100.,   0.,   0.],
       [  7.,  50.,   0.,   0.],
       [  8.,  50.,   0.,  40.],
       [  9.,   0.,  40.,  40.]])

or with loadtxt:

In [133]: data = np.loadtxt('test.csv', delimiter=',', skiprows=nodeLineStart, max_rows=nodeLineEnd-nodeLineStart-1)

Or with one readlines, passing just the desired block of lines to the reader:

In [138]: with open('test.csv', 'r') as file: 
     ...:     lines=file.readlines()
     ...:     for num, line in enumerate(lines,1):
     ...:             if '*Node' in line:
     ...:                 nodeLineStart = num
     ...:             if '*Element' in line:
     ...:                 nodeLineEnd = num
In [140]: data = np.loadtxt(lines[nodeLineStart:nodeLineEnd-1], delimiter=',')

np.genfromtxt/loadtxt can use anything that gives them a set of lines, a file iterator, stringio, or just a list of strings.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.