Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add type hints (Experimental) #755

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
add type hints (Experimental)
  • Loading branch information
CubeSugarCheese committed May 29, 2024
commit 755c07799a3b68e54f303d5b6876adef2c218f67
17 changes: 11 additions & 6 deletions pulp/apis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from typing import Any

from pulp.apis.core import LpSolver

from .coin_api import *
from .cplex_api import *
from .gurobi_api import *
Expand Down Expand Up @@ -40,6 +44,7 @@
import json

# Default solver selection
LpSolverDefault: Union[PULP_CBC_CMD, GLPK_CMD, COIN_CMD, None]
if PULP_CBC_CMD().available():
LpSolverDefault = PULP_CBC_CMD()
elif GLPK_CMD().available():
Expand All @@ -50,7 +55,7 @@
LpSolverDefault = None


def setConfigInformation(**keywords):
def setConfigInformation(**keywords: Any):
"""
set the data in the configuration file
at the moment will only edit things in [locations]
Expand Down Expand Up @@ -92,7 +97,7 @@ def configSolvers():
setConfigInformation(**configdict)


def getSolver(solver, *args, **kwargs):
def getSolver(solver: str, *args: Any, **kwargs: Any) -> LpSolver:
"""
Instantiates a solver from its name

Expand All @@ -112,7 +117,7 @@ def getSolver(solver, *args, **kwargs):
)


def getSolverFromDict(data):
def getSolverFromDict(data: Dict[str, Any]) -> LpSolver:
"""
Instantiates a solver from a dictionary with its data

Expand All @@ -128,7 +133,7 @@ def getSolverFromDict(data):
return getSolver(solver, **data)


def getSolverFromJson(filename):
def getSolverFromJson(filename: str) -> LpSolver:
"""
Instantiates a solver from a json file with its data

Expand All @@ -141,15 +146,15 @@ def getSolverFromJson(filename):
return getSolverFromDict(data)


def listSolvers(onlyAvailable=False):
def listSolvers(onlyAvailable: bool = False) -> List[str]:
"""
List the names of all the existing solvers in PuLP

:param bool onlyAvailable: if True, only show the available solvers
:return: list of solver names
:rtype: list
"""
result = []
result: List[str] = []
for s in _all_solvers:
solver = s()
if (not onlyAvailable) or solver.available():
Expand Down
33 changes: 21 additions & 12 deletions pulp/apis/choco_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""

from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple

if TYPE_CHECKING:
from pulp import LpProblem

from .core import LpSolver_CMD, subprocess, PulpSolverError
import os
from .. import constants
Expand All @@ -37,12 +42,12 @@ class CHOCO_CMD(LpSolver_CMD):

def __init__(
self,
path=None,
keepFiles=False,
mip=True,
msg=True,
options=None,
timeLimit=None,
path: Optional[str] = None,
keepFiles: bool = False,
mip: bool = True,
msg: bool = True,
options: Optional[List[Any]] = None,
timeLimit: Optional[float] = None,
):
"""
:param bool mip: if False, assume LP even if integer variables
Expand All @@ -62,15 +67,18 @@ def __init__(
keepFiles=keepFiles,
)

def defaultPath(self):
def defaultPath(self) -> str:
return self.executableExtension("choco-parsers-with-dependencies.jar")

def available(self):
def available(self) -> bool:
"""True if the solver is available"""
java_path = self.executableExtension("java")
return self.executable(self.path) and self.executable(java_path)
return (
self.executable(self.path) is not None
and self.executable(java_path) is not None
)

def actualSolve(self, lp):
def actualSolve(self, lp: "LpProblem") -> int:
"""Solve a well formulated lp problem"""
java_path = self.executableExtension("java")
if not self.executable(java_path):
Expand Down Expand Up @@ -117,12 +125,13 @@ def actualSolve(self, lp):

lp.assignStatus(status, status_sol)
if status not in [constants.LpStatusInfeasible, constants.LpStatusNotSolved]:
assert values is not None
lp.assignVarsVals(values)

return status

@staticmethod
def readsol(filename):
def readsol(filename: str) -> Tuple[int, Dict[str, float], int]:
"""Read a Choco solution file"""
# TODO: figure out the unbounded status in choco solver
chocoStatus = {
Expand All @@ -141,7 +150,7 @@ def readsol(filename):

status = constants.LpStatusNotSolved
sol_status = constants.LpSolutionNoSolutionFound
values = {}
values: Dict[str, float] = {}
with open(filename) as f:
content = f.readlines()
content = [l.strip() for l in content if l[:2] not in ["o ", "c "]]
Expand Down
78 changes: 45 additions & 33 deletions pulp/apis/coin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""

from typing import Any, Optional, List, Dict, TYPE_CHECKING, Tuple, Union

if TYPE_CHECKING:
from pulp.pulp import LpProblem, LpVariable

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
Expand All @@ -45,22 +50,22 @@ def defaultPath(self):

def __init__(
self,
mip=True,
msg=True,
timeLimit=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",
maxNodes=None,
mip: bool = True,
msg: bool = True,
timeLimit: Optional[float] = None,
gapRel: Optional[float] = None,
gapAbs: Optional[float] = None,
presolve: Optional[bool] = None,
cuts: Optional[bool] = None,
strong: Optional[bool] = None,
options: Optional[Dict[str, Any]] = None,
warmStart: bool = False,
keepFiles: bool = False,
path: Optional[str] = None,
threads: Optional[int] = None,
logPath: Optional[str] = None,
timeMode: str = "elapsed",
maxNodes: Optional[int] = None,
):
"""
:param bool mip: if False, assume LP even if integer variables
Expand Down Expand Up @@ -101,21 +106,21 @@ def __init__(
maxNodes=maxNodes,
)

