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