fortran66のブログ

fortran について書きます。

【メモ帳】fpm に X11 用の c ファイル入れる

fpm に c プログラム入れて X window に図

fpm を利用して unicode plot と X-window で同じ図を出すことにします。

fortran66.hatenablog.com

そのためには、fpm に c プログラムを認識させ、X11 のライブラリとリンクさせる必要があります。fpm 公式 gihub に例題集があって、それを見ることで何とかなりました。

github.com

fpm.toml の [build] に link = "X11" を付け加えるだけで解決します。

[build]
auto-executables = true
auto-tests = true
auto-examples = true

link = "X11" 

実行結果

f:id:fortran66:20210529022716p:plain

プログラム

OO 式に abstract type で抽象クラスを書いておきました。これによって fig オブジェクトを allocate する時に、どのデバイスに出すかを決定しています。そこ以外は共通のプログラムで書けます。

改訂版

下に貼ったソースコードを改訂したものを github に上げました。

github.com

git clone https://github.com/f66blog/xplot
cd xplot
fpm run
fpm test

f:id:fortran66:20210529223629p:plain

メインプログラム app/main.f90

program main
    use device
    implicit none  
    class (device_t), allocatable :: fig
    real, allocatable :: x(:), y(:)
    integer :: n = 10**3
    allocate(x(n), y(n))
    call random_seed()
    call random_number(x)
    call random_number(y)  

uplot: block
        use  uniplot
        allocate(fig, source = fig_t(100, 100))
        call monte_carlo(fig, x, y) 
        deallocate(fig)
    end block uplot

xplot: block
        use xplot
        allocate(fig, source = fig_t(450, 450))
        call monte_carlo(fig, x, y) 
        deallocate(fig)
    end block xplot

contains
    subroutine monte_carlo(fig, x, y)
        class(device_t), intent(in out) :: fig
        real, intent(in) :: x(:), y(:)
        integer :: i, ix0, iy0, ix1, iy1, k
        k = fig%nx
        print *
        print *, 'Monte Carlo: estimated pi =', 4.0 * count(x**2 + y**2 < 1.0) / size(x) 
        call fig%init()
        ! draw box
        call fig%line0(0, 0, k-1, 0)
        call fig%line0(0, 0, 0, k-1)
        call fig%line0(0, k-1, k-1, k-1)
        call fig%line0(k-1, 0, k-1, k-1)
        ! draw 1/4 circle
        ix0 = 0
        iy0 = 0
        do ix1 = 0, k - 1
            iy1 = k - 1 - int(sqrt(real((k-1)**2 - ix1**2)))
            call fig%line0(ix0, iy0, ix1, iy1)
            ix0 = ix1
            iy0 = iy1
        end do           
  
        do i = 1, n 
            call fig%point(int(k * x(i)), int(k * y(i)))
        end do 
        call fig%show()
    end subroutine monte_carlo

end program main

抽象クラス定義 src/device.f90

module device
    implicit none
    type, abstract :: device_t
        integer :: nx, ny
    contains
        procedure (device_init) , deferred, pass :: init     
        procedure (device_point), deferred, pass :: point 
        procedure (device_show) , deferred, pass :: show  
        procedure (device_line0), deferred, pass :: line0
        procedure (device_line) , deferred, pass :: line
    end type device_t
    
    abstract interface
        subroutine device_init(fig)
            import :: device_t
            class(device_t), intent(in out) :: fig 
        end subroutine device_init  
        
        subroutine device_point(fig, ix, iy)
            import :: device_t
            class(device_t), intent(in out) :: fig 
            integer, intent(in) :: ix, iy
        end subroutine device_point
        
        subroutine device_show(fig)
            import :: device_t
            class(device_t), intent(in) :: fig
        end subroutine device_show    
        
        subroutine device_line0(fig, ix0, iy0, ix1, iy1)
            import :: device_t
            class(device_t), intent(in out) :: fig 
            integer, intent(in) :: ix0, iy0, ix1, iy1
        end subroutine device_line0
        
        subroutine device_line(fig, x, y, ipen)
            import :: device_t
            class(device_t), intent(in out) :: fig
            real, intent(in) :: x, y
            integer, intent(in) :: ipen
        end subroutine device_line
    end interface
    
