From d7677b1f3f5ceee9cfdb5275ef00cbf79a714122 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Mon, 7 Mar 2016 13:51:48 -0800 Subject: Python interface to PHAT --- .gitignore | 13 ++ README.md | 4 +- include/phat/boundary_matrix.h | 8 +- python/.gitignore | 7 ++ python/MANIFEST.in | 1 + python/README.rst | 110 ++++++++++++++++ python/_phat.cpp | 279 +++++++++++++++++++++++++++++++++++++++++ python/phat.py | 88 +++++++++++++ python/setup.cfg | 5 + python/setup.py | 57 +++++++++ python/src/self_test.py | 168 +++++++++++++++++++++++++ python/src/simple_example.py | 59 +++++++++ src/simple_example.cpp | 2 +- 13 files changed, 796 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 python/.gitignore create mode 100644 python/MANIFEST.in create mode 100644 python/README.rst create mode 100644 python/_phat.cpp create mode 100644 python/phat.py create mode 100644 python/setup.cfg create mode 100644 python/setup.py create mode 100644 python/src/self_test.py create mode 100644 python/src/simple_example.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e05a4ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +GPATH +GRTAGS +GTAGS +CMakeCache.txt +self_test +simple_example +phat +info +convert +cmake_install.cmake +benchmark +Makefile +CMakeFiles \ No newline at end of file diff --git a/README.md b/README.md index b953afa..9a30ffa 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Ulrich Bauer, Michael Kerber, Jan Reininghaus ## Contributors: ## -Hubert Wagner +Hubert Wagner, Bryn Keller ## Downloads: ## * [PHAT, v1.4.0](https://drive.google.com/uc?id=0B7Yz6TPEpiGEWmNyeVFsNXgtUGc&export=download) @@ -138,4 +138,4 @@ References: 1. H.Edelsbrunner, J.Harer: Computational Topology, An Introduction. American Mathematical Society, 2010, ISBN 0-8218-4925-5 2. V.de Silva, D.Morozov, M.Vejdemo-Johansson: Dualities in persistent (co)homology. Inverse Problems 27, 2011 3. C.Chen, M.Kerber: Persistent Homology Computation With a Twist. 27th European Workshop on Computational Geometry, 2011. -4. U.Bauer, M.Kerber, J.Reininghaus: Clear and Compress: Computing Persistent Homology in Chunks. [http://arxiv.org/pdf/1303.0477.pdf arXiv:1303.0477] \ No newline at end of file +4. U.Bauer, M.Kerber, J.Reininghaus: Clear and Compress: Computing Persistent Homology in Chunks. [http://arxiv.org/pdf/1303.0477.pdf arXiv:1303.0477] diff --git a/include/phat/boundary_matrix.h b/include/phat/boundary_matrix.h index 10c66cc..f864dee 100644 --- a/include/phat/boundary_matrix.h +++ b/include/phat/boundary_matrix.h @@ -68,7 +68,7 @@ namespace phat { // finalizes given column void finalize( index idx ) { rep._finalize( idx ); } - // syncronizes all internal data structures -- has to be called before and after any multithreaded access! + // synchronizes all internal data structures -- has to be called before and after any multithreaded access! void sync() { rep._sync(); } // info functions -- independent of chosen 'Representation' @@ -283,12 +283,16 @@ namespace phat { // Loads boundary_matrix from given file // Format: nr_columns % dim1 % N1 % row1 row2 % ...% rowN1 % dim2 % N2 % ... - bool load_binary( std::string filename ) + bool load_binary(const std::string &filename ) { std::ifstream input_stream( filename.c_str( ), std::ios_base::binary | std::ios_base::in ); if( input_stream.fail( ) ) return false; + //TODO Note that if you read a file that isn't actually a data file, you may get + //a number of columns that is bigger than the available memory, which leads to crashes + //with deeply confusing error messages. Consider ways to prevent this. + //Magic number in the file header? Check for more columns than bytes in the file? int64_t nr_columns; input_stream.read( (char*)&nr_columns, sizeof( int64_t ) ); this->set_num_cols( (index)nr_columns ); diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 0000000..b576037 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,7 @@ +__pycache__ +*.so +build +dist +old +phat.egg-info +pybind11 diff --git a/python/MANIFEST.in b/python/MANIFEST.in new file mode 100644 index 0000000..dba0deb --- /dev/null +++ b/python/MANIFEST.in @@ -0,0 +1 @@ +recursive-include include *.h \ No newline at end of file diff --git a/python/README.rst b/python/README.rst new file mode 100644 index 0000000..b67ff47 --- /dev/null +++ b/python/README.rst @@ -0,0 +1,110 @@ +Persistent Homology Algorithm Toolkit (PHAT) +============================================ + +This is a Python interface for the `Persistent Homology Algorithm Toolkit`_, a software library +that contains methods for computing the persistence pairs of a +filtered cell complex represented by an ordered boundary matrix with Z\ :sub:`2` coefficients. + +For an introduction to persistent homology, see the textbook [1]_. This software package +contains code for several algorithmic variants: + +* The "standard" algorithm (see [1]_, p.153) +* The "row" algorithm from [2]_ (called pHrow in that paper) +* The "twist" algorithm, as described in [3]_ (default algorithm) +* The "chunk" algorithm presented in [4]_ +* The "spectral sequence" algorithm (see [1]_, p.166) + +All but the standard algorithm exploit the special structure of the boundary matrix +to take shortcuts in the computation. The chunk and the spectral sequence algorithms +make use of multiple CPU cores if PHAT is compiled with OpenMP support. + +All algorithms are implemented as function objects that manipulate a given +``boundary_matrix`` (to be defined below) object to reduced form. +From this reduced form one can then easily extract the persistence pairs. +Alternatively, one can use the ``compute_persistence_pairs function`` which takes an +algorithm as a parameter, reduces the given ``boundary_matrix`` and stores the +resulting pairs in a given ``persistence_pairs`` object. + +The ``boundary_matrix`` class takes a "Representation" class as a parameter. +This representation defines how columns of the matrix are represented and how +low-level operations (e.g., column additions) are performed. The right choice of the +representation class can be as important for the performance of the program as choosing +the algorithm. We provide the following choices of representation classes: + +* ``vector_vector``: Each column is represented as a sorted ``std::vector`` of integers, containing the indices of the non-zero entries of the column. The matrix itself is a ``std::vector`` of such columns. +* ``vector_heap``: Each column is represented as a heapified ``std::vector`` of integers, containing the indices of the non-zero entries of the column. The matrix itself is a ``std::vector`` of such columns. +* ``vector_set``: Each column is a ``std::set`` of integers, with the same meaning as above. The matrix is stored as a ``std::vector`` of such columns. +* ``vector_list``: Each column is a sorted ``std::list`` of integers, with the same meaning as above. The matrix is stored as a ``std::vector`` of such columns. +* ``sparse_pivot_column``: The matrix is stored as in the vector_vector representation. However, when a column is manipulated, it is first converted into a ``std::set``, using an extra data field called the "pivot column". When another column is manipulated later, the pivot column is converted back to the ``std::vector`` representation. This can lead to significant speed improvements when many columns are added to a given pivot column consecutively. In a multicore setup, there is one pivot column per thread. +* ``heap_pivot_column``: The same idea as in the sparse version. Instead of a ``std::set``, the pivot column is represented by a ``std::priority_queue``. +* ``full_pivot_column``: The same idea as in the sparse version. However, instead of a ``std::set``, the pivot column is expanded into a bit vector of size n (the dimension of the matrix). To avoid costly initializations, the class remembers which entries have been manipulated for a pivot column and updates only those entries when another column becomes the pivot. +* ``bit_tree_pivot_column`` (default representation): Similar to the ``full_pivot_column`` but the implementation is more efficient. Internally it is a bit-set with fast iteration over nonzero elements, and fast access to the maximal element. + +Sample usage +------------ + +From src/simple_example.py in the source:: + + print(""" + we will build an ordered boundary matrix of this simplicial complex consisting of a single triangle: + + 3 + |\\ + | \\ + | \\ + | \\ 4 + 5| \\ + | \\ + | 6 \\ + | \\ + |________\\ + 0 2 1 + + """) + + import phat + + # set the dimension of the cell that each column represents: + dimensions = [0, 0, 1, 0, 1, 1, 2] + + # define a boundary matrix with the chosen internal representation + boundary_matrix = phat.boundary_matrix(representation = phat.representations.vector_vector) + + # set the respective columns -- the columns entries have to be sorted + boundary_matrix.set_dims(dimensions) + boundary_matrix.set_col(0, []) + boundary_matrix.set_col(1, []) + boundary_matrix.set_col(2, [0,1]) + boundary_matrix.set_col(3, []) + boundary_matrix.set_col(4, [1,3]) + boundary_matrix.set_col(5, [0,3]) + boundary_matrix.set_col(6, [2,4,5]) + + # print some information of the boundary matrix: + print() + print("the boundary matrix has %d columns:" % boundary_matrix.get_num_cols()) + for col_idx in range(boundary_matrix.get_num_cols()): + s = "column %d represents a cell of dimension %d." % (col_idx, boundary_matrix.get_dim(col_idx)) + if (not boundary_matrix.is_empty(col_idx)): + s = s + " its boundary consists of the cells " + " ".join([str(c) for c in boundary_matrix.get_col(col_idx)]) + print(s) + print("overall, the boundary matrix has %d entries." % boundary_matrix.get_num_entries()) + + pairs = phat.compute_persistence_pairs(boundary_matrix) + + pairs.sort() + + print() + + print("there are %d persistence pairs: " % len(pairs)) + for pair in pairs: + print("birth: %d, death: %d" % pair) + +References: + +.. [1] H.Edelsbrunner, J.Harer: Computational Topology, An Introduction. American Mathematical Society, 2010, ISBN 0-8218-4925-5 +.. [2] V.de Silva, D.Morozov, M.Vejdemo-Johansson: Dualities in persistent (co)homology. Inverse Problems 27, 2011 +.. [3] C.Chen, M.Kerber: Persistent Homology Computation With a Twist. 27th European Workshop on Computational Geometry, 2011. +.. [4] U.Bauer, M.Kerber, J.Reininghaus: Clear and Compress: Computing Persistent Homology in Chunks. arXiv:1303.0477_ +.. _arXiv:1303.0477: http://arxiv.org/pdf/1303.0477.pdf +.. _`Persistent Homology Algorithm Toolkit`: https://bitbucket.org/phat/phat-code diff --git a/python/_phat.cpp b/python/_phat.cpp new file mode 100644 index 0000000..c43a3f5 --- /dev/null +++ b/python/_phat.cpp @@ -0,0 +1,279 @@ +//Required header for using pybind11 +#include + +//Automatic conversions of stl containers to Python ones +#include + +//Additional support for operators and numpy +#include +#include + +//All the things we're going to wrap +#include "phat/persistence_pairs.h" +#include "phat/compute_persistence_pairs.h" +#include "phat/boundary_matrix.h" +#include "phat/representations/abstract_pivot_column.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace py = pybind11; + +//## Some template functions we'll need later + +// This function defines two Python functions in the extension module, that are named +// `compute_persistence_pairs_${rep}_${reduction}` +// `compute_persistence_pairs_dualized_${rep}_${reductionx}`. +// +// The Python user will never see these, since we will use (in phat.py) the type of the +// boundary matrix and the requested reduction to dispatch to the correct function +// required. +// +// These functions are the main operations of PHAT. In the Python version, they take +// a boundary matrix, and return a persistence_pairs object. +// +// `Reduction` will be an algorithm, `Representation` is a type that controls +// how the boundary matrix stores its internal state. +// +// We will be using this function to define these two functions for every combination +// of `Representation` and `Reduction` that PHAT supports. +template +void define_compute_persistence(py::module &mod, + const std::string &representation_suffix, + const std::string &reduction_suffix) { + + auto suffix = representation_suffix + std::string("_") + reduction_suffix; + + //We don't annotate these with doc strings or py::args because + //they are only used internally by code in phat.py + mod.def((std::string("compute_persistence_pairs_") + suffix).c_str(), + [](phat::boundary_matrix &matrix){ + phat::persistence_pairs pairs; + phat::compute_persistence_pairs(pairs, matrix); + return pairs; + }); + mod.def((std::string("compute_persistence_pairs_dualized_") + suffix).c_str(), + [](phat::boundary_matrix &matrix){ + phat::persistence_pairs pairs; + phat::compute_persistence_pairs_dualized(pairs, matrix); + return pairs; + }); +} + +// Define a function to convert a `boundary_matrix` with one internal representation to a +// `boundary_matrix` with a different internal representation. Like with define_compute_persistence, +// the user will never see this function, but it is used internally by phat.py. +template +void define_converter(py::module &mod, const std::string &self_suffix, const std::string &other_suffix) { + //We don't annotate these with doc strings or py::args because + //they are only used internally by code in phat.py + mod.def((std::string("convert_") + other_suffix + "_to_" + self_suffix).c_str(), + [](phat::boundary_matrix &other) { + return phat::boundary_matrix(other); + }); +} + +// Creates a Python class for a `boundary_matrix`. Boundary matrices are one of two important types +// used by PHAT. +template +void wrap_boundary_matrix(py::module &mod, const std::string &representation_suffix) { + + using mat = phat::boundary_matrix; + + py::class_(mod, (std::string("boundary_matrix_") + representation_suffix).c_str()) + //Default no-args constructor + .def(py::init()) + //#### Loading and extracting data from Python lists + //Note we can use references to member functions (even templated ones) directly in many cases. + .def("load_vector_vector", + &mat::template load_vector_vector, + "Load this instance with the given columns and dimensions", + py::arg("columns"), py::arg("dimensions")) + .def("get_vector_vector", [](mat &m) { + std::vector< std::vector< int > > vector_vector_matrix; + std::vector< int > vector_dims; + m.save_vector_vector( vector_vector_matrix, vector_dims ); + return std::tuple>, std::vector>(vector_vector_matrix, vector_dims); + }, + "Extract the data in the boundary matrix into a list of columns, and a list of dimensions that correspond to the columns") + //#### Loading and saving files + .def("load_binary", &mat::load_binary, + "Load this instance with data from a binary file") + .def("save_binary", &mat::save_binary, + "Save this instance to a binary file") + .def("load_ascii", &mat::load_ascii, + "Load this instance with data from a text file") + .def("save_ascii", &mat::save_ascii, + "Save this instance to a text file") + //#### Getting and setting dimensions + //Note that boundary_matrix dimensions are not normal matrix dimensions, + //They refer to the dimension of the simplex stored in the given column. + .def("get_dim", &mat::get_dim, + "Get the dimension list for this boundary matrix. " + "The dimension list shows the dimension for each column in the matrix") + .def("set_dim", &mat::set_dim, "Set the dimension for a single column", + py::arg("index"), py::arg("dimension")) + //The `set_dims` method is an example of making the data structure easier to use + //from Python. This is a method that doesn't exist in the C++ class, but we add it + //using a C++ lambda. This ability to enhance the binding using lambdas + //is an *extremely* handy tool. + .def("set_dims", [](mat &m, std::vector dims) { + m.set_num_cols(dims.size()); + for(size_t i = 0; i < dims.size(); i++) { + m.set_dim(i, dims[i]); + } + }, + "Set the dimension list for this boundary matrix", + py::arg("dimensions")) + + //#### \__eq__ + //The `boundary_matrix`'s `operator==` is templated, which could make a Python wrapper + //very tricky indeed. Luckily, when we define multiple + //methods with the same name but different C++ types, pybind11 will create a Python method + //that chooses between them based on type tags that it manages. This is *also* extremely handy. + .def("__eq__", &mat::template operator==) + .def("__eq__", &mat::template operator==) + .def("__eq__", &mat::template operator==) + .def("__eq__", &mat::template operator==) + .def("__eq__", &mat::template operator==) + .def("__eq__", &mat::template operator==) + .def("__eq__", &mat::template operator==) + .def("__eq__", &mat::template operator==) + + //#### Data access + + // In `get_col`, since Python is garbage collected, the C++ idiom of passing in a collection + // to load doesn't make much sense. We can simply allocate an STL vector and + // return it. The pybind11 framework will take ownership and hook it into the + // Python reference counting system. + .def("get_col", [](mat &m, phat::index col_index) { + std::vector col; + m.get_col(col_index, col); + return col; + }, + "Extract a single column as a list", + py::arg("index")) + .def("set_col", &mat::set_col, + "Set the values for a given column", + py::arg("index"), py::arg("column")) + .def("get_num_cols", &mat::get_num_cols) + .def("is_empty", &mat::is_empty) + .def("get_num_entries", &mat::get_num_entries); + + //#### Compute persistence + // Define compute_persistence(_dualized) for all possible reductions. + define_compute_persistence(mod, representation_suffix, std::string("sr")); + define_compute_persistence(mod, representation_suffix, std::string("cr")); + define_compute_persistence(mod, representation_suffix, std::string("rr")); + define_compute_persistence(mod, representation_suffix, std::string("tr")); + define_compute_persistence(mod, representation_suffix, std::string("ssr")); + //#### Converters + //Define functions to convert from this kind of `boundary_matrix` to any of the other types + define_converter(mod, representation_suffix, std::string("btpc")); + define_converter(mod, representation_suffix, std::string("spc")); + define_converter(mod, representation_suffix, std::string("hpc")); + define_converter(mod, representation_suffix, std::string("fpc")); + define_converter(mod, representation_suffix, std::string("vv")); + define_converter(mod, representation_suffix, std::string("vh")); + define_converter(mod, representation_suffix, std:: string("vs")); + define_converter(mod, representation_suffix, std::string("vl")); +} +//fix_index checks for out-of-bounds indexes, and converts negative indices to positive ones +//e.g. pairs[-1] => pairs[len(pairs) - 1] +phat::index fix_index(const phat::persistence_pairs &p, int index) { + //Note get_num_pairs returns type index, which is not unsigned, though it comes from + //std::vector.size, which is size_t. + phat::index pairs = p.get_num_pairs(); + assert(pairs > 0); + if (index < 0) { + index = pairs + index; + } + if ((index < 0) || static_cast(index) >= static_cast(pairs)) { + //pybind11 helpfully converts C++ exceptions into Python ones + throw py::index_error(); + } + return index; +} + +//Here we define the wrapper for the persistence_pairs class. Unlike `boundary_matrix`, this +//class is not templated, so is simpler to wrap. +void wrap_persistence_pairs(py::module &m) { + py::class_(m, "persistence_pairs") + //No-args constructor + .def(py::init()) + + //This is a method that takes two ints + .def("append_pair", + &phat::persistence_pairs::append_pair, + "Appends a single (birth, death) pair", + py::arg("birth"), py::arg("death")) + + //This is a method that takes two ints + .def("set_pair", + &phat::persistence_pairs::set_pair, + "Sets the (birth, death) pair at a given index", + py::arg("index"), py::arg("birth"), py::arg("death")) + + //#### Python collection support + .def("__len__", &phat::persistence_pairs::get_num_pairs) + // Unlike set_pair, this takes a Python 2-tuple + .def("__setitem__", + [](phat::persistence_pairs &p, int index, std::pair &pair) { + phat::index idx = fix_index(p, index); + p.set_pair(idx, pair.first, pair.second); + }) + // \__len\__ and \__getitem\__ together serve to make this a Python iterable + // so you can do `for i in pairs: blah`. A nicer way is to support \__iter\__, + // which we leave for future work. + .def("__getitem__", [](const phat::persistence_pairs &p, int index) { + phat::index idx = fix_index(p, index); + return p.get_pair(idx); + }) + .def("clear", &phat::persistence_pairs::clear, "Empties the collection") + .def("sort", &phat::persistence_pairs::sort, "Sort in place") + .def("__eq__", &phat::persistence_pairs::operator==) + //#### File operations + .def("load_ascii", &phat::persistence_pairs::load_ascii, + "Load the contents of a text file into this instance") + .def("save_ascii", &phat::persistence_pairs::save_ascii, + "Save this instance to a text file") + .def("save_binary", &phat::persistence_pairs::save_binary, + "Save the contents of this instance to a binary file") + .def("load_binary", &phat::persistence_pairs::load_binary, + "Load the contents of a binary file into this instance"); +} + +//## Define the module +//This is where we actually define the `_phat` module. We'll also have a `phat` module that's written +//in Python, which will use `_phat` as an implementation detail. +PYBIND11_PLUGIN(_phat) { + //Create the module object. First arg is the name, second is the module docstring. + py::module m("_phat", "C++ wrapper for PHAT. Please use the phat module, not the _phat module"); + + //Wrap the `persistence_pairs` class + wrap_persistence_pairs(m); + + //#### Generate all the different representations of `boundary_matrix` + wrap_boundary_matrix(m, "btpc"); + wrap_boundary_matrix(m, "spc"); + wrap_boundary_matrix(m, "hpc"); + wrap_boundary_matrix(m, "fpc"); + wrap_boundary_matrix(m, "vv"); + wrap_boundary_matrix(m, "vh"); + wrap_boundary_matrix(m, "vs"); + wrap_boundary_matrix(m, "vl"); + + //We're done! + return m.ptr(); + +} diff --git a/python/phat.py b/python/phat.py new file mode 100644 index 0000000..7cc0ae2 --- /dev/null +++ b/python/phat.py @@ -0,0 +1,88 @@ +import _phat +import enum + +from _phat import persistence_pairs + +__all__ = ['boundary_matrix', + 'persistence_pairs', + 'compute_persistence_pairs', + 'compute_persistence_pairs_dualized'] + + +"""Bindings for the Persistent Homology Algorithm Toolbox + + +Please see https://bitbucket.org/phat-code/phat for more information. +""" + + +class representations(enum.Enum): + """Available representations for internal storage of columns in + a `boundary_matrix` + """ + bit_tree_pivot_column = 1 + sparse_pivot_column = 2 + full_pivot_column = 3 + vector_vector = 4 + vector_heap = 5 + vector_set = 6 + vector_list = 7 + +class reductions(enum.Enum): + "Available reduction algorithms" + twist_reduction = 1 + chunk_reduction = 2 + standard_reduction = 3 + row_reduction = 4 + spectral_sequence_reduction = 5 + +def __short_name(name): + return "".join([n[0] for n in name.split("_")]) + +def convert(source, to_representation): + """Internal - function to convert from one `boundary_matrix` implementation to another""" + class_name = source.__class__.__name__ + source_rep_short_name = class_name[len('boundary_matrix_'):] + to_rep_short_name = __short_name(to_representation.name) + function = getattr(_phat, "convert_%s_to_%s" % (source_rep_short_name, to_rep_short_name)) + return function(source) + +def boundary_matrix(representation = representations.bit_tree_pivot_column, source = None): + """Returns an instance of a `boundary_matrix` class. + The boundary matrix will use the specified implementation for storing its column data. + If the `source` parameter is specified, it will be assumed to be another boundary matrix, + whose data should be copied into the new matrix. + """ + if source: + return convert(source, representation) + else: + class_name = representation.name + short_name = __short_name(class_name) + function = getattr(_phat, "boundary_matrix_" + short_name) + return function() + +def compute_persistence_pairs(matrix, + reduction = reductions.twist_reduction): + """Computes persistence pairs (birth, death) for the given boundary matrix.""" + class_name = matrix.__class__.__name__ + representation_short_name = class_name[len('boundary_matrix_'):] + algo_name = reduction.name + algo_short_name = __short_name(algo_name) + function = getattr(_phat, "compute_persistence_pairs_" + representation_short_name + "_" + algo_short_name) + return function(matrix) + +def compute_persistence_pairs_dualized(matrix, + reduction = reductions.twist_reduction): + """Computes persistence pairs (birth, death) from the dualized form of the given boundary matrix.""" + class_name = matrix.__class__.__name__ + representation_short_name = class_name[len('boundary_matrix_'):] + algo_name = reduction.name + algo_short_name = __short_name(algo_name) + function = getattr(_phat, "compute_persistence_pairs_dualized_" + representation_short_name + "_" + algo_short_name) + return function(matrix) + + + + + + diff --git a/python/setup.cfg b/python/setup.cfg new file mode 100644 index 0000000..458847f --- /dev/null +++ b/python/setup.cfg @@ -0,0 +1,5 @@ +# [bdist_wheel] +# This flag says that the code is written to work on both Python 2 and Python +# 3. If at all possible, it is good practice to do this. If you cannot, you +# will need to generate wheels for each Python version that you support. +# universal=1 \ No newline at end of file diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..5e71cc7 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,57 @@ +from setuptools import setup, Extension, find_packages +from setuptools.command.build_ext import build_ext +import sys +import os.path +ext_modules = [ + Extension( + '_phat', + ['_phat.cpp'], + include_dirs=['include', '../include'], + language='c++', + ), +] + +here = os.path.abspath(os.path.dirname(__file__)) + +# Get the long description from the README file +with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: + long_description = f.read() + +class BuildExt(build_ext): + """A custom build extension for adding compiler-specific options.""" + c_opts = { + 'msvc': ['/EHsc'], + 'unix': ['-std=c++11'], + } + + if sys.platform == 'darwin': + c_opts['unix'] += ['-stdlib=libc++', '-mmacosx-version-min=10.7'] + + def build_extensions(self): + ct = self.compiler.compiler_type + opts = self.c_opts.get(ct, []) + for ext in self.extensions: + ext.extra_compile_args = opts + build_ext.build_extensions(self) + +setup( + name='phat', + version='0.0.1', + author='Bryn Keller', + author_email='bryn.keller@intel.com', + url='https://bitbucket.org/phat-code/phat', + description='Python bindings for PHAT', + license = 'LGPL', + keywords='algebraic-topology PHAT distributed topology persistent-homology', + long_description=long_description, + ext_modules=ext_modules, + install_requires=['pybind11'], + cmdclass={'build_ext': BuildExt}, + py_modules = ['phat'], + # packages = find_packages(exclude = ['doc', 'test']) + ) + + + + + diff --git a/python/src/self_test.py b/python/src/self_test.py new file mode 100644 index 0000000..c8174fb --- /dev/null +++ b/python/src/self_test.py @@ -0,0 +1,168 @@ +import sys +import phat + +if __name__=='__main__': + test_data = (sys.argv[1:] and sys.argv[1]) or "../../examples/torus.bin" + + print("Reading test data %s in binary format ..." % test_data) + + boundary_matrix = phat.boundary_matrix() + # This is broken for some reason + if not boundary_matrix.load_binary(test_data): + # if not boundary_matrix.load_ascii(test_data): + print("Error: test data %s not found!" % test_data) + sys.exit(1) + + error = False + + def compute_chunked(mat): + return phat.compute_persistence_pairs(mat, phat.reductions.chunk_reduction) + + print("Comparing representations using Chunk algorithm ...") + print("Running Chunk - Sparse ...") + sparse_boundary_matrix = phat.boundary_matrix(phat.representations.sparse_pivot_column, boundary_matrix) + sparse_pairs = compute_chunked(sparse_boundary_matrix) + + print("Running Chunk - Heap ...") + heap_boundary_matrix = phat.boundary_matrix(phat.representations.vector_heap, boundary_matrix) + heap_pairs = compute_chunked(heap_boundary_matrix) + + print("Running Chunk - Full ...") + full_boundary_matrix = phat.boundary_matrix(phat.representations.full_pivot_column, boundary_matrix) + full_pairs = compute_chunked(full_boundary_matrix) + + print("Running Chunk - BitTree ...") + bit_tree_boundary_matrix = phat.boundary_matrix(phat.representations.bit_tree_pivot_column, boundary_matrix) + bit_tree_pairs = compute_chunked(bit_tree_boundary_matrix) + + print("Running Chunk - Vec_vec ...") + vec_vec_boundary_matrix = phat.boundary_matrix(phat.representations.vector_vector, boundary_matrix) + vec_vec_pairs = compute_chunked(vec_vec_boundary_matrix) + + print("Running Chunk - Vec_heap ...") + vec_heap_boundary_matrix = phat.boundary_matrix(phat.representations.vector_heap, boundary_matrix) + vec_heap_pairs = compute_chunked(vec_heap_boundary_matrix) + + print("Running Chunk - Vec_set ...") + vec_set_boundary_matrix = phat.boundary_matrix(phat.representations.vector_set, boundary_matrix) + vec_set_pairs = compute_chunked(vec_set_boundary_matrix) + + print("Running Chunk - Vec_list ...") + vec_list_boundary_matrix = phat.boundary_matrix(phat.representations.vector_list, boundary_matrix) + vec_list_pairs = compute_chunked(vec_list_boundary_matrix) + + if sparse_pairs != heap_pairs: + print("Error: sparse and heap differ!", file=sys.stderr) + error = True + if heap_pairs != full_pairs: + print("Error: heap and full differ!", file=sys.stderr) + error = True + if full_pairs != vec_vec_pairs: + print("Error: full and vec_vec differ!", file=sys.stderr) + error = True + if vec_vec_pairs != vec_heap_pairs: + print("Error: vec_vec and vec_heap differ!", file=sys.stderr) + error = True + if vec_heap_pairs != vec_set_pairs: + print("Error: vec_heap and vec_set differ!", file=sys.stderr) + error = True + if vec_set_pairs != bit_tree_pairs: + print("Error: vec_set and bit_tree differ!", file=sys.stderr) + error = True + if bit_tree_pairs != vec_list_pairs: + print("Error: bit_tree and vec_list differ!", file=sys.stderr) + error = True + if vec_list_pairs != sparse_pairs: + print("Error: vec_list and sparse differ!", file=sys.stderr) + error = True + if error: + sys.exit(1) + else: + print("All results are identical (as they should be)") + + print("Comparing algorithms using BitTree representation ...") + print("Running Twist - BitTree ...") + + def bit_tree_mat(): + return phat.boundary_matrix(phat.representations.bit_tree_pivot_column, boundary_matrix) + + reps = phat.representations + reds = phat.reductions + pairs = phat.compute_persistence_pairs + + twist_boundary_matrix = bit_tree_mat() + twist_pairs = pairs(twist_boundary_matrix, reds.twist_reduction) + + print("Running Standard - BitTree ...") + std_boundary_matrix = bit_tree_mat() + std_pairs = pairs(std_boundary_matrix, reds.standard_reduction) + + print("Running Chunk - BitTree ...") + chunk_boundary_matrix = bit_tree_mat() + chunk_pairs = pairs(chunk_boundary_matrix, reds.chunk_reduction) + + print("Running Row - BitTree ...") + row_boundary_matrix = bit_tree_mat() + row_pairs = pairs(row_boundary_matrix, reds.row_reduction) + + print("Running Spectral sequence - BitTree ...") + ss_boundary_matrix = bit_tree_mat() + ss_pairs = pairs(ss_boundary_matrix, reds.spectral_sequence_reduction) + + if twist_pairs != std_pairs: + print("Error: twist and standard differ!", file=sys.stderr) + error = True + if std_pairs != chunk_pairs: + print("Error: standard and chunk differ!", file=sys.stderr) + error = True + if chunk_pairs != row_pairs: + print("Error: chunk and row differ!", file=sys.stderr) + error = True + if row_pairs != ss_pairs: + print("Error: row and spectral sequence differ!", file=sys.stderr) + error = True + if ss_pairs != twist_pairs: + print("Error: spectral sequence and twist differ!", file=sys.stderr) + error = True + if error: + sys.exit(1) + else: + print("All results are identical (as they should be)") + + print("Comparing primal and dual approach using Chunk - Full ...") + + primal_boundary_matrix = phat.boundary_matrix(reps.full_pivot_column, boundary_matrix) + primal_pairs = phat.compute_persistence_pairs(primal_boundary_matrix, reds.chunk_reduction) + + dual_boundary_matrix = phat.boundary_matrix(reps.full_pivot_column, boundary_matrix) + dual_pairs = phat.compute_persistence_pairs_dualized(dual_boundary_matrix) + + if primal_pairs != dual_pairs: + print("Error: primal and dual differ!", file=sys.stderr) + error = True + + + if error: + sys.exit(1) + else: + print("All results are identical (as they should be)") + + print("Testing vector interface ...") + + (vector_vector_matrix, vector_dims) = boundary_matrix.get_vector_vector() + + vector_vector_boundary_matrix = phat.boundary_matrix(phat.representations.bit_tree_pivot_column) + + vector_vector_boundary_matrix.load_vector_vector(vector_vector_matrix, vector_dims) + + if vector_vector_boundary_matrix != boundary_matrix: + print("Error: [load|save]_vector_vector bug", file=sys.stderr) + error = True + + + if error: + sys.exit(1) + else: + print("Test passed!") + + diff --git a/python/src/simple_example.py b/python/src/simple_example.py new file mode 100644 index 0000000..82cf6be --- /dev/null +++ b/python/src/simple_example.py @@ -0,0 +1,59 @@ +"""This file contains a simple example that demonstrates the usage of the library interface""" + +if __name__ == "__main__": + + print(""" + we will build an ordered boundary matrix of this simplicial complex consisting of a single triangle: + + 3 + |\\ + | \\ + | \\ + | \\ 4 + 5| \\ + | \\ + | 6 \\ + | \\ + |________\\ + 0 2 1 + +""") + + import phat + + # set the dimension of the cell that each column represents: + dimensions = [0, 0, 1, 0, 1, 1, 2] + + # define a boundary matrix with the chosen internal representation + boundary_matrix = phat.boundary_matrix(representation = phat.representations.vector_vector) + + # set the respective columns -- the columns entries have to be sorted + boundary_matrix.set_dims(dimensions) + boundary_matrix.set_col(0, []) + boundary_matrix.set_col(1, []) + boundary_matrix.set_col(2, [0,1]) + boundary_matrix.set_col(3, []) + boundary_matrix.set_col(4, [1,3]) + boundary_matrix.set_col(5, [0,3]) + boundary_matrix.set_col(6, [2,4,5]) + + # print some information of the boundary matrix: + print() + print("The boundary matrix has %d columns:" % boundary_matrix.get_num_cols()) + for col_idx in range(boundary_matrix.get_num_cols()): + s = "Column %d represents a cell of dimension %d." % (col_idx, boundary_matrix.get_dim(col_idx)) + if (not boundary_matrix.is_empty(col_idx)): + s = s + " Its boundary consists of the cells " + " ".join([str(c) for c in boundary_matrix.get_col(col_idx)]) + print(s) + print("Overall, the boundary matrix has %d entries." % boundary_matrix.get_num_entries()) + + pairs = phat.compute_persistence_pairs(boundary_matrix) + + pairs.sort() + + print() + + print("There are %d persistence pairs: " % len(pairs)) + for pair in pairs: + print("Birth: %d, Death: %d" % pair) + diff --git a/src/simple_example.cpp b/src/simple_example.cpp index aff3ff9..ba8aafe 100644 --- a/src/simple_example.cpp +++ b/src/simple_example.cpp @@ -96,7 +96,7 @@ int main( int argc, char** argv ) std::cout << std::endl; std::cout << "The boundary matrix has " << boundary_matrix.get_num_cols() << " columns: " << std::endl; for( phat::index col_idx = 0; col_idx < boundary_matrix.get_num_cols(); col_idx++ ) { - std::cout << "Colum " << col_idx << " represents a cell of dimension " << (int)boundary_matrix.get_dim( col_idx ) << ". "; + std::cout << "Column " << col_idx << " represents a cell of dimension " << (int)boundary_matrix.get_dim( col_idx ) << ". "; if( !boundary_matrix.is_empty( col_idx ) ) { std::vector< phat::index > temp_col; boundary_matrix.get_col( col_idx, temp_col ); -- cgit v1.2.3 From 816b21f9df89788669b15369c42b1c03eca39461 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Thu, 10 Mar 2016 16:04:37 -0800 Subject: Simplified, more Pythonic API, with additional documentation --- python/_phat.cpp | 3 +- python/phat.py | 318 ++++++++++++++++++++++++++++++++++++------- python/src/self_test.py | 16 +-- python/src/simple_example.py | 42 +++--- 4 files changed, 294 insertions(+), 85 deletions(-) diff --git a/python/_phat.cpp b/python/_phat.cpp index c43a3f5..40a9421 100644 --- a/python/_phat.cpp +++ b/python/_phat.cpp @@ -119,8 +119,7 @@ void wrap_boundary_matrix(py::module &mod, const std::string &representation_suf //Note that boundary_matrix dimensions are not normal matrix dimensions, //They refer to the dimension of the simplex stored in the given column. .def("get_dim", &mat::get_dim, - "Get the dimension list for this boundary matrix. " - "The dimension list shows the dimension for each column in the matrix") + "Get the dimension for the requested column.") .def("set_dim", &mat::set_dim, "Set the dimension for a single column", py::arg("index"), py::arg("dimension")) //The `set_dims` method is an example of making the data structure easier to use diff --git a/python/phat.py b/python/phat.py index 7cc0ae2..6537b26 100644 --- a/python/phat.py +++ b/python/phat.py @@ -1,20 +1,75 @@ -import _phat -import enum +"""Bindings for the Persistent Homology Algorithm Toolbox -from _phat import persistence_pairs +PHAT is a tool for algebraic topology. It can be used via phat.py to compute +persistent (co)homology from boundary matrices, using various reduction +algorithms and column data representations. -__all__ = ['boundary_matrix', - 'persistence_pairs', - 'compute_persistence_pairs', - 'compute_persistence_pairs_dualized'] +Here is a simple example of usage. +We will build an ordered boundary matrix of this simplicial complex consisting of a single triangle: -"""Bindings for the Persistent Homology Algorithm Toolbox + 3 + |\\ + | \\ + | \\ + | \\ 4 + 5| \\ + | \\ + | 6 \\ + | \\ + |________\\ + 0 2 1 + +Now the code:: + import phat -Please see https://bitbucket.org/phat-code/phat for more information. + # define a boundary matrix with the chosen internal representation + boundary_matrix = phat.boundary_matrix(representation = phat.representations.vector_vector) + + # set the respective columns -- (dimension, boundary) pairs + boundary_matrix.columns = [ (0, []), + (0, []), + (1, [0,1]), + (0, []), + (1, [1,3]), + (1, [0,3]), + (2, [2,4,5])] + + # or equivalently, boundary_matrix = phat.boundary_matrix(representation = ..., columns = ...) + # would combine the creation of the matrix and the assignment of the columns + + # print some information of the boundary matrix: + print("\nThe boundary matrix has %d columns:" % len(boundary_matrix.columns)) + for col in boundary_matrix.columns: + s = "Column %d represents a cell of dimension %d." % (col.index, col.dimension) + if (col.boundary): + s = s + " Its boundary consists of the cells " + " ".join([str(c) for c in col.boundary]) + print(s) + print("Overall, the boundary matrix has %d entries." % len(boundary_matrix)) + + pairs = boundary_matrix.compute_persistence_pairs() + + pairs.sort() + + print("\nThere are %d persistence pairs: " % len(pairs)) + for pair in pairs: + print("Birth: %d, Death: %d" % pair) + +Please see https://bitbucket.org/phat-code/phat/python for more information. """ +import _phat +import enum + +from _phat import persistence_pairs + +#The public API for the module + +__all__ = ['boundary_matrix', + 'persistence_pairs', + 'representations', + 'reductions'] class representations(enum.Enum): """Available representations for internal storage of columns in @@ -29,60 +84,221 @@ class representations(enum.Enum): vector_list = 7 class reductions(enum.Enum): - "Available reduction algorithms" + """Available reduction algorithms""" twist_reduction = 1 chunk_reduction = 2 standard_reduction = 3 row_reduction = 4 spectral_sequence_reduction = 5 -def __short_name(name): - return "".join([n[0] for n in name.split("_")]) +class column: + """A view on one column of data in a boundary matrix""" + def __init__(self, matrix, index): + """INTERNAL. Columns are created automatically by boundary matrices. + There is no need to construct them directly""" + self._matrix = matrix + self._index = index -def convert(source, to_representation): - """Internal - function to convert from one `boundary_matrix` implementation to another""" - class_name = source.__class__.__name__ - source_rep_short_name = class_name[len('boundary_matrix_'):] - to_rep_short_name = __short_name(to_representation.name) - function = getattr(_phat, "convert_%s_to_%s" % (source_rep_short_name, to_rep_short_name)) - return function(source) + @property + def index(self): + """The 0-based index of this column in its boundary matrix""" + return self._index + + @property + def dimension(self): + """The dimension of the column (0 = point, 1 = line, 2 = triangle, etc.)""" + return self._matrix._matrix.get_dim(self._index) + + @dimension.setter + def dimension(self, value): + return self._matrix._matrix.set_dim(self._index, value) -def boundary_matrix(representation = representations.bit_tree_pivot_column, source = None): - """Returns an instance of a `boundary_matrix` class. - The boundary matrix will use the specified implementation for storing its column data. - If the `source` parameter is specified, it will be assumed to be another boundary matrix, - whose data should be copied into the new matrix. + @property + def boundary(self): + """The boundary values in this column, i.e. the other columns that this column is bounded by""" + return self._matrix._matrix.get_col(self._index) + + @boundary.setter + def boundary(self, values): + return self._matrix._matrix.set_col(self._index, values) + +class boundary_matrix: + """Boundary matrices that store the shape information of a cell complex. """ - if source: - return convert(source, representation) - else: - class_name = representation.name - short_name = __short_name(class_name) - function = getattr(_phat, "boundary_matrix_" + short_name) - return function() - -def compute_persistence_pairs(matrix, - reduction = reductions.twist_reduction): - """Computes persistence pairs (birth, death) for the given boundary matrix.""" - class_name = matrix.__class__.__name__ - representation_short_name = class_name[len('boundary_matrix_'):] - algo_name = reduction.name - algo_short_name = __short_name(algo_name) - function = getattr(_phat, "compute_persistence_pairs_" + representation_short_name + "_" + algo_short_name) - return function(matrix) - -def compute_persistence_pairs_dualized(matrix, - reduction = reductions.twist_reduction): - """Computes persistence pairs (birth, death) from the dualized form of the given boundary matrix.""" - class_name = matrix.__class__.__name__ - representation_short_name = class_name[len('boundary_matrix_'):] - algo_name = reduction.name - algo_short_name = __short_name(algo_name) - function = getattr(_phat, "compute_persistence_pairs_dualized_" + representation_short_name + "_" + algo_short_name) - return function(matrix) + def __init__(self, representation = representations.bit_tree_pivot_column, source = None, columns = None): + """ + The boundary matrix will use the specified implementation for storing its + column data. If the `source` parameter is specified, it will be assumed to + be another boundary matrix, whose data should be copied into the new + matrix. + + Parameters + ---------- + + representation : phat.representation, optional + The type of column storage to use in the requested boundary matrix. + source : phat.boundary_matrix, optional + If provided, creates the requested matrix as a copy of the data and dimensions + in `source`. + columns : column list, or list of (dimension, boundary) tuples, optional + If provided, loads these columns into the new boundary matrix. Note that + columns will be loaded in the order given, not according to their ``index`` properties. + Returns + ------- + matrix : boundary_matrix + """ + self._representation = representation + if source: + self._matrix = _convert(source, representation) + else: + self._matrix = self.__matrix_for_representation(representation)() + if columns: + self.columns = columns + + @property + def columns(self): + """A collection of column objects""" + return [column(self, i) for i in range(self._matrix.get_num_cols())] + + @columns.setter + def columns(self, columns): + for col in columns: + if not (isinstance(col, column) or isinstance(col, tuple)): + raise TypeError("All columns must be column objects, or (dimension, values) tuples") + if len(columns) != len(self.dimensions): + self._matrix.set_dims([0] * len(columns)) + for i, col in enumerate(columns): + if isinstance(col, column): + self._matrix.set_dim(i, col.dimension) + self._matrix.set_col(i, col.boundary) + else: + dimension, values = col + self._matrix.set_dim(i, dimension) + self._matrix.set_col(i, values) + + @property + def dimensions(self): + """A collection of dimensions, equivalent to [c.dimension for c in self.columns]""" + return [self.get_dim(i) for i in range(self._matrix.get_num_cols())] + + @dimensions.setter + def dimensions(self, dimensions): + return self._matrix.set_dims(dimensions) + + def __matrix_for_representation(self, representation): + short_name = _short_name(representation.name) + return getattr(_phat, "boundary_matrix_" + short_name) + + def __eq__(self, other): + return self._matrix == other._matrix + + def __len__(self): + return self._matrix.get_num_entries() + + #Pickle support + def __getstate__(self): + (dimensions, columns) = self._matrix.get_vector_vector() + return (self._representation, dimensions, columns) + + #Pickle support + def __setstate__(self, state): + presentation, dimensions, columns = state + self._representation = representation + self._matrix = self.__matrix_for_representation(representation) + self._matrix.set_vector_vector(dimensions, columns) + + def load(self, file_name, mode = 'b'): + """Load this boundary matrix from a file + + Parameters + ---------- + + file_name : string + The file name to load + + mode : string, optional (defaults to 'b') + The mode ('b' for binary, 't' for text) to use for working with the file + + Returns + ------- + + success : bool + + """ + if mode == 'b': + return self._matrix.load_binary(file_name) + elif mode == 't': + return self._matrix.load_ascii(file_name) + else: + raise ValueError("Only 'b' - binary and 't' - text modes are supported") + + def save(self, file_name, mode = 'b'): + """Save this boundary matrix to a file + + Parameters + ---------- + + file_name : string + The file name to load + + mode : string, optional (defaults to 'b') + The mode ('b' for binary, 't' for text) to use for working with the file + + Returns + ------- + + success : bool + + """ + if mode == 'b': + return self._matrix.save_binary(file_name) + elif mode == 't': + return self._matrix.save_ascii(file_name) + else: + raise ValueError("Only 'b' - binary and 't' - text modes are supported") + + def compute_persistence_pairs(self, + reduction = reductions.twist_reduction): + """Computes persistence pairs (birth, death) for the given boundary matrix.""" + representation_short_name = _short_name(self._representation.name) + algo_name = reduction.name + algo_short_name = _short_name(algo_name) + #Look up an implementation that matches the requested characteristics + #in the _phat module + function = getattr(_phat, "compute_persistence_pairs_" + representation_short_name + "_" + algo_short_name) + return function(self._matrix) + + def compute_persistence_pairs_dualized(self, + reduction = reductions.twist_reduction): + """Computes persistence pairs (birth, death) from the dualized form of the given boundary matrix.""" + representation_short_name = _short_name(self._representation.name) + algo_name = reduction.name + algo_short_name = _short_name(algo_name) + #Look up an implementation that matches the requested characteristics + #in the _phat module + function = getattr(_phat, "compute_persistence_pairs_dualized_" + representation_short_name + "_" + algo_short_name) + return function(self._matrix) + + def convert(self, representation): + """Copy this matrix to another with a different representation""" + return boundary_matrix(representation, self) + +def _short_name(name): + """An internal API that takes leading characters from words + For instance, 'bit_tree_pivot_column' becomes 'btpc' + """ + return "".join([n[0] for n in name.split("_")]) + +def _convert(source, to_representation): + """Internal - function to convert from one `boundary_matrix` implementation to another""" + class_name = source._representation.name + source_rep_short_name = _short_name(class_name) + to_rep_short_name = _short_name(to_representation.name) + function = getattr(_phat, "convert_%s_to_%s" % (source_rep_short_name, to_rep_short_name)) + return function(source._matrix) diff --git a/python/src/self_test.py b/python/src/self_test.py index c8174fb..3f85fc1 100644 --- a/python/src/self_test.py +++ b/python/src/self_test.py @@ -8,15 +8,14 @@ if __name__=='__main__': boundary_matrix = phat.boundary_matrix() # This is broken for some reason - if not boundary_matrix.load_binary(test_data): - # if not boundary_matrix.load_ascii(test_data): + if not boundary_matrix.load(test_data): print("Error: test data %s not found!" % test_data) sys.exit(1) error = False def compute_chunked(mat): - return phat.compute_persistence_pairs(mat, phat.reductions.chunk_reduction) + return mat.compute_persistence_pairs(phat.reductions.chunk_reduction) print("Comparing representations using Chunk algorithm ...") print("Running Chunk - Sparse ...") @@ -88,7 +87,8 @@ if __name__=='__main__': reps = phat.representations reds = phat.reductions - pairs = phat.compute_persistence_pairs + def pairs(mat, red): + return mat.compute_persistence_pairs(red) twist_boundary_matrix = bit_tree_mat() twist_pairs = pairs(twist_boundary_matrix, reds.twist_reduction) @@ -132,10 +132,10 @@ if __name__=='__main__': print("Comparing primal and dual approach using Chunk - Full ...") primal_boundary_matrix = phat.boundary_matrix(reps.full_pivot_column, boundary_matrix) - primal_pairs = phat.compute_persistence_pairs(primal_boundary_matrix, reds.chunk_reduction) + primal_pairs = primal_boundary_matrix.compute_persistence_pairs(reds.chunk_reduction) dual_boundary_matrix = phat.boundary_matrix(reps.full_pivot_column, boundary_matrix) - dual_pairs = phat.compute_persistence_pairs_dualized(dual_boundary_matrix) + dual_pairs = dual_boundary_matrix.compute_persistence_pairs_dualized() if primal_pairs != dual_pairs: print("Error: primal and dual differ!", file=sys.stderr) @@ -149,11 +149,9 @@ if __name__=='__main__': print("Testing vector interface ...") - (vector_vector_matrix, vector_dims) = boundary_matrix.get_vector_vector() - vector_vector_boundary_matrix = phat.boundary_matrix(phat.representations.bit_tree_pivot_column) - vector_vector_boundary_matrix.load_vector_vector(vector_vector_matrix, vector_dims) + vector_vector_boundary_matrix.columns = boundary_matrix.columns if vector_vector_boundary_matrix != boundary_matrix: print("Error: [load|save]_vector_vector bug", file=sys.stderr) diff --git a/python/src/simple_example.py b/python/src/simple_example.py index 82cf6be..955e213 100644 --- a/python/src/simple_example.py +++ b/python/src/simple_example.py @@ -21,39 +21,35 @@ if __name__ == "__main__": import phat - # set the dimension of the cell that each column represents: - dimensions = [0, 0, 1, 0, 1, 1, 2] - # define a boundary matrix with the chosen internal representation boundary_matrix = phat.boundary_matrix(representation = phat.representations.vector_vector) - # set the respective columns -- the columns entries have to be sorted - boundary_matrix.set_dims(dimensions) - boundary_matrix.set_col(0, []) - boundary_matrix.set_col(1, []) - boundary_matrix.set_col(2, [0,1]) - boundary_matrix.set_col(3, []) - boundary_matrix.set_col(4, [1,3]) - boundary_matrix.set_col(5, [0,3]) - boundary_matrix.set_col(6, [2,4,5]) + # set the respective columns -- (dimension, boundary) pairs + boundary_matrix.columns = [ (0, []), + (0, []), + (1, [0,1]), + (0, []), + (1, [1,3]), + (1, [0,3]), + (2, [2,4,5])] + + # or equivalently, boundary_matrix = phat.boundary_matrix(representation = ..., columns = ...) + # would combine the creation of the matrix and the assignment of the columns # print some information of the boundary matrix: - print() - print("The boundary matrix has %d columns:" % boundary_matrix.get_num_cols()) - for col_idx in range(boundary_matrix.get_num_cols()): - s = "Column %d represents a cell of dimension %d." % (col_idx, boundary_matrix.get_dim(col_idx)) - if (not boundary_matrix.is_empty(col_idx)): - s = s + " Its boundary consists of the cells " + " ".join([str(c) for c in boundary_matrix.get_col(col_idx)]) + print("\nThe boundary matrix has %d columns:" % len(boundary_matrix.columns)) + for col in boundary_matrix.columns: + s = "Column %d represents a cell of dimension %d." % (col.index, col.dimension) + if (col.boundary): + s = s + " Its boundary consists of the cells " + " ".join([str(c) for c in col.boundary]) print(s) - print("Overall, the boundary matrix has %d entries." % boundary_matrix.get_num_entries()) + print("Overall, the boundary matrix has %d entries." % len(boundary_matrix)) - pairs = phat.compute_persistence_pairs(boundary_matrix) + pairs = boundary_matrix.compute_persistence_pairs() pairs.sort() - print() - - print("There are %d persistence pairs: " % len(pairs)) + print("\nThere are %d persistence pairs: " % len(pairs)) for pair in pairs: print("Birth: %d, Death: %d" % pair) -- cgit v1.2.3 From 833c52094f6a90228901fd9278d1cd68bd3f7bdc Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Fri, 11 Mar 2016 11:35:37 -0800 Subject: Updated README to use the more pythonic API --- python/README.rst | 51 ++++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/python/README.rst b/python/README.rst index b67ff47..ab4142c 100644 --- a/python/README.rst +++ b/python/README.rst @@ -43,11 +43,8 @@ the algorithm. We provide the following choices of representation classes: Sample usage ------------ -From src/simple_example.py in the source:: +We will build an ordered boundary matrix of this simplicial complex consisting of a single triangle: - print(""" - we will build an ordered boundary matrix of this simplicial complex consisting of a single triangle: - 3 |\\ | \\ @@ -60,45 +57,41 @@ From src/simple_example.py in the source:: |________\\ 0 2 1 - """) +Now the code:: import phat - # set the dimension of the cell that each column represents: - dimensions = [0, 0, 1, 0, 1, 1, 2] - # define a boundary matrix with the chosen internal representation boundary_matrix = phat.boundary_matrix(representation = phat.representations.vector_vector) - # set the respective columns -- the columns entries have to be sorted - boundary_matrix.set_dims(dimensions) - boundary_matrix.set_col(0, []) - boundary_matrix.set_col(1, []) - boundary_matrix.set_col(2, [0,1]) - boundary_matrix.set_col(3, []) - boundary_matrix.set_col(4, [1,3]) - boundary_matrix.set_col(5, [0,3]) - boundary_matrix.set_col(6, [2,4,5]) + # set the respective columns -- (dimension, boundary) pairs + boundary_matrix.columns = [ (0, []), + (0, []), + (1, [0,1]), + (0, []), + (1, [1,3]), + (1, [0,3]), + (2, [2,4,5])] + + # or equivalently, boundary_matrix = phat.boundary_matrix(representation = ..., columns = ...) + # would combine the creation of the matrix and the assignment of the columns # print some information of the boundary matrix: - print() - print("the boundary matrix has %d columns:" % boundary_matrix.get_num_cols()) - for col_idx in range(boundary_matrix.get_num_cols()): - s = "column %d represents a cell of dimension %d." % (col_idx, boundary_matrix.get_dim(col_idx)) - if (not boundary_matrix.is_empty(col_idx)): - s = s + " its boundary consists of the cells " + " ".join([str(c) for c in boundary_matrix.get_col(col_idx)]) + print("\nThe boundary matrix has %d columns:" % len(boundary_matrix.columns)) + for col in boundary_matrix.columns: + s = "Column %d represents a cell of dimension %d." % (col.index, col.dimension) + if (col.boundary): + s = s + " Its boundary consists of the cells " + " ".join([str(c) for c in col.boundary]) print(s) - print("overall, the boundary matrix has %d entries." % boundary_matrix.get_num_entries()) + print("Overall, the boundary matrix has %d entries." % len(boundary_matrix)) - pairs = phat.compute_persistence_pairs(boundary_matrix) + pairs = boundary_matrix.compute_persistence_pairs() pairs.sort() - print() - - print("there are %d persistence pairs: " % len(pairs)) + print("\nThere are %d persistence pairs: " % len(pairs)) for pair in pairs: - print("birth: %d, death: %d" % pair) + print("Birth: %d, Death: %d" % pair) References: -- cgit v1.2.3 From 2e08d6bf56f4c28e1fa583fe4f0f3d539b8dfec3 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Fri, 11 Mar 2016 11:50:04 -0800 Subject: Fixed sample complex diagram formatting in readme and doc string --- python/README.rst | 2 +- python/phat.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/python/README.rst b/python/README.rst index ab4142c..8495e5f 100644 --- a/python/README.rst +++ b/python/README.rst @@ -43,7 +43,7 @@ the algorithm. We provide the following choices of representation classes: Sample usage ------------ -We will build an ordered boundary matrix of this simplicial complex consisting of a single triangle: +We will build an ordered boundary matrix of this simplicial complex consisting of a single triangle:: 3 |\\ diff --git a/python/phat.py b/python/phat.py index 6537b26..ab8a7bc 100644 --- a/python/phat.py +++ b/python/phat.py @@ -6,7 +6,7 @@ algorithms and column data representations. Here is a simple example of usage. -We will build an ordered boundary matrix of this simplicial complex consisting of a single triangle: +We will build an ordered boundary matrix of this simplicial complex consisting of a single triangle:: 3 |\\ @@ -36,11 +36,15 @@ Now the code:: (1, [0,3]), (2, [2,4,5])] - # or equivalently, boundary_matrix = phat.boundary_matrix(representation = ..., columns = ...) - # would combine the creation of the matrix and the assignment of the columns + # or equivalently, + # boundary_matrix = phat.boundary_matrix(representation = ..., + # columns = ...) + # would combine the creation of the matrix and + # the assignment of the columns # print some information of the boundary matrix: - print("\nThe boundary matrix has %d columns:" % len(boundary_matrix.columns)) + print() + print("The boundary matrix has %d columns:" % len(boundary_matrix.columns)) for col in boundary_matrix.columns: s = "Column %d represents a cell of dimension %d." % (col.index, col.dimension) if (col.boundary): @@ -52,10 +56,12 @@ Now the code:: pairs.sort() - print("\nThere are %d persistence pairs: " % len(pairs)) + print() + print("There are %d persistence pairs: " % len(pairs)) for pair in pairs: print("Birth: %d, Death: %d" % pair) + Please see https://bitbucket.org/phat-code/phat/python for more information. """ -- cgit v1.2.3 From 57040bdfa4d7c022d8887420a7dda6c550b84858 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Wed, 13 Apr 2016 14:57:14 -0700 Subject: Correction for dimensions property --- python/phat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/phat.py b/python/phat.py index ab8a7bc..0aff4bd 100644 --- a/python/phat.py +++ b/python/phat.py @@ -188,7 +188,7 @@ class boundary_matrix: @property def dimensions(self): """A collection of dimensions, equivalent to [c.dimension for c in self.columns]""" - return [self.get_dim(i) for i in range(self._matrix.get_num_cols())] + return [self._matrix.get_dim(i) for i in range(self._matrix.get_num_cols())] @dimensions.setter def dimensions(self, dimensions): -- cgit v1.2.3 From 83e49c569c2b53a7068f42df616ed8ee695a0fb8 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Mon, 2 May 2016 11:50:31 -0700 Subject: Added readme section on installation, removed code that blocked execution under 2.7. Still needs work to build on desired platforms --- python/README.rst | 18 ++++++++++++++++++ python/setup.py | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/python/README.rst b/python/README.rst index 8495e5f..3fd685d 100644 --- a/python/README.rst +++ b/python/README.rst @@ -40,6 +40,24 @@ the algorithm. We provide the following choices of representation classes: * ``full_pivot_column``: The same idea as in the sparse version. However, instead of a ``std::set``, the pivot column is expanded into a bit vector of size n (the dimension of the matrix). To avoid costly initializations, the class remembers which entries have been manipulated for a pivot column and updates only those entries when another column becomes the pivot. * ``bit_tree_pivot_column`` (default representation): Similar to the ``full_pivot_column`` but the implementation is more efficient. Internally it is a bit-set with fast iteration over nonzero elements, and fast access to the maximal element. +Installation +------------ + +Suppose you have checked out the PHAT repository at location $PHAT. Then you can: + +``` +cd $PHAT/python + +pip install . +``` + +This will install PHAT for whatever Python installation your `pip` executable is associated with. +Also note that this will install dependencies (pybind11) as well. +If you already have pybind11 installed, then `python setup.py install` will most likely work too. + +Currently, the PHAT Python bindings are known to work on Mac OS X with Python 3.5. Other configurations are untested but in progress. +Please let us know if there is a platform you'd like us to support, we will do so if we can. + Sample usage ------------ diff --git a/python/setup.py b/python/setup.py index 5e71cc7..2c6d65d 100644 --- a/python/setup.py +++ b/python/setup.py @@ -2,6 +2,7 @@ from setuptools import setup, Extension, find_packages from setuptools.command.build_ext import build_ext import sys import os.path + ext_modules = [ Extension( '_phat', @@ -14,7 +15,7 @@ ext_modules = [ here = os.path.abspath(os.path.dirname(__file__)) # Get the long description from the README file -with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: +with open(os.path.join(here, 'README.rst')) as f: long_description = f.read() class BuildExt(build_ext): -- cgit v1.2.3 From a0d647b35e2cd7702eaffbd2074cb2914c6383cd Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Mon, 2 May 2016 13:23:42 -0700 Subject: Tested with system Python 2.7 on Ubuntu as well as Anaconda Python 3.5 on Ubuntu --- python/README.rst | 14 +++++++++----- python/_phat.cpp | 2 +- python/setup.py | 8 ++++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/python/README.rst b/python/README.rst index 3fd685d..9f7fe41 100644 --- a/python/README.rst +++ b/python/README.rst @@ -48,14 +48,18 @@ Suppose you have checked out the PHAT repository at location $PHAT. Then you can ``` cd $PHAT/python -pip install . +ln -s ../include include # (or copy, we just need $PHAT/include to be in the current folder as well) + +pip install pybind11 + +python setup.py install ``` -This will install PHAT for whatever Python installation your `pip` executable is associated with. -Also note that this will install dependencies (pybind11) as well. -If you already have pybind11 installed, then `python setup.py install` will most likely work too. +This will install PHAT for whatever Python installation your `python` executable is associated with. +Please ensure you use the `pip` that comes from the same directory where your `python` executable lives! -Currently, the PHAT Python bindings are known to work on Mac OS X with Python 3.5. Other configurations are untested but in progress. +Currently, the PHAT Python bindings are known to work on Linux with Python 2.7 and Python 3.5. +Other configurations are untested but in progress. Please let us know if there is a platform you'd like us to support, we will do so if we can. Sample usage diff --git a/python/_phat.cpp b/python/_phat.cpp index 40a9421..9883c97 100644 --- a/python/_phat.cpp +++ b/python/_phat.cpp @@ -227,7 +227,7 @@ void wrap_persistence_pairs(py::module &m) { .def("__len__", &phat::persistence_pairs::get_num_pairs) // Unlike set_pair, this takes a Python 2-tuple .def("__setitem__", - [](phat::persistence_pairs &p, int index, std::pair &pair) { + [](phat::persistence_pairs &p, int index, std::pair pair) { phat::index idx = fix_index(p, index); p.set_pair(idx, pair.first, pair.second); }) diff --git a/python/setup.py b/python/setup.py index 2c6d65d..133a204 100644 --- a/python/setup.py +++ b/python/setup.py @@ -2,12 +2,14 @@ from setuptools import setup, Extension, find_packages from setuptools.command.build_ext import build_ext import sys import os.path +from io import open ext_modules = [ Extension( '_phat', ['_phat.cpp'], - include_dirs=['include', '../include'], + include_dirs=['include', + '../include'], language='c++', ), ] @@ -15,7 +17,7 @@ ext_modules = [ here = os.path.abspath(os.path.dirname(__file__)) # Get the long description from the README file -with open(os.path.join(here, 'README.rst')) as f: +with open(os.path.join(here, 'README.rst'), encoding = 'utf8') as f: long_description = f.read() class BuildExt(build_ext): @@ -31,8 +33,10 @@ class BuildExt(build_ext): def build_extensions(self): ct = self.compiler.compiler_type opts = self.c_opts.get(ct, []) + import pybind11 for ext in self.extensions: ext.extra_compile_args = opts + ext.include_dirs.append(pybind11.get_include()) build_ext.build_extensions(self) setup( -- cgit v1.2.3 From 5e060fd1a8e2f36e7b2a3a0297e1301d89231365 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Mon, 2 May 2016 13:32:02 -0700 Subject: Updated to show Linux and Mac both working for 2.7 and 3.5 --- python/README.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/python/README.rst b/python/README.rst index 9f7fe41..792fc6e 100644 --- a/python/README.rst +++ b/python/README.rst @@ -58,8 +58,15 @@ python setup.py install This will install PHAT for whatever Python installation your `python` executable is associated with. Please ensure you use the `pip` that comes from the same directory where your `python` executable lives! -Currently, the PHAT Python bindings are known to work on Linux with Python 2.7 and Python 3.5. -Other configurations are untested but in progress. +Currently, the PHAT Python bindings are known to work on: + +* Linux with Python 2.7 (tested on Ubuntu 14.04 with system Python) +* Linux with Python 3.5 (tested on Ubuntu 14.04 with Anaconda) +* Mac OS X with Python 2.7 (tested on El Capitan with Anaconda) +* Mac OS X with Python 3.5 (tested on El Capitan with Anaconda) + +Other configurations are untested. + Please let us know if there is a platform you'd like us to support, we will do so if we can. Sample usage -- cgit v1.2.3 From 13f2865522c0201d4bce90f0d4c959883f9b4849 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Mon, 2 May 2016 13:35:19 -0700 Subject: RST literal format different from markdown's --- python/README.rst | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/python/README.rst b/python/README.rst index 792fc6e..0b71257 100644 --- a/python/README.rst +++ b/python/README.rst @@ -45,18 +45,16 @@ Installation Suppose you have checked out the PHAT repository at location $PHAT. Then you can: -``` -cd $PHAT/python + cd $PHAT/python -ln -s ../include include # (or copy, we just need $PHAT/include to be in the current folder as well) + ln -s ../include include # (or copy, we just need $PHAT/include to be in the current folder as well) -pip install pybind11 + pip install pybind11 -python setup.py install -``` + python setup.py install -This will install PHAT for whatever Python installation your `python` executable is associated with. -Please ensure you use the `pip` that comes from the same directory where your `python` executable lives! +This will install PHAT for whatever Python installation your ``python`` executable is associated with. +Please ensure you use the ``pip`` that comes from the same directory where your ``python`` executable lives! Currently, the PHAT Python bindings are known to work on: -- cgit v1.2.3 From a99e8a72971e4e02fef05e40a9ff28057540b754 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Mon, 2 May 2016 13:37:04 -0700 Subject: Typo --- python/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/README.rst b/python/README.rst index 0b71257..9436ae1 100644 --- a/python/README.rst +++ b/python/README.rst @@ -43,7 +43,7 @@ the algorithm. We provide the following choices of representation classes: Installation ------------ -Suppose you have checked out the PHAT repository at location $PHAT. Then you can: +Suppose you have checked out the PHAT repository at location $PHAT. Then you can:: cd $PHAT/python -- cgit v1.2.3 From 2f3ed798d26ae4ab31b0b45e5ef5305c58b28be1 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Mon, 2 May 2016 15:46:07 -0700 Subject: Various tweaks to support Python 2.7 --- python/_phat.cpp | 13 +++++++++++++ python/phat.py | 15 +++++++++++++-- python/src/self_test.py | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/python/_phat.cpp b/python/_phat.cpp index 9883c97..df7449d 100644 --- a/python/_phat.cpp +++ b/python/_phat.cpp @@ -149,6 +149,16 @@ void wrap_boundary_matrix(py::module &mod, const std::string &representation_suf .def("__eq__", &mat::template operator==) .def("__eq__", &mat::template operator==) + //Python 3.x can figure this out for itself, but Python 2.7 needs to be told: + .def("__ne__", &mat::template operator!=) + .def("__ne__", &mat::template operator!=) + .def("__ne__", &mat::template operator!=) + .def("__ne__", &mat::template operator!=) + .def("__ne__", &mat::template operator!=) + .def("__ne__", &mat::template operator!=) + .def("__ne__", &mat::template operator!=) + .def("__ne__", &mat::template operator!=) + //#### Data access // In `get_col`, since Python is garbage collected, the C++ idiom of passing in a collection @@ -241,6 +251,9 @@ void wrap_persistence_pairs(py::module &m) { .def("clear", &phat::persistence_pairs::clear, "Empties the collection") .def("sort", &phat::persistence_pairs::sort, "Sort in place") .def("__eq__", &phat::persistence_pairs::operator==) + .def("__ne__", [](phat::persistence_pairs &p, phat::persistence_pairs &other) { + return p != other; + }) //#### File operations .def("load_ascii", &phat::persistence_pairs::load_ascii, "Load the contents of a text file into this instance") diff --git a/python/phat.py b/python/phat.py index 0aff4bd..70f5b39 100644 --- a/python/phat.py +++ b/python/phat.py @@ -77,6 +77,7 @@ __all__ = ['boundary_matrix', 'representations', 'reductions'] + class representations(enum.Enum): """Available representations for internal storage of columns in a `boundary_matrix` @@ -89,6 +90,7 @@ class representations(enum.Enum): vector_set = 6 vector_list = 7 + class reductions(enum.Enum): """Available reduction algorithms""" twist_reduction = 1 @@ -97,7 +99,8 @@ class reductions(enum.Enum): row_reduction = 4 spectral_sequence_reduction = 5 -class column: + +class column(object): """A view on one column of data in a boundary matrix""" def __init__(self, matrix, index): """INTERNAL. Columns are created automatically by boundary matrices. @@ -128,7 +131,10 @@ class column: def boundary(self, values): return self._matrix._matrix.set_col(self._index, values) -class boundary_matrix: + def __str__(self): + return "(%d, %s)" % (self.dimension, self.boundary) + +class boundary_matrix(object): """Boundary matrices that store the shape information of a cell complex. """ @@ -201,6 +207,11 @@ class boundary_matrix: def __eq__(self, other): return self._matrix == other._matrix + #Note Python 2.7 needs BOTH __eq__ and __ne__ otherwise you get things that + #are both equal and not equal + def __ne__(self, other): + return self._matrix != other._matrix + def __len__(self): return self._matrix.get_num_entries() diff --git a/python/src/self_test.py b/python/src/self_test.py index 3f85fc1..8017387 100644 --- a/python/src/self_test.py +++ b/python/src/self_test.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys import phat @@ -157,7 +158,6 @@ if __name__=='__main__': print("Error: [load|save]_vector_vector bug", file=sys.stderr) error = True - if error: sys.exit(1) else: -- cgit v1.2.3 From 1d186c730074700995c3139af73267e9e498990a Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Tue, 10 May 2016 13:33:56 -0700 Subject: Setup.py checks for Python 2.7.11 or greater --- python/setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/setup.py b/python/setup.py index 133a204..556b722 100644 --- a/python/setup.py +++ b/python/setup.py @@ -4,6 +4,11 @@ import sys import os.path from io import open +if sys.version_info < (2, 7, 11): + print("Sorry, PHAT requires Python 2.7.11 or later") + sys.exit(1) + + ext_modules = [ Extension( '_phat', -- cgit v1.2.3 From 0dc635b981cf111b41ffd13115b21d8985f867b1 Mon Sep 17 00:00:00 2001 From: Mihai Capotă Date: Wed, 23 Nov 2016 14:23:42 -0800 Subject: Add support for user level pybind11 installations Currently, when pybind11 is installed using the `--user` flag of pip, its headers are not found. --- python/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/setup.py b/python/setup.py index 556b722..f366e88 100644 --- a/python/setup.py +++ b/python/setup.py @@ -42,6 +42,7 @@ class BuildExt(build_ext): for ext in self.extensions: ext.extra_compile_args = opts ext.include_dirs.append(pybind11.get_include()) + ext.include_dirs.append(pybind11.get_include(user=True)) build_ext.build_extensions(self) setup( -- cgit v1.2.3 From cdcf5f101523ca2edc497a8de559b2c538ddbd89 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Wed, 30 Nov 2016 20:26:34 -0800 Subject: Moved setup.py to root so that we could avoid need for symbolic link. Updated readme.rst and other files to reflect this change, added requirement for enum34 when using python 2.7. Also added explanation that the system python 2.7.10 on Mac OS X is to be avoided. --- python/README.rst | 24 +++++++++++------- python/setup.py | 68 --------------------------------------------------- setup.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 77 deletions(-) delete mode 100644 python/setup.py create mode 100644 setup.py diff --git a/python/README.rst b/python/README.rst index 9436ae1..7d9a1e6 100644 --- a/python/README.rst +++ b/python/README.rst @@ -45,26 +45,31 @@ Installation Suppose you have checked out the PHAT repository at location $PHAT. Then you can:: - cd $PHAT/python + cd $PHAT - ln -s ../include include # (or copy, we just need $PHAT/include to be in the current folder as well) + pip install . - pip install pybind11 - - python setup.py install - -This will install PHAT for whatever Python installation your ``python`` executable is associated with. +This will install PHAT for whatever Python installation your ``pip`` executable is associated with. Please ensure you use the ``pip`` that comes from the same directory where your ``python`` executable lives! Currently, the PHAT Python bindings are known to work on: * Linux with Python 2.7 (tested on Ubuntu 14.04 with system Python) * Linux with Python 3.5 (tested on Ubuntu 14.04 with Anaconda) -* Mac OS X with Python 2.7 (tested on El Capitan with Anaconda) -* Mac OS X with Python 3.5 (tested on El Capitan with Anaconda) +* Mac OS X with Python 2.7.12 (tested on Sierra with homebrew) +* Mac OS X with Python 3.5 (tested on Sierra with homebrew) Other configurations are untested. +Please note that this package DOES NOT work with the Python 2.7.10 that ships with the operating +system in Mac OS X. These words of wisdom from `python.org`_ are worth heeding: + + The version of Python that ships with OS X is great for learning but it’s not good for development. + The version shipped with OS X may be out of date from the official current Python release, + which is considered the stable production version. + +We recommend installing Python on Mac OS X using either homebrew or Anaconda, according to your taste. + Please let us know if there is a platform you'd like us to support, we will do so if we can. Sample usage @@ -128,3 +133,4 @@ References: .. [4] U.Bauer, M.Kerber, J.Reininghaus: Clear and Compress: Computing Persistent Homology in Chunks. arXiv:1303.0477_ .. _arXiv:1303.0477: http://arxiv.org/pdf/1303.0477.pdf .. _`Persistent Homology Algorithm Toolkit`: https://bitbucket.org/phat/phat-code +.. _`python.org`:http://docs.python-guide.org/en/latest/starting/install/osx/ diff --git a/python/setup.py b/python/setup.py deleted file mode 100644 index f366e88..0000000 --- a/python/setup.py +++ /dev/null @@ -1,68 +0,0 @@ -from setuptools import setup, Extension, find_packages -from setuptools.command.build_ext import build_ext -import sys -import os.path -from io import open - -if sys.version_info < (2, 7, 11): - print("Sorry, PHAT requires Python 2.7.11 or later") - sys.exit(1) - - -ext_modules = [ - Extension( - '_phat', - ['_phat.cpp'], - include_dirs=['include', - '../include'], - language='c++', - ), -] - -here = os.path.abspath(os.path.dirname(__file__)) - -# Get the long description from the README file -with open(os.path.join(here, 'README.rst'), encoding = 'utf8') as f: - long_description = f.read() - -class BuildExt(build_ext): - """A custom build extension for adding compiler-specific options.""" - c_opts = { - 'msvc': ['/EHsc'], - 'unix': ['-std=c++11'], - } - - if sys.platform == 'darwin': - c_opts['unix'] += ['-stdlib=libc++', '-mmacosx-version-min=10.7'] - - def build_extensions(self): - ct = self.compiler.compiler_type - opts = self.c_opts.get(ct, []) - import pybind11 - for ext in self.extensions: - ext.extra_compile_args = opts - ext.include_dirs.append(pybind11.get_include()) - ext.include_dirs.append(pybind11.get_include(user=True)) - build_ext.build_extensions(self) - -setup( - name='phat', - version='0.0.1', - author='Bryn Keller', - author_email='bryn.keller@intel.com', - url='https://bitbucket.org/phat-code/phat', - description='Python bindings for PHAT', - license = 'LGPL', - keywords='algebraic-topology PHAT distributed topology persistent-homology', - long_description=long_description, - ext_modules=ext_modules, - install_requires=['pybind11'], - cmdclass={'build_ext': BuildExt}, - py_modules = ['phat'], - # packages = find_packages(exclude = ['doc', 'test']) - ) - - - - - diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6d082d7 --- /dev/null +++ b/setup.py @@ -0,0 +1,73 @@ +from setuptools import setup, Extension, find_packages +from setuptools.command.build_ext import build_ext +import sys +import os.path +from io import open + +if sys.version_info < (2, 7, 12): + print("Sorry, PHAT requires Python 2.7.12 or later") + sys.exit(1) + + +ext_modules = [ + Extension( + '_phat', + ['python/_phat.cpp'], + include_dirs=['include'], + language='c++', + ), +] + +here = os.path.abspath(os.path.dirname(__file__)) + +# Get the long description from the README file +with open(os.path.join(here, 'python', 'README.rst'), encoding = 'utf8') as f: + long_description = f.read() + +class BuildExt(build_ext): + """A custom build extension for adding compiler-specific options.""" + c_opts = { + 'msvc': ['/EHsc'], + 'unix': ['-std=c++11'], + } + + if sys.platform == 'darwin': + c_opts['unix'] += ['-stdlib=libc++', '-mmacosx-version-min=10.7'] + + def build_extensions(self): + ct = self.compiler.compiler_type + opts = self.c_opts.get(ct, []) + import pybind11 + for ext in self.extensions: + ext.extra_compile_args = opts + ext.include_dirs.append(pybind11.get_include()) + ext.include_dirs.append(pybind11.get_include(user=True)) + build_ext.build_extensions(self) + +requires = ['pybind11'] + +if sys.version_info < (3,4,0): + requires.append('enum34') + +setup( + name='phat', + version='0.0.1', + author='Bryn Keller', + author_email='bryn.keller@intel.com', + url='https://bitbucket.org/phat-code/phat', + description='Python bindings for PHAT', + license = 'LGPL', + keywords='algebraic-topology PHAT distributed topology persistent-homology', + long_description=long_description, + ext_modules=ext_modules, + install_requires=requires, + cmdclass={'build_ext': BuildExt}, + package_dir={'':'python'}, + py_modules = ['phat'], + # packages = find_packages(exclude = ['doc', 'test']) + ) + + + + + -- cgit v1.2.3 From 2627c54de7891fbb5b9c3df72167d8c117058561 Mon Sep 17 00:00:00 2001 From: Ulrich Bauer Date: Fri, 9 Dec 2016 00:23:59 +0100 Subject: preparing for 1.5 release --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a30ffa..f166ee2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PHAT (Persistent Homology Algorithm Toolbox), v1.4.0 # +# PHAT (Persistent Homology Algorithm Toolbox), v1.5.0 # Copyright 2013, 2014 IST Austria ## Project Founders: ## @@ -10,7 +10,8 @@ Ulrich Bauer, Michael Kerber, Jan Reininghaus Hubert Wagner, Bryn Keller ## Downloads: ## -* [PHAT, v1.4.0](https://drive.google.com/uc?id=0B7Yz6TPEpiGEWmNyeVFsNXgtUGc&export=download) +* [PHAT, v1.5.0](https://bitbucket.org/phat-code/phat/get/v1.5.0.zip) +* [PHAT, v1.4.1](https://bitbucket.org/phat-code/phat/get/v1.4.1.zip) * [PHAT, v1.3.0](https://drive.google.com/uc?id=0B7Yz6TPEpiGEMGFNQ3FPX3ltelk&export=download) * [PHAT, v1.2.1](https://drive.google.com/uc?id=0B7Yz6TPEpiGENE9KUnhUSFdFQUk&export=download) * [benchmark data](https://drive.google.com/uc?id=0B7Yz6TPEpiGERGZFbjlXaUt1ZWM&export=download) -- cgit v1.2.3 From beadd363d0a78cea8718a605ed1364feb790770c Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Thu, 8 Dec 2016 15:52:34 -0800 Subject: Updated readme.md to mention the Python bindings, set python bindings version number to 1.5.0 to match main project version --- README.md | 4 ++++ setup.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a30ffa..0f83bc9 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,10 @@ Supported Platforms: * Visual Studio 2008 and 2012 (2010 untested) * GCC version 4.4. and higher +## Python Bindings ## +We provide bindings for Python 3.x and 2.7.12+, which are installable using `pip`. Please see +the Python-specific README.rst in the `python` folder of this repository for details. + References: 1. H.Edelsbrunner, J.Harer: Computational Topology, An Introduction. American Mathematical Society, 2010, ISBN 0-8218-4925-5 diff --git a/setup.py b/setup.py index 6d082d7..1ce0e51 100644 --- a/setup.py +++ b/setup.py @@ -51,11 +51,11 @@ if sys.version_info < (3,4,0): setup( name='phat', - version='0.0.1', + version='1.5.0', author='Bryn Keller', author_email='bryn.keller@intel.com', url='https://bitbucket.org/phat-code/phat', - description='Python bindings for PHAT', + description='Python bindings for PHAT, the C++ based Persistent Homology Algorithm Toolbox', license = 'LGPL', keywords='algebraic-topology PHAT distributed topology persistent-homology', long_description=long_description, -- cgit v1.2.3 From d8ef50e225f7abfbf8d085c808e77857827e5274 Mon Sep 17 00:00:00 2001 From: Bryn Keller Date: Thu, 8 Dec 2016 18:14:18 -0800 Subject: Added note that phat module can be installed with pip, without downloading source --- python/README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/README.rst b/python/README.rst index 7d9a1e6..0c6c391 100644 --- a/python/README.rst +++ b/python/README.rst @@ -43,6 +43,12 @@ the algorithm. We provide the following choices of representation classes: Installation ------------ +If you wish to use the released version of PHAT, you can simply install from PyPI:: + + pip install phat + +Installation from Source +------------------------ Suppose you have checked out the PHAT repository at location $PHAT. Then you can:: cd $PHAT -- cgit v1.2.3 From 316c554d0258870eacadcd28313cc68468856cff Mon Sep 17 00:00:00 2001 From: Ulrich Bauer Date: Thu, 5 Jan 2017 07:45:01 +0100 Subject: added PHAT paper reference --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e942f95..83daebc 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # PHAT (Persistent Homology Algorithm Toolbox), v1.5.0 # Copyright 2013, 2014 IST Austria -## Project Founders: ## +## Project Founders ## Ulrich Bauer, Michael Kerber, Jan Reininghaus -## Contributors: ## +## Contributors ## Hubert Wagner, Bryn Keller -## Downloads: ## +## Downloads ## * [PHAT, v1.5.0](https://bitbucket.org/phat-code/phat/get/v1.5.0.zip) * [PHAT, v1.4.1](https://bitbucket.org/phat-code/phat/get/v1.4.1.zip) * [PHAT, v1.3.0](https://drive.google.com/uc?id=0B7Yz6TPEpiGEMGFNQ3FPX3ltelk&export=download) @@ -17,7 +17,7 @@ Hubert Wagner, Bryn Keller * [benchmark data](https://drive.google.com/uc?id=0B7Yz6TPEpiGERGZFbjlXaUt1ZWM&export=download) * [benchmark data 2](https://drive.google.com/uc?id=0B7Yz6TPEpiGEWE55X3RuM3JjZ3M&export=download) -## Description: ## +## Description ## This software library contains methods for computing the persistence pairs of a filtered cell complex represented by an ordered boundary matrix with Z2 coefficients. @@ -96,7 +96,7 @@ pairs.get_pair(...) A simple example that demonstrates this functionality can be found in `src/simple_example.cpp` -## File Formats: ## +## File Formats ## The library supports input and output in ascii and binary format through the methods `[load|save]_[ascii|binary]` in the classes `boundary_matrix` @@ -138,9 +138,10 @@ Supported Platforms: We provide bindings for Python 3.x and 2.7.12+, which are installable using `pip`. Please see the Python-specific README.rst in the `python` folder of this repository for details. -References: +## References ## 1. H.Edelsbrunner, J.Harer: Computational Topology, An Introduction. American Mathematical Society, 2010, ISBN 0-8218-4925-5 2. V.de Silva, D.Morozov, M.Vejdemo-Johansson: Dualities in persistent (co)homology. Inverse Problems 27, 2011 3. C.Chen, M.Kerber: Persistent Homology Computation With a Twist. 27th European Workshop on Computational Geometry, 2011. -4. U.Bauer, M.Kerber, J.Reininghaus: Clear and Compress: Computing Persistent Homology in Chunks. [http://arxiv.org/pdf/1303.0477.pdf arXiv:1303.0477] +4. U.Bauer, M.Kerber, J.Reininghaus: Clear and Compress: Computing Persistent Homology in Chunks. [http://arxiv.org/pdf/1303.0477.pdf](arXiv:1303.0477) +5. U.Bauer, M.Kerber, J.Reininghausc, H.Wagner: Phat – Persistent Homology Algorithms Toolbox. Journal of Symbolic Computation 78, 2017, p. 76–90. \ No newline at end of file -- cgit v1.2.3