PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
particle_statistics.c
Go to the documentation of this file.
1/**
2 * @file particle_statistics.c
3 * @brief Global statistical reduction kernels for the Statistics Pipeline.
4 *
5 * Each kernel in this file:
6 * - Performs an MPI_Allreduce over all particles across all ranks.
7 * - Appends one CSV row per call to its own output file.
8 * - Logs a one-line summary via LOG_INFO on rank 0.
9 * - Produces O(1) output regardless of particle count, making these kernels
10 * the primary quantitative diagnostic path for large (100M+) particle runs.
11 *
12 * To add a new statistic:
13 * 1. Implement PetscErrorCode ComputeXxx(UserCtx*, const char*, PetscInt) here.
14 * 2. Declare it in include/particle_statistics.h.
15 * 3. Add one else-if in GlobalStatisticsPipeline() in src/postprocessor.c.
16 */
17
18#include "particle_statistics.h"
19#include <math.h>
20#include <mpi.h>
21
22#undef __FUNCT__
23#define __FUNCT__ "ComputeParticleMSD"
24/**
25 * @brief Computes the mean-squared displacement (MSD) of the particle cloud.
26 *
27 * Two-pass algorithm:
28 * Pass 1 — accumulate per-component squared displacements and centre-of-mass sums.
29 * MPI reduction — allreduce 7 values (6 sums + count) to get global MSD and COM.
30 * Compute scalars — MSD_total, r_rms_meas, r_rms_theory, rel_err_pct.
31 * Pass 2 — count particles inside 1σ/2σ/3σ theoretical shells; allreduce counts.
32 * Output (rank 0) — append one row to {stats_prefix}_msd.csv; LOG_INFO summary.
33 *
34 * Physics: D = 1/(Re·Sc), t = ti·dt, r_theory = sqrt(6·D·t).
35 * Expected fractions inside r_theory / 2·r_theory / 3·r_theory ≈ 61%/97%/99.9%
36 * for a 3D Maxwell-Boltzmann distribution.
37 */
38PetscErrorCode ComputeParticleMSD(UserCtx *user, const char *stats_prefix, PetscInt ti)
39{
40 PetscErrorCode ierr;
41 SimCtx *simCtx = user->simCtx;
42 PetscMPIInt rank;
43
44 PetscFunctionBeginUser;
46
47 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
48
49 /* ------------------------------------------------------------------ *
50 * Physics parameters *
51 * ------------------------------------------------------------------ */
52 const PetscReal Re = (simCtx->ren > 0.0) ? simCtx->ren : 1.0;
53 const PetscReal Sc = (simCtx->schmidt_number > 0.0) ? simCtx->schmidt_number : 1.0;
54 const PetscReal D = 1.0 / (Re * Sc);
55 const PetscReal t = (PetscReal)ti * simCtx->dt;
56 const PetscReal x0 = simCtx->psrc_x;
57 const PetscReal y0 = simCtx->psrc_y;
58 const PetscReal z0 = simCtx->psrc_z;
59
60 /* ------------------------------------------------------------------ *
61 * Pass 1: local accumulation *
62 * ------------------------------------------------------------------ */
63 PetscInt n_local;
64 const PetscReal (*pos_arr)[3];
65
66 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
67 ierr = DMSwarmGetField(user->swarm, "position", NULL, NULL, (const void**)&pos_arr); CHKERRQ(ierr);
68
69 PetscReal local_sq_x = 0.0, local_sq_y = 0.0, local_sq_z = 0.0;
70 PetscReal local_sx = 0.0, local_sy = 0.0, local_sz = 0.0;
71
72 for (PetscInt p = 0; p < n_local; p++) {
73 const PetscReal dx = pos_arr[p][0] - x0;
74 const PetscReal dy = pos_arr[p][1] - y0;
75 const PetscReal dz = pos_arr[p][2] - z0;
76 local_sq_x += dx * dx;
77 local_sq_y += dy * dy;
78 local_sq_z += dz * dz;
79 local_sx += dx;
80 local_sy += dy;
81 local_sz += dz;
82 }
83
84 ierr = DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (const void**)&pos_arr); CHKERRQ(ierr);
85
86 /* ------------------------------------------------------------------ *
87 * MPI reduction — 7 doubles + count *
88 * ------------------------------------------------------------------ */
89 PetscReal local_buf[7] = { local_sq_x, local_sq_y, local_sq_z,
90 local_sx, local_sy, local_sz,
91 (PetscReal)n_local };
92 PetscReal global_buf[7] = {0};
93 MPI_Allreduce(local_buf, global_buf, 7, MPI_DOUBLE, MPI_SUM, PETSC_COMM_WORLD);
94
95 const PetscReal N_total = global_buf[6];
96 if (N_total < 1.0) PetscFunctionReturn(0); /* no particles */
97
98 const PetscReal MSD_x = global_buf[0] / N_total;
99 const PetscReal MSD_y = global_buf[1] / N_total;
100 const PetscReal MSD_z = global_buf[2] / N_total;
101 const PetscReal com_x = global_buf[3] / N_total;
102 const PetscReal com_y = global_buf[4] / N_total;
103 const PetscReal com_z = global_buf[5] / N_total;
104
105 const PetscReal MSD_total = MSD_x + MSD_y + MSD_z;
106 const PetscReal r_rms_meas = PetscSqrtReal(MSD_total);
107 const PetscReal r_theory = (t > 1e-300) ? PetscSqrtReal(6.0 * D * t) : 0.0;
108 const PetscReal rel_err_pct = (r_theory > 1e-12)
109 ? PetscAbsReal(r_rms_meas - r_theory) / r_theory * 100.0
110 : 0.0;
111
112 /* ------------------------------------------------------------------ *
113 * Pass 2: fraction inside σ-shells *
114 * ------------------------------------------------------------------ */
115 ierr = DMSwarmGetField(user->swarm, "position", NULL, NULL, (const void**)&pos_arr); CHKERRQ(ierr);
116
117 PetscInt local_n1 = 0, local_n2 = 0, local_n3 = 0;
118 const PetscReal r1 = r_theory;
119 const PetscReal r2 = 2.0 * r_theory;
120 const PetscReal r3 = 3.0 * r_theory;
121
122 for (PetscInt p = 0; p < n_local; p++) {
123 const PetscReal dx = pos_arr[p][0] - x0;
124 const PetscReal dy = pos_arr[p][1] - y0;
125 const PetscReal dz = pos_arr[p][2] - z0;
126 const PetscReal r = PetscSqrtReal(dx*dx + dy*dy + dz*dz);
127 if (r < r1) local_n1++;
128 if (r < r2) local_n2++;
129 if (r < r3) local_n3++;
130 }
131
132 ierr = DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (const void**)&pos_arr); CHKERRQ(ierr);
133
134 PetscReal local_counts[3] = { (PetscReal)local_n1,
135 (PetscReal)local_n2,
136 (PetscReal)local_n3 };
137 PetscReal global_counts[3] = {0};
138 MPI_Allreduce(local_counts, global_counts, 3, MPI_DOUBLE, MPI_SUM, PETSC_COMM_WORLD);
139
140 const PetscReal frac_1s = global_counts[0] / N_total * 100.0;
141 const PetscReal frac_2s = global_counts[1] / N_total * 100.0;
142 const PetscReal frac_3s = global_counts[2] / N_total * 100.0;
143
144 /* ------------------------------------------------------------------ *
145 * Output: CSV + LOG_INFO (rank 0 only) *
146 * ------------------------------------------------------------------ */
147 if (rank == 0) {
148 char csv_path[PETSC_MAX_PATH_LEN];
149 PetscSNPrintf(csv_path, sizeof(csv_path), "%s_msd.csv", stats_prefix);
150
151 FILE *f = fopen(csv_path, "a");
152 if (!f) {
154 "ComputeParticleMSD: could not open '%s' for writing.\n", csv_path);
155 } else {
156 /* Write header if file is empty */
157 if (ftell(f) == 0) {
158 fprintf(f, "step,t,N,MSD_x,MSD_y,MSD_z,MSD_total,"
159 "r_rms_meas,r_rms_theory,rel_err_pct,"
160 "com_x,com_y,com_z,"
161 "frac_1sigma_pct,frac_2sigma_pct,frac_3sigma_pct\n");
162 }
163 fprintf(f, "%d,%.6e,%.0f,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.4f,%.6e,%.6e,%.6e,%.2f,%.2f,%.2f\n",
164 (int)ti, t, N_total,
165 MSD_x, MSD_y, MSD_z, MSD_total,
166 r_rms_meas, r_theory, rel_err_pct,
167 com_x, com_y, com_z,
168 frac_1s, frac_2s, frac_3s);
169 fclose(f);
170 }
171
173 "[MSD ti=%d t=%.4f] total=%.4e theory=%.4e err=%.2f%% | "
174 "fracs: %.1f/%.1f/%.1f%% | COM: (%.2e,%.2e,%.2e)\n",
175 (int)ti, t, MSD_total, r_theory * r_theory, rel_err_pct,
176 frac_1s, frac_2s, frac_3s,
177 com_x, com_y, com_z);
178 }
179
181 PetscFunctionReturn(0);
182}
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:746
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:29
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:737
PetscErrorCode ComputeParticleMSD(UserCtx *user, const char *stats_prefix, PetscInt ti)
Computes the mean-squared displacement (MSD) of the particle cloud.
Global statistics kernels for the Statistics Pipeline.
PetscReal schmidt_number
Definition variables.h:650
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:735
PetscReal psrc_x
Definition variables.h:647
PetscReal ren
Definition variables.h:636
PetscReal dt
Definition variables.h:603
PetscReal psrc_z
Point source location for PARTICLE_INIT_POINT_SOURCE.
Definition variables.h:647
PetscReal psrc_y
Definition variables.h:647
The master context for the entire simulation.
Definition variables.h:589
User-defined context containing data specific to a single computational grid level.
Definition variables.h:732