├── .github ├── FUNDING.yml └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .spi.yml ├── .swiftlint.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── Package.swift ├── README.md ├── Sources ├── MatrixModule │ ├── Algebra.swift │ ├── ApproximatelyEqual.swift │ ├── Arithmetic.swift │ ├── DataBuffer.swift │ ├── Determinant.swift │ ├── Documentation.docc │ │ └── MatrixModule.md │ ├── Exponential.swift │ ├── Formatter.swift │ ├── Inverse.swift │ ├── Logarithm.swift │ ├── Matrix.swift │ ├── Pad.swift │ ├── Power.swift │ └── Trigonometry.swift ├── Numerix │ ├── Documentation.docc │ │ ├── GetStarted.md │ │ ├── LinearAlgebra.md │ │ └── Numerix.md │ └── Numerix.swift ├── ShapedArrayModule │ ├── Arithmetic.swift │ ├── DataBuffer.swift │ ├── Documentation.docc │ │ └── ShapedArrayModule.md │ ├── Helpers.swift │ ├── NumberStyle.swift │ ├── ShapedArray.swift │ └── ShapedArrayElement.swift └── VectorModule │ ├── Algebra.swift │ ├── ApproximatelyEqual.swift │ ├── Arithmetic.swift │ ├── DataBuffer.swift │ ├── Documentation.docc │ ├── LinearAlgebra.md │ ├── Trigonometry.md │ └── VectorModule.md │ ├── Exponential.swift │ ├── Formatter.swift │ ├── Logarithm.swift │ ├── Power.swift │ ├── RandomDistribution.swift │ ├── Trigonometry.swift │ └── Vector.swift └── Tests ├── MatrixTests.swift ├── RandomDistTests.swift ├── ShapedArrayTests.swift ├── TrigonometryTests.swift └── VectorTests.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding platforms for this project 2 | github: wigging 3 | patreon: wigging 4 | buy_me_a_coffee: wigging 5 | custom: "https://www.paypal.me/gavinwiggins" -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Run linter checks 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | linter: 11 | runs-on: macos-15 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install swiftlint 15 | run: brew install swiftlint 16 | - name: Run linter checks 17 | run: swiftlint 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run swift tests 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | tests: 11 | runs-on: macos-15 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Select xcode version 15 | run: sudo xcode-select --switch /Applications/Xcode_16.2.app 16 | - name: Build the package 17 | run: swift build 18 | - name: Run tests with swift 19 | run: swift test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .build 3 | .swiftpm 4 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Numerix, VectorModule, MatrixModule, ShapedArrayModule] 5 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - cyclomatic_complexity 3 | - force_cast 4 | - function_body_length 5 | - identifier_name 6 | - file_length 7 | - type_body_length 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Numerix 2 | 3 | :+1::tada: Thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to the Numerix package. Submitted code that does not conform to these guidelines will not be merged into the package. Feel free to propose changes to this document in a Pull Request or Issue. 6 | 7 | ## Code style 8 | 9 | Use the [SwiftLint](https://github.com/realm/SwiftLint) tool to enforce Swift code styles and conventions. See the `.swiftlint.yml` file for disabled rules and other settings. 10 | 11 | ## Documentation 12 | 13 | The [DocC](https://www.swift.org/documentation/docc/) tool is used to generate documentation for the Numerix package. The documentation is hosted by the Swift Package Index. All submitted code should have documentation comments that are compatible with DocC. 14 | 15 | ## Tests 16 | 17 | The [Swift Testing](https://developer.apple.com/documentation/testing/) framework is used for creating and running tests. New tests should be added where appropriate and existing tests should pass for submitted code. 18 | 19 | ## Pull requests 20 | 21 | Fork this repository and create a new branch with your contributions. Then create a pull request to the main branch of the Numerix repository. Only pull requests that have been properly styled, documented, and tested (as discussed above) will be considered for merging. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Gavin Wiggins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Numerix", 8 | platforms: [.macOS(.v14), .iOS(.v17), .tvOS(.v17), .watchOS(.v10), .visionOS(.v1)], 9 | products: [ 10 | .library(name: "Numerix", targets: ["Numerix"]) 11 | ], 12 | targets: [ 13 | .target( 14 | name: "Numerix", 15 | dependencies: ["VectorModule", "MatrixModule", "ShapedArrayModule"], 16 | cxxSettings: [.define("ACCELERATE_NEW_LAPACK", to: "1"), .define("ACCELERATE_LAPACK_ILP64", to: "1")], 17 | linkerSettings: [.linkedFramework("Accelerate")] 18 | ), 19 | .target( 20 | name: "VectorModule", 21 | cxxSettings: [.define("ACCELERATE_NEW_LAPACK", to: "1"), .define("ACCELERATE_LAPACK_ILP64", to: "1")], 22 | linkerSettings: [.linkedFramework("Accelerate")] 23 | ), 24 | .target( 25 | name: "MatrixModule", 26 | cxxSettings: [.define("ACCELERATE_NEW_LAPACK", to: "1"), .define("ACCELERATE_LAPACK_ILP64", to: "1")], 27 | linkerSettings: [.linkedFramework("Accelerate")] 28 | ), 29 | .target( 30 | name: "ShapedArrayModule", 31 | cxxSettings: [.define("ACCELERATE_NEW_LAPACK", to: "1"), .define("ACCELERATE_LAPACK_ILP64", to: "1")], 32 | linkerSettings: [.linkedFramework("Accelerate")] 33 | ), 34 | .testTarget(name: "Tests", dependencies: ["Numerix"]) 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Numerix 2 | 3 | Numerix is an open-source Swift package that provides Complex, Vector, Matrix, and ShapedArray structures for performing linear algebra and other numerical computations on Apple devices. It uses the Accelerate framework to perform high-performance and energy-efficient calculations. More information is available on the [Swift Package Index](https://swiftpackageindex.com/wigging/numerix) and the [documentation website](https://swiftpackageindex.com/wigging/numerix/main/documentation/numerix). 4 | 5 | > [!WARNING] 6 | > Numerix is under heavy development. Many things are missing and breaking changes will likely happen. 7 | 8 | ## Installation 9 | 10 | If using Xcode, select *File* and choose *Add Package Dependencies...* from the menu, then enter the URL for the Numerix GitHub repository which is `https://github.com/wigging/numerix`. 11 | 12 | If editing a `Package.swift` manifest, add Numerix as a dependency such as: 13 | 14 | ```swift 15 | dependencies: [ 16 | .package(url: "https://github.com/wigging/numerix", branch: "main") 17 | ] 18 | 19 | targets: [ 20 | .target(name: "MyLibrary", dependencies: ["Numerix"]) 21 | ] 22 | ``` 23 | 24 | The final step is to import the package in a Swift file by using `import Numerix`. 25 | 26 | ## Usage 27 | 28 | The example below uses the Numerix package to perform matrix multiplication of two 3x3 matrices. The printed result is shown in the comments. 29 | 30 | ```swift 31 | import Numerix 32 | 33 | let a: Matrix = [[1, 2, 3], 34 | [4, 5, 6.0], 35 | [2, 3, 4]] 36 | 37 | let b: Matrix = [[1, 2, 3.5], 38 | [4, 5, 6], 39 | [4, 5, 9.1]] 40 | 41 | let c = a * b 42 | 43 | print(c) 44 | // ⎛ 21.0 27.0 42.8 ⎞ 45 | // ⎜ 48.0 63.0 98.6 ⎟ 46 | // ⎝ 30.0 39.0 61.4 ⎠ 47 | 48 | debugPrint(c) 49 | // 3x3 Matrix 50 | // ⎛ 21.0 27.0 42.8 ⎞ 51 | // ⎜ 48.0 63.0 98.6 ⎟ 52 | // ⎝ 30.0 39.0 61.4 ⎠ 53 | ``` 54 | 55 | An N-dimensional array is also available in Numerix. The example below prints a 2x3x5 shaped array. 56 | 57 | ```swift 58 | let shape = [2, 3, 5] 59 | let values = Array(1...shape.reduce(1, *)).map { Double($0) } 60 | let arr = ShapedArray(shape: shape, array: values) 61 | 62 | print(arr) 63 | // ⎛ ⎛ 1.0 2.0 3.0 4.0 5.0 ⎞ ⎞ 64 | // ⎜ ⎜ 6.0 7.0 8.0 9.0 10.0 ⎟ ⎟ 65 | // ⎜ ⎝ 11.0 12.0 13.0 14.0 15.0 ⎠ ⎟ 66 | // ⎜ ⎟ 67 | // ⎜ ⎛ 16.0 17.0 18.0 19.0 20.0 ⎞ ⎟ 68 | // ⎜ ⎜ 21.0 22.0 23.0 24.0 25.0 ⎟ ⎟ 69 | // ⎝ ⎝ 26.0 27.0 28.0 29.0 30.0 ⎠ ⎠ 70 | ``` 71 | 72 | ## Documentation 73 | 74 | Documentation for the Numerix package can be viewed [here](https://swiftpackageindex.com/wigging/numerix/main/documentation/numerix). The documentation can also be built and viewed in Xcode by selecting *Product* then *Build Documentation* from the Xcode menu. 75 | 76 | ## Contributing 77 | 78 | Please read the [contributing guidelines](CONTRIBUTING.md) if you would like to contribute to the Numerix package. The guidelines discuss testing, documentation, code style, and other practices used for the development of this package. 79 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Algebra.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Linear algebra protocol and extensions for the Matrix struct. 3 | */ 4 | 5 | import Accelerate 6 | 7 | public protocol Algebra { 8 | static func norm(_ a: Matrix) -> Self 9 | static func scale(_ a: inout Matrix, by k: Self) 10 | static func transpose(_ a: Matrix) -> Matrix 11 | static func swapValues(a: inout Matrix, b: inout Matrix) 12 | } 13 | 14 | extension Int: Algebra { 15 | 16 | public static func swapValues(a: inout Matrix, b: inout Matrix) { 17 | precondition(a.rows == b.rows && a.columns == b.columns, "Matrices must have same shape") 18 | swap(&a, &b) 19 | } 20 | 21 | public static func norm(_ a: Matrix) -> Int { 22 | var sumOfSquares = 0 23 | for i in 0.., by k: Int) { 33 | a = a .* k 34 | } 35 | 36 | public static func transpose(_ a: Matrix) -> Matrix { 37 | var transposed = Matrix(rows: a.columns, columns: a.rows) 38 | for i in 0.., b: inout Matrix) { 50 | precondition(a.rows == b.rows && a.columns == b.columns, "Matrices must have same shape") 51 | cblas_sswap(a.buffer.count, a.buffer.baseAddress, 1, b.buffer.baseAddress, 1) 52 | } 53 | 54 | public static func norm(_ a: Matrix) -> Float { 55 | cblas_snrm2(a.buffer.count, a.buffer.baseAddress, 1) 56 | } 57 | 58 | public static func scale(_ a: inout Matrix, by k: Float) { 59 | cblas_sscal(a.rows * a.columns, k, a.buffer.baseAddress, 1) 60 | } 61 | 62 | public static func transpose(_ a: Matrix) -> Matrix { 63 | let m = vDSP_Length(a.columns) 64 | let n = vDSP_Length(a.rows) 65 | let mat = Matrix(rows: a.columns, columns: a.rows) 66 | vDSP_mtrans(a.buffer.baseAddress!, 1, mat.buffer.baseAddress!, 1, m, n) 67 | return mat 68 | } 69 | } 70 | 71 | extension Double: Algebra { 72 | 73 | public static func swapValues(a: inout Matrix, b: inout Matrix) { 74 | precondition(a.rows == b.rows && a.columns == b.columns, "Matrices must have same shape") 75 | cblas_dswap(a.buffer.count, a.buffer.baseAddress, 1, b.buffer.baseAddress, 1) 76 | } 77 | 78 | public static func norm(_ a: Matrix) -> Double { 79 | cblas_dnrm2(a.buffer.count, a.buffer.baseAddress, 1) 80 | } 81 | 82 | public static func scale(_ a: inout Matrix, by k: Double) { 83 | cblas_dscal(a.rows * a.columns, k, a.buffer.baseAddress, 1) 84 | } 85 | 86 | public static func transpose(_ a: Matrix) -> Matrix { 87 | let m = vDSP_Length(a.columns) 88 | let n = vDSP_Length(a.rows) 89 | let mat = Matrix(rows: a.columns, columns: a.rows) 90 | vDSP_mtransD(a.buffer.baseAddress!, 1, mat.buffer.baseAddress!, 1, m, n) 91 | return mat 92 | } 93 | } 94 | 95 | extension Matrix where Scalar: Algebra { 96 | 97 | /// The Euclidean norm of the matrix. Also known as the 2-norm or maximum singular value. 98 | /// - Returns: The matrix norm. 99 | public func norm() -> Scalar { 100 | Scalar.norm(self) 101 | } 102 | 103 | /// Multiply each value in the matrix by a constant. 104 | /// 105 | /// For integer matrices, this performs element-wise multiplication. For 106 | /// single and double precision matrices this uses BLAS routines `sscal` 107 | /// and `dscal` respectively. 108 | /// 109 | /// - Parameter k: The scaling factor. 110 | public mutating func scale(by k: Scalar) { 111 | Scalar.scale(&self, by: k) 112 | } 113 | 114 | /// Transpose the matrix and return the result. 115 | /// - Returns: The transposed matrix. 116 | public func transpose() -> Matrix { 117 | Scalar.transpose(self) 118 | } 119 | } 120 | 121 | /// Swap the values of two matrices. 122 | /// 123 | /// In this example the scalar values in `mat1` are exchanged for the values in `mat2` and vice versa. 124 | /// ```swift 125 | /// var mat1 = Matrix([[2, 3, 4], [5, 6, 7]]) 126 | /// var mat2 = Matrix([[9, 8, 7], [10, 12, 13]]) 127 | /// swapValues(&mat1, &mat2) 128 | /// ``` 129 | /// - Parameters: 130 | /// - a: The first matrix. 131 | /// - b: The second matrix. 132 | public func swapValues(_ a: inout Matrix, _ b: inout Matrix) { 133 | Scalar.swapValues(a: &a, b: &b) 134 | } 135 | -------------------------------------------------------------------------------- /Sources/MatrixModule/ApproximatelyEqual.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Matrix extension for approximate equality methods. 3 | */ 4 | 5 | extension Matrix { 6 | 7 | /// Compare two matrices of single precision for approximate equality. 8 | /// 9 | /// Returns true when `norm(x - y) ≤ max(atol, rtol × max(norm(x), norm(y)))` 10 | /// where `x` and `y` are matrices, `atol` is absolute tolerance, and `rtol` 11 | /// is relative tolerance. 12 | /// 13 | /// - Parameters: 14 | /// - other: The other matrix used for comparison. 15 | /// - absoluteTolerance: The absolute tolerance, default is 0. 16 | /// - relativeTolerance: The relative tolerance, default is 1e-6. 17 | /// - Returns: True when two matrices are approximately equal. 18 | public func isApproximatelyEqual( 19 | to other: Matrix, absoluteTolerance: Float = 0, relativeTolerance: Float = 1e-6 20 | ) -> Bool where Scalar == Float { 21 | let delta = (self - other).norm() 22 | let scale = Swift.max(self.norm(), other.norm()) 23 | return delta <= Swift.max(absoluteTolerance, relativeTolerance * scale) 24 | } 25 | 26 | /// Compare two matrices of double precision for approximate equality. 27 | /// 28 | /// Returns true when `norm(x - y) ≤ max(atol, rtol × max(norm(x), norm(y)))` 29 | /// where `x` and `y` are matrices, `atol` is absolute tolerance, and `rtol` 30 | /// is relative tolerance. 31 | /// 32 | /// - Parameters: 33 | /// - other: The other matrix used for comparison. 34 | /// - absoluteTolerance: The absolute tolerance, default is 0. 35 | /// - relativeTolerance: The relative tolerance, default is 1e-8. 36 | /// - Returns: True when two matrices are approximately equal. 37 | public func isApproximatelyEqual( 38 | to other: Matrix, absoluteTolerance: Double = 0, relativeTolerance: Double = 1e-8 39 | ) -> Bool where Scalar == Double { 40 | let delta = (self - other).norm() 41 | let scale = Swift.max(self.norm(), other.norm()) 42 | return delta <= Swift.max(absoluteTolerance, relativeTolerance * scale) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Arithmetic.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Arithmetic protocol and extensions for the Matrix struct. 3 | */ 4 | 5 | import Accelerate 6 | 7 | infix operator .* : MultiplicationPrecedence 8 | infix operator ⊙ : MultiplicationPrecedence 9 | 10 | public protocol Arithmetic { 11 | static func add(_ a: Matrix, _ k: Self) -> Matrix 12 | static func add(_ a: Matrix, _ b: Matrix) -> Matrix 13 | static func subtract(_ k: Self, _ a: Matrix) -> Matrix 14 | static func subtract(_ a: Matrix, _ k: Self) -> Matrix 15 | static func subtract(_ a: Matrix, _ b: Matrix) -> Matrix 16 | static func multiply(_ a: Matrix, _ k: Self) -> Matrix 17 | static func multiply(_ a: Matrix, _ b: Matrix) -> Matrix 18 | static func matrixMultiply(_ a: Matrix, _ b: Matrix) -> Matrix 19 | static func divide(_ k: Self, _ b: Matrix) -> Matrix 20 | static func divide(_ a: Matrix, _ k: Self) -> Matrix 21 | static func divide(_ a: Matrix, _ b: Matrix) -> Matrix 22 | } 23 | 24 | extension Int: Arithmetic { 25 | 26 | public static func add(_ a: Matrix, _ k: Int) -> Matrix { 27 | var mat = Matrix(like: a) 28 | for i in 0.., _ b: Matrix) -> Matrix { 37 | var mat = Matrix(like: a) 38 | for i in 0..) -> Matrix { 47 | var mat = Matrix(like: a) 48 | for i in 0.., _ k: Int) -> Matrix { 57 | var mat = Matrix(like: a) 58 | for i in 0.., _ b: Matrix) -> Matrix { 67 | var mat = Matrix(like: a) 68 | for i in 0.., _ k: Int) -> Matrix { 77 | var mat = Matrix(like: a) 78 | for i in 0.., _ b: Matrix) -> Matrix { 87 | var mat = Matrix(like: a) 88 | for i in 0.., _ b: Matrix) -> Matrix { 97 | precondition(a.columns == b.rows, "Number of columns in matrix A must equal number of rows in matrix B") 98 | var mat = Matrix(rows: a.rows, columns: b.columns) 99 | for i in 0..) -> Matrix { 110 | var mat = Matrix(like: b) 111 | for i in 0.., _ k: Self) -> Matrix { 120 | var mat = Matrix(like: a) 121 | for i in 0.., _ b: Matrix) -> Matrix { 130 | var mat = Matrix(like: a) 131 | for i in 0.., _ k: Float) -> Matrix { 143 | var mat = Matrix(like: a) 144 | vDSP.add(k, a.buffer, result: &mat.buffer) 145 | return mat 146 | } 147 | 148 | public static func add(_ a: Matrix, _ b: Matrix) -> Matrix { 149 | var mat = Matrix(like: a) 150 | vDSP.add(a.buffer, b.buffer, result: &mat.buffer) 151 | return mat 152 | } 153 | 154 | public static func subtract(_ k: Float, _ a: Matrix) -> Matrix { 155 | let arr = Array(repeating: k, count: a.buffer.count) 156 | var mat = Matrix(like: a) 157 | vDSP.subtract(arr, a.buffer, result: &mat.buffer) 158 | return mat 159 | } 160 | 161 | public static func subtract(_ a: Matrix, _ k: Float) -> Matrix { 162 | let arr = Array(repeating: k, count: a.buffer.count) 163 | var mat = Matrix(like: a) 164 | vDSP.subtract(a.buffer, arr, result: &mat.buffer) 165 | return mat 166 | } 167 | 168 | public static func subtract(_ a: Matrix, _ b: Matrix) -> Matrix { 169 | var mat = Matrix(like: a) 170 | vDSP.subtract(a.buffer, b.buffer, result: &mat.buffer) 171 | return mat 172 | } 173 | 174 | public static func multiply(_ a: Matrix, _ k: Float) -> Matrix { 175 | var mat = Matrix(like: a) 176 | vDSP.multiply(k, a.buffer, result: &mat.buffer) 177 | return mat 178 | } 179 | 180 | public static func multiply(_ a: Matrix, _ b: Matrix) -> Matrix { 181 | var mat = Matrix(like: a) 182 | vDSP.multiply(a.buffer, b.buffer, result: &mat.buffer) 183 | return mat 184 | } 185 | 186 | public static func matrixMultiply(_ a: Matrix, _ b: Matrix) -> Matrix { 187 | precondition(a.columns == b.rows, "Number of columns in matrix A must equal number of rows in matrix B") 188 | 189 | let m = a.rows // number of rows in matrices A and C 190 | let n = b.columns // number of columns in matrices B and C 191 | let k = a.columns // number of columns in matrix A; number of rows in matrix B 192 | let alpha: Float = 1.0 193 | let beta: Float = 0.0 194 | 195 | // matrix multiplication where C ← αAB + βC 196 | let c = Matrix(rows: a.rows, columns: b.columns) 197 | cblas_sgemm( 198 | CblasRowMajor, CblasNoTrans, CblasNoTrans, 199 | m, n, k, alpha, 200 | a.buffer.baseAddress, k, 201 | b.buffer.baseAddress, n, 202 | beta, 203 | c.buffer.baseAddress, n 204 | ) 205 | 206 | return c 207 | } 208 | 209 | public static func divide(_ k: Self, _ b: Matrix) -> Matrix { 210 | var mat = Matrix(like: b) 211 | vDSP.divide(k, b.buffer, result: &mat.buffer) 212 | return mat 213 | } 214 | 215 | public static func divide(_ a: Matrix, _ k: Self) -> Matrix { 216 | var mat = Matrix(like: a) 217 | vDSP.divide(a.buffer, k, result: &mat.buffer) 218 | return mat 219 | } 220 | 221 | public static func divide(_ a: Matrix, _ b: Matrix) -> Matrix { 222 | var mat = Matrix(like: a) 223 | vDSP.divide(a.buffer, b.buffer, result: &mat.buffer) 224 | return mat 225 | } 226 | } 227 | 228 | extension Double: Arithmetic { 229 | 230 | public static func add(_ a: Matrix, _ k: Double) -> Matrix { 231 | var mat = Matrix(like: a) 232 | vDSP.add(k, a.buffer, result: &mat.buffer) 233 | return mat 234 | } 235 | 236 | public static func add(_ a: Matrix, _ b: Matrix) -> Matrix { 237 | var mat = Matrix(like: a) 238 | vDSP.add(a.buffer, b.buffer, result: &mat.buffer) 239 | return mat 240 | } 241 | 242 | public static func subtract(_ k: Double, _ a: Matrix) -> Matrix { 243 | let arr = Array(repeating: k, count: a.buffer.count) 244 | var mat = Matrix(like: a) 245 | vDSP.subtract(arr, a.buffer, result: &mat.buffer) 246 | return mat 247 | } 248 | 249 | public static func subtract(_ a: Matrix, _ k: Double) -> Matrix { 250 | let arr = Array(repeating: k, count: a.buffer.count) 251 | var mat = Matrix(like: a) 252 | vDSP.subtract(a.buffer, arr, result: &mat.buffer) 253 | return mat 254 | } 255 | 256 | public static func subtract(_ a: Matrix, _ b: Matrix) -> Matrix { 257 | var mat = Matrix(like: a) 258 | vDSP.subtract(a.buffer, b.buffer, result: &mat.buffer) 259 | return mat 260 | } 261 | 262 | public static func multiply(_ a: Matrix, _ k: Double) -> Matrix { 263 | var mat = Matrix(like: a) 264 | vDSP.multiply(k, a.buffer, result: &mat.buffer) 265 | return mat 266 | } 267 | 268 | public static func multiply(_ a: Matrix, _ b: Matrix) -> Matrix { 269 | var mat = Matrix(like: a) 270 | vDSP.multiply(a.buffer, b.buffer, result: &mat.buffer) 271 | return mat 272 | } 273 | 274 | public static func matrixMultiply(_ a: Matrix, _ b: Matrix) -> Matrix { 275 | precondition(a.columns == b.rows, "Number of columns in matrix A must equal number of rows in matrix B") 276 | 277 | let m = a.rows // number of rows in matrices A and C 278 | let n = b.columns // number of columns in matrices B and C 279 | let k = a.columns // number of columns in matrix A; number of rows in matrix B 280 | let alpha = 1.0 281 | let beta = 0.0 282 | 283 | // matrix multiplication where C ← αAB + βC 284 | let c = Matrix(rows: a.rows, columns: b.columns) 285 | cblas_dgemm( 286 | CblasRowMajor, CblasNoTrans, CblasNoTrans, 287 | m, n, k, alpha, 288 | a.buffer.baseAddress, k, 289 | b.buffer.baseAddress, n, 290 | beta, 291 | c.buffer.baseAddress, n 292 | ) 293 | 294 | return c 295 | } 296 | 297 | public static func divide(_ k: Self, _ b: Matrix) -> Matrix { 298 | var mat = Matrix(like: b) 299 | vDSP.divide(k, b.buffer, result: &mat.buffer) 300 | return mat 301 | } 302 | 303 | public static func divide(_ a: Matrix, _ k: Self) -> Matrix { 304 | var mat = Matrix(like: a) 305 | vDSP.divide(a.buffer, k, result: &mat.buffer) 306 | return mat 307 | } 308 | 309 | public static func divide(_ a: Matrix, _ b: Matrix) -> Matrix { 310 | var mat = Matrix(like: a) 311 | vDSP.divide(a.buffer, b.buffer, result: &mat.buffer) 312 | return mat 313 | } 314 | } 315 | 316 | extension Matrix where Scalar: Arithmetic { 317 | 318 | /// Element-wise addition of a scalar value and matrix. 319 | /// - Parameters: 320 | /// - lhs: The left-hand side scalar value. 321 | /// - rhs: The right-hand side matrix. 322 | /// - Returns: Element-wise sum of a scalar value and matrix. 323 | public static func + (lhs: Scalar, rhs: Matrix) -> Matrix { 324 | Scalar.add(rhs, lhs) 325 | } 326 | 327 | public static func + (lhs: Matrix, rhs: Scalar) -> Matrix { 328 | Scalar.add(lhs, rhs) 329 | } 330 | 331 | public static func + (lhs: Matrix, rhs: Matrix) -> Matrix { 332 | Scalar.add(lhs, rhs) 333 | } 334 | 335 | public static func += (lhs: inout Matrix, rhs: Matrix) { 336 | lhs = lhs + rhs 337 | } 338 | 339 | public static func - (lhs: Scalar, rhs: Matrix) -> Matrix { 340 | Scalar.subtract(lhs, rhs) 341 | } 342 | 343 | public static func - (lhs: Matrix, rhs: Scalar) -> Matrix { 344 | Scalar.subtract(lhs, rhs) 345 | } 346 | 347 | public static func - (lhs: Matrix, rhs: Matrix) -> Matrix { 348 | Scalar.subtract(lhs, rhs) 349 | } 350 | 351 | /// Matrix multiplication of two matrices. 352 | /// - Parameters: 353 | /// - lhs: The left-hand side matrix. 354 | /// - rhs: The right-hand side matrix. 355 | /// - Returns: Matrix product of the two matrices. 356 | public static func * (lhs: Matrix, rhs: Matrix) -> Matrix { 357 | Scalar.matrixMultiply(lhs, rhs) 358 | } 359 | 360 | /// Element-wise multiplication of a scalar value and matrix. 361 | /// - Parameters: 362 | /// - lhs: The left-hand side scalar value. 363 | /// - rhs: The right-hand side matrix. 364 | /// - Returns: Element-wise product of a scalar value and matrix. 365 | public static func .* (lhs: Scalar, rhs: Matrix) -> Matrix { 366 | Scalar.multiply(rhs, lhs) 367 | } 368 | 369 | public static func .* (lhs: Matrix, rhs: Scalar) -> Matrix { 370 | Scalar.multiply(lhs, rhs) 371 | } 372 | 373 | public static func .* (lhs: Matrix, rhs: Matrix) -> Matrix { 374 | Scalar.multiply(lhs, rhs) 375 | } 376 | 377 | /// Element-wise matrix multiplication of two matrices. 378 | /// 379 | /// Matrices must have same dimensions m × n. This is also known as the Hadamard product or Schur product. 380 | /// 381 | /// - Parameters: 382 | /// - lhs: Left matrix with dimension of m × n. 383 | /// - rhs: Right matrix with dimension m × n. 384 | /// - Returns: Element-wise matrix product with same dimensions as input matrices. 385 | public static func ⊙ (lhs: Matrix, rhs: Matrix) -> Matrix { 386 | precondition(lhs.rows == rhs.rows && lhs.columns == rhs.columns, "Matrices must have same dimensions") 387 | return Scalar.multiply(lhs, rhs) 388 | } 389 | 390 | public static func / (lhs: Scalar, rhs: Matrix) -> Matrix { 391 | Scalar.divide(lhs, rhs) 392 | } 393 | 394 | public static func / (lhs: Matrix, rhs: Scalar) -> Matrix { 395 | Scalar.divide(lhs, rhs) 396 | } 397 | 398 | public static func / (lhs: Matrix, rhs: Matrix) -> Matrix { 399 | Scalar.divide(lhs, rhs) 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /Sources/MatrixModule/DataBuffer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Data buffer class. 3 | */ 4 | 5 | class DataBuffer { 6 | var buffer: UnsafeMutableBufferPointer 7 | 8 | init(count: Int, fill: T) { 9 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: count) 10 | self.buffer.initialize(repeating: fill) 11 | } 12 | 13 | init(count: Int) { 14 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: count) 15 | } 16 | 17 | init(array: [T]) { 18 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: array.count) 19 | _ = self.buffer.initialize(fromContentsOf: array) 20 | } 21 | 22 | init(buffer: UnsafeMutableBufferPointer) { 23 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: buffer.count) 24 | _ = self.buffer.initialize(from: buffer) 25 | } 26 | 27 | deinit { 28 | self.buffer.deinitialize() 29 | self.buffer.deallocate() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Determinant.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Matrix extension for determinant. 3 | 4 | Uses LAPACK `sgetrf` or `dgetrf` functions. In LAPACK the INFO value is used 5 | as the U index `i` in the error message, this index is one-based so it needs 6 | to be zero-based (INFO - 1) for Swift error message. 7 | */ 8 | 9 | import Accelerate 10 | 11 | public protocol Determinant { 12 | static func det(a: Matrix) -> Self 13 | } 14 | 15 | extension Float: Determinant { 16 | public static func det(a: Matrix) -> Float { 17 | precondition(a.rows == a.columns, "Input matrix is not square") 18 | let mat = a.copy() 19 | 20 | // Perform LU factorization using LAPACK sgetrf 21 | var rows = mat.rows 22 | var pivots = [Int](repeating: 0, count: mat.rows) 23 | var info = 0 24 | var det: Float = 1.0 25 | sgetrf_(&rows, &rows, mat.buffer.baseAddress, &rows, &pivots, &info) 26 | 27 | // Check if INFO < 0 28 | precondition(info >= 0, "The \(abs(info))-th argument had an illegal value.") 29 | 30 | // Check if INFO > 0 31 | let err = """ 32 | U(\(info - 1),\(info - 1)) is exactly zero. The factorization has been \ 33 | completed, but the factor U is exactly singular, and division by zero will \ 34 | occur if it is used to solve a system of equations. 35 | """ 36 | precondition(info <= 0, err) 37 | 38 | // Calculate determinant from the diagonal if INFO = 0 39 | for i in 0..) -> Double { 52 | precondition(a.rows == a.columns, "Input matrix is not square") 53 | let mat = a.copy() 54 | 55 | // Perform LU factorization using LAPACK sgetrf 56 | var rows = mat.rows 57 | var pivots = [Int](repeating: 0, count: mat.rows) 58 | var info = 0 59 | var det: Double = 1.0 60 | dgetrf_(&rows, &rows, mat.buffer.baseAddress, &rows, &pivots, &info) 61 | 62 | // Check if INFO < 0 63 | precondition(info >= 0, "The \(abs(info))-th argument had an illegal value.") 64 | 65 | // Check if INFO > 0 66 | let err = """ 67 | U(\(info - 1),\(info - 1)) is exactly zero. The factorization has been \ 68 | completed, but the factor U is exactly singular, and division by zero will \ 69 | occur if it is used to solve a system of equations. 70 | """ 71 | precondition(info <= 0, err) 72 | 73 | // Calculate determinant from the diagonal if INFO = 0 74 | for i in 0.. = [[1, 2], [3, 4]] 97 | /// let detA = a.determinant() 98 | /// ``` 99 | /// 100 | /// - Returns: The determinant of the matrix. 101 | public func determinant() -> Scalar { 102 | Scalar.det(a: self) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Documentation.docc/MatrixModule.md: -------------------------------------------------------------------------------- 1 | # ``MatrixModule`` 2 | 3 | Two-dimensional matrix operations. 4 | 5 | ## Overview 6 | 7 | This module provides the ``Matrix`` structure for working with two-dimensional numerical data. Arithmetic and several linear algebra operations are supported via protocols. Scalar values can be Int, Float, or Double. See the topics below for more information. 8 | 9 | ## Topics 10 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Exponential.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Matrix extension for exponential functions. 3 | */ 4 | 5 | import Accelerate 6 | 7 | public protocol Exponential { 8 | static func exp(a: Matrix) -> Matrix 9 | static func exp2(a: Matrix) -> Matrix 10 | static func expm1(a: Matrix) -> Matrix 11 | } 12 | 13 | extension Float: Exponential { 14 | public static func exp(a: Matrix) -> Matrix { 15 | var mat = Matrix(like: a) 16 | vForce.exp(a.buffer, result: &mat.buffer) 17 | return mat 18 | } 19 | 20 | public static func exp2(a: Matrix) -> Matrix { 21 | var mat = Matrix(like: a) 22 | vForce.exp2(a.buffer, result: &mat.buffer) 23 | return mat 24 | } 25 | 26 | public static func expm1(a: Matrix) -> Matrix { 27 | var mat = Matrix(like: a) 28 | vForce.expm1(a.buffer, result: &mat.buffer) 29 | return mat 30 | } 31 | } 32 | 33 | extension Double: Exponential { 34 | public static func exp(a: Matrix) -> Matrix { 35 | var mat = Matrix(like: a) 36 | vForce.exp(a.buffer, result: &mat.buffer) 37 | return mat 38 | } 39 | 40 | public static func exp2(a: Matrix) -> Matrix { 41 | var mat = Matrix(like: a) 42 | vForce.exp2(a.buffer, result: &mat.buffer) 43 | return mat 44 | } 45 | 46 | public static func expm1(a: Matrix) -> Matrix { 47 | var mat = Matrix(like: a) 48 | vForce.expm1(a.buffer, result: &mat.buffer) 49 | return mat 50 | } 51 | } 52 | 53 | extension Matrix where Scalar: Exponential { 54 | 55 | /// Calculate the exponential for each matrix element. 56 | /// - Returns: Element-wise exponential matrix. 57 | public func exp() -> Matrix { 58 | Scalar.exp(a: self) 59 | } 60 | 61 | /// Calculate 2 raised to the power of each matrix element. 62 | /// - Returns: Element-wise 2 raised to the power matrix. 63 | public func exp2() -> Matrix { 64 | Scalar.exp2(a: self) 65 | } 66 | 67 | /// Calculate the exponential minus one of each matrix element. 68 | /// - Returns: Element-wise exponential minus one matrix. 69 | public func expm1() -> Matrix { 70 | Scalar.expm1(a: self) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Formatter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Function to format the matrix values for printing. 3 | Used by Matrix `description` in `CustomDebugStringConvertible`. 4 | */ 5 | 6 | extension Matrix { 7 | 8 | private func formatValue(_ value: Scalar, specifier: String?) -> String { 9 | guard let spec = specifier else { return "\(value)" } 10 | return String(format: spec, value as! CVarArg) 11 | } 12 | 13 | /// Format the matrix as a string representation. 14 | /// 15 | /// Use the optional format specifier to define the numeric format used for 16 | /// the underlying matrix values. See the [IEEE printf specification][1] for 17 | /// more information about the format specifiers. 18 | /// ```swift 19 | /// let mat = Matrix([[1, 5.2, 4], [10.1, 4, -12]]) 20 | /// let formatMat = mat.formatted(specifier: "%.2f") 21 | /// // ⎛ 1.00 5.20 4.00 ⎞ 22 | /// // ⎝ 10.10 4.00 -12.00 ⎠ 23 | /// ``` 24 | /// [1]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/fprintf.html 25 | /// - Parameter specifier: Format specifier. 26 | /// - Returns: String of matrix values. 27 | public func formatted(specifier: String? = nil) -> String { 28 | var desc = "" 29 | 30 | // Matrix is only one row so print as a row vector 31 | if self.rows == 1 { 32 | desc += "( " 33 | desc += self.buffer.map { formatValue($0, specifier: specifier) }.joined(separator: " ") 34 | desc += " )" 35 | return desc 36 | } 37 | 38 | // Max integer width and fraction width for each matrix column 39 | var maxIntWidth = Array(repeating: 0, count: self.columns) 40 | var maxFracWidth = Array(repeating: 0, count: self.columns) 41 | 42 | for i in 0..) -> Matrix 15 | } 16 | 17 | extension Float: Inverse { 18 | public static func inv(a: Matrix) -> Matrix { 19 | precondition(a.rows == a.columns, "Input matrix is not square") 20 | 21 | let mat = a.copy() 22 | var n = a.rows 23 | var pivots = [Int](repeating: 0, count: a.rows) 24 | var workspace = [Float](repeating: 0.0, count: a.rows) 25 | var info = 0 26 | 27 | // Perform LU decomposition 28 | sgetrf_(&n, &n, mat.buffer.baseAddress, &n, &pivots, &info) 29 | 30 | // Check if info < 0 31 | precondition(info >= 0, "The \(info - 1)-th argument had an illegal value") 32 | 33 | // Check if info > 0 34 | let err = """ 35 | U(\(info - 1),\(info - 1)) is exactly zero. The factorization has been completed, but \ 36 | the factor U is exactly singular, and division by zero will occur if it is used \ 37 | to solve a system of equations. 38 | """ 39 | precondition(info <= 0, err) 40 | 41 | // Calculate inverse based on LU decomposition 42 | info = 0 43 | sgetri_(&n, mat.buffer.baseAddress, &n, &pivots, &workspace, &n, &info) 44 | 45 | // Check if info < 0 46 | precondition(info >= 0, "The \(info - 1)-th argument had an illegal value") 47 | 48 | // Check if info > 0 49 | let err2 = """ 50 | U(\(info - 1),\(info - 1)) is exactly zero. The factorization has been completed, but \ 51 | the factor U is exactly singular, and division by zero will occur if it is used \ 52 | to solve a system of equations. 53 | """ 54 | precondition(info <= 0, err2) 55 | 56 | return mat 57 | } 58 | } 59 | 60 | extension Double: Inverse { 61 | public static func inv(a: Matrix) -> Matrix { 62 | precondition(a.rows == a.columns, "Input matrix is not square") 63 | 64 | let mat = a.copy() 65 | var n = a.rows 66 | var pivots = [Int](repeating: 0, count: a.rows) 67 | var workspace = [Double](repeating: 0.0, count: a.rows) 68 | var info = 0 69 | 70 | // Perform LU decomposition 71 | dgetrf_(&n, &n, mat.buffer.baseAddress, &n, &pivots, &info) 72 | 73 | // Check if info < 0 74 | precondition(info >= 0, "The \(info - 1)-th argument had an illegal value") 75 | 76 | // Check if info > 0 77 | let err = """ 78 | U(\(info - 1),\(info - 1)) is exactly zero. The factorization has been completed, but \ 79 | the factor U is exactly singular, and division by zero will occur if it is used \ 80 | to solve a system of equations. 81 | """ 82 | precondition(info <= 0, err) 83 | 84 | // Calculate inverse based on LU decomposition 85 | info = 0 86 | dgetri_(&n, mat.buffer.baseAddress, &n, &pivots, &workspace, &n, &info) 87 | 88 | // Check if info < 0 89 | precondition(info >= 0, "The \(info - 1)-th argument had an illegal value") 90 | 91 | // Check if info > 0 92 | let err2 = """ 93 | U(\(info - 1),\(info - 1)) is exactly zero. The factorization has been completed, but \ 94 | the factor U is exactly singular, and division by zero will occur if it is used \ 95 | to solve a system of equations. 96 | """ 97 | precondition(info <= 0, err2) 98 | 99 | return mat 100 | } 101 | } 102 | 103 | extension Matrix where Scalar: Inverse { 104 | 105 | /// Compute the inverse of the matrix. 106 | /// 107 | /// Given a square matrix, compute the inverse of the matrix using single 108 | /// and double precision values. A precondition error occurs if the matrix 109 | /// is not square. This inverse function uses the LAPACK `dgetri` function 110 | /// to compute the inverse using the LU factorization from the LAPACK `dgetrf` 111 | /// function. 112 | /// 113 | /// ```swift 114 | /// let a = Matrix([[1, 2], [3, 4]]) 115 | /// let invA = a.inverse() 116 | /// ``` 117 | /// 118 | /// - Returns: The inverse of the matrix. 119 | public func inverse() -> Matrix { 120 | Scalar.inv(a: self) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Logarithm.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Matrix extension for logarithm functions. 3 | */ 4 | 5 | import Accelerate 6 | 7 | public protocol Logarithm { 8 | static func log(a: Matrix) -> Matrix 9 | static func log1p(a: Matrix) -> Matrix 10 | static func log10(a: Matrix) -> Matrix 11 | static func log2(a: Matrix) -> Matrix 12 | static func logb(a: Matrix) -> Matrix 13 | } 14 | 15 | extension Float: Logarithm { 16 | public static func log(a: Matrix) -> Matrix { 17 | var result = Matrix(like: a) 18 | vForce.log(a.buffer, result: &result.buffer) 19 | return result 20 | } 21 | 22 | public static func log1p(a: Matrix) -> Matrix { 23 | var result = Matrix(like: a) 24 | vForce.log1p(a.buffer, result: &result.buffer) 25 | return result 26 | } 27 | 28 | public static func log10(a: Matrix) -> Matrix { 29 | var result = Matrix(like: a) 30 | vForce.log10(a.buffer, result: &result.buffer) 31 | return result 32 | } 33 | 34 | public static func log2(a: Matrix) -> Matrix { 35 | var result = Matrix(like: a) 36 | vForce.log2(a.buffer, result: &result.buffer) 37 | return result 38 | } 39 | 40 | public static func logb(a: Matrix) -> Matrix { 41 | var result = Matrix(like: a) 42 | vForce.logb(a.buffer, result: &result.buffer) 43 | return result 44 | } 45 | } 46 | 47 | extension Double: Logarithm { 48 | public static func log(a: Matrix) -> Matrix { 49 | var result = Matrix(like: a) 50 | vForce.log(a.buffer, result: &result.buffer) 51 | return result 52 | } 53 | 54 | public static func log1p(a: Matrix) -> Matrix { 55 | var result = Matrix(like: a) 56 | vForce.log1p(a.buffer, result: &result.buffer) 57 | return result 58 | } 59 | 60 | public static func log10(a: Matrix) -> Matrix { 61 | var result = Matrix(like: a) 62 | vForce.log10(a.buffer, result: &result.buffer) 63 | return result 64 | } 65 | 66 | public static func log2(a: Matrix) -> Matrix { 67 | var result = Matrix(like: a) 68 | vForce.log2(a.buffer, result: &result.buffer) 69 | return result 70 | } 71 | 72 | public static func logb(a: Matrix) -> Matrix { 73 | var result = Matrix(like: a) 74 | vForce.logb(a.buffer, result: &result.buffer) 75 | return result 76 | } 77 | } 78 | 79 | extension Matrix where Scalar: Logarithm { 80 | 81 | /// Calculate the natural logarithm for each matrix element. 82 | /// - Returns: Element-wise log matrix. 83 | public func log() -> Matrix { 84 | Scalar.log(a: self) 85 | } 86 | 87 | /// Calculate the log(1+x) for each matrix element. 88 | /// - Returns: Element-wise log(1+x) matrix. 89 | public func log1p() -> Matrix { 90 | Scalar.log1p(a: self) 91 | } 92 | 93 | /// Calculate the base 10 logarithm for each matrix element. 94 | /// - Returns: Element-wise base 10 logarithm matrix. 95 | public func log10() -> Matrix { 96 | Scalar.log10(a: self) 97 | } 98 | 99 | /// Calculate the base 2 logarithm for each matrix element. 100 | /// - Returns: Element-wise base 2 logarithm matrix. 101 | public func log2() -> Matrix { 102 | Scalar.log2(a: self) 103 | } 104 | 105 | /// Calculate the unbiased exponent for each matrix element. 106 | /// - Returns: Element-wise unbiased exponent matrix. 107 | public func logb() -> Matrix { 108 | Scalar.logb(a: self) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Matrix.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Matrix structure. 3 | */ 4 | 5 | import Accelerate 6 | 7 | /// A two-dimensional structure for numerical data. 8 | public struct Matrix { 9 | 10 | /// Number of rows in the matrix. 11 | public let rows: Int 12 | 13 | /// Number of columns in the matrix. 14 | public let columns: Int 15 | 16 | /// Number of elements in the matrix. 17 | public var count: Int { 18 | self.rows * self.columns 19 | } 20 | 21 | // Private reference to mutable buffer for underlying data storage 22 | private let data: DataBuffer 23 | 24 | var buffer: UnsafeMutableBufferPointer { 25 | get { self.data.buffer } 26 | set { self.data.buffer = newValue } 27 | } 28 | 29 | // This is used by the iterator, see next() function in Sequence, IteratorProtocol extension 30 | private var nextRowStartIndex = 0 31 | 32 | /// Create a matrix using embedded Swift arrays. 33 | /// - Parameter content: Arrays containing scalar values. 34 | public init(_ content: [[Scalar]]) { 35 | self.rows = content.count 36 | self.columns = content[0].count 37 | self.data = DataBuffer(array: content.flatMap { $0 }) 38 | } 39 | 40 | /// Create an empty matrix with size `m×n` where `m` is number of rows and `n` is number of columns. 41 | /// - Parameters: 42 | /// - rows: Number of rows in the matrix. 43 | /// - columns: Number of columns in the matrix. 44 | public init(rows: Int, columns: Int) { 45 | self.rows = rows 46 | self.columns = columns 47 | self.data = DataBuffer(count: rows * columns) 48 | } 49 | 50 | /// Create an empty matrix with the same size as another matrix. 51 | /// - Parameter matrix: The other matrix. 52 | public init(like matrix: Matrix) { 53 | self.rows = matrix.rows 54 | self.columns = matrix.columns 55 | self.data = DataBuffer(count: matrix.buffer.count) 56 | } 57 | 58 | /// Create a matrix filled with a given value. 59 | /// - Parameters: 60 | /// - rows: Number of rows in the matrix. 61 | /// - columns: Number of columns in the matrix. 62 | /// - fill: Scalar value to fill each matrix element. 63 | public init(rows: Int, columns: Int, fill: Scalar) { 64 | self.rows = rows 65 | self.columns = columns 66 | self.data = DataBuffer(count: rows * columns, fill: fill) 67 | } 68 | 69 | /// Create a matrix from an array of scalar values. The matrix size is m x n where 70 | /// m is number of rows and n is number of columns. 71 | /// - Parameters: 72 | /// - rows: Number of rows in the matrix. 73 | /// - columns: Number of columns in the matrix. 74 | /// - values: Array of scalar values for each matrix element. 75 | public init(rows: Int, columns: Int, values: [Scalar]) { 76 | self.rows = rows 77 | self.columns = columns 78 | self.data = DataBuffer(array: values) 79 | } 80 | 81 | // Create matrix from a mutable memory buffer. Internal use only. 82 | init(rows: Int, columns: Int, buffer: UnsafeMutableBufferPointer) { 83 | self.rows = rows 84 | self.columns = columns 85 | self.data = DataBuffer(buffer: buffer) 86 | } 87 | 88 | /// Get and set a matrix value using the row and column index. 89 | public subscript(row: Int, column: Int) -> Scalar { 90 | get { return self.buffer[(row * self.columns) + column] } 91 | set { self.buffer[(row * self.columns) + column] = newValue } 92 | } 93 | 94 | /// Get and set row values using the row index. 95 | public subscript(row row: Int) -> Matrix { 96 | get { 97 | let start = row * self.columns 98 | let end = row * self.columns + self.columns 99 | let mat = Matrix(rows: 1, columns: self.columns) 100 | mat.buffer[0.. [Scalar] { 112 | get { 113 | var result = [Scalar](repeating: 0 as! Scalar, count: self.rows) 114 | for i in 0.. Matrix { 131 | Matrix(rows: self.rows, columns: self.columns, buffer: self.buffer) 132 | } 133 | } 134 | 135 | extension Matrix: ExpressibleByArrayLiteral { 136 | 137 | public init(arrayLiteral elements: [Scalar]...) { 138 | self.init(elements) 139 | } 140 | } 141 | 142 | extension Matrix: CustomStringConvertible { 143 | 144 | public var description: String { 145 | self.formatted() 146 | } 147 | } 148 | 149 | extension Matrix: CustomDebugStringConvertible { 150 | 151 | public var debugDescription: String { 152 | """ 153 | \(self.rows)x\(self.columns) \(type(of: self)) 154 | \(self.description) 155 | """ 156 | } 157 | } 158 | 159 | extension Matrix: Equatable { 160 | 161 | /// Compare two matrices for equality. 162 | /// - Parameters: 163 | /// - lhs: The first matrix. 164 | /// - rhs: The second matrix. 165 | /// - Returns: True if both matrices are same dimension and contain the same values. 166 | public static func == (lhs: Matrix, rhs: Matrix) -> Bool { 167 | let n = lhs.buffer.count 168 | let cmp = memcmp(lhs.buffer.baseAddress, rhs.buffer.baseAddress, MemoryLayout.size * n) 169 | let buffersEqual = cmp == 0 ? true : false 170 | return lhs.rows == rhs.rows && lhs.columns == rhs.columns && buffersEqual 171 | } 172 | } 173 | 174 | extension Matrix: Sequence, IteratorProtocol { 175 | 176 | public mutating func next() -> [Scalar]? { 177 | if nextRowStartIndex == self.count { 178 | return nil 179 | } else { 180 | let currentRowStartIndex = nextRowStartIndex 181 | nextRowStartIndex += self.columns 182 | let a = Array(self.buffer[currentRowStartIndex.., value: Self) -> Matrix 11 | } 12 | 13 | extension Float: Pad { 14 | public static func pad(a: Matrix, value: Float) -> Matrix { 15 | let m = vDSP_Length(a.columns) // number of columns to copy 16 | let n = vDSP_Length(a.rows) // number of rows to copy 17 | let ta = vDSP_Length(a.columns) // number of columns in a 18 | let tc = vDSP_Length(a.columns + 2) // number of columns in c 19 | 20 | let c = Matrix(rows: a.rows + 2, columns: a.columns + 2, fill: value) 21 | vDSP_mmov(a.buffer.baseAddress!, c.buffer.baseAddress! + (a.columns + 3), m, n, ta, tc) 22 | 23 | return c 24 | } 25 | } 26 | 27 | extension Double: Pad { 28 | public static func pad(a: Matrix, value: Double) -> Matrix { 29 | let m = vDSP_Length(a.columns) // number of columns to copy 30 | let n = vDSP_Length(a.rows) // number of rows to copy 31 | let ta = vDSP_Length(a.columns) // number of columns in a 32 | let tc = vDSP_Length(a.columns + 2) // number of columns in c 33 | 34 | let c = Matrix(rows: a.rows + 2, columns: a.columns + 2, fill: value) 35 | vDSP_mmovD(a.buffer.baseAddress!, c.buffer.baseAddress! + (a.columns + 3), m, n, ta, tc) 36 | 37 | return c 38 | } 39 | } 40 | 41 | extension Matrix where Scalar: Pad, Scalar: Numeric { 42 | 43 | /// Pad a matrix with a constant value. 44 | /// 45 | /// Create a padded matrix with zeros. 46 | /// ```swift 47 | /// let a: Matrix = [[1, 2], 48 | /// [3, 4]] 49 | /// let p = a.pad() 50 | /// // The matrix p has values of 51 | /// // 0 0 0 0 52 | /// // 0 1 2 0 53 | /// // 0 3 4 0 54 | /// // 0 0 0 0 55 | /// ``` 56 | /// Create a padded matrix using a value of 9. 57 | /// ```swift 58 | /// let a: Matrix = [[1, 2], 59 | /// [3, 4]] 60 | /// let p = a.pad(with: 9) 61 | /// // The matrix p has values of 62 | /// // 9 9 9 9 63 | /// // 9 1 2 9 64 | /// // 9 3 4 9 65 | /// // 9 9 9 9 66 | /// ``` 67 | /// 68 | /// - Parameter value: The scalar value for padding the matrix. Default is 0. 69 | /// - Returns: A padded matrix with zeros or some other value along its border. 70 | public func pad(with value: Scalar = .zero) -> Matrix { 71 | Scalar.pad(a: self, value: value) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Power.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Matrix extension for power functions. 3 | */ 4 | 5 | import Accelerate 6 | 7 | public protocol Power { 8 | static func power(a: Matrix, exp: Self) -> Matrix 9 | static func power(a: Matrix, exp: [Self]) -> Matrix 10 | } 11 | 12 | extension Float: Power { 13 | public static func power(a: Matrix, exp: Float) -> Matrix { 14 | let output = Matrix(like: a) 15 | withUnsafePointer(to: exp) { expPtr in 16 | withUnsafePointer(to: Int32(a.buffer.count)) { lengthPtr in 17 | vvpowsf(output.buffer.baseAddress!, expPtr, a.buffer.baseAddress!, lengthPtr) 18 | } 19 | } 20 | return output 21 | } 22 | 23 | public static func power(a: Matrix, exp: [Float]) -> Matrix { 24 | precondition(a.buffer.count == exp.count, "Matrix size must correspond to number of exponents") 25 | var result = Matrix(like: a) 26 | vForce.pow(bases: a.buffer, exponents: exp, result: &result.buffer) 27 | return result 28 | } 29 | } 30 | 31 | extension Double: Power { 32 | public static func power(a: Matrix, exp: Double) -> Matrix { 33 | let output = Matrix(like: a) 34 | withUnsafePointer(to: exp) { expPtr in 35 | withUnsafePointer(to: Int32(a.buffer.count)) { lengthPtr in 36 | vvpows(output.buffer.baseAddress!, expPtr, a.buffer.baseAddress!, lengthPtr) 37 | } 38 | } 39 | return output 40 | } 41 | 42 | public static func power(a: Matrix, exp: [Double]) -> Matrix { 43 | precondition(a.buffer.count == exp.count, "Matrix size must correspond to number of exponents") 44 | var result = Matrix(like: a) 45 | vForce.pow(bases: a.buffer, exponents: exp, result: &result.buffer) 46 | return result 47 | } 48 | } 49 | 50 | extension Matrix where Scalar: Power { 51 | 52 | /// Raise each matrix element to the power of a constant exponent. 53 | /// - Parameter k: A constant exponent value. 54 | /// - Returns: Matrix from the bases raised to a constant power. 55 | public func power(_ k: Scalar) -> Matrix { 56 | Scalar.power(a: self, exp: k) 57 | } 58 | 59 | /// Raise each matrix element to the power of several exponents. 60 | /// - Parameter k: Array of exponent values. 61 | /// - Returns: Matrix from the bases raised to several powers. 62 | public func power(_ k: [Scalar]) -> Matrix { 63 | Scalar.power(a: self, exp: k) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/MatrixModule/Trigonometry.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Matrix trigonometry protocol and functions. 3 | */ 4 | 5 | import Accelerate 6 | 7 | public protocol Trigonometry { 8 | static func sin(_ a: Matrix) -> Matrix 9 | static func cos(_ a: Matrix) -> Matrix 10 | static func tan(_ a: Matrix) -> Matrix 11 | } 12 | 13 | extension Float: Trigonometry { 14 | 15 | public static func sin(_ a: Matrix) -> Matrix { 16 | var result = Matrix(like: a) 17 | vForce.sin(a.buffer, result: &result.buffer) 18 | return result 19 | } 20 | 21 | public static func cos(_ a: Matrix) -> Matrix { 22 | var result = Matrix(like: a) 23 | vForce.cos(a.buffer, result: &result.buffer) 24 | return result 25 | } 26 | 27 | public static func tan(_ a: Matrix) -> Matrix { 28 | var result = Matrix(like: a) 29 | vForce.tan(a.buffer, result: &result.buffer) 30 | return result 31 | } 32 | } 33 | 34 | extension Double: Trigonometry { 35 | 36 | public static func sin(_ a: Matrix) -> Matrix { 37 | var result = Matrix(like: a) 38 | vForce.sin(a.buffer, result: &result.buffer) 39 | return result 40 | } 41 | 42 | public static func cos(_ a: Matrix) -> Matrix { 43 | var result = Matrix(like: a) 44 | vForce.cos(a.buffer, result: &result.buffer) 45 | return result 46 | } 47 | 48 | public static func tan(_ a: Matrix) -> Matrix { 49 | var result = Matrix(like: a) 50 | vForce.tan(a.buffer, result: &result.buffer) 51 | return result 52 | } 53 | } 54 | 55 | /// Calculate the sine of each element in a matrix. 56 | /// ```swift 57 | /// let mat: Matrix = [[1, 2], [3, 4]] 58 | /// let result = sin(mat)) 59 | /// ``` 60 | /// - Parameter mat: The input matrix. 61 | /// - Returns: A matrix representing the sine of the input matrix. 62 | public func sin(_ mat: Matrix) -> Matrix where Scalar: Trigonometry { 63 | Scalar.sin(mat) 64 | } 65 | 66 | /// Calculate the cosine of each element in a matrix. 67 | /// ```swift 68 | /// let mat: Matrix = [[1, 2], [3, 4]] 69 | /// let result = cos(mat)) 70 | /// ``` 71 | /// - Parameter mat: The input matrix. 72 | /// - Returns: A matrix representing the cosine of the input matrix. 73 | public func cos(_ mat: Matrix) -> Matrix where Scalar: Trigonometry { 74 | Scalar.cos(mat) 75 | } 76 | 77 | /// Calculate the tangent of each element in a matrix. 78 | /// ```swift 79 | /// let mat: Matrix = [[1, 2], [3, 4]] 80 | /// let result = tan(mat)) 81 | /// ``` 82 | /// - Parameter mat: The input matrix. 83 | /// - Returns: A matrix representing the tangent of the input matrix. 84 | public func tan(_ mat: Matrix) -> Matrix where Scalar: Trigonometry { 85 | Scalar.tan(mat) 86 | } 87 | -------------------------------------------------------------------------------- /Sources/Numerix/Documentation.docc/GetStarted.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | First steps for installing and using Numerix. 4 | 5 | ## Installation 6 | 7 | If using Xcode, select *File* and choose *Add Package Dependencies...*, then enter the URL for the Numerix GitHub repository which is . 8 | 9 | If editing a `Package.swift` manifest, add Numerix as a dependency such as: 10 | 11 | ```swift 12 | dependencies: [ 13 | .package(url: "https://github.com/wigging/numerix", branch: "main") 14 | ] 15 | 16 | targets: [ 17 | .target(name: "MyLibrary", dependencies: ["Numerix"]) 18 | ] 19 | ``` 20 | 21 | The final step is to import the package in a Swift file as shown below. 22 | 23 | ```swift 24 | import Numerix 25 | ``` 26 | 27 | ## Usage 28 | 29 | The example below uses the Numerix package to perform element-wise matrix multiplication of two 2x3 matrices. See the documentation for more examples. 30 | 31 | ```swift 32 | import Numerix 33 | 34 | let a: Matrix = [[1, 2.5, 3], 35 | [4, 5, 6.8]] 36 | 37 | let b: Matrix = [[0.1, 2.5, 3], 38 | [4.2, 5, 6.8]] 39 | 40 | let c = a .* b 41 | 42 | print(c) 43 | // ⎛ 0.1000 6.2500 9.0000 ⎞ 44 | // ⎝ 16.8000 25.0000 46.2400 ⎠ 45 | ``` 46 | -------------------------------------------------------------------------------- /Sources/Numerix/Documentation.docc/LinearAlgebra.md: -------------------------------------------------------------------------------- 1 | # Linear Algebra 2 | 3 | Vector and Matrix linear algebra operations. 4 | 5 | Numerix uses the Accelerate framework to access Basic Linear Algebra Subprograms (BLAS) and Linear Algebra Package (LAPACK) for performing common linear algebra operations. More information about BLAS and LAPACK is available at and respectively. 6 | 7 | - ``Vector/dot(_:)`` 8 | - ``Vector/norm()`` 9 | - ``Vector/scale(by:)`` 10 | - ``Vector/sum()`` 11 | - ``Vector/absoluteSum()`` 12 | - ``Vector/cumulativeSum()`` 13 | - ``Numerix/swapValues(_:_:)-(Vector,_)`` 14 | -------------------------------------------------------------------------------- /Sources/Numerix/Documentation.docc/Numerix.md: -------------------------------------------------------------------------------- 1 | # ``Numerix`` 2 | 3 | Linear algebra and numerical computing with Swift on Apple devices. 4 | 5 | ## Overview 6 | 7 | Numerix is an open-source Swift package that provides Vector, Matrix, and ShapedArray structures for performing linear algebra and other numerical computations on Apple devices. It uses the Accelerate framework for high-performance and energy-efficient calculations. See the Modules documentation for more information about the Vector, Matrix, and ShapedArray structures. 8 | 9 | ## Modules 10 | 11 | Numerix is comprised of modules that provide complex number, vector, matrix, and shaped array structures for working with numerical data. Documentation for each module is available at: 12 | 13 | - [Vector Module](./VectorModule) 14 | - [Matrix Module](./MatrixModule) 15 | - [ShapedArray Module](./ShapedArrayModule) 16 | 17 | ## Contributing 18 | 19 | See the `CONTRIBUTING.md` document in the [Numerix GitHub repository](https://github.com/wigging/numerix) for contributing guidelines. 20 | 21 | ## Topics 22 | 23 | ### Essentials 24 | 25 | - 26 | - 27 | -------------------------------------------------------------------------------- /Sources/Numerix/Numerix.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Exported modules for the Numerix package. 3 | */ 4 | 5 | @_exported import VectorModule 6 | @_exported import MatrixModule 7 | @_exported import ShapedArrayModule 8 | -------------------------------------------------------------------------------- /Sources/ShapedArrayModule/Arithmetic.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Arithmetic protocol and extensions for the ShapedArray struct. 3 | */ 4 | 5 | import Accelerate 6 | 7 | public protocol Arithmetic { 8 | static func add(_ a: ShapedArray, _ k: Self) -> ShapedArray 9 | static func add(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray 10 | static func subtract(_ k: Self, _ a: ShapedArray) -> ShapedArray 11 | static func subtract(_ a: ShapedArray, _ k: Self) -> ShapedArray 12 | static func subtract(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray 13 | static func multiply(_ a: ShapedArray, _ k: Self) -> ShapedArray 14 | static func multiply(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray 15 | static func divide(_ k: Self, _ b: ShapedArray) -> ShapedArray 16 | static func divide(_ a: ShapedArray, _ k: Self) -> ShapedArray 17 | static func divide(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray 18 | } 19 | 20 | extension Int: Arithmetic { 21 | 22 | public static func add(_ a: ShapedArray, _ k: Int) -> ShapedArray { 23 | let arr = ShapedArray(shape: a.shape) 24 | let count = a.shape.reduce(1, *) 25 | for i in 0.., _ b: ShapedArray) -> ShapedArray { 32 | let arr = ShapedArray(shape: a.shape) 33 | let count = a.shape.reduce(1, *) 34 | for i in 0..) -> ShapedArray { 41 | let arr = ShapedArray(shape: b.shape) 42 | let count = b.shape.reduce(1, *) 43 | for i in 0.., _ k: Int) -> ShapedArray { 50 | let arr = ShapedArray(shape: a.shape) 51 | let count = a.shape.reduce(1, *) 52 | for i in 0.., _ b: ShapedArray) -> ShapedArray { 59 | let arr = ShapedArray(shape: a.shape) 60 | let count = a.shape.reduce(1, *) 61 | for i in 0.., _ k: Int) -> ShapedArray { 68 | let array = ShapedArray(shape: a.shape) 69 | let count = a.shape.reduce(1, *) 70 | for i in 0.., _ b: ShapedArray) -> ShapedArray { 77 | let array = ShapedArray(shape: a.shape) 78 | let count = a.shape.reduce(1, *) 79 | for i in 0..) -> ShapedArray { 86 | let arr = ShapedArray(shape: b.shape) 87 | let count = b.shape.reduce(1, *) 88 | for i in 0.., _ k: Int) -> ShapedArray { 95 | let arr = ShapedArray(shape: a.shape) 96 | let count = a.shape.reduce(1, *) 97 | for i in 0.., _ b: ShapedArray) -> ShapedArray { 104 | let arr = ShapedArray(shape: a.shape) 105 | let count = a.shape.reduce(1, *) 106 | for i in 0.., _ k: Float) -> ShapedArray { 116 | var arr = ShapedArray(shape: a.shape) 117 | vDSP.add(k, a.buffer, result: &arr.buffer) 118 | return arr 119 | } 120 | 121 | public static func add(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray { 122 | var arr = ShapedArray(shape: a.shape) 123 | vDSP.add(a.buffer, b.buffer, result: &arr.buffer) 124 | return arr 125 | } 126 | 127 | public static func subtract(_ k: Float, _ a: ShapedArray) -> ShapedArray { 128 | let arr = Array(repeating: k, count: a.buffer.count) 129 | var result = ShapedArray(shape: a.shape) 130 | vDSP.subtract(arr, a.buffer, result: &result.buffer) 131 | return result 132 | } 133 | 134 | public static func subtract(_ a: ShapedArray, _ k: Float) -> ShapedArray { 135 | let arr = Array(repeating: k, count: a.buffer.count) 136 | var result = ShapedArray(shape: a.shape) 137 | vDSP.subtract(arr, a.buffer, result: &result.buffer) 138 | return result 139 | } 140 | 141 | public static func subtract(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray { 142 | precondition(a.shape == b.shape, "Shaped arrays must have same shape") 143 | var result = ShapedArray(shape: a.shape) 144 | vDSP.subtract(a.buffer, b.buffer, result: &result.buffer) 145 | return result 146 | } 147 | 148 | public static func multiply(_ a: ShapedArray, _ k: Float) -> ShapedArray { 149 | var array = ShapedArray(shape: a.shape) 150 | vDSP.multiply(k, a.buffer, result: &array.buffer) 151 | return array 152 | } 153 | 154 | public static func multiply(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray { 155 | var array = ShapedArray(shape: a.shape) 156 | vDSP.multiply(a.buffer, b.buffer, result: &array.buffer) 157 | return array 158 | } 159 | 160 | public static func divide(_ k: Float, _ b: ShapedArray) -> ShapedArray { 161 | var arr = ShapedArray(shape: b.shape) 162 | vDSP.divide(k, b.buffer, result: &arr.buffer) 163 | return arr 164 | } 165 | 166 | public static func divide(_ a: ShapedArray, _ k: Float) -> ShapedArray { 167 | var arr = ShapedArray(shape: a.shape) 168 | vDSP.divide(a.buffer, k, result: &arr.buffer) 169 | return arr 170 | } 171 | 172 | public static func divide(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray { 173 | var arr = ShapedArray(shape: a.shape) 174 | vDSP.divide(a.buffer, b.buffer, result: &arr.buffer) 175 | return arr 176 | } 177 | } 178 | 179 | extension Double: Arithmetic { 180 | 181 | public static func add(_ a: ShapedArray, _ k: Double) -> ShapedArray { 182 | var arr = ShapedArray(shape: a.shape) 183 | vDSP.add(k, a.buffer, result: &arr.buffer) 184 | return arr 185 | } 186 | 187 | public static func add(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray { 188 | var arr = ShapedArray(shape: a.shape) 189 | vDSP.add(a.buffer, b.buffer, result: &arr.buffer) 190 | return arr 191 | } 192 | 193 | public static func subtract(_ k: Double, _ a: ShapedArray) -> ShapedArray { 194 | let arr = Array(repeating: k, count: a.buffer.count) 195 | var result = ShapedArray(shape: a.shape) 196 | vDSP.subtract(arr, a.buffer, result: &result.buffer) 197 | return result 198 | } 199 | 200 | public static func subtract(_ a: ShapedArray, _ k: Double) -> ShapedArray { 201 | let arr = Array(repeating: k, count: a.buffer.count) 202 | var result = ShapedArray(shape: a.shape) 203 | vDSP.subtract(arr, a.buffer, result: &result.buffer) 204 | return result 205 | } 206 | 207 | public static func subtract(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray { 208 | precondition(a.shape == b.shape, "Shaped arrays must have same shape") 209 | var result = ShapedArray(shape: a.shape) 210 | vDSP.subtract(a.buffer, b.buffer, result: &result.buffer) 211 | return result 212 | } 213 | 214 | public static func multiply(_ a: ShapedArray, _ k: Double) -> ShapedArray { 215 | var array = ShapedArray(shape: a.shape) 216 | vDSP.multiply(k, a.buffer, result: &array.buffer) 217 | return array 218 | } 219 | 220 | public static func multiply(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray { 221 | var array = ShapedArray(shape: a.shape) 222 | vDSP.multiply(a.buffer, b.buffer, result: &array.buffer) 223 | return array 224 | } 225 | 226 | public static func divide(_ k: Double, _ b: ShapedArray) -> ShapedArray { 227 | var arr = ShapedArray(shape: b.shape) 228 | vDSP.divide(k, b.buffer, result: &arr.buffer) 229 | return arr 230 | } 231 | 232 | public static func divide(_ a: ShapedArray, _ k: Double) -> ShapedArray { 233 | var arr = ShapedArray(shape: a.shape) 234 | vDSP.divide(a.buffer, k, result: &arr.buffer) 235 | return arr 236 | } 237 | 238 | public static func divide(_ a: ShapedArray, _ b: ShapedArray) -> ShapedArray { 239 | var arr = ShapedArray(shape: a.shape) 240 | vDSP.divide(a.buffer, b.buffer, result: &arr.buffer) 241 | return arr 242 | } 243 | } 244 | 245 | extension ShapedArray where Scalar: Arithmetic { 246 | 247 | /// Element-wise addition of a scalar and shaped array. 248 | /// - Parameters: 249 | /// - lhs: The left-hand side scalar value `k`. 250 | /// - rhs: The right-hand side shaped array `A`. 251 | /// - Returns: A shaped array that is the result of `k + A`. 252 | public static func + (lhs: Scalar, rhs: ShapedArray) -> ShapedArray { 253 | Scalar.add(rhs, lhs) 254 | } 255 | 256 | /// Element-wise addition of a scalar and shaped array. 257 | /// - Parameters: 258 | /// - lhs: The left-hand side shaped array `A`. 259 | /// - rhs: The right-hand side scalar value `k`. 260 | /// - Returns: A shaped array that is the result of `A + k`. 261 | public static func + (lhs: ShapedArray, rhs: Scalar) -> ShapedArray { 262 | Scalar.add(lhs, rhs) 263 | } 264 | 265 | /// Element-wise addition of two shaped arrays. 266 | /// - Parameters: 267 | /// - lhs: The left-hand side shaped array `A`. 268 | /// - rhs: The right-hand side shaped array `B`. 269 | /// - Returns: A shaped array that is the result of `A + B`. 270 | public static func + (lhs: ShapedArray, rhs: ShapedArray) -> ShapedArray { 271 | Scalar.add(lhs, rhs) 272 | } 273 | 274 | /// In-place element-wise addition of a shaped array and scalar. 275 | /// - Parameters: 276 | /// - left: The left-hand side mutable shaped array. 277 | /// - right: The right-hand side scalar value. 278 | public static func += (left: inout ShapedArray, right: Scalar) { 279 | left = left + right 280 | } 281 | 282 | /// In-place element-wise addition of two shaped arrays. 283 | /// - Parameters: 284 | /// - left: The left-hand side mutable input shaped array. 285 | /// - right: The right-hand side shaped array to add to the left-hand side mutable shaped array. 286 | public static func += (left: inout ShapedArray, right: ShapedArray) { 287 | precondition(left.shape == right.shape, "Shaped arrays must have same shape") 288 | left = left + right 289 | } 290 | 291 | /// Element-wise subtraction of a scalar and shaped array. 292 | /// - Parameters: 293 | /// - lhs: The left-hand side scalar value `k`. 294 | /// - rhs: The right-hand side shaped array `A`. 295 | /// - Returns: A shaped array that is the result of `k - A`. 296 | public static func - (lhs: Scalar, rhs: ShapedArray) -> ShapedArray { 297 | Scalar.subtract(lhs, rhs) 298 | } 299 | 300 | /// Element-wise subtraction of a scalar and shaped array. 301 | /// - Parameters: 302 | /// - lhs: The left-hand side shaped array `A`. 303 | /// - rhs: The right-hand side scalar value `k`. 304 | /// - Returns: A shaped array that is the result of `A - k`. 305 | public static func - (lhs: ShapedArray, rhs: Scalar) -> ShapedArray { 306 | Scalar.subtract(lhs, rhs) 307 | } 308 | 309 | /// Element-wise subtraction of two shaped arrays. 310 | /// - Parameters: 311 | /// - lhs: The left-hand side shaped array `A`. 312 | /// - rhs: The right-hand side shaped array `B`. 313 | /// - Returns: A shaped array that is the result of `A + B`. 314 | public static func - (lhs: ShapedArray, rhs: ShapedArray) -> ShapedArray { 315 | Scalar.subtract(lhs, rhs) 316 | } 317 | 318 | /// Element-wise multiplication of a scalar value and a shaped array. 319 | /// - Parameters: 320 | /// - lhs: The input scalar value `k` on the left-hand side. 321 | /// - rhs: The input shaped array `A` on the right-hand side. 322 | /// - Returns: The output shaped array `B` where `k * A = B`. 323 | public static func * (lhs: Scalar, rhs: ShapedArray) -> ShapedArray { 324 | Scalar.multiply(rhs, lhs) 325 | } 326 | 327 | /// Element-wise multiplication of a shaped array and a scalar value. 328 | /// - Parameters: 329 | /// - lhs: The input shaped array `A` on the left-hand side. 330 | /// - rhs: The input scalar value `k` on the right-hand side. 331 | /// - Returns: The output shaped array `B` where `k * A = B`. 332 | public static func * (lhs: ShapedArray, rhs: Scalar) -> ShapedArray { 333 | Scalar.multiply(lhs, rhs) 334 | } 335 | 336 | /// Element-wise multiplication of two shaped arrays. 337 | /// - Parameters: 338 | /// - lhs: The input shaped array `A` on the left-hand side. 339 | /// - rhs: The input shaped array `B` on the right-hand side. 340 | /// - Returns: The output shaped array `C` where `A * B = C`. 341 | public static func * (lhs: ShapedArray, rhs: ShapedArray) -> ShapedArray { 342 | Scalar.multiply(lhs, rhs) 343 | } 344 | 345 | /// Element-wise division operator for dividing a scalar by a shaped array. 346 | /// - Parameters: 347 | /// - lhs: The scalar value. 348 | /// - rhs: The shaped array. 349 | /// - Returns: The element-wise division of a scalar by a shaped array. 350 | public static func / (lhs: Scalar, rhs: ShapedArray) -> ShapedArray { 351 | Scalar.divide(lhs, rhs) 352 | } 353 | 354 | /// Element-wise division operator for dividing a scalar by a shaped array. 355 | /// - Parameters: 356 | /// - lhs: The shaped array. 357 | /// - rhs: The scalar value. 358 | /// - Returns: The element-wise division of a scalar by a shaped array. 359 | public static func / (lhs: ShapedArray, rhs: Scalar) -> ShapedArray { 360 | Scalar.divide(lhs, rhs) 361 | } 362 | 363 | /// Element-wise division operator for dividing two shaped arrays. Shaped 364 | /// arrays must have the same shape. 365 | /// - Parameters: 366 | /// - lhs: The left shaped array. 367 | /// - rhs: The right shaped array. 368 | /// - Returns: The element-wise division of two shaped arrays. 369 | public static func / (lhs: ShapedArray, rhs: ShapedArray) -> ShapedArray { 370 | Scalar.divide(lhs, rhs) 371 | } 372 | 373 | /// In-place element-wise division of a shaped array and scalar. 374 | /// - Parameters: 375 | /// - lhs: The left-hand side mutable shaped array. 376 | /// - rhs: The right-hand side scalar value. 377 | public static func /= (lhs: inout ShapedArray, rhs: Scalar) { 378 | lhs = lhs / rhs 379 | } 380 | 381 | /// In-place element-wise division of two shaped arrays. 382 | /// - Parameters: 383 | /// - lhs: The left-hand side mutable shaped array. 384 | /// - rhs: The right-hand side shaped array. 385 | public static func /= (lhs: inout ShapedArray, rhs: ShapedArray) { 386 | precondition(lhs.shape == rhs.shape, "Shaped arrays must have the same shape") 387 | lhs = lhs / rhs 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /Sources/ShapedArrayModule/DataBuffer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Data buffer class. 3 | */ 4 | 5 | class DataBuffer { 6 | var buffer: UnsafeMutableBufferPointer 7 | 8 | init(count: Int, fill: T) { 9 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: count) 10 | self.buffer.initialize(repeating: fill) 11 | } 12 | 13 | init(count: Int) { 14 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: count) 15 | } 16 | 17 | init(array: [T]) { 18 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: array.count) 19 | _ = self.buffer.initialize(fromContentsOf: array) 20 | } 21 | 22 | init(buffer: UnsafeMutableBufferPointer) { 23 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: buffer.count) 24 | _ = self.buffer.initialize(from: buffer) 25 | } 26 | 27 | deinit { 28 | self.buffer.deinitialize() 29 | self.buffer.deallocate() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ShapedArrayModule/Documentation.docc/ShapedArrayModule.md: -------------------------------------------------------------------------------- 1 | # ``ShapedArrayModule`` 2 | 3 | N-dimensional array operations. 4 | 5 | ## Overview 6 | 7 | This module provides the ``ShapedArray`` structure for working with N-dimensional numerical data. Arithmetic and several linear algebra operations are supported via protocols. Scalar values can be Int, Float, or Double. See the topics below for more information. 8 | 9 | ## Topics 10 | -------------------------------------------------------------------------------- /Sources/ShapedArrayModule/Helpers.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Helper functions for ShapedArray struct. 3 | */ 4 | 5 | // Get the last row for each sub-matrix 6 | // This accounts for the empty line between each sub-matrix when printing 7 | func getLastRows(_ array: [Int]) -> [Int] { 8 | array.reduce(into: [Int]()) { partialResult, x in 9 | if let last = partialResult.last { 10 | partialResult.append(last * x + x - 1) 11 | } else { 12 | partialResult.append(x) 13 | } 14 | } 15 | } 16 | 17 | // Get the shape of nested arrays that represent an N-dimensional array 18 | // This is used for ShapedArray init and ShapedArray literal 19 | func getShape(_ arr: [ShapedArrayElement]) -> [Int] { 20 | switch arr.first { 21 | case .array(let inner): 22 | [arr.count] + getShape(inner) 23 | default: 24 | [arr.count] 25 | } 26 | } 27 | 28 | // Flatten nested arrays to an array 29 | func flatten(_ arrays: [ShapedArrayElement]) -> [T] { 30 | var result = [T]() 31 | for val in arrays { 32 | switch val { 33 | case .array(let arr): 34 | result.append(contentsOf: flatten(arr)) 35 | case .element(let elm): 36 | result.append(elm) 37 | } 38 | } 39 | 40 | return result 41 | } 42 | 43 | /// Calculate the flat index of an N-dimensional element. 44 | /// 45 | /// The indices for each dimension are 46 | /// 47 | /// ``` 48 | /// indices = i₁, i₂, ... iₙ 49 | /// ``` 50 | /// 51 | /// The dimensions for each index are 52 | /// 53 | /// ``` 54 | /// shape = d₁, d₂, ... dₙ 55 | /// ``` 56 | /// 57 | /// The flat index that corresponds to N-dimensions is 58 | /// 59 | /// ``` 60 | /// flat index = i₁ + (i₂ * d₁) + (i₃ * d₁ * d₂) + ... + (iₙ * d₁ * d₂ * ... * dₙ₋₁) 61 | /// ``` 62 | /// 63 | /// - Parameters: 64 | /// - indices: Indices of the element for each dimension. 65 | /// - shape: Dimensions of the array. 66 | /// - Returns: Index of the element corresponding to one-dimension. 67 | func flatIndex(indices: [Int], shape: [Int]) -> Int { 68 | var index = 0 69 | var stride = 1 70 | 71 | for i in (0.. { 9 | 10 | /// Dimensions of the shaped array. 11 | public let shape: [Int] 12 | 13 | // Reference to mutable buffer for underlying data storage, use only within this struct 14 | private let data: DataBuffer 15 | 16 | /// Mutable buffer for underlying data storge and access. 17 | var buffer: UnsafeMutableBufferPointer { 18 | get { self.data.buffer } 19 | set { self.data.buffer = newValue } 20 | } 21 | 22 | /// Create an empty shaped array with a given shape. 23 | /// - Parameter shape: The shape of the shaped array. 24 | public init(shape: [Int]) { 25 | self.shape = shape 26 | self.data = DataBuffer(count: shape.reduce(1, *)) 27 | } 28 | 29 | /// Create a shaped array from a standard array with a given shape. 30 | /// - Parameters: 31 | /// - array: A standard array. 32 | /// - shape: The shape of the shaped array. 33 | public init(shape: [Int], array: [Scalar]) { 34 | self.shape = shape 35 | self.data = DataBuffer(array: array) 36 | } 37 | 38 | /// Create a shaped array from nested arrays. 39 | /// 40 | /// Nested arrays (an array of arrays) can be used to create a shaped array. 41 | /// For example, a single-precision 2D shaped array can be created as follows: 42 | /// ```swift 43 | /// let arr = ShapedArray([[1, 2, 3], [4, 5, 6]]) 44 | /// ``` 45 | /// The 2D shaped array can also be created as: 46 | /// ```swift 47 | /// let arr: ShapedArray = [[1, 2, 3], [4, 5, 6]] 48 | /// ``` 49 | /// - Parameter arrays: A nested array literal. 50 | public init(_ arrays: [ShapedArrayElement]) { 51 | self.shape = getShape(arrays) 52 | let arr = flatten(arrays) 53 | self.data = DataBuffer(array: arr) 54 | } 55 | 56 | public subscript(index: Int...) -> Scalar { 57 | get { 58 | let idx = flatIndex(indices: index, shape: self.shape) 59 | return self.buffer[idx] 60 | } 61 | set { 62 | let idx = flatIndex(indices: index, shape: self.shape) 63 | self.buffer[idx] = newValue 64 | } 65 | } 66 | } 67 | 68 | extension ShapedArray: Equatable { 69 | 70 | /// Compare two shaped arrays for equality. 71 | /// - Parameters: 72 | /// - lhs: The shaped array on the left-hand side. 73 | /// - rhs: The shaped array on the right-hand side. 74 | /// - Returns: True if both shaped arrays are same shape and contain the same values. 75 | public static func == (lhs: ShapedArray, rhs: ShapedArray) -> Bool { 76 | let n = lhs.buffer.count 77 | let cmp = memcmp(lhs.buffer.baseAddress, rhs.buffer.baseAddress, MemoryLayout.size * n) 78 | let buffersEqual = cmp == 0 ? true : false 79 | return lhs.shape == rhs.shape && buffersEqual 80 | } 81 | } 82 | 83 | extension ShapedArray: ExpressibleByArrayLiteral { 84 | 85 | public init(arrayLiteral elements: ShapedArrayElement...) { 86 | self.init(elements) 87 | } 88 | } 89 | 90 | extension ShapedArray: CustomStringConvertible where Scalar: NumberStyle { 91 | 92 | public var description: String { 93 | 94 | // Handle one-dimension array 95 | if shape.count == 1 || shape.count == 2 && shape[0] == 1 { 96 | var descr = "( " 97 | descr += self.buffer.map { "\($0)" }.joined(separator: " ") 98 | descr += " )" 99 | return descr 100 | } 101 | 102 | // Chunk size is number of columns 103 | let chunkSize = shape.last ?? 1 104 | 105 | let chunks = stride(from: 0, to: self.buffer.count, by: chunkSize).map { 106 | Array(self.buffer[$0.. { 7 | case array([ShapedArrayElement]) 8 | case element(T) 9 | } 10 | 11 | extension ShapedArrayElement: ExpressibleByArrayLiteral { 12 | public init(arrayLiteral elements: ShapedArrayElement...) { 13 | self = .array(elements) 14 | } 15 | } 16 | 17 | extension ShapedArrayElement: ExpressibleByIntegerLiteral where T: ExpressibleByIntegerLiteral { 18 | public init(integerLiteral value: T.IntegerLiteralType) { 19 | self = .element(T(integerLiteral: value)) 20 | } 21 | } 22 | 23 | extension ShapedArrayElement: ExpressibleByFloatLiteral where T: ExpressibleByFloatLiteral { 24 | public init(floatLiteral value: T.FloatLiteralType) { 25 | self = .element(T(floatLiteral: value)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/VectorModule/Algebra.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Linear algebra protocol and extensions for the Vector struct. 3 | */ 4 | 5 | import Accelerate 6 | 7 | @_documentation(visibility: private) 8 | public protocol Algebra { 9 | static func dot(_ a: Vector, _ b: Vector) -> Self 10 | static func norm(_ a: Vector) -> Self 11 | static func scale(_ a: inout Vector, by k: Self) 12 | static func sum(_ a: Vector) -> Self 13 | static func absoluteSum(_ a: Vector) -> Self 14 | static func cumulativeSum(_ a: Vector) -> Vector 15 | static func swapValues(a: inout Vector, b: inout Vector) 16 | } 17 | 18 | @_documentation(visibility: private) 19 | extension Int: Algebra { 20 | 21 | public static func swapValues(a: inout Vector, b: inout Vector) { 22 | precondition(a.size == b.size, "Vectors must be same size") 23 | swap(&a.buffer, &b.buffer) 24 | } 25 | 26 | public static func dot(_ a: Vector, _ b: Vector) -> Int { 27 | var res = Int.zero 28 | for (x, y) in zip(a.buffer, b.buffer) { 29 | res += x * y 30 | } 31 | return res 32 | } 33 | 34 | public static func norm(_ a: Vector) -> Int { 35 | var sumOfSquares = 0 36 | 37 | for i in 0.., by k: Int) { 46 | a *= k 47 | } 48 | 49 | public static func sum(_ a: Vector) -> Int { 50 | var res = Int.zero 51 | for i in 0..) -> Int { 58 | var res = Int.zero 59 | for i in 0..) -> Vector { 66 | var cumulative = Vector(like: a) 67 | var runningTotal = 0 68 | for i in 0.., b: inout Vector) { 80 | precondition(a.size == b.size, "Vectors must be same size") 81 | cblas_sswap(a.size, a.buffer.baseAddress, 1, b.buffer.baseAddress, 1) 82 | } 83 | 84 | public static func dot(_ a: Vector, _ b: Vector) -> Float { 85 | cblas_sdot(a.size, a.buffer.baseAddress, 1, b.buffer.baseAddress, 1) 86 | } 87 | 88 | public static func norm(_ a: Vector) -> Float { 89 | cblas_snrm2(a.size, a.buffer.baseAddress, 1) 90 | } 91 | 92 | public static func scale(_ a: inout Vector, by k: Float) { 93 | cblas_sscal(a.size, k, a.buffer.baseAddress, 1) 94 | } 95 | 96 | public static func sum(_ a: Vector) -> Float { 97 | vDSP.sum(a.buffer) 98 | } 99 | 100 | public static func absoluteSum(_ a: Vector) -> Float { 101 | cblas_sasum(a.size, a.buffer.baseAddress, 1) 102 | } 103 | 104 | public static func cumulativeSum(_ a: Vector) -> Vector { 105 | var s: Float = 1.0 // scalar weighting factor for A 106 | let n = vDSP_Length(a.size) // number of elements to process 107 | 108 | var result = Vector(like: a) 109 | vDSP_vrsum(a.buffer.baseAddress!, 1, &s, result.buffer.baseAddress!, 1, n) 110 | vDSP.add(a[0], result.buffer, result: &result.buffer) 111 | 112 | return result 113 | } 114 | } 115 | 116 | @_documentation(visibility: private) 117 | extension Double: Algebra { 118 | 119 | public static func swapValues(a: inout Vector, b: inout Vector) { 120 | precondition(a.size == b.size, "Vectors must be same size") 121 | cblas_dswap(a.size, a.buffer.baseAddress, 1, b.buffer.baseAddress, 1) 122 | } 123 | 124 | public static func dot(_ a: Vector, _ b: Vector) -> Double { 125 | cblas_ddot(a.size, a.buffer.baseAddress, 1, b.buffer.baseAddress, 1) 126 | } 127 | 128 | public static func norm(_ a: Vector) -> Double { 129 | cblas_dnrm2(a.size, a.buffer.baseAddress, 1) 130 | } 131 | 132 | public static func scale(_ a: inout Vector, by k: Double) { 133 | cblas_dscal(a.size, k, a.buffer.baseAddress, 1) 134 | } 135 | 136 | public static func sum(_ a: Vector) -> Double { 137 | vDSP.sum(a.buffer) 138 | } 139 | 140 | public static func absoluteSum(_ a: Vector) -> Double { 141 | cblas_dasum(a.size, a.buffer.baseAddress, 1) 142 | } 143 | 144 | public static func cumulativeSum(_ a: Vector) -> Vector { 145 | var s = 1.0 // scalar weighting factor for A 146 | let n = vDSP_Length(a.size) // number of elements to process 147 | 148 | var result = Vector(like: a) 149 | vDSP_vrsumD(a.buffer.baseAddress!, 1, &s, result.buffer.baseAddress!, 1, n) 150 | vDSP.add(a[0], result.buffer, result: &result.buffer) 151 | 152 | return result 153 | } 154 | } 155 | 156 | extension Vector where Scalar: Algebra { 157 | 158 | /// Calculate the dot product of two vectors. 159 | /// 160 | /// This calculates the dot product as `c = aᵀb` where `a` and `b` are vectors 161 | /// that must be the same length. 162 | /// ```swift 163 | /// let a: Vector = [1, 2, 3, 4, 5] 164 | /// let b: Vector = [9, 2, 3, 4, 5] 165 | /// let c = a.dot(b) 166 | /// // c is 63.0 167 | /// ``` 168 | /// 169 | /// - Parameter b: The second vector. 170 | /// - Returns: The dot product of two vectors 171 | public func dot(_ b: Vector) -> Scalar { 172 | precondition(self.size == b.size, "Vectors must be same size") 173 | return Scalar.dot(self, b) 174 | } 175 | 176 | /// The Euclidean norm of the vector. Also known as the L² norm, 2-norm, 177 | /// vector magnitude, or Euclidean length. 178 | /// - Returns: The vector norm. 179 | public func norm() -> Scalar { 180 | Scalar.norm(self) 181 | } 182 | 183 | /// Multiply each value in the vector by a constant. 184 | /// 185 | /// For integer vectors, this performs element-wise multiplication. For 186 | /// single and double precision vectors this uses BLAS routines `sscal` 187 | /// and `dscal` respectively. 188 | /// 189 | /// - Parameter k: The scaling factor. 190 | public mutating func scale(by k: Scalar) { 191 | Scalar.scale(&self, by: k) 192 | } 193 | 194 | /// Sum of the vector values. 195 | /// 196 | /// This example calculates the sum of the values in vector `a`. 197 | /// ```swift 198 | /// let a = Vector([1, 2, 3, 4, 5]) 199 | /// let c = a.sum() 200 | /// // c is 15 201 | /// ``` 202 | /// 203 | /// - Returns: Sum of the values. 204 | public func sum() -> Scalar { 205 | Scalar.sum(self) 206 | } 207 | 208 | /// Sum of the absolute values in the vector. 209 | /// 210 | /// This examples calculates the absolute sum of the values in vector `a`. 211 | /// ```swift 212 | /// let a = Vector([1, 2, -3, 4, -6.8]) 213 | /// let c = a.absoluteSum() 214 | /// // c is 16.8 215 | /// ``` 216 | /// 217 | /// - Returns: Sum of absolute values. 218 | public func absoluteSum() -> Scalar { 219 | Scalar.absoluteSum(self) 220 | } 221 | 222 | /// Calculate the cumulative sum of the vector. 223 | /// 224 | /// This returns a vector that is the cumulative sum of the scalar values. 225 | /// ```swift 226 | /// let a = Vector([1, 2, 3, 4, 5]) 227 | /// let c = a.cumulativeSum() 228 | /// // c is Vector with values [1, 3, 6, 10, 15] 229 | /// ``` 230 | /// 231 | /// - Returns: Cumulative sum vector. 232 | public func cumulativeSum() -> Vector { 233 | Scalar.cumulativeSum(self) 234 | } 235 | } 236 | 237 | /// Swap the values of two vectors. 238 | /// 239 | /// In this example the scalar values in `vec1` are exchanged for the values in `vec2` and vice versa. 240 | /// ```swift 241 | /// var vec1 = Vector([2, 3, 4, 5, 6]) 242 | /// var vec2 = Vector([9, 8, 7, 10, 12]) 243 | /// swapValues(&vec1, &vec2) 244 | /// ``` 245 | /// - Parameters: 246 | /// - a: The first vector. 247 | /// - b: The second vector. 248 | public func swapValues(_ a: inout Vector, _ b: inout Vector) { 249 | Scalar.swapValues(a: &a, b: &b) 250 | } 251 | -------------------------------------------------------------------------------- /Sources/VectorModule/ApproximatelyEqual.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Vector extension for approximate equality method. 3 | */ 4 | 5 | extension Vector { 6 | 7 | /// Compare two vectors of single precision for approximate equality. 8 | /// 9 | /// Returns true when `norm(x - y) ≤ max(atol, rtol × max(norm(x), norm(y)))` 10 | /// where `x` and `y` are vectors, `atol` is absolute tolerance, and `rtol` 11 | /// is relative tolerance. 12 | /// 13 | /// - Parameters: 14 | /// - other: The other vector used for comparison. 15 | /// - absoluteTolerance: The absolute tolerance, default is 0. 16 | /// - relativeTolerance: The relative tolerance, default is 1e-6. 17 | /// - Returns: True when two vectors are approximately equal. 18 | public func isApproximatelyEqual( 19 | to other: Vector, absoluteTolerance: Float = 0, relativeTolerance: Float = 1e-6 20 | ) -> Bool where Scalar == Float { 21 | let delta = (self - other).norm() 22 | let scale = max(self.norm(), other.norm()) 23 | return delta <= max(absoluteTolerance, relativeTolerance * scale) 24 | } 25 | 26 | /// Compare two vectors of double precision for approximate equality. 27 | /// 28 | /// Returns true when `norm(x - y) ≤ max(atol, rtol × max(norm(x), norm(y)))` 29 | /// where `x` and `y` are vectors, `atol` is absolute tolerance, and `rtol` 30 | /// is relative tolerance. 31 | /// 32 | /// - Parameters: 33 | /// - other: The other vector used for comparison. 34 | /// - absoluteTolerance: The absolute tolerance, default is 0. 35 | /// - relativeTolerance: The relative tolerance, default is 1e-8. 36 | /// - Returns: True when two vectors are approximately equal. 37 | public func isApproximatelyEqual( 38 | to other: Vector, absoluteTolerance: Double = 0, relativeTolerance: Double = 1e-8 39 | ) -> Bool where Scalar == Double { 40 | let delta = (self - other).norm() 41 | let scale = max(self.norm(), other.norm()) 42 | return delta <= max(absoluteTolerance, relativeTolerance * scale) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/VectorModule/Arithmetic.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Arithmetic protocol and extensions for the Vector struct. 3 | */ 4 | 5 | import Accelerate 6 | 7 | @_documentation(visibility: private) 8 | public protocol Arithmetic { 9 | static func add(_ a: Vector, _ k: Self) -> Vector 10 | static func add(_ a: Vector, _ b: Vector) -> Vector 11 | static func subtract(_ k: Self, _ a: Vector) -> Vector 12 | static func subtract(_ a: Vector, _ k: Self) -> Vector 13 | static func subtract(_ a: Vector, _ b: Vector) -> Vector 14 | static func multiply(_ a: Vector, _ k: Self) -> Vector 15 | static func multiply(_ a: Vector, _ b: Vector) -> Vector 16 | static func divide(_ k: Self, _ b: Vector) -> Vector 17 | static func divide(_ a: Vector, _ k: Self) -> Vector 18 | static func divide(_ a: Vector, _ b: Vector) -> Vector 19 | } 20 | 21 | @_documentation(visibility: private) 22 | extension Int: Arithmetic { 23 | 24 | public static func add(_ a: Vector, _ k: Int) -> Vector { 25 | var vec = Vector(like: a) 26 | for i in 0.., _ b: Vector) -> Vector { 33 | var vec = Vector(like: a) 34 | for i in 0..) -> Vector { 41 | var vec = Vector(like: a) 42 | for i in 0.., _ k: Int) -> Vector { 49 | var vec = Vector(like: a) 50 | for i in 0.., _ b: Vector) -> Vector { 57 | var vec = Vector(like: a) 58 | for i in 0.., _ k: Int) -> Vector { 65 | var vec = Vector(like: a) 66 | for i in 0.., _ b: Vector) -> Vector { 73 | var vec = Vector(like: a) 74 | for i in 0..) -> Vector { 81 | var vec = Vector(like: b) 82 | for i in 0.., _ k: Self) -> Vector { 89 | var vec = Vector(like: a) 90 | for i in 0.., _ b: Vector) -> Vector { 97 | var vec = Vector(like: a) 98 | for i in 0.., _ k: Float) -> Vector { 109 | var vec = Vector(like: a) 110 | vDSP.add(k, a.buffer, result: &vec.buffer) 111 | return vec 112 | } 113 | 114 | public static func add(_ a: Vector, _ b: Vector) -> Vector { 115 | var vec = Vector(like: a) 116 | vDSP.add(a.buffer, b.buffer, result: &vec.buffer) 117 | return vec 118 | } 119 | 120 | public static func subtract(_ k: Float, _ a: Vector) -> Vector { 121 | let arr = Array(repeating: k, count: a.size) 122 | var res = Vector(like: a) 123 | vDSP.subtract(arr, a.buffer, result: &res.buffer) 124 | return res 125 | } 126 | 127 | public static func subtract(_ a: Vector, _ k: Float) -> Vector { 128 | let arr = Array(repeating: k, count: a.size) 129 | var res = Vector(like: a) 130 | vDSP.subtract(a.buffer, arr, result: &res.buffer) 131 | return res 132 | } 133 | 134 | public static func subtract(_ a: Vector, _ b: Vector) -> Vector { 135 | var res = Vector(like: a) 136 | vDSP.subtract(a.buffer, b.buffer, result: &res.buffer) 137 | return res 138 | } 139 | 140 | public static func multiply(_ a: Vector, _ k: Float) -> Vector { 141 | var vec = Vector(like: a) 142 | vDSP.multiply(k, a.buffer, result: &vec.buffer) 143 | return vec 144 | } 145 | 146 | public static func multiply(_ a: Vector, _ b: Vector) -> Vector { 147 | var vec = Vector(like: a) 148 | vDSP.multiply(a.buffer, b.buffer, result: &vec.buffer) 149 | return vec 150 | } 151 | 152 | public static func divide(_ k: Self, _ b: Vector) -> Vector { 153 | var vec = Vector(like: b) 154 | for i in 0.., _ k: Self) -> Vector { 161 | var vec = Vector(like: a) 162 | for i in 0.., _ b: Vector) -> Vector { 169 | var vec = Vector(like: a) 170 | for i in 0.., _ k: Double) -> Vector { 181 | var vec = Vector(like: a) 182 | vDSP.add(k, a.buffer, result: &vec.buffer) 183 | return vec 184 | } 185 | 186 | public static func add(_ a: Vector, _ b: Vector) -> Vector { 187 | var vec = Vector(like: a) 188 | vDSP.add(a.buffer, b.buffer, result: &vec.buffer) 189 | return vec 190 | } 191 | 192 | public static func subtract(_ k: Double, _ a: Vector) -> Vector { 193 | let arr = Array(repeating: k, count: a.size) 194 | var res = Vector(like: a) 195 | vDSP.subtract(arr, a.buffer, result: &res.buffer) 196 | return res 197 | } 198 | 199 | public static func subtract(_ a: Vector, _ k: Double) -> Vector { 200 | let arr = Array(repeating: k, count: a.size) 201 | var res = Vector(like: a) 202 | vDSP.subtract(a.buffer, arr, result: &res.buffer) 203 | return res 204 | } 205 | 206 | public static func subtract(_ a: Vector, _ b: Vector) -> Vector { 207 | var res = Vector(like: a) 208 | vDSP.subtract(a.buffer, b.buffer, result: &res.buffer) 209 | return res 210 | } 211 | 212 | public static func multiply(_ a: Vector, _ k: Double) -> Vector { 213 | var vec = Vector(like: a) 214 | vDSP.multiply(k, a.buffer, result: &vec.buffer) 215 | return vec 216 | } 217 | 218 | public static func multiply(_ a: Vector, _ b: Vector) -> Vector { 219 | var vec = Vector(like: a) 220 | vDSP.multiply(a.buffer, b.buffer, result: &vec.buffer) 221 | return vec 222 | } 223 | 224 | public static func divide(_ k: Self, _ b: Vector) -> Vector { 225 | var vec = Vector(like: b) 226 | for i in 0.., _ k: Self) -> Vector { 233 | var vec = Vector(like: a) 234 | for i in 0.., _ b: Vector) -> Vector { 241 | var vec = Vector(like: a) 242 | for i in 0.. Vector { 257 | Scalar.add(rhs, lhs) 258 | } 259 | 260 | public static func + (lhs: Vector, rhs: Scalar) -> Vector { 261 | Scalar.add(lhs, rhs) 262 | } 263 | 264 | public static func + (lhs: Vector, rhs: Vector) -> Vector { 265 | Scalar.add(lhs, rhs) 266 | } 267 | 268 | public static func += (lhs: inout Vector, rhs: Vector) { 269 | lhs = lhs + rhs 270 | } 271 | 272 | public static func - (lhs: Scalar, rhs: Vector) -> Vector { 273 | Scalar.subtract(lhs, rhs) 274 | } 275 | 276 | public static func - (lhs: Vector, rhs: Scalar) -> Vector { 277 | Scalar.subtract(lhs, rhs) 278 | } 279 | 280 | public static func - (lhs: Vector, rhs: Vector) -> Vector { 281 | Scalar.subtract(lhs, rhs) 282 | } 283 | 284 | public static func * (lhs: Scalar, rhs: Vector) -> Vector { 285 | Scalar.multiply(rhs, lhs) 286 | } 287 | 288 | public static func * (lhs: Vector, rhs: Scalar) -> Vector { 289 | Scalar.multiply(lhs, rhs) 290 | } 291 | 292 | public static func * (lhs: Vector, rhs: Vector) -> Vector { 293 | Scalar.multiply(lhs, rhs) 294 | } 295 | 296 | public static func *= (lhs: inout Vector, rhs: Scalar) { 297 | lhs = lhs * rhs 298 | } 299 | 300 | public static func / (lhs: Scalar, rhs: Vector) -> Vector { 301 | Scalar.divide(lhs, rhs) 302 | } 303 | 304 | public static func / (lhs: Vector, rhs: Scalar) -> Vector { 305 | Scalar.divide(lhs, rhs) 306 | } 307 | 308 | public static func / (lhs: Vector, rhs: Vector) -> Vector { 309 | Scalar.divide(lhs, rhs) 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /Sources/VectorModule/DataBuffer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Data buffer class. 3 | */ 4 | 5 | class DataBuffer { 6 | var buffer: UnsafeMutableBufferPointer 7 | 8 | init(count: Int, fill: T) { 9 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: count) 10 | self.buffer.initialize(repeating: fill) 11 | } 12 | 13 | init(count: Int) { 14 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: count) 15 | } 16 | 17 | init(array: [T]) { 18 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: array.count) 19 | _ = self.buffer.initialize(fromContentsOf: array) 20 | } 21 | 22 | init(buffer: UnsafeMutableBufferPointer) { 23 | self.buffer = UnsafeMutableBufferPointer.allocate(capacity: buffer.count) 24 | _ = self.buffer.initialize(from: buffer) 25 | } 26 | 27 | deinit { 28 | self.buffer.deinitialize() 29 | self.buffer.deallocate() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/VectorModule/Documentation.docc/LinearAlgebra.md: -------------------------------------------------------------------------------- 1 | # Linear Algebra 2 | 3 | Linear algebra vector operations. 4 | 5 | ## Overview 6 | 7 | Numerix implements linear algebra operations using the Accelerate framework and BLAS routines. See the home page of the documentation for more information about the supported BLAS routines. 8 | 9 | ## Topics 10 | 11 | - ``Vector/dot(_:)`` 12 | - ``Vector/norm()`` 13 | - ``Vector/scale(by:)`` 14 | - ``Vector/sum()`` 15 | - ``Vector/absoluteSum()`` 16 | - ``Vector/cumulativeSum()`` 17 | -------------------------------------------------------------------------------- /Sources/VectorModule/Documentation.docc/Trigonometry.md: -------------------------------------------------------------------------------- 1 | # Trigonometry 2 | 3 | Trigonometic vector operations. 4 | 5 | ## Overview 6 | 7 | Numerix provides several element-wise trigonometic functions for vectors. The Accelerate framework is utilized for efficient element-by-element vector operations. 8 | 9 | ## Topics 10 | 11 | - ``sin(_:)`` 12 | - ``cos(_:)`` 13 | - ``tan(_:)`` 14 | - ``asin(_:)`` 15 | - ``acos(_:)`` 16 | - ``atan(_:)`` 17 | - ``csc(_:)`` 18 | - ``sec(_:)`` 19 | - ``cot(_:)`` 20 | -------------------------------------------------------------------------------- /Sources/VectorModule/Documentation.docc/VectorModule.md: -------------------------------------------------------------------------------- 1 | # ``VectorModule`` 2 | 3 | One-dimensional vector operations. 4 | 5 | ## Overview 6 | 7 | This module provides the ``Vector`` structure for working with one-dimensional numerical data. Arithmetic and several linear algebra operations are supported. See the topics below for more details. Scalar values can be Float or Double and limited support is provided for Int values. 8 | 9 | ## Topics 10 | 11 | - 12 | - 13 | -------------------------------------------------------------------------------- /Sources/VectorModule/Exponential.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Vector extension for exponential functions. 3 | */ 4 | 5 | import Accelerate 6 | 7 | @_documentation(visibility: private) 8 | public protocol Exponential { 9 | static func exp(a: Vector) -> Vector 10 | static func exp2(a: Vector) -> Vector 11 | static func expm1(a: Vector) -> Vector 12 | } 13 | 14 | @_documentation(visibility: private) 15 | extension Float: Exponential { 16 | public static func exp(a: Vector) -> Vector { 17 | var mat = Vector(like: a) 18 | vForce.exp(a.buffer, result: &mat.buffer) 19 | return mat 20 | } 21 | 22 | public static func exp2(a: Vector) -> Vector { 23 | var mat = Vector(like: a) 24 | vForce.exp2(a.buffer, result: &mat.buffer) 25 | return mat 26 | } 27 | 28 | public static func expm1(a: Vector) -> Vector { 29 | var mat = Vector(like: a) 30 | vForce.expm1(a.buffer, result: &mat.buffer) 31 | return mat 32 | } 33 | } 34 | 35 | @_documentation(visibility: private) 36 | extension Double: Exponential { 37 | public static func exp(a: Vector) -> Vector { 38 | var mat = Vector(like: a) 39 | vForce.exp(a.buffer, result: &mat.buffer) 40 | return mat 41 | } 42 | 43 | public static func exp2(a: Vector) -> Vector { 44 | var mat = Vector(like: a) 45 | vForce.exp2(a.buffer, result: &mat.buffer) 46 | return mat 47 | } 48 | 49 | public static func expm1(a: Vector) -> Vector { 50 | var mat = Vector(like: a) 51 | vForce.expm1(a.buffer, result: &mat.buffer) 52 | return mat 53 | } 54 | } 55 | 56 | extension Vector where Scalar: Exponential { 57 | 58 | /// Calculate the exponential for each vector element. 59 | /// - Returns: Element-wise exponential vector. 60 | public func exp() -> Vector { 61 | Scalar.exp(a: self) 62 | } 63 | 64 | /// Calculate 2 raised to the power of each vector element. 65 | /// - Returns: Element-wise 2 raised to the power vector. 66 | public func exp2() -> Vector { 67 | Scalar.exp2(a: self) 68 | } 69 | 70 | /// Calculate the exponential minus one of each vector element. 71 | /// - Returns: Element-wise exponential minus one vector. 72 | public func expm1() -> Vector { 73 | Scalar.expm1(a: self) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/VectorModule/Formatter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Functions that format the vector values for printing. 3 | Used by Vector `description` in `CustomDebugStringConvertible`. 4 | */ 5 | 6 | extension Vector { 7 | 8 | private func formatValue(_ value: Scalar, specifier: String?) -> String { 9 | guard let spec = specifier else { return "\(value)" } 10 | return String(format: spec, value as! CVarArg) 11 | } 12 | 13 | /// Format the vector as a string representation. 14 | /// 15 | /// Use the optional format specifier to define the numeric format used for 16 | /// the underlying vector values. See the [IEEE printf specification][1] for 17 | /// more information about the format specifiers. 18 | /// ```swift 19 | /// let vec = Vector([0.90129, 5, 8.02, 9, 10]) 20 | /// let formatVec = vec.formatted(specifier: "%.4f") 21 | /// // ( 0.9013 5.0000 8.0200 9.0000 10.0000 ) 22 | /// ``` 23 | /// [1]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/fprintf.html 24 | /// - Parameter specifier: Format specifier. 25 | /// - Returns: String of vector values. 26 | public func formatted(specifier: String? = nil) -> String { 27 | var desc = "( " 28 | desc += self.buffer.map { formatValue($0, specifier: specifier) }.joined(separator: " ") 29 | desc += " )" 30 | return desc 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/VectorModule/Logarithm.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Vector extension for logarithm functions. 3 | */ 4 | 5 | import Accelerate 6 | 7 | @_documentation(visibility: private) 8 | public protocol Logarithm { 9 | static func log(a: Vector) -> Vector 10 | static func log1p(a: Vector) -> Vector 11 | static func log10(a: Vector) -> Vector 12 | static func log2(a: Vector) -> Vector 13 | static func logb(a: Vector) -> Vector 14 | } 15 | 16 | @_documentation(visibility: private) 17 | extension Float: Logarithm { 18 | public static func log(a: Vector) -> Vector { 19 | var result = Vector(like: a) 20 | vForce.log(a.buffer, result: &result.buffer) 21 | return result 22 | } 23 | 24 | public static func log1p(a: Vector) -> Vector { 25 | var result = Vector(like: a) 26 | vForce.log1p(a.buffer, result: &result.buffer) 27 | return result 28 | } 29 | 30 | public static func log10(a: Vector) -> Vector { 31 | var result = Vector(like: a) 32 | vForce.log10(a.buffer, result: &result.buffer) 33 | return result 34 | } 35 | 36 | public static func log2(a: Vector) -> Vector { 37 | var result = Vector(like: a) 38 | vForce.log2(a.buffer, result: &result.buffer) 39 | return result 40 | } 41 | 42 | public static func logb(a: Vector) -> Vector { 43 | var result = Vector(like: a) 44 | vForce.logb(a.buffer, result: &result.buffer) 45 | return result 46 | } 47 | } 48 | 49 | @_documentation(visibility: private) 50 | extension Double: Logarithm { 51 | public static func log(a: Vector) -> Vector { 52 | var result = Vector(like: a) 53 | vForce.log(a.buffer, result: &result.buffer) 54 | return result 55 | } 56 | 57 | public static func log1p(a: Vector) -> Vector { 58 | var result = Vector(like: a) 59 | vForce.log1p(a.buffer, result: &result.buffer) 60 | return result 61 | } 62 | 63 | public static func log10(a: Vector) -> Vector { 64 | var result = Vector(like: a) 65 | vForce.log10(a.buffer, result: &result.buffer) 66 | return result 67 | } 68 | 69 | public static func log2(a: Vector) -> Vector { 70 | var result = Vector(like: a) 71 | vForce.log2(a.buffer, result: &result.buffer) 72 | return result 73 | } 74 | 75 | public static func logb(a: Vector) -> Vector { 76 | var result = Vector(like: a) 77 | vForce.logb(a.buffer, result: &result.buffer) 78 | return result 79 | } 80 | } 81 | 82 | extension Vector where Scalar: Logarithm { 83 | 84 | /// Calculate the natural logarithm for each vector element. 85 | /// - Returns: Element-wise log vector. 86 | public func log() -> Vector { 87 | Scalar.log(a: self) 88 | } 89 | 90 | /// Calculate the log(1+x) for each vector element. 91 | /// - Returns: Element-wise log(1+x) vector. 92 | public func log1p() -> Vector { 93 | Scalar.log1p(a: self) 94 | } 95 | 96 | /// Calculate the base 10 logarithm for each vector element. 97 | /// - Returns: Element-wise base 10 logarithm vector. 98 | public func log10() -> Vector { 99 | Scalar.log10(a: self) 100 | } 101 | 102 | /// Calculate the base 2 logarithm for each vector element. 103 | /// - Returns: Element-wise base 2 logarithm vector. 104 | public func log2() -> Vector { 105 | Scalar.log2(a: self) 106 | } 107 | 108 | /// Calculate the unbiased exponent for each vector element. 109 | /// - Returns: Element-wise unbiased exponent vector. 110 | public func logb() -> Vector { 111 | Scalar.logb(a: self) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Sources/VectorModule/Power.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Vector extension for power functions. 3 | */ 4 | 5 | import Accelerate 6 | 7 | @_documentation(visibility: private) 8 | public protocol Power { 9 | static func power(a: Vector, exp: Self) -> Vector 10 | static func power(a: Vector, exp: [Self]) -> Vector 11 | } 12 | 13 | @_documentation(visibility: private) 14 | extension Float: Power { 15 | public static func power(a: Vector, exp: Float) -> Vector { 16 | let output = Vector(like: a) 17 | withUnsafePointer(to: exp) { expPtr in 18 | withUnsafePointer(to: Int32(a.size)) { lengthPtr in 19 | vvpowsf(output.buffer.baseAddress!, expPtr, a.buffer.baseAddress!, lengthPtr) 20 | } 21 | } 22 | return output 23 | } 24 | 25 | public static func power(a: Vector, exp: [Float]) -> Vector { 26 | precondition(a.size == exp.count, "Vector size must correspond to number of exponents") 27 | var result = Vector(like: a) 28 | vForce.pow(bases: a.buffer, exponents: exp, result: &result.buffer) 29 | return result 30 | } 31 | } 32 | 33 | @_documentation(visibility: private) 34 | extension Double: Power { 35 | public static func power(a: Vector, exp: Double) -> Vector { 36 | let output = Vector(like: a) 37 | withUnsafePointer(to: exp) { expPtr in 38 | withUnsafePointer(to: Int32(a.size)) { lengthPtr in 39 | vvpows(output.buffer.baseAddress!, expPtr, a.buffer.baseAddress!, lengthPtr) 40 | } 41 | } 42 | return output 43 | } 44 | 45 | public static func power(a: Vector, exp: [Double]) -> Vector { 46 | precondition(a.size == exp.count, "Vector size must correspond to number of exponents") 47 | var result = Vector(like: a) 48 | vForce.pow(bases: a.buffer, exponents: exp, result: &result.buffer) 49 | return result 50 | } 51 | } 52 | 53 | extension Vector where Scalar: Power { 54 | 55 | /// Raise each vector element to the power of a constant exponent. 56 | /// - Parameter k: A constant exponent value. 57 | /// - Returns: Vector from the bases raised to a constant power. 58 | public func power(_ k: Scalar) -> Vector { 59 | Scalar.power(a: self, exp: k) 60 | } 61 | 62 | /// Raise each vector element to the power of several exponents. 63 | /// - Parameter k: Array of exponent values. 64 | /// - Returns: Vector from the bases raised to several powers. 65 | public func power(_ k: [Scalar]) -> Vector { 66 | Scalar.power(a: self, exp: k) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/VectorModule/RandomDistribution.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Function to create a random distribution vector using LAPACK. 3 | */ 4 | 5 | import Accelerate 6 | 7 | @_documentation(visibility: private) 8 | public protocol RandomDistribution { 9 | static func randomDist(size: Int, dist: Int) -> Vector 10 | } 11 | 12 | @_documentation(visibility: private) 13 | extension Float: RandomDistribution { 14 | 15 | public static func randomDist(size: Int, dist: Int) -> Vector { 16 | var idist = dist 17 | var num = size 18 | 19 | // Must be between 0 and 4095, and iseed[3] must be odd 20 | // See https://netlib.org/lapack/explore-html/d5/dd2/group__larnv.html 21 | var iseed: [Int] = (0..<3).map { _ in Int.random(in: 1..<4095) } 22 | iseed += [2 * (Int.random(in: 1..<4095) / 2) + 1 ] 23 | 24 | let vec = Vector(size: size) 25 | slarnv_(&idist, &iseed, &num, vec.buffer.baseAddress) 26 | return vec 27 | } 28 | } 29 | 30 | @_documentation(visibility: private) 31 | extension Double: RandomDistribution { 32 | 33 | public static func randomDist(size: Int, dist: Int) -> Vector { 34 | var idist = dist 35 | var num = size 36 | 37 | // Must be between 0 and 4095, and iseed[3] must be odd 38 | // See https://netlib.org/lapack/explore-html/d5/dd2/group__larnv.html 39 | var iseed: [Int] = (0..<3).map { _ in Int.random(in: 1..<4095) } 40 | iseed += [2 * (Int.random(in: 1..<4095) / 2) + 1 ] 41 | 42 | let vec = Vector(size: size) 43 | dlarnv_(&idist, &iseed, &num, vec.buffer.baseAddress) 44 | return vec 45 | } 46 | } 47 | 48 | extension Vector { 49 | 50 | /// Create a vector containing a random distribution of values. 51 | /// - Parameters: 52 | /// - size: Size of the vector. 53 | /// - dist: Distribution of random numbers where a value of `1` is for a 54 | /// uniform distribution `(0, 1)`, a value of `2` is for a uniform distribution 55 | /// `(-1, 1)`, and `3` is for a normal distribution `(0, 1)`. Default is `1`. 56 | /// - Returns: Vector of randomly distributed values. 57 | public static func randomDistribution(size: Int, dist: Int = 1) -> Vector where Scalar: RandomDistribution { 58 | Scalar.randomDist(size: size, dist: dist) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/VectorModule/Trigonometry.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Vector trigonometry protocol and functions. 3 | */ 4 | 5 | import Accelerate 6 | 7 | @_documentation(visibility: private) 8 | public protocol Trigonometry { 9 | static func sin(_ a: Vector) -> Vector 10 | static func cos(_ a: Vector) -> Vector 11 | static func tan(_ a: Vector) -> Vector 12 | static func asin(_ a: Vector) -> Vector 13 | static func acos(_ a: Vector) -> Vector 14 | static func atan(_ a: Vector) -> Vector 15 | static func csc(_ a: Vector) -> Vector 16 | static func sec(_ a: Vector) -> Vector 17 | static func cot(_ a: Vector) -> Vector 18 | } 19 | 20 | @_documentation(visibility: private) 21 | extension Float: Trigonometry { 22 | 23 | public static func sin(_ a: Vector) -> Vector { 24 | var result = Vector(like: a) 25 | vForce.sin(a.buffer, result: &result.buffer) 26 | return result 27 | } 28 | 29 | public static func cos(_ a: Vector) -> Vector { 30 | var result = Vector(like: a) 31 | vForce.cos(a.buffer, result: &result.buffer) 32 | return result 33 | } 34 | 35 | public static func tan(_ a: Vector) -> Vector { 36 | var result = Vector(like: a) 37 | vForce.tan(a.buffer, result: &result.buffer) 38 | return result 39 | } 40 | 41 | public static func asin(_ a: Vector) -> Vector { 42 | var result = Vector(like: a) 43 | vForce.asin(a.buffer, result: &result.buffer) 44 | return result 45 | } 46 | 47 | public static func acos(_ a: Vector) -> Vector { 48 | var result = Vector(like: a) 49 | vForce.acos(a.buffer, result: &result.buffer) 50 | return result 51 | } 52 | 53 | public static func atan(_ a: Vector) -> Vector { 54 | var result = Vector(like: a) 55 | vForce.atan(a.buffer, result: &result.buffer) 56 | return result 57 | } 58 | 59 | public static func csc(_ a: Vector) -> Vector { 60 | var result = Vector(like: a) 61 | vForce.sin(a.buffer, result: &result.buffer) 62 | vForce.reciprocal(result.buffer, result: &result.buffer) 63 | return result 64 | } 65 | 66 | public static func sec(_ a: Vector) -> Vector { 67 | var result = Vector(like: a) 68 | vForce.cos(a.buffer, result: &result.buffer) 69 | vForce.reciprocal(result.buffer, result: &result.buffer) 70 | return result 71 | } 72 | 73 | public static func cot(_ a: Vector) -> Vector { 74 | var result = Vector(like: a) 75 | vForce.tan(a.buffer, result: &result.buffer) 76 | vForce.reciprocal(result.buffer, result: &result.buffer) 77 | return result 78 | } 79 | } 80 | 81 | @_documentation(visibility: private) 82 | extension Double: Trigonometry { 83 | 84 | public static func sin(_ a: Vector) -> Vector { 85 | var result = Vector(like: a) 86 | vForce.sin(a.buffer, result: &result.buffer) 87 | return result 88 | } 89 | 90 | public static func cos(_ a: Vector) -> Vector { 91 | var result = Vector(like: a) 92 | vForce.cos(a.buffer, result: &result.buffer) 93 | return result 94 | } 95 | 96 | public static func tan(_ a: Vector) -> Vector { 97 | var result = Vector(like: a) 98 | vForce.tan(a.buffer, result: &result.buffer) 99 | return result 100 | } 101 | 102 | public static func asin(_ a: Vector) -> Vector { 103 | var result = Vector(like: a) 104 | vForce.asin(a.buffer, result: &result.buffer) 105 | return result 106 | } 107 | 108 | public static func acos(_ a: Vector) -> Vector { 109 | var result = Vector(like: a) 110 | vForce.acos(a.buffer, result: &result.buffer) 111 | return result 112 | } 113 | 114 | public static func atan(_ a: Vector) -> Vector { 115 | var result = Vector(like: a) 116 | vForce.atan(a.buffer, result: &result.buffer) 117 | return result 118 | } 119 | 120 | public static func csc(_ a: Vector) -> Vector { 121 | var result = Vector(like: a) 122 | vForce.sin(a.buffer, result: &result.buffer) 123 | vForce.reciprocal(result.buffer, result: &result.buffer) 124 | return result 125 | } 126 | 127 | public static func sec(_ a: Vector) -> Vector { 128 | var result = Vector(like: a) 129 | vForce.cos(a.buffer, result: &result.buffer) 130 | vForce.reciprocal(result.buffer, result: &result.buffer) 131 | return result 132 | } 133 | 134 | public static func cot(_ a: Vector) -> Vector { 135 | var result = Vector(like: a) 136 | vForce.tan(a.buffer, result: &result.buffer) 137 | vForce.reciprocal(result.buffer, result: &result.buffer) 138 | return result 139 | } 140 | } 141 | 142 | /// Calculate the sine of each element in a vector. 143 | /// ```swift 144 | /// let vec: Vector = [1, 2, 3, 4, 5] 145 | /// let result = sin(vec) 146 | /// ``` 147 | /// - Parameter vec: The input vector. 148 | /// - Returns: A vector representing the sine of the input vector. 149 | public func sin(_ vec: Vector) -> Vector where Scalar: Trigonometry { 150 | Scalar.sin(vec) 151 | } 152 | 153 | /// Calculate the cosine of each element in a vector. 154 | /// ```swift 155 | /// let vec: Vector = [1, 2, 3, 4, 5] 156 | /// let result = cos(vec) 157 | /// ``` 158 | /// - Parameter vec: The input vector. 159 | /// - Returns: A vector representing the cosine of the input vector. 160 | public func cos(_ vec: Vector) -> Vector where Scalar: Trigonometry { 161 | Scalar.cos(vec) 162 | } 163 | 164 | /// Calculate the tangent of each element in a vector. 165 | /// ```swift 166 | /// let vec: Vector = [1, 2, 3, 4, 5] 167 | /// let result = tan(vec) 168 | /// ``` 169 | /// - Parameter vec: The input vector. 170 | /// - Returns: A vector representing the tangent of the input vector. 171 | public func tan(_ vec: Vector) -> Vector where Scalar: Trigonometry { 172 | Scalar.tan(vec) 173 | } 174 | 175 | /// Calculate the arcsine of each element in a vector. 176 | /// 177 | /// Elements in the vector should be within the domain -1 ≤ x ≤ 1 otherwise results may be nan. Results are returned on the closed interval -π / 2 ≤ y ≤ π / 2. 178 | /// ```swift 179 | /// let vec = Vector([-1, -0.5, 0, 0.5, 1]) 180 | /// let result = asin(vec) 181 | /// ``` 182 | /// - Parameter vec: The input vector with values from -1 to 1. 183 | /// - Returns: A vector representing the arcsine of the input vector. 184 | public func asin(_ vec: Vector) -> Vector where Scalar: Trigonometry { 185 | Scalar.asin(vec) 186 | } 187 | 188 | /// Calculate the arccosine of each element in a vector. 189 | /// - Parameter vec: The input vector. 190 | /// - Returns: A vector representing the arccosine of the input vector. 191 | public func acos(_ vec: Vector) -> Vector where Scalar: Trigonometry { 192 | Scalar.acos(vec) 193 | } 194 | 195 | /// Calculate the arctangent of each element in a vector. 196 | /// - Parameter vec: The input vector. 197 | /// - Returns: A vector representing the arctangent of the input vector. 198 | public func atan(_ vec: Vector) -> Vector where Scalar: Trigonometry { 199 | Scalar.atan(vec) 200 | } 201 | 202 | /// Calculate the cosecant as 1 / sinθ for each element in a vector. 203 | /// - Parameter vec: The input vector. 204 | /// - Returns: A vector representing the cosecant of the input vector. 205 | public func csc(_ vec: Vector) -> Vector where Scalar: Trigonometry { 206 | Scalar.csc(vec) 207 | } 208 | 209 | /// Calculate the secant as 1 / cosθ for each element in a vector. 210 | /// - Parameter vec: The input vector. 211 | /// - Returns: A vector representing the secant of the input vector. 212 | public func sec(_ vec: Vector) -> Vector where Scalar: Trigonometry { 213 | Scalar.sec(vec) 214 | } 215 | 216 | /// Calculate the cotangent as 1 / tanθ for each element in a vector. 217 | /// - Parameter vec: The input vector. 218 | /// - Returns: A vector representing the cotangent of the input vector. 219 | public func cot(_ vec: Vector) -> Vector where Scalar: Trigonometry { 220 | Scalar.cot(vec) 221 | } 222 | -------------------------------------------------------------------------------- /Sources/VectorModule/Vector.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Vector structure. 3 | */ 4 | 5 | import Accelerate 6 | 7 | /// A one-dimensional structure for numerical data. 8 | /// 9 | /// Create a vector with double-precision values: 10 | /// ```swift 11 | /// let vec = Vector([1, 2, 3, 4, 5]) 12 | /// ``` 13 | /// 14 | /// Array literal syntax is also supported: 15 | /// ```swift 16 | /// let vec: Vector = [2, 3, 4, 5, 6, 7] 17 | /// ``` 18 | public struct Vector { 19 | 20 | // Class reference to mutable buffer for underlying data storage 21 | private let data: DataBuffer 22 | 23 | /// Mutable buffer for underlying data storge and access. 24 | var buffer: UnsafeMutableBufferPointer { 25 | get { self.data.buffer } 26 | set { self.data.buffer = newValue } 27 | } 28 | 29 | /// Number of elements in the vector. 30 | public var size: Int { 31 | self.buffer.count 32 | } 33 | 34 | /// Create a vector from an array of values. 35 | /// - Parameter values: Scalar values of the vector. 36 | public init(_ values: [Scalar]) { 37 | self.data = DataBuffer(array: values) 38 | } 39 | 40 | /// Create an empty vector that is the same size as another vector. 41 | /// - Parameter vector: Another vector. 42 | public init(like vector: Vector) { 43 | self.data = DataBuffer(count: vector.size) 44 | } 45 | 46 | /// Create an empty vector of a certain size. 47 | /// - Parameter size: Number of elements in the vector. 48 | public init(size: Int) { 49 | self.data = DataBuffer(count: size) 50 | } 51 | 52 | /// Create a vector of a certain size filled with the given value. 53 | /// - Parameters: 54 | /// - size: Numbers of elements in the vector. 55 | /// - fill: Scalar value to fill the vector. 56 | public init(size: Int, fill: Scalar) { 57 | self.data = DataBuffer(count: size, fill: fill) 58 | } 59 | 60 | public subscript(item: Int) -> Scalar { 61 | get { return self.buffer[item] } 62 | set { self.buffer[item] = newValue } 63 | } 64 | } 65 | 66 | extension Vector: ExpressibleByArrayLiteral { 67 | 68 | /// Create a vector using an array literal. 69 | /// 70 | /// Examples of using an array literal to create a vector. 71 | /// 72 | /// ```swift 73 | /// // Create a vector of integers 74 | /// let vec: Vector = [1, 2, 3, 4] 75 | /// 76 | /// // Create a vector of doubles 77 | /// let vec: Vector = [4.2, 5, 6, 7.31, 8] 78 | /// 79 | /// // Create vector of doubles where values given as integers 80 | /// let vec: Vector = [4, 5, 6, 7, 8] 81 | /// ``` 82 | /// 83 | /// - Parameter elements: Scalar values in the vector. 84 | public init(arrayLiteral elements: Scalar...) { 85 | self.init(elements) 86 | } 87 | } 88 | 89 | extension Vector: CustomStringConvertible { 90 | 91 | public var description: String { 92 | self.formatted() 93 | } 94 | } 95 | 96 | extension Vector: CustomDebugStringConvertible { 97 | 98 | public var debugDescription: String { 99 | var descr = "\(self.size)-element \(type(of: self))\n" 100 | descr += self.formatted() 101 | return descr 102 | } 103 | } 104 | 105 | extension Vector: Equatable { 106 | 107 | /// Compare two vectors for equality. 108 | /// - Parameters: 109 | /// - lhs: Left-hand side vector. 110 | /// - rhs: Right-hand side vector. 111 | /// - Returns: True if both vectors are same size and contain the same values. 112 | public static func == (lhs: Vector, rhs: Vector) -> Bool { 113 | let cmp = memcmp(lhs.buffer.baseAddress, rhs.buffer.baseAddress, MemoryLayout.size * lhs.size) 114 | let buffersEqual = cmp == 0 ? true : false 115 | return lhs.size == rhs.size && buffersEqual 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Tests/MatrixTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Tests for the Matrix structure. 3 | */ 4 | 5 | import Testing 6 | @testable import Numerix 7 | 8 | struct MatrixTests { 9 | 10 | @Test func initialize() { 11 | let a = Matrix(rows: 2, columns: 3, values: [3, 4, 8, 10, 11, 18.2]) 12 | let b = Matrix(rows: 2, columns: 3, fill: 0.0) 13 | let c = Matrix([[1, 2, 3], [4, 5, 6]]) 14 | 15 | #expect(a[0, 0] == 3) 16 | #expect(b[0, 0] == 0) 17 | #expect(c[0, 0] == 1) 18 | } 19 | 20 | @Test func subscripts() { 21 | // row subscript 22 | var a: Matrix = [[1, 2, 3], 23 | [4, 5, 6], 24 | [7, 8, 9]] 25 | 26 | a[row: 2] = [[9, 9, 7]] 27 | 28 | #expect(a[row: 1] == [[4, 5, 6]]) 29 | #expect(a == [[1, 2, 3], [4, 5, 6], [9, 9, 7]]) 30 | 31 | // column subscript 32 | var b: Matrix = [[1, 2, 3], 33 | [4, 5, 6], 34 | [7, 8, 9]] 35 | 36 | #expect(b[column: 1] == [2, 5, 8]) 37 | 38 | b[column: 2] = [9, 9, 7] 39 | #expect(b == [[1, 2, 9], [4, 5, 9], [7, 8, 7]]) 40 | } 41 | 42 | @Test func arrayLiteral() { 43 | let a: Matrix = [[1, 2, 3, 4], 44 | [5, 6, 7, 8]] 45 | #expect(a == [[1, 2, 3, 4], [5, 6, 7, 8]]) 46 | 47 | let b: Matrix = [[1, 2, 3, 4.7], 48 | [5, 16.1, 7, 8], 49 | [10, 11, 12, 13]] 50 | #expect(b == [[1, 2, 3, 4.7], [5, 16.1, 7, 8], [10, 11, 12, 13]]) 51 | } 52 | 53 | @Test func printing() { 54 | let a = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12.5]]) 55 | var outputA = "" 56 | print(a, terminator: "", to: &outputA) 57 | #expect(outputA == """ 58 | ⎛ 1.0 2.0 3.0 4.0 ⎞ 59 | ⎜ 5.0 6.0 7.0 8.0 ⎟ 60 | ⎝ 9.0 10.0 11.0 12.5 ⎠ 61 | """) 62 | 63 | let b = Matrix([[2.5, 1, 8.235], [0.45, 23.5, 3]]) 64 | var outputB = "" 65 | print(b, terminator: "", to: &outputB) 66 | #expect(outputB == """ 67 | ⎛ 2.5 1.0 8.235 ⎞ 68 | ⎝ 0.45 23.5 3.0 ⎠ 69 | """) 70 | } 71 | 72 | @Test func debugPrinting() { 73 | let a = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12.5]]) 74 | var outputA = "" 75 | debugPrint(a, terminator: "", to: &outputA) 76 | #expect(outputA == """ 77 | 3x4 Matrix 78 | ⎛ 1.0 2.0 3.0 4.0 ⎞ 79 | ⎜ 5.0 6.0 7.0 8.0 ⎟ 80 | ⎝ 9.0 10.0 11.0 12.5 ⎠ 81 | """) 82 | 83 | let b = Matrix([[2.5, 1, 8.235], [0.45, 23.5, 3]]) 84 | var outputB = "" 85 | debugPrint(b, terminator: "", to: &outputB) 86 | #expect(outputB == """ 87 | 2x3 Matrix 88 | ⎛ 2.5 1.0 8.235 ⎞ 89 | ⎝ 0.45 23.5 3.0 ⎠ 90 | """) 91 | } 92 | 93 | @Test func formatting() { 94 | let matA = Matrix([[2, 1, 892], [4, 5, 3]]) 95 | let formatA = matA.formatted() 96 | #expect(formatA == """ 97 | ⎛ 2 1 892 ⎞ 98 | ⎝ 4 5 3 ⎠ 99 | """) 100 | 101 | let arrayB: [Float] = [2.5, 1, 8.235, 102 | 0.409, 23.5, 3, 103 | 19, 0.02, 1, 104 | 201, 9, 1902.3] 105 | let matB = Matrix(rows: 4, columns: 3, values: arrayB) 106 | let formatB = matB.formatted() 107 | #expect(formatB == """ 108 | ⎛ 2.5 1.0 8.235 ⎞ 109 | ⎜ 0.409 23.5 3.0 ⎟ 110 | ⎜ 19.0 0.02 1.0 ⎟ 111 | ⎝ 201.0 9.0 1902.3 ⎠ 112 | """) 113 | 114 | let matC = Matrix([[1, 5.2, 4], [10.1, 4, -12]]) 115 | let formatC = matC.formatted(specifier: "%.2f") 116 | #expect(formatC == """ 117 | ⎛ 1.00 5.20 4.00 ⎞ 118 | ⎝ 10.10 4.00 -12.00 ⎠ 119 | """) 120 | } 121 | 122 | @Test func approximatelyEqual() { 123 | let a = Matrix([[1, 2, 3, 4], [5, 6, 7, 8.123]]) 124 | let b = Matrix([[1, 2, 3, 4], [5, 6, 7, 8.1234567891011]]) 125 | #expect(!a.isApproximatelyEqual(to: b)) 126 | #expect(a.isApproximatelyEqual(to: b, absoluteTolerance: 0.001)) 127 | 128 | let c = Matrix([[1, 2, 3, 4], [5, 6, 7, 8.123]]) 129 | let d = Matrix([[1, 2, 3, 4], [5, 6, 7, 8.1234567891011]]) 130 | #expect(!c.isApproximatelyEqual(to: d)) 131 | #expect(c.isApproximatelyEqual(to: d, absoluteTolerance: 0.001)) 132 | } 133 | 134 | @Test func padding() { 135 | let a: Matrix = [[1, 2], [3, 4]] 136 | let b: Matrix = [[0, 0, 0, 0], 137 | [0, 1, 2, 0], 138 | [0, 3, 4, 0], 139 | [0, 0, 0, 0]] 140 | #expect(a.pad() == b) 141 | 142 | let c: Matrix = [[1, 2], [3, 4]] 143 | let d: Matrix = [[0, 0, 0, 0], 144 | [0, 1, 2, 0], 145 | [0, 3, 4, 0], 146 | [0, 0, 0, 0]] 147 | #expect(c.pad(with: 0) == d) 148 | 149 | let e: Matrix = [[1, 2], [3, 4.0]] 150 | let f: Matrix = [[9, 9, 9, 9], 151 | [9, 1, 2, 9], 152 | [9, 3, 4, 9], 153 | [9, 9, 9, 9.0]] 154 | #expect(e.pad(with: 9) == f) 155 | } 156 | 157 | @Test func copying() { 158 | // Modifying matrix B also modifies matrix A because matrix B is 159 | // a reference to matrix A 160 | let a: Matrix = [[0, 0, 0], [0, 0, 0]] 161 | var b = a 162 | b[0, 0] = 99 163 | b[0, 1] = 22 164 | #expect(a == [[99, 22, 0], [0, 0, 0]]) 165 | 166 | // Modifying matrix D does not modify matrix C because matrix D is 167 | // a complete copy of matrix C 168 | let c: Matrix = [[0, 0, 0], [0, 0, 0]] 169 | var d = c.copy() 170 | d[0, 0] = 99 171 | d[0, 1] = 22 172 | #expect(c == [[0, 0, 0], [0, 0, 0]]) 173 | } 174 | 175 | @Test func iteration() { 176 | let mat = Matrix([[1, 2, 3], 177 | [4, 5, 6], 178 | [7, 8, 9.0]]) 179 | 180 | // Iterate over rows 181 | var counter = 1 182 | for row in mat { 183 | if counter == mat.rows { 184 | #expect(row == [7, 8, 9.0]) 185 | } 186 | counter += 1 187 | } 188 | 189 | // Iterate over columns 190 | counter = 1 191 | for col in mat.transpose() { 192 | if counter == mat.columns { 193 | #expect(col == [3, 6, 9.0]) 194 | } 195 | counter += 1 196 | } 197 | } 198 | 199 | @Test func determinant() { 200 | let a: Matrix = [[1, 2], [3, 4]] 201 | let detA = a.determinant() 202 | #expect(detA == -2.0) 203 | 204 | let b: Matrix = [[1, 12, 3], 205 | [4, 5, 6], 206 | [7, 8, 9.5]] 207 | let detB = b.determinant() 208 | #expect(detB == 38.500015) 209 | } 210 | 211 | /* 212 | Need to test singular matrix which would cause precondition failuare but 213 | Swift Testing does not have this feature yet. See discussion on Swift 214 | forum https://forums.swift.org/t/exit-tests-death-tests-and-you/71186 215 | 216 | @Test func determinantSingular() { 217 | let a: Matrix = [[0, 12, 0], 218 | [4, 5, 0], 219 | [7, 0, 0]] 220 | let detA = a.determinant() 221 | } 222 | */ 223 | 224 | @Test func inverse() { 225 | let a = Matrix([[1, 2], [3, 4]]) 226 | let invA = a.inverse() 227 | #expect(invA == [[-2, 1], [1.5, -0.5]]) 228 | 229 | let b = Matrix([[1, 2], [3, 4]]) 230 | let invB = b.inverse() 231 | #expect(invB == [[-2, 1], [1.5, -0.5]]) 232 | } 233 | 234 | @Test func exponential() { 235 | let a = Matrix([[1, 2], [3, 4]]) 236 | let b = Matrix([[1, 2], [3, 4]]) 237 | 238 | #expect(a.exp() == [[2.71828183, 7.3890561], [20.08553692, 54.59815003]]) 239 | #expect(b.exp().isApproximatelyEqual(to: [[2.71828, 7.38905], [20.08553, 54.59815]], relativeTolerance: 1e-4)) 240 | 241 | #expect(a.exp2() == [[2, 4], [8, 16]]) 242 | #expect(b.exp2() == [[2, 4], [8, 16]]) 243 | 244 | #expect(a.expm1().isApproximatelyEqual(to: [[1.71828, 6.38905], [19.08553, 53.59815]], relativeTolerance: 1e-4)) 245 | #expect(b.expm1().isApproximatelyEqual(to: [[1.71828, 6.38905], [19.08553, 53.59815]], relativeTolerance: 1e-4)) 246 | } 247 | 248 | @Test func power() { 249 | let a = Matrix([[1, 2], [3, 4]]) 250 | let b = Matrix([[1, 2], [3, 4]]) 251 | 252 | #expect(a.power(2) == [[1, 4], [9, 16]]) 253 | #expect(b.power(2) == [[1, 4], [9, 16]]) 254 | 255 | #expect(a.power([2, 3, 4, 5]).isApproximatelyEqual(to: [[1, 8], [81, 1024]], relativeTolerance: 1e-4)) 256 | #expect(b.power([2, 3, 4, 5]) == [[1, 8], [81, 1024]]) 257 | } 258 | 259 | @Test func logarithm() { 260 | let a = Matrix([[1, 2], [3, 4]]) 261 | let b = Matrix([[1, 2], [3, 4]]) 262 | 263 | #expect(a.log().isApproximatelyEqual(to: [[0, 0.6931], [1.0986, 1.3862]], relativeTolerance: 1e-4)) 264 | #expect(b.log().isApproximatelyEqual(to: [[0, 0.693147], [1.098612, 1.386294]], relativeTolerance: 1e-6)) 265 | 266 | #expect(a.log1p().isApproximatelyEqual(to: [[0.6931, 1.0986], [1.3862, 1.6094]], relativeTolerance: 1e-4)) 267 | #expect(b.log1p().isApproximatelyEqual(to: [[0.693147, 1.098612], [1.386294, 1.609437]], 268 | relativeTolerance: 1e-6)) 269 | 270 | #expect(a.log10().isApproximatelyEqual(to: [[0.0, 0.30103], [0.47712125, 0.60205999]])) 271 | #expect(a.log10().isApproximatelyEqual(to: [[0.0, 0.30103], [0.47712125, 0.60205999]])) 272 | 273 | #expect(b.log2().isApproximatelyEqual(to: [[0.0, 1.0], [1.5849625, 2]], relativeTolerance: 1e-6)) 274 | #expect(b.log2().isApproximatelyEqual(to: [[0.0, 1.0], [1.5849625, 2]], relativeTolerance: 1e-6)) 275 | 276 | #expect(b.logb().isApproximatelyEqual(to: [[0, 1], [1, 2]])) 277 | #expect(b.logb().isApproximatelyEqual(to: [[0, 1], [1, 2]])) 278 | } 279 | 280 | @Test func integerArithmetic() { 281 | let k = 5 282 | let a = Matrix([[1, 2, 3], [4, 5, 6]]) 283 | let b = Matrix([[7, 8, 9], [3, 4, 5]]) 284 | let c = Matrix([[1, 2], [3, 4], [5, 6]]) 285 | 286 | // Equality 287 | #expect(a == a) 288 | #expect(a != b) 289 | 290 | // Addition 291 | #expect(k + a == Matrix([[6, 7, 8], [9, 10, 11]])) 292 | #expect(a + k == Matrix([[6, 7, 8], [9, 10, 11]])) 293 | #expect(a + b == Matrix([[8, 10, 12], [7, 9, 11]])) 294 | 295 | var d = Matrix([[1, 2, 3], [4, 5, 6]]) 296 | d += b 297 | #expect(d == Matrix([[8, 10, 12], [7, 9, 11]])) 298 | 299 | // Subtraction 300 | #expect(k - a == Matrix([[4, 3, 2], [1, 0, -1]])) 301 | #expect(a - k == Matrix([[-4, -3, -2], [-1, 0, 1]])) 302 | #expect(a - b == Matrix([[-6, -6, -6], [1, 1, 1]])) 303 | 304 | // Element-wise multiplication 305 | #expect(k .* a == Matrix([[5, 10, 15], [20, 25, 30]])) 306 | #expect(a .* k == Matrix([[5, 10, 15], [20, 25, 30]])) 307 | #expect(a .* b == Matrix([[7, 16, 27], [12, 20, 30]])) 308 | 309 | // Matrix multiplication 310 | #expect(a * c == Matrix([[22, 28], [49, 64]])) 311 | 312 | // Division 313 | #expect(k / a == [[5, 2, 1], [1, 1, 0]]) 314 | #expect(a / k == [[0, 0, 0], [0, 1, 1]]) 315 | #expect(a / b == [[0, 0, 0], [1, 1, 1]]) 316 | 317 | // Methods 318 | // #expect(a.dot(b) == 100) 319 | // #expect(a.sum() == 15) 320 | // #expect(a.absoluteSum() == 15) 321 | } 322 | 323 | @Test func floatArithmetic() { 324 | let k: Float = 5 325 | let a = Matrix([[1, 2, 3], [4, 5, 6]]) 326 | let b = Matrix([[7, 8, 9], [3, 4, 5]]) 327 | let c = Matrix([[1, 2], [3, 4], [5, 6]]) 328 | 329 | // Equality 330 | #expect(a == a) 331 | #expect(a != b) 332 | 333 | // Addition 334 | #expect(k + a == Matrix([[6, 7, 8], [9, 10, 11]])) 335 | #expect(a + k == Matrix([[6, 7, 8], [9, 10, 11]])) 336 | #expect(a + b == Matrix([[8, 10, 12], [7, 9, 11]])) 337 | 338 | var d = Matrix([[1, 2, 3], [4, 5, 6]]) 339 | d += b 340 | #expect(d == Matrix([[8, 10, 12], [7, 9, 11]])) 341 | 342 | // Subtraction 343 | #expect(k - a == Matrix([[4, 3, 2], [1, 0, -1]])) 344 | #expect(a - k == Matrix([[-4, -3, -2], [-1, 0, 1]])) 345 | #expect(a - b == Matrix([[-6, -6, -6], [1, 1, 1]])) 346 | 347 | // Element-wise multiplication 348 | #expect(k .* a == Matrix([[5, 10, 15], [20, 25, 30]])) 349 | #expect(a .* k == Matrix([[5, 10, 15], [20, 25, 30]])) 350 | #expect(a .* b == Matrix([[7, 16, 27], [12, 20, 30]])) 351 | 352 | // Matrix multiplication 353 | #expect(a * c == Matrix([[22, 28], [49, 64]])) 354 | 355 | // Division 356 | #expect((k / a).isApproximatelyEqual(to: [[5, 2.5, 1.66666667], [1.25, 1.0, 0.83333333]])) 357 | #expect(a / k == [[0.2, 0.4, 0.6], [0.8, 1.0, 1.2]]) 358 | #expect((a / b).isApproximatelyEqual(to: [[0.14285714, 0.25, 0.33333333], [1.33333333, 1.25, 1.2]])) 359 | 360 | // Methods 361 | // #expect(a.dot(b) == 100) 362 | // #expect(a.sum() == 15) 363 | // #expect(a.absoluteSum() == 15) 364 | } 365 | 366 | @Test func doubleArithmetic() { 367 | let k = 5.0 368 | let a = Matrix([[1, 2, 3], [4, 5, 6.0]]) 369 | let b = Matrix([[7, 8, 9], [3, 4, 5.0]]) 370 | let c = Matrix([[1, 2], [3, 4], [5, 6.0]]) 371 | 372 | // Equality 373 | #expect(a == a) 374 | #expect(a != b) 375 | 376 | // Addition 377 | #expect(k + a == Matrix([[6, 7, 8], [9, 10, 11]])) 378 | #expect(a + k == Matrix([[6, 7, 8], [9, 10, 11]])) 379 | #expect(a + b == Matrix([[8, 10, 12], [7, 9, 11]])) 380 | 381 | var d = Matrix([[1, 2, 3], [4, 5, 6.0]]) 382 | d += b 383 | #expect(d == Matrix([[8, 10, 12], [7, 9, 11]])) 384 | 385 | // Subtraction 386 | #expect(k - a == Matrix([[4, 3, 2], [1, 0, -1]])) 387 | #expect(a - k == Matrix([[-4, -3, -2], [-1, 0, 1]])) 388 | #expect(a - b == Matrix([[-6, -6, -6], [1, 1, 1]])) 389 | 390 | // Element-wise multiplication 391 | #expect(k .* a == Matrix([[5, 10, 15], [20, 25, 30]])) 392 | #expect(a .* k == Matrix([[5, 10, 15], [20, 25, 30]])) 393 | #expect(a .* b == Matrix([[7, 16, 27], [12, 20, 30]])) 394 | 395 | // Matrix multiplication 396 | #expect(a * c == Matrix([[22, 28], [49, 64]])) 397 | 398 | // Division 399 | #expect((k / a).isApproximatelyEqual(to: [[5, 2.5, 1.66666667], [1.25, 1.0, 0.83333333]])) 400 | #expect((a / k).isApproximatelyEqual(to: [[0.2, 0.4, 0.6], [0.8, 1.0, 1.2]])) 401 | #expect((a / b).isApproximatelyEqual(to: [[0.14285714, 0.25, 0.33333333], [1.33333333, 1.25, 1.2]])) 402 | 403 | // Methods 404 | // #expect(a.dot(b) == 100) 405 | // #expect(a.sum() == 15) 406 | // #expect(a.absoluteSum() == 15) 407 | } 408 | 409 | @Test func integerAlgebra() { 410 | let a = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 411 | let b = Matrix([[2, 3, 4, 5], [6, 7, 8, 9]]) 412 | 413 | #expect(a.norm() == 16) 414 | #expect(a.transpose() == Matrix([[1, 4, 7], [2, 5, 8], [3, 6, 9]])) 415 | #expect(b.transpose() == Matrix([[2, 6], [3, 7], [4, 8], [5, 9]])) 416 | 417 | var c = Matrix([[1, 2, 3], [4, 5, 6]]) 418 | c.scale(by: 3) 419 | #expect(c == [[3, 6, 9], [12, 15, 18]]) 420 | 421 | var d = Matrix([[1, 2, 3], [4, 5, 6]]) 422 | var e = Matrix([[9, 10, 11], [12, 13, 14]]) 423 | swapValues(&d, &e) 424 | #expect(d == [[9, 10, 11], [12, 13, 14]]) 425 | #expect(e == [[1, 2, 3], [4, 5, 6]]) 426 | } 427 | 428 | @Test func floatAlgebra() { 429 | let a = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 430 | let b = Matrix([[2, 3, 4, 5], [6, 7, 8, 9]]) 431 | 432 | #expect(a.norm() == 16.881943) 433 | #expect(a.transpose() == Matrix([[1, 4, 7], [2, 5, 8], [3, 6, 9]])) 434 | #expect(b.transpose() == Matrix([[2, 6], [3, 7], [4, 8], [5, 9]])) 435 | 436 | var c = Matrix([[1, 2, 3], [4, 5, 6]]) 437 | c.scale(by: 3.0) 438 | #expect(c == [[3, 6, 9], [12, 15, 18.0]]) 439 | 440 | var d = Matrix([[1, 2, 3], [4, 5, 6]]) 441 | var e = Matrix([[9, 10, 11], [12, 13, 14]]) 442 | swapValues(&d, &e) 443 | #expect(d == [[9, 10, 11], [12, 13, 14]]) 444 | #expect(e == [[1, 2, 3], [4, 5, 6]]) 445 | } 446 | 447 | @Test func doubleAlgebra() { 448 | let a = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9.0]]) 449 | let b = Matrix([[2, 3, 4, 5], [6, 7, 8, 9.0]]) 450 | 451 | #expect(a.norm() == 16.881943016134134) 452 | #expect(a.transpose() == Matrix([[1, 4, 7], [2, 5, 8], [3, 6, 9.0]])) 453 | #expect(b.transpose() == Matrix([[2, 6], [3, 7], [4, 8], [5, 9.0]])) 454 | 455 | var c = Matrix([[1, 2, 3], [4, 5, 6.0]]) 456 | c.scale(by: 3.0) 457 | #expect(c == [[3, 6, 9], [12, 15, 18.0]]) 458 | 459 | var d = Matrix([[1, 2, 3], [4, 5, 6.0]]) 460 | var e = Matrix([[9, 10, 11], [12, 13, 14.0]]) 461 | swapValues(&d, &e) 462 | #expect(d == [[9, 10, 11], [12, 13, 14.0]]) 463 | #expect(e == [[1, 2, 3], [4, 5, 6.0]]) 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /Tests/RandomDistTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Tests for random distribution functions using Vector structs. 3 | */ 4 | 5 | import Testing 6 | @testable import Numerix 7 | 8 | struct RandomDistTests { 9 | 10 | @Test func randomDist() { 11 | let a = Vector.randomDistribution(size: 4) 12 | let b = Vector.randomDistribution(size: 5) 13 | #expect(a[0] <= 1.0) 14 | #expect(b[0] <= 1.0) 15 | } 16 | 17 | @Test func vectorTrigonometry() { 18 | let a = Vector([1, 2, 3, 4]) 19 | #expect(sin(a).isApproximatelyEqual(to: [0.841471, 0.909297, 0.14112, -0.756802])) 20 | #expect(cos(a).isApproximatelyEqual(to: [0.540302, -0.416147, -0.989992, -0.653644])) 21 | #expect(tan(a).isApproximatelyEqual(to: [1.55741, -2.18504, -0.142547, 1.15782], relativeTolerance: 1e-4)) 22 | 23 | let b = Vector([1, 2, 3, 4.0]) 24 | #expect(sin(b).isApproximatelyEqual(to: [0.841471, 0.909297, 0.14112, -0.756802], relativeTolerance: 1e-4)) 25 | #expect(cos(b).isApproximatelyEqual(to: [0.540302, -0.416147, -0.989992, -0.6536], relativeTolerance: 1e-4)) 26 | #expect(tan(b).isApproximatelyEqual(to: [1.55741, -2.18504, -0.142547, 1.15782], relativeTolerance: 1e-4)) 27 | } 28 | 29 | @Test func matrixTrigonometry() { 30 | let a = Matrix([[1, 2], [3, 4]]) 31 | #expect(sin(a).isApproximatelyEqual(to: [[0.841471, 0.909297], [0.14112, -0.756802]])) 32 | #expect(cos(a).isApproximatelyEqual(to: [[0.540302, -0.416147], [-0.989992, -0.653644]])) 33 | #expect(tan(a).isApproximatelyEqual(to: [[1.55741, -2.18504], [-0.142547, 1.15782]], relativeTolerance: 1e-4)) 34 | 35 | let b = Matrix([[1, 2], [3, 4.0]]) 36 | #expect(sin(b).isApproximatelyEqual(to: [[0.841471, 0.909297], [0.14112, -0.756802]], relativeTolerance: 1e-4)) 37 | #expect(cos(b).isApproximatelyEqual(to: [[0.540302, -0.416147], [-0.989992, -0.6536]], relativeTolerance: 1e-4)) 38 | #expect(tan(b).isApproximatelyEqual(to: [[1.55741, -2.18504], [-0.142547, 1.15782]], relativeTolerance: 1e-4)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/ShapedArrayTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Tests for the ShapedArray structure. 3 | */ 4 | 5 | import Testing 6 | @testable import Numerix 7 | 8 | struct ShapedArrayTests { 9 | 10 | @Test func initialize1D() { 11 | let a = ShapedArray([1, 2, 3, 4, 5]) 12 | #expect(a.shape == [5]) 13 | #expect(a[2] == 3) 14 | 15 | let b: ShapedArray = [1, 2, 3, 4] 16 | #expect(b.shape == [4]) 17 | #expect(b[1] == 2) 18 | } 19 | 20 | @Test func initialize2D() { 21 | let a = ShapedArray([[1, 2, 3, 4], 22 | [5, 6, 7, 8]]) 23 | #expect(a.shape == [2, 4]) 24 | #expect(a[1, 2] == 7) 25 | } 26 | 27 | @Test func initialize3D() { 28 | let a = [1, 2, 3, 4, 5, 29 | 6, 7, 8, 9, 10, 30 | 31 | 11, 12, 13, 14, 15, 32 | 16, 17, 18, 19, 20] 33 | 34 | let b = ShapedArray(shape: [2, 2, 5], array: a) 35 | 36 | #expect(b.shape == [2, 2, 5]) 37 | #expect(b[1, 0, 3] == 14) 38 | 39 | let c = ShapedArray([[[1, 2, 3], 40 | [4, 5, 6]], 41 | [[7, 8, 9], 42 | [3, 4, 5]]]) 43 | 44 | #expect(c.shape == [2, 2, 3]) 45 | } 46 | 47 | @Test func initialize4D() { 48 | let a = [1, 2, 3, 4, 49 | 5, 6, 7, 8, 50 | 51 | 9, 10, 11, 12, 52 | 13, 14, 15, 16, 53 | 54 | 17, 18, 19, 20, 55 | 21, 22, 23, 24, 56 | 57 | 25, 26, 27, 28, 58 | 29, 30, 31, 32, 59 | 60 | 33, 34, 35, 36, 61 | 37, 38, 39, 40, 62 | 63 | 41, 42, 43, 44, 64 | 45, 46, 47, 48] 65 | 66 | let b = ShapedArray(shape: [2, 3, 2, 4], array: a) 67 | 68 | #expect(b.shape == [2, 3, 2, 4]) 69 | #expect(b[1, 0, 1, 3] == 32) 70 | } 71 | 72 | @Test func equatable() { 73 | let a = ShapedArray([[1, 2, 3], [4, 5, 6]]) 74 | let b = ShapedArray([[1, 2, 3], [4, 5, 6]]) 75 | #expect(a == b) 76 | 77 | let c = ShapedArray([[1, 2, 3], [8, 9, 10]]) 78 | #expect(a != c) 79 | } 80 | 81 | @Test func printing() { 82 | let arrA = ShapedArray([3, 4, 5, 6, 7]) 83 | #expect(arrA.description == "( 3 4 5 6 7 )") 84 | 85 | let arrB = ShapedArray([3.1, 4, 5.09, 6, 7]) 86 | #expect(arrB.description == "( 3.1 4.0 5.09 6.0 7.0 )") 87 | 88 | let shapeC = [3, 2] 89 | let numsC = Array(1...shapeC.reduce(1, *)).map { Float($0) } 90 | let arrC = ShapedArray(shape: shapeC, array: numsC) 91 | 92 | let descC = """ 93 | ⎛ 1.0 2.0 ⎞ 94 | ⎜ 3.0 4.0 ⎟ 95 | ⎝ 5.0 6.0 ⎠ 96 | """ 97 | #expect(arrC.description == descC) 98 | 99 | let shapeD = [2, 3, 5] 100 | let numsD = Array(1...shapeD.reduce(1, *)).map { Double($0) } 101 | let arrD = ShapedArray(shape: shapeD, array: numsD) 102 | 103 | let descD = """ 104 | ⎛ ⎛ 1.0 2.0 3.0 4.0 5.0 ⎞ ⎞ 105 | ⎜ ⎜ 6.0 7.0 8.0 9.0 10.0 ⎟ ⎟ 106 | ⎜ ⎝ 11.0 12.0 13.0 14.0 15.0 ⎠ ⎟ 107 | ⎜ ⎟ 108 | ⎜ ⎛ 16.0 17.0 18.0 19.0 20.0 ⎞ ⎟ 109 | ⎜ ⎜ 21.0 22.0 23.0 24.0 25.0 ⎟ ⎟ 110 | ⎝ ⎝ 26.0 27.0 28.0 29.0 30.0 ⎠ ⎠ 111 | """ 112 | #expect(arrD.description == descD) 113 | } 114 | 115 | @Test func debugPrinting() { 116 | let shape = [2, 3, 5] 117 | let nums = Array(1...shape.reduce(1, *)).map { Double($0) } 118 | let arr = ShapedArray(shape: shape, array: nums) 119 | 120 | let desc = """ 121 | 2x3x5 ShapedArray 122 | ⎛ ⎛ 1.0 2.0 3.0 4.0 5.0 ⎞ ⎞ 123 | ⎜ ⎜ 6.0 7.0 8.0 9.0 10.0 ⎟ ⎟ 124 | ⎜ ⎝ 11.0 12.0 13.0 14.0 15.0 ⎠ ⎟ 125 | ⎜ ⎟ 126 | ⎜ ⎛ 16.0 17.0 18.0 19.0 20.0 ⎞ ⎟ 127 | ⎜ ⎜ 21.0 22.0 23.0 24.0 25.0 ⎟ ⎟ 128 | ⎝ ⎝ 26.0 27.0 28.0 29.0 30.0 ⎠ ⎠ 129 | """ 130 | #expect(arr.debugDescription == desc) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Tests/TrigonometryTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Tests for trigonometry functions using Vector and Matrix structs. 3 | */ 4 | 5 | import Testing 6 | @testable import Numerix 7 | 8 | struct TrigonometryTests { 9 | 10 | @Test func vectorTrig() { 11 | let a = Vector([1, 2, 3, 4]) // single precision 12 | 13 | #expect(sin(a).isApproximatelyEqual(to: [0.841471, 0.909297, 0.14112, -0.756802])) 14 | #expect(cos(a).isApproximatelyEqual(to: [0.540302, -0.416147, -0.989992, -0.653644])) 15 | #expect(tan(a).isApproximatelyEqual(to: [1.55741, -2.18504, -0.142547, 1.15782], relativeTolerance: 1e-4)) 16 | 17 | #expect(csc(a).isApproximatelyEqual(to: [1.18839511, 1.09975017, 7.0861674, -1.32134871])) 18 | #expect(sec(a).isApproximatelyEqual(to: [1.85081572, -2.40299796, -1.01010867, -1.52988566])) 19 | #expect(cot(a).isApproximatelyEqual(to: [0.64209262, -0.45765755, -7.01525255, 0.86369115])) 20 | 21 | let b = Vector([1, 2, 3, 4.0]) // double precision 22 | 23 | #expect(sin(b).isApproximatelyEqual(to: [0.841471, 0.909297, 0.14112, -0.756802], relativeTolerance: 1e-4)) 24 | #expect(cos(b).isApproximatelyEqual(to: [0.540302, -0.416147, -0.989992, -0.6536], relativeTolerance: 1e-4)) 25 | #expect(tan(b).isApproximatelyEqual(to: [1.55741, -2.18504, -0.142547, 1.15782], relativeTolerance: 1e-4)) 26 | 27 | #expect(csc(b).isApproximatelyEqual(to: [1.18839511, 1.09975017, 7.0861674, -1.32134871])) 28 | #expect(sec(b).isApproximatelyEqual(to: [1.85081572, -2.40299796, -1.01010867, -1.52988566])) 29 | #expect(cot(b).isApproximatelyEqual(to: [0.64209262, -0.45765755, -7.01525255, 0.86369115])) 30 | } 31 | 32 | @Test func vectorArcTrig() { 33 | let a = Vector([-1, -0.5, 0, 0.5, 1]) // single precision 34 | #expect(asin(a) == [-1.57079633, -0.52359878, 0.0, 0.52359878, 1.57079633]) 35 | #expect(acos(a) == [3.14159265, 2.0943951, 1.57079633, 1.04719755, 0.0]) 36 | #expect(atan(a) == [-0.78539816, -0.46364761, 0.0, 0.46364761, 0.78539816]) 37 | 38 | let b = Vector([-1, -0.5, 0, 0.5, 1]) // double precision 39 | #expect(asin(b).isApproximatelyEqual(to: [-1.57079633, -0.52359878, 0.0, 0.52359878, 1.57079633])) 40 | #expect(acos(b).isApproximatelyEqual(to: [3.14159265, 2.0943951, 1.57079633, 1.04719755, 0.0])) 41 | #expect(atan(b).isApproximatelyEqual(to: [-0.78539816, -0.46364761, 0.0, 0.46364761, 0.78539816])) 42 | } 43 | 44 | @Test func matrixTrig() { 45 | let a = Matrix([[1, 2], [3, 4]]) 46 | #expect(sin(a).isApproximatelyEqual(to: [[0.841471, 0.909297], [0.14112, -0.756802]])) 47 | #expect(cos(a).isApproximatelyEqual(to: [[0.540302, -0.416147], [-0.989992, -0.653644]])) 48 | #expect(tan(a).isApproximatelyEqual(to: [[1.55741, -2.18504], [-0.142547, 1.15782]], relativeTolerance: 1e-4)) 49 | 50 | let b = Matrix([[1, 2], [3, 4.0]]) 51 | #expect(sin(b).isApproximatelyEqual(to: [[0.841471, 0.909297], [0.14112, -0.756802]], relativeTolerance: 1e-4)) 52 | #expect(cos(b).isApproximatelyEqual(to: [[0.540302, -0.416147], [-0.989992, -0.6536]], relativeTolerance: 1e-4)) 53 | #expect(tan(b).isApproximatelyEqual(to: [[1.55741, -2.18504], [-0.142547, 1.15782]], relativeTolerance: 1e-4)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/VectorTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Tests for the Vector structure. 3 | */ 4 | 5 | import Testing 6 | @testable import Numerix 7 | 8 | struct VectorTests { 9 | 10 | @Test func initialization() { 11 | let a = Vector([1, 2, 3, 4]) 12 | let b = Vector(like: a) 13 | let c = Vector(size: 5) 14 | let d = Vector(size: 5, fill: 9.2) 15 | 16 | #expect(a == Vector([1, 2, 3, 4])) 17 | #expect(b == Vector([0, 0, 0, 0])) 18 | #expect(c == Vector([0, 0, 0, 0, 0.0])) 19 | #expect(d == Vector([9.2, 9.2, 9.2, 9.2, 9.2])) 20 | } 21 | 22 | @Test func arrayLiteral() { 23 | let a: Vector = [1, 2, 3, 4, 5] 24 | #expect(a == [1, 2, 3, 4, 5]) 25 | 26 | let b: Vector = [3, 4.2, 8, 21, 18.94] 27 | #expect(b == [3, 4.2, 8, 21, 18.94]) 28 | } 29 | 30 | @Test func printing() { 31 | let a = Vector([4, 5, 8.02, 9, 10]) 32 | 33 | var printOutput = "" 34 | print(a, terminator: "", to: &printOutput) 35 | #expect(printOutput == "( 4.0 5.0 8.02 9.0 10.0 )") 36 | 37 | var debugOutput = "" 38 | debugPrint(a, terminator: "", to: &debugOutput) 39 | #expect(debugOutput == "5-element Vector\n( 4.0 5.0 8.02 9.0 10.0 )") 40 | } 41 | 42 | @Test func formatting() { 43 | let vecA = Vector([4, 5, 8.02, 9, 10]) 44 | let formatA = vecA.formatted() 45 | #expect(formatA == "( 4.0 5.0 8.02 9.0 10.0 )") 46 | 47 | let vecB = Vector([0.90129, 5, 8.02, 9, 10]) 48 | let formatB = vecB.formatted(specifier: "%.4f") 49 | #expect(formatB == "( 0.9013 5.0000 8.0200 9.0000 10.0000 )") 50 | 51 | let vecC = Vector([-2, 5, 18, 7, 2215]) 52 | let formatC = vecC.formatted() 53 | #expect(formatC == "( -2 5 18 7 2215 )") 54 | } 55 | 56 | @Test func approximatelyEqual() { 57 | let a = Vector([5, 6, 7, 8.123]) 58 | let b = Vector([5, 6, 7, 8.1234567891011]) 59 | #expect(!a.isApproximatelyEqual(to: b)) 60 | #expect(a.isApproximatelyEqual(to: b, absoluteTolerance: 0.001)) 61 | 62 | let c = Vector([5, 6, 7, 8.123]) 63 | let d = Vector([5, 6, 7, 8.1234567891011]) 64 | #expect(!c.isApproximatelyEqual(to: d)) 65 | #expect(c.isApproximatelyEqual(to: d, absoluteTolerance: 0.001)) 66 | } 67 | 68 | @Test func exponential() { 69 | let a = Vector([1, 2, 3, 4]) 70 | let b = Vector([1, 2, 3, 4]) 71 | 72 | #expect(a.exp() == [2.71828183, 7.3890561, 20.08553692, 54.59815003]) 73 | #expect(b.exp().isApproximatelyEqual(to: [2.71828, 7.38905, 20.08553, 54.59815], relativeTolerance: 1e-4)) 74 | 75 | #expect(a.exp2() == [2, 4, 8, 16]) 76 | #expect(b.exp2() == [2, 4, 8, 16]) 77 | 78 | #expect(a.expm1().isApproximatelyEqual(to: [1.71828, 6.38905, 19.08553, 53.59815], relativeTolerance: 1e-4)) 79 | #expect(b.expm1().isApproximatelyEqual(to: [1.71828, 6.38905, 19.08553, 53.59815], relativeTolerance: 1e-4)) 80 | } 81 | 82 | @Test func power() { 83 | let a = Vector([1, 2, 3, 4]) 84 | let b = Vector([1, 2, 3, 4]) 85 | 86 | #expect(a.power(2) == [1, 4, 9, 16]) 87 | #expect(b.power(2) == [1, 4, 9, 16]) 88 | 89 | #expect(a.power([2, 3, 4, 5]).isApproximatelyEqual(to: [1, 8, 81, 1024], relativeTolerance: 1e-4)) 90 | #expect(b.power([2, 3, 4, 5]) == [1, 8, 81, 1024]) 91 | } 92 | 93 | @Test func logarithm() { 94 | let a = Vector([1, 2, 3, 4, 5]) 95 | let b = Vector([1, 2, 3, 4, 5]) 96 | 97 | #expect(a.log().isApproximatelyEqual(to: [0, 0.6931, 1.0986, 1.3862, 1.6094], relativeTolerance: 1e-4)) 98 | #expect(b.log().isApproximatelyEqual(to: [0, 0.693147, 1.098612, 1.386294, 1.609437], relativeTolerance: 1e-6)) 99 | 100 | #expect(a.log1p().isApproximatelyEqual(to: [0.6931, 1.0986, 1.3862, 1.6094, 1.7917], relativeTolerance: 1e-4)) 101 | #expect(b.log1p().isApproximatelyEqual(to: [0.693147, 1.098612, 1.386294, 1.609437, 1.791759], 102 | relativeTolerance: 1e-6)) 103 | 104 | #expect(a.log10().isApproximatelyEqual(to: [0.0, 0.30103, 0.47712125, 0.60205999, 0.69897])) 105 | #expect(a.log10().isApproximatelyEqual(to: [0.0, 0.30103, 0.47712125, 0.60205999, 0.69897])) 106 | 107 | #expect(b.log2().isApproximatelyEqual(to: [0.0, 1.0, 1.5849625, 2, 2.321928], relativeTolerance: 1e-6)) 108 | #expect(b.log2().isApproximatelyEqual(to: [0.0, 1.0, 1.5849625, 2, 2.321928], relativeTolerance: 1e-6)) 109 | 110 | #expect(b.logb().isApproximatelyEqual(to: [0, 1, 1, 2, 2])) 111 | #expect(b.logb().isApproximatelyEqual(to: [0, 1, 1, 2, 2])) 112 | } 113 | 114 | @Test func integerArithmetic() { 115 | let k = 5 116 | let a = Vector([1, 2, 3, 4, 5]) 117 | let b = Vector([4, 5, 6, 7, 8]) 118 | 119 | // Equality 120 | #expect(a == a) 121 | #expect(a != b) 122 | 123 | // Addition 124 | #expect(k + a == Vector([6, 7, 8, 9, 10])) 125 | #expect(a + k == Vector([6, 7, 8, 9, 10])) 126 | #expect(a + b == Vector([5, 7, 9, 11, 13])) 127 | 128 | var c = Vector([1, 2, 3, 4, 5]) 129 | c += Vector([1, 2, 3, 4, 5]) 130 | #expect(c == Vector([2, 4, 6, 8, 10])) 131 | 132 | // Subtraction 133 | #expect(k - a == Vector([4, 3, 2, 1, 0])) 134 | #expect(a - k == Vector([-4, -3, -2, -1, 0])) 135 | #expect(a - b == Vector([-3, -3, -3, -3, -3])) 136 | 137 | // Multiplication 138 | #expect(k * a == Vector([5, 10, 15, 20, 25])) 139 | #expect(a * k == Vector([5, 10, 15, 20, 25])) 140 | #expect(a * b == Vector([4, 10, 18, 28, 40])) 141 | 142 | // Division 143 | #expect(k / a == [5, 2, 1, 1, 1]) 144 | #expect(a / k == [0, 0, 0, 0, 1]) 145 | #expect(a / b == [0, 0, 0, 0, 0]) 146 | } 147 | 148 | @Test func floatArithmetic() { 149 | let k: Float = 5 150 | let a = Vector([1, 2, 3, 4, 5]) 151 | let b = Vector([4, 5, 6, 7, 8]) 152 | 153 | // Equality 154 | #expect(a == a) 155 | #expect(a != b) 156 | 157 | // Addition 158 | #expect(k + a == Vector([6, 7, 8, 9, 10])) 159 | #expect(a + k == Vector([6, 7, 8, 9, 10])) 160 | #expect(a + b == Vector([5, 7, 9, 11, 13])) 161 | 162 | var c = Vector([1, 2, 3, 4, 5]) 163 | c += Vector([1, 2, 3, 4, 5]) 164 | #expect(c == Vector([2, 4, 6, 8, 10])) 165 | 166 | // Subtraction 167 | #expect(k - a == Vector([4, 3, 2, 1, 0])) 168 | #expect(a - k == Vector([-4, -3, -2, -1, 0])) 169 | #expect(a - b == Vector([-3, -3, -3, -3, -3])) 170 | 171 | // Multiplication 172 | #expect(k * a == Vector([5, 10, 15, 20, 25])) 173 | #expect(a * k == Vector([5, 10, 15, 20, 25])) 174 | #expect(a * b == Vector([4, 10, 18, 28, 40])) 175 | 176 | // Division 177 | #expect(k / a == [5, 2.5, 1.66666667, 1.25, 1]) 178 | #expect(a / k == [0.2, 0.4, 0.6, 0.8, 1]) 179 | #expect(a / b == [0.25, 0.4, 0.5, 0.57142857, 0.625]) 180 | } 181 | 182 | @Test func doubleArithmetic() { 183 | let k = 5.0 184 | let a = Vector([1, 2, 3, 4, 5.0]) 185 | let b = Vector([4, 5, 6, 7, 8.0]) 186 | 187 | // Equality 188 | #expect(a == a) 189 | #expect(a != b) 190 | 191 | // Addition 192 | #expect(k + a == Vector([6, 7, 8, 9, 10])) 193 | #expect(a + k == Vector([6, 7, 8, 9, 10])) 194 | #expect(a + b == Vector([5, 7, 9, 11, 13])) 195 | 196 | var c = Vector([1, 2, 3, 4, 5.0]) 197 | c += Vector([1, 2, 3, 4, 5.0]) 198 | #expect(c == Vector([2, 4, 6, 8, 10])) 199 | 200 | // Subtraction 201 | #expect(k - a == Vector([4, 3, 2, 1, 0])) 202 | #expect(a - k == Vector([-4, -3, -2, -1, 0])) 203 | #expect(a - b == Vector([-3, -3, -3, -3, -3])) 204 | 205 | // Multiplication 206 | #expect(k * a == Vector([5, 10, 15, 20, 25])) 207 | #expect(a * k == Vector([5, 10, 15, 20, 25])) 208 | #expect(a * b == Vector([4, 10, 18, 28, 40])) 209 | 210 | // Division 211 | #expect((k / a).isApproximatelyEqual(to: [5, 2.5, 1.66666667, 1.25, 1])) 212 | #expect(a / k == [0.2, 0.4, 0.6, 0.8, 1]) 213 | #expect((a / b).isApproximatelyEqual(to: [0.25, 0.4, 0.5, 0.57142857, 0.625])) 214 | } 215 | 216 | @Test func integerAlgebra() { 217 | let a = Vector([1, 2, 3, 4, 5]) 218 | let b = Vector([4, 5, 6, 7, 8]) 219 | #expect(a.dot(b) == 100) 220 | #expect(a.norm() == 7) 221 | #expect(a.sum() == 15) 222 | #expect(a.absoluteSum() == 15) 223 | #expect(a.cumulativeSum() == [1, 3, 6, 10, 15]) 224 | 225 | var c = Vector([8, 9, 10, 11]) 226 | c.scale(by: 3) 227 | #expect(c == [24, 27, 30, 33]) 228 | 229 | var d = Vector([8, 9, 10, 11]) 230 | var e = Vector([1, 2, 3, 4]) 231 | swapValues(&d, &e) 232 | #expect(d == [1, 2, 3, 4]) 233 | #expect(e == [8, 9, 10, 11]) 234 | } 235 | 236 | @Test func floatAlgebra() { 237 | let a = Vector([1, 2, 3, 4, 5]) 238 | let b = Vector([4, 5, 6, 7, 8]) 239 | 240 | #expect(a.dot(b) == 100) 241 | #expect(a.norm() == 7.4161984871) 242 | #expect(a.sum() == 15) 243 | #expect(a.absoluteSum() == 15) 244 | #expect(a.cumulativeSum() == [1, 3, 6, 10, 15]) 245 | 246 | var c = Vector([8, 9, 10, 11]) 247 | c.scale(by: 3) 248 | #expect(c == [24, 27, 30, 33]) 249 | 250 | var d = Vector([8, 9, 10, 11]) 251 | var e = Vector([1, 2, 3, 4]) 252 | swapValues(&d, &e) 253 | #expect(d == [1, 2, 3, 4]) 254 | #expect(e == [8, 9, 10, 11]) 255 | } 256 | 257 | @Test func doubleAlgebra() { 258 | let a = Vector([1, 2, 3, 4, 5.0]) 259 | let b = Vector([4, 5, 6, 7, 8.0]) 260 | 261 | #expect(a.dot(b) == 100) 262 | #expect(a.norm() == 7.416198487095663) 263 | #expect(a.sum() == 15) 264 | #expect(a.absoluteSum() == 15) 265 | #expect(a.cumulativeSum() == [1, 3, 6, 10, 15]) 266 | 267 | var c = Vector([8, 9, 10, 11.0]) 268 | c.scale(by: 3) 269 | #expect(c == [24, 27, 30, 33]) 270 | 271 | var d = Vector([8, 9, 10, 11.0]) 272 | var e = Vector([1, 2, 3, 4.0]) 273 | swapValues(&d, &e) 274 | #expect(d == [1, 2, 3, 4.0]) 275 | #expect(e == [8, 9, 10, 11.0]) 276 | } 277 | } 278 | --------------------------------------------------------------------------------