def test_MST_spanning_diamond(): stages = setup_diamond_network_extra_edge() gsm = GuaranteedServiceModelDAG(stages) MST, _ = gsm._find_MST(stages) MST_cost = GuaranteedServiceModelDAG._compute_spanning_tree_cost(MST) assert MST_cost == calc_edge_cost(MST) assert MST_cost == -83.895 St = namedtuple('St', ['id', 'up', 'down']) l = ( (St('A', [], ['B', 'C']), St('B', ['A'], []), St('C', ['A'], ['D']), St('D', ['C'], [])), # no B->D,A->D (St('A', [], ['C', 'D']), St('C', ['A'], []), St('B', [], ['D']), St('D', ['B', 'A'], [])), # no C->D,A->B (St('A', [], ['D']), St('C', [], ['D']), St('B', [], ['D']), St('D', ['B', 'C', 'A'], [])), # no A->B,A->C (St('A', [], ['B']), St('B', ['A'], ['D']), St('C', [], ['D']), St('D', ['B', 'C'], [])), # no A->C,A->D (St('A', [], ['C']), St('B', [], ['D']), St('C', ['A'], ['D']), St('D', ['B', 'C'], [])) # no A->D,A->B ) # try some permutations for spanning tree for i, spanning_tree in enumerate(l): for s in spanning_tree: gsm.tree_stages[s.id].up_stages = {u: 1 for u in s.up} gsm.tree_stages[s.id].down_stages = {d: 1 for d in s.down} assert GuaranteedServiceModelDAG._compute_spanning_tree_cost( gsm.tree_stages) >= MST_cost check_mst_vs_scipy(stages, MST)
def test_ext_diamond_demand_bounds(): """ The true reference demand bound values in this and all other "test_..._demand_bounds" tests were computed by looking at the topology and using the pooling formula from Appendix 2.4. Demand Propagation in Humair and Willems 2011 "Optimizing inventory in DAGs" and the demand_bound_function for demand stages as in original tree GSM paper (Grave and Willems 2000) """ stages = setup_ext_diamond_network() gsm = GuaranteedServiceModelDAG(stages) for stage_id, stage in gsm.stages.items(): if stage_id == "A": assert set(stage.demand_stages_phis.keys()) == set(["D", "E"]) assert stage.demand_stages_phis["D"] == 2 assert stage.demand_stages_phis["E"] == 1 elif stage_id in ["B", "C", "D"]: assert len(stage.demand_stages_phis.keys()) == 1 assert stage.demand_stages_phis["D"] == 1 else: assert stage_id == "E" assert len(stage.demand_stages_phis.keys()) == 1 assert stage.demand_stages_phis["E"] == 1 stage_tau_val_list = [("D", 10, 25.20), ("C", 11, 27.45), ("B", 12, 29.69), ("A", 13, 213.94)] for stage_id, tau, d_bound in stage_tau_val_list: np.testing.assert_approx_equal( gsm.stages[stage_id].demand_bound_func(tau), d_bound, 4)
def calc_edge_cost(stages: Dict[str, Stage]): cost = 0 for stage_id, stage in stages.items(): for down_stage_id in stage.down_stages: down_stage = stages[down_stage_id] cost += GuaranteedServiceModelDAG._get_edge_cost(stage, down_stage) return cost
def check_mst_vs_scipy(stages_full: Dict[str, Stage], mst: Dict[str, Stage]) -> None: """ compare our MST solution to scipy baseline :param stages_full: full network :param mst: mst solution """ assign_ids = np.array( [stage_id for stage_id in sorted(stages_full.keys())]) N = assign_ids.size # create cost array cost = np.zeros((len(stages_full), len(stages_full))) for stage_id, stage in stages_full.items(): for down_stage_id in stage.down_stages: down_stage = stages_full[down_stage_id] i = np.flatnonzero(assign_ids == stage_id) j = np.flatnonzero(assign_ids == down_stage_id) cost[j, i] = cost[i, j] = GuaranteedServiceModelDAG._get_edge_cost( stage, down_stage) C = minimum_spanning_tree(cost).toarray() assert C.sum() == calc_edge_cost( mst), 'different costs for MST and scipy solvers' for i, c in enumerate(C): for d in range(N): if assign_ids[d] in mst[assign_ids[i]].down_stages.keys(): assert C[i, d] != 0, 'scipy said there is no edge' else: assert C[i, d] == 0, 'scipy said there must be an edge'
def test_coc_handling(): stages = setup_coc_network() gsm = GuaranteedServiceModelDAG(stages) for stage_id, stage in gsm.stages.items(): if stage_id in ["A", "B"]: assert set(stage.demand_stages_phis.keys()) == set(["C", "D"]) else: assert len(stage.demand_stages_phis) == 1 assert stage_id in stage.demand_stages_phis
def test_spanning_tree_basic_scenario(): stages = setup_diamond_network() gsm = GuaranteedServiceModelDAG(stages) St = namedtuple('St', ['id', 'up', 'down']) l = ( ((St('A', [], ['B', 'C']), St('B', ['A'], []), St('C', ['A'], ['D']), St('D', ['C'], [])), set([("B", "D")])), # no B->D ((St('A', [], ['B', 'C']), St('C', ['A'], []), St('B', ['A'], ['D']), St('D', ['B'], [])), set([("C", "D")])), # no C->D ((St('A', [], ['C']), St('C', ['A'], ['D']), St('B', [], ['D']), St('D', ['B', 'C'], [])), set([("A", "B")])), # no A->B ((St('A', [], ['B']), St('B', ['A'], ['D']), St('C', [], ['D']), St('D', ['B', 'C'], [])), set([("A", "C")])) # no A->C ) # try all possible permutations for spanning tree optimal_cost = None for spanning_tree, removed_links in l: for s in spanning_tree: gsm.tree_stages[s.id].up_stages = {u: 1 for u in s.up} gsm.tree_stages[s.id].down_stages = {d: 1 for d in s.down} gsm._ordered_removed_links = gsm._order_removed_links( removed_links) print(gsm.tree_stages) gsm.tree_gsm = GuaranteedServiceModelTree(gsm.tree_stages, initialise_bounds=False) soln = gsm.find_optimal_solution() assert optimal_cost is None or np.allclose(soln.cost, optimal_cost) optimal_cost = soln.cost
def run_gsm(path, network, figpath, run_gsm_optimiser, plotting=True): # Load data supply_chain_filename = os.path.join(path, network) stages = read_supply_chain_from_txt(supply_chain_filename) # Run GSM gsm = GuaranteedServiceModelDAG(stages) if not run_gsm_optimiser: solution = None base_stocks = None else: solution = gsm.find_optimal_solution() base_stocks = tree_gsm.compute_base_stocks(solution.policy, stages) if plotting: plot_gsm(args.network, filename=os.path.join(figpath, network), stages=stages, base_stocks=base_stocks, solution=solution.serialize(), do_save=True) return gsm
def test_spanning_tree_skip(): stages = setup_skip_network() gsm = GuaranteedServiceModelDAG(stages) St = namedtuple('St', ['id', 'up', 'down']) l = ( (St('A', [], ['B', 'C']), St('B', ['A'], []), St('C', ['A'], [])), # no B->C (St('A', [], ['C']), St('C', ['A', 'B'], []), St('B', [], ['C'])), # no A->B (St('A', [], ['B']), St('B', ['A'], ['C']), St('C', ['B'], [])) # no A->C ) # try all possible permutations for spanning tree optimal_cost = None for spanning_tree in l: for s in spanning_tree: gsm.tree_stages[s.id].up_stages = {u: 1 for u in s.up} gsm.tree_stages[s.id].down_stages = {d: 1 for d in s.down} print(gsm.tree_stages) gsm.tree_gsm = GuaranteedServiceModelTree(gsm.tree_stages, initialise_bounds=False) soln = gsm.find_optimal_solution() assert optimal_cost is None or np.allclose(soln.cost, optimal_cost) optimal_cost = soln.cost
def test_spanning_tree_basic_diamond(): stages = setup_diamond_network() # It seems to be non-deterministic # This causes infinite loop in the recursion in ordered_stages num_random_restarts = 100 for i in range(num_random_restarts): spanning_tree_stages, _ = GuaranteedServiceModelDAG._find_spanning_tree( stages) assert ((spanning_tree_stages['C'].down_stages == { 'D': 1 } and spanning_tree_stages['B'].down_stages == {}) or (spanning_tree_stages['B'].down_stages == { 'D': 1 } and spanning_tree_stages['C'].down_stages == {})), 'B->C or C->D removed'
def test_compute_spanning_tree_cost(): stages = setup_diamond_network_extra_edge() with pytest.raises(tree_gsm.IncompatibleGraphTopology): # Should fail because diamond is not a tree GuaranteedServiceModelDAG._compute_spanning_tree_cost(stages) gsm = GuaranteedServiceModelDAG(stages) MST, _ = gsm._find_MST(stages) MST_cost = GuaranteedServiceModelDAG._compute_spanning_tree_cost(MST) assert MST_cost == calc_edge_cost(MST)
def test_skip_demand_bounds(): stages = setup_skip_network() gsm = GuaranteedServiceModelDAG(stages) for stage_id, stage in gsm.stages.items(): assert len(stage.demand_stages_phis) == 1 assert "C" in stage.demand_stages_phis if stage_id == "A": assert stage.demand_stages_phis["C"] == 2 else: assert stage.demand_stages_phis["C"] == 1 stage_tau_val_list = [("C", 5, 86.78), ("B", 11, 164.56), ("A", 7, 227.04)] for stage_id, tau, d_bound in stage_tau_val_list: np.testing.assert_approx_equal( gsm.stages[stage_id].demand_bound_func(tau), d_bound, 4)
def test_diamond_demand_bounds(): stages = setup_diamond_network() gsm = GuaranteedServiceModelDAG(stages) for stage_id, stage in gsm.stages.items(): assert len(stage.demand_stages_phis) == 1 assert "D" in stage.demand_stages_phis, stage.demand_stages_phis if stage_id == "A": assert stage.demand_stages_phis["D"] == 2 else: assert stage.demand_stages_phis["D"] == 1 stage_tau_val_list = [("D", 10, 25.20), ("C", 11, 27.45), ("B", 12, 29.69), ("A", 13, 63.86)] for stage_id, tau, d_bound in stage_tau_val_list: np.testing.assert_approx_equal( gsm.stages[stage_id].demand_bound_func(tau), d_bound, 4)
def test_dist_demand_bounds(): stages = setup_dist_network() gsm = GuaranteedServiceModelDAG(stages) for stage_id, stage in gsm.stages.items(): assert all(v == 1 for v in stage.demand_stages_phis.values()) if stage_id == "A": assert set(stage.demand_stages_phis.keys()) == set(["F", "G", "E"]) elif stage_id in ["B", "D"]: assert set(stage.demand_stages_phis.keys()) == set(["F", "G"]) elif stage_id in ["C", "E"]: assert set(stage.demand_stages_phis.keys()) == set(["E"]) else: assert len(stage.demand_stages_phis) == 1 assert stage_id in stage.demand_stages_phis stage_tau_val_list = [("B", 10, 634.68), ("C", 11, 274.55), ("A", 13, 1088.31)] for stage_id, tau, d_bound in stage_tau_val_list: np.testing.assert_approx_equal( gsm.stages[stage_id].demand_bound_func(tau), d_bound, 4)
def test_skip_handling(): stages = setup_skip_network() gsm = GuaranteedServiceModelDAG(stages)
def test_diamond_handling(): stages = setup_diamond_network() gsm = GuaranteedServiceModelDAG(stages)
def test_cyclic_handling(): stages = setup_cyclic_network() with pytest.raises(tree_gsm.IncompatibleGraphTopology): # Should fail because of cycles GuaranteedServiceModelDAG(stages)