Files
blender-easy-patch/utils/pulp/apis/copt_api.py
小煜 ab91b120e6 feat: 初始化Easy Patch插件及依赖文件
- 添加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配置文件
2026-03-03 19:24:57 +08:00

1091 lines
36 KiB
Python

import os
import sys
import ctypes
import subprocess
import warnings
from uuid import uuid4
from .core import sparse, ctypesArrayFill, PulpSolverError
from .core import clock, log
from .core import LpSolver, LpSolver_CMD
from ..constants import (
LpStatusNotSolved,
LpStatusOptimal,
LpStatusInfeasible,
LpStatusUnbounded,
LpStatusUndefined,
)
from ..constants import LpContinuous, LpBinary, LpInteger
from ..constants import LpConstraintEQ, LpConstraintLE, LpConstraintGE
from ..constants import LpMinimize, LpMaximize
# COPT string convention
if sys.version_info >= (3, 0):
coptstr = lambda x: bytes(x, "utf-8")
else:
coptstr = lambda x: x
byref = ctypes.byref
class COPT_CMD(LpSolver_CMD):
"""
The COPT command-line solver
"""
name = "COPT_CMD"
def __init__(
self,
path=None,
keepFiles=0,
mip=True,
msg=True,
mip_start=False,
warmStart=False,
logfile=None,
**params,
):
"""
Initialize command-line solver
"""
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
if warmStart:
warnings.warn(
"Parameter warmStart and mip_start passed, using warmStart"
)
else:
warmStart = mip_start
LpSolver_CMD.__init__(self, path, keepFiles, mip, msg, [])
self.mipstart = warmStart
self.logfile = logfile
self.solverparams = params
def defaultPath(self):
"""
The default path of 'copt_cmd'
"""
return self.executableExtension("copt_cmd")
def available(self):
"""
True if 'copt_cmd' is available
"""
return self.executable(self.path)
def actualSolve(self, lp):
"""
Solve a well formulated LP problem
This function borrowed implementation of CPLEX_CMD.actualSolve and
GUROBI_CMD.actualSolve, with some modifications.
"""
if not self.available():
raise PulpSolverError("COPT_PULP: Failed to execute '{}'".format(self.path))
if not self.keepFiles:
uuid = uuid4().hex
tmpLp = os.path.join(self.tmpDir, "{}-pulp.lp".format(uuid))
tmpSol = os.path.join(self.tmpDir, "{}-pulp.sol".format(uuid))
tmpMst = os.path.join(self.tmpDir, "{}-pulp.mst".format(uuid))
else:
# Replace space with underscore to make filepath better
tmpName = lp.name
tmpName = tmpName.replace(" ", "_")
tmpLp = tmpName + "-pulp.lp"
tmpSol = tmpName + "-pulp.sol"
tmpMst = tmpName + "-pulp.mst"
lpvars = lp.writeLP(tmpLp, writeSOS=1)
# Generate solving commands
solvecmds = self.path
solvecmds += " -c "
solvecmds += '"read ' + tmpLp + ";"
if lp.isMIP() and self.mipstart:
self.writemst(tmpMst, lpvars)
solvecmds += "read " + tmpMst + ";"
if self.logfile is not None:
solvecmds += "set logfile {};".format(self.logfile)
if self.solverparams is not None:
for parname, parval in self.solverparams.items():
solvecmds += "set {0} {1};".format(parname, parval)
if lp.isMIP() and not self.mip:
solvecmds += "optimizelp;"
else:
solvecmds += "optimize;"
solvecmds += "write " + tmpSol + ";"
solvecmds += 'exit"'
try:
os.remove(tmpSol)
except:
pass
if self.msg:
msgpipe = None
else:
msgpipe = open(os.devnull, "w")
rc = subprocess.call(solvecmds, shell=True, stdout=msgpipe, stderr=msgpipe)
if msgpipe is not None:
msgpipe.close()
# Get and analyze result
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to execute '{}'".format(self.path))
if not os.path.exists(tmpSol):
status = LpStatusNotSolved
else:
status, values = self.readsol(tmpSol)
if not self.keepFiles:
for oldfile in [tmpLp, tmpSol, tmpMst]:
try:
os.remove(oldfile)
except:
pass
if status == LpStatusOptimal:
lp.assignVarsVals(values)
# lp.assignStatus(status)
lp.status = status
return status
def readsol(self, filename):
"""
Read COPT solution file
"""
with open(filename) as solfile:
try:
next(solfile)
except StopIteration:
warnings.warn("COPT_PULP: No solution was returned")
return LpStatusNotSolved, {}
# TODO: No information about status, assumed to be optimal
status = LpStatusOptimal
values = {}
for line in solfile:
if line[0] != "#":
varname, varval = line.split()
values[varname] = float(varval)
return status, values
def writemst(self, filename, lpvars):
"""
Write COPT MIP start file
"""
mstvals = [(v.name, v.value()) for v in lpvars if v.value() is not None]
mstline = []
for varname, varval in mstvals:
mstline.append("{0} {1}".format(varname, varval))
with open(filename, "w") as mstfile:
mstfile.write("\n".join(mstline))
return True
def COPT_DLL_loadlib():
"""
Load COPT shared library in all supported platforms
"""
from glob import glob
libfile = None
libpath = None
libhome = os.getenv("COPT_HOME")
if sys.platform == "win32":
libfile = glob(os.path.join(libhome, "bin", "copt.dll"))
elif sys.platform == "linux":
libfile = glob(os.path.join(libhome, "lib", "libcopt.so"))
elif sys.platform == "darwin":
libfile = glob(os.path.join(libhome, "lib", "libcopt.dylib"))
else:
raise PulpSolverError("COPT_PULP: Unsupported operating system")
# Find desired library in given search path
if libfile:
libpath = libfile[0]
if libpath is None:
raise PulpSolverError(
"COPT_PULP: Failed to locate solver library, "
"please refer to COPT manual for installation guide"
)
else:
if sys.platform == "win32":
coptlib = ctypes.windll.LoadLibrary(libpath)
else:
coptlib = ctypes.cdll.LoadLibrary(libpath)
return coptlib
# COPT LP/MIP status map
coptlpstat = {
0: LpStatusNotSolved,
1: LpStatusOptimal,
2: LpStatusInfeasible,
3: LpStatusUnbounded,
4: LpStatusNotSolved,
5: LpStatusNotSolved,
6: LpStatusNotSolved,
8: LpStatusNotSolved,
9: LpStatusNotSolved,
10: LpStatusNotSolved,
}
# COPT variable types map
coptctype = {
LpContinuous: coptstr("C"),
LpBinary: coptstr("B"),
LpInteger: coptstr("I"),
}
# COPT constraint types map
coptrsense = {
LpConstraintEQ: coptstr("E"),
LpConstraintLE: coptstr("L"),
LpConstraintGE: coptstr("G"),
}
# COPT objective senses map
coptobjsen = {LpMinimize: 1, LpMaximize: -1}
class COPT_DLL(LpSolver):
"""
The COPT dynamic library solver
"""
name = "COPT_DLL"
try:
coptlib = COPT_DLL_loadlib()
except Exception as e:
err = e
"""The COPT dynamic library solver (DLL). Something went wrong!!!!"""
def available(self):
"""True if the solver is available"""
return False
def actualSolve(self, lp):
"""Solve a well formulated lp problem"""
raise PulpSolverError(f"COPT_DLL: Not Available:\n{self.err}")
else:
# COPT API name map
CreateEnv = coptlib.COPT_CreateEnv
DeleteEnv = coptlib.COPT_DeleteEnv
CreateProb = coptlib.COPT_CreateProb
DeleteProb = coptlib.COPT_DeleteProb
LoadProb = coptlib.COPT_LoadProb
AddCols = coptlib.COPT_AddCols
WriteMps = coptlib.COPT_WriteMps
WriteLp = coptlib.COPT_WriteLp
WriteBin = coptlib.COPT_WriteBin
WriteSol = coptlib.COPT_WriteSol
WriteBasis = coptlib.COPT_WriteBasis
WriteMst = coptlib.COPT_WriteMst
WriteParam = coptlib.COPT_WriteParam
AddMipStart = coptlib.COPT_AddMipStart
SolveLp = coptlib.COPT_SolveLp
Solve = coptlib.COPT_Solve
GetSolution = coptlib.COPT_GetSolution
GetLpSolution = coptlib.COPT_GetLpSolution
GetIntParam = coptlib.COPT_GetIntParam
SetIntParam = coptlib.COPT_SetIntParam
GetDblParam = coptlib.COPT_GetDblParam
SetDblParam = coptlib.COPT_SetDblParam
GetIntAttr = coptlib.COPT_GetIntAttr
GetDblAttr = coptlib.COPT_GetDblAttr
SearchParamAttr = coptlib.COPT_SearchParamAttr
SetLogFile = coptlib.COPT_SetLogFile
def __init__(
self,
mip=True,
msg=True,
mip_start=False,
warmStart=False,
logfile=None,
**params,
):
"""
Initialize COPT solver
"""
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
if warmStart:
warnings.warn(
"Parameter warmStart and mip_start passed, using warmStart"
)
else:
warmStart = mip_start
LpSolver.__init__(self, mip, msg)
# Initialize COPT environment and problem
self.coptenv = None
self.coptprob = None
# Use MIP start information
self.mipstart = warmStart
# Create COPT environment and problem
self.create()
# Set log file
if logfile is not None:
rc = self.SetLogFile(self.coptprob, coptstr(logfile))
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to set log file")
# Set parameters to problem
if not self.msg:
self.setParam("Logging", 0)
for parname, parval in params.items():
self.setParam(parname, parval)
def available(self):
"""
True if dynamic library is available
"""
return True
def actualSolve(self, lp):
"""
Solve a well formulated LP/MIP problem
This function borrowed implementation of CPLEX_DLL.actualSolve,
with some modifications.
"""
# Extract problem data and load it into COPT
(
ncol,
nrow,
nnonz,
objsen,
objconst,
colcost,
colbeg,
colcnt,
colind,
colval,
coltype,
collb,
colub,
rowsense,
rowrhs,
colname,
rowname,
) = self.extract(lp)
rc = self.LoadProb(
self.coptprob,
ncol,
nrow,
objsen,
objconst,
colcost,
colbeg,
colcnt,
colind,
colval,
coltype,
collb,
colub,
rowsense,
rowrhs,
None,
colname,
rowname,
)
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to load problem")
if lp.isMIP() and self.mip:
# Load MIP start information
if self.mipstart:
mstdict = {
self.v2n[v]: v.value()
for v in lp.variables()
if v.value() is not None
}
if mstdict:
mstkeys = ctypesArrayFill(list(mstdict.keys()), ctypes.c_int)
mstvals = ctypesArrayFill(
list(mstdict.values()), ctypes.c_double
)
rc = self.AddMipStart(
self.coptprob, len(mstkeys), mstkeys, mstvals
)
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to add MIP start information"
)
# Solve the problem
rc = self.Solve(self.coptprob)
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to solve the MIP problem")
elif lp.isMIP() and not self.mip:
# Solve MIP as LP
rc = self.SolveLp(self.coptprob)
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to solve MIP as LP")
else:
# Solve the LP problem
rc = self.SolveLp(self.coptprob)
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to solve the LP problem")
# Get problem status and solution
status = self.getsolution(lp, ncol, nrow)
# Reset attributes
for var in lp.variables():
var.modified = False
return status
def extract(self, lp):
"""
Extract data from PuLP lp structure
This function borrowed implementation of LpSolver.getCplexStyleArrays,
with some modifications.
"""
cols = list(lp.variables())
ncol = len(cols)
nrow = len(lp.constraints)
collb = (ctypes.c_double * ncol)()
colub = (ctypes.c_double * ncol)()
colcost = (ctypes.c_double * ncol)()
coltype = (ctypes.c_char * ncol)()
colname = (ctypes.c_char_p * ncol)()
rowrhs = (ctypes.c_double * nrow)()
rowsense = (ctypes.c_char * nrow)()
rowname = (ctypes.c_char_p * nrow)()
spmat = sparse.Matrix(list(range(nrow)), list(range(ncol)))
# Objective sense and constant offset
objsen = coptobjsen[lp.sense]
objconst = ctypes.c_double(0.0)
# Associate each variable with a ordinal
self.v2n = dict(((cols[i], i) for i in range(ncol)))
self.vname2n = dict(((cols[i].name, i) for i in range(ncol)))
self.n2v = dict((i, cols[i]) for i in range(ncol))
self.c2n = {}
self.n2c = {}
self.addedVars = ncol
self.addedRows = nrow
# Extract objective cost
for col, val in lp.objective.items():
colcost[self.v2n[col]] = val
# Extract variable types, names and lower/upper bounds
for col in lp.variables():
colname[self.v2n[col]] = coptstr(col.name)
if col.lowBound is not None:
collb[self.v2n[col]] = col.lowBound
else:
collb[self.v2n[col]] = -1e30
if col.upBound is not None:
colub[self.v2n[col]] = col.upBound
else:
colub[self.v2n[col]] = 1e30
# Extract column types
if lp.isMIP():
for var in lp.variables():
coltype[self.v2n[var]] = coptctype[var.cat]
else:
coltype = None
# Extract constraint rhs, senses and names
idx = 0
for row in lp.constraints:
rowrhs[idx] = -lp.constraints[row].constant
rowsense[idx] = coptrsense[lp.constraints[row].sense]
rowname[idx] = coptstr(row)
self.c2n[row] = idx
self.n2c[idx] = row
idx += 1
# Extract coefficient matrix and generate CSC-format matrix
for col, row, coeff in lp.coefficients():
spmat.add(self.c2n[row], self.vname2n[col], coeff)
nnonz, _colbeg, _colcnt, _colind, _colval = spmat.col_based_arrays()
colbeg = ctypesArrayFill(_colbeg, ctypes.c_int)
colcnt = ctypesArrayFill(_colcnt, ctypes.c_int)
colind = ctypesArrayFill(_colind, ctypes.c_int)
colval = ctypesArrayFill(_colval, ctypes.c_double)
return (
ncol,
nrow,
nnonz,
objsen,
objconst,
colcost,
colbeg,
colcnt,
colind,
colval,
coltype,
collb,
colub,
rowsense,
rowrhs,
colname,
rowname,
)
def create(self):
"""
Create COPT environment and problem
This function borrowed implementation of CPLEX_DLL.grabLicense,
with some modifications.
"""
# In case recreate COPT environment and problem
self.delete()
self.coptenv = ctypes.c_void_p()
self.coptprob = ctypes.c_void_p()
# Create COPT environment
rc = self.CreateEnv(byref(self.coptenv))
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to create environment")
# Create COPT problem
rc = self.CreateProb(self.coptenv, byref(self.coptprob))
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to create problem")
def __del__(self):
"""
Destructor of COPT_DLL class
"""
self.delete()
def delete(self):
"""
Release COPT problem and environment
This function borrowed implementation of CPLEX_DLL.releaseLicense,
with some modifications.
"""
# Valid environment and problem exist
if self.coptenv is not None and self.coptprob is not None:
# Delete problem
rc = self.DeleteProb(byref(self.coptprob))
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to delete problem")
# Delete environment
rc = self.DeleteEnv(byref(self.coptenv))
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to delete environment")
# Reset to None
self.coptenv = None
self.coptprob = None
def getsolution(self, lp, ncols, nrows):
"""Get problem solution
This function borrowed implementation of CPLEX_DLL.findSolutionValues,
with some modifications.
"""
status = ctypes.c_int()
x = (ctypes.c_double * ncols)()
dj = (ctypes.c_double * ncols)()
pi = (ctypes.c_double * nrows)()
slack = (ctypes.c_double * nrows)()
var_x = {}
var_dj = {}
con_pi = {}
con_slack = {}
if lp.isMIP() and self.mip:
hasmipsol = ctypes.c_int()
# Get MIP problem satus
rc = self.GetIntAttr(self.coptprob, coptstr("MipStatus"), byref(status))
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to get MIP status")
# Has MIP solution
rc = self.GetIntAttr(
self.coptprob, coptstr("HasMipSol"), byref(hasmipsol)
)
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to check if MIP solution exists"
)
# Optimal/Feasible MIP solution
if status.value == 1 or hasmipsol.value == 1:
rc = self.GetSolution(self.coptprob, byref(x))
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to get MIP solution")
for i in range(ncols):
var_x[self.n2v[i].name] = x[i]
# Assign MIP solution to variables
lp.assignVarsVals(var_x)
else:
# Get LP problem status
rc = self.GetIntAttr(self.coptprob, coptstr("LpStatus"), byref(status))
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to get LP status")
# Optimal LP solution
if status.value == 1:
rc = self.GetLpSolution(
self.coptprob, byref(x), byref(slack), byref(pi), byref(dj)
)
if rc != 0:
raise PulpSolverError("COPT_PULP: Failed to get LP solution")
for i in range(ncols):
var_x[self.n2v[i].name] = x[i]
var_dj[self.n2v[i].name] = dj[i]
# NOTE: slacks in COPT are activities of rows
for i in range(nrows):
con_pi[self.n2c[i]] = pi[i]
con_slack[self.n2c[i]] = slack[i]
# Assign LP solution to variables and constraints
lp.assignVarsVals(var_x)
lp.assignVarsDj(var_dj)
lp.assignConsPi(con_pi)
lp.assignConsSlack(con_slack)
# Reset attributes
lp.resolveOK = True
for var in lp.variables():
var.isModified = False
lp.status = coptlpstat.get(status.value, LpStatusUndefined)
return lp.status
def write(self, filename):
"""
Write problem, basis, parameter or solution to file
"""
file_path = coptstr(filename)
file_name, file_ext = os.path.splitext(file_path)
if not file_ext:
raise PulpSolverError("COPT_PULP: Failed to determine output file type")
elif file_ext == coptstr(".mps"):
rc = self.WriteMps(self.coptprob, file_path)
elif file_ext == coptstr(".lp"):
rc = self.WriteLp(self.coptprob, file_path)
elif file_ext == coptstr(".bin"):
rc = self.WriteBin(self.coptprob, file_path)
elif file_ext == coptstr(".sol"):
rc = self.WriteSol(self.coptprob, file_path)
elif file_ext == coptstr(".bas"):
rc = self.WriteBasis(self.coptprob, file_path)
elif file_ext == coptstr(".mst"):
rc = self.WriteMst(self.coptprob, file_path)
elif file_ext == coptstr(".par"):
rc = self.WriteParam(self.coptprob, file_path)
else:
raise PulpSolverError("COPT_PULP: Unsupported file type")
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to write file '{}'".format(filename)
)
def setParam(self, name, val):
"""
Set parameter to COPT problem
"""
par_type = ctypes.c_int()
par_name = coptstr(name)
rc = self.SearchParamAttr(self.coptprob, par_name, byref(par_type))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to check type for '{}'".format(par_name)
)
if par_type.value == 0:
rc = self.SetDblParam(self.coptprob, par_name, ctypes.c_double(val))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to set double parameter '{}'".format(
par_name
)
)
elif par_type.value == 1:
rc = self.SetIntParam(self.coptprob, par_name, ctypes.c_int(val))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to set integer parameter '{}'".format(
par_name
)
)
else:
raise PulpSolverError(
"COPT_PULP: Invalid parameter '{}'".format(par_name)
)
def getParam(self, name):
"""
Get current value of parameter
"""
par_dblval = ctypes.c_double()
par_intval = ctypes.c_int()
par_type = ctypes.c_int()
par_name = coptstr(name)
rc = self.SearchParamAttr(self.coptprob, par_name, byref(par_type))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to check type for '{}'".format(par_name)
)
if par_type.value == 0:
rc = self.GetDblParam(self.coptprob, par_name, byref(par_dblval))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to get double parameter '{}'".format(
par_name
)
)
else:
retval = par_dblval.value
elif par_type.value == 1:
rc = self.GetIntParam(self.coptprob, par_name, byref(par_intval))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to get integer parameter '{}'".format(
par_name
)
)
else:
retval = par_intval.value
else:
raise PulpSolverError(
"COPT_PULP: Invalid parameter '{}'".format(par_name)
)
return retval
def getAttr(self, name):
"""
Get attribute of the problem
"""
attr_dblval = ctypes.c_double()
attr_intval = ctypes.c_int()
attr_type = ctypes.c_int()
attr_name = coptstr(name)
# Check attribute type by name
rc = self.SearchParamAttr(self.coptprob, attr_name, byref(attr_type))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to check type for '{}'".format(attr_name)
)
if attr_type.value == 2:
rc = self.GetDblAttr(self.coptprob, attr_name, byref(attr_dblval))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to get double attribute '{}'".format(
attr_name
)
)
else:
retval = attr_dblval.value
elif attr_type.value == 3:
rc = self.GetIntAttr(self.coptprob, attr_name, byref(attr_intval))
if rc != 0:
raise PulpSolverError(
"COPT_PULP: Failed to get integer attribute '{}'".format(
attr_name
)
)
else:
retval = attr_intval.value
else:
raise PulpSolverError(
"COPT_PULP: Invalid attribute '{}'".format(attr_name)
)
return retval
class COPT(LpSolver):
"""
The COPT Optimizer via its python interface
The COPT variables are available (after a solve) in var.solverVar
Constraints in constraint.solverConstraint
and the Model is in prob.solverModel
"""
name = "COPT"
try:
global coptpy
import coptpy
except:
def available(self):
"""True if the solver is available"""
return False
def actualSolve(self, lp, callback=None):
"""Solve a well formulated lp problem"""
raise PulpSolverError("COPT: Not available")
else:
def __init__(
self,
mip=True,
msg=True,
timeLimit=None,
epgap=None,
gapRel=None,
warmStart=False,
logPath=None,
**solverParams,
):
"""
:param bool mip: if False, assume LP even if integer variables
:param bool msg: if False, no log is shown
:param float timeLimit: maximum time for solver (in seconds)
:param float gapRel: relative gap tolerance for the solver to stop (in fraction)
:param bool warmStart: if True, the solver will use the current value of variables as a start
:param str logPath: path to the log file
:param float epgap: deprecated for gapRel
"""
if epgap is not None:
warnings.warn("Parameter epgap is being depreciated for gapRel")
if gapRel is not None:
warnings.warn("Parameter gapRel and epgap passed, using gapRel")
else:
gapRel = epgap
LpSolver.__init__(
self,
mip=mip,
msg=msg,
timeLimit=timeLimit,
gapRel=gapRel,
logPath=logPath,
warmStart=warmStart,
)
self.coptenv = coptpy.Envr()
self.coptmdl = self.coptenv.createModel()
if not self.msg:
self.coptmdl.setParam("Logging", 0)
for key, value in solverParams.items():
self.coptmdl.setParam(key, value)
def findSolutionValues(self, lp):
model = lp.solverModel
solutionStatus = model.status
CoptLpStatus = {
coptpy.COPT.UNSTARTED: LpStatusNotSolved,
coptpy.COPT.OPTIMAL: LpStatusOptimal,
coptpy.COPT.INFEASIBLE: LpStatusInfeasible,
coptpy.COPT.UNBOUNDED: LpStatusUnbounded,
coptpy.COPT.INF_OR_UNB: LpStatusInfeasible,
coptpy.COPT.NUMERICAL: LpStatusNotSolved,
coptpy.COPT.NODELIMIT: LpStatusNotSolved,
coptpy.COPT.TIMEOUT: LpStatusNotSolved,
coptpy.COPT.UNFINISHED: LpStatusNotSolved,
coptpy.COPT.INTERRUPTED: LpStatusNotSolved,
}
if self.msg:
print("COPT status=", solutionStatus)
lp.resolveOK = True
for var in lp._variables:
var.isModified = False
status = CoptLpStatus.get(solutionStatus, LpStatusUndefined)
lp.assignStatus(status)
if status != LpStatusOptimal:
return status
values = model.getInfo("Value", model.getVars())
for var, value in zip(lp._variables, values):
var.varValue = value
if not model.ismip:
# NOTE: slacks in COPT are activities of rows
slacks = model.getInfo("Slack", model.getConstrs())
for constr, value in zip(lp.constraints.values(), slacks):
constr.slack = value
redcosts = model.getInfo("RedCost", model.getVars())
for var, value in zip(lp._variables, redcosts):
var.dj = value
duals = model.getInfo("Dual", model.getConstrs())
for constr, value in zip(lp.constraints.values(), duals):
constr.pi = value
return status
def available(self):
"""True if the solver is available"""
return True
def callSolver(self, lp, callback=None):
"""Solves the problem with COPT"""
self.solveTime = -clock()
if callback is not None:
lp.solverModel.setCallback(
callback,
coptpy.COPT.CBCONTEXT_MIPRELAX | coptpy.COPT.CBCONTEXT_MIPSOL,
)
lp.solverModel.solve()
self.solveTime += clock()
def buildSolverModel(self, lp):
"""
Takes the pulp lp model and translates it into a COPT model
"""
lp.solverModel = self.coptmdl
if lp.sense == LpMaximize:
lp.solverModel.objsense = coptpy.COPT.MAXIMIZE
if self.timeLimit:
lp.solverModel.setParam("TimeLimit", self.timeLimit)
gapRel = self.optionsDict.get("gapRel")
logPath = self.optionsDict.get("logPath")
if gapRel:
lp.solverModel.setParam("RelGap", gapRel)
if logPath:
lp.solverModel.setLogFile(logPath)
for var in lp.variables():
lowBound = var.lowBound
if lowBound is None:
lowBound = -coptpy.COPT.INFINITY
upBound = var.upBound
if upBound is None:
upBound = coptpy.COPT.INFINITY
obj = lp.objective.get(var, 0.0)
varType = coptpy.COPT.CONTINUOUS
if var.cat == LpInteger and self.mip:
varType = coptpy.COPT.INTEGER
var.solverVar = lp.solverModel.addVar(
lowBound, upBound, vtype=varType, obj=obj, name=var.name
)
if self.optionsDict.get("warmStart", False):
for var in lp._variables:
if var.varValue is not None:
self.coptmdl.setMipStart(var.solverVar, var.varValue)
self.coptmdl.loadMipStart()
for name, constraint in lp.constraints.items():
expr = coptpy.LinExpr(
[v.solverVar for v in constraint.keys()], list(constraint.values())
)
if constraint.sense == LpConstraintLE:
relation = coptpy.COPT.LESS_EQUAL
elif constraint.sense == LpConstraintGE:
relation = coptpy.COPT.GREATER_EQUAL
elif constraint.sense == LpConstraintEQ:
relation = coptpy.COPT.EQUAL
else:
raise PulpSolverError("Detected an invalid constraint type")
constraint.solverConstraint = lp.solverModel.addConstr(
expr, relation, -constraint.constant, name
)
def actualSolve(self, lp, callback=None):
"""
Solve a well formulated lp problem
creates a COPT model, variables and constraints and attaches
them to the lp model which it then solves
"""
self.buildSolverModel(lp)
self.callSolver(lp, callback=callback)
solutionStatus = self.findSolutionValues(lp)
for var in lp._variables:
var.modified = False
for constraint in lp.constraints.values():
constraint.modified = False
return solutionStatus
def actualResolve(self, lp, callback=None):
"""
Solve a well formulated lp problem
uses the old solver and modifies the rhs of the modified constraints
"""
for constraint in lp.constraints.values():
if constraint.modified:
if constraint.sense == LpConstraintLE:
constraint.solverConstraint.ub = -constraint.constant
elif constraint.sense == LpConstraintGE:
constraint.solverConstraint.lb = -constraint.constant
else:
constraint.solverConstraint.lb = -constraint.constant
constraint.solverConstraint.ub = -constraint.constant
self.callSolver(lp, callback=callback)
solutionStatus = self.findSolutionValues(lp)
for var in lp._variables:
var.modified = False
for constraint in lp.constraints.values():
constraint.modified = False
return solutionStatus