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.
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  #include "lapack_common.h"
17
+ #include <string>
17
18
 
18
19
  // ── svd() ─────────────────────────────────────────────────────────────────────
19
20
 
@@ -147,3 +148,198 @@ Napi::Value Svd(const Napi::CallbackInfo& info) {
147
148
 
148
149
  return result;
149
150
  }
151
+
152
+ // ── svdComplex() ─────────────────────────────────────────────────────────────
153
+
154
+ Napi::Value SvdComplex(const Napi::CallbackInfo& info) {
155
+ Napi::Env env = info.Env();
156
+
157
+ if (info.Length() < 6
158
+ || !info[0].IsTypedArray()
159
+ || !info[1].IsTypedArray()
160
+ || !info[2].IsNumber()
161
+ || !info[3].IsNumber()
162
+ || !info[4].IsBoolean()
163
+ || !info[5].IsBoolean()) {
164
+ Napi::TypeError::New(env,
165
+ "svdComplex: expected (Float64Array dataRe, Float64Array dataIm, number m, number n, boolean econ, boolean computeUV)")
166
+ .ThrowAsJavaScriptException();
167
+ return env.Null();
168
+ }
169
+
170
+ auto arrRe = info[0].As<Napi::TypedArray>();
171
+ auto arrIm = info[1].As<Napi::TypedArray>();
172
+ if (arrRe.TypedArrayType() != napi_float64_array || arrIm.TypedArrayType() != napi_float64_array) {
173
+ Napi::TypeError::New(env, "svdComplex: data must be Float64Arrays")
174
+ .ThrowAsJavaScriptException();
175
+ return env.Null();
176
+ }
177
+
178
+ int m = info[2].As<Napi::Number>().Int32Value();
179
+ int n = info[3].As<Napi::Number>().Int32Value();
180
+ bool econ = info[4].As<Napi::Boolean>().Value();
181
+ bool computeUV = info[5].As<Napi::Boolean>().Value();
182
+
183
+ if (m <= 0 || n <= 0
184
+ || static_cast<int>(arrRe.ElementLength()) != m * n
185
+ || static_cast<int>(arrIm.ElementLength()) != m * n) {
186
+ Napi::RangeError::New(env, "svdComplex: data.length must equal m*n")
187
+ .ThrowAsJavaScriptException();
188
+ return env.Null();
189
+ }
190
+
191
+ auto float64Re = info[0].As<Napi::Float64Array>();
192
+ auto float64Im = info[1].As<Napi::Float64Array>();
193
+ int k = m < n ? m : n;
194
+
195
+ // Convert split real/imag to interleaved complex format
196
+ std::vector<lapack_complex_double> a(m * n);
197
+ for (int i = 0; i < m * n; ++i) {
198
+ a[i].real = float64Re[i];
199
+ a[i].imag = float64Im[i];
200
+ }
201
+
202
+ std::vector<double> s(k);
203
+ int info_val = 0;
204
+
205
+ char jobz;
206
+ if (!computeUV) {
207
+ jobz = 'N';
208
+ } else if (econ) {
209
+ jobz = 'S';
210
+ } else {
211
+ jobz = 'A';
212
+ }
213
+
214
+ int ldu, ldvt;
215
+ std::vector<lapack_complex_double> u_vec, vt_vec;
216
+
217
+ if (jobz == 'N') {
218
+ ldu = m;
219
+ ldvt = n;
220
+ } else if (jobz == 'S') {
221
+ ldu = m;
222
+ ldvt = k;
223
+ u_vec.resize(m * k);
224
+ vt_vec.resize(k * n);
225
+ } else { // 'A'
226
+ ldu = m;
227
+ ldvt = n;
228
+ u_vec.resize(m * m);
229
+ vt_vec.resize(n * n);
230
+ }
231
+
232
+ lapack_complex_double* u_ptr = (jobz == 'N') ? nullptr : u_vec.data();
233
+ lapack_complex_double* vt_ptr = (jobz == 'N') ? nullptr : vt_vec.data();
234
+
235
+ // Compute rwork size for zgesdd
236
+ // For jobz='N': 7*min(m,n) (some implementations need more than the documented 5)
237
+ // For jobz='S' or 'A': min(m,n) * max(5*min(m,n)+7, 2*max(m,n)+2*min(m,n)+1)
238
+ int rwork_size;
239
+ if (jobz == 'N') {
240
+ rwork_size = 7 * k;
241
+ } else {
242
+ int t1 = 5 * k + 7;
243
+ int t2 = 2 * std::max(m, n) + 2 * k + 1;
244
+ rwork_size = k * std::max(t1, t2);
245
+ }
246
+ std::vector<double> rwork(rwork_size);
247
+ std::vector<int> iwork(8 * k);
248
+
249
+ // Keep a copy of the input for potential zgesvd fallback
250
+ std::vector<lapack_complex_double> a_backup(a);
251
+
252
+ // Workspace query
253
+ int lwork = -1;
254
+ lapack_complex_double work_query;
255
+ zgesdd_(&jobz, &m, &n, a.data(), &m, s.data(), u_ptr, &ldu, vt_ptr, &ldvt,
256
+ &work_query, &lwork, rwork.data(), iwork.data(), &info_val);
257
+
258
+ lwork = static_cast<int>(work_query.real);
259
+ if (lwork < 1) lwork = 3 * k + std::max(m, n);
260
+
261
+ std::vector<lapack_complex_double> work(lwork);
262
+ zgesdd_(&jobz, &m, &n, a.data(), &m, s.data(), u_ptr, &ldu, vt_ptr, &ldvt,
263
+ work.data(), &lwork, rwork.data(), iwork.data(), &info_val);
264
+
265
+ // If zgesdd fails, fall back to zgesvd (standard algorithm, more robust)
266
+ if (info_val != 0) {
267
+ // Restore the input matrix (zgesdd overwrites it)
268
+ a = a_backup;
269
+ info_val = 0;
270
+
271
+ // Map jobz to jobu/jobvt for zgesvd
272
+ char jobu, jobvt;
273
+ if (jobz == 'N') {
274
+ jobu = 'N'; jobvt = 'N';
275
+ } else if (jobz == 'S') {
276
+ jobu = 'S'; jobvt = 'S';
277
+ } else {
278
+ jobu = 'A'; jobvt = 'A';
279
+ }
280
+
281
+ // rwork for zgesvd: 5*min(m,n)
282
+ std::vector<double> rwork_svd(5 * k);
283
+
284
+ // Workspace query for zgesvd
285
+ lwork = -1;
286
+ zgesvd_(&jobu, &jobvt, &m, &n, a.data(), &m, s.data(), u_ptr, &ldu,
287
+ vt_ptr, &ldvt, &work_query, &lwork, rwork_svd.data(), &info_val);
288
+
289
+ lwork = static_cast<int>(work_query.real);
290
+ if (lwork < 1) lwork = 2 * k + std::max(m, n);
291
+
292
+ work.resize(lwork);
293
+ info_val = 0;
294
+ zgesvd_(&jobu, &jobvt, &m, &n, a.data(), &m, s.data(), u_ptr, &ldu,
295
+ vt_ptr, &ldvt, work.data(), &lwork, rwork_svd.data(), &info_val);
296
+
297
+ if (info_val != 0) {
298
+ std::string msg = "svdComplex: zgesvd failed (info=" + std::to_string(info_val)
299
+ + ", m=" + std::to_string(m) + ", n=" + std::to_string(n) + ")";
300
+ Napi::Error::New(env, msg).ThrowAsJavaScriptException();
301
+ return env.Null();
302
+ }
303
+ }
304
+
305
+ // Build result
306
+ auto result = Napi::Object::New(env);
307
+
308
+ // S is always real
309
+ auto S_arr = Napi::Float64Array::New(env, static_cast<size_t>(k));
310
+ std::memcpy(S_arr.Data(), s.data(), k * sizeof(double));
311
+ result.Set("S", S_arr);
312
+
313
+ if (computeUV) {
314
+ // U: convert from interleaved to split real/imag
315
+ int u_size = (jobz == 'S') ? m * k : m * m;
316
+ auto URe = Napi::Float64Array::New(env, static_cast<size_t>(u_size));
317
+ auto UIm = Napi::Float64Array::New(env, static_cast<size_t>(u_size));
318
+ for (int i = 0; i < u_size; i++) {
319
+ URe[i] = u_vec[i].real;
320
+ UIm[i] = u_vec[i].imag;
321
+ }
322
+ result.Set("URe", URe);
323
+ result.Set("UIm", UIm);
324
+
325
+ // V = conj(VT^T): conjugate transpose of VT
326
+ int vt_rows = (jobz == 'S') ? k : n;
327
+ int vt_cols = n;
328
+ int v_size = vt_rows * vt_cols;
329
+ auto VRe = Napi::Float64Array::New(env, static_cast<size_t>(v_size));
330
+ auto VIm = Napi::Float64Array::New(env, static_cast<size_t>(v_size));
331
+ for (int i = 0; i < vt_rows; i++) {
332
+ for (int j = 0; j < vt_cols; j++) {
333
+ // V(j, i) = conj(VT(i, j)) — column-major conjugate transpose
334
+ int v_idx = j + i * vt_cols;
335
+ int vt_idx = i + j * vt_rows;
336
+ VRe[v_idx] = vt_vec[vt_idx].real;
337
+ VIm[v_idx] = -vt_vec[vt_idx].imag; // conjugate
338
+ }
339
+ }
340
+ result.Set("VRe", VRe);
341
+ result.Set("VIm", VIm);
342
+ }
343
+
344
+ return result;
345
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "numbl",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "description": "Run .m source files in the browser and on the command line by compiling to JavaScript",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/magland/numbl.git"
9
+ "url": "git+https://github.com/magland/numbl.git"
10
10
  },
11
11
  "keywords": [
12
12
  "compiler",
@@ -74,7 +74,7 @@
74
74
  "@xterm/xterm": "^6.0.0",
75
75
  "cors": "^2.8.5",
76
76
  "dexie": "^4.3.0",
77
- "esbuild": "^0.24.2",
77
+ "esbuild": "^0.27.3",
78
78
  "eslint": "^9.39.1",
79
79
  "eslint-config-prettier": "^10.1.8",
80
80
  "eslint-plugin-react-hooks": "^7.0.1",