I am trying to create a frequency array of a given array on Fortran 95. For instance if I have an array (\1 2 4 2 4 2 5), the frequency array should be the number of times each item appears; (\1 3 2 3 2 3 1). So because there are only 1 of 5s in the original array, the last entry in the new array is 1 and because there are 3 of 2s in the original array, the corresponding entries on the new array is a 2.
Below is a sample of the code I have, but I keep getting errors. I was wondering if anyone would be willing to give me some guidance and help on what I could be doing wrong. It would be very much appreciated.
I haven't included the part of the code that generates my original array because I'm pretty sure it is correct so here is just the subroutine for the frequency array. Also the original array was sorted in ascending order outside this subroutine. Perhaps I didn't pass the original array, num(i) correctly??
INTEGER, DIMENSION(100)::num(100)
INTEGER, DIMENSION(100)::freq(100)
INTEGER:: i=0, k=0, numinteger, count=0
CALL FreqArray(num, numinteger,freq)
SUBROUTINE FreqArray(num, numinteger, freq)
INTEGER, INTENT(IN):: num(100), numinteger
INTEGER, INTENT(OUT):: freq(100)
DO i=1,9
count=0
DO k=1, numinteger
IF (num(k)==i)THEN
count=count+1
END IF
END DO
freq(i)=count
END DO
PRINT*, "Frequency of Digits"
PRINT*, " "
WRITE(*,'(1X,A,T35,A)'),"Digit","Frequency"
WRITE(*,'(1X,I1,T35,I1)'),num(i),freq(i)
END SUBROUTINE
Thanks so much for your time.
I guess that the "(9)" is overriding the "DIMENSION(100)" making "freq" an array of length 9. Thus for the third argument the actual argument is length 9, while the dummy is length 100. Which causes your error message "actual argument ... is smaller than dummy size".
Other suggestions: you could make the subroutine more general using declarations "..., dimension (:) :: num". Then use the "size" intrinsic to determine the size of the num. freq could be fixed to 9 since there are always 9 digits.
With regards to your display issue, I suspect that you meant to have a loop around
WRITE(*,'(1X,I1,T35,I1)'),num(i),freq(i)
Related
In my code:
DO i=1,numJog,1
IF(val(i) .EQV. .TRUE.)THEN
DO j=1,contVenc,1
result(j) = i
END DO
END IF
END DO
Where val is a logical array, and result is a integer array.
For example, if val is:
F
T
F
T
Then, i=2 and i=4.
But the result array just write 4 twice. For example:
DO i=1,contVenc,1
WRITE(*,*) result(i)
END DO
The result is:
4
4
Instead of
2
4
If I make some changes in my code like:
DO i=1,numJog,1
IF(val(i) .EQV. .TRUE.)THEN
WRITE(*,*) i
END IF
END DO
The result is:
2
4
As I wanted.
Conclusion, I think this second loop is causing this problem.
Yes, your second loop is at fault here. You haven't said what contVenc is, but it crucially doesn't change at any point in the fragment you have there. That just means that the same elements of result are being assigned to whenever you have a .TRUE. in val.
In your case they are both set to 2 for the first .TRUE. and are then both set to 4 for the second.
You are more likely to mean something like (with extra tidying):
j = 0
DO i=1,numJog
IF (val(i)) THEN
j = j+1 ! Test this as a bound
result(j) = i
END IF
END DO
But then, I'd just use PACK. Your intended loop has the same effect as
result(1:COUNT(val(1:numJog))) = PACK([(i,i=1,numJog)], val(1:numJog))
Again hoping that result is large enough.
That said, if numJog is just the size of the array val (that is, you aren't just doing this on a sub-array) then, as High Performance Mark comments,
result(1:COUNT(val)) = PACK([(i,i=1,SIZE(val))], val)
avoids tracking this size separately.
Finally, with result an allocatable (Fortran 2003) array you needn't even (but still can) worry about counting the number of wanted indices and that the array is sufficiently large:
result = PACK([(i,i=1,SIZE(val))], val)
I am not 100% what the role of the 1: is here. At which index to start the copy? But then why not two such parameters for the rank 2 array?
To be a little more explicit:
In Fortran90 and up you access a single array value by giving a single index, and access a subarray (a window of the array) by giving a range of indices separated by a colon.
a(1) = 0.
a(2:5) = (/3.14,2.71,1.62,0./)
You can also give a step size.
a(1:5:2) = (/0,2.71,0./)
And finally, you can leave out values and a default will be inserted. So if a runs from index 1 to 5 then I could write the above as
a(::2) = (/0,2.71,0./)
and the 1 and 5 are implied. Obviously, you shouldn't leave these out if it makes the code unclear.
With a multidimensional array, you can mix and match these on each dimension, as in your example.
You're taking a slice of array2, namely the elements in the D'th column from row 1 to C and putting them in the slice of array1 which is elements 1 through A
So both slices are 1-dimensional arrays
Slice may not be the correct term in Fortran
I'm trying to check if my arrays are returning nonsense by accessing out of bounds elements, in fortran. And I want to check these values are less than one, and if they are, change them to one.
This is the piece of my code causing issues:
lastNeighLabel=(/clusterLabel(jj-1,kk,ll), clusterLabel(jj,kk-1,ll), clusterLabel(jj,kk,ll-1)/)
LastNeighLabel contains the cluster label (between 1 and n, where n isthe total number of unique seperate clusters found) for the last neighbour in the x,y,z direction respectively.
When jj or kk or ll are 1, they try and access the 0th element in the array, and as FORTRAN counts from 1 in arrays, it tries to destroy the universe. I'm currently in a tangled mess of about 8 if/elseif statements trying to code for every eventuality. But I was hoping there was a way of operating on each element. So basically I'd like to say where((/jj-1,kk-1,ll-1/).lt.1) do clusterLabel(jj-1,kk,ll)=0 etc depending on which element is causing the problem.
But I can't think of a way to do that because where will only manipulate the variables passed to it, not a different array at the same index. Or am I wrong?
Will gladly edit if this doesn't make sense.
It is not obligatory that Fortran accesses arrays starting from one. Any starting value is allowed. If it more convenient to you to have a zero indexed array, declare the array as:
real, dimension (0:N-1, 0:M-1) :: array
Or
real, dimension (0:N, 0:M) :: array
and have the 0 indices be extra to catch special cases.
This might be another solution to your problem, since zero index values would be legal.
Another possible way to approach this, is to create an extended cluster label array (with index bounds starting at 0), which is equal to the cluster label array with a layer of zeroes tacked on the outside. You can then let your loop run safely over all values of jj, kk, and ll. It depends on the size of the array if this is a feasible solution.
integer :: extended_cluster_label(0:size(cluster_label,1), &
0:size(cluster_label,2), &
0:size(cluster_label,3) &
)
extended_cluster_label(0,:,:) = 0
extended_cluster_label(:,0,:) = 0
extended_cluster_label(:,:,0) = 0
extended_cluster_label(1:, 1:, 1:) = cluster_label
Maybe you could use a function?
real function f(A,i,j,k)
real :: A(:,:,:)
integer :: i,j,k
if (i==0.or.j==0.or.k==0) then
f=0
else
f=A(i,j,k)
endif
end function f
and then use f(clusterLabel,jj-1,kk,ll) etc.
I am new to Fortran, and I would like to be able to write a two-dimensional array to a text file, in a row-wise manner (spaces between columns, and each row on its own line). I have tried the following, and it seems to work in the following simple example:
PROGRAM test3
IMPLICIT NONE
INTEGER :: i, j, k, numrows, numcols
INTEGER, DIMENSION(:,:), ALLOCATABLE :: a
numrows=5001
numcols=762
ALLOCATE(a(numrows,numcols))
k=1
DO i=1,SIZE(a,1)
DO j=1,SIZE(a,2)
a(i,j)=k
k=k+1
END DO
END DO
OPEN(UNIT=12, FILE="aoutput.txt", ACTION="write", STATUS="replace")
DO i=1,numrows
WRITE(12,*) (a(i,j), j=1,numcols)
END DO
END PROGRAM test3
As I said, this seems to work fine in this simple example: the resulting text file, aoutput.txt, contains the numbers 1-762 on line 1, numbers 763-1524 on line 2, and so on.
But, when I use the above ideas (i.e., the last fifth-to-last, fourth-to-last, third-to-last, and second-to-last lines of code above) in a more complicated program, I run into trouble; each row is delimited (by a new line) only intermittently, it seems. (I have not posted, and probably will not post, here my entire complicated program/script--because it is rather long.) The lack of consistent row delimiters in my complicated program/script probably suggests another bug in my code, not with the four-line write-to-file routine above, since the above simple example appears to work okay. Still, I am wondering, can you please help me think if there is a better row-wise write-to-text file routine that I should be using?
Thank you very much for your time. I really appreciate it.
There's a few issues here.
The fundamental one is that you shouldn't use text as a data format for sizable chunks of data. It's big and it's slow. Text output is good for something you're going to read yourself; you aren't going to sit down with a printout of 3.81 million integers and flip through them. As the code below demonstrates, the correct text output is about 10x slower, and 50% bigger, than the binary output. If you move to floating point values, there are precision loss issues with using ascii strings as a data interchange format. etc.
If your aim is to interchange data with matlab, it's fairly easy to write the data into a format matlab can read; you can use the matOpen/matPutVariable API from matlab, or just write it out as an HDF5 array that matlab can read. Or you can just write out the array in raw Fortran binary as below and have matlab read it.
If you must use ascii to write out huge arrays (which, as mentioned, is a bad and slow idea) then you're running into problems with default record lengths in list-drected IO. Best is to generate at runtime a format string which correctly describes your output, and safest on top of this for such large (~5000 character wide!) lines is to set the record length explicitly to something larger than what you'll be printing out so that the fortran IO library doesn't helpfully break up the lines for you.
In the code below,
WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'
generates the string rowfmt which in this case would be (762(1X,I6)) which is the format you'll use for printing out, and the RECL option to OPEN sets the record length to be something bigger than 7*numcols + 1.
PROGRAM test3
IMPLICIT NONE
INTEGER :: i, j, k, numrows, numcols
INTEGER, DIMENSION(:,:), ALLOCATABLE :: a
CHARACTER(LEN=30) :: rowfmt
INTEGER :: txtclock, binclock
REAL :: txttime, bintime
numrows=5001
numcols=762
ALLOCATE(a(numrows,numcols))
k=1
DO i=1,SIZE(a,1)
DO j=1,SIZE(a,2)
a(i,j)=k
k=k+1
END DO
END DO
CALL tick(txtclock)
WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'
OPEN(UNIT=12, FILE="aoutput.txt", ACTION="write", STATUS="replace", &
RECL=(7*numcols+10))
DO i=1,numrows
WRITE(12,FMT=rowfmt) (a(i,j), j=1,numcols)
END DO
CLOSE(UNIT=12)
txttime = tock(txtclock)
CALL tick(binclock)
OPEN(UNIT=13, FILE="boutput.dat", ACTION="write", STATUS="replace", &
FORM="unformatted")
WRITE(13) a
CLOSE(UNIT=13)
bintime = tock(binclock)
PRINT *, 'ASCII time = ', txttime
PRINT *, 'Binary time = ', bintime
CONTAINS
SUBROUTINE tick(t)
INTEGER, INTENT(OUT) :: t
CALL system_clock(t)
END SUBROUTINE tick
! returns time in seconds from now to time described by t
REAL FUNCTION tock(t)
INTEGER, INTENT(IN) :: t
INTEGER :: now, clock_rate
call system_clock(now,clock_rate)
tock = real(now - t)/real(clock_rate)
END FUNCTION tock
END PROGRAM test3
This may be a very roundabout and time-consuming way of doing it, but anyway... You could simply print each array element separately, using advance='no' (to suppress insertion of a newline character after what was being printed) in your write statement. Once you're done with a line you use a 'normal' write statement to get the newline character, and start again on the next line. Here's a small example:
program testing
implicit none
integer :: i, j, k
k = 1
do i=1,4
do j=1,10
write(*, '(I2,X)', advance='no') k
k = k + 1
end do
write(*, *) '' ! this gives you the line break
end do
end program testing
When you run this program the output is as follows:
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
Using an "*" is list-directed IO -- Fortran will make the decisions for you. Some behaviors aren't specified. You could gain more control using a format statement. If you wanted to positively identify row boundaries you write a marker symbol after each row. Something like:
DO i=1,numrows
WRITE(12,*) a(i,:)
write (12, '("X")' )
END DO
Addendum several hours later:
Perhaps with large values of numcols the lines are too long for some programs that are you using to examine the file? For the output statement, try:
WRITE(12, '( 10(2X, I11) )' ) a(i,:)
which will break each row of the matrix, if it has more than 10 columns, into multiple, shorter lines in the file.
I am trying to take my array of numbers based on a variable that determines its size and sort it.
The array is created using the random numbers seed on Fortran 95. However when I try to sort it I run into big trouble. It compiles fine, but the array is printed with a lot of asterisks in it.
In addition I wanted to print my array sideways, (for instance something like this: 1 2 3 4 etc.) but I even failed at doing that. I realize that it must be done using the Advance="no" within a DO loop, but apparently that is erroneous as well.
Below is the code that I am using. If anyone is willing to let me know where I may be wrong I would be very grateful. Thanks for your time.
SUBROUTINE Sorter(num, numinteger)
INTEGER, INTENT(OUT):: num(100)
INTEGER, INTENT(IN):: numinteger
DO i=1, (numinteger-1)
min=num(i)
pos=i
DO j=i,numinteger
IF (num(j)<min)THEN
min=num(j)
pos=j
END IF
END DO
temp=num(i)
num(i)=min
num(pos)=temp
END DO
PRINT*, " "
PRINT*, "Sorted Numbers"
DO i=1, numinteger
WRITE(*,23,ADVANCE="NO") num
23 FORMAT (I2)
END DO
END SUBROUTINE
Thanks!
You don't have any spaces between your numbers, but you are also looping over the array, but not incrementing an index... you are asking the computer to print the entire array on each interation.
I think this should be: WRITE(*,23,ADVANCE="NO") num(i)