@@ -131,12 +131,6 @@ def debug(type, value, tb):
131131 # get a list of modules to iterate through
132132 iterate_modules = get_iteration_list (model )
133133
134- # Very that the proper modules are installed
135- if model .options .solver == "cplex_direct" :
136- import cplex
137- elif model .options .solver in ("gurobi_direct" , "gurobi_aug" ):
138- import gurobipy
139-
140134 if model .options .verbose :
141135 print (
142136 "\n ======================================================================="
@@ -514,6 +508,11 @@ def define_arguments(argparser):
514508
515509 # Define solver-related arguments
516510 # These are a subset of the arguments offered by "pyomo solve --solver=cplex --help"
511+ argparser .add_argument (
512+ "--solver" ,
513+ default = "glpk" ,
514+ help = 'Name of Pyomo solver to use for the model (default is "glpk")' ,
515+ )
517516 argparser .add_argument (
518517 "--solver-manager" ,
519518 default = "serial" ,
@@ -535,7 +534,7 @@ def define_arguments(argparser):
535534 argparser .add_argument (
536535 "--solver-method" ,
537536 default = None ,
538- type = str ,
537+ type = int ,
539538 help = "Specify the solver method to use." ,
540539 )
541540 argparser .add_argument (
@@ -693,14 +692,6 @@ def define_arguments(argparser):
693692 action = "store_true" ,
694693 help = "Automatically run switch graph after post solve" ,
695694 )
696- argparser .add_argument (
697- "--no-crossover" ,
698- default = False ,
699- action = "store_true" ,
700- help = "Disables crosssover when using the barrier algorithm. This reduces"
701- " the solve time greatly however may result in less accurate values and may fail to find an optimal"
702- " solution. If you find that the solver returns a suboptimal solution remove this flag." ,
703- )
704695
705696 argparser .add_argument (
706697 "--threads" ,
@@ -724,6 +715,14 @@ def define_arguments(argparser):
724715 " that all variables must be the same between the previous and current scenario." ,
725716 )
726717
718+ argparser .add_argument (
719+ "--gurobi-make-mps" ,
720+ default = False ,
721+ action = "store_true" ,
722+ help = "Instead of solving just output a Gurobi .mps file that can be used for debugging numerical properties."
723+ " See https://github.com/staadecker/lp-analyzer/ for details." ,
724+ )
725+
727726
728727def add_recommended_args (argparser ):
729728 """
@@ -743,7 +742,9 @@ def add_recommended_args(argparser):
743742 "--recommended-fast" ,
744743 default = False ,
745744 action = "store_true" ,
746- help = "Equivalent to --recommended with --no-crossover." ,
745+ help = "Equivalent to --recommended however disables crossover during solving. This reduces"
746+ " the solve time greatly however may result in less accurate values and may fail to find an optimal"
747+ " solution. If you find that the solver returns a suboptimal solution use --recommended." ,
747748 )
748749
749750 argparser .add_argument (
@@ -753,12 +754,6 @@ def add_recommended_args(argparser):
753754 help = "Same as --recommended but adds the flags --keepfiles --tempdir temp --symbolic-solver-labels which are useful when debugging Gurobi." ,
754755 )
755756
756- argparser .add_argument (
757- "--solver" ,
758- default = "gurobi" ,
759- help = 'Name of Pyomo solver to use for the model (default is "gurobi")' ,
760- )
761-
762757
763758def parse_recommended_args (args ):
764759 argparser = _ArgumentParser (add_help = False , allow_abbrev = False )
@@ -775,24 +770,23 @@ def parse_recommended_args(args):
775770 if flags_used == 0 :
776771 return args
777772
778- # Note we don't append but rather prepend so that flags can override the --recommend flags to allow for overriding .
773+ # Note we don't append but rather prepend so that flags can override the --recommend flags.
779774 args = [
775+ "--solver" ,
776+ "gurobi" ,
780777 "-v" ,
781778 "--sorted-output" ,
782779 "--stream-output" ,
783780 "--log-run" ,
784781 "--debug" ,
785782 "--graph" ,
786783 "--solver-method" ,
787- "barrier" ,
784+ "2" , # Method 2 is barrier solve which is much faster than default
788785 ] + args
789- if options .solver in ("gurobi" , "gurobi_direct" , "gurobi_aug" ):
790- args = [
791- "--solver-options-string" ,
792- "BarHomogeneous=1 FeasibilityTol=1e-5" ,
793- ] + args
786+ solver_options_string = "BarHomogeneous=1 FeasibilityTol=1e-5"
794787 if options .recommended_fast :
795- args = ["--no-crossover" ] + args
788+ solver_options_string += " crossover=0"
789+ args = ["--solver-options-string" , solver_options_string ] + args
796790 if options .recommended_debug :
797791 args = ["--keepfiles" , "--tempdir" , "temp" , "--symbolic-solver-labels" ] + args
798792
@@ -887,79 +881,66 @@ def add_extra_suffixes(model):
887881
888882
889883def solve (model ):
890- options_string = model .options .solver_options_string
891- method = model .options .solver_method
892-
893- # If we need warm start switch the solver to our augmented version that supports warm starting
894- solver_type = model .options .solver
895- gurobi_types = ("gurobi" , "gurobi_direct" , "gurobi_aug" )
896- cplex_types = ("cplex" , "cplex_direct" )
897-
898- if model .options .warm_start is not None or model .options .save_warm_start :
899- if solver_type not in gurobi_types :
900- raise NotImplementedError (
901- "Warm start functionality requires --solver gurobi"
902- )
903- model .options .solver = "gurobi_aug"
904-
905- # Method 1 (dual simplex) is required since it supports warm starting.
906- if model .options .warm_start is not None :
907- method = 1
908-
909884 if hasattr (model , "solver" ):
910885 solver = model .solver
911886 solver_manager = model .solver_manager
912887 else :
888+ # If we need warm start switch the solver to our augmented version that supports warm starting
889+ if model .options .warm_start is not None or model .options .save_warm_start :
890+ if model .options .solver not in ("gurobi" , "gurobi_direct" ):
891+ raise NotImplementedError (
892+ "Warm start functionality requires --solver gurobi"
893+ )
894+ model .options .solver = "gurobi_aug"
895+
896+ if model .options .warm_start is not None :
897+ # Method 1 (dual simplex) is required since it supports warm starting.
898+ model .options .solver_method = 1
899+
913900 # Create a solver object the first time in. We don't do this until a solve is
914901 # requested, because sometimes a different solve function may be used,
915902 # with its own solver object (e.g., with runph or a parallel solver server).
916903 # In those cases, we don't want to go through the expense of creating an
917904 # unused solver object, or get errors if the solver options are invalid.
918905 #
919906 # Note previously solver was saved in model however this is very memory inefficient.
920- solver = SolverFactory (solver_type , solver_io = model .options .solver_io )
921- solver_manager = SolverManagerFactory (model .options .solver_manager )
922-
923- # If this option is enabled, gurobi will output an IIS to outputs\iis.ilp.
924- if model .options .gurobi_find_iis :
925- # Enable symbolic labels since otherwise we can't debug the .ilp file.
926- model .options .symbolic_solver_labels = True
927-
928- # Add to the solver options 'ResultFile=iis.ilp'
929- # https://stackoverflow.com/a/51994135/5864903
930- iis_file_path = os .path .join (model .options .outputs_dir , "iis.ilp" )
931- options_string += f" ResultFile={ iis_file_path } "
932-
933- if model .options .no_crossover :
934- if solver_type in gurobi_types :
935- options_string += " crossover=0"
936- elif solver_type in cplex_types :
937- options_string = " solutiontype=2"
938- else :
939- raise NotImplementedError (
940- f"--no-crossover not implemented for solver { solver } "
907+ solver = SolverFactory (model .options .solver , solver_io = model .options .solver_io )
908+
909+ if model .options .gurobi_find_iis and model .options .gurobi_make_mps :
910+ raise Exception ("Can't use --gurobi-find-iis with --gurobi-make-mps." )
911+
912+ if model .options .gurobi_find_iis or model .options .gurobi_make_mps :
913+ # If we are outputting a file we want to enable symbolic labels to help debugging
914+ model .options .symbolic_solver_labels = True
915+
916+ # If this option is enabled, gurobi will output an IIS to outputs\iis.ilp.
917+ if model .options .gurobi_find_iis :
918+ # Add to the solver options 'ResultFile=iis.ilp'
919+ # https://stackoverflow.com/a/51994135/5864903
920+ model .options .solver_options_string += " ResultFile=iis.ilp"
921+ if model .options .gurobi_make_mps :
922+ # Output the input file and set time limit to zero to ensure it doesn't actually solve
923+ model .options .solver_options_string += (
924+ f" ResultFile=problem.mps TimeLimit=0"
941925 )
942926
943- if model .options .threads is not None :
944- options_string += f" threads={ model .options .threads } "
945-
946- if method is not None :
947- if solver_type in gurobi_types :
948- if method == "barrier" :
949- method = 2
950- options_string += f" method={ method } "
951- elif solver_type in cplex_types :
952- if method == "barrier" :
953- method = 4
954- options_string += f" LPMethod={ method } "
955- else :
956- raise NotImplementedError (
957- f"Can't specify method { method } for solver { solver_type } "
927+ if model .options .threads :
928+ model .options .solver_options_string += f" Threads={ model .options .threads } "
929+
930+ if model .options .solver_method is not None :
931+ # If no string is passed make the string empty so we can add to it
932+ if model .options .solver_options_string is None :
933+ model .options .solver_options_string = ""
934+
935+ model .options .solver_options_string += (
936+ f" method={ model .options .solver_method } "
958937 )
959938
939+ solver_manager = SolverManagerFactory (model .options .solver_manager )
940+
960941 # get solver arguments
961942 solver_args = dict (
962- options_string = options_string ,
943+ options_string = model . options . solver_options_string ,
963944 keepfiles = model .options .keepfiles ,
964945 tee = model .options .tee ,
965946 symbolic_solver_labels = model .options .symbolic_solver_labels ,
0 commit comments