segunda-feira, 2 de novembro de 2015

Ramdom Walks (Caminhadas Aleatórias em Grafos)

Vamos começar os nossos tutoriais em grafos utilizando python.
É recomendado a leitura dos principais conceitos básicos de teoria dos grafos disponível no link:
Introdução a conceitos básicos

Para realizar esse tutorial é necessário ter instalado os seguintes softwares:

  • WinPython (WINDOWS)
Disponível em: http://winpython.github.io/


  • Anaconda (WINDOWS/LINUX/MAC OS)
Disponível em: https://www.continuum.io/downloads

Em ambos, o sofware Spyder estará incluso na instalação.

Caminhadas aleatórias


Dado um grafo G, uma caminhada aleatória é um dos modos em que se pode percorrer um grafo, ou seja, partindo de um vértice qualquer, escolhe-se um vértice adjacente para ir em seguida. É necessário saber quantas vezes um vértice v foi visitado para que possamos ter uma noção de quais vértices foram mais visitados que os outros. Podemos implementar isso com um vetor, onde cada posição representa um vértice do grafo e o conteúdo em cada posição representa o número de vezes que o vértice foi visitado. Esse vetor é chamado de esquema de distribuição estacionária;
Podemos estabelecer 3 formas de calcular a distribuição estacionária:

- Teórica


A distribuição estacionária teórica é dada pela fórmula w(i)= d(vi) / 2*|E|, onde w(i)  é a distribuição estacionária relativa á vi, d(vi) é o grau do vértice vi e |E| é a quantidade total de arestas do grafo.

               W = [ d(v1)/2*|E| , d(v2)/2*|E|  , d(v3)/2*|E| , … , d(vn)/2*|E| ]
O código em python é dado abaixo:

Para manipularmos grafos é necessário utilizar a biblioteca neworkx. Essa biblioteca já está inclusa no Spyder.
Fazemos uma referência a biblioteca networkx como nx.
O gráfico escolhido para analisar é o grafo football.gml disponível em [ Grafo Football ]
Para a leitura do grafo é necessário que ele esteja salvo na mesma pasta do projeto criado.

G= nx.read_gml("nome do grafo")
Lê o grafo e atribui a variável G

Criamos uma classe chamada caminhadas aleatórias. Vamos definir todas as formas de calcular a distribuição estacionária como métodos dessa classe:
Em seguida, definimos o método distribuicaoEstacionariaTeorica que recebe como parâmetro um grafo G.

A variável w é um vetor que armazenará a distribuição estacionária de cada nó.
D recebe um dicionário dos graus dos vértices do grafo G através da função nx.degree(G).
N recebe o número de nós disponível no grafo G através da função G.number_of_nodes()
E recebe o número de arestas através da função G.number_of_edges().

Logo após percorremos todos os vértices armazenando a distribuição estacionária de cada um em w

*obs: multiplicamos o dicionário de graus D por 1.0 para que a multiplicação não arredonde.

O resultado obtido para o grafo football.gml é o seguinte:


*Observação: A leitura  da distribuição estacionária de cada vértice é feita por linhas.

- Power Method

O Power Method é uma maneira de calcular a distribuição estacionária de um vértice através da sua matriz de adjacência (explicada no post de introdução a teoria dos grafos).
Então, dado um W0 qualquer onde a soma dos valores do vetor seja 1, por exemplo W0[1/n,1/n,...,1/n], onde n é igual ao número de vértices do grafo e |W0| = |V|, devemos multiplicar W0 por uma matriz P(matriz de probabilidade de visita de um vértice) onde k é um número inteiro e P é definido como:
P=D-1*A,
onde D é uma matriz que tem 1/d(vi) para os elementos da diagonal principal e 0 para os demais
A é a matriz de adjacência do grafo G.

Primeiramente, vamos aplicar o power method com k=5, depois com k=100.
Veremos que quanto maior o K aplicado, mais próximo da distribuição estacionária teórica o resultado estará.





