redzed-tda 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- redzed_tda-0.1.0/CMakeLists.txt +28 -0
- redzed_tda-0.1.0/LICENSE +21 -0
- redzed_tda-0.1.0/PKG-INFO +152 -0
- redzed_tda-0.1.0/README.md +123 -0
- redzed_tda-0.1.0/cpp/bindings.cpp +119 -0
- redzed_tda-0.1.0/cpp/redzed.cpp +1527 -0
- redzed_tda-0.1.0/pyproject.toml +62 -0
- redzed_tda-0.1.0/src/redzed_tda/__init__.py +96 -0
- redzed_tda-0.1.0/src/redzed_tda/py.typed +0 -0
- redzed_tda-0.1.0/tests/test_redzed.py +149 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.15...3.31)
|
|
2
|
+
project(redzed LANGUAGES CXX)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
7
|
+
|
|
8
|
+
# Development.Module (not the full Development) is what a extension module needs,
|
|
9
|
+
# and is the component that works when there are no static Python libraries around
|
|
10
|
+
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
|
|
11
|
+
find_package(pybind11 CONFIG REQUIRED)
|
|
12
|
+
|
|
13
|
+
pybind11_add_module(_core cpp/bindings.cpp)
|
|
14
|
+
|
|
15
|
+
target_compile_definitions(_core PRIVATE
|
|
16
|
+
REDZED_NO_MAIN # drop the command line driver in redzed.cpp
|
|
17
|
+
NDEBUG
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if(MSVC)
|
|
21
|
+
target_compile_options(_core PRIVATE /O2)
|
|
22
|
+
# windows.h defines min and max as macros, which breaks std::max in redzed.cpp
|
|
23
|
+
target_compile_definitions(_core PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN)
|
|
24
|
+
else()
|
|
25
|
+
target_compile_options(_core PRIVATE -O3)
|
|
26
|
+
endif()
|
|
27
|
+
|
|
28
|
+
install(TARGETS _core DESTINATION redzed_tda)
|
redzed_tda-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nathan Kershaw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: redzed-tda
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast Vietoris-Rips persistent homology
|
|
5
|
+
Keywords: topology,persistent homology,vietoris-rips,tda
|
|
6
|
+
Author-Email: Chris Kapulkin <krzysztof.r.kapulkin@vanderbilt.edu>, Nathan Kershaw <nkershaw@uwo.ca>
|
|
7
|
+
Maintainer-Email: Nathan Kershaw <nkershaw@uwo.ca>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: C++
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Project-URL: Homepage, https://github.com/nkershaw01/RedZeD
|
|
23
|
+
Project-URL: Source, https://github.com/nkershaw01/RedZeD/tree/main/python
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: numpy>=1.21
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# redzed-tda
|
|
31
|
+
|
|
32
|
+
Fast Vietoris–Rips persistent homology, implemented in C++ with a NumPy interface.
|
|
33
|
+
|
|
34
|
+
This is the Python package for **RedZeD**, an algorithm to compute Vietoris-Rips
|
|
35
|
+
persistent homology. A key technique is *active enumeration*, allowing one to avoid enumerating
|
|
36
|
+
almost all (n+1) birth simplices in a filtration.
|
|
37
|
+
RedZeD often gives a considerable improvement in both time and memory over
|
|
38
|
+
existing implementations. Full details are in the paper:
|
|
39
|
+
|
|
40
|
+
> Chris Kapulkin and Nathan Kershaw.
|
|
41
|
+
> *RedZeD: Computing persistent homology by Reduction to Zero Differentials.*
|
|
42
|
+
> arXiv:2606.06310 (2026). <https://arxiv.org/abs/2606.06310>
|
|
43
|
+
|
|
44
|
+
The [main repository](https://github.com/nkershaw01/RedZeD) also holds the
|
|
45
|
+
reference Julia implementation.
|
|
46
|
+
|
|
47
|
+
If you encounter any errors or bugs, please email nkershaw@uwo.ca.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install redzed-tda
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The distribution is `redzed-tda` and the import name is `redzed_tda`. (The name
|
|
56
|
+
`redzed` on PyPI belongs to an unrelated project.)
|
|
57
|
+
|
|
58
|
+
Wheels are provided for Linux, macOS and Windows, so no compiler is needed.
|
|
59
|
+
|
|
60
|
+
If no wheel matches your platform, or your Python version is newer than the last
|
|
61
|
+
release, pip falls back to building from source. That needs a C++17 compiler on
|
|
62
|
+
your machine. On Windows the compiler must be MSVC, not MinGW.
|
|
63
|
+
|
|
64
|
+
## Quick start
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import numpy as np
|
|
68
|
+
import redzed_tda as rz
|
|
69
|
+
|
|
70
|
+
points = np.random.default_rng(0).normal(size=(200, 3))
|
|
71
|
+
distances = rz.pairwise_distances(points)
|
|
72
|
+
|
|
73
|
+
h0, h1 = rz.rips(distances, maxdim=1)
|
|
74
|
+
|
|
75
|
+
print(h1) # (k, 2) array of [birth, death] rows
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Input
|
|
79
|
+
|
|
80
|
+
`rips` takes a **distance matrix, not coordinates**. If your data is an array of
|
|
81
|
+
points in R^n, either form works:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# square (n, n) matrix
|
|
85
|
+
h0, h1 = rz.rips(rz.pairwise_distances(points), maxdim=1)
|
|
86
|
+
|
|
87
|
+
# condensed vector of n(n-1)/2 values, in scipy pdist order
|
|
88
|
+
from scipy.spatial.distance import pdist
|
|
89
|
+
h0, h1 = rz.rips(pdist(points), maxdim=1)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
pairwise_distances and pdist both take in an array of points and return the
|
|
93
|
+
distance matrix. You may also pass redzed_tda.rips a square matrix constructed
|
|
94
|
+
yourself. Only the lower triangle of a square input is read, so your matrix is
|
|
95
|
+
assumed symmetric with a zero diagonal. Passing a non-square array raises
|
|
96
|
+
`ValueError`, but symmetry is not checked, so the user is responsible for
|
|
97
|
+
ensuring the matrix is symmetric.
|
|
98
|
+
|
|
99
|
+
## Options
|
|
100
|
+
|
|
101
|
+
| Argument | Default | Meaning |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `distances` | — | Square `(n, n)` matrix or condensed `n(n-1)/2` vector. |
|
|
104
|
+
| `maxdim` | `1` | Highest homology dimension. `maxdim=1` returns H0 and H1. |
|
|
105
|
+
| `threshold` | `inf` | Ignore simplices with greater diameter. |
|
|
106
|
+
| `simplex_pairs` | `False` | Also return the birth and death simplex associated to every interval. |
|
|
107
|
+
|
|
108
|
+
## Output
|
|
109
|
+
|
|
110
|
+
A list of `maxdim + 1` arrays. Entry `d` has shape `(k, 2)` and holds the
|
|
111
|
+
`[birth, death]` pairs in dimension `d`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
diagrams = rz.rips(distances, maxdim=2)
|
|
115
|
+
h0, h1, h2 = diagrams
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
With `simplex_pairs=True` you get `(diagrams, simplices)` instead, where
|
|
119
|
+
`simplices[d][i]` is a `(birth_vertices, death_vertices)` tuple corresponding to
|
|
120
|
+
`diagrams[d][i]`. The death tuple is empty for an infinite interval.
|
|
121
|
+
|
|
122
|
+
## Building from source
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
git clone https://github.com/nkershaw01/RedZeD
|
|
126
|
+
cd RedZeD/python
|
|
127
|
+
pip install -e ".[test]"
|
|
128
|
+
pytest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Requires a C++17 compiler. On Windows that must be MSVC.
|
|
132
|
+
|
|
133
|
+
## Citing
|
|
134
|
+
|
|
135
|
+
If you use this software in published work, please cite the paper:
|
|
136
|
+
|
|
137
|
+
```bibtex
|
|
138
|
+
@misc{kapulkin2026redzed,
|
|
139
|
+
title = {RedZeD: Computing persistent homology by Reduction to Zero Differentials},
|
|
140
|
+
author = {Kapulkin, Chris and Kershaw, Nathan},
|
|
141
|
+
year = {2026},
|
|
142
|
+
eprint = {2606.06310},
|
|
143
|
+
archivePrefix = {arXiv},
|
|
144
|
+
primaryClass = {cs.CG},
|
|
145
|
+
doi = {10.48550/arXiv.2606.06310},
|
|
146
|
+
url = {https://arxiv.org/abs/2606.06310}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT. See [LICENSE](https://github.com/nkershaw01/RedZeD/blob/main/python/LICENSE).
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# redzed-tda
|
|
2
|
+
|
|
3
|
+
Fast Vietoris–Rips persistent homology, implemented in C++ with a NumPy interface.
|
|
4
|
+
|
|
5
|
+
This is the Python package for **RedZeD**, an algorithm to compute Vietoris-Rips
|
|
6
|
+
persistent homology. A key technique is *active enumeration*, allowing one to avoid enumerating
|
|
7
|
+
almost all (n+1) birth simplices in a filtration.
|
|
8
|
+
RedZeD often gives a considerable improvement in both time and memory over
|
|
9
|
+
existing implementations. Full details are in the paper:
|
|
10
|
+
|
|
11
|
+
> Chris Kapulkin and Nathan Kershaw.
|
|
12
|
+
> *RedZeD: Computing persistent homology by Reduction to Zero Differentials.*
|
|
13
|
+
> arXiv:2606.06310 (2026). <https://arxiv.org/abs/2606.06310>
|
|
14
|
+
|
|
15
|
+
The [main repository](https://github.com/nkershaw01/RedZeD) also holds the
|
|
16
|
+
reference Julia implementation.
|
|
17
|
+
|
|
18
|
+
If you encounter any errors or bugs, please email nkershaw@uwo.ca.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install redzed-tda
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The distribution is `redzed-tda` and the import name is `redzed_tda`. (The name
|
|
27
|
+
`redzed` on PyPI belongs to an unrelated project.)
|
|
28
|
+
|
|
29
|
+
Wheels are provided for Linux, macOS and Windows, so no compiler is needed.
|
|
30
|
+
|
|
31
|
+
If no wheel matches your platform, or your Python version is newer than the last
|
|
32
|
+
release, pip falls back to building from source. That needs a C++17 compiler on
|
|
33
|
+
your machine. On Windows the compiler must be MSVC, not MinGW.
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import numpy as np
|
|
39
|
+
import redzed_tda as rz
|
|
40
|
+
|
|
41
|
+
points = np.random.default_rng(0).normal(size=(200, 3))
|
|
42
|
+
distances = rz.pairwise_distances(points)
|
|
43
|
+
|
|
44
|
+
h0, h1 = rz.rips(distances, maxdim=1)
|
|
45
|
+
|
|
46
|
+
print(h1) # (k, 2) array of [birth, death] rows
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Input
|
|
50
|
+
|
|
51
|
+
`rips` takes a **distance matrix, not coordinates**. If your data is an array of
|
|
52
|
+
points in R^n, either form works:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
# square (n, n) matrix
|
|
56
|
+
h0, h1 = rz.rips(rz.pairwise_distances(points), maxdim=1)
|
|
57
|
+
|
|
58
|
+
# condensed vector of n(n-1)/2 values, in scipy pdist order
|
|
59
|
+
from scipy.spatial.distance import pdist
|
|
60
|
+
h0, h1 = rz.rips(pdist(points), maxdim=1)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
pairwise_distances and pdist both take in an array of points and return the
|
|
64
|
+
distance matrix. You may also pass redzed_tda.rips a square matrix constructed
|
|
65
|
+
yourself. Only the lower triangle of a square input is read, so your matrix is
|
|
66
|
+
assumed symmetric with a zero diagonal. Passing a non-square array raises
|
|
67
|
+
`ValueError`, but symmetry is not checked, so the user is responsible for
|
|
68
|
+
ensuring the matrix is symmetric.
|
|
69
|
+
|
|
70
|
+
## Options
|
|
71
|
+
|
|
72
|
+
| Argument | Default | Meaning |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| `distances` | — | Square `(n, n)` matrix or condensed `n(n-1)/2` vector. |
|
|
75
|
+
| `maxdim` | `1` | Highest homology dimension. `maxdim=1` returns H0 and H1. |
|
|
76
|
+
| `threshold` | `inf` | Ignore simplices with greater diameter. |
|
|
77
|
+
| `simplex_pairs` | `False` | Also return the birth and death simplex associated to every interval. |
|
|
78
|
+
|
|
79
|
+
## Output
|
|
80
|
+
|
|
81
|
+
A list of `maxdim + 1` arrays. Entry `d` has shape `(k, 2)` and holds the
|
|
82
|
+
`[birth, death]` pairs in dimension `d`:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
diagrams = rz.rips(distances, maxdim=2)
|
|
86
|
+
h0, h1, h2 = diagrams
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
With `simplex_pairs=True` you get `(diagrams, simplices)` instead, where
|
|
90
|
+
`simplices[d][i]` is a `(birth_vertices, death_vertices)` tuple corresponding to
|
|
91
|
+
`diagrams[d][i]`. The death tuple is empty for an infinite interval.
|
|
92
|
+
|
|
93
|
+
## Building from source
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
git clone https://github.com/nkershaw01/RedZeD
|
|
97
|
+
cd RedZeD/python
|
|
98
|
+
pip install -e ".[test]"
|
|
99
|
+
pytest
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Requires a C++17 compiler. On Windows that must be MSVC.
|
|
103
|
+
|
|
104
|
+
## Citing
|
|
105
|
+
|
|
106
|
+
If you use this software in published work, please cite the paper:
|
|
107
|
+
|
|
108
|
+
```bibtex
|
|
109
|
+
@misc{kapulkin2026redzed,
|
|
110
|
+
title = {RedZeD: Computing persistent homology by Reduction to Zero Differentials},
|
|
111
|
+
author = {Kapulkin, Chris and Kershaw, Nathan},
|
|
112
|
+
year = {2026},
|
|
113
|
+
eprint = {2606.06310},
|
|
114
|
+
archivePrefix = {arXiv},
|
|
115
|
+
primaryClass = {cs.CG},
|
|
116
|
+
doi = {10.48550/arXiv.2606.06310},
|
|
117
|
+
url = {https://arxiv.org/abs/2606.06310}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT. See [LICENSE](https://github.com/nkershaw01/RedZeD/blob/main/python/LICENSE).
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Python bindings for redzed
|
|
2
|
+
|
|
3
|
+
#include <pybind11/pybind11.h>
|
|
4
|
+
#include <pybind11/numpy.h>
|
|
5
|
+
#include <pybind11/stl.h>
|
|
6
|
+
|
|
7
|
+
#include "redzed.cpp"
|
|
8
|
+
|
|
9
|
+
namespace py = pybind11;
|
|
10
|
+
|
|
11
|
+
typedef py::array_t<double, py::array::c_style | py::array::forcecast> DoubleArray;
|
|
12
|
+
|
|
13
|
+
// ------------------------------------------------------------
|
|
14
|
+
// Input conversion
|
|
15
|
+
// ------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
// Accepts either a square (n, n) matrix or a condensed 1-D vector in scipy's
|
|
18
|
+
// pdist order
|
|
19
|
+
static DistanceMatrix matrix_from_array(const DoubleArray& arr) {
|
|
20
|
+
DistanceMatrix D;
|
|
21
|
+
|
|
22
|
+
if (arr.ndim() == 2) {
|
|
23
|
+
if (arr.shape(0) != arr.shape(1))
|
|
24
|
+
throw std::invalid_argument("a 2-D distance matrix must be square, got shape (" +
|
|
25
|
+
std::to_string(arr.shape(0)) + ", " +
|
|
26
|
+
std::to_string(arr.shape(1)) + ")");
|
|
27
|
+
int nv = (int)arr.shape(0);
|
|
28
|
+
D.resize_lower(nv);
|
|
29
|
+
auto a = arr.unchecked<2>();
|
|
30
|
+
// only the lower triangle is read, so an asymmetric input is not an error here
|
|
31
|
+
for (int j = 1; j < nv; ++j) {
|
|
32
|
+
value_t* dst = D.mat.data() + DistanceMatrix::offset(0, j);
|
|
33
|
+
for (int i = 0; i < j; ++i) dst[i] = (value_t)a(j, i);
|
|
34
|
+
}
|
|
35
|
+
} else if (arr.ndim() == 1) {
|
|
36
|
+
int64_t m = (int64_t)arr.shape(0);
|
|
37
|
+
// invert m = nv(nv-1)/2
|
|
38
|
+
int64_t nv = (int64_t)((1.0 + std::sqrt(1.0 + 8.0 * (double)m)) / 2.0 + 0.5);
|
|
39
|
+
if (nv < 1 || nv * (nv - 1) / 2 != m)
|
|
40
|
+
throw std::invalid_argument("a condensed distance vector must hold n(n-1)/2 values, "
|
|
41
|
+
"and " + std::to_string(m) + " is not such a length");
|
|
42
|
+
D.resize_lower((int)nv);
|
|
43
|
+
auto a = arr.unchecked<1>();
|
|
44
|
+
int64_t k = 0;
|
|
45
|
+
for (int64_t i = 0; i < nv; ++i)
|
|
46
|
+
for (int64_t j = i + 1; j < nv; ++j)
|
|
47
|
+
D.mat[DistanceMatrix::offset((int)i, (int)j)] = (value_t)a((py::ssize_t)k++);
|
|
48
|
+
} else {
|
|
49
|
+
throw std::invalid_argument("expected a 2-D distance matrix or a 1-D condensed "
|
|
50
|
+
"distance vector, got a " + std::to_string(arr.ndim()) +
|
|
51
|
+
"-D array");
|
|
52
|
+
}
|
|
53
|
+
return D;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ------------------------------------------------------------
|
|
57
|
+
// Output conversion
|
|
58
|
+
// ------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
// one array of [birth, death] intervals per dimension
|
|
61
|
+
static py::array_t<double> pairs_to_array(const std::vector<std::pair<value_t, value_t>>& v) {
|
|
62
|
+
py::array_t<double> out({(py::ssize_t)v.size(), (py::ssize_t)2});
|
|
63
|
+
auto r = out.mutable_unchecked<2>();
|
|
64
|
+
for (size_t t = 0; t < v.size(); ++t) {
|
|
65
|
+
r((py::ssize_t)t, 0) = (double)v[t].first;
|
|
66
|
+
r((py::ssize_t)t, 1) = (double)v[t].second;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// (birth_vertices, death_vertices)
|
|
72
|
+
static py::list spx_to_list(const std::vector<SimplexPair>& v) {
|
|
73
|
+
py::list out;
|
|
74
|
+
for (const SimplexPair& sp : v)
|
|
75
|
+
out.append(py::make_tuple(py::cast(sp.first), py::cast(sp.second)));
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ------------------------------------------------------------
|
|
80
|
+
// Entry point
|
|
81
|
+
// ------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
static py::object rips_impl(const DoubleArray& distances, int maxdim, double threshold,
|
|
84
|
+
bool simplex_pairs) {
|
|
85
|
+
if (maxdim < 0)
|
|
86
|
+
throw std::invalid_argument("maxdim must be >= 0, got " + std::to_string(maxdim));
|
|
87
|
+
|
|
88
|
+
DistanceMatrix D = matrix_from_array(distances);
|
|
89
|
+
if (D.n < 1)
|
|
90
|
+
throw std::invalid_argument("need at least one point");
|
|
91
|
+
|
|
92
|
+
std::vector<std::vector<std::pair<value_t, value_t>>> pairs;
|
|
93
|
+
std::vector<std::vector<SimplexPair>> spx;
|
|
94
|
+
{
|
|
95
|
+
// the solver never touches Python, so other threads can run meanwhile
|
|
96
|
+
py::gil_scoped_release unlocked;
|
|
97
|
+
pairs = redzed(D, maxdim, (value_t)threshold, simplex_pairs,
|
|
98
|
+
simplex_pairs ? &spx : nullptr);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
py::list diagrams;
|
|
102
|
+
for (int d = 0; d <= maxdim; ++d) diagrams.append(pairs_to_array(pairs[d]));
|
|
103
|
+
if (!simplex_pairs) return diagrams;
|
|
104
|
+
|
|
105
|
+
py::list simplices;
|
|
106
|
+
for (int d = 0; d <= maxdim; ++d) simplices.append(spx_to_list(spx[d]));
|
|
107
|
+
return py::make_tuple(diagrams, simplices);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
PYBIND11_MODULE(_core, m) {
|
|
111
|
+
m.doc() = "Compiled core of redzed: Vietoris-Rips persistent homology.";
|
|
112
|
+
|
|
113
|
+
m.def("rips", &rips_impl,
|
|
114
|
+
py::arg("distances"),
|
|
115
|
+
py::arg("maxdim") = 1,
|
|
116
|
+
py::arg("threshold") = std::numeric_limits<double>::infinity(),
|
|
117
|
+
py::arg("simplex_pairs") = false,
|
|
118
|
+
"Compute Vietoris-Rips persistence from a distance matrix.");
|
|
119
|
+
}
|