/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                       */
/*    This file is part of the HiGHS linear optimization suite           */
/*                                                                       */
/*    Written and engineered 2008-2022 at the University of Edinburgh    */
/*                                                                       */
/*    Available as open-source under the MIT License                     */
/*                                                                       */
/*    Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael    */
/*    Feldmeier                                                          */
/*                                                                       */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**@file util/HFactor.cpp
 * @brief Types of solution classes
 */
#include "util/HFactor.h"

#include <cassert>
#include <iostream>

#include "lp_data/HConst.h"
#include "pdqsort/pdqsort.h"
#include "util/FactorTimer.h"
#include "util/HFactorDebug.h"
#include "util/HVector.h"
#include "util/HVectorBase.h"
#include "util/HighsTimer.h"

// std::vector, std::max and std::min used in HFactor.h for local
// in-line functions, so HFactor.h has #include <algorithm>
using std::fabs;

using std::copy;
using std::fill_n;
using std::make_pair;
using std::min;
using std::pair;

static void solveMatrixT(const HighsInt X_Start, const HighsInt x_end,
                         const HighsInt y_start, const HighsInt y_end,
                         const HighsInt* t_index, const double* t_value,
                         const double t_pivot, HighsInt* rhs_count,
                         HighsInt* rhs_index, double* rhs_array) {
  // Collect by X
  double pivot_multiplier = 0;
  for (HighsInt k = X_Start; k < x_end; k++)
    pivot_multiplier += t_value[k] * rhs_array[t_index[k]];

  // Scatter by Y
  if (fabs(pivot_multiplier) > kHighsTiny) {
    HighsInt work_count = *rhs_count;

    pivot_multiplier /= t_pivot;
    for (HighsInt k = y_start; k < y_end; k++) {
      const HighsInt index = t_index[k];
      const double value0 = rhs_array[index];
      const double value1 = value0 - pivot_multiplier * t_value[k];
      if (value0 == 0) rhs_index[work_count++] = index;
      rhs_array[index] = (fabs(value1) < kHighsTiny) ? kHighsZero : value1;
    }

    *rhs_count = work_count;
  }
}

static void solveHyper(const HighsInt h_size, const HighsInt* h_lookup,
                       const HighsInt* h_pivot_index,
                       const double* h_pivot_value, const HighsInt* h_start,
                       const HighsInt* h_end, const HighsInt* h_index,
                       const double* h_value, HVector* rhs) {
  HighsInt rhs_count = rhs->count;
  HighsInt* rhs_index = &rhs->index[0];
  double* rhs_array = &rhs->array[0];

  // Take count

  // Build list
  char* list_mark = &rhs->cwork[0];
  HighsInt* list_index = &rhs->iwork[0];
  HighsInt* list_stack = &rhs->iwork[h_size];
  HighsInt list_count = 0;

  HighsInt count_pivot = 0;
  HighsInt count_entry = 0;

  for (HighsInt i = 0; i < rhs_count; i++) {
    // Skip touched index
    HighsInt i_trans =
        h_lookup[rhs_index[i]];  // XXX: this contains a bug iTran
    if (list_mark[i_trans])      // XXX bug here
      continue;

    HighsInt Hi = i_trans;      // H matrix pivot index
    HighsInt Hk = h_start[Hi];  // H matrix non zero position
    HighsInt n_stack = -1;      // Usage of the stack (-1 not used)

    list_mark[Hi] = 1;  // Mark this as touched

    for (;;) {
      if (Hk < h_end[Hi]) {
        HighsInt Hi_sub = h_lookup[h_index[Hk++]];
        if (list_mark[Hi_sub] == 0) {  // Go to a child
          list_mark[Hi_sub] = 1;       // Mark as touched
          list_stack[++n_stack] = Hi;  // Store current into stack
          list_stack[++n_stack] = Hk;
          Hi = Hi_sub;  // Replace current with child
          Hk = h_start[Hi];
          if (Hi >= h_size) {
            count_pivot++;
            count_entry += h_end[Hi] - h_start[Hi];
          }
        }
      } else {
        list_index[list_count++] = Hi;
        if (n_stack == -1)  // Quit on empty stack
          break;
        Hk = list_stack[n_stack--];  // Back to last in stack
        Hi = list_stack[n_stack--];
      }
    }
  }

  rhs->synthetic_tick += count_pivot * 20 + count_entry * 10;

  // Solve with list
  if (h_pivot_value == 0) {
    rhs_count = 0;
    for (HighsInt iList = list_count - 1; iList >= 0; iList--) {
      HighsInt i = list_index[iList];
      list_mark[i] = 0;
      HighsInt pivotRow = h_pivot_index[i];
      double pivot_multiplier = rhs_array[pivotRow];
      if (fabs(pivot_multiplier) > kHighsTiny) {
        rhs_index[rhs_count++] = pivotRow;
        const HighsInt start = h_start[i];
        const HighsInt end = h_end[i];
        for (HighsInt k = start; k < end; k++)
          rhs_array[h_index[k]] -= pivot_multiplier * h_value[k];
      } else
        rhs_array[pivotRow] = 0;
    }
    rhs->count = rhs_count;
  } else {
    rhs_count = 0;
    for (HighsInt iList = list_count - 1; iList >= 0; iList--) {
      HighsInt i = list_index[iList];
      list_mark[i] = 0;
      HighsInt pivotRow = h_pivot_index[i];
      double pivot_multiplier = rhs_array[pivotRow];
      if (fabs(pivot_multiplier) > kHighsTiny) {
        pivot_multiplier /= h_pivot_value[i];
        rhs_array[pivotRow] = pivot_multiplier;
        rhs_index[rhs_count++] = pivotRow;
        const HighsInt start = h_start[i];
        const HighsInt end = h_end[i];
        for (HighsInt k = start; k < end; k++)
          rhs_array[h_index[k]] -= pivot_multiplier * h_value[k];
      } else
        rhs_array[pivotRow] = 0;
    }
    rhs->count = rhs_count;
  }
}

void HFactor::setup(const HighsSparseMatrix& a_matrix,
                    std::vector<HighsInt>& basic_index,
                    const double pivot_threshold, const double pivot_tolerance,
                    const HighsInt highs_debug_level,
                    const HighsLogOptions* log_options) {
  HighsInt basic_index_size = basic_index.size();
  // Nothing to do if basic index has no entries, and mustn't try to
  // pass the pointer to entry 0 of a vector of size 0.
  if (basic_index_size <= 0) return;
  this->setupGeneral(&a_matrix, basic_index_size, &basic_index[0],
                     pivot_threshold, pivot_tolerance, highs_debug_level,
                     log_options);
  return;
}

void HFactor::setupGeneral(const HighsSparseMatrix* a_matrix,
                           HighsInt num_basic, HighsInt* basic_index,
                           const double pivot_threshold,
                           const double pivot_tolerance,
                           const HighsInt highs_debug_level,
                           const HighsLogOptions* log_options) {
  this->setupGeneral(
      a_matrix->num_col_, a_matrix->num_row_, num_basic, &a_matrix->start_[0],
      &a_matrix->index_[0], &a_matrix->value_[0], basic_index, pivot_threshold,
      pivot_tolerance, highs_debug_level, log_options, true, kUpdateMethodFt);
}

void HFactor::setup(
    const HighsInt num_col_, const HighsInt num_row_, const HighsInt* a_start_,
    const HighsInt* a_index_, const double* a_value_, HighsInt* basic_index_,
    const double pivot_threshold_, const double pivot_tolerance_,
    const HighsInt highs_debug_level_, const HighsLogOptions* log_options_,
    const bool use_original_HFactor_logic_, const HighsInt update_method_) {
  setupGeneral(num_col_, num_row_, num_row_, a_start_, a_index_, a_value_,
               basic_index_, pivot_threshold_, pivot_tolerance_,
               highs_debug_level_, log_options_, use_original_HFactor_logic_,
               update_method_);
}

void HFactor::setupGeneral(
    const HighsInt num_col_, const HighsInt num_row_, HighsInt num_basic_,
    const HighsInt* a_start_, const HighsInt* a_index_, const double* a_value_,
    HighsInt* basic_index_, const double pivot_threshold_,
    const double pivot_tolerance_, const HighsInt highs_debug_level_,
    const HighsLogOptions* log_options_, const bool use_original_HFactor_logic_,
    const HighsInt update_method_) {
  // Copy Problem size and (pointer to) coefficient matrix
  num_row = num_row_;
  num_col = num_col_;
  num_basic = num_basic_;
  this->a_matrix_valid = true;
  a_start = a_start_;
  a_index = a_index_;
  a_value = a_value_;
  basic_index = basic_index_;
  pivot_threshold =
      max(kMinPivotThreshold, min(pivot_threshold_, kMaxPivotThreshold));
  pivot_tolerance =
      max(kMinPivotTolerance, min(pivot_tolerance_, kMaxPivotTolerance));
  highs_debug_level = highs_debug_level_;
  time_limit_ = kHighsInf;
  log_data = decltype(log_data)(new LogData());
  log_options.output_flag = &log_data->output_flag;
  log_options.log_to_console = &log_data->log_to_console;
  log_options.log_dev_level = &log_data->log_dev_level;

  if (!log_options_) {
    log_data->output_flag = false;
    log_data->log_to_console = true;
    log_data->log_dev_level = 0;
    log_options.log_file_stream = nullptr;
  } else {
    log_data->output_flag = *(log_options_->output_flag);
    log_data->log_to_console = *(log_options_->log_to_console);
    log_data->log_dev_level = *(log_options_->log_dev_level);
    log_options.log_file_stream = log_options_->log_file_stream;
  }

  use_original_HFactor_logic = use_original_HFactor_logic_;
  update_method = update_method_;

  // Allocate for working buffer
  iwork.reserve(num_row * 2);
  dwork.assign(num_row, 0);

  // Find Basis matrix limit size
  basis_matrix_limit_size = 0;

  iwork.assign(num_row + 1, 0);
  for (HighsInt i = 0; i < num_col; i++) iwork[a_start[i + 1] - a_start[i]]++;
  const HighsInt b_max_dim = max(num_row, num_basic);
  for (HighsInt i = num_row, counted = 0; i >= 0 && counted < b_max_dim; i--)
    basis_matrix_limit_size += i * iwork[i], counted += iwork[i];
  basis_matrix_limit_size += b_max_dim;

  // Allocate space for basis matrix, L, U factor and Update buffer
  b_var.resize(b_max_dim);
  b_start.resize(b_max_dim + 1, 0);
  b_index.resize(basis_matrix_limit_size);
  b_value.resize(basis_matrix_limit_size);

  // Allocate space for pivot records
  const HighsInt permute_max_dim = max(num_row, num_basic);
  permute.resize(permute_max_dim);

  // Allocate space for Markowitz matrices
  const HighsInt mc_dim = num_basic;
  mc_var.resize(mc_dim);
  mc_start.resize(mc_dim);
  mc_count_a.resize(mc_dim);
  mc_count_n.resize(mc_dim);
  mc_space.resize(mc_dim);
  mc_min_pivot.resize(mc_dim);
  mc_index.resize(basis_matrix_limit_size * kMCExtraEntriesMultiplier);
  mc_value.resize(basis_matrix_limit_size * kMCExtraEntriesMultiplier);

  mr_start.resize(num_row);
  mr_count.resize(num_row);
  mr_space.resize(num_row);
  mr_count_before.resize(num_row);
  mr_index.resize(basis_matrix_limit_size * kMRExtraEntriesMultiplier);

  mwz_column_mark.assign(num_row, 0);
  mwz_column_index.resize(num_row);
  mwz_column_array.assign(num_row, 0);

  // Allocate space for count-link-list
  const HighsInt col_link_max_count = num_row;
  const HighsInt col_link_dim = num_basic;
  col_link_first.assign(col_link_max_count + 1, -1);
  col_link_next.resize(col_link_dim);
  col_link_last.resize(col_link_dim);

  const HighsInt row_link_max_count = num_basic;
  const HighsInt row_link_dim = num_row;
  // Strictly not necessary, but nice if it's the right size
  row_link_first.resize(row_link_max_count + 1);
  row_link_first.assign(row_link_max_count + 1, -1);
  row_link_next.resize(row_link_dim);
  row_link_last.resize(row_link_dim);

  // Allocate space for L factor
  l_pivot_lookup.resize(num_row);
  l_pivot_index.reserve(num_row);
  l_start.reserve(num_row + 1);
  l_index.reserve(basis_matrix_limit_size * kLFactorExtraEntriesMultiplier);
  l_value.reserve(basis_matrix_limit_size * kLFactorExtraEntriesMultiplier);

  lr_start.reserve(num_row + 1);
  lr_index.reserve(basis_matrix_limit_size * kLFactorExtraEntriesMultiplier);
  lr_value.reserve(basis_matrix_limit_size * kLFactorExtraEntriesMultiplier);

  // Allocate space for U factor
  u_pivot_lookup.resize(num_row);
  u_pivot_index.reserve(num_row + kUFactorExtraVectors);
  u_pivot_value.reserve(num_row + kUFactorExtraVectors);

  u_start.reserve(num_row + kUFactorExtraVectors + 1);
  u_last_p.reserve(num_row + kUFactorExtraVectors);
  u_index.reserve(basis_matrix_limit_size * kUFactorExtraEntriesMultiplier);
  u_value.reserve(basis_matrix_limit_size * kUFactorExtraEntriesMultiplier);

  ur_start.reserve(num_row + kUFactorExtraVectors + 1);
  ur_lastp.reserve(num_row + kUFactorExtraVectors);
  ur_space.reserve(num_row + kUFactorExtraVectors);
  ur_index.reserve(basis_matrix_limit_size * kUFactorExtraEntriesMultiplier);
  ur_value.reserve(basis_matrix_limit_size * kUFactorExtraEntriesMultiplier);

  // Allocate spaces for Update buffer
  pf_pivot_value.reserve(kPFFPivotEntries);
  pf_pivot_index.reserve(kPFFPivotEntries);
  pf_start.reserve(kPFVectors + 1);
  pf_index.reserve(basis_matrix_limit_size * kPFEntriesMultiplier);
  pf_value.reserve(basis_matrix_limit_size * kPFEntriesMultiplier);

  // Set up the local HVector for use when RHS is
  // std::vector<double>.
  rhs_.setup(num_row);
  rhs_.count = -1;
}

