● Schrodinger 方程式による水素原子の動径波動関数

量子力学の発端となった水素ガスの輝線の系列の謎の法則には、 1913年にM.Bohrによってある程度理論的な説明が与えられたが、 物理学者たちは古典力学の限界を強く感じていた。 この難問に解決の糸口を与え、新たな物理学を創造した巨匠が W.HeisenbergとE.Schrodingerであることはあまりにも有名である。

Schrodingerは1926年に自身の提案したSchrodinger方程式を用いて 水素原子のエネルギー準位を見事に説明した。彼は数学の遺産である特殊関数 を駆使してこれを導いたのだが、現在の我々は計算機という強力な道具で Schrodinger方程式を解くことができる。いささか人間が退化したかの ように聞こえるかもしれないが、計算機を用いて問題を解くことには プログラムを学ぶこと以外にもそれなりの苦労が必要である。また 計算機を用いればもっと複雑な問題を解決することができ、過去の巨匠たち が想像すらできなかった新たな物理について知ることができるのである。

前置きが長くなったが、この節と続く何節かで原子中の電子の波動関数と エネルギー準位を計算する方法を学ぼう。 まず本節で1次元問題として Schrodinger 方程式から水素原子の1個の 電子の波動関数の動径部分とエネルギーを求める。次の節で 相対論的量子力学のDirac方程式を用いて同じ問題を解く。さらに次の 節で波動関数の球面部分を求めて3次元に広がる電子軌道の絵を描く。 そして最後の節で多電子原子の全電子の波動関数とエネルギーを求める ことにしよう。

■ 動径波動関数の基礎理論

▲ 動径波動関数のSchrodinger 方程式

原子番号 Z の原子核のCoulomb potential での Schrodinger 方程式は以下の通りである。

原子単位系で書き改める。

(1)

Potential V(r) が球対称であるため波動関数は動径部分と球面部分の 直積で表される。動径波動関数 R(r) よりも G(r)=rR(r) と定義した関数 G(r) の方が扱いやすいのでこれを求めることにする。 G(r) の方程式を取り出すと

(2)

となる。ここで l は軌道の角運動量である。 この方程式の右辺は原点の近くで発散傾向にあるので計算上扱いにくい。 そこで座標系を r=e^x と変換する。 さらに F≡ dG/dx として G の2階の微分方程式を G と F の x に関する2元1階の連立微分方程式に書き換える。 次の2式ができあがる。

(3)
(4)

この方程式の右辺は原点近くで発散しないことがわかる。 座標 x は原点 r=0 を表すことができないが、原点近傍を細かく、 遠方を粗く表すので計算を大幅に効率化する。 方程式を計算しやすいように変形することは大切なのである。


▲ 動径波動関数の解析解

原子核のCoulomb potentialのみの系の動径波動関数には解析解がある。 これは計算機による数値解の信頼性を確認する上で重要である。 信頼性を確認したら、解析解のないより複雑な系へと進むことができる。

原子核のCoulomb potential中での動径波動関数 R_{nl}(r) に r をかけた関数 G_{nl}(r) の解析解は次の通りである。

(5)
(6)

ここで L_{n+l}^{2l+1} は Laguerre陪多項式であり

(7)

である。いくつかの G_{nl}(r) を以下に載せる。

(8)
(9)
(10)
(11)
(12)
(13)

ここで大事な法則が2つある。potentialの種類に依らず、束縛状態の 動径波動関数 G_{nl}(r) は原点と無限遠において値が 0 である。 これは電子の確率密度の全空間での積分が有限であることに起因する。 またこれらの箇所以外にも n-l-1 箇所で値が 0 になる節(node)を 持つ。これは波動関数の直交性に起因する。 前者の法則は波動関数の境界条件に使われ、後者の法則は波動関数が 指定した量子数のものかを判定するのに使われる。


■ 動径波動関数の計算手法

▲ 基本手順

束縛状態の波動関数には境界条件が課さられエネルギーは離散的な準位しか 許されない。逆に、境界条件を満たす波動関数となるようなエネルギーを 探せばそれがエネルギー準位に他ならないのである。 これが計算機で Schrodinger 方程式を解く基本戦略である。 エネルギー E の初期暫定値を仮定して、以下の作業を繰り返す。

  1. 遠方から原点に向かって進んで、エネルギーがpotentialの上に出る所 r_{fit} を探す。その r_{fit} を堺にして領域を内側と外側に 分ける。この r_{fit} を適合点と呼ぶことにする。 外側の領域は十分に遠方な点 r_{far} を端とする。
  2. r → 0 の原点付近での G(r) の振舞いを微分方程式を近似して求めて r=0 付近の数点での G(r) の値を得る。 同様に r→ ∞ の遠方での振舞いも求めて r_{far} 付近 の数点での G(r) の値を得る。
  3. 内側、外側それぞれの端から適合点 r_{fit} に向けて微分方程式に 従って G(r) を解いていく。この解の精度をあげるため 解法にHamming法と呼ばれる予測子・修正子法の一種を採用する。
  4. 適合点で両側の G(r) の値が一致するように外側の G(r) を定数倍する。 ついでに全領域で G(r) を規格化する。
  5. 正しくないエネルギーでは、適合点で G(r) の1階微分が不連続と なっている。1階微分を不連続にするようなデルタ関数の摂動potential を考え、その摂動エネルギーからより正しいエネルギーに修正する。


動径波動関数を求める作業の概略図


▲ 端点での近似解

微分方程式から G,F を解き進むためには、端点での値、すなわち 境界条件を与える必要がある。用いる解法の都合上、端点のみでなく 端点周辺の4点での G,F の値が必要になっている。 端点近傍であることを利用して(2)式を 簡単な形に近似して解析解を筆算で求めて、端点近傍の値を計算する。 potentialが原点近傍で 1/r^2 より緩やかならば、 原点近傍の G,F の規格化されていない近似解は次の通りである。

(14)

potentialが遠方で十分小さければ、 遠方での G,F の近似解は次の通りである。

(15)

かなり大雑把な近似解であるが、端の条件としてはこれで十分である。


▲ Hamming法

水素原子の問題から離れて一般論として 微分方程式の数値解法のひとつであるHamming法を紹介する。

x の未知関数 y(x) の微係数が y'=f(x,y) と 与えられているとする。 4つの格子点での y の値 y_{i-1}, y_{i-2}, y_{i-3}, y_{i-4} が 既に求まっている時に、次の格子点での値 y_i をHamming法は 次の手順で計算する。

  1. 予測子(predictor)と呼ばれる量 y_p を計算する。

    (16)

  2. 補整子(modifier)と呼ばれる量 y_m を計算する。

    (17)

    ここで y_{pc} は予測子と後で登場する修正子との差であるが 始めは 0 としておく。
  3. 補整子での値を元に f(x_i,y_m) を計算して、 修正子(corrector)と呼ばれる量 y_c を計算する。

    (18)

  4. 予測子と修正子との差 y_{pc} を計算し、 y_{i} を計算する。

    (19)

  5. 得られた y_i を用いて f(x_i,y_i) を計算する。
1ステップを過去の何段階かの値を元に計算する手法を 多段階法と呼び、安定で精度の高い解を得ることができる。


▲ エネルギーの修正

内側、外側の両方からHamming法で G,F を 接続点 r_{fit} まで計算し、接続点での両者の G の値が 連続するように外側の G,F を定数倍する。エネルギーが正しくなければ G のrによる1階微分値 G'=F/r は連続しない。 仮に接続点にデルタ関数型のpotential Cδ(r-r_{fit}) がそこにあれば、1階微分値の屈折が起こる。これを逆に利用して G' も連続させるエネルギーの修正分を見積る。 簡単な計算によりデルタ関数の因子 C は次のように表される。

(20)

このデルタ関数のpotentialを摂動として、1次の摂動エネルギー E^1 は

(21)

となる。ただし G は規格化されているとした。この E^1 が G の1階微分を不連続にさせている余分なエネルギーに他ならない。 E から E^1 を引いたより正しいエネルギーを用いて、波動関数を最初から 計算しなおす。

■ プログラムの実装

プログラムを設計する準備が整った。ソースファイルは radschro.cc である。 まずは諸定数の定義である。

#include "cip.h"

//---- physical settings
const double  Z            = 1.0;

//---- experimental settings
const double DX           = 0.0625;     // size of a cell in x-space
const int    R_NUM        = 256;        // total number of grids
const int    R_FAR        = R_NUM-1;    // index of farthest grid
const int    R_O          = 160;        // index of Bohr radius
const double OUTPUT_R_MAX = 64.0;       // max radius output [a.u]
R_NUM が動径方向の格子点数である。この再遠方の格子点を簡潔に 表せるように R_FAR を用意しておく。 DX は x 座標系での セルの大きさ、 R_O はBohr半径に相当する所の格子点のindexである。 波動関数を出力する際には OUTPUT_R_MAX で定める距離より 遠くは除くことにする。

中心から各格子点までの距離をテーブルにしておこう。 Z によるスケール変換も考慮しておく。

//---- table of Radius
class RTable
{
  double r[R_NUM];
public:
  RTable( void ){
    for( int i=0 ; i<=R_FAR ; i++ ){
      r[i] = (1.0/Z)*exp(DX*(i-R_O));
    }
  }
  inline double operator [] ( int i ){
    return( r[i] );
  }
} R;

Potentialを管理するclassを用意しよう。ただし管理する物理量は potential V(r) に中心からの距離を掛けた量 U(r)=rV(r) である。 原子核のCoulomb potentialだけなら、 U=-Z であり、わざわざ classを用意するまでもないが、他のpotentialへの応用に備えてこの class Potential にまとめておこう。

class Potential
{
  double U[R_NUM];           // rV, potential muliplied by radius
public:
  Potential( void ){
    for( int i=0 ; i<=R_FAR ; i++ ){
      U[i] = -Z;             // Coulomb potential of nuclear
    }
  }
  double operator [] ( int i ){ return( U[i] ); }
};

プログラムの主題である動径波動関数を管理計算するclass Orbital を設計する。宣言部分は次のようになる。

class Orbital
{
  int    n, l;                   // quantum numbers
  double E;                      // energy of this orbital
  double G[R_NUM], F[R_NUM];     // wave function
public:
  void Assign( int _n, int _l );
  void Adjust( void );
  void Output( void );
private:
  inline double CalcDG( int i, double P[], double G, double F );
  inline double CalcDF( int i, double P[], double G, double F );
  int  PreInteg( double P[] );
  int  HammingA( double P[], int i_fit );
  int  HammingB( double P[], int i_fit );
  void Normalize( double a, int i_fit );
};
多くのmethodが必要になる。

まず Assign() method で軌道の量子数を設定する。 ついでに軌道エネルギーの見積りを与えておく。 この見積りには正解からわずかにずれた値を与えておこう。

//-- sets quantum numbers
void Orbital::Assign( int _n, int _l )
{
  n = _n;   l = _l;
  E = -0.5*Z*Z/(n*n)-0.01;  // a guess of the true energy
}

動径波動関数の計算の指揮をとる method Adjust() である。 エネルギー固有値と固有波動関数を捜し当てる。

//-- calculates radial wave function
void Orbital::Adjust( void )
{
  static double P[R_NUM];
  double GA, GB, LogA, LogB, dE;
  int true_node = n-l-1, node, i_fit;

  do{
    i_fit = PreInteg( P );                       // prepares integration
    node =  HammingA( P, i_fit );                // integrates from origin
    GA   =  G[i_fit];                            // stores G at the fitting grid
    LogA =  F[i_fit]/(R[i_fit]*G[i_fit]);        // stores logarithmic derivative of G at the fitting grid
    node += HammingB( P, i_fit );                // integrates from far
    GB   =  G[i_fit];                            // stores G at the fitting grid
    LogB =  F[i_fit]/(R[i_fit]*G[i_fit]);        // stores logarithmic derivative of G at the fitting grid
    Normalize( GA/GB, i_fit );                   // connects and normalize

    dE = -0.5*sqr(G[i_fit])*(LogB-LogA);         // estimates energy modification
    if( node != true_node ){                     // if node is violated
      dE = double(true_node-node)/(n*n*n);       // rough modification
    }else if( fabs(dE) > 1.0e-2 ){               // if still large error
      dE *= 0.5;                                 // smaller modification
    }

    E+=dE;                                       // modifies energy
    if( E>0.0 ){                                 // it can't be binding state
      fprintf( stderr, "Energy can not converge.\n" );
      exit(1);
    }
  }while( fabs(dE)>1e-8 );                       // repeats until the modification is sufficiently small

  fprintf( stderr, "Orbital energy of (%d %d) has converged to %16lf [a.u]\n", n, l, E );
}
static で用意した配列変数 P[] には (3)(4)式に登場する係数を格納する。 true_node には動径波動関数のこの量子数での真の節の数を与えて おき、後の計算で得られる節の数 node と比較する。 i_fit には内側外側の適合点のindexを格納する。 エネルギーとpotentialとの状況から適切な適合点の位置を求めておく 必要がある。

ループ冒頭の PreInteg() methodは以後の積分計算に必要な 諸量を計算する。適合点のindexを戻り値とする。 HammingA()HammingB() methodはそれぞれ内側外側から 波動関数を積分して構築する。計算結果の節の数と適合点の両側での G の値とその対数微分を記録しておく。 Normalize() methodは両側の波動関数を連結して全体を規格化する。 そして (21)式に従ってエネルギーの必要な修正分を 見積もる。 波動関数の節の数が真の数と異なる程エネルギーが大きくずれている 場合には修正分を大雑把なものにする。また一次摂動による修正分が やや大きい場合にはそれをそのまま使うと修正しすぎることがあるので 修正分を半分に変更する。束縛状態を求めているのでエネルギーが 0 を 越えるはずはないので、この場合、エネルギーの見積りがあまりに不適切 だったとして計算を中止する。 このような計算をエネルギー修正分が十分小さくなるまで繰り返す。 ここで登場した下請けmethod たちを設計していこう。

PreInteg() methodは G[],F[] の x による微係数を計算するのに 必要な係数の配列 P[] を(3)(4)式に 従って設定する。この時、potentialについての情報が必要になるので Potential classの object U を extern 宣言しておく。 さらに両端周辺の4点での G[],F[] の値を (14)(15)式に従って設定する。 そして接続点に適当なindexを P[] から判断して戻り値とする。

//---- prepares for integration
int Orbital::PreInteg( double P[] )
{
  extern Potential U;
  int i;

  for( i=0 ; i<=R_FAR ; i++ ){                     // sets parameters for differential equation
    P[i] = 2*R[i]*U[i] - 2*sqr(R[i])*E + l*(l+1);
  }
  for( i=0 ; i<4 ; i++ ){                          // sets G,F at near origin
    G[i] = pow(R[i],l+1);    F[i] = (l+1)*G[i];
  }
  double a = -sqrt(-2*E);                          // sets G,F at far away
  for( i=R_FAR ; i>R_FAR-4 ; i-- ){
    G[i] = exp(a*R[i]);      F[i] = (a*R[i])*G[i];
  }
  for( i=R_FAR ; P[i]>0.0 && i>=0 ; i-- );         // searches the cross grid and reports it as the fitting grid
  return( i );
}

次に G[],F[] の各点での微係数を計算する補助methodを 設計しよう。ここで先に計算した配列 P[] が使われる。

//---- represents derivative of G
inline double Orbital::CalcDG( int i, double P[], double G, double F ){
  return( F );
}
//---- represents derivative of F
inline double Orbital::CalcDF( int i, double P[], double G, double F ){
  return( P[i]*G + F );
}
CalcDG()CalcDF() の表向きの姿は似せているが、 CalcDG() での引数は実際には使われない。inline関数としたので このような無駄はコンパイラが自動的に取り除いてくれるであろう。

この微係数を計算するmethodを使って、 HammingA() methodは内側から G[],F[] を積分し、 HammingB() methodは外側から G[],F[] を積分する。 i_fit が両者の適合点のindexである。 両者ともその間にある G[] の節の数を報告する、

//---- integrates from origin to the fitting grid
int  Orbital::HammingA( double P[], int i_fit )
{
  int    i, node=0;
  double Gp, Fp, Gm, Fm, Gc, Fc, Gpc=0.0, Fpc=0.0, dGm, dFm;
  static double dG[R_NUM], dF[R_NUM];

  for( i=0 ; i<4 ; i++ ){                          // prepares derivatives at grids near origin
    dG[i] = CalcDG( i, P, G[i], F[i] );
    dF[i] = CalcDF( i, P, G[i], F[i] );
  }

  for( ; i<=i_fit ; i++ ){                         // integrates from the origin to the fitting grid
    Gp    = G[i-4] + (4.0*DX/3.0)*( 2*dG[i-1] - dG[i-2] + 2*dG[i-3] );    // calculates predictors
    Fp    = F[i-4] + (4.0*DX/3.0)*( 2*dF[i-1] - dF[i-2] + 2*dF[i-3] );
    Gm    = Gp - (112.0/121.0)*( Gpc );            // calculates modifiers
    Fm    = Fp - (112.0/121.0)*( Fpc );
    dGm   = CalcDG( i, P, Gm, Fm );                // calculates derivative at modifiers
    dFm   = CalcDF( i, P, Gm, Fm );
    Gc    = (9.0/8.0)*(G[i-1]) - (1.0/8.0)*(G[i-3])    // calculates correctors
          + (3.0*DX/8.0)*(dGm + 2*dG[i-1] - dG[i-2]);
    Fc    = (9.0/8.0)*(F[i-1]) - (1.0/8.0)*(F[i-3])
          + (3.0*DX/8.0)*(dFm + 2*dF[i-1] - dF[i-2]);
    Gpc   = Gp - Gc;                               // calculates difference between predictors and correctors
    Fpc   = Fp - Fc;
    G[i]  = Gc + (9.0/121.0)*Gpc;                  // calculates the next step
    F[i]  = Fc + (9.0/121.0)*Fpc;
    dG[i] = CalcDG( i, P, G[i], F[i] );            // calculates derivative at the next step
    dF[i] = CalcDF( i, P, G[i], F[i] );

    if( G[i]*G[i-1] < 0 ) node++;
  }
  return( node );
}
//---- integrates from far to the fitting grid
int Orbital::HammingB( double P[], int i_fit )
{
  int    i, node=0;
  double Gp, Fp, Gm, Fm, Gc, Fc, Gpc=0.0, Fpc=0.0, dGm, dFm;
  static double dG[R_NUM], dF[R_NUM];

  for( i=R_FAR ; i>R_FAR-4 ; i-- ){                // prepares derivatives at far grids
    dG[i] = CalcDG( i, P, G[i], F[i] );
    dF[i] = CalcDF( i, P, G[i], F[i] );
  }

  for( ; i>=i_fit ; i-- ){                         // integrates from far to the fitting grid
    Gp    = G[i+4] - (4.0*DX/3.0)*( 2*dG[i+1] - dG[i+2] + 2*dG[i+3] );    // calculates predictors
    Fp    = F[i+4] - (4.0*DX/3.0)*( 2*dF[i+1] - dF[i+2] + 2*dF[i+3] );
    Gm    = Gp - (112.0/121.0)*( Gpc );            // calculates modifiers
    Fm    = Fp - (112.0/121.0)*( Fpc );
    dGm   = CalcDG( i, P, Gm, Fm );                // calculates derivative at modifiers
    dFm   = CalcDF( i, P, Gm, Fm );
    Gc    = (9.0/8.0)*(G[i+1]) - (1.0/8.0)*(G[i+3])    // calculates correctors
          - (3.0*DX/8.0)*(dGm + 2*dG[i+1] - dG[i+2]);
    Fc    = (9.0/8.0)*(F[i+1]) - (1.0/8.0)*(F[i+3])
          - (3.0*DX/8.0)*(dFm + 2*dF[i+1] - dF[i+2]);
    Gpc   = Gp - Gc;                               // calculates difference between predictors and correctors
    Fpc   = Fp - Fc;
    G[i]  = Gc + (9.0/121.0)*Gpc;                  // calculates the next step
    F[i]  = Fc + (9.0/121.0)*Fpc;
    dG[i] = CalcDG( i, P, G[i], F[i] );            // calculates derivative at the next step
    dF[i] = CalcDF( i, P, G[i], F[i] );

    if( G[i]*G[i+1] < 0 ) node++;
  }
  return( node );
}
HammingA()HammingB() はよく似ているが indexの走る範囲や 参照先 DX にかかる符号などが違うことに注意しよう。

Normalize() methodは両領域の波動関数を連結しさらに規格化する。 引数 a には外側領域にかけるべき因子を与える。

//---- normalizes wave function
void Orbital::Normalize( double a, int i_fit )
{
  int i;
  double sum, norm;

  for( i=i_fit ; i<=R_FAR ; i++ ){
    G[i] *= a;    F[i] *= a;
  }
  for( i=0, sum=0.0 ; i<=R_FAR ; i++ ){
    sum += sqr(G[i])*R[i]*DX;
  }
  for( i=0, norm=sqrt(1.0/sum) ; i<=R_FAR ; i++ ){
    G[i] *= norm;    F[i] *= norm;
  }
}
Output() methodで動径波動関数の値を出力する。 r が OUTPUT_R_MAX を越えたら出力をやめる。 Redirectionでファイルに保存して、gnuplotなどでプロットしよう。
//---- outputs wave function
void Orbital::Output( void )
{
  for( int i=0 ; i<=R_FAR ; i++ ){
    printf("%lf %lf\n", R[i], G[i] );
    if( R[i]>OUTPUT_R_MAX ) break;
  }
  printf("\n");
}
最後に、 PotentialOrbital のobjectを生成し、 main() 関数でいくらかの量子数 n,l について計算を実行させる。
//---- definition of fundermental objects
static Potential U;
static Orbital   O;

//---- main function
int main( void )
{
  for( int n=1 ; n<=3 ; n++ ){
    for( int l=0 ; l<n ; l++ ){
      O.Assign( n, l );
      O.Adjust();
      O.Output();
    }
  }
  return(0);
}
下にこのデータをGnuplotで描画した図を載せる。


このプログラムが計算した種々の動径波動関数



  • 次へ
  • 目次
    Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
    渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp