/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                       */
/*    This file is part of the HiGHS linear optimization suite           */
/*                                                                       */
/*    Available as open-source under the MIT License                     */
/*                                                                       */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**@file simplex/HighsMipAnalysis.cpp
 * @brief
 */
#include "mip/HighsMipAnalysis.h"

#include <cmath>

#include "mip/HighsSeparator.h"
#include "mip/MipTimer.h"
#include "util/HighsUtils.h"

const HighsInt check_mip_clock = -4;

void HighsMipAnalysis::setup(const HighsLp& lp, const HighsOptions& options) {
  model_name = lp.model_name_;
  setupMipTime(options);
}

void HighsMipAnalysis::setupMipTime(const HighsOptions& options) {
  this->sub_solver_call_time_->initialise();
  analyse_mip_time = kHighsAnalysisLevelMipTime & options.highs_analysis_level;
  if (analyse_mip_time) {
    HighsTimerClock clock;
    clock.timer_pointer_ = timer_;
    MipTimer mip_timer;
    mip_timer.initialiseMipClocks(clock);
    mip_clocks = clock;
    sepa_name_clock.push_back(
        std::make_pair(kImplboundSepaString, kMipClockImplboundSepa));
    sepa_name_clock.push_back(
        std::make_pair(kCliqueSepaString, kMipClockCliqueSepa));
    sepa_name_clock.push_back(
        std::make_pair(kTableauSepaString, kMipClockTableauSepa));
    sepa_name_clock.push_back(
        std::make_pair(kPathAggrSepaString, kMipClockPathAggrSepa));
    sepa_name_clock.push_back(
        std::make_pair(kModKSepaString, kMipClockModKSepa));
  }
}

void HighsMipAnalysis::mipTimerStart(const HighsInt mip_clock
                                     // , const HighsInt thread_id
) const {
  if (!analyse_mip_time) return;
  HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock];
  if (highs_timer_clock == check_mip_clock) {
    std::string clock_name =
        mip_clocks.timer_pointer_->clock_names[check_mip_clock];
    printf("MipTimer: starting clock %d: %s\n", int(check_mip_clock),
           clock_name.c_str());
  }
  mip_clocks.timer_pointer_->start(highs_timer_clock);
}

void HighsMipAnalysis::mipTimerStop(const HighsInt mip_clock
                                    // , const HighsInt thread_id
) const {
  if (!analyse_mip_time) return;
  HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock];
  if (highs_timer_clock == check_mip_clock) {
    std::string clock_name =
        mip_clocks.timer_pointer_->clock_names[check_mip_clock];
    printf("MipTimer: stopping clock %d: %s\n", int(check_mip_clock),
           clock_name.c_str());
  }
  mip_clocks.timer_pointer_->stop(highs_timer_clock);
}

bool HighsMipAnalysis::mipTimerRunning(const HighsInt mip_clock
                                       // , const HighsInt thread_id
) const {
  if (!analyse_mip_time) return false;
  HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock];
  return mip_clocks.timer_pointer_->running(highs_timer_clock);
}

double HighsMipAnalysis::mipTimerRead(const HighsInt mip_clock
                                      // , const HighsInt thread_id
) const {
  if (!analyse_mip_time) return 0;
  HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock];
  return mip_clocks.timer_pointer_->read(highs_timer_clock);
}

HighsInt HighsMipAnalysis::mipTimerNumCall(const HighsInt mip_clock
                                           // , const HighsInt thread_id
) const {
  if (!analyse_mip_time) return 0;
  HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock];
  return mip_clocks.timer_pointer_->numCall(highs_timer_clock);
}

void HighsMipAnalysis::mipTimerAdd(const HighsInt mip_clock,
                                   const HighsInt num_call, const double time
                                   // , const HighsInt thread_id
) const {
  if (!analyse_mip_time) return;
  if (num_call == 0) {
    assert(time == 0);
    return;
  }
  HighsInt highs_timer_clock = mip_clocks.clock_[mip_clock];
  mip_clocks.timer_pointer_->add(highs_timer_clock, num_call, time);
}

