2
0
mirror of https://github.com/boostorg/python.git synced 2026-01-24 18:12:43 +00:00

NumPy (Numeric and numarray) support

[SVN r15521]
This commit is contained in:
Dave Abrahams
2002-09-26 00:16:16 +00:00
parent 31a8be0434
commit 17879958ca
12 changed files with 839 additions and 12 deletions

View File

@@ -13,6 +13,8 @@ if $(UNIX) && ( $(OS) = AIX )
dll bpl
:
src/numeric.cpp
src/list.cpp
src/long.cpp
src/dict.cpp

View File

@@ -105,7 +105,7 @@ class dict : public object
BOOST_PYTHON_DECL list values() const;
public: // implementation detail -- for internal use only
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(dict)
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(dict, object)
private:
static BOOST_PYTHON_DECL detail::new_reference call(object const&);

View File

@@ -94,7 +94,7 @@ class list : public object
}
public: // implementation detail -- for internal use only
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(list)
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(list, object)
private:
static BOOST_PYTHON_DECL detail::new_non_null_reference call(object const&);

View File

@@ -31,7 +31,7 @@ class long_ : public object
{
}
public: // implementation detail -- for internal use only
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(long_)
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(long_, object)
private:
static BOOST_PYTHON_DECL detail::new_non_null_reference call(object const&);

View File

@@ -0,0 +1,230 @@
// Copyright David Abrahams 2002. Permission to copy, use,
// modify, sell and distribute this software is granted provided this
// copyright notice appears in all copies. This software is provided
// "as is" without express or implied warranty, and with no claim as
// to its suitability for any purpose.
#ifndef NUMARRAY_DWA2002922_HPP
# define NUMARRAY_DWA2002922_HPP
# include <boost/python/tuple.hpp>
# include <boost/python/str.hpp>
# include <boost/preprocessor/iteration/local.hpp>
# include <boost/preprocessor/cat.hpp>
# include <boost/preprocessor/repetition/enum_params.hpp>
# include <boost/preprocessor/repetition/enum_binary_params.hpp>
namespace boost { namespace python { namespace numeric {
namespace aux
{
struct BOOST_PYTHON_DECL array_base : object
{
# define BOOST_PP_LOCAL_MACRO(n) \
array_base(BOOST_PP_ENUM_PARAMS(n, object const& x));
# define BOOST_PP_LOCAL_LIMITS (1, 7)
# include BOOST_PP_LOCAL_ITERATE()
object argmax(long axis=-1);
object argmin(long axis=-1);
object argsort(long axis=-1);
object astype(object const& type = object());
void byteswap();
object copy() const;
object diagonal(long offset = 0, long axis1 = 0, long axis2 = 1) const;
void info() const;
bool is_c_array() const;
bool isbyteswapped() const;
object new_(object type) const;
void sort();
object trace(long offset = 0, long axis1 = 0, long axis2 = 1) const;
object type() const;
char typecode() const;
object factory(object const& buffer=object()
, object const& type=object()
, object const& shape=object()
, bool copy = true
, bool savespace = false
, object typecode = object());
object getflat() const;
long getrank() const;
object getshape() const;
bool isaligned() const;
bool iscontiguous() const;
long itemsize() const;
long nelements() const;
object nonzero() const;
void put(object const& indices, object const& values);
void ravel();
object repeat(object const& repeats, long axis=0);
void resize(object const& shape);
void setflat(object const& flat);
void setshape(object const& shape);
void swapaxes(long axis1, long axis2);
object take(object const& sequence, long axis = 0) const;
void tofile(object const& file) const;
str tostring() const;
void transpose(object const& axes = object());
object view() const;
public: // implementation detail - do not touch.
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(array_base, object);
};
struct BOOST_PYTHON_DECL array_object_manager_traits
{
static bool check(PyObject* obj);
static detail::new_non_null_reference adopt(PyObject* obj);
};
} // namespace aux
class array : public aux::array_base
{
typedef aux::array_base base;
public:
object astype() { return base::astype(); }
template <class Type>
object astype(Type const& type_)
{
return base::astype(object(type_));
}
template <class Type>
object new_(Type const& type_) const
{
return base::new_(object(type_));
}
template <class Sequence>
void resize(Sequence const& x)
{
base::resize(object(x));
}
# define BOOST_PP_LOCAL_MACRO(n) \
void resize(BOOST_PP_ENUM_PARAMS(n, long x)) \
{ \
resize(make_tuple(BOOST_PP_ENUM_PARAMS(n, x))); \
}
# define BOOST_PP_LOCAL_LIMITS (1, BOOST_PYTHON_MAX_ARITY)
# include BOOST_PP_LOCAL_ITERATE()
template <class Sequence>
void setshape(Sequence const& x)
{
base::setshape(object(x));
}
# define BOOST_PP_LOCAL_MACRO(n) \
void setshape(BOOST_PP_ENUM_PARAMS(n, long x)) \
{ \
setshape(make_tuple(BOOST_PP_ENUM_PARAMS(n, x))); \
}
# define BOOST_PP_LOCAL_LIMITS (1, BOOST_PYTHON_MAX_ARITY)
# include BOOST_PP_LOCAL_ITERATE()
template <class Indices, class Values>
void put(Indices const& indices, Values const& values)
{
base::put(object(indices), object(values));
}
template <class Sequence>
object take(Sequence const& sequence, long axis = 0)
{
return base::take(object(sequence), axis);
}
template <class File>
void tofile(File const& f) const
{
base::tofile(object(f));
}
object factory()
{
return base::factory();
}
template <class Buffer>
object factory(Buffer const& buffer)
{
return base::factory(object(buffer));
}
template <class Buffer, class Type>
object factory(
Buffer const& buffer
, Type const& type_)
{
return base::factory(object(buffer), object(type_));
}
template <class Buffer, class Type, class Shape>
object factory(
Buffer const& buffer
, Type const& type_
, Shape const& shape
, bool copy = true
, bool savespace = false)
{
return base::factory(object(buffer), object(type_), object(shape), copy, savespace);
}
template <class Buffer, class Type, class Shape>
object factory(
Buffer const& buffer
, Type const& type_
, Shape const& shape
, bool copy
, bool savespace
, char typecode)
{
return base::factory(object(buffer), object(type_), object(shape), copy, savespace, object(typecode));
}
# define BOOST_PYTHON_ENUM_AS_OBJECT(z, n, x) object(BOOST_PP_CAT(x,n))
# define BOOST_PP_LOCAL_MACRO(n) \
template <BOOST_PP_ENUM_PARAMS(n, class T)> \
explicit array(BOOST_PP_ENUM_BINARY_PARAMS(n, T, const& x)) \
: array_base(BOOST_PP_ENUM(n, BOOST_PYTHON_ENUM_AS_OBJECT, x)) \
{}
# define BOOST_PP_LOCAL_LIMITS (1, 7)
# include BOOST_PP_LOCAL_ITERATE()
# undef BOOST_PYTHON_AS_OBJECT
static BOOST_PYTHON_DECL void set_module_and_type(char const* package_name = 0, char const* type_attribute_name = 0);
public: // implementation detail -- for internal use only
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(array, array_base);
};
} // namespace boost::python::numeric
namespace converter
{
template <>
struct object_manager_traits< numeric::array >
: numeric::aux::array_object_manager_traits
{
BOOST_STATIC_CONSTANT(bool, is_specialized = true);
};
}
}} // namespace boost::python
#endif // NUMARRAY_DWA2002922_HPP

