1

I want to linearly interpolate some metrics that are captured at times that fluctuate, to fixed timing intervals.

let original_times:[Double] = [0.0,1.3,2.2,3.4,4.2,5.5,6.6,7.2,8.4,9.5,10.0]
let metric_1:[Double] = [4,3,6,7,4,5,7,4,2,7,2]

let wanted_times:[Double] = [0,1,2,3,4,5,6,7,8,9,10]

//linearly resample metric_1 (with corresponding sampling times 'original_times') to fixed time interval times 'wanted_times'

Accelerate offers vDSP_vlint but I'm struggling to figure out how to implement it for my application.

func vDSP_vlint(_ __A: UnsafePointer<Float>, _ __B: UnsafePointer<Float>, _ __IB: vDSP_Stride, _ __C: UnsafeMutablePointer<Float>, _ __IC: vDSP_Stride, _ __N: vDSP_Length, _ __M: vDSP_Length)
Ian
  • 1,427
  • 1
  • 15
  • 27

3 Answers3

2

I don't understand the math you want to do 100%, but I do understand how to use Accelerate. I created a function which makes it easier to call this Accelerate function and shows you how it works.

/**
 Vector linear interpolation between neighboring elements

 - Parameter a: Input vector.
 - Parameter b: Input vector: integer parts are indices into a and fractional parts are interpolation constants.

 Performs the following operation:

 ```C
 for (n = 0; n < N; ++n) {
    double b = B[n];
    double index = trunc([b]); //int part of B value
    double alpha = b - index; //frac part of B value

    double a0 = A[(int)index];     //indexed A value
    double a1 = A[(int)index + 1]; //next A value

    C[n] = a0 + (alpha * (a1 -a0)); //interpolated value
 }
 ```
 Generates vector C by interpolating between neighboring values of vector A as controlled by vector B. The integer portion of each element in B is the zero-based index of the first element of a pair of adjacent values in vector A.

 The value of the corresponding element of C is derived from these two values by linear interpolation, using the fractional part of the value in B.
*/
func interpolate(inout a: [Double], inout b: [Double]) -> [Double] {
    var c = [Double](count: b.count, repeatedValue: 0)
    vDSP_vlintD(&a, &b, 1, &c, 1, UInt(b.count), UInt(a.count))
    return c
}

EDIT: Alright, I wrapped my head around your problem, I understand now what you want to do. Was pretty fun to do, I came up with this:

import Accelerate

func calculateB(sampleTimes: [Double], outputTimes: [Double]) -> [Double] {
    var i = 0
    return outputTimes.map { (time: Double) -> Double in
        defer {
            if time > sampleTimes[i] { i++ }
        }
        return Double(i) + (time - sampleTimes[i]) / (sampleTimes[i+1] - sampleTimes[i])
    }
}

func interpolate(inout b: [Double], inout data: [Double]) -> [Double] {
    var c = [Double](count: b.count, repeatedValue: 0)
    vDSP_vlintD(&data, &b, 1, &c, 1, UInt(b.count), UInt(data.count))
    return c
}


let sampleTimes : [Double] = [0.0, 1.3, 2.2, 3.4, 4.2, 5.5, 6.6, 7.2, 8.4, 9.5, 10.0]
let outputTimes : [Double] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

var metric_1 : [Double] = [4, 3, 6, 7, 4, 5, 7, 4, 2, 7, 2]
var metric_2 : [Double] = [5, 4, 7, 5, 6, 6, 1, 3, 1, 6, 7]
var metric_3 : [Double] = [9, 8, 5, 7, 4, 8, 5, 6, 8, 9, 5]

var b = calculateB(sampleTimes, outputTimes: outputTimes)

interpolate(&b, data: &metric_1)   // [4, 3.230769, 5.333333, 6.666667, 4.75, 4.615385, 5.909091, 5, 2.666667, 4.727273, 2]
interpolate(&b, data: &metric_2)   // [5, 4.230769, 6.333333, 5.666667, 5.75, 6, 3.727273, 2.333333, 1.666667, 3.727273, 7]
interpolate(&b, data: &metric_3)   // [9, 8.230769, 5.666667, 6.333333, 4.75, 6.461538, 6.636364, 5.666667, 7.333333, 8.545455, 5]

The vars are necessary for Accelerate. I don't know how calculateB could be done with Accelerate, I mean it's possible I think, but it's a pain to search for the correct vDSP functions...

