55#include " Status.h"
66#include " ipm/hipo/auxiliary/Auxiliary.h"
77#include " ipm/hipo/auxiliary/Log.h"
8+ #include " parallel/HighsParallel.h"
89
910namespace hipo {
1011
@@ -121,8 +122,7 @@ Int FactorHiGHSSolver::buildNEvalues(const HighsSparseMatrix& A,
121122 return kStatusOk ;
122123}
123124
124- Int FactorHiGHSSolver::buildNEstructure (const HighsSparseMatrix& A,
125- int64_t nz_limit) {
125+ Int FactorHiGHSSolver::buildNEstructure (const HighsSparseMatrix& A) {
126126 // Build lower triangular structure of AAt.
127127 // This approach uses a column-wise copy of A, a partial row-wise copy and a
128128 // vector of corresponding indices.
@@ -194,10 +194,16 @@ Int FactorHiGHSSolver::buildNEstructure(const HighsSparseMatrix& A,
194194 }
195195 // intersection of row with rows below finished.
196196
197- // if the total number of nonzeros exceeds the maximum , return error
198- if ((int64_t )ptrNE_[row] + (int64_t )nz_in_col >= nz_limit )
197+ // if the total number of nonzeros overflows the int type , return OoM
198+ if ((int64_t )ptrNE_[row] + (int64_t )nz_in_col >= kHighsIInf )
199199 return kStatusOoM ;
200200
201+ // if the total number of nonzeros exceeds the maximum, return error.
202+ // this is useful when the number of nnz from AS factor is known.
203+ if ((int64_t )ptrNE_[row] + (int64_t )nz_in_col >=
204+ NE_nz_limit_.load (std::memory_order_relaxed))
205+ return kStatusErrorAnalyse ;
206+
201207 // update pointers
202208 ptrNE_[row + 1 ] = ptrNE_[row] + nz_in_col;
203209
@@ -380,7 +386,7 @@ Int FactorHiGHSSolver::analyseAS(Symbolic& S) {
380386 std::vector<Int> ptrLower, rowsLower;
381387 if (Int status = getASstructure (model_.A (), ptrLower, rowsLower))
382388 return status;
383- if (info_) info_->matrix_structure_time = clock.stop ();
389+ if (info_) info_->AS_structure_time = clock.stop ();
384390
385391 // create vector of signs of pivots
386392 std::vector<Int> pivot_signs (model_.A ().num_col_ + model_.A ().num_row_ , -1 );
@@ -401,16 +407,12 @@ Int FactorHiGHSSolver::analyseAS(Symbolic& S) {
401407 return status ? kStatusErrorAnalyse : kStatusOk ;
402408}
403409
404- Int FactorHiGHSSolver::analyseNE (Symbolic& S, int64_t nz_limit ) {
410+ Int FactorHiGHSSolver::analyseNE (Symbolic& S) {
405411 // Perform analyse phase of augmented system and return symbolic factorisation
406412 // in object S and the status. If building the matrix failed, the status is
407413 // set to OoM.
408414
409- log_.printDevInfo (" Building NE structure\n " );
410-
411415 Clock clock;
412- if (Int status = buildNEstructure (model_.A (), nz_limit)) return status;
413- if (info_) info_->matrix_structure_time = clock.stop ();
414416
415417 // create vector of signs of pivots
416418 std::vector<Int> pivot_signs (model_.A ().num_row_ , 1 );
@@ -441,24 +443,57 @@ Int FactorHiGHSSolver::chooseNla() {
441443
442444 Clock clock;
443445
444- // Perform analyse phase of augmented system
445- if (analyseAS (symb_AS)) failure_AS = true ;
446-
447- // Perform analyse phase of normal equations
448- if (model_.m () > kMinRowsForDensity &&
449- model_.maxColDensity () > kDenseColThresh ) {
450- // Normal equations would be too expensive because there are dense
451- // columns, so skip it.
452- failure_NE = true ;
453- } else {
454- // If NE has more nonzeros than the factor of AS, then it's likely that AS
455- // will be preferred, so stop computation of NE.
446+ // Perform analyseAS concurrently with buildNEstructure. Then, perform
447+ // analyseNE. Metis is not thread-safe, so cannot perform the two analyse at
448+ // the same time.
449+ highs::parallel::TaskGroup tg;
450+
451+ tg.spawn ([&, this ]() {
452+ // Perform analyse phase of augmented system
453+ if (analyseAS (symb_AS)) failure_AS = true ;
454+
455+ // Set a multiple of the number of nonzeros in the factor
456+ // of AS as an upper limit for the number of nonzeros of NE.
457+ // This is potentially non-deterministic, because NE_nz_limit_ changes at a
458+ // random moment for function buildNEstructure. However, the most likely
459+ // outcome is that it stops the NE structure when it would not be chosen
460+ // anyway, so it shouldn't have a visible effect. There may be pathological
461+ // cases where this is not true though.
456462 int64_t NE_nz_limit = symb_AS.nz () * kSymbNzMult ;
457463 if (failure_AS || NE_nz_limit > kHighsIInf ) NE_nz_limit = kHighsIInf ;
464+ NE_nz_limit_.store (NE_nz_limit, std::memory_order_relaxed);
465+ });
466+
467+ // Run the two tasks sequentially in debug mode
468+ if (log_.debug (1 )) tg.taskWait ();
469+
470+ tg.spawn ([&, this ]() {
471+ if (model_.m () > kMinRowsForDensity &&
472+ model_.maxColDensity () > kDenseColThresh ) {
473+ // Normal equations would be too expensive because there are dense
474+ // columns, so skip it.
475+ failure_NE = true ;
476+ log_.printDevInfo (" NE skipped\n " );
477+ } else {
478+ log_.printDevInfo (" Building NE structure\n " );
479+
480+ Clock clock;
481+ if (Int status = buildNEstructure (model_.A ())) {
482+ failure_NE = true ;
483+ if (status == kStatusErrorAnalyse )
484+ log_.printDevInfo (" NE stopped early\n " );
485+ if (status == kStatusOoM ) log_.printDevInfo (" NE matrix is too large\n " );
486+ }
487+ if (info_) info_->NE_structure_time = clock.stop ();
488+ }
489+ });
490+
491+ tg.taskWait ();
458492
459- Int NE_status = analyseNE (symb_NE, NE_nz_limit);
493+ // Perform analyse phase of normal equations
494+ if (!failure_NE) {
495+ Int NE_status = analyseNE (symb_NE);
460496 if (NE_status) failure_NE = true ;
461- if (NE_status == kStatusOoM ) log_.printDevInfo (" NE matrix is too large\n " );
462497 }
463498
464499 Int status = kStatusOk ;
@@ -529,11 +564,16 @@ Int FactorHiGHSSolver::setNla() {
529564 }
530565
531566 case kOptionNlaNormEq : {
532- Int status = analyseNE (S_);
533- if (status == kStatusOoM ) {
567+ Clock clock;
568+ Int status = buildNEstructure (model_.A ());
569+ if (info_) info_->NE_structure_time = clock.stop ();
570+ if (status) {
534571 log_.printe (" NE requested, matrix is too large\n " );
535- return kStatusOoM ;
536- } else if (status) {
572+ return kStatusErrorAnalyse ;
573+ }
574+
575+ status = analyseNE (S_);
576+ if (status) {
537577 log_.printe (" NE requested, failed analyse phase\n " );
538578 return kStatusErrorAnalyse ;
539579 }
0 commit comments