#include "simplex/HEkkDual.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <set>
#include "lp_data/HighsLpUtils.h"
#include "parallel/HighsParallel.h"
#include "simplex/HEkkPrimal.h"
#include "simplex/SimplexTimer.h"
using std::fabs;
HighsStatus HEkkDual::solve(const bool pass_force_phase2) {
initialiseSolve();
if (debugDualSimplex("Initialise", true) == HighsDebugStatus::kLogicalError)
return ekk_instance_.returnFromSolve(HighsStatus::kError);
if (ekk_instance_.isUnconstrainedLp())
return ekk_instance_.returnFromSolve(HighsStatus::kError);
HighsOptions& options = *ekk_instance_.options_;
HighsSimplexInfo& info = ekk_instance_.info_;
HighsSimplexStatus& status = ekk_instance_.status_;
HighsModelStatus& model_status = ekk_instance_.model_status_;
if (!dualInfoOk(ekk_instance_.lp_)) {
highsLogDev(options.log_options, HighsLogType::kError,
"HPrimalDual::solve has error in dual information\n");
return ekk_instance_.returnFromSolve(HighsStatus::kError);
}
possiblyUseLiDualSteepestEdge();
assert(status.has_invert);
if (!status.has_invert) {
highsLogDev(options.log_options, HighsLogType::kError,
"HDual:: Should enter solve with INVERT\n");
return ekk_instance_.returnFromSolve(HighsStatus::kError);
}
ekk_instance_.initialiseCost(SimplexAlgorithm::kDual, kSolvePhaseUnknown);
ekk_instance_.computeDual();
ekk_instance_.computeSimplexDualInfeasible();
const bool dual_feasible_with_unperturbed_costs =
info.num_dual_infeasibilities == 0;
force_phase2 = pass_force_phase2 ||
info.max_dual_infeasibility * info.max_dual_infeasibility <
ekk_instance_.options_->dual_feasibility_tolerance;
if (ekk_instance_.debug_dual_feasible &&
!dual_feasible_with_unperturbed_costs) {
SimplexBasis& basis = ekk_instance_.basis_;
highsLogDev(
options.log_options, HighsLogType::kWarning,
"Basis should be dual feasible, but duals without cost perturbation "
"have num / max / sum = %4d / %g / %g infeasibilities",
(int)info.num_dual_infeasibilities, info.max_dual_infeasibility,
info.sum_dual_infeasibilities);
if (!force_phase2) {
highsLogDev(options.log_options, HighsLogType::kWarning,
" !!Not forcing phase 2!! basis Id = %d; update count = %d; "
"name = %s\n",
(int)basis.debug_id, (int)basis.debug_update_count,
basis.debug_origin_name.c_str());
} else {
highsLogDev(options.log_options, HighsLogType::kWarning, "\n");
}
}
const bool no_simplex_dual_infeasibilities =
dual_feasible_with_unperturbed_costs || force_phase2;
const bool near_optimal = no_simplex_dual_infeasibilities &&
info.num_primal_infeasibilities < 1000 &&
info.max_primal_infeasibility < 1e-3;
if (near_optimal)
highsLogDev(options.log_options, HighsLogType::kDetailed,
"Dual feasible with unperturbed costs and num / max / sum "
"primal infeasibilities of "
"%" HIGHSINT_FORMAT
" / %g "
"/ %g, so near-optimal\n",
info.num_primal_infeasibilities, info.max_primal_infeasibility,
info.sum_primal_infeasibilities);
const bool perturb_costs = !near_optimal;
if (!perturb_costs)
highsLogDev(options.log_options, HighsLogType::kDetailed,
"Near-optimal, so don't use cost perturbation\n");
ekk_instance_.initialiseCost(SimplexAlgorithm::kDual, kSolvePhaseUnknown,
perturb_costs);
if (ekk_instance_.bailout())
return ekk_instance_.returnFromSolve(HighsStatus::kWarning);
if (status.has_dual_steepest_edge_weights) {
assert((HighsInt)ekk_instance_.dual_edge_weight_.size() >= solver_num_row);
assert((HighsInt)ekk_instance_.scattered_dual_edge_weight_.size() >=
solver_num_tot);
ekk_instance_.devDebugDualSteepestEdgeWeights("before solve");
} else {
ekk_instance_.dual_edge_weight_.assign(solver_num_row, 1.0);
ekk_instance_.scattered_dual_edge_weight_.resize(solver_num_tot);
if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) {
if (ekk_instance_.logicalBasis()) {
status.has_dual_steepest_edge_weights = true;
} else {
if (near_optimal) {
highsLogDev(
options.log_options, HighsLogType::kDetailed,
"Basis is not logical, but near-optimal, so use Devex rather "
"than compute steepest edge weights\n");
edge_weight_mode = EdgeWeightMode::kDevex;
assert(!status.has_dual_steepest_edge_weights);
} else {
highsLogDev(
options.log_options, HighsLogType::kDetailed,
"Basis is not logical, so compute steepest edge weights\n");
ekk_instance_.computeDualSteepestEdgeWeights(true);
status.has_dual_steepest_edge_weights = true;
}
}
}
if (edge_weight_mode == EdgeWeightMode::kDevex) initialiseDevexFramework();
if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) {
assert(status.has_dual_steepest_edge_weights);
} else {
assert(!status.has_dual_steepest_edge_weights);
}
}
info.backtracking_basis_edge_weight_.resize(solver_num_tot);
if (perturb_costs) {
ekk_instance_.computeDual();
computeDualInfeasibilitiesWithFixedVariableFlips();
dualInfeasCount = info.num_dual_infeasibilities;
}
if (force_phase2) {
solve_phase = kSolvePhase2;
} else {
solve_phase = dualInfeasCount > 0 ? kSolvePhase1 : kSolvePhase2;
}
if (ekk_instance_.debugOkForSolve(SimplexAlgorithm::kDual, solve_phase) ==
HighsDebugStatus::kLogicalError)
return ekk_instance_.returnFromSolve(HighsStatus::kError);
while (solve_phase) {
HighsInt it0 = ekk_instance_.iteration_count_;
status.has_dual_objective_value = false;
if (solve_phase == kSolvePhaseUnknown) {
ekk_instance_.initialiseBound(SimplexAlgorithm::kDual,
kSolvePhaseUnknown);
ekk_instance_.initialiseNonbasicValueAndMove();
computeDualInfeasibilitiesWithFixedVariableFlips();
dualInfeasCount = info.num_dual_infeasibilities;
solve_phase = dualInfeasCount > 0 ? kSolvePhase1 : kSolvePhase2;
if (info.backtracking_) {
ekk_instance_.initialiseBound(SimplexAlgorithm::kDual, solve_phase);
ekk_instance_.initialiseNonbasicValueAndMove();
info.backtracking_ = false;
}
}
assert(solve_phase == kSolvePhase1 || solve_phase == kSolvePhase2);
if (solve_phase == kSolvePhase1) {
analysis->simplexTimerStart(SimplexDualPhase1Clock);
solvePhase1();
analysis->simplexTimerStop(SimplexDualPhase1Clock);
info.dual_phase1_iteration_count +=
(ekk_instance_.iteration_count_ - it0);
} else if (solve_phase == kSolvePhase2) {
analysis->simplexTimerStart(SimplexDualPhase2Clock);
solvePhase2();
analysis->simplexTimerStop(SimplexDualPhase2Clock);
info.dual_phase2_iteration_count +=
(ekk_instance_.iteration_count_ - it0);
} else {
model_status = HighsModelStatus::kSolveError;
return ekk_instance_.returnFromSolve(HighsStatus::kError);
}
if (ekk_instance_.solve_bailout_)
return ekk_instance_.returnFromSolve(HighsStatus::kWarning);
assert(solve_phase >= kSolvePhaseMin && solve_phase <= kSolvePhaseMax);
if (solve_phase == kSolvePhaseTabooBasis) {
ekk_instance_.model_status_ = HighsModelStatus::kUnknown;
return ekk_instance_.returnFromSolve(HighsStatus::kWarning);
}
if (solve_phase == kSolvePhaseError) {
assert(model_status == HighsModelStatus::kSolveError);
return ekk_instance_.returnFromSolve(HighsStatus::kError);
}
if (solve_phase == kSolvePhaseExit) {
assert(model_status == HighsModelStatus::kUnboundedOrInfeasible ||
model_status == HighsModelStatus::kInfeasible);
break;
}
if (solve_phase == kSolvePhaseOptimalCleanup ||
solve_phase == kSolvePhasePrimalInfeasibleCleanup) {
break;
}
}
assert(!ekk_instance_.solve_bailout_);
assert(solve_phase == kSolvePhaseExit || solve_phase == kSolvePhaseUnknown ||
solve_phase == kSolvePhaseOptimal ||
solve_phase == kSolvePhaseOptimalCleanup ||
solve_phase == kSolvePhasePrimalInfeasibleCleanup);
if (solve_phase == kSolvePhaseOptimalCleanup ||
solve_phase == kSolvePhasePrimalInfeasibleCleanup) {
ekk_instance_.dual_simplex_cleanup_level_++;
if (solve_phase == kSolvePhasePrimalInfeasibleCleanup) {
ekk_instance_.computeSimplexInfeasible();
}
if (ekk_instance_.dual_simplex_cleanup_level_ >
options.max_dual_simplex_cleanup_level) {
highsLogDev(options.log_options, HighsLogType::kWarning,
"HEkkDual:: Cannot use level %" HIGHSINT_FORMAT
" primal simplex cleanup for %" HIGHSINT_FORMAT
" dual infeasibilities\n",
ekk_instance_.dual_simplex_cleanup_level_,
info.num_dual_infeasibilities);
if (solve_phase == kSolvePhaseOptimalCleanup) {
ekk_instance_.model_status_ = HighsModelStatus::kOptimal;
} else {
ekk_instance_.model_status_ = HighsModelStatus::kInfeasible;
}
} else {
highsLogDev(options.log_options, HighsLogType::kInfo,
"HEkkDual:: Using primal simplex to try to clean up num / "
"max / sum = %" HIGHSINT_FORMAT
" / %g / %g dual infeasibilities\n",
info.num_dual_infeasibilities, info.max_dual_infeasibility,
info.sum_dual_infeasibilities);
HighsStatus return_status = HighsStatus::kOk;
analysis->simplexTimerStart(SimplexPrimalPhase2Clock);
double save_primal_simplex_bound_perturbation_multiplier =
info.primal_simplex_bound_perturbation_multiplier;
info.primal_simplex_bound_perturbation_multiplier = 0;
HEkkPrimal primal_solver(ekk_instance_);
HighsStatus call_status = primal_solver.solve(true);
info.primal_simplex_bound_perturbation_multiplier =
save_primal_simplex_bound_perturbation_multiplier;
analysis->simplexTimerStop(SimplexPrimalPhase2Clock);
assert(ekk_instance_.called_return_from_solve_);
return_status = interpretCallStatus(options.log_options, call_status,
return_status, "HEkkPrimal::solve");
ekk_instance_.called_return_from_solve_ = false;
if (return_status != HighsStatus::kOk)
return ekk_instance_.returnFromSolve(return_status);
if (ekk_instance_.model_status_ == HighsModelStatus::kOptimal &&
info.num_primal_infeasibilities + info.num_dual_infeasibilities)
highsLogDev(options.log_options, HighsLogType::kWarning,
"HEkkDual:: Primal simplex clean up yields optimality, "
"but with %" HIGHSINT_FORMAT
" (max %g) primal infeasibilities and " HIGHSINT_FORMAT
" (max %g) dual infeasibilities\n",
info.num_primal_infeasibilities,
info.max_primal_infeasibility,
info.num_dual_infeasibilities, info.max_dual_infeasibility);
}
}
assert(model_status == HighsModelStatus::kOptimal ||
model_status == HighsModelStatus::kInfeasible ||
model_status == HighsModelStatus::kUnbounded ||
model_status == HighsModelStatus::kUnboundedOrInfeasible);
if (ekk_instance_.debugOkForSolve(SimplexAlgorithm::kDual, solve_phase) ==
HighsDebugStatus::kLogicalError)
return ekk_instance_.returnFromSolve(HighsStatus::kError);
return ekk_instance_.returnFromSolve(HighsStatus::kOk);
}
void HEkkDual::initialiseInstance() {
solver_num_col = ekk_instance_.lp_.num_col_;
solver_num_row = ekk_instance_.lp_.num_row_;
solver_num_tot = solver_num_col + solver_num_row;
inv_solver_num_row = 1.0 / solver_num_row;
a_matrix = &ekk_instance_.lp_.a_matrix_;
simplex_nla = &ekk_instance_.simplex_nla_;
analysis = &ekk_instance_.analysis_;
jMove = ekk_instance_.basis_.nonbasicMove_.data();
workDual = ekk_instance_.info_.workDual_.data();
workValue = ekk_instance_.info_.workValue_.data();
workRange = ekk_instance_.info_.workRange_.data();
baseLower = ekk_instance_.info_.baseLower_.data();
baseUpper = ekk_instance_.info_.baseUpper_.data();
baseValue = ekk_instance_.info_.baseValue_.data();
col_DSE.setup(solver_num_row);
col_BFRT.setup(solver_num_row);
col_aq.setup(solver_num_row);
row_ep.setup(solver_num_row);
row_ap.setup(solver_num_col);
dev_row_ep.setup(solver_num_row);
dev_col_DSE.setup(solver_num_row);
dualRow.setup();
dualRHS.setup();
}
void HEkkDual::initialiseInstanceParallel(HEkk& simplex) {
if (ekk_instance_.info_.simplex_strategy == kSimplexStrategyDualPlain) return;
const HighsInt num_concurrency = ekk_instance_.info_.num_concurrency;
HighsInt pass_num_slice;
if (ekk_instance_.info_.simplex_strategy == kSimplexStrategyDualTasks) {
pass_num_slice = num_concurrency - 2;
assert(pass_num_slice > 0);
if (pass_num_slice <= 0) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kWarning,
"SIP trying to use using %" HIGHSINT_FORMAT
" slices due to concurrency (%" HIGHSINT_FORMAT
") being too small: results unpredictable\n",
pass_num_slice, num_concurrency);
}
} else {
multi_num = num_concurrency;
if (multi_num < 1) multi_num = 1;
if (multi_num > kSimplexConcurrencyLimit)
multi_num = kSimplexConcurrencyLimit;
for (HighsInt i = 0; i < multi_num; i++) {
multi_choice[i].row_out = -1;
multi_choice[i].row_ep.setup(solver_num_row);
multi_choice[i].col_aq.setup(solver_num_row);
multi_choice[i].col_BFRT.setup(solver_num_row);
}
pass_num_slice = max(multi_num - 1, HighsInt{1});
assert(pass_num_slice > 0);
if (pass_num_slice <= 0) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kWarning,
"PAMI trying to use using %" HIGHSINT_FORMAT
" slices due to concurrency (%" HIGHSINT_FORMAT
") being too small: results unpredictable\n",
pass_num_slice, num_concurrency);
}
}
for (HighsInt i = 0; i < pass_num_slice; i++)
slice_dualRow.push_back(HEkkDualRow(simplex));
initSlice(pass_num_slice);
multi_iteration = 0;
}
void HEkkDual::initSlice(const HighsInt initial_num_slice) {
slice_num = initial_num_slice;
if (slice_num < 1) slice_num = 1;
assert(slice_num <= kHighsSlicedLimit);
if (slice_num > kHighsSlicedLimit) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kWarning,
"WARNING: %" HIGHSINT_FORMAT
" = slice_num > kHighsSlicedLimit = %" HIGHSINT_FORMAT
" so truncating "
"slice_num\n",
slice_num, kHighsSlicedLimit);
slice_num = kHighsSlicedLimit;
}
const HighsInt* Astart = a_matrix->start_.data();
const HighsInt AcountX = Astart[solver_num_col];
double sliced_countX = AcountX / (double)slice_num;
slice_start[0] = 0;
for (HighsInt i = 0; i < slice_num - 1; i++) {
HighsInt endColumn = slice_start[i] + 1; HighsInt endX = Astart[endColumn];
HighsInt stopX = (i + 1) * sliced_countX;
while (endX < stopX) {
endX = Astart[++endColumn];
}
slice_start[i + 1] = endColumn;
if (endColumn >= solver_num_col) {
slice_num = i; break;
}
}
slice_start[slice_num] = solver_num_col;
vector<HighsInt> sliced_Astart;
for (HighsInt i = 0; i < slice_num; i++) {
HighsInt from_col = slice_start[i];
HighsInt to_col = slice_start[i + 1] - 1;
HighsInt slice_num_col = slice_start[i + 1] - from_col;
HighsInt from_el = Astart[from_col];
sliced_Astart.resize(slice_num_col + 1);
for (HighsInt k = 0; k <= slice_num_col; k++)
sliced_Astart[k] = Astart[k + from_col] - from_el;
slice_a_matrix[i].createSlice(ekk_instance_.lp_.a_matrix_, from_col,
to_col);
slice_ar_matrix[i].createRowwise(slice_a_matrix[i]);
slice_row_ap[i].setup(slice_num_col);
slice_dualRow[i].setupSlice(slice_num_col);
}
}
void HEkkDual::initialiseSolve() {
primal_feasibility_tolerance =
ekk_instance_.options_->primal_feasibility_tolerance;
dual_feasibility_tolerance =
ekk_instance_.options_->dual_feasibility_tolerance;
objective_bound = ekk_instance_.options_->objective_bound;
Tp = primal_feasibility_tolerance;
Td = dual_feasibility_tolerance;
initial_basis_is_logical_ = true;
for (HighsInt iRow = 0; iRow < solver_num_row; iRow++) {
if (ekk_instance_.basis_.basicIndex_[iRow] < solver_num_col) {
initial_basis_is_logical_ = false;
break;
}
}
interpretDualEdgeWeightStrategy(
ekk_instance_.info_.dual_edge_weight_strategy);
ekk_instance_.model_status_ = HighsModelStatus::kNotset;
ekk_instance_.solve_bailout_ = false;
ekk_instance_.called_return_from_solve_ = false;
ekk_instance_.exit_algorithm_ = SimplexAlgorithm::kDual;
rebuild_reason = kRebuildReasonNo;
}
void HEkkDual::solvePhase1() {
HighsSimplexInfo& info = ekk_instance_.info_;
HighsSimplexStatus& status = ekk_instance_.status_;
HighsModelStatus& model_status = ekk_instance_.model_status_;
status.has_primal_objective_value = false;
status.has_dual_objective_value = false;
rebuild_reason = kRebuildReasonNo;
assert(solve_phase == kSolvePhase1);
assert(!ekk_instance_.solve_bailout_);
if (ekk_instance_.bailout()) return;
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"dual-phase-1-start\n");
ekk_instance_.initialiseBound(SimplexAlgorithm::kDual, solve_phase);
ekk_instance_.initialiseNonbasicValueAndMove();
if (!info.valid_backtracking_basis_) ekk_instance_.putBacktrackingBasis();
analysis->simplexTimerStart(IterateClock);
for (;;) {
analysis->simplexTimerStart(IterateDualRebuildClock);
rebuild();
analysis->simplexTimerStop(IterateDualRebuildClock);
if (solve_phase == kSolvePhaseError) {
model_status = HighsModelStatus::kSolveError;
return;
}
if (solve_phase == kSolvePhaseUnknown) {
analysis->simplexTimerStop(IterateClock);
return;
}
if (ekk_instance_.bailout()) break;
for (;;) {
if (debugDualSimplex("Before iteration") ==
HighsDebugStatus::kLogicalError) {
solve_phase = kSolvePhaseError;
return;
}
switch (info.simplex_strategy) {
default:
case kSimplexStrategyDualPlain:
iterate();
break;
case kSimplexStrategyDualTasks:
iterateTasks();
break;
case kSimplexStrategyDualMulti:
iterateMulti();
break;
}
if (ekk_instance_.bailout()) break;
assert(solve_phase != kSolvePhaseTabooBasis);
if (rebuild_reason) break;
}
if (ekk_instance_.solve_bailout_) break;
bool finished = status.has_fresh_rebuild &&
!ekk_instance_.rebuildRefactor(rebuild_reason);
if (finished && ekk_instance_.tabooBadBasisChange()) {
solve_phase = kSolvePhaseTabooBasis;
return;
}
if (finished) break;
}
analysis->simplexTimerStop(IterateClock);
if (ekk_instance_.solve_bailout_) return;
assert(!ekk_instance_.solve_bailout_);
if (row_out == kNoRowChosen) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"dual-phase-1-optimal\n");
if (info.dual_objective_value == 0) {
solve_phase = kSolvePhase2;
} else {
assessPhase1Optimality();
}
} else if (rebuild_reason == kRebuildReasonChooseColumnFail ||
rebuild_reason == kRebuildReasonExcessivePrimalValue) {
solve_phase = kSolvePhaseError;
if (rebuild_reason == kRebuildReasonChooseColumnFail) {
highsLogUser(
ekk_instance_.options_->log_options, HighsLogType::kError,
"Dual simplex ratio test failed due to excessive dual values: "
"consider scaling down the LP objective coefficients\n");
} else {
highsLogUser(ekk_instance_.options_->log_options, HighsLogType::kError,
"Dual simplex detected excessive primal values: consider "
"scaling down the LP bounds\n");
}
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"dual-phase-1-not-solved\n");
model_status = HighsModelStatus::kSolveError;
} else if (variable_in == -1) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"dual-phase-1-unbounded\n");
if (ekk_instance_.info_.costs_perturbed) {
cleanup();
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kWarning,
"Cleaning up cost perturbation when unbounded in phase 1\n");
if (dualInfeasCount == 0) {
solve_phase = kSolvePhase2;
}
} else {
solve_phase = kSolvePhaseError;
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"dual-phase-1-not-solved\n");
model_status = HighsModelStatus::kSolveError;
}
}
const bool no_debug = ekk_instance_.info_.num_dual_infeasibilities > 0 &&
model_status == HighsModelStatus::kNotset;
if (!no_debug) {
if (debugDualSimplex("End of solvePhase1") ==
HighsDebugStatus::kLogicalError) {
solve_phase = kSolvePhaseError;
return;
}
}
const bool solve_phase_ok =
solve_phase == kSolvePhase1 || solve_phase == kSolvePhase2 ||
solve_phase == kSolvePhaseExit || solve_phase == kSolvePhaseError;
if (!solve_phase_ok)
highsLogDev(
ekk_instance_.options_->log_options, HighsLogType::kInfo,
"HEkkDual::solvePhase1 solve_phase == %d (solve call %d; iter %d)\n",
(int)solve_phase, (int)ekk_instance_.debug_solve_call_num_,
(int)ekk_instance_.iteration_count_);
assert(solve_phase_ok);
if (solve_phase == kSolvePhase2 || solve_phase == kSolvePhaseExit ||
solve_phase == kSolvePhaseError) {
ekk_instance_.initialiseBound(SimplexAlgorithm::kDual, kSolvePhase2);
ekk_instance_.initialiseNonbasicValueAndMove();
if (solve_phase == kSolvePhase2) {
if (ekk_instance_.dual_simplex_phase1_cleanup_level_ <
ekk_instance_.options_->max_dual_simplex_phase1_cleanup_level) {
info.allow_cost_shifting = true;
info.allow_cost_perturbation = true;
}
if (!info.allow_cost_perturbation)
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kWarning,
"Moving to phase 2, but not allowing cost perturbation\n");
}
}
return;
}
void HEkkDual::solvePhase2() {
multi_chooseAgain = 1;
HighsSimplexInfo& info = ekk_instance_.info_;
HighsSimplexStatus& status = ekk_instance_.status_;
HighsModelStatus& model_status = ekk_instance_.model_status_;
status.has_primal_objective_value = false;
status.has_dual_objective_value = false;
rebuild_reason = kRebuildReasonNo;
solve_phase = kSolvePhase2;
ekk_instance_.solve_bailout_ = false;
if (ekk_instance_.bailout()) return;
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"dual-phase-2-start\n");
dualRow.createFreelist();
if (!info.valid_backtracking_basis_) ekk_instance_.putBacktrackingBasis();
analysis->simplexTimerStart(IterateClock);
for (;;) {
analysis->simplexTimerStart(IterateDualRebuildClock);
rebuild();
analysis->simplexTimerStop(IterateDualRebuildClock);
if (solve_phase == kSolvePhaseError) {
model_status = HighsModelStatus::kSolveError;
return;
}
if (solve_phase == kSolvePhaseUnknown) {
analysis->simplexTimerStop(IterateClock);
return;
}
if (ekk_instance_.bailout()) break;
if (bailoutOnDualObjective()) break;
if (dualInfeasCount > 0) break;
for (;;) {
if (debugDualSimplex("Before iteration") ==
HighsDebugStatus::kLogicalError) {
solve_phase = kSolvePhaseError;
return;
}
switch (info.simplex_strategy) {
default:
case kSimplexStrategyDualPlain:
iterate();
break;
case kSimplexStrategyDualTasks:
iterateTasks();
break;
case kSimplexStrategyDualMulti:
iterateMulti();
break;
}
if (ekk_instance_.bailout()) break;
if (bailoutOnDualObjective()) break;
assert(solve_phase != kSolvePhaseTabooBasis);
if (rebuild_reason == kRebuildReasonPossiblyDualUnbounded)
assessPossiblyDualUnbounded();
if (rebuild_reason) break;
}
if (ekk_instance_.solve_bailout_) break;
bool finished = status.has_fresh_rebuild &&
!ekk_instance_.rebuildRefactor(rebuild_reason);
if (finished && ekk_instance_.tabooBadBasisChange()) {
solve_phase = kSolvePhaseTabooBasis;
return;
}
if (finished) break;
}
analysis->simplexTimerStop(IterateClock);
if (ekk_instance_.solve_bailout_) return;
assert(!ekk_instance_.solve_bailout_);
if (dualInfeasCount > 0) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"dual-phase-2-found-free\n");
solve_phase = kSolvePhase1;
} else if (row_out == kNoRowChosen) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"dual-phase-2-optimal\n");
cleanup();
if (dualInfeasCount > 0) {
solve_phase = kSolvePhaseOptimalCleanup;
} else {
solve_phase = kSolvePhaseOptimal;
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"problem-optimal\n");
model_status = HighsModelStatus::kOptimal;
}
} else if (rebuild_reason == kRebuildReasonChooseColumnFail ||
rebuild_reason == kRebuildReasonExcessivePrimalValue) {
solve_phase = kSolvePhaseError;
if (rebuild_reason == kRebuildReasonChooseColumnFail) {
highsLogUser(
ekk_instance_.options_->log_options, HighsLogType::kError,
"Dual simplex ratio test failed due to excessive dual values: "
"consider scaling down the LP objective coefficients\n");
} else {
highsLogUser(ekk_instance_.options_->log_options, HighsLogType::kError,
"Dual simplex detected excessive primal values: consider "
"scaling down the LP bounds\n");
}
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"dual-phase-2-not-solved\n");
model_status = HighsModelStatus::kSolveError;
} else {
assert(model_status == HighsModelStatus::kInfeasible);
assert(solve_phase == kSolvePhaseExit);
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"problem-primal-infeasible\n");
}
if (solve_phase != kSolvePhaseOptimalCleanup) {
if (debugDualSimplex("End of solvePhase2") ==
HighsDebugStatus::kLogicalError) {
solve_phase = kSolvePhaseError;
return;
}
}
return;
}
void HEkkDual::rebuild() {
HighsSimplexInfo& info = ekk_instance_.info_;
HighsSimplexStatus& status = ekk_instance_.status_;
ekk_instance_.clearBadBasisChangeTabooFlag();
const bool refactor_basis_matrix =
ekk_instance_.rebuildRefactor(rebuild_reason);
const HighsInt local_rebuild_reason = rebuild_reason;
rebuild_reason = kRebuildReasonNo;
if (refactor_basis_matrix) {
if (!ekk_instance_.getNonsingularInverse(solve_phase)) {
solve_phase = kSolvePhaseError;
return;
}
ekk_instance_.resetSyntheticClock();
}
HighsInt alt_debug_level = -1;
ekk_instance_.debugNlaCheckInvert("HEkkDual::rebuild", alt_debug_level);
if (!ekk_instance_.status_.has_ar_matrix) {
assert(info.backtracking_);
ekk_instance_.initialisePartitionedRowwiseMatrix();
assert(ekk_instance_.ar_matrix_.debugPartitionOk(
ekk_instance_.basis_.nonbasicFlag_.data()));
}
const bool check_updated_objective_value = status.has_dual_objective_value;
double previous_dual_objective_value = -kHighsInf;
if (check_updated_objective_value) {
previous_dual_objective_value = info.updated_dual_objective_value;
} else {
}
ekk_instance_.computeDual();
if (info.backtracking_) {
solve_phase = kSolvePhaseUnknown;
return;
}
analysis->simplexTimerStart(CorrectDualClock);
correctDualInfeasibilities(dualInfeasCount);
analysis->simplexTimerStop(CorrectDualClock);
ekk_instance_.computePrimal();
analysis->simplexTimerStart(CollectPrIfsClock);
dualRHS.createArrayOfPrimalInfeasibilities();
dualRHS.createInfeasList(ekk_instance_.info_.col_aq_density);
analysis->simplexTimerStop(CollectPrIfsClock);
ekk_instance_.computeDualObjectiveValue(solve_phase);
if (check_updated_objective_value) {
const double dual_objective_value_correction =
info.dual_objective_value - previous_dual_objective_value;
info.updated_dual_objective_value += dual_objective_value_correction;
}
info.updated_dual_objective_value = info.dual_objective_value;
if (!info.run_quiet) {
ekk_instance_.computeInfeasibilitiesForReporting(SimplexAlgorithm::kDual,
solve_phase);
reportRebuild(local_rebuild_reason);
}
ekk_instance_.resetSyntheticClock();
ekk_instance_.invalidatePrimalInfeasibilityRecord();
ekk_instance_.invalidateDualInfeasibilityRecord();
status.has_fresh_rebuild = true;
}
void HEkkDual::cleanup() {
if (solve_phase == kSolvePhase1) {
ekk_instance_.dual_simplex_phase1_cleanup_level_++;
const bool excessive_cleanup_calls =
ekk_instance_.dual_simplex_phase1_cleanup_level_ >
ekk_instance_.options_->max_dual_simplex_phase1_cleanup_level;
if (excessive_cleanup_calls) {
highsLogDev(
ekk_instance_.options_->log_options, HighsLogType::kError,
"Dual simplex cleanup level has exceeded limit of %d\n",
(int)ekk_instance_.options_->max_dual_simplex_phase1_cleanup_level);
assert(!excessive_cleanup_calls);
}
}
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"dual-cleanup-shift\n");
HighsSimplexInfo& info = ekk_instance_.info_;
ekk_instance_.initialiseCost(SimplexAlgorithm::kDual, kSolvePhaseUnknown);
info.allow_cost_perturbation = false;
ekk_instance_.initialiseBound(SimplexAlgorithm::kDual, solve_phase);
vector<double> original_workDual;
if (ekk_instance_.options_->highs_debug_level > kHighsDebugLevelCheap)
original_workDual = info.workDual_;
ekk_instance_.computeDual();
ekk_instance_.computeSimplexDualInfeasible();
dualInfeasCount = ekk_instance_.info_.num_dual_infeasibilities;
ekk_instance_.computeDualObjectiveValue(solve_phase);
info.updated_dual_objective_value = info.dual_objective_value;
if (!info.run_quiet) {
ekk_instance_.computeSimplexPrimalInfeasible();
if (solve_phase == kSolvePhase1)
ekk_instance_.computeSimplexLpDualInfeasible();
reportRebuild(kRebuildReasonCleanup);
}
}
void HEkkDual::iterate() {
const HighsInt from_check_iter = 0;
const HighsInt to_check_iter = from_check_iter + 100;
if (ekk_instance_.debug_solve_report_) {
ekk_instance_.debug_iteration_report_ =
ekk_instance_.iteration_count_ >= from_check_iter &&
ekk_instance_.iteration_count_ <= to_check_iter;
if (ekk_instance_.debug_iteration_report_) {
printf("HEkkDual::iterate Debug iteration %d\n",
(int)ekk_instance_.iteration_count_);
}
}
analysis->simplexTimerStart(IterateChuzrClock);
chooseRow();
analysis->simplexTimerStop(IterateChuzrClock);
analysis->simplexTimerStart(IterateChuzcClock);
chooseColumn(&row_ep);
analysis->simplexTimerStop(IterateChuzcClock);
if (isBadBasisChange()) return;
analysis->simplexTimerStart(IterateFtranClock);
updateFtranBFRT();
updateFtran();
if (edge_weight_mode == EdgeWeightMode::kSteepestEdge)
updateFtranDSE(&row_ep);
analysis->simplexTimerStop(IterateFtranClock);
analysis->simplexTimerStart(IterateVerifyClock);
updateVerify();
analysis->simplexTimerStop(IterateVerifyClock);
analysis->simplexTimerStart(IterateDualClock);
updateDual();
analysis->simplexTimerStop(IterateDualClock);
analysis->simplexTimerStart(IteratePrimalClock);
updatePrimal(&row_ep);
analysis->simplexTimerStop(IteratePrimalClock);
ekk_instance_.status_.has_primal_objective_value = false;
analysis->simplexTimerStart(IteratePivotsClock);
updatePivots();
analysis->simplexTimerStop(IteratePivotsClock);
if (new_devex_framework) {
analysis->simplexTimerStart(IterateDevexIzClock);
initialiseDevexFramework();
analysis->simplexTimerStop(IterateDevexIzClock);
}
iterationAnalysis();
}
void HEkkDual::iterateTasks() {
slice_PRICE = 1;
chooseRow();
if (1.0 * row_ep.count * inv_solver_num_row < 0.01) slice_PRICE = 0;
analysis->simplexTimerStart(Group1Clock);
{
highs::parallel::spawn([&]() {
col_DSE.copy(&row_ep);
updateFtranDSE(&col_DSE);
});
{
if (slice_PRICE)
chooseColumnSlice(&row_ep);
else
chooseColumn(&row_ep);
highs::parallel::spawn([&]() { updateFtranBFRT(); });
updateFtran();
highs::parallel::sync();
}
highs::parallel::sync();
}
analysis->simplexTimerStop(Group1Clock);
updateVerify();
updateDual();
updatePrimal(&col_DSE);
updatePivots();
}
void HEkkDual::iterationAnalysisData() {
double cost_scale_factor =
pow(2.0, -ekk_instance_.options_->cost_scale_factor);
HighsSimplexInfo& info = ekk_instance_.info_;
analysis->simplex_strategy = info.simplex_strategy;
analysis->edge_weight_mode = edge_weight_mode;
analysis->solve_phase = solve_phase;
analysis->simplex_iteration_count = ekk_instance_.iteration_count_;
analysis->devex_iteration_count = num_devex_iterations;
analysis->pivotal_row_index = row_out;
analysis->leaving_variable = variable_out;
analysis->entering_variable = variable_in;
analysis->rebuild_reason = rebuild_reason;
analysis->reduced_rhs_value = 0;
analysis->reduced_cost_value = 0;
analysis->edge_weight = 0;
analysis->primal_delta = delta_primal;
analysis->primal_step = theta_primal;
analysis->dual_step = theta_dual * cost_scale_factor;
analysis->pivot_value_from_column = alpha_col;
analysis->pivot_value_from_row = alpha_row;
analysis->factor_pivot_threshold = info.factor_pivot_threshold;
analysis->numerical_trouble = numericalTrouble;
analysis->edge_weight_error = ekk_instance_.edge_weight_error_;
analysis->objective_value = info.updated_dual_objective_value;
if (solve_phase == kSolvePhase2)
analysis->objective_value *= (HighsInt)ekk_instance_.lp_.sense_;
analysis->num_primal_infeasibility = info.num_primal_infeasibilities;
analysis->sum_primal_infeasibility = info.sum_primal_infeasibilities;
if (solve_phase == kSolvePhase1) {
analysis->num_dual_infeasibility =
analysis->num_dual_phase_1_lp_dual_infeasibility;
analysis->sum_dual_infeasibility =
analysis->sum_dual_phase_1_lp_dual_infeasibility;
} else {
analysis->num_dual_infeasibility = info.num_dual_infeasibilities;
analysis->sum_dual_infeasibility = info.sum_dual_infeasibilities;
}
if ((edge_weight_mode == EdgeWeightMode::kDevex) &&
(num_devex_iterations == 0))
analysis->num_devex_framework++;
analysis->col_aq_density = info.col_aq_density;
analysis->row_ep_density = info.row_ep_density;
analysis->row_ap_density = info.row_ap_density;
analysis->row_DSE_density = info.row_DSE_density;
analysis->col_basic_feasibility_change_density =
info.col_basic_feasibility_change_density;
analysis->row_basic_feasibility_change_density =
info.row_basic_feasibility_change_density;
analysis->col_BFRT_density = info.col_BFRT_density;
analysis->primal_col_density = info.primal_col_density;
analysis->dual_col_density = info.dual_col_density;
analysis->num_costly_DSE_iteration = info.num_costly_DSE_iteration;
analysis->costly_DSE_measure = info.costly_DSE_measure;
}
void HEkkDual::iterationAnalysis() {
const bool make_iteration_report = analysis->analyse_simplex_runtime_data &&
ekk_instance_.options_->log_dev_level >=
(HighsInt)kIterationReportLogType;
if (make_iteration_report)
ekk_instance_.computeInfeasibilitiesForReporting(SimplexAlgorithm::kDual,
solve_phase);
iterationAnalysisData();
analysis->iterationReport();
if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) {
const bool switch_to_devex = ekk_instance_.switchToDevex();
if (switch_to_devex) {
edge_weight_mode = EdgeWeightMode::kDevex;
initialiseDevexFramework();
}
}
if (analysis->analyse_simplex_summary_data) analysis->iterationRecord();
}
void HEkkDual::reportRebuild(const HighsInt reason_for_rebuild) {
analysis->simplexTimerStart(ReportRebuildClock);
iterationAnalysisData();
analysis->rebuild_reason = reason_for_rebuild;
analysis->rebuild_reason_string =
ekk_instance_.rebuildReason(reason_for_rebuild);
if (ekk_instance_.options_->output_flag) analysis->invertReport();
analysis->simplexTimerStop(ReportRebuildClock);
}
void HEkkDual::chooseRow() {
if (rebuild_reason) return;
ekk_instance_.applyTabooRowOut(dualRHS.work_infeasibility, 0);
if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) {
HighsDebugStatus return_status =
ekk_instance_.devDebugDualSteepestEdgeWeights("chooseRow");
assert(return_status == HighsDebugStatus::kNotChecked ||
return_status == HighsDebugStatus::kOk);
}
std::vector<double>& edge_weight = ekk_instance_.dual_edge_weight_;
for (;;) {
dualRHS.chooseNormal(&row_out);
if (row_out == kNoRowChosen) {
rebuild_reason = kRebuildReasonPossiblyOptimal;
return;
}
analysis->simplexTimerStart(BtranClock);
row_ep.clear();
row_ep.count = 1;
row_ep.index[0] = row_out;
row_ep.array[row_out] = 1;
row_ep.packFlag = true;
if (analysis->analyse_simplex_summary_data)
analysis->operationRecordBefore(kSimplexNlaBtranEp, row_ep,
ekk_instance_.info_.row_ep_density);
simplex_nla->btran(row_ep, ekk_instance_.info_.row_ep_density,
analysis->pointer_serial_factor_clocks);
if (analysis->analyse_simplex_summary_data)
analysis->operationRecordAfter(kSimplexNlaBtranEp, row_ep);
analysis->simplexTimerStop(BtranClock);
if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) {
double updated_edge_weight = edge_weight[row_out];
if (ekk_instance_.simplex_in_scaled_space_) {
computed_edge_weight = edge_weight[row_out] = row_ep.norm2();
} else {
computed_edge_weight = edge_weight[row_out] =
simplex_nla->rowEp2NormInScaledSpace(row_out, row_ep);
}
if (acceptDualSteepestEdgeWeight(updated_edge_weight)) break;
} else {
break;
}
}
ekk_instance_.unapplyTabooRowOut(dualRHS.work_infeasibility);
variable_out = ekk_instance_.basis_.basicIndex_[row_out];
if (baseValue[row_out] < baseLower[row_out]) {
delta_primal = baseValue[row_out] - baseLower[row_out];
} else {
delta_primal = baseValue[row_out] - baseUpper[row_out];
}
move_out = delta_primal < 0 ? -1 : 1;
const double local_row_ep_density = (double)row_ep.count * inv_solver_num_row;
ekk_instance_.updateOperationResultDensity(
local_row_ep_density, ekk_instance_.info_.row_ep_density);
}
bool HEkkDual::acceptDualSteepestEdgeWeight(const double updated_edge_weight) {
const bool accept_weight =
updated_edge_weight >= kAcceptDseWeightThreshold * computed_edge_weight;
ekk_instance_.assessDSEWeightError(computed_edge_weight, updated_edge_weight);
analysis->dualSteepestEdgeWeightError(computed_edge_weight,
updated_edge_weight);
return accept_weight;
}
bool HEkkDual::newDevexFramework(const double updated_edge_weight) {
const HighsInt kMinAbsNumberDevexIterations = 25;
const double kMinRlvNumberDevexIterations = 1e-2;
const double kMaxAllowedDevexWeightRatio = 3.0;
double devex_ratio = max(updated_edge_weight / computed_edge_weight,
computed_edge_weight / updated_edge_weight);
HighsInt i_te = solver_num_row / kMinRlvNumberDevexIterations;
i_te = max(kMinAbsNumberDevexIterations, i_te);
const double accept_ratio_threshold =
kMaxAllowedDevexWeightRatio * kMaxAllowedDevexWeightRatio;
const bool accept_ratio = devex_ratio <= accept_ratio_threshold;
const bool accept_it = num_devex_iterations <= i_te;
bool return_new_devex_framework;
return_new_devex_framework = !accept_ratio || !accept_it;
return return_new_devex_framework;
}
void HEkkDual::chooseColumn(HVector* row_ep) {
if (rebuild_reason) return;
HighsOptions* options = ekk_instance_.options_;
HighsLp& lp = ekk_instance_.lp_;
HighsInt debug_price_report = kDebugReportOff;
const bool debug_price_report_on = false;
const bool debug_small_pivot_issue_report_on = false;
bool debug_small_pivot_issue_report = false;
bool debug_rows_report = false;
if (ekk_instance_.debug_iteration_report_) {
if (debug_price_report_on) debug_price_report = kDebugReportAll;
debug_rows_report = debug_price_report_on;
debug_small_pivot_issue_report = debug_small_pivot_issue_report_on;
if (debug_price_report != kDebugReportOff || debug_rows_report ||
debug_small_pivot_issue_report)
printf("HEkkDual::chooseColumn Check iter = %d\n",
(int)ekk_instance_.iteration_count_);
}
const bool quad_precision = false;
ekk_instance_.tableauRowPrice(quad_precision, *row_ep, row_ap,
debug_price_report);
if (debug_rows_report) {
ekk_instance_.simplex_nla_.reportArray("Row a_p", 0, &row_ap, true);
ekk_instance_.simplex_nla_.reportArray("Row e_p", lp.num_col_, row_ep,
true);
}
analysis->simplexTimerStart(Chuzc0Clock);
dualRow.clear();
dualRow.workDelta = delta_primal;
dualRow.createFreemove(row_ep);
analysis->simplexTimerStop(Chuzc0Clock);
analysis->simplexTimerStart(Chuzc1Clock);
dualRow.chooseMakepack(&row_ap, 0);
dualRow.chooseMakepack(row_ep, solver_num_col);
const double row_ep_scale =
ekk_instance_.getValueScale(dualRow.packCount, dualRow.packValue);
analysis->simplexTimerStop(Chuzc1Clock);
HighsInt chuzc_pass = 0;
for (;;) {
analysis->simplexTimerStart(Chuzc2Clock);
dualRow.choosePossible();
analysis->simplexTimerStop(Chuzc2Clock);
variable_in = -1;
if (dualRow.workTheta <= 0 || dualRow.workCount == 0) {
if (chuzc_pass > 0 && debug_small_pivot_issue_report) {
printf(
" "
"Negative step or no candidates after %2d CHUZC passes\n",
(int)chuzc_pass);
}
if (debug_rows_report) {
ekk_instance_.simplex_nla_.reportVector(
"dualRow.packValue/Index", dualRow.packCount, dualRow.packValue,
dualRow.packIndex, true);
}
rebuild_reason = kRebuildReasonPossiblyDualUnbounded;
return;
}
bool chooseColumnFail = (dualRow.chooseFinal() != 0);
if (chooseColumnFail) {
rebuild_reason = kRebuildReasonChooseColumnFail;
return;
}
if (dualRow.workPivot >= 0) {
const double growth_tolerance =
options->dual_simplex_pivot_growth_tolerance; double alpha_row = dualRow.workAlpha;
assert(alpha_row);
const double scaled_value = row_ep_scale * alpha_row;
if (std::abs(scaled_value) <= growth_tolerance) {
if (chuzc_pass == 0 && debug_small_pivot_issue_report)
printf(
"CHUZC: Solve %6d; Iter %4d; ||e_p|| = %11.4g: Variable %6d "
"Pivot %11.4g (dual "
"%11.4g; ratio = %11.4g) is small",
(int)ekk_instance_.debug_solve_call_num_,
(int)ekk_instance_.iteration_count_, 1.0 / row_ep_scale,
(int)dualRow.workPivot, dualRow.workAlpha,
workDual[dualRow.workPivot],
workDual[dualRow.workPivot] / dualRow.workAlpha);
if (chuzc_pass == 0) {
if (debug_small_pivot_issue_report) printf(": improve row\n");
ekk_instance_.analysis_.num_improve_choose_column_row_call++;
improveChooseColumnRow(row_ep);
} else {
ekk_instance_.analysis_.num_remove_pivot_from_pack++;
for (HighsInt i = 0; i < dualRow.packCount; i++) {
if (dualRow.packIndex[i] == dualRow.workPivot) {
dualRow.packIndex[i] = dualRow.packIndex[dualRow.packCount - 1];
dualRow.packValue[i] = dualRow.packValue[dualRow.packCount - 1];
dualRow.packCount--;
if (chuzc_pass == 0 && debug_small_pivot_issue_report)
printf(": removing pivot gives pack count = %6d",
(int)dualRow.packCount);
break;
}
}
if (chuzc_pass == 0 && debug_small_pivot_issue_report)
printf(" so %s\n", dualRow.packCount > 0 ? "repeat CHUZC"
: "consider unbounded");
}
dualRow.workPivot = -1;
} else if (chuzc_pass > 0 && debug_small_pivot_issue_report) {
printf(
" Variable "
"%6d "
"Pivot %11.4g (dual "
"%11.4g; ratio = %11.4g) is OK after %2d CHUZC passes\n",
(int)dualRow.workPivot, dualRow.workAlpha,
workDual[dualRow.workPivot],
workDual[dualRow.workPivot] / dualRow.workAlpha, (int)chuzc_pass);
}
} else {
assert(dualRow.workPivot == -1);
if (chuzc_pass > 0 && debug_small_pivot_issue_report) {
printf(
" No pivot "
"after %2d CHUZC passes\n",
(int)chuzc_pass);
}
break;
}
if (dualRow.workPivot >= 0 || dualRow.packCount <= 0) break;
chuzc_pass++;
}
analysis->simplexTimerStart(Chuzc5Clock);
dualRow.deleteFreemove();
analysis->simplexTimerStop(Chuzc5Clock);
variable_in = dualRow.workPivot; alpha_row = dualRow.workAlpha; theta_dual = dualRow.workTheta;
if (edge_weight_mode == EdgeWeightMode::kDevex && !new_devex_framework) {
analysis->simplexTimerStart(DevexWtClock);
dualRow.computeDevexWeight();
computed_edge_weight = dualRow.computed_edge_weight;
computed_edge_weight = max(1.0, computed_edge_weight);
analysis->simplexTimerStop(DevexWtClock);
}
return;
}
void HEkkDual::improveChooseColumnRow(HVector* row_ep) {
HighsLp& lp = ekk_instance_.lp_;
const bool debug_price_report_on = false;
HighsInt debug_price_report = kDebugReportOff;
bool debug_rows_report = false;
if (ekk_instance_.debug_iteration_report_) {
if (debug_price_report_on) debug_price_report = kDebugReportAll;
debug_rows_report = debug_price_report_on;
if (debug_price_report != kDebugReportOff)
printf("HEkkDual::chooseColumn Check iter = %d\n",
(int)ekk_instance_.iteration_count_);
}
analysis->simplexTimerStart(Chuzc5Clock);
dualRow.deleteFreemove();
analysis->simplexTimerStop(Chuzc5Clock);
if (debug_rows_report)
ekk_instance_.simplex_nla_.reportArray("Row e_p.0", lp.num_col_, row_ep,
true);
ekk_instance_.unitBtranIterativeRefinement(row_out, *row_ep);
if (debug_rows_report)
ekk_instance_.simplex_nla_.reportArray("Row e_p.1", lp.num_col_, row_ep,
true);
const bool quad_precision = true;
ekk_instance_.tableauRowPrice(quad_precision, *row_ep, row_ap,
debug_price_report);
if (debug_rows_report) {
ekk_instance_.simplex_nla_.reportArray("Row a_p", 0, &row_ap, true);
ekk_instance_.simplex_nla_.reportArray("Row e_p", lp.num_col_, row_ep,
true);
}
analysis->simplexTimerStart(Chuzc0Clock);
dualRow.clear();
dualRow.workDelta = delta_primal;
dualRow.createFreemove(row_ep);
analysis->simplexTimerStop(Chuzc0Clock);
analysis->simplexTimerStart(Chuzc1Clock);
dualRow.chooseMakepack(&row_ap, 0);
dualRow.chooseMakepack(row_ep, solver_num_col);
analysis->simplexTimerStop(Chuzc1Clock);
}
void HEkkDual::chooseColumnSlice(HVector* row_ep) {
if (rebuild_reason) return;
analysis->simplexTimerStart(Chuzc0Clock);
dualRow.clear();
dualRow.workDelta = delta_primal;
dualRow.createFreemove(row_ep);
analysis->simplexTimerStop(Chuzc0Clock);
const double local_density = 1.0 * row_ep->count * inv_solver_num_row;
bool use_col_price;
bool use_row_price_w_switch;
HighsSimplexInfo& info = ekk_instance_.info_;
ekk_instance_.choosePriceTechnique(info.price_strategy, local_density,
use_col_price, use_row_price_w_switch);
if (analysis->analyse_simplex_summary_data) {
const HighsInt row_ep_count = row_ep->count;
if (use_col_price) {
analysis->operationRecordBefore(kSimplexNlaPriceAp, row_ep_count, 0.0);
analysis->num_col_price++;
} else if (use_row_price_w_switch) {
analysis->operationRecordBefore(kSimplexNlaPriceAp, row_ep_count,
ekk_instance_.info_.row_ep_density);
analysis->num_row_price_with_switch++;
} else {
analysis->operationRecordBefore(kSimplexNlaPriceAp, row_ep_count,
ekk_instance_.info_.row_ep_density);
analysis->num_row_price++;
}
}
analysis->simplexTimerStart(PriceChuzc1Clock);
highs::parallel::spawn([&]() {
dualRow.chooseMakepack(row_ep, solver_num_col);
dualRow.choosePossible();
});
highs::parallel::for_each(0, slice_num, [&](HighsInt start, HighsInt end) {
const bool quad_precision = false;
for (HighsInt i = start; i < end; i++) {
slice_row_ap[i].clear();
if (use_col_price) {
slice_a_matrix[i].priceByColumn(quad_precision, slice_row_ap[i],
*row_ep);
} else if (use_row_price_w_switch) {
slice_ar_matrix[i].priceByRowWithSwitch(
quad_precision, slice_row_ap[i], *row_ep,
ekk_instance_.info_.row_ap_density, 0, kHyperPriceDensity);
} else {
slice_ar_matrix[i].priceByRow(quad_precision, slice_row_ap[i], *row_ep);
}
slice_dualRow[i].clear();
slice_dualRow[i].workDelta = delta_primal;
slice_dualRow[i].chooseMakepack(&slice_row_ap[i], slice_start[i]);
slice_dualRow[i].choosePossible();
}
});
highs::parallel::sync();
if (analysis->analyse_simplex_summary_data) {
HighsInt row_ap_count = 0;
for (HighsInt i = 0; i < slice_num; i++)
row_ap_count += slice_row_ap[i].count;
analysis->operationRecordAfter(kSimplexNlaPriceAp, row_ap_count);
}
for (HighsInt i = 0; i < slice_num; i++) {
dualRow.chooseJoinpack(&slice_dualRow[i]);
}
analysis->simplexTimerStop(PriceChuzc1Clock);
variable_in = -1;
if (dualRow.workTheta <= 0 || dualRow.workCount == 0) {
rebuild_reason = kRebuildReasonPossiblyDualUnbounded;
return;
}
HighsInt return_code = dualRow.chooseFinal();
if (return_code) {
assert(return_code == -1);
if (return_code < 0) {
rebuild_reason = kRebuildReasonChooseColumnFail;
} else {
rebuild_reason = kRebuildReasonPossiblyDualUnbounded;
}
return;
}
if (slice_num == 0) {
HighsInt num_infeasibility = dualRow.debugChooseColumnInfeasibilities();
if (num_infeasibility) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kError,
"chooseFinal would create %d dual infeasibilities\n",
(int)num_infeasibility);
analysis->simplexTimerStop(Chuzc4dClock);
rebuild_reason = kRebuildReasonChooseColumnFail;
return;
}
}
analysis->simplexTimerStart(Chuzc5Clock);
dualRow.deleteFreemove();
analysis->simplexTimerStop(Chuzc5Clock);
variable_in = dualRow.workPivot;
alpha_row = dualRow.workAlpha;
theta_dual = dualRow.workTheta;
if (edge_weight_mode == EdgeWeightMode::kDevex && !new_devex_framework) {
analysis->simplexTimerStart(DevexWtClock);
dualRow.computeDevexWeight();
for (HighsInt i = 0; i < slice_num; i++)
slice_dualRow[i].computeDevexWeight(i);
computed_edge_weight = dualRow.computed_edge_weight;
for (HighsInt i = 0; i < slice_num; i++)
computed_edge_weight += slice_dualRow[i].computed_edge_weight;
computed_edge_weight = max(1.0, computed_edge_weight);
analysis->simplexTimerStop(DevexWtClock);
}
}
void HEkkDual::updateFtran() {
if (rebuild_reason) return;
analysis->simplexTimerStart(FtranClock);
col_aq.clear();
col_aq.packFlag = true;
a_matrix->collectAj(col_aq, variable_in, 1);
if (analysis->analyse_simplex_summary_data)
analysis->operationRecordBefore(kSimplexNlaFtran, col_aq,
ekk_instance_.info_.col_aq_density);
simplex_nla->ftran(col_aq, ekk_instance_.info_.col_aq_density,
analysis->pointer_serial_factor_clocks);
if (analysis->analyse_simplex_summary_data)
analysis->operationRecordAfter(kSimplexNlaFtran, col_aq);
const double local_col_aq_density = (double)col_aq.count * inv_solver_num_row;
ekk_instance_.updateOperationResultDensity(
local_col_aq_density, ekk_instance_.info_.col_aq_density);
alpha_col = col_aq.array[row_out];
analysis->simplexTimerStop(FtranClock);
}
void HEkkDual::updateFtranBFRT() {
if (rebuild_reason) return;
bool time_updateFtranBFRT = dualRow.workCount > 0;
if (time_updateFtranBFRT) {
analysis->simplexTimerStart(FtranBfrtClock);
}
dualRow.updateFlip(&col_BFRT);
if (col_BFRT.count) {
if (analysis->analyse_simplex_summary_data)
analysis->operationRecordBefore(kSimplexNlaFtranBfrt, col_BFRT,
ekk_instance_.info_.col_BFRT_density);
simplex_nla->ftran(col_BFRT, ekk_instance_.info_.col_BFRT_density,
analysis->pointer_serial_factor_clocks);
if (analysis->analyse_simplex_summary_data)
analysis->operationRecordAfter(kSimplexNlaFtranBfrt, col_BFRT);
}
if (time_updateFtranBFRT) {
analysis->simplexTimerStop(FtranBfrtClock);
}
const double local_col_BFRT_density =
(double)col_BFRT.count * inv_solver_num_row;
ekk_instance_.updateOperationResultDensity(
local_col_BFRT_density, ekk_instance_.info_.col_BFRT_density);
}
void HEkkDual::updateFtranDSE(HVector* DSE_Vector) {
if (rebuild_reason) return;
analysis->simplexTimerStart(FtranDseClock);
if (analysis->analyse_simplex_summary_data)
analysis->operationRecordBefore(kSimplexNlaFtranDse, *DSE_Vector,
ekk_instance_.info_.row_DSE_density);
simplex_nla->unapplyBasisMatrixRowScale(*DSE_Vector);
simplex_nla->ftranInScaledSpace(*DSE_Vector,
ekk_instance_.info_.row_DSE_density,
analysis->pointer_serial_factor_clocks);
if (analysis->analyse_simplex_summary_data)
analysis->operationRecordAfter(kSimplexNlaFtranDse, *DSE_Vector);
analysis->simplexTimerStop(FtranDseClock);
const double local_row_DSE_density =
(double)DSE_Vector->count * inv_solver_num_row;
ekk_instance_.updateOperationResultDensity(
local_row_DSE_density, ekk_instance_.info_.row_DSE_density);
}
void HEkkDual::updateVerify() {
if (rebuild_reason) return;
if (ekk_instance_.reinvertOnNumericalTrouble(
"HEkkDual::updateVerify", numericalTrouble, alpha_col, alpha_row,
kNumericalTroubleTolerance)) {
rebuild_reason = kRebuildReasonPossiblySingularBasis;
}
}
void HEkkDual::updateDual() {
if (rebuild_reason) return;
if (theta_dual == 0) {
shiftCost(variable_in, -workDual[variable_in]);
} else {
dualRow.updateDual(theta_dual);
if (ekk_instance_.info_.simplex_strategy != kSimplexStrategyDualPlain &&
slice_PRICE) {
for (HighsInt i = 0; i < slice_num; i++)
slice_dualRow[i].updateDual(theta_dual);
}
}
double dual_objective_value_change;
const double variable_in_delta_dual = workDual[variable_in];
const double variable_in_value = workValue[variable_in];
const HighsInt variable_in_nonbasicFlag =
ekk_instance_.basis_.nonbasicFlag_[variable_in];
dual_objective_value_change =
variable_in_nonbasicFlag * (-variable_in_value * variable_in_delta_dual);
dual_objective_value_change *= ekk_instance_.cost_scale_;
ekk_instance_.info_.updated_dual_objective_value +=
dual_objective_value_change;
const HighsInt variable_out_nonbasicFlag =
ekk_instance_.basis_.nonbasicFlag_[variable_out];
assert(variable_out_nonbasicFlag == 0);
if (variable_out_nonbasicFlag) {
const double variable_out_delta_dual = workDual[variable_out] - theta_dual;
const double variable_out_value = workValue[variable_out];
dual_objective_value_change =
variable_out_nonbasicFlag *
(-variable_out_value * variable_out_delta_dual);
dual_objective_value_change *= ekk_instance_.cost_scale_;
ekk_instance_.info_.updated_dual_objective_value +=
dual_objective_value_change;
}
workDual[variable_in] = 0;
workDual[variable_out] = -theta_dual;
shiftBack(variable_out);
}
void HEkkDual::updatePrimal(HVector* DSE_Vector) {
if (rebuild_reason) return;
std::vector<double>& edge_weight = ekk_instance_.dual_edge_weight_;
if (edge_weight_mode == EdgeWeightMode::kDevex) {
const double updated_edge_weight = edge_weight[row_out];
edge_weight[row_out] = computed_edge_weight;
new_devex_framework = newDevexFramework(updated_edge_weight);
}
dualRHS.updatePrimal(&col_BFRT, 1);
dualRHS.updateInfeasList(&col_BFRT);
double x_out = baseValue[row_out];
double l_out = baseLower[row_out];
double u_out = baseUpper[row_out];
theta_primal = (x_out - (delta_primal < 0 ? l_out : u_out)) / alpha_col;
const bool ok_update_primal = dualRHS.updatePrimal(&col_aq, theta_primal);
if (!ok_update_primal) {
rebuild_reason = kRebuildReasonExcessivePrimalValue;
return;
}
ekk_instance_.updateBadBasisChange(col_aq, theta_primal);
if (edge_weight_mode == EdgeWeightMode::kSteepestEdge) {
const double pivot_in_scaled_space =
ekk_instance_.simplex_nla_.pivotInScaledSpace(&col_aq, variable_in,
row_out);
if (ekk_instance_.simplex_in_scaled_space_)
assert(pivot_in_scaled_space == alpha_col);
const double new_pivotal_edge_weight =
edge_weight[row_out] / (pivot_in_scaled_space * pivot_in_scaled_space);
const double Kai = -2 / pivot_in_scaled_space;
ekk_instance_.updateDualSteepestEdgeWeights(row_out, variable_in, &col_aq,
new_pivotal_edge_weight, Kai,
DSE_Vector->array.data());
edge_weight[row_out] = new_pivotal_edge_weight;
} else if (edge_weight_mode == EdgeWeightMode::kDevex) {
double new_pivotal_edge_weight =
edge_weight[row_out] / (alpha_col * alpha_col);
new_pivotal_edge_weight = max(1.0, new_pivotal_edge_weight);
ekk_instance_.updateDualDevexWeights(&col_aq, new_pivotal_edge_weight);
edge_weight[row_out] = new_pivotal_edge_weight;
num_devex_iterations++;
}
dualRHS.updateInfeasList(&col_aq);
ekk_instance_.total_synthetic_tick_ += col_aq.synthetic_tick;
ekk_instance_.total_synthetic_tick_ += DSE_Vector->synthetic_tick;
}
void HEkkDual::shiftCost(const HighsInt iCol, const double amount) {
HighsSimplexInfo& info = ekk_instance_.info_;
info.costs_shifted = true;
assert(info.workShift_[iCol] == 0);
if (!amount) return;
double use_amount = amount;
info.workShift_[iCol] = use_amount;
const double shift = fabs(use_amount);
analysis->net_num_single_cost_shift++;
analysis->num_single_cost_shift++;
analysis->sum_single_cost_shift += shift;
analysis->max_single_cost_shift = max(shift, analysis->max_single_cost_shift);
}
void HEkkDual::shiftBack(const HighsInt iCol) {
HighsSimplexInfo& info = ekk_instance_.info_;
if (!info.workShift_[iCol]) return;
info.workDual_[iCol] -= info.workShift_[iCol];
info.workShift_[iCol] = 0;
analysis->net_num_single_cost_shift--;
}
void HEkkDual::updatePivots() {
if (rebuild_reason) return;
ekk_instance_.transformForUpdate(&col_aq, &row_ep, variable_in, &row_out);
ekk_instance_.updatePivots(variable_in, row_out, move_out);
ekk_instance_.iteration_count_++;
ekk_instance_.updateFactor(&col_aq, &row_ep, &row_out, &rebuild_reason);
ekk_instance_.updateMatrix(variable_in, variable_out);
dualRow.deleteFreelist(variable_in);
dualRHS.updatePivots(
row_out, ekk_instance_.info_.workValue_[variable_in] + theta_primal);
}
void HEkkDual::initialiseDevexFramework() {
HighsSimplexInfo& info = ekk_instance_.info_;
analysis->simplexTimerStart(DevexIzClock);
ekk_instance_.info_.devex_index_.resize(solver_num_tot);
const vector<int8_t>& nonbasicFlag = ekk_instance_.basis_.nonbasicFlag_;
for (HighsInt vr_n = 0; vr_n < solver_num_tot; vr_n++)
info.devex_index_[vr_n] = 1 - nonbasicFlag[vr_n] * nonbasicFlag[vr_n];
ekk_instance_.dual_edge_weight_.assign(solver_num_row, 1.0);
num_devex_iterations = 0;
new_devex_framework = false;
minor_new_devex_framework = false;
analysis->simplexTimerStop(DevexIzClock);
}
void HEkkDual::interpretDualEdgeWeightStrategy(
const HighsInt dual_edge_weight_strategy) {
if (dual_edge_weight_strategy == kSimplexEdgeWeightStrategyChoose) {
edge_weight_mode = EdgeWeightMode::kSteepestEdge;
allow_dual_steepest_edge_to_devex_switch = true;
} else if (dual_edge_weight_strategy == kSimplexEdgeWeightStrategyDantzig) {
edge_weight_mode = EdgeWeightMode::kDantzig;
} else if (dual_edge_weight_strategy == kSimplexEdgeWeightStrategyDevex) {
edge_weight_mode = EdgeWeightMode::kDevex;
} else if (dual_edge_weight_strategy ==
kSimplexEdgeWeightStrategySteepestEdge) {
edge_weight_mode = EdgeWeightMode::kSteepestEdge;
allow_dual_steepest_edge_to_devex_switch = false;
} else {
assert(1 == 0);
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"HEkkDual::interpretDualEdgeWeightStrategy: "
"unrecognised dual_edge_weight_strategy = %" HIGHSINT_FORMAT
" - using "
"dual steepest edge with possible switch to Devex\n",
dual_edge_weight_strategy);
edge_weight_mode = EdgeWeightMode::kSteepestEdge;
allow_dual_steepest_edge_to_devex_switch = true;
}
}
void HEkkDual::possiblyUseLiDualSteepestEdge() {
HighsOptions& options = *ekk_instance_.options_;
HighsSimplexInfo& info = ekk_instance_.info_;
info.store_squared_primal_infeasibility = true;
if (options.less_infeasible_DSE_check) {
if (isLessInfeasibleDSECandidate(options.log_options, ekk_instance_.lp_)) {
if (options.less_infeasible_DSE_choose_row)
info.store_squared_primal_infeasibility = false;
}
}
}
void HEkkDual::computeDualInfeasibilitiesWithFixedVariableFlips() {
HighsInt num_dual_infeasibility = 0;
double max_dual_infeasibility = 0;
double sum_dual_infeasibility = 0;
HighsLp& lp = ekk_instance_.lp_;
SimplexBasis& basis = ekk_instance_.basis_;
HighsSimplexInfo& info = ekk_instance_.info_;
HighsOptions* options = ekk_instance_.options_;
const HighsInt num_tot = lp.num_col_ + lp.num_row_;
for (HighsInt iVar = 0; iVar < num_tot; iVar++) {
if (!basis.nonbasicFlag_[iVar]) continue;
const double lower = info.workLower_[iVar];
const double upper = info.workUpper_[iVar];
const double dual = info.workDual_[iVar];
double dual_infeasibility = 0;
if (lower == -kHighsInf && upper == kHighsInf) {
dual_infeasibility = fabs(dual);
} else {
dual_infeasibility = -basis.nonbasicMove_[iVar] * dual;
}
if (dual_infeasibility > 0) {
if (dual_infeasibility >= options->dual_feasibility_tolerance)
num_dual_infeasibility++;
max_dual_infeasibility =
std::max(dual_infeasibility, max_dual_infeasibility);
sum_dual_infeasibility += dual_infeasibility;
}
}
info.num_dual_infeasibilities = num_dual_infeasibility;
info.max_dual_infeasibility = max_dual_infeasibility;
info.sum_dual_infeasibilities = sum_dual_infeasibility;
}
void HEkkDual::correctDualInfeasibilities(HighsInt& free_infeasibility_count) {
HighsLp& lp = ekk_instance_.lp_;
SimplexBasis& basis = ekk_instance_.basis_;
HighsSimplexInfo& info = ekk_instance_.info_;
HighsSimplexAnalysis& analysis = ekk_instance_.analysis_;
HighsRandom& random = ekk_instance_.random_;
HighsOptions* options = ekk_instance_.options_;
free_infeasibility_count = 0;
const double dual_feasibility_tolerance = options->dual_feasibility_tolerance;
double flip_dual_objective_value_change = 0;
double shift_dual_objective_value_change = 0;
HighsInt num_flip = 0;
HighsInt num_shift = 0;
double sum_flip = 0;
double sum_shift = 0;
double max_flip = 0;
double max_shift = 0;
double min_dual_infeasibility_for_flip = kHighsInf;
double max_dual_infeasibility_for_flip = 0;
HighsInt num_dual_infeasibilities_for_flip = 0;
double sum_dual_infeasibilities_for_flip = 0;
HighsInt num_dual_infeasibilities_for_shift = 0;
double max_dual_infeasibility_for_shift = 0;
double sum_dual_infeasibilities_for_shift = 0;
const HighsInt num_tot = lp.num_col_ + lp.num_row_;
for (HighsInt iVar = 0; iVar < num_tot; iVar++) {
if (!basis.nonbasicFlag_[iVar]) continue;
const double lower = info.workLower_[iVar];
const double upper = info.workUpper_[iVar];
const double current_dual = info.workDual_[iVar];
const HighsInt move = basis.nonbasicMove_[iVar];
const bool fixed = lower == upper;
const bool boxed = lower > -kHighsInf && upper < kHighsInf;
const bool free = lower == -kHighsInf && upper == kHighsInf;
double dual_infeasibility = 0;
if (free) {
dual_infeasibility = fabs(current_dual);
if (dual_infeasibility >= dual_feasibility_tolerance)
free_infeasibility_count++;
continue;
}
dual_infeasibility = -move * current_dual;
if (dual_infeasibility < dual_feasibility_tolerance) continue;
if (fixed || (boxed && !force_phase2)) {
ekk_instance_.flipBound(iVar);
const double flip = upper - lower;
double local_dual_objective_change = move * flip * current_dual;
local_dual_objective_change *= ekk_instance_.cost_scale_;
flip_dual_objective_value_change += local_dual_objective_change;
num_flip++;
max_flip = max(fabs(flip), max_flip);
sum_flip += fabs(flip);
if (!fixed) {
min_dual_infeasibility_for_flip =
std::min(dual_infeasibility, min_dual_infeasibility_for_flip);
if (dual_infeasibility >= dual_feasibility_tolerance)
num_dual_infeasibilities_for_flip++;
sum_dual_infeasibilities_for_flip += dual_infeasibility;
max_dual_infeasibility_for_flip =
std::max(dual_infeasibility, max_dual_infeasibility_for_flip);
}
continue;
}
assert(info.allow_cost_shifting);
if (dual_infeasibility >= dual_feasibility_tolerance)
num_dual_infeasibilities_for_shift++;
sum_dual_infeasibilities_for_shift += dual_infeasibility;
max_dual_infeasibility_for_shift =
std::max(dual_infeasibility, max_dual_infeasibility_for_shift);
info.costs_shifted = true;
double shift;
if (move == kNonbasicMoveUp) {
double new_dual = (1 + random.fraction()) * dual_feasibility_tolerance;
shift = new_dual - current_dual;
info.workDual_[iVar] = new_dual;
info.workCost_[iVar] = info.workCost_[iVar] + shift;
} else {
double new_dual = -(1 + random.fraction()) * dual_feasibility_tolerance;
shift = new_dual - current_dual;
info.workDual_[iVar] = new_dual;
info.workCost_[iVar] = info.workCost_[iVar] + shift;
}
double local_dual_objective_change = shift * info.workValue_[iVar];
local_dual_objective_change *= ekk_instance_.cost_scale_;
shift_dual_objective_value_change += local_dual_objective_change;
num_shift++;
max_shift = max(fabs(shift), max_shift);
sum_shift += fabs(shift);
const std::string direction = move == kNonbasicMoveUp ? " up" : "down";
highsLogDev(options->log_options, HighsLogType::kVerbose,
"Move %s: cost shift = %g; objective change = %g\n",
direction.c_str(), shift, local_dual_objective_change);
}
analysis.num_correct_dual_primal_flip += num_flip;
analysis.max_correct_dual_primal_flip =
max(max_flip, analysis.max_correct_dual_primal_flip);
analysis.min_correct_dual_primal_flip_dual_infeasibility =
std::min(min_dual_infeasibility_for_flip,
analysis.min_correct_dual_primal_flip_dual_infeasibility);
if (num_flip && force_phase2) {
highsLogDev(
options->log_options, HighsLogType::kDetailed,
"Performed num / max / sum = %" HIGHSINT_FORMAT
" / %g / %g flip(s) for num / min / max / sum dual infeasibility of "
"%" HIGHSINT_FORMAT " / %g / %g / %g; objective change = %g\n",
num_flip, max_flip, sum_flip, num_dual_infeasibilities_for_flip,
min_dual_infeasibility_for_flip, max_dual_infeasibility_for_flip,
sum_dual_infeasibilities_for_flip, flip_dual_objective_value_change);
}
analysis.num_correct_dual_cost_shift += num_shift;
analysis.max_correct_dual_cost_shift =
max(max_shift, analysis.max_correct_dual_cost_shift);
analysis.max_correct_dual_cost_shift_dual_infeasibility =
max(max_dual_infeasibility_for_shift,
analysis.max_correct_dual_cost_shift_dual_infeasibility);
if (num_shift) {
highsLogDev(
options->log_options, HighsLogType::kDetailed,
"Performed num / max / sum = %" HIGHSINT_FORMAT
" / %g / %g shift(s) for num / max / sum dual infeasibility of "
"%" HIGHSINT_FORMAT " / %g / %g; objective change = %g\n",
num_shift, max_shift, sum_shift, num_dual_infeasibilities_for_shift,
max_dual_infeasibility_for_shift, sum_dual_infeasibilities_for_shift,
shift_dual_objective_value_change);
}
force_phase2 = false;
}
bool HEkkDual::proofOfPrimalInfeasibility() {
return ekk_instance_.proofOfPrimalInfeasibility(row_ep, move_out, row_out);
}
void HEkkDual::saveDualRay() {
assert(row_out >= 0);
assert(move_out != kNoRaySign);
ekk_instance_.dual_ray_record_.clear();
ekk_instance_.dual_ray_record_.index = row_out;
ekk_instance_.dual_ray_record_.sign = move_out;
}
void HEkkDual::assessPhase1Optimality() {
assert(solve_phase == kSolvePhase1);
assert(row_out == kNoRowChosen);
assert(ekk_instance_.info_.dual_objective_value);
assert(ekk_instance_.status_.has_fresh_rebuild);
HighsSimplexInfo& info = ekk_instance_.info_;
HighsModelStatus& model_status = ekk_instance_.model_status_;
double& dual_objective_value = info.dual_objective_value;
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"Optimal in phase 1 but not jumping to phase 2 since "
"dual objective is %10.4g: Costs perturbed = %" HIGHSINT_FORMAT
"\n",
dual_objective_value, info.costs_perturbed);
if (info.costs_perturbed) {
cleanup();
assessPhase1OptimalityUnperturbed();
} else {
assert(dualInfeasCount == 0);
assert(dual_objective_value != 0);
assessPhase1OptimalityUnperturbed();
}
if (dualInfeasCount > 0) {
assert(solve_phase == kSolvePhase1);
} else {
assert(solve_phase == kSolvePhase2 ||
(solve_phase == kSolvePhaseExit &&
model_status == HighsModelStatus::kUnboundedOrInfeasible));
if (solve_phase == kSolvePhase2) {
exitPhase1ResetDuals();
}
}
}
void HEkkDual::assessPhase1OptimalityUnperturbed() {
HighsSimplexInfo& info = ekk_instance_.info_;
HighsModelStatus& model_status = ekk_instance_.model_status_;
double& dual_objective_value = info.dual_objective_value;
assert(!info.costs_perturbed);
if (dualInfeasCount == 0) {
if (dual_objective_value == 0) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"LP is dual feasible wrt Phase 2 bounds after removing cost "
"perturbations so go to phase 2\n");
solve_phase = kSolvePhase2;
} else {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"LP is dual feasible wrt Phase 1 bounds after removing cost "
"perturbations: "
"dual objective is %10.4g\n",
dual_objective_value);
ekk_instance_.computeSimplexLpDualInfeasible();
const HighsInt num_lp_dual_infeasibilities =
ekk_instance_.analysis_.num_dual_phase_1_lp_dual_infeasibility;
if (num_lp_dual_infeasibilities == 0) {
highsLogDev(
ekk_instance_.options_->log_options, HighsLogType::kInfo,
"LP is dual feasible wrt Phase 2 bounds after removing cost "
"perturbations so go to phase 2\n");
solve_phase = kSolvePhase2;
} else {
reportOnPossibleLpDualInfeasibility();
model_status = HighsModelStatus::kUnboundedOrInfeasible;
solve_phase = kSolvePhaseExit;
}
}
} else {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"LP has %d dual feasibilities wrt Phase 1 bounds after "
"removing cost perturbations "
"so return to phase 1\n",
dualInfeasCount);
assert(solve_phase == kSolvePhase1);
}
}
void HEkkDual::exitPhase1ResetDuals() {
const HighsLp& lp = ekk_instance_.lp_;
const SimplexBasis& basis = ekk_instance_.basis_;
HighsSimplexInfo& info = ekk_instance_.info_;
if (info.costs_perturbed) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"Costs are already perturbed in exitPhase1ResetDuals\n");
} else {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"Re-perturbing costs when optimal in phase 1\n");
ekk_instance_.initialiseCost(SimplexAlgorithm::kDual, kSolvePhase2, true);
ekk_instance_.computeDual();
}
const HighsInt numTot = lp.num_col_ + lp.num_row_;
HighsInt num_shift = 0;
double sum_shift = 0;
for (HighsInt iVar = 0; iVar < numTot; iVar++) {
if (basis.nonbasicFlag_[iVar]) {
double lp_lower;
double lp_upper;
if (iVar < lp.num_col_) {
lp_lower = lp.col_lower_[iVar];
lp_upper = lp.col_upper_[iVar];
} else {
HighsInt iRow = iVar - lp.num_col_;
lp_lower = lp.row_lower_[iRow];
lp_upper = lp.row_upper_[iRow];
}
if (lp_lower <= -kHighsInf && lp_upper >= kHighsInf) {
const double shift = -info.workDual_[iVar];
info.workDual_[iVar] = 0;
info.workCost_[iVar] = info.workCost_[iVar] + shift;
num_shift++;
sum_shift += fabs(shift);
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kVerbose,
"Variable %" HIGHSINT_FORMAT
" is free: shift cost to zero dual of %g\n",
iVar, shift);
}
}
}
if (num_shift) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"Performed %" HIGHSINT_FORMAT
" cost shift(s) for free variables to zero "
"dual values: total = %g\n",
num_shift, sum_shift);
info.costs_shifted = true;
}
}
void HEkkDual::reportOnPossibleLpDualInfeasibility() {
HighsSimplexInfo& info = ekk_instance_.info_;
HighsSimplexAnalysis& analysis = ekk_instance_.analysis_;
assert(solve_phase == kSolvePhase1);
assert(row_out == kNoRowChosen);
assert(!info.costs_perturbed);
std::string lp_dual_status;
if (analysis.num_dual_phase_1_lp_dual_infeasibility) {
lp_dual_status = "infeasible";
} else {
lp_dual_status = "feasible";
}
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"LP is dual %s with dual phase 1 objective %10.4g and num / "
"max / sum dual infeasibilities = %" HIGHSINT_FORMAT
" / %9.4g / %9.4g\n",
lp_dual_status.c_str(), info.dual_objective_value,
analysis.num_dual_phase_1_lp_dual_infeasibility,
analysis.max_dual_phase_1_lp_dual_infeasibility,
analysis.sum_dual_phase_1_lp_dual_infeasibility);
}
bool HEkkDual::dualInfoOk(const HighsLp& lp) const {
HighsInt lp_num_col = lp.num_col_;
HighsInt lp_num_row = lp.num_row_;
bool dimensions_ok;
dimensions_ok = lp_num_col == solver_num_col && lp_num_row == solver_num_row;
assert(dimensions_ok);
if (!dimensions_ok) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kError,
"LP-Solver dimension incompatibility (%" HIGHSINT_FORMAT
", %" HIGHSINT_FORMAT ") != (%" HIGHSINT_FORMAT
", %" HIGHSINT_FORMAT ")\n",
lp_num_col, solver_num_col, lp_num_row, solver_num_row);
return false;
}
dimensions_ok = lp_num_col == simplex_nla->lp_->num_col_ &&
lp_num_row == simplex_nla->lp_->num_row_;
assert(dimensions_ok);
if (!dimensions_ok) {
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kError,
"LP-Factor dimension incompatibility (%" HIGHSINT_FORMAT
", %" HIGHSINT_FORMAT ") != (%" HIGHSINT_FORMAT
", %" HIGHSINT_FORMAT ")\n",
lp_num_col, simplex_nla->lp_->num_col_, lp_num_row,
simplex_nla->lp_->num_row_);
return false;
}
return true;
}
bool HEkkDual::bailoutOnDualObjective() {
if (ekk_instance_.solve_bailout_) {
assert(ekk_instance_.model_status_ == HighsModelStatus::kTimeLimit ||
ekk_instance_.model_status_ == HighsModelStatus::kIterationLimit ||
ekk_instance_.model_status_ == HighsModelStatus::kObjectiveBound);
} else if (ekk_instance_.lp_.sense_ == ObjSense::kMinimize &&
solve_phase == kSolvePhase2) {
if (ekk_instance_.info_.updated_dual_objective_value >
ekk_instance_.options_->objective_bound)
ekk_instance_.solve_bailout_ = reachedExactObjectiveBound();
}
return ekk_instance_.solve_bailout_;
}
bool HEkkDual::reachedExactObjectiveBound() {
bool reached_exact_objective_bound = false;
double use_row_ap_density =
std::min(std::max(ekk_instance_.info_.row_ap_density, 0.01), 1.0);
HighsInt check_frequency = 1.0 / use_row_ap_density;
assert(check_frequency > 0);
bool check_exact_dual_objective_value =
ekk_instance_.info_.update_count % check_frequency == 0;
if (check_exact_dual_objective_value) {
const double objective_bound = ekk_instance_.options_->objective_bound;
const double perturbed_dual_objective_value =
ekk_instance_.info_.updated_dual_objective_value;
const double perturbed_value_residual =
perturbed_dual_objective_value - objective_bound;
HVector dual_col;
HVector dual_row;
const double exact_dual_objective_value =
computeExactDualObjectiveValue(dual_col, dual_row);
const double exact_value_residual =
exact_dual_objective_value - objective_bound;
std::string action;
if (exact_dual_objective_value > objective_bound) {
highsLogDev(
ekk_instance_.options_->log_options, HighsLogType::kDetailed,
"HEkkDual::solvePhase2: %12g = Objective > ObjectiveUB = %12g\n",
ekk_instance_.info_.updated_dual_objective_value, objective_bound);
action = "Have DualUB bailout";
if (ekk_instance_.info_.costs_perturbed ||
ekk_instance_.info_.costs_shifted) {
ekk_instance_.initialiseCost(SimplexAlgorithm::kDual, kSolvePhase2);
}
for (HighsInt i = 0; i < solver_num_col; i++)
ekk_instance_.info_.workDual_[i] =
ekk_instance_.info_.workCost_[i] - dual_row.array[i];
for (HighsInt i = solver_num_col; i < solver_num_tot; i++)
ekk_instance_.info_.workDual_[i] = -dual_col.array[i - solver_num_col];
force_phase2 = false;
correctDualInfeasibilities(dualInfeasCount);
assert(!ekk_instance_.info_.costs_shifted);
reached_exact_objective_bound = true;
ekk_instance_.model_status_ = HighsModelStatus::kObjectiveBound;
} else {
action = "No DualUB bailout";
}
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"%s on iteration %" HIGHSINT_FORMAT
": Density %11.4g; Frequency %" HIGHSINT_FORMAT
": "
"Residual(Perturbed = %g; Exact = %g)\n",
action.c_str(), ekk_instance_.iteration_count_,
use_row_ap_density, check_frequency, perturbed_value_residual,
exact_value_residual);
}
return reached_exact_objective_bound;
}
double HEkkDual::computeExactDualObjectiveValue(HVector& dual_col,
HVector& dual_row) {
const HighsLp& lp = ekk_instance_.lp_;
const SimplexBasis& basis = ekk_instance_.basis_;
const HighsSimplexInfo& info = ekk_instance_.info_;
dual_col.setup(lp.num_row_);
dual_col.clear();
for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) {
HighsInt iVar = basis.basicIndex_[iRow];
if (iVar < lp.num_col_) {
const double value = lp.col_cost_[iVar];
if (value) {
dual_col.array[iRow] = value;
dual_col.index[dual_col.count++] = iRow;
}
}
}
const HighsInt numTot = lp.num_col_ + lp.num_row_;
dual_row.setup(lp.num_col_);
dual_row.clear();
if (dual_col.count) {
const bool quad_precision = false;
const double expected_density = 1;
simplex_nla->btran(dual_col, expected_density);
lp.a_matrix_.priceByColumn(quad_precision, dual_row, dual_col);
}
ekk_instance_.computeSimplexDualInfeasible();
if (info.num_dual_infeasibilities > 0)
highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kInfo,
"When computing exact dual objective, the unperturbed costs "
"yield num / max / sum dual "
"infeasibilities = %d / %g / %g\n",
(int)info.num_dual_infeasibilities, info.max_dual_infeasibility,
info.sum_dual_infeasibilities);
HighsCDouble dual_objective = lp.offset_;
double norm_dual = 0;
double norm_delta_dual = 0;
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) {
if (!basis.nonbasicFlag_[iCol]) continue;
double exact_dual = lp.col_cost_[iCol] - dual_row.array[iCol];
double active_value;
if (exact_dual > ekk_instance_.options_->small_matrix_value)
active_value = lp.col_lower_[iCol];
else if (exact_dual < -ekk_instance_.options_->small_matrix_value)
active_value = lp.col_upper_[iCol];
else
active_value = info.workValue_[iCol];
if (highs_isInfinity(fabs(active_value))) return -kHighsInf;
double residual = fabs(exact_dual - info.workDual_[iCol]);
norm_dual += fabs(exact_dual);
norm_delta_dual += residual;
if (residual > 1e10)
highsLogDev(
ekk_instance_.options_->log_options, HighsLogType::kWarning,
"Col %4" HIGHSINT_FORMAT
": ExactDual = %11.4g; WorkDual = %11.4g; Residual = %11.4g\n",
iCol, exact_dual, info.workDual_[iCol], residual);
dual_objective += active_value * exact_dual;
}
for (HighsInt iVar = lp.num_col_; iVar < numTot; iVar++) {
if (!basis.nonbasicFlag_[iVar]) continue;
HighsInt iRow = iVar - lp.num_col_;
double exact_dual = dual_col.array[iRow];
double active_value;
if (exact_dual > ekk_instance_.options_->small_matrix_value)
active_value = lp.row_lower_[iRow];
else if (exact_dual < -ekk_instance_.options_->small_matrix_value)
active_value = lp.row_upper_[iRow];
else
active_value = -info.workValue_[iVar];
if (highs_isInfinity(fabs(active_value))) return -kHighsInf;
double residual = fabs(exact_dual + info.workDual_[iVar]);
norm_dual += fabs(exact_dual);
norm_delta_dual += residual;
if (residual > 1e10)
highsLogDev(
ekk_instance_.options_->log_options, HighsLogType::kWarning,
"Row %4" HIGHSINT_FORMAT
": ExactDual = %11.4g; WorkDual = %11.4g; Residual = %11.4g\n",
iRow, exact_dual, info.workDual_[iVar], residual);
dual_objective += active_value * exact_dual;
}
double relative_delta = norm_delta_dual / std::max(norm_dual, 1.0);
if (relative_delta > 1e-3)
highsLogDev(
ekk_instance_.options_->log_options, HighsLogType::kWarning,
"||exact dual vector|| = %g; ||delta dual vector|| = %g: ratio = %g\n",
norm_dual, norm_delta_dual, relative_delta);
return double(dual_objective);
}
HighsDebugStatus HEkkDual::debugDualSimplex(const std::string message,
const bool initialise) {
HighsDebugStatus return_status =
ekk_instance_.debugSimplex(message, algorithm, solve_phase, initialise);
if (return_status == HighsDebugStatus::kLogicalError) return return_status;
if (initialise) return return_status;
return HighsDebugStatus::kOk;
}
bool HEkkDual::isBadBasisChange() {
return ekk_instance_.isBadBasisChange(SimplexAlgorithm::kDual, variable_in,
row_out, rebuild_reason);
}
void HEkkDual::assessPossiblyDualUnbounded() {
assert(rebuild_reason == kRebuildReasonPossiblyDualUnbounded);
if (solve_phase != kSolvePhase2) return;
if (!ekk_instance_.status_.has_fresh_rebuild) return;
const bool proof_of_infeasibility = proofOfPrimalInfeasibility();
if (proof_of_infeasibility) {
solve_phase = kSolvePhaseExit;
saveDualRay();
assert(ekk_instance_.model_status_ == HighsModelStatus::kNotset);
ekk_instance_.model_status_ = HighsModelStatus::kInfeasible;
} else {
ekk_instance_.addBadBasisChange(
row_out, variable_out, variable_in,
BadBasisChangeReason::kFailedInfeasibilityProof, true);
rebuild_reason = kRebuildReasonNo;
}
}