Beispiel #1
0
    def post_iteration_0_solves(self, ph):
        self._interscenario_plugin(ph)
        count = 0
        while self.rho is None and self.feasibility_cuts:
            count += 1
            toc( "InterScenario plugin: PH iteration 0 re-solve pass %s"
                   % (count,) )
            _stale_scenarios = []
            for _id, _soln in enumerate(self.unique_scenario_solutions):
                _was_cut = sum(
                    1 for c in self.feasibility_cuts if type(c[_id]) is tuple)
                if _was_cut:
                    _stale_scenarios.extend(_soln[1])

            self._distribute_cuts(ph, True)
            toc("InterScenario: distributed cuts to scenarios")
            self._interscenario_plugin(ph)
        self.lastRun = 0
Beispiel #2
0
    def post_iteration_0_solves(self, ph):
        self._collect_unique_scenario_solutions(ph)
        self._interscenario_plugin(ph)
        count = 0
        while self.rho is None and self.feasibility_cuts:
            count += 1
            toc("InterScenario plugin: PH iteration 0 re-solve pass %s" %
                (count, ))
            _stale_scenarios = []
            for _id, _soln in enumerate(self.unique_scenario_solutions):
                _was_cut = sum(1 for c in self.feasibility_cuts
                               if type(c[_id]) is tuple)
                if _was_cut:
                    _stale_scenarios.extend(_soln[1])

            self._distribute_cuts(ph, True)
            toc("InterScenario plugin: distributed cuts to scenarios")
            self._collect_unique_scenario_solutions(ph)
            self._interscenario_plugin(ph)
        self.lastRun = 0
Beispiel #3
0
    def _distribute_cuts(self, ph, resolve=False):
        totalCuts = 0
        cutObj = sorted( c[0] for x in self.feasibility_cuts for c in x
                         if type(c) is tuple
                         and c[0] > self.cutThreshold_minDiff )
        if cutObj:
            allCutThreshold = cutObj[
                min( int((1-self.cutThreshold_crossCut)*len(cutObj)),
                     len(cutObj)-1 ) ]
        else:
            allCutThreshold = 1

        distributed = isinstance( ph._solver_manager, SolverManager_PHPyro )

        if ph._scenario_tree.contains_bundles():
            subproblems = ph._scenario_tree._scenario_bundles
            get_scenarios = lambda x: x._scenario_names
        else:
            subproblems = ph._scenario_tree._scenarios
            get_scenarios = lambda x: [x]

        resolves = []
        for problem in subproblems:
            cuts = []
            for id, (x, s) in enumerate(self.unique_scenario_solutions):
                found = False
                for scenario in get_scenarios(problem):
                    if scenario._name in s:
                        found = True
                        break
                if found:
                    cuts.extend( c[id] for c in self.feasibility_cuts
                                 if type(c[id]) is tuple
                                 and c[id][0] > self.cutThreshold_minDiff )
                elif self.feasible_objectives[id] is None:
                    # We only add cuts generated by other scenarios to
                    # scenarios that are not currently feasible (as
                    # these are feassibility cuts, they should not
                    # impact feasible scenarios)
                    cuts.extend( c[id] for c in self.feasibility_cuts
                                 if type(c[id]) is tuple
                                 and c[id][0] > allCutThreshold )

            if not cuts and not self.incumbent_cuts:
                resolves.append(None)
                continue

            totalCuts += len(cuts)
            if distributed:
                resolves.append(
                    ph._solver_manager.queue(
                        action="invoke_external_function",
                        name=problem._name,
                        queue_name=ph._phpyro_job_worker_map[problem._name],
                        invocation_type=InvocationType.SingleInvocation.key,
                        generateResponse=True,
                        module_name='pyomo.pysp.plugins.interscenario',
                        function_name='add_new_cuts',
                        function_kwds=None,
                        function_args=( cuts,
                                        self.incumbent_cuts,
                                        resolve ),
                    ) )
            else:
                ans = add_new_cuts( ph, ph._scenario_tree, problem,
                                    cuts, self.incumbent_cuts, resolve )
                resolves.append(ans)
                toc("distributed cuts to scenario %s%s" %
                    ( problem._name,
                      ' and resolved scenario' if resolve else '' ))

        toc( "InterScenario plugin: added %d feasibility cuts from a "
               "library of %s cuts" % (totalCuts, len(cutObj)) )
        self.feasibility_cuts = []

        if self.incumbent_cuts:
            print( "InterScenario plugin: added %d incumbent cuts" %
                   (len(self.incumbent_cuts), ) )
            self.incumbent_cuts = []


        if distributed:
            num_results_so_far = sum(1 for x in resolves if x is None)
            num_results = len(resolves)

            while (num_results_so_far < num_results):
                _ah = ph._solver_manager.wait_any()
                _ah_idx = resolves.index(_ah)
                resolves[_ah_idx] = ph._solver_manager.get_results(_ah)
                num_results_so_far += 1

        if resolve:
            # Transfer the first stage values and cost back to PH and
            # recompute xbar
            rootNode = ph._scenario_tree.findRootNode()
            for _id, problem in enumerate(subproblems):
                ans = resolves[_id]
                if ans is None:
                    continue
                for scenario in get_scenarios(problem):
                    scenario._cost = ans[1]
                    assert( sorted(ans[0]) ==
                            sorted(scenario._x[rootNode._name]) )
                    scenario._x[rootNode._name] = ans[0] #[_vid] = _vval
            ph.update_variable_statistics()
