Ejemplo n.º 1
0
def main():
    inst = "network-10-20-L-01"
    num_scen = int(inst.split("-")[-3])
    scenario_names = [f"Scen{i}" for i in range(num_scen)]
    scenario_creator_options = {"cb_data": f"./data/{inst}.dat"}
    options = {"solver": "gurobi"}
    ef = ExtensiveForm(options,
                       scenario_names,
                       scenario_creator,
                       model_name=f"{inst}-EF",
                       scenario_creator_options=scenario_creator_options)
    results = ef.solve_extensive_form()
    print("Netdes objective value:", pyo.value(ef.ef.EF_Obj))
    def solve_program(self):
        options = {"solver": self.solver_string}

        self.ef = ExtensiveForm(
            options=options,
            all_scenario_names=self.all_scenario_names,
            scenario_creator=self.scenario_creator,
        )

        self.results = self.ef.solve_extensive_form(
            solver_options=self.solver_options)

        objval = self.ef.get_objective_value()

        return objval
Ejemplo n.º 3
0
# Copyright 2020 by B. Knueven, D. Mildebrath, C. Muir, J-P Watson, and D.L. Woodruff
# This software is distributed under the 3-clause BSD License.
import sys
import hydro
import pyomo.environ as pyo
from mpisppy.opt.ef import ExtensiveForm
import mpisppy.utils.sputils as sputils

options = {"solver": sys.argv[1]}
num_scenarios = 9
BFs = [3, 3]
all_scenario_names = [f"Scen{i+1}" for i in range(num_scenarios)]

# This is multi-stage, so we need to supply node names
all_nodenames = sputils.create_nodenames_from_branching_factors(BFs)

options["branching_factors"] = BFs
ef = ExtensiveForm(options,
                   all_scenario_names,
                   hydro.scenario_creator,
                   scenario_creator_kwargs={"branching_factors": BFs},
                   all_nodenames=all_nodenames)
ef.solve_extensive_form(tee=True)
print(f'hydro objective value {pyo.value(ef.ef.EF_Obj)}')
ef.report_var_values_at_rank0("Hydro")
Ejemplo n.º 4
0
# Copyright 2020 by B. Knueven, D. Mildebrath, C. Muir, J-P Watson, and D.L. Woodruff
# This software is distributed under the 3-clause BSD License.
import sys
import uc_funcs as uc
import pyomo.environ as pyo

from mpisppy.opt.ef import ExtensiveForm
""" UC """
solver_name = sys.argv[1]
scen_count = 3
scenario_names = [f"Scenario{i+1}" for i in range(scen_count)]
scenario_creator_kwargs = {"path": f"{scen_count}scenarios_r1/"}
options = {"solver": solver_name}
ef = ExtensiveForm(
    options,
    scenario_names,
    uc.scenario_creator,
    model_name="TestEF",
    scenario_creator_kwargs=scenario_creator_kwargs,
)
results = ef.solve_extensive_form(tee=True)
print(f"{scen_count}-scenario UC objective value:", pyo.value(ef.ef.EF_Obj))
Ejemplo n.º 5
0
    _print_usage()
    sys.exit()
elif int(sys.argv[1]) not in [3,10]:
    _print_usage()
    sys.exit()

try:
    solver_avail = SolverFactory(sys.argv[2]).available()
    if not solver_avail:
        print(f"Cannot find solver {sys.argv[2]}")
        sys.exit()
except:
    print(f"Cannot find solver {sys.argv[2]}")
    _print_usage()
    sys.exit()

num_scen = int(sys.argv[1])
solver = sys.argv[2]

sizes = PySPModel(scenario_creator='./models/ReferenceModel.py',
                  tree_model=f'./SIZES{num_scen}/ScenarioStructure.dat',
                  )


ef = ExtensiveForm(options={'solver':solver}, 
                   all_scenario_names=sizes.all_scenario_names,
                   scenario_creator=sizes.scenario_creator,
                   model_name='sizes_EF')

