Skip to content

Commit e8054f8

Browse files
authored
Merge pull request #102 from staadecker/cplex
Add support for CPLEX
2 parents ae7b108 + ada1518 commit e8054f8

File tree

1 file changed

+69
-40
lines changed

1 file changed

+69
-40
lines changed

switch_model/solve.py

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ def debug(type, value, tb):
113113
# get a list of modules to iterate through
114114
iterate_modules = get_iteration_list(model)
115115

116+
# Very that the proper modules are installed
117+
if model.options.solver == "cplex_direct":
118+
import cplex
119+
elif model.options.solver in ("gurobi_direct", "gurobi_aug"):
120+
import gurobipy
121+
116122
if model.options.verbose:
117123
print("\n=======================================================================")
118124
print("Switch {}, http://switch-model.org".format(switch_model.__version__))
@@ -434,8 +440,6 @@ def define_arguments(argparser):
434440

435441
# Define solver-related arguments
436442
# 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")')
439443
argparser.add_argument("--solver-manager", default="serial",
440444
help='Name of Pyomo solver manager to use for the model ("neos" to use remote NEOS server)')
441445
argparser.add_argument("--solver-io", default=None, help="Method for Pyomo to use to communicate with solver")
@@ -444,7 +448,7 @@ def define_arguments(argparser):
444448
argparser.add_argument("--solver-options-string", default="",
445449
help='A quoted string of options to pass to the model solver. Each option must be of the form option=value. '
446450
'(e.g., --solver-options-string "mipgap=0.001 primalopt=\'\' advance=2 threads=1")')
447-
argparser.add_argument("--solver-method", default=None, type=int,
451+
argparser.add_argument("--solver-method", default=None, type=str,
448452
help="Specify the solver method to use.")
449453
argparser.add_argument("--keepfiles", action='store_true', default=None,
450454
help="Keep temporary files produced by the solver (may be useful with --symbolic-solver-labels)")
@@ -535,6 +539,12 @@ def define_arguments(argparser):
535539
"--graph", default=False, action='store_true',
536540
help="Automatically run switch graph after post solve"
537541
)
542+
argparser.add_argument(
543+
"--no-crossover", default=False, action='store_true',
544+
help="Disables crosssover when using the barrier algorithm. This reduces"
545+
' the solve time greatly however may result in less accurate values and may fail to find an optimal'
546+
" solution. If you find that the solver returns a suboptimal solution remove this flag."
547+
)
538548

