Module coinor.cuppy.cuttingPlanes
Expand source code
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
from builtins import range
from past.utils import old_div
__version__ = '0.5.2'
__author__ = 'Aykut Bulut and Ted Ralphs'
__license__ = 'Eclipse Public License'
__maintainer__ = 'Ted Ralphs'
__email__ = '[email protected]'
__url__ = 'https://github.com/tkralphs/CuPPy'
import sys
from math import floor
import numpy as np
from cylp.py.utils.sparseUtil import csc_matrixPlus
from cylp.cy import CyClpSimplex
from cylp.py.modeling import CyLPArray, CyLPModel
PYOMO_INSTALLED = True
try:
from pyomo.environ import AbstractModel, Var, Constraint, SolverFactory
from pyomo.environ import NonNegativeReals, NonPositiveReals, Reals, Set
from pyomo.environ import Integers, Objective, minimize, value
except ImportError:
PYOMO_INSTALLED = False
DISPLAY_ENABLED = True
try:
from src.grumpy.polyhedron2D import Polyhedron2D, Figure
from src.cuppy.milpInstance import MILPInstance
except ImportError:
try:
from coinor.grumpy.polyhedron2D import Polyhedron2D, Figure
from coinor.cuppy.milpInstance import MILPInstance
except ImportError:
DISPLAY_ENABLED = False
sys.path.append('examples')
EPS = 5
def isInt(x, eps = EPS):
'''
Return True if x is an integer, or if x is a numpy array
with all integer elements, False otherwise
'''
if isinstance(x, (int, float)):
return abs(floor(x + 10**(-eps)) - x) < 10**(-eps)
return (np.abs(np.around(x) - x) < 10**(-eps)).all()
def getFraction(x, eps = EPS):
'Return the fraction part of x: x - floor(x)'
# return x - floor(x)
return np.around(x, decimals = eps) - floor(np.around(x, decimals = eps))
def gomoryMixedIntegerCut(m, rowInds = None, eps = EPS, debug_print = False):
'''Return the Gomory mixed integer cut of rows in ``rowInds`` of lp
(a CyClpSimplex object)'''
cuts = []
lp = m.lp
sol = lp.primalVariableSolution['x']
if rowInds is None:
rowInds = list(range(lp.nConstraints))
for row in rowInds:
basicVarInd = lp.basicVariables[row]
if (basicVarInd in m.integerIndices) and (not isInt(sol[basicVarInd], eps)):
f0 = getFraction(sol[basicVarInd], eps)
f = []
for i in range(lp.nVariables):
if i in lp.basicVariables:
#This is to try to avoid getting very small numbers that
#should be zero
f.append(0)
else:
f.append(getFraction(lp.tableau[row, i], eps))
pi = np.array([old_div(f[j],f0) if f[j] <= f0
else old_div((1-f[j]),(1-f0)) for j in range(lp.nVariables)])
pi_slacks = np.array([old_div(x,f0) if x > 0 else old_div(-x,(1-f0))
for x in lp.tableau[row, lp.nVariables:]])
pi -= pi_slacks * lp.coefMatrix
pi0 = (1 - np.dot(pi_slacks, lp.constraintsUpper) if m.sense == '<='
else 1 + np.dot(pi_slacks, lp.constraintsUpper))
if m.sense == '>=':
cuts.append((pi, pi0))
else:
cuts.append((-pi, -pi0))
return cuts, []
def liftAndProject(m, rowInds = None, eps = EPS, debug_print = False):
'''Return the lift-and-project associated with variables that are basic in
rows in ``rowInds`` of lp (a CyClpSimplex object)'''
cuts = []
lp = m.lp
sol = lp.primalVariableSolution['x']
if rowInds is None:
rowInds = list(range(lp.nConstraints))
for row in rowInds:
basicVarInd = lp.basicVariables[row]
if (basicVarInd in m.integerIndices) and (not isInt(sol[basicVarInd], eps)):
e = np.zeros(lp.nCols)
e[basicVarInd] = 1
#Call function for solving CGLP for the associated variable
#disjunction (disjunction is "<=")
cuts += disjunctionToCut(m, e, floor(sol[basicVarInd]), eps = eps,
debug_print = debug_print)
return cuts, []
def disjunctionToCut(m, pi, pi0, debug_print = False, use_cylp = True, eps = EPS):
'''Generate the most violated valid inequality from a given disjunction'''
me = "cglp_cuts: "
lp = m.lp
sol = lp.primalVariableSolution['x']
if debug_print:
print(me, "constraints sense = ", m.sense)
print(me, "matrix = ")
print(m.A)
print(me, "rhs = ", m.b)
print(me, "vars lower bounds = ", lp.variablesLower)
print(me, "vars upper bounds = ", lp.variablesUpper)
print(me, "objective = ", lp.objective)
print(me, "current solution = ", sol)
print(me, "pi = ", pi)
print(me, "pi0 = ", pi0)
############################################################################
## There are two given LPs:
## s.t. Ax >= b s.t. Ax >= b
## -pi.x >= -pi_0 pi.x >= pi_0+1
## A, b, c, pi, pi_0 are given
##
## CGLP: alpha.x >= beta should be valid for both LPs above
##
## min alpha.x* - beta
## uA - u0.pi = alpha
## vA + v0.pi = alpha
## ub - u0.pi_0 >= beta
## vb + v0.(pi_0 + 1) >= beta
## u0 + v0 = 1
## u, v, u0, v0 >= 0
## if min value comes out < 0, then (alpha.x >= beta) is a cut.
############################################################################
pi = CyLPArray(pi)
Atran = m.A.transpose()
b = CyLPArray(m.b)
numRows, numCols = m.A.shape
if use_cylp:
sp = CyLPModel()
u = sp.addVariable('u', numRows, isInt = False)
v = sp.addVariable('v', numRows, isInt = False)
u0 = sp.addVariable('u0', 1, isInt = False)
v0 = sp.addVariable('v0', 1, isInt = False)
alpha = sp.addVariable('alpha', lp.nVariables, isInt = False)
beta = sp.addVariable('beta', 1, isInt = False)
#This should be as simple as this, but it doesn't work.
#Maybe a bug in CyLP?
#sp += alpha - Atran*u - pi*u0 == 0
#sp += alpha - Atran*v + pi*v0 == 0
for i in range(numCols):
sp += alpha[i] - sum(Atran[i,j]*u[j] for j in range(numRows)) - pi[i]*u0 == 0
for i in range(numCols):
sp += alpha[i] - sum(Atran[i,j]*v[j] for j in range(numRows)) + pi[i]*v0 == 0
if m.sense == '<=':
sp += beta - b*u - pi0*u0 >= 0
sp += beta - b*v + (pi0 + 1)*v0 >= 0
else:
sp += beta - b*u - pi0*u0 <= 0
sp += beta - b*v + (pi0 + 1)*v0 <= 0
sp += u0 + v0 == 1
sp += u >= 0
sp += v >= 0
sp += u0 >= 0
sp += v0 >= 0
if m.sense == '<=':
sp.objective = sum(-sol[i]*alpha[i] for i in range(numCols)) + beta
else:
#This direction is not debugged
sp.objective = sum(sol[i]*alpha[i] for i in range(numCols)) - beta
cglp = CyClpSimplex(sp)
# If we want to solve it as an MILP
# cglp = CyClpSimplex(sp).getCbcModel()
#cglp.writeLp('lp.lp')
cglp.logLevel = 0
cglp.primal(startFinishOptions = 'x')
# Solve as MILP
# cglp.solve()
beta = cglp.primalVariableSolution['beta'][0]
alpha = cglp.primalVariableSolution['alpha']
u = cglp.primalVariableSolution['u']
v = cglp.primalVariableSolution['v']
u0 = cglp.primalVariableSolution['u0'][0]
v0 = cglp.primalVariableSolution['v0'][0]
if debug_print:
print(me, 'Objective Value: ', cglp.objectiveValue)
if debug_print:
print(me, 'u: ', u)
print(me, 'v: ', v)
print(me, 'u0: ', u0)
print(me, 'v0: ', v0)
else:
CG = AbstractModel()
CG.u = Var(list(range(numRows)), domain=NonNegativeReals,
bounds = (0.0, None))
CG.v = Var(list(range(numRows)), domain=NonNegativeReals,
bounds = (0.0, None))
CG.u0 = Var(domain=NonNegativeReals, bounds = (0.0, None))
CG.v0 = Var(domain=NonNegativeReals, bounds = (0.0, None))
CG.alpha = Var(list(range(numRows)), domain=Reals,
bounds = (None, None))
CG.beta = Var(domain=Reals, bounds = (None, None))
## Constraints
def pi_rule_left(CG, i):
x = float(pi[i])
return(sum(Atran[i, j]*CG.u[j] for j in range(numRows)) -
x*CG.u0 - CG.alpha[i] == 0.0)
CG.pi_rule_left = Constraint(list(range(numCols)), rule=pi_rule_left)
def pi_rule_right(CG, i):
x = float(pi[i])
return(sum(Atran[i, j]*CG.v[j] for j in range(numRows)) +
x*CG.v0 - CG.alpha[i] == 0.0)
CG.pi_rule_right = Constraint(list(range(numCols)), rule=pi_rule_right)
if m.sense == '<=':
def pi0_rule_left(CG):
return(sum(b[j]*CG.u[j] for j in range(numRows)) -
pi0*CG.u0 - CG.beta <= 0.0)
CG.pi0_rule_left = Constraint(rule=pi0_rule_left)
def pi0_rule_right(CG):
return(sum(b[j]*CG.v[j] for j in range(numRows)) +
(pi0 + 1)*CG.v0 - CG.beta <= 0.0)
CG.pi0_rule_right = Constraint(rule=pi0_rule_right)
else:
def pi0_rule_left(CG):
return(sum(b[j]*CG.u[j] for j in range(numRows)) -
pi0*CG.u0 - CG.beta >= 0.0)
CG.pi0_rule_left = Constraint(rule=pi0_rule_left)
def pi0_rule_right(CG):
return(sum(b[j]*CG.v[j] for j in range(numRows)) +
(pi0 + 1)*CG.v0 - CG.beta >= 0.0)
CG.pi0_rule_right = Constraint(rule=pi0_rule_right)
def normalization_rule(CG):
return(CG.u0 + CG.v0 == 1.0)
CG.normalization_rule = Constraint(rule=normalization_rule)
def objective_rule(CG):
return(sum(sol[i]*CG.alpha[i] for i in range(numCols)) -
CG.beta)
if m.sense == '<=':
CG.objective = Objective(sense=maximize, rule=objective_rule)
else:
CG.objective = Objective(sense=minimize, rule=objective_rule)
opt = SolverFactory("cbc")
instance = CG.create_instance()
#instance.pprint()
#instance.write("foo.nl", format = "nl")
#opt.options['bonmin.bb_log_level'] = 5
#opt.options['bonmin.bb_log_interval'] = 1
results = opt.solve(instance, tee=False)
#results = opt.solve(instance)
instance.solutions.load_from(results)
beta = instance.beta.value
alpha = np.array([instance.alpha[i].value
for i in range(lp.nVariables)])
violation = beta - np.dot(alpha, sol)
if debug_print:
print(me, 'Beta: ', beta)
print(me, 'alpha: ', alpha)
print(me, 'Violation of cut: ', violation)
if np.abs(violation) > 10**(-eps):
return [(alpha, beta)]
print('No violated cuts found solving CGLP', violation)
return []
def disp_relaxation(f, A, b, cuts = [], sol = None, disj = [], filename = None):
#TODO: Check sense of inequalities by looking explicitly at
# lp.constraintsUpper and lp.constraintsLower
p = Polyhedron2D(A = A, b = b)
f.add_polyhedron(p, label = 'Polyhedron $P$')
f.set_xlim(p.xlim)
f.set_ylim(p.ylim)
pI = p.make_integer_hull()
f.add_polyhedron(pI, show_int_points = True, color = 'red',
linestyle = 'dashed',
label = 'Convex hull of integer points')
for (coeff, r) in cuts:
f.add_line(coeff, r, p.xlim, p.ylim, color = 'green', linestyle = 'dashed')
for (coeff, r) in disj:
f.add_line(coeff, r, p.xlim, p.ylim, color = 'red', linestyle = 'dashed')
f.add_line(coeff, r+1, p.xlim, p.ylim, color = 'red', linestyle = 'dashed')
if sol is not None:
f.add_point(sol, radius = .05)
f.show(filename = filename)
def solve(m, whichCuts = [], use_cglp = False, debug_print = False, eps = EPS,
max_iter = 100, max_cuts = 10, display = False, filename = None):
if not isinstance(m, MILPInstance):
print("Invalid first parameter: Must be of type MILPInstance")
exit
if not DISPLAY_ENABLED:
display = False
else:
f = Figure()
if m.lp.nCols > 2 or m.A is None:
display = False
m.lp.logLevel = 0
#Include bounds explicitly in the constraint matrix for display and for
#use in cut generators.
infinity = m.lp.getCoinInfinity()
if m.sense == '<=':
b = m.lp.constraintsUpper.copy()
mult = -1.0
else:
b = m.lp.constraintsLower.copy()
mult = 1.0
A = m.A.copy()
for i in range(m.lp.nCols):
e = np.zeros((1, m.lp.nCols))
if m.lp.variablesUpper[i] < infinity:
b.resize(b.size+1, refcheck = False)
e[0, i] = -mult
b[-1] = -mult*m.lp.variablesUpper[i]
A = np.vstack((A, e))
if m.lp.variablesLower[i] > -infinity:
b.resize(b.size+1, refcheck = False)
e[0, i] = mult
b[-1] = mult*m.lp.variablesLower[i]
A = np.vstack((A, e))
m.A = A
m.b = b
if display and filename is not None:
disp_relaxation(f, m.A, m.b, filename = filename+'.png')
elif display:
disp_relaxation(f, m.A, m.b)
disj = []
prev_sol = np.zeros((1, m.lp.nCols))
for i in range(max_iter):
print('Iteration ', i)
m.lp.primal(startFinishOptions = 'x')
print('Current bound:', m.lp.objectiveValue)
#Binv = np.zeros(shape = (lp.nConstraints, lp.nConstraints))
#for i in range(lp.nVariables, lp.nVariables+lp.nConstraints):
# lp.getBInvACol(i, Binv[i-lp.nVariables,:])
#rhs = lp.rhs
if m.sense == '<=':
rhs = np.dot(m.lp.basisInverse, m.lp.constraintsUpper)
else:
rhs = np.dot(m.lp.basisInverse, m.lp.constraintsLower)
sol = m.lp.primalVariableSolution['x']
if debug_print:
print('Current basis inverse:')
print(m.lp.basisInverse)
print('Condition number of basis inverse',
np.around(np.linalg.cond(m.lp.basisInverse)))
print('Current tableaux:')
print(m.lp.tableau)
print('Current right hand side:\n', rhs)
#print('Dual solution:', m.lp.dualConstraintSolution)
#print lp.rhs
print('Current solution: ', sol)
if (sol - prev_sol).any():
prev_sol = sol
else:
print ("Solution repeated, stalling detected")
print ("Exiting")
break
if isInt(sol[m.integerIndices], eps):
print('Integer solution found!')
break
if np.around(np.linalg.cond(m.lp.basisInverse)) >= 10**32:
print ("Condition number of the basis matrix exceeds 10^32")
print ("Exiting")
break
cuts = []
if disj == []:
for (cg, args) in whichCuts:
tmp_cuts, tmp_disj = cg(m, **args, eps = eps)
cuts += tmp_cuts
disj += tmp_disj
cur_num_cuts = len(cuts)
if use_cglp:
if len(disj) > 0:
for d in disj:
cuts += disjunctionToCut(m, d[0], d[1], eps = eps)
if cuts == []:
if disj == []:
print('No cuts found and terminating!')
break
else:
print('No cuts found but continuing!')
if display and filename is not None:
disp_relaxation(f, m.A, m.b, cuts, sol, disj,
filename = filename+str(i)+'.png')
elif display:
disp_relaxation(f, m.A, m.b, cuts, sol, disj)
if len(cuts) == cur_num_cuts:
disj = []
for (coeff, r) in cuts[:max_cuts]:
#TODO sort cuts by degree of violation
if m.sense == '<=':
coeff = np.floor(coeff*(10**eps))/(10**eps)
r = np.ceil(r*(10**eps))/(10**eps)
print('Adding cut: ', coeff, '<=', r)
m.lp += CyLPArray(coeff) * m.x <= r
else:
coeff = np.ceil(coeff*(10**eps))/(10**eps)
r = np.floor(r*(10**eps))/(10**eps)
print('Adding cut: ', coeff, '>=', r)
m.lp += CyLPArray(coeff) * m.x >= r
m.A = np.vstack((m.A, np.array(coeff)))
m.b.resize(m.b.size+1, refcheck = False)
m.b[-1] = r
if display:
disp_relaxation(f, m.A, m.b)
if __name__ == '__main__':
solve(MILPInstance(module_name = 'coinor.cuppy.examples.MIP6'),
whichCuts = [(gomoryMixedIntegerCut, {})],
display = True, debug_print = True, use_cglp = False)
Functions
def disjunctionToCut(m, pi, pi0, debug_print=False, use_cylp=True, eps=5)-
Generate the most violated valid inequality from a given disjunction
Expand source code
def disjunctionToCut(m, pi, pi0, debug_print = False, use_cylp = True, eps = EPS): '''Generate the most violated valid inequality from a given disjunction''' me = "cglp_cuts: " lp = m.lp sol = lp.primalVariableSolution['x'] if debug_print: print(me, "constraints sense = ", m.sense) print(me, "matrix = ") print(m.A) print(me, "rhs = ", m.b) print(me, "vars lower bounds = ", lp.variablesLower) print(me, "vars upper bounds = ", lp.variablesUpper) print(me, "objective = ", lp.objective) print(me, "current solution = ", sol) print(me, "pi = ", pi) print(me, "pi0 = ", pi0) ############################################################################ ## There are two given LPs: ## s.t. Ax >= b s.t. Ax >= b ## -pi.x >= -pi_0 pi.x >= pi_0+1 ## A, b, c, pi, pi_0 are given ## ## CGLP: alpha.x >= beta should be valid for both LPs above ## ## min alpha.x* - beta ## uA - u0.pi = alpha ## vA + v0.pi = alpha ## ub - u0.pi_0 >= beta ## vb + v0.(pi_0 + 1) >= beta ## u0 + v0 = 1 ## u, v, u0, v0 >= 0 ## if min value comes out < 0, then (alpha.x >= beta) is a cut. ############################################################################ pi = CyLPArray(pi) Atran = m.A.transpose() b = CyLPArray(m.b) numRows, numCols = m.A.shape if use_cylp: sp = CyLPModel() u = sp.addVariable('u', numRows, isInt = False) v = sp.addVariable('v', numRows, isInt = False) u0 = sp.addVariable('u0', 1, isInt = False) v0 = sp.addVariable('v0', 1, isInt = False) alpha = sp.addVariable('alpha', lp.nVariables, isInt = False) beta = sp.addVariable('beta', 1, isInt = False) #This should be as simple as this, but it doesn't work. #Maybe a bug in CyLP? #sp += alpha - Atran*u - pi*u0 == 0 #sp += alpha - Atran*v + pi*v0 == 0 for i in range(numCols): sp += alpha[i] - sum(Atran[i,j]*u[j] for j in range(numRows)) - pi[i]*u0 == 0 for i in range(numCols): sp += alpha[i] - sum(Atran[i,j]*v[j] for j in range(numRows)) + pi[i]*v0 == 0 if m.sense == '<=': sp += beta - b*u - pi0*u0 >= 0 sp += beta - b*v + (pi0 + 1)*v0 >= 0 else: sp += beta - b*u - pi0*u0 <= 0 sp += beta - b*v + (pi0 + 1)*v0 <= 0 sp += u0 + v0 == 1 sp += u >= 0 sp += v >= 0 sp += u0 >= 0 sp += v0 >= 0 if m.sense == '<=': sp.objective = sum(-sol[i]*alpha[i] for i in range(numCols)) + beta else: #This direction is not debugged sp.objective = sum(sol[i]*alpha[i] for i in range(numCols)) - beta cglp = CyClpSimplex(sp) # If we want to solve it as an MILP # cglp = CyClpSimplex(sp).getCbcModel() #cglp.writeLp('lp.lp') cglp.logLevel = 0 cglp.primal(startFinishOptions = 'x') # Solve as MILP # cglp.solve() beta = cglp.primalVariableSolution['beta'][0] alpha = cglp.primalVariableSolution['alpha'] u = cglp.primalVariableSolution['u'] v = cglp.primalVariableSolution['v'] u0 = cglp.primalVariableSolution['u0'][0] v0 = cglp.primalVariableSolution['v0'][0] if debug_print: print(me, 'Objective Value: ', cglp.objectiveValue) if debug_print: print(me, 'u: ', u) print(me, 'v: ', v) print(me, 'u0: ', u0) print(me, 'v0: ', v0) else: CG = AbstractModel() CG.u = Var(list(range(numRows)), domain=NonNegativeReals, bounds = (0.0, None)) CG.v = Var(list(range(numRows)), domain=NonNegativeReals, bounds = (0.0, None)) CG.u0 = Var(domain=NonNegativeReals, bounds = (0.0, None)) CG.v0 = Var(domain=NonNegativeReals, bounds = (0.0, None)) CG.alpha = Var(list(range(numRows)), domain=Reals, bounds = (None, None)) CG.beta = Var(domain=Reals, bounds = (None, None)) ## Constraints def pi_rule_left(CG, i): x = float(pi[i]) return(sum(Atran[i, j]*CG.u[j] for j in range(numRows)) - x*CG.u0 - CG.alpha[i] == 0.0) CG.pi_rule_left = Constraint(list(range(numCols)), rule=pi_rule_left) def pi_rule_right(CG, i): x = float(pi[i]) return(sum(Atran[i, j]*CG.v[j] for j in range(numRows)) + x*CG.v0 - CG.alpha[i] == 0.0) CG.pi_rule_right = Constraint(list(range(numCols)), rule=pi_rule_right) if m.sense == '<=': def pi0_rule_left(CG): return(sum(b[j]*CG.u[j] for j in range(numRows)) - pi0*CG.u0 - CG.beta <= 0.0) CG.pi0_rule_left = Constraint(rule=pi0_rule_left) def pi0_rule_right(CG): return(sum(b[j]*CG.v[j] for j in range(numRows)) + (pi0 + 1)*CG.v0 - CG.beta <= 0.0) CG.pi0_rule_right = Constraint(rule=pi0_rule_right) else: def pi0_rule_left(CG): return(sum(b[j]*CG.u[j] for j in range(numRows)) - pi0*CG.u0 - CG.beta >= 0.0) CG.pi0_rule_left = Constraint(rule=pi0_rule_left) def pi0_rule_right(CG): return(sum(b[j]*CG.v[j] for j in range(numRows)) + (pi0 + 1)*CG.v0 - CG.beta >= 0.0) CG.pi0_rule_right = Constraint(rule=pi0_rule_right) def normalization_rule(CG): return(CG.u0 + CG.v0 == 1.0) CG.normalization_rule = Constraint(rule=normalization_rule) def objective_rule(CG): return(sum(sol[i]*CG.alpha[i] for i in range(numCols)) - CG.beta) if m.sense == '<=': CG.objective = Objective(sense=maximize, rule=objective_rule) else: CG.objective = Objective(sense=minimize, rule=objective_rule) opt = SolverFactory("cbc") instance = CG.create_instance() #instance.pprint() #instance.write("foo.nl", format = "nl") #opt.options['bonmin.bb_log_level'] = 5 #opt.options['bonmin.bb_log_interval'] = 1 results = opt.solve(instance, tee=False) #results = opt.solve(instance) instance.solutions.load_from(results) beta = instance.beta.value alpha = np.array([instance.alpha[i].value for i in range(lp.nVariables)]) violation = beta - np.dot(alpha, sol) if debug_print: print(me, 'Beta: ', beta) print(me, 'alpha: ', alpha) print(me, 'Violation of cut: ', violation) if np.abs(violation) > 10**(-eps): return [(alpha, beta)] print('No violated cuts found solving CGLP', violation) return [] def disp_relaxation(f, A, b, cuts=[], sol=None, disj=[], filename=None)-
Expand source code
def disp_relaxation(f, A, b, cuts = [], sol = None, disj = [], filename = None): #TODO: Check sense of inequalities by looking explicitly at # lp.constraintsUpper and lp.constraintsLower p = Polyhedron2D(A = A, b = b) f.add_polyhedron(p, label = 'Polyhedron $P$') f.set_xlim(p.xlim) f.set_ylim(p.ylim) pI = p.make_integer_hull() f.add_polyhedron(pI, show_int_points = True, color = 'red', linestyle = 'dashed', label = 'Convex hull of integer points') for (coeff, r) in cuts: f.add_line(coeff, r, p.xlim, p.ylim, color = 'green', linestyle = 'dashed') for (coeff, r) in disj: f.add_line(coeff, r, p.xlim, p.ylim, color = 'red', linestyle = 'dashed') f.add_line(coeff, r+1, p.xlim, p.ylim, color = 'red', linestyle = 'dashed') if sol is not None: f.add_point(sol, radius = .05) f.show(filename = filename) def getFraction(x, eps=5)-
Return the fraction part of x: x - floor(x)
Expand source code
def getFraction(x, eps = EPS): 'Return the fraction part of x: x - floor(x)' # return x - floor(x) return np.around(x, decimals = eps) - floor(np.around(x, decimals = eps)) def gomoryMixedIntegerCut(m, rowInds=None, eps=5, debug_print=False)-
Return the Gomory mixed integer cut of rows in
rowIndsof lp (a CyClpSimplex object)Expand source code
def gomoryMixedIntegerCut(m, rowInds = None, eps = EPS, debug_print = False): '''Return the Gomory mixed integer cut of rows in ``rowInds`` of lp (a CyClpSimplex object)''' cuts = [] lp = m.lp sol = lp.primalVariableSolution['x'] if rowInds is None: rowInds = list(range(lp.nConstraints)) for row in rowInds: basicVarInd = lp.basicVariables[row] if (basicVarInd in m.integerIndices) and (not isInt(sol[basicVarInd], eps)): f0 = getFraction(sol[basicVarInd], eps) f = [] for i in range(lp.nVariables): if i in lp.basicVariables: #This is to try to avoid getting very small numbers that #should be zero f.append(0) else: f.append(getFraction(lp.tableau[row, i], eps)) pi = np.array([old_div(f[j],f0) if f[j] <= f0 else old_div((1-f[j]),(1-f0)) for j in range(lp.nVariables)]) pi_slacks = np.array([old_div(x,f0) if x > 0 else old_div(-x,(1-f0)) for x in lp.tableau[row, lp.nVariables:]]) pi -= pi_slacks * lp.coefMatrix pi0 = (1 - np.dot(pi_slacks, lp.constraintsUpper) if m.sense == '<=' else 1 + np.dot(pi_slacks, lp.constraintsUpper)) if m.sense == '>=': cuts.append((pi, pi0)) else: cuts.append((-pi, -pi0)) return cuts, [] def isInt(x, eps=5)-
Return True if x is an integer, or if x is a numpy array with all integer elements, False otherwise
Expand source code
def isInt(x, eps = EPS): ''' Return True if x is an integer, or if x is a numpy array with all integer elements, False otherwise ''' if isinstance(x, (int, float)): return abs(floor(x + 10**(-eps)) - x) < 10**(-eps) return (np.abs(np.around(x) - x) < 10**(-eps)).all() def liftAndProject(m, rowInds=None, eps=5, debug_print=False)-
Return the lift-and-project associated with variables that are basic in rows in
rowIndsof lp (a CyClpSimplex object)Expand source code
def liftAndProject(m, rowInds = None, eps = EPS, debug_print = False): '''Return the lift-and-project associated with variables that are basic in rows in ``rowInds`` of lp (a CyClpSimplex object)''' cuts = [] lp = m.lp sol = lp.primalVariableSolution['x'] if rowInds is None: rowInds = list(range(lp.nConstraints)) for row in rowInds: basicVarInd = lp.basicVariables[row] if (basicVarInd in m.integerIndices) and (not isInt(sol[basicVarInd], eps)): e = np.zeros(lp.nCols) e[basicVarInd] = 1 #Call function for solving CGLP for the associated variable #disjunction (disjunction is "<=") cuts += disjunctionToCut(m, e, floor(sol[basicVarInd]), eps = eps, debug_print = debug_print) return cuts, [] def solve(m, whichCuts=[], use_cglp=False, debug_print=False, eps=5, max_iter=100, max_cuts=10, display=False, filename=None)-
Expand source code
def solve(m, whichCuts = [], use_cglp = False, debug_print = False, eps = EPS, max_iter = 100, max_cuts = 10, display = False, filename = None): if not isinstance(m, MILPInstance): print("Invalid first parameter: Must be of type MILPInstance") exit if not DISPLAY_ENABLED: display = False else: f = Figure() if m.lp.nCols > 2 or m.A is None: display = False m.lp.logLevel = 0 #Include bounds explicitly in the constraint matrix for display and for #use in cut generators. infinity = m.lp.getCoinInfinity() if m.sense == '<=': b = m.lp.constraintsUpper.copy() mult = -1.0 else: b = m.lp.constraintsLower.copy() mult = 1.0 A = m.A.copy() for i in range(m.lp.nCols): e = np.zeros((1, m.lp.nCols)) if m.lp.variablesUpper[i] < infinity: b.resize(b.size+1, refcheck = False) e[0, i] = -mult b[-1] = -mult*m.lp.variablesUpper[i] A = np.vstack((A, e)) if m.lp.variablesLower[i] > -infinity: b.resize(b.size+1, refcheck = False) e[0, i] = mult b[-1] = mult*m.lp.variablesLower[i] A = np.vstack((A, e)) m.A = A m.b = b if display and filename is not None: disp_relaxation(f, m.A, m.b, filename = filename+'.png') elif display: disp_relaxation(f, m.A, m.b) disj = [] prev_sol = np.zeros((1, m.lp.nCols)) for i in range(max_iter): print('Iteration ', i) m.lp.primal(startFinishOptions = 'x') print('Current bound:', m.lp.objectiveValue) #Binv = np.zeros(shape = (lp.nConstraints, lp.nConstraints)) #for i in range(lp.nVariables, lp.nVariables+lp.nConstraints): # lp.getBInvACol(i, Binv[i-lp.nVariables,:]) #rhs = lp.rhs if m.sense == '<=': rhs = np.dot(m.lp.basisInverse, m.lp.constraintsUpper) else: rhs = np.dot(m.lp.basisInverse, m.lp.constraintsLower) sol = m.lp.primalVariableSolution['x'] if debug_print: print('Current basis inverse:') print(m.lp.basisInverse) print('Condition number of basis inverse', np.around(np.linalg.cond(m.lp.basisInverse))) print('Current tableaux:') print(m.lp.tableau) print('Current right hand side:\n', rhs) #print('Dual solution:', m.lp.dualConstraintSolution) #print lp.rhs print('Current solution: ', sol) if (sol - prev_sol).any(): prev_sol = sol else: print ("Solution repeated, stalling detected") print ("Exiting") break if isInt(sol[m.integerIndices], eps): print('Integer solution found!') break if np.around(np.linalg.cond(m.lp.basisInverse)) >= 10**32: print ("Condition number of the basis matrix exceeds 10^32") print ("Exiting") break cuts = [] if disj == []: for (cg, args) in whichCuts: tmp_cuts, tmp_disj = cg(m, **args, eps = eps) cuts += tmp_cuts disj += tmp_disj cur_num_cuts = len(cuts) if use_cglp: if len(disj) > 0: for d in disj: cuts += disjunctionToCut(m, d[0], d[1], eps = eps) if cuts == []: if disj == []: print('No cuts found and terminating!') break else: print('No cuts found but continuing!') if display and filename is not None: disp_relaxation(f, m.A, m.b, cuts, sol, disj, filename = filename+str(i)+'.png') elif display: disp_relaxation(f, m.A, m.b, cuts, sol, disj) if len(cuts) == cur_num_cuts: disj = [] for (coeff, r) in cuts[:max_cuts]: #TODO sort cuts by degree of violation if m.sense == '<=': coeff = np.floor(coeff*(10**eps))/(10**eps) r = np.ceil(r*(10**eps))/(10**eps) print('Adding cut: ', coeff, '<=', r) m.lp += CyLPArray(coeff) * m.x <= r else: coeff = np.ceil(coeff*(10**eps))/(10**eps) r = np.floor(r*(10**eps))/(10**eps) print('Adding cut: ', coeff, '>=', r) m.lp += CyLPArray(coeff) * m.x >= r m.A = np.vstack((m.A, np.array(coeff))) m.b.resize(m.b.size+1, refcheck = False) m.b[-1] = r if display: disp_relaxation(f, m.A, m.b)