diff --git a/.github/workflows/build-cpp.yml b/.github/workflows/build-cpp.yml index dfd9ac50..a16561c4 100644 --- a/.github/workflows/build-cpp.yml +++ b/.github/workflows/build-cpp.yml @@ -22,6 +22,9 @@ jobs: - name: macOS universal os: macOS-14 cmake-args: -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" + - name: Linux x86_64 (no diagnostics) + os: ubuntu-24.04 + cmake-args: -DDISABLE_DIAGNOSTICS=ON name: ${{ matrix.name }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml index 52d20584..51c6bc30 100644 --- a/.github/workflows/build-wasm.yml +++ b/.github/workflows/build-wasm.yml @@ -8,6 +8,8 @@ concurrency: jobs: build: + timeout-minutes: 10 + name: Wasm runs-on: ubuntu-24.04 steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index 46732533..03478720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,7 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS FALSE) option(BUILD_BENCHMARKING "Build CasADi and Sleipnir benchmarks" OFF) option(BUILD_EXAMPLES "Build examples" OFF) option(BUILD_PYTHON "Build Python module" OFF) +option(DISABLE_DIAGNOSTICS "Disable diagnostics support at compile-time" OFF) include(CompilerFlags) @@ -98,6 +99,10 @@ find_package(Threads REQUIRED) target_link_libraries(Sleipnir PUBLIC Threads::Threads) +if(DISABLE_DIAGNOSTICS) + target_compile_definitions(Sleipnir PUBLIC SLEIPNIR_DISABLE_DIAGNOSTICS) +endif() + # Eigen dependency if(NOT USE_SYSTEM_EIGEN) set(EIGEN_BUILD_CMAKE_PACKAGE TRUE) diff --git a/include/sleipnir/optimization/OptimizationProblem.hpp b/include/sleipnir/optimization/OptimizationProblem.hpp index e0520343..687274fd 100644 --- a/include/sleipnir/optimization/OptimizationProblem.hpp +++ b/include/sleipnir/optimization/OptimizationProblem.hpp @@ -21,7 +21,10 @@ #include "sleipnir/optimization/solver/InteriorPoint.hpp" #include "sleipnir/optimization/solver/Newton.hpp" #include "sleipnir/optimization/solver/SQP.hpp" + +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS #include "sleipnir/util/Print.hpp" +#endif #include "sleipnir/util/SymbolExports.hpp" #include "sleipnir/util/small_vector.hpp" @@ -274,6 +277,7 @@ class SLEIPNIR_DLLEXPORT OptimizationProblem { m_f = Variable(); } +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { constexpr std::array kExprTypeToName{"empty", "constant", "linear", "quadratic", "nonlinear"}; @@ -298,6 +302,7 @@ class SLEIPNIR_DLLEXPORT OptimizationProblem { sleipnir::println("Number of inequality constraints: {}\n", m_inequalityConstraints.size()); } +#endif // If the problem is empty or constant, there's nothing to do if (status.costFunctionType <= ExpressionType::kConstant && @@ -319,9 +324,11 @@ class SLEIPNIR_DLLEXPORT OptimizationProblem { false, x, s, &status); } +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { sleipnir::println("Exit condition: {}", ToMessage(status.exitCondition)); } +#endif // Assign the solution to the original Variable instances VariableMatrix{m_decisionVariables}.SetValue(x); diff --git a/src/optimization/solver/InteriorPoint.cpp b/src/optimization/solver/InteriorPoint.cpp index 4617c307..d399fb90 100644 --- a/src/optimization/solver/InteriorPoint.cpp +++ b/src/optimization/solver/InteriorPoint.cpp @@ -21,7 +21,10 @@ #include "sleipnir/autodiff/Hessian.hpp" #include "sleipnir/autodiff/Jacobian.hpp" #include "sleipnir/optimization/SolverExitCondition.hpp" + +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS #include "sleipnir/util/Print.hpp" +#endif #include "sleipnir/util/Spy.hpp" #include "sleipnir/util/small_vector.hpp" #include "util/PrintIterationDiagnostics.hpp" @@ -102,6 +105,7 @@ void InteriorPoint(std::span decisionVariables, // Check for overconstrained problem if (equalityConstraints.size() > decisionVariables.size()) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { sleipnir::println("The problem has too few degrees of freedom."); sleipnir::println( @@ -112,6 +116,7 @@ void InteriorPoint(std::span decisionVariables, } } } +#endif status->exitCondition = SolverExitCondition::kTooFewDOFs; return; @@ -139,9 +144,11 @@ void InteriorPoint(std::span decisionVariables, A_i.rows(), A_i.cols()); } +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics && !feasibilityRestoration) { sleipnir::println("Error tolerance: {}\n", config.tolerance); } +#endif std::chrono::steady_clock::time_point iterationsStartTime; @@ -151,6 +158,7 @@ void InteriorPoint(std::span decisionVariables, scope_exit exit{[&] { status->cost = f.Value(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics && !feasibilityRestoration) { auto solveEndTime = std::chrono::steady_clock::now(); @@ -187,6 +195,7 @@ void InteriorPoint(std::span decisionVariables, jacobianCi.GetProfiler().SolveMeasurements()); sleipnir::println(""); } +#endif }}; // Barrier parameter minimum @@ -246,19 +255,24 @@ void InteriorPoint(std::span decisionVariables, // Error estimate double E_0 = std::numeric_limits::infinity(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { iterationsStartTime = std::chrono::steady_clock::now(); } +#endif while (E_0 > config.tolerance && acceptableIterCounter < config.maxAcceptableIterations) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS std::chrono::steady_clock::time_point innerIterStartTime; if (config.diagnostics) { innerIterStartTime = std::chrono::steady_clock::now(); } +#endif // Check for local equality constraint infeasibility if (IsEqualityLocallyInfeasible(A_e, c_e)) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { sleipnir::println( "The problem is locally infeasible due to violated equality " @@ -271,6 +285,7 @@ void InteriorPoint(std::span decisionVariables, } } } +#endif status->exitCondition = SolverExitCondition::kLocallyInfeasible; return; @@ -278,6 +293,7 @@ void InteriorPoint(std::span decisionVariables, // Check for local inequality constraint infeasibility if (IsInequalityLocallyInfeasible(A_i, c_i)) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { sleipnir::println( "The problem is infeasible due to violated inequality " @@ -290,6 +306,7 @@ void InteriorPoint(std::span decisionVariables, } } } +#endif status->exitCondition = SolverExitCondition::kLocallyInfeasible; return; @@ -464,10 +481,12 @@ void InteriorPoint(std::span decisionVariables, bool stepAcceptable = false; for (int soc_iteration = 0; soc_iteration < 5 && !stepAcceptable; ++soc_iteration) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS std::chrono::steady_clock::time_point socIterStartTime; if (config.diagnostics) { socIterStartTime = std::chrono::steady_clock::now(); } +#endif // Rebuild Newton-KKT rhs with updated constraint values. // @@ -517,6 +536,7 @@ void InteriorPoint(std::span decisionVariables, stepAcceptable = true; } +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { const auto socIterEndTime = std::chrono::steady_clock::now(); @@ -527,6 +547,7 @@ void InteriorPoint(std::span decisionVariables, trial_c_e.lpNorm<1>() + (trial_c_i - trial_s).lpNorm<1>(), solver.HessianRegularization(), 1.0); } +#endif } if (stepAcceptable) { @@ -801,6 +822,7 @@ void InteriorPoint(std::span decisionVariables, const auto innerIterEndTime = std::chrono::steady_clock::now(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { PrintIterationDiagnostics( iterations, @@ -810,6 +832,7 @@ void InteriorPoint(std::span decisionVariables, c_e.lpNorm<1>() + (c_i - s).lpNorm<1>(), solver.HessianRegularization(), α); } +#endif ++iterations; diff --git a/src/optimization/solver/Newton.cpp b/src/optimization/solver/Newton.cpp index f7ebf899..23638ef2 100644 --- a/src/optimization/solver/Newton.cpp +++ b/src/optimization/solver/Newton.cpp @@ -17,7 +17,10 @@ #include "sleipnir/autodiff/Gradient.hpp" #include "sleipnir/autodiff/Hessian.hpp" #include "sleipnir/optimization/SolverExitCondition.hpp" + +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS #include "sleipnir/util/Print.hpp" +#endif #include "sleipnir/util/Spy.hpp" #include "util/PrintIterationDiagnostics.hpp" #include "util/ScopeExit.hpp" @@ -66,9 +69,11 @@ void Newton(std::span decisionVariables, Variable& f, "Decision variables", H.rows(), H.cols()); } +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { sleipnir::println("Error tolerance: {}\n", config.tolerance); } +#endif std::chrono::steady_clock::time_point iterationsStartTime; @@ -78,6 +83,7 @@ void Newton(std::span decisionVariables, Variable& f, scope_exit exit{[&] { status->cost = f.Value(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { auto solveEndTime = std::chrono::steady_clock::now(); @@ -106,6 +112,7 @@ void Newton(std::span decisionVariables, Variable& f, hessianL.GetProfiler().SolveMeasurements()); sleipnir::println(""); } +#endif }}; Filter filter{f}; @@ -119,16 +126,20 @@ void Newton(std::span decisionVariables, Variable& f, // Error estimate double E_0 = std::numeric_limits::infinity(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { iterationsStartTime = std::chrono::steady_clock::now(); } +#endif while (E_0 > config.tolerance && acceptableIterCounter < config.maxAcceptableIterations) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS std::chrono::steady_clock::time_point innerIterStartTime; if (config.diagnostics) { innerIterStartTime = std::chrono::steady_clock::now(); } +#endif // Check for diverging iterates if (x.lpNorm() > 1e20 || !x.allFinite()) { @@ -247,12 +258,14 @@ void Newton(std::span decisionVariables, Variable& f, const auto innerIterEndTime = std::chrono::steady_clock::now(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { PrintIterationDiagnostics(iterations, IterationMode::kNormal, innerIterEndTime - innerIterStartTime, E_0, f.Value(), 0.0, solver.HessianRegularization(), α); } +#endif ++iterations; diff --git a/src/optimization/solver/SQP.cpp b/src/optimization/solver/SQP.cpp index ea986690..3e9c694e 100644 --- a/src/optimization/solver/SQP.cpp +++ b/src/optimization/solver/SQP.cpp @@ -20,7 +20,10 @@ #include "sleipnir/autodiff/Hessian.hpp" #include "sleipnir/autodiff/Jacobian.hpp" #include "sleipnir/optimization/SolverExitCondition.hpp" + +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS #include "sleipnir/util/Print.hpp" +#endif #include "sleipnir/util/Spy.hpp" #include "sleipnir/util/small_vector.hpp" #include "util/PrintIterationDiagnostics.hpp" @@ -77,6 +80,7 @@ void SQP(std::span decisionVariables, // Check for overconstrained problem if (equalityConstraints.size() > decisionVariables.size()) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { sleipnir::println("The problem has too few degrees of freedom."); sleipnir::println( @@ -87,6 +91,7 @@ void SQP(std::span decisionVariables, } } } +#endif status->exitCondition = SolverExitCondition::kTooFewDOFs; return; @@ -110,9 +115,11 @@ void SQP(std::span decisionVariables, A_e.rows(), A_e.cols()); } +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { sleipnir::println("Error tolerance: {}\n", config.tolerance); } +#endif std::chrono::steady_clock::time_point iterationsStartTime; @@ -122,6 +129,7 @@ void SQP(std::span decisionVariables, scope_exit exit{[&] { status->cost = f.Value(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { auto solveEndTime = std::chrono::steady_clock::now(); @@ -154,6 +162,7 @@ void SQP(std::span decisionVariables, jacobianCe.GetProfiler().SolveMeasurements()); sleipnir::println(""); } +#endif }}; Filter filter{f}; @@ -172,19 +181,24 @@ void SQP(std::span decisionVariables, // Error estimate double E_0 = std::numeric_limits::infinity(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { iterationsStartTime = std::chrono::steady_clock::now(); } +#endif while (E_0 > config.tolerance && acceptableIterCounter < config.maxAcceptableIterations) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS std::chrono::steady_clock::time_point innerIterStartTime; if (config.diagnostics) { innerIterStartTime = std::chrono::steady_clock::now(); } +#endif // Check for local equality constraint infeasibility if (IsEqualityLocallyInfeasible(A_e, c_e)) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { sleipnir::println( "The problem is locally infeasible due to violated equality " @@ -197,6 +211,7 @@ void SQP(std::span decisionVariables, } } } +#endif status->exitCondition = SolverExitCondition::kLocallyInfeasible; return; @@ -318,10 +333,12 @@ void SQP(std::span decisionVariables, bool stepAcceptable = false; for (int soc_iteration = 0; soc_iteration < 5 && !stepAcceptable; ++soc_iteration) { +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS std::chrono::steady_clock::time_point socIterStartTime; if (config.diagnostics) { socIterStartTime = std::chrono::steady_clock::now(); } +#endif // Rebuild Newton-KKT rhs with updated constraint values. // @@ -354,6 +371,7 @@ void SQP(std::span decisionVariables, stepAcceptable = true; } +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { const auto socIterEndTime = std::chrono::steady_clock::now(); @@ -363,6 +381,7 @@ void SQP(std::span decisionVariables, socIterEndTime - socIterStartTime, E, f.Value(), trial_c_e.lpNorm<1>(), solver.HessianRegularization(), 1.0); } +#endif } if (stepAcceptable) { @@ -533,12 +552,14 @@ void SQP(std::span decisionVariables, const auto innerIterEndTime = std::chrono::steady_clock::now(); +#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS if (config.diagnostics) { PrintIterationDiagnostics(iterations, IterationMode::kNormal, innerIterEndTime - innerIterStartTime, E_0, f.Value(), c_e.lpNorm<1>(), solver.HessianRegularization(), α); } +#endif ++iterations;