PuLP 3.2.1__tar.gz → 3.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pulp-3.2.1 → pulp-3.2.2}/PKG-INFO +4 -4
- {pulp-3.2.1 → pulp-3.2.2}/PuLP.egg-info/PKG-INFO +4 -4
- {pulp-3.2.1 → pulp-3.2.2}/PuLP.egg-info/requires.txt +3 -1
- {pulp-3.2.1 → pulp-3.2.2}/README.rst +2 -2
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/__init__.py +16 -18
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/cplex_api.py +72 -5
- {pulp-3.2.1 → pulp-3.2.2}/pulp/mps_lp.py +5 -5
- {pulp-3.2.1 → pulp-3.2.2}/pulp/pulp.py +51 -17
- {pulp-3.2.1 → pulp-3.2.2}/pulp/tests/test_examples.py +4 -1
- {pulp-3.2.1 → pulp-3.2.2}/pulp/tests/test_gurobipy_env.py +12 -12
- {pulp-3.2.1 → pulp-3.2.2}/pulp/tests/test_pulp.py +238 -132
- {pulp-3.2.1 → pulp-3.2.2}/pyproject.toml +2 -2
- {pulp-3.2.1 → pulp-3.2.2}/PuLP.egg-info/SOURCES.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/PuLP.egg-info/dependency_links.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/PuLP.egg-info/entry_points.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/PuLP.egg-info/top_level.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/choco_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/coin_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/copt_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/core.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/cuopt_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/glpk_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/gurobi_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/highs_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/mipcl_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/mosek_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/sas_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/scip_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/apis/xpress_api.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/constants.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/arm64/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/arm64/cbc +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/arm64/coin-license.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/i32/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/i32/cbc +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/i32/coin-license.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/i64/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/i64/cbc +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/linux/i64/coin-license.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/osx/i64/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/osx/i64/cbc +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/osx/i64/coin-license.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/win/i32/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/win/i32/cbc.exe +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/win/i32/coin-license.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/win/i64/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/win/i64/cbc.exe +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/solverdir/cbc/win/i64/coin-license.txt +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/sparse.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/tests/__init__.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/tests/bin_packing_problem.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/tests/run_tests.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/tests/test_lpdot.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/tests/test_sparse.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/pulp/utilities.py +0 -0
- {pulp-3.2.1 → pulp-3.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PuLP
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: PuLP is an LP modeler written in python. PuLP can generate MPS or LP files and call GLPK, COIN CLP/CBC, CPLEX, and GUROBI to solve linear problems.
|
|
5
5
|
Author: J.S. Roy
|
|
6
6
|
Author-email: "S.A. Mitchell" <pulp@stuartmitchell.com>, Franco Peschiera <pchtsp@gmail.com>
|
|
@@ -19,7 +19,7 @@ Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
|
19
19
|
Requires-Python: >=3.9
|
|
20
20
|
Description-Content-Type: text/x-rst
|
|
21
21
|
Provides-Extra: open-py
|
|
22
|
-
Requires-Dist: cylp; extra == "open-py"
|
|
22
|
+
Requires-Dist: cylp; sys_platform != "win32" and extra == "open-py"
|
|
23
23
|
Requires-Dist: highspy; extra == "open-py"
|
|
24
24
|
Requires-Dist: pyscipopt; extra == "open-py"
|
|
25
25
|
Provides-Extra: public-py
|
|
@@ -153,8 +153,8 @@ To build, run the following in a terminal window, in the PuLP root directory
|
|
|
153
153
|
|
|
154
154
|
::
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
python3 -m pip install --upgrade pip
|
|
157
|
+
pip install --group=dev .
|
|
158
158
|
cd doc
|
|
159
159
|
make html
|
|
160
160
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PuLP
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: PuLP is an LP modeler written in python. PuLP can generate MPS or LP files and call GLPK, COIN CLP/CBC, CPLEX, and GUROBI to solve linear problems.
|
|
5
5
|
Author: J.S. Roy
|
|
6
6
|
Author-email: "S.A. Mitchell" <pulp@stuartmitchell.com>, Franco Peschiera <pchtsp@gmail.com>
|
|
@@ -19,7 +19,7 @@ Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
|
19
19
|
Requires-Python: >=3.9
|
|
20
20
|
Description-Content-Type: text/x-rst
|
|
21
21
|
Provides-Extra: open-py
|
|
22
|
-
Requires-Dist: cylp; extra == "open-py"
|
|
22
|
+
Requires-Dist: cylp; sys_platform != "win32" and extra == "open-py"
|
|
23
23
|
Requires-Dist: highspy; extra == "open-py"
|
|
24
24
|
Requires-Dist: pyscipopt; extra == "open-py"
|
|
25
25
|
Provides-Extra: public-py
|
|
@@ -153,8 +153,8 @@ To build, run the following in a terminal window, in the PuLP root directory
|
|
|
153
153
|
|
|
154
154
|
::
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
python3 -m pip install --upgrade pip
|
|
157
|
+
pip install --group=dev .
|
|
158
158
|
cd doc
|
|
159
159
|
make html
|
|
160
160
|
|
|
@@ -124,8 +124,8 @@ To build, run the following in a terminal window, in the PuLP root directory
|
|
|
124
124
|
|
|
125
125
|
::
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
python3 -m pip install --upgrade pip
|
|
128
|
+
pip install --group=dev .
|
|
129
129
|
cd doc
|
|
130
130
|
make html
|
|
131
131
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
from typing import Dict, Optional, Type, Union
|
|
2
|
-
|
|
3
|
-
from .choco_api import
|
|
4
|
-
from .coin_api import
|
|
5
|
-
from .copt_api import
|
|
6
|
-
from .core import
|
|
7
|
-
from .cplex_api import
|
|
8
|
-
from .glpk_api import
|
|
9
|
-
from .gurobi_api import
|
|
10
|
-
from .highs_api import
|
|
11
|
-
from .mipcl_api import
|
|
12
|
-
from .mosek_api import
|
|
13
|
-
from .sas_api import
|
|
14
|
-
from .scip_api import
|
|
15
|
-
from .xpress_api import
|
|
16
|
-
from .cuopt_api import
|
|
1
|
+
from typing import Dict, Optional, Type, Union, List
|
|
2
|
+
import json
|
|
3
|
+
from .choco_api import CHOCO_CMD
|
|
4
|
+
from .coin_api import CYLP, PULP_CBC_CMD, COIN_CMD, COINMP_DLL, YAPOSIB
|
|
5
|
+
from .copt_api import COPT, COPT_DLL, COPT_CMD
|
|
6
|
+
from .core import LpSolver, LpSolver_CMD, PulpSolverError
|
|
7
|
+
from .cplex_api import CPLEX_PY, CPLEX_CMD, CPLEX
|
|
8
|
+
from .glpk_api import GLPK_CMD, PYGLPK, GLPK
|
|
9
|
+
from .gurobi_api import GUROBI, GUROBI_CMD
|
|
10
|
+
from .highs_api import HiGHS, HiGHS_CMD
|
|
11
|
+
from .mipcl_api import MIPCL_CMD
|
|
12
|
+
from .mosek_api import MOSEK
|
|
13
|
+
from .sas_api import SAS94, SASCAS, SASsolver
|
|
14
|
+
from .scip_api import SCIP, SCIP_CMD, SCIP_PY, FSCIP_CMD, FSCIP
|
|
15
|
+
from .xpress_api import XPRESS_CMD, XPRESS_PY, XPRESS
|
|
16
|
+
from .cuopt_api import CUOPT
|
|
17
17
|
|
|
18
18
|
_all_solvers: List[Type[LpSolver]] = [
|
|
19
19
|
CYLP,
|
|
@@ -45,8 +45,6 @@ _all_solvers: List[Type[LpSolver]] = [
|
|
|
45
45
|
CUOPT,
|
|
46
46
|
]
|
|
47
47
|
|
|
48
|
-
import json
|
|
49
|
-
|
|
50
48
|
LpSolverDefault: Optional[Union[PULP_CBC_CMD, GLPK_CMD, COIN_CMD]] = None
|
|
51
49
|
# Default solver selection
|
|
52
50
|
if PULP_CBC_CMD().available():
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import warnings
|
|
3
|
+
from typing import Iterable, Optional
|
|
3
4
|
|
|
4
5
|
from .. import constants
|
|
5
6
|
from .core import LpSolver, LpSolver_CMD, PulpSolverError, clock, log, subprocess
|
|
@@ -252,7 +253,7 @@ class CPLEX_PY(LpSolver):
|
|
|
252
253
|
name = "CPLEX_PY"
|
|
253
254
|
try:
|
|
254
255
|
global cplex
|
|
255
|
-
import cplex # type: ignore[import-not-found]
|
|
256
|
+
import cplex # type: ignore[import-not-found, import-untyped, unused-ignore]
|
|
256
257
|
except Exception as e:
|
|
257
258
|
err = e
|
|
258
259
|
"""The CPLEX LP/MIP solver from python. Something went wrong!!!!"""
|
|
@@ -276,6 +277,7 @@ class CPLEX_PY(LpSolver):
|
|
|
276
277
|
warmStart=False,
|
|
277
278
|
logPath=None,
|
|
278
279
|
threads=None,
|
|
280
|
+
**solverParams,
|
|
279
281
|
):
|
|
280
282
|
"""
|
|
281
283
|
:param bool mip: if False, assume LP even if integer variables
|
|
@@ -285,6 +287,15 @@ class CPLEX_PY(LpSolver):
|
|
|
285
287
|
:param bool warmStart: if True, the solver will use the current value of variables as a start
|
|
286
288
|
:param str logPath: path to the log file
|
|
287
289
|
:param int threads: number of threads to be used by CPLEX to solve a problem (default None uses all available)
|
|
290
|
+
|
|
291
|
+
:param dict solverParams: Additional parameters to set in the CPLEX solver.
|
|
292
|
+
|
|
293
|
+
Parameters should use dot notation as specified in the CPLEX documentation.
|
|
294
|
+
The 'parameters.' prefix is optional. For example:
|
|
295
|
+
|
|
296
|
+
* parameters.advance (or advance)
|
|
297
|
+
* parameters.barrier.algorithm (or barrier.algorithm)
|
|
298
|
+
* parameters.mip.strategy.probe (or mip.strategy.probe)
|
|
288
299
|
"""
|
|
289
300
|
|
|
290
301
|
LpSolver.__init__(
|
|
@@ -297,22 +308,25 @@ class CPLEX_PY(LpSolver):
|
|
|
297
308
|
logPath=logPath,
|
|
298
309
|
threads=threads,
|
|
299
310
|
)
|
|
311
|
+
self.solverParams = solverParams
|
|
300
312
|
|
|
301
313
|
def available(self):
|
|
302
314
|
"""True if the solver is available"""
|
|
303
315
|
return True
|
|
304
316
|
|
|
305
|
-
def actualSolve(self, lp, callback=None): # type: ignore[misc]
|
|
317
|
+
def actualSolve(self, lp, callback: Optional[Iterable[type[cplex.callbacks.Callback]]] = None): # type: ignore[misc]
|
|
306
318
|
"""
|
|
307
319
|
Solve a well formulated lp problem
|
|
308
320
|
|
|
309
321
|
creates a cplex model, variables and constraints and attaches
|
|
310
322
|
them to the lp model which it then solves
|
|
323
|
+
|
|
324
|
+
:param callback: Optional list of CPLEX callback classes to register during solve
|
|
311
325
|
"""
|
|
312
326
|
self.buildSolverModel(lp)
|
|
313
327
|
# set the initial solution
|
|
314
328
|
log.debug("Solve the Model using cplex")
|
|
315
|
-
self.callSolver(lp)
|
|
329
|
+
self.callSolver(lp, callback=callback)
|
|
316
330
|
# get the solution information
|
|
317
331
|
solutionStatus = self.findSolutionValues(lp)
|
|
318
332
|
for var in lp._variables:
|
|
@@ -430,6 +444,47 @@ class CPLEX_PY(LpSolver):
|
|
|
430
444
|
self.solverModel.MIP_starts.add(
|
|
431
445
|
cplex.SparsePair(ind=ind, val=val), effort, "1"
|
|
432
446
|
)
|
|
447
|
+
for param, value in self.solverParams.items():
|
|
448
|
+
self.set_param(param, value)
|
|
449
|
+
|
|
450
|
+
def set_param(self, name: str, value):
|
|
451
|
+
"""
|
|
452
|
+
Sets a parameter value using its name.
|
|
453
|
+
"""
|
|
454
|
+
param = self.search_param(name=name)
|
|
455
|
+
param.set(value)
|
|
456
|
+
|
|
457
|
+
def get_param(self, name: str):
|
|
458
|
+
"""
|
|
459
|
+
Returns the value of a named parameter by searching within the instance's parameters.
|
|
460
|
+
"""
|
|
461
|
+
param = self.search_param(name=name)
|
|
462
|
+
return param.get()
|
|
463
|
+
|
|
464
|
+
def search_param(self, name: str):
|
|
465
|
+
"""
|
|
466
|
+
Searches for a solver model parameter by its name and returns the corresponding attribute.
|
|
467
|
+
|
|
468
|
+
The method takes a parameter name string, processes it to remove the "parameters." prefix
|
|
469
|
+
and splits it by periods to traverse the attribute hierarchy of the solver model's parameters.
|
|
470
|
+
"""
|
|
471
|
+
name = name.replace("parameters.", "")
|
|
472
|
+
param = self.solverModel.parameters
|
|
473
|
+
for attr in name.split("."):
|
|
474
|
+
param = getattr(param, attr)
|
|
475
|
+
return param
|
|
476
|
+
|
|
477
|
+
def get_all_params(self):
|
|
478
|
+
"""
|
|
479
|
+
Returns all parameters from the solver model.
|
|
480
|
+
"""
|
|
481
|
+
return self.solverModel.parameters.get_all()
|
|
482
|
+
|
|
483
|
+
def get_changed_params(self):
|
|
484
|
+
"""
|
|
485
|
+
Returns the parameters that have been changed in the solver model.
|
|
486
|
+
"""
|
|
487
|
+
return self.solverModel.parameters.get_changed()
|
|
433
488
|
|
|
434
489
|
def setlogfile(self, fileobj):
|
|
435
490
|
"""
|
|
@@ -458,9 +513,21 @@ class CPLEX_PY(LpSolver):
|
|
|
458
513
|
"""
|
|
459
514
|
self.solverModel.parameters.timelimit.set(timeLimit)
|
|
460
515
|
|
|
461
|
-
def callSolver(
|
|
462
|
-
|
|
516
|
+
def callSolver(
|
|
517
|
+
self,
|
|
518
|
+
isMIP,
|
|
519
|
+
callback: Optional[Iterable[type[cplex.callbacks.Callback]]] = None,
|
|
520
|
+
):
|
|
521
|
+
"""
|
|
522
|
+
Solves the problem with cplex
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
:param callback: Optional list of CPLEX callback classes to register during solve
|
|
526
|
+
"""
|
|
463
527
|
# solve the problem
|
|
528
|
+
if callback is not None:
|
|
529
|
+
for call in callback:
|
|
530
|
+
self.solverModel.register_callback(call)
|
|
464
531
|
self.solveTime = -clock()
|
|
465
532
|
self.solverModel.solve()
|
|
466
533
|
self.solveTime += clock()
|
|
@@ -312,9 +312,9 @@ def writeMPS(
|
|
|
312
312
|
if mpsSense != lp.sense:
|
|
313
313
|
n = cobj.name
|
|
314
314
|
cobj = -cobj
|
|
315
|
-
cobj.name = n
|
|
315
|
+
cobj.name = n
|
|
316
316
|
if rename:
|
|
317
|
-
constrNames, varNames, cobj.name = lp.normalisedNames()
|
|
317
|
+
constrNames, varNames, cobj.name = lp.normalisedNames()
|
|
318
318
|
# No need to call self.variables() again, we have just filled self._variables:
|
|
319
319
|
vs = lp._variables
|
|
320
320
|
else:
|
|
@@ -324,7 +324,7 @@ def writeMPS(
|
|
|
324
324
|
model_name = lp.name
|
|
325
325
|
if rename:
|
|
326
326
|
model_name = "MODEL"
|
|
327
|
-
objName = cobj.name
|
|
327
|
+
objName = cobj.name
|
|
328
328
|
if not objName:
|
|
329
329
|
objName = "OBJ"
|
|
330
330
|
|
|
@@ -346,7 +346,7 @@ def writeMPS(
|
|
|
346
346
|
for v in vs:
|
|
347
347
|
name = varNames[v.name]
|
|
348
348
|
columns_lines.extend(
|
|
349
|
-
writeMPSColumnLines(coefs[name], v, mip, name, cobj, objName)
|
|
349
|
+
writeMPSColumnLines(coefs[name], v, mip, name, cobj, objName)
|
|
350
350
|
)
|
|
351
351
|
|
|
352
352
|
# right hand side
|
|
@@ -382,7 +382,7 @@ def writeMPS(
|
|
|
382
382
|
if not rename:
|
|
383
383
|
return vs
|
|
384
384
|
else:
|
|
385
|
-
return vs, varNames, constrNames, cobj.name
|
|
385
|
+
return vs, varNames, constrNames, cobj.name
|
|
386
386
|
|
|
387
387
|
|
|
388
388
|
def writeMPSColumnLines(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#! /usr/bin/env python
|
|
2
2
|
# PuLP : Python LP Modeler
|
|
3
|
-
from __future__ import annotations
|
|
4
3
|
|
|
5
4
|
# Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org)
|
|
6
5
|
# Modifications Copyright (c) 2007- Stuart Anthony Mitchell (s.mitchell@auckland.ac.nz)
|
|
@@ -25,6 +24,7 @@ from __future__ import annotations
|
|
|
25
24
|
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
26
25
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
27
26
|
|
|
27
|
+
|
|
28
28
|
"""
|
|
29
29
|
PuLP is an linear and mixed integer programming modeler written in Python.
|
|
30
30
|
|
|
@@ -123,12 +123,14 @@ References
|
|
|
123
123
|
|
|
124
124
|
"""
|
|
125
125
|
|
|
126
|
+
from __future__ import annotations
|
|
127
|
+
|
|
126
128
|
from collections import Counter
|
|
127
129
|
import sys
|
|
128
130
|
import warnings
|
|
129
131
|
import math
|
|
130
132
|
from time import time
|
|
131
|
-
from typing import Any, Literal
|
|
133
|
+
from typing import Any, Literal, Optional
|
|
132
134
|
|
|
133
135
|
from .apis import LpSolverDefault, PULP_CBC_CMD
|
|
134
136
|
from .apis.core import clock
|
|
@@ -257,8 +259,21 @@ class LpVariable(LpElement):
|
|
|
257
259
|
existence in the objective function and constraints
|
|
258
260
|
"""
|
|
259
261
|
|
|
262
|
+
varValue: Optional[float]
|
|
263
|
+
dj: Optional[float]
|
|
264
|
+
lowBound: Optional[float]
|
|
265
|
+
upBound: Optional[float]
|
|
266
|
+
cat: str
|
|
267
|
+
_lowbound_original: Optional[float]
|
|
268
|
+
_upbound_original: Optional[float]
|
|
269
|
+
|
|
260
270
|
def __init__(
|
|
261
|
-
self,
|
|
271
|
+
self,
|
|
272
|
+
name: str,
|
|
273
|
+
lowBound: Optional[float] = None,
|
|
274
|
+
upBound: Optional[float] = None,
|
|
275
|
+
cat: str = const.LpContinuous,
|
|
276
|
+
e=None,
|
|
262
277
|
):
|
|
263
278
|
LpElement.__init__(self, name)
|
|
264
279
|
self._lowbound_original = self.lowBound = lowBound
|
|
@@ -270,6 +285,20 @@ class LpVariable(LpElement):
|
|
|
270
285
|
self._lowbound_original = self.lowBound = 0
|
|
271
286
|
self._upbound_original = self.upBound = 1
|
|
272
287
|
self.cat = const.LpInteger
|
|
288
|
+
if self.lowBound is not None:
|
|
289
|
+
if not math.isfinite(self.lowBound):
|
|
290
|
+
raise const.PulpError(
|
|
291
|
+
"The lower bound of a variable must be finite, got {}".format(
|
|
292
|
+
self.lowBound
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
if self.upBound is not None:
|
|
296
|
+
if not math.isfinite(self.upBound):
|
|
297
|
+
raise const.PulpError(
|
|
298
|
+
"The upper bound of a variable must be finite, got {}".format(
|
|
299
|
+
self.upBound
|
|
300
|
+
)
|
|
301
|
+
)
|
|
273
302
|
# Code to add a variable to constraints for column based
|
|
274
303
|
# modelling.
|
|
275
304
|
if e:
|
|
@@ -501,7 +530,7 @@ class LpVariable(LpElement):
|
|
|
501
530
|
):
|
|
502
531
|
self.varValue = self.upBound
|
|
503
532
|
elif (
|
|
504
|
-
self.lowBound
|
|
533
|
+
self.lowBound is not None
|
|
505
534
|
and self.varValue < self.lowBound
|
|
506
535
|
and self.varValue >= self.lowBound - eps
|
|
507
536
|
):
|
|
@@ -515,7 +544,7 @@ class LpVariable(LpElement):
|
|
|
515
544
|
def roundedValue(self, eps=1e-5):
|
|
516
545
|
if (
|
|
517
546
|
self.cat == const.LpInteger
|
|
518
|
-
and self.varValue
|
|
547
|
+
and self.varValue is not None
|
|
519
548
|
and abs(self.varValue - round(self.varValue)) <= eps
|
|
520
549
|
):
|
|
521
550
|
return round(self.varValue)
|
|
@@ -523,10 +552,10 @@ class LpVariable(LpElement):
|
|
|
523
552
|
return self.varValue
|
|
524
553
|
|
|
525
554
|
def valueOrDefault(self):
|
|
526
|
-
if self.varValue
|
|
555
|
+
if self.varValue is not None:
|
|
527
556
|
return self.varValue
|
|
528
|
-
elif self.lowBound
|
|
529
|
-
if self.upBound
|
|
557
|
+
elif self.lowBound is not None:
|
|
558
|
+
if self.upBound is not None:
|
|
530
559
|
if 0 >= self.lowBound and 0 <= self.upBound:
|
|
531
560
|
return 0
|
|
532
561
|
else:
|
|
@@ -539,7 +568,7 @@ class LpVariable(LpElement):
|
|
|
539
568
|
return 0
|
|
540
569
|
else:
|
|
541
570
|
return self.lowBound
|
|
542
|
-
elif self.upBound
|
|
571
|
+
elif self.upBound is not None:
|
|
543
572
|
if 0 <= self.upBound:
|
|
544
573
|
return 0
|
|
545
574
|
else:
|
|
@@ -564,11 +593,11 @@ class LpVariable(LpElement):
|
|
|
564
593
|
return True
|
|
565
594
|
|
|
566
595
|
def infeasibilityGap(self, mip=1):
|
|
567
|
-
if self.varValue
|
|
596
|
+
if self.varValue is None:
|
|
568
597
|
raise ValueError("variable value is None")
|
|
569
|
-
if self.upBound
|
|
598
|
+
if self.upBound is not None and self.varValue > self.upBound:
|
|
570
599
|
return self.varValue - self.upBound
|
|
571
|
-
if self.lowBound
|
|
600
|
+
if self.lowBound is not None and self.varValue < self.lowBound:
|
|
572
601
|
return self.varValue - self.lowBound
|
|
573
602
|
if (
|
|
574
603
|
mip
|
|
@@ -600,7 +629,7 @@ class LpVariable(LpElement):
|
|
|
600
629
|
return self.name + " free"
|
|
601
630
|
if self.isConstant():
|
|
602
631
|
return self.name + f" = {self.lowBound:.12g}"
|
|
603
|
-
if self.lowBound
|
|
632
|
+
if self.lowBound is None:
|
|
604
633
|
s = "-inf <= "
|
|
605
634
|
# Note: XPRESS and CPLEX do not interpret integer variables without
|
|
606
635
|
# explicit bounds
|
|
@@ -731,6 +760,11 @@ class LpAffineExpression(dict):
|
|
|
731
760
|
# TODO remove isinstance usage
|
|
732
761
|
if e is None:
|
|
733
762
|
e = {}
|
|
763
|
+
# maybe check for constant
|
|
764
|
+
if not math.isfinite(constant):
|
|
765
|
+
raise const.PulpError(
|
|
766
|
+
f"Invalid constant value: {constant}. It must be a finite number."
|
|
767
|
+
)
|
|
734
768
|
if isinstance(e, (LpAffineExpression, LpConstraint)):
|
|
735
769
|
# Will not copy the name
|
|
736
770
|
self.constant = e.constant # type: ignore[has-type]
|
|
@@ -832,9 +866,7 @@ class LpAffineExpression(dict):
|
|
|
832
866
|
return result
|
|
833
867
|
|
|
834
868
|
def __repr__(self, override_constant: float | None = None):
|
|
835
|
-
constant = constant
|
|
836
|
-
self.constant if override_constant is None else override_constant
|
|
837
|
-
)
|
|
869
|
+
constant = self.constant if override_constant is None else override_constant
|
|
838
870
|
l = [str(self[v]) + "*" + str(v) for v in self.sorted_keys()]
|
|
839
871
|
l.append(str(constant))
|
|
840
872
|
s = " + ".join(l)
|
|
@@ -1090,6 +1122,8 @@ class LpConstraint:
|
|
|
1090
1122
|
self.name = name
|
|
1091
1123
|
self.constant: float = self.expr.constant # type: ignore[annotation-unchecked]
|
|
1092
1124
|
if rhs is not None:
|
|
1125
|
+
if not math.isfinite(rhs):
|
|
1126
|
+
raise const.PulpError("Cannot set constraint RHS to NaN/inf values")
|
|
1093
1127
|
self.constant -= rhs
|
|
1094
1128
|
self.sense = sense
|
|
1095
1129
|
self.pi = None
|
|
@@ -1893,7 +1927,7 @@ class LpProblem:
|
|
|
1893
1927
|
|
|
1894
1928
|
def coefficients(self, translation=None):
|
|
1895
1929
|
coefs = []
|
|
1896
|
-
if translation
|
|
1930
|
+
if translation is None:
|
|
1897
1931
|
for c in self.constraints:
|
|
1898
1932
|
cst = self.constraints[c]
|
|
1899
1933
|
coefs.extend([(v.name, c, cst[v]) for v in cst])
|
|
@@ -10,7 +10,10 @@ class Examples_DocsTests(unittest.TestCase):
|
|
|
10
10
|
|
|
11
11
|
this_file = os.path.realpath(__file__)
|
|
12
12
|
parent_dir = os.path.dirname(this_file)
|
|
13
|
-
|
|
13
|
+
try:
|
|
14
|
+
files = os.listdir(os.path.join(parent_dir, examples_dir))
|
|
15
|
+
except FileNotFoundError:
|
|
16
|
+
raise unittest.SkipTest("Examples not found")
|
|
14
17
|
TMP_dir = "_tmp/"
|
|
15
18
|
if not os.path.exists(TMP_dir):
|
|
16
19
|
os.mkdir(TMP_dir)
|
|
@@ -55,11 +55,11 @@ class GurobiEnvTests(unittest.TestCase):
|
|
|
55
55
|
solver.close()
|
|
56
56
|
check_dummy_env()
|
|
57
57
|
|
|
58
|
-
@unittest.skipUnless(
|
|
59
|
-
is_single_use_license(),
|
|
60
|
-
"this test is only expected to pass with a single-use license",
|
|
61
|
-
)
|
|
62
58
|
def test_gp_env_no_close(self):
|
|
59
|
+
if not is_single_use_license():
|
|
60
|
+
raise unittest.SkipTest(
|
|
61
|
+
"this test is only expected to pass with a single-use license"
|
|
62
|
+
)
|
|
63
63
|
# Not closing results in an error for a single use license.
|
|
64
64
|
with gp.Env(params=self.env_options) as env:
|
|
65
65
|
prob = generate_lp()
|
|
@@ -82,16 +82,16 @@ class GurobiEnvTests(unittest.TestCase):
|
|
|
82
82
|
|
|
83
83
|
check_dummy_env()
|
|
84
84
|
|
|
85
|
-
@unittest.skipUnless(
|
|
86
|
-
is_single_use_license(),
|
|
87
|
-
"this test is only expected to pass with a single-use license",
|
|
88
|
-
)
|
|
89
85
|
def test_backward_compatibility(self):
|
|
90
86
|
"""
|
|
91
87
|
Backward compatibility check as previously the environment was not being
|
|
92
88
|
freed. On a single-use license this passes (fails to initialise a dummy
|
|
93
89
|
env).
|
|
94
90
|
"""
|
|
91
|
+
if not is_single_use_license():
|
|
92
|
+
raise unittest.SkipTest(
|
|
93
|
+
"this test is only expected to pass with a single-use license"
|
|
94
|
+
)
|
|
95
95
|
solver = GUROBI(msg=False, **self.options)
|
|
96
96
|
prob = generate_lp()
|
|
97
97
|
prob.solve(solver)
|
|
@@ -122,16 +122,16 @@ class GurobiEnvTests(unittest.TestCase):
|
|
|
122
122
|
solver2.close()
|
|
123
123
|
check_dummy_env()
|
|
124
124
|
|
|
125
|
-
@unittest.skipUnless(
|
|
126
|
-
is_single_use_license(),
|
|
127
|
-
"this test is only expected to pass with a single-use license",
|
|
128
|
-
)
|
|
129
125
|
def test_leak(self):
|
|
130
126
|
"""
|
|
131
127
|
Check that we cannot initialise environments after a memory leak. On a
|
|
132
128
|
single-use license this passes (fails to initialise a dummy env with a
|
|
133
129
|
memory leak).
|
|
134
130
|
"""
|
|
131
|
+
if not is_single_use_license():
|
|
132
|
+
raise unittest.SkipTest(
|
|
133
|
+
"this test is only expected to pass with a single-use license"
|
|
134
|
+
)
|
|
135
135
|
solver = GUROBI(msg=False, **self.options)
|
|
136
136
|
prob = generate_lp()
|
|
137
137
|
prob.solve(solver)
|