View File

@@ -234,13 +234,13 @@ namespace api
// Macros for forwarding constructors in classes derived from
// object. Derived classes will usually want these as an
// implementation detail
# define BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS_(derived) \
# define BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS_(derived, base) \
inline explicit derived(python::detail::borrowed_reference p) \
: object(p) {} \
: base(p) {} \
inline explicit derived(python::detail::new_reference p) \
: object(p) {} \
: base(p) {} \
inline explicit derived(python::detail::new_non_null_reference p) \
: object(p) {}
: base(p) {}
# if !defined(BOOST_MSVC) || BOOST_MSVC > 1200
# define BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS_
@@ -253,9 +253,9 @@ namespace api
// runtime failure into an ambiguity error at compile-time due to
// the lack of partial ordering, or at least a link-time error if no
// generalized template constructor is declared.
# define BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(derived) \
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS_(derived) \
template <class T> \
# define BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(derived, base) \
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS_(derived, base) \
template <class T> \
explicit derived(extract<T> const&);
# endif

View File

@@ -352,7 +352,7 @@ class str : public object
BOOST_PYTHON_DECL str upper() const;
public: // implementation detail -- for internal use only
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(str)
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(str, object)
private:
static BOOST_PYTHON_DECL detail::new_reference call(object const&);

View File

@@ -24,7 +24,7 @@ class tuple : public object
}
public: // implementation detail -- for internal use only
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(tuple)
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(tuple, object)
private:
static BOOST_PYTHON_DECL detail::new_reference call(object const&);

