Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cereeberus/cereeberus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"ReebGraph",
"LowerStar",
"computeReeb",
"computeMapper",
"MergeTree",
"MapperGraph",
"EmbeddedGraph",
Expand All @@ -20,6 +21,8 @@
from .distance.interleave import Interleave, Assignment
from .reeb.lowerstar import LowerStar
from .compute.computereeb import computeReeb
from .compute.computemapper import computeMapper
from .compute.computemapper import cover

# Examples
from .data import (
Expand Down
3 changes: 1 addition & 2 deletions cereeberus/cereeberus/compute/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
__all__ = ["draw", "unionfind", "computeReeb"]
from .draw import *
__all__ = ["unionfind", "computeReeb"]
from .unionfind import *
from .computereeb import *
158 changes: 158 additions & 0 deletions cereeberus/cereeberus/compute/computemapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
from ..reeb.mapper import MapperGraph


# Interprets the lensfunction as a python function, does it, then returns the new location of each point for every point
def __runlensfunction(lensfunction, pointcloud):
if callable(lensfunction):
lensfunctionoutput = []
for val in range(len(pointcloud)):
lensfunctionoutput.append(
[lensfunction(pointcloud[val]), tuple(pointcloud[val])]
)
else:
print("Invalid lens function")
# print("Lens Function Output: ")
# print(lensfunctionoutput)
return lensfunctionoutput


# Creates a list of covers, together with any points inside the cover, ie, [[cover, point1, point2], [cover, point3]]
# Also removes any covers that have no points inside of them
# this would probably be better done as a struct, containing a covering set, the covering set's position in the cover, and an array of points
def __createcoveringsets(points, cover):
# adds location information to cover
for val0 in range(len(cover)):
cover[val0] = (cover[val0][0], cover[val0][1], val0)
# creates the list
coveringsets = []
for val1 in range(len(cover)):
coveringsets.append([cover[val1]])
for val2 in range(len(points)):
if points[val2][0] >= cover[val1][0] and points[val2][0] <= cover[val1][1]:
coveringsets[val1].append(points[val2])
# removes unused covers
position = 0
while position < len(coveringsets):
if len(coveringsets[position]) == 1:
coveringsets.pop(position)
else:
position += 1
# print("Covering Sets Output: ")
# print(coveringsets)
return coveringsets


# cluster the points using a number of existing clustering algorithms
def __cluster(coveringsets, clusteralgorithm):
# trivial clustering
if clusteralgorithm == "trivial":
finished_cluster = list()
cluster = list()
for val1 in range(len(coveringsets)):
cluster.append(coveringsets[val1][0][2])
for val2 in range(1, len(coveringsets[val1])):
cluster.append(
(coveringsets[val1][val2][1][0], coveringsets[val1][val2][1][1])
)
finished_cluster.append(
cluster[:]
) # Works like this to avoid passing by reference in python lists
cluster.clear()
# print("Clustering Output: ")
# print(finished_cluster)
return finished_cluster
# execute sklearn clusterings
elif callable(clusteralgorithm):
finished_cluster = list()
coverpointcloud = list()
cluster = list()
for val1 in range(len(coveringsets)):
# alters data to fit with sklearn clustering algorithms
for val2 in range(1, len(coveringsets[val1])):
coverpointcloud.append(
(coveringsets[val1][val2][1][0], coveringsets[val1][val2][1][1])
)
# does clustering algorithm
cluster_out = clusteralgorithm(coverpointcloud)
# puts points into list
for val2 in range(max(cluster_out.labels_) + 1):
cluster.append(
[coveringsets[val1][0][2]]
) # The position of the covering set in the cover (preserved for distance purposes)
for val3 in range(len(cluster_out.labels_)):
if cluster_out.labels_[val3] == val2:
cluster[val2].append(coverpointcloud[val3])
finished_cluster.append(cluster[val2])
coverpointcloud.clear()
cluster.clear()
# print("Clustering Output: ")
# print(finished_cluster)
return finished_cluster
else:
print("input not valid")
return list()


# Adds edges between the cluster that share points
def __addedges(clusterpoints):
outputgraph = MapperGraph()
val2 = 0
for val1 in range(len(clusterpoints)):
outputgraph.add_node(val1, clusterpoints[val1][0])
while val2 < val1:
if clusterpoints[val1][0] != clusterpoints[val2][0]:
if len(set(clusterpoints[val1]) & set(clusterpoints[val2])) > 0:
outputgraph.add_edge(val1, val2)
val2 += 1
val2 = 0
# print("Final Output: ")
# print(outputgraph)
return outputgraph


# Does the Mapper Algorithm in order
def computeMapper(pointcloud, lensfunction, cover, clusteralgorithm):
"""
Computes the Mapper Alogirthm

Parameters:
A pointcloud (as a list)
A lens function (as a callable)
A cover (as a list of intervals)
A clustering algorithm (as a callable)

Returns:
A MapperGraph object as given by the Mapper Algorithm run on the parameters
"""
lensfunctionoutput = __runlensfunction(
lensfunction, pointcloud
) # move to compute folder, change name to computemapper
coveringsets = __createcoveringsets(lensfunctionoutput, cover)
clusterpoints = __cluster(coveringsets, clusteralgorithm)
outputgraph = __addedges(clusterpoints)
return outputgraph


# function to create covers
# cover(min, max, #covers, %overlap)
def cover(min=-1, max=1, numcovers=10, percentoverlap=0.5):
"""
Creates a cover to be used for inputs in the computeMapper function

Parameters:
min: the minimum for the range of the covering sets
max: the maximum for the range of the covering sets
numcovers: number of covers to create
percentoverlap: percentage (from 0 to 1) of overlap between covers

Returns:
An array of intervals
"""
output = []
val = 0
coversize = (max - min) / numcovers * (1 + (percentoverlap))
while val < numcovers:
center = (min * (numcovers - (val + 0.5)) + max * (val + 0.5)) / numcovers
output.append(((-0.5 * coversize) + center, (0.5 * coversize) + center))
val += 1
return output
2 changes: 2 additions & 0 deletions cereeberus/cereeberus/draw/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__all__ = ["draw"]
from .draw import *
2 changes: 1 addition & 1 deletion cereeberus/cereeberus/reeb/reebgraph.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import networkx as nx
from ..compute import draw
from ..draw import draw
import matplotlib.pyplot as plt
import numpy as np

Expand Down
7 changes: 7 additions & 0 deletions doc_source/modules/compute/compute_mapper.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Compute Mapper Graph of a Point Cloud
**********************************************

The ``computeMapper`` module provides functionality to compute the Maper graph of a point cloud.

.. autofunction:: cereeberus.compute.computemapper.computeMapper
.. autofunction:: cereeberus.compute.computemapper.cover
2 changes: 1 addition & 1 deletion doc_source/modules/compute/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ The ``compute`` module has helper code used throughout the package.
:maxdepth: 2

Compute Reeb Graph from Lower Star Filtration <compute_reeb.rst>
Draw <compute_draw.rst>
Compute Mapper Graph from a Point Cloud <compute_mapper.rst>
Union Find <compute_unionfind.rst>
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ Draw

The `draw` functions are helper functions to plot reeb graphs and merge trees.

.. automodule:: cereeberus.compute.draw
.. automodule:: cereeberus.draw.draw
:members:
10 changes: 10 additions & 0 deletions doc_source/modules/draw/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Draw Module

The ``draw`` module has helper code used throughout the package.

```{eval-rst}
.. toctree::
:maxdepth: 2

Draw <compute_draw.rst>
```
1 change: 1 addition & 0 deletions doc_source/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ See below for the full documentation of the modules available in `ceREEBerus`.
Distances <distance/index.rst>
Example data and graphs <data/index.md>
Compute <compute/index.md>
Draw <draw/index.md>
```
497 changes: 497 additions & 0 deletions doc_source/notebooks/compute_mapper.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions doc_source/notebooks/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ This section is for adding example jupyter notebooks.
example_graphs.ipynb
interleaving_basics.ipynb
compute_reeb.ipynb
compute_mapper.ipynb
46 changes: 18 additions & 28 deletions doc_source/notebooks/mapper_basics.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[project]
name = "cereeberus"
version = "0.1.9"
version = "0.1.10"
authors = [
{ name="Liz Munch", email="muncheli@msu.edu" },
]
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ myst-parser
sphinx_rtd_theme
flake8
black
scikit-learn
70 changes: 70 additions & 0 deletions tests/test_computemapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import unittest
from cereeberus import MapperGraph, computeMapper, cover
import networkx as nx
from sklearn.datasets import make_circles
from sklearn.cluster import DBSCAN

class TestReebClass(unittest.TestCase):
def test_cover(self):
#Checks to see if the cover generating function is working
examplecover = [(-1.125, -0.375), (-0.625, 0.125), (-0.125, 0.625), (0.375, 1.125)]
testcover = cover(min=-1, max=1, numcovers=4, percentoverlap=.5)
self.assertEqual(examplecover, testcover)

def test_computeMapper_trivial(self):
#checking the simple two point graph (mostly to check if the trivial clustering function works, it has been the problem child of this whole project)
examplegraph1 = MapperGraph()
examplegraph1.add_node(0,0)
examplegraph1.add_node(1,1)
examplegraph1.add_node(2,2)
examplegraph1.add_edge(0,1)
testgraph1 = computeMapper([(0.6, 0), (-0.1, 0.5)], (lambda a : a[0]), [(-1,0),(-0.5,0.5),(0,1)], "trivial")
check = nx.utils.graphs_equal(examplegraph1, testgraph1)
self.assertEqual(check, True)

def test_computeMapper_nontrivial(self):
#checking the default graph for the compute_mapper notebook to see if it remains the same
data, labels = make_circles(n_samples=500, factor=0.4, noise=0.05, random_state=0)
testgraph2 = computeMapper(data, (lambda a : a[0]), cover(min=-1, max=1, numcovers=7, percentoverlap=.5), DBSCAN(min_samples=2,eps=0.3).fit)
examplegraph2 = MapperGraph()
examplegraph2.add_node(0,0)
examplegraph2.add_node(1,1)
examplegraph2.add_node(2,1)
examplegraph2.add_node(3,1)
examplegraph2.add_node(4,2)
examplegraph2.add_node(5,2)
examplegraph2.add_node(6,2)
examplegraph2.add_node(7,3)
examplegraph2.add_node(8,3)
examplegraph2.add_node(9,3)
examplegraph2.add_node(10,3)
examplegraph2.add_node(11,4)
examplegraph2.add_node(12,4)
examplegraph2.add_node(13,4)
examplegraph2.add_node(14,5)
examplegraph2.add_node(15,5)
examplegraph2.add_node(16,5)
examplegraph2.add_node(17,6)
examplegraph2.add_edge(0,1)
examplegraph2.add_edge(0,2)
examplegraph2.add_edge(3,4)
examplegraph2.add_edge(1,6)
examplegraph2.add_edge(2,5)
examplegraph2.add_edge(4,7)
examplegraph2.add_edge(4,8)
examplegraph2.add_edge(6,10)
examplegraph2.add_edge(5,9)
examplegraph2.add_edge(7,11)
examplegraph2.add_edge(8,11)
examplegraph2.add_edge(10,13)
examplegraph2.add_edge(9,12)
examplegraph2.add_edge(12,16)
examplegraph2.add_edge(13,15)
examplegraph2.add_edge(11,14)
examplegraph2.add_edge(16,17)
examplegraph2.add_edge(15,17)
check = nx.utils.graphs_equal(examplegraph2, testgraph2)
self.assertEqual(check, True)

if __name__ == '__main__':
unittest.main()
Loading