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.
Files changed (60) hide show
  1. pulp-3.3.0/LICENSE +22 -0
  2. {pulp-3.2.2 → pulp-3.3.0}/PKG-INFO +39 -4
  3. {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/PKG-INFO +39 -4
  4. {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/SOURCES.txt +1 -0
  5. pulp-3.3.0/PuLP.egg-info/requires.txt +36 -0
  6. {pulp-3.2.2 → pulp-3.3.0}/README.rst +19 -0
  7. {pulp-3.2.2 → pulp-3.3.0}/pulp/__init__.py +0 -1
  8. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/coin_api.py +4 -0
  9. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/cplex_api.py +22 -3
  10. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/cuopt_api.py +4 -2
  11. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/xpress_api.py +132 -34
  12. {pulp-3.2.2 → pulp-3.3.0}/pulp/constants.py +1 -1
  13. {pulp-3.2.2 → pulp-3.3.0}/pulp/mps_lp.py +2 -2
  14. {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_pulp.py +16 -11
  15. {pulp-3.2.2 → pulp-3.3.0}/pyproject.toml +22 -8
  16. pulp-3.2.2/PuLP.egg-info/requires.txt +0 -12
  17. {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/dependency_links.txt +0 -0
  18. {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/entry_points.txt +0 -0
  19. {pulp-3.2.2 → pulp-3.3.0}/PuLP.egg-info/top_level.txt +0 -0
  20. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/__init__.py +0 -0
  21. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/choco_api.py +0 -0
  22. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/copt_api.py +0 -0
  23. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/core.py +0 -0
  24. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/glpk_api.py +0 -0
  25. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/gurobi_api.py +0 -0
  26. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/highs_api.py +0 -0
  27. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/mipcl_api.py +0 -0
  28. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/mosek_api.py +0 -0
  29. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/sas_api.py +0 -0
  30. {pulp-3.2.2 → pulp-3.3.0}/pulp/apis/scip_api.py +0 -0
  31. {pulp-3.2.2 → pulp-3.3.0}/pulp/pulp.py +0 -0
  32. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/__init__.py +0 -0
  33. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/arm64/__init__.py +0 -0
  34. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/arm64/cbc +0 -0
  35. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/arm64/coin-license.txt +0 -0
  36. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i32/__init__.py +0 -0
  37. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i32/cbc +0 -0
  38. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i32/coin-license.txt +0 -0
  39. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i64/__init__.py +0 -0
  40. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i64/cbc +0 -0
  41. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/linux/i64/coin-license.txt +0 -0
  42. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/osx/i64/__init__.py +0 -0
  43. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/osx/i64/cbc +0 -0
  44. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/osx/i64/coin-license.txt +0 -0
  45. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i32/__init__.py +0 -0
  46. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i32/cbc.exe +0 -0
  47. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i32/coin-license.txt +0 -0
  48. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i64/__init__.py +0 -0
  49. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i64/cbc.exe +0 -0
  50. {pulp-3.2.2 → pulp-3.3.0}/pulp/solverdir/cbc/win/i64/coin-license.txt +0 -0
  51. {pulp-3.2.2 → pulp-3.3.0}/pulp/sparse.py +0 -0
  52. {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/__init__.py +0 -0
  53. {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/bin_packing_problem.py +0 -0
  54. {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/run_tests.py +0 -0
  55. {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_examples.py +0 -0
  56. {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_gurobipy_env.py +0 -0
  57. {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_lpdot.py +0 -0
  58. {pulp-3.2.2 → pulp-3.3.0}/pulp/tests/test_sparse.py +0 -0
  59. {pulp-3.2.2 → pulp-3.3.0}/pulp/utilities.py +0 -0
  60. {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.2.2
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; sys_platform != "win32" and extra == "open-py"
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.2.2
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; sys_platform != "win32" and extra == "open-py"
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,3 +1,4 @@
1
+ LICENSE
1
2
  README.rst
2
3
  pyproject.toml
3
4
  PuLP.egg-info/PKG-INFO
@@ -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
  ===============
@@ -35,7 +35,6 @@ from .pulp import *
35
35
  from .apis import *
36
36
  from .utilities import *
37
37
  from .constants import *
38
- from .tests import pulpTestAll
39
38
 
40
39
  __doc__ = pulp.__doc__ # type: ignore[name-defined]
41
40
  __version__ = VERSION
@@ -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.solverParams = solverParams
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.solverParams.items():
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
- # Collect results
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
- # Solved as MIP
432
- x, slacks, duals, djs = [], [], None, None
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
- else:
449
- # Solved as continuous
450
- x, slacks, duals, djs = [], [], [], []
464
+
465
+ # New API first
451
466
  try:
452
- model.getlpsol(x, slacks, duals, djs)
453
- except:
454
- # No solution available
455
- x, slacks, duals, djs = None, None, None, None
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
- if x is not None:
469
- lp.assignVarsVals({v.name: x[v._xprs[0]] for v in lp.variables()})
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: djs[v._xprs[0]] for v in lp.variables()})
540
+ lp.assignVarsDj({v.name: rc for (v, _), rc in zip(xpress_vars, djs)})
541
+
472
542
  if duals is not None:
473
- lp.assignConsPi(
474
- {n: duals[c._xprs[0]] for (n, c) in lp.constraints.items()}
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
- if con.sense == constants.LpConstraintLE:
693
- c = xpress.constraint(body=lhs, sense=xpress.leq, rhs=rhs)
694
- elif con.sense == constants.LpConstraintGE:
695
- c = xpress.constraint(body=lhs, sense=xpress.geq, rhs=rhs)
696
- elif con.sense == constants.LpConstraintEQ:
697
- c = xpress.constraint(body=lhs, sense=xpress.eq, rhs=rhs)
698
- else:
699
- raise PulpSolverError(
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])
@@ -27,7 +27,7 @@
27
27
  This file contains the constant definitions for PuLP
28
28
  Note that hopefully these will be changed into something more pythonic
29
29
  """
30
- VERSION = "3.0.2"
30
+ VERSION = "3.3.0"
31
31
  EPS = 1e-7
32
32
 
33
33
  # variable categories
@@ -306,9 +306,9 @@ def writeMPS(
306
306
  wasNone, dummyVar = lp.fixObjective()
307
307
  if mpsSense == 0:
308
308
  mpsSense = lp.sense
309
- cobj = lp.objective
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.CPLEX_CMD
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(problem, self.solver, [LpStatusOptimal], callback=[Callback])
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
- version = "3.2.2"
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 = {text = "MIT"}
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
- # This is a workaround for https://github.com/astral-sh/uv/issues/9513
67
- license-files = []
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; sys_platform != 'win32'", "highspy", "pyscipopt"]
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"]
@@ -1,12 +0,0 @@
1
-
2
- [open_py]
3
- highspy
4
- pyscipopt
5
-
6
- [open_py:sys_platform != "win32"]
7
- cylp
8
-
9
- [public_py]
10
- gurobipy
11
- coptpy
12
- xpress
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