From 4309d7dbb0aa1ab7ab535131e14b5c713079cd5c Mon Sep 17 00:00:00 2001 From: SM Harwood Date: Wed, 4 Feb 2026 14:39:05 -0500 Subject: [PATCH 1/7] Add Highs::changeRowBoundsInterfaceUnchecked --- highs/Highs.h | 6 ++++++ highs/lp_data/HighsInterface.cpp | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/highs/Highs.h b/highs/Highs.h index 51c59115f5..b019d4a95e 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1711,9 +1711,15 @@ class Highs { HighsStatus changeColBoundsInterface(HighsIndexCollection& index_collection, const double* usr_col_lower, const double* usr_col_upper); + // Interface to change row bounds in a general (and safe) way HighsStatus changeRowBoundsInterface(HighsIndexCollection& index_collection, const double* usr_row_lower, const double* usr_row_upper); + // Interface to change row bounds without some safety checks, + // but with potential performance boosts + HighsStatus changeRowBoundsInterfaceUnchecked( + HighsIndexCollection& index_collection, + std::vector& usr_row_lower, std::vector& usr_row_upper); void changeCoefficientInterface(const HighsInt ext_row, const HighsInt ext_col, const double ext_new_value); diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 007605940f..f966fccfed 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -1027,16 +1027,23 @@ HighsStatus Highs::changeRowBoundsInterface( sortSetData(index_collection.set_num_entries_, index_collection.set_, lower, upper, NULL, local_rowLower.data(), local_rowUpper.data(), NULL); + return changeRowBoundsInterfaceUnchecked(index_collection, local_rowLower, + local_rowUpper); +} + +HighsStatus Highs::changeRowBoundsInterfaceUnchecked( + HighsIndexCollection& index_collection, std::vector& lower, + std::vector& upper) { HighsStatus return_status = HighsStatus::kOk; - return_status = interpretCallStatus( - options_.log_options, - assessBounds(options_, "row", 0, index_collection, local_rowLower, - local_rowUpper, options_.infinite_bound), - return_status, "assessBounds"); + return_status = + interpretCallStatus(options_.log_options, + assessBounds(options_, "row", 0, index_collection, + lower, upper, options_.infinite_bound), + return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; HighsLp& lp = model_.lp_; - changeLpRowBounds(lp, index_collection, local_rowLower, local_rowUpper); + changeLpRowBounds(lp, index_collection, lower, upper); // Update HiGHS basis status and (any) simplex move status of // nonbasic variables whose bounds have changed setNonbasicStatusInterface(index_collection, false); From c0e0334f3f568b7525483c22fe5893994eb3f834 Mon Sep 17 00:00:00 2001 From: SM Harwood Date: Wed, 4 Feb 2026 14:44:28 -0500 Subject: [PATCH 2/7] Use Highs::changeRowBoundsInterfaceUnchecked in changeRowBounds --- highs/lp_data/Highs.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 74403945cb..21089cfb1a 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -3057,8 +3057,11 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries, return analyseSetCreateError(options_.log_options, "changeRowsBounds", create_error, true, num_set_entries, local_set.data(), model_.lp_.num_row_); - HighsStatus call_status = changeRowBoundsInterface( - index_collection, local_lower.data(), local_upper.data()); + // Since we have already done the safety checks that would take place in + // changeRowBoundsInterface and created local copies of lower/upper, we can + // use the "unchecked" version + HighsStatus call_status = changeRowBoundsInterfaceUnchecked( + index_collection, local_lower, local_upper); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus(options_.log_options, call_status, return_status, "changeRowBounds"); From b3aaef9b129ce629019e0eb4f0ca9db6312288cc Mon Sep 17 00:00:00 2001 From: SM Harwood Date: Wed, 4 Feb 2026 16:12:41 -0500 Subject: [PATCH 3/7] Add Highs::changeColBoundsInterfaceUnchecked --- highs/Highs.h | 6 ++++++ highs/lp_data/HighsInterface.cpp | 13 ++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/highs/Highs.h b/highs/Highs.h index b019d4a95e..c20b23e3db 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1708,9 +1708,15 @@ class Highs { const double* usr_col_cost); bool feasibleWrtBounds(const bool columns = true) const; + // Interface to change column bounds in a general (and safe) way HighsStatus changeColBoundsInterface(HighsIndexCollection& index_collection, const double* usr_col_lower, const double* usr_col_upper); + // Interface to change column bounds without some safety checks, + // but with potential performance boosts + HighsStatus changeColBoundsInterfaceUnchecked( + HighsIndexCollection& index_collection, + std::vector& usr_col_lower, std::vector& usr_col_upper); // Interface to change row bounds in a general (and safe) way HighsStatus changeRowBoundsInterface(HighsIndexCollection& index_collection, const double* usr_row_lower, diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index f966fccfed..49d9c2cfb2 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -976,16 +976,23 @@ HighsStatus Highs::changeColBoundsInterface( sortSetData(index_collection.set_num_entries_, index_collection.set_, col_lower, col_upper, NULL, local_colLower.data(), local_colUpper.data(), NULL); + return changeColBoundsInterfaceUnchecked(index_collection, local_colLower, + local_colUpper); +} + +HighsStatus Highs::changeColBoundsInterfaceUnchecked( + HighsIndexCollection& index_collection, std::vector& col_lower, + std::vector& col_upper) { HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus( options_.log_options, - assessBounds(options_, "col", 0, index_collection, local_colLower, - local_colUpper, options_.infinite_bound), + assessBounds(options_, "col", 0, index_collection, col_lower, col_upper, + options_.infinite_bound), return_status, "assessBounds"); if (return_status == HighsStatus::kError) return return_status; HighsLp& lp = model_.lp_; - changeLpColBounds(lp, index_collection, local_colLower, local_colUpper); + changeLpColBounds(lp, index_collection, col_lower, col_upper); // Update HiGHS basis status and (any) simplex move status of // nonbasic variables whose bounds have changed setNonbasicStatusInterface(index_collection, true); From baedcff27363a202478c2dcc5aab3fe96cd2c9d2 Mon Sep 17 00:00:00 2001 From: SM Harwood Date: Wed, 4 Feb 2026 16:18:46 -0500 Subject: [PATCH 4/7] Use changeColBoundsInterfaceUnchecked in changeColBounds --- highs/lp_data/Highs.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index 21089cfb1a..e4b34b6e60 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -2976,8 +2976,11 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries, return analyseSetCreateError(options_.log_options, "changeColsBounds", create_error, true, num_set_entries, local_set.data(), model_.lp_.num_col_); - HighsStatus call_status = changeColBoundsInterface( - index_collection, local_lower.data(), local_upper.data()); + // Since we have already done the safety checks that would take place in + // changeColBoundsInterface and created local copies of lower/upper, we can + // use the "unchecked" version + HighsStatus call_status = changeColBoundsInterfaceUnchecked( + index_collection, local_lower, local_upper); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus(options_.log_options, call_status, return_status, "changeColBounds"); From 9785a920823bea3f157bf8ae0e8f6178b642f07e Mon Sep 17 00:00:00 2001 From: SM Harwood Date: Wed, 4 Feb 2026 16:56:32 -0500 Subject: [PATCH 5/7] Add Highs::changeCostsInterfaceUnchecked --- highs/Highs.h | 6 ++++++ highs/lp_data/HighsInterface.cpp | 11 ++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/highs/Highs.h b/highs/Highs.h index c20b23e3db..956034d3be 100644 --- a/highs/Highs.h +++ b/highs/Highs.h @@ -1704,8 +1704,14 @@ class Highs { HighsStatus changeObjectiveOffsetInterface(const double ext_offset); HighsStatus changeIntegralityInterface(HighsIndexCollection& index_collection, const HighsVarType* usr_inegrality); + // Interface to change costs in a general (and safe) way HighsStatus changeCostsInterface(HighsIndexCollection& index_collection, const double* usr_col_cost); + // Interface to change costs without some safety checks, + // but with potential performance boosts + HighsStatus changeCostsInterfaceUnchecked( + HighsIndexCollection& index_collection, + std::vector& usr_col_cost); bool feasibleWrtBounds(const bool columns = true) const; // Interface to change column bounds in a general (and safe) way diff --git a/highs/lp_data/HighsInterface.cpp b/highs/lp_data/HighsInterface.cpp index 49d9c2cfb2..371a1904b9 100644 --- a/highs/lp_data/HighsInterface.cpp +++ b/highs/lp_data/HighsInterface.cpp @@ -912,16 +912,21 @@ HighsStatus Highs::changeCostsInterface(HighsIndexCollection& index_collection, return HighsStatus::kError; // Take a copy of the cost that can be normalised std::vector local_colCost{cost, cost + num_cost}; + return changeCostsInterfaceUnchecked(index_collection, local_colCost); +} + +HighsStatus Highs::changeCostsInterfaceUnchecked( + HighsIndexCollection& index_collection, std::vector& cost) { HighsStatus return_status = HighsStatus::kOk; bool local_has_infinite_cost = false; return_status = interpretCallStatus( options_.log_options, - assessCosts(options_, 0, index_collection, local_colCost, - local_has_infinite_cost, options_.infinite_cost), + assessCosts(options_, 0, index_collection, cost, local_has_infinite_cost, + options_.infinite_cost), return_status, "assessCosts"); if (return_status == HighsStatus::kError) return return_status; HighsLp& lp = model_.lp_; - changeLpCosts(lp, index_collection, local_colCost, options_.infinite_cost); + changeLpCosts(lp, index_collection, cost, options_.infinite_cost); // Interpret possible introduction of infinite costs lp.has_infinite_cost_ = lp.has_infinite_cost_ || local_has_infinite_cost; From 0c37a268e8c6c4f823a9f7801f2378bbf3e62547 Mon Sep 17 00:00:00 2001 From: SM Harwood Date: Wed, 4 Feb 2026 17:04:06 -0500 Subject: [PATCH 6/7] Use changeCostsInterfaceUnchecked in changeColsCost --- highs/lp_data/Highs.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/highs/lp_data/Highs.cpp b/highs/lp_data/Highs.cpp index e4b34b6e60..f5316ac2b2 100644 --- a/highs/lp_data/Highs.cpp +++ b/highs/lp_data/Highs.cpp @@ -2897,8 +2897,11 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries, return analyseSetCreateError(options_.log_options, "changeColsCost", create_error, true, num_set_entries, local_set.data(), model_.lp_.num_col_); + // Since we have already done the safety checks that would take place in + // changeCostsInterface and created local copies of cost, we can + // use the "unchecked" version HighsStatus call_status = - changeCostsInterface(index_collection, local_cost.data()); + changeCostsInterfaceUnchecked(index_collection, local_cost); HighsStatus return_status = HighsStatus::kOk; return_status = interpretCallStatus(options_.log_options, call_status, return_status, "changeCosts"); From afad16b4bfbbc8cb958207e29ea709e480644f36 Mon Sep 17 00:00:00 2001 From: SM Harwood Date: Thu, 5 Feb 2026 11:05:47 -0500 Subject: [PATCH 7/7] Add tests --- check/TestLpModification.cpp | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/check/TestLpModification.cpp b/check/TestLpModification.cpp index c5c1b45506..efc212a327 100644 --- a/check/TestLpModification.cpp +++ b/check/TestLpModification.cpp @@ -809,7 +809,10 @@ TEST_CASE("LP-modification", "[highs_data]") { col1357_upper[2] = 0; col1357_upper[3] = 0; - REQUIRE(highs.changeColsBounds(col1357_num_ix, col1357_col_set, col1357_lower, + // Doing it with indices out of order is fine + HighsInt col5713_col_set[] = {5, 7, 1, 3}; + + REQUIRE(highs.changeColsBounds(col1357_num_ix, col5713_col_set, col1357_lower, col1357_upper) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); @@ -864,6 +867,29 @@ TEST_CASE("LP-modification", "[highs_data]") { callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); + // Change row bounds again but with indices out of order + HighsInt row7890135_row_set[] = {7, 8, 9, 0, 1, 3, 5}; + row0135789_lower[0] = local_lp.row_lower_[7]; + row0135789_lower[1] = local_lp.row_lower_[8]; + row0135789_lower[2] = local_lp.row_lower_[9]; + row0135789_lower[3] = local_lp.row_lower_[0]; + row0135789_lower[4] = local_lp.row_lower_[1]; + row0135789_lower[5] = local_lp.row_lower_[3]; + row0135789_lower[6] = local_lp.row_lower_[5]; + row0135789_upper[0] = local_lp.row_lower_[7]; + row0135789_upper[1] = local_lp.row_lower_[8]; + row0135789_upper[2] = local_lp.row_lower_[9]; + row0135789_upper[3] = local_lp.row_lower_[0]; + row0135789_upper[4] = local_lp.row_lower_[1]; + row0135789_upper[5] = local_lp.row_lower_[3]; + row0135789_upper[6] = local_lp.row_lower_[5]; + + REQUIRE(highs.changeRowsBounds(row0135789_num_ix, row7890135_row_set, + row0135789_lower, + row0135789_upper) == HighsStatus::kOk); + + callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); + REQUIRE(highs.deleteRows(0, num_row - 1) == HighsStatus::kOk); callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); @@ -926,6 +952,12 @@ TEST_CASE("LP-modification", "[highs_data]") { REQUIRE(highs.changeColsCost(col1357_num_ix, col1357_col_set, col1357_cost) == HighsStatus::kOk); + // Do it again but with indices out of order + double col5713_cost[] = {2.51, 2.71, 2.01, 2.31}; + + REQUIRE(highs.changeColsCost(col1357_num_ix, col5713_col_set, col5713_cost) == + HighsStatus::kOk); + callRun(highs, options.log_options, "highs.run()", HighsStatus::kOk); // Attempting to set row bounds with infinite lower bound returns error