Skip to content
Snippets Groups Projects
Commit a991402e authored by Michael Kuron's avatar Michael Kuron :mortar_board:
Browse files

Merge branch 'tutorial_fixes' into 'master'

Shan-Chen tutorial fixes

See merge request pycodegen/lbmpy!99
parents 13df23c8 15645ade
No related merge requests found
This diff is collapsed.
%% Cell type:markdown id: tags:
# Shan-Chen Two-Component Lattice Boltzmann
%% Cell type:code id: tags:
``` python
from lbmpy.session import *
from lbmpy.updatekernels import create_stream_pull_with_output_kernel
from lbmpy.macroscopic_value_kernels import macroscopic_values_getter, macroscopic_values_setter
from lbmpy.maxwellian_equilibrium import get_weights
```
%% Cell type:markdown id: tags:
This is based on section 9.3.3 of Krüger et al.'s "The Lattice Boltzmann Method", Springer 2017 (http://www.lbmbook.com).
Sample code is available at [https://github.com/lbm-principles-practice/code/](https://github.com/lbm-principles-practice/code/blob/master/chapter9/shanchen.cpp).
%% Cell type:markdown id: tags:
## Parameters
%% Cell type:code id: tags:
``` python
N = 64 # domain size
omega_a = 1. # relaxation rate of first component
omega_b = 1. # relaxation rate of second component
# interaction strength
g_aa = 0.
g_ab = g_ba = 6.
g_bb = 0.
rho0 = 1.
stencil = LBStencil(Stencil.D2Q9)
weights = get_weights(stencil, c_s_sq=sp.Rational(1, 3))
```
%% Cell type:markdown id: tags:
## Data structures
We allocate two sets of PDF's, one for each phase. Additionally, for each phase there is one field to store its density and velocity.
To run the simulation on GPU, change the `default_target` to gpu
%% Cell type:code id: tags:
``` python
dim = stencil.D
dh = ps.create_data_handling((N, ) * dim, periodicity=True, default_target=ps.Target.CPU)
src_a = dh.add_array('src_a', values_per_cell=stencil.Q)
dst_a = dh.add_array_like('dst_a', 'src_a')
src_b = dh.add_array('src_b', values_per_cell=stencil.Q)
dst_b = dh.add_array_like('dst_b', 'src_b')
ρ_a = dh.add_array('rho_a')
ρ_b = dh.add_array('rho_b')
u_a = dh.add_array('u_a', values_per_cell=stencil.D)
u_b = dh.add_array('u_b', values_per_cell=stencil.D)
u_bary = dh.add_array_like('u_bary', u_a.name)
f_a = dh.add_array('f_a', values_per_cell=stencil.D)
f_b = dh.add_array_like('f_b', f_a.name)
```
%% Cell type:markdown id: tags:
## Force & combined velocity
The two LB methods are coupled using a force term. Its symbolic representation is created in the next cells.
The force value is not written to a field, but directly evaluated inside the collision kernel.
%% Cell type:markdown id: tags:
The force between the two components is
$\mathbf{F}_k(\mathbf{x})=-\psi(\rho_k(\mathbf{x}))\sum\limits_{k^\prime\in\{A,B\}}g_{kk^\prime}\sum\limits_{i=1}^{q}w_i\psi(\rho_{k^\prime}(\mathbf{x}+\mathbf{c}_i))\mathbf{c}_i$
for $k\in\{A,B\}$
and with
$\psi(\rho)=\rho_0\left[1-\exp(-\rho/\rho_0)\right]$.
%% Cell type:code id: tags:
``` python
def psi(dens):
return rho0 * (1. - sp.exp(-dens / rho0));
```
%% Cell type:code id: tags:
``` python
zero_vec = sp.Matrix([0] * dh.dim)
force_a = zero_vec
for factor, ρ in zip([g_aa, g_ab], [ρ_a, ρ_b]):
force_a += sum((psi(ρ[d]) * w_d * sp.Matrix(d)
for d, w_d in zip(stencil, weights)),
zero_vec) * psi(ρ_a.center) * -1 * factor
force_b = zero_vec
for factor, ρ in zip([g_ba, g_bb], [ρ_a, ρ_b]):
force_b += sum((psi(ρ[d]) * w_d * sp.Matrix(d)
for d, w_d in zip(stencil, weights)),
zero_vec) * psi(ρ_b.center) * -1 * factor
f_expressions = ps.AssignmentCollection([
ps.Assignment(f_a.center_vector, force_a),
ps.Assignment(f_b.center_vector, force_b)
])
```
%% Cell type:markdown id: tags:
The barycentric velocity, which is used in place of the individual components' velocities in the equilibrium distribution and Guo force term, is
$\vec{u}=\frac{1}{\rho_a+\rho_b}\left(\rho_a\vec{u}_a+\frac{1}{2}\vec{F}_a+\rho_b\vec{u}_b+\frac{1}{2}\vec{F}_b\right)$.
%% Cell type:code id: tags:
``` python
u_full = ps.Assignment(u_bary.center_vector,
(ρ_a.center * u_a.center_vector + ρ_b.center * u_b.center_vector) / (ρ_a.center + ρ_b.center))
```
%% Cell type:markdown id: tags:
## Kernels
%% Cell type:code id: tags:
``` python
lbm_config_a = LBMConfig(stencil=stencil, relaxation_rate=omega_a, compressible=True,
velocity_input=u_bary, density_input=ρ_a, force_model=ForceModel.GUO,
force=f_a, kernel_type='collide_only')
lbm_config_b = LBMConfig(stencil=stencil, relaxation_rate=omega_b, compressible=True,
velocity_input=u_bary, density_input=ρ_b, force_model=ForceModel.GUO,
force=f_b, kernel_type='collide_only')
collision_a = create_lb_update_rule(lbm_config=lbm_config_a,
optimization={'symbolic_field': src_a})
collision_b = create_lb_update_rule(lbm_config=lbm_config_b,
optimization={'symbolic_field': src_b})
```
%% Cell type:code id: tags:
``` python
stream_a = create_stream_pull_with_output_kernel(collision_a.method, src_a, dst_a,
{'density': ρ_a, 'velocity': u_a})
stream_b = create_stream_pull_with_output_kernel(collision_b.method, src_b, dst_b,
{'density': ρ_b, 'velocity': u_b})
config = ps.CreateKernelConfig(target=dh.default_target)
stream_a_kernel = ps.create_kernel(stream_a, config=config).compile()
stream_b_kernel = ps.create_kernel(stream_b, config=config).compile()
collision_a_kernel = ps.create_kernel(collision_a, config=config).compile()
collision_b_kernel = ps.create_kernel(collision_b, config=config).compile()
force_kernel = ps.create_kernel(f_expressions, config=config).compile()
u_kernel = ps.create_kernel(u_full, config=config).compile()
```
%% Cell type:markdown id: tags:
## Initialization
%% Cell type:code id: tags:
``` python
init_a = macroscopic_values_setter(collision_a.method, velocity=(0, 0),
pdfs=src_a.center_vector, density=ρ_a.center)
init_b = macroscopic_values_setter(collision_b.method, velocity=(0, 0),
pdfs=src_b.center_vector, density=ρ_b.center)
init_a_kernel = ps.create_kernel(init_a, ghost_layers=0).compile()
init_b_kernel = ps.create_kernel(init_b, ghost_layers=0).compile()
dh.run_kernel(init_a_kernel)
dh.run_kernel(init_b_kernel)
```
%% Cell type:code id: tags:
``` python
def init():
dh.fill(ρ_a.name, 0.1, slice_obj=ps.make_slice[:, :0.5])
dh.fill(ρ_a.name, 0.9, slice_obj=ps.make_slice[:, 0.5:])
dh.fill(ρ_b.name, 0.9, slice_obj=ps.make_slice[:, :0.5])
dh.fill(ρ_b.name, 0.1, slice_obj=ps.make_slice[:, 0.5:])
dh.fill(f_a.name, 0.0)
dh.fill(f_b.name, 0.0)
dh.run_kernel(init_a_kernel)
dh.run_kernel(init_b_kernel)
dh.fill(u_a.name, 0.0)
dh.fill(u_b.name, 0.0)
```
%% Cell type:markdown id: tags:
## Timeloop
%% Cell type:code id: tags:
``` python
sync_pdfs = dh.synchronization_function([src_a.name, src_b.name])
sync_ρs = dh.synchronization_function([ρ_a.name, ρ_b.name])
def time_loop(steps):
dh.all_to_gpu()
for i in range(steps):
sync_ρs() # force values depend on neighboring ρ's
dh.run_kernel(force_kernel)
dh.run_kernel(u_kernel)
dh.run_kernel(collision_a_kernel)
dh.run_kernel(collision_b_kernel)
sync_pdfs()
dh.run_kernel(stream_a_kernel)
dh.run_kernel(stream_b_kernel)
dh.swap(src_a.name, dst_a.name)
dh.swap(src_b.name, dst_b.name)
dh.all_to_cpu()
```
%% Cell type:code id: tags:
``` python
def plot_ρs():
plt.figure(dpi=200)
plt.subplot(1,2,1)
plt.title("$\\rho_A$")
plt.scalar_field(dh.gather_array(ρ_a.name), vmin=0, vmax=2)
plt.colorbar()
plt.subplot(1,2,2)
plt.title("$\\rho_B$")
plt.scalar_field(dh.gather_array(ρ_b.name), vmin=0, vmax=2)
plt.colorbar()
```
%% Cell type:markdown id: tags:
## Run the simulation
### Initial state
%% Cell type:code id: tags:
``` python
init()
plot_ρs()
```
%% Output
%% Cell type:markdown id: tags:
### Run the simulation until converged
%% Cell type:code id: tags:
``` python
init()
time_loop(10000)
plot_ρs()
```
%% Output
%% Cell type:code id: tags:
``` python
assert np.isfinite(dh.gather_array(ρ_a.name)).all()
assert np.isfinite(dh.gather_array(ρ_b.name)).all()
```
......
"""
Test Shan-Chen two-component implementation against reference implementation
"""
import lbmpy
import pystencils as ps
import sympy as sp
import numpy as np
def test_shan_chen_two_component():
from lbmpy.enums import Stencil
from lbmpy import LBMConfig, ForceModel, create_lb_update_rule
from lbmpy.macroscopic_value_kernels import macroscopic_values_setter
from lbmpy.creationfunctions import create_stream_pull_with_output_kernel
from lbmpy.maxwellian_equilibrium import get_weights
N = 64
omega_a = 1.
omega_b = 1.
# interaction strength
g_aa = 0.
g_ab = g_ba = 6.
g_bb = 0.
rho0 = 1.
stencil = lbmpy.LBStencil(Stencil.D2Q9)
weights = get_weights(stencil, c_s_sq=sp.Rational(1, 3))
dim = stencil.D
dh = ps.create_data_handling((N, ) * dim, periodicity=True, default_target=ps.Target.CPU)
src_a = dh.add_array('src_a', values_per_cell=stencil.Q)
dst_a = dh.add_array_like('dst_a', 'src_a')
src_b = dh.add_array('src_b', values_per_cell=stencil.Q)
dst_b = dh.add_array_like('dst_b', 'src_b')
ρ_a = dh.add_array('rho_a')
ρ_b = dh.add_array('rho_b')
u_a = dh.add_array('u_a', values_per_cell=stencil.D)
u_b = dh.add_array('u_b', values_per_cell=stencil.D)
u_bary = dh.add_array_like('u_bary', u_a.name)
f_a = dh.add_array('f_a', values_per_cell=stencil.D)
f_b = dh.add_array_like('f_b', f_a.name)
def psi(dens):
return rho0 * (1. - sp.exp(-dens / rho0))
zero_vec = sp.Matrix([0] * stencil.D)
force_a = zero_vec
for factor, ρ in zip([g_aa, g_ab], [ρ_a, ρ_b]):
force_a += sum((psi(ρ[d]) * w_d * sp.Matrix(d)
for d, w_d in zip(stencil, weights)),
zero_vec) * psi(ρ_a.center) * -1 * factor
force_b = zero_vec
for factor, ρ in zip([g_ba, g_bb], [ρ_a, ρ_b]):
force_b += sum((psi(ρ[d]) * w_d * sp.Matrix(d)
for d, w_d in zip(stencil, weights)),
zero_vec) * psi(ρ_b.center) * -1 * factor
f_expressions = ps.AssignmentCollection([
ps.Assignment(f_a.center_vector, force_a),
ps.Assignment(f_b.center_vector, force_b)
])
# calculate the velocity without force correction
u_temp = ps.Assignment(u_bary.center_vector,
(ρ_a.center * u_a.center_vector
- f_a.center_vector / 2 + ρ_b.center * u_b.center_vector
- f_b.center_vector / 2) / (ρ_a.center + ρ_b.center))
# add the force correction to the velocity
u_corr = ps.Assignment(u_bary.center_vector,
u_bary.center_vector
+ (f_a.center_vector / 2 + f_b.center_vector / 2) / (ρ_a.center + ρ_b.center))
lbm_config_a = LBMConfig(stencil=stencil, relaxation_rate=omega_a, compressible=True,
velocity_input=u_bary, density_input=ρ_a, force_model=ForceModel.GUO,
force=f_a, kernel_type='collide_only')
lbm_config_b = LBMConfig(stencil=stencil, relaxation_rate=omega_b, compressible=True,
velocity_input=u_bary, density_input=ρ_b, force_model=ForceModel.GUO,
force=f_b, kernel_type='collide_only')
collision_a = create_lb_update_rule(lbm_config=lbm_config_a,
optimization={'symbolic_field': src_a})
collision_b = create_lb_update_rule(lbm_config=lbm_config_b,
optimization={'symbolic_field': src_b})
stream_a = create_stream_pull_with_output_kernel(collision_a.method, src_a, dst_a,
{'density': ρ_a, 'velocity': u_a})
stream_b = create_stream_pull_with_output_kernel(collision_b.method, src_b, dst_b,
{'density': ρ_b, 'velocity': u_b})
config = ps.CreateKernelConfig(target=dh.default_target)
stream_a_kernel = ps.create_kernel(stream_a, config=config).compile()
stream_b_kernel = ps.create_kernel(stream_b, config=config).compile()
collision_a_kernel = ps.create_kernel(collision_a, config=config).compile()
collision_b_kernel = ps.create_kernel(collision_b, config=config).compile()
force_kernel = ps.create_kernel(f_expressions, config=config).compile()
u_temp_kernel = ps.create_kernel(u_temp, config=config).compile()
u_corr_kernel = ps.create_kernel(u_corr, config=config).compile()
init_a = macroscopic_values_setter(collision_a.method, velocity=(0, 0),
pdfs=src_a.center_vector, density=ρ_a.center)
init_b = macroscopic_values_setter(collision_b.method, velocity=(0, 0),
pdfs=src_b.center_vector, density=ρ_b.center)
init_a_kernel = ps.create_kernel(init_a, ghost_layers=0).compile()
init_b_kernel = ps.create_kernel(init_b, ghost_layers=0).compile()
sync_pdfs = dh.synchronization_function([src_a.name, src_b.name])
sync_ρs = dh.synchronization_function([ρ_a.name, ρ_b.name])
dh.fill(ρ_a.name, 0.1, slice_obj=ps.make_slice[:, :0.5])
dh.fill(ρ_a.name, 0.9, slice_obj=ps.make_slice[:, 0.5:])
dh.fill(ρ_b.name, 0.9, slice_obj=ps.make_slice[:, :0.5])
dh.fill(ρ_b.name, 0.1, slice_obj=ps.make_slice[:, 0.5:])
dh.fill(u_a.name, 0.0)
dh.fill(u_b.name, 0.0)
dh.fill(f_a.name, 0.0)
dh.fill(f_b.name, 0.0)
dh.run_kernel(u_temp_kernel)
dh.run_kernel(init_a_kernel)
dh.run_kernel(init_b_kernel)
for i in range(1000):
sync_ρs()
dh.run_kernel(force_kernel)
dh.run_kernel(u_corr_kernel)
dh.run_kernel(collision_a_kernel)
dh.run_kernel(collision_b_kernel)
sync_pdfs()
dh.run_kernel(stream_a_kernel)
dh.run_kernel(stream_b_kernel)
dh.run_kernel(u_temp_kernel)
dh.swap(src_a.name, dst_a.name)
dh.swap(src_b.name, dst_b.name)
# reference generated from https://github.com/lbm-principles-practice/code/blob/master/chapter9/shanchen.cpp with
# const int nsteps = 1000;
# const int noutput = 1000;
# const int nfluids = 2;
# const double gA = 0;
ref_a = np.array([0.213948, 0.0816724, 0.0516763, 0.0470179, 0.0480882, 0.0504771, 0.0531983, 0.0560094, 0.0588071,
0.0615311, 0.064102, 0.0664467, 0.0684708, 0.070091, 0.0712222, 0.0718055, 0.0718055, 0.0712222,
0.070091, 0.0684708, 0.0664467, 0.064102, 0.0615311, 0.0588071, 0.0560094, 0.0531983, 0.0504771,
0.0480882, 0.0470179, 0.0516763, 0.0816724, 0.213948, 0.517153, 0.833334, 0.982884, 1.0151,
1.01361, 1.0043, 0.993178, 0.981793, 0.970546, 0.959798, 0.949751, 0.940746, 0.933035, 0.926947,
0.922713, 0.920548, 0.920548, 0.922713, 0.926947, 0.933035, 0.940746, 0.949751, 0.959798,
0.970546, 0.981793, 0.993178, 1.0043, 1.01361, 1.0151, 0.982884, 0.833334, 0.517153])
ref_b = np.array([0.517153, 0.833334, 0.982884, 1.0151, 1.01361, 1.0043, 0.993178, 0.981793, 0.970546, 0.959798,
0.949751, 0.940746, 0.933035, 0.926947, 0.922713, 0.920548, 0.920548, 0.922713, 0.926947,
0.933035, 0.940746, 0.949751, 0.959798, 0.970546, 0.981793, 0.993178, 1.0043, 1.01361, 1.0151,
0.982884, 0.833334, 0.517153, 0.213948, 0.0816724, 0.0516763, 0.0470179, 0.0480882, 0.0504771,
0.0531983, 0.0560094, 0.0588071, 0.0615311, 0.064102, 0.0664467, 0.0684708, 0.070091, 0.0712222,
0.0718055, 0.0718055, 0.0712222, 0.070091, 0.0684708, 0.0664467, 0.064102, 0.0615311, 0.0588071,
0.0560094, 0.0531983, 0.0504771, 0.0480882, 0.0470179, 0.0516763, 0.0816724, 0.213948])
assert np.allclose(dh.gather_array(ρ_a.name)[0], ref_a)
assert np.allclose(dh.gather_array(ρ_b.name)[0], ref_b)
"""
Test Shan-Chen two-phase implementation against reference implementation
"""
import lbmpy
import pystencils as ps
import sympy as sp
import numpy as np
def test_shan_chen_two_phase():
from lbmpy.enums import Stencil
from lbmpy import LBMConfig, ForceModel, create_lb_update_rule
from lbmpy.macroscopic_value_kernels import macroscopic_values_setter
from lbmpy.creationfunctions import create_stream_pull_with_output_kernel, create_lb_method
from lbmpy.maxwellian_equilibrium import get_weights
N = 64
omega = 1.
g_aa = -4.7
rho0 = 1.
stencil = lbmpy.LBStencil(Stencil.D2Q9)
weights = get_weights(stencil, c_s_sq=sp.Rational(1, 3))
dh = ps.create_data_handling((N, ) * stencil.D, periodicity=True, default_target=ps.Target.CPU)
src = dh.add_array('src', values_per_cell=stencil.Q)
dst = dh.add_array_like('dst', 'src')
ρ = dh.add_array('rho')
def psi(dens):
return rho0 * (1. - sp.exp(-dens / rho0))
zero_vec = sp.Matrix([0] * stencil.D)
force = sum((psi(ρ[d]) * w_d * sp.Matrix(d)
for d, w_d in zip(stencil, weights)), zero_vec) * psi(ρ.center) * -1 * g_aa
lbm_config = LBMConfig(stencil=stencil, relaxation_rate=omega, compressible=True,
force_model=ForceModel.GUO, force=force, kernel_type='collide_only')
collision = create_lb_update_rule(lbm_config=lbm_config,
optimization={'symbolic_field': src})
stream = create_stream_pull_with_output_kernel(collision.method, src, dst, {'density': ρ})
config = ps.CreateKernelConfig(target=dh.default_target, cpu_openmp=False)
stream_kernel = ps.create_kernel(stream, config=config).compile()
collision_kernel = ps.create_kernel(collision, config=config).compile()
method_without_force = create_lb_method(LBMConfig(stencil=stencil, relaxation_rate=omega, compressible=True))
init_assignments = macroscopic_values_setter(method_without_force, velocity=(0, 0),
pdfs=src.center_vector, density=ρ.center)
init_kernel = ps.create_kernel(init_assignments, ghost_layers=0, config=config).compile()
for x in range(N):
for y in range(N):
if (x - N / 2)**2 + (y - N / 2)**2 <= 15**2:
dh.fill(ρ.name, 2.1, slice_obj=[x, y])
else:
dh.fill(ρ.name, 0.15, slice_obj=[x, y])
dh.run_kernel(init_kernel)
sync_pdfs = dh.synchronization_function([src.name])
sync_ρs = dh.synchronization_function([ρ.name])
for i in range(1000):
sync_ρs()
dh.run_kernel(collision_kernel)
sync_pdfs()
dh.run_kernel(stream_kernel)
dh.swap(src.name, dst.name)
# reference generated from https://github.com/lbm-principles-practice/code/blob/master/chapter9/shanchen.cpp with
# const int nsteps = 1000;
# const int noutput = 1000;
ref = np.array([0.185757, 0.185753, 0.185743, 0.185727, 0.185703, 0.185672, 0.185636, 0.185599, 0.185586, 0.185694,
0.186302, 0.188901, 0.19923, 0.238074, 0.365271, 0.660658, 1.06766, 1.39673, 1.56644, 1.63217,
1.65412, 1.66064, 1.66207, 1.66189, 1.66123, 1.66048, 1.65977, 1.65914, 1.65861, 1.6582, 1.6579,
1.65772, 1.65766, 1.65772, 1.6579, 1.6582, 1.65861, 1.65914, 1.65977, 1.66048, 1.66123, 1.66189,
1.66207, 1.66064, 1.65412, 1.63217, 1.56644, 1.39673, 1.06766, 0.660658, 0.365271, 0.238074,
0.19923, 0.188901, 0.186302, 0.185694, 0.185586, 0.185599, 0.185636, 0.185672, 0.185703, 0.185727,
0.185743, 0.185753])
assert np.allclose(dh.gather_array(ρ.name)[N // 2], ref)
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment