PuLP 3.2.2__tar.gz → 3.3.0__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.3.0/LICENSE +22 -0
- {pulp-3.2.2 → pulp-3.3.0}/PKG-INFO +39 -4
- {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/PKG-INFO +39 -4
- {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/SOURCES.txt +1 -0
- pulp-3.3.0/PuLP.egg-info/requires.txt +36 -0
- {pulp-3.2.2 → pulp-3.3.0}/README.rst +19 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/__init__.py +0 -1
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/coin_api.py +4 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/cplex_api.py +22 -3
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/cuopt_api.py +4 -2
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/xpress_api.py +132 -34
- {pulp-3.2.2 → pulp-3.3.0}/pulp/constants.py +1 -1
- {pulp-3.2.2 → pulp-3.3.0}/pulp/mps_lp.py +2 -2
- {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_pulp.py +16 -11
- {pulp-3.2.2 → pulp-3.3.0}/pyproject.toml +22 -8
- pulp-3.2.2/PuLP.egg-info/requires.txt +0 -12
- {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/dependency_links.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/entry_points.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/top_level.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/choco_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/copt_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/core.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/glpk_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/gurobi_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/highs_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/mipcl_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/mosek_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/sas_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/scip_api.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/pulp.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/arm64/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/arm64/cbc +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/arm64/coin-license.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i32/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i32/cbc +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i32/coin-license.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i64/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i64/cbc +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i64/coin-license.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/osx/i64/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/osx/i64/cbc +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/osx/i64/coin-license.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i32/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i32/cbc.exe +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i32/coin-license.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i64/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i64/cbc.exe +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i64/coin-license.txt +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/sparse.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/__init__.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/bin_packing_problem.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/run_tests.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_examples.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_gurobipy_env.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_lpdot.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_sparse.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/pulp/utilities.py +0 -0
- {pulp-3.2.2 → pulp-3.3.0}/setup.cfg +0 -0
pulp-3.3.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2002-2005, Jean-Sebastien Roy
|
|
2
|
+
Modifications Copyright (c) 2007- Stuart Anthony Mitchell
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
5
|
+
copy of this software and associated documentation files (the
|
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included
|
|
13
|
+
in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
16
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
19
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
20
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
21
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
|
|
@@ -1,31 +1,47 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PuLP
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
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>
|
|
7
7
|
Maintainer-email: Franco Peschiera <pchtsp@gmail.com>
|
|
8
|
-
License: MIT
|
|
8
|
+
License-Expression: MIT
|
|
9
9
|
Project-URL: source, https://github.com/coin-or/pulp
|
|
10
10
|
Project-URL: download, https://github.com/coin-or/pulp/archive/master.zip
|
|
11
11
|
Keywords: Optimization,Linear Programming,Operations Research
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Science/Research
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
15
|
Classifier: Natural Language :: English
|
|
17
16
|
Classifier: Programming Language :: Python
|
|
18
17
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
19
18
|
Requires-Python: >=3.9
|
|
20
19
|
Description-Content-Type: text/x-rst
|
|
20
|
+
License-File: LICENSE
|
|
21
21
|
Provides-Extra: open-py
|
|
22
|
-
Requires-Dist: cylp;
|
|
22
|
+
Requires-Dist: cylp; 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
|
|
26
26
|
Requires-Dist: gurobipy; extra == "public-py"
|
|
27
27
|
Requires-Dist: coptpy; extra == "public-py"
|
|
28
28
|
Requires-Dist: xpress; extra == "public-py"
|
|
29
|
+
Requires-Dist: cplex; (sys_platform != "darwin" or python_full_version < "3.12") and extra == "public-py"
|
|
30
|
+
Provides-Extra: highs
|
|
31
|
+
Requires-Dist: highspy; extra == "highs"
|
|
32
|
+
Provides-Extra: scip
|
|
33
|
+
Requires-Dist: pyscipopt; extra == "scip"
|
|
34
|
+
Provides-Extra: gurobi
|
|
35
|
+
Requires-Dist: gurobipy; extra == "gurobi"
|
|
36
|
+
Provides-Extra: xpress
|
|
37
|
+
Requires-Dist: xpress; extra == "xpress"
|
|
38
|
+
Provides-Extra: copt
|
|
39
|
+
Requires-Dist: coptpy; extra == "copt"
|
|
40
|
+
Provides-Extra: cplex
|
|
41
|
+
Requires-Dist: cplex; (sys_platform != "darwin" or python_full_version < "3.12") and extra == "cplex"
|
|
42
|
+
Provides-Extra: mosek
|
|
43
|
+
Requires-Dist: mosek; extra == "mosek"
|
|
44
|
+
Dynamic: license-file
|
|
29
45
|
|
|
30
46
|
pulp
|
|
31
47
|
**************************
|
|
@@ -56,6 +72,25 @@ The easiest way to install PuLP is with ``pip``. If ``pip`` is available on your
|
|
|
56
72
|
|
|
57
73
|
Otherwise follow the download instructions on the `PyPi page <https://pypi.python.org/pypi/PuLP>`_.
|
|
58
74
|
|
|
75
|
+
Installing solvers
|
|
76
|
+
----------------------
|
|
77
|
+
|
|
78
|
+
PuLP can use a variety of solvers. The default solver is the COIN-OR CBC solver, which is included with PuLP. If you want to use other solvers, PuLP offers a quick way to install most solvers via their pypi package (some require a commercial license for running or for running large models)::
|
|
79
|
+
|
|
80
|
+
python -m pip install pulp[gurobi]
|
|
81
|
+
python -m pip install pulp[cplex]
|
|
82
|
+
python -m pip install pulp[xpress]
|
|
83
|
+
python -m pip install pulp[scip]
|
|
84
|
+
python -m pip install pulp[highs]
|
|
85
|
+
python -m pip install pulp[copt]
|
|
86
|
+
python -m pip install pulp[mosek]
|
|
87
|
+
python -m pip install pulp[cylp]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
If you want to install all open source solvers (scip, highs, cylp), you can use the shortcut::
|
|
91
|
+
python -m pip install pulp[open_py]
|
|
92
|
+
|
|
93
|
+
For more information on how to install solvers, see the `guide on configuring solvers <https://coin-or.github.io/pulp/guides/how_to_configure_solvers.html>`_.
|
|
59
94
|
|
|
60
95
|
Quickstart
|
|
61
96
|
===============
|
|
@@ -1,31 +1,47 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PuLP
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
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>
|
|
7
7
|
Maintainer-email: Franco Peschiera <pchtsp@gmail.com>
|
|
8
|
-
License: MIT
|
|
8
|
+
License-Expression: MIT
|
|
9
9
|
Project-URL: source, https://github.com/coin-or/pulp
|
|
10
10
|
Project-URL: download, https://github.com/coin-or/pulp/archive/master.zip
|
|
11
11
|
Keywords: Optimization,Linear Programming,Operations Research
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Science/Research
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
15
|
Classifier: Natural Language :: English
|
|
17
16
|
Classifier: Programming Language :: Python
|
|
18
17
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
19
18
|
Requires-Python: >=3.9
|
|
20
19
|
Description-Content-Type: text/x-rst
|
|
20
|
+
License-File: LICENSE
|
|
21
21
|
Provides-Extra: open-py
|
|
22
|
-
Requires-Dist: cylp;
|
|
22
|
+
Requires-Dist: cylp; 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
|
|
26
26
|
Requires-Dist: gurobipy; extra == "public-py"
|
|
27
27
|
Requires-Dist: coptpy; extra == "public-py"
|
|
28
28
|
Requires-Dist: xpress; extra == "public-py"
|
|
29
|
+
Requires-Dist: cplex; (sys_platform != "darwin" or python_full_version < "3.12") and extra == "public-py"
|
|
30
|
+
Provides-Extra: highs
|
|
31
|
+
Requires-Dist: highspy; extra == "highs"
|
|
32
|
+
Provides-Extra: scip
|
|
33
|
+
Requires-Dist: pyscipopt; extra == "scip"
|
|
34
|
+
Provides-Extra: gurobi
|
|
35
|
+
Requires-Dist: gurobipy; extra == "gurobi"
|
|
36
|
+
Provides-Extra: xpress
|
|
37
|
+
Requires-Dist: xpress; extra == "xpress"
|
|
38
|
+
Provides-Extra: copt
|
|
39
|
+
Requires-Dist: coptpy; extra == "copt"
|
|
40
|
+
Provides-Extra: cplex
|
|
41
|
+
Requires-Dist: cplex; (sys_platform != "darwin" or python_full_version < "3.12") and extra == "cplex"
|
|
42
|
+
Provides-Extra: mosek
|
|
43
|
+
Requires-Dist: mosek; extra == "mosek"
|
|
44
|
+
Dynamic: license-file
|
|
29
45
|
|
|
30
46
|
pulp
|
|
31
47
|
**************************
|
|
@@ -56,6 +72,25 @@ The easiest way to install PuLP is with ``pip``. If ``pip`` is available on your
|
|
|
56
72
|
|
|
57
73
|
Otherwise follow the download instructions on the `PyPi page <https://pypi.python.org/pypi/PuLP>`_.
|
|
58
74
|
|
|
75
|
+
Installing solvers
|
|
76
|
+
----------------------
|
|
77
|
+
|
|
78
|
+
PuLP can use a variety of solvers. The default solver is the COIN-OR CBC solver, which is included with PuLP. If you want to use other solvers, PuLP offers a quick way to install most solvers via their pypi package (some require a commercial license for running or for running large models)::
|
|
79
|
+
|
|
80
|
+
python -m pip install pulp[gurobi]
|
|
81
|
+
python -m pip install pulp[cplex]
|
|
82
|
+
python -m pip install pulp[xpress]
|
|
83
|
+
python -m pip install pulp[scip]
|
|
84
|
+
python -m pip install pulp[highs]
|
|
85
|
+
python -m pip install pulp[copt]
|
|
86
|
+
python -m pip install pulp[mosek]
|
|
87
|
+
python -m pip install pulp[cylp]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
If you want to install all open source solvers (scip, highs, cylp), you can use the shortcut::
|
|
91
|
+
python -m pip install pulp[open_py]
|
|
92
|
+
|
|
93
|
+
For more information on how to install solvers, see the `guide on configuring solvers <https://coin-or.github.io/pulp/guides/how_to_configure_solvers.html>`_.
|
|
59
94
|
|
|
60
95
|
Quickstart
|
|
61
96
|
===============
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
[copt]
|
|
3
|
+
coptpy
|
|
4
|
+
|
|
5
|
+
[cplex]
|
|
6
|
+
|
|
7
|
+
[cplex:sys_platform != "darwin" or python_full_version < "3.12"]
|
|
8
|
+
cplex
|
|
9
|
+
|
|
10
|
+
[gurobi]
|
|
11
|
+
gurobipy
|
|
12
|
+
|
|
13
|
+
[highs]
|
|
14
|
+
highspy
|
|
15
|
+
|
|
16
|
+
[mosek]
|
|
17
|
+
mosek
|
|
18
|
+
|
|
19
|
+
[open_py]
|
|
20
|
+
cylp
|
|
21
|
+
highspy
|
|
22
|
+
pyscipopt
|
|
23
|
+
|
|
24
|
+
[public_py]
|
|
25
|
+
gurobipy
|
|
26
|
+
coptpy
|
|
27
|
+
xpress
|
|
28
|
+
|
|
29
|
+
[public_py:sys_platform != "darwin" or python_full_version < "3.12"]
|
|
30
|
+
cplex
|
|
31
|
+
|
|
32
|
+
[scip]
|
|
33
|
+
pyscipopt
|
|
34
|
+
|
|
35
|
+
[xpress]
|
|
36
|
+
xpress
|
|
@@ -27,6 +27,25 @@ The easiest way to install PuLP is with ``pip``. If ``pip`` is available on your
|
|
|
27
27
|
|
|
28
28
|
Otherwise follow the download instructions on the `PyPi page <https://pypi.python.org/pypi/PuLP>`_.
|
|
29
29
|
|
|
30
|
+
Installing solvers
|
|
31
|
+
----------------------
|
|
32
|
+
|
|
33
|
+
PuLP can use a variety of solvers. The default solver is the COIN-OR CBC solver, which is included with PuLP. If you want to use other solvers, PuLP offers a quick way to install most solvers via their pypi package (some require a commercial license for running or for running large models)::
|
|
34
|
+
|
|
35
|
+
python -m pip install pulp[gurobi]
|
|
36
|
+
python -m pip install pulp[cplex]
|
|
37
|
+
python -m pip install pulp[xpress]
|
|
38
|
+
python -m pip install pulp[scip]
|
|
39
|
+
python -m pip install pulp[highs]
|
|
40
|
+
python -m pip install pulp[copt]
|
|
41
|
+
python -m pip install pulp[mosek]
|
|
42
|
+
python -m pip install pulp[cylp]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
If you want to install all open source solvers (scip, highs, cylp), you can use the shortcut::
|
|
46
|
+
python -m pip install pulp[open_py]
|
|
47
|
+
|
|
48
|
+
For more information on how to install solvers, see the `guide on configuring solvers <https://coin-or.github.io/pulp/guides/how_to_configure_solvers.html>`_.
|
|
30
49
|
|
|
31
50
|
Quickstart
|
|
32
51
|
===============
|
|
@@ -108,6 +108,10 @@ class COIN_CMD(LpSolver_CMD):
|
|
|
108
108
|
:param str timeMode: "elapsed": count wall-time to timeLimit; "cpu": count cpu-time
|
|
109
109
|
:param int maxNodes: max number of nodes during branching. Stops the solving when reached.
|
|
110
110
|
"""
|
|
111
|
+
if warmStart and not keepFiles and operating_system == "win":
|
|
112
|
+
warnings.warn(
|
|
113
|
+
"When using CBC on Windows, warmStart requires keepFiles=True."
|
|
114
|
+
)
|
|
111
115
|
|
|
112
116
|
LpSolver_CMD.__init__(
|
|
113
117
|
self,
|
|
@@ -240,6 +240,21 @@ class CPLEX_CMD(LpSolver_CMD):
|
|
|
240
240
|
return True
|
|
241
241
|
|
|
242
242
|
|
|
243
|
+
def check_solverModel(func):
|
|
244
|
+
"""
|
|
245
|
+
Decorator that checks if the solverModel is initialized.
|
|
246
|
+
|
|
247
|
+
:raises PulpSolverError: If the ``solverModel`` attribute is not initialized
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def wrapper(self, *args, **kwargs):
|
|
251
|
+
if self.solverModel is None:
|
|
252
|
+
raise PulpSolverError("solverModel not initialized")
|
|
253
|
+
return func(self, *args, **kwargs)
|
|
254
|
+
|
|
255
|
+
return wrapper
|
|
256
|
+
|
|
257
|
+
|
|
243
258
|
class CPLEX_PY(LpSolver):
|
|
244
259
|
"""
|
|
245
260
|
The CPLEX LP/MIP solver (via a Python Binding)
|
|
@@ -308,7 +323,8 @@ class CPLEX_PY(LpSolver):
|
|
|
308
323
|
logPath=logPath,
|
|
309
324
|
threads=threads,
|
|
310
325
|
)
|
|
311
|
-
self.
|
|
326
|
+
self.solver_params = solverParams
|
|
327
|
+
self.solverModel = None
|
|
312
328
|
|
|
313
329
|
def available(self):
|
|
314
330
|
"""True if the solver is available"""
|
|
@@ -444,7 +460,7 @@ class CPLEX_PY(LpSolver):
|
|
|
444
460
|
self.solverModel.MIP_starts.add(
|
|
445
461
|
cplex.SparsePair(ind=ind, val=val), effort, "1"
|
|
446
462
|
)
|
|
447
|
-
for param, value in self.
|
|
463
|
+
for param, value in self.solver_params.items():
|
|
448
464
|
self.set_param(param, value)
|
|
449
465
|
|
|
450
466
|
def set_param(self, name: str, value):
|
|
@@ -461,6 +477,7 @@ class CPLEX_PY(LpSolver):
|
|
|
461
477
|
param = self.search_param(name=name)
|
|
462
478
|
return param.get()
|
|
463
479
|
|
|
480
|
+
@check_solverModel
|
|
464
481
|
def search_param(self, name: str):
|
|
465
482
|
"""
|
|
466
483
|
Searches for a solver model parameter by its name and returns the corresponding attribute.
|
|
@@ -474,12 +491,14 @@ class CPLEX_PY(LpSolver):
|
|
|
474
491
|
param = getattr(param, attr)
|
|
475
492
|
return param
|
|
476
493
|
|
|
494
|
+
@check_solverModel
|
|
477
495
|
def get_all_params(self):
|
|
478
496
|
"""
|
|
479
497
|
Returns all parameters from the solver model.
|
|
480
498
|
"""
|
|
481
499
|
return self.solverModel.parameters.get_all()
|
|
482
500
|
|
|
501
|
+
@check_solverModel
|
|
483
502
|
def get_changed_params(self):
|
|
484
503
|
"""
|
|
485
504
|
Returns the parameters that have been changed in the solver model.
|
|
@@ -515,7 +534,7 @@ class CPLEX_PY(LpSolver):
|
|
|
515
534
|
|
|
516
535
|
def callSolver(
|
|
517
536
|
self,
|
|
518
|
-
isMIP,
|
|
537
|
+
isMIP: bool,
|
|
519
538
|
callback: Optional[Iterable[type[cplex.callbacks.Callback]]] = None,
|
|
520
539
|
):
|
|
521
540
|
"""
|
|
@@ -155,6 +155,8 @@ class CUOPT(LpSolver):
|
|
|
155
155
|
settings = solver_settings.SolverSettings()
|
|
156
156
|
settings.set_parameter("infeasibility_detection", True)
|
|
157
157
|
settings.set_parameter("log_to_console", self.msg)
|
|
158
|
+
if log_file:
|
|
159
|
+
settings.set_parameter("log_file", log_file)
|
|
158
160
|
if self.timeLimit:
|
|
159
161
|
settings.set_parameter("time_limit", self.timeLimit)
|
|
160
162
|
for key, value in self.solver_params.items():
|
|
@@ -163,8 +165,7 @@ class CUOPT(LpSolver):
|
|
|
163
165
|
gapRel = self.optionsDict.get("gapRel")
|
|
164
166
|
if gapRel:
|
|
165
167
|
settings.set_parameter("relative_gap_tolerance", gapRel)
|
|
166
|
-
|
|
167
|
-
solution = solver.Solve(lp.solverModel, settings, log_file)
|
|
168
|
+
solution = solver.Solve(lp.solverModel, settings)
|
|
168
169
|
|
|
169
170
|
self.solveTime += clock()
|
|
170
171
|
return solution
|
|
@@ -231,6 +232,7 @@ class CUOPT(LpSolver):
|
|
|
231
232
|
lp.solverModel.set_row_types(np.array(sense))
|
|
232
233
|
|
|
233
234
|
lp.solverModel.set_objective_coefficients(np.array(obj_coeff))
|
|
235
|
+
lp.solverModel.set_objective_offset(lp.objective.constant)
|
|
234
236
|
|
|
235
237
|
def actualSolve(self, lp, callback=None):
|
|
236
238
|
"""
|
|
@@ -426,14 +426,30 @@ class XPRESS_PY(LpSolver):
|
|
|
426
426
|
def findSolutionValues(self, lp):
|
|
427
427
|
try:
|
|
428
428
|
model = lp.solverModel
|
|
429
|
-
|
|
429
|
+
|
|
430
|
+
# Build ordered lists of solver vars/cons for new API
|
|
431
|
+
# PuLP stores Xpress handles in v._xprs / c._xprs; index 1 is the handle.
|
|
432
|
+
xpress_vars = []
|
|
433
|
+
for v in lp.variables():
|
|
434
|
+
h = (
|
|
435
|
+
v._xprs[1]
|
|
436
|
+
if isinstance(v._xprs, (list, tuple)) and len(v._xprs) > 1
|
|
437
|
+
else v._xprs
|
|
438
|
+
)
|
|
439
|
+
xpress_vars.append((v, h))
|
|
440
|
+
|
|
441
|
+
xpress_cons = []
|
|
442
|
+
for n, c in lp.constraints.items():
|
|
443
|
+
h = (
|
|
444
|
+
c._xprs[1]
|
|
445
|
+
if isinstance(c._xprs, (list, tuple)) and len(c._xprs) > 1
|
|
446
|
+
else c._xprs
|
|
447
|
+
)
|
|
448
|
+
xpress_cons.append((n, c, h))
|
|
449
|
+
|
|
430
450
|
if _ismip(lp) and self.mip:
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
try:
|
|
434
|
-
model.getmipsol(x, slacks)
|
|
435
|
-
except:
|
|
436
|
-
x, slacks = None, None
|
|
451
|
+
# ------- MIP branch -------
|
|
452
|
+
vals = slacks = duals = djs = None
|
|
437
453
|
statusmap = {
|
|
438
454
|
0: constants.LpStatusUndefined, # XPRS_MIP_NOT_LOADED
|
|
439
455
|
1: constants.LpStatusUndefined, # XPRS_MIP_LP_NOT_OPTIMAL
|
|
@@ -445,14 +461,35 @@ class XPRESS_PY(LpSolver):
|
|
|
445
461
|
7: constants.LpStatusUndefined, # XPRS_MIP_UNBOUNDED
|
|
446
462
|
}
|
|
447
463
|
statuskey = "mipstatus"
|
|
448
|
-
|
|
449
|
-
#
|
|
450
|
-
x, slacks, duals, djs = [], [], [], []
|
|
464
|
+
|
|
465
|
+
# New API first
|
|
451
466
|
try:
|
|
452
|
-
model.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
467
|
+
var_vals = model.getSolution([h for _, h in xpress_vars])
|
|
468
|
+
slacks = (
|
|
469
|
+
model.getSlacks([h for _, _, h in xpress_cons])
|
|
470
|
+
if xpress_cons
|
|
471
|
+
else None
|
|
472
|
+
)
|
|
473
|
+
vals = var_vals
|
|
474
|
+
except Exception as e:
|
|
475
|
+
# Fallback to deprecated API (avoids DeprecationWarning only if not hit)
|
|
476
|
+
try:
|
|
477
|
+
print(e)
|
|
478
|
+
x_list, s_list = [], []
|
|
479
|
+
model.getmipsol(x_list, s_list)
|
|
480
|
+
vals, slacks = (
|
|
481
|
+
x_list if x_list else None,
|
|
482
|
+
s_list if s_list else None,
|
|
483
|
+
)
|
|
484
|
+
except Exception:
|
|
485
|
+
vals = slacks = None
|
|
486
|
+
|
|
487
|
+
duals = None
|
|
488
|
+
djs = None
|
|
489
|
+
|
|
490
|
+
else:
|
|
491
|
+
# ------- LP (continuous) branch -------
|
|
492
|
+
vals = slacks = duals = djs = None
|
|
456
493
|
statusmap = {
|
|
457
494
|
0: constants.LpStatusNotSolved, # XPRS_LP_UNSTARTED
|
|
458
495
|
1: constants.LpStatusOptimal, # XPRS_LP_OPTIMAL
|
|
@@ -465,24 +502,54 @@ class XPRESS_PY(LpSolver):
|
|
|
465
502
|
8: constants.LpStatusUndefined, # XPRS_LP_NONCONVEX
|
|
466
503
|
}
|
|
467
504
|
statuskey = "lpstatus"
|
|
468
|
-
|
|
469
|
-
|
|
505
|
+
|
|
506
|
+
# New API first
|
|
507
|
+
try:
|
|
508
|
+
var_vals = model.getSolution([h for _, h in xpress_vars])
|
|
509
|
+
vals = var_vals
|
|
510
|
+
slacks = (
|
|
511
|
+
model.getSlacks([h for _, _, h in xpress_cons])
|
|
512
|
+
if xpress_cons
|
|
513
|
+
else None
|
|
514
|
+
)
|
|
515
|
+
duals = (
|
|
516
|
+
model.getDuals([h for _, _, h in xpress_cons])
|
|
517
|
+
if xpress_cons
|
|
518
|
+
else None
|
|
519
|
+
)
|
|
520
|
+
djs = model.getRedCosts([h for _, h in xpress_vars])
|
|
521
|
+
except Exception:
|
|
522
|
+
# Fallback to deprecated API
|
|
523
|
+
try:
|
|
524
|
+
x_list, s_list, d_list, rc_list = [], [], [], []
|
|
525
|
+
model.getlpsol(x_list, s_list, d_list, rc_list)
|
|
526
|
+
vals = x_list if x_list else None
|
|
527
|
+
slacks = s_list if s_list else None
|
|
528
|
+
duals = d_list if d_list else None
|
|
529
|
+
djs = rc_list if rc_list else None
|
|
530
|
+
except Exception:
|
|
531
|
+
vals = slacks = duals = djs = None
|
|
532
|
+
|
|
533
|
+
# ---- write back into PuLP structures ----
|
|
534
|
+
if vals is not None:
|
|
535
|
+
lp.assignVarsVals(
|
|
536
|
+
{v.name: val for (v, _), val in zip(xpress_vars, vals)}
|
|
537
|
+
)
|
|
538
|
+
|
|
470
539
|
if djs is not None:
|
|
471
|
-
lp.assignVarsDj({v.name:
|
|
540
|
+
lp.assignVarsDj({v.name: rc for (v, _), rc in zip(xpress_vars, djs)})
|
|
541
|
+
|
|
472
542
|
if duals is not None:
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
543
|
+
# constraints dict preserves insertion order in Python 3.7+
|
|
544
|
+
lp.assignConsPi({n: pi for (n, c, _), pi in zip(xpress_cons, duals)})
|
|
545
|
+
|
|
476
546
|
if slacks is not None:
|
|
477
|
-
lp.assignConsSlack(
|
|
478
|
-
{n: slacks[c._xprs[0]] for (n, c) in lp.constraints.items()}
|
|
479
|
-
)
|
|
547
|
+
lp.assignConsSlack({n: s for (n, c, _), s in zip(xpress_cons, slacks)})
|
|
480
548
|
|
|
481
549
|
status = statusmap.get(
|
|
482
550
|
model.getAttrib(statuskey), constants.LpStatusUndefined
|
|
483
551
|
)
|
|
484
552
|
lp.assignStatus(status)
|
|
485
|
-
|
|
486
553
|
return status
|
|
487
554
|
|
|
488
555
|
except (xpress.ModelError, xpress.InterfaceError, xpress.SolverError) as err:
|
|
@@ -648,6 +715,39 @@ class XPRESS_PY(LpSolver):
|
|
|
648
715
|
"""
|
|
649
716
|
self._reset(lp)
|
|
650
717
|
try:
|
|
718
|
+
# Map PuLP senses -> XPRESS tokens (prefer xp.leq/geq/eq if present; else fall back to 'L','G','E')
|
|
719
|
+
_XP_LEQ = getattr(xpress, "leq", "L")
|
|
720
|
+
_XP_GEQ = getattr(xpress, "geq", "G")
|
|
721
|
+
_XP_EQ = getattr(xpress, "eq", "E")
|
|
722
|
+
|
|
723
|
+
_SENSE_MAP = {
|
|
724
|
+
constants.LpConstraintLE: _XP_LEQ,
|
|
725
|
+
constants.LpConstraintGE: _XP_GEQ,
|
|
726
|
+
constants.LpConstraintEQ: _XP_EQ,
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
def _xp_make_constraint(lhs, rhs, xp_sense, name=None):
|
|
730
|
+
"""
|
|
731
|
+
Create an XPRESS constraint in a way that works with both APIs:
|
|
732
|
+
new: xpress.constraint(body=..., type=..., rhs=..., name=...)
|
|
733
|
+
old: xpress.constraint(body=..., sense=..., rhs=..., name=...)
|
|
734
|
+
"""
|
|
735
|
+
# Prefer the new keyword first
|
|
736
|
+
try:
|
|
737
|
+
return xpress.constraint(
|
|
738
|
+
body=lhs, type=xp_sense, rhs=rhs, name=name
|
|
739
|
+
)
|
|
740
|
+
except TypeError:
|
|
741
|
+
# Fallback to old keyword
|
|
742
|
+
try:
|
|
743
|
+
return xpress.constraint(
|
|
744
|
+
body=lhs, sense=xp_sense, rhs=rhs, name=name
|
|
745
|
+
)
|
|
746
|
+
except TypeError as e:
|
|
747
|
+
raise PulpSolverError(
|
|
748
|
+
f"XPRESS constraint constructor is incompatible: {e}"
|
|
749
|
+
)
|
|
750
|
+
|
|
651
751
|
model = xpress.problem()
|
|
652
752
|
if lp.sense == constants.LpMaximize:
|
|
653
753
|
model.chgobjsense(xpress.maximize)
|
|
@@ -689,16 +789,14 @@ class XPRESS_PY(LpSolver):
|
|
|
689
789
|
for x, a in sorted(con.items(), key=lambda x: x[0]._xprs[0])
|
|
690
790
|
)
|
|
691
791
|
rhs = -con.constant
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
"Unsupprted constraint type " + str(con.sense)
|
|
701
|
-
)
|
|
792
|
+
|
|
793
|
+
xp_sense = _SENSE_MAP.get(con.sense)
|
|
794
|
+
if xp_sense is None:
|
|
795
|
+
raise PulpSolverError(f"Unsupported constraint type {con.sense}")
|
|
796
|
+
|
|
797
|
+
c = _xp_make_constraint(
|
|
798
|
+
lhs=lhs, rhs=rhs, xp_sense=xp_sense, name=con.name
|
|
799
|
+
)
|
|
702
800
|
cons.append((i, c, con))
|
|
703
801
|
if len(cons) > 100:
|
|
704
802
|
model.addConstraint([c for _, c, _ in cons])
|
|
@@ -306,9 +306,9 @@ def writeMPS(
|
|
|
306
306
|
wasNone, dummyVar = lp.fixObjective()
|
|
307
307
|
if mpsSense == 0:
|
|
308
308
|
mpsSense = lp.sense
|
|
309
|
-
|
|
310
|
-
if cobj is None:
|
|
309
|
+
if lp.objective is None:
|
|
311
310
|
raise ValueError("objective is None")
|
|
311
|
+
cobj: LpAffineExpression = lp.objective
|
|
312
312
|
if mpsSense != lp.sense:
|
|
313
313
|
n = cobj.name
|
|
314
314
|
cobj = -cobj
|
|
@@ -19,6 +19,7 @@ from pulp import (
|
|
|
19
19
|
LpFractionConstraint,
|
|
20
20
|
LpProblem,
|
|
21
21
|
LpVariable,
|
|
22
|
+
PulpSolverError,
|
|
22
23
|
)
|
|
23
24
|
from pulp import constants as const
|
|
24
25
|
from pulp import lpSum
|
|
@@ -924,7 +925,7 @@ class BaseSolverTest:
|
|
|
924
925
|
"""
|
|
925
926
|
Test the availability of the function pulpTestAll
|
|
926
927
|
"""
|
|
927
|
-
from pulp import pulpTestAll
|
|
928
|
+
from pulp.tests.run_tests import pulpTestAll
|
|
928
929
|
|
|
929
930
|
def test_export_dict_LP(self):
|
|
930
931
|
prob = LpProblem(self._testMethodName, const.LpMinimize)
|
|
@@ -2071,7 +2072,7 @@ class CPLEX_CMDTest(BaseSolverTest.PuLPTest):
|
|
|
2071
2072
|
|
|
2072
2073
|
|
|
2073
2074
|
class CPLEX_PYTest(BaseSolverTest.PuLPTest):
|
|
2074
|
-
solveInst = solvers.
|
|
2075
|
+
solveInst = solvers.CPLEX_PY
|
|
2075
2076
|
|
|
2076
2077
|
def _build(self, **kwargs):
|
|
2077
2078
|
"""
|
|
@@ -2082,6 +2083,15 @@ class CPLEX_PYTest(BaseSolverTest.PuLPTest):
|
|
|
2082
2083
|
solver.buildSolverModel(lp=problem)
|
|
2083
2084
|
return solver
|
|
2084
2085
|
|
|
2086
|
+
def test_search_param_without_solver_model(self):
|
|
2087
|
+
"""
|
|
2088
|
+
Tests the behavior of the `search_param` method when invoked without a `solverModel`
|
|
2089
|
+
initialized. Validates that an appropriate error is raised under these conditions.
|
|
2090
|
+
"""
|
|
2091
|
+
solver = self.solveInst()
|
|
2092
|
+
with self.assertRaises(PulpSolverError):
|
|
2093
|
+
solver.search_param("barrier.algorithm")
|
|
2094
|
+
|
|
2085
2095
|
def test_get_param(self):
|
|
2086
2096
|
"""
|
|
2087
2097
|
Tests the `get_param` method of the solver instance to ensure the correct
|
|
@@ -2118,13 +2128,6 @@ class CPLEX_PYTest(BaseSolverTest.PuLPTest):
|
|
|
2118
2128
|
solver = self._build(**{param: 100})
|
|
2119
2129
|
self.assertEqual(len(solver.get_changed_params()), 1)
|
|
2120
2130
|
|
|
2121
|
-
def test_set_all_params(self):
|
|
2122
|
-
solver = self._build()
|
|
2123
|
-
parameters = solver.get_all_params()
|
|
2124
|
-
for param, value in parameters:
|
|
2125
|
-
solver.set_param(name=str(param), value=value)
|
|
2126
|
-
self.assertEqual(solver.get_changed_params(), [])
|
|
2127
|
-
|
|
2128
2131
|
def test_callback(self):
|
|
2129
2132
|
from cplex.callbacks import IncumbentCallback # type: ignore[import-not-found, import-untyped, unused-ignore]
|
|
2130
2133
|
|
|
@@ -2136,7 +2139,9 @@ class CPLEX_PYTest(BaseSolverTest.PuLPTest):
|
|
|
2136
2139
|
counter += 1
|
|
2137
2140
|
|
|
2138
2141
|
problem = create_bin_packing_problem(bins=5, seed=55)
|
|
2139
|
-
pulpTestCheck(
|
|
2142
|
+
pulpTestCheck(
|
|
2143
|
+
problem, self.solver, [const.LpStatusOptimal], callback=[Callback]
|
|
2144
|
+
)
|
|
2140
2145
|
self.assertGreaterEqual(counter, 1)
|
|
2141
2146
|
|
|
2142
2147
|
|
|
@@ -2405,7 +2410,7 @@ def pulpTestCheck(
|
|
|
2405
2410
|
)
|
|
2406
2411
|
if sol is not None:
|
|
2407
2412
|
for v, x in sol.items():
|
|
2408
|
-
if abs(v.varValue - x) > eps:
|
|
2413
|
+
if v.varValue is not None and abs(v.varValue - x) > eps:
|
|
2409
2414
|
dumpTestProblem(prob)
|
|
2410
2415
|
raise PulpError(
|
|
2411
2416
|
"Tests failed for solver {}:\nvar {} == {} != {}".format(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "PuLP"
|
|
3
|
-
|
|
3
|
+
dynamic = ["version"]
|
|
4
4
|
description = "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
|
authors = [
|
|
6
6
|
{name = 'J.S. Roy'},
|
|
@@ -11,12 +11,12 @@ maintainers= [{name= "Franco Peschiera", email= "pchtsp@gmail.com"}]
|
|
|
11
11
|
dependencies = []
|
|
12
12
|
requires-python = ">=3.9"
|
|
13
13
|
readme = "README.rst"
|
|
14
|
-
license =
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
15
16
|
classifiers=[
|
|
16
17
|
"Development Status :: 5 - Production/Stable",
|
|
17
18
|
"Environment :: Console",
|
|
18
19
|
"Intended Audience :: Science/Research",
|
|
19
|
-
"License :: OSI Approved :: MIT License",
|
|
20
20
|
"Natural Language :: English",
|
|
21
21
|
"Programming Language :: Python",
|
|
22
22
|
"Topic :: Scientific/Engineering :: Mathematics",
|
|
@@ -39,7 +39,7 @@ module = [
|
|
|
39
39
|
follow_imports = "skip"
|
|
40
40
|
|
|
41
41
|
[build-system]
|
|
42
|
-
requires = ["setuptools"]
|
|
42
|
+
requires = ["setuptools>=77"]
|
|
43
43
|
build-backend = "setuptools.build_meta"
|
|
44
44
|
|
|
45
45
|
[dependency-groups]
|
|
@@ -63,8 +63,9 @@ packages = [
|
|
|
63
63
|
"pulp.solverdir.cbc.osx.i64",
|
|
64
64
|
"pulp.apis",
|
|
65
65
|
"pulp.tests"]
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
|
|
67
|
+
[tool.setuptools.dynamic]
|
|
68
|
+
version = {attr = "pulp.__version__"}
|
|
68
69
|
|
|
69
70
|
[tool.setuptools.package-data]
|
|
70
71
|
"pulp.solverdir.cbc.linux.i32"= ["*", "*.*"]
|
|
@@ -83,6 +84,19 @@ target-version = ['py37']
|
|
|
83
84
|
include = '\.pyi?$'
|
|
84
85
|
exclude = '/(\.eggs|\.git|\.hg|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist)/'
|
|
85
86
|
|
|
87
|
+
[tool.uv]
|
|
88
|
+
required-environments = [
|
|
89
|
+
"sys_platform == 'darwin'",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
86
93
|
[project.optional-dependencies]
|
|
87
|
-
open_py = ["cylp
|
|
88
|
-
public_py = ["gurobipy", "coptpy", 'xpress']
|
|
94
|
+
open_py = ["cylp", "highspy", "pyscipopt"]
|
|
95
|
+
public_py = ["gurobipy", "coptpy", 'xpress', "cplex; sys_platform != 'darwin' or python_full_version < '3.12'"]
|
|
96
|
+
highs = ["highspy"]
|
|
97
|
+
scip = ["pyscipopt"]
|
|
98
|
+
gurobi = ["gurobipy"]
|
|
99
|
+
xpress = ["xpress"]
|
|
100
|
+
copt = ["coptpy"]
|
|
101
|
+
cplex = ["cplex; sys_platform != 'darwin' or python_full_version < '3.12'"]
|
|
102
|
+
mosek = ["mosek"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|