# HG changeset patch # User Jaroslav Hajek # Date 1245229520 -7200 # Node ID 2f4d6286fb36719c635dd44501206a15772daf45 # Parent 1132677391cf337a48b5e858247829d1ea464898 make Octave->Python conversions uniformly arrays diff -r 1132677391cf -r 2f4d6286fb36 ChangeLog --- a/ChangeLog Tue Jun 09 09:04:34 2009 +0200 +++ b/ChangeLog Wed Jun 17 11:05:20 2009 +0200 @@ -1,3 +1,17 @@ +2009-06-16 Jaroslav Hajek + + * 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 * package/pytave.py (canonicalize): New function. diff -r 1132677391cf -r 2f4d6286fb36 octave_to_python.cc --- a/octave_to_python.cc Tue Jun 09 09:04:34 2009 +0200 +++ b/octave_to_python.cc Wed Jun 17 11:05:20 2009 +0200 @@ -112,11 +112,6 @@ 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( len, dimensions, pyarrtype); @@ -246,6 +241,11 @@ return create_sint_array( matrix.int8_array_value()); } + if (matrix.is_bool_type()) { + // Numeric does not support bools, use uint8. + return create_uint_array( + matrix.uint8_array_value()); + } if (matrix.is_string()) { return create_array( matrix.char_array_value(), PyArray_CHAR); @@ -265,43 +265,17 @@ py_object = object(handle((PyObject *)pyarr)); } - static inline bool is_1xn_or_0x0(const dim_vector& dv) { - return (dv.length() == 2 && (dv(0) == 1 || (dv(0) == 0 && dv(1) == 0))); - } - - static void octcell_to_pyobject(boost::python::object &py_object, - const Cell& cell) { - if (is_1xn_or_0x0 (cell.dims ())) { - py_object = boost::python::list(); - - 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); - } - } else { - octvalue_to_pyarr (py_object, octave_value (cell)); - } - } - static void octmap_to_pyobject(boost::python::object &py_object, 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; const Cell c = map.contents(keys[i]); - if (scalar) { - octvalue_to_pyobj(py_val, c(0)); - } else { - octcell_to_pyobject(py_val, c); - } + octvalue_to_pyarr(py_val, c); py_object[keys[i]] = py_val; } @@ -309,33 +283,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_complex_scalar()) - py_object = object(octvalue.complex_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 (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()) { + } 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"); diff -r 1132677391cf -r 2f4d6286fb36 package/pytave.py --- a/package/pytave.py Tue Jun 09 09:04:34 2009 +0200 +++ b/package/pytave.py Wed Jun 17 11:05:20 2009 +0200 @@ -73,18 +73,16 @@ 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. + 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) - struct dict + All scalar values are regarded as 1x1 matrices, as they are in + Octave. Matrix values to Numeric arrays: double DOUBLE @@ -97,11 +95,8 @@ char CHAR cell OBJECT - If an array is a row vector, it is converted to a 1D Numeric - array. Further, a character row vector is converted to a - Python string, and a cell row vector is converted to a list. - - To reverse these simplifications, see pytave.canonicalize. + 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. @@ -254,52 +249,6 @@ """ return _LocalScope(func) -def canonicalize(obj, level=None): - """Canonicalizes output values as returned from Octave. - By default, Pytave applies aggressive simplifications to - the results returned - row vectors are converted to 1D - Numeric arrays, row character vectors are further converted - to Python strings, and row cell vectors are converted to - Python lists. This function can be used to reverse these - simplifications if necessary. It works recursively on tuples, - lists, object arrays and dicts. - level specifies the requested level of recursion. If not - given, infinite recursion is used. - """ - - if level == 0: - return obj - elif level: - level = level - 1 - - if isinstance(obj,list): - objr = Numeric.array([None]*len(obj),Numeric.PyObject) - for i in range(len(obj)): - objr[i] = canonicalize(obj[i], level) - return Numeric.reshape(objr,(1,len(obj))) - elif isinstance(obj,tuple): - return tuple(canonicalize(o,level) for o in obj) - elif isinstance(obj,str): - return Numeric.array([obj],Numeric.Character) - elif isinstance(obj,dict): - dictr = {} - for key,value in dict.iteritems(): - dictr[key] = canonicalize(value,level) - return dictr - elif isinstance(obj,Numeric.ArrayType): - dims = Numeric.shape(obj) - if obj.typecode() == Numeric.PyObject: - numel = Numeric.size(obj) - objr = Numeric.array([None]*numel) - for i in range(numel): - objr[i] = canonicalize(obj.flat[i],level) - obj = Numeric.reshape(objr, dims) - if len(dims) == 1: - obj = Numeric.reshape(obj, (1, dims[0])) - return obj - else: - return obj - # Emacs # Local Variables: # fill-column:70 diff -r 1132677391cf -r 2f4d6286fb36 python_to_octave.cc --- a/python_to_octave.cc Tue Jun 09 09:04:34 2009 +0200 +++ b/python_to_octave.cc Wed Jun 17 11:05:20 2009 +0200 @@ -310,8 +310,6 @@ dim_vector dims = dim_vector(1, 1); - bool dims_match = true; - Array vals (length); Array keys (length); @@ -346,15 +344,13 @@ pyobj_to_octvalue(val, tuple[1]); - if(dims_match && val.is_cell()) { - dim_vector dv = val.dims(); + if(val.is_cell()) { if(i == 0) { - dims = dv; - } else { - dims_match = dims == dv; + dims = val.dims(); + } else if (val.numel() != 1 && val.dims() != dims){ + throw object_convert_exception( + "Dimensions of the struct fields do not match"); } - } else { - dims_match = false; } } @@ -365,10 +361,15 @@ std::string& key = keys(i); octave_value val = vals(i); - if(dims_match) { - map.assign(key, val.cell_value ()); + if(val.is_cell()) { + const Cell c = val.cell_value(); + if (c.numel () == 1) { + map.assign(key, Cell(dims, c(0))); + } else { + map.assign(key, c); + } } else { - map.assign(key, val); + map.assign(key, Cell(dims, val)); } } oct_value = map; diff -r 1132677391cf -r 2f4d6286fb36 test/test.py --- a/test/test.py Tue Jun 09 09:04:34 2009 +0200 +++ b/test/test.py Wed Jun 17 11:05:20 2009 +0200 @@ -10,28 +10,28 @@ 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) -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) -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) +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 @@ -163,20 +163,25 @@ 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) testmatrix(alimit_int16) testmatrix(alimit_int8) # Strings -# Multi-row character matrix cannot be returned -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)) +testexpect(1L,Numeric.array([[1]],Numeric.Int64)) +testexpect(1.0,Numeric.array([[1]],Numeric.Float)) # Vector arrays testmatrix(arr1a) @@ -201,12 +206,11 @@ testmatrix(arr0_0) testmatrix(arr0_1) +testmatrix(arr1_0) # 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); @@ -217,19 +221,20 @@ # 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]}) +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": 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. +testequal({"foo": arr2o, "bar": arr2o}) # Try some invalid keys testobjecterror({"this is not an Octave identifier": 1}) @@ -241,7 +246,7 @@ testobjecterror(()) result, = pytave.feval(1, "eval", "[1, 1, 1]") -if result.shape != (3,): +if result.shape != (1,3): print "FAIL: expected length-3 vector" result, = pytave.feval(1, "eval", "[1; 2; 3]"); @@ -250,11 +255,11 @@ testparseerror(1, "endfunction") testevalexpect(1, "2 + 2", (4,)) -testevalexpect(1, "{2}", ([2],)) -testevalexpect(1, "struct('foo', 2)", ({'foo': 2},)) +testevalexpect(1, "{2}", (objarray([[2]]),)) +testevalexpect(1, "struct('foo', 2)", ({'foo': objarray([[2]])},)) -testsetget(pytave.locals, "xxx", [1,2,3]) -testsetget(pytave.globals, "xxx", [1,2,3]) +testsetget(pytave.locals, "xxx", arr1f) +testsetget(pytave.globals, "xxx", arr2o) def func(): pytave.locals["this is not a valid Octave identifier"] = 1