end module device

unicode plot 用モジュール src/uniplot.f90

module uniplot
    use device
    implicit none
    private
    public :: fig_t
    type, extends(device_t) :: fig_t
        private
        integer, allocatable :: array(:, :)
    contains
        procedure :: init  
        procedure :: point 
        procedure :: show  
        procedure :: line0
        procedure :: line
    end type fig_t
contains
    subroutine init(fig)
        class(fig_t), intent(in out) :: fig
        allocate(fig%array(0:(fig%nx+1)/2, 0:(fig%ny+3)/4) )
    end subroutine init  
  
    subroutine point(fig, ix, iy)
        class(fig_t), intent(in out) :: fig 
        integer, intent(in) :: ix, iy
        integer :: iax, iay
        iax = ix / 2
        iay = iy / 4
        ! clipping
        if (0<=ix .and. ix<fig%nx .and. 0<=iy .and. iy<fig%ny) then
            fig%array(iax, iay) = ior(fig%array(iax, iay), icode(mod(ix, 2), mod(iy, 4)))
        end if     
    end subroutine point
  
    pure elemental integer function icode(kx, ky)
        integer, intent(in) :: kx, ky
        if (ky == 3) then
            icode = 64 + 64 * kx
        else ! 0, 1, 2
            icode = 2**(ky + 3*kx)  
        end if
    end function icode
  
    subroutine line0(fig, ix0, iy0, ix1, iy1)
        class(fig_t), intent(in out) :: fig 
        integer, intent(in) :: ix0, iy0, ix1, iy1
        integer :: i, ix, iy, nx, ny
        real :: d
        nx = ix1 - ix0
        ny = iy1 - iy0
        if (nx == 0 .and. ny ==0) then
            call fig%point(ix, iy) 
        else if (abs(nx) < abs(ny)) then
            d = nx / real(ny)
            do i = 0, ny, sign(1, ny) 
                call fig%point(nint(ix0 + i * d), iy0 + i)
            end do    
        else
            d = ny / real(nx)
            do i = 0, nx, sign(1, nx)
                call fig%point(ix0 + i, nint(iy0 + i * d))
            end do  
        end if  
    end subroutine line0
  
    subroutine show(fig)
        class(fig_t), intent(in) :: fig 
        integer :: iy
        do iy = 0, ubound(fig%array, 2)
            print '(*(a))', reverse_endian(shift_code(fig%array(:, iy)))
        end do
    end subroutine show    
  
    pure elemental integer function shift_code(k)
        integer, intent(in) :: k
        integer, parameter :: n0 = Z'E2A080' !14852224
        shift_code = n0 + 256 * (k /64) + mod(k, 64)  !E2A180, E2A280, E2A380      
    end function shift_code    
    
    pure elemental character(len = 4) function reverse_endian(i)
        integer, intent(in) :: i
        character:: tmp(4)
        tmp = transfer(i, ' ', size = 4)
        reverse_endian = transfer(tmp(4:1:-1), '    ')  !array 4 to len 4
    end function reverse_endian
    
    
    subroutine line(fig, x, y, ipen)
        class(fig_t), intent(in out) :: fig
        real, intent(in) :: x, y
        integer, intent(in) :: ipen
        integer, save :: ix0 = 0, iy0 = 0 
        integer :: ix, iy
        real, parameter :: xn = 80.0, yn = 100.0, fx = 1.0, fy = 0.85
        ix = nint( fx * x + xn)      
        iy = nint(-fy * y + yn)
        if (ipen == 1) call fig%line0(ix0, iy0, ix, iy)
        ix0 = ix
        iy0 = iy
    end subroutine line
  