void HFactor::setupMatrix(const HighsInt* a_start_, const HighsInt* a_index_,
                          const double* a_value_) {
  a_start = a_start_;
  a_index = a_index_;
  a_value = a_value_;
  this->a_matrix_valid = true;
}

void HFactor::setupMatrix(const HighsSparseMatrix* a_matrix) {
  setupMatrix(&a_matrix->start_[0], &a_matrix->index_[0], &a_matrix->value_[0]);
}

HighsInt HFactor::build(HighsTimerClock* factor_timer_clock_pointer) {
  // Set up a timer to prevent build running longer than time_limit_,
  // which is kHighsInf by default, and only set to a finite value in
  // HPresolve::removeDependentEquations
  HighsTimer build_timer;
  build_timer_ = &build_timer;
  build_timer.startRunHighsClock();

  const bool report_lu = false;
  // Ensure that the A matrix is valid for factorization
  assert(this->a_matrix_valid);
  FactorTimer factor_timer;
  // Possibly use the refactorization information!
  if (refactor_info_.use) {
    factor_timer.start(FactorReinvert, factor_timer_clock_pointer);
    rank_deficiency = rebuild(factor_timer_clock_pointer);
    factor_timer.stop(FactorReinvert, factor_timer_clock_pointer);
    if (!rank_deficiency) return 0;
  }
  // Refactoring from just the list of basic variables. Initialise the
  // refactorization information.
  refactor_info_.clear();
  // Start the timer
  factor_timer.start(FactorInvert, factor_timer_clock_pointer);
  build_synthetic_tick = 0;
  factor_timer.start(FactorInvertSimple, factor_timer_clock_pointer);
  // Build the L, U factor
  buildSimple();
  if (report_lu) {
    printf("\nAfter units and singletons\n");
    reportLu(kReportLuBoth, false);
  }
  factor_timer.stop(FactorInvertSimple, factor_timer_clock_pointer);
  factor_timer.start(FactorInvertKernel, factor_timer_clock_pointer);
  const HighsInt build_kernel_return = buildKernel();
  factor_timer.stop(FactorInvertKernel, factor_timer_clock_pointer);
  //
  // build_kernel_return of kBuildKernelReturnTimeout (<0) indicates
  // that time limit has been reached, otherwise it's the rank
  // deficiency of the basic variables. If num_basic < num_row, then
  // have to identify the logicals required to complete the basis by
  // continuing as if a full-dimension set of basic variables was rank
  // deficient.
  if (build_kernel_return == kBuildKernelReturnTimeout)
    return kBuildKernelReturnTimeout;
  rank_deficiency = build_kernel_return;
  const bool incomplete_basis = num_basic < num_row;
  if (rank_deficiency || incomplete_basis) {
    factor_timer.start(FactorInvertDeficient, factor_timer_clock_pointer);
    if (num_basic == num_row)
      highsLogDev(log_options, HighsLogType::kWarning,
                  "Rank deficiency of %" HIGHSINT_FORMAT
                  " identified in basis matrix\n",
                  rank_deficiency);
    // Singular matrix B: reorder the basic variables so that the
    // singular columns are in the position corresponding to the
    // logical which replaces them
    buildHandleRankDeficiency();
    buildMarkSingC();
    factor_timer.stop(FactorInvertDeficient, factor_timer_clock_pointer);
  }
  if (incomplete_basis) {
    // Completing the factorization is not relevant if the basis
    // matrix is incomplete, so clear any refactorization information
    // and return
    this->refactor_info_.clear();
    assert(!this->refactor_info_.use);
    const HighsInt basic_index_rank_deficiency =
        rank_deficiency - (num_row - num_basic);
    return basic_index_rank_deficiency;
  }
  // Complete INVERT
  if (report_lu) {
    printf("\nFactored INVERT\n");
    reportLu(kReportLuBoth, false);
  }
  factor_timer.start(FactorInvertFinish, factor_timer_clock_pointer);
  buildFinish();
  factor_timer.stop(FactorInvertFinish, factor_timer_clock_pointer);
  //
  // Indicate that the refactorization information is known unless the basis was
  // rank deficient
  if (rank_deficiency) {
    this->refactor_info_.clear();
  } else {
    // Check that the refactorization information is not (yet) flagged
    // to be used in a future call
    assert(!this->refactor_info_.use);
    // Record build_synthetic_tick to use as the value of
    // build_synthetic_tick if refactorization is performed. Not only
    // is there no build_synthetic_tick measure for refactorization,
    // if there were it would give an unrealistic underestimate of the
    // cost of factorization from scratch
    this->refactor_info_.build_synthetic_tick = this->build_synthetic_tick;
  }

  // Record the number of entries in the INVERT
  invert_num_el = l_start[num_row] + u_last_p[num_row - 1] + num_row;

  kernel_dim -= rank_deficiency;
  debugLogRankDeficiency(highs_debug_level, log_options, rank_deficiency,
                         basis_matrix_num_el, invert_num_el, kernel_dim,
                         kernel_num_el, nwork);
  factor_timer.stop(FactorInvert, factor_timer_clock_pointer);
  return rank_deficiency;
}

void HFactor::ftranCall(HVector& vector, const double expected_density,
                        HighsTimerClock* factor_timer_clock_pointer) const {
  const bool use_indices = vector.count >= 0;
  FactorTimer factor_timer;
  factor_timer.start(FactorFtran, factor_timer_clock_pointer);
  ftranL(vector, expected_density, factor_timer_clock_pointer);
  ftranU(vector, expected_density, factor_timer_clock_pointer);
  // Possibly find the indices in order
  if (use_indices) vector.reIndex();
  factor_timer.stop(FactorFtran, factor_timer_clock_pointer);
}

void HFactor::ftranCall(std::vector<double>& vector,
                        HighsTimerClock* factor_timer_clock_pointer) {
  FactorTimer factor_timer;
  factor_timer.start(FactorFtran, factor_timer_clock_pointer);
  // Only have to clear the scalars of the local HVector, since the
  // array is moved in. Set the count to -1 to indicate that indices
  // aren't known.
  this->rhs_.clearScalars();
  this->rhs_.array = std::move(vector);
  this->rhs_.count = -1;
  const double expected_density = 1;
  ftranCall(this->rhs_, expected_density, factor_timer_clock_pointer);
  vector = std::move(this->rhs_.array);
  factor_timer.stop(FactorFtran, factor_timer_clock_pointer);
}

void HFactor::btranCall(HVector& vector, const double expected_density,
                        HighsTimerClock* factor_timer_clock_pointer) const {
  const bool use_indices = vector.count >= 0;
  FactorTimer factor_timer;
  factor_timer.start(FactorBtran, factor_timer_clock_pointer);
  btranU(vector, expected_density, factor_timer_clock_pointer);
  btranL(vector, expected_density, factor_timer_clock_pointer);
  // Possibly find the indices in order
  if (use_indices) vector.reIndex();
  factor_timer.stop(FactorBtran, factor_timer_clock_pointer);
}

void HFactor::btranCall(std::vector<double>& vector,
                        HighsTimerClock* factor_timer_clock_pointer) {
  // Only have to clear the scalars of the local HVector, since the
  // array is moved in. Set the count to -1 to indicate that indices
  // aren't known.
  this->rhs_.clearScalars();
  this->rhs_.array = std::move(vector);
  this->rhs_.count = -1;
  const double expected_density = 1;
  btranCall(this->rhs_, expected_density, factor_timer_clock_pointer);
  vector = std::move(this->rhs_.array);
}

void HFactor::update(HVector* aq, HVector* ep, HighsInt* iRow, HighsInt* hint) {
  // Updating implies a change of basis. Since the refactorizaion info
  // no longer corresponds to the current basis, it must be
  // invalidated
  this->refactor_info_.clear();
  // Special case
  if (aq->next) {
    updateCFT(aq, ep, iRow);
    return;
  }

  if (update_method == kUpdateMethodFt) updateFT(aq, ep, *iRow);
  if (update_method == kUpdateMethodPf) updatePF(aq, *iRow, hint);
  if (update_method == kUpdateMethodMpf) updateMPF(aq, ep, *iRow, hint);
  if (update_method == kUpdateMethodApf) updateAPF(aq, ep, *iRow);
}

bool HFactor::setPivotThreshold(const double new_pivot_threshold) {
  if (new_pivot_threshold < kMinPivotThreshold) return false;
  if (new_pivot_threshold > kMaxPivotThreshold) return false;
  pivot_threshold = new_pivot_threshold;
  return true;
}

void HFactor::setTimeLimit(const double time_limit) {
  this->time_limit_ = kHighsInf;
  if (time_limit < 0) return;
  this->time_limit_ = time_limit;
}

void HFactor::luClear() {
  l_start.clear();
  l_start.push_back(0);
  l_index.clear();
  l_value.clear();

  u_pivot_index.clear();
  u_pivot_value.clear();
  u_start.clear();
  u_start.push_back(0);
  u_index.clear();
  u_value.clear();
}

