Skip to content

Commit

Permalink
Merge branch 'master' into copt_pulp
Browse files Browse the repository at this point in the history
  • Loading branch information
wujianjack committed Sep 20, 2023
2 parents 44d355b + bd7cace commit 4e2ca5f
Show file tree
Hide file tree
Showing 16 changed files with 107 additions and 65 deletions.
21 changes: 8 additions & 13 deletions .github/workflows/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
name: Publish Python 🐍 distributions 📦 to PyPI

on: push
on: [push, pull_request, workflow_dispatch]

jobs:
build-n-publish:
name: Build and publish Python 🐍 distributions 📦 to PyPI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Set up Python 3.10
- uses: actions/checkout@v3
- name: Set up Python 3
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install wheel
run: >-
python -m
pip install
wheel
--user
python-version: "3.x"
- run: pip install -U wheel build
- name: Build a binary wheel and a source tarball
run: python setup.py sdist bdist_wheel
run: python -m build
- name: Publish distribution 📦 to Test PyPI
if: startsWith(github.event.ref, 'refs/tags')
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.test_pypi_password }}
repository_url: https://test.pypi.org/legacy/
- name: Publish distribution 📦 to PyPI
if: startsWith(github.event.ref, 'refs/tags')
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.pypi_password }}
8 changes: 4 additions & 4 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install .
- name: Install highspy
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
run: |
pip install highspy
# - name: Install highspy
# if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
# run: |
# pip install highspy
- name: Install coptpy
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest' || matrix.os == 'windows-latest'
run: |
Expand Down
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
pulp
**************************

.. image:: https://travis-ci.org/coin-or/pulp.svg?branch=master
:target: https://travis-ci.org/coin-or/pulp
.. image:: https://img.shields.io/pypi/v/pulp
:target: https://pypi.org/project/PuLP/
:alt: PyPI
.. image:: https://img.shields.io/pypi/dm/pulp
:target: https://pypi.org/project/PuLP/
:alt: PyPI - Downloads

PuLP is an LP modeler written in Python. PuLP can generate MPS or LP files and call GLPK_, COIN-OR CLP/`CBC`_, CPLEX_, GUROBI_, MOSEK_, XPRESS_, CHOCO_, MIPCL_, HiGHS_, SCIP_/FSCIP_ to solve linear problems.

Expand Down
2 changes: 0 additions & 2 deletions examples/CG.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def trim(self):


def masterSolve(Patterns, rollData, relax=True):

# The rollData is made into separate dictionaries
(rollDemand, surplusPrice) = splitDict(rollData)

Expand Down Expand Up @@ -93,7 +92,6 @@ def masterSolve(Patterns, rollData, relax=True):


def subSolve(Patterns, duals):

# The variable 'prob' is created
prob = LpProblem("SubProb", LpMinimize)

Expand Down
5 changes: 0 additions & 5 deletions examples/CGcolumnwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ def trim(self):


def createMaster():

rollData = { # Length Demand SalePrice
"5": [150, 0.25],
"7": [200, 0.33],
Expand Down Expand Up @@ -73,12 +72,10 @@ def createMaster():


def addPatterns(obj, constraints, newPatterns):

# A list called Patterns is created to contain all the Pattern class
# objects created in this function call
Patterns = []
for i in newPatterns:

# The new patterns are checked to see that their length does not exceed
# the total roll length
lsum = 0
Expand Down Expand Up @@ -109,7 +106,6 @@ def addPatterns(obj, constraints, newPatterns):


def masterSolve(prob, relax=True):

# Unrelaxes the Integer Constraint
if not relax:
for v in prob.variables():
Expand All @@ -135,7 +131,6 @@ def masterSolve(prob, relax=True):


def subSolve(duals):

# The variable 'prob' is created
prob = LpProblem("SubProb", LpMinimize)

Expand Down
1 change: 0 additions & 1 deletion examples/SpongeRollProblem5.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

# This loop will be repeated until morePatterns is set to False
while morePatterns == True:

# Solve the problem as a Relaxed LP
duals = masterSolve(Patterns, rollData)

Expand Down
2 changes: 1 addition & 1 deletion examples/Sudoku1.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
(5, 8, 9),
]

