/[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 1809 - (show annotations)
Thu Sep 25 06:43:44 2008 UTC (11 years ago) by ksteube
File MIME type: text/x-python
File size: 46626 byte(s)
Copyright updated in all python files

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