Switched PairwiseDistances in Python bindings to C++ dev
authorDmitriy Morozov <dmitriy@mrzv.org>
Thu, 30 Jul 2009 10:35:49 -0700
branchdev
changeset 147 d39a20acb253
parent 146 4e27f1f7c169
child 148 a99fdaafa31a
Switched PairwiseDistances in Python bindings to C++
bindings/python/CMakeLists.txt
bindings/python/dionysus.cpp
bindings/python/dionysus/__init__.py
bindings/python/distances.cpp
bindings/python/distances.h
doc/python/rips.rst
doc/tutorial.rst
examples/rips/rips-pairwise.py
include/geometry/distances.h
--- a/bindings/python/CMakeLists.txt	Thu Jul 30 10:23:31 2009 -0700
+++ b/bindings/python/CMakeLists.txt	Thu Jul 30 10:35:49 2009 -0700
@@ -15,6 +15,7 @@
                                                 zigzag-persistence.cpp
                                                 cohomology-persistence.cpp
                                                 rips.cpp
+                                                distances.cpp
                             )
 set                         (bindings_libraries ${libraries})
 
--- a/bindings/python/dionysus.cpp	Thu Jul 30 10:23:31 2009 -0700
+++ b/bindings/python/dionysus.cpp	Thu Jul 30 10:35:49 2009 -0700
@@ -12,6 +12,8 @@
 void export_cohomology_persistence();
 
 void export_rips();
+void export_pairwise_distances();
+
 #ifndef NO_CGAL
 void export_alphashapes2d();
 void export_alphashapes3d();
@@ -36,6 +38,8 @@
     export_cohomology_persistence();
 
     export_rips();
+    export_pairwise_distances();
+
 #ifndef NO_CGAL
     export_alphashapes2d();
     export_alphashapes3d();
--- a/bindings/python/dionysus/__init__.py	Thu Jul 30 10:23:31 2009 -0700
+++ b/bindings/python/dionysus/__init__.py	Thu Jul 30 10:35:49 2009 -0700
@@ -1,5 +1,5 @@
 from    _dionysus   import *
