array.cpp 14.7 KB
Newer Older
1
2
3
4
5
6
7
/**
 * @author Andre Anjos <andre.anjos@idiap.ch>
 * @date Tue 01 Oct 2013 15:37:07 CEST
 *
 * @brief Pure python bindings for Blitz Arrays
 */

André Anjos's avatar
André Anjos committed
8
9
#define BOB_BLITZ_MODULE
#include <bob.blitz/capi.h>
10
#include <structmember.h>
11

André Anjos's avatar
André Anjos committed
12
PyDoc_STRVAR(s_array_str, BOB_EXT_MODULE_PREFIX ".array");
13

André Anjos's avatar
André Anjos committed
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
PyDoc_STRVAR(s_array_doc,
"array(shape, dtype) -> new n-dimensional blitz::Array\n\
\n\
An N-dimensional blitz::Array<T,N> pythonic representation\n\
\n\
Constructor parameters:\n\
\n\
shape\n\
  An iterable, indicating the shape of the array to be constructed\n\
  \n\
  The implementation current supports a maximum of 4 dimensions.\n\
  Building an array with more dimensions will raise a \n\
  :py:class:`TypeError`. There are no explicit limits for the size in\n\
  each dimension, except for the machine's maximum address size.\n\
\n\
dtype\n\
  A :py:class:`numpy.dtype` or ``dtype`` convertible object that\n\
  specified the type of elements in this array.\n\
  \n\
  The following numpy data types are supported by this library:\n\
  \n\
    * :py:class:`numpy.bool_`\n\
    * :py:class:`numpy.int8`\n\
    * :py:class:`numpy.int16`\n\
    * :py:class:`numpy.int32`\n\
    * :py:class:`numpy.int64`\n\
    * :py:class:`numpy.uint8`\n\
    * :py:class:`numpy.uint16`\n\
    * :py:class:`numpy.uint32`\n\
    * :py:class:`numpy.uint64`\n\
    * :py:class:`numpy.float32`\n\
    * :py:class:`numpy.float64`\n\
    * :py:class:`numpy.float128` (if this architecture suppports it)\n\
    * :py:class:`numpy.complex64`\n\
    * :py:class:`numpy.complex128`\n\
    * :py:class:`numpy.complex256` (if this architecture suppports it)\n\
\n\
Objects of this class hold a pointer to C++ ``blitz::Array<T,N>``.\n\
The C++ data type ``T`` is mapped to a :py:class:`numpy.dtype` object,\n\
while the extents and number of dimensions ``N`` are mapped to a shape,\n\
similar to what is done for :py:class:`numpy.ndarray` objects.\n\
\n\
Objects of this class can be wrapped in :py:class:`numpy.ndarray`\n\
quite efficiently, so that flexible numpy-like operations are possible\n\
on its contents. You can also deploy objects of this class wherever\n\
:py:class:`numpy.ndarray`'s may be input.\n\
"
);

63
64
65
/**
 * Formal initialization of an Array object
 */
66
static int PyBlitzArray_init(PyBlitzArrayObject* self, PyObject *args,
67
68
69
    PyObject* kwds) {

  /* Parses input arguments in a single shot */
70
71
  static const char* const_kwlist[] = {"shape", "dtype", 0};
  static char** kwlist = const_cast<char**>(const_kwlist);
72

73
74
  PyBlitzArrayObject shape;
  PyBlitzArrayObject* shape_p = &shape;
75
  int type_num = NPY_NOTYPE;
76
  int* type_num_p = &type_num;
77
78
79

  if (!PyArg_ParseTupleAndKeywords(
        args, kwds, "O&O&", kwlist,
80
        &PyBlitzArray_IndexConverter, &shape_p,
81
        &PyBlitzArray_TypenumConverter, &type_num_p)
82
      )
83
    return -1; ///< FAILURE
84

85
86
87
88
89
90
91
92
  /* Checks if none of the shape positions are zero */
  for (Py_ssize_t i=0; i<shape.ndim; ++i) {
    if (shape.shape[i] == 0) {
      PyErr_Format(PyExc_ValueError, "shape values should not be 0, but one was found at position %" PY_FORMAT_SIZE_T "d of input sequence", i);
      return -1; ///< FAILURE
    }
  }

93
94
  PyBlitzArrayObject* tmp = reinterpret_cast<PyBlitzArrayObject*>(PyBlitzArray_SimpleNew(type_num, shape.ndim, shape.shape));
  if (!tmp) return -1;
95

96
97
  /* Copies the new object to the pre-allocated one */
  (*self) = (*tmp);
André Anjos's avatar
André Anjos committed
98
  Py_TYPE(tmp)->tp_free((PyObject*)tmp); ///< Deallocates only the Python stuff
99
100
101
102
103
104
105

  return 0; ///< SUCCESS
}

