from cereeberus import ReebGraph
[docs]
class MapperGraph(ReebGraph):
"""
A mapper graph structure. This inherits the properties of the Reeb graph in that it is a graph with a function given on the vertices, but with some additional requirements.
- The values are integers in some range, [n_low, n_low+1, \cdots, n_high], although we consider the funciton values to be [\delta * n_low, \delta* (n_low+1), \cdots, \delta * n_high] for a stored delta
- If an edge crosses a value, it has a vertex (so that the inverse image of any integer is only vertices, not interiors of edges)
- An internal delta is stored so that this can be interpreted as function values [\delta * n_low, \delta* (n_low+1), \cdots, \delta * n_high]
"""
[docs]
def __init__(self,
G=None, f={}, delta = None, seed = None, verbose=False):
# Check that $f$ values are only integers
if not all([isinstance(f[v], int) for v in f]):
raise ValueError("Function values must be integers.")
if delta is None:
self.delta = 1
else:
self.delta = delta
super().__init__(G, f, seed, verbose)
self.mapperify()
[docs]
def add_edge(self, u, v, reset_pos=True):
"""
Add an edge to the graph. This will also update the internal structure to make sure it satisfies the mapper properties.
"""
super().add_edge(u, v, reset_pos)
self.mapperify()
[docs]
def mapperify(self):
"""
Take the internal structure and make sure it satisfies the requirement that all edges have adjacent function values.
"""
# If we're initializing with nothing, this should pass.
# Note that if self.n_low is None, then self.n_high and self.delta
# are both None as well but I am not currently checking that.
try:
n_low = min(self.f.values())
n_high = max(self.f.values())
except:
return
last_vert_name = max(self.nodes())
for i in range(n_low,n_high+1):
e_list = [e for e in self.edges() if self.f[e[0]] < i and self.f[e[1]] > i]
for e in e_list:
w_name = self.next_vert_name(last_vert_name)
self.subdivide_edge(*e,w_name, i)
last_vert_name = w_name
[docs]
def add_node(self, vertex, f_vertex, reset_pos=True):
"""
Same as adding a node in Reeb, but with the additional requirement that the function value is an integer.
"""
if not isinstance(f_vertex, int):
raise ValueError("Function values must be integers.")
return super().add_node(vertex, f_vertex, reset_pos)
[docs]
def set_pos_from_f(self, seed=None, verbose=False):
"""
Same as the Reeb graph function, but we want to draw the vertex locations at delta*function value.
"""
super().set_pos_from_f(seed, verbose)
for v in self.nodes():
self.pos_f[v] = (self.pos_f[v][0],self.delta * self.f[v])
[docs]
def induced_subgraph(self, nodes):
"""
Returns the subgraph of the mapper graph induced by the nodes in the list nodes.
Parameters:
nodes (list): The list of nodes to include in the subgraph.
Returns:
MapperGraph
"""
R = super().induced_subgraph(nodes)
return R.to_mapper(self.delta)