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 [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Build Status](https://travis-ci.org/wearereasonablepeople/KalmanFilter.svg?branch=master)](https://travis-ci.org/wearereasonablepeople/KalmanFilter) [![codecov](https://codecov.io/gh/wearereasonablepeople/KalmanFilter/branch/master/graph/badge.svg)](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)