Program Listing for File solver.hpp

Return to documentation for file (/tmp/ws/src/proxsuite/include/proxsuite/proxqp/dense/solver.hpp)

//
// Copyright (c) 2022 INRIA
//
#ifndef PROXSUITE_PROXQP_DENSE_SOLVER_HPP
#define PROXSUITE_PROXQP_DENSE_SOLVER_HPP

#include "proxsuite/fwd.hpp"
#include "proxsuite/proxqp/dense/views.hpp"
#include "proxsuite/proxqp/dense/linesearch.hpp"
#include "proxsuite/proxqp/dense/helpers.hpp"
#include "proxsuite/proxqp/dense/utils.hpp"
#include <cmath>
#include <Eigen/Sparse>
#include <iostream>
#include <fstream>
#include <proxsuite/linalg/veg/util/dynstack_alloc.hpp>
#include <proxsuite/linalg/dense/ldlt.hpp>
#include <chrono>
#include <iomanip>

namespace proxsuite {
namespace proxqp {
namespace dense {

template<typename T>
void
refactorize(const Model<T>& qpmodel,
            Results<T>& qpresults,
            Workspace<T>& qpwork,
            T rho_new)
{

  if (!qpwork.constraints_changed && rho_new == qpresults.info.rho) {
    return;
  }
  qpwork.dw_aug.setZero();
  qpwork.kkt.diagonal().head(qpmodel.dim).array() +=
    rho_new - qpresults.info.rho;
  qpwork.kkt.diagonal().segment(qpmodel.dim, qpmodel.n_eq).array() =
    -qpresults.info.mu_eq;

  proxsuite::linalg::veg::dynstack::DynStackMut stack{
    proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut()
  };
  qpwork.ldl.factorize(qpwork.kkt.transpose(), stack);

  isize n = qpmodel.dim;
  isize n_eq = qpmodel.n_eq;
  isize n_in = qpmodel.n_in;
  isize n_c = qpwork.n_c;

  LDLT_TEMP_MAT(T, new_cols, n + n_eq + n_c, n_c, stack);
  T mu_in_neg(-qpresults.info.mu_in);
  for (isize i = 0; i < n_in; ++i) {
    isize j = qpwork.current_bijection_map[i];
    if (j < n_c) {
      auto col = new_cols.col(j);
      col.head(n) = qpwork.C_scaled.row(i);
      col.segment(n, n_eq + n_c).setZero();
      col(n + n_eq + j) = mu_in_neg;
    }
  }
  qpwork.ldl.insert_block_at(n + n_eq, new_cols, stack);

  qpwork.constraints_changed = false;

  qpwork.dw_aug.setZero();
}

template<typename T>
void
mu_update(const Model<T>& qpmodel,
          Results<T>& qpresults,
          Workspace<T>& qpwork,
          T mu_eq_new,
          T mu_in_new)
{
  proxsuite::linalg::veg::dynstack::DynStackMut stack{
    proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut()
  };

  isize n = qpmodel.dim;
  isize n_eq = qpmodel.n_eq;
  isize n_c = qpwork.n_c;

  if ((n_eq + n_c) == 0) {
    return;
  }

  LDLT_TEMP_VEC_UNINIT(T, rank_update_alpha, n_eq + n_c, stack);

  rank_update_alpha.head(n_eq).setConstant(qpresults.info.mu_eq - mu_eq_new);
  rank_update_alpha.tail(n_c).setConstant(qpresults.info.mu_in - mu_in_new);

  {
    auto _indices = stack.make_new_for_overwrite(
      proxsuite::linalg::veg::Tag<isize>{}, n_eq + n_c);
    isize* indices = _indices.ptr_mut();
    for (isize k = 0; k < n_eq; ++k) {
      indices[k] = n + k;
    }
    for (isize k = 0; k < n_c; ++k) {
      indices[n_eq + k] = n + n_eq + k;
    }
    qpwork.ldl.diagonal_update_clobber_indices(
      indices, n_eq + n_c, rank_update_alpha, stack);
  }

  qpwork.constraints_changed = true;
}
template<typename T>
void
iterative_residual(const Model<T>& qpmodel,
                   Results<T>& qpresults,
                   const Settings<T>& qpsettings,
                   Workspace<T>& qpwork,
                   isize inner_pb_dim)
{
  auto& Hdx = qpwork.Hdx;
  auto& Adx = qpwork.Adx;
  auto& ATdy = qpwork.CTz;
  qpwork.err.head(inner_pb_dim) = qpwork.rhs.head(inner_pb_dim);
  switch (qpsettings.problem_type) {
    case ProblemType::LP:
      break;
    case ProblemType::QP:
      Hdx.noalias() = qpwork.H_scaled.template selfadjointView<Eigen::Lower>() *
                      qpwork.dw_aug.head(qpmodel.dim);
      qpwork.err.head(qpmodel.dim).noalias() -= Hdx;
      break;
  }
  qpwork.err.head(qpmodel.dim) -=
    qpresults.info.rho * qpwork.dw_aug.head(qpmodel.dim);

  // PERF: fuse {A, C}_scaled multiplication operations
  ATdy.noalias() = qpwork.A_scaled.transpose() *
                   qpwork.dw_aug.segment(qpmodel.dim, qpmodel.n_eq);
  qpwork.err.head(qpmodel.dim).noalias() -= ATdy;
  for (isize i = 0; i < qpmodel.n_in; i++) {
    isize j = qpwork.current_bijection_map(i);
    if (j < qpwork.n_c) {
      qpwork.err.head(qpmodel.dim).noalias() -=
        qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * qpwork.C_scaled.row(i);
      qpwork.err(qpmodel.dim + qpmodel.n_eq + j) -=
        (qpwork.C_scaled.row(i).dot(qpwork.dw_aug.head(qpmodel.dim)) -
         qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * qpresults.info.mu_in);
    }
  }
  Adx.noalias() = qpwork.A_scaled * qpwork.dw_aug.head(qpmodel.dim);
  qpwork.err.segment(qpmodel.dim, qpmodel.n_eq).noalias() -= Adx;
  qpwork.err.segment(qpmodel.dim, qpmodel.n_eq) +=
    qpwork.dw_aug.segment(qpmodel.dim, qpmodel.n_eq) * qpresults.info.mu_eq;
}
template<typename T>
void
iterative_solve_with_permut_fact( //
  const Settings<T>& qpsettings,
  const Model<T>& qpmodel,
  Results<T>& qpresults,
  Workspace<T>& qpwork,
  T eps,
  isize inner_pb_dim)
{

  qpwork.err.setZero();
  i32 it = 0;
  i32 it_stability = 0;

  qpwork.dw_aug.head(inner_pb_dim) = qpwork.rhs.head(inner_pb_dim);
  proxsuite::linalg::veg::dynstack::DynStackMut stack{
    proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut()
  };
  qpwork.ldl.solve_in_place(qpwork.dw_aug.head(inner_pb_dim), stack);

  iterative_residual<T>(qpmodel, qpresults, qpsettings, qpwork, inner_pb_dim);

  ++it;
  T preverr = infty_norm(qpwork.err.head(inner_pb_dim));

  while (infty_norm(qpwork.err.head(inner_pb_dim)) >= eps) {

    if (it >= qpsettings.nb_iterative_refinement) {
      break;
    }
    ++it;
    qpwork.ldl.solve_in_place(qpwork.err.head(inner_pb_dim), stack);
    qpwork.dw_aug.head(inner_pb_dim) += qpwork.err.head(inner_pb_dim);

    qpwork.err.head(inner_pb_dim).setZero();
    iterative_residual<T>(qpmodel, qpresults, qpsettings, qpwork, inner_pb_dim);

    if (infty_norm(qpwork.err.head(inner_pb_dim)) > preverr) {
      it_stability += 1;

    } else {
      it_stability = 0;
    }
    if (it_stability == 2) {
      break;
    }
    preverr = infty_norm(qpwork.err.head(inner_pb_dim));
  }

  if (infty_norm(qpwork.err.head(inner_pb_dim)) >=
      std::max(eps, qpsettings.eps_refact)) {
    refactorize(qpmodel, qpresults, qpwork, qpresults.info.rho);
    it = 0;
    it_stability = 0;

    qpwork.dw_aug.head(inner_pb_dim) = qpwork.rhs.head(inner_pb_dim);
    qpwork.ldl.solve_in_place(qpwork.dw_aug.head(inner_pb_dim), stack);

    iterative_residual<T>(qpmodel, qpresults, qpsettings, qpwork, inner_pb_dim);

    preverr = infty_norm(qpwork.err.head(inner_pb_dim));
    ++it;
    while (infty_norm(qpwork.err.head(inner_pb_dim)) >= eps) {

      if (it >= qpsettings.nb_iterative_refinement) {
        break;
      }
      ++it;
      qpwork.ldl.solve_in_place(qpwork.err.head(inner_pb_dim), stack);
      qpwork.dw_aug.head(inner_pb_dim) += qpwork.err.head(inner_pb_dim);

      qpwork.err.head(inner_pb_dim).setZero();
      iterative_residual<T>(
        qpmodel, qpresults, qpsettings, qpwork, inner_pb_dim);

      if (infty_norm(qpwork.err.head(inner_pb_dim)) > preverr) {
        it_stability += 1;

      } else {
        it_stability = 0;
      }
      if (it_stability == 2) {
        break;
      }
      preverr = infty_norm(qpwork.err.head(inner_pb_dim));
    }
  }
  qpwork.rhs.head(inner_pb_dim).setZero();
}
template<typename T>
void
bcl_update(const Settings<T>& qpsettings,
           Results<T>& qpresults,
           Workspace<T>& qpwork,
           T& primal_feasibility_lhs_new,
           T& bcl_eta_ext,
           T& bcl_eta_in,

           T bcl_eta_ext_init,
           T eps_in_min,

           T& new_bcl_mu_in,
           T& new_bcl_mu_eq,
           T& new_bcl_mu_in_inv,
           T& new_bcl_mu_eq_inv

)
{
  if (primal_feasibility_lhs_new <= bcl_eta_ext ||
      qpresults.info.iter > qpsettings.safe_guard) {
    /* TO PUT IN DEBUG MODE
    if (qpsettings.verbose) {
            std::cout << "good step" << std::endl;
    }
    */
    bcl_eta_ext *= pow(qpresults.info.mu_in, qpsettings.beta_bcl);
    bcl_eta_in = std::max(bcl_eta_in * qpresults.info.mu_in, eps_in_min);
  } else {
    /* TO PUT IN DEBUG MODE
    if (qpsettings.verbose) {
            std::cout << "bad step" << std::endl;
    }
    */
    qpresults.y = qpwork.y_prev;
    qpresults.z = qpwork.z_prev;

    new_bcl_mu_in = std::max(qpresults.info.mu_in * qpsettings.mu_update_factor,
                             qpsettings.mu_min_in);
    new_bcl_mu_eq = std::max(qpresults.info.mu_eq * qpsettings.mu_update_factor,
                             qpsettings.mu_min_eq);
    new_bcl_mu_in_inv =
      std::min(qpresults.info.mu_in_inv * qpsettings.mu_update_inv_factor,
               qpsettings.mu_max_in_inv);
    new_bcl_mu_eq_inv =
      std::min(qpresults.info.mu_eq_inv * qpsettings.mu_update_inv_factor,
               qpsettings.mu_max_eq_inv);
    bcl_eta_ext = bcl_eta_ext_init * pow(new_bcl_mu_in, qpsettings.alpha_bcl);
    bcl_eta_in = std::max(new_bcl_mu_in, eps_in_min);
  }
}
template<typename T>
void
Martinez_update(const Settings<T>& qpsettings,
                Results<T>& qpresults,
                T& primal_feasibility_lhs_new,
                T& primal_feasibility_lhs_old,
                T& bcl_eta_in,
                T eps_in_min,

                T& new_bcl_mu_in,
                T& new_bcl_mu_eq,
                T& new_bcl_mu_in_inv,
                T& new_bcl_mu_eq_inv

)
{
  bcl_eta_in = std::max(bcl_eta_in * 0.1, eps_in_min);
  if (primal_feasibility_lhs_new <= 0.95 * primal_feasibility_lhs_old) {
    /* TO PUT IN DEBUG MODE
    if (qpsettings.verbose) {
            std::cout << "good step" << std::endl;
    }
    */
  } else {
    /* TO PUT IN DEBUG MODE
    if (qpsettings.verbose) {
            std::cout << "bad step" << std::endl;
    }
    */
    new_bcl_mu_in = std::max(qpresults.info.mu_in * qpsettings.mu_update_factor,
                             qpsettings.mu_min_in);
    new_bcl_mu_eq = std::max(qpresults.info.mu_eq * qpsettings.mu_update_factor,
                             qpsettings.mu_min_eq);
    new_bcl_mu_in_inv =
      std::min(qpresults.info.mu_in_inv * qpsettings.mu_update_inv_factor,
               qpsettings.mu_max_in_inv);
    new_bcl_mu_eq_inv =
      std::min(qpresults.info.mu_eq_inv * qpsettings.mu_update_inv_factor,
               qpsettings.mu_max_eq_inv);
  }
}
template<typename T>
auto
compute_inner_loop_saddle_point(const Model<T>& qpmodel,
                                Results<T>& qpresults,
                                Workspace<T>& qpwork,
                                const Settings<T>& qpsettings) -> T
{

  qpwork.active_part_z =
    helpers::positive_part(qpwork.primal_residual_in_scaled_up) +
    helpers::negative_part(qpwork.primal_residual_in_scaled_low);
  switch (qpsettings.merit_function_type) {
    case MeritFunctionType::GPDAL:
      qpwork.active_part_z -=
        qpsettings.alpha_gpdal * qpresults.z *
        qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+
      break;
    case MeritFunctionType::PDAL:
      qpwork.active_part_z -=
        qpresults.z *
        qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+
                              // + [Cx-l+z_prev*mu_in]- - z*mu_in
      break;
  }

  T err = infty_norm(qpwork.active_part_z);
  qpwork.err.segment(qpmodel.dim, qpmodel.n_eq) =
    qpwork.primal_residual_eq_scaled; // contains now Ax-b-(y-y_prev)/mu

  T prim_eq_e = infty_norm(
    qpwork.err.segment(qpmodel.dim, qpmodel.n_eq)); // ||Ax-b-(y-y_prev)/mu||
  err = std::max(err, prim_eq_e);
  T dual_e =
    infty_norm(qpwork.dual_residual_scaled); // contains ||Hx + rho(x-xprev) +
                                             // g + Aty + Ctz||
  err = std::max(err, dual_e);

  return err;
}
template<typename T>
void
primal_dual_semi_smooth_newton_step(const Settings<T>& qpsettings,
                                    const Model<T>& qpmodel,
                                    Results<T>& qpresults,
                                    Workspace<T>& qpwork,
                                    T eps)
{

  /* MUST BE
   *  dual_residual_scaled = Hx + rho * (x-x_prev) + A.T y + C.T z
   *  primal_residual_eq_scaled = Ax-b+mu_eq (y_prev-y)
   *  primal_residual_in_scaled_up = Cx-u+mu_in(z_prev)
   *  primal_residual_in_scaled_low = Cx-l+mu_in(z_prev)
   */
  qpwork.active_set_up.array() =
    (qpwork.primal_residual_in_scaled_up.array() >= 0);
  // primal_residual_in_scaled_low = Cx-l+mu_in(z_prev) + mu_in(alpha_gdpal - 1)
  // * z
  qpwork.active_set_low.array() =
    (qpwork.primal_residual_in_scaled_low.array() <= 0);
  qpwork.active_inequalities = qpwork.active_set_up || qpwork.active_set_low;

  isize numactive_inequalities = qpwork.active_inequalities.count();
  isize inner_pb_dim = qpmodel.dim + qpmodel.n_eq + numactive_inequalities;
  qpwork.rhs.setZero();
  qpwork.dw_aug.setZero();

  linesearch::active_set_change(qpmodel, qpresults, qpwork);

  qpwork.rhs.head(qpmodel.dim) = -qpwork.dual_residual_scaled;

  qpwork.rhs.segment(qpmodel.dim, qpmodel.n_eq) =
    -qpwork.primal_residual_eq_scaled;
  switch (qpsettings.merit_function_type) {
    case MeritFunctionType::GPDAL:
      for (isize i = 0; i < qpmodel.n_in; i++) {
        isize j = qpwork.current_bijection_map(i);
        if (j < qpwork.n_c) {
          if (qpwork.active_set_up(i)) {
            qpwork.rhs(j + qpmodel.dim + qpmodel.n_eq) =
              -qpwork.primal_residual_in_scaled_up(i) +
              qpresults.z(i) * qpresults.info.mu_in * qpsettings.alpha_gpdal;
          } else if (qpwork.active_set_low(i)) {
            qpwork.rhs(j + qpmodel.dim + qpmodel.n_eq) =
              -qpwork.primal_residual_in_scaled_low(i) +
              qpresults.z(i) * qpresults.info.mu_in * qpsettings.alpha_gpdal;
          }
        } else {
          qpwork.rhs.head(qpmodel.dim) +=
            qpresults.z(i) *
            qpwork.C_scaled.row(i); // unactive unrelevant columns
        }
      }
      break;
    case MeritFunctionType::PDAL:
      for (isize i = 0; i < qpmodel.n_in; i++) {
        isize j = qpwork.current_bijection_map(i);
        if (j < qpwork.n_c) {
          if (qpwork.active_set_up(i)) {
            qpwork.rhs(j + qpmodel.dim + qpmodel.n_eq) =
              -qpwork.primal_residual_in_scaled_up(i) +
              qpresults.z(i) * qpresults.info.mu_in;
          } else if (qpwork.active_set_low(i)) {
            qpwork.rhs(j + qpmodel.dim + qpmodel.n_eq) =
              -qpwork.primal_residual_in_scaled_low(i) +
              qpresults.z(i) * qpresults.info.mu_in;
          }
        } else {
          qpwork.rhs.head(qpmodel.dim) +=
            qpresults.z(i) *
            qpwork.C_scaled.row(i); // unactive unrelevant columns
        }
      }
      break;
  }
  iterative_solve_with_permut_fact( //
    qpsettings,
    qpmodel,
    qpresults,
    qpwork,
    eps,
    inner_pb_dim);

  // use active_part_z as a temporary variable to derive unpermutted dz step
  for (isize j = 0; j < qpmodel.n_in; ++j) {
    isize i = qpwork.current_bijection_map(j);
    if (i < qpwork.n_c) {
      qpwork.active_part_z(j) = qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + i);
    } else {
      qpwork.active_part_z(j) = -qpresults.z(j);
    }
  }
  qpwork.dw_aug.tail(qpmodel.n_in) = qpwork.active_part_z;
}
template<typename T>
void
primal_dual_newton_semi_smooth(const Settings<T>& qpsettings,
                               const Model<T>& qpmodel,
                               Results<T>& qpresults,
                               Workspace<T>& qpwork,
                               preconditioner::RuizEquilibration<T>& ruiz,
                               T eps_int)
{

  /* MUST CONTAIN IN ENTRY WITH x = x_prev ; y = y_prev ; z = z_prev
   *  dual_residual_scaled = Hx + rho * (x-x_prev) + A.T y + C.T z
   *  primal_residual_eq_scaled = Ax-b+mu_eq (y_prev-y)
   *  primal_residual_in_scaled_up = Cx-u+mu_in(z_prev)
   *  primal_residual_in_scaled_low = Cx-l+mu_in(z_prev)
   */
  /* for debug
  if (qpsettings.verbose) {
          std::cout << "---- inner iteration    inner error    alpha ----" <<
  std::endl;
  }
  */
  T err_in = 1.e6;

  for (i64 iter = 0; iter <= qpsettings.max_iter_in; ++iter) {

    if (iter == qpsettings.max_iter_in) {
      qpresults.info.iter += qpsettings.max_iter_in + 1;
      break;
    }
    proxsuite::linalg::veg::dynstack::DynStackMut stack{
      proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut()
    };
    primal_dual_semi_smooth_newton_step<T>(
      qpsettings, qpmodel, qpresults, qpwork, eps_int);

    auto& Hdx = qpwork.Hdx;
    auto& Adx = qpwork.Adx;
    auto& Cdx = qpwork.Cdx;
    auto& ATdy = qpwork.CTz;

    auto dx = qpwork.dw_aug.head(qpmodel.dim);
    auto dy = qpwork.dw_aug.segment(qpmodel.dim, qpmodel.n_eq);
    auto dz = qpwork.dw_aug.segment(qpmodel.dim + qpmodel.n_eq, qpmodel.n_in);
    Cdx.noalias() = qpwork.C_scaled * dx;
    switch (qpsettings.merit_function_type) {
      case MeritFunctionType::GPDAL:
        Cdx.noalias() +=
          (qpsettings.alpha_gpdal - 1.) * qpresults.info.mu_in * dz;
        break;
      case MeritFunctionType::PDAL:
        break;
    }
    LDLT_TEMP_VEC(T, CTdz, qpmodel.dim, stack);
    CTdz.noalias() = qpwork.C_scaled.transpose() * dz;
    if (qpmodel.n_in > 0) {
      linesearch::primal_dual_ls(qpmodel, qpresults, qpwork, qpsettings);
    }
    auto alpha = qpwork.alpha;

    if (infty_norm(alpha * qpwork.dw_aug) < 1.E-11 && iter > 0) {
      qpresults.info.iter += iter + 1;
      /* to put in debuger mode
      if (qpsettings.verbose) {
              std::cout << "infty_norm(alpha_step * dx) "
                                                      << infty_norm(alpha *
      qpwork.dw_aug) << std::endl;
      }
      */
      break;
    }
    qpresults.x += alpha * dx;

    // contains now :  C(x+alpha dx)-u + z_prev * mu_in
    qpwork.primal_residual_in_scaled_up += alpha * Cdx;

    // contains now :  C(x+alpha dx)-l + z_prev * mu_in
    qpwork.primal_residual_in_scaled_low += alpha * Cdx;

    qpwork.primal_residual_eq_scaled +=
      alpha * (Adx - qpresults.info.mu_eq * dy);
    qpresults.y += alpha * dy;
    qpresults.z += alpha * dz;
    switch (qpsettings.problem_type) {
      case ProblemType::LP:
        qpwork.dual_residual_scaled +=
          alpha * (qpresults.info.rho * dx + ATdy + CTdz);
        break;
      case ProblemType::QP:
        qpwork.dual_residual_scaled +=
          alpha * (qpresults.info.rho * dx + Hdx + ATdy + CTdz);
        break;
    }

    err_in = dense::compute_inner_loop_saddle_point(
      qpmodel, qpresults, qpwork, qpsettings);
    /* for debug
    if (qpsettings.verbose) {
            std::cout << "           " << iter << "              " <<
    std::setprecision(2) << err_in
                                                    << "         "  << alpha <<
    std::endl;
    }
    */
    if (qpsettings.verbose) {
      std::cout << "\033[1;34m[inner iteration " << iter + 1 << "]\033[0m"
                << std::endl;
      std::cout << std::scientific << std::setw(2) << std::setprecision(2)
                << "| inner residual=" << err_in << " | alpha=" << alpha
                << std::endl;
    }
    if (err_in <= eps_int) {
      qpresults.info.iter += iter + 1;
      /* for debug
      if (qpsettings.verbose) {
              std::cout << "-------------------------------------------------"
      << std::endl;
      }
      */
      break;
    }

    // compute primal and dual infeasibility criteria
    bool is_primal_infeasible =
      global_primal_residual_infeasibility(VectorViewMut<T>{ from_eigen, ATdy },
                                           VectorViewMut<T>{ from_eigen, CTdz },
                                           VectorViewMut<T>{ from_eigen, dy },
                                           VectorViewMut<T>{ from_eigen, dz },
                                           qpwork,
                                           qpsettings,
                                           ruiz);

    bool is_dual_infeasible =
      global_dual_residual_infeasibility(VectorViewMut<T>{ from_eigen, Adx },
                                         VectorViewMut<T>{ from_eigen, Cdx },
                                         VectorViewMut<T>{ from_eigen, Hdx },
                                         VectorViewMut<T>{ from_eigen, dx },
                                         qpwork,
                                         qpsettings,
                                         qpmodel,
                                         ruiz);

    if (is_primal_infeasible) {
      qpresults.info.status = QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE;
      /* for debug
      if (qpsettings.verbose) {
              std::cout << "-------------------------------------------------"
      << std::endl;
      }
      */
      break;
    } else if (is_dual_infeasible) {
      qpresults.info.status = QPSolverOutput::PROXQP_DUAL_INFEASIBLE;
      /* for debug
      if (qpsettings.verbose) {
              std::cout << "-------------------------------------------------"
      << std::endl;
      }
      */
      break;
    }
  }
  /* to put in debuger mode
  if (qpsettings.verbose) {
    if (err_in > eps_int){
          std::cout << " inner loop residual is to high! Its value is equal to "
  << err_in << ", while it should be inferior to: "  << eps_int << std::endl;
    }
  }
  */
}
template<typename T>
void
qp_solve( //
  const Settings<T>& qpsettings,
  const Model<T>& qpmodel,
  Results<T>& qpresults,
  Workspace<T>& qpwork,
  preconditioner::RuizEquilibration<T>& ruiz)
{
  /*** TEST WITH MATRIX FULL OF NAN FOR DEBUG
    static constexpr Layout layout = rowmajor;
    static constexpr auto DYN = Eigen::Dynamic;
  using RowMat = Eigen::Matrix<T, DYN, DYN, Eigen::RowMajor>;
  RowMat test(2,2); // test it is full of nan for debug
  std::cout << "test " << test << std::endl;
  */
  PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED();

  if (qpsettings.compute_timings) {
    qpwork.timer.stop();
    qpwork.timer.start();
  }
  if (qpsettings.verbose) {
    dense::print_setup_header(qpsettings, qpresults, qpmodel);
  }
  if (qpwork.dirty) { // the following is used when a solve has already been
                      // executed (and without any intermediary model update)
    switch (qpsettings.initial_guess) {
      case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: {
        qpwork.cleanup();
        qpresults.cleanup(qpsettings);
        break;
      }
      case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: {
        // keep solutions but restart workspace and results
        qpwork.cleanup();
        qpresults.cold_start(qpsettings);
        ruiz.scale_primal_in_place(
          { proxsuite::proxqp::from_eigen, qpresults.x });
        ruiz.scale_dual_in_place_eq(
          { proxsuite::proxqp::from_eigen, qpresults.y });
        ruiz.scale_dual_in_place_in(
          { proxsuite::proxqp::from_eigen, qpresults.z });
        break;
      }
      case InitialGuessStatus::NO_INITIAL_GUESS: {
        qpwork.cleanup();
        qpresults.cleanup(qpsettings);
        break;
      }
      case InitialGuessStatus::WARM_START: {
        qpwork.cleanup();
        qpresults.cold_start(
          qpsettings); // because there was already a solve,
                       // precond was already computed if set so
        ruiz.scale_primal_in_place(
          { proxsuite::proxqp::from_eigen,
            qpresults
              .x }); // it contains the value given in entry for warm start
        ruiz.scale_dual_in_place_eq(
          { proxsuite::proxqp::from_eigen, qpresults.y });
        ruiz.scale_dual_in_place_in(
          { proxsuite::proxqp::from_eigen, qpresults.z });
        break;
      }
      case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: {
        // keep workspace and results solutions except statistics
        qpresults.cleanup_statistics();
        ruiz.scale_primal_in_place(
          { proxsuite::proxqp::from_eigen, qpresults.x });
        ruiz.scale_dual_in_place_eq(
          { proxsuite::proxqp::from_eigen, qpresults.y });
        ruiz.scale_dual_in_place_in(
          { proxsuite::proxqp::from_eigen, qpresults.z });
        break;
      }
    }
    if (qpsettings.initial_guess !=
        InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT) {
      switch (qpsettings.problem_type) {
        case ProblemType::LP:
          break;
        case ProblemType::QP:
          qpwork.H_scaled = qpmodel.H;
          break;
      }
      qpwork.g_scaled = qpmodel.g;
      qpwork.A_scaled = qpmodel.A;
      qpwork.b_scaled = qpmodel.b;
      qpwork.C_scaled = qpmodel.C;
      qpwork.u_scaled = qpmodel.u;
      qpwork.l_scaled = qpmodel.l;
      proxsuite::proxqp::dense::setup_equilibration(
        qpwork, qpsettings, ruiz, false); // reuse previous equilibration
      proxsuite::proxqp::dense::setup_factorization(
        qpwork, qpmodel, qpsettings, qpresults);
    }
    switch (qpsettings.initial_guess) {
      case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: {
        compute_equality_constrained_initial_guess(
          qpwork, qpsettings, qpmodel, qpresults);
        break;
      }
      case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: {
        qpwork.n_c = 0;
        for (isize i = 0; i < qpmodel.n_in; i++) {
          if (qpresults.z[i] != 0) {
            qpwork.active_inequalities[i] = true;
          } else {
            qpwork.active_inequalities[i] = false;
          }
        }
        linesearch::active_set_change(qpmodel, qpresults, qpwork);
        break;
      }
      case InitialGuessStatus::NO_INITIAL_GUESS: {
        break;
      }
      case InitialGuessStatus::WARM_START: {
        qpwork.n_c = 0;
        for (isize i = 0; i < qpmodel.n_in; i++) {
          if (qpresults.z[i] != 0) {
            qpwork.active_inequalities[i] = true;
          } else {
            qpwork.active_inequalities[i] = false;
          }
        }
        linesearch::active_set_change(qpmodel, qpresults, qpwork);
        break;
      }
      case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: {
        // keep workspace and results solutions except statistics

        // meaningful for when one wants to warm start with previous result with
        // the same QP model
        break;
      }
    }
  } else { // the following is used for a first solve after initializing or
           // updating the Qp object
    switch (qpsettings.initial_guess) {
      case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: {
        proxsuite::proxqp::dense::setup_factorization(
          qpwork, qpmodel, qpsettings, qpresults);
        compute_equality_constrained_initial_guess(
          qpwork, qpsettings, qpmodel, qpresults);
        break;
      }
      case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: {
        ruiz.scale_primal_in_place(
          { proxsuite::proxqp::from_eigen,
            qpresults
              .x }); // meaningful for when there is an upate of the model and
                     // one wants to warm start with previous result
        ruiz.scale_dual_in_place_eq(
          { proxsuite::proxqp::from_eigen, qpresults.y });
        ruiz.scale_dual_in_place_in(
          { proxsuite::proxqp::from_eigen, qpresults.z });
        setup_factorization(qpwork, qpmodel, qpsettings, qpresults);
        qpwork.n_c = 0;
        for (isize i = 0; i < qpmodel.n_in; i++) {
          if (qpresults.z[i] != 0) {
            qpwork.active_inequalities[i] = true;
          } else {
            qpwork.active_inequalities[i] = false;
          }
        }
        linesearch::active_set_change(qpmodel, qpresults, qpwork);
        break;
      }
      case InitialGuessStatus::NO_INITIAL_GUESS: {
        setup_factorization(qpwork, qpmodel, qpsettings, qpresults);
        break;
      }
      case InitialGuessStatus::WARM_START: {
        ruiz.scale_primal_in_place(
          { proxsuite::proxqp::from_eigen, qpresults.x });
        ruiz.scale_dual_in_place_eq(
          { proxsuite::proxqp::from_eigen, qpresults.y });
        ruiz.scale_dual_in_place_in(
          { proxsuite::proxqp::from_eigen, qpresults.z });
        setup_factorization(qpwork, qpmodel, qpsettings, qpresults);
        qpwork.n_c = 0;
        for (isize i = 0; i < qpmodel.n_in; i++) {
          if (qpresults.z[i] != 0) {
            qpwork.active_inequalities[i] = true;
          } else {
            qpwork.active_inequalities[i] = false;
          }
        }
        linesearch::active_set_change(qpmodel, qpresults, qpwork);
        break;
      }
      case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: {

        ruiz.scale_primal_in_place(
          { proxsuite::proxqp::from_eigen,
            qpresults
              .x }); // meaningful for when there is an upate of the model and
                     // one wants to warm start with previous result
        ruiz.scale_dual_in_place_eq(
          { proxsuite::proxqp::from_eigen, qpresults.y });
        ruiz.scale_dual_in_place_in(
          { proxsuite::proxqp::from_eigen, qpresults.z });
        if (qpwork.refactorize) { // refactorization only when one of the
                                  // matrices has changed or one proximal
                                  // parameter has changed
          setup_factorization(qpwork, qpmodel, qpsettings, qpresults);
          qpwork.n_c = 0;
          for (isize i = 0; i < qpmodel.n_in; i++) {
            if (qpresults.z[i] != 0) {
              qpwork.active_inequalities[i] = true;
            } else {
              qpwork.active_inequalities[i] = false;
            }
          }
          linesearch::active_set_change(qpmodel, qpresults, qpwork);
          break;
        }
      }
    }
  }

  T bcl_eta_ext_init = pow(T(0.1), qpsettings.alpha_bcl);
  T bcl_eta_ext = bcl_eta_ext_init;
  T bcl_eta_in(1);
  T eps_in_min = std::min(qpsettings.eps_abs, T(1.E-9));

  T primal_feasibility_eq_rhs_0(0);
  T primal_feasibility_in_rhs_0(0);
  T dual_feasibility_rhs_0(0);
  T dual_feasibility_rhs_1(0);
  T dual_feasibility_rhs_3(0);
  T primal_feasibility_lhs(0);
  T primal_feasibility_eq_lhs(0);
  T primal_feasibility_in_lhs(0);
  T dual_feasibility_lhs(0);

  T duality_gap(0);
  T rhs_duality_gap(0);

  for (i64 iter = 0; iter < qpsettings.max_iter; ++iter) {

    // compute primal residual

    // PERF: fuse matrix product computations in global_{primal, dual}_residual
    global_primal_residual(qpmodel,
                           qpresults,
                           qpwork,
                           ruiz,
                           primal_feasibility_lhs,
                           primal_feasibility_eq_rhs_0,
                           primal_feasibility_in_rhs_0,
                           primal_feasibility_eq_lhs,
                           primal_feasibility_in_lhs);

    global_dual_residual(qpresults,
                         qpwork,
                         qpmodel,
                         qpsettings,
                         ruiz,
                         dual_feasibility_lhs,
                         dual_feasibility_rhs_0,
                         dual_feasibility_rhs_1,
                         dual_feasibility_rhs_3,
                         rhs_duality_gap,
                         duality_gap);
    qpresults.info.pri_res = primal_feasibility_lhs;
    qpresults.info.dua_res = dual_feasibility_lhs;
    qpresults.info.duality_gap = duality_gap;

    T new_bcl_mu_in(qpresults.info.mu_in);
    T new_bcl_mu_eq(qpresults.info.mu_eq);
    T new_bcl_mu_in_inv(qpresults.info.mu_in_inv);
    T new_bcl_mu_eq_inv(qpresults.info.mu_eq_inv);

    T rhs_pri(qpsettings.eps_abs);
    if (qpsettings.eps_rel != 0) {
      rhs_pri += qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0,
                                               primal_feasibility_in_rhs_0);
    }
    bool is_primal_feasible = primal_feasibility_lhs <= rhs_pri;

    T rhs_dua(qpsettings.eps_abs);
    if (qpsettings.eps_rel != 0) {
      rhs_dua +=
        qpsettings.eps_rel *
        std::max(
          std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0),
          std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2));
    }

