非常に高エネルギーの光子が宇宙から地球の大気に突入してくることが ある。この光子は大気中でさまざまな連鎖反応を起こし、大量の粒子を生成 する。粒子の飛散の様子から、この連鎖反応は電磁シャワー (Electromagnetic shower)と呼ばれている。 本節ではこの電磁シャワーを計算機の中で再現してみよう。 反応のプロセスは確率によって決まるので、まさにMonte Carlo法の 威力が発揮される問題である。
反応のシミュレーションで必要な物理量は次の3種である。 反応までの自由行程、生成粒子のエネルギー、生成粒子の飛散方向。 これらの量はその分布関数からサンプルとして抽出される。 その分布関数は量子電磁力学(Quantum Electro Dynamics(QED))に よって導かれる。ここではこの理論が導いた分布関数をいかに利用するか を学ぶことにして、理論の詳細には立ち入らないことにする。
この3つの反応で実際の電磁シャワーを表すことができるのは 粒子のエネルギーが 100[MeV]以上の場合に限られる。 反応によってより低いエネルギーの粒子が生じた場合、その粒子の 反応については一切扱わないことにする。
全反応断面積(total cross section) σは入射する粒子が衝突して 反応する物質のいわば 的の面積である。この値と標的の密度により反応の起こる確率が求まり、 粒子の自由行程も求まる。 この3種の反応の全反応断面積はQEDにより導かれているが、さらに簡単な 以下の近似式を用いる。
制動放射 (1)
対生成 (2)
対消滅 (3)
ここで αは微細構造定数、Z は空気を構成する原子の平均陽子数、 r_e は古典電子半径である。m_p は陽電子の静止質量エネルギーである。 さらに、E_{p,e,gamma} はそれぞれ入射する陽電子、電子、光子の エネルギーである。(1)式の E_{g,min} は制動放射で放射される光子の 最低エネルギーであり 100[keV] 程度であることが知られている。
物質がつまった単位断面積の筒の中を粒子が通過することを考える。 筒の薄い区間に全反応断面積 σ の的が dN 個あれば、 この区間を粒子が的に当たらずに通過する確率は 1-σdN である。
dN を一定値として筒を薄い区間に分割すると、粒子が N個の的を 避けて通過できる確率は (1-σ dN)^{N/dN} である。 dN を十分小さく、Nを十分大きくとれば、これは指数関数 exp(-σN) となる。よって粒子が N 個の所まで通過して、 N+dN 個の所で的に当たって反応を起こす確率 f(N)dN は
となる。この分布に従う N のサンプルは逆変換法を使って [0,1] の 一様乱数 r から得られる。すなわち、 1-r もまた [0,1] の一様乱数になることを利用した。 物質の密度が一様 ρ_0 なら自由行程 l は l=N/ρ_0 で得られる。電磁シャワーの粒子は地上から非常に高い所( 100[km] くらい) から地上までを飛ぶので空気の密度が高さによって大きく異なる。 このため自由行程の計算でもこの密度変化を考慮しなくてはならない。
大気の密度 ρ(h) はおおよそ高度 h の指数関数となっている。粒子が 高度 h_o の地点から真上に対して θ の角度で飛ぶ場合には通過距離 lとその間の単位面積当たりの標的数 N の関係は次の通りである。
これと先にサンプルとして得た N から l 、すなわち自由行程が求まる。 ただし、ひとつ注意しなければならないことがあり、粒子が天の方向へ 放射する場合には、この式の log() の中身が負になることがある。 これは、粒子が宇宙へ帰っていくことを意味するので自由行程は無限の 長さとなる。微分反応断面積(differential cross section) dσ/dA は 反応によるある物理量 A がある値になる確率密度を表す。 これをその物理量がとる範囲について積分すれば全反応断面積となる。
生成粒子のエネルギーの微分反応断面積もQEDにより 導かれているが、さらに簡単な以下の近似式を用いる。 ただし以下に示す微分反応断面積が対象としている生成粒子とは、 制動放射では生成光子、 対生成では生成電子陽電子の半々の確率でどちらか1つで、 対消滅では生成光子の1つである。
制動放射 (9)
対生成 (10)
対消滅 (11)
ここで、 ε は生成粒子のエネルギーの入射粒子のエネルギーに対する比 である。 ε_o はその最低値であり、エネルギー運動量保存則の要請など からこのようになる。 ε の最大値はどれもほとんど 1 である。先に挙げた3つの微分反応断面積の第一因子はどれも ε に対して規格化 されている。第二因子は ε の範囲内で 0 以上、 1 以下である。第二因子 があるため逆変換法でサンプルを得ることができない。この場合には棄却法 と組み合わせることが有効である。次のようにして ε のサンプルを得る。
放射角度には polar angle θ とazimuthal angle φ の2種類が ありazimuthal angleは一様に全方向を向くので一様乱数で適当に決める ことができる。polar angleを決める方法は少々込み入っている。
対消滅では 2つの生成光子の1つの光子のエネルギーからエネルギー運動量 保存則で他方の光子のエネルギーと両方の光子のpolar angleがただひとつ に定まる。しかし、制動放射と対生成では反応に仮想光子が関わり、 そのエネルギーの自由度のため polar angleに分布が生じる。
これらの飛散方向の微分反応断面積もQEDにより導かれているが、これは かなり複雑である。大まかな傾向として polar angleは入射粒子の進行方向 よりやや広がったある角度に多く飛散する。この大まかな傾向を簡単に 表す分布関数として、この業界では次の式がよく用いられる。
ここで変数 u は制動放射の場合には、 u ≡ E_e θ_p/ m_{ep}、さらに、 a = 0.625 、 d = (10.4 + 16.9/Z )( 1 + ε ) 。先に紹介した分布関数は2つの項の和で表されており、さらに各項は 1次元の逆変換法ではサンプルを抽出できない。 前者の問題には成分法が使われ、後者の問題には2次元の逆変換法が 使われる。次の手順で u のサンプルを得る。
エネルギー運動量保存則から他方の粒子のエネルギーと飛散方向を求める。 入射粒子のエネルギーが電子の静止質量エネルギーより十分高い領域での 反応を考えているので、エネルギーと運動量の関係は質量を無視して E=√{p^2+m^2} ≒ p とみなすことができる。
制動放射では、仮想光子を介して原子核に反跳運動量が伝わるのだが 原子核は重いのでその運動量は無視できる。運動量保存則のみで 入射電子あるいは陽電子の減速されたエネルギーと飛散方向が計算できる。
対生成でも仮想光子を介して原子核に反跳運動量が伝わるのだが無視する。 先に求めた1つめの生成粒子を半々の確率で電子または陽電子として、 運動量保存則のみで他方の生成粒子のエネルギーと飛散方向が計算できる。 対消滅反応では反応前の電子の運動量を 0 とし、質量エネルギーを無視する。 先に求めた1つ目の光子のエネルギーからエネルギー運動量保存則の両方を 使って他方の光子のエネルギーと両方の光子の放射方向が計算できる。反応について準備が整ったのでプログラムを作り始めよう。 この種のプログラムは、粒子の数が不定であるためそのメモリと 計算作業の指揮管理が厄介になる。 メモリ管理にはリンクリストの機構が有効である。 さらにC++でobject指向で設計すると複雑なプログラムも 局所的によくまとまるので設計しやすく理解もしやすい。
ソースファイルはshower.cc である。 プログラムの始めはいつも通り諸定数の定義と簡単なinline関数の 定義である。
#include "cip.h" #include "nxgraph.h" //---- definition of fundermental constants //---- air setting const double Z_AIR = 7.2; // mean atomic number of atom in air const double LN_Z_AIR = 2.00; // log(Z_AIR) const double A_AIR = 14.4; // mean mass number of atom in air const double RHO_AIR = 1.2; // density of air [g/little]==[kg/m^3] const double C_AIR = 1.4e-4; // factor of rho decresing [1/m] const double SIGMA_O = ALPHA*Z_AIR*Z_AIR*R_E*R_E; // don't edit const double FACT_Z = 5.21-LN_Z_AIR/3.0; // don't edit //---- experimental setting const double INIT_PHOTON_HEIGHT = 5e4; // [m] const double INIT_PHOTON_ENERGY = 1e3; // [MeV] const double ENERGY_CUTOFF = 1e2; // least energy [MeV], below thie energy, particles are treated as dead const double BREMS_CUTOFF = 0.1; // least photon energy radiated by br const double FAR_AWAY = 1e5; // long distance [m] //---- particle type indicators const int PHOTON = 0; const int ELECTRON = 1; const int POSITRON = 2; //---- reaction indicators const int FATE_NON = 0; const int FATE_BRE = 1; const int FATE_PRO = 2; const int FATE_ANN = 3; //---- graphics settings const int WIN_HEIGHT = 256; const int WIN_WIDTH = 256; const int GROUND_HEIGHT= 16; const double YMAG = (WIN_HEIGHT-GROUND_HEIGHT)/INIT_PHOTON_HEIGHT; const double XMAG = YMAG*256; //---- Density of the air at given height inline double N_AIR( double h ){ return( (RHO_AIR*1000/A_AIR*N_A)*exp(-h*C_AIR) ); } //---- transform real coordinates into window coordinates inline int Wx( double x, double y, double z ){ return( WIN_WIDTH/2 + int(XMAG*(CV*x-SV*y)) ); } inline int Wy( double x, double y, double z ){ return( WIN_HEIGHT - GROUND_HEIGHT - int(YMAG*z) + int((XMAG/8)*(SV*x+CV*y)) ); }
今まで解説してきた生成粒子の飛散方向はすべて親粒子の方向を基準とした 相対的な方向であった。粒子の絶対的な位置を 知るためにはこの相対的な方向を基準座標系に対する絶対的な方向に 変換する必要がある。しかしその計算は単純ではない。 以下に設計するクラス Position と Direction で 3次元空間での位置と方向の管理と変換の計算を専門に執り行うようにする。
Position クラスは粒子の位置を3次元デカルト座標で記録し、 その足し上げ演算 operator += を定義する。
Direction クラスは粒子の方向とエネルギーを記録し、 種々の演算を定義する。//---- definition of class Position class Position { public: double x, y, z; inline Position( void ){} inline Position( const double& _x, const double& _y, const double& _z ){ x = _x; y = _y; z = _z; } inline void operator+=( const Position& _r ){ x += _r.x; y += _r.y; z += _r.z; } };
Polar angleとazimuthal angleを回転行列 rot[3][3] として記録する。 この行列はデカルト座標を表現基底として、 z軸向きのベクトルをその方向に合わせる回転変換の行列である。 property norm にはベクトルの長さではなくて 粒子のエネルギーを与える。//---- definition of class Direction class Direction { public: double norm; double rot[3][3]; Direction( void ){}; Direction( double _norm, double _theta, double _phi ){ norm = _norm; double ct = cos(_theta), st = sin(_theta); double cp = cos(_phi), sp = sin(_phi); rot[0][0] = +ct*cp; rot[0][1] = -sp; rot[0][2] = st*cp; rot[1][0] = +ct*sp; rot[1][1] = +cp; rot[1][2] = st*sp; rot[2][0] = -st ; rot[2][1] = 0; rot[2][2] = ct ; } inline Position operator*( const double& k ){ return( Position( rot[0][2]*k, rot[1][2]*k, rot[2][2]*k ) ); } Direction& Turn( const Direction& _dir ){ double t[3][3]; int i, j, k; // multiply matrix rot[][] by given _dir's matrix for( i=0 ; i<3 ; i++ ){ for( j=0 ; j<3 ; j++ ){ t[i][j] = 0.0; for( k=0 ; k<3 ; k++ ){ t[i][j] += _dir.rot[i][k] * rot[k][j]; } } } // update matrix rot[][] for( i=0 ; i<3 ; i++ ){ for( j=0 ; j<3 ; j++ ){ rot[i][j] = t[i][j]; } } return( *this ); } };
operator * は与えられた空間の長さのその方向の3次元ベクトルを 返す。
method Turn() は自身のobjectの回転行列に、引数に与えられた 別なobjectの回転行列を掛け合わせることで、2つの方向を足し合わせる。 つまり、右上の図の様に (θ',φ') として 生成粒子の方向を親粒子を基準として設定し、 親粒子の絶対的な方向 (θ,φ) を記録するobjectを 生成粒子の Turn() methodに 与えると生成粒子の絶対的な方向が計算されるわけである。
親粒子の絶対方向と生成粒子の親粒子の方向に対する相対方向
光子、電子、陽電子の3種の粒子のさまざまな管理を行うclassを設計する ことになるのだが、それらの管理方法は多くの部分で共通した仕組みとなり そうである。そこで共通する仕組みを提供する基本クラスをまず設計する。 名付けて Particle classである。まずその宣言部を見てみよう。
ここで宣言された各 propertyを紹介しよう。//---- declaration of class Particle class Particle { protected: char type; // particle name char fate; // reaction u_long color; // color Position ri, rf; // initial and final position Direction dir; // direction and energy double free_path; // length of its free path Particle* next; // pointer to next particle public: Particle( const Position& _r, const Direction& _dir, Particle* _next=NULL ); virtual ~Particle( void ); void Evolve( void ); protected: double FreePath( double sigma ); virtual void Reaction( void )=0; void Draw( void ); private: void CheckAlive( void ); };
次に methodを紹介しよう。
Particle() constructorは与えられた引数でこの粒子の各propertyを 初期化するだけで特に計算はしない。
~Particle() destructorはリンクリストでの次の粒子を消去する。 従って、先頭粒子の変数に対してdeleteを行うと再帰的にそのリストに 属する全粒子の変数が消去されることになる。//---- base constructor Particle::Particle( const Position& _r, const Direction& _dir, Particle* _next ){ rf = ri = _r; dir = _dir; next = _next; }
FreePath() methodは引数として受け取る反応断面積からこの粒子の 自由行程を確率的に求める。大気の密度が高さによって変化することを 考慮した計算を行う。粒子が宇宙に帰る状況も考慮する。//---- recursive destructor Particle::~Particle( void ){ if( next ) delete next; }
//---- calculate length of free path double Particle::FreePath( double sigma ) { double a = -C_AIR*dir.rot[2][2]; // factor of decreasing density toward this direction. double n = -log(Urand0())/sigma; // number of targets double r = 1+n*a/N_AIR(ri.z); // temporal value if( r<0 ) return( FAR_AWAY ); // r<0 means the particle goes back to the universe return( log(r)/a ); }
Evolve() methodはこの粒子をその生成地点から消滅地点まで 時間発展させ、 CheckAlive() methodにその粒子の生存を確認させ、 Reaction() methodに反応を起こさせる。さらに再帰的に次の粒子の Evolve() methodを呼び出す。これにより全粒子の時間発展が行われる ことになる。 Draw() methodは粒子の軌跡を再帰的に絵がく。
Reaction() methodはその宣言の仕方からもわかるように特殊な methodでありプログラム用語では純粋仮想 methodと呼ばれている。 要するに、このmethodの内容はこのParticle classでは定義せずに これを継承した派生 classで定義するということを示しているだけである。//---- recursive evolve void Particle::Evolve( void ) { rf += dir*free_path; CheckAlive(); Reaction(); if( next ) next->Evolve(); }
CheckAlive() methodはこの粒子のエネルギーが既定値を下回ったか、 地面に到達したかなどをチェックして、もしそうなら これ以上反応を起こさせないようにproperty fate を設定する。
Draw() methodは粒子の飛跡を再帰的に描く。//---- check whether this particle is still alive void Particle::CheckAlive( void ){ if( rf.z < 0 || dir.norm < ENERGY_CUTOFF ) fate = FATE_NON; }
Particle classの設計はこれで完了である。//---- draw trace of particle from birth point to reaction point void Particle::Draw( void ) { int xi = Wx( ri.x, ri.y, ri.z ), yi = Wy( ri.x, ri.y, ri.z ); int xf = Wx( rf.x, rf.y, rf.z ), yf = Wy( rf.x, rf.y, rf.z ); NXSetColor( color ); NXDrawLine( xi, yi, xf, yf ); NXDrawCircle( xf, yf, 4 ); if( next ) next->Draw(); }
続いてこれを継承した 派生classとして、光子、電子、陽電子を管理する classを設計する。まず宣言部である。
どの派生粒子のconstructorも受け取る引数の型は同じであり、受け取った 引数は即、基本 classのconstructorに渡される。 派生粒子の constructorではさらに type 、 color を適切に 設定し、粒子固有の反応による自由行程を計算し free_path 、 fate を設定する。つまりこの粒子の運命を誕生と同時に 決定するのである。//---- declaration of class Photon class Photon : public Particle { public: Photon( const Position& _r, const Direction& _dir, Particle* _next=NULL ); private: void Reaction( void ); void PairProduction( void ); }; //---- declaration of class Electron class Electron : public Particle { public: Electron( const Position& _r, const Direction& _dir, Particle* _next=NULL ); private: void Reaction( void ); void Bremsstrahlung( void ); }; //---- declaration of class Positron class Positron : public Particle { public: Positron( const Position& _r, const Direction& _dir, Particle* _next=NULL ); private: void Reaction( void ); void PairAnnihilation( void ); void Bremsstrahlung( void ); };
Reaction() methodは基本classで純粋仮想methodとして宣言して おいたので派生classで具体的な内容が与えられることが期待されている。 Reaction() methodは fate に従って反応の計算を行う methodを呼び出す。反応が起きない状況では何も呼び出さずに終る。
Bremsstrahlung() 、 PairAnnihilation() 、 PairProduction() のmethod はその名の通り反応の具体的な計算を 行い、生成粒子の変数を動的確保しリンクリストに追加する。この 操作がこのプログラムの要となっている。対生成の場合のこの操作を示す。
rf は親粒子の消滅位置であり、それを娘粒子の誕生位置として constructorに渡す。 dir_e, dir_p はいろいろ計算され た結果の各粒子の放射方向である。そして注目すべきは next である。 リンクリストの構成が下図のように変化する。next = new Electron( rf, dir_e, next ); next = new Positron( rf, dir_p, next );
生成粒子のリンクリストへの登録
反応がすべて終了したらデータ収集を行うのだがこの例では 省略している。最後に delete を走らせてメモリを解放する。 派生粒子classのmethodの内容を以下に示す。
最後に、プログラムの全体の流れを制御する main() 関数を設計する。 だが、実際にここで行うことはあまり無い。これも粒子管理のclassが よく自立しているからである。//---- definition of methods under class Photon //---- constructor for photon Photon::Photon( const Position& _r, const Direction& _dir, Particle* _next ):Particle( _r, _dir, _next ) { type = PHOTON; color = NX_ORANGE; free_path = FreePath( (28.0/9.0)*SIGMA_O*FACT_Z ); fate = FATE_PRO; } //---- select reaction void Photon::Reaction( void ) { switch( fate ){ case FATE_PRO : PairProduction(); break; } } //---- let photon react pair production void Photon::PairProduction( void ) { double eps; // friction of energy carried by radiated electron double Ene_g = dir.norm; // energy of parent photon double Ene_1, Ene_2; // energys of electron and positron double the_1, the_2; // polar angles of each particles double phi_1, phi_2; // azimuthal angles of each particles // get a sample of radiation energy double epso = M_ELE/Ene_g, lnepso = log(epso); do{ eps = epso*exp(-Urand()*lnepso); }while( !(Urand() < eps-1+3.0/(4.0*eps) ) ); Ene_1 = Ene_g*eps; // get a sample of radiation angle double A = 1.6*M_ELE/Ene_1, double D = 3.0; do{ if( Urand()<1.0/(1.0+D) ) the_1 = -log(Urand0()*Urand0())*A; else the_1 = -log(Urand0()*Urand0())*A/3; }while( !(the_1 < M_PI) ); phi_1 = 2*M_PI*Urand(); // calculate about another particle Ene_2 = hypot( Ene_1*sin(the_1), Ene_g-Ene_1*cos(the_1) ); the_2 = atan2( Ene_1*sin(the_1), Ene_g-Ene_1*cos(the_1) ); phi_2 = phi_1 - M_PI; // prepare new direction Direction dir_1( Ene_1, the_1, phi_1 ); Directionn dir_2( Ene_2, the_2, phi_2 ); // make child particles if( Urand() < 0.5 ){ // determine whether No.1 particle is electron or positron next = new Electron( rf, dir_1.Turn( dir ), next ); next = new Positron( rf, dir_2.Turn( dir ), next ); }else{ next = new Positron( rf, dir_1.Turn( dir ), next ); next = new Electron( rf, dir_2.Turn( dir ), next ); } } //---- definition of methods under class Electron //---- constructor for electron Electron::Electron( const Position& _r, const Direction& _dir, Particle* _next ):Particle( _r, _dir, _next ) { type = ELECTRON; color = NX_GRASS; free_path = FreePath( (16.0/3.0)*SIGMA_O*FACT_Z*(log(dir.norm/BREMS_CUTOFF)-0.0625) ); fate = FATE_BRE; } //---- select reaction void Electron::Reaction( void ) { switch( fate ){ case FATE_BRE: Bremsstrahlung(); break; } } //---- let electron react bremsstrahlung void Electron::Bremsstrahlung( void ) { double eps; // friction of energy carried by radiated photon double Ene_e = dir.norm; // energy of parent electron double Ene_g, Ene_d; // energys of photon and decerated electron double the_g, the_d; // polar angles of each particles double phi_g, phi_d; // azimuthal angles of each particles // get a sample of radiation energy double epso = BREMS_CUTOFF/Ene_e, lnepso = log(epso); do{ eps = epso*exp(-Urand()*lnepso); }while( !(Urand() < (4.0/3.0)*eps*eps - eps + 1.0 ) ); Ene_g = Ene_e*eps; // get a sample of radiation angle double A = 1.6*M_ELE/Ene_e, D = (1.16+1.88/Z_AIR)*(1+eps); do{ if( Urand()<1.0/(1.0+D) ) the_g = -log(Urand0()*Urand0())*A; else the_g = -log(Urand0()*Urand0())*A/3; }while( !(the_g < M_PI) ); phi_g = 2*M_PI*Urand(); // calculate about another particle Ene_d = hypot( Ene_g*sin(the_g), Ene_e-Ene_g*cos(the_g) ); the_d = atan2( Ene_g*sin(the_g), Ene_e-Ene_g*cos(the_g) ); phi_d = phi_g - M_PI; // prepare new direction Direction dir_d( Ene_d, the_d, phi_d ); Direction dir_g( Ene_g, the_g, phi_g ); // make child particles next = new Electron( rf, dir_d.Turn( dir ), next ); next = new Photon( rf, dir_g.Turn( dir ), next ); } //---- definition of methods under class Positron //---- constructor for positron Positron::Positron( const Position& _r, const Direction& _dir, Particle* _next ):Particle( _r, _dir, _next ) { double gamma, path_ann, path_bre; type = POSITRON; color = NX_RED; gamma = dir.norm/M_ELE; path_ann = FreePath( M_PI*Z_AIR*R_E*R_E*(log(2*gamma)-1)/gamma ); path_bre = FreePath( (16.0/3.0)*SIGMA_O*FACT_Z*(log(dir.norm/BREMS_CUTOFF)-0.0625) ); if( path_ann < path_bre ){ free_path = path_ann; fate = FATE_ANN; }else{ free_path = path_bre; fate = FATE_BRE; } } //---- select reaction void Positron::Reaction( void ) { switch( fate ){ case FATE_BRE: Bremsstrahlung(); break; case FATE_ANN: PairAnnihilation(); break; } } //---- let electron react bremsstrahlung void Positron::Bremsstrahlung( void ) { double eps; // friction of energy carried by radiated photon double Ene_p = dir.norm; // energy of parent positron double Ene_g, Ene_d; // energys of photon and decerated positron double the_g, the_d; // polar angles of each particles double phi_g, phi_d; // azimuthal angles of each particles // get a sample of radiation energy double epso = BREMS_CUTOFF/Ene_p, lnepso = log(epso); do{ eps = epso*exp(-Urand()*lnepso); }while( !(Urand() < (4.0/3.0)*eps*eps - eps + 1.0 ) ); Ene_g = Ene_p*eps; // get a sample of radiation angle double A = 1.6*M_ELE/Ene_p, D = (1.16+1.88/Z_AIR)*(1+eps); do{ if( Urand()<1.0/(1.0+D) ) the_g = -log(Urand0()*Urand0())*A; else the_g = -log(Urand0()*Urand0())*A/3; }while( !(the_g < M_PI) ); phi_g = 2*M_PI*Urand(); // calculate about another particle Ene_d = hypot( Ene_g*sin(the_g), Ene_p-Ene_g*cos(the_g) ); the_d = atan2( Ene_g*sin(the_g), Ene_p-Ene_g*cos(the_g) ); phi_d = phi_g-M_PI; // prepare new direction Direction dir_d( Ene_d, the_d, phi_d ); Direction dir_g( Ene_g, the_g, phi_g ); // make child particles next = new Positron( rf, dir_d.Turn( dir ), next ); next = new Photon( rf, dir_g.Turn( dir ), next ); } //---- let positron react pair annhilation void Positron::PairAnnihilation( void ) { double eps; // friction of energy carried by one photon double Ene_p = dir.norm; // energy of parent positron double Ene_g1, Ene_g2; // energys of radiated photons double the_g1, the_g2; // polar angles of radiated photons double phi_g1, phi_g2; // azimuthal angles of radiated photons // get a sample of radiation energy double epso = M_ELE/(2*Ene_p), lnepso = log(epso); do{ eps = epso*exp(-Urand()*lnepso); }while( !(Urand() < 1-eps) ); Ene_g1 = Ene_p*(eps); the_g1 = acos( 1.0 - (1.0-eps)/((eps)*Ene_p/M_ELE) ); phi_g1 = 2*M_PI*Urand(); // calculate about another particle Ene_g2 = Ene_p*(1-eps); the_g2 = acos( 1.0 - (eps)/((1.0-eps)*Ene_p/M_ELE) ); phi_g2 = phi_g1-M_PI; // prepare new direction Direction dir_g1( Ene_g1, the_g1, phi_g1 ); Direction dir_g2( Ene_g2, the_g2, phi_g2 ); // make child particles next = new Photon( rf, dir_g1.Turn( dir ), next ); next = new Photon( rf, dir_g2.Turn( dir ), next ); }
int main( void ) { XEvent ev; NXOpenWindow( "Elemag Shower", WIN_WIDTH, WIN_HEIGHT ); NXEnable( NX_DOUBLEBUFFER ); Position r( 0, 0, INIT_PHOTON_HEIGHT ); Direction dir( INIT_PHOTON_ENERGY, M_PI, 0 ); do{ Particle* parent = new Photon( r, dir ); parent->Evolve(); NXClearWindow(); parent->Draw(); NXFlush(); delete parent; }while( NXCheckEvent( NX_WAIT, ev ) != KeyPress ); NXCloseWindow(); return(0); }