Beispiel #4
0
    def _interscenario_plugin(self,ph):
        toc("InterScenario plugin: analyzing scenario dual information")

        # (1) Collect all scenario (first) stage variables
        #self._collect_unique_scenario_solutions(ph)

        # (2) Filter them to find a set we want to distribute
        pass

        # (3) Distribute (some) of the variable sets out to the
        # scenarios, fix, and resolve; Collect and return the
        # objectives, duals, and any cuts
        partial_obj_values, dual_values, cuts, probability \
            = self._solve_interscenario_solutions( ph )

        # Compute the non-anticipative objective values for each
        # scenario solution
        self.feasible_objectives = self._compute_objective(
            partial_obj_values, probability )

        for _id, soln in enumerate(self.unique_scenario_solutions):
            _scenarios = [ph._scenario_tree.get_scenario(x) for x in soln[1]]
            print(
                "  Solution %2d: generated %2d cuts, "
                "cut by %2d other scenarios; objective %10s, "
                "scenario cost [%s], cut obj [%s] [generated by %s]" % (
                    _id,
                    sum(1 for c in cuts[_id] if type(c) is tuple),
                    sum(1 for c in cuts if type(c[_id]) is tuple),
                    "None" if self.feasible_objectives[_id] is None
                    else "%10.2f" % self.feasible_objectives[_id],
                    ", ".join("%10.2f" % x._cost for x in _scenarios),
                    " ".join("%5.2f" % x[0] if type(x) is tuple else "%5s" % x
                             for x in cuts[_id]),
                    ','.join(soln[1])
                ))

        scenarioCosts = [ ph._scenario_tree.get_scenario(x)._cost
                          for s in self.unique_scenario_solutions
                          for x in s[1] ]
        scenarioProb =  [ ph._scenario_tree.get_scenario(x)._probability
                          for s in self.unique_scenario_solutions
                          for x in s[1] ]
        _avg = sum( scenarioProb[i]*c for i,c in enumerate(scenarioCosts) )
        _max = max( scenarioCosts )
        _min = min( scenarioCosts )
        if self.average_solution is None:
            _del_avg = None
            _del_avg_str = "-----%"
        else:
            _prev = self.average_solution
            _del_avg = (_avg-_prev) / max(abs(_avg),abs(_prev))
            _del_avg_str = "%+.2f%%" % ( 100*_del_avg, )
        self.average_solution = _avg
        print("  Average scenario cost: %f (%s) Max-min: %f  (%0.2f%%)" % (
            _avg, _del_avg_str, _max-_min, abs(100.*(_max-_min)/_avg) ))

        # (4) save any cuts for distribution before the next solve
        #self.feasibility_cuts = []
        #for c in cuts:
        #    self.feasibility_cuts.extend(
        #        x for x in c if type(x) is tuple and x[0] > self.cutThreshold )
        #cutCount = len(self.feasibility_cuts)
        if self.enableFeasibilityCuts:
            self.feasibility_cuts = cuts
        cutCount = sum( sum( 1 for x in c if type(x) is tuple
                             and  x[0]>self.cutThreshold_minDiff )
                        for c in cuts )
        subProblemCount = sum(len(c) for c in cuts)

        # (5) compute and publish the new incumbent
        self._update_incumbent(ph)

        # (6a) If this is iteration 0, and we have feasibility cuts, and
        # they are (sufficiently) helping the Lagrangean bound, then
        # skip setting rho and do another round oc cuts
        if ph._current_iteration == 0:
            # Tell ph that we may have a good opter bound
            ph._update_reported_bounds(outer=self.average_solution)

            if ( cutCount > self.recutThreshold*(subProblemCount-len(cuts))
                 and ( _del_avg is None or
                       _del_avg > self.iteration0RecutBoundImprovement )):
                # Bypass RHO updates and check for more cuts
                #self.lastRun = ph._current_iteration - self.iterationInterval
                return

        # (6b) compute updated rho estimates
        new_rho, loginfo = self._process_dual_information(
            ph, dual_values, probability )
        _scale = self.rhoScale
        if self.rho is None:
            print("InterScenario plugin: initializing rho")
            self.rho = {}
            for v,r in iteritems(new_rho):
                self.rho[v] = _scale*r
        else:
            _damping = self.rhoDamping
            for v,r in iteritems(new_rho):
                if self.rho[v]:
                    self.rho[v] += (1-_damping)*(_scale*r - self.rho[v])
                    #self.rho[v] = max(_scale*r,  self.rho[v]) - \
                    #    _damping*abs(_scale*r - self.rho[v])
                else:
                    self.rho[v] = _scale*r

        for v,l in sorted(iteritems(loginfo)):
            if v is None:
                print(l)
            else:
                print(l % (self.rho[v],))

        #print("SETTING SELF.RHO", self.rho)
        rootNode = ph._scenario_tree.findRootNode()
        if self.enableRhoUpdates:
            for v, r in iteritems(self.rho):
                ph.setRhoAllScenarios(rootNode, v, r)