    bool is_dual_feasible = dual_feasibility_lhs <= rhs_dua;

    if (qpsettings.verbose) {

      ruiz.unscale_primal_in_place(VectorViewMut<T>{ from_eigen, qpresults.x });
      ruiz.unscale_dual_in_place_eq(
        VectorViewMut<T>{ from_eigen, qpresults.y });
      ruiz.unscale_dual_in_place_in(
        VectorViewMut<T>{ from_eigen, qpresults.z });

      {
        // EigenAllowAlloc _{};
        qpresults.info.objValue = 0;
        for (Eigen::Index j = 0; j < qpmodel.dim; ++j) {
          qpresults.info.objValue +=
            0.5 * (qpresults.x(j) * qpresults.x(j)) * qpmodel.H(j, j);
          qpresults.info.objValue +=
            qpresults.x(j) * T(qpmodel.H.col(j)
                                 .tail(qpmodel.dim - j - 1)
                                 .dot(qpresults.x.tail(qpmodel.dim - j - 1)));
        }
        qpresults.info.objValue += (qpmodel.g).dot(qpresults.x);
      }
      std::cout << "\033[1;32m[outer iteration " << iter + 1 << "]\033[0m"
                << std::endl;
      std::cout << std::scientific << std::setw(2) << std::setprecision(2)
                << "| primal residual=" << qpresults.info.pri_res
                << " | dual residual=" << qpresults.info.dua_res
                << " | duality gap=" << qpresults.info.duality_gap
                << " | mu_in=" << qpresults.info.mu_in
                << " | rho=" << qpresults.info.rho << std::endl;
      ruiz.scale_primal_in_place(VectorViewMut<T>{ from_eigen, qpresults.x });
      ruiz.scale_dual_in_place_eq(VectorViewMut<T>{ from_eigen, qpresults.y });
      ruiz.scale_dual_in_place_in(VectorViewMut<T>{ from_eigen, qpresults.z });
    }
    if (is_primal_feasible && is_dual_feasible) {
      if (qpsettings.check_duality_gap) {
        if (std::fabs(qpresults.info.duality_gap) <=
            qpsettings.eps_duality_gap_abs +
              qpsettings.eps_duality_gap_rel * rhs_duality_gap) {
          qpresults.info.status = QPSolverOutput::PROXQP_SOLVED;
          break;
        }
      } else {
        qpresults.info.status = QPSolverOutput::PROXQP_SOLVED;
        break;
      }
    }
    qpresults.info.iter_ext += 1; // We start a new external loop update

