Repository: wearereasonablepeople/KalmanFilter
Branch: master
Commit: 4f2f2b57b74e
Files: 17
Total size: 51.8 KB
Directory structure:
gitextract_m49pruxa/
├── .gitignore
├── .travis.yml
├── KalmanFilter/
│ ├── DoubleExtension.swift
│ ├── Info.plist
│ ├── KalmanFilter.h
│ ├── KalmanFilter.swift
│ ├── KalmanFilterType.swift
│ └── Matrix.swift
├── KalmanFilter.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── KalmanFilter.xcscheme
├── KalmanFilterTests/
│ ├── DoubleExtensionTests.swift
│ ├── Info.plist
│ ├── KalmanFilterTests.swift
│ └── MatrixTests.swift
├── LICENSE
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
KalmanFilter.xcodeproj/xcuserdata
================================================
FILE: .travis.yml
================================================
language: objective-c
osx_image: xcode9.1
script:
- xcodebuild clean build test -project KalmanFilter.xcodeproj -scheme KalmanFilter -destination 'platform=iOS Simulator,name=iPhone 8'
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
email: true
================================================
FILE: KalmanFilter/DoubleExtension.swift
================================================
//
// DoubleExtension.swift
// KalmanFilterTest
//
// Created by Oleksii on 20/06/16.
// Copyright © 2016 Oleksii Dykan. All rights reserved.
//
import Foundation
// MARK: Double as Kalman input
extension Double: KalmanInput {
public var transposed: Double {
return self
}
public var inversed: Double {
return 1 / self
}
public var additionToUnit: Double {
return 1 - self
}
}
================================================
FILE: KalmanFilter/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
FMWK
CFBundleShortVersionString
0.1.0
CFBundleSignature
????
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSPrincipalClass
================================================
FILE: KalmanFilter/KalmanFilter.h
================================================
//
// KalmanFilter.h
// KalmanFilter
//
// Created by Oleksii on 17/08/16.
// Copyright © 2016 Oleksii Dykan. All rights reserved.
//
#import
//! Project version number for KalmanFilter.
FOUNDATION_EXPORT double KalmanFilterVersionNumber;
//! Project version string for KalmanFilter.
FOUNDATION_EXPORT const unsigned char KalmanFilterVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import
================================================
FILE: KalmanFilter/KalmanFilter.swift
================================================
//
// KalmanFilter.swift
// KalmanFilter
//
// Created by Oleksii on 18/08/16.
// Copyright © 2016 Oleksii Dykan. All rights reserved.
//
import Foundation
/**
Conventional Kalman Filter
*/
public struct KalmanFilter: KalmanFilterType {
/// x̂_k|k-1
public let stateEstimatePrior: Type
/// P_k|k-1
public let errorCovariancePrior: Type
public init(stateEstimatePrior: Type, errorCovariancePrior: Type) {
self.stateEstimatePrior = stateEstimatePrior
self.errorCovariancePrior = errorCovariancePrior
}
/**
Predict step in Kalman filter.
- parameter stateTransitionModel: F_k
- parameter controlInputModel: B_k
- parameter controlVector: u_k
- parameter covarianceOfProcessNoise: Q_k
- returns: Another instance of Kalman filter with predicted x̂_k and P_k
*/
public func predict(stateTransitionModel: Type, controlInputModel: Type, controlVector: Type, covarianceOfProcessNoise: Type) -> KalmanFilter {
// x̂_k|k-1 = F_k * x̂_k-1|k-1 + B_k * u_k
let predictedStateEstimate = stateTransitionModel * stateEstimatePrior + controlInputModel * controlVector
// P_k|k-1 = F_k * P_k-1|k-1 * F_k^t + Q_k
let predictedEstimateCovariance = stateTransitionModel * errorCovariancePrior * stateTransitionModel.transposed + covarianceOfProcessNoise
return KalmanFilter(stateEstimatePrior: predictedStateEstimate, errorCovariancePrior: predictedEstimateCovariance)
}
/**
Update step in Kalman filter. We update our prediction with the measurements that we make
- parameter measurement: z_k
- parameter observationModel: H_k
- parameter covarienceOfObservationNoise: R_k
- returns: Updated with the measurements version of Kalman filter with new x̂_k and P_k
*/
public func update(measurement: Type, observationModel: Type, covarienceOfObservationNoise: Type) -> KalmanFilter {
// H_k^t transposed. We cache it improve performance
let observationModelTransposed = observationModel.transposed
// ỹ_k = z_k - H_k * x̂_k|k-1
let measurementResidual = measurement - observationModel * stateEstimatePrior
// S_k = H_k * P_k|k-1 * H_k^t + R_k
let residualCovariance = observationModel * errorCovariancePrior * observationModelTransposed + covarienceOfObservationNoise
// K_k = P_k|k-1 * H_k^t * S_k^-1
let kalmanGain = errorCovariancePrior * observationModelTransposed * residualCovariance.inversed
// x̂_k|k = x̂_k|k-1 + K_k * ỹ_k
let posterioriStateEstimate = stateEstimatePrior + kalmanGain * measurementResidual
// P_k|k = (I - K_k * H_k) * P_k|k-1
let posterioriEstimateCovariance = (kalmanGain * observationModel).additionToUnit * errorCovariancePrior
return KalmanFilter(stateEstimatePrior: posterioriStateEstimate, errorCovariancePrior: posterioriEstimateCovariance)
}
}
================================================
FILE: KalmanFilter/KalmanFilterType.swift
================================================
//
// KalmanFilterType.swift
// KalmanFilter
//
// Created by Oleksii on 18/08/16.
// Copyright © 2016 Oleksii Dykan. All rights reserved.
//
import Foundation
public protocol KalmanInput {
var transposed: Self { get }
var inversed: Self { get }
var additionToUnit: Self { get }
static func + (lhs: Self, rhs: Self) -> Self
static func - (lhs: Self, rhs: Self) -> Self
static func * (lhs: Self, rhs: Self) -> Self
}
public protocol KalmanFilterType {
associatedtype Input: KalmanInput
var stateEstimatePrior: Input { get }
var errorCovariancePrior: Input { get }
func predict(stateTransitionModel: Input, controlInputModel: Input, controlVector: Input, covarianceOfProcessNoise: Input) -> Self
func update(measurement: Input, observationModel: Input, covarienceOfObservationNoise: Input) -> Self
}
================================================
FILE: KalmanFilter/Matrix.swift
================================================
//
// Matrix.swift
// KalmanFilterTest
//
// Created by Oleksii on 20/06/16.
// Copyright © 2016 Oleksii Dykan. All rights reserved.
//
import Foundation
import Accelerate
public struct Matrix: Equatable {
// MARK: - Properties
public let rows: Int, columns: Int
public var grid: [Double]
var isSquare: Bool {
return rows == columns
}
// MARK: - Initialization
/**
Initialization of matrix with rows * columns
size where all the elements are set to 0.0
- parameter rows: number of rows in matrix
- parameter columns: number of columns in matrix
*/
public init(rows: Int, columns: Int) {
let grid = Array(repeating: 0.0, count: rows * columns)
self.init(grid: grid, rows: rows, columns: columns)
}
/**
Initialization with grid that contains all the
elements of matrix with given matrix size
- parameter grid: array of matrix elements. **warning**
Should be of rows * column size.
- parameter rows: number of rows in matrix
- parameter columns: number of columns in matrix
*/
public init(grid: [Double], rows: Int, columns: Int) {
assert(rows * columns == grid.count, "grid size should be rows * column size")
self.rows = rows
self.columns = columns
self.grid = grid
}
/**
Initialization of
[column vector](https://en.wikipedia.org/wiki/Row_and_column_vectors)
with given array. Number of
elements in array equals to number of rows in vector.
- parameter vector: array with elements of vector
*/
public init(vector: [Double]) {
self.init(grid: vector, rows: vector.count, columns: 1)
}
/**
Initialization of
[column vector](https://en.wikipedia.org/wiki/Row_and_column_vectors)
with given number of rows. Every element is assign to 0.0
- parameter size: vector size
*/
public init(vectorOf size: Int) {
self.init(rows: size, columns: 1)
}
/**
Initialization of square matrix with given size. Number of
elements in array equals to size * size. Every elements is
assigned to 0.0
- parameter size: number of rows and columns in matrix
*/
public init(squareOfSize size: Int) {
self.init(rows: size, columns: size)
}
/**
Initialization of
[identity matrix](https://en.wikipedia.org/wiki/Identity_matrix)
of given sizen
- parameter size: number of rows and columns in identity matrix
*/
public init(identityOfSize size: Int) {
self.init(squareOfSize: size)
for i in 0.. Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
public subscript(row: Int, column: Int) -> Double {
get {
assert(indexIsValid(forRow: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValid(forRow: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
// MARK: - Equatable
public func == (lhs: Matrix, rhs: Matrix) -> Bool {
return lhs.rows == rhs.rows && lhs.columns == rhs.columns && lhs.grid == rhs.grid
}
// MARK: - Matrix as KalmanInput
extension Matrix: KalmanInput {
/**
[Transposed](https://en.wikipedia.org/wiki/Transpose)
version of matrix
Compexity: O(n^2)
*/
public var transposed: Matrix {
var resultMatrix = Matrix(rows: columns, columns: rows)
let columnLength = resultMatrix.columns
let rowLength = resultMatrix.rows
grid.withUnsafeBufferPointer { xp in
resultMatrix.grid.withUnsafeMutableBufferPointer { rp in
vDSP_mtransD(xp.baseAddress!, 1, rp.baseAddress!, 1, vDSP_Length(rowLength), vDSP_Length(columnLength))
}
}
return resultMatrix
}
/**
Addition to Unit in form: **I - A**
where **I** - is
[identity matrix](https://en.wikipedia.org/wiki/Identity_matrix)
and **A** - is self
**warning** Only for square matrices
Complexity: O(n ^ 2)
*/
public var additionToUnit: Matrix {
assert(isSquare, "Matrix should be square")
return Matrix(identityOfSize: rows) - self
}
/**
Inversed matrix if
[it is invertible](https://en.wikipedia.org/wiki/Invertible_matrix)
*/
public var inversed: Matrix {
assert(isSquare, "Matrix should be square")
if rows == 1 {
return Matrix(grid: [1/self[0, 0]], rows: 1, columns: 1)
}
var inMatrix:[Double] = grid
// Get the dimensions of the matrix. An NxN matrix has N^2
// elements, so sqrt( N^2 ) will return N, the dimension
var N:__CLPK_integer = __CLPK_integer(sqrt(Double(grid.count)))
var N2:__CLPK_integer = N
var N3:__CLPK_integer = N
var lwork = __CLPK_integer(grid.count)
// Initialize some arrays for the dgetrf_(), and dgetri_() functions
var pivots:[__CLPK_integer] = [__CLPK_integer](repeating: 0, count: grid.count)
var workspace:[Double] = [Double](repeating: 0.0, count: grid.count)
var error: __CLPK_integer = 0
// Perform LU factorization
dgetrf_(&N, &N2, &inMatrix, &N3, &pivots, &error)
// Calculate inverse from LU factorization
dgetri_(&N, &inMatrix, &N2, &pivots, &workspace, &lwork, &error)
if error != 0 {
assertionFailure("Matrix Inversion Failure")
}
return Matrix.init(grid: inMatrix, rows: rows, columns: rows)
}
/**
[Matrix determinant](https://en.wikipedia.org/wiki/Determinant)
*/
public var determinant: Double {
assert(isSquare, "Matrix should be square")
var result = 0.0
if rows == 1 {
result = self[0, 0]
} else {
for i in 0.. Matrix {
assert(indexIsValid(forRow: row, column: column), "Invalid arguments")
var resultMatrix = Matrix(rows: rows - 1, columns: columns - 1)
for i in 0.. Double) -> Matrix {
assert(rows == otherMatrix.rows && columns == otherMatrix.columns, "Matrices should be of equal size")
var resultMatrix = Matrix(rows: rows, columns: columns)
for i in 0.. Matrix {
assert(lhs.rows == rhs.rows && lhs.columns == rhs.columns, "Matrices should be of equal size")
var resultMatrix = Matrix(rows: lhs.rows, columns: lhs.columns)
vDSP_vaddD(lhs.grid, vDSP_Stride(1), rhs.grid, vDSP_Stride(1), &resultMatrix.grid, vDSP_Stride(1), vDSP_Length(lhs.rows * lhs.columns))
return resultMatrix
}
/**
Naive subtract matrices
Complexity: O(n^2)
*/
public func - (lhs: Matrix, rhs: Matrix) -> Matrix {
assert(lhs.rows == rhs.rows && lhs.columns == rhs.columns, "Matrices should be of equal size")
var resultMatrix = Matrix(rows: lhs.rows, columns: lhs.columns)
vDSP_vsubD(rhs.grid, vDSP_Stride(1), lhs.grid, vDSP_Stride(1), &resultMatrix.grid, vDSP_Stride(1), vDSP_Length(lhs.rows * lhs.columns))
return resultMatrix
}
/**
Naive matrices multiplication
Complexity: O(n^3)
*/
public func * (lhs: Matrix, rhs: Matrix) -> Matrix {
assert(lhs.columns == rhs.rows, "Left matrix columns should be the size of right matrix's rows")
var resultMatrix = Matrix(rows: lhs.rows, columns: rhs.columns)
let order = CblasRowMajor
let atrans = CblasNoTrans
let btrans = CblasNoTrans
let α = 1.0
let β = 1.0
let resultColumns = resultMatrix.columns
lhs.grid.withUnsafeBufferPointer { pa in
rhs.grid.withUnsafeBufferPointer { pb in
resultMatrix.grid.withUnsafeMutableBufferPointer { pc in
cblas_dgemm(order, atrans, btrans, Int32(lhs.rows), Int32(rhs.columns), Int32(lhs.columns), α, pa.baseAddress!, Int32(lhs.columns), pb.baseAddress!, Int32(rhs.columns), β, pc.baseAddress!, Int32(resultColumns))
}
}
}
return resultMatrix
}
// MARK: - Nice additional methods
public func * (lhs: Matrix, rhs: Double) -> Matrix {
return Matrix(grid: lhs.grid.map({ $0*rhs }), rows: lhs.rows, columns: lhs.columns)
}
public func * (lhs: Double, rhs: Matrix) -> Matrix {
return rhs * lhs
}
// MARK: - CustomStringConvertible for debug output
extension Matrix: CustomStringConvertible {
public var description: String {
var description = ""
for i in 0..
================================================
FILE: KalmanFilter.xcodeproj/xcshareddata/xcschemes/KalmanFilter.xcscheme
================================================
================================================
FILE: KalmanFilterTests/DoubleExtensionTests.swift
================================================
//
// DoubleExtensionTests.swift
// KalmanFilterTest
//
// Created by Oleksii on 02/07/16.
// Copyright © 2016 Oleksii Dykan. All rights reserved.
//
import XCTest
@testable import KalmanFilter
class DoubleExtensionTests: XCTestCase {
func testDoubleAsKalmanInput() {
XCTAssertEqual(5.2.transposed, 5.2)
XCTAssertEqual(2.0.inversed, 0.5)
XCTAssertEqual(0.2.additionToUnit, 0.8)
}
}
================================================
FILE: KalmanFilterTests/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
BNDL
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1
================================================
FILE: KalmanFilterTests/KalmanFilterTests.swift
================================================
//
// KalmanFilterTests.swift
// KalmanFilterTests
//
// Created by Oleksii on 17/08/16.
// Copyright © 2016 Oleksii Dykan. All rights reserved.
//
import XCTest
@testable import KalmanFilter
class KalmanFilterTests: XCTestCase {
func testKalmanFilter2D() {
let measurements = [1.0, 2.0, 3.0]
let accuracy = 0.00001
let x = Matrix(vector: [0, 0])
let P = Matrix(grid: [1000, 0, 0, 1000], rows: 2, columns: 2)
let B = Matrix(identityOfSize: 2)
let u = Matrix(vector: [0, 0])
let F = Matrix(grid: [1, 1, 0, 1], rows: 2, columns: 2)
let H = Matrix(grid: [1, 0], rows: 1, columns: 2)
let R = Matrix(grid: [1], rows: 1, columns: 1)
let Q = Matrix(rows: 2, columns: 2)
var kalmanFilter = KalmanFilter(stateEstimatePrior: x, errorCovariancePrior: P)
for measurement in measurements {
let z = Matrix(grid: [measurement], rows: 1, columns: 1)
kalmanFilter = kalmanFilter.update(measurement: z, observationModel: H, covarienceOfObservationNoise: R)
kalmanFilter = kalmanFilter.predict(stateTransitionModel: F, controlInputModel: B, controlVector: u, covarianceOfProcessNoise: Q)
}
let resultX = Matrix(vector: [3.9996664447958645, 0.9999998335552873])
let resultP = Matrix(grid: [2.3318904241194827, 0.9991676099921091, 0.9991676099921067, 0.49950058263974184], rows: 2, columns: 2)
XCTAssertEqual(kalmanFilter.stateEstimatePrior[0, 0], resultX[0, 0], accuracy: accuracy)
XCTAssertEqual(kalmanFilter.stateEstimatePrior[1, 0], resultX[1, 0], accuracy: accuracy)
XCTAssertEqual(kalmanFilter.errorCovariancePrior[0, 0], resultP[0, 0], accuracy: accuracy)
XCTAssertEqual(kalmanFilter.errorCovariancePrior[0, 1], resultP[0, 1], accuracy: accuracy)
XCTAssertEqual(kalmanFilter.errorCovariancePrior[1, 0], resultP[1, 0], accuracy: accuracy)
XCTAssertEqual(kalmanFilter.errorCovariancePrior[1, 1], resultP[1, 1], accuracy: accuracy)
}
}
================================================
FILE: KalmanFilterTests/MatrixTests.swift
================================================
//
// MatrixTests.swift
// KalmanFilterTest
//
// Created by Oleksii on 20/06/16.
// Copyright © 2016 Oleksii Dykan. All rights reserved.
//
import XCTest
@testable import KalmanFilter
class MatrixTests: XCTestCase {
func testMatrixIsSquare() {
XCTAssertTrue(Matrix(identityOfSize: 2).isSquare)
XCTAssertFalse(Matrix(rows: 3, columns: 1).isSquare)
}
func testMatrixEquatable() {
var matrixOne = Matrix(rows: 1, columns: 2)
var matrixTwo = Matrix(rows: 1, columns: 2)
XCTAssertTrue(matrixOne == matrixTwo)
XCTAssertTrue(matrixTwo == matrixOne)
matrixOne[0, 0] = 1
XCTAssertFalse(matrixOne == matrixTwo)
matrixOne[0, 0] = 0
XCTAssertTrue(matrixTwo == matrixOne)
matrixTwo = Matrix(rows: 2, columns: 1)
XCTAssertFalse(matrixOne == matrixTwo)
}
func testMatrixInitialization() {
let rows = 4
let columns = 3
let matrix = Matrix(rows: rows, columns: columns)
XCTAssertEqual(matrix.rows, rows)
XCTAssertEqual(matrix.columns, columns)
XCTAssertEqual(matrix.grid.count, rows * columns)
let squareMatrix = Matrix(squareOfSize: rows)
XCTAssertEqual(squareMatrix.rows, rows)
XCTAssertEqual(squareMatrix.columns, rows)
XCTAssertEqual(squareMatrix.grid.count, rows * rows)
let identityMatrixSize = 3
let identityMatrix = Matrix(identityOfSize: identityMatrixSize)
var identityMatrixProper = Matrix(squareOfSize: identityMatrixSize)
identityMatrixProper[0, 0] = 1
identityMatrixProper[1, 1] = 1
XCTAssertNotEqual(identityMatrix, identityMatrixProper)
identityMatrixProper[2, 2] = 1
XCTAssertEqual(identityMatrix, identityMatrixProper)
let vectorMatrixEmpty = Matrix(vectorOf: 2)
XCTAssertEqual(vectorMatrixEmpty.rows, 2)
XCTAssertEqual(vectorMatrixEmpty.columns, 1)
let vectorMatrix = Matrix(vector: [2, 1, 3])
XCTAssertEqual(vectorMatrix.rows, 3)
XCTAssertEqual(vectorMatrix.columns, 1)
XCTAssertEqual(vectorMatrix[0, 0], 2)
XCTAssertEqual(vectorMatrix[1, 0], 1)
XCTAssertEqual(vectorMatrix[2, 0], 3)
let array2d = [[1.0, 0.0], [0.0, 1.0]]
XCTAssertEqual(Matrix(array2d), Matrix(identityOfSize: 2))
XCTAssertEqual(Matrix([[2.0], [1], [3]]), vectorMatrix)
}
func testMatrixCheckForSquare() {
XCTAssertTrue(Matrix(rows: 2, columns: 2).isSquare)
XCTAssertTrue(Matrix(squareOfSize: 2).isSquare)
XCTAssertTrue(Matrix(identityOfSize: 2).isSquare)
XCTAssertFalse(Matrix(rows: 3, columns: 2).isSquare)
XCTAssertFalse(Matrix(rows: 2, columns: 3).isSquare)
}
func testMatrixIndexValidation() {
let rows = 2
let columns = 3
let matrix = Matrix(rows: rows, columns: columns)
XCTAssertTrue(matrix.indexIsValid(forRow: 0, column: 0))
XCTAssertTrue(matrix.indexIsValid(forRow: rows - 1, column: columns - 1))
XCTAssertFalse(matrix.indexIsValid(forRow: rows, column: columns))
XCTAssertFalse(matrix.indexIsValid(forRow: -1, column: -1))
}
// MARK: Matrix Kalman Filter Extension Tests
func testMatrixTranspose() {
let initialMatrix = Matrix([[5, 4], [4, 0], [7, 10], [-1, 8]])
let transposedMatrixProper = Matrix([[5, 4, 7, -1], [4, 0, 10, 8]])
XCTAssertEqual(initialMatrix.transposed, transposedMatrixProper)
}
func testAdditionToUnit() {
let initialMatrix = Matrix([[4, 7, 1], [-2, 8, 3], [5, -4, 11]])
let properAddiotionToUnitMatrix = Matrix([[-3, -7, -1], [2, -7, -3], [-5, 4, -10]])
XCTAssertEqual(initialMatrix.additionToUnit, properAddiotionToUnitMatrix)
}
func testMatrixDeterminant() {
let initialMatrix = Matrix([[-2, 2, -3], [-1, 1, 3], [2, 0, -1]])
XCTAssertEqual(initialMatrix.determinant, 18)
}
func testMatrixInversed() {
let initialMatrix = Matrix([[1, 2, 3], [0, 1, 4], [5, 6, 0]])
// Using accelerate causes a very slight precision issue
let properInversedMatrix = Matrix([[-24.000000000000089, 18.000000000000068, 5.0000000000000178], [20.000000000000075, -15.000000000000055, -4.0000000000000142], [-5.0000000000000195, 4.0000000000000133, 1.0000000000000033]])
XCTAssertEqual(initialMatrix.inversed, properInversedMatrix)
XCTAssertEqual(Matrix(grid: [2], rows: 1, columns: 1).inversed, Matrix(grid: [1.0/2], rows: 1, columns: 1))
}
func testMatrixAdditionAndSubtraction() {
let size = (2, 3)
let matrixOne = Matrix([[5, 7, 9], [11, -2, -3]])
let matrixTwo = Matrix([[-8, 4, 9], [6, 3, 2]])
var additionMatrix = Matrix(rows: size.0, columns: size.1)
var subtractionMatrix = Matrix(rows: size.0, columns: size.1)
additionMatrix[0, 0] = matrixOne[0, 0] + matrixTwo[0, 0]
additionMatrix[0, 1] = matrixOne[0, 1] + matrixTwo[0, 1]
additionMatrix[0, 2] = matrixOne[0, 2] + matrixTwo[0, 2]
additionMatrix[1, 0] = matrixOne[1, 0] + matrixTwo[1, 0]
additionMatrix[1, 1] = matrixOne[1, 1] + matrixTwo[1, 1]
additionMatrix[1, 2] = matrixOne[1, 2] + matrixTwo[1, 2]
XCTAssertEqual(matrixOne + matrixTwo, additionMatrix)
subtractionMatrix[0, 0] = matrixOne[0, 0] - matrixTwo[0, 0]
subtractionMatrix[0, 1] = matrixOne[0, 1] - matrixTwo[0, 1]
subtractionMatrix[0, 2] = matrixOne[0, 2] - matrixTwo[0, 2]
subtractionMatrix[1, 0] = matrixOne[1, 0] - matrixTwo[1, 0]
subtractionMatrix[1, 1] = matrixOne[1, 1] - matrixTwo[1, 1]
subtractionMatrix[1, 2] = matrixOne[1, 2] - matrixTwo[1, 2]
XCTAssertEqual(matrixOne - matrixTwo, subtractionMatrix)
}
func testMatrixMultiplication() {
let matrixOne = Matrix([[3.0, 4, 2]])
let matrixTwo = Matrix([[13, 9, 7, 15], [8, 7, 4, 6], [6, 4, 0, 3]])
let multipliedMatrices = Matrix([[83.0, 63, 37, 75]])
XCTAssertEqual(matrixOne * matrixTwo, multipliedMatrices)
}
func testMatrixMultiplicationByScalar() {
let matrix = Matrix(grid: [1, 2, 3, 4], rows: 2, columns: 2)
XCTAssertEqual(matrix * 2, Matrix([[2, 4], [6, 8]]))
XCTAssertEqual(2 * matrix, Matrix([[2, 4], [6, 8]]))
XCTAssertEqual(matrix * 0.5, Matrix([[0.5, 1], [1.5, 2]]))
}
func testMatrixStringDescription() {
let matrix = Matrix([[0.0, 2.0, 3.0, 4.0],
[0.0, 2.0, 3.0, 4.0],
[0.0, 2.0, 3.0, 4.0]])
let string = "⎛\t0.0\t2.0\t3.0\t4.0\t⎞\n" +
"⎜\t0.0\t2.0\t3.0\t4.0\t⎥\n" +
"⎝\t0.0\t2.0\t3.0\t4.0\t⎠\n"
XCTAssertEqual(matrix.description, string)
XCTAssertEqual(Matrix([[0.0, 2.0, 3.0, 4.0]]).description, "(\t0.0\t2.0\t3.0\t4.0\t)\n")
}
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 WEAREREASONABLEPEOPLE B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# KalmanFilter
Swift implementation of Conventional Kalman Filter algorithm
[](https://github.com/Carthage/Carthage)
[](https://travis-ci.org/wearereasonablepeople/KalmanFilter)
[](https://codecov.io/gh/wearereasonablepeople/KalmanFilter)
## Motivation:
[Kalman filter](https://en.wikipedia.org/wiki/Kalman_filter) is a widely applied algorithm to get a more
accurate guess in noisy environment. It has a lot of applications in real life such as guidance, navigation
control for vehicles, etc. **Although it is mostly used to filter GPS data, this framework doesn't have a
ready-to-use solutions that work with GPS and is more general implementation of algorithm.**
## Example of usage
`Kalman filter` can work with anything that adopts `KalmanInput` protocol. Framework provides `Matrix`
struct that conforms to this protocol, although you can use anything that is more suitable for you. For
example, framework also provides `Double`'s extension with `KalmanInput` and you can use it if your
`KalmanFilter` has only 1 dimension.
The code below is the example of usage of `1D KalmanFilter` taken from
[here](http://bilgin.esme.org/BitsAndBytes/KalmanFilterforDummies).
```swift
let measurements = [0.39, 0.50, 0.48, 0.29, 0.25, 0.32, 0.34, 0.48, 0.41, 0.45, 0.46, 0.59, 0.42]
var filter = KalmanFilter(stateEstimatePrior: 0.0, errorCovariancePrior: 1)
for measurement in measurements {
let prediction = filter.predict(1, controlInputModel: 0, controlVector: 0, covarianceOfProcessNoise: 0)
let update = prediction.update(measurement, observationModel: 1, covarienceOfObservationNoise: 0.1)
filter = update
}
```
## Sources
All the names of properties and methods' parameters names are taken from
[wikipedia page](https://en.wikipedia.org/wiki/Kalman_filter#Details).
If you are looking for a good source to understand how `Kalman Filter` works then take a look at
[this page](http://bilgin.esme.org/BitsAndBytes/KalmanFilterforDummies),
[this one](http://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/) and there is a great series of
video tutorials on [udacity](https://www.udacity.com/course/artificial-intelligence-for-robotics--cs373)