def copy(self):
def copy(self) -> LpSolver:
"""Make a copy of self"""
aCopy = LpSolver_CMD.copy(self)
aCopy.optionsDict = self.optionsDict
return aCopy

def actualSolve(self, lp, **kwargs):
def actualSolve(self, lp: "LpProblem", **kwargs: Any) -> int:
"""Solve a well formulated lp problem"""
return self.solve_CBC(lp, **kwargs)

def available(self):
def available(self) -> bool:
"""True if the solver is available"""
return self.executable(self.path)
return self.executable(self.path) is not None

def solve_CBC(self, lp, use_mps=True):
def solve_CBC(self, lp: "LpProblem", use_mps: bool = True) -> int:
"""Solve a MIP problem using CBC"""
if not self.executable(self.path):
raise PulpSolverError(
Expand Down Expand Up @@ -254,13 +259,20 @@ def readsol_MPS(
shadowPrices[reverseCn[vn]] = float(dj)
return status, values, reducedCosts, shadowPrices, slacks, sol_status

def writesol(self, filename, lp, vs, variablesNames, constraintsNames):
def writesol(
self,
filename: str,
lp: "LpProblem",
vs: "List[LpVariable]",
variablesNames: Dict[str, str],
constraintsNames: Dict[str, str],
) -> bool:
"""
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: List[Tuple[int, str, Union[int, float, None], int]] = []
value_lines += [
(i, v, values[k], 0) for i, (k, v) in enumerate(variablesNames.items())
]
Expand All @@ -272,7 +284,7 @@ def writesol(self, filename, lp, vs, variablesNames, constraintsNames):

return True

def readsol_LP(self, filename, lp, vs):
def readsol_LP(self, filename: str, lp: "LpProblem", vs: "List[LpVariable]"):
"""
Read a CBC solution file generated from an lp (good names)
returns status, values, reducedCosts, shadowPrices, slacks, sol_status
Expand All @@ -281,7 +293,7 @@ def readsol_LP(self, filename, lp, vs):
constraintsNames = {c: c for c in lp.constraints}
return self.readsol_MPS(filename, lp, vs, variablesNames, constraintsNames)

def get_status(self, filename):
def get_status(self, filename: str):
cbcStatus = {
"Optimal": constants.LpStatusOptimal,
"Infeasible": constants.LpStatusInfeasible,
Expand Down Expand Up @@ -331,11 +343,11 @@ class PULP_CBC_CMD(COIN_CMD):
os.chmod(pulp_cbc_path, stat.S_IXUSR + stat.S_IXOTH)
except: # probably due to incorrect permissions

def available(self):
def available(self) -> bool:
"""True if the solver is available"""
return False

def actualSolve(self, lp, callback=None):
def actualSolve(self, lp: "LpProblem", callback: Optional[Any] = None) -> Any:
"""Solve a well formulated lp problem"""
raise PulpSolverError(
"PULP_CBC_CMD: Not Available (check permissions on %s)"
Expand Down Expand Up @@ -387,7 +399,7 @@ def __init__(
)


def COINMP_DLL_load_dll(path):
def COINMP_DLL_load_dll(path: str) -> ctypes.CDLL:
"""
function that loads the DLL useful for debugging installation problems
"""
Expand Down Expand Up @@ -417,11 +429,11 @@ class COINMP_DLL(LpSolver):
except (ImportError, OSError):

@classmethod
def available(cls):
def available(cls) -> bool:
"""True if the solver is available"""
return False

def actualSolve(self, lp):
def actualSolve(self, lp: "LpProblem") -> Any:
"""Solve a well formulated lp problem"""
raise PulpSolverError("COINMP_DLL: Not Available")

Expand Down Expand Up @@ -480,11 +492,11 @@ def copy(self):
return aCopy

@classmethod
def available(cls):
def available(cls) -> bool:
"""True if the solver is available"""
return True

def getSolverVersion(self):
def getSolverVersion(self) -> Any:
"""
returns a solver version string

Expand All @@ -494,7 +506,7 @@ def getSolverVersion(self):
"""
return self.lib.CoinGetVersionStr()

def actualSolve(self, lp):
def actualSolve(self, lp: "LpProblem"):
"""Solve a well formulated lp problem"""
# TODO alter so that msg parameter is handled correctly
self.debug = 0
Expand Down
Loading