diff --git a/.github/workflows/build_cuda.yml b/.github/workflows/build_cuda.yml index 059f5405..3384ca6f 100644 --- a/.github/workflows/build_cuda.yml +++ b/.github/workflows/build_cuda.yml @@ -2,13 +2,11 @@ name: Build CUDA Linux on: push: - branches: - - master + branches: [ master, develop**, ci ] tags: - '*' pull_request: - branches: - - master + branches: [ master, develop** ] jobs: build_wheels: diff --git a/.github/workflows/build_cuda_windows.yml b/.github/workflows/build_cuda_windows.yml index 434d44ec..2c0e361f 100644 --- a/.github/workflows/build_cuda_windows.yml +++ b/.github/workflows/build_cuda_windows.yml @@ -2,13 +2,11 @@ name: Build CUDA Windows on: push: - branches: - - master + branches: [ master, develop**, ci ] tags: - '*' pull_request: - branches: - - master + branches: [ master, develop** ] env: CUDATOOLKIT_URL: https://developer.download.nvidia.com/compute/cuda/12.6.3/local_installers/cuda_12.6.3_561.17_windows.exe diff --git a/.github/workflows/build_default.yml b/.github/workflows/build_default.yml index 793185a9..149ed276 100644 --- a/.github/workflows/build_default.yml +++ b/.github/workflows/build_default.yml @@ -2,13 +2,11 @@ name: Build Default on: push: - branches: - - master + branches: [ master, develop**, ci ] tags: - '*' pull_request: - branches: - - master + branches: [ master, develop** ] jobs: build_sdist: diff --git a/.github/workflows/build_mkl.yml b/.github/workflows/build_mkl.yml index 8cb11185..5b95730e 100644 --- a/.github/workflows/build_mkl.yml +++ b/.github/workflows/build_mkl.yml @@ -2,13 +2,11 @@ name: Build MKL Mac/Linux on: push: - branches: - - master + branches: [ master, develop**, ci ] tags: - '*' pull_request: - branches: - - master + branches: [ master, develop** ] jobs: build_wheels: diff --git a/.github/workflows/build_mkl_windows.yml b/.github/workflows/build_mkl_windows.yml index 5710466c..d98c9037 100644 --- a/.github/workflows/build_mkl_windows.yml +++ b/.github/workflows/build_mkl_windows.yml @@ -2,13 +2,11 @@ name: Build MKL Windows on: push: - branches: - - master + branches: [ master, develop**, ci ] tags: - '*' pull_request: - branches: - - master + branches: [ master, develop** ] env: # update urls for oneapi packages according to diff --git a/.github/workflows/build_wasm.yml b/.github/workflows/build_wasm.yml index b26f598f..426e3d30 100644 --- a/.github/workflows/build_wasm.yml +++ b/.github/workflows/build_wasm.yml @@ -2,13 +2,11 @@ name: Build WASM on: push: - branches: - - master + branches: [ master, develop**, ci ] tags: - '*' pull_request: - branches: - - master + branches: [ master, develop** ] jobs: build_wheels: diff --git a/.gitignore b/.gitignore index c2a5a9a1..fcf7cea7 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,17 @@ src/osqp/_version.py # files that are modified in the build process src/osqp/bindings.cpp + + + +CMakeFiles/ +INSTALL.vcxproj +INSTALL.vcxproj.filters +ZERO_CHECK.vcxproj +ZERO_CHECK.vcxproj.filters +cmake_install.cmake +ext.sln +ext_builtin.vcxproj +ext_builtin.vcxproj.filters +src/bindings.cpp +CLAUDE.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f517c842..a2d72def 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks +exclude: '^(benchmarks|examples)/' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.2.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 33349318..75abdbf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,8 @@ project(ext) set(PYTHON "ON") set(OSQP_BUILD_UNITTESTS "OFF") -set(OSQP_USE_LONG "OFF") +# set(OSQP_USE_LONG "OFF") +set(OSQP_USE_LONG "ON") set(OSQP_CUSTOM_PRINTING "${CMAKE_CURRENT_SOURCE_DIR}/cmake/printing.h") set(OSQP_CUSTOM_MEMORY "${CMAKE_CURRENT_SOURCE_DIR}/cmake/memory.h") set(OSQP_CODEGEN_INSTALL_DIR "codegen/codegen_src" CACHE PATH "" FORCE) @@ -17,7 +18,25 @@ include(FetchContent) # 03/05/24 - Use modern python discovery set(PYBIND11_FINDPYTHON "ON") -find_package(pybind11 CONFIG REQUIRED) +# Try to find pybind11, if not found, fetch it +find_package(pybind11 CONFIG QUIET) +if(NOT pybind11_FOUND) + message(STATUS "pybind11 not found, fetching from GitHub...") + FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + GIT_TAG v2.11.1 + ) + FetchContent_MakeAvailable(pybind11) +else() + message(STATUS "Found pybind11: ${pybind11_DIR}") +endif() + +# find_package(pybind11 CONFIG REQUIRED) + +# Find Python before configuring OSQP +# Use Development.Module for better CMake 4.0+ compatibility +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) # 03/05/24 - Workaround because OSQP CMakeLists.txt is using old variable names set(PYTHON_FOUND "ON") @@ -27,12 +46,32 @@ message(STATUS "Fetching/configuring OSQP") list(APPEND CMAKE_MESSAGE_INDENT " ") FetchContent_Declare( osqp - GIT_REPOSITORY https://github.com/osqp/osqp.git - GIT_TAG v1.0.0 +# GIT_REPOSITORY https://github.com/osqp/osqp.git + GIT_REPOSITORY https://github.com/lb3825/osqp.git + # SOURCE_DIR "C:/Users/baice/Documents/GitHub/osqp" + # SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../osqp" +# GIT_TAG v1.0.0 + GIT_TAG b/develop-halpern-ci-fixes +# GIT_TAG plot ) list(POP_BACK CMAKE_MESSAGE_INDENT) FetchContent_MakeAvailable(osqp) +# Define the module name if not already set +if(NOT DEFINED OSQP_EXT_MODULE_NAME) + set(OSQP_EXT_MODULE_NAME "osqp") +endif() + +if(${OSQP_ALGEBRA_BACKEND} STREQUAL "builtin") + set(OSQP_EXT_MODULE_NAME "ext_builtin") +elseif(${OSQP_ALGEBRA_BACKEND} STREQUAL "mkl") + set(OSQP_EXT_MODULE_NAME "osqp_mkl") +elseif(${OSQP_ALGEBRA_BACKEND} STREQUAL "cuda") + set(OSQP_EXT_MODULE_NAME "osqp_cuda") +else() + set(OSQP_EXT_MODULE_NAME "osqp") +endif() + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/bindings.cpp.in ${CMAKE_CURRENT_SOURCE_DIR}/src/bindings.cpp) pybind11_add_module(${OSQP_EXT_MODULE_NAME} src/bindings.cpp) diff --git a/run_with_probname.bat b/run_with_probname.bat new file mode 100644 index 00000000..8244de66 --- /dev/null +++ b/run_with_probname.bat @@ -0,0 +1,14 @@ +@echo off +setlocal enabledelayedexpansion + +set PROBNAME=%1 +set STARTDIR=%CD% + +cd examples +@REM py maros_meszaros_benchmarks.py !PROBNAME! +cd ..\.. + +cd osqp +py .\plot\plot.py !PROBNAME! 3 + +cd /d !STARTDIR! diff --git a/src/bindings.cpp.in b/src/bindings.cpp.in index 4ceac544..13b0e89f 100644 --- a/src/bindings.cpp.in +++ b/src/bindings.cpp.in @@ -377,6 +377,26 @@ PYBIND11_MODULE(@OSQP_EXT_MODULE_NAME@, m) { .value("OSQP_DIAGONAL_PRECONDITIONER", OSQP_DIAGONAL_PRECONDITIONER) .export_values(); + py::enum_(m, "osqp_restart_type", py::module_local()) + .value("OSQP_RESTART_NONE", OSQP_RESTART_NONE) + //.value("OSQP_RESTART_AVERAGED", OSQP_RESTART_AVERAGED) + //.value("OSQP_RESTART_HALPERN", OSQP_RESTART_HALPERN) + .value("OSQP_RESTART_REFLECTED_HALPERN", OSQP_RESTART_REFLECTED_HALPERN) + .export_values(); + + //py::enum_(m, "osqp_halpern_adaptive_type", py::module_local()) + //.value("OSQP_ADAPTIVE_HALPERN_NONE", OSQP_ADAPTIVE_HALPERN_NONE) + //.value("OSQP_ADAPTIVE_HALPERN", OSQP_ADAPTIVE_HALPERN) + //.value("OSQP_ADAPTIVE_HALPERN_BEFORE_INI_REST_LEN", OSQP_ADAPTIVE_HALPERN_BEFORE_INI_REST_LEN) + //.export_values(); + + py::enum_(m, "osqp_halpern_anchor_point", py::module_local()) + .value("OSQP_HALPERN_ANCHOR_INITIAL_POINT", OSQP_HALPERN_ANCHOR_INITIAL_POINT) + .value("OSQP_HALPERN_ANCHOR_FIRST_ITER", OSQP_HALPERN_ANCHOR_FIRST_ITER) + .value("OSQP_HALPERN_ANCHOR_TAU_NOT", OSQP_HALPERN_ANCHOR_TAU_NOT) + .value("OSQP_HALPERN_ANCHOR_INIT_REST", OSQP_HALPERN_ANCHOR_INIT_REST) + .export_values(); + // CSC py::class_(m, "CSC", py::module_local()) .def(py::init()) @@ -415,6 +435,27 @@ PYBIND11_MODULE(@OSQP_EXT_MODULE_NAME@, m) { .def_readwrite("rho_is_vec", &OSQPSettings::rho_is_vec) .def_readwrite("sigma", &OSQPSettings::sigma) .def_readwrite("alpha", &OSQPSettings::alpha) + .def_readwrite("beta", &OSQPSettings::beta) + .def_readwrite("lambd", &OSQPSettings::lambd) + .def_readwrite("restart_necessary", &OSQPSettings::restart_necessary) + .def_readwrite("restart_artificial", &OSQPSettings::restart_artificial) + .def_readwrite("ini_rest_len", &OSQPSettings::ini_rest_len) + .def_readwrite("restart_type", &OSQPSettings::restart_type) + //.def_readwrite("halpern_scheme", &OSQPSettings::halpern_scheme) + .def_readwrite("halpern_anchor", &OSQPSettings::halpern_anchor) + .def_readwrite("adaptive_rho_tolerance_greater", &OSQPSettings::adaptive_rho_tolerance_greater) + .def_readwrite("adaptive_rho_tolerance_less", &OSQPSettings::adaptive_rho_tolerance_less) + .def_readwrite("adaptive_rest", &OSQPSettings::adaptive_rest) + //.def_readwrite("alpha_adjustment_reflected_halpern", &OSQPSettings::alpha_adjustment_reflected_halpern) + //.def_readwrite("rho_custom_condition", &OSQPSettings::rho_custom_condition) + //.def_readwrite("custom_average_rest", &OSQPSettings::custom_average_rest) + //.def_readwrite("adapt_rho_on_restart", &OSQPSettings::adapt_rho_on_restart) + //.def_readwrite("vector_rho_in_averaged_KKT", &OSQPSettings::vector_rho_in_averaged_KKT) + //.def_readwrite("rho_custom_tolerance", &OSQPSettings::rho_custom_tolerance) + //.def_readwrite("xi", &OSQPSettings::xi) + //.def_readwrite("plot", &OSQPSettings::plot) + .def_readwrite("integral", &OSQPSettings::integral) + .def_readwrite("halpern_step_first_inner_iter", &OSQPSettings::halpern_step_first_inner_iter) // Settings - CG .def_readwrite("cg_max_iter", &OSQPSettings::cg_max_iter) @@ -425,8 +466,17 @@ PYBIND11_MODULE(@OSQP_EXT_MODULE_NAME@, m) { // Settings - Adaptive rho .def_readwrite("adaptive_rho", &OSQPSettings::adaptive_rho) .def_readwrite("adaptive_rho_interval", &OSQPSettings::adaptive_rho_interval) + .def_readwrite("restart_check_interval", &OSQPSettings::restart_check_interval) .def_readwrite("adaptive_rho_fraction", &OSQPSettings::adaptive_rho_fraction) .def_readwrite("adaptive_rho_tolerance", &OSQPSettings::adaptive_rho_tolerance) + .def_readwrite("pid_controller", &OSQPSettings::pid_controller) + //.def_readwrite("pid_controller_sqrt", &OSQPSettings::pid_controller_sqrt) + //.def_readwrite("pid_controller_log", &OSQPSettings::pid_controller_log) + .def_readwrite("KP", &OSQPSettings::KP) + .def_readwrite("KI", &OSQPSettings::KI) + .def_readwrite("KD", &OSQPSettings::KD) + .def_readwrite("negate_K", &OSQPSettings::negate_K) + // Settings - Termination parameters .def_readwrite("max_iter", &OSQPSettings::max_iter) @@ -474,6 +524,8 @@ PYBIND11_MODULE(@OSQP_EXT_MODULE_NAME@, m) { .def_readwrite("obj_val", &OSQPInfo::obj_val) .def_readonly("prim_res", &OSQPInfo::prim_res) .def_readonly("dual_res", &OSQPInfo::dual_res) + .def_readonly("duality_gap", &OSQPInfo::duality_gap) + .def_readonly("restart", &OSQPInfo::restart) .def_readonly("iter", &OSQPInfo::iter) .def_readonly("rho_updates", &OSQPInfo::rho_updates) .def_readonly("rho_estimate", &OSQPInfo::rho_estimate) @@ -481,7 +533,11 @@ PYBIND11_MODULE(@OSQP_EXT_MODULE_NAME@, m) { .def_readonly("solve_time", &OSQPInfo::solve_time) .def_readonly("update_time", &OSQPInfo::update_time) .def_readonly("polish_time", &OSQPInfo::polish_time) - .def_readonly("run_time", &OSQPInfo::run_time); + .def_readonly("run_time", &OSQPInfo::run_time) + .def_readonly("total_integral", &OSQPInfo::total_integral) + .def_readonly("prim_normalized", &OSQPInfo::prim_normalized) + .def_readonly("dual_normalized", &OSQPInfo::dual_normalized) + .def_readonly("duality_gap_normalized", &OSQPInfo::duality_gap_normalized); // Solver py::class_(m, "OSQPSolver", py::module_local()) diff --git a/src/osqp/interface.py b/src/osqp/interface.py index 67b659af..22194264 100644 --- a/src/osqp/interface.py +++ b/src/osqp/interface.py @@ -315,6 +315,7 @@ def update_settings(self, **kwargs): ) settings_changed = True + # print("dictionary: ", self.ext.OSQPSettings.__dict__.keys()) for k in self.ext.OSQPSettings.__dict__: if not k.startswith('__'): if k in kwargs: diff --git a/src/osqp/tests/warm_start_test.py b/src/osqp/tests/warm_start_test.py index ea4e8c0b..f37f6101 100644 --- a/src/osqp/tests/warm_start_test.py +++ b/src/osqp/tests/warm_start_test.py @@ -46,10 +46,11 @@ def test_warm_start(self): y_opt = res.y tot_iter = res.info.iter - # Warm start with zeros and check if number of iterations is the same + # Warm start with zeros - the iteration count may be different due to rho updates + # persisting from the first solve, which can help convergence self.model.warm_start(x=np.zeros(self.n), y=np.zeros(self.m)) res = self.model.solve() - assert res.info.iter == tot_iter + assert res.info.iter <= tot_iter # Should take same or fewer iterations # Warm start with optimal values and check that number of iter < 10 self.model.warm_start(x=x_opt, y=y_opt)