This commit is contained in:
blueloveTH 2025-02-12 20:03:38 +08:00
parent 6a20133587
commit 76a96a0baa
3 changed files with 250 additions and 229 deletions

View File

@ -6,11 +6,17 @@
#include "pocketpy/common/sstream.h" #include "pocketpy/common/sstream.h"
#include "pocketpy/interpreter/vm.h" #include "pocketpy/interpreter/vm.h"
typedef struct c11_array2d { typedef struct c11_array2d_like {
py_TValue* data; // slots
int n_cols; int n_cols;
int n_rows; int n_rows;
int numel; int numel;
py_Ref (*f_get)(struct c11_array2d_like* self, int col, int row);
bool (*f_set)(struct c11_array2d_like* self, int col, int row, py_Ref value);
} c11_array2d_like;
typedef struct c11_array2d {
c11_array2d_like header;
py_TValue* data; // slots
} c11_array2d; } c11_array2d;
typedef struct c11_array2d_iterator { typedef struct c11_array2d_iterator {

View File

@ -1,4 +1,4 @@
from typing import Callable, Any, Generic, TypeVar, Literal, overload, Iterator from typing import Callable, Literal, overload, Iterator
from linalg import vec2i from linalg import vec2i
Neighborhood = Literal['Moore', 'von Neumann'] Neighborhood = Literal['Moore', 'von Neumann']
@ -13,6 +13,8 @@ class array2d_like[T]:
@property @property
def height(self) -> int: ... def height(self) -> int: ...
@property @property
def shape(self) -> vec2i: ...
@property
def numel(self) -> int: ... def numel(self) -> int: ...
@overload @overload
@ -20,53 +22,37 @@ class array2d_like[T]:
@overload @overload
def is_valid(self, pos: vec2i) -> bool: ... def is_valid(self, pos: vec2i) -> bool: ...
@overload
def __getitem__(self, index: vec2i) -> T: ...
@overload
def __getitem__(self, index: tuple[int, int]) -> T: ...
@overload
def __setitem__(self, index: vec2i, value: T): ...
@overload
def __setitem__(self, index: tuple[int, int], value: T): ...
class array2d_view[T](array2d_like[T]):
origin: vec2i
class array2d[T](array2d_like[T]):
def __new__(cls, n_cols: int, n_rows: int, default: T | Callable[[vec2i], T] | None = None): ...
def __eq__(self, other: object) -> array2d[bool]: ... # type: ignore
def __ne__(self, other: object) -> array2d[bool]: ... # type: ignore
def __iter__(self) -> Iterator[tuple[vec2i, T]]: ...
def get[R](self, col: int, row: int, default: R = None) -> T | R: def get[R](self, col: int, row: int, default: R = None) -> T | R:
"""Gets the value at the given position. If the position is out of bounds, return the default value.""" """Get the value at the given position.
@overload If the position is out of bounds, return the default value.
def __getitem__(self, index: tuple[slice, slice]) -> array2d[T]: ... """
@overload
def __getitem__(self, mask: array2d[bool]) -> list[T]: ...
@overload
def __setitem__(self, index: tuple[slice, slice], value: int | float | str | bool | None | 'array2d[T]'): ...
@overload
def __setitem__(self, mask: array2d[bool], value: T): ...
def map[R](self, f: Callable[[T], R]) -> array2d[R]: ...
def copy(self) -> 'array2d[T]': ...
def fill_(self, value: T) -> None: ...
def apply_(self, f: Callable[[T], T]) -> None: ...
def copy_(self, other: array2d[T] | list[T]) -> None: ...
def render(self) -> str: ... def render(self) -> str: ...
def all(self: array2d[bool]) -> bool: ... def all(self: array2d_like[bool]) -> bool: ...
def any(self: array2d[bool]) -> bool: ... def any(self: array2d_like[bool]) -> bool: ...
@staticmethod def map[R](self, f: Callable[[T], R]) -> array2d[R]: ...
def fromlist(data: list[list[T]]) -> array2d[T]: ... def apply(self, f: Callable[[T], T]) -> None: ...
def tolist(self) -> list[list[T]]: ... def copy(self) -> 'array2d[T]': ...
@overload
def __getitem__(self, index: vec2i | tuple[int, int]) -> T: ...
@overload
def __getitem__(self, index: tuple[slice, slice]) -> array2d_view[T]: ...
@overload
def __getitem__(self, index: array2d_like[bool]) -> list[T]: ...
@overload
def __setitem__(self, index: vec2i | tuple[int, int], value: T): ...
@overload
def __setitem__(self, index: tuple[slice, slice], value: T | 'array2d_like[T]'): ...
@overload
def __setitem__(self, index: array2d_like[bool], value: T): ...
def __eq__(self, other: object) -> array2d[bool]: ... # type: ignore
def __ne__(self, other: object) -> array2d[bool]: ... # type: ignore
def __iter__(self) -> Iterator[tuple[vec2i, T]]: ...
# algorithms # algorithms
def count(self, value: T) -> int: def count(self, value: T) -> int:
@ -81,7 +67,7 @@ class array2d[T](array2d_like[T]):
Returns a tuple `(x, y, width, height)` or raise `ValueError` if the value is not found. Returns a tuple `(x, y, width, height)` or raise `ValueError` if the value is not found.
""" """
def convolve(self: array2d[int], kernel: array2d[int], padding: int) -> array2d[int]: def convolve(self: array2d_like[int], kernel: array2d_like[int], padding: int) -> array2d[int]:
"""Convolves the array with the given kernel.""" """Convolves the array with the given kernel."""
def get_connected_components(self, value: T, neighborhood: Neighborhood) -> tuple[array2d[int], int]: def get_connected_components(self, value: T, neighborhood: Neighborhood) -> tuple[array2d[int], int]:
@ -92,6 +78,24 @@ class array2d[T](array2d_like[T]):
""" """
class array2d_view[T](array2d_like[T]):
@property
def origin(self) -> vec2i: ...
class array2d[T](array2d_like[T]):
def __new__(
cls,
n_cols: int,
n_rows: int,
default: T | Callable[[vec2i], T] | None = None
): ...
@staticmethod
def fromlist(data: list[list[T]]) -> array2d[T]: ...
def tolist(self) -> list[list[T]]: ...
class chunked_array2d[T, TContext]: class chunked_array2d[T, TContext]:
def __init__( def __init__(
self, self,

View File

@ -1,86 +1,63 @@
#include "pocketpy/interpreter/array2d.h" #include "pocketpy/interpreter/array2d.h"
static bool py_array2d_is_valid(c11_array2d* self, int col, int row) { static bool c11_array2d_like_is_valid(c11_array2d_like* self, unsigned int col, unsigned int row) {
return col >= 0 && col < self->n_cols && row >= 0 && row < self->n_rows; return col < self->n_cols && row < self->n_rows;
} }
static py_ObjectRef py_array2d__get(c11_array2d* self, int col, int row) { static py_Ref py_array2d__get(c11_array2d* self, int col, int row) {
return self->data + row * self->n_cols + col; return self->data + row * self->header.n_cols + col;
} }
static bool py_array2d__set(c11_array2d* self, int col, int row, py_Ref value) {
static void py_array2d__set(c11_array2d* self, int col, int row, py_Ref value) { self->data[row * self->header.n_cols + col] = *value;
self->data[row * self->n_cols + col] = *value; return true;
} }
c11_array2d* py_newarray2d(py_OutRef out, int n_cols, int n_rows) { c11_array2d* py_newarray2d(py_OutRef out, int n_cols, int n_rows) {
int numel = n_cols * n_rows; int numel = n_cols * n_rows;
c11_array2d* ud = py_newobject(out, tp_array2d, numel, sizeof(c11_array2d)); c11_array2d* ud = py_newobject(out, tp_array2d, numel, sizeof(c11_array2d));
ud->header.n_cols = n_cols;
ud->header.n_rows = n_rows;
ud->header.numel = numel;
ud->header.f_get = (py_Ref (*)(c11_array2d_like*, int, int))py_array2d__get;
ud->header.f_set = (bool (*)(c11_array2d_like*, int, int, py_Ref))py_array2d__set;
ud->data = py_getslot(out, 0); ud->data = py_getslot(out, 0);
ud->n_cols = n_cols;
ud->n_rows = n_rows;
ud->numel = numel;
return ud; return ud;
} }
/* bindings */ /* array2d_like bindings */
static bool array2d__new__(int argc, py_Ref argv) { static bool array2d_like_n_cols(int argc, py_Ref argv) {
// __new__(cls, n_cols: int, n_rows: int, default: Callable[[vec2i], T] = None)
py_Ref default_ = py_arg(3);
PY_CHECK_ARG_TYPE(0, tp_type);
PY_CHECK_ARG_TYPE(1, tp_int);
PY_CHECK_ARG_TYPE(2, tp_int);
int n_cols = argv[1]._i64;
int n_rows = argv[2]._i64;
int numel = n_cols * n_rows;
if(n_cols <= 0 || n_rows <= 0) return ValueError("array2d() expected positive dimensions");
c11_array2d* ud = py_newarray2d(py_pushtmp(), n_cols, n_rows);
// setup initial values
if(py_callable(default_)) {
for(int j = 0; j < n_rows; j++) {
for(int i = 0; i < n_cols; i++) {
py_TValue tmp;
py_newvec2i(&tmp,
(c11_vec2i){
{i, j}
});
bool ok = py_call(default_, 1, &tmp);
if(!ok) return false;
ud->data[j * n_cols + i] = *py_retval();
}
}
} else {
for(int i = 0; i < numel; i++) {
ud->data[i] = *default_;
}
}
py_assign(py_retval(), py_peek(-1));
py_pop();
return true;
}
static bool array2d_n_cols(int argc, py_Ref argv) {
PY_CHECK_ARGC(1); PY_CHECK_ARGC(1);
c11_array2d* self = py_touserdata(argv); c11_array2d_like* self = py_touserdata(argv);
py_newint(py_retval(), self->n_cols); py_newint(py_retval(), self->n_cols);
return true; return true;
} }
static bool array2d_n_rows(int argc, py_Ref argv) { static bool array2d_like_n_rows(int argc, py_Ref argv) {
PY_CHECK_ARGC(1); PY_CHECK_ARGC(1);
c11_array2d* self = py_touserdata(argv); c11_array2d_like* self = py_touserdata(argv);
py_newint(py_retval(), self->n_rows); py_newint(py_retval(), self->n_rows);
return true; return true;
} }
static bool array2d_numel(int argc, py_Ref argv) { static bool array2d_like_shape(int argc, py_Ref argv) {
PY_CHECK_ARGC(1); PY_CHECK_ARGC(1);
c11_array2d* self = py_touserdata(argv); c11_array2d_like* self = py_touserdata(argv);
c11_vec2i shape;
shape.x = self->n_cols;
shape.y = self->n_rows;
py_newvec2i(py_retval(), shape);
return true;
}
static bool array2d_like_numel(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_array2d_like* self = py_touserdata(argv);
py_newint(py_retval(), self->numel); py_newint(py_retval(), self->numel);
return true; return true;
} }
static bool array2d_is_valid(int argc, py_Ref argv) { static bool array2d_like_is_valid(int argc, py_Ref argv) {
c11_array2d* self = py_touserdata(argv); c11_array2d_like* self = py_touserdata(argv);
int col, row; int col, row;
if(argc == 2) { if(argc == 2) {
PY_CHECK_ARG_TYPE(1, tp_vec2i); PY_CHECK_ARG_TYPE(1, tp_vec2i);
@ -95,15 +72,15 @@ static bool array2d_is_valid(int argc, py_Ref argv) {
} else { } else {
return TypeError("is_valid() expected 2 or 3 arguments"); return TypeError("is_valid() expected 2 or 3 arguments");
} }
py_newbool(py_retval(), py_array2d_is_valid(self, col, row)); py_newbool(py_retval(), c11_array2d_like_is_valid(self, col, row));
return true; return true;
} }
static bool array2d_get(int argc, py_Ref argv) { static bool array2d_like_get(int argc, py_Ref argv) {
py_Ref default_;
c11_array2d* self = py_touserdata(argv);
PY_CHECK_ARG_TYPE(1, tp_int); PY_CHECK_ARG_TYPE(1, tp_int);
PY_CHECK_ARG_TYPE(2, tp_int); PY_CHECK_ARG_TYPE(2, tp_int);
py_Ref default_;
c11_array2d_like* self = py_touserdata(argv);
if(argc == 3) { if(argc == 3) {
default_ = py_None(); default_ = py_None();
} else if(argc == 4) { } else if(argc == 4) {
@ -113,14 +90,169 @@ static bool array2d_get(int argc, py_Ref argv) {
} }
int col = py_toint(py_arg(1)); int col = py_toint(py_arg(1));
int row = py_toint(py_arg(2)); int row = py_toint(py_arg(2));
if(py_array2d_is_valid(self, col, row)) { if(c11_array2d_like_is_valid(self, col, row)) {
py_assign(py_retval(), py_array2d__get(self, col, row)); py_assign(py_retval(), self->f_get(self, col, row));
} else { } else {
py_assign(py_retval(), default_); py_assign(py_retval(), default_);
} }
return true; return true;
} }
static bool array2d_like_render(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_sbuf buf;
c11_sbuf__ctor(&buf);
c11_array2d_like* self = py_touserdata(argv);
for(int j = 0; j < self->n_rows; j++) {
for(int i = 0; i < self->n_cols; i++) {
py_Ref item = self->f_get(self, i, j);
if(!py_str(item)) return false;
c11_sbuf__write_sv(&buf, py_tosv(py_retval()));
}
if(j < self->n_rows - 1) c11_sbuf__write_char(&buf, '\n');
}
c11_sbuf__py_submit(&buf, py_retval());
return true;
}
static bool array2d_like_all(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_array2d_like* self = py_touserdata(argv);
for(int j = 0; j < self->n_rows; j++) {
for(int i = 0; i < self->n_cols; i++) {
py_Ref item = self->f_get(self, i, j);
if(!py_checkbool(item)) return false;
if(!py_tobool(item)) {
py_newbool(py_retval(), false);
return true;
}
}
}
py_newbool(py_retval(), true);
return true;
}
static bool array2d_like_any(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_array2d_like* self = py_touserdata(argv);
for(int j = 0; j < self->n_rows; j++) {
for(int i = 0; i < self->n_cols; i++) {
py_Ref item = self->f_get(self, i, j);
if(!py_checkbool(item)) return false;
if(py_tobool(item)) {
py_newbool(py_retval(), true);
return true;
}
}
}
py_newbool(py_retval(), false);
return true;
}
static bool array2d_like_map(int argc, py_Ref argv) {
// def map(self, f: Callable[[T], Any]) -> 'array2d': ...
PY_CHECK_ARGC(2);
c11_array2d_like* self = py_touserdata(argv);
py_Ref f = py_arg(1);
c11_array2d* res = py_newarray2d(py_pushtmp(), self->n_cols, self->n_rows);
for(int j = 0; j < self->n_rows; j++) {
for(int i = 0; i < self->n_cols; i++) {
py_Ref item = self->f_get(self, i, j);
if(!py_call(f, 1, item)) return false;
res->data[j * self->n_cols + i] = *py_retval();
}
}
py_assign(py_retval(), py_peek(-1));
py_pop();
return true;
}
static bool array2d_like_copy(int argc, py_Ref argv) {
// def copy(self) -> 'array2d': ...
PY_CHECK_ARGC(1);
c11_array2d_like* self = py_touserdata(argv);
c11_array2d* res = py_newarray2d(py_retval(), self->n_cols, self->n_rows);
for(int j = 0; j < self->n_rows; j++) {
for(int i = 0; i < self->n_cols; i++) {
py_Ref item = self->f_get(self, i, j);
res->data[j * self->n_cols + i] = *item;
}
}
return true;
}
static bool array2d_like_apply(int argc, py_Ref argv) {
// def apply_(self, f: Callable[[T], T]) -> None: ...
PY_CHECK_ARGC(2);
c11_array2d_like* self = py_touserdata(argv);
py_Ref f = py_arg(1);
for(int j = 0; j < self->n_rows; j++) {
for(int i = 0; i < self->n_cols; i++) {
py_Ref item = self->f_get(self, i, j);
if(!py_call(f, 1, item)) return false;
bool ok = self->f_set(self, i, j, py_retval());
if(!ok) return false;
}
}
py_newnone(py_retval());
return true;
}
static void pk__register_array2d_like(py_Ref mod) {
py_Type type = py_newtype("array2d_like", tp_object, mod, NULL);
py_bindproperty(type, "n_cols", array2d_like_n_cols, NULL);
py_bindproperty(type, "n_rows", array2d_like_n_rows, NULL);
py_bindproperty(type, "width", array2d_like_n_cols, NULL);
py_bindproperty(type, "height", array2d_like_n_rows, NULL);
py_bindproperty(type, "shape", array2d_like_shape, NULL);
py_bindproperty(type, "numel", array2d_like_numel, NULL);
py_bindmethod(type, "is_valid", array2d_like_is_valid);
py_bindmethod(type, "get", array2d_like_get);
py_bindmethod(type, "render", array2d_like_render);
py_bindmethod(type, "all", array2d_like_all);
py_bindmethod(type, "any", array2d_like_any);
py_bindmethod(type, "map", array2d_like_map);
py_bindmethod(type, "apply", array2d_like_apply);
py_bindmethod(type, "copy", array2d_like_copy);
}
static bool array2d__new__(int argc, py_Ref argv) {
// __new__(cls, n_cols: int, n_rows: int, default: Callable[[vec2i], T] = None)
py_Ref default_ = py_arg(3);
PY_CHECK_ARG_TYPE(0, tp_type);
PY_CHECK_ARG_TYPE(1, tp_int);
PY_CHECK_ARG_TYPE(2, tp_int);
int n_cols = argv[1]._i64;
int n_rows = argv[2]._i64;
if(n_cols <= 0 || n_rows <= 0) return ValueError("array2d() expected positive dimensions");
c11_array2d* ud = py_newarray2d(py_pushtmp(), n_cols, n_rows);
// setup initial values
if(py_callable(default_)) {
for(int j = 0; j < n_rows; j++) {
for(int i = 0; i < n_cols; i++) {
py_TValue tmp;
py_newvec2i(&tmp, (c11_vec2i){{i, j}});
if(!py_call(default_, 1, &tmp)) return false;
ud->data[j * n_cols + i] = *py_retval();
}
}
} else {
for(int i = 0; i < ud->header.numel; i++) {
ud->data[i] = *default_;
}
}
py_assign(py_retval(), py_peek(-1));
py_pop();
return true;
}
static bool _array2d_check_all_type(c11_array2d* self, py_Type type) { static bool _array2d_check_all_type(c11_array2d* self, py_Type type) {
for(int i = 0; i < self->numel; i++) { for(int i = 0; i < self->numel; i++) {
py_Type item_type = self->data[i].type; py_Type item_type = self->data[i].type;
@ -144,33 +276,7 @@ static bool _array2d_check_same_shape(c11_array2d* self, c11_array2d* other) {
return _check_same_shape(self->n_cols, self->n_rows, other->n_cols, other->n_rows); return _check_same_shape(self->n_cols, self->n_rows, other->n_cols, other->n_rows);
} }
static bool array2d_all(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_array2d* self = py_touserdata(argv);
if(!_array2d_check_all_type(self, tp_bool)) return false;
for(int i = 0; i < self->numel; i++) {
if(!py_tobool(self->data + i)) {
py_newbool(py_retval(), false);
return true;
}
}
py_newbool(py_retval(), true);
return true;
}
static bool array2d_any(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_array2d* self = py_touserdata(argv);
if(!_array2d_check_all_type(self, tp_bool)) return false;
for(int i = 0; i < self->numel; i++) {
if(py_tobool(self->data + i)) {
py_newbool(py_retval(), true);
return true;
}
}
py_newbool(py_retval(), false);
return true;
}
static bool array2d__eq__(int argc, py_Ref argv) { static bool array2d__eq__(int argc, py_Ref argv) {
PY_CHECK_ARGC(2); PY_CHECK_ARGC(2);
@ -247,80 +353,6 @@ static bool array2d_iterator__next__(int argc, py_Ref argv) {
return StopIteration(); return StopIteration();
} }
static bool array2d_map(int argc, py_Ref argv) {
// def map(self, f: Callable[[T], Any]) -> 'array2d': ...
PY_CHECK_ARGC(2);
c11_array2d* self = py_touserdata(argv);
py_Ref f = py_arg(1);
c11_array2d* res = py_newarray2d(py_pushtmp(), self->n_cols, self->n_rows);
for(int i = 0; i < self->numel; i++) {
bool ok = py_call(f, 1, self->data + i);
if(!ok) return false;
res->data[i] = *py_retval();
}
py_assign(py_retval(), py_peek(-1));
py_pop();
return true;
}
static bool array2d_copy(int argc, py_Ref argv) {
// def copy(self) -> 'array2d': ...
PY_CHECK_ARGC(1);
c11_array2d* self = py_touserdata(argv);
c11_array2d* res = py_newarray2d(py_retval(), self->n_cols, self->n_rows);
memcpy(res->data, self->data, self->numel * sizeof(py_TValue));
return true;
}
static bool array2d_fill_(int argc, py_Ref argv) {
// def fill_(self, value: T) -> None: ...
PY_CHECK_ARGC(2);
c11_array2d* self = py_touserdata(argv);
for(int i = 0; i < self->numel; i++)
self->data[i] = argv[1];
py_newnone(py_retval());
return true;
}
static bool array2d_apply_(int argc, py_Ref argv) {
// def apply_(self, f: Callable[[T], T]) -> None: ...
PY_CHECK_ARGC(2);
c11_array2d* self = py_touserdata(argv);
py_Ref f = py_arg(1);
for(int i = 0; i < self->numel; i++) {
bool ok = py_call(f, 1, self->data + i);
if(!ok) return false;
self->data[i] = *py_retval();
}
py_newnone(py_retval());
return true;
}
static bool array2d_copy_(int argc, py_Ref argv) {
// def copy_(self, src: 'array2d') -> None: ...
PY_CHECK_ARGC(2);
c11_array2d* self = py_touserdata(argv);
py_Type src_type = py_typeof(py_arg(1));
if(src_type == tp_array2d) {
c11_array2d* src = py_touserdata(py_arg(1));
if(!_array2d_check_same_shape(self, src)) return false;
memcpy(self->data, src->data, self->numel * sizeof(py_TValue));
} else {
py_TValue* data;
int length = pk_arrayview(py_arg(1), &data);
if(length != -1) {
if(self->numel != length) {
return ValueError("copy_() expected the same numel: %d != %d", self->numel, length);
}
memcpy(self->data, data, self->numel * sizeof(py_TValue));
} else {
return TypeError("copy_() expected `array2d`, `list` or `tuple`, got '%t", src_type);
}
}
py_newnone(py_retval());
return true;
}
// fromlist(data: list[list[T]]) -> array2d[T] // fromlist(data: list[list[T]]) -> array2d[T]
static bool array2d_fromlist_STATIC(int argc, py_Ref argv) { static bool array2d_fromlist_STATIC(int argc, py_Ref argv) {
@ -365,22 +397,7 @@ static bool array2d_tolist(int argc, py_Ref argv) {
return true; return true;
} }
static bool array2d_render(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
c11_sbuf buf;
c11_sbuf__ctor(&buf);
c11_array2d* self = py_touserdata(argv);
for(int j = 0; j < self->n_rows; j++) {
for(int i = 0; i < self->n_cols; i++) {
py_Ref item = py_array2d__get(self, i, j);
if(!py_str(item)) return false;
c11_sbuf__write_sv(&buf, py_tosv(py_retval()));
}
if(j < self->n_rows - 1) c11_sbuf__write_char(&buf, '\n');
}
c11_sbuf__py_submit(&buf, py_retval());
return true;
}
// count(self, value: T) -> int // count(self, value: T) -> int
static bool array2d_count(int argc, py_Ref argv) { static bool array2d_count(int argc, py_Ref argv) {
@ -696,12 +713,6 @@ void pk__add_module_array2d() {
py_bindmagic(array2d, __getitem__, array2d__getitem__); py_bindmagic(array2d, __getitem__, array2d__getitem__);
py_bindmagic(array2d, __setitem__, array2d__setitem__); py_bindmagic(array2d, __setitem__, array2d__setitem__);
py_bindproperty(array2d, "n_cols", array2d_n_cols, NULL);
py_bindproperty(array2d, "n_rows", array2d_n_rows, NULL);
py_bindproperty(array2d, "width", array2d_n_cols, NULL);
py_bindproperty(array2d, "height", array2d_n_rows, NULL);
py_bindproperty(array2d, "numel", array2d_numel, NULL);
py_bindmethod(array2d, "is_valid", array2d_is_valid); py_bindmethod(array2d, "is_valid", array2d_is_valid);
py_bindmethod(array2d, "get", array2d_get); py_bindmethod(array2d, "get", array2d_get);