Kimmy's Lab.

フルート好きのリケジョkimmyによる裏研究所

フルートを科学する ~ 室温とピッチ ~

熱くてムシムシする~
こんなときにフルート吹いてると,なんだか音が鳴りにくい(><)
頭もボーっと・・・熱中症!?
防音室の空気が入れ替わって,快適な温度と湿度にしてから吹いてみると,いつもどおり鳴る.

温度や湿度によって吹きやすさが変わるのは,なぜだろう?
経験上,ピッチはすごくズレる.あまりにズレすぎて鳴りにくいのかな.
温度や湿度によってピッチがズレるのは,音速が変わるからだろう.
湿度はちょっと話がややこしいので,温度によってどれくらいピッチが変わるのか計算してみた.

温度と音速

音速cは以下の式であらわされる.

 \displaystyle
c=\sqrt{(kRT)/M} ①

k:比熱比,R:気体定数M:分子量,T:絶対温度

空気の場合,k = 1.4030,R = 8.3145,M = 0.0290 kg/mol.
温度がtのとき,絶対温度は,T = 273.15 + t ℃.

式(1)から,温度と音速は以下のようになる.

表1 温度と音速

温度t [℃] 15 19 20 21 25
音速c [m/s] 340.7 343.0 343.6 344.2 346.5

これがどのくらいピッチに影響するのか?

温度とピッチ

周波数fと音速の関係は以下の式であらわされる.

 \displaystyle
f=\frac{c}{\lambda} ①

ここでλは波長.これは,ほぼ管の長さLで決まるはず.(開口端補正や音孔の影響は無視)
フルートは両側開端なので,管の長さLは,ほぼ半波長分の長さ.(L=λ/2)
式(2)は,

 \displaystyle
f=\frac{c}{2L} ②

20℃の環境で,いつものラの音,つまりA5(884Hz)の音でチューニングしたとすると, 低いラの音(A4: 442Hz)の管長Lは388 mm程度.(A4の倍音がきっちりA5と仮定)

音速が表1のように変化すると,A4の周波数は以下のように変化する.

表2 温度と周波数

温度t [℃] 15 19 20 21 25
A4周波数f_{a4} [Hz] 438.2 441.2 442.0 442.8 445.8
A5周波数f_{a5} [Hz] 876.4 882.5 884.0 885.5 891.5
A6周波数f_{a6} [Hz] 1314.6 1323.7 1326.0 1328.3 1337.3

温度が1℃上下すると,周波数は0.75Hzくらい上下する.
このくらいなら許容範囲?でも5℃上下すると3.7Hzくらい上下する.これは聴いてわかる?

同様にして,中音と高音のラについても周波数を求めてみると,高音ほど温度による周波数変化は大きい.

人間の耳がこれをどう感じるかはわからない.
もしかしたら高いラのピッチのズレは,低いラのズレより気にならないかもしれない.

人間の耳の感度はさておき.

このピッチの上がり下がりを補正するために,管をどれくらい抜き差ししないといけないか見積もってみた.

ふつうはA5でチューニングすると思うので,管を\Delta{L}だけ抜き差しして,

 \displaystyle
\frac{c}{L+\Delta{L}}=884 Hz ①

にすることになる.これを満たす\Delta{L}を計算すると以下のようになる.

表3 管の抜き差し必要量

温度t [℃] 15 19 20 21 25
長調整量\Delta{L} [mm] -1.7 -0.3 0.0 0.3 1.7

温度が1℃上がる(下がる)と,0.3mmくらい抜く(差し込む)必要がある.
5℃変化すると1.7mmくらい.

今回の計算は,開口端補正などを無視してシンプルに計算しているので,実際はこのとおりではありません!
私の場合,夏と冬で練習環境の温度差は10℃くらいあり,3mmくらい管を抜き差ししているので,だいたいこの計算どおりか.
日本の夏場は,湿度も上がるので,音速がやや速くなり,さらに管を抜く必要が出てくると思われる.それが何ミリかは・・・興味ある人は見積もって!

フルートを科学する ~楽器の支え~

最近、フルートを吹いていて、左手人差し指の付け根が痛くなることがあり、 気になって考えてみた。

フルート演奏時の力・モーメントのつり合い