Beispiel #5
0
def get_modified_instance( ph, scenario_tree, scenario_or_bundle, **options):
    # Find the model
    if scenario_tree.contains_bundles():
        model = ph._bundle_binding_instance_map[scenario_or_bundle._name]
    else:
        model = ph._instances[scenario_or_bundle._name]
    b = model.component('_interscenario_plugin')
    if b is not None:
        return model

    #
    # We need to add the interscenario information to this model
    #
    model._interscenario_plugin = b = Block()

    # Save our options
    #
    b.epsilon     = options.pop('epsilon')
    b.cut_scale   = options.pop('cut_scale')
    b.allow_slack = options.pop('allow_slack')
    b.enable_rho  = options.pop('enable_rho')
    b.enable_cuts = options.pop('enable_cuts')
    assert( len(options) == 0 )

    # Information for generating cuts
    #
    b.cutlist = ConstraintList()
    b.abs_int_vars = VarList(within=NonNegativeIntegers)
    b.abs_binary_vars = VarList(within=Binary)

    # Note: the var_ids are on the ORIGINAL scenario models
    rootNode = scenario_tree.findRootNode()
    var_ids = list(iterkeys(rootNode._variable_datas))

    # Right now, this is hard-coded for 2-stage problems - so we only
    # need to worry about the variables from the root node.  These
    # variables should exist on all scenarios.  Set up a (trivial)
    # equality constraint for each variable:
    #    var == current_value{param} + separation_variable{var, fixed=0}
    b.STAGE1VAR = _S1V = Set(initialize=var_ids)
    b.separation_variables = _sep = Var( _S1V, dense=True )
    b.fixed_variable_values = _param = Param(_S1V, mutable=True, initialize=0)

    b.rho = weakref.ref(model.component('PHRHO_%s' % rootNode._name))
    b.weights = weakref.ref(model.component('PHWEIGHT_%s' % rootNode._name))

    if b.allow_slack:
        for idx in _sep:
            _sep[idx].setlb(-b.epsilon)
            _sep[idx].setub(b.epsilon)
    else:
        _sep.fix(0)

    _cuidBuffer = {}
    _src = b.local_stage1_varmap = {}
    for i in _S1V:
        # Note indexing: for each 1st stage var, pick an arbitrary
        # (first) scenario and return the variable (and not it's
        # probability)
        _cuid = ComponentUID(rootNode._variable_datas[i][0][0], _cuidBuffer)
        _src[i] = weakref.ref(_cuid.find_component_on(model))
        #_base_src[i] = weakref.ref(_cuid.find_component_on(base_model))

    def _set_var_value(b, i):
        return _param[i] + _sep[i] - _src[i]() == 0
    b.fixed_variables_constraint \
        = _con = Constraint( _S1V, rule=_set_var_value )

    #
    # TODO: When we get the duals of the first-stage variables, do we
    # want the dual WRT the original objective, or the dual WRT the
    # augmented objective?
    #
    # Move the objective to a standardized place so we can easily find it later
    if PYOMO_4_0:
        _orig_objective = list( x[2] for x in model.all_component_data(
                Objective, active=True, descend_into=True ) )
    else:
        _orig_objective = list( model.component_data_objects(
                Objective, active=True, descend_into=True ) )
    assert(len(_orig_objective) == 1)
    _orig_objective = _orig_objective[0]
    b.original_obj = weakref.ref(_orig_objective)

    # add (and deactivate) the objective for the infeasibility
    # separation problem.
    b.separation_obj = Objective(
        expr= sum( _sep[i]**2 for i in var_ids ),
        sense = minimize )

    # Make sure we get dual information
    if 'dual' not in model:
        # Export and import floating point data
        model.dual = Suffix(direction=Suffix.IMPORT_EXPORT)
    #if 'rc' not in model:
    #    model.rc = Suffix(direction=Suffix.IMPORT_EXPORT)

    if FALLBACK_ON_BRUTE_FORCE_PREPROCESS:
        model.preprocess()
    else:
        _map = {}
        preprocess_block_constraints(b, idMap=_map)

    # Note: we wait to deactivate the objective until after we
    # preprocess so that the obective is correctly processed.
    b.separation_obj.deactivate()
    # (temporarily) deactivate the fixed stage-1 variables
    _con.deactivate()

    toc("InterScenario plugin: generated modified problem instance")
    return model
