// Copyright (C) 2004, 2011 International Business Machines and others.
// All Rights Reserved.
// This code is published under the Eclipse Public License.
//
// $Id: IpAlgBuilder.cpp 2666 2016-07-20 16:02:55Z stefan $
//
// Authors:  Carl Laird, Andreas Waechter     IBM    2004-09-29

#include "IpoptConfig.h"
#include "IpAlgBuilder.hpp"

#include "IpOrigIpoptNLP.hpp"
#include "IpIpoptData.hpp"
#include "IpIpoptCalculatedQuantities.hpp"
#include "IpCGPenaltyData.hpp"
#include "IpCGPenaltyCq.hpp"

#include "IpStdAugSystemSolver.hpp"
#include "IpAugRestoSystemSolver.hpp"
#include "IpPDFullSpaceSolver.hpp"
#include "IpPDPerturbationHandler.hpp"
#include "IpCGPerturbationHandler.hpp"
#include "IpOptErrorConvCheck.hpp"
#include "IpBacktrackingLineSearch.hpp"
#include "IpFilterLSAcceptor.hpp"
#include "IpCGPenaltyLSAcceptor.hpp"
#include "IpPenaltyLSAcceptor.hpp"
#include "IpPDSearchDirCalc.hpp"
#include "IpCGSearchDirCalc.hpp"
#include "IpMonotoneMuUpdate.hpp"
#include "IpAdaptiveMuUpdate.hpp"
#include "IpLoqoMuOracle.hpp"
#include "IpProbingMuOracle.hpp"
#include "IpQualityFunctionMuOracle.hpp"
#include "IpRestoMinC_1Nrm.hpp"
#include "IpLeastSquareMults.hpp"
#include "IpDefaultIterateInitializer.hpp"
#include "IpWarmStartIterateInitializer.hpp"
#include "IpOrigIterationOutput.hpp"
#include "IpLimMemQuasiNewtonUpdater.hpp"
#include "IpOrigIpoptNLP.hpp"
#include "IpLowRankAugSystemSolver.hpp"
#include "IpLowRankSSAugSystemSolver.hpp"
#include "IpRestoIterationOutput.hpp"
#include "IpRestoFilterConvCheck.hpp"
#include "IpRestoIterateInitializer.hpp"
#include "IpRestoPenaltyConvCheck.hpp"
#include "IpRestoRestoPhase.hpp"
#include "IpTSymLinearSolver.hpp"
#include "IpUserScaling.hpp"
#include "IpGradientScaling.hpp"
#include "IpEquilibrationScaling.hpp"
#include "IpExactHessianUpdater.hpp"

#ifdef COIN_HAS_HSL
#include "CoinHslConfig.h"
#endif
#include "IpMa27TSolverInterface.hpp"
#include "IpMa57TSolverInterface.hpp"
#include "IpMa77SolverInterface.hpp"
#include "IpMa86SolverInterface.hpp"
#include "IpMa97SolverInterface.hpp"
#include "IpMc19TSymScalingMethod.hpp"
#include "IpPardisoSolverInterface.hpp"
#include "IpSlackBasedTSymScalingMethod.hpp"

#ifdef HAVE_WSMP
# include "IpWsmpSolverInterface.hpp"
# include "IpIterativeWsmpSolverInterface.hpp"
#endif
#ifdef COIN_HAS_MUMPS
# include "IpMumpsSolverInterface.hpp"
#endif

#ifdef HAVE_LINEARSOLVERLOADER
# include "HSLLoader.h"
# include "PardisoLoader.h"
#endif

namespace Ipopt
{
#if COIN_IPOPT_VERBOSITY > 0
  static const Index dbg_verbosity = 0;
#endif

  AlgorithmBuilder::AlgorithmBuilder(SmartPtr<AugSystemSolver> custom_solver /*=NULL*/)
      :
      custom_solver_(custom_solver)
  {}

