/[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 147 - (show annotations)
Fri Aug 12 01:45:47 2005 UTC (13 years, 8 months ago) by jgs
Original Path: trunk/esys2/escript/py_src/modelframe.py
File MIME type: text/x-python
File size: 27004 byte(s)
erge of development branch dev-02 back to main trunk on 2005-08-12

1 # $Id$
2
3 from types import StringType,IntType,FloatType,BooleanType,ListType,DictType
4 from sys import stdout
5 import itertools
6 # import modellib temporarily removed!!!
7
8 # import the 'set' module if it's not defined (python2.3/2.4 difference)
9 try:
10 set
11 except NameError:
12 from sets import Set as set
13
14 from xml.dom import minidom
15
16 def dataNode(document, tagName, data):
17 """
18 dataNodes are the building blocks of the xml documents constructed in
19 this module. document is the current xml document, tagName is the
20 associated xml tag, and data is the values in the tag.
21 """
22 t = document.createTextNode(str(data))
23 n = document.createElement(tagName)
24 n.appendChild(t)
25 return n
26
27 def esysDoc():
28 """
29 Global method for creating an instance of an EsysXML document.
30 """
31 doc = minidom.Document()
32 esys = doc.createElement('ESys')
33 doc.appendChild(esys)
34 return doc, esys
35
36 def all(seq):
37 for x in seq:
38 if not x:
39 return False
40 return True
41
42 def any(seq):
43 for x in seq:
44 if x:
45 return True
46 return False
47
48 LinkableObjectRegistry = {}
49
50 def registerLinkableObject(obj_id, o):
51 LinkableObjectRegistry[obj_id] = o
52
53 LinkRegistry = []
54
55 def registerLink(obj_id, l):
56 LinkRegistry.append((obj_id,l))
57
58 def parse(xml):
59 """
60 Generic parse method for EsysXML. Without this, Links don't work.
61 """
62 global LinkRegistry, LinkableObjectRegistry
63 LinkRegistry = []
64 LinkableObjectRegistry = {}
65
66 doc = minidom.parseString(xml)
67 sim = getComponent(doc.firstChild)
68 for obj_id, link in LinkRegistry:
69 link.target = LinkableObjectRegistry[obj_id]
70
71 return sim
72
73 def getComponent(doc):
74 """
75 Used to get components of Simualtions, Models.
76 """
77 for node in doc.childNodes:
78
79 if isinstance(node, minidom.Element):
80 if node.tagName == 'Simulation':
81 if node.getAttribute("type") == 'Simulation':
82 return Simulation.fromDom(node)
83 if node.tagName == 'Model':
84 model_type = node.getAttribute("type")
85 model_subclasses = Model.__subclasses__()
86 for model in model_subclasses:
87 if model_type == model.__name__:
88 return Model.fromDom(node)
89 if node.tagName == 'ParameterSet':
90 parameter_type = node.getAttribute("type")
91 return ParameterSet.fromDom(node)
92 raise "Invalid simulation type, %r" % node.getAttribute("type")
93
94
95 raise ValueError("No Simulation Found")
96
97
98 class Link:
99 """
100 a Link makes an attribute of an object callable:
101 o.object()
102 o.a=8
103 l=Link(o,"a")
104 assert l()==8
105 """
106
107 def __init__(self,target,attribute=None):
108 """
109 creates a link to the object target. If attribute is given, the link is
110 establised to this attribute of the target. Otherwise the attribute is
111 undefined.
112 """
113 self.target = target
114 self.attribute = None
115 self.setAttributeName(attribute)
116
117 def setAttributeName(self,attribute):
118 """
119 set a new attribute name to be collected from the target object. The
120 target object must have the attribute with name attribute.
121 """
122 if attribute and self.target:
123 if isinstance(self.target,LinkableObject):
124 if not self.target.hasAttribute(attribute):
125 raise AttributeError("%s: target %s has no attribute %s."%(self, self.target, attribute))
126 else:
127 if not hasattr(self.target,attribute):
128 raise AttributeError("%s: target %s has no attribute %s."%(self, self.target, attribute))
129 self.attribute = attribute
130
131 def hasDefinedAttributeName(self):
132 """
133 returns true if an attribute name is set
134 """
135 return self.attribute != None
136
137 def __repr__(self):
138 """
139 returns a string representation of the link
140 """
141 if self.hasDefinedAttributeName():
142 return "<Link to attribute %s of %s>" % (self.attribute, self.target)
143 else:
144 return "<Link to target %s>" % self.target
145
146 def __call__(self,name=None):
147 """
148 returns the value of the attribute of the target object. If the
149 atrribute is callable then the return value of the call is returned.
150 """
151 if name:
152 out=getattr(self.target, name)
153 else:
154 out=getattr(self.target, self.attribute)
155
156 if callable(out):
157 return out()
158 else:
159 return out
160
161 def toDom(self, document, node):
162 """
163 toDom method of Link. Creates a Link node and appends it to the current XML
164 document
165 """
166 link = document.createElement('Link')
167 assert (self.target != None), ("Target was none, name was %r" % self.attribute)
168 link.appendChild(dataNode(document, 'Target', self.target.id))
169 # this use of id will not work for purposes of being able to retrieve the intended
170 # target from the xml later. I need a better unique identifier.
171 assert self.attribute, "You can't xmlify a Link without a target attribute"
172 link.appendChild(dataNode(document, 'Attribute', self.attribute))
173 node.appendChild(link)
174
175 def fromDom(cls, doc):
176 targetid = doc.getElementsByTagName("Target")[0].firstChild.nodeValue.strip()
177 attribute = doc.getElementsByTagName("Attribute")[0].firstChild.nodeValue.strip()
178 l = cls(None, attribute)
179 registerLink(targetid, l)
180 return l
181
182 fromDom = classmethod(fromDom)
183
184 def writeXML(self,ostream=stdout):
185 """
186 writes an XML representation of self to the output stream ostream.
187 If ostream is nor present the standart output stream is used. If
188 esysheader==True the esys XML header is written
189 """
190 print 'I got to the Link writeXML method'
191 document, rootnode = esysDoc()
192 self.toDom(document, rootnode)
193
194 ostream.write(document.toprettyxml())
195
196 class LinkableObject(object):
197 """
198 An object that allows to link its attributes to attributes of other objects
199 via a Link object. For instance
200
201 p = LinkableObject()
202 p.x = Link(o,"name")
203 print p.x
204
205 links attribute x of p to the attribute name of object o.
206
207 p.x will contain the current value of attribute name of object o.
208
209 If the value of getattr(o, "name") is callable, p.x will rturn the return
210 value of the call.
211 """
212
213 number_sequence = itertools.count(100)
214
215 def __init__(self, debug=False):
216 """ initializes LinkableObject so that we can operate on Links """
217 self.debug = debug
218 self.__linked_attributes={}
219 self.id = self.number_sequence.next()
220
221 def trace(self, msg):
222 """ If debugging is on, print the message, otherwise do nothing
223 """
224 if self.debug:
225 print "%s: %s"%(str(self),msg)
226
227 def __getattr__(self,name):
228 """returns the value of attribute name. If the value is a Link object the
229 object is called and the return value is returned."""
230 out = self.getAttributeObject(name)
231 if isinstance(out,Link):
232 return out()
233 else:
234 return out
235
236 def getAttributeObject(self,name):
237 """return the object stored for attribute name."""
238
239 if self.__dict__.has_key(name):
240 return self.__dict__[name]
241
242 if self.__linked_attributes.has_key(name):
243 return self.__linked_attributes[name]
244
245 if self.__class__.__dict__.has_key(name):
246 return self.__class.__dict__[name]
247
248 raise AttributeError,"No attribute %s."%name
249
250 def hasAttribute(self,name):
251 """returns True if self as attribute name"""
252 return self.__dict__.has_key(name) or self.__linked_attributes.has_key(name) or self.__class__.__dict__.has_key(name)
253
254 def __setattr__(self,name,value):
255 """sets the value for attribute name. If value is a Link the target
256 attribute is set to name if no attribute has been specified."""
257
258
259 if self.__dict__.has_key(name):
260 del self.__dict__[name]
261
262 if isinstance(value,Link):
263 if not value.hasDefinedAttributeName():
264 value.setAttributeName(name)
265 self.__linked_attributes[name] = value
266
267 self.trace("attribute %s is now linked by %s."%(name,value))
268 else:
269 self.__dict__[name] = value
270
271 def __delattr__(self,name):
272 """removes the attribute name."""
273
274 if self.__linked_attributes.has_key[name]:
275 del self.__linked_attributes[name]
276 elif self.__dict__.has_key(name):
277 del self.__dict__[name]
278 else:
279 raise AttributeError,"No attribute %s."%name
280
281 class _ParameterIterator:
282 def __init__(self,parameterset):
283
284 self.__set=parameterset
285 self.__iter=iter(parameterset.parameters)
286
287 def next(self):
288 o=self.__iter.next()
289 return (o,self.__set.getAttributeObject(o))
290
291 def __iter__(self):
292 return self
293
294 class ParameterSet(LinkableObject):
295 """a class which allows to emphazise attributes to be written and read to XML
296
297 Leaves of an ESySParameters objects can be
298
299 a real number
300 a integer number
301 a string
302 a boolean value
303 a ParameterSet object
304 a Simulation object
305 a Model object
306 any other object (not considered by writeESySXML and writeXML)
307
308 Example how to create an ESySParameters object:
309
310 p11=ParameterSet(gamma1=1.,gamma2=2.,gamma3=3.)
311 p1=ParameterSet(dim=2,tol_v=0.001,output_file="/tmp/u.%3.3d.dx",runFlag=True,parm11=p11)
312 parm=ParameterSet(parm1=p1,parm2=ParameterSet(alpha=Link(p11,"gamma1")))
313
314 This can be accessed as
315
316 parm.parm1.gamma=0.
317 parm.parm1.dim=2
318 parm.parm1.tol_v=0.001
319 parm.parm1.output_file="/tmp/u.%3.3d.dx"
320 parm.parm1.runFlag=True
321 parm.parm1.parm11.gamma1=1.
322 parm.parm1.parm11.gamma2=2.
323 parm.parm1.parm11.gamma3=3.
324 parm.parm2.alpha=1. (value of parm.parm1.parm11.gamma1)
325
326 """
327 def __init__(self, parameters=[], **kwargs):
328 """creates a ParameterSet with parameters parameters"""
329 LinkableObject.__init__(self, **kwargs)
330 self.parameters = set()
331 self.declareParameters(parameters)
332
333 def __repr__(self):
334 return "<%s %r>" % (self.__class__.__name__,
335 [(p, getattr(self, p, None)) for p in self.parameters])
336
337 def declareParameter(self,**parameters):
338 """declares a new parameter(s) and its (their) inital value."""
339 self.declareParameters(parameters)
340
341 def declareParameters(self,parameters):
342 """declares a set of parameters. parameters can be a list, a dictonary or a ParameterSet."""
343 if isinstance(parameters,ListType):
344 parameters = zip(parameters, itertools.repeat(None))
345 if isinstance(parameters,DictType):
346 parameters = parameters.iteritems()
347
348 for prm, value in parameters:
349 setattr(self,prm,value)
350 self.parameters.add(prm)
351
352 self.trace("parameter %s has been declared."%prm)
353
354 def releaseParameters(self,name):
355 """removes parameter name from the paramameters"""
356 if self.isParameter(name):
357 self.parameters.remove(name)
358 self.trace("parameter %s has been removed."%name)
359
360 def __iter__(self):
361 """creates an iterator over the parameter and their values"""
362 return _ParameterIterator(self)
363
364 def showParameters(self):
365 """returns a descrition of the parameters"""
366 out="{"
367 notfirst=False
368 for i,v in self:
369 if notfirst: out=out+","
370 notfirst=True
371 if isinstance(v,ParameterSet):
372 out="%s\"%s\" : %s"%(out,i,v.showParameters())
373 else:
374 out="%s\"%s\" : %s"%(out,i,v)
375 return out+"}"
376
377 def __delattr__(self,name):
378 """removes the attribute name."""
379 LinkableObject.__delattr__(self,name)
380 try:
381 self.releaseParameter(name)
382 except:
383 pass
384
385 def toDom(self, document, node):
386 """ toDom method of ParameterSet class """
387 pset = document.createElement('ParameterSet')
388 node.appendChild(pset)
389 self._parametersToDom(document, pset)
390
391 def _parametersToDom(self, document, node):
392 node.setAttribute ('id', str(self.id))
393 for name,value in self:
394 param = document.createElement('Parameter')
395 param.setAttribute('type', value.__class__.__name__)
396
397 param.appendChild(dataNode(document, 'Name', name))
398
399 val = document.createElement('Value')
400
401 if isinstance(value,ParameterSet):
402 value.toDom(document, val)
403 param.appendChild(val)
404 elif isinstance(value, Link):
405 value.toDom(document, val)
406 param.appendChild(val)
407 elif isinstance(value,StringType):
408 param.appendChild(dataNode(document, 'Value', value))
409 else:
410 param.appendChild(dataNode(document, 'Value', str(value)))
411
412 node.appendChild(param)
413
414 def fromDom(cls, doc):
415
416 # Define a host of helper functions to assist us.
417 def _children(node):
418 """
419 Remove the empty nodes from the children of this node
420 """
421 return [x for x in node.childNodes
422 if not isinstance(x, minidom.Text) or x.nodeValue.strip()]
423
424 def _floatfromValue(doc):
425 return float(doc.nodeValue.strip())
426
427 def _stringfromValue(doc):
428 return str(doc.nodeValue.strip())
429
430 def _intfromValue(doc):
431 return int(doc.nodeValue.strip())
432
433 def _boolfromValue(doc):
434 return bool(doc.nodeValue.strip())
435
436 # Mapping from text types in the xml to methods used to process trees of that type
437 ptypemap = {"Simulation": Simulation.fromDom,
438 "Model":Model.fromDom,
439 "ParameterSet":ParameterSet.fromDom,
440 "Link":Link.fromDom,
441 "float":_floatfromValue,
442 "int":_intfromValue,
443 "str":_stringfromValue,
444 "bool":_boolfromValue
445 }
446
447 # print doc.toxml()
448
449 parameters = {}
450 for node in _children(doc):
451 ptype = node.getAttribute("type")
452
453 pname = pvalue = None
454 for childnode in _children(node):
455
456 if childnode.tagName == "Name":
457 pname = childnode.firstChild.nodeValue.strip()
458
459 if childnode.tagName == "Value":
460 nodes = _children(childnode)
461 pvalue = ptypemap[ptype](nodes[0])
462
463 parameters[pname] = pvalue
464
465 # Create the instance of ParameterSet
466 o = cls()
467 o.declareParameters(parameters)
468 registerLinkableObject(doc.getAttribute("id"), o)
469 return o
470
471 fromDom = classmethod(fromDom)
472
473 def writeXML(self,ostream=stdout):
474 """writes the object as an XML object into an output stream"""
475 # ParameterSet(d) with d[Name]=Value
476 document, node = esysDoc()
477 self.toDom(document, node)
478 ostream.write(document.toprettyxml())
479
480 class Model(ParameterSet):
481 """
482
483 A Model object represents a processess marching over time
484 until a finalizing condition is fullfilled. At each time step an iterative
485 process can be performed and the time step size can be controlled. A Model has
486 the following work flow:
487
488 doInitialization()
489 while not finalize():
490 dt=getSafeTimeStepSize(dt)
491 doStepPreprocessing(dt)
492 while not terminateIteration(): doStep(dt)
493 doStepPostprocessing(dt)
494 doFinalization()
495
496 where doInitialization,finalize, getSafeTimeStepSize, doStepPreprocessing, terminateIteration, doStepPostprocessing, doFinalization
497 are methods of the particular instance of a Model. The default implementations of these methods have to be overwritten by
498 the subclass implementinf a Model.
499
500 """
501
502 UNDEF_DT=1.e300
503
504 def __init__(self,parameters=[],**kwarg):
505 """creates a model
506
507 Just calls the parent constructor.
508 """
509 ParameterSet.__init__(self, parameters=parameters,**kwarg)
510
511 def __str__(self):
512 return "<%s %d>"%(self.__class__,id(self))
513
514 def toDom(self, document, node):
515 """ toDom method of Model class """
516 pset = document.createElement('Model')
517 pset.setAttribute('type', self.__class__.__name__)
518 node.appendChild(pset)
519 self._parametersToDom(document, pset)
520
521 def doInitialization(self):
522 """initializes the time stepping scheme. This function may be overwritten."""
523 pass
524
525 def getSafeTimeStepSize(self,dt):
526 """returns a time step size which can safely be used.
527 dt gives the previously used step size.
528 This function may be overwritten."""
529 return self.UNDEF_DT
530
531 def finalize(self):
532 """returns False if the time stepping is finalized. This function may be
533 overwritten."""
534 return False
535
536 def doFinalization(self):
537 """finalizes the time stepping. This function may be overwritten."""
538 pass
539
540 def doStepPreprocessing(self,dt):
541 """sets up a time step of step size dt. This function may be overwritten."""
542 pass
543
544 def doStep(self,dt):
545 """executes an iteration step at a time step.
546 dt is the currently used time step size.
547 This function may be overwritten."""
548 pass
549
550 def terminateIteration(self):
551 """returns True if iteration on a time step is terminated."""
552 return True
553
554 def doStepPostprocessing(self,dt):
555 """finalalizes the time step.
556 dt is the currently used time step size.
557 This function may be overwritten."""
558 pass
559
560 def writeXML(self, ostream=stdout):
561 document, node = esysDoc()
562 self.toDom(document, node)
563 ostream.write(document.toprettyxml())
564
565
566
567 class Simulation(Model):
568 """
569 A Simulation object is special Model which runs a sequence of Models.
570
571 The methods doInitialization,finalize, getSafeTimeStepSize, doStepPreprocessing,
572 terminateIteration, doStepPostprocessing, doFinalization
573 are executing the corresponding methods of the models in the simulation.
574
575 """
576
577 FAILED_TIME_STEPS_MAX=20
578 MAX_ITER_STEPS=50
579
580 def __init__(self, models=[], **kwargs):
581 """initiates a simulation from a list of models. """
582 Model.__init__(self, **kwargs)
583 self.__models=[]
584
585 for i in range(len(models)):
586 self[i] = models[i]
587
588 def __repr__(self):
589 """
590 returns a string representation of the Simulation
591 """
592 return "<Simulation %r>" % self.__models
593
594 def __str__(self):
595 """
596 returning Simulation as a string
597 """
598 return "<Simulation %d>"%id(self)
599
600 def iterModels(self):
601 """returns an iterator over the models"""
602 return self.__models
603
604 def __getitem__(self,i):
605 """returns the i-th model"""
606 return self.__models[i]
607
608 def __setitem__(self,i,value):
609 """sets the i-th model"""
610 if not isinstance(value,Model):
611 raise ValueError("assigned value is not a Model")
612 for j in range(max(i-len(self.__models)+1,0)): self.__models.append(None)
613 self.__models[i]=value
614
615 def __len__(self):
616 """returns the number of models"""
617 return len(self.__models)
618
619 def toDom(self, document, node):
620 """ toDom method of Simulation class """
621 simulation = document.createElement('Simulation')
622 simulation.setAttribute('type', self.__class__.__name__)
623
624 for rank, sim in enumerate(self.iterModels()):
625 component = document.createElement('Component')
626 component.setAttribute('rank', str(rank))
627
628 sim.toDom(document, component)
629
630 simulation.appendChild(component)
631
632 node.appendChild(simulation)
633
634 def writeXML(self,ostream=stdout):
635 """writes the object as an XML object into an output stream"""
636 document, rootnode = esysDoc()
637 self.toDom(document, rootnode)
638 ostream.write(document.toprettyxml())
639
640 def getSafeTimeStepSize(self,dt):
641 """returns a time step size which can safely be used by all models.
642 This is the minimum over the time step sizes of all models."""
643 out=min([o.getSafeTimeStepSize(dt) for o in self.iterModels()])
644 print "%s: safe step size is %e."%(str(self),out)
645 return out
646
647 def doInitialization(self):
648 """initializes all models """
649 self.n=0
650 self.tn=0.
651 for o in self.iterModels():
652 o.doInitialization()
653
654 def finalize(self):
655 """returns True if any of the models is to be finalized"""
656 return any([o.finalize() for o in self.iterModels()])
657
658 def doFinalization(self):
659 """finalalizes the time stepping for all models."""
660 for i in self.iterModels(): i.doFinalization()
661 self.trace("end of time integation.")
662
663 def doStepPreprocessing(self,dt):
664 """initializes the time step for all models."""
665 for o in self.iterModels():
666 o.doStepPreprocessing(dt)
667
668 def terminateIteration(self):
669 """returns True if all iterations for all models are terminated."""
670 out=all([o.terminateIteration() for o in self.iterModels()])
671 return out
672
673 def doStepPostprocessing(self,dt):
674 """finalalizes the iteration process for all models."""
675 for o in self.iterModels():
676 o.doStepPostprocessing(dt)
677 self.n+=1
678 self.tn+=dt
679
680 def doStep(self,dt):
681 """
682 executes the iteration step at a time step for all model:
683
684 self.doStepPreprocessing(dt)
685 while not self.terminateIteration(): for all models: self.doStep(dt)
686 self.doStepPostprocessing(dt)
687
688 """
689 self.iter=0
690 while not self.terminateIteration():
691 if self.iter==0: self.trace("iteration at %d-th time step %e starts"%(self.n+1,self.tn+dt))
692 self.iter+=1
693 self.trace("iteration step %d"%(self.iter))
694 for o in self.iterModels():
695 o.doStep(dt)
696 if self.iter>0: self.trace("iteration at %d-th time step %e finalized."%(self.n+1,self.tn+dt))
697
698 def run(self,check_point=None):
699 """
700
701 run the simulation by performing essentially
702
703 self.doInitialization()
704 while not self.finalize():
705 dt=self.getSafeTimeStepSize()
706 self.doStep(dt)
707 if n%check_point==0: self.writeXML()
708 self.doFinalization()
709
710 If one of the models in throws a FailedTimeStepError exception a new time step size is
711 computed through getSafeTimeStepSize() and the time step is repeated.
712
713 If one of the models in throws a IterationDivergenceError exception the time step size
714 is halved and the time step is repeated.
715
716 In both cases the time integration is given up after Simulation.FAILED_TIME_STEPS_MAX
717 attempts.
718
719
720 """
721 dt=self.UNDEF_DT
722 self.doInitialization()
723 while not self.finalize():
724 step_fail_counter=0
725 iteration_fail_counter=0
726 dt_new=self.getSafeTimeStepSize(dt)
727 self.trace("%d. time step %e (step size %e.)" % (self.n+1,self.tn+dt_new,dt_new))
728 end_of_step=False
729 while not end_of_step:
730 end_of_step=True
731 if not dt_new>0:
732 raise NonPositiveStepSizeError("non-positive step size in step %d",self.n+1)
733 try:
734 self.doStepPreprocessing(dt_new)
735 self.doStep(dt_new)
736 self.doStepPostprocessing(dt_new)
737 except IterationDivergenceError:
738 dt_new*=0.5
739 end_of_step=False
740 iteration_fail_counter+=1
741 if iteration_fail_counter>self.FAILED_TIME_STEPS_MAX:
742 raise SimulationBreakDownError("reduction of time step to achieve convergence failed.")
743 self.trace("iteration fails. time step is repeated with new step size.")
744 except FailedTimeStepError:
745 dt_new=self.getSafeTimeStepSize(dt)
746 end_of_step=False
747 step_fail_counter+=1
748 self.trace("time step is repeated.")
749 if step_fail_counter>self.FAILED_TIME_STEPS_MAX:
750 raise SimulationBreakDownError("time integration is given up after %d attempts."%step_fail_counter)
751 dt=dt_new
752 if not check_point==None:
753 if n%check_point==0:
754 self.trace("check point is created.")
755 self.writeXML()
756 self.doFinalization()
757
758 def fromDom(cls, doc):
759 sims = []
760 for node in doc.childNodes:
761 if isinstance(node, minidom.Text):
762 continue
763
764 sims.append(getComponent(node))
765
766 return cls(sims)
767
768 fromDom = classmethod(fromDom)
769
770
771 class IterationDivergenceError(Exception):
772 """
773 excpetion which is thrown if there is no convergence of the iteration process at a time step
774 but there is a chance taht a smaller step could help to reach convergence.
775
776 """
777 pass
778
779 class FailedTimeStepError(Exception):
780 """excpetion which is thrown if the time step fails because of a step size that have been choosen to be too large"""
781 pass
782
783 class SimulationBreakDownError(Exception):
784 """excpetion which is thrown if the simulation does not manage to progress in time."""
785 pass
786
787 class NonPositiveStepSizeError(Exception):
788 """excpetion which is thrown if the step size is not positive"""
789 pass

Properties

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

  ViewVC Help
Powered by ViewVC 1.1.26