def MakeNodesforScen(model, BFs, scennum): """ Make just those scenario tree nodes needed by a scenario. Return them as a list. NOTE: the nodes depend on the scenario model and are, in some sense, local to it. Args: BFs (list of int): branching factors """ ndn = "ROOT_"+str((scennum-1) // BFs[0]) # scennum is one-based retval = [scenario_tree.ScenarioNode("ROOT", 1.0, 1, model.StageCost[1], None, [model.Pgt[1], model.Pgh[1], model.PDns[1], model.Vol[1]], model), scenario_tree.ScenarioNode(ndn, 1.0/BFs[0], 2, model.StageCost[2], None, [model.Pgt[2], model.Pgh[2], model.PDns[2], model.Vol[2]], model, parent_name="ROOT") ] return retval
def MakeAllScenarioTreeNodes(model, bf): """ Make the tree nodes and put them in a dictionary. Assume three stages and a branching factor of bf. Note: this might not ever be called. (Except maybe for the EF) Note: mpisppy does not have leaf nodes. Aside: every rank makes their own nodes; these nodes do not hold any data computed by a solution algorithm. """ TreeNodes = dict() TreeNodes["ROOT"] = scenario_tree.ScenarioNode("ROOT", 1.0, 1, model.StageCost[1], None, [model.Pgt[1], model.Pgh[1], model.PDns[1], model.Vol[1]], model) for b in range(bf): ndn = "ROOT_"+str(b) TreeNodes[ndn] = scenario_tree.ScenarioNode(ndn, 1.0/bf, 2, model.StageCost[2], None, [model.Pgt[2], model.Pgh[2], model.PDns[2], model.Vol[2]], model, parent_name="ROOT")
def MakeNodesforScen(model, nodenames, branching_factors, starting_stage=1): #Create all nonleaf nodes used by the scenario #Compatible with sample scenario creation TreeNodes = [] for stage in model.T: nonant_list = [ model.stage_models[stage].RegularProd, model.stage_models[stage].OvertimeProd ] nonant_ef_suppl_list = [model.stage_models[stage].Inventory] if model.start_ups: nonant_ef_suppl_list.append(model.stage_models[stage].StartUp) if stage == 1: ndn = "ROOT" TreeNodes.append( scenario_tree.ScenarioNode( name=ndn, cond_prob=1.0, stage=stage, cost_expression=model.stage_models[stage].StageObjective, scen_name_list=None, # Not maintained nonant_list=nonant_list, scen_model=model, nonant_ef_suppl_list=nonant_ef_suppl_list)) elif stage <= starting_stage: parent_ndn = ndn ndn = parent_ndn + "_0" #Only one node per stage before starting stage TreeNodes.append( scenario_tree.ScenarioNode( name=ndn, cond_prob=1.0, stage=stage, cost_expression=model.stage_models[stage].StageObjective, scen_name_list=None, # Not maintained nonant_list=nonant_list, scen_model=model, nonant_ef_suppl_list=nonant_ef_suppl_list, parent_name=parent_ndn)) elif stage < max(model.T): #We don't add the leaf node parent_ndn = ndn ndn = parent_ndn + "_" + nodenames[stage - starting_stage] TreeNodes.append( scenario_tree.ScenarioNode( name=ndn, cond_prob=1.0 / branching_factors[stage - starting_stage - 1], stage=stage, cost_expression=model.stage_models[stage].StageObjective, scen_name_list=None, # Not maintained nonant_list=nonant_list, scen_model=model, nonant_ef_suppl_list=nonant_ef_suppl_list, parent_name=parent_ndn)) return (TreeNodes)
def scenario_creator(scenario_name, node_names=None, cb_data=None): """ The callback needs to create an instance and then attach the PySP nodes to it in a list _PySPnode_list ordered by stages. Optionally attach _PHrho. Use cb_data for the scenario count (3 or 10) """ if cb_data not in [3, 10]: raise RuntimeError("cb_data passed to scenario counter " "must equal either 3 or 10") sizes_dir = os.path.dirname(mpisppy.examples.sizes.sizes.__file__) datadir = os.sep.join((sizes_dir, f"SIZES{cb_data}")) try: fname = datadir + os.sep + scenario_name + ".dat" except: print("FAIL: datadir=", datadir, " scenario_name=", scenario_name) model = ref.model.create_instance(fname) # now attach the one and only tree node (ROOT is a reserved word) model._PySPnode_list = [ scenario_tree.ScenarioNode( "ROOT", 1.0, 1, model.FirstStageCost, None, [model.NumProducedFirstStage, model.NumUnitsCutFirstStage], model, ) ] return model
def MakeNodesforScen(model, nodenames, branching_factors): #Create all nonleaf nodes used by the scenario TreeNodes = [] for stage in model.T: if stage == 1: ndn = "ROOT" TreeNodes.append( scenario_tree.ScenarioNode( name=ndn, cond_prob=1.0, stage=stage, cost_expression=model.stage_models[stage].StageObjective, scen_name_list=None, # Not maintained nonant_list=[ model.stage_models[stage].RegularProd, model.stage_models[stage].OvertimeProd ], scen_model=model, nonant_ef_suppl_list=[model.stage_models[stage].Inventory], )) elif stage < max(model.T): #We don't add the leaf node parent_ndn = ndn ndn = parent_ndn + "_" + nodenames[stage - 1] TreeNodes.append( scenario_tree.ScenarioNode( name=ndn, cond_prob=1.0 / branching_factors[stage - 2], stage=stage, cost_expression=model.stage_models[stage].StageObjective, scen_name_list=None, # Not maintained nonant_list=[ model.stage_models[stage].RegularProd, model.stage_models[stage].OvertimeProd ], scen_model=model, nonant_ef_suppl_list=[model.stage_models[stage].Inventory], parent_name=parent_ndn)) return (TreeNodes)
def scenario_creator(scenario_name, node_names=None, cb_data=None): """ The callback needs to create an instance and then attach the PySP nodes to it in a list _PySPnode_list ordered by stages. Optionally attach _PHrho. """ datadir = cb_data fname = datadir + os.sep + scenario_name + ".dat" model = ref.model.create_instance(fname, name=scenario_name) # now attach the one and only tree node (ROOT is a reserved word) model._PySPnode_list = [ scenario_tree.ScenarioNode( "ROOT", 1.0, 1, model.FirstStageCost, None, [model.FacilityOpen], model ) ] return model
def attach_root_node(model, firstobj, varlist, nonant_ef_suppl_list=None): """ Create a root node as a list to attach to a scenario model Args: model (ConcreteModel): model to which this will be attached firstobj (Pyomo Expression): First stage cost (e.g. model.FC) varlist (list): Pyomo Vars in first stage (e.g. [model.A, model.B]) nonant_ef_suppl_list (list of pyo Var, Vardata or slices): vars for which nonanticipativity constraints tighten the EF (important for bundling) Note: attaches a list consisting of one scenario node to the model """ model._PySPnode_list = [ scenario_tree.ScenarioNode("ROOT",1.0,1,firstobj, None, varlist, model, nonant_ef_suppl_list = nonant_ef_suppl_list) ]
def scenario_creator(scenario_name, data_dir=None): """ The callback needs to create an instance and then attach the PySP nodes to it in a list _PySPnode_list ordered by stages. Optionally attach _PHrho. """ if data_dir is None: raise ValueError( "kwarg `data_dir` is required for SSLP scenario_creator") fname = data_dir + os.sep + scenario_name + ".dat" model = ref.model.create_instance(fname, name=scenario_name) # now attach the one and only tree node (ROOT is a reserved word) model._PySPnode_list = [ scenario_tree.ScenarioNode("ROOT", 1.0, 1, model.FirstStageCost, None, [model.FacilityOpen], model) ] return model
def scenario_creator(sname, num_scens=None): scennum = sputils.extract_num(sname) model = GBD_model_creator(scennum) # Create the list of nodes associated with the scenario (for two stage, # there is only one node associated with the scenario--leaf nodes are # ignored). model._mpisppy_node_list = [ scenario_tree.ScenarioNode( name="ROOT", cond_prob=1.0, stage=1, cost_expression=model.obj, scen_name_list=None, # Deprecated? nonant_list=[model.x], scen_model=model, ) ] #Add the probability of the scenario if num_scens is not None : model._mpisppy_probability = 1/num_scens return(model)
def pysp2_callback(scenario_name, node_names=None, cb_data=None): ''' The callback needs to create an instance and then attach the PySP nodes to it in a list _PySPnode_list ordered by stages. Optionally attach _PHrho. Standard (1.0) PySP signature for now... ''' instance = pysp_instance_creation_callback(scenario_name, node_names, cb_data) # now attach the one and only tree node (ROOT is a reserved word) # UnitOn[*,*] is the only set of variables instance._PySPnode_list = [scenario_tree.ScenarioNode("ROOT", 1.0, 1, instance.StageCost["Stage_1"], #"Stage_1" hardcodes the commitments in all time periods None, [instance.UnitOn], instance, [instance.UnitStart, instance.UnitStop, instance.StartupIndicator], )] return instance
def pysp2_callback(scenario_name, node_names=None, cb_data=None): """ mpisppy signature for scenario creation. Then find a starting solution for the scenario if solver option is not None. Note that stage numbers are one-based. Args: scenario_name (str): put the scenario number on the end node_names (int): not used cb_data: (dict) "etree", "solver", "epath", "tee", "acstream", "convex_relaxation" Returns: scenario (pyo.ConcreteModel): the scenario instance Attaches: _enodes (ACtree nodes): a list of the ACtree tree nodes _egret_md (egret tuple with dict as [1]) egret model data """ # pull the number off the end of the scenario name scen_num = sputils.extract_num(scenario_name) etree = cb_data["etree"] solver = cb_data["solver"] acstream = cb_data["acstream"] convex_relaxation = cb_data["convex_relaxation"] if "convex_relaxation"\ in cb_data else False def lines_up_and_down(stage_md_dict, enode): # local routine to configure the lines in stage_md_dict for the scenario LinesDown = [] for f in enode.FailedLines: LinesDown.append(f[0]) for this_branch in stage_md_dict.elements("branch"): if this_branch[0] in enode.LinesUp: this_branch[1]["in_service"] = True elif this_branch[0] in LinesDown: this_branch[1]["in_service"] = False else: print("enode.LinesUp=", enode.LinesUp) print("enode.FailedLines=", enode.FailedLines) raise RuntimeError("Branch (line) {} neither up nor down in scenario {}".\ format(this_branch[0], scenario_name)) def _egret_model(md_dict): # the exact acopf model is hard-wired here: if not convex_relaxation: pyomod, mdict = eac.create_riv_acopf_model( md_dict, include_feasibility_slack=True) else: pyomod, mdict = eac_relax.create_soc_relaxation( md_dict, include_feasibility_slack=True, use_linear_relaxation=False) return pyomod, mdict # pull the number off the end of the scenario name scen_num = sputils.extract_num(scenario_name) #print ("debug scen_num=",scen_num) numstages = etree.NumStages enodes = etree.Nodes_for_Scenario(scen_num) full_scenario_model = pyo.ConcreteModel() full_scenario_model.stage_models = dict() # look at egret/data/model_data.py for the format specification of md_dict first_stage_md_dict = _md_dict(cb_data) generator_set = first_stage_md_dict.attributes("generator") generator_names = generator_set["names"] # the following creates the first stage model full_scenario_model.stage_models[1], model_dict = _egret_model( first_stage_md_dict) full_scenario_model.stage_models[1].obj.deactivate() setattr(full_scenario_model, "stage_models_" + str(1), full_scenario_model.stage_models[1]) for stage in range(2, numstages + 1): #print ("stage={}".format(stage)) stage_md_dict = copy.deepcopy(first_stage_md_dict) #print ("debug: processing node {}".format(enodes[stage-1].Name)) lines_up_and_down(stage_md_dict, enodes[stage - 1]) full_scenario_model.stage_models[stage], model_dict = \ _egret_model(stage_md_dict) full_scenario_model.stage_models[stage].obj.deactivate() setattr(full_scenario_model, "stage_models_" + str(stage), full_scenario_model.stage_models[stage]) def aggregate_ramping_rule(m): """ We are adding ramping to the obj instead of a constraint for now because we may not have ramp limit data. """ retval = 0 for stage in range(1, numstages): retval += sum((full_scenario_model.stage_models[stage+1].pg[this_gen]\ - full_scenario_model.stage_models[stage].pg[this_gen])**2\ for this_gen in generator_names) return retval full_scenario_model.ramping = pyo.Expression(rule=aggregate_ramping_rule) full_scenario_model.objective = pyo.Objective(expr=\ 1000000.0 * full_scenario_model.ramping+\ sum(full_scenario_model.stage_models[stage].obj.expr\ for stage in range(1,numstages+1))) inst = full_scenario_model # end code from PySP1 node_list = list() parent_name = None for sm1, enode in enumerate(etree.Nodes_for_Scenario(scen_num)): stage = sm1 + 1 if stage < etree.NumStages: node_list.append( scenario_tree.ScenarioNode( name=enode.Name, cond_prob=enode.CondProb, stage=stage, cost_expression=inst.stage_models[stage].obj, scen_name_list=enode.ScenarioList, nonant_list=[ inst.stage_models[stage].pg, inst.stage_models[stage].qg ], scen_model=inst, parent_name=parent_name)) parent_name = enode.Name inst._PySPnode_list = node_list # Optionally assign probability to PySP_prob inst.PySP_prob = 1 / etree.numscens # solve it so subsequent code will have a good start if solver is not None: print( f"scenario creation callback is solving {scenario_name} on rank {rank}" ) solver.solve( inst) #, tee=True) #symbolic_solver_labels=True, keepfiles=True) # attachments inst._enodes = enodes inst._egret_md = first_stage_md_dict return inst
def scenario_creator( scenario_name, use_integer=False, sense=pyo.minimize, crops_multiplier=1, ): """ Create a scenario for the (scalable) farmer example. Args: scenario_name (str): Name of the scenario to construct. use_integer (bool, optional): If True, restricts variables to be integer. Default is False. sense (int, optional): Model sense (minimization or maximization). Must be either pyo.minimize or pyo.maximize. Default is pyo.minimize. crops_multiplier (int, optional): Factor to control scaling. There will be three times this many crops. Default is 1. """ # scenario_name has the form <str><int> e.g. scen12, foobar7 # The digits are scraped off the right of scenario_name using regex then # converted mod 3 into one of the below avg./avg./above avg. scenarios scennum = sputils.extract_num(scenario_name) basenames = [ 'BelowAverageScenario', 'AverageScenario', 'AboveAverageScenario' ] basenum = scennum % 3 groupnum = scennum // 3 scenname = basenames[basenum] + str(groupnum) # The RNG is seeded with the scenario number so that it is # reproducible when used with multiple threads. # NOTE: if you want to do replicates, you will need to pass a seed # as a kwarg to scenario_creator then use seed+scennum as the seed argument. farmerstream.seed(scennum) # Check for minimization vs. maximization if sense not in [pyo.minimize, pyo.maximize]: raise ValueError("Model sense Not recognized") # Create the concrete model object model = pysp_instance_creation_callback( scenname, use_integer=use_integer, sense=sense, crops_multiplier=crops_multiplier, ) # Create the list of nodes associated with the scenario (for two stage, # there is only one node associated with the scenario--leaf nodes are # ignored). model._PySPnode_list = [ scenario_tree.ScenarioNode( name="ROOT", cond_prob=1.0, stage=1, cost_expression=model.FirstStageCost, scen_name_list=None, # Deprecated? nonant_list=[model.DevotedAcreage], scen_model=model, ) ] return model
def _experiment_instance_creation_callback(scenario_name, node_names=None, cb_data=None): """ This is going to be called by mpi-sppy or the local EF and it will call into the user's model's callback. Parameters: ----------- scenario_name: `str` Scenario name should end with a number node_names: `None` ( Not used here ) cb_data : dict with ["callback"], ["BootList"], ["theta_names"], ["cb_data"], etc. "cb_data" is passed through to user's callback function that is the "callback" value. "BootList" is None or bootstrap experiment number list. (called cb_data by mpisppy) Returns: -------- instance: `ConcreteModel` instantiated scenario Note: ---- There is flexibility both in how the function is passed and its signature. """ assert (cb_data is not None) outer_cb_data = cb_data scen_num_str = re.compile(r'(\d+)$').search(scenario_name).group(1) scen_num = int(scen_num_str) basename = scenario_name[:-len(scen_num_str)] # to reconstruct name CallbackFunction = outer_cb_data["callback"] if callable(CallbackFunction): callback = CallbackFunction else: cb_name = CallbackFunction if "CallbackModule" not in outer_cb_data: raise RuntimeError(\ "Internal Error: need CallbackModule in parmest callback") else: modname = outer_cb_data["CallbackModule"] if isinstance(modname, str): cb_module = im.import_module(modname, package=None) elif isinstance(modname, types.ModuleType): cb_module = modname else: print("Internal Error: bad CallbackModule") raise try: callback = getattr(cb_module, cb_name) except: print("Error getting function=" + cb_name + " from module=" + str(modname)) raise if "BootList" in outer_cb_data: bootlist = outer_cb_data["BootList"] #print("debug in callback: using bootlist=",str(bootlist)) # assuming bootlist itself is zero based exp_num = bootlist[scen_num] else: exp_num = scen_num scen_name = basename + str(exp_num) cb_data = outer_cb_data["cb_data"] # cb_data might be None. # at least three signatures are supported. The first is preferred try: instance = callback(experiment_number=exp_num, cb_data=cb_data) except TypeError: raise RuntimeError("Only one callback signature is supported: " "callback(experiment_number, cb_data) ") """ try: instance = callback(scenario_tree_model, scen_name, node_names) except TypeError: # deprecated signature? try: instance = callback(scen_name, node_names) except: print("Failed to create instance using callback; TypeError+") raise except: print("Failed to create instance using callback.") raise """ if hasattr(instance, "_mpisppy_node_list"): raise RuntimeError( f"scenario for experiment {exp_num} has _mpisppy_node_list") nonant_list = [instance.find_component(vstr) for vstr in\ outer_cb_data["theta_names"]] instance._mpisppy_node_list = [ scenario_tree.ScenarioNode( name="ROOT", cond_prob=1.0, stage=1, cost_expression=instance.FirstStageCost, scen_name_list=None, # Deprecated? nonant_list=nonant_list, scen_model=instance) ] if "ThetaVals" in outer_cb_data: thetavals = outer_cb_data["ThetaVals"] # dlw august 2018: see mea code for more general theta for vstr in thetavals: object = instance.find_component(vstr) if thetavals[vstr] is not None: #print("Fixing",vstr,"at",str(thetavals[vstr])) object.fix(thetavals[vstr]) else: #print("Freeing",vstr) object.fixed = False return instance
def pysp2_callback( scenario_name, etree=None, solver=None, epath=None, tee=False, acstream=None, convex_relaxation=False, verbose=False, ramp_coeff=1000000, load_mismatch_cost=1000, q_load_mismatch_cost=None, ): """ mpisppy signature for scenario creation. Then find a starting solution for the scenario if solver option is not None. Note that stage numbers are one-based. Args: scenario_name (str): Put the scenario number on the end etree (): Default is None. solver (str): Solver to use. epath (str): Path to the egret data tee (bool): If True, displays solver output. Default is False. acstream (): Default is None. convex_relaxation (bool): If True, build the convex relaxation. Default is False. verbose (bool, optional): If True, display verbose output. Default is False. ramp_coeff (int, optional): Default is 1e6. load_mismatch_cost (float, optional): Default is 1000. q_load_mismatch_cost (float, optional): Deafult is load_mismatch_cost. Returns: scenario (pyo.ConcreteModel): the scenario instance Attaches: _enodes (ACtree nodes): a list of the ACtree tree nodes _egret_md (egret tuple with dict as [1]) egret model data """ print("Debug: convex_relaxation=", convex_relaxation) # pull the number off the end of the scenario name scen_num = sputils.extract_num(scenario_name) if q_load_mismatch_cost is None: q_load_mismatch_cost = load_mismatch_cost def lines_up_and_down(stage_md_dict, enode): # local routine to configure the lines in stage_md_dict for the scenario LinesDown = [] for f in enode.FailedLines: LinesDown.append(f[0]) for this_branch in stage_md_dict.elements("branch"): if this_branch[0] in enode.LinesUp: this_branch[1]["in_service"] = True elif this_branch[0] in LinesDown: this_branch[1]["in_service"] = False else: print("enode.LinesUp=", enode.LinesUp) print("enode.FailedLines=", enode.FailedLines) raise RuntimeError("Branch (line) {} neither up nor down in scenario {}".\ format(this_branch[0], scenario_name)) def _egret_model(md_dict): # the exact acopf model is hard-wired here: md_dict.data['system']['load_mismatch_cost'] = load_mismatch_cost md_dict.data['system']['q_load_mismatch_cost'] = q_load_mismatch_cost if not convex_relaxation: pyomod, mdict = eac.create_riv_acopf_model( md_dict, include_feasibility_slack=True) else: pyomod, mdict = eac_relax.create_soc_relaxation( md_dict, include_feasibility_slack=True, use_linear_relaxation=False) return pyomod, mdict # pull the number off the end of the scenario name scen_num = sputils.extract_num(scenario_name) numstages = etree.NumStages enodes = etree.Nodes_for_Scenario(scen_num) full_scenario_model = pyo.ConcreteModel() full_scenario_model.stage_models = dict() # look at egret/data/model_data.py for the format specification of md_dict first_stage_md_dict = _md_dict(epath) generator_set = first_stage_md_dict.attributes("generator") generator_names = generator_set["names"] # the following creates the first stage model full_scenario_model.stage_models[1], model_dict = _egret_model( first_stage_md_dict) full_scenario_model.stage_models[1].obj.deactivate() setattr(full_scenario_model, "stage_models_" + str(1), full_scenario_model.stage_models[1]) for stage in range(2, numstages + 1): #print ("stage={}".format(stage)) stage_md_dict = copy.deepcopy(first_stage_md_dict) #print ("debug: processing node {}".format(enodes[stage-1].Name)) lines_up_and_down(stage_md_dict, enodes[stage - 1]) full_scenario_model.stage_models[stage], model_dict = \ _egret_model(stage_md_dict) full_scenario_model.stage_models[stage].obj.deactivate() setattr(full_scenario_model, "stage_models_" + str(stage), full_scenario_model.stage_models[stage]) def aggregate_ramping_rule(m): """ We are adding ramping to the obj instead of a constraint for now because we may not have ramp limit data. """ retval = 0 for stage in range(1, numstages): retval += sum((full_scenario_model.stage_models[stage+1].pg[this_gen]\ - full_scenario_model.stage_models[stage].pg[this_gen])**2\ for this_gen in generator_names) return retval full_scenario_model.ramping = pyo.Expression(rule=aggregate_ramping_rule) full_scenario_model.objective = pyo.Objective(expr=\ ramp_coeff * full_scenario_model.ramping+\ sum(full_scenario_model.stage_models[stage].obj.expr\ for stage in range(1,numstages+1))) inst = full_scenario_model # end code from PySP1 node_list = list() parent_name = None for sm1, enode in enumerate(etree.Nodes_for_Scenario(scen_num)): stage = sm1 + 1 if stage < etree.NumStages: node_list.append( scenario_tree.ScenarioNode( name=enode.Name, cond_prob=enode.CondProb, stage=stage, cost_expression=inst.stage_models[stage].obj, scen_name_list=enode.ScenarioList, nonant_list=[ inst.stage_models[stage].pg, inst.stage_models[stage].qg ], scen_model=inst, parent_name=parent_name)) parent_name = enode.Name if verbose: print(f"\nScenario creation for {scenario_name} on rank {rank}" f" FailedLines (line,minutes)={enode.FailedLines}") inst._PySPnode_list = node_list inst.PySP_prob = 1 / etree.numscens # solve it so subsequent code will have a good start if solver is not None: print( f"scenario creation callback is solving {scenario_name} on rank {rank}" ) solver.solve(inst, tee=tee) #symbolic_solver_labels=True, keepfiles=True) # attachments inst._enodes = enodes inst._egret_md = first_stage_md_dict return inst
def scenario_creator( scenario_name, solar_filname=None, use_LP=False, lam=None, ): """ Args: scenario_name (str): Name of the scenario to create. solar_filename (str): File containing the solar data. use_LP (bool, optional): If True, uses LP. Default is False. lam (float): Value of the dual variable for the chance constraint. """ if 'solar_filename' is None: raise ValueError("kwarg `solar_filename` is required") if 'lam' is None: raise RuntimeError("kwarg `lam` is required") data = getData(solar_filename) num_scenarios = data['solar'].shape[0] scenario_index = extract_scenario_index(scenario_name) if (scenario_index < 0) or (scenario_index >= num_scenarios): raise RuntimeError('Provided scenario index is invalid (must lie in ' '{0,1,...' + str(num_scenarios - 1) + '} inclusive)') model = pyo.ConcreteModel() T = range(data['T']) Tm1 = range(data['T'] - 1) model.y = pyo.Var(T, within=pyo.NonNegativeReals) model.p = pyo.Var(T, bounds=(0, data['cMax'])) model.q = pyo.Var(T, bounds=(0, data['dMax'])) model.x = pyo.Var(T, bounds=(data['eMin'], data['eMax'])) if (use_LP): model.z = pyo.Var([0], within=pyo.UnitInterval) else: model.z = pyo.Var([0], within=pyo.Binary) ''' "Flow balance" constraints ''' def flow_balance_constraint_rule(model, t): return model.x[t+1]==model.x[t] + \ data['eff'] * model.p[t] - (1/data['eff']) * model.q[t] model.flow_constr = pyo.Constraint(Tm1, rule=flow_balance_constraint_rule) ''' Big-M constraints ''' def big_M_constraint_rule(model, t): return model.y[t] - model.q[t] + model.p[t] \ <= data['solar'][scenario_index,t] + \ data['M'][scenario_index,t] * model.z[0] # Why indexed?? model.big_m_constr = pyo.Constraint(T, rule=big_M_constraint_rule) ''' Objective function (must be minimization or PH crashes) ''' model.obj = pyo.Objective(expr=-pyo.dot_product(data['rev'], model.y) + data['char'] * pyo.quicksum(model.p) + data['disc'] * pyo.quicksum(model.q) + lam * model.z[0], sense=pyo.minimize) fscr = lambda model: pyo.dot_product(data['rev'], model.y) model.first_stage_cost = pyo.Expression(rule=fscr) model._PySPnode_list = [ stree.ScenarioNode(name='ROOT', cond_prob=1., stage=1, cost_expression=model.first_stage_cost, scen_name_list=None, nonant_list=[model.y], scen_model=model) ] return model
def scenario_creator(scenario_name, node_names=None, cb_data=None): if (cb_data is None): raise RuntimeError('Must provide a cb_data dict to scenario creator. ' 'At a minimum, this dictionary must contain a key "lam" ' 'specifying the value of the dual multiplier lambda to use, ' 'and a key "solar_filename" specifying where the solar data ' 'is stored.') if ('solar_filename' not in cb_data): raise RuntimeError('Please provide a cb_data dict that contains ' '"solar_filename", with a valid path') if ('lam' not in cb_data): raise RuntimeError('Please provide a cb_data dict that contains ' '"lam", a value of the dual multiplier lambda.') data = getData(cb_data['solar_filename']) num_scenarios = data['solar'].shape[0] scenario_index = extract_scenario_index(scenario_name) if (scenario_index < 0) or (scenario_index >= num_scenarios): raise RuntimeError('Provided scenario index is invalid (must lie in ' '{0,1,...' + str(num_scenarios-1) + '} inclusive)') if ('use_LP' in cb_data): use_LP = cb_data['use_LP'] else: use_LP = False model = pyo.ConcreteModel() T = range(data['T']) Tm1 = range(data['T']-1) model.y = pyo.Var(T, within=pyo.NonNegativeReals) model.p = pyo.Var(T, bounds=(0, data['cMax'])) model.q = pyo.Var(T, bounds=(0, data['dMax'])) model.x = pyo.Var(T, bounds=(data['eMin'], data['eMax'])) if (use_LP): model.z = pyo.Var([0], within=pyo.UnitInterval) else: model.z = pyo.Var([0], within=pyo.Binary) ''' "Flow balance" constraints ''' def flow_balance_constraint_rule(model, t): return model.x[t+1]==model.x[t] + \ data['eff'] * model.p[t] - (1/data['eff']) * model.q[t] model.flow_constr = pyo.Constraint(Tm1, rule=flow_balance_constraint_rule) ''' Big-M constraints ''' def big_M_constraint_rule(model, t): return model.y[t] - model.q[t] + model.p[t] \ <= data['solar'][scenario_index,t] + \ data['M'][scenario_index,t] * model.z[0] # Why indexed?? model.big_m_constr = pyo.Constraint(T, rule=big_M_constraint_rule) ''' Objective function (must be minimization or PH crashes) ''' model.obj = pyo.Objective(expr=-pyo.dot_product(data['rev'], model.y) + data['char'] * pyo.quicksum(model.p) + data['disc'] * pyo.quicksum(model.q) + cb_data['lam'] * model.z[0], sense=pyo.minimize) fscr = lambda model: pyo.dot_product(data['rev'], model.y) model.first_stage_cost = pyo.Expression(rule=fscr) model._PySPnode_list = [ stree.ScenarioNode(name='ROOT', cond_prob=1., stage=1, cost_expression=model.first_stage_cost, scen_name_list=None, nonant_list=[model.y], scen_model=model) ] return model