フルートは,主に,唇,左手人差し指付け根、右手親指で支える(3点支持).
それにプラスして,補助的に,右手小指も楽器を支える役割をしている.

唇は「支点」としての役割をしていると思われる.

模式的にあらわすと以下の図のようになる.

f:id:kimmy123:20210827151029p:plain
図1: フルートに作用する力

ここで,Rは唇による支点反力をあらわす.

楽器を支えるためには,力とモーメントがつり合っていないといけない。

  • 力のつり合い
 \displaystyle
R-P_1+P_2+P_3=0 ①
  • 唇まわりのモーメントのつり合い
 \displaystyle
P_1a-P_2(a+b)-P_3(a+b+c)=0 ②

a:b:c=3:2:1くらいとすると,
式②から,

 \displaystyle
P_1=(5/3)P_2+2P_3 ③

つまり,右小指の力がゼロなら(P_3=0),
左人差し指で押す力(P_1)は、右親指で押す力(P_2)の2倍くらいでいいはず.
しかし,右小指に力が入ってしまうと(P_3>0),その力の2倍くらい,左人差し指で押す力を強くしないといけなくなる.

右手は,支点(唇)から離れている分,少しの力で大きく楽器を前方に回転させるモーメントをかけてしまう.
それに対抗するために,左手の支えを強くする必要があり,左人差し指付け根に負荷をかけてしまう.

右小指に力が入りすぎないようにしないと.

力とモーメントのつり合いがとれるためのP_1,P_2,P_3,a,b,cは,さまざまな値をとりうる.
いろいろ試して,自分にとって一番いい指の位置や力加減を探していこう.

2次元圧縮性流体計算(無反射境界条件)

テキスト「空力音の直接数値計算を支える技術」(暗黙の型宣言著)の補足課題7

全部読んだらやってみよう!その2

課題

補足課題6で作成した圧縮性流体計算プログラムに、無反射境界条件を適用する.

  • ヒント
    • 特性波の方向に注意.入ってくる特性波を0にして打ち消す.



解答例

プログラム全体は載せられないので,ポイントだけ解説します.


  • main.f90
        TVDRK : block
                !   :
                !  略
                !   :
                !--- 時間積分の前にNSCBCを適用
                ! NSCBC x-dir
                bd = ([1, Nx])

                do j = 1, Ny
                    ! x=0側の境界.
                    !入ってくる特性波(つまり流速が正の特性波)を0にして打ち消す.
                    L11(1,j) = max((u(1,j) - C0),0.d0)*(d_p_dx(1,j) - dens(1,j)*C0*d_u_dx(1,j))   
                    ! L11の特性波の流速は(u - C0)

                    L12(1,j) = max( u(1,j)      ,0.d0)*((C0**2)*d_dens_dx(1,j) - d_p_dx(1,j))        
                    ! L12の特性波の流速はu

                    L13(1,j) = max( u(1,j)      ,0.d0)*d_v_dx(1,j)                                                   
                    ! L13の特性波の流速はu

                    L15(1,j) = max((u(1,j) + C0),0.d0)*(d_p_dx(1,j) + dens(1,j)*C0*d_u_dx(1,j)) 
                    ! L15の特性波の流速は(u + C0)

                   ! x=Lx側の境界.
                   ! 入ってくる特性波(つまり流速が負の特性波)を0にして打ち消す.
                    L11(2,j) = min((u(Nx,j) - C0),0.d0)*(d_p_dx(Nx,j) - dens(Nx,j)*C0*d_u_dx(Nx,j))    
                    L12(2,j) = min( u(Nx,j)      ,0.d0)*((C0**2)*d_dens_dx(Nx,j) - d_p_dx(Nx,j))
                    L13(2,j) = min( u(Nx,j)      ,0.d0)*d_v_dx(Nx,j)
                    L15(2,j) = min((u(Nx,j) + C0),0.d0)*(d_p_dx(Nx,j) + dens(Nx,j)*C0*d_u_dx(Nx,j))
                end do
                
                do nb = 1, 2
                    d11 = (L12(nb,:) + 0.5d0*(L15(nb,:) + L11(nb,:)))/C0**2
                    d12 = (L15(nb,:) + L11(nb,:))*0.5d0
                    d13 = (L15(nb,:) - L11(nb,:))*0.5d0/(dens(bd(nb),:)*C0)
                    d14 =  L13(nb,:)

                   ! NSCBCを適用した支配方程式(テキストの式(6.1)~(6.5))と,
                   ! もともとの支配方程式(テキストの式(2.1)~(2.5))を比較し,
                   ! 境界の値にNSCBCを適用する.
                    d_dens_u_dx(bd(nb),:)   = d11(:)
                    d_dens_u_u_dx(bd(nb),:) = u(bd(nb),:)*d11(:) + dens(bd(nb),:)*d13(:)
                    d_p_dx(bd(nb), :)  = 0.d0
                    d_visyx_dx(bd(nb), :) = 0.d0
                    d_dens_u_v_dx(bd(nb),:) = v(bd(nb),:)*d11(:) + dens(bd(nb),:)*d14(:)
                    d_dens_u_TE_dx(bd(nb),:)= 0.5d0*(u(bd(nb),:)**2 + v(bd(nb),:)**2)*d11(:) + d12(:)/(Gamma-1.d0) &
                                            + dens(bd(nb),:)*u(bd(nb),:)*d13(:) + dens(bd(nb),:)*v(bd(nb),:)*d14(:)
                    d_u_p_dx(bd(nb),:) = 0.d0
                    d_qx_dx(bd(nb),:)  = 0.d0
                end do

                ! NSCBC y-dir
                !   :
                !  略
                !   :
                
                !--- Time Integration
                !   :
                !  略
                !   :                
        end block TVDRK