void HFactor::buildSimple() {
  /**
   * 0. Clear L and U factor
   */
  luClear();

  const bool progress_report = false;
  const HighsInt progress_frequency = 100000;

  // Set all values of permute to -1 so that unpermuted (rank
  // deficient) columns can be identified
  const HighsInt permute_dim = num_basic;
  // Strictly not necessary, but nice if it's the right size
  permute.resize(permute_dim);
  permute.assign(permute_dim, -1);

  /**
   * 1. Prepare basis matrix and deal with unit columns
   */
  const bool report_unit = false;
  const bool report_singletons = false;
  const bool report_markowitz = false;
  const bool report_anything =
      report_unit || report_singletons || report_markowitz;
  HighsInt BcountX = 0;
  fill_n(&mr_count_before[0], num_row, 0);
  nwork = 0;
  if (report_anything) printf("\nFactor\n");
  // Compile a vector iwork of the indices within basic_index of the
  // its nwork non-unit structural columns: they will be formed into
  // the B matrix as the kernel
  const HighsInt iwork_dim = num_basic;
  // Strictly not necessary, but nice if it's the right size
  iwork.resize(iwork_dim + 1, 0);
  iwork.assign(iwork_dim + 1, 0);
  for (HighsInt iCol = 0; iCol < num_basic; iCol++) {
    if (progress_report && iCol) {
      if (iCol % progress_frequency == 0)
        printf("HFactor::buildSimple stage = %6d\n", (int)iCol);
    }

    HighsInt iMat = basic_index[iCol];
    HighsInt iRow = -1;
    int8_t pivot_type = kPivotIllegal;
    // Look for unit columns as pivots. If there is already a pivot
    // corresponding to the nonzero in a unit column - evidenced by
    // mr_count_before[iRow] being negative - then, obviously, it
    // can't be used. However, it doesn't imply an error, or even rank
    // deficiency now that build() is being used to determine
    // rank. Treat it as a column to be handled in the kernel, so that
    // any rank deficiency or singularity is detected as late as
    // possible.
    if (iMat >= num_col) {
      // 1.1 Logical column
      //
      // Check for double pivot
      HighsInt lc_iRow = iMat - num_col;
      if (mr_count_before[lc_iRow] >= 0) {
        if (report_unit)
          printf("Stage %d: Logical\n", (int)(l_start.size() - 1));
        pivot_type = kPivotLogical;
        iRow = lc_iRow;
      } else {
        mr_count_before[lc_iRow]++;
        b_index[BcountX] = lc_iRow;
        b_value[BcountX++] = 1.0;
        iwork[nwork++] = iCol;
      }
    } else {
      // 1.2 Structural column
      HighsInt start = a_start[iMat];
      HighsInt count = a_start[iMat + 1] - start;
      // If this column and all subsequent columns are zero (so count
      // is 0) then a_index[start] and a_value[start] are unassigned,
      // so determine a unit column in two stages
      bool ok_unit_col = false;
      if (count == 1) {
        // Column has only one nonzero, but have to make sure that the
        // value is 1 and that there's not already a pivot
        // corresponding to this unit column
        ok_unit_col =
            a_value[start] == 1 && mr_count_before[a_index[start]] >= 0;
      }
      if (ok_unit_col) {
        if (report_unit) printf("Stage %d: Unit\n", (int)(l_start.size() - 1));
        // Don't exploit this special case in case the matrix is
        // re-factorized after scaling has been applied, making this
        // column non-unit.
        pivot_type = kPivotColSingleton;  //;kPivotUnit;//
        iRow = a_index[start];
      } else {
        for (HighsInt k = start; k < start + count; k++) {
          mr_count_before[a_index[k]]++;
          assert(BcountX < (HighsInt)b_index.size());
          b_index[BcountX] = a_index[k];
          b_value[BcountX++] = a_value[k];
        }
        iwork[nwork++] = iCol;
      }
    }

    if (iRow >= 0) {
      // 1.3 Record unit column
      permute[iCol] = iRow;
      l_start.push_back(l_index.size());
      u_pivot_index.push_back(iRow);
      u_pivot_value.push_back(1);
      u_start.push_back(u_index.size());
      // Was -num_row, but this is incorrect since the negation needs
      // to be great enough so that, starting from it, the accumulated
      // count can never reach zero
      mr_count_before[iRow] = -num_basic;
      assert(pivot_type != kPivotIllegal);
      this->refactor_info_.pivot_row.push_back(iRow);
      this->refactor_info_.pivot_var.push_back(iMat);
      this->refactor_info_.pivot_type.push_back(pivot_type);
    }
    b_start[iCol + 1] = BcountX;
    b_var[iCol] = iMat;
  }
  // Record the number of elements in the basis matrix
  basis_matrix_num_el = num_row - nwork + BcountX;

  // count1 = 0;
  // Comments: for pds-20, dfl001: 60 / 80
  // Comments: when system is large: enlarge
  // Comments: when system is small: decrease
  build_synthetic_tick += BcountX * 60 + (num_row - nwork) * 80;

  /**
   * 2. Search for and deal with singletons
   */
  double t2_search = 0;
  double t2_store_l = l_index.size();
  double t2_store_u = u_index.size();
  double t2_store_p = nwork;
  while (nwork > 0) {
    HighsInt nworkLast = nwork;
    nwork = 0;
    for (HighsInt i = 0; i < nworkLast; i++) {
      const HighsInt iCol = iwork[i];
      const HighsInt start = b_start[iCol];
      const HighsInt end = b_start[iCol + 1];
      HighsInt pivot_k = -1;
      HighsInt found_row_singleton = 0;
      HighsInt count = 0;

      // 2.1 Search for singleton
      t2_search += end - start;
      for (HighsInt k = start; k < end; k++) {
        const HighsInt iRow = b_index[k];
        if (mr_count_before[iRow] == 1) {
          pivot_k = k;
          found_row_singleton = 1;
          break;
        }
        if (mr_count_before[iRow] > 1) {
          pivot_k = k;
          count++;
        }
      }

      if (found_row_singleton) {
        // 2.2 Deal with row singleton
        const double pivot_multiplier = 1 / b_value[pivot_k];
        if (report_singletons)
          printf("Stage %d: Row singleton (%4d, %g)\n",
                 (int)(l_start.size() - 1), (int)pivot_k, pivot_multiplier);
        for (HighsInt section = 0; section < 2; section++) {
          HighsInt p0 = section == 0 ? start : pivot_k + 1;
          HighsInt p1 = section == 0 ? pivot_k : end;
          for (HighsInt k = p0; k < p1; k++) {
            HighsInt iRow = b_index[k];
            if (mr_count_before[iRow] > 0) {
              if (report_singletons)
                printf("Row singleton: L En (%4d, %11.4g)\n", (int)iRow,
                       b_value[k] * pivot_multiplier);
              l_index.push_back(iRow);
              l_value.push_back(b_value[k] * pivot_multiplier);
            } else {
              if (report_singletons)
                printf("Row singleton: U En (%4d, %11.4g)\n", (int)iRow,
                       b_value[k]);
              u_index.push_back(iRow);
              u_value.push_back(b_value[k]);
            }
            mr_count_before[iRow]--;
          }
        }
        HighsInt iRow = b_index[pivot_k];
        mr_count_before[iRow] = 0;
        permute[iCol] = iRow;
        l_start.push_back(l_index.size());

        if (report_singletons)
          printf("Row singleton: U Pv (%4d, %11.4g)\n", (int)iRow,
                 b_value[pivot_k]);
        u_pivot_index.push_back(iRow);
        u_pivot_value.push_back(b_value[pivot_k]);
        u_start.push_back(u_index.size());
        assert(b_var[iCol] == basic_index[iCol]);

        this->refactor_info_.pivot_row.push_back(iRow);
        this->refactor_info_.pivot_var.push_back(basic_index[iCol]);
        this->refactor_info_.pivot_type.push_back(kPivotRowSingleton);
      } else if (count == 1) {
        if (report_singletons)
          printf("Stage %d: Col singleton \n", (int)(l_start.size() - 1));
        // 2.3 Deal with column singleton
        for (HighsInt k = start; k < pivot_k; k++) {
          if (report_singletons)
            printf("Col singleton: U En (%4d, %11.4g)\n", (int)b_index[k],
                   b_value[k]);
          u_index.push_back(b_index[k]);
          u_value.push_back(b_value[k]);
        }
        for (HighsInt k = pivot_k + 1; k < end; k++) {
          if (report_singletons)
            printf("Col singleton: U En (%4d, %11.4g)\n", (int)b_index[k],
                   b_value[k]);
          u_index.push_back(b_index[k]);
          u_value.push_back(b_value[k]);
        }

        HighsInt iRow = b_index[pivot_k];
        mr_count_before[iRow] = 0;
        permute[iCol] = iRow;
        l_start.push_back(l_index.size());

        if (report_singletons)
          printf("Col singleton: U Pv (%4d, %11.4g)\n", (int)iRow,
                 b_value[pivot_k]);
        u_pivot_index.push_back(iRow);
        u_pivot_value.push_back(b_value[pivot_k]);
        u_start.push_back(u_index.size());
        assert(b_var[iCol] == basic_index[iCol]);
        this->refactor_info_.pivot_row.push_back(iRow);
        this->refactor_info_.pivot_var.push_back(basic_index[iCol]);
        this->refactor_info_.pivot_type.push_back(kPivotColSingleton);
      } else {
        iwork[nwork++] = iCol;
      }
    }

    // No singleton found in the last pass
    if (nworkLast == nwork) break;
  }
  if (report_anything) reportLu(kReportLuBoth, false);
  t2_store_l = l_index.size() - t2_store_l;
  t2_store_u = u_index.size() - t2_store_u;
  t2_store_p = t2_store_p - nwork;

  build_synthetic_tick +=
      t2_search * 20 + (t2_store_p + t2_store_l + t2_store_u) * 80;

  /**
   * 3. Prepare the kernel parts
   */
  // 3.1 Prepare row links, row matrix spaces
  row_link_first.assign(num_basic + 1, -1);
  mr_count.assign(num_row, 0);
  HighsInt mr_countX = 0;
  // Determine the number of entries in the kernel
  kernel_num_el = 0;
  for (HighsInt iRow = 0; iRow < num_row; iRow++) {
    HighsInt count = mr_count_before[iRow];
    if (count > 0) {
      mr_start[iRow] = mr_countX;
      mr_space[iRow] = count * 2;
      mr_countX += count * 2;
      rlinkAdd(iRow, count);
      kernel_num_el += count + 1;
    }
  }
  mr_index.resize(mr_countX);

  // 3.2 Prepare column links, kernel matrix
  col_link_first.assign(num_row + 1, -1);
  const HighsInt mc_dim = num_basic;
  mc_index.clear();
  mc_value.clear();
  mc_count_a.assign(mc_dim, 0);
  mc_count_n.assign(mc_dim, 0);
  HighsInt MCcountX = 0;
  for (HighsInt i = 0; i < nwork; i++) {
    HighsInt iCol = iwork[i];
    mc_var[iCol] = b_var[iCol];
    mc_start[iCol] = MCcountX;
    mc_space[iCol] = (b_start[iCol + 1] - b_start[iCol]) * 2;
    MCcountX += mc_space[iCol];
    mc_index.resize(MCcountX);
    mc_value.resize(MCcountX);
    for (HighsInt k = b_start[iCol]; k < b_start[iCol + 1]; k++) {
      const HighsInt iRow = b_index[k];
      const double value = b_value[k];
      if (mr_count_before[iRow] > 0) {
        colInsert(iCol, iRow, value);
        rowInsert(iCol, iRow);
      } else {
        colStoreN(iCol, iRow, value);
      }
    }
    colFixMax(iCol);
    clinkAdd(iCol, mc_count_a[iCol]);
  }
  build_synthetic_tick += (num_row + nwork + MCcountX) * 40 + mr_countX * 20;
  // Record the kernel dimension
  kernel_dim = nwork;
  assert((HighsInt)this->refactor_info_.pivot_row.size() == num_basic - nwork);
}

