def get_capacity_constraint(backend_model, parameter, loc_tech, _equals=None, _max=None, _min=None, scale=None): decision_variable = getattr(backend_model, parameter) if not _equals: _equals = get_param(backend_model, parameter + '_equals', loc_tech) if not _max: _max = get_param(backend_model, parameter + '_max', loc_tech) if not _min: _min = get_param(backend_model, parameter + '_min', loc_tech) if po.value(_equals) is not False and po.value(_equals) is not None: if np.isinf(po.value(_equals)): e = exceptions.ModelError raise e('Cannot use inf for {}_equals for loc:tech `{}`'.format(parameter, loc_tech)) if scale: _equals *= scale return decision_variable[loc_tech] == _equals else: if po.value(_min) == 0 and np.isinf(po.value(_max)): return po.Constraint.NoConstraint else: if scale: _max *= scale _min *= scale return (_min, decision_variable[loc_tech], _max)
def solveModel(self, x, y, z): model = self.model opt = SolverFactory(self.config.solver) opt.options.update(self.config.solver_options) results = opt.solve( model, keepfiles=self.keepfiles, tee=self.stream_solver) if ((results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal)): model.solutions.load_from(results) for i in range(0, self.lx): x[i] = value(self.TRF.xvars[i]) for i in range(0, self.ly): y[i] = value(self.TRF.y[i+1]) for i in range(0, self.lz): z[i] = value(self.TRF.zvars[i]) for obj in model.component_data_objects(Objective,active=True): return True, obj() else: print("Waring: solver Status: " + str(results.solver.status)) print("And Termination Conditions: " + str(results.solver.termination_condition)) return False, 0
def make2dPlot(expr, numticks=10, show_plot=False): mc_ccVals = [None] * (numticks + 1) mc_cvVals = [None] * (numticks + 1) aff_cc = [None] * (numticks + 1) aff_cv = [None] * (numticks + 1) fvals = [None] * (numticks + 1) mc_expr = mc(expr) x = next(identify_variables(expr)) # get the first variable tick_length = (x.ub - x.lb) / numticks xaxis = [x.lb + tick_length * n for n in range(numticks + 1)] x_val = value(x) # initial value of x cc = mc_expr.subcc() # Concave overestimator subgradient at x_val cv = mc_expr.subcv() # Convex underestimator subgradient at x_val f_cc = mc_expr.concave() # Concave overestimator value at x_val f_cv = mc_expr.convex() # Convex underestimator value at x_val for i, x_tick in enumerate(xaxis): aff_cc[i] = cc[x] * (x_tick - x_val) + f_cc aff_cv[i] = cv[x] * (x_tick - x_val) + f_cv mc_expr.changePoint(x, x_tick) mc_ccVals[i] = mc_expr.concave() mc_cvVals[i] = mc_expr.convex() fvals[i] = value(expr) if show_plot: import matplotlib.pyplot as plt plt.plot(xaxis, fvals, 'r', xaxis, mc_ccVals, 'b--', xaxis, mc_cvVals, 'b--', xaxis, aff_cc, 'k|', xaxis, aff_cv, 'k|') plt.show() return mc_ccVals, mc_cvVals, aff_cc, aff_cv
def _estimate_M(self, expr, name): # Calculate a best guess at M repn = generate_standard_repn(expr) M = [0, 0] if not repn.is_nonlinear(): if repn.constant is not None: for i in (0, 1): if M[i] is not None: M[i] += repn.constant for i, coef in enumerate(repn.linear_coefs or []): var = repn.linear_vars[i] bounds = (value(var.lb), value(var.ub)) for i in (0, 1): # reverse the bounds if the coefficient is negative if coef > 0: j = i else: j = 1 - i if bounds[i] is not None: M[j] += value(bounds[i]) * coef else: raise GDP_Error( "Cannot estimate M for " "expressions with unbounded variables." "\n\t(found unbounded var %s while processing " "constraint %s)" % (var.name, name)) else: raise GDP_Error("Cannot estimate M for nonlinear " "expressions.\n\t(found while processing " "constraint %s)" % name) return tuple(M)
def tear_diff_direct(self, G, tears): """ Returns numpy arrays of values for src and dest members for all edges in the tears list of edge indexes. """ svals = [] dvals = [] edge_list = self.idx_to_edge(G) for tear in tears: arc = G.edges[edge_list[tear]]["arc"] src, dest = arc.src, arc.dest sf = arc.expanded_block.component("splitfrac") for name, mem in src.iter_vars(names=True): if src.is_extensive(name) and sf is not None: # TODO: same as above, what if there's no splitfrac svals.append(value(mem * sf)) else: svals.append(value(mem)) try: index = mem.index() except AttributeError: index = None dvals.append(value(self.source_dest_peer(arc, name, index))) svals = numpy.array(svals) dvals = numpy.array(dvals) return svals, dvals
def copy_var_list_values(from_list, to_list, config, skip_stale=False): """Copy variable values from one list to another.""" for v_from, v_to in zip(from_list, to_list): if skip_stale and v_from.stale: continue # Skip stale variable values. try: v_to.set_value(value(v_from, exception=False)) if skip_stale: v_to.stale = False except ValueError as err: err_msg = getattr(err, 'message', str(err)) var_val = value(v_from) rounded_val = int(round(var_val)) # Check to see if this is just a tolerance issue if 'is not in domain Binary' in err_msg and ( fabs(var_val - 1) <= config.integer_tolerance or fabs(var_val) <= config.integer_tolerance): v_to.set_value(rounded_val) elif 'is not in domain Integers' in err_msg and ( fabs(var_val - rounded_val) <= config.integer_tolerance): v_to.set_value(rounded_val) # Value is zero, but shows up as slightly less than zero. elif 'is not in domain NonNegativeReals' in err_msg and ( fabs(var_val) <= config.zero_tolerance): v_to.set_value(0) else: raise
def disjunctive_bound(var, scope): """Compute the disjunctive bounds for a variable in a given scope. Args: var (_VarData): Variable for which to compute bound scope (Component): The scope in which to compute the bound. If not a _DisjunctData, it will walk up the tree and use the scope of the most immediate enclosing _DisjunctData. Returns: numeric: the tighter of either the disjunctive lower bound, the variable lower bound, or (-inf, inf) if neither exist. """ # Initialize to the global variable bound var_bnd = ( value(var.lb) if var.has_lb() else -inf, value(var.ub) if var.has_ub() else inf) possible_disjunct = scope while possible_disjunct is not None: try: disj_bnd = possible_disjunct._disj_var_bounds.get(var, (-inf, inf)) disj_bnd = ( max(var_bnd[0], disj_bnd[0]), min(var_bnd[1], disj_bnd[1])) return disj_bnd except AttributeError: # possible disjunct does not have attribute '_disj_var_bounds'. # Try again with the scope's parent block. possible_disjunct = possible_disjunct.parent_block() # Unable to find '_disj_var_bounds' attribute within search scope. return var_bnd
def unit_capacity_systemwide_constraint_rule(backend_model, tech): """ Set constraints to limit the number of purchased units of a single technology type across all locations in the model. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\sum_{loc}\\boldsymbol{units}(loc::tech) + \\boldsymbol{purchased}(loc::tech) \\begin{cases} = units_{equals, systemwide}(tech),& \\text{if } units_{equals, systemwide}(tech)\\\\ \\leq units_{max, systemwide}(tech),& \\text{if } units_{max, systemwide}(tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall tech \\in techs """ if tech in backend_model.techs_transmission_names: all_loc_techs = [ i for i in backend_model.loc_techs_transmission if i.split('::')[1].split(':')[0] == tech ] multiplier = 2 # there are always two technologies associated with one link else: all_loc_techs = [ i for i in backend_model.loc_techs if i.split('::')[1] == tech ] multiplier = 1 max_systemwide = get_param(backend_model, 'units_max_systemwide', tech) equals_systemwide = get_param(backend_model, 'units_equals_systemwide', tech) if np.isinf(po.value(max_systemwide)) and not equals_systemwide: return po.Constraint.NoConstraint elif equals_systemwide and np.isinf(po.value(equals_systemwide)): raise ValueError( 'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.format(tech) ) sum_expr_units = sum( backend_model.units[loc_tech] for loc_tech in all_loc_techs if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_milp') ) sum_expr_purchase = sum( backend_model.purchased[loc_tech] for loc_tech in all_loc_techs if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_purchase') ) if equals_systemwide: return sum_expr_units + sum_expr_purchase == equals_systemwide * multiplier else: return sum_expr_units + sum_expr_purchase <= max_systemwide * multiplier
def resource_availability_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Limit production from supply_plus techs to their available resource. .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) \\leq available\\_resource(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If :math:`force\\_resource(loc::tech)` is set: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) = available\\_resource(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps Where: .. container:: scrolling-wrapper .. math:: available\\_resource(loc::tech, timestep) = resource(loc::tech, timestep) \\times resource_{scale}(loc::tech) if :math:`loc::tech` is in :math:`loc::techs_{area}`: .. container:: scrolling-wrapper .. math:: available\\_resource(loc::tech, timestep) = resource(loc::tech, timestep) \\times resource_{scale}(loc::tech) \\times resource_{area}(loc::tech) """ resource = get_param(backend_model, 'resource', (loc_tech, timestep)) resource_scale = get_param(backend_model, 'resource_scale', loc_tech) force_resource = get_param(backend_model, 'force_resource', loc_tech) resource_unit = get_param(backend_model, 'resource_unit', loc_tech) if po.value(resource_unit) == 'energy_per_area': available_resource = resource * resource_scale * backend_model.resource_area[loc_tech] elif po.value(resource_unit) == 'energy_per_cap': available_resource = resource * resource_scale * backend_model.energy_cap[loc_tech] else: available_resource = resource * resource_scale if po.value(force_resource): return backend_model.resource_con[loc_tech, timestep] == available_resource else: return backend_model.resource_con[loc_tech, timestep] <= available_resource
def log_infeasible_constraints(m, tol=1E-6, logger=logger): """Print the infeasible constraints in the model. Uses the current model state. Uses pyomo.util.infeasible logger unless one is provided. Args: m (Block): Pyomo block or model to check tol (float): feasibility tolerance """ for constr in m.component_data_objects( ctype=Constraint, active=True, descend_into=True): # if constraint is an equality, handle differently if constr.equality and fabs(value(constr.lower - constr.body)) >= tol: logger.info('CONSTR {}: {} != {}'.format( constr.name, value(constr.body), value(constr.lower))) continue # otherwise, check LB and UB, if they exist if constr.has_lb() and value(constr.lower - constr.body) >= tol: logger.info('CONSTR {}: {} < {}'.format( constr.name, value(constr.body), value(constr.lower))) if constr.has_ub() and value(constr.body - constr.upper) >= tol: logger.info('CONSTR {}: {} > {}'.format( constr.name, value(constr.body), value(constr.upper)))
def getInitialValue(self): x = np.zeros(self.lx, dtype=float) y = np.zeros(self.ly, dtype=float) z = np.zeros(self.lz, dtype=float) for i in range(0, self.lx): x[i] = value(self.TRF.xvars[i]) for i in range(0, self.ly): #initialization of y? y[i] = 1 for i in range(0, self.lz): z[i] = value(self.TRF.zvars[i]) return x, y, z
def solve_NLP_feas(solve_data, config): m = solve_data.working_model.clone() add_feas_slacks(m) MindtPy = m.MindtPy_utils next(m.component_data_objects(Objective, active=True)).deactivate() for constr in m.component_data_objects( ctype=Constraint, active=True, descend_into=True): constr.deactivate() MindtPy.MindtPy_feas.activate() MindtPy.MindtPy_feas_obj = Objective( expr=sum(s for s in MindtPy.MindtPy_feas.slack_var[...]), sense=minimize) for v in MindtPy.variable_list: if v.is_binary(): v.fix(int(round(v.value))) # m.pprint() #print nlp feasibility problem for debugging with SuppressInfeasibleWarning(): feas_soln = SolverFactory(config.nlp_solver).solve( m, **config.nlp_solver_args) subprob_terminate_cond = feas_soln.solver.termination_condition if subprob_terminate_cond is tc.optimal: copy_var_list_values( MindtPy.variable_list, solve_data.working_model.MindtPy_utils.variable_list, config) pass elif subprob_terminate_cond is tc.infeasible: raise ValueError('Feasibility NLP infeasible. ' 'This should never happen.') else: raise ValueError( 'MindtPy unable to handle feasibility NLP termination condition ' 'of {}'.format(subprob_terminate_cond)) var_values = [v.value for v in MindtPy.variable_list] duals = [0 for _ in MindtPy.constraint_list] for i, constr in enumerate(MindtPy.constraint_list): # TODO rhs only works if constr.upper and constr.lower do not both have values. # Sometimes you might have 1 <= expr <= 1. This would give an incorrect rhs of 2. rhs = ((0 if constr.upper is None else constr.upper) + (0 if constr.lower is None else constr.lower)) sign_adjust = 1 if value(constr.upper) is None else -1 duals[i] = sign_adjust * max( 0, sign_adjust * (rhs - value(constr.body))) if value(MindtPy.MindtPy_feas_obj.expr) == 0: raise ValueError( 'Problem is not feasible, check NLP solver') return var_values, duals
def energy_capacity_systemwide_constraint_rule(backend_model, tech): """ Set constraints to limit the capacity of a single technology type across all locations in the model. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\sum_{loc}\\boldsymbol{energy_{cap}}(loc::tech) \\begin{cases} = energy_{cap, equals, systemwide}(loc::tech),& \\text{if } energy_{cap, equals, systemwide}(loc::tech)\\\\ \\leq energy_{cap, max, systemwide}(loc::tech),& \\text{if } energy_{cap, max, systemwide}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall tech \\in techs """ if tech in backend_model.techs_transmission_names: all_loc_techs = [ i for i in backend_model.loc_techs_transmission if i.split('::')[1].split(':')[0] == tech ] multiplier = 2 # there are always two technologies associated with one link else: all_loc_techs = [ i for i in backend_model.loc_techs if i.split('::')[1] == tech ] multiplier = 1 max_systemwide = get_param(backend_model, 'energy_cap_max_systemwide', tech) equals_systemwide = get_param(backend_model, 'energy_cap_equals_systemwide', tech) if np.isinf(po.value(max_systemwide)) and not equals_systemwide: return po.Constraint.NoConstraint elif equals_systemwide and np.isinf(po.value(equals_systemwide)): raise exceptions.ModelError( 'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.format(tech) ) sum_expr = sum(backend_model.energy_cap[loc_tech] for loc_tech in all_loc_techs) if equals_systemwide: return sum_expr == equals_systemwide * multiplier else: return sum_expr <= max_systemwide * multiplier
def cost_var_constraint_rule(backend_model, cost, loc_tech, timestep): """ Calculate costs from time-varying decision variables .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) = cost_{prod}(cost, loc::tech, timestep) + cost_{con}(cost, loc::tech, timestep) cost_{prod}(cost, loc::tech, timestep) = cost_{om\\_prod}(cost, loc::tech, timestep) \\times weight(timestep) \\times \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) prod\\_con\\_eff = \\begin{cases} = \\boldsymbol{resource_{con}}(loc::tech, timestep),& \\text{if } loc::tech \\in loc\\_techs\\_supply\\_plus \\\\ = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{energy_eff(loc::tech, timestep)},& \\text{if } loc::tech \\in loc\\_techs\\_supply \\\\ \\end{cases} cost_{con}(cost, loc::tech, timestep) = cost_{om\\_con}(cost, loc::tech, timestep) \\times weight(timestep) \\times prod\\_con\\_eff """ model_data_dict = backend_model.__calliope_model_data__ cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep)) cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep)) weight = backend_model.timestep_weights[timestep] loc_tech_carrier = model_data_dict['data']['lookup_loc_techs'][loc_tech] if po.value(cost_om_prod): cost_prod = cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier, timestep] else: cost_prod = 0 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply_plus') and cost_om_con: cost_con = cost_om_con * weight * backend_model.resource_con[loc_tech, timestep] elif loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply') and cost_om_con: energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value(energy_eff) > 0: # in case energy_eff is zero, to avoid an infinite value cost_con = cost_om_con * weight * (backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff) else: cost_con = 0 else: cost_con = 0 backend_model.cost_var_rhs[cost, loc_tech, timestep].expr = cost_prod + cost_con return (backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep])
def generate_gofx(self, G, tears): edge_list = self.idx_to_edge(G) gofx = [] for tear in tears: arc = G.edges[edge_list[tear]]["arc"] src = arc.src sf = arc.expanded_block.component("splitfrac") for name, mem in src.iter_vars(names=True): if src.is_extensive(name) and sf is not None: # TODO: same as above, what if there's no splitfrac gofx.append(value(mem * sf)) else: gofx.append(value(mem)) gofx = numpy.array(gofx) return gofx
def register_var(self, var, lb, ub): """Registers a new variable.""" var_idx = self.var_to_idx[var] inf = float('inf') # Guard against errant None values in lb and ub lb = -inf if lb is None else lb ub = inf if ub is None else ub lb = max(var.lb if var.has_lb() else -inf, lb) ub = min(var.ub if var.has_ub() else inf, ub) var_val = value(var, exception=False) if lb == -inf: lb = -500000 logger.warning( 'Var %s missing lower bound. Assuming LB of %s' % (var.name, lb)) if ub == inf: ub = 500000 logger.warning( 'Var %s missing upper bound. Assuming UB of %s' % (var.name, ub)) if var_val is None: var_val = (lb + ub) / 2 self.missing_value_warnings.append( 'Var %s missing value. Assuming midpoint value of %s' % (var.name, var_val)) return self.mcpp.newVar( lb, var_val, ub, self.num_vars, var_idx)
def subproblem_solve(gdp, config): subproblem = gdp.clone() TransformationFactory('gdp.bigm').apply_to(subproblem) main_obj = next(subproblem.component_data_objects(Objective, active=True)) obj_sign = 1 if main_obj.sense == minimize else -1 try: result = SolverFactory(config.solver).solve(subproblem, **config.solver_args) except RuntimeError as e: config.logger.warning( "Solver encountered RuntimeError. Treating as infeasible. " "Msg: %s\n%s" % (str(e), traceback.format_exc())) var_values = [v.value for v in subproblem.GDPbb_utils.variable_list] return obj_sign * float('inf'), SolverResults(), var_values var_values = [v.value for v in subproblem.GDPbb_utils.variable_list] term_cond = result.solver.termination_condition if result.solver.status is SolverStatus.ok and any( term_cond == valid_cond for valid_cond in (tc.optimal, tc.locallyOptimal, tc.feasible)): return value(main_obj.expr), result, var_values elif term_cond == tc.unbounded: return obj_sign * float('-inf'), result, var_values elif term_cond == tc.infeasible: return obj_sign * float('inf'), result, var_values else: config.logger.warning("Unknown termination condition of %s" % term_cond) return obj_sign * float('inf'), result, var_values
def energy_capacity_max_purchase_constraint_rule(backend_model, loc_tech): """ Set maximum energy capacity decision variable upper bound as a function of binary purchase variable The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)} \\begin{cases} = energy_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),& \\text{if } energy_{cap, equals}(loc::tech)\\\\ \\leq energy_{cap, max}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),& \\text{if } energy_{cap, max}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs_{purchase} """ energy_cap_max = get_param(backend_model, 'energy_cap_max', loc_tech) energy_cap_equals = get_param(backend_model, 'energy_cap_equals', loc_tech) energy_cap_scale = get_param(backend_model, 'energy_cap_scale', loc_tech) if po.value(energy_cap_equals): return backend_model.energy_cap[loc_tech] == ( energy_cap_equals * energy_cap_scale * backend_model.purchased[loc_tech] ) else: return backend_model.energy_cap[loc_tech] <= ( energy_cap_max * energy_cap_scale * backend_model.purchased[loc_tech] )
def pass_single_value(self, port, name, member, val, fixed): """ Fix the value of the port member and add it to the fixed set. If the member is an expression, appropriately fix the value of its free variable. Error if the member is already fixed but different from val, or if the member has more than one free variable." """ eq_tol = self.options["almost_equal_tol"] if member.is_fixed(): if abs(value(member) - val) > eq_tol: raise RuntimeError( "Member '%s' of port '%s' is already fixed but has a " "different value (by > %s) than what is being passed to it" % (name, port.name, eq_tol)) elif member.is_expression_type(): repn = generate_standard_repn(member - val) if repn.is_linear() and len(repn.linear_vars) == 1: # fix the value of the single variable fval = (0 - repn.constant) / repn.linear_coefs[0] var = repn.linear_vars[0] fixed.add(var) var.fix(fval) else: raise RuntimeError( "Member '%s' of port '%s' had more than " "one free variable when trying to pass a value " "to it. Please fix more variables before passing " "to this port." % (name, port.name)) else: fixed.add(member) member.fix(val)
def _collect_bilinear(self, expr, bilin, quad): if not expr.is_expression(): return if type(expr) is _ProductExpression: if len(expr._numerator) != 2: for e in expr._numerator: self._collect_bilinear(e, bilin, quad) # No need to check denominator, as this is poly_degree==2 return if not isinstance(expr._numerator[0], _VarData) or \ not isinstance(expr._numerator[1], _VarData): raise RuntimeError("Cannot yet handle complex subexpressions") if expr._numerator[0] is expr._numerator[1]: quad.append( (expr, expr._numerator[0]) ) else: bilin.append( (expr, expr._numerator[0], expr._numerator[1]) ) return if type(expr) is _PowExpression and value(expr._args[1]) == 2: # Note: directly testing the value of the exponent above is # safe: we have already verified that this expression is # polynominal, so the exponent must be constant. tmp = _ProductExpression() tmp._numerator = [ expr._args[0], expr._args[0] ] tmp._denominator = [] expr._args = (tmp, as_numeric(1)) #quad.append( (tmp, tmp._args[0]) ) self._collect_bilinear(tmp, bilin, quad) return # All other expression types for e in expr._args: self._collect_bilinear(e, bilin, quad)
def cost_var_conversion_constraint_rule(backend_model, cost, loc_tech, timestep): """ Add time-varying conversion technology costs .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) = \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep) + \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep) \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion} """ model_data_dict = backend_model.__calliope_model_data__ weight = backend_model.timestep_weights[timestep] loc_tech_carrier_in = ( model_data_dict['data']['lookup_loc_techs_conversion'][('in', loc_tech)] ) loc_tech_carrier_out = ( model_data_dict['data']['lookup_loc_techs_conversion'][('out', loc_tech)] ) cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep)) cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep)) if po.value(cost_om_prod): cost_prod = (cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier_out, timestep]) else: cost_prod = 0 if po.value(cost_om_con): cost_con = (cost_om_con * weight * -1 * backend_model.carrier_con[loc_tech_carrier_in, timestep]) else: cost_con = 0 backend_model.cost_var_rhs[cost, loc_tech, timestep] = cost_prod + cost_con return (backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep])
def log_close_to_bounds(m, tol=1E-6, logger=logger): """Print the variables and constraints that are near their bounds. Fixed variables and equality constraints are excluded from this analysis. Args: m (Block): Pyomo block or model to check tol (float): bound tolerance """ for var in m.component_data_objects( ctype=Var, descend_into=True): if var.fixed: continue if (var.has_lb() and var.has_ub() and fabs(value(var.ub - var.lb)) <= 2 * tol): continue # if the bounds are too close, skip. if var.has_lb() and fabs(value(var.lb - var)) <= tol: logger.info('{} near LB of {}'.format(var.name, value(var.lb))) elif var.has_ub() and fabs(value(var.ub - var)) <= tol: logger.info('{} near UB of {}'.format(var.name, value(var.ub))) for constr in m.component_data_objects( ctype=Constraint, descend_into=True, active=True): if not constr.equality: if (constr.has_ub() and fabs(value(constr.body - constr.upper)) <= tol): logger.info('{} near UB'.format(constr.name)) if (constr.has_lb() and fabs(value(constr.body - constr.lower)) <= tol): logger.info('{} near LB'.format(constr.name))
def combine_and_fix(self, port, name, obj, evars, fixed): """ For an extensive port member, combine the values of all expanded variables and fix the port member at their sum. Assumes that all expanded variables are fixed. """ assert all(evar.is_fixed() for evar in evars) total = sum(value(evar) for evar in evars) self.pass_single_value(port, name, obj, total, fixed)
def generate_first_x(self, G, tears): edge_list = self.idx_to_edge(G) x = [] for tear in tears: arc = G.edges[edge_list[tear]]["arc"] for name, index, mem in arc.src.iter_vars(names=True): peer = self.source_dest_peer(arc, name, index) x.append(value(peer)) x = numpy.array(x) return x
def balance_storage_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and consumption of storage technologies, alongside any use of the stored volume. .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)} - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] model_attrs = backend_model.__calliope_model_data__['attrs'] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value(energy_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff carrier_con = backend_model.carrier_con[loc_tech_carrier, timestep] * energy_eff current_timestep = backend_model.timesteps.order_dict[timestep] if current_timestep == 0 and not model_attrs['run.cyclic_storage']: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) elif (hasattr(backend_model, 'storage_inter_cluster') and model_data_dict['lookup_cluster_first_timestep'][timestep]): storage_previous_step = 0 else: if (hasattr(backend_model, 'clusters') and model_data_dict['lookup_cluster_first_timestep'][timestep]): previous_step = model_data_dict['lookup_cluster_last_timestep'][timestep] elif current_timestep == 0 and model_attrs['run.cyclic_storage']: previous_step = backend_model.timesteps[-1] else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( ((1 - storage_loss) ** time_resolution) * backend_model.storage[loc_tech, previous_step] ) return ( backend_model.storage[loc_tech, timestep] == storage_previous_step - carrier_prod - carrier_con )
def add_objective_linearization(solve_data, config): """Adds initial linearized objective in case it is nonlinear. This should be done for initializing the ECP method. """ m = solve_data.working_model MindtPy = m.MindtPy_utils solve_data.mip_iter += 1 gen = (obj for obj in MindtPy.jacs if obj is MindtPy.MindtPy_objective_expr) MindtPy.MindtPy_linear_cuts.mip_iters.add(solve_data.mip_iter) sign_adjust = 1 if MindtPy.obj.sense == minimize else -1 # generate new constraints # TODO some kind of special handling if the dual is phenomenally small? for obj in gen: c = MindtPy.MindtPy_linear_cuts.ecp_cuts.add(expr=sign_adjust * sum( value(MindtPy.jacs[obj][id(var)]) * (var - value(var)) for var in list(EXPR.identify_variables(obj.body))) + value(obj.body) <= 0) MindtPy.ECP_constr_map[obj, solve_data.mip_iter] = c
def solve_bounding_problem(model, solver): results = SolverFactory(solver).solve(model) if results.solver.termination_condition is tc.optimal: return value(model._var_bounding_obj.expr) elif results.solver.termination_condition is tc.infeasible: return None elif results.solver.termination_condition is tc.unbounded: return -inf else: raise NotImplementedError( "Unhandled termination condition: %s" % results.solver.termination_condition)
def copy_var_list_values(from_list, to_list, config, skip_stale=False, skip_fixed=True, ignore_integrality=False): """Copy variable values from one list to another. Rounds to Binary/Integer if necessary Sets to zero for NonNegativeReals if necessary """ for v_from, v_to in zip(from_list, to_list): if skip_stale and v_from.stale: continue # Skip stale variable values. if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. try: # We don't want to trigger the reset of the global stale # indicator, so we will set this variable to be "stale", # knowing that set_value will switch it back to "not # stale" v_to.stale = True # NOTE: PEP 2180 changes the var behavior so that domain / # bounds violations no longer generate exceptions (and # instead log warnings). This means that the following will # always succeed and the ValueError should never be raised. v_to.set_value(value(v_from, exception=False), skip_validation=True) except ValueError as err: err_msg = getattr(err, 'message', str(err)) var_val = value(v_from) rounded_val = int(round(var_val)) # Check to see if this is just a tolerance issue if ignore_integrality and v_to.is_integer(): v_to.set_value(var_val, skip_validation=True) elif v_to.is_integer() and (fabs(var_val - rounded_val) <= config.integer_tolerance): v_to.set_value(rounded_val, skip_validation=True) elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: v_to.set_value(0, skip_validation=True) else: config.logger.error( 'Unknown validation domain error setting variable %s', (v_to.name,)) raise
def handle_master_mip_optimal(master_mip, solve_data, config): """ This function copies the result from 'solve_OA_master' to the working model and updates the upper/lower bound. This function is called after an optimal solution is found for the master problem. Parameters ---------- master_mip: Pyomo model the MIP master problem solve_data: MindtPy Data Container data container that holds solve-instance data config: ConfigBlock contains the specific configurations for the algorithm """ # proceed. Just need integer values MindtPy = master_mip.MindtPy_utils main_objective = next( master_mip.component_data_objects(Objective, active=True)) # check if the value of binary variable is valid for var in MindtPy.variable_list: if var.value is None and var.is_integer(): config.logger.warning( "Integer variable {} not initialized. It is set to it's lower bound" .format(var.name)) var.value = var.lb # nlp_var.bounds[0] # warm start for the nlp subproblem copy_var_list_values(master_mip.MindtPy_utils.variable_list, solve_data.working_model.MindtPy_utils.variable_list, config) if main_objective.sense == minimize: solve_data.LB = max(value(MindtPy.MindtPy_oa_obj.expr), solve_data.LB) solve_data.LB_progress.append(solve_data.LB) else: solve_data.UB = min(value(MindtPy.MindtPy_oa_obj.expr), solve_data.UB) solve_data.UB_progress.append(solve_data.UB) config.logger.info( 'MIP %s: OBJ: %s LB: %s UB: %s' % (solve_data.mip_iter, value( MindtPy.MindtPy_oa_obj.expr), solve_data.LB, solve_data.UB))
def get_capacity_constraint(backend_model, parameter, loc_tech, _equals=None, _max=None, _min=None, scale=None): decision_variable = getattr(backend_model, parameter) if not _equals: _equals = get_param(backend_model, parameter + '_equals', loc_tech) if not _max: _max = get_param(backend_model, parameter + '_max', loc_tech) if not _min: _min = get_param(backend_model, parameter + '_min', loc_tech) if po.value(_equals) is not False and po.value(_equals) is not None: if np.isinf(po.value(_equals)): e = exceptions.ModelError raise e('Cannot use inf for {}_equals for loc:tech `{}`'.format( parameter, loc_tech)) if scale: _equals *= scale return decision_variable[loc_tech] == _equals else: if np.isinf(po.value(_max)): _max = None # to disable upper bound if po.value(_min) == 0 and po.value(_max) is None: return po.Constraint.NoConstraint else: if scale: _max *= scale _min *= scale return (_min, decision_variable[loc_tech], _max)
def _estimate_M(self, expr, name): # Calculate a best guess at M repn = generate_standard_repn(expr, quadratic=False) M = [0, 0] if not repn.is_nonlinear(): if repn.constant is not None: for i in (0, 1): if M[i] is not None: M[i] += repn.constant for i, coef in enumerate(repn.linear_coefs or []): var = repn.linear_vars[i] bounds = (value(var.lb), value(var.ub)) for i in (0, 1): # reverse the bounds if the coefficient is negative if coef > 0: j = i else: j = 1 - i if bounds[i] is not None: M[j] += value(bounds[i]) * coef else: raise GDP_Error( "Cannot estimate M for " "expressions with unbounded variables." "\n\t(found unbounded var %s while processing " "constraint %s)" % (var.name, name)) else: # expression is nonlinear. Try using `contrib.fbbt` to estimate. expr_lb, expr_ub = compute_bounds_on_expr(expr) if expr_lb is None or expr_ub is None: raise GDP_Error("Cannot estimate M for unbounded nonlinear " "expressions.\n\t(found while processing " "constraint %s)" % name) else: M = (expr_lb, expr_ub) return tuple(M)
def copy_var_list_values(from_list, to_list, config, skip_stale=False, skip_fixed=True, ignore_integrality=False): """Copy variable values from one list to another. Rounds to Binary/Integer if neccessary Sets to zero for NonNegativeReals if neccessary """ for v_from, v_to in zip(from_list, to_list): if skip_stale and v_from.stale: continue # Skip stale variable values. if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. try: v_to.set_value(value(v_from, exception=False)) if skip_stale: v_to.stale = False except ValueError as err: err_msg = getattr(err, 'message', str(err)) var_val = value(v_from) rounded_val = int(round(var_val)) # Check to see if this is just a tolerance issue if ignore_integrality \ and ('is not in domain Binary' in err_msg or 'is not in domain Integers' in err_msg): v_to.value = value(v_from, exception=False) elif 'is not in domain Binary' in err_msg and ( fabs(var_val - 1) <= config.integer_tolerance or fabs(var_val) <= config.integer_tolerance): v_to.set_value(rounded_val) # TODO What about PositiveIntegers etc? elif 'is not in domain Integers' in err_msg and ( fabs(var_val - rounded_val) <= config.integer_tolerance): v_to.set_value(rounded_val) # Value is zero, but shows up as slightly less than zero. elif 'is not in domain NonNegativeReals' in err_msg and ( fabs(var_val) <= config.zero_tolerance): v_to.set_value(0) else: raise
def _add_vars(self, var_seq): if not var_seq: return var_num = self._solver_model.getnumvar() vnames = tuple( self._symbol_map.getSymbol(v, self._labeler) for v in var_seq) vtypes = tuple(map(self._mosek_vartype_from_var, var_seq)) lbs = tuple( value(v) if v.fixed else -inf if value(v.lb) is None else value(v. lb) for v in var_seq) ubs = tuple( value(v) if v.fixed else inf if value(v.ub) is None else value(v.ub ) for v in var_seq) fxs = tuple(v.is_fixed() for v in var_seq) bound_types = tuple(map(self._mosek_bounds, lbs, ubs, fxs)) self._solver_model.appendvars(len(var_seq)) var_ids = range(var_num, var_num + len(var_seq)) _vnames = tuple(map(self._solver_model.putvarname, var_ids, vnames)) self._solver_model.putvartypelist(var_ids, vtypes) self._solver_model.putvarboundlist(var_ids, bound_types, lbs, ubs) self._pyomo_var_to_solver_var_map.update(zip(var_seq, var_ids)) self._solver_var_to_pyomo_var_map.update(zip(var_ids, var_seq)) self._referenced_variables.update(zip(var_seq, [0] * len(var_seq)))
def update_vars(self, *solver_vars): """ Update multiple scalar variables in solver model. This method allows fixing/unfixing, changing variable types and bounds. Parameters ---------- *solver_var: Constraint (scalar Constraint or single _ConstraintData) """ try: var_ids = [] for v in solver_vars: var_ids.append(self._pyomo_var_to_solver_var_map[v]) vtypes = tuple(map(self._mosek_vartype_from_var, solver_vars)) lbs = tuple( value(v) if v.fixed else -float('inf') if value(v.lb) is None else value(v.lb) for v in solver_vars) ubs = tuple( value(v) if v. fixed else float('inf') if value(v.ub) is None else value(v.ub) for v in solver_vars) fxs = tuple(v.is_fixed() for v in solver_vars) bound_types = tuple(map(self._mosek_bounds, lbs, ubs, fxs)) self._solver_model.putvartypelist(var_ids, vtypes) self._solver_model.putvarboundlist(var_ids, bound_types, lbs, ubs) except KeyError: print(v.name) v_name = self._symbol_map.getSymbol(v, self._labeler) raise ValueError( "Variable {} needs to be added before it can be modified.". format(v_name))
def _add_node(u, stage, succ, pred): if node_name_attribute is not None: if node_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing name attribute: '%s'" % (u, node_name_attribute)) node_name = tree.node[u][node_name_attribute] else: node_name = u m.NodeStage[node_name] = m.Stages[stage] if u == root: m.ConditionalProbability[node_name] = 1.0 else: assert u in pred # prior to networkx ~2.0, we used a .edge attribute on DiGraph, # which no longer exists. if hasattr(tree, 'edge'): edge = tree.edge[pred[u]][u] else: edge = tree.edges[pred[u],u] probability = None if edge_probability_attribute is not None: if edge_probability_attribute not in edge: raise KeyError( "edge '(%s, %s)' missing probability attribute: '%s'" % (pred[u], u, edge_probability_attribute)) probability = edge[edge_probability_attribute] else: probability = 1.0/len(succ[pred[u]]) m.ConditionalProbability[node_name] = probability if u in succ: child_names = [] for v in succ[u]: child_names.append( _add_node(v, stage+1, succ, pred)) total_probability = 0.0 for child_name in child_names: m.Children[node_name].add(child_name) total_probability += \ value(m.ConditionalProbability[child_name]) if abs(total_probability - 1.0) > 1e-5: raise ValueError( "edge probabilities leaving node '%s' " "do not sum to 1 (total=%r)" % (u, total_probability)) else: # a leaf node scenario_name = node_to_scenario[u] m.ScenarioLeafNode[scenario_name] = node_name m.Children[node_name].clear() return node_name
def exitNode(self, node, values): node = super().exitNode(node, values) if node.__class__ is not EXPR.ExternalFunctionExpression: return node if id(node._fcn) not in self.efSet: return node # At this point we know this is an ExternalFunctionExpression # node that we want to replace with an auliliary variable (y) new_args = [] seen = ComponentSet() # TODO: support more than PythonCallbackFunctions assert isinstance(node._fcn, PythonCallbackFunction) # # Note: the first argument to PythonCallbackFunction is the # function ID. Since we are going to complain about constant # parameters, we need to skip the first argument when processing # the argument list. This is really not good: we should allow # for constant arguments to the functions, and we should relax # the restriction that the external functions implement the # PythonCallbackFunction API (that restriction leads unfortunate # things later; i.e., accessing the private _fcn attribute # below). for arg in values[1][1:]: if type(arg) in nonpyomo_leaf_types or arg.is_fixed(): # We currently do not allow constants or parameters for # the external functions. raise RuntimeError( "TrustRegion does not support black boxes with " "constant or parameter inputs\n\tExpression: %s" % (node, )) if arg.is_expression_type(): # All expressions (including simple linear expressions) # are replaced with a single auxiliary variable (and # eventually an additional constraint equating the # auxiliary variable to the original expression) _x = self.trf.x.add() _x.set_value(value(arg)) self.trf.conset.add(_x == arg) new_args.append(_x) else: # The only thing left is bare variables: check for duplicates. if arg in seen: raise RuntimeError( "TrustRegion does not support black boxes with " "duplicate input arguments\n\tExpression: %s" % (node, )) seen.add(arg) new_args.append(arg) _y = self.trf.y.add() self.trf.external_fcns.append(node) self.trf.exfn_xvars.append(new_args) return _y
def handle_lazy_subproblem_infeasible(self, fixed_nlp, solve_data, config, opt): """Solves feasibility NLP subproblem and adds cuts according to the specified strategy. Parameters ---------- fixed_nlp : Pyomo model Integer-variable-fixed NLP model. solve_data : MindtPySolveData Data container that holds solve-instance data. config : ConfigBlock The specific configurations for MindtPy. opt : SolverFactory The cplex_persistent solver. """ # TODO try something else? Reinitialize with different initial # value? config.logger.info('NLP subproblem was locally infeasible.') solve_data.nlp_infeasible_counter += 1 if config.calculate_dual: for c in fixed_nlp.MindtPy_utils.constraint_list: rhs = ((0 if c.upper is None else c.upper) + (0 if c.lower is None else c.lower)) sign_adjust = 1 if c.upper is None else -1 fixed_nlp.dual[c] = (sign_adjust * max(0, sign_adjust * (rhs - value(c.body)))) dual_values = list( fixed_nlp.dual[c] for c in fixed_nlp.MindtPy_utils.constraint_list) else: dual_values = None config.logger.info('Solving feasibility problem') feas_subproblem, feas_subproblem_results = solve_feasibility_subproblem( solve_data, config) # In OA algorithm, OA cuts are generated based on the solution of the subproblem # We need to first copy the value of variables from the subproblem and then add cuts copy_var_list_values(feas_subproblem.MindtPy_utils.variable_list, solve_data.mip.MindtPy_utils.variable_list, config) if config.strategy == 'OA': self.add_lazy_oa_cuts(solve_data.mip, dual_values, solve_data, config, opt) if config.add_regularization is not None: add_oa_cuts(solve_data.mip, dual_values, solve_data, config) elif config.strategy == 'GOA': self.add_lazy_affine_cuts(solve_data, config, opt) if config.add_no_good_cuts: var_values = list(v.value for v in fixed_nlp.MindtPy_utils.variable_list) self.add_lazy_no_good_cuts(var_values, solve_data, config, opt)
def test_integer_arithmetic_non1_coefficients(self): m = ConcreteModel() m.x = Var(bounds=(0,9)) m.y = Var(bounds=(-5, 5)) m.c1 = Constraint(expr=4*m.x + m.y >= 4) m.c2 = Constraint(expr=m.y >= 2*m.x) fme = TransformationFactory('contrib.fourier_motzkin_elimination') fme.apply_to( m, vars_to_eliminate=m.x, constraint_filtering_callback=None, do_integer_arithmetic=True, verbose=True) constraints = m._pyomo_contrib_fme_transformation.projected_constraints self.assertEqual(len(constraints), 3) cons = constraints[3] self.assertEqual(value(cons.lower), -32) self.assertIs(cons.body, m.y) self.assertIsNone(cons.upper) cons = constraints[2] self.assertEqual(value(cons.lower), 0) self.assertIsNone(cons.upper) repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_coefs), 1) self.assertIs(repn.linear_vars[0], m.y) self.assertEqual(repn.linear_coefs[0], 2) cons = constraints[1] self.assertEqual(value(cons.lower), 4) self.assertIsNone(cons.upper) repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_coefs), 1) self.assertIs(repn.linear_vars[0], m.y) self.assertEqual(repn.linear_coefs[0], 3)
def solve_GLOA_master(solve_data, config): """Solve the rigorous outer approximation master problem.""" m = solve_data.linear_GDP.clone() GDPopt = m.GDPopt_utils solve_data.mip_iteration += 1 mip_results = solve_linear_GDP(m, solve_data, config) if mip_results: if GDPopt.objective.sense == minimize: solve_data.LB = max(value(GDPopt.objective.expr), solve_data.LB) else: solve_data.UB = min(value(GDPopt.objective.expr), solve_data.UB) solve_data.iteration_log[(solve_data.master_iteration, solve_data.mip_iteration, solve_data.nlp_iteration)] = ( value(GDPopt.objective.expr), value(GDPopt.objective.expr), mip_results[1] # mip_var_values ) config.logger.info( 'ITER %s.%s.%s-MIP: OBJ: %s LB: %s UB: %s' % (solve_data.master_iteration, solve_data.mip_iteration, solve_data.nlp_iteration, value( GDPopt.objective.expr), solve_data.LB, solve_data.UB)) else: # Master problem was infeasible. if solve_data.master_iteration == 1: config.logger.warning( 'GDPopt initialization may have generated poor ' 'quality cuts.') # set optimistic bound to infinity if GDPopt.objective.sense == minimize: solve_data.LB = float('inf') else: solve_data.UB = float('-inf') # Call the MILP post-solve callback config.master_postsolve(m, solve_data) return mip_results
def handle_lazy_NLP_subproblem_optimal(self, fixed_nlp, solve_data, config, opt): """Copies result to mip(explaination see below), updates bound, adds OA and integer cut, stores best solution if new one is best""" for c in fixed_nlp.tmp_duals: if fixed_nlp.dual.get(c, None) is None: fixed_nlp.dual[c] = fixed_nlp.tmp_duals[c] dual_values = list(fixed_nlp.dual[c] for c in fixed_nlp.MindtPy_utils.constraint_list) main_objective = next( fixed_nlp.component_data_objects(Objective, active=True)) if main_objective.sense == minimize: solve_data.UB = min(value(main_objective.expr), solve_data.UB) solve_data.solution_improved = solve_data.UB < solve_data.UB_progress[ -1] solve_data.UB_progress.append(solve_data.UB) else: solve_data.LB = max(value(main_objective.expr), solve_data.LB) solve_data.solution_improved = solve_data.LB > solve_data.LB_progress[ -1] solve_data.LB_progress.append(solve_data.LB) config.logger.info('NLP {}: OBJ: {} LB: {} UB: {}'.format( solve_data.nlp_iter, value(main_objective.expr), solve_data.LB, solve_data.UB)) if solve_data.solution_improved: solve_data.best_solution_found = fixed_nlp.clone() if config.strategy == 'OA': # In OA algorithm, OA cuts are generated based on the solution of the subproblem # We need to first copy the value of variables from the subproblem and then add cuts # since value(constr.body), value(jacs[constr][var]), value(var) are used in self.add_lazy_oa_cuts() copy_var_list_values(fixed_nlp.MindtPy_utils.variable_list, solve_data.mip.MindtPy_utils.variable_list, config) self.add_lazy_oa_cuts(solve_data.mip, dual_values, solve_data, config, opt)
def updateSurrogateModel(self): """ The parameters needed for the surrogate model are the values of: b(w_k) : basis_model_output d(w_k) : truth_model_output grad b(w_k) : grad_basis_model_output grad d(w_k) : grad_truth_model_output """ b = self.data for i, y in b.ef_outputs.items(): b.basis_model_output[i] = value(b.basis_expressions[y]) b.truth_model_output[i] = value(b.truth_models[y]) # Basis functions are Pyomo expressions (in theory) gradBasis = differentiate(b.basis_expressions[y], wrt_list=b.ef_inputs[i]) # These, however, are external functions gradTruth = differentiate(b.truth_models[y], wrt_list=b.ef_inputs[i]) for j, w in enumerate(b.ef_inputs[i]): b.grad_basis_model_output[i, j] = gradBasis[j] b.grad_truth_model_output[i, j] = gradTruth[j] b.value_of_ef_inputs[i, j] = value(w)
def init_rNLP(solve_data, config): """Initialize by solving the rNLP (relaxed binary variables).""" solve_data.nlp_iter += 1 m = solve_data.working_model.clone() config.logger.info("NLP %s: Solve relaxed integrality" % (solve_data.nlp_iter, )) MindtPy = m.MindtPy_utils TransformationFactory('core.relax_integrality').apply_to(m) with SuppressInfeasibleWarning(): results = SolverFactory(config.nlp_solver).solve( m, **config.nlp_solver_args) subprob_terminate_cond = results.solver.termination_condition if subprob_terminate_cond is tc.optimal: main_objective = next(m.component_data_objects(Objective, active=True)) nlp_solution_values = list(v.value for v in MindtPy.variable_list) dual_values = list(m.dual[c] for c in MindtPy.constraint_list) # Add OA cut if main_objective.sense == minimize: solve_data.LB = value(main_objective.expr) else: solve_data.UB = value(main_objective.expr) config.logger.info('NLP %s: OBJ: %s LB: %s UB: %s' % (solve_data.nlp_iter, value(main_objective.expr), solve_data.LB, solve_data.UB)) if config.strategy == 'OA': copy_var_list_values(m.MindtPy_utils.variable_list, solve_data.mip.MindtPy_utils.variable_list, config, ignore_integrality=True) add_oa_cuts(solve_data.mip, dual_values, solve_data, config) elif subprob_terminate_cond is tc.infeasible: # TODO fail? try something else? config.logger.info('Initial relaxed NLP problem is infeasible. ' 'Problem may be infeasible.') else: raise ValueError( 'MindtPy unable to handle relaxed NLP termination condition ' 'of %s. Solver message: %s' % (subprob_terminate_cond, results.solver.message))
def tear_diff_direct(self, G, tears): """ Returns numpy arrays of values for src and dest members for all edges in the tears list of edge indexes. """ svals = [] dvals = [] edge_list = self.idx_to_edge(G) for tear in tears: arc = G.edges[edge_list[tear]]["arc"] src, dest = arc.src, arc.dest sf = arc.expanded_block.component("splitfrac") for name, index, mem in src.iter_vars(names=True): if src.is_extensive(name) and sf is not None: # TODO: same as above, what if there's no splitfrac svals.append(value(mem * sf)) else: svals.append(value(mem)) dvals.append(value(self.source_dest_peer(arc, name, index))) svals = numpy.array(svals) dvals = numpy.array(dvals) return svals, dvals
def test_unbalanced(self): G = networkx.DiGraph() G.add_node("R") G.add_node("0") G.add_node("1") G.add_edge("R", "0") G.add_edge("R", "1") G.add_node("00") G.add_node("01") G.add_edge("0", "00") G.add_edge("0", "01") model = ScenarioTreeModelFromNetworkX(G, edge_probability_attribute=None) self.assertEqual(sorted(list(model.Stages)), sorted(["Stage1", "Stage2", "Stage3"])) self.assertEqual(sorted(list(model.Nodes)), sorted(["R", "0", "1", "00", "01"])) self.assertEqual(sorted(list(model.Children["R"])), sorted(["0", "1"])) self.assertEqual(sorted(list(model.Children["0"])), sorted(["00", "01"])) self.assertEqual(sorted(list(model.Children["1"])), sorted([])) self.assertEqual(sorted(list(model.Children["00"])), sorted([])) self.assertEqual(sorted(list(model.Children["01"])), sorted([])) self.assertEqual(sorted(list(model.Scenarios)), sorted(["00", "01", "1"])) self.assertEqual(value(model.ConditionalProbability["R"]), 1.0) self.assertEqual(value(model.ConditionalProbability["0"]), 0.5) self.assertEqual(value(model.ConditionalProbability["1"]), 0.5) self.assertEqual(value(model.ConditionalProbability["00"]), 0.5) self.assertEqual(value(model.ConditionalProbability["01"]), 0.5) model.StageCost["Stage1"] = "c1" model.StageCost["Stage2"] = "c2" model.StageCost["Stage3"] = "c3" model.StageVariables["Stage1"].add("x") model.StageVariables["Stage2"].add("x") self.assertEqual(model.Bundling.value, False) self.assertEqual(list(model.Bundles), []) self.assertEqual(len(model.BundleScenarios), 0) ScenarioTree(scenariotreeinstance=model)
def update_subproblem_progress_indicators(solved_model, solve_data, config): """Update the progress indicators for the subproblem.""" GDPopt = solved_model.GDPopt_utils objective = next(solved_model.component_data_objects(Objective, active=True)) if objective.sense == minimize: old_UB = solve_data.UB solve_data.UB = min(value(objective.expr), solve_data.UB) solve_data.feasible_solution_improved = (solve_data.UB < old_UB) else: old_LB = solve_data.LB solve_data.LB = max(value(objective.expr), solve_data.LB) solve_data.feasible_solution_improved = (solve_data.LB > old_LB) solve_data.iteration_log[ (solve_data.master_iteration, solve_data.mip_iteration, solve_data.nlp_iteration) ] = ( value(objective.expr), value(objective.expr), [v.value for v in GDPopt.variable_list] ) if solve_data.feasible_solution_improved: solve_data.best_solution_found = solved_model.clone() improvement_tag = ( "(IMPROVED) " if solve_data.feasible_solution_improved else "") lb_improved, ub_improved = ( ("", improvement_tag) if objective.sense == minimize else (improvement_tag, "")) config.logger.info( 'ITER {:d}.{:d}.{:d}-NLP: OBJ: {:.10g} LB: {:.10g} {:s} UB: {:.10g} {:s}'.format( solve_data.master_iteration, solve_data.mip_iteration, solve_data.nlp_iteration, value(objective.expr), solve_data.LB, lb_improved, solve_data.UB, ub_improved))
def back_off_constraint_with_calculated_cut_violation(cut, transBlock_rHull, bigm_to_hull_map, opt, stream_solver, TOL): """Calculates the maximum violation of cut subject to the relaxed hull constraints. Increases this violation by TOL (to account for optimality tolerance in solving the problem), and, if it finds that cut can be violated up to this tolerance, makes it more conservative such that it no longer can. Parameters ---------- cut: The cut to be made more conservative, a Constraint transBlock_rHull: the relaxed hull model's transformation Block bigm_to_hull_map: Dictionary mapping ids of bigM variables to the corresponding variables on the relaxed hull instance opt: SolverFactory object for solving the maximum violation problem stream_solver: Whether or not to set tee=True while solving the maximum violation problem. TOL: An absolute tolerance to be added to the calculated cut violation, to account for optimality tolerance in the maximum violation problem solve. """ instance_rHull = transBlock_rHull.model() logger.info("Post-processing cut: %s" % cut.expr) # Take a constraint. We will solve a problem maximizing its violation # subject to rHull. We will add some user-specified tolerance to that # violation, and then add that much padding to it if it can be violated. transBlock_rHull.separation_objective.deactivate() transBlock_rHull.infeasibility_objective = Objective( expr=clone_without_expression_components(cut.body, substitute=bigm_to_hull_map)) results = opt.solve(instance_rHull, tee=stream_solver) if verify_successful_solve(results) is not NORMAL: logger.warning("Problem to determine how much to " "back off the new cut " "did not solve normally. Leaving the constraint as is, " "which could lead to numerical trouble%s" % (results,)) # restore the objective transBlock_rHull.del_component(transBlock_rHull.infeasibility_objective) transBlock_rHull.separation_objective.activate() return # we're minimizing, val is <= 0 val = value(transBlock_rHull.infeasibility_objective) - TOL if val <= 0: logger.info("\tBacking off cut by %s" % val) cut._body += abs(val) # else there is nothing to do: restore the objective transBlock_rHull.del_component(transBlock_rHull.infeasibility_objective) transBlock_rHull.separation_objective.activate()
def visit(self, node, values): if node.__class__ is not EXPR.ExternalFunctionExpression: return node if id(node._fcn) not in self.efSet: return node # At this point we know this is an ExternalFunctionExpression # node that we want to replace with an auliliary variable (y) new_args = [] seen = ComponentSet() # TODO: support more than PythonCallbackFunctions assert isinstance(node._fcn, PythonCallbackFunction) # # Note: the first argument to PythonCallbackFunction is the # function ID. Since we are going to complain about constant # parameters, we need to skip the first argument when processing # the argument list. This is really not good: we should allow # for constant arguments to the functions, and we should relax # the restriction that the external functions implement the # PythonCallbackFunction API (that restriction leads unfortunate # things later; i.e., accessing the private _fcn attribute # below). for arg in list(values)[1:]: if type(arg) in nonpyomo_leaf_types or arg.is_fixed(): # We currently do not allow constants or parameters for # the external functions. raise RuntimeError( "TrustRegion does not support black boxes with " "constant or parameter inputs\n\tExpression: %s" % (node,) ) if arg.is_expression_type(): # All expressions (including simple linear expressions) # are replaced with a single auxiliary variable (and # eventually an additional constraint equating the # auxiliary variable to the original expression) _x = self.trf.x.add() _x.set_value( value(arg) ) self.trf.conset.add(_x == arg) new_args.append(_x) else: # The only thing left is bare variables: check for duplicates. if arg in seen: raise RuntimeError( "TrustRegion does not support black boxes with " "duplicate input arguments\n\tExpression: %s" % (node,) ) seen.add(arg) new_args.append(arg) _y = self.trf.y.add() self.trf.external_fcns.append(node) self.trf.exfn_xvars.append(new_args) return _y
def resource_area_constraint_rule(backend_model, loc_tech): """ Set upper and lower bounds for resource_area. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{area}}(loc::tech) \\begin{cases} = resource_{area, equals}(loc::tech),& \\text{if } resource_{area, equals}(loc::tech)\\\\ \\leq resource_{area, max}(loc::tech),& \\text{if } resource_{area, max}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs_{area} and (if ``equals`` not enforced): .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{area}}(loc::tech) \\geq resource_{area, min}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{area} """ energy_cap_max = get_param(backend_model, 'energy_cap_max', loc_tech) area_per_energy_cap = get_param(backend_model, 'resource_area_per_energy_cap', loc_tech) if po.value(energy_cap_max) == 0 and not po.value(area_per_energy_cap): # If a technology has no energy_cap here, we force resource_area to zero, # so as not to accrue spurious costs return backend_model.resource_area[loc_tech] == 0 else: return get_capacity_constraint(backend_model, 'resource_area', loc_tech)
def handle_main_optimal(main_mip, solve_data, config, update_bound=True): """This function copies the results from 'solve_main' to the working model and updates the upper/lower bound. This function is called after an optimal solution is found for the main problem. Parameters ---------- main_mip : Pyomo model The MIP main problem. solve_data : MindtPySolveData Data container that holds solve-instance data. config : ConfigBlock The specific configurations for MindtPy. update_bound : bool, optional Whether to update the bound, by default True. Bound will not be updated when handling regularization problem. """ # proceed. Just need integer values MindtPy = main_mip.MindtPy_utils # check if the value of binary variable is valid for var in MindtPy.discrete_variable_list: if var.value is None: config.logger.warning( f"Integer variable {var.name} not initialized. " "Setting it to its lower bound") var.set_value(var.lb, skip_validation=True) # nlp_var.bounds[0] # warm start for the nlp subproblem copy_var_list_values(main_mip.MindtPy_utils.variable_list, solve_data.working_model.MindtPy_utils.variable_list, config) if update_bound: update_dual_bound(solve_data, value(MindtPy.mip_obj.expr)) config.logger.info( solve_data.log_formatter.format( solve_data.mip_iter, 'MILP', value(MindtPy.mip_obj.expr), solve_data.LB, solve_data.UB, solve_data.rel_gap, get_main_elapsed_time(solve_data.timing)))
def beforeChild(self, node, child): if type(child) in nonpyomo_leaf_types: # This means the child is POD # i.e., int, float, string return False, str(child) elif child.is_variable_type(): return False, str(self.variable_label_map.getSymbol(child)) elif child.is_parameter_type(): return False, str(value(child)) elif not child.is_expression_type(): return False, str(child) else: # this is an expression node return True, ""
def beforeChild(self, node, child, child_idx): if type(child) in nonpyomo_leaf_types: # This means the child is POD # i.e., int, float, string return False, str(child) elif child.is_expression_type(): return True, "" elif child.is_numeric_type(): if child.is_fixed(): return False, str(value(child)) else: return False, str(self.variable_label_map.getSymbol(child)) else: return False, str(child)
def copy_var_list_values(from_list, to_list, config, skip_stale=False): """Copy variable values from one list to another.""" for v_from, v_to in zip(from_list, to_list): if skip_stale and v_from.stale: continue # Skip stale variable values. try: v_to.set_value(value(v_from, exception=False)) if skip_stale: v_to.stale = False except ValueError as err: err_msg = getattr(err, 'message', str(err)) var_val = value(v_from) rounded_val = round(var_val) # Check to see if this is just a tolerance issue if 'is not in domain Binary' in err_msg and ( fabs(var_val - 1) <= config.integer_tolerance or fabs(var_val) <= config.integer_tolerance): v_to.set_value(rounded_val) elif 'is not in domain Integers' in err_msg and ( fabs(var_val - rounded_val) <= config.integer_tolerance): v_to.set_value(rounded_val) else: raise
def test_combine_three_inequalities_and_flatten_blocks(self): m = ConcreteModel() m.x = Var() m.y = Var() m.b = Block() m.b.c = Constraint(expr=m.x >= 2) m.c = Constraint(expr=m.y <= m.x) m.b.b2 = Block() m.b.b2.c = Constraint(expr=m.y >= 4) TransformationFactory('contrib.fourier_motzkin_elimination').apply_to( m, vars_to_eliminate=m.y) constraints = m._pyomo_contrib_fme_transformation.projected_constraints self.assertEqual(len(constraints), 2) cons = constraints[1] self.assertEqual(value(cons.lower), 2) self.assertIsNone(cons.upper) self.assertIs(cons.body, m.x) cons = constraints[2] self.assertEqual(value(cons.lower), 4) self.assertIsNone(cons.upper) self.assertIs(cons.body, m.x)
def generate_first_x(self, G, tears): edge_list = self.idx_to_edge(G) x = [] for tear in tears: arc = G.edges[edge_list[tear]]["arc"] for name, mem in arc.src.iter_vars(names=True): try: index = mem.index() except AttributeError: index = None peer = self.source_dest_peer(arc, name, index) x.append(value(peer)) x = numpy.array(x) return x
def init_rNLP(solve_data, config): """Initialize by solving the rNLP (relaxed binary variables).""" solve_data.nlp_iter += 1 m = solve_data.working_model.clone() config.logger.info( "NLP %s: Solve relaxed integrality" % (solve_data.nlp_iter,)) MindtPy = m.MindtPy_utils TransformationFactory('core.relax_integrality').apply_to(m) with SuppressInfeasibleWarning(): results = SolverFactory(config.nlp_solver).solve( m, **config.nlp_solver_args) subprob_terminate_cond = results.solver.termination_condition if subprob_terminate_cond is tc.optimal: main_objective = next(m.component_data_objects(Objective, active=True)) nlp_solution_values = list(v.value for v in MindtPy.variable_list) dual_values = list(m.dual[c] for c in MindtPy.constraint_list) # Add OA cut if main_objective.sense == minimize: solve_data.LB = value(main_objective.expr) else: solve_data.UB = value(main_objective.expr) config.logger.info( 'NLP %s: OBJ: %s LB: %s UB: %s' % (solve_data.nlp_iter, value(main_objective.expr), solve_data.LB, solve_data.UB)) if config.strategy == 'OA': add_oa_cut(nlp_solution_values, dual_values, solve_data, config) elif subprob_terminate_cond is tc.infeasible: # TODO fail? try something else? config.logger.info( 'Initial relaxed NLP problem is infeasible. ' 'Problem may be infeasible.') else: raise ValueError( 'MindtPy unable to handle relaxed NLP termination condition ' 'of %s. Solver message: %s' % (subprob_terminate_cond, results.solver.message))
def detect_effectively_discrete_vars(block, equality_tolerance): """Detect effectively discrete variables. These continuous variables are the sum of discrete variables. """ # Map of effectively_discrete var --> inducing constraints effectively_discrete = ComponentMap() for constr in block.component_data_objects(Constraint, active=True): if constr.lower is None or constr.upper is None: continue # skip inequality constraints if fabs(value(constr.lower) - value(constr.upper)) > equality_tolerance: continue # not equality constriant. Skip. if constr.body.polynomial_degree() not in (1, 0): continue # skip nonlinear expressions repn = generate_standard_repn(constr.body) if len(repn.linear_vars) < 2: # TODO should this be < 2 or < 1? # TODO we should make sure that trivial equality relations are # preprocessed before this, or we will end up reformulating # expressions that we do not need to here. continue non_discrete_vars = list(v for v in repn.linear_vars if v.is_continuous()) if len(non_discrete_vars) == 1: # We know that this is an effectively discrete continuous # variable. Add it to our identified variable list. var = non_discrete_vars[0] inducing_constraints = effectively_discrete.get(var, []) inducing_constraints.append(constr) effectively_discrete[var] = inducing_constraints # TODO we should eventually also look at cases where all other # non_discrete_vars are effectively_discrete_vars return effectively_discrete
def _get_cone_data(self, con): cone_type, cone_param, cone_members = None, 0, None if isinstance(con, quadratic): cone_type = self._mosek.conetype.quad cone_members = [con.r] + list(con.x) elif isinstance(con, rotated_quadratic): cone_type = self._mosek.conetype.rquad cone_members = [con.r1, con.r2] + list(con.x) elif self._version[0] >= 9: if isinstance(con, primal_exponential): cone_type = self._mosek.conetype.pexp cone_members = [con.r, con.x1, con.x2] elif isinstance(con, primal_power): cone_type = self._mosek.conetype.ppow cone_param = value(con.alpha) cone_members = [con.r1, con.r2] + list(con.x) elif isinstance(con, dual_exponential): cone_type = self._mosek.conetype.dexp cone_members = [con.r, con.x1, con.x2] elif isinstance(con, dual_power): cone_type = self._mosek.conetype.dpow cone_param = value(con.alpha) cone_members = [con.r1, con.r2] + list(con.x) return (cone_type, cone_param, ComponentSet(cone_members))
def storage_capacity_max_purchase_constraint_rule(backend_model, loc_tech): """ Set maximum storage capacity, by either storage_cap_max.equals or a combination of energy_cap_max/equals and charge_rate. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{cap}}(loc::tech) \\begin{cases} = storage_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased},& \\text{if } storage_{cap, equals} \\\\ = \\frac{energy_{cap, equals}(loc::tech)}{charge\_rate} \\times \\boldsymbol{purchased} \\times energy_{cap, scale},& \\text{if } energy_{cap, equals}(loc::tech) \\text{ and } \\text{ and } charge_{rate}(loc::tech)\\\\ \\leq storage_{cap, max}(loc::tech) \\times \\boldsymbol{purchased},& \\text{if } storage_{cap, max}(loc::tech)\\\\ \\leq \\frac{energy_{cap, max}(loc::tech)}{charge\_rate} \\times \\boldsymbol{purchased} \\times energy_{cap, scale},& \\text{if } energy_{cap, max}(loc::tech) \\text{ and } charge_{rate}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs_{purchase, store} """ energy_cap_max = get_param(backend_model, 'energy_cap_max', loc_tech) energy_cap_equals = get_param(backend_model, 'energy_cap_equals', loc_tech) energy_cap_scale = get_param(backend_model, 'energy_cap_scale', loc_tech) storage_cap_max = get_param(backend_model, 'storage_cap_max', loc_tech) storage_cap_equals = get_param(backend_model, 'storage_cap_equals', loc_tech) charge_rate = get_param(backend_model, 'charge_rate', loc_tech) if po.value(storage_cap_equals): return backend_model.storage_cap[loc_tech] == ( storage_cap_equals * backend_model.purchased[loc_tech]) elif po.value(energy_cap_equals) and po.value(charge_rate): return backend_model.storage_cap[loc_tech] == ( (energy_cap_equals / charge_rate) * energy_cap_scale * backend_model.purchased[loc_tech]) elif po.value(storage_cap_max): return backend_model.storage_cap[loc_tech] <= ( storage_cap_max * backend_model.purchased[loc_tech]) elif po.value(energy_cap_max) and po.value(charge_rate): return backend_model.storage_cap[loc_tech] <= ( (energy_cap_max / charge_rate) * energy_cap_scale * backend_model.purchased[loc_tech]) else: return po.Constraint.Skip