# One-dimensional linear interpolation. (2.01)
# @note The product of trying to figure out the behavior of numpy.interp
# after reading the documentation. I think this captures the gist of it.
import math
import bisect
import numbers
# Utility.
def is_scalar(x):
return isinstance(x, numbers.Number)
def is_least_float(x):
return is_scalar(x) and not isinstance(x, numbers.Rational)
def to_least_float(x):
return x if is_least_float(x) else float(x)
def lerp(a, b, t):
return (1 - t) * a + t * b
def invlerp(x, a, b):
if math.isclose(a, b):
return 0.5
return (x - a) / (b - a)
def modinvlerp(x, a, b, m):
d = (b - a) % m
if math.isclose(0, d):
return 0.5
return (x - a) % m / d
def argsort(seq):
return sorted(range(len(seq)), key=seq.__getitem__)
# Interpolate.
def interp(x, xp, fp, left=None, right=None, period=None):
"""
Linear interpolation for monotonically increasing sample points.
"""
n = len(xp)
if n != len(fp):
raise ValueError('xp and fp are not of the same length')
if n == 0:
return
xp = list(map(to_least_float, xp))
fp = list(map(to_least_float, fp))
if period is None:
return _interp_bounded(n, x, xp, fp, left, right)
return _interp_periodic(n, x, xp, fp, period)
def _interp_bounded(n, x, xp, fp, left, right):
if left is None:
left = fp[0]
else:
left = to_least_float(left)
if right is None:
right = fp[-1]
else:
right = to_least_float(right)
first, last = xp[0], xp[-1]
for y in x:
if y < first:
yield left
elif y > last:
yield right
else:
i = bisect.bisect(xp, y)
j = i-1
k = min(i, n-1)
yield lerp(fp[j], fp[k], invlerp(y, xp[j], xp[k]))
def _interp_periodic(n, x, xp, fp, period):
if period == 0:
raise ValueError('period must be a non-zero value')
# Normalize periodic boundaries; reorder xp and fp.
xp = [x % period for x in xp]
xp_sorted_index = argsort(xp)
xp = [xp[i] for i in xp_sorted_index]
fp = [fp[i] for i in xp_sorted_index]
for y in x:
i = bisect.bisect(xp, y % period)
j = (i-1) % n
k = i % n
yield lerp(fp[j], fp[k], modinvlerp(y, xp[j], xp[k], period))
# Main.
import numpy as np
x = [0, 1, 1.5, 2.73, 3, 3.14]
xp = [1, 2, 3]
fp = [3, 2, 0]
print(list(np.interp([2.5], xp, fp)))
print(list(interp([2.5], xp, fp)))
print(list(np.interp(x, xp, fp)))
print(list(interp(x, xp, fp)))
UNDEF = 99
print(list(np.interp(x, xp, fp, left=-UNDEF, right=UNDEF)))
print(list(interp(x, xp, fp, left=-UNDEF, right=UNDEF)))
x = [-180, -170, -185, 185, -10, -5, 0, 365]
xp = [190, -190, 350, -350]
fp = [5, 10, 3, 4]
print(list(np.interp(x, xp, fp, period=360)))
print(list(interp(x, xp, fp, period=360)))
x = [1.5, 4.0]
xp = [2, 3, 5]
fp = [1.0j, 0, 2+3j]
print(list(np.interp(x, xp, fp)))
print(list(interp(x, xp, fp)))
IyBPbmUtZGltZW5zaW9uYWwgbGluZWFyIGludGVycG9sYXRpb24uICgyLjAxKQojIEBub3RlIFRoZSBwcm9kdWN0IG9mIHRyeWluZyB0byBmaWd1cmUgb3V0IHRoZSBiZWhhdmlvciBvZiBudW1weS5pbnRlcnAKIyBhZnRlciByZWFkaW5nIHRoZSBkb2N1bWVudGF0aW9uLiBJIHRoaW5rIHRoaXMgY2FwdHVyZXMgdGhlIGdpc3Qgb2YgaXQuCgppbXBvcnQgbWF0aAppbXBvcnQgYmlzZWN0CmltcG9ydCBudW1iZXJzCgojIFV0aWxpdHkuCgpkZWYgaXNfc2NhbGFyKHgpOgogICAgcmV0dXJuIGlzaW5zdGFuY2UoeCwgbnVtYmVycy5OdW1iZXIpCgpkZWYgaXNfbGVhc3RfZmxvYXQoeCk6CiAgICByZXR1cm4gaXNfc2NhbGFyKHgpIGFuZCBub3QgaXNpbnN0YW5jZSh4LCBudW1iZXJzLlJhdGlvbmFsKQoKZGVmIHRvX2xlYXN0X2Zsb2F0KHgpOgogICAgcmV0dXJuIHggaWYgaXNfbGVhc3RfZmxvYXQoeCkgZWxzZSBmbG9hdCh4KQoKZGVmIGxlcnAoYSwgYiwgdCk6CiAgICByZXR1cm4gKDEgLSB0KSAqIGEgKyB0ICogYgoKZGVmIGludmxlcnAoeCwgYSwgYik6CiAgICBpZiBtYXRoLmlzY2xvc2UoYSwgYik6CiAgICAgICAgcmV0dXJuIDAuNQogICAgcmV0dXJuICh4IC0gYSkgLyAoYiAtIGEpCgpkZWYgbW9kaW52bGVycCh4LCBhLCBiLCBtKToKICAgIGQgPSAoYiAtIGEpICUgbQogICAgaWYgbWF0aC5pc2Nsb3NlKDAsIGQpOgogICAgICAgIHJldHVybiAwLjUKICAgIHJldHVybiAoeCAtIGEpICUgbSAvIGQKCmRlZiBhcmdzb3J0KHNlcSk6CiAgICByZXR1cm4gc29ydGVkKHJhbmdlKGxlbihzZXEpKSwga2V5PXNlcS5fX2dldGl0ZW1fXykKCiMgSW50ZXJwb2xhdGUuCgpkZWYgaW50ZXJwKHgsIHhwLCBmcCwgbGVmdD1Ob25lLCByaWdodD1Ob25lLCBwZXJpb2Q9Tm9uZSk6CiAgICAiIiIKICAgIExpbmVhciBpbnRlcnBvbGF0aW9uIGZvciBtb25vdG9uaWNhbGx5IGluY3JlYXNpbmcgc2FtcGxlIHBvaW50cy4KICAgICIiIgogICAgbiA9IGxlbih4cCkKCiAgICBpZiBuICE9IGxlbihmcCk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigneHAgYW5kIGZwIGFyZSBub3Qgb2YgdGhlIHNhbWUgbGVuZ3RoJykKICAgIGlmIG4gPT0gMDoKICAgICAgICByZXR1cm4KCiAgICB4cCA9IGxpc3QobWFwKHRvX2xlYXN0X2Zsb2F0LCB4cCkpCiAgICBmcCA9IGxpc3QobWFwKHRvX2xlYXN0X2Zsb2F0LCBmcCkpCgogICAgaWYgcGVyaW9kIGlzIE5vbmU6CiAgICAgICAgcmV0dXJuIF9pbnRlcnBfYm91bmRlZChuLCB4LCB4cCwgZnAsIGxlZnQsIHJpZ2h0KQogICAgcmV0dXJuIF9pbnRlcnBfcGVyaW9kaWMobiwgeCwgeHAsIGZwLCBwZXJpb2QpCgpkZWYgX2ludGVycF9ib3VuZGVkKG4sIHgsIHhwLCBmcCwgbGVmdCwgcmlnaHQpOgogICAgaWYgbGVmdCBpcyBOb25lOgogICAgICAgIGxlZnQgPSBmcFswXQogICAgZWxzZToKICAgICAgICBsZWZ0ID0gdG9fbGVhc3RfZmxvYXQobGVmdCkKICAgIGlmIHJpZ2h0IGlzIE5vbmU6CiAgICAgICAgcmlnaHQgPSBmcFstMV0KICAgIGVsc2U6CiAgICAgICAgcmlnaHQgPSB0b19sZWFzdF9mbG9hdChyaWdodCkKCiAgICBmaXJzdCwgbGFzdCA9IHhwWzBdLCB4cFstMV0KCiAgICBmb3IgeSBpbiB4OgogICAgICAgIGlmIHkgPCBmaXJzdDoKICAgICAgICAgICAgeWllbGQgbGVmdAogICAgICAgIGVsaWYgeSA+IGxhc3Q6CiAgICAgICAgICAgIHlpZWxkIHJpZ2h0CiAgICAgICAgZWxzZToKICAgICAgICAgICAgaSA9IGJpc2VjdC5iaXNlY3QoeHAsIHkpCiAgICAgICAgICAgIGogPSBpLTEKICAgICAgICAgICAgayA9IG1pbihpLCBuLTEpCiAgICAgICAgICAgIHlpZWxkIGxlcnAoZnBbal0sIGZwW2tdLCBpbnZsZXJwKHksIHhwW2pdLCB4cFtrXSkpCgpkZWYgX2ludGVycF9wZXJpb2RpYyhuLCB4LCB4cCwgZnAsIHBlcmlvZCk6CiAgICBpZiBwZXJpb2QgPT0gMDoKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdwZXJpb2QgbXVzdCBiZSBhIG5vbi16ZXJvIHZhbHVlJykKCiAgICAjIE5vcm1hbGl6ZSBwZXJpb2RpYyBib3VuZGFyaWVzOyByZW9yZGVyIHhwIGFuZCBmcC4KCiAgICB4cCA9IFt4ICUgcGVyaW9kIGZvciB4IGluIHhwXQogICAgeHBfc29ydGVkX2luZGV4ID0gYXJnc29ydCh4cCkKICAgIHhwID0gW3hwW2ldIGZvciBpIGluIHhwX3NvcnRlZF9pbmRleF0KICAgIGZwID0gW2ZwW2ldIGZvciBpIGluIHhwX3NvcnRlZF9pbmRleF0KCiAgICBmb3IgeSBpbiB4OgogICAgICAgIGkgPSBiaXNlY3QuYmlzZWN0KHhwLCB5ICUgcGVyaW9kKQogICAgICAgIGogPSAoaS0xKSAlIG4KICAgICAgICBrID0gaSAlIG4KICAgICAgICB5aWVsZCBsZXJwKGZwW2pdLCBmcFtrXSwgbW9kaW52bGVycCh5LCB4cFtqXSwgeHBba10sIHBlcmlvZCkpCgojIE1haW4uCgppbXBvcnQgbnVtcHkgYXMgbnAKCnggID0gWzAsIDEsIDEuNSwgMi43MywgMywgMy4xNF0KeHAgPSBbMSwgMiwgM10KZnAgPSBbMywgMiwgMF0KCnByaW50KGxpc3QobnAuaW50ZXJwKFsyLjVdLCB4cCwgZnApKSkKcHJpbnQobGlzdChpbnRlcnAoWzIuNV0sIHhwLCBmcCkpKQpwcmludChsaXN0KG5wLmludGVycCh4LCB4cCwgZnApKSkKcHJpbnQobGlzdChpbnRlcnAoeCwgeHAsIGZwKSkpCgpVTkRFRiA9IDk5CnByaW50KGxpc3QobnAuaW50ZXJwKHgsIHhwLCBmcCwgbGVmdD0tVU5ERUYsIHJpZ2h0PVVOREVGKSkpCnByaW50KGxpc3QoaW50ZXJwKHgsIHhwLCBmcCwgbGVmdD0tVU5ERUYsIHJpZ2h0PVVOREVGKSkpCgp4ICA9IFstMTgwLCAtMTcwLCAtMTg1LCAxODUsIC0xMCwgLTUsIDAsIDM2NV0KeHAgPSBbMTkwLCAtMTkwLCAzNTAsIC0zNTBdCmZwID0gWzUsIDEwLCAzLCA0XQoKcHJpbnQobGlzdChucC5pbnRlcnAoeCwgeHAsIGZwLCBwZXJpb2Q9MzYwKSkpCnByaW50KGxpc3QoaW50ZXJwKHgsIHhwLCBmcCwgcGVyaW9kPTM2MCkpKQoKeCAgPSBbMS41LCA0LjBdCnhwID0gWzIsIDMsIDVdCmZwID0gWzEuMGosIDAsIDIrM2pdCgpwcmludChsaXN0KG5wLmludGVycCh4LCB4cCwgZnApKSkKcHJpbnQobGlzdChpbnRlcnAoeCwgeHAsIGZwKSkp