summaryrefslogtreecommitdiff
path: root/src/python
diff options
context:
space:
mode:
Diffstat (limited to 'src/python')
-rw-r--r--src/python/CMakeLists.txt4
-rw-r--r--src/python/gudhi/differentiation/__init__.py3
-rw-r--r--src/python/gudhi/differentiation/tensorflow.py234
-rw-r--r--src/python/gudhi/tensorflow/CubicalLayer.py66
-rw-r--r--src/python/gudhi/tensorflow/LowerStarSimplexTreeLayer.py77
-rw-r--r--src/python/gudhi/tensorflow/RipsLayer.py75
-rw-r--r--src/python/gudhi/tensorflow/__init__.py5
-rw-r--r--src/python/test/test_diff.py2
8 files changed, 226 insertions, 240 deletions
diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt
index 6c17223e..49ec3fea 100644
--- a/src/python/CMakeLists.txt
+++ b/src/python/CMakeLists.txt
@@ -67,7 +67,7 @@ if(PYTHONINTERP_FOUND)
set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'euclidean_strong_witness_complex', ")
# Modules that should not be auto-imported in __init__.py
set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'representations', ")
- set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'differentiation', ")
+ set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'tensorflow', ")
set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'wasserstein', ")
set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'point_cloud', ")
set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'weighted_rips_complex', ")
@@ -271,7 +271,7 @@ if(PYTHONINTERP_FOUND)
file(COPY "gudhi/persistence_graphical_tools.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi")
file(COPY "gudhi/representations" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi/")
file(COPY "gudhi/wasserstein" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi")
- file(COPY "gudhi/differentiation" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi")
+ file(COPY "gudhi/tensorflow" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi")
file(COPY "gudhi/point_cloud" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi")
file(COPY "gudhi/clustering" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi" FILES_MATCHING PATTERN "*.py")
file(COPY "gudhi/weighted_rips_complex.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi")
diff --git a/src/python/gudhi/differentiation/__init__.py b/src/python/gudhi/differentiation/__init__.py
deleted file mode 100644
index 3b7790e4..00000000
--- a/src/python/gudhi/differentiation/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .tensorflow import *
-
-__all__ = ["LowerStarSimplexTreeLayer", "RipsLayer", "CubicalLayer"]
diff --git a/src/python/gudhi/differentiation/tensorflow.py b/src/python/gudhi/differentiation/tensorflow.py
deleted file mode 100644
index 15d5811e..00000000
--- a/src/python/gudhi/differentiation/tensorflow.py
+++ /dev/null
@@ -1,234 +0,0 @@
-import numpy as np
-import tensorflow as tf
-from ..rips_complex import RipsComplex
-from ..cubical_complex import CubicalComplex
-
-# In this file, we write functions based on the Gudhi library that compute persistence diagrams associated to
-# different filtrations (lower star, Rips, cubical), as well as the corresponding positive and negative
-# simplices. We also wrap these functions into Tensorflow models.
-
-
-
-#########################################
-# Lower star filtration on simplex tree #
-#########################################
-
-# The parameters of the model are the vertex function values of the simplex tree.
-
-def _LowerStarSimplexTree(simplextree, filtration, dimension):
- # Parameters: simplextree (simplex tree on which to compute persistence)
- # filtration (function values on the vertices of st),
- # dimension (homology dimension),
-
- for s,_ in simplextree.get_filtration():
- simplextree.assign_filtration(s, -1e10)
-
- # Assign new filtration values
- for i in range(simplextree.num_vertices()):
- simplextree.assign_filtration([i], filtration[i])
- simplextree.make_filtration_non_decreasing()
-
- # Compute persistence diagram
- dgm = simplextree.persistence()
-
- # Get vertex pairs for optimization. First, get all simplex pairs
- pairs = simplextree.persistence_pairs()
-
- # Then, loop over all simplex pairs
- indices, pers = [], []
- for s1, s2 in pairs:
- # Select pairs with good homological dimension and finite lifetime
- if len(s1) == dimension+1 and len(s2) > 0:
- # Get IDs of the vertices corresponding to the filtration values of the simplices
- l1, l2 = np.array(s1), np.array(s2)
- i1 = l1[np.argmax(filtration[l1])]
- i2 = l2[np.argmax(filtration[l2])]
- indices.append(i1)
- indices.append(i2)
- # Compute lifetime
- pers.append(simplextree.filtration(s2)-simplextree.filtration(s1))
-
- # Sort vertex pairs wrt lifetime
- perm = np.argsort(pers)
- indices = np.reshape(indices, [-1,2])[perm][::-1,:].flatten()
-
- return np.array(indices, dtype=np.int32)
-
-class LowerStarSimplexTreeLayer(tf.keras.layers.Layer):
- """
- TensorFlow layer for computing lower-star persistence out of a simplex tree
-
- Attributes:
- simplextree (gudhi.SimplexTree()): underlying simplex tree
- dimension (int): homology dimension
- """
- def __init__(self, simplextree, dimension=0, **kwargs):
- super().__init__(dynamic=True, **kwargs)
- self.dimension = dimension
- self.simplextree = simplextree
-
- def build(self):
- super.build()
-
- def call(self, filtration):
- """
- Compute lower-star persistence diagram associated to a function defined on the vertices of the simplex tree
-
- Parameters:
- F (TensorFlow variable): filter function values over the vertices of the simplex tree
- """
- # Don't try to compute gradients for the vertex pairs
- indices = tf.stop_gradient(_LowerStarSimplexTree(self.simplextree, filtration.numpy(), self.dimension))
- # Get persistence diagram
- self.dgm = tf.reshape(tf.gather(filtration, indices), [-1,2])
- return self.dgm
-
-
-
-
-
-
-
-
-
-############################
-# Vietoris-Rips filtration #
-############################
-
-# The parameters of the model are the point coordinates.
-
-def _Rips(DX, max_edge, dimension):
- # Parameters: DX (distance matrix),
- # max_edge (maximum edge length for Rips filtration),
- # dimension (homology dimension)
-
- # Compute the persistence pairs with Gudhi
- rc = RipsComplex(distance_matrix=DX, max_edge_length=max_edge)
- st = rc.create_simplex_tree(max_dimension=dimension+1)
- dgm = st.persistence()
- pairs = st.persistence_pairs()
-
- # Retrieve vertices v_a and v_b by picking the ones achieving the maximal
- # distance among all pairwise distances between the simplex vertices
- indices, pers = [], []
- for s1, s2 in pairs:
- if len(s1) == dimension+1 and len(s2) > 0:
- l1, l2 = np.array(s1), np.array(s2)
- i1 = [l1[v] for v in np.unravel_index(np.argmax(DX[l1,:][:,l1]),[len(l1), len(l1)])]
- i2 = [l2[v] for v in np.unravel_index(np.argmax(DX[l2,:][:,l2]),[len(l2), len(l2)])]
- indices.append(i1)
- indices.append(i2)
- pers.append(st.filtration(s2)-st.filtration(s1))
-
- # Sort points with distance-to-diagonal
- perm = np.argsort(pers)
- indices = np.reshape(indices, [-1,4])[perm][::-1,:].flatten()
-
- return np.array(indices, dtype=np.int32)
-
-class RipsLayer(tf.keras.layers.Layer):
- """
- TensorFlow layer for computing Rips persistence out of a point cloud
-
- Attributes:
- maximum_edge_length (float): maximum edge length for the Rips complex
- dimension (int): homology dimension
- """
- def __init__(self, maximum_edge_length=12, dimension=1, **kwargs):
- super().__init__(dynamic=True, **kwargs)
- self.max_edge = maximum_edge_length
- self.dimension = dimension
-
- def build(self):
- super.build()
-
- def call(self, X):
- """
- Compute Rips persistence diagram associated to a point cloud
-
- Parameters:
- X (TensorFlow variable): point cloud of shape [number of points, number of dimensions]
- """
- # Compute distance matrix
- DX = tf.math.sqrt(tf.reduce_sum((tf.expand_dims(X, 1)-tf.expand_dims(X, 0))**2, 2))
- # Compute vertices associated to positive and negative simplices
- # Don't compute gradient for this operation
- indices = tf.stop_gradient(_Rips(DX.numpy(), self.max_edge, self.dimension))
- # Get persistence diagram by simply picking the corresponding entries in the distance matrix
- if self.dimension > 0:
- dgm = tf.reshape(tf.gather_nd(DX, tf.reshape(indices, [-1,2])), [-1,2])
- else:
- indices = tf.reshape(indices, [-1,2])[1::2,:]
- dgm = tf.concat([tf.zeros([indices.shape[0],1]), tf.reshape(tf.gather_nd(DX, indices), [-1,1])], axis=1)
- return dgm
-
-
-
-
-
-
-
-
-
-######################
-# Cubical filtration #
-######################
-
-# The parameters of the model are the pixel values.
-
-def _Cubical(X, dimension):
- # Parameters: X (image),
- # dimension (homology dimension)
-
- # Compute the persistence pairs with Gudhi
- cc = CubicalComplex(dimensions=X.shape, top_dimensional_cells=X.flatten())
- cc.persistence()
- try:
- cof = cc.cofaces_of_persistence_pairs()[0][dimension]
- except IndexError:
- cof = np.array([])
-
- if len(cof) > 0:
- # Sort points with distance-to-diagonal
- Xs = X.shape
- pers = [X[np.unravel_index(cof[idx,1], Xs)] - X[np.unravel_index(cof[idx,0], Xs)] for idx in range(len(cof))]
- perm = np.argsort(pers)
- cof = cof[perm[::-1]]
-
- # Retrieve and ouput image indices/pixels corresponding to positive and negative simplices
- D = len(Xs) if len(cof) > 0 else 1
- ocof = np.array([0 for _ in range(D*2*cof.shape[0])])
- count = 0
- for idx in range(0,2*cof.shape[0],2):
- ocof[D*idx:D*(idx+1)] = np.unravel_index(cof[count,0], Xs)
- ocof[D*(idx+1):D*(idx+2)] = np.unravel_index(cof[count,1], Xs)
- count += 1
- return np.array(ocof, dtype=np.int32)
-
-class CubicalLayer(tf.keras.layers.Layer):
- """
- TensorFlow layer for computing cubical persistence out of a cubical complex
-
- Attributes:
- dimension (int): homology dimension
- """
- def __init__(self, dimension=1, **kwargs):
- super().__init__(dynamic=True, **kwargs)
- self.dimension = dimension
-
- def build(self):
- super.build()
-
- def call(self, X):
- """
- Compute persistence diagram associated to a cubical complex filtered by some pixel values
-
- Parameters:
- X (TensorFlow variable): pixel values of the cubical complex
- """
- # Compute pixels associated to positive and negative simplices
- # Don't compute gradient for this operation
- indices = tf.stop_gradient(_Cubical(X.numpy(), self.dimension))
- # Get persistence diagram by simply picking the corresponding entries in the image
- dgm = tf.reshape(tf.gather_nd(X, tf.reshape(indices, [-1,len(X.shape)])), [-1,2])
- return dgm
diff --git a/src/python/gudhi/tensorflow/CubicalLayer.py b/src/python/gudhi/tensorflow/CubicalLayer.py
new file mode 100644
index 00000000..e36adec5
--- /dev/null
+++ b/src/python/gudhi/tensorflow/CubicalLayer.py
@@ -0,0 +1,66 @@
+import numpy as np
+import tensorflow as tf
+from ..cubical_complex import CubicalComplex
+
+######################
+# Cubical filtration #
+######################
+
+# The parameters of the model are the pixel values.
+
+def _Cubical(X, dimension):
+ # Parameters: X (image),
+ # dimension (homology dimension)
+
+ # Compute the persistence pairs with Gudhi
+ cc = CubicalComplex(dimensions=X.shape, top_dimensional_cells=X.flatten())
+ cc.persistence()
+ try:
+ cof = cc.cofaces_of_persistence_pairs()[0][dimension]
+ except IndexError:
+ cof = np.array([])
+
+ if len(cof) > 0:
+ # Sort points with distance-to-diagonal
+ Xs = X.shape
+ pers = [X[np.unravel_index(cof[idx,1], Xs)] - X[np.unravel_index(cof[idx,0], Xs)] for idx in range(len(cof))]
+ perm = np.argsort(pers)
+ cof = cof[perm[::-1]]
+
+ # Retrieve and ouput image indices/pixels corresponding to positive and negative simplices
+ D = len(Xs) if len(cof) > 0 else 1
+ ocof = np.array([0 for _ in range(D*2*cof.shape[0])])
+ count = 0
+ for idx in range(0,2*cof.shape[0],2):
+ ocof[D*idx:D*(idx+1)] = np.unravel_index(cof[count,0], Xs)
+ ocof[D*(idx+1):D*(idx+2)] = np.unravel_index(cof[count,1], Xs)
+ count += 1
+ return np.array(ocof, dtype=np.int32)
+
+class CubicalLayer(tf.keras.layers.Layer):
+ """
+ TensorFlow layer for computing cubical persistence out of a cubical complex
+
+ Attributes:
+ dimension (int): homology dimension
+ """
+ def __init__(self, dimension=1, **kwargs):
+ super().__init__(dynamic=True, **kwargs)
+ self.dimension = dimension
+
+ def build(self):
+ super.build()
+
+ def call(self, X):
+ """
+ Compute persistence diagram associated to a cubical complex filtered by some pixel values
+
+ Parameters:
+ X (TensorFlow variable): pixel values of the cubical complex
+ """
+ # Compute pixels associated to positive and negative simplices
+ # Don't compute gradient for this operation
+ indices = tf.stop_gradient(_Cubical(X.numpy(), self.dimension))
+ # Get persistence diagram by simply picking the corresponding entries in the image
+ dgm = tf.reshape(tf.gather_nd(X, tf.reshape(indices, [-1,len(X.shape)])), [-1,2])
+ return dgm
diff --git a/src/python/gudhi/tensorflow/LowerStarSimplexTreeLayer.py b/src/python/gudhi/tensorflow/LowerStarSimplexTreeLayer.py
new file mode 100644
index 00000000..fc963d2f
--- /dev/null
+++ b/src/python/gudhi/tensorflow/LowerStarSimplexTreeLayer.py
@@ -0,0 +1,77 @@
+import numpy as np
+import tensorflow as tf
+
+#########################################
+# Lower star filtration on simplex tree #
+#########################################
+
+# The parameters of the model are the vertex function values of the simplex tree.
+
+def _LowerStarSimplexTree(simplextree, filtration, dimension):
+ # Parameters: simplextree (simplex tree on which to compute persistence)
+ # filtration (function values on the vertices of st),
+ # dimension (homology dimension),
+
+ for s,_ in simplextree.get_filtration():
+ simplextree.assign_filtration(s, -1e10)
+
+ # Assign new filtration values
+ for i in range(simplextree.num_vertices()):
+ simplextree.assign_filtration([i], filtration[i])
+ simplextree.make_filtration_non_decreasing()
+
+ # Compute persistence diagram
+ dgm = simplextree.persistence()
+
+ # Get vertex pairs for optimization. First, get all simplex pairs
+ pairs = simplextree.persistence_pairs()
+
+ # Then, loop over all simplex pairs
+ indices, pers = [], []
+ for s1, s2 in pairs:
+ # Select pairs with good homological dimension and finite lifetime
+ if len(s1) == dimension+1 and len(s2) > 0:
+ # Get IDs of the vertices corresponding to the filtration values of the simplices
+ l1, l2 = np.array(s1), np.array(s2)
+ i1 = l1[np.argmax(filtration[l1])]
+ i2 = l2[np.argmax(filtration[l2])]
+ indices.append(i1)
+ indices.append(i2)
+ # Compute lifetime
+ pers.append(simplextree.filtration(s2)-simplextree.filtration(s1))
+
+ # Sort vertex pairs wrt lifetime
+ perm = np.argsort(pers)
+ indices = np.reshape(indices, [-1,2])[perm][::-1,:].flatten()
+
+ return np.array(indices, dtype=np.int32)
+
+class LowerStarSimplexTreeLayer(tf.keras.layers.Layer):
+ """
+ TensorFlow layer for computing lower-star persistence out of a simplex tree
+
+ Attributes:
+ simplextree (gudhi.SimplexTree()): underlying simplex tree
+ dimension (int): homology dimension
+ """
+ def __init__(self, simplextree, dimension=0, **kwargs):
+ super().__init__(dynamic=True, **kwargs)
+ self.dimension = dimension
+ self.simplextree = simplextree
+
+ def build(self):
+ super.build()
+
+ def call(self, filtration):
+ """
+ Compute lower-star persistence diagram associated to a function defined on the vertices of the simplex tree
+
+ Parameters:
+ F (TensorFlow variable): filter function values over the vertices of the simplex tree
+ """
+ # Don't try to compute gradients for the vertex pairs
+ indices = tf.stop_gradient(_LowerStarSimplexTree(self.simplextree, filtration.numpy(), self.dimension))
+ # Get persistence diagram
+ self.dgm = tf.reshape(tf.gather(filtration, indices), [-1,2])
+ return self.dgm
+
diff --git a/src/python/gudhi/tensorflow/RipsLayer.py b/src/python/gudhi/tensorflow/RipsLayer.py
new file mode 100644
index 00000000..373e021e
--- /dev/null
+++ b/src/python/gudhi/tensorflow/RipsLayer.py
@@ -0,0 +1,75 @@
+import numpy as np
+import tensorflow as tf
+from ..rips_complex import RipsComplex
+
+############################
+# Vietoris-Rips filtration #
+############################
+
+# The parameters of the model are the point coordinates.
+
+def _Rips(DX, max_edge, dimension):
+ # Parameters: DX (distance matrix),
+ # max_edge (maximum edge length for Rips filtration),
+ # dimension (homology dimension)
+
+ # Compute the persistence pairs with Gudhi
+ rc = RipsComplex(distance_matrix=DX, max_edge_length=max_edge)
+ st = rc.create_simplex_tree(max_dimension=dimension+1)
+ dgm = st.persistence()
+ pairs = st.persistence_pairs()
+
+ # Retrieve vertices v_a and v_b by picking the ones achieving the maximal
+ # distance among all pairwise distances between the simplex vertices
+ indices, pers = [], []
+ for s1, s2 in pairs:
+ if len(s1) == dimension+1 and len(s2) > 0:
+ l1, l2 = np.array(s1), np.array(s2)
+ i1 = [l1[v] for v in np.unravel_index(np.argmax(DX[l1,:][:,l1]),[len(l1), len(l1)])]
+ i2 = [l2[v] for v in np.unravel_index(np.argmax(DX[l2,:][:,l2]),[len(l2), len(l2)])]
+ indices.append(i1)
+ indices.append(i2)
+ pers.append(st.filtration(s2)-st.filtration(s1))
+
+ # Sort points with distance-to-diagonal
+ perm = np.argsort(pers)
+ indices = np.reshape(indices, [-1,4])[perm][::-1,:].flatten()
+
+ return np.array(indices, dtype=np.int32)
+
+class RipsLayer(tf.keras.layers.Layer):
+ """
+ TensorFlow layer for computing Rips persistence out of a point cloud
+
+ Attributes:
+ maximum_edge_length (float): maximum edge length for the Rips complex
+ dimension (int): homology dimension
+ """
+ def __init__(self, maximum_edge_length=12, dimension=1, **kwargs):
+ super().__init__(dynamic=True, **kwargs)
+ self.max_edge = maximum_edge_length
+ self.dimension = dimension
+
+ def build(self):
+ super.build()
+
+ def call(self, X):
+ """
+ Compute Rips persistence diagram associated to a point cloud
+
+ Parameters:
+ X (TensorFlow variable): point cloud of shape [number of points, number of dimensions]
+ """
+ # Compute distance matrix
+ DX = tf.math.sqrt(tf.reduce_sum((tf.expand_dims(X, 1)-tf.expand_dims(X, 0))**2, 2))
+ # Compute vertices associated to positive and negative simplices
+ # Don't compute gradient for this operation
+ indices = tf.stop_gradient(_Rips(DX.numpy(), self.max_edge, self.dimension))
+ # Get persistence diagram by simply picking the corresponding entries in the distance matrix
+ if self.dimension > 0:
+ dgm = tf.reshape(tf.gather_nd(DX, tf.reshape(indices, [-1,2])), [-1,2])
+ else:
+ indices = tf.reshape(indices, [-1,2])[1::2,:]
+ dgm = tf.concat([tf.zeros([indices.shape[0],1]), tf.reshape(tf.gather_nd(DX, indices), [-1,1])], axis=1)
+ return dgm
+
diff --git a/src/python/gudhi/tensorflow/__init__.py b/src/python/gudhi/tensorflow/__init__.py
new file mode 100644
index 00000000..47335a25
--- /dev/null
+++ b/src/python/gudhi/tensorflow/__init__.py
@@ -0,0 +1,5 @@
+from .CubicalLayer import CubicalLayer
+from .LowerStarSimplexTreeLayer import LowerStarSimplexTreeLayer
+from .RipsLayer import RipsLayer
+
+__all__ = ["LowerStarSimplexTreeLayer", "RipsLayer", "CubicalLayer"]
diff --git a/src/python/test/test_diff.py b/src/python/test/test_diff.py
index 129b9f03..73a03697 100644
--- a/src/python/test/test_diff.py
+++ b/src/python/test/test_diff.py
@@ -1,4 +1,4 @@
-from gudhi.differentiation import *
+from gudhi.tensorflow import *
import numpy as np
import tensorflow as tf
import gudhi as gd