class ModelAccess(IterativeTreeChecker): pyomo.util.plugin.alias( 'model.rule.model_access', 'Check that a rule does not reference a global model instance.') ModelTrackerHook() def checkerDoc(self): return """\ Within model rules, you should access the instance of the model that is passed in to the function, rather than the global model instance. For example: def rule(m, i): return m.x[i] >= 10.0 # not model.x[i] """ def check(self, runner, script, info): if isinstance(info, ast.FunctionDef): attrNodes = [ x for x in list(ast.walk(info)) if isinstance(x, ast.Attribute) ] for attrNode in attrNodes: if attrNode.value.id in script.modelVars: args = getattr(script, 'functionArgs', []) if len(args) > 0 and not attrNode.value.id in list( arg_name(arg) for arg in args[-1].args): # NOTE: this probably will not catch arguments defined as keyword arguments. self.problem( "Expression '{0}.{1}' may access a model variable that is outside of the function scope" .format(attrNode.value.id, attrNode.attr), lineno=attrNode.lineno)
class ArrayValue(IterativeTreeChecker): pyomo.util.plugin.alias( 'model.array_value', 'Check if assigning a value to an array of variables') ModelTrackerHook() varArrays = {} def checkerDoc(self): return """\ Assigning a value to an array of variables does nothing. """ def checkVarArray(self, script, node): """Check for the creation of a new VarArray; store name if created""" if isinstance(node.value, ast.Call): if isinstance(node.value.func, ast.Name): if node.value.func.id == 'Var': if len(node.value.args) > 0: for target in node.targets: if isinstance(target, ast.Attribute): if isinstance(target.value, ast.Name): if target.value.id in script.modelVars: if target.value.id not in self.varArrays: self.varArrays[ target.value.id] = [] self.varArrays[target.value.id].append( target.attr) def checkArrayValue(self, script, node): for target in node.targets: if isinstance(target, ast.Attribute): if isinstance(target.value, ast.Attribute): if isinstance(target.value.value, ast.Name): if target.value.value.id in script.modelVars: if target.value.value.id in self.varArrays: if target.value.attr in self.varArrays[ target.value.value.id]: if target.attr == 'value': self.problem( "Assigning value to variable array {0}.{1}" .format(target.value.value.id, target.value.attr), lineno=node.lineno) def check(self, runner, script, info): if isinstance(info, ast.Assign): self.checkVarArray(script, info) self.checkArrayValue(script, info)
class ModelShadowing(IterativeTreeChecker): pyomo.util.plugin.alias('model.rule.shadowing', 'Ignoring for now') ModelTrackerHook() def checkerDoc(self): return """\ Reusing the name of your model variable in a rule may lead to problems where the variable shadows the global value. In your rule definitions, consider changing the name of the model argument. """ def check(self, runner, script, info): if isinstance(info, ast.FunctionDef): for arg in info.args.args: if isinstance(arg, ast.Name): if arg.id in script.modelVars: self.problem("Function {0} may shadow model variable {1}".format(info.name, arg.id), lineno=info.lineno)
class ModelValue(_ModelRuleChecker): pyomo.common.plugin.alias('model.value', 'Check if comparisons are done using the "value()" function.') ModelTrackerHook() def checkerDoc(self): return """\ Comparisons done on model objects should generally be wrapped in a call to value(). The comparison alone will not produce a True/False result, but instead generate an expression for later use in a model. """ def check(self, runner, script, info): # call superclass to execute checkBody() as necessary _ModelRuleChecker.check(self, runner, script, info) # also check global If statements if isinstance(info, ast.If): self.checkCompare(info.test, script = script) def checkBody(self, funcdef): """Check the body of a function definition for model comparisons local to its scope (i.e. using its model argument).""" if not isinstance(funcdef.args.args[0], ast.Name): return modelArg = funcdef.args.args[0].id for bodyNode in funcdef.body: for node in ast.walk(bodyNode): if isinstance(node, ast.If): self.checkCompare(node.test, modelName = modelArg) def checkCompare(self, compare, modelName = None, script = None): """Check an AST Compare node - iterate for Attribute nodes and match against modelName argument. Recurse for script's model defs.""" if modelName is None and script is None: return if modelName is not None: valueCallArgs = [] generatorExps = [] for node in ast.walk(compare): if isinstance(node, ast.Attribute): if isinstance(node.value, ast.Name): if node.value.id == modelName: wrapped = self.checkWrapped(node, valueCallArgs, generatorExps) if not wrapped: self.problem("Comparison on attribute {0}.{1} not wrapped in value()".format(modelName, node.attr), lineno=compare.lineno) elif isinstance(node, ast.Call): if isinstance(node.func, ast.Name): if node.func.id == 'value': valueCallArgs.append(node.args) elif isinstance(node, ast.GeneratorExp): generatorExps.append(node) if script is not None: for name in script.modelVars: self.checkCompare(compare, modelName = name) def checkWrapped(self, attrNode, valueCallArgs, generatorExps): """check if the given attribute node has been 'wrapped', either in a value() call or as part of the iterator in a generator expression""" for i in range(len(valueCallArgs)): for j in range(len(valueCallArgs[i])): # i = call idx (to return), j = arg idx argNode = valueCallArgs[i][j] for subnode in ast.walk(argNode): if subnode is attrNode: return True for genExp in generatorExps: for generator in genExp.generators: for subnode in ast.walk(generator.iter): if subnode is attrNode: return True return False