def __init__(self, options, scenario_names, scenario_creator, kw_creator, scenario_denouement=None, verbose=True): self.options = options self.scenario_names = scenario_names self.scenario_creator = scenario_creator self.scenario_denouement = scenario_denouement self.kw_creator = kw_creator self.kwargs = self.kw_creator(self.options) self.verbose = verbose self.is_EF = _bool_option(options, "EF-2stage") or _bool_option( options, "EF-mstage") if self.is_EF: self.solvername = options['EF_solver_name'] if ( 'EF_solver_name' in options) else 'gurobi' self.solver_options = options['EF_solver_options'] \ if ('EF_solver_options' in options) else {} self.is_multi = _bool_option(options, "EF-mstage") or _bool_option( options, "mstage") if self.is_multi and not "all_nodenames" in options: if "branching_factors" in options: self.options[ "all_nodenames"] = sputils.create_nodenames_from_BFs( options["branching_factors"]) else: raise RuntimeError( "For a multistage problem, please provide branching factors or all_nodenames" )
def add_multistage_options(cylinder_dict, all_nodenames, branching_factors): cylinder_dict = copy.deepcopy(cylinder_dict) if branching_factors is not None: if hasattr(cylinder_dict["opt_kwargs"], "options"): cylinder_dict["opt_kwargs"]["options"][ "branching_factors"] = branching_factors if all_nodenames is None: all_nodenames = sputils.create_nodenames_from_BFs( branching_factors) if all_nodenames is not None: print("Hello, surprise !!") cylinder_dict["opt_kwargs"]["all_nodenames"] = all_nodenames print("Hello,", cylinder_dict) return cylinder_dict
options["xhat_looper_options"] = {"xhat_solver_options":\ None, "scen_limit": 3, "dump_prefix": "delme", "csvname": "looper.csv"} # branching factor (3 stages is hard-wired) BFs = options["branching_factors"] ScenCount = BFs[0] * BFs[1] all_scenario_names = list() for sn in range(ScenCount): all_scenario_names.append("Scen" + str(sn + 1)) # end hardwire # This is multi-stage, so we need to supply node names all_nodenames = sputils.create_nodenames_from_BFs(BFs) # **** ef **** solver = pyo.SolverFactory(options["solvername"]) ef = sputils.create_EF( all_scenario_names, scenario_creator, scenario_creator_kwargs={"branching_factors": BFs}, ) results = solver.solve(ef, tee=options["verbose"]) print('EF objective value:', pyo.value(ef.EF_Obj)) sputils.ef_nonants_csv(ef, "vardump.csv") # **** ph **** options["xhat_specific_options"] = {"xhat_solver_options":
def gap_estimators(xhat_one, mname, solving_type="EF-2stage", scenario_names=None, sample_options=None, ArRP=1, scenario_creator_kwargs={}, scenario_denouement=None, solvername='gurobi', solver_options=None, verbose=True, objective_gap=False ): ''' Given a xhat, scenario names, a scenario creator and options, gap_estimators creates a scenario tree and the associatd estimators G and s from §2 of [bm2011]. Returns G and s evaluated at xhat. If ArRP>1, G and s are pooled, from a number ArRP of estimators, computed with different scenario trees. Parameters ---------- xhat_one : dict A candidate first stage solution mname: str Name of the reference model, e.g. 'mpisppy.tests.examples.farmer'. solving_type: str, optional The way we solve the approximate problem. Can be "EF-2stage" (default) or "EF-mstage". scenario_names: list, optional List of scenario names used to compute G_n and s_n. Default is None Must be specified for 2 stage, but can be missing for multistage sample_options: dict, optional Only for multistage. Must contain a 'seed' and a 'BFs' attribute, specifying the starting seed and the branching factors of the scenario tree ArRP:int,optional Number of batches (we create a ArRP model). Default is 1 (one batch). scenario_creator_kwargs: dict, optional Additional arguments for scenario_creator. Default is {} scenario_denouement: function, optional Function to run after scenario creation. Default is None. solvername : str, optional Solver. Default is 'gurobi' solver_options: dict, optional Solving options. Default is None verbose: bool, optional Should it print the gap estimator ? Default is True objective_gap: bool, optional Returns a gap estimate around approximate objective value BFs: list, optional Only for multistage. List of branching factors of the sample scenario tree. Returns ------- G_k and s_k, gap estimator and associated standard deviation estimator. ''' if solving_type not in ["EF-2stage","EF-mstage"]: raise RuntimeError("Only EF solve for the approximate problem is supported yet.") else: is_multi = (solving_type=="EF-mstage") if is_multi: try: BFs = sample_options['BFs'] start = sample_options['seed'] except (TypeError,KeyError,RuntimeError): raise RuntimeError('For multistage problems, sample_options must be a dict with BFs and seed attributes.') else: start = sputils.extract_num(scenario_names[0]) if ArRP>1: #Special case : ArRP, G and s are pooled from r>1 estimators. if is_multi: raise RuntimeError("Pooled estimators are not supported for multistage problems yet.") n = len(scenario_names) if(n%ArRP != 0): raise RuntimeWarning("You put as an input a number of scenarios"+\ f" which is not a mutliple of {ArRP}.") n = n- n%ArRP G =[] s = [] for k in range(ArRP): scennames = scenario_names[k*(n//ArRP):(k+1)*(n//ArRP)] tmp = gap_estimators(xhat_one, mname, solvername=solvername, scenario_names=scennames, ArRP=1, scenario_creator_kwargs=scenario_creator_kwargs, scenario_denouement=scenario_denouement, solver_options=solver_options, solving_type=solving_type ) G.append(tmp['G']) s.append(tmp['s']) #Pooling G = np.mean(G) s = np.linalg.norm(s)/np.sqrt(n//ArRP) return {"G": G, "s": s, "seed": start} #A1RP #We start by computing the optimal solution to the approximate problem induced by our scenarios if is_multi: #Sample a scenario tree: this is a subtree, but starting from stage 1 samp_tree = sample_tree.SampleSubtree(mname, xhats =[], root_scen=None, starting_stage=1, BFs=BFs, seed=start, options=scenario_creator_kwargs, solvername=solvername, solver_options=solver_options) samp_tree.run() start += sputils.number_of_nodes(BFs) ama_object = samp_tree.ama else: #We use amalgomator to do it ama_options = dict(scenario_creator_kwargs) ama_options['start'] = start ama_options['num_scens'] = len(scenario_names) ama_options['EF_solver_name'] = solvername ama_options['EF_solver_options'] = solver_options ama_options[solving_type] = True ama_object = ama.from_module(mname, ama_options,use_command_line=False) ama_object.scenario_names = scenario_names ama_object.verbose = False ama_object.run() start += len(scenario_names) #Optimal solution of the approximate problem zstar = ama_object.best_outer_bound #Associated policies xstars = sputils.nonant_cache_from_ef(ama_object.ef) #Then, we evaluate the fonction value induced by the scenario at xstar. if is_multi: # Find feasible policies (i.e. xhats) for every non-leaf nodes if len(samp_tree.ef._ef_scenario_names)>1: local_scenarios = {sname:getattr(samp_tree.ef,sname) for sname in samp_tree.ef._ef_scenario_names} else: local_scenarios = {samp_tree.ef._ef_scenario_names[0]:samp_tree.ef} xhats,start = sample_tree.walking_tree_xhats(mname, local_scenarios, xhat_one['ROOT'], BFs, start, scenario_creator_kwargs, solvername=solvername, solver_options=solver_options) #Compute then the average function value with this policy scenario_creator_kwargs = samp_tree.ama.kwargs all_nodenames = sputils.create_nodenames_from_BFs(BFs) else: #In a 2 stage problem, the only non-leaf is the ROOT node xhats = xhat_one all_nodenames = None xhat_eval_options = {"iter0_solver_options": None, "iterk_solver_options": None, "display_timing": False, "solvername": solvername, "verbose": False, "solver_options":solver_options} ev = xhat_eval.Xhat_Eval(xhat_eval_options, scenario_names, ama_object.scenario_creator, scenario_denouement, scenario_creator_kwargs=scenario_creator_kwargs, all_nodenames = all_nodenames) #Evaluating xhat and xstar and getting the value of the objective function #for every (local) scenario ev.evaluate(xhats) objs_at_xhat = ev.objs_dict ev.evaluate(xstars) objs_at_xstar = ev.objs_dict eval_scen_at_xhat = [] eval_scen_at_xstar = [] scen_probs = [] for k,s in ev.local_scenarios.items(): eval_scen_at_xhat.append(objs_at_xhat[k]) eval_scen_at_xstar.append(objs_at_xstar[k]) scen_probs.append(s._mpisppy_probability) scen_gaps = np.array(eval_scen_at_xhat)-np.array(eval_scen_at_xstar) local_gap = np.dot(scen_gaps,scen_probs) local_ssq = np.dot(scen_gaps**2,scen_probs) local_prob_sqnorm = np.linalg.norm(scen_probs)**2 local_obj_at_xhat = np.dot(eval_scen_at_xhat,scen_probs) local_estim = np.array([local_gap,local_ssq,local_prob_sqnorm,local_obj_at_xhat]) global_estim = np.zeros(4) ev.mpicomm.Allreduce(local_estim, global_estim, op=mpi.SUM) G,ssq, prob_sqnorm,obj_at_xhat = global_estim if global_rank==0 and verbose: print(f"G = {G}") sample_var = (ssq - G**2)/(1-prob_sqnorm) #Unbiased sample variance s = np.sqrt(sample_var) use_relative_error = (np.abs(zstar)>1) G = correcting_numeric(G,objfct=obj_at_xhat, relative_error=use_relative_error) if objective_gap: if is_multi: return {"G":G,"s":s,"zhats": [obj_at_xhat], "seed":start} else: return {"G":G,"s":s,"zhats": eval_scen_at_xhat, "seed":start} else: return {"G":G,"s":s,"seed":start}