Skip to content

Commit

Permalink
Merge pull request #49 from PatrickOHara/metricness-londonaq
Browse files Browse the repository at this point in the history
Fix metricness for londonaq by dividing costs
  • Loading branch information
PatrickOHara authored Apr 7, 2022
2 parents 5b31aa8 + ad06424 commit a571475
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 5 deletions.
2 changes: 1 addition & 1 deletion tests/test_converter/test_split_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 13 additions & 0 deletions tests/test_metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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",
[
Expand Down
7 changes: 3 additions & 4 deletions tspwplib/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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


Expand Down
47 changes: 47 additions & 0 deletions tspwplib/metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import random
import networkx as nx
import numpy as np
from .exception import NoTreesException, NotConnectedException
from .types import SimpleEdgeList, SimpleEdgeFunction

Expand Down Expand Up @@ -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

0 comments on commit a571475

Please sign in to comment.