  void AlgorithmBuilder::RegisterOptions(SmartPtr<RegisteredOptions> roptions)
  {
    roptions->SetRegisteringCategory("Linear Solver");
    roptions->AddStringOption9(
      "linear_solver",
      "Linear solver used for step computations.",
#ifdef COINHSL_HAS_MA27
      "ma27",
#else
# ifdef COINHSL_HAS_MA57
      "ma57",
# else
# ifdef COINHSL_HAS_MA97
      "ma97",
#else
#   ifdef COINHSL_HAS_MA86
       "ma86",
#   else
#    ifdef HAVE_PARDISO
       "pardiso",
#    else
#     ifdef HAVE_WSMP
       "wsmp",
#     else
#      ifdef COIN_HAS_MUMPS
       "mumps",
#      else
#       ifdef COINHSL_HAS_MA77
        "ma77",
#       else
        "ma27",
#       endif
#      endif
#     endif
#    endif
#   endif
#  endif
# endif
#endif
      "ma27", "use the Harwell routine MA27",
      "ma57", "use the Harwell routine MA57",
      "ma77", "use the Harwell routine HSL_MA77",
      "ma86", "use the Harwell routine HSL_MA86",
      "ma97", "use the Harwell routine HSL_MA97",
      "pardiso", "use the Pardiso package",
      "wsmp", "use WSMP package",
      "mumps", "use MUMPS package",
      "custom", "use custom linear solver",
      "Determines which linear algebra package is to be used for the "
      "solution of the augmented linear system (for obtaining the search "
      "directions). "
      "Note, the code must have been compiled with the linear solver you want "
      "to choose. Depending on your Ipopt installation, not all options are "
      "available.");
    roptions->SetRegisteringCategory("Linear Solver");
    roptions->AddStringOption3(
      "linear_system_scaling",
      "Method for scaling the linear system.",
#ifdef COINHSL_HAS_MC19
      "mc19",
#else
      "none",
#endif
      "none", "no scaling will be performed",
      "mc19", "use the Harwell routine MC19",
      "slack-based", "use the slack values",
      "Determines the method used to compute symmetric scaling "
      "factors for the augmented system (see also the "
      "\"linear_scaling_on_demand\" option).  This scaling is independent "
      "of the NLP problem scaling.  By default, MC19 is only used if MA27 or "
      "MA57 are selected as linear solvers. This value is only available if "
      "Ipopt has been compiled with MC19.");

    roptions->SetRegisteringCategory("NLP Scaling");
    roptions->AddStringOption4(
      "nlp_scaling_method",
      "Select the technique used for scaling the NLP.",
      "gradient-based",
      "none", "no problem scaling will be performed",
      "user-scaling", "scaling parameters will come from the user",
      "gradient-based", "scale the problem so the maximum gradient at the starting point is scaling_max_gradient",
      "equilibration-based", "scale the problem so that first derivatives are of order 1 at random points (only available with MC19)",
      "Selects the technique used for scaling the problem internally before it is solved."
      " For user-scaling, the parameters come from the NLP. If you are using "
      "AMPL, they can be specified through suffixes (\"scaling_factor\")");

    roptions->SetRegisteringCategory("Barrier Parameter Update");
    roptions->AddStringOption2(
      "mu_strategy",
      "Update strategy for barrier parameter.",
      "monotone",
      "monotone", "use the monotone (Fiacco-McCormick) strategy",
      "adaptive", "use the adaptive update strategy",
      "Determines which barrier parameter update strategy is to be used.");
    roptions->AddStringOption3(
      "mu_oracle",
      "Oracle for a new barrier parameter in the adaptive strategy.",
      "quality-function",
      "probing", "Mehrotra's probing heuristic",
      "loqo", "LOQO's centrality rule",
      "quality-function", "minimize a quality function",
      "Determines how a new barrier parameter is computed in each "
      "\"free-mode\" iteration of the adaptive barrier parameter "
      "strategy. (Only considered if \"adaptive\" is selected for "
      "option \"mu_strategy\").");
    roptions->AddStringOption4(
      "fixed_mu_oracle",
      "Oracle for the barrier parameter when switching to fixed mode.",
      "average_compl",
      "probing", "Mehrotra's probing heuristic",
      "loqo", "LOQO's centrality rule",
      "quality-function", "minimize a quality function",
      "average_compl", "base on current average complementarity",
      "Determines how the first value of the barrier parameter should be "
      "computed when switching to the \"monotone mode\" in the adaptive "
      "strategy. (Only considered if \"adaptive\" is selected for option "
      "\"mu_strategy\".)");

    roptions->SetRegisteringCategory("Hessian Approximation");
    roptions->AddStringOption2(
      "limited_memory_aug_solver",
      "Strategy for solving the augmented system for low-rank Hessian.",
      "sherman-morrison",
      "sherman-morrison", "use Sherman-Morrison formula",
      "extended", "use an extended augmented system",
      "");

    roptions->SetRegisteringCategory("Line Search");
    roptions->AddStringOption3(
      "line_search_method",
      "Globalization method used in backtracking line search",
      "filter",
      "filter", "Filter method",
      "cg-penalty", "Chen-Goldfarb penalty function",
      "penalty", "Standard penalty function",
      "Only the \"filter\" choice is officially supported.  But sometimes, "
      "good results might be obtained with the other choices.");
    roptions->SetRegisteringCategory("Undocumented");
    roptions->AddStringOption2(
      "wsmp_iterative",
      "Switches to iterative solver in WSMP.",
      "no",
      "no", "use direct solver",
      "yes", "use iterative solver",
      "EXPERIMENTAL!");
  }

  SmartPtr<SymLinearSolver>
  AlgorithmBuilder::GetSymLinearSolver(const Journalist& jnlst,
                                       const OptionsList& options,
                                       const std::string& prefix)
  {
    if (IsNull(SymSolver_)) {
      SymSolver_ = SymLinearSolverFactory(jnlst, options, prefix);
    }
    DBG_ASSERT(IsValid(SymSolver_));
    return SymSolver_;
  }