/**
 * Methods for Sequence operation
 */
106
static Py_ssize_t PyBlitzArray_len (PyBlitzArrayObject* self) {
107
  Py_ssize_t retval = 1;
108
  for (Py_ssize_t i=0; i<self->ndim; ++i) retval *= self->shape[i];
109
110
111
  return retval;
}

112
static PyObject* PyBlitzArray_getitem(PyBlitzArrayObject* self,
113
114
115
116
    PyObject* item) {

  if (PyNumber_Check(item)) {

117
    if (self->ndim != 1) {
118
      PyErr_Format(PyExc_TypeError, "expected tuple for accessing %" PY_FORMAT_SIZE_T "dD array", self->ndim);
119
120
121
122
123
      return 0;
    }

    // if you get to this point, the user has passed single number
    Py_ssize_t k = PyNumber_AsSsize_t(item, PyExc_IndexError);
124
    return PyBlitzArray_GetItem(self, &k);
125
126
127
128
129

  }

  if (PySequence_Check(item)) {

130
    if (self->ndim != PySequence_Fast_GET_SIZE(item)) {
131
      PyErr_Format(PyExc_TypeError, "expected tuple of size %" PY_FORMAT_SIZE_T "d for accessing %" PY_FORMAT_SIZE_T "dD array", self->ndim, self->ndim);
132
133
134
135
      return 0;
    }

    // if you get to this point, then the input tuple has the same size
136
137
    PyBlitzArrayObject shape;
    PyBlitzArrayObject* shape_p = &shape;
138
    if (!PyBlitzArray_IndexConverter(item, &shape_p)) return 0;
139
    return PyBlitzArray_GetItem(self, shape.shape);
140
141
142

  }

André Anjos's avatar
André Anjos committed
143
  PyErr_Format(PyExc_TypeError, "%s(@%" PY_FORMAT_SIZE_T "d,'%s') indexing requires a single integers (for 1D arrays) or sequences, for any rank size", Py_TYPE(self)->tp_name, self->ndim, PyBlitzArray_TypenumAsString(self->type_num));
144
145
146
  return 0;
}

147
static int PyBlitzArray_setitem(PyBlitzArrayObject* self, PyObject* item,
148
149
150
151
    PyObject* value) {

  if (PyNumber_Check(item)) {

152
    if (self->ndim != 1) {
André Anjos's avatar
André Anjos committed
153
      PyErr_Format(PyExc_TypeError, "expected sequence for accessing %s(@%" PY_FORMAT_SIZE_T "d,'%s'", Py_TYPE(self)->tp_name, self->ndim, PyBlitzArray_TypenumAsString(self->type_num));
154
155
156
157
158
      return -1;
    }

    // if you get to this point, the user has passed single number
    Py_ssize_t k = PyNumber_AsSsize_t(item, PyExc_IndexError);
159
    return PyBlitzArray_SetItem(self, &k, value);
160
161
162
163
164

  }

  if (PySequence_Check(item)) {

165
    if (self->ndim != PySequence_Fast_GET_SIZE(item)) {
André Anjos's avatar
André Anjos committed
166
      PyErr_Format(PyExc_TypeError, "expected sequence of size %" PY_FORMAT_SIZE_T "d for accessing %s(@%" PY_FORMAT_SIZE_T "d,'%s')", PySequence_Fast_GET_SIZE(item), Py_TYPE(self)->tp_name, self->ndim, PyBlitzArray_TypenumAsString(self->type_num));
167
168
169
170
      return -1;
    }

    // if you get to this point, then the input tuple has the same size
171
172
    PyBlitzArrayObject shape;
    PyBlitzArrayObject* shape_p = &shape;
173
    if (!PyBlitzArray_IndexConverter(item, &shape_p)) return 0;
174
    return PyBlitzArray_SetItem(self, shape.shape, value);
175
176
177

  }

André Anjos's avatar
André Anjos committed
178
  PyErr_Format(PyExc_TypeError, "%s(@%" PY_FORMAT_SIZE_T "d,'%s') assignment requires a single integers (for 1D arrays) or sequences, for any rank size", Py_TYPE(self)->tp_name, self->ndim, PyBlitzArray_TypenumAsString(self->type_num));
179
180
181
  return -1;
}