    qpwork.x_prev = qpresults.x;
    qpwork.y_prev = qpresults.y;
    qpwork.z_prev = qpresults.z;

    // primal dual version from gill and robinson

    ruiz.scale_primal_residual_in_place_in(VectorViewMut<T>{
      from_eigen,
      qpwork.primal_residual_in_scaled_up }); // contains now scaled(Cx)
    qpwork.primal_residual_in_scaled_up +=
      qpwork.z_prev *
      qpresults.info.mu_in; // contains now scaled(Cx+z_prev*mu_in)
    switch (qpsettings.merit_function_type) {
      case MeritFunctionType::GPDAL:
        qpwork.primal_residual_in_scaled_up +=
          (qpsettings.alpha_gpdal - 1.) * qpresults.info.mu_in * qpresults.z;
        break;
      case MeritFunctionType::PDAL:
        break;
    }
    qpwork.primal_residual_in_scaled_low = qpwork.primal_residual_in_scaled_up;
    qpwork.primal_residual_in_scaled_up -=
      qpwork.u_scaled; // contains now scaled(Cx-u+z_prev*mu_in)
    qpwork.primal_residual_in_scaled_low -=
      qpwork.l_scaled; // contains now scaled(Cx-l+z_prev*mu_in)
    primal_dual_newton_semi_smooth(
      qpsettings, qpmodel, qpresults, qpwork, ruiz, bcl_eta_in);