  SmartPtr<SymLinearSolver>
  AlgorithmBuilder::SymLinearSolverFactory(const Journalist& jnlst,
                                           const OptionsList& options,
                                           const std::string& prefix)
  {
    SmartPtr<SparseSymLinearSolverInterface> SolverInterface;
    std::string linear_solver;
    options.GetStringValue("linear_solver", linear_solver, prefix);
    if (linear_solver=="ma27") {
#ifndef COINHSL_HAS_MA27
# ifdef HAVE_LINEARSOLVERLOADER
      SolverInterface = new Ma27TSolverInterface();
      if (!LSL_isMA27available()) {
        char buf[256];
        int rc = LSL_loadHSL(NULL, buf, 255);
        if (rc) {
          std::string errmsg;
          errmsg = "Selected linear solver MA27 not available.\nTried to obtain MA27 from shared library \"";
          errmsg += LSL_HSLLibraryName();
          errmsg += "\", but the following error occured:\n";
          errmsg += buf;
          THROW_EXCEPTION(OPTION_INVALID, errmsg.c_str());
        }
      }
# else
      THROW_EXCEPTION(OPTION_INVALID, "Support for MA27 has not been compiled into Ipopt.");
# endif
#else
      SolverInterface = new Ma27TSolverInterface();
#endif

    }
    else if (linear_solver=="ma57") {
#ifndef COINHSL_HAS_MA57
# ifdef HAVE_LINEARSOLVERLOADER
      SolverInterface = new Ma57TSolverInterface();
      if (!LSL_isMA57available()) {
        char buf[256];
        int rc = LSL_loadHSL(NULL, buf, 255);
        if (rc) {
          std::string errmsg;
          errmsg = "Selected linear solver MA57 not available.\nTried to obtain MA57 from shared library \"";
          errmsg += LSL_HSLLibraryName();
          errmsg += "\", but the following error occured:\n";
          errmsg += buf;
          THROW_EXCEPTION(OPTION_INVALID, errmsg.c_str());
        }
      }
# else
      THROW_EXCEPTION(OPTION_INVALID, "Support for MA57 has not been compiled into Ipopt.");
# endif
#else
      SolverInterface = new Ma57TSolverInterface();
#endif

    }
    else if (linear_solver=="ma77") {
#ifndef COINHSL_HAS_MA77
# ifdef HAVE_LINEARSOLVERLOADER
      SolverInterface = new Ma77SolverInterface();
      if (!LSL_isMA77available()) {
        char buf[256];
        int rc = LSL_loadHSL(NULL, buf, 255);
        if (rc) {
          std::string errmsg;
          errmsg = "Selected linear solver HSL_MA77 not available.\nTried to obtain HSL_MA77 from shared library \"";
          errmsg += LSL_HSLLibraryName();
          errmsg += "\", but the following error occured:\n";
          errmsg += buf;
          THROW_EXCEPTION(OPTION_INVALID, errmsg.c_str());
        }
      }
# else
      THROW_EXCEPTION(OPTION_INVALID, "Support for HSL_MA77 has not been compiled into Ipopt.");
# endif
#else
      SolverInterface = new Ma77SolverInterface();
#endif

    }
    else if (linear_solver=="ma86") {
#ifndef COINHSL_HAS_MA86
# ifdef HAVE_LINEARSOLVERLOADER
      SolverInterface = new Ma86SolverInterface();
      if (!LSL_isMA86available()) {
        char buf[256];
        int rc = LSL_loadHSL(NULL, buf, 255);
        if (rc) {
          std::string errmsg;
          errmsg = "Selected linear solver HSL_MA86 not available.\nTried to obtain HSL_MA86 from shared library \"";
          errmsg += LSL_HSLLibraryName();
          errmsg += "\", but the following error occured:\n";
          errmsg += buf;
          THROW_EXCEPTION(OPTION_INVALID, errmsg.c_str());
        }
      }
# else
      THROW_EXCEPTION(OPTION_INVALID, "Support for HSL_MA86 has not been compiled into Ipopt.");
# endif
#else
      SolverInterface = new Ma86SolverInterface();
#endif

    }
    else if (linear_solver=="pardiso") {
#ifndef HAVE_PARDISO
# ifdef HAVE_LINEARSOLVERLOADER
      SolverInterface = new PardisoSolverInterface();
      char buf[256];
      int rc = LSL_loadPardisoLib(NULL, buf, 255);
      if (rc) {
        std::string errmsg;
        errmsg = "Selected linear solver Pardiso not available.\nTried to obtain Pardiso from shared library \"";
        errmsg += LSL_PardisoLibraryName();
        errmsg += "\", but the following error occured:\n";
        errmsg += buf;
        THROW_EXCEPTION(OPTION_INVALID, errmsg.c_str());
      }
# else
      THROW_EXCEPTION(OPTION_INVALID, "Support for Pardiso has not been compiled into Ipopt.");
# endif
#else
      SolverInterface = new PardisoSolverInterface();
#endif

    }
    else if (linear_solver=="ma97") {
#ifndef COINHSL_HAS_MA97
# ifdef HAVE_LINEARSOLVERLOADER
      SolverInterface = new Ma97SolverInterface();
      if (!LSL_isMA97available()) {
        char buf[256];
        int rc = LSL_loadHSL(NULL, buf, 255);
        if (rc) {
          std::string errmsg;
          errmsg = "Selected linear solver HSL_MA97 not available.\nTried to obtain HSL_MA97 from shared library \"";
          errmsg += LSL_HSLLibraryName();
          errmsg += "\", but the following error occured:\n";
          errmsg += buf;
          THROW_EXCEPTION(OPTION_INVALID, errmsg.c_str());
        }
      }
# else
      THROW_EXCEPTION(OPTION_INVALID, "Support for HSL_MA97 has not been compiled into Ipopt.");
# endif
#else
      SolverInterface = new Ma97SolverInterface();
#endif

    }
    else if (linear_solver=="wsmp") {
#ifdef HAVE_WSMP
      bool wsmp_iterative;
      options.GetBoolValue("wsmp_iterative", wsmp_iterative, prefix);
      if (wsmp_iterative) {
        SolverInterface = new IterativeWsmpSolverInterface();
      }
      else {
        SolverInterface = new WsmpSolverInterface();
      }
#else

      THROW_EXCEPTION(OPTION_INVALID,
                      "Selected linear solver WSMP not available.");
#endif

    }
    else if (linear_solver=="mumps") {
#ifdef COIN_HAS_MUMPS
      SolverInterface = new MumpsSolverInterface();
#else

      THROW_EXCEPTION(OPTION_INVALID,
                      "Selected linear solver MUMPS not available.");
#endif

    }
    else if (linear_solver=="custom") {
      SolverInterface = NULL;
    }

    SmartPtr<TSymScalingMethod> ScalingMethod;
    std::string linear_system_scaling;
    if (!options.GetStringValue("linear_system_scaling",
                                linear_system_scaling, prefix)) {
      // By default, don't use mc19 for non-HSL solvers, or HSL_MA97
      if (linear_solver!="ma27" && linear_solver!="ma57" && linear_solver!="ma77" && linear_solver!="ma86") {
        linear_system_scaling="none";
      }
    }
    if (linear_system_scaling=="mc19") {
#ifndef COINHSL_HAS_MC19
# ifdef HAVE_LINEARSOLVERLOADER
      ScalingMethod = new Mc19TSymScalingMethod();
      if (!LSL_isMC19available()) {
        char buf[256];
        int rc = LSL_loadHSL(NULL, buf, 255);
        if (rc) {
          std::string errmsg;
          errmsg = "Selected linear system scaling method MC19 not available.\n";
          errmsg += buf;
          THROW_EXCEPTION(OPTION_INVALID, errmsg.c_str());
        }
      }
# else
      THROW_EXCEPTION(OPTION_INVALID, "Support for MC19 has not been compiled into Ipopt.");
# endif
#else
      ScalingMethod = new Mc19TSymScalingMethod();
#endif

    }
    else if (linear_system_scaling=="slack-based") {
      ScalingMethod = new SlackBasedTSymScalingMethod();
    }

    SmartPtr<SymLinearSolver> ScaledSolver =
      new TSymLinearSolver(SolverInterface, ScalingMethod);
    return ScaledSolver;
  }

