10#include <fmt/ostream.h> 
   15template <
typename Scalar>
 
   16ProxNLPSolverTpl<Scalar>::ProxNLPSolverTpl(
 
   17    Problem &prob, 
const Scalar tol, 
const Scalar mu_init,
 
   18    const Scalar rho_init, 
const VerboseLevel verbose, 
const Scalar mu_lower,
 
   19    const Scalar prim_alpha, 
const Scalar prim_beta, 
const Scalar dual_alpha,
 
   20    const Scalar dual_beta, 
LDLTChoice ldlt_choice,
 
   21    const LinesearchOptions ls_options)
 
   22    : problem_(&prob), merit_fun(*problem_, pdal_beta_),
 
   23      prox_penalty(prob.manifold_, manifold().neutral(),
 
   25                       MatrixXs::Identity(manifold().ndx(), manifold().ndx())),
 
   26      verbose(verbose), ldlt_choice_(ldlt_choice), rho_init_(rho_init),
 
   27      mu_init_(mu_init), mu_lower_(mu_lower),
 
   28      bcl_params{prim_alpha, prim_beta, dual_alpha, dual_beta},
 
   29      ls_options(ls_options), target_tol(tol) {}
 
   31template <
typename Scalar>
 
   34                                const std::vector<VectorRef> &lams0) {
 
   35  VectorXs new_lam(
problem_->getTotalConstraintDim());
 
   38  const std::size_t numc = 
problem_->getNumConstraints();
 
   39  if (numc != lams0.size()) {
 
   40    PROXSUITE_NLP_RUNTIME_ERROR(
 
   41        "Specified number of constraints is not the same " 
   42        "as the provided number of multipliers!");
 
   44  for (std::size_t i = 0; i < numc; i++) {
 
   46    new_lam.segment(
problem_->getIndex(i), nr) = lams0[i];
 
   48  return solve(x0, new_lam);
 
 
   51template <
typename Scalar>
 
   53                                                const ConstVectorRef &lams0) {
 
   57  if ((results_ == 
nullptr) || (workspace_ == 
nullptr)) {
 
   58    PROXSUITE_NLP_RUNTIME_ERROR(
 
   59        "Either Results or Workspace are unitialized. Call setup() first.");
 
   62  auto &results = *results_;
 
   63  auto &workspace = *workspace_;
 
   70  workspace.x_prev = x0;
 
   71  if (lams0.size() == workspace.numdual) {
 
   72    results.data_lams_opt = lams0;
 
   73    workspace.data_lams_prev = lams0;
 
   78  results.converged = ConvergenceFlag::UNINIT;
 
   80  std::size_t &i = results.num_iters;
 
   81  std::size_t &al_iter = results.al_iters;
 
   85  while ((i < max_iters) && (al_iter < max_al_iters)) {
 
   88    innerLoop(workspace, results);
 
   91    workspace.x_prev = results.x_opt;
 
   94    if (results.prim_infeas < prim_tol_) {
 
  101    if (std::max(results.prim_infeas, results.dual_infeas) < 
target_tol) {
 
  102      results.converged = ConvergenceFlag::SUCCESS;
 
  110  if (results.converged == SUCCESS)
 
  111    fmt::print(fmt::fg(fmt::color::dodger_blue),
 
  112               "Solver successfully converged");
 
  114  switch (results.converged) {
 
  115  case MAX_ITERS_REACHED:
 
  116    fmt::print(fmt::fg(fmt::color::orange_red),
 
  117               "Max number of iterations reached.");
 
  124  invokeCallbacks(workspace, results);
 
  126  return results.converged;
 
 
  130                         const Eigen::VectorXi &signature) {
 
  131  auto inertiaTuple = computeInertiaTuple(signature);
 
  132  int numpos = inertiaTuple[0];
 
  133  int numneg = inertiaTuple[1];
 
  134  int numzer = inertiaTuple[2];
 
  135  InertiaFlag flag = INERTIA_OK;
 
  136  bool pos_ok = numpos == ndx;
 
  137  bool neg_ok = numneg == numc;
 
  138  bool zer_ok = numzer == 0;
 
  139  if (!(pos_ok && neg_ok && zer_ok)) {
 
  141      flag = INERTIA_HAS_ZEROS;
 
 
  150template <
typename Scalar>
 
  152                                                 Workspace &workspace)
 const {
 
  153  switch (mul_update_mode) {
 
  154  case MultiplierUpdateMode::NEWTON:
 
  155    workspace.data_lams_prev = results.data_lams_opt;
 
  157  case MultiplierUpdateMode::PRIMAL:
 
  158    workspace.data_lams_prev = workspace.data_lams_plus;
 
  160  case MultiplierUpdateMode::PRIMAL_DUAL:
 
  161    workspace.data_lams_prev = workspace.data_lams_pdal;
 
  166  results.data_lams_opt = workspace.data_lams_prev;
 
 
  169template <
typename Scalar>
 
  171    const ConstVectorRef &inner_lams_data, Workspace &workspace)
 const {
 
  173  workspace.data_shift_cstr_values =
 
  174      workspace.data_cstr_values + mu_ * workspace.data_lams_prev;
 
  176  for (std::size_t i = 0; i < 
problem_->getNumConstraints(); i++) {
 
  177    const ConstraintSet &cstr_set = *
problem_->getConstraint(i).set_;
 
  182  workspace.data_lams_plus = mu_inv_ * workspace.data_lams_plus;
 
  185  workspace.data_shift_cstr_pdal =
 
  186      workspace.data_shift_cstr_values - 0.5 * mu_ * inner_lams_data;
 
  187  for (std::size_t i = 0; i < 
problem_->getNumConstraints(); i++) {
 
  188    const ConstraintSet &cstr_set = *
problem_->getConstraint(i).set_;
 
  192  workspace.data_lams_pdal *= mu_inv_ / pdal_beta_;
 
  194  workspace.data_lams_plus_reproj = workspace.data_lams_plus;
 
  195  workspace.data_lams_pdal_reproj = workspace.data_lams_pdal;
 
  196  for (std::size_t i = 0; i < 
problem_->getNumConstraints(); i++) {
 
  197    const ConstraintSet &cstr_set = *
problem_->getConstraint(i).set_;
 
  202                                     workspace.lams_pdal_reproj[i]);
 
 
  207template <
typename Scalar>
 
  209    const ConstVectorRef &x, Workspace &workspace, boost::mpl::false_)
 const {
 
  210  problem_->computeDerivatives(x, workspace);
 
  212  workspace.data_jacobians_proj = workspace.data_jacobians;
 
  213  for (std::size_t i = 0; i < 
problem_->getNumConstraints(); i++) {
 
  214    const ConstraintSet &cstr_set = *
problem_->getConstraint(i).set_;
 
  215    switch (kkt_system_) {
 
  220    case KKT_PRIMAL_DUAL:
 
  222          workspace.shift_cstr_pdal[i], workspace.cstr_jacobians_proj[i]);
 
 
  228template <
typename Scalar>
 
  230    const ConstVectorRef &x, Workspace &workspace, boost::mpl::true_)
 const {
 
  244    problem_->computeHessians(x, workspace,
 
 
  250template <
typename Scalar>
 
  252                                                      Results &results)
 const {
 
  254  workspace.data_shift_cstr_values =
 
  255      workspace.data_cstr_values + mu_ * results.data_lams_opt;
 
  257  for (std::size_t i = 0; i < 
problem_->getNumConstraints(); i++) {
 
  258    const ConstraintSet &cstr_set = *
problem_->getConstraint(i).set_;
 
  263    auto cstr_prox_err = workspace.
cstr_values[i] - displ_cstr;
 
 
  271  if (mu_ == mu_lower_) {
 
 
  278template <
typename Scalar>
 
  279void ProxNLPSolverTpl<Scalar>::innerLoop(Workspace &workspace,
 
  281  const int ndx = manifold().ndx();
 
  282  const long ntot = workspace.kkt_rhs.size();
 
  283  const long ndual = ntot - ndx;
 
  284  const std::size_t num_c = problem_->getNumConstraints();
 
  286  Scalar delta_last = 0.;
 
  287  Scalar delta = delta_last;
 
  291  auto phi_eval = [&](
const Scalar alpha) {
 
  292    tryStep(workspace, results, alpha);
 
  293    problem_->evaluate(workspace.x_trial, workspace);
 
  294    computeMultipliers(workspace.data_lams_trial, workspace);
 
  295    return merit_fun.evaluate(workspace.x_trial, workspace.lams_trial,
 
  297           prox_penalty.call(workspace.x_trial);
 
  302    problem_->evaluate(results.x_opt, workspace);
 
  303    computeMultipliers(results.data_lams_opt, workspace);
 
  304    computeProblemDerivatives(results.x_opt, workspace, boost::mpl::true_());
 
  306    for (std::size_t i = 0; i < num_c; i++) {
 
  307      const ConstraintSet &cstr_set = *problem_->getConstraint(i).set_;
 
  309                                results.active_set[i]);
 
  312    results.value = workspace.objective_value;
 
  314        merit_fun.evaluate(results.x_opt, results.lams_opt, workspace);
 
  317      results.merit += prox_penalty.call(results.x_opt);
 
  318      prox_penalty.computeGradient(results.x_opt, workspace.prox_grad);
 
  319      prox_penalty.computeHessian(results.x_opt, workspace.prox_hess);
 
  324    workspace.kkt_rhs.setZero();
 
  325    workspace.kkt_rhs_corr.setZero();
 
  328    workspace.kkt_rhs.head(ndx) = workspace.objective_gradient;
 
  329    workspace.kkt_rhs.head(ndx).noalias() +=
 
  330        workspace.data_jacobians.transpose() * results.data_lams_opt;
 
  332    switch (kkt_system_) {
 
  334      workspace.kkt_rhs.tail(ndual) =
 
  335          mu_ * (workspace.data_lams_plus - results.data_lams_opt);
 
  337    case KKT_PRIMAL_DUAL:
 
  338      workspace.kkt_rhs.tail(ndual) =
 
  339          0.5 * mu_ * (workspace.data_lams_pdal - results.data_lams_opt);
 
  343    merit_fun.computeGradient(results.lams_opt, workspace);
 
  346      workspace.kkt_rhs.head(ndx) += workspace.prox_grad;
 
  347      workspace.merit_gradient += workspace.prox_grad;
 
  350    PROXSUITE_NLP_RAISE_IF_NAN_NAME(workspace.kkt_rhs, 
"kkt_rhs");
 
  351    PROXSUITE_NLP_RAISE_IF_NAN_NAME(workspace.kkt_matrix, 
"kkt_matrix");
 
  353    computePrimalResiduals(workspace, results);
 
  356    workspace.dual_residual = workspace.objective_gradient;
 
  357    workspace.dual_residual.noalias() +=
 
  358        workspace.data_jacobians.transpose() * results.data_lams_opt;
 
  359    results.dual_infeas = math::infty_norm(workspace.dual_residual);
 
  360    Scalar inner_crit = math::infty_norm(workspace.kkt_rhs);
 
  361    Scalar outer_crit = std::max(results.prim_infeas, results.dual_infeas);
 
  363    bool inner_cond = inner_crit <= inner_tol_;
 
  364    bool outer_cond = outer_crit <= target_tol; 
 
  365    if (inner_cond || outer_cond) {
 
  372    workspace.kkt_rhs_corr.head(ndx).noalias() -=
 
  373        workspace.data_jacobians.transpose() * results.data_lams_opt;
 
  374    workspace.kkt_rhs_corr.head(ndx).noalias() +=
 
  375        workspace.data_jacobians_proj.transpose() * results.data_lams_opt;
 
  376    switch (kkt_system_) {
 
  378      workspace.kkt_rhs_corr.head(ndx).noalias() +=
 
  379          workspace.data_jacobians.transpose() *
 
  380          workspace.data_lams_plus_reproj;
 
  382    case KKT_PRIMAL_DUAL:
 
  383      workspace.kkt_rhs_corr.head(ndx).noalias() +=
 
  384          workspace.data_jacobians.transpose() *
 
  385          workspace.data_lams_pdal_reproj;
 
  389    workspace.kkt_rhs += workspace.kkt_rhs_corr;
 
  392    assembleKktMatrix(workspace);
 
  397    InertiaFlag is_inertia_correct = INERTIA_BAD;
 
  399    while (!(is_inertia_correct == INERTIA_OK) && delta <= DELTA_MAX) {
 
  401        workspace.kkt_matrix.diagonal().head(ndx).array() += delta;
 
  403      boost::apply_visitor(
 
  404          [&](
auto &&fac) { fac.compute(workspace.kkt_matrix); },
 
  406      boost::apply_visitor(ComputeSignatureVisitor{workspace.signature},
 
  408      workspace.kkt_matrix.diagonal().head(ndx).array() -= delta;
 
  410          checkInertia(manifold().ndx(), problem_->getTotalConstraintDim(),
 
  411                       workspace.signature);
 
  413      if (is_inertia_correct == INERTIA_OK) {
 
  416      } 
else if (delta == 0.) {
 
  418        if (delta_last == 0.)
 
  419          delta = DELTA_NONZERO_INIT; 
 
  421          delta = std::max(DELTA_MIN, del_dec_k * delta_last);
 
  424        if (delta_last == 0.)
 
  425          delta *= del_inc_big;
 
  431    iterativeRefinement(workspace);
 
  434    PROXSUITE_NLP_RAISE_IF_NAN_NAME(workspace.pd_step, 
"pd_step");
 
  438    workspace.dmerit_dir =
 
  439        workspace.merit_gradient.dot(workspace.prim_step) +
 
  440        workspace.merit_dual_gradient.dot(workspace.dual_step);
 
  442    Scalar phi0 = results.merit;
 
  443    Scalar dphi0 = workspace.dmerit_dir;
 
  445    case LinesearchStrategy::ARMIJO: {
 
  446      phi_new = ArmijoLinesearch<Scalar>(ls_options)
 
  447                    .run(phi_eval, results.merit, dphi0, workspace.alpha_opt);
 
  451      PROXSUITE_NLP_RUNTIME_ERROR(
"Unrecognized linesearch alternative.\n");
 
  455    tryStep(workspace, results, workspace.alpha_opt);
 
  457    PROXSUITE_NLP_RAISE_IF_NAN_NAME(workspace.alpha_opt, 
"alpha_opt");
 
  458    PROXSUITE_NLP_RAISE_IF_NAN_NAME(workspace.x_trial, 
"x_trial");
 
  459    PROXSUITE_NLP_RAISE_IF_NAN_NAME(workspace.data_lams_trial, 
"lams_trial");
 
  460    results.x_opt = workspace.x_trial;
 
  461    results.data_lams_opt = workspace.data_lams_trial;
 
  462    results.merit = phi_new;
 
  463    PROXSUITE_NLP_RAISE_IF_NAN_NAME(results.merit, 
"merit");
 
  465    invokeCallbacks(workspace, results);
 
  467    LogRecord record{results.num_iters + 1,
 
  476                     results.al_iters + 1};
 
  481    if (results.num_iters >= max_iters) {
 
  482      results.converged = ConvergenceFlag::MAX_ITERS_REACHED;
 
  490template <
typename Scalar>
 
  491void ProxNLPSolverTpl<Scalar>::assembleKktMatrix(Workspace &workspace) {
 
  492  const long ndx = (long)manifold().ndx();
 
  493  const long ndual = workspace.numdual;
 
  494  workspace.kkt_matrix.setZero();
 
  495  workspace.kkt_matrix.topLeftCorner(ndx, ndx) = workspace.objective_hessian;
 
  496  workspace.kkt_matrix.topRightCorner(ndx, ndual) =
 
  497      workspace.data_jacobians_proj.transpose();
 
  498  workspace.kkt_matrix.bottomLeftCorner(ndual, ndx) =
 
  499      workspace.data_jacobians_proj;
 
  500  auto lower_right_block = workspace.kkt_matrix.bottomRightCorner(ndual, ndual);
 
  501  lower_right_block.diagonal().setConstant(-mu_);
 
  504    workspace.kkt_matrix.topLeftCorner(ndx, ndx) += workspace.prox_hess;
 
  506  for (std::size_t i = 0; i < workspace.numblocks; i++) {
 
  507    const ConstraintSet &cstr_set = *problem_->getConstraint(i).set_;
 
  511      workspace.kkt_matrix.topLeftCorner(ndx, ndx) +=
 
  512          workspace.cstr_vector_hessian_prod[i];
 
  514    if (kkt_system_ == KKT_PRIMAL_DUAL) {
 
  516      int idx = problem_->getIndex(i);
 
  517      int nr = problem_->getConstraintDim(i);
 
  518      auto d_sub = lower_right_block.diagonal().segment(idx, nr);
 
  519      VectorXs d_sub2(d_sub);
 
  521      cstr_set.applyNormalConeProjectionJacobian(workspace.shift_cstr_pdal[i],
 
  523      d_sub = 0.5 * (d_sub + d_sub2);
 
  528template <
typename Scalar>
 
  531  boost::apply_visitor([&](
auto &&fac) { fac.solveInPlace(workspace.
pd_step); },
 
  536    if (math::infty_norm(workspace.
kkt_err) < kkt_tolerance_)
 
  538    boost::apply_visitor(
 
  539        [&](
auto &&fac) { fac.solveInPlace(workspace.
kkt_err); },
 
 
  546template <
typename Scalar>
 
  550  for (std::size_t i = 0; i < 
problem_->getNumConstraints(); i++) {
 
  551    const ConstraintObject &cstr = 
problem_->getConstraint(i);
 
  552    cstr.set_->setProxParameter(mu_);
 
 
  556template <
typename Scalar>
 
  558    const Scalar &new_rho) 
noexcept {
 
 
  564template <
typename Scalar>
 
  566  prim_tol_ = prim_tol0 * std::pow(mu_, 
bcl_params.prim_alpha);
 
  567  inner_tol_ = inner_tol0 * std::pow(mu_, 
bcl_params.dual_alpha);
 
  568  tolerancePostUpdate();
 
 
  571template <
typename Scalar>
 
  573  prim_tol_ = prim_tol_ * std::pow(mu_ / mu_upper_, 
bcl_params.prim_beta);
 
  574  inner_tol_ = inner_tol_ * std::pow(mu_ / mu_upper_, 
bcl_params.dual_beta);
 
  575  tolerancePostUpdate();
 
 
  578template <
typename Scalar>
 
  579void ProxNLPSolverTpl<Scalar>::tolerancePostUpdate() noexcept {
 
  580  inner_tol_ = std::max(inner_tol_, inner_tol_min);
 
  581  prim_tol_ = std::max(prim_tol_, target_tol);
 
  584template <
typename Scalar>
 
  586                                       const Results &results, Scalar alpha) {
 
  588  workspace.tmp_dx_scaled = alpha * workspace.prim_step;
 
  589  manifold().integrate(results.x_opt, workspace.tmp_dx_scaled,
 
  591  workspace.data_lams_trial =
 
  592      results.data_lams_opt + alpha * workspace.dual_step;
 
 
void tryStep(Workspace &workspace, const Results &results, Scalar alpha)
 
Scalar target_tol
Target tolerance for the problem.
 
void updateToleranceSuccess() noexcept
Update primal-dual subproblem tolerances upon successful outer-loop iterate (good primal feasibility)
 
void setProxParameter(const Scalar &new_rho) noexcept
Set proximal penalty parameter.
 
void computeMultipliers(const ConstVectorRef &inner_lams_data, Workspace &workspace) const
 
void acceptMultipliers(Results &results, Workspace &workspace) const
Accept Lagrange multiplier estimates.
 
void updateToleranceFailure() noexcept
Update primal-dual subproblem tolerances upon failure (insufficient primal feasibility)
 
void computePrimalResiduals(Workspace &workspace, Results &results) const
 
void setPenalty(const Scalar &new_mu) noexcept
Set penalty parameter, its inverse and update the merit function.
 
Problem * problem_
General nonlinear program to solve.
 
PROXSUITE_NLP_INLINE bool iterativeRefinement(Workspace &workspace) const
Iterative refinement of the KKT linear system.
 
BCLParamsTpl< Scalar > bcl_params
BCL strategy parameters.
 
VerboseLevel verbose
Level of verbosity of the solver.
 
ConvergenceFlag solve(const ConstVectorRef &x0, const std::vector< VectorRef > &lams0)
Solve the problem.
 
QuadraticDistanceCostTpl< Scalar > prox_penalty
Proximal regularization penalty.
 
void computeProblemDerivatives(const ConstVectorRef &x, Workspace &workspace, boost::mpl::false_) const
 
HessianApprox hess_approx
Use a Gauss-Newton approximation for the Lagrangian Hessian.
 
std::size_t max_refinement_steps_
linear algebra opts
 
VerboseLevel
Verbosity level.
 
Implements a basic Armijo back-tracking strategy.
 
#define PROXSUITE_NLP_NOMALLOC_END
Exiting performance-critical code.
 
#define PROXSUITE_NLP_NOMALLOC_BEGIN
Entering performance-critical code.
 
InertiaFlag checkInertia(const int ndx, const int nc, const Eigen::VectorXi &signature)
Check the matrix has the desired inertia.
 
@ BFGS
BFGS approximation.
 
@ EXACT
Exact Hessian construction from provided function Hessians.
 
virtual bool disableGaussNewton() const
 
virtual void applyProjectionJacobian(const ConstVectorRef &z, MatrixRef Jout) const
Apply a jacobian of the projection/proximal operator to a matrix.
 
virtual void computeActiveSet(const ConstVectorRef &z, Eigen::Ref< ActiveType > out) const =0
 
virtual void projection(const ConstVectorRef &z, VectorRef zout) const =0
Compute projection of variable z onto the constraint set.
 
virtual void applyNormalConeProjectionJacobian(const ConstVectorRef &z, MatrixRef Jout) const
Apply the jacobian of the projection on the normal cone.
 
virtual void normalConeProjection(const ConstVectorRef &z, VectorRef zout) const =0
Compute projection of z onto the normal cone to the set. The default implementation is just .
 
Scalar prim_infeas
Primal infeasibility error.
 
VectorXs constraint_violations
Violations for each constraint.
 
std::vector< VectorRef > lams_plus
First-order multipliers .
 
VectorXs pd_step
Primal-dual step .
 
std::vector< VectorRef > cstr_values
Values of each constraint.
 
MatrixXs kkt_matrix
KKT iteration matrix.
 
VectorXs kkt_rhs
KKT iteration right-hand side.
 
std::vector< VectorRef > lams_plus_reproj
Product of the projector Jacobians with the first-order multipliers.
 
VectorXs objective_gradient
Objective function gradient.
 
std::vector< VectorRef > lams_pdal
Primal-dual multiplier estimates (from the pdBCL algorithm)
 
MatrixXs objective_hessian
Objective function Hessian.
 
VectorXs kkt_err
KKT linear system error (for refinement)
 
std::vector< VectorRef > shift_cstr_values
Buffer for shifted constraints.
 
LDLTVariant< Scalar > ldlt_
LDLT storage.