def clone_without_expression_components(expr, substitute=None): """A function that is used to clone an expression. Cloning is roughly equivalent to calling ``copy.deepcopy``. However, the :attr:`clone_leaves` argument can be used to clone only interior (i.e. non-leaf) nodes in the expression tree. Note that named expression objects are treated as leaves when :attr:`clone_leaves` is :const:`True`, and hence those subexpressions are not cloned. This function uses a non-recursive logic, which makes it more scalable than the logic in ``copy.deepcopy``. Args: expr: The expression that will be cloned. substitute (dict): A dictionary mapping object ids to objects. This dictionary has the same semantics as the memo object used with ``copy.deepcopy``. Defaults to None, which indicates that no user-defined dictionary is used. Returns: The cloned expression. """ if substitute is None: substitute = {} # visitor = EXPR.ExpressionReplacementVisitor(substitute=substitute, remove_named_expressions=True) return visitor.dfs_postorder_stack(expr)
def grad_fd(c, scaled=False, h=1e-6): """Finite difference the gradient for a constraint, objective or named expression. This is only for use in examining scaling. For faster more accurate gradients refer to pynumero. Args: c: constraint to evaluate scaled: if True calculate the scaled grad (default=False) h: step size for calculating finite differnced derivatives Returns: (list of gradient values, list for varibles in the constraint) The order of the variables coresoponds to the gradient values. """ try: ex = c.body except AttributeError: ex = c.expr vars = list(EXPR.identify_variables(ex)) grad = [None]*len(vars) r = {} if scaled: # If you want the scaled grad put in variable scaling tansform orig = [pyo.value(v) for v in vars] for i, v in enumerate(vars): try: sf = v.parent_block().scaling_factor.get(v, 1) except AttributeError: sf = 1 r[id(v)] = v/sf v.value = orig[i]*sf vis = EXPR.ExpressionReplacementVisitor( substitute=r, remove_named_expressions=True, ) e = vis.dfs_postorder_stack(ex) else: e = ex for i, v in enumerate(vars): ov = pyo.value(v) # original variable value f1 = pyo.value(e) v.value = ov + h f2 = pyo.value(e) v.value = ov if scaled: try: sf = c.parent_block().scaling_factor.get(c, 1) except AttributeError: sf = 1 grad[i] = sf*(f2 - f1)/h else: grad[i] = (f2 - f1)/h if scaled: for i, v in enumerate(vars): v.value = orig[i] return grad, vars
def replace(instance, substitute): # Create the replacement dict. Do some argument validation and indexed # var handling d = {} for r in substitute: if not (_is_var(r[0]) and _is_var(r[1])): raise TypeError( "Replace only allows variables to be replaced, {} is type {}" " and {} is type {}".format(r[0], type(r[0]), r[1], type(r[1]))) if r[0].is_indexed() != r[1].is_indexed(): raise TypeError( "IndexedVars must be replaced by IndexedVars, {} is type {}" " and {} is type {}".format(r[0], type(r[0]), r[1], type(r[1]))) if r[0].is_indexed() and r[1].is_indexed(): if not r[0].index_set().issubset(r[1].index_set()): raise ValueError("The index set of {} must be a subset of" " {}.".format(r[0], r[1])) for i in r[0]: d[id(r[0][i])] = r[1][i] else: #scalar replace d[id(r[0])] = r[1] # Replacement Visitor vis = EXPR.ExpressionReplacementVisitor( substitute=d, descend_into_named_expressions=True, remove_named_expressions=False, ) # Do replacements in Expressions, Constraints, and Objectives for c in instance.component_data_objects( (Constraint, Expression, Objective), descend_into=True, active=True): c.set_value(expr=vis.dfs_postorder_stack(c.expr))
def _apply_to(self, instance, max_iter=5, reversible=True): """ Apply the transformation. This is called by ``apply_to`` in the superclass, and should not be called directly. ``apply_to`` takes the same arguments. Args: instance: A block or model to apply the transformation to Returns: None """ self.reversible = reversible if reversible: self._instance = instance self._all_subs = [] self._subs_map = {} self._expr_map = {} self._all_fixes = [] self._all_deactivate = [] self._original = {} # The named expressions could be changed as a side effect of the # constraint expression replacements, so for maximum saftey, just # store all the expressions for Expressions for c in instance.component_data_objects( pyo.Expression, descend_into=True, ): self._original[id(c)] = c.expr self._expr_map[id(c)] = c nr_tot = 0 # repeat elimination until no more can be eliminated or hit max_iter for i in range(max_iter): subs, cnstr, fixes, subs_map = self._get_subs(instance) if reversible: self._all_fixes += fixes self._all_deactivate += cnstr self._all_subs.append(subs) self._subs_map.update(subs_map) nr = len(cnstr) if nr == 0: break nr_tot += nr for c in cnstr: # deactivate constraints that aren't needed c.deactivate() for v in fixes: # fix variables that can be fixed v[0].fix(v[1]) # Do replacements in Expressions, Constraints, and Objectives # where one var is replaced with a linear expression containing # another vis = EXPR.ExpressionReplacementVisitor( substitute=subs, descend_into_named_expressions=True, remove_named_expressions=False, ) for c in instance.component_data_objects( (pyo.Constraint, pyo.Objective), descend_into=True, active=True ): if id(c) not in self._original and reversible: self._original[id(c)] = c.expr self._expr_map[id(c)] = c c.set_value(expr=vis.dfs_postorder_stack(c.expr)) _log.info("Eliminated {} variables and constraints".format(nr_tot))
def _replacement(m, basis): """PRIVATE FUNCTION Create a replacement visitor. The replacement visitor is used on user-provided scaling expressions. These expressions are written with model variables, but you generally don't want to calculate scaling factors based on the curent value of the model variables, you want to use their scaling factors, so the replacment visitor takes the user-defined scaling expression and replaces the model varible by some scaling factor, and returns a new expression. The basis argument can be used to specify the basis to use for scaling. Args: m (Block): model to collect vars from basis (list of ScalingBasis): value type to use as basis for scaling calcs Return: None or ExpressionReplacementVisitor """ # These long ifs up front find values to replace variables in the scaling # expressions with. if basis[0] == ScalingBasis.Value: return None # no need to replace anything if using value else: rdict = {} for v in m.component_data_objects((pyo.Var)): val = 1.0 for b in basis: try: if b == ScalingBasis.VarScale: val = v.parent_block().scaling_factor[v] break elif b == ScalingBasis.InverseVarScale: val = 1/v.parent_block().scaling_factor[v] break elif b == ScalingBasis.Value: val = pyo.value(v) break elif b == ScalingBasis.Mid: if v.lb is not None and v.ub is not None: val = (v.ub + v.lb)/2.0 break elif b == ScalingBasis.Lower: if v.lb is not None: val = v.lb break elif b == ScalingBasis.Upper: if v.ub is not None: val = v.ub break else: _log.warning("Unknown scaling expression basis {}".format(b)) except AttributeError: pass except KeyError: pass rdict[id(v)] = val for v in m.component_data_objects((pyo.Expression)): # check for expression scaling factors, while expressions don't # get scaled, the factor can be used in the calculation of other # scale factors. val = 1.0 for b in basis: try: if b == ScalingBasis.VarScale: val = v.parent_block().scaling_factor[v] break elif b == ScalingBasis.InverseVarScale: val = 1/v.parent_block().scaling_factor[v] break elif b == ScalingBasis.Value: val = pyo.value(v) break else: # Expressions don't have bounds continue except AttributeError: pass except KeyError: pass rdict[id(v)] = val # Use the substitutions dictionary from above to make a replacemnt visitor return EXPR.ExpressionReplacementVisitor(substitute=rdict)