182
183
184
185
static PyMappingMethods PyBlitzArray_mapping = {
    (lenfunc)PyBlitzArray_len,
    (binaryfunc)PyBlitzArray_getitem,
    (objobjargproc)PyBlitzArray_setitem,
186
187
};

188
PyDoc_STRVAR(s_as_ndarray_str, "as_ndarray");
189
PyDoc_STRVAR(s_private_array_str, "__array__");
190
PyDoc_STRVAR(s_private_array_doc,
191
192
"x.__array__([dtype]) -> numpy.ndarray\n\
x.as_ndarray([dtype]) -> numpy.ndarray\n\
193
\n\
André Anjos's avatar
André Anjos committed
194
numpy.ndarray accessor (shallow wraps ``bob.blitz.array`` as\n\
André Anjos's avatar
André Anjos committed
195
numpy.ndarray). If ``dtype`` is given and the current data type\n\
196
197
198
199
200
201
202
203
204
205
206
207
is not the same, then forces the creation of a copy conforming\n\
to the require data type, if possible.\n\
");

static PyObject* PyBlitzArray_AsNumpyArrayPrivate(PyBlitzArrayObject* self,
    PyObject* args, PyObject* kwds) {

  /* Parses input arguments in a single shot */
  static const char* const_kwlist[] = {"dtype", 0};
  static char** kwlist = const_cast<char**>(const_kwlist);

  PyArray_Descr* dtype = 0;
André Anjos's avatar
André Anjos committed
208
209

  if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&", kwlist,
210
211
212
213
214
        &PyArray_DescrConverter2, &dtype)) return 0;

  return PyBlitzArray_AsNumpyArray(self, dtype);

}
215

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
PyDoc_STRVAR(s_cast_str, "cast");
PyDoc_STRVAR(s_cast_doc,
"x.cast(dtype) -> blitz array\n\
\n\
Casts an existing array into a (possibly) different data type,\n\
without changing its shape. If the data type matches the current\n\
array's data type, then a new view to the same array is returned.\n\
Otherwise, a new array is allocated and returned.\n\
");

static PyObject* PyBlitzArray_SelfCast(PyBlitzArrayObject* self, PyObject* args, PyObject* kwds) {

  /* Parses input arguments in a single shot */
  static const char* const_kwlist[] = {"dtype", 0};
  static char** kwlist = const_cast<char**>(const_kwlist);

  int type_num = NPY_NOTYPE;
  int* type_num_p = &type_num;

  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&", kwlist,
        &PyBlitzArray_TypenumConverter, &type_num_p)) return 0;

  return PyBlitzArray_Cast(self, type_num);

}

242
static PyMethodDef PyBlitzArray_methods[] = {
243
    {
244
      s_as_ndarray_str,
245
246
      (PyCFunction)PyBlitzArray_AsNumpyArrayPrivate,
      METH_VARARGS|METH_KEYWORDS,
247
      s_private_array_doc
248
249
    },
    {
250
      s_private_array_str,
251
252
      (PyCFunction)PyBlitzArray_AsNumpyArrayPrivate,
      METH_VARARGS|METH_KEYWORDS,
253
254
255
256
257
258
259
      s_private_array_doc
    },
    {
      s_cast_str,
      (PyCFunction)PyBlitzArray_SelfCast,
      METH_VARARGS|METH_KEYWORDS,
      s_cast_doc
260
    },
261
    {0}  /* Sentinel */
262
263
};