Beispiel #6
0
def solve_fixed_scenario_solutions(
        ph, scenario_tree, scenario_or_bundle,
        scenario_solutions, **model_options ):

    model = get_modified_instance(
        ph, scenario_tree, scenario_or_bundle, **model_options )
    _block = model._interscenario_plugin
    _param = _block.fixed_variable_values
    _sep = _block.separation_variables
    _con = _block.fixed_variables_constraint

    # We need to know which scenarios are local to this instance ... so
    # we don't waste time repeating work.
    if scenario_tree.contains_bundles():
        local_scenarios = scenario_or_bundle._scenario_names
    else:
        local_scenarios = [ scenario_or_bundle._name ]

    ipopt = SolverFactory("ipopt")

    #
    # Turn off RHO!
    #
    _saved_rho_values = _block.rho().extract_values()
    _block.rho().store_values(0)

    # Enable the constraints to fix the Stage 1 variables:
    _con.activate()

    # Solve each solution here and cache the resulting objective
    cutlist = []
    obj_values = []
    dual_values = []
    for var_values, scenario_name_list in scenario_solutions:
        local = False
        for scenario in local_scenarios:
            if scenario in scenario_name_list:
                local = True
                break
        if local:
            # Here is where we could save some time and not repeat work
            # ... for now I am being lazy and re-solving so that we get
            # the dual values, etc for this scenario as well.  If nothing
            # else, it makes averaging easier.
            pass

        assert( len(var_values) == len(_param) )
        for var_id, var_value in iteritems(var_values):
            _param[var_id] = var_value

        # TODO: We only need to update the StandardRepn for the binding
        # constraints ... so we could save a LOT of time by not
        # preprocessing the whole model.
        #
        if FALLBACK_ON_BRUTE_FORCE_PREPROCESS:
            model.preprocess()
        else:
            var_id_map = {}
            preprocess_block_constraints(_block, idMap=var_id_map)

        toc("preprocessed scenario %s" % ( scenario_or_bundle._name, ))
        output_buffer = StringIO()
        pyutilib.misc.setup_redirect(output_buffer)
        try:
            results = ph._solver.solve(model, tee=True) # warmstart=True)
        except:
            logger.warning("Exception raised solving the interscenario "
                           "evaluation subproblem")
            logger.warning("Solver log:\n%s" % output_buffer.getvalue())
            raise
        finally:
            pyutilib.misc.reset_redirect()
        toc("solved solution from scenario set %s on scenario %s" %
            ( scenario_name_list, scenario_or_bundle._name, ))

        ss = results.solver.status
        tc = results.solver.termination_condition
        #self.timeInSolver += results['Solver'][0]['Time']
        if ss == SolverStatus.ok and tc in _acceptable_termination_conditions:
            state = 0 #'FEASIBLE'
            if PYOMO_4_0:
                model.load(results)
            else:
                model.solutions.load_from(results)
            #
            # Turn off W, recompute the objective
            #
            _saved_w_values = _block.weights().extract_values()
            _block.weights().store_values(0)
            obj_values.append(value(_block.original_obj()))
            _block.weights().store_values(_saved_w_values)

            # NOTE: Getting the dual values resolves the model
            # (potentially relaxing second state variables.
            if _block.enable_rho:
                dual_values.append( get_dual_values(ph._solver, model) )
            else:
                dual_values.append(None)
            cutlist.append(".  ")
        elif True or tc in _infeasible_termination_conditions:
            state = 1 #'INFEASIBLE'
            obj_values.append(None)
            dual_values.append(None)
            if _block.enable_cuts:
                cut = solve_separation_problem(ph._solver, model, True)
                if cut == '????':
                    if ph._solver.problem_format() != ProblemFormat.nl:
                        model.preprocess()
                        #preprocess_block_objectives(_block)
                        #preprocess_block_constraints(_block)
                    cut = solve_separation_problem(ipopt, model, False)
            else:
                cut = "X  "
            cutlist.append( cut )
            toc("solved separation problem for solution from scenario set "
                "%s on scenario %s" %
                ( scenario_name_list, scenario_or_bundle._name, ))
        else:
            state = 2 #'NONOPTIMAL'
            obj_values.append(None)
            dual_values.append(None)
            cutlist.append("?  ")
            logger.warning("Solving the interscenario evaluation "
                           "subproblem failed (%s)." % (state,) )
            logger.warning("Solver log:\n%s" % output_buffer.getvalue())


    #
    # Turn RHO, W back on!
    #
    _block.weights().store_values(_saved_w_values)
    _block.rho().store_values(_saved_rho_values)

    # Disable the constraints to fix the Stage 1 variables:
    _con.deactivate()

    return obj_values, dual_values, cutlist
Beispiel #7
0
    def _distribute_cuts(self, ph, resolve=False):
        totalCuts = 0
        cutObj = sorted(
            c[0] for x in self.feasibility_cuts for c in x
            if type(c) is tuple and c[0] > self.cutThreshold_minDiff)
        if cutObj:
            allCutThreshold = cutObj[min(
                int((1 - self.cutThreshold_crossCut) * len(cutObj)),
                len(cutObj) - 1)]
        else:
            allCutThreshold = 1

        distributed = isinstance(ph._solver_manager, SolverManager_PHPyro)

        if ph._scenario_tree.contains_bundles():
            subproblems = ph._scenario_tree._scenario_bundles
            get_scenarios = lambda x: x._scenario_names
        else:
            subproblems = ph._scenario_tree._scenarios
            get_scenarios = lambda x: [x]

        resolves = []
        for problem in subproblems:
            cuts = []
            for id, (x, s) in enumerate(self.unique_scenario_solutions):
                found = False
                for scenario in get_scenarios(problem):
                    if scenario._name in s:
                        found = True
                        break
                if found:
                    cuts.extend(c[id] for c in self.feasibility_cuts
                                if type(c[id]) is tuple
                                and c[id][0] > self.cutThreshold_minDiff)
                elif self.feasible_objectives[id] is None:
                    # We only add cuts generated by other scenarios to
                    # scenarios that are not currently feasible (as
                    # these are feassibility cuts, they should not
                    # impact feasible scenarios)
                    cuts.extend(
                        c[id] for c in self.feasibility_cuts
                        if type(c[id]) is tuple and c[id][0] > allCutThreshold)

            if not cuts and not self.incumbent_cuts:
                resolves.append(None)
                continue

            totalCuts += len(cuts)
            if distributed:
                resolves.append(
                    ph._solver_manager.queue(
                        action="invoke_external_function",
                        name=problem._name,
                        queue_name=ph._phpyro_job_worker_map[problem._name],
                        invocation_type=InvocationType.SingleInvocation.key,
                        generateResponse=True,
                        module_name='pyomo.pysp.plugins.interscenario',
                        function_name='add_new_cuts',
                        function_kwds=None,
                        function_args=(cuts, self.incumbent_cuts, resolve),
                    ))
            else:
                ans = add_new_cuts(ph, ph._scenario_tree, problem, cuts,
                                   self.incumbent_cuts, resolve)
                resolves.append(ans)
                toc("distributed cuts to scenario %s%s" %
                    (problem._name,
                     ' and resolved scenario' if resolve else ''))

        toc("InterScenario plugin: added %d feasibility cuts from a "
            "library of %s cuts" % (totalCuts, len(cutObj)))
        self.feasibility_cuts = []

        if self.incumbent_cuts:
            print("InterScenario plugin: added %d incumbent cuts" %
                  (len(self.incumbent_cuts), ))
            self.incumbent_cuts = []

        if distributed:
            num_results_so_far = sum(1 for x in resolves if x is None)
            num_results = len(resolves)

            while (num_results_so_far < num_results):
                _ah = ph._solver_manager.wait_any()
                _ah_idx = resolves.index(_ah)
                resolves[_ah_idx] = ph._solver_manager.get_results(_ah)
                num_results_so_far += 1

        if resolve:
            # Transfer the first stage values and cost back to PH and
            # recompute xbar
            rootNode = ph._scenario_tree.findRootNode()
            for _id, problem in enumerate(subproblems):
                ans = resolves[_id]
                if ans is None:
                    continue
                for scenario in get_scenarios(problem):
                    scenario._cost = ans[1]
                    assert (sorted(ans[0]) == sorted(
                        scenario._x[rootNode._name]))
                    scenario._x[rootNode._name] = ans[0]  #[_vid] = _vval
            ph.update_variable_statistics()
Beispiel #8
0
    def _interscenario_plugin(self, ph):
        toc("InterScenario plugin: analyzing scenario dual information")

        # (1) Collect all scenario (first) stage variables
        #self._collect_unique_scenario_solutions(ph)

        # (2) Filter them to find a set we want to distribute
        pass

        # (3) Distribute (some) of the variable sets out to the
        # scenarios, fix, and resolve; Collect and return the
        # objectives, duals, and any cuts
        partial_obj_values, dual_values, cuts, probability \
            = self._solve_interscenario_solutions( ph )

        # Compute the non-anticipative objective values for each
        # scenario solution
        self.feasible_objectives = self._compute_objective(
            partial_obj_values, probability)

        for _id, soln in enumerate(self.unique_scenario_solutions):
            _scenarios = [ph._scenario_tree.get_scenario(x) for x in soln[1]]
            print(
                "  Solution %2d: generated %2d cuts, "
                "cut by %2d other scenarios; objective %10s, "
                "scenario cost [%s], cut obj [%s] [generated by %s]" %
                (_id, sum(1 for c in cuts[_id] if type(c) is tuple),
                 sum(1 for c in cuts if type(c[_id]) is tuple), "None"
                 if self.feasible_objectives[_id] is None else "%10.2f" %
                 self.feasible_objectives[_id], ", ".join("%10.2f" % x._cost
                                                          for x in _scenarios),
                 " ".join("%5.2f" % x[0] if type(x) is tuple else "%5s" % x
                          for x in cuts[_id]), ','.join(soln[1])))

        scenarioCosts = [
            ph._scenario_tree.get_scenario(x)._cost
            for s in self.unique_scenario_solutions for x in s[1]
        ]
        scenarioProb = [
            ph._scenario_tree.get_scenario(x)._probability
            for s in self.unique_scenario_solutions for x in s[1]
        ]
        _avg = sum(scenarioProb[i] * c for i, c in enumerate(scenarioCosts))
        _max = max(scenarioCosts)
        _min = min(scenarioCosts)
        if self.average_solution is None:
            _del_avg = None
            _del_avg_str = "-----%"
        else:
            _prev = self.average_solution
            _del_avg = (_avg - _prev) / max(abs(_avg), abs(_prev))
            _del_avg_str = "%+.2f%%" % (100 * _del_avg, )
        self.average_solution = _avg
        print("  Average scenario cost: %f (%s) Max-min: %f  (%0.2f%%)" %
              (_avg, _del_avg_str, _max - _min, abs(100. *
                                                    (_max - _min) / _avg)))

        # (4) save any cuts for distribution before the next solve
        #self.feasibility_cuts = []
        #for c in cuts:
        #    self.feasibility_cuts.extend(
        #        x for x in c if type(x) is tuple and x[0] > self.cutThreshold )
        #cutCount = len(self.feasibility_cuts)
        if self.enableFeasibilityCuts:
            self.feasibility_cuts = cuts
        cutCount = sum(
            sum(1 for x in c
                if type(x) is tuple and x[0] > self.cutThreshold_minDiff)
            for c in cuts)
        subProblemCount = sum(len(c) for c in cuts)

        # (5) compute and publish the new incumbent
        self._update_incumbent(ph)

        # (6a) If this is iteration 0, and we have feasibility cuts, and
        # they are (sufficiently) helping the Lagrangean bound, then
        # skip setting rho and do another round oc cuts
        if ph._current_iteration == 0:
            # Tell ph that we may have a good opter bound
            ph._update_reported_bounds(outer=self.average_solution)

            if (cutCount > self.recutThreshold * (subProblemCount - len(cuts))
                    and (_del_avg is None
                         or _del_avg > self.iteration0RecutBoundImprovement)):
                # Bypass RHO updates and check for more cuts
                #self.lastRun = ph._current_iteration - self.iterationInterval
                return

        # (6b) compute updated rho estimates
        new_rho, loginfo = self._process_dual_information(
            ph, dual_values, probability)
        _scale = self.rhoScale
        if self.rho is None:
            print("InterScenario plugin: initializing rho")
            self.rho = {}
            for v, r in iteritems(new_rho):
                self.rho[v] = _scale * r
        else:
            _damping = self.rhoDamping
            for v, r in iteritems(new_rho):
                if self.rho[v]:
                    self.rho[v] += (1 - _damping) * (_scale * r - self.rho[v])
                    #self.rho[v] = max(_scale*r,  self.rho[v]) - \
                    #    _damping*abs(_scale*r - self.rho[v])
                else:
                    self.rho[v] = _scale * r

        for v, l in sorted(iteritems(loginfo)):
            if v is None:
                print(l)
            else:
                print(l % (self.rho[v], ))

        #print("SETTING SELF.RHO", self.rho)
        rootNode = ph._scenario_tree.findRootNode()
        if self.enableRhoUpdates:
            for v, r in iteritems(self.rho):
                ph.setRhoAllScenarios(rootNode, v, r)
