2010.10.18.
石立 喬

Visual C++ 2010 Express の易しい使い方(16)

―――― 複素数演算による等角写像と電気磁気学への応用 ――――


概要
 標準C++ライブラリの複素数演算用テンプレートクラスcomplexを応用して等角写像の計算を試みた。複素数演算は、複素関数論とか難しい理論もあるが、等角写像によって得た図形には電磁気学で馴染みの深いものが沢山ある。

等角写像
 等角写像(Conformal Mapping)は、局所的には角度を維持したまま、ある図形を別の形状に変換することを言う。変換には、元の座標系をz、写像後の座標系をwとして、複素数関数w=f(z)が用いられる。z座標の横と縦を、複素数の実部と虚部に見立て、複素数演算によりw座標の横(実部)と縦(虚部)を得る。

等角写像のいろいろ
 ここでは、次の4種類について紹介する。他にも多数あるので、興味ある者は、調べて、試みて欲しい。
1) zの二乗 ----------------- 基本的な等角写像の例で、放物線になる
2) zの平方根 --------------- これも基本的な等角写像に属し、双曲線になる
3) (1+exp(z))/(1-exp(z)) ---- 電磁気学における電気力線と等電位線のような図形になる
4) (z-1)/(z+1) -------------- 伝送線路の図式解法に使うSmith Chartのような図形になる

等角写像のプログラムと得られた結果
 z平面上の位置をw平面に1対1に対応させて描画する。z平面上の横軸(実部)と縦軸(虚部)を明確にするために、横軸は赤に、縦軸は緑で描いてある。各プログラムの流れは単純で、写像後の図形の原点位置を設定したり、倍率を変えたりする内容が中心である。
具体的には、以下の内容から成る。
1) 元となるz平面を画面に表示する(必要なわけではないので、zの二乗とzの平方根以外は、変換後の図形のみ)。
2) z平面の位置を複素数zに設定する(z.real(i)などで)。
3) w=f(z)を、複素数演算用オペレータを用いて計算し、w平面に変換する。
4) 得られた複素数wを実部と虚部に分け、適切な倍率ratioを掛け、座標の原点位置を動かしたりして見やすく画面に表示する。
 
Form1.hの頭の部分に、#include <complex>と、using namespace std;を記述しておく。

zの二乗のマッピング
 これと言って特別の応用は考えられないが、w=f(z)の簡単な例として採り上げる。全体のプログラムは下記の通りである。結果を図1に示す。図形間の対応を分かりやすくするために、Thread::Sleep(5)を用いて、描画を遅くした。そのために、名前空間System::Threadingを記述してある。

#include <complex>
using namespace std;
using namespace System::Threading;

private: System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) {

    Graphics^ g=e->Graphics;

    int X0=120,Y0=240;  //原図形位置
    int X1=360;      //等角写像変換後の図形位置
    
    int i,j;
    float x,y;
    float x1,y1,x1_old,y1_old;
    float x2,y2,x2_old,y2_old;
    float angle;
    float ratio=0.008f;
    complex<float> z,w;

    Pen^ pen1;

    //座標を描く
     for(j=-100;j<=100;j+=10)
       for(i=-100;i<=100;i++){
   
          //原座標を描く
          if(i==-100){
             x1_old=X0+i;
             y1_old=Y0-j;
          }
          else{
             x1=X0+i;
             y1=Y0-j;
             g->DrawLine(Pens::Red,x1_old,y1_old,x1,y1);
             x1_old=x1;
             y1_old=y1;
          }

          //変換後の座標を描く
          z.real(i);
          z.imag(j);
          w=z*z;
          if(i==-100){
             x2_old=X1+w.real()*ratio;
             y2_old=Y0-w.imag()*ratio;
          }
          else{
              x2=X1+w.real()*ratio;
              y2=Y0-w.imag()*ratio;
              g->DrawLine(Pens::Red,x2_old,y2_old,x2,y2);
              x2_old=x2;
              y2_old=y2;
          }
       }

    for(i=-100;i<=100;i+=10)
       for(j=-100;j<=100;j++){

          //原座標を描く
          if(j==-100){
             x1_old=X0+i;
             y1_old=Y0-j;
          }
          else{
             x1=X0+i;
             y1=Y0-j;
             g->DrawLine(Pens::Green,x1_old,y1_old,x1,y1);
             x1_old=x1;
             y1_old=y1;
          }

          //変換後の座標を描く
          z.real(i);
          z.imag(j);
          w=z*z;
          if(j==-100){
             x2_old=X1+w.real()*ratio;
             y2_old=Y0-w.imag()*ratio;
          }
          else{
             x2=X1+w.real()*ratio;
             y2=Y0-w.imag()*ratio;
             g->DrawLine(Pens::Green,x2_old,y2_old,x2,y2);
             x2_old=x2;
             y2_old=y2;
          }
       }

    //円を描く
    for(int type=0;type<=1;type++){
       for(angle=0.0;angle<(2*Math::PI);angle+=0.01){

          //円を表す式
          if(type==0){   //type=0の場合は、中心が原点)にあって、Blueで表す
             x=Math::Cos(angle)*90;
             y=Math::Sin(angle)*90;
             pen1=gcnew Pen(Brushes::Blue,2);
          }
          else{       //type=1の場合は、中心が(10,10)にあって、Oliveで表す
             x=Math::Cos(angle)*90+10;
             y=Math::Sin(angle)*90+10;
             pen1=gcnew Pen(Brushes::Olive,2);
          }

          //原座標に円を描く
          x1=X0+x;
          y1=Y0-y;
          if(angle==0.0){
             x1_old=x1;
             y1_old=y1;
          }
          else{
             g->DrawLine(pen1,x1_old,y1_old,x1,y1);
             x1_old=x1;
             y1_old=y1;
          }

          //変換後の座標に円を描く
          z.real(x);
          z.imag(y);
          w=z*z;
          x2_old=X1+w.real()*ratio;
          y2_old=Y0-w.imag()*ratio;
          if(angle==0.0){
             x2_old=x2;
             y2_old=y2;
          }
          else{
             g->DrawLine(pen1,x2_old,y2_old,x2,y2);
             x2_old=x2;
             y2_old=y2;
          }

          Thread::Sleep(5);  //軌跡が分かるようにゆっくりと描く
       }
    }

}

 得られた結果を図1に示す。原点を中心に描いた円は、円にマッピングされるが、原点から外れた円は、NTTのロゴマークに似た形になる。


図1 w=z*z によってマッピングした結果


zの平方根のマッピング
 これも応用は限られるが、w=f(z)の簡単な例として採り上げる。複素数の平方根は二つあるが、正のみを採っている。前記のプログラムと異なる部分のみを示す。

   float ratio=10.0f;
   w=sqrt(z);    //w=z*zとあった変換式を変更する
   if(j==-100 || j==0)    //if(j=-100) に追加

   //円を表す式
   if(type==0){   //type=0の場合は、中心が原点にあって、Blueで表す
      x=Math::Cos(angle)*40;
      y=Math::Sin(angle)*40;
      pen1=gcnew Pen(Brushes::Blue,2);
   }
   else{       //type=1の場合は、中心が(30,30)にあって径が大きく、Oliveで表す
      x=Math::Cos(angle)*60+30;
      y=Math::Sin(angle)*60+30;
      pen1=gcnew Pen(Brushes::Olive,2);
   }

 得られた結果を図2に示す。ここでも、原点を中心に描いた円は、円にマッピングされるが、原点から外れた円は円にならない。


図2 w=sqrt(z) によってマッピングした結果


(1+exp(z))/(1-exp(z))のマッピング
 これは、電磁気学の電気力線と等電位線を示すといわれている。しかし、実際に、それらしく見せるには、z.real(i*Math::PI/100)やz.imag(j*Math::PI/100)によって、zの範囲を与える必要がある。点を打つのではなく、前の点から線を引くので、描画範囲を設定するのに、flagを用いて制御した。プログラムは一部のみを示し、結果を図3に示す。
等角写像によるマッピングを理解するために、意図的に、z.real(i/100)やz.imag(j/100)を用いた場合を図4に示す。複素数演算は複素数同士でないと使用できないので、1に対応する複素数をone として定義してある。

   int X0=220,Y0=170;
   int WIDTH=400,HEIGHT=300;

   float i,j;
   float ratio=50.0f;
   complex<float> one(1.0,0.0);
   int flag=0;   //描画範囲外を指定するとセットされるフラグ

   //変換後の座標
   for(j=-100;j<=100;j+=10)
      for(i=-100;i<=100;i+=0.5f){
         z.real(i*Math::PI/100);
         z.imag(j*Math::PI/100);
         w=(one+exp(z))/(one-exp(z));   //変換式
         if(i==-100 || flag==1){
            x_old=X0+w.real()*ratio;
            y_old=Y0-w.imag()*ratio;
            flag=0;
         }
         else{
            x=X0+w.real()*ratio;
            y=Y0-w.imag()*ratio;
            if(x>X0-WIDTH/2 && x<X0+WIDTH/2 && y>Y0-HEIGHT/2 && y<Y0+HEIGHT/2){
               g->DrawLine(Pens::Red,x_old,y_old,x,y);
               x_old=x;
               y_old=y;
            }
            else flag=1;
         }
      }
   for(i=-100;i<=100;i+=10)
      for(j=-100;j<=100;j+=0.5f){
         z.real(i*Math::PI/100);
         z.imag(j*Math::PI/100);
         w=(one+exp(z))/(one-exp(z));   //変換式
         if(i==-100 || flag==1){
            x_old=X0+w.real()*ratio;
            y_old=Y0-w.imag()*ratio;
            flag=0;
         }
         ------ 同じ-------
      }

}

 得られた結果を図3と図4に示す。図4によれば、電磁気学に出てくる図形がどのようにして生まれたかが分かる。


図3 w= (1+exp(z))/(1-exp(z)) によってマッピングした結果



図4 z.real(i*Math::PI/100)などの代わりにz.real(i/100)などを用いた場合


(z-1)/(z+1)のマッピング
 これは、マイクロ波領域などでの伝送線路の図式解法に用いられるSmith Chartを示すといわれている。実用的なSmith Chartとして使うためには、まだ多くの改良点が残っている。Smith Chartもどきというべきであろう。プログラムは一部のみを示し、結果を図5に示す。

   double ratio=160.0f;

   //変換後の座標
   for(j=-100;j<=100;j+=0.25)
      for(i=0;i<=100;i+=0.02){
         z.real(i);
         z.imag(j);
         w=(z-one)/(z+one);    //変換式
         if(i==0){
            old_x=X0+w.real()*ratio;
            old_y=Y0-w.imag()*ratio;
         }
         else{
            x=X0+w.real()*ratio;
            y=Y0-w.imag()*ratio;
            g->DrawLine(Pens::Red,old_x,old_y,x,y);
            old_x=x;
            old_y=y;
         }
      }
   for(i=0;i<=100;i+=0.25)
      for(j=-100;j<=100;j+=0.02){
         ----- 同じ------
      }
}


図5 w=(z-1)/(z+1) によってマッピングした結果


Smith Chart
 伝送線路の終端条件により、どのように反射係数が変わるかを簡単に表示できる計算図表である。逆に、反射係数が分かれば、どのような終端がされているかが分かる。
 反射係数(複素数)の計算式は、
    
で表される。Γは反射係数、ZLは終端インピーダンス(一般的には複素数)、Z0は伝送線路の特性インピーダンス(理想的には実数であるが、一般化すれば複素数)である。Z0で正規化すると、Γ=(Z-1)/(Z+1)の形となり、ここで紹介したマッピングになる。

「Visual C++ の勉強部屋」(目次)へ