File: _ScriptElement.py

package info (click to toggle)
xmds2 3.0.0%2Bdfsg-5
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 52,068 kB
  • sloc: python: 63,652; javascript: 9,230; cpp: 3,929; ansic: 1,463; makefile: 121; sh: 54
file content (568 lines) | stat: -rw-r--r-- 22,136 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
#!/usr/bin/env python3
# encoding: utf-8
"""
_ScriptElement.py

This contains all the pure-python code for ScriptElement.tmpl

Created by Graham Dennis on 2007-10-17.
Copyright (c) 2007-2012 Graham Dennis.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import textwrap

from Cheetah.Template import Template
from xpdeint.ParserException import ParserException

from xpdeint.Utilities import lazy_property

class _ScriptElement (Template):
  class LoopingOrder(object):
    MemoryOrder = 1
    StrictlyAscendingOrder = 2
    StrictlyDescendingOrder = 3
  
  argumentsToTemplateConstructors = {}
  # Initialise the callOnceGuards to be empty
  _callOnceGuards = set()
  _callOncePerInstanceGuards = dict()
  
  simulation = None
  
  @classmethod
  def resetGuards(cls):
    """
    Reset the flags used by the `CallOnceGuards` function decorators to ensure that
    various functions are only called once.
    
    Calling these functions causes all of these flags to be reset to `False` causing
    the functions protected by the `CallOnceGuards` function decorators to be able
    to be called again.
    """
    _ScriptElement._callOnceGuards.clear()
    for instanceGuardSet in _ScriptElement._callOncePerInstanceGuards.values():
      instanceGuardSet.clear()
  
  @property
  def _driver(self):
    """
    Return the simulation driver, but cache the result in a variable shared between
    all `_ScriptElement` instances.
    """
    _ScriptElement._driver = self.getVar('features')['Driver']
    return _ScriptElement._driver
  
  
  _ScriptElement_haveCalledInit = False
  
  def __init__(self, *args, **KWs):
    # If we have a diamond-inheritence, this function could be called more than once
    # And that would be bad. Let's check for that case, and return if it is the case.
    # Template can deal with being called more than once, but some of the code below
    # wouldn't be safe called more than once
    if self._ScriptElement_haveCalledInit:
      return
    
    self._ScriptElement_haveCalledInit = True
    
    localKWs = self.extractLocalKWs(['xmlElement', 'parent'], KWs)
    
    Template.__init__(self, *args, **KWs)
    
    self.getVar('templates').add(self)
    
    # Only set the dependencies attribute if it isn't taken
    # care of elsewhere
    if not hasattr(type(self), 'dependencies'):
      self.dependencies = set()
    
    assert 'parent' in localKWs
    self.parent = localKWs['parent']
    
    if self.parent and self.parent == self.simulation:
      self.simulation._children.append(self)
    self.xmlElement = localKWs.get('xmlElement', None)
    self.dependenciesEntity = None
    self.functions = {}
    self.codeBlocks = {}
    self._haveBeenRemoved = False
    
    self._children = []
    
    if hasattr(type(self), 'globalNameSpaceName'):
      globalNameSpace = KWs['searchList'][0]
      globalNameSpace[self.globalNameSpaceName] = self
    
    # Create the entry in the callOnceGuards
    _ScriptElement._callOncePerInstanceGuards[self] = set()
  
  @lazy_property
  def id(self):
    """
    Return a string that should uniquely identify this object.
    
    The string returned will be appropriate for use as a ``C`` variable name and
    will begin with and underscore.
    """
    if not self.parent or not self.parent.id:
      return self.name
    else:
      return '_'.join([self.parent.id, self.name])
  
  @property
  def children(self):
    result = self._children[:]
    result.extend(list(self.codeBlocks.values()))
    return result
  
  @lazy_property
  def propagationDimension(self):
    """
    Return the name of the current propagation dimension for this template. Note that this
    does not need to be the same as the propagation dimension for the entire simulation because
    cross-propagation works by using a standard integrator, but by setting a different propagation
    dimension.
    """
    
    if self.parent:
      return self.parent.propagationDimension
    
    return self.getVar("globalPropagationDimension")
  
  @lazy_property
  def propagationDirection(self):
    """
    Return a string representing the sign of the direction of propagation. This string will
    either be '+' or '-'. Note that usually this will be '+', however it will be '-' for
    cross-propagators that have a 'right' boundary condition.
    
    This method is used in the creation of the `propagationDirection` property.
    """
    if self.parent:
      return self.parent.propagationDirection
    else:
      return '+'
  
  @lazy_property
  def scriptLineNumber(self):
    return self.xmlElement.lineNumberForCDATASection()
  
  def hasattr(self, attrName):
    """
    Helper method to return whether or not the instance has the attribute `attrName`.
    The difference between this method and the Python `hasattr` function is that this one
    will raise an exception if the attribute `attrName` does exist but accessing it would
    cause an exception to be raised.
    """
    try:
      getattr(self, attrName)
    except AttributeError as err:
      if hasattr(type(self), attrName):
        raise
      return False
    else:
      return True
  
  # Default description of the template
  def description(self):
    return type(self).__name__
  
  # Includes
  def includes(self):
    pass
  
  # Defines needed at the start of the simulation
  def defines(self):
    pass
  
  # Globals needed at the start of the simulation
  def globals(self):
    pass
  
  # Function prototypes
  def functionPrototypes(self):
    if not self.functions:
      return
    return ''.join([f.prototype() for f in self.functions.values() if f.predicate()])

  # Function implemenations
  def functionImplementations(self):
    if not self.functions:
      return
    return '\n\n'.join([f.implementation() for f in self.functions.values() if f.predicate()])
  
  # Define a whole bunch of static versions of these functions
  def static_includes(self):
    pass
  
  def static_defines(self):
    pass
  
  def static_globals(self):
    pass
  
  def static_functionPrototypes(self):
    pass
  
  def static_functionImplementations(self):
    pass
  
  def allocate(self):   return self.implementationsForChildren('allocate')
  def free(self):       return self.implementationsForChildren('free')
  
  def initialise(self): return self.implementationsForChildren('initialise')
  def finalise(self):   return self.implementationsForChildren('finalise')
  
  # Insert code for a list of features by calling a named function
  def insertCodeForFeatures(self, functionName, featureList, dict = None, reverse = False):
    """
    This function is at the core of the 'Feature' system used by xpdeint. Its design is kinda
    like aspect-oriented programming in that the idea is to separate code that is logically separate
    but required in a large number of places. The idea behind this system is the creation of a 
    number of named 'insertion points' in the generated C++ code where features can insert code
    if they need to. Usually these insertion points come in pairs called 'someFunctionNameBegin'
    and 'someFunctionNameEnd' that are at balanced points in the code.
    
    The 'Feature' system diverges from the usual Aspect-Oriented programming approach in that when
    you specify the insertion point, you also specify the features that you want to be able to insert
    code at that point and the order in which they should be able to insert code. This allows for a
    sensible balancing of the inserted code such that when code from multiple features is inserted
    at a 'someFunctionBegin' insertion point, by calling the `insertCodeForFeaturesInReverseOrder`
    method at the 'someFunctionEnd' insertion point, the code necessary for these features at this
    insertion point is inserted in the opposite order than for 'someFunctionBegin'.
    
    The optional `dict` argument can be used to pass additional variables to the features. The 
    `reverse` argument should not be used, it is used internally by `insertCodeForFeaturesInReverseOrder`.
    """
    featureDictionary = self.getVar('features')
    
    if not dict:
        dict = {}
    
    if self.hasattr('bannedFeatures') and self.bannedFeatures:
      # Check if any of the features in the featureList are in the bannedFeatures
      bannedFeatures = self.bannedFeatures
      featureList = [x for x in featureList if x not in bannedFeatures]

    result = []
    filt = self._CHEETAH__currentFilter
    dict['featureList'] = featureList
    
    # Loop over the features that we should include
    for featureName in featureList:
      if featureName not in featureDictionary:
        # If we don't have the feature, then there isn't much we can do
        continue
      
      # Grab the feature
      feature = featureDictionary[featureName]
      
      # If the function doesn't exist, we're done
      if not feature.hasattr(functionName):
        continue
      
      # Get functionName on feature by name
      featureFunction = getattr(feature, functionName)
      
      # Get the extra indent value, if we were passed one in the dict object
      extraIndent = dict.get('extraIndent', 0)
      
      # The caller object is self.
      dict['caller'] = self
      
      # Call the function with optional dictionary
      featureResult = featureFunction(dict)
      
      if not reverse:
        # If we're doing this in the forward order, then we use the initial indent as the indent for the result
        result.append(filt(featureResult, extraIndent=extraIndent))
      else:
        # If we're doing this in reverse order, then we use the final indent as the indent for the result
        result.append(filt(featureResult, extraIndent=dict.get('extraIndent', 0)))
    
    return ''.join(result)
  
  # Insert code for a list of features (in reverse order) by calling a named function
  def insertCodeForFeaturesInReverseOrder(self, functionName, featureList, dict = None):
    # Create a reversed feature list
    reversedFeatureList = featureList[:]
    reversedFeatureList.reverse()
    
    return self.insertCodeForFeatures(functionName, reversedFeatureList, dict, reverse=True)
  
  # Insert contents of function for self, classes and children
  def implementationsForFunctionName(self, functionName, *args, **KWs):
    """
    Helper function to call the function `functionName` for this instance, its
    class and its children and return the combined result as a string.
    """
    result = []
    blankLineSeparator = ''
    staticFunctionName = 'static_' + functionName
    
    for attrName in [staticFunctionName, functionName]:
      if self.hasattr(attrName):
        function = getattr(self, attrName)
        functionOutput = function(*args, **KWs)
        if functionOutput and not functionOutput.isspace():
          result.append(blankLineSeparator)
          blankLineSeparator = '\n'
          result.append(functionOutput)
    
    for child in self.children:
      childFunctionOutput = child.implementationsForFunctionName(functionName, *args, **KWs)
      if childFunctionOutput and not childFunctionOutput.isspace():
        result.append(blankLineSeparator)
        blankLineSeparator = '\n'
        result.append(childFunctionOutput)
    
    return ''.join(result)
  
  # Insert contents of function for children
  def implementationsForChildren(self, functionName, *args, **KWs):
    if not self.hasattr('children'):
      return
    result = []
    blankLineSeparator = ''
    for child in self.children:
      if child.hasattr(functionName) and callable(getattr(child, functionName)):
        childFunction = getattr(child, functionName)
        childFunctionOutput = childFunction(*args, **KWs)
        if childFunctionOutput and not childFunctionOutput.isspace():
          result.append(blankLineSeparator)
          blankLineSeparator = '\n'
          result.append(childFunctionOutput)
    
    return ''.join(result)
  
  def bindNamedVectors(self):
    """
    Part of the 'preflight' system. Once templates have been parsed, a template should
    implement this method if it needs to bind the name of a vector to the actual vector
    object itself and check if it even exists.
    """
    if self.dependenciesEntity:
      self.dependencies.update(self.vectorsFromEntity(self.dependenciesEntity))
    
    for codeBlock in self.codeBlocks.values():
      # Warning: This means that codeBlocks will have bindNamedVectors() called twice.
      codeBlock.bindNamedVectors()
    
  
  def preflight(self):
    """
    Part of the 'preflight' system. This function is guaranteed to be called after `bindNamedVectors`
    has been called on all templates. This is where other post-parsing code goes before the simulation
    is converted to a C++ source file.
    """
    if hasattr(self, 'uselib'):
      self.getVar('simulationUselib').update(self.uselib)
    if hasattr(self, 'buildVariant'):
      buildVariant = self.getVar('simulationBuildVariant')
      if buildVariant and not self.buildVariant in buildVariant:
        raise ParserException(self, "Internal Error. More than one build variant is trying to be used.\n"
                                    "Please report this error to %s\n" % self.getVar('bugReportAddress'))
      buildVariant.add(self.buildVariant)
    
  
  def vectorsFromEntity(self, entity):
    """
    Given the `ParsedEntity` `entity`, return the set of vectors corresponding to the list of names
    in the value of the XML element contained by the entity.
    """
    vectors = set()
    vectorDictionary = dict([(vector.name, vector) for vector in self.getVar('simulationVectors')])
    
    ancestors = []
    currObject = self
    while currObject:
      ancestors.append(currObject)
      currObject = currObject.parent
    
    for vectorName in entity.value:
      vector = None
      replacementVector = None
      if self.parent:
        replacementVector = self.parent.vectorForVectorName(vectorName, vectorDictionary)
      
      if not vectorName in vectorDictionary:
        raise ParserException(entity.xmlElement, "Unknown vector '%(vectorName)s'." % locals())
      else:
        vector = vectorDictionary[vectorName]
      
      if not (vector.parent == vector.field or vector.parent in ancestors):
        raise ParserException(entity.xmlElement, "Cannot access vector '%(vectorName)s' here. It is not available in this scope." % locals())
      
      if replacementVector:
        vectors.add(replacementVector)
      else:
        vectors.add(vector)
    return vectors
  
  def vectorForVectorName(self, vectorName, vectorDictionary):
    """
    Function that can be used by a template to override the mapping of vector names to vectors for children
    """
    if self.parent:
      return self.parent.vectorForVectorName(vectorName, vectorDictionary)
  
  def basisIndexForBasis(self, basis):
    transformMultiplier = self.getVar('features')['TransformMultiplexer']
    return transformMultiplier.basesNeeded.index(basis)
  
  def transformVectorsToBasis(self, vectors, basis):
    result = []
    for vector in vectors:
      if not vector.needsTransforms:
        continue
      vectorBasis = vector.field.basisForBasis(basis)
      if not vector.isTransformableTo(vectorBasis):
        raise ParserException(
          self.xmlElement,
          "Cannot satisfy dependence on vector '%s' because it cannot be transformed to the required basis (%s). "
          "The vector's initial basis is (%s)." % (vector.name, ', '.join(vectorBasis), ', '.join(vector.initialBasis))
        )
      basisString = ', '.join(vectorBasis)
      basisIndex = self.basisIndexForBasis(vectorBasis)
      result.extend([vector.functions['basisTransform'].call(new_basis=basisIndex), ' // (', basisString, ')\n'])
    return ''.join(result)
  
  def registerVectorsRequiredInBasis(self, vectors, basis):
    for vector in vectors:
      vector.basesNeeded.add(vector.field.basisForBasis(basis))
  
  def remove(self):
    """
    Remove the template from various global lists that it may have got itself into. This method
    should be called if a template is no longer needed and should be deleted.
    """
    self.getVar('templates').discard(self)
    
    for someIterable in (self.parent.children, self.getVar('fields'),
                         self.getVar('simulationVectors'), self.getVar('momentGroups')):
      while self in someIterable:
        someIterable.remove(self)
    
    if self.hasattr('children'):
      for child in self.children:
        child.remove()
    
    self._haveBeenRemoved = True
  
  def templateObjectFromStringWithTemplateVariables(self, templateString, templateVars):
    """
    Return a Cheetah template object (using the appropriate settings) for `templateString`
    with the dictionary `templateVars` as variables available in the template.
    """
    settings = {'directiveStartToken': '@',
                'commentStartToken': '@#',
                'multiLineCommentStartToken': '@*',
                'multiLineCommentEndToken': '*@'
               }
    return Template(source = templateString,
                    searchList = [templateVars],
                    compilerSettings = settings)
  
  
  def evaluationOrderForVectors(self, vectors, static, predicate = lambda x: True):
    """
    Return the ordering for the noise vectors and computed vectors in `vectors` taking into account
    any dependencies between the computed vectors.
    """
    
    def checkSelfConsistentVectors(vectors, static):
      #We check that all noise vectors are static or dynamic, as requested.
      for nv in [nv for nv in vectors if nv.isNoise and nv.static != static]:
        if static:
            raise ParserException(self.xmlElement, "Dynamic noises (Wiener and Jump processes) cannot be used outside of integration elements.\n"
                                                   "Perhaps %s should be a static noise (like 'Gaussian' or 'Poissonian')?" % nv.name)
        if not static:
            raise ParserException(self.xmlElement, "Static noises cannot be used inside integration elements.\n"
                                                   "Perhaps %s should be a dynamic noise (like 'Wiener' or 'Jump')?" % nv.name)
          
    stack = []
    
    checkSelfConsistentVectors(vectors, static)
    
    # The algorithm here is basically to start with any given vector and traverse
    # the dependencies recursively until a vector with no dependencies is found,
    # then it is added to the list of ordered dependencies. If, during a traversal,
    # a vector is encountered twice (the history of traversed dependencies is stored
    # in the stack variable), then a circular dependency chain has been hit and a
    # ParserException will be raised. Once all of a vector's dependencies have been
    # added to the orderedDependencies, the vector itself can be added.
    # Finally, if a vector is already in orderedDependencies, we don't need to consider
    # its dependencies as they will have already been considered when considering the
    # dependencies of another vector that depends on the first vector. Clear as mud?
    
    def orderedDependenciesForVectors(vectors):
      """
      Helper function that is called recursively to return the ordering for the computed
      vectors such that all dependencies of a computed vector are evaluated first. Any
      circular dependencies will result in a ParserException.
      """
      orderedDependencies = []
      checkSelfConsistentVectors(vectors, static)
      
      for v in vectors:
        # If v is in the ordering, then it has already been taken care of
        if v in orderedDependencies:
          continue
        
        # If v is on the stack, then we have a circular dependency
        if v in stack:
          startIndex = stack.index(v)
          conflictList = stack[startIndex:]
          conflictList.append(v)
          raise ParserException(self.xmlElement, "Cannot construct ordering for computed vector dependencies.\n"
                                                 "The vectors causing the conflict are: %s." % ' --> '.join([v.name for v in conflictList]))
        
        # v is not on the stack, so we are safe
        # Put v on the stack
        stack.append(v)
        # Add the dependencies for v at the end of my dependencies if they aren't there already
        newDependencies = orderedDependenciesForVectors([u for u in v.dependencies if predicate(u)])
        orderedDependencies.extend([v for v in newDependencies if not v in orderedDependencies])
        # Pop v off the stack and put it on our orderedDependencies
        orderedDependencies.append(stack.pop())
      
      return orderedDependencies
    
    return orderedDependenciesForVectors([v for v in vectors if predicate(v)])
  
  def dynamicVectorsNeedingPrecalculationForOperatorContainers(self, operatorContainers):
    """
    Return a set of computed vectors that `operatorContainers` need to be precomputed
    before executing the operator containers.
    """
    result = set()
    for operatorContainer in operatorContainers:
      result.update(operatorContainer.dynamicVectorsNeedingPrecalculation)
    return result
  
  @staticmethod
  def extractLocalKWs(legalKWs, KWs):
    result = {}
    for key in KWs.copy():
      if key in legalKWs:
        result[key] = KWs[key]
        del KWs[key]
    return result
    
  
  def wrapArray(self, array):
    return '\n  ' + textwrap.fill(', '.join([repr(float(e)) for e in array]), subsequent_indent='  ') + '\n  '