numbl 0.0.6 → 0.0.8
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 +4 -1
- package/dist-cli/cli.js +16569 -16059
- package/dist-plot-viewer/assets/{index-CYInUJ8O.js → index-_FVS_eKT.js} +5 -5
- package/dist-plot-viewer/index.html +1 -1
- package/native/lapack_addon.cpp +5 -0
- package/native/lapack_common.h +20 -0
- package/native/lapack_linsolve.cpp +154 -3
- package/native/lapack_svd.cpp +158 -0
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>numbl - Figures</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-_FVS_eKT.js"></script>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="root"></div>
|
package/native/lapack_addon.cpp
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* svd(data, m, n, econ, computeUV) — Singular Value Decomp. (lapack_svd.cpp)
|
|
10
10
|
* matmul(A, m, k, B, n) — matrix-matrix multiply (lapack_matmul.cpp)
|
|
11
11
|
* linsolve(A, m, n, B, nrhs) — linear solve / least-sq (lapack_linsolve.cpp)
|
|
12
|
+
* linsolveComplex(ARe, AIm, m, n, BRe, BIm, nrhs) — complex linear solve (lapack_linsolve.cpp)
|
|
12
13
|
* eig(data, n, computeVL, computeVR, balance) — eigenvalue decomp. (lapack_eig.cpp)
|
|
13
14
|
*/
|
|
14
15
|
|
|
@@ -25,10 +26,14 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
25
26
|
Napi::Function::New(env, Qr));
|
|
26
27
|
exports.Set(Napi::String::New(env, "svd"),
|
|
27
28
|
Napi::Function::New(env, Svd));
|
|
29
|
+
exports.Set(Napi::String::New(env, "svdComplex"),
|
|
30
|
+
Napi::Function::New(env, SvdComplex));
|
|
28
31
|
exports.Set(Napi::String::New(env, "matmul"),
|
|
29
32
|
Napi::Function::New(env, Matmul));
|
|
30
33
|
exports.Set(Napi::String::New(env, "linsolve"),
|
|
31
34
|
Napi::Function::New(env, Linsolve));
|
|
35
|
+
exports.Set(Napi::String::New(env, "linsolveComplex"),
|
|
36
|
+
Napi::Function::New(env, LinsolveComplex));
|
|
32
37
|
exports.Set(Napi::String::New(env, "eig"),
|
|
33
38
|
Napi::Function::New(env, Eig));
|
|
34
39
|
return exports;
|
package/native/lapack_common.h
CHANGED
|
@@ -49,6 +49,13 @@ extern "C" {
|
|
|
49
49
|
double* s, double* u, int* ldu, double* vt, int* ldvt,
|
|
50
50
|
double* work, int* lwork, int* iwork, int* info);
|
|
51
51
|
|
|
52
|
+
// Complex SVD using divide-and-conquer: A = U * Sigma * V^H
|
|
53
|
+
void zgesdd_(char* jobz, int* m, int* n, lapack_complex_double* a, int* lda,
|
|
54
|
+
double* s, lapack_complex_double* u, int* ldu,
|
|
55
|
+
lapack_complex_double* vt, int* ldvt,
|
|
56
|
+
lapack_complex_double* work, int* lwork, double* rwork,
|
|
57
|
+
int* iwork, int* info);
|
|
58
|
+
|
|
52
59
|
// ── Matrix-matrix multiplication (BLAS) ──────────────────────────────────
|
|
53
60
|
// C = alpha * op(A) * op(B) + beta * C
|
|
54
61
|
void dgemm_(char* transa, char* transb,
|
|
@@ -70,6 +77,17 @@ extern "C" {
|
|
|
70
77
|
double* a, int* lda, double* b, int* ldb,
|
|
71
78
|
double* work, int* lwork, int* info);
|
|
72
79
|
|
|
80
|
+
// ── Complex linear solve (square) ────────────────────────────────────────
|
|
81
|
+
// LU factorisation + solve for complex matrices: A * X = B (A is n×n, B is n×nrhs)
|
|
82
|
+
void zgesv_(int* n, int* nrhs, lapack_complex_double* a, int* lda, int* ipiv,
|
|
83
|
+
lapack_complex_double* b, int* ldb, int* info);
|
|
84
|
+
|
|
85
|
+
// ── Complex linear least-squares / minimum-norm solve (general) ──────────
|
|
86
|
+
// Uses QR (trans='N', m>=n) or LQ (trans='N', m<n) factorisation for complex matrices.
|
|
87
|
+
void zgels_(char* trans, int* m, int* n, int* nrhs,
|
|
88
|
+
lapack_complex_double* a, int* lda, lapack_complex_double* b, int* ldb,
|
|
89
|
+
lapack_complex_double* work, int* lwork, int* info);
|
|
90
|
+
|
|
73
91
|
// ── Eigenvalue decomposition ───────────────────────────────────────────────
|
|
74
92
|
// Compute eigenvalues and optionally left/right eigenvectors of a general
|
|
75
93
|
// real matrix A. A = VR * diag(WR+i*WI) * VR^(-1)
|
|
@@ -85,6 +103,8 @@ Napi::Value Inv(const Napi::CallbackInfo& info);
|
|
|
85
103
|
Napi::Value InvComplex(const Napi::CallbackInfo& info);
|
|
86
104
|
Napi::Value Qr(const Napi::CallbackInfo& info);
|
|
87
105
|
Napi::Value Svd(const Napi::CallbackInfo& info);
|
|
106
|
+
Napi::Value SvdComplex(const Napi::CallbackInfo& info);
|
|
88
107
|
Napi::Value Matmul(const Napi::CallbackInfo& info);
|
|
89
108
|
Napi::Value Linsolve(const Napi::CallbackInfo& info);
|
|
109
|
+
Napi::Value LinsolveComplex(const Napi::CallbackInfo& info);
|
|
90
110
|
Napi::Value Eig(const Napi::CallbackInfo& info);
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* linsolve() — solve A * X = B via LAPACK.
|
|
2
|
+
* linsolve() — solve A * X = B via LAPACK (real).
|
|
3
|
+
* linsolveComplex() — solve A * X = B via LAPACK (complex).
|
|
3
4
|
*
|
|
4
5
|
* linsolve(A: Float64Array, m: number, n: number,
|
|
5
6
|
* B: Float64Array, nrhs: number): Float64Array
|
|
6
7
|
*
|
|
8
|
+
* linsolveComplex(ARe: Float64Array, AIm: Float64Array, m: number, n: number,
|
|
9
|
+
* BRe: Float64Array, BIm: Float64Array, nrhs: number): {re, im}
|
|
10
|
+
*
|
|
7
11
|
* A is m×n in column-major order; B is m×nrhs.
|
|
8
12
|
* Returns X (n×nrhs) in a new Float64Array in column-major order.
|
|
9
13
|
*
|
|
10
14
|
* Square (m == n):
|
|
11
|
-
* Uses dgesv (LU with partial pivoting). Throws if A is singular.
|
|
15
|
+
* Uses dgesv / zgesv (LU with partial pivoting). Throws if A is singular.
|
|
12
16
|
*
|
|
13
17
|
* Non-square:
|
|
14
|
-
* Uses dgels (QR for overdetermined, LQ for underdetermined).
|
|
18
|
+
* Uses dgels / zgels (QR for overdetermined, LQ for underdetermined).
|
|
15
19
|
* Overdetermined (m > n): least-squares solution minimising ||A*X - B||₂.
|
|
16
20
|
* Underdetermined (m < n): minimum-norm solution minimising ||X||₂.
|
|
17
21
|
*/
|
|
@@ -137,3 +141,150 @@ Napi::Value Linsolve(const Napi::CallbackInfo& info) {
|
|
|
137
141
|
return result;
|
|
138
142
|
}
|
|
139
143
|
}
|
|
144
|
+
|
|
145
|
+
// ── linsolveComplex() ─────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
Napi::Value LinsolveComplex(const Napi::CallbackInfo& info) {
|
|
148
|
+
Napi::Env env = info.Env();
|
|
149
|
+
|
|
150
|
+
if (info.Length() < 7
|
|
151
|
+
|| !info[0].IsTypedArray()
|
|
152
|
+
|| !info[1].IsTypedArray()
|
|
153
|
+
|| !info[2].IsNumber()
|
|
154
|
+
|| !info[3].IsNumber()
|
|
155
|
+
|| !info[4].IsTypedArray()
|
|
156
|
+
|| !info[5].IsTypedArray()
|
|
157
|
+
|| !info[6].IsNumber()) {
|
|
158
|
+
Napi::TypeError::New(env,
|
|
159
|
+
"linsolveComplex: expected (Float64Array ARe, Float64Array AIm,"
|
|
160
|
+
" number m, number n, Float64Array BRe, Float64Array BIm, number nrhs)")
|
|
161
|
+
.ThrowAsJavaScriptException();
|
|
162
|
+
return env.Null();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
auto arrARe = info[0].As<Napi::TypedArray>();
|
|
166
|
+
auto arrAIm = info[1].As<Napi::TypedArray>();
|
|
167
|
+
auto arrBRe = info[4].As<Napi::TypedArray>();
|
|
168
|
+
auto arrBIm = info[5].As<Napi::TypedArray>();
|
|
169
|
+
|
|
170
|
+
if (arrARe.TypedArrayType() != napi_float64_array ||
|
|
171
|
+
arrAIm.TypedArrayType() != napi_float64_array ||
|
|
172
|
+
arrBRe.TypedArrayType() != napi_float64_array ||
|
|
173
|
+
arrBIm.TypedArrayType() != napi_float64_array) {
|
|
174
|
+
Napi::TypeError::New(env,
|
|
175
|
+
"linsolveComplex: ARe, AIm, BRe, BIm must be Float64Arrays")
|
|
176
|
+
.ThrowAsJavaScriptException();
|
|
177
|
+
return env.Null();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
int m = info[2].As<Napi::Number>().Int32Value();
|
|
181
|
+
int n = info[3].As<Napi::Number>().Int32Value();
|
|
182
|
+
int nrhs = info[6].As<Napi::Number>().Int32Value();
|
|
183
|
+
|
|
184
|
+
if (m <= 0 || n <= 0 || nrhs <= 0
|
|
185
|
+
|| static_cast<int>(arrARe.ElementLength()) != m * n
|
|
186
|
+
|| static_cast<int>(arrAIm.ElementLength()) != m * n
|
|
187
|
+
|| static_cast<int>(arrBRe.ElementLength()) != m * nrhs
|
|
188
|
+
|| static_cast<int>(arrBIm.ElementLength()) != m * nrhs) {
|
|
189
|
+
Napi::RangeError::New(env,
|
|
190
|
+
"linsolveComplex: array lengths must match m*n (A) and m*nrhs (B)")
|
|
191
|
+
.ThrowAsJavaScriptException();
|
|
192
|
+
return env.Null();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
auto fARe = info[0].As<Napi::Float64Array>();
|
|
196
|
+
auto fAIm = info[1].As<Napi::Float64Array>();
|
|
197
|
+
auto fBRe = info[4].As<Napi::Float64Array>();
|
|
198
|
+
auto fBIm = info[5].As<Napi::Float64Array>();
|
|
199
|
+
|
|
200
|
+
int info_val = 0;
|
|
201
|
+
|
|
202
|
+
if (m == n) {
|
|
203
|
+
// ── Square: zgesv (LU + solve) ────────────────────────────────────────────
|
|
204
|
+
std::vector<lapack_complex_double> a(n * n), b(n * nrhs);
|
|
205
|
+
for (int i = 0; i < n * n; ++i) { a[i].real = fARe[i]; a[i].imag = fAIm[i]; }
|
|
206
|
+
for (int i = 0; i < n * nrhs; ++i) { b[i].real = fBRe[i]; b[i].imag = fBIm[i]; }
|
|
207
|
+
|
|
208
|
+
std::vector<int> ipiv(n);
|
|
209
|
+
zgesv_(&n, &nrhs, a.data(), &n, ipiv.data(), b.data(), &n, &info_val);
|
|
210
|
+
|
|
211
|
+
if (info_val > 0) {
|
|
212
|
+
Napi::Error::New(env, "linsolveComplex: matrix is singular (zgesv)")
|
|
213
|
+
.ThrowAsJavaScriptException();
|
|
214
|
+
return env.Null();
|
|
215
|
+
}
|
|
216
|
+
if (info_val < 0) {
|
|
217
|
+
Napi::Error::New(env, "linsolveComplex: illegal argument passed to zgesv")
|
|
218
|
+
.ThrowAsJavaScriptException();
|
|
219
|
+
return env.Null();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
auto resultRe = Napi::Float64Array::New(env, static_cast<size_t>(n * nrhs));
|
|
223
|
+
auto resultIm = Napi::Float64Array::New(env, static_cast<size_t>(n * nrhs));
|
|
224
|
+
for (int i = 0; i < n * nrhs; ++i) { resultRe[i] = b[i].real; resultIm[i] = b[i].imag; }
|
|
225
|
+
|
|
226
|
+
auto result = Napi::Object::New(env);
|
|
227
|
+
result.Set("re", resultRe);
|
|
228
|
+
result.Set("im", resultIm);
|
|
229
|
+
return result;
|
|
230
|
+
|
|
231
|
+
} else {
|
|
232
|
+
// ── Non-square: zgels (QR / LQ least-squares / min-norm solve) ───────────
|
|
233
|
+
// zgels needs B with ldb = max(m, n) rows so the solution fits in place.
|
|
234
|
+
int ldb = m > n ? m : n;
|
|
235
|
+
std::vector<lapack_complex_double> a(m * n), b(ldb * nrhs);
|
|
236
|
+
for (int i = 0; i < m * n; ++i) { a[i].real = fARe[i]; a[i].imag = fAIm[i]; }
|
|
237
|
+
for (int i = 0; i < ldb * nrhs; ++i) { b[i].real = 0.0; b[i].imag = 0.0; }
|
|
238
|
+
|
|
239
|
+
// Copy each column of B into the top m rows of the extended buffer
|
|
240
|
+
for (int c = 0; c < nrhs; ++c) {
|
|
241
|
+
for (int r = 0; r < m; ++r) {
|
|
242
|
+
b[r + c * ldb].real = fBRe[r + c * m];
|
|
243
|
+
b[r + c * ldb].imag = fBIm[r + c * m];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
char trans = 'N';
|
|
248
|
+
|
|
249
|
+
// Workspace query
|
|
250
|
+
int lwork = -1;
|
|
251
|
+
lapack_complex_double work_query;
|
|
252
|
+
zgels_(&trans, &m, &n, &nrhs,
|
|
253
|
+
a.data(), &m, b.data(), &ldb,
|
|
254
|
+
&work_query, &lwork, &info_val);
|
|
255
|
+
lwork = static_cast<int>(work_query.real);
|
|
256
|
+
if (lwork < 1) lwork = std::max(1, std::max(m, n));
|
|
257
|
+
|
|
258
|
+
std::vector<lapack_complex_double> work(lwork);
|
|
259
|
+
zgels_(&trans, &m, &n, &nrhs,
|
|
260
|
+
a.data(), &m, b.data(), &ldb,
|
|
261
|
+
work.data(), &lwork, &info_val);
|
|
262
|
+
|
|
263
|
+
if (info_val < 0) {
|
|
264
|
+
Napi::Error::New(env, "linsolveComplex: illegal argument passed to zgels")
|
|
265
|
+
.ThrowAsJavaScriptException();
|
|
266
|
+
return env.Null();
|
|
267
|
+
}
|
|
268
|
+
if (info_val > 0) {
|
|
269
|
+
Napi::Error::New(env,
|
|
270
|
+
"linsolveComplex: A does not have full rank (zgels)")
|
|
271
|
+
.ThrowAsJavaScriptException();
|
|
272
|
+
return env.Null();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Solution is in the first n rows of b (column-major, ldb rows per column)
|
|
276
|
+
auto resultRe = Napi::Float64Array::New(env, static_cast<size_t>(n * nrhs));
|
|
277
|
+
auto resultIm = Napi::Float64Array::New(env, static_cast<size_t>(n * nrhs));
|
|
278
|
+
for (int c = 0; c < nrhs; ++c) {
|
|
279
|
+
for (int r = 0; r < n; ++r) {
|
|
280
|
+
resultRe[r + c * n] = b[r + c * ldb].real;
|
|
281
|
+
resultIm[r + c * n] = b[r + c * ldb].imag;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
auto result = Napi::Object::New(env);
|
|
286
|
+
result.Set("re", resultRe);
|
|
287
|
+
result.Set("im", resultIm);
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
}
|
package/native/lapack_svd.cpp
CHANGED
|
@@ -147,3 +147,161 @@ Napi::Value Svd(const Napi::CallbackInfo& info) {
|
|
|
147
147
|
|
|
148
148
|
return result;
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
// ── svdComplex() ─────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
Napi::Value SvdComplex(const Napi::CallbackInfo& info) {
|
|
154
|
+
Napi::Env env = info.Env();
|
|
155
|
+
|
|
156
|
+
if (info.Length() < 6
|
|
157
|
+
|| !info[0].IsTypedArray()
|
|
158
|
+
|| !info[1].IsTypedArray()
|
|
159
|
+
|| !info[2].IsNumber()
|
|
160
|
+
|| !info[3].IsNumber()
|
|
161
|
+
|| !info[4].IsBoolean()
|
|
162
|
+
|| !info[5].IsBoolean()) {
|
|
163
|
+
Napi::TypeError::New(env,
|
|
164
|
+
"svdComplex: expected (Float64Array dataRe, Float64Array dataIm, number m, number n, boolean econ, boolean computeUV)")
|
|
165
|
+
.ThrowAsJavaScriptException();
|
|
166
|
+
return env.Null();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
auto arrRe = info[0].As<Napi::TypedArray>();
|
|
170
|
+
auto arrIm = info[1].As<Napi::TypedArray>();
|
|
171
|
+
if (arrRe.TypedArrayType() != napi_float64_array || arrIm.TypedArrayType() != napi_float64_array) {
|
|
172
|
+
Napi::TypeError::New(env, "svdComplex: data must be Float64Arrays")
|
|
173
|
+
.ThrowAsJavaScriptException();
|
|
174
|
+
return env.Null();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
int m = info[2].As<Napi::Number>().Int32Value();
|
|
178
|
+
int n = info[3].As<Napi::Number>().Int32Value();
|
|
179
|
+
bool econ = info[4].As<Napi::Boolean>().Value();
|
|
180
|
+
bool computeUV = info[5].As<Napi::Boolean>().Value();
|
|
181
|
+
|
|
182
|
+
if (m <= 0 || n <= 0
|
|
183
|
+
|| static_cast<int>(arrRe.ElementLength()) != m * n
|
|
184
|
+
|| static_cast<int>(arrIm.ElementLength()) != m * n) {
|
|
185
|
+
Napi::RangeError::New(env, "svdComplex: data.length must equal m*n")
|
|
186
|
+
.ThrowAsJavaScriptException();
|
|
187
|
+
return env.Null();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
auto float64Re = info[0].As<Napi::Float64Array>();
|
|
191
|
+
auto float64Im = info[1].As<Napi::Float64Array>();
|
|
192
|
+
int k = m < n ? m : n;
|
|
193
|
+
|
|
194
|
+
// Convert split real/imag to interleaved complex format
|
|
195
|
+
std::vector<lapack_complex_double> a(m * n);
|
|
196
|
+
for (int i = 0; i < m * n; ++i) {
|
|
197
|
+
a[i].real = float64Re[i];
|
|
198
|
+
a[i].imag = float64Im[i];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
std::vector<double> s(k);
|
|
202
|
+
int info_val = 0;
|
|
203
|
+
|
|
204
|
+
char jobz;
|
|
205
|
+
if (!computeUV) {
|
|
206
|
+
jobz = 'N';
|
|
207
|
+
} else if (econ) {
|
|
208
|
+
jobz = 'S';
|
|
209
|
+
} else {
|
|
210
|
+
jobz = 'A';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
int ldu, ldvt;
|
|
214
|
+
std::vector<lapack_complex_double> u_vec, vt_vec;
|
|
215
|
+
|
|
216
|
+
if (jobz == 'N') {
|
|
217
|
+
ldu = m;
|
|
218
|
+
ldvt = n;
|
|
219
|
+
} else if (jobz == 'S') {
|
|
220
|
+
ldu = m;
|
|
221
|
+
ldvt = k;
|
|
222
|
+
u_vec.resize(m * k);
|
|
223
|
+
vt_vec.resize(k * n);
|
|
224
|
+
} else { // 'A'
|
|
225
|
+
ldu = m;
|
|
226
|
+
ldvt = n;
|
|
227
|
+
u_vec.resize(m * m);
|
|
228
|
+
vt_vec.resize(n * n);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
lapack_complex_double* u_ptr = (jobz == 'N') ? nullptr : u_vec.data();
|
|
232
|
+
lapack_complex_double* vt_ptr = (jobz == 'N') ? nullptr : vt_vec.data();
|
|
233
|
+
|
|
234
|
+
// Compute rwork size for zgesdd
|
|
235
|
+
// For jobz='N': 5*min(m,n)
|
|
236
|
+
// For jobz='S' or 'A': max(5*min(m,n)^2 + 5*min(m,n), 2*max(m,n)*min(m,n) + 2*min(m,n)^2 + min(m,n))
|
|
237
|
+
int rwork_size;
|
|
238
|
+
if (jobz == 'N') {
|
|
239
|
+
rwork_size = 5 * k;
|
|
240
|
+
} else {
|
|
241
|
+
int t1 = 5 * k * k + 5 * k;
|
|
242
|
+
int t2 = 2 * std::max(m, n) * k + 2 * k * k + k;
|
|
243
|
+
rwork_size = std::max(t1, t2);
|
|
244
|
+
}
|
|
245
|
+
std::vector<double> rwork(rwork_size);
|
|
246
|
+
std::vector<int> iwork(8 * k);
|
|
247
|
+
|
|
248
|
+
// Workspace query
|
|
249
|
+
int lwork = -1;
|
|
250
|
+
lapack_complex_double work_query;
|
|
251
|
+
zgesdd_(&jobz, &m, &n, a.data(), &m, s.data(), u_ptr, &ldu, vt_ptr, &ldvt,
|
|
252
|
+
&work_query, &lwork, rwork.data(), iwork.data(), &info_val);
|
|
253
|
+
|
|
254
|
+
lwork = static_cast<int>(work_query.real);
|
|
255
|
+
if (lwork < 1) lwork = 3 * k + std::max(m, n);
|
|
256
|
+
|
|
257
|
+
std::vector<lapack_complex_double> work(lwork);
|
|
258
|
+
zgesdd_(&jobz, &m, &n, a.data(), &m, s.data(), u_ptr, &ldu, vt_ptr, &ldvt,
|
|
259
|
+
work.data(), &lwork, rwork.data(), iwork.data(), &info_val);
|
|
260
|
+
|
|
261
|
+
if (info_val != 0) {
|
|
262
|
+
Napi::Error::New(env, "svdComplex: zgesdd failed")
|
|
263
|
+
.ThrowAsJavaScriptException();
|
|
264
|
+
return env.Null();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Build result
|
|
268
|
+
auto result = Napi::Object::New(env);
|
|
269
|
+
|
|
270
|
+
// S is always real
|
|
271
|
+
auto S_arr = Napi::Float64Array::New(env, static_cast<size_t>(k));
|
|
272
|
+
std::memcpy(S_arr.Data(), s.data(), k * sizeof(double));
|
|
273
|
+
result.Set("S", S_arr);
|
|
274
|
+
|
|
275
|
+
if (computeUV) {
|
|
276
|
+
// U: convert from interleaved to split real/imag
|
|
277
|
+
int u_size = (jobz == 'S') ? m * k : m * m;
|
|
278
|
+
auto URe = Napi::Float64Array::New(env, static_cast<size_t>(u_size));
|
|
279
|
+
auto UIm = Napi::Float64Array::New(env, static_cast<size_t>(u_size));
|
|
280
|
+
for (int i = 0; i < u_size; i++) {
|
|
281
|
+
URe[i] = u_vec[i].real;
|
|
282
|
+
UIm[i] = u_vec[i].imag;
|
|
283
|
+
}
|
|
284
|
+
result.Set("URe", URe);
|
|
285
|
+
result.Set("UIm", UIm);
|
|
286
|
+
|
|
287
|
+
// V = conj(VT^T): conjugate transpose of VT
|
|
288
|
+
int vt_rows = (jobz == 'S') ? k : n;
|
|
289
|
+
int vt_cols = n;
|
|
290
|
+
int v_size = vt_rows * vt_cols;
|
|
291
|
+
auto VRe = Napi::Float64Array::New(env, static_cast<size_t>(v_size));
|
|
292
|
+
auto VIm = Napi::Float64Array::New(env, static_cast<size_t>(v_size));
|
|
293
|
+
for (int i = 0; i < vt_rows; i++) {
|
|
294
|
+
for (int j = 0; j < vt_cols; j++) {
|
|
295
|
+
// V(j, i) = conj(VT(i, j)) — column-major conjugate transpose
|
|
296
|
+
int v_idx = j + i * vt_cols;
|
|
297
|
+
int vt_idx = i + j * vt_rows;
|
|
298
|
+
VRe[v_idx] = vt_vec[vt_idx].real;
|
|
299
|
+
VIm[v_idx] = -vt_vec[vt_idx].imag; // conjugate
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
result.Set("VRe", VRe);
|
|
303
|
+
result.Set("VIm", VIm);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return result;
|
|
307
|
+
}
|