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.
@@ -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
+ }
@@ -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
+ }
@@ -1,14 +1,17 @@
1
1
  /**
2
- * qr() — QR decomposition via LAPACK dgeqrf + dorgqr.
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 dorgqr; Q property is absent from the returned object.
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
+ }