Fortran: εντολές και άλλα χρήσιμα

1
Γράψτε εδώ "επιπλέον" εντολές και άλλα χρήσιμα "κολπάκια" που κατά καιρούς χρησιμοποιείτε σε κώδικες Fortran. Γράψτε επίσης εδώ ενδιαφέρουσες σχετικές ιστοσελίδες, όπως το Fortran Wiki.

Κάνω την αρχή με κάποια πράγματα που έχω πρόσφατα στο μυαλό μου:

1. Για να βρείτε το χρόνο εκτέλεσης ενός κομματιού κώδικα, μπορείτε να χρησιμοποιήσετε την υπορουτίνα cpu_time ως εξής:

Κώδικας: Επιλογή όλων

real :: time1, time2
...
call cpu_time(time1)

(κομμάτι κώδικα που ενδιαφέρει)

call cpu_time(time2)
Write (*,*) "Processing time = ", time2-time1, " seconds"
2. Για να αποθηκεύσετε δεδομένα κατευθείαν σε αρχεία που μπορεί να διαβάσει ως πίνακες το Excel ή άλλα προγράμματα, μπορείτε να αποθηκεύσετε σε μορφή ".csv" (="comma separated values"). Δεν είναι και ότι καλύτερο, αφού δεν είναι προτυποποιημένο ως προς υποδιαστολή/διαχωριστικό στηλών (column delimiter), αλλά μερικές φορές βολεύει. Θα πρέπει όταν γράψετε τις τιμές μιας γραμμής ενός πίνακα σε ένα αρχείο, να βάλετε ανάμεσα το σύμβολο που έχετε ρυθμίσει ως column delimiter. Συνηθίζεται αυτό να είναι η άνω τελεία (";"). Δηλαδή μπορείτε να γράψετε:

Κώδικας: Επιλογή όλων

Write (1,"(100(F5.2,';'))") (A(i,j), j=1,N)
Ο column delimiter και η υποδιαστολή που καταλαβαίνει το Excel αντλούνται από τις "τοπικές ρυθμίσεις" των Windows. Το LibreOffice Calc καταλαβαίνει ότι του πείτε σε κάθε αρχείο ή ακόμα και μεμονωμένο κελί.
(credits στον Orpheus για την ιδέα) edit: μάλλον θέλει μονά εισαγωγικά μέσα...

3. Για να καλέσετε εντολές του συστήματος μέσα από έναν κώδικα μπορείτε να χρησιμοποιήσετε την υπορουτίνα system.
Για παράδειγμα, εάν σε κάποιο σημείο γράψω:

Κώδικας: Επιλογή όλων

call system("nano results.txt")
τότε θα ανοίξει το αρχείο results.txt στον κειμενογράφο nano (μέσα στο terminal), θα μπορώ να δω-επεξεργαστώ τα αποτελέσματα και, αφού κλείσω τον κειμενογράφο, το πρόγραμμα θα συνεχίσει τη λειτουργία του.
Ομοίως μπορώ να καλέσω και γραφικές εφαρμογές φυσικά:

Κώδικας: Επιλογή όλων

call system("libreoffice --calc results.csv")
(credits στον κ. Καραντώνη)

4. Πριν λίγες μέρες, στα ««σεμινάρια»» χρήσης Η/Υ που γίνονται κάθε Τετάρτη προσπάθησα να κάνω μια παρουσίαση για σύνθετους τύπους δεδομένων (derived data types) στη Fortran. Επισυνάπτω τις διαφάνειες για όποιον ενδιαφέρεται, με κάθε επιφύλαξη για λάθη ή ανακρίβειες. Δείτε επίσης τις αντικειμενοστραφείς δυνατότητες της Fortran. Εκεί θα βρείτε και κάποια βασικά πράγματα για τα modules.
Συνημμένα
type.pdf
Παρουσίαση για derived types στη Fortran (pdf)
(71.37 KiB) Μεταφορτώθηκε 220 φορές
type.odp
Παρουσίση για derived types στη Fortran (πρωτότυπο odp)
(30.47 KiB) Μεταφορτώθηκε 203 φορές

Re: Fortran: εντολές και άλλα χρήσιμα

