@@ -434,17 +434,15 @@ def define_arguments(argparser):
434434
435435 # Define solver-related arguments
436436 # These are a subset of the arguments offered by "pyomo solve --solver=cplex --help"
437- argparser .add_argument ("--solver" , default = "glpk" ,
438- help = 'Name of Pyomo solver to use for the model (default is "glpk")' )
439437 argparser .add_argument ("--solver-manager" , default = "serial" ,
440438 help = 'Name of Pyomo solver manager to use for the model ("neos" to use remote NEOS server)' )
441439 argparser .add_argument ("--solver-io" , default = None , help = "Method for Pyomo to use to communicate with solver" )
442440 # note: pyomo has a --solver-options option but it is not clear
443441 # whether that does the same thing as --solver-options-string so we don't reuse the same name.
444- argparser .add_argument ("--solver-options-string" , default = None ,
442+ argparser .add_argument ("--solver-options-string" , default = "" ,
445443 help = 'A quoted string of options to pass to the model solver. Each option must be of the form option=value. '
446444 '(e.g., --solver-options-string "mipgap=0.001 primalopt=\' \' advance=2 threads=1")' )
447- argparser .add_argument ("--solver-method" , default = None , type = int ,
445+ argparser .add_argument ("--solver-method" , default = None , type = str ,
448446 help = "Specify the solver method to use." )
449447 argparser .add_argument ("--keepfiles" , action = 'store_true' , default = None ,
450448 help = "Keep temporary files produced by the solver (may be useful with --symbolic-solver-labels)" )
@@ -535,6 +533,12 @@ def define_arguments(argparser):
535533 "--graph" , default = False , action = 'store_true' ,
536534 help = "Automatically run switch graph after post solve"
537535 )
536+ argparser .add_argument (
537+ "--no-crossover" , default = False , action = 'store_true' ,
538+ help = "Disables crosssover when using the barrier algorithm. This reduces"
539+ ' the solve time greatly however may result in less accurate values and may fail to find an optimal'
540+ " solution. If you find that the solver returns a suboptimal solution remove this flag."
541+ )
538542
539543 argparser .add_argument (
540544 "--threads" , type = int , default = None ,
@@ -569,16 +573,17 @@ def add_recommended_args(argparser):
569573
570574 argparser .add_argument (
571575 "--recommended-fast" , default = False , action = 'store_true' ,
572- help = 'Equivalent to --recommended however disables crossover during solving. This reduces'
573- ' the solve time greatly however may result in less accurate values and may fail to find an optimal'
574- ' solution. If you find that the solver returns a suboptimal solution use --recommended.'
576+ help = 'Equivalent to --recommended with --no-crossover.'
575577 )
576578
577579 argparser .add_argument (
578580 "--recommended-debug" , default = False , action = 'store_true' ,
579581 help = 'Same as --recommended but adds the flags --keepfiles --tempdir temp --symbolic-solver-labels which are useful when debugging Gurobi.'
580582 )
581583
584+ argparser .add_argument ("--solver" , default = "gurobi" ,
585+ help = 'Name of Pyomo solver to use for the model (default is "gurobi")' )
586+
582587
583588def parse_recommended_args (args ):
584589 argparser = _ArgumentParser (add_help = False , allow_abbrev = False )
@@ -591,21 +596,20 @@ def parse_recommended_args(args):
591596 if flags_used == 0 :
592597 return args
593598
594- # Note we don't append but rather prepend so that flags can override the --recommend flags.
599+ # Note we don't append but rather prepend so that flags can override the --recommend flags to allow for overriding .
595600 args = [
596- '--solver' , 'gurobi' ,
597601 '-v' ,
598602 '--sorted-output' ,
599603 '--stream-output' ,
600604 '--log-run' ,
601605 '--debug' ,
602606 '--graph' ,
603- '--solver-method' , '2' , # Method 2 is barrier solve which is much faster than default
607+ '--solver-method' , 'barrier' ,
604608 ] + args
605- solver_options_string = "BarHomogeneous=1 FeasibilityTol=1e-5"
609+ if options .solver in ("gurobi" , "gurobi_direct" , "gurobi_aug" ):
610+ args = ['--solver-options-string' , "BarHomogeneous=1 FeasibilityTol=1e-5" ] + args
606611 if options .recommended_fast :
607- solver_options_string += " crossover=0"
608- args = ['--solver-options-string' , solver_options_string ] + args
612+ args = ["--no-crossover" ] + args
609613 if options .recommended_debug :
610614 args = ['--keepfiles' , '--tempdir' , 'temp' , '--symbolic-solver-labels' ] + args
611615
@@ -684,62 +688,71 @@ def add_extra_suffixes(model):
684688
685689
686690def solve (model ):
691+ options_string = model .options .solver_options_string
692+ method = model .options .solver_method
693+
694+ # If we need warm start switch the solver to our augmented version that supports warm starting
695+ solver_type = model .options .solver
696+ gurobi_types = ("gurobi" , "gurobi_direct" , "gurobi_aug" )
697+ if model .options .warm_start is not None or model .options .save_warm_start :
698+ if solver_type not in gurobi_types :
699+ raise NotImplementedError ("Warm start functionality requires --solver gurobi" )
700+ model .options .solver = "gurobi_aug"
701+
702+ # Method 1 (dual simplex) is required since it supports warm starting.
703+ if model .options .warm_start is not None :
704+ method = 1
705+
687706 if hasattr (model , "solver" ):
688707 solver = model .solver
689708 solver_manager = model .solver_manager
690709 else :
691- # If we need warm start switch the solver to our augmented version that supports warm starting
692- if model .options .warm_start is not None or model .options .save_warm_start :
693- if model .options .solver not in ("gurobi" , "gurobi_direct" ):
694- raise NotImplementedError ("Warm start functionality requires --solver gurobi" )
695- model .options .solver = "gurobi_aug"
696-
697- if model .options .warm_start is not None :
698- # Method 1 (dual simplex) is required since it supports warm starting.
699- model .options .solver_method = 1
700-
701710 # Create a solver object the first time in. We don't do this until a solve is
702711 # requested, because sometimes a different solve function may be used,
703712 # with its own solver object (e.g., with runph or a parallel solver server).
704713 # In those cases, we don't want to go through the expense of creating an
705714 # unused solver object, or get errors if the solver options are invalid.
706715 #
707716 # Note previously solver was saved in model however this is very memory inefficient.
708- solver = SolverFactory (model .options .solver , solver_io = model .options .solver_io )
709-
710- # If this option is enabled, gurobi will output an IIS to outputs\iis.ilp.
711- if model .options .gurobi_find_iis :
712- # Enable symbolic labels since otherwise we can't debug the .ilp file.
713- model .options .symbolic_solver_labels = True
714-
715- # If no string is passed make the string empty so we can add to it
716- if model .options .solver_options_string is None :
717- model .options .solver_options_string = ""
718-
719- # Add to the solver options 'ResultFile=iis.ilp'
720- # https://stackoverflow.com/a/51994135/5864903
721- iis_file_path = os .path .join (model .options .outputs_dir , "iis.ilp" )
722- model .options .solver_options_string += " ResultFile={}" .format (iis_file_path )
723-
724- if model .options .threads :
725- # If no string is passed make the string empty so we can add to it
726- if model .options .solver_options_string is None :
727- model .options .solver_options_string = ""
728-
729- model .options .solver_options_string += f" Threads={ model .options .threads } "
730-
731- if model .options .solver_method is not None :
732- # If no string is passed make the string empty so we can add to it
733- if model .options .solver_options_string is None :
734- model .options .solver_options_string = ""
735-
736- model .options .solver_options_string += f" method={ model .options .solver_method } "
737-
717+ solver = SolverFactory (solver_type , solver_io = model .options .solver_io )
738718 solver_manager = SolverManagerFactory (model .options .solver_manager )
739719
720+ # If this option is enabled, gurobi will output an IIS to outputs\iis.ilp.
721+ if model .options .gurobi_find_iis :
722+ # Enable symbolic labels since otherwise we can't debug the .ilp file.
723+ model .options .symbolic_solver_labels = True
724+
725+ # Add to the solver options 'ResultFile=iis.ilp'
726+ # https://stackoverflow.com/a/51994135/5864903
727+ iis_file_path = os .path .join (model .options .outputs_dir , "iis.ilp" )
728+ options_string += f" ResultFile={ iis_file_path } "
729+
730+ if model .options .no_crossover :
731+ if solver_type in gurobi_types :
732+ options_string += " crossover=0"
733+ elif solver_type == "cplex" :
734+ options_string = " solutiontype=2"
735+ else :
736+ raise NotImplementedError (f"--no-crossover not implemented for solver { solver } " )
737+
738+ if model .options .threads is not None :
739+ options_string += f" threads={ model .options .threads } "
740+
741+ if method is not None :
742+ if solver_type in gurobi_types :
743+ if method == "barrier" :
744+ method = 2
745+ options_string += f" method={ method } "
746+ elif solver_type == "cplex" :
747+ if method == "barrier" :
748+ method = 4
749+ options_string += f" LPMethod={ method } "
750+ else :
751+ raise NotImplementedError (f"Can't specify method { method } for solver { solver_type } " )
752+
740753 # get solver arguments
741754 solver_args = dict (
742- options_string = model . options . solver_options_string ,
755+ options_string = options_string ,
743756 keepfiles = model .options .keepfiles ,
744757 tee = model .options .tee ,
745758 symbolic_solver_labels = model .options .symbolic_solver_labels ,
0 commit comments