From d78836b82844e73bca9310699a0c2339eb90f5df Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Mon, 9 Oct 2006 04:05:25 +0000 Subject: [PATCH] Fix lots of bugs in the numeric interface and tests. Tests: * Coerce a result to bool to deal with Python's new Bool type * Better reporting of mismatches in expected and received results * Remove bogus nullary y.astype() call * Fix all uses of trace and diagonal so they don't cause errors * Use appropriate typecodes * Use doctest detailed API to run just the relevant tests * Factor out error handling from macro API: * Added get_module_name() function to get current numeric module * new_(x) now returns an array instead of object * Fixed the signatures of the factory() family of functions * Updated docs accordingly. [SVN r35528] --- doc/v2/numeric.html | 268 ++++++++++++++++--------------- include/boost/python/numeric.hpp | 73 +++++---- src/numeric.cpp | 25 +-- test/numpy.cpp | 44 +++-- test/numpy.py | 67 +++++--- 5 files changed, 267 insertions(+), 210 deletions(-) diff --git a/doc/v2/numeric.html b/doc/v2/numeric.html index 0817f5ef..ea04d5aa 100644 --- a/doc/v2/numeric.html +++ b/doc/v2/numeric.html @@ -1,105 +1,105 @@ - + - - - - + + + + - Boost.Python - <boost/python/numeric.hpp> - + Boost.Python - <boost/python/numeric.hpp> + - - - - + +
-

C++ Boost

-
+ + - - -
+

+

+
-

Boost.Python

+
+

Boost.Python

-

Header <boost/python/numeric.hpp>

-
-
+

Header <boost/python/numeric.hpp>

+ + + +
-

Contents

+

Contents

-
-
Introduction
+
+
Introduction
-
Classes
+
Classes
-
-
-
Class array
+
+
+
Class array
-
-
-
Class array - synopsis
+
+
+
Class array + synopsis
-
Class array - observer functions
+
Class array + observer functions
-
Class array - static functions
-
-
-
-
+
Class array static + functions
+
+
+
+
-
Example(s)
-
-
+
Example(s)
+
+
-

Introduction

+

Introduction

-

Exposes a TypeWrapper for the Python - array - type.

+

Exposes a TypeWrapper for the Python + array + type.

-

Classes

+

Classes

-

Class array

+

Class array

-

Provides access to the array types of Numerical Python's Numeric and NumArray modules. With - the exception of the functions documented below, the semantics of the constructors and - member functions defined below can be fully understood by reading the TypeWrapper concept - definition. Since array is publicly derived from object, the public object - interface applies to array instances as well.

+

Provides access to the array types of Numerical Python's Numeric and NumArray modules. With + the exception of the functions documented below, the semantics of the constructors and + member functions defined below can be fully understood by reading the + TypeWrapper concept + definition. Since array is publicly derived from + object, the public + object interface applies to array instances as well.

-

The default behavior is to use - numarray.NDArray as the associated Python type if the - numarray module is installed in the default location. - Otherwise it falls back to use Numeric.ArrayType. If neither - extension module is installed, conversions to arguments of type - numeric::array will cause overload resolution to reject the - overload, and other attempted uses of numeric::array will raise an appropriate Python exception. - The associated Python type can be set manually using the set_module_and_type(...) static - function.

+

The default behavior is + to use numarray.NDArray as the associated Python type if the + numarray module is installed in the default location. + Otherwise it falls back to use Numeric.ArrayType. If neither + extension module is installed, overloads of wrapped C++ functions with + numeric::array parameters will never be matched, and other + attempted uses of numeric::array will raise an appropriate Python exception. The + associated Python type can be set manually using the set_module_and_type(...) static + function.

-

Class array - synopsis

-
+  

Class + array synopsis

