diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be924ddc..d92d396f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-2022] steps: - uses: actions/checkout@master diff --git a/osqp_sources b/osqp_sources index f9fc23d3..16382ab2 160000 --- a/osqp_sources +++ b/osqp_sources @@ -1 +1 @@ -Subproject commit f9fc23d3436e4b17dd2cb95f70cfa1f37d122c24 +Subproject commit 16382ab256d60424d28f33d7e30ca3936fa475e8 diff --git a/pyproject.toml b/pyproject.toml index 704e3a4e..771409bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,4 +9,4 @@ write_to = "src/osqp/_version.py" build = "cp3*" skip = "*-win32 *-manylinux_i686 *-musllinux_*" test-requires = ["pytest"] -test-command = "pytest -s {project}/src/osqp/tests -k \"not codegen and not mkl\"" +test-command = "pytest -s {project}/src/osqp/tests -k \"not mkl\"" diff --git a/setup.py b/setup.py index b80b4db7..fe71bc1c 100644 --- a/setup.py +++ b/setup.py @@ -47,10 +47,10 @@ # Check if windows linux or mac to pass flag if system() == 'Windows': - cmake_args += ['-G', 'Visual Studio 14 2015'] + cmake_args += ['-G', 'Visual Studio 16 2019'] # Differentiate between 32-bit and 64-bit if sys.maxsize // 2 ** 32 > 0: - cmake_args[-1] += ' Win64' + cmake_args += ['-A', 'x64'] cmake_build_flags += ['--config', 'Release'] lib_name = 'osqp.lib' lib_subdir = ['Release'] diff --git a/src/osqp/codegen/code_generator.py b/src/osqp/codegen/code_generator.py index 5f91cf48..a5d2f890 100644 --- a/src/osqp/codegen/code_generator.py +++ b/src/osqp/codegen/code_generator.py @@ -184,3 +184,5 @@ def codegen(work, target_dir, python_ext_name, project_type, compile_python_ext, sh.copy(module_name, current_dir) os.chdir(current_dir) print("[done]") + + return current_dir diff --git a/src/osqp/interface.py b/src/osqp/interface.py index 1a2f23b7..8e9f49c4 100644 --- a/src/osqp/interface.py +++ b/src/osqp/interface.py @@ -310,7 +310,7 @@ def codegen(self, folder, project_type='', parameters='vectors', print("[done]") # Generate code with codegen module - cg.codegen(work, folder, python_ext_name, project_type, compile_python_ext, + return cg.codegen(work, folder, python_ext_name, project_type, compile_python_ext, embedded, force_rewrite, float_flag, long_flag) def derivative_iterative_refinement(self, rhs, max_iter=20, tol=1e-12): diff --git a/src/osqp/tests/codegen_lp_test.py b/src/osqp/tests/codegen_lp_test.py new file mode 100644 index 00000000..9332e3d8 --- /dev/null +++ b/src/osqp/tests/codegen_lp_test.py @@ -0,0 +1,110 @@ +# Test osqp python module +import osqp +# import osqppurepy as osqp +import numpy as np +from scipy import sparse + +# Unit Test +import unittest +import pytest +import numpy.testing as nptest +import shutil as sh +import sys + + +# OSQP Problem in which P is None, thus reducing it to an LP +class codegen_lp_tests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + P = None + q = np.array([3, 4]) + A = sparse.csc_matrix([[-1, 0], [0, -1], [-1, -3], + [2, 5], [3, 4]]) + A_new = sparse.csc_matrix([[-1, 0], [0, -1], [-2, -2], + [2, 5], [3, 4]]) + u = np.array([0, 0, -15, 100, 80]) + l = -np.inf * np.ones(len(u)) + n = 2 + m = A.shape[0] + opts = {'verbose': False, + 'eps_abs': 1e-08, + 'eps_rel': 1e-08, + 'alpha': 1.6, + 'max_iter': 3000, + 'warm_start': True} + + model = osqp.OSQP() + model.setup(P=P, q=q, A=A, l=l, u=u, **opts) + + model_dir = model.codegen('code2', python_ext_name='mat_lp_emosqp', force_rewrite=True, parameters='matrices') + sh.rmtree('code2') + sys.path.append(model_dir) + + cls.m = m + cls.n = n + cls.P = P + cls.q = q + cls.A = A + cls.A_new = A_new + cls.l = l + cls.u = u + cls.opts = opts + + def setUp(self): + + self.model = osqp.OSQP() + self.model.setup(P=self.P, q=self.q, A=self.A, l=self.l, u=self.u, + **self.opts) + + def test_solve(self): + import mat_lp_emosqp as mat_emosqp + + # Solve problem + x, y, _, _, _ = mat_emosqp.solve() + + # Assert close + nptest.assert_array_almost_equal(x, np.array([0., 5.]), decimal=5) + nptest.assert_array_almost_equal( + y, np.array([1.66666, 0., 1.33333, 0., 0.]), decimal=5) + + def test_update_A(self): + import mat_lp_emosqp as mat_emosqp + + # Update matrix A + Ax = self.A_new.data + Ax_idx = np.arange(self.A_new.nnz) + mat_emosqp.update_A(Ax, Ax_idx, len(Ax)) + + # Solve problem + x, y, _, _, _ = mat_emosqp.solve() + + # Assert close + nptest.assert_array_almost_equal(x, + np.array([7.5, 2.09205935e-08]), decimal=5) + nptest.assert_array_almost_equal( + y, np.array([0., 1., 1.5, 0., 0.]), decimal=5) + + # Update matrix A to the original value + Ax = self.A.data + Ax_idx = np.arange(self.A.nnz) + mat_emosqp.update_A(Ax, Ax_idx, len(Ax)) + + def test_update_A_allind(self): + import mat_lp_emosqp as mat_emosqp + + # Update matrix A + Ax = self.A_new.data + mat_emosqp.update_A(Ax, None, 0) + x, y, _, _, _ = mat_emosqp.solve() + + # Assert close + nptest.assert_array_almost_equal(x, + np.array([7.5, 2.09205935e-08]), decimal=5) + nptest.assert_array_almost_equal( + y, np.array([0., 1, 1.5, 0., 0.]), decimal=5) + + # Update matrix A to the original value + Ax = self.A.data + Ax_idx = np.arange(self.A.nnz) + mat_emosqp.update_A(Ax, Ax_idx, len(Ax)) diff --git a/src/osqp/tests/codegen_matrices_test.py b/src/osqp/tests/codegen_matrices_test.py index 18db623a..8285757f 100644 --- a/src/osqp/tests/codegen_matrices_test.py +++ b/src/osqp/tests/codegen_matrices_test.py @@ -6,41 +6,59 @@ # Unit Test import unittest +import pytest import numpy.testing as nptest import shutil as sh +import sys class codegen_matrices_tests(unittest.TestCase): - def setUp(self): - # Simple QP problem - self.P = sparse.diags([11., 0.1], format='csc') - self.P_new = sparse.eye(2, format='csc') - self.q = np.array([3, 4]) - self.A = sparse.csc_matrix([[-1, 0], [0, -1], [-1, -3], + @classmethod + def setUpClass(cls): + P = sparse.diags([11., 0.1], format='csc') + P_new = sparse.eye(2, format='csc') + q = np.array([3, 4]) + A = sparse.csc_matrix([[-1, 0], [0, -1], [-1, -3], [2, 5], [3, 4]]) - self.A_new = sparse.csc_matrix([[-1, 0], [0, -1], [-2, -2], + A_new = sparse.csc_matrix([[-1, 0], [0, -1], [-2, -2], [2, 5], [3, 4]]) - self.u = np.array([0, 0, -15, 100, 80]) - self.l = -np.inf * np.ones(len(self.u)) - self.n = self.P.shape[0] - self.m = self.A.shape[0] - self.opts = {'verbose': False, + u = np.array([0, 0, -15, 100, 80]) + l = -np.inf * np.ones(len(u)) + n = P.shape[0] + m = A.shape[0] + opts = {'verbose': False, 'eps_abs': 1e-08, 'eps_rel': 1e-08, 'alpha': 1.6, 'max_iter': 3000, 'warm_start': True} + + model = osqp.OSQP() + model.setup(P=P, q=q, A=A, l=l, u=u, **opts) + + model_dir = model.codegen('code2', python_ext_name='mat_emosqp', force_rewrite=True, parameters='matrices') + sh.rmtree('code2') + sys.path.append(model_dir) + + cls.m = m + cls.n = n + cls.P = P + cls.P_new = P_new + cls.q = q + cls.A = A + cls.A_new = A_new + cls.l = l + cls.u = u + cls.opts = opts + + def setUp(self): + self.model = osqp.OSQP() self.model.setup(P=self.P, q=self.q, A=self.A, l=self.l, u=self.u, **self.opts) def test_solve(self): - # Generate the code - self.model.codegen('code2', python_ext_name='mat_emosqp', - force_rewrite=True, parameters='matrices') - - sh.rmtree('code2') import mat_emosqp # Solve problem diff --git a/src/osqp/tests/codegen_vectors_test.py b/src/osqp/tests/codegen_vectors_test.py index 1f7518a3..f14cadef 100644 --- a/src/osqp/tests/codegen_vectors_test.py +++ b/src/osqp/tests/codegen_vectors_test.py @@ -6,38 +6,55 @@ # Unit Test import unittest +import pytest import numpy.testing as nptest import shutil as sh +import sys class codegen_vectors_tests(unittest.TestCase): - def setUp(self): - # Simple QP problem - self.P = sparse.diags([11., 0.], format='csc') - self.q = np.array([3, 4]) - self.A = sparse.csc_matrix( + @classmethod + def setUpClass(cls): + P = sparse.diags([11., 0.], format='csc') + q = np.array([3, 4]) + A = sparse.csc_matrix( [[-1, 0], [0, -1], [-1, -3], [2, 5], [3, 4]]) - self.u = np.array([0, 0, -15, 100, 80]) - self.l = -np.inf * np.ones(len(self.u)) - self.n = self.P.shape[0] - self.m = self.A.shape[0] - self.opts = {'verbose': False, + u = np.array([0, 0, -15, 100, 80]) + l = -np.inf * np.ones(len(u)) + n = P.shape[0] + m = A.shape[0] + opts = {'verbose': False, 'eps_abs': 1e-08, 'eps_rel': 1e-08, 'rho': 0.01, 'alpha': 1.6, 'max_iter': 10000, 'warm_start': True} + + model = osqp.OSQP() + model.setup(P=P, q=q, A=A, l=l, u=u, **opts) + + model_dir = model.codegen('code', python_ext_name='vec_emosqp', + force_rewrite=True) + sh.rmtree('code') + sys.path.append(model_dir) + + cls.m = m + cls.n = n + cls.P = P + cls.q = q + cls.A = A + cls.l = l + cls.u = u + cls.opts = opts + + def setUp(self): self.model = osqp.OSQP() self.model.setup(P=self.P, q=self.q, A=self.A, l=self.l, u=self.u, **self.opts) def test_solve(self): - # Generate the code - self.model.codegen('code', python_ext_name='vec_emosqp', - force_rewrite=True) - sh.rmtree('code') import vec_emosqp # Solve problem