計算結果

NSCBCを適用しても,壁からの非物理的な波の反射を完全には抑えられない. 特にdilatation分布を見ると,そのことがよくわかる.

局所境界条件であるNSCBCだけでは反射を完全に抑えられないので,適宜,領域境界条件を適用する. これはテキスト6.3でも述べられていることだけど,実際にやってみるとそのことがよく理解できる.

  • 流速分布

    f:id:kimmy123:20200725161250p:plain
    図1 流速分布

  • 渦度分布

    f:id:kimmy123:20200725161337p:plain
    図2 渦度分布

  • Dilatation分布
    t>3/4で,四隅から波が反射して返ってきている.

    f:id:kimmy123:20200725161406p:plain
    図3 Dilatation分布

2次元圧縮性流体計算(単極渦)

テキスト「空力音の直接数値計算を支える技術」(暗黙の型宣言著)の補足課題6

全部読んだらやってみよう!その1

課題

いよいよ圧縮性流体計算!

8.2章に書かれている例題(超音速一様流で移流する単極渦まわりの流れ場)に取り組もう.

  • ヒント
    • 補足課題5で作成したプログラム一式をベースに,支配方程式を質量,運動量,全エネルギの保存則に変更する.
    • どの物理量をどういう順番で解くか,よく考えること.特に粘性応力の計算が難しい(><)
    • 変数が膨大にあるので,ケアレスミスに注意!
    • 絶対にミスは起こるので、どういうふうにデバッグしていくか・・を考えるのも,この問題の目的.



解答例

空間微分,時間積分はこれまでの補足課題と同様に以下の条件とします.

  • 空間微分 6次精度コンパクト差分(境界では4次,境界1点内側では5次精度)

  • 時間積分 TVDルンゲ・クッタ法

プログラム全体は載せられないので,ポイントだけ解説します.


  • main.f90
    do n = 1, Nt
        TVDRK : block
            integer :: i, j
            dens_u = dens*u
            dens_v = dens*v
            dens_TE = dens*TE
           
           ! TVDRKで時間進行させる保存量をキャッシュに入れる
            call cacheTVDRK_2D_NS(dens, dens_u, dens_v, dens_TE)  
            do step=1,3
                do j=1, Ny
                    ! ひたすら空間微分
                    call compute1stDerivative(d_dens_u_dx(:,j), dens(:,j)*u(:,j), dx, Gx)
                    call compute1stDerivative(d_dens_u_u_dx(:,j), dens(:,j)*u(:,j)*u(:,j), dx, Gx)
                    call compute1stDerivative(d_p_dx(:,j), p(:,j), dx, Gx)
                    call compute1stDerivative(d_dens_u_v_dx(:,j), dens(:,j)*u(:,j)*v(:,j), dx, Gx)
                    call compute1stDerivative(d_dens_u_TE_dx(:,j), dens(:,j)*u(:,j)*TE(:,j), dx, Gx)
                    call compute1stDerivative(d_u_p_dx(:,j), u(:,j)*p(:,j), dx, Gx)
                    call compute1stDerivative(d_temp_dx(:,j), temp(:,j), dx, Gx)
                    kp(:,j) = mu(:,j)*Cp/Pr
                    qx(:,j) = -kp(:,j)*d_temp_dx(:,j)
                    call compute1stDerivative(d_qx_dx(:,j), qx(:,j), dx, Gx)

                    call compute1stDerivative(d_u_dx(:,j), u(:,j), dx, Gx)
                    call compute1stDerivative(d_v_dx(:,j), v(:,j), dx, Gx)
                end do
                do i=1, Nx
                    call compute1stDerivative(d_dens_v_dy(i,:), dens(i,:)*v(i,:), dy, Gy)
                   !   :
                   ! 省略
                   !  :
                end do

                ! 粘性応力は,x方向とy方向両方の微分から決まるため,ひととおりx方向とy方向の微分値を求めた後に計算.
                visxx = mu*(2.d0*d_u_dx - (2.d0/3.d0)*(d_u_dx + d_v_dy))
                visxy = mu*(d_u_dy + d_v_dx)
                visyx = mu*(d_v_dx + d_u_dy)
                visyy = mu*(2.d0*d_v_dy - (2.d0/3.d0)*(d_u_dx + d_v_dy))
     
                ! 粘性応力の微分.もっと厳密にやるなら,テキスト8.1章「2階微分の計算法」に書かれているやり方でやりましょう.
                do j=1, Ny
                    call compute1stDerivative(d_visxx_dx(:,j), visxx(:,j), dx, Gx)
                    call compute1stDerivative(d_visyx_dx(:,j), visyx(:,j), dx, Gx)
                    call compute1stDerivative(d_U_visx_dx(:,j), u(:,j)*visxx(:,j) + v(:,j)*visyx(:,j), dx, Gx)
                end do
                do i=1, Nx
                    !   :
                    ! 省略
                    !   :
                end do

               ! 時間積分
                call integrateTVDRK_2D_NS(dens, -(d_dens_u_dx + d_dens_v_dy), dt, step, 1) !OK
                call integrateTVDRK_2D_NS(dens_u, &
                                        -(d_dens_u_u_dx + d_dens_v_u_dy) - d_p_dx + d_visxx_dx + d_visxy_dy, &
                                          dt, step, 2)
                call integrateTVDRK_2D_NS(dens_v, &
                                        -(d_dens_u_v_dx + d_dens_v_v_dy) - d_p_dy + d_visyx_dx + d_visyy_dy, &
                                          dt, step, 3)
                call integrateTVDRK_2D_NS(dens_TE, &
                                        -(d_dens_u_TE_dx + d_dens_v_TE_dy + d_u_p_dx + d_v_p_dy) &
                                        + d_U_visx_dx + d_U_visy_dy - (d_qx_dx + d_qy_dy), dt, step, 4)

               ! 各物理量を計算.これを次の時刻で空間微分する.
                u = dens_u/dens
                v = dens_v/dens
                TE = dens_TE/dens
                dens_e = dens_TE - 0.5d0*(dens*u*u + dens*v*v)
                e = dens_e/dens
                p = dens_e*(Gamma-1.d0)
                temp = e/Cv
                mu = Mu0*((Temp0 + Temp1)/(temp + Temp1))*((temp/Temp0)**(3/2)) 
     
            enddo
        end block TVDRK
    end do
  • module_TimeIntegration.f90
    ! TVDRKで時間進行させる保存量をキャッシュに入れる
    subroutine cacheTVDRK_2D_NS(dens, dens_u, dens_v, dens_TE)
        real(8), intent(in) :: dens(:,:), dens_u(:,:), dens_v(:,:), dens_TE(:,:)
        dens2(:,:) = dens(:,:)
        dens_u2(:,:) = dens_u(:,:)
        dens_v2(:,:) = dens_v(:,:)
        dens_TE2(:,:) = dens_TE(:,:)
    end subroutine cacheTVDRK_2D_NS

   ! TVDRKでの時間積分は,こんなふうに書いてみた.
   ! けっきょくどの物理量も式①で積分することは同じなので,コードの繰り返しを避けるため,被積分配列をcase文で選択.
    subroutine integrateTVDRK_2D_NS(f, d_f_dt, dt, step, n)
        implicit none
        real(8), intent(inout) :: f(:,:)
        real(8), intent(in   ) :: d_f_dt(:,:)
        real(8), intent(in   ) :: dt
        integer, intent(in   ) :: step
        integer, intent(in   ) :: n
        real(8), parameter :: a(3) = [0d0, 3d0/4d0, 1d0/3d0]
        real(8), parameter :: b(3) = [1d0, 1d0/4d0, 2d0/3d0]

        select case (n)
        case (1)
            fn2 = dens2
        case (2)
            fn2 = dens_u2
        case (3)
            fn2 = dens_v2
        case (4)
            fn2 = dens_TE2    
        end select  

        f(:,:) = a(step)*fn2(:,:) + b(step)*(f(:,:)-dt*d_f_dt(:,:))  ! 式①

    end subroutine integrateTVDRK_2D_NS

計算結果

壁面に無反射境界条件を適用していないので,壁付近まで渦が広がっていったところ(t=4/8くらい)で,壁から非物理的な波が反射して返ってきます. その影響でt=5/8を過ぎると発散.

  • 流速分布

    f:id:kimmy123:20200711203831p:plain
    図1 流速分布

  • 渦度分布

    f:id:kimmy123:20200711203859p:plain
    図2 渦度分布

  • Dilatation分布

    f:id:kimmy123:20200711204021p:plain
    図3 Dilatation分布

この課題では,ここまでできればOK.次の課題で無反射境界条件に取り組もう!

2次元拡散方程式

テキスト「空力音の直接数値計算を支える技術」(暗黙の型宣言著)の補足課題5

7章まで読んだらやってみよう!その3


本課題も別冊テキスト「流体計算で覚えるPython3」(暗黙の型宣言著)に掲載されているテスト問題を使用します.

課題

2次元拡散方程式をFortranで解いてみよう.

  • 支配方程式は,2次元拡散方程式.
  • 初期条件は,2次元ベル型関数.(別冊テキスト7.7節と同様)
  • ヒント
    • 補足課題4「2次元移流方程式」で作成したプログラム一式をベースに,支配方程式を拡散方程式に変更すればOK.



解答例

プログラム全体は載せられないので,ポイントだけ解説します.


拡散方程式では,空間微分が1階微分ではなく2階微分になるので,これを計算するコードに修正. さらに,別冊テキストの離散化式(7.9)をTVD Runge-Kutta法で時間積分するコードを追加.

2次元移流方程式から2次元拡散方程式への変更はこれくらい.一瞬でできてしまう.

  • main.f90
    do n = 1, Nt
        TVDRK : block
            integer :: i, j
            call cacheTVDRK_2D(f%var)
            do step=1,3
                do j=2, Ny-1
                    call compute2ndDerivative(f%dx2(:,j), f%var(:,j), dx, Gx) ! x方向の2階微分を計算
                end do
                do i=2, Nx-1
                    call compute2ndDerivative(f%dy2(i,:), f%var(i,:), dy, Gy) ! y方向の2階微分を計算
                end do
                ! 境界でゼロを強制
                f%var(1 ,: ) = 0d0
                f%var(Nx,: ) = 0d0
                f%var(: ,1 ) = 0d0
                f%var(: ,Ny) = 0d0
                call integrateTVDRK_2D_DiffusionEq(f%var, f%dx2, f%dy2, dt, step, mu) ! TVDRK法で拡散方程式の時間積分するサブルーチンを呼び出す
            enddo
        end block TVDRK
    end do

ここまでの補足課題をこなしていれば,サブルーチン「compute2ndDerivative」は module_CompactScheme.f90 で既に定義してあるはず. もし定義していなければ,テキストを参考に定義してください.

  • module_TimeIntegration.f90
    subroutine integrateTVDRK_2D_DiffusionEq(f, d_f_dx2, d_f_dy2, dt, step, mu)
        implicit none
        real(8), intent(inout) :: f(:,:)
        real(8), intent(in   ) :: d_f_dx2(:,:), d_f_dy2(:,:)
        real(8), intent(in   ) :: dt
        integer, intent(in   ) :: step
        real(8), intent(in   ) :: mu
        real(8), parameter :: a(3) = [0d0, 3d0/4d0, 1d0/3d0]
        real(8), parameter :: b(3) = [1d0, 1d0/4d0, 2d0/3d0]

        integer :: Nx, Ny
        integer :: i, j

        Nx = size(f,1)
        Ny = size(f,2)

        do j=1, Ny
            do i=1, Nx
                f(i,j) = a(step)*fn2(i,j) + b(step)*(f(i,j) + mu*dt*(d_f_dx2(i,j) + d_f_dy2(i,j))) ! TVDRK法で拡散方程式の時間積分
            end do
        end do
    end subroutine integrateTVDRK_2D_DiffusionEq

拡散数0.25の計算結果は図1のとおり.時間とともに円錐が拡散している. もっとしっかりやるなら,厳密解(2次元ガウス分布)との誤差も計算するとよいでしょう.

f:id:kimmy123:20200621150600p:plain
Fig.1. Spatial distribution around cone. (CFL=0.04, N=1600)

2次元移流方程式

テキスト「空力音の直接数値計算を支える技術」(暗黙の型宣言著)の補足課題4

7章まで読んだらやってみよう!その2


今回は2次元の課題にトライ. 8章に2次元のテスト問題が書かれているけれど,いきなりナビエ・ストークス方程式を解き始めるよりは,もう少し単純な課題で2次元計算の基礎を知っておいたほうがいいと思う.


なお,本課題では別冊テキスト「流体計算で覚えるPython3」(暗黙の型宣言著)に掲載されているテスト問題を使用します.

課題

「流体計算で覚えるPython3」7章に書かれている回転円錐問題をFortranで解いてみよう.

  • 支配方程式は,2次元移流方程式.(7.1節に掲載)
  • 初期条件は,2次元ベル型関数.(7.5節に掲載)
  • ヒント
    • 補足課題3「時間積分」で作成したプログラム一式をベースに2次元化すればOK.



解答例

プログラム全体は載せられないので,ポイントだけ解説します.


まずはmain.f90のInitializeブロックで初期条件を定義. 移流速度c_xはy,c_yはxで決まる,という点に注意.うっかり逆にしないこと!

    Initialize : block
                 :
                c_x(i,j) = -2.d0*PI*y(j)     
                c_y(i,j) =  2.d0*PI*x(i)   
                 :
   call fileout_2dim(file_init, x, y, c_x, c_y, f%var) !初期条件出力
    end block Initialize

初期条件を定義できたら,いったん出力してみて確認. 図1のt/T=0のようになっていればOK.
次に,2次元移流方程式を解くためのコードを書く.ここでは重要なポイントだけ紹介.

  • main.f90

df/dx, df/dyをそれぞれ計算し,fを求める.というのを繰り返し,時間積分していく.

    do n = 1, Nt
        TVDRK : block
            integer :: i, j
            call cacheTVDRK_2D(f%var)
            do step=1,3
                do j=2, Ny-1
                    call compute1stDerivative(f%dx(:,j), f%var(:,j), dx, Gx)
                end do
                do i=2, Nx-1
                    call compute1stDerivative(f%dy(i,:), f%var(i,:), dy, Gy)
                end do
                call integrateTVDRK_2D_Adv(f%var, f%dx, f%dy, dt, step, c_x, c_y)
                ! 境界でゼロを強制
                f%var(1 ,: ) = 0d0
                f%var(Nx,: ) = 0d0
                f%var(: ,1 ) = 0d0
                f%var(: ,Ny) = 0d0         
            enddo
        end block TVDRK
    end do


  • module_TimeIntegration.f90

2次元移流方程式を時間方向に離散化して,次ステップのfを求める. ここではTVD Runge-Kutta法で時間積分

    subroutine integrateTVDRK_2D_Adv(f, d_f_dx, d_f_dy, dt, step, conv_x, conv_y)
        implicit none
        real(8), intent(inout) :: f(:,:)
        real(8), intent(in   ) :: d_f_dx(:,:), d_f_dy(:,:)
        real(8), intent(in   ) :: dt
        integer, intent(in   ) :: step
        real(8), intent(in   ) :: conv_x(:,:), conv_y(:,:)
        real(8), parameter :: a(3) = [0d0, 3d0/4d0, 1d0/3d0]
        real(8), parameter :: b(3) = [1d0, 1d0/4d0, 2d0/3d0]

        integer :: Nx, Ny
        integer :: i, j

        Nx = size(f,1)
        Ny = size(f,2)

        do j=1, Ny
            do i=1, Nx
                f(i,j) = a(step)*fn2(i,j) + b(step)*(f(i,j)-conv_x(i,j)*dt*d_f_dx(i,j)-conv_y(i,j)*dt*d_f_dy(i,j))
            end do
        end do

    end subroutine integrateTVDRK_2D


ひととおりプログラムができたら,円錐が1/4周,2/4周,3/4周,1周したときの計算結果を出力し,正しく反時計回りにまわっていることを確認. 可視化ソフトは「AVえせ」というフリーウェアを使用.# あやしいソフトじゃないよ!

f:id:kimmy123:20200620172700p:plain
Fig.1. Time variation of rotating cone

格子点数やクーラン数を変えてみて,1周したときの計算結果を可視化して確認する.

まず,総格子点数を変えてみた場合.可視化結果にほとんどちがいはみられない.(格子点数の分だけベクトルが表示される)

f:id:kimmy123:20200620173340p:plain
Fig.2. Rotating cone at t/T=4/4. (CFL = 0.04)

次に,クーラン数(CFL)を変えてみた場合.こちらも,ほとんどちがいはみられない.

f:id:kimmy123:20200620173958p:plain
Fig.3. Rotating cone at t/T=4/4. (N=1600)

1周したときの領域全体のエラーを出力する.

    print *, "CFL:", CFL, "error:", sum(abs(f%var-f0%var))/(Nx*Ny) ! f%varは計算結果.f0%varは初期値.

格子点数とクーラン数を変えたときのエラーをグラフにすると,

f:id:kimmy123:20200620205212p:plain
Fig.4. Error

クーラン数よりも格子点数によってエラーが決まっている.

時間積分

テキスト「空力音の直接数値計算を支える技術」(暗黙の型宣言著)の補足課題3

7章まで読んだらやってみよう!

課題

(1) 時間積分にEuler法を用いて移流方程式を計算する.

  • 初期値はテキスト式(7.11)とし,波が境界を出ない時間まで計算する.
  • 境界では勾配ゼロ(df/dx=0)の境界条件を与える.
    • 式7.11は境界でほぼ値がゼロ(f=0)だが,境界条件としてf=0を無理やり与えると境界で値が発散する.
    • テキストでは周期境界条件を用いて,波が2周する時間分計算しているが,実際の空力音の数値計算では周期境界条件を用いることができるケースというのは限られるため,ここでは周期境界条件を用いず勾配ゼロの境界条件を与える.
  • 誤差も計算して,実効的な精度を確認する.
  • テキストの以下プログラム参照.
    • List7.2(移流方程式メインルーチン)
    • List7.3(Euler法による時間積分)
  • 空間微分は,補足課題2を参考に,以下を用いる.
    • グローバルスキーム・・・6次精度
    • 境界1点内側・・・5次精度
    • 境界・・・4次精度片側差分

(2) 同様に,TVD Runge-Kutta法(以下,TVDRK)を用いて移流方程式を計算する.

  • List7.14(TVDRK法による時間積分).



解答例

まずはEuler法.テキストを写経して,以下のファイルを作成する.

  • main.f90(List7.3参照)
  • module_TimeIntegration.f90(List7.4参照)

main.f90を以下のように修正する.(修正箇所は「!---」, ただのコメントは「!」)

    real(8), parameter :: EndT = 10d0 !--- 計算時間を,波が境界に到達しない時間に設定
    :
    !--- 境界条件用パラメータを追加
    real(8), parameter :: df1 = 0d0
    real(8), parameter :: dfN = 0d0

   !--- 3重対角行列を指定
    type(TriDiagonalMatrix) :: G

    :

    Initialize : block
        
        :

        !--- 両端で勾配ゼロになるようオフセットする(3.8章参照)
        f%var(1) = f%var(2)
        f%var(Nx)= f%var(Nx-1)
    end block Initialize

    :

    do n = 0, Nt-1
        Euler : block
            call compute1stDerivative(f%dx, f%var, dx, G, df1, dfN)  !--- 境界条件用パラメータdf1, dfNを追加
            call integrateEuler(f%var, f%dx, dt, conv)
        end block Euler
    end do

    !--- 誤差計算・出力用ブロックを追加
    ResultOut : block
        integer, parameter :: Nx_err = nint(wavelength/dx) ! 波長を誤差計算範囲とする
        integer :: i, i_wavecenter_f, i_wavecenter_f0
        real :: err

        err = 0d0

        i_wavecenter_f0= nint(Nx/2d0) + 1                                           ! 初期の波f0の中心位置
        i_wavecenter_f = i_wavecenter_f0 + nint(conv*dt*(Nt-1)/dx)    ! 伝播した波fの中心位置

        open(1, file='FileOut.txt', status='replace')
    
        do i=1,Nx
            write(1,*)  dble(i-1)*dx - Lx/2d0, f0%var(i), f%var(i)             ! x, f0, f を出力
        enddo

        do i=-Nx_err, Nx_err
            err = err + abs(f%var(i_wavecenter_f+i)-f0%var(i_wavecenter_f0+i))  ! 初期の波と伝播した波の振幅差を計算
        enddo
        print *, "MinAveError:", err/(Nx_err*2+1) ! 累積誤差を格子点数で割って,最小平均誤差を出力

        close(1)
    end block ResultOut

    :

    contains

    subroutine compute1stDerivative(d_f_dx, f, dx, G, GraditntB1, GraditntBN) !--- 境界条件用パラメータを追加
        use TypeTriDiagonalMatrix !--- 3重対角行列を指定
        implicit none

        :

        ! 境界条件用パラメータを追加
        real(8), intent(in   ), optional :: GraditntB1
        real(8), intent(in   ), optional :: GraditntBN

        call construct1stDerivativeVector(d_f_dx, f, dx, G, GraditntB1, GraditntBN)    !--- 境界条件用パラメータを追加
        call solve(G, d_f_dx)
    end subroutine compute1stDerivative

end program main

空間微分には,補足課題2で作成した以下のファイルを用いる.

  • module_CompactScheme.f90(List3.4参照)
  • TypeTriDiagonalMatrix.f90(List4.2参照)

以上でEuler法のプログラムは完成.

パラメータ「CFL」をさまざまに変化させて,誤差の変化を確認する.確認結果は後述.

次に,TVDRK法.main.f90のEuler法用ブロックを,TVDRK法用ブロックに変更.

  • main.f90
    :
    do n = 0, Nt-1
        TVDRK : block
            call cacheTVDRK(f%var)            ! fnの値を保持
            do step=1,3
                call compute1stDerivative(f%dx, f%var, dx, G, df1, dfN)
                call integrateTVDRK(f%var, f%dx, dt, step, conv)   ! TVDRKのサブルーチンをcall
            enddo
        end block TVDRK
    end do
    :

さらに,module_TimeIntegration.f90にテキストList7.14のサブルーチンcacheTVDRKとintegrateTVDRKを追加する.

テキストにも書いてあるように,Euler法からTVDRK法への変更は簡単♪

TVDRK法は,Euler法から得られた結果と現時間ステップの結果に重みづけして,次の時間ステップの値を決めている,と解釈できる.

パラメータ「CFL」をさまざまに変化させて,誤差の変化を確認した結果は図1のとおり.

f:id:kimmy123:20200531221009p:plain
図1 移流方程式誤差

Euler法はslope 2(CFLを1/10にすると誤差が1/100)よりも傾きが浅い.近似直線を引いてみると,約1.1次精度であった.

TVDRK法(Nx = 400)は,0.1<CFL<0.6でほぼslope3に沿っているので約3次精度. 0.6<CFLでslope3より誤差が大きいのは,格子が荒すぎる影響.

CFL<0.1で誤差の減少がサチっているのは?

Nx=400では0.00001付近でサチっているのに対し,格子を2倍荒くしたNx=200では誤差が0.001付近でサチっている. CFL=0.002では,Nx=400の誤差は,Nx=200の誤差の約2^{-6}倍であった. 空間微分のグローバルスキームが6次精度なので,格子を2倍にすることで誤差が2^{-6}倍に減少している. つまり,誤差の減少がサチるのは,空間微分の精度の影響.