Exemple #1
0
def test_label_fst():
    '''Label small FSTs and check results'''
    O = (0, 0)  # dummy location

    Y = SteinerTree('abcs', ['as', 'bs', 'cs'], {'a': O, 'b': O, 'c': O})
    assert label_fst(Y) == ('b', 'c')

    H = SteinerTree('abcdst', ['as', 'bs', 'st', 'tc', 'td'], {
        'a': O,
        'b': O,
        'c': O,
        'd': O
    })
    assert label_fst(H) == ('b', ('c', 'd'))

    # from Fampa, figure 4
    t8 = SteinerTree('123456789abcdefg', [
        '1a', 'ab', 'ac', 'bd', 'b2', 'ce', 'cf', 'd3', 'd4', 'eg', 'e5', 'f6',
        'f7', 'g8', 'g9'
    ], {
        '1': O,
        '2': O,
        '3': O,
        '4': O,
        '5': O,
        '6': O,
        '7': O,
        '8': O,
        '9': O
    })
    assert label_fst(t8) == ((('2', ('3', '4')), (('5', ('8', '9')), ('6',
                                                                      '7'))))
Exemple #2
0
def make_forward_flow(tree, flow):
    '''Create tree with arcs oriented for positive, forward flow.

    args:
    - tree: a SteinerTree
    - flow: a dict from arc to (signed) flow value

    returns:
    - a new SteinerTree
    - a new flow dict with only positive flow values
    '''
    new_nodes = tree.get_nodes()
    new_arcs = []
    new_pos = tree.get_terminal_positions()
    new_flow = {}

    for u, v in tree.get_arcs():
        if flow[u, v] >= 0.0:
            new_arcs.append((u, v))
            new_flow[u, v] = flow[u, v]
        else:
            new_arcs.append((v, u))
            new_flow[v, u] = -1.0 * flow[u, v]

    new_tree = SteinerTree(new_nodes, new_arcs, new_pos)
    return new_tree, new_flow
Exemple #3
0
def test_solve():
    '''Solve model and check some solution properties'''
    tree = SteinerTree('abcs', ['as', 'bs', 'cs'], {
        'a': (0, 0),
        'b': (10, 0),
        'c': (0, 10)
    })
    demand = {'a': -30, 'b': 20, 'c': 10}
    diams = [0.4, 0.6, 0.8, 1., 1.2]
    costs = [680., 910., 1200., 1550., 1960.]
    pres = [40, 80]
    C = 1.0
    steiner_pos = {'s': (5, 5)}

    flow = find_arc_flow(tree, demand)

    ds, ps, obj = solve(tree, flow, steiner_pos, diams, costs, pres, C)
    assert obj is not None

    for n, p in ps.items():
        assert p >= min(pres) - TOL
        assert p <= max(pres) + TOL

    for a, d in ds.items():
        assert d >= min(diams) - TOL
        assert d <= max(diams) + TOL
Exemple #4
0
def test_no_steiner(term_pos):
    '''nothing to do for tree with no steiner nodes'''
    tree = SteinerTree('abc', ['ab', 'bc'], term_pos)
    pos = {}
    assert not is_degenerate(tree, pos)
    assert degenerate_edges(tree, pos) == []

    mtree, mpos = merged(tree, pos)
    assert mtree == tree
    assert mpos == pos
Exemple #5
0
def test_no_crossings():
    no_edge = SteinerTree('a', [], {'a': (0, 0)})
    assert crossing_edges(no_edge, {}) == []

    edge = SteinerTree('ab', ['ab'], {'a': (0, 0), 'b': (0, 1)})
    assert crossing_edges(edge, {}) == []

    path = SteinerTree('abc', ['ab', 'bc'], {
        'a': (0, 0),
        'b': (0, 1),
        'c': (0, 2)
    })
    assert crossing_edges(edge, {}) == []

    star = SteinerTree('abcs', ['as', 'bs', 'cs'], {
        'a': (0, 0),
        'b': (2, 0),
        'c': (0, 2)
    })
    assert crossing_edges(star, {'s': (1, 1)}) == []