for (v, r, c) in input_data:
for v, r, c in input_data:
prob += choices[v][r][c] == 1

# The problem data is written to an .lp file
Expand Down
2 changes: 1 addition & 1 deletion examples/Sudoku2.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
# (5, 8, 9)
]

for (v, r, c) in input_data:
for v, r, c in input_data:
prob += choices[v][r][c] == 1

# The problem data is written to an .lp file
Expand Down
4 changes: 2 additions & 2 deletions pulp/apis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def setConfigInformation(**keywords):
config = Parser()
config.read(config_filename)
# set the new keys
for (key, val) in keywords.items():
for key, val in keywords.items():
config.set("locations", key, val)
# write the new configuration
fp = open(config_filename, "w")
Expand All @@ -85,7 +85,7 @@ def configSolvers():
+ "for each solver available"
)
configdict = {}
for (default, key, msg) in configlist:
for default, key, msg in configlist:
value = input(msg + "[" + str(default) + "]")
if value:
configdict[key] = value
Expand Down
16 changes: 8 additions & 8 deletions pulp/apis/coin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def solve_CBC(self, lp, use_mps=True):
)
cmds = " " + tmpMps + " "
if lp.sense == constants.LpMaximize:
cmds += "max "
cmds += "-max "
else:
vs = lp.writeLP(tmpLp)
# In the Lp we do not create new variable or constraint names:
Expand All @@ -164,18 +164,18 @@ def solve_CBC(self, lp, use_mps=True):
cmds = " " + tmpLp + " "
if self.optionsDict.get("warmStart", False):
self.writesol(tmpMst, lp, vs, variablesNames, constraintsNames)
cmds += f"mips {tmpMst} "
cmds += f"-mips {tmpMst} "
if self.timeLimit is not None:
cmds += f"sec {self.timeLimit} "
cmds += f"-sec {self.timeLimit} "
options = self.options + self.getOptions()
for option in options:
cmds += option + " "
cmds += "-" + option + " "
if self.mip:
cmds += "branch "
cmds += "-branch "
else:
cmds += "initialSolve "
cmds += "printingOptions all "
cmds += "solution " + tmpSol + " "
cmds += "-initialSolve "
cmds += "-printingOptions all "
cmds += "-solution " + tmpSol + " "
if self.msg:
pipe = None
else:
Expand Down
2 changes: 1 addition & 1 deletion pulp/apis/cplex_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ class CPLEX_PY(LpSolver):
try:
global cplex
import cplex
except (Exception) as e:
except Exception as e:
err = e
"""The CPLEX LP/MIP solver from python PHANTOM Something went wrong!!!!"""

Expand Down
73 changes: 60 additions & 13 deletions pulp/apis/highs_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,11 @@ def actualSolve(self, lp):
constants.LpStatusUnbounded,
constants.LpSolutionNoSolutionFound,
)
else:
raise PulpSolverError("Pulp: Error while executing", self.path)
else: # no solution
status, status_sol = (
constants.LpStatusNotSolved,
constants.LpSolutionNoSolutionFound,
)

if not os.path.exists(tmpSol) or os.stat(tmpSol).st_size == 0:
status_sol = constants.LpSolutionNoSolutionFound
Expand Down Expand Up @@ -229,7 +232,6 @@ def readsol(variables, filename):


class HiGHS(LpSolver):

name = "HiGHS"

try:
Expand All @@ -246,33 +248,75 @@ def actualSolve(self, lp, callback=None):
raise PulpSolverError("HiGHS: Not Available")

else:
# Note(maciej): It was surprising to me that higshpy wasn't logging out of the box,
# even with the different logging options set. This callback seems to work, but there
# are probably better ways of doing this ¯\_(ツ)_/¯
DEFAULT_CALLBACK = lambda logType, logMsg, callbackValue: print(
f"[{logType.name}] {logMsg}"
)
DEFAULT_CALLBACK_VALUE = ""

