universidade estadual de campinas …lotavio/tgs/2012_estudo do método...fluidos, na qual o custo...
TRANSCRIPT
UNIVERSIDADE ESTADUAL DE CAMPINAS
FACULDADE DE ENGENHARIA MECÂNICA
Relatório Final
Trabalho Final de Graduação II
Estudo do Método Lattice Boltzmann aplicado à
Fluidodinâmica: Uso de Simulador em GPU e
Desenvolvimento de um Simulador em Linguagem C
Autor: Fabíola Martins Campos de Oliveira
Orientador: Prof. Dr. Luiz Otávio S. Ferreira
Campinas, novembro de 2012.
UNIVERSIDADE ESTADUAL DE CAMPINAS
FACULDADE DE ENGENHARIA MECÂNICA
Relatório Final
Trabalho Final de Graduação II
Estudo do Método Lattice Boltzmann aplicado à
Fluidodinâmica: Uso de Simulador em GPU e
Desenvolvimento de um Simulador em Linguagem C
Autor: Fabíola Martins Campos de Oliveira
Orientador: Prof. Dr. Luiz Otávio Saraiva Ferreira
Curso: Engenharia de Controle e Automação
Trabalho Final de Graduação apresentado à Comissão de Graduação da Faculdade
de Engenharia Mecânica, como requisito para a obtenção do título de Engenheiro de
Automação e Controle.
Campinas, 2012
S.P. – Brasil
i
Dedico este trabalho à minha família e ao meu namorado pelo apoio e incentivo.
ii
Agradecimentos
Este trabalho não poderia ser realizado sem a ajuda de diversas pessoas às quais
presto minha homenagem:
Ao Prof. Dr. Luiz Otávio Saraiva Ferreira, pela orientação e valiosos conhecimentos
passados.
À minha família, que sempre me apoiou e também é responsável por minhas
realizações.
Ao meu namorado, Helói Genari, que sempre me ajudou e incentivou.
A todos os professores que contribuíram para minha formação.
Aos funcionários e colegas.
À Unicamp pela infraestrutura disponibilizada.
À FAPESP, pelo financiamento do Laboratório de Simulações Multifísicas.
E à população brasileira pelo financiamento das instituições Unicamp e FAPESP.
iii
Resumo
O recente uso de unidades de processamento gráfico de propósito geral (GPGPUs)
tem contribuído para a aceleração de cálculos complexos, diminuindo assim o custo
computacional. Essa ferramenta vem sendo aplicada com êxito na área de mecânica dos
fluidos, na qual o custo computacional é elevado. Em vista de sua grande importância
econômica, há um grande interesse nessa área, com potencial aumento de eficiência,
dado que os algoritmos atuais experimentam bastante lentidão. Nesse contexto, este
trabalho objetiva estudar e simular o escoamento de fluidos bifásicos com o método Lattice
Boltzmann. Para isso, inicialmente se utilizou o simulador Sailfish, baseado na linguagem
CUDA C – para unidades de processamento gráfico (GPUs) NVIDIA – com scripts para a
simulação na linguagem Python. Esse simulador se baseia no método Lattice Boltzmann,
que simplifica a modelagem através da abordagem de partículas de fluido, satisfazendo as
equações de Navier-Stokes no nível macroscópico. Além de o método aumentar a
eficiência da simulação, o uso de GPUs também o faz, através da paralelização. Alguns
exemplos de autômatos celulares, que originaram o método, foram implementados em
CUDA C e os escoamentos monofásicos mais fundamentais foram simulados no Sailfish, a
fim de validar o simulador. Em seguida, realizaram-se simulações de escoamentos
bifásicos passando por dois tipos de micromisturadores: o ômega e o tesla, também no
Sailfish. O trabalho até essa etapa rendeu um artigo publicado no 14th Brazilian Congress
of Thermal Sciences and Engineering, em anexo (Saraiva Ferreira & Martins Campos de
Oliveira, 2012). Apesar de o Sailfish atender a algumas necessidades, avaliou-se a
construção de um simulador próprio, que cumprisse bem seu papel em todos os requisitos.
Dessa forma, em um primeiro passo, é desenvolvido um simulador em linguagem C,
visando à sua tradução para a linguagem CUDA C e um alto desempenho no
processamento.
Palavras-Chave: Simulação, Microfluídica, Método Lattice Boltzmann
iv
Lista de Figuras
Figura 2.1. Rede quadrada do modelo HPP. Fonte: (Wolf-Gladrow, 2000). 6
Figura 2.2. Única configuração que gera colisão e seu resultado final. Pode ser
representado por células (a) ou por vetores (b). Fonte: (Wolf-Gladrow, 2000). 6
Figura 2.3. Ângulo de contato de 90º entre fluido e superfície sólida. Fonte:
(Sukop, 2007). 11
Figura 4.1. Autômato Celular 1D, regra 2 e raio 2. 19
Figura 4.2. Autômato Celular 1D, regra 20 e raio 1. 21
Figura 4.3. Autômato Celular 1D, regra 52 e raio 1. 22
Figura 4.4. Vizinhança von Neumann raio 1. Fonte: (Wolf-Gladrow, 2000). 23
Figura 4.5. Jogo de Fredkin com a letra F como padrão a ser replicado. 24
Figura 4.6. Vizinhança de Moore de raio um. Fonte: (Wolf-Gladrow, 2000). 25
Figura 4.7. Jogo Life. 26
Figura 4.8. Escoamento de Poiseuille; x é o tamanho da rede no eixo x. Fonte:
(Sukop, 2007). 27
Figura 4.9. Escoamento de Poiseuille: resultado da simulação no Sailfish. 27
Figura 4.10. Visão simplificada do efeito do comprimento de entrada; u é a
velocidade, uavg é a velocidade média, r é a distância medida a partir do centro
do tubo e a é o raio do tubo. Fonte: (Sukop, 2007). 28
Figura 4.11. Efeito do comprimento de entrada simulado no Sailfish. 28
Figura 4.12. Escoamento laminar passando por cilindro. Fonte: (Sukop, 2007). 29
Figura 4.13. Escoamento laminar passando por cilindro – simulação em Sailfish. 29
Figura 4.14. Escoamento turbulento simulado (acima) e fotografado (abaixo).
Fonte: (Sukop, 2007). 30
Figura 4.15. Escoamento turbulento simulado no Sailfish. 30
Figura 4.16. Foto da formação de vórtices (acima) e simulação (abaixo). Fonte:
(Sukop, 2007). 31
Figura 4.17. Simulação do Sailfish; a primeira imagem mostra a magnitude da
v
velocidade e as quatro imagens abaixo representam diferentes visualizações
para a vorticidade. 31
Figura 4.18. Vetores de velocidade no escoamento. 31
Figura 4.19. Linhas de fluxo. 31
Figura 4.20. Separação da camada limite. Fonte: (Sukop, 2007). 32
Figura 4.21. Separação da camada limite no simulador. 32
Figura 4.22. Escoamento bifásico entre placas paralelas no Sailfish (a flecha
aponta o obstáculo). 33
Figura 4.24. Dimensões do microrreator Ômega. Fonte: (Arias, 2010) 34
Figura 4.25. Escoamento de etanol e óleo de mamona em microrreator Ômega;
seis instantes de tempo distintos e não adjacentes. 36
Figura 4.26. Escoamento de etanol e óleo de mamona passando por três
microrreatores Ômega em cascata; seis instantes de tempo distintos e não
adjacentes. 36
Figura 4.27. Dimensões do microrreator Tesla. Fonte: (Arias, 2010) 37
Figura 4.28. Escoamento de etanol e óleo de mamona em microrreator Tesla;
oito instantes de tempo distintos e não adjacentes. 37
Figura 4.29. Escoamento de etanol e óleo de mamona passando por três
microrreatores Tesla em cascata; quatro instantes de tempo distintos e não
adjacentes. 38
vi
Lista de Tabelas
Tabela 4.1. Tabela verdade para autômato celular 1D, regra 2 e raio 2. 17
Tabela 4.2. Tabela verdade para autômato celular 1D, regra 20 e raio 1. 20
Tabela 4.3. Tabela verdade para autômato celular 1D, regra 52 e raio 1. 22
Tabela 4.4. Parâmetros dos fluidos. 35
Tabela 4.5. Parâmetros da simulação. 35
vii
Nomenclatura
Letras Latinas
a estado da célula do autômato
c velocidade da rede
D diâmetro do obstáculo [m]
e velocidade
f função de distribuição de probabilidade
F força no fluido
G força de interação
j quantidade de movimento
k número de estados possíveis para a célula de um autômato
L altura do canal [m]
n número de ocupação
N número de ocupação média
P pressão cinemática [m²/s²]
p pressão [N/m²]
r raio da regra do autômato
R número de nós da rede
s coordenadas de cada nó
u velocidade do escoamento [m/s]
u’ velocidade macroscópica composta
w peso
x vetor posição
Letras Gregas
viscosidade cinemática [kg/ms]
α constante inteira que determina o estado atual de uma célula
de um autômato
Δ variação
θ ângulo de contato
ρ massa
viii
ρ0 densidade de massa constante [kg/m³]
σ12 tensão interfacial entre dois fluidos
σS1 tensão interfacial entre fluido 1 e superfície
σS2 tensão interfacial entre fluido 2 e superfície
τ tempo de relaxamento
ψ densidade
operador gradiente
viscosidade de cisalhamento cinemática [m²/s]
Superescritos
(1) partícula única
A fluido 1
B fluido 2
eq equilíbrio
t tempo atual
t-1 tempo anterior
Subscritos
a direção na rede
A fluido 1
avg média
B fluido 2
fís física
i posição da célula no autômato
i+r célula à direita por r posições da posição i
i-r célula à esquerda por r posições da posição i
i-r+1 célula à esquerda por r posições da posição i+1
j incremento de uma célula
k direção do vetor da rede
lb lattice-Boltzmann
máx máxima
σ fluido 1
fluido 2
ix
Abreviações
lt lattice time
lu lattice unit
Re Número de Reynolds
Siglas
CFD Fluidodinâmica computacional
CPU Unidade de processamento central
CUDA Arquitetura de dispositivo unificado para cálculos
FHP Frisch, Hasslacher e Pomeau
GB Gigabyte
GPGPU Unidade de processamento gráfico de propósito geral
GPU Unidade de processamento gráfico
HPP Hardy, de Pazzis e Pomeau
LB Método de Boltzmann em Rede
LGCA Autômatos celulares em rede de gases
RAM Memória de acesso aleatório
x
Índice
1 Introdução .................................................................................................................. 1
2 Revisão Bibliográfica .................................................................................................. 3
2.1 A Equação de Navier-Stokes ...................................................................................... 3
2.2 Autômatos Celulares ................................................................................................... 4
2.3 Lattice-Gas Cellular Automata (LGCA) ....................................................................... 5
2.4 O Método Lattice Boltzmann (LB) ............................................................................... 8
2.4.1 O Método Lattice Boltzmann em escoamentos multicomponentes multifásicos .....10
2.4.2 Parâmetros da simulação .......................................................................................12
3 Procedimento Experimental ......................................................................................14
3.1 Autômatos celulares em linguagem CUDA C ............................................................14
3.2 Escoamentos no programa Sailfish............................................................................14
3.3 Implementação de simulador em linguagem C ..........................................................15
4 Resultados e Discussões ..........................................................................................16
4.1 Autômatos Celulares em linguagem CUDA C ............................................................16
4.1.1 Autômato Celular 1D, regra 2 e raio 2 .....................................................................16
4.1.2 Autômato Celular 1D, regra 20 e raio 1 ...................................................................19
4.1.3 Autômato Celular 1D, regra 52 e raio 1 ...................................................................21
4.1.4 Jogo de Fredkin ......................................................................................................23
4.1.5 Jogo Life .................................................................................................................24
4.2 Aplicação do método Lattice Boltzmann em escoamentos monofásicos no Sailfish .26
4.2.1 Escoamento de Poiseuille .......................................................................................27
4.2.2 Efeito do Comprimento de Entrada .........................................................................28
4.2.3 Escoamento Passando por Cilindro ........................................................................29
4.2.4 Escoamento Instável em Altos Números de Reynolds ............................................30
4.2.5 Separação da Camada Limite .................................................................................32
xi
4.3 Aplicação do método Lattice Boltzmann em escoamentos bifásicos no Sailfish ........32
4.3.1 Escoamento bifásico entre placas paralelas ...........................................................33
4.3.2 Escoamento de álcool e óleo em microrreator Ômega ...........................................34
4.3.3 Escoamento de álcool e óleo em microrreator Tesla ..............................................37
4.4 Implementação de Simulador em Linguagem C ........................................................38
5 Conclusões e Perspectivas .......................................................................................44
5.1 Perspectivas ..............................................................................................................44
Referências Bibliográficas 46
Anexos .............................................................................................................................47
1) Artigo publicado no 14th Brazilian Congress of Thermal Sciences and Engineering 47
2) Código em linguagem C de simulador multicomponente ..........................................54
1
1 Introdução
O advento da computação trouxe consigo uma grande inovação tecnológica no
setor de pesquisa e desenvolvimento acadêmico e industrial: a possibilidade de
simular problemas físicos, cortando assim custos com materiais, construção de
protótipos e/ou modelos, além de a redução do tempo necessário para se atingir um
mesmo resultado. Além disso, com este aumento na velocidade de processamento
de dados, pode-se atingir uma maior produção de conhecimento, recentemente
potencializada com o surgimento do primeiro processador gráfico (GPU) de
propósito geral (GPGPU), da NVIDIA.
A simulação de escoamento de fluidos em geral é muito importante, dado que
grande parte do conhecimento nessa área é empírica (Fox, 2006). Um tópico de
grande interesse se mostra na mistura de fluidos em microrreatores para produção
de biodiesel. Há diversas abordagens para simular esse problema, dentre os
métodos conhecidos como CFD (Computational Fluid Dynamics) tradicionais,
encontram-se: métodos das diferenças finitas, dos elementos finitos, dos elementos
de contorno, do volume finito e o método do elemento espectral (Nourgaliev, 2003).
O método Lattice Boltzmann (LB), ou método de Boltzmann em Rede, é
baseado em um algoritmo CFD não tradicional e vem ganhando grande destaque na
comunidade científica nos últimos anos ( (Nourgaliev, 2003), (Chen, 1998) e (Zhao,
2008)). Esse método é derivado do método Lattice-Gas Cellular Automata (LGCA),
que por sua vez deriva da teoria dos autômatos celulares. Os autômatos celulares
são arranjos periódicos de células individuais, podendo ser de uma ou de duas
dimensões (Wolf-Gladrow, 2000). O LGCA mais simples é o HPP, cujo nome é dado
pelas iniciais de seus criadores, Hardy, de Pazzis e Pomeau, e surgiu em 1973. Sua
importância é histórica, dado que esse autômato não obedece à equação de Navier-
Stokes no limite macroscópico. Em 1986, Frisch, Hasslacher e Pomeau propuseram
um jogo com bolas que colidiam e conservavam massa e quantidade de movimento
– o LGCA FHP. Esse jogo, no limite macroscópico, levou à equação de Navier-
Stokes, quando aplicado sob simetrias apropriadas da rede (Wolf-Gladrow, 2000).
A ideia básica por trás desses métodos é que interações microscópicas
podem ter o mesmo resultado de equações macroscópicas e, assim, podem-se
simular corretamente os escoamentos. O computador realiza cálculos baseados nas
2
interações microscópicas, que devem ser simples para garantir uma boa eficiência
da simulação. Essas interações devem conservar massa e quantidade de
movimento localmente, envolvendo partículas de um único nó, e podem ser dividas
em duas etapas: colisão e propagação. A colisão realiza a troca de quantidade de
movimento, enquanto a propagação realiza o movimento propriamente dito, de
acordo com o sentido associado a essa partícula.
O método LB possui duas grandes características: simplificação da modelagem
e boa representação do comportamento de fluidos. Como um gás ou um líquido é
composto por partículas que interagem e podem ser descritas pela mecânica
clássica, e há um número muito grande dessas partículas, necessitam-se tratar os
problemas envolvendo fluidos como problemas estatísticos (Sukop, 2007). Essa é a
grande diferença entre o LGCA e o LB: o método Lattice Boltzmann lida com
funções de distribuição contínuas ao invés de lidar com partículas.
Inicialmente, esse trabalho aborda as questões relativas ao método LB, para,
em seguida, realizar simulações da mistura álcool-óleo em dois micromisturadores
em um simulador que utiliza o método LB: o Sailfish (Saraiva Ferreira & Martins
Campos de Oliveira, 2012). O Sailfish é um simulador de código aberto otimizado
para sistemas modernos de vários núcleos, principalmente GPUs, e foi
implementado em linguagem Python e CUDA C (Januszewski), sendo esta a
linguagem de programação utilizada pela NVIDIA em suas GPUs (Randima, 2004).
Como exemplo, pode-se comparar o tempo de simulação em microprocessadores
comuns (CPUs – Central Processing Units), da ordem de horas, com o tempo de
simulação deste trabalho, praticamente em tempo real (Zhao, 2008). O trabalho
envolvendo simulações no software Sailfish foi publicado no 14th Brazilian Congress
of Thermal Sciences and Engineering, anexado a este trabalho (Saraiva Ferreira &
Martins Campos de Oliveira, 2012).
Após verificar que o Sailfish não atenderia a todos os requisitos de simulação,
considerou-se como segunda parte do trabalho a construção de um simulador de
fluidos em linguagem C, baseado em outro muito simples que implementa apenas
um fluido, porém com funcionalidades escassas. Ainda, a implementação em
linguagem C visa a ser apenas uma etapa do processo para construção de um
simulador em linguagem CUDA C, cuja execução terá um desempenho muito maior.
3
2 Revisão Bibliográfica
Para o desenvolvimento deste trabalho, foi realizado um estudo sobre o
método Lattice Boltzmann e as técnicas que deram origem a ele. Inicialmente, é
apresentada a equação de Navier-Stokes, em seguida é discutida a teoria de
autômatos celulares e LGCAs e, finalmente, é apresentado o método Lattice
Boltzmann e suas peculiaridades.
2.1 A Equação de Navier-Stokes
A equação de Navier-Stokes descreve o escoamento de fluidos
incompressíveis e pode ser escrita como:
( )
Equação 2.1
junto com a equação de continuidade:
Equação 2.2
em que é o operador gradiente, u é a velocidade do escoamento, P = p/ρ0 é a
pressão cinemática, p é a pressão, ρ0, a densidade de massa constante e é a
viscosidade de cisalhamento cinemática, ou viscosidade cinemática (Wolf-Gladrow,
2000). A Equação 2.1 é não linear em u, o que requer métodos numéricos para
simular escoamentos de fluidos. Deve-se ressaltar que os métodos citados aqui têm
uma abordagem de baixo para cima, ou seja, através de interações microscópicas
chega-se ao mesmo resultado das equações de Navier-Stokes, em contraposição a
uma abordagem de cima para baixo, em que as equações de Navier-Stokes são
resolvidas analiticamente no método. Essa característica simplifica
consideravelmente o problema, já que a solução de equações não lineares aumenta
muito o custo computacional.
4
Para que seja possível simular corretamente escoamentos incompressíveis
com os métodos mencionados anteriormente, deve haver similaridade dinâmica das
simulações com os escoamentos reais. O número de Reynolds, descrito por:
Equação 2.3
é útil na explicação da similaridade dinâmica, que significa que escoamentos com
mesmo número de Reynolds, mas com diferentes velocidades de escoamento u,
diâmetros do obstáculo D e viscosidades cinemáticas , possuem a mesma solução
e, assim, um mesmo escoamento pode ser estudado em escala diferente, como
acontece quando se realiza uma simulação ou se constrói um modelo.
2.2 Autômatos Celulares
Autômatos Celulares são arranjos regulares de células individuais do mesmo
tipo. Cada célula possui um número finito de estados discretos, e esses estados são
atualizados simultaneamente a cada passo de tempo. As regras para essa
atualização dependem somente dos estados da célula e da vizinhança local de
células ao redor dela (Wolf-Gladrow, 2000).
O autômato celular de uma dimensão consiste em células que podem estar
ocupadas ou vazias ao longo de uma linha. É circular, ou seja, após a última célula
da linha segue-se a primeira célula dessa mesma linha. O estado atual de uma
célula pode ser determinado através da equação:
( )
( )
( )
( )
( )
Equação 2.4
com sendo o estado da célula no tempo , e como função dos estados anteriores
( ) das células num raio de a , de forma alternativa:
( )
[∑ ( )
]
Equação 2.5
5
tendo como argumento um único inteiro, e j sendo constantes inteiras. Um
autômato desse tipo pode ter até ( ) combinações diferentes de regras de
atualização – com k sendo o número de estados possíveis e r o raio de alcance dos
vizinhos. Esse autômato é denominado autômato celular elementar. As regras de
atualização para um autômato podem ser divididas em regras aditivas, totalísticas,
simétricas, com memória e legais.
Autômatos celulares de duas dimensões possuem mais liberdade para arranjar
as células, definir seus vizinhos e regras de atualização. Como exemplos de
vizinhos, tem-se a vizinhança von Neumann e a vizinhança de Moore, que serão
mostradas mais adiante. Dois jogos importantes também são autômatos de duas
dimensões: o jogo de Fredkin, que leva à autorreplicação da imagem inicial, e o jogo
Life, que simula a evolução de uma sociedade de organismos vivos (mais detalhes
em (Wolf-Gladrow, 2000)).
Em suma, autômatos celulares podem simular comportamentos complexos,
como escoamento de fluidos, com regras de atualização simples, as quais são fáceis
de implementar e se adaptam bem à computação paralela massiva. Porém, há
certas características que devem ser obedecidas antes de simular satisfatoriamente
um fenômeno físico, como exemplos: a ocorrência da conservação de certas
quantidades que não são físicas, a maioria das regras não permitir a propagação de
informações no autômato e a microdinâmica do sistema dever ser reversível. Há
considerável dificuldade em se reproduzir as leis dos fluidos com os autômatos
celulares.
2.3 Lattice-Gas Cellular Automata (LGCA)
O LGCA simplifica o problema da construção de autômatos celulares para a
simulação de fenômenos físicos, dividindo a regra de atualização em duas partes:
colisão e propagação.
Começando pelo mais simples, o HPP é um LGCA de duas dimensões numa
rede (lattice) quadrada. Sua importância é histórica, dado que esse autômato não
obedece à equação de Navier-Stokes no limite macroscópico. Possui quatro vetores
de rede, que apontam para cada nó adjacente, conforme vetores a, b, c e d na
Figura 2.1. Em cada nó há quatro células, cada uma associada ao seu respectivo
6
vetor de rede. Essas células podem estar vazias ou ocupadas por no máximo uma
partícula, cuja massa é igual para todas as partículas.
Figura 2.1. Rede quadrada do modelo HPP. Fonte: (Wolf-Gladrow, 2000).
A evolução no tempo é determinística e alterna colisões – entre partículas de
um mesmo nó – com propagações. A colisão conserva massa e quantidade de
movimento enquanto muda a ocupação das células. Quando duas partículas entram
em um nó por direções opostas e as outras células desse nó estão vazias, uma
colisão frontal acontece e rotaciona ambas as partículas em 90º no mesmo sentido
(Figura 2.2). Qualquer outra configuração não sofre mutação. A aplicação dupla do
operador de colisão retorna à configuração inicial, como mostra Figura 2.2.
Figura 2.2. Única configuração que gera colisão e seu resultado final. Pode ser
representado por células (a) ou por vetores (b). Fonte: (Wolf-Gladrow, 2000).
7
Pode-se ver, na Figura 2.1, que há duas populações de partículas
desacopladas na rede, formando duas sub-redes (branca e preta), características da
rede quadrada. O HPP não obedece à equação de Navier-Stokes porque o grau de
simetria rotacional na rede é insuficiente. Assim, certos tensores compostos de
produtos das velocidades da rede – os tensores da rede – não são isotrópicos na
rede quadrada. Essa anisotropia se manifesta, por exemplo, no escoamento
passando por um obstáculo simétrico não rotacional, no qual o arrasto depende da
orientação relativa do obstáculo com respeito à rede. Nesse autômato, também há
conservações adicionais indesejáveis que não ocorrem no mundo físico. Como
consequência, elas restringem a dinâmica do modelo.
No LGCA HPP, o estado das células é descrito por campos booleanos
( ), em que indica o número de ocupação – 0 ou 1 –, é a direção do vetor
da rede, , o tempo discreto e s, as coordenadas de cada nó. O número de ocupação
média, que pode variar de zero a um, é descrito por:
( ) ( )
Equação 2.6
Assim, a massa é dada por:
( ) ( ) Equação 2.7
e a quantidade de movimento:
( ) ( ) Equação 2.8
em que u é a velocidade do escoamento e ck são as velocidades da rede, dadas por:
√ ( ) Equação 2.9
√ ( )
Equação 2.10
8
√ ( ) Equação 2.11
√ ( )
Equação 2.12
que obedecem a condição de simetria da rede, ou seja, a somatória dessas
velocidades é igual a zero.
Outro LGCA de destaque é o FHP, que trouxe a terceira condição essencial
além de a conservação da massa e da quantidade de movimento: a condição de
simetria suficiente que garante isotropia de um certo tensor de posto quatro formado
pelas velocidades da rede, cuja simetria é hexagonal.
2.4 O Método Lattice Boltzmann (LB)
Como mencionado anteriormente, a grande diferença entre o LGCA e o Lattice
Boltzmann é que este lida com funções de distribuição de probabilidade contínuas
ao invés de partículas. A mecânica é simples e pode ser descrita como propagações
no espaço e interações de colisão, assim como no LGCA. Os modelos de Rede de
Boltzmann simplificam a modelagem e representam bem o comportamento de
fluidos (Chirila, 2010).
As interações podem ser consideradas colisões elásticas, conservando massa
e quantidade de movimento. Ao invés de se ter informações sobre cada partícula em
cada instante de tempo – o que tornaria possível prever todos os estados futuros,
mas aumentaria extraordinariamente os cálculos –, utilizam-se funções de
distribuição de probabilidades.
Essa função de distribuição indica a probabilidade de se encontrar uma
partícula numa dada posição com uma certa quantidade de movimento: essa é a
função de distribuição de partícula única, ( ). Ela é adequada para descrever todas
as propriedades de um gás que não dependem das posições relativas entre
moléculas.
O processo de propagação das partículas consiste em calcular as novas
funções de distribuição para o próximo passo de tempo, e o processo de colisão
rearranja as partículas que sofrem colisão (He, 1997).
9
Os modelos de Rede de Boltzmann reduzem o número de posições espaciais
das partículas, a quantidade de movimento microscópica e o tempo a alguns valores
discretos. O modelo mais comum, o D2Q9, possui oito direções mais o estado de
repouso, três magnitudes para a velocidade, apenas um valor de massa e duas
dimensões (Nourgaliev, 2003). A medida de comprimento fundamental é a unidade
de rede ( ) e a medida de tempo fundamental é o passo de tempo ( ). A equação
para um passo de tempo fica:
( ) ( ) ( )
( )
Equação 2.13
com sendo a função de distribuição de probabilidade na direção , x sendo o vetor
posição, a velocidade na direção , Δ uma quantidade de passos de tempo, t o
tempo, τ o tempo de relaxamento, ou seja, o tempo para o sistema atingir o
equilíbrio, e a função de distribuição de equilíbrio é definida como:
( ) ( ) [
( )
] Equação 2.14
com sendo os pesos, que valem para partículas em descanso ( ),
para e para , ρ é a densidade do fluido e é a
velocidade básica na rede – o valor mais simples é 1 (Sukop, 2007). A viscosidade
cinemática do fluido é dada por:
(τ
) Equação 2.15
em que τ não deve se aproximar muito de ½ (Sukop, 2007).
Outro tema importante nos modelos de Rede de Boltzamnn são as condições
de contorno. Elas podem ser periódicas, quando o fim de uma dimensão da rede se
liga ao início dela, formando um cilindro, ou um toroide no caso de todas as
dimensões serem periódicas. Podem também ser condições de rebatimento, útil
quando se quer representar obstáculos ou paredes, porque as partículas voltarão ao
nó que estavam antes de colidirem com o obstáculo.
As condições de contorno von Neumann ou condições de fluxo mantêm o valor
da velocidade enquanto as condições de Dirichlet ou de pressão mantêm o valor da
10
pressão ou densidade. Há ainda a possibilidade de se incluir a gravidade, somando-
se uma variação de velocidade causada pela ação de uma força.
2.4.1 O Método Lattice Boltzmann em escoamentos multicomponentes
multifásicos
O escoamento multicomponente multifásico é de grande interesse econômico
porque o petróleo geralmente é encontrado com água, e de grande preocupação
ambiental porque líquidos de fase não aquosa sob a superfície frequentemente
agem como fontes de contaminação das águas subterrâneas (Sukop, 2007).
O escoamento bifásico se mostra simples de ser implementado através da
adição de mais um fluido à simulação, junto com suas propriedades e um parâmetro
de interação entre os fluidos. Diferente do escoamento multifásico de um único
componente, no qual as forças de interação são atrativas, o escoamento multifásico
multicomponente possui forças de repulsão. No escoamento multifásico de um
componente, tem-se como exemplo a separação de fase, que acontece após
determinado tempo para esse tipo de escoamento. Já no escoamento
multicomponente, podem-se citar fluidos imiscíveis, como óleo e água.
A função de distribuição de equilíbrio é calculada através de uma velocidade
macroscópica composta:
∑
∑
∑
Equação 2.16
em que σ é o fluido 1, 2,...,n e é a densidade do fluido 1,2,...,n. A densidade é
calculada como:
∑
Equação 2.17
A velocidade macroscópica composta é a velocidade mais significativa para
análise (Sukop, 2007). Em relação à densidade, não é permitido que ambos os
fluidos tenham densidade zero no mesmo nó. A força no fluido σ é determinada por:
11
( ) ( ) ∑ ( ) Equação 2.18
em que é a força de interação entre os fluidos, indica o outro fluido – em um
escoamento com dois fluidos – e comumente são as densidades ( ). A
magnitude de G e as densidades dos fluidos determinam a força .
Para distinguir um fluido do outro no domínio, deve-se definir qual a densidade
de corte para a fronteira do fluido. Há algumas opções, dentre elas quando houver
50% de cada fluido no nó, ou quando a densidade de um fluido for maior que a
densidade do outro fluido, ou ainda quando a densidade de um fluido for maior que a
densidade da vizinhança.
Há ainda a interação dos fluidos com a superfície sólida, na qual cada fluido
interage separadamente com o sólido. Para se determinar o ângulo de contato entre
um fluido e uma superfície, deve-se usar a equação de Young:
Equação 2.19
em que é a tensão interfacial entre os dois fluidos e σ σ são as tensões
interfaciais entre a superfície e cada fluido. As forças de superfícies são
incorporadas ao modelo da mesma forma que no modelo de um único componente:
apenas somando a força à respectiva força resultante de cada fluido, com a força de
interação sendo :
Equação 2.20
Equação 2.21
Por exemplo, quando as densidades de cada fluido são iguais,
, σ σ e o ângulo de contato entre o fluido e a superfície
sólida é 90º, como mostra a Figura 2.3.
Figura 2.3. Ângulo de contato de 90º entre fluido e superfície sólida. Fonte:
(Sukop, 2007).
12
Assim, a equação de Young se torna:
, Equação 2.22
ou seja, é a diferença na força de adesão que determina o molhamento relativo, isto
é, o ângulo de contato.
2.4.2 Parâmetros da simulação
Para executar as simulações e interpretar corretamente seus resultados, é
necessário antes escolher apropriadamente os parâmetros de rede de Boltzmann,
tais como o tamanho da rede, a viscosidade numérica e a velocidade máxima
(Januszewski). Dado um escoamento físico com largura e altura do canal por onde o
fluido passa, sua velocidade e número de Reynolds, é possível calcular os
parâmetros da simulação.
Em unidades de rede, um passo de tempo da simulação é por definição 1 lt
(lattice time), e o espaço entre dois nós da rede é 1 lu (lattice unit). As Equações
2.23 a 2.25 são bastante úteis:
[
] Equação 2.23
em que é o espaçamento da rede, L é a altura do canal e R é o número de nós da
rede. O espaçamento da rede tem unidade em metros por unidade de rede. A
Equação 2.24 calcula a velocidade do escoamento da rede:
Equação 2.24
em que é o tamanho do passo de tempo da rede e é a velocidade do
escoamento real. A viscosidade da rede pode ser calculada com a Equação 2.25:
( )
Equação 2.25
13
em que Re denota o número de Reynolds do escoamento real, que será igual ao
número de Reynolds da simulação.
Arbitrando-se um número de nós da rede, pode-se calcular o espaçamento da
rede com a Equação 2.23 e, escolhendo-se a velocidade máxima de escoamento da
rede , pode-se calcular o tamanho do passo de tempo da rede utilizando:
. Equação 2.26
A partir desse resultado, é possível calcular a viscosidade da rede. A dificuldade
nesse método está em se escolher corretamente a velocidade máxima de
escoamento da rede, porque, apesar de o valor ser relativamente seguro,
alguns escoamentos podem se tornar instáveis com esse parâmetro.
Outra forma de se realizar estes cálculos é começando com um valor de
viscosidade numérica. Essa abordagem se mostra bastante lógica, visto que há uma
pequena faixa de valores com os quais a simulação não se torna instável (
). Basta adotar um valor nessa faixa, uma velocidade máxima de escoamento da
rede compatível e usar as equações acima para calcular os outros parâmetros.
14
3 Procedimento Experimental
A parte prática do presente trabalho foi dividida em algumas etapas, as quais
seguem: estudo de autômatos celulares em linguagem CUDA C, estudo e validação
do simulador Sailfish através de escoamentos monofásicos pelo método Lattice
Boltzmann, estudo de escoamentos bifásicos através do caso da interação entre
álcool e óleo de mamona em micromisturadores Ômega e Tesla e, finalmente,
implementação de simulador de fluidodinâmica em linguagem C, utilizando o método
Lattice Boltzmann.
3.1 Autômatos celulares em linguagem CUDA C
Cincos autômatos celulares foram implementados: três autômatos em uma
dimensão com três regras distintas e dois autômatos bastante conhecidos em duas
dimensões chamados jogo de Fredkin e jogo Life. Após completar o estudo sobre
autômatos celulares, o Sailfish passou a ser utilizado para a realização de
simulações no método Lattice Boltzmann, com a execução em placa de vídeo
(recursos da linguagem CUDA C).
3.2 Escoamentos no programa Sailfish
Os escoamentos monofásicos configurados em linguagem python no simulador
Sailfish também foram cinco: escoamento de Poiseuille, efeito do comprimento de
entrada, escoamentos laminar e turbulento passando por cilindro, escoamento
instável em altos números de Reynolds para visualização de vórtices e separação da
camada limite. Dessa forma, o simulador foi validado, com grande concordância em
relação a resultados da literatura (Sukop, 2007).
O estudo do escoamento bifásico consistiu no escoamento de dois fluidos entre
duas placas paralelas passando por obstáculo e no escoamento de álcool e óleo de
mamona passando por dois micromisturadores, o Ômega e o Tesla.
Nesta etapa do trabalho, houve a percepção de que o programa Sailfish não
atenderia a todos os casos de escoamentos, já que ele se encontra em um projeto
não concluído, no qual funções ainda serão acrescentadas. No mais, o programa e o
manual não deixam explícitos quais tipos de rede (lattice) e outros parâmetros
15
bastante relevantes estão realmente sendo adotados em todos os tipos de
escoamentos, o que dificulta a simulação de novos escoamentos, o estudo e o
correto entendimento do algoritmo utilizado no programa. O fato de o programa não
ter sido escrito em apenas uma linguagem, sendo na verdade a adoção de três
linguagens que se mesclam, apresenta outra desvantagem. Devido a esses
contratempos, decidiu-se criar um simulador próprio, a princípio em linguagem C, de
modo que houvesse ciência de todos os parâmetros e teoria aplicados ao algoritmo.
3.3 Implementação de simulador em linguagem C
Após verificar a inviabilidade da continuação no programa Sailfish, devido ao
fato de ele não ser explícito o suficiente em relação à teoria adotada em cada
escoamento, a última etapa deste trabalho se deu com a implementação de um
simulador de fluidodinâmica em linguagem C usando o método Lattice Boltzmann,
em que um simulador bem simples para escoamentos monofásicos com poucas
condições de contorno foi tomado como base para a construção de um simulador
para escoamentos bifásicos.
O primeiro passo foi melhorar a implementação base, tornando o programa
mais robusto, através da verificação da execução de alguns passos do programa,
que não eram verificados originalmente e poderiam gerar erros. Após essa fase,
houve a inclusão de um segundo fluido à simulação, configurando-se as condições
iniciais, atualizando-se a etapa de propagação e reprogramando-se a etapa de
colisão, que muda drasticamente em simulações multicomponentes. Por fim,
ajustaram-se as condições de contorno, considerando a presença de dois fluidos.
Terminada a base da implementação para dois fluidos, buscaram-se possíveis
erros no código, o que comprometeria a simulação, além de condições específicas
inerentes ao método Lattice Boltzmann, para alguns parâmetros, que, se não
respeitadas, levam a instabilidades na simulação. A seguir, são detalhados os
resultados obtidos da sistematização descrita no presente capítulo.
16
4 Resultados e Discussões
Na primeira parte do trabalho, foram implementados alguns autômatos
celulares de uma e de duas dimensões na linguagem CUDA C. Já no método Lattice
Boltzmann, o simulador Sailfish foi configurado a partir de scripts em linguagem
Python e foi utilizado em escoamentos monofásicos, o que possibilitou a validação
do simulador, e em escoamentos bifásicos, acrescentando posteriormente os
microrreatores necessários à simulação da mistura álcool-óleo. Por fim, iniciou-se o
desenvolvimento de um simulador de fluidodinâmica em linguagem C baseado no
método Lattice Boltzmann. Todas as simulações foram executadas em computador
com sistema operacional Ubuntu baseado em Linux com processador Intel Core i7
CPU 950 3,08 GHz, memória RAM de 6GB e placa de vídeo NVidia GeForce GTX
560 Ti com 386 núcleos CUDA e 1GB de memória.
4.1 Autômatos Celulares em linguagem CUDA C
4.1.1 Autômato Celular 1D, regra 2 e raio 2
Como explicado no capítulo 2, o autômato celular de uma dimensão consiste
em células que podem estar ocupadas ou vazias ao longo de uma linha e é circular,
ou seja, após a última célula da linha segue-se a primeira célula dessa mesma linha.
A linha seguinte representa o próximo estado da linha atual; ocorre uma atualização
da linha atual que depende do estado de cada célula e suas vizinhas num raio
determinado. Assim, cada célula é atualizada simultaneamente.
O raio igual a dois significa que os dois vizinhos à direita e os dois vizinhos à
esquerda de uma célula serão considerados na regra de atualização. Regra igual a
dois significa que somente a configuração correspondente à representação binária
desse número – 10 – mudará o estado da célula para ocupada, conforme Tabela
4.1.
17
Tabela 4.1. Tabela verdade para autômato celular 1D, regra 2 e raio 2.
( )
( )
( )
( )
( )
( )
0 0 0 0 0 0
0 0 0 0 1 1
0 0 0 1 0 0
1 1 1 1 1 0
Na Tabela 4.1, pode-se ver que a única configuração em que a célula ficará no
estado ocupada é aquela em que somente a célula dois passos à direita está
ocupada, enquanto todas as outras – incluindo a célula em questão, estão vazias. O
número binário 10 – regra 2 – pode ser visto na última coluna, com o bit menos
significativo na primeira linha.
No código em CUDA C, que foi baseado no exemplo shared_bitmap.cu do
capítulo cinco do livro CUDA by Example (Sanders, 2010), foi gerada uma linha
aleatória – primeira linha do bitmap de 1024 x 1024 pixels – com o auxílio da função
rand(), incluindo-se a biblioteca stdlib.h. Essa linha tem probabilidade de 50% para
ter pixels pretos (representando células ocupadas) e 50% para ter pixels brancos
(células vazias). Foi alocado espaço para um bitmap de 1024 x 1024 pixels na CPU
dentro da função main(), dentro da qual a primeira linha foi preenchida da forma
mencionada acima, ainda que a indexação de um bitmap seja com o índice zero no
canto inferior esquerdo e o último índice referente ao último pixel no canto superior
direito do bitmap. Para contornar este fato, o loop for que preenche a primeira linha
inicia na célula com o índice ( ) até ( ) – DIM é igual a
1024 neste caso.
Depois disso, esse bitmap foi copiado para a GPU através do comando
“cudaMemcpy” e o ponteiro para esse bitmap na CPU foi desalocado. A seguir, outro
loop for, que vai de ( ) até , decrementando o contador de um a cada
iteração para contornar os índices do bitmap, invoca o kernel, função que é
processada na GPU, linha a linha do bitmap, realizando a atualização segundo a
regra mencionada acima.
Esse kernel constrói um bloco com DIM threads – tarefas executadas
paralelamente –, para não haver o problema de acesso aos vizinhos, já que threads
só podem acessar dados de outras threads do mesmo bloco. Esse kernel também
18
recebe como parâmetro, além de um ponteiro para o bitmap na GPU, um índice que
indica qual linha do bitmap está se atualizando.
Para escrever na linha seguinte a que se está no bitmap – a atualização –,
deve-se calcular o índice que representa determinada célula na linha seguinte. Para
isso, utiliza-se a seguinte indexação:
int x = threadIdx.x;
int offset = x + i*blockDim.x;
int pto_prox_linha = offset - blockDim.x;
x indexa as threads no eixo x, offset indica a linha atual (i = linha atual) e
pto_prox_linha realiza o cálculo final, que é a linha atual subtraída da dimensão do
bloco no eixo x, que é igual à largura do bitmap. A subtração se dá por conta da
indexação do bitmap.
Também nesse kernel é realizada uma verificação que visa identificar quando o
vizinho está antes do início da linha ou após o fim da linha. Caso esteja uma posição
antes do início da linha, esse vizinho passa a ser a última célula da linha, se estiver
duas posições antes do início, o vizinho passará a ser a penúltima célula; caso
esteja uma posição após o fim da linha, o vizinho será a primeira célula da linha ou
se estiver duas posições após o fim, ele será a segunda célula – para satisfazer a
característica circular do autômato.
Comparações dentro de um if, para saber o estado de cada vizinho
considerado na regra e da célula cujo estado será definido, determina se a célula
será escrita com a cor preta, caso seja a segunda configuração da Tabela 4.1, ou
branca, para todos os outros casos.
A última linha deste kernel chama a função __syncthreads(), que garante que
todas as threads chegarão ao fim de sua execução antes que as linhas seguintes
sejam executadas, neste caso, o fim do kernel. A utilidade dessa função nesse
programa é justamente esperar que toda a próxima linha termine de se atualizar
antes que a linha seguinte a esta seja chamada para atualização. Se essa função
não fosse utilizada, poderia haver erros com threads verificando células com valores
errados. Um resultado desse programa pode ser visto na Figura 4.1.
19
Figura 4.1. Autômato Celular 1D, regra 2 e raio 2.
4.1.2 Autômato Celular 1D, regra 20 e raio 1
A definição deste autômato é a mesma daquela da subseção anterior, porém,
com raio um, apenas são verificados os vizinhos uma posição à direita e uma
posição à esquerda de cada célula.
Regra 20, em representação binária igual a 10100, significa que agora há duas
configurações em que a célula atual pode mudar seu estado para 1 (ocupada), como
pode ser confirmado na Tabela 4.2.
20
Tabela 4.2. Tabela verdade para autômato celular 1D, regra 20 e raio 1.
( )
( )
( )
( )
0 0 0 0
0 0 1 0
0 1 0 1
0 1 1 0
1 0 0 1
1 0 1 0
1 1 0 0
1 1 1 0
O programa em CUDA C segue o mesmo padrão daquele da seção anterior
(4.1.1), mudando apenas o raio verificado – um vizinho à direita e um à esquerda –,
e as comparações dentro do if com as configurações que mudarão o estado da
célula – nesta subseção, a terceira e quinta configurações da Tabela 4.2. Uma
imagem obtida pode ser vista na Figura 4.2.
21
Figura 4.2. Autômato Celular 1D, regra 20 e raio 1.
4.1.3 Autômato Celular 1D, regra 52 e raio 1
Novamente, o mesmo autômato foi utilizado para satisfazer a regra 52, que é
110100 em binário, agora com três configurações em que o estado da célula torna-
se ocupado. Em relação à regra 20, a regra 52 apenas acrescenta uma configuração
que vai para o estado 1. A Tabela 4.3 mostra todas as configurações possíveis
desse autômato.
22
Tabela 4.3. Tabela verdade para autômato celular 1D, regra 52 e raio 1.
( )
( )
( )
( )
0 0 0 0
0 0 1 0
0 1 0 1
0 1 1 0
1 0 0 1
1 0 1 1
1 1 0 0
1 1 1 0
Uma comparação também é acrescentada no código dentro do if que muda o
estado das células do autômato do programa da seção 4.2. Obteve-se a imagem da
Figura 4.3.
Figura 4.3. Autômato Celular 1D, regra 52 e raio 1.
23
4.1.4 Jogo de Fredkin
O jogo de Fredkin é um tipo de autômato cujas células também possuem
apenas dois estados, viva (ocupada) ou morta (vazia). Porém, é um autômato de
duas dimensões e as células são atualizadas simultaneamente, utilizando uma
vizinhança do tipo von Neumann de raio 1 (Figura 4.4).
Figura 4.4. Vizinhança von Neumann raio 1. Fonte: (Wolf-Gladrow, 2000).
A atualização consiste na seguinte regra: caso o número de vizinhos seja par
(0, 2 ou 4), a célula estará morta na próxima atualização. Caso contrário, a célula
estará viva. O estado da célula central não é relevante.
O programa foi baseado no exemplo heat_2d.cu do capítulo sete do livro CUDA
by Example (Sanders, 2010), que anima um bitmap, assim, pode-se ver o
desenvolvimento do autômato no tempo. O kernel imagem_inicial escreve a letra F
no centro do bitmap. A partir disso, o kernel blend_kernel utiliza a memória de
texturas para acessar os vizinhos e contar o número de vizinhos vivos. A memória
de texturas é uma memória somente leitura da GPU que, em certos casos, pode
acelerar substancialmente o desempenho e reduzir o tráfego de memória (Sanders,
2010). Ela é útil para acessar diretamente células como as da Figura 4.4, que, de
outra forma, seria necessário percorrer toda a linha para acessar as células vizinhas
desejadas.
Todos os ponteiros para os bitmaps foram alterados para o tipo unsigned char
e, para diminuir a velocidade de atualização do bitmap, o for dentro da função
anim_gpu vai apenas de zero a um.
O jogo foi realizado com a letra F e uma iteração pode ser vista na Figura 4.5.
24
Figura 4.5. Jogo de Fredkin com a letra F como padrão a ser replicado.
4.1.5 Jogo Life
O Jogo Life possui as mesmas características do jogo de Fredkin, mas utiliza
uma vizinhança do tipo Moore de raio um (Figura 4.6). A regra de atualização do
jogo Life também é de acordo com o número de vizinhos vivos: se uma célula tiver
três vizinhos vivos, estará viva no próximo passo de tempo e, se tiver menos que
dois vizinhos vivos ou mais do que três, estará morta.
25
Figura 4.6. Vizinhança de Moore de raio um. Fonte: (Wolf-Gladrow, 2000).
Foi gerado um bitmap aleatório na CPU assim como nos autômatos de uma
dimensão, mas ocupando todo o bitmap (2D), através da função rand(). As mesmas
alterações do Jogo de Fredkin, como mudar o tipo dos ponteiros e diminuir a
duração do for, foram feitas aqui. Um instante de tempo obtido está mostrado na
Figura 4.7.
26
Figura 4.7. Jogo Life.
4.2 Aplicação do método Lattice Boltzmann em escoamentos
monofásicos no Sailfish
Para validação do simulador Sailfish, alguns escoamentos clássicos foram
realizados, tais como: escoamento de Poiseuille, efeito do comprimento de entrada,
escoamento passando por um cilindro, escoamento instável em altos números de
Reynolds e separação da camada limite.
O Sailfish gera arquivos de dados com os resultados da simulação que
podem ser processados por outros programas (Januszewski). Os gráficos a seguir
foram plotados com o software MATLAB.
27
4.2.1 Escoamento de Poiseuille
O escoamento de Poiseuille pode ser o tipo de escoamento mais simples de se
simular usando o método Lattice Boltzmann (LB) (Sukop, 2007). Ele consiste em um
fluido escoando dentro de um tubo circular, que no caso de duas dimensões pode
ser aproximado por uma fenda. Usando-se o método LB, é necessário colocar
condições de contorno de rebatimento (bounce-back) nas paredes da fenda – o que
é feito no simulador considerando a fenda como nós de parede –, e condições de
contorno periódicas ao longo do escoamento – que é o padrão do Sailfish. Assim, o
escoamento que sai pelo fim da fenda entra pelo início dela, tornando o sistema
infinito nessa direção (Sukop, 2007).
Para adquirir os dados de velocidade em cada ponto da rede, basta passar
para a rotina que simula o escoamento de Poiseuille os parâmetros do tamanho da
rede nos eixos x e y, indicar se o escoamento é incompressível, se o perfil de
velocidade já é desenvolvido e a viscosidade da rede. A Figura 4.8 mostra o perfil de
velocidade da literatura (Sukop, 2007) e a Figura 4.9 mostra o perfil da simulação. O
número de Reynolds utilizado foi 4,4.
Figura 4.8. Escoamento de
Poiseuille; x é o tamanho da rede no
eixo x. Fonte: (Sukop, 2007).
Figura 4.9. Escoamento de Poiseuille:
resultado da simulação no Sailfish.
Outra forma de se obter o mesmo gráfico é programando uma simulação cuja
geometria consiste em nós de parede nas fendas como citado antes e com
condições de contorno de velocidade nas extremidades esquerda e direita.
28
4.2.2 Efeito do Comprimento de Entrada
Para números de Reynolds maiores que 30, aproximadamente, a teoria do
escoamento de Poiseuille só se aplica após uma certa distância do duto (Tritton,
1988). De acordo com as propriedades e velocidade do fluido e a geometria do duto,
o perfil de velocidade completamente desenvolvido demora a ser atingido, podendo
alcançar uma distância considerável desde a entrada. Dessa forma, o escoamento
de Poiseuille pode não ser o mais adequado para se tratar um escoamento, havendo
a necessidade de se estudar o efeito do comprimento de entrada.
No método LB, é possível verificar o comprimento de entrada através do uso de
condições de contorno de velocidade nas extremidades do escoamento. Então, a
rotina alternativa utilizada para se analisar o escoamento de Poiseuille também
serve para estudar o efeito do comprimento entrada. Para melhor visualização desse
efeito, pede-se um número de Reynolds mais alto, de outra forma o efeito é muito
pequeno e torna-se desprezível. As Figuras 4.10 e 4.11 ilustram esse efeito.
Figura 4.10. Visão simplificada do
efeito do comprimento de entrada; u é
a velocidade, uavg é a velocidade
média, r é a distância medida a partir
do centro do tubo e a é o raio do tubo.
Fonte: (Sukop, 2007).
Figura 4.11. Efeito do comprimento de
entrada simulado no Sailfish.
29
4.2.3 Escoamento Passando por Cilindro
O escoamento passando por um cilindro há muito tempo interessa à dinâmica
dos fluidos (Sukop, 2007). Com ele, é possível visualizar as linhas de fluxo no
escoamento e analisar como o fluido se comporta com obstáculos. Pode-se também
comparar os efeitos para escoamento laminar e turbulento.
A rotina que simula o escoamento passando por um cilindro indica a posição do
cilindro, o tamanho da rede e o valor da viscosidade, para assim controlar o número
de Reynolds e o regime do escoamento – laminar ou turbulento. As Figuras 4.12 a
4.15 demonstram esse exemplo.
Figura 4.12. Escoamento
laminar passando por cilindro.
Fonte: (Sukop, 2007).
Figura 4.13. Escoamento laminar passando por
cilindro – simulação em Sailfish.
30
Figura 4.14. Escoamento
turbulento simulado (acima) e
fotografado (abaixo). Fonte:
(Sukop, 2007).
Figura 4.15. Escoamento turbulento simulado
no Sailfish.
4.2.4 Escoamento Instável em Altos Números de Reynolds
No escoamento passando por um cilindro, conforme se aumenta o número de
Reynolds, vórtices passam a se desprender do cilindro para lados opostos
alternadamente (Sukop, 2007). Esse tipo de escoamento também é de grande
interesse para a comunidade científica. As Figuras 4.16 e 4.17 mostram imagens da
simulação propriamente dita, em que é possível ver os vórtices ao longo do
escoamento. As Figuras 4.18 e 4.19 mostram os vetores de velocidade e as linhas
de fluxo, respectivamente.
31
Figura 4.16. Foto da formação de
vórtices (acima) e simulação
(abaixo). Fonte: (Sukop, 2007).
Figura 4.17. Simulação do Sailfish; a
primeira imagem mostra a magnitude da
velocidade e as quatro imagens abaixo
representam diferentes visualizações para
a vorticidade.
Figura 4.18. Vetores de velocidade
no escoamento.
Figura 4.19. Linhas de fluxo.
32
4.2.5 Separação da Camada Limite
Outro fenômeno interessante é a indução da separação da camada limite. Ele
pode ser observado na subseção 4.2.3 no escoamento turbulento, mas aqui o
mesmo exemplo foi alterado para conter como obstáculo um quadrado, como
mostram as Figuras 4.20 e 4.21.
Figura 4.20. Separação da
camada limite. Fonte: (Sukop,
2007).
Figura 4.21. Separação da camada limite no
simulador.
4.3 Aplicação do método Lattice Boltzmann em escoamentos
bifásicos no Sailfish
Após a verificação da coerência entre os resultados gerados pelo Sailfish com
a literatura (Sukop, 2007), passou-se para a realização de simulações em
escoamentos bifásicos. Foram simulados dois fluidos imiscíveis escoando
paralelamente entre placas paralelas livremente, depois com a adição de um
obstáculo e por fim álcool e óleo de mamona foram simulados escoando pelos
micromisturadores Ômega e Tesla.
Para adicionar um novo fluido à simulação, basta definir a densidade de cada
fluido separadamente, através dos índices rho e phi, e os tempos de relaxação,
33
através dos parâmetros visc e tau_phi. Outro parâmetro a ser definido é a força de
interação G. Por último, deve-se adicionar uma condição para o fluido, como uma
força ao longo de todo domínio.
4.3.1 Escoamento bifásico entre placas paralelas
A Figura 4.22 apresenta o escoamento entre duas placas paralelas de dois
fluidos imiscíveis, com phi = 1 e rho = 0.4. Apesar de a imiscibilidade dos fluidos,
devido às suas velocidades, ao passarem por um obstáculo, ocorre um pequeno
grau de mistura entre eles.
Figura 4.22. Escoamento bifásico entre placas paralelas no Sailfish (a flecha aponta
o obstáculo).
Outro problema de interesse científico e econômico é simular a interação entre o
álcool e o óleo de mamona. A Figura 4.23 mostra o perfil de velocidade para os dois
fluidos escoando entre placas paralelas livremente. Essa simulação também
concorda com a teoria (Sukop, 2007), o que reforça a validação do simulador para
simulações multicomponentes.
34
Figura 4.23. À esquerda, perfil de velocidade de dois fluidos imiscíveis escoando
entre placas paralelas. Fonte: (Sukop, 2007). À direita, mesmo perfil de velocidade
para álcool e óleo de mamona.
4.3.2 Escoamento de álcool e óleo em microrreator Ômega
O microrreator Ômega foi desenhado em programa CAD com as dimensões da
Figura 4.24, e a imagem bitmap extraída do desenho CAD em duas dimensões foi
aplicada à simulação.
Figura 4.24. Dimensões do microrreator Ômega. Fonte: (Arias, 2010)
A Tabela 4.4 mostra os parâmetros reais de cada fluido.
35
Tabela 4.4. Parâmetros dos fluidos.
Óleo de Mamona Etanol
Densidade real [kg/m³] 957,3 789
Viscosidade dinâmica [Pa.s] 0,689 0,0012
Taxa Volumétrica [mL/h] 1 0,5
Velocidade real [m/s] 0,00111 0,000556
Número de Reynolds 0,0914
Altura do canal [m]
A partir desses parâmetros, devem-se calcular os parâmetros adimensionais
necessários à simulação – densidade, viscosidade, velocidade adimensionais. Com
as densidades da Tabela 4.4, pode-se calcular a densidade de simulação de cada
fluido: escolhendo a densidade do óleo como 1, a densidade relativa de simulação
do etanol é 0,824. Arbitrando-se o valor de viscosidade da rede como 0,16 – o valor
mais estável possível, de acordo com a seção 2.4.2 –, calculou-se a velocidade
máxima de escoamento da rede, igual a . A força de interação é obtida
experimentalmente através de testes até que os fluidos tenham comportamento igual
ao real (Sukop, 2007). A Tabela 4.5 mostra os parâmetros da simulação e as
Figuras 4.25 e 4.26 mostram a simulação com o microrreator Ômega e com três
microrreatores Ômega em cascata, respectivamente. Além disso, elas mostram seis
instantes de tempo distintos e não adjacentes.
Tabela 4.5. Parâmetros da simulação.
Óleo de Mamona Etanol
Densidade 1 0,824
Viscosidade cinemática 0,16 0,0402
Tempo de relaxação 0,98 0,62
Velocidade máxima
Força de interação -1,2
Espaçamento da rede
Tamanho do passo de tempo
36
Figura 4.25. Escoamento de etanol e óleo de mamona em microrreator Ômega; seis
instantes de tempo distintos e não adjacentes.
Figura 4.26. Escoamento de etanol e óleo de mamona passando por três
microrreatores Ômega em cascata; seis instantes de tempo distintos e não
adjacentes.
Pode-se observar que o micromisturador Ômega não é eficiente para misturar
álcool e óleo de mamona, já que tanto um único misturador ou misturadores em
cascata possuem o mesmo resultado: em cada estágio (misturador), apenas uma
gota de óleo se desprende para escoar no álcool, porém, ao fim do estágio, essa
gota volta para a parte com óleo. A seguir é realizada a mesma análise para o
misturador Tesla.
37
4.3.3 Escoamento de álcool e óleo em microrreator Tesla
O microrreator Tesla também foi desenhado em programa CAD com as
dimensões da Figura 4.27, e a imagem bitmap extraída do desenho CAD em duas
dimensões foi aplicada à simulação.
Figura 4.27. Dimensões do microrreator Tesla. Fonte: (Arias, 2010)
Os mesmos parâmetros reais dos fluidos da Tabela 4.4 foram utilizados para os
cálculos dos parâmetros da simulação da Tabela 4.5. As Figuras 4.28 e 4.29
mostram a simulação com o microrreator Tesla e com três microrreatores Tesla em
cascata, respectivamente. Além disso, A Figura 4.28 mostra oito instantes de tempo
distintos e não adjacentes, enquanto a Figura 4.29 mostra quatro instantes de tempo
distintos e não adjacentes.
Figura 4.28. Escoamento de etanol e óleo de mamona em microrreator Tesla; oito
instantes de tempo distintos e não adjacentes.
38
Figura 4.29. Escoamento de etanol e óleo de mamona passando por três
microrreatores Tesla em cascata; quatro instantes de tempo distintos e não
adjacentes.
Já no micromisturador Tesla, observa-se formação de bolhas após um curto
período de tempo (instante número 4 da Figura 4.28 para um único misturador e
instante número 3 da Figura 4.29 para os misturadores em cascata). Esse resultado
indica que o microrreator Tesla é mais eficiente que o Ômega para misturar os dois
fluidos em questão, havendo ainda a necessidade de se acrescentar um reagente
catalisador para haver mistura completa (Saraiva Ferreira & Martins Campos de
Oliveira, 2012).
A seção seguinte explica a implementação de um simulador em linguagem C,
visto que, apesar de os bons resultados até a presente seção, muitos aspectos do
método e dos modelos Lattice Boltzmann ficaram obscuros e uma abordagem mais
profunda é requerida para se obter simulações robustas e fortemente fiéis à
realidade.
4.4 Implementação de Simulador em Linguagem C
A fim de satisfazer os requisitos não presentes no Sailfish, como a explicitação
do modelo, iniciou-se a implementação de um simulador de fluidodinâmica em
39
linguagem C usando o método Lattice Boltzmann. Como base, tomou-se um
simulador bem simples para escoamentos monofásicos com poucas condições de
contorno e modificações foram realizadas de forma a se acrescentar um fluido à
simulação.
Como citado na seção de procedimentos experimentais, o primeiro passo foi
melhorar a implementação base, tornando o programa mais robusto, através da
verificação da execução de alguns passos do programa, que não eram verificados
originalmente e poderiam gerar erros. Como exemplo, a verificação da alocação
dinâmica de memória, checando se o ponteiro não apontou para vazio (NULL); a
escrita em arquivo de algumas variáveis críticas do programa; a verificação da
correta execução das funções em OpenGL originalmente implementas; teste de
mesa em todas as funções relativas ao método e verificação de divisões por zero e
prevenção dessas.
A segunda fase foi incluir o segundo fluido ao simulador, dobrando-se o
número de ponteiros existentes originalmente, acrescentando-se parâmetros do
segundo fluido, tais como: densidade, tempo de relaxação e velocidade de entrada
do fluido. O dobro de espaço também foi alocado e a velocidade composta inicial foi
calculada e aplicada a ambos os fluidos:
(
) ( )
(
)
Equação 4.1
A função de propagação foi atualizada para haver propagação também no
segundo fluido e, na função de colisão, cálculos de atualização de densidade e
velocidade foram realizados também para o outro fluido; a velocidade composta foi
calculada, como na configuração inicial (Equação 4.1), porém considerando
velocidades nos eixos x e y; a função de distribuição de probabilidade de equilíbrio
foi recalculada para ambos os fluidos considerando-se a velocidade composta;
segue o cálculo dessa função para as nove direções possíveis apenas para um
fluido:
f0eq = ro * faceq1 * (1.f - 1.5f*ulinha_2);
f1eq = ro * faceq2 * (1.f + 3.f*ulinha_x + 4.5f*ulinha_x*ulinha_x - 1.5f*ulinha_2);
40
f2eq = ro * faceq2 * (1.f + 3.f*ulinha_y + 4.5f*ulinha_y*ulinha_y - 1.5f*ulinha_2);
f3eq = ro * faceq2 * (1.f - 3.f*ulinha_x + 4.5f*ulinha_x*ulinha_x - 1.5f*ulinha_2);
f4eq = ro * faceq2 * (1.f - 3.f*ulinha_y + 4.5f*ulinha_y*ulinha_y - 1.5f*ulinha_2);
f5eq = ro * faceq3 * (1.f + 3.f*(ulinha_x + ulinha_y) + 4.5f*(ulinha_x +
ulinha_y)*(ulinha_x + ulinha_y) - 1.5f*ulinha_2);
f6eq = ro * faceq3 * (1.f + 3.f*(-ulinha_x + ulinha_y) + 4.5f*(-ulinha_x +
ulinha_y)*(-ulinha_x + ulinha_y) - 1.5f*ulinha_2);
f7eq = ro * faceq3 * (1.f + 3.f*(-ulinha_x - ulinha_y) + 4.5f*(-ulinha_x -
ulinha_y)*(-ulinha_x - ulinha_y) - 1.5f*ulinha_2);
f8eq = ro * faceq3 * (1.f + 3.f*(ulinha_x - ulinha_y) + 4.5f*(ulinha_x -
ulinha_y)*(ulinha_x - ulinha_y) - 1.5f*ulinha_2);
Finalmente, a colisão é efetuada para ambos os fluidos:
f0[i0] = rtau1 * f0[i0] + rtau * f0eq;
f1[i0] = rtau1 * f1[i0] + rtau * f1eq;
f2[i0] = rtau1 * f2[i0] + rtau * f2eq;
f3[i0] = rtau1 * f3[i0] + rtau * f3eq;
f4[i0] = rtau1 * f4[i0] + rtau * f4eq;
f5[i0] = rtau1 * f5[i0] + rtau * f5eq;
f6[i0] = rtau1 * f6[i0] + rtau * f6eq;
f7[i0] = rtau1 * f7[i0] + rtau * f7eq;
f8[i0] = rtau1 * f8[i0] + rtau * f8eq;
g0[i0] = rtau3 * g0[i0] + rtau2 * g0eq;
g1[i0] = rtau3 * g1[i0] + rtau2 * g1eq;
g2[i0] = rtau3 * g2[i0] + rtau2 * g2eq;
g3[i0] = rtau3 * g3[i0] + rtau2 * g3eq;
g4[i0] = rtau3 * g4[i0] + rtau2 * g4eq;
g5[i0] = rtau3 * g5[i0] + rtau2 * g5eq;
g6[i0] = rtau3 * g6[i0] + rtau2 * g6eq;
g7[i0] = rtau3 * g7[i0] + rtau2 * g7eq;
g8[i0] = rtau3 * g8[i0] + rtau2 * g8eq;
41
Por fim, as quatro funções de aplicação das condições de contorno foram ajustadas.
A função de condição periódica incluiu mais um fluido, a função de condição para
paredes bounce-back também incluiu o efeito da adição de outro fluido, a função
para condição de entrada de velocidade calculou parâmetros para o segundo fluido
e também aplicou esses parâmetros a ele. Por último, a condição de saída foi
atualizada para o segundo fluido. A condição periódica é implementada da seguinte
forma:
void per_BC(void){
int i0,i1,i;
for (i=0; i<ni; i++){
i0 = I2D(ni,i,0);
i1 = I2D(ni,i,nj-1);
f2[i0] = f2[i1];
f5[i0] = f5[i1];
f6[i0] = f6[i1];
f4[i1] = f4[i0];
f7[i1] = f7[i0];
f8[i1] = f8[i0];
g2[i0] = g2[i1];
g5[i0] = g5[i1];
g6[i0] = g6[i1];
g4[i1] = g4[i0];
g7[i1] = g7[i0];
g8[i1] = g8[i0];
}
}
A última etapa desse processo, para busca de erros, consistiu na verificação
dos valores dos parâmetros, e na inserção de uma condição de velocidade, a qual
deve ser no máximo igual a 0,1 lu/lt, para não haver instabilidades no sistema que
42
inviabilizem a simulação. Também se evitaram nesta etapa divisões por zero,
limitando-se as velocidades individuais para cada fluido.
Duas imagens para a versão do simulador com um fluido podem ser vistas nas
Figuras 4.30 e 4.31. Nelas o fluido entra pelo lado esquerdo com velocidade
constante, sai pelo lado direito e é periódico nas bordas de cima e de baixo.
Obstáculos foram colocados para a geração de vórtices e mudança de velocidade.
Figura 4.30. Geração de vórtices em simulação monocomponente.
Figura 4.31. Velocidades diferentes em simulação monocomponente.
Na simulação multicomponente, é possível visualizar a entrada de fluidos pelo
lado esquerdo conforma mostram as Figuras 4.32 e 4.33. Apesar de parte da
visualização já poder ser vista, como é o caso da condição de entrada na primeira
figura e alguns pontos na segunda figura, o simulador está em fase de
desenvolvimento, pois a implementação da força de interação entre os fluidos ainda
precisa ser efetuada.
43
Figura 4.32. Entrada de fluidos pelo lado esquerdo; simulação bifásica.
Figura 4.33. Alguns pontos mudam a velocidade na presença de obstáculos (pontos
pretos).
44
5 Conclusões e Perspectivas
A teoria de autômatos celulares e o método Lattice Boltzmann foram estudados
e alguns escoamentos clássicos monofásicos foram simulados tanto para validação
quanto para aprendizado do simulador Sailfish. Também foram desenhados os
microrreatores e investigaram-se os parâmetros dos fluidos para simulação da
interação entre álcool e óleo de mamona em escoamentos bifásicos. Por fim, iniciou-
se a implementação de um simulador de fluidodinâmica utilizando o método Lattice
Boltzmann em linguagem C, visando o estudo de escoamentos multifásicos
multicomponentes.
Para a simulação de escoamentos bifásicos, a maior fonte de erro está no
ajuste da força de interação, que é empírica; apesar disso, as simulações no Sailfish
condizem com o esperado: o microrreator Ômega não é muito eficiente para misturar
os fluidos, enquanto o microrreator Tesla realiza melhor a tarefa. Contudo, para uma
mistura completa, é necessário utilizar um reagente químico que facilita o processo.
As simulações no Sailfish resultaram na publicação de um artigo em congresso
internacional, sendo um trabalho bastante satisfatório ao aluno e ao orientador
(Saraiva Ferreira & Martins Campos de Oliveira, 2012).
Além disso, a implementação do simulador em linguagem C para substituir o
Sailfish, que possui algumas deficiências já descritas ao longo do texto já está
adiantada e os próximos passos serão adicionar a força de interação entre os fluidos
para a simulação completa. O desenvolvimento do simulador trouxe maior
aprofundamento no método Lattice Boltzmann, que será bastante útil em trabalhos
futuros.
5.1 Perspectivas
Como trabalhos futuros, pretende-se terminar o desenvolvimento do
simulador em linguagem C e traduzi-lo para linguagem CUDA C no mestrado.
Terminando o desenvolvimento do simulador em C, espera-se deixar o programa
mais robusto, aceitando um número maior de fluidos e otimizando-o para maior
velocidade de execução.
45
O objetivo final do simulador é executar em um cluster de GPUs no
Laboratório de Simulações Multifísicas do Departamento de Mecânica
Computacional (DMC) da Faculdade de Engenharia Mecânica (FEM). Através desse
alto ganho de desempenho, abordar-se-ão outros problemas de escoamento de
fluidos multicomponentes de grande interesse econômico, como os problemas
relacionados ao petróleo, no qual o método Lattice Boltzmann possui maior ganho
em relação a algortimos CFDs tradicionais. O cluster também possibilitará a
simulação de problemas em três dimensões.
46
Referências Bibliográficas
Arias, E. L. (2010). Desenvolvimento e Avaliação de Microreatores: Aplicação para Produção
de Biodiesel.
Chen, S. e. (1998). Lattice Boltzmann Method for Fluid Flows. Annual Review of Fluid
Mechanics.
Chirila, D. B. (2010). Introduction to Lattice Boltzmann Methods.
Fox, R. W. (2006). Introdução à Mecânica dos Fluidos (6ª ed.). LTC.
He, X. e.-S. (1997). Theory of the lattice Boltzmann method: From the Boltzmann equation to
the lattice Boltzmann equation. Physical Review, 56.
Januszewski, M. (s.d.). Sailfish Manual Reference. Acesso em 18 de Janeiro de 2012,
disponível em http://sailfish.us.edu.pl/
Nourgaliev, R. D. (2003). The Lattice Boltzmann Equation Method: Theoretical
Interpretation, Numerics and Implications. International Journal of Multiphase Flow.
Randima, F. (2004). GPU Gems - Programming Techniques, Tips, and Tricks for Real-Time
Graphics.
Sanders, J. e. (2010). CUDA by Example - An Introduction General-Purpose GPU
Programming. Addison-Wesley.
Saraiva Ferreira, L. O., & Martins Campos de Oliveira, F. (2012). Simulation of the Alcohol-
Oil Mixture in a Micromixer Using the lattice Boltzmann Method on a GPU Device.
14th Brazilian Congress of Thermal Sciences and Engineering.
Sukop, M. e. (2007). Lattice Boltzmann Modeling – An Introduction for Geoscientists and
Engineers. Springer.
Tritton, D. (1988). Physical Fluid Dynamics. Oxford Science Publications.
Wolf-Gladrow, D. A. (2000). Lattice-Gas Cellular Automata and Lattice Boltzmann Models –
An Introduction. Springer.
Zhao, Y. (2008). Lattice Boltzmann based PDE solver on the GPU. The Visual Computer:
International Journal of Computer Graphics.
47
Anexos
1) Artigo publicado no 14th Brazilian Congress of Thermal Sciences and
Engineering
48
49
50
51
52
53
54
2) Código em linguagem C de simulador multicomponente
////////////////////////////////////////////////////////////////////////////////
// Crude 2D Lattice Boltzmann Demo program
// C version
// Graham Pullan - Oct 2008
// com alterações (Fabíola Martins) - Nov 2012
//
// f6 f2 f5
// \ | /
// \ | /
// \|/
// f3---|--- f1
// /|\
// / | \ and f0 for the rest (zero) velocity
// / | \
// f7 f4 f8
//
///////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glew.h>
#include <GL/glut.h>
#define I2D(ni,i,j) (((ni)*(j)) + i)
////////////////////////////////////////////////////////////////////////////////
// OpenGL pixel buffer object and texture //
GLuint gl_PBO, gl_Tex;
// arrays //
float *f0,*f1,*f2,*f3,*f4,*f5,*f6,*f7,*f8;
float *g0,*g1,*g2,*g3,*g4,*g5,*g6,*g7,*g8; // função f para o para segundo fluido
float *tmpf0,*tmpf1,*tmpf2,*tmpf3,*tmpf4,*tmpf5,*tmpf6,*tmpf7,*tmpf8;
float *tmpg0,*tmpg1,*tmpg2,*tmpg3,*tmpg4,*tmpg5,*tmpg6,*tmpg7,*tmpg8; // variaveis
temporarias p 2º fluido
float *cmap,*plotvar;
int *solid;
unsigned int *cmap_rgba, *plot_rgba; //rgba arrays for plotting
// scalars //
float tau,faceq1,faceq2,faceq3, tau1; // tau1 (tau fluido 2)
float vxin, roout, vxin1, roout1; // vxin1 e roout1 (fluido 2)
float width, height;
int ni,nj;
int ncol;
int ipos_old,jpos_old, draw_solid_flag;
55
////////////////////////////////////////////////////////////////////////////////
//
// OpenGL function prototypes
//
void display(void);
void resize(int w, int h);
void mouse(int button, int state, int x, int y);
void mouse_motion(int x, int y);
void shutdown(void);
//
// Lattice Boltzmann function prototypes
//
//void display(void);
void stream(void);
void collide(void);
void solid_BC(void);
void per_BC(void);
void in_BC(void);
void ex_BC_crude(void);
void apply_BCs(void);
unsigned int get_col(float min, float max, float val);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char **argv)
{
int array_size_2d,totpoints,i, a;
float rcol,gcol,bcol, u_linha;
FILE *fp_col;
// The following parameters are usually read from a file, but
// hard code them for the demo:
ni=20;
nj=20;
vxin=0.04;
roout=1.0;
tau=0.62;
tau1=0.98; // parametros fluido 2
vxin1=0.08;
roout1=0.824;
// End of parameter list
// Write parameters to screen
56
printf ("ni = %d\n", ni);
printf ("nj = %d\n", nj);
printf ("vxin = %f\n", vxin);
printf ("roout = %f\n", roout);
printf ("tau = %f\n", tau);
printf ("vxin1 = %f\n", vxin1); // print parametros fluido 2
printf ("roout1 = %f\n", roout1);
printf ("tau1 = %f\n", tau1);
totpoints=ni*nj;
array_size_2d=ni*nj*sizeof(float);
// Allocate memory for arrays
f0 = malloc(array_size_2d);
f1 = malloc(array_size_2d);
f2 = malloc(array_size_2d);
f3 = malloc(array_size_2d);
f4 = malloc(array_size_2d);
f5 = malloc(array_size_2d);
f6 = malloc(array_size_2d);
f7 = malloc(array_size_2d);
f8 = malloc(array_size_2d);
if(f0==NULL || f1==NULL || f2==NULL || f3==NULL || f4==NULL || f5==NULL ||
f6==NULL || f7==NULL || f8==NULL){
printf("Nao alocou!");
return 1;
}
tmpf0 = malloc(array_size_2d);
tmpf1 = malloc(array_size_2d);
tmpf2 = malloc(array_size_2d);
tmpf3 = malloc(array_size_2d);
tmpf4 = malloc(array_size_2d);
tmpf5 = malloc(array_size_2d);
tmpf6 = malloc(array_size_2d);
tmpf7 = malloc(array_size_2d);
tmpf8 = malloc(array_size_2d);
if(tmpf0==NULL || tmpf1==NULL || tmpf2==NULL || tmpf3==NULL || tmpf4==NULL ||
tmpf5==NULL || tmpf6==NULL || tmpf7==NULL || tmpf8==NULL){
printf("Nao alocou!");
return 1;
}
g0 = malloc(array_size_2d); //aloca memoria p fluido 2 (var temp abaixo)
g1 = malloc(array_size_2d);
g2 = malloc(array_size_2d);
57
g3 = malloc(array_size_2d);
g4 = malloc(array_size_2d);
g5 = malloc(array_size_2d);
g6 = malloc(array_size_2d);
g7 = malloc(array_size_2d);
g8 = malloc(array_size_2d);
if(g0==NULL || g1==NULL || g2==NULL || g3==NULL || g4==NULL || g5==NULL ||
g6==NULL || g7==NULL || g8==NULL){
printf("Nao alocou!");
return 1;
}
tmpg0 = malloc(array_size_2d);
tmpg1 = malloc(array_size_2d);
tmpg2 = malloc(array_size_2d);
tmpg3 = malloc(array_size_2d);
tmpg4 = malloc(array_size_2d);
tmpg5 = malloc(array_size_2d);
tmpg6 = malloc(array_size_2d);
tmpg7 = malloc(array_size_2d);
tmpg8 = malloc(array_size_2d);
if(tmpg0==NULL || tmpg1==NULL || tmpg2==NULL || tmpg3==NULL || tmpg4==NULL
|| tmpg5==NULL || tmpg6==NULL || tmpg7==NULL || tmpg8==NULL){
printf("Nao alocou!");
return 1;
}
plotvar = malloc(array_size_2d);
plot_rgba = malloc(ni*nj*sizeof(unsigned int));
solid = malloc(ni*nj*sizeof(int));
if(plotvar==NULL || plot_rgba==NULL || solid==NULL) {
printf("Nao alocou!");
return 1;
}
//
// Some factors used to calculate the f_equilibrium values
//
faceq1 = 4.f/9.f;
faceq2 = 1.f/9.f;
faceq3 = 1.f/36.f;
// Cálculo da velocidade composta
u_linha=(1/tau+1/tau1)*(roout*vxin+roout1*vxin1)/(roout/tau+roout1/tau1);
58
//
// Initialise f's by setting them to the f_equilibirum values assuming
// that the whole domain is at velocity vx=vxin vy=0 and density ro=roout
//
for (i=0; i<totpoints; i++) {
f0[i] = faceq1 * roout * (1.f - 1.5f*u_linha*u_linha);
f1[i] = faceq2 * roout * (1.f + 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
f2[i] = faceq2 * roout * (1.f - 1.5f*u_linha*u_linha);
f3[i] = faceq2 * roout * (1.f - 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
f4[i] = faceq2 * roout * (1.f - 1.5f*u_linha*u_linha);
f5[i] = faceq3 * roout * (1.f + 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
f6[i] = faceq3 * roout * (1.f - 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
f7[i] = faceq3 * roout * (1.f - 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
f8[i] = faceq3 * roout * (1.f + 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
plotvar[i] = u_linha;
solid[i] = 1;
g0[i] = faceq1 * roout1 * (1.f - 1.5f*u_linha*u_linha);
g1[i] = faceq2 * roout1 * (1.f + 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
g2[i] = faceq2 * roout1 * (1.f - 1.5f*u_linha*u_linha);
g3[i] = faceq2 * roout1 * (1.f - 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
g4[i] = faceq2 * roout1 * (1.f - 1.5f*u_linha*u_linha);
g5[i] = faceq3 * roout1 * (1.f + 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
g6[i] = faceq3 * roout1 * (1.f - 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
g7[i] = faceq3 * roout1 * (1.f - 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
g8[i] = faceq3 * roout1 * (1.f + 3.f*u_linha + 4.5f*u_linha*u_linha -
1.5f*u_linha*u_linha);
/*printf("f0=%f\tf1=%f\tf2=%f\tf3=%f\tf4=%f\tf5=%f\tf6=%f\tf7=%f\tf8=%f\ng0=%f
\tg1=%f\tg2=%f\tg3=%f\tg4=%f\tg5=%f\tg6=%f\tg7=%f\tg8=%f\nFIM DA MAIN", f0[i],
f1[i], f2[i], f3[i], f4[i], f5[i], f6[i], f7[i], f8[i], g0[i], g1[i], g2[i], g3[i], g4[i], g5[i], g6[i], g7[i],
g8[i]);*/
}
//
// Read in colourmap data for OpenGL display
//
fp_col = fopen("cmap.dat","r");
if (fp_col==NULL) {
59
printf("Error: can't open cmap.dat \n");
return 1;
}
// allocate memory for colourmap (stored as a linear array of int's)
fscanf (fp_col, "%d", &ncol);
cmap_rgba = (unsigned int *)malloc(ncol*sizeof(unsigned int));
// read colourmap and store as int's
for (i=0;i<ncol;i++){
fscanf(fp_col, "%f%f%f", &rcol, &gcol, &bcol);
cmap_rgba[i]=((int)(255.0f) << 24) | // convert colourmap to int
((int)(bcol * 255.0f) << 16) |
((int)(gcol * 255.0f) << 8) |
((int)(rcol * 255.0f) << 0);
}
fclose(fp_col);
//
// Iinitialise OpenGL display - use glut
//
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(ni, nj); // Window of ni x nj pixels
glutInitWindowPosition(50, 50); // position
glutCreateWindow("2D LB"); // title
// Check for OpenGL extension support
printf("Loading extensions: %s\n", glewGetErrorString(glewInit()));
if(!glewIsSupported(
"GL_VERSION_2_0 "
"GL_ARB_pixel_buffer_object "
"GL_EXT_framebuffer_object "
)){
fprintf(stderr, "ERROR: Support for necessary OpenGL extensions missing.");
fflush(stderr);
return;
}
// Set up view
glClearColor(0.0, 0.0, 0.0, 0.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,ni,0.,nj, -200.0, 200.0);
// Create texture which we use to display the result and bind to gl_Tex
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &gl_Tex); // Generate 2D texture
glBindTexture(GL_TEXTURE_2D, gl_Tex); // bind to gl_Tex
// texture properties:
60
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, ni, nj, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
// Create pixel buffer object and bind to gl_PBO. We store the data we want to
// plot in memory on the graphics card - in a "pixel buffer". We can then
// copy this to the texture defined above and send it to the screen
glGenBuffers(1, &gl_PBO);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, gl_PBO);
printf("Buffer created.\n");
// Set the call-back functions and start the glut loop
printf("Starting GLUT main loop...\n");
glutDisplayFunc(display);
glutReshapeFunc(resize);
glutIdleFunc(display);
glutMouseFunc(mouse);
glutMotionFunc(mouse_motion);
glutMainLoop();
/* while(1){
display();
printf("OI");
}*/
free(f0);
free(f1);
free(f2);
free(f3);
free(f4);
free(f5);
free(f6);
free(f7);
free(f8);
free(tmpf0);
free(tmpf1);
free(tmpf2);
free(tmpf3);
free(tmpf4);
free(tmpf5);
free(tmpf6);
free(tmpf7);
free(tmpf8);
61
free(g0);
free(g1);
free(g2);
free(g3);
free(g4);
free(g5);
free(g6);
free(g7);
free(g8);
free(tmpg0);
free(tmpg1);
free(tmpg2);
free(tmpg3);
free(tmpg4);
free(tmpg5);
free(tmpg6);
free(tmpg7);
free(tmpg8);
free(plotvar);
free(plot_rgba);
free(solid);
free(cmap_rgba);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void stream(void)
// Move the f values one grid spacing in the directions that they are pointing
// i.e. f1 is copied one location to the right, etc.
{
int i,j,im1,ip1,jm1,jp1,i0;
// Initially the f's are moved to temporary arrays
for (j=0; j<nj; j++) {
jm1=j-1;
jp1=j+1;
if (j==0) jm1=0;
if (j==(nj-1)) jp1=nj-1;
for (i=0; i<ni; i++) {
i0 = I2D(ni,i,j);
im1 = i-1;
ip1 = i+1;
if (i==0) im1=0;
62
if (i==(ni-1)) ip1=ni-1;
tmpf1[i0] = f1[I2D(ni,im1,j)];
tmpf2[i0] = f2[I2D(ni,i,jm1)];
tmpf3[i0] = f3[I2D(ni,ip1,j)];
tmpf4[i0] = f4[I2D(ni,i,jp1)];
tmpf5[i0] = f5[I2D(ni,im1,jm1)];
tmpf6[i0] = f6[I2D(ni,ip1,jm1)];
tmpf7[i0] = f7[I2D(ni,ip1,jp1)];
tmpf8[i0] = f8[I2D(ni,im1,jp1)];
tmpg1[i0] = g1[I2D(ni,im1,j)];
tmpg2[i0] = g2[I2D(ni,i,jm1)];
tmpg3[i0] = g3[I2D(ni,ip1,j)];
tmpg4[i0] = g4[I2D(ni,i,jp1)];
tmpg5[i0] = g5[I2D(ni,im1,jm1)];
tmpg6[i0] = g6[I2D(ni,ip1,jm1)];
tmpg7[i0] = g7[I2D(ni,ip1,jp1)];
tmpg8[i0] = g8[I2D(ni,im1,jp1)];
}
}
// Now the temporary arrays are copied to the main f arrays
for (j=0; j<nj; j++) {
for (i=1; i<ni; i++) {
i0 = I2D(ni,i,j);
f1[i0] = tmpf1[i0];
f2[i0] = tmpf2[i0];
f3[i0] = tmpf3[i0];
f4[i0] = tmpf4[i0];
f5[i0] = tmpf5[i0];
f6[i0] = tmpf6[i0];
f7[i0] = tmpf7[i0];
f8[i0] = tmpf8[i0];
g1[i0] = tmpg1[i0];
g2[i0] = tmpg2[i0];
g3[i0] = tmpg3[i0];
g4[i0] = tmpg4[i0];
g5[i0] = tmpg5[i0];
g6[i0] = tmpg6[i0];
g7[i0] = tmpg7[i0];
g8[i0] = tmpg8[i0];
}
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
63
void collide(void)
// Collisions between the particles are modeled here. We use the very simplest
// model which assumes the f's change toward the local equlibrium value (based
// on density and velocity at that point) over a fixed timescale, tau
{
int i,j,i0;
float ro, rovx, rovy, vx, vy, v_sq_term;
float ro1, rovx1, rovy1, vx1, vy1, v_sq_term1, den_x, den_y;
float f0eq, f1eq, f2eq, f3eq, f4eq, f5eq, f6eq, f7eq, f8eq;
float g0eq, g1eq, g2eq, g3eq, g4eq, g5eq, g6eq, g7eq, g8eq;
float rtau, rtau1;
float rtau2, rtau3, ulinha_x, ulinha_y, ulinha_2;
// Some useful constants
rtau = 1.f/tau;
rtau1 = 1.f - rtau;
rtau2 = 1.f/tau1;
rtau3 = 1.f - rtau2;
for (j=0; j<nj; j++) {
for (i=0; i<ni; i++) {
i0 = I2D(ni,i,j);
// Do the summations needed to evaluate the density and components of velocity
ro = f0[i0] + f1[i0] + f2[i0] + f3[i0] + f4[i0] + f5[i0] + f6[i0] + f7[i0] + f8[i0];
rovx = f1[i0] - f3[i0] + f5[i0] - f6[i0] - f7[i0] + f8[i0];
rovy = f2[i0] - f4[i0] + f5[i0] + f6[i0] - f7[i0] - f8[i0];
if(ro==0){
vx = 0;
vy = 0;
}else{
vx = rovx/ro;
vy = rovy/ro;
}
ro1 = g0[i0] + g1[i0] + g2[i0] + g3[i0] + g4[i0] + g5[i0] + g6[i0] + g7[i0] + g8[i0];
rovx1 = g1[i0] - g3[i0] + g5[i0] - g6[i0] - g7[i0] + g8[i0];
rovy1 = g2[i0] - g4[i0] + g5[i0] + g6[i0] - g7[i0] - g8[i0];
if(ro1==0){
vx1 = 0;
vy1 = 0;
}else{
vx1 = rovx1/ro1;
vy1 = rovy1/ro1;
}
64
den_x=(ro/tau + ro1/tau1) * (vx*ro + vx1*ro1);
if(den_x==0){
ulinha_x=0;
}else{
ulinha_x = (1/tau + 1/tau1) / den_x;
}
den_y=(ro/tau + ro1/tau1) * (vy*ro + vy1*ro1);
if(den_y==0){
ulinha_y=0;
}else{
ulinha_y = (1/tau + 1/tau1) / den_y;
}
ulinha_2 = ulinha_x * ulinha_x + ulinha_y * ulinha_y;
if(ulinha_2>0.1){
ulinha_2=0.1;
}
//v_sq_term = 1.5f*(vx*vx + vy*vy);
// Also load the velocity magnitude into plotvar - this is what we will
// display using OpenGL later
plotvar[i0] = sqrt(ulinha_2);
printf("ro=%f\n", ro);
/*printf("f0=%f\tf1=%f\tf2=%f\tf3=%f\tf4=%f\tf5=%f\tf6=%f\tf7=%f\tf8=%f\ng0=%f\tg1=%
f\tg2=%f\tg3=%f\tg4=%f\tg5=%f\tg6=%f\tg7=%f\tg8=%f\n", f0[i0], f1[i0], f2[i0], f3[i0],
f4[i0], f5[i0], f6[i0], f7[i0], f8[i0], g0[i0], g1[i0], g2[i0], g3[i0], g4[i0], g5[i0], g6[i0], g7[i0],
g8[i0]);*/
// Evaluate the local equilibrium f values in all directions
f0eq = ro * faceq1 * (1.f
- 1.5f*ulinha_2);
f1eq = ro * faceq2 * (1.f + 3.f*ulinha_x + 4.5f*ulinha_x*ulinha_x
- 1.5f*ulinha_2);
f2eq = ro * faceq2 * (1.f + 3.f*ulinha_y + 4.5f*ulinha_y*ulinha_y
- 1.5f*ulinha_2);
f3eq = ro * faceq2 * (1.f - 3.f*ulinha_x + 4.5f*ulinha_x*ulinha_x
- 1.5f*ulinha_2);
f4eq = ro * faceq2 * (1.f - 3.f*ulinha_y + 4.5f*ulinha_y*ulinha_y
- 1.5f*ulinha_2);
f5eq = ro * faceq3 * (1.f + 3.f*(ulinha_x + ulinha_y) + 4.5f*(ulinha_x +
ulinha_y)*(ulinha_x + ulinha_y) - 1.5f*ulinha_2);
f6eq = ro * faceq3 * (1.f + 3.f*(-ulinha_x + ulinha_y) + 4.5f*(-ulinha_x +
ulinha_y)*(-ulinha_x + ulinha_y) - 1.5f*ulinha_2);
f7eq = ro * faceq3 * (1.f + 3.f*(-ulinha_x - ulinha_y) + 4.5f*(-ulinha_x -
ulinha_y)*(-ulinha_x - ulinha_y) - 1.5f*ulinha_2);
65
f8eq = ro * faceq3 * (1.f + 3.f*(ulinha_x - ulinha_y) + 4.5f*(ulinha_x -
ulinha_y)*(ulinha_x - ulinha_y) - 1.5f*ulinha_2);
g0eq = ro1 * faceq1 * (1.f
- 1.5f*ulinha_2);
g1eq = ro1 * faceq2 * (1.f + 3.f*ulinha_x +
4.5f*ulinha_x*ulinha_x - 1.5f*ulinha_2);
g2eq = ro1 * faceq2 * (1.f + 3.f*ulinha_y +
4.5f*ulinha_y*ulinha_y - 1.5f*ulinha_2);
g3eq = ro1 * faceq2 * (1.f - 3.f*ulinha_x + 4.5f*ulinha_x*ulinha_x
- 1.5f*ulinha_2);
g4eq = ro1 * faceq2 * (1.f - 3.f*ulinha_y + 4.5f*ulinha_y*ulinha_y
- 1.5f*ulinha_2);
g5eq = ro1 * faceq3 * (1.f + 3.f*(ulinha_x + ulinha_y) + 4.5f*(ulinha_x +
ulinha_y)*(ulinha_x + ulinha_y) - 1.5f*ulinha_2);
g6eq = ro1 * faceq3 * (1.f + 3.f*(-ulinha_x + ulinha_y) + 4.5f*(-ulinha_x +
ulinha_y)*(-ulinha_x + ulinha_y) - 1.5f*ulinha_2);
g7eq = ro1 * faceq3 * (1.f + 3.f*(-ulinha_x - ulinha_y) + 4.5f*(-ulinha_x -
ulinha_y)*(-ulinha_x - ulinha_y) - 1.5f*ulinha_2);
g8eq = ro1 * faceq3 * (1.f + 3.f*(ulinha_x - ulinha_y) + 4.5f*(ulinha_x -
ulinha_y)*(ulinha_x - ulinha_y) - 1.5f*ulinha_2);
// Simulate collisions by "relaxing" toward the local equilibrium
f0[i0] = rtau1 * f0[i0] + rtau * f0eq;
f1[i0] = rtau1 * f1[i0] + rtau * f1eq;
f2[i0] = rtau1 * f2[i0] + rtau * f2eq;
f3[i0] = rtau1 * f3[i0] + rtau * f3eq;
f4[i0] = rtau1 * f4[i0] + rtau * f4eq;
f5[i0] = rtau1 * f5[i0] + rtau * f5eq;
f6[i0] = rtau1 * f6[i0] + rtau * f6eq;
f7[i0] = rtau1 * f7[i0] + rtau * f7eq;
f8[i0] = rtau1 * f8[i0] + rtau * f8eq;
g0[i0] = rtau3 * g0[i0] + rtau2 * g0eq;
g1[i0] = rtau3 * g1[i0] + rtau2 * g1eq;
g2[i0] = rtau3 * g2[i0] + rtau2 * g2eq;
g3[i0] = rtau3 * g3[i0] + rtau2 * g3eq;
g4[i0] = rtau3 * g4[i0] + rtau2 * g4eq;
g5[i0] = rtau3 * g5[i0] + rtau2 * g5eq;
g6[i0] = rtau3 * g6[i0] + rtau2 * g6eq;
g7[i0] = rtau3 * g7[i0] + rtau2 * g7eq;
g8[i0] = rtau3 * g8[i0] + rtau2 * g8eq;
}
}
//printf("fim");
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
66
void solid_BC(void)
// This is the boundary condition for a solid node. All the f's are reversed -
// this is known as "bounce-back"
{
int i,j,i0;
float f1old,f2old,f3old,f4old,f5old,f6old,f7old,f8old;
float g1old,g2old,g3old,g4old,g5old,g6old,g7old,g8old;
for (j=0;j<nj;j++){
for (i=0;i<ni;i++){
i0=I2D(ni,i,j);
if (solid[i0]==0) {
f1old = f1[i0];
f2old = f2[i0];
f3old = f3[i0];
f4old = f4[i0];
f5old = f5[i0];
f6old = f6[i0];
f7old = f7[i0];
f8old = f8[i0];
f1[i0] = f3old;
f2[i0] = f4old;
f3[i0] = f1old;
f4[i0] = f2old;
f5[i0] = f7old;
f6[i0] = f8old;
f7[i0] = f5old;
f8[i0] = f6old;
g1old = g1[i0];
g2old = g2[i0];
g3old = g3[i0];
g4old = g4[i0];
g5old = g5[i0];
g6old = g6[i0];
g7old = g7[i0];
g8old = g8[i0];
g1[i0] = g3old;
g2[i0] = g4old;
g3[i0] = g1old;
g4[i0] = g2old;
g5[i0] = g7old;
g6[i0] = g8old;
g7[i0] = g5old;
g8[i0] = g6old;
}
67
}
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void per_BC(void)
// All the f's leaving the bottom of the domain (j=0) enter at the top (j=nj-1),
// and vice-versa
{
int i0,i1,i;
for (i=0; i<ni; i++){
i0 = I2D(ni,i,0);
i1 = I2D(ni,i,nj-1);
f2[i0] = f2[i1];
f5[i0] = f5[i1];
f6[i0] = f6[i1];
f4[i1] = f4[i0];
f7[i1] = f7[i0];
f8[i1] = f8[i0];
g2[i0] = g2[i1];
g5[i0] = g5[i1];
g6[i0] = g6[i1];
g4[i1] = g4[i0];
g7[i1] = g7[i0];
g8[i1] = g8[i0];
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void in_BC(void)
// This inlet BC is extremely crude but is very stable
// We set the incoming f values to the equilibirum values assuming:
// ro=roout; vx=vxin; vy=0
{
int i0, j;
float f1new, f5new, f8new, vx_term;
float g1new, g5new, g8new, vx_termg;
vx_term = 1.f + 3.f*vxin + 3.f*vxin*vxin;
f1new = roout * faceq2 * vx_term;
68
f5new = roout * faceq3 * vx_term;
f8new = f5new;
vx_termg = 1.f + 3.f*vxin1 + 3.f*vxin1*vxin1;
g1new = roout1 * faceq2 * vx_termg;
g5new = roout1 * faceq3 * vx_termg;
g8new = g5new;
for (j=0; j<nj; j++){
i0 = I2D(ni,0,j);
f1[i0] = f1new;
f5[i0] = f5new;
f8[i0] = f8new;
g1[i0] = g1new;
g5[i0] = g5new;
g8[i0] = g8new;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void ex_BC_crude(void)
// This is the very simplest (and crudest) exit BC. All the f values pointing
// into the domain at the exit (ni-1) are set equal to those one node into
// the domain (ni-2)
{
int i0, i1, j;
for (j=0; j<nj; j++){
i0 = I2D(ni,ni-1,j);
i1 = i0 - 1;
f3[i0] = f3[i1];
f6[i0] = f6[i1];
f7[i0] = f7[i1];
g3[i0] = g3[i1];
g6[i0] = g6[i1];
g7[i0] = g7[i1];
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void apply_BCs(void)
69
// Just calls the individual BC functions
{
per_BC();
solid_BC();
in_BC();
ex_BC_crude();
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void display(void)
// This function is called automatically, over and over again, by GLUT
{
int i,j,ip1,jp1,i0,icol,i1,i2,i3,i4,isol;
float minvar,maxvar,frac;
// set upper and lower limits for plotting
minvar=0.0;
maxvar=0.2;
// do one Lattice Boltzmann step: stream, BC, collide:
stream();
apply_BCs();
collide();
// convert the plotvar array into an array of colors to plot
// if the mesh point is solid, make it black
for (j=0;j<nj;j++){
for (i=0;i<ni;i++){
i0=I2D(ni,i,j);
frac=(plotvar[i0]-minvar)/(maxvar-minvar);
icol=frac*ncol;
isol=(int)solid[i0];
plot_rgba[i0] = isol*cmap_rgba[icol];
}
}
// Fill the pixel buffer with the plot_rgba array
glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB,ni*nj*sizeof(unsigned int),
(void **)plot_rgba,GL_STREAM_COPY);
// Copy the pixel buffer to the texture, ready to display
70
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,ni,nj,GL_RGBA,GL_UNSIGNED_BYTE,0);
// Render one quad to the screen and colour it using our texture
// i.e. plot our plotvar data to the screen
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glTexCoord2f (0.0, 0.0);
glVertex3f (0.0, 0.0, 0.0);
glTexCoord2f (1.0, 0.0);
glVertex3f (ni, 0.0, 0.0);
glTexCoord2f (1.0, 1.0);
glVertex3f (ni, nj, 0.0);
glTexCoord2f (0.0, 1.0);
glVertex3f (0.0, nj, 0.0);
glEnd();
glutSwapBuffers();
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void resize(int w, int h)
// GLUT resize callback to allow us to change the window size
{
width = w;
height = h;
glViewport (0, 0, w, h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (0., ni, 0., nj, -200. ,200.);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void mouse(int button, int state, int x, int y)
// GLUT mouse callback. Left button draws the solid, right button removes solid
{
float xx,yy;
if ((button == GLUT_LEFT_BUTTON) && (state == GLUT_DOWN)) {
71
draw_solid_flag = 0;
xx=x;
yy=y;
ipos_old=xx/width*ni;
jpos_old=(height-yy)/height*nj;
}
if ((button == GLUT_RIGHT_BUTTON) && (state == GLUT_DOWN)) {
draw_solid_flag = 1;
xx=x;
yy=y;
ipos_old=xx/width*ni;
jpos_old=(height-yy)/height*nj;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void mouse_motion(int x, int y)
// GLUT call back for when the mouse is moving
// This sets the solid array to draw_solid_flag as set in the mouse callback
// It will draw a staircase line if we move more than one pixel since the
// last callback - that makes the coding a bit cumbersome:
{
float xx,yy,frac;
int ipos,jpos,i,j,i1,i2,j1,j2, jlast, jnext;
xx=x;
yy=y;
ipos=(int)(xx/width*(float)ni);
jpos=(int)((height-yy)/height*(float)nj);
if (ipos <= ipos_old){
i1 = ipos;
i2 = ipos_old;
j1 = jpos;
j2 = jpos_old;
}
else {
i1 = ipos_old;
i2 = ipos;
j1 = jpos_old;
j2 = jpos;
}
jlast=j1;
for (i=i1;i<=i2;i++){
72
if (i1 != i2) {
frac=(float)(i-i1)/(float)(i2-i1);
jnext=(int)(frac*(j2-j1))+j1;
}
else {
jnext=j2;
}
if (jnext >= jlast) {
solid[I2D(ni,i,jlast)]=draw_solid_flag;
for (j=jlast; j<=jnext; j++){
solid[I2D(ni,i,j)]=draw_solid_flag;
}
}
else {
solid[I2D(ni,i,jlast)]=draw_solid_flag;
for (j=jnext; j<=jlast; j++){
solid[I2D(ni,i,j)]=draw_solid_flag;
}
}
jlast = jnext;
}
ipos_old=ipos;
jpos_old=jpos;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////