numbl 0.1.4 → 0.1.6
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/binding.gyp +2 -1
- package/dist-cli/cli.js +2215 -250
- package/dist-lib/graphics/types.d.ts +22 -0
- package/dist-lib/lib.js +2154 -189
- package/dist-lib/numbl-core/executeCode.d.ts +9 -0
- package/dist-lib/numbl-core/functionResolve.d.ts +4 -0
- package/dist-lib/numbl-core/helpers/gmres.d.ts +51 -0
- package/dist-lib/numbl-core/interpreter/interpreter.d.ts +3 -0
- package/dist-lib/numbl-core/interpreter/interpreterFunctions.d.ts +3 -0
- package/dist-lib/numbl-core/interpreter/interpreterSpecialBuiltins.d.ts +8 -3
- package/dist-lib/numbl-core/interpreter/jit/jitHelpers.d.ts +9 -0
- package/dist-lib/numbl-core/jsUserFunctions.d.ts +25 -10
- package/dist-lib/numbl-core/lowering/loweringContext.d.ts +12 -1
- package/dist-lib/numbl-core/native/lapack-bridge.d.ts +33 -1
- package/dist-lib/numbl-core/runtime/error.d.ts +2 -0
- package/dist-lib/numbl-core/runtime/plotUtils.d.ts +11 -2
- package/dist-lib/numbl-core/runtime/runtime.d.ts +10 -0
- package/dist-lib/numbl-core/runtime/runtimePlot.d.ts +6 -0
- package/dist-lib/numbl-core/version.d.ts +1 -1
- package/dist-plot-viewer/assets/index-vtrJ8bml.js +4426 -0
- package/dist-plot-viewer/index.html +1 -1
- package/native/lapack_gmres.cpp +612 -0
- package/native/numbl_addon.cpp +5 -1
- package/native/numbl_addon_common.h +26 -0
- package/package.json +1 -1
- package/dist-plot-viewer/assets/index-BjKyNJgj.js +0 -4426
|
@@ -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-vtrJ8bml.js"></script>
|
|
8
8
|
</head>
|
|
9
9
|
<body style="margin: 0; overflow: hidden">
|
|
10
10
|
<div id="root"></div>
|
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gmres() — Restarted GMRES solver using BLAS for matvec and LAPACK for
|
|
3
|
+
* preconditioner solves.
|
|
4
|
+
*
|
|
5
|
+
* gmres(A, n, b, restart, tol, maxit, M1, M2, x0):
|
|
6
|
+
* { x: Float64Array, flag: number, relres: number,
|
|
7
|
+
* iter: Int32Array, resvec: Float64Array }
|
|
8
|
+
*
|
|
9
|
+
* A : Float64Array (n×n column-major)
|
|
10
|
+
* n : number
|
|
11
|
+
* b : Float64Array (n)
|
|
12
|
+
* restart : number (inner iterations per cycle)
|
|
13
|
+
* tol : number
|
|
14
|
+
* maxit : number (max outer iterations)
|
|
15
|
+
* M1 : Float64Array (n×n) | null — preconditioner factor 1
|
|
16
|
+
* M2 : Float64Array (n×n) | null — preconditioner factor 2
|
|
17
|
+
* x0 : Float64Array (n) | null — initial guess
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
#include "numbl_addon_common.h"
|
|
21
|
+
#include <cmath>
|
|
22
|
+
|
|
23
|
+
// ── Local helpers ────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
static inline double vec_dot(int n, const double* a, const double* b) {
|
|
26
|
+
double s = 0;
|
|
27
|
+
for (int i = 0; i < n; i++) s += a[i] * b[i];
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static inline double vec_nrm2(int n, const double* a) {
|
|
32
|
+
double s = 0;
|
|
33
|
+
for (int i = 0; i < n; i++) s += a[i] * a[i];
|
|
34
|
+
return std::sqrt(s);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// y = A*x via BLAS dgemv
|
|
38
|
+
static inline void mat_vec(int n, const double* A, const double* x, double* y) {
|
|
39
|
+
char trans = 'N';
|
|
40
|
+
double alpha = 1.0, beta = 0.0;
|
|
41
|
+
int inc = 1;
|
|
42
|
+
dgemv_(&trans, &n, &n, &alpha, const_cast<double*>(A), &n,
|
|
43
|
+
const_cast<double*>(x), &inc, &beta, y, &inc);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Solve using pre-factored LU (dgetrs, in-place on rhs)
|
|
47
|
+
static inline void lu_solve(int n, const double* LU, const int* ipiv, double* rhs) {
|
|
48
|
+
char trans = 'N';
|
|
49
|
+
int nrhs = 1, info_val = 0;
|
|
50
|
+
dgetrs_(&trans, &n, &nrhs, const_cast<double*>(LU), &n,
|
|
51
|
+
const_cast<int*>(ipiv), rhs, &n, &info_val);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static inline void givens(double a, double b, double& c, double& s) {
|
|
55
|
+
if (b == 0.0) { c = 1.0; s = 0.0; return; }
|
|
56
|
+
if (std::abs(b) > std::abs(a)) {
|
|
57
|
+
double t = a / b;
|
|
58
|
+
s = 1.0 / std::sqrt(1.0 + t * t);
|
|
59
|
+
c = s * t;
|
|
60
|
+
} else {
|
|
61
|
+
double t = b / a;
|
|
62
|
+
c = 1.0 / std::sqrt(1.0 + t * t);
|
|
63
|
+
s = c * t;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Gmres ────────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
Napi::Value Gmres(const Napi::CallbackInfo& info) {
|
|
70
|
+
Napi::Env env = info.Env();
|
|
71
|
+
|
|
72
|
+
if (info.Length() < 9) {
|
|
73
|
+
Napi::TypeError::New(env, "gmres: expected 9 arguments")
|
|
74
|
+
.ThrowAsJavaScriptException();
|
|
75
|
+
return env.Null();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Parse required args ────────────────────────────────────────────────────
|
|
79
|
+
if (!info[0].IsTypedArray() || !info[1].IsNumber() || !info[2].IsTypedArray()
|
|
80
|
+
|| !info[3].IsNumber() || !info[4].IsNumber() || !info[5].IsNumber()) {
|
|
81
|
+
Napi::TypeError::New(env,
|
|
82
|
+
"gmres: expected (Float64Array A, number n, Float64Array b, "
|
|
83
|
+
"number restart, number tol, number maxit, M1?, M2?, x0?)")
|
|
84
|
+
.ThrowAsJavaScriptException();
|
|
85
|
+
return env.Null();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
auto fA = info[0].As<Napi::Float64Array>();
|
|
89
|
+
int n = info[1].As<Napi::Number>().Int32Value();
|
|
90
|
+
auto fB = info[2].As<Napi::Float64Array>();
|
|
91
|
+
|
|
92
|
+
if (n <= 0 || static_cast<int>(fA.ElementLength()) != n * n
|
|
93
|
+
|| static_cast<int>(fB.ElementLength()) != n) {
|
|
94
|
+
Napi::RangeError::New(env, "gmres: dimension mismatch")
|
|
95
|
+
.ThrowAsJavaScriptException();
|
|
96
|
+
return env.Null();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
int restart = info[3].As<Napi::Number>().Int32Value();
|
|
100
|
+
double tol = info[4].As<Napi::Number>().DoubleValue();
|
|
101
|
+
int maxit = info[5].As<Napi::Number>().Int32Value();
|
|
102
|
+
|
|
103
|
+
if (restart <= 0 || restart > n) restart = n;
|
|
104
|
+
|
|
105
|
+
const double* A = fA.Data();
|
|
106
|
+
const double* b = fB.Data();
|
|
107
|
+
|
|
108
|
+
// ── Pre-factor preconditioners ─────────────────────────────────────────────
|
|
109
|
+
bool hasM1 = !info[6].IsNull() && !info[6].IsUndefined();
|
|
110
|
+
bool hasM2 = !info[7].IsNull() && !info[7].IsUndefined();
|
|
111
|
+
|
|
112
|
+
std::vector<double> m1lu, m2lu;
|
|
113
|
+
std::vector<int> m1ipiv, m2ipiv;
|
|
114
|
+
|
|
115
|
+
if (hasM1) {
|
|
116
|
+
auto fM1 = info[6].As<Napi::Float64Array>();
|
|
117
|
+
if (static_cast<int>(fM1.ElementLength()) != n * n) {
|
|
118
|
+
Napi::RangeError::New(env, "gmres: M1 size mismatch")
|
|
119
|
+
.ThrowAsJavaScriptException();
|
|
120
|
+
return env.Null();
|
|
121
|
+
}
|
|
122
|
+
m1lu.assign(fM1.Data(), fM1.Data() + n * n);
|
|
123
|
+
m1ipiv.resize(n);
|
|
124
|
+
int info_val = 0;
|
|
125
|
+
dgetrf_(&n, &n, m1lu.data(), &n, m1ipiv.data(), &info_val);
|
|
126
|
+
if (info_val != 0) {
|
|
127
|
+
Napi::Error::New(env, "gmres: preconditioner M1 is singular")
|
|
128
|
+
.ThrowAsJavaScriptException();
|
|
129
|
+
return env.Null();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (hasM2) {
|
|
133
|
+
auto fM2 = info[7].As<Napi::Float64Array>();
|
|
134
|
+
if (static_cast<int>(fM2.ElementLength()) != n * n) {
|
|
135
|
+
Napi::RangeError::New(env, "gmres: M2 size mismatch")
|
|
136
|
+
.ThrowAsJavaScriptException();
|
|
137
|
+
return env.Null();
|
|
138
|
+
}
|
|
139
|
+
m2lu.assign(fM2.Data(), fM2.Data() + n * n);
|
|
140
|
+
m2ipiv.resize(n);
|
|
141
|
+
int info_val = 0;
|
|
142
|
+
dgetrf_(&n, &n, m2lu.data(), &n, m2ipiv.data(), &info_val);
|
|
143
|
+
if (info_val != 0) {
|
|
144
|
+
Napi::Error::New(env, "gmres: preconditioner M2 is singular")
|
|
145
|
+
.ThrowAsJavaScriptException();
|
|
146
|
+
return env.Null();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── Parse x0 ──────────────────────────────────────────────────────────────
|
|
151
|
+
std::vector<double> x(n, 0.0);
|
|
152
|
+
if (!info[8].IsNull() && !info[8].IsUndefined()) {
|
|
153
|
+
auto fX0 = info[8].As<Napi::Float64Array>();
|
|
154
|
+
if (static_cast<int>(fX0.ElementLength()) != n) {
|
|
155
|
+
Napi::RangeError::New(env, "gmres: x0 size mismatch")
|
|
156
|
+
.ThrowAsJavaScriptException();
|
|
157
|
+
return env.Null();
|
|
158
|
+
}
|
|
159
|
+
std::memcpy(x.data(), fX0.Data(), n * sizeof(double));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
163
|
+
auto apply_precond = [&](double* r) {
|
|
164
|
+
if (hasM1) lu_solve(n, m1lu.data(), m1ipiv.data(), r);
|
|
165
|
+
if (hasM2) lu_solve(n, m2lu.data(), m2ipiv.data(), r);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// ── Initial preconditioned residual ────────────────────────────────────────
|
|
169
|
+
std::vector<double> r(n), tmp(n);
|
|
170
|
+
mat_vec(n, A, x.data(), r.data()); // r = A*x
|
|
171
|
+
for (int i = 0; i < n; i++) r[i] = b[i] - r[i]; // r = b - A*x
|
|
172
|
+
apply_precond(r.data()); // r = M\(b - A*x)
|
|
173
|
+
double beta = vec_nrm2(n, r.data());
|
|
174
|
+
|
|
175
|
+
// Preconditioned RHS norm
|
|
176
|
+
std::vector<double> Mb(b, b + n);
|
|
177
|
+
apply_precond(Mb.data());
|
|
178
|
+
double normMb = vec_nrm2(n, Mb.data());
|
|
179
|
+
if (normMb == 0.0) normMb = 1.0;
|
|
180
|
+
|
|
181
|
+
std::vector<double> resvec;
|
|
182
|
+
resvec.push_back(beta);
|
|
183
|
+
|
|
184
|
+
int flag = 1;
|
|
185
|
+
int outerIter = 0, innerIter = 0;
|
|
186
|
+
|
|
187
|
+
// Already converged?
|
|
188
|
+
if (beta / normMb <= tol) {
|
|
189
|
+
flag = 0;
|
|
190
|
+
goto done;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
{
|
|
194
|
+
// Workspace allocated once and reused across restarts
|
|
195
|
+
std::vector<double> V(n * (restart + 1));
|
|
196
|
+
std::vector<double> H((restart + 1) * restart);
|
|
197
|
+
std::vector<double> cs(restart), sn(restart);
|
|
198
|
+
std::vector<double> g(restart + 1);
|
|
199
|
+
std::vector<double> w(n);
|
|
200
|
+
|
|
201
|
+
for (int outer = 1; outer <= maxit; outer++) {
|
|
202
|
+
outerIter = outer;
|
|
203
|
+
|
|
204
|
+
// V(:,0) = r / beta
|
|
205
|
+
for (int i = 0; i < n; i++) V[i] = r[i] / beta;
|
|
206
|
+
|
|
207
|
+
std::fill(H.begin(), H.end(), 0.0);
|
|
208
|
+
std::fill(cs.begin(), cs.end(), 0.0);
|
|
209
|
+
std::fill(sn.begin(), sn.end(), 0.0);
|
|
210
|
+
std::fill(g.begin(), g.end(), 0.0);
|
|
211
|
+
g[0] = beta;
|
|
212
|
+
|
|
213
|
+
bool converged = false;
|
|
214
|
+
const int ldh = restart + 1;
|
|
215
|
+
|
|
216
|
+
for (int j = 0; j < restart; j++) {
|
|
217
|
+
innerIter = j + 1;
|
|
218
|
+
|
|
219
|
+
// w = A * V(:,j)
|
|
220
|
+
mat_vec(n, A, &V[j * n], w.data());
|
|
221
|
+
apply_precond(w.data());
|
|
222
|
+
|
|
223
|
+
// Modified Gram-Schmidt
|
|
224
|
+
for (int i = 0; i <= j; i++) {
|
|
225
|
+
double hij = vec_dot(n, w.data(), &V[i * n]);
|
|
226
|
+
H[i + j * ldh] = hij;
|
|
227
|
+
for (int k = 0; k < n; k++) w[k] -= hij * V[k + i * n];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
double wnorm = vec_nrm2(n, w.data());
|
|
231
|
+
H[(j + 1) + j * ldh] = wnorm;
|
|
232
|
+
|
|
233
|
+
if (wnorm > 1e-300) {
|
|
234
|
+
double inv_wnorm = 1.0 / wnorm;
|
|
235
|
+
for (int k = 0; k < n; k++) V[k + (j + 1) * n] = w[k] * inv_wnorm;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Apply previous Givens rotations
|
|
239
|
+
for (int i = 0; i < j; i++) {
|
|
240
|
+
double hi = H[i + j * ldh];
|
|
241
|
+
double hi1 = H[(i + 1) + j * ldh];
|
|
242
|
+
H[i + j * ldh] = cs[i] * hi + sn[i] * hi1;
|
|
243
|
+
H[(i + 1) + j * ldh] = -sn[i] * hi + cs[i] * hi1;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// New Givens rotation
|
|
247
|
+
double c, s;
|
|
248
|
+
givens(H[j + j * ldh], H[(j + 1) + j * ldh], c, s);
|
|
249
|
+
cs[j] = c;
|
|
250
|
+
sn[j] = s;
|
|
251
|
+
|
|
252
|
+
H[j + j * ldh] = c * H[j + j * ldh] + s * H[(j + 1) + j * ldh];
|
|
253
|
+
H[(j + 1) + j * ldh] = 0.0;
|
|
254
|
+
|
|
255
|
+
double gj = g[j];
|
|
256
|
+
g[j] = c * gj;
|
|
257
|
+
g[j + 1] = -s * gj;
|
|
258
|
+
|
|
259
|
+
double residNorm = std::abs(g[j + 1]);
|
|
260
|
+
resvec.push_back(residNorm);
|
|
261
|
+
|
|
262
|
+
if (residNorm / normMb <= tol) {
|
|
263
|
+
// Back-solve H(0:j+1, 0:j+1) * y = g(0:j+1)
|
|
264
|
+
std::vector<double> y(j + 1);
|
|
265
|
+
for (int k = 0; k <= j; k++) y[k] = g[k];
|
|
266
|
+
for (int k = j; k >= 0; k--) {
|
|
267
|
+
for (int l = k + 1; l <= j; l++) y[k] -= H[k + l * ldh] * y[l];
|
|
268
|
+
y[k] /= H[k + k * ldh];
|
|
269
|
+
}
|
|
270
|
+
// x += V(:,0:j+1) * y
|
|
271
|
+
for (int k = 0; k < n; k++)
|
|
272
|
+
for (int l = 0; l <= j; l++) x[k] += V[k + l * n] * y[l];
|
|
273
|
+
|
|
274
|
+
flag = 0;
|
|
275
|
+
converged = true;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (converged) break;
|
|
281
|
+
|
|
282
|
+
// Restart: solve and update x
|
|
283
|
+
{
|
|
284
|
+
std::vector<double> y(restart);
|
|
285
|
+
for (int k = 0; k < restart; k++) y[k] = g[k];
|
|
286
|
+
for (int k = restart - 1; k >= 0; k--) {
|
|
287
|
+
for (int l = k + 1; l < restart; l++) y[k] -= H[k + l * ldh] * y[l];
|
|
288
|
+
y[k] /= H[k + k * ldh];
|
|
289
|
+
}
|
|
290
|
+
for (int k = 0; k < n; k++)
|
|
291
|
+
for (int l = 0; l < restart; l++) x[k] += V[k + l * n] * y[l];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Recompute residual
|
|
295
|
+
mat_vec(n, A, x.data(), r.data());
|
|
296
|
+
for (int i = 0; i < n; i++) r[i] = b[i] - r[i];
|
|
297
|
+
apply_precond(r.data());
|
|
298
|
+
beta = vec_nrm2(n, r.data());
|
|
299
|
+
|
|
300
|
+
if (beta / normMb <= tol) {
|
|
301
|
+
flag = 0;
|
|
302
|
+
innerIter = 0;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
done:
|
|
309
|
+
// Final relative residual (recompute for accuracy if converged)
|
|
310
|
+
double relres;
|
|
311
|
+
if (flag == 0) {
|
|
312
|
+
mat_vec(n, A, x.data(), tmp.data());
|
|
313
|
+
for (int i = 0; i < n; i++) tmp[i] = b[i] - tmp[i];
|
|
314
|
+
apply_precond(tmp.data());
|
|
315
|
+
relres = vec_nrm2(n, tmp.data()) / normMb;
|
|
316
|
+
} else {
|
|
317
|
+
relres = beta / normMb;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
auto result = Napi::Object::New(env);
|
|
321
|
+
result.Set("x", vecToF64(env, x));
|
|
322
|
+
result.Set("flag", Napi::Number::New(env, flag));
|
|
323
|
+
result.Set("relres", Napi::Number::New(env, relres));
|
|
324
|
+
auto iterArr = Napi::Int32Array::New(env, 2);
|
|
325
|
+
iterArr[0] = outerIter;
|
|
326
|
+
iterArr[1] = innerIter;
|
|
327
|
+
result.Set("iter", iterArr);
|
|
328
|
+
result.Set("resvec", vecToF64(env, resvec));
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
333
|
+
// GmresComplex — complex version using BLAS zgemv + LAPACK zgetrf/zgetrs
|
|
334
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
335
|
+
|
|
336
|
+
using cx = lapack_complex_double;
|
|
337
|
+
|
|
338
|
+
static inline double cx_abs(cx a) { return std::sqrt(a.real*a.real + a.imag*a.imag); }
|
|
339
|
+
static inline cx cx_conj(cx a) { return {a.real, -a.imag}; }
|
|
340
|
+
static inline cx cx_mul(cx a, cx b) { return {a.real*b.real - a.imag*b.imag, a.real*b.imag + a.imag*b.real}; }
|
|
341
|
+
static inline cx cx_add(cx a, cx b) { return {a.real+b.real, a.imag+b.imag}; }
|
|
342
|
+
static inline cx cx_sub(cx a, cx b) { return {a.real-b.real, a.imag-b.imag}; }
|
|
343
|
+
static inline cx cx_scale(double s, cx a) { return {s*a.real, s*a.imag}; }
|
|
344
|
+
static inline cx cx_div(cx a, cx b) {
|
|
345
|
+
double d = b.real*b.real + b.imag*b.imag;
|
|
346
|
+
return {(a.real*b.real + a.imag*b.imag)/d, (a.imag*b.real - a.real*b.imag)/d};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static double cvec_nrm2(int n, const cx* a) {
|
|
350
|
+
double s = 0;
|
|
351
|
+
for (int i = 0; i < n; i++) s += a[i].real*a[i].real + a[i].imag*a[i].imag;
|
|
352
|
+
return std::sqrt(s);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Conjugate dot product: sum(conj(a) * b)
|
|
356
|
+
static cx cvec_dot(int n, const cx* a, const cx* b) {
|
|
357
|
+
double re = 0, im = 0;
|
|
358
|
+
for (int i = 0; i < n; i++) {
|
|
359
|
+
re += a[i].real*b[i].real + a[i].imag*b[i].imag;
|
|
360
|
+
im += a[i].real*b[i].imag - a[i].imag*b[i].real;
|
|
361
|
+
}
|
|
362
|
+
return {re, im};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// y = A*x using zgemv
|
|
366
|
+
static void cmat_vec(int n, const cx* A, const cx* x, cx* y) {
|
|
367
|
+
char trans = 'N';
|
|
368
|
+
cx alpha = {1,0}, beta_val = {0,0};
|
|
369
|
+
int inc = 1;
|
|
370
|
+
zgemv_(&trans, &n, &n, &alpha, const_cast<cx*>(A), &n,
|
|
371
|
+
const_cast<cx*>(x), &inc, &beta_val, y, &inc);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
static void clu_solve(int n, const cx* LU, const int* ipiv, cx* rhs) {
|
|
375
|
+
char trans = 'N';
|
|
376
|
+
int nrhs = 1, info_val = 0;
|
|
377
|
+
zgetrs_(&trans, &n, &nrhs, const_cast<cx*>(LU), &n,
|
|
378
|
+
const_cast<int*>(ipiv), rhs, &n, &info_val);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Complex Givens: [c s; -conj(s) c] * [a; b] = [r; 0], c real >= 0
|
|
382
|
+
static void cgivens(cx a, cx b, double& c, cx& s, cx& r) {
|
|
383
|
+
double absB = cx_abs(b);
|
|
384
|
+
if (absB == 0) { c = 1; s = {0,0}; r = a; return; }
|
|
385
|
+
double absA = cx_abs(a);
|
|
386
|
+
if (absA == 0) { c = 0; s = cx_conj({b.real/absB, b.imag/absB}); r = {absB,0}; return; }
|
|
387
|
+
double norm = std::sqrt(absA*absA + absB*absB);
|
|
388
|
+
c = absA / norm;
|
|
389
|
+
cx alpha = {a.real/absA, a.imag/absA};
|
|
390
|
+
s = cx_div(cx_mul(alpha, cx_conj(b)), {norm, 0});
|
|
391
|
+
r = cx_scale(norm, alpha);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
Napi::Value GmresComplex(const Napi::CallbackInfo& info) {
|
|
395
|
+
Napi::Env env = info.Env();
|
|
396
|
+
|
|
397
|
+
// Args: ARe, AIm, n, bRe, bIm, restart, tol, maxit,
|
|
398
|
+
// M1Re, M1Im, M2Re, M2Im, x0Re, x0Im (14 args)
|
|
399
|
+
if (info.Length() < 14) {
|
|
400
|
+
Napi::TypeError::New(env, "gmresComplex: expected 14 arguments")
|
|
401
|
+
.ThrowAsJavaScriptException();
|
|
402
|
+
return env.Null();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
auto fARe = info[0].As<Napi::Float64Array>();
|
|
406
|
+
auto fAIm = info[1].As<Napi::Float64Array>();
|
|
407
|
+
int n = info[2].As<Napi::Number>().Int32Value();
|
|
408
|
+
auto fBRe = info[3].As<Napi::Float64Array>();
|
|
409
|
+
auto fBIm = info[4].As<Napi::Float64Array>();
|
|
410
|
+
int restart = info[5].As<Napi::Number>().Int32Value();
|
|
411
|
+
double tol = info[6].As<Napi::Number>().DoubleValue();
|
|
412
|
+
int maxit = info[7].As<Napi::Number>().Int32Value();
|
|
413
|
+
|
|
414
|
+
if (restart <= 0 || restart > n) restart = n;
|
|
415
|
+
|
|
416
|
+
// Interleave A
|
|
417
|
+
auto A = splitToInterleaved(fARe, fAIm, n * n);
|
|
418
|
+
|
|
419
|
+
// b
|
|
420
|
+
std::vector<cx> b(n);
|
|
421
|
+
for (int i = 0; i < n; i++) b[i] = {fBRe[i], fBIm[i]};
|
|
422
|
+
|
|
423
|
+
// Pre-factor preconditioners
|
|
424
|
+
bool hasM1 = !info[8].IsNull() && !info[8].IsUndefined();
|
|
425
|
+
bool hasM2 = !info[10].IsNull() && !info[10].IsUndefined();
|
|
426
|
+
|
|
427
|
+
std::vector<cx> m1lu, m2lu;
|
|
428
|
+
std::vector<int> m1ipiv, m2ipiv;
|
|
429
|
+
|
|
430
|
+
if (hasM1) {
|
|
431
|
+
auto fM1Re = info[8].As<Napi::Float64Array>();
|
|
432
|
+
auto fM1Im = info[9].As<Napi::Float64Array>();
|
|
433
|
+
m1lu = splitToInterleaved(fM1Re, fM1Im, n * n);
|
|
434
|
+
m1ipiv.resize(n);
|
|
435
|
+
int info_val = 0;
|
|
436
|
+
zgetrf_(&n, &n, m1lu.data(), &n, m1ipiv.data(), &info_val);
|
|
437
|
+
if (info_val != 0) {
|
|
438
|
+
Napi::Error::New(env, "gmresComplex: M1 is singular").ThrowAsJavaScriptException();
|
|
439
|
+
return env.Null();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (hasM2) {
|
|
443
|
+
auto fM2Re = info[10].As<Napi::Float64Array>();
|
|
444
|
+
auto fM2Im = info[11].As<Napi::Float64Array>();
|
|
445
|
+
m2lu = splitToInterleaved(fM2Re, fM2Im, n * n);
|
|
446
|
+
m2ipiv.resize(n);
|
|
447
|
+
int info_val = 0;
|
|
448
|
+
zgetrf_(&n, &n, m2lu.data(), &n, m2ipiv.data(), &info_val);
|
|
449
|
+
if (info_val != 0) {
|
|
450
|
+
Napi::Error::New(env, "gmresComplex: M2 is singular").ThrowAsJavaScriptException();
|
|
451
|
+
return env.Null();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// x0
|
|
456
|
+
std::vector<cx> x(n, {0,0});
|
|
457
|
+
if (!info[12].IsNull() && !info[12].IsUndefined()) {
|
|
458
|
+
auto fX0Re = info[12].As<Napi::Float64Array>();
|
|
459
|
+
auto fX0Im = info[13].As<Napi::Float64Array>();
|
|
460
|
+
for (int i = 0; i < n; i++) x[i] = {fX0Re[i], fX0Im[i]};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
auto apply_precond = [&](cx* r) {
|
|
464
|
+
if (hasM1) clu_solve(n, m1lu.data(), m1ipiv.data(), r);
|
|
465
|
+
if (hasM2) clu_solve(n, m2lu.data(), m2ipiv.data(), r);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Initial residual
|
|
469
|
+
std::vector<cx> r(n), tmp(n);
|
|
470
|
+
cmat_vec(n, A.data(), x.data(), r.data());
|
|
471
|
+
for (int i = 0; i < n; i++) r[i] = cx_sub(b[i], r[i]);
|
|
472
|
+
apply_precond(r.data());
|
|
473
|
+
double beta = cvec_nrm2(n, r.data());
|
|
474
|
+
|
|
475
|
+
std::vector<cx> Mb(b);
|
|
476
|
+
apply_precond(Mb.data());
|
|
477
|
+
double normMb = cvec_nrm2(n, Mb.data());
|
|
478
|
+
if (normMb == 0.0) normMb = 1.0;
|
|
479
|
+
|
|
480
|
+
std::vector<double> resvec;
|
|
481
|
+
resvec.push_back(beta);
|
|
482
|
+
|
|
483
|
+
int flag = 1, outerIter = 0, innerIter = 0;
|
|
484
|
+
|
|
485
|
+
if (beta / normMb <= tol) { flag = 0; goto cdone; }
|
|
486
|
+
|
|
487
|
+
{
|
|
488
|
+
std::vector<cx> V(n * (restart + 1));
|
|
489
|
+
std::vector<cx> H((restart + 1) * restart, {0,0});
|
|
490
|
+
std::vector<double> cs_arr(restart);
|
|
491
|
+
std::vector<cx> sn_arr(restart);
|
|
492
|
+
std::vector<cx> g(restart + 1, {0,0});
|
|
493
|
+
std::vector<cx> w(n);
|
|
494
|
+
|
|
495
|
+
for (int outer = 1; outer <= maxit; outer++) {
|
|
496
|
+
outerIter = outer;
|
|
497
|
+
for (int i = 0; i < n; i++) V[i] = cx_scale(1.0/beta, r[i]);
|
|
498
|
+
|
|
499
|
+
std::fill(H.begin(), H.end(), cx{0,0});
|
|
500
|
+
std::fill(cs_arr.begin(), cs_arr.end(), 0.0);
|
|
501
|
+
std::fill(sn_arr.begin(), sn_arr.end(), cx{0,0});
|
|
502
|
+
std::fill(g.begin(), g.end(), cx{0,0});
|
|
503
|
+
g[0] = {beta, 0};
|
|
504
|
+
|
|
505
|
+
bool converged = false;
|
|
506
|
+
const int ldh = restart + 1;
|
|
507
|
+
|
|
508
|
+
for (int j = 0; j < restart; j++) {
|
|
509
|
+
innerIter = j + 1;
|
|
510
|
+
|
|
511
|
+
cmat_vec(n, A.data(), &V[j*n], w.data());
|
|
512
|
+
apply_precond(w.data());
|
|
513
|
+
|
|
514
|
+
// Modified Gram-Schmidt (conjugate dot)
|
|
515
|
+
for (int i = 0; i <= j; i++) {
|
|
516
|
+
cx hij = cvec_dot(n, &V[i*n], w.data());
|
|
517
|
+
H[i + j*ldh] = hij;
|
|
518
|
+
for (int k = 0; k < n; k++) w[k] = cx_sub(w[k], cx_mul(hij, V[k + i*n]));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
double wnorm = cvec_nrm2(n, w.data());
|
|
522
|
+
H[(j+1) + j*ldh] = {wnorm, 0};
|
|
523
|
+
if (wnorm > 1e-300) {
|
|
524
|
+
double inv_w = 1.0 / wnorm;
|
|
525
|
+
for (int k = 0; k < n; k++) V[k + (j+1)*n] = cx_scale(inv_w, w[k]);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Apply previous Givens rotations
|
|
529
|
+
for (int i = 0; i < j; i++) {
|
|
530
|
+
double c = cs_arr[i]; cx s = sn_arr[i];
|
|
531
|
+
cx hi = H[i + j*ldh], hi1 = H[(i+1) + j*ldh];
|
|
532
|
+
H[i + j*ldh] = cx_add(cx_scale(c, hi), cx_mul(s, hi1));
|
|
533
|
+
H[(i+1) + j*ldh] = cx_add(cx_mul({-s.real, s.imag}, hi), cx_scale(c, hi1));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// New Givens rotation
|
|
537
|
+
double c; cx s, rr;
|
|
538
|
+
cgivens(H[j + j*ldh], H[(j+1) + j*ldh], c, s, rr);
|
|
539
|
+
cs_arr[j] = c;
|
|
540
|
+
sn_arr[j] = s;
|
|
541
|
+
|
|
542
|
+
H[j + j*ldh] = rr;
|
|
543
|
+
H[(j+1) + j*ldh] = {0,0};
|
|
544
|
+
|
|
545
|
+
cx gj = g[j], gj1 = g[j+1];
|
|
546
|
+
g[j] = cx_add(cx_scale(c, gj), cx_mul(s, gj1));
|
|
547
|
+
g[j+1] = cx_add(cx_mul({-s.real, s.imag}, gj), cx_scale(c, gj1));
|
|
548
|
+
|
|
549
|
+
double residNorm = cx_abs(g[j+1]);
|
|
550
|
+
resvec.push_back(residNorm);
|
|
551
|
+
|
|
552
|
+
if (residNorm / normMb <= tol) {
|
|
553
|
+
// Complex back-solve
|
|
554
|
+
std::vector<cx> y(j+1);
|
|
555
|
+
for (int k = 0; k <= j; k++) y[k] = g[k];
|
|
556
|
+
for (int k = j; k >= 0; k--) {
|
|
557
|
+
for (int l = k+1; l <= j; l++) y[k] = cx_sub(y[k], cx_mul(H[k + l*ldh], y[l]));
|
|
558
|
+
y[k] = cx_div(y[k], H[k + k*ldh]);
|
|
559
|
+
}
|
|
560
|
+
for (int k = 0; k < n; k++)
|
|
561
|
+
for (int l = 0; l <= j; l++) x[k] = cx_add(x[k], cx_mul(V[k + l*n], y[l]));
|
|
562
|
+
flag = 0; converged = true; break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (converged) break;
|
|
566
|
+
|
|
567
|
+
// Restart
|
|
568
|
+
{
|
|
569
|
+
std::vector<cx> y(restart);
|
|
570
|
+
for (int k = 0; k < restart; k++) y[k] = g[k];
|
|
571
|
+
for (int k = restart-1; k >= 0; k--) {
|
|
572
|
+
for (int l = k+1; l < restart; l++) y[k] = cx_sub(y[k], cx_mul(H[k + l*ldh], y[l]));
|
|
573
|
+
y[k] = cx_div(y[k], H[k + k*ldh]);
|
|
574
|
+
}
|
|
575
|
+
for (int k = 0; k < n; k++)
|
|
576
|
+
for (int l = 0; l < restart; l++) x[k] = cx_add(x[k], cx_mul(V[k + l*n], y[l]));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
cmat_vec(n, A.data(), x.data(), r.data());
|
|
580
|
+
for (int i = 0; i < n; i++) r[i] = cx_sub(b[i], r[i]);
|
|
581
|
+
apply_precond(r.data());
|
|
582
|
+
beta = cvec_nrm2(n, r.data());
|
|
583
|
+
if (beta / normMb <= tol) { flag = 0; innerIter = 0; break; }
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
cdone:
|
|
588
|
+
double relres;
|
|
589
|
+
if (flag == 0) {
|
|
590
|
+
cmat_vec(n, A.data(), x.data(), tmp.data());
|
|
591
|
+
for (int i = 0; i < n; i++) tmp[i] = cx_sub(b[i], tmp[i]);
|
|
592
|
+
apply_precond(tmp.data());
|
|
593
|
+
relres = cvec_nrm2(n, tmp.data()) / normMb;
|
|
594
|
+
} else {
|
|
595
|
+
relres = beta / normMb;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
auto result = Napi::Object::New(env);
|
|
599
|
+
// Split x into re/im
|
|
600
|
+
auto xReArr = Napi::Float64Array::New(env, n);
|
|
601
|
+
auto xImArr = Napi::Float64Array::New(env, n);
|
|
602
|
+
for (int i = 0; i < n; i++) { xReArr[i] = x[i].real; xImArr[i] = x[i].imag; }
|
|
603
|
+
result.Set("xRe", xReArr);
|
|
604
|
+
result.Set("xIm", xImArr);
|
|
605
|
+
result.Set("flag", Napi::Number::New(env, flag));
|
|
606
|
+
result.Set("relres", Napi::Number::New(env, relres));
|
|
607
|
+
auto iterArr = Napi::Int32Array::New(env, 2);
|
|
608
|
+
iterArr[0] = outerIter; iterArr[1] = innerIter;
|
|
609
|
+
result.Set("iter", iterArr);
|
|
610
|
+
result.Set("resvec", vecToF64(env, resvec));
|
|
611
|
+
return result;
|
|
612
|
+
}
|
package/native/numbl_addon.cpp
CHANGED
|
@@ -29,7 +29,7 @@ extern "C" {
|
|
|
29
29
|
// ── Addon version ────────────────────────────────────────────────────────────
|
|
30
30
|
// Bump this integer whenever the addon's API changes (new functions, signature
|
|
31
31
|
// changes, etc.) so that the JS side can detect stale builds.
|
|
32
|
-
static const int ADDON_VERSION =
|
|
32
|
+
static const int ADDON_VERSION = 2;
|
|
33
33
|
|
|
34
34
|
static Napi::Value AddonVersion(const Napi::CallbackInfo& info) {
|
|
35
35
|
return Napi::Number::New(info.Env(), ADDON_VERSION);
|
|
@@ -101,6 +101,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
101
101
|
Napi::Function::New(env, FillRandn));
|
|
102
102
|
exports.Set(Napi::String::New(env, "unaryElemwise"),
|
|
103
103
|
Napi::Function::New(env, UnaryElemwise));
|
|
104
|
+
exports.Set(Napi::String::New(env, "gmres"),
|
|
105
|
+
Napi::Function::New(env, Gmres));
|
|
106
|
+
exports.Set(Napi::String::New(env, "gmresComplex"),
|
|
107
|
+
Napi::Function::New(env, GmresComplex));
|
|
104
108
|
return exports;
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -146,6 +146,30 @@ extern "C" {
|
|
|
146
146
|
// A = U^H * U (uplo='U') or A = L * L^H (uplo='L')
|
|
147
147
|
void zpotrf_(char* uplo, int* n, lapack_complex_double* a, int* lda, int* info);
|
|
148
148
|
|
|
149
|
+
// ── Matrix-vector multiply (BLAS) ─────────────────────────────────────────
|
|
150
|
+
// y = alpha * op(A) * x + beta * y
|
|
151
|
+
void dgemv_(char* trans, int* m, int* n,
|
|
152
|
+
double* alpha, double* a, int* lda,
|
|
153
|
+
double* x, int* incx,
|
|
154
|
+
double* beta, double* y, int* incy);
|
|
155
|
+
|
|
156
|
+
// ── Triangular solve using LU factors (LAPACK) ──────────────────────────────
|
|
157
|
+
// Solve A * X = B using the LU factorization from dgetrf
|
|
158
|
+
void dgetrs_(char* trans, int* n, int* nrhs,
|
|
159
|
+
double* a, int* lda, int* ipiv,
|
|
160
|
+
double* b, int* ldb, int* info);
|
|
161
|
+
|
|
162
|
+
// Complex matrix-vector multiply: y = alpha * op(A) * x + beta * y
|
|
163
|
+
void zgemv_(char* trans, int* m, int* n,
|
|
164
|
+
lapack_complex_double* alpha, lapack_complex_double* a, int* lda,
|
|
165
|
+
lapack_complex_double* x, int* incx,
|
|
166
|
+
lapack_complex_double* beta, lapack_complex_double* y, int* incy);
|
|
167
|
+
|
|
168
|
+
// Complex triangular solve using LU factors from zgetrf
|
|
169
|
+
void zgetrs_(char* trans, int* n, int* nrhs,
|
|
170
|
+
lapack_complex_double* a, int* lda, int* ipiv,
|
|
171
|
+
lapack_complex_double* b, int* ldb, int* info);
|
|
172
|
+
|
|
149
173
|
// ── QZ factorization (generalized Schur decomposition) ──────────────────────
|
|
150
174
|
// Compute the generalized real Schur form of (A, B):
|
|
151
175
|
// A = VSL * S * VSR^T, B = VSL * T * VSR^T
|
|
@@ -286,3 +310,5 @@ Napi::Value ElemwiseScalar(const Napi::CallbackInfo& info);
|
|
|
286
310
|
Napi::Value ElemwiseComplex(const Napi::CallbackInfo& info);
|
|
287
311
|
Napi::Value FillRandn(const Napi::CallbackInfo& info);
|
|
288
312
|
Napi::Value UnaryElemwise(const Napi::CallbackInfo& info);
|
|
313
|
+
Napi::Value Gmres(const Napi::CallbackInfo& info);
|
|
314
|
+
Napi::Value GmresComplex(const Napi::CallbackInfo& info);
|