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