264
/* Property API */
265
PyDoc_STRVAR(s_shape_str, "shape");
266
PyDoc_STRVAR(s_shape_doc,
267
268
269
270
"A tuple indicating the shape of this array (in **elements**)"
);

PyDoc_STRVAR(s_stride_str, "stride");
271
PyDoc_STRVAR(s_stride_doc,
272
"A tuple indicating the strides of this array (in **bytes**)"
273
274
275
);

PyDoc_STRVAR(s_dtype_str, "dtype");
276
PyDoc_STRVAR(s_dtype_doc,
277
278
279
"The :py:class:`numpy.dtype` for every element in this array"
);

280
PyDoc_STRVAR(s_writeable_str, "writeable");
281
PyDoc_STRVAR(s_writeable_doc,
282
283
284
285
"A flag, indicating if this array is writeable"
);

PyDoc_STRVAR(s_base_str, "base");
286
PyDoc_STRVAR(s_base_doc,
287
288
289
"If the memory of this array is borrowed from some other object, this is it"
);

290
291
static PyGetSetDef PyBlitzArray_getseters[] = {
    {
André Anjos's avatar
André Anjos committed
292
      s_dtype_str,
293
      (getter)PyBlitzArray_PyDTYPE,
294
      0,
295
      s_dtype_doc,
296
      0,
297
298
    },
    {
299
      s_shape_str,
300
      (getter)PyBlitzArray_PySHAPE,
301
      0,
302
      s_shape_doc,
303
      0,
304
    },
305
    {
306
307
308
      s_stride_str,
      (getter)PyBlitzArray_PySTRIDE,
      0,
309
      s_stride_doc,
310
311
312
313
314
315
      0,
    },
    {
      s_writeable_str,
      (getter)PyBlitzArray_PyWRITEABLE,
      0,
316
      s_writeable_doc,
317
318
319
320
321
      0,
    },
    {
      s_base_str,
      (getter)PyBlitzArray_PyBASE,
322
      0,
323
      s_base_doc,
324
325
326
      0,
    },
    {0}  /* Sentinel */
327
328
};

329
330
/* Stringification */
static PyObject* PyBlitzArray_str(PyBlitzArrayObject* o) {
331
  PyObject* nd = PyBlitzArray_AsNumpyArray(o, 0);
332
333
  if (!nd) {
    PyErr_Print();
334
    PyErr_SetString(PyExc_RuntimeError, "could not convert array into numpy ndarray for str() method call");
335
336
    return 0;
  }
André Anjos's avatar
André Anjos committed
337
  PyObject* retval = PyObject_Str(nd);
338
339
340
341
342
343
  Py_DECREF(nd);
  return retval;
}