+
 namespace boost { namespace python { namespace numeric
 {
    class array : public object
@@ -110,7 +110,7 @@ namespace boost { namespace python { namespace numeric
       object astype(Type const& type_);
 
       template <class Type>
-      object new_(Type const& type_) const;
+      array new_(Type const& type_) const;
 
       template <class Sequence> 
       void resize(Sequence const& x);
@@ -136,14 +136,14 @@ namespace boost { namespace python { namespace numeric
       void tofile(File const& f) const;
 
       object factory();
-      template <class Buffer>
-      object factory(Buffer const&);
-      template <class Buffer, class Type>
-      object factory(Buffer const&, Type const&);
-      template <class Buffer, class Type, class Shape>
-      object factory(Buffer const&, Type const&, Shape const&, bool copy = true, bool savespace = false);
-      template <class Buffer, class Type, class Shape>
-      object factory(Buffer const&, Type const&, Shape const&, bool copy, bool savespace, char typecode);
+      template <class Sequence>
+      object factory(Sequence const&);
+      template <class Sequence, class Typecode>
+      object factory(Sequence const&, Typecode const&, bool copy = true, bool savespace = false);
+      template <class Sequence, class Typecode, class Type>
+      object factory(Sequence const&, Typecode const&, bool copy, bool savespace, Type const&);
+      template <class Sequence, class Typecode, class Type, class Shape>
+      object factory(Sequence const&, Typecode const&, bool copy, bool savespace, Type const&, Shape const&);
 
       template <class T1>
       explicit array(T1 const& x1);
@@ -155,6 +155,7 @@ namespace boost { namespace python { namespace numeric
 
       static void set_module_and_type();
       static void set_module_and_type(char const* package_path = 0, char const* type_name = 0);
+      static void get_module_name();
 
       object argmax(long axis=-1);
 
@@ -203,54 +204,60 @@ namespace boost { namespace python { namespace numeric
 }}}
 
-

Class array observer - functions

-
+  

Class + array observer functions

+
 object factory();
-template <class Buffer>
-object factory(Buffer const&);
-template <class Buffer, class Type>
-object factory(Buffer const&, Type const&);
-template <class Buffer, class Type, class Shape>
-object factory(Buffer const&, Type const&, Shape const&, bool copy = true, bool savespace = false);
-template <class Buffer, class Type, class Shape>
-object factory(Buffer const&, Type const&, Shape const&, bool copy, bool savespace, char typecode);
-
- These functions map to the underlying array type's array() - function family. They are not called "array" because of the - C++ limitation that you can't define a member function with the same name - as its enclosing class. -
+template <class Sequence>
+object factory(Sequence const&);
+template <class Sequence, class Typecode>
+object factory(Sequence const&, Typecode const&, bool copy = true, bool savespace = false);
+template <class Sequence, class Typecode, class Type>
+object factory(Sequence const&, Typecode const&, bool copy, bool savespace, Type const&);
+template <class Sequence, class Typecode, class Type, class Shape>
+object factory(Sequence const&, Typecode const&, bool copy, bool savespace, Type const&, Shape const&);
+
These functions map to the underlying array type's array() +function family. They are not called "array" because of the C++ +limitation that you can't define a member function with the same name as its +enclosing class. +
 template <class Type>
-object new_(Type const&) const;
-
- This function maps to the underlying array type's new() - function. It is not called "new" because that is a keyword - in C++. +array new_(Type const&) const; +
This function maps to the underlying array type's new() +function. It is not called "new" because that is a keyword in +C++. -

Class array static - functions

-
+  

Class + array static functions

+
 static void set_module_and_type(char const* package_path, char const* type_name);
 static void set_module_and_type();
 
-
-
Requires: package_path and - type_name, if supplied, is an ntbs.
+
+
Requires: package_path and + type_name, if supplied, is an ntbs.
-
Effects: The first form sets the package path of the module - which supplies the type named by type_name to - package_path. The second form restores the default search behavior. The associated Python - type will be searched for only the first time it is needed, and - thereafter the first time it is needed after an invocation of - set_module_and_type.
-
+
Effects: The first form sets the package path of the module + that supplies the type named by type_name to + package_path. The second form restores the default search behavior. The associated Python type + will be searched for only the first time it is needed, and thereafter the + first time it is needed after an invocation of + set_module_and_type.
+
+
+static std::string get_module_name()
+
-

Example

-
+  
+
Effects: Returns the name of the module containing the class + that will be held by new numeric::array instances.
+
+ +

Example

+
 #include <boost/python/numeric.hpp>
 #include <boost/python/tuple.hpp>
 
@@ -261,10 +268,9 @@ void set_first_element(numeric::array& y, double value)
 }
 
-

Revised 03 October, 2002

+

Revised 07 October, 2006

-

© Copyright Dave Abrahams 2002.

- +

© Copyright Dave + Abrahams 2002-2006.

+ - diff --git a/include/boost/python/numeric.hpp b/include/boost/python/numeric.hpp index 14aa7d6d..3868154b 100644 --- a/include/boost/python/numeric.hpp +++ b/include/boost/python/numeric.hpp @@ -17,6 +17,8 @@ namespace boost { namespace python { namespace numeric { +struct array; + namespace aux { struct BOOST_PYTHON_DECL array_base : object @@ -36,18 +38,19 @@ namespace aux void info() const; bool is_c_array() const; bool isbyteswapped() const; - object new_(object type) const; + array 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 factory( + object const& sequence = object() + , object const& typecode = object() + , bool copy = true + , bool savespace = false + , object type = object() + , object shape = object()); object getflat() const; long getrank() const; @@ -106,7 +109,7 @@ class array : public aux::array_base } template - object new_(Type const& type_) const + array new_(Type const& type_) const { return base::new_(object(type_)); } @@ -162,43 +165,48 @@ class array : public aux::array_base return base::factory(); } - template - object factory(Buffer const& buffer) + template + object factory(Sequence const& sequence) { - return base::factory(object(buffer)); + return base::factory(object(sequence)); } - template + template object factory( - Buffer const& buffer - , Type const& type_) + Sequence const& sequence + , Typecode const& typecode_ + , bool copy = true + , bool savespace = false + ) { - return base::factory(object(buffer), object(type_)); + return base::factory(object(sequence), object(typecode_), copy, savespace); } - template + template object factory( - Buffer const& buffer - , Type const& type_ - , Shape const& shape - , bool copy = true - , bool savespace = false) + Sequence const& sequence + , Typecode const& typecode_ + , bool copy + , bool savespace + , Type const& type + ) { - return base::factory(object(buffer), object(type_), object(shape), copy, savespace); + return base::factory(object(sequence), object(typecode_), copy, savespace, object(type)); } - template + template object factory( - Buffer const& buffer - , Type const& type_ - , Shape const& shape - , bool copy - , bool savespace - , char typecode) + Sequence const& sequence + , Typecode const& typecode_ + , bool copy + , bool savespace + , Type const& type + , Shape const& shape + ) { - return base::factory(object(buffer), object(type_), object(shape), copy, savespace, object(typecode)); + return base::factory(object(sequence), object(typecode_), copy, savespace, object(type), object(shape)); } - + # define BOOST_PYTHON_ENUM_AS_OBJECT(z, n, x) object(BOOST_PP_CAT(x,n)) # define BOOST_PP_LOCAL_MACRO(n) \ template \ @@ -210,6 +218,7 @@ class array : public aux::array_base # 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); + static BOOST_PYTHON_DECL std::string get_module_name(); public: // implementation detail -- for internal use only BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(array, base); diff --git a/src/numeric.cpp b/src/numeric.cpp index 3e64e141..35c469c5 100644 --- a/src/numeric.cpp +++ b/src/numeric.cpp @@ -90,7 +90,12 @@ void array::set_module_and_type(char const* package_name, char const* type_attri module_name = package_name ? package_name : "" ; type_name = type_attribute_name ? type_attribute_name : "" ; } - + +std::string array::get_module_name() +{ + load(false); + return module_name; +} namespace aux { @@ -173,9 +178,9 @@ namespace aux return extract(attr("isbyteswapped")()); } - object array_base::new_(object type) const + array array_base::new_(object type) const { - return attr("new")(type); + return extract(attr("new")(type))(); } void array_base::sort() @@ -197,15 +202,17 @@ namespace aux { return extract(attr("typecode")()); } - - object array_base::factory(object const& buffer - , object const& type - , object const& shape + + object array_base::factory( + object const& sequence + , object const& typecode , bool copy , bool savespace - , object typecode) + , object type + , object shape + ) { - return attr("factory")(buffer, type, shape, copy, savespace, typecode); + return attr("factory")(sequence, typecode, copy, savespace, type, shape); } object array_base::getflat() const diff --git a/test/numpy.cpp b/test/numpy.cpp index deb9667b..962a6a8c 100644 --- a/test/numpy.cpp +++ b/test/numpy.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace boost::python; @@ -39,19 +40,28 @@ void info(numeric::array const& z) z.info(); } +namespace +{ + object handle_error() + { + PyObject* type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + handle<> ty(type), v(value), tr(traceback); + return object("exception"); + str format("exception type: %sn"); + format += "exception value: %sn"; + format += "traceback:n%s" ; + object ret = format % boost::python::make_tuple(ty, v, tr); + return ret; + } +} #define CHECK(expr) \ { \ object result; \ try { result = object(expr); } \ catch(error_already_set) \ { \ - PyObject* type, *value, *traceback; \ - PyErr_Fetch(&type, &value, &traceback); \ - handle<> ty(type), v(value), tr(traceback); \ - str format("exception type: %s\n"); \ - format += "exception value: %s\n"; \ - format += "traceback:\n%s" ; \ - result = format % boost::python::make_tuple(ty, v, tr); \ + result = handle_error(); \ } \ check(result); \ } @@ -73,7 +83,7 @@ void exercise(numeric::array& y, object check) // the results of corresponding python operations. void exercise_numarray(numeric::array& y, object check) { - CHECK(y.astype()); + CHECK(str(y)); CHECK(y.argmax()); CHECK(y.argmax(0)); @@ -89,7 +99,7 @@ void exercise_numarray(numeric::array& y, object check) CHECK(y.diagonal()); CHECK(y.diagonal(1)); - CHECK(y.diagonal(0, 1)); + CHECK(y.diagonal(0, 0)); CHECK(y.diagonal(0, 1, 0)); CHECK(y.is_c_array()); @@ -97,19 +107,22 @@ void exercise_numarray(numeric::array& y, object check) CHECK(y.trace()); CHECK(y.trace(1)); - CHECK(y.trace(0, 1)); + CHECK(y.trace(0, 0)); CHECK(y.trace(0, 1, 0)); - CHECK(y.new_('D')); + CHECK(y.new_("D").getshape()); + CHECK(y.new_("D").type()); 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)); + CHECK(y.factory(make_tuple(1.2, 3.4), "f8")); + CHECK(y.factory(make_tuple(1.2, 3.4), "f8", true)); + CHECK(y.factory(make_tuple(1.2, 3.4), "f8", true, false)); + CHECK(y.factory(make_tuple(1.2, 3.4), "f8", true, false, object())); + CHECK (y.factory(make_tuple(1.2, 3.4), "f8", true, false, object(), make_tuple(1,2,1))); + } BOOST_PYTHON_MODULE(numpy_ext) @@ -119,6 +132,7 @@ BOOST_PYTHON_MODULE(numpy_ext) def("exercise", exercise); def("exercise_numarray", exercise_numarray); def("set_module_and_type", &numeric::array::set_module_and_type); + def("get_module_name", &numeric::array::get_module_name); def("info", info); } diff --git a/test/numpy.py b/test/numpy.py index 6f186c59..f4fbcdf3 100644 --- a/test/numpy.py +++ b/test/numpy.py @@ -8,6 +8,9 @@ # tests based on the availability of Numeric and numarray, the corresponding # test functions are simply deleted below if necessary. +# So we can coerce portably across Python versions +bool = type(1 == 1) + def numeric_tests(): ''' >>> from numpy_ext import * @@ -55,8 +58,8 @@ def _numarray_tests(): >>> check = p.check >>> exercise_numarray(x, p) - >>> check(y.astype()); - + >>> check(str(y)) + >>> check(y.argmax()); >>> check(y.argmax(0)); @@ -68,30 +71,35 @@ def _numarray_tests(): >>> y.byteswap(); >>> check(y); - + >>> check(y.diagonal()); >>> check(y.diagonal(1)); - >>> check(y.diagonal(0, 1)); + >>> check(y.diagonal(0, 0)); >>> check(y.diagonal(0, 1, 0)); >>> check(y.is_c_array()); - >>> check(y.isbyteswapped()); + + # coerce because numarray still returns an int and the C++ interface forces + # the return type to bool + >>> check( bool(y.isbyteswapped()) ); >>> check(y.trace()); >>> check(y.trace(1)); - >>> check(y.trace(0, 1)); + >>> check(y.trace(0, 0)); >>> check(y.trace(0, 1, 0)); - >>> check(y.new('D')); + >>> check(y.new('D').getshape()); + >>> check(y.new('D').type()); >>> y.sort(); >>> check(y); >>> check(y.type()); >>> check(y.factory((1.2, 3.4))); - >>> check(y.factory((1.2, 3.4), "Double")); - >>> check(y.factory((1.2, 3.4), "Double", (1,2,1))); - >>> check(y.factory((1.2, 3.4), "Double", (2,1,1), false)); - >>> check(y.factory((1.2, 3.4), "Double", (2,), true, true)); + >>> check(y.factory((1.2, 3.4), "f8")) + >>> check(y.factory((1.2, 3.4), "f8", true)) + >>> check(y.factory((1.2, 3.4), "f8", true, false)) + >>> check(y.factory((1.2, 3.4), "f8", true, false, None)) + >>> check(y.factory((1.2, 3.4), "f8", true, false, None, (1,2,1))) >>> p.results [] @@ -105,12 +113,12 @@ class _printer(object): def __init__(self): self.results = []; def __call__(self, *stuff): - self.results += [ str(x) for x in stuff ] + for x in stuff: + self.results.append(str(x)) 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]) + if self.results[0] != str(x): + print ' Expected:\n %s\n but the C++ interface gave:\n %s' % (x, self.results[0]) + del self.results[0] def _run(args = None): import sys @@ -150,21 +158,29 @@ def _run(args = None): failures = 0 + find = doctest.DocTestFinder().find + run = doctest.DocTestRunner().run + # # 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] + print 'testing default extension module:', \ + numpy_ext.get_module_name() or '[numeric support not installed]' + for test in find(numeric_tests): + failures += run(test)[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] - + + for test in find(numeric_tests): + failures += run(test)[0] + global __test__ if has_numarray: # Add the _numarray_tests to the list of things to test in @@ -173,13 +189,18 @@ def _run(args = None): '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] + + for test in find(numeric_tests) + find(_numarray_tests): + failures += run(test)[0] del __test__ # see that we can go back to the default - print 'testing default module again' numpy_ext.set_module_and_type('', '') - failures += doctest.testmod(sys.modules.get(__name__))[0] + print 'testing default module again:', \ + numpy_ext.get_module_name() or '[numeric support not installed]' + + for test in find(numeric_tests): + failures += run(test)[0] return failures