Skip to content

Commit e2ed0d7

Browse files
committed
Improve cuPDLPx solver integration
- Add warning when explicit_coordinate_names is set (unsupported) - Add warning when log_fn is provided (unsupported) - Fix typo in comment (cuDPLPx -> cuPDLPx) - Use pytest.skip() for clearer MIP test reporting - Guard feasible_mip_solvers.remove() when cupdlpx unavailable
1 parent 14883b5 commit e2ed0d7

File tree

3 files changed

+77
-52
lines changed

3 files changed

+77
-52
lines changed

linopy/io.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import logging
99
import shutil
1010
import time
11+
import warnings
1112
from collections.abc import Callable
1213
from io import BufferedWriter
1314
from pathlib import Path
@@ -764,23 +765,33 @@ def to_cupdlpx(m: Model, explicit_coordinate_names: bool = False) -> cupdlpxMode
764765
This function does not write the model to intermediate files but directly
765766
passes it to cupdlpx.
766767
767-
cuPDLPx does not support named variables and constraints, so names
768-
are not tracked by this function.
768+
cuPDLPx does not support named variables and constraints, so the
769+
`explicit_coordinate_names` parameter is ignored.
769770
770771
Parameters
771772
----------
772773
m : linopy.Model
774+
explicit_coordinate_names : bool, optional
775+
Ignored. cuPDLPx does not support named variables/constraints.
773776
774777
Returns
775778
-------
776779
model : cupdlpx.Model
777780
"""
778781
import cupdlpx
779782

783+
if explicit_coordinate_names:
784+
warnings.warn(
785+
"cuPDLPx does not support named variables/constraints. "
786+
"The explicit_coordinate_names parameter is ignored.",
787+
UserWarning,
788+
stacklevel=2,
789+
)
790+
780791
# build model using canonical form matrices and vectors
781792
# see https://github.com/MIT-Lu-Lab/cuPDLPx/tree/main/python#modeling
782793
M = m.matrices
783-
A = M.A.tocsr() # cuDPLPx only support CSR sparse matrix format
794+
A = M.A.tocsr() # cuPDLPx only supports CSR sparse matrix format
784795
# linopy stores constraints as Ax ?= b and keeps track of inequality
785796
# sense in M.sense. Convert to separate lower and upper bound vectors.
786797
l = np.where(

linopy/solvers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2487,6 +2487,9 @@ def _solve(
24872487
if basis_fn is not None:
24882488
logger.warning("Basis files are not supported by cuPDLPx. Ignoring.")
24892489

2490+
if log_fn is not None:
2491+
logger.warning("Log files are not supported by cuPDLPx. Ignoring.")
2492+
24902493
# solve
24912494
cu_model.optimize()
24922495

test/test_optimization.py

Lines changed: 60 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
feasible_quadratic_solvers.remove("scip")
5454

5555
feasible_mip_solvers: list[str] = available_solvers.copy()
56-
feasible_mip_solvers.remove("cupdlpx") # cuPDLPx does not support MIP yet
56+
if "cupdlpx" in feasible_mip_solvers:
57+
feasible_mip_solvers.remove("cupdlpx") # cuPDLPx does not support MIP yet
5758

5859
gpu_solvers: list[str] = ["cupdlpx"]
5960

@@ -630,11 +631,13 @@ def test_infeasible_model(
630631
def test_model_with_inf(
631632
model_with_inf: Model, solver: str, io_api: str, explicit_coordinate_names: bool
632633
) -> None:
633-
if solver in feasible_mip_solvers:
634-
status, condition = model_with_inf.solve(solver, io_api=io_api)
635-
assert condition == "optimal"
636-
assert (model_with_inf.solution.x == 0).all()
637-
assert (model_with_inf.solution.y == 10).all()
634+
if solver not in feasible_mip_solvers:
635+
pytest.skip(f"{solver} does not support MIP")
636+
637+
status, condition = model_with_inf.solve(solver, io_api=io_api)
638+
assert condition == "optimal"
639+
assert (model_with_inf.solution.x == 0).all()
640+
assert (model_with_inf.solution.y == 10).all()
638641

639642

640643
@pytest.mark.parametrize(
@@ -644,14 +647,16 @@ def test_model_with_inf(
644647
def test_milp_binary_model(
645648
milp_binary_model: Model, solver: str, io_api: str, explicit_coordinate_names: bool
646649
) -> None:
647-
if solver in feasible_mip_solvers:
648-
status, condition = milp_binary_model.solve(
649-
solver, io_api=io_api, explicit_coordinate_names=explicit_coordinate_names
650-
)
651-
assert condition == "optimal"
652-
assert (
653-
(milp_binary_model.solution.y == 1) | (milp_binary_model.solution.y == 0)
654-
).all()
650+
if solver not in feasible_mip_solvers:
651+
pytest.skip(f"{solver} does not support MIP")
652+
653+
status, condition = milp_binary_model.solve(
654+
solver, io_api=io_api, explicit_coordinate_names=explicit_coordinate_names
655+
)
656+
assert condition == "optimal"
657+
assert (
658+
(milp_binary_model.solution.y == 1) | (milp_binary_model.solution.y == 0)
659+
).all()
655660

656661

657662
@pytest.mark.parametrize(
@@ -664,15 +669,16 @@ def test_milp_binary_model_r(
664669
io_api: str,
665670
explicit_coordinate_names: bool,
666671
) -> None:
667-
if solver in feasible_mip_solvers:
668-
status, condition = milp_binary_model_r.solve(
669-
solver, io_api=io_api, explicit_coordinate_names=explicit_coordinate_names
670-
)
671-
assert condition == "optimal"
672-
assert (
673-
(milp_binary_model_r.solution.x == 1)
674-
| (milp_binary_model_r.solution.x == 0)
675-
).all()
672+
if solver not in feasible_mip_solvers:
673+
pytest.skip(f"{solver} does not support MIP")
674+
675+
status, condition = milp_binary_model_r.solve(
676+
solver, io_api=io_api, explicit_coordinate_names=explicit_coordinate_names
677+
)
678+
assert condition == "optimal"
679+
assert (
680+
(milp_binary_model_r.solution.x == 1) | (milp_binary_model_r.solution.x == 0)
681+
).all()
676682

677683

678684
@pytest.mark.parametrize(
@@ -682,12 +688,14 @@ def test_milp_binary_model_r(
682688
def test_milp_model(
683689
milp_model: Model, solver: str, io_api: str, explicit_coordinate_names: bool
684690
) -> None:
685-
if solver in feasible_mip_solvers:
686-
status, condition = milp_model.solve(
687-
solver, io_api=io_api, explicit_coordinate_names=explicit_coordinate_names
688-
)
689-
assert condition == "optimal"
690-
assert ((milp_model.solution.y == 9) | (milp_model.solution.x == 0.5)).all()
691+
if solver not in feasible_mip_solvers:
692+
pytest.skip(f"{solver} does not support MIP")
693+
694+
status, condition = milp_model.solve(
695+
solver, io_api=io_api, explicit_coordinate_names=explicit_coordinate_names
696+
)
697+
assert condition == "optimal"
698+
assert ((milp_model.solution.y == 9) | (milp_model.solution.x == 0.5)).all()
691699

692700

693701
@pytest.mark.parametrize(
@@ -697,19 +705,20 @@ def test_milp_model(
697705
def test_milp_model_r(
698706
milp_model_r: Model, solver: str, io_api: str, explicit_coordinate_names: bool
699707
) -> None:
700-
if solver in feasible_mip_solvers:
701-
# MPS format by Highs wrong, see https://github.com/ERGO-Code/HiGHS/issues/1325
702-
# skip it
703-
if io_api != "mps":
704-
status, condition = milp_model_r.solve(
705-
solver,
706-
io_api=io_api,
707-
explicit_coordinate_names=explicit_coordinate_names,
708-
)
709-
assert condition == "optimal"
710-
assert (
711-
(milp_model_r.solution.x == 11) | (milp_model_r.solution.y == 0)
712-
).all()
708+
if solver not in feasible_mip_solvers:
709+
pytest.skip(f"{solver} does not support MIP")
710+
711+
# MPS format by Highs wrong, see https://github.com/ERGO-Code/HiGHS/issues/1325
712+
if io_api == "mps":
713+
pytest.skip("MPS format bug in HiGHS")
714+
715+
status, condition = milp_model_r.solve(
716+
solver,
717+
io_api=io_api,
718+
explicit_coordinate_names=explicit_coordinate_names,
719+
)
720+
assert condition == "optimal"
721+
assert ((milp_model_r.solution.x == 11) | (milp_model_r.solution.y == 0)).all()
713722

714723

715724
@pytest.mark.parametrize(
@@ -834,13 +843,15 @@ def test_quadratic_model_unbounded(
834843
def test_modified_model(
835844
modified_model: Model, solver: str, io_api: str, explicit_coordinate_names: bool
836845
) -> None:
837-
if solver in feasible_mip_solvers:
838-
status, condition = modified_model.solve(
839-
solver, io_api=io_api, explicit_coordinate_names=explicit_coordinate_names
840-
)
841-
assert condition == "optimal"
842-
assert (modified_model.solution.x == 0).all()
843-
assert (modified_model.solution.y == 10).all()
846+
if solver not in feasible_mip_solvers:
847+
pytest.skip(f"{solver} does not support MIP")
848+
849+
status, condition = modified_model.solve(
850+
solver, io_api=io_api, explicit_coordinate_names=explicit_coordinate_names
851+
)
852+
assert condition == "optimal"
853+
assert (modified_model.solution.x == 0).all()
854+
assert (modified_model.solution.y == 10).all()
844855

845856

846857
@pytest.mark.parametrize("solver,io_api,explicit_coordinate_names", params)

0 commit comments

Comments
 (0)