1
0
forked from HQU-gxy/CVTH3PE

feat: Add CVXOPT solver infrastructure and VSCode settings

- Add CVXOPT dependency to pyproject.toml and uv.lock
- Create solver module with GLPK-based integer linear programming solver
- Add VSCode Python analysis settings
- Implement matrix and sparse matrix wrappers for CVXOPT
- Add GLPK solver wrapper with type-safe interfaces
This commit is contained in:
2025-03-03 17:27:42 +08:00
parent f0f71f7f81
commit 39da7992d4
6 changed files with 702 additions and 0 deletions

View File

@ -0,0 +1,184 @@
"""
See also:
https://github.com/cvxopt/cvxopt/blob/master/src/C/base.c
"""
from typing import (
Any,
BinaryIO,
Generic,
Literal,
Optional,
Protocol,
Sequence,
Tuple,
TypeVar,
Union,
overload,
)
import numpy as np
from cvxopt import matrix as cvxopt_matrix
from cvxopt import sparse as cvxopt_sparse
from cvxopt import spmatrix as cvxopt_spmatrix
from numpy.typing import NDArray
from typing_extensions import Self, TypeAlias
Typecode: TypeAlias = Literal["i", "d", "z"]
# Integer sparse matrices are not implemented.
SparseTypecode: TypeAlias = Literal["d", "z"]
DenseT = TypeVar("DenseT", int, float, complex)
SparseT = TypeVar("SparseT", float, complex)
IndexType = Union[int, slice, Sequence[int], "Matrix[int]"]
class Matrix(Generic[DenseT], Protocol):
"""
cvxopt.matrix interface
"""
@property
def size(self) -> Tuple[int, int]: ...
@property
def typecode(self) -> Typecode: ...
def __mul__(self, other): ...
def __add__(self, other): ...
def __sub__(self, other): ...
def __truediv__(self, other): ...
def __mod__(self, other): ...
def __len__(self) -> int: ...
def transpose(self) -> Self: ...
def ctrans(self) -> Self: ...
def real(self) -> "Matrix[float]": ...
def imag(self) -> "Matrix[float]": ...
def tofile(self, f: BinaryIO) -> None: ...
def fromfile(self, f: BinaryIO) -> None: ...
def __getitem__(
self, index: Union[IndexType, Tuple[IndexType, IndexType]]
) -> Union[DenseT, Self]: ...
def __setitem__(
self,
index: Union[IndexType, Tuple[IndexType, IndexType]],
value: Union[DenseT, "Matrix[Any]"],
) -> None: ...
@overload
def matrix(
data: Any, size: Optional[Tuple[int, int]] = None, tc: Typecode = "d"
) -> Matrix[float]: ...
@overload
def matrix(
data: Any, size: Optional[Tuple[int, int]] = None, tc: Typecode = "i"
) -> Matrix[int]: ...
@overload
def matrix(
data: Any, size: Optional[Tuple[int, int]] = None, tc: Typecode = "z"
) -> Matrix[complex]: ...
def matrix(data: Any, size: Optional[Tuple[int, int]] = None, tc: Typecode = "d"):
if size is None:
return cvxopt_matrix(data, tc=tc)
return cvxopt_matrix(data, size=size, tc=tc)
class SparseMatrix(Generic[SparseT], Protocol):
"""
cvxopt.spmatrix interface
"""
@property
def size(self) -> Tuple[int, int]: ...
@property
def typecode(self) -> Typecode: ...
@property
def V(self) -> "Matrix[SparseT]": ...
@property
def I(self) -> "Matrix[int]": ...
@property
def J(self) -> "Matrix[int]": ...
@property
def CCS(self) -> "Matrix[int]": ...
def __mul__(self, other): ...
def __add__(self, other): ...
def __sub__(self, other): ...
def __truediv__(self, other): ...
def __mod__(self, other): ...
def __len__(self) -> int: ...
def transpose(self) -> Self: ...
def ctrans(self) -> Self: ...
def real(self) -> "Matrix[float]": ...
def imag(self) -> "Matrix[float]": ...
def tofile(self, f: BinaryIO) -> None: ...
def fromfile(self, f: BinaryIO) -> None: ...
def __getitem__(
self, index: Union[IndexType, Tuple[IndexType, IndexType]]
) -> Union[DenseT, Self]: ...
def __setitem__(
self,
index: Union[IndexType, Tuple[IndexType, IndexType]],
value: Union[DenseT, "Matrix[Any]"],
) -> None: ...
@overload
def spmatrix(
x: Union[Sequence[float], float, Matrix[float], NDArray[np.floating[Any]]],
I: Union[Sequence[int], NDArray[np.int_]],
J: Union[Sequence[int], NDArray[np.int_]],
size: Optional[Tuple[int, int]] = None,
tc: SparseTypecode = "d",
) -> SparseMatrix[float]: ...
@overload
def spmatrix(
x: Union[
Sequence[complex], complex, Matrix[complex], NDArray[np.complexfloating[Any]]
],
I: Union[Sequence[int], NDArray[np.int_]],
J: Union[Sequence[int], NDArray[np.int_]],
size: Optional[Tuple[int, int]] = None,
tc: SparseTypecode = "z",
) -> SparseMatrix[complex]: ...
def spmatrix(
x: Any,
I: Any,
J: Any,
size: Optional[Tuple[int, int]] = None,
tc: SparseTypecode = "d",
):
if size is None:
return cvxopt_spmatrix(x, I, J, tc=tc)
return cvxopt_spmatrix(x, I, J, size=size, tc=tc)
@overload
def sparse(x: Any, tc: SparseTypecode = "d") -> SparseMatrix[float]: ...
@overload
def sparse(x: Any, tc: SparseTypecode = "z") -> SparseMatrix[complex]: ...
def sparse(x: Any, tc: SparseTypecode = "d"):
return cvxopt_sparse(x, tc=tc)

