これは私にとってうまくいくものです。おそらくいくつかのクリーンアップを使用できます。
class InterpolatedArray(object):
""" An array-like object that provides interpolated values between set points.
"""
points = None
wrap_value = None
offset = None
def _mod_delta(self, a, b):
""" Perform a difference within a modular domain.
Return a value in the range +/- wrap_value/2.
"""
limit = self.wrap_value / 2.
val = a - b
if val < -limit: val += self.wrap_value
elif val > limit: val -= self.wrap_value
return val
def __init__(self, points, wrap_value=None):
"""Initialization of InterpolatedArray instance.
Parameter 'points' is a list of two-element tuples, each of which maps
an input value to an output value. The list does not need to be sorted.
Optional parameter 'wrap_value' is used when the domain is closed, to
indicate that both the input and output domains wrap. For example, a
table of degree values would provide a 'wrap_value' of 360.0.
After sorting, a wrapped domain's output values must all be monotonic
in either the positive or negative direction.
For tables that don't wrap, attempts to interpolate values outside the
input range cause a ValueError exception.
"""
if wrap_value is None:
points.sort() # Sort in-place on first element of each tuple
else: # Force values to positive modular range
points = sorted([(p[0]%wrap_value, p[1]%wrap_value) for p in points])
# Wrapped domains must be monotonic, positive or negative
monotonic = [points[x][1] < points[x+1][1] for x in xrange(0,len(points)-1)]
num_pos_steps = monotonic.count(True)
num_neg_steps = monotonic.count(False)
if num_pos_steps > 1 and num_neg_steps > 1: # Allow 1 wrap point
raise ValueError("Table for wrapped domains must be monotonic.")
self.wrap_value = wrap_value
# Pre-compute inter-value slopes
self.x_list, self.y_list = zip(*points)
if wrap_value is None:
intervals = zip(self.x_list, self.x_list[1:], self.y_list, self.y_list[1:])
self.slopes = [(y2 - y1)/(x2 - x1) for x1, x2, y1, y2 in intervals]
else: # Create modular slopes, including wrap element
x_rot = list(self.x_list[1:]); x_rot.append(self.x_list[0])
y_rot = list(self.y_list[1:]); y_rot.append(self.y_list[0])
intervals = zip(self.x_list, x_rot, self.y_list, y_rot)
self.slopes = [self._mod_delta(y2, y1)/self._mod_delta(x2, x1) for x1, x2, y1, y2 in intervals]
def __getitem__(self, x): # Works with indexing operator []
result = None
if self.wrap_value is None:
if x < self.x_list[0] or x > self.x_list[-1]:
raise ValueError('Input value out-of-range: %s'%str(x))
i = bisect.bisect_left(self.x_list, x) - 1
result = self.y_list[i] + self.slopes[i] * (x - self.x_list[i])
else:
x %= self.wrap_value
i = bisect.bisect_left(self.x_list, x) - 1
result = self.y_list[i] + self.slopes[i] * self._mod_delta(x, self.x_list[i])
result %= self.wrap_value
return result
そしてテスト:
import nose
def xfrange(start, stop, step=1.):
""" Floating point equivalent to xrange()."""
while start < stop:
yield start
start += step
# Test simple inverted mapping for non-wrapped domain
pts = [(x,-x) for x in xfrange(1.,16., 1.)]
a = InterpolatedArray(pts)
for i in xfrange(1., 15., 0.1):
nose.tools.assert_almost_equal(a[i], -i)
# Cause expected over/under range errors
result = False # Assume failure
try: x = a[0.5]
except ValueError: result = True
assert result
result = False
try: x = a[15.5]
except ValueError: result = True
assert result
# Test simple wrapped domain
wrap = 360.
offset = 1.234
pts = [(x,((wrap/2.) - x)) for x in xfrange(offset, wrap+offset, 10.)]
a = InterpolatedArray(pts, wrap)
for i in xfrange(0.5, wrap, 0.1):
nose.tools.assert_almost_equal(a[i], (((wrap/2.) - i)%wrap))