Exemple #6
0
def test_merge_pos():
    term_pos = {'a': 1, 'b': 2, 'c': 3}
    star = SteinerTree('abcs', ['as', 'bs', 'cs'], term_pos)

    steiner_pos = {'s': 4}

    pos = merge_pos(star, steiner_pos)

    assert len(pos) == 4
    assert pos['a'] == 1
    assert pos['b'] == 2
    assert pos['c'] == 3
    assert pos['s'] == 4
Exemple #7
0
def test_positions():
    pos = {'a': 1, 'b': 2, 'c': 3}
    star = SteinerTree('abcs', ['as', 'bs', 'cs'], pos)

    assert star.get_position('a') == 1
    assert star.get_position('b') == 2
    assert star.get_position('c') == 3
    assert star.get_terminal_positions() == pos

    with pytest.raises(KeyError) as e:
        star.get_position('s')
    assert 'Not a terminal' in e.value.message
Exemple #8
0
def test_crossings():
    pair = SteinerTree('abcd', ['ab', 'cd'], {
        'a': (0, 1),
        'b': (2, 1),
        'c': (1, 0),
        'd': (1, 2)
    })
    edges = crossing_edges(pair, {})
    assert len(edges) == 1
    assert set(edges[0]) == set([('a', 'b'), ('c', 'd')])

    path = SteinerTree('abcde', ['ab', 'bc', 'cd', 'de'], {
        'a': (1, 0),
        'b': (1, 1),
        'c': (2, 0),
        'e': (0, 0)
    })
    edges = crossing_edges(path, {'d': (2, 1)})
    assert len(edges) == 2
    for p in edges:
        assert ('d', 'e') in p
        assert ('a', 'b') in p or ('b', 'c') in p
    assert edges[0] != edges[1]
Exemple #9
0
def test_SteinerTree():
    nodes = ['a', 'b', 'c', 's']
    arcs = [('a', 's'), ('b', 's'), ('c', 's')]
    pos = {'a': (0, 0), 'b': (1, 0), 'c': (0, 1)}

    tree = SteinerTree(nodes, arcs, pos)

    for n in 'abc':
        assert tree.is_terminal(n)
    assert tree.is_steiner('s')

    assert set(tree.get_terminal_nodes()) == set('abc')
    assert set(tree.get_steiner_nodes()) == set(['s'])

    assert tree.is_full_steiner_topology()
Exemple #10
0
def merged(tree, steiner_pos, abstol=abstol):
    '''build new tree that merges all degenerate edges.

    when merging an edge, the lexicographically smaller node will
    survive. returns a tree and a matching dict of steiner node
    positions.
    '''
    degedges = degenerate_edges(tree, steiner_pos, abstol)

    # key: removed node, value: remaining node (taking over)
    turn_into = {}
    for u, v in degedges:
        if tree.is_terminal(u) and tree.is_terminal(v):
            # don't merge terminals
            continue
        elif tree.is_terminal(u):
            # keep terminals
            pass
        elif tree.is_terminal(v):
            # keep terminals
            u, v = v, u
        elif v < u:
            # keep lexicographically smaller node
            u, v = v, u

        turn_into[v] = u

    # merge nodes into transitive end-point
    for v, u in turn_into.iteritems():
        while u in turn_into:
            u = turn_into[u]
        turn_into[v] = u

    # build new tree data
    new_nodes = [u for u in tree.get_nodes() if u not in turn_into]
    new_edges = []
    for u, v in tree.get_arcs():
        uu, vv = turn_into.get(u, u), turn_into.get(v, v)
        if uu != vv:  # remove self-loops
            new_edges.append((uu, vv))
    new_tree = SteinerTree(new_nodes, new_edges, tree.get_terminal_positions())
    new_pos = {s: steiner_pos[s] for s in steiner_pos if s in new_nodes}

    return new_tree, new_pos
Exemple #11
0
def test_merged_multi():
    tree = SteinerTree('abcdst', ['as', 'bs', 'st', 'tc', 'td'], {
        'a': (0, 0),
        'b': (0, 2),
        'c': (2, 2),
        'd': (2, 0)
    })
    pos = {'s': (0, 0), 't': (0, 0)}

    assert is_degenerate(tree, pos)
    assert set(degenerate_edges(tree, pos)) == set([('a', 's'), ('s', 't')])

    mtree, mpos = merged(tree, pos)
    assert mtree != tree
    assert mtree.get_steiner_nodes() == []
    assert set(mtree.get_arcs()) == \
        set([('b', 'a'), ('a', 'c'), ('a', 'd')])
    assert mpos != pos
    assert mpos == {}