-from    distances   import *
+from    distances   import l2, ExplicitDistances, points_file
 from    zigzag      import *
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/python/distances.cpp	Thu Jul 30 10:35:49 2009 -0700
@@ -0,0 +1,21 @@
+#include <boost/python.hpp>
+namespace bp = boost::python;
+
+#include "distances.h"
+namespace dp = dionysus::python;
+
+boost::shared_ptr<dp::ListPointPairwiseDistances>       init_from_list(bp::list lst)
+{
+    boost::shared_ptr<dp::ListPointPairwiseDistances>   p(new dp::ListPointPairwiseDistances(lst));
+    return p;
+}
+
+void export_pairwise_distances()
+{
+    bp::class_<dp::ListPointPairwiseDistances>("PairwiseDistances", bp::no_init)
+        .def("__init__",        bp::make_constructor(&init_from_list))
+        .def("__len__",         &dp::ListPointPairwiseDistances::size)
+        .def("__call__",        &dp::ListPointPairwiseDistances::operator())
+    ;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/python/distances.h	Thu Jul 30 10:35:49 2009 -0700
@@ -0,0 +1,54 @@
+#include <utilities/log.h>
+
+#include <boost/python.hpp>
+namespace bp = boost::python;
+
+namespace dionysus { 
+namespace python   {
+
+typedef     bp::list            ListPoint;
+
+struct ListPointL2Distance:
+    public std::binary_function<bp::object, bp::object, double>
+{
+    result_type     operator()(bp::object p1, bp::object p2) const
+    {
+        ListPoint lp1 = bp::extract<ListPoint>(p1), lp2 = bp::extract<ListPoint>(p2);
+
+        AssertMsg(bp::len(lp1) == bp::len(lp2), "Points must be in the same dimension (in L2Distance): dim1=%d, dim2=%d", bp::len(lp1), bp::len(lp2));
+        result_type sum = 0;
+        for (size_t i = 0; i < bp::len(lp1); ++i)
+        {
+            double diff = bp::extract<double>(lp1[i]) - bp::extract<double>(lp2[i]);
+            sum += diff*diff;
+        }
+
+        return sqrt(sum);
+    }
+};
+
+class ListPointPairwiseDistances
+{
+    public:
+        typedef             bp::list                                        Container;
+        typedef             ListPointL2Distance                             Distance;
+        typedef             unsigned                                        IndexType;
+        typedef             Distance::result_type                           DistanceType;
+
+
+                            ListPointPairwiseDistances(Container container): 
+                                container_(container)                       {}
+
+        DistanceType        operator()(IndexType a, IndexType b) const      { return distance_(container_[a], container_[b]); }
+
+        size_t              size() const                                    { return bp::len(container_); }
+        IndexType           begin() const                                   { return 0; }
+        IndexType           end() const                                     { return size(); }
+
+    private:
+        Container           container_;
+        Distance            distance_;
+};
+
+} }     // namespace dionysus::python
+
--- a/doc/python/rips.rst	Thu Jul 30 10:23:31 2009 -0700
+++ b/doc/python/rips.rst	Thu Jul 30 10:35:49 2009 -0700
@@ -60,9 +60,11 @@
         def __call__(self, x, y):
             return math.fabs(y-x)
 
-The bindings provide a pure Python class :class:`PairwiseDistances` to deal with
-explicit points in a Euclidean space. It is defined in
-:sfile:`bindings/python/dionysus/distances.py`::
+The bindings expose a C++ class as a Python class :class:`PairwiseDistances` to deal with
+explicit points in a Euclidean space. In pure Python it could be defined as
+follows (in fact it used to be a pure Python class, and one may still find it in 
+:sfile:`bindings/python/dionysus/distances.py`; its performance is much slower
+than its pure C++ analog)::
 
     class PairwiseDistances:
         def __init__(self, points, norm = l2):
@@ -84,6 +86,9 @@
     distances = PairwiseDistances(points)
     distances = ExplicitDistances(distances)
 
+With :class:`PairwiseDistances` being a C++ class, and
+:class:`ExplicitDistances` being pure Python, the speead-up seems minor.
+
 
 Example
 -------
--- a/doc/tutorial.rst	Thu Jul 30 10:23:31 2009 -0700
+++ b/doc/tutorial.rst	Thu Jul 30 10:35:49 2009 -0700
@@ -172,7 +172,9 @@
 Currently, when the choice comes between efficiency and flexibility, the Python
 bindings err on the side of flexibility. There is hope that in the future the
 choice won't really be necessary. Meanwhile, one can use a few techniques that
-speed up computation at the expense of memory:
+speed up computation at the expense of memory. Note, however, that since the
+recent switch of :class:`PairwiseDistances` to C++ rather than pure Python, it
+is not clear whether these deliver a substantial speed-up:
 
 * To avoid (possibly expensive) computation of distances during Rips complex
   generation, store :class:`ExplicitDistances` (see :ref:`distances`)::
--- a/examples/rips/rips-pairwise.py	Thu Jul 30 10:23:31 2009 -0700
+++ b/examples/rips/rips-pairwise.py	Thu Jul 30 10:35:49 2009 -0700
@@ -9,7 +9,7 @@
 def main(filename, skeleton, max):
     points = [p for p in points_file(filename)]
     distances = PairwiseDistances(points)
-    distances = ExplicitDistances(distances)           # speeds up generation of the Rips complex at the expense of memory usage
+    # distances = ExplicitDistances(distances)           # speeds up generation of the Rips complex at the expense of memory usage
     rips = Rips(distances)
     print time.asctime(), "Rips initialized"
 
--- a/include/geometry/distances.h	Thu Jul 30 10:23:31 2009 -0700
+++ b/include/geometry/distances.h	Thu Jul 30 10:35:49 2009 -0700
@@ -1,6 +1,8 @@
 #ifndef __DISTANCES_H__
 #define __DISTANCES_H__
 
+#include <vector>
+
 /**
  * Class: ExplicitDistances 
  * Stores the pairwise distances of Distances_ instance passed at construction.