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