Kametrixom
  • 14,673
  • 7
  • 45
  • 62
  • Thanks for the tip. Nice simplification. What I'm trying to do is resample the data to from varying time interval sampling, to fixed time interval sampling. I'll try to clean up the question to help explain – Ian Sep 26 '15 at 21:31
  • 1
    @Ian I wrapped my head around it once more, I edited my answer – Kametrixom Sep 26 '15 at 22:08
  • Fantastic!! That's a perfect solution.. really appreciate the help!! Just going to implement now – Ian Sep 26 '15 at 22:14
  • I'm debugging some code and am seeing this behavior: vDSP_lint([-149.3, -149.5], [ 0.0, 0.5, 1.0], 1, output, 1, output.count, 2) is setting output to [ -149.3, -149.4, NaN]. Why isn't the last value -149.5 instead of NaN? – Ryan Oct 22 '15 at 00:46
1

Here's another solution without using any of the Accelerate stuff

public class Resampler {


///
/// ### Class method to resample some data
///
/// ### Inputs
/// - Actual time data that may not be regularly sampled
/// - Desired times you want the metric found at
/// - Metric data corresponding with actual time data
///
public class func resample( acualTimes atimes: [Double], desiredTimes dtimes: [Double], metric: [Double] ) ->[Double]
{
    //
    // Initialize the desired metrics array
    //
    var desiredMetrics: [Double] = [Double](count: dtimes.count, repeatedValue: 0);

    // Initialize a counter to keep track of which metric value we are on
    var counter: Int = 0;

    // Loop through the desired times
    for dtime in dtimes {

        // Find the bounding indices, based on actual time data, for the desired time
        // using a binary search
        let (li, ri) = binarySearch(0,highBound: atimes.count-1, desiredTime: dtime, timeData: atimes);

        // Find the desired metric using an interpolation
        desiredMetrics[counter] = linearInterpolate(lowTime: atimes[li],
                                                    highTime: atimes[ri],
                                                    lowMetric: metric[li],
                                                    highMetric: metric[ri],
                                                    desiredTime: dtime);
        // Increment the counter
        counter++;
    }

    // Return the desired metrics
    return desiredMetrics;
}


///
/// ### Binary search code to find the bounding time value indices
///
private class func binarySearch(  lowBound: Int,
                            highBound: Int,
                            desiredTime: Double,
                            timeData: [Double]) -> (leftIndex: Int, rightIndex: Int)
{
    if( highBound-lowBound == 1 ){
        return (lowBound, highBound);
    }else{
        let center: Int = (lowBound + highBound)/2;
        if( desiredTime <= timeData[center]){
            return binarySearch(lowBound, highBound: center, desiredTime: desiredTime, timeData: timeData);
        }else{
            return binarySearch(center, highBound: highBound, desiredTime: desiredTime, timeData: timeData);
        }
    }
}


///
/// ### Linear interpolation method
///
private class func linearInterpolate(   lowTime lt: Double,
                                        highTime ht: Double,
                                        lowMetric lm: Double,
                                        highMetric hm: Double,
                                        desiredTime dt: Double ) -> Double
{
    return lm + (dt-lt)*(hm-lm)/(ht-lt);
}

}

And then you can run it by just doing the following:

    let times: [Double] = [0.0,1.3,2.2,3.4,4.2,5.5,6.6,7.2,8.4,9.5,10.0];
    let desiredTimes: [Double] = [0,1,2,3,4,5,6,7,8,9,10];
    let metricData: [Double] = [4,3,6,7,4,5,7,4,2,7,2];

    let desiredMetrics = Resampler.resample(acualTimes: times, desiredTimes: desiredTimes, metric: metricData);
    print(desiredMetrics)

This example above will output:

[4.0, 3.23076923076923, 5.33333333333333, 6.66666666666667, 4.75, 4.61538461538461, 5.90909090909091, 5.0, 2.66666666666667, 4.72727272727273, 2.0]
spektr
  • 777
  • 5
  • 14
1

Another solution to the problem, 100% Accelerate:

import Accelerate

let metric_1: [Double] = [4.0, 3.0, 6.0, 7.0, 4.0, 5.0, 7.0, 4.0, 2.0, 7.0, 2.0]
let original_times: [Double] = [0.0, 1.3, 2.2, 3.4, 4.2, 5.5, 6.6, 7.2, 8.4, 9.5, 10.0]
let wanted_times: [Double] = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]

let count = metric_1.count
let length = vDSP_Length(count)
var output = [Double](repeating: 0, count: count)
var interpolationConstant = 2.0

// calculate interpolated times
vDSP_vintbD(original_times, 1, wanted_times, 1, &interpolationConstant, &output, 1, length)

// calculate interpolated values
vDSP_vlintD(metric_1, &output, 1, &output, 1, length, length)

output: [4.0,
         3.2999999999999998,
         5.3999999999999995,
         6.5999999999999996,
         4.6000000000000005,
         4.5,
         5.8000000000000007,
         4.6000000000000005,
         2.8000000000000007,
         4.5,
         2.0]
cromandini
  • 1,222
  • 1
  • 13
  • 19