Skip to content

Instantly share code, notes, and snippets.

@heitorPB
Last active October 19, 2022 17:45
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save heitorPB/abc750898443d6302b0b733c8a87faa5 to your computer and use it in GitHub Desktop.
Save heitorPB/abc750898443d6302b0b733c8a87faa5 to your computer and use it in GitHub Desktop.
AulaExtra FisComp 2017

Tutorial de Fortran

Neste curso usaremos Linux para programar em Fortran. Então precisamos estudar um pouco de Linux e de Fortran.

Índice:

Alguns links:

Básico de Linux

Alguns comandos de Linux são importantes e eles rodam no terminal:

  • cd Esse comando muda de diretório. Por exemplo, estou na pasta Music e quero entrar na pasta Iron Maiden:

    cd Iron\ Maiden
    

    E tem que ter essa barra invertida antes do espaco. Isso significa que o espaco faz parte do nome da pasta. Se não tiver a barra, o cd acha que você quer entrar em duas pastas, uma chamada Iron e outra Maiden, e aí dá erro. Outra opção é usar áspas: cd "Iron Maiden".

  • ls Esse comando lista o conteúdo de uma pasta. Por exemplo, estou na pasta Iron Maiden/Dance of Death:

    ls
    '01 - Wildest Dreams.mp3'  '03 - No More Lies.mp3'  '05 - Dance Of Death.mp3'     '07 - New Frontier.mp3'  '09 - Face In The Sand.mp3'  '11 - Journeyman.mp3'
    '02 - Rainmaker.mp3'       '04 - Montsegur.mp3'     '06 - Gates Of Tomorrow.mp3'  '08 - Paschendale.mp3'   '10 - Age Of Innocence.mp3'
    
  • mkdir Esse comando cria uma pasta. Por exemplo:

    mkdir FisComp17
    cd FisComp17
    mkdir Aula1
    ls
    'Aula1'
    
  • pwd Esse comando fala em qual diretório você está. Por exemplo:

    pwd
    /home/h/Music/Iron Maiden/Dance Of Death
    
  • mv Esse comando move arquivos de lugar. Por exemplo: você acabou de baixar o álbum Powerslave, de 1984 do Iron Maiden, e descompactou em sua pasta pessoal (/home/voce/) e quer mover para a pasta /home/voce/Music/IronMaiden:

    cd /home/voce
    mv Powerslave /home/voce/Music/IronMaiden
    
  • rm Esse comando deleta arquivos. Por exemplo, você quer deletar o arquivo WesleySafadao.zip:

    rm WesleySafadao.zip
    

    Mas se você quiser deletar a pasta Wesley, o comando é diferente:

    rm -r Wesley
    

    Aquele -r significa faça isso de modo recursivo, ou seja, para pastas. Para copiar diretórios com o comando cp, tem que usar o -r também.

  • gfortran: esse é o compilador de Fortran90 (detalhes disso mais pra baixo). Ao contrário de Python, que é só escrever o código e rodar, em Fortran temos que compilar o código antes. Isso significa: escrever o código e em seguida rodar um programa (compilador) que cria um executável. Por exemplo, quero compilar o código 01.f90:

    gfortran 01.f90 -o 01
    

    Cada coisa (se chamam argumentos) depois do gfortran significa algo:

    • 01.f90: esse é o nome do código fonte a ser compilado.
    • -o 01: isso significa: "Compilador, produza como saída (o de output e não o número zero!) um arquivo binário (executável) chamado 01". Uma coisa legal é que se você não escrever esse -o 01, o gfortran vai gerar um aquivo chamado a.out. E aqui dá pra perceber uma coisa interessante: não importa a extensão do arquivo! Você até pode chamar ele de programinha.bonitinho que funciona do mesmo jeito!

    Existem alguns outros argumentos que são interessantes para usar tbm, mas são opcionais:

    • -Wall -Wextra -Wconversion-extra: isso faz com que o compilador te fale coisas que podem estar erradas no seu código!
    • -O3: isso faz com que o compilador otimize o código gerado, para ficar mais rápido. Essa é a letra O maiúscula e não o número zero! E depois da letra O tem o número 3.
  • ./01 Isso executa o programa 01 que acabamos de compilar!

Caso queira aprender mais sobre bash, aqui tem um guia muito bom! Recomendo!

VIM

VIM (Vi IMproved) é um editor de texto maravilindo! Ele funciona no terminal e faz MUITA coisa. Um modo de aprender a usar ele é pelo comando vimtutor. Basta abrir um terminal e digitar esse comando. Uma alternativa mais coloridinha é o jogo online VIM Adventures.

Instalação do gfortran

Precisamos instalar um compilador para compilar os códigos. Recomendo o gfortran, o compilador de Fortran do projeto GNU. Esse compilador é livre, gratuito e open source.

No Linux

Se você está no maravilindo mundo do Linux, instalar é super simples:

  • no Ubuntu & Debian: sudo apt-get install gfortran
  • no ArchLinux: sudo pacman -Sy gcc-fortran

No Windows

Se você gosta de sofrer, pode sofrer com Fortran no Windows também! Para instalar, adquira o instalador na seção de downloads oficiais para Windows e instale.

No MacOS

Até isso é mais simples que Windows:

brew install gcc

Básico de Fortran

Estrutura básica

Vamos fazer um programa em Fortran que escreva algum blablabla no terminal. Para isso, abra seu editor de texto favorito (gedit, vim, emacs, atom, nano qualquer um mesmo) e cole isso:

program ola
      write(*, *) "666, the number of the beast"
end program ola

Agora salve esse arquivo como oi.f90 (o nome nao importa, mas tem que terminar em .f90) e compile:

gfortran -Wall -Wextra -Wconversion-extra -O3 oi.f90 -o oi

E vamos rodar o programa:

./oi
666, the number of the beast

Legal, né?

Fortran é bem simples mas tem algumas frescuras.. Todo códifo Fortran comeca com program NomeDoPrograma e termina com end program NomeDoPrograma e tudo o que estiver entre essas linhas é o programa.

Aquela linha ali no meio, write(*, *) "666, the number of the beast" escreve aquela string, que está entre áspas. Parece python, né? Vamos ver mais para frente o que são aqueles asteríscos entre parênteses.

Variáveis e a primeira frescura do Fortran

Em Fortran, as variáveis tem tipo e isso não pode mudar. Os tipos básicos de variáveis são:

  • INTEGER Apenas para números inteiros.
  • REAL Nesse tipo, os números podem ter casas decimais.
  • COMPLEX Isso é para números complexos.
  • LOGICAL Isso é o famoso booleano, tem dois valores possíveis: verdadeiro e falso
  • CHARACTER Isso é para fazer textão.

E se você simplesmente sair usando as variáveis, o compilador vai decidir o tipo delas pelo nome da variável! Isso pode bugar seu programa e pode ficar muito difícil achar esse erro. Isso se chama nomes implícitos das variáveis (ou algo assim). e podemos desligar isso no código, e falar pro compilador o que queremos para cada variável:

program ola
        implicit none
        integer :: N
        character(30) :: textao

        N = 666
        textao = "O número da besta eh"
        write(*, *) textao, N
end program ola

Vamos compilar e executar:

gfortran -Wall -Wextra -Wconversion-extra -O3 ola.f90 -o ola
./ola
O número da besta eh         666

Aquele implicit none tira essa funcionalidade do fortran, e assim podemos declarar o tipo de nossas variáveis.

O character(30) significa que a string tem 30 caracteres. Se você salvar na variável um texto com mais de 30 caracteres, como a letra toda de uma música, só os primeiros 30 vão ficar salvos.

Para escrever várias variáveis com a funcão write, separe-as por vírgulas.

Comentários

Em Fortran, comentários são identificados pelo ponto de exclamacão ! e tudo o que vier depois dele na mesma linha é ignorado. Vamos comentar o exemplo anterior:

program ola
        ! Iron Maiden é uma banda muito foda!
        implicit none  ! nada de variáveis implícitas
        integer :: N   ! uma variável inteira chamada N
        character(30) :: textao

        N = 666
        textao = "O número da besta eh"
        write(*, *) textao, N
end program ola

Indentacão

Não precisa indentar um código Fortran. Mas ele fica mais bonitinho e facilita a leitura.

Read E Write

As funcões read e write são bem parecidas. Uma lê alguma coisa e salva numa variável e a outra escreve o conteúdo de uma variável em algum lugar. Esse lugar pode ser um arquivo ou na tela.

Por exemplo:

program oi
        implicit none
        character(80) :: banda

        write(*, *) "Que banda eh a melhor: IronMaiden ou JudasPriest?"
        read(*, *) banda
        write(*, *) "Voce respondeu: ", banda
end program oi

Se você compilar e rodar esse programa, vai aparecer no terminal o texto Que banda eh a melhor: IronMaiden ou JudasPriest? e o programa vai esperar você dar uma resposta. Depois que você responder, o programa imprime na tela a sua resposta.

if

Em alguns casos, o programa precisa tomar uma decisão baseada em algum resultado. Para isso existe a estrura de condicionais. Por exemplo:

program oi
        implicit none
        character(80) :: banda

        write(*, *) "Que banda eh a melhor: IronMaiden ou JudasPriest?"
        read(*, *) banda

        if (banda .eq. "IronMaiden") then
                write(*, *) "\m/"
        else if (banda == "JudasPriest") then
                write(*, *) "Resposta quase certa..."
        else
                write(*, *) "bye bye..."
        endif
end program oi

Agora as coisas ficam mais interessantes: dependendo da resposta que você der, o programa tem uma saída diferente.

Existem dois modos de comparar o valor das variáveis: usando os símbolos matemáticos ou escrevendo as coisas. Vejamos:

símbolo < <= == /= > >=
texto .LT. .LE. .EQ. .NE. .GT. .GE.

Obviamente nem todas essas operacões fazem sentido para texto...

Loops

Em alguns casos, queremos fazer alguma coisa várias vezes, para isso usa-se um laço (loop em inglês). Em Fortran isso é assim:

do i = inicio, fim, [incremento]
        faz algo aqui várias vezes
enddo

Por exemplo, se você quiser escrever um texto várias vezes na tela, pode usar esse código aqui:

program oi
        implicit none
        integer :: N
        integer :: i

        write(*, *) "Me diga um numero:"
        read(*, *) N

        do i = 1, N
                write(*, *) "666, the number of the beast!"
        enddo
end program oi

Ele pede um número, depois escreve na tela 666, the number of the beast! N vezes!

Se você nao especificar um valor para o incremento, o fortran assume que é 1. Mas pode ser qualquer valor. Tome muito cuidado com isso: Se você fizer um loop assim:

do i = 1, 10, -1
        write(*, *) "666, the number of the beast!"
enddo

O programa nunca vai parar! Porque a variável i vai ser cada vez menor: 1, 0, -1, -2, -3... e isso vai sempre ser menor que 10.

Mas laços também podem ser assim:

do while (condição)
        blab lab lá. ...
end do

Por exemplo:

Program uhul
        Integer :: I
        I = 13
        Do while (I < 42)
                Write(*, *) "666"
                I = I + 1
        End do
end program uhul

Um exemplo um pouco mais legal:

Vamos fazer uma calculadora que converte temperatura em graus célsius para farenheit. A fórmula para isso é:

F = (C * 9/5) + 32

Para isso precisamos ler a temperatura em Célsius, checar se o valor é válido, fazer a conta, e mostrar o resultado.

Agora vem uma coisa nova: números reais. Também chamados de ponto flutuantes. Nesse curso, vamos usar precisão dupla sempre. Para isso, a variável deve ser declarada assim:

       real(8) :: T_c, T_f

Aquele 8 alí é o número de bytes usado para salvar a informacão. Os valores válidos são 4, 8, 16; e 4 é o padrão (precisão simples).

O programa então fica assim:

program temperatura
      real(8) :: T_c, T_f

      write(*, *) "Qual a temperatura em Celsius?"
      read(*, *) T_c

      if (T_c < -273.15d0) then
            write(*, *) "Essa temperatura não existe!"
      else
            T_f = (T_c * 9.d0 / 5.d0) + 32

            write(*, *) T_c, "C = ", T_f, "F"
      endif

end program temperatura

E sempre que fizermos alguma conta com números, precisamos colocar o d0 depois, para o compilador saber que é precisão dupla.

Esse programa salva na variável T_c o valor da temperatura. Depois checa se está acima de zero absoluto, se não estiver, não converte para fahrenheit. Se for uma temperatura válida, converte e escreve na tela.

Trabalhando com arquivos.

O primeiro argumento das funcões read e write é qual arquivo é usado para ler ou escrever. Se tiver um asterísco, é o teclado ou a tela.

Para abrir um arquivo, usamos uma funcão chamada open. Ela precisa de uma variável inteira e do nome do arquivo:

program escreve
        implicit none
        integer :: numero = 10
        open(numero, file = 'NumberOfTheBeast.txt')
        write(numero, *) 666
        close(numero)
end program escreve

Vejamos linha a linha o que acontece:

  • primeiro vem o nome do programa: escreve
  • implicit none: não queremos variáveis com tipos implícitos
  • integer :: numero = 10: declaramos uma variável do tipo integer, chamada numero e com valor 10
  • open(numero, file = 'NumberOfTheBeast.txt'): abre (cria) um arquivo, chamado NumberOfTheBeast.txt, identificado por numero
  • write(numero, *) 666: escreve no arquivo identificado por numero o valor 666
  • close(numero): fecha o arquivo. Isso é importante para garantir que será salvo.
  • end program escreve: programa acaba aqui

Dica: nunca use os numeros 5 e 6 para trabalhar com arquivos, eles são numeros especiais: um é o teclado e o outro é a tela. Sempre use números maiores que 10 para evitar problemas.

Funções e Subrotinas

Também podemos fazer funções em Fortran! E também existem subrotinas! A diferença entre elas:

  • função em Fortran retorna um valor.
  • subrotina em Fortran não retorna um valor.

Mas ambas podem modificar as variáveis de entrada!!

Um exemplo de função:

function NumberOfBeast(N)
        integer :: NumberOfBeast  ! aqui definimos o tipo de retorno da funcao
        real(8) :: N  ! aqui definimos que o tipo do argumento N é um real(8)

        if (N > 0) Then
                write(*, *) "666"
        end if

        NumberOfBeast = 666 ! a funcão retorna o que estiver salvo no nome dela
        N = N + 666  ! A variável de entrada agora muda de valor!!
end function NumberOfBeast

Veja que temos que falar qual o tipo de cada argumento no corpo da funcão, assim como especificar qual o tipo de retorno. A funcão retorna o que estiver salvo numa variável com o mesmo nome dela.

Um exemplo de um código completo usando essa função acima:

program bla
        implicit none
        real(8) :: A
        A = -13

        write(*, *) NumberOfBeast(A), A


contains ! as funcoes sao definidas aqui
function NumberOfBeast(N)
        integer :: NumberOfBeast  ! aqui definimos o tipo de retorno da funcao
        real(8) :: N  ! aqui definimos que o tipo do argumento N é um real(8)

        if (N > 0) Then
                write(*, *) "666"
        end if

        NumberOfBeast = 666 ! a funcão retorna o que estiver salvo no nome dela
        N = N + 666 ! A variável de entrada agora muda de valor!!
end function NumberOfBeast
end program bla

Se executarmos esse código:

gfortran -O bla.f90 -o bla
./bla
         666   653.0000000000000

Você entende o por quê?

Agora um exemplo de subrotina, é quase igual:

program blu
        implicit none
        real(8) :: A, B
        integer :: C
        A = 13
        B = 42
        C = 137

        write(*, *) "antes da subrotina", A, B, C
        call NumberOfBeast(A, B, C) ! note a diferença de uso de função e subrotina!!!!
        write(*, *) "depois da subrotina", A, B, C


contains
subroutine NumberOfBeast(M, N, P)
        ! temos que falar que tipos de argumentos M, N, P são
        real(8) :: M, N
        integer :: P

        if (M > 0) Then
                N = 666
        else
                N = -666
        end if

        P = M + N
end subroutine NumberOfBeast
end program blu

E a saída:

gfortran -O blu.f90 -o blu
./blu
 antes da subrotina   13.000000000000000        42.000000000000000              137
 depois da subrotina   13.000000000000000        666.00000000000000              679

Se você tem uma função f(x), para usá-la basta chamar f(x) no seu código. Mas, se você tem uma subrotina s(y), para usá-la você precisa [literalmente] chamá-la: call s(y).

É possível mudar o valor dos argumentos dentro das funcões/subrotinas! Tome muito cuidado com isso! Tem como desligar isso, e isso está explicado aqui.

Vetores e Matrizes

Podemos definir vetores em Fortran da seguinte maneira:

real(8), dimension(666) :: bla

E assim temos um vetor de 666 elementos reais! Os vetores comecam por padrão na posicão um e nào na posicão zero!!!!!

Vamos criar um vetor e atribuir alguns valores:

real(8), dimension(666) :: bla
bla(1) = 42
bla(2) = 137
bla(3) = 666

E se quisermos uma matriz?

real(8), dimension(666, 42) :: blatriz
blatriz(1, 1) = 6.66d0
blatriz(2, 2) = 6.d0
blatriz(137, 42) = 642.d0

HA!

Alocação Dinâmica

Mas e se não soubermos o tamanho dos vetores e matrizes na criação do código fonte? Nesse caso temos que usar a chamada alocação dinâmica, que é o processo de criar dinamicamente um vetor.

Para isso, precisamos declarar nossa variável com um atributo de alocável e um indicando qual a dimensão dela - se é um vetor, tem 1 dimensão; se é uma matriz, tem 2. Depois precisamos dizer a dimensão. E depois limpar a variável:

program dinamico
        implicit none
        real, allocatable, dimension(:)   :: vetorzinho
        real, allocatable, dimension(:,:) :: matrizinha

        allocate(vetorzinho(42))      ! nosso vetorzinho tem 42 posições :)
        allocate(matrizinha(137,666)) ! nossa matriz tem tamanho 137x666 \o/

        ! aqui a gente faz alguma  coisa com esse vetor/matriz

        deallocate(vetorzinho, matrizinha) ! dessa linha pra baixo, **nao** podemos
                                           ! usar nem o vetorzinho nem a matrizinha