2
Τελικά οι αρχικοποιήσεις μεταβλητών (στο τμήμα δηλώσεων) και οι στατικές δομές δεδομένων δεν είναι και ότι καλύτερο... Δοκιμάστε να τρέξετε τους εξής τρεις κώδικες και παρατηρήστε διαφορά χρόνου στο compilation, στη μνήμη που καταναλώνεται και στο μέγεθος του αρχείου που δημιουργείται! Παίξτε με το μέγεθος της array και δείτε διαφορές. Οι πρώτοι δυο κώδικες δημιουργούν εκτελέσιμο αρχείο μεγέθους 38MB, ενώ ο τρίτος μόλις 9KB! (χρησιμοποιώντας τον GNU gfortran compiler)
Spoiler: show

Κώδικας: Επιλογή όλων

Program test1

real :: A(10000000) = 1

print *, A

end

Κώδικας: Επιλογή όλων

Program test1

integer, parameter :: N = 10000000
real :: A(N) = 1

print *, A

end

Κώδικας: Επιλογή όλων

Program test1

integer :: N
real, allocatable :: A(:)

N = 10000000
Allocate (A(N))
A = 1
print *, A

end
Αυτοί οι κώδικες δείχνουν μονάχα το πρόβλημα που δημιουργεί η αρχικοποίηση. Διαφορές υπάρχουν ωστόσο και ανάμεσα στις στατικές-δυναμικές arrays.
Γιατί συμβαίνει αυτό; Δείτε εδώ, στις παραγράφους "Program Size" και "Memory Map". [credits στον tim και στον κ. Α. Σπυρόπουλο]

Re: Fortran: εντολές και άλλα χρήσιμα

3
Συναρπαστική ανακάλυψη!!!
Σκεφτήκατε ποτέ να χρησιμοποιήσετε το ":" στη θέση ενός δείκτη μιας array; Έχει μια συμπεριφορά αντίστοιχη του Matlab! Όπως επίσης (ήδη γνωστό), μπορώ να γράφω σκέτα τα ονόματα των arrays (χωρίς δείκτες) και να κάνω πράξεις με ολόκληρα διανύσματα/πίνακες! (χρησιμοποιώντας ένα προς ένα τα στοιχεία τους)

Για παράδειγμα, έστω δυο πίνακες a και b με τα στοιχεία των οποίων θέλω να κάνω πράξεις και να βάλω το αποτέλεσμα σε μια άλλη array. Τρέξτε το ακόλουθο παράδειγμα:
Spoiler: show

Κώδικας: Επιλογή όλων

Program test

        implicit none 
        real, allocatable :: a(:,:), b(:,:), x(:,:)
        integer :: N
        
        N=2
        allocate( a(N,N), b(N,N), x(N,N) )
        
        a=0
        b=0
        
        a(1,1)=1 ; a(1,2)=2
        a(2,1)=3 ; a(2,2)=4
        
        b(1,1)=5 ; b(1,2)=6
        b(2,1)=7 ; b(2,2)=8
        
        x=a*b
        
        print *, x
        
End 
1. Οι εντολές a=0, b=0 βάζουν σε όλα τα στοιχεία της array a και της array b την τιμή 0.
2. Η x=a*b πολλαπλασιάζει κάθε στοιχείο του a με το αντίστοιχο στοιχείο του b και βάζει το αποτέλεσμα στο αντίστοιχο στοιχείο του x. Προσοχή όμως! Όταν οι διαστάσεις είναι ασύμβατες, τα πράγματα γίνονται περίεργα, δείτε το!
3. Παρατηρήστε πώς εκτυπώνονται τα στοιχεία της x! Σε μια γραμμή και ακολουθώντας σειρά "κατά στήλες" και όχι "κατά γραμμές" όπως σκεφτόμαστε. Με αυτή τη σειρά αποθηκεύει arrays η Fortran. Δοκιμάστε να αυξήσετε το Ν.

Το καινούργιο που ανακάλυψα σήμερα εξυπηρετεί το εξής σκεπτικό: έστω ότι θέλω να έχω πολλά διανύσματα, έστω u1, u2, u3 κτλ. Π.χ. u1=[1 2 3], u2=[3.14 1.23 1.01] κτλ. Έστω ότι έχω ένα μεγάλο ή άγνωστο πλήθος τέτοιων διανυσμάτων: είναι πρακτικά αδύνατο να δηλώσω μια ξεχωριστή array για κάθε διάνυσμα! Θέλω λοιπόν ένα διάνυσμα του οποίου τα στοιχεία να είναι διανύσματα. Νομίζω ότι κάτι τέτοιο δεν γίνεται άμεσα στη Fortran μπορεί να γίνει όμως το εξής: κάθε διάνυσμα θα είναι η στήλη ενός διδιάστατου πίνακα, συνεπώς το πόσα διανύσματα θα έχω θα καθορίζεται από την "οριζόντια" διάσταση του πίνακα, κάτι που μπορεί να καθοριστεί και σε μια allocate. Μπορώ να γράψω το εξής:

Κώδικας: Επιλογή όλων

Program test

        implicit none 
        real, allocatable :: u(:,:), x(:)
        integer :: N,m
        
        N=3 ; m=2
        allocate( u(N,m), x(N) )
        
        u(:,1) = (/1, 2, 3/)
        u(:,2) = (/3.14, 1.23, 1.01/)
        
        print *, u(:,1)
        
        x(:) = 10*u(:,2)
        print *, x
        
End 
Όταν γράφω u(:,2) απομονώνω μόνο τη 2η στήλη της (διδιάστατης) array u, η οποία συμπεριφέρεται ακριβώς όπως ένα απλό διάνυσμα! (μονοδιάστατη array) Παρατηρήστε ότι τα x και u δεν έχουν τις ίδιες διαστάσεις, όμως η εντολή που έχω γράψει μπορεί να εκτελεστεί. Από εδώ ξεκινάνε πολλές σκέψεις και διευκολύνσεις, παίξτε!

Re: Fortran: εντολές και άλλα χρήσιμα

4
Formatted output και αλλαγή γραμμών:

Για να μην αλλάξετε γραμμή στο τέλος του record προσθέστε ένα «advance="yes"» μετά την περιγραφή του format. Δεν δουλεύει με unformatted output (βλ. "*"). Παράδειγμα:

Κώδικας: Επιλογή όλων

write (*,"(A)",advance="yes") "hello"
write (*,*) "world!"
Παρατηρήστε ότι αφήνει ένα κενό ανάμεσα στα δυο πράγματα που τυπώνει. Το "(Α)" δηλώνει format χαρακτήρων με αυτόματο μέγεθος.

Για να αφήσετε μια κενή γραμμή (ακριβέστερα: για να αλλάξετε γραμμή), χρησιμοποιήστε το "/" στο format. Παράδειγμα:

Κώδικας: Επιλογή όλων

write (*,"(A)") "Hello!"
write (*,"(/,A)") "Great day, isn't it?"
Άλλο παράδειγμα:

Κώδικας: Επιλογή όλων

write (*,"(A)") "Hello!"
write (*,"(/,A,/,A)") "Great day, isn't it?","Oh yeah!"

Unformatted output και ακρίβεια:
Αν χρησιμοποιείτε το gfortran ίσως να παρατηρήσατε ότι αν κάνετε επαναληπτικούς υπολογισμούς με REAL απλής ακρίβειας και τους εμφανίσετε unformatted, φαίνεται η συσσώρευση round-off errors. Το γεγονός αυτό φαίνεται αρχικά ανησυχητικό, αφού δεν έχουμε συνηθίσει να βλέπουμε τέτοια στο Developer Studio.

Για να δούμε την ακρίβεια με την οποία αποθηκεύεται μια μεταβλητή μπορούμε να χρησιμοποιήσουμε τη συνάρτηση epsilon(x). Στον υπολογιστή μου τουλάχιστον, βγαίνει:
  • για REAL: eps = 1.19209290E-07 (δηλαδή σφάλμα στο 7ο δεκαδικό)
  • για REAL*8: eps = 2.22044604925031308E-016 (δηλαδή σφάλμα στο 16ο δεκαδικό)
Ας δοκιμάσουμε να τρέξουμε τον παρακάτω κώδικα:
Spoiler: show

Κώδικας: Επιλογή όλων

Program prec_test

implicit none
real x
integer i

x=0
print *, epsilon(x)

Do i=1,100
        x=x+0.1
        print *, x
enddo
     
End
Διαφορετικοί compilers και διαφορετικοί υπολογιστές από ότι φαίνεται εμφανίζουν διαφορετικό πλήθος ψηφίων όταν τα δεδομένα τυπώνονται unformatted. Συγκεκριμένα:
  • Σε intel i5 με Xubuntu 12.04 64bit και gfortran (gcc) 4.6.3 εμφανίζονται 8 δεκαδικά ψηφία και τα round-offs εμφανίζονται από την 3η επανάληψη και μετά. Μετά από 100 επαναλήψεις, η απόκλιση δείχνει να είναι 2*10-6.
  • Σε intel atom με Windows XP 32bit και gfortran (gcc) 4.7.0 εμφανίζονται 9 δεκαδικά ψηφία και τα round-offs εμφανίζονται από την πρώτη επανάληψη. Μετά από 100 επαναλήψεις, η απόκλιση δείχνει να είναι 1.9*10-6.
