147int main(
int argc,
char* argv[]) {
151 std::map<std::string, double> args;
152 std::string param_path;
155 const std::string pf =
"--param_file=";
156 std::string a1 = argv[1];
157 if (a1.rfind(pf, 0) == 0) {
158 param_path = a1.substr(pf.size());
164 if (args.empty() && argc > 1)
return 1;
167 const int N_EQ = P.N_eq;
176 FILE* fp_bin =
nullptr;
177 FILE* fp_win =
nullptr;
178 if (!param_path.empty()) {
179 std::string bp = param_path;
180 const std::string suf =
".txt";
181 if (bp.size() >= suf.size() &&
182 bp.compare(bp.size() - suf.size(), suf.size(), suf) == 0)
183 bp.replace(bp.size() - suf.size(), suf.size(),
".bin");
186 fopen_s(&fp_bin, bp.c_str(),
"wb");
188 fp_bin = std::fopen(bp.c_str(),
"wb");
191 std::cerr <<
"Warning: cannot open " << bp <<
" — using stdout\n";
194 const std::string wp = bp +
".window.csv";
196 fopen_s(&fp_win, wp.c_str(),
"w");
198 fp_win = std::fopen(wp.c_str(),
"w");
201 std::fprintf(fp_win,
"t,x_hi_i,x_hi_v\n");
204 auto write_row = [&](
double t,
const double* data,
int n) {
206 std::fwrite(&t,
sizeof(
double), 1, fp_bin);
207 std::fwrite(data,
sizeof(
double), n, fp_bin);
211 for (
int k = 0; k < n; ++k) std::cout <<
' ' << data[k];
216 auto write_window = [&](
double t,
int x_hi_i,
int x_hi_v) {
218 std::fprintf(fp_win,
"%.17e,%d,%d\n", t, x_hi_i, x_hi_v);
224 constexpr int WINDOW_N_THRESH = 500;
225 if (P.window_mode == 4 &&
226 P.I < WINDOW_N_THRESH && P.V < WINDOW_N_THRESH) {
227 std::cerr <<
"[Window] I=" << P.I <<
" and V=" << P.V
228 <<
" both < threshold=" << WINDOW_N_THRESH
229 <<
" — using full solver.\n";
232#ifndef CD_HAVE_OPENMP
233 if (P.window_mode == 4) {
234 std::cerr <<
"[active_window] OpenMP unavailable — running serial (single thread).\n";
250 const int OMP_WORK = std::max(N_EQ, P.I + P.V);
254 char* omp_env =
nullptr;
255 size_t omp_env_len = 0;
256 _dupenv_s(&omp_env, &omp_env_len,
"OMP_NUM_THREADS");
258 const char* omp_env = std::getenv(
"OMP_NUM_THREADS");
260 if (omp_env && *omp_env) {
261 omp_threads = omp_get_max_threads();
262 std::cerr <<
"[OpenMP] OMP_NUM_THREADS=" << omp_env
263 <<
" honored — using " << omp_threads <<
" thread"
264 << (omp_threads == 1 ?
"" :
"s") <<
"\n";
266 int hw_max = omp_get_max_threads();
276 size_t len =
sizeof(perf);
277 if (sysctlbyname(
"hw.perflevel0.logicalcpu", &perf, &len,
nullptr, 0) == 0
278 && perf > 0 && perf < hw_max)
283 if (OMP_WORK < 500) picked = 1;
284 else if (OMP_WORK < 2000) picked = 2;
285 else if (OMP_WORK < 5000) picked = 4;
286 else if (OMP_WORK < 10000) picked = 8;
287 else if (OMP_WORK < 20000) picked = 12;
288 else if (OMP_WORK < 40000) picked = 16;
289 else if (OMP_WORK < 80000) picked = 20;
290 else picked = hw_max;
291 if (picked > hw_max) picked = hw_max;
292 omp_set_num_threads(picked);
293 omp_threads = picked;
294 std::cerr <<
"[OpenMP] auto-selected " << omp_threads
295 <<
" thread" << (omp_threads == 1 ?
"" :
"s")
296 <<
" for work=" << OMP_WORK
297 <<
" (N_eq=" << N_EQ <<
", I+V=" << (P.I + P.V)
298 <<
", hw_max=" << hw_max <<
")\n";
302 std::cerr <<
"[OpenMP_threads] " << omp_threads <<
"\n";
304 if (omp_env) std::free(omp_env);
307 std::cerr <<
"[OpenMP] not available — running serial (1 thread)\n";
308 std::cerr <<
"[OpenMP_threads] 1\n";
311 const char* he_kin_str = (P.he_kinetics == 1) ?
"quasi_steady_state" :
"dynamic";
312 std::cerr <<
"RadCluster_2_1 solver: N_eq=" << N_EQ
313 <<
" physics_option=" << P.physics_option
314 <<
" he_mode=" << P.he_mode
315 <<
" he_kinetics=" << he_kin_str
316 <<
" C_floor=" << P.C_floor
317 <<
" window_mode=" << P.window_mode;
318 if (P.window_mode != 0) {
319 std::cerr <<
" win_SIA=[0," << std::min(P.window_width - 1, P.I - 1) <<
"->" << (P.I - 1) <<
"]"
320 <<
" win_VAC=[0," << std::min(P.window_width - 1, P.V - 1) <<
"->" << (P.V - 1) <<
"]";
325 std::vector<double> t_eval(P.n_points);
327 double l0 = std::log10(P.t_begin), lf = std::log10(P.t_end);
328 double step = (lf - l0) / (P.n_points - 1);
329 for (
int i = 0; i < P.n_points; ++i)
330 t_eval[i] = std::pow(10.0, l0 + i * step);
332 double step = (P.t_end - P.t_begin) / (P.n_points - 1);
333 for (
int i = 0; i < P.n_points; ++i)
334 t_eval[i] = P.t_begin + i * step;
339 if (SUNContext_Create(SUN_COMM_NULL, &sunctx) != 0) {
340 std::cerr <<
"SUNContext_Create failed\n";
return 1;
344 N_Vector y = N_VNew_Serial(N_EQ, sunctx);
345 if (!y) { std::cerr <<
"N_VNew_Serial failed\n";
return 1; }
346 double* ydata = N_VGetArrayPointer_Serial(y);
347 for (
int k = 0; k < N_EQ; ++k)
348 ydata[k] = std::max(P.y0[k], P.C_floor);
353 const int i_VAC_off = (P.physics_option >= 2)
354 ? (P.i_discrete + P.n_mom * P.I_bin)
357 const int V_states = (P.physics_option >= 2)
358 ? (P.v_discrete + P.n_mom * P.V_bin)
363 if (P.physics_option >= 2)
379 void* cvode_mem = CVodeCreate(CV_BDF, sunctx);
380 if (!cvode_mem) { std::cerr <<
"CVodeCreate failed\n";
return 1; }
382 CHECK_SUNDIALS(CVodeInit(cvode_mem, rhs_fn, P.t_begin, y));
383 CHECK_SUNDIALS(CVodeSStolerances(cvode_mem, P.rtol, P.atol));
384 CHECK_SUNDIALS(CVodeSetUserData(cvode_mem, &ud));
385 CHECK_SUNDIALS(CVodeSetMaxNumSteps(cvode_mem, 500000));
387 CHECK_SUNDIALS(CVodeSetMaxOrd(cvode_mem, P.max_order));
389 CHECK_SUNDIALS(CVodeSetMinStep(cvode_mem, P.hmin));
391 CHECK_SUNDIALS(CVodeSetMaxStep(cvode_mem, P.hmax));
399 SUNMatrix sunmat =
nullptr;
400 SUNLinearSolver sunls =
nullptr;
404 constexpr int GMRES_MAXL = 50;
405 sunls = SUNLinSol_SPGMR(y, SUN_PREC_RIGHT, GMRES_MAXL, sunctx);
406 if (!sunls) { std::cerr <<
"SPGMR create failed\n";
return 1; }
407 CHECK_SUNDIALS(CVodeSetLinearSolver(cvode_mem, sunls,
nullptr));
409 std::cout <<
"[solver] preconditioner: "
410 << (P.prec_type == 1 ?
"Woodbury (bordered-arrow, rank "
411 + std::to_string(P.prec_rank)
412 +
", bw " + std::to_string(P.prec_bw) +
")"
413 :
"Jacobi (diagonal)")
415 }
else if (P.linsol == 1) {
417 int mu = (P.mu > 0) ? P.mu : N_EQ - 1;
418 int ml = (P.ml > 0) ? P.ml : N_EQ - 1;
419 sunmat = SUNBandMatrix(N_EQ, mu, ml, sunctx);
420 sunls = SUNLinSol_Band(y, sunmat, sunctx);
421 if (!sunmat || !sunls) { std::cerr <<
"Band solver create failed\n";
return 1; }
422 CHECK_SUNDIALS(CVodeSetLinearSolver(cvode_mem, sunls, sunmat));
423 }
else if (P.linsol == 3) {
427 if (P.physics_option != 0 && P.physics_option != 1) {
428 std::cerr <<
"[KLU] linsol=klu only supports full_CD modes "
429 "(physics_option 0 or 1). Use gmres for bin_moment.\n";
434 std::cerr <<
"[KLU] sparsity pattern build failed\n";
return 1;
438 std::cout <<
"[KLU] N_eq=" << N_EQ
440 <<
" density=" << std::scientific << std::setprecision(2)
441 << double(nnz) / (double(N_EQ) * double(N_EQ))
442 <<
" colors=" << P.jac_n_colors
443 <<
" (~" << P.jac_n_colors + 1
444 <<
" RHS evals per Jacobian)\n";
446 sunmat = SUNSparseMatrix(N_EQ, N_EQ, nnz, CSC_MAT, sunctx);
447 if (!sunmat) { std::cerr <<
"Sparse matrix create failed\n";
return 1; }
449 sunindextype* col_ptr = SUNSparseMatrix_IndexPointers(sunmat);
450 sunindextype* row_idx = SUNSparseMatrix_IndexValues (sunmat);
451 for (
int j = 0; j <= N_EQ; ++j) col_ptr[j] = P.jac_col_ptr[j];
452 for (
int k = 0; k < nnz; ++k) row_idx[k] = P.jac_row_idx[k];
454 sunls = SUNLinSol_KLU(y, sunmat, sunctx);
455 if (!sunls) { std::cerr <<
"KLU create failed\n";
return 1; }
456 CHECK_SUNDIALS(CVodeSetLinearSolver(cvode_mem, sunls, sunmat));
459 std::cerr <<
"[KLU] linsol=klu requested but solver was built without "
460 "KLU support (CD_HAVE_KLU not defined). Rebuild SUNDIALS "
461 "with -DENABLE_KLU=ON and link sunlinsolklu.\n";
466 sunmat = SUNDenseMatrix(N_EQ, N_EQ, sunctx);
467 sunls = SUNLinSol_Dense(y, sunmat, sunctx);
468 if (!sunmat || !sunls) { std::cerr <<
"Dense solver create failed\n";
return 1; }
469 CHECK_SUNDIALS(CVodeSetLinearSolver(cvode_mem, sunls, sunmat));
481 int x_hi_i = (P.window_mode == 0) ? P.I - 1
482 : std::min(P.window_width - 1, P.I - 1);
484 int x_hi_v = (P.window_mode == 0) ? V_states - 1
485 : std::min(P.window_width - 1, V_states - 1);
488 std::vector<double> out_row(N_EQ);
491 for (
int k = 0; k < N_EQ; ++k)
492 out_row[k] = std::max(ydata[k], 0.0);
493 write_row(P.t_begin, out_row.data(), N_EQ);
494 write_window(P.t_begin, x_hi_i, x_hi_v);
497 int check_every = std::max(P.window_check_every, 1);
498 bool was_interrupted =
false;
500 for (
int i = 1; i < P.n_points; ++i) {
501 if (g_interrupt_requested.load()) {
502 std::cerr <<
"[interrupt] received at pt=" << i <<
"/" << P.n_points
503 <<
" — finalising " << n_written
504 <<
" saved time points and exiting cleanly.\n";
505 was_interrupted =
true;
508 double t_out = t_eval[i];
511 if (P.window_mode != 0) {
512 if ((i - 1) % check_every == 0) {
516 if (x_hi_i < P.I - 1 && x_hi_i < N_EQ - 1 &&
517 ydata[x_hi_i] > P.concentration_threshold) {
518 x_hi_i = std::min(x_hi_i + P.window_pad, P.I - 1);
523 if (x_hi_v < V_states - 1) {
524 const int vac_abs = i_VAC_off + x_hi_v;
525 if (vac_abs < N_EQ && ydata[vac_abs] > P.concentration_threshold) {
526 x_hi_v = std::min(x_hi_v + P.window_pad_v, V_states - 1);
536 double t_now = P.t_begin;
537 if (i > 0) CVodeGetCurrentTime(cvode_mem, &t_now);
539 int retval = CVode(cvode_mem, t_out, y, &t_now, CV_NORMAL);
543 long int nst = 0, nfe = 0, nni = 0, ncfn = 0, netf = 0;
545 CVodeGetNumSteps(cvode_mem, &nst);
546 CVodeGetNumRhsEvals(cvode_mem, &nfe);
547 CVodeGetNumNonlinSolvIters(cvode_mem, &nni);
548 CVodeGetNumNonlinSolvConvFails(cvode_mem, &ncfn);
549 CVodeGetNumErrTestFails(cvode_mem, &netf);
550 CVodeGetLastStep(cvode_mem, &hlast);
551 std::cerr <<
"[cvode] pt=" << i <<
"/" << P.n_points
553 <<
" steps=" << nst <<
" rhs=" << nfe
554 <<
" nlcf=" << ncfn <<
" etf=" << netf
556 <<
" ret=" << retval <<
"\n";
560 std::cerr <<
"CVode failed at t=" << t_out <<
" retval=" << retval
561 <<
" — stopping integration (no reinit to preserve conservation)\n";
576 for (
int k = 0; k < N_EQ; ++k)
577 ydata[k] = std::max(ydata[k], P.C_floor);
578 for (
int k = 0; k < N_EQ; ++k)
579 out_row[k] = ydata[k];
580 write_row(t_out, out_row.data(), N_EQ);
581 write_window(t_out, x_hi_i, x_hi_v);
586 if (!P.verbose)
continue;
598 const bool is_bin = (P.physics_option >= 2);
599 const int Ib = is_bin ? P.I_bin : 0;
600 const int Kv = is_bin ? P.V_bin : 0;
601 const int PM = is_bin ? P.n_mom : 0;
602 const int i_d = is_bin ? P.i_discrete : 0;
603 const int v_d = is_bin ? P.v_discrete : 0;
604 const int i_VAC = is_bin ? (i_d + PM * Ib) : I;
605 const int i_Q_base = is_bin ? (i_VAC + v_d + PM * Kv)
609 const double c_i1 = ydata[0];
610 const double c_v1 = ydata[i_VAC];
613 double SIA_content = 0.0;
616 for (
int n = 0; n < i_d; ++n)
617 SIA_content += (n + 1.0) * ydata[n];
619 for (
int k = 0; k < Ib; ++k)
620 SIA_content += ydata[i_d + PM*k + 1];
622 for (
int n = 0; n < I; ++n)
623 SIA_content += (n + 1.0) * ydata[n];
627 double VAC_content = 0.0;
630 for (
int m = 0; m < v_d; ++m)
631 VAC_content += (m + 1.0) * ydata[i_VAC + m];
636 const int vac_mom = i_VAC + v_d;
638 for (
int k = 0; k < Kv; ++k)
639 VAC_content += ydata[vac_mom + PM*k + 1];
641 for (
int k = 0; k < Kv; ++k) {
642 int mlo = P.vac_bin_lo[k];
643 int mhi = P.vac_bin_hi[k];
644 double mid = 0.5 * (mlo + mhi - 1);
645 VAC_content += ydata[vac_mom + k] * mid;
650 for (
int m = 0; m < V; ++m)
651 VAC_content += (m + 1.0) * ydata[I + m];
655 const double c_i2 = is_bin ? ((i_d > 1) ? ydata[1] : 0.0)
656 : ((I > 1) ? ydata[1] : 0.0);
657 const double c_i5 = is_bin ? ((i_d > 4) ? ydata[4] : 0.0)
658 : ((I > 4) ? ydata[4] : 0.0);
659 const double c_v2 = is_bin ? ((v_d > 1) ? ydata[i_VAC + 1] : 0.0)
660 : ((V > 1) ? ydata[I + 1] : 0.0);
661 const double c_v5 = is_bin ? ((v_d > 4) ? ydata[i_VAC + 4] : 0.0)
662 : ((V > 4) ? ydata[I + 4] : 0.0);
665 const double Q_tot = ydata[i_Q_base];
666 const double c_h = (P.he_kinetics == 0 && N_EQ > i_Q_base + 1)
667 ? ydata[i_Q_base + 1] : -1.0;
669 std::cerr << std::scientific << std::setprecision(3)
670 <<
" [diag] t=" << t_out
677 <<
" Q_tot=" << Q_tot;
679 std::cerr <<
" c_h=" << c_h;
680 std::cerr <<
" SIA_tot=" << SIA_content
681 <<
" VAC_tot=" << VAC_content
686 if (!is_bin && I > 4) {
687 const double ci4 = std::max(ydata[3], 0.0);
688 const double ci5_ = c_i5;
689 const double ci6 = (I > 5) ? std::max(ydata[5], 0.0) : 0.0;
691 const double rate_prod = P.Pr_SIA[4];
692 const double rate_emit_gain = (I > 5) ? P.GII[5] * ci6 : 0.0;
693 const double rate_emit_loss = -P.GII[4] * ci5_;
694 const double rate_grow_gain = P.KII[3] * c_i1 * ci4;
695 const double rate_grow_loss = -P.KII[4] * c_i1 * ci5_;
696 const double rate_shrink_gain = (I > 5) ? P.KIV[5] * c_v1 * ci6 : 0.0;
697 const double rate_shrink_loss = -P.KIV[4] * c_v1 * ci5_;
699 double rate_1D_loss = 0.0;
700 if (4 < P.i_mobile && P.K_1D_pref[4] > 1e-300) {
701 for (
int m = 0; m < V; ++m) {
702 const double m_f =
static_cast<double>(m + 1);
703 const double m13 = std::cbrt(m_f);
704 const double denom = 1.0 + P.B_rot * P.L_hat * P.L_hat / m13;
705 const double k1d = P.K_1D_pref[4] * m13 / denom;
706 rate_1D_loss -= k1d * ci5_ * std::max(ydata[i_VAC + m], 0.0);
710 const double rate_sink = -P.k2_SIA[4] * ci5_;
711 const double dc_i5_total = rate_prod + rate_emit_gain + rate_emit_loss
712 + rate_grow_gain + rate_grow_loss
713 + rate_shrink_gain + rate_shrink_loss
714 + rate_1D_loss + rate_sink;
716 std::cerr << std::scientific << std::setprecision(3)
717 <<
" [ci5_rates] t=" << t_out
718 <<
" prod=" << rate_prod
719 <<
" emit_in=" << rate_emit_gain
720 <<
" emit_out=" << rate_emit_loss
721 <<
" grow_in=" << rate_grow_gain
722 <<
" grow_out=" << rate_grow_loss
723 <<
" shrink_in=" << rate_shrink_gain
724 <<
" shrink_out=" << rate_shrink_loss
725 <<
" 1D_loss=" << rate_1D_loss
726 <<
" sink=" << rate_sink
727 <<
" dc_i5=" << dc_i5_total
732 if (!is_bin && V > 4) {
733 const double cv4_ = (V > 3) ? std::max(ydata[i_VAC+3], 0.0) : 0.0;
734 const double cv5_ = c_v5;
735 const double cv6_ = (V > 5) ? std::max(ydata[i_VAC+5], 0.0) : 0.0;
737 const double rate_prod = P.Pr_VAC[4];
738 const double rate_emit_gain = (V > 5) ? P.GVV[5] * cv6_ : 0.0;
739 const double rate_emit_loss = -P.GVV[4] * cv5_;
740 const double rate_grow_gain = P.KVV[3] * c_v1 * cv4_;
741 const double rate_grow_loss = -P.KVV[4] * c_v1 * cv5_;
742 const double rate_shrink_gain = (V > 5) ? P.KVI[5] * c_i1 * cv6_ : 0.0;
743 const double rate_shrink_loss = -P.KVI[4] * c_i1 * cv5_;
745 double rate_1D_loss = 0.0;
747 const double m5f = 5.0;
748 const double m513 = std::cbrt(m5f);
749 for (
int n = 4; n < std::min(I, P.i_mobile); ++n) {
750 if (P.K_1D_pref[n] < 1e-300)
continue;
751 const double denom = 1.0 + P.B_rot * P.L_hat * P.L_hat / m513;
752 const double k1d = P.K_1D_pref[n] * m513 / denom;
753 rate_1D_loss -= k1d * std::max(ydata[n], 0.0) * cv5_;
757 const double rate_sink = -P.k2_disl_v * cv5_;
758 const double dc_v5_total = rate_prod + rate_emit_gain + rate_emit_loss
759 + rate_grow_gain + rate_grow_loss
760 + rate_shrink_gain + rate_shrink_loss
761 + rate_1D_loss + rate_sink;
763 std::cerr << std::scientific << std::setprecision(3)
764 <<
" [cv5_rates] t=" << t_out
765 <<
" prod=" << rate_prod
766 <<
" emit_in=" << rate_emit_gain
767 <<
" emit_out=" << rate_emit_loss
768 <<
" grow_in=" << rate_grow_gain
769 <<
" grow_out=" << rate_grow_loss
770 <<
" shrink_in=" << rate_shrink_gain
771 <<
" shrink_out=" << rate_shrink_loss
772 <<
" 1D_loss=" << rate_1D_loss
773 <<
" sink=" << rate_sink
774 <<
" dc_v5=" << dc_v5_total
784 long nst = 0, nfe = 0, nni = 0, ncfn = 0, netf = 0;
785 long nli = 0, npe = 0, nps = 0, nlsetup = 0;
786 CVodeGetNumSteps (cvode_mem, &nst);
787 CVodeGetNumRhsEvals (cvode_mem, &nfe);
788 CVodeGetNumNonlinSolvIters (cvode_mem, &nni);
789 CVodeGetNumNonlinSolvConvFails(cvode_mem, &ncfn);
790 CVodeGetNumErrTestFails (cvode_mem, &netf);
791 CVodeGetNumLinSolvSetups (cvode_mem, &nlsetup);
793 CVodeGetNumLinIters (cvode_mem, &nli);
794 CVodeGetNumPrecEvals(cvode_mem, &npe);
795 CVodeGetNumPrecSolves(cvode_mem, &nps);
796 const double nli_per_nni = (nni > 0) ?
double(nli) / double(nni) : 0.0;
797 std::cerr <<
"[stats] steps=" << nst
801 <<
" nli_per_nni=" << std::fixed << std::setprecision(2)
807 <<
" nlsetup=" << nlsetup
808 <<
" interrupted=" << (was_interrupted ? 1 : 0)
812 std::cerr <<
"Done: " << n_written <<
" time points written"
813 << (was_interrupted ?
" (interrupted)" :
"") <<
".\n";
816 if (fp_bin) std::fclose(fp_bin);
817 if (fp_win) std::fclose(fp_win);
818 N_VDestroy_Serial(y);
819 CVodeFree(&cvode_mem);
820 if (sunls) SUNLinSolFree(sunls);
821 if (sunmat) SUNMatDestroy(sunmat);
822 SUNContext_Free(&sunctx);