Geant4 Cross Reference |
1 // 1 2 // ******************************************* 3 // * License and Disclaimer 4 // * 5 // * The Geant4 software is copyright of th 6 // * the Geant4 Collaboration. It is provided 7 // * conditions of the Geant4 Software License 8 // * LICENSE and available at http://cern.ch/ 9 // * include a list of copyright holders. 10 // * 11 // * Neither the authors of this software syst 12 // * institutes,nor the agencies providing fin 13 // * work make any representation or warran 14 // * regarding this software system or assum 15 // * use. Please see the license in the file 16 // * for the full disclaimer and the limitatio 17 // * 18 // * This code implementation is the result 19 // * technical work of the GEANT4 collaboratio 20 // * By using, copying, modifying or distri 21 // * any work based on the software) you ag 22 // * use in resulting scientific publicati 23 // * acceptance of all terms of the Geant4 Sof 24 // ******************************************* 25 // 26 // G4DNADoubleIonisationModel.cc 27 // 28 // Created at 2024/04/03 (Thu.) 29 // Author: Shogo OKADA @KEK-CRC (shogo.okada@ 30 // 31 // Reference: J.Meesungnoen et. al, DOI: 10.1 32 // 33 34 #include "G4DNADoubleIonisationModel.hh" 35 36 #include "G4PhysicalConstants.hh" 37 #include "G4SystemOfUnits.hh" 38 #include "G4UAtomicDeexcitation.hh" 39 #include "G4LossTableManager.hh" 40 #include "G4DNAChemistryManager.hh" 41 #include "G4DNAMolecularMaterial.hh" 42 43 #include "G4IonTable.hh" 44 #include "G4GenericIon.hh" 45 #include "G4DNARuddAngle.hh" 46 #include "G4DeltaAngle.hh" 47 #include "G4Exp.hh" 48 49 #include <sstream> 50 51 namespace { 52 53 G4DNAWaterIonisationStructure water_structure; 54 55 // parameters for rejection function 56 struct FuncParams { 57 G4double Bj_energy; 58 G4double alpha_const; 59 G4double beta_squared; 60 G4double velocity; 61 G4double correction_factor; 62 G4double wc; 63 G4double F1; 64 G4double F2; 65 G4double c; 66 }; 67 68 //-------------------------------------------- 69 void setup_rejection_function(G4ParticleDefini 70 const G4int shel 71 { 72 73 // Following values provided by M. Dingfelde 74 const G4double Bj[5] 75 = { 12.60 * eV, 14.70 * eV, 18.40 * eV, 32 76 77 // Data For Liquid Water from Dingfelder (Pr 78 G4double A1{1.02}, B1{82.0}, C1{0.45}, D1{-0 79 B2{11.6}, // Value provided by M. D 80 C2{0.60}, D2{0.04}, alpha_const{0.6 81 82 auto Bj_energy = Bj[shell]; 83 84 if (shell == 4) { 85 alpha_const = 0.66; 86 //Data For Liquid Water K SHELL from Dingf 87 A1 = 1.25; B1 = 0.5; C1 = 1.00; D1 = 1.00 88 A2 = 1.10; B2 = 1.30; C2 = 1.00; D2 = 0.00 89 // The following cases are provided by M. 90 Bj_energy = water_structure.IonisationEner 91 } 92 93 const auto mass = pdef->GetPDGMass(); 94 const auto tau = ekin * electron_mass_c2 / 95 const auto A_ion = pdef->GetAtomicMass(); 96 97 G4double v2; 98 G4double beta2; 99 100 constexpr G4double Ry = 13.6 * eV; 101 constexpr G4double xxx = 5.447761194E-02 * M 102 103 if (tau < xxx) { 104 v2 = tau / Bj_energy; 105 beta2 = 2.0 * tau / electron_mass_c2; 106 } else { 107 // Relativistic 108 v2 = (0.5 * electron_mass_c2 / Bj_energy) 109 * (1.0 - (1.0 / std::pow((1.0 + (t 110 beta2 = 1.0 - 1.0 / std::pow((1.0 + (tau / 111 } 112 113 const auto v = std::sqrt(v2); 114 const auto wc = 4.0 * v2 - 2.0 * v - (Ry / ( 115 116 const auto L1 = (C1 * std::pow(v, D1)) / (1. 117 const auto L2 = C2 * std::pow(v, D2); 118 const auto H1 = (A1 * G4Log(1.0 + v2)) / (v2 119 const auto H2 = (A2 / v2) + (B2 /(v2 * v2)); 120 const auto F1 = L1 + H1; 121 const auto F2 = (L2 * H2) / (L2 + H2); 122 123 // ZF. generalized & relativistic version 124 G4double max_energy; 125 126 if (ekin <= 0.1 * mass) { 127 // maximum kinetic energy , non relativist 128 max_energy = 4.0 * (electron_mass_c2 / mas 129 } else { 130 // relativistic 131 auto gamma = 1.0 / std::sqrt(1.0 - beta2); 132 max_energy = 2.0 * electron_mass_c2 * (gam 133 / (1.0 + 2.0 * gamma * (electron_mass_ 134 + std::pow(electron_mass_c2 / mass 135 } 136 137 const auto wmax = max_energy / Bj_energy; 138 auto c = wmax * (F2 * wmax+ F1 * (2.0 + wmax 139 / (2.0 * (1.0 + wmax) * (1.0 + w 140 c = 1.0 / c; // manual calculus leads to c = 141 142 par.Bj_energy = Bj_energy; 143 par.alpha_const = alpha_const; 144 par.beta_squared = beta2; 145 par.velocity = v; 146 par.correction_factor = 1.0; 147 par.wc = wc; 148 par.F1 = F1; 149 par.F2 = F2; 150 par.c = c; 151 152 } 153 154 //-------------------------------------------- 155 G4double rejection_function(G4ParticleDefiniti 156 const FuncParams& 157 { 158 159 const G4double Gj[5] = { 0.99, 1.11, 1.11, 0 160 161 proposed_ws /= par.Bj_energy; 162 163 auto rejection_term = 1.0 + G4Exp(par.alpha_ 164 / par.ve 165 rejection_term = (1.0 / rejection_term) * pa 166 167 if (pdef == G4Proton::ProtonDefinition()) { 168 169 // for protons 170 return rejection_term; 171 172 } else if (pdef->GetAtomicMass() > 4) { 173 174 // for carbon ions 175 auto Z = pdef->GetAtomicNumber(); 176 auto x = 100.0 * std::sqrt(par.beta_square 177 auto zeff = Z * (1.0 - G4Exp(x * (-1.316 + 178 179 rejection_term *= (zeff * zeff); 180 181 return rejection_term; 182 183 } 184 185 // for alpha particles 186 auto zeff = pdef->GetPDGCharge() / eplus + p 187 rejection_term *= (zeff * zeff); 188 189 return rejection_term; 190 191 } 192 193 //-------------------------------------------- 194 G4double proposed_sampled_energy(const FuncPar 195 { 196 const auto rval = G4UniformRand(); 197 198 auto proposed_ws = par.c * (par.F1 * par.F1 199 + 2.0 * rval * (par.F2 - 200 201 proposed_ws = -par.F1 * par.c + 2.0 * rval + 202 203 proposed_ws /= (par.c * (par.F1 + par.F2) - 204 205 proposed_ws *= par.Bj_energy; 206 207 return proposed_ws; 208 } 209 210 } // end of anonymous namespace 211 212 //============================================ 213 214 // constructor 215 G4DNADoubleIonisationModel::G4DNADoubleIonisat 216 const G4ParticleDefinition*, const G4String& 217 : G4VEmModel(model_name), 218 is_initialized_(false) 219 { 220 water_density_ = nullptr; 221 222 model_elow_tab_[1] = 100 * eV; 223 model_elow_tab_[4] = 1.0 * keV; 224 model_elow_tab_[5] = 0.5 * MeV; // For A = 3 225 226 verbose_level_ = 0; 227 228 // Define default angular generator 229 SetAngularDistribution(new G4DNARuddAngle()) 230 231 // Mark this model as "applicable" for atomi 232 SetDeexcitationFlag(true); 233 atom_deex_ = nullptr; 234 particle_change_ = nullptr; 235 236 // Selection of stationary mode 237 stat_code_ = false; 238 239 // True if use champion alpha parameter 240 use_champion_param_ = false; 241 242 // Double-ionization energy 243 energy_threshold_ = 40.0 * eV; 244 } 245 246 //-------------------------------------------- 247 G4DNADoubleIonisationModel::~G4DNADoubleIonisa 248 { 249 for (const auto& x : xs_tab_) { 250 G4DNACrossSectionDataSet* table = x.second 251 if (table) { delete table; } 252 } 253 } 254 255 //-------------------------------------------- 256 void G4DNADoubleIonisationModel::Initialise( 257 const G4ParticleDefinition* particle, const 258 { 259 if (verbose_level_ > 3) { 260 G4cout << "Calling G4DNADoubleIonisationMo 261 } 262 263 proton_def_ = G4Proton::ProtonDefinition(); 264 alpha_def_ = G4DNAGenericIonsManager::Inst 265 carbon_def_ = G4IonTable::GetIonTable()->Ge 266 267 constexpr G4double kScaleFactor = 1.0 * m * 268 269 mioni_manager_ = new G4DNAMultipleIonisation 270 271 G4double Z{0.0}, A{0.0}; 272 G4String alpha_param_file{"dna/multipleionis 273 274 if (particle == proton_def_) { 275 276 // *************************************** 277 // for protons 278 const auto& proton = proton_def_->GetParti 279 elow_tab_[proton] = model_elow_tab_[1]; 280 eupp_tab_[proton] = 3.0 * MeV; 281 282 // load cross-section data for single ioni 283 auto xs_proton = new G4DNACrossSectionData 284 new G4LogLogInterpol 285 xs_proton->LoadData("dna/sigma_ionisation_ 286 xs_tab_[proton] = xs_proton; 287 288 // set energy limits 289 SetLowEnergyLimit(elow_tab_[proton]); 290 SetHighEnergyLimit(eupp_tab_[proton]); 291 292 if (!use_champion_param_) { 293 alpha_param_file = "dna/multipleionisati 294 } 295 296 Z = static_cast<G4double>(proton_def_->Get 297 A = static_cast<G4double>(proton_def_->Get 298 299 } else if (particle == alpha_def_) { 300 301 //**************************************** 302 // for alpha particles 303 const auto& alpha = alpha_def_->GetParticl 304 elow_tab_[alpha] = model_elow_tab_[4]; 305 eupp_tab_[alpha] = 23.0 * MeV; 306 307 // load cross-section data for single ioni 308 auto xs_alpha = new G4DNACrossSectionDataS 309 new G4LogLogInterpolat 310 xs_alpha->LoadData("dna/sigma_ionisation_a 311 xs_tab_[alpha] = xs_alpha; 312 313 // set energy limits 314 SetLowEnergyLimit(elow_tab_[alpha]); 315 SetHighEnergyLimit(eupp_tab_[alpha]); 316 317 if (!use_champion_param_) { 318 alpha_param_file = "dna/multipleionisati 319 } 320 321 Z = static_cast<G4double>(alpha_def_->GetA 322 A = static_cast<G4double>(alpha_def_->GetA 323 324 } else if (particle == G4GenericIon::Generic 325 326 // *************************************** 327 // for carbon ions 328 const auto& carbon = carbon_def_->GetParti 329 elow_tab_[carbon] = model_elow_tab_[5] * c 330 eupp_tab_[carbon] = 120.0 * MeV; 331 332 // load cross-section data for single ioni 333 auto xs_carbon = new G4DNACrossSectionData 334 new G4LogLogInterpolat 335 xs_carbon->LoadData("dna/sigma_ionisation_ 336 xs_tab_[carbon] = xs_carbon; 337 338 // set energy limits 339 SetLowEnergyLimit(elow_tab_[carbon]); 340 SetHighEnergyLimit(eupp_tab_[carbon]); 341 342 if (!use_champion_param_) { 343 alpha_param_file = "dna/multipleionisati 344 } 345 346 Z = static_cast<G4double>(carbon_def_->Get 347 A = static_cast<G4double>(carbon_def_->Get 348 349 } 350 351 // load alpha parameter 352 mioni_manager_->LoadAlphaParam(alpha_param_f 353 354 if (verbose_level_ > 0) { 355 G4cout << "G4DNADoubleIonisationModel is i 356 << "Energy range: " 357 << LowEnergyLimit() / eV << " eV - 358 << HighEnergyLimit() / keV << " keV 359 << particle->GetParticleName() 360 << G4endl; 361 } 362 363 water_density_ = G4DNAMolecularMaterial::Ins 364 G4Material::GetMaterial( 365 366 atom_deex_ = G4LossTableManager::Instance()- 367 368 if (is_initialized_) { return; } 369 370 particle_change_ = GetParticleChangeForGamma 371 is_initialized_ = true; 372 } 373 374 //-------------------------------------------- 375 G4double G4DNADoubleIonisationModel::GetLowEne 376 { 377 G4double elim{0.0}; 378 379 EnergyLimitTable::iterator itr = elow_tab_.f 380 if (itr != elow_tab_.end()) { elim = itr->se 381 382 return elim; 383 } 384 385 //-------------------------------------------- 386 G4double G4DNADoubleIonisationModel::GetUppEne 387 { 388 G4double elim{0.0}; 389 390 EnergyLimitTable::iterator itr = eupp_tab_.f 391 if (itr != eupp_tab_.end()) { elim = itr->se 392 393 return elim; 394 } 395 396 //-------------------------------------------- 397 G4double G4DNADoubleIonisationModel::CrossSect 398 const G4Material* material, const G4Particle 399 G4double ekin, G4double, G4double) 400 { 401 402 if (verbose_level_ > 3) { 403 G4cout << "Calling G4DNADoubleIonisationMo 404 << G4endl; 405 } 406 407 // Calculate total cross section for model 408 409 if (pdef != proton_def_ && pdef != alpha_def 410 return 0.0; 411 } 412 413 static G4double water_dens = (*water_density 414 415 const auto& pname = pdef->GetParticleName(); 416 417 const auto low_energy_lim = GetLowEnergyLimi 418 const auto upp_energy_lim = GetUppEnergyLimi 419 420 G4double sigma{0.0}; 421 if (ekin <= upp_energy_lim) { 422 423 if (ekin < low_energy_lim) { ekin = low_en 424 425 CrossSectionDataTable::iterator pos = xs_t 426 if (pos == xs_tab_.end()) { 427 G4Exception("G4DNADoubleIonisationModel: 428 "em0002", FatalException, 429 "Model not applicable to par 430 } 431 432 G4DNACrossSectionDataSet* table = pos->sec 433 if (table != nullptr) { 434 const auto a = mioni_manager_->GetAlphaP 435 sigma = table->FindValue(ekin) * a; 436 } 437 438 } 439 440 if (verbose_level_ > 2) { 441 442 std::stringstream msg; 443 444 msg << "---------------------------------- 445 msg << " G4DNADoubleIonisationModel - XS I 446 msg << " - Kinetic energy(eV): " << ekin/ 447 << pdef->GetParticleName() << "\n"; 448 msg << " - Cross section per water molecu 449 << sigma / cm / cm << "\n"; 450 msg << " - Cross section per water molecu 451 << sigma * water_dens / (1.0 / cm) << 452 msg << " G4DNADoubleIonisationModel - XS I 453 msg << "---------------------------------- 454 455 G4cout << msg.str() << G4endl; 456 457 } 458 459 return (sigma * water_dens); 460 461 } 462 463 //-------------------------------------------- 464 G4double G4DNADoubleIonisationModel::GenerateS 465 std::vector<G4DynamicParticle*>* vsec, const 466 const G4DynamicParticle* particle, G4int ion 467 G4double& theta, G4double& phi, G4double& sh 468 { 469 auto pdef = particle->GetDefinition(); 470 471 // get kinetic energy for a parent particle 472 auto ekin1 = particle->GetKineticEnergy(); 473 474 // sample kinetic energy for a secondary ele 475 auto ekin2 = RandomizeEjectedElectronEnergy( 476 477 // sample momentum direction for a secondary 478 auto sample_electron_direction = [this]( 479 const G4DynamicParticle* dp, G4double _e 480 const G4MaterialCutsCouple* mcc, G4doubl 481 482 G4ThreeVector locdir; 483 484 if (_theta > 0.0) { 485 486 auto costh = std::cos(_theta); 487 auto sinth = std::sqrt((1.0 - costh) * ( 488 locdir.set(sinth * std::cos(_phi), sinth 489 locdir.rotateUz(dp->GetMomentumDirection 490 491 } else { 492 493 locdir = GetAngularDistribution()->Sampl 494 dp, _ekin2, _Z, _ioni_shell, 495 _theta = locdir.theta(); 496 _phi = locdir.phi(); 497 498 } 499 500 return locdir; 501 }; 502 503 constexpr G4int Z = 8; 504 auto delta_dir = sample_electron_direction( 505 particle, ekin2, Z, ioni_ 506 507 // generate a secondary electron and put it 508 auto dp = new G4DynamicParticle(G4Electron:: 509 vsec->push_back(dp); 510 511 if (!atom_deex_ || ioni_shell != 4) { return 512 513 // ***************************************** 514 // Only atomic deexcitation from K shell is 515 516 constexpr auto k_shell = G4AtomicShellEnumer 517 const auto shell = atom_deex_->GetAtomicShel 518 519 // get number of secondary electrons in the 520 // before processing atomic deescitation 521 const auto num_sec_init = vsec->size(); 522 523 // perform atomic deexcitation process 524 atom_deex_->GenerateParticles(vsec, shell, Z 525 526 // get number of secondary electrons in the 527 // after processing atomic deescitation 528 const auto num_sec_final = vsec->size(); 529 530 if (num_sec_final == num_sec_init) { return 531 532 for (auto i = num_sec_init; i < num_sec_fina 533 534 auto e = ((*vsec)[i])->GetKineticEnergy(); 535 536 // Check if there is enough residual energ 537 if (shell_energy < e) { 538 539 // Invalid secondary: not enough energy 540 // Keep its energy in the local deposit 541 delete (*vsec)[i]; 542 (*vsec)[i] = 0; 543 544 continue; 545 546 } 547 548 // Ok, this is a valid secondary: keep it 549 shell_energy -= e; 550 } 551 552 // ***************************************** 553 554 return ekin2; 555 } 556 557 //-------------------------------------------- 558 void G4DNADoubleIonisationModel::SampleSeconda 559 std::vector<G4DynamicParticle*>* vsec, const 560 const G4DynamicParticle* particle, G4double, 561 { 562 563 if (verbose_level_ > 3) { 564 G4cout << "Calling SampleSecondaries() of 565 << G4endl; 566 } 567 568 // get the definition for this parent partic 569 auto pdef = particle->GetDefinition(); 570 571 // get kinetic energy 572 auto ekin = particle->GetKineticEnergy(); 573 574 // get particle name 575 const auto& pname = pdef->GetParticleName(); 576 577 // get energy limits 578 const auto low_energy_lim = GetLowEnergyLimi 579 580 // ***************************************** 581 // stop the transportation process of this p 582 // if its kinetic energy is below the lower 583 if (ekin < low_energy_lim) { 584 particle_change_->SetProposedKineticEnergy 585 particle_change_->ProposeTrackStatus(fStop 586 particle_change_->ProposeLocalEnergyDeposi 587 return; 588 } 589 // ***************************************** 590 591 constexpr G4int kNumSecondaries = 2; 592 constexpr G4double kDeltaTheta = pi; 593 594 G4int ioni_shell[kNumSecondaries]; 595 G4double shell_energy[kNumSecondaries]; 596 597 const auto scale_param = mioni_manager_->Get 598 G4double tot_ioni_energy{0.0}; 599 for (G4int i = 0; i < kNumSecondaries; i++) 600 ioni_shell[i] = RandomSelect(ekin, scale 601 shell_energy[i] = ::water_structure.Ionisa 602 tot_ioni_energy += shell_energy[i]; 603 } 604 605 if (ekin < tot_ioni_energy || tot_ioni_energ 606 return; 607 } 608 609 // generate secondary electrons 610 G4double theta{0.0}, phi{0.0}, tot_ekin2{0.0 611 for (G4int i = 0; i < kNumSecondaries; i++) 612 tot_ekin2 += GenerateSecondaries(vsec, cou 613 theta, ph 614 theta += kDeltaTheta; 615 } 616 617 // This should never happen 618 if (mioni_manager_->CheckShellEnergy(eDouble 619 G4Exception("G4DNADoubleIonisatioModel::Sa 620 "em2050", FatalException, "Negative lo 621 } 622 623 // ***************************************** 624 // update kinematics for this parent particl 625 const auto primary_dir = particle->GetMoment 626 particle_change_->ProposeMomentumDirection(p 627 628 const auto scattered_energy = ekin - tot_ion 629 630 // update total amount of shell energy 631 tot_ioni_energy = shell_energy[0] + shell_en 632 633 if (stat_code_) { 634 particle_change_->SetProposedKineticEnergy 635 particle_change_->ProposeLocalEnergyDeposi 636 } else { 637 particle_change_->SetProposedKineticEnergy 638 particle_change_->ProposeLocalEnergyDeposi 639 } 640 641 // ***************************************** 642 // generate double-ionized water molecules ( 643 const auto the_track = particle_change_->Get 644 mioni_manager_->CreateMultipleIonisedWaterMo 645 eDoubleIonisedMolecule, io 646 // ***************************************** 647 648 } 649 650 //-------------------------------------------- 651 G4double G4DNADoubleIonisationModel::Randomize 652 G4ParticleDefinition* pdef, G4double ekin, G 653 { 654 655 // 656 // based on RandomizeEjectedElectronEnergy() 657 // of G4DNARuddIonisationExtendedModel 658 // 659 660 ::FuncParams par; 661 ::setup_rejection_function(pdef, ekin, shell 662 663 // calculate maximum value 664 G4double emax{0.0}, val; 665 for (G4double en = 0.0; en < 20.0; en += 1.0 666 val = ::rejection_function(pdef, shell, pa 667 if (val <= emax) { continue; } 668 emax = val; 669 } 670 671 G4double proposed_energy, rand; 672 do { 673 // Proposed energy by inverse function sam 674 proposed_energy = ::proposed_sampled_energ 675 rand = G4UniformRand() * emax; 676 val = ::rejection_function(pdef, shell, pa 677 } while (rand > val); 678 679 return proposed_energy; 680 } 681 682 //-------------------------------------------- 683 G4int G4DNADoubleIonisationModel::RandomSelect 684 G4double ekin, G4double scale_param, const G 685 { 686 687 // 688 // based on RandomSelect() of G4DNARuddIonis 689 // 690 691 // Retrieve data table corresponding to the 692 CrossSectionDataTable::iterator pos = xs_tab 693 694 if (pos == xs_tab_.end()) { 695 G4Exception("G4DNADoubleIonisationModel::R 696 FatalException, "Model not applicable 697 } 698 699 G4DNACrossSectionDataSet* table = pos->secon 700 701 if (table != nullptr) { 702 703 // get total number of energy level 704 const auto num_component = table->NumberOf 705 706 auto* valuesBuffer = new G4double[num_comp 707 708 auto shell = num_component; 709 G4double value = 0.0; 710 711 while (shell > 0) { 712 shell--; 713 valuesBuffer[shell] = table->GetComponen 714 * scale_param; 715 value += valuesBuffer[shell]; 716 } 717 718 value *= G4UniformRand(); 719 720 shell = num_component; 721 722 while (shell > 0) { 723 shell--; 724 if (valuesBuffer[shell] > value) { 725 delete [] valuesBuffer; 726 return (G4int)shell; 727 } 728 value -= valuesBuffer[shell]; 729 } 730 731 delete [] valuesBuffer; 732 } 733 734 return 0; 735 } 736