Κοινώς, ακόμα και στο unformatted output γίνεται κάποια στρογγυλοποίηση. Επίσης, μπορεί να εμφανίζονται περισσότερα δεκαδικά από ότι επιτρέπει η ακρίβεια της μεταβλητής. Δεν γνωρίζω αν αυτό είναι bug ή απλώς υπάρχει για να σου υπενθυμίζει ότι το δεκαδικό σύστημα δεν αναπαρίσταται ακριβώς.

Στο βιβλίο των Metcalf, Reid, Cohen προτείνεται να χρησιμοποιείται γενικώς unformatted output εάν τα δεδομένα διακινούνται μέσα στον ίδιο υπολογιστή και πρόγραμμα και να χρησιμοποιείται format μονάχα εάν πρέπει να παρουσιαστούν.

Μπορεί κάποιος να τρέξει τον κώδικα σε άλλο pc-compiler και να παραθέσει τα αντίστοιχα αποτελέσματα;

Re: Fortran: εντολές και άλλα χρήσιμα

5
Αλλαγή γραμμής σε read/write, αν π.χ. διαβάζετε ένα αρχείο και η πρώτη του γραμμή περιέχει τους τίτλους:
απλώς βάλτε μια κενή read(*,*) ή, αντίστοιχα μια κενή write(*,*)! Ναι, εννοώ αυτό το απλό:

Κώδικας: Επιλογή όλων

open(unit=1, file='input.txt')

read(1,*)
do i=1,N
    read(1,*) A(i)
enddo
Μη αλλαγή γραμμής σε read/write, αν π.χ. θέλετε να γράψετε σε μια γραμμή ενός αρχείου ή της οθόνης πρώτα κάποιες στήλες σε ένα σημείο του προγράμματος και κάποιες άλλες στήλες σε ένα άλλο σημείο αργότερα (χωρίς να έχετε αλλάξει γραμμή)
Μπορείτε να χρησιμοποιήσετε την επιλογή "advance":

Κώδικας: Επιλογή όλων

write(1,*,advance="no") x, ";", y, ";"
metro = sqrt( x**2 + y**2 )
write(1,*) metro
Αν το παραλείψετε, υποννοείται advance="yes", οπότε, μετά τη δεύτερη write, αλλάζει γραμμή στο αρχείο/οθόνη.

Λογικές μεταβλητές
Υπενθυμίζω ότι μπορείτε να χρησιμοποιείτε μεταβλητές τύπου logical, οι οποίες παίρνουν τιμές .true. ή .false. και οι οποίες μπορούν να χρησιμοποιούνται κατευθείαν σε μια IF στη μορφή π.χ. IF (test) μπλαμπλα ή IF (.not. test) μπλαμπλα.

Re: Fortran: εντολές και άλλα χρήσιμα

6
Για τον @panos108 που είχε ρωτήσει πριν ?5? χρόνια: Fortran increase dynamic array size in function

Συνοπτικά:

Κώδικας: Επιλογή όλων

real, allocatable :: x(:), temp(:)
integer :: new_size
...
(allocate x)
...
new_size = ...
allocate(temp(new_size))
temp(1:size(x,1)) = x
temp(size(x,1)+1,:) = ...

call move_alloc(temp, x)
Μετά την move_alloc(), η temp καταστρέφεται. Απαιτείται Fortran 2003 ή νεότερη.

edit: Επίσης, στη Fortran 2003 δεν απαιτείται allocate() πριν την ανάθεση τιμών. Για παράδειγμα, το εξής:

Κώδικας: Επιλογή όλων

x = [1, 2]
x = [3]
x = [4, 5, 6]
θα δημιουργήσει αρχικά μια array με δυο στοιχεία (με τιμές 1, 2), μετά θα αλλάξει το μέγεθος σε ένα στοιχείο και μετά θα αλλάξει το μέγεθος σε τρία στοιχεία.

Re: Fortran: εντολές και άλλα χρήσιμα

8
Μερικές ενδιαφέρουσες προβληματικές περιπτώσεις: Mistakes in Fortran 90 Programs That Might Surprise You.

Πολλά αφορούν pointers και derived types που δεν μαθαίνουμε στο μάθημα του 1ου εξαμήνου, αλλά έχει και κάποια πιο απλά.