Exemple #12
0
def test_one_proper_steiner(spos, deg, edg):
    tree = SteinerTree('abcs', ['as', 'bs', 'cs'], {
        'a': (0, 0),
        'b': (2, 0),
        'c': (0, 2)
    })
    pos = {'s': spos}

    assert is_degenerate(tree, pos) == deg
    assert degenerate_edges(tree, pos) == edg

    mtree, mpos = merged(tree, pos)
    if deg:
        assert mtree != tree
        assert len(mtree.get_nodes()) == 3
        assert len(mtree.get_arcs()) == 2
        assert mpos == {}
    else:
        assert mtree == tree
        assert mpos == pos
Exemple #13
0
    def build_tree(nodes, raw_arcs, pos):
        _nodes = list(nodes)
        _arcs = []
        _steiner_pos = {}

        num = 0
        for ra in raw_arcs:
            if ra[1] == 'T':
                tail = nodes[int(ra[0])]
            else:  # must be Steiner node
                coords = '_'.join(ra[0:2])
                if coords in _steiner_pos:
                    tail = _steiner_pos[coords]
                else:
                    node = '_%d' % num
                    _nodes.append(node)
                    tail = _steiner_pos.setdefault(coords, node)
                    num += 1
            if ra[3] == 'T':
                head = nodes[int(ra[2])]
            else:  # must be Steiner node
                coords = '_'.join(ra[2:4])
                if coords in _steiner_pos:
                    head = _steiner_pos[coords]
                else:
                    node = '_%d' % num
                    _nodes.append(node)
                    head = _steiner_pos.setdefault(coords, node)
                    num += 1

            _arcs.append((tail, head))

        tree = SteinerTree(_nodes, _arcs, pos)

        steiner_pos = {}
        for k, v in _steiner_pos.items():
            node = v
            coords = k.split('_')
            steiner_pos[node] = float(coords[0]), float(coords[1])

        return tree, steiner_pos
Exemple #14
0
def test_length():
    '''check Steiner tree geometric property for minimized length'''
    nodes = 'abcs'
    arcs = ['as', 'bs', 'cs']
    terminals = {'a': (0, 0), 'b': (1, 0), 'c': (0, 1)}
    tree = SteinerTree(nodes, arcs, terminals)

    flow = find_arc_flow(tree, {'a': -4, 'b': 2, 'c': 2})

    pos = steiner_pos(tree, flow, flow_exp=0.0)

    # check that fixed positions are kept
    for k, v in terminals.items():
        assert_allclose(pos[k], v, atol=1e-7)

    # check (known) optimal position
    assert_allclose(pos['s'], (0.211, 0.211), atol=1e-3)

    # check angle property of Steiner node position
    # all angles are equal (to 120 deg = 2/3 pi)
    angles = star_angles('s', 'abc', pos)
    assert_allclose(angles, [2.0 / 3.0 * np.pi] * 3, atol=1e-4)
Exemple #15
0
def test_make_forward_flow():
    O = (0, 0)
    tree = SteinerTree('abcs', ['as', 'bs', 'cs'], {'a':O, 'b':O, 'c':O})
    flow = {('a', 's'):20, ('b','s'):-5, ('c','s'):-15}

    new_tree, new_flow = make_forward_flow(tree, flow)
    assert set(tree.get_nodes()) == set(new_tree.get_nodes())
    assert set(tree.get_terminal_nodes()) == set(new_tree.get_terminal_nodes())
    assert len(tree.get_arcs()) == len(new_tree.get_arcs())
    assert tree.get_terminal_positions() == new_tree.get_terminal_positions()

    assert ('a', 's') in new_tree.get_arcs()
    assert ('s', 'a') not in new_tree.get_arcs()

    assert ('s', 'b') in new_tree.get_arcs()
    assert ('b', 's') not in new_tree.get_arcs()

    assert ('s', 'c') in new_tree.get_arcs()
    assert ('c', 's') not in new_tree.get_arcs()

    assert all(new_flow[a] > 0.0 for a in new_tree.get_arcs())
