1"""Contains the classes that are used to write to and read from restart files. 2 3Copyright (C) 2013, Joshua More and Michele Ceriotti 4 5This program is free software: you can redistribute it and/or modify 6it under the terms of the GNU General Public License as published by 7the Free Software Foundation, either version 3 of the License, or 8(at your option) any later version. 9 10This program is distributed in the hope that it will be useful, 11but WITHOUT ANY WARRANTY; without even the implied warranty of 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13GNU General Public License for more details. 14 15You should have received a copy of the GNU General Public License 16along with this program. If not, see <http.//www.gnu.org/licenses/>. 17 18 19The classes defined in this module define the base functions which parse the 20data in the restart files. Each restart object defined has a fields and an 21attributes dictionary, which are filled with the tags and attributes that 22are allowed to be present, along with their default values and data type. 23 24These are then filled with the data from the xml file when the program 25is initialized, and are filled by the values calculated in the program which 26are then output to the checkpoint file when a restart file is required. 27 28Also deals with checking for user input errors, of the form of misspelt tags, 29bad data types, and failure to input required fields. 30 31Classes: 32 Input: Base input class. 33 InputAttribute: Input class for attribute data. 34 InputValue: Input class for scalar objects. 35 InputArray: Input class for arrays. 36 input_default: Class used to create mutable objects dynamically. 37""" 38 39__all__ = ['Input', 'InputValue', 'InputAttribute', 'InputArray', 'input_default'] 40 41import numpy as np 42from copy import copy 43from ipi.utils.io.io_xml import * 44from ipi.utils.units import unit_to_internal, unit_to_user 45 46 47 48class input_default(object): 49 """Contains information required to dynamically create objects 50 51 Used so that we can define mutable default input values to various tags 52 without the usual trouble with having a class object that is also mutable, 53 namely that all members of that class share the same mutable object, so that 54 changing it for one instance of that class changes it for all others. It 55 does this by not holding the mutable default value, but instead the 56 information to create it, so that each instance of an input class can 57 have a separate instance of the default value. 58 59 Attributes: 60 type: Either a class type or function call from which to create the 61 default object. 62 args: A tuple giving positional arguments to be passed to the function. 63 kwargs: A dictionary giving key word arguments to be passed to the 64 function. 65 """ 66 67 def __init__(self, factory, args = None, kwargs = None): 68 """Initializes input_default. 69 70 Args: 71 type: The class or function to be used to create the default object. 72 args: A tuple giving the arguments to be used to initialize 73 the default value. 74 kwargs: A dictionary giving the key word arguments to be used 75 to initialize the default value. 76 """ 77 78 if args is None: 79 args = () 80 if kwargs is None: 81 kwargs = {} 82 # a default will be generated by factory(*args, **kwargs) 83 # *args unpacks the tuple, and is used for positional arguments 84 # **kwargs unpacks the dictionary, and is used for keyword arguments 85 self.factory = factory 86 self.args = args 87 self.kwargs = kwargs 88 89 90class Input(object): 91 """Base class for input handling. 92 93 Has the generic methods for dealing with the xml input file. Parses the input 94 data, outputs the output data, and deals with storing and returning the 95 data obtained during the simulation for the restart files. 96 97 Attributes: 98 fields: A dictionary holding the possible tags contained within the 99 tags for this restart object, which are then turned into the objects 100 held by the object given by this restart object. The dictionary is 101 of the form: 102 {"tag name": ( Input_object, 103 {"default": default value, 104 "dtype": data type, 105 "options": list of available options, 106 "help": help string, 107 "dimension": dimensionality of data}), ... }. 108 dynamic: A dictionary holding the possible tags contained within the 109 tags for this restart object, which are then turned into the objects 110 held by the object given by this restart object. These are used for 111 tags that can be specified more than once. 112 The dictionary is of the form: 113 {"tag name": ( Input_object, 114 {"default": default value, 115 "dtype": data type, 116 "options": list of available options, 117 "help": help string, 118 "dimension": dimensionality of data}), ... }. 119 attribs: A dictionary holding the attribute data for the tag for this 120 restart object. The dictionary is of the form: 121 {"attribute name": ( Input_object, 122 {"default": default value, 123 "dtype": data type, 124 "options": list of available options, 125 "help": help string, 126 "dimension": dimensionality of data}), ... }. 127 extra: A list of tuples ( "name", Input_object ) that may be used to 128 extend the capabilities of the class, i.e. to hold several instances of 129 a field with the same name, or to hold variable numbers of elements. 130 default_help: The default help string. 131 _help: The help string of the object. Defaults to default_help. 132 _default: Optional default value. 133 _optional: A bool giving whether the field is a required field. 134 _explicit: A bool giving whether the field has been specified by the user. 135 _text: All text written between the tags of the object. 136 _label: A label to be used to identify the class in the latex user manual. 137 _defwrite: The string which would be output if the class has its default 138 value. 139 """ 140 141 fields = {} 142 attribs = {} 143 dynamic = {} 144 145 default_help = "Generic input value" 146 default_label = "" #used as a way to reference a particular class using 147 #hyperlinks 148 149 def __init__(self, help=None, default=None): 150 """Initializes Input. 151 152 Automatically adds all the fields and attribs names to the input object's 153 dictionary, then initializes all the appropriate input objects 154 as the corresponding values. 155 156 Args: 157 help: A help string. 158 default: A default value. 159 """ 160 161 # list of extended (dynamic) fields 162 self.extra = [] 163 164 if help is None: 165 self._help = self.default_help 166 else: 167 self._help = help 168 169 if isinstance(default,input_default): 170 #creates default dynamically if a suitable template is defined. 171 self._default = default.factory(*default.args, **default.kwargs) 172 else: 173 self._default = default 174 175 self._optional = not (self._default is None) 176 177 self._label = self.default_label 178 179 #For each tag name in the fields and attribs dictionaries, 180 #creates and object of the type given, expanding the dictionary to give 181 #the arguments of the __init__() function, then adds it to the input 182 #object's dictionary. 183 for f, v in self.fields.iteritems(): 184 self.__dict__[f] = v[0](**v[1]) 185 186 for a, v in self.attribs.iteritems(): 187 self.__dict__[a] = v[0](**v[1]) 188 189 self.set_default() 190 191 self._text = "" 192 193 # stores what we would write out if the default was set 194 self._defwrite = "" 195 if not self._default is None: 196 self._defwrite = self.write(name="%%NAME%%") 197 198 def set_default(self): 199 """Sets the default value of the object.""" 200 201 if not self._default is None: 202 self.store(self._default) 203 elif not hasattr(self, 'value'): 204 self.value = None #Makes sure we don't get exceptions when we 205 #look for self.value 206 207 self._explicit = False #Since the value was not set by the user 208 209 def store(self, value=None): 210 """Dummy function for storing data.""" 211 212 self._explicit = True 213 pass 214 215 def fetch(self): 216 """Dummy function to retrieve data.""" 217 218 self.check() 219 pass 220 221 def check(self): 222 """Base function to check for input errors. 223 224 Raises: 225 ValueError: Raised if the user does not specify a required field. 226 """ 227 228 if not (self._explicit or self._optional): 229 raise ValueError("Uninitialized Input value of type " + type(self).__name__) 230 231 def extend(self, name, xml): 232 """ Dynamically add elements to the 'extra' list. 233 234 Picks from one of the templates in the self.dynamic dictionary, then 235 parses. 236 237 Args: 238 name: The tag name of the dynamically stored tag. 239 xml: The xml_node object used to parse the data stored in the tags. 240 """ 241 242 newfield = self.dynamic[name][0](**self.dynamic[name][1]) 243 newfield.parse(xml) 244 self.extra.append((name,newfield)) 245 246 def write(self, name="", indent="", text="\n"): 247 """Writes data in xml file format. 248 249 Writes the tag, attributes, data and closing tag appropriate to the 250 particular fields and attribs data. Writes in a recursive manner, so 251 that objects contained in the fields dictionary have their write function 252 called, so that their tags are written between the start and end tags 253 of this object, as is required for the xml format. 254 255 This also adds an indent to the lower levels of the xml hierarchy, 256 so that it is easy to see which tags contain other tags. 257 258 Args: 259 name: An optional string giving the tag name. Defaults to "". 260 indent: An optional string giving the string to be added to the start 261 of the line, so usually a number of tabs. Defaults to "". 262 text: Additional text to be output between the tags. 263 264 Returns: 265 A string giving all the data contained in the fields and attribs 266 dictionaries, in the appropriate xml format. 267 """ 268 269 rstr = indent + "<" + name; 270 for a in self.attribs: 271 # only write out attributes that are not defaults 272 # have a very simple way to check whether they actually add something: 273 # we compare with the string that would be output if the argument was set 274 # to its default 275 defstr = self.__dict__[a]._defwrite.replace("%%NAME%%",a) 276 outstr = self.__dict__[a].write(name=a) 277 if outstr != defstr: 278 rstr += " " + outstr 279 rstr += ">" 280 rstr += text 281 for f in self.fields: 282 #only write out fields that are not defaults 283 284 defstr = self.__dict__[f]._defwrite.replace("%%NAME%%",f) 285 if defstr != self.__dict__[f].write(f): # here we must compute the write string twice not to be confused by indents. 286 rstr += self.__dict__[f].write(f, " " + indent) 287 288 for (f,v) in self.extra: 289 # also write out extended (dynamic) fields if present 290 rstr += v.write(f, " " + indent) 291 292 if text.find('\n') >= 0: 293 rstr += indent + "</" + name + ">\n" 294 else: 295 rstr += "</" + name + ">\n" 296 return rstr 297 298 def parse(self, xml=None, text=""): 299 """Parses an xml file. 300 301 Uses the xml_node class defined in io_xml to read all the information 302 contained within the root tags, and uses it to give values for the attribs 303 and fields data recursively. It does this by giving all the data between 304 the appropriate field tag to the appropriate field restart object as a 305 string, and the appropriate attribute data to the appropriate attribs 306 restart object as a string. These data are then parsed by these objects 307 until all the information is read, or an input error is found. 308 309 Args: 310 xml: An xml_node object containing all the data for the parent 311 tag. 312 text: The data held between the start and end tags. 313 314 Raises: 315 NameError: Raised if one of the tags in the xml input file is 316 incorrect. 317 ValueError: Raised if the user does not specify a required field. 318 """ 319 320 # before starting, sets everything to its default -- if a default is set! 321 for a in self.attribs: 322 self.__dict__[a].set_default() 323 for f in self.fields: 324 self.__dict__[f].set_default() 325 326 self.extra = [] 327 self._explicit = True 328 if xml is None: 329 self._text = text 330 else: 331 for a, v in xml.attribs.iteritems(): 332 if a in self.attribs: 333 self.__dict__[a].parse(text=v) 334 elif a == "_text": 335 pass 336 else: 337 raise NameError("Attribute name '" + a + "' is not a recognized property of '" + xml.name + "' objects") 338 339 for (f, v) in xml.fields: #reads all field and dynamic data. 340 if f in self.fields: 341 self.__dict__[f].parse(xml=v) 342 elif f == "_text": 343 self._text = v 344 elif f in self.dynamic: 345 self.extend(f, v) 346 else: 347 raise NameError("Tag name '" + f + "' is not a recognized property of '" + xml.name + "' objects") 348 349 #checks for missing arguments. 350 for a in self.attribs: 351 va = self.__dict__[a] 352 if not (va._explicit or va._optional): 353 raise ValueError("Attribute name '" + a + "' is mandatory and was not found in the input for the property " + xml.name) 354 for f in self.fields: 355 vf = self.__dict__[f] 356 if not (vf._explicit or vf._optional): 357 raise ValueError("Field name '" + f + "' is mandatory and was not found in the input for the property " + xml.name) 358 359 def detail_str(self): 360 """Prints out the supplementary information about a particular input class. 361 362 Used to print out the dimensions, default value, possible options and data 363 type of an input value to the LaTeX helf file. 364 """ 365 366 xstr = "" 367 if hasattr(self, '_dimension') and self._dimension != "undefined": #gives dimension 368 xstr += "dimension: " + self._dimension + "; " 369 370 if self._default != None and issubclass(self.__class__, InputAttribute): 371 #We only print out the default if it has a well defined value. 372 #For classes such as InputCell, self._default is not the value, 373 #instead it is an object that is stored to give the default value in 374 #self.value. For this reason we print out self.value at this stage, 375 #and not self._default 376 xstr += "default: " + self.pprint(self.value) + "; " 377 378 if issubclass(self.__class__, InputAttribute): 379 #if possible, prints out the type of data that is being used 380 xstr += "data type: " + self.type_print(self.type) + "; " 381 382 if hasattr(self, "_valid"): 383 if self._valid is not None: 384 xstr += "options: " #prints out valid options, if 385 for option in self._valid: #required. 386 xstr += "`" + str(option) + "', " 387 xstr = xstr.rstrip(", ") 388 xstr += "; " 389 return xstr 390 391 def help_latex(self, name="", level=0, stop_level=None, standalone=True): 392 """Function to generate a LaTeX formatted help file. 393 394 Args: 395 name: Name of the tag that has to be written out. 396 level: Current level of the hierarchy being considered. 397 stop_level: The depth to which information will be given. If not given, 398 will give all information. 399 standalone: A boolean giving whether the latex file produced will be a 400 stand-alone document, or will be intended as a section of a larger 401 document with cross-references between the different sections. 402 403 Returns: 404 A LaTeX formatted string. 405 """ 406 407 #stops when we've printed out the prerequisite number of levels 408 if (not stop_level is None and level > stop_level): 409 return "" 410 411 rstr = "" 412 if level == 0: 413 if standalone: 414 #assumes that it is a stand-alone document, so must have 415 #document options. 416 rstr += r"\documentclass[12pt,fleqn]{report}" 417 rstr += r""" 418\usepackage{etoolbox} 419\usepackage{suffix} 420 421\newcommand{\ipiitem}[3]{% 422\setul{1pt}{.4pt}\ifblank{#1}{}{\ifstrequal{#1}{\underline{\smash{}}}{}{ 423{\noindent\textbf{#1}:\rule{0.0pt}{1.05\baselineskip}\quad}}}% uses a strut to add a bit of vertical space 424{#2}\parskip=0pt\par 425\ifblank{#3}{}% 426{ {\hfill\raggedleft\textit{\small #3}\par} } 427} 428 429\makeatletter 430\newenvironment{ipifield}[4]{% 431 \ifblank{#1}{}{\vspace{0.5em}} 432 \noindent\parskip=0pt\begin{tabular}[t]{|p{1.0\linewidth}} 433 %cell without border 434 \multicolumn{1}{@{}p{1.0\linewidth}}{ 435 \ipiitem{\underline{\smash{#1}}}{#2}{} 436 \ifblank{#4}{ % 437 \ifblank{#3}{}{{\hfill\raggedleft\textit{\small #3}}\par}}{} } \vspace{-1em}\\ % 438 % cell with border 439 \ifblank{#4}{} % 440 { \ifblank{#3}{}{\vspace{-1em}{\hfill\raggedleft\textit{\small #3}}\par} % 441 {#4}\vspace{-1em}\\\hline } % negative vspace to undo the line break 442 \end{tabular} 443 \parskip=0pt\list{}{\listparindent 1.5em% 444 \leftmargin \listparindent 445 \rightmargin 0pt 446 \parsep 0pt 447 \itemsep 0pt 448 \topsep 0pt 449 }% 450 \item\relax 451 } 452 {\endlist} 453\makeatother 454""" 455 rstr += "\n\\begin{document}\n" 456 if self._label != "" and not standalone: 457 #assumes that it is part of a cross-referenced document, so only 458 #starts a new section. 459 rstr += "\\section{" + self._label + "}\n" 460 rstr += "\\label{" + self._label + "}\n" 461 462 rstr += "\\begin{ipifield}{}%\n" 463 else: 464 if self._label != "" and not standalone: 465 rstr += "\\begin{ipifield}{\hyperref["+self._label+"]{"+name+"}}%\n" 466 else: 467 rstr += "\\begin{ipifield}{"+name+"}%\n" 468 469 rstr += "{"+self._help+"}%\n" 470 471 rstr += "{"+self.detail_str()+"}%\n" 472 473 rstr += "{" 474 # Prints out the attributes 475 if len(self.attribs) != 0: 476 #don't print out units if not necessary 477 if len(self.attribs) == 1 and (("units" in self.attribs) and self._dimension == "undefined"): 478 pass 479 else: 480 for a in self.attribs: 481 #don't print out units if not necessary 482 if not (a == "units" and self._dimension == "undefined"): 483 rstr += "\\ipiitem{" + a + "}%\n{" + self.__dict__[a]._help + "}%\n{"+self.__dict__[a].detail_str()+"}%\n" #!!MUST ADD OTHER STUFF 484 rstr+="}\n" 485 486 #As above, for the fields. Only prints out if we have not reached the 487 #user-specified limit. 488 if len(self.fields) != 0 and level != stop_level: 489 for f in self.fields: 490 rstr += self.__dict__[f].help_latex(name=f, level=level+1, stop_level=stop_level, standalone=standalone) 491 492 if len(self.dynamic) != 0 and level != stop_level: 493 for f, v in self.dynamic.iteritems(): 494 dummy_obj = v[0](**v[1]) 495 rstr += dummy_obj.help_latex(name=f, level=level+1, stop_level=stop_level, standalone=standalone) 496 497 rstr += "\\end{ipifield}\n" 498 if level == 0 and standalone: 499 #ends the created document if it is not part of a larger document 500 rstr += "\\end{document}" 501 502 #Some escape characters are necessary for the proper latex formatting 503 rstr = rstr.replace('_', '\\_') 504 rstr = rstr.replace('\\\\_', '\\_') 505 rstr = rstr.replace('...', '\\ldots ') 506 rstr = rstr.replace('<', '$<$') 507 rstr = rstr.replace('>', '$>$') 508 509 return rstr 510 511 def pprint(self, default, indent="", latex = True): 512 """Function to convert arrays and other objects to human-readable strings. 513 514 Args: 515 default: The object that needs to be converted to a string. 516 indent: The indent at the beginning of a line. 517 latex: A boolean giving whether the string will be latex-format. 518 519 Returns: 520 A formatted string. 521 """ 522 523 if type(default) is np.ndarray: 524 if default.shape == (0,): 525 return " [ ] " #proper treatment of empty arrays. 526 else: 527 #indents new lines for multi-D arrays properly 528 rstr = "\n" + indent + " " 529 rstr += str(default).replace("\n", "\n" + indent + " ") 530 if not latex: 531 rstr += "\n" + indent + " " 532 533 return rstr 534 elif type(default) == str: 535 if latex: 536 return "`" + default + "'" #indicates that it is a string 537 else: 538 return " " + default + " " 539 elif default == []: 540 return " [ ] " 541 elif default == {}: 542 if latex: 543 return " \\{ \\} " #again, escape characters needed for latex 544 else: #formatting 545 return " { } " 546 else: 547 #in most cases standard formatting will do 548 return " " + str(default) + " " 549 550 def type_print(self, dtype): 551 """Function to convert a data types to human-readable strings. 552 553 Args: 554 dtype: A data type. 555 """ 556 557 if dtype == bool: 558 return "boolean" 559 elif dtype == float or dtype == np.float64: 560 return "float" 561 elif dtype == int or dtype == np.uint64 or dtype == np.int64: 562 return "integer" 563 elif dtype == dict: 564 return "dictionary" 565 elif dtype == str: 566 return "string" 567 elif dtype == tuple: 568 return "tuple" 569 else: 570 raise TypeError("Unrecognized data type " + str(dtype)) 571 572 def help_xml(self, name="", indent="", level=0, stop_level=None): 573 """Function to generate an xml formatted help file. 574 575 Args: 576 name: A string giving the name of the root node. 577 indent: The indent at the beginning of a line. 578 level: Current level of the hierarchy being considered. 579 stop_level: The depth to which information will be given. If not given, 580 all information will be given 581 582 Returns: 583 An xml formatted string. 584 """ 585 586 #stops when we've printed out the prerequisite number of levels 587 if (not stop_level is None and level > stop_level): 588 return "" 589 590 #these are booleans which tell us whether there are any attributes 591 #and fields to print out 592 show_attribs = (len(self.attribs) != 0) 593 show_fields = (not (len(self.fields) == 0 and len(self.dynamic) == 0)) and level != stop_level 594 595 rstr = "" 596 rstr = indent + "<" + name; #prints tag name 597 for a in self.attribs: 598 if not (a == "units" and self._dimension == "undefined"): 599 #don't print out units if not necessary 600 rstr += " " + a + "=''" #prints attribute names 601 rstr += ">\n" 602 603 #prints help string 604 rstr += indent + " <help> " + self._help + " </help>\n" 605 if show_attribs: 606 for a in self.attribs: 607 if not (a == "units" and self._dimension == "undefined"): 608 #information about tags is found in tags beginning with the name 609 #of the attribute 610 rstr += indent + " <" + a + "_help> " + self.__dict__[a]._help + " </" + a + "_help>\n" 611 612 #prints dimensionality of the object 613 if hasattr(self, '_dimension') and self._dimension != "undefined": 614 rstr += indent + " <dimension> " + self._dimension + " </dimension>\n" 615 616 if self._default != None and issubclass(self.__class__, InputAttribute): 617 #We only print out the default if it has a well defined value. 618 #For classes such as InputCell, self._default is not the value, 619 #instead it is an object that is stored, putting the default value in 620 #self.value. For this reason we print out self.value at this stage, 621 #and not self._default 622 rstr += indent + " <default>" + self.pprint(self.value, indent=indent, latex=False) + "</default>\n" 623 if show_attribs: 624 for a in self.attribs: 625 if not (a == "units" and self._dimension == "undefined"): 626 if self.__dict__[a]._default is not None: 627 rstr += indent + " <" + a + "_default>" + self.pprint(self.__dict__[a]._default, indent=indent, latex=False) + "</" + a + "_default>\n" 628 629 #prints out valid options, if required. 630 if hasattr(self, "_valid"): 631 if self._valid is not None: 632 rstr += indent + " <options> " + str(self._valid) + " </options>\n" 633 if show_attribs: 634 for a in self.attribs: 635 if not (a == "units" and self._dimension == "undefined"): 636 if hasattr(self.__dict__[a], "_valid"): 637 if self.__dict__[a]._valid is not None: 638 rstr += indent + " <" + a + "_options> " + str(self.__dict__[a]._valid) + " </" + a + "_options>\n" 639 640 #if possible, prints out the type of data that is being used 641 if issubclass(self.__class__, InputAttribute): 642 rstr += indent + " <dtype> " + self.type_print(self.type) + " </dtype>\n" 643 if show_attribs: 644 for a in self.attribs: 645 if not (a == "units" and self._dimension == "undefined"): 646 rstr += indent + " <" + a + "_dtype> " + self.type_print(self.__dict__[a].type) + " </" + a + "_dtype>\n" 647 648 #repeats the above instructions for any fields or dynamic tags. 649 #these will only be printed if their level in the hierarchy is not above 650 #the user specified limit. 651 if show_fields: 652 for f in self.fields: 653 rstr += self.__dict__[f].help_xml(f, " " + indent, level+1, stop_level) 654 for f, v in self.dynamic.iteritems(): 655 #we must create the object manually, as dynamic objects are 656 #not automatically added to the input object's dictionary 657 dummy_obj = v[0](**v[1]) 658 rstr += dummy_obj.help_xml(f, " " + indent, level+1, stop_level) 659 660 rstr += indent + "</" + name + ">\n" 661 return rstr 662 663 664class InputAttribute(Input): 665 """Class for handling attribute data. 666 667 Has the methods for dealing with attribute data of the form: 668 <tag_name attrib='data'> ..., where data is just a value. Takes the data and 669 converts it to the required data_type, so that it can be used in the 670 simulation. 671 672 Attributes: 673 type: Data type of the data. 674 value: Value of data. Also specifies data type if type is None. 675 _valid: An optional list of valid options. 676 """ 677 678 def __init__(self, help=None, default=None, dtype=None, options=None): 679 """Initializes InputAttribute. 680 681 Args: 682 help: A help string. 683 default: A default value. 684 dtype: An optional data type. Defaults to None. 685 options: An optional list of valid options. 686 """ 687 688 if not dtype is None: 689 self.type = dtype 690 else: 691 raise TypeError("You must provide dtype") 692 693 super(InputAttribute,self).__init__(help, default) 694 695 if options is not None: 696 self._valid = options 697 if not default is None and not self._default in self._valid: 698 #This makes sure that the programmer has set the default value 699 #so that it is a valid value. 700 raise ValueError("Default value '" + str(self._default) + "' not in option list " + str(self._valid)+ "\n" + self._help) 701 else: 702 self._valid = None 703 704 def parse(self, text=""): 705 """Reads the data for a single attribute value from an xml file. 706 707 Args: 708 text: The data held between the start and end tags. 709 """ 710 711 super(InputAttribute, self).parse(text=text) 712 713 self.value = read_type(self.type, self._text) 714 715 def store(self, value): 716 """Stores the input data. 717 718 Args: 719 value: The raw data to be stored. 720 """ 721 super(InputAttribute,self).store(value) 722 self.value = value 723 724 def fetch(self): 725 """Returns the stored data.""" 726 727 super(InputAttribute,self).fetch() 728 return self.value 729 730 def check(self): 731 """Function to check for input errors. 732 733 Raises: 734 ValueError: Raised if the value chosen is not one of the valid options. 735 """ 736 737 super(InputAttribute,self).check() 738 if not (self._valid is None or self.value in self._valid): 739 #This checks that the user has set the value to a valid value. 740 raise ValueError(str(self.value) + " is not a valid option (" + str(self._valid) + ")") 741 742 def write(self, name=""): 743 """Writes data in xml file format. 744 745 Writes the attribute data in the appropriate format. 746 747 Args: 748 name: An optional string giving the attribute name. Defaults to "". 749 750 Returns: 751 A string giving the stored value in the appropriate format. 752 """ 753 754 return name + "='" + write_type(self.type, self.value) + "'" 755 756 757class InputValue(InputAttribute): 758 """Scalar class for input handling. 759 760 Has the methods for dealing with simple data tags of the form: 761 <tag_name> data </tag_name>, where data is just a value. Takes the data and 762 converts it to the required data_type, so that it can be used in the 763 simulation. 764 765 Attributes: 766 units: The units that the input data is given in. 767 _dimension: The dimensionality of the data. 768 """ 769 770 default_dimension = "undefined" 771 default_units = "" 772 773 attribs= { "units" : ( InputAttribute, { "dtype" : str, "help" : "The units the input data is given in.", "default" : default_units } ) } 774 775 def __init__(self, help=None, default=None, dtype=None, options=None, dimension=None): 776 """Initializes InputValue. 777 778 Args: 779 help: A help string. 780 dimension: The dimensionality of the value. 781 default: A default value. 782 dtype: An optional data type. Defaults to None. 783 options: An optional list of valid options. 784 """ 785 786 # a note on units handling: 787 # 1) units are only processed at parse/fetch time: 788 # internally EVERYTHING is in internal units 789 # 2) if one adds an explicit "units" attribute to a derived class, 790 # the internal units handling will be just ignored 791 if dimension is None: 792 self._dimension = self.default_dimension 793 else: 794 self._dimension = dimension 795 796 super(InputValue,self).__init__(help, default, dtype, options) 797 798 def store(self, value, units=""): 799 """Converts the data to the appropriate data type and units and stores it. 800 801 Args: 802 value: The raw data to be stored. 803 units: Optional string giving the units that the data should be stored 804 in. 805 """ 806 807 super(InputValue,self).store(value) 808 809 if units != "": 810 self.units.store(units) #User can define in the code the units to be 811 #printed 812 813 self.value = value 814 if self._dimension != "undefined": 815 self.value *= unit_to_user(self._dimension, units, 1.0) 816 817 def fetch(self): 818 """Returns the stored data in the user defined units.""" 819 820 super(InputValue,self).fetch() 821 822 rval = self.value 823 if self._dimension != "undefined": 824 rval *= unit_to_internal(self._dimension, self.units.fetch(), 1.0) 825 return rval 826 827 def write(self, name="", indent=""): 828 """Writes data in xml file format. 829 830 Writes the data in the appropriate format between appropriate tags. 831 832 Args: 833 name: An optional string giving the tag name. Defaults to "". 834 indent: An optional string giving the string to be added to the start 835 of the line, so usually a number of tabs. Defaults to "". 836 837 Returns: 838 A string giving the stored value in the appropriate xml format. 839 """ 840 841 return Input.write(self, name=name, indent=indent, text=write_type(self.type, self.value)) 842 843 def parse(self, xml=None, text=""): 844 """Reads the data for a single value from an xml file. 845 846 Args: 847 xml: An xml_node object containing the all the data for the parent 848 tag. 849 text: The data held between the start and end tags. 850 """ 851 852 Input.parse(self, xml=xml, text=text) 853 self.value = read_type(self.type, self._text) 854 855 856ELPERLINE = 5 857class InputArray(InputValue): 858 """Array class for input handling. 859 860 Has the methods for dealing with simple data tags of the form: 861 <tag_name shape="(shape)"> data </tag_name>, where data is an array 862 of the form [data[0], data[1], ... , data[length]]. 863 864 Takes the data and converts it to the required data type, 865 so that it can be used in the simulation. Also holds the shape of the array, 866 so that we can use a simple 1D list of data to specify a multi-dimensional 867 array. 868 869 Attributes: 870 shape: The shape of the array. 871 """ 872 873 attribs = copy(InputValue.attribs) 874 attribs["shape"] = (InputAttribute, {"dtype": tuple, "help": "The shape of the array.", "default": (0,)}) 875 876 def __init__(self, help=None, default=None, dtype=None, dimension=None): 877 """Initializes InputArray. 878 879 Args: 880 help: A help string. 881 dimension: The dimensionality of the value. 882 default: A default value. 883 dtype: An optional data type. Defaults to None. 884 """ 885 886 super(InputArray,self).__init__(help, default, dtype, dimension=dimension) 887 888 def store(self, value, units=""): 889 """Converts the data to the appropriate data type, shape and units and 890 stores it. 891 892 Args: 893 value: The raw data to be stored. 894 units: Optional string giving the units that the data should be stored 895 in. 896 """ 897 898 super(InputArray,self).store(value=np.array(value, dtype=self.type).flatten().copy(), units=units) 899 self.shape.store(value.shape) 900 901 #if the shape is not specified, assume the array is linear. 902 if self.shape.fetch() == (0,): 903 self.shape.store((len(self.value),)) 904 905 def fetch(self): 906 """Returns the stored data in the user defined units.""" 907 908 value = super(InputArray,self).fetch() 909 910 #if the shape is not specified, assume the array is linear. 911 if self.shape.fetch() == (0,): 912 value = np.resize(self.value,0).copy() 913 else: 914 value = self.value.reshape(self.shape.fetch()).copy() 915 916 return value 917 918 def write(self, name="", indent=""): 919 """Writes data in xml file format. 920 921 Writes the data in the appropriate format between appropriate tags. Note 922 that only ELPERLINE values are printed on each line if there are more 923 than this in the array. If the values are floats, or another data type 924 with a fixed width of data output, then they are aligned in columns. 925 926 Args: 927 name: An optional string giving the tag name. Defaults to "". 928 indent: An optional string giving the string to be added to the start 929 of the line, so usually a number of tabs. Defaults to "". 930 931 Returns: 932 A string giving the stored value in the appropriate xml format. 933 """ 934 935 rstr = "" 936 if (len(self.value) > ELPERLINE): 937 rstr += "\n" + indent + " [ " 938 else: 939 rstr += " [ " #inlines the array if it is small enough 940 941 for i, v in enumerate(self.value): 942 if (len(self.value) > ELPERLINE and i > 0 and i%ELPERLINE == 0): 943 rstr += "\n" + indent + " " 944 rstr += write_type(self.type, v) + ", " 945 946 rstr = rstr.rstrip(", ") #get rid of trailing commas 947 if (len(self.value) > ELPERLINE): 948 rstr += " ]\n" 949 else: 950 rstr += " ] " 951 952 return Input.write(self, name=name, indent=indent, text=rstr) 953 954 def parse(self, xml=None, text=""): 955 """Reads the data for an array from an xml file. 956 957 Args: 958 xml: An xml_node object containing the all the data for the parent 959 tag. 960 text: The data held between the start and end tags. 961 """ 962 963 Input.parse(self, xml=xml, text=text) 964 self.value = read_array(self.type, self._text) 965 966 #if the shape is not specified, assume the array is linear. 967 if self.shape.fetch() == (0,): 968 self.shape.store((len(self.value),)) 969