Coverage for lpsolvers / __init__.py: 69%

52 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-20 15:19 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3# 

4# SPDX-License-Identifier: LGPL-3.0-or-later 

5# Copyright (C) 2016-2022 Stéphane Caron 

6 

7"""Linear programming solvers in Python with a unified API.""" 

8 

9from typing import Optional 

10 

11import numpy as np 

12 

13from .exceptions import NoSolverSelected, SolverNotFound 

14 

15__version__ = "2.2.0" 

16 

17available_solvers = [] 

18 

19 

20# cdd 

21# === 

22 

23try: 

24 from .cdd_ import cdd_solve_lp 

25 

26 available_solvers.append("cdd") 

27except ImportError: 

28 

29 def cdd_solve_lp( 

30 c: np.ndarray, 

31 G: np.ndarray, 

32 h: np.ndarray, 

33 A: Optional[np.ndarray] = None, 

34 b: Optional[np.ndarray] = None, 

35 ) -> np.ndarray: 

36 """Error function defined when cdd is not available.""" 

37 raise ImportError("cdd not found") 

38 

39 

40# CVXOPT 

41# ====== 

42 

43try: 

44 from .cvxopt_ import cvxopt_solve_lp 

45 

46 available_solvers.append("cvxopt") 

47except ImportError: 

48 

49 def cvxopt_solve_lp( 

50 c: np.ndarray, 

51 G: np.ndarray, 

52 h: np.ndarray, 

53 A: Optional[np.ndarray] = None, 

54 b: Optional[np.ndarray] = None, 

55 solver: Optional[str] = None, 

56 **kwargs, 

57 ) -> np.ndarray: 

58 """Error function defined when CVXOPT is not available.""" 

59 raise ImportError("CVXOPT not found") 

60 

61 

62# CVXPY 

63# ===== 

64 

65try: 

66 from .cvxpy_ import cvxpy_solve_lp 

67 

68 available_solvers.append("cvxpy") 

69except ImportError: 

70 

71 def cvxpy_solve_lp( 

72 c: np.ndarray, 

73 G: np.ndarray, 

74 h: np.ndarray, 

75 A: Optional[np.ndarray] = None, 

76 b: Optional[np.ndarray] = None, 

77 solver: Optional[str] = None, 

78 verbose: bool = False, 

79 **kwargs, 

80 ) -> np.ndarray: 

81 """Error function defined when CVXPY is not available.""" 

82 raise ImportError("CVXPY not found") 

83 

84 

85# PDLP 

86# ==== 

87 

88try: 

89 from .pdlp_ import pdlp_solve_lp 

90 

91 available_solvers.append("pdlp") 

92except ImportError: 

93 

94 def pdlp_solve_lp( 

95 c: np.ndarray, 

96 G: np.ndarray, 

97 h: np.ndarray, 

98 A: Optional[np.ndarray] = None, 

99 b: Optional[np.ndarray] = None, 

100 verbose: bool = False, 

101 eps_optimal_absolute: Optional[float] = None, 

102 eps_optimal_relative: Optional[float] = None, 

103 time_sec_limits: Optional[float] = None, 

104 **kwargs, 

105 ) -> np.ndarray: 

106 """Error function defined when PDLP is not available.""" 

107 raise ImportError("PDLP not found") 

108 

109 

110# ProxQP 

111# ====== 

112 

113try: 

114 from .proxqp_ import proxqp_solve_lp 

115 

116 available_solvers.append("proxqp") 

117except ImportError: 

118 

119 def proxqp_solve_lp( 

120 c: np.ndarray, 

121 G: np.ndarray, 

122 h: np.ndarray, 

123 A: Optional[np.ndarray] = None, 

124 b: Optional[np.ndarray] = None, 

125 verbose: bool = False, 

126 backend: Optional[str] = None, 

127 **kwargs, 

128 ) -> np.ndarray: 

129 """Error function defined when ProxQP is not available.""" 

130 raise ImportError("ProxQP not found") 

131 

132 

133def solve_lp( 

134 c: np.ndarray, 

135 G: np.ndarray, 

136 h: np.ndarray, 

137 A: Optional[np.ndarray] = None, 

138 b: Optional[np.ndarray] = None, 

139 solver: Optional[str] = None, 

140 **kwargs, 

141) -> np.ndarray: 

142 r"""Solve a linear program using one of the available LP solvers. 

143 

144 The linear program is defined as: 

145 

146 .. math:: 

147 

148 \begin{split}\begin{array}{ll} 

149 \mbox{minimize} & 

150 c^T x \\ 

151 \mbox{subject to} 

152 & G x \leq h \\ 

153 & A x = b 

154 \end{array}\end{split} 

155 

156 Parameters 

157 ---------- 

158 c : 

159 Linear cost vector. 

160 G : 

161 Linear inequality constraint matrix. 

162 h : 

163 Linear inequality constraint vector. 

164 A : 

165 Linear equality constraint matrix. 

166 b : 

167 Linear equality constraint vector. 

168 solver : 

169 Name of the LP solver to choose in :data:`lpsolvers.available_solvers`. 

170 

171 Returns 

172 ------- 

173 : 

174 Optimal solution if found, ``None`` otherwise. 

175 

176 Raises 

177 ------ 

178 ValueError 

179 If the LP is not feasible. 

180 SolverNotFound 

181 If the requested LP solver is not found. 

182 

183 Notes 

184 ----- 

185 Extra keyword arguments given to this function are forwarded to the 

186 underlying solver. For example, we can call ProxQP with a custom absolute 

187 feasibility tolerance by ``solve_lp(c, G, h, solver='proxqp', 

188 eps_abs=1e-8)``. 

189 """ 

190 if solver is None: 

191 raise NoSolverSelected( 

192 "Set the `solver` keyword argument to one of the " 

193 f"available solvers in {available_solvers}" 

194 ) 

195 if isinstance(G, np.ndarray) and G.ndim == 1: 

196 G = G.reshape((1, G.shape[0])) 

197 if solver == "cdd": 

198 return cdd_solve_lp(c, G, h, A, b) 

199 if solver == "cvxopt": 

200 return cvxopt_solve_lp(c, G, h, A, b, **kwargs) 

201 if solver == "cvxpy": 

202 return cvxpy_solve_lp(c, G, h, A, b, **kwargs) 

203 if solver == "pdlp": 

204 return pdlp_solve_lp(c, G, h, A, b, **kwargs) 

205 if solver == "proxqp": 

206 return proxqp_solve_lp(c, G, h, A, b, **kwargs) 

207 raise SolverNotFound(f"solver '{solver}' is not available") 

208 

209 

210__all__ = [ 

211 "__version__", 

212 "available_solvers", 

213 "cdd_solve_lp", 

214 "cvxopt_solve_lp", 

215 "cvxpy_solve_lp", 

216 "pdlp_solve_lp", 

217 "proxqp_solve_lp", 

218 "solve_lp", 

219]