changeset 68:2f4d6286fb36

make Octave->Python conversions uniformly arrays
author Jaroslav Hajek <highegg@gmail.com>
date Wed, 17 Jun 2009 11:05:20 +0200
parents 1132677391cf
children 4954c14457f2
files ChangeLog octave_to_python.cc package/pytave.py python_to_octave.cc test/test.py
diffstat 5 files changed, 91 insertions(+), 167 deletions(-) [+]
line wrap: on
line diff
--- 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  <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.
--- 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<int8NDArray, sizeof(int8_t)>(
             matrix.int8_array_value());
       }
+      if (matrix.is_bool_type()) {
+         // Numeric does not support bools, use uint8.
+         return create_uint_array<uint8NDArray, sizeof(uint8_t)>(
+            matrix.uint8_array_value());
+      }
       if (matrix.is_string()) {
          return create_array<char, charNDArray>(
             matrix.char_array_value(), PyArray_CHAR);
@@ -265,43 +265,17 @@
       py_object = object(handle<PyObject>((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");
--- 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
--- 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<octave_value> vals (length);
       Array<std::string> 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;
--- 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