HighsInt HFactor::buildKernel() {
  // Deal with the kernel part by 'n-work' pivoting

  double fake_search = 0;
  double fake_fill = 0;
  double fake_eliminate = 0;

  const bool progress_report = false;  // num_basic != num_row;
  const HighsInt progress_frequency = 10000;
  // Initial timer frequency: may be reduced if iterations get slow
  HighsInt timer_frequency = 100;
  double previous_iteration_time = 0;
  double average_iteration_time = 0;
  const bool check_for_timeout = this->time_limit_ < kHighsInf;
  HighsInt search_k = 0;

  const HighsInt check_nwork = -11;
  while (nwork-- > 0) {
    //    printf("\nnwork = %d\n", (int)nwork);
    if (nwork == check_nwork) {
      reportAsm();
    }
    // Detemine whether to return due to exceeding the time limit
    if (check_for_timeout && search_k % timer_frequency == 0) {
      double current_time = build_timer_->readRunHighsClock();
      double time_difference = current_time - previous_iteration_time;
      previous_iteration_time = current_time;
      double iteration_time = time_difference / (1.0 * timer_frequency);
      average_iteration_time =
          0.9 * average_iteration_time + 0.1 * iteration_time;

      if (time_difference > this->time_limit_ / 1e3)
        timer_frequency = std::max(HighsInt(1), timer_frequency / 10);
      HighsInt iterations_left = kernel_dim - search_k + 1;
      double remaining_time_bound = average_iteration_time * iterations_left;
      double total_time_bound = current_time + remaining_time_bound;
      if (current_time > this->time_limit_ ||
          total_time_bound > this->time_limit_)
        return kBuildKernelReturnTimeout;
    }

    /**
     * 1. Search for the pivot
     */
    HighsInt jColPivot = -1;
    HighsInt iRowPivot = -1;
    //    int8_t pivot_type = kPivotIllegal;
    // 1.1. Setup search merits
    HighsInt searchLimit = min(nwork, HighsInt{8});
    HighsInt searchCount = 0;

    double merit_limit = 1.0 * num_basic * num_row;
    double merit_pivot = merit_limit;

    if (progress_report && search_k) {
      if (search_k % progress_frequency == 0) {
        HighsInt min_col_count = kHighsIInf;
        HighsInt min_row_count = kHighsIInf;
        for (HighsInt count = 1; count < num_row; count++) {
          if (col_link_first[count] >= 0) {
            min_col_count = count;
            break;
          }
        }
        for (HighsInt count = 1; count < num_basic; count++) {
          if (row_link_first[count] >= 0) {
            min_row_count = count;
            break;
          }
        }
        printf(
            "HFactor::buildKernel stage = %6d: min_col_count = %3d; "
            "min_row_count = %3d\n",
            (int)search_k, (int)min_col_count, (int)min_row_count);
      }
    }
    search_k++;
    // 1.2. Search for local singletons
    bool foundPivot = false;
    //    jColPivot = col_link_first[1];
    if (!foundPivot && col_link_first[1] != -1) {
      // Not yet found a pivot and there is at least one column
      // singleton
      jColPivot = col_link_first[1];
      iRowPivot = mc_index[mc_start[jColPivot]];
      foundPivot = true;
    }
    if (!foundPivot && row_link_first[1] != -1) {
      iRowPivot = row_link_first[1];
      jColPivot = mr_index[mr_start[iRowPivot]];
      foundPivot = true;
    }
    const bool singleton_pivot = foundPivot;
#ifndef NDEBUG
    double candidate_pivot_value = 0;
#endif
    // 1.3. Major search loop
    //
    // Row count can be more than the number of rows if num_basic >
    // num_row
    const HighsInt max_count = max(num_row, num_basic);
    for (HighsInt count = 2; !foundPivot && count <= max_count; count++) {
      // Column count cannot exceed the number of rows
      if (count <= num_row) {
        // 1.3.1 Search for columns
        for (HighsInt j = col_link_first[count]; j != -1;
             j = col_link_next[j]) {
          double min_pivot = mc_min_pivot[j];
          HighsInt start = mc_start[j];
          HighsInt end = start + mc_count_a[j];
          for (HighsInt k = start; k < end; k++) {
            if (fabs(mc_value[k]) >= min_pivot) {
              HighsInt i = mc_index[k];
              HighsInt row_count = mr_count[i];
              double merit_local = 1.0 * (count - 1) * (row_count - 1);
              if (merit_pivot > merit_local) {
#ifndef NDEBUG
                candidate_pivot_value = fabs(mc_value[k]);
#endif
                merit_pivot = merit_local;
                jColPivot = j;
                iRowPivot = i;
                foundPivot = foundPivot || (row_count < count);
              }
            }
          }

          if (searchCount++ >= searchLimit && merit_pivot < merit_limit)
            foundPivot = true;
          if (foundPivot) break;

          fake_search += count;
        }
      }

      // Row count cannot exceed the number of basic variables
      if (count <= num_basic) {
        // 1.3.2 Search for rows
        for (HighsInt i = row_link_first[count]; i != -1;
             i = row_link_next[i]) {
          HighsInt start = mr_start[i];
          HighsInt end = start + mr_count[i];
          for (HighsInt k = start; k < end; k++) {
            HighsInt j = mr_index[k];
            HighsInt column_count = mc_count_a[j];
            double merit_local = 1.0 * (count - 1) * (column_count - 1);
            if (merit_local < merit_pivot) {
              HighsInt ifind = mc_start[j];
              while (mc_index[ifind] != i) ifind++;
              if (fabs(mc_value[ifind]) >= mc_min_pivot[j]) {
#ifndef NDEBUG
                candidate_pivot_value = fabs(mc_value[ifind]);
#endif
                merit_pivot = merit_local;
                jColPivot = j;
                iRowPivot = i;
                foundPivot = foundPivot || (column_count <= count);
              }
            }
          }
          if (searchCount++ >= searchLimit && merit_pivot < merit_limit)
            foundPivot = true;
          if (foundPivot) break;
        }

        fake_search += count;
      }
    }
    // 1.4. If we found nothing: tell singular
    if (!foundPivot) {
      rank_deficiency = nwork + 1;
      highsLogDev(log_options, HighsLogType::kWarning,
                  "Factorization identifies rank deficiency of %d\n",
                  (int)rank_deficiency);
      return rank_deficiency;
    }

    /**
     * 2. Elimination other elements by the pivot
     */
#ifndef NDEBUG
    const HighsInt original_pivotal_row_count = mr_count[iRowPivot];
    const HighsInt original_pivotal_col_count = mc_count_a[jColPivot];
#endif
    // 2.1. Delete the pivot
    //
    // Remove the pivot row index from the pivotal column of the
    // col-wise matrix. Also decreases the column count
    double pivot_multiplier = colDelete(jColPivot, iRowPivot);
    // Remove the pivot column index from the pivotal row of the
    // row-wise matrix. Also decreases the row count
    rowDelete(jColPivot, iRowPivot);
    // Remove the pivotal column from the linked list of columns
    // containing it
    clinkDel(jColPivot);
    // Remove the pivotal row from the linked list of rows containing
    // it
    rlinkDel(iRowPivot);
    if (!singleton_pivot)
      assert(candidate_pivot_value == fabs(pivot_multiplier));
    if (fabs(pivot_multiplier) < pivot_tolerance) {
      highsLogDev(log_options, HighsLogType::kWarning,
                  "Defer singular pivot = %11.4g\n", pivot_multiplier);
      // Matrix is singular, but defer return since other valid pivots
      // may exist.
      assert(mr_count[iRowPivot] == original_pivotal_row_count - 1);
      if (mr_count[iRowPivot] == 0) {
        // The pivot corresponds to a singleton row. Entry is zeroed,
        // and do no more since there may be other valid entries in
        // the pivotal column
        //
        // Add the pivotal column to the linked list of columns with
        // its new count
        assert(mc_count_a[jColPivot] == original_pivotal_col_count - 1);
        clinkAdd(jColPivot, mc_count_a[jColPivot]);
      } else {
        // Otherwise, other entries in the pivotal column will be
        // smaller than the pivot, so zero the column
        zeroCol(jColPivot);
        // Add the pivotal row to the linked list of rows with its new
        // count
        assert(mr_count[iRowPivot] == original_pivotal_row_count - 1);
        rlinkAdd(iRowPivot, mr_count[iRowPivot]);
      }
      // No pivot found, so have to increment nwork
      nwork++;
      continue;
    }
    permute[jColPivot] = iRowPivot;
    assert(mc_var[jColPivot] == basic_index[jColPivot]);

    this->refactor_info_.pivot_row.push_back(iRowPivot);
    this->refactor_info_.pivot_var.push_back(basic_index[jColPivot]);
    this->refactor_info_.pivot_type.push_back(kPivotMarkowitz);

    // 2.2. Store active pivot column to L
    HighsInt start_A = mc_start[jColPivot];
    HighsInt end_A = start_A + mc_count_a[jColPivot];
    HighsInt mwz_column_count = 0;
    for (HighsInt k = start_A; k < end_A; k++) {
      const HighsInt iRow = mc_index[k];
      const double value = mc_value[k] / pivot_multiplier;
      mwz_column_index[mwz_column_count++] = iRow;
      mwz_column_array[iRow] = value;
      mwz_column_mark[iRow] = 1;
      l_index.push_back(iRow);
      l_value.push_back(value);
      mr_count_before[iRow] = mr_count[iRow];
      rowDelete(jColPivot, (int)iRow);
    }
    l_start.push_back(l_index.size());
    fake_fill += 2 * mc_count_a[jColPivot];

    // 2.3. Store non active pivot column to U
    HighsInt end_N = start_A + mc_space[jColPivot];
    HighsInt start_N = end_N - mc_count_n[jColPivot];
    for (HighsInt i = start_N; i < end_N; i++) {
      u_index.push_back(mc_index[i]);
      u_value.push_back(mc_value[i]);
    }
    u_pivot_index.push_back(iRowPivot);
    u_pivot_value.push_back(pivot_multiplier);
    u_start.push_back(u_index.size());
    fake_fill += end_N - start_N;

    // 2.4. Loop over pivot row to eliminate other column
    const HighsInt row_start = mr_start[iRowPivot];
    const HighsInt row_end = row_start + mr_count[iRowPivot];
    for (HighsInt row_k = row_start; row_k < row_end; row_k++) {
      // 2.4.1. My pointer
      HighsInt iCol = mr_index[row_k];
      const HighsInt my_count = mc_count_a[iCol];
      const HighsInt my_start = mc_start[iCol];
      const HighsInt my_end = my_start + my_count - 1;
      double my_pivot = colDelete(iCol, iRowPivot);
      colStoreN(iCol, iRowPivot, my_pivot);

      // 2.4.2. Elimination on the overlapping part
      HighsInt nFillin = mwz_column_count;
      HighsInt nCancel = 0;
      for (HighsInt my_k = my_start; my_k < my_end; my_k++) {
        HighsInt iRow = mc_index[my_k];
        double value = mc_value[my_k];
        if (mwz_column_mark[iRow]) {
          mwz_column_mark[iRow] = 0;
          nFillin--;
          value -= my_pivot * mwz_column_array[iRow];
          if (fabs(value) < kHighsTiny) {
            value = 0;
            nCancel++;
          }
          mc_value[my_k] = value;
        }
      }
      fake_eliminate += mwz_column_count;
      fake_eliminate += nFillin * 2;

      // 2.4.3. Remove cancellation gaps
      if (nCancel > 0) {
        HighsInt new_end = my_start;
        for (HighsInt my_k = my_start; my_k < my_end; my_k++) {
          if (mc_value[my_k] != 0) {
            mc_index[new_end] = mc_index[my_k];
            mc_value[new_end++] = mc_value[my_k];
          } else {
            rowDelete(iCol, mc_index[my_k]);
          }
        }
        mc_count_a[iCol] = new_end - my_start;
      }

      // 2.4.4. Insert fill-in
      if (nFillin > 0) {
        // 2.4.4.1 Check column size
        if (mc_count_a[iCol] + mc_count_n[iCol] + nFillin > mc_space[iCol]) {
          // p1&2=active, p3&4=non active, p5=new p1, p7=new p3
          HighsInt p1 = mc_start[iCol];
          HighsInt p2 = p1 + mc_count_a[iCol];
          HighsInt p3 = p1 + mc_space[iCol] - mc_count_n[iCol];
          HighsInt p4 = p1 + mc_space[iCol];
          mc_space[iCol] += max(mc_space[iCol], nFillin);
          HighsInt p5 = mc_start[iCol] = mc_index.size();
          HighsInt p7 = p5 + mc_space[iCol] - mc_count_n[iCol];
          mc_index.resize(p5 + mc_space[iCol]);
          mc_value.resize(p5 + mc_space[iCol]);
          copy(&mc_index[p1], &mc_index[p2], &mc_index[p5]);
          copy(&mc_value[p1], &mc_value[p2], &mc_value[p5]);
          copy(&mc_index[p3], &mc_index[p4], &mc_index[p7]);
          copy(&mc_value[p3], &mc_value[p4], &mc_value[p7]);
        }

        // 2.4.4.2 Fill into column copy
        for (HighsInt i = 0; i < mwz_column_count; i++) {
          HighsInt iRow = mwz_column_index[i];
          if (mwz_column_mark[iRow])
            colInsert(iCol, iRow, -my_pivot * mwz_column_array[iRow]);
        }

        // 2.4.4.3 Fill into the row copy
        for (HighsInt i = 0; i < mwz_column_count; i++) {
          HighsInt iRow = mwz_column_index[i];
          if (mwz_column_mark[iRow]) {
            // Expand row space
            if (mr_count[iRow] == mr_space[iRow]) {
              HighsInt p1 = mr_start[iRow];
              HighsInt p2 = p1 + mr_count[iRow];
              HighsInt p3 = mr_start[iRow] = mr_index.size();
              mr_space[iRow] *= 2;
              mr_index.resize(p3 + mr_space[iRow]);
              copy(&mr_index[p1], &mr_index[p2], &mr_index[p3]);
            }
            rowInsert(iCol, iRow);
          }
        }
      }

      // 2.4.5. Reset pivot column mark
      for (HighsInt i = 0; i < mwz_column_count; i++)
        mwz_column_mark[mwz_column_index[i]] = 1;

      // 2.4.6. Fix max value and link list
      colFixMax(iCol);
      if (my_count != mc_count_a[iCol]) {
        clinkDel(iCol);
        clinkAdd(iCol, mc_count_a[iCol]);
      }
    }

    // 2.5. Clear pivot column buffer
    for (HighsInt i = 0; i < mwz_column_count; i++)
      mwz_column_mark[mwz_column_index[i]] = 0;

    // 2.6. Correct row links for the remain active part
    for (HighsInt i = start_A; i < end_A; i++) {
      HighsInt iRow = mc_index[i];
      if (mr_count_before[iRow] != mr_count[iRow]) {
        rlinkDel(iRow);
        rlinkAdd(iRow, mr_count[iRow]);
      }
    }
  }
  build_synthetic_tick +=
      fake_search * 20 + fake_fill * 160 + fake_eliminate * 80;
  rank_deficiency = 0;
  return rank_deficiency;
}

void HFactor::buildHandleRankDeficiency() {
  debugReportRankDeficiency(0, highs_debug_level, log_options, num_row, permute,
                            iwork, basic_index, rank_deficiency,
                            row_with_no_pivot, col_with_no_pivot);
  // iwork can now be used as workspace: use it to accumulate the new
  // basic_index. iwork is set to -1 and basic_index is permuted into it.
  // Indices of iwork corresponding to missing indices in permute
  // remain -1. Hence the -1's become markers for the logicals which
  // will replace singular columns. Once basic_index[i] is read, it can
  // be used to pack up the entries in basic_index which are not
  // permuted anywhere - and so will be singular columns.
  //
  // On entry, rank_deficiency is the rank deficiency of basic_index, which is
  //
  // * Less than the rank deficiency of the basis matrix if num_basic < num_row
  //
  //
  const HighsInt basic_index_rank_deficiency = rank_deficiency;
  if (num_basic < num_row) {
    rank_deficiency += num_row - num_basic;
  }
  row_with_no_pivot.resize(rank_deficiency);
  col_with_no_pivot.resize(rank_deficiency);
  HighsInt lc_rank_deficiency = 0;
  if (num_basic < num_row) {
    // iwork still has to be used to indicate the rows with no pivots,
    // so resize it
    iwork.resize(num_row);
  } else if (num_basic > num_row) {
    // iwork only has to be used to indicate the rows with no pivots,
    // so resize it
    iwork.resize(num_basic);
  }
  // ToDo: surely this is neater as iwork.assign(num_row, -1);
  for (HighsInt i = 0; i < num_row; i++) iwork[i] = -1;
  for (HighsInt i = 0; i < num_basic; i++) {
    HighsInt perm_i = permute[i];
    if (perm_i >= 0) {
      iwork[perm_i] = basic_index[i];
    } else {
      col_with_no_pivot[lc_rank_deficiency++] = i;
    }
  }
  if (num_basic < num_row) {
    // Resize permute and complete iwork and col_with_no_pivot with
    // fictitious indices and entries of basic_index
    permute.resize(num_row);
    for (HighsInt i = num_basic; i < num_row; i++) {
      col_with_no_pivot[lc_rank_deficiency++] = i;
      permute[i] = -1;
    }
  }
  assert(lc_rank_deficiency == rank_deficiency);
  lc_rank_deficiency = 0;
  for (HighsInt i = 0; i < num_row; i++) {
    if (iwork[i] < 0) {
      // Record the rows with no pivots in row_with_no_pivot and indicate them
      // within iwork by storing the negation of one more than their
      // rank deficiency counter [since we can't have -0].
      row_with_no_pivot[lc_rank_deficiency] = i;
      iwork[i] = -(lc_rank_deficiency + 1);
      lc_rank_deficiency++;
    }
  }
  if (num_row < num_basic) {
    // Record fictitious rows with no pivots for the excess basic
    // variables so that permute will be constructed as a permutation
    // of all entries in basic_index
    for (HighsInt i = num_row; i < num_basic; i++) {
      row_with_no_pivot[lc_rank_deficiency] = i;
      iwork[i] = -(lc_rank_deficiency + 1);
      lc_rank_deficiency++;
    }
  }
  assert(lc_rank_deficiency == rank_deficiency);
  debugReportRankDeficiency(1, highs_debug_level, log_options, num_row, permute,
                            iwork, basic_index, rank_deficiency,
                            row_with_no_pivot, col_with_no_pivot);
  const HighsInt row_rank_deficiency =
      rank_deficiency - max(num_basic - num_row, (HighsInt)0);
  // Complete the permutation using the indices of rows with no pivot,
  // the last max(num_basic-num_row, 0) of which will be fictitious
  for (HighsInt k = 0; k < rank_deficiency; k++) {
    HighsInt iRow = row_with_no_pivot[k];
    HighsInt iCol = col_with_no_pivot[k];
    assert(permute[iCol] == -1);
    permute[iCol] = iRow;
    if (k < row_rank_deficiency) {
      // Only correct the factorization for the true rows
      l_start.push_back(l_index.size());
      u_pivot_index.push_back(iRow);
      u_pivot_value.push_back(1);
      u_start.push_back(u_index.size());
    }
  }
  debugReportRankDeficiency(2, highs_debug_level, log_options, num_row, permute,
                            iwork, basic_index, rank_deficiency,
                            row_with_no_pivot, col_with_no_pivot);
  debugReportRankDeficientASM(
      highs_debug_level, log_options, num_row, mc_start, mc_count_a, mc_index,
      mc_value, iwork, rank_deficiency, col_with_no_pivot, row_with_no_pivot);
}

