Skip to content

Type hints #807

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

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
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
5 changes: 1 addition & 4 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ jobs:
strategy:
max-parallel: 21
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.9', '3.10', '3.11', '3.12']
os: [ubuntu-latest, macOS-latest, windows-latest]
exclude:
- os: macOS-latest
python-version: '3.8'

steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Installation
Note that to install PuLP you must first have a working python installation as
described in `installing python`_.

PuLP requires Python >= 2.7 or Python >= 3.4.
PuLP requires Python >= 3.9.

The latest version of PuLP can be freely obtained from github_.

Expand All @@ -15,7 +15,7 @@ By far the easiest way to install pulp is through the use of pip_.

* In windows (please make sure pip is on your path)::

c:\Python34\Scripts\> pip install pulp
c:\Python39\Scripts\> pip install pulp

* In Linux::

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ PuLP is part of the `COIN-OR project <https://www.coin-or.org/>`_.
Installation
================

PuLP requires Python 3.7 or newer.
PuLP requires Python 3.9 or newer.

The easiest way to install PuLP is with ``pip``. If ``pip`` is available on your system, type::

Expand Down
4 changes: 2 additions & 2 deletions examples/AmericanSteelProblem.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
("Chicago", "Gary"),
]

arcData = { # ARC Cost Min Max
arcData: dict[tuple[str, str], list[float]] = { # ARC Cost Min Max
("Youngstown", "Albany"): [0.5, 0, 1000],
("Youngstown", "Cincinatti"): [0.35, 0, 3000],
("Youngstown", "Kansas City"): [0.45, 1000, 5000],
Expand All @@ -72,7 +72,7 @@
(costs, mins, maxs) = splitDict(arcData)

# Creates the boundless Variables as Integers
vars = LpVariable.dicts("Route", Arcs, None, None, LpInteger)
vars = LpVariable.dicts("Route", Arcs, cat=LpInteger)

# Creates the upper and lower bounds on the variables
for a in Arcs:
Expand Down
36 changes: 17 additions & 19 deletions examples/CG.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Pattern:
totalRollLength = 20
lenOpts = ["5", "7", "9"]

def __init__(self, name, lengths=None):
def __init__(self, name: str, lengths: list[int]):
self.name = name
self.lengthsdict = dict(zip(self.lenOpts, lengths))

Expand All @@ -31,7 +31,7 @@ def trim(self):
)


def masterSolve(Patterns, rollData, relax=True):
def masterSolve(Patterns: list[Pattern], rollData: dict[str, tuple[int, float]], relax: bool=True):
# The rollData is made into separate dictionaries
(rollDemand, surplusPrice) = splitDict(rollData)

Expand Down Expand Up @@ -72,17 +72,15 @@ def masterSolve(Patterns, rollData, relax=True):

if relax:
# Creates a dual variables list
duals = {}
duals: dict[str, float | None] = {}
for name, i in zip(["Min5", "Min7", "Min9"], Pattern.lenOpts):
duals[i] = prob.constraints[name].pi

return duals

else:
# Creates a dictionary of the variables and their values
varsdict = {}
for v in prob.variables():
varsdict[v.name] = v.varValue
varsdict = {v.name: v.varValue for v in prob.variables()}

# The number of rolls of each length in each pattern is printed
for i in Patterns:
Expand All @@ -91,23 +89,23 @@ def masterSolve(Patterns, rollData, relax=True):
return value(prob.objective), varsdict


def subSolve(Patterns, duals):
def subSolve(patterns: list[Pattern], duals: dict[str, LptNumber]):
# The variable 'prob' is created
prob = LpProblem("SubProb", LpMinimize)

# The problem variables are created
_vars = LpVariable.dicts("Roll Length", Pattern.lenOpts, 0, None, LpInteger)
vars = LpVariable.dicts("Roll Length", Pattern.lenOpts, 0, None, LpInteger)

trim = LpVariable("Trim", 0, None, LpInteger)

# The objective function is entered: the reduced cost of a new pattern
prob += (Pattern.cost - Pattern.trimValue * trim) - lpSum(
[_vars[i] * duals[i] for i in Pattern.lenOpts]
[vars[i] * duals[i] for i in Pattern.lenOpts]
), "Objective"

# The conservation of length constraint is entered
prob += (
lpSum([_vars[i] * int(i) for i in Pattern.lenOpts]) + trim
lpSum([vars[i] * int(i) for i in Pattern.lenOpts]) + trim
== Pattern.totalRollLength,
"lengthEquate",
)
Expand All @@ -119,22 +117,22 @@ def subSolve(Patterns, duals):
prob.roundSolution()

# The new pattern is written to a dictionary
varsdict = {}
newPattern = {}
for v in prob.variables():
varsdict[v.name] = v.varValue
varsdict = {v.name: v.varValue for v in prob.variables()}
newPattern: dict[str, int] = {}
for i, j in zip(
Pattern.lenOpts, ["Roll_Length_5", "Roll_Length_7", "Roll_Length_9"]
):
newPattern[i] = int(varsdict[j])

# Check if there are more patterns which would reduce the master LP objective function further
if value(prob.objective) < -(10**-5):
objective_value = value(prob.objective)
assert objective_value is not None
if objective_value < -(10**-5):
morePatterns = True # continue adding patterns
Patterns += [
Pattern("P" + str(len(Patterns)), [newPattern[i] for i in ["5", "7", "9"]])
]
patterns.append(
Pattern("P" + str(len(patterns)), [newPattern[i] for i in ["5", "7", "9"]])
)
else:
morePatterns = False # all patterns have been added

return Patterns, morePatterns
return patterns, morePatterns
49 changes: 23 additions & 26 deletions examples/CGcolumnwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@ class Pattern:
lenOpts = ["5", "7", "9"]
numPatterns = 0

def __init__(self, name, lengths=None):
def __init__(self, name: str, lengths: list[int] | None=None):
self.name = name
self.lengthsdict = dict(zip(self.lenOpts, lengths))
self.lengthsdict: dict[str, int] = dict(zip(self.lenOpts, lengths))
Pattern.numPatterns += 1

def __str__(self):
return self.name

def trim(self):
return Pattern.totalRollLength - sum(
return self.totalRollLength - sum(
[int(i) * int(self.lengthsdict[i]) for i in self.lengthsdict]
)


def createMaster():
rollData = { # Length Demand SalePrice
rollData: dict[str, list[float | int]] = { # Length Demand SalePrice
"5": [150, 0.25],
"7": [200, 0.33],
"9": [300, 0.40],
Expand All @@ -50,31 +50,31 @@ def createMaster():
prob.setObjective(obj)

# The constraints are initialised and added to prob
constraints = {}
constraints: dict[str, LpConstraintVar] = {}
for l in Pattern.lenOpts:
constraints[l] = LpConstraintVar("Min" + str(l), LpConstraintGE, rollDemand[l])
constraints[l] = LpConstraintVar(f"Min{l}", LpConstraintGE, rollDemand[l])
prob += constraints[l]

# The surplus variables are created
surplusVars = []
surplusVars: list[LpVariable] = []
for i in Pattern.lenOpts:
surplusVars += [
surplusVars.append(
LpVariable(
"Surplus " + i,
f"Surplus {i}",
0,
None,
LpContinuous,
-surplusPrice[i] * obj - constraints[i],
)
]
)

return prob, obj, constraints


def addPatterns(obj, constraints, newPatterns):
def addPatterns(obj: LpConstraintVar, constraints: dict[str, LpConstraintVar], newPatterns):
# A list called Patterns is created to contain all the Pattern class
# objects created in this function call
Patterns = []
Patterns: list[Pattern] = []
for i in newPatterns:
# The new patterns are checked to see that their length does not exceed
# the total roll length
Expand All @@ -88,12 +88,12 @@ def addPatterns(obj, constraints, newPatterns):
print("P" + str(Pattern.numPatterns), "=", i)

# The patterns are instantiated as Pattern objects
Patterns += [Pattern("P" + str(Pattern.numPatterns), i)]
Patterns.append(Pattern("P" + str(Pattern.numPatterns), i))

# The pattern variables are created
pattVars = []
pattVars: list[LpVariable] = []
for i in Patterns:
pattVars += [
pattVars.append(
LpVariable(
"Pattern " + i.name,
0,
Expand All @@ -102,10 +102,10 @@ def addPatterns(obj, constraints, newPatterns):
(i.cost - Pattern.trimValue * i.trim()) * obj
+ lpSum([constraints[l] * i.lengthsdict[l] for l in Pattern.lenOpts]),
)
]
)


def masterSolve(prob, relax=True):
def masterSolve(prob: LpProblem, relax: bool=True) -> tuple[LptNumber | None, dict[str, float | None]]:
# Unrelaxes the Integer Constraint
if not relax:
for v in prob.variables():
Expand All @@ -117,20 +117,18 @@ def masterSolve(prob, relax=True):

if relax:
# A dictionary of dual variable values is returned
duals = {}
duals: dict[str, float | None] = {}
for i, name in zip(Pattern.lenOpts, ["Min5", "Min7", "Min9"]):
duals[i] = prob.constraints[name].pi
return duals
else:
# A dictionary of variable values and the objective value are returned
varsdict = {}
for v in prob.variables():
varsdict[v.name] = v.varValue
varsdict = {v.name: v.varValue for v in prob.variables()}

return value(prob.objective), varsdict


def subSolve(duals):
def subSolve(duals) -> list[list[int]]:
# The variable 'prob' is created
prob = LpProblem("SubProb", LpMinimize)

Expand All @@ -157,12 +155,11 @@ def subSolve(duals):
# The variable values are rounded
prob.roundSolution()

newPatterns = []
newPatterns: list[list[int]] = []
# Check if there are more patterns which would reduce the master LP objective function further
if value(prob.objective) < -(10**-5):
varsdict = {}
for v in prob.variables():
varsdict[v.name] = v.varValue
varsdict = {v.name: v.varValue for v in prob.variables()}

# Adds the new pattern to the newPatterns list
newPatterns += [
[
Expand Down
8 changes: 4 additions & 4 deletions examples/SpongeRollProblem2.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
# A list of all the roll lengths is created
LenOpts = ["5", "7", "9"]

rollData = { # Length Demand SalePrice
"5": [150, 0.25],
"7": [200, 0.33],
"9": [300, 0.40],
rollData: dict[str, tuple[int, float]] = { # Length Demand SalePrice
"5": (150, 0.25),
"7": (200, 0.33),
"9": (300, 0.40),
}

# A list of all the patterns is created
Expand Down
17 changes: 8 additions & 9 deletions examples/SpongeRollProblem3.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""


def calculatePatterns(totalRollLength, lenOpts, head):
def calculatePatterns(totalRollLength: int, lenOpts: list[int], head: list[int]):
"""
Recursively calculates the list of options lists for a cutting stock problem. The input
'tlist' is a pointer, and will be the output of the function call.
Expand All @@ -20,7 +20,7 @@ def calculatePatterns(totalRollLength, lenOpts, head):
Authors: Bojan Blazevic, Dr Stuart Mitchell 2007
"""
if lenOpts:
patterns = []
patterns: list[list[int]] = []
# take the first option off lenOpts
opt = lenOpts[0]
for rep in range(int(totalRollLength / opt) + 1):
Expand All @@ -36,7 +36,7 @@ def calculatePatterns(totalRollLength, lenOpts, head):
return patterns


def makePatterns(totalRollLength, lenOpts):
def makePatterns(totalRollLength: int, lenOpts: list[int]):
"""
Makes the different cutting patterns for a cutting stock problem.

Expand All @@ -51,13 +51,12 @@ def makePatterns(totalRollLength, lenOpts):
patterns = calculatePatterns(totalRollLength, lenOpts, [])

# The list 'PatternNames' is created
PatternNames = []
for i in range(len(patterns)):
PatternNames += ["P" + str(i)]
PatternNames = [f"P{i}" for i in range(len(patterns))]


# The amount of trim (unused material) for each pattern is calculated and added to the dictionary
# 'trim', with the reference key of the pattern name.
trim = {}
trim: dict[str, int] = {}
for name, pattern in zip(PatternNames, patterns):
ssum = 0
for rep, l in zip(pattern, lenOpts):
Expand All @@ -69,7 +68,7 @@ def makePatterns(totalRollLength, lenOpts):
for name, pattern in zip(PatternNames, patterns):
print(name + f" = {pattern}")

return (PatternNames, patterns, trim)
return PatternNames, patterns, trim


# Import PuLP modeler functions
Expand All @@ -87,7 +86,7 @@ def makePatterns(totalRollLength, lenOpts):
# A list of all the roll lengths is created
LenOpts = ["5", "7", "9"]

rollData = { # Length Demand SalePrice
rollData: dict[str, list[int | float]] = { # Length Demand SalePrice
"5": [150, 0.25],
"7": [200, 0.33],
"9": [300, 0.40],
Expand Down
Loading
Loading