Skip to content

Commit 64c7517

Browse files
authored
Convert to pyproject style (#167)
* Remove testing changes * Add robinhood to wheel build * Add debug verbosity to build * More debugging, decrease verbosity * Update debug statement * Include robinhood in source dist
1 parent 4b7be99 commit 64c7517

File tree

6 files changed

+105
-63
lines changed

6 files changed

+105
-63
lines changed

.github/workflows/build_and_deploy.yml

+10-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@ jobs:
1818
steps:
1919
- uses: actions/checkout@v4
2020

21+
- name: Clone robinhood
22+
run: |
23+
git clone https://github.com/martinus/robin-hood-hashing ripser/robinhood
24+
2125
- name: Build wheels
22-
uses: pypa/cibuildwheel@v2.19.1
26+
uses: pypa/cibuildwheel@v2.19.2
2327
env:
2428
CIBW_SKIP: "pp* cp36-* cp37-* *win_arm64 *_i686 *musllinux*"
29+
CIBW_BUILD_VERBOSITY: 1
2530

2631
- uses: actions/upload-artifact@v4
2732
with:
@@ -34,6 +39,10 @@ jobs:
3439
steps:
3540
- uses: actions/checkout@v4
3641

42+
- name: Clone robinhood
43+
run: |
44+
git clone https://github.com/martinus/robin-hood-hashing ripser/robinhood
45+
3746
- name: Build sdist
3847
run: pipx run build --sdist
3948

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ To be able to use `robin_hood` instead of STL, you only need to clone the reposi
6363
git clone https://github.com/martinus/robin-hood-hashing ripser/robinhood
6464
```
6565

66+
After cloning robinhood with the above command, install `ripser.py` with
67+
68+
```
69+
pip install -v -e .
70+
```
71+
72+
This will install a local, editable version of `ripser.py` with verbose output. In the verbose output,
73+
you will see confirmation that robinhood was found or not.
74+
6675
<sup>1</sup> The Python package is already compiled with `robin_hood` by default.
6776

6877
## Usage

pyproject.toml

+57-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,58 @@
11
[build-system]
2-
requires = ["setuptools", "wheel", "numpy", "cython"]
2+
requires = ["setuptools>=61.0", "wheel", "numpy", "Cython"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "ripser"
7+
dynamic = ["version"]
8+
description = "A Lean Persistent Homology Library for Python"
9+
readme = "README.md"
10+
authors = [
11+
{ name = "Chris Tralie", email = "chris.tralie@gmail.com" },
12+
{ name = "Nathaniel Saul", email = "nat@riverasaul.com" },
13+
]
14+
maintainers = [
15+
{ name = "Chris Tralie", email = "chris.tralie@gmail.com" },
16+
{ name = "Nathaniel Saul", email = "nat@riverasaul.com" },
17+
{ name = "Michael Catanzaro", email = "catanzaromj@pm.me" },
18+
]
19+
20+
dependencies = ["Cython", "numpy", "persim", "scipy", "scikit-learn"]
21+
22+
classifiers = [
23+
"Development Status :: 3 - Alpha",
24+
"Intended Audience :: Science/Research",
25+
"Intended Audience :: Education",
26+
"Intended Audience :: Financial and Insurance Industry",
27+
"Intended Audience :: Healthcare Industry",
28+
"Topic :: Scientific/Engineering :: Information Analysis",
29+
"Topic :: Scientific/Engineering :: Mathematics",
30+
"License :: OSI Approved :: MIT License",
31+
"Programming Language :: Python",
32+
]
33+
34+
keywords = [
35+
"topological data analysis",
36+
"persistent homology",
37+
"Rips filtration",
38+
"algebraic topology",
39+
"unsupervised learning",
40+
"persistence diagrams",
41+
]
42+
43+
[project.optional-dependencies]
44+
testing = ["pytest", "pytest-cov"]
45+
46+
docs = ["sktda_docs_config"]
47+
48+
examples = ["tadasets", "jupyter", "pillow"]
49+
50+
[tool.setuptools.packages.find]
51+
where = ["ripser"]
52+
53+
[project.urls]
54+
Homepage = "https://ripser.scikit-tda.org"
55+
Documentation = "https://ripser.scikit-tda.org"
56+
Repository = "https://github.com/scikit-tda/ripser.py"
57+
Issues = "https://github.com/scikit-tda/ripser.py/issues"
58+
Changelog = "https://github.com/scikit-tda/ripser.py/blob/master/RELEASE.txt"

ripser/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.6.8"
1+
__version__ = "0.6.9"

setup.py

+18-51
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import sys
21
import os
32
import platform
3+
import re
4+
import sys
45

5-
from setuptools import setup
6-
from setuptools.extension import Extension
6+
from setuptools import Extension, setup
77

88
# Ensure Cython is installed before we even attempt to install Ripser.py
99
try:
@@ -14,21 +14,16 @@
1414
print("copy from www.cython.org or install it with `pip install Cython`")
1515
sys.exit(1)
1616

17-
## Get version information from _version.py
18-
import re
1917

20-
VERSIONFILE = "ripser/_version.py"
21-
verstrline = open(VERSIONFILE, "rt").read()
22-
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
23-
mo = re.search(VSRE, verstrline, re.M)
24-
if mo:
25-
verstr = mo.group(1)
26-
else:
27-
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
28-
29-
# Use README.md as the package long description
30-
with open("README.md") as f:
31-
long_description = f.read()
18+
def get_version():
19+
VERSIONFILE = "ripser/_version.py"
20+
verstrline = open(VERSIONFILE, "rt").read()
21+
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
22+
mo = re.search(VSRE, verstrline, re.M)
23+
if mo:
24+
return mo.group(1)
25+
else:
26+
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
3227

3328

3429
class CustomBuildExtCommand(build_ext):
@@ -67,6 +62,9 @@ def run(self):
6762
# Robinhood
6863
robinhood_path = os.path.join("ripser", "robinhood")
6964
if os.path.isdir(robinhood_path):
65+
print(
66+
"\nFound local copy of robinhood! Using robinhood for ripser.py compilation.\n"
67+
)
7068
macros.extend([("USE_ROBINHOOD_HASHMAP", 1)])
7169

7270
robinhood_include_path = os.path.join("src", "include")
@@ -78,6 +76,8 @@ def run(self):
7876
extra_compile_args.extend(
7977
["-I" + os.path.join(robinhood_path, robinhood_include_path)]
8078
)
79+
else:
80+
print("Did not find a local copy of robinhood. Proceeding anyways.")
8181

8282
ext_modules = Extension(
8383
"pyRipser",
@@ -90,40 +90,7 @@ def run(self):
9090

9191

9292
setup(
93-
name="ripser",
94-
version=verstr,
95-
description="A Lean Persistent Homology Library for Python",
96-
long_description=long_description,
97-
long_description_content_type="text/markdown",
98-
author="Chris Tralie, Nathaniel Saul",
99-
author_email="chris.tralie@gmail.com, nat@riverasaul.com",
100-
url="https://ripser.scikit-tda.org",
101-
license="MIT",
102-
packages=["ripser"],
10393
ext_modules=cythonize(ext_modules),
104-
install_requires=["Cython", "numpy", "scipy", "scikit-learn", "persim"],
105-
extras_require={
106-
"testing": [ # `pip install -e ".[testing]"``
107-
"pytest"
108-
],
109-
"docs": [ # `pip install -e ".[docs]"`
110-
"sktda_docs_config"
111-
],
112-
"examples": ["persim", "tadasets", "jupyter", "pillow"],
113-
},
11494
cmdclass={"build_ext": CustomBuildExtCommand},
115-
python_requires=">=3.6",
116-
classifiers=[
117-
"Intended Audience :: Science/Research",
118-
"Intended Audience :: Education",
119-
"Intended Audience :: Financial and Insurance Industry",
120-
"Intended Audience :: Healthcare Industry",
121-
"Topic :: Scientific/Engineering :: Information Analysis",
122-
"Topic :: Scientific/Engineering :: Mathematics",
123-
"License :: OSI Approved :: MIT License",
124-
"Programming Language :: Python :: 3.6",
125-
"Programming Language :: Python :: 3.7",
126-
"Programming Language :: Python :: 3.8",
127-
],
128-
keywords="persistent homology, rips filtration, persistence diagrams, topology data analysis, algebraic topology, unsupervised learning",
95+
version=get_version(),
12996
)

test/test_ripser.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class TestLibrary:
2626
def test_import(self):
2727
import ripser
2828
from ripser import ripser, Rips
29+
2930
assert 1
3031

3132

@@ -193,7 +194,7 @@ def test_greedyperm_circlebottleneck(self):
193194
h12 = res2["dgms"][1]
194195
assert res2["r_cover"] > 0
195196
assert np.max(np.abs(h11 - h12)) <= 2 * res2["r_cover"]
196-
197+
197198
def test_cocycle_indices_greedyperm(self):
198199
"""
199200
Make sure the original vertex indices are retained in the
@@ -206,26 +207,26 @@ def test_cocycle_indices_greedyperm(self):
206207
t = 2 * np.pi * np.random.rand(N)
207208
X = np.array([np.cos(t), np.sin(t)]).T
208209
res1 = ripser(X, n_perm=n_perm, do_cocycles=True, maxdim=1)
209-
cocycles1 = res1['cocycles'][1]
210-
idx_perm = res1['idx_perm']
210+
cocycles1 = res1["cocycles"][1]
211+
idx_perm = res1["idx_perm"]
211212
X = X[idx_perm, :]
212213
res2 = ripser(X, do_cocycles=True, maxdim=1)
213-
cocycles2 = res2['cocycles'][1]
214+
cocycles2 = res2["cocycles"][1]
214215
for cc1, cc2 in zip(cocycles1, cocycles2):
215216
assert cc1.shape[0] == cc2.shape[0]
216217
cc2[:, 0:-1] = idx_perm[cc2[:, 0:-1]]
217218
assert np.allclose(cc1, cc2)
218-
219+
219220
def test_sparse_format(self):
220221
"""
221222
Test to make sure different formats for sparse matrices
222223
yield the same answer. Test courtesy of Umberto Lupo (@ulupo)
223224
"""
224-
data = np.array([6., 8., 2., 4., 5., 9., 10., 3., 1., 1.])
225+
data = np.array([6.0, 8.0, 2.0, 4.0, 5.0, 9.0, 10.0, 3.0, 1.0, 1.0])
225226
row = np.array([0, 0, 0, 0, 1, 1, 1, 2, 2, 3])
226227
col = np.array([4, 1, 3, 2, 4, 3, 2, 3, 4, 4])
227228
dm = sparse.coo_matrix((data, (row, col)), shape=(5, 5))
228-
dgms1 = ripser(dm, distance_matrix=True)['dgms']
229-
dgms2 = ripser(dm.tocsr(), distance_matrix=True)['dgms']
229+
dgms1 = ripser(dm, distance_matrix=True)["dgms"]
230+
dgms2 = ripser(dm.tocsr(), distance_matrix=True)["dgms"]
230231
for dgm1k, dgm2k in zip(dgms1, dgms2):
231-
assert(np.allclose(dgm1k, dgm2k))
232+
assert np.allclose(dgm1k, dgm2k)

0 commit comments

Comments
 (0)