1#include "particles.hpp"
2#include "constants.hpp"
24int Particles::find_cell_index(
const Eigen::VectorXd& node_pos,
double x)
const {
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]) +
"]");
35 for (
int i = 0; i < node_pos.size() - 1; ++i) {
36 if (x >= node_pos[i] && x < node_pos[i + 1]) {
42 return node_pos.size() - 2;
45double Particles::interp_linear(
const Eigen::VectorXd& x_nodes,
const Eigen::VectorXd& val_nodes,
double x) {
46 const int N = x_nodes.size();
49 if (x <= x_nodes[0])
return val_nodes[0];
50 if (x >= x_nodes[N - 1])
return val_nodes[N - 1];
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));
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];
62 double t = (x - x0) / (x1 - x0);
63 return E0 + t * (E1 - E0);
72 int N,
double xpos,
double dt,
double energy_eV,
73 const Eigen::VectorXd& node_pos,
const Eigen::VectorXd& efield, std::mt19937& rng) {
75 std::cout <<
" + Emmiting " << N <<
" new particles" << std::endl;
76 int n_nodes = node_pos.size();
78 int new_N = old_N + N;
84 double kT = energy_eV * constants::q_e;
85 double sigma_v = std::sqrt(kT /
m_mass);
87 double t_emit, v_emit = 0;
89 bool overshoot =
false;
92 std::normal_distribution<double> normal(0.0, sigma_v);
93 std::uniform_real_distribution<double> uniform(0.0, dt);
97 std::mt19937 local_rng(rng());
100 for (
int i = 0; i < N; i++) {
103 t_emit = uniform(local_rng);
104 v_emit = std::fabs(normal(local_rng));
106 double p_vel = v_emit;
114 double dt_sub = dt / std::ceil(n_nodes/2);
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;
131 double dt,
const Eigen::VectorXd& node_pos,
const Eigen::VectorXd& efield) {
136 #pragma omp parallel for
137 for (
int i = 0; i < Np; i++) {
139 double local_efield = interp_linear(node_pos, efield,
m_positions[i]);
153Eigen::Vector4d
Particles::fluxes(
const Eigen::Vector4d& el_pos, std::mt19937& rng,
double p_ce,
double p_ag) {
156 int initial_size = N;
158 int fil_particles = 0;
159 int ce_particles = 0;
160 int ag_particles = 0;
161 int ic_particles = 0;
166 bool removed =
false;
173 else if (
m_positions[i] > el_pos.tail(1).value()) {
179 if (std::generate_canonical<double, 53>(rng) < p_ce) {
186 if (std::generate_canonical<double, 53>(rng) < p_ag) {
210 Eigen::Vector4d particle_fluxes(fil_particles, ce_particles, ag_particles, ic_particles);
212 std::cout <<
" + removed " << initial_size - N <<
" particles " << std::endl;
217 const Eigen::VectorXd& dx)
const {
218 std::cout <<
" + depositing particle charges (OMP)" << std::endl;
220 int Nnodes = node_pos.size();
223 Eigen::VectorXd rho = Eigen::VectorXd::Zero(Nnodes);
230 Eigen::VectorXd local_rho = Eigen::VectorXd::Zero(Nnodes);
232 #pragma omp for nowait
233 for (
int p = 0; p < Np; p++) {
236 int j = find_cell_index(node_pos, xpos);
238 double xL = node_pos[j];
239 double xR = node_pos[j+1];
241 double wL = (xR - xpos) / dx[j];
242 double wR = (xpos - xL) / dx[j];
244 local_rho[j] += qsp * wL;
245 local_rho[j + 1] += qsp * wR;
256 #pragma omp parallel for
257 for (
int j = 0; j < Nnodes - 1; j++) {
267Eigen::VectorXd Particles::get_kinetic_energies()
const {
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;
275Eigen::VectorXd Particles::energy_distribution(
int n_bins,
double W_max)
const {
276 Eigen::VectorXd energies = get_kinetic_energies();
278 Eigen::VectorXd hist = Eigen::VectorXd::Zero(n_bins);
279 double dW = W_max / n_bins;
281 for (
int i = 0; i < energies.size(); ++i) {
282 int bin = std::min(
static_cast<int>(energies[i] / dW), n_bins - 1);
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.
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.
void resize(int N)
Resize particle arrays to contain N superparticles.
Eigen::VectorXd m_positions
Particle positions (1D).
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).
double m_charge
Charge of a single physical particle (C).
Eigen::VectorXd m_pos_prev
Previous positions for boundaryâcrossing detection.
Eigen::VectorXd m_vel_half
Half-step velocities for leapfrog integration.