#include "model/HighsHessianUtils.h"
#include <algorithm>
#include <cmath>
#include "lp_data/HighsModelUtils.h"
#include "util/HighsMatrixUtils.h"
#include "util/HighsSort.h"
using std::fabs;
HighsStatus assessHessian(HighsHessian& hessian, const HighsOptions& options) {
HighsStatus return_status = HighsStatus::kOk;
HighsStatus call_status;
return_status = interpretCallStatus(options.log_options,
assessHessianDimensions(options, hessian),
return_status, "assessHessianDimensions");
if (return_status == HighsStatus::kError) return return_status;
if (hessian.dim_ == 0) {
hessian.clear();
return HighsStatus::kOk;
}
if (hessian.start_[0]) {
highsLogUser(options.log_options, HighsLogType::kError,
"Hessian has nonzero value (%" HIGHSINT_FORMAT
") for the start of column 0\n",
hessian.start_[0]);
return HighsStatus::kError;
}
call_status = assessMatrix(options.log_options, "Hessian", hessian.dim_,
hessian.dim_, hessian.start_, hessian.index_,
hessian.value_, 0, kHighsInf);
return_status = interpretCallStatus(options.log_options, call_status,
return_status, "assessMatrix");
if (return_status == HighsStatus::kError) return return_status;
if (hessian.format_ == HessianFormat::kSquare) {
call_status = normaliseHessian(options, hessian);
return_status = interpretCallStatus(options.log_options, call_status,
return_status, "normaliseHessian");
if (return_status == HighsStatus::kError) return return_status;
}
call_status = extractTriangularHessian(options, hessian);
return_status =
interpretCallStatus(options.log_options, call_status, return_status,
"extractTriangularHessian");
if (return_status == HighsStatus::kError) return return_status;
call_status =
assessMatrix(options.log_options, "Hessian", hessian.dim_, hessian.dim_,
hessian.start_, hessian.index_, hessian.value_,
options.small_matrix_value, options.large_matrix_value);
return_status = interpretCallStatus(options.log_options, call_status,
return_status, "assessMatrix");
if (return_status == HighsStatus::kError) return return_status;
HighsInt hessian_num_nz = hessian.numNz();
if (hessian_num_nz) {
completeHessianDiagonal(options, hessian);
hessian_num_nz = hessian.numNz();
}
if ((HighsInt)hessian.index_.size() > hessian_num_nz)
hessian.index_.resize(hessian_num_nz);
if ((HighsInt)hessian.value_.size() > hessian_num_nz)
hessian.value_.resize(hessian_num_nz);
if (return_status != HighsStatus::kError) return_status = HighsStatus::kOk;
if (return_status != HighsStatus::kOk)
highsLogDev(options.log_options, HighsLogType::kInfo,
"assessHessian returns HighsStatus = %s\n",
highsStatusToString(return_status).c_str());
return return_status;
}
HighsStatus assessHessianDimensions(const HighsOptions& options,
HighsHessian& hessian) {
if (hessian.dim_ == 0) return HighsStatus::kOk;
vector<HighsInt> hessian_p_end;
const bool partitioned = false;
return assessMatrixDimensions(options.log_options, hessian.dim_, partitioned,
hessian.start_, hessian_p_end, hessian.index_,
hessian.value_);
}
void completeHessianDiagonal(const HighsOptions& options,
HighsHessian& hessian) {
HighsInt num_missing_diagonal_entries = 0;
const HighsInt dim = hessian.dim_;
const HighsInt num_nz = hessian.numNz();
for (HighsInt iCol = 0; iCol < dim; iCol++) {
HighsInt iEl = hessian.start_[iCol];
if (iEl < num_nz) {
if (hessian.index_[iEl] != iCol) num_missing_diagonal_entries++;
} else {
num_missing_diagonal_entries++;
}
}
if (num_missing_diagonal_entries > 0)
highsLogDev(options.log_options, HighsLogType::kInfo,
"Hessian has dimension %d and %d nonzeros: inserting %d zeros "
"onto the diagonal\n",
int(dim), int(num_nz), int(num_missing_diagonal_entries));
assert(num_missing_diagonal_entries >= dim - num_nz);
if (!num_missing_diagonal_entries) return;
const HighsInt new_num_nz = hessian.numNz() + num_missing_diagonal_entries;
HighsInt to_iEl = new_num_nz;
hessian.index_.resize(new_num_nz);
hessian.value_.resize(new_num_nz);
HighsInt next_start = hessian.numNz();
hessian.start_[dim] = to_iEl;
HighsInt num_missing_diagonal_entries_added = 0;
for (HighsInt iCol = dim - 1; iCol >= 0; iCol--) {
for (HighsInt iEl = next_start - 1; iEl > hessian.start_[iCol]; iEl--) {
assert(hessian.index_[iEl] != iCol);
to_iEl--;
hessian.index_[to_iEl] = hessian.index_[iEl];
hessian.value_[to_iEl] = hessian.value_[iEl];
}
bool no_diagonal_entry;
if (hessian.start_[iCol] < next_start) {
const HighsInt iEl = hessian.start_[iCol];
to_iEl--;
hessian.index_[to_iEl] = hessian.index_[iEl];
hessian.value_[to_iEl] = hessian.value_[iEl];
no_diagonal_entry = hessian.index_[iEl] != iCol;
} else {
no_diagonal_entry = true;
}
if (no_diagonal_entry) {
to_iEl--;
hessian.index_[to_iEl] = iCol;
hessian.value_[to_iEl] = 0;
num_missing_diagonal_entries_added++;
assert(num_missing_diagonal_entries_added <=
num_missing_diagonal_entries);
}
next_start = hessian.start_[iCol];
hessian.start_[iCol] = to_iEl;
}
assert(to_iEl == 0);
}
bool okHessianDiagonal(const HighsOptions& options, HighsHessian& hessian,
const ObjSense sense) {
double min_diagonal_value = kHighsInf;
double max_diagonal_value = -kHighsInf;
const HighsInt dim = hessian.dim_;
const HighsInt sense_sign = (HighsInt)sense;
HighsInt num_illegal_diagonal_value = 0;
for (HighsInt iCol = 0; iCol < dim; iCol++) {
double diagonal_value = 0;
HighsInt iEl = hessian.start_[iCol];
assert(hessian.index_[iEl] == iCol);
diagonal_value = sense_sign * hessian.value_[iEl];
min_diagonal_value = std::min(diagonal_value, min_diagonal_value);
max_diagonal_value = std::max(diagonal_value, max_diagonal_value);
if (diagonal_value < 0) num_illegal_diagonal_value++;
}
const bool certainly_not_positive_semidefinite =
num_illegal_diagonal_value > 0;
if (certainly_not_positive_semidefinite) {
if (sense == ObjSense::kMinimize) {
highsLogUser(options.log_options, HighsLogType::kError,
"Hessian has %" HIGHSINT_FORMAT
" diagonal entries in [%g, 0) so is not positive "
"semidefinite for minimization\n",
num_illegal_diagonal_value, min_diagonal_value);
} else {
highsLogUser(options.log_options, HighsLogType::kError,
"Hessian has %" HIGHSINT_FORMAT
" diagonal entries in (0, %g] so is not negative "
"semidefinite for maximization\n",
num_illegal_diagonal_value, -min_diagonal_value);
}
}
return !certainly_not_positive_semidefinite;
}
HighsStatus extractTriangularHessian(const HighsOptions& options,
HighsHessian& hessian) {
HighsStatus return_status = HighsStatus::kOk;
const HighsInt dim = hessian.dim_;
HighsInt nnz = 0;
for (HighsInt iCol = 0; iCol < dim; iCol++) {
const HighsInt nnz0 = nnz;
for (HighsInt iEl = hessian.start_[iCol]; iEl < hessian.start_[iCol + 1];
iEl++) {
HighsInt iRow = hessian.index_[iEl];
if (iRow < iCol) continue;
hessian.index_[nnz] = iRow;
hessian.value_[nnz] = hessian.value_[iEl];
if (iRow == iCol && nnz > nnz0) {
hessian.index_[nnz] = hessian.index_[nnz0];
hessian.value_[nnz] = hessian.value_[nnz0];
hessian.index_[nnz0] = iRow;
hessian.value_[nnz0] = hessian.value_[iEl];
}
nnz++;
}
hessian.start_[iCol] = nnz0;
}
const HighsInt num_ignored_nz = hessian.start_[dim] - nnz;
assert(num_ignored_nz >= 0);
if (num_ignored_nz) {
if (hessian.format_ == HessianFormat::kTriangular) {
highsLogUser(options.log_options, HighsLogType::kWarning,
"Ignored %" HIGHSINT_FORMAT
" entries of Hessian in opposite triangle\n",
num_ignored_nz);
return_status = HighsStatus::kWarning;
}
hessian.start_[dim] = nnz;
}
assert(hessian.start_[dim] == nnz);
hessian.format_ = HessianFormat::kTriangular;
return return_status;
}
void triangularToSquareHessian(const HighsHessian& hessian,
vector<HighsInt>& start, vector<HighsInt>& index,
vector<double>& value) {
const HighsInt dim = hessian.dim_;
if (dim <= 0) {
start.assign(1, 0);
return;
}
assert(hessian.format_ == HessianFormat::kTriangular);
const HighsInt nnz = hessian.start_[dim];
const HighsInt square_nnz = nnz + (nnz - dim);
start.resize(dim + 1);
index.resize(square_nnz);
value.resize(square_nnz);
vector<HighsInt> length;
length.assign(dim, 0);
for (HighsInt iCol = 0; iCol < dim; iCol++) {
HighsInt iRow = hessian.index_[hessian.start_[iCol]];
assert(iRow == iCol);
length[iCol]++;
for (HighsInt iEl = hessian.start_[iCol] + 1;
iEl < hessian.start_[iCol + 1]; iEl++) {
HighsInt iRow = hessian.index_[iEl];
assert(iRow > iCol);
length[iRow]++;
length[iCol]++;
}
}
start[0] = 0;
for (HighsInt iCol = 0; iCol < dim; iCol++)
start[iCol + 1] = start[iCol] + length[iCol];
assert(square_nnz == start[dim]);
for (HighsInt iCol = 0; iCol < dim; iCol++) {
HighsInt iEl = hessian.start_[iCol];
HighsInt iRow = hessian.index_[iEl];
HighsInt toEl = start[iCol];
index[toEl] = iRow;
value[toEl] = hessian.value_[iEl];
start[iCol]++;
for (HighsInt iEl = hessian.start_[iCol] + 1;
iEl < hessian.start_[iCol + 1]; iEl++) {
HighsInt iRow = hessian.index_[iEl];
HighsInt toEl = start[iRow];
index[toEl] = iCol;
value[toEl] = hessian.value_[iEl];
start[iRow]++;
toEl = start[iCol];
index[toEl] = iRow;
value[toEl] = hessian.value_[iEl];
start[iCol]++;
}
}
start[0] = 0;
for (HighsInt iCol = 0; iCol < dim; iCol++)
start[iCol + 1] = start[iCol] + length[iCol];
}
HighsStatus normaliseHessian(const HighsOptions& options,
HighsHessian& hessian) {
assert(hessian.format_ == HessianFormat::kSquare);
HighsStatus return_status = HighsStatus::kOk;
const HighsInt dim = hessian.dim_;
const HighsInt hessian_num_nz = hessian.start_[dim];
if (hessian_num_nz <= 0) return HighsStatus::kOk;
bool warning_found = false;
HighsHessian transpose;
transpose.dim_ = dim;
transpose.start_.resize(dim + 1);
transpose.index_.resize(hessian_num_nz);
transpose.value_.resize(hessian_num_nz);
vector<HighsInt> qr_length;
qr_length.assign(dim, 0);
for (HighsInt iEl = 0; iEl < hessian_num_nz; iEl++)
qr_length[hessian.index_[iEl]]++;
transpose.start_[0] = 0;
for (HighsInt iRow = 0; iRow < dim; iRow++)
transpose.start_[iRow + 1] = transpose.start_[iRow] + qr_length[iRow];
for (HighsInt iCol = 0; iCol < dim; iCol++) {
for (HighsInt iEl = hessian.start_[iCol]; iEl < hessian.start_[iCol + 1];
iEl++) {
HighsInt iRow = hessian.index_[iEl];
HighsInt iRowEl = transpose.start_[iRow];
transpose.index_[iRowEl] = iCol;
transpose.value_[iRowEl] = hessian.value_[iEl];
transpose.start_[iRow]++;
}
}
transpose.start_[0] = 0;
for (HighsInt iRow = 0; iRow < dim; iRow++)
transpose.start_[iRow + 1] = transpose.start_[iRow] + qr_length[iRow];
HighsHessian normalised;
normalised.format_ = HessianFormat::kSquare;
HighsInt normalised_num_nz = 0;
HighsInt normalised_size = hessian_num_nz;
normalised.dim_ = dim;
normalised.start_.resize(dim + 1);
normalised.index_.resize(normalised_size);
normalised.value_.resize(normalised_size);
vector<double> column_value;
vector<HighsInt> column_index;
column_index.resize(dim);
column_value.assign(dim, 0.0);
const bool check_column_value_zero = false;
const double small_matrix_value = 0;
HighsInt num_small_values = 0;
double max_small_value = 0;
double min_small_value = kHighsInf;
normalised.start_[0] = 0;
for (HighsInt iCol = 0; iCol < dim; iCol++) {
HighsInt column_num_nz = 0;
for (HighsInt iEl = hessian.start_[iCol]; iEl < hessian.start_[iCol + 1];
iEl++) {
HighsInt iRow = hessian.index_[iEl];
column_value[iRow] = hessian.value_[iEl];
column_index[column_num_nz] = iRow;
column_num_nz++;
}
for (HighsInt iEl = transpose.start_[iCol];
iEl < transpose.start_[iCol + 1]; iEl++) {
HighsInt iRow = transpose.index_[iEl];
if (column_value[iRow]) {
column_value[iRow] += transpose.value_[iEl];
} else {
column_value[iRow] = transpose.value_[iEl];
column_index[column_num_nz] = iRow;
column_num_nz++;
}
}
if (normalised_num_nz + column_num_nz > normalised_size) {
normalised_size =
std::max(normalised_num_nz + column_num_nz, 2 * normalised_size);
normalised.index_.resize(normalised_size);
normalised.value_.resize(normalised_size);
}
for (HighsInt ix = 0; ix < column_num_nz; ix++) {
HighsInt iRow = column_index[ix];
double value = 0.5 * column_value[iRow];
double abs_value = std::fabs(value);
bool ok_value = abs_value > small_matrix_value;
if (!ok_value) {
value = 0;
if (max_small_value < abs_value) max_small_value = abs_value;
if (min_small_value > abs_value) min_small_value = abs_value;
num_small_values++;
}
column_value[iRow] = value;
}
const HighsInt kDimTolerance = 10;
const double kDensityTolerance = 0.1;
const double density = (1.0 * column_num_nz) / (1.0 * dim);
HighsInt to_ix = dim;
const bool exploit_sparsity =
dim > kDimTolerance && density < kDensityTolerance;
if (exploit_sparsity) {
to_ix = column_num_nz;
sortSetData(column_num_nz, column_index, NULL, NULL);
} else {
to_ix = dim;
}
for (HighsInt ix = 0; ix < to_ix; ix++) {
HighsInt iRow;
if (exploit_sparsity) {
iRow = column_index[ix];
} else {
iRow = ix;
}
double value = column_value[iRow];
if (value) {
normalised.index_[normalised_num_nz] = iRow;
normalised.value_[normalised_num_nz] = value;
normalised_num_nz++;
column_value[iRow] = 0;
}
}
if (check_column_value_zero) {
for (HighsInt iRow = 0; iRow < dim; iRow++)
assert(column_value[iRow] == 0);
}
normalised.start_[iCol + 1] = normalised_num_nz;
}
if (num_small_values) {
highsLogUser(options.log_options, HighsLogType::kWarning,
"Normalised Hessian contains %" HIGHSINT_FORMAT
" |values| in [%g, %g] "
"less than %g: ignored\n",
num_small_values, min_small_value, max_small_value,
small_matrix_value);
warning_found = true;
}
hessian = normalised;
assert(hessian.format_ == HessianFormat::kSquare);
if (warning_found)
return_status = HighsStatus::kWarning;
else
return_status = HighsStatus::kOk;
return return_status;
}
void completeHessian(const HighsInt full_dim, HighsHessian& hessian) {
assert(hessian.dim_ <= full_dim);
if (hessian.dim_ == full_dim) return;
HighsInt nnz = hessian.numNz();
hessian.exactResize();
for (HighsInt iCol = hessian.dim_; iCol < full_dim; iCol++) {
hessian.index_.push_back(iCol);
hessian.value_.push_back(0);
nnz++;
hessian.start_.push_back(nnz);
}
hessian.dim_ = full_dim;
assert(HighsInt(hessian.start_.size()) == hessian.dim_ + 1);
}
void reportHessian(const HighsLogOptions& log_options, const HighsInt dim,
const HighsInt num_nz, const HighsInt* start,
const HighsInt* index, const double* value) {
if (dim <= 0) return;
highsLogUser(log_options, HighsLogType::kInfo,
"Hessian Index Value\n");
for (HighsInt col = 0; col < dim; col++) {
highsLogUser(log_options, HighsLogType::kInfo,
" %8" HIGHSINT_FORMAT " Start %10" HIGHSINT_FORMAT "\n",
col, start[col]);
HighsInt to_el = (col < dim - 1 ? start[col + 1] : num_nz);
for (HighsInt el = start[col]; el < to_el; el++)
highsLogUser(log_options, HighsLogType::kInfo,
" %8" HIGHSINT_FORMAT " %12g\n", index[el],
value[el]);
}
highsLogUser(log_options, HighsLogType::kInfo,
" Start %10" HIGHSINT_FORMAT "\n", num_nz);
}
void userScaleHessian(HighsHessian& hessian, HighsUserScaleData& data,
const bool apply) {
data.num_infinite_hessian_values = 0;
if (!hessian.dim_) return;
const HighsInt user_objective_scale = data.user_objective_scale;
const HighsInt user_bound_scale = data.user_bound_scale;
if (!user_objective_scale && !user_bound_scale) return;
double objective_scale_value = std::pow(2, user_objective_scale);
double bound_scale_value = std::pow(2, -user_bound_scale);
for (HighsInt iEl = 0; iEl < hessian.start_[hessian.dim_]; iEl++) {
double value =
hessian.value_[iEl] * objective_scale_value * bound_scale_value;
if (std::abs(value) > data.infinite_cost)
data.num_infinite_hessian_values++;
if (apply) hessian.value_[iEl] = value;
}
}