2121 ADVANCED_OPTION ,
2222)
2323
24+ from typing import Any
25+ from pyomo .common .timing import HierarchicalTimer , default_timer
2426from pyomo .common .modeling import unique_component_name
2527from pyomo .common .dependencies import numpy as np
2628from pyomo .contrib .multistart .high_conf_stop import should_stop
3032from pyomo .contrib .solver .common .config import SolverConfig
3133from pyomo .contrib .solver .common .factory import SolverFactory
3234from pyomo .contrib .solver .common .results import SolutionStatus
35+ from pyomo .contrib .solver .common .util import NoOptimalSolutionError , NoSolutionError , NoFeasibleSolutionError
3336from pyomo .util .vars_from_expressions import get_vars_from_components
3437
3538from 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
0 commit comments