1414from typing import Sequence , Dict , Optional , Mapping , NoReturn , List , Tuple
1515import os
1616
17- from pyomo .core .base .constraint import _GeneralConstraintData
18- from pyomo .core .base .var import _GeneralVarData
17+ from pyomo .core .base .constraint import Constraint , _GeneralConstraintData
18+ from pyomo .core .base .var import Var , _GeneralVarData
1919from pyomo .core .base .param import _ParamData
2020from pyomo .core .base .block import _BlockData
21- from pyomo .core .base .objective import _GeneralObjectiveData
21+ from pyomo .core .base .objective import Objective , _GeneralObjectiveData
2222from pyomo .common .config import document_kwargs_from_configdict , ConfigValue
2323from pyomo .common .errors import ApplicationError
2424from pyomo .common .deprecation import deprecation_warning
@@ -348,9 +348,19 @@ class LegacySolverWrapper:
348348 interface. Necessary for backwards compatibility.
349349 """
350350
351- def __init__ (self , solver_io = None , ** kwargs ):
352- if solver_io is not None :
351+ def __init__ (self , ** kwargs ):
352+ if ' solver_io' in kwargs :
353353 raise NotImplementedError ('Still working on this' )
354+ # There is no reason for a user to be trying to mix both old
355+ # and new options. That is silly. So we will yell at them.
356+ self .options = kwargs .pop ('options' , None )
357+ if 'solver_options' in kwargs :
358+ if self .options is not None :
359+ raise ValueError (
360+ "Both 'options' and 'solver_options' were requested. "
361+ "Please use one or the other, not both."
362+ )
363+ self .options = kwargs .pop ('solver_options' )
354364 super ().__init__ (** kwargs )
355365
356366 #
@@ -376,6 +386,8 @@ def _map_config(
376386 keepfiles = NOTSET ,
377387 solnfile = NOTSET ,
378388 options = NOTSET ,
389+ solver_options = NOTSET ,
390+ writer_config = NOTSET ,
379391 ):
380392 """Map between legacy and new interface configuration options"""
381393 self .config = self .config ()
@@ -393,8 +405,26 @@ def _map_config(
393405 self .config .time_limit = timelimit
394406 if report_timing is not NOTSET :
395407 self .config .report_timing = report_timing
396- if options is not NOTSET :
408+ if self .options is not None :
409+ self .config .solver_options .set_value (self .options )
410+ if (options is not NOTSET ) and (solver_options is not NOTSET ):
411+ # There is no reason for a user to be trying to mix both old
412+ # and new options. That is silly. So we will yell at them.
413+ # Example that would raise an error:
414+ # solver.solve(model, options={'foo' : 'bar'}, solver_options={'foo' : 'not_bar'})
415+ raise ValueError (
416+ "Both 'options' and 'solver_options' were requested. "
417+ "Please use one or the other, not both."
418+ )
419+ elif options is not NOTSET :
420+ # This block is trying to mimic the existing logic in the legacy
421+ # interface that allows users to pass initialized options to
422+ # the solver object and override them in the solve call.
397423 self .config .solver_options .set_value (options )
424+ elif solver_options is not NOTSET :
425+ self .config .solver_options .set_value (solver_options )
426+ if writer_config is not NOTSET :
427+ self .config .writer_config .set_value (writer_config )
398428 # This is a new flag in the interface. To preserve backwards compatibility,
399429 # its default is set to "False"
400430 if raise_exception_on_nonoptimal_result is not NOTSET :
@@ -435,9 +465,14 @@ def _map_results(self, model, results):
435465 ]
436466 legacy_soln .status = legacy_solution_status_map [results .solution_status ]
437467 legacy_results .solver .termination_message = str (results .termination_condition )
438- legacy_results .problem .number_of_constraints = model .nconstraints ()
439- legacy_results .problem .number_of_variables = model .nvariables ()
440- number_of_objectives = model .nobjectives ()
468+ legacy_results .problem .number_of_constraints = float ('nan' )
469+ legacy_results .problem .number_of_variables = float ('nan' )
470+ number_of_objectives = sum (
471+ 1
472+ for _ in model .component_data_objects (
473+ Objective , active = True , descend_into = True
474+ )
475+ )
441476 legacy_results .problem .number_of_objectives = number_of_objectives
442477 if number_of_objectives == 1 :
443478 obj = get_objective (model )
@@ -508,7 +543,10 @@ def solve(
508543 options : Optional [Dict ] = None ,
509544 keepfiles : bool = False ,
510545 symbolic_solver_labels : bool = False ,
546+ # These are for forward-compatibility
511547 raise_exception_on_nonoptimal_result : bool = False ,
548+ solver_options : Optional [Dict ] = None ,
549+ writer_config : Optional [Dict ] = None ,
512550 ):
513551 """
514552 Solve method: maps new solve method style to backwards compatible version.
@@ -534,6 +572,8 @@ def solve(
534572 'keepfiles' ,
535573 'solnfile' ,
536574 'options' ,
575+ 'solver_options' ,
576+ 'writer_config' ,
537577 )
538578 loc = locals ()
539579 filtered_args = {k : loc [k ] for k in map_args if loc .get (k , None ) is not None }
@@ -559,7 +599,10 @@ def available(self, exception_flag=True):
559599 """
560600 ans = super ().available ()
561601 if exception_flag and not ans :
562- raise ApplicationError (f'Solver { self .__class__ } is not available ({ ans } ).' )
602+ raise ApplicationError (
603+ f'Solver "{ self .name } " is not available. '
604+ f'The returned status is: { ans } .'
605+ )
563606 return bool (ans )
564607
565608 def license_is_valid (self ) -> bool :
0 commit comments