def test_list5(self): M = self._setup() M.cc = ComplementarityList(rule=(complements(M.y + M.x3, M.x1 + 2 * M.x2 == i) for i in range(3))) self._test("list5", M)
def create_submodel_kkt_block(instance, submodel, deterministic, fixed_upper_vars): """ Add optimality conditions for the submodel This assumes that the original model has the form: min c1*x + d1*y A3*x <= b3 A1*x + B1*y <= b1 min c2*x + d2*y + x'*Q*y A2*x + B2*y + x'*E2*y <= b2 y >= 0 NOTE THE VARIABLE BOUNDS! """ fixed_vars = {id(v) for v in fixed_upper_vars} # # Populate the block with the linear constraints. # Note that we don't simply clone the current block. # We need to collect a single set of equations that # can be easily expressed. # d2 = {} B2 = {} vtmp = {} utmp = {} sids_set = set() sids_list = [] # block = Block(concrete=True) block.u = VarList( ) # Note: Dual variables associated to bounds in primal problem block.v = VarList( ) # Note: Dual variables associated to constraints in primal problem block.c1 = ConstraintList() block.c2 = ComplementarityList() block.c3 = ComplementarityList() # # Collect submodel objective terms # # TODO: detect fixed variables # for odata in submodel.component_data_objects(Objective, active=True): if odata.sense == maximize: d_sense = -1 else: d_sense = 1 # # Iterate through the variables in the representation # o_terms = generate_standard_repn(odata.expr, compute_values=False) # # Linear terms # for i, var in enumerate(o_terms.linear_vars): if id(var) in fixed_vars: # # Skip fixed upper variables # continue # # Store the coefficient for the variable. The coefficient is # negated if the objective is maximized. # id_ = id(var) d2[id_] = d_sense * o_terms.linear_coefs[i] if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) # # Quadratic terms # for i, var in enumerate(o_terms.quadratic_vars): if id(var[0]) in fixed_vars: if id(var[1]) in fixed_vars: # # Skip fixed upper variables # continue # # Add the linear term # id_ = id(var[1]) d2[id_] = d2.get( id_, 0) + d_sense * o_terms.quadratic_coefs[i] * var[0] if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) elif id(var[1]) in fixed_vars: # # Add the linear term # id_ = id(var[0]) d2[id_] = d2.get( id_, 0) + d_sense * o_terms.quadratic_coefs[i] * var[1] if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) else: raise RuntimeError( "Cannot apply this transformation to a problem with \ quadratic terms where both variables are in the lower level.") # # Stop after the first objective # break # # Iterate through all lower level variables, adding dual variables # and complementarity slackness conditions for y bound constraints # for vcomponent in instance.component_objects(Var, active=True): for ndx in vcomponent: if id(vcomponent[ndx]) in fixed_vars: # # Skip fixed upper variables # continue # # For each index, get the bounds for the variable # lb, ub = vcomponent[ndx].bounds if not lb is None: # # Add the complementarity slackness condition for a lower bound # v = block.v.add() block.c3.add(complements(vcomponent[ndx] >= lb, v >= 0)) else: v = None if not ub is None: # # Add the complementarity slackness condition for an upper bound # w = block.v.add() vtmp[id(vcomponent[ndx])] = w block.c3.add(complements(vcomponent[ndx] <= ub, w >= 0)) else: w = None if not (v is None and w is None): # # Record the variables for which complementarity slackness conditions # were created. # id_ = id(vcomponent[ndx]) vtmp[id_] = (v, w) if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) # # Iterate through all constraints, adding dual variables and # complementary slackness conditions (for inequality constraints) # for cdata in submodel.component_data_objects(Constraint, active=True): if cdata.equality: # Don't add a complementary slackness condition for an equality constraint u = block.u.add() utmp[id(cdata)] = (None, u) else: if not cdata.lower is None: # # Add the complementarity slackness condition for a greater-than inequality # u = block.u.add() block.c2.add(complements(-cdata.body <= -cdata.lower, u >= 0)) else: u = None if not cdata.upper is None: # # Add the complementarity slackness condition for a less-than inequality # w = block.u.add() block.c2.add(complements(cdata.body <= cdata.upper, w >= 0)) else: w = None if not (u is None and w is None): utmp[id(cdata)] = (u, w) # # Store the coefficients for the constraint variables that are not fixed # c_terms = generate_standard_repn(cdata.body, compute_values=False) # # Linear terms # for i, var in enumerate(c_terms.linear_vars): if id(var) in fixed_vars: continue id_ = id(var) B2.setdefault(id_, {}).setdefault(id(cdata), c_terms.linear_coefs[i]) if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) # # Quadratic terms # for i, var in enumerate(c_terms.quadratic_vars): if id(var[0]) in fixed_vars: if id(var[1]) in fixed_vars: continue id_ = id(var[1]) if id_ in B2: B2[id_][id(cdata)] = c_terms.quadratic_coefs[i] * var[0] else: B2.setdefault(id_, {}).setdefault( id(cdata), c_terms.quadratic_coefs[i] * var[0]) if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) elif id(var[1]) in fixed_vars: id_ = id(var[0]) if id_ in B2: B2[id_][id(cdata)] = c_terms.quadratic_coefs[i] * var[1] else: B2.setdefault(id_, {}).setdefault( id(cdata), c_terms.quadratic_coefs[i] * var[1]) if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) else: raise RuntimeError( "Cannot apply this transformation to a problem with \ quadratic terms where both variables are in the lower level.") # # Generate stationarity equations # tmp__ = (None, None) for vid in sids_list: exp = d2.get(vid, 0) # lb_dual, ub_dual = vtmp.get(vid, tmp__) if vid in vtmp: if not lb_dual is None: exp -= lb_dual # dual for variable lower bound if not ub_dual is None: exp += ub_dual # dual for variable upper bound # B2_ = B2.get(vid, {}) utmp_keys = list(utmp.keys()) if deterministic: utmp_keys.sort(key=lambda x: utmp[x][0].local_name\ if utmp[x][1] is None else utmp[x][1].local_name) for uid in utmp_keys: if uid in B2_: lb_dual, ub_dual = utmp[uid] if not lb_dual is None: exp -= B2_[uid] * lb_dual if not ub_dual is None: exp += B2_[uid] * ub_dual if type(exp) in six.integer_types or type(exp) is float: # TODO: Annotate the model as unbounded raise IOError("Unbounded variable without side constraints") block.c1.add(exp == 0) return block
def test_list1(self): M = self._setup() M.cc = ComplementarityList() M.cc.add(complements(M.y + M.x3, M.x1 + 2 * M.x2 == 0)) M.cc.add(complements(M.y + M.x3, M.x1 + 2 * M.x2 == 2)) self._test("list1", M)
def _apply_solver(self): start_time = time.time() M=self.options.dual_bound if not self.options.dual_bound: M=1e6 print(f'Dual bound not specified, set to default {M}') delta = self.options.delta if not self.options.delta: delta = 0.05 #What should default robustness delta be if not specified? Or should I raise an error? print(f'Robustness parameter not specified, set to default {delta}') # matrix representation for bilevel problem matrix_repn = BilevelMatrixRepn(self._instance,standard_form=False) # each lower-level problem submodel = [block for block in self._instance.component_objects(SubModel)][0] if len(submodel) != 1: raise Exception('Problem encountered, this is not a valid bilevel model for the solver.') self._instance.reclassify_component_type(submodel, Block) #varref(submodel) #dataref(submodel) all_vars = {key: var for (key, var) in matrix_repn._all_vars.items()} # get the variables that are fixed for the submodel (lower-level block) fixed_vars = {key: var for (key, var) in matrix_repn._all_vars.items() if key in matrix_repn._fixed_var_ids[submodel.name]} #Is there a way to get integer, continuous, etc for the upper level rather than lumping them all into fixed? # continuous variables in SubModel c_vars = {key: var for (key, var) in matrix_repn._all_vars.items() if key in matrix_repn._c_var_ids - fixed_vars.keys()} # binary variables in SubModel SHOULD BE EMPTY FOR THIS SOLVER b_vars = {key: var for (key, var) in matrix_repn._all_vars.items() if key in matrix_repn._b_var_ids - fixed_vars.keys()} if len(b_vars)!= 0: raise Exception('Problem encountered, this is not a valid bilevel model for the solver. Binary variables present!') # integer variables in SubModel SHOULD BE EMPTY FOR THIS SOLVER i_vars = {key: var for (key, var) in matrix_repn._all_vars.items() if key in matrix_repn._i_var_ids - fixed_vars.keys()} if len(i_vars) != 0: raise Exception('Problem encountered, this is not a valid bilevel model for the solver. Integer variables present!') # get constraint information related to constraint id, sign, and rhs value sub_cons = matrix_repn._cons_sense_rhs[submodel.name] cons= matrix_repn._cons_sense_rhs[self._instance.name] # construct the high-point problem (LL feasible, no LL objective) # s0 <- solve the high-point # if s0 infeasible then return high_point_infeasible xfrm = TransformationFactory('pao.bilevel.highpoint') xfrm.apply_to(self._instance) # # Solve with a specified solver # solver = self.options.solver if not self.options.solver: solver = 'gurobi' for c in self._instance.component_objects(Block, descend_into=False): if 'hp' in c.name: #if '_hp' in c.name: c.activate() with pyomo.opt.SolverFactory(solver) as opt: self.results.append(opt.solve(c, tee=self._tee, timelimit=self._timelimit)) _check_termination_condition(self.results[-1]) c.deactivate() if self.options.do_print==True: print('Solution to the Highpoint Relaxation') for _, var in all_vars.items(): var.pprint() # s1 <- solve the optimistic bilevel (linear/linear) problem (call solver3) # if s1 infeasible then return optimistic_infeasible' with pyomo.opt.SolverFactory('pao.bilevel.blp_global') as opt: opt.options.solver = solver self.results.append(opt.solve(self._instance,tee=self._tee,timelimit=self._timelimit)) _check_termination_condition(self.results[-1]) if self.options.do_print==True: print('Solution to the Optimistic Bilevel') for _, var in all_vars.items(): var.pprint() #self._instance.pprint() #checking for active blocks left over from previous solves # sk <- solve the dual adversarial problem # if infeasible then return dual_adversarial_infeasible # Collect the vertices solutions for the dual adversarial problem #Collect up the matrix B and the vector d for use in all adversarial feasibility problems n=len(c_vars.items()) m=len(sub_cons.items()) K=len(cons.items()) B=np.empty([m,n]) L=np.empty([K,1]) i=0 p=0 for _, var in c_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) B[:,i]=np.transpose(np.array(A)) i+=1 _ad_block_name='_adversarial' self._instance.add_component(_ad_block_name, Block(Any)) _Vertices_name='_Vertices' _Vertices_B_name='_VerticesB' self._instance.add_component(_Vertices_name,Param(cons.keys()*NonNegativeIntegers*sub_cons.keys(),mutable=True)) Vertices=getattr(self._instance,_Vertices_name) self._instance.add_component(_Vertices_B_name,Param(cons.keys()*NonNegativeIntegers,mutable=True)) VerticesB=getattr(self._instance,_Vertices_B_name) adversarial=getattr(self._instance,_ad_block_name) #Add Adversarial blocks for _cidS, _ in cons.items(): # <for each constraint in the upper-level problem> (_cid,_)=_cidS ad=adversarial[_cid] #shorthand ad.alpha=Var(sub_cons.keys(),within=NonNegativeReals) #sub_cons.keys() because it's a dual variable on the lower level constraints ad.beta=Var(within=NonNegativeReals) Hk=np.empty([n,1]) i=0 d=np.empty([n,1]) ad.cons=Constraint(c_vars.keys()) #B^Talpha+beta*d>= H_k, v-dimension constraints so index by c_vars lhs_expr = {key: 0. for key in c_vars.keys()} rhs_expr = {key: 0. for key in c_vars.keys()} for _vid, var in c_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A #+ dot(A_q.toarray(), _fixed) (C, C_q, C_constant) = matrix_repn.cost_vectors(submodel, var) d[i,0]=float(C) lhs_expr[_vid]=float(C)*ad.beta (A,A_q,sign,b)=matrix_repn.coef_matrices(self._instance,var) idx = list(cons.keys()).index(_cidS) Hk[i,0]=A[idx] i+=1 for _cid2 in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid2) lhs_expr[_vid] += float(coef[idx])*ad.alpha[_cid2] rhs_expr[_vid] = float(A[idx]) expr = lhs_expr[_vid] >= rhs_expr[_vid] if not type(expr) is bool: ad.cons[_vid] = expr else: ad.cons[_vid] = Constraint.Skip ad.Obj=Objective(expr=0) #THIS IS A FEASIBILITY PROBLEM with pyomo.opt.SolverFactory(solver) as opt: self.results.append(opt.solve(ad, tee=self._tee, timelimit=self._timelimit)) _check_termination_condition(self.results[-1]) ad.deactivate() Bd=np.hstack((np.transpose(B),d)) Eye=np.identity(m+1) Bd=np.vstack((Bd,Eye)) Hk=np.vstack((Hk,np.zeros((m+1,1)))) mat=np.hstack((-Hk,Bd)) mat=cdd.Matrix(mat,number_type='float') mat.rep_type=cdd.RepType.INEQUALITY poly=cdd.Polyhedron(mat) ext=poly.get_generators() extreme=np.array(ext) if self.options.do_print==True: print(ext) (s,t)=extreme.shape l=1 for i in range(0,s): j=1 if extreme[0,i]==1: for _scid in sub_cons.keys(): #for j in range(1,t-1): #Need to loop over extreme 1 to t-1 and link those to the cons.keys for alpha? Vertices[(_cidS,l,_scid)]=extreme[i,j] #Vertex l of the k-th polytope j+=1 VerticesB[(_cidS,l)]=extreme[i,t-1] l+=1 L[p,0]=l-1 p+=1 #vertex enumeration goes from 1 to L # Solving the full problem sn0 _model_name = '_extended' _model_name = unique_component_name(self._instance, _model_name) xfrm = TransformationFactory('pao.bilevel.highpoint') #5.6a-c kwds = {'submodel_name': _model_name} xfrm.apply_to(self._instance, **kwds) extended=getattr(self._instance,_model_name) extended.sigma=Var(c_vars.keys(),within=NonNegativeReals,bounds=(0,M)) extended.lam=Var(sub_cons.keys(),within=NonNegativeReals,bounds=(0,M)) #5.d extended.d = Constraint(c_vars.keys()) #indexed by lower level variables d_expr= {key: 0. for key in c_vars.keys()} for _vid, var in c_vars.items(): (C, C_q, C_constant) = matrix_repn.cost_vectors(submodel, var) #gets d_i d_expr[_vid]+=float(C) d_expr[_vid]=d_expr[_vid]-extended.sigma[_vid] (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) for _cid, _ in sub_cons.items(): idx = list(sub_cons.keys()).index(_cid) d_expr[_vid]+=extended.lam[_cid]*float(A[idx]) expr = d_expr[_vid] == 0 if not type(expr) is bool: extended.d[_vid] = expr else: extended.d[_vid] = Constraint.Skip #5.e (Complementarity) extended.e = ComplementarityList() for _cid, _ in sub_cons.items(): idx=list(sub_cons.keys()).index(_cid) expr=0 for _vid, var in fixed_vars.items(): #A_i*x (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) expr+=float(A[idx])*fixed_vars[_vid] for _vid, var in c_vars.items(): #B_i*v (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) expr+=float(A[idx])*c_vars[_vid] expr=expr-float(b[idx]) extended.e.add(complements(extended.lam[_cid] >= 0, expr <= 0)) #5.f (Complementarity) extended.f = ComplementarityList() for _vid,var in c_vars.items(): extended.f.add(complements(extended.sigma[_vid]>=0,var>=0)) #Replace 5.h-5.j with 5.7 Disjunction extended.disjunction=Block(cons.keys()) #One disjunction per adversarial problem, one adversarial problem per upper level constraint k=0 for _cidS,_ in cons.items(): idxS=list(cons.keys()).index(_cidS) [_cid,sign]=_cidS disjunction=extended.disjunction[_cidS] #shorthand disjunction.Lset=RangeSet(1,L[k,0]) disjunction.disjuncts=Disjunct(disjunction.Lset) for i in disjunction.Lset: #defining the L disjuncts l_expr=0 for _vid, var in c_vars.items(): (C, C_q, C_constant) = matrix_repn.cost_vectors(submodel, var) l_expr+=float(C)*var #d^Tv l_expr+=delta l_expr=VerticesB[(_cidS,i)]*l_expr #beta(d^Tv+delta) for _cid, Scons in sub_cons.items(): #SUM over i to ml Ax=0 idx=list(sub_cons.keys()).index(_cid) for _vid, var in fixed_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) Ax += float(A[idx])*var l_expr+=Vertices[(_cidS,i,_cid)]*(float(b[idx])-Ax) r_expr=0 for _vid,var in fixed_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(self._instance, var) #get q and G r_expr=r_expr-float(A[idxS])*var r_expr+=float(b[idxS]) disjunction.disjuncts[i].cons=Constraint(expr= l_expr<=r_expr) disjunction.seven=Disjunction(expr=[disjunction.disjuncts[i] for i in disjunction.Lset],xor=False) k+=1 #extended.pprint() TransformationFactory('mpec.simple_disjunction').apply_to(extended) bigm = TransformationFactory('gdp.bigm') bigm.apply_to(extended) with pyomo.opt.SolverFactory(solver) as opt: self.results.append(opt.solve(extended, tee=self._tee, timelimit=self._timelimit)) _check_termination_condition(self.results[-1]) # Return the sn0 solution if self.options.do_print==True: print('Robust Solution') for _vid, _ in fixed_vars.items(): fixed_vars[_vid].pprint() for _vid, _ in c_vars.items(): c_vars[_vid].pprint() extended.lam.pprint() extended.sigma.pprint() stop_time = time.time() self.wall_time = stop_time - start_time return pyutilib.misc.Bunch(rc=getattr(opt, '_rc', None), log=getattr(opt, '_log', None))
# ex1c.py import pyomo.environ as pyo from pyomo.mpec import ComplementarityList, complements n = 5 model = pyo.ConcreteModel() model.x = pyo.Var(range(1, n + 1)) model.f = pyo.Objective(expr=sum(i * (model.x[i] - 1)**2 for i in range(1, n + 1))) def compl_(model): yield complements(model.x[1] >= 0, model.x[2] >= 0) yield complements(model.x[2] >= 0, model.x[3] >= 0) yield complements(model.x[3] >= 0, model.x[4] >= 0) yield complements(model.x[4] >= 0, model.x[5] >= 0) model.compl = ComplementarityList(rule=compl_)
def _apply_solver(self): self.results = [] start_time = time.time() # # Solve with a specified solver # solver = self.options.solver if not self.options.solver: solver = 'gurobi' bigm = TransformationFactory('gdp.bigm') # Step 1. Initialization LB = -inf UB = inf theta = 0. xi = 10e-3 k = 0 epsilon = 1e-4 M = 1e6 # matrix representation for bilevel problem matrix_repn = BilevelMatrixRepn(self._instance) # each lower-level problem submodel = [ block for block in self._instance.component_objects(SubModel) ][0] if len(submodel) != 1: raise Exception( 'Problem encountered, this is not a valid bilevel model for the solver.' ) self._instance.reclassify_component_type(submodel, Block) varref(submodel) dataref(submodel) # all algorithm blocks algorithm_blocks = list() algorithm_blocks.append(submodel) all_vars = {key: var for (key, var) in matrix_repn._all_vars.items()} # for k,v in all_vars.items(): # if v.ub is None: # v.setub(M) # if v.lb is None: # v.setlb(-M) # get the variables that are fixed for the submodel (lower-level block) fixed_vars = { key: var for (key, var) in matrix_repn._all_vars.items() if key in matrix_repn._fixed_var_ids[submodel.name] } # continuous variables in SubModel c_vars = { key: var for (key, var) in matrix_repn._all_vars.items() if key in matrix_repn._c_var_ids - fixed_vars.keys() } # binary variables in SubModel b_vars = { key: var for (key, var) in matrix_repn._all_vars.items() if key in matrix_repn._b_var_ids - fixed_vars.keys() } # integer variables in SubModel i_vars = { key: var for (key, var) in matrix_repn._all_vars.items() if key in matrix_repn._i_var_ids - fixed_vars.keys() } # get constraint information related to constraint id, sign, and rhs value sub_cons = matrix_repn._cons_sense_rhs[submodel.name] # lower bounding block name -- unique naming convention _lower_model_name = '_p9' _lower_model_name = unique_component_name(self._instance, _lower_model_name) # upper bounding block name -- unique naming convention _upper_model_name = '_p7' _upper_model_name = unique_component_name(self._instance, _upper_model_name) while k <= self._k_max_iter: # Step 2. Lower Bounding # Solve problem (P5) master problem. # This includes equations (53), (12), (13), (15), and (54) # On iteration k = 0, (54) does not exist. Instead of implementing (54), # this approach applies problem (P9) which incorporates KKT-based tightening # constraints, and a projection and indicator constraint set. lower_bounding_master = getattr(self._instance, _lower_model_name, None) if lower_bounding_master is None: xfrm = TransformationFactory('pao.bilevel.highpoint') kwds = {'submodel_name': _lower_model_name} xfrm.apply_to(self._instance, **kwds) lower_bounding_master = getattr(self._instance, _lower_model_name) algorithm_blocks.append(lower_bounding_master) _c_var_bounds_rule = lambda m, k: c_vars[k].bounds _c_var_init_rule = lambda m, k: (c_vars[k].lb + c_vars[k].ub ) / 2 lower_bounding_master._iter_c = Var( Any, bounds=(0, M), within=Reals, dense=False) # set (iter k, c_var_ids) lower_bounding_master._iter_c_tilde = Var( c_vars.keys(), bounds=_c_var_bounds_rule, initialize=_c_var_init_rule, within=Reals) # set (iter k, c_var_ids) lower_bounding_master._iter_b = Var( Any, within=Binary, bounds=(0, M), dense=False) # set (iter k, b_var_ids) lower_bounding_master._iter_i = Var( Any, within=Integers, bounds=(0, M), dense=False) # set (iter k, i_var_ids) lower_bounding_master._iter_pi_tilde = Var( sub_cons.keys(), bounds=(0, M)) # set (iter k, sub_cons_ids) lower_bounding_master._iter_pi = Var( Any, bounds=(0, M), dense=False) # set (iter k, sub_cons_ids) lower_bounding_master._iter_t = Var( Any, bounds=(0, M), dense=False) # set (iter k, sub_cons_ids) lower_bounding_master._iter_lambda = Var( Any, bounds=(0, M), dense=False) # set (iter k, sub_cons_ids) m = lower_bounding_master # shorthand reference to model if k == 0: # constraint (74) lhs_expr = 0. rhs_expr = 0. for _vid, var in c_vars.items(): (C, C_q, C_constant) = matrix_repn.cost_vectors(submodel, var) coef = float(C) # + dot(C_q,_fixed)) ref = m._map[var] lhs_expr += coef * ref rhs_expr += coef * m._iter_c_tilde[_vid] expr = lhs_expr >= rhs_expr if not type(expr) is bool: lower_bounding_master.KKT_tight1 = Constraint( expr=lhs_expr >= rhs_expr) # constraint (75a) lower_bounding_master.KKT_tight2a = Constraint(sub_cons.keys()) lhs_expr_a = {key: 0. for key in sub_cons.keys()} rhs_expr_a = {key: 0. for key in sub_cons.keys()} for _vid, var in c_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A #+ dot(A_q.toarray(), _fixed) for _cid in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid) lhs_expr_a[_cid] += float( coef[idx]) * m._iter_c_tilde[_vid] for var in {**b_vars, **i_vars, **fixed_vars}.values(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A #+ dot(A_q.toarray(), _fixed) for _cid in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid) ref = m._map[var] rhs_expr_a[_cid] += -float(coef[idx]) * ref for _cid, b in sub_cons.items(): (_, sign) = _cid rhs_expr_a[_cid] += b if sign == 'l' or sign == 'g->l': expr = lhs_expr_a[_cid] <= rhs_expr_a[_cid] if sign == 'e' or sign == 'g': raise Exception( 'Problem encountered, this problem is not in standard form.' ) if not type(expr) is bool: lower_bounding_master.KKT_tight2a[_cid] = expr else: lower_bounding_master.KKT_tight2a[ _cid] = Constraint.Skip # constraint (75b) lower_bounding_master.KKT_tight2b = Constraint(c_vars.keys()) lhs_expr_b = {key: 0. for key in c_vars.keys()} rhs_expr_b = {key: 0. for key in c_vars.keys()} for _vid, var in c_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A #+ dot(A_q.toarray(), _fixed) for _cid in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid) lhs_expr_b[_vid] += float( coef[idx]) * m._iter_pi_tilde[_cid] (C, C_q, C_constant) = matrix_repn.cost_vectors(submodel, var) rhs_expr_b[_vid] = float(C) #+ dot(C_q,_fixed)) expr = lhs_expr_b[_vid] >= rhs_expr_b[_vid] if not type(expr) is bool: lower_bounding_master.KKT_tight2b[_vid] = expr else: lower_bounding_master.KKT_tight2b[ _vid] = Constraint.Skip # constraint (76a) lower_bounding_master.KKT_tight3a = ComplementarityList() for _vid in c_vars.keys(): lower_bounding_master.KKT_tight3a.add( complements(m._iter_c_tilde[_vid] >= 0, lhs_expr_b[_vid] - rhs_expr_b[_vid] >= 0)) # constraint (76b) lower_bounding_master.KKT_tight3b = ComplementarityList() for _cid in sub_cons.keys(): lower_bounding_master.KKT_tight3b.add( complements(m._iter_pi_tilde[_cid] >= 0, rhs_expr_a[_cid] - lhs_expr_a[_cid] >= 0)) # constraint (77a) lower_bounding_master.KKT_tight4a = Constraint(c_vars.keys()) for _vid in c_vars.keys(): lower_bounding_master.KKT_tight4a[ _vid] = m._iter_c_tilde[_vid] >= 0 # constraint (77b) lower_bounding_master.KKT_tight4b = Constraint(sub_cons.keys()) for _cid in sub_cons.keys(): lower_bounding_master.KKT_tight4b[ _cid] = m._iter_pi_tilde[_cid] >= 0 # solve the HPR and check the termination condition lower_bounding_master.activate() TransformationFactory('mpec.simple_disjunction').apply_to( lower_bounding_master) bigm.apply_to(lower_bounding_master) with pyomo.opt.SolverFactory(solver) as opt: self.results.append( opt.solve(lower_bounding_master, tee=self._tee, timelimit=self._timelimit)) if not _check_termination_condition(self.results[-1]): raise Exception( 'The lower-bounding master is infeasible or sub-optimal.') # the LB should be a sequence of non-decreasing lower-bounds _lb = [ value(odata) for odata in self._instance.component_objects(Objective) if odata.parent_block() == self._instance ][0] if _lb < LB: raise Exception( 'The lower-bound should be non-decreasing; a decreasing lower-bound indicates an algorithm issue.' ) LB = max(LB, _lb) lower_bounding_master.deactivate() # Step 3. Termination if UB - LB < xi: # print(UB) # print(LB) if self._instance.solutions.solutions: self._instance.solutions.select(0, ignore_fixed_vars=True) stop_time = time.time() self.wall_time = stop_time - start_time self.results_obj = self._setup_results_obj() return pyutilib.misc.Bunch(rc=getattr(opt, '_rc', None), log=getattr(opt, '_log', None)) # fix the upper-level (master) variables to solve (P6) and (P7) for key, var in fixed_vars.items(): var.fix(var.value) # Step 4. Subproblem 1 # Solve problem (P6) lower-level problem for fixed upper-level optimal vars. # In iteration k=0, this first subproblem is always feasible; furthermore, the # optimal solution to (P5), alternatively (P9), will also be feasible to (P6). with pyomo.opt.SolverFactory(solver) as _opt: _results = _opt.solve(submodel, tee=self._tee, timelimit=self._timelimit) if not _check_termination_condition(_results): raise Exception( 'The lower-level subproblem with fixed upper-level variables is infeasible or sub-optimal.' ) theta = [ value(odata) for odata in submodel.component_objects(Objective) ][0] # solution for all variable values _fixed = array( [var.value for (key, var) in matrix_repn._all_vars.items()]) # temporary dictionary for b_vars _b_vars_subproblem1 = dict() for _vid, var in b_vars.items(): _b_vars_subproblem1[_vid] = var.value # temporary dictionary for i_vars _i_vars_subproblem1 = dict() for _vid, var in i_vars.items(): _i_vars_subproblem1[_vid] = var.value # Step 5. Subproblem 2 # Solve problem (P7) upper bounding problem for fixed upper-level optimal vars. upper_bounding_subproblem = getattr(self._instance, _upper_model_name, None) if upper_bounding_subproblem is None: xfrm = TransformationFactory('pao.bilevel.highpoint') kwds = {'submodel_name': _upper_model_name} xfrm.apply_to(self._instance, **kwds) upper_bounding_subproblem = getattr(self._instance, _upper_model_name) algorithm_blocks.append(upper_bounding_subproblem) for odata in upper_bounding_subproblem.component_objects( Objective): upper_bounding_subproblem.del_component(odata) # solve for the master problem objective value for just the lower level variables upper_bounding_subproblem.del_component('objective') obj_constant = 0. obj_expr = 0. for var in {**c_vars, **b_vars, **i_vars}.values(): (C, C_q, C_constant) = matrix_repn.cost_vectors(self._instance, var) if obj_constant == 0. and C_constant != 0.: obj_constant += C_constant # only add the constant once obj_expr += float(C + dot(C_q, _fixed)) * var upper_bounding_subproblem.objective = Objective(expr=obj_expr + obj_constant) # include lower bound constraint on the subproblem objective upper_bounding_subproblem.del_component('theta_pareto') sub_constant = 0. sub_expr = 0. for var in all_vars.values(): (C, C_q, C_constant) = matrix_repn.cost_vectors(submodel, var) if sub_constant == 0. and C_constant != 0.: sub_constant += C_constant # only add the constant once sub_expr += float(C + dot(C_q, _fixed)) * var upper_bounding_subproblem.theta_pareto = Constraint( expr=sub_expr + sub_constant >= theta) with pyomo.opt.SolverFactory(solver) as opt: self.results.append( opt.solve(upper_bounding_subproblem, tee=self._tee, timelimit=self._timelimit)) if _check_termination_condition(self.results[-1]): # calculate new upper bound obj_constant = 0. obj_expr = 0. for var in fixed_vars.values(): (C, C_q, C_constant) = matrix_repn.cost_vectors( self._instance, var) if obj_constant == 0. and C_constant != 0.: obj_constant += C_constant # only add the constant once obj_expr += float(C + dot(C_q, _fixed)) * var.value # line 16 of decomposition algorithm _ub = obj_expr + obj_constant + [ value(odata) for odata in upper_bounding_subproblem.component_objects(Objective) ][0] UB = min(UB, _ub) # unfix the upper-level variables for var in fixed_vars.values(): var.unfix() # fix the solution for submodel binary variables for _vid, var in b_vars.items(): m._iter_b[(k, _vid)].fix(var.value) # fix the solution for submodel integer variables for _vid, var in i_vars.items(): m._iter_i[(k, _vid)].fix(var.value) else: # infeasible problem # unfix the upper-level variables for var in fixed_vars.values(): var.unfix() # retrieve b_vars from temporary dictionary for subproblem1 for _vid, var in b_vars.items(): m._iter_b[(k, _vid)].fix(_b_vars_subproblem1[_vid]) # retrieve i_vars from temporary dictionary for subproblem1 for _vid, var in i_vars.items(): m._iter_i[(k, _vid)].fix(_i_vars_subproblem1[_vid]) # Step 6. Tightening the Master Problem projections = getattr(lower_bounding_master, 'projections', None) if projections is None: lower_bounding_master.projections = Block(Any) projections = lower_bounding_master.projections # constraint (79) projections[k].projection1 = Constraint(sub_cons.keys()) lhs_expr_proj = {key: 0. for key in sub_cons.keys()} rhs_expr_proj = {key: 0. for key in sub_cons.keys()} for _vid, var in c_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A + dot(A_q.toarray(), _fixed) for _cid in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid) lhs_expr_proj[_cid] += float( coef[idx]) * m._iter_c[(k, _vid)] for _vid, var in b_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A + dot(A_q.toarray(), _fixed) for _cid in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid) rhs_expr_proj[_cid] += -float(coef[idx]) * m._iter_b[ (k, _vid)] for _vid, var in i_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A + dot(A_q.toarray(), _fixed) for _cid in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid) rhs_expr_proj[_cid] += -float(coef[idx]) * m._iter_i[ (k, _vid)] for _vid, var in fixed_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A + dot(A_q.toarray(), _fixed) for _cid in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid) rhs_expr_proj[_cid] += -float(coef[idx]) * var for _cid, b in sub_cons.items(): (id, sign) = _cid rhs_expr_proj[_cid] += b if sign == 'l' or sign == 'g->l': projections[k].projection1[ _cid] = lhs_expr_proj[_cid] - m._iter_t[ (k, _cid)] <= rhs_expr_proj[_cid] if sign == 'e' or sign == 'g': raise Exception( 'Problem encountered, this problem is not in standard form.' ) # constraint (80a) projections[k].projection2a = Constraint(c_vars.keys()) for _vid in c_vars.keys(): projections[k].projection2a[_vid] = m._iter_c[(k, _vid)] >= 0 # constraint (80b) projections[k].projection2b = Constraint(sub_cons.keys()) for _cid in sub_cons.keys(): projections[k].projection2b[_cid] = m._iter_t[(k, _cid)] >= 0 # constraint (82) projections[k].projection3 = Block() projections[k].projection3.indicator = Disjunct() projections[k].projection3.block = Disjunct() projections[k].projection3.disjunct = Disjunction(expr=[ projections[k].projection3.indicator, projections[k].projection3.block ]) projections[k].projection3.indicator.cons_feas = Constraint( expr=sum(m._iter_t[(k, _cid)] for _cid in sub_cons.keys()) >= epsilon) disjunction = projections[k].projection3.block # constraint (82a) lhs_constant = 0. lhs_expr = 0. rhs_constant = 0. rhs_expr = 0. for _vid, var in {**c_vars, **b_vars, **i_vars}.items(): (C, C_q, C_constant) = matrix_repn.cost_vectors(submodel, var) lhs_expr += float(C + dot(C_q, _fixed)) * var if var.is_continuous(): rhs_expr += float(C + dot(C_q, _fixed)) * m._iter_c[(k, _vid)] if var.is_binary(): rhs_expr += float(C + dot(C_q, _fixed)) * m._iter_b[(k, _vid)] if var.is_integer(): rhs_expr += float(C + dot(C_q, _fixed)) * m._iter_i[(k, _vid)] disjunction.projection3a = Constraint( expr=lhs_expr + lhs_constant >= rhs_expr + rhs_constant) # constraint (82b) disjunction.projection3b = Constraint(sub_cons.keys()) for _cid in sub_cons.keys(): (_, sign) = _cid if sign == 'l' or sign == 'g->l': expr = lhs_expr_proj[_cid] <= rhs_expr_proj[_cid] if sign == 'e' or sign == 'g': raise Exception( 'Problem encountered, this problem is not in standard form.' ) if not type(expr) is bool: disjunction.projection3b[_cid] = expr else: disjunction.projection3b[_cid] = Constraint.Skip # constraint (82c) disjunction.projection3c = Constraint(c_vars.keys()) lhs_expr = {key: 0. for key in c_vars.keys()} rhs_expr = {key: 0. for key in c_vars.keys()} for _vid, var in c_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A + dot(A_q.toarray(), _fixed) idx = list(sub_cons.keys()).index(_cid) lhs_expr[_vid] += float(coef[idx]) * m._iter_pi[(k, _cid)] (C, C_q, C_constant) = matrix_repn.cost_vectors(submodel, var) rhs_expr[_vid] = float(C + dot(C_q, _fixed)) expr = lhs_expr[_vid] >= rhs_expr[_vid] if not type(expr) is bool: disjunction.projection3c[_vid] = expr else: disjunction.projection3c[_vid] = Constraint.Skip # constraint (82d) disjunction.projection3d = ComplementarityList() for _vid in c_vars.keys(): disjunction.projection3d.add( complements(m._iter_c[(k, _vid)] >= 0, lhs_expr[_vid] - rhs_expr[_vid] >= 0)) # constraint (82e) disjunction.projection3e = ComplementarityList() for _cid in sub_cons.keys(): disjunction.projection3e.add( complements( m._iter_pi[(k, _cid)] >= 0, rhs_expr_proj[_cid] - lhs_expr_proj[_cid] >= 0)) # constraint (82f) disjunction.projection3f = Constraint(c_vars.keys()) for _vid in c_vars.keys(): disjunction.projection3f[_vid] = m._iter_c[(k, _vid)] >= 0 # constraint (82g) disjunction.projection3g = Constraint(sub_cons.keys()) for _cid in sub_cons.keys(): disjunction.projection3g[_cid] = m._iter_pi[(k, _cid)] >= 0 # constraint (83a) projections[k].projection4a = Constraint(c_vars.keys()) lhs_expr = {key: 0. for key in c_vars.keys()} for _vid, var in c_vars.items(): (A, A_q, sign, b) = matrix_repn.coef_matrices(submodel, var) coef = A + dot(A_q.toarray(), _fixed) for _cid in sub_cons.keys(): idx = list(sub_cons.keys()).index(_cid) lhs_expr[_vid] += float( coef[idx]) * m._iter_lambda[(k, _cid)] expr = lhs_expr[_vid] >= 0 if not type(expr) is bool: projections[k].projection4a[_vid] = expr else: projections[k].projection4a[_vid] = Constraint.Skip # constraint (83b) projections[k].projection4b = ComplementarityList() for _vid in c_vars.keys(): projections[k].projection4b.add( complements(m._iter_c[(k, _vid)] >= 0, lhs_expr[_vid] >= 0)) # constraint (84a) projections[k].projection5a = Constraint(sub_cons.keys()) for _cid in sub_cons.keys(): projections[k].projection5a[_cid] = 1 - m._iter_lambda[ (k, _cid)] >= 0 # constraint (84b) projections[k].projection5b = ComplementarityList() for _cid in sub_cons.keys(): projections[k].projection5b.add( complements(m._iter_t[(k, _cid)] >= 0, 1 - m._iter_lambda[(k, _cid)] >= 0)) # constraint (85) projections[k].projection6 = ComplementarityList() for _cid in sub_cons.keys(): projections[k].projection6.add( complements( m._iter_lambda[(k, _cid)] >= 0, rhs_expr_proj[_cid] - lhs_expr_proj[_cid] + m._iter_t[(k, _cid)] >= 0)) k = k + 1 # Step 7. Loop if UB - LB < xi: # print(UB) # print(LB) if self._instance.solutions.solutions: self._instance.solutions.select(0, ignore_fixed_vars=True) stop_time = time.time() self.wall_time = stop_time - start_time self.results_obj = self._setup_results_obj() return pyutilib.misc.Bunch(rc=getattr(opt, '_rc', None), log=getattr(opt, '_log', None))
def _add_optimality_conditions(self, instance, submodel): """ Add optimality conditions for the submodel This assumes that the original model has the form: min c1*x + d1*y A3*x <= b3 A1*x + B1*y <= b1 min c2*x + d2*y y >= 0 A2*x + B2*y <= b2 NOTE THE VARIABLE BOUNDS! """ # # Populate the block with the linear constraints. Note that we don't simply clone the # current block. We need to collect a single set of equations that can be easily # expressed. # d2 = {} B2 = {} vtmp = {} utmp = {} sids_set = set() sids_list = [] # block = Block(concrete=True) block.u = VarList() block.v = VarList() block.c1 = ConstraintList() block.c2 = ComplementarityList() block.c3 = ComplementarityList() # # Collect submodel objective terms # for odata in submodel.component_data_objects(Objective, active=True): if odata.sense == maximize: d_sense = -1 else: d_sense = 1 # # Iterate through the variables in the canonical representation # o_terms = generate_canonical_repn(odata.expr, compute_values=False) for i in range(len(o_terms.variables)): var = o_terms.variables[i] if var.parent_component().name in self._fixed_upper_vars: # # Skip fixed upper variables # continue # # Store the coefficient for the variable. The coefficient is # negated if the objective is maximized. # id_ = id(var) d2[id_] = d_sense * o_terms.linear[i] if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) # Stop after the first objective break # # Iterate through all lower level variables, adding dual variables # and complementarity slackness conditions for y bound constraints # for vcomponent in instance.component_objects(Var, active=True): if vcomponent.name in self._fixed_upper_vars: # # Skip fixed upper variables # continue for ndx in vcomponent: # # For each index, get the bounds for the variable # lb, ub = vcomponent[ndx].bounds if not lb is None: # # Add the complementarity slackness condition for a lower bound # v = block.v.add() block.c3.add(complements(vcomponent[ndx] >= lb, v >= 0)) else: v = None if not ub is None: # # Add the complementarity slackness condition for an upper bound # w = block.v.add() vtmp[id(vcomponent[ndx])] = w block.c3.add(complements(vcomponent[ndx] <= ub, w >= 0)) else: w = None if not (v is None and w is None): # # Record the variables for which complementarity slackness conditions # were created. # id_ = id(vcomponent[ndx]) vtmp[id_] = (v, w) if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) # # Iterate through all constraints, adding dual variables and # complementary slackness conditions (for inequality constraints) # for cdata in submodel.component_data_objects(Constraint, active=True): if cdata.equality: # Don't add a complementary slackness condition for an equality constraint u = block.u.add() utmp[id(cdata)] = (None, u) else: if not cdata.lower is None: # # Add the complementarity slackness condition for a greater-than inequality # u = block.u.add() block.c2.add( complements(-cdata.body <= -cdata.lower, u >= 0)) else: u = None if not cdata.upper is None: # # Add the complementarity slackness condition for a less-than inequality # w = block.u.add() block.c2.add(complements(cdata.body <= cdata.upper, w >= 0)) else: w = None if not (u is None and w is None): utmp[id(cdata)] = (u, w) # # Store the coefficients for the contraint variables that are not fixed # c_terms = generate_canonical_repn(cdata.body, compute_values=False) for i in range(len(c_terms.variables)): var = c_terms.variables[i] if var.parent_component().name in self._fixed_upper_vars: continue id_ = id(var) B2.setdefault(id_, {}).setdefault(id(cdata), c_terms.linear[i]) if not id_ in sids_set: sids_set.add(id_) sids_list.append(id_) # # Generate stationarity equations # tmp__ = (None, None) for vid in sids_list: exp = d2.get(vid, 0) # lb_dual, ub_dual = vtmp.get(vid, tmp__) if vid in vtmp: if not lb_dual is None: exp -= lb_dual # dual for variable lower bound if not ub_dual is None: exp += ub_dual # dual for variable upper bound # B2_ = B2.get(vid, {}) utmp_keys = list(utmp.keys()) if self._deterministic: utmp_keys.sort(key=lambda x: utmp[x][0].cname() if utmp[x][1] is None else utmp[x][1].cname()) for uid in utmp_keys: if uid in B2_: lb_dual, ub_dual = utmp[uid] if not lb_dual is None: exp -= B2_[uid] * lb_dual if not ub_dual is None: exp += B2_[uid] * ub_dual if type(exp) in six.integer_types or type(exp) is float: # TODO: Annotate the model as unbounded raise IOError("Unbounded variable without side constraints") else: block.c1.add(exp == 0) # # Return block # return block
def create_model_replacing_LL_with_kkt(repn): """ TODO - Document this transformation """ U = repn.U LL = repn.U.LL N = len(LL) # # Create Pyomo model # M = pe.ConcreteModel() M.U = pe.Block() M.L = pe.Block(range(N)) M.kkt = pe.Block(range(N)) # upper- and lower-level variables pyomo_util.add_variables(M.U, U) for i in range(N): L = LL[i] # lower-level variables pyomo_util.add_variables(M.L[i], L) # dual variables M.kkt[i].lam = pe.Var(range(len(L.b))) # equality constraints M.kkt[i].nu = pe.Var(range(len(L.x)), within=pe.NonNegativeReals) # variable bounds # objective e = pyomo_util.dot(U.c[U], U.x, num=1) + U.d for i in range(N): L = LL[i] e += pyomo_util.dot(U.c[L], L.x, num=1) M.o = pe.Objective(expr=e) # upper-level constraints pyomo_util.add_linear_constraints(M.U, U.A, U, L, U.b, U.inequalities) for i in range(N): # lower-level constraints L = LL[i] pyomo_util.add_linear_constraints(M.L[i], L.A, U, L, L.b, L.inequalities) for i in range(N): L = LL[i] # stationarity M.kkt[i].stationarity = pe.ConstraintList() # L_A_L' * lam L_A_L_T = L.A[L].transpose().todok() X = pyomo_util.dot(L_A_L_T, M.kkt[i].lam) if L.c[L] is not None: for k in range(len(L.c[L])): M.kkt[i].stationarity.add(L.c[L][k] + X[k] - M.kkt[i].nu[k] == 0) for i in range(N): # complementarity slackness - variables M.kkt[i].slackness = ComplementarityList() for j in M.kkt[i].nu: M.kkt[i].slackness.add( complements(M.L[i].xR[j] >= 0, M.kkt[i].nu[j] >= 0)) return M