Page MenuHomec4science

inputvalue.py
No OneTemporary

File Metadata

Created
Mon, May 13, 17:50

inputvalue.py

"""Contains the classes that are used to write to and read from restart files.
Copyright (C) 2013, Joshua More and Michele Ceriotti
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http.//www.gnu.org/licenses/>.
The classes defined in this module define the base functions which parse the
data in the restart files. Each restart object defined has a fields and an
attributes dictionary, which are filled with the tags and attributes that
are allowed to be present, along with their default values and data type.
These are then filled with the data from the xml file when the program
is initialised, and are filled by the values calculated in the program which
are then output to the checkpoint file when a restart file is required.
Also deals with checking for user input errors, of the form of misspelt tags,
bad data types, and failure to input required fields.
Classes:
Input: Base input class.
InputAttribute: Input class for attribute data.
InputValue: Input class for scalar objects.
InputArray: Input class for arrays.
input_default: Class used to create mutable objects dynamically.
"""
__all__ = ['Input', 'InputValue', 'InputAttribute', 'InputArray', 'input_default']
import numpy as np
from copy import copy
from ipi.utils.io.io_xml import *
from ipi.utils.units import unit_to_internal, unit_to_user
class input_default(object):
"""Contains information required to dynamically create objects
Used so that we can define mutable default input values to various tags
without the usual trouble with having a class object that is also mutable,
namely that all members of that class share the same mutable object, so that
changing it for one instance of that class changes it for all others. It
does this by not holding the mutable default value, but instead the
information to create it, so that each instance of an input class can
have a separate instance of the default value.
Attributes:
type: Either a class type or function call from which to create the
default object.
args: A tuple giving positional arguments to be passed to the function.
kwargs: A dictionary giving key word arguments to be passed to the
function.
"""
def __init__(self, factory, args = None, kwargs = None):
"""Initialises input_default.
Args:
type: The class or function to be used to create the default object.
args: A tuple giving the arguments to be used to initialise
the default value.
kwargs: A dictionary giving the key word arguments to be used
to initialise the default value.
"""
if args is None:
args = ()
if kwargs is None:
kwargs = {}
# a default will be generated by factory(*args, **kwargs)
# *args unpacks the tuple, and is used for positional arguments
# **kwargs unpacks the dictionary, and is used for keyword arguments
self.factory = factory
self.args = args
self.kwargs = kwargs
class Input(object):
"""Base class for input handling.
Has the generic methods for dealing with the xml input file. Parses the input
data, outputs the output data, and deals with storing and returning the
data obtained during the simulation for the restart files.
Attributes:
fields: A dictionary holding the possible tags contained within the
tags for this restart object, which are then turned into the objects
held by the object given by this restart object. The dictionary is
of the form:
{"tag name": ( Input_object,
{"default": default value,
"dtype": data type,
"options": list of available options,
"help": help string,
"dimension": dimensionality of data}), ... }.
dynamic: A dictionary holding the possible tags contained within the
tags for this restart object, which are then turned into the objects
held by the object given by this restart object. These are used for
tags that can be specified more than once.
The dictionary is of the form:
{"tag name": ( Input_object,
{"default": default value,
"dtype": data type,
"options": list of available options,
"help": help string,
"dimension": dimensionality of data}), ... }.
attribs: A dictionary holding the attribute data for the tag for this
restart object. The dictionary is of the form:
{"attribute name": ( Input_object,
{"default": default value,
"dtype": data type,
"options": list of available options,
"help": help string,
"dimension": dimensionality of data}), ... }.
extra: A list of tuples ( "name", Input_object ) that may be used to
extend the capabilities of the class, i.e. to hold several instances of
a field with the same name, or to hold variable numbers of elements.
default_help: The default help string.
_help: The help string of the object. Defaults to default_help.
_default: Optional default value.
_optional: A bool giving whether the field is a required field.
_explicit: A bool giving whether the field has been specified by the user.
_text: All text written between the tags of the object.
_label: A label to be used to identify the class in the latex user manual.
_defwrite: The string which would be output if the class has its default
value.
"""
fields = {}
attribs = {}
dynamic = {}
default_help = "Generic input value"
default_label = "" #used as a way to reference a particular class using
#hyperlinks
def __init__(self, help=None, default=None):
"""Initialises Input.
Automatically adds all the fields and attribs names to the input object's
dictionary, then initialises all the appropriate input objects
as the corresponding values.
Args:
help: A help string.
default: A default value.
"""
# list of extended (dynamic) fields
self.extra = []
if help is None:
self._help = self.default_help
else:
self._help = help
if isinstance(default,input_default):
#creates default dynamically if a suitable template is defined.
self._default = default.factory(*default.args, **default.kwargs)
else:
self._default = default
self._optional = not (self._default is None)
self._label = self.default_label
#For each tag name in the fields and attribs dictionaries,
#creates and object of the type given, expanding the dictionary to give
#the arguments of the __init__() function, then adds it to the input
#object's dictionary.
for f, v in self.fields.iteritems():
self.__dict__[f] = v[0](**v[1])
for a, v in self.attribs.iteritems():
self.__dict__[a] = v[0](**v[1])
self.set_default()
self._text = ""
# stores what we would write out if the default was set
self._defwrite = ""
if not self._default is None:
self._defwrite = self.write(name="%%NAME%%")
def set_default(self):
"""Sets the default value of the object."""
if not self._default is None:
self.store(self._default)
elif not hasattr(self, 'value'):
self.value = None #Makes sure we don't get exceptions when we
#look for self.value
self._explicit = False #Since the value was not set by the user
def store(self, value=None):
"""Dummy function for storing data."""
self._explicit = True
pass
def fetch(self):
"""Dummy function to retrieve data."""
self.check()
pass
def check(self):
"""Base function to check for input errors.
Raises:
ValueError: Raised if the user does not specify a required field.
"""
if not (self._explicit or self._optional):
raise ValueError("Uninitialized Input value of type " + type(self).__name__)
def extend(self, name, xml):
""" Dynamically add elements to the 'extra' list.
Picks from one of the templates in the self.dynamic dictionary, then
parses.
Args:
name: The tag name of the dynamically stored tag.
xml: The xml_node object used to parse the data stored in the tags.
"""
newfield = self.dynamic[name][0](**self.dynamic[name][1])
newfield.parse(xml)
self.extra.append((name,newfield))
def write(self, name="", indent="", text="\n"):
"""Writes data in xml file format.
Writes the tag, attributes, data and closing tag appropriate to the
particular fields and attribs data. Writes in a recursive manner, so
that objects contained in the fields dictionary have their write function
called, so that their tags are written between the start and end tags
of this object, as is required for the xml format.
This also adds an indent to the lower levels of the xml heirarchy,
so that it is easy to see which tags contain other tags.
Args:
name: An optional string giving the tag name. Defaults to "".
indent: An optional string giving the string to be added to the start
of the line, so usually a number of tabs. Defaults to "".
text: Additional text to be output between the tags.
Returns:
A string giving all the data contained in the fields and attribs
dictionaries, in the appropriate xml format.
"""
rstr = indent + "<" + name;
for a in self.attribs:
# only write out attributes that are not defaults
# have a very simple way to check whether they actually add something:
# we compare with the string that would be output if the argument was set
# to its default
defstr = self.__dict__[a]._defwrite.replace("%%NAME%%",a)
outstr = self.__dict__[a].write(name=a)
if outstr != defstr:
rstr += " " + outstr
rstr += ">"
rstr += text
for f in self.fields:
#only write out fields that are not defaults
defstr = self.__dict__[f]._defwrite.replace("%%NAME%%",f)
if defstr != self.__dict__[f].write(f): # here we must compute the write string twice not to be confused by indents.
rstr += self.__dict__[f].write(f, " " + indent)
for (f,v) in self.extra:
# also write out extended (dynamic) fields if present
rstr += v.write(f, " " + indent)
if text.find('\n') >= 0:
rstr += indent + "</" + name + ">\n"
else:
rstr += "</" + name + ">\n"
return rstr
def parse(self, xml=None, text=""):
"""Parses an xml file.
Uses the xml_node class defined in io_xml to read all the information
contained within the root tags, and uses it to give values for the attribs
and fields data recursively. It does this by giving all the data between
the appropriate field tag to the appropriate field restart object as a
string, and the appropriate attribute data to the appropriate attribs
restart object as a string. These data are then parsed by these objects
until all the information is read, or an input error is found.
Args:
xml: An xml_node object containing all the data for the parent
tag.
text: The data held between the start and end tags.
Raises:
NameError: Raised if one of the tags in the xml input file is
incorrect.
ValueError: Raised if the user does not specify a required field.
"""
# before starting, sets everything to its default -- if a default is set!
for a in self.attribs:
self.__dict__[a].set_default()
for f in self.fields:
self.__dict__[f].set_default()
self.extra = []
self._explicit = True
if xml is None:
self._text = text
else:
for a, v in xml.attribs.iteritems():
if a in self.attribs:
self.__dict__[a].parse(text=v)
elif a == "_text":
pass
else:
raise NameError("Attribute name '" + a + "' is not a recognized property of '" + xml.name + "' objects")
for (f, v) in xml.fields: #reads all field and dynamic data.
if f in self.fields:
self.__dict__[f].parse(xml=v)
elif f == "_text":
self._text = v
elif f in self.dynamic:
self.extend(f, v)
else:
raise NameError("Tag name '" + f + "' is not a recognized property of '" + xml.name + "' objects")
#checks for missing arguments.
for a in self.attribs:
va = self.__dict__[a]
if not (va._explicit or va._optional):
raise ValueError("Attribute name '" + a + "' is mandatory and was not found in the input for the property " + xml.name)
for f in self.fields:
vf = self.__dict__[f]
if not (vf._explicit or vf._optional):
raise ValueError("Field name '" + f + "' is mandatory and was not found in the input for the property " + xml.name)
def detail_str(self):
"""Prints out the supplementary information about a particular input class.
Used to print out the dimensions, default value, possible options and data
type of an input value to the LaTeX helf file.
"""
xstr = ""
if hasattr(self, '_dimension') and self._dimension != "undefined": #gives dimension
xstr += "dimension: " + self._dimension + "; "
if self._default != None and issubclass(self.__class__, InputAttribute):
#We only print out the default if it has a well defined value.
#For classes such as InputCell, self._default is not the value,
#instead it is an object that is stored to give the default value in
#self.value. For this reason we print out self.value at this stage,
#and not self._default
xstr += "default: " + self.pprint(self.value) + "; "
if issubclass(self.__class__, InputAttribute):
#if possible, prints out the type of data that is being used
xstr += "data type: " + self.type_print(self.type) + "; "
if hasattr(self, "_valid"):
if self._valid is not None:
xstr += "options: " #prints out valid options, if
for option in self._valid: #required.
xstr += "`" + str(option) + "', "
xstr = xstr.rstrip(", ")
xstr += "; "
return xstr
def help_latex(self, name="", level=0, stop_level=None, standalone=True):
"""Function to generate a LaTeX formatted help file.
Args:
name: Name of the tag that has to be written out.
level: Current level of the hierarchy being considered.
stop_level: The depth to which information will be given. If not given,
will give all information.
standalone: A boolean giving whether the latex file produced will be a
stand-alone document, or will be intended as a section of a larger
document with cross-references between the different sections.
Returns:
A LaTeX formatted string.
"""
#stops when we've printed out the prerequisite number of levels
if (not stop_level is None and level > stop_level):
return ""
rstr = ""
if level == 0:
if standalone:
#assumes that it is a stand-alone document, so must have
#document options.
rstr += r"\documentclass[12pt,fleqn]{report}"
rstr += r"""
\usepackage{etoolbox}
\usepackage{suffix}
\newcommand{\ipiitem}[3]{%
\setul{1pt}{.4pt}\ifblank{#1}{}{\ifstrequal{#1}{\underline{\smash{}}}{}{
{\noindent\textbf{#1}:\rule{0.0pt}{1.05\baselineskip}\quad}}}% uses a strut to add a bit of vertical space
{#2}\parskip=0pt\par
\ifblank{#3}{}%
{ {\hfill\raggedleft\textit{\small #3}\par} }
}
\makeatletter
\newenvironment{ipifield}[4]{%
\ifblank{#1}{}{\vspace{0.5em}}
\noindent\parskip=0pt\begin{tabular}[t]{|p{1.0\linewidth}}
%cell without border
\multicolumn{1}{@{}p{1.0\linewidth}}{
\ipiitem{\underline{\smash{#1}}}{#2}{}
\ifblank{#4}{ %
\ifblank{#3}{}{{\hfill\raggedleft\textit{\small #3}}\par}}{} } \vspace{-1em}\\ %
% cell with border
\ifblank{#4}{} %
{ \ifblank{#3}{}{\vspace{-1em}{\hfill\raggedleft\textit{\small #3}}\par} %
{#4}\vspace{-1em}\\\hline } % negative vspace to undo the line break
\end{tabular}
\parskip=0pt\list{}{\listparindent 1.5em%
\leftmargin \listparindent
\rightmargin 0pt
\parsep 0pt
\itemsep 0pt
\topsep 0pt
}%
\item\relax
}
{\endlist}
\makeatother
"""
rstr += "\n\\begin{document}\n"
if self._label != "" and not standalone:
#assumes that it is part of a cross-referenced document, so only
#starts a new section.
rstr += "\\section{" + self._label + "}\n"
rstr += "\\label{" + self._label + "}\n"
rstr += "\\begin{ipifield}{}%\n"
else:
if self._label != "" and not standalone:
rstr += "\\begin{ipifield}{\hyperref["+self._label+"]{"+name+"}}%\n"
else:
rstr += "\\begin{ipifield}{"+name+"}%\n"
rstr += "{"+self._help+"}%\n"
rstr += "{"+self.detail_str()+"}%\n"
rstr += "{"
# Prints out the attributes
if len(self.attribs) != 0:
#don't print out units if not necessary
if len(self.attribs) == 1 and (("units" in self.attribs) and self._dimension == "undefined"):
pass
else:
for a in self.attribs:
#don't print out units if not necessary
if not (a == "units" and self._dimension == "undefined"):
rstr += "\\ipiitem{" + a + "}%\n{" + self.__dict__[a]._help + "}%\n{"+self.__dict__[a].detail_str()+"}%\n" #!!MUST ADD OTHER STUFF
rstr+="}\n"
#As above, for the fields. Only prints out if we have not reached the
#user-specified limit.
if len(self.fields) != 0 and level != stop_level:
for f in self.fields:
rstr += self.__dict__[f].help_latex(name=f, level=level+1, stop_level=stop_level, standalone=standalone)
if len(self.dynamic) != 0 and level != stop_level:
for f, v in self.dynamic.iteritems():
dummy_obj = v[0](**v[1])
rstr += dummy_obj.help_latex(name=f, level=level+1, stop_level=stop_level, standalone=standalone)
rstr += "\\end{ipifield}\n"
if level == 0 and standalone:
#ends the created document if it is not part of a larger document
rstr += "\\end{document}"
#Some escape characters are necessary for the proper latex formatting
rstr = rstr.replace('_', '\\_')
rstr = rstr.replace('\\\\_', '\\_')
rstr = rstr.replace('...', '\\ldots ')
rstr = rstr.replace('<', '$<$')
rstr = rstr.replace('>', '$>$')
return rstr
def pprint(self, default, indent="", latex = True):
"""Function to convert arrays and other objects to human-readable strings.
Args:
default: The object that needs to be converted to a string.
indent: The indent at the beginning of a line.
latex: A boolean giving whether the string will be latex-format.
Returns:
A formatted string.
"""
if type(default) is np.ndarray:
if default.shape == (0,):
return " [ ] " #proper treatment of empty arrays.
else:
#indents new lines for multi-D arrays properly
rstr = "\n" + indent + " "
rstr += str(default).replace("\n", "\n" + indent + " ")
if not latex:
rstr += "\n" + indent + " "
return rstr
elif type(default) == str:
if latex:
return "`" + default + "'" #indicates that it is a string
else:
return " " + default + " "
elif default == []:
return " [ ] "
elif default == {}:
if latex:
return " \\{ \\} " #again, escape characters needed for latex
else: #formatting
return " { } "
else:
#in most cases standard formatting will do
return " " + str(default) + " "
def type_print(self, dtype):
"""Function to convert a data types to human-readable strings.
Args:
dtype: A data type.
"""
if dtype == bool:
return "boolean"
elif dtype == float or dtype == np.float64:
return "float"
elif dtype == int or dtype == np.uint64 or dtype == np.int64:
return "integer"
elif dtype == dict:
return "dictionary"
elif dtype == str:
return "string"
elif dtype == tuple:
return "tuple"
else:
raise TypeError("Unrecognized data type " + str(dtype))
def help_xml(self, name="", indent="", level=0, stop_level=None):
"""Function to generate an xml formatted help file.
Args:
name: A string giving the name of the root node.
indent: The indent at the beginning of a line.
level: Current level of the hierarchy being considered.
stop_level: The depth to which information will be given. If not given,
all information will be given
Returns:
An xml formatted string.
"""
#stops when we've printed out the prerequisite number of levels
if (not stop_level is None and level > stop_level):
return ""
#these are booleans which tell us whether there are any attributes
#and fields to print out
show_attribs = (len(self.attribs) != 0)
show_fields = (not (len(self.fields) == 0 and len(self.dynamic) == 0)) and level != stop_level
rstr = ""
rstr = indent + "<" + name; #prints tag name
for a in self.attribs:
if not (a == "units" and self._dimension == "undefined"):
#don't print out units if not necessary
rstr += " " + a + "=''" #prints attribute names
rstr += ">\n"
#prints help string
rstr += indent + " <help> " + self._help + " </help>\n"
if show_attribs:
for a in self.attribs:
if not (a == "units" and self._dimension == "undefined"):
#information about tags is found in tags beginning with the name
#of the attribute
rstr += indent + " <" + a + "_help> " + self.__dict__[a]._help + " </" + a + "_help>\n"
#prints dimensionality of the object
if hasattr(self, '_dimension') and self._dimension != "undefined":
rstr += indent + " <dimension> " + self._dimension + " </dimension>\n"
if self._default != None and issubclass(self.__class__, InputAttribute):
#We only print out the default if it has a well defined value.
#For classes such as InputCell, self._default is not the value,
#instead it is an object that is stored, putting the default value in
#self.value. For this reason we print out self.value at this stage,
#and not self._default
rstr += indent + " <default>" + self.pprint(self.value, indent=indent, latex=False) + "</default>\n"
if show_attribs:
for a in self.attribs:
if not (a == "units" and self._dimension == "undefined"):
if self.__dict__[a]._default is not None:
rstr += indent + " <" + a + "_default>" + self.pprint(self.__dict__[a]._default, indent=indent, latex=False) + "</" + a + "_default>\n"
#prints out valid options, if required.
if hasattr(self, "_valid"):
if self._valid is not None:
rstr += indent + " <options> " + str(self._valid) + " </options>\n"
if show_attribs:
for a in self.attribs:
if not (a == "units" and self._dimension == "undefined"):
if hasattr(self.__dict__[a], "_valid"):
if self.__dict__[a]._valid is not None:
rstr += indent + " <" + a + "_options> " + str(self.__dict__[a]._valid) + " </" + a + "_options>\n"
#if possible, prints out the type of data that is being used
if issubclass(self.__class__, InputAttribute):
rstr += indent + " <dtype> " + self.type_print(self.type) + " </dtype>\n"
if show_attribs:
for a in self.attribs:
if not (a == "units" and self._dimension == "undefined"):
rstr += indent + " <" + a + "_dtype> " + self.type_print(self.__dict__[a].type) + " </" + a + "_dtype>\n"
#repeats the above instructions for any fields or dynamic tags.
#these will only be printed if their level in the hierarchy is not above
#the user specified limit.
if show_fields:
for f in self.fields:
rstr += self.__dict__[f].help_xml(f, " " + indent, level+1, stop_level)
for f, v in self.dynamic.iteritems():
#we must create the object manually, as dynamic objects are
#not automatically added to the input object's dictionary
dummy_obj = v[0](**v[1])
rstr += dummy_obj.help_xml(f, " " + indent, level+1, stop_level)
rstr += indent + "</" + name + ">\n"
return rstr
class InputAttribute(Input):
"""Class for handling attribute data.
Has the methods for dealing with attribute data of the form:
<tag_name attrib='data'> ..., where data is just a value. Takes the data and
converts it to the required data_type, so that it can be used in the
simulation.
Attributes:
type: Data type of the data.
value: Value of data. Also specifies data type if type is None.
_valid: An optional list of valid options.
"""
def __init__(self, help=None, default=None, dtype=None, options=None):
"""Initialises InputAttribute.
Args:
help: A help string.
default: A default value.
dtype: An optional data type. Defaults to None.
options: An optional list of valid options.
"""
if not dtype is None:
self.type = dtype
else:
raise TypeError("You must provide dtype")
super(InputAttribute,self).__init__(help, default)
if options is not None:
self._valid = options
if not default is None and not self._default in self._valid:
#This makes sure that the programmer has set the default value
#so that it is a valid value.
raise ValueError("Default value '" + str(self._default) + "' not in option list " + str(self._valid)+ "\n" + self._help)
else:
self._valid = None
def parse(self, text=""):
"""Reads the data for a single attribute value from an xml file.
Args:
text: The data held between the start and end tags.
"""
super(InputAttribute, self).parse(text=text)
self.value = read_type(self.type, self._text)
def store(self, value):
"""Stores the input data.
Args:
value: The raw data to be stored.
"""
super(InputAttribute,self).store(value)
self.value = value
def fetch(self):
"""Returns the stored data."""
super(InputAttribute,self).fetch()
return self.value
def check(self):
"""Function to check for input errors.
Raises:
ValueError: Raised if the value chosen is not one of the valid options.
"""
super(InputAttribute,self).check()
if not (self._valid is None or self.value in self._valid):
#This checks that the user has set the value to a valid value.
raise ValueError(str(self.value) + " is not a valid option (" + str(self._valid) + ")")
def write(self, name=""):
"""Writes data in xml file format.
Writes the attribute data in the appropriate format.
Args:
name: An optional string giving the attribute name. Defaults to "".
Returns:
A string giving the stored value in the appropriate format.
"""
return name + "='" + write_type(self.type, self.value) + "'"
class InputValue(InputAttribute):
"""Scalar class for input handling.
Has the methods for dealing with simple data tags of the form:
<tag_name> data </tag_name>, where data is just a value. Takes the data and
converts it to the required data_type, so that it can be used in the
simulation.
Attributes:
units: The units that the input data is given in.
_dimension: The dimensionality of the data.
"""
default_dimension = "undefined"
default_units = ""
attribs= { "units" : ( InputAttribute, { "dtype" : str, "help" : "The units the input data is given in.", "default" : default_units } ) }
def __init__(self, help=None, default=None, dtype=None, options=None, dimension=None):
"""Initialises InputValue.
Args:
help: A help string.
dimension: The dimensionality of the value.
default: A default value.
dtype: An optional data type. Defaults to None.
options: An optional list of valid options.
"""
# a note on units handling:
# 1) units are only processed at parse/fetch time:
# internally EVERYTHING is in internal units
# 2) if one adds an explicit "units" attribute to a derived class,
# the internal units handling will be just ignored
if dimension is None:
self._dimension = self.default_dimension
else:
self._dimension = dimension
super(InputValue,self).__init__(help, default, dtype, options)
def store(self, value, units=""):
"""Converts the data to the appropriate data type and units and stores it.
Args:
value: The raw data to be stored.
units: Optional string giving the units that the data should be stored
in.
"""
super(InputValue,self).store(value)
if units != "":
self.units.store(units) #User can define in the code the units to be
#printed
self.value = value
if self._dimension != "undefined":
self.value *= unit_to_user(self._dimension, units, 1.0)
def fetch(self):
"""Returns the stored data in the user defined units."""
super(InputValue,self).fetch()
rval = self.value
if self._dimension != "undefined":
rval *= unit_to_internal(self._dimension, self.units.fetch(), 1.0)
return rval
def write(self, name="", indent=""):
"""Writes data in xml file format.
Writes the data in the appropriate format between appropriate tags.
Args:
name: An optional string giving the tag name. Defaults to "".
indent: An optional string giving the string to be added to the start
of the line, so usually a number of tabs. Defaults to "".
Returns:
A string giving the stored value in the appropriate xml format.
"""
return Input.write(self, name=name, indent=indent, text=write_type(self.type, self.value))
def parse(self, xml=None, text=""):
"""Reads the data for a single value from an xml file.
Args:
xml: An xml_node object containing the all the data for the parent
tag.
text: The data held between the start and end tags.
"""
Input.parse(self, xml=xml, text=text)
self.value = read_type(self.type, self._text)
ELPERLINE = 5
class InputArray(InputValue):
"""Array class for input handling.
Has the methods for dealing with simple data tags of the form:
<tag_name shape="(shape)"> data </tag_name>, where data is an array
of the form [data[0], data[1], ... , data[length]].
Takes the data and converts it to the required data type,
so that it can be used in the simulation. Also holds the shape of the array,
so that we can use a simple 1D list of data to specify a multi-dimensional
array.
Attributes:
shape: The shape of the array.
"""
attribs = copy(InputValue.attribs)
attribs["shape"] = (InputAttribute, {"dtype": tuple, "help": "The shape of the array.", "default": (0,)})
def __init__(self, help=None, default=None, dtype=None, dimension=None):
"""Initialises InputArray.
Args:
help: A help string.
dimension: The dimensionality of the value.
default: A default value.
dtype: An optional data type. Defaults to None.
"""
super(InputArray,self).__init__(help, default, dtype, dimension=dimension)
def store(self, value, units=""):
"""Converts the data to the appropriate data type, shape and units and
stores it.
Args:
value: The raw data to be stored.
units: Optional string giving the units that the data should be stored
in.
"""
super(InputArray,self).store(value=np.array(value, dtype=self.type).flatten().copy(), units=units)
self.shape.store(value.shape)
#if the shape is not specified, assume the array is linear.
if self.shape.fetch() == (0,):
self.shape.store((len(self.value),))
def fetch(self):
"""Returns the stored data in the user defined units."""
value = super(InputArray,self).fetch()
#if the shape is not specified, assume the array is linear.
if self.shape.fetch() == (0,):
value = np.resize(self.value,0).copy()
else:
value = self.value.reshape(self.shape.fetch()).copy()
return value
def write(self, name="", indent=""):
"""Writes data in xml file format.
Writes the data in the appropriate format between appropriate tags. Note
that only ELPERLINE values are printed on each line if there are more
than this in the array. If the values are floats, or another data type
with a fixed width of data output, then they are aligned in columns.
Args:
name: An optional string giving the tag name. Defaults to "".
indent: An optional string giving the string to be added to the start
of the line, so usually a number of tabs. Defaults to "".
Returns:
A string giving the stored value in the appropriate xml format.
"""
rstr = ""
if (len(self.value) > ELPERLINE):
rstr += "\n" + indent + " [ "
else:
rstr += " [ " #inlines the array if it is small enough
for i, v in enumerate(self.value):
if (len(self.value) > ELPERLINE and i > 0 and i%ELPERLINE == 0):
rstr += "\n" + indent + " "
rstr += write_type(self.type, v) + ", "
rstr = rstr.rstrip(", ") #get rid of trailing commas
if (len(self.value) > ELPERLINE):
rstr += " ]\n"
else:
rstr += " ] "
return Input.write(self, name=name, indent=indent, text=rstr)
def parse(self, xml=None, text=""):
"""Reads the data for an array from an xml file.
Args:
xml: An xml_node object containing the all the data for the parent
tag.
text: The data held between the start and end tags.
"""
Input.parse(self, xml=xml, text=text)
self.value = read_array(self.type, self._text)
#if the shape is not specified, assume the array is linear.
if self.shape.fetch() == (0,):
self.shape.store((len(self.value),))

Event Timeline