Beispiel #9
0
def get_modified_instance(ph, scenario_tree, scenario_or_bundle, **options):
    # Find the model
    if scenario_tree.contains_bundles():
        model = ph._bundle_binding_instance_map[scenario_or_bundle._name]
    else:
        model = ph._instances[scenario_or_bundle._name]
    b = model.component('_interscenario_plugin')
    if b is not None:
        return model

    #
    # We need to add the interscenario information to this model
    #
    model._interscenario_plugin = b = Block()

    # Save our options
    #
    b.epsilon = options.pop('epsilon')
    b.cut_scale = options.pop('cut_scale')
    b.allow_slack = options.pop('allow_slack')
    b.enable_rho = options.pop('enable_rho')
    b.enable_cuts = options.pop('enable_cuts')
    assert (len(options) == 0)

    # Information for generating cuts
    #
    b.cutlist = ConstraintList()
    b.abs_int_vars = VarList(within=NonNegativeIntegers)
    b.abs_binary_vars = VarList(within=Binary)

    # Note: the var_ids are on the ORIGINAL scenario models
    rootNode = scenario_tree.findRootNode()
    var_ids = list(iterkeys(rootNode._variable_datas))

    # Right now, this is hard-coded for 2-stage problems - so we only
    # need to worry about the variables from the root node.  These
    # variables should exist on all scenarios.  Set up a (trivial)
    # equality constraint for each variable:
    #    var == current_value{param} + separation_variable{var, fixed=0}
    b.STAGE1VAR = _S1V = Set(initialize=var_ids)
    b.separation_variables = _sep = Var(_S1V, dense=True)
    b.fixed_variable_values = _param = Param(_S1V, mutable=True, initialize=0)

    b.rho = weakref.ref(model.component('PHRHO_%s' % rootNode._name))
    b.weights = weakref.ref(model.component('PHWEIGHT_%s' % rootNode._name))

    if b.allow_slack:
        for idx in _sep:
            _sep[idx].setlb(-b.epsilon)
            _sep[idx].setub(b.epsilon)
    else:
        _sep.fix(0)

    _cuidBuffer = {}
    _src = b.local_stage1_varmap = {}
    for i in _S1V:
        # Note indexing: for each 1st stage var, pick an arbitrary
        # (first) scenario and return the variable (and not it's
        # probability)
        _cuid = ComponentUID(rootNode._variable_datas[i][0][0], _cuidBuffer)
        _src[i] = weakref.ref(_cuid.find_component_on(model))
        #_base_src[i] = weakref.ref(_cuid.find_component_on(base_model))

    def _set_var_value(b, i):
        return _param[i] + _sep[i] - _src[i]() == 0
    b.fixed_variables_constraint \
        = _con = Constraint( _S1V, rule=_set_var_value )

    #
    # TODO: When we get the duals of the first-stage variables, do we
    # want the dual WRT the original objective, or the dual WRT the
    # augmented objective?
    #
    # Move the objective to a standardized place so we can easily find it later
    if PYOMO_4_0:
        _orig_objective = list(x[2] for x in model.all_component_data(
            Objective, active=True, descend_into=True))
    else:
        _orig_objective = list(
            model.component_data_objects(Objective,
                                         active=True,
                                         descend_into=True))
    assert (len(_orig_objective) == 1)
    _orig_objective = _orig_objective[0]
    b.original_obj = weakref.ref(_orig_objective)

    # add (and deactivate) the objective for the infeasibility
    # separation problem.
    b.separation_obj = Objective(expr=sum(_sep[i]**2 for i in var_ids),
                                 sense=minimize)

    # Make sure we get dual information
    if 'dual' not in model:
        # Export and import floating point data
        model.dual = Suffix(direction=Suffix.IMPORT_EXPORT)
    #if 'rc' not in model:
    #    model.rc = Suffix(direction=Suffix.IMPORT_EXPORT)

    if FALLBACK_ON_BRUTE_FORCE_PREPROCESS:
        model.preprocess()
    else:
        _map = {}
        preprocess_block_constraints(b, idMap=_map)

    # Note: we wait to deactivate the objective until after we
    # preprocess so that the obective is correctly processed.
    b.separation_obj.deactivate()
    # (temporarily) deactivate the fixed stage-1 variables
    _con.deactivate()

    toc("InterScenario plugin: generated modified problem instance")
    return model
