CPP-snippets 0.0.1
A silly C++ project to use for demonstrating code integration
Loading...
Searching...
No Matches
particles.cpp
1#include "particles.hpp"
2#include "constants.hpp"
3
4#include <Eigen/Core>
5#include <cmath>
6#include <iostream>
7#include <random>
8
9// Constructors
10// -----------------------------------------------------------------------------
11Particles::Particles(int N, double q_in, double m_in, double w_in)
12 : m_positions(N), m_vel_half(N), m_pos_prev(N),
13 m_charge(q_in), m_mass(m_in), m_weight(w_in) { }
14
15// -----------------------------------------------------------------------------
16// Resize positions + velocities
17// -----------------------------------------------------------------------------
18void Particles::resize(int N) {
19 m_pos_prev.resize(N);
20 m_positions.resize(N);
21 m_vel_half.resize(N);
22}
23
24int Particles::find_cell_index(const Eigen::VectorXd& node_pos, double x) const {
25 // Check if x is inside the domain
26 if (x < node_pos[0] || x > node_pos[node_pos.size() - 1]) {
27 throw std::out_of_range("Particle position " + std::to_string(x) +
28 " is outside the domain [" +
29 std::to_string(node_pos[0]) + ", " +
30 std::to_string(node_pos[node_pos.size()-1]) + "]");
31 }
32
33 // Find the index of the cell containing x
34 // Assumes node_pos is sorted ascending
35 for (int i = 0; i < node_pos.size() - 1; ++i) {
36 if (x >= node_pos[i] && x < node_pos[i + 1]) {
37 return i;
38 }
39 }
40
41 // Special case: x == last node
42 return node_pos.size() - 2;
43}
44
45double Particles::interp_linear(const Eigen::VectorXd& x_nodes, const Eigen::VectorXd& val_nodes, double x) {
46 const int N = x_nodes.size();
47
48 // --- bounds check (clamp) ---
49 if (x <= x_nodes[0]) return val_nodes[0];
50 if (x >= x_nodes[N - 1]) return val_nodes[N - 1];
51
52 // --- locate cell via binary search ---
53 auto it = std::lower_bound(x_nodes.data(), x_nodes.data() + N, x);
54 int i = std::max(0, int(it - x_nodes.data() - 1));
55
56 // --- linear interpolation ---
57 double x0 = x_nodes[i];
58 double x1 = x_nodes[i+1];
59 double E0 = val_nodes[i];
60 double E1 = val_nodes[i+1];
61
62 double t = (x - x0) / (x1 - x0);
63 return E0 + t * (E1 - E0);
64}
65
66
67// -----------------------------------------------------------------------------
68// Maxwellian velocity initialization
69// T in Joules (k_B*T) or use T[eV] * e
70// -----------------------------------------------------------------------------
72 int N, double xpos, double dt, double energy_eV,
73 const Eigen::VectorXd& node_pos, const Eigen::VectorXd& efield, std::mt19937& rng) {
74
75 std::cout << " + Emmiting " << N << " new particles" << std::endl;
76 int n_nodes = node_pos.size();
77 int old_N = m_positions.size();
78 int new_N = old_N + N;
79
80 m_pos_prev.conservativeResize(new_N);
81 m_positions.conservativeResize(new_N);
82 m_vel_half.conservativeResize(new_N);
83
84 double kT = energy_eV * constants::q_e;
85 double sigma_v = std::sqrt(kT / m_mass);
86
87 double t_emit, v_emit = 0;
88 double a = (m_charge * efield[0]) / m_mass;
89 bool overshoot = false;
90
91 // IMPORTANT: RNG must be thread-local
92 std::normal_distribution<double> normal(0.0, sigma_v); // Maxwellian velocity
93 std::uniform_real_distribution<double> uniform(0.0, dt); // Uniform emission time
94
95 #pragma omp parallel
96 {
97 std::mt19937 local_rng(rng()); // copy engine per thread
98
99 #pragma omp for
100 for (int i = 0; i < N; i++) {
101 int idx = old_N + i;
102
103 t_emit = uniform(local_rng); // relative time of emission inside timestep
104 v_emit = std::fabs(normal(local_rng)); // thermionic emission speed
105 double p_pos = xpos;
106 double p_vel = v_emit;
107
108 // initial values
109 m_pos_prev[idx] = xpos;
110 m_positions[idx] = xpos;
111 m_vel_half[idx] = v_emit;
112
113 // preadvance position and velocity in efield solution with substepping
114 double dt_sub = dt / std::ceil(n_nodes/2);
115 double local_a = 0;
116 for (int j = 0; j < std::ceil(n_nodes/2); j++) {
117 local_a = (m_charge * this->interp_linear(node_pos, efield, p_pos)) / m_mass;
118 m_vel_half[idx] += local_a * dt_sub;
119 m_positions[idx] += m_vel_half[idx] * dt_sub;
120
121 }
122 }
123 }
124}
125
126// -----------------------------------------------------------------------------
127// Particle push (simple leapfrog / Euler method)
128// E_part has the electric field interpolated TO particle positions
129// -----------------------------------------------------------------------------
131 double dt, const Eigen::VectorXd& node_pos, const Eigen::VectorXd& efield) {
132
133 int Np = m_positions.size();
134 m_pos_prev = m_positions; // store previous positions for diagnostics
135
136 #pragma omp parallel for
137 for (int i = 0; i < Np; i++) {
138 // 1. Interpolate electric field at particle position
139 double local_efield = interp_linear(node_pos, efield, m_positions[i]);
140
141 // 2. Compute acceleration
142 double local_a = m_charge * local_efield / m_mass;
143
144 // 3. Update half-step velocity
145 m_vel_half[i] += local_a * dt;
146
147 // 4. Update position
148 m_positions[i] += m_vel_half[i] * dt;
149 }
150
151}
152
153Eigen::Vector4d Particles::fluxes(const Eigen::Vector4d& el_pos, std::mt19937& rng, double p_ce, double p_ag) {
154
155 int N = m_positions.size();
156 int initial_size = N;
157
158 int fil_particles = 0;
159 int ce_particles = 0;
160 int ag_particles = 0;
161 int ic_particles = 0;
162
163 int i = 0;
164 while (i < N) {
165
166 bool removed = false;
167
168 if (m_positions[i] < el_pos[0]) {
169 // --- Particle hit filament ---
170 fil_particles++;
171 removed = true;
172 }
173 else if (m_positions[i] > el_pos.tail(1).value()) {
174 // --- Particle hit ion collector ---
175 ic_particles++;
176 removed = true;
177 }
178 else if ((m_pos_prev[i] - el_pos[1]) * (m_positions[i] - el_pos[1]) <= 0.0) {
179 if (std::generate_canonical<double, 53>(rng) < p_ce) {
180 ce_particles++;
181 removed = true;
182 }
183 }
184 else if ((m_pos_prev[i] - el_pos[2]) * (m_positions[i] - el_pos[2]) <= 0.0) {
185 // --- Particle crossed acceleration grid ---
186 if (std::generate_canonical<double, 53>(rng) < p_ag) {
187 ag_particles++;
188 removed = true;
189 }
190 }
191
192 if (removed) {
193 // Swap-and-pop to remove particle efficiently
194 int last = N - 1;
195 m_positions[i] = m_positions[last];
196 m_pos_prev[i] = m_pos_prev[last];
197 m_vel_half[i] = m_vel_half[last];
198 --N; // reduce particle count
199 }
200 else {
201 ++i;
202 }
203 }
204
205 // Resize vectors to remove absorbed particles
206 m_positions.conservativeResize(N);
207 m_pos_prev.conservativeResize(N);
208 m_vel_half.conservativeResize(N);
209
210 Eigen::Vector4d particle_fluxes(fil_particles, ce_particles, ag_particles, ic_particles);
211
212 std::cout << " + removed " << initial_size - N << " particles " << std::endl;
213 return particle_fluxes * m_charge * m_weight;
214}
215
216Eigen::VectorXd Particles::deposit_charge(const Eigen::VectorXd& node_pos,
217 const Eigen::VectorXd& dx) const {
218 std::cout << " + depositing particle charges (OMP)" << std::endl;
219
220 int Nnodes = node_pos.size();
221 int Np = m_positions.size();
222
223 Eigen::VectorXd rho = Eigen::VectorXd::Zero(Nnodes);
224
225 double qsp = m_charge * m_weight;
226
227 #pragma omp parallel
228 {
229 // Thread-local accumulator
230 Eigen::VectorXd local_rho = Eigen::VectorXd::Zero(Nnodes);
231
232 #pragma omp for nowait
233 for (int p = 0; p < Np; p++) {
234
235 double xpos = m_positions[p];
236 int j = find_cell_index(node_pos, xpos);
237
238 double xL = node_pos[j];
239 double xR = node_pos[j+1];
240
241 double wL = (xR - xpos) / dx[j];
242 double wR = (xpos - xL) / dx[j];
243
244 local_rho[j] += qsp * wL;
245 local_rho[j + 1] += qsp * wR;
246 }
247
248 // reduction (atomic add)
249 #pragma omp critical
250 {
251 rho += local_rho;
252 }
253 }
254
255 // Convert charge to density
256 #pragma omp parallel for
257 for (int j = 0; j < Nnodes - 1; j++) {
258 if (dx[j] > 0) {
259 rho[j] /= dx[j];
260 rho[j + 1] /= dx[j];
261 }
262 }
263
264 return rho;
265}
266
267Eigen::VectorXd Particles::get_kinetic_energies() const {
268 // kinetic E = 0.5 * m * v^2 / e (eV) for physical particle
269 Eigen::VectorXd energies = 0.5 * m_mass * (m_vel_half.array().square()) / constants::q_e;
270 std::cout << " max v_p " << m_vel_half.maxCoeff() / constants::c << " (fraction of c0)" << std::endl;
271 std::cout << " max E_kin " << energies.maxCoeff() << " (eV)" << std::endl;
272 return energies;
273}
274
275Eigen::VectorXd Particles::energy_distribution(int n_bins, double W_max) const {
276 Eigen::VectorXd energies = get_kinetic_energies();
277
278 Eigen::VectorXd hist = Eigen::VectorXd::Zero(n_bins);
279 double dW = W_max / n_bins;
280
281 for (int i = 0; i < energies.size(); ++i) {
282 int bin = std::min(static_cast<int>(energies[i] / dW), n_bins - 1);
283 hist(bin) += m_weight; // scale by superparticle weight
284 }
285
286 // normalize if desired (number of particles per unit energy)
287 // hist /= dW;
288 return hist;
289}
Particles()=default
Default constructor, creates an empty particle set.
Eigen::Vector4d fluxes(const Eigen::Vector4d &el_pos, std::mt19937 &rng, double p_ce, double p_ag)
Compute absorbed particle fluxes on four electrodes.
Eigen::VectorXd deposit_charge(const Eigen::VectorXd &node_pos, const Eigen::VectorXd &dx) const
Deposit particle charge onto grid nodes (CIC scheme).
double m_weight
Superparticle statistical weight.
Definition particles.hpp:41
void emit(int N, double xpos, double dt, double energy_eV, const Eigen::VectorXd &node_pos, const Eigen::VectorXd &efield, std::mt19937 &rng)
Emit N particles at position xpos with Maxwellian velocity distribution.
Definition particles.cpp:71
void resize(int N)
Resize particle arrays to contain N superparticles.
Definition particles.cpp:18
Eigen::VectorXd m_positions
Particle positions (1D).
Definition particles.hpp:26
void push_leapfrog(double dt, const Eigen::VectorXd &node_pos, const Eigen::VectorXd &efield)
Advance particles using leapfrog (velocity half-step scheme).
double m_mass
Mass of a single physical particle (kg).
Definition particles.hpp:38
double m_charge
Charge of a single physical particle (C).
Definition particles.hpp:35
Eigen::VectorXd m_pos_prev
Previous positions for boundary‐crossing detection.
Definition particles.hpp:29
Eigen::VectorXd m_vel_half
Half-step velocities for leapfrog integration.
Definition particles.hpp:32