Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions check/TestLpSolvers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -553,3 +553,32 @@ TEST_CASE("choose-lp-solver", "[highs_lp_solver]") {

h.resetGlobalScheduler(true);
}

TEST_CASE("releaseMemory", "[highs_lp_solver]") {
std::string model_file =
std::string(HIGHS_DIR) + "/check/instances/avgas.mps";
Highs h;
h.setOptionValue("output_flag", dev_run);

// First solve
REQUIRE(h.readModel(model_file) == HighsStatus::kOk);
REQUIRE(h.run() == HighsStatus::kOk);
REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal);
double first_objective = h.getInfo().objective_function_value;

// Release memory and verify we can solve again
REQUIRE(h.releaseMemory() == HighsStatus::kOk);

// Second solve on a different model
std::string model_file2 =
std::string(HIGHS_DIR) + "/check/instances/adlittle.mps";
REQUIRE(h.readModel(model_file2) == HighsStatus::kOk);
REQUIRE(h.run() == HighsStatus::kOk);
REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal);
double second_objective = h.getInfo().objective_function_value;

// Verify objectives are different (different problems)
REQUIRE(first_objective != second_objective);

h.resetGlobalScheduler(true);
}
10 changes: 10 additions & 0 deletions highs/Highs.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ class Highs {
*/
HighsStatus clearSolverDualData();

/**
* @brief Release all retained memory back to the allocator
*
* Clears all solver state and shrinks internal vectors to free
* unused capacity. Useful in long-running services that reuse a
* Highs instance across multiple solves to prevent unbounded RSS
* growth from heap fragmentation.
*/
HighsStatus releaseMemory();

/**
* Methods for model input
*/
Expand Down
1 change: 1 addition & 0 deletions highs/highs_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) {
.def("clear", &Highs::clear)
.def("clearModel", &Highs::clearModel)
.def("clearSolver", &Highs::clearSolver)
.def("releaseMemory", &Highs::releaseMemory)
.def("passModel", &highs_passModel)
.def("passModel", &highs_passModelPointers)
.def("passModel", &highs_passLp)
Expand Down
1 change: 1 addition & 0 deletions highs/highspy/_core/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,7 @@ class _Highs:
def clearModel(self) -> HighsStatus: ...
def clearLinearObjectives(self) -> HighsStatus: ...
def clearSolver(self) -> HighsStatus: ...
def releaseMemory(self) -> HighsStatus: ...
def crossover(self, solution: HighsSolution) -> HighsStatus: ...
def deleteCols(self, num_cols: int, cols: HighsIntArrayType) -> HighsStatus: ...
def deleteRows(self, num_rows: int, rows: HighsIntArrayType) -> HighsStatus: ...
Expand Down
4 changes: 4 additions & 0 deletions highs/interfaces/highs_c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,10 @@ HighsInt Highs_clearSolver(void* highs) {
return (HighsInt)((Highs*)highs)->clearSolver();
}

HighsInt Highs_releaseMemory(void* highs) {
return (HighsInt)((Highs*)highs)->releaseMemory();
}

HighsInt Highs_setBoolOptionValue(void* highs, const char* option,
const HighsInt value) {
return (HighsInt)((Highs*)highs)
Expand Down
14 changes: 14 additions & 0 deletions highs/interfaces/highs_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,20 @@ HighsInt Highs_clearModel(void* highs);
*/
HighsInt Highs_clearSolver(void* highs);

/**
* Release all retained memory back to the allocator.
*
* Clears all solver state and shrinks internal vectors to free unused
* capacity. Useful in long-running services that reuse a Highs instance
* across multiple solves to prevent unbounded RSS growth from heap
* fragmentation.
*
* @param highs A pointer to the Highs instance.
*
* @returns A `kHighsStatus` constant indicating whether the call succeeded.
*/
HighsInt Highs_releaseMemory(void* highs);

/**
* Presolve a model.
*
Expand Down
57 changes: 57 additions & 0 deletions highs/lp_data/Highs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ HighsStatus Highs::clear() {
HighsStatus Highs::clearModel() {
model_.clear();
multi_linear_objective_.clear();
saved_objective_and_solution_.clear();
return clearSolver();
}

HighsStatus Highs::clearSolver() {
HighsStatus return_status = HighsStatus::kOk;
clearDerivedModelProperties();
invalidateSolverData();
ekk_instance_.clear();
return returnFromHighs(return_status);
}

Expand All @@ -75,6 +77,61 @@ HighsStatus Highs::clearSolverDualData() {
return returnFromHighs(return_status);
}

HighsStatus Highs::releaseMemory() {
HighsStatus return_status = HighsStatus::kOk;
// 1. Clear all model and solver state (same as clear()).
clearModel();

// 2. Clear and shrink vectors that clearModel/clearSolver only
// invalidate or leave with residual capacity. After this block the
// instance holds zero allocated heap memory for solver state —
// equivalent to a freshly constructed Highs.
saved_objective_and_solution_.shrink_to_fit();
solution_.clear();
solution_.col_value.shrink_to_fit();
solution_.col_dual.shrink_to_fit();
solution_.row_value.shrink_to_fit();
solution_.row_dual.shrink_to_fit();
basis_.clear();
basis_.col_status.shrink_to_fit();
basis_.row_status.shrink_to_fit();
ranging_.clear();
ranging_.col_cost_up.value_.shrink_to_fit();
ranging_.col_cost_up.objective_.shrink_to_fit();
ranging_.col_cost_up.in_var_.shrink_to_fit();
ranging_.col_cost_up.ou_var_.shrink_to_fit();
ranging_.col_cost_dn.value_.shrink_to_fit();
ranging_.col_cost_dn.objective_.shrink_to_fit();
ranging_.col_cost_dn.in_var_.shrink_to_fit();
ranging_.col_cost_dn.ou_var_.shrink_to_fit();
ranging_.col_bound_up.value_.shrink_to_fit();
ranging_.col_bound_up.objective_.shrink_to_fit();
ranging_.col_bound_up.in_var_.shrink_to_fit();
ranging_.col_bound_up.ou_var_.shrink_to_fit();
ranging_.col_bound_dn.value_.shrink_to_fit();
ranging_.col_bound_dn.objective_.shrink_to_fit();
ranging_.col_bound_dn.in_var_.shrink_to_fit();
ranging_.col_bound_dn.ou_var_.shrink_to_fit();
ranging_.row_bound_up.value_.shrink_to_fit();
ranging_.row_bound_up.objective_.shrink_to_fit();
ranging_.row_bound_up.in_var_.shrink_to_fit();
ranging_.row_bound_up.ou_var_.shrink_to_fit();
ranging_.row_bound_dn.value_.shrink_to_fit();
ranging_.row_bound_dn.objective_.shrink_to_fit();
ranging_.row_bound_dn.in_var_.shrink_to_fit();
ranging_.row_bound_dn.ou_var_.shrink_to_fit();
iis_.clear();

// 3. Shrink standard form LP vectors.
standard_form_cost_.shrink_to_fit();
standard_form_rhs_.shrink_to_fit();

// 4. Reset timer clocks (accumulated timing data).
timer_.zeroAllClocks();

return returnFromHighs(return_status);
}

HighsStatus Highs::setOptionValue(const std::string& option, const bool value) {
if (setLocalOptionValue(options_.log_options, option, options_.records,
value) == OptionStatus::kOk)
Expand Down
18 changes: 18 additions & 0 deletions tests/test_highspy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,24 @@ def try_change_ptr(e):
h.solve()
self.assertEqual(check_called[0], True)

def test_releaseMemory(self):
"""Test that releaseMemory() frees memory and allows solving again."""
# Create and solve first problem
h = self.get_example_model()
h.run()
first_objective = h.getInfo().objective_function_value

# Release memory
status = h.releaseMemory()
self.assertEqual(status, highspy.HighsStatus.kOk)

# Solve a different problem to verify the solver still works
h2 = self.get_basic_model()
h2.run()
second_objective = h2.getInfo().objective_function_value

# Verify objectives are different (different problems)
self.assertNotEqual(first_objective, second_objective)
def test_addVars_invalid_parameter(self):
"""ensure_real raises on invalid parameter"""
h = highspy.Highs()
Expand Down
Loading