Definimos o método powerMethod (l.31) que recebe como parâmetro um grafo G e um inteiro k.
A seguir (l.32,33,34) declaramos as principais variáveis utilizadas:
W0 = armazenará um vetor com a função escolhida em cada índice (nesse caso, escolhemos W0 como sendo [1/|v|,1/|v|...1/|v|] onde |v| o número de vértices do grafo G.(linha.32)
V= armazena o número de vértices que o grafo G tem através da função G.number_of_nodes() (linha 33)
D= armazena um dicionário contendo o grau de cada vértice de G através da função nx.degree(G) (linha 34)

Na linha 37, definimos Delta, utilizando uma função da bibioteca numpy(np), np.zeros() que define uma matriz preenchida por zeros do tamanho VxV (lembrando que V é o número de vértices de G)
Na linha 39,40 colocamos em cada índice do vetor W0, 1/V.
Na linha 42, a variável A é definida como a matriz de adjacência de G utilizando a função contida na biblioteca networkx (adjacency_matrix()).
Na linha 44, j controlará quando a linha e coluna da matriz Delta forem iguais, ou seja, só vamos alterar a diagonal principal da matriz Delta como explicado acima. 
A diagonal principal da matriz Delta será dada pela função 1/d(vi), ou seja, 1/grau do vértice i. Já temos um dicionário D que armazena em cada índice i o grau do vértice i.
A matriz P será A*Delta (linha 49)
O loop definido nas linhas 51 e 52 eleva P à k.
Obs: a função dot faz a multiplicação de duas matrizes e faz parte da biblioteca numpy
Por fim, multiplicamos P e W0 para determinarmos a função estácionária de cada vértice utilizando o power method.

Os resultados foram os seguintes:

Para k=5
Para k=100

Conclusão:

Podemos observar que quanto maior o k fornecido, mais próximo da distribuição estacionária teórica o power method chegará.


Distribuição Estacionária pelo "Andar do bêbado" (Caminhada Aleatória)

De fato, a forma de calcular essa distribuição estacionária é a caminhada aleatória, pois seus dados são gerados aleatoriamente, diferente das outros métodos mostrados acima.
A ideia geral é partindo de um vértice inicial a caminhada é feita escolhendo um vértice aleatório para prosseguir. Para que possamos ter uma noção da probabilidade de um vértice x ter sido escolhido durante a caminhada podemos definir uma variável de contagem de quantas vezes o vértice x foi escolhido dividido pelo tamanho da caminhada, isto é, a quantidade de nós visitados.

P(vi) = vezes que v foi visitado / quantidade de vértices visitados
Para que a escolha do vértice seja aleatório, iremos utilizar a função random()



O método andarBebado recebe como parâmetro um grafo G, uma raiz que é o no que começaremos  caminhada e um tamanho de caminhada, que é quantos nós iremos visitar.
A variável nó recebe a raiz fornecida para que não perdemos a referência
O vetor visitas é inicialmente iniciado com 0 (linha 63)
A linha 66 define um loop que elaborará uma caminhada de tamanhoCaminhada fazendo:
1. Pegando todos os vizinhos do nó atual (linha 67)
2. Incrementando as visitas do nó atual (linha 68)
3. Escolhendo um nó aleatório para prosseguir (linha 69)

Logo, definiremos um vetor w que de fato armazenará a definição estacionaria de cada vértice.
Na linha 74 é feito um loop que percorrerá o vetor pegando o valor de visitas para cada vértice, dividindo pelo tamanho da caminhada e armazenando em w.

Foram realizadas as seguintes consultas:

1. Dois nós distintos com tamanho de caminhada = 100

2. Dois nós distintos com tamanho de caminhada = 10000


Conclusão:


Na caminhada aleatória com tamanho da caminhada = 100 podemos observar que as duas execuções com os vértices 5 e 25 produzem resultados diferentes em vários pontos. Isso acontece porque a caminhada tem o valor muito pequeno, o que faz com que os vértices mais distantes não cheguem a ser alcançados.
Na caminhada aleatória com tamanho da caminhada=10000 podemos perceber que os valores se aproximam mais da distribuição estacionária teórica. Como conclusão, quanto maior o tamanho da caminhada, mais próximo a distribuição estacionária probabilística estará da distribuição estacionária teórica.















Introdução a Teoria dos Grafos

Para começar, vamos dar um breve introdução à teoria dos grafos, explanando os principais conceitos para que se possa entender os próximos tutoriais.

1- O que é um Grafo?
A teoria dos grafos busca estudar as relações entre objetos de um conjunto. Um grafo é formalmente definido como:
G=(V,E) onde,
G é o Grafo que é formado por um conjunto não-vazio de vértices(V) e arestas (E), um conjunto não ordenado de pares de V.
As arestas conectam dois vétices e podem ou não ter direção e pesos associados. Se as arestas são direcionadas, o grafo formado por elas é denominado um grafo direcionado, orientado ou dígrafo.
Um grafo dito trivial é um grafo com um único vértice.

Em resumo:

Um grafo pode ser representado por G=(V,E)
V = conjunto não vazio de arestas
E= conjunto não ordenado de pares de V chamado arestas. E é um par formado por {vértice, vértice destino ligado por uma aresta}
Na representação gráfica V são os nós (a,b,c,d,e) e E são retas que interligam 2 nós.

Nesse exemplo temos G=(V,E), onde V={a,b,c,d,e} e E={{a,b},{b,c},{b,d},{c,d},{d,e}}

Quando dois vértices são ligados por uma aresta e ,esses vértices são ditos incidentes a aresta e.
O grau (d) de um vértice é o número de arestas incidentes à ele.
No exemplo:
d(a)=1
d(b)=3
d(c)=2
d(d)=3
d(e)=1

Se V(conjunto de vértices é finito, então d(V)=2*E. Em outras palavras, se temos um conjunto finito de arestas, o grau total dos vértices de um grafo G é dado pelo dobro do número de arestas.

Dois vértices são adjacentes se existe uma aresta entre eles. No exemplo: (a,b) são adjacentes assim como, (b,d),(c,d),(b,c),(d,e).

Podemos usar uma matriz de adjacência para representar um grafo direcionado finito ou não direcionado finito: Dado um grafo com n vértices, a matriz de adjacência terá o tamanho nXn cujo o valor na linha i e coluna j fornece o número de arestas do i-ésimo ao j-ésimo vértices.
No exemplo acima, temos o grafo com a sua respectiva matriz de adjacência. As linhas e colunas representam os vértices do grafo. Quando temos um aresta ligando dois vértices, as células correspondentes recebem o valor 1. Por exemplo linha A coluna C vale 1, pois há uma aresta ligando esses dois vértices. Já linha A coluna D é igual a 0 pois não existe uma aresta que ligue os vértices A e D.

Um grafo conexo é um grafo que se pode estabelecer um caminho  de qualquer vértice para qualquer outro vértice.

Um grafo k-conexo é aquele que mesmo após remover k-1 vértices  ainda é possível estabelecer um caminho entre vértices.


Fonte: Imagens:
http://wikiciencias.casadasciencias.org/wiki/images/c/cb/Matrizadjacencias.png
http://www.gpec.ucdb.br/pistori/disciplinas/discreta/aulas/figuras/fig1.gif

Texto:
https://pt.wikipedia.org/wiki/Teoria_dos_grafos





Boas Vindas ao Blog Teoria dos Grafos UFSCar DC-2015

Introdução

Olá,

Esse blog tem como principal função divulgar os trabalhos da disciplina Teoria dos Grafos oferecida pelo Departamento de Computação (DC) da Universidade Federal de São Carlos (UFSCar - São Carlos) ministrada pelo Professor Alexandre Luís Magalhães Levada.
Para os tutoriais disponibilizados, usaremos os softwares:

WinPython(WINDOWS)


Disponível em: http://winpython.github.io/
Esse Software é um pacote de softwares para desenvolvimento em Python. Dentro desses, usaremos o Spyder que é usado para programação científica em Python.
WinPython é uma versão para SO Windows.

Anaconda(WINDOWS/LINUX/MAC OS)

Disponível em: https://www.continuum.io/downloads
Também contém o software Spyder.