fortran66のブログ

fortran について書きます。

グラフ作画ルーチン(仮

本番の清書などの場合、適切な作図ソフトで作図する分には不満は無いのですが、計算結果のチェックやパラメータ振りのような捨て作図のときには、計算プログラム内で簡単にスコッと描いてくれると便利なことが多いです。計算結果をスクリプトなどで処理してもいいのですが、window がポップしたり埋もれて煩わしかったり、作図ソフト側がイマイチ気の利かないこともままあります。

そういうわけで本体のメインルーチンに、デバッグ行的に軽く挿入して作図してくれるルーチンが前々から欲しかったのですが、Fortran2008 で block construct が導入されて、一気にやりやすくなりました。block 内では use 文によるモジュール読み込みや、ローカル変数の利用が可能なので、局所的に完全に独立したプログラム構造が構成可能だからです。

さて作図ルーチンは今のところ気の利かなさという点では事態はむしろ悪化してるんですが、とりあえず備忘録代わりに書きつけておきます。

プログラム

ここでは、エラトステネスのふるいで素数を求めて、素数の逆数の和を求めます。素数の逆数の和は、LogLog に比例して発散するらしいので、それをグラフによって確かめます。整数N以下の素数を求めるとして、横軸をlog10(log10(N)) にとり、縦軸を N 以下の素数の和に取ります。

グラフを見ると N=10^9 まで直線的に伸びているので、この範囲で確かに loglog で発散していっていることが分かります。最初の方で直線の傾きがずれているのでは、最初の点が素数の数が少なすぎるためだと思われます。

10**9 以下の素数を求めて、10 のべき乗毎に、そこまでの素数の逆数の和を求めています。10**9 以下の場合、約五千万個の素数の逆数の和を求めていることになります。

f:id:fortran66:20160505014241p:plain


メイン・プログラム
    module m_eratos
      implicit none
    contains
      function eratos(n) result(ires)
        integer, intent(in) :: n
        integer, allocatable :: ires(:)
        logical(1), allocatable :: tab(:)
        integer :: i, k
        allocate(tab(n))
        tab = .true.
        tab(1) = .false.
        do i = 1, int(sqrt(real(n)))
          if (tab(i)) tab(i**2::i) = .false.  
        end do
        allocate(ires(count(tab)))
        k = 0
        do i = 1, n
          if (.not. tab(i)) cycle
          k = k + 1
          ires(k) = i
        end do            
      end function eratos
    end module m_eratos
    
    program Console6
      use m_eratos
      implicit none
      integer, allocatable :: iprim(:)
      integer :: i, np(9)
      real(8) :: tw(9)
      do i = 1, 9
        iprim = eratos(10**i)     
        np(i) = size(iprim)
        tw(i) = sum(1.0d0 / iprim)
        print *, i, np(i), tw(i)
      end do

      block
        use m_graph
        call plot(log10(log10(real(np))), real(tw), &
                  yrange = [0.0, 4.0], &
                  title = ['Log\_1\_0Log\_1\_0(N)', 'Sum of 1/primes', '\gR1/p~log(log(N))'], &
                  fmt = ['f4.1', 'f3.1'])
      end block    
    end program Console6
グラフ作図用モジュール

x 軸と y 軸の作図を深く考えず共通化しようとしてドツボにはまったので、あきらめて同じようなルーチンを並べています。

    module m_axis
      use m_plot
      implicit none
      real, parameter :: pi = 4 * atan(1.0), rad = pi / 180.0 ! degree to radian
      type, extends (t_fig) :: t_graph
        real :: xmin, xmax, ymin, ymax
      contains
        procedure :: graph
        procedure :: x_axis
        procedure :: y_axis
        procedure :: x_mk_axis
        procedure :: y_mk_axis
        procedure :: x_tick
        procedure :: y_tick
      end type t_graph    
    contains  
      subroutine graph(self, xmin, ymin, xmax, ymax)
        class (t_graph), intent(in out) :: self
        real, intent(in) :: xmin, ymin, xmax, ymax
        self%xmin = xmin
        self%ymin = ymin
        self%xmax = xmax
        self%ymax = ymax         
      end subroutine graph
      
      subroutine x_axis(self, fmt, xstep, mtick, d, title, log)
        class (t_graph), intent(in) :: self
        character (len = *), intent(in) :: fmt
        real   , intent(in) :: xstep, d
        integer, intent(in) :: mtick
        character (len = *), intent(in), optional :: title
        logical, intent(in), optional :: log
        character (len = :), allocatable :: title_
        logical :: log_
        real :: d_scaled
        call self%move(self%xmin, self%ymin)
        call self%line(self%xmax, self%ymin)
        title_ = 'x-axis'
        if (present(title)) title_ = trim(title)
        log_ = .false.
        if (present(log)) log_ = log
        d_scaled = d * (self%y1 - self%y0) / 100.0
        call self%x_mk_axis(fmt, self%xmin, self%xmax, xstep, mtick, d_scaled, title_, log_)
      end subroutine x_axis
      
      subroutine y_axis(self, fmt, ystep, mtick, d, title, log)
        class (t_graph), intent(in) :: self
        character (len = *), intent(in) :: fmt
        real   , intent(in) :: ystep, d
        integer, intent(in) :: mtick
        character (len = *), intent(in), optional :: title
        logical, intent(in), optional :: log
        character (len = :), allocatable :: title_
        logical :: log_
        real :: d_scaled
        call self%move(self%xmin, self%ymin)
        call self%line(self%xmin, self%ymax)
        title_ = 'y-axis'
        if (present(title)) title_ = trim(title)
        log_ = .false.
        if (present(log)) log_ = log
        d_scaled = d * (self%x1 - self%x0) / 100.0
        call self%y_mk_axis(fmt, self%ymin, self%ymax, ystep, mtick, d_scaled, title_, log_)
      end subroutine y_axis

      subroutine x_mk_axis(self, fmt, rmin, rmax, rstep, mtick, d, title, log)
        class (t_graph), intent(in) :: self
        character (len = *), intent(in) :: fmt, title
        real   , intent(in) :: rmin, rmax, rstep, d
        integer, intent(in) :: mtick
        real, allocatable :: r(:)
        logical, intent(in), optional :: log
        logical :: log_
        real :: tmin, xt, yt
        character (len = 20), allocatable :: tr(:)
        integer :: i, j, n
        tmin = rstep * int(rmin / rstep) - rstep
        n = (rmax - tmin) / rstep + 1
        ! tick    
        log_ = .false.
        if (present(log)) log_ = log
        allocate(r(n), tr(n))
        call mk_rt(fmt, tmin, rstep, r, tr, log_)
        call self%x_tick(r, rmin, rmax, 2.0 * d, 'center', tr)
        ! subtick
        r = [(( tmin + rstep * (i - 1) + rstep / mtick * j, j = 1, mtick), i = 0, n)]  
        if (log_) r = [((( tmin + rstep * (i - 1) + rstep * log10(real(j))), j = 1, mtick), i = 0, n)]  
        call self%x_tick(r, rmin, rmax, d)
        ! title
        xt = (self%xmax + self%xmin) / 2 
        yt = self%ymin + 8 * d        
        call self%text(xt, yt, title, 4.0, 0.0, 'center')
      end subroutine x_mk_axis
      
      subroutine y_mk_axis(self, fmt, rmin, rmax, rstep, mtick, d, title, log)
        class (t_graph), intent(in) :: self
        character (len = *), intent(in) :: fmt, title
        real   , intent(in) :: rmin, rmax, rstep, d
        integer, intent(in) :: mtick
        logical, intent(in), optional :: log
        logical :: log_
        real, allocatable :: r(:)
        real :: tmin, xt, yt
        character (len = 20), allocatable :: tr(:)
        integer :: i, j, n
        tmin = rstep * int(rmin / rstep) - rstep 
        n = (rmax - tmin) / rstep + 1   
        ! tick     
        log_ = .false.
        if (present(log)) log_ = log
        allocate(r(n), tr(n))
        call mk_rt(fmt, tmin, rstep, r, tr, log_)
        call self%y_tick(r, rmin, rmax, 2.0 * d, 'right', tr)
        ! subtick
        r = [(( tmin + rstep * (i - 1) + rstep / mtick * j, j = 1, mtick), i = 0, n)]  
        if (log_) r = [((( tmin + rstep * (i - 1) + rstep * log10(real(j))), j = 1, mtick), i = 0, n)]  
        call self%y_tick(r, rmin, rmax, d)
        ! title 
        xt =  self%xmin - (5.0 + 0.4 * maxval(len_trim(tr))) * d
        yt = (self%ymax + self%ymin) / 2 
        call self%text(xt, yt, title, 4.0, 90.0, 'center')
      end subroutine y_mk_axis
      
      subroutine mk_rt(fmt, tmin, rstep, r, tr, log)
        character (len = *), intent(in) :: fmt
        real, intent(in ) :: tmin, rstep
        real, intent(out) :: r(:)
        character (len = *), intent(out) :: tr(:)
        logical, intent(in) :: log
        integer :: i
        real :: t
        do i = 1, size(r) 
          r(i) = tmin + rstep * (i - 1)
          t = r(i)
          if (log) t = 10.0**(r(i))
          if (scan(fmt, 'iI') /= 0) then
            write(tr(i), fmt) int(t)  
          else  
            write(tr(i), fmt) t
          end if 
        end do   
      end subroutine mk_rt

      subroutine x_tick(self, r, rmin, rmax, d, adjust, tr)
        class (t_graph), intent(in) :: self
        real, intent(in) :: r(:), rmin, rmax, d
        character (len = *), intent(in), optional :: adjust, tr(:)
        integer :: i
        real :: r0(2), r1(2), rt(2)
        do i = 1, size(r)
          if (rmin <= r(i) .and. r(i) <= rmax) then
            call self%move(r(i), self%ymin    )
            call self%line(r(i), self%ymin + d)
            if (present(tr)) then
              call self%text(r(i) + 0.01 * (self%xmax - self%xmin), self%ymin + 2.2 * d, trim(tr(i)), 2.2, 0.0, trim(adjust))
            end if  
          end if  
        end do    
      end subroutine x_tick

      subroutine y_tick(self, r, rmin, rmax, d, adjust, tr)
        class (t_graph), intent(in) :: self
        real, intent(in) :: r(:), rmin, rmax, d
        character (len = *), intent(in), optional :: adjust, tr(:)
        integer :: i
        real :: r0(2), r1(2), rt(2)
        do i = 1, size(r)
          if (rmin <= r(i) .and. r(i) <= rmax) then
            call self%move(self%xmin    , r(i))
            call self%line(self%xmin - d, r(i))
            if (present(tr)) then
              call self%text(self%xmin - 0.08 * (self%xmax - self%xmin), r(i) - 0.02 * (self%ymax - self%ymin), trim(tr(i)), 2.2, 0.0, trim(adjust))
            end if  
          end if  
        end do    
      end subroutine y_tick
    end module m_axis
    !================================================================
    module m_graph
      use m_axis
      implicit none
    contains
      subroutine plot(x, y, xrange, yrange, title, fmt, logq)
        real, intent(in) :: x(:)
        real, intent(in), optional :: y(:), xrange(:), yrange(:)
        character (len = *), intent(in), optional :: title(3), fmt(2)
        logical, intent(in), optional :: logq(2)
        real, allocatable :: xx(:), yy(:)
        real :: x0, y0, x1, y1, xmin, ymin, xmax, ymax, xstep, ystep, f
        integer :: i, m, mstepx, mstepy
        character (len = :), allocatable :: fmt_(:)  
        type (t_graph) :: fig
        if (present(y)) then
          xx = x
          yy = y
        else
          allocate(xx(0:size(x)))
          forall (i = 0:size(x)) xx(i) = i  
          allocate(yy(0:size(x)))
          yy(0) = 0.0 
          yy(1:) = x
        end if
        
        xmin = minval(xx)
        ymin = minval(yy) 
        xmax = maxval(xx) 
        ymax = maxval(yy) 
        ymin = ymin - 0.001 * abs(ymax - ymin)
        ymax = ymax + 0.001 * abs(ymax - ymin)
        if (present(xrange)) then 
          xmin = xrange(1)
          xmax = xrange(2)
        end if  
        if (present(yrange)) then 
          ymin = yrange(1)
          ymax = yrange(2)
        end if  
        x0 = xmin - 0.4 * abs(xmax - xmin)
        y0 = ymin - 0.4 * abs(ymax - ymin)
        x1 = xmax + 0.2 * abs(xmax - xmin)
        y1 = ymax + 0.2 * abs(ymax - ymin )
        
        xstep = (xmax - xmin) / 4
        m = floor(log10(xstep))
        if (m==0) m = m - 1
        f = 10.0**m
        xstep = int(xstep / f) * f
        ystep = (ymax - ymin) / 2
        m = floor(log10(ystep))
        if (m==0) m = m - 1
        f = 10.0**m
        ystep = int(ystep / f) * f
        
        call fig%on(t_win32(800, 600, title = 'Graph Auto Plot'))
        call fig%window(x0, y0, x1, y1)
        call fig%graph (xmin, ymin, xmax, ymax)       
        call fig%pen(2)
      
        mstepx = 5
        mstepy = 5
        if (present(logq)) then
          if (logq(1)) mstepx = 10
          if (logq(2)) mstepy = 10
        end if  
        fmt_ = ['(F10.1)', '(ES8.1)'] 
        if (present(fmt)) fmt_ = '('//fmt//')'
        call fig%x_axis(fmt_(1), xstep, mstepx, -2.0, title(1), logq(1))
        call fig%y_axis(fmt_(2), ystep, mstepy,  2.0, title(2), logq(2))
 
        call fig%move(fig%xmin, fig%ymin)
        do i = 1, ubound(xx, 1)
          if (i == 1) call fig%move(xx(i), yy(i))
          call fig%line(xx(i), yy(i))
        end do
        if (present(title)) call fig%text((fig%xmax + fig%xmin) / 2, fig%ymax + 0.02 * abs(fig%ymax - fig%ymin), title(3), 4.0, 0.0, 'center')
        call fig%off()
      end subroutine plot
    end module m_graph