Source code for mpisppy.opt.ef

# Copyright 2020 by B. Knueven, D. Mildebrath, C. Muir, J-P Watson, and D.L. Woodruff
# This software is distributed under the 3-clause BSD License.
import mpisppy.spbase
import pyomo.environ as pyo
import logging
import mpisppy.utils.sputils as sputils

logger = logging.getLogger("mpisppy.ef")

[docs] class ExtensiveForm(mpisppy.spbase.SPBase): """ Create and solve an extensive form. Attributes: ef (:class:`pyomo.environ.ConcreteModel`): Pyomo model of the extensive form. solver: Solver produced by the Pyomo solver factory. Args: options (dict): Dictionary of options. May include a `solver` key to specify which solver name to use on the EF. all_scenario_names (list): List of the names of each scenario in the EF (strings). scenario_creator (callable): Scenario creator function, which takes as input a scenario name, and returns a Pyomo model of that scenario. model_name (str, optional): Name of the resulting EF model object. scenario_creator_kwargs (dict): Keyword args passed to `scenario_creator`. suppress_warnings (bool, optional): Boolean to suppress warnings when building the EF. Default is False. Note: allowing use of the "solver" option key is for backward compatibility """ def __init__( self, options, all_scenario_names, scenario_creator, scenario_creator_kwargs=None, all_nodenames=None, model_name=None, suppress_warnings=False, ): """ Create the EF and associated solver. """ super().__init__( options, all_scenario_names, scenario_creator, scenario_creator_kwargs=scenario_creator_kwargs, all_nodenames=all_nodenames ) self.bundling = True if self.n_proc > 1 and self.cylinder_rank == 0: logger.warning("Creating an ExtensiveForm object in parallel. Why?") required = ["solver"] self._options_check(required, self.options) self.solver = pyo.SolverFactory(self.options["solver"]) self.ef = sputils._create_EF_from_scen_dict(self.local_scenarios, EF_name=model_name)
[docs] def solve_extensive_form(self, solver_options=None, tee=False): """ Solve the extensive form. Args: solver_options (dict, optional): Dictionary of solver-specific options (e.g. Gurobi options, CPLEX options, etc.). tee (bool, optional): If True, displays solver output. Default False. Returns: :class:`pyomo.opt.results.results_.SolverResults`: Result returned by the Pyomo solve method. """ if "persistent" in self.options["solver"]: self.solver.set_instance(self.ef) # Pass solver-specifiec (e.g. Gurobi, CPLEX) options if solver_options is not None: for (opt, value) in solver_options.items(): self.solver.options[opt] = value results = self.solver.solve(self.ef, tee=tee, load_solutions=False) if len(results.solution) > 0: if sputils.is_persistent(self.solver): self.solver.load_vars() else: self.ef.solutions.load_from(results) self.first_stage_solution_available = True self.tree_solution_available = True return results
[docs] def get_objective_value(self): """ Retrieve the objective value. Returns: float: Objective value. Raises: ValueError: If optimal objective value could not be retrieved. """ try: obj_val = pyo.value(self.ef.EF_Obj) except Exception as e: raise ValueError(f"Could not extract EF objective value with error: {str(e)}") return obj_val
[docs] def get_root_solution(self): """ Get the value of the variables at the root node. Returns: dict: Dictionary mapping variable name (str) to variable value (float) for all variables at the root node. """ result = dict() for var in self.ef.ref_vars.values(): var_name = var.name dot_index = var_name.find(".") if dot_index >= 0 and var_name[:dot_index] in self.all_scenario_names: var_name = var_name[dot_index+1:] result[var_name] = var.value return result
[docs] def nonants(self): """ An iterator to give representative Vars subject to non-anticipitivity Args: None Yields: tree node name, full EF Var name, Var value """ yield from sputils.ef_nonants(self.ef)
[docs] def nonants_to_csv(self, filename): """ Dump the nonant vars from an ef to a csv file; truly a dump... Args: filename (str): the full name of the csv output file """ sputils.ef_nonants_csv(self.ef, filename)
[docs] def scenarios(self): """ An iterator to give the scenario sub-models in an ef Args: None Yields: scenario name, scenario instance (str, ConcreteModel) """ yield from self.local_scenarios.items()
if __name__ == "__main__": # for ad hoc developer testing import mpisppy.tests.examples.farmer as farmer """ Farmer example """ scenario_names = ["Scen" + str(i) for i in range(3)] scenario_creator_kwargs = {"sense": pyo.minimize, "use_integer": False} options = {"solver": "gurobi"} ef = ExtensiveForm( options, scenario_names, farmer.scenario_creator, model_name="TestEF", scenario_creator_kwargs=scenario_creator_kwargs, ) results = ef.solve_extensive_form() print("Farmer objective value:", pyo.value(ef.ef.EF_Obj))