539549
argparser.add_argument(
540550
"--threads", type=int, default=None,
@@ -575,16 +585,17 @@ def add_recommended_args(argparser):
575585

576586
argparser.add_argument(
577587
"--recommended-fast", default=False, action='store_true',
578-
help='Equivalent to --recommended however disables crossover during solving. This reduces'
579-
' the solve time greatly however may result in less accurate values and may fail to find an optimal'
580-
' solution. If you find that the solver returns a suboptimal solution use --recommended.'
588+
help='Equivalent to --recommended with --no-crossover.'
581589
)
582590

583591
argparser.add_argument(
584592
"--recommended-debug", default=False, action='store_true',
585593
help='Same as --recommended but adds the flags --keepfiles --tempdir temp --symbolic-solver-labels which are useful when debugging Gurobi.'
586594
)
587595

596+
argparser.add_argument("--solver", default="gurobi",
597+
help='Name of Pyomo solver to use for the model (default is "gurobi")')
598+
588599

589600
def parse_recommended_args(args):
590601
argparser = _ArgumentParser(add_help=False, allow_abbrev=False)
@@ -597,21 +608,20 @@ def parse_recommended_args(args):
597608
if flags_used == 0:
598609
return args
599610

600-
# Note we don't append but rather prepend so that flags can override the --recommend flags.
611+
# Note we don't append but rather prepend so that flags can override the --recommend flags to allow for overriding.
601612
args = [
602-
'--solver', 'gurobi',
603613
'-v',
604614
'--sorted-output',
605615
'--stream-output',
606616
'--log-run',
607617
'--debug',
608618
'--graph',
609-
'--solver-method', '2', # Method 2 is barrier solve which is much faster than default
619+
'--solver-method', 'barrier',
610620
] + args
611-
solver_options_string = "BarHomogeneous=1 FeasibilityTol=1e-5"
621+
if options.solver in ("gurobi", "gurobi_direct", "gurobi_aug"):
622+
args = ['--solver-options-string', "BarHomogeneous=1 FeasibilityTol=1e-5"] + args
612623
if options.recommended_fast:
613-
solver_options_string += " crossover=0"
614-
args = ['--solver-options-string', solver_options_string] + args
624+
args = ["--no-crossover"] + args
615625
if options.recommended_debug:
616626
args = ['--keepfiles', '--tempdir', 'temp', '--symbolic-solver-labels'] + args
617627

@@ -690,35 +700,43 @@ def add_extra_suffixes(model):
690700

691701

692702
def solve(model):
703+
options_string = model.options.solver_options_string
704+
method = model.options.solver_method
705+
706+
# If we need warm start switch the solver to our augmented version that supports warm starting
707+
solver_type = model.options.solver
708+
gurobi_types = ("gurobi", "gurobi_direct", "gurobi_aug")
709+
cplex_types = ("cplex", "cplex_direct")
710+
711+
if model.options.warm_start is not None or model.options.save_warm_start:
712+
if solver_type not in gurobi_types:
713+
raise NotImplementedError("Warm start functionality requires --solver gurobi")
714+
model.options.solver = "gurobi_aug"
715+
716+
# Method 1 (dual simplex) is required since it supports warm starting.
717+
if model.options.warm_start is not None:
718+
method = 1
719+
693720
if hasattr(model, "solver"):
694721
solver = model.solver
695722
solver_manager = model.solver_manager
696723
else:
697-
# If we need warm start switch the solver to our augmented version that supports warm starting
698-
if model.options.warm_start is not None or model.options.save_warm_start:
699-
if model.options.solver not in ("gurobi", "gurobi_direct"):
700-
raise NotImplementedError("Warm start functionality requires --solver gurobi")
701-
model.options.solver = "gurobi_aug"
702-
703-
if model.options.warm_start is not None:
704-
# Method 1 (dual simplex) is required since it supports warm starting.
705-
model.options.solver_method = 1
706-
707724
# Create a solver object the first time in. We don't do this until a solve is
708725
# requested, because sometimes a different solve function may be used,
709726
# with its own solver object (e.g., with runph or a parallel solver server).
710727
# In those cases, we don't want to go through the expense of creating an
711728
# unused solver object, or get errors if the solver options are invalid.
712729
#
713730
# Note previously solver was saved in model however this is very memory inefficient.
714-
solver = SolverFactory(model.options.solver, solver_io=model.options.solver_io)
731+
solver = SolverFactory(solver_type, solver_io=model.options.solver_io)
732+
solver_manager = SolverManagerFactory(model.options.solver_manager)
715733

716-
if model.options.gurobi_find_iis and model.options.gurobi_make_mps:
717-
raise Exception("Can't use --gurobi-find-iis with --gurobi-make-mps.")
734+
if model.options.gurobi_find_iis and model.options.gurobi_make_mps:
735+
raise Exception("Can't use --gurobi-find-iis with --gurobi-make-mps.")
718736

719-
if model.options.gurobi_find_iis or model.options.gurobi_make_mps:
720-
# If we are outputting a file we want to enable symbolic labels to help debugging
721-
model.options.symbolic_solver_labels = True
737+
if model.options.gurobi_find_iis or model.options.gurobi_make_mps:
738+
# If we are outputting a file we want to enable symbolic labels to help debugging
739+
model.options.symbolic_solver_labels = True
722740

723741
# If this option is enabled, gurobi will output an IIS to outputs\iis.ilp.
724742
if model.options.gurobi_find_iis:
@@ -729,21 +747,32 @@ def solve(model):
729747
# Output the input file and set time limit to zero to ensure it doesn't actually solve
730748
model.options.solver_options_string += f" ResultFile=problem.mps TimeLimit=0"
731749

732-
if model.options.threads:
733-
model.options.solver_options_string += f" Threads={model.options.threads}"
734-
735-
if model.options.solver_method is not None:
736-
# If no string is passed make the string empty so we can add to it
737-
if model.options.solver_options_string is None:
738-
model.options.solver_options_string = ""
739-
740-
model.options.solver_options_string += f" method={model.options.solver_method}"
741-
742-
solver_manager = SolverManagerFactory(model.options.solver_manager)
750+
if model.options.no_crossover:
751+
if solver_type in gurobi_types:
752+
options_string += " crossover=0"
753+
elif solver_type in cplex_types:
754+
options_string = " solutiontype=2"
755+
else:
756+
raise NotImplementedError(f"--no-crossover not implemented for solver {solver}")
757+
758+
if model.options.threads is not None:
759+
options_string += f" threads={model.options.threads}"
760+
761+
if method is not None:
762+
if solver_type in gurobi_types:
763+
if method == "barrier":
764+
method = 2
765+
options_string += f" method={method}"
766+
elif solver_type in cplex_types:
767+
if method == "barrier":
768+
method = 4
769+
options_string += f" LPMethod={method}"
770+
else:
771+
raise NotImplementedError(f"Can't specify method {method} for solver {solver_type}")
743772

744773
# get solver arguments
745774
solver_args = dict(
746-
options_string=model.options.solver_options_string,
775+
options_string=options_string,
747776
keepfiles=model.options.keepfiles,
748777
tee=model.options.tee,
749778
symbolic_solver_labels=model.options.symbolic_solver_labels,

0 commit comments

Comments
 (0)