gaussian.cpp
Go to the documentation of this file.
00001 // Copyright Jim Bosch 2010-2012.
00002 // Distributed under the Boost Software License, Version 1.0.
00003 //    (See accompanying file LICENSE_1_0.txt or copy at
00004 //          http://www.boost.org/LICENSE_1_0.txt)
00005 
00006 #include <boost/numpy.hpp>
00007 
00008 #include <cmath>
00009 #include <memory>
00010 
00011 #ifndef M_PI
00012 #include <boost/math/constants/constants.hpp>
00013 const double M_PI = boost::math::constants::pi<double>();
00014 #endif
00015 
00016 namespace bp = boost::python;
00017 namespace bn = boost::numpy;
00018 
00024 class matrix2 {
00025 public:
00026 
00027     double & operator()(int i, int j) {
00028         return _data[i*2 + j];
00029     }
00030 
00031     double const & operator()(int i, int j) const {
00032         return _data[i*2 + j];
00033     }
00034     
00035     double const * data() const { return _data; }
00036 
00037 private:
00038     double _data[4];
00039 };
00040 
00046 class vector2 {
00047 public:
00048 
00049     double & operator[](int i) {
00050         return _data[i];
00051     }
00052 
00053     double const & operator[](int i) const {
00054         return _data[i];
00055     }
00056     
00057     double const * data() const { return _data; }
00058 
00059     vector2 operator+(vector2 const & other) const {
00060         vector2 r;
00061         r[0] = _data[0] + other[0];
00062         r[1] = _data[1] + other[1];
00063         return  r;
00064     }
00065 
00066     vector2 operator-(vector2 const & other) const {
00067         vector2 r;
00068         r[0] = _data[0] - other[0];
00069         r[1] = _data[1] - other[1];
00070         return  r;
00071     }
00072 
00073 private:
00074     double _data[2];
00075 };
00076 
00080 vector2 operator*(matrix2 const & m, vector2 const & v) {
00081     vector2 r;
00082     r[0] = m(0, 0) * v[0] + m(0, 1) * v[1];
00083     r[1] = m(1, 0) * v[0] + m(1, 1) * v[1];
00084     return r;
00085 }
00086 
00090 double dot(vector2 const & v1, vector2 const & v2) {
00091     return v1[0] * v2[0] + v1[1] * v2[1];
00092 }
00093 
00098 class bivariate_gaussian {
00099 public:
00100 
00101     vector2 const & get_mu() const { return _mu; }
00102 
00103     matrix2 const & get_sigma() const { return _sigma; }
00104 
00108     double operator()(vector2 const & p) const {
00109         vector2 u = _cholesky * (p - _mu);
00110         return 0.5 * _cholesky(0, 0) * _cholesky(1, 1) * std::exp(-0.5 * dot(u, u)) / M_PI;
00111     }
00112 
00116     double operator()(double x, double y) const {
00117         vector2 p;
00118         p[0] = x;
00119         p[1] = y;
00120         return operator()(p);
00121     }
00122 
00126     bivariate_gaussian(vector2 const & mu, matrix2 const & sigma)
00127         : _mu(mu), _sigma(sigma), _cholesky(compute_inverse_cholesky(sigma))
00128     {}
00129     
00130 private:
00131 
00136     static matrix2 compute_inverse_cholesky(matrix2 const & m) {
00137         matrix2 l;
00138         // First do cholesky factorization: l l^t = m
00139         l(0, 0) = std::sqrt(m(0, 0));
00140         l(0, 1) = m(0, 1) / l(0, 0);
00141         l(1, 1) = std::sqrt(m(1, 1) - l(0,1) * l(0,1));
00142         // Now do forward-substitution (in-place) to invert:
00143         l(0, 0) = 1.0 / l(0, 0);
00144         l(1, 0) = l(0, 1) = -l(0, 1) / l(1, 1);
00145         l(1, 1) = 1.0 / l(1, 1);
00146         return l;
00147     }
00148 
00149     vector2 _mu;
00150     matrix2 _sigma;
00151     matrix2 _cholesky;
00152                         
00153 };
00154 
00155 /*
00156  *  We have a two options for wrapping get_mu and get_sigma into NumPy-returning Python methods:
00157  *   - we could deep-copy the data, making totally new NumPy arrays;
00158  *   - we could make NumPy arrays that point into the existing memory.
00159  *  The latter is often preferable, especially if the arrays are large, but it's dangerous unless
00160  *  the reference counting is correct: the returned NumPy array needs to hold a reference that
00161  *  keeps the memory it points to from being deallocated as long as it is alive.  This is what the
00162  *  "owner" argument to from_data does - the NumPy array holds a reference to the owner, keeping it
00163  *  from being destroyed.
00164  *
00165  *  Note that this mechanism isn't completely safe for data members that can have their internal
00166  *  storage reallocated.  A std::vector, for instance, can be invalidated when it is resized,
00167  *  so holding a Python reference to a C++ class that holds a std::vector may not be a guarantee
00168  *  that the memory in the std::vector will remain valid.
00169  */
00170 
00179 static bn::ndarray py_get_mu(bp::object const & self) {
00180     vector2 const & mu = bp::extract<bivariate_gaussian const &>(self)().get_mu();
00181     return bn::from_data(
00182         mu.data(),
00183         bn::dtype::get_builtin<double>(),
00184         bp::make_tuple(2),
00185         bp::make_tuple(sizeof(double)),
00186         self
00187     );  
00188 }
00189 static bn::ndarray py_get_sigma(bp::object const & self) {
00190     matrix2 const & sigma = bp::extract<bivariate_gaussian const &>(self)().get_sigma();
00191     return bn::from_data(
00192         sigma.data(),
00193         bn::dtype::get_builtin<double>(),
00194         bp::make_tuple(2, 2),
00195         bp::make_tuple(2 * sizeof(double), sizeof(double)),
00196         self
00197     );
00198 }
00199 
00211 static void copy_ndarray_to_mv2(bn::ndarray const & array, vector2 & vec) {
00212     vec[0] = bp::extract<double>(array[0]);
00213     vec[1] = bp::extract<double>(array[1]);
00214 }
00215 
00220 static void copy_ndarray_to_mv2(bn::ndarray const & array, matrix2 & mat) {
00221     // Unfortunately, get_strides() can't be inlined, so it's best to call it once up-front.
00222     Py_intptr_t const * strides = array.get_strides();
00223     for (int i = 0; i < 2; ++i) {
00224         for (int j = 0; j < 2; ++j) {
00225             mat(i, j) = *reinterpret_cast<double const *>(array.get_data() + i * strides[0] + j * strides[1]);
00226         }
00227     }
00228 }
00229 
00234 template <typename T, int N>
00235 struct mv2_from_python {
00236     
00240     mv2_from_python() {
00241         bp::converter::registry::push_back(
00242             &convertible,
00243             &construct,
00244             bp::type_id< T >()
00245         );
00246     }
00247 
00252     static void * convertible(PyObject * p) {
00253         try {
00254             bp::object obj(bp::handle<>(bp::borrowed(p)));
00255             std::auto_ptr<bn::ndarray> array(
00256                 new bn::ndarray(
00257                     bn::from_object(obj, bn::dtype::get_builtin<double>(), N, N, bn::ndarray::V_CONTIGUOUS)
00258                 )
00259             );
00260             if (array->shape(0) != 2) return 0;
00261             if (N == 2 && array->shape(1) != 2) return 0;
00262             return array.release();
00263         } catch (bp::error_already_set & err) {
00264             bp::handle_exception();
00265             return 0;
00266         }
00267     }
00268 
00272     static void construct(PyObject * obj, bp::converter::rvalue_from_python_stage1_data * data) {
00273         // Extract the array we passed out of the convertible() member function.
00274         std::auto_ptr<bn::ndarray> array(reinterpret_cast<bn::ndarray*>(data->convertible));
00275         // Find the memory block Boost.Python has prepared for the result.
00276         typedef bp::converter::rvalue_from_python_storage<T> storage_t;
00277         storage_t * storage = reinterpret_cast<storage_t*>(data);
00278         // Use placement new to initialize the result.
00279         T * m_or_v = new (storage->storage.bytes) T();
00280         // Fill the result with the values from the NumPy array.
00281         copy_ndarray_to_mv2(*array, *m_or_v);
00282         // Finish up.
00283         data->convertible = storage->storage.bytes;
00284     }
00285 
00286 };
00287 
00288 
00289 BOOST_PYTHON_MODULE(gaussian) {
00290     bn::initialize();
00291 
00292     // Register the from-python converters
00293     mv2_from_python< vector2, 1 >();
00294     mv2_from_python< matrix2, 2 >();
00295 
00296     typedef double (bivariate_gaussian::*call_vector)(vector2 const &) const;
00297 
00298     bp::class_<bivariate_gaussian>("bivariate_gaussian", bp::init<bivariate_gaussian const &>())
00299 
00300         // Declare the constructor (wouldn't work without the from-python converters).
00301         .def(bp::init< vector2 const &, matrix2 const & >())
00302 
00303         // Use our custom reference-counting getters
00304         .add_property("mu", &py_get_mu)
00305         .add_property("sigma", &py_get_sigma)
00306 
00307         // First overload accepts a two-element array argument
00308         .def("__call__", (call_vector)&bivariate_gaussian::operator())
00309 
00310         // This overload works like a binary NumPy universal function: you can pass
00311         // in scalars or arrays, and the C++ function will automatically be called
00312         // on each element of an array argument.
00313         .def("__call__", bn::binary_ufunc<bivariate_gaussian,double,double,double>::make())
00314         ;
00315 }


boost_numpy
Author(s): Jim Bosch, Ankit Daftery
autogenerated on Fri Aug 28 2015 10:10:40