ulab can now be compiled with complex support
This commit is contained in:
parent
1013daa902
commit
911fdb4587
19 changed files with 204 additions and 22 deletions
108
code/ndarray.c
108
code/ndarray.c
|
|
@ -171,7 +171,11 @@ void ndarray_rewind_array(uint8_t ndim, uint8_t *array, size_t *shape, int32_t *
|
|||
static int32_t *strides_from_shape(size_t *shape, uint8_t dtype) {
|
||||
// returns a strides array that corresponds to a dense array with the prescribed shape
|
||||
int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS);
|
||||
strides[ULAB_MAX_DIMS-1] = (int32_t)mp_binary_get_size('@', dtype, NULL);
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
strides[ULAB_MAX_DIMS-1] = (int32_t)mp_binary_get_complex_size(dtype);
|
||||
#else
|
||||
strides[ULAB_MAX_DIMS-1] = (int32_t)mp_binary_get_size('@', dtype, NULL);
|
||||
#endif
|
||||
for(uint8_t i=ULAB_MAX_DIMS; i > 1; i--) {
|
||||
strides[i-2] = strides[i-1] * shape[i-1];
|
||||
}
|
||||
|
|
@ -231,7 +235,13 @@ void ndarray_dtype_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kin
|
|||
mp_print_str(print, "uint16')");
|
||||
} else if(self->dtype == NDARRAY_INT16) {
|
||||
mp_print_str(print, "int16')");
|
||||
} else {
|
||||
}
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
else if(self->dtype == NDARRAY_COMPLEX) {
|
||||
mp_print_str(print, "complex')");
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
|
||||
mp_print_str(print, "float32')");
|
||||
#else
|
||||
|
|
@ -280,7 +290,13 @@ mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *type, size_t n_args, size_t
|
|||
_dtype = NDARRAY_INT16;
|
||||
} else if(memcmp(_dtype_, "float", 5) == 0) {
|
||||
_dtype = NDARRAY_FLOAT;
|
||||
} else {
|
||||
}
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
else if(memcmp(_dtype_, "complex", 7) == 0) {
|
||||
_dtype = NDARRAY_COMPLEX;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
mp_raise_TypeError(translate("data type not understood"));
|
||||
}
|
||||
}
|
||||
|
|
@ -308,7 +324,11 @@ mp_obj_t ndarray_dtype(mp_obj_t self_in) {
|
|||
GET_STR_DATA_LEN(self_in, _dtype, len);
|
||||
if((len != 1) || ((*_dtype != NDARRAY_BOOL) && (*_dtype != NDARRAY_UINT8)
|
||||
&& (*_dtype != NDARRAY_INT8) && (*_dtype != NDARRAY_UINT16)
|
||||
&& (*_dtype != NDARRAY_INT16) && (*_dtype != NDARRAY_FLOAT))) {
|
||||
&& (*_dtype != NDARRAY_INT16) && (*_dtype != NDARRAY_FLOAT)
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
&& (*_dtype != NDARRAY_COMPLEX)
|
||||
#endif
|
||||
)) {
|
||||
mp_raise_TypeError(translate("data type not understood"));
|
||||
}
|
||||
dtype = *_dtype;
|
||||
|
|
@ -361,32 +381,55 @@ mp_obj_t ndarray_get_item(ndarray_obj_t *ndarray, void *array) {
|
|||
}
|
||||
}
|
||||
|
||||
static void ndarray_print_element(const mp_print_t *print, ndarray_obj_t *ndarray, uint8_t *array) {
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
if(ndarray->dtype == NDARRAY_COMPLEX) {
|
||||
// real part first
|
||||
mp_float_t fvalue = *(mp_float_t *)array;
|
||||
mp_obj_print_helper(print, mp_obj_new_float(fvalue), PRINT_REPR);
|
||||
// imaginary part
|
||||
array += ndarray->itemsize / 2;
|
||||
fvalue = *(mp_float_t *)array;
|
||||
if(fvalue >= MICROPY_FLOAT_CONST(0.0) || isnan(fvalue)) {
|
||||
mp_print_str(print, "+");
|
||||
}
|
||||
array += ndarray->itemsize / 2;
|
||||
mp_obj_print_helper(print, mp_obj_new_float(fvalue), PRINT_REPR);
|
||||
mp_print_str(print, "j");
|
||||
} else {
|
||||
mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR);
|
||||
}
|
||||
#else
|
||||
mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ndarray_print_row(const mp_print_t *print, ndarray_obj_t * ndarray, uint8_t *array, size_t stride, size_t n) {
|
||||
if(n == 0) {
|
||||
return;
|
||||
}
|
||||
mp_print_str(print, "[");
|
||||
if((n <= ndarray_print_threshold) || (n <= 2*ndarray_print_edgeitems)) { // if the array is short, print everything
|
||||
mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR);
|
||||
ndarray_print_element(print, ndarray, array);
|
||||
array += stride;
|
||||
for(size_t i=1; i < n; i++, array += stride) {
|
||||
mp_print_str(print, ", ");
|
||||
mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR);
|
||||
ndarray_print_element(print, ndarray, array);
|
||||
}
|
||||
} else {
|
||||
mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR);
|
||||
array += stride;
|
||||
for(size_t i=1; i < ndarray_print_edgeitems; i++, array += stride) {
|
||||
mp_print_str(print, ", ");
|
||||
mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR);
|
||||
ndarray_print_element(print, ndarray, array);
|
||||
}
|
||||
mp_printf(print, ", ..., ");
|
||||
array += stride * (n - 2 * ndarray_print_edgeitems);
|
||||
mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR);
|
||||
array += stride * (n - 2 * ndarray_print_edgeitems);
|
||||
ndarray_print_element(print, ndarray, array);
|
||||
array += stride;
|
||||
for(size_t i=1; i < ndarray_print_edgeitems; i++, array += stride) {
|
||||
mp_print_str(print, ", ");
|
||||
mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR);
|
||||
ndarray_print_element(print, ndarray, array);
|
||||
}
|
||||
}
|
||||
mp_print_str(print, "]");
|
||||
|
|
@ -494,7 +537,19 @@ void ndarray_assign_elements(ndarray_obj_t *ndarray, mp_obj_t iterable, uint8_t
|
|||
}
|
||||
} else {
|
||||
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
||||
ndarray_set_value(dtype, ndarray->array, (*idx)++, item);
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
mp_float_t real;
|
||||
mp_float_t imag;
|
||||
if(dtype == NDARRAY_COMPLEX) {
|
||||
mp_obj_get_complex(item, &real, &imag);
|
||||
ndarray_set_value(NDARRAY_FLOAT, ndarray->array, (*idx)++, mp_obj_new_float(real));
|
||||
ndarray_set_value(NDARRAY_FLOAT, ndarray->array, (*idx)++, mp_obj_new_float(imag));
|
||||
} else {
|
||||
ndarray_set_value(dtype, ndarray->array, (*idx)++, item);
|
||||
}
|
||||
#else
|
||||
ndarray_set_value(dtype, ndarray->array, (*idx)++, item);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -518,7 +573,11 @@ ndarray_obj_t *ndarray_new_ndarray(uint8_t ndim, size_t *shape, int32_t *strides
|
|||
ndarray->boolean = dtype == NDARRAY_BOOL ? NDARRAY_BOOLEAN : NDARRAY_NUMERIC;
|
||||
ndarray->ndim = ndim;
|
||||
ndarray->len = ndim == 0 ? 0 : 1;
|
||||
ndarray->itemsize = mp_binary_get_size('@', ndarray->dtype, NULL);
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
ndarray->itemsize = mp_binary_get_complex_size(dtype);
|
||||
#else
|
||||
ndarray->itemsize = mp_binary_get_size('@', ndarray->dtype, NULL);
|
||||
#endif
|
||||
int32_t *_strides;
|
||||
if(strides == NULL) {
|
||||
_strides = strides_from_shape(shape, ndarray->dtype);
|
||||
|
|
@ -546,7 +605,11 @@ ndarray_obj_t *ndarray_new_dense_ndarray(uint8_t ndim, size_t *shape, uint8_t dt
|
|||
// creates a dense array, i.e., one, where the strides are derived directly from the shapes
|
||||
// the function should work in the general n-dimensional case
|
||||
int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS);
|
||||
strides[ULAB_MAX_DIMS-1] = dtype == NDARRAY_BOOL ? 1 : mp_binary_get_size('@', dtype, NULL);
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
strides[ULAB_MAX_DIMS-1] = dtype == NDARRAY_BOOL ? 1 : (int32_t)mp_binary_get_complex_size(dtype);
|
||||
#else
|
||||
strides[ULAB_MAX_DIMS-1] = dtype == NDARRAY_BOOL ? 1 : (int32_t)mp_binary_get_size('@', dtype, NULL);
|
||||
#endif
|
||||
for(size_t i=ULAB_MAX_DIMS; i > 1; i--) {
|
||||
strides[i-2] = strides[i-1] * MAX(1, shape[i-1]);
|
||||
}
|
||||
|
|
@ -567,13 +630,18 @@ ndarray_obj_t *ndarray_new_ndarray_from_tuple(mp_obj_tuple_t *_shape, uint8_t dt
|
|||
return ndarray_new_dense_ndarray(_shape->len, shape, dtype);
|
||||
}
|
||||
|
||||
void ndarray_copy_array(ndarray_obj_t *source, ndarray_obj_t *target) {
|
||||
void ndarray_copy_array(ndarray_obj_t *source, ndarray_obj_t *target, uint8_t shift) {
|
||||
// TODO: if the array is dense, the content could be copied in a single pass
|
||||
// copies the content of source->array into a new dense void pointer
|
||||
// it is assumed that the dtypes in source and target are the same
|
||||
// Since the target is a new array, it is supposed to be dense
|
||||
uint8_t *sarray = (uint8_t *)source->array;
|
||||
uint8_t *tarray = (uint8_t *)target->array;
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
if(source->dtype == NDARRAY_COMPLEX) {
|
||||
sarray += shift;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ULAB_MAX_DIMS > 3
|
||||
size_t i = 0;
|
||||
|
|
@ -589,7 +657,7 @@ void ndarray_copy_array(ndarray_obj_t *source, ndarray_obj_t *target) {
|
|||
#endif
|
||||
size_t l = 0;
|
||||
do {
|
||||
memcpy(tarray, sarray, source->itemsize);
|
||||
memcpy(tarray, sarray, target->itemsize);
|
||||
tarray += target->itemsize;
|
||||
sarray += source->strides[ULAB_MAX_DIMS - 1];
|
||||
l++;
|
||||
|
|
@ -648,7 +716,7 @@ ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *source) {
|
|||
dtype = NDARRAY_BOOLEAN;
|
||||
}
|
||||
ndarray_obj_t *ndarray = ndarray_new_ndarray(source->ndim, source->shape, strides, dtype);
|
||||
ndarray_copy_array(source, ndarray);
|
||||
ndarray_copy_array(source, ndarray, 0);
|
||||
return ndarray;
|
||||
}
|
||||
|
||||
|
|
@ -1805,7 +1873,11 @@ mp_obj_t ndarray_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
|
|||
if(ndarray->boolean) {
|
||||
for(size_t i=0; i < ndarray->len; i++, array++) *array = *array ^ 0x01;
|
||||
} else {
|
||||
uint8_t itemsize = mp_binary_get_size('@', self->dtype, NULL);
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
uint8_t itemsize = mp_binary_get_complex_size(self->dtype);
|
||||
#else
|
||||
uint8_t itemsize = mp_binary_get_size('@', self->dtype, NULL);
|
||||
#endif
|
||||
for(size_t i=0; i < ndarray->len*itemsize; i++, array++) *array ^= 0xFF;
|
||||
}
|
||||
return MP_OBJ_FROM_PTR(ndarray);
|
||||
|
|
@ -1914,7 +1986,7 @@ mp_obj_t ndarray_reshape_core(mp_obj_t oin, mp_obj_t _shape, bool inplace) {
|
|||
mp_raise_ValueError(translate("cannot assign new shape"));
|
||||
}
|
||||
ndarray = ndarray_new_ndarray_from_tuple(shape, source->dtype);
|
||||
ndarray_copy_array(source, ndarray);
|
||||
ndarray_copy_array(source, ndarray, 0);
|
||||
}
|
||||
return MP_OBJ_FROM_PTR(ndarray);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ enum NDARRAY_TYPE {
|
|||
NDARRAY_INT8 = 'b',
|
||||
NDARRAY_UINT16 = 'H',
|
||||
NDARRAY_INT16 = 'h',
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
NDARRAY_COMPLEX = 'c',
|
||||
#endif
|
||||
NDARRAY_FLOAT = FLOAT_TYPECODE,
|
||||
};
|
||||
|
||||
|
|
@ -138,7 +141,7 @@ ndarray_obj_t *ndarray_new_linear_array(size_t , uint8_t );
|
|||
ndarray_obj_t *ndarray_new_view(ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t );
|
||||
bool ndarray_is_dense(ndarray_obj_t *);
|
||||
ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *);
|
||||
void ndarray_copy_array(ndarray_obj_t *, ndarray_obj_t *);
|
||||
void ndarray_copy_array(ndarray_obj_t *, ndarray_obj_t *, uint8_t );
|
||||
|
||||
MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_array_constructor_obj);
|
||||
mp_obj_t ndarray_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "../ulab.h"
|
||||
#include "../ulab_tools.h"
|
||||
#include "carray/carray_tools.h"
|
||||
#include "approx.h"
|
||||
|
||||
//| """Numerical approximation methods"""
|
||||
|
|
@ -60,6 +61,9 @@ STATIC mp_obj_t approx_interp(size_t n_args, const mp_obj_t *pos_args, mp_map_t
|
|||
ndarray_obj_t *x = ndarray_from_mp_obj(args[0].u_obj, 0);
|
||||
ndarray_obj_t *xp = ndarray_from_mp_obj(args[1].u_obj, 0); // xp must hold an increasing sequence of independent values
|
||||
ndarray_obj_t *fp = ndarray_from_mp_obj(args[2].u_obj, 0);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype)
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(xp->dtype)
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(fp->dtype)
|
||||
if((xp->ndim != 1) || (fp->ndim != 1) || (xp->len < 2) || (fp->len < 2) || (xp->len != fp->len)) {
|
||||
mp_raise_ValueError(translate("interp is defined for 1D iterables of equal length"));
|
||||
}
|
||||
|
|
@ -157,6 +161,7 @@ STATIC mp_obj_t approx_trapz(size_t n_args, const mp_obj_t *pos_args, mp_map_t *
|
|||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
ndarray_obj_t *y = ndarray_from_mp_obj(args[0].u_obj, 0);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(y->dtype)
|
||||
ndarray_obj_t *x;
|
||||
mp_float_t mean = MICROPY_FLOAT_CONST(0.0);
|
||||
if(y->len < 2) {
|
||||
|
|
@ -174,6 +179,7 @@ STATIC mp_obj_t approx_trapz(size_t n_args, const mp_obj_t *pos_args, mp_map_t *
|
|||
|
||||
if(args[1].u_obj != mp_const_none) {
|
||||
x = ndarray_from_mp_obj(args[1].u_obj, 0); // x must hold an increasing sequence of independent values
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype)
|
||||
if((x->ndim != 1) || (y->len != x->len)) {
|
||||
mp_raise_ValueError(translate("trapz is defined for 1D arrays of equal length"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,17 @@
|
|||
#include "../ulab.h"
|
||||
#include "../ndarray_operators.h"
|
||||
#include "../ulab_tools.h"
|
||||
#include "carray/carray_tools.h"
|
||||
#include "compare.h"
|
||||
|
||||
static mp_obj_t compare_function(mp_obj_t x1, mp_obj_t x2, uint8_t op) {
|
||||
ndarray_obj_t *lhs = ndarray_from_mp_obj(x1, 0);
|
||||
ndarray_obj_t *rhs = ndarray_from_mp_obj(x2, 0);
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) {
|
||||
NOT_IMPLEMENTED_FOR_COMPLEX()
|
||||
}
|
||||
#endif
|
||||
uint8_t ndim = 0;
|
||||
size_t *shape = m_new(size_t, ULAB_MAX_DIMS);
|
||||
int32_t *lstrides = m_new(int32_t, ULAB_MAX_DIMS);
|
||||
|
|
@ -197,6 +203,7 @@ static mp_obj_t compare_isinf_isfinite(mp_obj_t _x, uint8_t mask) {
|
|||
}
|
||||
} else if(mp_obj_is_type(_x, &ulab_ndarray_type)) {
|
||||
ndarray_obj_t *x = MP_OBJ_TO_PTR(_x);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype)
|
||||
ndarray_obj_t *results = ndarray_new_dense_ndarray(x->ndim, x->shape, NDARRAY_BOOL);
|
||||
// At this point, results is all False
|
||||
uint8_t *rarray = (uint8_t *)results->array;
|
||||
|
|
@ -313,6 +320,10 @@ mp_obj_t compare_where(mp_obj_t _condition, mp_obj_t _x, mp_obj_t _y) {
|
|||
ndarray_obj_t *x = ndarray_from_mp_obj(_x, 0);
|
||||
ndarray_obj_t *y = ndarray_from_mp_obj(_y, 0);
|
||||
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(c->dtype)
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype)
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(y->dtype)
|
||||
|
||||
int32_t *cstrides = m_new(int32_t, ULAB_MAX_DIMS);
|
||||
int32_t *xstrides = m_new(int32_t, ULAB_MAX_DIMS);
|
||||
int32_t *ystrides = m_new(int32_t, ULAB_MAX_DIMS);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include "py/obj.h"
|
||||
#include "py/objarray.h"
|
||||
|
||||
#include "../carray/carray_tools.h"
|
||||
#include "fft.h"
|
||||
|
||||
//| """Frequency-domain functions"""
|
||||
|
|
@ -60,6 +61,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft);
|
|||
//|
|
||||
|
||||
static mp_obj_t fft_ifft(size_t n_args, const mp_obj_t *args) {
|
||||
NOT_IMPLEMENTED_FOR_COMPLEX()
|
||||
if(n_args == 2) {
|
||||
return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_IFFT);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "../../ndarray.h"
|
||||
#include "../../ulab_tools.h"
|
||||
#include "../carray/carray_tools.h"
|
||||
#include "fft_tools.h"
|
||||
|
||||
#ifndef MP_PI
|
||||
|
|
@ -95,6 +96,7 @@ mp_obj_t fft_fft_ifft_spectrogram(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_i
|
|||
ndarray_obj_t *re = MP_OBJ_TO_PTR(arg_re);
|
||||
#if ULAB_MAX_DIMS > 1
|
||||
if(re->ndim != 1) {
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(re->dtype)
|
||||
mp_raise_TypeError(translate("FFT is implemented for linear arrays only"));
|
||||
}
|
||||
#endif
|
||||
|
|
@ -122,6 +124,7 @@ mp_obj_t fft_fft_ifft_spectrogram(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_i
|
|||
ndarray_obj_t *im = MP_OBJ_TO_PTR(arg_im);
|
||||
#if ULAB_MAX_DIMS > 1
|
||||
if(im->ndim != 1) {
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(im->dtype)
|
||||
mp_raise_TypeError(translate("FFT is implemented for linear arrays only"));
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "../ulab.h"
|
||||
#include "../scipy/signal/signal.h"
|
||||
#include "carray/carray_tools.h"
|
||||
#include "filter.h"
|
||||
|
||||
#if ULAB_NUMPY_HAS_CONVOLVE
|
||||
|
|
@ -40,6 +41,8 @@ mp_obj_t filter_convolve(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_a
|
|||
|
||||
ndarray_obj_t *a = MP_OBJ_TO_PTR(args[0].u_obj);
|
||||
ndarray_obj_t *c = MP_OBJ_TO_PTR(args[1].u_obj);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(a->dtype)
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(c->dtype)
|
||||
// deal with linear arrays only
|
||||
#if ULAB_MAX_DIMS > 1
|
||||
if((a->ndim != 1) || (c->ndim != 1)) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "../../ulab.h"
|
||||
#include "../../ulab_tools.h"
|
||||
#include "../carray/carray_tools.h"
|
||||
#include "linalg.h"
|
||||
|
||||
#if ULAB_NUMPY_HAS_LINALG_MODULE
|
||||
|
|
@ -44,6 +45,7 @@
|
|||
|
||||
static mp_obj_t linalg_cholesky(mp_obj_t oin) {
|
||||
ndarray_obj_t *ndarray = tools_object_is_square(oin);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
ndarray_obj_t *L = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, ndarray->shape[ULAB_MAX_DIMS - 1], ndarray->shape[ULAB_MAX_DIMS - 1]), NDARRAY_FLOAT);
|
||||
mp_float_t *Larray = (mp_float_t *)L->array;
|
||||
|
||||
|
|
@ -110,6 +112,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(linalg_cholesky_obj, linalg_cholesky);
|
|||
|
||||
static mp_obj_t linalg_det(mp_obj_t oin) {
|
||||
ndarray_obj_t *ndarray = tools_object_is_square(oin);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
uint8_t *array = (uint8_t *)ndarray->array;
|
||||
size_t N = ndarray->shape[ULAB_MAX_DIMS - 1];
|
||||
mp_float_t *tmp = m_new(mp_float_t, N * N);
|
||||
|
|
@ -182,6 +185,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(linalg_det_obj, linalg_det);
|
|||
|
||||
static mp_obj_t linalg_eig(mp_obj_t oin) {
|
||||
ndarray_obj_t *in = tools_object_is_square(oin);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(in->dtype)
|
||||
uint8_t *iarray = (uint8_t *)in->array;
|
||||
size_t S = in->shape[ULAB_MAX_DIMS - 1];
|
||||
mp_float_t *array = m_new(mp_float_t, S*S);
|
||||
|
|
@ -243,6 +247,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(linalg_eig_obj, linalg_eig);
|
|||
//|
|
||||
static mp_obj_t linalg_inv(mp_obj_t o_in) {
|
||||
ndarray_obj_t *ndarray = tools_object_is_square(o_in);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
uint8_t *array = (uint8_t *)ndarray->array;
|
||||
size_t N = ndarray->shape[ULAB_MAX_DIMS - 1];
|
||||
ndarray_obj_t *inverted = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, N, N), NDARRAY_FLOAT);
|
||||
|
|
@ -305,6 +310,7 @@ static mp_obj_t linalg_norm(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k
|
|||
return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(dot * (count - 1)));
|
||||
} else if(mp_obj_is_type(x, &ulab_ndarray_type)) {
|
||||
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(x);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
uint8_t *array = (uint8_t *)ndarray->array;
|
||||
// always get a float, so that we don't have to resolve the dtype later
|
||||
mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "../ulab.h"
|
||||
#include "../ulab_tools.h"
|
||||
#include "./carray/carray_tools.h"
|
||||
#include "numerical.h"
|
||||
|
||||
enum NUMERICAL_FUNCTION_TYPE {
|
||||
|
|
@ -97,6 +98,7 @@ static mp_obj_t numerical_all_any(mp_obj_t oin, mp_obj_t axis, uint8_t optype) {
|
|||
bool anytype = optype == NUMERICAL_ALL ? 1 : 0;
|
||||
if(mp_obj_is_type(oin, &ulab_ndarray_type)) {
|
||||
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
uint8_t *array = (uint8_t *)ndarray->array;
|
||||
if(ndarray->len == 0) { // return immediately with empty arrays
|
||||
if(optype == NUMERICAL_ALL) {
|
||||
|
|
@ -237,6 +239,7 @@ static mp_obj_t numerical_sum_mean_std_iterable(mp_obj_t oin, uint8_t optype, si
|
|||
}
|
||||
|
||||
static mp_obj_t numerical_sum_mean_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype, size_t ddof) {
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
uint8_t *array = (uint8_t *)ndarray->array;
|
||||
shape_strides _shape_strides = tools_reduce_axes(ndarray, axis);
|
||||
|
||||
|
|
@ -558,9 +561,11 @@ static mp_obj_t numerical_function(size_t n_args, const mp_obj_t *pos_args, mp_m
|
|||
case NUMERICAL_MAX:
|
||||
case NUMERICAL_ARGMIN:
|
||||
case NUMERICAL_ARGMAX:
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
return numerical_argmin_argmax_ndarray(ndarray, axis, optype);
|
||||
case NUMERICAL_SUM:
|
||||
case NUMERICAL_MEAN:
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
return numerical_sum_mean_std_ndarray(ndarray, axis, optype, 0);
|
||||
default:
|
||||
mp_raise_NotImplementedError(translate("operation is not implemented on ndarrays"));
|
||||
|
|
@ -583,6 +588,7 @@ static mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inpla
|
|||
} else {
|
||||
ndarray = ndarray_copy_view(MP_OBJ_TO_PTR(oin));
|
||||
}
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
|
||||
int8_t ax = 0;
|
||||
if(axis == mp_const_none) {
|
||||
|
|
@ -685,6 +691,7 @@ mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw
|
|||
}
|
||||
|
||||
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
if(args[1].u_obj == mp_const_none) {
|
||||
// bail out, though dense arrays could still be sorted
|
||||
mp_raise_NotImplementedError(translate("argsort is not implemented for flattened arrays"));
|
||||
|
|
@ -788,6 +795,8 @@ static mp_obj_t numerical_cross(mp_obj_t _a, mp_obj_t _b) {
|
|||
}
|
||||
ndarray_obj_t *a = MP_OBJ_TO_PTR(_a);
|
||||
ndarray_obj_t *b = MP_OBJ_TO_PTR(_b);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(a->dtype)
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(b->dtype)
|
||||
if((a->ndim != 1) || (b->ndim != 1) || (a->len != b->len) || (a->len != 3)) {
|
||||
mp_raise_ValueError(translate("cross is defined for 1D arrays of length 3"));
|
||||
}
|
||||
|
|
@ -876,6 +885,7 @@ mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
|
|||
}
|
||||
|
||||
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
int8_t ax = args[2].u_int;
|
||||
if(ax < 0) ax += ndarray->ndim;
|
||||
|
||||
|
|
@ -959,7 +969,7 @@ mp_obj_t numerical_flip(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
|
|||
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj);
|
||||
if(args[1].u_obj == mp_const_none) { // flip the flattened array
|
||||
results = ndarray_new_linear_array(ndarray->len, ndarray->dtype);
|
||||
ndarray_copy_array(ndarray, results);
|
||||
ndarray_copy_array(ndarray, results, 0);
|
||||
uint8_t *rarray = (uint8_t *)results->array;
|
||||
rarray += (results->len - 1) * results->itemsize;
|
||||
results->array = rarray;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include "numpy.h"
|
||||
#include "../ulab_create.h"
|
||||
#include "approx.h"
|
||||
#include "carray/carray.h"
|
||||
#include "compare.h"
|
||||
#include "fft/fft.h"
|
||||
#include "filter.h"
|
||||
|
|
@ -125,6 +126,9 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = {
|
|||
{ MP_ROM_QSTR(MP_QSTR_uint16), MP_ROM_INT(NDARRAY_UINT16) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_int16), MP_ROM_INT(NDARRAY_INT16) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_float), MP_ROM_INT(NDARRAY_FLOAT) },
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
{ MP_ROM_QSTR(MP_QSTR_complex), MP_ROM_INT(NDARRAY_COMPLEX) },
|
||||
#endif
|
||||
// modules of numpy
|
||||
#if ULAB_NUMPY_HAS_FFT_MODULE
|
||||
{ MP_ROM_QSTR(MP_QSTR_fft), MP_ROM_PTR(&ulab_fft_module) },
|
||||
|
|
@ -350,7 +354,10 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = {
|
|||
#if ULAB_NUMPY_HAS_VECTORIZE
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_vectorize), (mp_obj_t)&vectorise_vectorize_obj },
|
||||
#endif
|
||||
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_real), (mp_obj_t)&carray_real_obj },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_imag), (mp_obj_t)&carray_imag_obj },
|
||||
#endif
|
||||
};
|
||||
|
||||
static MP_DEFINE_CONST_DICT(mp_module_ulab_numpy_globals, ulab_numpy_globals_table);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include "../ulab.h"
|
||||
#include "linalg/linalg_tools.h"
|
||||
#include "../ulab_tools.h"
|
||||
#include "carray/carray_tools.h"
|
||||
#include "poly.h"
|
||||
|
||||
#if ULAB_NUMPY_HAS_POLYFIT
|
||||
|
|
@ -27,6 +28,12 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) {
|
|||
if(!ndarray_object_is_array_like(args[0])) {
|
||||
mp_raise_ValueError(translate("input data must be an iterable"));
|
||||
}
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
if(MP_OBJ_IS_TYPE(args[0], &ulab_ndarray_type)) {
|
||||
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0]);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
}
|
||||
#endif
|
||||
size_t lenx = 0, leny = 0;
|
||||
uint8_t deg = 0;
|
||||
mp_float_t *x, *XT, *y, *prod;
|
||||
|
|
@ -142,6 +149,17 @@ mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) {
|
|||
if(!ndarray_object_is_array_like(o_p) || !ndarray_object_is_array_like(o_x)) {
|
||||
mp_raise_TypeError(translate("inputs are not iterable"));
|
||||
}
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
ndarray_obj_t *input;
|
||||
if(MP_OBJ_IS_TYPE(o_p, &ulab_ndarray_type)) {
|
||||
input = MP_OBJ_TO_PTR(o_p);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(input->dtype)
|
||||
}
|
||||
if(MP_OBJ_IS_TYPE(o_x, &ulab_ndarray_type)) {
|
||||
input = MP_OBJ_TO_PTR(o_x);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(input->dtype)
|
||||
}
|
||||
#endif
|
||||
// p had better be a one-dimensional standard iterable
|
||||
uint8_t plen = mp_obj_get_int(mp_obj_len_maybe(o_p));
|
||||
mp_float_t *p = m_new(mp_float_t, plen);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "../ulab.h"
|
||||
#include "../ulab_tools.h"
|
||||
#include "carray/carray_tools.h"
|
||||
#include "stats.h"
|
||||
|
||||
#if ULAB_MAX_DIMS > 1
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
|
||||
static mp_obj_t stats_trace(mp_obj_t oin) {
|
||||
ndarray_obj_t *ndarray = tools_object_is_square(oin);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
mp_float_t trace = 0.0;
|
||||
for(size_t i=0; i < ndarray->shape[ULAB_MAX_DIMS - 1]; i++) {
|
||||
int32_t pos = i * (ndarray->strides[ULAB_MAX_DIMS - 1] + ndarray->strides[ULAB_MAX_DIMS - 2]);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "../ulab.h"
|
||||
#include "../ulab_tools.h"
|
||||
#include "carray/carray_tools.h"
|
||||
#include "transform.h"
|
||||
|
||||
#if ULAB_MAX_DIMS > 1
|
||||
|
|
@ -39,6 +40,9 @@ mp_obj_t transform_dot(mp_obj_t _m1, mp_obj_t _m2) {
|
|||
}
|
||||
ndarray_obj_t *m1 = MP_OBJ_TO_PTR(_m1);
|
||||
ndarray_obj_t *m2 = MP_OBJ_TO_PTR(_m2);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(m1->dtype)
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(m2->dtype)
|
||||
|
||||
uint8_t *array1 = (uint8_t *)m1->array;
|
||||
uint8_t *array2 = (uint8_t *)m2->array;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "../ulab.h"
|
||||
#include "../ulab_tools.h"
|
||||
#include "carray/carray_tools.h"
|
||||
#include "vector.h"
|
||||
|
||||
//| """Element-by-element functions
|
||||
|
|
@ -39,6 +40,7 @@ static mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float
|
|||
ndarray_obj_t *ndarray = NULL;
|
||||
if(mp_obj_is_type(o_in, &ulab_ndarray_type)) {
|
||||
ndarray_obj_t *source = MP_OBJ_TO_PTR(o_in);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype)
|
||||
uint8_t *sarray = (uint8_t *)source->array;
|
||||
ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT);
|
||||
mp_float_t *array = (mp_float_t *)ndarray->array;
|
||||
|
|
@ -169,6 +171,7 @@ mp_obj_t vectorise_around(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_
|
|||
int8_t n = args[1].u_int;
|
||||
mp_float_t mul = MICROPY_FLOAT_C_FUN(pow)(10.0, n);
|
||||
ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0].u_obj);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype)
|
||||
ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT);
|
||||
mp_float_t *narray = (mp_float_t *)ndarray->array;
|
||||
uint8_t *sarray = (uint8_t *)source->array;
|
||||
|
|
@ -238,7 +241,10 @@ MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atan_obj, vectorise_atan);
|
|||
|
||||
mp_obj_t vectorise_arctan2(mp_obj_t y, mp_obj_t x) {
|
||||
ndarray_obj_t *ndarray_x = ndarray_from_mp_obj(x, 0);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray_x->dtype)
|
||||
|
||||
ndarray_obj_t *ndarray_y = ndarray_from_mp_obj(y, 0);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray_y->dtype)
|
||||
|
||||
uint8_t ndim = 0;
|
||||
size_t *shape = m_new(size_t, ULAB_MAX_DIMS);
|
||||
|
|
@ -544,6 +550,7 @@ static mp_obj_t vectorise_vectorized_function_call(mp_obj_t self_in, size_t n_ar
|
|||
mp_obj_t fvalue;
|
||||
if(mp_obj_is_type(args[0], &ulab_ndarray_type)) {
|
||||
ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0]);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype)
|
||||
ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, self->otypes);
|
||||
for(size_t i=0; i < source->len; i++) {
|
||||
avalue[0] = mp_binary_get_val_array(source->dtype, source->array, i);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "../../ulab.h"
|
||||
#include "../../ndarray.h"
|
||||
#include "../../numpy/carray/carray_tools.h"
|
||||
#include "../../numpy/fft/fft_tools.h"
|
||||
|
||||
#if ULAB_SCIPY_SIGNAL_HAS_SPECTROGRAM
|
||||
|
|
@ -68,6 +69,12 @@ mp_obj_t signal_sosfilt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
|
|||
if(!ndarray_object_is_array_like(args[0].u_obj) || !ndarray_object_is_array_like(args[1].u_obj)) {
|
||||
mp_raise_TypeError(translate("sosfilt requires iterable arguments"));
|
||||
}
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
if(MP_OBJ_IS_TYPE(args[1].u_obj, &ulab_ndarray_type)) {
|
||||
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[1].u_obj);
|
||||
COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype)
|
||||
}
|
||||
#endif
|
||||
size_t lenx = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[1].u_obj));
|
||||
ndarray_obj_t *y = ndarray_new_linear_array(lenx, NDARRAY_FLOAT);
|
||||
mp_float_t *yarray = (mp_float_t *)y->array;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@
|
|||
#include ULAB_CONFIG_FILE
|
||||
#endif
|
||||
|
||||
// Adds support for complex ndarrays
|
||||
#ifndef ULAB_SUPPORTS_COMPLEX
|
||||
#define ULAB_SUPPORTS_COMPLEX (1)
|
||||
#endif
|
||||
|
||||
// Determines, whether scipy is defined in ulab. The sub-modules and functions
|
||||
// of scipy have to be defined separately
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "ulab.h"
|
||||
#include "ulab_create.h"
|
||||
#include "ulab_tools.h"
|
||||
|
||||
#if ULAB_NUMPY_HAS_ONES | ULAB_NUMPY_HAS_ZEROS | ULAB_NUMPY_HAS_FULL | ULAB_NUMPY_HAS_EMPTY
|
||||
static mp_obj_t create_zeros_ones_full(mp_obj_t oshape, uint8_t dtype, mp_obj_t value) {
|
||||
|
|
@ -530,7 +531,11 @@ mp_obj_t create_frombuffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw
|
|||
if(mp_get_buffer(args[0].u_obj, &bufinfo, MP_BUFFER_READ)) {
|
||||
size_t sz = 1;
|
||||
if(dtype != NDARRAY_BOOL) { // mp_binary_get_size doesn't work with Booleans
|
||||
sz = mp_binary_get_size('@', dtype, NULL);
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
sz = mp_binary_get_complex_size(dtype);
|
||||
#else
|
||||
sz = mp_binary_get_size('@', dtype, NULL);
|
||||
#endif
|
||||
}
|
||||
if(bufinfo.len < offset) {
|
||||
mp_raise_ValueError(translate("offset must be non-negative and no greater than buffer length"));
|
||||
|
|
|
|||
|
|
@ -231,3 +231,13 @@ ndarray_obj_t *tools_object_is_square(mp_obj_t obj) {
|
|||
return ndarray;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ULAB_SUPPORTS_COMPLEX
|
||||
uint8_t mp_binary_get_complex_size(uint8_t dtype) {
|
||||
if(dtype == NDARRAY_COMPLEX) {
|
||||
return 2 * (uint8_t)mp_binary_get_size('@', NDARRAY_FLOAT, NULL);
|
||||
} else {
|
||||
return dtype == NDARRAY_BOOL ? 1 : mp_binary_get_size('@', dtype, NULL);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -34,4 +34,6 @@ void *ndarray_set_float_function(uint8_t );
|
|||
|
||||
shape_strides tools_reduce_axes(ndarray_obj_t *, mp_obj_t );
|
||||
ndarray_obj_t *tools_object_is_square(mp_obj_t );
|
||||
|
||||
uint8_t mp_binary_get_complex_size(uint8_t );
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in a new issue