Commit 95597556 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Fixes and an example on how to use NomadParser.

parent 2be57cc6
......@@ -131,7 +131,7 @@ class NomadParser(object):
"""
for path, file_id in self.files.iteritems():
if file_id:
self.setup_file_id(path, id)
self.setup_file_id(path, file_id)
@abstractmethod
def setup_version(self):
......@@ -178,9 +178,14 @@ class NomadParser(object):
Checks through the list given by get_supported_quantities and also
checks the metainfoToSkip parameter given in the JSON input.
"""
if name not in self.metainfos:
logger.error("The metaname '{}' was not declared on the metainfo file defined in the JSON input.".format(name))
return False
if name not in self.get_supported_quantities():
logger.error("The metaname '{}' is not available in this parser version.".format(name))
return False
if name in self.metainfo_to_skip:
logger.error("The metaname '{}' cannot be calculated as it is in the list 'metaInfoToSkip'.".format(name))
return False
return True
......@@ -189,13 +194,12 @@ class NomadParser(object):
the parser, parses the corresponding quantity (if available), converts
it to SI units and return the value as json.
"""
logger.info("===========================================================================")
logger.info("GETTING QUANTITY '{}'".format(name))
#Check availability
available = self.check_quantity_availability(name)
if not available:
logger.warning("The quantity '{}' is not available for this parser setup.".format(name))
return
result = self.start_parsing(name)
......
# Testing markup
# Parsing library
## NomadParser
The class NomadParser is used as a base class for the parsers in the NoMaD
project. When starting to develop a new parser, you subclass it and gain access
to it's functionality. A minimal example of a parser class that inherit NomadParser:
´´´python
class MyParser(NomadParser):
"""
This class is responsible for setting up the actual parser implementation
and provides the input and output for parsing. It inherits the NomadParser
class to get access to many useful features.
"""
def __init__(self, input_json_string):
NomadParser.__init__(self, input_json_string)
self.version = None
self.implementation = None
self.setup_version()
# You would typically also setup some file id's here to help handling
# the files. In this example the id's are already given in the JSON
# input. To register a file if you can call 'setup_file_id()'
def start_parsing(self, name):
"""Asks the implementation object to give the result object by calling
the function corresponding to the quantity name (=metaname). The
NomadParser then automatically handles the conversion, validation and
saving of the results.
"""
return getattr(self.implementation, name)()
def setup_version(self):
"""The parsers should be able to support different version of the same
code. In this function you can determine which version of the software
we're dealing with and initialize the correct implementation
accordingly.
"""
self.version = "1"
self.implementation = globals()["MyParserImplementation{}".format(self.version)](self)
def get_supported_quantities(self):
return self.implementation.supported_quantities
´´´
This class only defines how to setup a parser based on the given input. The
actual dirty work is done by a parser implementation class. A minimal example
of a parser implementation class:
´´´python
class MyParserImplementation1():
"""This is an implementation class that contains the actual parsing logic
for a certain software version. There can be multiple implementation
classes and MyParser decides which one to use.
"""
supported_quantities = ["energy_total", "particle_forces", "particle_position"]
def __init__(self, parser):
self.parser = parser
def energy_total(self):
"""Returns the total energy. Used to illustrate on how to parse a single
result.
"""
result = Result()
result.unit = ureg.joule
result.value = 2.0
return result
def particle_forces(self):
"""Returns multiple force configurations as a list. Has to load the
entire list into memory. You should avoid loading very big files
unnecessarily into memory. See the function 'particle_position()' for a
one example on how to avoid loading the entire file into memory.
"""
result = Result()
result.unit = ureg.newton
xyz_string = self.parser.get_file_contents("forces")
forces = []
i_forces = []
for line in xyz_string.split('\n'):
line = line.strip()
if not line:
continue
if line.startswith("i"):
if i_forces:
forces.append(np.array(i_forces))
i_forces = []
continue
elif line.startswith("2"):
continue
else:
i_forces.append([float(x) for x in line.split()[-3:]])
if i_forces:
forces.append(np.array(i_forces))
result.value_iterable = forces
return result
def particle_position(self):
"""An example of a function returning a generator. This function does
not load the whole position file into memory, but goes throught it line
by line and returns configurations as soon as they are ready ready.
"""
def position_generator():
"""This inner function is the generator, a function that remebers
it's state and can yield intermediate results.
"""
xyz_file = self.parser.get_file_handle("positions")
i_forces = []
for line in xyz_file:
line = line.strip()
if not line:
continue
if line.startswith("i"):
if i_forces:
yield np.array(i_forces)
i_forces = []
continue
elif line.startswith("2"):
continue
else:
i_forces.append([float(x) for x in line.split()[-3:]])
if i_forces:
yield np.array(i_forces)
result = Result()
result.unit = ureg.angstrom
result.value_iterable = position_generator()
return result
´´´
The MyParser class decides which implementation to use based on e.g. the
software version number that is available on one of the input files. New
implementations corresponding to other software versions can then be easily
defined and they can also use the functionality of other implementation.
Example:
´´´python
class MyParserImplementation2(MyParserImplementation1):
"""Implementation for a different version of the electronic structure
software. Subclasses MyParserImplementation1. In this version the
energy unit has changed and the 'energy' function from
MyParserImplementation1 is overwritten.
"""
def energy(self):
"""The energy unit has changed in this version."""
result = Result()
result.unit = ureg.hartree
result.value = "2.0"
return result
´´´
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment