-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #33
- Loading branch information
Showing
7 changed files
with
401 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// | ||
// Created by Daniel Strobusch on 08.12.22. | ||
// | ||
|
||
import Darwin | ||
import Accelerate | ||
|
||
public extension Matrix where T == Double { | ||
|
||
/// Compute the SVD | ||
/// | ||
/// Factorization is computed by LAPACKs DGESDD method, which computes a SVD of a general | ||
/// M-by-N matrix, optionally computing the left and right singular | ||
/// vectors. If singular vectors are desired, it uses a | ||
/// divide-and-conquer algorithm. | ||
/// | ||
/// The SVD is written | ||
/// | ||
/// A = U * SIGMA * transpose(V) | ||
/// | ||
/// where SIGMA is an M-by-N matrix which is zero except for its | ||
/// min(m,n) diagonal elements, U is an M-by-M orthogonal matrix, and | ||
/// V is an N-by-N orthogonal matrix. The diagonal elements of SIGMA | ||
/// are the singular values of A; they are real and non-negative, and | ||
/// are returned in descending order. The first min(m,n) columns of | ||
/// U and V are the left and right singular vectors of A. | ||
/// | ||
/// - Returns: a tuple of the matrices (U, SIGMA, transpose(V)) | ||
func svd() throws -> (Matrix<T>, Vector<T>, Matrix<T>) { | ||
// A is destroyed if jobz != O | ||
let A = Matrix(copy: self, order: .F) | ||
|
||
var m = __CLPK_integer(shape[0]) | ||
var n = __CLPK_integer(shape[1]) | ||
|
||
// see | ||
var jobz: CChar = "A".utf8CString[0] | ||
|
||
// leading dimension is the number of rows in column major order | ||
var lda = __CLPK_integer(A.shape[0]) | ||
var info: __CLPK_integer = 0 | ||
|
||
let U: Matrix<T> = Matrix.empty(shape: [Int(m), Int(m)], order: .F) | ||
var ldu = __CLPK_integer(U.shape[0]) | ||
let s: Vector<T> = Vector.empty(shape: [Int(Swift.min(m, n))], order: .F) | ||
let Vt: Matrix<T> = Matrix.empty(shape: [Int(n), Int(n)], order: .F) | ||
var ldvt = __CLPK_integer(Vt.shape[0]) | ||
|
||
let iwork: Vector<__CLPK_integer> = Vector.empty(shape: [8 * Int(Swift.min(m, n))], order: .F) | ||
|
||
// do optimal workspace query | ||
var lwork: __CLPK_integer = -1 | ||
var work: Vector<__CLPK_doublereal> = Vector.empty(shape: [1], order: .F) | ||
dgesdd_(&jobz, &m, &n, A.dataStart, &lda, s.dataStart, U.dataStart, &ldu, Vt.dataStart, &ldvt, work.dataStart, &lwork, iwork.dataStart, &info) | ||
if info != 0 { | ||
throw LapackError.getri(info) | ||
} | ||
|
||
// retrieve optimal workspace | ||
lwork = __CLPK_integer(work[0]) | ||
work = Vector.empty(shape: [Int(lwork)], order: .F) | ||
dgesdd_(&jobz, &m, &n, A.dataStart, &lda, s.dataStart, U.dataStart, &ldu, Vt.dataStart, &ldvt, work.dataStart, &lwork, iwork.dataStart, &info) | ||
|
||
if info != 0 { | ||
throw LapackError.gesdd(info) | ||
} | ||
return (U, s, Vt) | ||
} | ||
|
||
} | ||
|
||
public extension Matrix where T == Float { | ||
|
||
/// Compute the SVD | ||
/// | ||
/// Factorization is computed by LAPACKs SGESDD method, which computes a SVD of a general | ||
/// M-by-N matrix, optionally computing the left and right singular | ||
/// vectors. If singular vectors are desired, it uses a | ||
/// divide-and-conquer algorithm. | ||
/// | ||
/// The SVD is written | ||
/// | ||
/// A = U * SIGMA * transpose(V) | ||
/// | ||
/// where SIGMA is an M-by-N matrix which is zero except for its | ||
/// min(m,n) diagonal elements, U is an M-by-M orthogonal matrix, and | ||
/// V is an N-by-N orthogonal matrix. The diagonal elements of SIGMA | ||
/// are the singular values of A; they are real and non-negative, and | ||
/// are returned in descending order. The first min(m,n) columns of | ||
/// U and V are the left and right singular vectors of A. | ||
/// | ||
/// - Returns: a tuple of the matrices (U, SIGMA, transpose(V)) | ||
func svd() throws -> (Matrix<T>, Vector<T>, Matrix<T>) { | ||
// A is destroyed if jobz != O | ||
let A = Matrix(copy: self, order: .F) | ||
|
||
var m = __CLPK_integer(shape[0]) | ||
var n = __CLPK_integer(shape[1]) | ||
|
||
// see | ||
var jobz: CChar = "A".utf8CString[0] | ||
|
||
// leading dimension is the number of rows in column major order | ||
var lda = __CLPK_integer(A.shape[0]) | ||
var info: __CLPK_integer = 0 | ||
|
||
let U: Matrix<T> = Matrix.empty(shape: [Int(m), Int(m)], order: .F) | ||
var ldu = __CLPK_integer(U.shape[0]) | ||
let s: Vector<T> = Vector.empty(shape: [Int(Swift.min(m, n))], order: .F) | ||
let Vt: Matrix<T> = Matrix.empty(shape: [Int(n), Int(n)], order: .F) | ||
var ldvt = __CLPK_integer(Vt.shape[0]) | ||
|
||
let iwork: Vector<__CLPK_integer> = Vector.empty(shape: [8 * Int(Swift.min(m, n))], order: .F) | ||
|
||
// do optimal workspace query | ||
var lwork: __CLPK_integer = -1 | ||
var work: Vector<__CLPK_real> = Vector.empty(shape: [1], order: .F) | ||
sgesdd_(&jobz, &m, &n, A.dataStart, &lda, s.dataStart, U.dataStart, &ldu, Vt.dataStart, &ldvt, work.dataStart, &lwork, iwork.dataStart, &info) | ||
if info != 0 { | ||
throw LapackError.getri(info) | ||
} | ||
|
||
// retrieve optimal workspace | ||
lwork = __CLPK_integer(work[0]) | ||
work = Vector.empty(shape: [Int(lwork)], order: .F) | ||
sgesdd_(&jobz, &m, &n, A.dataStart, &lda, s.dataStart, U.dataStart, &ldu, Vt.dataStart, &ldvt, work.dataStart, &lwork, iwork.dataStart, &info) | ||
|
||
if info != 0 { | ||
throw LapackError.gesdd(info) | ||
} | ||
return (U, s, Vt) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import XCTest | ||
import Darwin | ||
@testable import NdArray | ||
|
||
class SvdTestsDouble: XCTestCase { | ||
func testSvdUnit() throws { | ||
let a = Matrix<Double>([ | ||
[1, 0, 0], | ||
[0, 1, 0], | ||
[0, 0, 1], | ||
]) | ||
let (U, s, Vt) = try a.svd() | ||
let S = Matrix(diag: s) | ||
XCTAssertEqual((U * S * Vt).dataArray, a.dataArray, accuracy: 1e-15) | ||
|
||
XCTAssertEqual(U.dataArray, Matrix<Double>([ | ||
[1, 0, 0], | ||
[0, 1, 0], | ||
[0, 0, 1], | ||
], order: .F).dataArray, accuracy: 1e-15) | ||
XCTAssertEqual(S.dataArray, Matrix<Double>([ | ||
[1, 0, 0], | ||
[0, 1, 0], | ||
[0, 0, 1], | ||
], order: .F).dataArray, accuracy: 1e-15) | ||
XCTAssertEqual(Vt.dataArray, Matrix<Double>([ | ||
[1, 0, 0], | ||
[0, 1, 0], | ||
[0, 0, 1], | ||
], order: .F).dataArray, accuracy: 1e-15) | ||
} | ||
|
||
func testSvdWide() throws { | ||
let a = Matrix<Double>([ | ||
[1, 2, 3], | ||
[4, 5, 6], | ||
]) | ||
let (U, s, Vt) = try a.svd() | ||
let Sd = Matrix(diag: s) | ||
let S = Matrix<Double>.zeros(a.shape) | ||
let mn = a.shape.min()! | ||
S[..<mn, ..<mn] = Sd | ||
print("a") | ||
print(a) | ||
print("U * S * Vt") | ||
print(U * S * Vt) | ||
XCTAssertEqual(NdArray(U * S * Vt, order: .C).dataArray, a.dataArray, accuracy: 1e-8) | ||
|
||
print("U") | ||
print(U) | ||
XCTAssertEqual(U.dataArray, Matrix<Double>([ | ||
[-0.3863177, -0.92236578], | ||
[-0.92236578, 0.3863177] | ||
], order: .F).dataArray, accuracy: 1e-8) | ||
print("s") | ||
print(s) | ||
XCTAssertEqual(s.dataArray, Matrix<Double>([[9.508032, 0.77286964]]).dataArray, accuracy: 1e-8) | ||
print("Vt") | ||
print(Vt) | ||
XCTAssertEqual(Vt.dataArray, Matrix<Double>([ | ||
[-0.42866713, -0.56630692, -0.7039467], | ||
[0.80596391, 0.11238241, -0.58119908], | ||
[0.40824829, -0.81649658, 0.40824829], | ||
], order: .F).dataArray, accuracy: 1e-8) | ||
} | ||
|
||
func testSvdTall() throws { | ||
let a = Matrix<Double>([ | ||
[1, 2], | ||
[3, 4], | ||
[5, 6], | ||
]) | ||
let (U, s, Vt) = try a.svd() | ||
let Sd = Matrix(diag: s) | ||
let S = Matrix<Double>.zeros(a.shape) | ||
let mn = a.shape.min()! | ||
S[..<mn, ..<mn] = Sd | ||
print("a") | ||
print(a) | ||
print("U * S * Vt") | ||
print(U * S * Vt) | ||
XCTAssertEqual(NdArray(U * S * Vt, order: .C).dataArray, a.dataArray, accuracy: 1e-8) | ||
|
||
print("U") | ||
print(U) | ||
XCTAssertEqual(U.dataArray, Matrix<Double>([ | ||
[-0.2298477, 0.88346102, 0.40824829], | ||
[-0.52474482, 0.24078249, -0.81649658], | ||
[-0.81964194, -0.40189603, 0.40824829] | ||
], order: .F).dataArray, accuracy: 1e-8) | ||
print("s") | ||
print(s) | ||
XCTAssertEqual(s.dataArray, Matrix<Double>([[9.52551809, 0.51430058]]).dataArray, accuracy: 1e-8) | ||
print("Vt") | ||
print(Vt) | ||
XCTAssertEqual(Vt.dataArray, Matrix<Double>([ | ||
[-0.61962948, -0.78489445], | ||
[-0.78489445, 0.61962948], | ||
], order: .F).dataArray, accuracy: 1e-8) | ||
} | ||
} |
Oops, something went wrong.