vDSP.convolve incorrectly reverses kernel?

vDSP.convolve() reverses the kernel before applying it.

For example, the following uses a kernel of 10 elements where the first element is 1.0 and the rest of the elements are 0.0. Applying this kernel to a vector should return the same vector.

let values = (0 ..< 30).map { Double($0) }
var kernel = Array.init(repeating: 0.0, count: 10)
kernel[0] = 1.0
let result = vDSP.convolve(values, withKernel: kernel)

print("kernel: \(kernel)")
print("values: \(values)")
print("result: \(result)")

Applied to a values array containing elements 0.0, 1.0, 2.0, etc. the first results should be 0.0, 1.0, 2.0, etc, but instead the results start at 9.0 and increase from there:

kernel: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
values: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0]
result: [9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0]

If instead the kernel is reversed, placing the 1.0 at the end of the kernel:

let values = (0 ..< 30).map { Double($0) }
var kernel = Array.init(repeating: 0.0, count: 10)
kernel[9] = 1.0
let result = vDSP.convolve(values, withKernel: kernel)

print("kernel: \(kernel)")
print("values: \(values)")
print("result: \(result)")

The results are now correct:

kernel: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]
values: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0]
result: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0]

This could be due to how vDSP_conv operates: https://developer.apple.com/documentation/accelerate/1450516-vdsp_conv/

If __IF is positive, vDSP_conv(_:_:_:_:_:_:_:_:) performs correlation. If __IF is negative, it performs convolution and F must point to the last vector element. The function can run in place, but C cannot be in place with F:

You are correct - the vDSP.convolve() is equivalent to running vDSP_conv with a negative stride over the filter. The following code compares the overlay function with the underlying C API classic convolution:

let values: [Float] = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let kernel: [Float] = [1, 1, 1]
let n = values.count - kernel.count + 1

// vDSP Swift Overlay

var overlayResult: [Float] = [Float](repeating: .nan, count: n)

vDSP.convolve(values,
              withKernel: kernel,
              result: &overlayResult)


// vDSP Classic API

var classicResult: [Float] = [Float](repeating: .nan, count: n)

classicResult.withUnsafeMutableBufferPointer { dest in
    values.withUnsafeBufferPointer { src in
        kernel.withUnsafeBufferPointer { k in
            vDSP_conv(src.baseAddress!, 1,
                      k.baseAddress!.advanced(by: kernel.count - 1), -1,
                      dest.baseAddress!, 1,
                      vDSP_Length(n),
                      vDSP_Length(kernel.count))
        }
    }
}


print(overlayResult.elementsEqual(classicResult))
vDSP.convolve incorrectly reverses kernel?
 
 
Q