Advertisement
phystota

e-n collisions(v6) - elastic collisions as in Vahedi - just loosing energy

Apr 1st, 2025
808
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 16.14 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 50000
  14. #define Emin 0.0
  15. #define Emax 450.0
  16. #define E_mean 100 // mean electron energy, initial distribution
  17. #define bin_width 0.1
  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-29 // 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 5.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.     // --- starts 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.     // --- finish reading cross-section datafile
  186.  
  187.     std::ofstream file0("velocities.dat");    
  188.     std::ofstream file1("energies.dat");        
  189.     std::ofstream file2("energies_final.dat");    
  190.  
  191.     std::ofstream file3("histo_random.dat");    
  192.     file3 << std::fixed << std::setprecision(10);
  193.    
  194.     std::ofstream file4("histo_maxwell.dat");    
  195.     file4 << std::fixed << std::setprecision(10);          
  196.    
  197.     std::ofstream file5("neutral_distribution.dat");    
  198.     std::ofstream file6("E*f(E).dat");    
  199.     std::ofstream file7("nu_max.dat");
  200.     std::ofstream file8("electron_mean_energy.dat");
  201.     std::ofstream file9("nu.dat");    
  202.  
  203.     // Initialize all electrons
  204.     for (auto& e : electrons) {
  205. //        e.initialize(gen, dis);
  206.         e.initialize(gen, dis, maxwell_electron);
  207.     }
  208.     // initialize all nenutrals
  209.     for (auto&n : neutrals) {
  210.         n.initialize(gen, dis, maxwell_neutral);
  211.     }
  212.     // precalculate cross-sections for each energy bin
  213.     for (int i = 0; i < N; i++){
  214.         elastic[i] = interpolate(bin_width*(i+0.5), elastic_CS);
  215.     }
  216.  
  217.  
  218.     for (int i = 0; i < n_e; i++){
  219.         file1 << i << " " << electrons.at(i).energy << "\n";
  220.         file0 << i << " " << electrons[i].vx << " " << electrons[i].vy << " " << electrons[i].vz << "\n";
  221.     }
  222.  
  223.     // -----initial electrons energy distribution starts------------////
  224.     for (int i = 0; i < n_e; i++){
  225.         int bin = (int)( (electrons[i].energy - Emin)/bin_width );
  226.         if (bin >=0 && bin < histo_random.size())
  227.             histo_random[bin]++;
  228.     }
  229.  
  230.     for (int i = 0; i < histo_random.size(); i++){
  231.         double bin_center = Emin + (i + 0.5) * bin_width;
  232.         file3 << bin_center << " " <<  (i*bin_width)*static_cast<double>(histo_random[i])/(electrons.size()*bin_width) << "\n"; // this is electron normalized distribution function
  233.     }
  234.     // -----initial electrons energy distribution ends------------////    
  235.  
  236.     // -----neutrals Maxwell-Boltzmann distribution starts------------////
  237.     for (int i = 0; i < N_He; i++){
  238.         int bin = (int)( (neutrals[i].energy - Emin)/bin_width );
  239.         if (bin >=0 && bin < histo_neutral.size())
  240.             histo_neutral[bin]++;
  241.     }    
  242.  
  243.  
  244.     for (int i = 0; i < histo_neutral.size(); i++){
  245.         double bin_center = Emin + (i + 0.5) * bin_width;
  246.         file5 << bin_center << " " << static_cast<double>(histo_neutral[i])/(neutrals.size()*bin_width) << "\n"; // this is real f(E) - normalized distribution
  247.         file6 << bin_center << " " << bin_center*static_cast<double>(histo_neutral[i])/(neutrals.size()*bin_width) << "\n"; // this should be E*f(E)
  248.     }  
  249.  
  250.     // -----neutrals Maxwell-Boltzmann distribution starts------------////
  251.  
  252.     // -----calculating nu-max for null-collision method starts ------------////
  253.     double nu_max = 0.0;
  254.     double nu_max_temp = 0.0;
  255.  
  256.  
  257.     for (int i = 0; i < N; i++){
  258.         nu_max_temp = (N_He/Volume)*elastic[i] * sqrt(2.0*(i*bin_width + bin_width/2.0)*q/m_e);
  259.         file7 << (i+0.5)*bin_width << " " << nu_max_temp << "\n";
  260.         if (nu_max_temp > nu_max)
  261.             nu_max = nu_max_temp;
  262.     }
  263.     // -----calculating nu-max for null-collision method ends ------------////
  264.  
  265.     //----- calculating nu-average from our electron distribution starts---------///
  266.     for (int i = 0; i < N; i++){
  267.         double bin_center = Emin + (i + 0.5) * bin_width;
  268.         file9 << bin_center << " " << (N_He/Volume)*elastic[i] * sqrt(2.0*bin_center*q/m_e)*static_cast<double>(histo_random[i])/(electrons.size()*bin_width) << "\n";
  269.     }
  270.     //----- calculating nu-average from our electron distribution ends ---------///
  271.  
  272.     double dt = 0.1/nu_max;   // minimum should be 0.1/nu_max to get acceptable numerical error range see Vahedi Surrendra 1995
  273.     double steps = static_cast<int>(time/dt);
  274.  
  275. //    std::cout << steps << "\n";
  276.  
  277.     //using  null-collision technique, getting the number of particles colliding each step: P_collision = 1 - exp(-nu_max*dt)
  278.  
  279.     int Ne_collided = (1.0-exp(-1.0*dt*nu_max))*n_e;
  280.  
  281. //    int Ne_collided = n_e*0.98;  // in case I want to check smth
  282.  
  283.  
  284.     // Generate shuffled list of electron indices
  285.     std::vector<int> electron_indices(n_e);
  286.     std::iota(electron_indices.begin(), electron_indices.end(), 0); // fill with index
  287.     std::shuffle(electron_indices.begin(), electron_indices.end(), gen); // shuffle the indexes    
  288.     int reshuffle_interval = 1;
  289.     int print_interval = 100;
  290.  
  291.     for (int t = 0; t < steps; t++){
  292.  
  293.         std::cout << "timestep remains: " << steps - t << "\n";
  294.  
  295.         //reshuffle the indices
  296.         if (t % reshuffle_interval == 0) {
  297.             std::shuffle(electron_indices.begin(), electron_indices.end(), gen);
  298.         }
  299.  
  300.         // setting flags to false each timestep
  301.         for (auto& e : electrons) e.collided = false;
  302.  
  303.         int collision_counter = 0;
  304.  
  305.  
  306.         for (int idx : electron_indices) {
  307.  
  308.             if (collision_counter >= Ne_collided) break; // quit if reached all collisions
  309.  
  310.             Electron& e = electrons[idx];
  311.             if (e.collided) continue;  // Skip already collided electrons
  312.  
  313.             double electron_energy = e.energy;
  314.             int bin_energy = static_cast<int>(electron_energy / bin_width);
  315.             double nu_elastic = (N_He/Volume) * elastic[bin_energy] * sqrt(2.0*electron_energy*q/m_e);
  316.  
  317.             if (dis(gen) < nu_elastic/nu_max) {
  318.  
  319.                 // ----   Collision energy redistribution module
  320.  
  321.                 // electron particle X Y Z initial velocities and energy
  322.                 double V0_x = e.vx;
  323.                 double V0_y = e.vy;
  324.                 double V0_z = e.vz;
  325.                 double E_0 = e.energy;
  326.  
  327.                 double V0 = sqrt(V0_x*V0_x + V0_y*V0_y + V0_z*V0_z);
  328.                
  329.                 // generating random variables to calculate random direction of center-of-mass after the collision
  330.  
  331.                 double R1 = dis(gen);
  332.                 double R2 = dis(gen);
  333.  
  334.                 //// calculating spherical angles for center-of-mass random direction
  335.                 // double theta = acos(1.0- 2.0*R1);
  336.                 // double phi = 2*M_PI*R2;
  337.                 double cos_khi = (2.0 + E_0 - 2.0*pow((1+E_0), R1))/E_0;
  338.                 double sin_khi = sqrt(1.0 - cos_khi*cos_khi);
  339.  
  340.                 double phi = 2.0*M_PI*R2;
  341.                 double cos_theta = V0_x/V0;
  342.                 double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
  343.                 // //calculating final relative velocity with random direction
  344.  
  345.                 //calculating final velocity of electron
  346.  
  347.                 double i_scat = (V0_x/V0)*cos_khi + (1.0 - (V0_x/V0)*(V0_x/V0))*(sin_khi*cos(phi)/sin_theta);
  348.                 double j_scat = (V0_y/V0)*cos_khi +  (V0_z/V0)*sin_khi*sin(phi)/sin_theta - (V0_x/V0)*(V0_y/V0)*sin_khi*cos(phi)/sin_theta;
  349.                 double k_scat = (V0_z/V0)*cos_khi - (V0_y/V0)*sin_khi*sin(phi)/sin_theta - (V0_x/V0)*(V0_z/V0)*sin_khi*cos(phi)/sin_theta;
  350.  
  351.                 //updating electron energy and velocities
  352.  
  353.                 double delta_E = (2*m_e/M_n)*(1-cos_khi)*e.energy;                
  354.                
  355.                 if (delta_E < e.energy)
  356.                     e.energy = E_0 - delta_E;
  357.                 else
  358.                     e.energy = E_0;
  359.  
  360.                 double speed = sqrt(2*e.energy*q/m_e);
  361.  
  362.                 e.vx = speed*i_scat;
  363.                 e.vy = speed*j_scat;
  364.                 e.vz = speed*k_scat;
  365.  
  366.                 collision_counter++;
  367.  
  368.                 e.collided = true;
  369.             }            
  370.         }
  371.                 if (t%print_interval == 0){
  372.                 // open datafiles to write each time step to see evolution
  373.                 std::ostringstream filename;
  374.                 filename << "data/distribution_" << std::setw(4) << std::setfill('0') << t << ".dat";
  375.  
  376.                 std::ofstream file(filename.str());
  377.                 if (!file.is_open()){
  378.                 std::cerr << "Error opening file: " << filename.str() << std::endl;
  379.                 return 1;
  380.                 }
  381.                 // end opening datafiles for each timestep
  382.                
  383.                 // creating histogram each timestep
  384.                 for (int i = 0; i < n_e; i++){
  385.                     int bin = (int)( (electrons[i].energy - Emin)/bin_width );
  386.                     if (bin >=0 && bin < N)
  387.                     histo_maxwell[bin]++;
  388.                 }
  389.  
  390.                 // writing data each time step
  391.                 for (int i = 0; i < N; i++){
  392.                     double bin_center = Emin + (i + 0.5) * bin_width;
  393.                     file << bin_center << " " <<  static_cast<double>(histo_maxwell[i])/(electrons.size()*bin_width) << "\n"; // later need to divide by total partcles number to get normalized distribution
  394.                     histo_maxwell[i] = 0;
  395.                 }
  396.  
  397.                 file.close();
  398.                 // end writing data each timestep
  399.             }
  400.  
  401.             // calculating mean energy
  402.             double total_energy = 0.0;
  403.             for (const auto& e : electrons) total_energy += e.energy;
  404.             double mean_energy = total_energy / n_e;
  405.             file8 << t*dt << " " << mean_energy << "\n";
  406.     }
  407.  
  408.  
  409.     for (int i = 0; i < n_e; i++){
  410.  
  411.         file2 << i << " " << electrons[i].energy << "\n";
  412.  
  413.         int bin = (int)( (electrons[i].energy - Emin)/bin_width );
  414.         if (bin >=0 && bin < histo_maxwell.size())
  415.             histo_maxwell[bin]++;
  416.     }
  417.  
  418.     int check = 0;
  419.     for (int i = 0; i < histo_maxwell.size(); i++){
  420.         check += histo_maxwell[i];
  421.         double bin_center = Emin + (i + 0.5) * bin_width;
  422.         file4 << bin_center << " " <<  bin_center*static_cast<double>(histo_maxwell[i])/(electrons.size()*bin_width) << "\n"; // getting f(E)*E
  423.     }
  424.     std::cout << "Total # of electrons in histo: " << check << "\n";
  425.  
  426.  
  427.     file0.close();
  428.     file1.close();
  429.     file2.close();
  430.     file3.close();
  431.     file4.close();
  432.     file5.close();
  433.     file6.close();
  434.     file7.close();
  435.     file8.close();
  436.     file9.close();
  437.  
  438.     clock_t end = clock();
  439.  
  440.     double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
  441.  
  442.     std::cout << "Ne collided each timesteps:" << Ne_collided << "\n";
  443.     std::cout << "Energies written successfuly\n";
  444.     std::cout << "Elapsed time: %f seconds " << elapsed << "\n";
  445.     std::cout << "Nu_max: " << nu_max << "\n";
  446.  
  447.  
  448.     return 0;
  449.  
  450. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement