Skip to content

Commit d11c371

Browse files
authored
Merge pull request #73 from MunchLab/ReebOfSimplexTree
Adding Reeb graph computation stuff
2 parents ee37bf3 + 55df325 commit d11c371

25 files changed

+797
-15
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ venv/
113113
ENV/
114114
env.bak/
115115
venv.bak/
116+
.conda*
116117

117118
# Spyder project settings
118119
.spyderproject
@@ -132,6 +133,8 @@ dmypy.json
132133
# Pyre type checker
133134
.pyre/
134135

136+
# Vscode settings
137+
.vscode/
135138

136139
# Liz's specific sandbox jupyter notebook that probably shouldn't be on the website.
137140
doc_source/notebooks/sandbox_liz.ipynb

cereeberus/cereeberus/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
__all__ = ['data','dist', 'reeb', 'compute']
1+
__all__ = [
2+
'ReebGraph', 'LowerStarSC', 'reeb_of_lower_star',
3+
'MergeTree', 'MapperGraph', 'EmbeddedGraph', 'Interleave', 'Assignment',
4+
'data', 'dist', 'reeb', 'compute'
5+
]
26

37
from .reeb.reebgraph import ReebGraph
48
from .reeb.merge import MergeTree
59
from .reeb.mapper import MapperGraph
610
from .reeb.embeddedgraph import EmbeddedGraph
711
from .distance.interleave import Interleave, Assignment
12+
from .reeb.lowerstarSC import LowerStarSC
13+
from .compute.reeb_of_lower_star import reeb_of_lower_star
14+
15+
# Examples
16+
from .data import ex_reebgraphs, ex_mergetrees, ex_mappergraphs, ex_embedgraphs, ex_torus
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
__all__ = ['degree', 'draw', 'unionfind', 'distance']
1+
__all__ = ['draw', 'unionfind', 'distance', 'reeb_of_lower_star']
2+
from .draw import *
3+
from .unionfind import *
4+
from .reeb_of_lower_star import *
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
from .unionfind import UnionFind
2+
from ..reeb.lowerstarSC import LowerStarSC
3+
import numpy as np
4+
5+
6+
7+
def is_face(sigma, tau):
8+
'''
9+
Check if tau is a face of sigma
10+
11+
Args:
12+
sigma: A simplicial complex (a list of simplices).
13+
tau: A simplex (a list of vertices).
14+
15+
Returns:
16+
bool: True if tau is a face of sigma, False otherwise.
17+
'''
18+
return set(tau).issubset(set(sigma))
19+
20+
def get_levelset_components(L):
21+
'''
22+
Given a list of simplices L representing a level set, compute the connected components. This function is really only helpful inside of reeb_of_lower_star.
23+
24+
Args:
25+
L: A list of simplices (each simplex is a list of vertices).
26+
27+
Returns:
28+
dict: A dictionary where keys are representative simplices and values are lists of simplices in the same connected component.
29+
'''
30+
31+
UF = UnionFind(range(len(L)))
32+
for i, simplex1 in enumerate(L):
33+
for j, simplex2 in enumerate(L):
34+
if i < j:
35+
# Check if they share a vertex
36+
if is_face(simplex1, simplex2) or is_face(simplex2, simplex1):
37+
UF.union(i, j)
38+
39+
# Replace indices with simplices
40+
components_index = UF.components_dict()
41+
components = {}
42+
for key in components_index:
43+
components[tuple(L[key])] = [L[i] for i in components_index[key]]
44+
45+
46+
return components
47+
48+
49+
def reeb_of_lower_star(K: LowerStarSC, verbose = False):
50+
"""Computes the Reeb graph of a Lower Star Simplicial Complex K.
51+
52+
Args:
53+
K (LowerStarSC): A Lower Star Simplicial Complex with assigned filtration values.
54+
verbose (boolean): Make it True if you want lots of printouts.
55+
56+
Returns:
57+
ReebGraph: The computed Reeb graph.
58+
59+
Example:
60+
>>> from cereeberus.reeb.lowerstarSC import LowerStarSC
61+
>>> K = LowerStarSC()
62+
>>> K.insert([0, 1, 2])
63+
>>> K.insert([1, 3])
64+
>>> K.insert([2,3])
65+
>>> K.assign_filtration([0], 0.0)
66+
>>> K.assign_filtration([1], 3.0)
67+
>>> K.assign_filtration([2], 5.0)
68+
>>> K.assign_filtration([3], 7)
69+
>>> R = reeb_of_lower_star(K)
70+
>>> R.draw()
71+
"""
72+
from ..reeb.reebgraph import ReebGraph
73+
74+
funcVals = [(i,K.filtration([i])) for i in K.iter_vertices()]
75+
funcVals.sort(key=lambda x: x[1]) # Sort by filtration value
76+
77+
R = ReebGraph()
78+
79+
currentLevelSet = []
80+
components = {}
81+
half_edge_index = 0
82+
83+
# This will keep track of the components represented by every vertex in the graph so far.
84+
# It will be vertName: connected_component (given as a list of lists) represented by that vertex
85+
vert_to_component = {}
86+
87+
edges_at_prev_level = []
88+
89+
90+
for i, (vert, filt) in enumerate(funcVals):
91+
if verbose:
92+
print(f"\n---\n Processing {vert} at func val {filt:.2f}")
93+
now_min = filt
94+
now_max = funcVals[i+1][1] if i+1 < len(funcVals) else np.inf
95+
star = K.get_star([vert])
96+
lower_star = [s[0] for s in star if s[1] <= filt and len(s[0]) > 1]
97+
upper_star = [s[0] for s in star if s[1] > filt and len(s[0]) > 1]
98+
99+
if verbose:
100+
print(f" Lower star simplices: {lower_star}")
101+
print(f" Upper star simplices: {upper_star}")
102+
103+
#----
104+
# Update the levelset list
105+
#----
106+
107+
for s in lower_star:
108+
# Remove from current level set
109+
if s in currentLevelSet:
110+
currentLevelSet.remove(s)
111+
112+
currentLevelSet.append([vert]) # Add the vertex itself to the level set
113+
components_at_vertex = get_levelset_components(currentLevelSet)
114+
115+
if verbose:
116+
print(f" Current level set simplices: {currentLevelSet}")
117+
print(f" Level set components at vertex {vert} (func val {filt:.2f}):")
118+
for comp in components_at_vertex.values():
119+
print(f" Component: {comp}")
120+
121+
verts_at_level = []
122+
for rep, comp in components_at_vertex.items():
123+
# Add a vertex for each component in this levelset
124+
nextNodeName = R.get_next_vert_name()
125+
R.add_node(nextNodeName, now_min)
126+
vert_to_component[nextNodeName] = comp # Store the component represented by this vertex
127+
verts_at_level.append(nextNodeName)
128+
129+
# Check if any simplex in vertex component is a subset of any of simplices in a previous edge's component
130+
for e in edges_at_prev_level:
131+
prev_comp = vert_to_component[e]
132+
if any([is_face(prev_simp, simp) for simp in comp for prev_simp in prev_comp]):
133+
R.add_edge(e, nextNodeName)
134+
135+
#----
136+
# Add the edge vertices for after the vertex is passed
137+
#----
138+
139+
# Remove the vertex from the level set
140+
if [vert] in currentLevelSet:
141+
currentLevelSet.remove([vert])
142+
143+
# Add the upper star to the current level set
144+
for s in upper_star:
145+
if s not in currentLevelSet:
146+
currentLevelSet.append(s)
147+
148+
components = get_levelset_components(currentLevelSet)
149+
if verbose:
150+
print(f"\n Updated current level set simplices: {currentLevelSet}")
151+
print(f" Level set components after vertex {vert} (func val {filt:.2f}):")
152+
for comp in components.values():
153+
print(f" Component: {comp}")
154+
#----
155+
# Set up a vertex in the Reeb graph for each connected component
156+
# These will represent edges
157+
# These are at height (now_min + now_max)/2
158+
#----
159+
edges_at_prev_level = []
160+
for comp in components.values():
161+
# Create a new vertex in the Reeb graph
162+
e_name = 'e_'+str(half_edge_index)
163+
R.add_node(e_name, (now_min + now_max) / 2)
164+
vert_to_component[e_name] = comp # Store the component represented by this half edge top
165+
half_edge_index += 1
166+
edges_at_prev_level.append(e_name)
167+
168+
# Now connect to the vertices at this level
169+
for v in verts_at_level:
170+
171+
# Get the component represented by vertex v
172+
prev_comp = vert_to_component[v]
173+
174+
if any([is_face(simp, prev_simp) for simp in comp for prev_simp in prev_comp]):
175+
R.add_edge(v, e_name)
176+
177+
return R
178+