Exemple #16
0
def enum(term_pos, steiner_ids=None):
    '''Enumerate all representative full steiner trees.

    - term_pos: a dict mapping terminal node IDs to their (fixed)
    positions.
    - steiner_ids: optional list of Steiner node IDs. Must be of correct
    length.

    Following "Fampa et al.: A specialized branch-and-bound algorithm
    for the Euclidean Steiner tree problem in n-space", algorithm 3.
    '''
    terms = sorted(term_pos.keys())
    nt = len(terms)
    assert nt >= 3
    if steiner_ids is None:
        steiner_ids = default_steiner_ids(nt)
    ns = nt - 2
    assert len(steiner_ids) == ns

    # compute all representative trees connecting the Steiner nodes
    nclasses, reprtree = enum_Steiner_only(len(terms), steiner_ids)
    nc = nclasses[ns]

    # resulting data structure of representatives, indexed by label
    trees = {}

    # special case: 3 terminals, 1 Steiner node
    if nt == 3:
        s = steiner_ids[0]
        edges = [(t, s) for t in terms]
        tree = SteinerTree(terms + steiner_ids, edges, term_pos)
        trees[label_fst(tree)] = tree
        return trees

    # for each class of 'inner tree'
    for c in range(nc):
        tree = reprtree[ns, c]
        deg1 = [s for s in steiner_ids if tree.get_degree(s) == 1]
        deg2 = [s for s in steiner_ids if tree.get_degree(s) == 2]
        deg3 = [s for s in steiner_ids if tree.get_degree(s) == 3]
        assert 2 * len(deg1) + len(deg2) == nt

        cur_nodes = terms + steiner_ids
        cur_edges = tree.get_arcs()

        # (naive) enumeration of all permutations of terminals
        for perm in permutations(terms):
            # TODO: skip those permutations that yield the same tree,
            # i.e. where two terminals are swapped that are connected to
            # the same degree-1 Steiner node

            # connect all terminals to Steiner nodes in permutation order
            new_edges = []
            K = len(deg1)
            for k in range(K):
                new_edges.append((perm[2 * k], deg1[k]))
                new_edges.append((perm[2 * k + 1], deg1[k]))
            for l in range(len(deg2)):
                new_edges.append((perm[2 * K + l], deg2[l]))
            new_tree = SteinerTree(cur_nodes, cur_edges + new_edges, term_pos)

            # check if isomorphic to saved tree
            label = label_fst(new_tree)
            if label in trees:
                # TODO: select lexicographically smaller tree
                continue

            # save tree
            trees[label] = new_tree

    return trees
Exemple #17
0
def enum(term_pos, steiner_ids=None):
    '''Enumerate all representative full steiner trees.

    - term_pos: a dict mapping terminal node IDs to their (fixed)
    positions.
    - steiner_ids: optional list of Steiner node IDs. Must be of correct
    length.

    Following "Fampa et al.: A specialized branch-and-bound algorithm
    for the Euclidean Steiner tree problem in n-space", algorithm 3.
    '''
    terms = sorted(term_pos.keys())
    nt = len(terms)
    assert nt >= 3
    if steiner_ids is None:
        steiner_ids = default_steiner_ids(nt)
    ns = nt - 2
    assert len(steiner_ids) == ns

    # compute all representative trees connecting the Steiner nodes
    nclasses, reprtree = enum_Steiner_only(len(terms), steiner_ids)
    nc = nclasses[ns]

    # resulting data structure of representatives, indexed by label
    trees = {}

    # special case: 3 terminals, 1 Steiner node
    if nt == 3:
        s = steiner_ids[0]
        edges = [(t, s) for t in terms]
        tree = SteinerTree(terms + steiner_ids, edges, term_pos)
        trees[label_fst(tree)] = tree
        return trees

    # for each class of 'inner tree'
    for c in range(nc):
        tree = reprtree[ns, c]
        deg1 = [s for s in steiner_ids if tree.get_degree(s) == 1]
        deg2 = [s for s in steiner_ids if tree.get_degree(s) == 2]
        deg3 = [s for s in steiner_ids if tree.get_degree(s) == 3]
        assert 2*len(deg1) + len(deg2) == nt

        cur_nodes = terms + steiner_ids
        cur_edges = tree.get_arcs()

        # (naive) enumeration of all permutations of terminals
        for perm in permutations(terms):
            # TODO: skip those permutations that yield the same tree,
            # i.e. where two terminals are swapped that are connected to
            # the same degree-1 Steiner node

            # connect all terminals to Steiner nodes in permutation order
            new_edges = []
            K = len(deg1)
            for k in range(K):
                new_edges.append((perm[2*k], deg1[k]))
                new_edges.append((perm[2*k + 1], deg1[k]))
            for l in range(len(deg2)):
                new_edges.append((perm[2*K + l], deg2[l]))
            new_tree = SteinerTree(cur_nodes, cur_edges + new_edges, term_pos)

            # check if isomorphic to saved tree
            label = label_fst(new_tree)
            if label in trees:
                # TODO: select lexicographically smaller tree
                continue

            # save tree
            trees[label] = new_tree

    return trees