  SmartPtr<AugSystemSolver>
  AlgorithmBuilder::GetAugSystemSolver(const Journalist& jnlst,
                                       const OptionsList& options,
                                       const std::string& prefix)
  {
    if (IsNull(AugSolver_)) {
      AugSolver_ = AugSystemSolverFactory(jnlst, options, prefix);
    }
    DBG_ASSERT(IsValid(AugSolver_));
    return AugSolver_;
  }

  SmartPtr<AugSystemSolver>
  AlgorithmBuilder::AugSystemSolverFactory(const Journalist& jnlst,
                                           const OptionsList& options,
                                           const std::string& prefix)
  {
    SmartPtr<AugSystemSolver> AugSolver;
    std::string linear_solver;
    options.GetStringValue("linear_solver", linear_solver, prefix);
    if (linear_solver=="custom") {
      ASSERT_EXCEPTION(IsValid(custom_solver_), OPTION_INVALID,
                       "Selected linear solver CUSTOM not available.");
      AugSolver = custom_solver_;
    }
    else {
      AugSolver = new StdAugSystemSolver(*GetSymLinearSolver(jnlst, options, prefix));
    }

    Index enum_int;
    options.GetEnumValue("hessian_approximation", enum_int, prefix);
    HessianApproximationType hessian_approximation =
      HessianApproximationType(enum_int);
    if (hessian_approximation==LIMITED_MEMORY) {
      std::string lm_aug_solver;
      options.GetStringValue("limited_memory_aug_solver", lm_aug_solver,
                             prefix);
      if (lm_aug_solver == "sherman-morrison") {
        AugSolver = new LowRankAugSystemSolver(*AugSolver);
      }
      else if (lm_aug_solver == "extended") {
        Index lm_history;
        options.GetIntegerValue("limited_memory_max_history", lm_history,
                                prefix);
        Index max_rank;
        std::string lm_type;
        options.GetStringValue("limited_memory_update_type", lm_type, prefix);
        if (lm_type == "bfgs") {
          max_rank = 2*lm_history;
        }
        else if (lm_type == "sr1") {
          max_rank = lm_history;
        }
        else {
          THROW_EXCEPTION(OPTION_INVALID, "Unknown value for option \"limited_memory_update_type\".");
        }
        AugSolver = new LowRankSSAugSystemSolver(*AugSolver, max_rank);
      }
      else {
        THROW_EXCEPTION(OPTION_INVALID, "Unknown value for option \"limited_memory_aug_solver\".");
      }
    }
    return AugSolver;
  }

