Skip to content

Commit e48d747

Browse files
Reducing dependency on our own graph.Graph class in favor of networks
1. links.simplify does not use it anymore, except to provide a user-facing method of Link. 2. Deleted the graph.Graph.one_min_cut method which has at a bug.
1 parent 98f07fe commit e48d747

File tree

10 files changed

+100
-195
lines changed

10 files changed

+100
-195
lines changed

spherogram_src/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
from .graphs import *
21
from .presentations import *
32
from .links import *
43
from .codecs import *
5-
# try:
6-
# import snappy
7-
# except ImportError:
8-
# pass
94

105
# Make the module version number easily accessible.
116
from . import version as _version

spherogram_src/codecs/DT.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from .. import FatGraph, FatEdge, Link, Crossing
1+
from ..graphs import FatGraph, FatEdge
2+
from .. import Link, Crossing
23
from ..links.links import CrossingEntryPoint
34
from ..links.ordered_set import OrderedSet
45
from .Base64LikeDT import (decode_base64_like_DT_code, encode_base64_like_DT_code)

spherogram_src/graphs.py

Lines changed: 8 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@
3434
3535
where x and y will be the two endpoints of the edge (ordered as
3636
tail, head in the case of directed edges).
37+
38+
IMPORTANT NOTE: New code should use the standard nextworkx module
39+
rather that the classes here.
40+
3741
"""
3842
from collections import deque
43+
from .presentations import CyclicList
3944

4045
try:
4146
import sage.all
@@ -52,20 +57,6 @@
5257
pass
5358

5459

55-
class CyclicList(list):
56-
def __getitem__(self, n):
57-
if isinstance(n, int):
58-
return list.__getitem__(self, n % len(self))
59-
elif isinstance(n, slice):
60-
# Python3 only: in python2, __getslice__ gets called instead.
61-
return list.__getitem__(self, n)
62-
63-
def succ(self, x):
64-
return self[(self.index(x) + 1) % len(self)]
65-
66-
def pred(self, x):
67-
return self[(self.index(x) - 1) % len(self)]
68-
6960

7061
class BaseEdge(tuple):
7162
"""
@@ -394,112 +385,6 @@ def is_connected(self, deleted_vertices=[]):
394385
"""
395386
return len(self.components(deleted_vertices)) <= 1
396387

397-
def one_min_cut(self, source, sink, capacity=None):
398-
"""
399-
Find one minimal cut which separates source from sink, using
400-
the classical Ford-Fulkerson algorithm.
401-
402-
Returns a dict containing the set of vertices on the source
403-
side of the cut, the set of edges that cross the cut, a
404-
maximal family of weighted edge-disjoint paths from source to
405-
sink, the set of edges with non-zero residual, and the
406-
associated maximum flow.
407-
408-
The edge capacities are supplied as a dictionary, with
409-
edges as keys and the capacity of the edge as value. If
410-
no capacity dict is supplied, every edge is given capacity
411-
1. Edges omitted from the capacity dict have infinite
412-
capacity.
413-
414-
When called as a Graph method, the flow is relative to the
415-
implicit orientation of an undirected edge e from e[0] to
416-
e[1]. (This is determined when the edge is first constructed
417-
from a pair of vertices.) A negative flow value means the flow
418-
direction is from e[1] to e[0]. When called as a DiGraph
419-
method, paths are directed and flows go in the direction of
420-
the directed edge.
421-
422-
>>> caps = {('s',0):3,('s',1):2,(0,1):2,(0,'t'):4,(1,'t'):1}
423-
>>> G = Graph(caps.keys())
424-
>>> cap_dict = dict((e, caps[tuple(e)]) for e in G.edges)
425-
>>> flow = G.one_min_cut('s', 't', cap_dict)['flow']
426-
>>> [flow[e] for e in sorted(G.edges, key=str)]
427-
[-1, 4, 1, 3, 2]
428-
>>> G = Digraph(caps.keys())
429-
>>> cap_dict = dict((e, caps[tuple(e)]) for e in G.edges)
430-
>>> flow = G.one_min_cut('s', 't', cap_dict)['flow']
431-
>>> [flow[e] for e in sorted(G.edges, key=str)]
432-
[0, 3, 1, 3, 1]
433-
"""
434-
if sink == source:
435-
return None
436-
if capacity is None:
437-
residual = dict.fromkeys(self.edges, 1)
438-
else:
439-
residual = dict.copy(capacity)
440-
for e in self.edges:
441-
if e not in residual:
442-
residual[e] = float('inf')
443-
full_edges = {e for e in self.edges if residual[e] == 0}
444-
children = {}
445-
for vertex in self.vertices:
446-
children[vertex] = set()
447-
cut_set = set()
448-
path_list = []
449-
while True:
450-
# Try to find a new path from source to sink
451-
parents, cut_set, reached_sink = {}, {source}, False
452-
generator = self.breadth_first_edges(
453-
source=source,
454-
forbidden=full_edges,
455-
for_flow=True)
456-
for parent, vertex, child in generator:
457-
parents[child] = (parent, vertex)
458-
cut_set.add(child(vertex))
459-
if child(vertex) == sink:
460-
reached_sink = True
461-
break
462-
# If we did not get to the sink, we visited every vertex
463-
# reachable from the source, thereby providing the cut
464-
# set.
465-
if not reached_sink:
466-
break
467-
# If we got to the sink, do the bookkeeping and continue.
468-
path = deque()
469-
flow = residual[child]
470-
while True:
471-
path.appendleft((vertex, child))
472-
children[vertex].add(child)
473-
if vertex == source:
474-
break
475-
child = parent
476-
parent, vertex = parents[child]
477-
flow = min(flow, residual[child])
478-
for vertex, edge in path:
479-
residual[edge] -= flow
480-
if residual[edge] == 0:
481-
full_edges.add(edge)
482-
path_list.append((flow, path))
483-
# Find the cut edges.
484-
cut_edges = set()
485-
for vertex in cut_set:
486-
cut_edges |= {edge for edge in self.edges
487-
if vertex in edge
488-
and edge(vertex) not in cut_set}
489-
# Find the unsaturated edges.
490-
unsaturated = [e for e in self.edges if residual[e] > 0]
491-
flow_dict = dict.fromkeys(self.edges, 0)
492-
# Compute the flow.
493-
for flow, edges in path_list:
494-
for vertex, edge in edges:
495-
if vertex == edge[0]:
496-
flow_dict[edge] += flow
497-
else:
498-
flow_dict[edge] -= flow
499-
return {'set': cut_set, 'edges': cut_edges, 'paths': path_list,
500-
'residuals': residual, 'unsaturated': unsaturated,
501-
'flow': flow_dict}
502-
503388
def reduced(self):
504389
R = ReducedGraph()
505390
for e in self.edges:
@@ -551,9 +436,9 @@ def mergeable(self):
551436

552437
def to_networkx(self):
553438
"""
554-
Return a copy of the graph in the networkx format
439+
Return a copy of the graph in the networkx format.
555440
"""
556-
G = nx.Graph()
441+
G = nx.MultiGraph()
557442
G.add_nodes_from(self.vertices)
558443
G.add_edges_from(self.edges)
559444
return G
@@ -625,12 +510,6 @@ def embedding(self):
625510
if self.is_planar():
626511
return self._embedding
627512

628-
def one_min_cut(self, source, sink):
629-
capacity = {e: e.multiplicity for e in self.edges}
630-
cut = Graph.one_min_cut(self, source, sink, capacity)
631-
cut['size'] = sum(e.multiplicity for e in cut['edges'])
632-
return cut
633-
634513
def cut_pairs(self):
635514
"""
636515
Return a list of cut_pairs. The graph is assumed to be
@@ -940,7 +819,7 @@ def DAG(self):
940819
edges.add((dag_tail, dag_head))
941820
return Digraph(edges, self.components)
942821