Beispiel #10
0
def solve_fixed_scenario_solutions(ph, scenario_tree, scenario_or_bundle,
                                   scenario_solutions, **model_options):

    model = get_modified_instance(ph, scenario_tree, scenario_or_bundle,
                                  **model_options)
    _block = model._interscenario_plugin
    _param = _block.fixed_variable_values
    _sep = _block.separation_variables
    _con = _block.fixed_variables_constraint

    # We need to know which scenarios are local to this instance ... so
    # we don't waste time repeating work.
    if scenario_tree.contains_bundles():
        local_scenarios = scenario_or_bundle._scenario_names
    else:
        local_scenarios = [scenario_or_bundle._name]

    ipopt = SolverFactory("ipopt")

    #
    # Turn off RHO!
    #
    _saved_rho_values = _block.rho().extract_values()
    _block.rho().store_values(0)

    # Enable the constraints to fix the Stage 1 variables:
    _con.activate()

    # Solve each solution here and cache the resulting objective
    cutlist = []
    obj_values = []
    dual_values = []
    for var_values, scenario_name_list in scenario_solutions:
        local = False
        for scenario in local_scenarios:
            if scenario in scenario_name_list:
                local = True
                break
        if local:
            # Here is where we could save some time and not repeat work
            # ... for now I am being lazy and re-solving so that we get
            # the dual values, etc for this scenario as well.  If nothing
            # else, it makes averaging easier.
            pass

        assert (len(var_values) == len(_param))
        for var_id, var_value in iteritems(var_values):
            _param[var_id] = var_value

        # TODO: We only need to update the StandardRepn for the binding
        # constraints ... so we could save a LOT of time by not
        # preprocessing the whole model.
        #
        if FALLBACK_ON_BRUTE_FORCE_PREPROCESS:
            model.preprocess()
        else:
            var_id_map = {}
            preprocess_block_constraints(_block, idMap=var_id_map)

        toc("preprocessed scenario %s" % (scenario_or_bundle._name, ))
        output_buffer = StringIO()
        setup_redirect(output_buffer)
        try:
            results = ph._solver.solve(model, tee=True)  # warmstart=True)
        except:
            logger.warning("Exception raised solving the interscenario "
                           "evaluation subproblem")
            logger.warning("Solver log:\n%s" % output_buffer.getvalue())
            raise
        finally:
            reset_redirect()
        toc("solved solution from scenario set %s on scenario %s" % (
            scenario_name_list,
            scenario_or_bundle._name,
        ))

        ss = results.solver.status
        tc = results.solver.termination_condition
        #self.timeInSolver += results['Solver'][0]['Time']
        if ss == SolverStatus.ok and tc in _acceptable_termination_conditions:
            state = 0  #'FEASIBLE'
            if PYOMO_4_0:
                model.load(results)
            else:
                model.solutions.load_from(results)
            #
            # Turn off W, recompute the objective
            #
            _saved_w_values = _block.weights().extract_values()
            _block.weights().store_values(0)
            obj_values.append(value(_block.original_obj()))
            _block.weights().store_values(_saved_w_values)

            # NOTE: Getting the dual values resolves the model
            # (potentially relaxing second state variables.
            if _block.enable_rho:
                dual_values.append(get_dual_values(ph._solver, model))
            else:
                dual_values.append(None)
            cutlist.append(".  ")
        elif True or tc in _infeasible_termination_conditions:
            state = 1  #'INFEASIBLE'
            obj_values.append(None)
            dual_values.append(None)
            if _block.enable_cuts:
                cut = solve_separation_problem(ph._solver, model, True)
                if cut == '????':
                    if ph._solver.problem_format() != ProblemFormat.nl:
                        model.preprocess()
                        #preprocess_block_objectives(_block)
                        #preprocess_block_constraints(_block)
                    cut = solve_separation_problem(ipopt, model, False)
            else:
                cut = "X  "
            cutlist.append(cut)
            toc("solved separation problem for solution from scenario set "
                "%s on scenario %s" % (
                    scenario_name_list,
                    scenario_or_bundle._name,
                ))
        else:
            state = 2  #'NONOPTIMAL'
            obj_values.append(None)
            dual_values.append(None)
            cutlist.append("?  ")
            logger.warning("Solving the interscenario evaluation "
                           "subproblem failed (%s)." % (state, ))
            logger.warning("Solver log:\n%s" % output_buffer.getvalue())

    #
    # Turn RHO, W back on!
    #
    _block.weights().store_values(_saved_w_values)
    _block.rho().store_values(_saved_rho_values)

    # Disable the constraints to fix the Stage 1 variables:
    _con.deactivate()

    return obj_values, dual_values, cutlist
