numbl 0.0.22 → 0.1.0
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 +2 -3
- package/binding.gyp +6 -4
- package/dist-cli/cli.js +33457 -30228
- package/native/elemwise.cpp +168 -0
- package/native/lapack_chol.cpp +1 -1
- package/native/lapack_eig.cpp +1 -1
- package/native/lapack_fft.cpp +1 -1
- package/native/lapack_fft_batch.cpp +1 -1
- package/native/lapack_inv.cpp +1 -1
- package/native/lapack_linsolve.cpp +1 -1
- package/native/lapack_lu.cpp +1 -1
- package/native/lapack_matmul.cpp +1 -1
- package/native/lapack_matmul_complex.cpp +110 -0
- package/native/lapack_qr.cpp +213 -1
- package/native/lapack_qz.cpp +1 -1
- package/native/lapack_svd.cpp +1 -1
- package/native/{lapack_addon.cpp → numbl_addon.cpp} +34 -3
- package/native/{lapack_common.h → numbl_addon_common.h} +20 -1
- package/package.json +2 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element-wise binary operations on Float64Arrays.
|
|
3
|
+
*
|
|
4
|
+
* Real:
|
|
5
|
+
* elemwise(a: Float64Array, b: Float64Array, op: number): Float64Array
|
|
6
|
+
* op: 0=add, 1=sub, 2=mul, 3=div
|
|
7
|
+
*
|
|
8
|
+
* Complex:
|
|
9
|
+
* elemwiseComplex(aRe: Float64Array, aIm: Float64Array,
|
|
10
|
+
* bRe: Float64Array, bIm: Float64Array,
|
|
11
|
+
* op: number): { re: Float64Array, im: Float64Array }
|
|
12
|
+
* op: 0=add, 1=sub, 2=mul, 3=div
|
|
13
|
+
* Pass null for aIm or bIm to treat as zero (mixed real/complex).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include "numbl_addon_common.h"
|
|
17
|
+
|
|
18
|
+
// ── elemwise() — real element-wise binary op ────────────────────────────────
|
|
19
|
+
|
|
20
|
+
Napi::Value Elemwise(const Napi::CallbackInfo& info) {
|
|
21
|
+
Napi::Env env = info.Env();
|
|
22
|
+
|
|
23
|
+
if (info.Length() < 3
|
|
24
|
+
|| !info[0].IsTypedArray()
|
|
25
|
+
|| !info[1].IsTypedArray()
|
|
26
|
+
|| !info[2].IsNumber()) {
|
|
27
|
+
Napi::TypeError::New(env,
|
|
28
|
+
"elemwise: expected (Float64Array a, Float64Array b, number op)")
|
|
29
|
+
.ThrowAsJavaScriptException();
|
|
30
|
+
return env.Null();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
auto arrA = info[0].As<Napi::Float64Array>();
|
|
34
|
+
auto arrB = info[1].As<Napi::Float64Array>();
|
|
35
|
+
int op = info[2].As<Napi::Number>().Int32Value();
|
|
36
|
+
|
|
37
|
+
size_t n = arrA.ElementLength();
|
|
38
|
+
if (arrB.ElementLength() != n) {
|
|
39
|
+
Napi::RangeError::New(env, "elemwise: arrays must have same length")
|
|
40
|
+
.ThrowAsJavaScriptException();
|
|
41
|
+
return env.Null();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
auto result = Napi::Float64Array::New(env, n);
|
|
45
|
+
const double* a = arrA.Data();
|
|
46
|
+
const double* b = arrB.Data();
|
|
47
|
+
double* out = result.Data();
|
|
48
|
+
|
|
49
|
+
switch (op) {
|
|
50
|
+
case 0: // add
|
|
51
|
+
for (size_t i = 0; i < n; i++) out[i] = a[i] + b[i];
|
|
52
|
+
break;
|
|
53
|
+
case 1: // sub
|
|
54
|
+
for (size_t i = 0; i < n; i++) out[i] = a[i] - b[i];
|
|
55
|
+
break;
|
|
56
|
+
case 2: // mul
|
|
57
|
+
for (size_t i = 0; i < n; i++) out[i] = a[i] * b[i];
|
|
58
|
+
break;
|
|
59
|
+
case 3: // div
|
|
60
|
+
for (size_t i = 0; i < n; i++) out[i] = a[i] / b[i];
|
|
61
|
+
break;
|
|
62
|
+
default:
|
|
63
|
+
Napi::RangeError::New(env, "elemwise: op must be 0-3")
|
|
64
|
+
.ThrowAsJavaScriptException();
|
|
65
|
+
return env.Null();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── elemwiseComplex() — complex element-wise binary op ──────────────────────
|
|
72
|
+
|
|
73
|
+
Napi::Value ElemwiseComplex(const Napi::CallbackInfo& info) {
|
|
74
|
+
Napi::Env env = info.Env();
|
|
75
|
+
|
|
76
|
+
// (aRe, aIm_or_null, bRe, bIm_or_null, op)
|
|
77
|
+
if (info.Length() < 5 || !info[0].IsTypedArray() || !info[2].IsTypedArray()
|
|
78
|
+
|| !info[4].IsNumber()) {
|
|
79
|
+
Napi::TypeError::New(env,
|
|
80
|
+
"elemwiseComplex: expected (Float64Array aRe, Float64Array|null aIm, "
|
|
81
|
+
"Float64Array bRe, Float64Array|null bIm, number op)")
|
|
82
|
+
.ThrowAsJavaScriptException();
|
|
83
|
+
return env.Null();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
auto arrARe = info[0].As<Napi::Float64Array>();
|
|
87
|
+
auto arrBRe = info[2].As<Napi::Float64Array>();
|
|
88
|
+
int op = info[4].As<Napi::Number>().Int32Value();
|
|
89
|
+
|
|
90
|
+
size_t n = arrARe.ElementLength();
|
|
91
|
+
if (arrBRe.ElementLength() != n) {
|
|
92
|
+
Napi::RangeError::New(env, "elemwiseComplex: arrays must have same length")
|
|
93
|
+
.ThrowAsJavaScriptException();
|
|
94
|
+
return env.Null();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const double* aRe = arrARe.Data();
|
|
98
|
+
const double* bRe = arrBRe.Data();
|
|
99
|
+
|
|
100
|
+
// aIm and bIm may be null (treat as zero)
|
|
101
|
+
bool hasAIm = info[1].IsTypedArray();
|
|
102
|
+
bool hasBIm = info[3].IsTypedArray();
|
|
103
|
+
const double* aIm = hasAIm ? info[1].As<Napi::Float64Array>().Data() : nullptr;
|
|
104
|
+
const double* bIm = hasBIm ? info[3].As<Napi::Float64Array>().Data() : nullptr;
|
|
105
|
+
|
|
106
|
+
auto outRe = Napi::Float64Array::New(env, n);
|
|
107
|
+
auto outIm = Napi::Float64Array::New(env, n);
|
|
108
|
+
double* oRe = outRe.Data();
|
|
109
|
+
double* oIm = outIm.Data();
|
|
110
|
+
|
|
111
|
+
switch (op) {
|
|
112
|
+
case 0: // add
|
|
113
|
+
for (size_t i = 0; i < n; i++) {
|
|
114
|
+
oRe[i] = aRe[i] + bRe[i];
|
|
115
|
+
oIm[i] = (aIm ? aIm[i] : 0.0) + (bIm ? bIm[i] : 0.0);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case 1: // sub
|
|
119
|
+
for (size_t i = 0; i < n; i++) {
|
|
120
|
+
oRe[i] = aRe[i] - bRe[i];
|
|
121
|
+
oIm[i] = (aIm ? aIm[i] : 0.0) - (bIm ? bIm[i] : 0.0);
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
case 2: { // mul: (a+bi)(c+di) = (ac-bd) + (ad+bc)i
|
|
125
|
+
for (size_t i = 0; i < n; i++) {
|
|
126
|
+
double ar = aRe[i], ai = aIm ? aIm[i] : 0.0;
|
|
127
|
+
double br = bRe[i], bi = bIm ? bIm[i] : 0.0;
|
|
128
|
+
oRe[i] = ar * br - ai * bi;
|
|
129
|
+
oIm[i] = ar * bi + ai * br;
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 3: { // div: (a+bi)/(c+di) = ((ac+bd) + (bc-ad)i) / (c²+d²)
|
|
134
|
+
for (size_t i = 0; i < n; i++) {
|
|
135
|
+
double ar = aRe[i], ai = aIm ? aIm[i] : 0.0;
|
|
136
|
+
double br = bRe[i], bi = bIm ? bIm[i] : 0.0;
|
|
137
|
+
double denom = br * br + bi * bi;
|
|
138
|
+
if (denom == 0.0) {
|
|
139
|
+
oRe[i] = (ar == 0.0 && ai == 0.0) ? 0.0 / 0.0 /* NaN */
|
|
140
|
+
: (ar > 0 ? 1.0 : ar < 0 ? -1.0 : 0.0) / 0.0 /* ±Inf */;
|
|
141
|
+
oIm[i] = (ar == 0.0 && ai == 0.0) ? 0.0
|
|
142
|
+
: (ai > 0 ? 1.0 : ai < 0 ? -1.0 : 0.0) / 0.0;
|
|
143
|
+
} else {
|
|
144
|
+
oRe[i] = (ar * br + ai * bi) / denom;
|
|
145
|
+
oIm[i] = (ai * br - ar * bi) / denom;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
default:
|
|
151
|
+
Napi::RangeError::New(env, "elemwiseComplex: op must be 0-3")
|
|
152
|
+
.ThrowAsJavaScriptException();
|
|
153
|
+
return env.Null();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check if result is purely real
|
|
157
|
+
bool isReal = true;
|
|
158
|
+
for (size_t i = 0; i < n; i++) {
|
|
159
|
+
if (oIm[i] != 0.0) { isReal = false; break; }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
auto result = Napi::Object::New(env);
|
|
163
|
+
result.Set("re", outRe);
|
|
164
|
+
if (!isReal) {
|
|
165
|
+
result.Set("im", outIm);
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
package/native/lapack_chol.cpp
CHANGED
package/native/lapack_eig.cpp
CHANGED
package/native/lapack_fft.cpp
CHANGED
package/native/lapack_inv.cpp
CHANGED
package/native/lapack_lu.cpp
CHANGED
package/native/lapack_matmul.cpp
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* matmulComplex() — Complex matrix-matrix multiplication via BLAS zgemm.
|
|
3
|
+
*
|
|
4
|
+
* matmulComplex(ARe: Float64Array, AIm: Float64Array,
|
|
5
|
+
* m: number, k: number,
|
|
6
|
+
* BRe: Float64Array, BIm: Float64Array,
|
|
7
|
+
* n: number): { re: Float64Array, im: Float64Array }
|
|
8
|
+
*
|
|
9
|
+
* Computes C = A * B where:
|
|
10
|
+
* A is an m×k complex matrix (split re/im) stored in column-major order
|
|
11
|
+
* B is a k×n complex matrix (split re/im) stored in column-major order
|
|
12
|
+
* C is an m×n complex matrix returned as {re, im} in column-major order
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
#include "numbl_addon_common.h"
|
|
16
|
+
|
|
17
|
+
Napi::Value MatmulComplex(const Napi::CallbackInfo& info) {
|
|
18
|
+
Napi::Env env = info.Env();
|
|
19
|
+
|
|
20
|
+
// matmulComplex(ARe, AIm, m, k, BRe, BIm, n)
|
|
21
|
+
if (info.Length() < 7
|
|
22
|
+
|| !info[0].IsTypedArray() // ARe
|
|
23
|
+
|| !info[1].IsTypedArray() // AIm
|
|
24
|
+
|| !info[2].IsNumber() // m
|
|
25
|
+
|| !info[3].IsNumber() // k
|
|
26
|
+
|| !info[4].IsTypedArray() // BRe
|
|
27
|
+
|| !info[5].IsTypedArray() // BIm
|
|
28
|
+
|| !info[6].IsNumber()) { // n
|
|
29
|
+
Napi::TypeError::New(env,
|
|
30
|
+
"matmulComplex: expected (Float64Array ARe, Float64Array AIm, "
|
|
31
|
+
"number m, number k, Float64Array BRe, Float64Array BIm, number n)")
|
|
32
|
+
.ThrowAsJavaScriptException();
|
|
33
|
+
return env.Null();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
auto arrARe = info[0].As<Napi::Float64Array>();
|
|
37
|
+
auto arrAIm = info[1].As<Napi::Float64Array>();
|
|
38
|
+
int m = info[2].As<Napi::Number>().Int32Value();
|
|
39
|
+
int k = info[3].As<Napi::Number>().Int32Value();
|
|
40
|
+
auto arrBRe = info[4].As<Napi::Float64Array>();
|
|
41
|
+
auto arrBIm = info[5].As<Napi::Float64Array>();
|
|
42
|
+
int n = info[6].As<Napi::Number>().Int32Value();
|
|
43
|
+
|
|
44
|
+
if (m < 0 || k < 0 || n < 0) {
|
|
45
|
+
Napi::RangeError::New(env, "matmulComplex: m, k, n must be non-negative")
|
|
46
|
+
.ThrowAsJavaScriptException();
|
|
47
|
+
return env.Null();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
int mk = m * k;
|
|
51
|
+
int kn = k * n;
|
|
52
|
+
int mn = m * n;
|
|
53
|
+
|
|
54
|
+
// Handle empty-dimension multiply
|
|
55
|
+
if (m == 0 || k == 0 || n == 0) {
|
|
56
|
+
auto result = Napi::Object::New(env);
|
|
57
|
+
result.Set("re", Napi::Float64Array::New(env, static_cast<size_t>(mn)));
|
|
58
|
+
result.Set("im", Napi::Float64Array::New(env, static_cast<size_t>(mn)));
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Interleave into complex arrays for zgemm
|
|
63
|
+
std::vector<lapack_complex_double> a(mk);
|
|
64
|
+
for (int i = 0; i < mk; ++i) {
|
|
65
|
+
a[i].real = arrARe[i];
|
|
66
|
+
a[i].imag = arrAIm[i];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
std::vector<lapack_complex_double> b(kn);
|
|
70
|
+
for (int i = 0; i < kn; ++i) {
|
|
71
|
+
b[i].real = arrBRe[i];
|
|
72
|
+
b[i].imag = arrBIm[i];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
std::vector<lapack_complex_double> c(mn, {0.0, 0.0});
|
|
76
|
+
|
|
77
|
+
char transa = 'N';
|
|
78
|
+
char transb = 'N';
|
|
79
|
+
lapack_complex_double alpha = {1.0, 0.0};
|
|
80
|
+
lapack_complex_double beta = {0.0, 0.0};
|
|
81
|
+
int lda = m;
|
|
82
|
+
int ldb = k;
|
|
83
|
+
int ldc = m;
|
|
84
|
+
|
|
85
|
+
zgemm_(&transa, &transb,
|
|
86
|
+
&m, &n, &k,
|
|
87
|
+
&alpha, a.data(), &lda,
|
|
88
|
+
b.data(), &ldb,
|
|
89
|
+
&beta, c.data(), &ldc);
|
|
90
|
+
|
|
91
|
+
// Deinterleave result
|
|
92
|
+
auto result = Napi::Object::New(env);
|
|
93
|
+
auto outRe = Napi::Float64Array::New(env, static_cast<size_t>(mn));
|
|
94
|
+
auto outIm = Napi::Float64Array::New(env, static_cast<size_t>(mn));
|
|
95
|
+
for (int i = 0; i < mn; ++i) {
|
|
96
|
+
outRe[i] = c[i].real;
|
|
97
|
+
outIm[i] = c[i].imag;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check if result is purely real
|
|
101
|
+
bool isReal = true;
|
|
102
|
+
for (int i = 0; i < mn; ++i) {
|
|
103
|
+
if (outIm[i] != 0.0) { isReal = false; break; }
|
|
104
|
+
}
|
|
105
|
+
result.Set("re", outRe);
|
|
106
|
+
if (!isReal) {
|
|
107
|
+
result.Set("im", outIm);
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
package/native/lapack_qr.cpp
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* wantQ=false: skips Q generation.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
#include "
|
|
16
|
+
#include "numbl_addon_common.h"
|
|
17
17
|
|
|
18
18
|
// ── qr() ─────────────────────────────────────────────────────────────────────
|
|
19
19
|
|
|
@@ -115,6 +115,218 @@ Napi::Value Qr(const Napi::CallbackInfo& info) {
|
|
|
115
115
|
return result;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// ── qrPivot() ────────────────────────────────────────────────────────────────
|
|
119
|
+
// Column-pivoted QR: A*P = Q*R via dgeqp3 + dorgqr.
|
|
120
|
+
// Returns {Q, R, jpvt} where jpvt is a 1-based permutation vector.
|
|
121
|
+
|
|
122
|
+
Napi::Value QrPivot(const Napi::CallbackInfo& info) {
|
|
123
|
+
Napi::Env env = info.Env();
|
|
124
|
+
|
|
125
|
+
if (info.Length() < 4
|
|
126
|
+
|| !info[0].IsTypedArray()
|
|
127
|
+
|| !info[1].IsNumber()
|
|
128
|
+
|| !info[2].IsNumber()
|
|
129
|
+
|| !info[3].IsBoolean()) {
|
|
130
|
+
Napi::TypeError::New(env,
|
|
131
|
+
"qrPivot: expected (Float64Array data, number m, number n, boolean econ)")
|
|
132
|
+
.ThrowAsJavaScriptException();
|
|
133
|
+
return env.Null();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
auto arr = info[0].As<Napi::TypedArray>();
|
|
137
|
+
if (arr.TypedArrayType() != napi_float64_array) {
|
|
138
|
+
Napi::TypeError::New(env, "qrPivot: data must be a Float64Array")
|
|
139
|
+
.ThrowAsJavaScriptException();
|
|
140
|
+
return env.Null();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
int m = info[1].As<Napi::Number>().Int32Value();
|
|
144
|
+
int n = info[2].As<Napi::Number>().Int32Value();
|
|
145
|
+
bool econ = info[3].As<Napi::Boolean>().Value();
|
|
146
|
+
|
|
147
|
+
if (m <= 0 || n <= 0 || static_cast<int>(arr.ElementLength()) != m * n) {
|
|
148
|
+
Napi::RangeError::New(env, "qrPivot: data.length must equal m*n")
|
|
149
|
+
.ThrowAsJavaScriptException();
|
|
150
|
+
return env.Null();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
auto float64arr = info[0].As<Napi::Float64Array>();
|
|
154
|
+
int k = m < n ? m : n;
|
|
155
|
+
|
|
156
|
+
// Copy input data
|
|
157
|
+
std::vector<double> a(m * n);
|
|
158
|
+
std::memcpy(a.data(), float64arr.Data(), m * n * sizeof(double));
|
|
159
|
+
|
|
160
|
+
// jpvt initialised to 0 → all columns are free
|
|
161
|
+
std::vector<int> jpvt(n, 0);
|
|
162
|
+
std::vector<double> tau(k);
|
|
163
|
+
int info_val = 0;
|
|
164
|
+
|
|
165
|
+
// Workspace query
|
|
166
|
+
int lwork = -1;
|
|
167
|
+
double work_query = 0.0;
|
|
168
|
+
dgeqp3_(&m, &n, a.data(), &m, jpvt.data(), tau.data(),
|
|
169
|
+
&work_query, &lwork, &info_val);
|
|
170
|
+
lwork = static_cast<int>(work_query);
|
|
171
|
+
if (lwork < 1) lwork = 3 * n + 1;
|
|
172
|
+
|
|
173
|
+
std::vector<double> work(lwork);
|
|
174
|
+
dgeqp3_(&m, &n, a.data(), &m, jpvt.data(), tau.data(),
|
|
175
|
+
work.data(), &lwork, &info_val);
|
|
176
|
+
|
|
177
|
+
if (!checkLapackInfo(env, info_val, "qrPivot", "dgeqp3"))
|
|
178
|
+
return env.Null();
|
|
179
|
+
|
|
180
|
+
// Extract R from the upper triangle
|
|
181
|
+
int r_rows = econ ? k : m;
|
|
182
|
+
std::vector<double> R(r_rows * n, 0.0);
|
|
183
|
+
for (int j = 0; j < n; j++) {
|
|
184
|
+
int ilim = j < k ? j : k - 1;
|
|
185
|
+
for (int i = 0; i <= ilim; i++) {
|
|
186
|
+
R[i + j * r_rows] = a[i + j * m];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Generate Q via dorgqr
|
|
191
|
+
int q_cols = econ ? k : m;
|
|
192
|
+
std::vector<double> q_buf(m * q_cols, 0.0);
|
|
193
|
+
int cols_to_copy = n < q_cols ? n : q_cols;
|
|
194
|
+
for (int j = 0; j < cols_to_copy; j++) {
|
|
195
|
+
for (int i = 0; i < m; i++) {
|
|
196
|
+
q_buf[i + j * m] = a[i + j * m];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
lwork = -1;
|
|
201
|
+
dorgqr_(&m, &q_cols, &k, q_buf.data(), &m, tau.data(),
|
|
202
|
+
&work_query, &lwork, &info_val);
|
|
203
|
+
lwork = static_cast<int>(work_query);
|
|
204
|
+
if (lwork < 1) lwork = q_cols;
|
|
205
|
+
|
|
206
|
+
work.assign(lwork, 0.0);
|
|
207
|
+
dorgqr_(&m, &q_cols, &k, q_buf.data(), &m, tau.data(),
|
|
208
|
+
work.data(), &lwork, &info_val);
|
|
209
|
+
|
|
210
|
+
if (!checkLapackInfo(env, info_val, "qrPivot", "dorgqr"))
|
|
211
|
+
return env.Null();
|
|
212
|
+
|
|
213
|
+
auto result = Napi::Object::New(env);
|
|
214
|
+
result.Set("Q", vecToF64(env, q_buf));
|
|
215
|
+
result.Set("R", vecToF64(env, R));
|
|
216
|
+
result.Set("jpvt", vecToI32(env, jpvt));
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── qrPivotComplex() ─────────────────────────────────────────────────────────
|
|
221
|
+
// Column-pivoted complex QR: A*P = Q*R via zgeqp3 + zungqr.
|
|
222
|
+
|
|
223
|
+
Napi::Value QrPivotComplex(const Napi::CallbackInfo& info) {
|
|
224
|
+
Napi::Env env = info.Env();
|
|
225
|
+
|
|
226
|
+
if (info.Length() < 5
|
|
227
|
+
|| !info[0].IsTypedArray()
|
|
228
|
+
|| !info[1].IsTypedArray()
|
|
229
|
+
|| !info[2].IsNumber()
|
|
230
|
+
|| !info[3].IsNumber()
|
|
231
|
+
|| !info[4].IsBoolean()) {
|
|
232
|
+
Napi::TypeError::New(env,
|
|
233
|
+
"qrPivotComplex: expected (Float64Array dataRe, Float64Array dataIm, "
|
|
234
|
+
"number m, number n, boolean econ)")
|
|
235
|
+
.ThrowAsJavaScriptException();
|
|
236
|
+
return env.Null();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
auto arrRe = info[0].As<Napi::TypedArray>();
|
|
240
|
+
auto arrIm = info[1].As<Napi::TypedArray>();
|
|
241
|
+
if (arrRe.TypedArrayType() != napi_float64_array ||
|
|
242
|
+
arrIm.TypedArrayType() != napi_float64_array) {
|
|
243
|
+
Napi::TypeError::New(env, "qrPivotComplex: dataRe and dataIm must be Float64Arrays")
|
|
244
|
+
.ThrowAsJavaScriptException();
|
|
245
|
+
return env.Null();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
int m = info[2].As<Napi::Number>().Int32Value();
|
|
249
|
+
int n = info[3].As<Napi::Number>().Int32Value();
|
|
250
|
+
bool econ = info[4].As<Napi::Boolean>().Value();
|
|
251
|
+
|
|
252
|
+
if (m <= 0 || n <= 0 ||
|
|
253
|
+
static_cast<int>(arrRe.ElementLength()) != m * n ||
|
|
254
|
+
static_cast<int>(arrIm.ElementLength()) != m * n) {
|
|
255
|
+
Napi::RangeError::New(env,
|
|
256
|
+
"qrPivotComplex: dataRe.length and dataIm.length must equal m*n")
|
|
257
|
+
.ThrowAsJavaScriptException();
|
|
258
|
+
return env.Null();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
int k = m < n ? m : n;
|
|
262
|
+
|
|
263
|
+
auto a = splitToInterleaved(
|
|
264
|
+
info[0].As<Napi::Float64Array>(),
|
|
265
|
+
info[1].As<Napi::Float64Array>(), m * n);
|
|
266
|
+
|
|
267
|
+
std::vector<int> jpvt(n, 0);
|
|
268
|
+
std::vector<lapack_complex_double> tau(k);
|
|
269
|
+
std::vector<double> rwork(2 * n);
|
|
270
|
+
int info_val = 0;
|
|
271
|
+
|
|
272
|
+
// Workspace query
|
|
273
|
+
int lwork = -1;
|
|
274
|
+
lapack_complex_double work_query;
|
|
275
|
+
zgeqp3_(&m, &n, a.data(), &m, jpvt.data(), tau.data(),
|
|
276
|
+
&work_query, &lwork, rwork.data(), &info_val);
|
|
277
|
+
lwork = static_cast<int>(work_query.real);
|
|
278
|
+
if (lwork < 1) lwork = n + 1;
|
|
279
|
+
|
|
280
|
+
std::vector<lapack_complex_double> work(lwork);
|
|
281
|
+
zgeqp3_(&m, &n, a.data(), &m, jpvt.data(), tau.data(),
|
|
282
|
+
work.data(), &lwork, rwork.data(), &info_val);
|
|
283
|
+
|
|
284
|
+
if (!checkLapackInfo(env, info_val, "qrPivotComplex", "zgeqp3"))
|
|
285
|
+
return env.Null();
|
|
286
|
+
|
|
287
|
+
// Extract R
|
|
288
|
+
int r_rows = econ ? k : m;
|
|
289
|
+
std::vector<double> R_re(r_rows * n, 0.0);
|
|
290
|
+
std::vector<double> R_im(r_rows * n, 0.0);
|
|
291
|
+
for (int j = 0; j < n; j++) {
|
|
292
|
+
int ilim = j < k ? j : k - 1;
|
|
293
|
+
for (int i = 0; i <= ilim; i++) {
|
|
294
|
+
R_re[i + j * r_rows] = a[i + j * m].real;
|
|
295
|
+
R_im[i + j * r_rows] = a[i + j * m].imag;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Generate Q via zungqr
|
|
300
|
+
int q_cols = econ ? k : m;
|
|
301
|
+
std::vector<lapack_complex_double> q_buf(m * q_cols, {0.0, 0.0});
|
|
302
|
+
int cols_to_copy = n < q_cols ? n : q_cols;
|
|
303
|
+
for (int j = 0; j < cols_to_copy; j++) {
|
|
304
|
+
for (int i = 0; i < m; i++) {
|
|
305
|
+
q_buf[i + j * m] = a[i + j * m];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
lwork = -1;
|
|
310
|
+
zungqr_(&m, &q_cols, &k, q_buf.data(), &m, tau.data(),
|
|
311
|
+
&work_query, &lwork, &info_val);
|
|
312
|
+
lwork = static_cast<int>(work_query.real);
|
|
313
|
+
if (lwork < 1) lwork = q_cols;
|
|
314
|
+
|
|
315
|
+
work.assign(lwork, {0.0, 0.0});
|
|
316
|
+
zungqr_(&m, &q_cols, &k, q_buf.data(), &m, tau.data(),
|
|
317
|
+
work.data(), &lwork, &info_val);
|
|
318
|
+
|
|
319
|
+
if (!checkLapackInfo(env, info_val, "qrPivotComplex", "zungqr"))
|
|
320
|
+
return env.Null();
|
|
321
|
+
|
|
322
|
+
auto result = Napi::Object::New(env);
|
|
323
|
+
setSplitComplex(env, result, "QRe", "QIm", q_buf.data(), m * q_cols);
|
|
324
|
+
result.Set("RRe", vecToF64(env, R_re));
|
|
325
|
+
result.Set("RIm", vecToF64(env, R_im));
|
|
326
|
+
result.Set("jpvt", vecToI32(env, jpvt));
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
|
|
118
330
|
// ── qrComplex() ──────────────────────────────────────────────────────────────
|
|
119
331
|
|
|
120
332
|
Napi::Value QrComplex(const Napi::CallbackInfo& info) {
|
package/native/lapack_qz.cpp
CHANGED
package/native/lapack_svd.cpp
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* numbl native addon — LAPACK/BLAS, FFT, element-wise arithmetic, and more.
|
|
3
3
|
*
|
|
4
4
|
* Exported functions (see individual .cpp files for full documentation):
|
|
5
5
|
*
|
|
@@ -19,17 +19,42 @@
|
|
|
19
19
|
* cholComplex(dataRe, dataIm, n, upper) — complex Cholesky (lapack_chol.cpp)
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
#include "
|
|
22
|
+
#include "numbl_addon_common.h"
|
|
23
|
+
#include <cstdlib>
|
|
24
|
+
|
|
25
|
+
extern "C" {
|
|
26
|
+
void openblas_set_num_threads(int num_threads);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Addon version ────────────────────────────────────────────────────────────
|
|
30
|
+
// Bump this integer whenever the addon's API changes (new functions, signature
|
|
31
|
+
// changes, etc.) so that the JS side can detect stale builds.
|
|
32
|
+
static const int ADDON_VERSION = 1;
|
|
33
|
+
|
|
34
|
+
static Napi::Value AddonVersion(const Napi::CallbackInfo& info) {
|
|
35
|
+
return Napi::Number::New(info.Env(), ADDON_VERSION);
|
|
36
|
+
}
|
|
23
37
|
|
|
24
38
|
// ── Module initialisation ─────────────────────────────────────────────────────
|
|
25
39
|
|
|
26
40
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
41
|
+
// Use single-threaded BLAS unless the user explicitly set the env var.
|
|
42
|
+
// Multi-threaded BLAS adds overhead for the many small matmuls in numbl.
|
|
43
|
+
if (!std::getenv("OPENBLAS_NUM_THREADS")) {
|
|
44
|
+
openblas_set_num_threads(1);
|
|
45
|
+
}
|
|
46
|
+
exports.Set(Napi::String::New(env, "addonVersion"),
|
|
47
|
+
Napi::Function::New(env, AddonVersion));
|
|
27
48
|
exports.Set(Napi::String::New(env, "inv"),
|
|
28
49
|
Napi::Function::New(env, Inv));
|
|
29
50
|
exports.Set(Napi::String::New(env, "invComplex"),
|
|
30
51
|
Napi::Function::New(env, InvComplex));
|
|
31
52
|
exports.Set(Napi::String::New(env, "qr"),
|
|
32
53
|
Napi::Function::New(env, Qr));
|
|
54
|
+
exports.Set(Napi::String::New(env, "qrPivot"),
|
|
55
|
+
Napi::Function::New(env, QrPivot));
|
|
56
|
+
exports.Set(Napi::String::New(env, "qrPivotComplex"),
|
|
57
|
+
Napi::Function::New(env, QrPivotComplex));
|
|
33
58
|
exports.Set(Napi::String::New(env, "qrComplex"),
|
|
34
59
|
Napi::Function::New(env, QrComplex));
|
|
35
60
|
exports.Set(Napi::String::New(env, "lu"),
|
|
@@ -42,6 +67,8 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
42
67
|
Napi::Function::New(env, SvdComplex));
|
|
43
68
|
exports.Set(Napi::String::New(env, "matmul"),
|
|
44
69
|
Napi::Function::New(env, Matmul));
|
|
70
|
+
exports.Set(Napi::String::New(env, "matmulComplex"),
|
|
71
|
+
Napi::Function::New(env, MatmulComplex));
|
|
45
72
|
exports.Set(Napi::String::New(env, "linsolve"),
|
|
46
73
|
Napi::Function::New(env, Linsolve));
|
|
47
74
|
exports.Set(Napi::String::New(env, "linsolveComplex"),
|
|
@@ -64,7 +91,11 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
64
91
|
Napi::Function::New(env, Fft1dComplex));
|
|
65
92
|
exports.Set(Napi::String::New(env, "fftAlongDim"),
|
|
66
93
|
Napi::Function::New(env, FftAlongDim));
|
|
94
|
+
exports.Set(Napi::String::New(env, "elemwise"),
|
|
95
|
+
Napi::Function::New(env, Elemwise));
|
|
96
|
+
exports.Set(Napi::String::New(env, "elemwiseComplex"),
|
|
97
|
+
Napi::Function::New(env, ElemwiseComplex));
|
|
67
98
|
return exports;
|
|
68
99
|
}
|
|
69
100
|
|
|
70
|
-
NODE_API_MODULE(
|
|
101
|
+
NODE_API_MODULE(numbl_addon, Init)
|