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:]