  SmartPtr<PDSystemSolver>
  AlgorithmBuilder::GetPDSystemSolver(const Journalist& jnlst,
                                      const OptionsList& options,
                                      const std::string& prefix)
  {
    if (IsNull(PDSolver_)) {
      PDSolver_ = PDSystemSolverFactory(jnlst, options, prefix);
    }
    DBG_ASSERT(IsValid(PDSolver_));
    return PDSolver_;
  }

  SmartPtr<PDSystemSolver>
  AlgorithmBuilder::PDSystemSolverFactory(const Journalist& jnlst,
                                          const OptionsList& options,
                                          const std::string& prefix)
  {
    SmartPtr<PDPerturbationHandler> pertHandler;
    std::string lsmethod;
    options.GetStringValue("line_search_method", lsmethod, prefix);
    if (lsmethod=="cg-penalty") {
      pertHandler = new CGPerturbationHandler();
    }
    else {
      pertHandler = new PDPerturbationHandler();
    }

    SmartPtr<PDSystemSolver> PDSolver =
      new PDFullSpaceSolver(*GetAugSystemSolver(jnlst, options, prefix),
                            *pertHandler);
    return PDSolver;
  }

  void AlgorithmBuilder::BuildIpoptObjects(const Journalist& jnlst,
                                           const OptionsList& options,
                                           const std::string& prefix,
                                           const SmartPtr<NLP>& nlp,
                                           SmartPtr<IpoptNLP>& ip_nlp,
                                           SmartPtr<IpoptData>& ip_data,
                                           SmartPtr<IpoptCalculatedQuantities>& ip_cq)
  {
    DBG_ASSERT(prefix == "");

    SmartPtr<NLPScalingObject> nlp_scaling;
    std::string nlp_scaling_method;
    options.GetStringValue("nlp_scaling_method", nlp_scaling_method, "");
    if (nlp_scaling_method == "user-scaling") {
      nlp_scaling = new UserScaling(ConstPtr(nlp));
    }
    else if (nlp_scaling_method == "gradient-based") {
      nlp_scaling = new GradientScaling(nlp);
    }
    else if (nlp_scaling_method == "equilibration-based") {
      nlp_scaling = new EquilibrationScaling(nlp);
    }
    else {
      nlp_scaling = new NoNLPScalingObject();
    }

    ip_nlp = new OrigIpoptNLP(&jnlst, GetRawPtr(nlp), nlp_scaling);

    // Create the IpoptData.  Check if there is additional data that
    // is needed
    std::string lsmethod;
    SmartPtr<IpoptAdditionalData> add_data;
    options.GetStringValue("line_search_method", lsmethod, prefix);
    if (lsmethod=="cg-penalty") {
      add_data = new CGPenaltyData();
    }
    ip_data = new IpoptData(add_data);

    // Create the IpoptCalculators.  Check if there are additional
    // calcluated quantities that are needed
    ip_cq = new IpoptCalculatedQuantities(ip_nlp, ip_data);
    if (lsmethod=="cg-penalty") {
      SmartPtr<IpoptAdditionalCq> add_cq =
        new CGPenaltyCq(GetRawPtr(ip_nlp), GetRawPtr(ip_data),
                        GetRawPtr(ip_cq));
      ip_cq->SetAddCq(add_cq);
    }
  }

  SmartPtr<IpoptAlgorithm>
  AlgorithmBuilder::BuildBasicAlgorithm(const Journalist& jnlst,
                                        const OptionsList& options,
                                        const std::string& prefix)
  {

    /* These three objects don't have any dependencies on other
     * other major components of the algorithm */
    IterOutput_ = BuildIterationOutput(jnlst, options, prefix);
    HessUpdater_ = BuildHessianUpdater(jnlst, options, prefix);
    ConvCheck_ = BuildConvergenceCheck(jnlst, options, prefix);

    /* All solver type factory methods are likely
     * first invoked here */
    SearchDirCalc_ = BuildSearchDirectionCalculator(jnlst, options, prefix);
    EqMultCalculator_ = BuildEqMultiplierCalculator(jnlst, options, prefix);

    /* Requires:
     *   -> EqMultCalculator_
     */
    IterInitializer_ = BuildIterateInitializer(jnlst, options, prefix);

    /* Requires:
     *   -> ConvCheck_
     *   -> EqMultCalculator_
     */
    LineSearch_ = BuildLineSearch(jnlst, options, prefix);

    /* Requires:
     *   -> LineSearch_
     *      -> ConvCheck_
     *      -> EqMultCalculator_
     */
    MuUpdate_ = BuildMuUpdate(jnlst, options, prefix);

    SmartPtr<IpoptAlgorithm> alg =
      new IpoptAlgorithm(SearchDirCalc_,
                         LineSearch_,
                         MuUpdate_,
                         ConvCheck_,
                         IterInitializer_,
                         IterOutput_,
                         HessUpdater_,
                         EqMultCalculator_);

    return alg;
  }

