numbl 0.0.7 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/binding.gyp +3 -1
- package/dist-cli/cli.js +32353 -37666
- package/native/lapack_addon.cpp +17 -0
- package/native/lapack_chol.cpp +173 -0
- package/native/lapack_common.h +40 -0
- package/native/lapack_lu.cpp +156 -0
- package/native/lapack_qr.cpp +141 -3
- package/native/lapack_svd.cpp +196 -0
- package/package.json +3 -3
package/native/lapack_addon.cpp
CHANGED
|
@@ -6,11 +6,16 @@
|
|
|
6
6
|
* inv(data, n) — real matrix inversion (lapack_inv.cpp)
|
|
7
7
|
* invComplex(dataRe, dataIm, n) — complex matrix inversion (lapack_inv.cpp)
|
|
8
8
|
* qr(data, m, n, econ, wantQ) — QR decomposition (lapack_qr.cpp)
|
|
9
|
+
* qrComplex(dataRe, dataIm, m, n, econ, wantQ) — complex QR (lapack_qr.cpp)
|
|
10
|
+
* lu(data, m, n) — LU factorization (lapack_lu.cpp)
|
|
11
|
+
* luComplex(dataRe, dataIm, m, n) — complex LU factorization (lapack_lu.cpp)
|
|
9
12
|
* svd(data, m, n, econ, computeUV) — Singular Value Decomp. (lapack_svd.cpp)
|
|
10
13
|
* matmul(A, m, k, B, n) — matrix-matrix multiply (lapack_matmul.cpp)
|
|
11
14
|
* linsolve(A, m, n, B, nrhs) — linear solve / least-sq (lapack_linsolve.cpp)
|
|
12
15
|
* linsolveComplex(ARe, AIm, m, n, BRe, BIm, nrhs) — complex linear solve (lapack_linsolve.cpp)
|
|
13
16
|
* eig(data, n, computeVL, computeVR, balance) — eigenvalue decomp. (lapack_eig.cpp)
|
|
17
|
+
* chol(data, n, upper) — Cholesky factorization (lapack_chol.cpp)
|
|
18
|
+
* cholComplex(dataRe, dataIm, n, upper) — complex Cholesky (lapack_chol.cpp)
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
#include "lapack_common.h"
|
|
@@ -24,8 +29,16 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
24
29
|
Napi::Function::New(env, InvComplex));
|
|
25
30
|
exports.Set(Napi::String::New(env, "qr"),
|
|
26
31
|
Napi::Function::New(env, Qr));
|
|
32
|
+
exports.Set(Napi::String::New(env, "qrComplex"),
|
|
33
|
+
Napi::Function::New(env, QrComplex));
|
|
34
|
+
exports.Set(Napi::String::New(env, "lu"),
|
|
35
|
+
Napi::Function::New(env, Lu));
|
|
36
|
+
exports.Set(Napi::String::New(env, "luComplex"),
|
|
37
|
+
Napi::Function::New(env, LuComplex));
|
|
27
38
|
exports.Set(Napi::String::New(env, "svd"),
|
|
28
39
|
Napi::Function::New(env, Svd));
|
|
40
|
+
exports.Set(Napi::String::New(env, "svdComplex"),
|
|
41
|
+
Napi::Function::New(env, SvdComplex));
|
|
29
42
|
exports.Set(Napi::String::New(env, "matmul"),
|
|
30
43
|
Napi::Function::New(env, Matmul));
|
|
31
44
|
exports.Set(Napi::String::New(env, "linsolve"),
|
|
@@ -34,6 +47,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
34
47
|
Napi::Function::New(env, LinsolveComplex));
|
|
35
48
|
exports.Set(Napi::String::New(env, "eig"),
|
|
36
49
|
Napi::Function::New(env, Eig));
|
|
50
|
+
exports.Set(Napi::String::New(env, "chol"),
|
|
51
|
+
Napi::Function::New(env, Chol));
|
|
52
|
+
exports.Set(Napi::String::New(env, "cholComplex"),
|
|
53
|
+
Napi::Function::New(env, CholComplex));
|
|
37
54
|
return exports;
|
|
38
55
|
}
|
|
39
56
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chol() and cholComplex() — Cholesky factorization via LAPACK.
|
|
3
|
+
*
|
|
4
|
+
* chol(data: Float64Array, n: number, upper: boolean):
|
|
5
|
+
* {R: Float64Array, info: number}
|
|
6
|
+
*
|
|
7
|
+
* cholComplex(dataRe: Float64Array, dataIm: Float64Array, n: number, upper: boolean):
|
|
8
|
+
* {RRe: Float64Array, RIm: Float64Array, info: number}
|
|
9
|
+
*
|
|
10
|
+
* Cholesky factorization of an n×n symmetric (Hermitian) positive definite
|
|
11
|
+
* matrix stored in column-major order.
|
|
12
|
+
* Uses LAPACK dpotrf (real) / zpotrf (complex).
|
|
13
|
+
* Returns the triangular factor and info (0 = success, >0 = not pos def).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include "lapack_common.h"
|
|
17
|
+
|
|
18
|
+
// ── chol() ───────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
Napi::Value Chol(const Napi::CallbackInfo& info) {
|
|
21
|
+
Napi::Env env = info.Env();
|
|
22
|
+
|
|
23
|
+
if (info.Length() < 3
|
|
24
|
+
|| !info[0].IsTypedArray()
|
|
25
|
+
|| !info[1].IsNumber()
|
|
26
|
+
|| !info[2].IsBoolean()) {
|
|
27
|
+
Napi::TypeError::New(env,
|
|
28
|
+
"chol: expected (Float64Array data, number n, boolean upper)")
|
|
29
|
+
.ThrowAsJavaScriptException();
|
|
30
|
+
return env.Null();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
auto arr = info[0].As<Napi::TypedArray>();
|
|
34
|
+
if (arr.TypedArrayType() != napi_float64_array) {
|
|
35
|
+
Napi::TypeError::New(env, "chol: data must be a Float64Array")
|
|
36
|
+
.ThrowAsJavaScriptException();
|
|
37
|
+
return env.Null();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
int n = info[1].As<Napi::Number>().Int32Value();
|
|
41
|
+
bool upper = info[2].As<Napi::Boolean>().Value();
|
|
42
|
+
|
|
43
|
+
if (n <= 0 || static_cast<int>(arr.ElementLength()) != n * n) {
|
|
44
|
+
Napi::RangeError::New(env, "chol: data.length must equal n*n")
|
|
45
|
+
.ThrowAsJavaScriptException();
|
|
46
|
+
return env.Null();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
auto float64arr = info[0].As<Napi::Float64Array>();
|
|
50
|
+
|
|
51
|
+
// Copy input (dpotrf overwrites in-place)
|
|
52
|
+
std::vector<double> a(n * n);
|
|
53
|
+
std::memcpy(a.data(), float64arr.Data(), n * n * sizeof(double));
|
|
54
|
+
|
|
55
|
+
char uplo = upper ? 'U' : 'L';
|
|
56
|
+
int info_val = 0;
|
|
57
|
+
|
|
58
|
+
dpotrf_(&uplo, &n, a.data(), &n, &info_val);
|
|
59
|
+
|
|
60
|
+
if (info_val < 0) {
|
|
61
|
+
Napi::Error::New(env, "chol: illegal argument passed to dpotrf")
|
|
62
|
+
.ThrowAsJavaScriptException();
|
|
63
|
+
return env.Null();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Zero out the opposite triangle
|
|
67
|
+
if (upper) {
|
|
68
|
+
for (int j = 0; j < n; j++)
|
|
69
|
+
for (int i = j + 1; i < n; i++)
|
|
70
|
+
a[i + j * n] = 0.0;
|
|
71
|
+
} else {
|
|
72
|
+
for (int j = 0; j < n; j++)
|
|
73
|
+
for (int i = 0; i < j; i++)
|
|
74
|
+
a[i + j * n] = 0.0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
auto R_arr = Napi::Float64Array::New(env, static_cast<size_t>(n * n));
|
|
78
|
+
std::memcpy(R_arr.Data(), a.data(), n * n * sizeof(double));
|
|
79
|
+
|
|
80
|
+
auto result = Napi::Object::New(env);
|
|
81
|
+
result.Set("R", R_arr);
|
|
82
|
+
result.Set("info", Napi::Number::New(env, info_val));
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── cholComplex() ────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
Napi::Value CholComplex(const Napi::CallbackInfo& info) {
|
|
89
|
+
Napi::Env env = info.Env();
|
|
90
|
+
|
|
91
|
+
if (info.Length() < 4
|
|
92
|
+
|| !info[0].IsTypedArray()
|
|
93
|
+
|| !info[1].IsTypedArray()
|
|
94
|
+
|| !info[2].IsNumber()
|
|
95
|
+
|| !info[3].IsBoolean()) {
|
|
96
|
+
Napi::TypeError::New(env,
|
|
97
|
+
"cholComplex: expected (Float64Array dataRe, Float64Array dataIm, "
|
|
98
|
+
"number n, boolean upper)")
|
|
99
|
+
.ThrowAsJavaScriptException();
|
|
100
|
+
return env.Null();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
auto arrRe = info[0].As<Napi::TypedArray>();
|
|
104
|
+
auto arrIm = info[1].As<Napi::TypedArray>();
|
|
105
|
+
|
|
106
|
+
if (arrRe.TypedArrayType() != napi_float64_array ||
|
|
107
|
+
arrIm.TypedArrayType() != napi_float64_array) {
|
|
108
|
+
Napi::TypeError::New(env, "cholComplex: dataRe and dataIm must be Float64Arrays")
|
|
109
|
+
.ThrowAsJavaScriptException();
|
|
110
|
+
return env.Null();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
int n = info[2].As<Napi::Number>().Int32Value();
|
|
114
|
+
bool upper = info[3].As<Napi::Boolean>().Value();
|
|
115
|
+
|
|
116
|
+
if (n <= 0 ||
|
|
117
|
+
static_cast<int>(arrRe.ElementLength()) != n * n ||
|
|
118
|
+
static_cast<int>(arrIm.ElementLength()) != n * n) {
|
|
119
|
+
Napi::RangeError::New(env,
|
|
120
|
+
"cholComplex: dataRe.length and dataIm.length must equal n*n")
|
|
121
|
+
.ThrowAsJavaScriptException();
|
|
122
|
+
return env.Null();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
auto float64arrRe = info[0].As<Napi::Float64Array>();
|
|
126
|
+
auto float64arrIm = info[1].As<Napi::Float64Array>();
|
|
127
|
+
|
|
128
|
+
// Convert to interleaved complex format
|
|
129
|
+
std::vector<lapack_complex_double> a(n * n);
|
|
130
|
+
for (int i = 0; i < n * n; ++i) {
|
|
131
|
+
a[i].real = float64arrRe[i];
|
|
132
|
+
a[i].imag = float64arrIm[i];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
char uplo = upper ? 'U' : 'L';
|
|
136
|
+
int info_val = 0;
|
|
137
|
+
|
|
138
|
+
zpotrf_(&uplo, &n, a.data(), &n, &info_val);
|
|
139
|
+
|
|
140
|
+
if (info_val < 0) {
|
|
141
|
+
Napi::Error::New(env, "cholComplex: illegal argument passed to zpotrf")
|
|
142
|
+
.ThrowAsJavaScriptException();
|
|
143
|
+
return env.Null();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Zero out the opposite triangle
|
|
147
|
+
if (upper) {
|
|
148
|
+
for (int j = 0; j < n; j++)
|
|
149
|
+
for (int i = j + 1; i < n; i++) {
|
|
150
|
+
a[i + j * n].real = 0.0;
|
|
151
|
+
a[i + j * n].imag = 0.0;
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
for (int j = 0; j < n; j++)
|
|
155
|
+
for (int i = 0; i < j; i++) {
|
|
156
|
+
a[i + j * n].real = 0.0;
|
|
157
|
+
a[i + j * n].imag = 0.0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
auto RRe_arr = Napi::Float64Array::New(env, static_cast<size_t>(n * n));
|
|
162
|
+
auto RIm_arr = Napi::Float64Array::New(env, static_cast<size_t>(n * n));
|
|
163
|
+
for (int i = 0; i < n * n; ++i) {
|
|
164
|
+
RRe_arr[i] = a[i].real;
|
|
165
|
+
RIm_arr[i] = a[i].imag;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
auto result = Napi::Object::New(env);
|
|
169
|
+
result.Set("RRe", RRe_arr);
|
|
170
|
+
result.Set("RIm", RIm_arr);
|
|
171
|
+
result.Set("info", Napi::Number::New(env, info_val));
|
|
172
|
+
return result;
|
|
173
|
+
}
|
package/native/lapack_common.h
CHANGED
|
@@ -43,12 +43,37 @@ extern "C" {
|
|
|
43
43
|
void dorgqr_(int* m, int* n, int* k, double* a, int* lda, double* tau,
|
|
44
44
|
double* work, int* lwork, int* info);
|
|
45
45
|
|
|
46
|
+
// ── Complex QR factorisation ───────────────────────────────────────────────
|
|
47
|
+
// Compute QR factorisation of a general complex m×n matrix: A = Q * R
|
|
48
|
+
void zgeqrf_(int* m, int* n, lapack_complex_double* a, int* lda,
|
|
49
|
+
lapack_complex_double* tau,
|
|
50
|
+
lapack_complex_double* work, int* lwork, int* info);
|
|
51
|
+
// Generate the m×n (or m×m) unitary matrix Q from zgeqrf reflectors
|
|
52
|
+
void zungqr_(int* m, int* n, int* k, lapack_complex_double* a, int* lda,
|
|
53
|
+
lapack_complex_double* tau,
|
|
54
|
+
lapack_complex_double* work, int* lwork, int* info);
|
|
55
|
+
|
|
46
56
|
// ── SVD ───────────────────────────────────────────────────────────────────
|
|
47
57
|
// Compute SVD using divide-and-conquer: A = U * Sigma * V^T
|
|
48
58
|
void dgesdd_(char* jobz, int* m, int* n, double* a, int* lda,
|
|
49
59
|
double* s, double* u, int* ldu, double* vt, int* ldvt,
|
|
50
60
|
double* work, int* lwork, int* iwork, int* info);
|
|
51
61
|
|
|
62
|
+
// Complex SVD using divide-and-conquer: A = U * Sigma * V^H
|
|
63
|
+
void zgesdd_(char* jobz, int* m, int* n, lapack_complex_double* a, int* lda,
|
|
64
|
+
double* s, lapack_complex_double* u, int* ldu,
|
|
65
|
+
lapack_complex_double* vt, int* ldvt,
|
|
66
|
+
lapack_complex_double* work, int* lwork, double* rwork,
|
|
67
|
+
int* iwork, int* info);
|
|
68
|
+
|
|
69
|
+
// Complex SVD (standard algorithm, more robust fallback): A = U * Sigma * V^H
|
|
70
|
+
void zgesvd_(char* jobu, char* jobvt, int* m, int* n,
|
|
71
|
+
lapack_complex_double* a, int* lda,
|
|
72
|
+
double* s, lapack_complex_double* u, int* ldu,
|
|
73
|
+
lapack_complex_double* vt, int* ldvt,
|
|
74
|
+
lapack_complex_double* work, int* lwork, double* rwork,
|
|
75
|
+
int* info);
|
|
76
|
+
|
|
52
77
|
// ── Matrix-matrix multiplication (BLAS) ──────────────────────────────────
|
|
53
78
|
// C = alpha * op(A) * op(B) + beta * C
|
|
54
79
|
void dgemm_(char* transa, char* transb,
|
|
@@ -88,6 +113,15 @@ extern "C" {
|
|
|
88
113
|
double* wr, double* wi,
|
|
89
114
|
double* vl, int* ldvl, double* vr, int* ldvr,
|
|
90
115
|
double* work, int* lwork, int* info);
|
|
116
|
+
|
|
117
|
+
// ── Cholesky factorization ─────────────────────────────────────────────────
|
|
118
|
+
// Compute the Cholesky factorization of a real symmetric positive definite
|
|
119
|
+
// matrix: A = U^T * U (uplo='U') or A = L * L^T (uplo='L')
|
|
120
|
+
void dpotrf_(char* uplo, int* n, double* a, int* lda, int* info);
|
|
121
|
+
|
|
122
|
+
// Complex Cholesky factorization of a Hermitian positive definite matrix:
|
|
123
|
+
// A = U^H * U (uplo='U') or A = L * L^H (uplo='L')
|
|
124
|
+
void zpotrf_(char* uplo, int* n, lapack_complex_double* a, int* lda, int* info);
|
|
91
125
|
}
|
|
92
126
|
|
|
93
127
|
// ── Function prototypes (implemented in their respective .cpp files) ──────────
|
|
@@ -95,8 +129,14 @@ extern "C" {
|
|
|
95
129
|
Napi::Value Inv(const Napi::CallbackInfo& info);
|
|
96
130
|
Napi::Value InvComplex(const Napi::CallbackInfo& info);
|
|
97
131
|
Napi::Value Qr(const Napi::CallbackInfo& info);
|
|
132
|
+
Napi::Value QrComplex(const Napi::CallbackInfo& info);
|
|
133
|
+
Napi::Value Lu(const Napi::CallbackInfo& info);
|
|
134
|
+
Napi::Value LuComplex(const Napi::CallbackInfo& info);
|
|
98
135
|
Napi::Value Svd(const Napi::CallbackInfo& info);
|
|
136
|
+
Napi::Value SvdComplex(const Napi::CallbackInfo& info);
|
|
99
137
|
Napi::Value Matmul(const Napi::CallbackInfo& info);
|
|
100
138
|
Napi::Value Linsolve(const Napi::CallbackInfo& info);
|
|
101
139
|
Napi::Value LinsolveComplex(const Napi::CallbackInfo& info);
|
|
102
140
|
Napi::Value Eig(const Napi::CallbackInfo& info);
|
|
141
|
+
Napi::Value Chol(const Napi::CallbackInfo& info);
|
|
142
|
+
Napi::Value CholComplex(const Napi::CallbackInfo& info);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lu() and luComplex() — LU factorization with partial pivoting via LAPACK.
|
|
3
|
+
*
|
|
4
|
+
* lu(data: Float64Array, m: number, n: number):
|
|
5
|
+
* {LU: Float64Array, ipiv: Int32Array}
|
|
6
|
+
*
|
|
7
|
+
* luComplex(dataRe: Float64Array, dataIm: Float64Array, m: number, n: number):
|
|
8
|
+
* {LURe: Float64Array, LUIm: Float64Array, ipiv: Int32Array}
|
|
9
|
+
*
|
|
10
|
+
* LU factorization of an m×n matrix stored in column-major order.
|
|
11
|
+
* Uses LAPACK dgetrf (real) / zgetrf (complex) with partial pivoting.
|
|
12
|
+
* Returns the packed LU matrix and 1-based pivot indices.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
#include "lapack_common.h"
|
|
16
|
+
|
|
17
|
+
// ── lu() ─────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
Napi::Value Lu(const Napi::CallbackInfo& info) {
|
|
20
|
+
Napi::Env env = info.Env();
|
|
21
|
+
|
|
22
|
+
if (info.Length() < 3
|
|
23
|
+
|| !info[0].IsTypedArray()
|
|
24
|
+
|| !info[1].IsNumber()
|
|
25
|
+
|| !info[2].IsNumber()) {
|
|
26
|
+
Napi::TypeError::New(env,
|
|
27
|
+
"lu: expected (Float64Array data, number m, number n)")
|
|
28
|
+
.ThrowAsJavaScriptException();
|
|
29
|
+
return env.Null();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
auto arr = info[0].As<Napi::TypedArray>();
|
|
33
|
+
if (arr.TypedArrayType() != napi_float64_array) {
|
|
34
|
+
Napi::TypeError::New(env, "lu: data must be a Float64Array")
|
|
35
|
+
.ThrowAsJavaScriptException();
|
|
36
|
+
return env.Null();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
int m = info[1].As<Napi::Number>().Int32Value();
|
|
40
|
+
int n = info[2].As<Napi::Number>().Int32Value();
|
|
41
|
+
|
|
42
|
+
if (m <= 0 || n <= 0 || static_cast<int>(arr.ElementLength()) != m * n) {
|
|
43
|
+
Napi::RangeError::New(env, "lu: data.length must equal m*n")
|
|
44
|
+
.ThrowAsJavaScriptException();
|
|
45
|
+
return env.Null();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
auto float64arr = info[0].As<Napi::Float64Array>();
|
|
49
|
+
int k = m < n ? m : n;
|
|
50
|
+
|
|
51
|
+
// Copy input (dgetrf overwrites A in-place)
|
|
52
|
+
std::vector<double> a(m * n);
|
|
53
|
+
std::memcpy(a.data(), float64arr.Data(), m * n * sizeof(double));
|
|
54
|
+
|
|
55
|
+
std::vector<int> ipiv(k);
|
|
56
|
+
int info_val = 0;
|
|
57
|
+
|
|
58
|
+
dgetrf_(&m, &n, a.data(), &m, ipiv.data(), &info_val);
|
|
59
|
+
|
|
60
|
+
if (info_val < 0) {
|
|
61
|
+
Napi::Error::New(env, "lu: illegal argument passed to dgetrf")
|
|
62
|
+
.ThrowAsJavaScriptException();
|
|
63
|
+
return env.Null();
|
|
64
|
+
}
|
|
65
|
+
// info_val > 0 means U(info_val,info_val) is zero (singular) — still return result like MATLAB
|
|
66
|
+
|
|
67
|
+
auto LU_arr = Napi::Float64Array::New(env, static_cast<size_t>(m * n));
|
|
68
|
+
std::memcpy(LU_arr.Data(), a.data(), m * n * sizeof(double));
|
|
69
|
+
|
|
70
|
+
auto ipiv_arr = Napi::Int32Array::New(env, static_cast<size_t>(k));
|
|
71
|
+
std::memcpy(ipiv_arr.Data(), ipiv.data(), k * sizeof(int));
|
|
72
|
+
|
|
73
|
+
auto result = Napi::Object::New(env);
|
|
74
|
+
result.Set("LU", LU_arr);
|
|
75
|
+
result.Set("ipiv", ipiv_arr);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── luComplex() ──────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
Napi::Value LuComplex(const Napi::CallbackInfo& info) {
|
|
82
|
+
Napi::Env env = info.Env();
|
|
83
|
+
|
|
84
|
+
if (info.Length() < 4
|
|
85
|
+
|| !info[0].IsTypedArray()
|
|
86
|
+
|| !info[1].IsTypedArray()
|
|
87
|
+
|| !info[2].IsNumber()
|
|
88
|
+
|| !info[3].IsNumber()) {
|
|
89
|
+
Napi::TypeError::New(env,
|
|
90
|
+
"luComplex: expected (Float64Array dataRe, Float64Array dataIm, "
|
|
91
|
+
"number m, number n)")
|
|
92
|
+
.ThrowAsJavaScriptException();
|
|
93
|
+
return env.Null();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
auto arrRe = info[0].As<Napi::TypedArray>();
|
|
97
|
+
auto arrIm = info[1].As<Napi::TypedArray>();
|
|
98
|
+
|
|
99
|
+
if (arrRe.TypedArrayType() != napi_float64_array ||
|
|
100
|
+
arrIm.TypedArrayType() != napi_float64_array) {
|
|
101
|
+
Napi::TypeError::New(env, "luComplex: dataRe and dataIm must be Float64Arrays")
|
|
102
|
+
.ThrowAsJavaScriptException();
|
|
103
|
+
return env.Null();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
int m = info[2].As<Napi::Number>().Int32Value();
|
|
107
|
+
int n = info[3].As<Napi::Number>().Int32Value();
|
|
108
|
+
|
|
109
|
+
if (m <= 0 || n <= 0 ||
|
|
110
|
+
static_cast<int>(arrRe.ElementLength()) != m * n ||
|
|
111
|
+
static_cast<int>(arrIm.ElementLength()) != m * n) {
|
|
112
|
+
Napi::RangeError::New(env,
|
|
113
|
+
"luComplex: dataRe.length and dataIm.length must equal m*n")
|
|
114
|
+
.ThrowAsJavaScriptException();
|
|
115
|
+
return env.Null();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
auto float64arrRe = info[0].As<Napi::Float64Array>();
|
|
119
|
+
auto float64arrIm = info[1].As<Napi::Float64Array>();
|
|
120
|
+
int k = m < n ? m : n;
|
|
121
|
+
|
|
122
|
+
// Convert to interleaved complex format
|
|
123
|
+
std::vector<lapack_complex_double> a(m * n);
|
|
124
|
+
for (int i = 0; i < m * n; ++i) {
|
|
125
|
+
a[i].real = float64arrRe[i];
|
|
126
|
+
a[i].imag = float64arrIm[i];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
std::vector<int> ipiv(k);
|
|
130
|
+
int info_val = 0;
|
|
131
|
+
|
|
132
|
+
zgetrf_(&m, &n, a.data(), &m, ipiv.data(), &info_val);
|
|
133
|
+
|
|
134
|
+
if (info_val < 0) {
|
|
135
|
+
Napi::Error::New(env, "luComplex: illegal argument passed to zgetrf")
|
|
136
|
+
.ThrowAsJavaScriptException();
|
|
137
|
+
return env.Null();
|
|
138
|
+
}
|
|
139
|
+
// info_val > 0 means U(info_val,info_val) is zero (singular) — still return result like MATLAB
|
|
140
|
+
|
|
141
|
+
auto LURe_arr = Napi::Float64Array::New(env, static_cast<size_t>(m * n));
|
|
142
|
+
auto LUIm_arr = Napi::Float64Array::New(env, static_cast<size_t>(m * n));
|
|
143
|
+
for (int i = 0; i < m * n; ++i) {
|
|
144
|
+
LURe_arr[i] = a[i].real;
|
|
145
|
+
LUIm_arr[i] = a[i].imag;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
auto ipiv_arr = Napi::Int32Array::New(env, static_cast<size_t>(k));
|
|
149
|
+
std::memcpy(ipiv_arr.Data(), ipiv.data(), k * sizeof(int));
|
|
150
|
+
|
|
151
|
+
auto result = Napi::Object::New(env);
|
|
152
|
+
result.Set("LURe", LURe_arr);
|
|
153
|
+
result.Set("LUIm", LUIm_arr);
|
|
154
|
+
result.Set("ipiv", ipiv_arr);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
package/native/lapack_qr.cpp
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* qr() — QR decomposition via LAPACK
|
|
2
|
+
* qr() and qrComplex() — QR decomposition via LAPACK.
|
|
3
3
|
*
|
|
4
4
|
* qr(data: Float64Array, m: number, n: number, econ: boolean,
|
|
5
5
|
* wantQ: boolean): {Q: Float64Array | undefined, R: Float64Array}
|
|
6
6
|
*
|
|
7
|
+
* qrComplex(dataRe: Float64Array, dataIm: Float64Array, m: number, n: number,
|
|
8
|
+
* econ: boolean, wantQ: boolean):
|
|
9
|
+
* {QRe?: Float64Array, QIm?: Float64Array, RRe: Float64Array, RIm: Float64Array}
|
|
10
|
+
*
|
|
7
11
|
* QR decomposition of an m×n matrix stored in column-major order.
|
|
8
12
|
* econ=true: economy/thin QR — Q is m×k, R is k×n (k = min(m,n))
|
|
9
13
|
* econ=false: full QR — Q is m×m, R is m×n
|
|
10
|
-
* wantQ=false: skips
|
|
11
|
-
* Returns object with {Q, R} as Float64Arrays in column-major order.
|
|
14
|
+
* wantQ=false: skips Q generation; Q properties are absent from the returned object.
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
#include "lapack_common.h"
|
|
@@ -131,3 +134,138 @@ Napi::Value Qr(const Napi::CallbackInfo& info) {
|
|
|
131
134
|
|
|
132
135
|
return result;
|
|
133
136
|
}
|
|
137
|
+
|
|
138
|
+
// ── qrComplex() ──────────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
Napi::Value QrComplex(const Napi::CallbackInfo& info) {
|
|
141
|
+
Napi::Env env = info.Env();
|
|
142
|
+
|
|
143
|
+
if (info.Length() < 6
|
|
144
|
+
|| !info[0].IsTypedArray()
|
|
145
|
+
|| !info[1].IsTypedArray()
|
|
146
|
+
|| !info[2].IsNumber()
|
|
147
|
+
|| !info[3].IsNumber()
|
|
148
|
+
|| !info[4].IsBoolean()
|
|
149
|
+
|| !info[5].IsBoolean()) {
|
|
150
|
+
Napi::TypeError::New(env,
|
|
151
|
+
"qrComplex: expected (Float64Array dataRe, Float64Array dataIm, "
|
|
152
|
+
"number m, number n, boolean econ, boolean wantQ)")
|
|
153
|
+
.ThrowAsJavaScriptException();
|
|
154
|
+
return env.Null();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
auto arrRe = info[0].As<Napi::TypedArray>();
|
|
158
|
+
auto arrIm = info[1].As<Napi::TypedArray>();
|
|
159
|
+
|
|
160
|
+
if (arrRe.TypedArrayType() != napi_float64_array ||
|
|
161
|
+
arrIm.TypedArrayType() != napi_float64_array) {
|
|
162
|
+
Napi::TypeError::New(env, "qrComplex: dataRe and dataIm must be Float64Arrays")
|
|
163
|
+
.ThrowAsJavaScriptException();
|
|
164
|
+
return env.Null();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
int m = info[2].As<Napi::Number>().Int32Value();
|
|
168
|
+
int n = info[3].As<Napi::Number>().Int32Value();
|
|
169
|
+
bool econ = info[4].As<Napi::Boolean>().Value();
|
|
170
|
+
bool wantQ = info[5].As<Napi::Boolean>().Value();
|
|
171
|
+
|
|
172
|
+
if (m <= 0 || n <= 0 ||
|
|
173
|
+
static_cast<int>(arrRe.ElementLength()) != m * n ||
|
|
174
|
+
static_cast<int>(arrIm.ElementLength()) != m * n) {
|
|
175
|
+
Napi::RangeError::New(env,
|
|
176
|
+
"qrComplex: dataRe.length and dataIm.length must equal m*n")
|
|
177
|
+
.ThrowAsJavaScriptException();
|
|
178
|
+
return env.Null();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
auto float64arrRe = info[0].As<Napi::Float64Array>();
|
|
182
|
+
auto float64arrIm = info[1].As<Napi::Float64Array>();
|
|
183
|
+
int k = m < n ? m : n;
|
|
184
|
+
|
|
185
|
+
// ── Step 1: Convert to interleaved complex format ──────────────────────────
|
|
186
|
+
std::vector<lapack_complex_double> a(m * n);
|
|
187
|
+
for (int i = 0; i < m * n; ++i) {
|
|
188
|
+
a[i].real = float64arrRe[i];
|
|
189
|
+
a[i].imag = float64arrIm[i];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
std::vector<lapack_complex_double> tau(k);
|
|
193
|
+
int info_val = 0;
|
|
194
|
+
|
|
195
|
+
// ── Step 2: QR factorisation (zgeqrf) ──────────────────────────────────────
|
|
196
|
+
int lwork = -1;
|
|
197
|
+
lapack_complex_double work_query;
|
|
198
|
+
zgeqrf_(&m, &n, a.data(), &m, tau.data(), &work_query, &lwork, &info_val);
|
|
199
|
+
lwork = static_cast<int>(work_query.real);
|
|
200
|
+
if (lwork < 1) lwork = k;
|
|
201
|
+
|
|
202
|
+
std::vector<lapack_complex_double> work(lwork);
|
|
203
|
+
zgeqrf_(&m, &n, a.data(), &m, tau.data(), work.data(), &lwork, &info_val);
|
|
204
|
+
|
|
205
|
+
if (info_val != 0) {
|
|
206
|
+
Napi::Error::New(env, "qrComplex: zgeqrf failed")
|
|
207
|
+
.ThrowAsJavaScriptException();
|
|
208
|
+
return env.Null();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Step 3: Extract R from the upper triangle ──────────────────────────────
|
|
212
|
+
int r_rows = econ ? k : m;
|
|
213
|
+
std::vector<double> R_re(r_rows * n, 0.0);
|
|
214
|
+
std::vector<double> R_im(r_rows * n, 0.0);
|
|
215
|
+
for (int j = 0; j < n; j++) {
|
|
216
|
+
int ilim = j < k ? j : k - 1;
|
|
217
|
+
for (int i = 0; i <= ilim; i++) {
|
|
218
|
+
R_re[i + j * r_rows] = a[i + j * m].real;
|
|
219
|
+
R_im[i + j * r_rows] = a[i + j * m].imag;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── Step 4: Generate Q via zungqr (only if wantQ) ─────────────────────────
|
|
224
|
+
int q_cols = econ ? k : m;
|
|
225
|
+
|
|
226
|
+
auto result = Napi::Object::New(env);
|
|
227
|
+
|
|
228
|
+
if (wantQ) {
|
|
229
|
+
std::vector<lapack_complex_double> q_buf(m * q_cols, {0.0, 0.0});
|
|
230
|
+
int cols_to_copy = n < q_cols ? n : q_cols;
|
|
231
|
+
for (int j = 0; j < cols_to_copy; j++) {
|
|
232
|
+
for (int i = 0; i < m; i++) {
|
|
233
|
+
q_buf[i + j * m] = a[i + j * m];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
lwork = -1;
|
|
238
|
+
zungqr_(&m, &q_cols, &k, q_buf.data(), &m, tau.data(),
|
|
239
|
+
&work_query, &lwork, &info_val);
|
|
240
|
+
lwork = static_cast<int>(work_query.real);
|
|
241
|
+
if (lwork < 1) lwork = q_cols;
|
|
242
|
+
|
|
243
|
+
work.assign(lwork, {0.0, 0.0});
|
|
244
|
+
zungqr_(&m, &q_cols, &k, q_buf.data(), &m, tau.data(),
|
|
245
|
+
work.data(), &lwork, &info_val);
|
|
246
|
+
|
|
247
|
+
if (info_val != 0) {
|
|
248
|
+
Napi::Error::New(env, "qrComplex: zungqr failed")
|
|
249
|
+
.ThrowAsJavaScriptException();
|
|
250
|
+
return env.Null();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
auto QRe_arr = Napi::Float64Array::New(env, static_cast<size_t>(m * q_cols));
|
|
254
|
+
auto QIm_arr = Napi::Float64Array::New(env, static_cast<size_t>(m * q_cols));
|
|
255
|
+
for (int i = 0; i < m * q_cols; ++i) {
|
|
256
|
+
QRe_arr[i] = q_buf[i].real;
|
|
257
|
+
QIm_arr[i] = q_buf[i].imag;
|
|
258
|
+
}
|
|
259
|
+
result.Set("QRe", QRe_arr);
|
|
260
|
+
result.Set("QIm", QIm_arr);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
auto RRe_arr = Napi::Float64Array::New(env, static_cast<size_t>(r_rows * n));
|
|
264
|
+
auto RIm_arr = Napi::Float64Array::New(env, static_cast<size_t>(r_rows * n));
|
|
265
|
+
std::memcpy(RRe_arr.Data(), R_re.data(), r_rows * n * sizeof(double));
|
|
266
|
+
std::memcpy(RIm_arr.Data(), R_im.data(), r_rows * n * sizeof(double));
|
|
267
|
+
result.Set("RRe", RRe_arr);
|
|
268
|
+
result.Set("RIm", RIm_arr);
|
|
269
|
+
|
|
270
|
+
return result;
|
|
271
|
+
}
|