Skip to content

Commit 093efa2

Browse files
authored
Merge pull request #3494 from quantresearch1/appsi_highs_warmstart
Add warmstart for appsi_highs (issue #3450)
2 parents 6e8b973 + 28230be commit 093efa2

File tree

3 files changed

+58
-0
lines changed

3 files changed

+58
-0
lines changed

pyomo/contrib/appsi/base.py

+4
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,14 @@ def __init__(
139139
)
140140

141141
self.declare('time_limit', ConfigValue(domain=NonNegativeFloat))
142+
self.declare('warmstart', ConfigValue(domain=bool))
142143
self.declare('stream_solver', ConfigValue(domain=bool))
143144
self.declare('load_solution', ConfigValue(domain=bool))
144145
self.declare('symbolic_solver_labels', ConfigValue(domain=bool))
145146
self.declare('report_timing', ConfigValue(domain=bool))
146147

147148
self.time_limit: Optional[float] = None
149+
self.warmstart: bool = False
148150
self.stream_solver: bool = False
149151
self.load_solution: bool = True
150152
self.symbolic_solver_labels: bool = False
@@ -1525,13 +1527,15 @@ def solve(
15251527
options: Optional[Dict] = None,
15261528
keepfiles: bool = False,
15271529
symbolic_solver_labels: bool = False,
1530+
warmstart: bool = False,
15281531
):
15291532
original_config = self.config
15301533
self.config = self.config()
15311534
self.config.stream_solver = tee
15321535
self.config.load_solution = load_solutions
15331536
self.config.symbolic_solver_labels = symbolic_solver_labels
15341537
self.config.time_limit = timelimit
1538+
self.config.warmstart = warmstart
15351539
self.config.report_timing = report_timing
15361540
if solver_io is not None:
15371541
raise NotImplementedError('Still working on this')

pyomo/contrib/appsi/solvers/highs.py

+26
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,29 @@ def symbol_map(self):
221221
return SymbolMap()
222222
# raise RuntimeError('Highs interface does not have a symbol map')
223223

224+
def warm_start_capable(self):
225+
return True
226+
227+
def _warm_start(self):
228+
# Collect all variable values
229+
col_value = np.zeros(len(self._pyomo_var_to_solver_var_map))
230+
has_values = False
231+
232+
for var_id, col_ndx in self._pyomo_var_to_solver_var_map.items():
233+
var = self._vars[var_id][0]
234+
if var.value is not None:
235+
col_value[col_ndx] = value(var)
236+
has_values = True
237+
238+
if has_values:
239+
solution = highspy.HighsSolution()
240+
solution.col_value = col_value
241+
solution.value_valid = True
242+
solution.dual_valid = False
243+
244+
# Set the solution as a MIP start
245+
self._solver_model.setSolution(solution)
246+
224247
def _solve(self, timer: HierarchicalTimer):
225248
config = self.config
226249
options = self.highs_options
@@ -245,6 +268,9 @@ def _solve(self, timer: HierarchicalTimer):
245268

246269
for key, option in options.items():
247270
self._solver_model.setOptionValue(key, option)
271+
272+
if config.warmstart:
273+
self._warm_start()
248274
timer.start('optimize')
249275
ostreams[-1].write("RUN!\n")
250276
self._solver_model.HandleKeyboardInterrupt = True

pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py

+28
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,31 @@ def test_capture_highs_output(self):
155155
self.assertIn("HiGHS run time", OUT.getvalue())
156156
ref = "10.0 5.0\n"
157157
self.assertEqual(ref, OUT.getvalue()[-len(ref) :])
158+
159+
def test_warm_start(self):
160+
m = pe.ConcreteModel()
161+
162+
# decision variables
163+
m.x1 = pe.Var(domain=pe.Integers, name="x1", bounds=(0, 10))
164+
m.x2 = pe.Var(domain=pe.Reals, name="x2", bounds=(0, 10))
165+
m.x3 = pe.Var(domain=pe.Binary, name="x3")
166+
167+
# objective function
168+
m.OBJ = pe.Objective(expr=(3 * m.x1 + 2 * m.x2 + 4 * m.x3), sense=pe.maximize)
169+
170+
# constraints
171+
m.C1 = pe.Constraint(expr=m.x1 + m.x2 <= 9)
172+
m.C2 = pe.Constraint(expr=3 * m.x1 + m.x2 <= 18)
173+
m.C3 = pe.Constraint(expr=m.x1 <= 7)
174+
m.C4 = pe.Constraint(expr=m.x2 <= 6)
175+
176+
# MIP start
177+
m.x1 = 4
178+
m.x2 = 4.5
179+
m.x3 = True
180+
181+
# solving process
182+
with capture_output() as output:
183+
pe.SolverFactory("appsi_highs").solve(m, tee=True, warmstart=True)
184+
log = output.getvalue()
185+
self.assertIn("MIP start solution is feasible, objective value is 25", log)

0 commit comments

Comments
 (0)