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(_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 performIPOperatorSanityCheck(componentName, propagationDimension, operatorCodeSlice, codeBlock): """ Check that the user hasn't tried to use an IP operator where an IP operator cannot be used. IP operators must be diagonal, so one cannot have expressions of the form ``dy_dt = L[x];`` for IP operators. This is valid for EX operators, but not for IP. This is a common mistake for users to make, and so we should do our best to spot it and report the error. Another mistake users make is trying to multiply the operator, for example ``dy_dt = i*L[y];``. This code does a sophisticated validation by constructing a parse tree for each statement in the code taking into account operator precedence. This sanity checking is even able to pick up problems such as ``dphi_dt = i*(V*phi + U*mod2(phi)*phi + T[phi]);``. If the user's code passes this test, then it is a reasonable assumption that they are using IP operators safely. """ operatorString = codeBlock.codeString[operatorCodeSlice] expr = Forward() operatorKeyword = Keyword(operatorString).setResultsName('targetOperator') operand = operatorKeyword \ | (identifier + Group('(' + delimitedList(expr) + ')')) \ | (identifier + Group(OneOrMore('[' + expr + ']'))) \ | quotedString.copy() \ | identifier \ | numericConstant operand.ignore(cppStyleComment.copy()) expr << operatorPrecedence( operand, [ (oneOf('++ --'), 1, opAssoc.LEFT), (oneOf('. ->'), 2, opAssoc.LEFT), (~oneOf('-> -= += *= &= |=') + oneOf('+ - ! ~ * & ++ --'), 1, opAssoc.RIGHT), (~oneOf('*= /= %=') + oneOf('* / %'), 2, opAssoc.LEFT), (~oneOf('++ -- -> -= +=') + oneOf('+ -'), 2, opAssoc.LEFT), # Although the operators below don't all have the same precedence, as we don't actually # care about them as they are all invalid uses of the IP operator, we can cheat and lump # them together (~oneOf('<<= >>= &= |=') + oneOf('<< >> < <= > >= == != & ^ | && ||'), 2, opAssoc.LEFT), # Correct ordering # (~oneOf('<<= >>=') + oneOf('<< >>'), 2, opAssoc.LEFT), # (~oneOf('<< >> <<= >>=') + oneOf('< <= > >='), 2, opAssoc.LEFT), # (oneOf('== !='), 2, opAssoc.LEFT), # (~oneOf('&& &=') + '&', 2, opAssoc.LEFT), # ('^', 2, opAssoc.LEFT), # (~oneOf('|| |=') + '|', 2, opAssoc.LEFT), # ('&&', 2, opAssoc.LEFT), # ('||', 2, opAssoc.LEFT), (('?',':'), 3, opAssoc.RIGHT), (~Literal('==') + oneOf('= += -= *= /= %= <<= >>= &= ^= |= =>'), 2, opAssoc.RIGHT), (',', 2, opAssoc.LEFT), ] ) expr.ignore(cppStyleComment.copy()) statement = expr + Suppress(';') stack = [] expectedAssignmentVariable = 'd%(componentName)s_d%(propagationDimension)s' % locals() def validateStack(): """ It is the job of this function to validate the operations that the located operator is involved in. The stack describes the part of the parse tree in which the operator was found. The first element in the stack is the outermost operation, and the last the innermost. The last element is guaranteed to be the operator itself. """ # Reverse the stack as we want to search the parse tree from inner-most expression to outer-most. stack.reverse() assignmentHit = False errorMessageCommon = "Due to the way IP operators work, they can only contribute to the derivative of the variable " \ "they act on, i.e. dx_dt = L[x]; not dy_dt = L[x];\n\n" # We don't need to check the first element of the stack # as we are guaranteed that it is the operator itself. This will be useful for determining # which part of the parse tree we should be looking at. for idx, node in enumerate(stack[1:]): if len(node) == 1: continue # idx is the index in the stack of the next element *deeper* in the parse tree. previousStackEntry = stack[idx] if not isinstance(stack[idx], basestring): previousStackEntry = previousStackEntry.asList() binaryOpIdx = node.asList().index(previousStackEntry) - 1 if binaryOpIdx < 0: binaryOpIdx = 1 # Unary '+' is safe. if node[0] == '+': continue # Binary '+' is safe. if node[binaryOpIdx] == '+': continue # Binary '-' is safe if the operator is the first argument. if node[binaryOpIdx] == '-' and node.asList().index(previousStackEntry) == 0: continue # Assignment is safe if it there is only one, and if it's to the right variable if node[binaryOpIdx] in ['=', '+=']: if node[0] == expectedAssignmentVariable: assignmentHit = True continue else: return errorMessageCommon + "In this case, you should probably use an EX operator instead of an "\ "IP operator." else: return errorMessageCommon + "You appear to be using the IP operator in an unsafe operation. " \ "The most likely cause is trying to multiply it by something, e.g. dphi_dt = 0.5*L[phi]; "\ "If this is the cause and you are multiplying by a constant, just move the constant into the "\ "definition of the operator itself. i.e. L = -0.5*kx*kx; If you are multiplying by something "\ "that isn't constant e.g. dphi_dt = x*L[phi]; where x is a dimension, you must use an EX operator "\ "instead." if not assignmentHit: return errorMessageCommon + "You appear to be missing the assignment for this particular operator." return True class FoundTargetException(Exception): pass def findOperatorInParseTree(results): stack.append(results) if 'targetOperator' in results: stack.append(results.targetOperator) raise FoundTargetException() for item in results: if isinstance(item, basestring): continue findOperatorInParseTree(item) del stack[-1] try: foundOperator = False for tokens, start, end in statement.scanString(codeBlock.codeString): if start > operatorCodeSlice.stop or end < operatorCodeSlice.start: continue try: findOperatorInParseTree(tokens) except FoundTargetException: foundOperator = True result = validateStack() if result is not True: raise CodeParserException( codeBlock, operatorCodeSlice.start, result + ("\n\nThe conflict was caused by the operator '%s'." \ % operatorString) ) if not foundOperator: parserWarning( codeBlock.xmlElement, "Unable to check the safety of your IP operator '%s' because the containing expression could not be found. " "Please send a copy of your script to [email protected] so this problem can be investigated." \ % operatorString ) except RuntimeError: parserWarning( codeBlock.xmlElement, "Unable to check the safety of your IP operator because your code is too deeply nested." )
# Attempt to import lxml and run the script through # the schema try: from lxml import etree except ImportError, err: pass else: # Parse the schema relaxng_doc = etree.parse(resource_filename(__name__, 'support/xpdeint.rng')) relaxng = etree.RelaxNG(relaxng_doc) # Parse the script script_doc = etree.fromstring(globalNameSpace['inputScript']) if not relaxng.validate(script_doc): # Validation failed for error in relaxng.error_log: parserWarning((error.line, error.column), error.message) globalNameSpace['debug'] = debug globalNameSpace['xmlDocument'] = xmlDocument globalNameSpace['features'] = {} globalNameSpace['fields'] = [] globalNameSpace['simulationVectors'] = [] globalNameSpace['momentGroups'] = [] globalNameSpace['symbolNames'] = set() globalNameSpace['xmds'] = {'versionString': versionString, 'subversionRevision': subversionRevisionString} globalNameSpace['templates'] = set() globalNameSpace['precision'] = 'double' globalNameSpace['simulationBuildVariant'] = set() globalNameSpace['simulationUselib'] = set()