943-
822+
944823
if _within_sage:
945824
def _to_sage(self, loops=True, multiedges=True):
946825
S = sage.graphs.graph.Graph(loops=loops, multiedges=multiedges)

spherogram_src/links/links_base.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -985,11 +985,11 @@ def is_alternating(self):
985985

986986
def faces(self):
987987
"""
988-
The faces are the complementary regions of the link diagram. Each face
989-
is given as a list of corners of crossings as one goes around
990-
*clockwise*. These corners are recorded as CrossingStrands,
991-
where CrossingStrand(c, j) denotes the corner of the face
992-
abutting crossing c between strand j and j + 1.
988+
The faces are the complementary regions of the link diagram.
989+
Each face is given as a list of corners of crossings as one
990+
goes around *clockwise*. These corners are recorded as
991+
CrossingStrands, where CrossingStrand(c, j) denotes the corner
992+
of the face abutting crossing c between strand j and j + 1.
993993
994994
Alternatively, the sequence of CrossingStrands can be regarded
995995
as the *heads* of the oriented edges of the face.

spherogram_src/links/morse.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
reduced to using a generic ILP solver.
3535
"""
3636
from ..sage_helper import _within_sage
37-
from ..graphs import CyclicList, Digraph
37+
from ..graphs import Digraph
38+
from ..presentations import CyclicList
3839
from .links import CrossingStrand, Crossing, Strand, Link
3940
from .orthogonal import basic_topological_numbering
4041
from .tangles import join_strands, RationalTangle

