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配置文件
This commit is contained in:
401
utils/pulp/mps_lp.py
Normal file
401
utils/pulp/mps_lp.py
Normal file
@@ -0,0 +1,401 @@
|
||||
"""
|
||||
|
||||
@author: Franco Peschiera
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
from . import constants as const
|
||||
|
||||
CORE_FILE_ROW_MODE = "ROWS"
|
||||
CORE_FILE_COL_MODE = "COLUMNS"
|
||||
CORE_FILE_RHS_MODE = "RHS"
|
||||
CORE_FILE_BOUNDS_MODE = "BOUNDS"
|
||||
|
||||
CORE_FILE_BOUNDS_MODE_NAME_GIVEN = "BOUNDS_NAME"
|
||||
CORE_FILE_BOUNDS_MODE_NO_NAME = "BOUNDS_NO_NAME"
|
||||
CORE_FILE_RHS_MODE_NAME_GIVEN = "RHS_NAME"
|
||||
CORE_FILE_RHS_MODE_NO_NAME = "RHS_NO_NAME"
|
||||
|
||||
ROW_MODE_OBJ = "N"
|
||||
|
||||
BOUNDS_EQUIV = dict(LO="lowBound", UP="upBound")
|
||||
|
||||
ROW_EQUIV = {v: k for k, v in const.LpConstraintTypeToMps.items()}
|
||||
COL_EQUIV = {1: "Integer", 0: "Continuous"}
|
||||
ROW_DEFAULT = dict(pi=None, constant=0)
|
||||
COL_DEFAULT = dict(lowBound=0, upBound=None, varValue=None, dj=None)
|
||||
|
||||
|
||||
def readMPS(path, sense, dropConsNames=False):
|
||||
"""
|
||||
adapted from Julian Märte (https://github.com/pchtsp/pysmps)
|
||||
returns a dictionary with the contents of the model.
|
||||
This dictionary can be used to generate an LpProblem
|
||||
|
||||
:param path: path of mps file
|
||||
:param sense: 1 for minimize, -1 for maximize
|
||||
:param dropConsNames: if True, do not store the names of constraints
|
||||
:return: a dictionary with all the problem data
|
||||
"""
|
||||
|
||||
mode = ""
|
||||
parameters = dict(name="", sense=sense, status=0, sol_status=0)
|
||||
variable_info = {}
|
||||
constraints = {}
|
||||
objective = dict(name="", coefficients=[])
|
||||
sos1 = []
|
||||
sos2 = []
|
||||
# TODO: maybe take out rhs_names and bnd_names? not sure if they're useful
|
||||
rhs_names = []
|
||||
bnd_names = []
|
||||
integral_marker = False
|
||||
|
||||
with open(path) as reader:
|
||||
for line in reader:
|
||||
line = re.split(" |\t", line)
|
||||
line = [x.strip() for x in line]
|
||||
line = list(filter(None, line))
|
||||
|
||||
if line[0] == "ENDATA":
|
||||
break
|
||||
if line[0] == "*":
|
||||
continue
|
||||
if line[0] == "NAME":
|
||||
if len(line) > 1:
|
||||
parameters["name"] = line[1]
|
||||
else:
|
||||
parameters["name"] = ""
|
||||
continue
|
||||
|
||||
# here we get the mode
|
||||
if line[0] in [CORE_FILE_ROW_MODE, CORE_FILE_COL_MODE]:
|
||||
mode = line[0]
|
||||
elif line[0] == CORE_FILE_RHS_MODE and len(line) <= 2:
|
||||
if len(line) > 1:
|
||||
rhs_names.append(line[1])
|
||||
mode = CORE_FILE_RHS_MODE_NAME_GIVEN
|
||||
else:
|
||||
mode = CORE_FILE_RHS_MODE_NO_NAME
|
||||
elif line[0] == CORE_FILE_BOUNDS_MODE and len(line) <= 2:
|
||||
if len(line) > 1:
|
||||
bnd_names.append(line[1])
|
||||
mode = CORE_FILE_BOUNDS_MODE_NAME_GIVEN
|
||||
else:
|
||||
mode = CORE_FILE_BOUNDS_MODE_NO_NAME
|
||||
|
||||
# here we query the mode variable
|
||||
elif mode == CORE_FILE_ROW_MODE:
|
||||
row_type = line[0]
|
||||
row_name = line[1]
|
||||
if row_type == ROW_MODE_OBJ:
|
||||
objective["name"] = row_name
|
||||
else:
|
||||
constraints[row_name] = dict(
|
||||
sense=ROW_EQUIV[row_type],
|
||||
name=row_name,
|
||||
coefficients=[],
|
||||
**ROW_DEFAULT,
|
||||
)
|
||||
elif mode == CORE_FILE_COL_MODE:
|
||||
var_name = line[0]
|
||||
if len(line) > 1 and line[1] == "'MARKER'":
|
||||
if line[2] == "'INTORG'":
|
||||
integral_marker = True
|
||||
elif line[2] == "'INTEND'":
|
||||
integral_marker = False
|
||||
continue
|
||||
if var_name not in variable_info:
|
||||
variable_info[var_name] = dict(
|
||||
cat=COL_EQUIV[integral_marker], name=var_name, **COL_DEFAULT
|
||||
)
|
||||
j = 1
|
||||
while j < len(line) - 1:
|
||||
if line[j] == objective["name"]:
|
||||
# we store the variable objective coefficient
|
||||
objective["coefficients"].append(
|
||||
dict(name=var_name, value=float(line[j + 1]))
|
||||
)
|
||||
else:
|
||||
# we store the variable coefficient
|
||||
constraints[line[j]]["coefficients"].append(
|
||||
dict(name=var_name, value=float(line[j + 1]))
|
||||
)
|
||||
j = j + 2
|
||||
elif mode == CORE_FILE_RHS_MODE_NAME_GIVEN:
|
||||
if line[0] != rhs_names[-1]:
|
||||
raise Exception(
|
||||
"Other RHS name was given even though name was set after RHS tag."
|
||||
)
|
||||
readMPSSetRhs(line, constraints)
|
||||
elif mode == CORE_FILE_RHS_MODE_NO_NAME:
|
||||
readMPSSetRhs(line, constraints)
|
||||
if line[0] not in rhs_names:
|
||||
rhs_names.append(line[0])
|
||||
elif mode == CORE_FILE_BOUNDS_MODE_NAME_GIVEN:
|
||||
if line[1] != bnd_names[-1]:
|
||||
raise Exception(
|
||||
"Other BOUNDS name was given even though name was set after BOUNDS tag."
|
||||
)
|
||||
readMPSSetBounds(line, variable_info)
|
||||
elif mode == CORE_FILE_BOUNDS_MODE_NO_NAME:
|
||||
readMPSSetBounds(line, variable_info)
|
||||
if line[1] not in bnd_names:
|
||||
bnd_names.append(line[1])
|
||||
constraints = list(constraints.values())
|
||||
if dropConsNames:
|
||||
for c in constraints:
|
||||
c["name"] = None
|
||||
objective["name"] = None
|
||||
variable_info = list(variable_info.values())
|
||||
return dict(
|
||||
parameters=parameters,
|
||||
objective=objective,
|
||||
variables=variable_info,
|
||||
constraints=constraints,
|
||||
sos1=sos1,
|
||||
sos2=sos2,
|
||||
)
|
||||
|
||||
|
||||
def readMPSSetBounds(line, variable_dict):
|
||||
bound = line[0]
|
||||
var_name = line[2]
|
||||
|
||||
def set_one_bound(bound_type, value):
|
||||
variable_dict[var_name][BOUNDS_EQUIV[bound_type]] = value
|
||||
|
||||
def set_both_bounds(value_low, value_up):
|
||||
set_one_bound("LO", value_low)
|
||||
set_one_bound("UP", value_up)
|
||||
|
||||
if bound == "FR":
|
||||
set_both_bounds(None, None)
|
||||
return
|
||||
elif bound == "BV":
|
||||
set_both_bounds(0, 1)
|
||||
return
|
||||
value = float(line[3])
|
||||
if bound in ["LO", "UP"]:
|
||||
set_one_bound(bound, value)
|
||||
elif bound == "FX":
|
||||
set_both_bounds(value, value)
|
||||
return
|
||||
|
||||
|
||||
def readMPSSetRhs(line, constraintsDict):
|
||||
constraintsDict[line[1]]["constant"] = -float(line[2])
|
||||
if len(line) == 5: # read fields 5, 6
|
||||
constraintsDict[line[3]]["constant"] = -float(line[4])
|
||||
return
|
||||
|
||||
|
||||
def writeMPS(
|
||||
LpProblem, filename, mpsSense=0, rename=0, mip=1, with_objsense: bool = False
|
||||
):
|
||||
wasNone, dummyVar = LpProblem.fixObjective()
|
||||
if mpsSense == 0:
|
||||
mpsSense = LpProblem.sense
|
||||
cobj = LpProblem.objective
|
||||
if mpsSense != LpProblem.sense:
|
||||
n = cobj.name
|
||||
cobj = -cobj
|
||||
cobj.name = n
|
||||
if rename:
|
||||
constrNames, varNames, cobj.name = LpProblem.normalisedNames()
|
||||
# No need to call self.variables() again, we have just filled self._variables:
|
||||
vs = LpProblem._variables
|
||||
else:
|
||||
vs = LpProblem.variables()
|
||||
varNames = {v.name: v.name for v in vs}
|
||||
constrNames = {c: c for c in LpProblem.constraints}
|
||||
model_name = LpProblem.name
|
||||
if rename:
|
||||
model_name = "MODEL"
|
||||
objName = cobj.name
|
||||
if not objName:
|
||||
objName = "OBJ"
|
||||
|
||||
# constraints
|
||||
row_lines = [
|
||||
" " + const.LpConstraintTypeToMps[c.sense] + " " + constrNames[k] + "\n"
|
||||
for k, c in LpProblem.constraints.items()
|
||||
]
|
||||
# Creation of a dict of dict:
|
||||
# coefs[variable_name][constraint_name] = coefficient
|
||||
coefs = {varNames[v.name]: {} for v in vs}
|
||||
for k, c in LpProblem.constraints.items():
|
||||
k = constrNames[k]
|
||||
for v, value in c.items():
|
||||
coefs[varNames[v.name]][k] = value
|
||||
|
||||
# matrix
|
||||
columns_lines = []
|
||||
for v in vs:
|
||||
name = varNames[v.name]
|
||||
columns_lines.extend(
|
||||
writeMPSColumnLines(coefs[name], v, mip, name, cobj, objName)
|
||||
)
|
||||
|
||||
# right hand side
|
||||
rhs_lines = [
|
||||
" RHS %-8s % .12e\n"
|
||||
% (constrNames[k], -c.constant if c.constant != 0 else 0)
|
||||
for k, c in LpProblem.constraints.items()
|
||||
]
|
||||
# bounds
|
||||
bound_lines = []
|
||||
for v in vs:
|
||||
bound_lines.extend(writeMPSBoundLines(varNames[v.name], v, mip))
|
||||
|
||||
with open(filename, "w") as f:
|
||||
if with_objsense:
|
||||
f.write("OBJSENSE\n")
|
||||
f.write(f" {const.LpSensesMPS[mpsSense]}\n")
|
||||
else:
|
||||
f.write(f"*SENSE:{const.LpSenses[mpsSense]}\n")
|
||||
f.write(f"NAME {model_name}\n")
|
||||
f.write("ROWS\n")
|
||||
f.write(f" N {objName}\n")
|
||||
f.write("".join(row_lines))
|
||||
f.write("COLUMNS\n")
|
||||
f.write("".join(columns_lines))
|
||||
f.write("RHS\n")
|
||||
f.write("".join(rhs_lines))
|
||||
f.write("BOUNDS\n")
|
||||
f.write("".join(bound_lines))
|
||||
f.write("ENDATA\n")
|
||||
LpProblem.restoreObjective(wasNone, dummyVar)
|
||||
# returns the variables, in writing order
|
||||
if rename == 0:
|
||||
return vs
|
||||
else:
|
||||
return vs, varNames, constrNames, cobj.name
|
||||
|
||||
|
||||
def writeMPSColumnLines(cv, variable, mip, name, cobj, objName):
|
||||
columns_lines = []
|
||||
if mip and variable.cat == const.LpInteger:
|
||||
columns_lines.append(" MARK 'MARKER' 'INTORG'\n")
|
||||
# Most of the work is done here
|
||||
_tmp = [" %-8s %-8s % .12e\n" % (name, k, v) for k, v in cv.items()]
|
||||
columns_lines.extend(_tmp)
|
||||
|
||||
# objective function
|
||||
if variable in cobj:
|
||||
columns_lines.append(
|
||||
" %-8s %-8s % .12e\n" % (name, objName, cobj[variable])
|
||||
)
|
||||
if mip and variable.cat == const.LpInteger:
|
||||
columns_lines.append(" MARK 'MARKER' 'INTEND'\n")
|
||||
return columns_lines
|
||||
|
||||
|
||||
def writeMPSBoundLines(name, variable, mip):
|
||||
if variable.lowBound is not None and variable.lowBound == variable.upBound:
|
||||
return [" FX BND %-8s % .12e\n" % (name, variable.lowBound)]
|
||||
elif (
|
||||
variable.lowBound == 0
|
||||
and variable.upBound == 1
|
||||
and mip
|
||||
and variable.cat == const.LpInteger
|
||||
):
|
||||
return [" BV BND %-8s\n" % name]
|
||||
bound_lines = []
|
||||
if variable.lowBound is not None:
|
||||
# In MPS files, variables with no bounds (i.e. >= 0)
|
||||
# are assumed BV by COIN and CPLEX.
|
||||
# So we explicitly write a 0 lower bound in this case.
|
||||
if variable.lowBound != 0 or (
|
||||
mip and variable.cat == const.LpInteger and variable.upBound is None
|
||||
):
|
||||
bound_lines.append(
|
||||
" LO BND %-8s % .12e\n" % (name, variable.lowBound)
|
||||
)
|
||||
else:
|
||||
if variable.upBound is not None:
|
||||
bound_lines.append(" MI BND %-8s\n" % name)
|
||||
else:
|
||||
bound_lines.append(" FR BND %-8s\n" % name)
|
||||
if variable.upBound is not None:
|
||||
bound_lines.append(" UP BND %-8s % .12e\n" % (name, variable.upBound))
|
||||
return bound_lines
|
||||
|
||||
|
||||
def writeLP(LpProblem, filename, writeSOS=1, mip=1, max_length=100):
|
||||
f = open(filename, "w")
|
||||
f.write("\\* " + LpProblem.name + " *\\\n")
|
||||
if LpProblem.sense == 1:
|
||||
f.write("Minimize\n")
|
||||
else:
|
||||
f.write("Maximize\n")
|
||||
wasNone, objectiveDummyVar = LpProblem.fixObjective()
|
||||
objName = LpProblem.objective.name
|
||||
if not objName:
|
||||
objName = "OBJ"
|
||||
f.write(LpProblem.objective.asCplexLpAffineExpression(objName, constant=0))
|
||||
f.write("Subject To\n")
|
||||
ks = list(LpProblem.constraints.keys())
|
||||
ks.sort()
|
||||
dummyWritten = False
|
||||
for k in ks:
|
||||
constraint = LpProblem.constraints[k]
|
||||
if not list(constraint.keys()):
|
||||
# empty constraint add the dummyVar
|
||||
dummyVar = LpProblem.get_dummyVar()
|
||||
constraint += dummyVar
|
||||
# set this dummyvar to zero so infeasible problems are not made feasible
|
||||
if not dummyWritten:
|
||||
f.write((dummyVar == 0.0).asCplexLpConstraint("_dummy"))
|
||||
dummyWritten = True
|
||||
f.write(constraint.asCplexLpConstraint(k))
|
||||
# check if any names are longer than 100 characters
|
||||
LpProblem.checkLengthVars(max_length)
|
||||
vs = LpProblem.variables()
|
||||
# check for repeated names
|
||||
LpProblem.checkDuplicateVars()
|
||||
# Bounds on non-"positive" variables
|
||||
# Note: XPRESS and CPLEX do not interpret integer variables without
|
||||
# explicit bounds
|
||||
if mip:
|
||||
vg = [
|
||||
v
|
||||
for v in vs
|
||||
if not (v.isPositive() and v.cat == const.LpContinuous) and not v.isBinary()
|
||||
]
|
||||
else:
|
||||
vg = [v for v in vs if not v.isPositive()]
|
||||
if vg:
|
||||
f.write("Bounds\n")
|
||||
for v in vg:
|
||||
f.write(f" {v.asCplexLpVariable()}\n")
|
||||
# Integer non-binary variables
|
||||
if mip:
|
||||
vg = [v for v in vs if v.cat == const.LpInteger and not v.isBinary()]
|
||||
if vg:
|
||||
f.write("Generals\n")
|
||||
for v in vg:
|
||||
f.write(f"{v.name}\n")
|
||||
# Binary variables
|
||||
vg = [v for v in vs if v.isBinary()]
|
||||
if vg:
|
||||
f.write("Binaries\n")
|
||||
for v in vg:
|
||||
f.write(f"{v.name}\n")
|
||||
# Special Ordered Sets
|
||||
if writeSOS and (LpProblem.sos1 or LpProblem.sos2):
|
||||
f.write("SOS\n")
|
||||
if LpProblem.sos1:
|
||||
for sos in LpProblem.sos1.values():
|
||||
f.write("S1:: \n")
|
||||
for v, val in sos.items():
|
||||
f.write(f" {v.name}: {val:.12g}\n")
|
||||
if LpProblem.sos2:
|
||||
for sos in LpProblem.sos2.values():
|
||||
f.write("S2:: \n")
|
||||
for v, val in sos.items():
|
||||
f.write(f" {v.name}: {val:.12g}\n")
|
||||
f.write("End\n")
|
||||
f.close()
|
||||
LpProblem.restoreObjective(wasNone, objectiveDummyVar)
|
||||
return vs
|
||||
Reference in New Issue
Block a user