Exemple #18
0
def test_equality():
    # testing just Net
    assert Net('', []) == Net('', [])
    assert not Net('', []) != Net('', [])

    assert Net('a', []) == Net('a', [])
    assert Net('a', []) != Net('', [])
    assert Net('a', []) != Net('A', [])

    assert Net('abc', ['ab']) == Net('abc', ['ab'])
    assert Net('abc', ['ab']) == Net('cba', ['ab'])
    assert Net('abc', ['ab']) != Net('abc', ['ba'])
    assert Net('abc', ['ab']) != Net('abc', ['bc'])
    assert Net('abc', ['ab', 'bc']) == Net('abc', ['bc', 'ab'])

    # testing SteinerTree with no terminals
    assert SteinerTree('', [], {}) == SteinerTree('', [], {})
    assert not SteinerTree('', [], {}) != SteinerTree('', [], {})

    assert SteinerTree('a', [], {}) == SteinerTree('a', [], {})
    assert SteinerTree('a', [], {}) != SteinerTree('', [], {})
    assert SteinerTree('a', [], {}) != SteinerTree('A', [], {})

    assert SteinerTree('abc', ['ab'], {}) == SteinerTree('abc', ['ab'], {})
    assert SteinerTree('abc', ['ab'], {}) == SteinerTree('cba', ['ab'], {})
    assert SteinerTree('abc', ['ab'], {}) != SteinerTree('abc', ['ba'], {})
    assert SteinerTree('abc', ['ab'], {}) != SteinerTree('abc', ['bc'], {})
    assert SteinerTree('abc', ['ab', 'bc'],
                       {}) == SteinerTree('abc', ['bc', 'ab'], {})

    # testing SteinerTree with terminals
    assert SteinerTree('abc', ['ab'],
                       {'a': 0}) == SteinerTree('abc', ['ab'], {'a': 0})
    assert not SteinerTree('abc', ['ab'], {'a': 0}) != SteinerTree(
        'abc', ['ab'], {'a': 0})
    assert SteinerTree('abc', ['ab'], {'a': 0}) != SteinerTree(
        'abc', ['ab'], {})
    assert SteinerTree('abc', ['ab'], {'a': 0}) != SteinerTree(
        'abc', ['ab'], {'a': 1})
Exemple #19
0
def test_full_steiner_tree():
    O = (0, 0)

    point = SteinerTree(['a'], [], {'a': O})
    assert point.is_full_steiner_topology()

    line = SteinerTree('ab', ['ab'], {'a': O, 'b': O})
    assert line.is_full_steiner_topology()

    path = SteinerTree('abs', ['as', 'sb'], {'a': O, 'b': O})
    assert not path.is_full_steiner_topology()

    star = SteinerTree('abcs', ['as', 'bs', 'cs'], {'a': 0, 'b': O, 'c': O})
    assert star.is_full_steiner_topology()

    V = SteinerTree('abc', ['ab', 'bc'], {'a': 0, 'b': O, 'c': O})
    assert not V.is_full_steiner_topology()

    H = SteinerTree('abcdst', ['as', 'bs', 'st', 'ct', 'dt'], {
        'a': O,
        'b': O,
        'c': O,
        'd': O
    })
    assert H.is_full_steiner_topology()

    X = SteinerTree('abcds', ['as', 'bs', 'cs', 'ds'], {
        'a': O,
        'b': O,
        'c': O,
        'd': O
    })

    assert not X.is_full_steiner_topology()