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:
227
app/solver/__init__.py
Normal file
227
app/solver/__init__.py
Normal file
@ -0,0 +1,227 @@
|
||||
import itertools
|
||||
from abc import abstractmethod
|
||||
from collections import defaultdict
|
||||
from typing import Tuple, override
|
||||
|
||||
import numpy as np
|
||||
|
||||
from app._typing import NDArray
|
||||
|
||||
from ._wrap import matrix, spmatrix
|
||||
from ._wrap.glpk import ilp
|
||||
from ._wrap.glpk import set_global_options as set_glpk_options
|
||||
|
||||
set_glpk_options({"msg_lev": "GLP_MSG_ERR"})
|
||||
|
||||
FROZEN_POS_EDGE = -1
|
||||
FROZEN_NEG_EDGE = -2
|
||||
INVALID_EDGE = -100
|
||||
|
||||
|
||||
class _BIPSolver:
|
||||
"""
|
||||
Base class for BIP solvers
|
||||
"""
|
||||
|
||||
min_affinity: float
|
||||
max_affinity: float
|
||||
|
||||
def __init__(self, min_affinity: float = -np.inf, max_affinity: float = np.inf):
|
||||
self.min_affinity = min_affinity
|
||||
self.max_affinity = max_affinity
|
||||
|
||||
@staticmethod
|
||||
def _create_bip(affinity_matrix: NDArray, min_affinity: float, max_affinity: float):
|
||||
n_nodes = affinity_matrix.shape[0]
|
||||
|
||||
# mask for selecting pairs of nodes
|
||||
triu_mask = np.triu(np.ones_like(affinity_matrix, dtype=bool), 1)
|
||||
|
||||
affinities = affinity_matrix[triu_mask]
|
||||
frozen_pos_mask = affinities >= max_affinity
|
||||
frozen_neg_mask = affinities <= min_affinity
|
||||
unfrozen_mask = np.logical_not(frozen_pos_mask | frozen_neg_mask)
|
||||
|
||||
# generate objective coefficients
|
||||
objective_coefficients = affinities[unfrozen_mask]
|
||||
|
||||
if len(objective_coefficients) == 0: # nio unfrozen edges
|
||||
|
||||
objective_coefficients = np.asarray([affinity_matrix[0, -1]])
|
||||
unfrozen_mask = np.zeros_like(unfrozen_mask, dtype=np.bool)
|
||||
unfrozen_mask[affinity_matrix.shape[1] - 1] = 1
|
||||
|
||||
# create matrix whose rows are the indices of the three edges in a
|
||||
# constraint x_ij + x_ik - x_jk <= 1
|
||||
constraints_edges_idx = []
|
||||
if n_nodes >= 3:
|
||||
edges_idx = np.empty_like(affinities, dtype=int)
|
||||
edges_idx[frozen_pos_mask] = FROZEN_POS_EDGE
|
||||
edges_idx[frozen_neg_mask] = FROZEN_NEG_EDGE
|
||||
edges_idx[unfrozen_mask] = np.arange(len(objective_coefficients))
|
||||
nodes_to_edge_matrix = np.empty_like(affinity_matrix, dtype=int)
|
||||
nodes_to_edge_matrix.fill(INVALID_EDGE)
|
||||
nodes_to_edge_matrix[triu_mask] = edges_idx
|
||||
|
||||
triplets = np.asarray(
|
||||
tuple(itertools.combinations(range(n_nodes), 3)), dtype=int
|
||||
)
|
||||
constraints_edges_idx = np.zeros_like(triplets)
|
||||
constraints_edges_idx[:, 0] = nodes_to_edge_matrix[
|
||||
(triplets[:, 0], triplets[:, 1])
|
||||
]
|
||||
constraints_edges_idx[:, 1] = nodes_to_edge_matrix[
|
||||
(triplets[:, 0], triplets[:, 2])
|
||||
]
|
||||
constraints_edges_idx[:, 2] = nodes_to_edge_matrix[
|
||||
(triplets[:, 1], triplets[:, 2])
|
||||
]
|
||||
constraints_edges_idx = constraints_edges_idx[
|
||||
np.any(constraints_edges_idx >= 0, axis=1)
|
||||
]
|
||||
|
||||
if len(constraints_edges_idx) == 0: # no constraints
|
||||
constraints_edges_idx = np.asarray([0, 0, 0], dtype=int).reshape(-1, 3)
|
||||
|
||||
# add remaining constraints by permutation
|
||||
constraints_edges_idx = np.vstack(
|
||||
(
|
||||
constraints_edges_idx,
|
||||
np.roll(constraints_edges_idx, 1, axis=1),
|
||||
np.roll(constraints_edges_idx, 2, axis=1),
|
||||
)
|
||||
)
|
||||
|
||||
# clean redundant constraints
|
||||
# x1 + x2 <= 2
|
||||
constraints_edges_idx = constraints_edges_idx[
|
||||
constraints_edges_idx[:, 2] != FROZEN_POS_EDGE
|
||||
]
|
||||
# x1 - x2 <= 1
|
||||
constraints_edges_idx = constraints_edges_idx[
|
||||
np.all(constraints_edges_idx[:, 0:2] != FROZEN_NEG_EDGE, axis=1)
|
||||
]
|
||||
if len(constraints_edges_idx) == 0: # no constraints
|
||||
constraints_edges_idx = np.asarray([0, 0, 0], dtype=int).reshape(-1, 3)
|
||||
|
||||
# generate constraint coefficients
|
||||
constraints_coefficients = np.ones_like(constraints_edges_idx)
|
||||
constraints_coefficients[:, 2] = -1
|
||||
|
||||
# generate constraint upper bounds
|
||||
upper_bounds = np.ones(len(constraints_coefficients), dtype=float)
|
||||
upper_bounds -= np.sum(
|
||||
constraints_coefficients * (constraints_edges_idx == FROZEN_POS_EDGE),
|
||||
axis=1,
|
||||
)
|
||||
|
||||
# flatten constraints data into sparse matrix format
|
||||
constraints_idx = np.repeat(np.arange(len(constraints_edges_idx)), 3)
|
||||
constraints_edges_idx = constraints_edges_idx.reshape(-1)
|
||||
constraints_coefficients = constraints_coefficients.reshape(-1)
|
||||
|
||||
unfrozen_edges = constraints_edges_idx >= 0
|
||||
constraints_idx = constraints_idx[unfrozen_edges]
|
||||
constraints_edges_idx = constraints_edges_idx[unfrozen_edges]
|
||||
constraints_coefficients = constraints_coefficients[unfrozen_edges]
|
||||
|
||||
return (
|
||||
objective_coefficients,
|
||||
unfrozen_mask,
|
||||
frozen_pos_mask,
|
||||
frozen_neg_mask,
|
||||
(constraints_coefficients, constraints_idx, constraints_edges_idx),
|
||||
upper_bounds,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def _solve_bip(self, objective_coefficients, sparse_constraints, upper_bounds): ...
|
||||
|
||||
@staticmethod
|
||||
def solution_mat_clusters(solution_mat: NDArray):
|
||||
n = solution_mat.shape[0]
|
||||
labels = np.arange(1, n + 1)
|
||||
for i in range(n):
|
||||
for j in range(i + 1, n):
|
||||
if solution_mat[i, j] > 0:
|
||||
labels[j] = labels[i]
|
||||
|
||||
clusters = defaultdict(list)
|
||||
for i, label in enumerate(labels):
|
||||
clusters[label].append(i)
|
||||
return list(clusters.values())
|
||||
|
||||
def solve(self, affinity_matrix, rtn_matrix=False):
|
||||
n_nodes = affinity_matrix.shape[0]
|
||||
if n_nodes <= 1:
|
||||
solution_x, sol_matrix = (
|
||||
np.asarray([], dtype=int),
|
||||
np.asarray([0] * n_nodes, dtype=int),
|
||||
)
|
||||
sol_matrix = sol_matrix[:, None]
|
||||
elif n_nodes == 2:
|
||||
solution_matrix = np.zeros_like(affinity_matrix, dtype=int)
|
||||
solution_matrix[0, 1] = affinity_matrix[0, 1] > 0
|
||||
solution_matrix += solution_matrix.T
|
||||
solution_x = (
|
||||
[solution_matrix[0, 1]]
|
||||
if self.min_affinity < affinity_matrix[0, 1] < self.max_affinity
|
||||
else []
|
||||
)
|
||||
solution_x, sol_matrix = np.asarray(solution_x), solution_matrix
|
||||
else:
|
||||
# create BIP problem
|
||||
(
|
||||
objective_coefficients,
|
||||
unfrozen_mask,
|
||||
frozen_pos_mask,
|
||||
frozen_neg_mask,
|
||||
sparse_constraints,
|
||||
upper_bounds,
|
||||
) = self._create_bip(affinity_matrix, self.min_affinity, self.max_affinity)
|
||||
|
||||
# solve
|
||||
solution_x = self._solve_bip(
|
||||
objective_coefficients, sparse_constraints, upper_bounds
|
||||
)
|
||||
|
||||
# solution to matrix
|
||||
all_sols = np.zeros_like(unfrozen_mask, dtype=int)
|
||||
all_sols[unfrozen_mask] = np.array(solution_x, dtype=int).reshape(-1)
|
||||
all_sols[frozen_neg_mask] = 0
|
||||
all_sols[frozen_pos_mask] = 1
|
||||
sol_matrix = np.zeros_like(affinity_matrix, dtype=int)
|
||||
sol_matrix[np.triu(np.ones([n_nodes, n_nodes], dtype=int), 1) > 0] = (
|
||||
all_sols
|
||||
)
|
||||
sol_matrix += sol_matrix.T
|
||||
|
||||
clusters = self.solution_mat_clusters(sol_matrix)
|
||||
if not rtn_matrix:
|
||||
return clusters
|
||||
return clusters, sol_matrix
|
||||
|
||||
|
||||
class GLPKSolver(_BIPSolver):
|
||||
def __init__(self, min_affinity=-np.inf, max_affinity=np.inf):
|
||||
super().__init__(min_affinity, max_affinity)
|
||||
|
||||
@override
|
||||
def _solve_bip(
|
||||
self,
|
||||
objective_coefficients: NDArray,
|
||||
sparse_constraints: Tuple[NDArray, NDArray, NDArray],
|
||||
upper_bounds: NDArray,
|
||||
):
|
||||
c = matrix(-objective_coefficients) # max -> min
|
||||
# G * x <= h
|
||||
G = spmatrix(
|
||||
*sparse_constraints, size=(len(upper_bounds), len(objective_coefficients))
|
||||
)
|
||||
h = matrix(upper_bounds, tc="d")
|
||||
|
||||
status, solution = ilp(c, G, h, B=set(range(len(c))))
|
||||
|
||||
assert solution is not None, "Solver error: {}".format(status)
|
||||
|
||||
return np.asarray(solution, int).reshape(-1)
|
||||
184
app/solver/_wrap/__init__.py
Normal file
184
app/solver/_wrap/__init__.py
Normal 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
249
app/solver/_wrap/glpk.py
Normal 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
|
||||
Reference in New Issue
Block a user