313
src/numeric.cpp Normal file
View File

@@ -0,0 +1,313 @@
// Copyright David Abrahams 2002. Permission to copy, use,
// modify, sell and distribute this software is granted provided this
// copyright notice appears in all copies. This software is provided
// "as is" without express or implied warranty, and with no claim as
// to its suitability for any purpose.
#include <boost/python/numeric.hpp>
#include <boost/python/handle.hpp>
#include <boost/python/cast.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/detail/raw_pyobject.hpp>
#include <boost/python/extract.hpp>
namespace boost { namespace python { namespace numeric {
namespace
{
enum state_t { failed = -1, unknown, succeeded };
state_t state = unknown;
std::string module_name;
std::string type_name;
handle<> array_module;
object array_type;
object array_function;
void throw_load_failure()
{
PyErr_Format(
PyExc_ImportError
, "No module named '%s' or its type '%s' did not follow the NumPy protocol"
, module_name.c_str(), type_name.c_str());
throw_error_already_set();
}
bool load(bool throw_on_error)
{
if (!state)
{
if (module_name.size() == 0)
{
module_name = "numarray";
type_name = "NDArray";
if (load(false))
return true;
module_name = "Numeric";
type_name = "ArrayType";
}
state = failed;
PyObject* module = ::PyImport_Import(object(module_name).ptr());
if (module)
{
PyObject* type = ::PyObject_GetAttrString(module, const_cast<char*>(type_name.c_str()));
if (type && PyType_Check(type))
{
array_type = object(detail::new_non_null_reference(type));
PyObject* function = ::PyObject_GetAttrString(module, const_cast<char*>("array"));
if (function && PyCallable_Check(function))
{
array_function = object(detail::new_reference(function));
state = succeeded;
}
}
}
}
if (state == succeeded)
return true;
if (throw_on_error)
throw_load_failure();
PyErr_Clear();
return false;
}
object const& demand_array_function()
{
load(true);
return array_function;
}
}
void array::set_module_and_type(char const* package_name, char const* type_attribute_name)
{
state = unknown;
module_name = package_name ? package_name : "" ;
type_name = type_attribute_name ? type_attribute_name : "" ;
}
namespace aux
{
bool array_object_manager_traits::check(PyObject* obj)
{
if (!load(false))
return false;
return ::PyObject_IsInstance(obj, array_type.ptr());
}
python::detail::new_non_null_reference
array_object_manager_traits::adopt(PyObject* obj)
{
load(true);
return detail::new_non_null_reference(
pytype_check(downcast<PyTypeObject>(array_type.ptr()), obj));
}
# define BOOST_PYTHON_AS_OBJECT(z, n, _) object(x##n)
# define BOOST_PP_LOCAL_MACRO(n) \
array_base::array_base(BOOST_PP_ENUM_PARAMS(n, object const& x)) \
: object((load(true), array_function)(BOOST_PP_ENUM_PARAMS(n, x))) \
{}
# define BOOST_PP_LOCAL_LIMITS (1, 6)
# include BOOST_PP_LOCAL_ITERATE()
# undef BOOST_PYTHON_AS_OBJECT
array_base::array_base(BOOST_PP_ENUM_PARAMS(7, object const& x))
: object((load(true), array_type)(BOOST_PP_ENUM_PARAMS(7, x)))
{}
object array_base::argmax(long axis)
{
return attr("argmax")(axis);
}
object array_base::argmin(long axis)
{
return attr("argmin")(axis);
}
object array_base::argsort(long axis)
{
return attr("argsort")(axis);
}
object array_base::astype(object const& type)
{
return attr("astype")(type);
}
void array_base::byteswap()
{
attr("byteswap")();
}
object array_base::copy() const
{
return attr("copy")();
}
object array_base::diagonal(long offset, long axis1, long axis2) const
{
return attr("diagonal")(offset, axis1, axis2);
}
void array_base::info() const
{
attr("info")();
}
bool array_base::is_c_array() const
{
return extract<bool>(attr("is_c_array")());
}
bool array_base::isbyteswapped() const
{
return extract<bool>(attr("isbyteswapped")());
}
object array_base::new_(object type) const
{
return attr("new")(type);
}
void array_base::sort()
{
attr("sort")();
}
object array_base::trace(long offset, long axis1, long axis2) const
{
return attr("trace")(offset, axis1, axis2);
}
object array_base::type() const
{
return attr("type")();
}
char array_base::typecode() const
{
return extract<char>(attr("typecode")());
}
object array_base::factory(object const& buffer
, object const& type
, object const& shape
, bool copy
, bool savespace
, object typecode)
{
return attr("array")(buffer, type, shape, copy, savespace, typecode);
}
object array_base::getflat() const
{
return attr("getflat")();
}
long array_base::getrank() const
{
return extract<long>(attr("getrank")());
}
object array_base::getshape() const
{
return attr("getshape")();
}
bool array_base::isaligned() const
{
return extract<bool>(attr("isaligned"));
}
bool array_base::iscontiguous() const
{
return extract<bool>(attr("isaligned"));
}
long array_base::itemsize() const
{
return extract<long>(attr("itemsize"));
}
long array_base::nelements() const
{
return extract<long>(attr("nelements"));
}
object array_base::nonzero() const
{
return attr("nonzero")();
}
void array_base::put(object const& indices, object const& values)
{
attr("put")(indices, values);
}
void array_base::ravel()
{
attr("ravel")();
}
object array_base::repeat(object const& repeats, long axis)
{
return attr("repeat")(repeats, axis);
}
void array_base::resize(object const& shape)
{
attr("resize")(shape);
}
void array_base::setflat(object const& flat)
{
attr("setflat")(flat);
}
void array_base::setshape(object const& shape)
{
attr("setshape")(shape);
}
void array_base::swapaxes(long axis1, long axis2)
{
attr("swapaxes")(axis1, axis2);
}
object array_base::take(object const& sequence, long axis) const
{
return attr("take")(sequence, axis);
}
void array_base::tofile(object const& file) const
{
attr("tofile")(file);
}
str array_base::tostring() const
{
return str(attr("tostring")());
}
void array_base::transpose(object const& axes)
{
attr("transpose")(axes);
}
object array_base::view() const
{
return attr("view")();
}
}
}}} // namespace boost::python::numeric

View File

@@ -54,6 +54,8 @@ rule bpl-test ( name ? : files * : requirements * )
boost-python-runtest $(name) : $(py) <pyd>$(modules) ;
}
bpl-test numpy ;
bpl-test enum ;
bpl-test minimal ;
bpl-test docstring ;

105
test/numpy.cpp Normal file
View File

@@ -0,0 +1,105 @@
// Copyright David Abrahams 2002. Permission to copy, use,
// modify, sell and distribute this software is granted provided this
// copyright notice appears in all copies. This software is provided
// "as is" without express or implied warranty, and with no claim as
// to its suitability for any purpose.
#include <boost/python/numeric.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/module_init.hpp>
#include <boost/python/def.hpp>
using namespace boost::python;
// See if we can invoke array() from C++
object new_array()
{
return numeric::array(
make_tuple(
make_tuple(1,2,3)
, make_tuple(4,5,6)
, make_tuple(7,8,9)
)
);
}
// test argument conversion
void take_array(numeric::array x)
{
}
// A separate function to invoke the info() member. Must happen
// outside any doctests since this prints directly to stdout and the
// result text includes the address of the 'self' array.
void info(numeric::array const& z)
{
z.info();
}
// Tests which work on both Numeric and numarray array objects. Of
// course all of the operators "just work" since numeric::array
// inherits that behavior from object.
void exercise(numeric::array& y, object check)
{
y[make_tuple(2,1)] = 3;
check(y);
check(y.astype('D'));
check(y.copy());
check(y.typecode());
}
// numarray-specific tests. check is a callable object which we can
// use to record intermediate results, which are later compared with
// the results of corresponding python operations.
void exercise_numarray(numeric::array& y, object check)
{
check(y.astype());
check(y.argmax());
check(y.argmax(0));
check(y.argmin());
check(y.argmin(0));
check(y.argsort());
check(y.argsort(1));
y.byteswap();
check(y);
check(y.diagonal());
check(y.diagonal(1));
check(y.diagonal(0, 1));
check(y.diagonal(0, 1, 0));
check(y.is_c_array());
check(y.isbyteswapped());
check(y.trace());
check(y.trace(1));
check(y.trace(0, 1));
check(y.trace(0, 1, 0));
check(y.new_('D'));
y.sort();
check(y);
check(y.type());
check(y.factory(make_tuple(1.2, 3.4)));
check(y.factory(make_tuple(1.2, 3.4), "Double"));
check(y.factory(make_tuple(1.2, 3.4), "Double", make_tuple(1,2,1)));
check(y.factory(make_tuple(1.2, 3.4), "Double", make_tuple(2,1,1), false));
check(y.factory(make_tuple(1.2, 3.4), "Double", make_tuple(2), true, true));
}
BOOST_PYTHON_MODULE_INIT(numpy_ext)
{
def("new_array", new_array);
def("take_array", take_array);
def("exercise", exercise);
def("exercise_numarray", exercise_numarray);
def("set_module_and_type", &numeric::array::set_module_and_type);
def("info", info);
}
#include "module_tail.cpp"