  SmartPtr<IterationOutput>
  AlgorithmBuilder::BuildIterationOutput(const Journalist& jnlst,
                                         const OptionsList& options,
                                         const std::string& prefix)
  {
    // Create the object for the iteration output
    SmartPtr<IterationOutput> IterOutput =
      new OrigIterationOutput();
    return IterOutput;
  }

  SmartPtr<HessianUpdater>
  AlgorithmBuilder::BuildHessianUpdater(const Journalist& jnlst,
                                        const OptionsList& options,
                                        const std::string& prefix)
  {
    // Get the Hessian updater for the main algorithm
    SmartPtr<HessianUpdater> HessUpdater;
    Index enum_int;
    options.GetEnumValue("hessian_approximation", enum_int, prefix);
    HessianApproximationType hessian_approximation =
      HessianApproximationType(enum_int);
    switch (hessian_approximation) {
    case EXACT:
      HessUpdater = new ExactHessianUpdater();
      break;
    case LIMITED_MEMORY:
      // ToDo This needs to be replaced!
      HessUpdater  = new LimMemQuasiNewtonUpdater(false);
      break;
    }
    return HessUpdater;
  }

  SmartPtr<ConvergenceCheck>
  AlgorithmBuilder::BuildConvergenceCheck(const Journalist& jnlst,
                                          const OptionsList& options,
                                          const std::string& prefix)
  {
    SmartPtr<ConvergenceCheck> ConvCheck =
      new OptimalityErrorConvergenceCheck();
    return ConvCheck;
  }

  SmartPtr<SearchDirectionCalculator>
  AlgorithmBuilder::BuildSearchDirectionCalculator(const Journalist& jnlst,
                                                   const OptionsList& options,
                                                   const std::string& prefix)
  {
    std::string lsmethod;
    options.GetStringValue("line_search_method", lsmethod, prefix);
    SmartPtr<SearchDirectionCalculator> SearchDirCalc;
    if (lsmethod=="cg-penalty") {
      SearchDirCalc = new CGSearchDirCalculator(GetRawPtr(GetPDSystemSolver(jnlst, options, prefix)));
    }
    else {
      SearchDirCalc = new PDSearchDirCalculator(GetRawPtr(GetPDSystemSolver(jnlst, options, prefix)));
    }
    return SearchDirCalc;
  }

  SmartPtr<EqMultiplierCalculator>
  AlgorithmBuilder::BuildEqMultiplierCalculator(const Journalist& jnlst,
                                                const OptionsList& options,
                                                const std::string& prefix)
  {
    SmartPtr<EqMultiplierCalculator> EqMultCalculator =
      new LeastSquareMultipliers(*GetAugSystemSolver(jnlst, options, prefix));
    return EqMultCalculator;
  }

  SmartPtr<IterateInitializer>
  AlgorithmBuilder::BuildIterateInitializer(const Journalist& jnlst,
                                            const OptionsList& options,
                                            const std::string& prefix)
  {
    DBG_ASSERT(IsValid(EqMultCalculator_));

    // Create the object for initializing the iterates Initialization
    // object.  We include both the warm start and the default
    // initializer, so that the warm start options can be activated
    // without having to rebuild the algorithm
    SmartPtr<IterateInitializer> WarmStartInitializer =
      new WarmStartIterateInitializer();

    SmartPtr<IterateInitializer> IterInitializer =
      new DefaultIterateInitializer(EqMultCalculator_, WarmStartInitializer,
                                    GetAugSystemSolver(jnlst, options, prefix));
    return IterInitializer;
  }

