Advertisement
phystota

superelastic+field_heating

Apr 12th, 2025
428
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 37.67 KB | None | 0 0
  1. #include <iostream>
  2. #include <random>
  3. #include <fstream>
  4. #include <assert.h>
  5.  
  6. #include <math.h>
  7. #include <time.h>
  8. #include <iomanip>  // For std::fixed and std::setprecision
  9.  
  10. #include <algorithm>  // For std::shuffle
  11. #include <numeric>    // For std::iota
  12.  
  13. //physical constants
  14.  
  15. #define m_e 9.1E-31 // electron mass in kg
  16. #define M_n 6.6464731E-27 // Helium atom mass
  17. #define k_b 1.38E-23 // Boltzmann constant
  18. #define q 1.602176634E-19 // elementary charge    - eV -> J transfer param
  19. #define Coulomb_log 10.0 // Coulomb logarithm
  20. #define epsilon_0 8.854188E-12 // Vacuum permittivity
  21. #define Coulomb_const pow(q,4)/(pow(4.0*M_PI*epsilon_0,2)) // e^4/(4*pi*eps0)^2
  22. #define thresh1 19.82 //threshold energy excitation tripet state
  23.  
  24. // simulation parameters
  25.  
  26. #define n_e 3000000
  27. #define N_He 100000000 // Helium neutrals number
  28. #define T_n 10.0 // Helium neutral temperature in eV
  29. #define T_e 100.0    // electron Maxwell initial distribution
  30. #define Emin 0.0
  31. #define Emax 2000.0
  32. #define Volume 1.0E-12 // Volume to calculate netral density and collision frequency
  33. #define time 6.0E-7 // 500 microsec time to equalibrate the system
  34. #define dopant 1.0E-12 // addition to avoid zero
  35. #define E_reduced 100.0 // constant electrin field along z-axis
  36.  
  37. #define bin_width 0.1 // keep energy step ~ this to maintain cross-section clarity (Ramsauer minimum etc)
  38. #define N ( (int)((Emax-Emin)/bin_width) + 1) // add 1 to include E_max if needed?
  39.  
  40. // handling final energy bin
  41.  
  42. #define bin_width_smooth 0.5 // energy bin for smooth final distribution
  43. #define N_smooth ( (int)((Emax-Emin)/bin_width_smooth) )
  44.  
  45. double solve_A(double s) { // Netwon method solver
  46.  
  47.     if (s > 3) {
  48.         return 3*exp(-s);
  49.     }
  50.     if (s < 0.01) {
  51.         return 1.0/s;
  52.     }
  53.    
  54.     double A0 = 0.01; // initial guess
  55.     double A = A0;  //starting with initial guess
  56.     double tol = 1.0E-7; // accuracy
  57.  
  58.              
  59.     for (int i = 0; i < 1000; i++){
  60.  
  61.         double tanhA = tanh(A);
  62.         // Avoid division by an extremely small tanh(A)
  63.         if (fabs(tanhA) < 1e-12) {
  64.             std::cerr << "tanh(A) too small, returning fallback at iteration " << i << "\n";
  65.             return 1.0E-7;
  66.         }        
  67.  
  68.         double f = 1.0 / tanhA - 1.0 / A - exp(-s);
  69.         if (fabs(f) < tol)
  70.             break;
  71.  
  72.         double sinhA = sinh(A);
  73.         if (fabs(sinhA) < 1e-12) {
  74.             std::cerr << "sinh(A) too small, returning fallback at iteration " << i << "\n";
  75.             return 1.0E-5;
  76.         }
  77.  
  78.         double dfdA = -1.0/(sinh(A)*sinh(A)) + 1.0/(A*A);
  79.  
  80.         // Check if derivative is too close to zero to avoid huge updates
  81.         if (fabs(dfdA) < 1e-12) {
  82.             std::cerr << "dfdA is too small at iteration " << i << ", returning fallback\n";
  83.             if (s < 0.01) {
  84. //                std::cout << "Small s! Huge A!" << "\n";
  85.                 return 1.0/s;
  86.             }
  87.             if (s > 3) {
  88.                 return 3.0*exp(-s);
  89.             }
  90.         }        
  91.  
  92.         A -= f/dfdA;
  93.  
  94.         // Early check for numerical issues
  95.         if (std::isnan(A) || std::isinf(A)) {
  96.             std::cerr << "Numerical error detected, invalid A at iteration " << i << "\n";
  97.             return (A > 0) ? 1.0E-5 : -1.0E-5;  // Fallback value based on sign
  98.         }        
  99.  
  100.  
  101.     }
  102.  
  103.     return A;
  104. }
  105.  
  106. struct Electron {
  107.  
  108.     //velocity components
  109.     double vx = 0.0;
  110.     double vy = 0.0;
  111.     double vz = 0.0;
  112.     //energy in eV
  113.     double energy = 0.0;
  114.     //Collision flag
  115.     bool collided_en = false;
  116.     bool collided_ee = false;
  117.  
  118.     // initializing Maxwell-Boltzmann distribution with T_e
  119.     void initialize(std::mt19937& gen, std::uniform_real_distribution<double>& dis, std::gamma_distribution<double>& maxwell) {
  120.  
  121.         double R = dis(gen);
  122.  
  123.         // velocity angles in spherical coordinates
  124.         double phi = 2*M_PI*dis(gen);
  125.         double cosTheta = 2.0*dis(gen) - 1.0;
  126.         double sinTheta = sqrt(1.0 - cosTheta*cosTheta);
  127.  
  128.            
  129.         energy = maxwell(gen); // neutrals energies sampled as Maxwell distribution in eV
  130.            
  131.         double speed = sqrt(2*energy*q/m_e);
  132.  
  133.         //velocity components of neutrals in m/s
  134.         vx = speed * sinTheta * cos(phi);
  135.         vy = speed * sinTheta * sin(phi);
  136.         vz = speed * cosTheta;
  137.     }
  138.  
  139.  
  140. };
  141.  
  142. struct CrossSection {
  143.     double energy;
  144.     double sigma;
  145. };
  146.  
  147. double interpolate (double energy, const std::vector<CrossSection>& CS) {
  148.  
  149.  
  150.     if (energy < CS.front().energy) {
  151. //        std::cout << " required energy value lower than range of cross-section data at energy: " << energy << "\n";
  152.         return 0.0;
  153.     }
  154.     if (energy > CS.back().energy) {
  155. //        std::cout << " required energy value higher than range of cross-section data" << "\n";
  156.         return 0.0;        
  157.     }
  158.  
  159.     int step = 0;  
  160.         while (step < CS.size() && energy > CS[step].energy) {
  161.             step++;
  162.         }
  163.  
  164.     double k = (CS[step].sigma - CS[step-1].sigma)/(CS[step].energy - CS[step-1].energy);
  165.     double m = CS[step].sigma - k*CS[step].energy;
  166.    
  167.     return k*energy + m;
  168. }
  169.  
  170.  
  171. struct NeutralParticle {
  172.  
  173.     double energy = 0.0;
  174.     double vx = 0.0;
  175.     double vy = 0.0;
  176.     double vz = 0.0;
  177.  
  178.     void initialize(std::mt19937& gen, std::uniform_real_distribution<double>& dis, std::gamma_distribution<double>& maxwell) {
  179.  
  180.         double R = dis(gen);
  181.  
  182.         // velocity angles in spherical coordinates
  183.         double phi = 2*M_PI*dis(gen);
  184.         double cosTheta = 2.0*dis(gen) - 1.0;
  185.         double sinTheta = sqrt(1.0 - cosTheta*cosTheta);
  186.  
  187.            
  188.         energy = maxwell(gen); // neutrals energies sampled as Maxwell distribution in eV
  189.            
  190.         double speed = sqrt(2*energy*q/M_n);
  191.  
  192.         //velocity components of neutrals in m/s
  193.         vx = speed * sinTheta * cos(phi);
  194.         vy = speed * sinTheta * sin(phi);
  195.         vz = speed * cosTheta;
  196.     }
  197.    
  198. };
  199.  
  200. struct Excited_neutral {
  201.  
  202.     double energy;
  203.     double vx;
  204.     double vy;
  205.     double vz;
  206.    
  207. };
  208.  
  209.  
  210.  
  211. int main() {
  212.  
  213.     clock_t start = clock();
  214.  
  215.     std::vector<Electron> electrons(n_e); // better to use vector instead of simple array as it's dynamically allocated (beneficial for ionization)
  216. //    std::vector<NeutralParticle> neutrals(N_He); // I don't need a vector of neutrals bcs it's like a backhround in MCC-simulation
  217.     std::vector<Excited_neutral> exc_1;  // vector to track triplet excited atoms of Helium
  218.  
  219.     std::vector<int> histo_random(N, 0); // initialize N size zero-vector for random (initial) histogram
  220.     std::vector<int> histo_maxwell(N, 0); // initialize N size zero-vector for maxwellian histogram
  221.     std::vector<int> histo_neutral(N, 0); // initialize N size zero-vector for neutral distribution histogram
  222.     std::vector<int> histo_excited(N, 0); // initialize N size zero-vector for excited He distribution histogram
  223.  
  224.     std::vector<double> elastic_vec(N, 0); // precompiled elastic cross-section-energy vector
  225.     std::vector<double> inelastic1_vec(N, 0); // precompiled inelastic(triplet excitation) cross-section-energy vector
  226.     std::vector<double> superelastic1_vec(N, 0); // precompiled superelastic(triplet de-excitation) cross-section-energy vector
  227.  
  228.     std::random_device rd;
  229.     std::mt19937 gen(rd());
  230.     std::uniform_real_distribution<double> dis(0.0, 1.0);
  231.     std::gamma_distribution<double> maxwell_neutral(1.5, T_n);
  232.     std::gamma_distribution<double> maxwell_electron(1.5, T_e);
  233.  
  234.     std::uniform_int_distribution<int> pair(0, n_e-1);
  235.     std::uniform_int_distribution<int> neutral_pair(0, N_He-1);    
  236.  
  237.  
  238.     std::ifstream elastic_cs_dat("cross_sections/elastic.dat");
  239.     if (!elastic_cs_dat.is_open()) {
  240.         std::cerr << "Error opening elastic cross-sections file!" << std::endl;
  241.         return 1;
  242.     }    
  243.  
  244.     std::ifstream excitation1_cs_dat("cross_sections/inelastic_triplet.dat");
  245.     if (!excitation1_cs_dat.is_open()) {
  246.         std::cerr << "Error opening inelastic triplet cross-sections file!" << std::endl;
  247.         return 1;
  248.     }  
  249.  
  250.     // --- starts reading cross section datafiles
  251.  
  252.     std::vector<CrossSection> elastic_CS_temp;
  253.  
  254.     double energy, sigma;
  255.  
  256.     while (elastic_cs_dat >> energy >> sigma) {
  257.         elastic_CS_temp.push_back({energy, sigma});
  258.     }    
  259.     elastic_cs_dat.close();
  260.  
  261.     energy = 0.0;
  262.     sigma = 0.0;
  263.  
  264.     std::vector<CrossSection> inelastic1_CS_temp;
  265.  
  266.     while (excitation1_cs_dat >> energy >> sigma) {
  267.         inelastic1_CS_temp.push_back({energy, sigma});
  268.     }    
  269.     excitation1_cs_dat.close();    
  270.  
  271.     // --- finish reading cross-section datafiles  
  272.  
  273.     std::ofstream file0("output_files/velocities.dat");    
  274.     std::ofstream file1("output_files/energies.dat");        
  275.     std::ofstream file2("output_files/energies_final.dat");    
  276.     std::ofstream file3("output_files/histo_random.dat");    
  277.     file3 << std::fixed << std::setprecision(10);
  278.    
  279.     std::ofstream file4("output_files/histo_maxwell.dat");
  280.     file4 << std::fixed << std::setprecision(10);          
  281.    
  282.     std::ofstream file5("output_files/neutral_distribution.dat");    
  283.     std::ofstream file6("output_files/E*f(E).dat");    
  284.     std::ofstream file7("output_files/nu_max.dat");
  285.     std::ofstream file8("output_files/electron_mean_energy.dat");
  286.     std::ofstream file9("output_files/nu_elastic_average_initial.dat");
  287.     std::ofstream file10("output_files/nu_inelastic1_average_initial.dat");
  288.     std::ofstream file11("output_files/nu_elastic_average_final.dat");
  289.     std::ofstream file12("output_files/nu_inelastic1_average_final.dat");
  290.     std::ofstream file13("output_files/log_output.dat");  
  291.     std::ofstream file14("output_files/excited_energies.dat");      
  292.     std::ofstream file15("output_files/excited_histo.dat");            
  293.     std::ofstream file_temp("output_files/superlastic.dat");  
  294.  
  295.     // Initialize all electrons
  296.     for (auto& e : electrons) {
  297.         e.initialize(gen, dis, maxwell_electron);
  298.     }
  299.     // // initialize all nenutrals
  300.     // for (auto&n : neutrals) {
  301.     //     n.initialize(gen, dis, maxwell_neutral);
  302.     // }
  303.     // precalculate elastic cross-section for each energy bin
  304.     for (int i = 0; i < N; i++){
  305.         elastic_vec[i] = interpolate(bin_width*(i+0.5), elastic_CS_temp);
  306.     }
  307.     // precalculate inelastic cross-section (ground -> triplet) for each energy bin
  308.     for (int i = 0; i < N; i++){
  309.         inelastic1_vec[i] = interpolate(bin_width*(i+0.5), inelastic1_CS_temp);
  310.     }
  311.     // precalculate superelastic cross-section (triplet -> ground) for each energy bin
  312.     // detailed balance gives: sigma_j_i(E) = (g_i/g_j)*((E+theshold)/E)*sigma_i_j(E+theshold)
  313.     for (int i = 0; i < N; i++){
  314.  
  315.         double energy = Emin + (i + 0.5) * bin_width;
  316.         int thresh_bin = (int)( (thresh1 - Emin)/bin_width ); // calculating bin for threshold energy
  317.         superelastic1_vec[i] = (1.0/3.0)*((energy + thresh1)/energy)*interpolate(bin_width*(i+0.5)+thresh1, inelastic1_CS_temp); // using detailed balance, calculating backward deexcitation cross-section
  318. //        superelastic1_vec[i] = 0.0;
  319.     }
  320.  
  321.     for (int i = 0; i < N; i++){
  322.         file_temp << (i+0.5)*bin_width << " " << superelastic1_vec[i] << "\n";
  323.     }
  324.  
  325.  
  326.     for (int i = 0; i < n_e; i++){
  327.         file1 << i << " " << electrons.at(i).energy << "\n";
  328.         file0 << i << " " << electrons[i].vx << " " << electrons[i].vy << " " << electrons[i].vz << "\n";
  329.     }
  330.  
  331.     // -----initial electrons energy distribution starts------------////
  332.     for (int i = 0; i < n_e; i++){
  333.         int bin = (int)( (electrons[i].energy - Emin)/bin_width );
  334.         if (bin >=0 && bin < histo_random.size())
  335.             histo_random[bin]++;
  336.     }
  337.  
  338.     for (int i = 0; i < histo_random.size(); i++){
  339.         double bin_center = Emin + (i + 0.5) * bin_width;
  340.         file3 << bin_center << " " <<  static_cast<double>(histo_random[i])/(electrons.size()*bin_width) << "\n"; // this is electron normalized distribution function
  341.     }
  342.     // -----initial electrons energy distribution ends------------////    
  343.  
  344.     // // -----neutrals Maxwell-Boltzmann distribution starts------------////
  345.     // for (int i = 0; i < N_He; i++){
  346.     //     int bin = (int)( (neutrals[i].energy - Emin)/bin_width );
  347.     //     if (bin >=0 && bin < histo_neutral.size())
  348.     //         histo_neutral[bin]++;
  349.     // }    
  350.  
  351.     // for (int i = 0; i < histo_neutral.size(); i++){
  352.     //     double bin_center = Emin + (i + 0.5) * bin_width;
  353.     //     file5 << bin_center << " " << static_cast<double>(histo_neutral[i])/(neutrals.size()*bin_width) << "\n"; // this is real f(E) - normalized distribution
  354.     //     file6 << bin_center << " " << bin_center*static_cast<double>(histo_neutral[i])/(neutrals.size()*bin_width) << "\n"; // this should be E*f(E)
  355.  
  356.     // }
  357.     // // -----neutrals Maxwell-Boltzmann distribution starts------------////      
  358.  
  359.     // ---- precalculating A from eq.13 nanbu1997 ------ ///
  360.  
  361.  
  362.  
  363.    
  364.    
  365.     // ---- end A precalculation -------------------------//
  366.  
  367.     // -----calculating nu-max for null-collision method starts ------------////
  368.     double nu_max = 0.0;
  369.     double nu_max_temp = 0.0;
  370.     double sigma_total = 0.0;
  371.    
  372.     for (int i = 0; i < N; i++){
  373.         sigma_total = elastic_vec[i] + inelastic1_vec[i] + superelastic1_vec[i];
  374.         nu_max_temp = (N_He/Volume)*sigma_total * sqrt(2.0*(i*bin_width + bin_width/2.0)*q/m_e);
  375.         file7 << i << " " << nu_max_temp << "\n";
  376.         if (nu_max_temp > nu_max)
  377.             nu_max = nu_max_temp;
  378.     }
  379.     // -----calculating nu-max for null-collision method ends ------------////
  380.  
  381.     //----- calculating number to calculate nu-average (both elastic/inelastic )from our electron distribution starts---------///
  382.     // --- calculating nu(E)*f(E) for later external integration, using initial f(E)
  383.     for (int i = 0; i < N; i++){
  384.         double bin_center = Emin + (i + 0.5) * bin_width;
  385.         file9 << bin_center << " " << (N_He/Volume)*elastic_vec[i] * sqrt(2.0*bin_center*q/m_e)*static_cast<double>(histo_random[i])/(electrons.size()*bin_width) << "\n";
  386.         file10 << bin_center << " " << (N_He/Volume)*inelastic1_vec[i] * sqrt(2.0*bin_center*q/m_e)*static_cast<double>(histo_random[i])/(electrons.size()*bin_width) << "\n";
  387.     }
  388.     //----- calculating nu-average from our electron distribution ends ---------///    
  389.  
  390.     double dt = 0.01/nu_max;   // minimum should be 0.1/nu_max to get acceptable numerical error range see Vahedi Surrendra 1995
  391.     double steps = static_cast<int>(time/dt);
  392.  
  393.     std::cout << steps << "\n";
  394.  
  395.     //using  null-collision technique, getting the number of particles colliding each step: P_collision = 1 - exp(-nu_max*dt)
  396.     int Ne_collided = (1.0-exp(-1.0*dt*nu_max))*n_e;
  397.  
  398.  
  399.     int print_interval = 100;
  400.     int el_coll_counter = 0; // track all elastic collisions
  401.     int exc1_coll_counter = 0; // track all excitation collisions
  402.     int null_coll_counter = 0; // track null-collisions
  403.     int ee_coll_counter = 0; //track e-e Coulomb collisions
  404.     int super1_coll_counter = 0; // track superelastic triplet collisions
  405.  
  406.     for (int t = 0; t < steps; t++){
  407.  
  408.         // Generate shuffled list of electron indices
  409.         int reshuffle_interval = 1;
  410.         std::vector<int> electron_indices(n_e);
  411.         std::iota(electron_indices.begin(), electron_indices.end(), 0); // fill with index
  412.         std::shuffle(electron_indices.begin(), electron_indices.end(), gen); // shuffle the indexes    
  413.  
  414.         // Generate shuffled list of excited atoms indices
  415.         std::vector<int> excited1_indices(exc_1.size());
  416.         std::iota(excited1_indices.begin(), excited1_indices.end(), 0); // fill with index
  417.         std::shuffle(excited1_indices.begin(), excited1_indices.end(), gen); // shuffle the indexes    
  418.  
  419.         int super1_coll_counter_temp = 0;
  420.         int exc1_coll_counter_temp = 0;
  421.  
  422.         std::cout << "timestep remains: " << steps - t << "\n";
  423.  
  424.         // calculating mean energy
  425.         double total_energy = 0.0;
  426.         for (const auto& e : electrons) total_energy += e.energy;
  427.         double mean_energy = total_energy / n_e;
  428.         file8 << t*dt << " " << mean_energy << "\n";            
  429.  
  430.  
  431.         // setting flags to false each timestep
  432.         for (auto& e : electrons) e.collided_en = false;
  433.         for (auto& e : electrons) e.collided_ee = false;        
  434.  
  435.         int collision_counter_en = 0; // electron-neutral collision counter
  436.         int collision_counter_ee = 0; // e-e collisoin counter
  437.  
  438.  
  439.         for (int idx : electron_indices) {
  440.  
  441.             if (collision_counter_en >= Ne_collided) break; // quit if reached all collisions
  442.  
  443.             Electron& e = electrons[idx];
  444.             if (e.collided_en) continue;  // Skip already collided electrons
  445.  
  446.             double electron_energy = e.energy;
  447.             int bin_energy = static_cast<int>(electron_energy / bin_width);
  448.             double nu_elastic = (N_He/Volume) * elastic_vec[bin_energy] * sqrt(2.0*electron_energy*q/m_e);
  449.             double nu_inelastic1 = (N_He/Volume) * inelastic1_vec[bin_energy] * sqrt(2.0*electron_energy*q/m_e);
  450.             double nu_superelastic1 = (N_He/Volume) * superelastic1_vec[bin_energy] * sqrt(2.0*electron_energy*q/m_e);
  451.  
  452.  
  453.             double r = dis(gen);
  454.  
  455.             double P0 = nu_elastic/nu_max;
  456.             double P1 = (nu_elastic + nu_inelastic1)/nu_max;
  457.             double P2 = (nu_elastic + nu_inelastic1 + nu_superelastic1)/nu_max;
  458.            
  459.  
  460.             if (r < P0) {
  461.  
  462.                 // elastic collision happens
  463.  
  464.                 // ----   Collision energy redistribution module
  465.  
  466.                 // electron particle X Y Z initial velocities and energy
  467.                 double V0_x_1 = e.vx;
  468.                 double V0_y_1 = e.vy;
  469.                 double V0_z_1 = e.vz;
  470.  
  471.                 // neutral particle X Y Z initial velocities
  472.  
  473.                 // int k = neutral_pair(gen);
  474.  
  475.                 // double V0_x_2 = neutrals[k].vx;
  476.                 // double V0_y_2 = neutrals[k].vy;
  477.                 // double V0_z_2 = neutrals[k].vz;
  478.  
  479.                 // randomize particles each collision
  480.                 NeutralParticle tmp_neutral;
  481.                 tmp_neutral.initialize(gen, dis, maxwell_neutral);
  482.                 double V0_x_2 = tmp_neutral.vx;
  483.                 double V0_y_2 = tmp_neutral.vy;
  484.                 double V0_z_2 = tmp_neutral.vz;
  485.  
  486.                 // initial relative velocity X Y Z (must be equal to final relative velocity in center-of-mass frame)
  487.  
  488.                 double V0_rel_x = (V0_x_1 - V0_x_2);
  489.                 double V0_rel_y = (V0_y_1 - V0_y_2);
  490.                 double V0_rel_z = (V0_z_1 - V0_z_2);
  491.  
  492.                 double V0_rel = sqrt(V0_rel_x*V0_rel_x + V0_rel_y*V0_rel_y + V0_rel_z*V0_rel_z);
  493.  
  494.                 // center-of-mass frame initial velocity (magnitude of it must be equal to the counterpart in this frame)
  495.  
  496.                 double V_cm_x = (m_e*V0_x_1 + M_n*V0_x_2)/(m_e + M_n);
  497.                 double V_cm_y = (m_e*V0_y_1 + M_n*V0_y_2)/(m_e + M_n);
  498.                 double V_cm_z = (m_e*V0_z_1 + M_n*V0_z_2)/(m_e + M_n);                    
  499.  
  500.                 // generating random variables to calculate random direction of center-of-mass after the collision
  501.  
  502.                 double R1 = dis(gen);
  503.                 double R2 = dis(gen);
  504.  
  505.                 // calculating spherical angles for center-of-mass random direction
  506.                 double theta = acos(1.0- 2.0*R1);
  507.                 double phi = 2*M_PI*R2;
  508.  
  509.                 //calculating final relative velocity with random direction
  510.  
  511.                 double V_rel_x = V0_rel*sin(theta)*cos(phi);
  512.                 double V_rel_y = V0_rel*sin(theta)*sin(phi);
  513.                 double V_rel_z = V0_rel*cos(theta);
  514.  
  515.                 double V_rel = sqrt(V_rel_x*V_rel_x + V_rel_y*V_rel_y + V_rel_z*V_rel_z);
  516.  
  517.                 //calculating final velocity of electron
  518.  
  519.                 double V_x_1 = V_cm_x + V_rel_x * (M_n/(m_e + M_n));
  520.                 double V_y_1 = V_cm_y + V_rel_y * (M_n/(m_e + M_n));
  521.                 double V_z_1 = V_cm_z + V_rel_z * (M_n/(m_e + M_n));
  522.  
  523.                 double V_1 = sqrt(V_x_1*V_x_1 + V_y_1*V_y_1 + V_z_1*V_z_1);
  524.  
  525.                 //updating electron energy and velocities
  526.                
  527.                 e.energy = m_e*V_1*V_1/(2.0*q);
  528.                 e.vx = V_x_1;
  529.                 e.vy = V_y_1;
  530.                 e.vz = V_z_1;
  531.  
  532.                 collision_counter_en++;
  533.                 el_coll_counter++;
  534.  
  535.                 e.collided_en = true;
  536.             }        
  537.  
  538.             else if (r < P1) {
  539.  
  540.                 //inelastic 1(triplet) collision happens
  541.  
  542.                 // ----   Collision energy redistribution module
  543.  
  544.                 // electron particle X Y Z initial velocities and energy
  545.                 double V0_x = e.vx;
  546.                 double V0_y = e.vy;
  547.                 double V0_z = e.vz;
  548.                 double E_0 = e.energy;
  549.                
  550.                 // neutral that collides with electron
  551.  
  552.                 // randomize particles each collision
  553.  
  554.                 NeutralParticle tmp_neutral;
  555.                 tmp_neutral.initialize(gen, dis, maxwell_neutral);
  556.                 double V_x_n = tmp_neutral.vx;
  557.                 double V_y_n = tmp_neutral.vy;
  558.                 double V_z_n = tmp_neutral.vz;
  559.                 double E_n = tmp_neutral.energy;
  560.  
  561.  
  562.                 double V0 = sqrt(V0_x*V0_x + V0_y*V0_y + V0_z*V0_z);
  563.                
  564.                 // generating random variables to calculate random direction of center-of-mass after the collision
  565.  
  566.                 double R1 = dis(gen);
  567.                 double R2 = dis(gen);
  568.  
  569.                 //// calculating spherical angles for center-of-mass random direction
  570.                 // double theta = acos(1.0- 2.0*R1);
  571.                 // double phi = 2*M_PI*R2;
  572.                 double cos_khi = (2.0 + E_0 - 2.0*pow((1+E_0), R1))/E_0;
  573.                 double sin_khi = sqrt(1.0 - cos_khi*cos_khi);
  574.  
  575.                 double phi = 2.0*M_PI*R2;
  576.                 double cos_theta = V0_x/V0;
  577.                 double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
  578.                 // //calculating final relative velocity with random direction
  579.  
  580.                 //calculating final velocity of electron
  581.  
  582.                 double i_scat = (V0_x/V0)*cos_khi + (1.0 - (V0_x/V0)*(V0_x/V0))*(sin_khi*cos(phi)/sin_theta);
  583.                 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;
  584.                 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;
  585.  
  586.                 //updating electron energy and velocities        
  587.                
  588.                 if (e.energy < thresh1) {
  589.                     null_coll_counter++;
  590.                     collision_counter_en++;
  591.                     e.collided_en = true;
  592.                     continue;
  593.                 }
  594.                 else {
  595.                     e.energy = E_0 - thresh1;
  596.  
  597.                     double speed = sqrt(2*e.energy*q/m_e);
  598.  
  599.                     e.vx = speed*i_scat;
  600.                     e.vy = speed*j_scat;
  601.                     e.vz = speed*k_scat;
  602.  
  603.                     collision_counter_en++;  
  604.                     exc1_coll_counter++;
  605.                     exc1_coll_counter_temp++;
  606.  
  607.                     e.collided_en = true;
  608.  
  609.                     // pushing this neutral to an array of excited species exc_1
  610.  
  611.                     exc_1.push_back({E_n, V_x_n, V_y_n, V_z_n});
  612.                 }
  613.             }    
  614.  
  615.             else if (r < P2) {
  616.  
  617.                 //supernelastic 1(triplet -> ground state) collision happens
  618.  
  619.                 if (exc_1.empty()) {
  620.                     null_coll_counter++;
  621.                     collision_counter_en++;
  622.                     e.collided_en = true;
  623.                     continue;            
  624.                 }
  625.  
  626.                 // ----   Collision energy redistribution module
  627.  
  628.                 // electron particle X Y Z initial velocities and energy
  629.                 double V0_x = e.vx;
  630.                 double V0_y = e.vy;
  631.                 double V0_z = e.vz;
  632.                 double E_0 = e.energy;
  633.  
  634.                 double V0 = sqrt(V0_x*V0_x + V0_y*V0_y + V0_z*V0_z);
  635.  
  636.                 // neutral that collides with electron
  637.                 // taking particles from dynamic array of excited neutrals
  638.  
  639.                 int index = std::uniform_int_distribution<int>(0, exc_1.size()-1)(gen);
  640.                 Excited_neutral& exc = exc_1[index];
  641.                 double V_x = exc.vx;
  642.                 double V_y = exc.vy;
  643.                 double V_z = exc.vz;
  644.                 double E = exc.energy;
  645.                
  646.                 // generating random variables to calculate random direction of center-of-mass after the collision
  647.  
  648.                 double R1 = dis(gen);
  649.                 double R2 = dis(gen);
  650.  
  651.                 //// calculating spherical angles for center-of-mass random direction
  652.                 // double theta = acos(1.0- 2.0*R1);
  653.                 // double phi = 2*M_PI*R2;
  654.                 double cos_khi = (2.0 + E_0 - 2.0*pow((1+E_0), R1))/E_0;
  655.                 double sin_khi = sqrt(1.0 - cos_khi*cos_khi);
  656.  
  657.                 double phi = 2.0*M_PI*R2;
  658.                 double cos_theta = V0_x/V0;
  659.                 double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
  660.                 // //calculating final relative velocity with random direction
  661.  
  662.                 //calculating final velocity of electron
  663.  
  664.                 double i_scat = (V0_x/V0)*cos_khi + (1.0 - (V0_x/V0)*(V0_x/V0))*(sin_khi*cos(phi)/sin_theta);
  665.                 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;
  666.                 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;
  667.  
  668.                 //updating electron energy and velocities        
  669.                
  670.                 e.energy = E_0 + thresh1;
  671.  
  672.                 double speed = sqrt(2*e.energy*q/m_e);
  673.  
  674.                 e.vx = speed*i_scat;
  675.                 e.vy = speed*j_scat;
  676.                 e.vz = speed*k_scat;
  677.  
  678.                 //counting collisions, working with flags, popping atom out of the vector
  679.  
  680.                 std::swap(exc_1[index], exc_1.back());
  681.                 exc_1.pop_back();
  682.  
  683.                 collision_counter_en++;  
  684.                 super1_coll_counter++;
  685.                 super1_coll_counter_temp++;
  686.  
  687.                 e.collided_en = true;
  688.             }  
  689.  
  690.             else {
  691.                 // null-collision
  692.                 collision_counter_en++;
  693.                 null_coll_counter++;
  694.                 e.collided_en = true;
  695.             }
  696.         }
  697.  
  698.         // ----- -------now begin e-e collisions ------ /////
  699.  
  700.         // Reshuffle electron indices for random pairing for e-e collisions
  701.         std::shuffle(electron_indices.begin(), electron_indices.end(), gen);
  702.  
  703.         int max_pairs = n_e/2; // each electron collides
  704.        
  705.         #pragma omp parallel for
  706.         for (int i = 0; i < max_pairs; i++){
  707.  
  708.             int id1 = electron_indices[2 * i];
  709.             int id2 = electron_indices[2 * i + 1];
  710.             if (id1 >= n_e || id2 >= n_e) continue; // Handle edge case
  711.  
  712.             Electron& e1 = electrons[id1];
  713.             Electron& e2 = electrons[id2];
  714.  
  715.             if (e1.collided_ee || e2.collided_ee) continue; //handle already collided cases
  716.  
  717.             double E_initial = e1.energy + e2.energy; // total initial energy of pair to check the energy conservation
  718.  
  719.             // generating random variables to calculate random direction of center-of-mass after the collision
  720.  
  721.             double R1 = dis(gen);
  722.             double R2 = dis(gen);        
  723.  
  724.             // ----   Collision energy redistribution module
  725.  
  726.             // first particle X Y Z initial velocities
  727.             double V0_x_1 = e1.vx;
  728.             double V0_y_1 = e1.vy;
  729.             double V0_z_1 = e1.vz;
  730.             // second particle X Y Z initial velocities
  731.             double V0_x_2 = e2.vx;
  732.             double V0_y_2 = e2.vy;
  733.             double V0_z_2 = e2.vz;
  734.  
  735.             // file13 << "V0_x_1: " << V0_x_1 << " " << "V0_y_1: " << V0_y_1 << " " << " V0_z_1: " << V0_z_1 << " ";
  736.             // file13 << "V0_x_2: " << V0_x_2 << " " << "V0_y_2: " << V0_y_2 << " " << " V0_z_2: " << V0_z_2 << " ";
  737.  
  738.             // initial relative velocity X Y Z (must be equal to final relative velocity in center-of-mass frame)
  739.  
  740.             double V0_rel_x = (V0_x_1 - V0_x_2);
  741.             double V0_rel_y = (V0_y_1 - V0_y_2);
  742.             double V0_rel_z = (V0_z_1 - V0_z_2);
  743.  
  744.             double V0_rel = sqrt(V0_rel_x*V0_rel_x + V0_rel_y*V0_rel_y + V0_rel_z*V0_rel_z);
  745.             double V0_rel_normal = sqrt(V0_rel_y*V0_rel_y + V0_rel_z*V0_rel_z);
  746.  
  747.             // file13 << "V0_rel: " << V0_rel << " " << "V0_rel_normal: " << V0_rel_normal << " ";
  748.  
  749.             if(std::isnan(V0_rel) || std::isinf(V0_rel) || fabs(V0_rel) < 1e-12){
  750.                 std::cerr << "Invalid V0_rel computed: " << V0_rel << " at timestep " << t << std::endl;
  751.                 continue;
  752.             }
  753.            
  754.             if(std::isnan(V0_rel_normal) || std::isinf(V0_rel_normal) || fabs(V0_rel_normal) < 1e-12){
  755.                 std::cerr << "Invalid V0_rel_normal computed: " << V0_rel << " at timestep " << t << std::endl;
  756.                 continue;
  757.             }                        
  758.  
  759.             // calculating spherical angles for center-of-mass random direction
  760.             double theta = acos(1.0- 2.0*R1);
  761.             double phi = 2*M_PI*R2;
  762.  
  763.             // calcluating h for equations 20a, 20b (Nanbu1995)
  764.  
  765.             double eps = 2*M_PI*R1;
  766.  
  767.             double h_x = V0_rel_normal*cos(eps);
  768.             double h_y = -(V0_rel_y*V0_rel_x*cos(eps) + V0_rel*V0_rel_z*sin(eps))/V0_rel_normal;
  769.             double h_z = -(V0_rel_z*V0_rel_x*cos(eps) - V0_rel*V0_rel_y*sin(eps))/V0_rel_normal;    
  770.  
  771.             //  calculating s (Nanbu1995 eq 19)
  772.  
  773.             double s = Coulomb_log/(4.0*M_PI) * pow((q*q/(epsilon_0*(m_e/2))),2) * (n_e/Volume) * pow(V0_rel,-3) * dt;
  774.  
  775.             // file13 << "s: " << s << " ";
  776.  
  777.             if(std::isnan(s) || std::isinf(s) || fabs(s) < 1e-12){
  778.                 std::cerr << "Invalid s computed: " << s << " at timestep " << t << std::endl;
  779.                 continue;
  780.             }
  781.  
  782.             double A = solve_A(s);  
  783.  
  784.             if(std::isnan(A) || std::isinf(A) || fabs(A) < 1e-12){
  785. //                std::cerr << "Invalid A computed: " << A << " at timestep " << t << std::endl;
  786.                 A = 1.0E-12;
  787. //                continue;
  788.             }
  789.  
  790.  
  791.             // calculating cos(khi) (Nanbu1995 eq 17)
  792.             double cos_khi = 0.0;
  793.             double sin_khi = 0.0;
  794.            
  795.             if (s < 1.0E-2) {// taking care of small s  
  796.                 cos_khi = 1.0 + s*log(R1);    
  797.             }
  798.             else {
  799.                 cos_khi = (1.0/A)*log(exp(-A) + 2.0*R1*sinh(A));
  800.             }
  801.  
  802.             sin_khi = sqrt(1.0 - cos_khi*cos_khi);
  803.  
  804.  
  805.             //calculating final velocity of first particle
  806.  
  807.             double V_x_1 = V0_x_1 - 0.5*(V0_rel_x*(1.0-cos_khi) + h_x*sin_khi);
  808.             double V_y_1 = V0_y_1 - 0.5*(V0_rel_y*(1.0-cos_khi) + h_y*sin_khi);
  809.             double V_z_1 = V0_z_1 - 0.5*(V0_rel_z*(1.0-cos_khi) + h_z*sin_khi);
  810.  
  811.             double V_1 = sqrt(V_x_1*V_x_1 + V_y_1*V_y_1 + V_z_1*V_z_1);
  812.  
  813.             //calculating final velocity of second particle
  814.  
  815.             double V_x_2 = V0_x_2 + 0.5*(V0_rel_x*(1.0-cos_khi) + h_x*sin_khi);
  816.             double V_y_2 = V0_y_2 + 0.5*(V0_rel_y*(1.0-cos_khi) + h_y*sin_khi);
  817.             double V_z_2 = V0_z_2 + 0.5*(V0_rel_z*(1.0-cos_khi) + h_z*sin_khi);
  818.  
  819.             double V_2 = sqrt(V_x_2*V_x_2 + V_y_2*V_y_2 + V_z_2*V_z_2);
  820.  
  821.             // updating velocities
  822.  
  823.             e1.vx = V_x_1;
  824.             e1.vy = V_y_1;
  825.             e1.vz = V_z_1;
  826.  
  827.             e2.vx = V_x_2; // Update velocity components
  828.             e2.vy = V_y_2;
  829.             e2.vz = V_z_2;
  830.  
  831.             // calculating final energies of first and second colliding particles
  832.  
  833.             e1.energy = V_1*V_1*m_e/(2.0*q);
  834.             e2.energy = V_2*V_2*m_e/(2.0*q);          
  835.  
  836.             double E_final = e1.energy + e2.energy;
  837.  
  838.  
  839.             // if(fabs(E_final - E_initial) > 1e-6) {
  840.             //     std::cerr << "Energy conservation violation: " << E_final - E_initial << " eV\n";
  841.             // }
  842.  
  843.             // --- collision energy redistrubution module ends  
  844.  
  845.             // collision counters handling
  846.  
  847.             ee_coll_counter++;
  848.             e1.collided_ee = true;
  849.             e2.collided_ee = true;
  850.  
  851.         }
  852.         //////----------------------e-e coulomb collision ends --------------/////////////////
  853.  
  854.         /// -- electrin field heating along E-Z axis begin--- ///
  855.         for (int idx : electron_indices) {
  856.  
  857.             // Update velocity component due to electric field
  858.             double a_z = (q * E_reduced) / m_e; // acceleration in z-direction, m/s^2
  859.             electrons[idx].vz += a_z * dt;
  860.  
  861.             // Recalculate energy from updated velocity
  862.             double vx = electrons[idx].vx;
  863.             double vy = electrons[idx].vy;
  864.             double vz = electrons[idx].vz;
  865.             electrons[idx].energy = 0.5 * m_e * (vx*vx + vy*vy + vz*vz) / q;
  866.         }
  867.         // -------------------------------------------- filed heating ends ------------------------////////////////
  868.  
  869.         // /// ---- data writing starts -----------////////////
  870.  
  871.         //     if (t%print_interval == 0){
  872.         //     // open datafiles to write each time step to see evolution
  873.         //     std::ostringstream filename;
  874.         //     filename << "data/distribution_" << std::setw(4) << std::setfill('0') << t << ".dat";
  875.  
  876.         //     std::ofstream file(filename.str());
  877.         //     if (!file.is_open()){
  878.         //     std::cerr << "Error opening file: " << filename.str() << std::endl;
  879.         //     return 1;
  880.         //     }
  881.         //     // end opening datafiles for each timestep
  882.            
  883.         //     // creating histogram each timestep
  884.         //     for (int i = 0; i < n_e; i++){
  885.         //         int bin = (int)( (electrons[i].energy - Emin)/bin_width );
  886.         //         if (bin >=0 && bin < N)
  887.         //         histo_maxwell[bin]++;
  888.         //     }
  889.  
  890.         //     // writing data each time step
  891.         //     for (int i = 0; i < N; i++){
  892.         //         double bin_center = Emin + (i + 0.5) * bin_width;
  893.         //         file << bin_center << " " <<  static_cast<double>(histo_maxwell[i])/(electrons.size()*bin_width) << "\n"; //f(E)
  894.         //         histo_maxwell[i] = 0;
  895.         //     }
  896.  
  897.         //     file.close();
  898.  
  899.         //     }
  900.  
  901.             // end writing data each timestep
  902. //            std::cout << "number excitation collisions at timestep: " << t << " " << "is: " << exc1_coll_counter_temp << "\n";            
  903. //            std::cout << "number superelatic collisions at timestep: " << t << " " << "is: " << super1_coll_counter_temp << "\n";            
  904.     }
  905.  
  906.     // ----- final electron energies distribution begins
  907.     for (int i = 0; i < n_e; i++){
  908.  
  909.         file2 << i << " " << electrons[i].energy << "\n";
  910.  
  911.         int bin = static_cast<int>( (electrons[i].energy - Emin)/bin_width_smooth);
  912.         if (bin >=0 && bin < histo_maxwell.size())
  913.             histo_maxwell[bin]++;
  914.     }
  915.  
  916.     int check = 0;
  917.     for (int i = 0; i < N_smooth; i++){
  918.         check += histo_maxwell[i];
  919.         double bin_center = Emin + (i + 0.5) * bin_width_smooth;
  920.         file4 << bin_center << " " <<  static_cast<double>(histo_maxwell[i])/(electrons.size()*bin_width_smooth) << "\n"; // getting f(E)
  921.     }
  922.  
  923.         std::cout << "Total # of electrons in a final histogram: " << check << "\n";
  924.  
  925.     // ----- final electron energies distribution ends
  926.  
  927.     // ------ excited atoms histogram --------/////
  928.  
  929.     for (int i = 0; i < exc_1.size(); i++) {
  930.  
  931.         file14 << i << " " << exc_1[i].energy << "\n";
  932.  
  933.         int bin = static_cast<int>( (exc_1[i].energy - Emin)/bin_width);
  934.         if (bin >=0 && bin < histo_excited.size())
  935.             histo_excited[bin]++;        
  936.     }
  937.  
  938.     for (int i = 0; i < histo_excited.size(); i++){
  939.  
  940.         double bin_center = Emin + (i + 0.5) * bin_width;
  941.         file15 << bin_center << " " <<  static_cast<double>(histo_maxwell[i])/(electrons.size()*bin_width_smooth) << "\n"; // getting f(E)
  942.     }
  943.  
  944.  
  945.     file0.close();
  946.     file1.close();
  947.     file2.close();
  948.     file3.close();
  949.     file4.close();
  950.     file5.close();
  951.     file6.close();
  952.     file7.close();
  953.     file8.close();
  954.     file9.close();
  955.     file10.close();
  956.     file11.close();
  957.     file12.close();
  958.     file13.close();
  959.     file14.close();
  960.     file15.close();
  961.     file_temp.close();
  962.  
  963.     clock_t end = clock();
  964.  
  965.     double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
  966.  
  967.     std::cout << "# of steps: " << steps << "\n";
  968.     std::cout << "# of electrons collided each timesteps:" << Ne_collided << "\n";
  969.    
  970.     std::cout << "Average elastic collisions per timestep: " << static_cast<int>(el_coll_counter/steps) << "\n";
  971.     std::cout << "Average null collisions per timestep: " << static_cast<int>(null_coll_counter/steps) << "\n";
  972.     std::cout << "\n";
  973.  
  974.     std::cout << "Average triplet excitation collisions per timestep: " << static_cast<int>(exc1_coll_counter/steps) << "\n";
  975.     std::cout << "\n";
  976.  
  977.     std::cout << "Average superelastic collisions per timestep: " << static_cast<int>(super1_coll_counter/steps) << "\n";
  978.     std::cout << "\n";
  979.  
  980.     std::cout << "Average e-e collisions per timestep: " << static_cast<int>(ee_coll_counter/steps) << "\n";
  981.  
  982.     std::cout << "Elapsed time: %f seconds " << elapsed << "\n";
  983.  
  984.  
  985.     return 0;
  986.  
  987. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement