前のブログ中で双極放射について言及した. 実は約9年くらい前ではあるが, Java による「双極放射のシュミレーション」を行うアニメーションプログラムを書いた. 「シミュレーション」と言えるような厳密なものではないが, 一応は双極放射の振舞いを視覚的に知ることは出来るのではないかと思う. 明らかに初期画面の設定部分にバグが存在するし, 正当でない部分があるとは思うが, 自分のパソコン環境では一応はそれらしく動くように見える. そこで取り敢えずは, そのソースプログラムを提示しておこう. もしバグの修正が出来たならば, プログラム更新しようと思う.
【 追伸 】 初期画面の不具合を修正したものに付け替えました.この修正版では,「半波長だけ離して複数の双極子を並べると放射場に指向性が生じる」ことをシュミレーション出来るような補強もしました (ただし双極子が4本の場合は実行結果の様子のみを示す).不具合の修正は初期画面以外にも色々行ったので, ソースプログラムは2つとも全部入れ替えました.なので, もし自分のPC上でコンパイルしてプログラム実行する際には, もう一度示された手順をやり直して下さい.
まずは, プログラムを書く際に必要となる数式を求めておこう. ブログ記事「電磁波の放射について」中の「双極放射」での式 (20) は次であった:
ここで, 電荷
ただし
この双極モーメントが原点 O にあるとき, そこから
ただし,
参考までに, もし観測地点
従って, 方向ベクトル
従って, このような 3 次元空間の一般的な方向では, 観測地点
よって, 観測地点が

