# One-dimensional linear interpolation. (2.00)
# @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 + b * t
def invlerp(x, a, b):
if math.isclose(a, b):
return 0.5
return (x - a) / (b - a)
def modinvlerp(x, a, b, m):
t = (b - a) % m
if math.isclose(0, t):
return 0.5
return (x - a) % m / t
# 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_sort_index = sorted(range(n), key=lambda i: xp[i])
xp = [xp[i] for i in xp_sort_index]
fp = [fp[i] for i in xp_sort_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)))
IyBPbmUtZGltZW5zaW9uYWwgbGluZWFyIGludGVycG9sYXRpb24uICgyLjAwKQojIEBub3RlIFRoZSBwcm9kdWN0IG9mIHRyeWluZyB0byBmaWd1cmUgb3V0IHRoZSBiZWhhdmlvciBvZiBudW1weS5pbnRlcnAKIyBhZnRlciByZWFkaW5nIHRoZSBkb2N1bWVudGF0aW9uLiBJIHRoaW5rIHRoaXMgY2FwdHVyZXMgdGhlIGdpc3Qgb2YgaXQuCgppbXBvcnQgbWF0aAppbXBvcnQgYmlzZWN0CmltcG9ydCBudW1iZXJzCgojIFV0aWxpdHkuCgpkZWYgaXNfc2NhbGFyKHgpOgogICAgcmV0dXJuIGlzaW5zdGFuY2UoeCwgbnVtYmVycy5OdW1iZXIpCgpkZWYgaXNfbGVhc3RfZmxvYXQoeCk6CiAgICByZXR1cm4gaXNfc2NhbGFyKHgpIGFuZCBub3QgaXNpbnN0YW5jZSh4LCBudW1iZXJzLlJhdGlvbmFsKQoKZGVmIHRvX2xlYXN0X2Zsb2F0KHgpOgogICAgcmV0dXJuIHggaWYgaXNfbGVhc3RfZmxvYXQoeCkgZWxzZSBmbG9hdCh4KQoKZGVmIGxlcnAoYSwgYiwgdCk6CiAgICByZXR1cm4gKDEgLSB0KSAqIGEgKyBiICogdAoKZGVmIGludmxlcnAoeCwgYSwgYik6CiAgICBpZiBtYXRoLmlzY2xvc2UoYSwgYik6CiAgICAgICAgcmV0dXJuIDAuNQogICAgcmV0dXJuICh4IC0gYSkgLyAoYiAtIGEpCgpkZWYgbW9kaW52bGVycCh4LCBhLCBiLCBtKToKICAgIHQgPSAoYiAtIGEpICUgbQogICAgaWYgbWF0aC5pc2Nsb3NlKDAsIHQpOgogICAgICAgIHJldHVybiAwLjUKICAgIHJldHVybiAoeCAtIGEpICUgbSAvIHQKCiMgSW50ZXJwb2xhdGUuCgpkZWYgaW50ZXJwKHgsIHhwLCBmcCwgbGVmdD1Ob25lLCByaWdodD1Ob25lLCBwZXJpb2Q9Tm9uZSk6CiAgICAiIiIKICAgIExpbmVhciBpbnRlcnBvbGF0aW9uIGZvciBtb25vdG9uaWNhbGx5IGluY3JlYXNpbmcgc2FtcGxlIHBvaW50cy4KICAgICIiIgogICAgbiA9IGxlbih4cCkKCiAgICBpZiBuICE9IGxlbihmcCk6CiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigneHAgYW5kIGZwIGFyZSBub3Qgb2YgdGhlIHNhbWUgbGVuZ3RoJykKICAgIGlmIG4gPT0gMDoKICAgICAgICByZXR1cm4KCiAgICB4cCA9IGxpc3QobWFwKHRvX2xlYXN0X2Zsb2F0LCB4cCkpCiAgICBmcCA9IGxpc3QobWFwKHRvX2xlYXN0X2Zsb2F0LCBmcCkpCgogICAgaWYgcGVyaW9kIGlzIE5vbmU6CiAgICAgICAgcmV0dXJuIF9pbnRlcnBfYm91bmRlZChuLCB4LCB4cCwgZnAsIGxlZnQsIHJpZ2h0KQogICAgcmV0dXJuIF9pbnRlcnBfcGVyaW9kaWMobiwgeCwgeHAsIGZwLCBwZXJpb2QpCgpkZWYgX2ludGVycF9ib3VuZGVkKG4sIHgsIHhwLCBmcCwgbGVmdCwgcmlnaHQpOgogICAgaWYgbGVmdCBpcyBOb25lOgogICAgICAgIGxlZnQgPSBmcFswXQogICAgZWxzZToKICAgICAgICBsZWZ0ID0gdG9fbGVhc3RfZmxvYXQobGVmdCkKICAgIGlmIHJpZ2h0IGlzIE5vbmU6CiAgICAgICAgcmlnaHQgPSBmcFstMV0KICAgIGVsc2U6CiAgICAgICAgcmlnaHQgPSB0b19sZWFzdF9mbG9hdChyaWdodCkKCiAgICBmaXJzdCwgbGFzdCA9IHhwWzBdLCB4cFstMV0KCiAgICBmb3IgeSBpbiB4OgogICAgICAgIGlmIHkgPCBmaXJzdDoKICAgICAgICAgICAgeWllbGQgbGVmdAogICAgICAgIGVsaWYgeSA+IGxhc3Q6CiAgICAgICAgICAgIHlpZWxkIHJpZ2h0CiAgICAgICAgZWxzZToKICAgICAgICAgICAgaSA9IGJpc2VjdC5iaXNlY3QoeHAsIHkpCiAgICAgICAgICAgIGogPSBpLTEKICAgICAgICAgICAgayA9IG1pbihpLCBuLTEpCiAgICAgICAgICAgIHlpZWxkIGxlcnAoZnBbal0sIGZwW2tdLCBpbnZsZXJwKHksIHhwW2pdLCB4cFtrXSkpCgpkZWYgX2ludGVycF9wZXJpb2RpYyhuLCB4LCB4cCwgZnAsIHBlcmlvZCk6CiAgICBpZiBwZXJpb2QgPT0gMDoKICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdwZXJpb2QgbXVzdCBiZSBhIG5vbi16ZXJvIHZhbHVlJykKCiAgICAjIE5vcm1hbGl6ZSBwZXJpb2RpYyBib3VuZGFyaWVzOyByZW9yZGVyIHhwIGFuZCBmcC4KCiAgICB4cCA9IFt4ICUgcGVyaW9kIGZvciB4IGluIHhwXQogICAgeHBfc29ydF9pbmRleCA9IHNvcnRlZChyYW5nZShuKSwga2V5PWxhbWJkYSBpOiB4cFtpXSkKICAgIHhwID0gW3hwW2ldIGZvciBpIGluIHhwX3NvcnRfaW5kZXhdCiAgICBmcCA9IFtmcFtpXSBmb3IgaSBpbiB4cF9zb3J0X2luZGV4XQoKICAgIGZvciB5IGluIHg6CiAgICAgICAgaSA9IGJpc2VjdC5iaXNlY3QoeHAsIHkgJSBwZXJpb2QpCiAgICAgICAgaiA9IChpLTEpICUgbgogICAgICAgIGsgPSBpICUgbgogICAgICAgIHlpZWxkIGxlcnAoZnBbal0sIGZwW2tdLCBtb2RpbnZsZXJwKHksIHhwW2pdLCB4cFtrXSwgcGVyaW9kKSkKCiMgTWFpbi4KCmltcG9ydCBudW1weSBhcyBucAoKeCAgPSBbMCwgMSwgMS41LCAyLjczLCAzLCAzLjE0XQp4cCA9IFsxLCAyLCAzXQpmcCA9IFszLCAyLCAwXQoKcHJpbnQobGlzdChucC5pbnRlcnAoWzIuNV0sIHhwLCBmcCkpKQpwcmludChsaXN0KGludGVycChbMi41XSwgeHAsIGZwKSkpCnByaW50KGxpc3QobnAuaW50ZXJwKHgsIHhwLCBmcCkpKQpwcmludChsaXN0KGludGVycCh4LCB4cCwgZnApKSkKClVOREVGID0gOTkKcHJpbnQobGlzdChucC5pbnRlcnAoeCwgeHAsIGZwLCBsZWZ0PS1VTkRFRiwgcmlnaHQ9VU5ERUYpKSkKcHJpbnQobGlzdChpbnRlcnAoeCwgeHAsIGZwLCBsZWZ0PS1VTkRFRiwgcmlnaHQ9VU5ERUYpKSkKCnggID0gWy0xODAsIC0xNzAsIC0xODUsIDE4NSwgLTEwLCAtNSwgMCwgMzY1XQp4cCA9IFsxOTAsIC0xOTAsIDM1MCwgLTM1MF0KZnAgPSBbNSwgMTAsIDMsIDRdCgpwcmludChsaXN0KG5wLmludGVycCh4LCB4cCwgZnAsIHBlcmlvZD0zNjApKSkKcHJpbnQobGlzdChpbnRlcnAoeCwgeHAsIGZwLCBwZXJpb2Q9MzYwKSkpCgp4ICA9IFsxLjUsIDQuMF0KeHAgPSBbMiwgMywgNV0KZnAgPSBbMS4waiwgMCwgMiszal0KCnByaW50KGxpc3QobnAuaW50ZXJwKHgsIHhwLCBmcCkpKQpwcmludChsaXN0KGludGVycCh4LCB4cCwgZnApKSk=