void HFactor::buildMarkSingC() {
  // Singular matrix B: reorder the basic variables so that the
  // singular columns are in the position corresponding to the
  // logical which replaces them
  debugReportMarkSingC(0, highs_debug_level, log_options, num_row, iwork,
                       basic_index);

  const HighsInt basic_index_rank_deficiency =
      rank_deficiency - max(num_row - num_basic, (HighsInt)0);
  var_with_no_pivot.resize(rank_deficiency);
  for (HighsInt k = 0; k < rank_deficiency; k++) {
    HighsInt ASMrow = row_with_no_pivot[k];
    HighsInt ASMcol = col_with_no_pivot[k];
    assert(ASMrow < (HighsInt)iwork.size());
    assert(-iwork[ASMrow] - 1 >= 0 && -iwork[ASMrow] - 1 < rank_deficiency);
    // Store negation of 1+ASMcol so that removing column 0 can be
    // identified!
    iwork[ASMrow] = -(ASMcol + 1);
    // Only update basic_index for the true entries
    if (ASMcol < num_basic) {
      assert(k < basic_index_rank_deficiency);
      // Record the variable in basic_index that had no pivot, and
      // replace it with the logical
      var_with_no_pivot[k] = basic_index[ASMcol];
      basic_index[ASMcol] = num_col + ASMrow;
    } else if (num_basic < num_row) {
      assert(ASMcol == num_basic + k - basic_index_rank_deficiency);
      // Record an illegal variable when there's no index to displace
      var_with_no_pivot[k] = -1;
    }
  }
  debugReportMarkSingC(1, highs_debug_level, log_options, num_row, iwork,
                       basic_index);
}

void HFactor::buildFinish() {
  // Must only be called in the case where there are at least as many basic
  // variables as rows
  assert(num_basic >= num_row);
  //  debugPivotValueAnalysis(highs_debug_level, log_options, num_row,
  //  u_pivot_value);
  // The look up table
  for (HighsInt i = 0; i < num_row; i++) u_pivot_lookup[u_pivot_index[i]] = i;
  l_pivot_index = u_pivot_index;
  l_pivot_lookup = u_pivot_lookup;

  // LR space
  HighsInt LcountX = l_index.size();
  lr_index.resize(LcountX);
  lr_value.resize(LcountX);

  // LR pointer
  iwork.assign(num_row, 0);
  for (HighsInt k = 0; k < LcountX; k++) iwork[l_pivot_lookup[l_index[k]]]++;

  lr_start.assign(num_row + 1, 0);
  for (HighsInt i = 1; i <= num_row; i++)
    lr_start[i] = lr_start[i - 1] + iwork[i - 1];

  // LR elements
  iwork.assign(&lr_start[0], &lr_start[num_row]);
  for (HighsInt i = 0; i < num_row; i++) {
    const HighsInt index = l_pivot_index[i];
    for (HighsInt k = l_start[i]; k < l_start[i + 1]; k++) {
      HighsInt iRow = l_pivot_lookup[l_index[k]];
      HighsInt i_put = iwork[iRow]++;
      lr_index[i_put] = index;
      lr_value[i_put] = l_value[k];
    }
  }

  // U pointer
  u_start.push_back(0);
  u_last_p.assign(&u_start[1], &u_start[num_row + 1]);
  u_start.resize(num_row);

  // UR space
  HighsInt u_countX = u_index.size();
  HighsInt ur_stuff_size = update_method == kUpdateMethodFt ? 5 : 0;
  HighsInt ur_count_size = u_countX + ur_stuff_size * num_row;
  ur_index.resize(ur_count_size);
  ur_value.resize(ur_count_size);

  // UR pointer
  //
  // NB ur_lastp just being used as temporary storage here
  ur_start.assign(num_row + 1, 0);
  ur_lastp.assign(num_row, 0);
  ur_space.assign(num_row, ur_stuff_size);
  for (HighsInt k = 0; k < u_countX; k++)
    ur_lastp[u_pivot_lookup[u_index[k]]]++;
  for (HighsInt i = 1; i <= num_row; i++)
    ur_start[i] = ur_start[i - 1] + ur_lastp[i - 1] + ur_stuff_size;
  ur_start.resize(num_row);

  // UR element
  //
  // NB ur_lastp initialised here!
  ur_lastp = ur_start;
  for (HighsInt i = 0; i < num_row; i++) {
    const HighsInt index = u_pivot_index[i];
    for (HighsInt k = u_start[i]; k < u_last_p[i]; k++) {
      HighsInt iRow = u_pivot_lookup[u_index[k]];
      HighsInt i_put = ur_lastp[iRow]++;
      ur_index[i_put] = index;
      ur_value[i_put] = u_value[k];
    }
  }

  // Re-factor merit
  u_merit_x = num_row + (LcountX + u_countX) * 1.5;
  u_total_x = u_countX;
  if (update_method == kUpdateMethodPf) u_merit_x = num_row + u_countX * 4;
  if (update_method == kUpdateMethodMpf) u_merit_x = num_row + u_countX * 3;

  // Clear update buffer
  pf_pivot_value.clear();
  pf_pivot_index.clear();
  pf_start.clear();
  pf_start.push_back(0);
  pf_index.clear();
  pf_value.clear();

  if (!this->refactor_info_.use) {
    // Finally, if not calling buildFinish after refactorizing,
    // permute the basic variables
    iwork.assign(basic_index, basic_index + num_basic);
    for (HighsInt i = 0; i < num_basic; i++) basic_index[permute[i]] = iwork[i];
    // If there are more basic variables than rows, basic_index
    // should have been permuted so that its last num_basic-num_row
    // entries are logicals
    for (HighsInt i = num_row; i < num_basic; i++)
      assert(basic_index[i] >= num_col);
    // Add cost of buildFinish to build_synthetic_tick
    build_synthetic_tick += num_row * 80 + (LcountX + u_countX) * 60;
  }
}

void HFactor::zeroCol(const HighsInt jCol) {
  const HighsInt a_count = mc_count_a[jCol];
  const HighsInt a_start = mc_start[jCol];
  const HighsInt a_end = a_start + a_count;
  for (HighsInt iEl = a_start; iEl < a_end; iEl++) {
    const double abs_value = std::abs(mc_value[iEl]);
    const HighsInt iRow = mc_index[iEl];
    const HighsInt original_row_count = mr_count[iRow];
    // Remove the column index from this row of the row-wise
    // matrix. Also decreases the row count
    rowDelete(jCol, iRow);
    // Remove this row from the linked list of rows containing it
    rlinkDel(iRow);
    // Add the this row to the linked list of rows with this reduced
    // count
    assert(mr_count[iRow] == original_row_count - 1);
    rlinkAdd(iRow, mr_count[iRow]);
    assert(abs_value < pivot_tolerance);
  }
  // Remove the column from the linked list of columns containing it
  clinkDel(jCol);
  // Zero the counts of the active and inactive sections of the column
  mc_count_a[jCol] = 0;
  mc_count_n[jCol] = 0;
}

void HFactor::ftranL(HVector& rhs, const double expected_density,
                     HighsTimerClock* factor_timer_clock_pointer) const {
  FactorTimer factor_timer;
  factor_timer.start(FactorFtranLower, factor_timer_clock_pointer);
  if (update_method == kUpdateMethodApf) {
    assert(!(update_method == kUpdateMethodApf));
    factor_timer.start(FactorFtranLowerAPF, factor_timer_clock_pointer);
    rhs.tight();
    rhs.pack();
    ftranAPF(rhs);
    factor_timer.stop(FactorFtranLowerAPF, factor_timer_clock_pointer);
    rhs.tight();
  }

  // Determine style of solve
  double current_density = 1.0 * rhs.count / num_row;
  const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel ||
                            expected_density > kHyperFtranL;
  if (sparse_solve) {
    factor_timer.start(FactorFtranLowerSps, factor_timer_clock_pointer);
    // Alias to RHS
    HighsInt* rhs_index = &rhs.index[0];
    double* rhs_array = &rhs.array[0];
    // Alias to factor L
    const HighsInt* l_start = &this->l_start[0];
    const HighsInt* l_index =
        this->l_index.size() > 0 ? &this->l_index[0] : NULL;
    const double* l_value = this->l_value.size() > 0 ? &this->l_value[0] : NULL;
    // Local accumulation of RHS count
    HighsInt rhs_count = 0;
    // Transform
    for (HighsInt i = 0; i < num_row; i++) {
      HighsInt pivotRow = l_pivot_index[i];
      const double pivot_multiplier = rhs_array[pivotRow];
      if (fabs(pivot_multiplier) > kHighsTiny) {
        rhs_index[rhs_count++] = pivotRow;
        const HighsInt start = l_start[i];
        const HighsInt end = l_start[i + 1];
        for (HighsInt k = start; k < end; k++)
          rhs_array[l_index[k]] -= pivot_multiplier * l_value[k];
      } else
        rhs_array[pivotRow] = 0;
    }
    // Save the count
    rhs.count = rhs_count;
    factor_timer.stop(FactorFtranLowerSps, factor_timer_clock_pointer);
  } else {
    // Hyper-sparse solve
    factor_timer.start(FactorFtranLowerHyper, factor_timer_clock_pointer);
    const HighsInt* l_index =
        this->l_index.size() > 0 ? &this->l_index[0] : NULL;
    const double* l_value = this->l_value.size() > 0 ? &this->l_value[0] : NULL;
    solveHyper(num_row, &l_pivot_lookup[0], &l_pivot_index[0], 0, &l_start[0],
               &l_start[1], &l_index[0], &l_value[0], &rhs);
    factor_timer.stop(FactorFtranLowerHyper, factor_timer_clock_pointer);
  }
  factor_timer.stop(FactorFtranLower, factor_timer_clock_pointer);
}

void HFactor::btranL(HVector& rhs, const double expected_density,
                     HighsTimerClock* factor_timer_clock_pointer) const {
  FactorTimer factor_timer;
  factor_timer.start(FactorBtranLower, factor_timer_clock_pointer);

  // Determine style of solve
  const double current_density = 1.0 * rhs.count / num_row;
  const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel ||
                            expected_density > kHyperBtranL;
  if (sparse_solve) {
    factor_timer.start(FactorBtranLowerSps, factor_timer_clock_pointer);
    // Alias to RHS
    HighsInt* rhs_index = &rhs.index[0];
    double* rhs_array = &rhs.array[0];
    // Alias to factor L
    const HighsInt* lr_start = &this->lr_start[0];
    const HighsInt* lr_index =
        this->lr_index.size() > 0 ? &this->lr_index[0] : NULL;
    const double* lr_value =
        this->lr_value.size() > 0 ? &this->lr_value[0] : NULL;
    // Local accumulation of RHS count
    HighsInt rhs_count = 0;
    // Transform
    for (HighsInt i = num_row - 1; i >= 0; i--) {
      HighsInt pivotRow = l_pivot_index[i];
      const double pivot_multiplier = rhs_array[pivotRow];
      if (fabs(pivot_multiplier) > kHighsTiny) {
        rhs_index[rhs_count++] = pivotRow;
        rhs_array[pivotRow] = pivot_multiplier;
        const HighsInt start = lr_start[i];
        const HighsInt end = lr_start[i + 1];
        for (HighsInt k = start; k < end; k++)
          rhs_array[lr_index[k]] -= pivot_multiplier * lr_value[k];
      } else
        rhs_array[pivotRow] = 0;
    }
    // Save the count
    rhs.count = rhs_count;
    factor_timer.stop(FactorBtranLowerSps, factor_timer_clock_pointer);
  } else {
    // Hyper-sparse solve
    factor_timer.start(FactorBtranLowerHyper, factor_timer_clock_pointer);
    const HighsInt* lr_index =
        this->lr_index.size() > 0 ? &this->lr_index[0] : NULL;
    const double* lr_value =
        this->lr_value.size() > 0 ? &this->lr_value[0] : NULL;
    solveHyper(num_row, &l_pivot_lookup[0], &l_pivot_index[0], 0, &lr_start[0],
               &lr_start[1], &lr_index[0], &lr_value[0], &rhs);
    factor_timer.stop(FactorBtranLowerHyper, factor_timer_clock_pointer);
  }

  if (update_method == kUpdateMethodApf) {
    factor_timer.start(FactorBtranLowerAPF, factor_timer_clock_pointer);
    btranAPF(rhs);
    rhs.tight();
    rhs.pack();
    factor_timer.stop(FactorBtranLowerAPF, factor_timer_clock_pointer);
  }
  factor_timer.stop(FactorBtranLower, factor_timer_clock_pointer);
}