図 3. 観測方向への電気双極子放射では, 電場は図の
以下は, 峯村吉泰 著「Javaによるコンピュータグラフィックス」(森北出版) に書かれていた Java プログラムを全面的に参照し, それに多少の修正をして, 上記の式 (4) が表していること, すなわち「原点 O にある双極子が単振動して電場
Java の実行環境のある方は, 例えば以下の手順で動かして見て頂きたい. ただし, しばらくプログラミングをしていなかったら, 最新の JDK では Java アプレットは廃止されているらしい. なので, 以前の開発実行環境 Java SE 8 や Eclipse 2018 などを用いて作業して頂きたい.
- まずは, 以下に示した 3 つのソースファイル prog.html, DipoleRadiation.java, DrawCanvas.java をコピー&ペーストして同名のファイルを作成する.
- 次に java のコマンド javac で 2つの Java ファイルをコンパイルする:
$ javac DipoleRadiation.java DrawCanvas.java
すると同じディレクトリに java のバイトコードである DipoleRadiation.class と DrawCanvas.class が作られる. - 最後に java のコマンド appletviewer でプログラムの実行を見る:
$ appletviewer prog.html
プログラムの動かし方は, 実行画面のボタンを見ればすぐ分かるであろう. まずは Y-angle を少し動かして傾きを変化させてから Start ボタンを押して開始して欲しい. Display を color mesh にするとカラフルな図形が見えると思う.
(1) 双極子が3本の場合:
(2) 双極子が4本の場合:
prog.html :
1 2 3 4 5 6 | <!-- prog.html --> < html > < body > < applet-desc main-class = "DipoleRadiation" width = "800" height = "800" > </ body > </ html > |
DipoleRadiation.java :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | // DipoleRadiation.java /************************************************************************************** * 双極子放射のシミュレーション * by ISSHY on September, 23, 2011 , revised on October, 22, 2011. revised on July, 5, 2020. <applet code="DipoleRadiation.class" width="800" height="800"></applet> ***************************************************************************************/ import java.applet.Applet; import java.awt.BorderLayout; import java.awt.Button; import java.awt.Choice; import java.awt.Color; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Image; import java.awt.Label; import java.awt.Panel; import java.awt.Scrollbar; import java.awt.TextField; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; public class DipoleRadiation extends Applet implements ActionListener,AdjustmentListener,ItemListener,Runnable{ // 使用するメンバー名 Scrollbar scrK,scrW,scrZ,scrY; int sliderW,sliderZ,sliderY; private DrawCanvas drw; private Thread thread; // スレッドオブジェクトの変数を定義 private Image img; private Graphics bg; private Button b_start, b_stop, b_clear; // アニメ開始と停止用ボタン名を定義 private Choice ch_select; private double PI = Math.PI; private double x [], y [], z []; private double xp [], yp [], xm [], ym []; int n= 0 ,flag= 0 ; int Width, Height,Nxmin,Nymin; int Nup= 200 ,Nbot= 20 ; // Nup=50,Nbot=20; int Stepmax= 200000 ; int Interval= 40 ; int t= 0 ,T= 10000 ; // 時間 double dt = 0.0003 ; // 時間のステップ幅 int N= 70 ,M=N; private int[][] E = new int [ 2 *N*M][ 3 ]; double k; // 波数 double w; // 角周波数 double c= 3.0 ; // 位相速度 double amp= 0.005 ; // 最大振幅 double region= 2 *PI; // 表示する範囲 double angleZ,angleY; // Z軸、Y軸の回転角度 String str; boolean error= false ; TextField text0; Thread animator; // GUI環境(対話型画面)の設定 public void init (){ setLayout( new BorderLayout()); Panel p= new Panel(); p.setLayout( new GridLayout( 2 , 5 , 1 , 1 )); b_start = new Button( "Start(Resume)" ); // RESUMEボタンを定義 b_stop = new Button( "Suspend" ); // SUSPENDボタンを定義 b_clear = new Button( "Clear" ); // CLEARボタン p.add(b_start); p.add(b_stop); // ボタンをパネルに取付け p.add(b_clear); ch_select = new Choice(); ch_select.addItem( "black mesh" ); ch_select.addItem( "color mesh" ); ch_select.addItem( "Three Dipole" ); ch_select.select( "black mesh" ); p.add( new Label( "Display=" ,Label.RIGHT)); p.add(ch_select); str= "black mesh" ; scrW = new Scrollbar( Scrollbar.HORIZONTAL); scrZ = new Scrollbar( Scrollbar.HORIZONTAL); scrY = new Scrollbar( Scrollbar.HORIZONTAL); scrW.setValues( 15 , 1 , 6 , 24 ); // スライダーの(初期値、幅、最小値、最大値) scrZ.setValues( 20 , 1 , 0 , 90 ); scrY.setValues( 25 , 1 , 0 , 90 ); scrW.setUnitIncrement( 1 ); // 方向ボタンの増分を設定 scrZ.setUnitIncrement( 1 ); scrY.setUnitIncrement( 1 ); sliderW= 15 ; sliderZ= 20 ; sliderY= 25 ; // 初期値を代入 w=(double)sliderW; k=w/c; angleZ=sliderZ*PI/ 180.0 ; angleY=sliderY*PI/ 180.0 ; // これが抜けていた!!!!!!!. // スクロールバーをパネルに取り付け p.add( new Label( "" ) ); // 空白のラベルを取り付け p.add( new Label( "" ) ); // 空白のラベルを取り付け p.add( new Label( "Frequency = " , Label.RIGHT )); p.add(scrW); p.add( new Label( "Z_Angle = " , Label.RIGHT )); p.add(scrZ); p.add( new Label( "Y_Angle = " , Label.RIGHT )); p.add(scrY); p.add( new Label( "" ) ); // 空白のラベルを取り付け add( "North" ,p); // パネルを上部に取り付け b_start.addActionListener( this ); // イベントリスナー登録 b_stop.addActionListener( this ); b_clear.addActionListener( this ); ch_select.addItemListener( this ); scrW.addAdjustmentListener( this ); scrZ.addAdjustmentListener( this ); scrY.addAdjustmentListener( this ); Width = 800 ; Height = 800 ; //Width= getSize().width; //Height = getSize().height; setBackground(Color.white); img = createImage(Width, Height); // イメージ画面 bg = img.getGraphics(); // 裏画面用グラフィックス animator = new Thread( this ); // ビューポートの設定と座標変換係数の設定 //orig = new double[] {0.0f, PI}; // 原点の位置 orig[0]=x0, orig[1]=y0 drw = new DrawCanvas( img, bg, Width, Height); // 描画オブジェクト生成 drw.viewPort(Nup, Nbot, false , region); // ビューポート変換 drw.gridNum(N, M, E); // 格子点番号付け inputData(n); drw.simPaint(x, y, z, angleZ, angleY,N, M, E,flag); } // GUI環境を用いたイベント処理 public void actionPerformed(ActionEvent e){ // イベントの定義 if (e.getSource() == b_start){ if (thread == null ) thread = new Thread( this ); thread.start(); } if (e.getSource() == b_stop) stop(); if (e.getSource() == b_clear){ thread = null ; drw.deleteImage(); n = 0 ; inputData(n); drw.simPaint(x, y, z, angleZ, angleY,N, M, E,flag); repaint(); } } public void adjustmentValueChanged(AdjustmentEvent ev){ if (ev.getSource() == scrW){ sliderW = scrW.getValue(); w=sliderW/ 1.0 ; k=w/c; System.out.println( "w=" +w); } else if (ev.getSource() == scrZ){ sliderZ = scrZ.getValue(); angleZ=sliderZ*PI/ 180.0 ; System.out.println( "angleZ=" +sliderZ); } else if (ev.getSource() == scrY){ sliderY = scrY.getValue(); angleY=sliderY*PI/ 180.0 ; System.out.println( "angleY=" +sliderY); } n= 0 ; inputData(n); drw.deleteImage(); drw.simPaint(x, y, z,angleZ,angleY,N, M, E,flag); repaint(); } public void itemStateChanged(ItemEvent ev){ if (ev.getSource()==ch_select){ str=ch_select.getSelectedItem(); if (str== "black mesh" ) flag= 0 ; else if (str== "color mesh" ) flag= 1 ; else flag= 2 ; } n= 0 ; inputData(n); drw.deleteImage(); drw.simPaint(x, y, z, angleZ, angleY,N, M, E,flag); repaint(); } // スレッドを実行する命令を記述 public void run(){ while ( thread != null ){ n++; inputData(n); if (n%Interval== 0 ){ drw.simPaint(x, y, z, angleZ, angleY,N, M, E,flag); repaint(); try { thread.sleep( 10 ); } catch (InterruptedException e){ } // 例外処理 } if (n>Stepmax) stop(); } } public void stop(){ thread = null ; } public void update( Graphics g ){ paint( g ); // 画面をクリアしないで paint を呼び出す } // メインルーチン public void paint( Graphics g ){ paintBG(); g.drawImage( img, 0 , 0 , this ); // 裏画面を表画面に } public void paintBG(){ bg.clearRect( 0 , 0 , Width, Height); inputData(n); drw.simPaint(x, y, z, angleZ, angleY,N, M, E,flag); } // 双極子場のデータ入力 ============================ public void inputData(int n){ int s = 0 , smax= 100000 ; x = new double [smax]; y = new double [smax]; z = new double [smax]; xp = new double [smax]; yp = new double [smax]; xm = new double [smax]; ym = new double [smax]; double delta = 2 *region/(double)( N - 1 ); double r1= 0.0 ,r2= 0.0 , r3= 0.0 ; for ( int i = 0 ; i < N; i++ ){ for ( int j = 0 ; j < M; j++ ){ x[s] = -region + delta * (double)i; y[s] = -region + delta * (double)j; xp[s] = -region -PI/k+ delta * (double)i; // x-方向へプラス半波長 (λ/2 = π/k) だけ位置がずれている双極子 xm[s] = -region +PI/k+ delta * (double)i; // x-方向へマイナス半波長 (-λ/2 = π/k) だけ位置がずれている双極子 r1 = x[s]*x[s] + y[s]*y[s]; r2 = xp[s]*xp[s] + y[s]*y[s]; r3 = xm[s]*xm[s] + y[s]*y[s]; r1 = Math.sqrt(r1); r2 = Math.sqrt(r2); r3 = Math.sqrt(r3); if (flag== 0 || flag== 1 ) { if (w*n*dt<=k*r1) z[s]= 0 ; else z[s]=amp*k*k*(double)Math.sin(w*n*dt-k*r1)/r1; } else { if (w*n*dt<=k*r1 && w*n*dt<=k*r2 && w*n*dt<=k*r3) z[s]= 0 ; else z[s]=amp*k*k*Math.sin(w*n*dt-k*r1)/r1+amp*k*k*Math.sin(w*n*dt-k*r2)/r2+amp*k*k*Math.sin(w*n*dt-k*r3)/r3; } s++; } } } } |
DrawCanvas.java :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | //DrawCanvas.java /************************************************************************ 各種グラフを描くための DrawCanvas クラス DrawCanvas.class : Last update : Feb. 3, 2006. All rights reserved, Copyright (C) 2002-2006, Kiyoshi Minemura modified by ISSHY : Oct. 23, 2011. revised on July 5, 2020 ************************************************************************/ import java.awt.Canvas; import java.awt.Color; import java.awt.Graphics; import java.awt.Image; class DrawCanvas extends Canvas{ private Graphics g; private Image img; private int Width, Height; // HTML から取得したウィンドウのサイズ private int Nup, Nbot, Nxmin, Nymin; private double xmin, xmax, ymin, ymax, rx, ry; private double xorig= 0.0 ,yorig= 0.0 ; // コンストラクタ public DrawCanvas( Image im, Graphics bg, int width, int height ){ img = im; g = bg; Width = width; Height = height; } // ビューポート変換のための初期化メソッド public void viewPort( int Nup, int Nbot, boolean aspect, double range){ // Nup,Nbot= 画面の上側、下側の非描画領域のサイズ(ピクセル値) // aspect= 縦横比を正確に描く場合は true // 対象とする図形の描画領域(ウィンドウポート)のサイズ xmin = xorig-range; xmax = xorig+range; ymin = yorig-range; ymax = yorig+range; //System.out.println("xmin="+xmin+", xmax="+xmax+", ymin="+ymin+" ymax="+ymax); transView( Nup, Nbot, aspect ); } // ビューポート変換メソッド public void transView( int Nup, int Nbelow, boolean aspect ){ // Nup,Nbelow = ウィンドウ上下の非描画域寸法(ピクセル単位) // aspect = 真偽値変数, アスペクト比を正しく表現する場合は "true" double ap = ( ymax - ymin )/( xmax - xmin ); double aw = (double)( Height - Nup - Nbelow )/Width; rx = Width/( xmax - xmin ); ry = 2 *( Height - Nup - Nbelow )/( ymax - ymin ); Nxmin = 0 ; Nymin = Nbelow; if ( aspect == true ){ if ( ap > aw ){ Nxmin = (int)( 0. 5f*( 1. 0f - ry/rx)*(double)Width ); rx = ry; } else { Nymin += (int)( 0.5 *( 1. 0f - rx/ry )*(double)Height ); ry = rx; } } } // x座標変換メソッド public int xtr( double x ){ // オーバーロードメソッド return (int)( rx*( x -xmin )) + Nxmin; } // y座標変換メソッド public int ytr( double y ){ // オーバーロードメソッド return Height - Nymin - (int)( rx*( y - ymin ) ); } // 変数 z[] の最小値を求めるメソッド public double Zmin( double z[] ){ double zmin = z[ 0 ]; for ( int k = 1 ; k < z.length; k++){ if ( zmin > z[k] ) zmin = z[k]; } return zmin; } // 変数 z[] の最大値を求めるメソッド public double Zmax( double z[] ){ double zmax = z[ 0 ]; for ( int k = 1 ; k < z.length; k++){ if ( zmax < z[k] ) zmax = z[k]; } return zmax; } // 3角形格子の節点番号を付けるメソッド public void gridNum( int N, int M, int E[][]){ int k = 0 ; for ( int j = 0 ; j < N -1 ; j++){ for ( int i = 0 ; i < M -1 ; i++){ E[k][ 0 ] = M*i + j; E[k][ 1 ] = M*i + j + M; E[k][ 2 ] = M*i + j + 1 ; k++; E[k][ 0 ] = M*i + j + 1 ; E[k][ 1 ] = M*i + j + M; E[k][ 2 ] = M*i + j + M + 1 ; k++; } } } // ======================================================================= // 簡易ペインタ法により曲面を網目状に描くメソッド // revised by ISSHY public void simPaint(double x[],double y[],double z[],double theta,double psi,int N,int M,int E[][],int flag){ // N×M 個の3次元データ(x,y,z) で、z は高さに相当 double ax [] = new double [ 3 ], ay [] = new double[ 3 ], az [] = new double [ 3 ]; int X [] = new int [ 3 ], Y [] = new int[ 3 ], Z [] = new int [ 3 ]; int k = 0 , n; int k0, k1, k2, k3; // 軸測投影図を描くデータための図形回転角度 double ct = Math.cos( theta ), st = Math.sin( theta ); double cp = Math.cos( psi ), sp = Math.sin( psi ); double zm; //double zmax = Zmax( z ), zmin = Zmin( z ); float Hcol, Scol= 1. 0f, Bcol = 1. 0f; int Xmin,Xmax,Ymin,Ymax; double lxmax= 1. 3f*(xmax+xorig),lxmin= 1. 3f*(xmin+xorig); double lymax= 1. 3f*(ymax+yorig),lymin= 1. 3f*(ymin+yorig); //System.out.println("lxmin="+lxmin+", lxmax="+lxmax+", lymin="+lymin+" lymax="+lymax); Xmin=xtr(lxmin*st); Xmax=xtr(lxmax*st); Ymin=ytr(-lymin*sp*ct); Ymax=ytr(-lymax*sp*ct); g.setColor(Color.black); g.drawLine(Xmin, Ymin, Xmax, Ymax); g.drawString( "X" , Xmax, Ymax);g.drawString( "-X" , Xmin, Ymin); Xmin=xtr(lxmin*ct); Xmax=xtr(lxmax*ct); Ymin=ytr(lymin*sp*st); Ymax=ytr(lymax*sp*st); g.drawLine(Xmin,Ymin,Xmax,Ymax); g.drawString( "Y" , Xmax, Ymax);g.drawString( "-Y" , Xmin, Ymin); for ( int j = N -2 ; j >= 0 ; j--){ for ( int i = M -2 ; i >= 0 ; i--){ k = 2 *( N - 1 )*j + 2 *i + 1 ; k0 = M*i + j; k1 = k0 + M; k2 = k1 + 1 ; zm = ( z[k0] + z[k1] + z[k2])/ 3.0 ; Hcol= -1. 5f*(float)(zm -1.0 ); for ( int repeat = 0 ; repeat < 2 ; repeat++){ if ( repeat == 1 ) k -= 1 ; for ( int m = 0 ; m < 3 ; m++){ n = E[k][m]; ay[m] = st*x[n] + ct*y[n]; az[m] = -sp*ct*x[n] + sp*st*y[n] + cp*z[n]; } if ( S_norm( ay, az ) == true ){ // visible when S_norm >= 0 ( = "true" ) for ( int m = 0 ; m < 3 ; m++ ){ X[m] = xtr( ay[m] ); Y[m] = ytr( az[m] ); } if (flag== 0 ){ // 単色で編目状に表示 g.setColor(Color.white); g.fillPolygon(X,Y, 3 ); // 背景色で塗りつぶし g.setColor(Color.black); g.drawPolygon(X,Y, 3 ); // 手前側の線の描き直し } else { // カラーでの面塗り g.setColor(Color.getHSBColor( Hcol, Scol, Bcol ) ); g.fillPolygon( X, Y, 3 ); g.setColor(Color.black); g.drawPolygon(X,Y, 3 ); } } } } } } // 面の法線ベクトルを計算するメソッド public boolean S_norm( double y[], double z[] ){ // (y, z) = 面を外側から見て反時計周りに番号付けした順に並べた座標値 // 面の法線と視点方向へのベクトル内積を求め、可視の場合は true を返す double aa = ( y[ 1 ] - y[ 0 ] )*( z[ 2 ] - z[ 0 ] )-( y[ 2 ] - y[ 0 ] )*( z[ 1 ] - z[ 0 ] ); if ( aa > 0.0 ) return true ; else return false ; } // ============================================================== // アクセスメソッド public Image getImage(){ return img; } public void deleteImage(){ g.clearRect( 0 , 0 , Width, Height ); } } |