def preflight(self): super(_EXOperator, self).preflight() for operatorName in self.operatorNames: self.operatorComponents[operatorName] = {} operatorNamesUsed = set() operatorNames = set(self.operatorNames) sharedCodeBlock = self.parent.sharedCodeBlock operatorTargetPairs = CodeParser.targetComponentsForOperatorsInString(self.operatorNames, sharedCodeBlock) targetComponents = set() for vector in sharedCodeBlock.dependencies: targetComponents.update(vector.components) indexAccessedVariables = None # We loop over this in reverse order as we will be modifying the code string. So in order to not have to # re-run targetComponentsForOperatorsInString after each modification, we loop over the operatorTargetPairs in # reverse order so that slices (character index ranges) for earlier operator-target pairs don't change for operatorName, target, codeSlice in reversed(operatorTargetPairs): operatorNamesUsed.add(operatorName) # Target is what is inside the square brackets in the integration code block # As this is the EX operator, we have fewer constraints. # If the target matches something of the form 'phi' or 'phi[j+3, k*k][m % 3, n]' # Then we don't need to use our result vector to make 'phi' available to the operator # If it doesn't match, then we have to assume that the expression is well formed, and # copy it into the result vector then fourier transform it into the space of the operator targetVector = None replacementStringSuffix = '' if target in targetComponents: targetComponentName = target # We have direct access to the component, so just work out which vector it belongs to # Now we need to get the vector corresponding to componentName tempVectorList = [v for v in sharedCodeBlock.dependencies if targetComponentName in v.components] assert len(tempVectorList) == 1 targetVector = tempVectorList[0] else: # The target of our EX operator isn't a simple component. # In principle, this could be OK. We just need to construct the variable using the result vector. # If the user has made a mistake with their code, the compiler will barf, not xpdeint. This isn't ideal # but we can't understand an arbitrary string; that's what the compiler is for. targetComponentName = sharedCodeBlock.addCodeStringToSpecialTargetsVector(target, codeSlice) targetVector = sharedCodeBlock.specialTargetsVector # We have our match, now we need to create the operatorComponents dictionary if not operatorName in self.operatorComponents: self.operatorComponents[operatorName] = {} if not targetVector in self.operatorComponents[operatorName]: self.operatorComponents[operatorName][targetVector] = [targetComponentName] elif not targetComponentName in self.operatorComponents[operatorName][targetVector]: self.operatorComponents[operatorName][targetVector].append(targetComponentName) if targetVector.type == 'complex': for v in [self.operatorVector, self.resultVector]: if v: v.type = 'complex' # Set the replacement string for the L[x] operator replacementString = "_%(operatorName)s_%(targetComponentName)s" % locals() sharedCodeBlock.codeString = sharedCodeBlock.codeString[:codeSlice.start] + replacementString + sharedCodeBlock.codeString[codeSlice.stop:] # If any operator names weren't used in the code, issue a warning unusedOperatorNames = operatorNames.difference(operatorNamesUsed) if unusedOperatorNames: unusedOperatorNamesString = ', '.join(unusedOperatorNames) parserWarning(self.xmlElement, "The following EX operator names were declared but not used: %(unusedOperatorNamesString)s" % locals()) # Iterate over the operator components adding the appropriate bits to the resultVector, but do it in # the order of the components in the targetVectors to make it easier to optimise out an FFT. for operatorName, operatorDict in self.operatorComponents.iteritems(): for targetVector, targetVectorUsedComponents in operatorDict.iteritems(): for targetComponent in targetVector.components: if targetComponent in targetVectorUsedComponents: self.resultVector.components.append("_%(operatorName)s_%(targetComponent)s" % locals()) if self.resultVector.nComponents == 0: self.resultVector.remove() self.resultVector = None # Add the result vector to the shared dependencies for the operator container # These dependencies are just the delta a dependencies, so this is just adding # our result vector to the dependencies for the delta a operator if self.resultVector: sharedCodeBlock.dependencies.add(self.resultVector) # If we are nonconstant then we need to add the target vectors to the dependencies of our primary code block if not 'calculateOperatorField' in self.functions: self.primaryCodeBlock.dependencies.update(self.targetVectors) vectors = set(self.targetVectors) vectors.add(self.resultVector) self.registerVectorsRequiredInBasis(vectors, self.operatorBasis)
def preflight(self): super(_DeltaAOperator, self).preflight() # Construct the operator components dictionary for integrationVector in self.integrationVectors: for componentName in integrationVector.components: derivativeString = "d%s_d%s" % (componentName, self.propagationDimension) # Map of operator names to vector -> component list dictionary self.operatorComponents[derivativeString] = {integrationVector: [componentName]} # Check that the user code block contains derivatives for every vector. # If not, throw an exception. if not derivativeString in self.primaryCodeBlock.codeString: raise ParserException( self.primaryCodeBlock.xmlElement, "Missing derivative for integration variable '%s' in vector '%s'." % (componentName, integrationVector.name) ) # Our job here is to consider the case where the user's integration code # depends on a component of an integration vector which might get overwritten # in the process of looping over the integration code. For example, if the # user has code like: # dx_dt[j] = x[j-1] # then on the previous loop, x[j-1] will have been overwritten with # dx_dt[j-1]*_step (due to the way the delta a operator works). Consequently, # x[j-1] won't mean what the user think it means. This would be OK if the code # was # dx_dt[j] = x[j + 1] # however we cannot safely know in all cases if j + 1 is greater than j or not. # # The solution will be to create an array to save all of the results for dx_dt # and then copy the results back in to the x array. # # As an optimisation, we don't want to do this if all of the accesses for an # integer valued dimension is with just the value of the dimension index. # # Additionally, if we have an integer-valued dimension at the start that we # need to fix this problem for, the array would need to be large enough to # hold all of the dimensions after that dimension as well. To reduce the # memory requirement for this, we will re-order the looping of the dimensions # to put any integer-valued dimensions that need this special treatment as the # innermost loops. dimRepNamesNeedingReordering = set() # Not all integration vectors may be forcing this reordering. For any that aren't, # we can just do the normal behaviour. This saves memory. self.vectorsForcingReordering = set() components = set() derivativeMap = {} propagationDimension = self.propagationDimension basis = self.primaryCodeBlock.basis dimRepNameMap = dict([(dimRep.name, dimRep) for dimRep in self.field.inBasis(basis)]) for vector in self.integrationVectors: components.update(vector.components) for componentName in vector.components: derivativeString = ''.join(['d', componentName, '_d', propagationDimension]) components.add(derivativeString) derivativeMap[derivativeString] = vector indexAccessedVariables = CodeParser.nonlocalDimensionAccessForComponents(components, self.primaryCodeBlock) simulationDriver = self.getVar('features')['Driver'] for componentName, resultDict, codeSlice in indexAccessedVariables: vectors = [v for v in self.integrationVectors if componentName in v.components] if len(vectors) == 1: # Either our component belongs to one of the integration vectors vector = vectors[0] else: # Or it is a derivative, and so the vector we should use is the one for the original component vector = derivativeMap[componentName] # Add the dimension names that aren't being accessed with the dimension variable # to the set of dimensions needing reordering. dimRepNamesForThisVectorNeedingReordering = [dimRepName for dimRepName, (indexString, accessLoc) in resultDict.iteritems() if indexString != dimRepName] if vector.field.isDistributed: distributedDimRepsNeedingReordering = set( [dimRep.name for dimRep in self.field.inBasis(basis) if dimRep.hasLocalOffset] ).intersection(dimRepNamesForThisVectorNeedingReordering) if distributedDimRepsNeedingReordering: # This vector is being accessed nonlocally on a dimension that is distributed. This isn't legal. dimRepName = list(distributedDimRepsNeedingReordering)[0] raise ParserException(self.xmlElement, "The dimension '%(dimRepName)s' cannot be accessed nonlocally because it is being distributed with MPI. " "Try turning off MPI or re-ordering the dimensions in the <geometry> element." % locals()) if dimRepNamesForThisVectorNeedingReordering: # If we have any dimensions that need reordering for this vector, add them to the complete set dimRepNamesNeedingReordering.update(dimRepNamesForThisVectorNeedingReordering) # ... and add the vector itself to the set of vectors forcing this reordering. self.vectorsForcingReordering.add(vector) # We now have all of the dimension names that need re-ordering to the end of the array. # We only need to do our magic if this set is non-empty if dimRepNamesNeedingReordering: # Now we need to construct a new field which has the same dimensions as self.field, # but has the dimensions that need reordering at the end. newFieldDimensions = self.field.dimensions[:] dimensionsNeedingReordering = [] # Remove the dimensions needing reordering and replace them at the end for dim in newFieldDimensions[:]: if dim.inBasis(basis).name in dimRepNamesNeedingReordering: newFieldDimensions.remove(dim) newFieldDimensions.append(dim) dimensionsNeedingReordering.append(dim) loopingFieldName = ''.join([self.integrator.name, '_', self.name, '_looping_field']) loopingField = FieldElement(name = loopingFieldName, **self.argumentsToTemplateConstructors) loopingField.dimensions = [dim.copy(parent=loopingField) for dim in newFieldDimensions] self.primaryCodeBlock.field = loopingField # Now construct a second field for the vector which will hold our delta a operators deltaAFieldName = ''.join([self.integrator.name, '_', self.name, '_delta_a_field']) self.deltaAField = FieldElement(name = deltaAFieldName, **self.argumentsToTemplateConstructors) self.deltaAField.dimensions = [dim.copy(parent = self.deltaAField) for dim in dimensionsNeedingReordering] propagationDimension = self.propagationDimension # For each integration vector forcing the reordering, we need to construct # a corresponding vector in the new field. for integrationVector in self.vectorsForcingReordering: deltaAVector = VectorElement( name = integrationVector.name, field = self.deltaAField, parent = self, initialBasis = self.operatorBasis, type = integrationVector.type, **self.argumentsToTemplateConstructors ) # The vector will only need initialisation if the derivatives are accessed out # of order, i.e. dphi_dt[j+1] for example. We can detect this later and change this # if that is the case. deltaAVector.needsInitialisation = False # Construct dx_dt variables for the delta a vector. deltaAVector.components = [''.join(['d', componentName, '_d', propagationDimension]) for componentName in integrationVector.components] # Make sure the vector gets allocated etc. self._children.append(deltaAVector) # Make the vector available when looping self.primaryCodeBlock.dependencies.add(deltaAVector) # Remove the components of the vector from our operatorComponents so that we won't get doubly-defined variables for componentName in deltaAVector.components: del self.operatorComponents[componentName] # Add the new delta a vector to the integration vector --> delta a vector map self.deltaAVectorMap[integrationVector] = deltaAVector # We need to rewrite all the derivatives to only use dimensions in the delta a field (if we have one) # This needs to be done even if we don't have a delta-a field as otherwise writing dx_dt(j: j) wouldn't # get transformed as dx_dt won't be vector. indexAccessedDerivatives = CodeParser.nonlocalDimensionAccessForComponents(derivativeMap.keys(), self.primaryCodeBlock) for componentName, resultDict, codeSlice in reversed(indexAccessedDerivatives): componentAccessString = componentName componentAccesses = [] for dimRepName, (accessString, accessCodeLoc) in resultDict.iteritems(): if not dimRepName in dimRepNamesNeedingReordering: continue componentAccesses.append('%(dimRepName)s => %(accessString)s' % locals()) if componentAccesses: componentAccessString += '(' + ','.join(componentAccesses) + ')' # If we have at least one dimension that is not being accessed with the correct index, # we must initialise the delta a vector just in case. (See gravity.xmds for an example) if any([resultDict[dimRepName][0] != dimRepName for dimRepName in resultDict if dimRepName in dimRepNamesNeedingReordering]): # The integrationVector must be in the deltaAVectorMap because we would have had to allocate # a delta-a vector for this integration vector. deltaAVector = self.deltaAVectorMap[integrationVector] if not deltaAVector.needsInitialisation: deltaAVector.needsInitialisation = True deltaAVector.initialiser = VectorInitialisation(parent = deltaAVector, **self.argumentsToTemplateConstructors) deltaAVector.initialiser.vector = deltaAVector self.primaryCodeBlock.codeString = self.primaryCodeBlock.codeString[:codeSlice.start] + componentAccessString \ + self.primaryCodeBlock.codeString[codeSlice.stop:] if self.deltaAField: copyDeltaAFunctionName = ''.join(['_', self.id, '_copy_delta_a']) loopingField = self.primaryCodeBlock.field arguments = [('real', '_step')] deltaAFieldReps = self.deltaAField.inBasis(self.operatorBasis) arguments.extend([('long', '_' + dimRep.name + '_index') \ for dimRep in loopingField.inBasis(self.operatorBasis) if not dimRep in deltaAFieldReps]) copyDeltaAFunction = Function(name = copyDeltaAFunctionName, args = arguments, implementation = self.copyDeltaAFunctionContents, returnType = 'inline void') self.functions['copyDeltaA'] = copyDeltaAFunction # Create arguments dictionary for a call to the copyDeltaA function arguments = dict([('_' + dimRep.name + '_index', dimRep.loopIndex) \ for dimRep in loopingField.inBasis(self.operatorBasis) if not dimRep in deltaAFieldReps]) functionCall = self.functions['copyDeltaA'].call(parentFunction = self.functions['evaluate'], arguments = arguments) + '\n' self.primaryCodeBlock.loopArguments['postDimensionLoopClosingCode'] = { self.deltaAField.dimensions[0].inBasis(self.operatorBasis).name: functionCall }
def transformCodeString(self): CodeParser.checkForIntegerDivision(self) if self.codeString.count('\n'): # Deindent code and add '#line' compiler directives self.addCompilerLineDirectives()
def preflight(self): super(_IPOperator, self).preflight() for operatorName in self.operatorNames: self.operatorComponents[operatorName] = {} sharedCodeBlock = self.parent.sharedCodeBlock operatorTargetPairs = CodeParser.targetComponentsForOperatorsInString(self.operatorNames, sharedCodeBlock) operatorNamesUsed = set() operatorNames = set(self.operatorNames) integrationVectors = self.parent.deltaAOperator.integrationVectors field = self.field legalTargetComponentNames = set() for v in integrationVectors: legalTargetComponentNames.update(v.components) targetComponentNamesUsed = set() indexAccessedVariables = None # We loop over this in reverse order as we will be modifying the code string. So in order to not have to # re-run targetComponentsForOperatorsInString after each modification, we loop over the operatorTargetPairs in # reverse order so that slices (character index ranges) for earlier operator-target pairs don't change for operatorName, target, codeSlice in reversed(operatorTargetPairs): operatorNamesUsed.add(operatorName) # Target is what is inside the square brackets in the integration code block # As this is the IP operator, we have a few additional constraints # Firstly, the targets must be of the form 'phi' or 'phi[j,k][m,n]' # where j, k, m, n are the names of the integer dimension if target in legalTargetComponentNames: # Everything is OK componentName = target else: if indexAccessedVariables == None: indexAccessedVariables = CodeParser.nonlocalDimensionAccessForVectors( integrationVectors, sharedCodeBlock ) try: # This will extract the componentName corresponding to the indexed variable in the target # or it will fail because it isn't of that form. componentName, resultDict = [ (l[0], l[2]) for l in indexAccessedVariables if sharedCodeBlock.codeString[l[3]] == target ][0] except IndexError: # Target didn't match something of the form 'phi[j, k][m+3,n-9]' raise ParserException( self.xmlElement, "IP operators can only act on components of integration vectors. " "The '%(operatorName)s' operator acting on '%(target)s' doesn't seem to be of the right form " "or '%(target)s' isn't in one of the integration vectors." % locals(), ) # Check that nonlocally-accessed dimensions are being accessed with the dimension names # i.e. of the form 'phi(j: j, k:k, m:m, n:n)' not 'phi(j: j-7, k: k*2, m: 3, n: n+1)' for dimName, (indexString, codeSlice) in resultDict.iteritems(): if not dimName == indexString: raise ParserException( self.xmlElement, "IP operators can only act on every value of a dimension. " "The problem was caused by the '%(operatorName)s' operator acting on '%(target)s'. " "EX operators do not have this restriction." % locals(), ) if componentName in targetComponentNamesUsed: raise ParserException( self.xmlElement, "Check the documentation, only one IP operator can act on a given component, " "and this operator can only appear once. " "The problem was with the '%(componentName)s' term appearing more than once in an IP operator. " "You may be able to accomplish what you are trying with an EX operator." % locals(), ) targetComponentNamesUsed.add(componentName) # Now we need to get the vector corresponding to componentName tempVectorList = [v for v in integrationVectors if componentName in v.components] assert len(tempVectorList) == 1 targetVector = tempVectorList[0] # We have our match, now we need to create the operatorComponents dictionary if not targetVector in self.operatorComponents[operatorName]: self.operatorComponents[operatorName][targetVector] = [componentName] else: self.operatorComponents[operatorName][targetVector].append(componentName) if targetVector.type == "real": self.operatorVector.type = "real" # Check the sanity of the integration code. # i.e. check that we don't have something of the form: # dy_dt = L[x]. # Obviously the user could hide this from us, but if we can check the most # common case that frequently goes wrong, then we should. CodeParser.performIPOperatorSanityCheck( componentName, self.propagationDimension, codeSlice, sharedCodeBlock ) # Replace the L[x] string with 0.0 sharedCodeBlock.codeString = ( sharedCodeBlock.codeString[: codeSlice.start] + "0.0" + sharedCodeBlock.codeString[codeSlice.stop :] ) # If any operator names weren't used in the code, issue a warning unusedOperatorNames = operatorNames.difference(operatorNamesUsed) if unusedOperatorNames: unusedOperatorNamesString = ", ".join(unusedOperatorNames) parserWarning( self.xmlElement, "The following operator names weren't used: %(unusedOperatorNamesString)s" % locals() ) del self.functions["evaluate"] vectors = set(self.targetVectors) self.registerVectorsRequiredInBasis(vectors, self.parent.ipOperatorBasis)
def fixupNonlocallyAccessedComponents(self): """ In user code, the user may refer to parts of a vector nonlocally in integer-valued dimensions. This code translates variables accessed with the ``phi(j: j-3, k:k+5, l:l/2, p:p*p, q:q, r:r)`` notation to a form that can be used in the C++ source file. The form currently used is ``_phi_jklpqr(j-3, k+5, l/2, p*p, q, r)``. This function makes an optimisation where if all dimensions are accessed locally, the ``phi(j: j, k:k, l:l, p:p, q: q, r: r)`` notation is replaced with the string ``phi`` which is a faster way of accessing the local value than through using the ``_phi_jklpqr(...)`` macro. """ vectorsToFix = self.dependencies.copy() if self.targetVector: vectorsToFix.add(self.targetVector) nonlocalVariablesCreated = set() vectorOverrides = self.loopArguments.get('vectorOverrides', []) simulationDriver = self.getVar('features')['Driver'] for componentName, vector, nonlocalAccessDict, codeSlice in reversed(CodeParser.nonlocalDimensionAccessForVectors(vectorsToFix, self)): availableDimReps = vector.field.inBasis(self.basis) validDimensionNames = [dimRep.name for dimRep in availableDimReps] validDimensionNames.extend([dimRep.name + "_index" for dimRep in availableDimReps]) # If the dict is empty, then it probably means something else if not nonlocalAccessDict: continue if vector in vectorOverrides: vectorID = vector.id raise CodeParserException(self, codeSlice.start, "Cannot access vector '%(vectorID)s' non-locally." % locals()) # Check that there are no dimensions listed in the nonlocalAccessDict that don't refer to valid # dimensions for this vector for dimName in nonlocalAccessDict.iterkeys(): if not dimName in validDimensionNames: raise CodeParserException(self, nonlocalAccessDict[dimName][1], "Component '%s' doesn't have dimension '%s'." % (componentName, dimName)) dimRepsNeeded = [dimRep for dimRep in availableDimReps if dimRep.name in nonlocalAccessDict and nonlocalAccessDict[dimRep.name][0] != dimRep.name] dimRepsNeeded.extend([dimRep for dimRep in availableDimReps if dimRep.name + "_index" in nonlocalAccessDict]) if not dimRepsNeeded: replacementString = componentName else: # Check that the mpi distributed dimension isn't being accessed nonlocally. if vector.field.isDistributed: for dimRep in dimRepsNeeded: if dimRep.hasLocalOffset: dimRepName = dimRep.name raise CodeParserException(self, nonlocalAccessDict[dimRepName][1], "It is illegal to access the dimension '%(dimRepName)s' nonlocally because it is being distributed with MPI.\n" "Try not using MPI or changing the order of your dimensions." % locals()) nonlocalAccessVariableName = '_%s_' % componentName nonlocalAccessVariableName += ''.join([dimRep.name for dimRep in dimRepsNeeded]) if not nonlocalAccessVariableName in nonlocalVariablesCreated: # Populate with whatever we have set for us if it's there. indexOverrides = self.loopArguments.get('indexOverrides', {}).copy() for dimRep in dimRepsNeeded: indexOverrides[dimRep.name] = {vector.field: dimRep.loopIndex} argumentsString = ', '.join([dimRep.loopIndex for dimRep in dimRepsNeeded]) vectorID = vector.id componentNumber = vector.components.index(componentName) defineString = "#define %(nonlocalAccessVariableName)s(%(argumentsString)s) " % locals() nonlocalAccessString = "_active_%(vectorID)s[%(componentNumber)s + (0" % locals() for dimRep in vector.field.inBasis(self.basis): termString = self.explicitIndexPointerTermForVectorAndDimRepWithFieldAndBasis(vector, dimRep, self.field, self.basis, indexOverrides) nonlocalAccessString += termString.replace('\n', ' \\\n') nonlocalAccessString += ') * _%(vectorID)s_ncomponents]' % locals() defineString += nonlocalAccessString + '\n' undefineString = "#undef %(nonlocalAccessVariableName)s\n" % locals() featureDict = { 'vector': vector, 'componentName': componentName, 'availableDimReps': availableDimReps, 'dimRepsNeeded': dimRepsNeeded, 'nonlocalAccessVariableName': nonlocalAccessVariableName, 'nonlocalAccessString': nonlocalAccessString, 'defineString': defineString, 'undefineString': undefineString } featureOrdering = ['Diagnostics'] self.insertCodeForFeatures('nonlocalAccess', featureOrdering, featureDict) self.prefixCodeString += featureDict['defineString'] self.postfixCodeString += featureDict['undefineString'] nonlocalVariablesCreated.add(nonlocalAccessVariableName) arguments = [] for dimRep in dimRepsNeeded: accessViaIndex = not dimRep.name in nonlocalAccessDict dimRepVariableName = dimRep.name if not accessViaIndex else dimRep.name + "_index" accessString = nonlocalAccessDict[dimRepVariableName][0] dimRepName = dimRep.name if not accessViaIndex: argumentValue = dimRep.nonlocalAccessIndexFromStringForFieldInBasis(accessString, self.field, self.basis) if not argumentValue: raise CodeParserException(self, nonlocalAccessDict[dimRep.name][1], "Cannot access the '%(dimRepName)s' dimension nonlocally with the string '%(accessString)s'. Check the documentation." % locals()) else: argumentValue = accessString arguments.append('/* %(dimRepVariableName)s => %(accessString)s */ (%(argumentValue)s)' % locals()) argumentsString = ', '.join(arguments) replacementString = '%(nonlocalAccessVariableName)s(%(argumentsString)s)' % locals() # Replace the phi(j => j + 7) string with the appropriate string # i.e. _phi_j(j + 7) self.codeString = self.codeString[:codeSlice.start] + replacementString + self.codeString[codeSlice.stop:]