diff --git a/tests/test_converter/test_split_converter.py b/tests/test_converter/test_split_converter.py index 2716864..303e002 100644 --- a/tests/test_converter/test_split_converter.py +++ b/tests/test_converter/test_split_converter.py @@ -37,4 +37,4 @@ def test_split_graph_from_properties(): G = split_graph_from_properties(properties) for _, _, data in G.edges(data=True): old_edge = data["old_edge"] - assert data["cost"] == float(properties[old_edge]["cost"]) / 2.0 + assert data["cost"] == float(properties[old_edge]["cost"]) or data["cost"] == 0 diff --git a/tests/test_metric.py b/tests/test_metric.py index 1d6ce7a..41e9133 100644 --- a/tests/test_metric.py +++ b/tests/test_metric.py @@ -3,6 +3,7 @@ import networkx as nx import pytest from tspwplib import build_path_to_oplib_instance, metricness, mst_cost, ProfitsProblem +from tspwplib.metric import semi_mst_cost def test_mst_cost(oplib_root, generation, graph_name): @@ -24,6 +25,18 @@ def test_mst_cost(oplib_root, generation, graph_name): ) +def test_semi_mst_cost(oplib_root, generation, graph_name): + """Test Semi MST cost""" + filepath = build_path_to_oplib_instance(oplib_root, generation, graph_name) + problem = ProfitsProblem.load(filepath) + G = problem.get_graph() + new_cost = semi_mst_cost(G, cost_attr="cost") + T = nx.minimum_spanning_tree(G, weight="cost") + tree_cost = sum(nx.get_edge_attributes(T, "cost").values()) + upper_bound_cost = sum(mst_cost(G).values()) + assert tree_cost < sum(new_cost.values()) < upper_bound_cost + + @pytest.mark.parametrize( "edges,expected_metricness", [ diff --git a/tspwplib/converter.py b/tspwplib/converter.py index 4bd94a5..6af5495 100644 --- a/tspwplib/converter.py +++ b/tspwplib/converter.py @@ -419,7 +419,7 @@ def prize_from_weighted_edges( def split_edge_cost( edge_cost: EdgeFunction, to_split: LookupToSplit -) -> Dict[Edge, float]: +) -> Dict[Edge, int]: """Assign half the cost of the original edge to each of the split edges. Args: @@ -436,9 +436,8 @@ def split_edge_cost( split_cost = {} for edge, cost in edge_cost.items(): first_split, second_split = to_split[edge] - half_cost = float(cost) / 2.0 - split_cost[first_split] = half_cost - split_cost[second_split] = half_cost + split_cost[first_split] = cost + split_cost[second_split] = 0 return split_cost diff --git a/tspwplib/metric.py b/tspwplib/metric.py index b950892..988bfc9 100644 --- a/tspwplib/metric.py +++ b/tspwplib/metric.py @@ -2,6 +2,7 @@ import random import networkx as nx +import numpy as np from .exception import NoTreesException, NotConnectedException from .types import SimpleEdgeList, SimpleEdgeFunction @@ -93,3 +94,49 @@ def mst_cost(G: nx.Graph, cost_attr: str = "cost") -> SimpleEdgeFunction: else: new_cost[(u, v)] = cost + tree_cost[u][v] return new_cost + + +def semi_mst_cost( + G: nx.Graph, cost_attr: str = "cost", seed: int = 0 +) -> SimpleEdgeFunction: + """Half of the non-MST-tree edges are left unchanged. + The other half are assigned cost as described in mst_cost. + + The half of edges are chosen with uniform and independent probability. + + Args: + G: Undirected, simple graph + cost_attr: Name of the cost attribute of edges + seed: Set the seed of the random number generator + + Returns + A new cost function + """ + # find the cost of the minimum spanning tree in G + T = nx.minimum_spanning_tree(G, weight=cost_attr) + tree_cost = dict(nx.all_pairs_bellman_ford_path_length(T, weight=cost_attr)) + + # get edges not in the tree + non_tree_edges = [(u, v) for u, v in G.edges() if not T.has_edge(u, v)] + num_non_tree_edges = len(non_tree_edges) + + gen = np.random.default_rng(seed=seed) + donot_change_edge_cost = set() + while len(non_tree_edges) >= float(num_non_tree_edges) * 0.5: + index = gen.integers(0, len(non_tree_edges) - 1) # get random index + donot_change_edge_cost.add( + non_tree_edges.pop(index) + ) # remove item at random index, add to set + + # set the cost of the new edges + new_cost: SimpleEdgeFunction = {} + for (u, v), cost in nx.get_edge_attributes(G, cost_attr).items(): + if ( + T.has_edge(u, v) + or (u, v) in donot_change_edge_cost + or (v, u) in donot_change_edge_cost + ): + new_cost[(u, v)] = cost + else: + new_cost[(u, v)] = cost + tree_cost[u][v] + return new_cost