spherogram_src/links/orthogonal.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
import networkx
2424
import random
2525
from .links import Strand
26-
from ..graphs import CyclicList, Digraph
26+
from ..graphs import Digraph
27+
from ..presentations import CyclicList
2728
from collections import namedtuple, Counter
2829

2930
try:

spherogram_src/links/random_links.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
your breath, though.
1010
"""
1111
import random
12-
from .. import graphs
1312
from . import links, twist
1413
from spherogram.planarmap import random_map as raw_random_map
1514

spherogram_src/links/simplify.py

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,52 @@
1717
import collections
1818

1919

20+
class DualGraphOfFaces(graphs.Graph):
21+
"""
22+
The dual graph to a link diagram D, whose vertices correspond to
23+
complementary regions (faces) of D and whose edges are dual to the
24+
edges of D.
25+
26+
This is class is no longer used in the simplfy code, but is called
27+
by Link.dual_graph. It is the only place in this file that
28+
graph.Graph is used.
29+
"""
30+
def __init__(self, link):
31+
graphs.Graph.__init__(self)
32+
faces = [Face(face, i) for i, face in enumerate(link.faces())]
33+
self.edge_to_face = to_face = {}
34+
for face in faces:
35+
for edge in face:
36+
to_face[edge] = face
37+
38+
for edge, face in to_face.items():
39+
neighbor = to_face[edge.opposite()]
40+
if face.label < neighbor.label:
41+
dual_edge = self.add_edge(face, neighbor)
42+
dual_edge.interface = (edge, edge.opposite())
43+
dual_edge.label = len(self.edges) - 1
44+
45+
# assert self.is_planar()
46+
47+
def two_cycles(self):
48+
"""
49+
Find all two cycles and yield them as a pair of CrossingStrands which
50+
are dual to the edges in the cycle.
51+
52+
The crossing strands are
53+
oriented consistently with respect to one of the faces which a
54+
vertex for the cycle.
55+
"""
56+
for face0 in self.vertices:
57+
for dual_edge0 in self.incident(face0):
58+
face1 = dual_edge0(face0)
59+
if face0.label < face1.label:
60+
for dual_edge1 in self.incident(face1):
61+
if dual_edge0.label < dual_edge1.label and dual_edge1(face1) == face0:
62+
yield (common_element(face0, dual_edge0.interface),
63+
common_element(face0, dual_edge1.interface))
64+
65+
2066
def remove_crossings(link, eliminate):
2167
"""
2268
Deletes the given crossings. Assumes that they have already been
@@ -219,49 +265,7 @@ def __repr__(self):
219265
return "<F%d>" % self.label
220266

