"""This modules holds all of the neccesarry functions and classes for reading
NUFT input data into the classes described in :mod:`~nuftio.spec`.
"""
from __future__ import print_function
__displayname__ = 'File I/O'
__all__ = [
'NuftMesh',
'Parser',
'read_genmsh',
'read_rocktab',
'read_usnt',
'read_tab',
]
import properties
import discretize
import pandas as pd
import numpy as np
import cPyparsing as pyparsing
import time
import re
import sys
if sys.version_info < (3,):
from StringIO import StringIO
else:
from io import StringIO
from .spec import MeshSpecifications, RockType, USNT
[docs]class Parser(properties.HasProperties):
"""Parses NUFT data files"""
COMMENTS = ";"
@staticmethod
def __toDict(lst, no=False):
"""An internal helper to handle parsing the ridiculously cumbersome
NUFT data format into nested dictionaries."""
if isinstance(lst[0], list):
return Parser.__toDict(lst[0])
key = lst[0]
key = key.replace('-','_')
values = lst[1::]
# Check if there is an option
if len(values) > 1 and not isinstance(values[0], list) and isinstance(values[1], list):
#print('made an option:', values[0])
values[0] = ['option', values[0]]
# clean the values into a nested dict if needed
#print(key, values)
if len(values) < 1:
tmp = key
key ='option'
values = tmp
if isinstance(values[0], list) and len(values[0]) > 1:
clean = {}
for vs in values:
k,v = Parser.__toDict(vs, no=True)
if key == 'mat':
tmp = clean.get(k, {})
nk, nv = Parser.__toDict(v, no=True)
tmp2 = tmp.get(nk, [])
tmp2.append(nv)
tmp[nk] = tmp2
clean[k] = tmp
else:
clean[k] = v
values = clean
if all(not isinstance(x, list) for x in values) and len(values) > 1:
#print('passing', key, values)
pass
elif isinstance(values, list) and len(values) == 1:
#print('did not pass', key, values)
values = values[0]
# return
if no:
return key, values
return {key:values}
@staticmethod
def _createSpecs(dic):
"""Creates the proper specs for the input nested dictionary. The top
level of values will be transformed to ``spec`` objects
"""
pass
@staticmethod
def _readFileContents(filename, comments=';', skiprows=0):
"""Reads the contents of a file to a giant text string"""
# Load in all the file lines
text = np.genfromtxt(filename,
dtype=str,
delimiter='\n',
comments=comments)[skiprows::]
return '\n'.join(text)
[docs] @staticmethod
def parseFile(filename, comments=';', skiprows=0, opener='(', closer=')'):
"""Parses general NUFT data file into a disctionary. If it is a table
then use the ``parseTabFile`` method.
"""
text = Parser._readFileContents(filename, comments=comments, skiprows=skiprows)
# Run the parsing and get a nest list of the results
return Parser.parseString(text, opener=opener, closer=closer)
[docs] @staticmethod
def parseString(text, opener='(', closer=')'):
"""Perses a string of text in NUFT data format to nested dictionaries"""
start_time = time.time()
# print('Parsing...', end='\r')
# Create the data block parser
r = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'*+,-./:;<=>?@[\]^_`{|}~'
se = pyparsing.nestedExpr(opener=opener, closer=closer, content=pyparsing.Word(r))
# Run the parsing and get a nest list of the results
results = se.parseString(text)
results = results.asList()
# print('Parsed text in {} seconds'.format(time.time() - start_time))
# Now turn that nested dictionary into data objects!
data = Parser.__toDict(results)
return data
[docs] @staticmethod
def parseTabFile(filename, comments=';', skiprows=0, opener='(', closer=')', names=None):
"""Reads the NUFT table data format (``.tab`` files)."""
# reade the file lines
text = Parser._readFileContents(filename, comments=comments, skiprows=skiprows)
# now find all tables
TABLE = re.compile(r'\(table(.+?)\)', re.MULTILINE|re.DOTALL)
tables = TABLE.findall(text)
if len(tables) < 1:
raise RuntimeError('No tables found in the iput file.')
dfs = []
# Now create pandas data frames of all the tables in that file
for tab in tables:
dfs.append(pd.read_table(StringIO(tables[0]), delim_whitespace=True, names=names))
if len(dfs) == 1:
# Id only one dataframe, return it
return dfs[0]
return dfs
# Now define the functions that we want to use
[docs]def read_genmsh(filename, comments=';', skiprows=0, opener='(', closer=')', usnt=False):
"""Reads genmsh specifiation files"""
datadict = Parser.parseFile(filename, comments=comments, skiprows=skiprows, opener=opener, closer=closer)
keys = list(datadict.keys())
if len(keys) != 1:
raise RuntimeError('This file is specifies too many/few data structures: {}'.format(keys))
if keys[0] != 'genmsh':
raise RuntimeError('The data type ({}) is not (genmsh).'.format(key[0]))
# Okay we got a genmsh
if usnt:
return USNT._create(datadict[keys[0]])
return MeshSpecifications._create(datadict[keys[0]])
[docs]def read_rocktab(filename, comments=';', skiprows=0, opener='(', closer=')'):
"""Reads rocktab material specification files"""
datadict = Parser.parseFile(filename, comments=comments, skiprows=skiprows, opener=opener, closer=closer)
keys = list(datadict.keys())
if len(keys) != 1:
raise RuntimeError('This file is specifies too many/few data structures: {}'.format(keys))
if keys[0] != 'rocktab':
raise RuntimeError('The data type ({}) is not (rocktab).'.format(key[0]))
# Okay we got a rocktab
tabs = {}
for k, v in datadict[keys[0]].items():
tabs[k] = RockType._create(k, v)
return tabs
[docs]def read_usnt(fname_mesh, fname_rtab, comments=';', skiprows=0, opener='(', closer=')'):
"""Reads mesh specifications and rock property table into one data object"""
usnt = read_genmsh(fname_mesh, comments=comments, skiprows=skiprows, opener=opener, closer=closer, usnt=True)
rocktab = read_rocktab(fname_rtab, comments=comments, skiprows=skiprows, opener=opener, closer=closer)
usnt.rocktab = rocktab
return usnt
[docs]def read_tab(filename, comments=';', skiprows=0, opener='(', closer=')', names=None):
"""Reads the NUFT table data format (``.tab`` files)."""
return Parser.parseTabFile(filename, comments=comments, skiprows=skiprows, opener=opener, closer=closer, names=names)
[docs]class NuftMesh(discretize.TensorMesh):
"""This is an extension of ``discretize``s ``TensorMesh`` to provide
file IO for NUFT simulation results"""
[docs] @classmethod
def readNuft(TensorMesh, filename, fix_indices=True):
"""Run the Foo algorithm on an input number ``nub``.
Args:
filename (str): the relative or absolute file name
fix_indices (bool): If True, decrease the indexing arrays by one
because someone chose to use +1 indexing in the NUFT format.
"""
# read the NUFT results using pandas because its a big ol table
try:
data = pd.read_table(filename, delim_whitespace=True)
except (FileNotFoundError, IOError, OSError):
raise RuntimeError('File ("{}") not found.'.format(filename))
# Now use spatial refernce data to reconstruct the TensorMesh
#- The array titles that should always be present and that we will use
refs = ['index', 'i', 'j', 'k', 'x', 'dx', 'y', 'dy', 'z', 'dz', 'element_ref', 'nuft_ind', 'volume']
#- subtract one from indexing arrays because someone chose +1 indexing :(
if fix_indices:
data['index'] -= 1
data['i'] -= 1
data['j'] -= 1
data['k'] -= 1
data['element_ref'] -= 1
data['nuft_ind'] -= 1
#- Get the tensors on each axis
xedge = data[data['j'] == 0]
xedge = xedge[xedge['k'] == 0]
yedge = data[data['i'] == 0]
yedge = yedge[yedge['k'] == 0]
zedge = data[data['i'] == 0]
zedge = zedge[zedge['j'] == 0]
xt = xedge['dx'].values
yt = yedge['dy'].values
zt = zedge['dz'].values
# Find the origin
odx = xedge[xedge['j'] == 0]
ox = (odx['x'] - odx['dx']/2.).values[0]
ody = yedge[yedge['i'] == 0]
oy = (odx['y'] - odx['dy']/2.).values[0]
odz = xedge[xedge['j'] == 0]
oz = (odx['z'] - odx['dz']/2.).values[0]
# Construct the TensorMesh
mesh = TensorMesh([xt, yt, zt], x0=(ox, oy, oz))
# Make a model dictionary
data = data[[k for k in data.keys() if k not in refs]]
models = dict()
# Validate the mesh and models
#TODO: mesh.validate()
for name in data.keys():
mod = data[name].values
if mod.size != mesh.nC:
raise RuntimeError('Number of elements ({}) in data array does not match number of cells ({}) in the mesh.'.format(mod.size, mesh.nC))
models[name] = mod
# Return the constructed mesh and models
return mesh, models