void HFactor::ftranU(HVector& rhs, const double expected_density,
                     HighsTimerClock* factor_timer_clock_pointer) const {
  assert(rhs.count >= 0);
  FactorTimer factor_timer;
  factor_timer.start(FactorFtranUpper, factor_timer_clock_pointer);
  // The update part
  if (update_method == kUpdateMethodFt) {
    factor_timer.start(FactorFtranUpperFT, factor_timer_clock_pointer);
    ftranFT(rhs);
    rhs.tight();
    rhs.pack();
    factor_timer.stop(FactorFtranUpperFT, factor_timer_clock_pointer);
  } else if (update_method == kUpdateMethodMpf) {
    assert(!(update_method == kUpdateMethodMpf));
    factor_timer.start(FactorFtranUpperMPF, factor_timer_clock_pointer);
    ftranMPF(rhs);
    rhs.tight();
    rhs.pack();
    factor_timer.stop(FactorFtranUpperMPF, factor_timer_clock_pointer);
  }

  // The regular part
  //
  // Determine style of solve
  const double current_density = 1.0 * rhs.count / num_row;
  const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel ||
                            expected_density > kHyperFtranU;
  if (sparse_solve) {
    const bool report_ftran_upper_sparse =
        false;  // current_density < kHyperCancel;
    HighsInt use_clock;
    if (current_density < 0.1)
      use_clock = FactorFtranUpperSps2;
    else if (current_density < 0.5)
      use_clock = FactorFtranUpperSps1;
    else
      use_clock = FactorFtranUpperSps0;
    factor_timer.start(use_clock, factor_timer_clock_pointer);
    // Alias to non constant
    double rhs_synthetic_tick = 0;
    // Alias to RHS
    HighsInt* rhs_index = &rhs.index[0];
    double* rhs_array = &rhs.array[0];
    // Alias to factor U
    const HighsInt* u_start = &this->u_start[0];
    const HighsInt* u_end = &this->u_last_p[0];
    const HighsInt* u_index =
        this->u_index.size() > 0 ? &this->u_index[0] : NULL;
    const double* u_value = this->u_value.size() > 0 ? &this->u_value[0] : NULL;
    // Local accumulation of RHS count
    HighsInt rhs_count = 0;
    // Transform
    HighsInt u_pivot_count = u_pivot_index.size();
    for (HighsInt i_logic = u_pivot_count - 1; i_logic >= 0; i_logic--) {
      // Skip void
      if (u_pivot_index[i_logic] == -1) continue;
      // Normal part
      const HighsInt pivotRow = u_pivot_index[i_logic];
      double pivot_multiplier = rhs_array[pivotRow];
      if (fabs(pivot_multiplier) > kHighsTiny) {
        pivot_multiplier /= u_pivot_value[i_logic];
        rhs_index[rhs_count++] = pivotRow;
        rhs_array[pivotRow] = pivot_multiplier;
        const HighsInt start = u_start[i_logic];
        const HighsInt end = u_end[i_logic];
        if (i_logic >= num_row) {
          rhs_synthetic_tick += (end - start);
        }
        for (HighsInt k = start; k < end; k++)
          rhs_array[u_index[k]] -= pivot_multiplier * u_value[k];
      } else
        rhs_array[pivotRow] = 0;
    }
    // Save the count
    rhs.count = rhs_count;
    rhs.synthetic_tick +=
        rhs_synthetic_tick * 15 + (u_pivot_count - num_row) * 10;
    factor_timer.stop(use_clock, factor_timer_clock_pointer);
    if (report_ftran_upper_sparse) {
      const double final_density = 1.0 * rhs.count / num_row;
      printf(
          "FactorFtranUpperSps: expected_density = %10.4g; current_density = "
          "%10.4g; final_density = %10.4g\n",
          expected_density, current_density, final_density);
    }
  } else {
    HighsInt use_clock = -1;
    if (current_density < 5e-6)
      use_clock = FactorFtranUpperHyper5;
    else if (current_density < 1e-5)
      use_clock = FactorFtranUpperHyper4;
    else if (current_density < 1e-4)
      use_clock = FactorFtranUpperHyper3;
    else if (current_density < 1e-3)
      use_clock = FactorFtranUpperHyper2;
    else if (current_density < 1e-2)
      use_clock = FactorFtranUpperHyper1;
    else
      use_clock = FactorFtranUpperHyper0;
    factor_timer.start(use_clock, factor_timer_clock_pointer);
    const HighsInt* u_index =
        this->u_index.size() > 0 ? &this->u_index[0] : NULL;
    const double* u_value = this->u_value.size() > 0 ? &this->u_value[0] : NULL;
    solveHyper(num_row, &u_pivot_lookup[0], &u_pivot_index[0],
               &u_pivot_value[0], &u_start[0], &u_last_p[0], &u_index[0],
               &u_value[0], &rhs);
    factor_timer.stop(use_clock, factor_timer_clock_pointer);
  }
  if (update_method == kUpdateMethodPf) {
    assert(!(update_method == kUpdateMethodPf));
    factor_timer.start(FactorFtranUpperPF, factor_timer_clock_pointer);
    ftranPF(rhs);
    rhs.tight();
    rhs.pack();
    factor_timer.stop(FactorFtranUpperPF, factor_timer_clock_pointer);
  }
  factor_timer.stop(FactorFtranUpper, factor_timer_clock_pointer);
}

void HFactor::btranU(HVector& rhs, const double expected_density,
                     HighsTimerClock* factor_timer_clock_pointer) const {
  FactorTimer factor_timer;
  factor_timer.start(FactorBtranUpper, factor_timer_clock_pointer);
  if (update_method == kUpdateMethodPf) {
    assert(!(update_method == kUpdateMethodPf));
    factor_timer.start(FactorBtranUpperPF, factor_timer_clock_pointer);
    btranPF(rhs);
    factor_timer.stop(FactorBtranUpperPF, factor_timer_clock_pointer);
  }

  // The regular part
  //
  // Determine style of solve
  const double current_density = 1.0 * rhs.count / num_row;
  const bool sparse_solve = rhs.count < 0 || current_density > kHyperCancel ||
                            expected_density > kHyperBtranU;
  if (sparse_solve) {
    factor_timer.start(FactorBtranUpperSps, factor_timer_clock_pointer);
    // Alias to non constant
    double rhs_synthetic_tick = 0;
    // Alias to RHS
    double* rhs_array = &rhs.array[0];
    HighsInt* rhs_index = &rhs.index[0];
    // Alias to factor U
    const HighsInt* ur_start = &this->ur_start[0];
    const HighsInt* ur_end = &this->ur_lastp[0];
    const HighsInt* ur_index = &this->ur_index[0];
    const double* ur_value = &this->ur_value[0];
    // Local accumulation of RHS count
    HighsInt rhs_count = 0;
    // Transform
    HighsInt u_pivot_count = u_pivot_index.size();
    for (HighsInt i_logic = 0; i_logic < u_pivot_count; i_logic++) {
      // Skip void
      if (u_pivot_index[i_logic] == -1) continue;
      // Normal part
      const HighsInt pivotRow = u_pivot_index[i_logic];
      double pivot_multiplier = rhs_array[pivotRow];
      if (fabs(pivot_multiplier) > kHighsTiny) {
        pivot_multiplier /= u_pivot_value[i_logic];
        rhs_index[rhs_count++] = pivotRow;
        rhs_array[pivotRow] = pivot_multiplier;
        const HighsInt start = ur_start[i_logic];
        const HighsInt end = ur_end[i_logic];
        if (i_logic >= num_row) {
          rhs_synthetic_tick += (end - start);
        }
        for (HighsInt k = start; k < end; k++)
          rhs_array[ur_index[k]] -= pivot_multiplier * ur_value[k];
      } else
        rhs_array[pivotRow] = 0;
    }
    // Save the count
    rhs.count = rhs_count;
    rhs.synthetic_tick +=
        rhs_synthetic_tick * 15 + (u_pivot_count - num_row) * 10;
    factor_timer.stop(FactorBtranUpperSps, factor_timer_clock_pointer);
  } else {
    factor_timer.start(FactorBtranUpperHyper, factor_timer_clock_pointer);
    solveHyper(num_row, &u_pivot_lookup[0], &u_pivot_index[0],
               &u_pivot_value[0], &ur_start[0], &ur_lastp[0], &ur_index[0],
               &ur_value[0], &rhs);
    factor_timer.stop(FactorBtranUpperHyper, factor_timer_clock_pointer);
  }

  // The update part
  assert(rhs.count >= 0);
  if (update_method == kUpdateMethodFt) {
    factor_timer.start(FactorBtranUpperFT, factor_timer_clock_pointer);
    rhs.tight();
    rhs.pack();
    btranFT(rhs);
    rhs.tight();
    factor_timer.stop(FactorBtranUpperFT, factor_timer_clock_pointer);
  }
  if (update_method == kUpdateMethodMpf) {
    assert(!(update_method == kUpdateMethodMpf));
    factor_timer.start(FactorBtranUpperMPF, factor_timer_clock_pointer);
    rhs.tight();
    rhs.pack();
    btranMPF(rhs);
    rhs.tight();
    factor_timer.stop(FactorBtranUpperMPF, factor_timer_clock_pointer);
  }
  factor_timer.stop(FactorBtranUpper, factor_timer_clock_pointer);
}

void HFactor::ftranFT(HVector& vector) const {
  // Alias to non constant
  assert(vector.count >= 0);
  HighsInt rhs_count = vector.count;
  HighsInt* rhs_index = &vector.index[0];
  double* rhs_array = &vector.array[0];
  // Alias to PF buffer
  const HighsInt pf_pivot_count = pf_pivot_index.size();
  HighsInt* pf_pivot_index = NULL;
  if (this->pf_pivot_index.size() > 0)
    pf_pivot_index = (HighsInt*)&this->pf_pivot_index[0];

  const HighsInt* pf_start =
      this->pf_start.size() > 0 ? &this->pf_start[0] : NULL;
  const HighsInt* pf_index =
      this->pf_index.size() > 0 ? &this->pf_index[0] : NULL;
  const double* pf_value =
      this->pf_value.size() > 0 ? &this->pf_value[0] : NULL;
  for (HighsInt i = 0; i < pf_pivot_count; i++) {
    HighsInt iRow = pf_pivot_index[i];
    double value0 = rhs_array[iRow];
    double value1 = value0;
    const HighsInt start = pf_start[i];
    const HighsInt end = pf_start[i + 1];
    for (HighsInt k = start; k < end; k++)
      value1 -= rhs_array[pf_index[k]] * pf_value[k];
    // This would skip the situation where they are both zeros
    if (value0 || value1) {
      if (value0 == 0) rhs_index[rhs_count++] = iRow;
      rhs_array[iRow] = (fabs(value1) < kHighsTiny) ? kHighsZero : value1;
    }
  }
  // Save count back
  vector.count = rhs_count;
  vector.synthetic_tick += pf_pivot_count * 20 + pf_start[pf_pivot_count] * 5;
  if (pf_start[pf_pivot_count] / (pf_pivot_count + 1) < 5) {
    vector.synthetic_tick += pf_start[pf_pivot_count] * 5;
  }
}

void HFactor::btranFT(HVector& vector) const {
  // Alias to non constant
  assert(vector.count >= 0);
  HighsInt rhs_count = vector.count;
  HighsInt* rhs_index = &vector.index[0];
  double* rhs_array = &vector.array[0];
  // Alias to PF buffer
  const HighsInt pf_pivot_count = pf_pivot_index.size();
  const HighsInt* pf_pivot_index =
      this->pf_pivot_index.size() > 0 ? &this->pf_pivot_index[0] : NULL;
  const HighsInt* pf_start =
      this->pf_start.size() > 0 ? &this->pf_start[0] : NULL;
  const HighsInt* pf_index =
      this->pf_index.size() > 0 ? &this->pf_index[0] : NULL;
  const double* pf_value =
      this->pf_value.size() > 0 ? &this->pf_value[0] : NULL;
  // Apply row ETA backward
  double rhs_synthetic_tick = 0;
  for (HighsInt i = pf_pivot_count - 1; i >= 0; i--) {
    HighsInt pivotRow = pf_pivot_index[i];
    double pivot_multiplier = rhs_array[pivotRow];
    if (pivot_multiplier) {
      const HighsInt start = pf_start[i];
      const HighsInt end = pf_start[i + 1];
      rhs_synthetic_tick += (end - start);
      for (HighsInt k = start; k < end; k++) {
        HighsInt iRow = pf_index[k];
        double value0 = rhs_array[iRow];
        double value1 = value0 - pivot_multiplier * pf_value[k];
        if (value0 == 0) rhs_index[rhs_count++] = iRow;
        rhs_array[iRow] = (fabs(value1) < kHighsTiny) ? kHighsZero : value1;
      }
    }
  }
  vector.synthetic_tick += rhs_synthetic_tick * 15 + pf_pivot_count * 10;
  // Save count back
  vector.count = rhs_count;
}

