1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2 /* */
3 /* This file is part of the HiGHS linear optimization suite */
4 /* */
5 /* Written and engineered 2008-2020 at the University of Edinburgh */
6 /* */
7 /* Available as open-source under the MIT License */
8 /* */
9 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
10 /**@file simplex/HDual.cpp
11 * @brief
12 * @author Julian Hall, Ivet Galabova, Qi Huangfu and Michael Feldmeier
13 */
14 #include "HDual.h"
15
16 #include <cassert>
17 #include <cmath>
18 #include <cstdio>
19 #include <cstring>
20 #include <iostream>
21 #include <set>
22 #include <stdexcept>
23
24 #include "io/HighsIO.h"
25 #include "lp_data/HConst.h"
26 #include "lp_data/HighsLp.h"
27 #include "lp_data/HighsLpUtils.h"
28 #include "lp_data/HighsModelObject.h"
29 #include "simplex/HCrash.h"
30 #include "simplex/HPrimal.h"
31 #include "simplex/HQPrimal.h"
32 #include "simplex/HSimplex.h"
33 #include "simplex/HSimplexDebug.h"
34 #include "simplex/SimplexTimer.h"
35 #include "util/HighsTimer.h"
36
37 #ifdef OPENMP
38 #include "omp.h"
39 #endif
40
41 using std::cout;
42 using std::endl;
43 using std::fabs;
44 using std::flush;
45 using std::runtime_error;
46
solve()47 HighsStatus HDual::solve() {
48 assert(SOLVE_PHASE_ERROR == -3);
49 assert(SOLVE_PHASE_EXIT == -2);
50 assert(SOLVE_PHASE_UNKNOWN == -1);
51 assert(SOLVE_PHASE_OPTIMAL == 0);
52 assert(SOLVE_PHASE_1 == 1);
53 assert(SOLVE_PHASE_2 == 2);
54 assert(SOLVE_PHASE_CLEANUP == 4);
55 HighsOptions& options = workHMO.options_;
56 HighsSolutionParams& scaled_solution_params = workHMO.scaled_solution_params_;
57 HighsIterationCounts& iteration_counts = workHMO.iteration_counts_;
58 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
59 HighsSimplexLpStatus& simplex_lp_status = workHMO.simplex_lp_status_;
60 HighsModelStatus& scaled_model_status = workHMO.scaled_model_status_;
61 scaled_model_status = HighsModelStatus::NOTSET;
62 if (debugSimplexInfoBasisRightSize(workHMO) ==
63 HighsDebugStatus::LOGICAL_ERROR)
64 return returnFromSolve(HighsStatus::Error);
65 // Assumes that the LP has a positive number of rows, since
66 // unconstrained LPs should be solved in solveLpSimplex
67 bool positive_num_row = workHMO.simplex_lp_.numRow_ > 0;
68 assert(positive_num_row);
69 if (!positive_num_row) {
70 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::ERROR,
71 "HPrimal::solve called for LP with non-positive (%d) "
72 "number of constraints",
73 workHMO.simplex_lp_.numRow_);
74 return returnFromSolve(HighsStatus::Error);
75 }
76
77 invertHint = INVERT_HINT_NO;
78
79 // Set solve_bailout to be true if control is to be returned immediately to
80 // calling function
81 solve_bailout = false;
82 if (bailoutOnTimeIterations()) return returnFromSolve(HighsStatus::Warning);
83
84 // Initialise working environment. Does LOTS, including
85 // initialisation of edge weights to 1s. Should only be called if
86 // model dimension changes
87 init();
88 initParallel();
89
90 bool dual_info_ok = dualInfoOk(workHMO.lp_);
91 if (!dual_info_ok) {
92 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::ERROR,
93 "HPrimalDual::solve has error in dual information");
94 return returnFromSolve(HighsStatus::Error);
95 }
96
97 // Decide whether to use LiDSE by not storing squared primal infeasibilities
98 simplex_info.store_squared_primal_infeasibility = true;
99 if (options.less_infeasible_DSE_check) {
100 if (isLessInfeasibleDSECandidate(options, workHMO.simplex_lp_)) {
101 // LP is a candidate for LiDSE
102 if (options.less_infeasible_DSE_choose_row)
103 // Use LiDSE
104 simplex_info.store_squared_primal_infeasibility = false;
105 }
106 }
107
108 initialiseCost(workHMO, 1);
109 assert(simplex_lp_status.has_invert);
110 if (!simplex_lp_status.has_invert) {
111 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::ERROR,
112 "HPrimalDual:: Should enter solve with INVERT");
113 return returnFromSolve(HighsStatus::Error);
114 }
115 // Consider initialising edge weights
116 //
117 // NB workEdWt is assigned and initialised to 1s in
118 // dualRHS.setup(workHMO) so that CHUZR is well defined, even for
119 // Dantzig pricing
120 //
121 if (!simplex_lp_status.has_dual_steepest_edge_weights) {
122 // Edge weights are not known
123 // Set up edge weights according to dual_edge_weight_mode and
124 // initialise_dual_steepest_edge_weights
125 if (dual_edge_weight_mode == DualEdgeWeightMode::DEVEX) {
126 // Using dual Devex edge weights, so set up the first framework
127 simplex_info.devex_index_.assign(solver_num_tot, 0);
128 initialiseDevexFramework();
129 } else if (dual_edge_weight_mode == DualEdgeWeightMode::STEEPEST_EDGE) {
130 // Using dual steepest edge (DSE) weights
131 int num_basic_structurals =
132 solver_num_row - simplex_info.num_basic_logicals;
133 bool computeExactDseWeights =
134 num_basic_structurals > 0 && initialise_dual_steepest_edge_weights;
135 #ifdef HiGHSDEV
136 if (computeExactDseWeights) {
137 printf(
138 "If (0<num_basic_structurals = %d) && %d = "
139 "initialise_dual_steepest_edge_weights: Compute exact "
140 "DSE weights\n",
141 num_basic_structurals, initialise_dual_steepest_edge_weights);
142 }
143 #endif
144 if (computeExactDseWeights) {
145 // Basis is not logical and DSE weights are to be initialised
146 #ifdef HiGHSDEV
147 printf("Compute exact DSE weights\n");
148 analysis->simplexTimerStart(SimplexIzDseWtClock);
149 analysis->simplexTimerStart(DseIzClock);
150 #endif
151 for (int i = 0; i < solver_num_row; i++) {
152 row_ep.clear();
153 row_ep.count = 1;
154 row_ep.index[0] = i;
155 row_ep.array[i] = 1;
156 row_ep.packFlag = false;
157 factor->btran(row_ep, analysis->row_ep_density,
158 analysis->pointer_serial_factor_clocks);
159 dualRHS.workEdWt[i] = row_ep.norm2();
160 const double local_row_ep_density =
161 (double)row_ep.count / solver_num_row;
162 analysis->updateOperationResultDensity(local_row_ep_density,
163 analysis->row_ep_density);
164 }
165 #ifdef HiGHSDEV
166 analysis->simplexTimerStop(SimplexIzDseWtClock);
167 analysis->simplexTimerStop(DseIzClock);
168 double IzDseWtTT = analysis->simplexTimerRead(SimplexIzDseWtClock);
169 HighsPrintMessage(options.output, options.message_level, ML_DETAILED,
170 "Computed %d initial DSE weights in %gs\n",
171 solver_num_row, IzDseWtTT);
172 #endif
173 }
174 #ifdef HiGHSDEV
175 else {
176 HighsPrintMessage(options.output, options.message_level, ML_DETAILED,
177 "solve:: %d basic structurals: starting from B=I so "
178 "unit initial DSE weights\n",
179 num_basic_structurals);
180 }
181 #endif
182 }
183 // Indicate that edge weights are known
184 simplex_lp_status.has_dual_steepest_edge_weights = true;
185 }
186 // Compute the dual values
187 computeDual(workHMO);
188 // Determine the number of dual infeasibilities, and hence the solve phase
189 computeDualInfeasibleWithFlips(workHMO);
190 dualInfeasCount = scaled_solution_params.num_dual_infeasibilities;
191 solvePhase = dualInfeasCount > 0 ? SOLVE_PHASE_1 : SOLVE_PHASE_2;
192 if (debugOkForSolve(workHMO, solvePhase) == HighsDebugStatus::LOGICAL_ERROR)
193 return returnFromSolve(HighsStatus::Error);
194 //
195 // The major solving loop
196 //
197 while (solvePhase) {
198 int it0 = iteration_counts.simplex;
199 // When starting a new phase the (updated) dual objective function
200 // value isn't known. Indicate this so that when the value
201 // computed from scratch in build() isn't checked against the the
202 // updated value
203 simplex_lp_status.has_dual_objective_value = false;
204 if (solvePhase == SOLVE_PHASE_UNKNOWN) {
205 // Reset the phase 2 bounds so that true number of dual
206 // infeasibilities canbe determined
207 initialiseBound(workHMO);
208 // Determine the number of dual infeasibilities, and hence the solve phase
209 computeDualInfeasibleWithFlips(workHMO);
210 dualInfeasCount = scaled_solution_params.num_dual_infeasibilities;
211 solvePhase = dualInfeasCount > 0 ? SOLVE_PHASE_1 : SOLVE_PHASE_2;
212 if (simplex_info.backtracking_) {
213 // Backtracking, so set the bounds and primal values
214 initialiseBound(workHMO, solvePhase);
215 initialiseValueAndNonbasicMove(workHMO);
216 // Can now forget that we might have been backtracking
217 simplex_info.backtracking_ = false;
218 }
219 }
220 assert(solvePhase == SOLVE_PHASE_1 || solvePhase == SOLVE_PHASE_2);
221 if (solvePhase == SOLVE_PHASE_1) {
222 // Phase 1
223 analysis->simplexTimerStart(SimplexDualPhase1Clock);
224 solvePhase1();
225 analysis->simplexTimerStop(SimplexDualPhase1Clock);
226 simplex_info.dual_phase1_iteration_count +=
227 (iteration_counts.simplex - it0);
228 } else if (solvePhase == SOLVE_PHASE_2) {
229 // Phase 2
230 analysis->simplexTimerStart(SimplexDualPhase2Clock);
231 solvePhase2();
232 analysis->simplexTimerStop(SimplexDualPhase2Clock);
233 simplex_info.dual_phase2_iteration_count +=
234 (iteration_counts.simplex - it0);
235 } else {
236 // Should only be SOLVE_PHASE_1 or SOLVE_PHASE_2
237 scaled_model_status = HighsModelStatus::SOLVE_ERROR;
238 return returnFromSolve(HighsStatus::Error);
239 }
240 // Return if bailing out from solve
241 if (solve_bailout) return returnFromSolve(HighsStatus::Warning);
242 // Can have all possible cases of solvePhase
243 assert(solvePhase >= SOLVE_PHASE_MIN && solvePhase <= SOLVE_PHASE_MAX);
244 // Look for scenarios when the major solving loop ends
245 if (solvePhase == SOLVE_PHASE_ERROR) {
246 // Solver error so return HighsStatus::Error
247 assert(scaled_model_status == HighsModelStatus::SOLVE_ERROR);
248 return returnFromSolve(HighsStatus::Error);
249 }
250 if (solvePhase == SOLVE_PHASE_EXIT) {
251 // LP identified as not having an optimal solution
252 assert(scaled_model_status == HighsModelStatus::PRIMAL_DUAL_INFEASIBLE ||
253 scaled_model_status == HighsModelStatus::PRIMAL_INFEASIBLE);
254 break;
255 }
256 if (solvePhase == SOLVE_PHASE_1 &&
257 scaled_model_status == HighsModelStatus::DUAL_INFEASIBLE) {
258 // Dual infeasibilities after phase 2 for a problem known to be dual
259 // infeasible.
260 break;
261 }
262 if (solvePhase == SOLVE_PHASE_CLEANUP) {
263 // Dual infeasibilities after phase 2 for a problem not known to be dual
264 // infeasible Primal feasible and dual infeasibilities
265 break;
266 }
267 // If solvePhase == SOLVE_PHASE_OPTIMAL == 0 then major solving
268 // loop ends naturally since solvePhase is false
269 }
270 // If bailing out, should have returned already
271 assert(!solve_bailout);
272 // Should only have these cases
273 assert(solvePhase == SOLVE_PHASE_EXIT || solvePhase == SOLVE_PHASE_UNKNOWN ||
274 solvePhase == SOLVE_PHASE_OPTIMAL || solvePhase == SOLVE_PHASE_1 ||
275 solvePhase == SOLVE_PHASE_CLEANUP);
276 if (solvePhase == SOLVE_PHASE_1) {
277 assert(scaled_model_status == HighsModelStatus::DUAL_INFEASIBLE);
278 // Resolve case of LP that is dual infeasible (and not primal
279 // feasible since that would yield solvePhase ==
280 // SOLVE_PHASE_CLEANUP Looking to identify primal infeasiblilty or
281 // primal unboundedness Cleanup with phase 1 for new primal code
282 computePrimalObjectiveValue(workHMO);
283 HQPrimal hPrimal(workHMO);
284 hPrimal.solve();
285 // Horrible hack until the primal simplex solver is refined
286 if (scaled_model_status == HighsModelStatus::OPTIMAL) {
287 if (workHMO.scaled_solution_params_.num_primal_infeasibilities) {
288 // Optimal with primal infeasibilities => primal infeasible
289 assert(workHMO.scaled_solution_params_.num_primal_infeasibilities > 0);
290 scaled_model_status = HighsModelStatus::PRIMAL_DUAL_INFEASIBLE;
291 }
292 } else {
293 // Should only be primal unbounded
294 assert(scaled_model_status == HighsModelStatus::PRIMAL_UNBOUNDED);
295 }
296 }
297 if (solvePhase == SOLVE_PHASE_CLEANUP) {
298 computePrimalObjectiveValue(workHMO);
299 #ifdef HiGHSDEV
300 vector<double> primal_value_before_cleanup;
301 getPrimalValue(workHMO, primal_value_before_cleanup);
302 // printf("\nAnalyse primal objective evaluation before cleanup\n");
303 // analysePrimalObjectiveValue(workHMO);
304 const double objective_before = simplex_info.primal_objective_value;
305 #endif
306 if (options.dual_simplex_cleanup_strategy ==
307 DUAL_SIMPLEX_CLEANUP_STRATEGY_NONE) {
308 // No clean up. Dual simplex was optimal with perturbed costs,
309 // so say that the scaled LP has been solved
310 // optimally. Optimality (unlikely) for the unscaled LP will
311 // still be assessed honestly, so leave it to the user to
312 // deceide whether the solution can be accepted.
313 scaled_model_status = HighsModelStatus::OPTIMAL;
314 } else {
315 // Use primal to clean up
316 #ifdef HiGHSDEV
317 initialiseValueDistribution("Cleanup primal step summary", "", 1e-16,
318 1e16, 10.0,
319 analysis->cleanup_primal_step_distribution);
320 initialiseValueDistribution("Cleanup dual step summary", "", 1e-16, 1e16,
321 10.0,
322 analysis->cleanup_dual_step_distribution);
323 #endif
324 int it0 = iteration_counts.simplex;
325 const bool full_logging = false; // true;//
326 if (full_logging)
327 analysis->messaging(options.logfile, options.output, ML_ALWAYS);
328 analysis->simplexTimerStart(SimplexPrimalPhase2Clock);
329 if (options.dual_simplex_cleanup_strategy ==
330 DUAL_SIMPLEX_CLEANUP_STRATEGY_HPRIMAL) {
331 // Cleanup with original primal phase 2 code
332 HPrimal hPrimal(workHMO);
333 hPrimal.solvePhase2();
334 } else {
335 // Cleanup with phase 2 for new primal code
336 HQPrimal hPrimal(workHMO);
337 hPrimal.solvePhase2();
338 }
339 analysis->simplexTimerStop(SimplexPrimalPhase2Clock);
340 #ifdef HiGHSDEV
341 vector<double> primal_value_after_cleanup;
342 getPrimalValue(workHMO, primal_value_after_cleanup);
343 for (int var = 0; var < solver_num_tot; var++) {
344 const double primal_change = fabs(primal_value_after_cleanup[var] -
345 primal_value_before_cleanup[var]);
346 updateValueDistribution(primal_change,
347 analysis->cleanup_primal_change_distribution);
348 }
349 // printf("\nAnalyse primal objective evaluation after cleanup\n");
350 // analysePrimalObjectiveValue(workHMO);
351 const double objective_after = simplex_info.primal_objective_value;
352 const double abs_objective_change =
353 fabs(objective_before - objective_after);
354 const double rel_objective_change =
355 abs_objective_change / max(1.0, fabs(objective_after));
356 printf(
357 "\nDuring cleanup, (abs: rel) primal objective changes is (%10.4g: "
358 "%10.4g) \nfrom %20.10g\nto %20.10g\n",
359 abs_objective_change, rel_objective_change, objective_before,
360 objective_after);
361 #endif
362 simplex_info.primal_phase2_iteration_count +=
363 (iteration_counts.simplex - it0);
364 }
365 }
366 if (debugOkForSolve(workHMO, solvePhase) == HighsDebugStatus::LOGICAL_ERROR)
367 return returnFromSolve(HighsStatus::Error);
368 computePrimalObjectiveValue(workHMO);
369 return returnFromSolve(HighsStatus::OK);
370 }
371
options()372 void HDual::options() {
373 // Set solver options from simplex options
374
375 const HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
376 const HighsSolutionParams& scaled_solution_params =
377 workHMO.scaled_solution_params_;
378
379 interpretDualEdgeWeightStrategy(simplex_info.dual_edge_weight_strategy);
380
381 // Copy values of simplex solver options to dual simplex options
382 primal_feasibility_tolerance =
383 scaled_solution_params.primal_feasibility_tolerance;
384 dual_feasibility_tolerance =
385 scaled_solution_params.dual_feasibility_tolerance;
386 dual_objective_value_upper_bound =
387 workHMO.options_.dual_objective_value_upper_bound;
388 // perturb_costs = simplex_info.perturb_costs;
389 // iterationLimit = simplex_info.iterationLimit;
390
391 // Set values of internal options
392 }
393
init()394 void HDual::init() {
395 // Copy size, matrix and factor
396
397 solver_num_col = workHMO.simplex_lp_.numCol_;
398 solver_num_row = workHMO.simplex_lp_.numRow_;
399 solver_num_tot = solver_num_col + solver_num_row;
400
401 matrix = &workHMO.matrix_;
402 factor = &workHMO.factor_;
403 analysis = &workHMO.simplex_analysis_;
404
405 // Copy pointers
406 jMove = &workHMO.simplex_basis_.nonbasicMove_[0];
407 workDual = &workHMO.simplex_info_.workDual_[0];
408 workValue = &workHMO.simplex_info_.workValue_[0];
409 workRange = &workHMO.simplex_info_.workRange_[0];
410 baseLower = &workHMO.simplex_info_.baseLower_[0];
411 baseUpper = &workHMO.simplex_info_.baseUpper_[0];
412 baseValue = &workHMO.simplex_info_.baseValue_[0];
413
414 // Copy tolerances
415 Tp = primal_feasibility_tolerance;
416 Td = dual_feasibility_tolerance;
417
418 // Setup local vectors
419 col_DSE.setup(solver_num_row);
420 col_BFRT.setup(solver_num_row);
421 col_aq.setup(solver_num_row);
422 row_ep.setup(solver_num_row);
423 row_ap.setup(solver_num_col);
424 // Setup other buffers
425 dualRow.setup();
426 dualRHS.setup();
427 }
428
initParallel()429 void HDual::initParallel() {
430 // Identify the (current) number of HiGHS tasks to be used
431 const int num_threads = workHMO.simplex_info_.num_threads;
432
433 // Initialize for tasks
434 if (workHMO.simplex_info_.simplex_strategy == SIMPLEX_STRATEGY_DUAL_TASKS) {
435 const int pass_num_slice = num_threads - 2;
436 assert(pass_num_slice > 0);
437 if (pass_num_slice <= 0) {
438 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::WARNING,
439 "SIP trying to use using %d slices due to number of "
440 "threads (%d) being too small: results unpredictable",
441 pass_num_slice, num_threads);
442 }
443 initSlice(pass_num_slice);
444 }
445
446 // Initialize for multi
447 if (workHMO.simplex_info_.simplex_strategy == SIMPLEX_STRATEGY_DUAL_MULTI) {
448 multi_num = num_threads;
449 if (multi_num < 1) multi_num = 1;
450 if (multi_num > HIGHS_THREAD_LIMIT) multi_num = HIGHS_THREAD_LIMIT;
451 for (int i = 0; i < multi_num; i++) {
452 multi_choice[i].row_ep.setup(solver_num_row);
453 multi_choice[i].col_aq.setup(solver_num_row);
454 multi_choice[i].col_BFRT.setup(solver_num_row);
455 }
456 const int pass_num_slice = max(multi_num - 1, 1);
457 assert(pass_num_slice > 0);
458 if (pass_num_slice <= 0) {
459 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::WARNING,
460 "PAMI trying to use using %d slices due to number of "
461 "threads (%d) being too small: results unpredictable",
462 pass_num_slice, num_threads);
463 }
464 initSlice(pass_num_slice);
465 }
466 multi_iteration = 0;
467 // string partitionFile = model->strOption[STROPT_PARTITION_FILE];
468 // if (partitionFile.size())
469 // {
470 // dualRHS.setup_partition(partitionFile.c_str());
471 // }
472 }
473
initSlice(const int initial_num_slice)474 void HDual::initSlice(const int initial_num_slice) {
475 // Number of slices
476 slice_num = initial_num_slice;
477 if (slice_num < 1) slice_num = 1;
478 assert(slice_num <= HIGHS_SLICED_LIMIT);
479 if (slice_num > HIGHS_SLICED_LIMIT) {
480 #ifdef HiGHSDEV
481 printf(
482 "WARNING: %d = slice_num > HIGHS_SLICED_LIMIT = %d so truncating "
483 "slice_num\n",
484 slice_num, HIGHS_SLICED_LIMIT);
485 #endif
486 slice_num = HIGHS_SLICED_LIMIT;
487 }
488
489 // Alias to the matrix
490 const int* Astart = matrix->getAstart();
491 const int* Aindex = matrix->getAindex();
492 const double* Avalue = matrix->getAvalue();
493 const int AcountX = Astart[solver_num_col];
494
495 // Figure out partition weight
496 double sliced_countX = AcountX / slice_num;
497 slice_start[0] = 0;
498 for (int i = 0; i < slice_num - 1; i++) {
499 int endColumn = slice_start[i] + 1; // At least one column
500 int endX = Astart[endColumn];
501 int stopX = (i + 1) * sliced_countX;
502 while (endX < stopX) {
503 endX = Astart[++endColumn];
504 }
505 slice_start[i + 1] = endColumn;
506 if (endColumn >= solver_num_col) {
507 slice_num = i; // SHRINK
508 break;
509 }
510 }
511 slice_start[slice_num] = solver_num_col;
512
513 // Partition the matrix, row_ap and related packet
514 vector<int> sliced_Astart;
515 for (int i = 0; i < slice_num; i++) {
516 // The matrix
517 int mystart = slice_start[i];
518 int mycount = slice_start[i + 1] - mystart;
519 int mystartX = Astart[mystart];
520 sliced_Astart.resize(mycount + 1);
521 for (int k = 0; k <= mycount; k++)
522 sliced_Astart[k] = Astart[k + mystart] - mystartX;
523 slice_matrix[i].setup_lgBs(mycount, solver_num_row, &sliced_Astart[0],
524 Aindex + mystartX, Avalue + mystartX);
525
526 // The row_ap and its packages
527 slice_row_ap[i].setup(mycount);
528 slice_dualRow[i].setupSlice(mycount);
529 }
530 }
531
solvePhase1()532 void HDual::solvePhase1() {
533 // Performs dual phase 1 iterations. Returns solvePhase with value
534 //
535 // SOLVE_PHASE_ERROR => Solver error
536 //
537 // SOLVE_PHASE_UNKNOWN => Back-tracking due to singularity
538 //
539 // SOLVE_PHASE_1 => Dual infeasibility suspected, but have to go out
540 // and back in to solvePhase1 to perform fresh rebuild. Also if
541 // bailing out due to reaching time/iteration limit.
542 //
543 // SOLVE_PHASE_2 => Continue with dual phase 2 iterations
544
545 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
546 HighsSimplexLpStatus& simplex_lp_status = workHMO.simplex_lp_status_;
547 HighsModelStatus& scaled_model_status = workHMO.scaled_model_status_;
548 // When starting a new phase the (updated) dual objective function
549 // value isn't known. Indicate this so that when the value computed
550 // from scratch in build() isn't checked against the the updated
551 // value
552 simplex_lp_status.has_primal_objective_value = false;
553 simplex_lp_status.has_dual_objective_value = false;
554 // Set invertHint so that it's assigned when first tested
555 invertHint = INVERT_HINT_NO;
556 // Set solvePhase = SOLVE_PHASE_1 and solve_bailout = false so they are set if
557 // solvePhase1() is called directly
558 solvePhase = SOLVE_PHASE_1;
559 solve_bailout = false;
560 if (bailoutOnTimeIterations()) return;
561 // Report the phase start
562 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
563 ML_DETAILED, "dual-phase-1-start\n");
564 // Switch to dual phase 1 bounds
565 initialiseBound(workHMO, 1);
566 initialiseValueAndNonbasicMove(workHMO);
567
568 // If there's no backtracking basis Save the initial basis in case of
569 // backtracking
570 if (!simplex_info.valid_backtracking_basis_) putBacktrackingBasis();
571
572 // Main solving structure
573 analysis->simplexTimerStart(IterateClock);
574 for (;;) {
575 analysis->simplexTimerStart(IterateDualRebuildClock);
576 rebuild();
577 analysis->simplexTimerStop(IterateDualRebuildClock);
578 if (solvePhase == SOLVE_PHASE_ERROR) {
579 scaled_model_status = HighsModelStatus::SOLVE_ERROR;
580 return;
581 }
582 if (solvePhase == SOLVE_PHASE_UNKNOWN) {
583 // If backtracking, may change phase, so drop out
584 analysis->simplexTimerStop(IterateClock);
585 return;
586 }
587 if (bailoutOnTimeIterations()) break;
588 for (;;) {
589 switch (simplex_info.simplex_strategy) {
590 default:
591 case SIMPLEX_STRATEGY_DUAL_PLAIN:
592 iterate();
593 break;
594 case SIMPLEX_STRATEGY_DUAL_TASKS:
595 iterateTasks();
596 break;
597 case SIMPLEX_STRATEGY_DUAL_MULTI:
598 iterateMulti();
599 break;
600 }
601 if (bailoutOnTimeIterations()) break;
602 if (invertHint) break;
603 }
604 if (solve_bailout) break;
605 // If the data are fresh from rebuild(), break out of
606 // the outer loop to see what's ocurred
607 // Was: if (simplex_info.update_count == 0) break;
608 if (simplex_lp_status.has_fresh_rebuild) break;
609 }
610
611 analysis->simplexTimerStop(IterateClock);
612 // Possibly return due to bailing out, having now stopped
613 // IterateClock
614 if (bailoutReturn()) return;
615
616 // If bailing out, should have done so already
617 assert(!solve_bailout);
618 // Assess outcome of dual phase 1
619 if (rowOut == -1) {
620 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
621 ML_DETAILED, "dual-phase-1-optimal\n");
622 // Optimal in phase 1
623 if (simplex_info.dual_objective_value == 0) {
624 // Zero phase 1 objective so go to phase 2
625 //
626 // This is the usual way to exit phase 1. Although the test
627 // looks ambitious, the dual objective is the sum of products of
628 // primal and dual values for nonbasic variables. For dual
629 // simplex phase 1, the primal bounds are set so that when the
630 // dual value is feasible, the primal value is set to
631 // zero. Otherwise the value is +1/-1 according to the required
632 // sign of the dual, except for free variables, where the bounds
633 // are [-1000, 1000].
634 //
635 // OK if costs are perturbed, since they remain perturbed in phase 2 until
636 // the final clean-up
637 solvePhase = SOLVE_PHASE_2;
638 } else {
639 // A negative dual objective value at an optimal solution of
640 // phase 1 means that there are dual infeasibilities. If the
641 // objective value is very negative, then it's clear
642 // enough. However, if it's small, it could be the sum of
643 // values, all of which are smaller than the dual deasibility
644 // tolerance. Plus there may be cost perturbations to remove
645 assessPhase1Optimality();
646 }
647 } else if (invertHint == INVERT_HINT_CHOOSE_COLUMN_FAIL) {
648 // chooseColumn has failed
649 // Behave as "Report strange issues" below
650 solvePhase = SOLVE_PHASE_ERROR;
651 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
652 ML_MINIMAL, "dual-phase-1-not-solved\n");
653 scaled_model_status = HighsModelStatus::SOLVE_ERROR;
654 } else if (columnIn == -1) {
655 // We got dual phase 1 unbounded - strange
656 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
657 ML_MINIMAL, "dual-phase-1-unbounded\n");
658 if (workHMO.simplex_info_.costs_perturbed) {
659 // Clean up perturbation
660 cleanup();
661 HighsLogMessage(
662 workHMO.options_.logfile, HighsMessageType::WARNING,
663 "Cleaning up cost perturbation when unbounded in phase 1");
664 if (dualInfeasCount == 0) {
665 // No dual infeasibilities and (since unbounded) at least zero
666 // phase 1 objective so go to phase 2
667 solvePhase = SOLVE_PHASE_2;
668 }
669 } else {
670 // Report strange issues
671 solvePhase = SOLVE_PHASE_ERROR;
672 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
673 ML_MINIMAL, "dual-phase-1-not-solved\n");
674 scaled_model_status = HighsModelStatus::SOLVE_ERROR;
675 }
676 }
677
678 if (solvePhase == SOLVE_PHASE_2) {
679 // Moving to phase 2 so allow cost perturbation. It may have been
680 // prevented to avoid cleanup-perturbation loops when optimal in
681 // phase 1
682 simplex_info.allow_cost_perturbation = true;
683 initialiseBound(workHMO);
684 initialiseValueAndNonbasicMove(workHMO);
685 }
686 return;
687 }
688
solvePhase2()689 void HDual::solvePhase2() {
690 // Performs dual phase 2 iterations. Returns solvePhase with value
691 //
692 // SOLVE_PHASE_ERROR => Solver error
693 //
694 // SOLVE_PHASE_EXIT => LP identified as not having an optimal solution
695 //
696 // SOLVE_PHASE_UNKNOWN => Back-tracking due to singularity
697 //
698 // SOLVE_PHASE_OPTIMAL => Primal feasible and no dual infeasibilities =>
699 // Optimal
700 //
701 // SOLVE_PHASE_1 => Primal feasible and dual infeasibilities for a
702 // problem known to be dual infeasible => Use primal phase 2 to
703 // determine primal unboundedness.
704 //
705 // SOLVE_PHASE_2 => Dual unboundedness suspected, but have to go out
706 // and back in to solvePhase2 to perform fresh rebuild. Also if
707 // bailing out due to reaching time/iteration limit or dual
708 // objective
709 //
710 // SOLVE_PHASE_CLEANUP => Contrinue with primal phase 2 iterations to clean up
711 // dual infeasibilities
712 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
713 HighsSimplexLpStatus& simplex_lp_status = workHMO.simplex_lp_status_;
714 HighsModelStatus& scaled_model_status = workHMO.scaled_model_status_;
715 // When starting a new phase the (updated) dual objective function
716 // value isn't known. Indicate this so that when the value computed
717 // from scratch in build() isn't checked against the the updated
718 // value
719 simplex_lp_status.has_primal_objective_value = false;
720 simplex_lp_status.has_dual_objective_value = false;
721 // Set invertHint so that it's assigned when first tested
722 invertHint = INVERT_HINT_NO;
723 // Set solvePhase = SOLVE_PHASE_2 and solve_bailout = false so they are set if
724 // solvePhase2() is called directly
725 solvePhase = SOLVE_PHASE_2;
726 solve_bailout = false;
727 if (bailoutOnTimeIterations()) return;
728 // Report the phase start
729 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
730 ML_DETAILED, "dual-phase-2-start\n");
731 // Collect free variables
732 dualRow.createFreelist();
733
734 // If there's no backtracking basis Save the initial basis in case of
735 // backtracking
736 if (!simplex_info.valid_backtracking_basis_) putBacktrackingBasis();
737
738 // Main solving structure
739 analysis->simplexTimerStart(IterateClock);
740 for (;;) {
741 // Outer loop of solvePhase2()
742 // Rebuild all values, reinverting B if updates have been performed
743 analysis->simplexTimerStart(IterateDualRebuildClock);
744 rebuild();
745 analysis->simplexTimerStop(IterateDualRebuildClock);
746 if (solvePhase == SOLVE_PHASE_ERROR) {
747 scaled_model_status = HighsModelStatus::SOLVE_ERROR;
748 return;
749 }
750 if (solvePhase == SOLVE_PHASE_UNKNOWN) {
751 // If backtracking, may change phase, so drop out
752 analysis->simplexTimerStop(IterateClock);
753 return;
754 }
755 if (bailoutOnTimeIterations()) break;
756 if (bailoutOnDualObjective()) break;
757 if (dualInfeasCount > 0) break;
758 for (;;) {
759 // Inner loop of solvePhase2()
760 // Performs one iteration in case SIMPLEX_STRATEGY_DUAL_PLAIN:
761 switch (simplex_info.simplex_strategy) {
762 default:
763 case SIMPLEX_STRATEGY_DUAL_PLAIN:
764 iterate();
765 break;
766 case SIMPLEX_STRATEGY_DUAL_TASKS:
767 iterateTasks();
768 break;
769 case SIMPLEX_STRATEGY_DUAL_MULTI:
770 iterateMulti();
771 break;
772 }
773 if (bailoutOnTimeIterations()) break;
774 if (bailoutOnDualObjective()) break;
775 if (invertHint) break;
776 }
777 if (solve_bailout) break;
778 // If the data are fresh from rebuild(), break out of
779 // the outer loop to see what's ocurred
780 // Was: if (simplex_info.update_count == 0) break;
781 if (simplex_lp_status.has_fresh_rebuild) break;
782 }
783 analysis->simplexTimerStop(IterateClock);
784 // Possibly return due to bailing out, having now stopped
785 // IterateClock
786 if (bailoutReturn()) return;
787
788 // If bailing out, should have done so already
789 assert(!solve_bailout);
790 // Assess outcome of dual phase 2
791 if (dualInfeasCount > 0) {
792 // There are dual infeasiblities so possibly switch to Phase 1 and
793 // return. "Possibly" because, if dual infeasibility has already
794 // been shown, primal simplex is used to distinguish primal
795 // unboundedness from primal infeasibility
796 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
797 ML_DETAILED, "dual-phase-2-found-free\n");
798 solvePhase = SOLVE_PHASE_1;
799 } else if (rowOut == -1) {
800 // There is no candidate in CHUZR, even after rebuild so probably optimal
801 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
802 ML_DETAILED, "dual-phase-2-optimal\n");
803 // Remove any cost perturbations and see if basis is still dual feasible
804 cleanup();
805 if (dualInfeasCount > 0) {
806 // There are dual infeasiblities, so consider performing primal
807 // simplex iterations to get dual feasibility
808 solvePhase = SOLVE_PHASE_CLEANUP;
809 } else {
810 // There are no dual infeasiblities so optimal!
811 solvePhase = SOLVE_PHASE_OPTIMAL;
812 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
813 ML_DETAILED, "problem-optimal\n");
814 scaled_model_status = HighsModelStatus::OPTIMAL;
815 }
816 } else if (invertHint == INVERT_HINT_CHOOSE_COLUMN_FAIL) {
817 // chooseColumn has failed
818 // Behave as "Report strange issues" below
819 solvePhase = SOLVE_PHASE_ERROR;
820 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
821 ML_MINIMAL, "dual-phase-2-not-solved\n");
822 scaled_model_status = HighsModelStatus::SOLVE_ERROR;
823 } else if (columnIn == -1) {
824 // There is no candidate in CHUZC, so probably dual unbounded
825 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
826 ML_MINIMAL, "dual-phase-2-unbounded\n");
827 if (workHMO.simplex_info_.costs_perturbed) {
828 // If the costs have been perturbed, clean up and return
829 cleanup();
830 } else {
831 // If the costs have not been perturbed, so dual unbounded---and hence
832 // primal infeasible (and possibly also dual infeasible)
833 solvePhase = SOLVE_PHASE_EXIT;
834 if (scaled_model_status == HighsModelStatus::DUAL_INFEASIBLE) {
835 HighsPrintMessage(workHMO.options_.output,
836 workHMO.options_.message_level, ML_MINIMAL,
837 "problem-primal-dual-infeasible\n");
838 scaled_model_status = HighsModelStatus::PRIMAL_DUAL_INFEASIBLE;
839 } else {
840 // Dual unbounded, so save dual ray
841 saveDualRay();
842 // Model status should be unset?
843 assert(scaled_model_status == HighsModelStatus::NOTSET);
844 HighsPrintMessage(workHMO.options_.output,
845 workHMO.options_.message_level, ML_MINIMAL,
846 "problem-primal-infeasible\n");
847 scaled_model_status = HighsModelStatus::PRIMAL_INFEASIBLE;
848 }
849 }
850 }
851 return;
852 }
853
rebuild()854 void HDual::rebuild() {
855 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
856 HighsSimplexLpStatus& simplex_lp_status = workHMO.simplex_lp_status_;
857 // Save history information
858 // Move this to Simplex class once it's created
859 // record_pivots(-1, -1, 0); // Indicate REINVERT
860
861 const int rebuild_invert_hint = invertHint;
862 invertHint = INVERT_HINT_NO;
863 // Possibly Rebuild workHMO.factor_
864 bool reInvert = simplex_info.update_count > 0;
865 if (!invert_if_row_out_negative) {
866 // Don't reinvert if rowOut is negative [equivalently, if
867 // rebuild_invert_hint == INVERT_HINT_POSSIBLY_OPTIMAL]
868 if (rebuild_invert_hint == INVERT_HINT_POSSIBLY_OPTIMAL) {
869 assert(rowOut == -1);
870 reInvert = false;
871 }
872 }
873 if (reInvert) {
874 // Get a nonsingular inverse if possible. One of three things
875 // happens: Current basis is nonsingular; Current basis is
876 // singular and last nonsingular basis is refactorized as
877 // nonsingular - or found singular. Latter is code failure.
878 if (!getNonsingularInverse()) {
879 solvePhase = SOLVE_PHASE_ERROR;
880 return;
881 }
882 }
883
884 if (!workHMO.simplex_lp_status_.has_matrix_row_wise ||
885 !workHMO.simplex_lp_status_.has_matrix_col_wise) {
886 // Don't have the matrix either row-wise or col-wise, so
887 // reinitialise it
888 assert(simplex_info.backtracking_);
889 HighsLp& simplex_lp = workHMO.simplex_lp_;
890 analysis->simplexTimerStart(matrixSetupClock);
891 workHMO.matrix_.setup(simplex_lp.numCol_, simplex_lp.numRow_,
892 &simplex_lp.Astart_[0], &simplex_lp.Aindex_[0],
893 &simplex_lp.Avalue_[0],
894 &workHMO.simplex_basis_.nonbasicFlag_[0]);
895 simplex_lp_status.has_matrix_col_wise = true;
896 simplex_lp_status.has_matrix_row_wise = true;
897 analysis->simplexTimerStop(matrixSetupClock);
898 }
899 // Record whether the update objective value should be tested. If
900 // the objective value is known, then the updated objective value
901 // should be correct - once the correction due to recomputing the
902 // dual values has been applied.
903 //
904 // Note that computePrimalObjectiveValue sets
905 // has_primal_objective_value
906 const bool check_updated_objective_value =
907 simplex_lp_status.has_dual_objective_value;
908 double previous_dual_objective_value;
909 if (check_updated_objective_value) {
910 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
911 "Before computeDual");
912 previous_dual_objective_value = simplex_info.updated_dual_objective_value;
913 } else {
914 // Reset the knowledge of previous objective values
915 debugUpdatedObjectiveValue(workHMO, algorithm, -1, "");
916 }
917 // Recompute dual solution
918 analysis->simplexTimerStart(ComputeDualClock);
919 computeDual(workHMO);
920 analysis->simplexTimerStop(ComputeDualClock);
921
922 if (simplex_info.backtracking_) {
923 // If backtracking, may change phase, so drop out
924 solvePhase = SOLVE_PHASE_UNKNOWN;
925 return;
926 }
927 analysis->simplexTimerStart(CorrectDualClock);
928 correctDual(workHMO, &dualInfeasCount);
929 analysis->simplexTimerStop(CorrectDualClock);
930
931 // Recompute primal solution
932 analysis->simplexTimerStart(ComputePrimalClock);
933 computePrimal(workHMO);
934 analysis->simplexTimerStop(ComputePrimalClock);
935
936 // Collect primal infeasible as a list
937 analysis->simplexTimerStart(CollectPrIfsClock);
938 dualRHS.createArrayOfPrimalInfeasibilities();
939 dualRHS.createInfeasList(analysis->col_aq_density);
940 analysis->simplexTimerStop(CollectPrIfsClock);
941
942 // Dual objective section
943 //
944 analysis->simplexTimerStart(ComputeDuObjClock);
945 computeDualObjectiveValue(workHMO, solvePhase);
946 analysis->simplexTimerStop(ComputeDuObjClock);
947
948 if (check_updated_objective_value) {
949 // Apply the objective value correction due to computing duals
950 // from scratch.
951 const double dual_objective_value_correction =
952 simplex_info.dual_objective_value - previous_dual_objective_value;
953 simplex_info.updated_dual_objective_value +=
954 dual_objective_value_correction;
955 debugUpdatedObjectiveValue(workHMO, algorithm);
956 }
957 // Now that there's a new dual_objective_value, reset the updated
958 // value
959 simplex_info.updated_dual_objective_value = simplex_info.dual_objective_value;
960
961 if (!simplex_info.run_quiet) {
962 // Report the primal infeasiblities
963 computeSimplexPrimalInfeasible(workHMO);
964 if (solvePhase == SOLVE_PHASE_1) {
965 // In phase 1, report the simplex LP dual infeasiblities
966 computeSimplexLpDualInfeasible(workHMO);
967 } else {
968 // In phase 2, report the simplex dual infeasiblities
969 computeSimplexDualInfeasible(workHMO);
970 }
971 reportRebuild(rebuild_invert_hint);
972 }
973
974 build_syntheticTick = factor->build_syntheticTick;
975 total_syntheticTick = 0;
976
977 #ifdef HiGHSDEV
978 if (simplex_info.analyse_rebuild_time) {
979 int total_rebuilds = analysis->simplexTimerNumCall(IterateDualRebuildClock);
980 double total_rebuild_time =
981 analysis->simplexTimerRead(IterateDualRebuildClock);
982 printf(
983 "Dual Ph%-2d rebuild %4d (%1d) on iteration %9d: Total rebuild time = "
984 "%11.4g\n",
985 solvePhase, total_rebuilds, rebuild_invert_hint,
986 workHMO.iteration_counts_.simplex, total_rebuild_time);
987 }
988 #endif
989 // Data are fresh from rebuild
990 simplex_lp_status.has_fresh_rebuild = true;
991 }
992
cleanup()993 void HDual::cleanup() {
994 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
995 ML_DETAILED, "dual-cleanup-shift\n");
996 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
997 // Remove perturbation and don't permit further perturbation
998 initialiseCost(workHMO);
999 simplex_info.allow_cost_perturbation = false;
1000 // No solvePhase term in initialiseBound is surely an omission -
1001 // when cleanup called in phase 1
1002 initialiseBound(workHMO, solvePhase);
1003 // Possibly take a copy of the original duals before recomputing them
1004 vector<double> original_workDual;
1005 if (workHMO.options_.highs_debug_level > HIGHS_DEBUG_LEVEL_CHEAP)
1006 original_workDual = simplex_info.workDual_;
1007 // Compute the dual values
1008 analysis->simplexTimerStart(ComputeDualClock);
1009 computeDual(workHMO);
1010 analysis->simplexTimerStop(ComputeDualClock);
1011 // Possibly analyse the change in duals
1012 debugCleanup(workHMO, original_workDual);
1013 // Compute the dual infeasibilities
1014 analysis->simplexTimerStart(ComputeDuIfsClock);
1015 computeSimplexDualInfeasible(workHMO);
1016 analysis->simplexTimerStop(ComputeDuIfsClock);
1017 dualInfeasCount = workHMO.simplex_info_.num_dual_infeasibilities;
1018
1019 // Compute the dual objective value
1020 analysis->simplexTimerStart(ComputeDuObjClock);
1021 computeDualObjectiveValue(workHMO, solvePhase);
1022 analysis->simplexTimerStop(ComputeDuObjClock);
1023 // Now that there's a new dual_objective_value, reset the updated
1024 // value
1025 simplex_info.updated_dual_objective_value = simplex_info.dual_objective_value;
1026
1027 if (!simplex_info.run_quiet) {
1028 // Report the primal infeasiblities
1029 computeSimplexPrimalInfeasible(workHMO);
1030 // In phase 1, report the simplex LP dual infeasiblities
1031 // In phase 2, report the simplex dual infeasiblities (known)
1032 if (solvePhase == SOLVE_PHASE_1) computeSimplexLpDualInfeasible(workHMO);
1033 reportRebuild();
1034 }
1035 }
1036
iterate()1037 void HDual::iterate() {
1038 // This is the main teration loop for dual revised simplex. All the
1039 // methods have as their first line if (invertHint) return;, where
1040 // invertHint is, for example, set to 1 when CHUZR finds no
1041 // candidate. This causes a break from the inner loop of
1042 // solve_phase% and, hence, a call to rebuild()
1043
1044 // Reporting:
1045 // Row-wise matrix after update in updateMatrix(columnIn, columnOut);
1046 analysis->simplexTimerStart(IterateChuzrClock);
1047 chooseRow();
1048 analysis->simplexTimerStop(IterateChuzrClock);
1049
1050 analysis->simplexTimerStart(IterateChuzcClock);
1051 chooseColumn(&row_ep);
1052 analysis->simplexTimerStop(IterateChuzcClock);
1053
1054 analysis->simplexTimerStart(IterateFtranClock);
1055 updateFtranBFRT();
1056
1057 // updateFtran(); computes the pivotal column in the data structure "column"
1058 updateFtran();
1059
1060 // updateFtranDSE performs the DSE FTRAN on pi_p
1061 if (dual_edge_weight_mode == DualEdgeWeightMode::STEEPEST_EDGE)
1062 updateFtranDSE(&row_ep);
1063 analysis->simplexTimerStop(IterateFtranClock);
1064
1065 // updateVerify() Checks row-wise pivot against column-wise pivot for
1066 // numerical trouble
1067 analysis->simplexTimerStart(IterateVerifyClock);
1068 updateVerify();
1069 analysis->simplexTimerStop(IterateVerifyClock);
1070
1071 // updateDual() Updates the dual values
1072 analysis->simplexTimerStart(IterateDualClock);
1073 updateDual();
1074 analysis->simplexTimerStop(IterateDualClock);
1075
1076 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1077 "Before updatePrimal");
1078 // updatePrimal(&row_ep); Updates the primal values and the edge weights
1079 analysis->simplexTimerStart(IteratePrimalClock);
1080 updatePrimal(&row_ep);
1081 analysis->simplexTimerStop(IteratePrimalClock);
1082 // After primal update in dual simplex the primal objective value is not known
1083 workHMO.simplex_lp_status_.has_primal_objective_value = false;
1084 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1085 "After updatePrimal");
1086
1087 // Update the basis representation
1088 analysis->simplexTimerStart(IteratePivotsClock);
1089 updatePivots();
1090 analysis->simplexTimerStop(IteratePivotsClock);
1091
1092 if (new_devex_framework) {
1093 // Initialise new Devex framework
1094 analysis->simplexTimerStart(IterateDevexIzClock);
1095 initialiseDevexFramework();
1096 analysis->simplexTimerStop(IterateDevexIzClock);
1097 }
1098
1099 // Analyse the iteration: possibly report; possibly switch strategy
1100 iterationAnalysis();
1101 }
1102
iterateTasks()1103 void HDual::iterateTasks() {
1104 slice_PRICE = 1;
1105
1106 // Group 1
1107 chooseRow();
1108
1109 // Disable slice when too sparse
1110 if (1.0 * row_ep.count / solver_num_row < 0.01) slice_PRICE = 0;
1111
1112 analysis->simplexTimerStart(Group1Clock);
1113 //#pragma omp parallel
1114 //#pragma omp single
1115 {
1116 //#pragma omp task
1117 {
1118 col_DSE.copy(&row_ep);
1119 updateFtranDSE(&col_DSE);
1120 }
1121 //#pragma omp task
1122 {
1123 if (slice_PRICE)
1124 chooseColumnSlice(&row_ep);
1125 else
1126 chooseColumn(&row_ep);
1127 //#pragma omp task
1128 updateFtranBFRT();
1129 //#pragma omp task
1130 updateFtran();
1131 //#pragma omp taskwait
1132 }
1133 }
1134 analysis->simplexTimerStop(Group1Clock);
1135
1136 updateVerify();
1137 updateDual();
1138 updatePrimal(&col_DSE);
1139 updatePivots();
1140 }
1141
iterationAnalysisData()1142 void HDual::iterationAnalysisData() {
1143 HighsSolutionParams& scaled_solution_params = workHMO.scaled_solution_params_;
1144 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1145 analysis->simplex_strategy = simplex_info.simplex_strategy;
1146 analysis->edge_weight_mode = dual_edge_weight_mode;
1147 analysis->solve_phase = solvePhase;
1148 analysis->simplex_iteration_count = workHMO.iteration_counts_.simplex;
1149 analysis->devex_iteration_count = num_devex_iterations;
1150 analysis->pivotal_row_index = rowOut;
1151 analysis->leaving_variable = columnOut;
1152 analysis->entering_variable = columnIn;
1153 analysis->invert_hint = invertHint;
1154 analysis->reduced_rhs_value = 0;
1155 analysis->reduced_cost_value = 0;
1156 analysis->edge_weight = 0;
1157 analysis->primal_delta = deltaPrimal;
1158 analysis->primal_step = thetaPrimal;
1159 analysis->dual_step = thetaDual;
1160 analysis->pivot_value_from_column = alpha;
1161 analysis->pivot_value_from_row = alphaRow;
1162 analysis->factor_pivot_threshold = simplex_info.factor_pivot_threshold;
1163 analysis->numerical_trouble = numericalTrouble;
1164 analysis->objective_value = simplex_info.updated_dual_objective_value;
1165 // Since maximization is achieved by minimizing the LP with negated
1166 // costs, in phase 2 the dual objective value is negated, so flip
1167 // its sign according to the LP sense
1168 if (solvePhase == SOLVE_PHASE_2)
1169 analysis->objective_value *= (int)workHMO.lp_.sense_;
1170 analysis->num_primal_infeasibilities =
1171 simplex_info.num_primal_infeasibilities;
1172 analysis->sum_primal_infeasibilities =
1173 simplex_info.sum_primal_infeasibilities;
1174 if (solvePhase == SOLVE_PHASE_1) {
1175 analysis->num_dual_infeasibilities =
1176 scaled_solution_params.num_dual_infeasibilities;
1177 analysis->sum_dual_infeasibilities =
1178 scaled_solution_params.sum_dual_infeasibilities;
1179 } else {
1180 analysis->num_dual_infeasibilities = simplex_info.num_dual_infeasibilities;
1181 analysis->sum_dual_infeasibilities = simplex_info.sum_dual_infeasibilities;
1182 }
1183 #ifdef HiGHSDEV
1184 analysis->basis_condition = simplex_info.invert_condition;
1185 #endif
1186 if ((dual_edge_weight_mode == DualEdgeWeightMode::DEVEX) &&
1187 (num_devex_iterations == 0))
1188 analysis->num_devex_framework++;
1189 }
1190
iterationAnalysis()1191 void HDual::iterationAnalysis() {
1192 // Possibly report on the iteration
1193 iterationAnalysisData();
1194 analysis->iterationReport();
1195
1196 // Possibly switch from DSE to Devex
1197 if (dual_edge_weight_mode == DualEdgeWeightMode::STEEPEST_EDGE) {
1198 bool switch_to_devex = false;
1199 switch_to_devex = analysis->switchToDevex();
1200 if (switch_to_devex) {
1201 dual_edge_weight_mode = DualEdgeWeightMode::DEVEX;
1202 // Using dual Devex edge weights, so set up the first framework
1203 workHMO.simplex_info_.devex_index_.assign(solver_num_tot, 0);
1204 initialiseDevexFramework();
1205 }
1206 }
1207
1208 #ifdef HiGHSDEV
1209 analysis->iterationRecord();
1210 #endif
1211 }
1212
reportRebuild(const int rebuild_invert_hint)1213 void HDual::reportRebuild(const int rebuild_invert_hint) {
1214 analysis->simplexTimerStart(ReportRebuildClock);
1215 iterationAnalysisData();
1216 analysis->invert_hint = rebuild_invert_hint;
1217 analysis->invertReport();
1218 analysis->simplexTimerStop(ReportRebuildClock);
1219 }
1220
chooseRow()1221 void HDual::chooseRow() {
1222 // Choose the index of a row to leave the basis (CHUZR)
1223 //
1224 // If reinversion is needed then skip this method
1225 if (invertHint) return;
1226 // Choose candidates repeatedly until candidate is OK or optimality is
1227 // detected
1228 for (;;) {
1229 // Choose the index of a good row to leave the basis
1230 dualRHS.chooseNormal(&rowOut);
1231 if (rowOut == -1) {
1232 // No index found so may be dual optimal. By setting
1233 // invertHint>0 all subsequent methods in the iteration will
1234 // be skipped until reinversion and rebuild have taken place
1235 invertHint = INVERT_HINT_POSSIBLY_OPTIMAL;
1236 return;
1237 }
1238 // Compute pi_p = B^{-T}e_p in row_ep
1239 analysis->simplexTimerStart(BtranClock);
1240 // Set up RHS for BTRAN
1241 row_ep.clear();
1242 row_ep.count = 1;
1243 row_ep.index[0] = rowOut;
1244 row_ep.array[rowOut] = 1;
1245 row_ep.packFlag = true;
1246 #ifdef HiGHSDEV
1247 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1248 if (simplex_info.analyse_iterations)
1249 analysis->operationRecordBefore(ANALYSIS_OPERATION_TYPE_BTRAN_EP, row_ep,
1250 analysis->row_ep_density);
1251 #endif
1252 // Perform BTRAN
1253 factor->btran(row_ep, analysis->row_ep_density,
1254 analysis->pointer_serial_factor_clocks);
1255 #ifdef HiGHSDEV
1256 if (simplex_info.analyse_iterations)
1257 analysis->operationRecordAfter(ANALYSIS_OPERATION_TYPE_BTRAN_EP, row_ep);
1258 #endif
1259 analysis->simplexTimerStop(BtranClock);
1260 // Verify DSE weight
1261 if (dual_edge_weight_mode == DualEdgeWeightMode::STEEPEST_EDGE) {
1262 // For DSE, see how accurate the updated weight is
1263 // Save the updated weight
1264 double updated_edge_weight = dualRHS.workEdWt[rowOut];
1265 // Compute the weight from row_ep and over-write the updated weight
1266 computed_edge_weight = dualRHS.workEdWt[rowOut] = row_ep.norm2();
1267 // If the weight error is acceptable then break out of the
1268 // loop. All we worry about is accepting rows with weights
1269 // which are not too small, since this can make the row look
1270 // unreasonably attractive
1271 if (acceptDualSteepestEdgeWeight(updated_edge_weight)) break;
1272 // Weight error is unacceptable so look for another
1273 // candidate. Of course, it's possible that the same
1274 // candidate is chosen, but the weight will be correct (so
1275 // no infinite loop).
1276 } else {
1277 // If not using DSE then accept the row by breaking out of
1278 // the loop
1279 break;
1280 }
1281 }
1282 // Index of row to leave the basis has been found
1283 //
1284 // Assign basic info:
1285 //
1286 // Record the column (variable) associated with the leaving row
1287 columnOut = workHMO.simplex_basis_.basicIndex_[rowOut];
1288 // Record the change in primal variable associated with the move to the bound
1289 // being violated
1290 if (baseValue[rowOut] < baseLower[rowOut]) {
1291 // Below the lower bound so set deltaPrimal = value - LB < 0
1292 deltaPrimal = baseValue[rowOut] - baseLower[rowOut];
1293 } else {
1294 // Above the upper bound so set deltaPrimal = value - UB > 0
1295 deltaPrimal = baseValue[rowOut] - baseUpper[rowOut];
1296 }
1297 // Set sourceOut to be -1 if deltaPrimal<0, otherwise +1 (since deltaPrimal>0)
1298 sourceOut = deltaPrimal < 0 ? -1 : 1;
1299 // Update the record of average row_ep (pi_p) density. This ignores
1300 // any BTRANs done for skipped candidates
1301 const double local_row_ep_density = (double)row_ep.count / solver_num_row;
1302 analysis->updateOperationResultDensity(local_row_ep_density,
1303 analysis->row_ep_density);
1304 }
1305
acceptDualSteepestEdgeWeight(const double updated_edge_weight)1306 bool HDual::acceptDualSteepestEdgeWeight(const double updated_edge_weight) {
1307 // Accept the updated weight if it is at least a quarter of the
1308 // computed weight. Excessively large updated weights don't matter!
1309 const double accept_weight_threshold = 0.25;
1310 const bool accept_weight =
1311 updated_edge_weight >= accept_weight_threshold * computed_edge_weight;
1312 analysis->dualSteepestEdgeWeightError(computed_edge_weight,
1313 updated_edge_weight);
1314 return accept_weight;
1315 }
1316
newDevexFramework(const double updated_edge_weight)1317 bool HDual::newDevexFramework(const double updated_edge_weight) {
1318 // Analyse the Devex weight to determine whether a new framework
1319 // should be set up
1320 double devex_ratio = max(updated_edge_weight / computed_edge_weight,
1321 computed_edge_weight / updated_edge_weight);
1322 int i_te = solver_num_row / minRlvNumberDevexIterations;
1323 i_te = max(minAbsNumberDevexIterations, i_te);
1324 // Square maxAllowedDevexWeightRatio due to keeping squared
1325 // weights
1326 const double accept_ratio_threshold =
1327 maxAllowedDevexWeightRatio * maxAllowedDevexWeightRatio;
1328 const bool accept_ratio = devex_ratio <= accept_ratio_threshold;
1329 const bool accept_it = num_devex_iterations <= i_te;
1330 bool return_new_devex_framework;
1331 return_new_devex_framework = !accept_ratio || !accept_it;
1332 /*
1333 if (return_new_devex_framework) {
1334 printf("New Devex framework: (Iter %d) updated weight = %11.4g; computed
1335 weight = %11.4g; Devex ratio = %11.4g\n",
1336 workHMO.iteration_counts_.simplex,
1337 updated_edge_weight, computed_edge_weight, devex_ratio);
1338 return true;
1339 }
1340 */
1341 return return_new_devex_framework;
1342 }
1343
chooseColumn(HVector * row_ep)1344 void HDual::chooseColumn(HVector* row_ep) {
1345 // Compute pivot row (PRICE) and choose the index of a column to enter the
1346 // basis (CHUZC)
1347 //
1348 // If reinversion is needed then skip this method
1349 if (invertHint) return;
1350 //
1351 // PRICE
1352 //
1353 computeTableauRowFromPiP(workHMO, *row_ep, row_ap);
1354 //
1355 // CHUZC
1356 //
1357 // Section 0: Clear data and call createFreemove to set a value of
1358 // nonbasicMove for all free columns to prevent their dual values
1359 // from being changed.
1360 analysis->simplexTimerStart(Chuzc0Clock);
1361 dualRow.clear();
1362 dualRow.workDelta = deltaPrimal;
1363 dualRow.createFreemove(row_ep);
1364 analysis->simplexTimerStop(Chuzc0Clock);
1365 //
1366 // Section 1: Pack row_ap and row_ep, then determine the possible
1367 // variables - candidates for CHUZC
1368 analysis->simplexTimerStart(Chuzc1Clock);
1369 // Pack row_ap into the packIndex/Value of HDualRow
1370 dualRow.chooseMakepack(&row_ap, 0);
1371 // Pack row_ep into the packIndex/Value of HDualRow
1372 dualRow.chooseMakepack(row_ep, solver_num_col);
1373 // Determine the possible variables - candidates for CHUZC
1374 dualRow.choosePossible();
1375 analysis->simplexTimerStop(Chuzc1Clock);
1376 //
1377 // Take action if the step to an expanded bound is not positive, or
1378 // there are no candidates for CHUZC
1379 columnIn = -1;
1380 if (dualRow.workTheta <= 0 || dualRow.workCount == 0) {
1381 invertHint = INVERT_HINT_POSSIBLY_DUAL_UNBOUNDED;
1382 return;
1383 }
1384 //
1385 // Sections 2 and 3: Perform (bound-flipping) ratio test. This can
1386 // fail if the dual values are excessively large
1387 bool chooseColumnFail = dualRow.chooseFinal();
1388 if (chooseColumnFail) {
1389 invertHint = INVERT_HINT_CHOOSE_COLUMN_FAIL;
1390 return;
1391 }
1392 //
1393 // Section 4: Reset the nonbasicMove values for free columns
1394 analysis->simplexTimerStart(Chuzc4Clock);
1395 dualRow.deleteFreemove();
1396 analysis->simplexTimerStop(Chuzc4Clock);
1397 // Record values for basis change, checking for numerical problems and update
1398 // of dual variables
1399 columnIn = dualRow.workPivot; // Index of the column entering the basis
1400 alphaRow = dualRow.workAlpha; // Pivot value computed row-wise - used for
1401 // numerical checking
1402 thetaDual = dualRow.workTheta; // Dual step length
1403
1404 if (dual_edge_weight_mode == DualEdgeWeightMode::DEVEX &&
1405 !new_devex_framework) {
1406 // When using Devex, unless a new framework is to be used, get the
1407 // exact weight for the pivotal row and, based on its accuracy,
1408 // determine that a new framework is to be used. In serial
1409 // new_devex_framework should only ever be false at this point in
1410 // this method, but in PAMI, this method may be called multiple
1411 // times in minor iterations and the new framework is set up in
1412 // majorUpdate.
1413 analysis->simplexTimerStart(DevexWtClock);
1414 // Determine the exact Devex weight
1415 dualRow.computeDevexWeight();
1416 computed_edge_weight = dualRow.computed_edge_weight;
1417 computed_edge_weight = max(1.0, computed_edge_weight);
1418 analysis->simplexTimerStop(DevexWtClock);
1419 }
1420 return;
1421 }
1422
chooseColumnSlice(HVector * row_ep)1423 void HDual::chooseColumnSlice(HVector* row_ep) {
1424 // Choose the index of a column to enter the basis (CHUZC) by
1425 // exploiting slices of the pivotal row - for SIP and PAMI
1426 //
1427 // If reinversion is needed then skip this method
1428 if (invertHint) return;
1429
1430 analysis->simplexTimerStart(Chuzc0Clock);
1431 dualRow.clear();
1432 dualRow.workDelta = deltaPrimal;
1433 dualRow.createFreemove(row_ep);
1434 analysis->simplexTimerStop(Chuzc0Clock);
1435
1436 // const int solver_num_row = workHMO.simplex_lp_.numRow_;
1437 const double local_density = 1.0 * row_ep->count / solver_num_row;
1438 bool use_col_price;
1439 bool use_row_price_w_switch;
1440 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1441 choosePriceTechnique(simplex_info.price_strategy, local_density,
1442 use_col_price, use_row_price_w_switch);
1443
1444 #ifdef HiGHSDEV
1445 if (simplex_info.analyse_iterations) {
1446 const int row_ep_count = row_ep->count;
1447 if (use_col_price) {
1448 analysis->operationRecordBefore(ANALYSIS_OPERATION_TYPE_PRICE_AP,
1449 row_ep_count, 0.0);
1450 analysis->num_col_price++;
1451 } else if (use_row_price_w_switch) {
1452 analysis->operationRecordBefore(ANALYSIS_OPERATION_TYPE_PRICE_AP,
1453 row_ep_count, analysis->row_ep_density);
1454 analysis->num_row_price_with_switch++;
1455 } else {
1456 analysis->operationRecordBefore(ANALYSIS_OPERATION_TYPE_PRICE_AP,
1457 row_ep_count, analysis->row_ep_density);
1458 analysis->num_row_price++;
1459 }
1460 }
1461 #endif
1462 analysis->simplexTimerStart(PriceChuzc1Clock);
1463 // Row_ep: PACK + CC1
1464
1465 /*
1466 int row_ep_thread_id = 0;
1467 vector<int> row_ap_thread_id;
1468 row_ap_thread_id.resize(slice_num);
1469 */
1470
1471 //#pragma omp task
1472 {
1473 dualRow.chooseMakepack(row_ep, solver_num_col);
1474 dualRow.choosePossible();
1475 #ifdef OPENMP
1476 // int row_ep_thread_id = omp_get_thread_num();
1477 // printf("Hello world from Row_ep: PACK + CC1 thread %d\n",
1478 // row_ep_thread_id);
1479 #endif
1480 }
1481
1482 // Row_ap: PRICE + PACK + CC1
1483 for (int i = 0; i < slice_num; i++) {
1484 //#pragma omp task
1485 {
1486 #ifdef OPENMP
1487 // int row_ap_thread_id = omp_get_thread_num();
1488 // printf("Hello world from omp Row_ap: PRICE + PACK + CC1 [%1d]
1489 // thread %d\n", i, row_ap_thread_id);
1490 #endif
1491 slice_row_ap[i].clear();
1492
1493 // slice_matrix[i].priceByRowSparseResult(slice_row_ap[i], *row_ep);
1494
1495 if (use_col_price) {
1496 // Perform column-wise PRICE
1497 slice_matrix[i].priceByColumn(slice_row_ap[i], *row_ep);
1498 } else if (use_row_price_w_switch) {
1499 // Perform hyper-sparse row-wise PRICE, but switch if the density of
1500 // row_ap becomes extreme
1501 slice_matrix[i].priceByRowSparseResultWithSwitch(
1502 slice_row_ap[i], *row_ep, analysis->row_ap_density, 0,
1503 slice_matrix[i].hyperPRICE);
1504 } else {
1505 // Perform hyper-sparse row-wise PRICE
1506 slice_matrix[i].priceByRowSparseResult(slice_row_ap[i], *row_ep);
1507 }
1508
1509 slice_dualRow[i].clear();
1510 slice_dualRow[i].workDelta = deltaPrimal;
1511 slice_dualRow[i].chooseMakepack(&slice_row_ap[i], slice_start[i]);
1512 slice_dualRow[i].choosePossible();
1513 }
1514 }
1515 //#pragma omp taskwait
1516
1517 #ifdef HiGHSDEV
1518 // Determine the nonzero count of the whole row
1519 if (simplex_info.analyse_iterations) {
1520 int row_ap_count = 0;
1521 for (int i = 0; i < slice_num; i++) row_ap_count += slice_row_ap[i].count;
1522 analysis->operationRecordAfter(ANALYSIS_OPERATION_TYPE_PRICE_AP,
1523 row_ap_count);
1524 }
1525 #endif
1526
1527 // Join CC1 results here
1528 for (int i = 0; i < slice_num; i++) {
1529 dualRow.chooseJoinpack(&slice_dualRow[i]);
1530 }
1531
1532 analysis->simplexTimerStop(PriceChuzc1Clock);
1533
1534 // Infeasible we created before
1535 columnIn = -1;
1536 if (dualRow.workTheta <= 0 || dualRow.workCount == 0) {
1537 invertHint = INVERT_HINT_POSSIBLY_DUAL_UNBOUNDED;
1538 return;
1539 }
1540
1541 // Choose column 2, This only happens if didn't go out
1542 bool chooseColumnFail = dualRow.chooseFinal();
1543 if (chooseColumnFail) {
1544 invertHint = INVERT_HINT_CHOOSE_COLUMN_FAIL;
1545 return;
1546 }
1547
1548 analysis->simplexTimerStart(Chuzc4Clock);
1549 dualRow.deleteFreemove();
1550 analysis->simplexTimerStop(Chuzc4Clock);
1551
1552 columnIn = dualRow.workPivot;
1553 alphaRow = dualRow.workAlpha;
1554 thetaDual = dualRow.workTheta;
1555
1556 if (dual_edge_weight_mode == DualEdgeWeightMode::DEVEX &&
1557 !new_devex_framework) {
1558 // When using Devex, unless a new framework is to be used, get the
1559 // exact weight for the pivotal row and, based on its accuracy,
1560 // determine that a new framework is to be used. In serial
1561 // new_devex_framework should only ever be false at this point in
1562 // this method, but in PAMI, this method may be called multiple
1563 // times in minor iterations and the new framework is set up in
1564 // majorUpdate.
1565 analysis->simplexTimerStart(DevexWtClock);
1566 // Determine the partial sums of the exact Devex weight
1567 // First the partial sum for row_ep
1568 dualRow.computeDevexWeight();
1569 // Second the partial sums for the slices of row_ap
1570 for (int i = 0; i < slice_num; i++) slice_dualRow[i].computeDevexWeight(i);
1571 // Accumulate the partial sums
1572 // Initialse with the partial sum for row_ep
1573 computed_edge_weight = dualRow.computed_edge_weight;
1574 // Update with the partial sum for row_ep
1575 for (int i = 0; i < slice_num; i++)
1576 computed_edge_weight += slice_dualRow[i].computed_edge_weight;
1577 computed_edge_weight = max(1.0, computed_edge_weight);
1578 analysis->simplexTimerStop(DevexWtClock);
1579 }
1580 }
1581
updateFtran()1582 void HDual::updateFtran() {
1583 // Compute the pivotal column (FTRAN)
1584 //
1585 // If reinversion is needed then skip this method
1586 if (invertHint) return;
1587 analysis->simplexTimerStart(FtranClock);
1588 // Clear the picotal column and indicate that its values should be packed
1589 col_aq.clear();
1590 col_aq.packFlag = true;
1591 // Get the constraint matrix column by combining just one column
1592 // with unit multiplier
1593 matrix->collect_aj(col_aq, columnIn, 1);
1594 #ifdef HiGHSDEV
1595 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1596 if (simplex_info.analyse_iterations)
1597 analysis->operationRecordBefore(ANALYSIS_OPERATION_TYPE_FTRAN, col_aq,
1598 analysis->col_aq_density);
1599 #endif
1600 // Perform FTRAN
1601 factor->ftran(col_aq, analysis->col_aq_density,
1602 analysis->pointer_serial_factor_clocks);
1603 #ifdef HiGHSDEV
1604 if (simplex_info.analyse_iterations)
1605 analysis->operationRecordAfter(ANALYSIS_OPERATION_TYPE_FTRAN, col_aq);
1606 #endif
1607 const double local_col_aq_density = (double)col_aq.count / solver_num_row;
1608 analysis->updateOperationResultDensity(local_col_aq_density,
1609 analysis->col_aq_density);
1610 // Save the pivot value computed column-wise - used for numerical checking
1611 alpha = col_aq.array[rowOut];
1612 analysis->simplexTimerStop(FtranClock);
1613 }
1614
updateFtranBFRT()1615 void HDual::updateFtranBFRT() {
1616 // Compute the RHS changes corresponding to the BFRT (FTRAN-BFRT)
1617 //
1618 // If reinversion is needed then skip this method
1619 if (invertHint) return;
1620
1621 // Only time updateFtranBFRT if dualRow.workCount > 0;
1622 // If dualRow.workCount = 0 then dualRow.updateFlip(&col_BFRT)
1623 // merely clears col_BFRT so no FTRAN is performed
1624 bool time_updateFtranBFRT = dualRow.workCount > 0;
1625
1626 if (time_updateFtranBFRT) {
1627 analysis->simplexTimerStart(FtranBfrtClock);
1628 }
1629
1630 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1631 "Before update_flip");
1632 dualRow.updateFlip(&col_BFRT);
1633 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1634 "After update_flip");
1635
1636 if (col_BFRT.count) {
1637 #ifdef HiGHSDEV
1638 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1639 if (simplex_info.analyse_iterations)
1640 analysis->operationRecordBefore(ANALYSIS_OPERATION_TYPE_FTRAN_BFRT,
1641 col_BFRT, analysis->col_BFRT_density);
1642 #endif
1643 // Perform FTRAN BFRT
1644 factor->ftran(col_BFRT, analysis->col_BFRT_density,
1645 analysis->pointer_serial_factor_clocks);
1646 #ifdef HiGHSDEV
1647 if (simplex_info.analyse_iterations)
1648 analysis->operationRecordAfter(ANALYSIS_OPERATION_TYPE_FTRAN_BFRT,
1649 col_BFRT);
1650 #endif
1651 }
1652 if (time_updateFtranBFRT) {
1653 analysis->simplexTimerStop(FtranBfrtClock);
1654 }
1655 const double local_col_BFRT_density = (double)col_BFRT.count / solver_num_row;
1656 analysis->updateOperationResultDensity(local_col_BFRT_density,
1657 analysis->col_BFRT_density);
1658 }
1659
updateFtranDSE(HVector * DSE_Vector)1660 void HDual::updateFtranDSE(HVector* DSE_Vector) {
1661 // Compute the vector required to update DSE weights - being FTRAN
1662 // applied to the pivotal column (FTRAN-DSE)
1663 //
1664 // If reinversion is needed then skip this method
1665 if (invertHint) return;
1666 analysis->simplexTimerStart(FtranDseClock);
1667 #ifdef HiGHSDEV
1668 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1669 if (simplex_info.analyse_iterations)
1670 analysis->operationRecordBefore(ANALYSIS_OPERATION_TYPE_FTRAN_DSE,
1671 *DSE_Vector, analysis->row_DSE_density);
1672 #endif
1673 // Perform FTRAN DSE
1674 factor->ftran(*DSE_Vector, analysis->row_DSE_density,
1675 analysis->pointer_serial_factor_clocks);
1676 #ifdef HiGHSDEV
1677 if (simplex_info.analyse_iterations)
1678 analysis->operationRecordAfter(ANALYSIS_OPERATION_TYPE_FTRAN_DSE,
1679 *DSE_Vector);
1680 #endif
1681 analysis->simplexTimerStop(FtranDseClock);
1682 const double local_row_DSE_density =
1683 (double)DSE_Vector->count / solver_num_row;
1684 analysis->updateOperationResultDensity(local_row_DSE_density,
1685 analysis->row_DSE_density);
1686 }
1687
updateVerify()1688 void HDual::updateVerify() {
1689 // Compare the pivot value computed row-wise and column-wise and
1690 // determine whether reinversion is advisable
1691 //
1692 // If reinversion is needed then skip this method
1693 if (invertHint) return;
1694
1695 // Use the two pivot values to identify numerical trouble
1696 if (reinvertOnNumericalTrouble("HDual::updateVerify", workHMO,
1697 numericalTrouble, alpha, alphaRow,
1698 numerical_trouble_tolerance)) {
1699 invertHint = INVERT_HINT_POSSIBLY_SINGULAR_BASIS;
1700 }
1701 }
1702
updateDual()1703 void HDual::updateDual() {
1704 // Update the dual values
1705 //
1706 // If reinversion is needed then skip this method
1707 if (invertHint) return;
1708
1709 // Update - dual (shift and back)
1710 if (thetaDual == 0) {
1711 // Little to do if thetaDual is zero
1712 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1713 "Before shift_cost");
1714 shift_cost(workHMO, columnIn, -workDual[columnIn]);
1715 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1716 "After shift_cost");
1717 } else {
1718 // Update the whole vector of dual values
1719 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1720 "Before calling dualRow.updateDual");
1721 dualRow.updateDual(thetaDual);
1722 if (workHMO.simplex_info_.simplex_strategy != SIMPLEX_STRATEGY_DUAL_PLAIN &&
1723 slice_PRICE) {
1724 // Update the slice-by-slice copy of dual variables
1725 for (int i = 0; i < slice_num; i++)
1726 slice_dualRow[i].updateDual(thetaDual);
1727 }
1728 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1729 "After calling dualRow.updateDual");
1730 }
1731 // Identify the changes in the dual objective
1732 double dual_objective_value_change;
1733 const double columnIn_delta_dual = workDual[columnIn];
1734 const double columnIn_value = workValue[columnIn];
1735 const int columnIn_nonbasicFlag =
1736 workHMO.simplex_basis_.nonbasicFlag_[columnIn];
1737 dual_objective_value_change =
1738 columnIn_nonbasicFlag * (-columnIn_value * columnIn_delta_dual);
1739 dual_objective_value_change *= workHMO.scale_.cost_;
1740 workHMO.simplex_info_.updated_dual_objective_value +=
1741 dual_objective_value_change;
1742 // Surely columnOut_nonbasicFlag is always 0 since it's basic - so there's no
1743 // dual objective change
1744 const int columnOut_nonbasicFlag =
1745 workHMO.simplex_basis_.nonbasicFlag_[columnOut];
1746 assert(columnOut_nonbasicFlag == 0);
1747 if (columnOut_nonbasicFlag) {
1748 const double columnOut_delta_dual = workDual[columnOut] - thetaDual;
1749 const double columnOut_value = workValue[columnOut];
1750 dual_objective_value_change =
1751 columnOut_nonbasicFlag * (-columnOut_value * columnOut_delta_dual);
1752 dual_objective_value_change *= workHMO.scale_.cost_;
1753 workHMO.simplex_info_.updated_dual_objective_value +=
1754 dual_objective_value_change;
1755 }
1756 workDual[columnIn] = 0;
1757 workDual[columnOut] = -thetaDual;
1758
1759 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1760 "Before shift_back");
1761 shift_back(workHMO, columnOut);
1762 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1763 "After shift_back");
1764 }
1765
updatePrimal(HVector * DSE_Vector)1766 void HDual::updatePrimal(HVector* DSE_Vector) {
1767 // Update the primal values and any edge weights
1768 //
1769 // If reinversion is needed then skip this method
1770 if (invertHint) return;
1771 if (dual_edge_weight_mode == DualEdgeWeightMode::DEVEX) {
1772 const double updated_edge_weight = dualRHS.workEdWt[rowOut];
1773 dualRHS.workEdWt[rowOut] = computed_edge_weight;
1774 new_devex_framework = newDevexFramework(updated_edge_weight);
1775 }
1776 // DSE_Vector is either col_DSE = B^{-1}B^{-T}e_p (if using dual
1777 // steepest edge weights) or row_ep = B^{-T}e_p.
1778 //
1779 // Update - primal and weight
1780 dualRHS.updatePrimal(&col_BFRT, 1);
1781 dualRHS.updateInfeasList(&col_BFRT);
1782 double x_out = baseValue[rowOut];
1783 double l_out = baseLower[rowOut];
1784 double u_out = baseUpper[rowOut];
1785 thetaPrimal = (x_out - (deltaPrimal < 0 ? l_out : u_out)) / alpha;
1786 dualRHS.updatePrimal(&col_aq, thetaPrimal);
1787 if (dual_edge_weight_mode == DualEdgeWeightMode::STEEPEST_EDGE) {
1788 const double new_pivotal_edge_weight =
1789 dualRHS.workEdWt[rowOut] / (alpha * alpha);
1790 const double Kai = -2 / alpha;
1791 dualRHS.updateWeightDualSteepestEdge(&col_aq, new_pivotal_edge_weight, Kai,
1792 &DSE_Vector->array[0]);
1793 dualRHS.workEdWt[rowOut] = new_pivotal_edge_weight;
1794 } else if (dual_edge_weight_mode == DualEdgeWeightMode::DEVEX) {
1795 // Pivotal row is for the current basis: weights are required for
1796 // the next basis so have to divide the current (exact) weight by
1797 // the pivotal value
1798 double new_pivotal_edge_weight = dualRHS.workEdWt[rowOut] / (alpha * alpha);
1799 new_pivotal_edge_weight = max(1.0, new_pivotal_edge_weight);
1800 // nw_wt is max(workEdWt[iRow], NewExactWeight*columnArray[iRow]^2);
1801 //
1802 // But NewExactWeight is new_pivotal_edge_weight = max(1.0,
1803 // dualRHS.workEdWt[rowOut] / (alpha * alpha))
1804 //
1805 // so nw_wt = max(workEdWt[iRow],
1806 // new_pivotal_edge_weight*columnArray[iRow]^2);
1807 //
1808 // Update rest of weights
1809 dualRHS.updateWeightDevex(&col_aq, new_pivotal_edge_weight);
1810 dualRHS.workEdWt[rowOut] = new_pivotal_edge_weight;
1811 num_devex_iterations++;
1812 }
1813 dualRHS.updateInfeasList(&col_aq);
1814
1815 /*
1816 // Move these to where the FTRAN operations are actually performed
1817 if (dual_edge_weight_mode == DualEdgeWeightMode::STEEPEST_EDGE) {
1818 const double local_row_DSE_density = (double)DSE_Vector->count /
1819 solver_num_row; analysis->updateOperationResultDensity(local_row_DSE_density,
1820 analysis->row_DSE_density);
1821 }
1822 const double local_col_aq_density = (double)col_aq.count / solver_num_row;
1823 analysis->updateOperationResultDensity(local_col_aq_density,
1824 analysis->col_aq_density);
1825 */
1826 // Whether or not dual steepest edge weights are being used, have to
1827 // add in DSE_Vector->syntheticTick since this contains the
1828 // contribution from forming row_ep = B^{-T}e_p.
1829 total_syntheticTick += col_aq.syntheticTick;
1830 total_syntheticTick += DSE_Vector->syntheticTick;
1831 }
1832
updatePivots()1833 void HDual::updatePivots() {
1834 // UPDATE
1835 //
1836 // If reinversion is needed then skip this method
1837 if (invertHint) return;
1838 //
1839 // Update the sets of indices of basic and nonbasic variables
1840 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1841 "Before update_pivots");
1842 update_pivots(workHMO, columnIn, rowOut, sourceOut);
1843 debugUpdatedObjectiveValue(workHMO, algorithm, solvePhase,
1844 "After update_pivots");
1845 //
1846 // Update the iteration count and store the basis change if HiGHSDEV
1847 // is defined
1848 // Move this to Simplex class once it's created
1849 // simplex_method.record_pivots(columnIn, columnOut, alpha);
1850 workHMO.iteration_counts_.simplex++;
1851 //
1852 // Update the invertible representation of the basis matrix
1853 update_factor(workHMO, &col_aq, &row_ep, &rowOut, &invertHint);
1854 //
1855 // Update the row-wise representation of the nonbasic columns
1856 update_matrix(workHMO, columnIn, columnOut);
1857 //
1858 // Delete Freelist entry for columnIn
1859 dualRow.deleteFreelist(columnIn);
1860 //
1861 // Update the primal value for the row where the basis change has
1862 // occurred, and set the corresponding primal infeasibility value in
1863 // dualRHS.work_infeasibility
1864 dualRHS.updatePivots(
1865 rowOut, workHMO.simplex_info_.workValue_[columnIn] + thetaPrimal);
1866 // Determine whether to reinvert based on the synthetic clock
1867 bool reinvert_syntheticClock = total_syntheticTick >= build_syntheticTick;
1868 const bool performed_min_updates =
1869 workHMO.simplex_info_.update_count >=
1870 synthetic_tick_reinversion_min_update_count;
1871 #ifdef HiGHSDEV
1872 if (rp_reinvert_syntheticClock)
1873 printf(
1874 "Synth Reinversion: total_syntheticTick = %11.4g >=? %11.4g = "
1875 "build_syntheticTick: (%1d, %4d)\n",
1876 total_syntheticTick, build_syntheticTick, reinvert_syntheticClock,
1877 workHMO.simplex_info_.update_count);
1878 #endif
1879 if (reinvert_syntheticClock && performed_min_updates)
1880 invertHint = INVERT_HINT_SYNTHETIC_CLOCK_SAYS_INVERT;
1881 }
1882
initialiseDevexFramework(const bool parallel)1883 void HDual::initialiseDevexFramework(const bool parallel) {
1884 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1885 // Initialise the Devex framework: reference set is all basic
1886 // variables
1887 analysis->simplexTimerStart(DevexIzClock);
1888 const vector<int>& nonbasicFlag = workHMO.simplex_basis_.nonbasicFlag_;
1889 // Initialise the devex framework. The devex reference set is
1890 // initialise to be the current set of basic variables - and never
1891 // changes until a new framework is set up. In a simplex iteration,
1892 // to compute the exact Devex weight for the pivotal row requires
1893 // summing the squares of the its entries over the indices in the
1894 // reference set. This is achieved by summing over all indices, but
1895 // multiplying the entry by the value in devex_index before
1896 // equaring. Thus devex_index contains 1 for indices in the
1897 // reference set, and 0 otherwise. This is achieved by setting the
1898 // values of devex_index to be 1-nonbasicFlag^2, ASSUMING
1899 // |nonbasicFlag|=1 iff the corresponding variable is nonbasic
1900 for (int vr_n = 0; vr_n < solver_num_tot; vr_n++)
1901 simplex_info.devex_index_[vr_n] =
1902 1 - nonbasicFlag[vr_n] * nonbasicFlag[vr_n];
1903 // Set all initial weights to 1, zero the count of iterations with
1904 // this Devex framework, increment the number of Devex frameworks
1905 // and indicate that there's no need for a new Devex framework
1906 dualRHS.workEdWt.assign(solver_num_row, 1.0);
1907 num_devex_iterations = 0;
1908 new_devex_framework = false;
1909 minor_new_devex_framework = false;
1910 analysis->simplexTimerStop(DevexIzClock);
1911 }
1912
interpretDualEdgeWeightStrategy(const int dual_edge_weight_strategy)1913 void HDual::interpretDualEdgeWeightStrategy(
1914 const int dual_edge_weight_strategy) {
1915 if (dual_edge_weight_strategy == SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_CHOOSE) {
1916 dual_edge_weight_mode = DualEdgeWeightMode::STEEPEST_EDGE;
1917 initialise_dual_steepest_edge_weights = true;
1918 allow_dual_steepest_edge_to_devex_switch = true;
1919 } else if (dual_edge_weight_strategy ==
1920 SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_DANTZIG) {
1921 dual_edge_weight_mode = DualEdgeWeightMode::DANTZIG;
1922 } else if (dual_edge_weight_strategy ==
1923 SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_DEVEX) {
1924 dual_edge_weight_mode = DualEdgeWeightMode::DEVEX;
1925 } else if (dual_edge_weight_strategy ==
1926 SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE) {
1927 dual_edge_weight_mode = DualEdgeWeightMode::STEEPEST_EDGE;
1928 initialise_dual_steepest_edge_weights = true;
1929 allow_dual_steepest_edge_to_devex_switch = false;
1930 } else if (dual_edge_weight_strategy ==
1931 SIMPLEX_DUAL_EDGE_WEIGHT_STRATEGY_STEEPEST_EDGE_UNIT_INITIAL) {
1932 dual_edge_weight_mode = DualEdgeWeightMode::STEEPEST_EDGE;
1933 initialise_dual_steepest_edge_weights = false;
1934 allow_dual_steepest_edge_to_devex_switch = false;
1935 } else {
1936 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
1937 ML_MINIMAL,
1938 "HDual::interpretDualEdgeWeightStrategy: "
1939 "unrecognised dual_edge_weight_strategy = %d - using "
1940 "dual steepest edge with possible switch to Devex\n",
1941 dual_edge_weight_strategy);
1942 dual_edge_weight_mode = DualEdgeWeightMode::STEEPEST_EDGE;
1943 initialise_dual_steepest_edge_weights = true;
1944 allow_dual_steepest_edge_to_devex_switch = true;
1945 }
1946 }
1947
returnFromSolve(const HighsStatus return_status)1948 HighsStatus HDual::returnFromSolve(const HighsStatus return_status) {
1949 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1950 simplex_info.valid_backtracking_basis_ = false;
1951 return return_status;
1952 }
1953
saveDualRay()1954 void HDual::saveDualRay() {
1955 workHMO.simplex_lp_status_.has_dual_ray = true;
1956 workHMO.simplex_info_.dual_ray_row_ = rowOut;
1957 workHMO.simplex_info_.dual_ray_sign_ = sourceOut;
1958 }
1959
getNonsingularInverse()1960 bool HDual::getNonsingularInverse() {
1961 const vector<int>& basicIndex = workHMO.simplex_basis_.basicIndex_;
1962 // Take a copy of basicIndex from before INVERT to be used as the
1963 // saved ordering of basic variables - so reinvert will run
1964 // identically.
1965 const vector<int> basicIndex_before_compute_factor = basicIndex;
1966 // Save the number of updates performed in case it has to be used to determine
1967 // a limit
1968 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
1969 const int simplex_update_count = simplex_info.update_count;
1970 // Scatter the edge weights so that, after INVERT, they can be
1971 // gathered according to the new permutation of basicIndex
1972 analysis->simplexTimerStart(PermWtClock);
1973 for (int i = 0; i < solver_num_row; i++)
1974 dualRHS.workEdWtFull[basicIndex[i]] = dualRHS.workEdWt[i];
1975 analysis->simplexTimerStop(PermWtClock);
1976
1977 // Call computeFactor to perform INVERT
1978 analysis->simplexTimerStart(InvertClock);
1979 int rank_deficiency = computeFactor(workHMO);
1980 analysis->simplexTimerStop(InvertClock);
1981 const bool artificial_rank_deficiency = false; // true;
1982 if (artificial_rank_deficiency) {
1983 if (!simplex_info.phase1_backtracking_test_done &&
1984 solvePhase == SOLVE_PHASE_1) {
1985 // Claim rank deficiency to test backtracking
1986 printf("Phase1 (Iter %d) Claiming rank deficiency to test backtracking\n",
1987 workHMO.iteration_counts_.simplex);
1988 rank_deficiency = 1;
1989 simplex_info.phase1_backtracking_test_done = true;
1990 } else if (!simplex_info.phase2_backtracking_test_done &&
1991 solvePhase == SOLVE_PHASE_2) {
1992 // Claim rank deficiency to test backtracking
1993 printf("Phase2 (Iter %d) Claiming rank deficiency to test backtracking\n",
1994 workHMO.iteration_counts_.simplex);
1995 rank_deficiency = 1;
1996 simplex_info.phase2_backtracking_test_done = true;
1997 }
1998 }
1999 if (rank_deficiency) {
2000 // Rank deficient basis, so backtrack to last full rank basis
2001 //
2002 // Get the last nonsingular basis - so long as there is one
2003 if (!getBacktrackingBasis(dualRHS.workEdWtFull)) return false;
2004 // Record that backtracking is taking place
2005 simplex_info.backtracking_ = true;
2006 updateSimplexLpStatus(workHMO.simplex_lp_status_, LpAction::BACKTRACKING);
2007 analysis->simplexTimerStart(InvertClock);
2008 int backtrack_rank_deficiency = computeFactor(workHMO);
2009 analysis->simplexTimerStop(InvertClock);
2010 // This basis has previously been inverted successfully, so it shouldn't be
2011 // singular
2012 if (backtrack_rank_deficiency) return false;
2013 // simplex update limit will be half of the number of updates
2014 // performed, so make sure that at least one update was performed
2015 if (simplex_update_count <= 1) return false;
2016 int use_simplex_update_limit = simplex_info.update_limit;
2017 int new_simplex_update_limit = simplex_update_count / 2;
2018 simplex_info.update_limit = new_simplex_update_limit;
2019 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::WARNING,
2020 "Rank deficiency of %d after %d simplex updates, so "
2021 "backtracking: max updates reduced from %d to %d",
2022 rank_deficiency, simplex_update_count,
2023 use_simplex_update_limit, new_simplex_update_limit);
2024 } else {
2025 // Current basis is full rank so save it
2026 putBacktrackingBasis(basicIndex_before_compute_factor,
2027 dualRHS.workEdWtFull);
2028 // Indicate that backtracking is not taking place
2029 simplex_info.backtracking_ = false;
2030 // Reset the update limit in case this is the first successful
2031 // inversion after backtracking
2032 simplex_info.update_limit = workHMO.options_.simplex_update_limit;
2033 }
2034 // Gather the edge weights according to the permutation of
2035 // basicIndex after INVERT
2036 analysis->simplexTimerStart(PermWtClock);
2037 for (int i = 0; i < solver_num_row; i++)
2038 dualRHS.workEdWt[i] = dualRHS.workEdWtFull[basicIndex[i]];
2039 analysis->simplexTimerStop(PermWtClock);
2040 return true;
2041 }
2042
getBacktrackingBasis(vector<double> & scattered_edge_weights)2043 bool HDual::getBacktrackingBasis(vector<double>& scattered_edge_weights) {
2044 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
2045 if (!simplex_info.valid_backtracking_basis_) return false;
2046
2047 workHMO.simplex_basis_ = simplex_info.backtracking_basis_;
2048 simplex_info.costs_perturbed =
2049 simplex_info.backtracking_basis_costs_perturbed_;
2050 simplex_info.workShift_ = simplex_info.backtracking_basis_workShift_;
2051 scattered_edge_weights = simplex_info.backtracking_basis_edge_weights_;
2052 return true;
2053 }
2054
putBacktrackingBasis()2055 void HDual::putBacktrackingBasis() {
2056 const vector<int>& basicIndex = workHMO.simplex_basis_.basicIndex_;
2057 analysis->simplexTimerStart(PermWtClock);
2058 for (int i = 0; i < solver_num_row; i++)
2059 dualRHS.workEdWtFull[basicIndex[i]] = dualRHS.workEdWt[i];
2060 analysis->simplexTimerStop(PermWtClock);
2061 putBacktrackingBasis(basicIndex, dualRHS.workEdWtFull);
2062 }
2063
putBacktrackingBasis(const vector<int> & basicIndex_before_compute_factor,const vector<double> & scattered_edge_weights)2064 void HDual::putBacktrackingBasis(
2065 const vector<int>& basicIndex_before_compute_factor,
2066 const vector<double>& scattered_edge_weights) {
2067 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
2068 simplex_info.valid_backtracking_basis_ = true;
2069 simplex_info.backtracking_basis_ = workHMO.simplex_basis_;
2070 simplex_info.backtracking_basis_.basicIndex_ =
2071 basicIndex_before_compute_factor;
2072 simplex_info.backtracking_basis_costs_perturbed_ =
2073 simplex_info.costs_perturbed;
2074 simplex_info.backtracking_basis_workShift_ = simplex_info.workShift_;
2075 simplex_info.backtracking_basis_edge_weights_ = scattered_edge_weights;
2076 }
2077
assessPhase1Optimality()2078 void HDual::assessPhase1Optimality() {
2079 // Should only be called when optimal in phase 1 (rowOut == -1) with negative
2080 // dual activity
2081 assert(solvePhase == SOLVE_PHASE_1);
2082 assert(rowOut == -1);
2083 // assert(workHMO.simplex_info_.dual_objective_value < 0);
2084 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
2085 HighsModelStatus& scaled_model_status = workHMO.scaled_model_status_;
2086 // We still have dual infeasibilities, so clean up any perturbations
2087 // before concluding dual infeasibility
2088 //
2089 // What if the dual objective is positive but tiny?
2090 if (fabs(simplex_info.dual_objective_value) <= primal_feasibility_tolerance)
2091 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::INFO,
2092 "Optimal in phase 1 but not jumping to phase 2 since "
2093 "dual objective is %10.4g: Costs perturbed = %d",
2094 simplex_info.dual_objective_value,
2095 workHMO.simplex_info_.costs_perturbed);
2096 if (workHMO.simplex_info_.costs_perturbed) {
2097 // Clean up perturbation
2098 cleanup();
2099 // If there are now dual infeasibilities with respect to phase 1
2100 // bounds, have to got back to rebuild()
2101 if (dualInfeasCount == 0) {
2102 // No dual infeasibilities with respect to phase 1 bounds.
2103 if (simplex_info.dual_objective_value == 0) {
2104 // No dual infeasibilities (with respect to phase 2 bounds) so
2105 // go to phase 2
2106 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::INFO,
2107 "LP is dual feasible after removing cost perturbations "
2108 "so go to phase 2");
2109 } else {
2110 // LP is dual infeasible if the dual objective is sufficiently
2111 // positive, so no conclusions on the primal LP can be deduced
2112 // - could be primal unbounded or primal infeasible.
2113 //
2114 // Shift any dual infeasibilities and go to dual phase 2. If a
2115 // primal feasible point is found then the shifts are removed
2116 // and primal phase 2 will identify whether the LP is primal
2117 // unbounded. If dual unboundedness is found, then no
2118 // conclusion can be drawn. Have to use primal phase 1 (and
2119 // possibly phase 2) to determine whether the LP is primal
2120 // infeasible or unbounded.
2121 //
2122 // What's important is that the solver doesn't go back to dual
2123 // phase 1, otherwise it can fail to terminate
2124 //
2125 // Indicate the conclusion of dual infeasiblility by setting
2126 // the scaled model status
2127 reportOnPossibleLpDualInfeasibility();
2128 scaled_model_status = HighsModelStatus::DUAL_INFEASIBLE;
2129 }
2130 solvePhase = SOLVE_PHASE_2;
2131 }
2132 } else {
2133 // Phase 1 problem is optimal with original costs and negative
2134 // dual objective. In this case, hsol deduces dual infeasibility
2135 // and returns UNBOUNDED as a status, but this is wrong if the LP
2136 // is primal infeasible. As discussed above, this can only be
2137 // determined by going to dual phase 2, and then primal phase 1,
2138 // if necessary.
2139 //
2140 reportOnPossibleLpDualInfeasibility();
2141 scaled_model_status = HighsModelStatus::DUAL_INFEASIBLE;
2142 solvePhase = SOLVE_PHASE_2;
2143 }
2144 if (dualInfeasCount > 0) {
2145 // Must still be solvePhase = SOLVE_PHASE_1 since dual infeasibilities with
2146 // respect to phase 1 bounds mean that primal values must
2147 // change, so primal feasibility is unknown
2148 assert(solvePhase == SOLVE_PHASE_1);
2149 } else {
2150 // Optimal in dual phase 1, so must go to phase 2
2151 assert(solvePhase == SOLVE_PHASE_2);
2152 // Reset the duals, if necessary shifting costs of free variable
2153 // so that their duals are zero
2154 exitPhase1ResetDuals();
2155 }
2156 }
2157
exitPhase1ResetDuals()2158 void HDual::exitPhase1ResetDuals() {
2159 const HighsLp& simplex_lp = workHMO.simplex_lp_;
2160 const SimplexBasis& simplex_basis = workHMO.simplex_basis_;
2161 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
2162
2163 const bool reperturb_costs = true;
2164 if (reperturb_costs) {
2165 if (simplex_info.costs_perturbed) {
2166 HighsPrintMessage(
2167 workHMO.options_.output, workHMO.options_.message_level, ML_MINIMAL,
2168 "Costs are already perturbed in exitPhase1ResetDuals\n");
2169 } else {
2170 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
2171 ML_DETAILED,
2172 "Re-perturbing costs when optimal in phase 1\n");
2173 initialiseCost(workHMO, 1);
2174 analysis->simplexTimerStart(ComputeDualClock);
2175 computeDual(workHMO);
2176 analysis->simplexTimerStop(ComputeDualClock);
2177 }
2178 }
2179
2180 const int numTot = simplex_lp.numCol_ + simplex_lp.numRow_;
2181 int num_shift = 0;
2182 double sum_shift = 0;
2183 for (int iVar = 0; iVar < numTot; iVar++) {
2184 if (simplex_basis.nonbasicFlag_[iVar]) {
2185 double lp_lower;
2186 double lp_upper;
2187 if (iVar < simplex_lp.numCol_) {
2188 lp_lower = simplex_lp.colLower_[iVar];
2189 lp_upper = simplex_lp.colUpper_[iVar];
2190 } else {
2191 int iRow = iVar - simplex_lp.numCol_;
2192 lp_lower = simplex_lp.rowLower_[iRow];
2193 lp_upper = simplex_lp.rowUpper_[iRow];
2194 }
2195 if (lp_lower <= -HIGHS_CONST_INF && lp_upper >= HIGHS_CONST_INF) {
2196 const double shift = -simplex_info.workDual_[iVar];
2197 simplex_info.workDual_[iVar] = 0;
2198 simplex_info.workCost_[iVar] = simplex_info.workCost_[iVar] + shift;
2199 num_shift++;
2200 sum_shift += fabs(shift);
2201 HighsPrintMessage(
2202 workHMO.options_.output, workHMO.options_.message_level, ML_VERBOSE,
2203 "Variable %d is free: shift cost to zero dual of %g\n", iVar,
2204 shift);
2205 }
2206 }
2207 }
2208 if (num_shift)
2209 HighsPrintMessage(workHMO.options_.output, workHMO.options_.message_level,
2210 ML_DETAILED,
2211 "Performed %d cost shift(s) for free variables to zero "
2212 "dual values: total = %g\n",
2213 num_shift, sum_shift);
2214 }
2215
reportOnPossibleLpDualInfeasibility()2216 void HDual::reportOnPossibleLpDualInfeasibility() {
2217 HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
2218 assert(solvePhase == SOLVE_PHASE_1);
2219 assert(rowOut == -1);
2220 // assert(simplex_info.dual_objective_value < 0);
2221 assert(!simplex_info.costs_perturbed);
2222 const int num_lp_dual_infeasibilities =
2223 workHMO.scaled_solution_params_.num_dual_infeasibilities;
2224 const double max_lp_dual_infeasibility =
2225 workHMO.scaled_solution_params_.max_dual_infeasibility;
2226 const double sum_lp_dual_infeasibilities =
2227 workHMO.scaled_solution_params_.sum_dual_infeasibilities;
2228 std::string lp_dual_status;
2229 if (num_lp_dual_infeasibilities) {
2230 lp_dual_status = "infeasible";
2231 } else {
2232 lp_dual_status = "feasible";
2233 }
2234 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::INFO,
2235 "LP is dual %s with dual phase 1 objective %10.4g and num / "
2236 "max / sum dual infeasibilities = %d / %9.4g / %9.4g",
2237 lp_dual_status.c_str(), simplex_info.dual_objective_value,
2238 num_lp_dual_infeasibilities, max_lp_dual_infeasibility,
2239 sum_lp_dual_infeasibilities);
2240 }
2241
dualInfoOk(const HighsLp & lp)2242 bool HDual::dualInfoOk(const HighsLp& lp) {
2243 int lp_numCol = lp.numCol_;
2244 int lp_numRow = lp.numRow_;
2245 bool dimensions_ok;
2246 dimensions_ok = lp_numCol == solver_num_col && lp_numRow == solver_num_row;
2247 assert(dimensions_ok);
2248 if (!dimensions_ok) {
2249 printf("LP-Solver dimension incompatibility (%d, %d) != (%d, %d)\n",
2250 lp_numCol, solver_num_col, lp_numRow, solver_num_row);
2251 return false;
2252 }
2253 dimensions_ok = lp_numCol == factor->numCol && lp_numRow == factor->numRow;
2254 assert(dimensions_ok);
2255 if (!dimensions_ok) {
2256 printf("LP-Factor dimension incompatibility (%d, %d) != (%d, %d)\n",
2257 lp_numCol, factor->numCol, lp_numRow, factor->numRow);
2258 return false;
2259 }
2260 return true;
2261 }
2262
bailoutReturn()2263 bool HDual::bailoutReturn() {
2264 if (solve_bailout) {
2265 // If bailout has already been decided: check that it's for one of
2266 // these reasons
2267 assert(workHMO.scaled_model_status_ ==
2268 HighsModelStatus::REACHED_TIME_LIMIT ||
2269 workHMO.scaled_model_status_ ==
2270 HighsModelStatus::REACHED_ITERATION_LIMIT ||
2271 workHMO.scaled_model_status_ ==
2272 HighsModelStatus::REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND);
2273 }
2274 return solve_bailout;
2275 }
2276
bailoutOnTimeIterations()2277 bool HDual::bailoutOnTimeIterations() {
2278 HighsModelStatus& scaled_model_status = workHMO.scaled_model_status_;
2279 if (solve_bailout) {
2280 // Bailout has already been decided: check that it's for one of these
2281 // reasons
2282 assert(scaled_model_status == HighsModelStatus::REACHED_TIME_LIMIT ||
2283 scaled_model_status == HighsModelStatus::REACHED_ITERATION_LIMIT ||
2284 scaled_model_status ==
2285 HighsModelStatus::REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND);
2286 } else if (workHMO.timer_.readRunHighsClock() > workHMO.options_.time_limit) {
2287 solve_bailout = true;
2288 scaled_model_status = HighsModelStatus::REACHED_TIME_LIMIT;
2289 } else if (workHMO.iteration_counts_.simplex >=
2290 workHMO.options_.simplex_iteration_limit) {
2291 solve_bailout = true;
2292 scaled_model_status = HighsModelStatus::REACHED_ITERATION_LIMIT;
2293 }
2294 return solve_bailout;
2295 }
2296
bailoutOnDualObjective()2297 bool HDual::bailoutOnDualObjective() {
2298 if (solve_bailout) {
2299 // Bailout has already been decided: check that it's for one of these
2300 // reasons
2301 assert(workHMO.scaled_model_status_ ==
2302 HighsModelStatus::REACHED_TIME_LIMIT ||
2303 workHMO.scaled_model_status_ ==
2304 HighsModelStatus::REACHED_ITERATION_LIMIT ||
2305 workHMO.scaled_model_status_ ==
2306 HighsModelStatus::REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND);
2307 } else if (workHMO.lp_.sense_ == ObjSense::MINIMIZE &&
2308 solvePhase == SOLVE_PHASE_2) {
2309 if (workHMO.simplex_info_.updated_dual_objective_value >
2310 workHMO.options_.dual_objective_value_upper_bound)
2311 solve_bailout = reachedExactDualObjectiveValueUpperBound();
2312 }
2313 return solve_bailout;
2314 }
2315
reachedExactDualObjectiveValueUpperBound()2316 bool HDual::reachedExactDualObjectiveValueUpperBound() {
2317 // Solving a minimization in dual simplex phase 2, and dual
2318 // objective exceeds the prescribed upper bound. However, costs
2319 // will be perturbed, so need to check whether exact dual
2320 // objective value exceeds the prescribed upper bound. This can be
2321 // a relatively expensive calculation, so determine whether to do
2322 // it according to the sparsity of the pivotal row
2323 bool reached_exact_dual_objective_value_upper_bound = false;
2324 double use_row_ap_density =
2325 std::min(std::max(analysis->row_ap_density, 0.01), 1.0);
2326 int check_frequency = 1.0 / use_row_ap_density;
2327 assert(check_frequency > 0);
2328
2329 bool check_exact_dual_objective_value =
2330 workHMO.simplex_info_.update_count % check_frequency == 0;
2331
2332 if (check_exact_dual_objective_value) {
2333 const double dual_objective_value_upper_bound =
2334 workHMO.options_.dual_objective_value_upper_bound;
2335 const double perturbed_dual_objective_value =
2336 workHMO.simplex_info_.updated_dual_objective_value;
2337 const double perturbed_value_residual =
2338 perturbed_dual_objective_value - dual_objective_value_upper_bound;
2339 const double exact_dual_objective_value = computeExactDualObjectiveValue();
2340 const double exact_value_residual =
2341 exact_dual_objective_value - dual_objective_value_upper_bound;
2342 std::string action;
2343 if (exact_dual_objective_value > dual_objective_value_upper_bound) {
2344 #ifdef SCIP_DEV
2345 printf("HDual::solvePhase2: %12g = Objective > ObjectiveUB\n",
2346 workHMO.simplex_info_.updated_dual_objective_value,
2347 dual_objective_value_upper_bound);
2348 #endif
2349 action = "Have DualUB bailout";
2350 reached_exact_dual_objective_value_upper_bound = true;
2351 workHMO.scaled_model_status_ =
2352 HighsModelStatus::REACHED_DUAL_OBJECTIVE_VALUE_UPPER_BOUND;
2353 } else {
2354 action = "No DualUB bailout";
2355 }
2356 HighsLogMessage(workHMO.options_.logfile, HighsMessageType::INFO,
2357 "%s on iteration %d: Density %11.4g; Frequency %d: "
2358 "Residual(Perturbed = %g; Exact = %g)",
2359 action.c_str(), workHMO.iteration_counts_.simplex,
2360 use_row_ap_density, check_frequency,
2361 perturbed_value_residual, exact_value_residual);
2362 }
2363 return reached_exact_dual_objective_value_upper_bound;
2364 }
2365
computeExactDualObjectiveValue()2366 double HDual::computeExactDualObjectiveValue() {
2367 const HighsLp& simplex_lp = workHMO.simplex_lp_;
2368 const SimplexBasis& simplex_basis = workHMO.simplex_basis_;
2369 const HighsSimplexInfo& simplex_info = workHMO.simplex_info_;
2370 HMatrix& matrix = workHMO.matrix_;
2371 HFactor& factor = workHMO.factor_;
2372 // Create a local buffer for the pi vector
2373 HVector dual_col;
2374 dual_col.setup(simplex_lp.numRow_);
2375 dual_col.clear();
2376 for (int iRow = 0; iRow < simplex_lp.numRow_; iRow++) {
2377 int iVar = simplex_basis.basicIndex_[iRow];
2378 if (iVar < simplex_lp.numCol_) {
2379 const double value = simplex_lp.colCost_[iVar];
2380 if (value) {
2381 dual_col.count++;
2382 dual_col.index[iRow] = iRow;
2383 dual_col.array[iRow] = value;
2384 }
2385 }
2386 }
2387 // Create a local buffer for the dual vector
2388 const int numTot = simplex_lp.numCol_ + simplex_lp.numRow_;
2389 HVector dual_row;
2390 dual_row.setup(simplex_lp.numCol_);
2391 dual_row.clear();
2392 if (dual_col.count) {
2393 const double NoDensity = 1;
2394 factor.btran(dual_col, NoDensity);
2395 matrix.priceByColumn(dual_row, dual_col);
2396 }
2397 double dual_objective = simplex_lp.offset_;
2398 double norm_dual = 0;
2399 double norm_delta_dual = 0;
2400 for (int iCol = 0; iCol < simplex_lp.numCol_; iCol++) {
2401 if (!simplex_basis.nonbasicFlag_[iCol]) continue;
2402 double exact_dual = simplex_lp.colCost_[iCol] - dual_row.array[iCol];
2403 double residual = fabs(exact_dual - simplex_info.workDual_[iCol]);
2404 norm_dual += fabs(exact_dual);
2405 norm_delta_dual += residual;
2406 if (residual > 1e10)
2407 HighsLogMessage(
2408 workHMO.options_.logfile, HighsMessageType::WARNING,
2409 "Col %4d: ExactDual = %11.4g; WorkDual = %11.4g; Residual = %11.4g",
2410 iCol, exact_dual, simplex_info.workDual_[iCol], residual);
2411 dual_objective += simplex_info.workValue_[iCol] * exact_dual;
2412 }
2413 for (int iVar = simplex_lp.numCol_; iVar < numTot; iVar++) {
2414 if (!simplex_basis.nonbasicFlag_[iVar]) continue;
2415 int iRow = iVar - simplex_lp.numCol_;
2416 double exact_dual = -dual_col.array[iRow];
2417 double residual = fabs(exact_dual - simplex_info.workDual_[iVar]);
2418 norm_dual += fabs(exact_dual);
2419 norm_delta_dual += residual;
2420 if (residual > 1e10)
2421 HighsLogMessage(
2422 workHMO.options_.logfile, HighsMessageType::WARNING,
2423 "Row %4d: ExactDual = %11.4g; WorkDual = %11.4g; Residual = %11.4g",
2424 iRow, exact_dual, simplex_info.workDual_[iVar], residual);
2425 dual_objective += simplex_info.workValue_[iVar] * exact_dual;
2426 }
2427 double relative_delta = norm_delta_dual / std::max(norm_dual, 1.0);
2428 if (relative_delta > 1e-3)
2429 HighsLogMessage(
2430 workHMO.options_.logfile, HighsMessageType::WARNING,
2431 "||exact dual vector|| = %g; ||delta dual vector|| = %g: ratio = %g",
2432 norm_dual, norm_delta_dual, relative_delta);
2433 return dual_objective;
2434 }
2435