Skip to content

Commit 5272ddf

Browse files
committed
merge hpm updates
2 parents f16b195 + 64aff00 commit 5272ddf

File tree

16 files changed

+268
-157
lines changed

16 files changed

+268
-157
lines changed

docs/src/options/definitions.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
- Default: "choose"
77

88
## [solver](@id option-solver)
9-
- Solver option: "simplex", "choose", "ipm" or "pdlp". If "simplex"/"ipm"/"pdlp" is chosen then, for a MIP (QP) the integrality constraint (quadratic term) will be ignored
9+
- Solver option: "simplex", "choose", "ipm", "hipo" or "pdlp". If "simplex"/"ipm"/"hipo"/"pdlp" is chosen then, for a MIP (QP) the integrality constraint (quadratic term) will be ignored
1010
- Type: string
1111
- Default: "choose"
1212

@@ -409,6 +409,27 @@
409409
- Range: {0, 2147483647}
410410
- Default: 2147483647
411411

412+
## [hipo\_system](@id option-hipo-system)
413+
- Type of Newton system for HiPO: "augmented", "normaleq" or "choose"
414+
- Type: string
415+
- Default: "choose"
416+
417+
## [hipo\_parallel\_type](@id option-hipo-parallel)
418+
- Type of parallelism for HiPO: "tree", "node", "both"
419+
- Type: string
420+
- Default: "both"
421+
422+
## hipo\_block\_size
423+
- Block size for dense linear algebra within HiPO
424+
- Type: integer
425+
- Range: {0, 2147483647}
426+
- Default: 128
427+
428+
## pdlp\_native\_termination
429+
- Use native termination for PDLP solver: Default = false
430+
- Type: boolean
431+
- Default: "false"
432+
412433
## pdlp\_scaling
413434
- Scaling option for PDLP solver: Default = true
414435
- Type: boolean

docs/src/parallel.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
HiGHS currently has limited opportunities for exploiting parallel
66
computing. When using a CPU, these are currently restricted to the
7-
dual simplex solver for LP, and the MIP solver. Details of these and
8-
future plans are set out below. HiGHS has an implementation of a first
9-
order method (PDLP) for solving LPs that can exploit the availability of a
10-
[GPU](@ref gpu).
7+
dual simplex solver for LP, the factorisation-based interior point solver,
8+
and the MIP solver. Details of these and future plans are set out below.
9+
HiGHS has an implementation of a first order method (PDLP) for solving LPs
10+
that can exploit the availability of a [GPU](@ref highs-gpu).
1111

1212
By default, when running in parallel, HiGHS will use half the
1313
available threads on a machine. This number can be modified by setting
@@ -47,14 +47,32 @@ clique tables, and when the interior point solver is used to compute
4747
the analytic centre. This parallelism is always advantageous, so is
4848
performed regardless of the value of the [parallel](@ref) option.
4949

50+
## IPM
51+
52+
The interior point solver HiPO uses multiple threads to process the
53+
elimination tree during the multifrontal factorisation (_tree level_)
54+
and to perform the dense factorisation of the frontal matrices
55+
(_node level_).
56+
57+
If the [parallel](@ref) option is set "on", the level of parallelism is
58+
determined by the [hipo\_parallel\_type](@ref option-hipo-parallel) option,
59+
which can be "tree" for tree level only, "node" for node level only, or
60+
"both" for both levels.
61+
62+
If the [parallel](@ref) option is set "choose", the solver selects which
63+
level to use based on a heuristic. When the [parallel](@ref) option is set
64+
"choose" or "off", the value of the hipo\_parallel\_type option is ignored.
65+
66+
5067
## Future plans
5168

5269
The MIP solver has been written with parallel tree search in mind. Some
5370
work has started (Feb 2025), and it is hoped that a prototype solver
5471
will be available during 2025.
5572

56-
Development of a new parallel interior point solver began 2023, and a
57-
prototype solver is expected to be be available during 2025.
73+
Multi-threading within HiPO will be extended to other phases of the solver,
74+
including the solve phase of the factorisation and the process of assemblying
75+
the matrices.
5876

5977
First-order solvers for LP are still very much in their infancy, and
6078
are not robust. Hence the availability of a PDLP solver for LP is

docs/src/solvers.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,30 @@ J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142,
2626

2727
#### Interior point
2828

29-
HiGHS has one interior point (IPM) solver based on the preconditioned conjugate gradient method, as discussed in
29+
HiGHS has two interior point (IPM) solvers:
3030

