2424from switch_model .tools .graph .cli_graph import main as graph_main
2525from switch_model .utilities .patches import patch_pyomo
2626from switch_model .utilities .results_info import save_info , add_info , ResultsInfoSection
27-
27+ import switch_model . utilities . gurobi_aug # We keep this line here to ensure that 'gurobi_aug' gets registered as a solver
2828
2929def main (args = None , return_model = False , return_instance = False , attach_data_portal = False ):
3030 start_to_end_timer = StepTimer ()
@@ -128,9 +128,6 @@ def debug(type, value, tb):
128128 # create an instance (also reports time spent reading data and loading into model)
129129 instance = model .load_inputs (attach_data_portal = attach_data_portal )
130130
131- if instance .options .warm_start :
132- warm_start (instance )
133-
134131 #### Below here, we refer to instance instead of model ####
135132
136133 instance .pre_solve ()
@@ -161,6 +158,13 @@ def debug(type, value, tb):
161158 # We no longer need model (only using instance) so we can garbage collect it to optimize our memory usage
162159 del model
163160
161+ if instance .options .warm_start_mip :
162+ if instance .options .verbose :
163+ timer .step_time ()
164+ warm_start_mip (instance )
165+ if instance .options .verbose :
166+ print (f"Loaded warm start inputs in { timer .step_time_as_str ()} ." )
167+
164168 if instance .options .reload_prior_solution :
165169 print ('Loading prior solution...' )
166170 reload_prior_solution_from_pickle (instance , instance .options .outputs_dir )
@@ -231,14 +235,14 @@ def debug(type, value, tb):
231235 code .interact (banner = banner , local = dict (list (globals ().items ()) + list (locals ().items ())))
232236
233237
234- def warm_start (instance ):
238+ def warm_start_mip (instance ):
235239 """
236- This function loads in the variables from a previous run
237- and starts out our model at these variables to make it reach
238- a solution faster.
240+ This function loads the results from a previous run into the Pyomo variables.
241+ This allows Gurobi's Mixed Integer Programming algorithm to "warm start" (start closer to the solution).
242+ Warm starting only works in Gurobi if the initial values don't violate any constraints
243+ (i.e. valid but not optimal solution).
239244 """
240- warm_start_timer = StepTimer ()
241- warm_start_dir = os .path .join (instance .options .warm_start , "outputs" )
245+ warm_start_dir = os .path .join (instance .options .warm_start_mip , "outputs" )
242246 if not os .path .isdir (warm_start_dir ):
243247 warnings .warn (
244248 f"Path { warm_start_dir } does not exist and cannot be used to warm start solver. Warm start skipped." )
@@ -262,8 +266,6 @@ def warm_start(instance):
262266 # If the index isn't valid that's ok, just don't warm start that variable
263267 pass
264268
265- print (f"Loaded warm start inputs in { warm_start_timer .step_time_as_str ()} ." )
266-
267269
268270def reload_prior_solution_from_pickle (instance , outdir ):
269271 with open (os .path .join (outdir , 'results.pickle' ), 'rb' ) as fh :
@@ -442,6 +444,8 @@ def define_arguments(argparser):
442444 argparser .add_argument ("--solver-options-string" , default = None ,
443445 help = 'A quoted string of options to pass to the model solver. Each option must be of the form option=value. '
444446 '(e.g., --solver-options-string "mipgap=0.001 primalopt=\' \' advance=2 threads=1")' )
447+ argparser .add_argument ("--solver-method" , default = None , type = int ,
448+ help = "Specify the solver method to use." )
445449 argparser .add_argument ("--keepfiles" , action = 'store_true' , default = None ,
446450 help = "Keep temporary files produced by the solver (may be useful with --symbolic-solver-labels)" )
447451 argparser .add_argument (
@@ -503,6 +507,10 @@ def define_arguments(argparser):
503507 argparser .add_argument (
504508 '--save-solution' , default = False , action = 'store_true' ,
505509 help = "Save the solution to a pickle file after model is solved to allow for later inspection via --reload-prior-solution." )
510+ argparser .add_argument (
511+ '--save-warm-start' , default = False , action = 'store_true' ,
512+ help = "Save warm_start.pickle to the outputs which allows future runs to warm start from this one."
513+ )
506514 argparser .add_argument (
507515 '--interact' , default = False , action = 'store_true' ,
508516 help = 'Enter interactive shell after solving the instance to enable inspection of the solved model.' )
@@ -533,9 +541,17 @@ def define_arguments(argparser):
533541 help = "Number of threads to be used while solving. Currently only supported for Gurobi"
534542 )
535543
544+ argparser .add_argument (
545+ "--warm-start-mip" , default = None ,
546+ help = "Enables warm start for a Mixed Integer problem by specifying the "
547+ "path to a previous scenario. Warm starting only works if the solution to the previous solution"
548+ "is also a feasible (but not necessarily optimal) solution to the current scenario."
549+ )
550+
536551 argparser .add_argument (
537552 "--warm-start" , default = None ,
538- help = "Path to folder of directory to use for warm start"
553+ help = "Enables warm start for a LP Problem by specifying the path to the previous scenario. Note"
554+ " that all variables must be the same between the previous and current scenario."
539555 )
540556
541557
@@ -560,7 +576,7 @@ def add_recommended_args(argparser):
560576
561577 argparser .add_argument (
562578 "--recommended-debug" , default = False , action = 'store_true' ,
563- 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" '
579+ help = 'Same as --recommended but adds the flags -- keepfiles --tempdir temp --symbolic-solver-labels which are useful when debugging Gurobi. '
564580 )
565581
566582
@@ -584,8 +600,9 @@ def parse_recommended_args(args):
584600 '--log-run' ,
585601 '--debug' ,
586602 '--graph' ,
603+ '--solver-method' , '2' , # Method 2 is barrier solve which is much faster than default
587604 ] + args
588- solver_options_string = "BarHomogeneous=1 FeasibilityTol=1e-5 method=2 "
605+ solver_options_string = "BarHomogeneous=1 FeasibilityTol=1e-5"
589606 if options .recommended_fast :
590607 solver_options_string += " crossover=0"
591608 args = ['--solver-options-string' , solver_options_string ] + args
@@ -671,6 +688,16 @@ def solve(model):
671688 solver = model .solver
672689 solver_manager = model .solver_manager
673690 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+
674701 # Create a solver object the first time in. We don't do this until a solve is
675702 # requested, because sometimes a different solve function may be used,
676703 # with its own solver object (e.g., with runph or a parallel solver server).
@@ -701,6 +728,13 @@ def solve(model):
701728
702729 model .options .solver_options_string += f" Threads={ model .options .threads } "
703730
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+
704738 solver_manager = SolverManagerFactory (model .options .solver_manager )
705739
706740 # get solver arguments
@@ -712,9 +746,15 @@ def solve(model):
712746 save_results = model .options .save_solution if isinstance (solver , DirectOrPersistentSolver ) else None ,
713747 )
714748
715- if model .options .warm_start is not None :
749+ if model .options .warm_start_mip is not None or model . options . warm_start is not None :
716750 solver_args ["warmstart" ] = True
717751
752+ if model .options .warm_start is not None :
753+ solver_args ["read_warm_start" ] = os .path .join (model .options .warm_start , "outputs" , "warm_start.pickle" )
754+
755+ if model .options .save_warm_start :
756+ solver_args ["write_warm_start" ] = os .path .join ("outputs" , "warm_start.pickle" )
757+
718758 # drop all the unspecified options
719759 solver_args = {k : v for (k , v ) in solver_args .items () if v is not None }
720760
0 commit comments