    if (qpresults.info.status == QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE ||
        qpresults.info.status == QPSolverOutput::PROXQP_DUAL_INFEASIBLE) {
      // certificate of infeasibility
      qpresults.x = qpwork.dw_aug.head(qpmodel.dim);
      qpresults.y = qpwork.dw_aug.segment(qpmodel.dim, qpmodel.n_eq);
      qpresults.z = qpwork.dw_aug.tail(qpmodel.n_in);
      break;
    }

    T primal_feasibility_lhs_new(primal_feasibility_lhs);

    global_primal_residual(qpmodel,
                           qpresults,
                           qpwork,
                           ruiz,
                           primal_feasibility_lhs_new,
                           primal_feasibility_eq_rhs_0,
                           primal_feasibility_in_rhs_0,
                           primal_feasibility_eq_lhs,
                           primal_feasibility_in_lhs);

    is_primal_feasible =
      primal_feasibility_lhs_new <=
      (qpsettings.eps_abs +
       qpsettings.eps_rel *
         std::max(primal_feasibility_eq_rhs_0, primal_feasibility_in_rhs_0));
    qpresults.info.pri_res = primal_feasibility_lhs_new;
    if (is_primal_feasible) {
      T dual_feasibility_lhs_new(dual_feasibility_lhs);

      global_dual_residual(qpresults,
                           qpwork,
                           qpmodel,
                           qpsettings,
                           ruiz,
                           dual_feasibility_lhs_new,
                           dual_feasibility_rhs_0,
                           dual_feasibility_rhs_1,
                           dual_feasibility_rhs_3,
                           rhs_duality_gap,
                           duality_gap);
      qpresults.info.dua_res = dual_feasibility_lhs_new;
      qpresults.info.duality_gap = duality_gap;

      is_dual_feasible =
        dual_feasibility_lhs_new <=
        (qpsettings.eps_abs +
         qpsettings.eps_rel *
           std::max(
             std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0),
             std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2)));

      if (is_dual_feasible) {
        if (qpsettings.check_duality_gap) {
          if (std::fabs(qpresults.info.duality_gap) <=
              qpsettings.eps_duality_gap_abs +
                qpsettings.eps_duality_gap_rel * rhs_duality_gap) {
            qpresults.info.status = QPSolverOutput::PROXQP_SOLVED;
            break;
          }
        } else {
          qpresults.info.status = QPSolverOutput::PROXQP_SOLVED;
          break;
        }
      }
    }
    if (qpsettings.bcl_update) {
      bcl_update(qpsettings,
                 qpresults,
                 qpwork,
                 primal_feasibility_lhs_new,
                 bcl_eta_ext,
                 bcl_eta_in,
                 bcl_eta_ext_init,
                 eps_in_min,

                 new_bcl_mu_in,
                 new_bcl_mu_eq,
                 new_bcl_mu_in_inv,
                 new_bcl_mu_eq_inv);
    } else {
      Martinez_update(qpsettings,
                      qpresults,
                      primal_feasibility_lhs_new,
                      primal_feasibility_lhs,
                      bcl_eta_in,
                      eps_in_min,
                      new_bcl_mu_in,
                      new_bcl_mu_eq,
                      new_bcl_mu_in_inv,
                      new_bcl_mu_eq_inv);
    }
    // COLD RESTART

    T dual_feasibility_lhs_new(dual_feasibility_lhs);

    global_dual_residual(qpresults,
                         qpwork,
                         qpmodel,
                         qpsettings,
                         ruiz,
                         dual_feasibility_lhs_new,
                         dual_feasibility_rhs_0,
                         dual_feasibility_rhs_1,
                         dual_feasibility_rhs_3,
                         rhs_duality_gap,
                         duality_gap);
    qpresults.info.dua_res = dual_feasibility_lhs_new;
    qpresults.info.duality_gap = duality_gap;

    if (primal_feasibility_lhs_new >= primal_feasibility_lhs &&
        dual_feasibility_lhs_new >= dual_feasibility_lhs &&
        qpresults.info.mu_in <= T(1e-5)) {
      /* to put in debuger mode
      if (qpsettings.verbose) {
              std::cout << "cold restart" << std::endl;
      }
      */

      new_bcl_mu_in = qpsettings.cold_reset_mu_in;
      new_bcl_mu_eq = qpsettings.cold_reset_mu_eq;
      new_bcl_mu_in_inv = qpsettings.cold_reset_mu_in_inv;
      new_bcl_mu_eq_inv = qpsettings.cold_reset_mu_eq_inv;
    }


    if (qpresults.info.mu_in != new_bcl_mu_in ||
        qpresults.info.mu_eq != new_bcl_mu_eq) {
      {
        ++qpresults.info.mu_updates;
      }
      mu_update(qpmodel, qpresults, qpwork, new_bcl_mu_eq, new_bcl_mu_in);
    }

    qpresults.info.mu_eq = new_bcl_mu_eq;
    qpresults.info.mu_in = new_bcl_mu_in;
    qpresults.info.mu_eq_inv = new_bcl_mu_eq_inv;
    qpresults.info.mu_in_inv = new_bcl_mu_in_inv;
  }

  ruiz.unscale_primal_in_place(VectorViewMut<T>{ from_eigen, qpresults.x });
  ruiz.unscale_dual_in_place_eq(VectorViewMut<T>{ from_eigen, qpresults.y });
  ruiz.unscale_dual_in_place_in(VectorViewMut<T>{ from_eigen, qpresults.z });

  {
    // EigenAllowAlloc _{};
    qpresults.info.objValue = 0;
    for (Eigen::Index j = 0; j < qpmodel.dim; ++j) {
      qpresults.info.objValue +=
        0.5 * (qpresults.x(j) * qpresults.x(j)) * qpmodel.H(j, j);
      qpresults.info.objValue +=
        qpresults.x(j) * T(qpmodel.H.col(j)
                             .tail(qpmodel.dim - j - 1)
                             .dot(qpresults.x.tail(qpmodel.dim - j - 1)));
    }
    qpresults.info.objValue += (qpmodel.g).dot(qpresults.x);
  }

  if (qpsettings.compute_timings) {
    qpresults.info.solve_time = qpwork.timer.elapsed().user; // in nanoseconds
    qpresults.info.run_time =
      qpresults.info.solve_time + qpresults.info.setup_time;
  }

  if (qpsettings.verbose) {
    std::cout << "-------------------SOLVER STATISTICS-------------------"
              << std::endl;
    std::cout << "outer iter:   " << qpresults.info.iter_ext << std::endl;
    std::cout << "total iter:   " << qpresults.info.iter << std::endl;
    std::cout << "mu updates:   " << qpresults.info.mu_updates << std::endl;
    std::cout << "rho updates:  " << qpresults.info.rho_updates << std::endl;
    std::cout << "objective:    " << qpresults.info.objValue << std::endl;
    switch (qpresults.info.status) {
      case QPSolverOutput::PROXQP_SOLVED: {
        std::cout << "status:       "
                  << "Solved" << std::endl;
        break;
      }
      case QPSolverOutput::PROXQP_MAX_ITER_REACHED: {
        std::cout << "status:       "
                  << "Maximum number of iterations reached" << std::endl;
        break;
      }
      case QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE: {
        std::cout << "status:       "
                  << "Primal infeasible" << std::endl;
        break;
      }
      case QPSolverOutput::PROXQP_DUAL_INFEASIBLE: {
        std::cout << "status:       "
                  << "Dual infeasible" << std::endl;
        break;
      }
      default: {
        assert(false && "Should never happened");
        break;
      }
    }

    if (qpsettings.compute_timings)
      std::cout << "run time:     " << qpresults.info.solve_time << std::endl;
    std::cout << "--------------------------------------------------------"
              << std::endl;
  }
  qpwork.dirty = true;
  qpwork.is_initialized = true; // necessary because we call workspace cleanup

  assert(!std::isnan(qpresults.info.pri_res));
  assert(!std::isnan(qpresults.info.dua_res));
  assert(!std::isnan(qpresults.info.duality_gap));

  PROXSUITE_EIGEN_MALLOC_ALLOWED();
}

} // namespace dense

} // namespace proxqp
} // namespace proxsuite

#endif /* end of include guard PROXSUITE_PROXQP_DENSE_SOLVER_HPP */