Skip to content

OsqpSolver::initSolver() throws "solver already initialized" with osqp-eigen ≥ v0.9.0 (OSQP 1.0) #129

Description

@jmiddelberg

Summary

OsqpSolver::solve() calls solver.initSolver() on an already-initialized OsqpEigen::Solver whenever a cold start is requested (warm-starting disabled). With osqp-eigen ≥ v0.9.0 (the first release compatible with OSQP v1.0.0), initSolver() now aborts in this situation with:

[OsqpEigen::Solver::initSolver] The solver has been already initialized. Please use clearSolver() method to deallocate memory.

Older osqp-eigen (e.g. v0.8.1, which targets OSQP 0.6.x) tolerated the repeated initSolver() call, so the bug was latent until OSQP 1.0.

Environment

  • wbc: master, built with -DSOLVER_OSQP=ON
  • osqp-eigen: v0.9.0+ (reproduced on master / v0.11.0)
  • osqp: 1.0.0

Root cause

In src/solvers/osqp/OsqpSolver.cpp, solve():

if(!allow_warm_start)      // when warm-start is disabled ...
    configured = false;    // ... force the (re-)configure path every cycle

if(!configured){
    resize(qp.nq, nc);
    configured = true;
    solver.initSolver();   // <-- called again on a solver that is still initialized
}

When solve() is called with allow_warm_start = false, configured is reset to false on every cycle, so the init branch runs again on the second and subsequent cycles — but solver was already initialized on the first cycle. Newer osqp-eigen rejects a second initSolver() without a preceding clearSolver().

Proposed fix

Tear the solver down before re-initializing on the cold-start path:

     if(!configured){
+        if(solver.isInitialized())
+            solver.clearSolver();
         resize(qp.nq, nc);
         configured = true;
         solver.initSolver();
     }

This is a no-op on the very first solve (isInitialized() is false) and never runs on the warm-start path (the if(!configured) branch is skipped), so it is safe for both the OSQP 0.6.x and OSQP 1.0 builds of osqp-eigen. It also makes the "warm-start disabled" case an actual clean cold start rather than relying on the old wrapper silently ignoring the duplicate initSolver().

Note on a related, separate issue

A few lines below, the constraint-count change is handled by re-resize()-ing but not re-initializing the solver:

if(solver.data()->getData()->m != nc)
    resize(qp.nq, nc);

If the number of constraints ever changes between calls, the subsequent update* calls operate on a solver whose workspace still has the old dimensions. This is orthogonal to the crash above and may deserve its own fix (e.g. clearSolver() + initSolver() on dimension change).

@dmronga

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions