Escolar Documentos
Profissional Documentos
Cultura Documentos
Fernando Barão
12 de Março 2022
Conteúdo
3
12 de Março 2022 Fis Computacional/IST (2021-22, P3)
ROOT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Documentação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Gráficos: representação de pontos . . . . . . . . . . . . . . . . . . . . . . . . . . 36
windows: deve abrir a aplicação Ubuntu (após a sua instalação de acordo com o descrito na
página da disciplina).
Uma vez aberto o terminal, a pasta onde se encontra deve ser a pasta de entrada do utlizador.
Admitamos nos exemplos de comandos que se seguem que o nome do utilizador é silva .
pwd
ls -l
• listar o conteúdo da pasta incluindo os ficheiros invisíveis (no linux estes possuem um nome
que se inicia com . )
ls -la
cd main
cd
cd ~silva
rm a.C
cp a.C b.C
• remover a pasta teste com todo o seu conteúdo (certifique-se que é mesmo isto que
pretende!)
rm -fr teste
ls -l /mnt/c
Notas finais:
• é conveniente que os nomes das pastas ou ficheiros criadas em Linux não contenham
espaços, ou caracteres acentuados.
Comecemos por criar um pequeno programa em C++. Para tal teremos que definir que quadro de
programação iremos usar. Há duas hipóteses possíveis:
A resposta a esta questão não será nunca única porque será sempre uma escolha muito pessoal.
Listam-se de seguida alguns editores:
• vim : editor clássico de texto em Unix e muito versátil, disponívels em todos os sistemas link
• emacs : editor muito versátil, disponível em todos os sistemas link
• xemacs : editor emacs com interface gráfica link
• sublime : disponível para todos os sistemas link
• atom : disponível para todos os sistemas link
• nano : editor simples
Usando o editor que achar mais conveniente ou IDE, realizemos o nosso primeiro programa de C++
que será implementada no ficheiro teste.C , existente na pasta main/ .
1 #include <iostream>
2 using namespace std;
3 int main() {
4 cout << "o meu primeiro programa" << endl;
5 }
A linha 1 contém a instrução #include <iostream> . Esta instrução permite incluir no có-
digo as declarações existentes no ficheiro iostream que o compilador sabe onde encontrar no
computador (por isso usámos <> ).
A linha 2 contém a instrução using namespace std; . Esta instrução permite selecionar
por defeito o domínio ( namespace ) std onde se encontram definidas as classes da bi-
blioteca STL inerente ao C++. Senão existisse esta instrução teríamos que colocar no código,
std::cout << ... << std::endl .
A linha 3 contém a instrução int main() { : um programa C++ necessita sempre de definir a
função main() , de outra forma não funcionará. Note também a existência da chaveta { que
assinala o início do bloco de código da função.
Verifique primeiramente que possui o compilador g++ correndo o seguinte comando que lhe
dará a versão do compilador que possui instalada:
g++ -v
O uso da opção -c realiza somente a compilação do código. Liste os ficheiros existentes na pasta
em que realizou o comando. Caso a compilação tenha ocorrido sem erros, verificará que existe
um ficheiro teste.o . Gostaríamos no entanto que este ficheiro binário (não legível por nós)
fosse criado na pasta bin/ . Para isso alteramos o comando anterior de forma a conter o nome
do output :
Se pretender ver o aspecto do ficheiro que é criado contendo as declarações ìostream , faça:
g++ -E main/teste.C
Para obter o programa executável ( teste.exe ) que permitirá realizar a tarefa definida no
programa, retira-se a opção -c :
Nota: no comando anterior usámos \ para indicar a continuação do comando na linha seguinte.
Isso não será necessário, se inserir o comando numa só linha no computador.
root
devendo aparecer a informação seguinte no ecran (indicando neste caso que a versão 6.26 se
encontra instalada):
O ROOT possui um interpretador de C++ podendo por isso introduzir código C++ directamente em
ROOT.
Mas a utilização de ROOT sugerida no âmbito do curso de Física Computacional, é como biblio-
teca, podendo os programas que desenvolvamos utilizar os inúmeros objectos que se encontram
definidos na biblioteca.
ROOT possui um programa utilitário que permite ligar facilmente o programa com ROOT. Trata-se
do comando root-config .
Escrevendo na linha de comandos do terminal,
root-config --cflags
root-config --libs
temos acesso à biblioteca de ROOT (que não é mais que o código compilado .o de ROOT reunido
em vários ficheiros cuja extensão é .so).
Admitamos que desenvolvemos o programa main/tvector.C onde se encontram chamadas a
funções/classes de ROOT. Para compilar e linkar o programa com a biblioteca de ROOT, faríamos:
O uso das pelicas no comando root-config ... permite que o resultado do comando apa-
reça imediatamente na linha de comandos.
As variáveis declaradas num programa são armazenadas na memória do computador durante a sua
execução. A linguagem C++ permite aceder ao endereço de memória onde a variável se encontra.
Veremos de seguida um exemplo comentado de um programa onde se se utiliza o endereço de
variáveis.
1 #include <iostream>
2 using namespace std;
3 #include <cstdio> // printf
4
5 int main() {
6
18 double* p = &a[0];
19 for (int i=0; i<10; i++, p++) {
20 printf("a[%d]=%g | %p | %g \n", i, a[i], p, *p );
21 }
22
Comentários:
• linhas 9-14:
Define-se um array a do tipo double com 10 elementos, onde se colocam os números
0, 1, 2, · · · , 9
• linhas 18-21:
Realiza-se de seguida um ciclo sobre todos os valores do array , imprimindo o seu
índice e o seu valor e ainda somando a cada um dos valores do array o valor 10. Note o
incremento do endereço realizado no ciclo e ainda o acesso ao valor da variável através da
desreferenciação do seu endereço *p .
• linhas 25-27:
Mostra-se a utilização da declaração auto que permite definir variáveis em algumas situa-
ções.
O exemplo de programa que se segue mostra como chamar uma função que calcula o factorial de
um número. Neste exemplo aproveitamos para estruturar o código C++ em três ficheiros:
1 #include <iostream>
2 using namespace std;
3
4 #include "factorial.h"
5
6 int main() {
7
1 include <iostream>
2 using namespace std;
3
23 (*result) *= i;
24 }
25 }
O exemplo de programa que se segue mostra como lêr um ficheiro de uma série temporal de dados
tempo e amplitude. As operações que iremos realizar são as seguintes:
1 #include <fstream>
2 #include <iostream>
3 #include <string>
4 using namespace std;
5
6 int main() {
7
10 string d("/mnt/c/Users/silva/DATA/");
11 ifstream F(d+"tDFT.dat");
12 cout << "file: " << d+"tDFT.dat" << endl;
13
14 /*
15 data file structure:
16 - 2 first lines with comments
17 - data after: time, amplitude
18 */
19
22 string s;
23 for (int i=0; i<2; i++) {
24 getline(F, s);
25 cout << s << endl;
26 }
27
Comentários:
• linhas 34-39:
a leitura dos dados faz-se campo a campo do ficheiro (os números no ficheiro estão separados
por um espaço e por isso são distinguíveis) para elementos do array do tipo double
Estruturas em C++
Podemos ainda incluir funções no interior da estrutura e que poderão ser usadas pelos objectos
planet .
1 #include <iostream>
2 using namespace std;
3
4 struct planet {
5 string nome; // planet name
6 double mass; // planet mass (kg)
7 double radius; // planet mean radius (m)
8 double distance; // planet distance to sun (m)
9 void print(){
10 cout << "nome=" << nome << " | mass=" << mass << endl;
11 };
12 };
No programa principal que se segue faremos uso do objecto planeta definido através da estrutura.
Este programa encontra-se localizado na pasta main/ . Não esquecer de incluir antes do porgrama
principal a declaração da estrutura. Fá-lo-emos através da inclusão do ficheiro planet.h que
contém a declaracção.
A inicialização da estrutura pode ser feita de diferentes formas como se mostra de seguida.
programa principal: rplanet.C
1 #include <iostream>
2 using namespace std;
3
4 #include "planet.h"
5
6 int main() {
7
10 planet mars;
11 mars.nome = "mars";
12 mars.mass = 6.4171E23;
13 mars.radius = 3389.5E3;
14
15 mars.print();
16
22 }
Funções lambda
As funções lambda na linguagem C++ foram introduzidas na revisão de 2011. E estão amplamente
ligadas à biblioteca STL (standard template library), porque podem ser usadas de forma fácil em
algoritmos envolvendo containers do STL.
A função lambda tem na sua definição possibilidade de capturar variáveis definidas fora dela,
fazer passar argumentos e definir o retorno da função. Regra geral não temos que definir o retorno,
porque o compilador da linguagem será capaz de inferir o tipo de objecto que está a ser retornado.
Comecemos por um exemplo muito simples que consiste em produzir uma função que imprima
uma frase.
1 #include <string>
2 #include <iostream>
3 using namespace std;
4
5 int main() {
6
16 // call function
17 f1();
18 }
1 #include <string>
2 #include <iostream>
3 using namespace std;
4
5 int main() {
6
7 /*
8 - define lambda function that searchs for a pattern in variable name
9 that is defined outside function
10 - we need to capture variables outside function (by copy)
11 */
12 std::string name="teste name então o que fazes por aqui";
13 auto f2 = [name](std::string patt) {
14 return name.find( patt ) != std::string::npos; // pos is a static
,→ var
15 };
16
Na próxima função lambda iremos calcular o comprimento de onda dada a frequência. Procedemos
à captura da velocidade da luz e passamos a frequência como argumento da função.
1 #include <string>
2 #include <iostream>
3 using namespace std;
4
5 int main() {
6
7 // physical constants
8 const double h = 6.62607015E-34; // Planck constant kg.m2.s-1
9 const double c = 299792458.; // light speed m/s
10 const double eV2J = 1.6022E-19; // 1eV in Joules
11
17 cout << "fwl(3.E11)=" << fwl(3.E11) << " nm" << endl;
18
19 }
Por último, veremos como poderíamos calcular o o comprimento de onda dada a frequência,
mas em lugar do retorno da função, capturamos a variável comprimento de onda por referência,
podendo portanto modificá-la.
1 #include <string>
2 #include <iostream>
3 using namespace std;
4
5 int main() {
6
7 // physical constants
8 const double h = 6.62607015E-34; // Planck constant kg.m2.s-1
9 const double c = 299792458.; // light speed m/s
10 const double eV2J = 1.6022E-19; // 1eV in Joules
11
vector
O vector é uma estrutura dinâmica cujo número de elementos pode variar ao longo do programa. O
tipo de elementos do vector necessita de ser definido aquando da sua criação. De seguida veremos
como:
Criação do vector
Iremos de seguida criar um vector vazio e adicionar elementos através de um ciclo. De notar que
para podermos alterar os elementos do vector teremos que aceder a estes através do mecanismo
de referência:
1 vector<int> vi {1,2,3,4,5};
2 vector<int> v2(vi);
Por último vejamos como criar um vector de objectos. Na secção anterior abordámos as estruturas
que são meios fáceis de criar objectos. De seguida criaremos um vector de objectos planet
definidos anteriormente e inicializados tal como descrito anteriormente,
1 vector<double> vi {1,2,3,4,5};
2 cout << vi.size() << endl;
1 vector<double> vi {1,2,3,4,5};
2 for (int i=0; i<int(vi.size()); i++) {
3 v[i] *= 10;
4 cout << vi[i] << endl;
5 }
Por exemplo, para se obter o iterador para o primeiro eçemento do vector e utilizando a
declaração auto ,
1 auto it = vi.begin();
1 vi.push_back(22);
2 vi.push_back(12);
1 vector<int> a {1,2,3,4,5,6,7,8,9};
2 vector<int> b {12,13,14,15};
3 a.insert(a.end(), b.begin(), b.end());
1 vector<int> a {1,2,3,4,5,6,7,8,9};
2 a.clear();
Para remover elementos do vector , podemos usar o método erase() . Após esta operação,
o vector reduz o seu tamanho.
Por exemplo, se pretendermos remover o 4º elemento do vector ,
1 vector<int> a {1,2,3,4,5,6,7,8,9};
2 a.erase(a.begin()+3);
1 vector<int> a {1,2,3,4,5,6,7,8,9};
2 a.erase(a.begin(), a.begin()+3);
1 vector<int> a {1,2,3,4,5,6,7,8,9};
2 a.pop_back();
1 vector<int> a {1,2,3,4,5,6,7,8,9};
2 a.pop_front();
Criação de matrizes
A criação de matrizes pode ser realizada com o elemento dinâmico vector . Admitamos que
queremos definir uma matriz com nr linhas (rows) and nc colunas (columns).
O exemplo seguinte mostra como criar uma matriz de números inteiros nr × nc,
Comentários:
• linha 3:
um vector com nr vectores de inteiros é alocado em memória. Os vectores de inteiros possuem
0 elementos.
• linha 5:
ciclo no vector<vector<int>> . Cada elemento v do ciclo é um vector<int> .
• linha 6:
estendemos o tamanho do vector v para que este possua um número de elementos nc
Uma outra forma alternativa e mais compacta de criar a matriz seria a seguinte,
Podemos ainda definir a matriz inicializando directamente os seus elementos. No exemplo seguinte
procedemos à criação de uma matriz de números inteiros de 3 × 4,
1 vector<vector<int> > M = {
2 {1,2,3},
3 {10,11,12},
4 {20,21,22},
5 {30,31,32}
6 };
Os elementos da matriz podem ser acedidos com o operador [] . Por exemplo para termos acesso
ao elemnto [1][2] (1a linha, 2a coluna),
z = a + bi = r eiθ
onde:
√
• r = a2 + b2
• θ = atan(b/a)
Na biblioteca STL da linguagem C++ existe a classe complex que permite operações com este
tipo de números.
No exemplo seguinte procedemos a operações sobre números complexos.
1 #include <complex>
2 #include <iostream>
3 using namespace std;
4
5 int main() {
6
10 // add numbers
11 auto z3 = z1 + z2;
12 cout << z3 << endl;
13
14 // get conjugate
15 complex<double> z3_conjugate = conj(z3);
16
17 // get norm
18 double z3_norm = norm(z3);
19
24 // exponential
25 complex<double> exponential =exp(z3);
26 cout<<"exponential of z3 = "<<exponential<<'\n';
27
34 }
A partir da revisão de 2014 do C++ (c++14) é possível escrever simbolicamente um número com-
plexo,
1 #include <complex>
2 #include <iostream>
3 using namespace std;
4 using namespace std::literals;
5
6 int main() {
7 std::complex<double> z = 1.5 + 4.2i;
8 }
map
1 #include <map>
2 using namespace std;
Criação de um mapa
Criemos de seguida um mapa com uma key do tipo inteiro e um value do tipo double ,
Para acedermos aos elementos do mapa, há várias formas que mostram de seguida no exemplo.
1 // create map
2 map<int,double> mapa = { {1,2.345}, {10, 1.23}, {2, 0.23445} };
3
1 // create map
2 map<int,double> mapa = { {1,2.345}, {10, 1.23}, {2, 0.23445} };
3
1 // create map
2 map<int,double> mapa = { {1,2.345}, {10, 1.23}, {2, 0.23445} };
3
Funções várias
c++20
A partir da revisão de 2020 do C++ (c++20) há a possibilidade de fazer o merge de dois mapas e de
testar se uma determinada key existe.
Imprimir um vector
8 // print vector
9 cout << "vector vr: " <<;
10 std::for_each(vr.begin(), vr.end(), [](double x){ cout << x << " "; });
11 cout << endl;
Copiar um vector
A cópia de um vector para outro, impôe que criemos primeiramente o vector destino com o mesmo
número elementos que pretendemos copiar. No exemplo que se segue, procedemos à cópia integral
do vector de números aleatórios num outro,
2 vector<double> vc(vr.size());
3 std::copy(vr.begin(), vr.end(), vc.begin());
Podemos também realizar uma cópia condicional. Usaremos o método std::copy_if que faz
uso de uma função predicado cujo retorno é um booleano. No exemplo que se segue faremos uma
cópia dos elementos superiores a 0.5,
2 vector<double> vc(vr.size());
3 std::copy_if(vr.begin(), vr.end(), vc.begin() [](double x){return x>0.5;
,→ });
Ordenar um vector
2 std::sort(vr.begin(), vr.end());
ou descendente,
Para obtermos a soma dos elementos de um vector, usarmeos uma variável externa sum que
acumulará a soma dos elementos.
2 double sum=0;
3 for_each(vr.begin(), vr.end(), [&sum](double x){ sum += x; });
4 cout << "sum=" << sum << endl;
Transformar um vector
De seguida modificaremos os elementos de um vector de acordo com uma função (neste caso o
logaritmo) e colocaremos noutro vector,
2 vector<double> vt(vr.size());
3 std::transform(vr.begin(), vr.end(), vt.begin(), [](double x) {return
,→ log(x);});
O método transform pode também ser usado para fazer a soma de dois vectores. Neste
exemplo faremos a som de dois vectores de números aleatórios.
Estruturas
Tal como vimos anteriormente, usámos a declaração struct para realizar uma estrutura de
elementos de diferentes tipos que contenha as características do objecto planeta. Uma particulari-
dade da estrutura é que todos os seus elementos são public , ou seja, podem ser acedidos do
exterior; dizendo acedidos, significa que podem ser modificados!
Por exemplo, para modificarmos a massa do pleneta faríamos,
2 // create planet
3 planet mars = {"mars", 6.4171E23, 3389.5E3};
4
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 struct planet {
6 string nome; // planet name
11 // -------------------------------------------- functions
12
13 // constructor
14 planet() : nome(""), mass(0.), radius(0) {;}
15 planet(string s, double m, double r) : nome(s), mass(m), radius(r) {;}
16
17 // print
18 void print() {
19 cout << "nome=" << nome << " | mass=" << mass << endl;
20 };
21 };
1 #include "planet.h"
2 int main() {
3
10 }
ROOT
Documentação
ROOT, é uma biblioteca de C++ gratuita desenvolvida pelo Laboratório Europeu de Física de Partí-
culas (CERN). Possui imensos recursos disponíveis como por exemplo classes para representação
gráfica e tratamento de dados. Para mais detalhes sobre ROOT consulte o seu site.
No quadro do curso de Física Computacional iremos usar ROOT para fazer representação gráfica
de dados e funções. Existe um avasta documentação sobre ROOT que pode ser encontrada no
endereço . Em particular, podem ser encontrados muitos exemplos de utilização dos recursos de
ROOT sob a forma de tutoriais que podem ser encontrados no endereço .x
Vamos de seguida usar o objecto TGraph de ROOT para fazermos a representação gráfica de
pontos. No exemplo que se segue realizamos as seguintes ações:
• geram-se 20 números aleatórios entre 0 e 1
• abre-se uma tela ( TCanvas )
• constrói-se um objecto TGraph e adicionam-se de seguida os 20 pontos
• realiza-se um primeiro gráfico com símbolos quadrados (símbolo 25) nos pontos e estes
ligados por linhas rectas
• salva-se o gráfico num ficheiro .png
• um duplo click na janela gráfica fará avançar o programa ( WaitPrimitive )
• a chamada de gSystem->ProcessEvents() activa o display do gráfico
• segue-se um segundo gráfico
2 #include <vector>
3 #include <iostream>
4 #include <algorithm>
5 using namespace std;
6
9 #include "printv.h"
10
11 // root includes
12
13 #include "TCanvas.h"
14 #include "TApplication.h"
15 #include "TGraph.h"
16 #include "TSystem.h"
17
18 int main() {
19
24 vector<double> vr(100);
25 for (auto& e: vr) {
26 e = rand()/(double)RAND_MAX;
27 }
28 printv(vr,"vr");
29
32 auto v2=vr;
33 double sum=0.;
34 for_each(vr.begin(), vr.end(),[&sum](double x){ sum +=x; });
35 double mean = sum/vr.size();
36 transform(v2.begin(), v2.end(), v2.begin(), [mean](double x){return
,→ x-mean;});
37
38 ///////////////////// drawing
39
40 // draw
41 // - TCanvas
42 // - TGraph
43 // - TApplication
44
45
46 TApplication A("A",nullptr,nullptr);
47 TCanvas canvas("canvas", "O meu primeiro canvas", 0, 0, 1200, 800);
48
49 // 1st graph
50
51 TGraph g;
52 int i=0;
53 for (auto e: vr) {
54 g.AddPoint(i,e);
55 i++;
56 }
57 g.SetMarkerStyle(90);
58 g.SetMarkerSize(1.5);
59 g.SetMarkerColor(kRed+2);
60 g.SetLineColor(kBlue+1);
61 g.SetLineWidth(4);
62 g.Draw("APL"); // axis drawn
63 g.SetTitle("random numbers; index; amplitude");
64 canvas.Update();
65 canvas.SaveAs("FIG_random.png");
66 canvas.WaitPrimitive();
67 gSystem->ProcessEvents();
68
69 // 2nd graph
70
71 TGraph g2;
72 i=0;
73 for (auto e: v2) {
74 g2.AddPoint(i,e);
75 i++;
76 }
77 g2.SetMarkerStyle(90);
78 g2.SetMarkerSize(1.5);
79 g2.SetMarkerColor(kRed+2);
80 g2.SetLineColor(kBlue+1);
81 g2.SetLineWidth(4);
82 g2.Draw("APL"); // axis drawn
83 g2.SetTitle("random numbers-mean; index; amplitude");
84 canvas.Update();
85 canvas.SaveAs("FIG_random-mean.png");
86 canvas.WaitPrimitive();
87 gSystem->ProcessEvents();
88
89 }