numbl 0.0.1
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 +24 -0
- package/binding.gyp +25 -0
- package/dist-cli/cli.js +31008 -0
- package/dist-plot-viewer/assets/index-CYInUJ8O.js +9 -0
- package/dist-plot-viewer/index.html +12 -0
- package/native/lapack_addon.cpp +37 -0
- package/native/lapack_common.h +90 -0
- package/native/lapack_eig.cpp +142 -0
- package/native/lapack_inv.cpp +193 -0
- package/native/lapack_linsolve.cpp +139 -0
- package/native/lapack_matmul.cpp +103 -0
- package/native/lapack_qr.cpp +133 -0
- package/native/lapack_svd.cpp +149 -0
- package/package.json +86 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common includes, type definitions, LAPACK/BLAS declarations, and function
|
|
3
|
+
* prototypes shared across the lapack_addon source files.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#include <napi.h>
|
|
9
|
+
#include <algorithm>
|
|
10
|
+
#include <cstring>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
// ── Complex number type used by LAPACK (interleaved real and imaginary parts) ─
|
|
14
|
+
|
|
15
|
+
struct lapack_complex_double {
|
|
16
|
+
double real;
|
|
17
|
+
double imag;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// ── LAPACK/BLAS declarations (Fortran ABI — all args passed by pointer) ───────
|
|
21
|
+
|
|
22
|
+
extern "C" {
|
|
23
|
+
// ── Real matrix inversion ─────────────────────────────────────────────────
|
|
24
|
+
// LU factorisation: A = P * L * U
|
|
25
|
+
void dgetrf_(int* m, int* n, double* a, int* lda, int* ipiv, int* info);
|
|
26
|
+
// Matrix inversion using LU factors produced by dgetrf
|
|
27
|
+
void dgetri_(int* n, double* a, int* lda, int* ipiv,
|
|
28
|
+
double* work, int* lwork, int* info);
|
|
29
|
+
|
|
30
|
+
// ── Complex matrix inversion ──────────────────────────────────────────────
|
|
31
|
+
// LU factorisation for complex matrices: A = P * L * U
|
|
32
|
+
void zgetrf_(int* m, int* n, lapack_complex_double* a, int* lda,
|
|
33
|
+
int* ipiv, int* info);
|
|
34
|
+
// Complex matrix inversion using LU factors produced by zgetrf
|
|
35
|
+
void zgetri_(int* n, lapack_complex_double* a, int* lda, int* ipiv,
|
|
36
|
+
lapack_complex_double* work, int* lwork, int* info);
|
|
37
|
+
|
|
38
|
+
// ── QR factorisation ──────────────────────────────────────────────────────
|
|
39
|
+
// Compute QR factorisation of a general m×n matrix: A = Q * R
|
|
40
|
+
void dgeqrf_(int* m, int* n, double* a, int* lda, double* tau,
|
|
41
|
+
double* work, int* lwork, int* info);
|
|
42
|
+
// Generate the m×n (or m×m) orthogonal matrix Q from dgeqrf reflectors
|
|
43
|
+
void dorgqr_(int* m, int* n, int* k, double* a, int* lda, double* tau,
|
|
44
|
+
double* work, int* lwork, int* info);
|
|
45
|
+
|
|
46
|
+
// ── SVD ───────────────────────────────────────────────────────────────────
|
|
47
|
+
// Compute SVD using divide-and-conquer: A = U * Sigma * V^T
|
|
48
|
+
void dgesdd_(char* jobz, int* m, int* n, double* a, int* lda,
|
|
49
|
+
double* s, double* u, int* ldu, double* vt, int* ldvt,
|
|
50
|
+
double* work, int* lwork, int* iwork, int* info);
|
|
51
|
+
|
|
52
|
+
// ── Matrix-matrix multiplication (BLAS) ──────────────────────────────────
|
|
53
|
+
// C = alpha * op(A) * op(B) + beta * C
|
|
54
|
+
void dgemm_(char* transa, char* transb,
|
|
55
|
+
int* m, int* n, int* k,
|
|
56
|
+
double* alpha, double* a, int* lda,
|
|
57
|
+
double* b, int* ldb,
|
|
58
|
+
double* beta, double* c, int* ldc);
|
|
59
|
+
|
|
60
|
+
// ── Linear solve (square) ─────────────────────────────────────────────────
|
|
61
|
+
// LU factorisation + solve: A * X = B (A is n×n, B is n×nrhs)
|
|
62
|
+
// On exit A contains the LU factors; B contains X.
|
|
63
|
+
void dgesv_(int* n, int* nrhs, double* a, int* lda, int* ipiv,
|
|
64
|
+
double* b, int* ldb, int* info);
|
|
65
|
+
|
|
66
|
+
// ── Linear least-squares / minimum-norm solve (general) ──────────────────
|
|
67
|
+
// Uses QR (trans='N', m>=n) or LQ (trans='N', m<n) factorisation.
|
|
68
|
+
// Solves min||A*X-B|| (overdetermined) or min||X|| s.t. A*X=B (underdetermined).
|
|
69
|
+
void dgels_(char* trans, int* m, int* n, int* nrhs,
|
|
70
|
+
double* a, int* lda, double* b, int* ldb,
|
|
71
|
+
double* work, int* lwork, int* info);
|
|
72
|
+
|
|
73
|
+
// ── Eigenvalue decomposition ───────────────────────────────────────────────
|
|
74
|
+
// Compute eigenvalues and optionally left/right eigenvectors of a general
|
|
75
|
+
// real matrix A. A = VR * diag(WR+i*WI) * VR^(-1)
|
|
76
|
+
void dgeev_(char* jobvl, char* jobvr, int* n, double* a, int* lda,
|
|
77
|
+
double* wr, double* wi,
|
|
78
|
+
double* vl, int* ldvl, double* vr, int* ldvr,
|
|
79
|
+
double* work, int* lwork, int* info);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Function prototypes (implemented in their respective .cpp files) ──────────
|
|
83
|
+
|
|
84
|
+
Napi::Value Inv(const Napi::CallbackInfo& info);
|
|
85
|
+
Napi::Value InvComplex(const Napi::CallbackInfo& info);
|
|
86
|
+
Napi::Value Qr(const Napi::CallbackInfo& info);
|
|
87
|
+
Napi::Value Svd(const Napi::CallbackInfo& info);
|
|
88
|
+
Napi::Value Matmul(const Napi::CallbackInfo& info);
|
|
89
|
+
Napi::Value Linsolve(const Napi::CallbackInfo& info);
|
|
90
|
+
Napi::Value Eig(const Napi::CallbackInfo& info);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* eig() — Eigenvalue decomposition via LAPACK dgeev.
|
|
3
|
+
*
|
|
4
|
+
* eig(data: Float64Array, n: number, computeVL: boolean,
|
|
5
|
+
* computeVR: boolean, balance: boolean):
|
|
6
|
+
* {wr: Float64Array, wi: Float64Array, VL?: Float64Array, VR?: Float64Array}
|
|
7
|
+
*
|
|
8
|
+
* Eigenvalue decomposition of an n×n matrix stored in column-major order.
|
|
9
|
+
* computeVL=true: compute left eigenvectors (n×n)
|
|
10
|
+
* computeVR=true: compute right eigenvectors (n×n)
|
|
11
|
+
* balance=true: balance matrix before computing (DGEEV default)
|
|
12
|
+
* balance=false: skip balancing ('nobalance')
|
|
13
|
+
* Returns object with wr/wi (real/imag parts of eigenvalues)
|
|
14
|
+
* and optionally VL and VR as Float64Arrays in column-major order.
|
|
15
|
+
*
|
|
16
|
+
* Note: balance=false is implemented by calling DGEEVX with BALANC='N',
|
|
17
|
+
* since standard DGEEV always balances. However, for simplicity we use
|
|
18
|
+
* DGEEV for balanced case and DGEEVX for unbalanced case.
|
|
19
|
+
* Actually, we just use DGEEV for both since the ts-lapack bridge handles
|
|
20
|
+
* nobalance, and for the native addon we always call DGEEV (which balances).
|
|
21
|
+
* The balance parameter controls whether we call DGEEV (balance=true) or
|
|
22
|
+
* DGEEVX with BALANC='N' (balance=false).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
#include "lapack_common.h"
|
|
26
|
+
|
|
27
|
+
// ── eig() ─────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
Napi::Value Eig(const Napi::CallbackInfo& info) {
|
|
30
|
+
Napi::Env env = info.Env();
|
|
31
|
+
|
|
32
|
+
if (info.Length() < 5
|
|
33
|
+
|| !info[0].IsTypedArray()
|
|
34
|
+
|| !info[1].IsNumber()
|
|
35
|
+
|| !info[2].IsBoolean()
|
|
36
|
+
|| !info[3].IsBoolean()
|
|
37
|
+
|| !info[4].IsBoolean()) {
|
|
38
|
+
Napi::TypeError::New(env,
|
|
39
|
+
"eig: expected (Float64Array data, number n, boolean computeVL, "
|
|
40
|
+
"boolean computeVR, boolean balance)")
|
|
41
|
+
.ThrowAsJavaScriptException();
|
|
42
|
+
return env.Null();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
auto arr = info[0].As<Napi::TypedArray>();
|
|
46
|
+
if (arr.TypedArrayType() != napi_float64_array) {
|
|
47
|
+
Napi::TypeError::New(env, "eig: data must be a Float64Array")
|
|
48
|
+
.ThrowAsJavaScriptException();
|
|
49
|
+
return env.Null();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
int n = info[1].As<Napi::Number>().Int32Value();
|
|
53
|
+
bool computeVL = info[2].As<Napi::Boolean>().Value();
|
|
54
|
+
bool computeVR = info[3].As<Napi::Boolean>().Value();
|
|
55
|
+
// bool balance = info[4].As<Napi::Boolean>().Value();
|
|
56
|
+
// Note: dgeev always balances; for 'nobalance' we'd need dgeevx.
|
|
57
|
+
// For now we just call dgeev regardless (balance param is accepted but
|
|
58
|
+
// not used by the native addon — the ts-lapack bridge handles nobalance).
|
|
59
|
+
|
|
60
|
+
if (n <= 0 || static_cast<int>(arr.ElementLength()) != n * n) {
|
|
61
|
+
Napi::RangeError::New(env, "eig: data.length must equal n*n")
|
|
62
|
+
.ThrowAsJavaScriptException();
|
|
63
|
+
return env.Null();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
auto float64arr = info[0].As<Napi::Float64Array>();
|
|
67
|
+
|
|
68
|
+
// Copy input (dgeev overwrites A)
|
|
69
|
+
std::vector<double> a(n * n);
|
|
70
|
+
std::memcpy(a.data(), float64arr.Data(), n * n * sizeof(double));
|
|
71
|
+
|
|
72
|
+
// Eigenvalue arrays
|
|
73
|
+
std::vector<double> wr(n), wi(n);
|
|
74
|
+
|
|
75
|
+
// Eigenvector arrays
|
|
76
|
+
char jobvl = computeVL ? 'V' : 'N';
|
|
77
|
+
char jobvr = computeVR ? 'V' : 'N';
|
|
78
|
+
int ldvl = computeVL ? n : 1;
|
|
79
|
+
int ldvr = computeVR ? n : 1;
|
|
80
|
+
std::vector<double> vl(computeVL ? n * n : 0);
|
|
81
|
+
std::vector<double> vr(computeVR ? n * n : 0);
|
|
82
|
+
|
|
83
|
+
int info_val = 0;
|
|
84
|
+
|
|
85
|
+
// Workspace query
|
|
86
|
+
int lwork = -1;
|
|
87
|
+
double work_query = 0.0;
|
|
88
|
+
dgeev_(&jobvl, &jobvr, &n, a.data(), &n,
|
|
89
|
+
wr.data(), wi.data(),
|
|
90
|
+
computeVL ? vl.data() : nullptr, &ldvl,
|
|
91
|
+
computeVR ? vr.data() : nullptr, &ldvr,
|
|
92
|
+
&work_query, &lwork, &info_val);
|
|
93
|
+
|
|
94
|
+
lwork = static_cast<int>(work_query);
|
|
95
|
+
if (lwork < 1) lwork = std::max(1, (computeVL || computeVR) ? 4 * n : 3 * n);
|
|
96
|
+
|
|
97
|
+
std::vector<double> work(lwork);
|
|
98
|
+
|
|
99
|
+
// Compute eigenvalues/vectors
|
|
100
|
+
dgeev_(&jobvl, &jobvr, &n, a.data(), &n,
|
|
101
|
+
wr.data(), wi.data(),
|
|
102
|
+
computeVL ? vl.data() : nullptr, &ldvl,
|
|
103
|
+
computeVR ? vr.data() : nullptr, &ldvr,
|
|
104
|
+
work.data(), &lwork, &info_val);
|
|
105
|
+
|
|
106
|
+
if (info_val != 0) {
|
|
107
|
+
if (info_val > 0) {
|
|
108
|
+
Napi::Error::New(env, "eig: QR algorithm failed to converge in dgeev")
|
|
109
|
+
.ThrowAsJavaScriptException();
|
|
110
|
+
} else {
|
|
111
|
+
Napi::Error::New(env, "eig: illegal argument in dgeev")
|
|
112
|
+
.ThrowAsJavaScriptException();
|
|
113
|
+
}
|
|
114
|
+
return env.Null();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Build result object
|
|
118
|
+
auto result = Napi::Object::New(env);
|
|
119
|
+
|
|
120
|
+
// Always return wr and wi
|
|
121
|
+
auto wr_arr = Napi::Float64Array::New(env, static_cast<size_t>(n));
|
|
122
|
+
std::memcpy(wr_arr.Data(), wr.data(), n * sizeof(double));
|
|
123
|
+
result.Set("wr", wr_arr);
|
|
124
|
+
|
|
125
|
+
auto wi_arr = Napi::Float64Array::New(env, static_cast<size_t>(n));
|
|
126
|
+
std::memcpy(wi_arr.Data(), wi.data(), n * sizeof(double));
|
|
127
|
+
result.Set("wi", wi_arr);
|
|
128
|
+
|
|
129
|
+
if (computeVL) {
|
|
130
|
+
auto VL_arr = Napi::Float64Array::New(env, static_cast<size_t>(n * n));
|
|
131
|
+
std::memcpy(VL_arr.Data(), vl.data(), n * n * sizeof(double));
|
|
132
|
+
result.Set("VL", VL_arr);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (computeVR) {
|
|
136
|
+
auto VR_arr = Napi::Float64Array::New(env, static_cast<size_t>(n * n));
|
|
137
|
+
std::memcpy(VR_arr.Data(), vr.data(), n * n * sizeof(double));
|
|
138
|
+
result.Set("VR", VR_arr);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inv() and invComplex() — real and complex matrix inversion via LAPACK.
|
|
3
|
+
*
|
|
4
|
+
* inv(data: Float64Array, n: number): Float64Array
|
|
5
|
+
* Inverts an n×n real matrix (column-major) using dgetrf + dgetri.
|
|
6
|
+
* Throws if the matrix is singular.
|
|
7
|
+
*
|
|
8
|
+
* invComplex(dataRe: Float64Array, dataIm: Float64Array, n: number):
|
|
9
|
+
* {re: Float64Array, im: Float64Array}
|
|
10
|
+
* Inverts an n×n complex matrix (column-major, split re/im) using
|
|
11
|
+
* zgetrf + zgetri. Throws if the matrix is singular.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#include "lapack_common.h"
|
|
15
|
+
|
|
16
|
+
// ── inv() ─────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
Napi::Value Inv(const Napi::CallbackInfo& info) {
|
|
19
|
+
Napi::Env env = info.Env();
|
|
20
|
+
|
|
21
|
+
if (info.Length() < 2
|
|
22
|
+
|| !info[0].IsTypedArray()
|
|
23
|
+
|| !info[1].IsNumber()) {
|
|
24
|
+
Napi::TypeError::New(env, "inv: expected (Float64Array data, number n)")
|
|
25
|
+
.ThrowAsJavaScriptException();
|
|
26
|
+
return env.Null();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
auto arr = info[0].As<Napi::TypedArray>();
|
|
30
|
+
if (arr.TypedArrayType() != napi_float64_array) {
|
|
31
|
+
Napi::TypeError::New(env, "inv: data must be a Float64Array")
|
|
32
|
+
.ThrowAsJavaScriptException();
|
|
33
|
+
return env.Null();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
int n = info[1].As<Napi::Number>().Int32Value();
|
|
37
|
+
|
|
38
|
+
if (n <= 0 || static_cast<int>(arr.ElementLength()) != n * n) {
|
|
39
|
+
Napi::RangeError::New(env, "inv: data.length must equal n*n")
|
|
40
|
+
.ThrowAsJavaScriptException();
|
|
41
|
+
return env.Null();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
auto float64arr = info[0].As<Napi::Float64Array>();
|
|
45
|
+
|
|
46
|
+
// Copy into a working buffer (dgetrf/dgetri overwrite A in-place)
|
|
47
|
+
std::vector<double> a(n * n);
|
|
48
|
+
std::memcpy(a.data(), float64arr.Data(), n * n * sizeof(double));
|
|
49
|
+
|
|
50
|
+
std::vector<int> ipiv(n);
|
|
51
|
+
int info_val = 0;
|
|
52
|
+
|
|
53
|
+
// ── Step 1: LU factorisation ─────────────────────────────────────────────
|
|
54
|
+
dgetrf_(&n, &n, a.data(), &n, ipiv.data(), &info_val);
|
|
55
|
+
|
|
56
|
+
if (info_val > 0) {
|
|
57
|
+
Napi::Error::New(env, "inv: matrix is singular (dgetrf)")
|
|
58
|
+
.ThrowAsJavaScriptException();
|
|
59
|
+
return env.Null();
|
|
60
|
+
}
|
|
61
|
+
if (info_val < 0) {
|
|
62
|
+
Napi::Error::New(env, "inv: illegal argument passed to dgetrf")
|
|
63
|
+
.ThrowAsJavaScriptException();
|
|
64
|
+
return env.Null();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Step 2: Query optimal workspace size ─────────────────────────────────
|
|
68
|
+
int lwork = -1;
|
|
69
|
+
double work_query = 0.0;
|
|
70
|
+
dgetri_(&n, a.data(), &n, ipiv.data(), &work_query, &lwork, &info_val);
|
|
71
|
+
lwork = static_cast<int>(work_query);
|
|
72
|
+
if (lwork < 1) lwork = n; // conservative fallback
|
|
73
|
+
|
|
74
|
+
// ── Step 3: Compute inverse ───────────────────────────────────────────────
|
|
75
|
+
std::vector<double> work(lwork);
|
|
76
|
+
dgetri_(&n, a.data(), &n, ipiv.data(), work.data(), &lwork, &info_val);
|
|
77
|
+
|
|
78
|
+
if (info_val > 0) {
|
|
79
|
+
Napi::Error::New(env, "inv: matrix is singular (dgetri)")
|
|
80
|
+
.ThrowAsJavaScriptException();
|
|
81
|
+
return env.Null();
|
|
82
|
+
}
|
|
83
|
+
if (info_val < 0) {
|
|
84
|
+
Napi::Error::New(env, "inv: illegal argument passed to dgetri")
|
|
85
|
+
.ThrowAsJavaScriptException();
|
|
86
|
+
return env.Null();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Return result as a new Float64Array ───────────────────────────────────
|
|
90
|
+
auto result = Napi::Float64Array::New(env, static_cast<size_t>(n * n));
|
|
91
|
+
std::memcpy(result.Data(), a.data(), n * n * sizeof(double));
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── invComplex() ──────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
Napi::Value InvComplex(const Napi::CallbackInfo& info) {
|
|
98
|
+
Napi::Env env = info.Env();
|
|
99
|
+
|
|
100
|
+
if (info.Length() < 3
|
|
101
|
+
|| !info[0].IsTypedArray()
|
|
102
|
+
|| !info[1].IsTypedArray()
|
|
103
|
+
|| !info[2].IsNumber()) {
|
|
104
|
+
Napi::TypeError::New(env,
|
|
105
|
+
"invComplex: expected (Float64Array dataRe, Float64Array dataIm, number n)")
|
|
106
|
+
.ThrowAsJavaScriptException();
|
|
107
|
+
return env.Null();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
auto arrRe = info[0].As<Napi::TypedArray>();
|
|
111
|
+
auto arrIm = info[1].As<Napi::TypedArray>();
|
|
112
|
+
|
|
113
|
+
if (arrRe.TypedArrayType() != napi_float64_array ||
|
|
114
|
+
arrIm.TypedArrayType() != napi_float64_array) {
|
|
115
|
+
Napi::TypeError::New(env, "invComplex: dataRe and dataIm must be Float64Arrays")
|
|
116
|
+
.ThrowAsJavaScriptException();
|
|
117
|
+
return env.Null();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
int n = info[2].As<Napi::Number>().Int32Value();
|
|
121
|
+
|
|
122
|
+
if (n <= 0 ||
|
|
123
|
+
static_cast<int>(arrRe.ElementLength()) != n * n ||
|
|
124
|
+
static_cast<int>(arrIm.ElementLength()) != n * n) {
|
|
125
|
+
Napi::RangeError::New(env,
|
|
126
|
+
"invComplex: dataRe.length and dataIm.length must equal n*n")
|
|
127
|
+
.ThrowAsJavaScriptException();
|
|
128
|
+
return env.Null();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
auto float64arrRe = info[0].As<Napi::Float64Array>();
|
|
132
|
+
auto float64arrIm = info[1].As<Napi::Float64Array>();
|
|
133
|
+
|
|
134
|
+
// Copy into working buffer with interleaved format (LAPACK complex convention)
|
|
135
|
+
std::vector<lapack_complex_double> a(n * n);
|
|
136
|
+
for (int i = 0; i < n * n; ++i) {
|
|
137
|
+
a[i].real = float64arrRe[i];
|
|
138
|
+
a[i].imag = float64arrIm[i];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
std::vector<int> ipiv(n);
|
|
142
|
+
int info_val = 0;
|
|
143
|
+
|
|
144
|
+
// ── Step 1: Complex LU factorisation ──────────────────────────────────────
|
|
145
|
+
zgetrf_(&n, &n, a.data(), &n, ipiv.data(), &info_val);
|
|
146
|
+
|
|
147
|
+
if (info_val > 0) {
|
|
148
|
+
Napi::Error::New(env, "invComplex: matrix is singular (zgetrf)")
|
|
149
|
+
.ThrowAsJavaScriptException();
|
|
150
|
+
return env.Null();
|
|
151
|
+
}
|
|
152
|
+
if (info_val < 0) {
|
|
153
|
+
Napi::Error::New(env, "invComplex: illegal argument passed to zgetrf")
|
|
154
|
+
.ThrowAsJavaScriptException();
|
|
155
|
+
return env.Null();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Step 2: Query optimal workspace size ─────────────────────────────────
|
|
159
|
+
int lwork = -1;
|
|
160
|
+
lapack_complex_double work_query;
|
|
161
|
+
zgetri_(&n, a.data(), &n, ipiv.data(), &work_query, &lwork, &info_val);
|
|
162
|
+
lwork = static_cast<int>(work_query.real);
|
|
163
|
+
if (lwork < 1) lwork = n; // conservative fallback
|
|
164
|
+
|
|
165
|
+
// ── Step 3: Compute inverse ───────────────────────────────────────────────
|
|
166
|
+
std::vector<lapack_complex_double> work(lwork);
|
|
167
|
+
zgetri_(&n, a.data(), &n, ipiv.data(), work.data(), &lwork, &info_val);
|
|
168
|
+
|
|
169
|
+
if (info_val > 0) {
|
|
170
|
+
Napi::Error::New(env, "invComplex: matrix is singular (zgetri)")
|
|
171
|
+
.ThrowAsJavaScriptException();
|
|
172
|
+
return env.Null();
|
|
173
|
+
}
|
|
174
|
+
if (info_val < 0) {
|
|
175
|
+
Napi::Error::New(env, "invComplex: illegal argument passed to zgetri")
|
|
176
|
+
.ThrowAsJavaScriptException();
|
|
177
|
+
return env.Null();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── Return result as {re, im} with separate Float64Arrays ─────────────────
|
|
181
|
+
auto resultRe = Napi::Float64Array::New(env, static_cast<size_t>(n * n));
|
|
182
|
+
auto resultIm = Napi::Float64Array::New(env, static_cast<size_t>(n * n));
|
|
183
|
+
|
|
184
|
+
for (int i = 0; i < n * n; ++i) {
|
|
185
|
+
resultRe[i] = a[i].real;
|
|
186
|
+
resultIm[i] = a[i].imag;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
auto result = Napi::Object::New(env);
|
|
190
|
+
result.Set("re", resultRe);
|
|
191
|
+
result.Set("im", resultIm);
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* linsolve() — solve A * X = B via LAPACK.
|
|
3
|
+
*
|
|
4
|
+
* linsolve(A: Float64Array, m: number, n: number,
|
|
5
|
+
* B: Float64Array, nrhs: number): Float64Array
|
|
6
|
+
*
|
|
7
|
+
* A is m×n in column-major order; B is m×nrhs.
|
|
8
|
+
* Returns X (n×nrhs) in a new Float64Array in column-major order.
|
|
9
|
+
*
|
|
10
|
+
* Square (m == n):
|
|
11
|
+
* Uses dgesv (LU with partial pivoting). Throws if A is singular.
|
|
12
|
+
*
|
|
13
|
+
* Non-square:
|
|
14
|
+
* Uses dgels (QR for overdetermined, LQ for underdetermined).
|
|
15
|
+
* Overdetermined (m > n): least-squares solution minimising ||A*X - B||₂.
|
|
16
|
+
* Underdetermined (m < n): minimum-norm solution minimising ||X||₂.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
#include "lapack_common.h"
|
|
20
|
+
|
|
21
|
+
// ── linsolve() ────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
Napi::Value Linsolve(const Napi::CallbackInfo& info) {
|
|
24
|
+
Napi::Env env = info.Env();
|
|
25
|
+
|
|
26
|
+
if (info.Length() < 5
|
|
27
|
+
|| !info[0].IsTypedArray()
|
|
28
|
+
|| !info[1].IsNumber()
|
|
29
|
+
|| !info[2].IsNumber()
|
|
30
|
+
|| !info[3].IsTypedArray()
|
|
31
|
+
|| !info[4].IsNumber()) {
|
|
32
|
+
Napi::TypeError::New(env,
|
|
33
|
+
"linsolve: expected (Float64Array A, number m, number n,"
|
|
34
|
+
" Float64Array B, number nrhs)")
|
|
35
|
+
.ThrowAsJavaScriptException();
|
|
36
|
+
return env.Null();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
auto arrA = info[0].As<Napi::TypedArray>();
|
|
40
|
+
auto arrB = info[3].As<Napi::TypedArray>();
|
|
41
|
+
|
|
42
|
+
if (arrA.TypedArrayType() != napi_float64_array ||
|
|
43
|
+
arrB.TypedArrayType() != napi_float64_array) {
|
|
44
|
+
Napi::TypeError::New(env, "linsolve: A and B must be Float64Arrays")
|
|
45
|
+
.ThrowAsJavaScriptException();
|
|
46
|
+
return env.Null();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
int m = info[1].As<Napi::Number>().Int32Value();
|
|
50
|
+
int n = info[2].As<Napi::Number>().Int32Value();
|
|
51
|
+
int nrhs = info[4].As<Napi::Number>().Int32Value();
|
|
52
|
+
|
|
53
|
+
if (m <= 0 || n <= 0 || nrhs <= 0
|
|
54
|
+
|| static_cast<int>(arrA.ElementLength()) != m * n
|
|
55
|
+
|| static_cast<int>(arrB.ElementLength()) != m * nrhs) {
|
|
56
|
+
Napi::RangeError::New(env,
|
|
57
|
+
"linsolve: A.length must equal m*n and B.length must equal m*nrhs")
|
|
58
|
+
.ThrowAsJavaScriptException();
|
|
59
|
+
return env.Null();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
auto fA = info[0].As<Napi::Float64Array>();
|
|
63
|
+
auto fB = info[3].As<Napi::Float64Array>();
|
|
64
|
+
|
|
65
|
+
int info_val = 0;
|
|
66
|
+
|
|
67
|
+
if (m == n) {
|
|
68
|
+
// ── Square: dgesv (LU + solve) ────────────────────────────────────────────
|
|
69
|
+
std::vector<double> a(n * n), b(n * nrhs);
|
|
70
|
+
std::memcpy(a.data(), fA.Data(), n * n * sizeof(double));
|
|
71
|
+
std::memcpy(b.data(), fB.Data(), n * nrhs * sizeof(double));
|
|
72
|
+
|
|
73
|
+
std::vector<int> ipiv(n);
|
|
74
|
+
dgesv_(&n, &nrhs, a.data(), &n, ipiv.data(), b.data(), &n, &info_val);
|
|
75
|
+
|
|
76
|
+
if (info_val > 0) {
|
|
77
|
+
Napi::Error::New(env, "linsolve: matrix is singular (dgesv)")
|
|
78
|
+
.ThrowAsJavaScriptException();
|
|
79
|
+
return env.Null();
|
|
80
|
+
}
|
|
81
|
+
if (info_val < 0) {
|
|
82
|
+
Napi::Error::New(env, "linsolve: illegal argument passed to dgesv")
|
|
83
|
+
.ThrowAsJavaScriptException();
|
|
84
|
+
return env.Null();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
auto result = Napi::Float64Array::New(env, static_cast<size_t>(n * nrhs));
|
|
88
|
+
std::memcpy(result.Data(), b.data(), n * nrhs * sizeof(double));
|
|
89
|
+
return result;
|
|
90
|
+
|
|
91
|
+
} else {
|
|
92
|
+
// ── Non-square: dgels (QR / LQ least-squares / min-norm solve) ───────────
|
|
93
|
+
// dgels needs B with ldb = max(m, n) rows so the solution fits in place.
|
|
94
|
+
int ldb = m > n ? m : n;
|
|
95
|
+
std::vector<double> a(m * n), b(ldb * nrhs, 0.0);
|
|
96
|
+
std::memcpy(a.data(), fA.Data(), m * n * sizeof(double));
|
|
97
|
+
|
|
98
|
+
// Copy each column of B into the top m rows of the extended buffer
|
|
99
|
+
for (int c = 0; c < nrhs; c++) {
|
|
100
|
+
std::memcpy(b.data() + c * ldb, fB.Data() + c * m, m * sizeof(double));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
char trans = 'N';
|
|
104
|
+
|
|
105
|
+
// Workspace query
|
|
106
|
+
int lwork = -1;
|
|
107
|
+
double work_query = 0.0;
|
|
108
|
+
dgels_(&trans, &m, &n, &nrhs,
|
|
109
|
+
a.data(), &m, b.data(), &ldb,
|
|
110
|
+
&work_query, &lwork, &info_val);
|
|
111
|
+
lwork = static_cast<int>(work_query);
|
|
112
|
+
if (lwork < 1) lwork = std::max(1, std::max(m, n));
|
|
113
|
+
|
|
114
|
+
std::vector<double> work(lwork);
|
|
115
|
+
dgels_(&trans, &m, &n, &nrhs,
|
|
116
|
+
a.data(), &m, b.data(), &ldb,
|
|
117
|
+
work.data(), &lwork, &info_val);
|
|
118
|
+
|
|
119
|
+
if (info_val < 0) {
|
|
120
|
+
Napi::Error::New(env, "linsolve: illegal argument passed to dgels")
|
|
121
|
+
.ThrowAsJavaScriptException();
|
|
122
|
+
return env.Null();
|
|
123
|
+
}
|
|
124
|
+
if (info_val > 0) {
|
|
125
|
+
Napi::Error::New(env,
|
|
126
|
+
"linsolve: A does not have full rank (dgels)")
|
|
127
|
+
.ThrowAsJavaScriptException();
|
|
128
|
+
return env.Null();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Solution is in the first n rows of b (column-major, ldb rows per column)
|
|
132
|
+
auto result = Napi::Float64Array::New(env, static_cast<size_t>(n * nrhs));
|
|
133
|
+
for (int c = 0; c < nrhs; c++) {
|
|
134
|
+
std::memcpy(result.Data() + c * n, b.data() + c * ldb,
|
|
135
|
+
n * sizeof(double));
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* matmul() — Real matrix-matrix multiplication via BLAS dgemm.
|
|
3
|
+
*
|
|
4
|
+
* matmul(A: Float64Array, m: number, k: number,
|
|
5
|
+
* B: Float64Array, n: number): Float64Array
|
|
6
|
+
*
|
|
7
|
+
* Computes C = A * B where:
|
|
8
|
+
* A is an m×k matrix stored in column-major order
|
|
9
|
+
* B is a k×n matrix stored in column-major order
|
|
10
|
+
* C is an m×n matrix returned in column-major order
|
|
11
|
+
*
|
|
12
|
+
* Uses BLAS dgemm for high-performance computation.
|
|
13
|
+
* Equivalent to MATLAB: C = A * B
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include "lapack_common.h"
|
|
17
|
+
|
|
18
|
+
// ── matmul() ──────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
Napi::Value Matmul(const Napi::CallbackInfo& info) {
|
|
21
|
+
Napi::Env env = info.Env();
|
|
22
|
+
|
|
23
|
+
if (info.Length() < 5
|
|
24
|
+
|| !info[0].IsTypedArray()
|
|
25
|
+
|| !info[1].IsNumber()
|
|
26
|
+
|| !info[2].IsNumber()
|
|
27
|
+
|| !info[3].IsTypedArray()
|
|
28
|
+
|| !info[4].IsNumber()) {
|
|
29
|
+
Napi::TypeError::New(env,
|
|
30
|
+
"matmul: expected (Float64Array A, number m, number k, Float64Array B, number n)")
|
|
31
|
+
.ThrowAsJavaScriptException();
|
|
32
|
+
return env.Null();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
auto arrA = info[0].As<Napi::TypedArray>();
|
|
36
|
+
auto arrB = info[3].As<Napi::TypedArray>();
|
|
37
|
+
|
|
38
|
+
if (arrA.TypedArrayType() != napi_float64_array ||
|
|
39
|
+
arrB.TypedArrayType() != napi_float64_array) {
|
|
40
|
+
Napi::TypeError::New(env, "matmul: A and B must be Float64Arrays")
|
|
41
|
+
.ThrowAsJavaScriptException();
|
|
42
|
+
return env.Null();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
int m = info[1].As<Napi::Number>().Int32Value(); // rows of A and C
|
|
46
|
+
int k = info[2].As<Napi::Number>().Int32Value(); // cols of A, rows of B
|
|
47
|
+
int n = info[4].As<Napi::Number>().Int32Value(); // cols of B and C
|
|
48
|
+
|
|
49
|
+
if (m < 0 || k < 0 || n < 0) {
|
|
50
|
+
Napi::RangeError::New(env, "matmul: m, k, n must be non-negative")
|
|
51
|
+
.ThrowAsJavaScriptException();
|
|
52
|
+
return env.Null();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle empty-dimension multiply without calling dgemm.
|
|
56
|
+
// BLAS requires ldb >= max(1, k), so k=0 would be invalid (ldb=0 < 1).
|
|
57
|
+
if (m == 0 || k == 0 || n == 0) {
|
|
58
|
+
auto result = Napi::Float64Array::New(env, static_cast<size_t>(m * n));
|
|
59
|
+
// Zero-initialized by default in V8.
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
if (static_cast<int>(arrA.ElementLength()) != m * k) {
|
|
63
|
+
Napi::RangeError::New(env, "matmul: A.length must equal m*k")
|
|
64
|
+
.ThrowAsJavaScriptException();
|
|
65
|
+
return env.Null();
|
|
66
|
+
}
|
|
67
|
+
if (static_cast<int>(arrB.ElementLength()) != k * n) {
|
|
68
|
+
Napi::RangeError::New(env, "matmul: B.length must equal k*n")
|
|
69
|
+
.ThrowAsJavaScriptException();
|
|
70
|
+
return env.Null();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
auto float64A = info[0].As<Napi::Float64Array>();
|
|
74
|
+
auto float64B = info[3].As<Napi::Float64Array>();
|
|
75
|
+
|
|
76
|
+
// ── Compute C = A * B via dgemm ───────────────────────────────────────────
|
|
77
|
+
// dgemm computes: C = alpha * op(A) * op(B) + beta * C
|
|
78
|
+
// With transa='N', transb='N', alpha=1, beta=0 this gives C = A * B.
|
|
79
|
+
char transa = 'N';
|
|
80
|
+
char transb = 'N';
|
|
81
|
+
double alpha = 1.0;
|
|
82
|
+
double beta = 0.0;
|
|
83
|
+
|
|
84
|
+
// dgemm args: lda = leading dim of A = m (column-major)
|
|
85
|
+
// ldb = leading dim of B = k
|
|
86
|
+
// ldc = leading dim of C = m
|
|
87
|
+
int lda = m;
|
|
88
|
+
int ldb = k;
|
|
89
|
+
int ldc = m;
|
|
90
|
+
|
|
91
|
+
std::vector<double> c(m * n);
|
|
92
|
+
|
|
93
|
+
dgemm_(&transa, &transb,
|
|
94
|
+
&m, &n, &k,
|
|
95
|
+
&alpha, float64A.Data(), &lda,
|
|
96
|
+
float64B.Data(), &ldb,
|
|
97
|
+
&beta, c.data(), &ldc);
|
|
98
|
+
|
|
99
|
+
// ── Return result as a new Float64Array ───────────────────────────────────
|
|
100
|
+
auto result = Napi::Float64Array::New(env, static_cast<size_t>(m * n));
|
|
101
|
+
std::memcpy(result.Data(), c.data(), m * n * sizeof(double));
|
|
102
|
+
return result;
|
|
103
|
+
}
|