void HighsMipAnalysis::mipTimerUpdate(
    const HighsSubSolverCallTime& sub_solver_call_time, const bool valid_basis,
    const bool presolve, const bool analytic_centre
    // , const HighsInt thread_id
) const {
  if (!analyse_mip_time) return;
  // If IPM has been run first, then there may be a valid basis for
  // simplex
  const bool run_ipm = sub_solver_call_time.num_call[kSubSolverHipo] > 0 ||
                       sub_solver_call_time.num_call[kSubSolverIpx] > 0;
  if (!run_ipm) {
    // If IPM has not been run first, then check that simplex call
    // counts are consistent with valid_basis and presolve
    if (valid_basis) {
      assert(sub_solver_call_time.num_call[kSubSolverDuSimplexNoBasis] == 0);
    } else if (!presolve) {
      assert(sub_solver_call_time.num_call[kSubSolverDuSimplexBasis] == 0);
    }
  } else {
    // IPM has been run first, then at least one of the simplex call
    // counts should be zero
    assert(sub_solver_call_time.num_call[kSubSolverDuSimplexBasis] == 0 ||
           sub_solver_call_time.num_call[kSubSolverDuSimplexNoBasis] == 0);
  }

  mipTimerAdd(kMipClockDuSimplexBasisSolveLp,
              sub_solver_call_time.num_call[kSubSolverDuSimplexBasis],
              sub_solver_call_time.run_time[kSubSolverDuSimplexBasis]);
  mipTimerAdd(kMipClockDuSimplexNoBasisSolveLp,
              sub_solver_call_time.num_call[kSubSolverDuSimplexNoBasis],
              sub_solver_call_time.run_time[kSubSolverDuSimplexNoBasis]);
  mipTimerAdd(kMipClockPrSimplexBasisSolveLp,
              sub_solver_call_time.num_call[kSubSolverPrSimplexBasis],
              sub_solver_call_time.run_time[kSubSolverPrSimplexBasis]);
  mipTimerAdd(kMipClockPrSimplexNoBasisSolveLp,
              sub_solver_call_time.num_call[kSubSolverPrSimplexNoBasis],
              sub_solver_call_time.run_time[kSubSolverPrSimplexNoBasis]);

  if (sub_solver_call_time.num_call[kSubSolverHipo]) {
    const HighsInt mip_clock = analytic_centre
                                   ? kMipClockHipoSolveAnalyticCentreLp
                                   : kMipClockHipoSolveLp;
    mipTimerAdd(mip_clock, sub_solver_call_time.num_call[kSubSolverHipo],
                sub_solver_call_time.run_time[kSubSolverHipo]);
  }
  if (sub_solver_call_time.num_call[kSubSolverIpx]) {
    const HighsInt mip_clock = analytic_centre
                                   ? kMipClockIpxSolveAnalyticCentreLp
                                   : kMipClockIpxSolveLp;
    mipTimerAdd(mip_clock, sub_solver_call_time.num_call[kSubSolverIpx],
                sub_solver_call_time.run_time[kSubSolverIpx]);
  }
  assert(sub_solver_call_time.num_call[kSubSolverMip] == 0);
  assert(sub_solver_call_time.num_call[kSubSolverPdlp] == 0);
  assert(sub_solver_call_time.num_call[kSubSolverQpAsm] == 0);
  assert(sub_solver_call_time.num_call[kSubSolverSubMip] == 0);
}

void HighsMipAnalysis::reportMipSolveLpClock(const bool header) {
  if (header) {
    printf(
        ",simplex time,IPM time,#simplex,#IPM,simplex/total time,IPM/total "
        "time,#No basis solve,simplex/#Basis solve,simplex/#No basis solve\n");
    return;
  }
  if (!analyse_mip_time) return;
  double total_time = mip_clocks.timer_pointer_->read(0);
  if (total_time < 0.01) return;
  HighsInt simplex_basis_solve_iclock =
      mip_clocks.clock_[kMipClockDuSimplexBasisSolveLp];
  HighsInt simplex_no_basis_solve_iclock =
      mip_clocks.clock_[kMipClockDuSimplexNoBasisSolveLp];
  HighsInt ipm_solve_iclock = mip_clocks.clock_[kMipClockIpxSolveLp];
  //  HighsInt num_no_basis_solve =
  //  mip_clocks.timer_pointer_->clock_num_call[no_basis_solve_iclock]; HighsInt
  //  num_basis_solve =
  //  mip_clocks.timer_pointer_->clock_num_call[basis_solve_iclock];
  HighsInt num_simplex_basis_solve =
      mip_clocks.timer_pointer_->clock_num_call[simplex_basis_solve_iclock];
  HighsInt num_simplex_no_basis_solve =
      mip_clocks.timer_pointer_->clock_num_call[simplex_no_basis_solve_iclock];
  HighsInt num_ipm_solve =
      mip_clocks.timer_pointer_->clock_num_call[ipm_solve_iclock];
  HighsInt num_simplex_solve =
      num_simplex_basis_solve + num_simplex_no_basis_solve;
  //  assert(num_no_basis_solve+num_basis_solve == num_simplex_solve);
  double simplex_basis_solve_time =
      mip_clocks.timer_pointer_->read(simplex_basis_solve_iclock);
  double simplex_no_basis_solve_time =
      mip_clocks.timer_pointer_->read(simplex_no_basis_solve_iclock);
  double simplex_solve_time =
      simplex_basis_solve_time + simplex_no_basis_solve_time;
  double ipm_solve_time = mip_clocks.timer_pointer_->read(ipm_solve_iclock);
  double frac_simplex_solve_time = simplex_solve_time / total_time;
  double frac_ipm_solve_time = ipm_solve_time / total_time;
  double average_simplex_basis_solve_time =
      num_simplex_basis_solve > 0
          ? simplex_basis_solve_time / int(num_simplex_basis_solve)
          : 0.0;
  double average_simplex_no_basis_solve_time =
      num_simplex_no_basis_solve > 0
          ? simplex_no_basis_solve_time / int(num_simplex_no_basis_solve)
          : 0.0;
  printf(",%11.2g,%11.2g,%d,%d,%11.2g,%11.2g,%d,%11.2g,%11.2g\n",
         simplex_solve_time, ipm_solve_time, int(num_simplex_solve),
         int(num_ipm_solve), frac_simplex_solve_time, frac_ipm_solve_time,
         int(num_simplex_no_basis_solve), average_simplex_basis_solve_time,
         average_simplex_no_basis_solve_time);
  printf(
      "LP solver analysis: %d LP with %d simplex (%11.2g CPU), %d IPM (%11.2g "
      "CPU) and %d solved without basis; average simplex solve time "
      "(basis/no_basis) = (%11.2g, %11.2g)\n",
      int(num_simplex_solve + num_ipm_solve), int(num_simplex_solve),
      simplex_solve_time, int(num_ipm_solve), ipm_solve_time,
      int(num_simplex_no_basis_solve), average_simplex_basis_solve_time,
      average_simplex_no_basis_solve_time);
};

