RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
rhs_dispatch.cpp
Go to the documentation of this file.
1
30#include "rate_equations.h"
31#include <nvector/nvector_serial.h>
32#include <cmath>
33#include <algorithm>
34#include <iostream>
35#include <vector>
36
37// ══════════════════════════════════════════════════════════════════════════════
38// Preconditioner: Jacobi (legacy) and Woodbury (bordered-banded)
39//
40// The Woodbury preconditioner exploits the fact that the Jacobian has the
41// structure J = T + U·V^T where:
42// T = banded part (half-bandwidth prec_bw)
43// U = N_eq × r dense columns from mobile species coupling
44// V = selector matrix [e_{j1}, ..., e_{jr}]
45//
46// The Newton correction system (I - γJ)x = r is solved via SMW:
47// M = T̂ - γ U V^T, T̂ = I - γT
48// M^{-1} = T̂^{-1} + T̂^{-1} U S^{-1} V^T T̂^{-1}
49// where S = -I/γ_scale + V^T T̂^{-1} U (r × r Schur complement)
50//
51// LAPACK band storage (dgbtrf):
52// A(i,j) is stored at band[kl + ku + i - j][j] (0-indexed)
53// Total rows = 2*kl + ku + 1 = 3*bw + 1 (kl = ku = bw)
54// ══════════════════════════════════════════════════════════════════════════════
55
56// Forward declarations for LAPACK band and dense solvers.
57// The Woodbury preconditioner needs LAPACK (dgbtrf/dgbtrs/dgetrf/dgetrs).
58// When LAPACK is unavailable (CD_HAVE_LAPACK undefined), the entire Woodbury
59// section is excluded and the dispatcher falls back to Jacobi.
60#ifdef CD_HAVE_LAPACK
61extern "C" {
62 void dgbtrf_(const int* m, const int* n, const int* kl, const int* ku,
63 double* ab, const int* ldab, int* ipiv, int* info);
64 void dgbtrs_(const char* trans, const int* n, const int* kl, const int* ku,
65 const int* nrhs, const double* ab, const int* ldab,
66 const int* ipiv, double* b, const int* ldb, int* info);
67 void dgetrf_(const int* m, const int* n, double* a, const int* lda,
68 int* ipiv, int* info);
69 void dgetrs_(const char* trans, const int* n, const int* nrhs,
70 const double* a, const int* lda, const int* ipiv,
71 double* b, const int* ldb, int* info);
72}
73#endif
74
75// ── Jacobi preconditioner (legacy, prec_type==0) ─────────────────────────────
76
77static int prec_setup_jacobi(sunrealtype /*t*/, N_Vector /*y*/, N_Vector /*fy*/,
78 sunbooleantype /*jok*/, sunbooleantype* jcurPtr,
79 sunrealtype /*gamma*/, void* /*user_data*/) {
80 *jcurPtr = SUNTRUE;
81 return 0;
82}
83
84static int prec_solve_jacobi(sunrealtype /*t*/, N_Vector /*y*/, N_Vector /*fy*/,
85 N_Vector r, N_Vector z,
86 sunrealtype gamma, sunrealtype /*delta*/,
87 int /*lr*/, void* user_data) {
88 const UserData* ud = static_cast<const UserData*>(user_data);
89 const Parameters& P = *ud->P;
90 const double* rv = N_VGetArrayPointer_Serial(r);
91 double* zv = N_VGetArrayPointer_Serial(z);
92 if (P.prec_diag.empty()) {
93 for (int k = 0; k < P.N_eq; ++k) zv[k] = rv[k];
94 return 0;
95 }
96 for (int k = 0; k < P.N_eq; ++k) {
97 double d = 1.0 - gamma * P.prec_diag[k];
98 zv[k] = rv[k] / (std::abs(d) > 1e-20 ? d : 1e-20);
99 }
100 return 0;
101}
102
103// ── Woodbury preconditioner (prec_type==1) ───────────────────────────────────
104#ifdef CD_HAVE_LAPACK
105
106// Finite-difference perturbation size (same as CVODE internal formula)
107static inline double fd_delta(double yj, double atol) {
108 const double srur = std::sqrt(2.2e-16); // sqrt(machine epsilon)
109 double dy = srur * std::max(std::abs(yj), std::max(atol, 1e-30));
110 // Ensure (yj + dy) - yj is exactly representable
111 volatile double temp = yj + dy;
112 return temp - yj;
113}
114
115static int prec_setup_woodbury(sunrealtype t, N_Vector y, N_Vector fy,
116 sunbooleantype jok, sunbooleantype* jcurPtr,
117 sunrealtype gamma, void* user_data) {
118 UserData* ud = static_cast<UserData*>(user_data);
119 Parameters& P = *ud->P;
120 const int N = P.N_eq;
121 const int bw = P.prec_bw;
122 const int r = P.prec_rank;
123
124 // ── Reuse check: if CVODE says the old Jacobian is still OK (jok=TRUE)
125 // and the band storage has been initialised, skip the expensive rebuild.
126 // CVODE will pass jok=FALSE when it wants a fresh Jacobian (e.g. after
127 // a convergence failure or at the start of a new step).
128 if (jok && !P.prec_band.empty()) {
129 *jcurPtr = SUNFALSE; // tell CVODE we kept the old Jacobian
130 P.prec_gamma = gamma; // update γ (step size may have changed)
131 return 0;
132 }
133
134 *jcurPtr = SUNTRUE;
135 P.prec_gamma = gamma;
136
137 double* ydata = N_VGetArrayPointer_Serial(y);
138
139 // ── Allocate storage on first call ───────────────────────────────────────
140 const int ldab = 3 * bw + 1; // LAPACK dgbtrf: 2*kl + ku + 1 with kl=ku=bw
141 if (static_cast<int>(P.prec_band.size()) != ldab * N) {
142 P.prec_band.resize(ldab * N, 0.0);
143 P.prec_Tinv_U.resize(N * r, 0.0);
144 P.prec_schur.resize(r * r, 0.0);
145 P.prec_ipiv_band.resize(N, 0);
146 P.prec_ipiv_schur.resize(r, 0);
147 P.prec_f0.resize(N, 0.0);
148 P.prec_work.resize(N, 0.0);
149 P.prec_y_save.resize(N, 0.0);
150 P.prec_deltas.resize(N, 0.0);
151 P.prec_f_pert.resize(N, 0.0);
152 }
153
154 // ── Base RHS evaluation ──────────────────────────────────────────────────
155 // fy already contains f(t,y); copy it so we can reuse fy as scratch
156 const double* f0_ptr = N_VGetArrayPointer_Serial(fy);
157 for (int k = 0; k < N; ++k) P.prec_f0[k] = f0_ptr[k];
158
159 // Save the original y for restoration after each probe
160 for (int k = 0; k < N; ++k) P.prec_y_save[k] = ydata[k];
161
162 // ── Step 1: Build banded part T via Curtis-Powell-Reid coloring ──────────
163 // Columns j with j mod (2*bw+1) == c can be probed simultaneously
164 // because they are more than bw apart → only (2*bw+1) RHS evaluations.
165 const int n_colors = 2 * bw + 1;
166
167 // Zero the band storage
168 std::fill(P.prec_band.begin(), P.prec_band.end(), 0.0);
169 std::fill(P.prec_deltas.begin(), P.prec_deltas.end(), 0.0);
170
171 for (int c = 0; c < n_colors; ++c) {
172 // Perturb all columns in this color group
173 for (int j = c; j < N; j += n_colors) {
174 double dy = fd_delta(ydata[j], P.atol);
175 P.prec_deltas[j] = dy;
176 ydata[j] += dy;
177 }
178
179 // Evaluate perturbed RHS (writes into fy, which we treat as scratch)
180 ud->rhs_fn(t, y, fy, user_data);
181 const double* f_tmp = N_VGetArrayPointer_Serial(fy);
182 for (int k = 0; k < N; ++k) P.prec_f_pert[k] = f_tmp[k];
183
184 // Extract banded Jacobian entries and form T̂ = I - γT
185 for (int j = c; j < N; j += n_colors) {
186 double inv_dy = 1.0 / P.prec_deltas[j];
187 int klo = std::max(0, j - bw);
188 int khi = std::min(N - 1, j + bw);
189 for (int k = klo; k <= khi; ++k) {
190 double Jkj = (P.prec_f_pert[k] - P.prec_f0[k]) * inv_dy;
191 // LAPACK column-major band storage:
192 // A(i,j) is at band[kl + ku + i - j, j]
193 // = band[(kl + ku + i - j) + j * ldab]
194 int band_row = 2 * bw + (k - j); // kl + ku + i - j
195 P.prec_band[band_row + j * ldab] = (k == j)
196 ? (1.0 - gamma * Jkj)
197 : (-gamma * Jkj);
198 }
199 }
200
201 // Restore y for this color group
202 for (int j = c; j < N; j += n_colors) {
203 ydata[j] = P.prec_y_save[j];
204 P.prec_deltas[j] = 0.0;
205 }
206 }
207
208 // ── Step 2: Factor banded T̂ via LAPACK dgbtrf ────────────────────────────
209 {
210 int info = 0;
211 int kl = bw, ku = bw;
212 dgbtrf_(&N, &N, &kl, &ku, P.prec_band.data(), &ldab,
213 P.prec_ipiv_band.data(), &info);
214 if (info != 0) {
215 std::cerr << "[prec_setup_woodbury] dgbtrf failed, info=" << info << "\n";
216 // Restore fy before returning
217 double* fy_data = N_VGetArrayPointer_Serial(fy);
218 for (int k = 0; k < N; ++k) fy_data[k] = P.prec_f0[k];
219 return -1;
220 }
221 }
222
223 // ── Step 3: Probe dense columns for mobile species ──────────────────────
224 // For each mobile index j, compute full column J[:,j] via one FD probe,
225 // subtract the banded part already in T, giving U[:,j] (the correction).
226 // Then solve T̂^{-1} U[:,j] in-place.
227 for (int jj = 0; jj < r; ++jj) {
228 int j = P.prec_mobile_idx[jj];
229 double dy = fd_delta(ydata[j], P.atol);
230 ydata[j] += dy;
231
232 ud->rhs_fn(t, y, fy, user_data);
233 const double* f_tmp = N_VGetArrayPointer_Serial(fy);
234
235 double inv_dy = 1.0 / dy;
236 double* col = &P.prec_Tinv_U[jj * N]; // column jj of Tinv_U
237
238 for (int k = 0; k < N; ++k) {
239 double Jkj = (f_tmp[k] - P.prec_f0[k]) * inv_dy;
240 // U[:,jj] = -γ * (J[k,j] - T[k,j]) for rows outside the band.
241 // Within the band, T already captures J, so the correction is zero.
242 int dist = std::abs(k - j);
243 if (dist <= bw) {
244 col[k] = 0.0;
245 } else {
246 col[k] = -gamma * Jkj;
247 }
248 }
249
250 ydata[j] = P.prec_y_save[j];
251
252 // Solve T̂^{-1} col in-place via dgbtrs
253 {
254 int info = 0;
255 int kl = bw, ku = bw;
256 int nrhs = 1;
257 char trans = 'N';
258 dgbtrs_(&trans, &N, &kl, &ku, &nrhs, P.prec_band.data(), &ldab,
259 P.prec_ipiv_band.data(), col, &N, &info);
260 if (info != 0) {
261 std::cerr << "[prec_setup_woodbury] dgbtrs for col " << jj
262 << " failed, info=" << info << "\n";
263 double* fy_data = N_VGetArrayPointer_Serial(fy);
264 for (int k = 0; k < N; ++k) fy_data[k] = P.prec_f0[k];
265 return -1;
266 }
267 }
268 }
269
270 // ── Step 4: Form and factor Schur complement S = I + V^T T̂^{-1} U ──────
271 // V = [e_{j1}, ..., e_{jr}], so V^T picks rows j1..jr from T̂^{-1} U.
272 // S[ii][jj] = δ_{ii,jj} + (T̂^{-1} U)[mobile_idx[ii], jj]
273 for (int ii = 0; ii < r; ++ii) {
274 int row_idx = P.prec_mobile_idx[ii];
275 for (int jj = 0; jj < r; ++jj) {
276 P.prec_schur[ii + jj * r] = (ii == jj ? 1.0 : 0.0)
277 + P.prec_Tinv_U[jj * N + row_idx];
278 }
279 }
280
281 // Factor S via LAPACK dgetrf
282 {
283 int info = 0;
284 dgetrf_(&r, &r, P.prec_schur.data(), &r,
285 P.prec_ipiv_schur.data(), &info);
286 if (info != 0) {
287 std::cerr << "[prec_setup_woodbury] dgetrf (Schur) failed, info="
288 << info << "\n";
289 double* fy_data = N_VGetArrayPointer_Serial(fy);
290 for (int k = 0; k < N; ++k) fy_data[k] = P.prec_f0[k];
291 return -1;
292 }
293 }
294
295 // ── Restore fy to original f(t,y) ────────────────────────────────────────
296 // CVODE expects fy to remain unchanged after prec_setup returns.
297 double* fy_data = N_VGetArrayPointer_Serial(fy);
298 for (int k = 0; k < N; ++k) fy_data[k] = P.prec_f0[k];
299
300 return 0;
301}
302
303static int prec_solve_woodbury(sunrealtype t, N_Vector y, N_Vector fy,
304 N_Vector rv, N_Vector zv,
305 sunrealtype gamma, sunrealtype /*delta*/,
306 int /*lr*/, void* user_data) {
307 const UserData* ud = static_cast<const UserData*>(user_data);
308 const Parameters& P = *ud->P;
309 const int N = P.N_eq;
310 const int bw = P.prec_bw;
311 const int r = P.prec_rank;
312 const int ldab = 3 * bw + 1;
313
314 double* rdata = N_VGetArrayPointer_Serial(rv);
315 double* zdata = N_VGetArrayPointer_Serial(zv);
316
317 (void)t; (void)y; (void)fy; (void)gamma;
318
319 // Step 1: w1 = T̂^{-1} r (banded back-solve)
320 for (int k = 0; k < N; ++k) zdata[k] = rdata[k];
321 {
322 int info = 0;
323 int kl = bw, ku = bw;
324 int nrhs = 1;
325 char trans = 'N';
326 dgbtrs_(&trans, &N, &kl, &ku, &nrhs, P.prec_band.data(), &ldab,
327 P.prec_ipiv_band.data(), zdata, &N, &info);
328 if (info != 0) {
329 std::cerr << "[prec_solve_woodbury] dgbtrs failed, info=" << info << "\n";
330 return -1;
331 }
332 }
333
334 if (r == 0) return 0; // No dense border — pure banded solve
335
336 // Step 2: w2 = V^T w1 (extract r components at mobile indices)
337 std::vector<double> w2(r);
338 for (int jj = 0; jj < r; ++jj)
339 w2[jj] = zdata[P.prec_mobile_idx[jj]];
340
341 // Step 3: w3 = S^{-1} w2 (dense solve)
342 {
343 int info = 0;
344 int nrhs = 1;
345 char trans = 'N';
346 dgetrs_(&trans, &r, &nrhs, P.prec_schur.data(), &r,
347 P.prec_ipiv_schur.data(), w2.data(), &r, &info);
348 if (info != 0) {
349 std::cerr << "[prec_solve_woodbury] dgetrs (Schur) failed, info="
350 << info << "\n";
351 return -1;
352 }
353 }
354
355 // Step 4: z = w1 - (T̂^{-1} U) · w3
356 for (int k = 0; k < N; ++k) {
357 double corr = 0.0;
358 for (int jj = 0; jj < r; ++jj)
359 corr += P.prec_Tinv_U[jj * N + k] * w2[jj];
360 zdata[k] -= corr;
361 }
362
363 return 0;
364}
365
366#endif // CD_HAVE_LAPACK
367
368// ── Dispatch wrappers ────────────────────────────────────────────────────────
369
370int prec_setup(sunrealtype t, N_Vector y, N_Vector fy,
371 sunbooleantype jok, sunbooleantype* jcurPtr,
372 sunrealtype gamma, void* user_data) {
373 const UserData* ud = static_cast<const UserData*>(user_data);
374#ifdef CD_HAVE_LAPACK
375 if (ud->P->prec_type == 1)
376 return prec_setup_woodbury(t, y, fy, jok, jcurPtr, gamma, user_data);
377#else
378 if (ud->P->prec_type == 1) {
379 static bool warned = false;
380 if (!warned) {
381 std::cerr << "[prec_setup] Woodbury requested but binary was built "
382 "without LAPACK -- falling back to Jacobi.\n";
383 warned = true;
384 }
385 }
386#endif
387 return prec_setup_jacobi(t, y, fy, jok, jcurPtr, gamma, user_data);
388}
389
390int prec_solve(sunrealtype t, N_Vector y, N_Vector fy,
391 N_Vector r, N_Vector z,
392 sunrealtype gamma, sunrealtype delta,
393 int lr, void* user_data) {
394 const UserData* ud = static_cast<const UserData*>(user_data);
395#ifdef CD_HAVE_LAPACK
396 if (ud->P->prec_type == 1)
397 return prec_solve_woodbury(t, y, fy, r, z, gamma, delta, lr, user_data);
398#endif
399 return prec_solve_jacobi(t, y, fy, r, z, gamma, delta, lr, user_data);
400}
void dgetrf_(const int *m, const int *n, double *a, const int *lda, int *ipiv, int *info)
int prec_solve(sunrealtype t, N_Vector y, N_Vector fy, N_Vector r, N_Vector z, sunrealtype gamma, sunrealtype delta, int lr, void *user_data)
int prec_setup(sunrealtype t, N_Vector y, N_Vector fy, sunbooleantype jok, sunbooleantype *jcurPtr, sunrealtype gamma, void *user_data)
static double fd_delta(double yj, double atol)
static int prec_solve_woodbury(sunrealtype t, N_Vector y, N_Vector fy, N_Vector rv, N_Vector zv, sunrealtype gamma, sunrealtype, int, void *user_data)
void dgbtrf_(const int *m, const int *n, const int *kl, const int *ku, double *ab, const int *ldab, int *ipiv, int *info)
void dgetrs_(const char *trans, const int *n, const int *nrhs, const double *a, const int *lda, const int *ipiv, double *b, const int *ldb, int *info)
static int prec_setup_woodbury(sunrealtype t, N_Vector y, N_Vector fy, sunbooleantype jok, sunbooleantype *jcurPtr, sunrealtype gamma, void *user_data)
static int prec_setup_jacobi(sunrealtype, N_Vector, N_Vector, sunbooleantype, sunbooleantype *jcurPtr, sunrealtype, void *)
void dgbtrs_(const char *trans, const int *n, const int *kl, const int *ku, const int *nrhs, const double *ab, const int *ldab, const int *ipiv, double *b, const int *ldb, int *info)
static int prec_solve_jacobi(sunrealtype, N_Vector, N_Vector, N_Vector r, N_Vector z, sunrealtype gamma, sunrealtype, int, void *user_data)
CVRhsFn_t rhs_fn
Parameters * P