├── Sources └── Plinth │ ├── Extensions │ ├── Linear Algebra │ │ ├── Ones.swift │ │ ├── Zeros.swift │ │ ├── Exponentiation.swift │ │ ├── Identity.swift │ │ ├── SquareRoot.swift │ │ ├── Roots.swift │ │ ├── Diagonal.swift │ │ ├── Products.swift │ │ ├── Transposition.swift │ │ ├── Circulant.swift │ │ ├── Division.swift │ │ ├── Inversion.swift │ │ ├── Multiplication.swift │ │ └── Eigendecomposition.swift │ ├── Signal Processing │ │ ├── FFTShift.swift │ │ ├── FFT.swift │ │ ├── Autocorrelation.swift │ │ ├── Convolution1D.swift │ │ ├── Autoconvolution.swift │ │ ├── Resampling.swift │ │ ├── FFTRamp.swift │ │ ├── FFT1D.swift │ │ ├── FFT2D.swift │ │ └── Convolution2D.swift │ ├── Transformations │ │ ├── Center.swift │ │ ├── Repeat.swift │ │ ├── Reverse.swift │ │ ├── Reshape.swift │ │ ├── Crop.swift │ │ ├── Concatenate.swift │ │ ├── Pad.swift │ │ └── Shift.swift │ ├── Mathematics │ │ ├── Interpolation.swift │ │ ├── Ramps.swift │ │ ├── Comparisons.swift │ │ └── Powers.swift │ └── Statistics │ │ ├── Normalization.swift │ │ ├── Gaussian.swift │ │ ├── Random.swift │ │ └── Moments.swift │ ├── Core │ ├── Conversions.swift │ ├── Functors.swift │ └── Wrappers.swift │ ├── Shape.swift │ ├── Matrix.swift │ └── ComplexMatrix.swift ├── Package.swift ├── Plinth.podspec ├── LICENSE ├── .gitignore └── README.md /Sources/Plinth/Extensions/Linear Algebra/Ones.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ones.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 14/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar: Numeric { 11 | 12 | public static func ones(shape: Shape) -> Matrix { 13 | return .init(shape: shape, repeating: 1) 14 | } 15 | 16 | } 17 | 18 | extension ComplexMatrix where Scalar: Numeric { 19 | 20 | public static func ones(shape: Shape) -> ComplexMatrix { 21 | return ComplexMatrix(real: .ones(shape: shape), imaginary: .zeros(shape: shape)) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Zeros.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Zeros.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 14/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar: Numeric { 11 | 12 | public static func zeros(shape: Shape) -> Matrix { 13 | return .init(shape: shape, repeating: 0) 14 | } 15 | 16 | } 17 | 18 | extension ComplexMatrix where Scalar: Numeric { 19 | 20 | public static func zeros(shape: Shape) -> ComplexMatrix { 21 | return ComplexMatrix(real: .zeros(shape: shape), imaginary: .zeros(shape: shape)) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Exponentiation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Exponents.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 28/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension ComplexMatrix where Scalar == Float { 12 | 13 | public func exp() -> ComplexMatrix { 14 | return real.exp() * ComplexMatrix(real: imaginary.cos(), imaginary: imaginary.sin()) 15 | } 16 | 17 | } 18 | 19 | extension ComplexMatrix where Scalar == Double { 20 | 21 | public func exp() -> ComplexMatrix { 22 | return real.exp() * ComplexMatrix(real: imaginary.cos(), imaginary: imaginary.sin()) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Identity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Identity.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 8/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar: Numeric { 11 | 12 | public static func identity(shape: Shape) -> Matrix { 13 | return .init(shape: shape) { row, column in 14 | return row == column ? 1 : 0 15 | } 16 | } 17 | 18 | } 19 | 20 | extension ComplexMatrix where Scalar: Numeric { 21 | 22 | public static func identity(shape: Shape) -> ComplexMatrix { 23 | return .init(shape: shape) { row, column in 24 | return row == column ? 1 : 0 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 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: "Plinth", 8 | platforms: [ 9 | .macOS(.v13), 10 | .iOS(.v16) 11 | ], 12 | products: [ 13 | .library(name: "Plinth", targets: ["Plinth"]) 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/apple/swift-numerics", from: "1.0.0") 17 | ], 18 | targets: [ 19 | .target( 20 | name: "Plinth", 21 | dependencies: [ 22 | .product(name: "Numerics", package: "swift-numerics") 23 | ] 24 | ) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/SquareRoot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SquareRoot.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 30/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension ComplexMatrix where Scalar == Float { 11 | 12 | public func sqrt() -> ComplexMatrix { 13 | let phase = phase() 14 | return absolute().sqrt() * ComplexMatrix(real: (phase / 2.0).cos(), imaginary: (phase / 2.0).sin()) 15 | } 16 | 17 | } 18 | 19 | extension ComplexMatrix where Scalar == Double { 20 | 21 | public func sqrt() -> ComplexMatrix { 22 | let phase = phase() 23 | return absolute().sqrt() * ComplexMatrix(real: (phase / 2.0).cos(), imaginary: (phase / 2.0).sin()) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Plinth.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "Plinth" 3 | spec.version = "2.4.5" 4 | spec.summary = "Hardware-accelerated matrix/numeric programming library for Swift." 5 | # spec.description = "Hardware-accelerated matrix/numeric programming library for Swift." 6 | spec.homepage = "https://github.com/dclelland/Plinth" 7 | spec.license = "MIT" 8 | spec.author = { "Daniel Clelland" => "daniel.clelland@gmail.com" } 9 | spec.ios.deployment_target = "14.0" 10 | spec.osx.deployment_target = "12.0" 11 | spec.source = { :git => "https://github.com/dclelland/Plinth.git", :tag => "2.4.5" } 12 | spec.source_files = "Sources/Plinth/**/*.swift" 13 | # spec.framework = "Accelerate" 14 | # spec.dependency "swift-numerics", "~> 1.4" 15 | end -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/FFTShift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FFTShift.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 9/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar == Float { 11 | 12 | public func fftShifted() -> Matrix { 13 | return shifted(rows: shape.rows / 2, columns: shape.columns / 2) 14 | } 15 | 16 | } 17 | 18 | extension ComplexMatrix where Scalar == Float { 19 | 20 | public func fftShifted() -> ComplexMatrix { 21 | return ComplexMatrix(real: real.fftShifted(), imaginary: imaginary.fftShifted()) 22 | } 23 | 24 | } 25 | 26 | extension Matrix where Scalar == Double { 27 | 28 | public func fftShifted() -> Matrix { 29 | return shifted(rows: shape.rows / 2, columns: shape.columns / 2) 30 | } 31 | 32 | } 33 | 34 | extension ComplexMatrix where Scalar == Double { 35 | 36 | public func fftShifted() -> ComplexMatrix { 37 | return ComplexMatrix(real: real.fftShifted(), imaginary: imaginary.fftShifted()) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Roots.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Roots.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 5/05/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | import Numerics 11 | 12 | extension Matrix where Scalar == Float { 13 | 14 | public func roots() throws -> ComplexMatrix { 15 | precondition(shape.isRow) 16 | var companion: Matrix = .diagonal(length: count - 2, index: -1) 17 | companion[row: 0] = -self[0, 1...shape.columnIndices.upperBound] / self[0, 0] 18 | return try companion.eigendecomposition(computing: .eigenvalues).eigenvalues 19 | } 20 | 21 | } 22 | 23 | extension Matrix where Scalar == Double { 24 | 25 | public func roots() throws -> ComplexMatrix { 26 | precondition(shape.isRow) 27 | var companion: Matrix = .diagonal(length: count - 2, index: -1) 28 | companion[row: 0] = -self[0, 1...shape.columnIndices.upperBound] / self[0, 0] 29 | return try companion.eigendecomposition(computing: .eigenvalues).eigenvalues 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Daniel Clelland 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 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Diagonal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Matrix.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 5/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar: Numeric { 11 | 12 | public static func diagonal(length: Int, index: Int = 0) -> Matrix { 13 | return .diagonal(vector: .init(repeating: 1, count: length), index: index) 14 | } 15 | 16 | public static func diagonal(vector: [Scalar], index: Int = 0) -> Matrix { 17 | return .init(shape: .square(length: vector.count + abs(index))) { row, column in 18 | return column - row == index ? vector[Swift.min(row, column)] : 0 19 | } 20 | } 21 | 22 | } 23 | 24 | extension ComplexMatrix where Scalar: Numeric { 25 | 26 | public static func diagonal(length: Int, index: Int = 0) -> ComplexMatrix { 27 | return .diagonal(vector: .init(repeating: 1, count: length), index: index) 28 | } 29 | 30 | public static func diagonal(vector: [Complex], index: Int = 0) -> ComplexMatrix { 31 | return .init(shape: .square(length: vector.count + abs(index))) { row, column in 32 | return column - row == index ? vector[Swift.min(row, column)] : 0 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/FFT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FFT.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 26/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | public enum FFT { 12 | 13 | public typealias Setup = OpaquePointer 14 | 15 | } 16 | 17 | extension FFT where Scalar == Float { 18 | 19 | public static func createSetup(shape: Shape) -> Setup { 20 | let log2N = vDSP_Length(log2(Scalar(shape.length))) 21 | return vDSP_create_fftsetup(log2N, FFTRadix(kFFTRadix2))! 22 | } 23 | 24 | public static func destroySetup(_ setup: Setup) { 25 | vDSP_destroy_fftsetup(setup) 26 | } 27 | 28 | } 29 | 30 | extension FFT where Scalar == Double { 31 | 32 | public static func createSetup(shape: Shape) -> Setup { 33 | let log2N = vDSP_Length(log2(Scalar(shape.length))) 34 | return vDSP_create_fftsetupD(log2N, FFTRadix(kFFTRadix2))! 35 | } 36 | 37 | public static func destroySetup(_ setup: Setup) { 38 | vDSP_destroy_fftsetupD(setup) 39 | } 40 | 41 | } 42 | 43 | extension FFTDirection { 44 | 45 | public static let forward = FFTDirection(kFFTDirection_Forward) 46 | 47 | public static let inverse = FFTDirection(kFFTDirection_Inverse) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Products.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Products.swift 3 | // Products 4 | // 5 | // Created by Daniel Clelland on 28/04/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar == Float { 11 | 12 | public func innerProduct() -> Matrix { 13 | return transposed() <*> self 14 | } 15 | 16 | public func outerProduct() -> Matrix { 17 | return self <*> transposed() 18 | } 19 | 20 | } 21 | 22 | extension ComplexMatrix where Scalar == Float { 23 | 24 | public func innerProduct() -> ComplexMatrix { 25 | return transposed() <*> self 26 | } 27 | 28 | public func outerProduct() -> ComplexMatrix { 29 | return self <*> transposed() 30 | } 31 | 32 | } 33 | 34 | extension Matrix where Scalar == Double { 35 | 36 | public func innerProduct() -> Matrix { 37 | return transposed() <*> self 38 | } 39 | 40 | public func outerProduct() -> Matrix { 41 | return self <*> transposed() 42 | } 43 | 44 | } 45 | 46 | extension ComplexMatrix where Scalar == Double { 47 | 48 | public func innerProduct() -> ComplexMatrix { 49 | return transposed() <*> self 50 | } 51 | 52 | public func outerProduct() -> ComplexMatrix { 53 | return self <*> transposed() 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Transposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Transposition.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 25/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public func transposed() -> Matrix { 14 | var output: Matrix = .zeros(shape: .init(rows: shape.columns, columns: shape.rows)) 15 | vDSP_mtrans(elements, 1, &output.elements, 1, vDSP_Length(shape.columns), vDSP_Length(shape.rows)) 16 | return output 17 | } 18 | 19 | } 20 | 21 | extension ComplexMatrix where Scalar == Float { 22 | 23 | public func transposed() -> ComplexMatrix { 24 | return ComplexMatrix(real: real.transposed(), imaginary: imaginary.transposed()) 25 | } 26 | 27 | } 28 | 29 | extension Matrix where Scalar == Double { 30 | 31 | public func transposed() -> Matrix { 32 | var output: Matrix = .zeros(shape: .init(rows: shape.columns, columns: shape.rows)) 33 | vDSP_mtransD(elements, 1, &output.elements, 1, vDSP_Length(shape.columns), vDSP_Length(shape.rows)) 34 | return output 35 | } 36 | 37 | } 38 | 39 | extension ComplexMatrix where Scalar == Double { 40 | 41 | public func transposed() -> ComplexMatrix { 42 | return ComplexMatrix(real: real.transposed(), imaginary: imaginary.transposed()) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Transformations/Center.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Center.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 26/04/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct CenterRoundingRule { 11 | 12 | internal let row: FloatingPointRoundingRule 13 | internal let column: FloatingPointRoundingRule 14 | 15 | public static let towardsTopLeft = CenterRoundingRule(row: .down, column: .down) 16 | public static let towardsTopRight = CenterRoundingRule(row: .down, column: .up) 17 | public static let towardsBottomLeft = CenterRoundingRule(row: .up, column: .down) 18 | public static let towardsBottomRight = CenterRoundingRule(row: .up, column: .up) 19 | 20 | } 21 | 22 | extension Shape { 23 | 24 | public func center(_ rule: CenterRoundingRule = .towardsTopLeft) -> (row: Int, column: Int) { 25 | return ( 26 | row: Int((Double(rows - 1) / 2.0).rounded(rule.row)), 27 | column: Int((Double(columns - 1) / 2.0).rounded(rule.column)) 28 | ) 29 | } 30 | 31 | } 32 | 33 | extension Matrix { 34 | 35 | public func center(_ rule: CenterRoundingRule = .towardsTopLeft) -> Scalar { 36 | let center = shape.center(rule) 37 | return self[center.row, center.column] 38 | } 39 | 40 | } 41 | 42 | extension ComplexMatrix { 43 | 44 | public func center(_ rule: CenterRoundingRule = .towardsTopLeft) -> Complex { 45 | let center = shape.center(rule) 46 | return self[center.row, center.column] 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/Autocorrelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Autocorrelation.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 26/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | 12 | extension Matrix where Scalar == Float { 13 | 14 | public func autocorrelated(setup: FFT.Setup? = nil) -> ComplexMatrix { 15 | let frequencies = fft2D(setup: setup) 16 | let correlated = frequencies * frequencies.conjugate() 17 | return correlated.ifft2D(setup: setup) / Scalar(shape.count) 18 | } 19 | 20 | public func magnitudeAutocorrelated(setup: FFT.Setup? = nil) -> ComplexMatrix { 21 | let frequencies = fft2D(setup: setup) 22 | let correlated = frequencies * frequencies.absolute() 23 | return correlated.ifft2D(setup: setup) / Scalar(shape.count) 24 | } 25 | 26 | } 27 | 28 | extension Matrix where Scalar == Double { 29 | 30 | public func autocorrelated(setup: FFT.Setup? = nil) -> ComplexMatrix { 31 | let frequencies = fft2D(setup: setup) 32 | let correlated = frequencies * frequencies.conjugate() 33 | return correlated.ifft2D(setup: setup) / Scalar(shape.count) 34 | } 35 | 36 | public func magnitudeAutocorrelated(setup: FFT.Setup? = nil) -> ComplexMatrix { 37 | let frequencies = fft2D(setup: setup) 38 | let correlated = frequencies * frequencies.absolute() 39 | return correlated.ifft2D(setup: setup) / Scalar(shape.count) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/Convolution1D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // Plinth 4 | // 5 | // Created by June Russell on 10/03/2025. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public func convolve1D(filter: [Element]) -> Matrix { 14 | let input = self 15 | var output: Matrix = .zeros(shape: shape) 16 | input.withUnsafeBufferPointer { inputVector in 17 | output.withUnsafeMutableBufferPointer { outputVector in 18 | filter.withUnsafeBufferPointer { filterVector in 19 | vDSP_conv(inputVector.baseAddress!, vDSP_Stride(1), filterVector.baseAddress!, vDSP_Stride(1), outputVector.baseAddress!, vDSP_Stride(1), vDSP_Length(shape.count), vDSP_Length(filter.count)) 20 | } 21 | } 22 | } 23 | return output 24 | } 25 | 26 | } 27 | 28 | extension Matrix where Scalar == Double { 29 | 30 | public func convolve1D(filter: [Element]) -> Matrix { 31 | let input = self 32 | var output: Matrix = .zeros(shape: shape) 33 | input.withUnsafeBufferPointer { inputVector in 34 | output.withUnsafeMutableBufferPointer { outputVector in 35 | filter.withUnsafeBufferPointer { filterVector in 36 | vDSP_convD(inputVector.baseAddress!, vDSP_Stride(1), filterVector.baseAddress!, vDSP_Stride(1), outputVector.baseAddress!, vDSP_Stride(1), vDSP_Length(shape.count), vDSP_Length(filter.count)) 37 | } 38 | } 39 | } 40 | return output 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Transformations/Repeat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Repeat.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 28/02/2025. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar == Float { 11 | 12 | public func repeated(rows: Int) -> Matrix { 13 | let elements = Array([[Scalar]](repeating: elements, count: rows).joined()) 14 | return Matrix(shape: .init(rows: rows, columns: shape.count), elements: elements) 15 | } 16 | 17 | public func repeated(columns: Int) -> Matrix { 18 | return repeated(rows: columns).transposed() 19 | } 20 | 21 | } 22 | 23 | extension ComplexMatrix where Scalar == Float { 24 | 25 | public func repeated(rows: Int) -> ComplexMatrix { 26 | return ComplexMatrix(real: real.repeated(rows: rows), imaginary: imaginary.repeated(rows: rows)) 27 | } 28 | 29 | public func repeated(columns: Int) -> ComplexMatrix { 30 | return ComplexMatrix(real: real.repeated(columns: columns), imaginary: imaginary.repeated(columns: columns)) 31 | } 32 | 33 | } 34 | 35 | extension Matrix where Scalar == Double { 36 | 37 | public func repeated(rows: Int) -> Matrix { 38 | let elements = Array([[Scalar]](repeating: elements, count: rows).joined()) 39 | return Matrix(shape: .init(rows: rows, columns: shape.count), elements: elements) 40 | } 41 | 42 | public func repeated(columns: Int) -> Matrix { 43 | return repeated(rows: columns).transposed() 44 | } 45 | 46 | } 47 | 48 | extension ComplexMatrix where Scalar == Double { 49 | 50 | public func repeated(rows: Int) -> ComplexMatrix { 51 | return ComplexMatrix(real: real.repeated(rows: rows), imaginary: imaginary.repeated(rows: rows)) 52 | } 53 | 54 | public func repeated(columns: Int) -> ComplexMatrix { 55 | return ComplexMatrix(real: real.repeated(columns: columns), imaginary: imaginary.repeated(columns: columns)) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Mathematics/Interpolation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Interpolation.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 5/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar == Float { 11 | 12 | public func linearInterpolated(to y0: Scalar, _ y1: Scalar) -> Matrix { 13 | return self * (y1 - y0) + y0 14 | } 15 | 16 | public func inverseLinearInterpolated(from x0: Scalar, _ x1: Scalar) -> Matrix { 17 | guard x0 != x1 else { 18 | return .init(shape: shape, repeating: x0) 19 | } 20 | 21 | return (self - x0) / (x1 - x0) 22 | } 23 | 24 | } 25 | 26 | extension Matrix where Scalar == Float { 27 | 28 | public func linearInterpolated(to destination: ClosedRange) -> Matrix { 29 | return linearInterpolated(to: destination.lowerBound, destination.upperBound) 30 | } 31 | 32 | public func inverseLinearInterpolated(from origin: ClosedRange) -> Matrix { 33 | return inverseLinearInterpolated(from: origin.lowerBound, origin.upperBound) 34 | } 35 | 36 | } 37 | 38 | extension Matrix where Scalar == Double { 39 | 40 | public func linearInterpolated(to y0: Scalar, _ y1: Scalar) -> Matrix { 41 | return self * (y1 - y0) + y0 42 | } 43 | 44 | public func inverseLinearInterpolated(from x0: Scalar, _ x1: Scalar) -> Matrix { 45 | guard x0 != x1 else { 46 | return .init(shape: shape, repeating: x0) 47 | } 48 | 49 | return (self - x0) / (x1 - x0) 50 | } 51 | 52 | } 53 | 54 | extension Matrix where Scalar == Double { 55 | 56 | public func linearInterpolated(to destination: ClosedRange) -> Matrix { 57 | return linearInterpolated(to: destination.lowerBound, destination.upperBound) 58 | } 59 | 60 | public func inverseLinearInterpolated(from origin: ClosedRange) -> Matrix { 61 | return inverseLinearInterpolated(from: origin.lowerBound, origin.upperBound) 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Circulant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Circulant.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 10/03/2025. 6 | // 7 | 8 | import Foundation 9 | import Plinth 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public static func circulant(vector: [Scalar]) -> Matrix { 14 | return .init(shape: .square(length: vector.count)) { row, column in 15 | let index = (row - column) % vector.count 16 | return index < 0 ? vector[index + vector.count] : vector[index] 17 | if index < 0 { 18 | return vector[vector.count + index] 19 | } 20 | } 21 | } 22 | 23 | } 24 | 25 | extension ComplexMatrix where Scalar == Float { 26 | 27 | public static func circulant(vector: [Complex]) -> ComplexMatrix { 28 | return .init(shape: .square(length: vector.count)) { row, column in 29 | let index = (row - column) % vector.count 30 | return index < 0 ? vector[index + vector.count] : vector[index] 31 | if index < 0 { 32 | return vector[vector.count + index] 33 | } 34 | } 35 | } 36 | 37 | } 38 | 39 | extension Matrix where Scalar == Double { 40 | 41 | public static func circulant(vector: [Scalar]) -> Matrix { 42 | return .init(shape: .square(length: vector.count)) { row, column in 43 | let index = (row - column) % vector.count 44 | return index < 0 ? vector[index + vector.count] : vector[index] 45 | if index < 0 { 46 | return vector[vector.count + index] 47 | } 48 | } 49 | } 50 | 51 | } 52 | 53 | extension ComplexMatrix where Scalar == Double { 54 | 55 | public static func circulant(vector: [Complex]) -> ComplexMatrix { 56 | return .init(shape: .square(length: vector.count)) { row, column in 57 | let index = (row - column) % vector.count 58 | return index < 0 ? vector[index + vector.count] : vector[index] 59 | if index < 0 { 60 | return vector[vector.count + index] 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Transformations/Reverse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reverse.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 07/03/2025. 6 | // 7 | 8 | import Foundation 9 | import Plinth 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public func reversedRows() -> Matrix { 14 | var output = self 15 | for row in output.shape.rowIndices { 16 | output[row: row] = output[row: row].reversed() 17 | } 18 | return output 19 | } 20 | 21 | public func reversedColumns() -> Matrix { 22 | var output = self 23 | for column in output.shape.columnIndices { 24 | output[column: column] = output[column: column].reversed() 25 | } 26 | return output 27 | } 28 | 29 | } 30 | 31 | extension ComplexMatrix where Scalar == Float { 32 | 33 | public func reversedRows() -> ComplexMatrix { 34 | return ComplexMatrix(real: real.reversedRows(), imaginary: imaginary.reversedRows()) 35 | } 36 | 37 | public func reversedColumns() -> ComplexMatrix { 38 | return ComplexMatrix(real: real.reversedColumns(), imaginary: imaginary.reversedColumns()) 39 | } 40 | 41 | } 42 | 43 | extension Matrix where Scalar == Double { 44 | 45 | public func reversedRows() -> Matrix { 46 | var output = self 47 | for row in output.shape.rowIndices { 48 | output[row: row] = output[row: row].reversed() 49 | } 50 | return output 51 | } 52 | 53 | public func reversedColumns() -> Matrix { 54 | var output = self 55 | for column in output.shape.columnIndices { 56 | output[column: column] = output[column: column].reversed() 57 | } 58 | return output 59 | } 60 | 61 | } 62 | 63 | extension ComplexMatrix where Scalar == Double { 64 | 65 | public func reversedRows() -> ComplexMatrix { 66 | return ComplexMatrix(real: real.reversedRows(), imaginary: imaginary.reversedRows()) 67 | } 68 | 69 | public func reversedColumns() -> ComplexMatrix { 70 | return ComplexMatrix(real: real.reversedColumns(), imaginary: imaginary.reversedColumns()) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Statistics/Normalization.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Normalization.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 5/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar == Float { 11 | 12 | public func normalized() -> Matrix { 13 | return inverseLinearInterpolated(from: minimum()...maximum()) 14 | } 15 | 16 | public func normalized(to destination: ClosedRange) -> Matrix { 17 | return inverseLinearInterpolated(from: minimum()...maximum()).linearInterpolated(to: destination) 18 | } 19 | 20 | public func normalized(from origin: ClosedRange) -> Matrix { 21 | return inverseLinearInterpolated(from: origin) 22 | } 23 | 24 | public func normalized(from origin: ClosedRange, to destination: ClosedRange) -> Matrix { 25 | return inverseLinearInterpolated(from: origin).linearInterpolated(to: destination) 26 | } 27 | 28 | } 29 | 30 | extension Matrix where Scalar == Float { 31 | 32 | public func meanNormalized() -> Matrix { 33 | return meanNormalized(mean: mean()) 34 | } 35 | 36 | public func meanNormalized(mean: Scalar) -> Matrix { 37 | return self - mean 38 | } 39 | 40 | } 41 | 42 | extension Matrix where Scalar == Double { 43 | 44 | public func normalized() -> Matrix { 45 | return inverseLinearInterpolated(from: minimum()...maximum()) 46 | } 47 | 48 | public func normalized(to destination: ClosedRange) -> Matrix { 49 | return inverseLinearInterpolated(from: minimum()...maximum()).linearInterpolated(to: destination) 50 | } 51 | 52 | public func normalized(from origin: ClosedRange) -> Matrix { 53 | return inverseLinearInterpolated(from: origin) 54 | } 55 | 56 | public func normalized(from origin: ClosedRange, to destination: ClosedRange) -> Matrix { 57 | return inverseLinearInterpolated(from: origin).linearInterpolated(to: destination) 58 | } 59 | 60 | } 61 | 62 | extension Matrix where Scalar == Double { 63 | 64 | public func meanNormalized() -> Matrix { 65 | return meanNormalized(mean: mean()) 66 | } 67 | 68 | public func meanNormalized(mean: Scalar) -> Matrix { 69 | return self - mean 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Division.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Division.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 4/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | infix operator />: MultiplicationPrecedence 11 | 12 | infix operator =: AssignmentPrecedence 15 | 16 | infix operator (left: Matrix, right: Matrix) throws -> Matrix { 21 | return try left.leftDivided(by: right) 22 | } 23 | 24 | @inlinable public static func Matrix { 25 | return try left.rightDivided(by: right) 26 | } 27 | 28 | } 29 | 30 | extension Matrix where Scalar == Float { 31 | 32 | @inlinable public static func />= (left: inout Matrix, right: Matrix) throws { 33 | left = try left right 38 | } 39 | 40 | } 41 | 42 | extension Matrix where Scalar == Float { 43 | 44 | public func leftDivided(by dividend: Matrix) throws -> Matrix { 45 | return try inverted() <*> dividend 46 | } 47 | 48 | public func rightDivided(by divisor: Matrix) throws -> Matrix { 49 | return try divisor <*> inverted() 50 | } 51 | 52 | } 53 | 54 | extension Matrix where Scalar == Double { 55 | 56 | @inlinable public static func /> (left: Matrix, right: Matrix) throws -> Matrix { 57 | return try left.leftDivided(by: right) 58 | } 59 | 60 | @inlinable public static func Matrix { 61 | return try left.rightDivided(by: right) 62 | } 63 | 64 | } 65 | 66 | extension Matrix where Scalar == Double { 67 | 68 | @inlinable public static func />= (left: inout Matrix, right: Matrix) throws { 69 | try left = left right 74 | } 75 | 76 | } 77 | 78 | extension Matrix where Scalar == Double { 79 | 80 | public func leftDivided(by dividend: Matrix) throws -> Matrix { 81 | return try inverted() <*> dividend 82 | } 83 | 84 | public func rightDivided(by divisor: Matrix) throws -> Matrix { 85 | return try divisor <*> inverted() 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## User settings 9 | xcuserdata/ 10 | 11 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 12 | *.xcscmblueprint 13 | *.xccheckout 14 | 15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 16 | build/ 17 | DerivedData/ 18 | *.moved-aside 19 | *.pbxuser 20 | !default.pbxuser 21 | *.mode1v3 22 | !default.mode1v3 23 | *.mode2v3 24 | !default.mode2v3 25 | *.perspectivev3 26 | !default.perspectivev3 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | 31 | ## App packaging 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | ## Playgrounds 37 | timeline.xctimeline 38 | playground.xcworkspace 39 | 40 | # Swift Package Manager 41 | # 42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 43 | # Packages/ 44 | # Package.pins 45 | # Package.resolved 46 | # *.xcodeproj 47 | # 48 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 49 | # hence it is not needed unless you have added a package configuration file to your project 50 | .swiftpm 51 | 52 | .build/ 53 | 54 | # CocoaPods 55 | # 56 | # We recommend against adding the Pods directory to your .gitignore. However 57 | # you should judge for yourself, the pros and cons are mentioned at: 58 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 59 | # 60 | # Pods/ 61 | # 62 | # Add this line if you want to avoid checking in source code from the Xcode workspace 63 | # *.xcworkspace 64 | 65 | # Carthage 66 | # 67 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 68 | # Carthage/Checkouts 69 | 70 | Carthage/Build/ 71 | 72 | # Accio dependency management 73 | Dependencies/ 74 | .accio/ 75 | 76 | # fastlane 77 | # 78 | # It is recommended to not store the screenshots in the git repo. 79 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 80 | # For more information about the recommended setup visit: 81 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 82 | 83 | fastlane/report.xml 84 | fastlane/Preview.html 85 | fastlane/screenshots/**/*.png 86 | fastlane/test_output 87 | 88 | # Code Injection 89 | # 90 | # After new code Injection tools there's a generated folder /iOSInjectionProject 91 | # https://github.com/johnno1962/injectionforxcode 92 | 93 | iOSInjectionProject/ 94 | -------------------------------------------------------------------------------- /Sources/Plinth/Core/Conversions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Conversions.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 30/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public init(_ matrix: Matrix) { 14 | self = matrix.fmap(vDSP.convertElements) 15 | } 16 | 17 | public init(_ matrix: Matrix) { 18 | self = matrix.fmap(vDSP.convertElements) 19 | } 20 | 21 | public init(_ matrix: Matrix) { 22 | self = matrix.fmap(vDSP.convertElements) 23 | } 24 | 25 | public init(_ matrix: Matrix) { 26 | self = matrix.fmap(vDSP.convertElements) 27 | } 28 | 29 | public init(_ matrix: Matrix) { 30 | self = matrix.fmap(vDSP.convertElements) 31 | } 32 | 33 | public init(_ matrix: Matrix) { 34 | self = matrix.fmap(vDSP.convertElements) 35 | } 36 | 37 | } 38 | 39 | extension Matrix where Scalar: vDSP_IntegerConvertable { 40 | 41 | public init(_ matrix: Matrix, rounding: vDSP.RoundingMode) { 42 | self = matrix.fmap { vDSP.floatingPointToInteger($0, integerType: Scalar.self, rounding: rounding) } 43 | } 44 | 45 | } 46 | 47 | extension Matrix where Scalar == Float { 48 | 49 | public init(_ matrix: Matrix) { 50 | self = matrix.fmap(vDSP.doubleToFloat) 51 | } 52 | 53 | } 54 | 55 | extension Matrix where Scalar == Double { 56 | 57 | public init(_ matrix: Matrix) { 58 | self = matrix.fmap(vDSP.convertElements) 59 | } 60 | 61 | public init(_ matrix: Matrix) { 62 | self = matrix.fmap(vDSP.convertElements) 63 | } 64 | 65 | public init(_ matrix: Matrix) { 66 | self = matrix.fmap(vDSP.convertElements) 67 | } 68 | 69 | public init(_ matrix: Matrix) { 70 | self = matrix.fmap(vDSP.convertElements) 71 | } 72 | 73 | public init(_ matrix: Matrix) { 74 | self = matrix.fmap(vDSP.convertElements) 75 | } 76 | 77 | public init(_ matrix: Matrix) { 78 | self = matrix.fmap(vDSP.convertElements) 79 | } 80 | 81 | } 82 | 83 | extension Matrix where Scalar: vDSP_IntegerConvertable { 84 | 85 | public init(_ matrix: Matrix, rounding: vDSP.RoundingMode) { 86 | self = matrix.fmap { vDSP.floatingPointToInteger($0, integerType: Scalar.self, rounding: rounding) } 87 | } 88 | 89 | } 90 | 91 | extension Matrix where Scalar == Double { 92 | 93 | public init(_ matrix: Matrix) { 94 | self = matrix.fmap(vDSP.floatToDouble) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Statistics/Gaussian.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Gaussian.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 3/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Float { 11 | 12 | internal static func gaussian() -> Self { 13 | var generator = SystemRandomNumberGenerator() 14 | return gaussian(using: &generator) 15 | } 16 | 17 | internal static func gaussian(using generator: inout T) -> Self where T: RandomNumberGenerator { 18 | var s: Self = 0.0 19 | var x: Self = 0.0 20 | var y: Self = 0.0 21 | while true { 22 | x = .random(in: -1.0...1.0, using: &generator) 23 | y = .random(in: -1.0...1.0, using: &generator) 24 | s = x * x + y * y 25 | if s < 1.0 { break } 26 | } 27 | return x * (-2.0 * log(s) / s).squareRoot() 28 | } 29 | 30 | } 31 | 32 | extension Matrix where Scalar == Float { 33 | 34 | public static func gaussian(shape: Shape, mean: Scalar = 0.0, standardDeviation: Scalar = 1.0) -> Matrix { 35 | return .init(shape: shape, Scalar.gaussian()) * standardDeviation + mean 36 | } 37 | 38 | public static func gaussian(shape: Shape, mean: Scalar = 0.0, standardDeviation: Scalar = 1.0, using generator: inout T) -> Matrix where T: RandomNumberGenerator { 39 | return .init(shape: shape, Scalar.gaussian(using: &generator)) * standardDeviation + mean 40 | } 41 | 42 | } 43 | 44 | extension Double { 45 | 46 | internal static func gaussian() -> Self { 47 | var generator = SystemRandomNumberGenerator() 48 | return gaussian(using: &generator) 49 | } 50 | 51 | internal static func gaussian(using generator: inout T) -> Self where T: RandomNumberGenerator { 52 | var s: Self = 0.0 53 | var x: Self = 0.0 54 | var y: Self = 0.0 55 | while true { 56 | x = .random(in: -1.0...1.0, using: &generator) 57 | y = .random(in: -1.0...1.0, using: &generator) 58 | s = x * x + y * y 59 | if s < 1.0 { break } 60 | } 61 | return x * (-2.0 * log(s) / s).squareRoot() 62 | } 63 | 64 | } 65 | 66 | extension Matrix where Scalar == Double { 67 | 68 | public static func gaussian(shape: Shape, mean: Scalar = 0.0, standardDeviation: Scalar = 1.0) -> Matrix { 69 | return .init(shape: shape, Scalar.gaussian()) * standardDeviation + mean 70 | } 71 | 72 | public static func gaussian(shape: Shape, mean: Scalar = 0.0, standardDeviation: Scalar = 1.0, using generator: inout T) -> Matrix where T: RandomNumberGenerator { 73 | return .init(shape: shape, Scalar.gaussian(using: &generator)) * standardDeviation + mean 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/Autoconvolution.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Autoconvolution.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 28/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar == Float { 11 | 12 | public func autoconvolved(setup: FFT.Setup? = nil) -> ComplexMatrix { 13 | let frequencies = fft2D(setup: setup) 14 | let convolved = frequencies * frequencies 15 | return convolved.ifft2D(setup: setup) / Scalar(shape.count) 16 | } 17 | 18 | public func magnitudeAutoconvolved(setup: FFT.Setup? = nil) -> ComplexMatrix { 19 | let frequencies = fft2D(setup: setup) 20 | let convolved = frequencies.squareMagnitudes() 21 | return convolved.ifft2D(setup: setup) / Scalar(shape.count) 22 | } 23 | 24 | } 25 | 26 | extension ComplexMatrix where Scalar == Float { 27 | 28 | public func autoconvolved(setup: FFT.Setup? = nil) -> ComplexMatrix { 29 | let frequencies = fft2D(setup: setup) 30 | let convolved = frequencies * frequencies 31 | return convolved.ifft2D(setup: setup) / Scalar(shape.count) 32 | } 33 | 34 | public func magnitudeAutoconvolved(setup: FFT.Setup? = nil) -> ComplexMatrix { 35 | let frequencies = fft2D(setup: setup) 36 | let convolved = frequencies.squareMagnitudes() 37 | return convolved.ifft2D(setup: setup) / Scalar(shape.count) 38 | } 39 | 40 | } 41 | 42 | extension Matrix where Scalar == Double { 43 | 44 | public func autoconvolved(setup: FFT.Setup? = nil) -> ComplexMatrix { 45 | let frequencies = fft2D(setup: setup) 46 | let convolved = frequencies * frequencies 47 | return convolved.ifft2D(setup: setup) / Scalar(shape.count) 48 | } 49 | 50 | public func magnitudeAutoconvolved(setup: FFT.Setup? = nil) -> ComplexMatrix { 51 | let frequencies = fft2D(setup: setup) 52 | let convolved = frequencies.squareMagnitudes() 53 | return convolved.ifft2D(setup: setup) / Scalar(shape.count) 54 | } 55 | 56 | } 57 | 58 | extension ComplexMatrix where Scalar == Double { 59 | 60 | public func autoconvolved(setup: FFT.Setup? = nil) -> ComplexMatrix { 61 | let frequencies = fft2D(setup: setup) 62 | let convolved = frequencies * frequencies 63 | return convolved.ifft2D(setup: setup) / Scalar(shape.count) 64 | } 65 | 66 | public func magnitudeAutoconvolved(setup: FFT.Setup? = nil) -> ComplexMatrix { 67 | let frequencies = fft2D(setup: setup) 68 | let convolved = frequencies.squareMagnitudes() 69 | return convolved.ifft2D(setup: setup) / Scalar(shape.count) 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/Resampling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Resampling.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 06/03/2025. 6 | // 7 | 8 | import Plinth 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public func upsampled(factor: Int = 2) -> Matrix { 14 | var elements = Matrix.zeros(shape: .init(rows: shape.count, columns: factor)) 15 | elements[column: 0] = asColumn() 16 | return elements.reshaped(.init(rows: shape.rows, columns: shape.columns * factor)) 17 | } 18 | 19 | public func downsampled(factor: Int = 2, filter: [Scalar] = [1.0]) -> Matrix { 20 | let coefficients = [Scalar](repeating: 1.0 / Scalar(factor), count: factor) 21 | let elements = vDSP.downsample(elements, decimationFactor: factor, filter: filter) 22 | return Matrix(shape: .init(rows: shape.rows, columns: shape.columns / factor), elements: elements) 23 | } 24 | 25 | } 26 | 27 | extension ComplexMatrix where Scalar == Float { 28 | 29 | public func upsampled(factor: Int = 2) -> ComplexMatrix { 30 | return ComplexMatrix( 31 | real: real.upsampled(factor: factor), 32 | imaginary: imaginary.upsampled(factor: factor) 33 | ) 34 | } 35 | 36 | public func downsampled(factor: Int = 2, filter: [Scalar] = [1.0]) -> ComplexMatrix { 37 | return ComplexMatrix( 38 | real: real.downsampled(factor: factor, filter: filter), 39 | imaginary: imaginary.downsampled(factor: factor, filter: filter) 40 | ) 41 | } 42 | 43 | } 44 | 45 | extension Matrix where Scalar == Double { 46 | 47 | public func upsampled(factor: Int = 2) -> Matrix { 48 | var elements = Matrix.zeros(shape: .init(rows: shape.count, columns: factor)) 49 | elements[column: 0] = asColumn() 50 | return elements.reshaped(.init(rows: shape.rows, columns: shape.columns * factor)) 51 | } 52 | 53 | public func downsampled(factor: Int = 2, filter: [Scalar] = [1.0]) -> Matrix { 54 | let coefficients = [Scalar](repeating: 1.0 / Scalar(factor), count: factor) 55 | let elements = vDSP.downsample(elements, decimationFactor: factor, filter: filter) 56 | return Matrix(shape: .init(rows: shape.rows, columns: shape.columns / factor), elements: elements) 57 | } 58 | 59 | } 60 | 61 | extension ComplexMatrix where Scalar == Double { 62 | 63 | public func upsampled(factor: Int = 2) -> ComplexMatrix { 64 | return ComplexMatrix( 65 | real: real.upsampled(factor: factor), 66 | imaginary: imaginary.upsampled(factor: factor) 67 | ) 68 | } 69 | 70 | public func downsampled(factor: Int = 2, filter: [Scalar] = [1.0]) -> ComplexMatrix { 71 | return ComplexMatrix( 72 | real: real.downsampled(factor: factor, filter: filter), 73 | imaginary: imaginary.downsampled(factor: factor, filter: filter) 74 | ) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/FFTRamp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FFTRamp.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 28/08/24. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar == Float { 11 | 12 | public static func centeredXRamp(shape: Shape) -> Matrix { 13 | let width = shape.columns / 2 14 | let range = Scalar(-width)...Scalar(width - 1) 15 | return Matrix.xRamp(shape: shape, range: range) 16 | } 17 | 18 | public static func centeredYRamp(shape: Shape) -> Matrix { 19 | let height = shape.rows / 2 20 | let range = Scalar(-height)...Scalar(height - 1) 21 | return Matrix.yRamp(shape: shape, range: range) 22 | } 23 | 24 | } 25 | 26 | extension Matrix where Scalar == Float { 27 | 28 | public static func fftXRamp(shape: Shape) -> Matrix { 29 | return centeredXRamp(shape: shape).fftShifted() 30 | } 31 | 32 | public static func fftYRamp(shape: Shape) -> Matrix { 33 | return centeredYRamp(shape: shape).fftShifted() 34 | } 35 | 36 | } 37 | 38 | extension Matrix where Scalar == Float { 39 | 40 | public static func fftRadiusRamp(shape: Shape) -> Matrix { 41 | let xRamp = Matrix.fftXRamp(shape: shape) 42 | let yRamp = Matrix.fftYRamp(shape: shape) 43 | return xRamp.hypot(yRamp) 44 | } 45 | 46 | public static func fftAngleRamp(shape: Shape) -> Matrix { 47 | let xRamp = Matrix.fftXRamp(shape: shape) 48 | let yRamp = Matrix.fftYRamp(shape: shape) 49 | return xRamp.atan2(yRamp) 50 | } 51 | 52 | } 53 | 54 | extension Matrix where Scalar == Double { 55 | 56 | public static func centeredXRamp(shape: Shape) -> Matrix { 57 | let width = shape.columns / 2 58 | let range = Scalar(-width)...Scalar(width - 1) 59 | return Matrix.xRamp(shape: shape, range: range) 60 | } 61 | 62 | public static func centeredYRamp(shape: Shape) -> Matrix { 63 | let height = shape.rows / 2 64 | let range = Scalar(-height)...Scalar(height - 1) 65 | return Matrix.yRamp(shape: shape, range: range) 66 | } 67 | 68 | } 69 | 70 | extension Matrix where Scalar == Double { 71 | 72 | public static func fftXRamp(shape: Shape) -> Matrix { 73 | return centeredXRamp(shape: shape).fftShifted() 74 | } 75 | 76 | public static func fftYRamp(shape: Shape) -> Matrix { 77 | return centeredYRamp(shape: shape).fftShifted() 78 | } 79 | 80 | } 81 | 82 | extension Matrix where Scalar == Double { 83 | 84 | public static func fftRadiusRamp(shape: Shape) -> Matrix { 85 | let xRamp = Matrix.fftXRamp(shape: shape) 86 | let yRamp = Matrix.fftYRamp(shape: shape) 87 | return xRamp.hypot(yRamp) 88 | } 89 | 90 | public static func fftAngleRamp(shape: Shape) -> Matrix { 91 | let xRamp = Matrix.fftXRamp(shape: shape) 92 | let yRamp = Matrix.fftYRamp(shape: shape) 93 | return xRamp.atan2(yRamp) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Mathematics/Ramps.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Daniel Clelland on 15/04/24. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public static func xRamp(shape: Shape, range: ClosedRange) -> Matrix { 14 | let ramp = vDSP.ramp(in: range, count: shape.columns) 15 | let elements = Array([[Scalar]](repeating: ramp, count: shape.rows).joined()) 16 | return Matrix(shape: shape, elements: elements) 17 | } 18 | 19 | public static func yRamp(shape: Shape, range: ClosedRange) -> Matrix { 20 | let ramp = vDSP.ramp(in: range, count: shape.rows) 21 | let elements = Array(ramp.map({ [Scalar](repeating: $0, count: shape.columns) }).joined()) 22 | return Matrix(shape: shape, elements: elements) 23 | } 24 | 25 | } 26 | 27 | extension Matrix where Scalar == Float { 28 | 29 | public static func radiusRamp(shape: Shape, xRange: ClosedRange = -1.0...1.0, yRange: ClosedRange = -1.0...1.0) -> Matrix { 30 | let xRamp = xRamp(shape: shape, range: xRange) 31 | let yRamp = yRamp(shape: shape, range: yRange) 32 | return xRamp.hypot(yRamp) 33 | } 34 | 35 | public static func angleRamp(shape: Shape, xRange: ClosedRange = -1.0...1.0, yRange: ClosedRange = -1.0...1.0) -> Matrix { 36 | let xRamp = xRamp(shape: shape, range: xRange) 37 | let yRamp = yRamp(shape: shape, range: yRange) 38 | return xRamp.atan2(yRamp) 39 | } 40 | 41 | } 42 | 43 | extension Matrix where Scalar == Double { 44 | 45 | public static func xRamp(shape: Shape, range: ClosedRange) -> Matrix { 46 | let ramp = vDSP.ramp(in: range, count: shape.columns) 47 | let elements = Array([[Scalar]](repeating: ramp, count: shape.rows).joined()) 48 | return Matrix(shape: shape, elements: elements) 49 | } 50 | 51 | public static func yRamp(shape: Shape, range: ClosedRange) -> Matrix { 52 | let ramp = vDSP.ramp(in: range, count: shape.rows) 53 | let elements = Array(ramp.map({ [Scalar](repeating: $0, count: shape.columns) }).joined()) 54 | return Matrix(shape: shape, elements: elements) 55 | } 56 | 57 | } 58 | 59 | extension Matrix where Scalar == Double { 60 | 61 | public static func radiusRamp(shape: Shape, xRange: ClosedRange = -1.0...1.0, yRange: ClosedRange = -1.0...1.0) -> Matrix { 62 | let xRamp = xRamp(shape: shape, range: xRange) 63 | let yRamp = yRamp(shape: shape, range: yRange) 64 | return xRamp.hypot(yRamp) 65 | } 66 | 67 | public static func angleRamp(shape: Shape, xRange: ClosedRange = -1.0...1.0, yRange: ClosedRange = -1.0...1.0) -> Matrix { 68 | let xRamp = xRamp(shape: shape, range: xRange) 69 | let yRamp = yRamp(shape: shape, range: yRange) 70 | return xRamp.atan2(yRamp) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Transformations/Reshape.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reshape.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 4/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum ReshapeOrder { 11 | 12 | case rowMajor 13 | case columnMajor 14 | 15 | } 16 | 17 | extension Matrix where Scalar == Float { 18 | 19 | public func reshaped(_ shape: Shape, order: ReshapeOrder = .rowMajor) -> Matrix { 20 | precondition(self.shape.count == shape.count) 21 | switch order { 22 | case .rowMajor: 23 | return Matrix(shape: shape, elements: elements) 24 | case .columnMajor: 25 | return Matrix(shape: shape, elements: transposed().elements) 26 | } 27 | } 28 | 29 | } 30 | 31 | extension Matrix where Scalar == Float { 32 | 33 | public func asRow(order: ReshapeOrder = .rowMajor) -> Matrix { 34 | return reshaped(.row(length: shape.count), order: order) 35 | } 36 | 37 | public func asColumn(order: ReshapeOrder = .rowMajor) -> Matrix { 38 | return reshaped(.column(length: shape.count), order: order) 39 | } 40 | 41 | } 42 | 43 | extension ComplexMatrix where Scalar == Float { 44 | 45 | public func reshaped(_ shape: Shape, order: ReshapeOrder = .rowMajor) -> ComplexMatrix { 46 | precondition(self.shape.count == shape.count) 47 | return ComplexMatrix(real: real.reshaped(shape, order: order), imaginary: imaginary.reshaped(shape, order: order)) 48 | } 49 | 50 | } 51 | 52 | 53 | extension ComplexMatrix where Scalar == Float { 54 | 55 | public func asRow(order: ReshapeOrder = .rowMajor) -> ComplexMatrix { 56 | return reshaped(.row(length: shape.count), order: order) 57 | } 58 | 59 | public func asColumn(order: ReshapeOrder = .rowMajor) -> ComplexMatrix { 60 | return reshaped(.column(length: shape.count), order: order) 61 | } 62 | 63 | } 64 | 65 | extension Matrix where Scalar == Double { 66 | 67 | public func reshaped(_ shape: Shape, order: ReshapeOrder = .rowMajor) -> Matrix { 68 | precondition(self.shape.count == shape.count) 69 | switch order { 70 | case .rowMajor: 71 | return Matrix(shape: shape, elements: elements) 72 | case .columnMajor: 73 | return Matrix(shape: shape, elements: transposed().elements) 74 | } 75 | } 76 | 77 | } 78 | 79 | extension Matrix where Scalar == Double { 80 | 81 | public func asRow(order: ReshapeOrder = .rowMajor) -> Matrix { 82 | return reshaped(.row(length: shape.count), order: order) 83 | } 84 | 85 | public func asColumn(order: ReshapeOrder = .rowMajor) -> Matrix { 86 | return reshaped(.column(length: shape.count), order: order) 87 | } 88 | 89 | } 90 | 91 | extension ComplexMatrix where Scalar == Double { 92 | 93 | public func reshaped(_ shape: Shape, order: ReshapeOrder = .rowMajor) -> ComplexMatrix { 94 | precondition(self.shape.count == shape.count) 95 | return ComplexMatrix(real: real.reshaped(shape, order: order), imaginary: imaginary.reshaped(shape, order: order)) 96 | } 97 | 98 | } 99 | 100 | 101 | extension ComplexMatrix where Scalar == Double { 102 | 103 | public func asRow(order: ReshapeOrder = .rowMajor) -> ComplexMatrix { 104 | return reshaped(.row(length: shape.count), order: order) 105 | } 106 | 107 | public func asColumn(order: ReshapeOrder = .rowMajor) -> ComplexMatrix { 108 | return reshaped(.column(length: shape.count), order: order) 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /Sources/Plinth/Shape.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Shape.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 30/03/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Shape { 11 | 12 | public let rows: Int 13 | public let columns: Int 14 | 15 | public init(rows: Int, columns: Int) { 16 | precondition(rows >= 0) 17 | precondition(columns >= 0) 18 | self.rows = rows 19 | self.columns = columns 20 | } 21 | 22 | } 23 | 24 | extension Shape { 25 | 26 | public var rowIndices: ClosedRange { 27 | return 0...(rows - 1) 28 | } 29 | 30 | public var columnIndices: ClosedRange { 31 | return 0...(columns - 1) 32 | } 33 | 34 | } 35 | 36 | extension Shape { 37 | 38 | public var count: Int { 39 | return rows * columns 40 | } 41 | 42 | public var length: Int { 43 | return max(rows, columns) 44 | } 45 | 46 | public var breadth: Int { 47 | return min(rows, columns) 48 | } 49 | 50 | } 51 | 52 | extension Shape { 53 | 54 | public static var empty: Shape { 55 | return .init(rows: 0, columns: 0) 56 | } 57 | 58 | public static func row(length: Int) -> Shape { 59 | return .init(rows: 1, columns: length) 60 | } 61 | 62 | public static func column(length: Int) -> Shape { 63 | return .init(rows: length, columns: 1) 64 | } 65 | 66 | public static func square(length: Int) -> Shape { 67 | return .init(rows: length, columns: length) 68 | } 69 | 70 | public var isEmpty: Bool { 71 | return rows == 0 || columns == 0 72 | } 73 | 74 | public var isRow: Bool { 75 | return rows == 1 76 | } 77 | 78 | public var isColumn: Bool { 79 | return columns == 1 80 | } 81 | 82 | public var isSquare: Bool { 83 | return rows == columns 84 | } 85 | 86 | } 87 | 88 | extension Shape { 89 | 90 | @inlinable public func indexFor(row: Int, column: Int) -> Int { 91 | return row * columns + column 92 | } 93 | 94 | @inlinable public func indicesFor(row: Int) -> ClosedRange { 95 | return indexFor(row: row, column: columnIndices.lowerBound)...indexFor(row: row, column: columnIndices.upperBound) 96 | } 97 | 98 | } 99 | 100 | extension Shape { 101 | 102 | @inlinable public func contains(row: Int, column: Int) -> Bool { 103 | return rowIndices.contains(row) && columnIndices.contains(column) 104 | } 105 | 106 | @inlinable public func contains(rows: ClosedRange, columns: ClosedRange) -> Bool { 107 | return rowIndices.contains(rows.lowerBound) && rowIndices.contains(rows.upperBound) && columnIndices.contains(columns.lowerBound) && columnIndices.contains(columns.upperBound) 108 | } 109 | 110 | } 111 | 112 | extension Shape: CustomStringConvertible { 113 | 114 | public var description: String { 115 | return "\(rows)×\(columns)" 116 | } 117 | 118 | } 119 | 120 | extension Shape: Equatable { 121 | 122 | public static func == (left: Shape, right: Shape) -> Bool { 123 | return left.rows == right.rows && left.columns == right.columns 124 | } 125 | 126 | } 127 | 128 | extension Shape: Hashable { 129 | 130 | public func hash(into hasher: inout Hasher) { 131 | hasher.combine(rows) 132 | hasher.combine(columns) 133 | } 134 | 135 | } 136 | 137 | extension Shape: Codable { 138 | 139 | enum CodingKeys: String, CodingKey { 140 | case rows 141 | case columns 142 | } 143 | 144 | public init(from decoder: Decoder) throws { 145 | let container = try decoder.container(keyedBy: CodingKeys.self) 146 | self.rows = try container.decode(Int.self, forKey: .rows) 147 | self.columns = try container.decode(Int.self, forKey: .columns) 148 | } 149 | 150 | public func encode(to encoder: Encoder) throws { 151 | var container = encoder.container(keyedBy: CodingKeys.self) 152 | try container.encode(rows, forKey: .rows) 153 | try container.encode(columns, forKey: .columns) 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Transformations/Crop.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Crop.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 26/04/22. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct CropRoundingRule { 11 | 12 | internal let top: FloatingPointRoundingRule 13 | internal let left: FloatingPointRoundingRule 14 | internal let bottom: FloatingPointRoundingRule 15 | internal let right: FloatingPointRoundingRule 16 | 17 | public static let towardsTopLeft = CropRoundingRule(top: .down, left: .down, bottom: .up, right: .up) 18 | public static let towardsTopRight = CropRoundingRule(top: .down, left: .up, bottom: .up, right: .down) 19 | public static let towardsBottomLeft = CropRoundingRule(top: .up, left: .down, bottom: .down, right: .up) 20 | public static let towardsBottomRight = CropRoundingRule(top: .up, left: .up, bottom: .down, right: .down) 21 | 22 | } 23 | 24 | extension Matrix where Scalar == Float { 25 | 26 | public func cropped(to shape: Shape, _ rule: CropRoundingRule = .towardsTopLeft) -> Matrix { 27 | let top = Int((Scalar(self.shape.rows - shape.rows) / 2.0).rounded(rule.top)) 28 | let left = Int((Scalar(self.shape.columns - shape.columns) / 2.0).rounded(rule.left)) 29 | let bottom = Int((Scalar(self.shape.rows - shape.rows) / 2.0).rounded(rule.bottom)) 30 | let right = Int((Scalar(self.shape.columns - shape.columns) / 2.0).rounded(rule.right)) 31 | return cropped(top: top, left: left, bottom: bottom, right: right) 32 | } 33 | 34 | public func cropped(inset: Int) -> Matrix { 35 | return cropped(top: inset, left: inset, bottom: inset, right: inset) 36 | } 37 | 38 | public func cropped(top: Int = 0, left: Int = 0, bottom: Int = 0, right: Int = 0) -> Matrix { 39 | let rows = (top)...(shape.rows - bottom - 1) 40 | let columns = (left)...(shape.columns - right - 1) 41 | return self[rows, columns] 42 | } 43 | 44 | } 45 | 46 | extension ComplexMatrix where Scalar == Float { 47 | 48 | public func cropped(to shape: Shape, _ rule: CropRoundingRule = .towardsTopLeft) -> ComplexMatrix { 49 | return ComplexMatrix(real: real.cropped(to: shape, rule), imaginary: imaginary.cropped(to: shape, rule)) 50 | } 51 | 52 | public func cropped(inset: Int) -> ComplexMatrix { 53 | return ComplexMatrix(real: real.cropped(inset: inset), imaginary: imaginary.cropped(inset: inset)) 54 | } 55 | 56 | public func cropped(top: Int = 0, left: Int = 0, bottom: Int = 0, right: Int = 0) -> ComplexMatrix { 57 | return ComplexMatrix(real: real.cropped(top: top, left: left, bottom: bottom, right: right), imaginary: imaginary.cropped(top: top, left: left, bottom: bottom, right: right)) 58 | } 59 | 60 | } 61 | 62 | extension Matrix where Scalar == Double { 63 | 64 | public func cropped(to shape: Shape, _ rule: CropRoundingRule = .towardsTopLeft) -> Matrix { 65 | let top = Int((Scalar(self.shape.rows - shape.rows) / 2.0).rounded(rule.top)) 66 | let left = Int((Scalar(self.shape.columns - shape.columns) / 2.0).rounded(rule.left)) 67 | let bottom = Int((Scalar(self.shape.rows - shape.rows) / 2.0).rounded(rule.bottom)) 68 | let right = Int((Scalar(self.shape.columns - shape.columns) / 2.0).rounded(rule.right)) 69 | return cropped(top: top, left: left, bottom: bottom, right: right) 70 | } 71 | 72 | public func cropped(inset: Int) -> Matrix { 73 | return cropped(top: inset, left: inset, bottom: inset, right: inset) 74 | } 75 | 76 | public func cropped(top: Int = 0, left: Int = 0, bottom: Int = 0, right: Int = 0) -> Matrix { 77 | let rows = (top)...(shape.rows - bottom - 1) 78 | let columns = (left)...(shape.columns - right - 1) 79 | return self[rows, columns] 80 | } 81 | 82 | } 83 | 84 | extension ComplexMatrix where Scalar == Double { 85 | 86 | public func cropped(to shape: Shape, _ rule: CropRoundingRule = .towardsTopLeft) -> ComplexMatrix { 87 | return ComplexMatrix(real: real.cropped(to: shape, rule), imaginary: imaginary.cropped(to: shape, rule)) 88 | } 89 | 90 | public func cropped(inset: Int) -> ComplexMatrix { 91 | return ComplexMatrix(real: real.cropped(inset: inset), imaginary: imaginary.cropped(inset: inset)) 92 | } 93 | 94 | public func cropped(top: Int = 0, left: Int = 0, bottom: Int = 0, right: Int = 0) -> ComplexMatrix { 95 | return ComplexMatrix(real: real.cropped(top: top, left: left, bottom: bottom, right: right), imaginary: imaginary.cropped(top: top, left: left, bottom: bottom, right: right)) 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/FFT1D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FFT1D.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 27/02/2025. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public func fft1D(setup: FFT.Setup? = nil) -> ComplexMatrix { 14 | return ComplexMatrix(real: self).fft1D(setup: setup) 15 | } 16 | 17 | public func ifft1D(setup: FFT.Setup? = nil) -> ComplexMatrix { 18 | return ComplexMatrix(real: self).ifft1D(setup: setup) 19 | } 20 | 21 | } 22 | 23 | extension Matrix where Scalar == Float { 24 | 25 | public func fft1D(direction: FFTDirection, setup: FFT.Setup? = nil) -> ComplexMatrix { 26 | return ComplexMatrix(real: self).fft1D(direction: direction, setup: setup) 27 | } 28 | 29 | } 30 | 31 | extension ComplexMatrix where Scalar == Float { 32 | 33 | public func fft1D(setup: FFT.Setup? = nil) -> ComplexMatrix { 34 | return fft1D(direction: .forward, setup: setup) 35 | } 36 | 37 | public func ifft1D(setup: FFT.Setup? = nil) -> ComplexMatrix { 38 | return fft1D(direction: .inverse, setup: setup) / Scalar(shape.count) 39 | } 40 | 41 | } 42 | 43 | extension ComplexMatrix where Scalar == Float { 44 | 45 | public func fft1D(direction: FFTDirection, setup: FFT.Setup? = nil) -> ComplexMatrix { 46 | switch setup { 47 | case .none: 48 | return fft1D(direction: direction) 49 | case .some(let setup): 50 | return fft1D(direction: direction, setup: setup) 51 | } 52 | } 53 | 54 | } 55 | 56 | extension ComplexMatrix where Scalar == Float { 57 | 58 | private func fft1D(direction: FFTDirection) -> ComplexMatrix { 59 | let setup = FFT.createSetup(shape: shape) 60 | let output = fft1D(direction: direction, setup: setup) 61 | FFT.destroySetup(setup) 62 | return output 63 | } 64 | 65 | private func fft1D(direction: FFTDirection, setup: FFT.Setup) -> ComplexMatrix { 66 | let log2N = vDSP_Length(log2(Scalar(shape.count))) 67 | return fmap { inputVector in 68 | vDSP_fft_zip(setup, &inputVector, 1, log2N, direction) 69 | } 70 | } 71 | 72 | } 73 | 74 | extension Matrix where Scalar == Double { 75 | 76 | public func fft1D(setup: FFT.Setup? = nil) -> ComplexMatrix { 77 | return ComplexMatrix(real: self).fft1D(setup: setup) 78 | } 79 | 80 | public func ifft1D(setup: FFT.Setup? = nil) -> ComplexMatrix { 81 | return ComplexMatrix(real: self).ifft1D(setup: setup) 82 | } 83 | 84 | } 85 | 86 | extension Matrix where Scalar == Double { 87 | 88 | public func fft1D(direction: FFTDirection, setup: FFT.Setup? = nil) -> ComplexMatrix { 89 | return ComplexMatrix(real: self).fft1D(direction: direction, setup: setup) 90 | } 91 | 92 | } 93 | 94 | extension ComplexMatrix where Scalar == Double { 95 | 96 | public func fft1D(setup: FFT.Setup? = nil) -> ComplexMatrix { 97 | return fft1D(direction: .forward, setup: setup) 98 | } 99 | 100 | public func ifft1D(setup: FFT.Setup? = nil) -> ComplexMatrix { 101 | return fft1D(direction: .inverse, setup: setup) / Scalar(shape.count) 102 | } 103 | 104 | } 105 | 106 | extension ComplexMatrix where Scalar == Double { 107 | 108 | public func fft1D(direction: FFTDirection, setup: FFT.Setup? = nil) -> ComplexMatrix { 109 | switch setup { 110 | case .none: 111 | return fft1D(direction: direction) 112 | case .some(let setup): 113 | return fft1D(direction: direction, setup: setup) 114 | } 115 | } 116 | 117 | } 118 | 119 | extension ComplexMatrix where Scalar == Double { 120 | 121 | private func fft1D(direction: FFTDirection) -> ComplexMatrix { 122 | let setup = FFT.createSetup(shape: shape) 123 | let output = fft1D(direction: direction, setup: setup) 124 | FFT.destroySetup(setup) 125 | return output 126 | } 127 | 128 | private func fft1D(direction: FFTDirection, setup: FFT.Setup) -> ComplexMatrix { 129 | let log2N = vDSP_Length(log2(Scalar(shape.count))) 130 | return fmap { inputVector in 131 | vDSP_fft_zipD(setup, &inputVector, 1, log2N, direction) 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/FFT2D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FFT2D.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 27/02/2025. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public func fft2D(setup: FFT.Setup? = nil) -> ComplexMatrix { 14 | return ComplexMatrix(real: self).fft2D(setup: setup) 15 | } 16 | 17 | public func ifft2D(setup: FFT.Setup? = nil) -> ComplexMatrix { 18 | return ComplexMatrix(real: self).ifft2D(setup: setup) 19 | } 20 | 21 | } 22 | 23 | extension Matrix where Scalar == Float { 24 | 25 | public func fft2D(direction: FFTDirection, setup: FFT.Setup? = nil) -> ComplexMatrix { 26 | return ComplexMatrix(real: self).fft2D(direction: direction, setup: setup) 27 | } 28 | 29 | } 30 | 31 | extension ComplexMatrix where Scalar == Float { 32 | 33 | public func fft2D(setup: FFT.Setup? = nil) -> ComplexMatrix { 34 | return fft2D(direction: .forward, setup: setup) 35 | } 36 | 37 | public func ifft2D(setup: FFT.Setup? = nil) -> ComplexMatrix { 38 | return fft2D(direction: .inverse, setup: setup) / Scalar(shape.count) 39 | } 40 | 41 | } 42 | 43 | extension ComplexMatrix where Scalar == Float { 44 | 45 | public func fft2D(direction: FFTDirection, setup: FFT.Setup? = nil) -> ComplexMatrix { 46 | switch setup { 47 | case .none: 48 | return fft2D(direction: direction) 49 | case .some(let setup): 50 | return fft2D(direction: direction, setup: setup) 51 | } 52 | } 53 | 54 | } 55 | 56 | extension ComplexMatrix where Scalar == Float { 57 | 58 | private func fft2D(direction: FFTDirection) -> ComplexMatrix { 59 | let setup = FFT.createSetup(shape: shape) 60 | let output = fft2D(direction: direction, setup: setup) 61 | FFT.destroySetup(setup) 62 | return output 63 | } 64 | 65 | private func fft2D(direction: FFTDirection, setup: FFT.Setup) -> ComplexMatrix { 66 | let log2N0 = vDSP_Length(log2(Scalar(shape.columns))) 67 | let log2N1 = vDSP_Length(log2(Scalar(shape.rows))) 68 | return fmap { inputVector in 69 | vDSP_fft2d_zip(setup, &inputVector, 1, 0, log2N0, log2N1, direction) 70 | } 71 | } 72 | 73 | } 74 | 75 | extension Matrix where Scalar == Double { 76 | 77 | public func fft2D(setup: FFT.Setup? = nil) -> ComplexMatrix { 78 | return ComplexMatrix(real: self).fft2D(setup: setup) 79 | } 80 | 81 | public func ifft2D(setup: FFT.Setup? = nil) -> ComplexMatrix { 82 | return ComplexMatrix(real: self).ifft2D(setup: setup) 83 | } 84 | 85 | } 86 | 87 | extension Matrix where Scalar == Double { 88 | 89 | public func fft2D(direction: FFTDirection, setup: FFT.Setup? = nil) -> ComplexMatrix { 90 | return ComplexMatrix(real: self).fft2D(direction: direction, setup: setup) 91 | } 92 | 93 | } 94 | 95 | extension ComplexMatrix where Scalar == Double { 96 | 97 | public func fft2D(setup: FFT.Setup? = nil) -> ComplexMatrix { 98 | return fft2D(direction: .forward, setup: setup) 99 | } 100 | 101 | public func ifft2D(setup: FFT.Setup? = nil) -> ComplexMatrix { 102 | return fft2D(direction: .inverse, setup: setup) / Scalar(shape.count) 103 | } 104 | 105 | } 106 | 107 | extension ComplexMatrix where Scalar == Double { 108 | 109 | public func fft2D(direction: FFTDirection, setup: FFT.Setup? = nil) -> ComplexMatrix { 110 | switch setup { 111 | case .none: 112 | return fft2D(direction: direction) 113 | case .some(let setup): 114 | return fft2D(direction: direction, setup: setup) 115 | } 116 | } 117 | 118 | } 119 | 120 | extension ComplexMatrix where Scalar == Double { 121 | 122 | private func fft2D(direction: FFTDirection) -> ComplexMatrix { 123 | let setup = FFT.createSetup(shape: shape) 124 | let output = fft2D(direction: direction, setup: setup) 125 | FFT.destroySetup(setup) 126 | return output 127 | } 128 | 129 | private func fft2D(direction: FFTDirection, setup: FFT.Setup) -> ComplexMatrix { 130 | let log2N0 = vDSP_Length(log2(Scalar(shape.columns))) 131 | let log2N1 = vDSP_Length(log2(Scalar(shape.rows))) 132 | return fmap { inputVector in 133 | vDSP_fft2d_zipD(setup, &inputVector, 1, 0, log2N0, log2N1, direction) 134 | } 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Mathematics/Comparisons.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LessThan.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 14/05/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | @inlinable public static func < (left: Matrix, right: Scalar) -> Matrix { 14 | return left.threshold(to: right, with: .signedConstant(-1.0)).threshold(to: .zero, with: .zeroFill) 15 | } 16 | 17 | @inlinable public static func <= (left: Matrix, right: Scalar) -> Matrix { 18 | return left.threshold(to: right.nextUp, with: .signedConstant(-1.0)).threshold(to: .zero, with: .zeroFill) 19 | } 20 | 21 | @inlinable public static func > (left: Matrix, right: Scalar) -> Matrix { 22 | return left.threshold(to: right.nextUp, with: .signedConstant(1.0)).threshold(to: .zero, with: .zeroFill) 23 | } 24 | 25 | @inlinable public static func >= (left: Matrix, right: Scalar) -> Matrix { 26 | return left.threshold(to: right, with: .signedConstant(1.0)).threshold(to: .zero, with: .zeroFill) 27 | } 28 | 29 | } 30 | 31 | extension Matrix where Scalar == Float { 32 | 33 | @inlinable public static func < (left: Scalar, right: Matrix) -> Matrix { 34 | return right > left 35 | } 36 | 37 | @inlinable public static func <= (left: Scalar, right: Matrix) -> Matrix { 38 | return right >= left 39 | } 40 | 41 | @inlinable public static func > (left: Scalar, right: Matrix) -> Matrix { 42 | return right < left 43 | } 44 | 45 | @inlinable public static func >= (left: Scalar, right: Matrix) -> Matrix { 46 | return right <= left 47 | } 48 | 49 | } 50 | 51 | extension Matrix where Scalar == Float { 52 | 53 | @inlinable public static func == (left: Matrix, right: Scalar) -> Matrix { 54 | return (left - right).absolute().threshold(to: .zero.nextUp, with: .signedConstant(-1.0)).threshold(to: .zero, with: .zeroFill) 55 | } 56 | 57 | @inlinable public static func !== (left: Matrix, right: Scalar) -> Matrix { 58 | return (left - right).absolute().threshold(to: .zero.nextUp, with: .signedConstant(1.0)).threshold(to: .zero, with: .zeroFill) 59 | } 60 | 61 | } 62 | 63 | extension Matrix where Scalar == Float { 64 | 65 | @inlinable public static func == (left: Scalar, right: Matrix) -> Matrix { 66 | return right == left 67 | } 68 | 69 | @inlinable public static func !== (left: Scalar, right: Matrix) -> Matrix { 70 | return right !== left 71 | } 72 | 73 | } 74 | 75 | extension Matrix where Scalar == Double { 76 | 77 | @inlinable public static func < (left: Matrix, right: Scalar) -> Matrix { 78 | return left.threshold(to: right, with: .signedConstant(-1.0)).threshold(to: .zero, with: .zeroFill) 79 | } 80 | 81 | @inlinable public static func <= (left: Matrix, right: Scalar) -> Matrix { 82 | return left.threshold(to: right.nextUp, with: .signedConstant(-1.0)).threshold(to: .zero, with: .zeroFill) 83 | } 84 | 85 | @inlinable public static func > (left: Matrix, right: Scalar) -> Matrix { 86 | return left.threshold(to: right.nextUp, with: .signedConstant(1.0)).threshold(to: .zero, with: .zeroFill) 87 | } 88 | 89 | @inlinable public static func >= (left: Matrix, right: Scalar) -> Matrix { 90 | return left.threshold(to: right, with: .signedConstant(1.0)).threshold(to: .zero, with: .zeroFill) 91 | } 92 | 93 | } 94 | 95 | extension Matrix where Scalar == Double { 96 | 97 | @inlinable public static func < (left: Scalar, right: Matrix) -> Matrix { 98 | return right > left 99 | } 100 | 101 | @inlinable public static func <= (left: Scalar, right: Matrix) -> Matrix { 102 | return right >= left 103 | } 104 | 105 | @inlinable public static func > (left: Scalar, right: Matrix) -> Matrix { 106 | return right < left 107 | } 108 | 109 | @inlinable public static func >= (left: Scalar, right: Matrix) -> Matrix { 110 | return right <= left 111 | } 112 | 113 | } 114 | 115 | extension Matrix where Scalar == Double { 116 | 117 | @inlinable public static func == (left: Matrix, right: Scalar) -> Matrix { 118 | return (left - right).absolute().threshold(to: .zero.nextUp, with: .signedConstant(-1.0)).threshold(to: .zero, with: .zeroFill) 119 | } 120 | 121 | @inlinable public static func !== (left: Matrix, right: Scalar) -> Matrix { 122 | return (left - right).absolute().threshold(to: .zero.nextUp, with: .signedConstant(1.0)).threshold(to: .zero, with: .zeroFill) 123 | } 124 | 125 | } 126 | 127 | extension Matrix where Scalar == Double { 128 | 129 | @inlinable public static func == (left: Scalar, right: Matrix) -> Matrix { 130 | return right == left 131 | } 132 | 133 | @inlinable public static func !== (left: Scalar, right: Matrix) -> Matrix { 134 | return right !== left 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Transformations/Concatenate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Concatenate.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 15/05/22. 6 | // 7 | 8 | import Foundation 9 | import Numerics 10 | 11 | extension Array where Element == Matrix { 12 | 13 | public func concatenatedRows() -> Element { 14 | return .concatenating(rows: self) 15 | } 16 | 17 | public func concatenatedColumns() -> Element { 18 | return .concatenating(columns: self) 19 | } 20 | 21 | } 22 | 23 | extension Matrix where Scalar == Float { 24 | 25 | public static func concatenating(rows matrices: [Matrix]) -> Matrix { 26 | let outputRows = matrices.map(\.shape.rows).reduce(0, +) 27 | let outputColumns = matrices.map(\.shape.columns).min() ?? 0 28 | var output: Matrix = .zeros(shape: .init(rows: outputRows, columns: outputColumns)) 29 | var currentRow = 0 30 | for matrix in matrices { 31 | output[rows: (matrix.shape.rowIndices.lowerBound + currentRow)...(matrix.shape.rowIndices.upperBound + currentRow)] = matrix 32 | currentRow += matrix.shape.rows 33 | } 34 | return output 35 | } 36 | 37 | public static func concatenating(columns matrices: [Matrix]) -> Matrix { 38 | let outputRows = matrices.map(\.shape.rows).min() ?? 0 39 | let outputColumns = matrices.map(\.shape.columns).reduce(0, +) 40 | var output: Matrix = .zeros(shape: .init(rows: outputRows, columns: outputColumns)) 41 | var currentColumn = 0 42 | for matrix in matrices { 43 | output[columns: (matrix.shape.columnIndices.lowerBound + currentColumn)...(matrix.shape.columnIndices.upperBound + currentColumn)] = matrix 44 | currentColumn += matrix.shape.columns 45 | } 46 | return output 47 | } 48 | 49 | } 50 | 51 | extension Array where Element == ComplexMatrix { 52 | 53 | public func concatenatedRows() -> Element { 54 | return .concatenating(rows: self) 55 | } 56 | 57 | public func concatenatedColumns() -> Element { 58 | return .concatenating(columns: self) 59 | } 60 | 61 | } 62 | 63 | extension ComplexMatrix where Scalar == Float { 64 | 65 | public static func concatenating(rows matrices: [ComplexMatrix]) -> ComplexMatrix { 66 | return ComplexMatrix(real: .concatenating(rows: matrices.map(\.real)), imaginary: .concatenating(rows: matrices.map(\.imaginary))) 67 | } 68 | 69 | public static func concatenating(columns matrices: [ComplexMatrix]) -> ComplexMatrix { 70 | return ComplexMatrix(real: .concatenating(columns: matrices.map(\.real)), imaginary: .concatenating(columns: matrices.map(\.imaginary))) 71 | } 72 | 73 | } 74 | 75 | extension Array where Element == Matrix { 76 | 77 | public func concatenatedRows() -> Element { 78 | return .concatenating(rows: self) 79 | } 80 | 81 | public func concatenatedColumns() -> Element { 82 | return .concatenating(columns: self) 83 | } 84 | 85 | } 86 | 87 | extension Matrix where Scalar == Double { 88 | 89 | public static func concatenating(rows matrices: [Matrix]) -> Matrix { 90 | let outputRows = matrices.map(\.shape.rows).reduce(0, +) 91 | let outputColumns = matrices.map(\.shape.columns).min() ?? 0 92 | var output: Matrix = .zeros(shape: .init(rows: outputRows, columns: outputColumns)) 93 | var currentRow = 0 94 | for matrix in matrices { 95 | output[rows: (matrix.shape.rowIndices.lowerBound + currentRow)...(matrix.shape.rowIndices.upperBound + currentRow)] = matrix 96 | currentRow += matrix.shape.rows 97 | } 98 | return output 99 | } 100 | 101 | public static func concatenating(columns matrices: [Matrix]) -> Matrix { 102 | let outputRows = matrices.map(\.shape.rows).min() ?? 0 103 | let outputColumns = matrices.map(\.shape.columns).reduce(0, +) 104 | var output: Matrix = .zeros(shape: .init(rows: outputRows, columns: outputColumns)) 105 | var currentColumn = 0 106 | for matrix in matrices { 107 | output[columns: (matrix.shape.columnIndices.lowerBound + currentColumn)...(matrix.shape.columnIndices.upperBound + currentColumn)] = matrix 108 | currentColumn += matrix.shape.columns 109 | } 110 | return output 111 | } 112 | 113 | } 114 | 115 | extension Array where Element == ComplexMatrix { 116 | 117 | public func concatenatedRows() -> Element { 118 | return .concatenating(rows: self) 119 | } 120 | 121 | public func concatenatedColumns() -> Element { 122 | return .concatenating(columns: self) 123 | } 124 | 125 | } 126 | 127 | extension ComplexMatrix where Scalar == Double { 128 | 129 | public static func concatenating(rows matrices: [ComplexMatrix]) -> ComplexMatrix { 130 | return ComplexMatrix(real: .concatenating(rows: matrices.map(\.real)), imaginary: .concatenating(rows: matrices.map(\.imaginary))) 131 | } 132 | 133 | public static func concatenating(columns matrices: [ComplexMatrix]) -> ComplexMatrix { 134 | return ComplexMatrix(real: .concatenating(columns: matrices.map(\.real)), imaginary: .concatenating(columns: matrices.map(\.imaginary))) 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Statistics/Random.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Random.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 3/05/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | public enum RandomDistribution: Int { 12 | 13 | case uniformUnitInterval = 1 14 | case uniformSymmetricUnitInterval = 2 15 | case gaussian = 3 16 | 17 | } 18 | 19 | public enum ComplexRandomDistribution: Int { 20 | 21 | case uniformUnitInterval = 1 22 | case uniformSymmetricUnitInterval = 2 23 | case gaussian = 3 24 | case uniformDisc = 4 25 | case uniformCircle = 5 26 | 27 | } 28 | 29 | extension Matrix where Scalar == Float { 30 | 31 | public static func random(shape: Shape, in range: Range) -> Matrix { 32 | return Matrix(shape: shape, Scalar.random(in: range)) 33 | } 34 | 35 | public static func random(shape: Shape, in range: Range, using generator: inout T) -> Matrix where T : RandomNumberGenerator { 36 | return Matrix(shape: shape, Scalar.random(in: range, using: &generator)) 37 | } 38 | 39 | public static func random(shape: Shape, in range: ClosedRange) -> Matrix { 40 | return Matrix(shape: shape, Scalar.random(in: range)) 41 | } 42 | 43 | public static func random(shape: Shape, in range: ClosedRange, using generator: inout T) -> Matrix where T : RandomNumberGenerator { 44 | return Matrix(shape: shape, Scalar.random(in: range, using: &generator)) 45 | } 46 | 47 | } 48 | 49 | extension Matrix where Scalar == Float { 50 | 51 | private static var seed: [__CLPK_integer] = [0, 0, 0, 1] 52 | 53 | public static func random(shape: Shape, distribution: RandomDistribution) -> Matrix { 54 | var count = __CLPK_integer(shape.count) 55 | var code = __CLPK_integer(distribution.rawValue) 56 | var workspace = [Scalar](repeating: .zero, count: shape.count) 57 | 58 | withUnsafeMutablePointer(to: &count) { count in 59 | slarnv_(&code, &seed, count, &workspace) 60 | } 61 | 62 | return .init(shape: shape, elements: workspace) 63 | } 64 | 65 | } 66 | 67 | extension ComplexMatrix where Scalar == Float { 68 | 69 | private static var seed: [__CLPK_integer] = [0, 0, 0, 1] 70 | 71 | public static func random(shape: Shape, distribution: ComplexRandomDistribution) -> ComplexMatrix { 72 | var count = __CLPK_integer(shape.count) 73 | var code = __CLPK_integer(distribution.rawValue) 74 | var workspace = [__CLPK_complex](repeating: __CLPK_complex(), count: shape.count) 75 | 76 | withUnsafeMutablePointer(to: &count) { count in 77 | clarnv_(&code, &seed, count, &workspace) 78 | } 79 | 80 | return .init(real: .init(shape: shape, elements: workspace.map(\.r)), imaginary: .init(shape: shape, elements: workspace.map(\.i))) 81 | } 82 | 83 | } 84 | 85 | extension Matrix where Scalar == Double { 86 | 87 | public static func random(shape: Shape, in range: Range) -> Matrix { 88 | return Matrix(shape: shape, Scalar.random(in: range)) 89 | } 90 | 91 | public static func random(shape: Shape, in range: Range, using generator: inout T) -> Matrix where T : RandomNumberGenerator { 92 | return Matrix(shape: shape, Scalar.random(in: range, using: &generator)) 93 | } 94 | 95 | public static func random(shape: Shape, in range: ClosedRange) -> Matrix { 96 | return Matrix(shape: shape, Scalar.random(in: range)) 97 | } 98 | 99 | public static func random(shape: Shape, in range: ClosedRange, using generator: inout T) -> Matrix where T : RandomNumberGenerator { 100 | return Matrix(shape: shape, Scalar.random(in: range, using: &generator)) 101 | } 102 | 103 | } 104 | 105 | extension Matrix where Scalar == Double { 106 | 107 | private static var seed: [__CLPK_integer] = [0, 0, 0, 1] 108 | 109 | public static func random(shape: Shape, distribution: RandomDistribution) -> Matrix { 110 | var count = __CLPK_integer(shape.count) 111 | var code = __CLPK_integer(distribution.rawValue) 112 | var workspace = [Scalar](repeating: .zero, count: shape.count) 113 | 114 | withUnsafeMutablePointer(to: &count) { count in 115 | dlarnv_(&code, &seed, count, &workspace) 116 | } 117 | 118 | return .init(shape: shape, elements: workspace) 119 | } 120 | 121 | } 122 | 123 | extension ComplexMatrix where Scalar == Double { 124 | 125 | private static var seed: [__CLPK_integer] = [0, 0, 0, 1] 126 | 127 | public static func random(shape: Shape, distribution: ComplexRandomDistribution = .uniformUnitInterval) -> ComplexMatrix { 128 | var count = __CLPK_integer(shape.count) 129 | var code = __CLPK_integer(distribution.rawValue) 130 | var workspace = [__CLPK_doublecomplex](repeating: __CLPK_doublecomplex(), count: shape.count) 131 | 132 | withUnsafeMutablePointer(to: &count) { count in 133 | zlarnv_(&code, &seed, count, &workspace) 134 | } 135 | 136 | return .init(real: .init(shape: shape, elements: workspace.map(\.r)), imaginary: .init(shape: shape, elements: workspace.map(\.i))) 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /Sources/Plinth/Core/Functors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functors.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 20/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | import Numerics 11 | 12 | extension Matrix { 13 | 14 | @inlinable public func fmap(_ function: ([Scalar]) -> A) -> A { 15 | return function(elements) 16 | } 17 | 18 | @inlinable public func fmap(_ function: ([Scalar]) -> [A]) -> Matrix { 19 | return Matrix(shape: shape, elements: function(elements)) 20 | } 21 | 22 | @inlinable public func fmap(_ function: (inout [Scalar]) -> ()) -> Matrix { 23 | var output = self 24 | function(&output.elements) 25 | return output 26 | } 27 | 28 | @inlinable public func fmap(_ function: ([Scalar], inout [A]) -> ()) -> Matrix where A: Numeric { 29 | var output = Matrix.zeros(shape: shape) 30 | function(elements, &output.elements) 31 | return output 32 | } 33 | 34 | } 35 | 36 | extension ComplexMatrix { 37 | 38 | @inlinable public func fmap(real realFunction: ([Scalar]) -> [A], imaginary imaginaryFunction: ([Scalar]) -> [A]) -> ComplexMatrix { 39 | return ComplexMatrix(real: real.fmap(realFunction), imaginary: imaginary.fmap(imaginaryFunction)) 40 | } 41 | 42 | @inlinable public func fmap(real realFunction: ([Scalar], inout [A]) -> (), imaginary imaginaryFunction: ([Scalar], inout [A]) -> ()) -> ComplexMatrix where A: Numeric { 43 | return ComplexMatrix(real: real.fmap(realFunction), imaginary: imaginary.fmap(imaginaryFunction)) 44 | } 45 | 46 | } 47 | 48 | extension ComplexMatrix where Scalar == Float { 49 | 50 | @inlinable public func fmap(_ function: (DSPSplitComplex, inout [Scalar]) -> ()) -> Matrix { 51 | var input = self 52 | var output = Matrix.zeros(shape: shape) 53 | input.withUnsafeMutableSplitComplexVector { inputVector in 54 | function(inputVector, &output.elements) 55 | } 56 | return output 57 | } 58 | 59 | } 60 | 61 | extension ComplexMatrix where Scalar == Float { 62 | 63 | @inlinable public func fmap(_ function: (inout DSPSplitComplex) -> ()) -> ComplexMatrix { 64 | var input = self 65 | input.withUnsafeMutableSplitComplexVector { inputVector in 66 | function(&inputVector) 67 | } 68 | return input 69 | } 70 | 71 | @inlinable public func fmap(_ function: (DSPSplitComplex, inout DSPSplitComplex) -> ()) -> ComplexMatrix { 72 | var input = self 73 | var output = ComplexMatrix.zeros(shape: shape) 74 | input.withUnsafeMutableSplitComplexVector { inputVector in 75 | output.withUnsafeMutableSplitComplexVector { outputVector in 76 | function(inputVector, &outputVector) 77 | } 78 | } 79 | return output 80 | } 81 | 82 | } 83 | 84 | extension ComplexMatrix where Scalar == Float { 85 | 86 | @inlinable public mutating func withUnsafeMutableSplitComplexVector(_ body: (inout DSPSplitComplex) throws -> Result) rethrows -> Result { 87 | return try real.withUnsafeMutableBufferPointer { realPointer in 88 | return try imaginary.withUnsafeMutableBufferPointer { imaginaryPointer in 89 | var split = DSPSplitComplex(realp: realPointer.baseAddress!, imagp: imaginaryPointer.baseAddress!) 90 | return try body(&split) 91 | } 92 | } 93 | } 94 | 95 | } 96 | 97 | extension ComplexMatrix where Scalar == Double { 98 | 99 | @inlinable public func fmap(_ function: (DSPDoubleSplitComplex, inout [Scalar]) -> ()) -> Matrix { 100 | var input = self 101 | var output = Matrix.zeros(shape: shape) 102 | input.withUnsafeMutableSplitComplexVector { inputVector in 103 | function(inputVector, &output.elements) 104 | } 105 | return output 106 | } 107 | 108 | } 109 | 110 | extension ComplexMatrix where Scalar == Double { 111 | 112 | @inlinable public func fmap(_ function: (inout DSPDoubleSplitComplex) -> ()) -> ComplexMatrix { 113 | var input = self 114 | input.withUnsafeMutableSplitComplexVector { inputVector in 115 | function(&inputVector) 116 | } 117 | return input 118 | } 119 | 120 | @inlinable public func fmap(_ function: (DSPDoubleSplitComplex, inout DSPDoubleSplitComplex) -> ()) -> ComplexMatrix { 121 | var input = self 122 | var output = ComplexMatrix.zeros(shape: shape) 123 | input.withUnsafeMutableSplitComplexVector { inputVector in 124 | output.withUnsafeMutableSplitComplexVector { outputVector in 125 | function(inputVector, &outputVector) 126 | } 127 | } 128 | return output 129 | } 130 | 131 | } 132 | 133 | extension ComplexMatrix where Scalar == Double { 134 | 135 | @inlinable public mutating func withUnsafeMutableSplitComplexVector(_ body: (inout DSPDoubleSplitComplex) throws -> Result) rethrows -> Result { 136 | return try real.withUnsafeMutableBufferPointer { realPointer in 137 | return try imaginary.withUnsafeMutableBufferPointer { imaginaryPointer in 138 | var split = DSPDoubleSplitComplex(realp: realPointer.baseAddress!, imagp: imaginaryPointer.baseAddress!) 139 | return try body(&split) 140 | } 141 | } 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Statistics/Moments.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Statistics.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 13/04/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Matrix where Scalar == Float { 11 | 12 | public func moment(degree: Int) -> Scalar { 13 | precondition(degree >= 0) 14 | switch degree { 15 | case 0: 16 | return 1.0 17 | case 1: 18 | return mean() 19 | case 2: 20 | return meanSquare() 21 | default: 22 | return (self ** degree).mean() 23 | } 24 | } 25 | 26 | } 27 | 28 | extension Matrix where Scalar == Float { 29 | 30 | public func centralMoment(degree: Int) -> Scalar { 31 | return centralMoment(degree: degree, mean: mean()) 32 | } 33 | 34 | public func centralMoment(degree: Int, mean: Scalar) -> Scalar { 35 | return meanNormalized(mean: mean).moment(degree: degree) 36 | } 37 | 38 | } 39 | 40 | extension Matrix where Scalar == Float { 41 | 42 | public func standardizedMoment(degree: Int) -> Scalar { 43 | return standardizedMoment(degree: degree, mean: mean()) 44 | } 45 | 46 | public func standardizedMoment(degree: Int, mean: Scalar) -> Scalar { 47 | let standardDeviation = standardDeviation(mean: mean) 48 | guard standardDeviation.isZero == false else { 49 | return .zero 50 | } 51 | return centralMoment(degree: degree, mean: mean) / standardDeviation ** Scalar(degree) 52 | } 53 | 54 | } 55 | 56 | extension Matrix where Scalar == Float { 57 | 58 | public func variance() -> Scalar { 59 | return variance(mean: mean()) 60 | } 61 | 62 | public func variance(mean: Scalar) -> Scalar { 63 | return centralMoment(degree: 2, mean: mean) 64 | } 65 | 66 | } 67 | 68 | extension Matrix where Scalar == Float { 69 | 70 | public func standardDeviation() -> Scalar { 71 | return standardDeviation(mean: mean()) 72 | } 73 | 74 | public func standardDeviation(mean: Scalar) -> Scalar { 75 | return centralMoment(degree: 2, mean: mean).squareRoot() 76 | } 77 | 78 | } 79 | 80 | extension Matrix where Scalar == Float { 81 | 82 | public func skewness() -> Scalar { 83 | return skewness(mean: mean()) 84 | } 85 | 86 | public func skewness(mean: Scalar) -> Scalar { 87 | return standardizedMoment(degree: 3, mean: mean) 88 | } 89 | 90 | } 91 | 92 | extension Matrix where Scalar == Float { 93 | 94 | public func kurtosis() -> Scalar { 95 | return kurtosis(mean: mean()) 96 | } 97 | 98 | public func kurtosis(mean: Scalar) -> Scalar { 99 | return standardizedMoment(degree: 4, mean: mean) 100 | } 101 | 102 | } 103 | 104 | extension Matrix where Scalar == Double { 105 | 106 | public func moment(degree: Int) -> Scalar { 107 | precondition(degree >= 0) 108 | switch degree { 109 | case 0: 110 | return 1.0 111 | case 1: 112 | return mean() 113 | case 2: 114 | return meanSquare() 115 | default: 116 | return (self ** degree).mean() 117 | } 118 | } 119 | 120 | } 121 | 122 | extension Matrix where Scalar == Double { 123 | 124 | public func centralMoment(degree: Int) -> Scalar { 125 | return centralMoment(degree: degree, mean: mean()) 126 | } 127 | 128 | public func centralMoment(degree: Int, mean: Scalar) -> Scalar { 129 | return meanNormalized(mean: mean).moment(degree: degree) 130 | } 131 | 132 | } 133 | 134 | extension Matrix where Scalar == Double { 135 | 136 | public func standardizedMoment(degree: Int) -> Scalar { 137 | return standardizedMoment(degree: degree, mean: mean()) 138 | } 139 | 140 | public func standardizedMoment(degree: Int, mean: Scalar) -> Scalar { 141 | let standardDeviation = standardDeviation(mean: mean) 142 | guard standardDeviation.isZero == false else { 143 | return .zero 144 | } 145 | return centralMoment(degree: degree, mean: mean) / standardDeviation ** Scalar(degree) 146 | } 147 | 148 | } 149 | 150 | extension Matrix where Scalar == Double { 151 | 152 | public func variance() -> Scalar { 153 | return variance(mean: mean()) 154 | } 155 | 156 | public func variance(mean: Scalar) -> Scalar { 157 | return centralMoment(degree: 2, mean: mean) 158 | } 159 | 160 | } 161 | 162 | extension Matrix where Scalar == Double { 163 | 164 | public func standardDeviation() -> Scalar { 165 | return standardDeviation(mean: mean()) 166 | } 167 | 168 | public func standardDeviation(mean: Scalar) -> Scalar { 169 | return centralMoment(degree: 2, mean: mean).squareRoot() 170 | } 171 | 172 | } 173 | 174 | extension Matrix where Scalar == Double { 175 | 176 | public func skewness() -> Scalar { 177 | return skewness(mean: mean()) 178 | } 179 | 180 | public func skewness(mean: Scalar) -> Scalar { 181 | return standardizedMoment(degree: 3, mean: mean) 182 | } 183 | 184 | } 185 | 186 | extension Matrix where Scalar == Double { 187 | 188 | public func kurtosis() -> Scalar { 189 | return kurtosis(mean: mean()) 190 | } 191 | 192 | public func kurtosis(mean: Scalar) -> Scalar { 193 | return standardizedMoment(degree: 4, mean: mean) 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Transformations/Pad.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pad.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 27/04/22. 6 | // 7 | 8 | import Foundation 9 | import Numerics 10 | 11 | public struct PadRoundingRule { 12 | 13 | internal let top: FloatingPointRoundingRule 14 | internal let left: FloatingPointRoundingRule 15 | internal let bottom: FloatingPointRoundingRule 16 | internal let right: FloatingPointRoundingRule 17 | 18 | public static let towardsTopLeft = PadRoundingRule(top: .down, left: .down, bottom: .up, right: .up) 19 | public static let towardsTopRight = PadRoundingRule(top: .down, left: .up, bottom: .up, right: .down) 20 | public static let towardsBottomLeft = PadRoundingRule(top: .up, left: .down, bottom: .down, right: .up) 21 | public static let towardsBottomRight = PadRoundingRule(top: .up, left: .up, bottom: .down, right: .down) 22 | 23 | } 24 | 25 | extension Matrix where Scalar == Float { 26 | 27 | public func padded(to shape: Shape, _ rule: PadRoundingRule = .towardsTopLeft, repeating element: Scalar = .zero) -> Matrix { 28 | let top = Int((Scalar(shape.rows - self.shape.rows) / 2.0).rounded(rule.top)) 29 | let left = Int((Scalar(shape.columns - self.shape.columns) / 2.0).rounded(rule.left)) 30 | let bottom = Int((Scalar(shape.rows - self.shape.rows) / 2.0).rounded(rule.bottom)) 31 | let right = Int((Scalar(shape.columns - self.shape.columns) / 2.0).rounded(rule.right)) 32 | return padded(top: top, left: left, bottom: bottom, right: right) 33 | } 34 | 35 | public func padded(inset: Int, repeating element: Scalar = .zero) -> Matrix { 36 | return padded(top: inset, left: inset, bottom: inset, right: inset, repeating: element) 37 | } 38 | 39 | public func padded(top: Int = 0, left: Int = 0, bottom: Int = 0, right: Int = 0, repeating element: Scalar = .zero) -> Matrix { 40 | var output: Matrix = .init(shape: .init(rows: shape.rows + top + bottom, columns: shape.columns + left + right), repeating: element) 41 | output[top...(top + shape.rows - 1), left...(left + shape.columns - 1)] = self 42 | return output 43 | } 44 | 45 | } 46 | 47 | extension ComplexMatrix where Scalar == Float { 48 | 49 | public func padded(to shape: Shape, _ rule: PadRoundingRule = .towardsTopLeft, repeating element: Complex = .zero) -> ComplexMatrix { 50 | return ComplexMatrix( 51 | real: real.padded(to: shape, rule, repeating: element.real), 52 | imaginary: imaginary.padded(to: shape, rule, repeating: element.imaginary) 53 | ) 54 | } 55 | 56 | public func padded(inset: Int, repeating element: Complex = .zero) -> ComplexMatrix { 57 | return ComplexMatrix( 58 | real: real.padded(inset: inset), 59 | imaginary: imaginary.padded(inset: inset) 60 | ) 61 | } 62 | 63 | public func padded(top: Int = 0, left: Int = 0, bottom: Int = 0, right: Int = 0, repeating element: Complex = .zero) -> ComplexMatrix { 64 | return ComplexMatrix( 65 | real: real.padded(top: top, left: left, bottom: bottom, right: right, repeating: element.real), 66 | imaginary: imaginary.padded(top: top, left: left, bottom: bottom, right: right, repeating: element.imaginary) 67 | ) 68 | } 69 | 70 | } 71 | 72 | extension Matrix where Scalar == Double { 73 | 74 | public func padded(to shape: Shape, _ rule: PadRoundingRule = .towardsTopLeft, repeating element: Scalar = .zero) -> Matrix { 75 | let top = Int((Scalar(shape.rows - self.shape.rows) / 2.0).rounded(rule.top)) 76 | let left = Int((Scalar(shape.columns - self.shape.columns) / 2.0).rounded(rule.left)) 77 | let bottom = Int((Scalar(shape.rows - self.shape.rows) / 2.0).rounded(rule.bottom)) 78 | let right = Int((Scalar(shape.columns - self.shape.columns) / 2.0).rounded(rule.right)) 79 | return padded(top: top, left: left, bottom: bottom, right: right) 80 | } 81 | 82 | public func padded(inset: Int, repeating element: Scalar = .zero) -> Matrix { 83 | return padded(top: inset, left: inset, bottom: inset, right: inset, repeating: element) 84 | } 85 | 86 | public func padded(top: Int = 0, left: Int = 0, bottom: Int = 0, right: Int = 0, repeating element: Scalar = .zero) -> Matrix { 87 | var output: Matrix = .init(shape: .init(rows: shape.rows + top + bottom, columns: shape.columns + left + right), repeating: element) 88 | output[top...(top + shape.rows - 1), left...(left + shape.columns - 1)] = self 89 | return output 90 | } 91 | 92 | } 93 | 94 | extension ComplexMatrix where Scalar == Double { 95 | 96 | public func padded(to shape: Shape, _ rule: PadRoundingRule = .towardsTopLeft, repeating element: Complex = .zero) -> ComplexMatrix { 97 | return ComplexMatrix( 98 | real: real.padded(to: shape, rule, repeating: element.real), 99 | imaginary: imaginary.padded(to: shape, rule, repeating: element.imaginary) 100 | ) 101 | } 102 | 103 | public func padded(inset: Int, repeating element: Complex = .zero) -> ComplexMatrix { 104 | return ComplexMatrix( 105 | real: real.padded(inset: inset), 106 | imaginary: imaginary.padded(inset: inset) 107 | ) 108 | } 109 | 110 | public func padded(top: Int = 0, left: Int = 0, bottom: Int = 0, right: Int = 0, repeating element: Complex = .zero) -> ComplexMatrix { 111 | return ComplexMatrix( 112 | real: real.padded(top: top, left: left, bottom: bottom, right: right, repeating: element.real), 113 | imaginary: imaginary.padded(top: top, left: left, bottom: bottom, right: right, repeating: element.imaginary) 114 | ) 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Mathematics/Powers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Powers.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 29/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | precedencegroup PowerPrecedence { 12 | associativity: left 13 | higherThan: MultiplicationPrecedence 14 | } 15 | 16 | infix operator **: PowerPrecedence 17 | 18 | infix operator **=: AssignmentPrecedence 19 | 20 | extension Float { 21 | 22 | @inlinable public static func ** (left: Self, right: Self) -> Self { 23 | return Darwin.pow(left, right) 24 | } 25 | 26 | } 27 | 28 | extension Float { 29 | 30 | @inlinable public static func **= (left: inout Self, right: Self) { 31 | left = left ** right 32 | } 33 | 34 | } 35 | 36 | extension Array where Element == Float { 37 | 38 | @inlinable internal static func ** (left: Array, right: Int) -> Array { 39 | switch right.signum() { 40 | case -1: 41 | return vForce.reciprocal([Array](repeating: left, count: -right - 1).reduce(left, vDSP.multiply)) 42 | case 0: 43 | return Array(repeating: 1.0, count: left.count) 44 | case 1: 45 | return [Array](repeating: left, count: right - 1).reduce(left, vDSP.multiply) 46 | default: 47 | fatalError() 48 | } 49 | } 50 | 51 | @inlinable internal static func ** (left: Array, right: Element) -> Array { 52 | return vForce.pow(bases: left, exponents: Array(repeating: right, count: left.count)) 53 | } 54 | 55 | @inlinable internal static func ** (left: Array, right: Array) -> Array { 56 | return vForce.pow(bases: left, exponents: right) 57 | } 58 | 59 | } 60 | 61 | extension Array where Element == Float { 62 | 63 | @inlinable public static func **= (left: inout Array, right: Int) { 64 | left = left ** right 65 | } 66 | 67 | @inlinable public static func **= (left: inout Array, right: Element) { 68 | left = left ** right 69 | } 70 | 71 | @inlinable public static func **= (left: inout Array, right: Array) { 72 | left = left ** right 73 | } 74 | 75 | } 76 | 77 | extension Matrix where Scalar == Float { 78 | 79 | @inlinable public static func ** (left: Matrix, right: Int) -> Matrix { 80 | return left.fmap { $0 ** right } 81 | } 82 | 83 | @inlinable public static func ** (left: Matrix, right: Element) -> Matrix { 84 | return left.fmap { $0 ** right } 85 | } 86 | 87 | @inlinable public static func ** (left: Matrix, right: Matrix) -> Matrix { 88 | return left.fmap { $0 ** right.elements } 89 | } 90 | 91 | } 92 | 93 | extension Matrix where Element == Float { 94 | 95 | @inlinable public static func **= (left: inout Matrix, right: Int) { 96 | left = left ** right 97 | } 98 | 99 | @inlinable public static func **= (left: inout Matrix, right: Element) { 100 | left = left ** right 101 | } 102 | 103 | @inlinable public static func **= (left: inout Matrix, right: Matrix) { 104 | left = left ** right 105 | } 106 | 107 | } 108 | 109 | extension Double { 110 | 111 | @inlinable public static func ** (left: Self, right: Self) -> Self { 112 | return Darwin.pow(left, right) 113 | } 114 | 115 | } 116 | 117 | extension Double { 118 | 119 | @inlinable public static func **= (left: inout Self, right: Self) { 120 | left = left ** right 121 | } 122 | 123 | } 124 | 125 | extension Array where Element == Double { 126 | 127 | @inlinable internal static func ** (left: Array, right: Int) -> Array { 128 | switch right.signum() { 129 | case -1: 130 | return vForce.reciprocal([Array](repeating: left, count: -right - 1).reduce(left, vDSP.multiply)) 131 | case 0: 132 | return Array(repeating: 1.0, count: left.count) 133 | case 1: 134 | return [Array](repeating: left, count: right - 1).reduce(left, vDSP.multiply) 135 | default: 136 | fatalError() 137 | } 138 | } 139 | 140 | @inlinable internal static func ** (left: Array, right: Element) -> Array { 141 | return vForce.pow(bases: left, exponents: Array(repeating: right, count: left.count)) 142 | } 143 | 144 | @inlinable internal static func ** (left: Array, right: Array) -> Array { 145 | return vForce.pow(bases: left, exponents: right) 146 | } 147 | 148 | } 149 | 150 | extension Array where Element == Double { 151 | 152 | @inlinable public static func **= (left: inout Array, right: Int) { 153 | left = left ** right 154 | } 155 | 156 | @inlinable public static func **= (left: inout Array, right: Element) { 157 | left = left ** right 158 | } 159 | 160 | @inlinable public static func **= (left: inout Array, right: Array) { 161 | left = left ** right 162 | } 163 | 164 | } 165 | 166 | extension Matrix where Scalar == Double { 167 | 168 | @inlinable public static func ** (left: Matrix, right: Int) -> Matrix { 169 | return left.fmap { $0 ** right } 170 | } 171 | 172 | @inlinable public static func ** (left: Matrix, right: Element) -> Matrix { 173 | return left.fmap { $0 ** right } 174 | } 175 | 176 | @inlinable public static func ** (left: Matrix, right: Matrix) -> Matrix { 177 | return left.fmap { $0 ** right.elements } 178 | } 179 | 180 | } 181 | 182 | extension Matrix where Element == Double { 183 | 184 | @inlinable public static func **= (left: inout Matrix, right: Int) { 185 | left = left ** right 186 | } 187 | 188 | @inlinable public static func **= (left: inout Matrix, right: Element) { 189 | left = left ** right 190 | } 191 | 192 | @inlinable public static func **= (left: inout Matrix, right: Matrix) { 193 | left = left ** right 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Inversion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Inversion.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 4/05/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | import Numerics 11 | 12 | public enum InversionError: LocalizedError { 13 | 14 | case illegalValue(matrix: Matrix, code: Int32) 15 | case computationFailed(matrix: Matrix, code: Int32) 16 | 17 | public var errorDescription: String? { 18 | switch self { 19 | case .illegalValue(_, let code): 20 | return "Matrix inversion error: Illegal value (\(code))" 21 | case .computationFailed(_, let code): 22 | return "Matrix inversion error: Computation failed (\(code))" 23 | } 24 | } 25 | 26 | } 27 | 28 | public enum ComplexInversionError: LocalizedError where Scalar: Real { 29 | 30 | case illegalValue(matrix: ComplexMatrix, code: Int32) 31 | case computationFailed(matrix: ComplexMatrix, code: Int32) 32 | 33 | public var errorDescription: String? { 34 | switch self { 35 | case .illegalValue(_, let code): 36 | return "Complex matrix inversion error: Illegal value (\(code))" 37 | case .computationFailed(_, let code): 38 | return "Complex matrix inversion error: Computation failed (\(code))" 39 | } 40 | } 41 | 42 | } 43 | 44 | extension Matrix where Scalar == Float { 45 | 46 | public func inverted() throws -> Matrix { 47 | precondition(shape.isSquare) 48 | 49 | var input = self 50 | var length = __CLPK_integer(shape.length) 51 | var pivots = [__CLPK_integer](repeating: .zero, count: shape.length) 52 | var workspace = [Scalar](repeating: .zero, count: shape.length) 53 | var error: __CLPK_integer = 0 54 | 55 | withUnsafeMutablePointer(to: &length) { length in 56 | sgetrf_(length, length, &input.elements, length, &pivots, &error) 57 | sgetri_(length, &input.elements, length, &pivots, &workspace, length, &error) 58 | } 59 | 60 | if error < 0 { 61 | throw InversionError.illegalValue(matrix: self, code: error) 62 | } 63 | 64 | if error > 0 { 65 | throw InversionError.computationFailed(matrix: self, code: error) 66 | } 67 | 68 | return input 69 | } 70 | 71 | } 72 | 73 | extension ComplexMatrix where Scalar == Float { 74 | 75 | public func inverted() throws -> ComplexMatrix { 76 | precondition(shape.isSquare) 77 | 78 | var input = zip(real, imaginary).map { __CLPK_complex(r: $0, i: $1) } 79 | var length = __CLPK_integer(shape.length) 80 | var pivots = [__CLPK_integer](repeating: .zero, count: shape.length) 81 | var workspace = [__CLPK_complex](repeating: __CLPK_complex(), count: shape.length) 82 | var error: __CLPK_integer = 0 83 | 84 | withUnsafeMutablePointer(to: &length) { length in 85 | cgetrf_(length, length, &input, length, &pivots, &error) 86 | cgetri_(length, &input, length, &pivots, &workspace, length, &error) 87 | } 88 | 89 | if error < 0 { 90 | throw ComplexInversionError.illegalValue(matrix: self, code: error) 91 | } 92 | 93 | if error > 0 { 94 | throw ComplexInversionError.computationFailed(matrix: self, code: error) 95 | } 96 | 97 | return ComplexMatrix(real: .init(shape: shape, elements: input.map(\.r)), imaginary: .init(shape: shape, elements: input.map(\.i))) 98 | } 99 | 100 | } 101 | 102 | extension Matrix where Scalar == Double { 103 | 104 | public func inverted() throws -> Matrix { 105 | precondition(shape.isSquare) 106 | 107 | var input = self 108 | var length = __CLPK_integer(shape.length) 109 | var pivots = [__CLPK_integer](repeating: .zero, count: shape.length) 110 | var workspace = [Scalar](repeating: .zero, count: shape.length) 111 | var error: __CLPK_integer = 0 112 | 113 | withUnsafeMutablePointer(to: &length) { length in 114 | dgetrf_(length, length, &input.elements, length, &pivots, &error) 115 | dgetri_(length, &input.elements, length, &pivots, &workspace, length, &error) 116 | } 117 | 118 | if error < 0 { 119 | throw InversionError.illegalValue(matrix: self, code: error) 120 | } 121 | 122 | if error > 0 { 123 | throw InversionError.computationFailed(matrix: self, code: error) 124 | } 125 | 126 | return input 127 | } 128 | 129 | } 130 | 131 | extension ComplexMatrix where Scalar == Double { 132 | 133 | public func inverted() throws -> ComplexMatrix { 134 | precondition(shape.isSquare) 135 | 136 | var input = zip(real, imaginary).map { __CLPK_doublecomplex(r: $0, i: $1) } 137 | var length = __CLPK_integer(shape.length) 138 | var pivots = [__CLPK_integer](repeating: .zero, count: shape.length) 139 | var workspace = [__CLPK_doublecomplex](repeating: __CLPK_doublecomplex(), count: shape.length) 140 | var error: __CLPK_integer = 0 141 | 142 | withUnsafeMutablePointer(to: &length) { length in 143 | zgetrf_(length, length, &input, length, &pivots, &error) 144 | zgetri_(length, &input, length, &pivots, &workspace, length, &error) 145 | } 146 | 147 | if error < 0 { 148 | throw ComplexInversionError.illegalValue(matrix: self, code: error) 149 | } 150 | 151 | if error > 0 { 152 | throw ComplexInversionError.computationFailed(matrix: self, code: error) 153 | } 154 | 155 | return ComplexMatrix(real: .init(shape: shape, elements: input.map(\.r)), imaginary: .init(shape: shape, elements: input.map(\.i))) 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Multiplication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Multiplication.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 28/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | import Numerics 11 | 12 | infix operator <*>: MultiplicationPrecedence 13 | 14 | infix operator <*>=: AssignmentPrecedence 15 | 16 | extension Matrix where Scalar == Float { 17 | 18 | @inlinable public static func <*> (left: Matrix, right: Matrix) -> Matrix { 19 | return left.multiplied(by: right) 20 | } 21 | 22 | } 23 | 24 | extension Matrix where Scalar == Float { 25 | 26 | @inlinable public static func <*>= (left: inout Matrix, right: Matrix) { 27 | left = left <*> right 28 | } 29 | 30 | } 31 | 32 | extension Matrix where Scalar == Float { 33 | 34 | public func multiplied(by multiplicand: Matrix) -> Matrix { 35 | precondition(shape.columns == multiplicand.shape.rows) 36 | var output: Matrix = .zeros(shape: .init(rows: shape.rows, columns: multiplicand.shape.columns)) 37 | vDSP_mmul(elements, 1, multiplicand.elements, 1, &output.elements, 1, vDSP_Length(shape.rows), vDSP_Length(multiplicand.shape.columns), vDSP_Length(shape.columns)) 38 | return output 39 | } 40 | 41 | } 42 | 43 | extension ComplexMatrix where Scalar == Float { 44 | 45 | @inlinable public static func <*> (left: Matrix, right: ComplexMatrix) -> ComplexMatrix { 46 | return ComplexMatrix(real: left).multiplied(by: right) 47 | } 48 | 49 | @inlinable public static func <*> (left: ComplexMatrix, right: Matrix) -> ComplexMatrix { 50 | return left.multiplied(by: ComplexMatrix(real: right)) 51 | } 52 | 53 | @inlinable public static func <*> (left: ComplexMatrix, right: ComplexMatrix) -> ComplexMatrix { 54 | return left.multiplied(by: right) 55 | } 56 | 57 | } 58 | 59 | extension ComplexMatrix where Scalar == Float { 60 | 61 | @inlinable public static func <*>= (left: inout ComplexMatrix, right: Matrix) { 62 | left = left <*> ComplexMatrix(real: right) 63 | } 64 | 65 | @inlinable public static func <*>= (left: inout ComplexMatrix, right: ComplexMatrix) { 66 | left = left <*> right 67 | } 68 | 69 | } 70 | 71 | extension ComplexMatrix where Scalar == Float { 72 | 73 | public func multiplied(by multiplicand: ComplexMatrix) -> ComplexMatrix { 74 | precondition(shape.columns == multiplicand.shape.rows) 75 | var left = self 76 | var right = multiplicand 77 | var output: ComplexMatrix = .zeros(shape: .init(rows: shape.rows, columns: multiplicand.shape.columns)) 78 | left.withUnsafeMutableSplitComplexVector { leftVector in 79 | right.withUnsafeMutableSplitComplexVector { rightVector in 80 | output.withUnsafeMutableSplitComplexVector { outputVector in 81 | vDSP_zmmul(&leftVector, 1, &rightVector, 1, &outputVector, 1, vDSP_Length(shape.rows), vDSP_Length(multiplicand.shape.columns), vDSP_Length(shape.columns)) 82 | } 83 | } 84 | } 85 | return output 86 | } 87 | 88 | } 89 | 90 | extension Matrix where Scalar == Double { 91 | 92 | @inlinable public static func <*> (left: Matrix, right: Matrix) -> Matrix { 93 | return left.multiplied(by: right) 94 | } 95 | 96 | } 97 | 98 | extension Matrix where Scalar == Double { 99 | 100 | @inlinable public static func <*>= (left: inout Matrix, right: Matrix) { 101 | left = left <*> right 102 | } 103 | 104 | } 105 | 106 | extension Matrix where Scalar == Double { 107 | 108 | public func multiplied(by multiplicand: Matrix) -> Matrix { 109 | precondition(shape.columns == multiplicand.shape.rows) 110 | var output: Matrix = .zeros(shape: .init(rows: shape.rows, columns: multiplicand.shape.columns)) 111 | vDSP_mmulD(elements, 1, multiplicand.elements, 1, &output.elements, 1, vDSP_Length(shape.rows), vDSP_Length(multiplicand.shape.columns), vDSP_Length(shape.columns)) 112 | return output 113 | } 114 | 115 | } 116 | 117 | extension ComplexMatrix where Scalar == Double { 118 | 119 | @inlinable public static func <*> (left: Matrix, right: ComplexMatrix) -> ComplexMatrix { 120 | return ComplexMatrix(real: left).multiplied(by: right) 121 | } 122 | 123 | @inlinable public static func <*> (left: ComplexMatrix, right: Matrix) -> ComplexMatrix { 124 | return left.multiplied(by: ComplexMatrix(real: right)) 125 | } 126 | 127 | @inlinable public static func <*> (left: ComplexMatrix, right: ComplexMatrix) -> ComplexMatrix { 128 | return left.multiplied(by: right) 129 | } 130 | 131 | } 132 | 133 | extension ComplexMatrix where Scalar == Double { 134 | 135 | @inlinable public static func <*>= (left: inout ComplexMatrix, right: Matrix) { 136 | left = left <*> ComplexMatrix(real: right) 137 | } 138 | 139 | @inlinable public static func <*>= (left: inout ComplexMatrix, right: ComplexMatrix) { 140 | left = left <*> right 141 | } 142 | 143 | } 144 | 145 | extension ComplexMatrix where Scalar == Double { 146 | 147 | public func multiplied(by multiplicand: ComplexMatrix) -> ComplexMatrix { 148 | precondition(shape.columns == multiplicand.shape.rows) 149 | var left = self 150 | var right = multiplicand 151 | var output: ComplexMatrix = .zeros(shape: .init(rows: shape.rows, columns: multiplicand.shape.columns)) 152 | left.withUnsafeMutableSplitComplexVector { leftVector in 153 | right.withUnsafeMutableSplitComplexVector { rightVector in 154 | output.withUnsafeMutableSplitComplexVector { outputVector in 155 | vDSP_zmmulD(&leftVector, 1, &rightVector, 1, &outputVector, 1, vDSP_Length(shape.rows), vDSP_Length(multiplicand.shape.columns), vDSP_Length(shape.columns)) 156 | } 157 | } 158 | } 159 | return output 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Signal Processing/Convolution2D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Convolution2D.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 27/08/24. 6 | // 7 | 8 | import Plinth 9 | import Accelerate 10 | 11 | public enum ConvolutionFilter { } 12 | 13 | extension ConvolutionFilter where Scalar == Float { 14 | 15 | public static let blur3x3: Matrix = [ 16 | [1.0, 2.0, 1.0], 17 | [2.0, 4.0, 2.0], 18 | [1.0, 2.0, 1.0] 19 | ] / 16.0 20 | 21 | public static let blur5x5: Matrix = [ 22 | [1.0, 4.0, 6.0, 4.0, 1.0], 23 | [4.0, 16.0, 24.0, 16.0, 4.0], 24 | [6.0, 24.0, 36.0, 24.0, 6.0], 25 | [4.0, 16.0, 24.0, 16.0, 4.0], 26 | [1.0, 4.0, 6.0, 4.0, 1.0] 27 | ] / 256.0 28 | 29 | public static let sharpen3x3: Matrix = [ 30 | [0.0, -1.0, 0.0], 31 | [-1.0, 5.0, -1.0], 32 | [0.0, -1.0, 0.0] 33 | ] 34 | 35 | public static let sharpen5x5: Matrix = [ 36 | [1.0, 4.0, 6.0, 4.0, 1.0], 37 | [4.0, 16.0, 24.0, 16.0, 4.0], 38 | [6.0, 24.0, -476.0, 24.0, 6.0], 39 | [4.0, 16.0, 24.0, 16.0, 4.0], 40 | [1.0, 4.0, 6.0, 4.0, 1.0] 41 | ] / -256.0 42 | 43 | } 44 | 45 | extension Matrix where Scalar == Float { 46 | 47 | public func convolve3x3(filter: Matrix) -> Matrix { 48 | let input = self 49 | var output: Matrix = .zeros(shape: shape) 50 | input.withUnsafeBufferPointer { inputVector in 51 | output.withUnsafeMutableBufferPointer { outputVector in 52 | filter.withUnsafeBufferPointer { filterVector in 53 | vDSP_f3x3(inputVector.baseAddress!, vDSP_Length(shape.rows), vDSP_Length(shape.columns), filterVector.baseAddress!, outputVector.baseAddress!) 54 | } 55 | } 56 | } 57 | return output 58 | } 59 | 60 | public func convolve5x5(filter: Matrix) -> Matrix { 61 | let input = self 62 | var output: Matrix = .zeros(shape: shape) 63 | input.withUnsafeBufferPointer { inputVector in 64 | output.withUnsafeMutableBufferPointer { outputVector in 65 | filter.withUnsafeBufferPointer { filterVector in 66 | vDSP_f5x5(inputVector.baseAddress!, vDSP_Length(shape.rows), vDSP_Length(shape.columns), filterVector.baseAddress!, outputVector.baseAddress!) 67 | } 68 | } 69 | } 70 | return output 71 | } 72 | 73 | public func convolve2D(filter: Matrix) -> Matrix { 74 | let input = self 75 | var output: Matrix = .zeros(shape: shape) 76 | input.withUnsafeBufferPointer { inputVector in 77 | output.withUnsafeMutableBufferPointer { outputVector in 78 | filter.withUnsafeBufferPointer { filterVector in 79 | vDSP_imgfir(inputVector.baseAddress!, vDSP_Length(shape.rows), vDSP_Length(shape.columns), filterVector.baseAddress!, outputVector.baseAddress!, vDSP_Length(filter.shape.rows), vDSP_Length(filter.shape.columns)) 80 | } 81 | } 82 | } 83 | return output 84 | } 85 | 86 | } 87 | 88 | extension ConvolutionFilter where Scalar == Double { 89 | 90 | public static let blur3x3: Matrix = [ 91 | [1.0, 2.0, 1.0], 92 | [2.0, 4.0, 2.0], 93 | [1.0, 2.0, 1.0] 94 | ] / 16.0 95 | 96 | public static let blur5x5: Matrix = [ 97 | [1.0, 4.0, 6.0, 4.0, 1.0], 98 | [4.0, 16.0, 24.0, 16.0, 4.0], 99 | [6.0, 24.0, 36.0, 24.0, 6.0], 100 | [4.0, 16.0, 24.0, 16.0, 4.0], 101 | [1.0, 4.0, 6.0, 4.0, 1.0] 102 | ] / 256.0 103 | 104 | public static let sharpen3x3: Matrix = [ 105 | [0.0, -1.0, 0.0], 106 | [-1.0, 5.0, -1.0], 107 | [0.0, -1.0, 0.0] 108 | ] 109 | 110 | public static let sharpen5x5: Matrix = [ 111 | [1.0, 4.0, 6.0, 4.0, 1.0], 112 | [4.0, 16.0, 24.0, 16.0, 4.0], 113 | [6.0, 24.0, -476.0, 24.0, 6.0], 114 | [4.0, 16.0, 24.0, 16.0, 4.0], 115 | [1.0, 4.0, 6.0, 4.0, 1.0] 116 | ] / -256.0 117 | 118 | } 119 | 120 | extension Matrix where Scalar == Double { 121 | 122 | public func convolve3x3(filter: Matrix) -> Matrix { 123 | let input = self 124 | var output: Matrix = .zeros(shape: shape) 125 | input.withUnsafeBufferPointer { inputVector in 126 | output.withUnsafeMutableBufferPointer { outputVector in 127 | filter.withUnsafeBufferPointer { filterVector in 128 | vDSP_f3x3D(inputVector.baseAddress!, vDSP_Length(shape.rows), vDSP_Length(shape.columns), filterVector.baseAddress!, outputVector.baseAddress!) 129 | } 130 | } 131 | } 132 | return output 133 | } 134 | 135 | public func convolve5x5(filter: Matrix) -> Matrix { 136 | let input = self 137 | var output: Matrix = .zeros(shape: shape) 138 | input.withUnsafeBufferPointer { inputVector in 139 | output.withUnsafeMutableBufferPointer { outputVector in 140 | filter.withUnsafeBufferPointer { filterVector in 141 | vDSP_f5x5D(inputVector.baseAddress!, vDSP_Length(shape.rows), vDSP_Length(shape.columns), filterVector.baseAddress!, outputVector.baseAddress!) 142 | } 143 | } 144 | } 145 | return output 146 | } 147 | 148 | public func convolve2D(filter: Matrix) -> Matrix { 149 | let input = self 150 | var output: Matrix = .zeros(shape: shape) 151 | input.withUnsafeBufferPointer { inputVector in 152 | output.withUnsafeMutableBufferPointer { outputVector in 153 | filter.withUnsafeBufferPointer { filterVector in 154 | vDSP_imgfirD(inputVector.baseAddress!, vDSP_Length(shape.rows), vDSP_Length(shape.columns), filterVector.baseAddress!, outputVector.baseAddress!, vDSP_Length(filter.shape.rows), vDSP_Length(filter.shape.columns)) 155 | } 156 | } 157 | } 158 | return output 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Transformations/Shift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Shift.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 26/04/22. 6 | // 7 | 8 | import Foundation 9 | 10 | infix operator %%: MultiplicationPrecedence 11 | 12 | extension BinaryInteger { 13 | 14 | internal static func %% (left: Self, right: Self) -> Self { 15 | let remainder = left % right 16 | return remainder >= 0 ? remainder : remainder + right 17 | } 18 | 19 | } 20 | 21 | extension Matrix where Scalar == Float { 22 | 23 | public func shifted(rows: Int) -> Matrix { 24 | let rows = rows %% shape.rows 25 | 26 | guard rows != 0 else { 27 | return self 28 | } 29 | 30 | let inputTop = 0...(shape.rows - rows - 1) 31 | let inputBottom = (shape.rows - rows)...(shape.rows - 1) 32 | 33 | let outputTop = 0...(rows - 1) 34 | let outputBottom = (rows)...(shape.rows - 1) 35 | 36 | var output: Matrix = .zeros(shape: shape) 37 | 38 | output[rows: outputTop] = self[rows: inputBottom] 39 | output[rows: outputBottom] = self[rows: inputTop] 40 | 41 | return output 42 | } 43 | 44 | public func shifted(columns: Int) -> Matrix { 45 | let columns = columns %% shape.columns 46 | 47 | guard columns != 0 else { 48 | return self 49 | } 50 | 51 | let inputLeft = 0...(shape.columns - columns - 1) 52 | let inputRight = (shape.columns - columns)...(shape.columns - 1) 53 | 54 | let outputLeft = 0...(columns - 1) 55 | let outputRight = (columns)...(shape.columns - 1) 56 | 57 | var output: Matrix = .zeros(shape: shape) 58 | 59 | output[columns: outputLeft] = self[columns: inputRight] 60 | output[columns: outputRight] = self[columns: inputLeft] 61 | 62 | return output 63 | } 64 | 65 | public func shifted(rows: Int, columns: Int) -> Matrix { 66 | let rows = rows %% shape.rows 67 | let columns = columns %% shape.columns 68 | 69 | switch (rows, columns) { 70 | case (0, 0): 71 | return self 72 | case (_, 0): 73 | return shifted(rows: rows) 74 | case (0, _): 75 | return shifted(columns: columns) 76 | case (_, _): 77 | break 78 | } 79 | 80 | let inputTop = 0...(shape.rows - rows - 1) 81 | let inputLeft = 0...(shape.columns - columns - 1) 82 | let inputBottom = (shape.rows - rows)...(shape.rows - 1) 83 | let inputRight = (shape.columns - columns)...(shape.columns - 1) 84 | 85 | let outputTop = 0...(rows - 1) 86 | let outputLeft = 0...(columns - 1) 87 | let outputBottom = (rows)...(shape.rows - 1) 88 | let outputRight = (columns)...(shape.columns - 1) 89 | 90 | var output: Matrix = .zeros(shape: shape) 91 | 92 | output[outputTop, outputLeft] = self[inputBottom, inputRight] 93 | output[outputTop, outputRight] = self[inputBottom, inputLeft] 94 | output[outputBottom, outputLeft] = self[inputTop, inputRight] 95 | output[outputBottom, outputRight] = self[inputTop, inputLeft] 96 | 97 | return output 98 | } 99 | 100 | } 101 | 102 | extension ComplexMatrix where Scalar == Float { 103 | 104 | public func shifted(rows: Int) -> ComplexMatrix { 105 | return ComplexMatrix(real: real.shifted(rows: rows), imaginary: imaginary.shifted(rows: rows)) 106 | } 107 | 108 | public func shifted(columns: Int) -> ComplexMatrix { 109 | return ComplexMatrix(real: real.shifted(columns: columns), imaginary: imaginary.shifted(columns: columns)) 110 | } 111 | 112 | public func shifted(rows: Int, columns: Int) -> ComplexMatrix { 113 | return ComplexMatrix(real: real.shifted(rows: rows, columns: columns), imaginary: imaginary.shifted(rows: rows, columns: columns)) 114 | } 115 | 116 | } 117 | 118 | 119 | extension Matrix where Scalar == Double { 120 | 121 | public func shifted(rows: Int) -> Matrix { 122 | let rows = rows %% shape.rows 123 | 124 | guard rows != 0 else { 125 | return self 126 | } 127 | 128 | let inputTop = 0...(shape.rows - rows - 1) 129 | let inputBottom = (shape.rows - rows)...(shape.rows - 1) 130 | 131 | let outputTop = 0...(rows - 1) 132 | let outputBottom = (rows)...(shape.rows - 1) 133 | 134 | var output: Matrix = .zeros(shape: shape) 135 | 136 | output[rows: outputTop] = self[rows: inputBottom] 137 | output[rows: outputBottom] = self[rows: inputTop] 138 | 139 | return output 140 | } 141 | 142 | public func shifted(columns: Int) -> Matrix { 143 | let columns = columns %% shape.columns 144 | 145 | guard columns != 0 else { 146 | return self 147 | } 148 | 149 | let inputLeft = 0...(shape.columns - columns - 1) 150 | let inputRight = (shape.columns - columns)...(shape.columns - 1) 151 | 152 | let outputLeft = 0...(columns - 1) 153 | let outputRight = (columns)...(shape.columns - 1) 154 | 155 | var output: Matrix = .zeros(shape: shape) 156 | 157 | output[columns: outputLeft] = self[columns: inputRight] 158 | output[columns: outputRight] = self[columns: inputLeft] 159 | 160 | return output 161 | } 162 | 163 | public func shifted(rows: Int, columns: Int) -> Matrix { 164 | let rows = rows %% shape.rows 165 | let columns = columns %% shape.columns 166 | 167 | switch (rows, columns) { 168 | case (0, 0): 169 | return self 170 | case (_, 0): 171 | return shifted(rows: rows) 172 | case (0, _): 173 | return shifted(columns: columns) 174 | case (_, _): 175 | break 176 | } 177 | 178 | let inputTop = 0...(shape.rows - rows - 1) 179 | let inputLeft = 0...(shape.columns - columns - 1) 180 | let inputBottom = (shape.rows - rows)...(shape.rows - 1) 181 | let inputRight = (shape.columns - columns)...(shape.columns - 1) 182 | 183 | let outputTop = 0...(rows - 1) 184 | let outputLeft = 0...(columns - 1) 185 | let outputBottom = (rows)...(shape.rows - 1) 186 | let outputRight = (columns)...(shape.columns - 1) 187 | 188 | var output: Matrix = .zeros(shape: shape) 189 | 190 | output[outputTop, outputLeft] = self[inputBottom, inputRight] 191 | output[outputTop, outputRight] = self[inputBottom, inputLeft] 192 | output[outputBottom, outputLeft] = self[inputTop, inputRight] 193 | output[outputBottom, outputRight] = self[inputTop, inputLeft] 194 | 195 | return output 196 | } 197 | 198 | } 199 | 200 | extension ComplexMatrix where Scalar == Double { 201 | 202 | public func shifted(rows: Int) -> ComplexMatrix { 203 | return ComplexMatrix(real: real.shifted(rows: rows), imaginary: imaginary.shifted(rows: rows)) 204 | } 205 | 206 | public func shifted(columns: Int) -> ComplexMatrix { 207 | return ComplexMatrix(real: real.shifted(columns: columns), imaginary: imaginary.shifted(columns: columns)) 208 | } 209 | 210 | public func shifted(rows: Int, columns: Int) -> ComplexMatrix { 211 | return ComplexMatrix(real: real.shifted(rows: rows, columns: columns), imaginary: imaginary.shifted(rows: rows, columns: columns)) 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /Sources/Plinth/Matrix.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Matrix.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 30/03/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | public struct Matrix { 12 | 13 | public let shape: Shape 14 | public var elements: [Scalar] 15 | 16 | public init(shape: Shape, elements: [Scalar]) { 17 | self.shape = shape 18 | self.elements = elements 19 | } 20 | 21 | public init(shape: Shape, repeating element: Scalar) { 22 | self.init(shape: shape, elements: [Scalar](repeating: element, count: shape.count)) 23 | } 24 | 25 | public init(shape: Shape, _ closure: @autoclosure () throws -> Scalar) rethrows { 26 | var elements: [Scalar] = [] 27 | elements.reserveCapacity(shape.count) 28 | for _ in 0.. Scalar) rethrows { 35 | var elements: [Scalar] = [] 36 | elements.reserveCapacity(shape.count) 37 | for row in shape.rowIndices { 38 | for column in shape.columnIndices { 39 | elements.append(try closure(row, column)) 40 | } 41 | } 42 | self.init(shape: shape, elements: elements) 43 | } 44 | 45 | } 46 | 47 | extension Matrix { 48 | 49 | public init(element: Scalar) { 50 | self.init(shape: .square(length: 1), elements: [element]) 51 | } 52 | 53 | public init(row: [Scalar]) { 54 | self.init(shape: .row(length: row.count), elements: row) 55 | } 56 | 57 | public init(column: [Scalar]) { 58 | self.init(shape: .column(length: column.count), elements: column) 59 | } 60 | 61 | public init(grid: [[Scalar]]) { 62 | self.init(shape: .init(rows: grid.count, columns: grid.first?.count ?? 0), elements: Array(grid.joined())) 63 | } 64 | 65 | } 66 | 67 | extension Matrix { 68 | 69 | public static var empty: Matrix { 70 | return .init(shape: .empty, elements: []) 71 | } 72 | 73 | } 74 | 75 | extension Matrix { 76 | 77 | public enum State: CustomStringConvertible { 78 | 79 | case regular 80 | case malformed(_ malformation: Malformation) 81 | 82 | public var description: String { 83 | switch self { 84 | case .regular: 85 | return "Regular" 86 | case .malformed(let malformation): 87 | return "Malformed: \(malformation)" 88 | } 89 | } 90 | 91 | } 92 | 93 | public enum Malformation: CustomStringConvertible { 94 | 95 | case shapeIsEmpty 96 | case shapeMismatch(shape: Shape, count: Int) 97 | 98 | public var description: String { 99 | switch self { 100 | case .shapeIsEmpty: 101 | return "Shape is empty" 102 | case .shapeMismatch(let shape, let count): 103 | return "Mismatched shape and elements; \(shape) ≠ \(count)" 104 | } 105 | } 106 | 107 | } 108 | 109 | public var state: State { 110 | guard shape.isEmpty == false else { 111 | return .malformed(.shapeIsEmpty) 112 | } 113 | 114 | guard shape.count == elements.count else { 115 | return .malformed(.shapeMismatch(shape: shape, count: elements.count)) 116 | } 117 | 118 | return .regular 119 | } 120 | 121 | } 122 | 123 | extension Matrix { 124 | 125 | public var grid: [[Scalar]] { 126 | return shape.rowIndices.map { row in 127 | return Array(elements[shape.indicesFor(row: row)]) 128 | } 129 | } 130 | 131 | } 132 | 133 | extension Matrix { 134 | 135 | public subscript(row: Int, column: Int) -> Scalar { 136 | get { 137 | precondition(shape.contains(row: row, column: column)) 138 | return elements[shape.indexFor(row: row, column: column)] 139 | } 140 | set { 141 | precondition(shape.contains(row: row, column: column)) 142 | elements[shape.indexFor(row: row, column: column)] = newValue 143 | } 144 | } 145 | 146 | } 147 | 148 | extension Matrix: ExpressibleByIntegerLiteral where Scalar == IntegerLiteralType { 149 | 150 | public init(integerLiteral value: Scalar) { 151 | self.init(element: value) 152 | } 153 | 154 | } 155 | 156 | extension Matrix: ExpressibleByFloatLiteral where Scalar == FloatLiteralType { 157 | 158 | public init(floatLiteral value: Scalar) { 159 | self.init(element: value) 160 | } 161 | 162 | } 163 | 164 | extension Matrix: ExpressibleByArrayLiteral { 165 | 166 | public init(arrayLiteral elements: [Scalar]...) { 167 | self.init(grid: elements) 168 | } 169 | 170 | } 171 | 172 | extension Matrix: CustomStringConvertible where Scalar: CustomStringConvertible { 173 | 174 | public var description: String { 175 | switch state { 176 | case .regular: 177 | return "[[" + grid.map { $0.map(\.description).joined(separator: ", ") }.joined(separator: "],\n [") + "]]" 178 | case .malformed(let description): 179 | return "Malformed \(type(of: self)): \(description)" 180 | } 181 | } 182 | 183 | } 184 | 185 | extension Matrix: Equatable where Scalar: Equatable { 186 | 187 | public static func == (left: Matrix, right: Matrix) -> Bool { 188 | return left.shape == right.shape && left.elements == right.elements 189 | } 190 | 191 | } 192 | 193 | extension Matrix: Hashable where Scalar: Hashable { 194 | 195 | public func hash(into hasher: inout Hasher) { 196 | hasher.combine(shape) 197 | hasher.combine(elements) 198 | } 199 | 200 | } 201 | 202 | extension Matrix: Codable where Scalar: Codable { 203 | 204 | public init(from decoder: Decoder) throws { 205 | let container = try decoder.singleValueContainer() 206 | self.init(grid: try container.decode([[Scalar]].self)) 207 | if case .malformed(let malformation) = self.state { 208 | throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Malformed \(type(of: self)): \(malformation)")) 209 | } 210 | } 211 | 212 | public func encode(to encoder: Encoder) throws { 213 | if case .malformed(let malformation) = self.state { 214 | throw EncodingError.invalidValue(self, .init(codingPath: encoder.codingPath, debugDescription: "Malformed \(type(of: self)): \(malformation)")) 215 | } 216 | var container = encoder.singleValueContainer() 217 | try container.encode(grid) 218 | } 219 | 220 | } 221 | 222 | extension Matrix: Collection { 223 | 224 | public typealias Index = Int 225 | 226 | public var startIndex: Index { 227 | return 0 228 | } 229 | 230 | public var endIndex: Index { 231 | return elements.count 232 | } 233 | 234 | public func index(after index: Index) -> Index { 235 | return index + 1 236 | } 237 | 238 | public subscript(_ index: Index) -> Scalar { 239 | return elements[index] 240 | } 241 | 242 | } 243 | 244 | extension Matrix: BidirectionalCollection { 245 | 246 | public func index(before index: Index) -> Index { 247 | return index - 1 248 | } 249 | 250 | public func reversed() -> Matrix { 251 | return fmap { $0.reversed() } 252 | } 253 | 254 | } 255 | 256 | extension Matrix: AccelerateBuffer { 257 | 258 | @inlinable public func withUnsafeBufferPointer(_ body: (UnsafeBufferPointer) throws -> Result) rethrows -> Result { 259 | return try elements.withUnsafeBufferPointer(body) 260 | } 261 | 262 | } 263 | 264 | extension Matrix: AccelerateMutableBuffer { 265 | 266 | @inlinable public mutating func withUnsafeMutableBufferPointer(_ body: (inout UnsafeMutableBufferPointer) throws -> Result) rethrows -> Result { 267 | return try elements.withUnsafeMutableBufferPointer(body) 268 | } 269 | 270 | } 271 | -------------------------------------------------------------------------------- /Sources/Plinth/ComplexMatrix.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComplexMatrix.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 13/04/22. 6 | // 7 | 8 | import Foundation 9 | import Numerics 10 | 11 | public struct ComplexMatrix where Scalar: Real { 12 | 13 | public typealias Matrix = Plinth.Matrix 14 | public typealias Complex = Numerics.Complex 15 | 16 | public var real: Matrix 17 | public var imaginary: Matrix 18 | 19 | public init(real: Matrix, imaginary: Matrix) { 20 | self.real = real 21 | self.imaginary = imaginary 22 | } 23 | 24 | public init(shape: Shape, repeating element: Complex) { 25 | self.init(real: .init(shape: shape, repeating: element.real), imaginary: .init(shape: shape, repeating: element.imaginary)) 26 | } 27 | 28 | public init(shape: Shape, elements: [Complex]) { 29 | self.init(real: .init(shape: shape, elements: elements.map(\.real)), imaginary: .init(shape: shape, elements: elements.map(\.imaginary))) 30 | } 31 | 32 | public init(shape: Shape, _ closure: @autoclosure () throws -> Complex) rethrows { 33 | var elements: [Complex] = [] 34 | elements.reserveCapacity(shape.count) 35 | for _ in 0.. Complex) rethrows { 42 | var elements: [Complex] = [] 43 | elements.reserveCapacity(shape.count) 44 | for row in shape.rowIndices { 45 | for column in shape.columnIndices { 46 | elements.append(try closure(row, column)) 47 | } 48 | } 49 | self.init(shape: shape, elements: elements) 50 | } 51 | 52 | } 53 | 54 | extension ComplexMatrix { 55 | 56 | public init(element: Complex) { 57 | self.init(shape: .square(length: 1), elements: [element]) 58 | } 59 | 60 | public init(row: [Complex]) { 61 | self.init(shape: .row(length: row.count), elements: row) 62 | } 63 | 64 | public init(column: [Complex]) { 65 | self.init(shape: .column(length: column.count), elements: column) 66 | } 67 | 68 | public init(grid: [[Complex]]) { 69 | self.init(shape: .init(rows: grid.count, columns: grid.first?.count ?? 0), elements: Array(grid.joined())) 70 | } 71 | 72 | } 73 | 74 | extension ComplexMatrix { 75 | 76 | public static var empty: ComplexMatrix { 77 | return .init(shape: .empty, elements: []) 78 | } 79 | 80 | } 81 | 82 | extension ComplexMatrix where Scalar: Numeric { 83 | 84 | public init(real: Matrix) { 85 | self.init(real: real, imaginary: .zeros(shape: real.shape)) 86 | } 87 | 88 | public init(imaginary: Matrix) { 89 | self.init(real: .zeros(shape: imaginary.shape), imaginary: imaginary) 90 | } 91 | 92 | } 93 | 94 | extension ComplexMatrix { 95 | 96 | public enum State: CustomStringConvertible { 97 | 98 | case regular 99 | case malformed(_ malformation: Malformation) 100 | 101 | public var description: String { 102 | switch self { 103 | case .regular: 104 | return "Regular" 105 | case .malformed(let malformation): 106 | return "Malformed: \(malformation)" 107 | } 108 | } 109 | 110 | } 111 | 112 | public enum Malformation: CustomStringConvertible { 113 | 114 | case parts(_ real: Matrix.Malformation, _ imaginary: Matrix.Malformation) 115 | case realPart(_ real: Matrix.Malformation) 116 | case imaginaryPart(_ imaginary: Matrix.Malformation) 117 | case shapeMismatch(_ real: Shape, _ imaginary: Shape) 118 | 119 | public var description: String { 120 | switch self { 121 | case .parts(let real, let imaginary): 122 | return "Malformed real part: \(real); Malformed imaginary part: \(imaginary)" 123 | case .realPart(let real): 124 | return "Malformed real part: \(real)" 125 | case .imaginaryPart(let imaginary): 126 | return "Malformed imaginary part: \(imaginary)" 127 | case .shapeMismatch(let real, let imaginary): 128 | return "Shape mismatch between real and imaginary parts; \(real) ≠ \(imaginary)" 129 | } 130 | } 131 | 132 | } 133 | 134 | public var state: State { 135 | switch (real.state, imaginary.state) { 136 | case (.malformed(let real), .malformed(let imaginary)): 137 | return .malformed(.parts(real, imaginary)) 138 | case (.malformed(let real), .regular): 139 | return .malformed(.realPart(real)) 140 | case (.regular, .malformed(let imaginary)): 141 | return .malformed(.imaginaryPart(imaginary)) 142 | case (.regular, .regular): 143 | guard real.shape == imaginary.shape else { 144 | return .malformed(.shapeMismatch(real.shape, imaginary.shape)) 145 | } 146 | 147 | return .regular 148 | } 149 | } 150 | 151 | public var shape: Shape { 152 | return Shape( 153 | rows: Swift.min(real.shape.rows, imaginary.shape.rows), 154 | columns: Swift.min(real.shape.columns, imaginary.shape.columns) 155 | ) 156 | } 157 | 158 | } 159 | 160 | extension ComplexMatrix { 161 | 162 | public var elements: [Complex] { 163 | return Array(self) 164 | } 165 | 166 | public var grid: [[Complex]] { 167 | return shape.rowIndices.map { row in 168 | return Array(elements[shape.indicesFor(row: row)]) 169 | } 170 | } 171 | 172 | } 173 | 174 | extension ComplexMatrix { 175 | 176 | public subscript(row: Int, column: Int) -> Complex { 177 | get { 178 | precondition(shape.contains(row: row, column: column)) 179 | return Complex(real[row, column], imaginary[row, column]) 180 | } 181 | set { 182 | precondition(shape.contains(row: row, column: column)) 183 | real[row, column] = newValue.real 184 | imaginary[row, column] = newValue.imaginary 185 | } 186 | } 187 | 188 | } 189 | 190 | extension ComplexMatrix: ExpressibleByFloatLiteral where Scalar == FloatLiteralType { 191 | 192 | public init(floatLiteral value: Scalar) { 193 | self.init(element: Complex(value)) 194 | } 195 | 196 | } 197 | 198 | extension ComplexMatrix: ExpressibleByArrayLiteral { 199 | 200 | public init(arrayLiteral elements: [Complex]...) { 201 | self.init(grid: elements) 202 | } 203 | 204 | } 205 | 206 | extension ComplexMatrix: CustomStringConvertible where Scalar: CustomStringConvertible { 207 | 208 | public var description: String { 209 | switch state { 210 | case .regular: 211 | return "[[" + grid.map { $0.map(\.description).joined(separator: ", ") }.joined(separator: "],\n [") + "]]" 212 | case .malformed(let malformation): 213 | return "Malformed \(type(of: self)): \(malformation)" 214 | } 215 | } 216 | 217 | } 218 | 219 | extension ComplexMatrix: Equatable where Scalar: Equatable { 220 | 221 | public static func == (left: ComplexMatrix, right: ComplexMatrix) -> Bool { 222 | return left.real == right.real && left.imaginary == right.imaginary 223 | } 224 | 225 | } 226 | 227 | extension ComplexMatrix: Hashable where Scalar: Hashable { 228 | 229 | public func hash(into hasher: inout Hasher) { 230 | hasher.combine(real) 231 | hasher.combine(imaginary) 232 | } 233 | 234 | } 235 | 236 | extension ComplexMatrix: Codable where Scalar: Codable { 237 | 238 | enum CodingKeys: String, CodingKey { 239 | case real 240 | case imaginary 241 | } 242 | 243 | public init(from decoder: Decoder) throws { 244 | let container = try decoder.container(keyedBy: CodingKeys.self) 245 | self.real = try container.decode(Matrix.self, forKey: .real) 246 | self.imaginary = try container.decode(Matrix.self, forKey: .imaginary) 247 | if case .malformed(let malformation) = self.state { 248 | throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Malformed \(type(of: self)): \(malformation)")) 249 | } 250 | } 251 | 252 | public func encode(to encoder: Encoder) throws { 253 | if case .malformed(let malformation) = self.state { 254 | throw EncodingError.invalidValue(self, .init(codingPath: encoder.codingPath, debugDescription: "Malformed \(type(of: self)): \(malformation)")) 255 | } 256 | var container = encoder.container(keyedBy: CodingKeys.self) 257 | try container.encode(real, forKey: .real) 258 | try container.encode(imaginary, forKey: .imaginary) 259 | } 260 | 261 | } 262 | 263 | extension ComplexMatrix: Collection { 264 | 265 | public typealias Index = Int 266 | 267 | public var startIndex: Index { 268 | return 0 269 | } 270 | 271 | public var endIndex: Index { 272 | return Swift.min(real.count, imaginary.count) 273 | } 274 | 275 | public func index(after index: Index) -> Index { 276 | return index + 1 277 | } 278 | 279 | public subscript(_ index: Index) -> Complex { 280 | return Complex(real[index], imaginary[index]) 281 | } 282 | 283 | } 284 | 285 | extension ComplexMatrix: BidirectionalCollection { 286 | 287 | public func index(before index: Index) -> Index { 288 | return index - 1 289 | } 290 | 291 | public func reversed() -> ComplexMatrix { 292 | return ComplexMatrix(real: real.reversed(), imaginary: imaginary.reversed()) 293 | } 294 | 295 | } 296 | -------------------------------------------------------------------------------- /Sources/Plinth/Extensions/Linear Algebra/Eigendecomposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Eigendecomposition.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 9/05/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | import Numerics 11 | 12 | public enum EigendecompositionError: LocalizedError { 13 | 14 | case illegalValue(matrix: Matrix, code: Int32) 15 | case computationFailed(matrix: Matrix, code: Int32) 16 | 17 | public var errorDescription: String? { 18 | switch self { 19 | case .illegalValue(_, let code): 20 | return "Matrix eigendecomposition error: Illegal value (\(code))" 21 | case .computationFailed(_, let code): 22 | return "Matrix eigendecomposition error: Computation failed (\(code))" 23 | } 24 | } 25 | 26 | } 27 | 28 | public struct EigendecompositionComponents { 29 | 30 | internal enum Computed { 31 | 32 | case computed 33 | case notComputed 34 | 35 | internal var flag: [CChar] { 36 | switch self { 37 | case .computed: 38 | return [0x56, 0x00] 39 | case .notComputed: 40 | return [0x4E, 0x00] 41 | } 42 | } 43 | 44 | } 45 | 46 | internal let leftEigenvectors: Computed 47 | internal let rightEigenvectors: Computed 48 | 49 | public static let eigenvalues = EigendecompositionComponents(leftEigenvectors: .notComputed, rightEigenvectors: .notComputed) 50 | public static let leftEigenvectors = EigendecompositionComponents(leftEigenvectors: .computed, rightEigenvectors: .notComputed) 51 | public static let rightEigenvectors = EigendecompositionComponents(leftEigenvectors: .notComputed, rightEigenvectors: .computed) 52 | public static let allComponents = EigendecompositionComponents(leftEigenvectors: .computed, rightEigenvectors: .computed) 53 | 54 | } 55 | 56 | public struct Eigendecomposition where Scalar: Real { 57 | 58 | public let leftEigenvectors: ComplexMatrix 59 | public let eigenvalues: ComplexMatrix 60 | public let rightEigenvectors: ComplexMatrix 61 | 62 | internal init(leftEigenvectors: ComplexMatrix, eigenvalues: ComplexMatrix, rightEigenvectors: ComplexMatrix) { 63 | self.leftEigenvectors = leftEigenvectors 64 | self.eigenvalues = eigenvalues 65 | self.rightEigenvectors = rightEigenvectors 66 | } 67 | 68 | internal init(leftWorkspace: Matrix, eigenvalues: ComplexMatrix, rightWorkspace: Matrix) { 69 | self.leftEigenvectors = Self.eigenvectors(from: leftWorkspace, eigenvalues: eigenvalues) 70 | self.eigenvalues = eigenvalues 71 | self.rightEigenvectors = Self.eigenvectors(from: rightWorkspace, eigenvalues: eigenvalues) 72 | } 73 | 74 | private static func eigenvectors(from workspace: Matrix, eigenvalues: ComplexMatrix) -> ComplexMatrix { 75 | var eigenvectors: ComplexMatrix = .zeros(shape: workspace.shape) 76 | for row in workspace.shape.rowIndices { 77 | var column = 0 78 | while column < workspace.shape.columns { 79 | let eigenvalue = eigenvalues[0, column] 80 | switch eigenvalue.imaginary.isZero || eigenvalue.imaginary.isNaN { 81 | case true: 82 | eigenvectors[row, column] = Complex(workspace[row, column], .zero) 83 | column += 1 84 | case false: 85 | eigenvectors[row, column] = Complex(workspace[row, column], workspace[row, column + 1]) 86 | eigenvectors[row, column + 1] = Complex(workspace[row, column], -workspace[row, column + 1]) 87 | column += 2 88 | } 89 | } 90 | } 91 | return eigenvectors 92 | } 93 | 94 | } 95 | 96 | extension Eigendecomposition { 97 | 98 | public enum SortOrder { 99 | 100 | case realAscending 101 | case realDescending 102 | case magnitudeAscending 103 | case magnitudeDescending 104 | 105 | internal func areInIncreasingOrder(_ lhs: Complex, _ rhs: Complex) -> Bool { 106 | switch self { 107 | case .realAscending: 108 | return lhs.real < rhs.real 109 | case .realDescending: 110 | return lhs.real > rhs.real 111 | case .magnitudeAscending: 112 | return lhs.magnitude < rhs.magnitude 113 | case .magnitudeDescending: 114 | return lhs.magnitude > rhs.magnitude 115 | } 116 | } 117 | 118 | } 119 | 120 | public func sorted(_ sortOrder: SortOrder) -> Eigendecomposition { 121 | return sorted(by: sortOrder.areInIncreasingOrder) 122 | } 123 | 124 | public func sorted(by areInIncreasingOrder: (Complex, Complex) throws -> Bool) rethrows -> Eigendecomposition { 125 | let sorted = try eigenvalues.enumerated().sorted { lhs, rhs in 126 | return try areInIncreasingOrder(lhs.element, rhs.element) 127 | } 128 | 129 | return Eigendecomposition( 130 | leftEigenvectors: .init(shape: leftEigenvectors.shape) { row, column in 131 | return leftEigenvectors[sorted[row].offset, column] 132 | }, 133 | eigenvalues: ComplexMatrix(row: sorted.map(\.element)), 134 | rightEigenvectors: .init(shape: rightEigenvectors.shape) { row, column in 135 | return rightEigenvectors[row, sorted[column].offset] 136 | } 137 | ) 138 | } 139 | 140 | } 141 | 142 | extension Matrix where Scalar == Float { 143 | 144 | public func eigendecomposition(computing components: EigendecompositionComponents = .allComponents) throws -> Eigendecomposition { 145 | precondition(shape.isSquare) 146 | 147 | var input = transposed() 148 | var length = __CLPK_integer(shape.length) 149 | var leftFlag = components.leftEigenvectors.flag 150 | var rightFlag = components.rightEigenvectors.flag 151 | 152 | var eigenvalues = ComplexMatrix.zeros(shape: .row(length: shape.length)) 153 | var leftWorkspace = Matrix.zeros(shape: shape) 154 | var rightWorkspace = Matrix.zeros(shape: shape) 155 | 156 | var workspace = [Scalar]() 157 | var workspaceQuery = Scalar(0.0) 158 | var workspaceSize = __CLPK_integer(-1) 159 | var error = __CLPK_integer(0) 160 | 161 | withUnsafeMutablePointer(to: &length) { length in 162 | sgeev_(&leftFlag, &rightFlag, length, &input.elements, length, &eigenvalues.real.elements, &eigenvalues.imaginary.elements, &leftWorkspace.elements, length, &rightWorkspace.elements, length, &workspaceQuery, &workspaceSize, &error) 163 | 164 | workspace = [Scalar](repeating: .zero, count: Int(workspaceQuery)) 165 | workspaceSize = __CLPK_integer(workspaceQuery) 166 | 167 | sgeev_(&leftFlag, &rightFlag, length, &input.elements, length, &eigenvalues.real.elements, &eigenvalues.imaginary.elements, &leftWorkspace.elements, length, &rightWorkspace.elements, length, &workspace, &workspaceSize, &error) 168 | } 169 | 170 | if error < 0 { 171 | throw EigendecompositionError.illegalValue(matrix: self, code: error) 172 | } 173 | 174 | if error > 0 { 175 | throw EigendecompositionError.computationFailed(matrix: self, code: error) 176 | } 177 | 178 | return Eigendecomposition( 179 | leftWorkspace: leftWorkspace.transposed(), 180 | eigenvalues: eigenvalues, 181 | rightWorkspace: rightWorkspace.transposed() 182 | ) 183 | } 184 | 185 | } 186 | 187 | extension Matrix where Scalar == Double { 188 | 189 | public func eigendecomposition(computing components: EigendecompositionComponents = .allComponents) throws -> Eigendecomposition { 190 | precondition(shape.isSquare) 191 | 192 | var input = transposed() 193 | var length = __CLPK_integer(shape.length) 194 | var leftFlag = components.leftEigenvectors.flag 195 | var rightFlag = components.rightEigenvectors.flag 196 | 197 | var eigenvalues = ComplexMatrix.zeros(shape: .row(length: shape.length)) 198 | var leftWorkspace = Matrix.zeros(shape: shape) 199 | var rightWorkspace = Matrix.zeros(shape: shape) 200 | 201 | var workspace = [Scalar]() 202 | var workspaceQuery = Scalar(0.0) 203 | var workspaceSize = __CLPK_integer(-1) 204 | var error = __CLPK_integer(0) 205 | 206 | withUnsafeMutablePointer(to: &length) { length in 207 | dgeev_(&leftFlag, &rightFlag, length, &input.elements, length, &eigenvalues.real.elements, &eigenvalues.imaginary.elements, &leftWorkspace.elements, length, &rightWorkspace.elements, length, &workspaceQuery, &workspaceSize, &error) 208 | 209 | workspace = [Scalar](repeating: .zero, count: Int(workspaceQuery)) 210 | workspaceSize = __CLPK_integer(workspaceQuery) 211 | 212 | dgeev_(&leftFlag, &rightFlag, length, &input.elements, length, &eigenvalues.real.elements, &eigenvalues.imaginary.elements, &leftWorkspace.elements, length, &rightWorkspace.elements, length, &workspace, &workspaceSize, &error) 213 | } 214 | 215 | if error < 0 { 216 | throw EigendecompositionError.illegalValue(matrix: self, code: error) 217 | } 218 | 219 | if error > 0 { 220 | throw EigendecompositionError.computationFailed(matrix: self, code: error) 221 | } 222 | 223 | return Eigendecomposition( 224 | leftWorkspace: leftWorkspace.transposed(), 225 | eigenvalues: eigenvalues, 226 | rightWorkspace: rightWorkspace.transposed() 227 | ) 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plinth 2 | 3 | Hardware-accelerated matrix/numeric programming library for Swift. 4 | 5 | ```swift 6 | let frumb: Matrix = .random(shape: .square(length: 4), in: -1.0...1.0) 7 | let brumf = frumb <*> frumb.transposed() 8 | ``` 9 | 10 | ## Installation 11 | 12 | ### Swift Package Manager 13 | 14 | Simply add Plinth to your `Package.swift` file: 15 | 16 | ```swift 17 | let package = Package( 18 | name: "Example", 19 | dependencies: [ 20 | .package(url: "https://github.com/dclelland/Plinth.git", from: "2.0.0"), 21 | ], 22 | targets: [ 23 | .target(name: "Example", dependencies: ["Plinth"]) 24 | ] 25 | ) 26 | ``` 27 | 28 | Then import Plinth into your Swift files: 29 | 30 | ```swift 31 | import Plinth 32 | ``` 33 | 34 | Or for full [`ComplexMatrix`](#complexmatrix) support you should also import [`swift-numerics`](https://github.com/apple/swift-numerics), as that's where the [`Complex`](https://github.com/apple/swift-numerics/blob/main/Sources/ComplexModule/Complex.swift) type lives. 35 | 36 | ```swift 37 | import Plinth 38 | import Numerics 39 | ``` 40 | 41 | ## Links 42 | 43 | ### Dependencies 44 | 45 | - [apple/swift-numerics](https://github.com/apple/swift-numerics) 46 | 47 | ### References/prior art 48 | 49 | - https://github.com/apple/swift-numerics/issues/6 (discussion on adding a `ShapedArray` type to `swift-numerics`) 50 | - [Jounce/Surge](https://github.com/Jounce/Surge) 51 | - [hollance/Matrix](https://github.com/hollance/Matrix) 52 | - [stsievert/swix](https://github.com/stsievert/swix) 53 | - [cgarciae/NDArray](https://github.com/cgarciae/NDArray) 54 | 55 | ## Todo 56 | 57 | - [ ] ~~Add Cocoapods support~~ Can't do this, `swift-numerics` only supports SPM. I'd have to make my own `Complex` type. 58 | - [x] Implement Equality/Comparisons extension 59 | - [x] Implement both `.zeros` and `.ones` initializers 60 | - [x] Implement exception handling for LAPACK calls 61 | - [x] Revisit `Eigendecomposition.sorted`, is sorting the eigenvalues by real component or the magnitude preferable? 62 | - [x] Implement wrapper for `vDSP.ramp` 63 | - [x] Implement wrapper for `vDSP.convolve` 64 | - [ ] Implement wrappers for `vDSP.fill`, `vDSP.clear`, `vDSP.window`, `vDSP.stereoRamp` 65 | - [ ] Implement API for specifying seeds for LAPACK random number generator calls. 66 | - Note the LAPACK specifications: "ISEED is INTEGER array, dimension (4). On entry, the seed of the random number generator; the array elements must be between 0 and 4095, and ISEED(4) must be odd." 67 | - [ ] Write code examples 68 | 69 | ## Philosophy 70 | 71 | Plinth's philosophy is to do "the simplest thing that works". For example, many of the arithmetic/numeric functions are implemented twice – for both `Matrix` and `Matrix` – instead of managing this through a morass of protocols and protocol implementations, we just implement the given function twice using copy and paste. Plinth is not [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). 72 | 73 | # Documentation 74 | 75 | ## Types 76 | 77 | ### [Shape](Sources/Plinth/Shape.swift) 78 | 79 | Defines the shape of a matrix using `rows` and `columns` properties. 80 | 81 | ```swift 82 | public struct Shape { 83 | 84 | public let rows: Int 85 | public let columns: Int 86 | 87 | } 88 | ``` 89 | 90 | This includes a number of convenience properties like `count`, `length` and `breadth`; as well as convenience initializers `.row(length:)`, `.column(length:)` and `.square(length:)`. 91 | 92 | ### [Matrix](Sources/Plinth/Matrix.swift) 93 | 94 | Generic matrix struct with `Scalar` type argument and `shape` and `elements` properties. Elements are stored as a single array in row-major format. 95 | 96 | ```swift 97 | public struct Matrix { 98 | 99 | public let shape: Shape 100 | public var elements: [Scalar] 101 | 102 | } 103 | ``` 104 | 105 | This also includes a large number of convenience initializers and implementations of typical typeclasses such as `Codable` and `ExpressibleByArrayLiteral`. 106 | 107 | The `elements` property is directly mutable but this is ideally to be avoided; matrix regularity is not enforced, except during encoding to or decoding from a serialization format. 108 | 109 | There is a computed property `state` which can be used to check if the matrix is considered to be malformed: 110 | 111 | ```swift 112 | let malformed = Matrix( 113 | shape: .init(rows: 2, columns: 2), 114 | elements: [1.0, 2.0, 3.0, 4.0, 5.0] 115 | ) 116 | 117 | print(malformed.state) 118 | ``` 119 | 120 | ``` 121 | > Malformed: Mismatched shape and elements; 2×2 != 5 122 | ``` 123 | 124 | ### [ComplexMatrix](Sources/Plinth/ComplexMatrix.swift) 125 | 126 | Generic complex matrix struct encapsulating two separate matrices for the `real` and `imaginary` parts. 127 | 128 | ```swift 129 | public struct ComplexMatrix where Scalar: Real { 130 | 131 | public var real: Matrix 132 | public var imaginary: Matrix 133 | 134 | } 135 | ``` 136 | 137 | This also includes a large number of convenience initializers and implementations of typical typeclasses such as `Codable` and `ExpressibleByArrayLiteral`. 138 | 139 | The `real` and `imaginary` properties are also directly mutable; `ComplexMatrix` has its own `state` property which can be used to check if the parts are mismatched or malformed. 140 | 141 | ## Core 142 | 143 | ### [Arithmetic](Sources/Plinth/Core/Arithmetic.swift) 144 | 145 | `+` and `-` prefix operators and `+`, `-`, `*`, `/` infix operators. 146 | 147 | Implements fast pointwise arithmetic for combinations of `Scalar`, `Complex`, `Matrix` and `ComplexMatrix`, where `Scalar` is `Float` or `Double`. 148 | 149 | ### [Conversions](Sources/Plinth/Core/Conversions.swift) 150 | 151 | Fast type conversions between the integer types `UInt8`, `UInt16`, `UInt32`, `Int8`, `Int16`, `Int32` and the floating point types `Float` and `Double`. 152 | 153 | ### [Functors](Sources/Plinth/Core/Functors.swift) 154 | 155 | Higher-order functions for shape-preserving operations on a matrix's elements. 156 | 157 | Includes support for complex matrix operations on [`DSPSplitComplex`](https://developer.apple.com/documentation/accelerate/dspsplitcomplex)/[`DSPDoubleSplitComplex`](https://developer.apple.com/documentation/accelerate/dspdoublesplitcomplex). 158 | 159 | > Disclaimer: These are not true functors, Swift lacks higher-kinded types... 160 | 161 | ### [Submatrix](Sources/Plinth/Core/Submatrix.swift) 162 | 163 | Fast submatrix read/write access using a Swift subscript interface. 164 | 165 | > Uses Accelerate's [`vDSP_mmov`](https://developer.apple.com/documentation/accelerate/1449950-vdsp_mmov)/[`vDSP_mmovD`](https://developer.apple.com/documentation/accelerate/1449956-vdsp_mmovd). 166 | 167 | ### [Wrappers](Sources/Plinth/Core/Wrappers.swift) 168 | 169 | Wrappers over most of the basic `vDSP` and `vForce` functions in Accelerate. 170 | 171 | ## Transformations 172 | 173 | ### [Center](Sources/Plinth/Extensions/Transformations/Center.swift) 174 | 175 | Find the center point of a matrix, given a rounding rule. 176 | 177 | ### [Concatenate](Sources/Plinth/Extensions/Transformations/Concatenate.swift) 178 | 179 | Concatentate multiple matrices together, row-wise or column-wise. 180 | 181 | ### [Crop](Sources/Plinth/Extensions/Transformations/Crop.swift) 182 | 183 | Crop a matrix towards the center, given a rounding rule. 184 | 185 | ### [Pad](Sources/Plinth/Extensions/Transformations/Pad.swift) 186 | 187 | Zero-pad a matrix away from the center, given a rounding rule. 188 | 189 | ### [Repeat](Sources/Plinth/Extensions/Transformations/Repeat.swift) 190 | 191 | Repeat the elements in a matrix as rows or columns. 192 | 193 | ### [Reshape](Sources/Plinth/Extensions/Transformations/Reshape.swift) 194 | 195 | Apply a new shape to a matrix, or reshape it as a single row or column. 196 | 197 | This also supports both `.rowMajor` and `.columnMajor` orderings. 198 | 199 | ### [Reverse](Sources/Plinth/Extensions/Transformations/Reverse.swift) 200 | 201 | Reverse a matrix's elements, rows, or columns. 202 | 203 | ### [Shift](Sources/Plinth/Extensions/Transformations/Shift.swift) 204 | 205 | Apply a circular shift to a matrix. 206 | 207 | ## Mathematics 208 | 209 | ### [Comparisons](Sources/Plinth/Extensions/Mathematics/Comparisons.swift) 210 | 211 | `<`, `<=`, `>`, `>=`, `==`, `!==` infix operators. 212 | 213 | Pointwise comparison or equality checks, returning `0.0` for `false` and `1.0` for `true`. 214 | 215 | ### [Interpolation](Sources/Plinth/Extensions/Mathematics/Interpolation.swift) 216 | 217 | Linear interpolate values from a given range to/from `0.0...1.0`. 218 | 219 | > This is similar to C++'s [`std::lerp`](https://en.cppreference.com/w/cpp/numeric/lerp). 220 | 221 | ### [Powers](Sources/Plinth/Extensions/Mathematics/Powers.swift) 222 | 223 | `**` infix operator. 224 | 225 | Implements fast pointwise power operations for `Scalar` and `Matrix`. 226 | 227 | Includes special functions for taking integer powers of matrices, for use when recursive application of `vDSP.multiply` will be faster than `vForce.pow` (which is quite an expensive operation). 228 | 229 | This also supports negative integers by applying `vForce.reciprocal` to the result. 230 | 231 | ### [Ramps](Sources/Plinth/Extensions/Mathematics/Ramps.swift) 232 | 233 | Generate matrices which ramp from the start to end of a range of values, along cartesian or polar coordinates. 234 | 235 | ## Statistics 236 | 237 | ### [Random](Sources/Plinth/Extensions/Statistics/Random.swift) 238 | 239 | Generate matrices populated with random noise using the Swift random number generators or LAPACK functions for faster generation within set distributions. 240 | 241 | > Uses LAPACK's [`slarnv_`](https://www.netlib.org/lapack/explore-html/d5/dd2/group__larnv_ga46eb9295314bab79acf8821078c29d18.html)/[`dlarnv_`](https://www.netlib.org/lapack/explore-html/d5/dd2/group__larnv_ga768743496c909a18850020a8ce0382b4.html) for real matrices and [`clarnv_`](https://www.netlib.org/lapack/explore-html/d5/dd2/group__larnv_ga72a1ed9b1c8b1417e5d20234c3b7683d.html)/[`zlarnv_`](https://www.netlib.org/lapack/explore-html/d5/dd2/group__larnv_ga6c82846cc4856910b261619be101560c.html) for complex matrices. 242 | 243 | ### [Gaussian](Sources/Plinth/Extensions/Statistics/Gaussian.swift) 244 | 245 | Generate matrices populated with Gaussian noise using the Swift random number generators. 246 | 247 | > This is derived from an answer on the [comp.lang.c FAQ](http://c-faq.com/lib/gaussian.html). 248 | 249 | ### [Moments](Sources/Plinth/Extensions/Statistics/Moments.swift) 250 | 251 | Calculate central and standardized moments; convenience methods for `variance`, `standardDeviation`, `skewness`, and `kurtosis`. 252 | 253 | ### [Normalization](Sources/Plinth/Extensions/Statistics/Normalization.swift) 254 | 255 | Normalize a matrix to `0.0...1.0` using its `minimum()` and `maximum()` values; or shift it so that its `mean()` is centered on zero. 256 | 257 | ## Linear Algebra 258 | 259 | ### [Zeros](Sources/Plinth/Extensions/Linear%20Algebra/Zeros.swift) 260 | 261 | Generate matrices populated by zeros. 262 | 263 | ### [Ones](Sources/Plinth/Extensions/Linear%20Algebra/Ones.swift) 264 | 265 | Generate matrices populated by ones. 266 | 267 | ### [Identity](Sources/Plinth/Extensions/Linear%20Algebra/Identity.swift) 268 | 269 | Generate identity matrices. 270 | 271 | ### [Diagonal](Sources/Plinth/Extensions/Linear%20Algebra/Diagonal.swift) 272 | 273 | Generate diagonal matrices. 274 | 275 | ### [Circulant](Sources/Plinth/Extensions/Linear%20Algebra/Circulant.swift) 276 | 277 | Generate circulant matrices. 278 | 279 | ### [Transposition](Sources/Plinth/Extensions/Linear%20Algebra/Transposition.swift) 280 | 281 | Transpose a matrix. 282 | 283 | > Uses Accelerate's [`vDSP_mtrans`](https://developer.apple.com/documentation/accelerate/1449988-vdsp_mtrans)/[`vDSP_mtransD`](https://developer.apple.com/documentation/accelerate/1450422-vdsp_mtransd). 284 | 285 | ### [Inversion](Sources/Plinth/Extensions/Linear%20Algebra/Inversion.swift) 286 | 287 | Calculate the inverse of a matrix. 288 | 289 | > Uses LAPACK's [`sgetri_`](http://www.netlib.org/lapack/explore-html/d8/ddc/group__real_g_ecomputational_ga1af62182327d0be67b1717db399d7d83.html)/[`dgetri_`](http://www.netlib.org/lapack/explore-html/dd/d9a/group__double_g_ecomputational_ga56d9c860ce4ce42ded7f914fdb0683ff.html) for real matrices and [`cgetri_`](http://www.netlib.org/lapack/explore-html/d4/d7e/group__complex_g_ecomputational_gae22ce12a3734b080ad8369ebf7e9c3a7.html)/[`zgetri_`](http://www.netlib.org/lapack/explore-html/d3/d01/group__complex16_g_ecomputational_gab490cfc4b92edec5345479f19a9a72ca.html) for complex matrices. 290 | 291 | ### [Multiplication](Sources/Plinth/Extensions/Linear%20Algebra/Multiplication.swift) 292 | 293 | `<*>` infix operator. 294 | 295 | Implements matrix multiplication. 296 | 297 | > Uses Accelerate's [`vDSP_mmul`](https://developer.apple.com/documentation/accelerate/1449984-vdsp_mmul)/[`vDSP_mmulD`](https://developer.apple.com/documentation/accelerate/1450386-vdsp_mmuld) for real matrices and [`vDSP_zmmul`](https://developer.apple.com/documentation/accelerate/1449712-vdsp_zmmul)/[`vDSP_zmmulD`](https://developer.apple.com/documentation/accelerate/1450796-vdsp_zmmuld) for complex matrices. 298 | 299 | ### [Division](Sources/Plinth/Extensions/Linear%20Algebra/Division.swift) 300 | 301 | `/>` and ` Formula taken from MATLAB's [`sqrt`](https://www.mathworks.com/help/matlab/ref/sqrt.html) function. 310 | 311 | ### [Exponentiation](Sources/Plinth/Extensions/Mathematics/Exponentiation.swift) 312 | 313 | Complex exponentials. 314 | 315 | > Formula taken from MATLAB's [`exp`](https://www.mathworks.com/help/matlab/ref/exp.html) function. 316 | 317 | ### [Products](Sources/Plinth/Extensions/Linear%20Algebra/Products.swift) 318 | 319 | Inner and outer products. 320 | 321 | ### [Eigendecomposition](Sources/Plinth/Extensions/Linear%20Algebra/Eigendecomposition.swift) 322 | 323 | Calculate the eigendecomposition of a matrix. Includes support for only calculating the necessary components. Also includes support for sorting the eigenvectors by properties of the eigenvalues. 324 | 325 | > Uses LAPACK's [`sgeev_`](http://www.netlib.org/lapack/explore-html/d3/dfb/group__real_g_eeigen_ga104525b749278774f7b7f57195aa6798.html)/[`dgeev_`](http://www.netlib.org/lapack/explore-html/d9/d8e/group__double_g_eeigen_ga66e19253344358f5dee1e60502b9e96f.html). 326 | > Swift implementation cribbed from [Surge](https://github.com/Jounce/Surge/blob/master/Sources/Surge/Linear%20Algebra/Matrix.swift#L944). 327 | 328 | ### [Roots](Sources/Plinth/Extensions/Linear%20Algebra/Roots.swift) 329 | 330 | Calculate the roots of a polynomial by taking the eigenvalues of a companion matrix. 331 | 332 | ## Signal Processing 333 | 334 | ### [FFT](Sources/Plinth/Extensions/Signal%20Processing/FFT.swift) 335 | 336 | Includes support for creating, reusing, and destroying your own [`FFTSetup`](https://developer.apple.com/documentation/accelerate/fftsetup)/[`FFTSetupD`](https://developer.apple.com/documentation/accelerate/fftsetupd) structure. 337 | 338 | ### [FFT1D](Sources/Plinth/Extensions/Signal%20Processing/FFT1D.swift) 339 | 340 | Forward and inverse one-dimensional fourier transforms. 341 | 342 | Some of the inverse fourier transform methods implement energy conservation by dividing by the size of the matrix. 343 | 344 | > Uses Accelerate's [`vDSP_fft_zip`](https://developer.apple.com/documentation/accelerate/1450224-vdsp_fft_zip)/[`vDSP_fft_zipD`](https://developer.apple.com/documentation/accelerate/1449916-vdsp_fft_zipd). 345 | 346 | ### [FFT2D](Sources/Plinth/Extensions/Signal%20Processing/FFT2D.swift) 347 | 348 | Forward and inverse two-dimensional fourier transforms. 349 | 350 | Some of the inverse fourier transform methods implement energy conservation by dividing by the size of the matrix. 351 | 352 | > Uses Accelerate's [`vDSP_fft2d_zip`](https://developer.apple.com/documentation/accelerate/1450430-vdsp_fft2d_zip)/[`vDSP_fft2d_zipD`](https://developer.apple.com/documentation/accelerate/1450508-vdsp_fft2d_zipd). 353 | 354 | ### [FFTShift](Sources/Plinth/Extensions/Signal%20Processing/FFTShift.swift) 355 | 356 | Apply a circular rotation to a frequency-domain matrix so that the DC/DC signal is at the top left of the lower right quadrant. 357 | 358 | ### [FFTRamp](Sources/Plinth/Extensions/Signal%20Processing/FFTRamp.swift) 359 | 360 | Generate centered ramps and ramps aligned to the FFT layout, for use when masking FFT signals. 361 | 362 | ### [Autocorrelation](Sources/Plinth/Extensions/Signal%20Processing/Autocorrelation.swift) 363 | 364 | Calculate the autocorrelation of a matrix by taking the product of the spectrum with its complex conjugate or magnitudes. 365 | 366 | ### [Autoconvolution](Sources/Plinth/Extensions/Signal%20Processing/Autoconvolution.swift) 367 | 368 | Calculate the autoconvolution of a matrix by taking the square of the spectrum or its magnitudes. 369 | 370 | ### [Convolution1D](Sources/Plinth/Extensions/Signal%20Processing/Convolution1D.swift) 371 | 372 | Calculate convolutions using one-dimensional kernels. 373 | 374 | ### [Convolution2D](Sources/Plinth/Extensions/Signal%20Processing/Convolution2D.swift) 375 | 376 | Calculate convolutions using two-dimensional kernels. 377 | 378 | ### [Resampling](Sources/Plinth/Extensions/Signal%20Processing/Resampling.swift) 379 | 380 | Upsample and downsample signals, with an optional anti-aliasing filter. 381 | -------------------------------------------------------------------------------- /Sources/Plinth/Core/Wrappers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // vDSP.swift 3 | // Plinth 4 | // 5 | // Created by Daniel Clelland on 30/04/22. 6 | // 7 | 8 | import Foundation 9 | import Accelerate 10 | 11 | extension Matrix where Scalar == Float { 12 | 13 | public func maximum() -> Scalar { 14 | return fmap(vDSP.maximum) 15 | } 16 | 17 | public func maximumMagnitude() -> Scalar { 18 | return fmap(vDSP.maximumMagnitude) 19 | } 20 | 21 | public func minimum() -> Scalar { 22 | return fmap(vDSP.minimum) 23 | } 24 | 25 | public func sum() -> Scalar { 26 | return fmap(vDSP.sum) 27 | } 28 | 29 | public func sumOfSquares() -> Scalar { 30 | return fmap(vDSP.sumOfSquares) 31 | } 32 | 33 | public func sumAndSumOfSquares() -> (elementsSum: Scalar, squaresSum: Scalar) { 34 | return fmap(vDSP.sumAndSumOfSquares) 35 | } 36 | 37 | public func sumOfMagnitudes() -> Scalar { 38 | return fmap(vDSP.sumOfMagnitudes) 39 | } 40 | 41 | } 42 | 43 | extension Matrix where Scalar == Float { 44 | 45 | public func indexOfMaximum() -> (UInt, Float) { 46 | return fmap(vDSP.indexOfMaximum) 47 | } 48 | 49 | public func indexOfMaximumMagnitude() -> (UInt, Float) { 50 | return fmap(vDSP.indexOfMaximumMagnitude) 51 | } 52 | 53 | public func indexOfMinimum() -> (UInt, Float) { 54 | return fmap(vDSP.indexOfMinimum) 55 | } 56 | 57 | } 58 | 59 | extension Matrix where Scalar == Float { 60 | 61 | public func meanSquare() -> Scalar { 62 | return fmap(vDSP.meanSquare) 63 | } 64 | 65 | public func meanMagnitude() -> Scalar { 66 | return fmap(vDSP.meanMagnitude) 67 | } 68 | 69 | public func mean() -> Scalar { 70 | return fmap(vDSP.mean) 71 | } 72 | 73 | public func rootMeanSquare() -> Scalar { 74 | return fmap(vDSP.rootMeanSquare) 75 | } 76 | 77 | } 78 | 79 | extension Matrix where Scalar == Float { 80 | 81 | public func clip(to bounds: ClosedRange) -> Matrix { 82 | return fmap { vDSP.clip($0, to: bounds) } 83 | } 84 | 85 | public func invertedClip(to bounds: ClosedRange) -> Matrix { 86 | return fmap { vDSP.invertedClip($0, to: bounds) } 87 | } 88 | 89 | public func threshold(to lowerBound: Scalar, with rule: vDSP.ThresholdRule) -> Matrix { 90 | return fmap { vDSP.threshold($0, to: lowerBound, with: rule) } 91 | } 92 | 93 | public func limit(_ limit: Scalar, withOutputConstant outputConstant: Scalar) -> Matrix { 94 | return fmap { vDSP.limit($0, limit: limit, withOutputConstant: outputConstant) } 95 | } 96 | 97 | } 98 | 99 | extension Matrix where Scalar == Float { 100 | 101 | public func dot(_ vector: Matrix) -> Scalar { 102 | return fmap { vDSP.dot($0, vector) } 103 | } 104 | 105 | public func hypot(_ y: Matrix) -> Matrix { 106 | return fmap { vDSP.hypot($0, y) } 107 | } 108 | 109 | public func hypot(x1: Matrix, y0: Matrix, y1: Matrix) -> Matrix { 110 | return fmap { vDSP.hypot(x0: $0, x1: x1, y0: y0, y1: y1) } 111 | } 112 | 113 | public func distanceSquared(_ point: Matrix) -> Scalar { 114 | return fmap { vDSP.distanceSquared($0, point) } 115 | } 116 | 117 | } 118 | 119 | extension Matrix where Scalar == Float { 120 | 121 | public func evaluatePolynomial(usingCoefficients coefficients: [Scalar]) -> Matrix { 122 | return fmap { vDSP.evaluatePolynomial(usingCoefficients: coefficients, withVariables: $0) } 123 | } 124 | 125 | } 126 | 127 | extension Matrix where Scalar == Float { 128 | 129 | public func linearInterpolate(_ vector: Matrix, using interpolationConstant: Scalar) -> Matrix { 130 | return fmap { vDSP.linearInterpolate($0, vector, using: interpolationConstant) } 131 | } 132 | 133 | public func linearInterpolate(using controlVector: Matrix) -> Matrix { 134 | return fmap { vDSP.linearInterpolate(elementsOf: $0, using: controlVector) } 135 | } 136 | 137 | } 138 | 139 | extension Matrix where Scalar == Float { 140 | 141 | public func minimum(_ vector: Matrix) -> Matrix { 142 | return fmap { vDSP.minimum($0, vector.elements) } 143 | } 144 | 145 | public func maximum(_ vector: Matrix) -> Matrix { 146 | return fmap { vDSP.maximum($0, vector.elements) } 147 | } 148 | 149 | } 150 | 151 | extension Matrix where Scalar == Float { 152 | 153 | public func absolute() -> Matrix { 154 | return fmap(vDSP.absolute) 155 | } 156 | 157 | public func negativeAbsolute() -> Matrix { 158 | return fmap(vDSP.negativeAbsolute) 159 | } 160 | 161 | public func negative() -> Matrix { 162 | return fmap(vDSP.negative) 163 | } 164 | 165 | } 166 | 167 | extension Matrix where Scalar == Float { 168 | 169 | public func reverse() -> Matrix { 170 | return fmap(vDSP.reverse) 171 | } 172 | 173 | public func sort(_ sortOrder: vDSP.SortOrder) -> Matrix { 174 | return fmap { vDSP.sort(&$0, sortOrder: sortOrder) } 175 | } 176 | 177 | } 178 | 179 | extension Matrix where Scalar == Float { 180 | 181 | public func square() -> Matrix { 182 | return fmap(vDSP.square) 183 | } 184 | 185 | public func signedSquare() -> Matrix { 186 | return fmap(vDSP.signedSquare) 187 | } 188 | 189 | public func trunc() -> Matrix { 190 | return fmap(vDSP.trunc) 191 | } 192 | 193 | public func countZeroCrossings() -> UInt { 194 | return fmap(vDSP.countZeroCrossings) 195 | } 196 | 197 | } 198 | 199 | extension ComplexMatrix where Scalar == Float { 200 | 201 | public func phase() -> Matrix { 202 | return fmap(vDSP.phase) 203 | } 204 | 205 | public func conjugate() -> ComplexMatrix { 206 | return fmap { vDSP.conjugate($0, count: shape.count, result: &$1) } 207 | } 208 | 209 | public func absolute() -> Matrix { 210 | return fmap(vDSP.absolute) 211 | } 212 | 213 | public func squareMagnitudes() -> Matrix { 214 | return fmap(vDSP.squareMagnitudes) 215 | } 216 | 217 | } 218 | 219 | extension Matrix where Scalar == Float { 220 | 221 | public func ceil() -> Matrix { 222 | return fmap(vForce.ceil) 223 | } 224 | 225 | public func floor() -> Matrix { 226 | return fmap(vForce.floor) 227 | } 228 | 229 | public func copysign(_ signs: Matrix) -> Matrix { 230 | return fmap { vForce.copysign(magnitudes: $0, signs: signs) } 231 | } 232 | 233 | public func truncatingRemainder(_ divisors: Matrix) -> Matrix { 234 | return fmap { vForce.truncatingRemainder(dividends: $0, divisors: divisors) } 235 | } 236 | 237 | public func remainder(_ divisors: Matrix) -> Matrix { 238 | return fmap { vForce.remainder(dividends: $0, divisors: divisors) } 239 | } 240 | 241 | public func integerTruncation() -> Matrix { 242 | return fmap(vForce.trunc) 243 | } 244 | 245 | public func nearestInteger() -> Matrix { 246 | return fmap(vForce.nearestInteger) 247 | } 248 | 249 | public func rsqrt() -> Matrix { 250 | return fmap(vForce.rsqrt) 251 | } 252 | 253 | public func sqrt() -> Matrix { 254 | return fmap(vForce.sqrt) 255 | } 256 | 257 | public func reciprocal() -> Matrix { 258 | return fmap(vForce.reciprocal) 259 | } 260 | 261 | } 262 | 263 | extension Matrix where Scalar == Float { 264 | 265 | public func exp() -> Matrix { 266 | return fmap(vForce.exp) 267 | } 268 | 269 | public func expm1() -> Matrix { 270 | return fmap(vForce.expm1) 271 | } 272 | 273 | public func exp2() -> Matrix { 274 | return fmap(vForce.exp2) 275 | } 276 | 277 | public func log2() -> Matrix { 278 | return fmap(vForce.log2) 279 | } 280 | 281 | public func log10() -> Matrix { 282 | return fmap(vForce.log10) 283 | } 284 | 285 | public func logb() -> Matrix { 286 | return fmap(vForce.logb) 287 | } 288 | 289 | 290 | } 291 | 292 | extension Matrix where Scalar == Float { 293 | 294 | public func pow(_ exponents: Matrix) -> Matrix { 295 | return fmap { vForce.pow(bases: $0, exponents: exponents) } 296 | } 297 | 298 | } 299 | 300 | extension Matrix where Scalar == Float { 301 | 302 | public func sin() -> Matrix { 303 | return fmap(vForce.sin) 304 | } 305 | 306 | public func sinPi() -> Matrix { 307 | return fmap(vForce.sinPi) 308 | } 309 | 310 | public func cos() -> Matrix { 311 | return fmap(vForce.cos) 312 | } 313 | 314 | public func cosPi() -> Matrix { 315 | return fmap(vForce.cosPi) 316 | } 317 | 318 | public func sincos() -> (sinResult: Matrix, cosResult: Matrix) { 319 | var result = (sinResult: Matrix.zeros(shape: shape), cosResult: Matrix.zeros(shape: shape)) 320 | vForce.sincos(self, sinResult: &result.sinResult, cosResult: &result.cosResult) 321 | return result 322 | } 323 | 324 | public func tan() -> Matrix { 325 | return fmap(vForce.tan) 326 | } 327 | 328 | public func tanPi() -> Matrix { 329 | return fmap(vForce.tanPi) 330 | } 331 | 332 | } 333 | 334 | extension Matrix where Scalar == Float { 335 | 336 | public func asin() -> Matrix { 337 | return fmap(vForce.asin) 338 | } 339 | 340 | public func acos() -> Matrix { 341 | return fmap(vForce.acos) 342 | } 343 | 344 | public func atan() -> Matrix { 345 | return fmap(vForce.atan) 346 | } 347 | 348 | public func sinh() -> Matrix { 349 | return fmap(vForce.sinh) 350 | } 351 | 352 | public func cosh() -> Matrix { 353 | return fmap(vForce.cosh) 354 | } 355 | 356 | public func tanh() -> Matrix { 357 | return fmap(vForce.tanh) 358 | } 359 | 360 | public func asinh() -> Matrix { 361 | return fmap(vForce.asinh) 362 | } 363 | 364 | public func acosh() -> Matrix { 365 | return fmap(vForce.acosh) 366 | } 367 | 368 | public func atanh() -> Matrix { 369 | return fmap(vForce.atanh) 370 | } 371 | 372 | } 373 | 374 | extension Matrix where Scalar == Float { 375 | 376 | public func log() -> Matrix { 377 | return fmap(vForce.log) 378 | } 379 | 380 | public func log1p() -> Matrix { 381 | return fmap(vForce.log1p) 382 | } 383 | 384 | public func atan2(_ y: Matrix) -> Matrix { 385 | return fmap { vForce.atan2(x: $0, y: y) } 386 | } 387 | 388 | } 389 | 390 | extension Matrix where Scalar == Double { 391 | 392 | public func maximum() -> Scalar { 393 | return fmap(vDSP.maximum) 394 | } 395 | 396 | public func maximumMagnitude() -> Scalar { 397 | return fmap(vDSP.maximumMagnitude) 398 | } 399 | 400 | public func minimum() -> Scalar { 401 | return fmap(vDSP.minimum) 402 | } 403 | 404 | public func sum() -> Scalar { 405 | return fmap(vDSP.sum) 406 | } 407 | 408 | public func sumOfSquares() -> Scalar { 409 | return fmap(vDSP.sumOfSquares) 410 | } 411 | 412 | public func sumAndSumOfSquares() -> (elementsSum: Scalar, squaresSum: Scalar) { 413 | return fmap(vDSP.sumAndSumOfSquares) 414 | } 415 | 416 | public func sumOfMagnitudes() -> Scalar { 417 | return fmap(vDSP.sumOfMagnitudes) 418 | } 419 | 420 | } 421 | 422 | extension Matrix where Scalar == Double { 423 | 424 | public func indexOfMaximum() -> (UInt, Double) { 425 | return fmap(vDSP.indexOfMaximum) 426 | } 427 | 428 | public func indexOfMaximumMagnitude() -> (UInt, Double) { 429 | return fmap(vDSP.indexOfMaximumMagnitude) 430 | } 431 | 432 | public func indexOfMinimum() -> (UInt, Double) { 433 | return fmap(vDSP.indexOfMinimum) 434 | } 435 | 436 | } 437 | 438 | extension Matrix where Scalar == Double { 439 | 440 | public func meanSquare() -> Scalar { 441 | return fmap(vDSP.meanSquare) 442 | } 443 | 444 | public func meanMagnitude() -> Scalar { 445 | return fmap(vDSP.meanMagnitude) 446 | } 447 | 448 | public func mean() -> Scalar { 449 | return fmap(vDSP.mean) 450 | } 451 | 452 | public func rootMeanSquare() -> Scalar { 453 | return fmap(vDSP.rootMeanSquare) 454 | } 455 | 456 | } 457 | 458 | extension Matrix where Scalar == Double { 459 | 460 | public func clip(to bounds: ClosedRange) -> Matrix { 461 | return fmap { vDSP.clip($0, to: bounds) } 462 | } 463 | 464 | public func invertedClip(to bounds: ClosedRange) -> Matrix { 465 | return fmap { vDSP.invertedClip($0, to: bounds) } 466 | } 467 | 468 | public func threshold(to lowerBound: Scalar, with rule: vDSP.ThresholdRule) -> Matrix { 469 | return fmap { vDSP.threshold($0, to: lowerBound, with: rule) } 470 | } 471 | 472 | public func limit(_ limit: Scalar, withOutputConstant outputConstant: Scalar) -> Matrix { 473 | return fmap { vDSP.limit($0, limit: limit, withOutputConstant: outputConstant) } 474 | } 475 | 476 | } 477 | 478 | extension Matrix where Scalar == Double { 479 | 480 | public func dot(_ vector: Matrix) -> Scalar { 481 | return fmap { vDSP.dot($0, vector) } 482 | } 483 | 484 | public func hypot(_ y: Matrix) -> Matrix { 485 | return fmap { vDSP.hypot($0, y) } 486 | } 487 | 488 | public func hypot(x1: Matrix, y0: Matrix, y1: Matrix) -> Matrix { 489 | return fmap { vDSP.hypot(x0: $0, x1: x1, y0: y0, y1: y1) } 490 | } 491 | 492 | public func distanceSquared(_ point: Matrix) -> Scalar { 493 | return fmap { vDSP.distanceSquared($0, point) } 494 | } 495 | 496 | } 497 | 498 | extension Matrix where Scalar == Double { 499 | 500 | public func evaluatePolynomial(usingCoefficients coefficients: [Scalar]) -> Matrix { 501 | return fmap { vDSP.evaluatePolynomial(usingCoefficients: coefficients, withVariables: $0) } 502 | } 503 | 504 | } 505 | 506 | extension Matrix where Scalar == Double { 507 | 508 | public func linearInterpolate(_ vector: Matrix, using interpolationConstant: Scalar) -> Matrix { 509 | return fmap { vDSP.linearInterpolate($0, vector, using: interpolationConstant) } 510 | } 511 | 512 | public func linearInterpolate(using controlVector: Matrix) -> Matrix { 513 | return fmap { vDSP.linearInterpolate(elementsOf: $0, using: controlVector) } 514 | } 515 | 516 | } 517 | 518 | extension Matrix where Scalar == Double { 519 | 520 | public func minimum(_ vector: Matrix) -> Matrix { 521 | return fmap { vDSP.minimum($0, vector.elements) } 522 | } 523 | 524 | public func maximum(_ vector: Matrix) -> Matrix { 525 | return fmap { vDSP.maximum($0, vector.elements) } 526 | } 527 | 528 | } 529 | 530 | extension Matrix where Scalar == Double { 531 | 532 | public func absolute() -> Matrix { 533 | return fmap(vDSP.absolute) 534 | } 535 | 536 | public func negativeAbsolute() -> Matrix { 537 | return fmap(vDSP.negativeAbsolute) 538 | } 539 | 540 | public func negative() -> Matrix { 541 | return fmap(vDSP.negative) 542 | } 543 | 544 | } 545 | 546 | extension Matrix where Scalar == Double { 547 | 548 | public func reverse() -> Matrix { 549 | return fmap(vDSP.reverse) 550 | } 551 | 552 | public func sort(_ sortOrder: vDSP.SortOrder) -> Matrix { 553 | return fmap { vDSP.sort(&$0, sortOrder: sortOrder) } 554 | } 555 | 556 | } 557 | 558 | extension Matrix where Scalar == Double { 559 | 560 | public func square() -> Matrix { 561 | return fmap(vDSP.square) 562 | } 563 | 564 | public func signedSquare() -> Matrix { 565 | return fmap(vDSP.signedSquare) 566 | } 567 | 568 | public func trunc() -> Matrix { 569 | return fmap(vDSP.trunc) 570 | } 571 | 572 | public func countZeroCrossings() -> UInt { 573 | return fmap(vDSP.countZeroCrossings) 574 | } 575 | 576 | } 577 | 578 | extension ComplexMatrix where Scalar == Double { 579 | 580 | public func phase() -> Matrix { 581 | return fmap(vDSP.phase) 582 | } 583 | 584 | public func conjugate() -> ComplexMatrix { 585 | return fmap { vDSP.conjugate($0, count: shape.count, result: &$1) } 586 | } 587 | 588 | public func absolute() -> Matrix { 589 | return fmap(vDSP.absolute) 590 | } 591 | 592 | public func squareMagnitudes() -> Matrix { 593 | return fmap(vDSP.squareMagnitudes) 594 | } 595 | 596 | } 597 | 598 | extension Matrix where Scalar == Double { 599 | 600 | public func ceil() -> Matrix { 601 | return fmap(vForce.ceil) 602 | } 603 | 604 | public func floor() -> Matrix { 605 | return fmap(vForce.floor) 606 | } 607 | 608 | public func copysign(_ signs: Matrix) -> Matrix { 609 | return fmap { vForce.copysign(magnitudes: $0, signs: signs) } 610 | } 611 | 612 | public func truncatingRemainder(_ divisors: Matrix) -> Matrix { 613 | return fmap { vForce.truncatingRemainder(dividends: $0, divisors: divisors) } 614 | } 615 | 616 | public func remainder(_ divisors: Matrix) -> Matrix { 617 | return fmap { vForce.remainder(dividends: $0, divisors: divisors) } 618 | } 619 | 620 | public func integerTruncation() -> Matrix { 621 | return fmap(vForce.trunc) 622 | } 623 | 624 | public func nearestInteger() -> Matrix { 625 | return fmap(vForce.nearestInteger) 626 | } 627 | 628 | public func rsqrt() -> Matrix { 629 | return fmap(vForce.rsqrt) 630 | } 631 | 632 | public func sqrt() -> Matrix { 633 | return fmap(vForce.sqrt) 634 | } 635 | 636 | public func reciprocal() -> Matrix { 637 | return fmap(vForce.reciprocal) 638 | } 639 | 640 | } 641 | 642 | extension Matrix where Scalar == Double { 643 | 644 | public func exp() -> Matrix { 645 | return fmap(vForce.exp) 646 | } 647 | 648 | public func expm1() -> Matrix { 649 | return fmap(vForce.expm1) 650 | } 651 | 652 | public func exp2() -> Matrix { 653 | return fmap(vForce.exp2) 654 | } 655 | 656 | public func log2() -> Matrix { 657 | return fmap(vForce.log2) 658 | } 659 | 660 | public func log10() -> Matrix { 661 | return fmap(vForce.log10) 662 | } 663 | 664 | public func logb() -> Matrix { 665 | return fmap(vForce.logb) 666 | } 667 | 668 | 669 | } 670 | 671 | extension Matrix where Scalar == Double { 672 | 673 | public func pow(_ exponents: Matrix) -> Matrix { 674 | return fmap { vForce.pow(bases: $0, exponents: exponents) } 675 | } 676 | 677 | } 678 | 679 | extension Matrix where Scalar == Double { 680 | 681 | public func sin() -> Matrix { 682 | return fmap(vForce.sin) 683 | } 684 | 685 | public func sinPi() -> Matrix { 686 | return fmap(vForce.sinPi) 687 | } 688 | 689 | public func cos() -> Matrix { 690 | return fmap(vForce.cos) 691 | } 692 | 693 | public func cosPi() -> Matrix { 694 | return fmap(vForce.cosPi) 695 | } 696 | 697 | public func sincos() -> (sinResult: Matrix, cosResult: Matrix) { 698 | var result = (sinResult: Matrix.zeros(shape: shape), cosResult: Matrix.zeros(shape: shape)) 699 | vForce.sincos(self, sinResult: &result.sinResult, cosResult: &result.cosResult) 700 | return result 701 | } 702 | 703 | public func tan() -> Matrix { 704 | return fmap(vForce.tan) 705 | } 706 | 707 | public func tanPi() -> Matrix { 708 | return fmap(vForce.tanPi) 709 | } 710 | 711 | } 712 | 713 | extension Matrix where Scalar == Double { 714 | 715 | public func asin() -> Matrix { 716 | return fmap(vForce.asin) 717 | } 718 | 719 | public func acos() -> Matrix { 720 | return fmap(vForce.acos) 721 | } 722 | 723 | public func atan() -> Matrix { 724 | return fmap(vForce.atan) 725 | } 726 | 727 | public func sinh() -> Matrix { 728 | return fmap(vForce.sinh) 729 | } 730 | 731 | public func cosh() -> Matrix { 732 | return fmap(vForce.cosh) 733 | } 734 | 735 | public func tanh() -> Matrix { 736 | return fmap(vForce.tanh) 737 | } 738 | 739 | public func asinh() -> Matrix { 740 | return fmap(vForce.asinh) 741 | } 742 | 743 | public func acosh() -> Matrix { 744 | return fmap(vForce.acosh) 745 | } 746 | 747 | public func atanh() -> Matrix { 748 | return fmap(vForce.atanh) 749 | } 750 | 751 | } 752 | 753 | extension Matrix where Scalar == Double { 754 | 755 | public func log() -> Matrix { 756 | return fmap(vForce.log) 757 | } 758 | 759 | public func log1p() -> Matrix { 760 | return fmap(vForce.log1p) 761 | } 762 | 763 | public func atan2(_ y: Matrix) -> Matrix { 764 | return fmap { vForce.atan2(x: $0, y: y) } 765 | } 766 | 767 | } 768 | --------------------------------------------------------------------------------