def __init__(
self,
mip=True,
msg=True,
timeLimit=None,
epgap=None,
callbackTuple=None,
gapAbs=None,
gapRel=None,
warmStart=False,
logPath=None,
*args,
threads=None,
timeLimit=None,
**solverParams,
):
super().__init__(mip, msg, timeLimit, gapRel=gapRel, *args, **solverParams)
"""
:param bool mip: if False, assume LP even if integer variables
:param bool msg: if False, no log is shown
:param tuple callbackTuple: Tuple of log callback function (see DEFAULT_CALLBACK above for definition)
and callbackValue (tag embedded in every callback)
: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 float timeLimit: maximum time for solver (in seconds)
:param dict solverParams: list of named options to pass directly to the HiGHS solver
"""
super().__init__(mip=mip, msg=msg, timeLimit=timeLimit, **solverParams)
self.callbackTuple = callbackTuple
self.gapAbs = gapAbs
self.gapRel = gapRel
self.threads = threads

def available(self):
return True

def callSolver(self, lp):
lp.solverModel.run()

def buildSolverModel(self, lp):
def createAndConfigureSolver(self, lp):
lp.solverModel = highspy.Highs()

gapRel = self.optionsDict.get("gapRel", 0)
lp.solverModel.setOptionValue("mip_rel_gap", gapRel)
if self.msg or self.callbackTuple:
callbackTuple = self.callbackTuple or (
HiGHS.DEFAULT_CALLBACK,
HiGHS.DEFAULT_CALLBACK_VALUE,
)
lp.solverModel.setLogCallback(*callbackTuple)

if self.gapRel is not None:
lp.solverModel.setOptionValue("mip_rel_gap", self.gapRel)

if self.gapAbs is not None:
lp.solverModel.setOptionValue("mip_abs_gap", self.gapAbs)

if self.threads is not None:
lp.solverModel.setOptionValue("threads", self.threads)

if self.timeLimit is not None:
lp.solverModel.setOptionValue("time_limit", float(self.timeLimit))

# set remaining parameter values
for key, value in self.optionsDict.items():
lp.solverModel.setOptionValue(key, value)

def buildSolverModel(self, lp):
inf = highspy.kHighsInf

obj_mult = -1 if lp.sense == constants.LpMaximize else 1
Expand Down Expand Up @@ -340,19 +384,22 @@ def findSolutionValues(self, lp):
HighsModelStatus.kUnknown: constants.LpStatusNotSolved,
}

col_values = list(solution.col_value)
for var in lp.variables():
var.varValue = solution.col_value[var.index]
var.varValue = col_values[var.index]

return status_dict[status]

def actualSolve(self, lp):
self.createAndConfigureSolver(lp)
self.buildSolverModel(lp)
self.callSolver(lp)

solutionStatus = self.findSolutionValues(lp)

for var in lp.variables():
var.modified = False

for constraint in lp.constraints.values():
constraint.modifier = False

Expand Down
3 changes: 0 additions & 3 deletions pulp/apis/scip_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ def actualSolve(self, lp):
def readsol(filename):
"""Read a SCIP solution file"""
with open(filename) as f:

# First line must contain 'solution status: <something>'
try:
line = f.readline()
Expand Down Expand Up @@ -420,7 +419,6 @@ def parse_variable(string: str) -> Optional[Tuple[str, float]]:
def readsol(filename):
"""Read a FSCIP solution file"""
with open(filename) as file:

# First line must contain a solution status
status_line = file.readline()
status = FSCIP_CMD.parse_status(status_line)
Expand Down Expand Up @@ -469,7 +467,6 @@ class SCIP_PY(LpSolver):
name = "SCIP_PY"

try:

global scip
import pyscipopt as scip

Expand Down
Loading

0 comments on commit 4e2ca5f

Please sign in to comment.