void HighsMipAnalysis::reportMipTimer() {
  if (!analyse_mip_time) return;
  MipTimer mip_timer;
  mip_timer.reportMipCoreClock(mip_clocks);
  mip_timer.reportMipLevel1Clock(mip_clocks);
  mip_timer.reportMipEvaluateRootNodeClock(mip_clocks);
  //  mip_timer.reportAltEvaluateRootNodeClock(mip_clocks);
  //  mip_timer.reportMipPresolveClock(mip_clocks);
  //  mip_timer.reportMipRootSeparationClock(mip_clocks);
  //  mip_timer.reportMipSearchClock(mip_clocks);
  //  mip_timer.reportMipDiveClock(mip_clocks);
  //  mip_timer.reportMipNodeSearchClock(mip_clocks);
  //  mip_timer.reportMipDivePrimalHeuristicsClock(mip_clocks);
  mip_timer.reportMipSubMipSolveClock(mip_clocks);
  mip_timer.reportMipSeparationClock(mip_clocks);
  mip_timer.reportMipSolveLpClock(mip_clocks);
  //  mip_timer.csvMipClock(this->model_name, mip_clocks, true, false);
  //  reportMipSolveLpClock(true);
  //
  //  mip_timer.csvMipClock(this->model_name, mip_clocks, false, false);
  //  reportMipSolveLpClock(false);
  //
  //  mip_timer.csvEvaluateRootNodeClock(this->model_name, mip_clocks, true,
  //  true);
  //
  //  mip_timer.csvEvaluateRootNodeClock(this->model_name, mip_clocks, false,
  //  true);
  //
  //  analyseVectorValues(nullptr, "Node search time",
  //                      HighsInt(node_search_time.size()), node_search_time);
  //
  //  analyseVectorValues(nullptr, "Dive time", HighsInt(dive_time.size()),
  //                      dive_time);
  // mip_timer.reportFjClock(this->model_name, mip_clocks);
}

HighsInt HighsMipAnalysis::getSepaClockIndex(const std::string& name) const {
  HighsInt num_sepa_clock = this->sepa_name_clock.size();
  assert(num_sepa_clock > 0);
  for (HighsInt iSepaClock = 0; iSepaClock < num_sepa_clock; iSepaClock++) {
    if (this->sepa_name_clock[iSepaClock].first == name)
      return this->sepa_name_clock[iSepaClock].second;
  }
  return -1;
}

void HighsMipAnalysis::addSubSolverCallTime(
    const HighsSubSolverCallTime& sub_solver_call_time,
    const bool analytic_centre) const {
  this->sub_solver_call_time_->add(sub_solver_call_time, analytic_centre);
}

void HighsMipAnalysis::checkSubSolverCallTime(
    const HighsSubSolverCallTime& sub_solver_call_time) {
  if (!analyse_mip_time) return;
  const bool printf_flag = mip_clocks.timer_pointer_->printf_flag;
  bool error = false;
  auto check = [&](const HighsInt& sub_solver_clock,
                   const HighsInt& mip_clock) {
    HighsInt sub_solver_num_call =
        sub_solver_call_time.num_call[sub_solver_clock];
    HighsInt mip_clock_num_call =
        mip_clocks.timer_pointer_->numCall(mip_clocks.clock_[mip_clock]);
    const bool ok = sub_solver_num_call == mip_clock_num_call;
    if (!ok) {
      if (printf_flag)
        printf("HighsMipAnalysis::checkSubSolverCallTime: Error for %s\n",
               sub_solver_call_time.name[sub_solver_clock].c_str());
      error = true;
    }
  };
  check(kSubSolverDuSimplexBasis, kMipClockDuSimplexBasisSolveLp);
  check(kSubSolverDuSimplexNoBasis, kMipClockDuSimplexNoBasisSolveLp);
  check(kSubSolverHipo, kMipClockHipoSolveLp);
  check(kSubSolverIpx, kMipClockIpxSolveLp);
  check(kSubSolverHipoAc, kMipClockHipoSolveAnalyticCentreLp);
  check(kSubSolverIpxAc, kMipClockIpxSolveAnalyticCentreLp);
  check(kSubSolverSubMip, kMipClockSubMipSolve);
  if (printf_flag)
    printf("\nHighsMipAnalysis::checkSubSolverCallTime: %s\n",
           error ? "ERROR!" : "OK");
  assert(!error);
}