221267

222-
class DualGraphOfFaces(graphs.Graph):
223-
"""
224-
The dual graph to a link diagram D, whose vertices correspond to
225-
complementary regions (faces) of D and whose edges are dual to the
226-
edges of D.
227-
"""
228-
def __init__(self, link):
229-
graphs.Graph.__init__(self)
230-
faces = [Face(face, i) for i, face in enumerate(link.faces())]
231-
self.edge_to_face = to_face = {}
232-
for face in faces:
233-
for edge in face:
234-
to_face[edge] = face
235-
236-
for edge, face in to_face.items():
237-
neighbor = to_face[edge.opposite()]
238-
if face.label < neighbor.label:
239-
dual_edge = self.add_edge(face, neighbor)
240-
dual_edge.interface = (edge, edge.opposite())
241-
dual_edge.label = len(self.edges) - 1
242-
243-
# assert self.is_planar()
244-
245-
def two_cycles(self):
246-
"""
247-
Find all two cycles and yield them as a pair of CrossingStrands which
248-
are dual to the edges in the cycle.
249-
250-
The crossing strands are
251-
oriented consistently with respect to one of the faces which a
252-
vertex for the cycle.
253-
"""
254-
for face0 in self.vertices:
255-
for dual_edge0 in self.incident(face0):
256-
face1 = dual_edge0(face0)
257-
if face0.label < face1.label:
258-
for dual_edge1 in self.incident(face1):
259-
if dual_edge0.label < dual_edge1.label and dual_edge1(face1) == face0:
260-
yield (common_element(face0, dual_edge0.interface),
261-
common_element(face0, dual_edge1.interface))
262-
263-
264-
def dual_graph_as_nx(link):
268+
def dual_graph_as_nx(link, graph_class=nx.Graph):
265269
corners = OrderedSet([CrossingStrand(c, i)
266270
for c in link.crossings for i in range(4)])
267271
faces = []
@@ -281,14 +285,15 @@ def dual_graph_as_nx(link):
281285
corners.remove(next)
282286
face.append(next)
283287

284-
G = nx.Graph()
288+
G = graph_class()
285289
to_face = {edge: faces[f] for edge, f in to_face_index.items()}
286290

287291
for edge, face in to_face.items():
288292
opp_edge = edge.opposite()
289293
neighbor = to_face[opp_edge]
290294
if face.label < neighbor.label:
291295
G.add_edge(face.label, neighbor.label,
296+
label=G.number_of_edges(),
292297
interface={face.label: edge, neighbor.label: opp_edge})
293298

294299
G.edge_to_face = to_face_index
@@ -299,13 +304,22 @@ def deconnect_sum(link):
299304
"""
300305
Warning: Destroys the original link.
301306
"""
302-
for cs0, cs1 in DualGraphOfFaces(link).two_cycles():
303-
A, a = cs0.opposite()
304-
B, b = cs0
305-
C, c = cs1.opposite()
306-
D, d = cs1
307-
A[a] = D[d]
308-
B[b] = C[c]
307+
G = dual_graph_as_nx(link, nx.MultiGraph)
308+
for path in nx.simple_cycles(G, 2):
309+
if len(path) == 2:
310+
face0, face1 = path
311+
edges = G[face0][face1]
312+
assert len(edges) == 2
313+
cs0 = edges[0]['interface'][face0]
314+
cs1 = edges[1]['interface'][face0]
315+
316+
# Now cut and reglue.
317+
A, a = cs0.opposite()
318+
B, b = cs0
319+
C, c = cs1.opposite()
320+
D, d = cs1
321+
A[a] = D[d]
322+
B[b] = C[c]
309323
link._build_components()
310324
return link.split_link_diagram(destroy_original=True)
311325

0 commit comments

Comments
 (0)