cereeberus/cereeberus/compute/unionfind.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Edited from https://yuminlee2.medium.com/union-find-algorithm-ffa9cd7d2dba
22

33
class UnionFind:
4+
"""Union find data structure
5+
"""
46
def __init__(self, vertices):
57
self.parent = {vertex: vertex for vertex in vertices}
68
self.size = {vertex: 1 for vertex in vertices}
@@ -33,6 +35,20 @@ def union(self, node1, node2):
3335

3436
self.count -= 1
3537

38+
def components_dict(self):
39+
"""Returns a dictionary mapping component representatives to lists of elements in that component.
40+
41+
Returns:
42+
dict: A dictionary where keys are component representatives and values are lists of elements in that component.
43+
"""
44+
components = {}
45+
for node in self.parent:
46+
root = self.find(node)
47+
if root not in components:
48+
components[root] = []
49+
components[root].append(node)
50+
return components
51+
3652

3753
if __name__ == "__main__":
3854
edges = [
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11

2-
__all__ = ['ex_reebgraph', 'ex_mergetrees']
2+
__all__ = ['ex_reebgraphs', 'ex_mergetrees', 'ex_mappergraphs', 'ex_embedgraphs', 'ex_torus']
3+
4+
from .ex_reebgraphs import *
5+
from .ex_mergetrees import *
6+
from . import ex_mappergraphs
7+
from . import ex_embedgraphs
8+
from . import ex_torus

0 commit comments

Comments
 (0)