ef.solve_extensive_form(tee=True)
Ejemplo n.º 6
0
# Copyright 2020 by B. Knueven, D. Mildebrath, C. Muir, J-P Watson, and D.L. Woodruff
# This software is distributed under the 3-clause BSD License.
import sys
import uc_funcs as uc
import pyomo.environ as pyo

from mpisppy.opt.ef import ExtensiveForm
""" UC """
solver_name = sys.argv[1]
scen_count = 3
scenario_names = [f"Scenario{i+1}" for i in range(scen_count)]
scenario_creator_kwargs = {"path": f"{scen_count}scenarios_r1/"}
options = {"solver": solver_name}
ef = ExtensiveForm(
    options,
    scenario_names,
    uc.scenario_creator,
    model_name="TestEF",
    scenario_creator_kwargs=scenario_creator_kwargs,
)
results = ef.solve_extensive_form(tee=True)
print(f"{scen_count}-scenario UC objective value:", pyo.value(ef.ef.EF_Obj))
if ef.tree_solution_available:
    print(f"Writing tree solution")
    ef.write_tree_solution(f"{scen_count}scenario_EF_solution",
                           uc.scenario_tree_solution_writer)
class PyomoModelRunner:
    def __init__(
        self,
        model_constructor,
        model_constructor_params,
        n_scenarios,
        demand_provider,
        demand_provider_kwargs=None,
        scenario_name_start=0,  # Used this as starting seed for Pyomo experiments with sim data
        solver_string="gurobi_persistent",
        solver_options={
            "LogFile": "gurobi.log",
            "OutputFlag": 1,
            "LogToConsole": 0
        },
        log=None,
    ):
        self.model_constructor = model_constructor
        self.model_constructor_params = model_constructor_params
        self.n_scenarios = n_scenarios
        self.demand_provider = demand_provider
        self.demand_provider_kwargs = demand_provider_kwargs
        self.scenario_name_start = scenario_name_start
        self.solver_string = solver_string
        self.solver_options = solver_options

        self.all_scenario_names = [
            f"{i+self.scenario_name_start}"
            for i in range(0, self.n_scenarios)
        ]

        self.checks_to_perform = self._determine_checks_to_perform()

        self.log = log

    def scenario_creator(self, scenario_name):

        if self.demand_provider_kwargs:
            prov = self.demand_provider(**self.demand_provider_kwargs,
                                        seed=int(scenario_name))
        else:
            prov = self.demand_provider(seed=int(scenario_name))
        prov.reset()
        demand = {
            t: prov.generate_demand()
            for t in range(1, self.model_constructor_params["t_max"] + 1)
        }

        model = self.model_constructor(
            demand=demand, **self.model_constructor_params).build_model()

        # Telling it which decisions belong to first stage - for us this could be all our policy parameters
        # because we can't change them during a trajectory

        first_stage_params = self._get_first_stage_decision_params(model)

        sputils.attach_root_node(model, 0, first_stage_params)
        # If we don't specify, assume that all equally likely
        model._mpisppy_probability = 1.0 / self.n_scenarios
        return model

    def _get_first_stage_decision_params(self, model):

        if self.model_constructor.policy_parameters() == ["s", "S"]:
            return [model.s, model.S]
        elif self.model_constructor.policy_parameters() == ["s", "Q"]:
            return [model.s, model.Q]
        elif self.model_constructor.policy_parameters() == [
                "s", "S", "alpha", "Q"
        ]:
            return [model.s, model.S, model.alpha, model.Q]
        elif self.model_constructor.policy_parameters() == [
                "s", "S", "beta", "Q"
        ]:
            return [model.s, model.S, model.beta, model.Q]
        elif self.model_constructor.policy_parameters() == ["S"]:
            return [model.S]
        else:
            raise ValueError("Policy parameters not recognised")

    def solve_program(self):
        options = {"solver": self.solver_string}

        self.ef = ExtensiveForm(
            options=options,
            all_scenario_names=self.all_scenario_names,
            scenario_creator=self.scenario_creator,
        )

        self.results = self.ef.solve_extensive_form(
            solver_options=self.solver_options)

        objval = self.ef.get_objective_value()

        return objval

    def construct_results_dfs(self):
        self.results_list = []
        self.costs_df = pd.DataFrame(columns=[
            "Seed",
            "Variable cost",
            "Holding cost",
            "Fixed cost",
            "Wastage cost",
            "Shortage cost",
        ])
        for tup in self.ef.scenarios():
            scen = tup[0]
            if self.demand_provider_kwargs:
                prov = self.demand_provider(**self.demand_provider_kwargs,
                                            seed=int(scen))
            else:
                prov = self.demand_provider(seed=int(scen))
            prov.reset()
            demand = {
                t: prov.generate_demand()
                for t in range(1, self.model_constructor_params["t_max"] + 1)
            }
            model = tup[1]

            # Add common variables to output
            res_dicts = [{
                "opening_inventory":
                [round(model.IssB[t, a](), 0) for a in model.A],
                "received": [round(model.X[t, a](), 0) for a in model.A],
                "demand":
                round(demand[t], 0),
                "DSSR": [round(model.DssR[t, a](), 0) for a in model.A],
                "wastage":
                round(model.W[t](), 0),
                "shortage":
                round(model.E[t](), 0),
                "closing inventory":
                [round(model.IssE[t, a](), 0) for a in model.A],
                "inventory position":
                round(model.IP[t](), 0),
                "order quantity":
                round(model.OQ[t](), 0),
            } for t in model.T]

            # Add policy paramters to results
            for res_dict, t in zip(res_dicts, model.T):
                for param in self.model_constructor.policy_parameters():
                    if self.model_constructor_params["weekly_policy"]:
                        param_string = f"model.{param}[(t-1) % 7]()"
                    else:
                        param_string = f"model.{param}[t]()"
                    res_dict[f"{param}"] = round(eval(param_string), 0)

            self.results_list.append(pd.DataFrame(res_dicts))

            # Record the costs for each scenario and store in a single Pandas DataFrame
            scen_costs_dict = {
                "Seed": scen,
                "Variable cost": round(model.variable_cost(), 0),
                "Holding cost": round(model.holding_cost(), 0),
                "Fixed cost": round(model.fixed_cost(), 0),
                "Wastage cost": round(model.wastage_cost(), 0),
                "Shortage cost": round(model.shortage_cost(), 0),
            }

            self.costs_df = self.costs_df.append(scen_costs_dict,
                                                 ignore_index=True)

            if self.log is not None:
                self.log.info(f"##### Scenario {scen} #####")
                self.log.info(
                    f"Variable cost: {round(model.variable_cost(),0)}")
                self.log.info(f"Holding cost: {round(model.holding_cost(),0)}")
                self.log.info(f"Fixed cost: {round(model.fixed_cost(),0)}")
                self.log.info(f"Wastage cost: {round(model.wastage_cost(),0)}")
                self.log.info(
                    f"Shortage cost: {round(model.shortage_cost(),0)}")
            else:
                print(f"##### Scenario {scen} #####")
                # For now, also print the costs as useful for debugging
                print(f"Variable cost: {round(model.variable_cost(),0)}")
                print(f"Holding cost: {round(model.holding_cost(),0)}")
                print(f"Fixed cost: {round(model.fixed_cost(),0)}")
                print(f"Wastage cost: {round(model.wastage_cost(),0)}")
                print(f"Shortage cost: {round(model.shortage_cost(),0)}")

    def save_results(self, directory_path_string):
        for scen, df in zip(self.all_scenario_names, self.results_list):
            filename = Path(
                directory_path_string) / f"scenario_{scen}_output.csv"
            df.to_csv(filename)

        filename = Path(directory_path_string) / f"all_costs.csv"
        self.costs_df.to_csv(filename)

    def check_outputs(self, directory_path_string):
        self.results_of_checks_list = []
        for scen, scenario_df in zip(self.all_scenario_names,
                                     self.results_list):

            # Ensure that entries in columns with array values are numpy arrays
            array_cols = [
                "opening_inventory", "received", "DSSR", "closing inventory"
            ]
            for col in array_cols:
                scenario_df[f"{col}"] = scenario_df[f"{col}"].apply(
                    lambda x: np.array(x))

            # Do a merge to easily run checks where we look at consecutive rows
            merged_results = pd.concat(
                [
                    scenario_df,
                    scenario_df.loc[:, ["opening_inventory", "received"]].
                    shift(-1).add_prefix("next_"),
                ],
                axis=1,
            )

            # Run the necessary checks
            out_df = pd.DataFrame()
            for f in self.checks_to_perform:
                res = merged_results.apply(f, axis=1)
                out_df = pd.concat([out_df, res], axis=1)

            # Print the number of rows with failure and store
            # the results if any failures for a scenario
            fail_check_rows = out_df[~out_df.all(axis=1)]
            n_rows_with_fail = fail_check_rows.shape[0]
            if self.log is not None:
                self.log.info(
                    f"Scenario {scen}: {n_rows_with_fail} rows with a failed check"
                )
            else:
                print(
                    f"Scenario {scen}: {n_rows_with_fail} rows with a failed check"
                )

            if n_rows_with_fail > 0:
                filename = Path(
                    directory_path_string) / f"scenario_{scen}_checks.csv"
                out_df.to_csv(filename)

            self.results_of_checks_list.append(out_df)

    ### Functions for checking the output is consistent with constraints ###
    # TODO: Could run a check that policy params same in each scenario

    def _determine_checks_to_perform(self):
        checks_to_run = [
            self._check_wastage,
            self._check_shortage,
            self._check_inventory_during_day,
            self._check_no_max_age_opening_inventory,
            self._check_close_to_next_open_inventory,
            self._check_order_to_next_received,
        ]
        if self.model_constructor.policy_parameters() == ["s", "S"]:
            return checks_to_run + [self._check_sS]
        elif self.model_constructor.policy_parameters() == ["s", "Q"]:
            return checks_to_run + [self._check_sQ]
        elif self.model_constructor.policy_parameters() == [
                "s", "S", "alpha", "Q"
        ]:
            return checks_to_run + [self._check_sSaQ]
        elif self.model_constructor.policy_parameters() == [
                "s", "S", "beta", "Q"
        ]:
            return checks_to_run + [self._check_sSbQ]
        elif self.model_constructor.policy_parameters() == ["S"]:
            return checks_to_run + [self._check_S]
        else:
            raise ValueError("Policy parameters not recognised")

    # High level wastage check
    def _check_wastage(self, row):
        return pd.Series({
            "check_wastage":
            row["wastage"] == max(
                0, row["opening_inventory"][0] + row["received"][0] -
                row["demand"])
        })

    # High level shortage check
    def _check_shortage(self, row):
        return pd.Series({
            "check_shortage":
            row["shortage"] == max(
                0,
                row["demand"] - row["opening_inventory"].sum() -
                row["received"].sum(),
            )
        })

    # Check closing inventory
    def _calculate_remaining_stock_and_demand(self, row):
        total_remaining_demand = row["demand"]
        inventory = row["opening_inventory"] + row["received"]
        remaining_demand = np.zeros_like(inventory)
        for idx, stock in enumerate(inventory):
            demand_filled = min(total_remaining_demand, stock)
            remaining_stock = stock - demand_filled
            total_remaining_demand = total_remaining_demand - demand_filled
            inventory[idx] = remaining_stock
            remaining_demand[idx] = total_remaining_demand

        return inventory, remaining_demand

    def _check_inventory_during_day(self, row):

        (
            calc_closing_inventory,
            calc_remaining_demand,
        ) = self._calculate_remaining_stock_and_demand(row)

        return pd.Series({
            "check_closing_inventory":
            (row["closing inventory"] == calc_closing_inventory).all(),
            "check_DSSR": (row["DSSR"] == calc_remaining_demand).all(),
            "check_inventory_position":
            row["inventory position"] == row["closing inventory"][1:].sum(),
        })

    def _check_no_max_age_opening_inventory(self, row):
        return pd.Series({
            "check_no_max_age_opening_inventory":
            row["opening_inventory"][-1] == 0
        })

    def _check_close_to_next_open_inventory(self, row):
        if row["next_opening_inventory"] is np.nan:
            return pd.Series({"check_close_to_next_open_inventory": None})
        else:
            return pd.Series({
                "check_close_to_next_open_inventory":
                (row["closing inventory"][1:] == row["next_opening_inventory"]
                 [:-1]).all()
            })

    def _check_order_to_next_received(self, row):
        if row["next_received"] is np.nan:
            return pd.Series({"check_order_to_next_received": None})
        else:
            return pd.Series({
                "check_order_to_next_received":
                row["order quantity"] == row["next_received"].sum()
            })

    def _check_sS(self, row):

        S_gt_s = row["S"] >= row["s"] + 1

        if row["inventory position"] < row["s"]:
            order_quantity_to_params = (row["order quantity"] == row["S"] -
                                        row["inventory position"])
        else:
            order_quantity_to_params = row["order quantity"] == 0

        return pd.Series({
            "check_sS_S_gt_s":
            S_gt_s,
            "check_sS_order_quantity_to_params":
            order_quantity_to_params,
        })

    def _check_S(self, row):

        if row["inventory position"] < row["S"]:
            order_quantity_to_params = (row["order quantity"] == row["S"] -
                                        row["inventory position"])
        else:
            order_quantity_to_params = row["order quantity"] == 0

        return pd.Series({
            "check_S_order_quantity_to_params":
            order_quantity_to_params,
        })

    def _check_sQ(self, row):
        if row["inventory position"] < row["s"]:
            order_quantity_to_params = row["order quantity"] == row["Q"]
        else:
            order_quantity_to_params = row["order quantity"] == 0

        return pd.Series(
            {"check_sQ_order_quantity_to_params": order_quantity_to_params})

    def _check_sSaQ(self, row):

        S_gt_s = row["S"] >= row["s"] + 1

        s_gt_a = row["s"] >= row["alpha"] + 1

        if row["inventory position"] < row["alpha"]:
            order_quantity_to_params = (row["order quantity"] == row["S"] -
                                        row["inventory position"])
        elif row["inventory position"] < row["s"]:
            order_quantity_to_params = row["order quantity"] == row["Q"]
        else:
            order_quantity_to_params = row["order quantity"] == 0

        return pd.Series({
            "check_sSaQ_S_gt_s":
            S_gt_s,
            "check_sSaQ_s_gt_a":
            s_gt_a,
            "check_sSaQ_order_quantity_to_params":
            order_quantity_to_params,
        })

    def _check_sSbQ(self, row):

        S_gt_s = row["S"] >= row["s"] + 1

        s_gt_b = row["s"] >= row["beta"] + 1

        if row["inventory position"] < row["beta"]:
            order_quantity_to_params = row["order quantity"] == row["Q"]
        elif row["inventory position"] < row["s"]:
            order_quantity_to_params = (row["order quantity"] == row["S"] -
                                        row["inventory position"])
        else:
            order_quantity_to_params = row["order quantity"] == 0

        return pd.Series({
            "check_sSbQ_S_gt_s":
            S_gt_s,
            "check_sSbQ_s_gt_b":
            s_gt_b,
            "check_sSbQ_order_quantity_to_params":
            order_quantity_to_params,
        })