31-
_Implementation of an interior point method with basis
32-
preconditioning_, L. Schork and J. Gondzio, Mathematical Programming Computation, 12, 603-635, 2020. [DOI:
33-
10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8).
31+
* IPX is based on the preconditioned conjugate gradient method, as discussed in
3432

35-
This solver is serial. An interior point solver based on direct factorization is being developed.
33+
_Implementation of an interior point method with basis
34+
preconditioning_, Mathematical Programming Computation, 12, 603-635, 2020. [DOI:
35+
10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8).
3636

37-
Setting the option [__solver__](@ref option-solver) to "ipm" forces the IPM solver to be used
37+
This solver is serial.
38+
39+
Setting the option [__solver__](@ref option-solver) to "ipm" forces the IPX solver to be used
40+
41+
* HiPO is based on a direct factorisation, as discussed in
42+
43+
_A factorisation-based regularised interior point method using the augmented system_, F. Zanetti and J. Gondzio, 2025,
44+
[available on arxiv](https://arxiv.org/abs/2508.04370)
45+
46+
This solver is parallel.
47+
48+
Setting the option [__solver__](@ref option-solver) to "hipo" forces the HiPO solver to be used.
49+
50+
The [hipo\_system](@ref option-hipo-system) option can be used to select the approach to use when solving the Newton systems
51+
within the interior point solver: select "augmented" to force the solver to use the augmented system, "normaleq" for normal
52+
equations, or "choose" to leave the choice to the solver.
3853

3954
#### Primal-dual hybrid gradient method
4055

@@ -55,7 +70,7 @@ search is work in progress.
5570
## QP
5671

5772
The HiGHS solver for convex QP problems uses an established primal
58-
active set method. The new interior point solver will also be able to
73+
active set method. The new interior point solver HiPO will soon be able to
5974
solve convex QP problems.
6075

6176

highs/ipm/IpxWrapper.cpp

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -499,11 +499,22 @@ HighsStatus solveLpHipo(const HighsOptions& options, HighsTimer& timer,
499499
// hipo_options.refine_with_ipx = true;
500500
// hipo_options.display_ipx = true;
501501

502-
// Option parallel for now is just "on", "off", "choose".
503-
// hipo can accept also partially on, to select only tree or node parallelism.
504-
// It is worth considering whether this choice should be exposed to the user.
505-
if (options.parallel == kHighsOnString)
506-
hipo_options.parallel = hipo::kOptionParallelOn;
502+
// if option parallel is on, it can be refined by option hipo_parallel_type
503+
if (options.parallel == kHighsOnString) {
504+
if (options.hipo_parallel_type == kHipoTreeString)
505+
hipo_options.parallel = hipo::kOptionParallelTreeOnly;
506+
else if (options.hipo_parallel_type == kHipoNodeString)
507+
hipo_options.parallel = hipo::kOptionParallelNodeOnly;
508+
else if (options.hipo_parallel_type == kHipoBothString)
509+
hipo_options.parallel = hipo::kOptionParallelOn;
510+
else {
511+
highsLogUser(options.log_options, HighsLogType::kError,
512+
"Unknown value of option %s\n", kHipoParallelString.c_str());
513+
model_status = HighsModelStatus::kSolveError;
514+
return HighsStatus::kError;
515+
}
516+
}
517+
// otherwise, option hipo_parallel_type is ignored
507518
else if (options.parallel == kHighsOffString)
508519
hipo_options.parallel = hipo::kOptionParallelOff;
509520
else {
@@ -520,18 +531,13 @@ HighsStatus solveLpHipo(const HighsOptions& options, HighsTimer& timer,
520531
hipo_options.nla = hipo::kOptionNlaChoose;
521532
} else {
522533
highsLogUser(options.log_options, HighsLogType::kError,
523-
"Unknown value of option hipo_system\n");
534+
"Unknown value of option %s\n", kHipoSystemString.c_str());
524535
model_status = HighsModelStatus::kSolveError;
525536
return HighsStatus::kError;
526537
}
527538

528-
// ===========================================================================
529-
// TO DO
530-
// - consider adding options for parallel tree/node
531-
// - block size for dense factorisation can have large impact on performance
532-
// and depends on the specific architecture. It may be worth exposing it to
533-
// the user as an advanced option.
534-
// ===========================================================================
539+
// block size option
540+
hipo_options.block_size = options.hipo_block_size;
535541

536542
hipo.set(hipo_options, options.log_options, callback, timer);
537543

highs/ipm/hipo/factorhighs/Analyse.cpp

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
namespace hipo {
1818

1919
Analyse::Analyse(const std::vector<Int>& rows, const std::vector<Int>& ptr,
20-
const std::vector<Int>& signs, const Log* log,
20+
const std::vector<Int>& signs, Int nb, const Log* log,
2121
DataCollector& data)
2222
: log_{log}, data_{data} {
2323
// Input the symmetric matrix to be analysed in CSC format.
@@ -29,7 +29,7 @@ Analyse::Analyse(const std::vector<Int>& rows, const std::vector<Int>& ptr,
2929
n_ = ptr.size() - 1;
3030
nz_ = rows.size();
3131
signs_ = signs;
32-
nb_ = kBlockSize;
32+
nb_ = nb;
3333

3434
// Create upper triangular part
3535
rows_upper_.resize(nz_);
@@ -111,12 +111,12 @@ Int Analyse::getPermutation() {
111111
// fix seed of rng inside Metis, to make it deterministic (?)
112112
options[METIS_OPTION_SEED] = 42;
113113

114-
if (log_) log_->printDevInfo("Metis...");
114+
if (log_) log_->printDevInfo("Metis...\n");
115115

116116
Int status = METIS_NodeND(&n_, temp_ptr.data(), temp_rows.data(), NULL,
117117
options, perm_.data(), iperm_.data());
118118

119-
if (log_) log_->printDevInfo("done\n");
119+
if (log_) log_->printDevInfo("...done\n");
120120
if (status != METIS_OK) {
121121
if (log_) log_->printDevInfo("Error with Metis\n");
122122
return kRetMetisError;
@@ -1357,18 +1357,9 @@ Int Analyse::run(Symbolic& S) {
13571357
computeBlockStart();
13581358
computeCriticalPath();
13591359

1360-
// Too many nonzeros for the integer type selected
1361-
if (nz_factor_ >= std::numeric_limits<Int>::max()) {
1362-
if (log_) log_->printDevInfo("Integer overflow in analyse phase\n");
1363-
return kRetIntOverflow;
1364-
}
1365-
13661360
// move relevant stuff into S
13671361
S.n_ = n_;
13681362
S.sn_ = sn_count_;
1369-
1370-
S.sn_ = sn_count_;
1371-
S.n_ = n_;
13721363
S.nz_ = nz_factor_;
13731364
S.fillin_ = (double)nz_factor_ / nz_;
13741365
S.artificial_nz_ = artificial_nz_;
@@ -1378,6 +1369,7 @@ Int Analyse::run(Symbolic& S) {
13781369
S.largest_front_ = *std::max_element(sn_indices_.begin(), sn_indices_.end());
13791370
S.serial_storage_ = serial_storage_;
13801371
S.flops_ = dense_ops_;
1372+
S.block_size_ = nb_;
13811373

13821374
// compute largest supernode
13831375
std::vector<Int> sn_size(sn_start_.begin() + 1, sn_start_.end());
@@ -1391,14 +1383,18 @@ Int Analyse::run(Symbolic& S) {
13911383
if (i <= 100) S.sn_size_100_++;
13921384
}
13931385

1386+
// Too many nonzeros for the integer type selected.
1387+
// Check after statistics have been moved into S, so that info is accessible
1388+
// for debug logging.
1389+
if (nz_factor_ >= std::numeric_limits<Int>::max()) {
1390+
if (log_) log_->printDevInfo("Integer overflow in analyse phase\n");
1391+
return kRetIntOverflow;
1392+
}
1393+
13941394
// permute signs of pivots
13951395
S.pivot_sign_ = std::move(signs_);
13961396
permuteVector(S.pivot_sign_, perm_);
13971397

1398-
S.nz_ = nz_factor_;
1399-
S.flops_ = dense_ops_;
1400-
S.spops_ = sparse_ops_;
1401-
S.critops_ = critical_ops_;
14021398
S.iperm_ = std::move(iperm_);
14031399
S.rows_ = std::move(rows_sn_);
14041400
S.ptr_ = std::move(ptr_sn_);

highs/ipm/hipo/factorhighs/Analyse.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ class Analyse {
106106
public:
107107
// Constructor: matrix must be in lower triangular format
108108
Analyse(const std::vector<Int>& rows, const std::vector<Int>& ptr,
109-
const std::vector<Int>& signs, const Log* log, DataCollector& data);
109+
const std::vector<Int>& signs, Int nb, const Log* log,
110+
DataCollector& data);
110111

111112
// Run analyse phase and save the result in Symbolic object S
112113
Int run(Symbolic& S);

highs/ipm/hipo/factorhighs/FactorHiGHS.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ FHsolver::FHsolver(const Log* log) : log_{log} {
2020
}
2121

2222
FHsolver::~FHsolver() {
23-
data_.printTimes(*log_);
24-
data_.printIter(*log_);
23+
if (log_) {
24+
data_.printTimes(*log_);
25+
data_.printIter(*log_);
26+
}
2527
}
2628

2729
void FHsolver::newIter() { data_.append(); }
@@ -31,10 +33,12 @@ void FHsolver::setRegularisation(double reg_p, double reg_d) {
3133
regul_.dual = reg_d;
3234
}
3335

36+
void FHsolver::setBlockSize(Int nb) { nb_ = nb; }
37+
3438
Int FHsolver::analyse(Symbolic& S, const std::vector<Int>& rows,
3539
const std::vector<Int>& ptr,
3640
const std::vector<Int>& signs) {
37-
Analyse an_obj(rows, ptr, signs, log_, data_);
41+
Analyse an_obj(rows, ptr, signs, nb_, log_, data_);
3842
return an_obj.run(S);
3943
}
4044

highs/ipm/hipo/factorhighs/FactorHiGHS.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ To add static regularisation when the pivots are selected, use
5252
setRegularisation(reg_p,reg_d) to choose values of primal and dual
5353
regularisation. If regularisation is already added to the matrix, ignore.
5454
55+
The default block size is 128. To set a different block size, use setBlockSize.
56+
Make sure that the block size used for the analyse and factorise phases are the
57+
same.
58+
5559
*/
5660

5761
namespace hipo {
@@ -61,6 +65,8 @@ class FHsolver {
6165
DataCollector data_;
6266
Regul regul_;
6367

68+
Int nb_ = 128; // block size
69+
6470
public:
6571
// Create object and initialise DataCollector
6672
FHsolver(const Log* log = nullptr);
@@ -89,6 +95,9 @@ class FHsolver {
8995
// Set values for static regularisation to be added when a pivot is selected.
9096
// If regularisation is already added to the matrix, ignore.
9197
void setRegularisation(double reg_p, double reg_d);
98+
99+
// Set the block size for dense linear algebra.
100+
void setBlockSize(Int nb);
92101
};
93102

94103
} // namespace hipo

highs/ipm/hipo/factorhighs/FactorHiGHSSettings.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ const Int kMaxIterRelax = 10;
4040
const Int kSnSizeRelax = 16;
4141

4242
// dense factorisation
43-
const Int kBlockSize = 128;
4443
const double kAlphaBK = 0.01; //(sqrt(17.0) + 1.0) / 8.0;
4544
const Int kBlockGrainSize = 1;
4645
const Int kBlockParallelThreshold = 5;

highs/ipm/hipo/factorhighs/Symbolic.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace hipo {
99

10-
Symbolic::Symbolic() : block_size_{kBlockSize} {}
10+
Symbolic::Symbolic() {}
1111

1212
void Symbolic::setParallel(bool par_tree, bool par_node) {
1313
parallel_tree_ = par_tree;
@@ -71,7 +71,7 @@ void Symbolic::print(const Log& log, bool verbose) const {
7171
log_stream << textline("Flops:") << sci(flops_, 0, 1) << '\n';
7272
if (verbose) {
7373
log_stream << textline("Sparse ops:") << sci(spops_, 0, 1) << '\n';
74-
log_stream << textline("critical ops:") << sci(critops_, 0, 1) << '\n';
74+
log_stream << textline("Critical ops:") << sci(critops_, 0, 1) << '\n';
7575
log_stream << textline("Max tree speedup:") << fix(flops_ / critops_, 0, 2)
7676
<< '\n';
7777
log_stream << textline("Artificial nz:") << sci(artificial_nz_, 0, 1)
@@ -87,7 +87,8 @@ void Symbolic::print(const Log& log, bool verbose) const {
8787
log_stream << textline("Sn size <= 10:") << integer(sn_size_10_, 0) << '\n';
8888
log_stream << textline("Sn size <= 100:") << integer(sn_size_100_, 0)
8989
<< '\n';
90-
log_stream << textline("Sn avg size:") << sci(n_, 0, 1) << '\n';
90+
log_stream << textline("Sn avg size:") << sci((double)n_ / sn_, 0, 1)
91+
<< '\n';
9192
}
9293

9394
log_stream << '\n';

0 commit comments

Comments
 (0)