3535from switch_model .tools .graph .cli_graph import main as graph_main
3636from switch_model .utilities .patches import patch_pyomo
3737from switch_model .utilities .results_info import save_info , add_info , ResultsInfoSection
38+ import switch_model .utilities .gurobi_aug # We keep this line here to ensure that 'gurobi_aug' gets registered as a solver
3839
3940
4041def main (
@@ -155,9 +156,6 @@ def debug(type, value, tb):
155156 # create an instance (also reports time spent reading data and loading into model)
156157 instance = model .load_inputs (attach_data_portal = attach_data_portal )
157158
158- if instance .options .warm_start :
159- warm_start (instance )
160-
161159 #### Below here, we refer to instance instead of model ####
162160
163161 instance .pre_solve ()
@@ -190,6 +188,13 @@ def debug(type, value, tb):
190188 # We no longer need model (only using instance) so we can garbage collect it to optimize our memory usage
191189 del model
192190
191+ if instance .options .warm_start_mip :
192+ if instance .options .verbose :
193+ timer .step_time ()
194+ warm_start_mip (instance )
195+ if instance .options .verbose :
196+ print (f"Loaded warm start inputs in { timer .step_time_as_str ()} ." )
197+
193198 if instance .options .reload_prior_solution :
194199 print ("Loading prior solution..." )
195200 reload_prior_solution_from_pickle (instance , instance .options .outputs_dir )
@@ -276,14 +281,14 @@ def debug(type, value, tb):
276281 )
277282
278283
279- def warm_start (instance ):
284+ def warm_start_mip (instance ):
280285 """
281- This function loads in the variables from a previous run
282- and starts out our model at these variables to make it reach
283- a solution faster.
286+ This function loads the results from a previous run into the Pyomo variables.
287+ This allows Gurobi's Mixed Integer Programming algorithm to "warm start" (start closer to the solution).
288+ Warm starting only works in Gurobi if the initial values don't violate any constraints
289+ (i.e. valid but not optimal solution).
284290 """
285- warm_start_timer = StepTimer ()
286- warm_start_dir = os .path .join (instance .options .warm_start , "outputs" )
291+ warm_start_dir = os .path .join (instance .options .warm_start_mip , "outputs" )
287292 if not os .path .isdir (warm_start_dir ):
288293 warnings .warn (
289294 f"Path { warm_start_dir } does not exist and cannot be used to warm start solver. Warm start skipped."
@@ -310,8 +315,6 @@ def warm_start(instance):
310315 # If the index isn't valid that's ok, just don't warm start that variable
311316 pass
312317
313- print (f"Loaded warm start inputs in { warm_start_timer .step_time_as_str ()} ." )
314-
315318
316319def reload_prior_solution_from_pickle (instance , outdir ):
317320 with open (os .path .join (outdir , "results.pickle" ), "rb" ) as fh :
@@ -528,6 +531,12 @@ def define_arguments(argparser):
528531 help = "A quoted string of options to pass to the model solver. Each option must be of the form option=value. "
529532 "(e.g., --solver-options-string \" mipgap=0.001 primalopt='' advance=2 threads=1\" )" ,
530533 )
534+ argparser .add_argument (
535+ "--solver-method" ,
536+ default = None ,
537+ type = int ,
538+ help = "Specify the solver method to use." ,
539+ )
531540 argparser .add_argument (
532541 "--keepfiles" ,
533542 action = "store_true" ,
@@ -639,6 +648,12 @@ def define_arguments(argparser):
639648 action = "store_true" ,
640649 help = "Save the solution to a pickle file after model is solved to allow for later inspection via --reload-prior-solution." ,
641650 )
651+ argparser .add_argument (
652+ "--save-warm-start" ,
653+ default = False ,
654+ action = "store_true" ,
655+ help = "Save warm_start.pickle to the outputs which allows future runs to warm start from this one." ,
656+ )
642657 argparser .add_argument (
643658 "--interact" ,
644659 default = False ,
@@ -685,10 +700,19 @@ def define_arguments(argparser):
685700 help = "Number of threads to be used while solving. Currently only supported for Gurobi" ,
686701 )
687702
703+ argparser .add_argument (
704+ "--warm-start-mip" ,
705+ default = None ,
706+ help = "Enables warm start for a Mixed Integer problem by specifying the "
707+ "path to a previous scenario. Warm starting only works if the solution to the previous solution"
708+ "is also a feasible (but not necessarily optimal) solution to the current scenario." ,
709+ )
710+
688711 argparser .add_argument (
689712 "--warm-start" ,
690713 default = None ,
691- help = "Path to folder of directory to use for warm start" ,
714+ help = "Enables warm start for a LP Problem by specifying the path to the previous scenario. Note"
715+ " that all variables must be the same between the previous and current scenario." ,
692716 )
693717
694718
@@ -719,7 +743,7 @@ def add_recommended_args(argparser):
719743 "--recommended-debug" ,
720744 default = False ,
721745 action = "store_true" ,
722- help = 'Equivalent to running with all of the following options: --solver gurobi -v --sorted-output -- keepfiles --tempdir temp --stream-output -- symbolic-solver-labels --log-run --debug --solver-options-string "method=2 BarHomogeneous=1 FeasibilityTol=1e-5"' ,
746+ help = "Same as --recommended but adds the flags -- keepfiles --tempdir temp --symbolic-solver-labels which are useful when debugging Gurobi." ,
723747 )
724748
725749
@@ -748,8 +772,10 @@ def parse_recommended_args(args):
748772 "--log-run" ,
749773 "--debug" ,
750774 "--graph" ,
775+ "--solver-method" ,
776+ "2" , # Method 2 is barrier solve which is much faster than default
751777 ] + args
752- solver_options_string = "BarHomogeneous=1 FeasibilityTol=1e-5 method=2 "
778+ solver_options_string = "BarHomogeneous=1 FeasibilityTol=1e-5"
753779 if options .recommended_fast :
754780 solver_options_string += " crossover=0"
755781 args = ["--solver-options-string" , solver_options_string ] + args
@@ -851,6 +877,18 @@ def solve(model):
851877 solver = model .solver
852878 solver_manager = model .solver_manager
853879 else :
880+ # If we need warm start switch the solver to our augmented version that supports warm starting
881+ if model .options .warm_start is not None or model .options .save_warm_start :
882+ if model .options .solver not in ("gurobi" , "gurobi_direct" ):
883+ raise NotImplementedError (
884+ "Warm start functionality requires --solver gurobi"
885+ )
886+ model .options .solver = "gurobi_aug"
887+
888+ if model .options .warm_start is not None :
889+ # Method 1 (dual simplex) is required since it supports warm starting.
890+ model .options .solver_method = 1
891+
854892 # Create a solver object the first time in. We don't do this until a solve is
855893 # requested, because sometimes a different solve function may be used,
856894 # with its own solver object (e.g., with runph or a parallel solver server).
@@ -883,6 +921,15 @@ def solve(model):
883921
884922 model .options .solver_options_string += f" Threads={ model .options .threads } "
885923
924+ if model .options .solver_method is not None :
925+ # If no string is passed make the string empty so we can add to it
926+ if model .options .solver_options_string is None :
927+ model .options .solver_options_string = ""
928+
929+ model .options .solver_options_string += (
930+ f" method={ model .options .solver_method } "
931+ )
932+
886933 solver_manager = SolverManagerFactory (model .options .solver_manager )
887934
888935 # get solver arguments
@@ -896,9 +943,17 @@ def solve(model):
896943 else None ,
897944 )
898945
899- if model .options .warm_start is not None :
946+ if model .options .warm_start_mip is not None or model . options . warm_start is not None :
900947 solver_args ["warmstart" ] = True
901948
949+ if model .options .warm_start is not None :
950+ solver_args ["read_warm_start" ] = os .path .join (
951+ model .options .warm_start , "outputs" , "warm_start.pickle"
952+ )
953+
954+ if model .options .save_warm_start :
955+ solver_args ["write_warm_start" ] = os .path .join ("outputs" , "warm_start.pickle" )
956+
902957 # drop all the unspecified options
903958 solver_args = {k : v for (k , v ) in solver_args .items () if v is not None }
904959
0 commit comments