end module uniplot

X window 用 plot モジュール src/xplot.f90

module xplot
    use device
    implicit none
    private
    public :: fig_t

    interface
        subroutine Xopen(nx, ny) bind(c, name = 'X_open')
            integer, value :: nx, ny
        end subroutine Xopen

        subroutine Xpoint(ix, iy) bind(c, name = 'X_point')
            integer, value :: ix, iy
        end subroutine Xpoint

        subroutine Xclose() bind(c, name = 'X_close')
        end subroutine Xclose

        subroutine Xflush() bind(c, name = 'X_flush')
        end subroutine Xflush
    end interface

    type, extends(device_t) :: fig_t
        private
    contains
        procedure :: init     
        procedure :: point 
        procedure :: show  
        procedure :: line0
        procedure :: line
        final :: x_close
    end type fig_t

contains

    subroutine init(fig)
        class(fig_t), intent(in out) :: fig 
        call Xopen(fig%nx, fig%ny)  
     !   call sleep(1)  ! non-standard
    end subroutine init  

    subroutine point(fig, ix, iy)
        class(fig_t), intent(in out) :: fig 
        integer, intent(in) :: ix, iy
        call Xpoint(ix, iy)
    end subroutine point

    subroutine show(fig)
       class(fig_t), intent(in) :: fig
       call XFlush() 
    end subroutine show    

    subroutine line0(fig, ix0, iy0, ix1, iy1)
        class(fig_t), intent(in out) :: fig 
        integer, intent(in) :: ix0, iy0, ix1, iy1
        integer :: nx, ny, i
        real  :: d
        nx = ix1 - ix0
        ny = iy1 - iy0       
        if (nx == 0 .and. ny == 0) then
            call Xpoint(ix0, iy0)
        else if (abs(nx) < abs(ny)) then
            d = nx / real(ny)
            do i = 0, ny, sign(1, ny) 
                call Xpoint(nint(ix0 + i * d), iy0 + i)
            end do    
        else
            d = ny / real(nx)
            do i = 0, nx, sign(1, nx)
                call Xpoint(ix0 + i, nint(iy0 + i * d))
            end do    
        end if
    end subroutine line0

    subroutine x_close(fig)
        type(fig_t), intent(in out) :: fig
        print *, 'press ENTER to continue'
        read *
        call Xclose()
    end subroutine x_close

  
    subroutine line(fig, x, y, ipen)
        class(fig_t), intent(in out) :: fig
        real, intent(in) :: x, y
        integer, intent(in) :: ipen
        integer, save :: ix0 = 0, iy0 = 0 
        integer :: ix, iy
        real, parameter :: xn = 250.0, yn = 600.0, fx = 4.0, fy = 4.0
        ix = nint( fx * x + xn)      
        iy = nint(-fy * y + yn)
        if (ipen == 1) call fig%line0(ix0, iy0, ix, iy)
        ix0 = ix
        iy0 = iy
    end subroutine line

end module xplot

X11 c 言語インターフェース src/plot.c

#include <X11/Xlib.h>
#include <X11/Xutil.h>
static Display* d;
static Window   w;
static GC       gc;
unsigned long white, black;

void X_open(int nx, int ny){
    // Open a display
    d = XOpenDisplay(0);
    if ( !d ) return;
    //
    white = WhitePixel(d, DefaultScreen(d));
    black = BlackPixel(d, DefaultScreen(d));
    // Create a window
    w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, nx, ny, 0, black, white);
    XMapWindow(d, w);
    gc = XCreateGC(d, w, 0, 0);
    XFlush(d);
}

void X_point(int ix, int iy){
     XDrawPoint(d, w, gc, ix, iy);
     XFlush(d);
}

void X_flush(){
     XFlush(d);
}

void X_close(void){
    XFreeGC(d, gc);
    XDestroyWindow(d, w);
    XFlush(d);
    XCloseDisplay(d);
}