/* Representation */
static PyObject* PyBlitzArray_repr(PyBlitzArrayObject* o) {
344
345
  switch (o->ndim) {
    case 1:
André Anjos's avatar
André Anjos committed
346
      return
André Anjos's avatar
André Anjos committed
347
348
349
350
351
352
353
#       if PY_VERSION_HEX >= 0x03000000
        PyUnicode_FromFormat
#       else
        PyString_FromFormat
#       endif
          ("%s(%" PY_FORMAT_SIZE_T "d,'%s')",
          Py_TYPE(o)->tp_name,
354
355
356
357
          o->shape[0],
          PyBlitzArray_TypenumAsString(o->type_num)
          );
    case 2:
André Anjos's avatar
André Anjos committed
358
      return
André Anjos's avatar
André Anjos committed
359
360
361
362
363
364
365
#       if PY_VERSION_HEX >= 0x03000000
        PyUnicode_FromFormat
#       else
        PyString_FromFormat
#       endif
          ("%s((%" PY_FORMAT_SIZE_T "d,%" PY_FORMAT_SIZE_T "d),'%s')",
          Py_TYPE(o)->tp_name,
366
367
368
369
370
          o->shape[0],
          o->shape[1],
          PyBlitzArray_TypenumAsString(o->type_num)
          );
    case 3:
André Anjos's avatar
André Anjos committed
371
      return
André Anjos's avatar
André Anjos committed
372
373
374
375
376
#       if PY_VERSION_HEX >= 0x03000000
        PyUnicode_FromFormat
#       else
        PyString_FromFormat
#       endif
André Anjos's avatar
André Anjos committed
377
          ("%s((%" PY_FORMAT_SIZE_T "d,%" PY_FORMAT_SIZE_T "d,%" PY_FORMAT_SIZE_T "d),'%s')",
André Anjos's avatar
André Anjos committed
378
          Py_TYPE(o)->tp_name,
379
380
381
382
383
384
          o->shape[0],
          o->shape[1],
          o->shape[2],
          PyBlitzArray_TypenumAsString(o->type_num)
          );
    case 4:
André Anjos's avatar
André Anjos committed
385
386
387
388
389
390
      return
#       if PY_VERSION_HEX >= 0x03000000
        PyUnicode_FromFormat
#       else
        PyString_FromFormat
#       endif
André Anjos's avatar
André Anjos committed
391
          ("%s((%" PY_FORMAT_SIZE_T "d,%" PY_FORMAT_SIZE_T "d,%" PY_FORMAT_SIZE_T "d,%" PY_FORMAT_SIZE_T "d),'%s')",
André Anjos's avatar
André Anjos committed
392
          Py_TYPE(o)->tp_name,
393
394
395
396
397
398
399
          o->shape[0],
          o->shape[1],
          o->shape[2],
          o->shape[3],
          PyBlitzArray_TypenumAsString(o->type_num)
          );
    default:
André Anjos's avatar
André Anjos committed
400
401
402
403
404
405
406
407
      return
#       if PY_VERSION_HEX >= 0x03000000
        PyUnicode_FromFormat
#       else
        PyString_FromFormat
#       endif
          ("[unsupported] %s(@%" PY_FORMAT_SIZE_T "d,'%s') %" PY_FORMAT_SIZE_T "d elements>",
          Py_TYPE(o)->tp_name,
408
409
410
411
412
          o->ndim,
          PyBlitzArray_TypenumAsString(o->type_num),
          PyBlitzArray_len(o)
          );
  }
413
414
}

André Anjos's avatar
André Anjos committed
415
416
/* Members */
static PyMemberDef PyBlitzArray_members[] = {
417
    {0}  /* Sentinel */
André Anjos's avatar
André Anjos committed
418
419
};

420
PyTypeObject PyBlitzArray_Type = {
André Anjos's avatar
André Anjos committed
421
    PyVarObject_HEAD_INIT(0, 0)
422
    s_array_str,                                /*tp_name*/
423
424
425
426
427
428
429
    sizeof(PyBlitzArrayObject),                 /*tp_basicsize*/
    0,                                          /*tp_itemsize*/
    (destructor)PyBlitzArray_Delete,            /*tp_dealloc*/
    0,                                          /*tp_print*/
    0,                                          /*tp_getattr*/
    0,                                          /*tp_setattr*/
    0,                                          /*tp_compare*/
430
    (reprfunc)PyBlitzArray_repr,                /*tp_repr*/
431
432
433
434
435
    0,                                          /*tp_as_number*/
    0,                                          /*tp_as_sequence*/
    &PyBlitzArray_mapping,                      /*tp_as_mapping*/
    0,                                          /*tp_hash */
    0,                                          /*tp_call*/
436
    (reprfunc)PyBlitzArray_str,                 /*tp_str*/
437
438
439
    0,                                          /*tp_getattro*/
    0,                                          /*tp_setattro*/
    0,                                          /*tp_as_buffer*/
440
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /*tp_flags*/
André Anjos's avatar
André Anjos committed
441
    s_array_doc,                                /* tp_doc */
442
443
444
445
446
447
448
    0,		                                      /* tp_traverse */
    0,		                                      /* tp_clear */
    0,		                                      /* tp_richcompare */
    0,		                                      /* tp_weaklistoffset */
    0,		                                      /* tp_iter */
    0,		                                      /* tp_iternext */
    PyBlitzArray_methods,                       /* tp_methods */
André Anjos's avatar
André Anjos committed
449
    PyBlitzArray_members,                       /* tp_members */
450
451
452
453
454
455
    PyBlitzArray_getseters,                     /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
456
    (initproc)PyBlitzArray_init,                /* tp_init */
457
458
    0,                                          /* tp_alloc */
    PyBlitzArray_New,                           /* tp_new */
459
};