Skip to content

Commit e1a9f50

Browse files
committed
Add support for cplex
1 parent 54755dd commit e1a9f50

File tree

1 file changed

+67
-54
lines changed

1 file changed

+67
-54
lines changed

switch_model/solve.py

Lines changed: 67 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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

583588
def 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

686690
def 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

Comments
 (0)