end program dinamico

Números aleatórios

Na verdade, pseudo aleatórios.

Suponha que, por algum motivo, você quer produzir uma sequência de números que parecem ser totalmente aleatórios entre zero e um: 0 ≤ x < 1.

Para fazer isso em Fortran, use a subrotina random_number(harvest). Você tem que passar para ela uma variável real. Essa subrotina vai fazer uma colheita (harvest em inglês) de um número pseudo aleatório e salvar nessa variável. Se você passar um vetor de reais, a subrotina vai preencher todas as posições com números aleatórios.

E o segredo de uma boa colheita é uma boa semente! Para ter uma boa semente, chame a subrotina random_seed() no começo do seu código.

Um exemplo de como fazer isso tudo está aqui:

program teste
        implicit none

        ! numeros reais
        real(4)  :: r4
        real(8)  :: r8
        real(16) :: r16

        ! uma matriz 4x4 de reais
        real(8) :: muitos(4,4)

        ! inicia o gerador de numerinhos
        call random_seed()

        ! todo mundo recebe um número pseudoaleatório
        call random_number(r16)
        call random_number(r8)
        call random_number(r4)
        call random_number(muitos)

        ! olha só o resultado:
        print *, r16, r8, r4
        print *, muitos(1,:)
        print *, muitos(2,:)
        print *, muitos(3,:)
        print *, muitos(4,:)
end program teste

E se compilarmos e executarmos:

$ gfortran aleat.f90 -o aleat
$ ./aleat
  0.874676781539981845053480738391677618        0.57505145031418081       0.541935444
  0.73207031044638016       0.28057229020748109        1.9748846033733503E-002  0.35994364391829448
  0.32109422766730178       0.85784435637327083        1.1146918705856490E-002  0.12327309451436308
  0.68715436105949601       0.97579903334737772       0.69876248466784485       0.78320390097181725
  0.45095051262175967       0.74165725644654046       0.86718512447000606        9.9055518599761361E-002

Se você executar esse código, provavelmente vai ter outra saída.

Gnuplot

gnuplot é um software para fazer gráficos, bem simples de usar. Tem o manual em pdf online no site dele, mas pode clicar aqui tbm para acessar a documentação.

Para usar o gnuplot, abra um terminal e execute o comando gnuplot.

Suponha que você queira fazer um gráfico com as funções:

  • e^(-x^2)
  • sin(x)
  • tanh(x)

Então abra o gnuplot e execute esse comando:

plot exp(-x**2), sin(x), tanh(x)

Pois é. Mas e se quiser graficar um arquivo de dados?

Se você tem um arquivo.txt com duas colunas de dados, sendo que cada linha é um conjunto de pontos (x, y):

plot "arquivo.txt"

Se quiser saber como definir textos de título, eixos, legenda, etc, veja a próxima seção.

Saída em PDF com várias páginas

Para fazer um gráfico em PDF, precisa mudar o padrão do gnuplot de fazer o gráfico na tela para fazer num pdf, e qual o nome do arquivo:

set term pdfcairo color enhanced
# aqui define o nome do arquivo de saída
set output "bla.pdf"

E depois disso, cada comando plot vai criar uma página nova com apenas aquele gráfico. Por exemplo:

# magia negra
set encoding utf8
set term pdfcairo enhanced color
# aqui define o nome do arquivo de saída
set output "bla.pdf"

# primeiro gráfico: título e eixos
set title "Gráfico da página 1"
set xlabel "dist (m)"
set ylabel "bla (s)"
plot sin(x)/x title "Xablaucius"

# segundo gráfico: título e eixos
set title "Gráfico da página 2"
set xlabel "bla (ble)"
set ylabel "bli (blo)"
plot atan(sin(x)**2/x) with impulses title "Nota dos hellatórios", \
     real(sin(x)**besj0(x))/(2*x) title "vontade de corrigir"

# terceirográfico: título e eixos
set title "Gráfico da página 3"
set xlabel "Curintia"
set ylabel "Sorvete de morango"
# intervalo no eixo y
set yrange [-500:15000]
# muda a legenda de lugar
set key left box
plot exp(x) title "expectativaa"

Eu ia colocar o resultado desse script aqui, mas não ficou legal mostrar nessa página. Então copia isso aí e roda vc mesmo.

Licensa

Este material está disponibilizado sob uma licensa Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment