/[escript]/trunk/escript/py_src/modelframe.py
ViewVC logotype

Contents of /trunk/escript/py_src/modelframe.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2169 - (show annotations)
Wed Dec 17 03:08:58 2008 UTC (10 years, 8 months ago) by caltinay
File MIME type: text/x-python
File size: 47370 byte(s)
Assorted spelling, grammar, whitespace and copy/paste error fixes (Part 2).
All epydoc warnings for these files have been fixed.
This commit should be a no-op.

1
2 ########################################################
3 #
4 # Copyright (c) 2003-2008 by University of Queensland
5 # Earth Systems Science Computational Center (ESSCC)
6 # http://www.uq.edu.au/esscc
7 #
8 # Primary Business: Queensland, Australia
9 # Licensed under the Open Software License version 3.0
10 # http://www.opensource.org/licenses/osl-3.0.php
11 #
12 ########################################################
13
14 __copyright__="""Copyright (c) 2003-2008 by University of Queensland
15 Earth Systems Science Computational Center (ESSCC)
16 http://www.uq.edu.au/esscc
17 Primary Business: Queensland, Australia"""
18 __license__="""Licensed under the Open Software License version 3.0
19 http://www.opensource.org/licenses/osl-3.0.php"""
20 __url__="http://www.uq.edu.au/esscc/escript-finley"
21
22 """
23 Environment for implementing models in escript
24
25 @var __author__: name of author
26 @var __copyright__: copyrights
27 @var __license__: licence agreement
28 @var __url__: url entry point on documentation
29 @var __version__: version
30 @var __date__: date of the version
31 """
32
33 __author__="Lutz Gross, l.gross@uq.edu.au"
34
35
36 from types import StringType,IntType,FloatType,BooleanType,ListType,DictType
37 from sys import stdout
38 import numarray
39 import operator
40 import itertools
41 import time
42 import os
43
44 # import the 'set' module if it's not defined (python2.3/2.4 difference)
45 try:
46 set
47 except NameError:
48 from sets import Set as set
49
50 from xml.dom import minidom
51
52
53 def all(seq):
54 """
55 Returns True if no element in C{seq} is C{None}, False otherwise.
56 """
57 for x in seq:
58 if not x:
59 return False
60 return True
61
62 def any(seq):
63 """
64 Returns True if not all elements in C{seq} are C{None}, False otherwise.
65 """
66 for x in seq:
67 if x:
68 return True
69 return False
70
71 def importName(modulename, name):
72 """
73 Imports a named object from a module in the context of this function,
74 which means you should use fully qualified module paths.
75 Returns None on failure.
76
77 This function is from U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52241}
78 """
79 module = __import__(modulename, globals(), locals(), [name])
80
81 try:
82 return vars(module)[name]
83 except KeyError:
84 raise ImportError("Could not import %s from %s" % (name, modulename))
85
86 class ESySXMLParser(object):
87 """
88 Parser for an ESysXML file.
89 """
90 def __init__(self,xml, debug=False):
91 self.__dom = minidom.parseString(xml)
92 self.__linkable_object_registry= {}
93 self.__link_registry= []
94 self.__esys=self.__dom.getElementsByTagName('ESys')[0]
95 self.debug=debug
96
97 def getClassPath(self, node):
98 type = node.getAttribute("type")
99 if (node.getAttribute("module")):
100 module = node.getAttribute("module")
101 return importName(module, type)
102 else:
103 return importName("__main__", type)
104
105 def setLinks(self):
106 for obj_id, link in self.__link_registry:
107 link.target = self.__linkable_object_registry[obj_id]
108
109 def parse(self):
110 """
111 Parses EsysXML and returns the list of generating ParameterSets.
112 """
113 found=[]
114 for node in self.__esys.childNodes:
115 if isinstance(node, minidom.Element):
116 if node.tagName == 'Simulation':
117 found.append(Simulation.fromDom(self, node))
118 elif node.tagName == 'Model':
119 found.append(self.getClassPath(node).fromDom(self, node))
120 elif node.tagName == 'ParameterSet':
121 found.append(self.getClassPath(node).fromDom(self, node))
122 else:
123 raise "Invalid type, %r" % node.getAttribute("type")
124 self.setLinks()
125 return found
126
127 def registerLink(self,obj_id, link):
128 self.__link_registry.append((int(obj_id),link))
129
130 def registerLinkableObject(self,obj, node):
131 id_str=node.getAttribute('id').strip()
132 if len(id_str)>0:
133 id=int(id_str)
134 if self.__linkable_object_registry.has_key(id):
135 raise ValueError("Object id %s already exists."%id)
136 else:
137 self.__linkable_object_registry[id]=obj
138
139 def getComponent(self, node):
140 """
141 Returns a single component + rank from a simulation.
142 """
143 rank = int(node.getAttribute("rank"))
144 for n in node.childNodes:
145 if isinstance(n, minidom.Element):
146 if n.tagName == 'Simulation':
147 return (rank, Simulation.fromDom(self, n))
148 elif n.tagName == 'Model':
149 return (rank, self.getClassPath(n).fromDom(self, n))
150 elif n.tagName == 'ParameterSet':
151 return (rank, self.getClassPath(n).fromDom(self, n))
152 else:
153 raise ValueError("illegal component type %s"%n.tagName)
154 raise ValueError("cannot resolve Component")
155
156 class ESySXMLCreator(object):
157 """
158 Creates an XML Dom representation.
159 """
160 def __init__(self):
161 self.__dom=minidom.Document()
162 self.__esys =self.__dom.createElement('ESys')
163 self.__dom.appendChild(self.__esys)
164 self.__linkable_object_registry={}
165 self.__number_sequence = itertools.count(100)
166
167 def getRoot(self):
168 return self.__esys
169
170 def createElement(self,name):
171 return self.__dom.createElement(name)
172
173 def createTextNode(self,name):
174 return self.__dom.createTextNode(name)
175
176 def getElementById(self,name):
177 return self.__dom.getElementById(name)
178
179 def createDataNode(self, tagName, data):
180 """
181 C{createDataNode}s are the building blocks of the XML documents
182 constructed in this module.
183
184 @param tagName: the associated XML tag
185 @param data: the values in the tag
186 """
187 n = self.createElement(tagName)
188 n.appendChild(self.createTextNode(str(data)))
189 return n
190
191 def getLinkableObjectId(self, obj):
192 for id, o in self.__linkable_object_registry.items():
193 if o == obj: return id
194 id =self.__number_sequence.next()
195 self.__linkable_object_registry[id]=obj
196 return id
197
198 def registerLinkableObject(self, obj, node):
199 """
200 Returns a unique object id for object C{obj}.
201 """
202 id=self.getLinkableObjectId(obj)
203 node.setAttribute('id',str(id))
204 node.setIdAttribute("id")
205
206 def includeTargets(self):
207 target_written=True
208 while target_written:
209 targetsList =self.__dom.getElementsByTagName('Target')
210 target_written=False
211 for element in targetsList:
212 targetId = int(element.firstChild.nodeValue.strip())
213 if self.getElementById(str(targetId)): continue
214 targetObj = self.__linkable_object_registry[targetId]
215 targetObj.toDom(self, self.__esys)
216 target_written=True
217
218 def toprettyxml(self):
219 self.includeTargets()
220 return self.__dom.toprettyxml()
221
222 class Link:
223 """
224 A Link makes an attribute of an object callable::
225
226 o.object()
227 o.a=8
228 l=Link(o,"a")
229 assert l()==8
230 """
231
232 def __init__(self,target,attribute=None):
233 """
234 Creates a link to the object target. If attribute is given, the link is
235 established to this attribute of the target. Otherwise the attribute is
236 undefined.
237 """
238 self.target = target
239 self.attribute = None
240 self.setAttributeName(attribute)
241
242 def getTarget(self):
243 """
244 Returns the target.
245 """
246 return self.target
247
248 def getAttributeName(self):
249 """
250 Returns the name of the attribute the link is pointing to.
251 """
252 return self.attribute
253
254 def setAttributeName(self,attribute):
255 """
256 Sets a new attribute name to be collected from the target object. The
257 target object must have the attribute with name attribute.
258 """
259 if attribute and self.target:
260 if isinstance(self.target,LinkableObject):
261 if not self.target.hasAttribute(attribute):
262 raise AttributeError("%s: target %s has no attribute %s."%(self, self.target, attribute))
263 else:
264 if not hasattr(self.target,attribute):
265 raise AttributeError("%s: target %s has no attribute %s."%(self, self.target, attribute))
266 self.attribute = attribute
267
268 def hasDefinedAttributeName(self):
269 """
270 Returns true if an attribute name is set.
271 """
272 return self.attribute != None
273
274 def __repr__(self):
275 """
276 Returns a string representation of the link.
277 """
278 if self.hasDefinedAttributeName():
279 return "<Link to attribute %s of %s>" % (self.attribute, self.target)
280 else:
281 return "<Link to target %s>" % self.target
282
283 def __call__(self,name=None):
284 """
285 Returns the value of the attribute of the target object. If the
286 attribute is callable then the return value of the call is returned.
287 """
288 if name:
289 out=getattr(self.target, name)
290 else:
291 out=getattr(self.target, self.attribute)
292
293 if callable(out):
294 return out()
295 else:
296 return out
297
298 def toDom(self, esysxml, node):
299 """
300 C{toDom} method of Link. Creates a Link node and appends it to the
301 current XML esysxml.
302 """
303 link = esysxml.createElement('Link')
304 assert (self.target != None), ("Target was none, name was %r" % self.attribute)
305 link.appendChild(esysxml.createDataNode('Target', esysxml.getLinkableObjectId(self.target)))
306 # this use of id will not work for purposes of being able to retrieve the intended
307 # target from the xml later. I need a better unique identifier.
308 assert self.attribute, "You can't xmlify a Link without a target attribute"
309 link.appendChild(esysxml.createDataNode('Attribute', self.attribute))
310 node.appendChild(link)
311
312 def fromDom(cls, esysxml, node):
313 targetid = int(node.getElementsByTagName("Target")[0].firstChild.nodeValue.strip())
314 attribute =str(node.getElementsByTagName("Attribute")[0].firstChild.nodeValue.strip())
315 l = cls(None, attribute)
316 esysxml.registerLink(targetid, l)
317 return l
318
319 fromDom = classmethod(fromDom)
320
321 class LinkableObject(object):
322 """
323 An object that allows to link its attributes to attributes of other objects
324 via a Link object. For instance::
325
326 p = LinkableObject()
327 p.x = Link(o,"name")
328 print p.x
329
330 links attribute C{x} of C{p} to the attribute name of object C{o}.
331
332 C{p.x} will contain the current value of attribute C{name} of object C{o}.
333
334 If the value of C{getattr(o, "name")} is callable, C{p.x} will return
335 the return value of the call.
336 """
337
338 def __init__(self, id = None, debug=False):
339 """
340 Initializes LinkableObject so that we can operate on Links.
341 """
342 self.debug = debug
343 self.__linked_attributes={}
344
345 def trace(self, msg):
346 """
347 If debugging is on, prints the message, otherwise does nothing.
348 """
349 if self.debug:
350 print "%s: %s"%(str(self),msg)
351
352 def __getattr__(self,name):
353 """
354 Returns the value of attribute name. If the value is a Link object the
355 object is called and the return value is returned.
356 """
357 out = self.getAttributeObject(name)
358 if isinstance(out,Link):
359 return out()
360 else:
361 return out
362
363 def getAttributeObject(self,name):
364 """
365 Returns the object stored for attribute C{name}.
366 """
367
368 if self.__dict__.has_key(name):
369 return self.__dict__[name]
370
371 if self.__linked_attributes.has_key(name):
372 return self.__linked_attributes[name]
373
374 if self.__class__.__dict__.has_key(name):
375 return self.__class.__dict__[name]
376
377 raise AttributeError,"No attribute %s."%name
378
379 def hasAttribute(self,name):
380 """
381 Returns True if self has attribute C{name}.
382 """
383 return self.__dict__.has_key(name) or self.__linked_attributes.has_key(name) or self.__class__.__dict__.has_key(name)
384
385 def __setattr__(self,name,value):
386 """
387 Sets the value for attribute name. If value is a Link the target
388 attribute is set to name if no attribute has been specified.
389 """
390
391 if self.__dict__.has_key(name):
392 del self.__dict__[name]
393
394 if isinstance(value,Link):
395 if not value.hasDefinedAttributeName():
396 value.setAttributeName(name)
397 self.__linked_attributes[name] = value
398
399 self.trace("attribute %s is now linked by %s."%(name,value))
400 else:
401 self.__dict__[name] = value
402
403 def __delattr__(self,name):
404 """
405 Removes the attribute C{name}.
406 """
407
408 if self.__linked_attributes.has_key[name]:
409 del self.__linked_attributes[name]
410 elif self.__dict__.has_key(name):
411 del self.__dict__[name]
412 else:
413 raise AttributeError,"No attribute %s."%name
414
415 class _ParameterIterator:
416 def __init__(self,parameterset):
417
418 self.__set=parameterset
419 self.__iter=iter(parameterset.parameters)
420
421 def next(self):
422 o=self.__iter.next()
423 return (o,self.__set.getAttributeObject(o))
424
425 def __iter__(self):
426 return self
427
428 class ParameterSet(LinkableObject):
429 """
430 A class which allows to emphasize attributes to be written and read to XML.
431
432 Leaves of an ESySParameters object can be:
433
434 - a real number
435 - an integer number
436 - a string
437 - a boolean value
438 - a ParameterSet object
439 - a Simulation object
440 - a Model object
441 - a numarray object
442 - a list of booleans
443 - any other object (not considered by writeESySXML and writeXML)
444
445 Example for how to create an ESySParameters object::
446
447 p11=ParameterSet(gamma1=1.,gamma2=2.,gamma3=3.)
448 p1=ParameterSet(dim=2,tol_v=0.001,output_file="/tmp/u.%3.3d.dx",runFlag=True,parm11=p11)
449 parm=ParameterSet(parm1=p1,parm2=ParameterSet(alpha=Link(p11,"gamma1")))
450
451 This can be accessed as::
452
453 parm.parm1.gamma=0.
454 parm.parm1.dim=2
455 parm.parm1.tol_v=0.001
456 parm.parm1.output_file="/tmp/u.%3.3d.dx"
457 parm.parm1.runFlag=True
458 parm.parm1.parm11.gamma1=1.
459 parm.parm1.parm11.gamma2=2.
460 parm.parm1.parm11.gamma3=3.
461 parm.parm2.alpha=1. (value of parm.parm1.parm11.gamma1)
462 """
463 def __init__(self, parameters=[], **kwargs):
464 """
465 Creates a ParameterSet with given parameters.
466 """
467 LinkableObject.__init__(self, **kwargs)
468 self.parameters = set()
469 self.declareParameters(parameters)
470
471 def __repr__(self):
472 return "<%s %d>"%(self.__class__.__name__,id(self))
473
474 def declareParameter(self,**parameters):
475 """
476 Declares one or more new parameters and their initial value.
477 """
478 self.declareParameters(parameters)
479
480 def declareParameters(self,parameters):
481 """
482 Declares a set of parameters. parameters can be a list, a dictionary
483 or a ParameterSet.
484 """
485 if isinstance(parameters,ListType):
486 parameters = zip(parameters, itertools.repeat(None))
487 if isinstance(parameters,DictType):
488 parameters = parameters.iteritems()
489
490 for prm, value in parameters:
491 setattr(self,prm,value)
492 self.parameters.add(prm)
493
494 def releaseParameters(self,name):
495 """
496 Removes parameter name from the parameters.
497 """
498 if self.isParameter(name):
499 self.parameters.remove(name)
500 self.trace("parameter %s has been removed."%name)
501
502 def checkLinkTargets(self, models, hash):
503 """
504 Returns a set of tuples ("<self>(<name>)", <target model>) if the
505 parameter <name> is linked to model <target model> but <target model>
506 is not in the list of models. If a parameter is linked to another
507 parameter set which is not in the hash list the parameter set is
508 checked for its models. hash gives the call history.
509 """
510 out=set()
511 for name, value in self:
512 if isinstance(value, Link):
513 m=value.getTarget()
514 if isinstance(m, Model):
515 if not m in models: out.add( (str(self)+"("+name+")",m) )
516 elif isinstance(m, ParameterSet) and not m in hash:
517 out|=set( [ (str(self)+"("+name+")."+f[0],f[1]) for f in m.checkLinkTargets(models, hash+[ self ] ) ] )
518 return out
519
520 def __iter__(self):
521 """
522 Creates an iterator over the parameter and their values.
523 """
524 return _ParameterIterator(self)
525
526 def showParameters(self):
527 """
528 Returns a description of the parameters.
529 """
530 out="{"
531 notfirst=False
532 for i,v in self:
533 if notfirst: out=out+","
534 notfirst=True
535 if isinstance(v,ParameterSet):
536 out="%s\"%s\" : %s"%(out,i,v.showParameters())
537 else:
538 out="%s\"%s\" : %s"%(out,i,v)
539 return out+"}"
540
541 def __delattr__(self,name):
542 """
543 Removes the attribute C{name}.
544 """
545 LinkableObject.__delattr__(self,name)
546 try:
547 self.releaseParameter(name)
548 except:
549 pass
550
551 def toDom(self, esysxml, node):
552 """
553 C{toDom} method of Model class.
554 """
555 pset = esysxml.createElement('ParameterSet')
556 pset.setAttribute('type', self.__class__.__name__)
557 pset.setAttribute('module', self.__class__.__module__)
558 esysxml.registerLinkableObject(self, pset)
559 self._parametersToDom(esysxml, pset)
560 node.appendChild(pset)
561
562 def _parametersToDom(self, esysxml, node):
563 for name,value in self:
564 # convert list to numarray when possible:
565 if isinstance (value, list):
566 elem_type=-1
567 for i in value:
568 if isinstance(i, bool):
569 elem_type = max(elem_type,0)
570 elif isinstance(i, int):
571 elem_type = max(elem_type,1)
572 elif isinstance(i, float):
573 elem_type = max(elem_type,2)
574 if elem_type == 0: value = numarray.array(value,numarray.Bool)
575 if elem_type == 1: value = numarray.array(value,numarray.Int)
576 if elem_type == 2: value = numarray.array(value,numarray.Float)
577
578 param = esysxml.createElement('Parameter')
579 param.setAttribute('type', value.__class__.__name__)
580
581 param.appendChild(esysxml.createDataNode('Name', name))
582
583 val = esysxml.createElement('Value')
584 if isinstance(value,(ParameterSet,Link,DataSource)):
585 value.toDom(esysxml, val)
586 param.appendChild(val)
587 elif isinstance(value, numarray.NumArray):
588 shape = value.getshape()
589 if isinstance(shape, tuple):
590 size = reduce(operator.mul, shape)
591 shape = ' '.join(map(str, shape))
592 else:
593 size = shape
594 shape = str(shape)
595
596 arraytype = value.type()
597 if isinstance(arraytype, numarray.BooleanType):
598 arraytype_str="Bool"
599 elif isinstance(arraytype, numarray.IntegralType):
600 arraytype_str="Int"
601 elif isinstance(arraytype, numarray.FloatingType):
602 arraytype_str="Float"
603 elif isinstance(arraytype, numarray.ComplexType):
604 arraytype_str="Complex"
605 else:
606 arraytype_str=str(arraytype)
607 numarrayElement = esysxml.createElement('NumArray')
608 numarrayElement.appendChild(esysxml.createDataNode('ArrayType', arraytype_str))
609 numarrayElement.appendChild(esysxml.createDataNode('Shape', shape))
610 numarrayElement.appendChild(esysxml.createDataNode('Data', ' '.join(
611 [str(x) for x in numarray.reshape(value, size)])))
612 val.appendChild(numarrayElement)
613 param.appendChild(val)
614 elif isinstance(value, list):
615 param.appendChild(esysxml.createDataNode('Value', ' '.join([str(x) for x in value]) ))
616 elif isinstance(value, (str, bool, int, float, type(None))):
617 param.appendChild(esysxml.createDataNode('Value', str(value)))
618 elif isinstance(value, dict):
619 dic = esysxml.createElement('dictionary')
620 if len(value.keys())>0:
621 dic.setAttribute('key_type', value.keys()[0].__class__.__name__)
622 dic.setAttribute('value_type', value[value.keys()[0]].__class__.__name__)
623 for k,v in value.items():
624 i=esysxml.createElement('item')
625 i.appendChild(esysxml.createDataNode('key', k))
626 i.appendChild(esysxml.createDataNode('value', v))
627 dic.appendChild(i)
628 param.appendChild(dic)
629 else:
630 raise ValueError("cannot serialize %s type to XML."%str(value.__class__))
631
632 node.appendChild(param)
633
634 def fromDom(cls, esysxml, node):
635 # Define a host of helper functions to assist us.
636 def _children(node):
637 """
638 Remove the empty nodes from the children of this node.
639 """
640 ret = []
641 for x in node.childNodes:
642 if isinstance(x, minidom.Text):
643 if x.nodeValue.strip():
644 ret.append(x)
645 else:
646 ret.append(x)
647 return ret
648
649 def _floatfromValue(esysxml, node):
650 return float(node.nodeValue.strip())
651
652 def _stringfromValue(esysxml, node):
653 return str(node.nodeValue.strip())
654
655 def _intfromValue(esysxml, node):
656 return int(node.nodeValue.strip())
657
658 def _boolfromValue(esysxml, node):
659 return _boolfromstring(node.nodeValue.strip())
660
661 def _nonefromValue(esysxml, node):
662 return None
663
664 def _numarrayfromValue(esysxml, node):
665 for node in _children(node):
666 if node.tagName == 'ArrayType':
667 arraytype = node.firstChild.nodeValue.strip()
668 if node.tagName == 'Shape':
669 shape = node.firstChild.nodeValue.strip()
670 shape = [int(x) for x in shape.split()]
671 if node.tagName == 'Data':
672 data = node.firstChild.nodeValue.strip()
673 data = [float(x) for x in data.split()]
674 return numarray.reshape(numarray.array(data, type=getattr(numarray, arraytype)),
675 shape)
676
677 def _listfromValue(esysxml, node):
678 return [x for x in node.nodeValue.split()]
679
680 def _boolfromstring(s):
681 if s == 'True':
682 return True
683 else:
684 return False
685 # Mapping from text types in the xml to methods used to process trees of that type
686 ptypemap = {"Simulation": Simulation.fromDom,
687 "Model":Model.fromDom,
688 "ParameterSet":ParameterSet.fromDom,
689 "Link":Link.fromDom,
690 "DataSource":DataSource.fromDom,
691 "float":_floatfromValue,
692 "int":_intfromValue,
693 "str":_stringfromValue,
694 "bool":_boolfromValue,
695 "list":_listfromValue,
696 "NumArray":_numarrayfromValue,
697 "NoneType":_nonefromValue,
698 }
699
700 parameters = {}
701 for n in _children(node):
702 ptype = n.getAttribute("type")
703 if not ptypemap.has_key(ptype):
704 raise KeyError("cannot handle parameter type %s."%ptype)
705
706 pname = pvalue = None
707 for childnode in _children(n):
708 if childnode.tagName == "Name":
709 pname = childnode.firstChild.nodeValue.strip()
710
711 if childnode.tagName == "Value":
712 nodes = _children(childnode)
713 pvalue = ptypemap[ptype](esysxml, nodes[0])
714
715 parameters[pname] = pvalue
716
717 # Create the instance of ParameterSet
718 try:
719 o = cls(debug=esysxml.debug)
720 except TypeError, inst:
721 print inst.args[0]
722 if inst.args[0]=="__init__() got an unexpected keyword argument 'debug'":
723 raise TypeError("The Model class %s __init__ needs to have argument 'debug'.")
724 else:
725 raise inst
726 o.declareParameters(parameters)
727 esysxml.registerLinkableObject(o, node)
728 return o
729
730 fromDom = classmethod(fromDom)
731
732 def writeXML(self,ostream=stdout):
733 """
734 Writes the object as an XML object into an output stream.
735 """
736 esysxml=ESySXMLCreator()
737 self.toDom(esysxml, esysxml.getRoot())
738 ostream.write(esysxml.toprettyxml())
739
740 class Model(ParameterSet):
741 """
742 A Model object represents a process marching over time until a
743 finalizing condition is fulfilled. At each time step an iterative
744 process can be performed and the time step size can be controlled. A
745 Model has the following work flow::
746
747 doInitialization()
748 while not terminateInitialIteration(): doInitializationiStep()
749 doInitialPostprocessing()
750 while not finalize():
751 dt=getSafeTimeStepSize(dt)
752 doStepPreprocessing(dt)
753 while not terminateIteration(): doStep(dt)
754 doStepPostprocessing(dt)
755 doFinalization()
756
757 where C{doInitialization}, C{finalize}, C{getSafeTimeStepSize},
758 C{doStepPreprocessing}, C{terminateIteration}, C{doStepPostprocessing},
759 C{doFinalization} are methods of the particular instance of a Model. The
760 default implementations of these methods have to be overwritten by the
761 subclass implementing a Model.
762 """
763
764 UNDEF_DT=1.e300
765
766 def __init__(self,parameters=[],**kwargs):
767 """
768 Creates a model.
769
770 Just calls the parent constructor.
771 """
772 ParameterSet.__init__(self, parameters=parameters,**kwargs)
773
774 def __str__(self):
775 return "<%s %d>"%(self.__class__.__name__,id(self))
776
777
778 def setUp(self):
779 """
780 Sets up the model.
781
782 This function may be overwritten.
783 """
784 pass
785
786 def doInitialization(self):
787 """
788 Initializes the time stepping scheme. This method is not called in
789 case of a restart.
790
791 This function may be overwritten.
792 """
793 pass
794
795 def doInitialStep(self):
796 """
797 Performs an iteration step in the initialization phase. This method
798 is not called in case of a restart.
799
800 This function may be overwritten.
801 """
802 pass
803
804 def terminateInitialIteration(self):
805 """
806 Returns True if iteration at the inital phase is terminated.
807 """
808 return True
809
810 def doInitialPostprocessing(self):
811 """
812 Finalises the initialization iteration process. This method is not
813 called in case of a restart.
814
815 This function may be overwritten.
816 """
817 pass
818
819 def getSafeTimeStepSize(self,dt):
820 """
821 Returns a time step size which can be safely used.
822
823 C{dt} gives the previously used step size.
824
825 This function may be overwritten.
826 """
827 return self.UNDEF_DT
828
829 def finalize(self):
830 """
831 Returns False if the time stepping is finalized.
832
833 This function may be overwritten.
834 """
835 return False
836
837 def doFinalization(self):
838 """
839 Finalizes the time stepping.
840
841 This function may be overwritten.
842 """
843 pass
844
845 def doStepPreprocessing(self,dt):
846 """
847 Sets up a time step of step size dt.
848
849 This function may be overwritten.
850 """
851 pass
852
853 def doStep(self,dt):
854 """
855 Executes an iteration step at a time step.
856
857 C{dt} is the currently used time step size.
858
859 This function may be overwritten.
860 """
861 pass
862
863 def terminateIteration(self):
864 """
865 Returns True if iteration on a time step is terminated.
866 """
867 return True
868
869 def doStepPostprocessing(self,dt):
870 """
871 Finalises the time step.
872
873 dt is the currently used time step size.
874
875 This function may be overwritten.
876 """
877 pass
878
879 def toDom(self, esysxml, node):
880 """
881 C{toDom} method of Model class.
882 """
883 pset = esysxml.createElement('Model')
884 pset.setAttribute('type', self.__class__.__name__)
885 pset.setAttribute('module', self.__class__.__module__)
886 esysxml.registerLinkableObject(self, pset)
887 node.appendChild(pset)
888 self._parametersToDom(esysxml, pset)
889
890 class Simulation(Model):
891 """
892 A Simulation object is a special Model which runs a sequence of Models.
893
894 The methods C{doInitialization}, C{finalize}, C{getSafeTimeStepSize},
895 C{doStepPreprocessing}, C{terminateIteration}, C{doStepPostprocessing},
896 C{doFinalization} execute the corresponding methods of the models in
897 the simulation.
898 """
899
900 FAILED_TIME_STEPS_MAX=20
901 MAX_ITER_STEPS=50
902 MAX_CHANGE_OF_DT=2.
903
904 def __init__(self, models=[], **kwargs):
905 """
906 Initiates a simulation from a list of models.
907 """
908 super(Simulation, self).__init__(**kwargs)
909 self.declareParameter(time=0.,
910 time_step=0,
911 dt = self.UNDEF_DT)
912 for m in models:
913 if not isinstance(m, Model):
914 raise TypeError("%s is not a subclass of Model."%m)
915 self.__models=[]
916 for i in range(len(models)):
917 self[i] = models[i]
918
919
920 def __repr__(self):
921 """
922 Returns a string representation of the Simulation.
923 """
924 return "<Simulation %r>" % self.__models
925
926 def __str__(self):
927 """
928 Returns Simulation as a string.
929 """
930 return "<Simulation %d>" % id(self)
931
932 def iterModels(self):
933 """
934 Returns an iterator over the models.
935 """
936 return self.__models
937
938 def __getitem__(self,i):
939 """
940 Returns the i-th model.
941 """
942 return self.__models[i]
943
944 def __setitem__(self,i,value):
945 """
946 Sets the i-th model.
947 """
948 if not isinstance(value,Model):
949 raise ValueError,"assigned value is not a Model but instance of %s"%(value.__class__.__name__,)
950 for j in range(max(i-len(self.__models)+1,0)):
951 self.__models.append(None)
952 self.__models[i]=value
953
954 def __len__(self):
955 """
956 Returns the number of models.
957 """
958 return len(self.__models)
959
960 def getAllModels(self):
961 """
962 Returns a list of all models used in the Simulation including
963 subsimulations.
964 """
965 out=[]
966 for m in self.iterModels():
967 if isinstance(m, Simulation):
968 out+=m.getAllModels()
969 else:
970 out.append(m)
971 return list(set(out))
972
973 def checkModels(self, models, hash):
974 """
975 Returns a list of (model, parameter, target model) if the parameter
976 of model is linking to the target_model which is not in the list of
977 models.
978 """
979 out=self.checkLinkTargets(models, hash + [self])
980 for m in self.iterModels():
981 if isinstance(m, Simulation):
982 out|=m.checkModels(models, hash)
983 else:
984 out|=m.checkLinkTargets(models, hash + [self])
985 return set( [ (str(self)+"."+f[0],f[1]) for f in out ] )
986
987
988 def getSafeTimeStepSize(self,dt):
989 """
990 Returns a time step size which can be safely used by all models.
991
992 This is the minimum over the time step sizes of all models.
993 """
994 out=min([o.getSafeTimeStepSize(dt) for o in self.iterModels()])
995 return out
996
997 def setUp(self):
998 """
999 Performs the setup for all models.
1000 """
1001 for o in self.iterModels():
1002 o.setUp()
1003
1004 def doInitialization(self):
1005 """
1006 Initializes all models.
1007 """
1008 for o in self.iterModels():
1009 o.doInitialization()
1010
1011 def doInitialStep(self):
1012 """
1013 Performs an iteration step in the initialization step for all models.
1014 """
1015 iter=0
1016 while not self.terminateInitialIteration():
1017 if iter==0: self.trace("iteration for initialization starts")
1018 iter+=1
1019 self.trace("iteration step %d"%(iter))
1020 for o in self.iterModels():
1021 o.doInitialStep()
1022 if iter>self.MAX_ITER_STEPS:
1023 raise IterationDivergenceError("initial iteration did not converge after %s steps."%iter)
1024 self.trace("Initialization finalized after %s iteration steps."%iter)
1025
1026 def doInitialPostprocessing(self):
1027 """
1028 Finalises the initialization iteration process for all models.
1029 """
1030 for o in self.iterModels():
1031 o.doInitialPostprocessing()
1032
1033 def finalize(self):
1034 """
1035 Returns True if any of the models is to be finalized.
1036 """
1037 return any([o.finalize() for o in self.iterModels()])
1038
1039 def doFinalization(self):
1040 """
1041 Finalises the time stepping for all models.
1042 """
1043 for i in self.iterModels(): i.doFinalization()
1044 self.trace("end of time integation.")
1045
1046 def doStepPreprocessing(self,dt):
1047 """
1048 Initializes the time step for all models.
1049 """
1050 for o in self.iterModels():
1051 o.doStepPreprocessing(dt)
1052
1053 def terminateIteration(self):
1054 """
1055 Returns True if all iterations for all models are terminated.
1056 """
1057 out=all([o.terminateIteration() for o in self.iterModels()])
1058 return out
1059
1060 def terminateInitialIteration(self):
1061 """
1062 Returns True if all initial iterations for all models are terminated.
1063 """
1064 out=all([o.terminateInitialIteration() for o in self.iterModels()])
1065 return out
1066
1067 def doStepPostprocessing(self,dt):
1068 """
1069 Finalises the iteration process for all models.
1070 """
1071 for o in self.iterModels():
1072 o.doStepPostprocessing(dt)
1073 self.time_step+=1
1074 self.time+=dt
1075 self.dt=dt
1076
1077 def doStep(self,dt):
1078 """
1079 Executes the iteration step at a time step for all models::
1080
1081 self.doStepPreprocessing(dt)
1082 while not self.terminateIteration():
1083 for all models:
1084 self.doStep(dt)
1085 self.doStepPostprocessing(dt)
1086 """
1087 self.iter=0
1088 while not self.terminateIteration():
1089 if self.iter==0: self.trace("iteration at %d-th time step %e starts"%(self.time_step+1,self.time+dt))
1090 self.iter+=1
1091 self.trace("iteration step %d"%(self.iter))
1092 for o in self.iterModels():
1093 o.doStep(dt)
1094 if self.iter>0: self.trace("iteration at %d-th time step %e finalized."%(self.time_step+1,self.time+dt))
1095
1096 def run(self,check_pointing=None):
1097 """
1098 Runs the simulation by performing essentially::
1099
1100 self.setUp()
1101 if not restart:
1102 self.doInitialization()
1103 while not self.terminateInitialIteration(): self.doInitialStep()
1104 self.doInitialPostprocessing()
1105 while not self.finalize():
1106 dt=self.getSafeTimeStepSize()
1107 self.doStepPreprocessing(dt_new)
1108 self.doStep(dt_new)
1109 self.doStepPostprocessing(dt_new)
1110 self.doFinalization()
1111
1112 If one of the models throws a C{FailedTimeStepError} exception a
1113 new time step size is computed through getSafeTimeStepSize() and the
1114 time step is repeated.
1115
1116 If one of the models throws a C{IterationDivergenceError}
1117 exception the time step size is halved and the time step is repeated.
1118
1119 In both cases the time integration is given up after
1120 C{Simulation.FAILED_TIME_STEPS_MAX} attempts.
1121 """
1122 # check the completeness of the models:
1123 # first a list of all the models involved in the simulation including
1124 # subsimulations:
1125 #
1126 missing=self.checkModels(self.getAllModels(), [])
1127 if len(missing)>0:
1128 msg=""
1129 for l in missing:
1130 msg+="\n\t"+str(l[1])+" at "+l[0]
1131 raise MissingLink("link targets missing in the Simulation: %s"%msg)
1132 #==============================
1133 self.setUp()
1134 if self.time_step < 1:
1135 self.doInitialization()
1136 self.doInitialStep()
1137 self.doInitialPostprocessing()
1138 while not self.finalize():
1139 step_fail_counter=0
1140 iteration_fail_counter=0
1141 if self.time_step==0:
1142 dt_new=self.getSafeTimeStepSize(self.dt)
1143 else:
1144 dt_new=min(max(self.getSafeTimeStepSize(self.dt),self.dt/self.MAX_CHANGE_OF_DT),self.dt*self.MAX_CHANGE_OF_DT)
1145 self.trace("%d. time step %e (step size %e.)" % (self.time_step+1,self.time+dt_new,dt_new))
1146 end_of_step=False
1147 while not end_of_step:
1148 end_of_step=True
1149 if not dt_new>0:
1150 raise NonPositiveStepSizeError("non-positive step size in step %d"%(self.time_step+1))
1151 try:
1152 self.doStepPreprocessing(dt_new)
1153 self.doStep(dt_new)
1154 self.doStepPostprocessing(dt_new)
1155 except IterationDivergenceError:
1156 dt_new*=0.5
1157 end_of_step=False
1158 iteration_fail_counter+=1
1159 if iteration_fail_counter>self.FAILED_TIME_STEPS_MAX:
1160 raise SimulationBreakDownError("reduction of time step to achieve convergence failed after %s steps."%self.FAILED_TIME_STEPS_MAX)
1161 self.trace("Iteration failed. Time step is repeated with new step size %s."%dt_new)
1162 except FailedTimeStepError:
1163 dt_new=self.getSafeTimeStepSize(self.dt)
1164 end_of_step=False
1165 step_fail_counter+=1
1166 self.trace("Time step is repeated with new time step size %s."%dt_new)
1167 if step_fail_counter>self.FAILED_TIME_STEPS_MAX:
1168 raise SimulationBreakDownError("Time integration is given up after %d attempts."%step_fail_counter)
1169 if not check_pointing==None:
1170 if check_pointing.doDump():
1171 self.trace("check point is created.")
1172 self.writeXML()
1173 self.doFinalization()
1174
1175
1176 def toDom(self, esysxml, node):
1177 """
1178 C{toDom} method of Simulation class.
1179 """
1180 simulation = esysxml.createElement('Simulation')
1181 esysxml.registerLinkableObject(self, simulation)
1182 for rank, sim in enumerate(self.iterModels()):
1183 component = esysxml.createElement('Component')
1184 component.setAttribute('rank', str(rank))
1185 sim.toDom(esysxml, component)
1186 simulation.appendChild(component)
1187 node.appendChild(simulation)
1188
1189 def fromDom(cls, esysxml, node):
1190 sims = []
1191 for n in node.childNodes:
1192 if isinstance(n, minidom.Text):
1193 continue
1194 sims.append(esysxml.getComponent(n))
1195 sims.sort(_comp)
1196 sim=cls([s[1] for s in sims], debug=esysxml.debug)
1197 esysxml.registerLinkableObject(sim, node)
1198 return sim
1199
1200 fromDom = classmethod(fromDom)
1201
1202 def _comp(a,b):
1203 if a[0]<a[1]:
1204 return 1
1205 elif a[0]>a[1]:
1206 return -1
1207 else:
1208 return 0
1209
1210 class IterationDivergenceError(Exception):
1211 """
1212 Exception which is thrown if there is no convergence of the iteration
1213 process at a time step.
1214
1215 But there is a chance that a smaller step could help to reach convergence.
1216 """
1217 pass
1218
1219 class FailedTimeStepError(Exception):
1220 """
1221 Exception which is thrown if the time step fails because of a step
1222 size that has been chosen to be too large.
1223 """
1224 pass
1225
1226 class SimulationBreakDownError(Exception):
1227 """
1228 Exception which is thrown if the simulation does not manage to progress
1229 in time.
1230 """
1231 pass
1232
1233 class NonPositiveStepSizeError(Exception):
1234 """
1235 Exception which is thrown if the step size is not positive.
1236 """
1237 pass
1238
1239 class MissingLink(Exception):
1240 """
1241 Exception which is thrown when a link is missing.
1242 """
1243 pass
1244
1245 class DataSource(object):
1246 """
1247 Class for handling data sources, including local and remote files.
1248 This class is under development.
1249 """
1250
1251 def __init__(self, uri="file.ext", fileformat="unknown"):
1252 self.uri = uri
1253 self.fileformat = fileformat
1254
1255 def toDom(self, esysxml, node):
1256 """
1257 C{toDom} method of DataSource. Creates a DataSource node and appends
1258 it to the current XML esysxml.
1259 """
1260 ds = esysxml.createElement('DataSource')
1261 ds.appendChild(esysxml.createDataNode('URI', self.uri))
1262 ds.appendChild(esysxml.createDataNode('FileFormat', self.fileformat))
1263 node.appendChild(ds)
1264
1265 def fromDom(cls, esysxml, node):
1266 uri= str(node.getElementsByTagName("URI")[0].firstChild.nodeValue.strip())
1267 fileformat= str(node.getElementsByTagName("FileFormat")[0].firstChild.nodeValue.strip())
1268 ds = cls(uri, fileformat)
1269 return ds
1270
1271 def getLocalFileName(self):
1272 return self.uri
1273
1274 fromDom = classmethod(fromDom)
1275
1276 class RestartManager(object):
1277 """
1278 A restart manager which does two things: it decides when restart files
1279 were created (when doDump returns true) and manages directories for
1280 restart files. The method getNewDumper creates a new directory and
1281 returns its name.
1282
1283 This restart manager will decide to dump restart files every dump_step
1284 calls of doDump or if more than dump_time since the last dump has
1285 elapsed. The restart manager controls two directories for dumping
1286 restart data, namely for the current and previous dump. This way the
1287 previous dump can be used for restart in the case the current dump failed.
1288
1289 @cvar SEC: unit of seconds, for instance 5*RestartManager.SEC defines 5 seconds
1290 @cvar MIN: unit of minutes, for instance 5*RestartManager.MIN defines 5 minutes
1291 @cvar H: unit of hours, for instance 5*RestartManager.H defines 5 hours
1292 @cvar D: unit of days, for instance 5*RestartManager.D defines 5 days
1293 """
1294 SEC=1.
1295 MIN=60.
1296 H=360.
1297 D=8640.
1298 def __init__(self,dump_time=1080., dump_step=None, dumper=None):
1299 """
1300 Initializes the RestartManager.
1301
1302 @param dump_time: defines the minimum time interval in SEC between two
1303 dumps. If C{None}, time is not used as criterion.
1304 @param dump_step: defines the number of calls of doDump between two
1305 dump events. If C{None}, the call counter is not
1306 used as criterion.
1307 @param dumper: defines the directory for dumping restart files.
1308 Additionally, the directories dumper+"_bkp" and
1309 dumper+"_bkp2" are used. If the directory does not
1310 exist it is created. If dumper is not present a unique
1311 directory within the current working directory is used.
1312 """
1313 self.__dump_step=dump_time
1314 self.__dump_time=dump_step
1315 self.__counter=0
1316 self.__saveMarker()
1317 if dumper == None:
1318 self.__dumper="restart"+str(os.getpid())
1319 else:
1320 self.__dumper=dumper
1321 self.__dumper_bkp=self.__dumper+"_bkp"
1322 self.__dumper_bkp2=self.__dumper+"_bkp2"
1323 self.__current_dumper=None
1324
1325 def __saveMarker(self):
1326 self.__last_restart_time=time.time()
1327 self.__last_restart_counter=self.__counter
1328
1329 def getCurrentDumper(self):
1330 """
1331 Returns the name of the currently used dumper.
1332 """
1333 return self.__current_dumper
1334
1335 def doDump(self):
1336 """
1337 Returns true if the restart should be dumped. Use C{getNewDumper} to
1338 retrieve the directory name to be used for dumping.
1339 """
1340 if self.__dump_step == None:
1341 if self.__dump_step == None:
1342 out = False
1343 else:
1344 out = (self.__dump_step + self.__last_restart_counter) <= self.__counter
1345 else:
1346 if dump_step == None:
1347 out = (self.__last_restart_time + self.__dump_time) <= time.time()
1348 else:
1349 out = ( (self.__dump_step + self.__last_restart_counter) <= self.__counter) \
1350 or ( (self.__last_restart_time + self.__dump_time) <= time.time() )
1351 if out: self.__saveMarker()
1352 self__counter+=1
1353
1354 def getNewDumper(self):
1355 """
1356 Creates a new directory to be used for dumping and returns its name.
1357 """
1358 if os.access(self.__dumper_bkp,os.F_OK):
1359 if os.access(self.__dumper_bkp2, os.F_OK):
1360 raise RunTimeError("please remove %s."%self.__dumper_bkp2)
1361 try:
1362 os.rename(self.__dumper_bkp, self.__dumper_bkp2)
1363 except:
1364 self.__current_dumper=self.__dumper
1365 raise RunTimeError("renaming backup directory %s failed. Use %s for restart."%(self.__dumper_bkp,self.__dumper))
1366 if os.access(self.__dumper,os.F_OK):
1367 if os.access(self.__dumper_bkp, os.F_OK):
1368 raise RunTimeError("please remove %s."%self.__dumper_bkp)
1369 try:
1370 os.rename(self.__dumper, self.__dumper_bkp)
1371 except:
1372 self.__current_dumper=self.__dumper_bkp2
1373 raise RunTimeError("moving directory %s to backup failed. Use %s for restart."%(self.__dumper,self.__dumper_bkp2))
1374 try:
1375 os.mkdir(self.__dumper)
1376 except:
1377 self.__current_dumper=self.__dumper_bkp
1378 raise RunTimeError("creating a new restart directory %s failed. Use %s for restart."%(self.__dumper,self.__dumper_bkp))
1379 if os.access(self.__dumper_bkp2, os.F_OK): os.rmdir(self.__dumper_bkp2)
1380 return self.getCurrentDumper()
1381
1382
1383 # vim: expandtab shiftwidth=4:

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

  ViewVC Help
Powered by ViewVC 1.1.26