Advertisement
phystota

e-n collisions(v5) - electrons starts with maxwellian

Mar 31st, 2025
525
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 16.38 KB | None | 0 0
  1. #include <iostream>
  2. #include <random>
  3. #include <fstream>
  4.  
  5. #include <math.h>
  6. #include <time.h>
  7. #include <iomanip>  // For std::fixed and std::setprecision
  8.  
  9. #include <algorithm>  // For std::shuffle
  10. #include <numeric>    // For std::iota
  11.  
  12.  
  13. #define n_e 100000
  14. #define Emin 0.0
  15. #define Emax 400.0
  16. #define E_mean 100 // mean electron energy, initial distribution
  17. #define bin_width 0.01
  18. #define m_e 9.1E-31 // electron mass in kg
  19. #define k_b 1.38E-23 // Boltzmann constant
  20. #define q 1.602176634E-19 // elementary charge    - eV -> J transfer param
  21. #define N ( (int)((Emax-Emin)/bin_width) ) // add 1 to include E_max if needed?
  22. #define T_n 10.0 // Helium neutral temperature in eV
  23. #define T_e 50.0    // electron Maxwell initial distribution
  24. #define M_n 6.6464731E-31 // Helium atom mass
  25. #define N_He 1000000 // Helium neutrals number
  26. #define Volume 1.0E-12 // Volume to calculate netral density and collision frequency
  27. #define time 1.0E-3 // 500 microsec time to equalibrate the system
  28.  
  29. struct Electron {
  30.  
  31.     //velocity components
  32.     double vx = 0.0;
  33.     double vy = 0.0;
  34.     double vz = 0.0;
  35.     //energy in eV
  36.     double energy = 0.0;
  37.     //Collision flag
  38.     bool collided = false;
  39.  
  40.     //initialization function // void func(Type0& t) → means the function expects a reference to variable t of type0
  41.     // void initialize(std::mt19937& gen, std::uniform_real_distribution<double>& dis) {
  42.     //     // velocity angles in spherical coordinates
  43.     //     double phi = 2*M_PI*dis(gen);
  44.     //     double cosTheta = 2.0*dis(gen) - 1.0;
  45.     //     double sinTheta = sqrt(1.0 - cosTheta*cosTheta);
  46.        
  47.     //     energy = E_mean*dis(gen);
  48.        
  49.     //     double speed = sqrt(2*energy*q/m_e);
  50.  
  51.     //     vx = speed * sinTheta * cos(phi);
  52.     //     vy = speed * sinTheta * sin(phi);
  53.     //     vz = speed * cosTheta;
  54.     // }
  55.  
  56.     // initializing Maxwell-Boltzmann distribution with T_e
  57.     void initialize(std::mt19937& gen, std::uniform_real_distribution<double>& dis, std::gamma_distribution<double>& maxwell) {
  58.  
  59.         double R = dis(gen);
  60.  
  61.         // velocity angles in spherical coordinates
  62.         double phi = 2*M_PI*dis(gen);
  63.         double cosTheta = 2.0*dis(gen) - 1.0;
  64.         double sinTheta = sqrt(1.0 - cosTheta*cosTheta);
  65.  
  66.            
  67.         energy = maxwell(gen); // neutrals energies sampled as Maxwell distribution in eV
  68.            
  69.         double speed = sqrt(2*energy*q/m_e);
  70.  
  71.         //velocity components of neutrals in m/s
  72.         vx = speed * sinTheta * cos(phi);
  73.         vy = speed * sinTheta * sin(phi);
  74.         vz = speed * cosTheta;
  75.     }
  76.  
  77.  
  78. };
  79.  
  80.  
  81. struct CrossSection {
  82.     double energy;
  83.     double sigma;
  84. };
  85.  
  86. double interpolate (double energy, const std::vector<CrossSection>& elastic_CS) {
  87.  
  88.  
  89.     if (energy < elastic_CS.front().energy) {
  90.         std::cout << " required energy value lower than range of cross-section data" << "\n";
  91.         return 0.0;
  92.     }
  93.     if (energy > elastic_CS.back().energy) {
  94.         std::cout << " required energy value higher than range of cross-section data" << "\n";
  95.         return 0.0;        
  96.     }
  97.  
  98.     int step = 0;  
  99.         while (step < elastic_CS.size() && energy > elastic_CS[step].energy) {
  100.             step++;
  101.         }
  102.  
  103.     double k = (elastic_CS[step].sigma - elastic_CS[step-1].sigma)/(elastic_CS[step].energy - elastic_CS[step-1].energy);
  104.     double m = elastic_CS[step].sigma - k*elastic_CS[step].energy;
  105.    
  106.     return k*energy + m;
  107. }
  108.  
  109.  
  110. struct NeutralParticle {
  111.  
  112.     double energy = 0.0;
  113.     double vx = 0.0;
  114.     double vy = 0.0;
  115.     double vz = 0.0;
  116.  
  117.     void initialize(std::mt19937& gen, std::uniform_real_distribution<double>& dis, std::gamma_distribution<double>& maxwell) {
  118.  
  119.         double R = dis(gen);
  120.  
  121.         // velocity angles in spherical coordinates
  122.         double phi = 2*M_PI*dis(gen);
  123.         double cosTheta = 2.0*dis(gen) - 1.0;
  124.         double sinTheta = sqrt(1.0 - cosTheta*cosTheta);
  125.  
  126.            
  127.         energy = maxwell(gen); // neutrals energies sampled as Maxwell distribution in eV
  128.            
  129.         double speed = sqrt(2*energy*q/M_n);
  130.  
  131.         //velocity components of neutrals in m/s
  132.         vx = speed * sinTheta * cos(phi);
  133.         vy = speed * sinTheta * sin(phi);
  134.         vz = speed * cosTheta;
  135.     }
  136.    
  137. };
  138.  
  139.  
  140.  
  141.  
  142. int main() {
  143.  
  144.     clock_t start = clock();
  145.  
  146.     std::vector<Electron> electrons(n_e); // better to use vector instead of simple array as it's dynamically allocated (beneficial for ionization)
  147. //    std::vector<double> neutrals(N_He); // vector for neutrals
  148.     std::vector<NeutralParticle> neutrals(N_He);
  149.  
  150.  
  151.     std::vector<int> histo_random(N, 0); // initialize N size zero-vector for random (initial) histogram
  152.     std::vector<int> histo_maxwell(N, 0); // initialize N size zero-vector for maxwellian histogram
  153.     std::vector<int> histo_neutral(N, 0); // initialize N size zero-vector for neutral distribution histogram
  154.  
  155.     std::vector<double> elastic(N, 0); // precompiled cross-section energy vector
  156.  
  157.     std::random_device rd;
  158.     std::mt19937 gen(rd());
  159.     std::uniform_real_distribution<double> dis(0.0, 1.0);
  160.     std::gamma_distribution<double> maxwell_neutral(1.5, T_n);
  161.     std::gamma_distribution<double> maxwell_electron(1.5, T_e);
  162.  
  163.     std::uniform_int_distribution<int> pair(0, n_e-1);
  164.     std::uniform_int_distribution<int> neutral_pair(0, N_He-1);    
  165.  
  166.  
  167.     std::ifstream elastic_cs("cross_sections/elastic.dat");
  168.     if (!elastic_cs.is_open()) {
  169.         std::cerr << "Error opening file!" << std::endl;
  170.         return 1;
  171.     }    
  172.  
  173.     // reading elastic cross section datafile
  174.  
  175.     std::vector<CrossSection> elastic_CS;
  176.  
  177.     double energy, sigma;
  178.  
  179.     while (elastic_cs >> energy >> sigma) {
  180.         elastic_CS.push_back({energy, sigma});
  181.     }    
  182.  
  183.     elastic_cs.close();
  184.  
  185.  
  186.     std::ofstream file0("velocities.dat");    
  187.     if (!file0.is_open()) {
  188.         std::cerr << "Error opening file!" << std::endl;
  189.         return 1;
  190.     }
  191.  
  192.     std::ofstream file1("energies.dat");    
  193.     if (!file1.is_open()) {
  194.         std::cerr << "Error opening file!" << std::endl;
  195.         return 1;
  196.     }
  197.    
  198.     std::ofstream file2("energies_final.dat");    
  199.     if (!file2.is_open()) {
  200.         std::cerr << "Error opening file!" << std::endl;
  201.         return 1;
  202.     }
  203.  
  204.     std::ofstream file3("histo_random.dat");    
  205.     if (!file3.is_open()) {
  206.         std::cerr << "Error opening file!" << std::endl;
  207.         return 1;
  208.     }
  209.     file3 << std::fixed << std::setprecision(10);
  210.    
  211.     std::ofstream file4("histo_maxwell.dat");    
  212.     if (!file4.is_open()) {
  213.         std::cerr << "Error opening file!" << std::endl;
  214.         return 1;
  215.     }
  216.     file4 << std::fixed << std::setprecision(10);          
  217.    
  218.     std::ofstream file5("neutral_distribution.dat");    
  219.     if (!file5.is_open()) {
  220.         std::cerr << "Error opening file!" << std::endl;
  221.         return 1;
  222.     }
  223.  
  224.     std::ofstream file6("E*f(E).dat");    
  225.     if (!file6.is_open()) {
  226.         std::cerr << "Error opening file!" << std::endl;
  227.         return 1;
  228.     }
  229.  
  230.     std::ofstream file7("nu_max.dat");
  231.     if (!file7.is_open()) {
  232.         std::cerr << "Error opening file7!" << std::endl;
  233.         return 1;
  234.     }
  235.  
  236.     // Initialize all electrons
  237.     for (auto& e : electrons) {
  238. //        e.initialize(gen, dis);
  239.         e.initialize(gen, dis, maxwell_electron);
  240.     }
  241.     // initialize all nenutrals
  242.     for (auto&n : neutrals) {
  243.         n.initialize(gen, dis, maxwell_neutral);
  244.     }
  245.     // precalculate cross-sections for each energy bin
  246.     for (int i = 0; i < N; i++){
  247.         elastic[i] = interpolate(bin_width*(i+0.5), elastic_CS);
  248.     }
  249.  
  250.  
  251.     for (int i = 0; i < n_e; i++){
  252.         file1 << i << " " << electrons.at(i).energy << "\n";
  253.         file0 << i << " " << electrons[i].vx << " " << electrons[i].vy << " " << electrons[i].vz << "\n";
  254.     }
  255.  
  256.  
  257.     for (int i = 0; i < n_e; i++){
  258.         int bin = (int)( (electrons[i].energy - Emin)/bin_width );
  259.         if (bin >=0 && bin < histo_random.size())
  260.             histo_random[bin]++;
  261.     }
  262.  
  263.     for (int i = 0; i < histo_random.size(); i++){
  264.         double bin_start = Emin + i*bin_width;
  265.         file3 << i*bin_width << " " <<  (i*bin_width)*static_cast<double>(histo_random[i])/(electrons.size()*bin_width) << "\n"; // dividing by n_e to get normalized distribution
  266.     }
  267.  
  268.  
  269.     for (int i = 0; i < N_He; i++){
  270.         int bin = (int)( (neutrals[i].energy - Emin)/bin_width );
  271.         if (bin >=0 && bin < histo_neutral.size())
  272.             histo_neutral[bin]++;
  273.     }    
  274.  
  275.     for (int i = 0; i < histo_neutral.size(); i++){
  276.         double bin_start = Emin + i*bin_width;
  277.         file5 << i*bin_width << " " << static_cast<double>(histo_neutral[i])/(neutrals.size()*bin_width) << "\n"; // this is real f(E) - normalized distribution
  278.         file6 << i*bin_width << " " << (i*bin_width)*static_cast<double>(histo_neutral[i])/(neutrals.size()*bin_width) << "\n"; // this should be E*f(E)
  279.  
  280.     }  
  281.  
  282.     double nu_max = 0.0;
  283.     double nu_max_temp = 0.0;
  284.  
  285.     for (int i = 0; i < N; i++){
  286.         nu_max_temp = (N_He/Volume)*elastic[i] * sqrt(2.0*(i*bin_width + bin_width/2.0)*q/m_e);
  287.         file7 << i << " " << nu_max_temp << "\n";
  288.         if (nu_max_temp > nu_max)
  289.             nu_max = nu_max_temp;
  290.     }
  291.  
  292.     std::cout << nu_max << "\n";
  293.  
  294.     double dt = 0.1/nu_max;   // minimum should be 0.1/nu_max to get acceptable numerical error range see Vahedi Surrendra 1995
  295.     double steps = static_cast<int>(time/dt);
  296.  
  297. //    std::cout << steps << "\n";
  298.  
  299.     //using  null-collision technique, getting the number of particles colliding each step: P_collision = 1 - exp(-nu_max*dt)
  300.     int Ne_collided = (1.0-exp(-1.0*dt*nu_max))*n_e;
  301. //    int Ne_collided = n_e*0.98;  // in case I want to check smth
  302.  
  303.  
  304.     // Generate shuffled list of electron indices
  305.     std::vector<int> electron_indices(n_e);
  306.     std::iota(electron_indices.begin(), electron_indices.end(), 0); // fill with index
  307.     std::shuffle(electron_indices.begin(), electron_indices.end(), gen); // shuffle the indexes    
  308.     int reshuffle_interval = 1;
  309.     int print_interval = 100;
  310.  
  311.     for (int t = 0; t < steps; t++){
  312.         std::cout << "timestep remains: " << steps - t << "\n";
  313.  
  314.         //reshuffle the indices
  315.         if (t % reshuffle_interval == 0) {
  316.             std::shuffle(electron_indices.begin(), electron_indices.end(), gen);
  317.         }
  318.  
  319.         // setting flags to false each timestep
  320.         for (auto& e : electrons) e.collided = false;
  321.  
  322.         int collision_counter = 0;
  323.  
  324.  
  325.         for (int idx : electron_indices) {
  326.  
  327.             if (collision_counter >= Ne_collided) break; // quit if reached all collisions
  328.  
  329.             Electron& e = electrons[idx];
  330.             if (e.collided) continue;  // Skip already collided electrons
  331.  
  332.             double electron_energy = e.energy;
  333.             int bin_energy = static_cast<int>(electron_energy / bin_width);
  334.             double nu_elastic = (N_He/Volume) * elastic[bin_energy] * sqrt(2.0*electron_energy*q/m_e);
  335.  
  336.             if (dis(gen) < nu_elastic/nu_max) {
  337.  
  338.                 // ----   Collision energy redistribution module
  339.  
  340.                 // electron particle X Y Z initial velocities and energy
  341.                 double V0_x_1 = e.vx;
  342.                 double V0_y_1 = e.vy;
  343.                 double V0_z_1 = e.vz;
  344.  
  345.                 // neutral particle X Y Z initial velocities
  346.  
  347.                 // int k = neutral_pair(gen);
  348.  
  349.                 // double V0_x_2 = neutrals[k].vx;
  350.                 // double V0_y_2 = neutrals[k].vy;
  351.                 // double V0_z_2 = neutrals[k].vz;
  352.  
  353.                 NeutralParticle tmp_neutral;
  354.                 tmp_neutral.initialize(gen, dis, maxwell_neutral);
  355.                 double V0_x_2 = tmp_neutral.vx;
  356.                 double V0_y_2 = tmp_neutral.vy;
  357.                 double V0_z_2 = tmp_neutral.vz;
  358.  
  359.                 // initial relative velocity X Y Z (must be equal to final relative velocity in center-of-mass frame)
  360.  
  361.                 double V0_rel_x = (V0_x_1 - V0_x_2);
  362.                 double V0_rel_y = (V0_y_1 - V0_y_2);
  363.                 double V0_rel_z = (V0_z_1 - V0_z_2);
  364.  
  365.                 double V0_rel = sqrt(V0_rel_x*V0_rel_x + V0_rel_y*V0_rel_y + V0_rel_z*V0_rel_z);
  366.  
  367.                 // center-of-mass frame initial velocity (magnitude of it must be equal to the counterpart in this frame)
  368.  
  369.                 double V_cm_x = (m_e*V0_x_1 + M_n*V0_x_2)/(m_e + M_n);
  370.                 double V_cm_y = (m_e*V0_y_1 + M_n*V0_y_2)/(m_e + M_n);
  371.                 double V_cm_z = (m_e*V0_z_1 + M_n*V0_z_2)/(m_e + M_n);                    
  372.  
  373.                 // generating random variables to calculate random direction of center-of-mass after the collision
  374.  
  375.                 double R1 = dis(gen);
  376.                 double R2 = dis(gen);
  377.  
  378.                 // calculating spherical angles for center-of-mass random direction
  379.                 double theta = acos(1.0- 2.0*R1);
  380.                 double phi = 2*M_PI*R2;
  381.  
  382.                 //calculating final relative velocity with random direction
  383.  
  384.                 double V_rel_x = V0_rel*sin(theta)*cos(phi);
  385.                 double V_rel_y = V0_rel*sin(theta)*sin(phi);
  386.                 double V_rel_z = V0_rel*cos(theta);
  387.  
  388.                 double V_rel = sqrt(V_rel_x*V_rel_x + V_rel_y*V_rel_y + V_rel_z*V_rel_z);
  389.  
  390.                 //calculating final velocity of electron
  391.  
  392.                 double V_x_1 = V_cm_x + V_rel_x * (M_n/(m_e + M_n));
  393.                 double V_y_1 = V_cm_y + V_rel_y * (M_n/(m_e + M_n));
  394.                 double V_z_1 = V_cm_z + V_rel_z * (M_n/(m_e + M_n));
  395.  
  396.                 double V_1 = sqrt(V_x_1*V_x_1 + V_y_1*V_y_1 + V_z_1*V_z_1);
  397.  
  398.                 //updating electron energy and velocities
  399.                
  400.                 e.energy = m_e*V_1*V_1/(2.0*q);
  401.                 e.vx = V_x_1;
  402.                 e.vy = V_y_1;
  403.                 e.vz = V_z_1;
  404.  
  405.                 collision_counter++;
  406.  
  407.                 e.collided = true;
  408.             }            
  409.         }
  410.                 if (t%print_interval == 0){
  411.                 // open datafiles to write each time step to see evolution
  412.                 std::ostringstream filename;
  413.                 filename << "data/distribution_" << std::setw(4) << std::setfill('0') << t << ".dat";
  414.  
  415.                 std::ofstream file(filename.str());
  416.                 if (!file.is_open()){
  417.                 std::cerr << "Error opening file: " << filename.str() << std::endl;
  418.                 return 1;
  419.                 }
  420.                 // end opening datafiles for each timestep
  421.                
  422.                 // creating histogram each timestep
  423.                 for (int i = 0; i < n_e; i++){
  424.                     int bin = (int)( (electrons[i].energy - Emin)/bin_width );
  425.                     if (bin >=0 && bin < N)
  426.                     histo_maxwell[bin]++;
  427.                 }
  428.  
  429.                 // writing data each time step
  430.                 for (int i = 0; i < N; i++){
  431.                     double bin_start = Emin + i*bin_width;
  432.                     file << i*bin_width << " " <<  static_cast<double>(histo_maxwell[i])/(electrons.size()*bin_width) << "\n"; // later need to divide by total partcles number to get normalized distribution
  433.                     histo_maxwell[i] = 0;
  434.                 }
  435.  
  436.                 file.close();
  437.                 // end writing data each timestep
  438.             }
  439.  
  440.     }
  441.  
  442.  
  443.     for (int i = 0; i < n_e; i++){
  444.  
  445.         file2 << i << " " << electrons[i].energy << "\n";
  446.  
  447.         int bin = (int)( (electrons[i].energy - Emin)/bin_width );
  448.         if (bin >=0 && bin < histo_maxwell.size())
  449.             histo_maxwell[bin]++;
  450.     }
  451.  
  452.     int check = 0;
  453.     for (int i = 0; i < histo_maxwell.size(); i++){
  454.         check += histo_maxwell[i];
  455.         double bin_start = Emin + i*bin_width;
  456.         file4 << i*bin_width << " " <<  (i*bin_width)*static_cast<double>(histo_maxwell[i])/(electrons.size()*bin_width) << "\n"; // getting f(E)*E
  457.     }
  458.     std::cout << "Total # of electrons in histo: " << check << "\n";
  459.  
  460.  
  461.     file0.close();
  462.     file1.close();
  463.     file2.close();
  464.     file3.close();
  465.     file4.close();
  466.     file5.close();
  467.     file6.close();
  468.     file7.close();
  469.  
  470.     clock_t end = clock();
  471.  
  472.     double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
  473.  
  474.     std::cout << "Ne collided each timesteps:" << Ne_collided << "\n";
  475.     std::cout << "Energies written successfuly\n";
  476.     std::cout << "Elapsed time: %f seconds " << elapsed << "\n";
  477.  
  478.  
  479.     return 0;
  480.  
  481. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement