Skip to content

Commit 28245d6

Browse files
committed
Adding cost scaling without breaking unit tests!
1 parent 53d7df8 commit 28245d6

File tree

7 files changed

+111
-141
lines changed

7 files changed

+111
-141
lines changed

check/TestBasisSolves.cpp

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -617,19 +617,47 @@ TEST_CASE("Kappa", "[highs_basis_solves]") {
617617
}
618618

619619
TEST_CASE("scaling-kappa", "[highs_basis_solves]") {
620-
std::string model;
621-
// model = "chip";
622-
// model = "avgas";
623-
model = "adlittle";
624-
std::string filename =
625-
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
626-
627620
Highs highs;
628621
// highs.setOptionValue("output_flag", dev_run);
622+
highs.setOptionValue("simplex_scale_strategy", kSimplexScaleStrategyMaxValue);
623+
highs.setOptionValue("log_dev_level", kHighsLogDevLevelInfo);
624+
const bool model_test = false;
625+
if (model_test) {
626+
std::string model;
627+
// model = "chip";
628+
// model = "avgas";
629+
model = "adlittle";
630+
std::string filename =
631+
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
632+
// Read the LP given by filename
633+
highs.readModel(filename);
634+
highs.run();
629635

630-
// Read the LP given by filename
631-
highs.readModel(filename);
632-
633-
highs.setOptionValue("simplex_scale_strategy", kSimplexScaleStrategyMaxValue0157);
636+
/*
637+
highs.clearSolver();
638+
highs.setOptionValue("simplex_scale_strategy", kSimplexScaleStrategyMaxValueMatrixAndCost);
634639
highs.run();
640+
*/
641+
}
642+
const bool lp_test = false;
643+
if (lp_test) {
644+
const double cost = 1e6;
645+
HighsLp lp;
646+
lp.num_col_ = 2;
647+
lp.num_row_ = 2;
648+
lp.offset_ = 1e-4;
649+
lp.col_cost_ = {-4.1 * cost, -7.3 * cost};
650+
lp.col_lower_ = {0, 0};
651+
lp.col_upper_ = {10, 10};
652+
lp.row_lower_ = {-kHighsInf, -2};
653+
lp.row_upper_ = {6, kHighsInf};
654+
lp.a_matrix_.start_ = {0, 2, 4};
655+
lp.a_matrix_.index_ = {0, 1, 0, 1};
656+
lp.a_matrix_.value_ = {0.95, 0.98, 0.97, -1.01};
657+
highs.passModel(lp);
658+
highs.setOptionValue("presolve", kHighsOffString);
659+
highs.setOptionValue("simplex_scale_strategy",
660+
kSimplexScaleStrategyMaxValueMatrixAndCost);
661+
highs.run();
662+
}
635663
}

check/TestLpValidation.cpp

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -108,48 +108,6 @@ TEST_CASE("LP-dimension-validation", "[highs_data]") {
108108
lp.a_matrix_.index_[1] = 1;
109109
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
110110

111-
/*
112-
if (dev_run) printf("Give valid scale_.strategy\n");
113-
lp.scale_.strategy = kSimplexScaleStrategyOff;
114-
REQUIRE(highs.passModel(lp) == HighsStatus::kError);
115-
116-
if (dev_run) printf("Give valid scale_.num_col\n");
117-
lp.scale_.num_col = 0;
118-
REQUIRE(highs.passModel(lp) == HighsStatus::kError);
119-
120-
if (dev_run) printf("Give valid scale_.num_row\n");
121-
lp.scale_.num_row = 0;
122-
REQUIRE(highs.passModel(lp) == HighsStatus::kError);
123-
124-
if (dev_run) printf("Give valid scale_.col.size()\n");
125-
lp.scale_.col.resize(0);
126-
REQUIRE(highs.passModel(lp) == HighsStatus::kError);
127-
128-
if (dev_run) printf("Give valid scale_.row.size()\n");
129-
lp.scale_.row.resize(0);
130-
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
131-
132-
if (dev_run) printf("Set scale_.strategy =
133-
kSimplexScaleStrategyMaxValue015\n"); lp.scale_.strategy =
134-
kSimplexScaleStrategyMaxValue015; REQUIRE(highs.passModel(lp) ==
135-
HighsStatus::kError);
136-
137-
if (dev_run) printf("Give valid scale_.num_col\n");
138-
lp.scale_.num_col = true_num_col;
139-
REQUIRE(highs.passModel(lp) == HighsStatus::kError);
140-
141-
if (dev_run) printf("Give valid scale_.num_row\n");
142-
lp.scale_.num_row = true_num_row;
143-
REQUIRE(highs.passModel(lp) == HighsStatus::kError);
144-
145-
if (dev_run) printf("Give valid scale_.col.size()\n");
146-
lp.scale_.col.resize(true_num_col);
147-
REQUIRE(highs.passModel(lp) == HighsStatus::kError);
148-
149-
if (dev_run) printf("Give valid scale_.row.size()\n");
150-
lp.scale_.row.resize(true_num_row);
151-
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
152-
*/
153111
}
154112

155113
TEST_CASE("LP-validation", "[highs_data]") {

highs/lp_data/HConst.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
#include "util/HighsInt.h"
1919

20-
const bool kSimplexScaleDev = false;
20+
const bool kSimplexScaleDev = true;
21+
const bool kSimplexScaleDevReport = kSimplexScaleDev && false;
2122

2223
const std::string kHighsCopyrightStatement =
2324
"Copyright (c) 2025 HiGHS under MIT licence terms";
@@ -55,9 +56,8 @@ enum SimplexScaleStrategy {
5556
kSimplexScaleStrategyEquilibration, // 2
5657
kSimplexScaleStrategyForcedEquilibration, // 3
5758
kSimplexScaleStrategyMaxValue, // 4
58-
kSimplexScaleStrategyMaxValue015 = kSimplexScaleStrategyMaxValue,
59-
kSimplexScaleStrategyMaxValue0157 = kSimplexScaleStrategyMaxValue,
60-
kSimplexScaleStrategyMax = kSimplexScaleStrategyMaxValue
59+
kSimplexScaleStrategyMaxValueMatrixAndCost, // 5
60+
kSimplexScaleStrategyMax = kSimplexScaleStrategyMaxValueMatrixAndCost
6161
};
6262

6363
enum HighsDebugLevel {

highs/lp_data/HighsLp.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,12 @@ void HighsLp::unapplyScale() {
281281
assert(scale.has_scaling);
282282
// Unapply the scaling to the bounds, costs and matrix, and record
283283
// that it has been unapplied
284+
assert(scale.cost);
285+
const double unscale_cost_mu = 1.0 / scale.cost;
284286
for (HighsInt iCol = 0; iCol < this->num_col_; iCol++) {
285287
this->col_lower_[iCol] *= scale.col[iCol];
286288
this->col_upper_[iCol] *= scale.col[iCol];
287-
this->col_cost_[iCol] /= scale.col[iCol];
289+
this->col_cost_[iCol] /= (unscale_cost_mu * scale.col[iCol]);
288290
}
289291
for (HighsInt iRow = 0; iRow < this->num_row_; iRow++) {
290292
this->row_lower_[iRow] /= scale.row[iRow];

highs/simplex/HApp.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) {
155155
// considering computing scaling factors if there are none - and
156156
// then move to EKK
157157
considerSimplexScaling(options, incumbent_lp);
158-
if (kSimplexScaleDev)
158+
if (kSimplexScaleDevReport)
159159
incumbent_lp.scale_.print("grepSimplexScaling",
160160
"After scaling, " + incumbent_lp.model_name_ +
161161
"," + incumbent_lp.origin_name_);

highs/simplex/HEkk.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3613,10 +3613,11 @@ void HEkk::testBasisCondition(const HighsLp& lp,
36133613
exact = false;
36143614
double approx_kappa = this->computeBasisCondition(lp, exact);
36153615
approx_kappa_tt += timer_->read();
3616-
highsLogUser(options_->log_options, HighsLogType::kInfo,
3617-
"getKappa,%s,%g,%g,%g,%g,%s,%s\n", lp.model_name_.c_str(),
3618-
exact_kappa, exact_kappa_tt, approx_kappa, approx_kappa_tt,
3619-
message.c_str(), lp.origin_name_.c_str());
3616+
if (kSimplexScaleDevReport)
3617+
highsLogUser(options_->log_options, HighsLogType::kInfo,
3618+
"getKappa,%s,%g,%g,%g,%g,%s,%s\n", lp.model_name_.c_str(),
3619+
exact_kappa, exact_kappa_tt, approx_kappa, approx_kappa_tt,
3620+
message.c_str(), lp.origin_name_.c_str());
36203621
}
36213622

36223623
double HEkk::computeBasisCondition(const HighsLp& lp, const bool exact) const {

highs/simplex/HSimplex.cpp

Lines changed: 59 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -484,16 +484,18 @@ void simplexScaleLp(const HighsOptions& options, HighsLp& lp,
484484
max_cost = std::max(abs_cost, max_cost);
485485
}
486486
}
487-
printf("grepSimplexRangeTxt HighsScale: costs in [%g, %g]; matrix in [%g, %g]; %s: %s\n",
488-
min_cost, max_cost,
489-
original_matrix_min_value, original_matrix_max_value,
490-
lp.model_name_.c_str(),
491-
lp.origin_name_.c_str());
492-
printf("grepSimplexRangeCsv,%g,%g,%g,%g,%s,%s\n",
493-
min_cost, max_cost,
494-
original_matrix_min_value, original_matrix_max_value,
495-
lp.model_name_.c_str(),
496-
lp.origin_name_.c_str());
487+
if (kSimplexScaleDevReport) {
488+
printf("grepSimplexRangeTxt HighsScale: costs in [%g, %g]; matrix in [%g, %g]; %s: %s\n",
489+
min_cost, max_cost,
490+
original_matrix_min_value, original_matrix_max_value,
491+
lp.model_name_.c_str(),
492+
lp.origin_name_.c_str());
493+
printf("grepSimplexRangeCsv,%g,%g,%g,%g,%s,%s\n",
494+
min_cost, max_cost,
495+
original_matrix_min_value, original_matrix_max_value,
496+
lp.model_name_.c_str(),
497+
lp.origin_name_.c_str());
498+
}
497499
}
498500
// Possibly force scaling, otherwise base the decision on the range
499501
// of values in the matrix, values that will be used later for
@@ -504,21 +506,20 @@ void simplexScaleLp(const HighsOptions& options, HighsLp& lp,
504506
no_scaling_original_matrix_min_value) &&
505507
(original_matrix_max_value <=
506508
no_scaling_original_matrix_max_value);
509+
HighsScale& scale = lp.scale_;
507510
bool scaled_matrix = false;
508511
if (no_scaling) {
509-
// No matrix scaling, but possible cost scaling
510-
if (options.highs_analysis_level)
511-
highsLogDev(options.log_options, HighsLogType::kInfo,
512-
"Scaling: Matrix has [min, max] values of [%g, %g] within "
513-
"[%g, %g] so no scaling performed\n",
514-
original_matrix_min_value, original_matrix_max_value,
515-
no_scaling_original_matrix_min_value,
516-
no_scaling_original_matrix_max_value);
512+
// No matrix scaling
513+
highsLogDev(options.log_options, HighsLogType::kInfo,
514+
"Scaling: Matrix has [min, max] values of [%g, %g] within "
515+
"[%g, %g] so no scaling performed\n",
516+
original_matrix_min_value, original_matrix_max_value,
517+
no_scaling_original_matrix_min_value,
518+
no_scaling_original_matrix_max_value);
517519
} else {
518520
// Try scaling, so assign unit factors - partly because initial
519521
// factors may be assumed by the scaling method, but also because
520522
// scaling factors may not be computed for empty rows/columns
521-
HighsScale& scale = lp.scale_;
522523
scale.col.assign(numCol, 1);
523524
scale.row.assign(numRow, 1);
524525
const bool equilibration_scaling =
@@ -549,17 +550,18 @@ void simplexScaleLp(const HighsOptions& options, HighsLp& lp,
549550
scale.cost = 1.0;
550551
lp.is_scaled_ = true;
551552
}
552-
bool scaled_costs = false;
553-
if (kSimplexScaleDev &&
554-
use_scale_strategy == kSimplexScaleStrategyMaxValue0157) {
555-
// Consider cost scaling
556-
simplexScaleCost(options, lp);
557-
scaled_costs = scale.cost != 1.0;
558-
lp.is_scaled_ = true;
559-
}
560-
// If neither costs nor matrix are scaled, then clear the scaling
561-
if (!scaled_costs && !scaled_matrix) lp.clearScaling();
562553
}
554+
bool scaled_costs = false;
555+
if (kSimplexScaleDev &&
556+
use_scale_strategy == kSimplexScaleStrategyMaxValueMatrixAndCost) {
557+
// Consider cost scaling
558+
simplexScaleCost(options, lp);
559+
scaled_costs = scale.cost != 1.0;
560+
scale.has_scaling = true;
561+
lp.is_scaled_ = true;
562+
}
563+
// If neither costs nor matrix are scaled, then clear the scaling
564+
if (!scaled_costs && !scaled_matrix) lp.clearScaling();
563565
// Record the scaling strategy used
564566
lp.scale_.strategy = use_scale_strategy;
565567
}
@@ -898,26 +900,7 @@ bool maxValueScaleMatrix(const HighsOptions& options, HighsLp& lp,
898900
vector<HighsInt>& Aindex = lp.a_matrix_.index_;
899901
vector<double>& Avalue = lp.a_matrix_.value_;
900902

901-
assert(options.simplex_scale_strategy == kSimplexScaleStrategyMaxValue);
902-
assert(kSimplexScaleStrategyMaxValue015 == kSimplexScaleStrategyMaxValue);
903-
assert(kSimplexScaleStrategyMaxValue0157 == kSimplexScaleStrategyMaxValue);
904-
905-
// The 015(7) values refer to bit settings in FICO's scaling options.
906-
// Specifically
907-
//
908-
// 0: Row scaling
909-
//
910-
// 1: Column scaling
911-
//
912-
// 5: Scale by maximum element
913-
//
914-
// 7: Scale objective function for the simplex method
915-
//
916-
// Note that 7 is not yet implemented, so
917-
// kSimplexScaleStrategyMaxValue015 and
918-
// kSimplexScaleStrategyMaxValue0157 are equivalent. However, cost
919-
// scaling could be well worth adding, now that the unscaled problem
920-
// can be solved using scaled NLA
903+
assert(options.simplex_scale_strategy >= kSimplexScaleStrategyMaxValue);
921904

922905
const double log2 = log(2.0);
923906
const double max_allow_scale = pow(2.0, options.allowed_matrix_scale_factor);
@@ -1093,40 +1076,38 @@ void simplexScaleCost(const HighsOptions& options, HighsLp& lp) {
10931076
// Scale the costs by no less than minAlwCostScale
10941077
double max_allowed_cost_scale = pow(2.0, options.allowed_cost_scale_factor);
10951078
double max_nonzero_cost = 0;
1096-
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) {
1097-
if (lp.col_cost_[iCol]) {
1079+
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++)
1080+
if (lp.col_cost_[iCol])
10981081
max_nonzero_cost = max(fabs(lp.col_cost_[iCol]), max_nonzero_cost);
1099-
}
1100-
}
1101-
// Scaling the costs up effectively increases the dual tolerance to
1102-
// which the problem is solved - so, if the max cost is small the
1103-
// scaling factor pushes it up by a power of 2 so it's close to 1
1104-
// Scaling the costs down effectively decreases the dual tolerance
1105-
// to which the problem is solved - so this can't be done too much
1106-
cost_scale = 1;
1107-
const double ln2 = log(2.0);
1108-
// Scale if the max cost is positive and outside the range [1/16, 16]
1109-
if ((max_nonzero_cost > 0) &&
1110-
((max_nonzero_cost < (1.0 / 16)) || (max_nonzero_cost > 16))) {
1082+
// Scaling the costs up effectively means that the problem is solved
1083+
// to a smaller dual tolerance, which seems pointless. HiGHS will
1084+
// warn about excessively small costs, and users can set
1085+
// user_objective_scale to a positive value.
1086+
//
1087+
// Scaling the costs down effectively means that the problem is
1088+
// solved to a larger dual tolerance, which may require clean-up
1089+
// iterations when the scaling is removed after solving the scaled
1090+
// problem
1091+
cost_scale = 1.0;
1092+
// Scale if the max cost is greater than 16
1093+
if (max_nonzero_cost > 16) {
11111094
cost_scale = max_nonzero_cost;
1112-
cost_scale = pow(2.0, floor(log(cost_scale) / ln2 + 0.5));
1095+
cost_scale = pow(2.0, floor(log(cost_scale) / log(2.0) + 0.5));
11131096
cost_scale = min(cost_scale, max_allowed_cost_scale);
11141097
}
1115-
if (cost_scale == 1) {
1116-
highsLogUser(options.log_options, HighsLogType::kInfo,
1117-
"LP cost vector not scaled down: max cost is %g\n",
1118-
max_nonzero_cost);
1119-
return;
1120-
}
1121-
// Scale the costs (and record of max_nonzero_cost) by cost_scale, being at
1122-
// most max_allowed_cost_scale
1123-
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) {
1124-
lp.col_cost_[iCol] /= cost_scale;
1098+
if (cost_scale == 1.0) {
1099+
highsLogDev(options.log_options, HighsLogType::kInfo,
1100+
"LP cost vector not scaled down: max cost is %g\n",
1101+
max_nonzero_cost);
1102+
} else {
1103+
// Scale the costs (and record of max_nonzero_cost) by cost_scale, being at
1104+
// most max_allowed_cost_scale
1105+
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++)
1106+
lp.col_cost_[iCol] /= cost_scale;
1107+
highsLogDev(options.log_options, HighsLogType::kInfo,
1108+
"LP cost vector scaled down by %g: max scaled cost is %g\n", cost_scale,
1109+
max_nonzero_cost/cost_scale);
11251110
}
1126-
max_nonzero_cost /= cost_scale;
1127-
highsLogUser(options.log_options, HighsLogType::kInfo,
1128-
"LP cost vector scaled down by %g: max cost is %g\n", cost_scale,
1129-
max_nonzero_cost);
11301111
}
11311112

11321113
void simplexUnscaleCost(HighsLp& lp) {

0 commit comments

Comments
 (0)