Mercurial > pytave
changeset 82:f6e8abc9a8cf task
merge
author | David Grundberg <c04dgg@cs.umu.se> |
---|---|
date | Mon, 24 Aug 2009 11:02:29 +0200 |
parents | 798efee55cd0 (current diff) 6517417e75b2 (diff) |
children | 8145ecfecfb9 |
files | |
diffstat | 14 files changed, 1112 insertions(+), 296 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Sat May 09 15:50:16 2009 +0200 +++ b/ChangeLog Mon Aug 24 11:02:29 2009 +0200 @@ -1,3 +1,180 @@ +2009-07-03 David Grundberg <individ@acc.umu.se> + + * pytave.cc: Sorted includes. + (atexit): New function. Call do_octave_atexit before Python + unloads shared libraries. + * package/pytave.py (_atexit): New function. + +2009-07-01 David Grundberg <individ@acc.umu.se> + + * pytave.cc [HAVE_USELOCALE] (init, func_eval, str_eval): Run + Octave interpreter in C locale. + * configure.ac: Define HAVE_USELOCALE. Substitute @PYTHON@. Added + checks for integer typedefs. Make setup.py executable. Friendly + message on configuration completion. + * setup.py.in: Let setup.py be interpreted by the same version of + Python we configured for. + +2009-06-19 Jaroslav Hajek <highegg@gmail.com> + + * configure.ac: Support --enable-numpy + * setup.py.in: Dynamically determine NumPy include path. + * pytave.cc (get_module_name): New function. + * octave_to_python.cc (octvalue_to_pyarrobj): + Support bool arrays with NumPy. + * python_to_octave.cc (pyarr_to_octvalue, + copy_pyarrobj_to_octarray_boot): Likewise. + * package/pytave.py: Dynamically import Numeric, + forward to numpy.oldnumeric if run with NumPy. + * test/test.py: Update some tests. + +2009-06-17 Jaroslav Hajek <highegg@gmail.com> + + * package/pytave.py (stripdict): New function. + (narrowlist): New function. + (simplify): New function. + +2009-06-16 Jaroslav Hajek <highegg@gmail.com> + + * octave_to_python.cc + (createPyArr): Don't simplify row vectors to 1D arrays. + (octvalue_to_pyarrobj): Simplify, don't special-case scalars. + (octcell_to_pyobject): Remove. + (octmap_to_pyobject): Simplify, return uniform results. + (is_1xn_or_0x0): Remove. + * python_to_octave.cc + (pydict_to_octmap): Revert to the former behaviour aka Octave's + `struct'. + * package/pytave.py (canonicalize): Remove. + * test/test.py: Update tests. + +2009-06-09 Jaroslav Hajek <highegg@gmail.com> + + * package/pytave.py (canonicalize): New function. + (feval): Update doc string. + +2009-06-08 David Grundberg <individ@acc.umu.se> + + * pytave.cc (func_eval, str_eval): Reread changed files. + +2009-06-08 David Grundberg <individ@acc.umu.se> + + * test/test.py: New tests for pytave.globals and pytave.locals. + (testsetget, testexception): Call fail() instead of print. + +2009-06-03 Jaroslav Hajek <highegg@gmail.com> + + * pytave.cc (delvar): New function. + * package/pytave.py (_VariablesDict.__delitem__): New method. + +2009-06-03 Jaroslav Hajek <highegg@gmail.com> + + * pytave.cc (isvar): Fix tests. + +2009-06-08 Jaroslav Hajek <highegg@gmail.com>, David Grundberg <individ@acc.umu.se> + + * octave_to_python.cc (copy_octarray_to_pyarrobj<PyObject *, + Cell>): New template specialization. + (createPyArr): Simplify row vectors to 1D arrays. + (octvalue_to_pyarrobj): Add string and cell cases. + (octcell_to_pyobject): Create a PyObject array if not row vector. + (octmap_to_pyobject): Create a simple dict if scalar struct. + (octvalue_to_pyobj): Allow converting arbitrary character matrices. + * python_to_octave.cc (pydict_to_octmap): Make scalar structs by + default, detect matching dimensions. + * package/pytave.py (feval): Update doc string. + (getvar, setvar, isvar): Remove. + (_VariablesDict): New class. + (locals, globals): Default instances. + (_local_scope): Rename to _LocalScope + * test/test.py: New tests. Update old tests. + (testsetget, testexception): New functions. + +2009-06-03 Jaroslav Hajek <highegg@gmail.com> + + * python_to_octave.cc (copy_pyarrobj_to_octarray<PyObject *, + Cell>): New template specialization. + (matching_type<PyObject *, octave_value>): Ditto. + (copy_pyarrobj_to_octarray_boot): Include PyArray_CHAR + and PyArray_Object cases. + (pyarr_to_octvalue): Likewise. + * test/test.py: Add tests for the new conversions. + +2009-06-03 David Grundberg <individ@acc.umu.se> + + * octave_to_python.cc (create_uint_array, create_sint_array): + Prefer int to other datatypes of identical size. + (is_1xn_or_0x0): Whitespace. + * python_to_octave.cc (copy_pyarrobj_to_octarray_dispatch): Prefer + int to other datatypes of identical size. + +2009-05-28 Jaroslav Hajek <highegg@gmail.com> + + * configure.ac: Remove --enable-float-matrices option. + * octave_to_python.cc: Remove uses of PYTAVE_USE_OCTAVE_FLOAT. + * python_to_octave.cc: Ditto. + +2009-05-26 Jaroslav Hajek <highegg@gmail.com> + + * octave_to_python.cc (octvalue_to_pyarrobj): Support Complex + and FloatComplex values. + (octvalue_to_pyobj): Support complex scalars. + * python_to_octave.cc (copy_pyarrobj_to_octarray_dispatch): + New template function. + (matching_type): New helper traits class. + (copy_pyarrobj_to_octarray_dispatch): Support complex types. + (pyarrobj_to_octvalue): Support complex scalars. + * test/test.py: Add tests for complex values. + +2009-05-26 Jaroslav Hajek <highegg@gmail.com> + + * octave_to_python.cc: New #include (boost/type_traits). + (copy_octarray_to_pyarrobj): Don't use .value(); don't specialize. + (create_array (..., boost::true_type)): New overload (forward). + (create_array (..., boost::false_type)): New overload (dummy). + (create_uint_array): Pick proper overload. + (create_sint_array): Pick proper overload. + +2009-05-25 Jaroslav Hajek <highegg@gmail.com> + + * python_to_octave.cc (pydict_to_octmap): Save key and val in an + array, to avoid doing duplicate conversions. + +2009-05-25 Jaroslav Hajek <highegg@gmail.com> + + * pytave.cc (init): Add parameter; only display Octave banner if + requested. + * package/pytave.cc: Determine whether the interpreter is + interactive. + +2009-05-25 Jaroslav Hajek <highegg@gmail.com> + + * octave_to_python.cc (is_1xn_or_0x0): New inline func. + (octcell_to_pyobject): Use it. + (octvalue_to_pyobj): Use it. + * test/test.py: Update & fix tests. + +2009-05-13 Jaroslav Hajek <highegg@gmail.com> + + * exceptions.h (variable_name_exception): New exception class. + * exceptions.cc: Initialize it. + * octave_to_python.h (octvalue_to_pyobj): Declare prototype. + * pytave.cc (init): Init variable_name_exception. + (getvar): New function. + (setvar): New function. + (isvar): New function. + (push_scope): New function. + (pop_scope): New function. + * package/pytave.py (__init__): Init VarNameError. + (getvar): New function. + (setvar): New function. + (isvar): New function. + (push_scope): New function. + (pop_scope): New function. + (_local_scope): New decorator helper class. + (local_scope): New decorator. + * test/test.py: Include new tests. + 2009-05-09 David Grundberg <individ@acc.umu.se> * ax_octave_float.m4: New file.
--- a/NEWS Sat May 09 15:50:16 2009 +0200 +++ b/NEWS Mon Aug 24 11:02:29 2009 +0200 @@ -1,5 +1,44 @@ Version 0.1.1-bzr +2009-05-25 + +* Added functionality for explicit manipulation of variables. + getvar, setvar, isvar can be used to get, set and query variables + in the current Octave scope. + Example: + pytave.setvar("x", 1) + pytave.eval(0,"x += 1") + x = pytave.getvar("x") + +* Added functionality to push/pop anonymous scopes on the Octave call + stack. push_scope and pop_scope are provided to create an anonymous scope + and push it on Octave's call stack, to prevent cluttering other variables if + nested calls to pytave are in effect. + + Example: + pytave.push_scope() + pytave.setvar("x", something) + pytave.eval(0, "... do something with x) + pytave.pop_scope() # wipes out x and restores its previous value, if any + + Of course, for proper safety, try/finally block should be used to ensure the + cleanup. For convenience, a local_scope decorator is provided that encloses a + whole function in a push_scope/try/finally/pop_scope sequence: + + @pytave.local_scope + def my_oct_add(x,y): + pytave.setvar("x",x) + pytave.setvar("y",y) + result, = pytave.eval(1, "x + y") + return result + + this function, when called, will not affect the top-level values of x and y, if + any. + +* The Octave welcome banner is now only displayed if Python is run interactively. + +* {}, "" and '' are now accepted as return values and converted to an empty list/string. + 2009-05-07 * Added an eval function. A string of Octave code can be executed
--- a/arrayobjectdefs.h Sat May 09 15:50:16 2009 +0200 +++ b/arrayobjectdefs.h Mon Aug 24 11:02:29 2009 +0200 @@ -29,7 +29,16 @@ #endif #define PY_ARRAY_UNIQUE_SYMBOL pytave_array_symbol #include <Python.h> +#ifdef HAVE_NUMPY +#include <numpy/oldnumeric.h> +#include <numpy/old_defines.h> +// Avoid deprecation warnings from NumPy +#undef PyArray_FromDims +#define PyArray_FromDims PyArray_SimpleNew +#else #include <Numeric/arrayobject.h> +typedef int npy_intp; +#endif /* Emacs * Local Variables:
--- a/configure.ac Sat May 09 15:50:16 2009 +0200 +++ b/configure.ac Mon Aug 24 11:02:29 2009 +0200 @@ -17,36 +17,21 @@ AC_PRESERVE_HELP_ORDER -AC_ARG_ENABLE(float-matrices, - [AS_HELP_STRING([--enable-float-matrices], - [use Octave float matrices (experimental) - @<:@default=no@:>@])], - [pytave_enable_float="$enableval"], - [pytave_enable_float=no]) dnl TODO: Change to check someday +AC_ARG_ENABLE(numpy, + [AS_HELP_STRING([--enable-numpy], + [use NumPy module (experimental) + @<:@default=no@:>@])], + [pytave_enable_numpy="$enableval"], + [pytave_enable_numpy=no]) dnl TODO: Check? + +if test "$pytave_enable_numpy" == "yes" ; then + AC_DEFINE([HAVE_NUMPY], 1, [Define if using NumPy]) +fi pytave_libs_ok= AX_OCTAVE([], [], [pytave_libs_ok=no]) -AS_IF([test "x$pytave_enable_float" = "xcheck" dnl - -o "x$pytave_enable_float" = "xyes"], [ - AX_OCTAVE_FLOAT([ - AC_DEFINE([PYTAVE_USE_OCTAVE_FLOATS], [], - [Define to enable conversion of Octave float - matrices. If not defined, float matrices are - expanded to double matrices]) - ], [ - AS_IF([test "x$pytave_enable_float" = "xyes"], [ - AC_WARN([[ -======================================================================== -Float matrices enabled, but could not compile test program against -Octave. -========================================================================]]) - pytave_libs_ok=no - ]) - ]) -]) - # Pick a Python library to use AX_PYTHON_DEVEL([], [], [pytave_libs_ok=no]) @@ -89,12 +74,23 @@ # Checks for libraries. # Checks for header files. +AC_CHECK_HEADERS([locale.h]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL AC_C_CONST +AC_C_INLINE +AC_TYPE_INT16_T +AC_TYPE_INT32_T +AC_TYPE_INT64_T +AC_TYPE_INT8_T +AC_TYPE_SIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT8_T # Checks for library functions. +AC_CHECK_FUNCS([uselocale], [pytave_have_uselocale=yes], [pytave_have_uselocale=no]) # This needs a more usable, less unusual solution. AS_IF(test "x${prefix}" == "xNONE", @@ -111,11 +107,32 @@ PYTAVE_OCTAVE_RPATH="$OCTAVE_LIBRARYDIR" AC_SUBST(PYTAVE_OCTAVE_RPATH) AC_SUBST(PYTAVE_MODULE_INSTALL_PATH) +AC_SUBST(pytave_enable_numpy) # Substitutes for the Jamfile. XXX: Replace lib*.so with OS independent name. AC_SUBST(JAM_LIBOCTAVE, $OCTAVE_LIBRARYDIR/liboctave.so) AC_SUBST(JAM_LIBCRUFT, $OCTAVE_LIBRARYDIR/libcruft.so) AC_SUBST(JAM_LIBOCTINTERP, $OCTAVE_LIBRARYDIR/liboctinterp.so) +# setup.py +AC_SUBST(PYTHON) + # Substitute in these files, copy project-root.jam to VPATH too AC_OUTPUT([Makefile Jamfile setup.py project-root.jam]) + +chmod u+x "setup.py" + +AC_MSG_NOTICE([ +======================================================================== +Pytave now configured with the following setup: + +Dependencies + Octave ............. $OCTAVE_INCLUDEDIR + Python ............. $PYTHON_CPPFLAGS + executable ....... $PYTHON + +Features + NumPy .............. $pytave_enable_numpy + uselocale .......... $pytave_have_uselocale + +========================================================================])
--- a/exceptions.cc Sat May 09 15:50:16 2009 +0200 +++ b/exceptions.cc Mon Aug 24 11:02:29 2009 +0200 @@ -26,6 +26,7 @@ PyObject *value_convert_exception::excclass = NULL; PyObject *object_convert_exception::excclass = NULL; PyObject *octave_parse_exception::excclass = NULL; + PyObject *variable_name_exception::excclass = NULL; }
--- a/exceptions.h Sat May 09 15:50:16 2009 +0200 +++ b/exceptions.h Mon Aug 24 11:02:29 2009 +0200 @@ -113,6 +113,26 @@ std::string error; }; + class variable_name_exception { + public: + static bool init() { + excclass = PyErr_NewException( + const_cast<char*>("pytave.VarNameError"), + PyExc_RuntimeError, NULL); + return excclass != NULL; + }; + static void translate_exception(variable_name_exception const &py_ex) { + PyErr_SetString(excclass, py_ex.error.c_str()); + } + static PyObject *excclass; + + variable_name_exception(std::string err) { error = err; }; + + private: + std::string error; + + }; + } #endif /* PYTAVE_EXCEPTIONS_H */
--- a/octave_to_python.cc Sat May 09 15:50:16 2009 +0200 +++ b/octave_to_python.cc Mon Aug 24 11:02:29 2009 +0200 @@ -1,5 +1,6 @@ /* * Copyright 2008 David Grundberg, Håkan Fors Nilsson + * Copyright 2009 VZLU Prague * * This file is part of Pytave. * @@ -20,6 +21,7 @@ #include "arrayobjectdefs.h" #include <boost/python.hpp> #include <boost/python/numeric.hpp> +#include <boost/type_traits/integral_constant.hpp> #undef HAVE_STAT /* both boost::python and octave define HAVE_STAT... */ #include <octave/oct.h> #include <octave/Matrix.h> @@ -29,6 +31,7 @@ #include <iostream> #include "pytavedefs.h" #include "exceptions.h" +#include "octave_to_python.h" /* From docs: * Note that the names of the element type constants refer to the C data @@ -43,9 +46,6 @@ namespace pytave { - void octvalue_to_pyobj(boost::python::object &py_object, - const octave_value& octvalue); - template <class PythonPrimitive, class OctaveBase> static void copy_octarray_to_pyarrobj( PyArrayObject *pyarr, @@ -59,7 +59,7 @@ // Last dimension, base case for (int i = 0; i < pyarr->dimensions[dimension]; i++) { *(PythonPrimitive *)&ptr[offset + i*pyarr->strides[dimension]] - = matrix.elem(matindex + i*matstride).value(); + = matrix.elem(matindex + i*matstride); } } else { for (int i = 0; i < pyarr->dimensions[dimension]; i++) { @@ -75,9 +75,9 @@ } template <> - void copy_octarray_to_pyarrobj<double, NDArray>( + void copy_octarray_to_pyarrobj<PyObject *, Cell>( PyArrayObject *pyarr, - const NDArray &matrix, + const Cell &matrix, const unsigned int matindex, const unsigned int matstride, const int dimension, @@ -86,12 +86,15 @@ if (dimension == pyarr->nd - 1) { // Last dimension, base case for (int i = 0; i < pyarr->dimensions[dimension]; i++) { - *(double *)&ptr[offset + i*pyarr->strides[dimension]] - = matrix.elem(matindex + i*matstride); + object pyobj; + octvalue_to_pyobj (pyobj, matrix.elem(matindex + i*matstride)); + Py_INCREF (pyobj.ptr()); + *(PyObject **)&ptr[offset + i*pyarr->strides[dimension]] + = pyobj.ptr(); } } else { for (int i = 0; i < pyarr->dimensions[dimension]; i++) { - copy_octarray_to_pyarrobj<double, NDArray>( + copy_octarray_to_pyarrobj<PyObject *, Cell>( pyarr, matrix, matindex + i*matstride, @@ -102,45 +105,16 @@ } } -#ifdef PYTAVE_USE_OCTAVE_FLOATS - template <> - void copy_octarray_to_pyarrobj<float, FloatNDArray>( - PyArrayObject *pyarr, - const FloatNDArray &matrix, - const unsigned int matindex, - const unsigned int matstride, - const int dimension, - const unsigned int offset) { - unsigned char *ptr = (unsigned char*) pyarr->data; - if (dimension == pyarr->nd - 1) { - // Last dimension, base case - for (int i = 0; i < pyarr->dimensions[dimension]; i++) { - *(float *)&ptr[offset + i*pyarr->strides[dimension]] - = matrix.elem(matindex + i*matstride); - } - } else { - for (int i = 0; i < pyarr->dimensions[dimension]; i++) { - copy_octarray_to_pyarrobj<float, FloatNDArray>( - pyarr, - matrix, - matindex + i*matstride, - matstride * pyarr->dimensions[dimension], - dimension + 1, - offset + i*pyarr->strides[dimension]); - } - } - } -#endif /* PYTAVE_USE_OCTAVE_FLOATS */ - static PyArrayObject *createPyArr(const dim_vector &dims, int pyarrtype) { - int dimensions[dims.length()]; + int len = dims.length(); + npy_intp dimensions[len]; for (int i = 0; i < dims.length(); i++) { dimensions[i] = dims(i); } return (PyArrayObject *)PyArray_FromDims( - dims.length(), dimensions, pyarrtype); + len, dimensions, pyarrtype); } template <class PythonPrimitive, class OctaveBase> @@ -157,14 +131,31 @@ return pyarr; } + template <class PythonPrimitive, class OctaveBase> + static PyArrayObject *create_array(const OctaveBase &octarr, + int pyarraytype, boost::true_type) { + return create_array<PythonPrimitive, OctaveBase> (octarr, pyarraytype); + } + + template <class PythonPrimitive, class OctaveBase> + static PyArrayObject *create_array(const OctaveBase &octarr, + int pyarraytype, boost::false_type) { + assert(0); + return 0; + } + template <class CLASS, size_t bytes> inline static PyArrayObject *create_uint_array(CLASS value) { if (bytes == sizeof(int)) { - return create_array<unsigned int, CLASS>(value, PyArray_UINT); + boost::integral_constant<bool, bytes == sizeof(int)> inst; + return create_array<unsigned int, CLASS>(value, PyArray_UINT, inst); } else if (bytes == sizeof(char)) { - return create_array<unsigned char, CLASS>(value, PyArray_UBYTE); + boost::integral_constant<bool, bytes == sizeof(char)> inst; + return create_array<unsigned char, CLASS>(value, PyArray_UBYTE, inst); } else if (bytes == sizeof(short)) { - return create_array<unsigned short, CLASS>(value, PyArray_USHORT); + boost::integral_constant<bool, + bytes == sizeof(short) && bytes != sizeof(int)> inst; + return create_array<unsigned short, CLASS>(value, PyArray_USHORT, inst); } else { ostringstream os; os << "Numeric arrays doesn't support unsigned " << (bytes*8) @@ -175,14 +166,22 @@ template <class CLASS, size_t bytes> inline static PyArrayObject *create_sint_array(CLASS value) { - if (bytes == sizeof(long)) { - return create_array<long, CLASS>(value, PyArray_LONG); - } else if (bytes == sizeof(int)) { - return create_array<signed int, CLASS>(value, PyArray_INT); + if (bytes == sizeof(int)) { + // We test int first since we prefer int to other datatypes of the + // same size. + boost::integral_constant<bool, bytes == sizeof(int)> inst; + return create_array<signed int, CLASS>(value, PyArray_INT, inst); + } else if (bytes == sizeof(long)) { + boost::integral_constant<bool, + bytes==sizeof(long) && bytes != sizeof(int)> inst; + return create_array<long, CLASS>(value, PyArray_LONG, inst); } else if (bytes == sizeof(char)) { - return create_array<signed char, CLASS>(value, PyArray_SBYTE); + boost::integral_constant<bool, bytes == sizeof(char)> inst; + return create_array<signed char, CLASS>(value, PyArray_SBYTE, inst); } else if (bytes == sizeof(short)) { - return create_array<signed short, CLASS>(value, PyArray_SHORT); + boost::integral_constant<bool, + bytes==sizeof(short) && bytes != sizeof(int)> inst; + return create_array<signed short, CLASS>(value, PyArray_SHORT, inst); } else { ostringstream os; os << "Numeric arrays doesn't support signed " << (bytes*8) @@ -192,28 +191,27 @@ } static PyArrayObject *octvalue_to_pyarrobj(const octave_value &matrix) { - if (matrix.is_complex_type ()) { - throw value_convert_exception( - "Complex Octave matrices conversion not implemented"); - } - if (matrix.is_double_type ()) { - if (matrix.is_real_type()) { + if (matrix.is_complex_type ()) { + return create_array<Complex, ComplexNDArray> + (matrix.complex_array_value(), PyArray_CDOUBLE); + } else if (matrix.is_real_type()) { return create_array<double, NDArray>(matrix.array_value(), PyArray_DOUBLE); } else throw value_convert_exception("Unknown double matrix type"); } -#ifdef PYTAVE_USE_OCTAVE_FLOATS if (matrix.is_single_type ()) { - if (matrix.is_real_type()) { + if (matrix.is_complex_type ()) { + return create_array<FloatComplex, FloatComplexNDArray> + (matrix.float_complex_array_value(), PyArray_CFLOAT); + } else if (matrix.is_real_type()) { return create_array<float, FloatNDArray>( matrix.float_array_value(), PyArray_FLOAT); } else throw value_convert_exception("Unknown float matrix type"); } -#endif if (matrix.is_int64_type()) { return create_sint_array<int64NDArray, sizeof(int64_t)>( @@ -243,6 +241,25 @@ return create_sint_array<int8NDArray, sizeof(int8_t)>( matrix.int8_array_value()); } + if (matrix.is_bool_type()) { +#ifdef HAVE_NUMPY + // NumPY has logical arrays, and even provides an old-style #define. + return create_array<bool, boolNDArray>( + matrix.bool_array_value(), PyArray_BOOL); +#else + // Numeric does not support bools, use uint8. + return create_uint_array<uint8NDArray, sizeof(uint8_t)>( + matrix.uint8_array_value()); +#endif + } + if (matrix.is_string()) { + return create_array<char, charNDArray>( + matrix.char_array_value(), PyArray_CHAR); + } + if (matrix.is_cell()) { + return create_array<PyObject *, Cell>( + matrix.cell_value(), PyArray_OBJECT); + } throw value_convert_exception("Octave matrix type not known, " "conversion not implemented"); @@ -254,25 +271,6 @@ py_object = object(handle<PyObject>((PyObject *)pyarr)); } - - static void octcell_to_pyobject(boost::python::object &py_object, - const Cell& cell) { - py_object = boost::python::list(); - - if(cell.dim1() != 1) { - throw value_convert_exception( - "Only one-dimensional (row mayor) cell arrays can be converted."); - } - - for(octave_idx_type i = 0 ; i < cell.length(); i++) { - boost::python::object py_val; - - octvalue_to_pyobj(py_val, cell.elem(i)); - - ((boost::python::list&)py_object).insert(i, py_val); - } - } - static void octmap_to_pyobject(boost::python::object &py_object, const Octave_map& map) { py_object = boost::python::dict(); @@ -281,7 +279,9 @@ for(octave_idx_type i = 0 ; i < keys.length(); i++) { boost::python::object py_val; - octvalue_to_pyobj(py_val, map.contents(keys[i])); + const Cell c = map.contents(keys[i]); + + octvalue_to_pyarr(py_val, c); py_object[keys[i]] = py_val; } @@ -289,30 +289,14 @@ void octvalue_to_pyobj(boost::python::object &py_object, const octave_value& octvalue) { - if (octvalue.is_undefined()) + if (octvalue.is_undefined()) { throw value_convert_exception( "Octave value `undefined'. Can not convert to a Python object"); - else if (octvalue.is_scalar_type()) { - if (octvalue.is_bool_type()) - py_object = object(octvalue.bool_value()); - else if (octvalue.is_real_scalar()) - py_object = object(octvalue.double_value()); - else if (octvalue.is_integer_type()) - py_object = object(octvalue.int_value()); - else - throw value_convert_exception( - "Conversion for this scalar not implemented"); - } else if (octvalue.is_string()) { - if (octvalue.all_strings().dim1() > 1) - throw value_convert_exception( - "Multi-row character matrices can not be converted."); - py_object = str(octvalue.string_value()); - } else if (octvalue.is_matrix_type()) { + } else if (octvalue.is_numeric_type() || octvalue.is_string() + || octvalue.is_cell()) { octvalue_to_pyarr(py_object, octvalue); } else if (octvalue.is_map()) { octmap_to_pyobject(py_object, octvalue.map_value()); - } else if (octvalue.is_cell()) { - octcell_to_pyobject(py_object, octvalue.cell_value()); } else throw value_convert_exception( "Conversion from Octave value not implemented");
--- a/octave_to_python.h Sat May 09 15:50:16 2009 +0200 +++ b/octave_to_python.h Mon Aug 24 11:02:29 2009 +0200 @@ -21,6 +21,8 @@ #define OCTAVE_TO_PYTHON_H namespace pytave { + void octvalue_to_pyobj(boost::python::object &py_object, + const octave_value& octvalue); void octlist_to_pytuple(boost::python::tuple &python_tuple, const octave_value_list &octave_list); }
--- a/package/pytave.py Sat May 09 15:50:16 2009 +0200 +++ b/package/pytave.py Mon Aug 24 11:02:29 2009 +0200 @@ -1,6 +1,7 @@ # -*- coding:utf-8 -*- # # Copyright 2008 David Grundberg, Håkan Fors Nilsson +# Copyright 2009 Jaroslav Hajek, VZLU Prague # # This file is part of Pytave. # @@ -19,11 +20,34 @@ """Python to Octave bridge""" +import UserDict import _pytave +import atexit +import sys + +arg0 = sys.argv[0] +interactive = sys.stdin.isatty() and (arg0 == '' or arg0 == '-') + +_pytave.init(interactive) +(OctaveError, ValueConvertError, ObjectConvertError, ParseError, \ + VarNameError) = _pytave.get_exceptions(); -_pytave.init() -(OctaveError, ValueConvertError, ObjectConvertError, ParseError) \ - = _pytave.get_exceptions(); +# Dynamic import. *Must* go after _pytave.init() ! +__modname__ = _pytave.get_module_name() +if __modname__ == 'numpy': + from numpy import oldnumeric as Numeric +elif __modname__ == 'Numeric': + import Numeric +elif __modname__ == 'numarray': + # FIXME: Is this OK? + import numarray as Numeric +else: + raise ImportError("Failed to import module: %s" % __modname__) + +def _atexit(): + _pytave.atexit() + +atexit.register(_atexit) def feval(nargout, funcname, *arguments): @@ -49,9 +73,9 @@ Objects: int (32-bit) int32 float (64-bit) double - str string + str character array dict struct - list cell + list cell array Numeric Array: UBYTE, SBYTE, matrix of correct type @@ -59,29 +83,37 @@ UINT, SINT, -''- LONG, -''- DOUBLE -''- + CHAR character array + OBJECT cell array All other objects causes a pytave.ObjectConvertError to be raised. This exception inherits TypeError. + + When dicts are converted, all keys must be strings and + constitute valid Octave identifiers. The behavior is + analogical to the Octave "struct" function: values that + evaluate to cells must have matching dimensions, singleton + cells and non-cell values are expanded. Octave to Python ================ - Scalar values to objects: - bool bool - real scalar float (64-bit) - any string* str - struct dict - cell* list + All scalar values are regarded as 1x1 matrices, as they are in + Octave. - * Cell arrays must be one-dimensional (row vector) and - character matrices must only have one row. Any - other form will raise a ValueConvertError. - Matrix values to Numeric arrays: + double DOUBLE + single FLOAT + logical DOUBLE int64 LONG int32, uint32 INT, UINT int16, uint16 SHORT, USHORT int8, unint8 SBYTE, UBYTE + char CHAR + cell OBJECT + + Structs are converted to dicts, where each value is an OBJECT + array. All other values causes a pytave.ValueConvertError to be raised. This exception inherits TypeError. @@ -131,6 +163,63 @@ return _pytave.eval(nargout, code, silent) +def stripdict(dictarray): + """A helper function to convert structures obtained from Octave. + Because in Octave, all structs are also arrays, they are returned + as dicts of object arrays. In the common case of a 1x1 struct, + stripdict strips the values.""" + + sdict = {} + for key in dictarray: + sdict[key] = dictarray[key][0,0] + return sdict + +def narrowlist(objarray): + """A helper function to convert cell arrays obtained from Octave. + Octave cells are returned as Numeric object arrays. This function + will flatten the array and convert it into a 1D list.""" + + return Numeric.ravel(objarray).tolist() + +def simplify(obj): + """A helper function to convert results obtained from Octave. + This will convert all 1x1 arrays to scalars, vectors to 1D arrays, + 1xN and 0x0 character arrays to strings, 1xN, Nx1 and 0x0 cell + arrays to lists, and strip scalar dicts. It will work recursively.""" + + def vectordims(dims,column_allowed = True): + return (len(dims) == 2 and + ((dims[0] == 1 or (column_allowed and dims[1] == 1)) or + (dims[0] == 0 and dims[1] == 0))) + + if isinstance(obj,Numeric.ArrayType): + tc = obj.typecode() + if tc == 'O': + if vectordims(Numeric.shape(obj)): + return map(simplify,narrowlist(obj)) + elif tc == 'c': + if vectordims(Numeric.shape(obj), False): + return obj.tostring() + else: + dims = Numeric.shape(obj) + if dims == (1,1): + return obj.toscalar() + elif vectordims(dims): + return Numeric.ravel(obj) + elif isinstance(obj,dict): + sobj = {} + for key in obj: + sval = simplify(obj[key]) + if isinstance(sval,list) and len(sval) == 1: + sval = sval[0] + sobj[key] = sval + return sobj + elif isinstance(obj,tuple): + return tuple(map(simplify,obj)) + ## default. + return obj + + def addpath(*arguments): """See Octave documentation""" return _pytave.feval(1, "addpath", arguments)[0] @@ -143,11 +232,103 @@ """See Octave documentation""" return _pytave.feval(1, "path", paths)[0] +class _VariablesDict(UserDict.DictMixin): + def __init__(self, global_variables, native=False): + self.global_variables = global_variables + self.native = native + + def __getitem__(self, name): + if not isinstance(name, basestring): + raise TypeError('Expected a string, not a ' + repr(type(name))) + try: + return _pytave.getvar(name, self.global_variables) + except VarNameError: + raise KeyError('No Octave variable named ' + name) + + def __setitem__(self, name, value): + if not isinstance(name, basestring): + raise TypeError('Expected a string, not a ' + repr(type(name))) + _pytave.setvar(name, value, self.global_variables) + + def __contains__(self, name): + if not isinstance(name, basestring): + raise TypeError('Expected a string, not a ' + repr(type(name))) + return _pytave.isvar(name, self.global_variables) + + def __delitem__(self, name): + if not isinstance(name, basestring): + raise TypeError('Expected a string, not a ' + repr(type(name))) + # Octave does not gripe when clearing non-existent + # variables. To be consistent with Python dict + # behavior, we shall do so. + if self.__contains__(name): + _pytave.delvar(name, self.global_variables) + else: + raise KeyError('No Octave variable named ' + name) + + +locals = _VariablesDict(global_variables=False) +globals = _VariablesDict(global_variables=True) + +def push_scope(): + """Creates a new anonymous local variable scope on the Octave call + stack and sets it as the current Octave scope. Subsequent eval, + getvar and setvar calls will affect variables within this scope. + + This is useful to do if you call the Octave engine from within + multiple Python functions, to prevent them from hampering each + other's data. As such, it is advisable to always create a local + scope in a production code. + """ + return _pytave.push_scope() + +def pop_scope(): + """Pops the current active scope (created previously by + push_scope) off the Octave call stack. The previous scope + will become the active scope. + + If already at the top-level scope, this function does nothing. + """ + _pytave.pop_scope() + +class _LocalScope: + def __init__(self, func): + self.func = func + self.__name__ = func.__name__ + self.__doc__ = func.__doc__ + + def __call__(self, *args, **kwargs): + try: + _pytave.push_scope() + return self.func(*args, **kwargs) + finally: + _pytave.pop_scope() + +def local_scope(func): + """Decorates a function to use local Octave scope. + Example: + + @pytave.local_scope + def myfunc(a,b): + <function body> + + is equivalent to: + + def myfunc(a,b): + try: + pytave.push_scope() + <function body> + finally: + pytave.pop_scope() + """ + return _LocalScope(func) + # Emacs # Local Variables: # fill-column:70 # coding:utf-8 # indent-tabs-mode:t # tab-width:8 +# python-indent:8 # End: -# vim: set textwidth=70 noexpandtab tabstop=3 : +# vim: set textwidth=70 noexpandtab tabstop=8 :
--- a/pytave.cc Sat May 09 15:50:16 2009 +0200 +++ b/pytave.cc Mon Aug 24 11:02:29 2009 +0200 @@ -1,5 +1,6 @@ /* * Copyright 2008 David Grundberg, Håkan Fors Nilsson + * Copyright 2009 Jaroslav Hajek, VZLU Prague * * This file is part of Pytave. * @@ -23,33 +24,49 @@ #include <boost/python/numeric.hpp> #undef HAVE_STAT /* Both boost.python and octave define HAVE_STAT... */ +#undef HAVE_FSTAT /* Both boost.python and octave define HAVE_FSTAT... */ #include <octave/oct.h> + #include <octave/oct-map.h> #include <octave/octave.h> #include <octave/ov.h> #include <octave/parse.h> +#include <octave/symtab.h> +#include <octave/toplev.h> +#include <octave/utils.h> #include <iostream> +#ifdef HAVE_USELOCALE +#include <locale.h> +#endif #include <sstream> #include <sys/types.h> #include "pytavedefs.h" #include "exceptions.h" +#include "octave_to_python.h" #include "python_to_octave.h" -#include "octave_to_python.h" using namespace boost::python; using namespace std; -namespace pytave { /* {{{ */ +namespace pytave { /* {{{ */ - void init() { +#ifdef HAVE_USELOCALE + locale_t c_locale; +#endif + + void init(bool silent = true) { +#ifdef HAVE_USELOCALE + c_locale = newlocale(LC_ALL, "C", 0); +#endif if (!octave_error_exception::init() || !value_convert_exception::init() || !object_convert_exception::init() - || !octave_parse_exception::init()) { + || !octave_parse_exception::init() + || !variable_name_exception::init ()) { PyErr_SetString(PyExc_ImportError, "_pytave: init failed"); return; } @@ -59,14 +76,39 @@ const char* argv[] = {"octave", "--no-line-editing", "--no-history", + "--silent", NULL}; - octave_main(3, const_cast<char**>(argv), 1); + int argc = 4; + + if (silent) { + argv[3] = 0; + argc = 3; + } + +#ifdef HAVE_USELOCALE + // Set C locale + locale_t old_locale = uselocale(c_locale); +#endif + + octave_main(argc, const_cast<char**>(argv), 1); + +#ifdef HAVE_USELOCALE + // Reset locale + uselocale(old_locale); +#endif // Initialize Python Numeric Array // This is actually a macro that becomes a block expression. If an error // occurs, e.g. Numeric Array not installed, an exception is set. import_array() +#ifdef HAVE_NUMPY + numeric::array::set_module_and_type ("numpy", "ndarray"); +#endif + } + + string get_module_name () { + return numeric::array::get_module_name (); } boost::python::tuple get_exceptions() { @@ -77,7 +119,9 @@ object(handle<PyObject>( object_convert_exception::excclass)), object(handle<PyObject>( - octave_parse_exception::excclass))); + octave_parse_exception::excclass)), + object(handle<PyObject>( + variable_name_exception::excclass))); } string make_error_message (const Octave_map& map) { @@ -111,7 +155,6 @@ return exceptionmsg.str (); } - boost::python::tuple func_eval(const int nargout, const string &funcname, const boost::python::tuple &arguments) { @@ -122,11 +165,24 @@ reset_error_handler(); buffer_error_messages++; - + + // Updating the timestamp makes Octave reread changed files + Vlast_prompt_time.stamp(); + +#ifdef HAVE_USELOCALE + // Set C locale + locale_t old_locale = uselocale(c_locale); +#endif + Py_BEGIN_ALLOW_THREADS retval = feval(funcname, octave_args, (nargout >= 0) ? nargout : 0); Py_END_ALLOW_THREADS +#ifdef HAVE_USELOCALE + // Reset locale + uselocale(old_locale); +#endif + if (error_state != 0) { // error_state values: // -2 error without traceback @@ -162,12 +218,25 @@ reset_error_handler(); buffer_error_messages++; - + + // Updating the timestamp makes Octave reread changed files + Vlast_prompt_time.stamp(); + +#ifdef HAVE_USELOCALE + // Set C locale + locale_t old_locale = uselocale(c_locale); +#endif + Py_BEGIN_ALLOW_THREADS retval = eval_string(code, silent, parse_status, (nargout >= 0) ? nargout : 0); Py_END_ALLOW_THREADS +#ifdef HAVE_USELOCALE + // Reset locale + uselocale(old_locale); +#endif + if (parse_status != 0 || error_state != 0) { // error_state values: // -2 error without traceback @@ -197,14 +266,122 @@ return make_tuple(); } } + + boost::python::object getvar(const string& name, + bool global) { + octave_value val; + + if (global) + val = symbol_table::global_varval(name); + else + val = symbol_table::varval(name); + + if (val.is_undefined()) { + throw variable_name_exception (name + " not defined in current scope"); + } + + boost::python::object pyobject; + octvalue_to_pyobj(pyobject, val); + + return pyobject; + } + + void setvar(const string& name, + const boost::python::object& pyobject, + bool global) { + octave_value val; + + if (!valid_identifier(name)) { + throw variable_name_exception (name + " is not a valid identifier"); + } + + pyobj_to_octvalue(val, pyobject); + + if (global) + symbol_table::global_varref(name) = val; + else + symbol_table::varref(name) = val; + } + + bool isvar(const string& name, bool global) { + bool retval; + + if (global) + retval = symbol_table::global_varval (name).is_defined (); + else + retval = symbol_table::is_variable (name); + + return retval; + } + + void delvar(const string& name, bool global) { + + if (global) { + + // FIXME: workaround a bug in Octave 3.2.0. + if (! symbol_table::is_global (name)) + symbol_table::insert (name).mark_global (); + + symbol_table::clear_global (name); + } else + symbol_table::clear_variable (name); + } + + int push_scope() { + symbol_table::scope_id local_scope = symbol_table::alloc_scope(); + symbol_table::set_scope(local_scope); + octave_call_stack::push(local_scope); + return local_scope; + } + + void pop_scope () { + symbol_table::scope_id curr_scope = symbol_table::current_scope(); + if (curr_scope != symbol_table::top_scope()) + { + symbol_table::erase_scope(curr_scope); + octave_call_stack::pop(); + } + } + +// Make sure Octave is correctly unloaded. We cannot depend on Octave running +// at the (true) process atexit point, because at that time the Octave library +// might have been unloaded. +// +// At least that is the hypothesis, since Octave (in certain circumstances) +// cause a segmentation fault in do_octave_atexit called from the exit +// function. (One Octave call that causes this is "sleep(0)".) + void atexit () { +#ifdef HAVE_USELOCALE + // Set C locale + locale_t old_locale = uselocale(c_locale); +#endif + + Py_BEGIN_ALLOW_THREADS + do_octave_atexit(); + Py_END_ALLOW_THREADS + +#ifdef HAVE_USELOCALE + // Reset locale + uselocale(old_locale); +#endif + } + } /* namespace pytave }}} */ BOOST_PYTHON_MODULE(_pytave) { /* {{{ */ using namespace boost::python; def("init", pytave::init); + def("get_module_name", pytave::get_module_name); def("feval", pytave::func_eval); def("eval", pytave::str_eval); + def("getvar", pytave::getvar); + def("setvar", pytave::setvar); + def("isvar", pytave::isvar); + def("delvar", pytave::delvar); + def("push_scope", pytave::push_scope); + def("pop_scope", pytave::pop_scope); + def("atexit", pytave::atexit); def("get_exceptions", pytave::get_exceptions); register_exception_translator<pytave::pytave_exception>( @@ -222,6 +399,9 @@ register_exception_translator<pytave::value_convert_exception>( pytave::value_convert_exception::translate_exception); + register_exception_translator<pytave::variable_name_exception>( + pytave::variable_name_exception::translate_exception); + } /* }}} */ /* Emacs
--- a/python_to_octave.cc Sat May 09 15:50:16 2009 +0200 +++ b/python_to_octave.cc Mon Aug 24 11:02:29 2009 +0200 @@ -1,5 +1,6 @@ /* * Copyright 2008 David Grundberg, Håkan Fors Nilsson + * Copyright 2009 VZLU Prague * * This file is part of Pytave. * @@ -21,6 +22,7 @@ #include <boost/python.hpp> #include <boost/python/numeric.hpp> #include "arrayobjectdefs.h" +#include <boost/type_traits/integral_constant.hpp> #undef HAVE_STAT /* both boost.python and octave defines HAVE_STAT... */ #include <octave/oct.h> #include <octave/oct-map.h> @@ -67,17 +69,96 @@ } } + template <> + void copy_pyarrobj_to_octarray<PyObject *, Cell>(Cell &matrix, + const PyArrayObject* const pyarr, + const int unsigned matindex, + const unsigned int matstride, + const int dimension, + const unsigned int offset) { + unsigned char *ptr = (unsigned char*) pyarr->data; + if (dimension == pyarr->nd - 1) { + // Last dimension, base case + for (int i = 0; i < pyarr->dimensions[dimension]; i++) { + PyObject *pobj = *(PyObject **) + &ptr[offset + i*pyarr->strides[dimension]]; + pyobj_to_octvalue (matrix.elem(matindex + i*matstride), + object(handle<PyObject> (borrowed (pobj)))); + } + } else { + for (int i = 0; i < pyarr->dimensions[dimension]; i++) { + copy_pyarrobj_to_octarray<PyObject *, Cell>( + matrix, + pyarr, + matindex + i*matstride, + matstride * pyarr->dimensions[dimension], + dimension + 1, + offset + i*pyarr->strides[dimension]); + } + } + } + + template <class PythonPrimitive, class OctaveBase> + static void copy_pyarrobj_to_octarray_dispatch(OctaveBase &matrix, + const PyArrayObject* const pyarr, + const boost::true_type&) { + copy_pyarrobj_to_octarray<PythonPrimitive, OctaveBase> + (matrix, pyarr, 0, 1, 0, 0); + } + + template <class PythonPrimitive, class OctaveBase> + static void copy_pyarrobj_to_octarray_dispatch(OctaveBase &matrix, + const PyArrayObject* const pyarr, + const boost::false_type&) { + assert(0); + } + + template <class X, class Y> class matching_type : public boost::false_type { }; + template <class X> class matching_type<X, X> : public boost::true_type { }; + template <class X> class matching_type<X, octave_int<X> > : public boost::true_type { }; + template <> class matching_type<float, double> : public boost::true_type { }; + template <> class matching_type<FloatComplex, Complex> : public boost::true_type { }; + template <> class matching_type<PyObject *, octave_value> : public boost::true_type { }; + + template <class PythonPrimitive, class OctaveBase> + static void copy_pyarrobj_to_octarray_dispatch(OctaveBase &matrix, + const PyArrayObject* const pyarr) { + matching_type<PythonPrimitive, typename OctaveBase::element_type> inst; + copy_pyarrobj_to_octarray_dispatch<PythonPrimitive, OctaveBase> (matrix, pyarr, inst); + } + template <class OctaveBase> static void copy_pyarrobj_to_octarray_boot(OctaveBase &matrix, const PyArrayObject* const pyarr) { #define ARRAYCASE(AC_pyarrtype, AC_primitive) case AC_pyarrtype: \ - copy_pyarrobj_to_octarray<AC_primitive, OctaveBase>\ - (matrix, pyarr, 0, 1, 0, 0); \ + copy_pyarrobj_to_octarray_dispatch<AC_primitive, OctaveBase>\ + (matrix, pyarr); \ break; \ - switch (pyarr->descr->type_num) { -// ARRAYCASE(PyArray_CHAR, ) + // Prefer int to other types of the same size. + // E.g. on 32-bit x86 architectures: sizeof(long) == sizeof(int). + int type_num = pyarr->descr->type_num; + switch (type_num) { + case PyArray_LONG: + if (sizeof(long) == sizeof(int)) { + type_num = PyArray_INT; + } + break; + case PyArray_SHORT: + if (sizeof(short) == sizeof(int)) { + type_num = PyArray_INT; + } + break; + case PyArray_USHORT: + if (sizeof(unsigned short) == sizeof(unsigned int)) { + type_num = PyArray_UINT; + } + break; + } + + switch (type_num) { + ARRAYCASE(PyArray_CHAR, char) ARRAYCASE(PyArray_UBYTE, unsigned char) ARRAYCASE(PyArray_SBYTE, signed char) ARRAYCASE(PyArray_SHORT, signed short) @@ -91,9 +172,19 @@ /* Commonly Numeric.array(..., Numeric.Float) */ ARRAYCASE(PyArray_DOUBLE, double) -// ARRAYCASE(PyArray_CFLOAT, ) -// ARRAYCASE(PyArray_CDOUBLE, ) -// ARRAYCASE(PyArray_OBJECT, ) + + /* Commonly Numeric.array(..., Numeric.Complex32) */ + ARRAYCASE(PyArray_CFLOAT, FloatComplex) + + /* Commonly Numeric.array(..., Numeric.Complex) */ + ARRAYCASE(PyArray_CDOUBLE, Complex) + +#ifdef HAVE_NUMPY + ARRAYCASE(PyArray_BOOL, bool) +#endif + + ARRAYCASE(PyArray_OBJECT, PyObject *) + default: throw object_convert_exception( PyEval_GetFuncName((PyObject*)pyarr) @@ -168,15 +259,30 @@ } break; case PyArray_FLOAT: -#ifdef PYTAVE_USE_OCTAVE_FLOATS pyarrobj_to_octvalueNd<FloatNDArray>(octvalue, pyarr, dims); break; -#else - /* fallthrough */ -#endif case PyArray_DOUBLE: pyarrobj_to_octvalueNd<NDArray>(octvalue, pyarr, dims); break; + case PyArray_CFLOAT: + pyarrobj_to_octvalueNd<FloatComplexNDArray>(octvalue, pyarr, dims); + break; + case PyArray_CDOUBLE: + pyarrobj_to_octvalueNd<ComplexNDArray>(octvalue, pyarr, dims); + break; + case PyArray_CHAR: + pyarrobj_to_octvalueNd<charNDArray>(octvalue, pyarr, dims); + // FIXME: is the following needed? + octvalue = octvalue.convert_to_str(true, true, '"'); + break; +#ifdef HAVE_NUMPY + case PyArray_BOOL: + pyarrobj_to_octvalueNd<boolNDArray>(octvalue, pyarr, dims); + break; +#endif + case PyArray_OBJECT: + pyarrobj_to_octvalueNd<Cell>(octvalue, pyarr, dims); + break; default: throw object_convert_exception( PyEval_GetFuncDesc((PyObject*)(pyarr)) + string(" ") @@ -213,46 +319,15 @@ dim_vector dims = dim_vector(1, 1); - bool has_dimensions = false; - - for(octave_idx_type i = 0; i < length; i++) { - octave_value val; - - boost::python::tuple tuple = - boost::python::extract<boost::python::tuple>(list[i])(); - - pyobj_to_octvalue(val, tuple[1]); - - if(val.is_cell()) { - const Cell c(val.cell_value()); - if (error_state) - throw object_convert_exception("Octave error"); - - // Some things are assumed since we have converted a Python list to - // a cell. - assert(c.dims().length() == 2); - assert(c.dim1() == 1); + Array<octave_value> vals (length); + Array<std::string> keys (length); - // We do not bother measuring 1x1 values, since they are replicated - // to fill up the necessary dimensions. - if(!(c.dims().length() == 2 && c.dims()(0) == 1 && c.dims()(1) == 1)) { + // Extract all keys and convert values. Remember whether dimensions + // match. + + for(octave_idx_type i = 0; i < length; i++) { - if(!has_dimensions) { - dims = c.dims(); - has_dimensions = true; - } else if(c.dims() != dims) { - throw object_convert_exception( - "Dimensions of the struct fields do not match"); - } - } - } - } - - Octave_map map = Octave_map(dims); - - for(octave_idx_type i = 0; i < length; i++) { - octave_value val; - std::string key; + std::string& key = keys(i); boost::python::tuple tuple = boost::python::extract<boost::python::tuple>(list[i])(); @@ -274,26 +349,36 @@ "field name. Field names must be valid Octave identifiers."); } - // FIXME: Second time around we convert exactly the same object + octave_value& val = vals(i); + pyobj_to_octvalue(val, tuple[1]); - if(!val.is_cell()) { - map.assign(key, Cell(dims, val)); - } else { - const Cell c(val.cell_value()); + if(val.is_cell()) { + if(i == 0) { + dims = val.dims(); + } else if (val.numel() != 1 && val.dims() != dims){ + throw object_convert_exception( + "Dimensions of the struct fields do not match"); + } + } + } - if (error_state) - throw object_convert_exception("Octave error"); + Octave_map map = Octave_map(dims); + + for(octave_idx_type i = 0; i < length; i++) { - if(c.dims().length() == 2 && c.dims()(0) == 1 && c.dims()(1) == 1) { + std::string& key = keys(i); + octave_value val = vals(i); + + if(val.is_cell()) { + const Cell c = val.cell_value(); + if (c.numel () == 1) { map.assign(key, Cell(dims, c(0))); - } - else { + } else { map.assign(key, c); } - } - if (error_state) { - throw object_convert_exception("Octave error"); + } else { + map.assign(key, Cell(dims, val)); } } oct_value = map; @@ -303,6 +388,7 @@ const boost::python::object &py_object) { extract<int> intx(py_object); extract<double> doublex(py_object); + extract<Complex> complexx(py_object); extract<string> stringx(py_object); extract<numeric::array> arrayx(py_object); extract<boost::python::list> listx(py_object); @@ -311,6 +397,8 @@ oct_value = intx(); } else if (doublex.check()) { oct_value = doublex(); + } else if (complexx.check()) { + oct_value = complexx(); } else if (arrayx.check()) { pyarr_to_octvalue(oct_value, (PyArrayObject*)py_object.ptr()); } else if (stringx.check()) {
--- a/python_to_octave.h Sat May 09 15:50:16 2009 +0200 +++ b/python_to_octave.h Mon Aug 24 11:02:29 2009 +0200 @@ -21,6 +21,8 @@ #define PYTHON_TO_OCTAVE_H namespace pytave { + void pyobj_to_octvalue(octave_value &oct_value, + const boost::python::object &py_object); void pytuple_to_octlist(octave_value_list &octave_list, const boost::python::tuple &python_tuple); }
--- a/setup.py.in Sat May 09 15:50:16 2009 +0200 +++ b/setup.py.in Mon Aug 24 11:02:29 2009 +0200 @@ -1,8 +1,20 @@ -#!/usr/bin/python +#!@PYTHON@ # -*- coding: utf-8; c-basic-offset: 3; indent-tabs-mode: nil; tab-width: 3; -*- # @configure_input@ -from distutils.core import setup, Extension +from distutils.core import setup, Extension, DistutilsModuleError + +include_dirs = ['/usr/local/include/octave-3.1.55', + '/home/hajek/devel/pytave-repo/pytave', '.'] # Python always included. + +# check for numpy. If it exists, define the path. + +if '@pytave_enable_numpy@' == 'yes': + try: + from numpy import get_include + include_dirs.append(get_include()) + except ImportError: + raise DistutilsModuleError("could not found numpy") setup( name = 'pytave', @@ -31,8 +43,8 @@ # TODO: Check whether paths work on Windows or not. # The file separator might be wrong. (Must be / in setup.cfg) - include_dirs = ['@OCTAVE_INCLUDEDIR@', '@abs_builddir@', '@srcdir@'], # Python always included. - define_macros = [('HAVE_CONFIG_H', '1')], + include_dirs = include_dirs, + define_macros = [('HAVE_CONFIG_H', '1')], library_dirs = ['@OCTAVE_LIBRARYDIR@'], runtime_library_dirs = ['@PYTAVE_OCTAVE_RPATH@'], libraries = ['octinterp', 'octave', 'cruft', '@BOOST_PYTHON_LIB@']
--- a/test/test.py Sat May 09 15:50:16 2009 +0200 +++ b/test/test.py Mon Aug 24 11:02:29 2009 +0200 @@ -2,14 +2,15 @@ # -*- coding:utf-8 -*- import pytave -import Numeric +from pytave import Numeric +import traceback print "No messages indicates test pass." arr0_0 = Numeric.zeros((0,0)); arr0_1 = Numeric.zeros((0,1)); arr1_0 = Numeric.zeros((1,0)); -number = Numeric.array([1.32], Numeric.Float32) +number = Numeric.array([[1.32]], Numeric.Float32) arr1fT = Numeric.array([[1.32], [2], [3], [4]], Numeric.Float32) arr1fT2 = Numeric.array([[1.32, 2, 3, 4]], Numeric.Float32) arr1f = Numeric.array([[1.32, 2, 3, 4]], Numeric.Float32) @@ -20,6 +21,12 @@ arr2f = Numeric.array([[1.32, 2, 3, 4],[5,6,7,8]], Numeric.Float32) arr2d = Numeric.array([[1.17, 2, 3, 4],[5,6,7,8]], Numeric.Float) arr3f = Numeric.array([[[1.32, 2, 3, 4],[5,6,7,8]],[[9, 10, 11, 12],[13,14,15,16]]], Numeric.Float32) +arr1c = Numeric.array([[1+2j, 3+4j, 5+6j, 7+0.5j]], Numeric.Complex) +arr1fc = Numeric.array([[1+2j, 3+4j, 5+6j, 7+0.5j]], Numeric.Complex32) +arr1ch = Numeric.array(["abc"],Numeric.Character) +arr2ch = Numeric.array(["abc","def"],Numeric.Character) +arr1o = Numeric.array([[1.0,"abc",2+3j]],Numeric.PyObject) +arr2o = Numeric.array([[1.0,"abc",2+3j],[4.0,arr1i,"def"]],Numeric.PyObject) alimit_int32 = Numeric.array([[-2147483648, 2147483647]], Numeric.Int32); alimit_int16 = Numeric.array([[-32768, 32767, -32769, 32768]], Numeric.Int16); @@ -29,48 +36,57 @@ # This eval call is not to be seen as a encouragement to use Pytave # like this. Create a separate .m-file with your complex Octave code. -pytave.feval(1, "eval", "function [result] = test_return(arg) " -"result = arg; endfunction") +pytave.eval(0, "function [result] = test_return(arg); result = arg; endfunction") pytave.feval(1, "test_return", 1) +def equals(a,b): + return Numeric.alltrue(Numeric.ravel(a == b)) + +def fail(msg, exc=None): + print "FAIL:", msg + traceback.print_stack() + if exc is not None: + traceback.print_exc(exc) + print "" + def testequal(value): try: nvalue, = pytave.feval(1, "test_return", value) - if nvalue != value: - print "FAIL as ", value, " != ", nvalue + if not equals(value, nvalue): + fail("as %s != %s" % (value, nvalue)) except TypeError, e: - print "FAIL: ", value,":", e + fail(value, e) def testexpect(value, expected): try: nvalue, = pytave.feval(1, "test_return", value) - if nvalue != expected: - print "FAIL as ", nvalue, " != ", expected, "," - print " sent in", value + if not equals(value, nvalue): + fail("sent in %s, expecting %s, got %s", (value, expected, nvalue)) except TypeError, e: - print "FAIL: ", value,":", e - + fail(value, e) def testmatrix(value): try: nvalue, = pytave.feval(1, "test_return", value) -# print "test", (value,) -# print "returned ", (nvalue,) - class1 = pytave.feval(1, "class", value) - class2 = pytave.feval(1, "class", nvalue) - if nvalue != value: - print "FAIL as ", value, " != ", nvalue + class1, = pytave.feval(1, "class", value) + class1 = class1.tostring() + class2, = pytave.feval(1, "class", nvalue) + class2 = class2.tostring() + if not equals(value, nvalue): + fail("as %s != %s" % (value, nvalue)) if value.shape != nvalue.shape: - print "Size check failed for: ", (value,) ,". Got ",value.shape, "and later", nvalue.shape, " =++ ", (nvalue,) + fail("Size check failed for: %s. Expected shape %s, got %s with shape %s" \ + %(value, value.shape, nvalue, nvalue.shape)) if class1 != class2: - print "Type check failed for: ", (value,) ,". Got ",class1, "and later", class2 + fail( "Type check failed for: %s. Expected %s. Got %s." + %(value, class1, class2)) except TypeError, e: - print "Execute failed: ", value,":", e + fail("Execute failed: %s" % value, e) def testobjecterror(value): try: - print pytave.feval(1, "test_return", value); + pytave.feval(1, "test_return", value); print "FAIL:", (value,) except pytave.ObjectConvertError: pass @@ -79,16 +95,16 @@ def testvalueerror(*value): try: - print pytave.feval(1, *value); - print "FAIL:", (value,) + pytave.feval(1, *value); + fail(value) except pytave.ValueConvertError: pass except Exception, e: - print "FAIL", (value,), e + fail(value, e) def testparseerror(*value): try: - print pytave.eval(*value); + pytave.eval(*value); print "FAIL:", (value,) except pytave.ParseError: pass @@ -104,14 +120,61 @@ def testevalexpect(numargout, code, expectations): try: results = pytave.eval(numargout, code); - if results != expectations: - print "FAIL: eval: ", code, " because", results, " != ", expectations, "," + if not equals(results, expectations): + fail("eval: %s : because %s != %s" % (code, results, expectations)) + except Exception, e: + fail("eval: %s" % code, e) + +def testsetget(variables, name, value): + try: + variables[name] = value + if name not in variables: + fail("set/get: %s: Should exist, not there." % name) + result, = pytave.feval(1, "isequal", value, variables[name]) + if not result: + fail("set/get: %s -> %s: results diverged" % (name, value)) + except Exception, e: + fail("set/get: %s" % name, e) + +def testexception(exception, func): + try: + func() + fail("Expecting %s but nothing was raised." % repr(exception)) except Exception, e: - print "FAIL: eval:", code, ":", e -def testcellinvariant(value): - pass + if not isinstance(e, exception): + fail("Expecting %s but got %s instead" % (repr(exception), repr(e)), e) + +def testlocalscope(x): -testequal('a') + @pytave.local_scope + def sloppy_factorial(x): + pytave.locals["x"] = x + xm1, = pytave.eval(1,"x-1") + xm1 = xm1.toscalar() + if xm1 > 0: + fxm1 = sloppy_factorial(xm1) + else: + fxm1 = 1 + pytave.locals["fxm1"] = fxm1 + fx, = pytave.eval(1,"x * fxm1") + fx = fx.toscalar() + return fx + + try: + fx = sloppy_factorial(x) + fx1 = 1.0 + for k in range(1,x+1): + fx1 = k * fx1 + if fx != fx1: + fail('testlocalscope: result incorrect') + except Exception, e: + fail("testlocalscope: %s" % (x,), e) + +def objarray(obj): + return Numeric.array(obj,Numeric.PyObject) + +def charray(obj): + return Numeric.array(obj,Numeric.Character) testmatrix(alimit_int32) @@ -119,18 +182,14 @@ testmatrix(alimit_int8) # Strings -# Multi-row character matrix cannot be returned -testvalueerror("eval", "['foo'; 'bar']") -testequal('a') + +testequal(["mystring"]) +testequal(["mystringåäöÅÄÖ"]) -testequal("mystring") -testequal('mystring') -testequal("mystringåäöÅÄÖ") - -testequal(1) -testequal(1L) -testequal(1.2) -testequal(1.2) +testexpect(1,Numeric.array([[1]],Numeric.Int)) +if "Int64" in Numeric.__dict__: + testexpect(1L,Numeric.array([[1]],Numeric.Int64)) +testexpect(1.0,Numeric.array([[1]],Numeric.Float)) # Vector arrays testmatrix(arr1a) @@ -139,11 +198,12 @@ testmatrix(arr1fT2) testmatrix(arr1i) testmatrix(arr1b) -testmatrix(arr1i32) +testmatrix(arr1fc) # 2d arrays testmatrix(arr2f) testmatrix(arr2d) +testmatrix(arr2ch) # 3d arrays testmatrix(arr3f) @@ -153,51 +213,36 @@ print "FAIL: Zero test", testmatrix(arr0_0) +testmatrix(arr0_1) testmatrix(arr1_0) -testmatrix(arr0_1) # Lists -testequal([1, 2]) -testequal([[1, 2], [3, 4]]) -testequal([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) -testequal([]) +testexpect([1, 2],objarray([[1,2]])) +testexpect([],objarray([[]])) # Return cells with OK dimensions testvalueok("cell", 1, 3); testvalueok("cell", 1, 0) - -# Return cells with incompatible dimensions -testvalueerror("cell", 3, 1) -testvalueerror("cell", 0, 0) -testvalueerror("cell", 0, 1) +testvalueok("cell", 0, 0) +testvalueok("cell", 3, 1) +testvalueok("cell", 0, 1) # Dictionaries -# Simple dictionary tests -testequal({"foo": [1], "bar": [2]}) -testequal({"x": [1, 3], "y": [2, 4]}) -testequal({"x": [1, "baz"], "y": [2, "foobar"]}) -testequal({"x": [arr1f], "y": [arr1i]}) -testequal({}) -# Try some odd dictionaries -# The implicit conversion makes Pytave return cell-wrapped results. -testexpect({"foo": number, "bar": 2}, - {"foo": [number], "bar": [2]}) -testexpect({"foo": arr1f, "bar": arr2f}, - {"foo": [arr1f], "bar": [arr2f]}) -testexpect({"foo": 1, "bar": 2}, - {"foo": [1], "bar": [2]}) -testexpect({"foo": 1, "bar": [2]}, - {"foo": [1], "bar": [2]}) -testexpect({"foo": 1, "bar": [2, 3]}, - {"foo": [1, 1], "bar": [2, 3]}) -testexpect({"foo": [1], "bar": [2, 4]}, - {"foo": [1, 1], "bar": [2, 4]}) -testexpect({"bar": 1, "foo": [2, 3]}, - {"bar": [1, 1], "foo": [2, 3]}) -testexpect({"bar": [1], "foo": [2, 4]}, - {"bar": [1, 1], "foo": [2, 4]}) +# Simple dictionary tests +testexpect({"foo": 1, "bar": 2}, + {"foo": objarray([[1]]), "bar": objarray([[2]])}) +testexpect({"x": [1, 3], "y": [2, 4]}, + {"x": objarray([[1,3]]), "y": objarray([[2,4]])}) +# just constructing the second value with Numeric 24.2! +#testexpect({"x": [1, "baz"], "y": [2, "foobar"]}, +# {"x": objarray([[1, charray(["baz"])]]), +# "y": objarray([[2, charray(["foobar"])]])}) + +testequal({"x": objarray([[arr1f]]), "y": objarray([[arr1i]])}) +testequal({}) +testequal({"foo": arr2o, "bar": arr2o}) # Try some invalid keys testobjecterror({"this is not an Octave identifier": 1}) @@ -209,8 +254,8 @@ testobjecterror(()) result, = pytave.feval(1, "eval", "[1, 1, 1]") -if result.shape != (1, 3): - print "FAIL: expected 1x3 matrix" +if result.shape != (1,3): + print "FAIL: expected length-3 vector" result, = pytave.feval(1, "eval", "[1; 2; 3]"); if result.shape != (3, 1): @@ -218,5 +263,64 @@ testparseerror(1, "endfunction") testevalexpect(1, "2 + 2", (4,)) -testevalexpect(0, "{2}", ([2],)) -testevalexpect(2, "struct('foo', 2)", ({'foo': [2]},)) +testevalexpect(1, "{2}", (objarray([[2]]),)) +testevalexpect(1, "struct('foo', 2)", ({'foo': objarray([[2]])},)) + +testsetget(pytave.locals, "xxx", arr1f) +testsetget(pytave.globals, "xxx", arr2o) + +def func(): + pytave.locals["this is not a valid Octave identifier"] = 1 +testexception(pytave.VarNameError, func) + +def func(): + pytave.locals["nonexistentvariable"] +testexception(KeyError, func) + +def func(key): + pytave.locals[key] = 1 +testexception(TypeError, lambda: func(0.1)) +testexception(TypeError, lambda: func(1)) +testexception(TypeError, lambda: func([])) + +def func(key): + pytave.locals[key] +testexception(TypeError, lambda: func(0.1)) +testexception(TypeError, lambda: func(1)) +testexception(TypeError, lambda: func([])) + +testlocalscope(5) + +testexception(KeyError, lambda: pytave.locals["localvariable"]) +pytave.locals["localvariable"] = 1 +if "localvariable" in pytave.globals: + fail("Local variable in globals") +del pytave.locals["localvariable"] +if "localvariable" in pytave.locals: + fail("Could not clear local variable") +testexception(KeyError, lambda: pytave.locals["localvariable"]) +def func(): + del pytave.locals["localvariable"] +testexception(KeyError, lambda: func()) + +testexception(KeyError, lambda: pytave.globals["globalvariable"]) +pytave.globals["globalvariable"] = 1 +if "globalvariable" in pytave.locals: + fail("Global variable in locals") +del pytave.globals["globalvariable"] +if "globalvariable" in pytave.globals: + fail("Could not clear global variable") +testexception(KeyError, lambda: pytave.globals["globalvariable"]) +def func(): + del pytave.globals["globalvariable"] +testexception(KeyError, lambda: func()) + +# Emacs +# Local Variables: +# fill-column:70 +# coding:utf-8 +# indent-tabs-mode:t +# tab-width:8 +# python-indent:8 +# End: +# vim: set textwidth=70 noexpandtab tabstop=8 :