Robert Kern wrote:

> On Wed, Mar 13, 2013 at 12:16 AM, Neal Becker <ndbeck...@gmail.com> wrote:
>> I guess I talked to you about 100 years ago about sharing state between numpy
>> rng and code I have in c++ that wraps boost::random.  So is there a C-api for
>> this RandomState object I could use to call from c++?  Maybe I could do
>> something with that.
> 
> There is not one currently. Cython has provisions for sharing such
> low-level access to other Cython extensions, but I'm not sure how well
> it works for exporting data pointers and function pointers to general
> C/++ code. We could probably package the necessities into a struct and
> export a pointer to it via a PyCapsule.
> 

I did find a way to do this, and the results are good enough.  Timing is quite 
comparable to my pure c++ implementation.

I used rk_ulong from mtrand.so.  I also tried using rk_fill, but it was a bit 
slower.

The boost::python c++ code is attached, for posterity.
#include <boost/python.hpp>

// Include this AFTER Python.h; in projects with multiple source files, you may have to
// #define some things first (see the numpy C-API documentation).
#include <numpy/arrayobject.h>
#include <boost/numpy/dtype.hpp>

#include <boost/python/class.hpp>
#include <boost/python/init.hpp>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/args.hpp>
#include <boost/python/str.hpp>
#include <boost/python/call.hpp>
#include <boost/numpy.hpp>
#include <ndarray/Array.h>
#include <ndarray/bp/auto.h>
#include <inttypes.h>
#include <stdexcept>

using namespace boost::python;
namespace bp=boost::python;
namespace nd=ndarray;

#define RK_STATE_LEN 624

typedef struct rk_state_
{
    unsigned long key[RK_STATE_LEN];
    int pos;
    int has_gauss; /* !=0: gauss contains a gaussian deviate */
    double gauss;

    /* The rk_state structure has been extended to store the following
     * information for the binomial generator. If the input values of n or p
     * are different than nsave and psave, then the other parameters will be
     * recomputed. RTK 2005-09-02 */

    int has_binomial; /* !=0: following parameters initialized for
                              binomial */
    double psave;
    long nsave;
    double r;
    double q;
    double fm;
    long m;
    double p1;
    double xm;
    double xl;
    double xr;
    double c;
    double laml;
    double lamr;
    double p2;
    double p3;
    double p4;

}
rk_state;

struct RandomState {
  PyObject_HEAD
  rk_state *internal_state;
};

extern "C" void rk_fill(void *buffer, size_t size, rk_state *state);
extern "C" unsigned long rk_ulong(rk_state *state);


inline NPY_TYPES dtype_to_type (bp::object const& dtype, NPY_TYPES _default) {
  if (dtype == bp::object())
    return _default;
  else {
    boost::numpy::dtype dt (dtype);
    return NPY_TYPES (reinterpret_cast<PyArray_Descr*>(dt.ptr())->type_num);
    // PyArray_Descr *tp_descr;
    // if (PyArray_DescrConverter(dtype.ptr(), &tp_descr) != NPY_SUCCEED)
    //   throw bp::error_already_set();
    // return NPY_TYPES(tp_descr->type_num);
  }
}

template<int> struct type_imap;
template<>
struct type_imap<NPY_INT8> {
  typedef npy_int8 type;
};
template<>
struct type_imap<NPY_INT16> {
  typedef npy_int16 type;
};
template<>
struct type_imap<NPY_INT32> {
  typedef npy_int32 type;
};
template<>
struct type_imap<NPY_INT64> {
  typedef npy_int64 type;
};
template<>
struct type_imap<NPY_FLOAT32> {
  typedef float type;
};
template<>
struct type_imap<NPY_FLOAT64> {
  typedef double type;
};
template<>
struct type_imap<NPY_COMPLEX64> {
  typedef std::complex<float> type;
};
template<>
struct type_imap<NPY_COMPLEX128> {
  typedef std::complex<double> type;
};


  template <class IntType=uint64_t>
  class pnseq_generator {
  public:
    typedef IntType result_type;
    typedef IntType input_type;

    explicit  pnseq_generator(int _width) :  width (_width), count (0),
					     mask (
						   _width == std::numeric_limits<result_type>::digits ? ~(result_type(0)) : ~(result_type(-1LL) << _width))
    {
      if (std::numeric_limits<result_type>::digits < width or width <= 0)
	throw std::runtime_error ("pnseq_generator::invalid width");
    }

    explicit pnseq_generator (int _width, int _count, result_type _cache):
      width (_width), count (_count), cache (_cache), mask (~(result_type(-1LL) << _width)) {}

    template<class Engine>
    result_type operator() (Engine& eng) {

      //      BOOST_STATIC_ASSERT (sizeof (typename Engine::result_type) >= width);

      if (count <= width-1) {
	cache = eng();
	count = std::min (std::numeric_limits<typename Engine::result_type>::digits, std::numeric_limits<result_type>::digits);
      }
      result_type bits = cache & mask;
      cache >>= width;
      count -= width;
      return bits;
    }

    //  private:
    const int width;
    int count;
    result_type cache;
    const result_type mask;

  };

