changeset 352:eac35d84ef0d

Convert Octave numeric vectors into Python array.array * oct-py-types.cc, oct-py-types.h (pytave::make_py_array): New functions to convert an Octave numeric array into Python array object of appropriate type. * octave_to_python.cc (pytave::octvalue_to_pyobj): Add numeric vector case. * pycall.cc: Delete %!tests that relied on conversion to numpy array.
author Mike Miller <mtmiller@octave.org>
date Wed, 17 Aug 2016 21:19:58 -0700
parents 040aff46e4db
children 826a23f63f75
files oct-py-types.cc oct-py-types.h octave_to_python.cc pycall.cc
diffstat 4 files changed, 186 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/oct-py-types.cc	Wed Aug 17 14:02:33 2016 -0700
+++ b/oct-py-types.cc	Wed Aug 17 21:19:58 2016 -0700
@@ -29,6 +29,7 @@
 #include <octave/quit.h>
 
 #include "exceptions.h"
+#include "oct-py-eval.h"
 #include "oct-py-types.h"
 
 // FIXME: only here to bootstrap nested conversions needed in this file
@@ -134,6 +135,112 @@
 }
 
 PyObject *
+make_py_array (const void *data, size_t len, char typecode)
+{
+  if (! typecode)
+    throw object_convert_exception ("unable to create array from Octave data");
+
+  std::string arg { typecode };
+  PyObject *array = py_call_function ("array.array", ovl (arg));
+
+  if (len > 0)
+    {
+      // create a byte buffer containing a copy of the array binary data
+      const char *cdata = reinterpret_cast<const char *> (data);
+      PyObject *buf = PyBytes_FromStringAndSize (cdata, len);
+      if (! buf)
+        octave_throw_bad_alloc ();
+
+      PyObject *frombytes = (PyObject_HasAttrString (array, "frombytes") ?
+                             PyObject_GetAttrString (array, "frombytes") :
+                             PyObject_GetAttrString (array, "fromstring"));
+      PyObject *args = PyTuple_Pack (1, buf);
+      py_call_function (frombytes, args);
+      Py_DECREF (args);
+      Py_DECREF (buf);
+    }
+
+  return array;
+}
+
+// Prefer the 'q' and 'Q' typecodes if they are available (if Python 3 and
+// built with support for long long integers)
+
+#if (PY_VERSION_HEX >= 0x03000000) && defined (HAVE_LONG_LONG)
+#  define ARRAY_INT64_TYPECODE 'q'
+#  define ARRAY_UINT64_TYPECODE 'Q'
+#elif (SIZEOF_LONG == 8)
+#  define ARRAY_INT64_TYPECODE 'l'
+#  define ARRAY_UINT64_TYPECODE 'L'
+#else
+#  define ARRAY_INT64_TYPECODE 0
+#  define ARRAY_UINT64_TYPECODE 0
+#endif
+
+template <typename T>
+struct py_array_info { };
+
+template <>
+struct py_array_info<octave_int8> { static const char typecode = 'b'; };
+
+template <>
+struct py_array_info<octave_int16> { static const char typecode = 'h'; };
+
+template <>
+struct py_array_info<octave_int32> { static const char typecode = 'i'; };
+
+template <>
+struct py_array_info<octave_int64>
+{
+  static const char typecode = ARRAY_INT64_TYPECODE;
+};
+
+template <>
+struct py_array_info<octave_uint8> { static const char typecode = 'B'; };
+
+template <>
+struct py_array_info<octave_uint16> { static const char typecode = 'H'; };
+
+template <>
+struct py_array_info<octave_uint32> { static const char typecode = 'I'; };
+
+template <>
+struct py_array_info<octave_uint64> {
+  static const char typecode = ARRAY_UINT64_TYPECODE;
+};
+
+PyObject *
+make_py_array (const NDArray& nda)
+{
+  return make_py_array (nda.data (), nda.numel () * sizeof (double), 'd');
+}
+
+PyObject *
+make_py_array (const FloatNDArray& nda)
+{
+  return make_py_array (nda.data (), nda.numel () * sizeof (float), 'f');
+}
+
+template <typename T>
+PyObject *
+make_py_array (const intNDArray<T>& nda)
+{
+  return make_py_array (nda.data (), nda.numel () * sizeof (T),
+                        py_array_info<T>::typecode);
+}
+
+// Instantiate all possible integer array template functions needed
+
+template PyObject * make_py_array<octave_int8> (const int8NDArray&);
+template PyObject * make_py_array<octave_int16> (const int16NDArray&);
+template PyObject * make_py_array<octave_int32> (const int32NDArray&);
+template PyObject * make_py_array<octave_int64> (const int64NDArray&);
+template PyObject * make_py_array<octave_uint8> (const uint8NDArray&);
+template PyObject * make_py_array<octave_uint16> (const uint16NDArray&);
+template PyObject * make_py_array<octave_uint32> (const uint32NDArray&);
+template PyObject * make_py_array<octave_uint64> (const uint64NDArray&);
+
+PyObject *
 make_py_numeric_value (const octave_value& value)
 {
   if (value.is_scalar_type ())
@@ -169,6 +276,40 @@
   return 0;
 }
 