249
app/solver/_wrap/glpk.py Normal file
View File

@ -0,0 +1,249 @@
"""
See also:
https://github.com/cvxopt/cvxopt/blob/master/src/C/glpk.c
"""
from typing import Tuple, Union, Literal, Optional, Dict, Any, Set, overload, TypedDict
from cvxopt import glpk # type: ignore
from . import Matrix, SparseMatrix
CvxMatLike = Union[Matrix, SparseMatrix]
CvxBool = Literal["GLP_ON", "GLP_OFF"]
class GLPKOptions(TypedDict, total=False):
# Common parameters
msg_lev: Literal["GLP_MSG_OFF", "GLP_MSG_ERR", "GLP_MSG_ON", "GLP_MSG_ALL"]
presolve: CvxBool
tm_lim: int
out_frq: int
out_dly: int
# LP-specific parameters
meth: Literal["GLP_PRIMAL", "GLP_DUAL", "GLP_DUALP"]
pricing: Literal["GLP_PT_STD", "GLP_PT_PSE"]
r_test: Literal["GLP_RT_STD", "GLP_RT_HAR"]
tol_bnd: float
tol_dj: float
tol_piv: float
obj_ll: float
obj_ul: float
it_lim: int
# MILP-specific parameters
br_tech: Literal[
"GLP_BR_FFV", "GLP_BR_LFV", "GLP_BR_MFV", "GLP_BR_DTH", "GLP_BR_PCH"
]
bt_tech: Literal["GLP_BT_DFS", "GLP_BT_BFS", "GLP_BT_BLB", "GLP_BT_BPH"]
pp_tech: Literal["GLP_PP_NONE", "GLP_PP_ROOT", "GLP_PP_ALL"]
fp_heur: CvxBool
gmi_cuts: CvxBool
mir_cuts: CvxBool
cov_cuts: CvxBool
clq_cuts: CvxBool
tol_int: float
tol_obj: float
mip_gap: float
cb_size: int
binarize: CvxBool
StatusLP = Literal["optimal", "primal infeasible", "dual infeasible", "unknown"]
StatusILP = Literal[
"optimal",
"feasible",
"undefined",
"invalid formulation",
"infeasible problem",
"LP relaxation is primal infeasible",
"LP relaxation is dual infeasible",
"unknown",
]
@overload
def lp(
c: Matrix,
G: CvxMatLike,
h: Matrix,
) -> Tuple[StatusLP, Optional[Matrix], Optional[Matrix]]:
"""
(status, x, z) = lp(c, G, h)
PURPOSE
(status, x, z) = lp(c, G, h) solves the pair of primal and dual LPs
minimize c'*x maximize -h'*z
subject to G*x <= h subject to G'*z + c = 0
z >= 0.
ARGUMENTS
c nx1 dense 'd' matrix with n>=1
G mxn dense or sparse 'd' matrix with m>=1
h mx1 dense 'd' matrix
status 'optimal', 'primal infeasible', 'dual infeasible'
or 'unknown'
x if status is 'optimal', a primal optimal solution;
None otherwise
z if status is 'optimal', the dual optimal solution;
None otherwise
"""
@overload
def lp(
c: Matrix,
G: CvxMatLike,
h: Matrix,
A: CvxMatLike,
b: Matrix,
) -> Tuple[StatusLP, Optional[Matrix], Optional[Matrix], Optional[Matrix]]:
"""
(status, x, z, y) = lp(c, G, h, A, b)
PURPOSE
(status, x, z, y) = lp(c, G, h, A, b) solves the pair of primal and
dual LPs
minimize c'*x maximize -h'*z + b'*y
subject to G*x <= h subject to G'*z + A'*y + c = 0
A*x = b z >= 0.
ARGUMENTS
c nx1 dense 'd' matrix with n>=1
G mxn dense or sparse 'd' matrix with m>=1
h mx1 dense 'd' matrix
A pxn dense or sparse 'd' matrix with p>=0
b px1 dense 'd' matrix
status 'optimal', 'primal infeasible', 'dual infeasible'
or 'unknown'
x if status is 'optimal', a primal optimal solution;
None otherwise
z,y if status is 'optimal', the dual optimal solution;
None otherwise
"""
# https://cvxopt.org/userguide/coneprog.html#linear-programming
def lp(
c: Matrix,
G: CvxMatLike,
h: Matrix,
A: Optional[CvxMatLike] = None,
b: Optional[Matrix] = None,
):
"""
(status, x, z, y) = lp(c, G, h, A, b)
(status, x, z) = lp(c, G, h)
PURPOSE
(status, x, z, y) = lp(c, G, h, A, b) solves the pair of primal and
dual LPs
minimize c'*x maximize -h'*z + b'*y
subject to G*x <= h subject to G'*z + A'*y + c = 0
A*x = b z >= 0.
(status, x, z) = lp(c, G, h) solves the pair of primal and dual LPs
minimize c'*x maximize -h'*z
subject to G*x <= h subject to G'*z + c = 0
z >= 0.
ARGUMENTS
c nx1 dense 'd' matrix with n>=1
G mxn dense or sparse 'd' matrix with m>=1
h mx1 dense 'd' matrix
A pxn dense or sparse 'd' matrix with p>=0
b px1 dense 'd' matrix
status 'optimal', 'primal infeasible', 'dual infeasible'
or 'unknown'
x if status is 'optimal', a primal optimal solution;
None otherwise
z,y if status is 'optimal', the dual optimal solution;
None otherwise
"""
if A is None and b is None:
return glpk.lp(c, G, h)
return glpk.lp(c, G, h, A, b)
def ilp(
c: Matrix,
G: CvxMatLike,
h: Matrix,
A: Optional[CvxMatLike] = None,
b: Optional[Matrix] = None,
I: Optional[Set[int]] = None,
B: Optional[Set[int]] = None,
) -> Tuple[StatusILP, Optional[Matrix]]:
"""
Solves a mixed integer linear program using GLPK.
(status, x) = ilp(c, G, h, A, b, I, B)
PURPOSE
Solves the mixed integer linear programming problem
minimize c'*x
subject to G*x <= h
A*x = b
x[k] is integer for k in I
x[k] is binary for k in B
ARGUMENTS
c nx1 dense 'd' matrix with n>=1
G mxn dense or sparse 'd' matrix with m>=1
h mx1 dense 'd' matrix
A pxn dense or sparse 'd' matrix with p>=0
b px1 dense 'd' matrix
I set of indices of integer variables
B set of indices of binary variables
status if status is 'optimal', 'feasible', or 'undefined',
a value of x is returned and the status string
gives the status of x. Other possible values of
status are: 'invalid formulation',
'infeasible problem', 'LP relaxation is primal
infeasible', 'LP relaxation is dual infeasible',
'unknown'.
x a (sub-)optimal solution if status is 'optimal',
'feasible', or 'undefined'. None otherwise
"""
return glpk.ilp(c, G, h, A, b, I, B)
def set_global_options(options: GLPKOptions) -> None:
glpk.options = options
def get_global_options() -> GLPKOptions:
return glpk.options