Source code for pyomo.contrib.pyros.solve_data

#  ___________________________________________________________________________
#
#  Pyomo: Python Optimization Modeling Objects
#  Copyright (c) 2008-2024
#  National Technology and Engineering Solutions of Sandia, LLC
#  Under the terms of Contract DE-NA0003525 with National Technology and
#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
#  rights in this software.
#  This software is distributed under the 3-clause BSD License.
#  ___________________________________________________________________________

"""
Containers for PyROS subproblem solve results.
"""


[docs] class ROSolveResults(object): """ PyROS solver results object. Parameters ---------- config : ConfigDict, optional User-specified solver settings. iterations : int, optional Number of iterations required. time : float, optional Total elapsed time (or wall time), in seconds. final_objective_value : float, optional Final objective function value to report. pyros_termination_condition : pyrosTerminationCondition, optional PyROS-specific termination condition. Attributes ---------- config : ConfigDict User-specified solver settings. iterations : int Number of iterations required by PyROS. time : float Total elapsed time (or wall time), in seconds. final_objective_value : float Final objective function value to report. If a nominal objective focus was elected, then the value of the nominal objective function is reported. If a worst-case objective focus was elected, then the value of the worst-case objective function is reported. pyros_termination_condition : pyrosTerminationCondition Indicator of the manner of termination. """
[docs] def __init__( self, config=None, iterations=None, time=None, final_objective_value=None, pyros_termination_condition=None, ): """Initialize self (see class docstring).""" self.config = config self.iterations = iterations self.time = time self.final_objective_value = final_objective_value self.pyros_termination_condition = pyros_termination_condition
def __str__(self): """ Generate string representation of self. Does not include any information about `self.config`. """ lines = ["Termination stats:"] attr_name_format_dict = { "iterations": ("Iterations", "f'{val}'"), "time": ("Solve time (wall s)", "f'{val:.3f}'"), "final_objective_value": ("Final objective value", "f'{val:.4e}'"), "pyros_termination_condition": ("Termination condition", "f'{val}'"), } attr_desc_pad_length = max( len(desc) for desc, _ in attr_name_format_dict.values() ) for attr_name, (attr_desc, fmt_str) in attr_name_format_dict.items(): val = getattr(self, attr_name) val_str = eval(fmt_str) if val is not None else str(val) lines.append(f" {attr_desc:<{attr_desc_pad_length}s} : {val_str}") return "\n".join(lines)
[docs] class MasterResults: """ Result of solving the master problem in a single PyROS iteration. Attributes ---------- master_model : ConcreteModel Master model. feasibility_problem_results : SolverResults Feasibility problem subsolver results. master_results_list : list of SolverResults List of subsolver results for the master problem. pyros_termination_condition : None or pyrosTerminationCondition PyROS termination status established via solution of the master problem. If `None`, then no termination status has been established. """
[docs] def __init__( self, master_model=None, feasibility_problem_results=None, master_results_list=None, pyros_termination_condition=None, ): """Initialize self (see class docstring).""" self.master_model = master_model self.feasibility_problem_results = feasibility_problem_results if master_results_list is None: self.master_results_list = [] else: self.master_results_list = list(master_results_list) self.pyros_termination_condition = pyros_termination_condition
[docs] class SeparationSolveCallResults: """ Container for results of solve attempt for single separation problem. Parameters ---------- solved_globally : bool True if separation problem was solved globally, False otherwise. results_list : list of pyomo.opt.results.SolverResults, optional Pyomo solver results for each subordinate optimizer invoked on the separation problem. For problems with non-discrete uncertainty set types, each entry corresponds to a single subordinate solver. For problems with discrete set types, the list may be empty (didn't need to use a subordinate solver to evaluate optimal separation solution), or the number of entries may be as high as the product of the number of subordinate local/global solvers provided (including backup) and the number of scenarios in the uncertainty set. scaled_violations : ComponentMap, optional Mapping from second-stage inequality constraints to floats equal to their scaled violations by separation problem solution stored in this result. violating_param_realization : list of float, optional Uncertain parameter realization for reported separation problem solution. auxiliary_param_values : list of float, optional Auxiliary parameter values corresponding to the uncertain parameter realization `violating_param_realization`. variable_values : ComponentMap, optional Second-stage DOF and state variable values for reported separation problem solution. found_violation : bool, optional True if violation of second-stage inequality constraint (i.e. constraint expression value) by reported separation solution was found to exceed tolerance, False otherwise. time_out : bool, optional True if PyROS time limit reached attempting to solve the separation problem, False otherwise. subsolver_error : bool, optional True if subsolvers found to be unable to solve separation problem of interest, False otherwise. discrete_set_scenario_index : None or int, optional If discrete set used to solve the problem, index of `violating_param_realization` as listed in the `scenarios` attribute of a ``DiscreteScenarioSet`` instance. If discrete set not used, pass None. Attributes ---------- solved_globally results_list scaled_violations violating_param_realizations auxiliary_param_values variable_values found_violation time_out subsolver_error discrete_set_scenario_index """
[docs] def __init__( self, solved_globally, results_list=None, scaled_violations=None, violating_param_realization=None, auxiliary_param_values=None, variable_values=None, found_violation=None, time_out=None, subsolver_error=None, discrete_set_scenario_index=None, ): """Initialize self (see class docstring).""" self.results_list = results_list self.solved_globally = solved_globally self.scaled_violations = scaled_violations self.violating_param_realization = violating_param_realization self.auxiliary_param_values = auxiliary_param_values self.variable_values = variable_values self.found_violation = found_violation self.time_out = time_out self.subsolver_error = subsolver_error self.discrete_set_scenario_index = discrete_set_scenario_index
[docs] def termination_acceptable(self, acceptable_terminations): """ Return True if termination condition for at least one result in `self.results_list` is in list of pre-specified acceptable terminations, False otherwise. Parameters ---------- acceptable_terminations : set of pyomo.opt.TerminationCondition Acceptable termination conditions. Returns ------- bool """ return any( res.solver.termination_condition in acceptable_terminations for res in self.results_list )
[docs] class DiscreteSeparationSolveCallResults: """ Container for results of solve attempt for single separation problem. Parameters ---------- solved_globally : bool True if separation problems solved to global optimality, False otherwise. solver_call_results : dict Mapping from discrete uncertainty set scenario list indexes to solver call results for separation problems subject to the scenarios. second_stage_ineq_con : Constraint Separation problem second-stage inequality constraint for which `self` was generated. Attributes ---------- solved_globally solver_call_results second_stage_ineq_con """
[docs] def __init__( self, solved_globally, solver_call_results=None, second_stage_ineq_con=None ): """Initialize self (see class docstring).""" self.solved_globally = solved_globally self.solver_call_results = solver_call_results self.second_stage_ineq_con = second_stage_ineq_con
@property def time_out(self): """ bool : True if there is a time out status for at least one of the ``SeparationSolveCallResults`` objects listed in `self`, False otherwise. """ return any(res.time_out for res in self.solver_call_results.values()) @property def subsolver_error(self): """ bool : True if there is a subsolver error status for at least one of the the ``SeparationSolveCallResults`` objects listed in `self`, False otherwise. """ return any(res.subsolver_error for res in self.solver_call_results.values())
[docs] class SeparationLoopResults: """ Container for results of all separation problems solved to a single desired optimality target (local or global). Parameters ---------- solved_globally : bool True if separation problems were solved to global optimality, False otherwise. solver_call_results : ComponentMap Mapping from second-stage inequality constraints to corresponding ``SeparationSolveCallResults`` objects. worst_case_ss_ineq_con : None or Constraint Second-stage inequality constraint mapped to ``SeparationSolveCallResults`` object in `self` corresponding to maximally violating separation problem solution. all_discrete_scenarios_exhausted : bool, optional For problems with discrete uncertainty sets, True if all scenarios were explicitly accounted for in master (which occurs if there have been as many PyROS iterations as there are scenarios in the set) False otherwise. Attributes ---------- solved_globally : bool True if global solver was used, False otherwise. solver_call_results : ComponentMap Mapping from second-stage inequality constraints to corresponding ``SeparationSolveCallResults`` objects. worst_case_ss_ineq_con : None or ConstraintData Worst-case second-stage inequality constraint. all_discrete_scenarios_exhausted : bool True if all scenarios of the discrete set were exhausted already explicitly accounted for in the master problems, False otherwise. """
[docs] def __init__( self, solved_globally, solver_call_results, worst_case_ss_ineq_con, all_discrete_scenarios_exhausted=False, ): """Initialize self (see class docstring).""" self.solver_call_results = solver_call_results self.solved_globally = solved_globally self.worst_case_ss_ineq_con = worst_case_ss_ineq_con self.all_discrete_scenarios_exhausted = all_discrete_scenarios_exhausted
@property def found_violation(self): """ bool : True if separation solution for at least one ``SeparationSolveCallResults`` object listed in self was reported to violate its corresponding second-stage inequality constraint, False otherwise. """ return any( solver_call_res.found_violation for solver_call_res in self.solver_call_results.values() ) @property def violating_param_realization(self): """ None or list of float : Uncertain parameter values for for maximally violating separation problem solution, specified according to solver call results object listed in self at index ``self.worst_case_ss_ineq_con``. If ``self.worst_case_ss_ineq_con`` is not specified, then None is returned. """ if self.worst_case_ss_ineq_con is not None: return self.solver_call_results[ self.worst_case_ss_ineq_con ].violating_param_realization else: return None @property def auxiliary_param_values(self): """ None or list of float : Auxiliary parameter values for the maximially violating separation problem solution. """ if self.worst_case_ss_ineq_con is not None: return self.solver_call_results[ self.worst_case_ss_ineq_con ].auxiliary_param_values else: return None @property def scaled_violations(self): """ None or ComponentMap : Scaled second-stage inequality constraint violations for maximally violating separation problem solution, specified according to solver call results object listed in self at index ``self.worst_case_ss_ineq_con``. If ``self.worst_case_ss_ineq_con`` is not specified, then None is returned. """ if self.worst_case_ss_ineq_con is not None: return self.solver_call_results[ self.worst_case_ss_ineq_con ].scaled_violations else: return None @property def violating_separation_variable_values(self): """ None or ComponentMap : Second-stage and state variable values for maximally violating separation problem solution, specified according to solver call results object listed in self at index ``self.worst_case_ss_ineq_con``. If ``self.worst_case_ss_ineq_con`` is not specified, then None is returned. """ if self.worst_case_ss_ineq_con is not None: return self.solver_call_results[self.worst_case_ss_ineq_con].variable_values else: return None @property def violated_second_stage_ineq_cons(self): """ list of Constraint : Second-stage inequality constraints for which violation found. """ return [ con for con, solver_call_results in self.solver_call_results.items() if solver_call_results.found_violation ] @property def subsolver_error(self): """ bool : Return True if subsolver error reported for at least one ``SeparationSolveCallResults`` stored in `self`, False otherwise. """ return any( solver_call_res.subsolver_error for solver_call_res in self.solver_call_results.values() ) @property def time_out(self): """ bool : Return True if time out reported for at least one ``SeparationSolveCallResults`` stored in `self`, False otherwise. """ return any( solver_call_res.time_out for solver_call_res in self.solver_call_results.values() )
[docs] class SeparationResults: """ Container for results of PyROS separation problem routine. Parameters ---------- local_separation_loop_results : None or SeparationLoopResults Local separation problem loop results. global_separation_loop_results : None or SeparationLoopResults Global separation problem loop results. Attributes ---------- local_separation_loop_results : None or SeparationLoopResults Local separation results. If separation problems were not solved locally, then this attribute is set to None. global_separation_loop_results : None or SeparationLoopResults Global separation results. If separation problems were not solved globally, then this attribute is set to None. """
[docs] def __init__(self, local_separation_loop_results, global_separation_loop_results): """Initialize self (see class docstring).""" self.local_separation_loop_results = local_separation_loop_results self.global_separation_loop_results = global_separation_loop_results
@property def time_out(self): """ bool : True if time out found for local or global separation loop, False otherwise. """ local_time_out = ( self.solved_locally and self.local_separation_loop_results.time_out ) global_time_out = ( self.solved_globally and self.global_separation_loop_results.time_out ) return local_time_out or global_time_out @property def subsolver_error(self): """ bool : True if subsolver error found for local or global separation loop, False otherwise. """ local_subsolver_error = ( self.solved_locally and self.local_separation_loop_results.subsolver_error ) global_subsolver_error = ( self.solved_globally and self.global_separation_loop_results.subsolver_error ) return local_subsolver_error or global_subsolver_error @property def solved_locally(self): """ bool : true if local separation loop was invoked, False otherwise. """ return self.local_separation_loop_results is not None @property def solved_globally(self): """ bool : True if global separation loop was invoked, False otherwise. """ return self.global_separation_loop_results is not None
[docs] def get_violating_attr(self, attr_name): """ If separation problems solved globally, returns value of attribute of global separation loop results. Otherwise, if separation problems solved locally, returns value of attribute of local separation loop results. If local separation loop results specified, return value of attribute of local separation loop results. Otherwise, if global separation loop results specified, return value of attribute of global separation loop results. Otherwise, return None. Parameters ---------- attr_name : str Name of attribute to be retrieved. Should be valid attribute name for object of type ``SeparationLoopResults``. Returns ------- object Attribute value. """ return getattr(self.main_loop_results, attr_name, None)
@property def all_discrete_scenarios_exhausted(self): """ bool : For problems where the uncertainty set is of type DiscreteScenarioSet, True if last master problem solved explicitly accounts for all scenarios in the uncertainty set, False otherwise. """ return self.get_violating_attr("all_discrete_scenarios_exhausted") @property def worst_case_ss_ineq_con(self): """ ConstraintData : Second-stage inequality constraint corresponding to the separation solution chosen for the next master problem. """ return self.get_violating_attr("worst_case_ss_ineq_con") @property def main_loop_results(self): """ SeparationLoopResults : Main separation loop results. In particular, this is considered to be the global loop result if solved globally, and the local loop results otherwise. """ if self.solved_globally: return self.global_separation_loop_results return self.local_separation_loop_results @property def found_violation(self): """ bool : True if ``found_violation`` attribute for main separation loop results is True, False otherwise. """ found_viol = self.get_violating_attr("found_violation") if found_viol is None: found_viol = False return found_viol @property def violating_param_realization(self): """ None or list of float : Uncertain parameter values for maximally violating separation problem solution reported in local or global separation loop results. If no such solution found, (i.e. ``worst_case_ss_ineq_con`` set to None for both local and global loop results), then None is returned. """ return self.get_violating_attr("violating_param_realization") @property def auxiliary_param_values(self): """ None or list of float: Auxiliary parameter values accompanying `self.violating_param_realization`. """ return self.get_violating_attr("auxiliary_param_values") @property def scaled_violations(self): """ None or ComponentMap : Scaled second-stage inequality constraint violations for maximally violating separation problem solution reported in local or global separation loop results. If no such solution found, (i.e. ``worst_case_ss_ineq_con`` set to None for both local and global loop results), then None is returned. """ return self.get_violating_attr("scaled_violations") @property def violating_separation_variable_values(self): """ None or ComponentMap : Second-stage and state variable values for maximally violating separation problem solution reported in local or global separation loop results. If no such solution found, (i.e. ``worst_case_ss_ineq_con`` set to None for both local and global loop results), then None is returned. """ return self.get_violating_attr("violating_separation_variable_values") @property def violated_second_stage_ineq_cons(self): """ Return list of violated second-stage inequality constraints. """ return self.get_violating_attr("violated_second_stage_ineq_cons") @property def robustness_certified(self): """ bool : Return True if separation results certify that first-stage solution is robust, False otherwise. """ assert self.solved_locally or self.solved_globally if self.time_out or self.subsolver_error: return False if self.solved_locally: heuristically_robust = ( not self.local_separation_loop_results.found_violation ) else: heuristically_robust = None if self.solved_globally: is_robust = not self.global_separation_loop_results.found_violation else: # global separation bypassed, either # because uncertainty set is discrete # or user opted to bypass global separation is_robust = heuristically_robust return is_robust