Skip to content

Commit aa9ba5b

Browse files
committed
Starting to fix issues with test_solvers
1 parent b7262fb commit aa9ba5b

3 files changed

Lines changed: 64 additions & 50 deletions

File tree

pyomo/contrib/multistart/multi.py

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
ADVANCED_OPTION,
2222
)
2323

24+
from typing import Any
25+
from pyomo.common.timing import HierarchicalTimer, default_timer
2426
from pyomo.common.modeling import unique_component_name
2527
from pyomo.common.dependencies import numpy as np
2628
from pyomo.contrib.multistart.high_conf_stop import should_stop
@@ -30,6 +32,7 @@
3032
from pyomo.contrib.solver.common.config import SolverConfig
3133
from pyomo.contrib.solver.common.factory import SolverFactory
3234
from pyomo.contrib.solver.common.results import SolutionStatus
35+
from pyomo.contrib.solver.common.util import NoOptimalSolutionError, NoSolutionError, NoFeasibleSolutionError
3336
from pyomo.util.vars_from_expressions import get_vars_from_components
3437

3538
from pyomo.common.dependencies.scipy import stats
@@ -150,7 +153,7 @@ def __init__(
150153
self.sampling_method = self.declare(
151154
"sampling_method",
152155
ConfigValue(
153-
default="random_uniform",
156+
default="latin_hypercube",
154157
description="Method for sampling random starting points for reinitialization step. "
155158
"Supported options are 'random_uniform', 'latin_hypercube', and 'sobol_sampling'. "
156159
"Only utilized when config.strategy is 'rand_vector'.",
@@ -189,62 +192,80 @@ class MultiStart(SolverBase):
189192

190193
CONFIG = MultistartConfig()
191194

195+
def __init__(self, **kwds: Any) -> None:
196+
super().__init__(**kwds)
197+
198+
#: Instance configuration;
199+
self.config = self.config
200+
201+
192202
def available(self, exception_flag=True):
193203
"""Check if solver is available.
194204
195-
TODO: For now, it is always available. However, sub-solvers may not
196-
always be available, and so this should reflect that possibility.
197-
205+
The multistart solver wrapper should always be available,
206+
but it is not guaranteed the subsolvers will be.
207+
Check if the selected subsolver is available, which by default is ipopt.
198208
"""
199-
return True
200209

201-
def version(self):
202-
"""Get solver version
203-
TODO: This is a solver wrapper, unsure how to define version in this case."""
210+
subsolver = SolverFactory(self.config.solver)
211+
return subsolver.available()
204212

205-
return
213+
def version(self):
214+
"""Get solver version."""
215+
"""
216+
Original implementation: 0.1.0,
217+
Current implementation: 0.2.0,
218+
"""
219+
return (0, 2, 0)
206220

207221
def license_is_valid(self):
208222
return True
209223

210224
def solve(self, model, **kwds):
225+
start_time = default_timer()
226+
211227
# initialize keyword args
212228
config = self.config(kwds.pop('options', {}))
213229
config.set_value(kwds)
214230

231+
timer = config.timer
232+
if timer is None:
233+
timer = config.timer = HierarchicalTimer()
234+
215235
# Create centralized sampler once
216236
sampler = SamplingManager(
217237
method=config.sampling_method, rng=config.rng, seed=config.seed
218238
)
219239

220-
# Set options so infeasible solve does not interrupt runs
221-
# config.solver_args["load_solutions"] = False
222-
# config.solver_args["raise_exception_on_nonoptimal_result"] = False
240+
# Set sub-solver options so infeasible solve does not interrupt runs
241+
config.solver_args["load_solutions"] = False
242+
config.solver_args["raise_exception_on_nonoptimal_result"] = False
243+
244+
# config.raise_exception_on_nonoptimal_result = False
223245

224246
solver = SolverFactory(config.solver)
225247

226248
# Model sense
227-
objectives = model.component_data_objects(Objective, active=True)
228-
obj = next(objectives, None)
229-
# Check model validity
230-
if next(objectives, None) is not None:
249+
objectives = list(model.component_data_objects(Objective, active=True))
250+
# Check length
251+
print(len(objectives))
252+
if len(objectives) > 1:
231253
raise RuntimeError(
232254
"Multistart solver is unable to handle model with multiple active objectives."
233255
)
234-
# if obj is None:
235-
# raise RuntimeError(
236-
# "Multistart solver is unable to handle model with no active objective."
237-
# )
238-
# if obj.polynomial_degree() == 0:
239-
# raise RuntimeError(
240-
# "Multistart solver received model with constant objective"
241-
# )
256+
elif len(objectives) == 1:
257+
obj = objectives[0]
258+
obj.sign = 1 if obj.sense == minimize else -1
259+
obj_sign = obj.sign
260+
261+
else:
262+
obj_sign = 1
263+
264+
best_objective = float('inf') * obj_sign
242265

243266
# store objective values and objective/result information for best
244267
# solution obtained
245268
objectives = []
246-
obj_sign = 1 if obj.sense == minimize else -1
247-
best_objective = float('inf') * obj_sign
248269
best_model = model
249270
best_result = None
250271

@@ -309,7 +330,7 @@ def solve(self, model, **kwds):
309330
# at first iteration, solve the originally passed model
310331
m = model.clone() if num_iter > 1 else model
311332
reinitialize_variables(m, config, sampler)
312-
result = solver.solve(m, **config.solver_args) # , tee=True)
333+
result = solver.solve(m, **config.solver_args)
313334

314335
# Check the solution status before loading variables into the model.
315336
if result.solution_status in {
@@ -326,6 +347,7 @@ def solve(self, model, **kwds):
326347

327348
if best_result.solution_status is SolutionStatus.optimal:
328349
model_objectives = m.component_data_objects(Objective, active=True)
350+
print("Model objs", model_objectives)
329351
mobj = next(model_objectives)
330352
obj_val = value(mobj.expr)
331353
objectives.append(obj_val)
@@ -335,16 +357,23 @@ def solve(self, model, **kwds):
335357
best_model = m
336358
best_result = result
337359

338-
if using_HCS and not HCS_completed:
339-
logger.warning(
340-
"High confidence stopping rule was unable to complete "
341-
"after %s iterations. To increase this limit, change the "
342-
"HCS_max_iterations flag." % num_iter
343-
)
360+
if using_HCS:
361+
if not HCS_completed:
362+
logger.warning(
363+
"High confidence stopping rule was unable to complete "
364+
"after %s iterations. To increase this limit, change the "
365+
"HCS_max_iterations flag." % num_iter
366+
)
344367

368+
else:
369+
if config.raise_exception_on_nonoptimal_result and not using_HCS:
370+
if best_result.solution_status != SolutionStatus.optimal:
371+
raise NoOptimalSolutionError
372+
373+
345374
# if no better result was found than initial solve, then return
346375
# that without needing to copy variables.
347-
if best_model is model:
376+
if best_model is model:
348377
return best_result
349378

350379
# reassign the given models vars to the new models vars

pyomo/contrib/multistart/tests/test_multi.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -134,21 +134,6 @@ def test_multiple_obj(self):
134134
with self.assertRaisesRegex(RuntimeError, "multiple active objectives"):
135135
SolverFactory('multistart').solve(m)
136136

137-
# Would like to remove these tests to allow for square model solves.
138-
# def test_no_obj(self):
139-
# m = ConcreteModel()
140-
# m.x = Var()
141-
# with self.assertRaisesRegex(RuntimeError, "no active objective"):
142-
# SolverFactory('multistart').solve(m)
143-
144-
# def test_const_obj(self):
145-
# m = ConcreteModel()
146-
# m.x = Var()
147-
# m.o = Objective(expr=5)
148-
# with self.assertRaisesRegex(RuntimeError, "constant objective"):
149-
# SolverFactory('multistart').solve(m)
150-
151-
152137
def build_model():
153138
"""Simple non-convex model with many local minima"""
154139
model = ConcreteModel()

pyomo/contrib/solver/tests/solvers/test_solvers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,7 @@ def test_trivial_constraints(
12321232
opt.config.load_solutions = False
12331233
res = opt.solve(m)
12341234
self.assertNotEqual(res.solution_status, SolutionStatus.optimal)
1235-
if isinstance(opt, Ipopt):
1235+
if isinstance(opt, Ipopt) or isinstance(opt, MultiStart):
12361236
acceptable_termination_conditions = {
12371237
TerminationCondition.locallyInfeasible,
12381238
TerminationCondition.unbounded,

0 commit comments

Comments
 (0)