Files
blender-easy-patch/utils/pulp/apis/coin_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

874 lines
31 KiB
Python

# PuLP : Python LP Modeler
# Version 1.4.2
# Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org)
# Modifications Copyright (c) 2007- Stuart Anthony Mitchell (s.mitchell@auckland.ac.nz)
# $Id:solvers.py 1791 2008-04-23 22:54:34Z smit023 $
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""
from .core import LpSolver_CMD, LpSolver, subprocess, PulpSolverError, clock, log
from .core import cbc_path, pulp_cbc_path, coinMP_path, devnull, operating_system
import os
from .. import constants
from tempfile import mktemp
import ctypes
import warnings
class COIN_CMD(LpSolver_CMD):
"""The COIN CLP/CBC LP solver
now only uses cbc
"""
name = "COIN_CMD"
def defaultPath(self):
return self.executableExtension(cbc_path)
def __init__(
self,
mip=True,
msg=True,
timeLimit=None,
fracGap=None,
maxSeconds=None,
gapRel=None,
gapAbs=None,
presolve=None,
cuts=None,
strong=None,
options=None,
warmStart=False,
keepFiles=False,
path=None,
threads=None,
logPath=None,
timeMode="elapsed",
mip_start=False,
maxNodes=None,
):
"""
: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 float gapAbs: absolute gap tolerance for the solver to stop
:param int threads: sets the maximum number of threads
:param list options: list of additional options to pass to solver
:param bool warmStart: if True, the solver will use the current value of variables as a start
:param bool keepFiles: if True, files are saved in the current directory and not deleted after solving
:param str path: path to the solver binary
:param str logPath: path to the log file
:param bool presolve: if True, adds presolve on
:param bool cuts: if True, adds gomory on knapsack on probing on
:param bool strong: if True, adds strong
:param float fracGap: deprecated for gapRel
:param float maxSeconds: deprecated for timeLimit
:param str timeMode: "elapsed": count wall-time to timeLimit; "cpu": count cpu-time
:param bool mip_start: deprecated for warmStart
:param int maxNodes: max number of nodes during branching. Stops the solving when reached.
"""
if fracGap is not None:
warnings.warn("Parameter fracGap is being depreciated for gapRel")
if gapRel is not None:
warnings.warn("Parameter gapRel and fracGap passed, using gapRel")
else:
gapRel = fracGap
if maxSeconds is not None:
warnings.warn("Parameter maxSeconds is being depreciated for timeLimit")
if timeLimit is not None:
warnings.warn(
"Parameter timeLimit and maxSeconds passed, using timeLimit"
)
else:
timeLimit = maxSeconds
if mip_start:
warnings.warn("Parameter mip_start is being depreciated for warmStart")
if warmStart:
warnings.warn(
"Parameter mipStart and mip_start passed, using warmStart"
)
else:
warmStart = mip_start
LpSolver_CMD.__init__(
self,
gapRel=gapRel,
mip=mip,
msg=msg,
timeLimit=timeLimit,
presolve=presolve,
cuts=cuts,
strong=strong,
options=options,
warmStart=warmStart,
path=path,
keepFiles=keepFiles,
threads=threads,
gapAbs=gapAbs,
logPath=logPath,
timeMode=timeMode,
maxNodes=maxNodes,
)
def copy(self):
"""Make a copy of self"""
aCopy = LpSolver_CMD.copy(self)
aCopy.optionsDict = self.optionsDict
return aCopy
def actualSolve(self, lp, **kwargs):
"""Solve a well formulated lp problem"""
return self.solve_CBC(lp, **kwargs)
def available(self):
"""True if the solver is available"""
return self.executable(self.path)
def solve_CBC(self, lp, use_mps=True):
"""Solve a MIP problem using CBC"""
if not self.executable(self.path):
raise PulpSolverError(
f"Pulp: cannot execute {self.path} cwd: {os.getcwd()}"
)
tmpLp, tmpMps, tmpSol, tmpMst = self.create_tmp_files(
lp.name, "lp", "mps", "sol", "mst"
)
if use_mps:
vs, variablesNames, constraintsNames, objectiveName = lp.writeMPS(
tmpMps, rename=1
)
cmds = " " + tmpMps + " "
if lp.sense == constants.LpMaximize:
cmds += "-max "
else:
vs = lp.writeLP(tmpLp)
# In the Lp we do not create new variable or constraint names:
variablesNames = {v.name: v.name for v in vs}
constraintsNames = {c: c for c in lp.constraints}
cmds = " " + tmpLp + " "
if self.optionsDict.get("warmStart", False):
self.writesol(tmpMst, lp, vs, variablesNames, constraintsNames)
cmds += f"-mips {tmpMst} "
if self.timeLimit is not None:
cmds += f"-sec {self.timeLimit} "
options = self.options + self.getOptions()
for option in options:
cmds += "-" + option + " "
if self.mip:
cmds += "-branch "
else:
cmds += "-initialSolve "
cmds += "-printingOptions all "
cmds += "-solution " + tmpSol + " "
if self.msg:
pipe = None
else:
pipe = open(os.devnull, "w")
logPath = self.optionsDict.get("logPath")
if logPath:
if self.msg:
warnings.warn(
"`logPath` argument replaces `msg=1`. The output will be redirected to the log file."
)
pipe = open(self.optionsDict["logPath"], "w")
log.debug(self.path + cmds)
args = []
args.append(self.path)
args.extend(cmds[1:].split())
if not self.msg and operating_system == "win":
# Prevent flashing windows if used from a GUI application
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
cbc = subprocess.Popen(
args, stdout=pipe, stderr=pipe, stdin=devnull, startupinfo=startupinfo
)
else:
cbc = subprocess.Popen(args, stdout=pipe, stderr=pipe, stdin=devnull)
if cbc.wait() != 0:
if pipe:
pipe.close()
raise PulpSolverError(
"Pulp: Error while trying to execute, use msg=True for more details"
+ self.path
)
if pipe:
pipe.close()
if not os.path.exists(tmpSol):
raise PulpSolverError("Pulp: Error while executing " + self.path)
(
status,
values,
reducedCosts,
shadowPrices,
slacks,
sol_status,
) = self.readsol_MPS(tmpSol, lp, vs, variablesNames, constraintsNames)
lp.assignVarsVals(values)
lp.assignVarsDj(reducedCosts)
lp.assignConsPi(shadowPrices)
lp.assignConsSlack(slacks, activity=True)
lp.assignStatus(status, sol_status)
self.delete_tmp_files(tmpMps, tmpLp, tmpSol, tmpMst)
return status
def getOptions(self):
params_eq = dict(
gapRel="ratio {}",
gapAbs="allow {}",
threads="threads {}",
presolve="presolve on",
strong="strong {}",
cuts="gomory on knapsack on probing on",
timeMode="timeMode {}",
maxNodes="maxNodes {}",
)
return [
v.format(self.optionsDict[k])
for k, v in params_eq.items()
if self.optionsDict.get(k) is not None
]
def readsol_MPS(
self, filename, lp, vs, variablesNames, constraintsNames, objectiveName=None
):
"""
Read a CBC solution file generated from an mps or lp file (possible different names)
"""
values = {v.name: 0 for v in vs}
reverseVn = {v: k for k, v in variablesNames.items()}
reverseCn = {v: k for k, v in constraintsNames.items()}
reducedCosts = {}
shadowPrices = {}
slacks = {}
status, sol_status = self.get_status(filename)
with open(filename) as f:
for l in f:
if len(l) <= 2:
break
l = l.split()
# incase the solution is infeasible
if l[0] == "**":
l = l[1:]
vn = l[1]
val = l[2]
dj = l[3]
if vn in reverseVn:
values[reverseVn[vn]] = float(val)
reducedCosts[reverseVn[vn]] = float(dj)
if vn in reverseCn:
slacks[reverseCn[vn]] = float(val)
shadowPrices[reverseCn[vn]] = float(dj)
return status, values, reducedCosts, shadowPrices, slacks, sol_status
def writesol(self, filename, lp, vs, variablesNames, constraintsNames):
"""
Writes a CBC solution file generated from an mps / lp file (possible different names)
returns True on success
"""
values = {v.name: v.value() if v.value() is not None else 0 for v in vs}
value_lines = []
value_lines += [
(i, v, values[k], 0) for i, (k, v) in enumerate(variablesNames.items())
]
lines = ["Stopped on time - objective value 0\n"]
lines += ["{:>7} {} {:>15} {:>23}\n".format(*tup) for tup in value_lines]
with open(filename, "w") as f:
f.writelines(lines)
return True
def readsol_LP(self, filename, lp, vs):
"""
Read a CBC solution file generated from an lp (good names)
returns status, values, reducedCosts, shadowPrices, slacks, sol_status
"""
variablesNames = {v.name: v.name for v in vs}
constraintsNames = {c: c for c in lp.constraints}
return self.readsol_MPS(filename, lp, vs, variablesNames, constraintsNames)
def get_status(self, filename):
cbcStatus = {
"Optimal": constants.LpStatusOptimal,
"Infeasible": constants.LpStatusInfeasible,
"Integer": constants.LpStatusInfeasible,
"Unbounded": constants.LpStatusUnbounded,
"Stopped": constants.LpStatusNotSolved,
}
cbcSolStatus = {
"Optimal": constants.LpSolutionOptimal,
"Infeasible": constants.LpSolutionInfeasible,
"Unbounded": constants.LpSolutionUnbounded,
"Stopped": constants.LpSolutionNoSolutionFound,
}
with open(filename) as f:
statusstrs = f.readline().split()
status = cbcStatus.get(statusstrs[0], constants.LpStatusUndefined)
sol_status = cbcSolStatus.get(
statusstrs[0], constants.LpSolutionNoSolutionFound
)
# here we could use some regex expression.
# Not sure what's more desirable
if status == constants.LpStatusNotSolved and len(statusstrs) >= 5:
if statusstrs[4] == "objective":
status = constants.LpStatusOptimal
sol_status = constants.LpSolutionIntegerFeasible
return status, sol_status
COIN = COIN_CMD
class PULP_CBC_CMD(COIN_CMD):
"""
This solver uses a precompiled version of cbc provided with the package
"""
name = "PULP_CBC_CMD"
pulp_cbc_path = pulp_cbc_path
try:
if os.name != "nt":
if not os.access(pulp_cbc_path, os.X_OK):
import stat
os.chmod(pulp_cbc_path, stat.S_IXUSR + stat.S_IXOTH)
except: # probably due to incorrect permissions
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(
"PULP_CBC_CMD: Not Available (check permissions on %s)"
% self.pulp_cbc_path
)
else:
def __init__(
self,
mip=True,
msg=True,
timeLimit=None,
fracGap=None,
maxSeconds=None,
gapRel=None,
gapAbs=None,
presolve=None,
cuts=None,
strong=None,
options=None,
warmStart=False,
keepFiles=False,
path=None,
threads=None,
logPath=None,
mip_start=False,
timeMode="elapsed",
):
if path is not None:
raise PulpSolverError("Use COIN_CMD if you want to set a path")
# check that the file is executable
COIN_CMD.__init__(
self,
path=self.pulp_cbc_path,
mip=mip,
msg=msg,
timeLimit=timeLimit,
fracGap=fracGap,
maxSeconds=maxSeconds,
gapRel=gapRel,
gapAbs=gapAbs,
presolve=presolve,
cuts=cuts,
strong=strong,
options=options,
warmStart=warmStart,
keepFiles=keepFiles,
threads=threads,
logPath=logPath,
mip_start=mip_start,
timeMode=timeMode,
)
def COINMP_DLL_load_dll(path):
"""
function that loads the DLL useful for debugging installation problems
"""
if os.name == "nt":
lib = ctypes.windll.LoadLibrary(str(path[-1]))
else:
# linux hack to get working
mode = ctypes.RTLD_GLOBAL
for libpath in path[:-1]:
# RTLD_LAZY = 0x00001
ctypes.CDLL(libpath, mode=mode)
lib = ctypes.CDLL(path[-1], mode=mode)
return lib
class COINMP_DLL(LpSolver):
"""
The COIN_MP LP MIP solver (via a DLL or linux so)
:param timeLimit: The number of seconds before forcing the solver to exit
:param epgap: The fractional mip tolerance
"""
name = "COINMP_DLL"
try:
lib = COINMP_DLL_load_dll(coinMP_path)
except (ImportError, OSError):
@classmethod
def available(cls):
"""True if the solver is available"""
return False
def actualSolve(self, lp):
"""Solve a well formulated lp problem"""
raise PulpSolverError("COINMP_DLL: Not Available")
else:
COIN_INT_LOGLEVEL = 7
COIN_REAL_MAXSECONDS = 16
COIN_REAL_MIPMAXSEC = 19
COIN_REAL_MIPFRACGAP = 34
lib.CoinGetInfinity.restype = ctypes.c_double
lib.CoinGetVersionStr.restype = ctypes.c_char_p
lib.CoinGetSolutionText.restype = ctypes.c_char_p
lib.CoinGetObjectValue.restype = ctypes.c_double
lib.CoinGetMipBestBound.restype = ctypes.c_double
def __init__(
self,
cuts=1,
presolve=1,
dual=1,
crash=0,
scale=1,
rounding=1,
integerPresolve=1,
strong=5,
epgap=None,
*args,
**kwargs,
):
LpSolver.__init__(self, *args, **kwargs)
self.fracGap = None
if epgap is not None:
self.fracGap = float(epgap)
if self.timeLimit is not None:
self.timeLimit = float(self.timeLimit)
# Todo: these options are not yet implemented
self.cuts = cuts
self.presolve = presolve
self.dual = dual
self.crash = crash
self.scale = scale
self.rounding = rounding
self.integerPresolve = integerPresolve
self.strong = strong
def copy(self):
"""Make a copy of self"""
aCopy = LpSolver.copy(self)
aCopy.cuts = self.cuts
aCopy.presolve = self.presolve
aCopy.dual = self.dual
aCopy.crash = self.crash
aCopy.scale = self.scale
aCopy.rounding = self.rounding
aCopy.integerPresolve = self.integerPresolve
aCopy.strong = self.strong
return aCopy
@classmethod
def available(cls):
"""True if the solver is available"""
return True
def getSolverVersion(self):
"""
returns a solver version string
example:
>>> COINMP_DLL().getSolverVersion() # doctest: +ELLIPSIS
'...'
"""
return self.lib.CoinGetVersionStr()
def actualSolve(self, lp):
"""Solve a well formulated lp problem"""
# TODO alter so that msg parameter is handled correctly
self.debug = 0
# initialise solver
self.lib.CoinInitSolver("")
# create problem
self.hProb = hProb = self.lib.CoinCreateProblem(lp.name)
# set problem options
self.lib.CoinSetIntOption(
hProb, self.COIN_INT_LOGLEVEL, ctypes.c_int(self.msg)
)
if self.timeLimit:
if self.mip:
self.lib.CoinSetRealOption(
hProb, self.COIN_REAL_MIPMAXSEC, ctypes.c_double(self.timeLimit)
)
else:
self.lib.CoinSetRealOption(
hProb,
self.COIN_REAL_MAXSECONDS,
ctypes.c_double(self.timeLimit),
)
if self.fracGap:
# Hopefully this is the bound gap tolerance
self.lib.CoinSetRealOption(
hProb, self.COIN_REAL_MIPFRACGAP, ctypes.c_double(self.fracGap)
)
# CoinGetInfinity is needed for varibles with no bounds
coinDblMax = self.lib.CoinGetInfinity()
if self.debug:
print("Before getCoinMPArrays")
(
numVars,
numRows,
numels,
rangeCount,
objectSense,
objectCoeffs,
objectConst,
rhsValues,
rangeValues,
rowType,
startsBase,
lenBase,
indBase,
elemBase,
lowerBounds,
upperBounds,
initValues,
colNames,
rowNames,
columnType,
n2v,
n2c,
) = self.getCplexStyleArrays(lp)
self.lib.CoinLoadProblem(
hProb,
numVars,
numRows,
numels,
rangeCount,
objectSense,
objectConst,
objectCoeffs,
lowerBounds,
upperBounds,
rowType,
rhsValues,
rangeValues,
startsBase,
lenBase,
indBase,
elemBase,
colNames,
rowNames,
"Objective",
)
if lp.isMIP() and self.mip:
self.lib.CoinLoadInteger(hProb, columnType)
if self.msg == 0:
self.lib.CoinRegisterMsgLogCallback(
hProb, ctypes.c_char_p(""), ctypes.POINTER(ctypes.c_int)()
)
self.coinTime = -clock()
self.lib.CoinOptimizeProblem(hProb, 0)
self.coinTime += clock()
# TODO: check Integer Feasible status
CoinLpStatus = {
0: constants.LpStatusOptimal,
1: constants.LpStatusInfeasible,
2: constants.LpStatusInfeasible,
3: constants.LpStatusNotSolved,
4: constants.LpStatusNotSolved,
5: constants.LpStatusNotSolved,
-1: constants.LpStatusUndefined,
}
solutionStatus = self.lib.CoinGetSolutionStatus(hProb)
solutionText = self.lib.CoinGetSolutionText(hProb)
objectValue = self.lib.CoinGetObjectValue(hProb)
# get the solution values
NumVarDoubleArray = ctypes.c_double * numVars
NumRowsDoubleArray = ctypes.c_double * numRows
cActivity = NumVarDoubleArray()
cReducedCost = NumVarDoubleArray()
cSlackValues = NumRowsDoubleArray()
cShadowPrices = NumRowsDoubleArray()
self.lib.CoinGetSolutionValues(
hProb,
ctypes.byref(cActivity),
ctypes.byref(cReducedCost),
ctypes.byref(cSlackValues),
ctypes.byref(cShadowPrices),
)
variablevalues = {}
variabledjvalues = {}
constraintpivalues = {}
constraintslackvalues = {}
if lp.isMIP() and self.mip:
lp.bestBound = self.lib.CoinGetMipBestBound(hProb)
for i in range(numVars):
variablevalues[self.n2v[i].name] = cActivity[i]
variabledjvalues[self.n2v[i].name] = cReducedCost[i]
lp.assignVarsVals(variablevalues)
lp.assignVarsDj(variabledjvalues)
# put pi and slack variables against the constraints
for i in range(numRows):
constraintpivalues[self.n2c[i]] = cShadowPrices[i]
constraintslackvalues[self.n2c[i]] = cSlackValues[i]
lp.assignConsPi(constraintpivalues)
lp.assignConsSlack(constraintslackvalues)
self.lib.CoinFreeSolver()
status = CoinLpStatus[self.lib.CoinGetSolutionStatus(hProb)]
lp.assignStatus(status)
return status
if COINMP_DLL.available():
COIN = COINMP_DLL
yaposib = None
class YAPOSIB(LpSolver):
"""
COIN OSI (via its python interface)
Copyright Christophe-Marie Duquesne 2012
The yaposib variables are available (after a solve) in var.solverVar
The yaposib constraints are available in constraint.solverConstraint
The Model is in prob.solverModel
"""
name = "YAPOSIB"
try:
# import the model into the global scope
global yaposib
import yaposib
except ImportError:
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("YAPOSIB: Not Available")
else:
def __init__(
self,
mip=True,
msg=True,
timeLimit=None,
epgap=None,
solverName=None,
**solverParams,
):
"""
Initializes the yaposib solver.
@param mip: if False the solver will solve a MIP as
an LP
@param msg: displays information from the solver to
stdout
@param timeLimit: not supported
@param epgap: not supported
@param solverParams: not supported
"""
LpSolver.__init__(self, mip, msg)
if solverName:
self.solverName = solverName
else:
self.solverName = yaposib.available_solvers()[0]
def findSolutionValues(self, lp):
model = lp.solverModel
solutionStatus = model.status
yaposibLpStatus = {
"optimal": constants.LpStatusOptimal,
"undefined": constants.LpStatusUndefined,
"abandoned": constants.LpStatusInfeasible,
"infeasible": constants.LpStatusInfeasible,
"limitreached": constants.LpStatusInfeasible,
}
# populate pulp solution values
for var in lp.variables():
var.varValue = var.solverVar.solution
var.dj = var.solverVar.reducedcost
# put pi and slack variables against the constraints
for constr in lp.constraints.values():
constr.pi = constr.solverConstraint.dual
constr.slack = -constr.constant - constr.solverConstraint.activity
if self.msg:
print("yaposib status=", solutionStatus)
lp.resolveOK = True
for var in lp.variables():
var.isModified = False
status = yaposibLpStatus.get(solutionStatus, constants.LpStatusUndefined)
lp.assignStatus(status)
return status
def available(self):
"""True if the solver is available"""
return True
def callSolver(self, lp, callback=None):
"""Solves the problem with yaposib"""
savestdout = None
if self.msg == 0:
# close stdout to get rid of messages
tempfile = open(mktemp(), "w")
savestdout = os.dup(1)
os.close(1)
if os.dup(tempfile.fileno()) != 1:
raise PulpSolverError("couldn't redirect stdout - dup() error")
self.solveTime = -clock()
lp.solverModel.solve(self.mip)
self.solveTime += clock()
if self.msg == 0:
# reopen stdout
os.close(1)
os.dup(savestdout)
os.close(savestdout)
def buildSolverModel(self, lp):
"""
Takes the pulp lp model and translates it into a yaposib model
"""
log.debug("create the yaposib model")
lp.solverModel = yaposib.Problem(self.solverName)
prob = lp.solverModel
prob.name = lp.name
log.debug("set the sense of the problem")
if lp.sense == constants.LpMaximize:
prob.obj.maximize = True
log.debug("add the variables to the problem")
for var in lp.variables():
col = prob.cols.add(yaposib.vec([]))
col.name = var.name
if not var.lowBound is None:
col.lowerbound = var.lowBound
if not var.upBound is None:
col.upperbound = var.upBound
if var.cat == constants.LpInteger:
col.integer = True
prob.obj[col.index] = lp.objective.get(var, 0.0)
var.solverVar = col
log.debug("add the Constraints to the problem")
for name, constraint in lp.constraints.items():
row = prob.rows.add(
yaposib.vec(
[
(var.solverVar.index, value)
for var, value in constraint.items()
]
)
)
if constraint.sense == constants.LpConstraintLE:
row.upperbound = -constraint.constant
elif constraint.sense == constants.LpConstraintGE:
row.lowerbound = -constraint.constant
elif constraint.sense == constants.LpConstraintEQ:
row.upperbound = -constraint.constant
row.lowerbound = -constraint.constant
else:
raise PulpSolverError("Detected an invalid constraint type")
row.name = name
constraint.solverConstraint = row
def actualSolve(self, lp, callback=None):
"""
Solve a well formulated lp problem
creates a yaposib model, variables and constraints and attaches
them to the lp model which it then solves
"""
self.buildSolverModel(lp)
# set the initial solution
log.debug("Solve the model using yaposib")
self.callSolver(lp, callback=callback)
# get the solution information
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
"""
log.debug("Resolve the model using yaposib")
for constraint in lp.constraints.values():
row = constraint.solverConstraint
if constraint.modified:
if constraint.sense == constants.LpConstraintLE:
row.upperbound = -constraint.constant
elif constraint.sense == constants.LpConstraintGE:
row.lowerbound = -constraint.constant
elif constraint.sense == constants.LpConstraintEQ:
row.upperbound = -constraint.constant
row.lowerbound = -constraint.constant
else:
raise PulpSolverError("Detected an invalid constraint type")
self.callSolver(lp, callback=callback)
# get the solution information
solutionStatus = self.findSolutionValues(lp)
for var in lp.variables():
var.modified = False
for constraint in lp.constraints.values():
constraint.modified = False
return solutionStatus