Beispiel #11
0
    def _interscenario_plugin(self,ph):
        toc("InterScenario plugin: analyzing scenario dual information")

        # (1) Collect all scenario (first) stage variables
        self._collect_unique_scenario_solutions(ph)

        # (2) Filter them to find a set we want to distribute
        pass

        # (3) Distribute (some) of the variable sets out to the
        # scenarios, fix, and resolve; Collect and return the
        # objectives, duals, and any cuts
        partial_obj_values, dual_values, cuts, probability \
            = self._solve_interscenario_solutions( ph )

        # Compute the non-anticipative objective values for each
        # scenario solution
        self.feasible_objectives = self._compute_objective(
            partial_obj_values, probability )

        for _id, soln in enumerate(self.unique_scenario_solutions):
            _scenarios = [ph._scenario_tree.get_scenario(x) for x in soln[1]]
            print(
                "  Solution %2d: generated %2d cuts, "
                "cut by %2d other scenarios; objective %10s, "
                "scenario cost [%s], cut obj [%s] [generated by %s]" % (
                    _id,
                    sum(1 for c in cuts[_id] if type(c) is tuple),
                    sum(1 for c in cuts if type(c[_id]) is tuple),
                    "None" if self.feasible_objectives[_id] is None
                    else "%10.2f" % self.feasible_objectives[_id],
                    ", ".join("%10.2f" % x._cost for x in _scenarios),
                    " ".join("%5.2f" % x[0] if type(x) is tuple else "%5s" % x
                             for x in cuts[_id]),
                    ','.join(soln[1])
                ))
            )
        scenarioCosts = [ ph._scenario_tree.get_scenario(x)._cost 
                          for s in self.unique_scenario_solutions
                          for x in s[1] ]
        scenarioProb =  [ ph._scenario_tree.get_scenario(x)._probability 
                          for s in self.unique_scenario_solutions
                          for x in s[1] ]
        _avg = sum( scenarioProb[i]*c for i,c in enumerate(scenarioCosts) )
        _max = max( scenarioCosts )
        _min = min( scenarioCosts )
        if self.average_solution is None:
            _del_avg = None
            _del_avg_str = "-----%"
        else:
            _prev = self.average_solution
            _del_avg = (_avg-_prev) / max(abs(_avg),abs(_prev))
            _del_avg_str = "%+.2f%%" % ( 100*_del_avg, )
        self.average_solution = _avg
        print("  Average scenario cost: %f (%s) Max-min: %f  (%0.2f%%)" % (
            _avg, _del_avg_str, _max-_min, abs(100.*(_max-_min)/_avg) ))

        # (4) save any cuts for distribution before the next solve
        #self.feasibility_cuts = []
        #for c in cuts:
        #    self.feasibility_cuts.extend(
        #        x for x in c if type(x) is tuple and x[0] > self.cutThreshold )
        #cutCount = len(self.feasibility_cuts)
        if self.enableFeasibilityCuts:
            self.feasibility_cuts = cuts
        cutCount = sum( sum( 1 for x in c if type(x) is tuple 
                             and  x[0]>self.cutThreshold_minDiff )
                        for c in cuts )
Beispiel #12
0
                        name=problem._name,
                        queue_name=ph._phpyro_job_worker_map[problem._name],
                        invocation_type=InvocationType.SingleInvocation.key,
                        generateResponse=True,
                        module_name='pyomo.pysp.plugins.interscenario',
                        function_name='add_new_cuts',
                        function_kwds=None,
                        function_args=( cuts,
                                        self.incumbent_cuts,
                                        resolve ),
                    ) )
            else:
                ans = add_new_cuts( ph, ph._scenario_tree, problem,
                                    cuts, self.incumbent_cuts, resolve )
                resolves.append(ans)
                toc("distributed cuts to scenario %s%s" %
                    ( problem._name,
                      ' and resolved scenario' if resolve else '' ))

        toc( "InterScenario plugin: added %d feasibility cuts from a "
               "library of %s cuts" % (totalCuts, len(cutObj)) )
        self.feasibility_cuts = []

        if self.incumbent_cuts:
            print( "InterScenario plugin: added %d incumbent cuts" %
                   (len(self.incumbent_cuts), ) )
            self.incumbent_cuts = []


        if distributed:
            num_results_so_far = sum(1 for x in resolves if x is None)