robotology / osqp-eigen

Simple Eigen-C++ wrapper for OSQP library
https://robotology.github.io/osqp-eigen/
BSD 3-Clause "New" or "Revised" License
396 stars 118 forks source link

Matlab S-function freeze #29

Closed stribor14 closed 2 years ago

stribor14 commented 5 years ago

Hello.

I've hit a roadblock of sorts with this library. Usecase is following, I've implemented an MPC controller as a library and tested it with an ideal plant written in C++ (no noise, ideal delay, etc), and everything works fine. But for a more real use-case, we have a Simulink model so I've written s-function level 2 wrapper for the MPC controller. Every time I run the simulation, it works fine, but for some reason, Matlab hangs at the end of the simulation (or earlier if I hit stop). No error is given and the only way to recover is to kill the Matlab process and run everything from begging.

After a few hours of trying to find the source of the problem by commenting out everything and then block by block uncommenting and testing, I've narrowed it down to two lines:

solver.updateHessianMatrix(P_);
solver.updateLinearConstraintsMatrix(A_);

If I comment out both of them, Matlab behaves normally (except, MPC is useless without them)

P.S. updating bounds does not result in freeze:

solver.updateBounds(lo_bound_, up_bound_);

P.P.S. we have already implemented and tested similar things with osqp and Matlab, but now we wanted to use Eigen wrapper since a lot of our data is in Eigen classes

traversaro commented 5 years ago

Hi @stribor14 ,

Just a curiosity: do you have a Short, Self Contained and Compilable Example for your problem that you can share?

stribor14 commented 5 years ago

Sadly, I cannot share an example, but If I don't figure this out, I'll try to create a new one (which might also help me if I figure out how to produce this behavior)

Tomorrow I'll try running Matlab in gdb and see if there is an infinite loop occurring

stribor14 commented 5 years ago

Hi @traversaro ,

I think I found the problem, but I would like your thoughts on it. All matrices I give to update functions are class members variables (not pointers):

solver.updateHessianMatrix(P_);
solver.updateLinearConstraintsMatrix(A_);
solver.updateBounds(lo_bound_, up_bound_);

After debugging, I noticed that Matlab hangs when trying to clean up memory, so I removed P-value delete (intentionally introduced memory leak). Now everything works as intended.

My reasoning is that when I call updateSomeMatrix(Mat), osqp-eigen or osqp takes ownership of it and frees that memory before class destructor tries to free the same memory. (#16 )

P.S. This all sounds fishy because Valgrind gives 0 errors and memory leaks with c++ plant. I only have problem with Matlab. So far experience with osqp-eigen is great and straightforward (Kudos!)

stribor14 commented 5 years ago

Problem solved :)

In MPC class constructor, I had solver initialization (it should be noted that all of the matrices are initialized but still empty at this point):

    solver.settings()->setVerbosity(false);
    solver.settings()->setWarmStart(true);
    solver.data()->setNumberOfVariables((N__ + 1) * n_x__ + N__ * n_u__);
    solver.data()->setNumberOfConstraints(2 * (N__ + 1) * n_x__ + N__ * n_u__);
    solver.data()->setHessianMatrix(P_);
    solver.data()->setGradient(q_);
    solver.data()->setLinearConstraintsMatrix(A_);
    solver.data()->setUpperBound(up_bound_);
    solver.data()->setLowerBound(lo_bound_);
    solver.initSolver();

Then, in function call to get a new solution, I recalculate new matrices and update solver with them:

solver.updateHessianMatrix(P_);
solver.updateLinearConstraintsMatrix(A_);
solver.updateBounds(lo_bound_, up_bound_);

But, when this function is called first time, sparsity pattern changes and osqp-eigen has to create new solver. On all other subsequent calls, it only updates values in osqp (since later sparsity pattern doesn't change). This works without any problem with normal C++ application.

When writing Matlab S-function, this doesn't seem to work, so I tried to avoid creating new solver because of sparsity pattern change. I removed initialization from class constructor to function call after all of the matrices are calculated:

  static bool first_pass = true;
  if (first_pass)
  {
    first_pass = false;
    solver.settings()->setVerbosity(false);
    solver.settings()->setWarmStart(true);
    solver.data()->setNumberOfVariables((N__ + 1) * n_x__ + N__ * n_u__);
    solver.data()->setNumberOfConstraints(2 * (N__ + 1) * n_x__ + N__ * n_u__);
    solver.data()->setHessianMatrix(P_);
    solver.data()->setGradient(q_);
    solver.data()->setLinearConstraintsMatrix(A_);
    solver.data()->setUpperBound(up_bound_);
    solver.data()->setLowerBound(lo_bound_);
    solver.initSolver();
  }
  else {
    solver.updateHessianMatrix(P_);
    solver.updateLinearConstraintsMatrix(A_);
    solver.updateBounds(lo_bound_, up_bound_);
  }

I doubt that Matlab should crash because of that, maybe this is only a workaround for a bug, but it works.

traversaro commented 2 years ago

Thanks @stribor14 ! Given that you found your solution, I think we can close the issue.