Reputation: 13
I'm converting a script from Python to Swift. In the original Python code I'm doing a simple interpolation between two lists, using Numpy interp
in the following format:
>>> xp = [1, 2, 3]
>>> fp = [3, 2, 0]
>>> np.interp(2.5, xp, fp)
1.0
This is from the Numpy documentation, not my actual code, but this is exactly what I require and have used in the Python script. In short, I have the x and y axes as arrays, and I want to do one-dimensional interpolation to evaluate an x coordinate.
All this works great with Python/Numpy, but I cannot get it to work in Swift. The Accelerate vDSP framework has linearInterpolate(VectorA, VectorB, using: X)
, but it returns an array rather than a single value.
var y = vDSP.linearInterpolate([1.0, 2.0, 3.0], [3.0, 2.0, 0.0], using: 2.5)
Results in:
[6, 2, -4.5]
This should be very simple, and I guess one option is to just write my own function. But surely there must be an easy one-liner way to do this? I'm new to Swift and it feels like I'm missing something very obvious.
Upvotes: 1
Views: 873
Reputation: 437882
That rendition of linearInterpolate(_:_:using:)
is designed to solve a very different problem:
For example, the following code creates two arrays,
vectorA
andvectorB
, that contain sine waves:let n = 1024 let vectorA: [Float] = (0 ... n).map { return 2 + sin(Float($0) * 0.07) } let vectorB: [Float] = (0 ... n).map { return -2 + sin(Float($0) * 0.03) }
Use
linearInterpolate(_:_:using:)
with an interpolation constant of0.5
to generate a new vector that's the average of the two sine waves:let result = vDSP.linearInterpolate(vectorA, vectorB, using: 0.5)
The following figure visualizes the two source vectors: the blue lines at the top and bottom, and the interpolation result: the red line in the center:
The linearInterpolate(elementsOf:using:)
does something closer to what you are looking for (though you only supply the y values, and it assumes integer x index values).
So consider:
>>> xp = [0, 1, 2]
>>> fp = [3, 2, 0]
>>> np.interp(1.5, xp, fp)
1.0
The equivalent vDSP call might be:
let z = vDSP.linearInterpolate(elementsOf: [3.0, 2.0, 0.0], using: [1.5])
[1.0]
Now that employs implicit x values that are a zero-based index (e.g. 0, 1, 2, etc.), thus I tweaked your Python example to use zero-based index values for x.
So, this vDSP solution is a special case of interpolation between neighboring values, but I don't know whether that is sufficient, or whether you needed a more general solution (for arbitrary x values in your input). If so, yes, you might just have to write your own little helper function.
Here is an example of a more general solution that retrieves for only one x value:
func linearInterpolate<S, T>(_ sequenceX: S, _ sequenceY: S, at index: T) -> T? where S: Sequence, S.Element == T, T: FloatingPoint {
var xIterator = sequenceX.makeIterator()
var yIterator = sequenceY.makeIterator()
guard
var previousX = xIterator.next(),
var previousY = yIterator.next()
else {
return nil
}
while true {
if index == previousX { return previousY }
guard
let x = xIterator.next(),
let y = yIterator.next()
else {
return nil
}
if (index > previousX && index < x) || (index > x && index < previousX) {
let percent = (index - previousX) / (x - previousX)
return previousY + percent * (y - previousY)
}
previousX = x
previousY = y
}
}
and
let xp: [Double] = [1, 2, 3]
let fp: [Double] = [3, 2, 0]
if let y = linearInterpolate(xp, fp, at: 2.5) {
print(y) // 1.0
}
Upvotes: 1