void HFactor::ftranPF(HVector& vector) const {
  // Alias to PF buffer
  const HighsInt pf_pivot_count = pf_pivot_index.size();
  const HighsInt* pf_pivot_index = &this->pf_pivot_index[0];
  const double* pf_pivot_value = &this->pf_pivot_value[0];
  const HighsInt* pf_start = &this->pf_start[0];
  const HighsInt* pf_index = &this->pf_index[0];
  const double* pf_value = &this->pf_value[0];

  // Alias to non constant
  HighsInt rhs_count = vector.count;
  HighsInt* rhs_index = &vector.index[0];
  double* rhs_array = &vector.array[0];

  // Forwardly
  for (HighsInt i = 0; i < pf_pivot_count; i++) {
    HighsInt pivotRow = pf_pivot_index[i];
    double pivot_multiplier = rhs_array[pivotRow];
    if (fabs(pivot_multiplier) > kHighsTiny) {
      pivot_multiplier /= pf_pivot_value[i];
      rhs_array[pivotRow] = pivot_multiplier;
      for (HighsInt k = pf_start[i]; k < pf_start[i + 1]; k++) {
        const HighsInt index = pf_index[k];
        const double value0 = rhs_array[index];
        const double value1 = value0 - pivot_multiplier * pf_value[k];
        if (value0 == 0) rhs_index[rhs_count++] = index;
        rhs_array[index] = (fabs(value1) < kHighsTiny) ? kHighsZero : value1;
      }
    }
  }

  // Save count
  vector.count = rhs_count;
}

void HFactor::btranPF(HVector& vector) const {
  // Alias to PF buffer
  const HighsInt pf_pivot_count = pf_pivot_index.size();
  const HighsInt* pf_pivot_index = &this->pf_pivot_index[0];
  const double* pf_pivot_value = &this->pf_pivot_value[0];
  const HighsInt* pf_start = &this->pf_start[0];
  const HighsInt* pf_index = &this->pf_index[0];
  const double* pf_value = &this->pf_value[0];

  // Alias to non constant
  HighsInt rhs_count = vector.count;
  HighsInt* rhs_index = &vector.index[0];
  double* rhs_array = &vector.array[0];

  // Backwardly
  for (HighsInt i = pf_pivot_count - 1; i >= 0; i--) {
    HighsInt pivotRow = pf_pivot_index[i];
    double pivot_multiplier = rhs_array[pivotRow];
    for (HighsInt k = pf_start[i]; k < pf_start[i + 1]; k++)
      pivot_multiplier -= pf_value[k] * rhs_array[pf_index[k]];
    pivot_multiplier /= pf_pivot_value[i];

    if (rhs_array[pivotRow] == 0) rhs_index[rhs_count++] = pivotRow;
    rhs_array[pivotRow] =
        (fabs(pivot_multiplier) < kHighsTiny) ? 1e-100 : pivot_multiplier;
  }

  // Save count
  vector.count = rhs_count;
}

void HFactor::ftranMPF(HVector& vector) const {
  // Alias to non constant
  HighsInt rhs_count = vector.count;
  HighsInt* rhs_index = &vector.index[0];
  double* rhs_array = &vector.array[0];

  // Forwardly
  HighsInt pf_pivot_count = pf_pivot_value.size();
  for (HighsInt i = 0; i < pf_pivot_count; i++) {
    solveMatrixT(pf_start[i * 2 + 1], pf_start[i * 2 + 2], pf_start[i * 2],
                 pf_start[i * 2 + 1], &pf_index[0], &pf_value[0],
                 pf_pivot_value[i], &rhs_count, rhs_index, rhs_array);
  }

  // Remove cancellation
  vector.count = rhs_count;
}

void HFactor::btranMPF(HVector& vector) const {
  // Alias to non constant
  HighsInt rhs_count = vector.count;
  HighsInt* rhs_index = &vector.index[0];
  double* rhs_array = &vector.array[0];

  // Backwardly
  for (HighsInt i = pf_pivot_value.size() - 1; i >= 0; i--) {
    solveMatrixT(pf_start[i * 2], pf_start[i * 2 + 1], pf_start[i * 2 + 1],
                 pf_start[i * 2 + 2], &pf_index[0], &pf_value[0],
                 pf_pivot_value[i], &rhs_count, rhs_index, rhs_array);
  }

  // Remove cancellation
  vector.count = rhs_count;
}

void HFactor::ftranAPF(HVector& vector) const {
  // Alias to non constant
  HighsInt rhs_count = vector.count;
  HighsInt* rhs_index = &vector.index[0];
  double* rhs_array = &vector.array[0];

  // Backwardly
  HighsInt pf_pivot_count = pf_pivot_value.size();
  for (HighsInt i = pf_pivot_count - 1; i >= 0; i--) {
    solveMatrixT(pf_start[i * 2 + 1], pf_start[i * 2 + 2], pf_start[i * 2],
                 pf_start[i * 2 + 1], &pf_index[0], &pf_value[0],
                 pf_pivot_value[i], &rhs_count, rhs_index, rhs_array);
  }

  // Remove cancellation
  vector.count = rhs_count;
}

void HFactor::btranAPF(HVector& vector) const {
  // Alias to non constant
  HighsInt rhs_count = vector.count;
  HighsInt* rhs_index = &vector.index[0];
  double* rhs_array = &vector.array[0];

  // Forwardly
  HighsInt pf_pivot_count = pf_pivot_value.size();
  for (HighsInt i = 0; i < pf_pivot_count; i++) {
    solveMatrixT(pf_start[i * 2], pf_start[i * 2 + 1], pf_start[i * 2 + 1],
                 pf_start[i * 2 + 2], &pf_index[0], &pf_value[0],
                 pf_pivot_value[i], &rhs_count, rhs_index, rhs_array);
  }
  vector.count = rhs_count;
}

void HFactor::updateCFT(HVector* aq, HVector* ep, HighsInt* iRow
                        //, HighsInt* hint
) {
  /*
   * In the major update loop, the prefix
   *
   * c(p) = current working pivot
   * p(p) = previous pivot  (0 =< pp < cp)
   */

  HighsInt num_update = 0;
  for (HVector* vec = aq; vec != 0; vec = vec->next) num_update++;

  HVector** aq_work = new HVector*[num_update];
  HVector** ep_work = new HVector*[num_update];

  for (HighsInt i = 0; i < num_update; i++) {
    aq_work[i] = aq;
    ep_work[i] = ep;
    aq = aq->next;
    ep = ep->next;
  }

  // Pivot related buffers
  HighsInt pf_np0 = pf_pivot_index.size();
  HighsInt* p_logic = new HighsInt[num_update];
  double* p_value = new double[num_update];
  double* p_alpha = new double[num_update];
  for (HighsInt cp = 0; cp < num_update; cp++) {
    HighsInt c_row = iRow[cp];
    HighsInt i_logic = u_pivot_lookup[c_row];
    p_logic[cp] = i_logic;
    p_value[cp] = u_pivot_value[i_logic];
    p_alpha[cp] = aq_work[cp]->array[c_row];
  }

  // Temporary U pointers
  HighsInt* t_start = new HighsInt[num_update + 1];
  double* t_pivot = new double[num_update];
  t_start[0] = u_index.size();

  // Logically sorted previous row_ep
  vector<pair<HighsInt, int> > sorted_pp;

  // Major update loop
  for (HighsInt cp = 0; cp < num_update; cp++) {
    // 1. Expand partial FTRAN result to buffer
    iwork.clear();
    for (HighsInt i = 0; i < aq_work[cp]->packCount; i++) {
      HighsInt index = aq_work[cp]->packIndex[i];
      double value = aq_work[cp]->packValue[i];
      iwork.push_back(index);
      dwork[index] = value;
    }

    // 2. Update partial FTRAN result by recent FT matrix
    for (HighsInt pp = 0; pp < cp; pp++) {
      HighsInt p_row = iRow[pp];
      double value = dwork[p_row];
      HighsInt pf_pp = pp + pf_np0;
      for (HighsInt i = pf_start[pf_pp]; i < pf_start[pf_pp + 1]; i++)
        value -= dwork[pf_index[i]] * pf_value[i];
      iwork.push_back(p_row);  // OK to duplicate
      dwork[p_row] = value;
    }

    // 3. Store the partial FTRAN result to matirx U
    double ppaq = dwork[iRow[cp]];  // pivot of the partial aq
    dwork[iRow[cp]] = 0;
    HighsInt u_countX = t_start[cp];
    HighsInt u_startX = u_countX;
    for (unsigned i = 0; i < iwork.size(); i++) {
      HighsInt index = iwork[i];
      double value = dwork[index];
      dwork[index] = 0;  // This effectively removes all duplication
      if (fabs(value) > kHighsTiny) {
        u_index.push_back(index);
        u_value.push_back(value);
      }
    }
    u_countX = u_index.size();
    t_start[cp + 1] = u_countX;
    t_pivot[cp] = p_value[cp] * p_alpha[cp];

    // 4. Expand partial BTRAN result to buffer
    iwork.clear();
    for (HighsInt i = 0; i < ep_work[cp]->packCount; i++) {
      HighsInt index = ep_work[cp]->packIndex[i];
      double value = ep_work[cp]->packValue[i];
      iwork.push_back(index);
      dwork[index] = value;
    }

    // 5. Delete logical later rows (in logical order)
    for (HighsInt isort = 0; isort < cp; isort++) {
      HighsInt pp = sorted_pp[isort].second;
      HighsInt p_row = iRow[pp];
      double multiplier = -p_value[pp] * dwork[p_row];
      if (fabs(dwork[p_row]) > kHighsTiny) {
        for (HighsInt i = 0; i < ep_work[pp]->packCount; i++) {
          HighsInt index = ep_work[pp]->packIndex[i];
          double value = ep_work[pp]->packValue[i];
          iwork.push_back(index);
          dwork[index] += value * multiplier;
        }
      }
      dwork[p_row] = 0;  // Force to be 0
    }

    // 6. Update partial BTRAN result by recent U columns
    for (HighsInt pp = 0; pp < cp; pp++) {
      HighsInt kpivot = iRow[pp];
      double value = dwork[kpivot];
      for (HighsInt k = t_start[pp]; k < t_start[pp + 1]; k++)
        value -= dwork[u_index[k]] * u_value[k];
      value /= t_pivot[pp];
      iwork.push_back(kpivot);
      dwork[kpivot] = value;  // Again OK to duplicate
    }

    // 6.x compute current alpha
    double thex = 0;
    for (HighsInt k = u_startX; k < u_countX; k++) {
      HighsInt index = u_index[k];
      double value = u_value[k];
      thex += dwork[index] * value;
    }
    t_pivot[cp] = ppaq + thex * p_value[cp];

    // 7. Store BTRAN result to FT elimination, update logic helper
    dwork[iRow[cp]] = 0;
    double pivot_multiplier = -p_value[cp];
    for (unsigned i = 0; i < iwork.size(); i++) {
      HighsInt index = iwork[i];
      double value = dwork[index];
      dwork[index] = 0;
      if (fabs(value) > kHighsTiny) {
        pf_index.push_back(index);
        pf_value.push_back(value * pivot_multiplier);
      }
    }
    pf_pivot_index.push_back(iRow[cp]);
    u_total_x += pf_index.size() - pf_start.back();
    pf_start.push_back(pf_index.size());

    // 8. Update the sorted ep
    sorted_pp.push_back(make_pair(p_logic[cp], cp));
    pdqsort(sorted_pp.begin(), sorted_pp.end());
  }

  // Now modify the U matrix
  for (HighsInt cp = 0; cp < num_update; cp++) {
    // 1. Delete pivotal row from U
    HighsInt cIndex = iRow[cp];
    HighsInt cLogic = p_logic[cp];
    u_total_x -= ur_lastp[cLogic] - ur_start[cLogic];
    for (HighsInt k = ur_start[cLogic]; k < ur_lastp[cLogic]; k++) {
      // Find the pivotal position
      HighsInt i_logic = u_pivot_lookup[ur_index[k]];
      HighsInt i_find = u_start[i_logic];
      HighsInt i_last = --u_last_p[i_logic];
      for (; i_find <= i_last; i_find++)
        if (u_index[i_find] == cIndex) break;
      // Put last to find, and delete last
      u_index[i_find] = u_index[i_last];
      u_value[i_find] = u_value[i_last];
    }

    // 2. Delete pivotal column from UR
    u_total_x -= u_last_p[cLogic] - u_start[cLogic];
    for (HighsInt k = u_start[cLogic]; k < u_last_p[cLogic]; k++) {
      // Find the pivotal position
      HighsInt i_logic = u_pivot_lookup[u_index[k]];
      HighsInt i_find = ur_start[i_logic];
      HighsInt i_last = --ur_lastp[i_logic];
      for (; i_find <= i_last; i_find++)
        if (ur_index[i_find] == cIndex) break;
      // Put last to find, and delete last
      ur_space[i_logic]++;
      ur_index[i_find] = ur_index[i_last];
      ur_value[i_find] = ur_value[i_last];
    }

    // 3. Insert the (stored) partial FTRAN to the row matrix
    HighsInt u_startX = t_start[cp];
    HighsInt u_endX = t_start[cp + 1];
    u_total_x += u_endX - u_startX;
    // Store column as UR elements
    for (HighsInt k = u_startX; k < u_endX; k++) {
      // Which ETA file
      HighsInt i_logic = u_pivot_lookup[u_index[k]];

      // Move row to the end if necessary
      if (ur_space[i_logic] == 0) {
        // Make pointers
        HighsInt row_start = ur_start[i_logic];
        HighsInt row_count = ur_lastp[i_logic] - row_start;
        HighsInt new_start = ur_index.size();
        HighsInt new_space = row_count * 1.1 + 5;

        // Check matrix UR
        ur_index.resize(new_start + new_space);
        ur_value.resize(new_start + new_space);

        // Move elements
        HighsInt i_from = row_start;
        HighsInt i_end = row_start + row_count;
        HighsInt i_to = new_start;
        copy(&ur_index[i_from], &ur_index[i_end], &ur_index[i_to]);
        copy(&ur_value[i_from], &ur_value[i_end], &ur_value[i_to]);

        // Save new pointers
        ur_start[i_logic] = new_start;
        ur_lastp[i_logic] = new_start + row_count;
        ur_space[i_logic] = new_space - row_count;
      }

      // Put into the next available space
      ur_space[i_logic]--;
      HighsInt i_put = ur_lastp[i_logic]++;
      ur_index[i_put] = cIndex;
      ur_value[i_put] = u_value[k];
    }

    // 4. Save pointers
    u_start.push_back(u_startX);
    u_last_p.push_back(u_endX);

    ur_start.push_back(ur_start[cLogic]);
    ur_lastp.push_back(ur_start[cLogic]);
    ur_space.push_back(ur_space[cLogic] + ur_lastp[cLogic] - ur_start[cLogic]);

    u_pivot_lookup[cIndex] = u_pivot_index.size();
    u_pivot_index[cLogic] = -1;
    u_pivot_index.push_back(cIndex);
    u_pivot_value.push_back(t_pivot[cp]);
  }

  //    // See if we want refactor
  //    if (u_total_x > u_merit_x && pf_pivot_index.size() > 100)
  //        *hint = 1;
  delete[] aq_work;
  delete[] ep_work;
  delete[] p_logic;
  delete[] p_value;
  delete[] p_alpha;
  delete[] t_start;
  delete[] t_pivot;
}

