mirror of https://github.com/exaloop/codon.git
284 lines
8.6 KiB
Python
284 lines
8.6 KiB
Python
# Copyright (C) 2022-2025 Exaloop Inc. <https://exaloop.io>
|
|
|
|
from .routines import empty as empty_array, asarray, atleast_1d, empty, ascontiguousarray, _check_out, concatenate
|
|
from .sorting import argsort
|
|
import util
|
|
|
|
_LIKELY_IN_CACHE_SIZE: Static[int] = 8
|
|
|
|
def _linear_search(key, arr, lenxp):
|
|
for i in range(lenxp):
|
|
if key < arr[i]:
|
|
return i - 1
|
|
return lenxp - 1
|
|
|
|
def _binary_search_with_guess(key, arr, lenxp, guess):
|
|
imin = 0
|
|
imax = lenxp
|
|
# Handle keys outside of the arr range first
|
|
if key > arr[lenxp - 1]:
|
|
return lenxp
|
|
elif key < arr[0]:
|
|
return -1
|
|
|
|
# If len <= 4 use linear search
|
|
if lenxp <= 4:
|
|
return _linear_search(key, arr, lenxp)
|
|
|
|
if guess > lenxp - 3:
|
|
guess = lenxp - 3
|
|
if guess < 1:
|
|
guess = 1
|
|
|
|
# Check most likely values: guess - 1, guess, guess + 1
|
|
if key < arr[guess]:
|
|
if key < arr[guess - 1]:
|
|
imax = guess - 1
|
|
# Last attempt to restrict search to items in cache
|
|
if guess > _LIKELY_IN_CACHE_SIZE and key >= arr[
|
|
guess - _LIKELY_IN_CACHE_SIZE]:
|
|
imin = guess - _LIKELY_IN_CACHE_SIZE
|
|
else:
|
|
return guess - 1
|
|
else:
|
|
if key < arr[guess + 1]:
|
|
return guess
|
|
elif key < arr[guess + 2]:
|
|
return guess + 1
|
|
else:
|
|
imin = guess + 2
|
|
# Last attempt to restrict search to items in cache
|
|
if guess < lenxp - _LIKELY_IN_CACHE_SIZE - 1 and key < arr[
|
|
guess + _LIKELY_IN_CACHE_SIZE]:
|
|
imax = guess + _LIKELY_IN_CACHE_SIZE
|
|
|
|
# Finally, find index by bisection
|
|
while imin < imax:
|
|
imid = imin + (imax - imin) // 2
|
|
if key >= arr[imid]:
|
|
imin = imid + 1
|
|
else:
|
|
imax = imid
|
|
|
|
return imin - 1
|
|
|
|
def _compiled_interp_complex(x: Ptr[X],
|
|
lenx: int,
|
|
X: type,
|
|
xp: Ptr[T],
|
|
lenxp: int,
|
|
T: type,
|
|
fp: Ptr[D],
|
|
lenafp: int,
|
|
D: type,
|
|
left=None,
|
|
right=None):
|
|
dx = xp
|
|
dz = x
|
|
dy = fp
|
|
|
|
af = empty_array(lenx, dtype=complex)
|
|
|
|
# Get left and right fill values
|
|
lval = dy[0] if left is None else complex(left)
|
|
rval = dy[lenafp - 1] if right is None else complex(right)
|
|
|
|
# binary_search_with_guess needs at least a 3 item long array
|
|
if lenxp == 1:
|
|
xp_val, fp_val = dx[0], dy[0]
|
|
for i in range(lenx):
|
|
x_val = dz[i]
|
|
af[i] = lval if x_val < xp_val else (
|
|
rval if x_val > xp_val else fp_val)
|
|
else:
|
|
j = 0
|
|
|
|
# only pre-calculate slopes if there are relatively few of them
|
|
slopes = empty_array(lenxp -
|
|
1, dtype=complex) if lenxp <= lenx else None
|
|
|
|
if slopes is not None:
|
|
for i in range(lenxp - 1):
|
|
inv_dx = 1.0 / (dx[i + 1] - dx[i])
|
|
slopes[i] = ((dy[i + 1] - dy[i]) * inv_dx)
|
|
|
|
for i in range(lenx):
|
|
x_val = dz[i]
|
|
|
|
if util.isnan(x_val):
|
|
af[i] = x_val
|
|
continue
|
|
|
|
j = _binary_search_with_guess(x_val, dx, lenxp, j)
|
|
|
|
if j == -1:
|
|
af[i] = lval
|
|
elif j == lenxp:
|
|
af[i] = rval
|
|
elif j == lenxp - 1:
|
|
af[i] = dy[j]
|
|
elif dx[j] == x_val:
|
|
af[i] = dy[j]
|
|
else:
|
|
slope = slopes[j] if slopes is not None else (
|
|
(dy[j + 1] - dy[j]) / (dx[j + 1] - dx[j]))
|
|
|
|
af_real = slope.real * (x_val - dx[j]) + dy[j].real
|
|
af_imag = slope.imag * (x_val - dx[j]) + dy[j].imag
|
|
|
|
if util.isnan(af_real):
|
|
af_real = slope.real * (x_val - dx[j + 1]) + dy[j + 1].real
|
|
if util.isnan(af_real) and dy[j].real == dy[j + 1].real:
|
|
af_real = dy[j].real
|
|
|
|
if util.isnan(af_imag):
|
|
af_imag = slope.imag * (x_val - dx[j]) + dy[j].imag
|
|
if util.isnan(af_imag) and dy[j].imag == dy[j + 1].imag:
|
|
af_imag = dy[j].imag
|
|
|
|
af[i] = complex(af_real, af_imag)
|
|
|
|
return af
|
|
|
|
def _compiled_interp(x: Ptr[X],
|
|
lenx: int,
|
|
X: type,
|
|
xp: Ptr[T],
|
|
lenxp: int,
|
|
T: type,
|
|
fp: Ptr[D],
|
|
lenafp: int,
|
|
D: type,
|
|
left=None,
|
|
right=None):
|
|
afp = fp
|
|
axp = xp
|
|
ax = x
|
|
if lenxp == 0:
|
|
raise ValueError("Array of sample points is empty")
|
|
|
|
if lenafp != lenxp:
|
|
raise ValueError("fp and xp are not of the same length.")
|
|
|
|
af = empty_array(lenx, dtype=float)
|
|
dy = afp
|
|
dx = axp
|
|
dz = ax
|
|
dres = af
|
|
|
|
# Get left and right fill values
|
|
lval = dy[0] if left is None else float(left)
|
|
rval = dy[lenafp - 1] if right is None else float(right)
|
|
|
|
# binary_search_with_guess needs at least a 3 item long array
|
|
if lenxp == 1:
|
|
xp_val = dx[0]
|
|
fp_val = dy[0]
|
|
|
|
for i in range(lenx):
|
|
x_val = dz[i]
|
|
if x_val < xp_val:
|
|
dres[i] = lval
|
|
elif x_val > xp_val:
|
|
dres[i] = rval
|
|
else:
|
|
dres[i] = fp_val
|
|
else:
|
|
slopes = None
|
|
|
|
if lenxp <= lenx:
|
|
slopes = [0.0] * (lenxp - 1)
|
|
for i in range(lenxp - 1):
|
|
slopes[i] = (dy[i + 1] - dy[i]) / (dx[i + 1] - dx[i])
|
|
|
|
j = 0
|
|
|
|
for i in range(lenx):
|
|
x_val = dz[i]
|
|
|
|
if util.isnan(x_val):
|
|
dres[i] = x_val
|
|
continue
|
|
|
|
j = _binary_search_with_guess(x_val, dx, lenxp, j)
|
|
|
|
if j == -1:
|
|
dres[i] = lval
|
|
elif j == lenxp:
|
|
dres[i] = rval
|
|
elif j == lenxp - 1 or dx[j] == x_val:
|
|
dres[i] = dy[j]
|
|
else:
|
|
slope = slopes[j] if slopes is not None else (
|
|
dy[j + 1] - dy[j]) / (dx[j + 1] - dx[j])
|
|
dres[i] = slope * (x_val - dx[j]) + dy[j]
|
|
|
|
if util.isnan(dres[i]):
|
|
dres[i] = slope * (x_val - dx[j + 1]) + dy[j + 1]
|
|
|
|
if util.isnan(dres[i]) and dy[j] == dy[j + 1]:
|
|
dres[i] = dy[j]
|
|
|
|
return af
|
|
|
|
def _interp_type(dtype: type):
|
|
if dtype is complex64:
|
|
return complex()
|
|
else:
|
|
return dtype()
|
|
|
|
def interp(x, xp, fp, left=None, right=None, period=None):
|
|
x = ascontiguousarray(atleast_1d(asarray(x)))
|
|
xp = ascontiguousarray(atleast_1d(asarray(xp)))
|
|
fp = asarray(fp)
|
|
|
|
fp_dtype = type(_interp_type(fp.dtype))
|
|
fp = ascontiguousarray(atleast_1d(asarray(fp, fp_dtype)))
|
|
|
|
if period is not None:
|
|
if period == 0:
|
|
raise ValueError("period must be a non-zero value")
|
|
period = abs(period)
|
|
left = None
|
|
right = None
|
|
|
|
if xp.ndim != 1 or fp.ndim != 1:
|
|
compile_error("Data points must be 1-D sequences")
|
|
if xp.shape[0] != fp.shape[0]:
|
|
raise ValueError("fp and xp are not of the same length")
|
|
|
|
x = (x % period + period) % period
|
|
xp = (xp % period + period) % period
|
|
asort_xp = argsort(xp)
|
|
xp = xp[asort_xp]
|
|
fp = fp[asort_xp]
|
|
xp = concatenate((xp[-1:] - period, xp, xp[0:1] + period))
|
|
fp = concatenate((fp[-1:], fp, fp[0:1]))
|
|
|
|
if fp.dtype is complex:
|
|
af = _compiled_interp_complex(x.data,
|
|
x.size,
|
|
x.dtype,
|
|
xp.data,
|
|
xp.size,
|
|
xp.dtype,
|
|
fp.data,
|
|
fp.size,
|
|
fp.dtype,
|
|
left=left,
|
|
right=right)
|
|
return af
|
|
else:
|
|
af = _compiled_interp(x.data,
|
|
x.size,
|
|
x.dtype,
|
|
xp.data,
|
|
xp.size,
|
|
xp.dtype,
|
|
fp.data,
|
|
fp.size,
|
|
fp.dtype,
|
|
left=left,
|
|
right=right)
|
|
return af
|