codon/stdlib/numpy/routines.codon

5022 lines
148 KiB
Python

# Copyright (C) 2022-2025 Exaloop Inc. <https://exaloop.io>
import util
from .npdatetime import datetime64, timedelta64, busdaycalendar, _apply_busines_day_offset, \
_apply_busines_day_count, _apply_is_business_day, _BUSDAY_FORWARD, \
_BUSDAY_FOLLOWING, _BUSDAY_BACKWARD, _BUSDAY_PRECEDING, \
_BUSDAY_MODIFIEDFOLLOWING, _BUSDAY_MODIFIEDPRECEDING, _BUSDAY_NAT, \
_BUSDAY_RAISE
from .ndarray import ndarray, flagsobj
def _check_out(out, shape):
if not isinstance(out, ndarray):
compile_error("output must be an array")
if out.ndim != staticlen(shape):
compile_error("output parameter has the wrong number of dimensions")
if out.shape != shape:
raise ValueError("output parameter has incorrect shape")
############
# Creation #
############
def _inner_type(t):
if isinstance(t, ndarray):
return t.dtype()
elif isinstance(t, List) or isinstance(t, Tuple):
return _inner_type(t[0])
else:
return t
def _extract_shape(t):
if isinstance(t, ndarray):
return t.shape
elif isinstance(t, List) or isinstance(t, Tuple):
rest = _extract_shape(t[0])
return (len(t), *rest)
else:
return ()
def _flatten(t, shape, D: type):
def helper(t, p, start: int, D: type):
for s in t:
if isinstance(s, ndarray):
cc, fc = s._contig
if cc:
str.memcpy((p + start).as_byte(), s.data.as_byte(), s.nbytes)
else:
for idx in util.multirange(s.shape):
p[start] = s._ptr(idx)[0]
start += 1
start += s.size
elif isinstance(s, List) or isinstance(s, Tuple):
start = helper(s, p, start, D)
else:
p[start] = util.cast(s, D)
start += 1
return start
p = Ptr[D](util.count(shape))
helper(t, p, 0, D)
return p
def _validate_shape(t, shape):
def error():
raise ValueError('array dimensions mismatch (jagged arrays are not allowed)')
if staticlen(shape) == 0:
return
else:
if not hasattr(type(t), "__getitem__"):
error()
else:
if isinstance(t, ndarray):
if not util.tuple_equal(t.shape, shape):
error()
else:
if len(t) != shape[0]:
error()
for s in t:
_validate_shape(s, shape[1:])
def _array(a, dtype: type = NoneType, copy: bool = True, order: str = 'K'):
if dtype is NoneType:
if isinstance(a, ndarray):
return _array(a, a.dtype, copy, order)
else:
return _array(a, type(_inner_type(a)), copy, order)
ndarray._check_order(order)
if isinstance(a, ndarray):
if copy:
return a.astype(dtype, order=order, copy=True)
else:
if order == 'K' or order == 'A':
return a.astype(dtype, copy=False)
cc, fc = a._contig
if order == 'C':
if cc:
return a.astype(dtype, copy=False)
else:
return a.astype(dtype, order='C', copy=False)
if order == 'F':
if fc:
return a.astype(dtype, copy=False)
else:
return a.astype(dtype, order='F', copy=False)
elif isinstance(a, List) or isinstance(a, Tuple):
shape = _extract_shape(a)
data = _flatten(a, shape, dtype)
_validate_shape(a, shape)
result = ndarray(shape, data)
if order == 'F':
return result.astype(dtype, order='F')
else:
return result
else:
shape = ()
data = Ptr[dtype](1)
data[0] = util.cast(a, dtype)
return ndarray(shape, data)
def array(a, dtype: type = NoneType, copy: bool = True, order: str = 'K', ndmin: Static[int] = 0):
result = _array(a, dtype=dtype, copy=copy, order=order)
if staticlen(result.shape) < ndmin:
o = (1,) * (ndmin - staticlen(result.shape))
return result.reshape((*o, *result.shape))
return result
def asarray(a, dtype: type = NoneType, order: str = 'K'):
return array(a, dtype=dtype, copy=False, order=order)
def asanyarray(a, dtype: type = NoneType, order: str = 'K'):
return asarray(a, dtype=dtype, order=order)
def asarray_chkfinite(a, dtype: type = NoneType, order: str = 'K'):
# Note: this is c/p from ndmath
def isfinite(x):
if isinstance(x, float) or isinstance(x, float32):
return util.isfinite(x)
elif isinstance(x, complex) or isinstance(x, complex64):
return util.isfinite(x.real) and util.isfinite(x.imag)
else:
return True
a = asarray(a, dtype=dtype, order=order)
for idx in util.multirange(a.shape):
if not isfinite(a._ptr(idx)[0]):
raise ValueError("array must not contain infs or NaNs")
return a
def empty(shape, dtype: type = float, order: str = 'C'):
if isinstance(shape, int):
return empty((shape,), dtype, order)
for s in shape:
if s < 0:
raise ValueError('negative dimensions are not allowed')
ccontig = (order == 'C')
fcontig = (order == 'F')
if not (ccontig or fcontig):
raise ValueError("'order' must be 'C' or 'F'")
data = Ptr[dtype](util.count(shape))
return ndarray(shape, data, fcontig=fcontig)
def empty_like(prototype, dtype: type = NoneType, order: str = 'K', shape = None):
prototype = asarray(prototype)
if dtype is NoneType:
return empty_like(prototype, dtype=prototype.dtype, order=order, shape=None)
if shape is None:
return empty_like(prototype, dtype=dtype, order=order, shape=prototype.shape)
cc, fc = prototype._contig
if order == 'A':
order = 'F' if fc else 'C'
elif order == 'K':
if staticlen(shape) == prototype.ndim:
if cc or prototype.ndim <= 1:
order = 'C'
elif fc:
order = 'F'
else:
strides = (0,) * staticlen(prototype.strides)
pstrides = Ptr[int](__ptr__(strides).as_byte())
r = util.tuple_range(staticlen(shape))
perm, strides_sorted = util.sort_by_stride(r, prototype.strides)
stride = util.sizeof(dtype)
ndim = prototype.ndim
for idim in range(ndim - 1, -1, -1):
iperm = perm[idim]
pstrides[iperm] = stride
stride *= shape[iperm]
data = Ptr[dtype](util.count(shape))
return ndarray(shape, strides, data)
else:
order = 'C'
return empty(shape, dtype, order)
def zeros(shape, dtype: type = float, order: str = 'C'):
result = empty(shape, dtype, order)
str.memset(result.data.as_byte(), byte(0), result.nbytes)
return result
def zeros_like(prototype, dtype: type = NoneType, order: str = 'K'):
result = empty_like(prototype, dtype, order)
str.memset(result.data.as_byte(), byte(0), result.nbytes)
return result
def ones(shape, dtype: type = float, order: str = 'C'):
result = empty(shape, dtype, order)
result.map(lambda x: util.cast(1, result.dtype), inplace=True)
return result
def ones_like(prototype, dtype: type = NoneType, order: str = 'K'):
result = empty_like(prototype, dtype, order)
result.map(lambda x: util.cast(1, result.dtype), inplace=True)
return result
def identity(n: int, dtype: type = float):
result = zeros((n, n), dtype)
p = result.data
for i in range(n):
p[i * (n + 1)] = dtype(1)
return result
def _diag_count(k: int, n: int, m: int):
count = 0
if k < m and k > -n:
count = min(n, m)
if k > 0:
d = max(m - n, 0)
if k > d:
count -= (k - d)
elif k < 0:
d = max(n - m, 0)
if k < -d:
count -= (-k - d)
return count
def eye(N: int, M: Optional[int] = None, k: int = 0, dtype: type = float, order: str = 'C'):
n: int = N
m: int = M if M is not None else n
result = zeros((n, m), dtype=dtype, order=order)
p = result.data
for i in range(_diag_count(k, n, m)):
if k >= 0:
result[i, i + k] = dtype(1)
else:
j = n - i - 1
result[i - k, i] = dtype(1)
return result
def diag(v, k: int = 0):
v = asarray(v)
data = v.data
T = type(data[0])
if staticlen(v.shape) == 1:
count = v.shape[0]
n = count + abs(k)
result = zeros((n, n), dtype=T)
p = result.data
if k > 0:
p += k
elif k < 0:
p += (-k) * n
for i in range(count):
q = v._ptr((i,))
p[0] = q[0]
p += n + 1
return result
elif staticlen(v.shape) == 2:
n, m = v.shape
sn, sm = v.strides
new_shape = (_diag_count(k, n, m),)
new_strides = (sn + sm,)
new_data = data
if new_shape[0] > 0:
if k >= 0:
new_data = v._ptr((0, k))
else:
new_data = v._ptr((-k, 0))
return ndarray(new_shape, new_strides, new_data)
else:
compile_error('Input must be 1- or 2-d.')
def diagflat(v, k: int = 0):
return diag(asarray(v).flatten(), k)
def tri(N: int, M: Optional[int] = None, k: int = 0, dtype: type = float):
n: int = N
m: int = M if M is not None else n
result = zeros((n, m), dtype=dtype)
p = result.data
for i in range(n):
for j in range(min(i + k + 1, m)):
p[i*m + j] = dtype(1)
return result
def triu(x, k: int = 0):
x = asarray(x)
T = x.dtype
if staticlen(x.shape) == 0:
compile_error('Cannot call triu on 0-d array.')
elif staticlen(x.shape) == 1:
n = x.shape[0]
result = zeros((n, n), dtype=T)
p = result.data
for i in range(n):
for j in range(max(0, i + k), n):
p[i*n + j] = x[j]
return result
else:
y = x.copy()
n, m = x.shape[-2], x.shape[-1]
pre = (slice(None, None, None),) * (staticlen(x.shape) - 2)
for i in range(n):
for j in range(min(i + k, m)):
y[(*pre, i, j)] = T(0)
return y
def tril(x, k: int = 0):
x = asarray(x)
T = x.dtype
if staticlen(x.shape) == 0:
compile_error('Cannot call tril on 0-d array.')
elif staticlen(x.shape) == 1:
n = x.shape[0]
result = zeros((n, n), dtype=T)
p = result.data
for i in range(n):
for j in range(min(i + k + 1, n)):
p[i*n + j] = x[j]
return result
else:
y = x.copy()
n, m = x.shape[-2], x.shape[-1]
pre = (slice(None, None, None),) * (staticlen(x.shape) - 2)
for i in range(n):
for j in range(max(0, i + k + 1), m):
y[(*pre, i, j)] = T(0)
return y
def vander(x, N: Optional[int] = None, increasing: bool = False):
x = asarray(x)
if staticlen(x.shape) != 1:
compile_error('x must be a one-dimensional array or sequence.')
T = x.dtype
n: int = x.shape[0]
m: int = N if N is not None else n
result = zeros((n, m), dtype=T)
p = result.data
for i in range(n):
base = x._ptr((i,))[0]
for j in range(m):
power = j if increasing else m - j - 1
p[i*m + j] = base ** power
return result
@pure
@llvm
def _signbit(x: float) -> bool:
%y = bitcast double %x to i64
%z = icmp slt i64 %y, 0
%b = zext i1 %z to i8
ret i8 %b
_imin: Static[int] = -9223372036854775808
_imax: Static[int] = 9223372036854775807
def _safe_ceil(value: float):
ivalue = util.ceil64(value)
if util.isnan64(ivalue):
raise ValueError('arange: cannot compute array length')
if not (float(_imin) <= ivalue <= float(_imax)):
raise OverflowError('arange: overflow while computing length')
return int(ivalue)
def _datetime_arange(start, stop, step, dtype: type):
def convert(x, dtype: type):
if isinstance(x, datetime64) or isinstance(x, timedelta64):
return x.value
elif isinstance(x, int):
return x
elif isinstance(x, str):
return dtype(x, dtype.base).value
else:
compile_error("datetime_arange inputs must be datetime64, timedelta64 or int")
def nat(x: int):
return timedelta64(x, 'generic')._nat
if not (isinstance(dtype, datetime64) or isinstance(dtype, timedelta64)):
compile_error("datetime_arange was given a non-datetime dtype")
a = convert(start, dtype)
b = convert(stop, dtype)
c = 0
if step is None:
c = 1
elif isinstance(step, datetime64) or isinstance(step, str):
compile_error("cannot use a datetime as a step in arange")
else:
c = convert(step, dtype)
if ((isinstance(start, datetime64) or isinstance(start, str)) and not
(isinstance(stop, datetime64) or isinstance(stop, str))):
b += a
if nat(a) or nat(b) or nat(c):
raise ValueError("arange: cannot use NaT (not-a-time) datetime values")
length = len(range(a, b, c))
ans = empty(length, dtype)
for i in range(length):
ans.data[i] = dtype(a, dtype.base)
a += c
return ans
def arange(start: float, stop: float, step: float, dtype: type = float):
if step == 0.0:
raise ValueError('step cannot be zero')
delta = stop - start
tmp_len = delta / step
length = 0
if tmp_len == 0.0 and delta != 0.0:
if _signbit(tmp_len):
length = 0
else:
length = 1
else:
length = _safe_ceil(tmp_len)
if length <= 0:
return empty(0, dtype=dtype)
result = empty(length, dtype=dtype)
p = result.data
i = start
j = 0
while (i < stop) if step > 0.0 else (i > stop):
p[j] = util.cast(i, dtype)
j += 1
i += step
return result
@overload
def arange(stop: float, step: float, dtype: type = float):
return arange(0.0, stop, step, dtype)
@overload
def arange(start: float, stop: float, dtype: type = float):
return arange(start, stop, 1.0, dtype)
@overload
def arange(stop: float, dtype: type = float):
return arange(0.0, stop, 1.0, dtype)
@overload
def arange(start: int, stop: int, step: int, dtype: type = int):
if isinstance(dtype, datetime64) or isinstance(dtype, timedelta64):
return _datetime_arange(start, stop, step, dtype)
length = len(range(start, stop, step))
result = empty(length, dtype=dtype)
p = result.data
j = 0
for i in range(start, stop, step):
p[j] = util.cast(i, dtype)
j += 1
return result
@overload
def arange(stop: int, step: int, dtype: type = int):
return arange(0, stop, step, dtype)
@overload
def arange(start: int, stop: int, dtype: type = int):
return arange(start, stop, 1, dtype)
@overload
def arange(stop: int, dtype: type = int):
return arange(0, stop, 1, dtype)
@overload
def arange(start: datetime64, stop, step = None, dtype: type = NoneType):
if dtype is NoneType:
return _datetime_arange(start, stop, step, type(start))
else:
return _datetime_arange(start, stop, step, dtype)
@overload
def arange(start: timedelta64, stop, step, dtype: type = NoneType):
if dtype is NoneType:
return _datetime_arange(start, stop, step, type(start))
else:
return _datetime_arange(start, stop, step, dtype)
@overload
def arange(start: timedelta64, stop, dtype: type = NoneType):
if dtype is NoneType:
return _datetime_arange(start, stop, 1, type(start))
else:
return _datetime_arange(start, stop, 1, dtype)
@overload
def arange(stop: timedelta64, dtype: type = NoneType):
if dtype is NoneType:
return _datetime_arange(0, stop, 1, type(stop))
else:
return _datetime_arange(0, stop, 1, dtype)
@overload
def arange(start: str, stop, step = None, dtype: type = datetime64['D', 1]):
return _datetime_arange(start, stop, step, dtype)
def linspace(start: float, stop: float, num: int = 50,
endpoint: bool = True, retstep: Static[int] = False,
dtype: type = float):
if num < 0:
raise ValueError(f'Number of samples, {num}, must be non-negative.')
delta = stop - start
div = (num - 1) if endpoint else num
step = delta / div
result = empty(num, dtype=dtype)
p = result.data
if div > 0:
if step == 0:
for i in range(num):
p[i] = util.cast(((i / div) * delta) + start, dtype)
else:
for i in range(num):
p[i] = util.cast((i * step) + start, dtype)
else:
for i in range(num):
p[i] = util.cast((i * delta) + start, dtype)
step = util.nan64()
if endpoint and num > 1:
p[num - 1] = stop
if retstep:
return result, step
else:
return result
def _linlogspace(start: float, stop: float, num: int = 50, base: float = 10.0,
out_sign: int = 1, endpoint: bool = True, retstep: Static[int] = False,
dtype: type = float, log: Static[int] = False):
if num < 0:
raise ValueError(f'Number of samples, {num}, must be non-negative.')
delta = stop - start
div = (num - 1) if endpoint else num
step = delta / div
result = empty(num, dtype=dtype)
p = result.data
if div > 0:
if step == 0:
for i in range(num):
y = ((i / div) * delta) + start
if log:
y = util.pow64(base, y)
y *= out_sign
p[i] = util.cast(y, dtype)
else:
for i in range(num):
y = (i * step) + start
if log:
y = util.pow64(base, y)
y *= out_sign
p[i] = util.cast(y, dtype)
else:
for i in range(num):
y = (i * delta) + start
if log:
y = util.pow64(base, y)
y *= out_sign
p[i] = util.cast(y, dtype)
step = util.nan64()
if endpoint and num > 1:
y = stop
if log:
y = util.pow64(base, y)
y *= out_sign
p[num - 1] = util.cast(y, dtype)
if retstep:
return result, step
else:
return result
def linspace(start: float, stop: float, num: int = 50,
endpoint: bool = True, retstep: Static[int] = False,
dtype: type = float):
return _linlogspace(start=start, stop=stop, num=num,
endpoint=endpoint, retstep=retstep,
dtype=dtype, log=False)
def logspace(start: float, stop: float, num: int = 50,
endpoint: bool = True, base: float = 10.0,
retstep: Static[int] = False,
dtype: type = float):
return _linlogspace(start=start, stop=stop, num=num,
endpoint=endpoint, retstep=retstep,
dtype=dtype, base=base, log=True)
def geomspace(start: float, stop: float, num: int = 50,
endpoint: bool = True, dtype: type = float):
if start == 0 or stop == 0:
raise ValueError('Geometric sequence cannot include zero')
out_sign = 1
if start < 0 and stop < 0:
start, stop = -start, -stop
out_sign = -out_sign
start = start + (stop - stop)
stop = stop + (start - start)
log_start = util.log10_64(start)
log_stop = util.log10_64(stop)
return _linlogspace(start=log_start, stop=log_stop, num=num,
endpoint=endpoint, retstep=False,
dtype=dtype, base=10.0, out_sign=out_sign,
log=True)
def fromfunction(function, shape, dtype: type = float, **kwargs):
result_dtype = type(
function(
*tuple(util.zero(dtype)
for _ in staticrange(staticlen(shape))),
**kwargs))
result = empty(shape, dtype=result_dtype)
for idx in util.multirange(shape):
p = result._ptr(idx)
args = tuple(util.cast(i, dtype) for i in idx)
p[0] = function(*args, **kwargs)
return result
def fromiter(iterable, dtype: type, count: int = -1):
if count < 0:
return array([a for a in iterable], dtype=dtype)
else:
result = empty((count,), dtype=dtype)
if count:
p = result.data
i = 0
for a in iterable:
p[i] = util.cast(a, dtype)
i += 1
if i == count:
break
if i != count:
raise ValueError(f'iterator too short: Expected {count} but iterator had only {i} items.')
return result
def frombuffer(buffer: str, dtype: type = float, count: int = -1, offset: int = 0):
if count < 0:
count = len(buffer) // util.sizeof(dtype)
p = Ptr[dtype](buffer.ptr + offset)
return ndarray((count,), p)
################
# Broadcasting #
################
def broadcast_shapes(*args):
def _largest(args):
if staticlen(args) == 1:
return args[0]
a = args[0]
b = _largest(args[1:])
if staticlen(b) > staticlen(a):
return b
else:
return a
def _ensure_tuple(x):
if isinstance(x, Tuple):
return x
else:
return (x,)
if staticlen(args) == 0:
return ()
args = tuple(_ensure_tuple(a) for a in args)
for a in args:
for i in a:
if i < 0:
raise ValueError('negative dimensions are not allowed')
t = _largest(args)
N: Static[int] = staticlen(t)
ans = (0,) * N
p = Ptr[int](__ptr__(ans).as_byte())
for i in staticrange(N):
p[i] = t[i]
for a in args:
for i in staticrange(staticlen(a)):
x = a[len(a) - 1 - i]
q = p + (len(t) - 1 - i)
y = q[0]
if y == 1:
q[0] = x
elif x != 1 and x != y:
raise ValueError('shape mismatch: objects cannot be broadcast to a single shape')
return ans
def broadcast_to(x, shape):
x = asarray(x)
if not isinstance(shape, Tuple):
return broadcast_to(x, (shape,))
N: Static[int] = staticlen(x.shape)
if staticlen(shape) < N:
compile_error('input operand has more dimensions than allowed by the axis remapping')
shape1, shape2 = shape[:-N], shape[-N:]
substrides = (0,) * N
p = Ptr[int](__ptr__(substrides).as_byte())
if N > 0:
for i in range(N):
a = x.shape[i]
b = shape2[i]
if a == b:
p[i] = x.strides[i]
elif a == 1:
p[i] = 0
else:
raise ValueError(f'cannot broadcast array of shape {x.shape} to shape {shape}')
z = (0,) * (staticlen(shape) - N)
new_strides = (*z, *substrides)
return ndarray(shape, new_strides, x.data)
def broadcast_arrays(*args):
def _ensure_array(x):
if isinstance(x, ndarray):
return x
else:
return array(x)
args = tuple(_ensure_array(a) for a in args)
shapes = tuple(a.shape for a in args)
bshape = broadcast_shapes(*shapes)
return [broadcast_to(a, bshape) for a in args]
def meshgrid(*xi, copy: bool = True, sparse: Static[int] = False, indexing: Static[str] = 'xy'):
def make_shape(i, ndim: Static[int]):
t = (1,) * ndim
p = Ptr[int](__ptr__(t).as_byte())
p[i] = -1
return t
def build_output(xi, i: int = 0, ndim: Static[int]):
if staticlen(xi) == 0:
return ()
x = xi[0]
y = array(x).reshape(make_shape(i, ndim))
rest = build_output(xi[1:], i + 1, ndim)
return (y, *rest)
if indexing != 'xy' and indexing != 'ij':
compile_error("Valid values for `indexing` are 'xy' and 'ij'.")
ndim: Static[int] = staticlen(xi)
s0 = (1,) * ndim
output = build_output(xi, ndim=ndim)
if indexing == 'xy' and ndim > 1:
# switch first and second axis
output0 = output[0].reshape(1, -1, *s0[2:])
output1 = output[1].reshape(-1, 1, *s0[2:])
output = (output0, output1, *output[2:])
if not sparse:
# Return the full N-D matrix (not only the 1-D vector)
return [a for a in broadcast_arrays(*output)]
if copy:
return [a.copy() for a in output]
return [a for a in output]
class _broadcast[A, S]:
_arrays: A
_shape: S
_index: int
def __init__(self, arrays: A, shape: S):
self._arrays = arrays
self._shape = shape
self._index = 0
@property
def shape(self):
return self._shape
@property
def index(self):
return self._index
@property
def size(self):
return util.count(self.shape)
@property
def ndim(self):
return staticlen(self.shape)
@property
def nd(self):
return self.ndim
@property
def numiter(self):
return staticlen(self._arrays)
@property
def iters(self):
def get_flat(arr, index, bshape):
f = broadcast_to(arr, bshape).flat
f.index = index
return f
return tuple(get_flat(a, self.index, self.shape) for a in self._arrays)
def __iter__(self):
arrays = self._arrays
n = self.size
while self.index < n:
idx = util.index_to_coords(self.index, self.shape)
self._index += 1
yield tuple(a._ptr(idx, broadcast=True)[0] for a in arrays)
def reset(self):
self._index = 0
def broadcast(*args):
arrays = tuple(asarray(a) for a in args)
shape = broadcast_shapes(*tuple(a.shape for a in arrays))
return _broadcast(arrays, shape)
def full(shape, fill_value, dtype: type = NoneType, order: str = 'C'):
if isinstance(shape, int):
sh = (shape,)
else:
sh = shape
fv = asarray(fill_value)
if fv.ndim != 0:
broadcast_to(fv, shape) # error check
if dtype is NoneType:
result = empty(shape, fv.dtype, order)
if fv.ndim == 0:
e = fv.item()
result.map(lambda x: e, inplace=True)
else:
for idx in util.multirange(shape):
result._ptr(idx)[0] = fv._ptr(idx, broadcast=True)[0]
return result
else:
result = empty(shape, dtype, order)
if fv.ndim == 0:
e = fv.item()
result.map(lambda x: util.cast(e, result.dtype), inplace=True)
else:
for idx in util.multirange(shape):
result._ptr(idx)[0] = util.cast(fv._ptr(idx, broadcast=True)[0], result.dtype)
return result
def full_like(prototype, fill_value, dtype: type = NoneType, order: str = 'K'):
prototype = asarray(prototype)
shape = prototype.shape
fv = asarray(fill_value)
if fv.ndim != 0:
broadcast_to(fv, shape) # error check
if dtype is NoneType:
result = empty_like(prototype, fv.dtype, order)
if fv.ndim == 0:
e = fv.item()
result.map(lambda x: e, inplace=True)
else:
for idx in util.multirange(shape):
result._ptr(idx)[0] = fv._ptr(idx, broadcast=True)[0]
return result
else:
result = empty_like(prototype, dtype, order)
if fv.ndim == 0:
e = fv.item()
result.map(lambda x: util.cast(e, result.dtype), inplace=True)
else:
for idx in util.multirange(shape):
result._ptr(idx)[0] = util.cast(fv._ptr(idx, broadcast=True)[0], result.dtype)
return result
################
# Manipulation #
################
def copyto(dst: ndarray, src, where = True):
src = asarray(src)
dst_dtype = dst.dtype
src_dtype = src.dtype
if isinstance(where, bool):
if not where:
return
if dst_dtype is src_dtype and src._contig_match(dst):
str.memcpy(dst.data.as_byte(), src.data.as_byte(), dst.nbytes)
return
src = broadcast_to(src, dst.shape)
where = broadcast_to(asarray(where), dst.shape)
for idx in util.multirange(dst.shape):
w = where._ptr(idx)
if w[0]:
p = src._ptr(idx)
q = dst._ptr(idx)
q[0] = util.cast(p[0], dst_dtype)
def ndim(a):
return asarray(a).ndim
def size(a, axis: Optional[int] = None):
a = asarray(a)
if axis is None:
return a.size
else:
return a.shape[axis]
def shape(a):
if isinstance(a, ndarray):
return a.shape
else:
shape = _extract_shape(a)
_validate_shape(a, shape)
return shape
def reshape(a, newshape, order: str = 'C'):
return asarray(a).reshape(newshape, order=order)
def transpose(a, axes=None):
return a.transpose(axes)
def ravel(a, order: str = 'C'):
return asarray(a).ravel(order=order)
def ascontiguousarray(a, dtype: type = NoneType):
return asarray(a, dtype=dtype, order='C')
def asfortranarray(a, dtype: type = NoneType):
return asarray(a, dtype=dtype, order='F')
def asfarray(a, dtype: type = float):
if (dtype is not float and
dtype is not float32 and
dtype is not complex and
dtype is not complex64):
return asfarray(a, float)
return asarray(a, dtype=dtype)
def moveaxis(a, source, destination):
a = asarray(a)
source = util.normalize_axis_tuple(source, a.ndim, 'source')
destination = util.normalize_axis_tuple(destination, a.ndim, 'destination')
if len(source) != len(destination):
raise ValueError('`source` and `destination` arguments must have '
'the same number of elements')
order = [n for n in range(a.ndim) if n not in source]
for dest, src in sorted(zip(destination, source)):
order.insert(dest, src)
order = Ptr[type(a.shape)](order.arr.ptr.as_byte())[0]
return a.transpose(order)
def swapaxes(a, axis1: int, axis2: int):
return asarray(a).swapaxes(axis1, axis2)
def atleast_1d(*arys):
def atl1d(a):
a = asarray(a)
if staticlen(a.shape) == 0:
return a.reshape(1)
else:
return a
if staticlen(arys) == 1:
return atl1d(arys[0])
else:
return tuple(atl1d(a) for a in arys)
def atleast_2d(*arys):
def atl2d(a):
a = asarray(a)
if staticlen(a.shape) == 0:
return a.reshape(1, 1)
elif staticlen(a.shape) == 1:
return a[None, :]
else:
return a
if staticlen(arys) == 1:
return atl2d(arys[0])
else:
return tuple(atl2d(a) for a in arys)
def atleast_3d(*arys):
def atl3d(a):
a = asarray(a)
if staticlen(a.shape) == 0:
return a.reshape(1, 1, 1)
elif staticlen(a.shape) == 1:
return a[None, :, None]
elif staticlen(a.shape) == 2:
return a[:, :, None]
else:
return a
if staticlen(arys) == 1:
return atl3d(arys[0])
else:
return tuple(atl3d(a) for a in arys)
def require(a, dtype: type = NoneType, requirements = None):
REQ_C: Static[int] = 1
REQ_F: Static[int] = 2
REQ_A: Static[int] = 4
REQ_W: Static[int] = 8
REQ_O: Static[int] = 16
REQ_E: Static[int] = 32
if requirements is None:
return asarray(a, dtype=dtype)
if not requirements:
return asarray(a, dtype=dtype)
req = 0
for x in requirements:
if x in ('C', 'C_CONTIGUOUS', 'CONTIGUOUS'):
req |= REQ_C
elif x in ('F', 'F_CONTIGUOUS', 'FORTRAN'):
req |= REQ_F
elif x in ('A', 'ALIGNED'):
req |= REQ_A
elif x in ('W', 'WRITEABLE'):
req |= REQ_W
elif x in ('O', 'OWNDATA'):
req |= REQ_O
elif x in ('E', 'ENSUREARRAY'):
req |= REQ_E
else:
raise ValueError("invalid requirement: " + repr(x))
order = 'A'
if (req & REQ_C) and (req & REQ_F):
raise ValueError('Cannot specify both "C" and "F" order')
elif req & REQ_F:
order = 'F'
req &= ~REQ_F
elif req & REQ_C:
order = 'C'
req &= ~REQ_C
# Codon-NumPy ignores other flags/properties currently
copy = ((req & REQ_O) != 0)
arr = array(a, dtype=dtype, order=order, copy=copy)
return arr
def _copy_data_c(dst: cobj, src: cobj, shape: Ptr[int],
strides: Ptr[int], dst_strides: Ptr[int],
ndim: Static[int], element_size: int,
block: int, block_dim: int):
if ndim < 0:
return
if ndim == block_dim:
str.memcpy(dst, src, block)
else:
src_stride = strides[0]
dst_stride = dst_strides[0]
for j in range(shape[0]):
_copy_data_c(dst + j * dst_stride, src + j * src_stride,
shape + 1, strides + 1, dst_strides + 1,
ndim - 1, element_size, block, block_dim)
def _copy_data_f(dst: cobj, src: cobj, shape: Ptr[int],
strides: Ptr[int], dst_strides: Ptr[int],
ndim: Static[int], element_size: int,
block: int, block_dim: int):
if ndim < 0:
return
if ndim == block_dim:
str.memcpy(dst, src, block)
else:
src_stride = strides[ndim - 1]
dst_stride = dst_strides[ndim - 1]
for j in range(shape[ndim - 1]):
_copy_data_f(dst + j * dst_stride, src + j * src_stride,
shape, strides, dst_strides,
ndim - 1, element_size, block, block_dim)
def concatenate(arrays, axis = 0, out = None, dtype: type = NoneType):
def check_array_dims_static(arrays, ndim: Static[int]):
if staticlen(arrays) > 0:
if staticlen(arrays[0].shape) != ndim:
compile_error("all the input arrays must have same number of dimensions")
check_array_dims_static(arrays[1:], ndim)
def find_out_type(arrays, dtype: type):
if staticlen(arrays) == 0:
return dtype()
else:
x = util.coerce(arrays[0].dtype, dtype)
return find_out_type(arrays[1:], type(x))
def concat_inner(arrays, axis: int, out, dtype: type):
if out is not None and dtype is NoneType:
return concat_inner(arrays, axis=axis, out=out, dtype=out.dtype)
if out is None and dtype is NoneType:
compile_error("[internal error] bad out and dtype given to concat_inner")
arr0 = asarray(arrays[0])
ndim: Static[int] = arrays[0].ndim
axis = util.normalize_axis_index(axis, ndim)
shape = arr0.shape
pshape = Ptr[int](__ptr__(shape).as_byte())
for i in range(1, len(arrays)):
arr = arrays[i]
arr_shape = arr.shape
if arr.ndim != ndim:
compile_error("all the input arrays must have same number of dimensions")
for idim in staticrange(ndim):
if idim == axis:
pshape[idim] += arr_shape[idim]
elif pshape[idim] != arr_shape[idim]:
raise ValueError("all the input array dimensions except for "
"the concatenation axis must match exactly")
corder = True
if out is not None:
if dtype is not NoneType:
compile_error("can only specify one of 'out' and 'dtype'")
_check_out(out, shape)
ret = out
else:
num_c = 0
num_f = 0
for array in arrays:
cc, fc = array._contig
if cc:
num_c += 1
if fc:
num_f += 1
corder = (num_c >= num_f)
ret = empty(shape, dtype, order=('C' if corder else 'F'))
element_size = util.sizeof(dtype)
offset = 0
dst_strides = ret.strides
dst_stride_axis = dst_strides[axis]
element_size = util.sizeof(dtype)
for array in arrays:
array_shape = array.shape
if array.dtype is dtype:
strides = array.strides
dst = ret.data.as_byte() + offset * dst_stride_axis
src = array.data.as_byte()
block = element_size
block_dim = ndim
if corder:
i = ndim - 1
while i >= 0:
if strides[i] == block and dst_strides[i] == block:
block *= array_shape[i]
else:
block_dim = ndim - 1 - i
break
i -= 1
_copy_data_c(dst, src, Ptr[int](__ptr__(array_shape).as_byte()),
Ptr[int](__ptr__(strides).as_byte()),
Ptr[int](__ptr__(dst_strides).as_byte()),
array.ndim, element_size, block, block_dim)
else:
i = 0
while i < ndim:
if strides[i] == block and dst_strides[i] == block:
block *= array_shape[i]
else:
block_dim = i
break
i += 1
_copy_data_f(dst, src, Ptr[int](__ptr__(array_shape).as_byte()),
Ptr[int](__ptr__(strides).as_byte()),
Ptr[int](__ptr__(dst_strides).as_byte()),
array.ndim, element_size, block, block_dim)
else:
for src_idx in util.multirange(array_shape):
dst_idx = util.tuple_add(src_idx, axis, offset)
ret._ptr(dst_idx)[0] = util.cast(array._ptr(src_idx)[0], dtype)
offset += array_shape[axis]
return ret
def concat_flatten(arrays, out):
dtype = out.dtype
out_ccontig = out._contig[0]
i = 0
for a in arrays:
if a.dtype is dtype and out_ccontig and a._contig[0]:
q = out._ptr((i,))
n = a.size
str.memcpy(q.as_byte(), a.data.as_byte(), n * util.sizeof(dtype))
i += n
else:
for idx in util.multirange(a.shape):
p = a._ptr(idx)
q = out._ptr((i,))
q[0] = util.cast(p[0], dtype)
i += 1
return out
def concat_tuple(arrays, axis = 0, out = None, dtype: type = NoneType):
if staticlen(arrays) == 0:
compile_error("need at least one array to concatenate")
arrays = tuple(asarray(arr) for arr in arrays)
if axis is None:
tot = 0
for a in arrays:
tot += a.size
shape = (tot,)
if out is None:
if dtype is NoneType:
x = find_out_type(arrays[1:], arrays[0].dtype)
return concat_flatten(arrays, empty(shape, dtype=type(x)))
else:
return concat_flatten(arrays, empty(shape, dtype=dtype))
else:
if staticlen(out.shape) != 1:
compile_error("Output array has wrong dimensionality")
if not util.tuple_equal(out.shape, shape):
raise ValueError("Output array is the wrong shape")
return concat_flatten(arrays, out)
else:
ndim: Static[int] = staticlen(arrays[0].shape)
if ndim == 0:
compile_error("zero-dimensional arrays cannot be concatenated")
check_array_dims_static(arrays[1:], ndim)
if out is None:
if dtype is NoneType:
x = find_out_type(arrays[1:], arrays[0].dtype)
return concat_inner(arrays, axis, out=None, dtype=type(x))
else:
return concat_inner(arrays, axis, out=None, dtype=dtype)
else:
return concat_inner(arrays, axis, out, dtype=out.dtype)
def concat_list(arrays, axis = 0, out = None, dtype: type = NoneType):
if len(arrays) == 0:
raise ValueError("need at least one array to concatenate")
if not isinstance(arrays[0], ndarray):
arrays = [asarray(arr) for arr in arrays]
if axis is None:
if axis is not None and len(arrays) == 1:
util.normalize_axis_index(axis, 1) # error check
tot = 0
for a in arrays:
tot += a.size
shape = (tot,)
if out is None:
if dtype is NoneType:
return concat_flatten(arrays, empty(shape, dtype=arrays[0].dtype))
else:
return concat_flatten(arrays, empty(shape, dtype=dtype))
else:
if staticlen(out.shape) != 1:
compile_error("Output array has wrong dimensionality")
if not util.tuple_equal(out.shape, shape):
raise ValueError("Output array is the wrong shape")
return concat_flatten(arrays, out)
else:
ndim: Static[int] = staticlen(arrays[0].shape)
if ndim == 0:
compile_error("zero-dimensional arrays cannot be concatenated")
for arr in arrays:
if arr.ndim != arrays[0].ndim:
raise ValueError("all the input arrays must have same number of dimensions")
shape = arrays[0].shape
pshape = Ptr[int](__ptr__(shape).as_byte())
axis = util.normalize_axis_index(axis, ndim)
for iarrays in range(1, len(arrays)):
arr_shape = arrays[iarrays].shape
for idim in range(ndim):
if idim == axis:
pshape[idim] += arr_shape[idim]
elif pshape[idim] != arr_shape[idim]:
raise ValueError("all the input array dimensions except for the "
"concatenation axis must match exactly")
if out is None:
if dtype is NoneType:
return concat_inner(arrays, axis, out=None, dtype=arrays[0].dtype)
else:
return concat_inner(arrays, axis, out=None, dtype=dtype)
else:
return concat_inner(arrays, axis, out=out, dtype=out.dtype)
if out is not None:
if dtype is not NoneType:
compile_error("concatenate() only takes `out` or `dtype` as an argument, but both were provided.")
if not isinstance(out, ndarray):
compile_error("'out' must be an array")
if isinstance(arrays, Tuple):
return concat_tuple(arrays, axis=axis, out=out, dtype=dtype)
else:
return concat_list(arrays, axis=axis, out=out, dtype=dtype)
def expand_dims(a, axis):
a = asarray(a)
old_ndims: Static[int] = staticlen(a.shape)
new_ndims: Static[int] = (old_ndims +
staticlen(util.normalize_axis_tuple(axis, 9999999)))
axis = util.normalize_axis_tuple(axis, new_ndims)
old_shape = a.shape
pold_shape = Ptr[int](__ptr__(old_shape).as_byte())
new_shape = (1,) * new_ndims
pnew_shape = Ptr[int](__ptr__(new_shape).as_byte())
j = 0
for i in range(new_ndims):
if i not in axis:
pnew_shape[i] = pold_shape[j]
j += 1
return a.reshape(new_shape)
def _apply(fn, seq):
if isinstance(seq, Tuple):
return tuple(fn(a) for a in seq)
elif isinstance(seq, List):
return [fn(a) for a in seq]
else:
compile_error("expected a tuple or a list as input")
def stack(arrays, axis: int = 0, out = None, dtype: type = NoneType):
if not (isinstance(arrays, Tuple) or
(isinstance(arrays, List) and
isinstance(arrays[0], ndarray))):
return asarray(arrays)
if len(arrays) == 0:
raise ValueError("need at least one array to stack")
arrays = _apply(asarray, arrays)
arr0 = arrays[0]
for arr in arrays:
if not util.tuple_equal(arr0.shape, arr.shape):
raise ValueError("all input arrays must have the same shape")
result_ndim = arrays[0].ndim + 1
axis = util.normalize_axis_index(axis, result_ndim)
expanded_arrays = _apply(lambda arr: expand_dims(arr, axis=axis), arrays)
return concatenate(expanded_arrays, axis=axis, out=out, dtype=dtype)
def vstack(tup, dtype: type = NoneType):
if not (isinstance(tup, Tuple) or
(isinstance(tup, List) and
isinstance(tup[0], ndarray))):
return asarray(tup)
arrs = _apply(atleast_2d, tup)
return concatenate(arrs, axis=0, dtype=dtype)
def hstack(tup, dtype: type = NoneType):
if not (isinstance(tup, Tuple) or
(isinstance(tup, List) and
isinstance(tup[0], ndarray))):
return asarray(tup)
arrs = _apply(atleast_1d, tup)
axis = 0 if (len(arrs) > 0 and arrs[0].ndim == 1) else 1
return concatenate(arrs, axis=axis, dtype=dtype)
def dstack(tup):
arrs = _apply(atleast_3d, tup)
return concatenate(arrs, axis=2)
row_stack = vstack
def column_stack(tup):
def fix_array(v):
arr = asarray(v)
if staticlen(arr.shape) < 2:
return array(arr, copy=False, ndmin=2).T
else:
return arr
arrs = _apply(fix_array, tup)
return concatenate(arrs, axis=1)
def repeat(a, repeats, axis = None):
def neg_rep_error():
raise ValueError("negative dimensions are not allowed")
a = asarray(a, order='C')
dtype = a.dtype
shape = a.shape
pshape = Ptr[int](__ptr__(shape).as_byte())
if isinstance(repeats, int):
if repeats < 0:
neg_rep_error()
else:
for rep in repeats:
if rep < 0:
neg_rep_error()
if isinstance(axis, int):
axis = util.normalize_axis_index(axis, a.ndim)
n = pshape[axis]
nel = 1
for i in range(axis + 1, a.ndim):
nel *= pshape[i]
chunk = nel * a.itemsize
n_outer = 1
for i in range(axis):
n_outer *= pshape[i]
rep_shape = shape
prep_shape = Ptr[int](__ptr__(rep_shape).as_byte())
if isinstance(repeats, int):
prep_shape[axis] *= repeats
repeated = empty(rep_shape, dtype=dtype)
old_data = a.data.as_byte()
new_data = repeated.data.as_byte()
for i in range(n_outer):
for j in range(n):
for k in range(repeats):
str.memcpy(new_data, old_data, chunk)
new_data += chunk
old_data += chunk
for src in util.multirange(shape):
e = a._ptr(src)[0]
off = src[axis]
for r in range(repeats):
dst = util.tuple_set(src, axis, off * repeats + r)
p = repeated._ptr(dst)
p[0] = e
return repeated
else:
axis_dim = prep_shape[axis]
if len(repeats) != axis_dim:
raise ValueError("length of 'repeats' does not match axis size")
prep_shape[axis] = 0
for rep in repeats:
prep_shape[axis] += rep
repeated = empty(rep_shape, dtype=dtype)
old_data = a.data.as_byte()
new_data = repeated.data.as_byte()
for i in range(n_outer):
for j in range(n):
for k in range(repeats[j]):
str.memcpy(new_data, old_data, chunk)
new_data += chunk
old_data += chunk
return repeated
elif axis is None:
if isinstance(repeats, int):
a_size = a.size
rep_size = a_size * repeats
repeated = empty((rep_size,), dtype=dtype)
p = a.data
q = repeated.data
off = 0
for i in range(a_size):
elem = p[i]
for _ in range(repeats):
q[off] = elem
off += 1
return repeated
else:
a_size = a.size
if len(repeats) != a_size:
raise ValueError("length of 'repeats' does not match array size")
rep_tot = 0
for rep in repeats:
rep_tot += rep
repeated = empty((rep_tot,), dtype=dtype)
p = a.data
q = repeated.data
rep_idx = 0
off = 0
for i in range(a_size):
elem = p[i]
for _ in range(repeats[i]):
q[off] = elem
off += 1
return repeated
else:
compile_error("'axis' must be None or an int")
def delete(arr, obj, axis = None):
arr = asarray(arr)
dtype = arr.dtype
shape = arr.shape
newshape = shape
pnewshape = Ptr[int](__ptr__(newshape).as_byte())
cc, fc = arr._contig
arrorder = 'F' if (fc and not cc) else 'C'
if isinstance(axis, int):
axis = util.normalize_axis_index(axis, arr.ndim)
shape_no_axis = util.tuple_delete(shape, axis)
N = arr.shape[axis]
if isinstance(obj, int):
pnewshape[axis] -= 1
new = empty(newshape, dtype, arrorder)
obj = util.normalize_index(obj, axis, N)
for i in range(obj):
for idx0 in util.multirange(shape_no_axis):
idx = util.tuple_insert(idx0, axis, i)
p = arr._ptr(idx)
q = new._ptr(idx)
q[0] = p[0]
for i in range(obj + 1, N):
for idx0 in util.multirange(shape_no_axis):
idx1 = util.tuple_insert(idx0, axis, i)
idx2 = util.tuple_insert(idx0, axis, i - 1)
p = arr._ptr(idx1)
q = new._ptr(idx2)
q[0] = p[0]
return new
elif isinstance(obj, slice):
start, stop, step = obj.adjust_indices(N)
xr = range(start, stop, step)
numtodel = len(xr)
if numtodel <= 0:
return arr.copy(order=arrorder)
if step < 0:
step = -step
start = xr[-1]
stop = xr[0] + 1
pnewshape[axis] -= numtodel
new = empty(newshape, dtype, arrorder)
if start:
for i in range(start):
for idx0 in util.multirange(shape_no_axis):
idx = util.tuple_insert(idx0, axis, i)
p = arr._ptr(idx)
q = new._ptr(idx)
q[0] = p[0]
if stop != N:
for i in range(stop, N):
for idx0 in util.multirange(shape_no_axis):
idx1 = util.tuple_insert(idx0, axis, i)
idx2 = util.tuple_insert(idx0, axis, i - numtodel)
p = arr._ptr(idx1)
q = new._ptr(idx2)
q[0] = p[0]
if step != 1:
off = start
for i in range(start, stop):
if i in xr:
continue
for idx0 in util.multirange(shape_no_axis):
idx1 = util.tuple_insert(idx0, axis, i)
idx2 = util.tuple_insert(idx0, axis, off)
p = arr._ptr(idx1)
q = new._ptr(idx2)
q[0] = p[0]
off += 1
return new
else:
if isinstance(obj[0], int):
remove = [util.normalize_index(r, axis, N) for r in obj]
remove.sort()
# remove duplicates
n = len(remove)
numtodel = 0
if n < 2:
numtodel = n
else:
j = 0
for i in range(n - 1):
if remove[i] != remove[i+1]:
remove[j] = remove[i]
j += 1
remove[j] = remove[n-1]
j += 1
numtodel = j
if numtodel == 0:
return arr.copy(order=arrorder)
pnewshape[axis] -= numtodel
new = empty(newshape, dtype, arrorder)
curr = 0
skip = remove[curr]
for i in range(N):
if i == skip:
curr += 1
if curr < numtodel:
skip = remove[curr]
continue
for idx0 in util.multirange(shape_no_axis):
idx1 = util.tuple_insert(idx0, axis, i)
idx2 = util.tuple_insert(idx0, axis, i - curr)
p = arr._ptr(idx1)
q = new._ptr(idx2)
q[0] = p[0]
return new
elif isinstance(obj[0], bool):
if len(obj) != N:
raise ValueError(
f"boolean array argument obj to delete must be one dimensional and match the axis length of {N}")
numtodel = 0
for r in obj:
if r:
numtodel += 1
pnewshape[axis] -= numtodel
new = empty(newshape, dtype, arrorder)
off = 0
for i in range(N):
if obj[i]:
continue
for idx0 in util.multirange(shape_no_axis):
idx1 = util.tuple_insert(idx0, axis, i)
idx2 = util.tuple_insert(idx0, axis, off)
p = arr._ptr(idx1)
q = new._ptr(idx2)
q[0] = p[0]
off += 1
return new
else:
compile_error("arrays used as indices must be of integer (or boolean) type")
elif axis is None:
return delete(arr.ravel(), obj, axis=0)
else:
compile_error("'axis' must be None or an int")
def append(arr, values, axis = None):
arr = asarray(arr)
values = asarray(values)
if staticlen(arr.shape) != staticlen(values.shape):
compile_error("'arr' and 'values' must have the same number of dimensions")
dtype1 = arr.dtype
dtype2 = values.dtype
dtype_out = type(util.coerce(dtype1, dtype2))
ndim: Static[int] = staticlen(arr.shape)
shape = arr.shape
val_shape = values.shape
arr_nbytes = arr.nbytes
val_nbytes = values.nbytes
newshape = shape
pnewshape = Ptr[int](__ptr__(newshape).as_byte())
cc, fc = arr._contig
arrorder = 'F' if (fc and not cc) else 'C'
if isinstance(axis, int):
axis = util.normalize_axis_index(axis, ndim)
if util.tuple_delete(val_shape, axis) != util.tuple_delete(shape, axis):
raise ValueError("'arr' and 'values' must have the same shape aside from specified axis")
pnewshape[axis] += val_shape[axis]
new = empty(newshape, dtype_out, arrorder)
off = shape[axis]
if dtype1 is dtype_out and ((axis == 0 and cc) or (axis == ndim - 1 and fc)):
str.memcpy(new.data.as_byte(), arr.data.as_byte(), arr_nbytes)
else:
for idx in util.multirange(shape):
p = arr._ptr(idx)
q = new._ptr(idx)
q[0] = util.cast(p[0], dtype_out)
if dtype2 is dtype_out and ((axis == 0 and cc) or (axis == ndim - 1 and fc)):
q = new.data.as_byte() + arr_nbytes
str.memcpy(q, values.data.as_byte(), val_nbytes)
else:
for idx1 in util.multirange(val_shape):
idx2 = util.tuple_add(idx1, axis, off)
p = values._ptr(idx1)
q = new._ptr(idx2)
q[0] = util.cast(p[0], dtype_out)
return new
elif axis is None:
new = empty((arr.size + values.size,), dtype_out, arrorder)
q = new.data
off = 0
val_cc = values._contig[0]
if dtype1 is dtype_out and cc:
str.memcpy(q.as_byte(), arr.data.as_byte(), arr_nbytes)
else:
for idx in util.multirange(shape):
p = arr._ptr(idx)
q[off] = util.cast(p[0], dtype_out)
off += 1
if dtype2 is dtype_out and val_cc:
str.memcpy(q.as_byte() + arr_nbytes, values.data.as_byte(), val_nbytes)
else:
for idx in util.multirange(val_shape):
p = values._ptr(idx)
q[off] = util.cast(p[0], dtype_out)
off += 1
return new
else:
compile_error("'axis' must be None or an int")
def insert(arr, obj, values, axis = None):
def normalize_special(idx: int, axis: int, n: int, numnew: int):
idx0 = idx
if idx < 0:
idx += n
if idx < 0 or idx >= n + numnew:
raise IndexError(f'index {idx0} is out of bounds for axis {axis} with size {n + numnew}')
return idx
arr = asarray(arr)
values = asarray(values)
dtype = arr.dtype
shape = arr.shape
newshape = shape
pnewshape = Ptr[int](__ptr__(newshape).as_byte())
cc, fc = arr._contig
arrorder = 'F' if (fc and not cc) else 'C'
ndim: Static[int] = staticlen(arr.shape)
if isinstance(axis, int):
axis = util.normalize_axis_index(axis, arr.ndim)
N = pnewshape[axis]
numnew = 0
indices: List[Tuple[int,int]]
if isinstance(obj, slice):
start, stop, step, length = obj.adjust_indices(N)
numnew = length
indices = [(normalize_special(a, axis, N, numnew), i)
for i, a in enumerate(range(start, stop, step))]
indices.sort()
elif isinstance(obj, int):
numnew = 1
indices = [(normalize_special(obj, axis, N, numnew), 0)]
else:
numnew = len(obj)
indices = [(normalize_special(a, axis, N, numnew), i)
for i, a in enumerate(obj)]
indices.sort()
numnew = len(indices)
for i in range(numnew):
idx, rank = indices[i]
idx += i
util.normalize_index(idx, axis, N + numnew) # error check
indices[i] = (idx, rank)
if numnew == 0:
return arr.copy(order=arrorder)
pnewshape[axis] += numnew
new = empty(newshape, dtype, arrorder)
newshape_no_axis = util.tuple_delete(newshape, axis)
curr = 0
off = 0
next_index, next_rank = indices[curr]
multiple_values = False
if staticlen(values.shape) > 0:
multiple_values = (len(values) == numnew)
for ai in range(pnewshape[axis]):
if ai == next_index:
for idx0 in util.multirange(newshape_no_axis):
idx1 = util.tuple_insert(idx0, axis, 0)
idx2 = util.tuple_insert(idx0, axis, ai)
q = new._ptr(idx2)
if staticlen(values.shape) == 0:
q[0] = util.cast(values.data[0], dtype)
else:
if multiple_values:
p = asarray(values[next_rank])._ptr(idx1, broadcast=True)
q[0] = util.cast(p[0], dtype)
else:
p = values._ptr(idx1, broadcast=True)
q[0] = util.cast(p[0], dtype)
curr += 1
if curr < numnew:
next_index, next_rank = indices[curr]
else:
for idx0 in util.multirange(newshape_no_axis):
idx1 = util.tuple_insert(idx0, axis, off)
idx2 = util.tuple_insert(idx0, axis, ai)
p = arr._ptr(idx1)
q = new._ptr(idx2)
q[0] = util.cast(p[0], dtype)
off += 1
return new
elif axis is None:
return insert(arr.ravel(), obj, values, axis=0)
else:
compile_error("'axis' must be None or an int")
def array_split(ary, indices_or_sections, axis: int = 0):
def correct(idx: int, n: int):
if idx > n:
return n
elif idx < -n:
return 0
elif idx < 0:
return idx + n
else:
return idx
def slice_axis(arr, axis: int, start: int, stop: int):
ndim: Static[int] = staticlen(ary.shape)
dtype = arr.dtype
shape = arr.shape
pshape = Ptr[int](__ptr__(shape).as_byte())
limit = pshape[axis]
start = correct(start, limit)
stop = correct(stop, limit)
base = (0,) * ndim
pbase = Ptr[int](__ptr__(base).as_byte())
pbase[axis] = start
pshape[axis] = stop - start if stop > start else 0
sub = arr._ptr(base) if stop > start else Ptr[dtype]()
return ndarray(shape, arr.strides, sub)
ary = asarray(ary)
axis = util.normalize_axis_index(axis, ary.ndim)
ntotal = ary.shape[axis]
if isinstance(indices_or_sections, int):
nsections = indices_or_sections
if nsections <= 0:
raise ValueError("number sections must be larger than 0.")
neach, extras = divmod(ntotal, nsections)
result = List(capacity=nsections)
start = 0
for i in range(nsections):
stop = start + neach + (1 if i < extras else 0)
result.append(slice_axis(ary, axis, start, stop))
start = stop
return result
else:
result = List(capacity=(len(indices_or_sections) + 1))
prev = 0
for s in indices_or_sections:
result.append(slice_axis(ary, axis, prev, s))
prev = s
result.append(slice_axis(ary, axis, prev, ntotal))
return result
def split(ary, indices_or_sections, axis: int = 0):
ary = asarray(ary)
if isinstance(indices_or_sections, int):
sections = indices_or_sections
axis = util.normalize_axis_index(axis, ary.ndim)
N = ary.shape[axis]
if N % sections:
raise ValueError("array split does not result in an equal division")
return array_split(ary, indices_or_sections, axis)
def vsplit(ary, indices_or_sections):
ary = asarray(ary)
if staticlen(ary.shape) < 2:
compile_error("vsplit only works on arrays of 2 or more dimensions")
return split(ary, indices_or_sections, 0)
def hsplit(ary, indices_or_sections):
ary = asarray(ary)
if staticlen(ary.shape) == 0:
compile_error("hsplit only works on arrays of 1 or more dimensions")
return split(ary, indices_or_sections, 1 if ary.ndim > 1 else 0)
def dsplit(ary, indices_or_sections):
ary = asarray(ary)
if staticlen(ary.shape) < 3:
compile_error("dsplit only works on arrays of 3 or more dimensions")
return split(ary, indices_or_sections, 2)
def trim_zeros(filt, trim: str = 'fb'):
filt = asarray(filt)
if staticlen(filt.shape) != 1:
compile_error("trim_zeros() only takes 1-dimensional arrays as input")
fb = (trim == 'fb' or trim == 'bf')
just_f = (trim == 'f')
just_b = (trim == 'b')
trim_front = fb or just_f
trim_back = fb or just_b
if trim_front:
n = len(filt)
i = 0
while i < n and not filt[i]:
i += 1
filt = filt[i:]
if trim_back:
n = len(filt)
i = n - 1
while i >= 0 and not filt[i]:
i -= 1
filt = filt[:i+1]
return filt
def flip(m, axis = None):
m = asarray(m)
ndim: Static[int] = staticlen(m.shape)
if axis is None:
return flip(m, util.tuple_range(ndim))
elif isinstance(axis, int):
return flip(m, (axis,))
axis = util.normalize_axis_tuple(axis, ndim)
dtype = m.dtype
offset = 0
shape = m.shape
strides = m.strides
pshape = Ptr[int](__ptr__(shape).as_byte())
pstrides = Ptr[int](__ptr__(strides).as_byte())
for i in staticrange(ndim):
if i in axis:
st = pstrides[i]
offset += (pshape[i] - 1) * st
pstrides[i] = -st
return ndarray(shape, strides, Ptr[dtype](m.data.as_byte() + offset))
def fliplr(m):
m = asarray(m)
if staticlen(m.shape) < 2:
compile_error("Input must be >= 2-d.")
return flip(m, axis=1)
def flipud(m):
m = asarray(m)
if staticlen(m.shape) < 1:
compile_error("Input must be >= 1-d.")
return flip(m, axis=0)
def rot90(m, k: int = 1, axes: Tuple[int,int] = (0, 1)):
m = asarray(m)
ndim: Static[int] = staticlen(m.shape)
if axes[0] == axes[1] or abs(axes[0] - axes[1]) == ndim:
raise ValueError("Axes must be different.")
if (axes[0] >= ndim or axes[0] < -ndim
or axes[1] >= ndim or axes[1] < -ndim):
raise ValueError(f"Axes={axes} out of range for array of ndim={m.ndim}.")
k %= 4
if k == 0:
return m[:]
if k == 2:
return flip(flip(m, axes[0]), axes[1])
axes_list = util.tuple_range(ndim)
axes_list = util.tuple_swap(axes_list, axes[0], axes[1])
if k == 1:
return flip(m, axes[1]).transpose(axes_list)
else:
# k == 3
return flip(m.transpose(axes_list), axes[1])
def resize(a, new_shape):
if isinstance(new_shape, int):
return resize(a, (new_shape,))
a = asarray(a)
new_size = 1
for dim_length in new_shape:
new_size *= dim_length
if dim_length < 0:
raise ValueError('all elements of `new_shape` must be non-negative')
if a.size == 0 or new_size == 0:
# First case must zero fill. The second would have repeats == 0.
return zeros(new_shape, dtype=a.dtype)
new = empty(new_shape, dtype=a.dtype)
off = 0
go = True
while go:
for idx in util.multirange(a.shape):
p = a._ptr(idx)
new.data[off] = p[0]
off += 1
if off == new_size:
go = False
break
return new
def tile(A, reps):
if isinstance(reps, int):
return tile(A, (reps,))
A = asarray(A)
dtype = A.dtype
ndim: Static[int] = staticlen(A.shape)
d: Static[int] = staticlen(reps)
if ndim < d:
new_shape = ((1,) * (d - ndim)) + A.shape
return tile(A.reshape(new_shape), reps)
if ndim > d:
new_reps = ((1,) * (ndim - d)) + reps
return tile(A, new_reps)
out_shape = util.tuple_apply(int.__mul__, A.shape, reps)
new = empty(out_shape, dtype=dtype)
for idx1 in util.multirange(out_shape):
idx2 = util.tuple_apply(int.__mod__, idx1, A.shape)
q = new._ptr(idx1)
p = A._ptr(idx2)
q[0] = p[0]
return new
def roll(a, shift, axis = None):
a = asarray(a)
ndim: Static[int] = staticlen(a.shape)
if axis is None:
return roll(a.ravel(), shift, axis=0).reshape(a.shape)
if isinstance(axis, int):
return roll(a, shift, (axis,))
if isinstance(shift, int):
return roll(a, (shift,), axis)
na: Static[int] = staticlen(axis)
ns: Static[int] = staticlen(shift)
if na == 0:
compile_error("empty tuple given for 'axis'")
if ns == 0:
compile_error("empty tuple given for 'shift'")
if na == 1 and ns != 1:
return roll(a, shift, axis * ns)
if na != 1 and ns == 1:
return roll(a, shift * na, axis)
if staticlen(axis) != staticlen(shift):
compile_error("'shift' and 'axis' must be tuples of the same size")
axis = util.normalize_axis_tuple(axis, a.ndim, allow_duplicates=True)
shifts = (0,) * ndim
pshifts = Ptr[int](__ptr__(shifts).as_byte())
for i in staticrange(staticlen(axis)):
pshifts[axis[i]] += shift[i]
new = empty_like(a)
@pure
@llvm
def cdiv(a: int, b: int) -> int:
%0 = sdiv i64 %a, %b
ret i64 %0
def pymod(a: int, b: int):
d = cdiv(a, b)
m = a - d * b
if m and ((b ^ m) < 0):
m += b
return m
for idx1 in util.multirange(a.shape):
idx2 = util.tuple_apply(int.__add__, idx1, shifts)
idx2 = util.tuple_apply(pymod, idx2, a.shape)
p = a._ptr(idx1)
q = new._ptr(idx2)
q[0] = p[0]
return new
def _atleast_nd(a, ndim: Static[int]):
return array(a, ndmin=ndim, copy=False)
def _accumulate(values):
import itertools
return list(itertools.accumulate(values))
def _longest_shape(a):
if isinstance(a, ndarray):
return (0,) * staticlen(a.shape)
elif isinstance(a, List):
if len(a) == 0:
raise ValueError("Lists passed to block cannot be empty")
return _longest_shape(a[0])
elif isinstance(a, Tuple):
if staticlen(a) == 0:
compile_error("Tuples passed to block cannot be empty")
if staticlen(a) == 1:
return _longest_shape(a[0])
ls1 = _longest_shape(a[0])
ls2 = _longest_shape(a[1:])
if staticlen(ls1) > staticlen(ls2):
return ls1
else:
return ls2
else:
return ()
def _bottom_index(a):
if isinstance(a, ndarray):
return ()
elif isinstance(a, List):
return (0,) + _bottom_index(a[0])
elif isinstance(a, Tuple):
bi0 = _bottom_index(a[0])
for i in staticrange(1, staticlen(a)):
if staticlen(_bottom_index(a[i])) != staticlen(bi0):
compile_error("Depths of block argument are mismatched")
return (0,) + bi0
else:
return ()
def _final_size(a):
if isinstance(a, ndarray):
return a.size
elif isinstance(a, List) or isinstance(a, Tuple):
ans = 0
for x in a:
ans += _final_size(x)
return ans
else:
return 1
def _block(arrays, max_depth: Static[int], result_ndim: Static[int], depth: Static[int] = 0):
if depth < max_depth:
arrs = [_block(arr, max_depth, result_ndim, depth+1)
for arr in arrays]
return concatenate(arrs, axis=-(max_depth-depth))
else:
return _atleast_nd(arrays, result_ndim)
def _block_concatenate(arrays, list_ndim: Static[int], result_ndim: Static[int]):
result = _block(arrays, list_ndim, result_ndim)
if list_ndim == 0:
result = result.copy()
return result
def _concatenate_shapes(shapes, axis: Static[int]):
# Cache a result that will be reused.
shape_at_axis = [shape[axis] for shape in shapes]
# Take a shape, any shape
first_shape = shapes[0]
first_shape_pre = first_shape[:axis]
first_shape_post = first_shape[axis+1:]
if any(shape[:axis] != first_shape_pre or
shape[axis+1:] != first_shape_post for shape in shapes):
raise ValueError(
f"Mismatched array shapes in block along axis {axis}.")
shape = (first_shape_pre + (sum(shape_at_axis),) + first_shape[axis+1:])
offsets_at_axis = _accumulate(shape_at_axis)
slice_prefixes = [(slice(start, end),)
for start, end in zip([0] + offsets_at_axis,
offsets_at_axis)]
return shape, slice_prefixes
def _block_info_recursion(arrs, max_depth: Static[int], result_ndim: Static[int], depth: Static[int] = 0):
if depth < max_depth:
shapes = []
slices = []
arrays = []
for arr in arrs:
sh, sl, ar = _block_info_recursion(arr, max_depth, result_ndim, depth+1)
shapes.append(sh)
slices.append(sl)
arrays.append(ar)
axis: Static[int] = result_ndim - max_depth + depth
shape, slice_prefixes = _concatenate_shapes(shapes, axis)
# Prepend the slice prefix and flatten the slices
slices = [slice_prefix + the_slice
for slice_prefix, inner_slices in zip(slice_prefixes, slices)
for the_slice in inner_slices]
# Flatten the array list
arrays_flat = []
for arr in arrays:
for a in arr:
arrays_flat.append(a)
return shape, slices, arrays_flat
else:
arr = _atleast_nd(arrs, result_ndim)
return arr.shape, [()], [arr]
def _block_slicing(arrays, list_ndim: Static[int], result_ndim: Static[int]):
shape, slices, arrays = _block_info_recursion(
arrays, list_ndim, result_ndim)
dtype = arrays[0].dtype
C_order = True
F_order = True
for arr in arrays:
cc, fc = arr._contig
C_order = C_order and cc
F_order = F_order and fc
if not C_order and not F_order:
break
order = 'F' if F_order and not C_order else 'C'
result = empty(shape=shape, dtype=dtype, order=order)
for the_slice, arr in zip(slices, arrays):
result[(Ellipsis,) + the_slice] = arr
return result
def block(arrays):
ls = _longest_shape(arrays)
bi = _bottom_index(arrays)
final_size = _final_size(arrays)
list_ndim: Static[int] = staticlen(bi)
arr_ndim: Static[int] = staticlen(ls)
result_ndim: Static[int] = arr_ndim if arr_ndim > list_ndim else list_ndim
# Note: This is just the heuristic NumPy uses. I have not tested how
# good it is for Codon.
if list_ndim * final_size > (2 * 512 * 512):
return _block_slicing(arrays, list_ndim, result_ndim)
else:
return _block_concatenate(arrays, list_ndim, result_ndim)
def _close(a, b, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False):
A = type(a)
B = type(b)
if A is not B:
C = type(util.coerce(A, B))
if (C is not float and
C is not float32 and
C is not float16 and
C is not complex and
C is not complex64):
return _close(util.to_float(a),
util.to_float(b),
rtol=rtol,
atol=atol,
equal_nan=equal_nan)
else:
return _close(util.cast(a, C),
util.cast(b, C),
rtol=rtol,
atol=atol,
equal_nan=equal_nan)
elif (A is not float and
A is not float32 and
A is not float16 and
A is not complex and
A is not complex64):
return _close(util.to_float(a),
util.to_float(b),
rtol=rtol,
atol=atol,
equal_nan=equal_nan)
if A is float or A is float32 or A is float16:
fin_a = util.isfinite(a)
fin_b = util.isfinite(b)
if fin_a and fin_b:
return abs(a - b) <= A(atol) + A(rtol) * abs(b)
else:
nan_a = util.isnan(a)
nan_b = util.isnan(b)
if nan_a or nan_b:
if equal_nan:
return nan_a and nan_b
else:
return False
else:
return a == b
else: # complex or complex64
R = type(a.real)
fin_a = util.isfinite(a.real) and util.isfinite(a.imag)
fin_b = util.isfinite(b.real) and util.isfinite(b.imag)
if fin_a and fin_b:
return abs(a - b) <= R(atol) + R(rtol) * abs(b)
else:
nan_a = util.isnan(a.real) or util.isnan(a.imag)
nan_b = util.isnan(b.real) or util.isnan(b.imag)
if nan_a or nan_b:
if equal_nan:
return nan_a and nan_b
else:
return False
else:
return a == b
def isclose(a, b, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False):
a = asarray(a)
b = asarray(b)
if a.ndim == 0 and b.ndim == 0:
return _close(a.item(), b.item(), rtol=rtol, atol=atol, equal_nan=equal_nan)
ans_shape = broadcast_shapes(a.shape, b.shape)
ans = empty(ans_shape, bool)
for idx in util.multirange(ans_shape):
xa = a._ptr(idx, broadcast=True)[0]
xb = b._ptr(idx, broadcast=True)[0]
xans = _close(xa, xb, rtol=rtol, atol=atol, equal_nan=equal_nan)
ans._ptr(idx)[0] = xans
return ans
def allclose(a, b, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False):
a = asarray(a)
b = asarray(b)
if a.ndim == 0 and b.ndim == 0:
return _close(a.item(), b.item(), rtol=rtol, atol=atol, equal_nan=equal_nan)
ans_shape = broadcast_shapes(a.shape, b.shape)
for idx in util.multirange(ans_shape):
xa = a._ptr(idx, broadcast=True)[0]
xb = b._ptr(idx, broadcast=True)[0]
if not _close(xa, xb, rtol=rtol, atol=atol, equal_nan=equal_nan):
return False
return True
def array_equal(a1, a2, equal_nan: bool = False):
from .ndmath import isnan
a1 = asarray(a1)
a2 = asarray(a2)
if a1.ndim != a2.ndim:
return False
if a1.shape != a2.shape:
return False
dtype = type(util.coerce(a1.dtype, a2.dtype))
if a1._contig_match(a2):
p1 = a1.data
p2 = a2.data
n = a1.size
for i in range(n):
e1 = util.cast(p1[i], dtype)
e2 = util.cast(p2[i], dtype)
if equal_nan:
if e1 != e2 and not (isnan(e1) and isnan(e2)):
return False
else:
if e1 != e2:
return False
else:
for idx in util.multirange(a1.shape):
e1 = util.cast(a1._ptr(idx)[0], dtype)
e2 = util.cast(a2._ptr(idx)[0], dtype)
if equal_nan:
if e1 != e2 and not (isnan(e1) and isnan(e2)):
return False
else:
if e1 != e2:
return False
return True
def array_equiv(a1, a2):
def can_broadcast(s1, s2):
if staticlen(s1) > staticlen(s2):
return can_broadcast(s1[-staticlen(s2):], s2)
elif staticlen(s1) < staticlen(s2):
return can_broadcast(s1, s2[-staticlen(s1):])
else:
for i in staticrange(staticlen(s1)):
d1 = s1[i]
d2 = s2[i]
if d1 != d2 and not (d1 == 1 or d2 == 1):
return False
return True
a1 = asarray(a1)
a2 = asarray(a2)
if not can_broadcast(a1.shape, a2.shape):
return False
bshape = broadcast_shapes(a1.shape, a2.shape)
dtype = type(util.coerce(a1.dtype, a2.dtype))
for idx in util.multirange(bshape):
e1 = util.cast(a1._ptr(idx, broadcast=True)[0], dtype)
e2 = util.cast(a2._ptr(idx, broadcast=True)[0], dtype)
if e1 != e2:
return False
return True
def squeeze(a, axis = None):
if axis is None:
compile_error("squeeze() must specify 'axis' argument in Codon-NumPy")
if isinstance(axis, int):
return squeeze(a, (axis,))
if not isinstance(axis, Tuple):
compile_error("'axis' must be an int or a tuple of ints")
a = asarray(a)
axis = util.normalize_axis_tuple(axis, a.ndim)
shape = a.shape
strides = a.strides
new_shape = (0,) * (staticlen(shape) - staticlen(axis))
new_strides = (0,) * (staticlen(shape) - staticlen(axis))
pnew_shape = Ptr[int](__ptr__(new_shape).as_byte())
pnew_strides = Ptr[int](__ptr__(new_strides).as_byte())
j = 0
for i in staticrange(staticlen(shape)):
if i in axis:
if shape[i] != 1:
raise ValueError("cannot select an axis to squeeze out which has size not equal to one")
else:
pnew_shape[j] = shape[i]
pnew_strides[j] = strides[i]
j += 1
return ndarray(new_shape, new_strides, a.data)
def pad(array, pad_width, mode = 'constant', **kwargs):
from .ndmath import isnan
def unpack_params(x, iaxis: int, name: Static[str]):
if isinstance(x, Tuple):
if staticlen(x) == 1:
if isinstance(x[0], Tuple):
if staticlen(x[0]) == 1:
return x[0][0], x[0][0]
elif staticlen(x[0]) == 2:
return x[0]
else:
compile_error("invalid parameter '" + name + "' given")
else:
return x[0], x[0]
elif staticlen(x) == 2:
if isinstance(x[0], Tuple):
return x[iaxis]
else:
return x
else:
return x[iaxis]
else:
return x, x
def unpack_stat_length(k: int, iaxis: int, kwargs):
stat_length = kwargs.get('stat_length', k)
s1, s2 = unpack_params(stat_length, iaxis, 'stat_length')
if s1 <= 0 or s2 <= 0:
raise ValueError("'stat_length' must contain positive values")
s1 = min(s1, k)
s2 = min(s2, k)
return s1, s2
def round_if_needed(x, dtype: type):
if dtype is int or isinstance(dtype, Int) or isinstance(dtype, UInt):
return util.cast(util.rint(x), dtype)
else:
return util.cast(x, dtype)
def pad_from_function(a: ndarray, pw, padding_func, kwargs, extra = None):
shape = a.shape
strides = a.strides
ndim: Static[int] = staticlen(shape)
for i in staticrange(ndim):
length = shape[i]
stride = strides[i]
for idx in util.multirange(util.tuple_delete(shape, i)):
p = a._ptr(util.tuple_insert(idx, i, 0))
vector = ndarray((length,), (stride,), p)
padding_func(vector, pw[i], i, kwargs, extra)
def pad_constant(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
p1, p2 = iaxis_pad_width
cval = kwargs.get('constant_values', 0)
dtype = vector.dtype
c1, c2 = unpack_params(cval, iaxis, 'constant_values')
n = vector.size
# Note we don't need to do range checks since padded
# array size will always ensure we're not out of bounds,
# as long as each `iaxis_pad_width` is non-negative.
for i in range(0, p1):
vector._ptr((i,))[0] = util.cast(c1, dtype)
for i in range(n - p2, n):
vector._ptr((i,))[0] = util.cast(c2, dtype)
def pad_wrap(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
p1, p2 = iaxis_pad_width
n = vector.size
k = n - (p1 + p2)
for i in range(n - p2, n):
vector._ptr((i,))[0] = vector._ptr((i - k,))[0]
for i in range(p1 - 1, -1, -1):
vector._ptr((i,))[0] = vector._ptr((i + k,))[0]
def pad_edge(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
p1, p2 = iaxis_pad_width
n = vector.size
c1 = vector._ptr((p1,))[0]
c2 = vector._ptr((n - 1 - p2,))[0]
for i in range(0, p1):
vector._ptr((i,))[0] = c1
for i in range(n - p2, n):
vector._ptr((i,))[0] = c2
def pad_max(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
p1, p2 = iaxis_pad_width
n = vector.size
k = n - (p1 + p2)
s1, s2 = unpack_stat_length(k, iaxis, kwargs)
m1 = vector._ptr((p1,))[0]
for i in range(p1 + 1, p1 + s1):
e = vector._ptr((i,))[0]
if e > m1:
m1 = e
if s1 == k and s2 == k:
m2 = m1
else:
m2 = vector._ptr((n - p2 - s2,))[0]
for i in range(n - p2 - s2 + 1, n - p2):
e = vector._ptr((i,))[0]
if e > m2:
m2 = e
for i in range(0, p1):
vector._ptr((i,))[0] = m1
for i in range(n - p2, n):
vector._ptr((i,))[0] = m2
def pad_min(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
p1, p2 = iaxis_pad_width
n = vector.size
k = n - (p1 + p2)
s1, s2 = unpack_stat_length(k, iaxis, kwargs)
m1 = vector._ptr((p1,))[0]
for i in range(p1 + 1, p1 + s1):
e = vector._ptr((i,))[0]
if e < m1:
m1 = e
if s1 == k and s2 == k:
m2 = m1
else:
m2 = vector._ptr((n - p2 - s2,))[0]
for i in range(n - p2 - s2 + 1, n - p2):
e = vector._ptr((i,))[0]
if e < m2:
m2 = e
for i in range(0, p1):
vector._ptr((i,))[0] = m1
for i in range(n - p2, n):
vector._ptr((i,))[0] = m2
def pad_mean(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
p1, p2 = iaxis_pad_width
n = vector.size
k = n - (p1 + p2)
s1, s2 = unpack_stat_length(k, iaxis, kwargs)
m1 = vector._ptr((p1,))[0]
for i in range(p1 + 1, p1 + s1):
m1 += vector._ptr((i,))[0]
if s1 == k and s2 == k:
m2 = m1
else:
m2 = vector._ptr((n - p2 - s2,))[0]
for i in range(n - p2 - s2 + 1, n - p2):
m2 += vector._ptr((i,))[0]
dtype = vector.dtype
av1 = round_if_needed(m1 / util.cast(s1, dtype), dtype)
av2 = round_if_needed(m2 / util.cast(s2, dtype), dtype)
for i in range(0, p1):
vector._ptr((i,))[0] = av1
for i in range(n - p2, n):
vector._ptr((i,))[0] = av2
def pad_median(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
p1, p2 = iaxis_pad_width
n = vector.size
k = n - (p1 + p2)
s1, s2 = unpack_stat_length(k, iaxis, kwargs)
buf = extra
nan_idx = -1
for i in range(p1, p1 + s1):
e = vector._ptr((i,))[0]
if isnan(e):
nan_idx = i
break
else:
buf[i - p1] = e
dtype = vector.dtype
if nan_idx >= 0:
m1 = vector._ptr((nan_idx,))[0]
else:
m1a, m1b = util.median(buf, s1)
if m1a == m1b:
m1 = m1a
else:
m1 = round_if_needed((m1a + m1b) / util.cast(2, dtype), dtype)
if s1 == k and s2 == k:
m2 = m1
else:
nan_idx = -1
base = n - p2 - s2
for i in range(base, n - p2):
e = vector._ptr((i,))[0]
if isnan(e):
nan_idx = i
break
else:
buf[i - base] = e
if nan_idx >= 0:
m2 = vector._ptr((nan_idx,))[0]
else:
m2a, m2b = util.median(buf, s2)
if m2a == m2b:
m2 = m2a
else:
m2 = round_if_needed((m2a + m2b) / util.cast(2, dtype), dtype)
for i in range(0, p1):
vector._ptr((i,))[0] = m1
for i in range(n - p2, n):
vector._ptr((i,))[0] = m2
def pad_linear_ramp(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
def fill_linear(vec: ndarray, offset: int, start: float,
stop: float, num: int, rev: bool):
if num == 0:
return
dtype = vec.dtype
delta = stop - start
step = delta / num
for i in range(num):
j = num - 1 - i if rev else i
p = vec._ptr((j + offset,))
e = ((i / num) * delta) + start if not step else (i * step) + start
p[0] = round_if_needed(e, dtype)
p1, p2 = iaxis_pad_width
n = vector.size
end_values = kwargs.get('end_values', 0)
start1, end2 = unpack_params(end_values, iaxis, 'end_values')
end1 = vector._ptr((p1,))[0]
start2 = vector._ptr((n - 1 - p2,))[0]
fill_linear(vector,
offset=0,
start=util.cast(start1, float),
stop=util.cast(end1, float),
num=p1,
rev=False)
fill_linear(vector,
offset=(n - p2),
start=util.cast(end2, float),
stop=util.cast(start2, float),
num=p2,
rev=True)
def pad_reflect_or_symmetric(vector: ndarray,
iaxis_pad_width: Tuple[int, int],
iaxis: int,
kwargs,
extra):
p1, p2 = iaxis_pad_width
n = vector.size
k = n - (p1 + p2)
diff = extra
if k == 1:
e = vector._ptr((p1,))[0]
for i in range(0, p1):
vector._ptr((i,))[0] = e
for i in range(n - p2, n):
vector._ptr((i,))[0] = e
return
even = kwargs.get('reflect_type', 'even') != 'odd'
# left side
i = p1 - 1
while i >= 0:
z = i
edge = vector._ptr((z + 1,))[0]
for j in range(k - diff):
e = vector._ptr((z + j + (1 + diff),))[0]
if not even:
e = (edge + edge) - e
vector._ptr((i,))[0] = e
i -= 1
if i < 0:
break
# right side
i = n - p2
while i < n:
z = i
edge = vector._ptr((z - 1,))[0]
for j in range(k - diff):
e = vector._ptr((z - j - (1 + diff),))[0]
if not even:
e = (edge + edge) - e
vector._ptr((i,))[0] = e
i += 1
if i >= n:
break
a = asarray(array)
s = a.shape
dtype = a.dtype
ndim: Static[int] = staticlen(s)
if ndim == 0:
return a
if isinstance(mode, str):
if a.size == 0 and mode not in ('empty', 'constant'):
raise ValueError("can't pad empty array using modes other than 'constant' or 'empty'")
if isinstance(pad_width, int):
pw = ((pad_width, pad_width),) * ndim
elif isinstance(pad_width, Tuple[int]):
pw = ((pad_width[0], pad_width[0]),) * ndim
elif isinstance(pad_width, Tuple[int, int]):
pw = (pad_width,) * ndim
elif isinstance(pad_width, Tuple[Tuple[int, int]]):
pw = (pad_width[0],) * ndim
elif staticlen(pad_width) == 1:
pw = pad_width * ndim
else:
pw = pad_width
if staticlen(pw) != ndim:
compile_error("invalid pad_width")
for p in pw:
if p[0] < 0 or p[1] < 0:
raise ValueError("padding can't be negative")
new_shape = tuple(s[i] + pw[i][0] + pw[i][1] for i in staticrange(ndim))
if isinstance(mode, str):
ans = empty(new_shape, dtype)
else:
ans = zeros(new_shape, dtype)
# copy in original array
for idx in util.multirange(s):
idx1 = tuple(idx[i] + pw[i][0] for i in staticrange(ndim))
p = a._ptr(idx)
q = ans._ptr(idx1)
q[0] = p[0]
if isinstance(mode, str):
if mode == 'empty':
pass # do nothing
elif mode == 'constant':
pad_from_function(ans, pw, pad_constant, kwargs)
elif mode == 'wrap':
pad_from_function(ans, pw, pad_wrap, kwargs)
elif mode == 'edge':
pad_from_function(ans, pw, pad_edge, kwargs)
elif mode == 'linear_ramp':
pad_from_function(ans, pw, pad_linear_ramp, kwargs)
elif mode == 'maximum':
pad_from_function(ans, pw, pad_max, kwargs)
elif mode == 'minimum':
pad_from_function(ans, pw, pad_min, kwargs)
elif mode == 'mean':
pad_from_function(ans, pw, pad_mean, kwargs)
elif mode == 'median':
buf_size = 0
for iaxis in range(ndim):
s1, s2 = unpack_stat_length(s[iaxis], iaxis, kwargs)
if s1 > buf_size:
buf_size = s1
if s2 > buf_size:
buf_size = s2
buf = Ptr[dtype](buf_size)
pad_from_function(ans, pw, pad_median, kwargs, extra=buf)
elif mode == 'reflect':
pad_from_function(ans, pw, pad_reflect_or_symmetric, kwargs, extra=1)
elif mode == 'symmetric':
pad_from_function(ans, pw, pad_reflect_or_symmetric, kwargs, extra=0)
else:
raise ValueError(f"mode {repr(mode)} is not supported")
else:
pad_from_function(ans, pw, mode, kwargs)
return ans
#############
# Searching #
#############
def nonzero(a):
a = asarray(a)
if staticlen(a.shape) == 0:
# Note: this is technically deprecated behavior
if bool(a.item()):
ans = empty(1, int)
ans.data[0] = 0
return (ans,)
else:
return (empty(0, int),)
else:
num_true = 0
if a._is_contig:
for i in range(a.size):
if bool(a.data[i]):
num_true += 1
else:
for idx in util.multirange(a.shape):
if bool(a._ptr(idx)[0]):
num_true += 1
ans = tuple(empty(num_true, int) for _ in a.shape)
k = 0
for idx in util.multirange(a.shape):
if bool(a._ptr(idx)[0]):
for i in staticrange(staticlen(ans)):
ans[i].data[k] = idx[i]
k += 1
return ans
def flatnonzero(a):
a = asarray(a)
if staticlen(a.shape) <= 1:
return nonzero(a)[0]
else:
sz = a.size
cc, fc = a._contig
num_true = 0
if cc or fc:
for i in range(sz):
if bool(a.data[i]):
num_true += 1
else:
for idx in util.multirange(a.shape):
if bool(a._ptr(idx)[0]):
num_true += 1
ans = empty(num_true, int)
k = 0
if cc:
for i in range(sz):
if bool(a.data[i]):
ans.data[k] = i
k += 1
else:
j = 0
for idx in util.multirange(a.shape):
if bool(a._ptr(idx)[0]):
ans.data[k] = j
k += 1
j += 1
return ans
def argwhere(a):
a = asarray(a)
if staticlen(a.shape) == 0:
m = 1 if bool(a.item()) else 0
return empty((m, 0), int)
else:
num_true = 0
if a._is_contig:
for i in range(a.size):
if bool(a.data[i]):
num_true += 1
else:
for idx in util.multirange(a.shape):
if bool(a._ptr(idx)[0]):
num_true += 1
ans = empty((num_true, a.ndim), int)
k = 0
for idx in util.multirange(a.shape):
if bool(a._ptr(idx)[0]):
for i in range(a.ndim):
ans._ptr((k, i))[0] = idx[i]
k += 1
return ans
def where(condition, x, y):
condition = asarray(condition)
x = asarray(x)
y = asarray(y)
T = type(util.coerce(x.dtype, y.dtype))
if condition._contig_match(x) and condition._contig_match(y):
cc, fc = condition._contig
ans = empty(condition.shape, dtype=T, order=('C' if cc else 'F'))
sz = condition.size
pc = condition.data
px = x.data
py = y.data
q = ans.data
for i in range(sz):
if pc[i]:
q[i] = util.cast(px[i], T)
else:
q[i] = util.cast(py[i], T)
return ans
else:
bshape = broadcast_shapes(condition.shape, x.shape, y.shape)
ans = empty(bshape, dtype=T)
for idx in util.multirange(bshape):
q = ans._ptr(idx)
if condition._ptr(idx, broadcast=True)[0]:
q[0] = util.cast(x._ptr(idx, broadcast=True)[0], T)
else:
q[0] = util.cast(y._ptr(idx, broadcast=True)[0], T)
return ans
@overload
def where(condition):
return nonzero(asarray(condition))
def extract(condition, arr):
condition = ravel(asarray(condition))
arr = ravel(asarray(arr))
num_true = 0
for i in range(condition.size):
if condition.data[i]:
num_true += 1
if num_true > arr.size:
raise IndexError(f"index {num_true} is out of bounds for axis 0 with size {arr.size}")
ans = empty(num_true, arr.dtype)
k = 0
i = 0
m = min(arr.size, condition.size)
while i < m and k < num_true:
if condition.data[i]:
ans.data[k] = arr.data[i]
k += 1
i += 1
return ans
def _less_than(a, b):
T = type(util.coerce(type(a), type(b)))
return util.cast(a, T) < util.cast(b, T)
def _bisect_left(p: Ptr[T], s: int, n: int, x: X, lo: int, hi: int, T: type, X: type):
while lo < hi:
mid = (lo + hi) >> 1
elem = Ptr[T](p.as_byte() + (mid * s))[0]
if _less_than(elem, x):
lo = mid + 1
else:
hi = mid
return lo, hi
def _bisect_right(p: Ptr[T], s: int, n: int, x: X, lo: int, hi: int, T: type, X: type):
while lo < hi:
mid = (lo + hi) >> 1
elem = Ptr[T](p.as_byte() + (mid * s))[0]
if _less_than(x, elem):
hi = mid
else:
lo = mid + 1
return lo, hi
def _bisect_left_perm(p: Ptr[T], s: int, n: int, x: X, perm: Ptr[int],
sp: int, lo: int, hi: int, T: type, X: type):
while lo < hi:
mid = (lo + hi) >> 1
mid_remap = Ptr[int](perm.as_byte() + (mid * sp))[0]
elem = Ptr[T](p.as_byte() + (mid_remap * s))[0]
if _less_than(elem, x):
lo = mid + 1
else:
hi = mid
return lo, hi
def _bisect_right_perm(p: Ptr[T], s: int, n: int, x: X, perm: Ptr[int],
sp: int, lo: int, hi: int, T: type, X: type):
while lo < hi:
mid = (lo + hi) >> 1
mid_remap = Ptr[int](perm.as_byte() + (mid * sp))[0]
elem = Ptr[T](p.as_byte() + (mid_remap * s))[0]
if _less_than(x, elem):
hi = mid
else:
lo = mid + 1
return lo, hi
def searchsorted(a, v, side: str = 'left', sorter = None):
a = asarray(a)
v = asarray(v)
if staticlen(a.shape) != 1:
compile_error("searchsorted requires 1-dimensional input array")
n = a.size
s = a.strides[0]
left = False
if side == 'left':
left = True
elif side == 'right':
left = False
else:
raise ValueError(f"side must be 'left' or 'right' (got {repr(side)})")
if sorter is None:
perm = None
sp = 0
else:
perm = asarray(sorter)
if staticlen(perm.shape) != 1:
compile_error("sorter argument must be 1-dimensional")
if perm.dtype is not int:
compile_error("sorter must only contain integers")
if perm.size != n:
raise ValueError("sorter.size must equal a.size")
for sorter_idx in perm:
if sorter_idx < 0 or sorter_idx >= n:
raise ValueError("Sorter index out of range.")
sp = perm.strides[0]
# copy for large needles to improve cache performance
if v.size > a.size:
a = ascontiguousarray(a)
s = a.strides[0]
if staticlen(v.shape) == 0:
x = v.item()
if sorter is None:
if left:
return _bisect_left(a.data, s, n, x, lo=0, hi=n)[0]
else:
return _bisect_right(a.data, s, n, x, lo=0, hi=n)[0]
else:
if left:
return _bisect_left_perm(a.data, s, n, x, perm.data, sp, lo=0, hi=n)[0]
else:
return _bisect_right_perm(a.data, s, n, x, perm.data, sp, lo=0, hi=n)[0]
else:
ans = empty(v.shape, int)
if v.size == 0:
return ans
last_x = v._ptr((0,) * staticlen(v.shape))[0]
lo = 0
hi = n
for idx in util.multirange(v.shape):
x = v._ptr(idx)[0]
if last_x < x:
hi = n
else:
lo = 0
hi = hi + 1 if hi < n else n
last_x = x
if sorter is None:
if left:
lo, hi = _bisect_left(a.data, s, n, x, lo=lo, hi=hi)
else:
lo, hi = _bisect_right(a.data, s, n, x, lo=lo, hi=hi)
else:
if left:
lo, hi = _bisect_left_perm(a.data, s, n, x, perm.data, sp, lo=lo, hi=hi)
else:
lo, hi = _bisect_right_perm(a.data, s, n, x, perm.data, sp, lo=lo, hi=hi)
ans._ptr(idx)[0] = lo
return ans
############
# Indexing #
############
def take(a, indices, axis = None, out = None, mode: str = 'raise'):
a = atleast_1d(asarray(a, order='C'))
if axis is None:
return take(a.ravel(), indices, axis=0, out=out, mode=mode)
indices = asarray(indices, order='C')
axis = util.normalize_axis_index(axis, a.ndim)
n = 1
m = 1
chunk = 1
nd: Static[int] = staticlen(a.shape) + staticlen(indices.shape) - 1
shape = (0,) * nd
ashape0 = a.shape
ishape0 = indices.shape
ashape = Ptr[int](__ptr__(ashape0).as_byte())
ishape = Ptr[int](__ptr__(ishape0).as_byte())
pshape = Ptr[int](__ptr__(shape).as_byte())
for i in staticrange(nd):
if i < axis:
pshape[i] = ashape[i]
n *= pshape[i]
else:
if i < axis + indices.ndim:
pshape[i] = ishape[i - axis]
m *= pshape[i]
else:
pshape[i] = ashape[i - indices.ndim + 1]
chunk *= pshape[i]
if out is None:
res = empty(shape, dtype=a.dtype)
elif isinstance(out, ndarray):
if staticlen(out.shape) != staticlen(shape) or out.dtype is not a.dtype:
compile_error("output array does not match result of ndarray.take")
if out.shape != shape:
raise ValueError("output array does not match result of ndarray.take")
res = asarray(out, order='C')
else:
compile_error("output must be an array")
max_item = a.shape[axis]
nelem = chunk
itemsize = res.itemsize
chunk *= itemsize
src = a.data.as_byte()
dest = res.data.as_byte()
indices_data = indices.data
if max_item == 0 and res.size != 0:
raise IndexError("cannot do a non-empty take from an empty axes.")
if mode == 'raise':
for i in range(n):
for j in range(m):
tmp = indices_data[j]
tmp = util.normalize_index(tmp, axis, max_item)
tmp_src = src + tmp * chunk
str.memcpy(dest, tmp_src, chunk)
dest += chunk
src += chunk * max_item
elif mode == 'wrap':
for i in range(n):
for j in range(m):
tmp = indices_data[j]
if tmp < 0:
while tmp < 0:
tmp += max_item
elif tmp >= max_item:
while tmp >= max_item:
tmp -= max_item
tmp_src = src + tmp * chunk
str.memcpy(dest, tmp_src, chunk)
dest += chunk
src += chunk * max_item
elif mode == 'clip':
for i in range(n):
for j in range(m):
tmp = indices_data[j]
if tmp < 0:
tmp = 0
elif tmp >= max_item:
tmp = max_item - 1
tmp_src = src + tmp * chunk
str.memcpy(dest, tmp_src, chunk)
dest += chunk
src += chunk * max_item
else:
raise ValueError(f"clipmode must be one of 'clip', 'raise', or 'wrap' (got {repr(mode)})")
if res.ndim == 0:
return res.item()
else:
return res
def indices(dimensions, dtype: type = int, sparse: Static[int] = False):
if not isinstance(dimensions, Tuple):
compile_error("dimensions must be a tuple of integers")
N: Static[int] = staticlen(dimensions)
shape = (1,) * N
if sparse:
return tuple(
arange(dimensions[i], dtype=dtype).reshape(
shape[:i] + (dimensions[i],) + shape[i+1:]) for i in staticrange(N))
res = empty((N,) + dimensions, dtype=dtype)
for i in staticrange(N):
dim = dimensions[i]
idx = arange(dim, dtype=dtype).reshape(shape[:i] + (dim,) + shape[i+1:])
res[i] = idx
return res
def ix_(*args):
def ix_one(new, nd: Static[int], k: Static[int]):
new = asarray(new)
if staticlen(new.shape) != 1:
compile_error("Cross index must be 1 dimensional")
if new.dtype is bool:
newx = new.nonzero()[0]
else:
newx = new
return newx.reshape((1,)*k + (newx.size,) + (1,)*(nd-k-1))
nd: Static[int] = staticlen(args)
return tuple(ix_one(args[k], nd, k) for k in staticrange(nd))
def ravel_multi_index(multi_index, dims, mode: str = 'raise', order: str = 'C'):
def fix_index(idx: int, axis: int, dim: int, mode: str):
if mode == 'raise':
if idx < 0 or idx >= dim:
raise ValueError("invalid entry in coordinates array")
elif mode == 'wrap':
if idx < 0:
while idx < 0:
idx += dim
elif idx >= dim:
while idx >= dim:
idx -= dim
elif mode == 'clip':
if idx < 0:
idx = 0
elif idx >= dim:
idx = dim - 1
return idx
def gather_non_ints(x):
if staticlen(x) == 0:
return ()
i = x[0]
rest = gather_non_ints(x[1:])
if isinstance(i, int):
return rest
else:
return (i,) + rest
if isinstance(dims, int):
return ravel_multi_index(multi_index, (dims,), mode=mode, order=order)
for d in dims:
if d <= 0:
raise ValueError("dimensions must be positive")
if mode not in ('raise', 'wrap', 'clip'):
raise ValueError(f"clipmode must be one of 'clip', 'raise', or 'wrap' (got {repr(mode)})")
corder = True
if order == 'C':
corder = True
elif order == 'F':
corder = False
else:
raise ValueError("only 'C' or 'F' order is permitted")
N: Static[int] = staticlen(dims)
if isinstance(multi_index, List[int]):
if len(multi_index) != N:
raise ValueError(f"parameter multi_index must be a sequence of length {N}")
idx = tuple(fix_index(multi_index[j], j, dims[j], mode) for j in staticrange(N))
return util.coords_to_index(idx, dims) if corder else util.coords_to_findex(idx, dims)
elif isinstance(multi_index, Tuple):
if staticlen(gather_non_ints(multi_index)) == 0:
if staticlen(multi_index) != staticlen(dims):
compile_error("parameter multi_index does not match dims in size")
idx = tuple(fix_index(multi_index[j], j, dims[j], mode) for j in staticrange(N))
return util.coords_to_index(idx, dims) if corder else util.coords_to_findex(idx, dims)
midx = vstack(multi_index)
else:
midx = asarray(multi_index)
if staticlen(midx.shape) != 2:
compile_error("multi_index must be 2 dimensional")
if len(multi_index) != N:
raise ValueError(f"parameter multi_index must be a sequence of length {N}")
ans = empty(midx.shape[1], int)
for i in range(ans.size):
idx = tuple(midx._ptr((j, i))[0] for j in staticrange(N))
idx = tuple(fix_index(idx[j], j, dims[j], mode) for j in staticrange(N))
res = util.coords_to_index(idx, dims) if corder else util.coords_to_findex(idx, dims)
ans.data[i] = res
return ans
def unravel_index(indices, shape, order: str = 'C'):
def check(idx: int, n: int):
if idx < 0 or idx >= n:
raise ValueError(f"index {idx} is out of bounds for array with size {n}")
return idx
if isinstance(shape, int):
return unravel_index(indices, (shape,), order=order)
corder = True
if order == 'C':
corder = True
elif order == 'F':
corder = False
else:
raise ValueError("only 'C' or 'F' order is permitted")
N: Static[int] = staticlen(shape)
if N == 0:
if not isinstance(indices, int):
compile_error("multiple indices are not supported for 0d arrays")
check(indices, 1)
return ()
n = 1
for s in shape:
if s < 0:
raise ValueError("dimensions must be non-negative")
n *= s
if isinstance(indices, int):
check(indices, n)
return util.index_to_coords(indices, shape) if corder else util.index_to_fcoords(indices, shape)
indices = asarray(indices)
N: Static[int] = staticlen(shape)
ans = tuple(empty(indices.shape, int) for _ in shape)
for idx in util.multirange(indices.shape):
index = indices._ptr(idx)[0]
check(index, n)
res = util.index_to_coords(index, shape) if corder else util.index_to_fcoords(index, shape)
for i in staticrange(N):
ans[i]._ptr(idx)[0] = res[i]
return ans
def diag_indices(n: int, ndim: int):
idx = arange(n)
return [idx for _ in range(ndim)]
@overload
def diag_indices(n: int, ndim: Static[int] = 2):
idx = arange(n)
return (idx,) * ndim
def diag_indices_from(arr):
arr = asarray(arr)
shape = arr.shape
ndim: Static[int] = staticlen(shape)
if ndim < 2:
compile_error("input array must be at least 2-d")
for i in staticrange(1, ndim):
if shape[i] != shape[0]:
raise ValueError("All dimensions of input must be of equal length")
return diag_indices(shape[0], ndim)
def mask_indices(n: int, mask_func, k = 0):
m = ones((n, n), int)
a = mask_func(m, k)
return nonzero(a != 0)
def tril_indices(n: int, k: int = 0, m: Optional[int] = None):
tri_ = tri(n, m, k=k, dtype=bool)
return tuple(broadcast_to(inds, tri_.shape)[tri_]
for inds in indices(tri_.shape, sparse=True))
def tril_indices_from(arr, k: int = 0):
arr = asarray(arr)
if staticlen(arr.shape) != 2:
compile_error("input array must be 2-d")
return tril_indices(arr.shape[-2], k=k, m=arr.shape[-1])
def triu_indices(n: int, k: int = 0, m: Optional[int] = None):
tri_ = ~tri(n, m, k=k - 1, dtype=bool)
return tuple(broadcast_to(inds, tri_.shape)[tri_]
for inds in indices(tri_.shape, sparse=True))
def triu_indices_from(arr, k: int = 0):
arr = asarray(arr)
if staticlen(arr.shape) != 2:
compile_error("input array must be 2-d")
return triu_indices(arr.shape[-2], k=k, m=arr.shape[-1])
def take_along_axis(arr, indices, axis):
def dim_mismatch():
compile_error("`indices` and `arr` must have the same number of dimensions")
arr = asarray(arr)
indices = asarray(indices)
if indices.dtype is not int:
compile_error("`indices` must be an integer array")
if axis is None:
if staticlen(indices.shape) != 1:
dim_mismatch()
out = empty(indices.size, arr.dtype)
for i in range(indices.size):
out.data[i] = arr._get_flat(indices._ptr((i,))[0], check=True)
return out
if staticlen(arr.shape) != staticlen(indices.shape):
dim_mismatch()
axis = util.normalize_axis_index(axis, arr.ndim)
M = arr.shape[axis]
J = indices.shape[axis]
bshape = broadcast_shapes(util.tuple_delete(arr.shape, axis),
util.tuple_delete(indices.shape, axis))
out_shape = util.tuple_insert(bshape, axis, J)
out = empty(out_shape, arr.dtype)
a_stride = arr.strides[axis]
indices_stride = indices.strides[axis]
out_stride = out.strides[axis]
for idx in util.multirange(bshape):
base_idx = util.tuple_insert(idx, axis, 0)
a_1d = ndarray((M,), (a_stride,), arr._ptr(base_idx, broadcast=True))
indices_1d = ndarray((J,), (indices_stride,), indices._ptr(base_idx, broadcast=True))
out_1d = ndarray((J,), (out_stride,), out._ptr(base_idx))
for j in range(J):
out_1d._ptr((j,))[0] = a_1d[indices_1d._ptr((j,))[0]]
return out
def choose(a, choices, out = None, mode: str = 'raise'):
MODE_RAISE: Static[int] = 0
MODE_WRAP : Static[int] = 1
MODE_CLIP : Static[int] = 2
a = asarray(a)
if a.dtype is not int:
compile_error("first argument must be an integer array")
xmode = 0
if mode == 'raise':
xmode = MODE_RAISE
elif mode == 'wrap':
xmode = MODE_WRAP
elif mode == 'clip':
xmode = MODE_CLIP
else:
raise ValueError(f"clipmode must be one of 'clip', 'raise', or 'wrap' (got {repr(mode)})")
n = len(choices)
if n == 0:
raise ValueError("0-length sequence.")
if isinstance(choices, Tuple):
xchoices1 = tuple(asarray(c) for c in choices)
bshape = broadcast_shapes(*(tuple(c.shape for c in xchoices1) + (a.shape,)))
xchoices = tuple(broadcast_to(c, bshape) for c in xchoices1)
elif isinstance(choices, List):
if not isinstance(choices[0], ndarray):
xchoices = [asarray(c) for c in choices]
else:
xchoices = choices
bshape = broadcast_shapes(xchoices[0].shape, a.shape)
elif isinstance(choices, ndarray):
xchoices = choices
bshape = broadcast_shapes(xchoices[0].shape, a.shape)
else:
compile_error("'choices' must be a list, tuple or array")
dtype = xchoices[0].dtype
if out is None:
ans = empty(bshape, dtype)
else:
if not isinstance(out, ndarray):
compile_error("'out' must be an array")
if out.dtype is not dtype:
compile_error("'out' has incorrect dtype")
if staticlen(bshape) != staticlen(out.shape):
compile_error("'out' has incorrect number of dimensions")
if bshape != out.shape:
raise ValueError("'out' has incorrect shape")
ans = out
for idx in util.multirange(bshape):
mi = a._ptr(idx, broadcast=True)[0]
if mi < 0 or mi >= n:
if xmode == MODE_RAISE:
raise ValueError("invalid entry in choice array")
elif xmode == MODE_WRAP:
if mi < 0:
while mi < 0:
mi += n;
else:
while mi >= n:
mi -= n
elif xmode == MODE_CLIP:
if mi < 0:
mi = 0
elif mi >= n:
mi = n - 1
choice = xchoices[mi]._ptr(idx, broadcast=True)[0]
ans._ptr(idx)[0] = choice
return ans
def compress(condition, a, axis = None, out = None):
condition = asarray(condition)
a = asarray(a)
if staticlen(condition.shape) != 1:
compile_error("condition must be a 1-d array")
if axis is None:
num_true = 0
i = 0
n = a.size
for c in condition:
if c:
if i >= n:
raise IndexError(f"index {i} is out of bounds for axis 0 with size {n}")
num_true += 1
i += 1
if out is None:
ans = empty(num_true, a.dtype)
ans_cc = True
else:
if not isinstance(out, ndarray):
compile_error("'out' must be an array")
if staticlen(out.shape) != 1:
compile_error("'out' must be 1-dimensional")
if out.size != num_true:
raise ValueError("'out' has incorrect length")
ans = out
ans_cc = ans._contig[0]
a_cc = a._contig[0]
k = 0
if a_cc and ans_cc:
for i in range(min(condition.size, n)):
if condition._ptr((i,))[0]:
ans.data[k] = util.cast(a.data[i], ans.dtype)
k += 1
else:
i = 0
for idx in util.multirange(a.shape):
if i >= condition.size:
break
if condition[i]:
ans._ptr((k,))[0] = util.cast(a._ptr(idx)[0], ans.dtype)
k += 1
i += 1
return ans
axis = util.normalize_axis_index(axis, a.ndim)
num_true = 0
i = 0
n = a.shape[axis]
for c in condition:
if c:
if i >= n:
raise IndexError(f"index {i} is out of bounds for axis {axis} with size {n}")
num_true += 1
i += 1
ans_shape = util.tuple_set(a.shape, axis, num_true)
if out is None:
ans = empty(ans_shape, a.dtype)
else:
if not isinstance(out, ndarray):
compile_error("'out' must be an array")
if staticlen(out.shape) != staticlen(ans_shape):
compile_error("'out' has incorrect number of dimensions")
if out.shape != ans_shape:
raise ValueError("'out' has incorrect shape")
ans = out
sub_shape = util.tuple_delete(ans_shape, axis)
k = 0
for i in range(min(condition.size, n)):
if condition._ptr((i,))[0]:
for idx in util.multirange(sub_shape):
idx1 = util.tuple_insert(idx, axis, i)
idx2 = util.tuple_insert(idx, axis, k)
p = a._ptr(idx1)
q = ans._ptr(idx2)
q[0] = util.cast(p[0], ans.dtype)
k += 1
return ans
def diagonal(a, offset: int = 0, axis1: int = 0, axis2: int = 1):
a = asarray(a)
shape = a.shape
strides = a.strides
ndim: Static[int] = staticlen(shape)
if ndim < 2:
compile_error("diag requires an array of at least two dimensions")
axis1 = util.normalize_axis_index(axis1, ndim)
axis2 = util.normalize_axis_index(axis2, ndim)
if axis1 == axis2:
raise ValueError("axis1 and axis2 cannot be the same")
if axis1 > axis2:
axis1, axis2 = axis2, axis1
dim1 = shape[axis1]
dim2 = shape[axis2]
stride1 = strides[axis1]
stride2 = strides[axis2]
data = a.data
if offset >= 0:
offset_stride = stride2
dim2 -= offset
else:
offset = -offset
offset_stride = stride1
dim1 -= offset
diag_size = dim2 if dim2 < dim1 else dim1
if diag_size < 0:
diag_size = 0
else:
data = Ptr[a.dtype](data.as_byte() + (offset * offset_stride))
ret_shape = util.tuple_delete(util.tuple_delete(shape, axis2), axis1) + (diag_size,)
ret_strides = util.tuple_delete(util.tuple_delete(strides, axis2), axis1) + (stride1 + stride2,)
return ndarray(ret_shape, ret_strides, data)
def select(condlist: List, choicelist: List, default = 0):
n = len(condlist)
if n != len(choicelist):
raise ValueError(
"list of cases must be same length as list of conditions")
if n == 0:
raise ValueError("select with an empty condition list is not possible")
condlist = [asarray(cond) for cond in condlist]
if condlist[0].dtype is not bool:
compile_error("condlist entries should be boolean ndarray")
cond_bshape = condlist[0].shape
for cond in condlist:
cond_bshape = broadcast_shapes(cond_bshape, cond.shape)
for i in range(n):
condlist[i] = broadcast_to(condlist[i], cond_bshape)
choicelist = [asarray(choice) for choice in choicelist]
choice_bshape = choicelist[0].shape
for choice in choicelist:
choice_bshape = broadcast_shapes(choice_bshape, choice.shape)
for i in range(n):
choicelist[i] = broadcast_to(choicelist[i], choice_bshape)
default = asarray(default)
ans_shape = broadcast_shapes(cond_bshape, choice_bshape, default.shape)
dtype = type(util.coerce(choicelist[0].dtype, default.dtype))
ans = empty(ans_shape, dtype)
for idx in util.multirange(ans_shape):
found = False
p = ans._ptr(idx)
for i in range(n):
if condlist[i]._ptr(idx, broadcast=True)[0]:
p[0] = util.cast(choicelist[i]._ptr(idx, broadcast=True)[0], dtype)
found = True
break
if not found:
p[0] = util.cast(default._ptr(idx, broadcast=True)[0], dtype)
return ans
def place(arr: ndarray, mask, vals):
mask = asarray(mask)
vals = asarray(vals)
ni = arr.size
nm = mask.size
nv = vals.size
if nm != ni:
raise ValueError("place: mask and data must be the same size")
if nv <= 0:
if mask.any():
raise ValueError("Cannot insert from an empty array!");
return
cc1, _ = arr._contig
cc2, _ = mask._contig
cc3, _ = vals._contig
j = 0
if cc1 and cc2 and cc3:
for i in range(ni):
if mask.data[i]:
if j >= nv:
j = 0
arr.data[i] = util.cast(vals.data[j], arr.dtype)
j += 1
else:
for i in range(ni):
if mask._get_flat(i, check=False):
if j >= nv:
j = 0
arr._set_flat(i, vals._get_flat(j, check=False), check=False)
j += 1
def put(a: ndarray, ind, v, mode: str = 'raise'):
def fix_index(idx: int, dim: int, mode: str):
if mode == 'raise':
if idx < 0 or idx >= dim:
raise ValueError("invalid entry in coordinates array")
elif mode == 'wrap':
if idx < 0:
while idx < 0:
idx += dim
elif idx >= dim:
while idx >= dim:
idx -= dim
elif mode == 'clip':
if idx < 0:
idx = 0
elif idx >= dim:
idx = dim - 1
return idx
if mode not in ('raise', 'wrap', 'clip'):
raise ValueError(f"clipmode must be one of 'clip', 'raise', or 'wrap' (got {repr(mode)})")
ind = asarray(ind)
v = asarray(v)
na = a.size
ni = ind.size
nv = v.size
if ni == 0 or nv == 0:
return
if na == 0 and (mode == 'wrap' or mode == 'clip'):
raise ValueError("empty array given to put")
cc1, _ = a._contig
cc2, _ = ind._contig
cc3, _ = v._contig
j = 0
if cc1 and cc2 and cc3:
for i in range(ni):
idx = util.cast(ind.data[i], int)
idx = fix_index(idx, na, mode)
if j >= nv:
j = 0
a.data[idx] = util.cast(v.data[j], a.dtype)
j += 1
else:
for i in range(ni):
idx = util.cast(ind._get_flat(i, check=False), int)
idx = fix_index(idx, na, mode)
if j >= nv:
j = 0
a._set_flat(idx, v._get_flat(j, check=False), check=True)
j += 1
def put_along_axis(arr: ndarray, indices, values, axis):
def dim_mismatch():
compile_error("`indices` and `arr` must have the same number of dimensions")
indices = asarray(indices)
values = asarray(values)
if indices.dtype is not int:
compile_error("`indices` must be an integer array")
if axis is None:
nv = values.size
j = 0
for i in range(indices.size):
if j >= nv:
j = 0
arr._set_flat(indices._get_flat(i, check=False),
values._get_flat(j, check=False),
check=True)
j += 1
return
bshape = broadcast_shapes(indices.shape, values.shape)
indices = broadcast_to(indices, bshape)
values = broadcast_to(values, bshape)
if staticlen(arr.shape) != staticlen(indices.shape):
dim_mismatch()
axis = util.normalize_axis_index(axis, arr.ndim)
M = arr.shape[axis]
J = indices.shape[axis]
bshape = broadcast_shapes(util.tuple_delete(arr.shape, axis),
util.tuple_delete(indices.shape, axis))
a_stride = arr.strides[axis]
indices_stride = indices.strides[axis]
values_stride = values.strides[axis]
for idx in util.multirange(bshape):
base_idx = util.tuple_insert(idx, axis, 0)
a_1d = ndarray((M,), (a_stride,), arr._ptr(base_idx, broadcast=True))
indices_1d = ndarray((J,), (indices_stride,), indices._ptr(base_idx, broadcast=True))
values_1d = ndarray((J,), (values_stride,), values._ptr(base_idx, broadcast=True))
for j in range(J):
a_1d[indices_1d._ptr((j,))[0]] = util.cast(values_1d._ptr((j,))[0], arr.dtype)
def putmask(a: ndarray, mask, values):
mask = asarray(mask)
values = asarray(values)
na = a.size
nv = values.size
if na != mask.size:
raise ValueError("putmask: mask and data must be the same size")
if na == 0 or nv == 0:
return
cc1, _ = a._contig
cc2, _ = mask._contig
cc3, _ = values._contig
j = 0
if cc1 or cc2 or cc3:
for i in range(na):
if mask.data[i]:
a.data[i] = values.data[j]
j += 1
if j >= nv:
j = 0
else:
for i in range(na):
if mask._get_flat(i, check=False):
a._set_flat(i, values._get_flat(j, check=False), check=False)
j += 1
if j >= nv:
j = 0
def fill_diagonal(a: ndarray, val, wrap: bool = False):
if staticlen(a.shape) < 2:
compile_error("array must be at least 2-d")
val = asarray(val)
nv = val.size
end = a.size
if nv == 0 or end == 0:
return
if staticlen(a.shape) == 2:
step = a.shape[1] + 1
if not wrap:
end = min(end, a.shape[1] * a.shape[1])
else:
for i in range(1, a.ndim):
if a.shape[i] != a.shape[0]:
raise ValueError("All dimensions of input must be of equal length")
step = 1
acc = 1
for s in a.shape[:-1]:
acc *= s
step += acc
j = 0
for i in range(0, end, step):
a._set_flat(i, val._get_flat(j, check=False), check=False)
j += 1
if j >= nv:
j = 0
########
# Misc #
########
def _power_of_ten(n: int):
p10 = (1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8)
if n < 9:
return Ptr[float](__ptr__(p10).as_byte())[n]
else:
ret = 1e9
while n > 9:
ret *= 10.
n -= 1
return ret
def _round_int(x: T, decimals: int, T: type):
if decimals >= 0:
return x
else:
f = _power_of_ten(-decimals)
y = util.cast(x, float)
return T(int(util.rint(y / f) * f))
def _round(x: T, decimals: int, T: type):
if isinstance(x, complex) or isinstance(x, complex64):
return T(_round(x.real, decimals), _round(x.imag, decimals))
if (isinstance(x, int) or
isinstance(x, Int) or
isinstance(x, UInt) or
isinstance(x, byte) or
isinstance(x, bool)):
return _round_int(x, decimals)
if not (isinstance(x, float) or isinstance(x, float32)):
compile_error("don't know how to round type '" + T.__name__ + "'")
if decimals == 0:
return util.rint(x)
elif decimals > 0:
f = T(_power_of_ten(decimals))
return util.rint(x * f) / f
else:
f = T(_power_of_ten(-decimals))
return util.rint(x / f) * f
def round(a, decimals: int = 0, out = None):
a = asarray(a)
if staticlen(a.shape) == 0:
return _round(a.data[0], decimals)
cc, fc = a._contig
n = a.size
if out is None:
ans = empty_like(a, order=('F' if fc and not cc else 'C'))
else:
if not isinstance(out, ndarray):
compile_error("output must be an array")
if out.dtype is not a.dtype:
compile_error("output has wrong type")
if not util.tuple_equal(a.shape, out.shape):
raise ValueError("invalid output shape")
ans = out
cc1, fc1 = ans._contig
if (cc and cc1) or (fc and fc1):
p = a.data
q = ans.data
for i in range(n):
q[i] = _round(p[i], decimals)
else:
for idx in util.multirange(a.shape):
p = a._ptr(idx)
q = ans._ptr(idx)
q[0] = _round(p[0], decimals)
return ans
around = round
def _clip_full(a, a_min, a_max, out):
a = asarray(a)
a_min = asarray(a_min)
a_max = asarray(a_max)
bshape = broadcast_shapes(a.shape, a_min.shape, a_max.shape)
if out is None:
ans = empty(bshape, a.dtype)
elif isinstance(out, ndarray):
if staticlen(bshape) != staticlen(out.shape):
compile_error("'out' has incorrect number of dimensions")
if bshape != out.shape:
raise ValueError("'out' has incorrect shape")
ans = out
else:
compile_error("'out' must be an array")
maxf = lambda a, b: a if a > b else b
minf = lambda a, b: a if a < b else b
for idx in util.multirange(bshape):
x = a._ptr(idx, broadcast=True)[0]
xmin = a_min._ptr(idx, broadcast=True)[0]
xmax = a_max._ptr(idx, broadcast=True)[0]
x = util.cast(x, ans.dtype)
xmin = util.cast(xmin, ans.dtype)
xmax = util.cast(xmax, ans.dtype)
ans._ptr(idx)[0] = minf(xmax, maxf(x, xmin))
return ans
def _clip_min(a, a_min, out):
a = asarray(a)
a_min = asarray(a_min)
bshape = broadcast_shapes(a.shape, a_min.shape)
if out is None:
ans = empty(bshape, a.dtype)
elif isinstance(out, ndarray):
if staticlen(bshape) != staticlen(out.shape):
compile_error("'out' has incorrect number of dimensions")
if bshape != out.shape:
raise ValueError("'out' has incorrect shape")
ans = out
else:
compile_error("'out' must be an array")
maxf = lambda a, b: a if a > b else b
for idx in util.multirange(bshape):
x = a._ptr(idx, broadcast=True)[0]
xmin = a_min._ptr(idx, broadcast=True)[0]
x = util.cast(x, ans.dtype)
xmin = util.cast(xmin, ans.dtype)
ans._ptr(idx)[0] = maxf(x, xmin)
return ans
def _clip_max(a, a_max, out):
a = asarray(a)
a_max = asarray(a_max)
bshape = broadcast_shapes(a.shape, a_max.shape)
if out is None:
ans = empty(bshape, a.dtype)
elif isinstance(out, ndarray):
if staticlen(bshape) != staticlen(out.shape):
compile_error("'out' has incorrect number of dimensions")
if bshape != out.shape:
raise ValueError("'out' has incorrect shape")
ans = out
else:
compile_error("'out' must be an array")
minf = lambda a, b: a if a < b else b
for idx in util.multirange(bshape):
x = a._ptr(idx, broadcast=True)[0]
xmax = a_max._ptr(idx, broadcast=True)[0]
x = util.cast(x, ans.dtype)
xmax = util.cast(xmax, ans.dtype)
ans._ptr(idx)[0] = minf(xmax, x)
return ans
def clip(a, a_min, a_max, out = None):
if a_min is None and a_max is None:
compile_error("One of max or min must be given")
elif a_max is None:
return _clip_min(a, a_min, out)
elif a_min is None:
return _clip_max(a, a_max, out)
else:
return _clip_full(a, a_min, a_max, out)
def ndenumerate(arr):
arr = asarray(arr)
for idx in util.multirange(arr.shape):
yield (idx, arr._ptr(idx)[0])
def ndindex(*shape):
if staticlen(shape) == 1:
if isinstance(shape[0], Tuple):
return ndindex(*shape[0])
for s in shape:
if s < 0:
raise ValueError("negative dimensions are not allowed")
return util.multirange(shape)
def iterable(y):
if not hasattr(y, "__iter__"):
return False
elif isinstance(y, ndarray):
if staticlen(y.shape) == 0:
return False
else:
return True
else:
return True
def packbits(a, axis = None, bitorder: str = 'big'):
a = asarray(a)
if (a.dtype is not bool and
a.dtype is not int and
a.dtype is not byte and
not isinstance(a.dtype, Int) and
not isinstance(a.dtype, UInt)):
compile_error("Expected an input array of integer or boolean data type")
little_endian = False
if bitorder == 'big':
little_endian = False
elif bitorder == 'little':
little_endian = True
else:
raise ValueError("'order' must be either 'little' or 'big'")
if axis is None:
pack_size = ((a.size - 1) >> 3) + 1
ans = empty(pack_size, dtype=u8)
m = 0
k = 0
e = u8(0)
for idx in util.multirange(a.shape):
if m == 8:
ans.data[k] = e
k += 1
e = u8(0)
m = 0
b = u8(1 if a._ptr(idx)[0] else 0)
if little_endian:
e = (e >> u8(1)) | (b << u8(7))
else:
e = (e << u8(1)) | b
m += 1
if k < pack_size:
if little_endian:
e >>= u8(8 - m)
else:
e <<= u8(8 - m)
ans.data[k] = e
return ans
if not isinstance(axis, int):
compile_error("'axis' must be an int or None")
axis = util.normalize_axis_index(axis, a.ndim)
n = a.shape[axis]
pack_size = ((n - 1) >> 3) + 1
ans_shape = util.tuple_set(a.shape, axis, pack_size)
ans = empty(ans_shape, dtype=u8)
for idx0 in util.multirange(util.tuple_delete(a.shape, axis)):
m = 0
k = 0
e = u8(0)
for i in range(n):
if m == 8:
ans._ptr(util.tuple_insert(idx0, axis, k))[0] = e
k += 1
e = u8(0)
m = 0
b = u8(1 if a._ptr(util.tuple_insert(idx0, axis, i))[0] else 0)
if little_endian:
e = (e >> u8(1)) | (b << u8(7))
else:
e = (e << u8(1)) | b
m += 1
if k < pack_size:
if little_endian:
e >>= u8(8 - m)
else:
e <<= u8(8 - m)
ans._ptr(util.tuple_insert(idx0, axis, k))[0] = e
return ans
def unpackbits(a, axis = None, count = None, bitorder: str = 'big'):
a = asarray(a)
if a.dtype is not u8:
compile_error("Expected an input array of unsigned byte data type")
little_endian = False
if bitorder == 'big':
little_endian = False
elif bitorder == 'little':
little_endian = True
else:
raise ValueError("'order' must be either 'little' or 'big'")
if axis is None:
unpack_size = a.size * 8
if count is not None:
if count < 0:
if -count > unpack_size:
raise ValueError("-count larger than number of elements")
unpack_size += count
else:
unpack_size = count
ans = empty(unpack_size, dtype=u8)
k = 0
for idx in util.multirange(a.shape):
e = a._ptr(idx)[0]
for i in range(8):
if k >= unpack_size:
break
sh = u8(i if little_endian else 7 - i)
ans.data[k] = (e & (u8(1) << sh)) >> sh
k += 1
if k >= unpack_size:
break
while k < unpack_size:
ans.data[k] = u8(0)
k += 1
return ans
if not isinstance(axis, int):
compile_error("'axis' must be an int or None")
axis = util.normalize_axis_index(axis, a.ndim)
n = a.shape[axis]
unpack_size = n * 8
if count is not None:
if count < 0:
if -count > unpack_size:
raise ValueError("-count larger than number of elements")
unpack_size += count
else:
unpack_size = count
ans_shape = util.tuple_set(a.shape, axis, unpack_size)
ans = empty(ans_shape, dtype=u8)
if unpack_size == 0:
return ans
for idx0 in util.multirange(util.tuple_delete(a.shape, axis)):
k = 0
for m in range(n):
e = a._ptr(util.tuple_insert(idx0, axis, m))[0]
for i in range(8):
if k >= unpack_size:
break
sh = u8(i if little_endian else 7 - i)
ans._ptr(util.tuple_insert(idx0, axis, k))[0] = (e & (u8(1) << sh)) >> sh
k += 1
if k >= unpack_size:
break
while k < unpack_size:
ans._ptr(util.tuple_insert(idx0, axis, k))[0] = u8(0)
k += 1
return ans
def _is_pos_neg_inf(x, pos: bool, out = None):
from .ndmath import isinf, signbit
x = asarray(x)
if out is None:
ans = empty(x.shape, bool)
else:
if not isinstance(out, ndarray):
compile_error("'out' must be an array")
if out.ndim != x.ndim:
compile_error("'out' has incorrect number of dimensions")
if x.shape != out.shape:
raise ValueError("'out' has incorrect shape")
ans = out
for idx in util.multirange(x.shape):
e = x._ptr(idx)[0]
b = isinf(e)
if pos:
b = b and not signbit(e)
else:
b = b and signbit(e)
ans._ptr(idx)[0] = util.cast(b, ans.dtype)
if out is None and ans.ndim == 0:
return ans.item()
else:
return ans
def isposinf(x, out = None):
return _is_pos_neg_inf(x, pos=True, out=out)
def isneginf(x, out = None):
return _is_pos_neg_inf(x, pos=False, out=out)
def iscomplex(x):
x = asarray(x)
if x.dtype is complex or x.dtype is complex64:
ans = x.map(lambda c: bool(c.imag))
else:
ans = zeros(x.shape, bool)
if ans.ndim == 0:
return ans.item()
else:
return ans
def iscomplexobj(x):
if isinstance(x, ndarray):
return x.dtype is complex or x.dtype is complex64
else:
dtype = asarray(x).dtype
return dtype is complex or dtype is complex64
def isreal(x):
x = asarray(x)
if x.dtype is complex or x.dtype is complex64:
ans = x.map(lambda c: not bool(c.imag))
else:
ans = ones(x.shape, bool)
if ans.ndim == 0:
return ans.item()
else:
return ans
def isrealobj(x):
return not iscomplexobj(x)
def isfortran(a: ndarray):
cc, fc = a._contig
return fc and not cc
def isscalar(element):
T = type(element)
return (T is int or
T is float or
T is complex or
T is complex64 or
T is bool or
T is byte or
isinstance(T, Int) or
isinstance(T, UInt) or
T is str or
T is NoneType)
def _array_get_part(arr: ndarray, imag: Static[int]):
if arr.dtype is complex:
offset = util.sizeof(float) if imag else 0
data = Ptr[float](arr.data.as_byte() + offset)
return ndarray(arr.shape, arr.strides, data)
elif arr.dtype is complex64:
offset = util.sizeof(float32) if imag else 0
data = Ptr[float32](arr.data.as_byte() + offset)
return ndarray(arr.shape, arr.strides, data)
else:
if imag:
n = arr.size
data = Ptr[arr.dtype](n)
str.memset(data.as_byte(), byte(0), n * arr.itemsize)
return ndarray(arr.shape, data)
else:
return arr
def real(val):
return _array_get_part(asarray(val), imag=False)
def imag(val):
return _array_get_part(asarray(val), imag=True)
def _real_set(arr: ndarray, val):
val = broadcast_to(asarray(val), arr.shape)
for idx in util.multirange(arr.shape):
x = val._ptr(idx)[0]
p = arr._ptr(idx)
if arr.dtype is complex:
p[0] = complex(util.cast(x, float), p[0].imag)
elif arr.dtype is complex64:
p[0] = complex64(util.cast(x, float32), p[0].imag)
else:
p[0] = util.cast(x, arr.dtype)
def _imag_set(arr: ndarray, val):
val = broadcast_to(asarray(val), arr.shape)
for idx in util.multirange(arr.shape):
x = val._ptr(idx)[0]
p = arr._ptr(idx)
if arr.dtype is complex:
p[0] = complex(p[0].real, util.cast(x, float))
elif arr.dtype is complex64:
p[0] = complex64(p[0].imag, util.cast(x, float32))
else:
compile_error("array does not have imaginary part to set")
@extend
class ndarray:
def take(self, indices, axis = None, out = None, mode: str = 'raise'):
return take(self, indices, axis=axis, out=out, mode=mode)
def squeeze(self, axis = None):
return squeeze(self, axis)
def nonzero(self):
return nonzero(self)
def searchsorted(self, v, side: str = 'left', sorter = None):
return searchsorted(self, v=v, side=side, sorter=sorter)
def repeat(self, repeats, axis = None):
return repeat(self, repeats, axis=axis)
def compress(self, condition, axis = None, out = None):
return compress(condition, self, axis=axis, out=out)
def choose(self, choices, out = None, mode: str = 'raise'):
return choose(self, choices, out, mode)
def diagonal(self, offset: int = 0, axis1: int = 0, axis2: int = 1):
return diagonal(self, offset, axis1=axis1, axis2=axis2)
def put(self, ind, v, mode: str = 'raise'):
return put(self, ind, v, mode)
def round(self, decimals: int = 0, out = None):
return round(self, decimals, out=out)
def clip(self, min = None, max = None, out = None):
return clip(self, min, max, out)
@property
def real(self):
return real(self)
@real.setter
def real(self, val):
_real_set(self, val)
@property
def imag(self):
return imag(self)
@imag.setter
def imag(self, val):
_imag_set(self, val)
def conj(self):
if hasattr(dtype(), 'conjugate'):
return self._op_unary(lambda a: a.conjugate())
else:
return self
def conjugate(self):
return self.conj()
@flat.setter
def flat(self, val):
val = asarray(val)
j = 0
m = val.size
if m == 0:
return
for i in range(self.size):
p = self._ptr_flat(i, check=False)
if j == m:
j = 0
q = val._ptr_flat(j, check=False)
p[0] = util.cast(q[0], dtype)
j += 1
# Not supported:
# - rollaxis (use is discouraged in NumPy docs)
# - asmatrix (matrix class not supported)
############
# Datetime #
############
@extend
class busdaycalendar:
@property
def weekmask(self):
wm = empty(7, bool)
for i in range(7):
wm.data[i] = self._wm[i]
return wm
@property
def holidays(self):
hd = empty(self._nholidays, datetime64['D', 1])
for i in range(len(hd)):
hd.data[i] = self._holidays[i]
return hd
def _get_busdaycal(weekmask=None, holidays=None, busdaycal=None):
if busdaycal is None:
if weekmask is None:
w = "1111100"
else:
w = weekmask
return busdaycalendar(weekmask=w, holidays=holidays)
else:
if weekmask is not None or holidays is not None:
compile_error("Cannot supply both the weekmask/holidays and the "
"busdaycal parameters to busday_offset()")
if not isinstance(busdaycal, busdaycalendar):
compile_error("busdaycal parameter must be a busdaycalendar")
return busdaycal
def busday_offset(dates, offsets, roll: str = "raise", weekmask=None,
holidays=None, busdaycal=None, out=None):
cal = _get_busdaycal(weekmask=weekmask, holidays=holidays, busdaycal=busdaycal)
wm = cal._wm
busdays_in_weekmask = wm.count
dates = asarray(dates, dtype=datetime64['D', 1])
offsets = asarray(offsets)
if offsets.dtype is not int:
compile_error("offsets parameter must be an array of integers")
roll_code = 0
if roll == "forward":
roll_code = _BUSDAY_FORWARD
elif roll == "following":
roll_code = _BUSDAY_FOLLOWING
elif roll == "backward":
roll_code = _BUSDAY_BACKWARD
elif roll == "preceding":
roll_code = _BUSDAY_PRECEDING
elif roll == "modifiedfollowing":
roll_code = _BUSDAY_MODIFIEDFOLLOWING
elif roll == "modifiedpreceding":
roll_code = _BUSDAY_MODIFIEDPRECEDING
elif roll == "nat":
roll_code = _BUSDAY_NAT
elif roll == "raise":
roll_code = _BUSDAY_RAISE
else:
raise ValueError(f"Invalid business day roll parameter \"{roll}\"")
bshape = broadcast_shapes(dates.shape, offsets.shape)
if out is not None:
if not isinstance(out, ndarray):
compile_error("'out' must be an array")
if out.dtype is not datetime64['D', 1]:
compile_error("'out' must have dtype datetime64[D]")
if out.ndim != staticlen(bshape):
compile_error("'out' has incorrect number of dimensions")
if out.shape != bshape:
raise ValueError("'out' has incorrect shape")
ans = out
else:
ans = empty(bshape, datetime64['D', 1])
for idx in util.multirange(bshape):
d = dates._ptr(idx, broadcast=True)[0]
o = offsets._ptr(idx, broadcast=True)[0]
b = _apply_busines_day_offset(d, o, roll_code, wm, busdays_in_weekmask,
cal._holidays, cal._holidays + cal._nholidays)
ans._ptr(idx)[0] = b
if ans.ndim == 0 and out is None:
return ans.item()
else:
return ans
def busday_count(begindates, enddates, weekmask=None,
holidays=None, busdaycal=None, out=None):
cal = _get_busdaycal(weekmask=weekmask, holidays=holidays, busdaycal=busdaycal)
wm = cal._wm
busdays_in_weekmask = wm.count
begindates = asarray(begindates, dtype=datetime64['D', 1])
enddates = asarray(enddates, dtype=datetime64['D', 1])
if out is not None:
if not isinstance(out, ndarray):
compile_error("'out' must be an array")
if out.dtype is not int:
compile_error("'out' must have dtype int")
bshape = broadcast_shapes(begindates.shape, enddates.shape, out.shape)
ans = out
else:
bshape = broadcast_shapes(begindates.shape, enddates.shape)
ans = empty(bshape, int)
for idx in util.multirange(bshape):
d1 = begindates._ptr(idx, broadcast=True)[0]
d2 = enddates._ptr(idx, broadcast=True)[0]
c = _apply_busines_day_count(d1, d2, wm, busdays_in_weekmask,
cal._holidays, cal._holidays + cal._nholidays)
ans._ptr(idx, broadcast=(out is not None))[0] = c
if ans.ndim == 0 and out is None:
return ans.item()
else:
return ans
def is_busday(dates, weekmask=None, holidays=None, busdaycal=None, out=None):
cal = _get_busdaycal(weekmask=weekmask, holidays=holidays, busdaycal=busdaycal)
wm = cal._wm
busdays_in_weekmask = wm.count
dates = asarray(dates, dtype=datetime64['D', 1])
if out is not None:
if not isinstance(out, ndarray):
compile_error("'out' must be an array")
if out.dtype is not bool:
compile_error("'out' must have dtype bool")
if out.ndim != dates.ndim:
compile_error("'out' has incorrect number of dimensions")
if out.shape != dates.shape:
raise ValueError("'out' has incorrect shape")
ans = out
else:
ans = empty(dates.shape, bool)
for idx in util.multirange(dates.shape):
d = dates._ptr(idx)[0]
ans._ptr(idx)[0] = _apply_is_business_day(d, wm, cal._holidays, cal._holidays + cal._nholidays)
if ans.ndim == 0 and out is None:
return ans.item()
else:
return ans
def datetime_data(dtype: type):
if not isinstance(dtype, datetime64) and not isinstance(dtype, timedelta64):
compile_error("cannot get datetime metadata from non-datetime type")
return (dtype.base, dtype.num)
def datetime_as_string(arr, unit=None, timezone: str = "naive"):
arr = asarray(arr)
if not isinstance(arr.dtype, datetime64):
compile_error("input must have type NumPy datetime")
return arr.map(lambda d: d._as_string(unit=unit, timezone=timezone))