+PyObject *
+make_py_array (const octave_value& value)
+{
+  if (value.is_numeric_type () && ! value.is_complex_type ()
+      && value.ndims () == 2 && (value.columns () == 1 || value.rows () == 1))
+    {
+      if (value.is_double_type ())
+        return make_py_array (value.array_value ());
+      else if (value.is_single_type ())
+        return make_py_array (value.float_array_value ());
+
+      else if (value.is_int8_type ())
+        return make_py_array (value.int8_array_value ());
+      else if (value.is_int16_type ())
+        return make_py_array (value.int16_array_value ());
+      else if (value.is_int32_type ())
+        return make_py_array (value.int32_array_value ());
+      else if (value.is_int64_type ())
+        return make_py_array (value.int64_array_value ());
+
+      else if (value.is_uint8_type ())
+        return make_py_array (value.uint8_array_value ());
+      else if (value.is_uint16_type ())
+        return make_py_array (value.uint16_array_value ());
+      else if (value.is_uint32_type ())
+        return make_py_array (value.uint32_array_value ());
+      else if (value.is_uint64_type ())
+        return make_py_array (value.uint64_array_value ());
+    }
+
+  throw value_convert_exception ("unhandled Octave numeric vector type");
+  return 0;
+}
+
 inline PyObject *
 wrap_octvalue_to_pyobj (const octave_value& value)
 {
--- a/oct-py-types.h	Wed Aug 17 14:02:33 2016 -0700
+++ b/oct-py-types.h	Wed Aug 17 21:19:58 2016 -0700
@@ -28,6 +28,9 @@
 #include <string>
 
 class Cell;
+class FloatNDArray;
+class NDArray;
+template <typename T> class intNDArray;
 class octave_scalar_map;
 class octave_value;
 
@@ -128,6 +131,41 @@
 PyObject *
 make_py_int (uint64_t value);
 
+//! Create a Python array object with the value of the given Octave array.
+//!
+//! @param nda array value
+//! @return Python array object
+PyObject *
+make_py_array (const NDArray& nda);
+
+//! Create a Python array object with the value of the given Octave array.
+//!
+//! @param nda array value
+//! @return Python array object
+PyObject *
+make_py_array (const FloatNDArray& nda);
+
+//! Create a Python array object with the value of the given Octave array.
+//!
+//! @param nda array value
+//! @return Python array object
+template <typename T>
+PyObject *
+make_py_array (const intNDArray<T>& nda);
+
+//! Create a Python array object from the given Octave numeric vector.
+//!
+//! All Octave real floating point and integer values are converted to
+//! corresponding Python array types by this function.
+//!
+//! @warning Depending on the version of Python and how it is configured,
+//!          @c int64 and @c uint64 vectors may not be supported.
+//!
+//! @param value Octave numeric or boolean scalar value
+//! @return Python array object
+PyObject *
+make_py_array (const octave_value& value);
+
 //! Create a Python tuple object from the given Octave cell array value.
 //!
 //! The values contained in the cell array are recursively converted to
--- a/octave_to_python.cc	Wed Aug 17 14:02:33 2016 -0700
+++ b/octave_to_python.cc	Wed Aug 17 21:19:58 2016 -0700
@@ -178,6 +178,12 @@
         PyObject *obj = make_py_tuple (octvalue.cell_value ());
         py_object = object (handle<PyObject> (obj));
       }
+    else if (octvalue.is_numeric_type () && octvalue.ndims () == 2
+             && (octvalue.columns () == 1 || octvalue.rows () == 1))
+      {
+        PyObject *obj = make_py_array (octvalue);
+        py_object = object (handle<PyObject> (obj));
+      }
     else if (octvalue.is_numeric_type () || octvalue.is_string ()
              || octvalue.is_bool_type ())
       octvalue_to_pyarr (py_object, octvalue);
--- a/pycall.cc	Wed Aug 17 14:02:33 2016 -0700
+++ b/pycall.cc	Wed Aug 17 21:19:58 2016 -0700
@@ -193,8 +193,7 @@
 ## Test round trip type preservation / conversion
 %!test
 %! pyexec ("def roundtrip(x): return x");
-%! values = { 0, pi, 2j, eps, false, true, version, "Hello world", ...
-%!            [1, 2, 3], eye (4) };
+%! values = { 0, pi, 2j, eps, false, true, version, "Hello world" };
 %! for i = 1:numel (values)
 %!   assert (pycall ("roundtrip", values{i}), values{i});
 %! endfor
@@ -248,11 +247,6 @@
 %!assert (isa (pycall ("int", 2^100), "pyobject"))
 
 %!test
-%! pyexec ("def pyfunc(x): return 2*x");
-%! z = pycall ("pyfunc", [20 20]);
-%! assert (z, [40 40])
-
-%!test
 %! pyexec (["def pyfunc(x):\n" ...
 %!         "    if x is True:\n        return 30\n" ...
 %!         "    elif x is False:\n        return 20\n" ...