Source code for eureka.lib.readECF
import os
# Required in case user passes in a numpy object (e.g. np.inf)
import numpy as np
[docs]class MetaClass:
'''A class to hold Eureka! metadata.
This class loads a Eureka! Control File (ecf) and lets you
query the parameters and values.
Notes
-----
History:
- 2009-01-02 Christopher Campo
Initial Version.
- 2010-03-08 Patricio Cubillos
Modified from ccampo version.
- 2010-10-27 Patricio Cubillos
Docstring updated
- 2011-02-12 Patricio Cubillos
Merged with ccampo's tepclass.py
- 2022-03-24 Taylor J Bell
Significantly modified for Eureka
'''
def __init__(self, folder=None, file=None, **kwargs):
'''Initialize the MetaClass object.
Parameters
----------
folder : str; optional
The folder containing an ECF file to be read in. Defaults to None
which resolves to './'.
file : str; optional
The ECF filename to be read in. Defaults to None which results
in an empty MetaClass object.
**kwargs : dict
Any additional parameters to be loaded into the MetaClass after
the ECF has been read in
Notes
-----
History:
- Mar 2022 Taylor J Bell
Initial Version based on old readECF code.
'''
if folder is None:
folder = '.'+os.sep
self.params = {}
if file is not None and folder is not None:
if os.path.exists(os.path.join(folder, file)):
self.read(folder, file)
else:
raise ValueError(f"The file {os.path.join(folder,file)} "
f"does not exist.")
if kwargs is not None:
# Add any kwargs to the parameter dict
self.params.update(kwargs)
# Store each as an attribute
for param, value in kwargs.items():
setattr(self, param, value)
def __str__(self):
'''A function to nicely format some outputs when a MetaClass object is
converted to a string.
This function gets used if one does str(meta) or print(meta).
Returns
-------
str
A string representation of what is contained in the
MetaClass object.
Notes
-----
History:
- Mar 2022 Taylor J Bell
Initial version.
'''
output = ''
for par in self.params:
# For each parameter, format a line as "Name: Value"
output += par+': '+str(getattr(self, par))+'\n'
return output
def __repr__(self):
'''A function to nicely format some outputs when asked for a printable
representation of the MetaClass object.
This function gets used if one does repr(meta) or does just meta in an
interactive shell.
Returns
-------
str
A string representation of what is contained in the MetaClass
object in a manner that could reproduce a similar MetaClass object.
Notes
-----
History:
- Mar 2022 Taylor J Bell
Initial version.
'''
# Get the fully qualified name of the class
output = type(self).__module__+'.'+type(self).__qualname__+'('
# Show what folder and file were used to read in an ECF
output += f"folder='{self.folder}', file='{self.filename}', "
# Show what values have been loaded into the params dictionary
output += "**"+str(self.params)
output = output+')'
return output
def __setattr__(self, item, value):
"""Maps attributes to values
Parameters
----------
item : str
The name for the attribute
value : any
The attribute value
"""
if item in ['lines', 'params', 'filename', 'folder']:
self.__dict__[item] = value
return
# Set the attribute
self.__dict__[item] = value
# Add it to the list of parameters
self.__dict__['params'][item] = value
[docs] def read(self, folder, file):
"""A function to read ECF files
Parameters
----------
folder : str
The folder containing an ECF file to be read in.
file : str
The ECF filename to be read in.
Notes
-----
History:
- Mar 2022 Taylor J Bell
Initial Version based on old readECF code.
- April 25, 2022 Taylor J Bell
Joining topdir and inputdir/outputdir here.
"""
self.filename = file
self.folder = folder
# Read the file
with open(os.path.join(folder, file), 'r') as file:
self.lines = file.readlines()
cleanlines = [] # list with only the important lines
# Clean the lines:
for line in self.lines:
# Strip off comments:
if "#" in line:
line = line[0:line.index('#')]
line = line.strip()
# Keep only useful lines:
if len(line) > 0:
cleanlines.append(line)
for line in cleanlines:
name = line.split()[0]
val = ''.join(line.split()[1:])
try:
val = eval(val)
except:
# FINDME: Need to catch only the expected exception
pass
self.params[name] = val
# Store each as an attribute
for param, value in self.params.items():
setattr(self, param, value)
self.inputdir_raw = self.inputdir
self.outputdir_raw = self.outputdir
# Join inputdir_raw and outputdir_raw to topdir for convenience
# Use split to avoid issues from beginning
self.inputdir = os.path.join(self.topdir,
*self.inputdir.split(os.sep))
self.outputdir = os.path.join(self.topdir,
*self.outputdir.split(os.sep))
# Make sure there's a trailing slash at the end of the paths
if self.inputdir[-1] != os.sep:
self.inputdir += os.sep
if self.outputdir[-1] != os.sep:
self.outputdir += os.sep
[docs] def write(self, folder):
"""A function to write an ECF file based on the current MetaClass settings.
NOTE: For now this only rewrites the input ECF file to a new ECF file
in the requested folder. In the future this function should make a full
ECF file based on any adjusted parameters.
Parameters
----------
folder : str
The folder where the ECF file should be written.
Notes
-----
History:
- Mar 2022 Taylor J Bell
Initial Version.
"""
with open(os.path.join(folder, self.filename), 'w') as file:
file.writelines(self.lines)
[docs] def copy_ecf(self):
"""Copy an ECF file to the output directory to ensure reproducibility.
NOTE: This will update the inputdir of the ECF file to point to the
exact inputdir used to avoid ambiguity later and ensure that the ECF
could be used to make the same outputs.
Notes
-----
History:
- Mar 2022 Taylor J Bell
Initial Version based on old readECF code.
"""
# Copy ecf (and update inputdir to be precise which exact inputs
# were used)
new_ecfname = os.path.join(self.outputdir, self.filename)
with open(new_ecfname, 'w') as new_file:
for line in self.lines:
if len(line.strip()) == 0 or line.strip()[0] == '#':
new_file.write(line)
else:
line_segs = line.strip().split()
if line_segs[0] == 'inputdir':
new_file.write(line_segs[0]+'\t\t'+self.inputdir_raw +
'\t'+' '.join(line_segs[2:])+'\n')
else:
new_file.write(line)