  SmartPtr<LineSearch>
  AlgorithmBuilder::BuildLineSearch(const Journalist& jnlst,
                                    const OptionsList& options,
                                    const std::string& prefix)
  {
    DBG_ASSERT(IsValid(ConvCheck_));
    DBG_ASSERT(IsValid(EqMultCalculator_));

    Index enum_int;
    options.GetEnumValue("hessian_approximation", enum_int, prefix);
    HessianApproximationType hessian_approximation =
      HessianApproximationType(enum_int);

    SmartPtr<RestorationPhase> resto_phase;
    SmartPtr<RestoConvergenceCheck> resto_convCheck;

    // We only need a restoration phase object if we use the filter
    // line search
    std::string lsmethod;
    options.GetStringValue("line_search_method", lsmethod, prefix);
    if (lsmethod=="filter" || lsmethod=="penalty") {
      // Solver for the restoration phase
      SmartPtr<AugSystemSolver> resto_AugSolver =
        new AugRestoSystemSolver(*GetAugSystemSolver(jnlst, options, prefix));
      SmartPtr<PDPerturbationHandler> resto_pertHandler =
        new PDPerturbationHandler();
      SmartPtr<PDSystemSolver> resto_PDSolver =
        new PDFullSpaceSolver(*resto_AugSolver, *resto_pertHandler);

      // Convergence check in the restoration phase
      if (lsmethod=="filter") {
        resto_convCheck = new RestoFilterConvergenceCheck();
      }
      else if (lsmethod=="penalty") {
        resto_convCheck = new RestoPenaltyConvergenceCheck();
      }

      // Line search method for the restoration phase
      SmartPtr<RestoRestorationPhase> resto_resto =
        new RestoRestorationPhase();

      SmartPtr<BacktrackingLSAcceptor> resto_LSacceptor;
      std::string resto_lsacceptor;
      options.GetStringValue("line_search_method", resto_lsacceptor,
                             "resto."+prefix);
      if (resto_lsacceptor=="filter") {
        resto_LSacceptor = new FilterLSAcceptor(GetRawPtr(resto_PDSolver));
      }
      else if (resto_lsacceptor=="cg-penalty") {
        resto_LSacceptor = new CGPenaltyLSAcceptor(GetRawPtr(resto_PDSolver));
      }
      else if (resto_lsacceptor=="penalty") {
        resto_LSacceptor = new PenaltyLSAcceptor(GetRawPtr(resto_PDSolver));
      }
      SmartPtr<LineSearch> resto_LineSearch =
        new BacktrackingLineSearch(resto_LSacceptor, GetRawPtr(resto_resto),
                                   GetRawPtr(resto_convCheck));

      // Create the mu update that will be used by the restoration phase
      // algorithm
      SmartPtr<MuUpdate> resto_MuUpdate;
      std::string resto_smuupdate;
      if (!options.GetStringValue("mu_strategy", resto_smuupdate, "resto."+prefix)) {
        // Change default for quasi-Newton option (then we use adaptive)
        if (hessian_approximation==LIMITED_MEMORY) {
          resto_smuupdate = "adaptive";
        }
      }

      std::string resto_smuoracle;
      std::string resto_sfixmuoracle;
      if (resto_smuupdate=="adaptive" ) {
        options.GetStringValue("mu_oracle", resto_smuoracle, "resto."+prefix);
        options.GetStringValue("fixed_mu_oracle", resto_sfixmuoracle, "resto."+prefix);
      }

      if (resto_smuupdate=="monotone" ) {
        resto_MuUpdate = new MonotoneMuUpdate(GetRawPtr(resto_LineSearch));
      }
      else if (resto_smuupdate=="adaptive") {
        SmartPtr<MuOracle> resto_MuOracle;
        if (resto_smuoracle=="loqo") {
          resto_MuOracle = new LoqoMuOracle();
        }
        else if (resto_smuoracle=="probing") {
          resto_MuOracle = new ProbingMuOracle(resto_PDSolver);
        }
        else if (resto_smuoracle=="quality-function") {
          resto_MuOracle = new QualityFunctionMuOracle(resto_PDSolver);
        }
        SmartPtr<MuOracle> resto_FixMuOracle;
        if (resto_sfixmuoracle=="loqo") {
          resto_FixMuOracle = new LoqoMuOracle();
        }
        else if (resto_sfixmuoracle=="probing") {
          resto_FixMuOracle = new ProbingMuOracle(resto_PDSolver);
        }
        else if (resto_sfixmuoracle=="quality-function") {
          resto_FixMuOracle = new QualityFunctionMuOracle(resto_PDSolver);
        }
        else {
          resto_FixMuOracle = NULL;
        }
        resto_MuUpdate =
          new AdaptiveMuUpdate(GetRawPtr(resto_LineSearch),
                               resto_MuOracle, resto_FixMuOracle);
      }

      // Initialization of the iterates for the restoration phase
      SmartPtr<EqMultiplierCalculator> resto_EqMultCalculator =
        new LeastSquareMultipliers(*resto_AugSolver);
      SmartPtr<IterateInitializer> resto_IterInitializer =
        new RestoIterateInitializer(resto_EqMultCalculator);

      // Create the object for the iteration output during restoration
      SmartPtr<OrigIterationOutput> resto_OrigIterOutput = NULL;
      //   new OrigIterationOutput();
      SmartPtr<IterationOutput> resto_IterOutput =
        new RestoIterationOutput(resto_OrigIterOutput);

      // Get the Hessian updater for the restoration phase
      SmartPtr<HessianUpdater> resto_HessUpdater;
      switch (hessian_approximation) {
      case EXACT:
        resto_HessUpdater = new ExactHessianUpdater();
        break;
      case LIMITED_MEMORY:
        // ToDo This needs to be replaced!
        resto_HessUpdater  = new LimMemQuasiNewtonUpdater(true);
        break;
      }

      // Put together the overall restoration phase IP algorithm
      SmartPtr<SearchDirectionCalculator> resto_SearchDirCalc;
      if (resto_lsacceptor=="cg-penalty") {
        resto_SearchDirCalc = new CGSearchDirCalculator(GetRawPtr(resto_PDSolver));
      }
      else {
        resto_SearchDirCalc = new PDSearchDirCalculator(GetRawPtr(resto_PDSolver));
      }

      SmartPtr<IpoptAlgorithm> resto_alg =
        new IpoptAlgorithm(resto_SearchDirCalc,
                           GetRawPtr(resto_LineSearch),
                           GetRawPtr(resto_MuUpdate),
                           GetRawPtr(resto_convCheck),
                           resto_IterInitializer,
                           resto_IterOutput,
                           resto_HessUpdater,
                           resto_EqMultCalculator);

      // Set the restoration phase
      resto_phase =
        new MinC_1NrmRestorationPhase(*resto_alg, EqMultCalculator_);
    }

    // Create the line search to be used by the main algorithm
    SmartPtr<BacktrackingLSAcceptor> LSacceptor;
    if (lsmethod=="filter") {
      LSacceptor = new FilterLSAcceptor(GetRawPtr(GetPDSystemSolver(jnlst, options, prefix)));
    }
    else if (lsmethod=="cg-penalty") {
      LSacceptor = new CGPenaltyLSAcceptor(GetRawPtr(GetPDSystemSolver(jnlst, options, prefix)));
    }
    else if (lsmethod=="penalty") {
      LSacceptor = new PenaltyLSAcceptor(GetRawPtr(GetPDSystemSolver(jnlst, options, prefix)));
    }
    SmartPtr<LineSearch> LineSearch =
      new BacktrackingLineSearch(LSacceptor,
                                 GetRawPtr(resto_phase), ConvCheck_);

    // The following cross reference is not good: We have to store a
    // pointer to the LineSearch_ object in resto_convCheck as a
    // non-SmartPtr to make sure that things are properly deleted when
    // the IpoptAlgorithm returned by the Builder is destructed.
    if (IsValid(resto_convCheck)) {
      resto_convCheck->SetOrigLSAcceptor(*LSacceptor);
    }

    return LineSearch;
  }

