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 #ifndef SIMPLEX_HAPP_H_
11 #define SIMPLEX_HAPP_H_
12 
13 // todo: clear includes.
14 #include <cstring>
15 #include <fstream>
16 #include <iomanip>
17 #include <iostream>
18 #include <map>
19 #include <set>
20 #include <vector>
21 
22 #include "HConfig.h"
23 #include "lp_data/HighsLp.h"
24 #include "lp_data/HighsLpUtils.h"
25 #include "lp_data/HighsModelObject.h"
26 #include "lp_data/HighsSolution.h"
27 #include "lp_data/HighsSolve.h"
28 #include "lp_data/HighsStatus.h"
29 #include "simplex/HDual.h"
30 #include "simplex/HPrimal.h"
31 #include "simplex/HQPrimal.h"
32 #include "simplex/HSimplex.h"
33 #include "simplex/HSimplexDebug.h"
34 #include "simplex/HSimplexReport.h"
35 #include "simplex/HighsSimplexInterface.h"
36 #include "simplex/SimplexConst.h"
37 #include "simplex/SimplexTimer.h"
38 #include "util/HighsUtils.h"
39 
40 #ifdef OPENMP
41 #include "omp.h"
42 #endif
43 
44 #ifdef HiGHSDEV
reportAnalyseInvertForm(const HighsModelObject & highs_model_object)45 void reportAnalyseInvertForm(const HighsModelObject& highs_model_object) {
46   const HighsSimplexInfo& simplex_info = highs_model_object.simplex_info_;
47 
48   printf("grep_kernel,%s,%s,%d,%d,%d,",
49          highs_model_object.lp_.model_name_.c_str(),
50          highs_model_object.lp_.lp_name_.c_str(), simplex_info.num_invert,
51          simplex_info.num_kernel, simplex_info.num_major_kernel);
52   if (simplex_info.num_kernel)
53     printf("%g", simplex_info.sum_kernel_dim / simplex_info.num_kernel);
54   printf(",%g,%g,", simplex_info.running_average_kernel_dim,
55          simplex_info.max_kernel_dim);
56   if (simplex_info.num_invert)
57     printf("Fill-in,%g",
58            simplex_info.sum_invert_fill_factor / simplex_info.num_invert);
59   printf(",");
60   if (simplex_info.num_kernel)
61     printf("%g", simplex_info.sum_kernel_fill_factor / simplex_info.num_kernel);
62   printf(",");
63   if (simplex_info.num_major_kernel)
64     printf("%g", simplex_info.sum_major_kernel_fill_factor /
65                      simplex_info.num_major_kernel);
66   printf(",%g,%g,%g\n", simplex_info.running_average_invert_fill_factor,
67          simplex_info.running_average_kernel_fill_factor,
68          simplex_info.running_average_major_kernel_fill_factor);
69 }
70 #endif
71 
72 // Single function to solve the (scaled) LP according to
73 // options. Assumes that the LP has a positive number of rows, since
74 // unconstrained LPs should be solved in solveLpSimplex
75 //
76 // Also sets the solution parameters for the unscaled LP
runSimplexSolver(HighsModelObject & highs_model_object)77 HighsStatus runSimplexSolver(HighsModelObject& highs_model_object) {
78   HighsStatus return_status = HighsStatus::OK;
79   HighsStatus call_status;
80   HighsSimplexInfo& simplex_info = highs_model_object.simplex_info_;
81   FILE* logfile = highs_model_object.options_.logfile;
82 
83   // Assumes that the LP has a positive number of rows, since
84   // unconstrained LPs should be solved in solveLpSimplex
85   bool positive_num_row = highs_model_object.lp_.numRow_ > 0;
86   assert(positive_num_row);
87   if (!positive_num_row) {
88     HighsLogMessage(logfile, HighsMessageType::ERROR,
89                     "runSimplexSolver called for LP with non-positive (%d) "
90                     "number of constraints",
91                     highs_model_object.lp_.numRow_);
92     return HighsStatus::Error;
93   }
94 #ifdef HiGHSDEV
95   HighsSimplexAnalysis& analysis = highs_model_object.simplex_analysis_;
96   analysis.simplexTimerStart(SimplexTotalClock);
97 #endif
98   // Indicate that dual and primal rays are not known
99   highs_model_object.simplex_lp_status_.has_dual_ray = false;
100   highs_model_object.simplex_lp_status_.has_primal_ray = false;
101 
102   // Transition to the best possible simplex basis and solution
103   call_status = transition(highs_model_object);
104   return_status = interpretCallStatus(call_status, return_status, "transition");
105   if (return_status == HighsStatus::Error) return return_status;
106 #ifdef HiGHSDEV
107     // reportSimplexLpStatus(simplex_lp_status, "After transition");
108 #endif
109   HighsSolutionParams& scaled_solution_params =
110       highs_model_object.scaled_solution_params_;
111   // Determine whether the scaled solution is optimal
112   if (scaled_solution_params.num_primal_infeasibilities == 0 &&
113       scaled_solution_params.num_dual_infeasibilities == 0) {
114     // No scaled primal or dual infeasiblities => Optimal
115     highs_model_object.scaled_model_status_ = HighsModelStatus::OPTIMAL;
116     scaled_solution_params.primal_status =
117         PrimalDualStatus::STATUS_FEASIBLE_POINT;
118     scaled_solution_params.dual_status =
119         PrimalDualStatus::STATUS_FEASIBLE_POINT;
120   } else {
121     // Not optimal
122     //
123     // Given a simplex basis and solution, use the number of primal and
124     // dual infeasibilities to determine which simplex variant to use.
125     //
126     // 1. If it is "CHOOSE", in which case an approapriate stratgy is
127     // used
128     //
129     // 2. If re-solving choose the strategy appropriate to primal or
130     // dual feasibility
131     //
132     int simplex_strategy = highs_model_object.options_.simplex_strategy;
133     if (scaled_solution_params.num_primal_infeasibilities > 0) {
134       // Not primal feasible, so use dual simplex if choice is permitted
135       if (simplex_strategy == SIMPLEX_STRATEGY_CHOOSE)
136         simplex_strategy = SIMPLEX_STRATEGY_DUAL;
137     } else {
138       // Primal feasible - so must be dual infeasible
139       assert(scaled_solution_params.num_dual_infeasibilities > 0);
140       // Use primal simplex if choice is permitted
141       if (simplex_strategy == SIMPLEX_STRATEGY_CHOOSE)
142         simplex_strategy = SIMPLEX_STRATEGY_PRIMAL;
143     }
144     // Set min/max_threads to correspond to serial code. They will be
145     // set to other values if parallel options are used.
146     simplex_info.min_threads = 1;
147     simplex_info.max_threads = 1;
148     // Record the min/max minimum number of HiGHS threads in the options
149     const int highs_min_threads = highs_model_object.options_.highs_min_threads;
150     const int highs_max_threads = highs_model_object.options_.highs_max_threads;
151     int omp_max_threads = 0;
152 #ifdef OPENMP
153     omp_max_threads = omp_get_max_threads();
154 #endif
155     if (highs_model_object.options_.parallel == on_string &&
156         simplex_strategy == SIMPLEX_STRATEGY_DUAL) {
157       // The parallel strategy is on and the simplex strategy is dual so use
158       // PAMI if there are enough OMP threads
159       if (omp_max_threads >= DUAL_MULTI_MIN_THREADS)
160         simplex_strategy = SIMPLEX_STRATEGY_DUAL_MULTI;
161     }
162     //
163     // If parallel stratgies are used, the minimum number of HiGHS threads used
164     // will be set to be at least the minimum required for the strategy
165     //
166     // All this is independent of the number of OMP threads available,
167     // since code with multiple HiGHS threads can be run in serial.
168 #ifdef OPENMP
169     if (simplex_strategy == SIMPLEX_STRATEGY_DUAL_TASKS) {
170       simplex_info.min_threads = max(DUAL_TASKS_MIN_THREADS, highs_min_threads);
171       simplex_info.max_threads =
172           max(simplex_info.min_threads, highs_max_threads);
173     } else if (simplex_strategy == SIMPLEX_STRATEGY_DUAL_MULTI) {
174       simplex_info.min_threads = max(DUAL_MULTI_MIN_THREADS, highs_min_threads);
175       simplex_info.max_threads =
176           max(simplex_info.min_threads, highs_max_threads);
177     }
178 #endif
179     // Set the number of HiGHS threads to be used to be the maximum
180     // number to be used
181     simplex_info.num_threads = simplex_info.max_threads;
182     // Give a warning if the number of threads to be used is fewer than
183     // the minimum number of HiGHS threads allowed
184     if (simplex_info.num_threads < highs_min_threads) {
185       HighsLogMessage(
186           logfile, HighsMessageType::WARNING,
187           "Using %d HiGHS threads for parallel strategy rather than "
188           "minimum number (%d) specified in options",
189           simplex_info.num_threads, highs_min_threads);
190     }
191     // Give a warning if the number of threads to be used is more than
192     // the maximum number of HiGHS threads allowed
193     if (simplex_info.num_threads > highs_max_threads) {
194       HighsLogMessage(
195           logfile, HighsMessageType::WARNING,
196           "Using %d HiGHS threads for parallel strategy rather than "
197           "maximum number (%d) specified in options",
198           simplex_info.num_threads, highs_max_threads);
199     }
200     // Give a warning if the number of threads to be used is fewer than
201     // the number of OMP threads available
202     if (simplex_info.num_threads > omp_max_threads) {
203       HighsLogMessage(
204           logfile, HighsMessageType::WARNING,
205           "Number of OMP threads available = %d < %d = Number of HiGHS threads "
206           "to be used: Parallel performance will be less than anticipated",
207           omp_max_threads, simplex_info.num_threads);
208     }
209     // Simplex strategy is now fixed - so set the value to be referred
210     // to in the simplex solver
211     simplex_info.simplex_strategy = simplex_strategy;
212     // Official start of solver Start the solve clock - because
213     // setupForSimplexSolve has simplex computations
214     SimplexAlgorithm algorithm = SimplexAlgorithm::DUAL;
215     if (simplex_strategy == SIMPLEX_STRATEGY_PRIMAL)
216       algorithm = SimplexAlgorithm::PRIMAL;
217     reportSimplexPhaseIterations(highs_model_object, algorithm, true);
218     if (simplex_strategy == SIMPLEX_STRATEGY_PRIMAL) {
219       // Use primal simplex solver
220       HighsLogMessage(logfile, HighsMessageType::INFO,
221                       "Using primal simplex solver");
222       HQPrimal primal_solver(highs_model_object);
223       call_status = primal_solver.solve();
224       return_status =
225           interpretCallStatus(call_status, return_status, "HQPrimal::solve");
226       if (return_status == HighsStatus::Error) return return_status;
227     } else {
228       // Use dual simplex solver
229       HDual dual_solver(highs_model_object);
230       dual_solver.options();
231       // Solve, depending on the particular strategy
232       if (simplex_strategy == SIMPLEX_STRATEGY_DUAL_TASKS) {
233         // Parallel - SIP
234         HighsLogMessage(logfile, HighsMessageType::INFO,
235                         "Using parallel simplex solver - SIP with %d threads",
236                         simplex_info.num_threads);
237         // writePivots("tasks");
238         call_status = dual_solver.solve();
239         return_status =
240             interpretCallStatus(call_status, return_status, "HDual::solve");
241         if (return_status == HighsStatus::Error) return return_status;
242       } else if (simplex_strategy == SIMPLEX_STRATEGY_DUAL_MULTI) {
243         // Parallel - PAMI
244         HighsLogMessage(logfile, HighsMessageType::INFO,
245                         "Using parallel simplex solver - PAMI with %d threads",
246                         simplex_info.num_threads);
247         call_status = dual_solver.solve();
248         return_status =
249             interpretCallStatus(call_status, return_status, "HDual::solve");
250         if (return_status == HighsStatus::Error) return return_status;
251       } else {
252         // Serial
253         HighsLogMessage(logfile, HighsMessageType::INFO,
254                         "Using dual simplex solver - serial");
255         call_status = dual_solver.solve();
256         return_status =
257             interpretCallStatus(call_status, return_status, "HDual::solve");
258         if (return_status == HighsStatus::Error) return return_status;
259       }
260 
261       int& num_scaled_primal_infeasibilities =
262           simplex_info.num_primal_infeasibilities;
263       if (highs_model_object.scaled_model_status_ ==
264               HighsModelStatus::OPTIMAL &&
265           num_scaled_primal_infeasibilities) {
266         // If Phase 2 primal simplex solver creates primal
267         // infeasibilities it doesn't check and may claim
268         // optimality. Try again with serial dual solver
269         HighsLogMessage(
270             logfile, HighsMessageType::WARNING,
271             "Phase 2 primal simplex clean-up infeasibilities: Pr %d(Max %9.4g, "
272             "Sum %9.4g) so re-solving",
273             num_scaled_primal_infeasibilities,
274             highs_model_object.scaled_solution_params_.max_primal_infeasibility,
275             highs_model_object.scaled_solution_params_
276                 .sum_primal_infeasibilities);
277         call_status = dual_solver.solve();
278         return_status =
279             interpretCallStatus(call_status, return_status, "HDual::solve");
280         if (return_status == HighsStatus::Error) return return_status;
281         if (highs_model_object.scaled_model_status_ ==
282                 HighsModelStatus::OPTIMAL &&
283             num_scaled_primal_infeasibilities) {
284           // Still optimal with primal infeasibilities
285           highs_model_object.scaled_model_status_ = HighsModelStatus::NOTSET;
286         }
287       }
288     }
289 
290     computeSimplexInfeasible(highs_model_object);
291     copySimplexInfeasible(highs_model_object);
292 
293     scaled_solution_params.objective_function_value =
294         simplex_info.primal_objective_value;
295 
296     if (highs_model_object.scaled_model_status_ == HighsModelStatus::OPTIMAL) {
297       highs_model_object.scaled_solution_params_.primal_status =
298           PrimalDualStatus::STATUS_FEASIBLE_POINT;
299       highs_model_object.scaled_solution_params_.dual_status =
300           PrimalDualStatus::STATUS_FEASIBLE_POINT;
301     }
302     debugBasisCondition(highs_model_object, "Final");
303 
304     // Official finish of solver
305     reportSimplexPhaseIterations(highs_model_object, algorithm);
306   }
307 
308 #ifdef HiGHSDEV
309   analysis.simplexTimerStop(SimplexTotalClock);
310 #endif
311   // Reaches here whether optimal or not
312   if (debugSimplexBasicSolution("After runSimplexSolver", highs_model_object) ==
313       HighsDebugStatus::LOGICAL_ERROR)
314     return HighsStatus::Error;
315 
316   return_status =
317       highsStatusFromHighsModelStatus(highs_model_object.scaled_model_status_);
318 #ifdef HiGHSDEV
319   //  reportSimplexLpStatus(simplex_lp_status, "After running the simplex
320   //  solver");
321 #endif
322   return return_status;
323 }
324 
tryToSolveUnscaledLp(HighsModelObject & highs_model_object)325 HighsStatus tryToSolveUnscaledLp(HighsModelObject& highs_model_object) {
326   HighsStatus return_status = HighsStatus::OK;
327   HighsStatus call_status;
328   for (int pass = 0; pass < 2; pass++) {
329     double new_primal_feasibility_tolerance;
330     double new_dual_feasibility_tolerance;
331 #ifdef HiGHSDEV
332     HighsLogMessage(highs_model_object.options_.logfile, HighsMessageType::INFO,
333                     "tryToSolveUnscaledLp pass %1d:", pass);
334 #endif
335     // Deduce the unscaled solution parameters, and new fasibility tolerances if
336     // not primal and/or dual feasible
337     call_status = getNewInfeasibilityTolerancesFromSimplexBasicSolution(
338         highs_model_object, highs_model_object.unscaled_solution_params_,
339         new_primal_feasibility_tolerance, new_dual_feasibility_tolerance);
340     return_status = interpretCallStatus(
341         call_status, return_status,
342         "getNewInfeasibilityTolerancesFromSimplexBasicSolution");
343     if (return_status == HighsStatus::Error) return return_status;
344     int num_unscaled_primal_infeasibilities =
345         highs_model_object.unscaled_solution_params_.num_primal_infeasibilities;
346     int num_unscaled_dual_infeasibilities =
347         highs_model_object.unscaled_solution_params_.num_dual_infeasibilities;
348     // Set the model and solution status according to the unscaled solution
349     // parameters
350     if (num_unscaled_primal_infeasibilities == 0 &&
351         num_unscaled_dual_infeasibilities == 0) {
352       highs_model_object.unscaled_model_status_ = HighsModelStatus::OPTIMAL;
353       highs_model_object.unscaled_solution_params_.primal_status =
354           PrimalDualStatus::STATUS_FEASIBLE_POINT;
355       highs_model_object.unscaled_solution_params_.dual_status =
356           PrimalDualStatus::STATUS_FEASIBLE_POINT;
357       return HighsStatus::OK;
358     }
359 
360     // Not optimal
361     assert(num_unscaled_primal_infeasibilities > 0 ||
362            num_unscaled_dual_infeasibilities > 0);
363 
364     HighsLogMessage(highs_model_object.options_.logfile, HighsMessageType::INFO,
365                     "Have %d primal and %d dual unscaled infeasibilities",
366                     num_unscaled_primal_infeasibilities,
367                     num_unscaled_dual_infeasibilities);
368     HighsLogMessage(highs_model_object.options_.logfile, HighsMessageType::INFO,
369                     "Possibly re-solve with feasibility tolerances of %g "
370                     "primal and %g dual",
371                     new_primal_feasibility_tolerance,
372                     new_dual_feasibility_tolerance);
373     const bool refinement = false;
374     if (refinement) {
375       HighsLogMessage(highs_model_object.options_.logfile,
376                       HighsMessageType::INFO,
377                       "Re-solving with refined tolerances");
378       highs_model_object.scaled_solution_params_.primal_feasibility_tolerance =
379           new_primal_feasibility_tolerance;
380       highs_model_object.scaled_solution_params_.dual_feasibility_tolerance =
381           new_dual_feasibility_tolerance;
382 
383       HighsOptions save_options = highs_model_object.options_;
384       HighsOptions& options = highs_model_object.options_;
385       options.simplex_strategy = SIMPLEX_STRATEGY_CHOOSE;
386       call_status = runSimplexSolver(highs_model_object);
387       options = save_options;
388       return_status =
389           interpretCallStatus(call_status, return_status, "runSimplexSolver");
390       if (return_status == HighsStatus::Error) return return_status;
391       // Assess success according to the scaled model status, unless
392       // something worse has happened earlier
393       call_status = highsStatusFromHighsModelStatus(
394           highs_model_object.scaled_model_status_);
395       return_status = interpretCallStatus(call_status, return_status);
396       if (return_status == HighsStatus::Error) return return_status;
397     } else {
398       HighsLogMessage(highs_model_object.options_.logfile,
399                       HighsMessageType::INFO,
400                       "Not re-solving with refined tolerances");
401       return return_status;
402     }
403   }
404   return return_status;
405 }
406 
407 // Single method to solve an LP with the simplex method. Solves the
408 // scaled LP then analyses the unscaled solution. If it doesn't satisfy
409 // the required tolerances, tolerances for the scaled LP are
410 // identified which, if used, might yield an unscaled solution that
411 // satisfies the required tolerances.
412 //
413 // This method and tryToSolveUnscaledLp may make mutiple calls to
414 // runSimplexSolver
415 //
416 // It sets the HiGHS basis within highs_model_object and, if optimal,
417 // the HiGHS solution, too
solveLpSimplex(HighsModelObject & highs_model_object)418 HighsStatus solveLpSimplex(HighsModelObject& highs_model_object) {
419   HighsStatus return_status = HighsStatus::OK;
420   HighsStatus call_status;
421   // Reset unscaled and scaled model status and solution params - except for
422   // iteration counts
423   resetModelStatusAndSolutionParams(highs_model_object);
424   // Set the value of simplex_info_.run_quiet to suppress computation
425   // that is just for reporting
426   //  setRunQuiet(highs_model_object);
427   //  printf("Forcing simplex_info_.run_quiet true for testing\n");
428   //  highs_model_object.simplex_info_.run_quiet = true;
429 
430   if (!highs_model_object.lp_.numRow_) {
431     // Unconstrained LP so solve directly
432     call_status = solveUnconstrainedLp(highs_model_object);
433     return_status =
434         interpretCallStatus(call_status, return_status, "solveUnconstrainedLp");
435     return return_status;
436   }
437   HighsSimplexAnalysis& simplex_analysis = highs_model_object.simplex_analysis_;
438   simplex_analysis.setup(highs_model_object.lp_, highs_model_object.options_,
439                          highs_model_object.iteration_counts_.simplex);
440   //  SimplexTimer simplex_timer;
441   //  simplex_timer.initialiseSimplexClocks(highs_model_object);
442   // (Try to) solve the scaled LP
443   call_status = runSimplexSolver(highs_model_object);
444   return_status =
445       interpretCallStatus(call_status, return_status, "runSimplexSolver");
446   if (return_status == HighsStatus::Error) return return_status;
447 #ifdef HiGHSDEV
448   const HighsSimplexInfo& simplex_info = highs_model_object.simplex_info_;
449   if (simplex_info.analyse_invert_form)
450     reportAnalyseInvertForm(highs_model_object);
451 #endif
452 
453   double cost_scale = highs_model_object.scale_.cost_;
454 #ifdef HiGHSDEV
455   if (cost_scale != 1) printf("solveLpSimplex: Can't handle cost scaling\n");
456 #endif
457   assert(cost_scale == 1);
458   if (cost_scale != 1) return HighsStatus::Error;
459 
460   if (highs_model_object.scaled_model_status_ == HighsModelStatus::OPTIMAL) {
461     // (Scaled) LP solved to optimality
462     if (highs_model_object.scale_.is_scaled_) {
463       // LP solved was scaled, so see whether the scaled problem has
464       // been solved
465       //
466       // Analyse the unscaled solution and, if it doesn't satisfy the
467       // required tolerances, tolerances for the scaled LP are identified
468       // which, if used, might yield an unscaled solution that satisfies
469       // the required tolerances. Can't handle cost scaling
470       //
471       call_status = tryToSolveUnscaledLp(highs_model_object);
472       return_status =
473           interpretCallStatus(call_status, return_status, "runSimplexSolver");
474       if (return_status == HighsStatus::Error) return return_status;
475     } else {
476       // If scaling hasn't been used, then the original LP has been
477       // solved to the required tolerances
478       highs_model_object.unscaled_model_status_ =
479           highs_model_object.scaled_model_status_;
480       highs_model_object.unscaled_solution_params_ =
481           highs_model_object.scaled_solution_params_;
482     }
483   } else {
484     // If the solution isn't optimal, then clear the scaled solution
485     // infeasibility parameters
486     highs_model_object.unscaled_model_status_ =
487         highs_model_object.scaled_model_status_;
488     invalidateSolutionInfeasibilityParams(
489         highs_model_object.scaled_solution_params_);
490   }
491 
492 #ifdef HiGHSDEV
493   // Report profiling and analysis for the application of the simplex
494   // method to this LP problem
495   reportSimplexProfiling(highs_model_object);
496   if (simplex_info.report_HFactor_clock) simplex_analysis.reportFactorTimer();
497   if (simplex_info.analyse_iterations) simplex_analysis.summaryReport();
498   simplex_analysis.summaryReportFactor();
499 #endif
500 
501   // Deduce the HiGHS basis and solution from the simplex basis and solution
502   HighsSimplexInterface simplex_interface(highs_model_object);
503   simplex_interface.convertSimplexToHighsSolution();
504   simplex_interface.convertSimplexToHighsBasis();
505 
506   copySolutionObjectiveParams(highs_model_object.scaled_solution_params_,
507                               highs_model_object.unscaled_solution_params_);
508 
509   // Assess success according to the scaled model status, unless
510   // something worse has happened earlier
511   call_status =
512       highsStatusFromHighsModelStatus(highs_model_object.scaled_model_status_);
513   return_status = interpretCallStatus(call_status, return_status);
514   return return_status;
515 }
516 #endif
517