""" piecewise.py: several approaches for solving problems with piecewise linear functions. Approaches: - mult_selection: multiple selection model - convex_comb_sos: model with SOS2 constraints - convex_comb_dis: convex combination with binary variables (disaggregated model) - convex_comb_dis_log: convex combination with a logarithmic number of binary variables - convex_comb_agg: convex combination with binary variables (aggregated model) - convex_comb_agg_log: convex combination with a logarithmic number of binary variables Copyright (c) by Joao Pedro PEDROSO and Mikio KUBO, 2012 """ from gurobipy import * import math import random def mult_selection(model,a,b): """mult_selection -- add piecewise relation with multiple selection formulation Parameters: - model: a model where to include the piecewise linear relation - a[k]: x-coordinate of the k-th point in the piecewise linear relation - b[k]: y-coordinate of the k-th point in the piecewise linear relation Returns the model with the piecewise linear relation on added variables X, Y, and z. """ K = len(a)-1 w,z = {},{} for k in range(K): w[k] = model.addVar(lb=-GRB.INFINITY) # do not name variables for avoiding clash z[k] = model.addVar(vtype="B") X = model.addVar(lb=a[0], ub=a[K], vtype="C") Y = model.addVar(lb=-GRB.INFINITY) model.update() for k in range(K): model.addConstr(w[k] >= a[k]*z[k]) model.addConstr(w[k] <= a[k+1]*z[k]) model.addConstr(quicksum(z[k] for k in range(K)) == 1) model.addConstr(X == quicksum(w[k] for k in range(K))) c = [float(b[k+1]-b[k])/(a[k+1]-a[k]) for k in range(K)] d = [b[k]-c[k]*a[k] for k in range(K)] model.addConstr(Y == quicksum(d[k]*z[k] + c[k]*w[k] for k in range(K))) return X,Y,z def convex_comb_sos(model,a,b): """convex_comb_sos -- add piecewise relation with gurobi's SOS constraints Parameters: - model: a model where to include the piecewise linear relation - a[k]: x-coordinate of the k-th point in the piecewise linear relation - b[k]: y-coordinate of the k-th point in the piecewise linear relation Returns the model with the piecewise linear relation on added variables X, Y, and z. """ K = len(a)-1 z = {} for k in range(K+1): z[k] = model.addVar(lb=0, ub=1, vtype="C") # do not name variables for avoiding clash X = model.addVar(lb=a[0], ub=a[K], vtype="C") Y = model.addVar(lb=-GRB.INFINITY, vtype="C") model.update() model.addConstr(X == quicksum(a[k]*z[k] for k in range(K+1))) model.addConstr(Y == quicksum(b[k]*z[k] for k in range(K+1))) model.addConstr(quicksum(z[k] for k in range(K+1)) == 1) model.addSOS(GRB.SOS_TYPE2, [z[k] for k in range(K+1)]) return X,Y,z def convex_comb_dis(model,a,b): """convex_comb_dis -- add piecewise relation with convex combination formulation Parameters: - model: a model where to include the piecewise linear relation - a[k]: x-coordinate of the k-th point in the piecewise linear relation - b[k]: y-coordinate of the k-th point in the piecewise linear relation Returns the model with the piecewise linear relation on added variables X, Y, and z. """ K = len(a)-1 wL,wR,z = {},{},{} for k in range(K): wL[k] = model.addVar(lb=0, ub=1, vtype="C") # do not name variables for avoiding clash wR[k] = model.addVar(lb=0, ub=1, vtype="C") z[k] = model.addVar(vtype="B") X = model.addVar(lb=a[0], ub=a[K], vtype="C") Y = model.addVar(lb=-GRB.INFINITY, vtype="C") model.update() model.addConstr(X == quicksum(a[k]*wL[k] + a[k+1]*wR[k] for k in range(K))) model.addConstr(Y == quicksum(b[k]*wL[k] + b[k+1]*wR[k] for k in range(K))) for k in range(K): model.addConstr(wL[k] + wR[k] == z[k]) model.addConstr(quicksum(z[k] for k in range(K)) == 1) return X,Y,z def gray(i): return i^i/2 def convex_comb_dis_log(model,a,b): """convex_comb_dis_log -- add piecewise relation with a logarithmic number of binary variables using the convex combination formulation. Parameters: - model: a model where to include the piecewise linear relation - a[k]: x-coordinate of the k-th point in the piecewise linear relation - b[k]: y-coordinate of the k-th point in the piecewise linear relation Returns the model with the piecewise linear relation on added variables X, Y, and z. """ K = len(a)-1 G = int(math.ceil((math.log(K)/math.log(2)))) # number of required bits N = 1<zeros:",zeros,"ones:",ones if (1 & gray(k)>>j) == 1 and (1 & gray(k-1)>>j) == 1: ones.append(k) if (1 & gray(k)>>j) == 0 and (1 & gray(k-1)>>j) == 0: zeros.append(k) # print j,k,"\tzeros>:",zeros,"ones:",ones # print j,"\tzeros:",zeros,"ones:",ones model.addConstr(quicksum(w[k] for k in ones) <= g[j]) model.addConstr(quicksum(w[k] for k in zeros) <= 1-g[j]) return X,Y,w if __name__ == "__main__": # random.seed(1) a = [ -10, 10, 15, 25, 30, 35, 40, 45, 50, 55, 60, 70] b = [ -20,-20, 15, -21, 0, 50, 18, 0, 15, 24, 10, 15] print "\n\n\npiecewise: multiple selection" model = Model("multiple selection") X,Y,z = mult_selection(model,a,b) # X,Y --> piecewise linear replacement of x,f(x) based on points a,b # model using X and Y (and possibly other variables) u = model.addVar(vtype="C", name="u") model.update() A = model.addConstr(3*X + 4*Y <= 250, "A") B = model.addConstr(7*X - 2*Y + 3*u == 170, "B") model.setObjective(2*X + 15*Y + 5*u, GRB.MAXIMIZE) model.optimize() print "X:",X.X print "Y:",Y.X print "u:",u.X print "\n\n\npiecewise: disaggregated convex combination" model = Model("disaggregated convex combination") X,Y,z = convex_comb_dis(model,a,b) u = model.addVar(vtype="C", name="u") model.update() A = model.addConstr(3*X + 4*Y <= 250, "A") B = model.addConstr(7*X - 2*Y + 3*u == 170, "B") model.setObjective(2*X + 15*Y + 5*u, GRB.MAXIMIZE) model.optimize() print "X:",X.X print "Y:",Y.X print "u:",u.X print "\n\n\npiecewise: disaggregated convex combination, logarithmic number of variables" model = Model("disaggregated convex combination (log)") X,Y,z = convex_comb_dis(model,a,b) u = model.addVar(vtype="C", name="u") model.update() A = model.addConstr(3*X + 4*Y <= 250, "A") B = model.addConstr(7*X - 2*Y + 3*u == 170, "B") model.setObjective(2*X + 15*Y + 5*u, GRB.MAXIMIZE) model.optimize() print "X:",X.X print "Y:",Y.X print "u:",u.X print "\n\n\npiecewise: SOS2 constraint" model = Model("SOS2") X,Y,w = convex_comb_sos(model,a,b) u = model.addVar(vtype="C", name="u") model.update() A = model.addConstr(3*X + 4*Y <= 250, "A") B = model.addConstr(7*X - 2*Y + 3*u == 170, "B") model.setObjective(2*X + 15*Y + 5*u, GRB.MAXIMIZE) model.optimize() print "X:",X.X print "Y:",Y.X print "u:",u.X print "\n\n\npiecewise: aggregated convex combination" model = Model("aggregated convex combination") X,Y,z = convex_comb_agg(model,a,b) u = model.addVar(vtype="C", name="u") model.update() A = model.addConstr(3*X + 4*Y <= 250, "A") B = model.addConstr(7*X - 2*Y + 3*u == 170, "B") model.setObjective(2*X + 15*Y + 5*u, GRB.MAXIMIZE) model.optimize() print "X:",X.X print "Y:",Y.X print "u:",u.X print "\n\n\npiecewise: aggregated convex combination, logarithmic number of variables" model = Model("aggregated convex combination (log)") X,Y,w = convex_comb_agg_log(model,a,b) u = model.addVar(vtype="C", name="u") model.update() A = model.addConstr(3*X + 4*Y <= 250, "A") B = model.addConstr(7*X - 2*Y + 3*u == 170, "B") model.setObjective(2*X + 15*Y + 5*u, GRB.MAXIMIZE) model.optimize() print "X:",X.X print "Y:",Y.X print "u:",u.X