  SmartPtr<MuUpdate>
  AlgorithmBuilder::BuildMuUpdate(const Journalist& jnlst,
                                  const OptionsList& options,
                                  const std::string& prefix)
  {
    DBG_ASSERT(IsValid(LineSearch_));

    bool mehrotra_algorithm;
    options.GetBoolValue("mehrotra_algorithm", mehrotra_algorithm, prefix);

    // Create the mu update that will be used by the main algorithm
    SmartPtr<MuUpdate> MuUpdate;
    std::string smuupdate;
    if (!options.GetStringValue("mu_strategy", smuupdate, prefix)) {
      // Change default for quasi-Newton option (then we use adaptive)
      Index enum_int;
      if (options.GetEnumValue("hessian_approximation", enum_int, prefix)) {
        HessianApproximationType hessian_approximation =
          HessianApproximationType(enum_int);
        if (hessian_approximation==LIMITED_MEMORY) {
          smuupdate = "adaptive";
        }
      }
      if (mehrotra_algorithm)
        smuupdate = "adaptive";
    }
    ASSERT_EXCEPTION(!mehrotra_algorithm || smuupdate=="adaptive",
                     OPTION_INVALID,
                     "If mehrotra_algorithm=yes, mu_strategy must be \"adaptive\".");
    std::string smuoracle;
    std::string sfixmuoracle;
    if (smuupdate=="adaptive" ) {
      if (!options.GetStringValue("mu_oracle", smuoracle, prefix)) {
        if (mehrotra_algorithm)
          smuoracle = "probing";
      }
      options.GetStringValue("fixed_mu_oracle", sfixmuoracle, prefix);
      ASSERT_EXCEPTION(!mehrotra_algorithm || smuoracle=="probing",
                       OPTION_INVALID,
                       "If mehrotra_algorithm=yes, mu_oracle must be \"probing\".");
    }

    if (smuupdate=="monotone" ) {
      MuUpdate = new MonotoneMuUpdate(GetRawPtr(LineSearch_));
    }
    else if (smuupdate=="adaptive") {
      SmartPtr<MuOracle> muOracle;
      if (smuoracle=="loqo") {
        muOracle = new LoqoMuOracle();
      }
      else if (smuoracle=="probing") {
        muOracle = new ProbingMuOracle(GetPDSystemSolver(jnlst, options, prefix));
      }
      else if (smuoracle=="quality-function") {
        muOracle = new QualityFunctionMuOracle(GetPDSystemSolver(jnlst, options, prefix));
      }
      SmartPtr<MuOracle> FixMuOracle;
      if (sfixmuoracle=="loqo") {
        FixMuOracle = new LoqoMuOracle();
      }
      else if (sfixmuoracle=="probing") {
        FixMuOracle = new ProbingMuOracle(GetPDSystemSolver(jnlst, options, prefix));
      }
      else if (sfixmuoracle=="quality-function") {
        FixMuOracle = new QualityFunctionMuOracle(GetPDSystemSolver(jnlst, options, prefix));
      }
      else {
        FixMuOracle = NULL;
      }
      MuUpdate = new AdaptiveMuUpdate(GetRawPtr(LineSearch_),
                                      muOracle, FixMuOracle);
    }
    return MuUpdate;
  }

} // namespace
