bd10
bd10

Reputation: 11

Fortran character format string as subroutine argument

I am struggling with reading a text string in. Am using gfortran 4.9.2.

Below I have written a little subroutine in which I would like to submit the write format as argument.

Ideally I'd like to be able to call it with

call printarray(mat1, "F8.3")

to print out a matrix mat1 in that format for example. The numbers of columns should be determined automatically inside the subroutine.

subroutine printarray(x, udf_temp)
implicit none
real, dimension(:,:), intent(in) :: x           ! array to be printed     
integer, dimension(2)            :: dims        ! array for shape of x
integer                          :: i, j
character(len=10)                :: udf_temp    ! user defined format, eg "F8.3, ...
character(len = :), allocatable  :: udf         ! trimmed udf_temp
character(len = 10)              :: udf2
character(len = 10)              :: txt1, txt2
integer                          :: ncols       ! no. of columns of array
integer                          :: udf_temp_length

udf_temp_length = len_trim(udf_temp)
allocate(character(len=udf_temp_length) :: udf)

dims = shape(x)
ncols = dims(2)
write (txt1, '(I5)') ncols
udf2 = trim(txt1)//adjustl(udf)
txt2 = "("//trim(udf2)//")"

do i = 1, dims(1)
   write (*, txt2) (x(i, j), j = 1, dims(2))        ! this is line 38
end do

end suroutine printarray

when I set len = 10:

character(len=10) :: udf_temp

I get compile error:

call printarray(mat1, "F8.3")
                      1
Warning: Character length of actual argument shorter than of dummy argument 'udf_temp' (4/10) at (1)

When I set len = *

character(len=*) :: udf_temp

it compiles but at runtime:

At line 38 of file where2.f95 (unit = 6, file = 'stdout')
Fortran runtime error: Unexpected element '(    8

What am I doing wrong? Is there a neater way to do this?

Upvotes: 1

Views: 792

Answers (2)

agentp
agentp

Reputation: 6999

besides the actual error (not using the input argument), this whole thing can be done much more simply:

  subroutine printarray(m,f)
  implicit none
  character(len=*)f
  real m(:,:)
  character*10 n
  write(n,'(i0)')size(m(1,:))
  write(*,'('//n//f//')')transpose(m)
  end subroutine
  end

note no need for the loop constructs as fortran will automatically write the whole array , line wrapping as you reach the length of data specified by your format.

alternately you can use a loop construct, then you can use a '*' repeat count in the format and obviate the need for the internal write to construct the format string.

  subroutine printarray(m,f)
  implicit none
  character(len=*)f
  real m(:,:)
  integer :: i
  do i=1,size(m(:,1))
     write(*,'(*('//f//'))')m(i,:)
  enddo
  end subroutine
  end

Upvotes: 1

Matt P
Matt P

Reputation: 2367

Here's a summary of your question that I will try to address: You want to have a subroutine that will print a specified two-dimensional array with a specified format, such that each row is printed on a single line. For example, assume we have the real array:

real, dimension(2,8) :: x
x = reshape([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], shape=[2,8], order=[2,1])

! Then the array is:
!    1.000   2.000   3.000   4.000   5.000   6.000   7.000   8.000
!    9.000  10.000  11.000  12.000  13.000  14.000  15.000  16.000

We want to use the format "F8.3", which prints floating point values (reals) with a field width of 8 and 3 decimal places.

Now, you are making a couple of mistakes when creating the format within your subroutine. First, you try to use udf to create the udf2 string. This is a problem because although you have allocated the size of udf, nothing has been assigned to it (pointed out in a comment by @francescalus). Thus, you see the error message you reported: Fortran runtime error: Unexpected element '( 8.

In the following, I make a couple of simplifying changes and demonstrate a few (slightly) different techniques. As shown, I suggest the use of * to indicate that the format can be applied an unlimited number of times, until all elements of the output list have been visited. Of course, explicitly stating the number of times to apply the format (ie, "(8F8.3)" instead of "(*(F8.3))") is fine, but the latter is slightly less work.

program main
    implicit none

    real, dimension(2,8) :: x
    character(len=:), allocatable :: udf_in

    x = reshape([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], shape=[2,8], order=[2,1])
    udf_in = "F8.3"
    call printarray(x, udf_in)

contains
    subroutine printarray(x, udf_in)
        implicit none
        real, dimension(:,:), intent(in) :: x
        character(len=*), intent(in)     :: udf_in

        integer                        :: ncols         ! size(x,dim=2)
        character(len=10)              :: ncols_str     ! ncols, stringified
        integer, dimension(2)          :: dims          ! shape of x
        character(len=:), allocatable  :: udf0, udf1    ! format codes
        integer                        :: i, j          ! index counters

        dims = shape(x)                                 ! or just use: ncols = size(x, dim=2)
        ncols = dims(2)

        write (ncols_str, '(i0)') ncols                 ! use 'i0' for min. size

        udf0 = "(" // ncols_str // udf_in // ")"        ! create string: "(8F8.3)"
        udf1 = "(*(" // udf_in // "))"                  ! create string: "(*(F8.3))"

        print *, "Version 1:"
        do i = 1, dims(1)
            write (*, udf0) (x(i, j), j = 1,ncols)      ! implied do-loop over j.
        end do

        print *, "Version 2:"
        do i = 1, dims(1)
            ! udf1: "(*(F8.3))"
            write (*, udf1) (x(i, j), j = 1,ncols)      ! implied do-loop over j
        end do

        print *, "Version 3:"
        do i = 1, size(x,dim=1)                         ! no need to create nrows/ncols vars.
            write(*, udf1) x(i,:)                       ! let the compiler handle the extents.
        enddo

    end subroutine printarray
end program main

Observe: the final do-loop ("Version 3") is very simple. It does not need an explicit count of ncols because the * takes care of it automatically. Due to its simplicity, there is really no need for a subroutine at all.

Upvotes: 2

Related Questions