template<typename out_type=uint64_t>
struct engine {
  typedef out_type result_type;
  bp::object rs;		// RandomState object

  engine (bp::object const& _rs) : rs (_rs) {}

  result_type operator() () {
    RandomState* r = (RandomState*)(rs.ptr());
    return rk_ulong (r->internal_state);
    //    result_type buffer;
    //    rk_fill ((void*)&buffer, sizeof(buffer), r->internal_state);
    //    return buffer;
  }
};

template<typename result_type=uint64_t>
struct wrapped_pn {
  engine<result_type> eng;
  pnseq_generator<result_type> pn;

  wrapped_pn (bp::object const& _rs, int _width) :
    eng (_rs), pn (_width) {}

  // for pickle
  wrapped_pn (bp::object const& _rs, int width, int count, typename pnseq_generator<result_type>::result_type cache) :
    eng (_rs),
    pn (width, count, cache) {}

  typename pnseq_generator<result_type>::result_type generate1() { return pn (eng); }

  template<typename T>
  object generate_impl (npy_intp n) {
    nd::Array<T,1,1> out = nd::allocate (n);
    auto i = boost::begin(out);
    auto e = boost::end(out);
    for (; i < e; ++i)
      *i = generate1();
    
    return object (out);
  }

  object generate (npy_intp size, object const& dtype) {
    NPY_TYPES type = dtype_to_type (dtype, NPY_INT);

    if (type == NPY_BYTE)
      return generate_impl<type_imap<NPY_BYTE>::type> (size);
    else if (type == NPY_SHORT)
      return generate_impl<type_imap<NPY_SHORT>::type> (size);
    else if (type == NPY_INT)
      return generate_impl<type_imap<NPY_INT>::type> (size);
    else if (type == NPY_LONG)
      return generate_impl<type_imap<NPY_LONG>::type> (size);
    else
      throw std::runtime_error ("pn::generate bad dtype");
  }

  object get_rs() const { return eng.rs; }
  void set_rs(bp::object const& rs) { eng.rs = rs; }
  int get_width() const { return pn.width; }
};

struct pn_pickle_suite : bp::pickle_suite {
  static bp::tuple getinitargs (wrapped_pn<uint64_t> const& gen) {
    return bp::make_tuple (gen.eng.rs, gen.pn.width, gen.pn.count, gen.pn.cache);
  }
};

BOOST_PYTHON_MODULE (pn64) {
  boost::numpy::initialize();
  import_array();

  class_<engine<uint64_t> > ("engine", init<bp::object>())
    .def ("__call__", &engine<uint64_t>::operator())
    ;

  class_<wrapped_pn<uint64_t> > ("pn",
				bp::init<bp::object, int>((bp::arg("rng"),
					      bp::arg("bits")
					      ),
					     "__init__(r,bits)\n"
					     "Construct a pn-generator\n\n"
					     ":Parameters:\n"
					     "  `r` : rng\n"
					     "    Mersenne Twister\n"
					     "  `bits` : int\n"
					     "    #bits\n"
					     )
			)
    .def (bp::init<bp::object,int,int,pnseq_generator<uint64_t>::result_type>()) // for pickle
    .def ("__call__", &wrapped_pn<uint64_t>::generate,
	  bp::default_call_policies(),
	  (bp::arg ("size"),
	   bp::arg ("dtype")=object()),
	  "__call__(cnt[,dtype])\n"
	  "Without cnt, generate a single r.v.\n"
	  "With cnt generate a vector of r.v.'s.\n\n"
	  ":Returns: a single r.v. or a vector of r.v.'s\n"
	  "  The returned values will be placed in the lowest bits of an integer.\n"
	  )
    .def ("__call__", &wrapped_pn<uint64_t>::generate1)
    .add_property ("width", &wrapped_pn<uint64_t>::get_width)
    //    .def_readwrite ("eng", &wrapped_pn<uint64_t>::eng)
    .add_property ("rs", &wrapped_pn<uint64_t>::get_rs, &wrapped_pn<uint64_t>::set_rs)
    .def_pickle (pn_pickle_suite())
    ;

}

_______________________________________________
NumPy-Discussion mailing list
NumPy-Discussion@scipy.org
http://mail.scipy.org/mailman/listinfo/numpy-discussion

Reply via email to