# ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects # Copyright 2017 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. # ___________________________________________________________________________ import sys import os import time from pyomo.core import minimize from pyomo.pysp.ef_writer_script import ExtensiveFormAlgorithm from pyomo.pysp.phinit import run_ph from pyomo.pysp.phutils import (reset_nonconverged_variables, extractVariableNameAndIndex, reset_stage_cost_variables) from pyomo.pysp.solutionwriter import ISolutionWriterExtension from pyomo.common.plugin import ExtensionPoint # Tear the scenario instances off the ef instance when it is no longer required # so warnings are not generated next time scenarios instances are placed inside # a new ef instance def _tear_down_ef(ef_instance, scenario_instances): for name in scenario_instances: ef_instance.del_component(name) def solve_ph_code(ph, options): import pyomo.environ import pyomo.solvers.plugins.smanager.phpyro # consolidate the code to solve the problem for the "global" ph object # return a solver code (string from the solver if EF, "PH" if PH) and the objective fct val SolStatus = None ph._preprocess_scenario_instances() ObjectiveFctValue = \ float('inf') if (ph._objective_sense is minimize) else float('-inf') SolStatus = None if not options.solve_with_ph: if options.verbose is True: print("Creating the extensive form.") print("Time="+time.asctime()) with ExtensiveFormAlgorithm(ph, options._ef_options, options_prefix="ef_") as ef: ef.build_ef() failure = ef.solve(io_options=\ {'output_fixed_variable_bounds': options.write_fixed_variables}, exception_on_failure=False) if not failure: ObjectiveFctValue = ef.objective SolStatus = str(ef.solver_status) """ ef = create_ef_instance(ph._scenario_tree, verbose_output=options.verbose) if options.verbose: print("Time="+time.asctime()) print("Solving the extensive form.") ef_results = solve_ef(ef, options) SolStatus = str(ef_results.solver.status) print("SolStatus="+SolStatus) if options.verbose is True: print("Loading extensive form solution.") print("Time="+time.asctime()) ### If the solution is infeasible, we don't want to load the results ### It is up to the caller to decide what to do with non-optimal if SolStatus != "infeasible" and SolStatus != "unknown": # IMPT: the following method populates the _solution variables on the scenario tree # nodes by forming an average of the corresponding variable values for all # instances particpating in that node. if you don't do this, the scenario tree # doesn't have the solution - and we need this below for variable bounding ph._scenario_tree.pullScenarioSolutionsFromInstances() ph._scenario_tree.snapshotSolutionFromScenarios() if options.verbose is True: print("SolStatus="+SolStatus) print("Time="+time.asctime()) _tear_down_ef(ef, ph._instances) """ else: if options.verbose: print("Solving via Progressive Hedging.") run_ph(options, ph) ph._scenario_tree.pullScenarioSolutionsFromInstances() root_node = ph._scenario_tree._stages[0]._tree_nodes[0] ObjectiveFctValue = root_node.computeExpectedNodeCost() SolStatus = "PH" """ phretval = ph.solve() #print("--------->>>> "+str(phretval)) SolStatus = "PH" if phretval is not None: print("Iteration zero solve was not successful for scenario: "+str(phretval)) if options.verbose is True: print("Iteration zero solve was not successful for scenario: "+str(phretval)) SolStatus = "PHFailAtScen"+str(phretval) # TBD - also not sure if PH calls snapshotSolutionFromAverages. if options.verbose is True: print("Done with PH solve.") ##begin copy from phinit solution_writer_plugins = ExtensionPoint(ISolutionWriterExtension) for plugin in solution_writer_plugins: plugin.write(ph._scenario_tree, "ph") # store the binding instance, if created, in order to load # the solution back into the scenario tree. binding_instance = None # # create the extensive form binding instance, so that we can either write or solve it (if specified). # if (options.write_ef) or (options.solve_ef): # The post-solve plugins may have done more variable # fixing. These should be pushed to the instance at this # point. print("Pushing fixed variable statuses to scenario instances") ph._push_all_node_fixed_to_instances() total_fixed_discrete_vars, total_fixed_continuous_vars = \ ph.compute_fixed_variable_counts() print("Number of discrete variables fixed " "prior to ef creation=" +str(total_fixed_discrete_vars)+ " (total="+str(ph._total_discrete_vars)+")") print("Number of continuous variables fixed " "prior to ef creation=" +str(total_fixed_continuous_vars)+ " (total="+str(ph._total_continuous_vars)+")") print("Creating extensive form for remainder problem") ef_instance_start_time = time.time() binding_instance = create_ef_instance(ph._scenario_tree) ef_instance_end_time = time.time() print("Time to construct extensive form instance=%.2f seconds" % (ef_instance_end_time - ef_instance_start_time)) ph._preprocess_scenario_instances() # # solve the extensive form and load the solution back into the PH scenario tree. # contents from the PH solve will obviously be over-written! # if options.write_ef: output_filename = os.path.expanduser(options.ef_output_file) # technically, we don't need the symbol map since we aren't solving it. print("Starting to write the extensive form") ef_write_start_time = time.time() symbol_map = write_ef(binding_instance, output_filename, symbolic_solver_labels=options.symbolic_solver_labels, output_fixed_variable_bounds=options.write_fixed_variables) ef_write_end_time = time.time() print("Extensive form written to file="+output_filename) print("Time to write output file=%.2f seconds" % (ef_write_end_time - ef_write_start_time)) if options.solve_ef: # set the value of each non-converged, non-final-stage variable to None - # this will avoid infeasible warm-stats. reset_nonconverged_variables(ph._scenario_tree, ph._instances) reset_stage_cost_variables(ph._scenario_tree, ph._instances) ef_results = solve_ef(binding_instance, options) print("Storing solution in scenario tree") ph._scenario_tree.pullScenarioSolutionsFromInstances() ph._scenario_tree.snapshotSolutionFromScenarios() ef_solve_end_time = time.time() print("Time to solve and load results for the " "extensive form=%.2f seconds" % (ef_solve_end_time - ef_solve_start_time)) # print *the* metric of interest. print("") root_node = ph._scenario_tree._stages[0]._tree_nodes[0] print("***********************************************************************************************") print(">>>THE EXPECTED SUM OF THE STAGE COST VARIABLES="+str(root_node.computeExpectedNodeCost())+"<<<") print("***********************************************************************************************") print("") print("Extensive form solution:") ph._scenario_tree.pprintSolution() print("") print("Extensive form costs:") ph._scenario_tree.pprintCosts() solution_writer_plugins = ExtensionPoint(ISolutionWriterExtension) for plugin in solution_writer_plugins: plugin.write(ph._scenario_tree, "postphef") if binding_instance is not None: _tear_down_ef(binding_instance, ph._instances) """ print("SolStatus="+str(SolStatus)) if options.verbose: print("Time="+time.asctime()) ## print "(using PySP Cost vars) ObjectiveFctValue=",ObjectiveFctValue return SolStatus, ObjectiveFctValue ########### def ZeroOneIndexListsforVariable(ph, IndVarName, CCStageNum): # return lists across scenarios of the zero value scenarios and one value scenarios # for unindexed variable in the ph object for a stage (one based) # in this routine we trust that it is binary ZerosList = [] OnesList = [] stage = ph._scenario_tree._stages[CCStageNum-1] for tree_node in stage._tree_nodes: for scenario in tree_node._scenarios: instance = ph._instances[scenario._name] locval = getattr(instance, IndVarName).value #print locval if locval < 0.5: ZerosList.append(scenario) else: OnesList.append(scenario) return [ZerosList,OnesList] ########### def PrintanIndexList(IndList): # show some useful information about an index list (note: indexes are scenarios) print("Zeros:") for i in IndList[0]: print(i._name+'\n') print("Ones:") for i in IndList[1]: print(i._name+'\n') ########### def ReturnIndexListNames(IndList): ListNames=[[],[]] #print "Zeros:" for i in IndList[0]: ListNames[0].append(i._name) #print "Ones:" for i in IndList[1]: ListNames[1].append(i._name) return ListNames ########### def Set_ParmValue(ph, ParmName, NewVal): # set the value of the named parm in all instances (unindexed) rootnode = ph._scenario_tree._stages[0]._tree_nodes[0] for scenario in rootnode._scenarios: instance = ph._instances[scenario._name] pm = getattr(instance, ParmName) if pm.is_indexed(): for index in pm: pm[index].value = NewVal[index] else: pm.value = NewVal ##### dlw adds value :( Jan 2014 # required for advanced preprocessing that takes # place in PySP ph._problem_states.\ user_constraints_updated[scenario._name] = True ########### def Get_ParmValueOneScenario(ph, scenarioName, ParmName): instance = ph._instances[scenarioName] pm = getattr(instance, ParmName) values = [] for index in pm: values.append(pm[index].value) return values def PurifyIndVar(ph, IndVarName, tolZero=1.e-6): rootnode = ph._scenario_tree._stages[0]._tree_nodes[0] for scenario in rootnode._scenarios: instance = ph._instances[scenario._name] pm = getattr(instance, IndVarName) for index in pm: delta = pm[index].value if abs(delta) < tolZero: delta = 0 elif abs(delta-1.) < tolZero: delta = 1 else: print("\n** delta["+str(index)+"," + scenario._name + "] = "+str(delta)) print("\tnot within " + str(tolZero) + " of 0 or 1 ... causes sys.exit") print("**************************************************************\n") sys.exit() pm[index].value = delta return def FreeAllIndicatorVariables(ph, IndVarName): for scenario in ph._scenario_tree._scenarios: FreeIndicatorVariableOneScenario(ph, scenario, IndVarName) def FreeIndicatorVariableOneScenario(ph, scenario, IndVarName): instance = ph._instances[scenario._name] getattr(instance, IndVarName).free() # required for advanced preprocessing that takes # place in PySP ph._problem_states.\ freed_variables[scenario._name].\ append((IndVarName, None)) def FixAllIndicatorVariables(ph, IndVarName, value): for scenario in ph._scenario_tree._scenarios: FixIndicatorVariableOneScenario(ph, scenario, IndVarName, value) def FixIndicatorVariableOneScenario(ph, scenario, IndVarName, fix_value): instance = ph._instances[scenario._name] getattr(instance, IndVarName).fix(fix_value) # required for advanced preprocessing that takes # place in PySP ph._problem_states.\ fixed_variables[scenario._name].\ append((IndVarName, None)) def FixFromLists(Lists, ph, IndVarName, CCStageNum): # fix variables from the lists stage = ph._scenario_tree._stages[CCStageNum-1] for tree_node in stage._tree_nodes: for scenario in tree_node._scenarios: instance = ph._instances[scenario._name] fix_value = None if scenario in Lists[0]: fix_value = 0 elif scenario in Lists[1]: fix_value = 1 if fix_value is not None: # we are assuming no index FixIndicatorVariableOneVariable(ph, scenario, IndVarName, fix_value) return ########### def UseListsVariableThenSolve(Lists, ph, IndVarName, CCStageNum): # use lists across scenarios of to fix the variable value # then solve and return some stuff FixFromLists(Lists, ph, IndVarName, CCStageNum) LagrangianObj = solve_ph_code(ph) b = Compute_ExpectationforVariable(ph, IndVarName, 2) print("back from compute exp") return LagrangianObj, b #============================================== def AlgoExpensiveFlip(TrueForZeros, List, lambdaval, ph, IndVarName, CCStageNum): # Flip every delta of every scenario and solve with solve_ph_code(ph) # to find scenarios for which we will flip the indicator variable # returns list of scenarios sorted by the LagrObj if TrueForZeros is True: print("ExpensiveFlipAlgo: Fix ZerosToOnes and compute: ") value=1 else: print("ExpensiveFlipAlgo: Fix OnesToZeros and compute: ") value=0 D={} stage = ph._scenario_tree._stages[CCStageNum-1] for tree_node in stage._tree_nodes: for scenario in tree_node._scenarios: instance = ph._instances[scenario._name] if scenario._name in ReturnIndexListNames(List)[abs(value-1)]: FixIndicatorVariableOneScenario(ph, scenario, IndVarName, value) LagrObj = solve_ph_code(ph) b = Compute_ExpectationforVariable(ph, IndVarName, 2) z = LagrObj+(b*lambdaval) D[z]=[scenario,b] print("ObjVal w/ "+str(scenario._name)+ " delta flipped to "+str(value)+ " (LagrObj,b,z): "+str(LagrObj)+ " "+str(b)+" "+str(z)) FixIndicatorVariableOneScenario(ph, scenario, IndVarName, abs(value-1)) Dsort = [] for key in sorted(D): Dsort.append(D[key]) print("---SmallestObjValue:---> " +str(sorted(D)[0])+" with (LagrObj,b) = " +str(sorted(D)[0]-(lambdaval*Dsort[0][1]))+" " +str(Dsort[0][1])+" at "+str(Dsort[0][0]._name)) if TrueForZeros is True: print("Back from ExpensiveFlipAlgo:Fix ZerosToOnes") else: print("Back from ExpensiveFlipAlgo:Fix OnesToZeros") return Dsort,sorted(D)[0],Dsort[0][1] def Compute_ExpectationforVariable(ph, IndVarName, CCname, CCStageNum): rootnode = ph._scenario_tree._stages[0]._tree_nodes[0] ReferenceInstance = ph._instances[rootnode._scenarios[0]._name] # arbitrary scenario CC = getattr(ReferenceInstance,CCname) SumSoFar = {} for cc in CC: SumSoFar[cc] = 0.0 node_probability = 0.0 stage = ph._scenario_tree._stages[CCStageNum-1] for tree_node in stage._tree_nodes: for scenario in tree_node._scenarios: instance = ph._instances[scenario._name] node_probability += scenario._probability for cc in CC: deltaValue = getattr(instance,IndVarName)[cc].value SumSoFar[cc] += scenario._probability * deltaValue for cc in CC: SumSoFar[cc] = SumSoFar[cc] / node_probability return SumSoFar def GetPenaltyCost(ph, IndVarName, multName): # E[lambda*delta] contribution to cost rootnode = ph._scenario_tree._stages[0]._tree_nodes[0] ReferenceInstance = ph._instances[rootnode._scenarios[0]._name] # arbitrary scenario CC = ReferenceInstance.ChanceConstraints cost = 0. for scenario in rootnode._scenarios: instance = ph._instances[scenario._name] delta = getattr(instance, IndVarName) mult = getattr(instance,multName) for index in delta: cost = cost + scenario._probability*delta[index].value*mult[index].value return cost