# HG changeset patch # User Jaroslav Hajek # Date 1244454919 -7200 # Node ID 43a413b7c151f0f9a392a02b53fd4c39dff70d91 # Parent b001edc0f81ab64a2a30afdc80bad82cb9a794c7 improve conversions, handle POobject and char arrays diff -r b001edc0f81a -r 43a413b7c151 ChangeLog --- a/ChangeLog Wed Jun 03 16:41:44 2009 +0200 +++ b/ChangeLog Mon Jun 08 11:55:19 2009 +0200 @@ -1,3 +1,21 @@ +2009-06-08 Jaroslav Hajek , David Grundberg + + * octave_to_python.cc (copy_octarray_to_pyarrobj): 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. + 2009-06-03 Jaroslav Hajek * python_to_octave.cc (copy_pyarrobj_to_octarray #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 @@ -45,9 +46,6 @@ namespace pytave { - void octvalue_to_pyobj(boost::python::object &py_object, - const octave_value& octvalue); - template static void copy_octarray_to_pyarrobj( PyArrayObject *pyarr, @@ -76,15 +74,52 @@ } } + template <> + void copy_octarray_to_pyarrobj( + PyArrayObject *pyarr, + const Cell &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++) { + 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( + pyarr, + matrix, + matindex + i*matstride, + matstride * pyarr->dimensions[dimension], + dimension + 1, + offset + i*pyarr->strides[dimension]); + } + } + } + static PyArrayObject *createPyArr(const dim_vector &dims, int pyarrtype) { - int dimensions[dims.length()]; + int len = dims.length(); + int dimensions[len]; for (int i = 0; i < dims.length(); i++) { dimensions[i] = dims(i); } + // Simplify row vectors to 1d arrays. + if (len == 2 && dimensions[0] == 1) { + dimensions[0] = dimensions[1]; + len = 1; + } return (PyArrayObject *)PyArray_FromDims( - dims.length(), dimensions, pyarrtype); + len, dimensions, pyarrtype); } template @@ -211,6 +246,14 @@ return create_sint_array( matrix.int8_array_value()); } + if (matrix.is_string()) { + return create_array( + matrix.char_array_value(), PyArray_CHAR); + } + if (matrix.is_cell()) { + return create_array( + matrix.cell_value(), PyArray_OBJECT); + } throw value_convert_exception("Octave matrix type not known, " "conversion not implemented"); @@ -228,19 +271,18 @@ static void octcell_to_pyobject(boost::python::object &py_object, const Cell& cell) { - py_object = boost::python::list(); + if (is_1xn_or_0x0 (cell.dims ())) { + py_object = boost::python::list(); - if(!is_1xn_or_0x0(cell.dims())) { - 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; - for(octave_idx_type i = 0 ; i < cell.length(); i++) { - boost::python::object py_val; + octvalue_to_pyobj(py_val, cell.elem(i)); - octvalue_to_pyobj(py_val, cell.elem(i)); - - ((boost::python::list&)py_object).insert(i, py_val); + ((boost::python::list&)py_object).insert(i, py_val); + } + } else { + octvalue_to_pyarr (py_object, octave_value (cell)); } } @@ -248,11 +290,18 @@ const Octave_map& map) { py_object = boost::python::dict(); string_vector keys = map.keys(); + bool scalar = map.dims().all_ones(); 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]); + + if (scalar) { + octvalue_to_pyobj(py_val, c(0)); + } else { + octcell_to_pyobject(py_val, c); + } py_object[keys[i]] = py_val; } @@ -276,16 +325,17 @@ throw value_convert_exception( "Conversion for this scalar not implemented"); } else if (octvalue.is_string()) { - if (! is_1xn_or_0x0 (octvalue.dims ())) - throw value_convert_exception( - "Multi-row character matrices can not be converted."); - py_object = str(octvalue.string_value()); + if (is_1xn_or_0x0 (octvalue.dims ())) { + py_object = object(octvalue.string_value()); + } else { + octvalue_to_pyarr(py_object, octvalue); + } } else if (octvalue.is_matrix_type()) { 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()); + octcell_to_pyobject(py_object, octvalue.cell_value()); } else throw value_convert_exception( "Conversion from Octave value not implemented"); diff -r b001edc0f81a -r 43a413b7c151 package/pytave.py --- a/package/pytave.py Wed Jun 03 16:41:44 2009 +0200 +++ b/package/pytave.py Mon Jun 08 11:55:19 2009 +0200 @@ -21,7 +21,9 @@ """Python to Octave bridge""" import _pytave +import UserDict import sys +import Numeric arg0 = sys.argv[0] interactive = sys.stdin.isatty() and (arg0 == '' or arg0 == '-') @@ -54,9 +56,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 @@ -64,9 +66,17 @@ 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. By default, scalar + structures are created. However, when all values evaluate + to cell arrays with matching dimensions, an Octave struct + array is created. Octave to Python ================ @@ -74,19 +84,18 @@ Scalar values to objects: bool bool real scalar float (64-bit) - any string* str struct dict - cell* list - * 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 (if native = False) + cell OBJECT (if native = False) All other values causes a pytave.ValueConvertError to be raised. This exception inherits TypeError. @@ -210,7 +219,7 @@ """ _pytave.pop_scope() -class _local_scope: +class _LocalScope: def __init__(self, func): self.func = func self.__name__ = func.__name__ @@ -240,7 +249,7 @@ finally: pytave.pop_scope() """ - return _local_scope(func) + return _LocalScope(func) # Emacs # Local Variables: @@ -248,5 +257,6 @@ # 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 : diff -r b001edc0f81a -r 43a413b7c151 python_to_octave.cc --- a/python_to_octave.cc Wed Jun 03 16:41:44 2009 +0200 +++ b/python_to_octave.cc Mon Jun 08 11:55:19 2009 +0200 @@ -310,11 +310,14 @@ dim_vector dims = dim_vector(1, 1); - bool has_dimensions = false; + bool dims_match = true; Array vals (length); Array keys (length); + // Extract all keys and convert values. Remember whether dimensions + // match. + for(octave_idx_type i = 0; i < length; i++) { std::string& key = keys(i); @@ -343,23 +346,15 @@ 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"); - - // 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)) { - - 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"); - } + if(dims_match && val.is_cell()) { + dim_vector dv = val.dims(); + if(i == 0) { + dims = dv; + } else { + dims_match = dims == dv; } + } else { + dims_match = false; } } @@ -370,23 +365,10 @@ std::string& key = keys(i); octave_value val = vals(i); - if(!val.is_cell()) { - map.assign(key, Cell(dims, val)); + if(dims_match) { + map.assign(key, val.cell_value ()); } else { - const Cell c(val.cell_value()); - - if (error_state) - throw object_convert_exception("Octave error"); - - if(c.dims().length() == 2 && c.dims()(0) == 1 && c.dims()(1) == 1) { - map.assign(key, Cell(dims, c(0))); - } - else { - map.assign(key, c); - } - } - if (error_state) { - throw object_convert_exception("Octave error"); + map.assign(key, val); } } oct_value = map; diff -r b001edc0f81a -r 43a413b7c151 test/test.py --- a/test/test.py Wed Jun 03 16:41:44 2009 +0200 +++ b/test/test.py Mon Jun 08 11:55:19 2009 +0200 @@ -3,6 +3,7 @@ import pytave import Numeric +import traceback print "No messages indicates test pass." @@ -11,72 +12,76 @@ arr1_0 = Numeric.zeros((1,0)); 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) -arr1b = Numeric.array([[8, 2, 3, 256]], Numeric.Int8) -arr1i = Numeric.array([[17, 2, 3, 4]], Numeric.Int) -arr1i32 = Numeric.array([[32, 2, 3, 4]], Numeric.Int32) -arr1a = Numeric.array([[1, 2, 3, 4]]) +arr1fT2 = Numeric.array([1.32, 2, 3, 4], Numeric.Float32) +arr1f = Numeric.array([1.32, 2, 3, 4], Numeric.Float32) +arr1b = Numeric.array([8, 2, 3, 256], Numeric.Int8) +arr1i = Numeric.array([17, 2, 3, 4], Numeric.Int) +arr1i32 = Numeric.array([32, 2, 3, 4], Numeric.Int32) +arr1a = Numeric.array([1, 2, 3, 4]) 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) -arr1o = Numeric.array([["abc",1.0,2+3j]],Numeric.PyObject) -arr2o = Numeric.array([["abc",1.0,2+3j],[4.0,arr1i,"def"]],Numeric.PyObject) -arr1ch = Numeric.array(["abc"],Numeric.Character) +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); -alimit_int8 = Numeric.array([[-128, 127, -129, 128]], Numeric.Int8); -alimit_uint8 = Numeric.array([[0, 255, -1, 256]], Numeric.UnsignedInt8); +alimit_int32 = Numeric.array([-2147483648, 2147483647], Numeric.Int32); +alimit_int16 = Numeric.array([-32768, 32767, -32769, 32768], Numeric.Int16); +alimit_int8 = Numeric.array([-128, 127, -129, 128], Numeric.Int8); +alimit_uint8 = Numeric.array([0, 255, -1, 256], Numeric.UnsignedInt8); # 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(0, "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 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 + 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 + 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 + 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 @@ -85,16 +90,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 @@ -111,11 +116,9 @@ try: results = pytave.eval(numargout, code); if results != expectations: - print "FAIL: eval: ", code, " because", results, " != ", expectations, "," + fail("eval: %s : because %s != %s" % (code, results, expectations)) except Exception, e: - print "FAIL: eval:", code, ":", e -def testcellinvariant(value): - pass + fail("eval: %s" % code, e) def testsetget(name,value): try: @@ -134,6 +137,9 @@ pass except Exception, e: print "FAIL: ", name + fail("eval: %s : because %s != %s" % (code, results, expectations)) + except Exception, e: + fail("eval: %s" % code, e) def testlocalscope(x): @@ -145,7 +151,7 @@ fxm1 = sloppy_factorial(xm1) else: fxm1 = 1 - pytave.setvar("fxm1",fxm1) + pytave.locals["fxm1"] = fxm1 fx, = pytave.eval(1,"x * fxm1") return fx @@ -155,12 +161,9 @@ for k in range(1,x+1): fx1 = k * fx1 if fx != fx1: - print 'FAIL: testlocalscope: result incorrect' + fail('testlocalscope: result incorrect') except Exception, e: - print "FAIL: testlocalscope:", (x,), e - - -testequal('a') + fail("testlocalscope: %s" % (x,), e) testmatrix(alimit_int32) @@ -169,11 +172,8 @@ # Strings # Multi-row character matrix cannot be returned -testvalueerror("eval", "['foo'; 'bar']") -testequal('a') testequal("mystring") -testequal('mystring') testequal("mystringåäöÅÄÖ") testequal(1) @@ -188,12 +188,12 @@ testmatrix(arr1fT2) testmatrix(arr1i) testmatrix(arr1b) -testmatrix(arr1c) testmatrix(arr1fc) # 2d arrays testmatrix(arr2f) testmatrix(arr2d) +testmatrix(arr2ch) # 3d arrays testmatrix(arr3f) @@ -203,7 +203,6 @@ print "FAIL: Zero test", testmatrix(arr0_0) -testmatrix(arr1_0) testmatrix(arr0_1) # Lists @@ -216,38 +215,24 @@ testvalueok("cell", 1, 3); testvalueok("cell", 1, 0) testvalueok("cell", 0, 0) - -# Return cells with incompatible dimensions -testvalueerror("cell", 3, 1) -testvalueerror("cell", 0, 1) +testvalueok("cell", 3, 1) +testvalueok("cell", 0, 1) # Dictionaries # Simple dictionary tests -testequal({"foo": [1], "bar": [2]}) +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({}) +testequal({"foo": arr1f, "bar": arr2f}) +testequal({"foo": 1, "bar": [2]}) +testexpect({"foo": [[1,2]], "bar": [[3,2]]}, + {"foo": [1,2], "bar": [3,2]}) # 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]}) # Try some invalid keys testobjecterror({"this is not an Octave identifier": 1}) @@ -259,8 +244,8 @@ testobjecterror(()) result, = pytave.feval(1, "eval", "[1, 1, 1]") -if result.shape != (1, 3): - print "FAIL: expected 1x3 matrix" +if result.shape != (3,): + print "FAIL: expected length-3 vector" result, = pytave.feval(1, "eval", "[1; 2; 3]"); if result.shape != (3, 1): @@ -269,13 +254,16 @@ testparseerror(1, "endfunction") testevalexpect(1, "2 + 2", (4,)) testevalexpect(1, "{2}", ([2],)) -testevalexpect(2, "struct('foo', 2)", ({'foo': [2]},)) - -testsetget("xxx", [1,2,3]) +testevalexpect(2, "struct('foo', 2)", ({'foo': 2},)) testlocalscope(5) -# Try converting Numeric arrays of objects and characters - -testexpect(arr1o,arr1o[0].tolist()) -testexpect(arr1ch,arr1ch.tostring()) +# 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 :