14
14
from typing import Sequence , Dict , Optional , Mapping , NoReturn , List , Tuple
15
15
import os
16
16
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
19
19
from pyomo .core .base .param import _ParamData
20
20
from pyomo .core .base .block import _BlockData
21
- from pyomo .core .base .objective import _GeneralObjectiveData
21
+ from pyomo .core .base .objective import Objective , _GeneralObjectiveData
22
22
from pyomo .common .config import document_kwargs_from_configdict , ConfigValue
23
23
from pyomo .common .errors import ApplicationError
24
24
from pyomo .common .deprecation import deprecation_warning
@@ -348,9 +348,19 @@ class LegacySolverWrapper:
348
348
interface. Necessary for backwards compatibility.
349
349
"""
350
350
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 :
353
353
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' )
354
364
super ().__init__ (** kwargs )
355
365
356
366
#
@@ -376,6 +386,8 @@ def _map_config(
376
386
keepfiles = NOTSET ,
377
387
solnfile = NOTSET ,
378
388
options = NOTSET ,
389
+ solver_options = NOTSET ,
390
+ writer_config = NOTSET ,
379
391
):
380
392
"""Map between legacy and new interface configuration options"""
381
393
self .config = self .config ()
@@ -393,8 +405,26 @@ def _map_config(
393
405
self .config .time_limit = timelimit
394
406
if report_timing is not NOTSET :
395
407
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.
397
423
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 )
398
428
# This is a new flag in the interface. To preserve backwards compatibility,
399
429
# its default is set to "False"
400
430
if raise_exception_on_nonoptimal_result is not NOTSET :
@@ -435,9 +465,14 @@ def _map_results(self, model, results):
435
465
]
436
466
legacy_soln .status = legacy_solution_status_map [results .solution_status ]
437
467
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
+ )
441
476
legacy_results .problem .number_of_objectives = number_of_objectives
442
477
if number_of_objectives == 1 :
443
478
obj = get_objective (model )
@@ -508,7 +543,10 @@ def solve(
508
543
options : Optional [Dict ] = None ,
509
544
keepfiles : bool = False ,
510
545
symbolic_solver_labels : bool = False ,
546
+ # These are for forward-compatibility
511
547
raise_exception_on_nonoptimal_result : bool = False ,
548
+ solver_options : Optional [Dict ] = None ,
549
+ writer_config : Optional [Dict ] = None ,
512
550
):
513
551
"""
514
552
Solve method: maps new solve method style to backwards compatible version.
@@ -534,6 +572,8 @@ def solve(
534
572
'keepfiles' ,
535
573
'solnfile' ,
536
574
'options' ,
575
+ 'solver_options' ,
576
+ 'writer_config' ,
537
577
)
538
578
loc = locals ()
539
579
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):
559
599
"""
560
600
ans = super ().available ()
561
601
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
+ )
563
606
return bool (ans )
564
607
565
608
def license_is_valid (self ) -> bool :
0 commit comments