void HFactor::updateFT(HVector* aq, HVector* ep, HighsInt iRow
                       //, HighsInt* hint
) {
  // Store pivot
  HighsInt p_logic = u_pivot_lookup[iRow];
  double pivot = u_pivot_value[p_logic];
  double alpha = aq->array[iRow];
  u_pivot_index[p_logic] = -1;

  // Delete pivotal row from U
  for (HighsInt k = ur_start[p_logic]; k < ur_lastp[p_logic]; k++) {
    // Find the pivotal position
    HighsInt i_logic = u_pivot_lookup[ur_index[k]];
    HighsInt i_find = u_start[i_logic];
    HighsInt i_last = --u_last_p[i_logic];
    for (; i_find <= i_last; i_find++)
      if (u_index[i_find] == iRow) break;
    // Put last to find, and delete last
    u_index[i_find] = u_index[i_last];
    u_value[i_find] = u_value[i_last];
  }

  // Delete pivotal column from UR
  for (HighsInt k = u_start[p_logic]; k < u_last_p[p_logic]; k++) {
    // Find the pivotal position
    HighsInt i_logic = u_pivot_lookup[u_index[k]];
    HighsInt i_find = ur_start[i_logic];
    HighsInt i_last = --ur_lastp[i_logic];
    for (; i_find <= i_last; i_find++)
      if (ur_index[i_find] == iRow) break;
    // Put last to find, and delete last
    ur_space[i_logic]++;
    ur_index[i_find] = ur_index[i_last];
    ur_value[i_find] = ur_value[i_last];
  }

  // Store column to U
  u_start.push_back(u_index.size());
  for (HighsInt i = 0; i < aq->packCount; i++)
    if (aq->packIndex[i] != iRow) {
      u_index.push_back(aq->packIndex[i]);
      u_value.push_back(aq->packValue[i]);
    }
  u_last_p.push_back(u_index.size());
  HighsInt u_startX = u_start.back();
  HighsInt u_endX = u_last_p.back();
  u_total_x += u_endX - u_startX + 1;

  // Store column as UR elements
  for (HighsInt k = u_startX; k < u_endX; k++) {
    // Which ETA file
    HighsInt i_logic = u_pivot_lookup[u_index[k]];

    // Move row to the end if necessary
    if (ur_space[i_logic] == 0) {
      // Make pointers
      HighsInt row_start = ur_start[i_logic];
      HighsInt row_count = ur_lastp[i_logic] - row_start;
      HighsInt new_start = ur_index.size();
      HighsInt new_space = row_count * 1.1 + 5;

      // Check matrix UR
      ur_index.resize(new_start + new_space);
      ur_value.resize(new_start + new_space);

      // Move elements
      HighsInt i_from = row_start;
      HighsInt i_end = row_start + row_count;
      HighsInt i_to = new_start;
      copy(&ur_index[i_from], &ur_index[i_end], &ur_index[i_to]);
      copy(&ur_value[i_from], &ur_value[i_end], &ur_value[i_to]);

      // Save new pointers
      ur_start[i_logic] = new_start;
      ur_lastp[i_logic] = new_start + row_count;
      ur_space[i_logic] = new_space - row_count;
    }

    // Put into the next available space
    ur_space[i_logic]--;
    HighsInt i_put = ur_lastp[i_logic]++;
    ur_index[i_put] = iRow;
    ur_value[i_put] = u_value[k];
  }

  // Store UR pointers
  ur_start.push_back(ur_start[p_logic]);
  ur_lastp.push_back(ur_start[p_logic]);
  ur_space.push_back(ur_space[p_logic] + ur_lastp[p_logic] - ur_start[p_logic]);

  // Update pivot count
  u_pivot_lookup[iRow] = u_pivot_index.size();
  u_pivot_index.push_back(iRow);
  u_pivot_value.push_back(pivot * alpha);

  // Store row_ep as R matrix
  for (HighsInt i = 0; i < ep->packCount; i++) {
    if (ep->packIndex[i] != iRow) {
      pf_index.push_back(ep->packIndex[i]);
      pf_value.push_back(-ep->packValue[i] * pivot);
    }
  }
  u_total_x += pf_index.size() - pf_start.back();

  // Store R matrix pivot
  pf_pivot_index.push_back(iRow);
  pf_start.push_back(pf_index.size());

  // Update total countX
  u_total_x -= u_last_p[p_logic] - u_start[p_logic];
  u_total_x -= ur_lastp[p_logic] - ur_start[p_logic];

  //    // See if we want refactor
  //    if (u_total_x > u_merit_x && pf_pivot_index.size() > 100)
  //        *hint = 1;
}

void HFactor::updatePF(HVector* aq, HighsInt iRow, HighsInt* hint) {
  // Check space
  const HighsInt column_count = aq->packCount;
  const HighsInt* variable_index = &aq->packIndex[0];
  const double* columnArray = &aq->packValue[0];

  // Copy the pivotal column
  for (HighsInt i = 0; i < column_count; i++) {
    HighsInt index = variable_index[i];
    double value = columnArray[i];
    if (index != iRow) {
      pf_index.push_back(index);
      pf_value.push_back(value);
    }
  }

  // Save pivot
  pf_pivot_index.push_back(iRow);
  pf_pivot_value.push_back(aq->array[iRow]);
  pf_start.push_back(pf_index.size());

  // Check refactor
  u_total_x += aq->packCount;
  if (u_total_x > u_merit_x) *hint = 1;
}

void HFactor::updateMPF(HVector* aq, HVector* ep, HighsInt iRow,
                        HighsInt* hint) {
  // Store elements
  for (HighsInt i = 0; i < aq->packCount; i++) {
    pf_index.push_back(aq->packIndex[i]);
    pf_value.push_back(aq->packValue[i]);
  }
  HighsInt p_logic = u_pivot_lookup[iRow];
  HighsInt u_startX = u_start[p_logic];
  HighsInt u_endX = u_start[p_logic + 1];
  for (HighsInt k = u_startX; k < u_endX; k++) {
    pf_index.push_back(u_index[k]);
    pf_value.push_back(-u_value[k]);
  }
  pf_index.push_back(iRow);
  pf_value.push_back(-u_pivot_value[p_logic]);
  pf_start.push_back(pf_index.size());

  for (HighsInt i = 0; i < ep->packCount; i++) {
    pf_index.push_back(ep->packIndex[i]);
    pf_value.push_back(ep->packValue[i]);
  }
  pf_start.push_back(pf_index.size());

  // Store pivot
  pf_pivot_value.push_back(aq->array[iRow]);

  // Refactor or not
  u_total_x += aq->packCount + ep->packCount;
  if (u_total_x > u_merit_x) *hint = 1;
}

void HFactor::updateAPF(HVector* aq, HVector* ep, HighsInt iRow
                        //, HighsInt* hint
) {
  // Store elements
  for (HighsInt i = 0; i < aq->packCount; i++) {
    pf_index.push_back(aq->packIndex[i]);
    pf_value.push_back(aq->packValue[i]);
  }

  HighsInt variable_out = basic_index[iRow];
  if (variable_out >= num_col) {
    pf_index.push_back(variable_out - num_col);
    pf_value.push_back(-1);
  } else {
    for (HighsInt k = a_start[variable_out]; k < a_start[variable_out + 1];
         k++) {
      pf_index.push_back(a_index[k]);
      pf_value.push_back(-a_value[k]);
    }
  }
  pf_start.push_back(pf_index.size());

  for (HighsInt i = 0; i < ep->packCount; i++) {
    pf_index.push_back(ep->packIndex[i]);
    pf_value.push_back(ep->packValue[i]);
  }
  pf_start.push_back(pf_index.size());

  // Store pivot
  pf_pivot_value.push_back(aq->array[iRow]);
}

InvertibleRepresentation HFactor::getInvert() const {
  InvertibleRepresentation invert;
  invert.l_pivot_index = this->l_pivot_index;
  invert.l_pivot_lookup = this->l_pivot_lookup;
  invert.l_start = this->l_start;
  invert.l_index = this->l_index;
  invert.l_value = this->l_value;
  invert.lr_start = this->lr_start;
  invert.lr_index = this->lr_index;
  invert.lr_value = this->lr_value;

  invert.u_pivot_lookup = this->u_pivot_lookup;
  invert.u_pivot_index = this->u_pivot_index;
  invert.u_pivot_value = this->u_pivot_value;
  invert.u_start = this->u_start;
  invert.u_last_p = this->u_last_p;
  invert.u_index = this->u_index;
  invert.u_value = this->u_value;

  invert.ur_start = this->ur_start;
  invert.ur_lastp = this->ur_lastp;
  invert.ur_space = this->ur_space;
  invert.ur_index = this->ur_index;
  invert.ur_value = this->ur_value;
  invert.pf_start = this->pf_start;
  invert.pf_index = this->pf_index;
  invert.pf_value = this->pf_value;
  invert.pf_pivot_index = this->pf_pivot_index;
  invert.pf_pivot_value = this->pf_pivot_value;
  return invert;
}

void HFactor::setInvert(const InvertibleRepresentation& invert) {
  this->l_pivot_index = invert.l_pivot_index;
  this->l_pivot_lookup = invert.l_pivot_lookup;
  this->l_start = invert.l_start;
  this->l_index = invert.l_index;
  this->l_value = invert.l_value;
  this->lr_start = invert.lr_start;
  this->lr_index = invert.lr_index;
  this->lr_value = invert.lr_value;

  this->u_pivot_lookup = invert.u_pivot_lookup;
  this->u_pivot_index = invert.u_pivot_index;
  this->u_pivot_value = invert.u_pivot_value;
  this->u_start = invert.u_start;
  this->u_last_p = invert.u_last_p;
  this->u_index = invert.u_index;
  this->u_value = invert.u_value;

  this->ur_start = invert.ur_start;
  this->ur_lastp = invert.ur_lastp;
  this->ur_space = invert.ur_space;
  this->ur_index = invert.ur_index;
  this->ur_value = invert.ur_value;
  this->pf_start = invert.pf_start;
  this->pf_index = invert.pf_index;
  this->pf_value = invert.pf_value;
  this->pf_pivot_index = invert.pf_pivot_index;
  this->pf_pivot_value = invert.pf_pivot_value;
}

void InvertibleRepresentation::clear() {
  this->l_pivot_index.clear();
  this->l_pivot_lookup.clear();
  this->l_start.clear();
  this->l_index.clear();
  this->l_value.clear();
  this->lr_start.clear();
  this->lr_index.clear();
  this->lr_value.clear();

  this->u_pivot_lookup.clear();
  this->u_pivot_index.clear();
  this->u_pivot_value.clear();
  this->u_start.clear();
  this->u_last_p.clear();
  this->u_index.clear();
  this->u_value.clear();

  this->ur_start.clear();
  this->ur_lastp.clear();
  this->ur_space.clear();
  this->ur_index.clear();
  this->ur_value.clear();
  this->pf_start.clear();
  this->pf_index.clear();
  this->pf_value.clear();
  this->pf_pivot_index.clear();
  this->pf_pivot_value.clear();
}