175
test/numpy.py Normal file
View File

@@ -0,0 +1,175 @@
def numeric_tests():
'''
>>> from numpy_ext import *
>>> x = new_array()
>>> x[1,1] = 0.0
>>> try: take_array(3)
... except TypeError: pass
... else: print 'expected a TypeError'
>>> take_array(x)
>>> print x
[[1 2 3]
[4 0 6]
[7 8 9]]
>>> y = x.copy()
>>> p = _printer()
>>> check = p.check
>>> exercise(x, p)
>>> y[2,1] = 3
>>> check(y);
>>> check(y.astype('D'));
>>> check(y.copy());
>>> check(y.typecode());
>>> p.results
[]
>>> del p
'''
pass
def _numarray_tests():
'''
>>> from numpy_ext import *
>>> x = new_array()
>>> y = x.copy()
>>> p = _printer()
>>> check = p.check
>>> exercise_numarray(x, p)
>>> check(y.astype());
>>> check(y.argmax());
>>> check(y.argmax(0));
>>> check(y.argmin());
>>> check(y.argmin(0));
>>> check(y.argsort());
>>> check(y.argsort(1));
>>> y.byteswap();
>>> check(y);
>>> check(y.diagonal());
>>> check(y.diagonal(1));
>>> check(y.diagonal(0, 1));
>>> check(y.diagonal(0, 1, 0));
>>> check(y.is_c_array());
>>> check(y.isbyteswapped());
>>> check(y.trace());
>>> check(y.trace(1));
>>> check(y.trace(0, 1));
>>> check(y.trace(0, 1, 0));
>>> check(y.new('D'));
>>> y.sort();
>>> check(y);
>>> check(y.type());
>>> check(y.array((1.2, 3.4)));
>>> check(y.array((1.2, 3.4), "Double"));
>>> check(y.array((1.2, 3.4), "Double", (1,2,1)));
>>> check(y.array((1.2, 3.4), "Double", (2,1,1), false));
>>> check(y.array((1.2, 3.4), "Double", (2,), true, true));
>>> p.results
[]
>>> del p
'''
pass
false = 0;
true = 1;
class _printer(object):
def __init__(self):
self.results = [];
def __call__(self, *stuff):
self.results += [ str(x) for x in stuff ]
def check(self, x):
if self.results[0] == str(x):
del self.results[0]
else:
print ' Expected:\n %s\n but got:\n %s' % (x, self.results[0])
def _run(args = None):
import sys
import doctest
if args is not None:
sys.argv = args
# See which of the numeric modules are installed
has_numeric = 0
try:
import Numeric
m = Numeric
has_numeric = 1
except ImportError: pass
has_numarray = 0
try:
import numarray
m = numarray
has_numarray = 1
except ImportError: pass
# Bail if neither one is installed
if not (has_numeric or has_numarray):
return 0
# test the info routine outside the doctest. See numpy.cpp for an
# explanation
import numpy_ext
if (has_numarray):
numpy_ext.info(m.array((1,2,3)))
failures = 0
#
# Run tests 4 different ways if both modules are installed, just
# to show that set_module_and_type() is working properly
#
# run all the tests with default module search
print 'testing default extension module'
failures += doctest.testmod(sys.modules.get(__name__))[0]
# test against Numeric if installed
if has_numeric:
print 'testing Numeric module explicitly'
numpy_ext.set_module_and_type('Numeric', 'ArrayType')
failures += doctest.testmod(sys.modules.get(__name__))[0]
global __test__
if has_numarray:
# Add the _numarray_tests to the list of things to test in
# this case.
__test__ = { 'numarray_tests':_numarray_tests,
'numeric_tests': numeric_tests }
print 'testing numarray module explicitly'
numpy_ext.set_module_and_type('numarray', 'NDArray')
failures += doctest.testmod(sys.modules.get(__name__))[0]
# see that we can go back to the default
del __test__
print 'testing default module again'
numpy_ext.set_module_and_type('', '')
failures += doctest.testmod(sys.modules.get(__name__))[0]
return failures
if __name__ == '__main__':
print "running..."
import sys
sys.exit(_run())