• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stephane-caron / qpsolvers / 3928787436

pending completion
3928787436

Pull #158

github

GitHub
Merge 09bc904bb into 2f054d54c
Pull Request #158: Fix CVXOPT infinite bounds

69 of 69 new or added lines in 4 files covered. (100.0%)

1810 of 2072 relevant lines covered (87.36%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.56
/tests/test_solve_problem.py
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
#
4
# Copyright (C) 2023 Inria
5
#
6
# This file is part of qpsolvers.
7
#
8
# qpsolvers is free software: you can redistribute it and/or modify it under
9
# the terms of the GNU Lesser General Public License as published by the Free
10
# Software Foundation, either version 3 of the License, or (at your option) any
11
# later version.
12
#
13
# qpsolvers is distributed in the hope that it will be useful, but WITHOUT ANY
14
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15
# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
16
# details.
17
#
18
# You should have received a copy of the GNU Lesser General Public License
19
# along with qpsolvers. If not, see <http://www.gnu.org/licenses/>.
20

21
"""
1✔
22
Tests for the `solve_problem` function.
23
"""
24

25
import math
1✔
26
import unittest
1✔
27

28
from numpy.linalg import norm
1✔
29

30
from qpsolvers import available_solvers, solve_problem
1✔
31

32
from .solved_problems import (
1✔
33
    get_maros_meszaros_qptest,
34
    get_qpsut01,
35
    get_qpsut02,
36
    get_qpsut03,
37
)
38

39

40
class TestSolveProblem(unittest.TestCase):
1✔
41

42
    """
43
    Test fixture for primal and dual solutions of a variety of quadratic
44
    programs.
45

46
    Notes
47
    -----
48
    Solver-specific tests are implemented in static methods called
49
    ``get_test_{foo}`` that return the test function for a given solver. The
50
    corresponding test function ``test_{foo}_{solver}`` is then added to the
51
    fixture below the class definition.
52
    """
53

54
    @staticmethod
1✔
55
    def get_test_qpsut01(solver: str):
1✔
56
        """
57
        Get test function for a given solver.
58

59
        Parameters
60
        ----------
61
        solver :
62
            Name of the solver to test.
63

64
        Returns
65
        -------
66
        test : function
67
            Test function for that solver.
68
        """
69

70
        def test(self):
1✔
71
            ref_solution = get_qpsut01()
1✔
72
            problem = ref_solution.problem
1✔
73
            solution = solve_problem(problem, solver=solver)
1✔
74
            eps_abs = (
1✔
75
                5e-1
76
                if solver == "osqp"
77
                else 5e-3
78
                if solver == "proxqp"
79
                else 1e-4
80
                if solver == "ecos"
81
                else 1e-6
82
                if solver in ["cvxopt", "qpswift", "scs"]
83
                else 1e-7
84
            )
85
            self.assertLess(norm(solution.x - ref_solution.x), eps_abs)
1✔
86
            # NB: in general the dual solution is not unique (that's why the
87
            # other tests check residuals). This test only works because the
88
            # dual solution is unique in this particular problem.
89
            self.assertLess(norm(solution.y - ref_solution.y), eps_abs)
1✔
90
            self.assertLess(norm(solution.z - ref_solution.z), eps_abs)
1✔
91
            self.assertLess(norm(solution.z_box - ref_solution.z_box), eps_abs)
1✔
92

93
        return test
1✔
94

95
    @staticmethod
1✔
96
    def get_test_qpsut02(solver: str):
1✔
97
        """
98
        Get test function for a given solver.
99

100
        Parameters
101
        ----------
102
        solver :
103
            Name of the solver to test.
104

105
        Returns
106
        -------
107
        test : function
108
            Test function for that solver.
109
        """
110

111
        def test(self):
1✔
112
            ref_solution = get_qpsut02()
1✔
113
            problem = ref_solution.problem
1✔
114
            solution = solve_problem(problem, solver=solver)
1✔
115
            eps_abs = (
1✔
116
                5e-2
117
                if solver == "ecos"
118
                else 5e-4
119
                if solver in ["proxqp", "scs"]
120
                else 1e-4
121
                if solver == "cvxopt"
122
                else 1e-5
123
                if solver in ["highs", "osqp"]
124
                else 5e-7
125
                if solver == "qpswift"
126
                else 1e-7
127
                if solver == "gurobi"
128
                else 1e-8
129
            )
130
            self.assertLess(norm(solution.x - ref_solution.x), eps_abs)
1✔
131
            self.assertLess(solution.primal_residual(), eps_abs)
1✔
132
            self.assertLess(solution.dual_residual(), eps_abs)
1✔
133
            self.assertLess(solution.duality_gap(), eps_abs)
1✔
134

135
        return test
1✔
136

137
    @staticmethod
1✔
138
    def get_test_qpsut03(solver: str):
1✔
139
        """
140
        Get test function for a given solver.
141

142
        Parameters
143
        ----------
144
        solver :
145
            Name of the solver to test.
146

147
        Returns
148
        -------
149
        test : function
150
            Test function for that solver.
151
        """
152

153
        def test(self):
1✔
154
            ref_solution = get_qpsut03()
1✔
155
            problem = ref_solution.problem
1✔
156
            solution = solve_problem(problem, solver=solver)
1✔
157
            self.assertFalse(math.isnan(solution.duality_gap()))
1✔
158

159
        return test
1✔
160

161
    @staticmethod
1✔
162
    def get_test_maros_meszaros_qptest(solver):
1✔
163
        """
164
        Get test function for the QPTEST problem.
165

166
        Parameters
167
        ----------
168
        solver : string
169
            Name of the solver to test.
170

171
        Returns
172
        -------
173
        test : function
174
            Test function for that solver.
175

176
        Note
177
        ----
178
        ECOS fails to solve this problem.
179
        """
180

181
        def test(self):
1✔
182
            solution = get_maros_meszaros_qptest()
1✔
183
            problem = solution.problem
1✔
184
            result = solve_problem(problem, solver=solver)
1✔
185
            tolerance = (
1✔
186
                1e1
187
                if solver == "gurobi"
188
                else 1.0
189
                if solver == "proxqp"
190
                else 2e-3
191
                if solver == "osqp"
192
                else 5e-5
193
                if solver == "scs"
194
                else 1e-7
195
                if solver == "highs"
196
                else 5e-7
197
                if solver == "cvxopt"
198
                else 1e-8
199
            )
200
            self.assertIsNotNone(result.x)
1✔
201
            self.assertIsNotNone(result.z)
1✔
202
            self.assertIsNotNone(result.z_box)
1✔
203
            self.assertLess(norm(result.x - solution.x), tolerance)
1✔
204
            self.assertLess(norm(result.z - solution.z), tolerance)
1✔
205
            self.assertLess(norm(result.z_box - solution.z_box), tolerance)
1✔
206

207
        return test
1✔
208

209
    @staticmethod
1✔
210
    def get_test_infinite_linear_bounds(solver):
1✔
211
        """
212
        Problem with some infinite linear bounds.
213

214
        Parameters
215
        ----------
216
        solver : string
217
            Name of the solver to test.
218

219
        Returns
220
        -------
221
        test : function
222
            Test function for that solver.
223
        """
224

225
        def test(self):
×
226
            solution = get_qpsut01()
×
227
            problem = solution.problem
×
228
            result = solve_problem(problem, solver=solver)
×
229
            tolerance = (
×
230
                1e1
231
                if solver == "gurobi"
232
                else 1.0
233
                if solver == "proxqp"
234
                else 2e-3
235
                if solver == "osqp"
236
                else 5e-5
237
                if solver == "scs"
238
                else 1e-7
239
                if solver == "highs"
240
                else 1e-8
241
            )
242
            self.assertIsNotNone(result.x)
×
243
            self.assertIsNotNone(result.z)
×
244
            self.assertIsNotNone(result.z_box)
×
245
            self.assertLess(norm(result.x - solution.x), tolerance)
×
246
            self.assertLess(norm(result.z - solution.z), tolerance)
×
247
            self.assertLess(norm(result.z_box - solution.z_box), tolerance)
×
248

249
        return test
×
250

251

252
# Generate test fixtures for each solver
253
for solver in available_solvers:
1✔
254
    setattr(
1✔
255
        TestSolveProblem,
256
        f"test_qpsut01_{solver}",
257
        TestSolveProblem.get_test_qpsut01(solver),
258
    )
259
    setattr(
1✔
260
        TestSolveProblem,
261
        f"test_qpsut02_{solver}",
262
        TestSolveProblem.get_test_qpsut02(solver),
263
    )
264
    setattr(
1✔
265
        TestSolveProblem,
266
        f"test_qpsut03_{solver}",
267
        TestSolveProblem.get_test_qpsut03(solver),
268
    )
269
    if solver != "ecos":  # ECOS fails to solve this problem
1✔
270
        setattr(
1✔
271
            TestSolveProblem,
272
            f"test_maros_meszaros_qptest_{solver}",
273
            TestSolveProblem.get_test_maros_meszaros_qptest(solver),
274
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc