Fortran90(where)

旧2-5. Fortran毎日学習

ずっとFortran学習してますが、なかなか飽きません。飽きるまで続く気がしています。そして新たな発見。where構文。こんな強力な構文があるとは知りませんでした。
今回はwhereを使っていきます。

whereその1

まずはwhereを使わない例です。doで回して配列aの要素が5より大きかったら、配列bに代入するときに、2倍にするプログラムです。

takk@deb9:~$ cat do.f90
      program main
        integer a(10),b(10)
        data a /1,2,3,4,5,6,7,8,9,10/

        do i=1,10
          if (a(i) > 5) then
            b(i) = a(i) * 2
          else
            b(i) = a(i)
          end if
        end do

        do i=1,10
         write(*,*) b(i)
        end do

        stop
      end
takk@deb9:~$ gfortran do.f90
takk@deb9:~$ ./a.out
           1
           2
           3
           4
           5
          12
          14
          16
          18
          20
takk@deb9:~$

6行目から2倍になってますね。

上のプログラムをwhereを使って作り直すとこうなります。

takk@deb9:~$ cat where.f90
      program main
        integer a(10),b(10)
        data a /1,2,3,4,5,6,7,8,9,10/

        where (a > 5)
         b = a * 2
        else where
         b = a
        end where

        do i=1,10
         write(*,*) b(i)
        end do

        stop
      end
takk@deb9:~$

いやあスゴイ。なんて簡潔に書けるのでしょうか。

実行するともちろん同じ結果になります。

takk@deb9:~$ gfortran where.f90
takk@deb9:~$ ./a.out
           1
           2
           3
           4
           5
          12
          14
          16
          18
          20
takk@deb9:~$

使い方は、whereとend whereの間に配列代入文を挿むだけ。

where 条件
  配列代入文
end where

もしくはこのパターン。

where 条件
  配列代入文1
else where
  配列代入文2
end where

配列代入文以外はだめなのでしょうか。配列代入の代わりにwriteを入れてみます。

takk@deb9:~$ cat where2.f90
      program main
        integer a(10),b(10)
        data a /1,2,3,4,5,6,7,8,9,10/

        where (a > 5)
         write(*,*) 'HELLO'
        end where

        stop
      end
takk@deb9:~$

ビルドエラーとなります。

takk@deb9:~$ gfortran where2.f90
where2.f90:6:27:

          write(*,*) 'HELLO'
                           1
Error: Unexpected WRITE statement in WHERE block at (1)
takk@deb9:~$

もちろんwhere構文内なので配列代入しかできないのであって、if文の中なら問題ないです。

takk@deb9:~$ gfortran if.f90
takk@deb9:~$ ./a.out
 HELLO
takk@deb9:~$ cat if.f90
      program main
        integer a(10)
        data a /1,2,3,4,5,6,7,8,9,10/

        if (a(6) > 5) then
         write(*,*) 'HELLO'
        end if

        stop
      end
takk@deb9:~$ gfortran if.f90
takk@deb9:~$ ./a.out
 HELLO
takk@deb9:~$

普通の代入はどうでしょうか。

takk@deb9:~$ cat where3.f90
      program main
        integer a(10),b(10)
        data a /1,2,3,4,5,6,7,8,9,10/

        where (a > 5)
         i = 1
        end where

        stop
      end
takk@deb9:~$ gfortran where3.f90
where3.f90:6:9:

          i = 1
         1
Error: WHERE assignment target at (1) has inconsistent shape
takk@deb9:~$

whereの中では、普通の代入もエラーとなりました。
やはり配列代入のみしか書くことはできないようです。

whereその2

whereを使って配列を初期化する方法を考えてみます。

配列を同じ値で初期化する場合は、たとえば1なら、こんな風に書けると思います。

        where (a .ne. 1)
         a = 1
        end where

whereのあとの a .ne. 1は常に真でもよかったかもしれませんが、書き方が分からないので、1以外なら1で初期化するようにしています。
実行してみます。

takk@deb9:~$ cat where4.f90
      program main
        integer a(10)

        where (a .ne. 1)
         a = 1
        end where

        do i = 1,10
          write(*,*) a(i)
        end do

        stop
      end
takk@deb9:~$ gfortran where4.f90
takk@deb9:~$ ./a.out
           1
           1
           1
           1
           1
           1
           1
           1
           1
           1
takk@deb9:~$

1で初期化できました。

data文で初期化するのとどちらがプログラムサイズが少なくて済むでしょうか。配列数を10000ぐらいにして確認してみます。
まずはdata文を使って静的に初期化した場合。

takk@deb9:~$ cat data5.f90
      program main
        integer a(10000)
        data a /10000*1/

        write(*,*) a(5000)
        stop
      end
takk@deb9:~$ gfortran data5.f90
takk@deb9:~$ ./a.out
           1
takk@deb9:~$ ls -l a.out
-rwxr-xr-x 1 takk takk 49136 12月 20 20:34 a.out
takk@deb9:~$

簡潔に書けますね。サイズは49136。大きいです。

where文で動的に初期化した場合。

takk@deb9:~$ cat where5.f90
      program main
        integer a(10000)

        where (a .ne. 1)
         a = 1
        end where

        write(*,*) a(5000)

        stop
      end
takk@deb9:~$ gfortran where5.f90
takk@deb9:~$ ./a.out
           1
takk@deb9:~$ ls -l a.out
-rwxr-xr-x 1 takk takk 9104 12月 20 20:33 a.out
takk@deb9:~$

行数は2行増えましたが、こちらも簡潔に書けてる気がします。プログラムサイズは9104。5分の1になりました。

whereその3

前回whereを使って、配列に初期値を格納したのですが、whereを使う意味はまったくありませんでした。
配列代入の意味を勘違いしていたのですが、まずは前回作った初期化のプログラムを見てみます。

takk@deb9:~$ cat where4.f90
      program main
        integer a(10)

        where (a .ne. 1)
         a = 1
        end where

        do i = 1,10
          write(*,*) a(i)
        end do

        stop
      end
takk@deb9:~$

10個の要素すべてが1に初期化されていたので正しく動いてはいたのですが、実はwhereを使う必要はありませんでした。こうすれば良いです。

takk@deb9:~$ cat where6.f90
      program main
        integer a(10)

        a = 1

        do i=1,10
          write(*,*) a(i)
        end do

        stop
      end
takk@deb9:~$

そうです。a = 1とするだけ。10個の要素すべてが1になります。
実行してみます。

takk@deb9:~$ gfortran where6.f90
takk@deb9:~$ ./a.out
           1
           1
           1
           1
           1
           1
           1
           1
           1
           1
takk@deb9:~$

whereはすごい! と糠喜びしてましたが、こうなると、whereって何に使うのだろう? と疑問符。

要素の総和とかできるでしょうか。1~10までの足し算を試みます。

takk@deb9:~$ cat where7.f90
      program main
        integer a(10),s
        data a /1,2,3,4,5,6,7,8,9,10/

        s = 0

        where(a > 0)
          s = s + a
        end where

        do i=1,10
          write(*,*) a(i)
        end do

        stop
      end
takk@deb9:~$

こんなんでいけるでしょうか。ビルドしてみます。

takk@deb9:~$ gfortran where7.f90
where7.f90:8:10:

           s = s + a
          1
Error: Incompatible ranks 0 and 1 in assignment at (1)
where7.f90:8:10:

           s = s + a
          1
Error: WHERE assignment target at (1) has inconsistent shape
takk@deb9:~$

エラーになりました。スカラー変数への代入はwhere構文内ではできないことを忘れてました。
配列代入しかできないということは、総和もできないってことですね。

コメント

タイトルとURLをコピーしました