- 添加Blender插件核心文件:__init__.py、ui.py、property.py、preference.py - 添加插件工具模块:g.py、loop.py、generate_loop.py、const.py、op.py - 添加翻译工具:utils/trans.py - 添加PuLP线性规划库及其依赖文件,包括CBC求解器二进制文件 - 添加.gitignore和VSCode配置文件
1674 lines
62 KiB
Python
1674 lines
62 KiB
Python
"""
|
|
Tests for pulp
|
|
"""
|
|
import os
|
|
import tempfile
|
|
|
|
from pulp.constants import PulpError
|
|
from pulp.apis import *
|
|
from pulp import LpVariable, LpProblem, lpSum, LpConstraintVar, LpFractionConstraint
|
|
from pulp import constants as const
|
|
from pulp.tests.bin_packing_problem import create_bin_packing_problem
|
|
from pulp.utilities import makeDict
|
|
import functools
|
|
import unittest
|
|
|
|
try:
|
|
import gurobipy as gp
|
|
except ImportError:
|
|
gp = None
|
|
|
|
# from: http://lpsolve.sourceforge.net/5.5/mps-format.htm
|
|
EXAMPLE_MPS_RHS56 = """NAME TESTPROB
|
|
ROWS
|
|
N COST
|
|
L LIM1
|
|
G LIM2
|
|
E MYEQN
|
|
COLUMNS
|
|
XONE COST 1 LIM1 1
|
|
XONE LIM2 1
|
|
YTWO COST 4 LIM1 1
|
|
YTWO MYEQN -1
|
|
ZTHREE COST 9 LIM2 1
|
|
ZTHREE MYEQN 1
|
|
RHS
|
|
RHS1 LIM1 5 LIM2 10
|
|
RHS1 MYEQN 7
|
|
BOUNDS
|
|
UP BND1 XONE 4
|
|
LO BND1 YTWO -1
|
|
UP BND1 YTWO 1
|
|
ENDATA
|
|
"""
|
|
|
|
|
|
def gurobi_test(test_item):
|
|
@functools.wraps(test_item)
|
|
def skip_wrapper(*args, **kwargs):
|
|
if gp is None:
|
|
raise unittest.SkipTest("No gurobipy, can't check license")
|
|
try:
|
|
test_item(*args, **kwargs)
|
|
except gp.GurobiError as ge:
|
|
# Skip the test if the failure was due to licensing
|
|
if ge.errno == gp.GRB.Error.SIZE_LIMIT_EXCEEDED:
|
|
raise unittest.SkipTest("Size-limited Gurobi license")
|
|
if ge.errno == gp.GRB.Error.NO_LICENSE:
|
|
raise unittest.SkipTest("No Gurobi license")
|
|
# Otherwise, let the error go through as-is
|
|
raise
|
|
|
|
return skip_wrapper
|
|
|
|
|
|
def dumpTestProblem(prob):
|
|
try:
|
|
prob.writeLP("debug.lp")
|
|
prob.writeMPS("debug.mps")
|
|
except:
|
|
print("(Failed to write the test problem.)")
|
|
|
|
|
|
class BaseSolverTest:
|
|
class PuLPTest(unittest.TestCase):
|
|
solveInst = None
|
|
|
|
def setUp(self):
|
|
self.solver = self.solveInst(msg=False)
|
|
if not self.solver.available():
|
|
self.skipTest(f"solver {self.solveInst} not available")
|
|
|
|
def tearDown(self):
|
|
for ext in ["mst", "log", "lp", "mps", "sol"]:
|
|
filename = f"{self._testMethodName}.{ext}"
|
|
try:
|
|
os.remove(filename)
|
|
except:
|
|
pass
|
|
pass
|
|
|
|
def test_pulp_001(self):
|
|
"""
|
|
Test that a variable is deleted when it is suptracted to 0
|
|
"""
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
c1 = x + y <= 5
|
|
c2 = c1 + z - z
|
|
print("\t Testing zero subtraction")
|
|
assert str(c2) # will raise an exception
|
|
|
|
def test_pulp_009(self):
|
|
# infeasible
|
|
prob = LpProblem("test09", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += (
|
|
lpSum([v for v in [x] if False]) >= 5,
|
|
"c1",
|
|
) # this is a 0 >=5 constraint
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
print("\t Testing inconsistent lp solution")
|
|
# this was a problem with use_mps=false
|
|
if self.solver.__class__ in [PULP_CBC_CMD, COIN_CMD]:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusInfeasible],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
use_mps=False,
|
|
)
|
|
elif self.solver.__class__ in [CHOCO_CMD, MIPCL_CMD]:
|
|
# this error is not detected with mps and choco, MIPCL_CMD can only use mps files
|
|
pass
|
|
else:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[
|
|
const.LpStatusInfeasible,
|
|
const.LpStatusNotSolved,
|
|
const.LpStatusUndefined,
|
|
],
|
|
)
|
|
|
|
def test_pulp_010(self):
|
|
# Continuous
|
|
prob = LpProblem("test010", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
print("\t Testing continuous LP solution")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_pulp_011(self):
|
|
# Continuous Maximisation
|
|
prob = LpProblem("test011", const.LpMaximize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
print("\t Testing maximize continuous LP solution")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: 1, z: 8, w: 0}
|
|
)
|
|
|
|
def test_pulp_012(self):
|
|
# Unbounded
|
|
prob = LpProblem("test012", const.LpMaximize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z + w, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
print("\t Testing unbounded continuous LP solution")
|
|
if self.solver.__class__ in [GUROBI, CPLEX_CMD, YAPOSIB, MOSEK, COPT]:
|
|
# These solvers report infeasible or unbounded
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusInfeasible, const.LpStatusUnbounded],
|
|
)
|
|
elif self.solver.__class__ in [COINMP_DLL, MIPCL_CMD]:
|
|
# COINMP_DLL is just plain wrong
|
|
# also MIPCL_CMD
|
|
print("\t\t Error in CoinMP and MIPCL_CMD: reports Optimal")
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusOptimal])
|
|
elif self.solver.__class__ is GLPK_CMD:
|
|
# GLPK_CMD Does not report unbounded problems, correctly
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusUndefined])
|
|
elif self.solver.__class__ in [GUROBI_CMD, SCIP_CMD, FSCIP_CMD, SCIP_PY]:
|
|
# GUROBI_CMD has a very simple interface
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusNotSolved])
|
|
elif self.solver.__class__ in [CHOCO_CMD]:
|
|
# choco bounds all variables. Would not return unbounded status
|
|
pass
|
|
else:
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusUnbounded])
|
|
|
|
def test_pulp_013(self):
|
|
# Long name
|
|
prob = LpProblem("test013", const.LpMinimize)
|
|
x = LpVariable("x" * 120, 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
print("\t Testing Long Names")
|
|
if self.solver.__class__ in [
|
|
CPLEX_CMD,
|
|
GLPK_CMD,
|
|
GUROBI_CMD,
|
|
MIPCL_CMD,
|
|
SCIP_CMD,
|
|
FSCIP_CMD,
|
|
SCIP_PY,
|
|
HiGHS,
|
|
HiGHS_CMD,
|
|
XPRESS,
|
|
XPRESS_CMD,
|
|
]:
|
|
try:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
except PulpError:
|
|
# these solvers should raise an error'
|
|
pass
|
|
else:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
|
|
def test_pulp_014(self):
|
|
# repeated name
|
|
prob = LpProblem("test014", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("x", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
print("\t Testing repeated Names")
|
|
if self.solver.__class__ in [
|
|
COIN_CMD,
|
|
COINMP_DLL,
|
|
PULP_CBC_CMD,
|
|
CPLEX_CMD,
|
|
CPLEX_PY,
|
|
GLPK_CMD,
|
|
GUROBI_CMD,
|
|
CHOCO_CMD,
|
|
MIPCL_CMD,
|
|
MOSEK,
|
|
SCIP_CMD,
|
|
FSCIP_CMD,
|
|
SCIP_PY,
|
|
HiGHS,
|
|
HiGHS_CMD,
|
|
XPRESS,
|
|
XPRESS_CMD,
|
|
XPRESS_PY,
|
|
]:
|
|
try:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
except PulpError:
|
|
# these solvers should raise an error
|
|
pass
|
|
else:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
|
|
def test_pulp_015(self):
|
|
# zero constraint
|
|
prob = LpProblem("test015", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
prob += lpSum([0, 0]) <= 0, "c5"
|
|
print("\t Testing zero constraint")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_pulp_016(self):
|
|
# zero objective
|
|
prob = LpProblem("test016", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
prob += lpSum([0, 0]) <= 0, "c5"
|
|
print("\t Testing zero objective")
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusOptimal])
|
|
|
|
def test_pulp_017(self):
|
|
# variable as objective
|
|
prob = LpProblem("test017", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob.setObjective(x)
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
prob += lpSum([0, 0]) <= 0, "c5"
|
|
print("\t Testing LpVariable (not LpAffineExpression) objective")
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusOptimal])
|
|
|
|
def test_pulp_018(self):
|
|
# Long name in lp
|
|
prob = LpProblem("test018", const.LpMinimize)
|
|
x = LpVariable("x" * 90, 0, 4)
|
|
y = LpVariable("y" * 90, -1, 1)
|
|
z = LpVariable("z" * 90, 0)
|
|
w = LpVariable("w" * 90, 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
if self.solver.__class__ in [PULP_CBC_CMD, COIN_CMD]:
|
|
print("\t Testing Long lines in LP")
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
use_mps=False,
|
|
)
|
|
|
|
def test_pulp_019(self):
|
|
# divide
|
|
prob = LpProblem("test019", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += (2 * x + 2 * y).__div__(2.0) <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
print("\t Testing LpAffineExpression divide")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_pulp_020(self):
|
|
# MIP
|
|
prob = LpProblem("test020", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, None, const.LpInteger)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7.5, "c3"
|
|
print("\t Testing MIP solution")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 3, y: -0.5, z: 7}
|
|
)
|
|
|
|
def test_pulp_021(self):
|
|
# MIP with floats in objective
|
|
prob = LpProblem("test021", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, None, const.LpInteger)
|
|
prob += 1.1 * x + 4.1 * y + 9.1 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7.5, "c3"
|
|
print("\t Testing MIP solution with floats in objective")
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 3, y: -0.5, z: 7},
|
|
objective=64.95,
|
|
)
|
|
|
|
def test_pulp_022(self):
|
|
# Initial value
|
|
prob = LpProblem("test022", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, None, const.LpInteger)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7.5, "c3"
|
|
x.setInitialValue(3)
|
|
y.setInitialValue(-0.5)
|
|
z.setInitialValue(7)
|
|
if self.solver.name in [
|
|
"GUROBI",
|
|
"GUROBI_CMD",
|
|
"CPLEX_CMD",
|
|
"CPLEX_PY",
|
|
"COPT",
|
|
]:
|
|
self.solver.optionsDict["warmStart"] = True
|
|
print("\t Testing Initial value in MIP solution")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 3, y: -0.5, z: 7}
|
|
)
|
|
|
|
def test_pulp_023(self):
|
|
# Initial value (fixed)
|
|
prob = LpProblem("test023", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, None, const.LpInteger)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7.5, "c3"
|
|
solution = {x: 4, y: -0.5, z: 7}
|
|
for v in [x, y, z]:
|
|
v.setInitialValue(solution[v])
|
|
v.fixValue()
|
|
self.solver.optionsDict["warmStart"] = True
|
|
print("\t Testing fixing value in MIP solution")
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusOptimal], solution)
|
|
|
|
def test_pulp_030(self):
|
|
# relaxed MIP
|
|
prob = LpProblem("test030", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, None, const.LpInteger)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7.5, "c3"
|
|
self.solver.mip = 0
|
|
print("\t Testing MIP relaxation")
|
|
if self.solver.__class__ in [
|
|
GUROBI_CMD,
|
|
CHOCO_CMD,
|
|
MIPCL_CMD,
|
|
SCIP_CMD,
|
|
FSCIP_CMD,
|
|
SCIP_PY,
|
|
]:
|
|
# these solvers do not let the problem be relaxed
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 3.0, y: -0.5, z: 7}
|
|
)
|
|
else:
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 3.5, y: -1, z: 6.5}
|
|
)
|
|
|
|
def test_pulp_040(self):
|
|
# Feasibility only
|
|
prob = LpProblem("test040", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, None, const.LpInteger)
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7.5, "c3"
|
|
print("\t Testing feasibility problem (no objective)")
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusOptimal])
|
|
|
|
def test_pulp_050(self):
|
|
# Infeasible
|
|
prob = LpProblem("test050", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, 10)
|
|
prob += x + y <= 5.2, "c1"
|
|
prob += x + z >= 10.3, "c2"
|
|
prob += -y + z == 17.5, "c3"
|
|
print("\t Testing an infeasible problem")
|
|
if self.solver.__class__ is GLPK_CMD:
|
|
# GLPK_CMD return codes are not informative enough
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusUndefined])
|
|
elif self.solver.__class__ in [GUROBI_CMD, FSCIP_CMD]:
|
|
# GUROBI_CMD Does not solve the problem
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusNotSolved])
|
|
else:
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusInfeasible])
|
|
|
|
def test_pulp_060(self):
|
|
# Integer Infeasible
|
|
prob = LpProblem("test060", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4, const.LpInteger)
|
|
y = LpVariable("y", -1, 1, const.LpInteger)
|
|
z = LpVariable("z", 0, 10, const.LpInteger)
|
|
prob += x + y <= 5.2, "c1"
|
|
prob += x + z >= 10.3, "c2"
|
|
prob += -y + z == 7.4, "c3"
|
|
print("\t Testing an integer infeasible problem")
|
|
if self.solver.__class__ in [GLPK_CMD, COIN_CMD, PULP_CBC_CMD, MOSEK]:
|
|
# GLPK_CMD returns InfeasibleOrUnbounded
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusInfeasible, const.LpStatusUndefined],
|
|
)
|
|
elif self.solver.__class__ in [COINMP_DLL]:
|
|
# Currently there is an error in COINMP for problems where
|
|
# presolve eliminates too many variables
|
|
print("\t\t Error in CoinMP to be fixed, reports Optimal")
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusOptimal])
|
|
elif self.solver.__class__ in [GUROBI_CMD, FSCIP_CMD]:
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusNotSolved])
|
|
else:
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusInfeasible])
|
|
|
|
def test_pulp_061(self):
|
|
# Integer Infeasible
|
|
prob = LpProblem("sample", const.LpMaximize)
|
|
|
|
dummy = LpVariable("dummy")
|
|
c1 = LpVariable("c1", 0, 1, const.LpBinary)
|
|
c2 = LpVariable("c2", 0, 1, const.LpBinary)
|
|
|
|
prob += dummy
|
|
prob += c1 + c2 == 2
|
|
prob += c1 <= 0
|
|
print("\t Testing another integer infeasible problem")
|
|
if self.solver.__class__ in [GUROBI_CMD, SCIP_CMD, FSCIP_CMD, SCIP_PY]:
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusNotSolved])
|
|
elif self.solver.__class__ in [GLPK_CMD]:
|
|
# GLPK_CMD returns InfeasibleOrUnbounded
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusInfeasible, const.LpStatusUndefined],
|
|
)
|
|
else:
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusInfeasible])
|
|
|
|
def test_pulp_070(self):
|
|
# Column Based modelling of test_pulp_1
|
|
prob = LpProblem("test070", const.LpMinimize)
|
|
obj = LpConstraintVar("obj")
|
|
# constraints
|
|
a = LpConstraintVar("C1", const.LpConstraintLE, 5)
|
|
b = LpConstraintVar("C2", const.LpConstraintGE, 10)
|
|
c = LpConstraintVar("C3", const.LpConstraintEQ, 7)
|
|
|
|
prob.setObjective(obj)
|
|
prob += a
|
|
prob += b
|
|
prob += c
|
|
# Variables
|
|
x = LpVariable("x", 0, 4, const.LpContinuous, obj + a + b)
|
|
y = LpVariable("y", -1, 1, const.LpContinuous, 4 * obj + a - c)
|
|
z = LpVariable("z", 0, None, const.LpContinuous, 9 * obj + b + c)
|
|
print("\t Testing column based modelling")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6}
|
|
)
|
|
|
|
def test_pulp_075(self):
|
|
# Column Based modelling of test_pulp_1 with empty constraints
|
|
prob = LpProblem("test075", const.LpMinimize)
|
|
obj = LpConstraintVar("obj")
|
|
# constraints
|
|
a = LpConstraintVar("C1", const.LpConstraintLE, 5)
|
|
b = LpConstraintVar("C2", const.LpConstraintGE, 10)
|
|
c = LpConstraintVar("C3", const.LpConstraintEQ, 7)
|
|
|
|
prob.setObjective(obj)
|
|
prob += a
|
|
prob += b
|
|
prob += c
|
|
# Variables
|
|
x = LpVariable("x", 0, 4, const.LpContinuous, obj + b)
|
|
y = LpVariable("y", -1, 1, const.LpContinuous, 4 * obj - c)
|
|
z = LpVariable("z", 0, None, const.LpContinuous, 9 * obj + b + c)
|
|
if self.solver.__class__ in [CPLEX_CMD, COINMP_DLL, YAPOSIB, PYGLPK]:
|
|
print("\t Testing column based modelling with empty constraints")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6}
|
|
)
|
|
|
|
def test_pulp_080(self):
|
|
"""
|
|
Test the reporting of dual variables slacks and reduced costs
|
|
"""
|
|
prob = LpProblem("test080", const.LpMinimize)
|
|
x = LpVariable("x", 0, 5)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
c1 = x + y <= 5
|
|
c2 = x + z >= 10
|
|
c3 = -y + z == 7
|
|
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += c1, "c1"
|
|
prob += c2, "c2"
|
|
prob += c3, "c3"
|
|
|
|
if self.solver.__class__ in [
|
|
CPLEX_CMD,
|
|
COINMP_DLL,
|
|
PULP_CBC_CMD,
|
|
YAPOSIB,
|
|
PYGLPK,
|
|
]:
|
|
print("\t Testing dual variables and slacks reporting")
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
sol={x: 4, y: -1, z: 6},
|
|
reducedcosts={x: 0, y: 12, z: 0},
|
|
duals={"c1": 0, "c2": 1, "c3": 8},
|
|
slacks={"c1": 2, "c2": 0, "c3": 0},
|
|
)
|
|
|
|
def test_pulp_090(self):
|
|
# Column Based modelling of test_pulp_1 with a resolve
|
|
prob = LpProblem("test090", const.LpMinimize)
|
|
obj = LpConstraintVar("obj")
|
|
# constraints
|
|
a = LpConstraintVar("C1", const.LpConstraintLE, 5)
|
|
b = LpConstraintVar("C2", const.LpConstraintGE, 10)
|
|
c = LpConstraintVar("C3", const.LpConstraintEQ, 7)
|
|
|
|
prob.setObjective(obj)
|
|
prob += a
|
|
prob += b
|
|
prob += c
|
|
|
|
prob.setSolver(self.solver) # Variables
|
|
x = LpVariable("x", 0, 4, const.LpContinuous, obj + a + b)
|
|
y = LpVariable("y", -1, 1, const.LpContinuous, 4 * obj + a - c)
|
|
prob.resolve()
|
|
z = LpVariable("z", 0, None, const.LpContinuous, 9 * obj + b + c)
|
|
if self.solver.__class__ in [COINMP_DLL]:
|
|
print("\t Testing resolve of problem")
|
|
prob.resolve()
|
|
# difficult to check this is doing what we want as the resolve is
|
|
# overridden if it is not implemented
|
|
# test_pulp_Check(prob, self.solver, [const.LpStatusOptimal], {x:4, y:-1, z:6})
|
|
|
|
def test_pulp_100(self):
|
|
"""
|
|
Test the ability to sequentially solve a problem
|
|
"""
|
|
# set up a cubic feasible region
|
|
prob = LpProblem("test100", const.LpMinimize)
|
|
x = LpVariable("x", 0, 1)
|
|
y = LpVariable("y", 0, 1)
|
|
z = LpVariable("z", 0, 1)
|
|
|
|
obj1 = x + 0 * y + 0 * z
|
|
obj2 = 0 * x - 1 * y + 0 * z
|
|
prob += x <= 1, "c1"
|
|
|
|
if self.solver.__class__ in [COINMP_DLL, GUROBI]:
|
|
print("\t Testing Sequential Solves")
|
|
status = prob.sequentialSolve([obj1, obj2], solver=self.solver)
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[[const.LpStatusOptimal, const.LpStatusOptimal]],
|
|
sol={x: 0, y: 1},
|
|
status=status,
|
|
)
|
|
|
|
def test_pulp_110(self):
|
|
"""
|
|
Test the ability to use fractional constraints
|
|
"""
|
|
prob = LpProblem("test110", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
prob += LpFractionConstraint(x, z, const.LpConstraintEQ, 0.5, name="c5")
|
|
print("\t Testing fractional constraints")
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 10 / 3.0, y: -1 / 3.0, z: 20 / 3.0, w: 0},
|
|
)
|
|
|
|
def test_pulp_120(self):
|
|
"""
|
|
Test the ability to use Elastic constraints
|
|
"""
|
|
prob = LpProblem("test120", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w")
|
|
prob += x + 4 * y + 9 * z + w, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob.extend((w >= -1).makeElasticSubProblem())
|
|
print("\t Testing elastic constraints (no change)")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: -1}
|
|
)
|
|
|
|
def test_pulp_121(self):
|
|
"""
|
|
Test the ability to use Elastic constraints
|
|
"""
|
|
prob = LpProblem("test121", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w")
|
|
prob += x + 4 * y + 9 * z + w, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob.extend((w >= -1).makeElasticSubProblem(proportionFreeBound=0.1))
|
|
print("\t Testing elastic constraints (freebound)")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: -1.1}
|
|
)
|
|
|
|
def test_pulp_122(self):
|
|
"""
|
|
Test the ability to use Elastic constraints (penalty unchanged)
|
|
"""
|
|
prob = LpProblem("test122", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w")
|
|
prob += x + 4 * y + 9 * z + w, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob.extend((w >= -1).makeElasticSubProblem(penalty=1.1))
|
|
print("\t Testing elastic constraints (penalty unchanged)")
|
|
pulpTestCheck(
|
|
prob, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: -1.0}
|
|
)
|
|
|
|
def test_pulp_123(self):
|
|
"""
|
|
Test the ability to use Elastic constraints (penalty unbounded)
|
|
"""
|
|
prob = LpProblem("test123", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w")
|
|
prob += x + 4 * y + 9 * z + w, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob.extend((w >= -1).makeElasticSubProblem(penalty=0.9))
|
|
print("\t Testing elastic constraints (penalty unbounded)")
|
|
if self.solver.__class__ in [
|
|
COINMP_DLL,
|
|
GUROBI,
|
|
CPLEX_CMD,
|
|
YAPOSIB,
|
|
MOSEK,
|
|
COPT,
|
|
]:
|
|
# COINMP_DLL Does not report unbounded problems, correctly
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusInfeasible, const.LpStatusUnbounded],
|
|
)
|
|
elif self.solver.__class__ is GLPK_CMD:
|
|
# GLPK_CMD Does not report unbounded problems, correctly
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusUndefined])
|
|
elif self.solver.__class__ in [GUROBI_CMD, SCIP_CMD, FSCIP_CMD, SCIP_PY]:
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusNotSolved])
|
|
elif self.solver.__class__ in [CHOCO_CMD]:
|
|
# choco bounds all variables. Would not return unbounded status
|
|
pass
|
|
else:
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusUnbounded])
|
|
|
|
def test_msg_arg(self):
|
|
"""
|
|
Test setting the msg arg to True does not interfere with solve
|
|
"""
|
|
prob = LpProblem("test_msg_arg", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
data = prob.toDict()
|
|
var1, prob1 = LpProblem.fromDict(data)
|
|
x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"])
|
|
if self.solver.name in ["HiGHS"]:
|
|
# HiGHS has issues with displaying output in Ubuntu
|
|
return
|
|
self.solver.msg = True
|
|
pulpTestCheck(
|
|
prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_pulpTestAll(self):
|
|
"""
|
|
Test the availability of the function pulpTestAll
|
|
"""
|
|
print("\t Testing the availability of the function pulpTestAll")
|
|
from pulp import pulpTestAll
|
|
|
|
def test_export_dict_LP(self):
|
|
prob = LpProblem("test_export_dict_LP", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
data = prob.toDict()
|
|
var1, prob1 = LpProblem.fromDict(data)
|
|
x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"])
|
|
print("\t Testing continuous LP solution - export dict")
|
|
pulpTestCheck(
|
|
prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_export_dict_LP_no_obj(self):
|
|
prob = LpProblem("test_export_dict_LP_no_obj", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0, 0)
|
|
prob += x + y >= 5, "c1"
|
|
prob += x + z == 10, "c2"
|
|
prob += -y + z <= 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
data = prob.toDict()
|
|
var1, prob1 = LpProblem.fromDict(data)
|
|
x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"])
|
|
print("\t Testing export dict for LP")
|
|
pulpTestCheck(
|
|
prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: 1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_export_json_LP(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
filename = name + ".json"
|
|
prob.toJson(filename, indent=4)
|
|
var1, prob1 = LpProblem.fromJson(filename)
|
|
try:
|
|
os.remove(filename)
|
|
except:
|
|
pass
|
|
x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"])
|
|
print("\t Testing continuous LP solution - export JSON")
|
|
pulpTestCheck(
|
|
prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_export_dict_MIP(self):
|
|
import copy
|
|
|
|
prob = LpProblem("test_export_dict_MIP", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, None, const.LpInteger)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7.5, "c3"
|
|
data = prob.toDict()
|
|
data_backup = copy.deepcopy(data)
|
|
var1, prob1 = LpProblem.fromDict(data)
|
|
x, y, z = (var1[name] for name in ["x", "y", "z"])
|
|
print("\t Testing export dict MIP")
|
|
pulpTestCheck(
|
|
prob1, self.solver, [const.LpStatusOptimal], {x: 3, y: -0.5, z: 7}
|
|
)
|
|
# we also test that we have not modified the dictionary when importing it
|
|
self.assertDictEqual(data, data_backup)
|
|
|
|
def test_export_dict_max(self):
|
|
prob = LpProblem("test_export_dict_max", const.LpMaximize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
data = prob.toDict()
|
|
var1, prob1 = LpProblem.fromDict(data)
|
|
x, y, z, w = (var1[name] for name in ["x", "y", "z", "w"])
|
|
print("\t Testing maximize continuous LP solution")
|
|
pulpTestCheck(
|
|
prob1, self.solver, [const.LpStatusOptimal], {x: 4, y: 1, z: 8, w: 0}
|
|
)
|
|
|
|
def test_export_solver_dict_LP(self):
|
|
prob = LpProblem("test_export_dict_LP", const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
data = self.solver.toDict()
|
|
solver1 = getSolverFromDict(data)
|
|
print("\t Testing continuous LP solution - export solver dict")
|
|
pulpTestCheck(
|
|
prob, solver1, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_export_solver_json(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
self.solver.mip = True
|
|
logFilename = name + ".log"
|
|
if self.solver.name == "CPLEX_CMD":
|
|
self.solver.optionsDict = dict(
|
|
gapRel=0.1,
|
|
gapAbs=1,
|
|
maxMemory=1000,
|
|
maxNodes=1,
|
|
threads=1,
|
|
logPath=logFilename,
|
|
warmStart=True,
|
|
)
|
|
elif self.solver.name in ["GUROBI_CMD", "COIN_CMD", "PULP_CBC_CMD"]:
|
|
self.solver.optionsDict = dict(
|
|
gapRel=0.1, gapAbs=1, threads=1, logPath=logFilename, warmStart=True
|
|
)
|
|
filename = name + ".json"
|
|
self.solver.toJson(filename, indent=4)
|
|
solver1 = getSolverFromJson(filename)
|
|
try:
|
|
os.remove(filename)
|
|
except:
|
|
pass
|
|
print("\t Testing continuous LP solution - export solver JSON")
|
|
pulpTestCheck(
|
|
prob, solver1, [const.LpStatusOptimal], {x: 4, y: -1, z: 6, w: 0}
|
|
)
|
|
|
|
def test_timeLimit(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
self.solver.timeLimit = 20
|
|
# CHOCO has issues when given a time limit
|
|
print("\t Testing timeLimit argument")
|
|
if self.solver.name != "CHOCO_CMD":
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
|
|
def test_assignInvalidStatus(self):
|
|
print("\t Testing invalid status")
|
|
t = LpProblem("test")
|
|
Invalid = -100
|
|
self.assertRaises(const.PulpError, lambda: t.assignStatus(Invalid))
|
|
self.assertRaises(const.PulpError, lambda: t.assignStatus(0, Invalid))
|
|
|
|
def test_logPath(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
logFilename = name + ".log"
|
|
self.solver.optionsDict["logPath"] = logFilename
|
|
if self.solver.name in [
|
|
"CPLEX_PY",
|
|
"CPLEX_CMD",
|
|
"GUROBI",
|
|
"GUROBI_CMD",
|
|
"PULP_CBC_CMD",
|
|
"COIN_CMD",
|
|
]:
|
|
print("\t Testing logPath argument")
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
if not os.path.exists(logFilename):
|
|
raise PulpError(f"Test failed for solver: {self.solver}")
|
|
if not os.path.getsize(logFilename):
|
|
raise PulpError(f"Test failed for solver: {self.solver}")
|
|
|
|
def test_makeDict_behavior(self):
|
|
"""
|
|
Test if makeDict is returning the expected value.
|
|
"""
|
|
headers = [["A", "B"], ["C", "D"]]
|
|
values = [[1, 2], [3, 4]]
|
|
target = {"A": {"C": 1, "D": 2}, "B": {"C": 3, "D": 4}}
|
|
dict_with_default = makeDict(headers, values, default=0)
|
|
dict_without_default = makeDict(headers, values)
|
|
print("\t Testing makeDict general behavior")
|
|
self.assertEqual(dict_with_default, target)
|
|
self.assertEqual(dict_without_default, target)
|
|
|
|
def test_makeDict_default_value(self):
|
|
"""
|
|
Test if makeDict is returning a default value when specified.
|
|
"""
|
|
headers = [["A", "B"], ["C", "D"]]
|
|
values = [[1, 2], [3, 4]]
|
|
dict_with_default = makeDict(headers, values, default=0)
|
|
dict_without_default = makeDict(headers, values)
|
|
print("\t Testing makeDict default value behavior")
|
|
# Check if a default value is passed
|
|
self.assertEqual(dict_with_default["X"]["Y"], 0)
|
|
# Check if a KeyError is raised
|
|
_func = lambda: dict_without_default["X"]["Y"]
|
|
self.assertRaises(KeyError, _func)
|
|
|
|
def test_importMPS_maximize(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMaximize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
filename = name + ".mps"
|
|
prob.writeMPS(filename)
|
|
_vars, prob2 = LpProblem.fromMPS(filename, sense=prob.sense)
|
|
_dict1 = getSortedDict(prob)
|
|
_dict2 = getSortedDict(prob2)
|
|
print("\t Testing reading MPS files - maximize")
|
|
self.assertDictEqual(_dict1, _dict2)
|
|
|
|
def test_importMPS_noname(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem("", const.LpMaximize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
filename = name + ".mps"
|
|
prob.writeMPS(filename)
|
|
_vars, prob2 = LpProblem.fromMPS(filename, sense=prob.sense)
|
|
_dict1 = getSortedDict(prob)
|
|
_dict2 = getSortedDict(prob2)
|
|
print("\t Testing reading MPS files - noname")
|
|
self.assertDictEqual(_dict1, _dict2)
|
|
|
|
def test_importMPS_integer(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0, None, const.LpInteger)
|
|
prob += 1.1 * x + 4.1 * y + 9.1 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7.5, "c3"
|
|
filename = name + ".mps"
|
|
prob.writeMPS(filename)
|
|
_vars, prob2 = LpProblem.fromMPS(filename, sense=prob.sense)
|
|
_dict1 = getSortedDict(prob)
|
|
_dict2 = getSortedDict(prob2)
|
|
print("\t Testing reading MPS files - integer variable")
|
|
self.assertDictEqual(_dict1, _dict2)
|
|
|
|
def test_importMPS_binary(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMaximize)
|
|
dummy = LpVariable("dummy")
|
|
c1 = LpVariable("c1", 0, 1, const.LpBinary)
|
|
c2 = LpVariable("c2", 0, 1, const.LpBinary)
|
|
prob += dummy
|
|
prob += c1 + c2 == 2
|
|
prob += c1 <= 0
|
|
filename = name + ".mps"
|
|
prob.writeMPS(filename)
|
|
_vars, prob2 = LpProblem.fromMPS(
|
|
filename, sense=prob.sense, dropConsNames=True
|
|
)
|
|
_dict1 = getSortedDict(prob, keyCons="constant")
|
|
_dict2 = getSortedDict(prob2, keyCons="constant")
|
|
print("\t Testing reading MPS files - binary variable, no constraint names")
|
|
self.assertDictEqual(_dict1, _dict2)
|
|
|
|
def test_importMPS_RHS_fields56(self):
|
|
"""Import MPS file with RHS definitions in fields 5 & 6."""
|
|
with tempfile.NamedTemporaryFile(delete=False) as h:
|
|
h.write(str.encode(EXAMPLE_MPS_RHS56))
|
|
_, problem = LpProblem.fromMPS(h.name)
|
|
os.unlink(h.name)
|
|
self.assertEqual(problem.constraints["LIM2"].constant, -10)
|
|
|
|
# def test_importMPS_2(self):
|
|
# name = self._testMethodName
|
|
# # filename = name + ".mps"
|
|
# filename = "/home/pchtsp/Downloads/test.mps"
|
|
# _vars, _prob = LpProblem.fromMPS(filename)
|
|
# _prob.solve()
|
|
# for k, v in _vars.items():
|
|
# print(k, v.value())
|
|
|
|
def test_unset_objective_value__is_valid(self):
|
|
"""Given a valid problem that does not converge,
|
|
assert that it is still categorised as valid.
|
|
"""
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMaximize)
|
|
x = LpVariable("x")
|
|
prob += 0 * x
|
|
prob += x >= 1
|
|
pulpTestCheck(prob, self.solver, [const.LpStatusOptimal])
|
|
self.assertTrue(prob.valid())
|
|
|
|
def test_unbounded_problem__is_not_valid(self):
|
|
"""Given an unbounded problem, where x will tend to infinity
|
|
to maximise the objective, assert that it is categorised
|
|
as invalid."""
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMaximize)
|
|
x = LpVariable("x")
|
|
prob += 1000 * x
|
|
prob += x >= 1
|
|
self.assertFalse(prob.valid())
|
|
|
|
def test_infeasible_problem__is_not_valid(self):
|
|
"""Given a problem where x cannot converge to any value
|
|
given conflicting constraints, assert that it is invalid."""
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMaximize)
|
|
x = LpVariable("x")
|
|
prob += 1 * x
|
|
prob += x >= 2 # Constraint x to be more than 2
|
|
prob += x <= 1 # Constraint x to be less than 1
|
|
if self.solver.name in ["GUROBI_CMD", "FSCIP_CMD"]:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[
|
|
const.LpStatusNotSolved,
|
|
const.LpStatusInfeasible,
|
|
const.LpStatusUndefined,
|
|
],
|
|
)
|
|
else:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusInfeasible, const.LpStatusUndefined],
|
|
)
|
|
self.assertFalse(prob.valid())
|
|
|
|
def test_false_constraint(self):
|
|
prob = LpProblem(self._testMethodName, const.LpMinimize)
|
|
|
|
def add_const(prob):
|
|
prob += 0 - 3 == 0
|
|
|
|
self.assertRaises(TypeError, add_const, prob=prob)
|
|
|
|
@gurobi_test
|
|
def test_measuring_solving_time(self):
|
|
print("\t Testing measuring optimization time")
|
|
|
|
time_limit = 10
|
|
solver_settings = dict(
|
|
PULP_CBC_CMD=30,
|
|
COIN_CMD=30,
|
|
SCIP_CMD=30,
|
|
GUROBI_CMD=50,
|
|
CPLEX_CMD=50,
|
|
GUROBI=50,
|
|
HiGHS=50,
|
|
)
|
|
bins = solver_settings.get(self.solver.name)
|
|
if bins is None:
|
|
# not all solvers have timeLimit support
|
|
return
|
|
prob = create_bin_packing_problem(bins=bins, seed=99)
|
|
self.solver.timeLimit = time_limit
|
|
prob.solve(self.solver)
|
|
delta = 20
|
|
reported_time = prob.solutionTime
|
|
if self.solver.name in ["PULP_CBC_CMD", "COIN_CMD"]:
|
|
reported_time = prob.solutionCpuTime
|
|
|
|
self.assertAlmostEqual(
|
|
reported_time,
|
|
time_limit,
|
|
delta=delta,
|
|
msg=f"optimization time for solver {self.solver.name}",
|
|
)
|
|
self.assertTrue(prob.objective.value() is not None)
|
|
for v in prob.variables():
|
|
self.assertTrue(v.varValue is not None)
|
|
|
|
def test_invalid_var_names(self):
|
|
prob = LpProblem(self._testMethodName, const.LpMinimize)
|
|
x = LpVariable("a")
|
|
w = LpVariable("b")
|
|
y = LpVariable("g", -1, 1)
|
|
z = LpVariable("End")
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
print("\t Testing invalid var names")
|
|
if self.solver.name not in [
|
|
"GUROBI_CMD", # end is a key-word for LP files
|
|
]:
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
|
|
def test_LpVariable_indexs_param(self):
|
|
"""
|
|
Test that 'indexs' param continues to work
|
|
"""
|
|
|
|
prob = LpProblem(self._testMethodName, const.LpMinimize)
|
|
customers = [1, 2, 3]
|
|
agents = ["A", "B", "C"]
|
|
|
|
print("\t Testing 'indexs' param continues to work for LpVariable.dicts")
|
|
# explicit param creates a dict of type LpVariable
|
|
assign_vars = LpVariable.dicts(name="test", indexs=(customers, agents))
|
|
for k, v in assign_vars.items():
|
|
for a, b in v.items():
|
|
self.assertIsInstance(b, LpVariable)
|
|
|
|
# param by position creates a dict of type LpVariable
|
|
assign_vars = LpVariable.dicts("test", (customers, agents))
|
|
for k, v in assign_vars.items():
|
|
for a, b in v.items():
|
|
self.assertIsInstance(b, LpVariable)
|
|
|
|
print("\t Testing 'indexs' param continues to work for LpVariable.matrix")
|
|
# explicit param creates list of list of LpVariable
|
|
assign_vars_matrix = LpVariable.matrix(
|
|
name="test", indices=(customers, agents)
|
|
)
|
|
for a in assign_vars_matrix:
|
|
for b in a:
|
|
self.assertIsInstance(b, LpVariable)
|
|
|
|
# param by position creates list of list of LpVariable
|
|
assign_vars_matrix = LpVariable.matrix("test", (customers, agents))
|
|
for a in assign_vars_matrix:
|
|
for b in a:
|
|
self.assertIsInstance(b, LpVariable)
|
|
|
|
def test_LpVariable_indices_param(self):
|
|
"""
|
|
Test that 'indices' argument works
|
|
"""
|
|
prob = LpProblem(self._testMethodName, const.LpMinimize)
|
|
customers = [1, 2, 3]
|
|
agents = ["A", "B", "C"]
|
|
|
|
print("\t Testing 'indices' argument works in LpVariable.dicts")
|
|
# explicit param creates a dict of type LpVariable
|
|
assign_vars = LpVariable.dicts(name="test", indices=(customers, agents))
|
|
for k, v in assign_vars.items():
|
|
for a, b in v.items():
|
|
self.assertIsInstance(b, LpVariable)
|
|
|
|
print("\t Testing 'indices' param continues to work for LpVariable.matrix")
|
|
# explicit param creates list of list of LpVariable
|
|
assign_vars_matrix = LpVariable.matrix(
|
|
name="test", indices=(customers, agents)
|
|
)
|
|
for a in assign_vars_matrix:
|
|
for b in a:
|
|
self.assertIsInstance(b, LpVariable)
|
|
|
|
def test_LpVariable_indexs_deprecation_logic(self):
|
|
"""
|
|
Test that logic put in place for deprecation handling of indexs works
|
|
"""
|
|
print(
|
|
"\t Test that logic put in place for deprecation handling of indexs works"
|
|
)
|
|
prob = LpProblem(self._testMethodName, const.LpMinimize)
|
|
customers = [1, 2, 3]
|
|
agents = ["A", "B", "C"]
|
|
|
|
with self.assertRaises(TypeError):
|
|
# both variables
|
|
assign_vars_matrix = LpVariable.dicts(
|
|
name="test", indices=(customers, agents), indexs=(customers, agents)
|
|
)
|
|
|
|
with self.assertRaises(TypeError):
|
|
# no variables
|
|
assign_vars_matrix = LpVariable.dicts(name="test")
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
assign_vars_matrix = LpVariable.dicts(
|
|
name="test", indexs=(customers, agents)
|
|
)
|
|
|
|
def test_parse_cplex_mipopt_solution(self):
|
|
"""
|
|
Ensures `readsol` can parse CPLEX mipopt solutions (see issue #508).
|
|
"""
|
|
from io import StringIO
|
|
|
|
print("\t Testing that `readsol` can parse CPLEX mipopt solution")
|
|
# Example solution generated by CPLEX mipopt solver
|
|
file_content = """<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
|
|
<CPLEXSolution version="1.2">
|
|
<header
|
|
problemName="mipopt_solution_example.lp"
|
|
solutionName="incumbent"
|
|
solutionIndex="-1"
|
|
objectiveValue="442"
|
|
solutionTypeValue="3"
|
|
solutionTypeString="primal"
|
|
solutionStatusValue="101"
|
|
solutionStatusString="integer optimal solution"
|
|
solutionMethodString="mip"
|
|
primalFeasible="1"
|
|
dualFeasible="1"
|
|
MIPNodes="25471"
|
|
MIPIterations="282516"
|
|
writeLevel="1"/>
|
|
<quality
|
|
epInt="1.0000000000000001e-05"
|
|
epRHS="9.9999999999999995e-07"
|
|
maxIntInfeas="8.8817841970012523e-16"
|
|
maxPrimalInfeas="0"
|
|
maxX="48"
|
|
maxSlack="141"/>
|
|
<linearConstraints>
|
|
<constraint name="C1" index="0" slack="0"/>
|
|
<constraint name="C2" index="1" slack="0"/>
|
|
</linearConstraints>
|
|
<variables>
|
|
<variable name="x" index="0" value="42"/>
|
|
<variable name="y" index="1" value="0"/>
|
|
</variables>
|
|
<objectiveValues>
|
|
<objective index="0" name="x" value="42"/>
|
|
</objectiveValues>
|
|
</CPLEXSolution>
|
|
"""
|
|
solution_file = StringIO(file_content)
|
|
|
|
# This call to `readsol` would crash for this solution format #508
|
|
_, _, reducedCosts, shadowPrices, _, _ = CPLEX_CMD().readsol(solution_file)
|
|
|
|
# Because mipopt solutions have no `reducedCost` fields
|
|
# it should be all None
|
|
self.assertTrue(all(c is None for c in reducedCosts.values()))
|
|
|
|
# Because mipopt solutions have no `shadowPrices` fields
|
|
# it should be all None
|
|
self.assertTrue(all(c is None for c in shadowPrices.values()))
|
|
|
|
def test_options_parsing_SCIP_HIGHS(self):
|
|
name = self._testMethodName
|
|
prob = LpProblem(name, const.LpMinimize)
|
|
x = LpVariable("x", 0, 4)
|
|
y = LpVariable("y", -1, 1)
|
|
z = LpVariable("z", 0)
|
|
w = LpVariable("w", 0)
|
|
prob += x + 4 * y + 9 * z, "obj"
|
|
prob += x + y <= 5, "c1"
|
|
prob += x + z >= 10, "c2"
|
|
prob += -y + z == 7, "c3"
|
|
prob += w >= 0, "c4"
|
|
# CHOCO has issues when given a time limit
|
|
print("\t Testing options parsing")
|
|
if self.solver.__class__ in [SCIP_CMD, FSCIP_CMD]:
|
|
self.solver.options = ["limits/time", 20]
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
elif self.solver.__class__ in [HiGHS_CMD]:
|
|
self.solver.options = ["time_limit", 20]
|
|
pulpTestCheck(
|
|
prob,
|
|
self.solver,
|
|
[const.LpStatusOptimal],
|
|
{x: 4, y: -1, z: 6, w: 0},
|
|
)
|
|
|
|
|
|
class PULP_CBC_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = PULP_CBC_CMD
|
|
|
|
|
|
class CPLEX_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = CPLEX_CMD
|
|
|
|
|
|
class CPLEX_PYTest(BaseSolverTest.PuLPTest):
|
|
solveInst = CPLEX_CMD
|
|
|
|
|
|
class XPRESS_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = XPRESS_CMD
|
|
|
|
|
|
class XPRESS_PyTest(BaseSolverTest.PuLPTest):
|
|
solveInst = XPRESS_PY
|
|
|
|
|
|
class COIN_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = COIN_CMD
|
|
|
|
|
|
class COINMP_DLLTest(BaseSolverTest.PuLPTest):
|
|
solveInst = COINMP_DLL
|
|
|
|
|
|
class GLPK_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = GLPK_CMD
|
|
|
|
|
|
class GUROBITest(BaseSolverTest.PuLPTest):
|
|
solveInst = GUROBI
|
|
|
|
|
|
class GUROBI_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = GUROBI_CMD
|
|
|
|
|
|
class PYGLPKTest(BaseSolverTest.PuLPTest):
|
|
solveInst = PYGLPK
|
|
|
|
|
|
class YAPOSIBTest(BaseSolverTest.PuLPTest):
|
|
solveInst = YAPOSIB
|
|
|
|
|
|
class CHOCO_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = CHOCO_CMD
|
|
|
|
|
|
class MIPCL_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = MIPCL_CMD
|
|
|
|
|
|
class MOSEKTest(BaseSolverTest.PuLPTest):
|
|
solveInst = MOSEK
|
|
|
|
|
|
class SCIP_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = SCIP_CMD
|
|
|
|
|
|
class FSCIP_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = FSCIP_CMD
|
|
|
|
|
|
class SCIP_PYTest(BaseSolverTest.PuLPTest):
|
|
solveInst = SCIP_PY
|
|
|
|
|
|
class HiGHS_PYTest(BaseSolverTest.PuLPTest):
|
|
solveInst = HiGHS
|
|
|
|
|
|
class HiGHS_CMDTest(BaseSolverTest.PuLPTest):
|
|
solveInst = HiGHS_CMD
|
|
|
|
|
|
class COPTTest(BaseSolverTest.PuLPTest):
|
|
solveInst = COPT
|
|
|
|
|
|
def pulpTestCheck(
|
|
prob,
|
|
solver,
|
|
okstatus,
|
|
sol=None,
|
|
reducedcosts=None,
|
|
duals=None,
|
|
slacks=None,
|
|
eps=10**-3,
|
|
status=None,
|
|
objective=None,
|
|
**kwargs,
|
|
):
|
|
if status is None:
|
|
status = prob.solve(solver, **kwargs)
|
|
if status not in okstatus:
|
|
dumpTestProblem(prob)
|
|
raise PulpError(
|
|
"Tests failed for solver {}:\nstatus == {} not in {}\nstatus == {} not in {}".format(
|
|
solver,
|
|
status,
|
|
okstatus,
|
|
const.LpStatus[status],
|
|
[const.LpStatus[s] for s in okstatus],
|
|
)
|
|
)
|
|
if sol is not None:
|
|
for v, x in sol.items():
|
|
if abs(v.varValue - x) > eps:
|
|
dumpTestProblem(prob)
|
|
raise PulpError(
|
|
"Tests failed for solver {}:\nvar {} == {} != {}".format(
|
|
solver, v, v.varValue, x
|
|
)
|
|
)
|
|
if reducedcosts:
|
|
for v, dj in reducedcosts.items():
|
|
if abs(v.dj - dj) > eps:
|
|
dumpTestProblem(prob)
|
|
raise PulpError(
|
|
"Tests failed for solver {}:\nTest failed: var.dj {} == {} != {}".format(
|
|
solver, v, v.dj, dj
|
|
)
|
|
)
|
|
if duals:
|
|
for cname, p in duals.items():
|
|
c = prob.constraints[cname]
|
|
if abs(c.pi - p) > eps:
|
|
dumpTestProblem(prob)
|
|
raise PulpError(
|
|
"Tests failed for solver {}:\nconstraint.pi {} == {} != {}".format(
|
|
solver, cname, c.pi, p
|
|
)
|
|
)
|
|
if slacks:
|
|
for cname, slack in slacks.items():
|
|
c = prob.constraints[cname]
|
|
if abs(c.slack - slack) > eps:
|
|
dumpTestProblem(prob)
|
|
raise PulpError(
|
|
"Tests failed for solver {}:\nconstraint.slack {} == {} != {}".format(
|
|
solver, cname, c.slack, slack
|
|
)
|
|
)
|
|
if objective is not None:
|
|
z = prob.objective.value()
|
|
if abs(z - objective) > eps:
|
|
dumpTestProblem(prob)
|
|
raise PulpError(
|
|
f"Tests failed for solver {solver}:\nobjective {z} != {objective}"
|
|
)
|
|
|
|
|
|
def getSortedDict(prob, keyCons="name", keyVars="name"):
|
|
_dict = prob.toDict()
|
|
_dict["constraints"].sort(key=lambda v: v[keyCons])
|
|
_dict["variables"].sort(key=lambda v: v[keyVars])
|
|
return _dict
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|