Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • ravi.k.ayyala/lbmpy
  • brendan-waters/lbmpy
  • anirudh.jonnalagadda/lbmpy
  • jbadwaik/lbmpy
  • alexander.reinauer/lbmpy
  • itischler/lbmpy
  • he66coqe/lbmpy
  • ev81oxyl/lbmpy
  • Bindgen/lbmpy
  • da15siwa/lbmpy
  • holzer/lbmpy
  • RudolfWeeber/lbmpy
  • pycodegen/lbmpy
13 results
Select Git revision
Show changes
Commits on Source (88)
Showing
with 418 additions and 198 deletions
[flake8] [flake8]
max-line-length=120 max-line-length=120
exclude=lbmpy/plot.py exclude=src/lbmpy/plot.py
lbmpy/session.py src/lbmpy/session.py
ignore = W293 W503 W291 E741 ignore = W293 W503 W291 C901 E741
lbmpy/_version.py export-subst src/lbmpy/_version.py export-subst
__pycache__ __pycache__
.ipynb_checkpoints .ipynb_checkpoints
.coverage .coverage*
*.pyc *.pyc
*.vti *.vti
/build /build
/html_doc /html_doc
/dist /dist
/*.egg-info *.egg-info
.cache .cache
_build _build
/.idea /.idea
...@@ -15,14 +15,15 @@ _local_tmp ...@@ -15,14 +15,15 @@ _local_tmp
**/pylintrc **/pylintrc
*.bak *.bak
*.tmp *.tmp
/lbmpy_tests/db /tests/db
doc/bibtex.json doc/bibtex.json
/db /db
/lbmpy/phasefield/simplex_projection.*.so /src/lbmpy/phasefield/simplex_projection.*.so
/lbmpy/phasefield/simplex_projection.c /src/lbmpy/phasefield/simplex_projection.c
# macOS # macOS
**/.DS_Store **/.DS_Store
*.uuid
# benchmark database # benchmark database
/lbmpy_tests/benchmark/db /tests/benchmark/db
\ No newline at end of file \ No newline at end of file
stages: stages:
- pretest - pretest
- test - test
- nightly
- docs
- deploy - deploy
# -------------------------- Templates ------------------------------------------------------------------------------------
# Base configuration for jobs meant to run at every commit
.every-commit:
rules:
- if: $CI_PIPELINE_SOURCE != "schedule"
# Configuration for jobs meant to run on each commit to pycodegen/pystencils/master
.every-commit-master:
rules:
- if: '$CI_PIPELINE_SOURCE != "schedule" && $CI_PROJECT_PATH == "pycodegen/lbmpy" && $CI_COMMIT_BRANCH == "master"'
# Base configuration for jobs meant to run at a schedule
.scheduled:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
# -------------------------- Pre Tests -------------------------------------------------------------------------------- # -------------------------- Pre Tests --------------------------------------------------------------------------------
# Normal test - runs on every commit all but "long run" tests # Normal test - runs on every commit all but "long run" tests
tests-and-coverage: tests-and-coverage:
stage: pretest stage: pretest
except: extends: .every-commit
variables: image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full:cupy12.3
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
script: script:
# - pip install sympy --upgrade # - pip install sympy --upgrade
- export NUM_CORES=$(nproc --all) - export NUM_CORES=$(nproc --all)
...@@ -22,7 +39,7 @@ tests-and-coverage: ...@@ -22,7 +39,7 @@ tests-and-coverage:
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
- env - env
- pip list - pip list
- py.test -v -n $NUM_CORES --cov-report html --cov-report term --cov=. -m "not longrun" --junitxml=report.xml - py.test -v -n $NUM_CORES --cov-report html --cov-report xml --cov-report term --cov=. -m "not longrun" --junitxml=report.xml
- python3 -m coverage xml - python3 -m coverage xml
tags: tags:
- docker - docker
...@@ -62,25 +79,22 @@ tests-and-coverage-with-longrun: ...@@ -62,25 +79,22 @@ tests-and-coverage-with-longrun:
minimal-conda: minimal-conda:
stage: pretest stage: pretest
except: extends: .every-commit
variables:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/minimal_conda image: i10git.cs.fau.de:5005/pycodegen/pycodegen/minimal_conda
script: script:
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
- python setup.py quicktest - pip install -e .
- python quicktest.py
tags: tags:
- docker - docker
# Linter for code formatting # Linter for code formatting
flake8-lint: flake8-lint:
stage: pretest stage: pretest
except: extends: .every-commit
variables:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
script: script:
- flake8 lbmpy - flake8 src/lbmpy
tags: tags:
- docker - docker
- cuda11 - cuda11
...@@ -90,9 +104,7 @@ flake8-lint: ...@@ -90,9 +104,7 @@ flake8-lint:
# pipeline with latest python version # pipeline with latest python version
latest-python: latest-python:
stage: test stage: test
except: extends: .every-commit
variables:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/latest_python image: i10git.cs.fau.de:5005/pycodegen/pycodegen/latest_python
before_script: before_script:
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
...@@ -113,34 +125,34 @@ latest-python: ...@@ -113,34 +125,34 @@ latest-python:
junit: report.xml junit: report.xml
# Minimal tests in windows environment # Minimal tests in windows environment
minimal-windows: #minimal-windows:
stage: test # stage: test
except: # except:
variables: # variables:
- $ENABLE_NIGHTLY_BUILDS # - $ENABLE_NIGHTLY_BUILDS
tags: # tags:
- win # - win
script: # script:
- export NUM_CORES=$(nproc --all) # - export NUM_CORES=$(nproc --all)
- export MPLBACKEND=Agg # - export MPLBACKEND=Agg
- source /cygdrive/c/Users/build/Miniconda3/Scripts/activate # - source /cygdrive/c/Users/build/Miniconda3/Scripts/activate
- source activate pystencils # - source activate pystencils
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils # - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
- python -c "import numpy" # - python -c "import numpy"
- pip install sympy==1.9 # - pip install sympy==1.9
- py.test -v -m "not (notebook or longrun)" # - py.test -v -m "not (notebook or longrun)"
minimal-sympy-master: minimal-sympy-master:
stage: test stage: test
except: extends: .every-commit
variables:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/minimal_conda image: i10git.cs.fau.de:5005/pycodegen/pycodegen/minimal_conda
before_script:
- pip install -e .
script: script:
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
- python -m pip install --upgrade git+https://github.com/sympy/sympy.git - python -m pip install --upgrade git+https://github.com/sympy/sympy.git
- pip list - pip list
- python setup.py quicktest - python quicktest.py
allow_failure: true allow_failure: true
tags: tags:
- docker - docker
...@@ -148,9 +160,7 @@ minimal-sympy-master: ...@@ -148,9 +160,7 @@ minimal-sympy-master:
ubuntu: ubuntu:
stage: test stage: test
except: extends: .every-commit
variables:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/ubuntu image: i10git.cs.fau.de:5005/pycodegen/pycodegen/ubuntu
before_script: before_script:
# - apt-get -y remove python3-sympy # - apt-get -y remove python3-sympy
...@@ -163,7 +173,7 @@ ubuntu: ...@@ -163,7 +173,7 @@ ubuntu:
- echo "backend:template" > ~/.config/matplotlib/matplotlibrc - echo "backend:template" > ~/.config/matplotlib/matplotlibrc
- env - env
- pip3 list - pip3 list
- pytest-3 -v -n $NUM_CORES -m "not longrun" --junitxml=report.xml - pytest -v -n $NUM_CORES -m "not longrun" --junitxml=report.xml
tags: tags:
- docker - docker
- cuda11 - cuda11
...@@ -207,11 +217,44 @@ pycodegen-integration: ...@@ -207,11 +217,44 @@ pycodegen-integration:
- cuda11 - cuda11
- AVX - AVX
# -------------------- Scheduled Tasks --------------------------------------------------------------------------
nightly-sympy:
stage: nightly
extends: .scheduled
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/latest_python
before_script:
- pip install -e .
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
- pip install --upgrade --pre sympy
script:
- env
- pip list
- export NUM_CORES=$(nproc --all)
- mkdir -p ~/.config/matplotlib
- echo "backend:template" > ~/.config/matplotlib/matplotlibrc
- mkdir public
- pytest -v -n $NUM_CORES -m "not longrun" --junitxml=report.xml
tags:
- docker
- AVX
- cuda
artifacts:
when: always
reports:
junit: report.xml
# -------------------- Documentation and deploy ------------------------------------------------------------------------ # -------------------- Documentation and deploy ------------------------------------------------------------------------
build-documentation: build-documentation:
stage: test stage: docs
needs: []
extends: .every-commit
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/documentation image: i10git.cs.fau.de:5005/pycodegen/pycodegen/documentation
before_script:
- pip install -e .
script: script:
- export PYTHONPATH=`pwd` - export PYTHONPATH=`pwd`
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
...@@ -227,7 +270,9 @@ build-documentation: ...@@ -227,7 +270,9 @@ build-documentation:
pages: pages:
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
extends: .every-commit-master
stage: deploy stage: deploy
needs: ["tests-and-coverage", "build-documentation"]
script: script:
- ls -l - ls -l
- mv coverage_report html_doc - mv coverage_report html_doc
...@@ -237,5 +282,3 @@ pages: ...@@ -237,5 +282,3 @@ pages:
- public - public
tags: tags:
- docker - docker
only:
- master@pycodegen/lbmpy
...@@ -11,3 +11,4 @@ Contributors: ...@@ -11,3 +11,4 @@ Contributors:
- Rudolf Weeber <weeber@icp.uni-stuttgart.de> - Rudolf Weeber <weeber@icp.uni-stuttgart.de>
- Christian Godenschwager <christian.godenschwager@fau.de> - Christian Godenschwager <christian.godenschwager@fau.de>
- Jan Hönig <jan.hoenig@fau.de> - Jan Hönig <jan.hoenig@fau.de>
- Philipp Suffa <philipp.suffa@fau.de>
------------------------ Important ---------------------------------
lbmpy is under the following GNU AGPLv3 license.
This license holds for the sources of lbmpy itself as well
as for all kernels generated with lbmpy i.e.
the output of lbmpy is also protected by the GNU AGPLv3 license.
----------------------------------------------------------------------
GNU AFFERO GENERAL PUBLIC LICENSE GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007 Version 3, 19 November 2007
......
include README.md
include COPYING.txt
include AUTHORS.txt include AUTHORS.txt
include CONTRIBUTING.md include CONTRIBUTING.md
global-include *.pyx include CHANGELOG.md
include versioneer.py
include lbmpy/_version.py
...@@ -71,6 +71,7 @@ Many thanks go to the [contributors](https://i10git.cs.fau.de/pycodegen/lbmpy/-/ ...@@ -71,6 +71,7 @@ Many thanks go to the [contributors](https://i10git.cs.fau.de/pycodegen/lbmpy/-/
If you use lbmpy in a publication, please cite the following articles: If you use lbmpy in a publication, please cite the following articles:
Overview: Overview:
- F. Hennig et al, Advanced Automatic Code Generation for Multiple Relaxation-Time Lattice Boltzmann Methods. SIAM Journal on Scientific Computing, 2023. https://doi.org/10.1137/22M1531348 ([Preprint](https://arxiv.org/abs/2211.02435))
- M. Bauer et al, lbmpy: Automatic code generation for efficient parallel lattice Boltzmann methods. Journal of Computational Science, 2021. https://doi.org/10.1016/j.jocs.2020.101269 ([Preprint](https://arxiv.org/abs/2001.11806)) - M. Bauer et al, lbmpy: Automatic code generation for efficient parallel lattice Boltzmann methods. Journal of Computational Science, 2021. https://doi.org/10.1016/j.jocs.2020.101269 ([Preprint](https://arxiv.org/abs/2001.11806))
Multiphase: Multiphase:
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
# conda env create -f conda_environment_user.yml # conda env create -f conda_environment_user.yml
# . activate pystencils # . activate pystencils
# #
# If you have CUDA installed and want to use your GPU, uncomment the last line to install pycuda # If you have CUDA or ROCm installed and want to use your GPU, uncomment the last line to install cupy
# #
# ---------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------
...@@ -33,4 +33,4 @@ dependencies: ...@@ -33,4 +33,4 @@ dependencies:
- pyevtk # VTK output for serial simulations - pyevtk # VTK output for serial simulations
- blitzdb # file-based No-SQL database to store simulation results - blitzdb # file-based No-SQL database to store simulation results
- pystencils - pystencils
#- pycuda # add this if you have CUDA installed #- cupy # add this if you have CUDA or ROCm installed
...@@ -19,9 +19,10 @@ try: ...@@ -19,9 +19,10 @@ try:
import pyximport import pyximport
pyximport.install(language_level=3) pyximport.install(language_level=3)
from lbmpy.phasefield.simplex_projection import simplex_projection_2d # NOQA
except ImportError: except ImportError:
pass pass
from lbmpy.phasefield.simplex_projection import simplex_projection_2d # NOQA
SCRIPT_FOLDER = os.path.dirname(os.path.realpath(__file__)) SCRIPT_FOLDER = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, os.path.abspath('lbmpy')) sys.path.insert(0, os.path.abspath('lbmpy'))
...@@ -42,33 +43,35 @@ def add_path_to_ignore(path): ...@@ -42,33 +43,35 @@ def add_path_to_ignore(path):
collect_ignore = [os.path.join(SCRIPT_FOLDER, "doc", "conf.py"), collect_ignore = [os.path.join(SCRIPT_FOLDER, "doc", "conf.py"),
os.path.join(SCRIPT_FOLDER, "doc", "img", "mb_discretization", "maxwell_boltzmann_stencil_plot.py")] os.path.join(SCRIPT_FOLDER, "doc", "img", "mb_discretization", "maxwell_boltzmann_stencil_plot.py")]
add_path_to_ignore('pystencils_tests/benchmark')
add_path_to_ignore('_local_tmp') add_path_to_ignore('_local_tmp')
try: try:
import pycuda import cupy
except ImportError: except ImportError:
collect_ignore += [os.path.join(SCRIPT_FOLDER, "lbmpy_tests/test_cpu_gpu_equivalence.py")] collect_ignore += [os.path.join(SCRIPT_FOLDER, "tests/test_cpu_gpu_equivalence.py")]
try: try:
import waLBerla import waLBerla
except ImportError: except ImportError:
collect_ignore += [os.path.join(SCRIPT_FOLDER, "lbmpy_tests/test_datahandling_parallel.py")] collect_ignore += [os.path.join(SCRIPT_FOLDER, "tests/test_datahandling_parallel.py")]
try: try:
import blitzdb import blitzdb
except ImportError: except ImportError:
collect_ignore += [os.path.join(SCRIPT_FOLDER, "lbmpy_tests/benchmark"), collect_ignore += [os.path.join(SCRIPT_FOLDER, "tests/benchmark"),
os.path.join(SCRIPT_FOLDER, os.path.join(SCRIPT_FOLDER,
"lbmpy_tests/full_scenarios/kida_vortex_flow/scenario_kida_vortex_flow.py")] "tests/full_scenarios/kida_vortex_flow/scenario_kida_vortex_flow.py"),
os.path.join(SCRIPT_FOLDER, "tests/full_scenarios/shear_wave/scenario_shear_wave.py"),
os.path.join(SCRIPT_FOLDER, "tests/test_json_serializer.py"),
os.path.join(SCRIPT_FOLDER, "src/lbmpy/db.py")]
if platform.system().lower() == 'windows': if platform.system().lower() == 'windows':
collect_ignore += [os.path.join(SCRIPT_FOLDER, "lbmpy_tests/test_quicktests.py")] collect_ignore += [os.path.join(SCRIPT_FOLDER, "tests/test_quicktests.py")]
sver = sympy.__version__.split(".") sver = sympy.__version__.split(".")
if int(sver[0]) == 1 and int(sver[1]) < 2: if int(sver[0]) == 1 and int(sver[1]) < 2:
add_path_to_ignore('lbmpy_tests/phasefield') add_path_to_ignore('tests/phasefield')
collect_ignore += [os.path.join(SCRIPT_FOLDER, "lbmpy_tests/test_n_phase_boyer_noncoupled.ipynb")] collect_ignore += [os.path.join(SCRIPT_FOLDER, "tests/test_n_phase_boyer_noncoupled.ipynb")]
collect_ignore += [os.path.join(SCRIPT_FOLDER, 'setup.py')] collect_ignore += [os.path.join(SCRIPT_FOLDER, 'setup.py')]
...@@ -104,18 +107,25 @@ class IPyNbTest(pytest.Item): ...@@ -104,18 +107,25 @@ class IPyNbTest(pytest.Item):
# disable matplotlib output # disable matplotlib output
exec("import matplotlib.pyplot as p; " exec("import matplotlib.pyplot as p; "
"p.close('all'); "
"p.switch_backend('Template')", global_dict) "p.switch_backend('Template')", global_dict)
# in notebooks there is an implicit plt.show() - if this is not called a warning is shown when the next # in notebooks there is an implicit plt.show() - if this is not called a warning is shown when the next
# plot is created. This warning is suppressed here # plot is created. This warning is suppressed here
# Also animations cannot be shown, which also leads to a warning.
exec("import warnings;" exec("import warnings;"
"warnings.filterwarnings('ignore', 'Adding an axes using the same arguments as a previous.*');", "warnings.filterwarnings('ignore', 'Adding an axes using the same arguments as a previous.*');"
"warnings.filterwarnings('ignore', 'Animation was deleted without rendering anything.*');",
global_dict) global_dict)
with tempfile.NamedTemporaryFile() as f: with tempfile.NamedTemporaryFile() as f:
f.write(self.code.encode()) f.write(self.code.encode())
f.flush() f.flush()
runpy.run_path(f.name, init_globals=global_dict, run_name=self.name) runpy.run_path(f.name, init_globals=global_dict, run_name=self.name)
# Close any open figures
exec("import matplotlib.pyplot as p; "
"p.close('all')", global_dict)
class IPyNbFile(pytest.File): class IPyNbFile(pytest.File):
def collect(self): def collect(self):
...@@ -138,10 +148,19 @@ class IPyNbFile(pytest.File): ...@@ -138,10 +148,19 @@ class IPyNbFile(pytest.File):
pass pass
def pytest_collect_file(path, parent): if pytest_version >= 70000:
glob_exprs = ["*demo*.ipynb", "*tutorial*.ipynb", "test_*.ipynb"] # Since pytest 7.0, usage of `py.path.local` is deprecated and `pathlib.Path` should be used instead
if any(path.fnmatch(g) for g in glob_exprs): import pathlib
if pytest_version >= 50403:
return IPyNbFile.from_parent(fspath=path, parent=parent) def pytest_collect_file(file_path: pathlib.Path, parent):
else: glob_exprs = ["*demo*.ipynb", "*tutorial*.ipynb", "test_*.ipynb"]
return IPyNbFile(path, parent) if any(file_path.match(g) for g in glob_exprs):
return IPyNbFile.from_parent(path=file_path, parent=parent)
else:
def pytest_collect_file(path, parent):
glob_exprs = ["*demo*.ipynb", "*tutorial*.ipynb", "test_*.ipynb"]
if any(path.fnmatch(g) for g in glob_exprs):
if pytest_version >= 50403:
return IPyNbFile.from_parent(fspath=path, parent=parent)
else:
return IPyNbFile(path, parent)
...@@ -33,7 +33,7 @@ version = re.sub(r'(\d+\.\d+)\.\d+(.*)', r'\1\2', lbmpy.__version__) ...@@ -33,7 +33,7 @@ version = re.sub(r'(\d+\.\d+)\.\d+(.*)', r'\1\2', lbmpy.__version__)
version = re.sub(r'(\.dev\d+).*?$', r'\1', version) version = re.sub(r'(\.dev\d+).*?$', r'\1', version)
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = lbmpy.__version__ release = lbmpy.__version__
language = None language = 'en'
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints']
default_role = 'any' default_role = 'any'
pygments_style = 'sphinx' pygments_style = 'sphinx'
......
File added
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="190.286" height="190.833" viewBox="0 0 190.286 190.833">
<defs>
<g>
<g id="glyph-0-0">
</g>
<g id="glyph-0-1">
<path d="M 3.5 1.71875 L 3.359375 1.71875 C 3.015625 1.71875 2.828125 1.625 2.84375 1.453125 C 2.84375 1.421875 2.859375 1.390625 2.859375 1.359375 L 4.328125 -3.828125 L 3.671875 -3.828125 L 3.546875 -3.40625 C 3.40625 -3.8125 3.21875 -3.953125 2.859375 -3.953125 C 1.671875 -3.953125 0.21875 -2.3125 0.21875 -0.9375 C 0.21875 -0.3125 0.578125 0.09375 1.140625 0.09375 C 1.78125 0.09375 2.1875 -0.234375 2.984375 -1.375 L 2.1875 1.25 C 2.078125 1.609375 1.921875 1.6875 1.34375 1.734375 L 1.34375 1.875 L 3.5 1.875 Z M 2.875 -3.765625 C 3.171875 -3.765625 3.40625 -3.515625 3.40625 -3.203125 C 3.40625 -2.46875 2.796875 -1.1875 2.1875 -0.6875 C 1.953125 -0.484375 1.71875 -0.375 1.5 -0.375 C 1.1875 -0.375 1 -0.640625 1 -1.0625 C 1 -1.734375 1.46875 -2.71875 2.046875 -3.3125 C 2.328125 -3.59375 2.625 -3.765625 2.875 -3.765625 Z M 2.875 -3.765625 "/>
</g>
<g id="glyph-1-0">
</g>
<g id="glyph-1-1">
<path d="M 3.59375 -1.109375 C 3.234375 -0.65625 3.140625 -0.578125 2.984375 -0.578125 C 2.8125 -0.578125 2.71875 -0.703125 2.65625 -1.046875 L 2.375 -2.453125 C 2.390625 -2.484375 2.40625 -2.5 2.453125 -2.5625 C 2.765625 -3.078125 2.984375 -3.296875 3.1875 -3.296875 C 3.25 -3.296875 3.3125 -3.28125 3.421875 -3.21875 C 3.546875 -3.140625 3.609375 -3.125 3.703125 -3.125 C 3.96875 -3.125 4.203125 -3.34375 4.203125 -3.59375 C 4.203125 -3.890625 3.953125 -4.140625 3.6875 -4.140625 C 3.265625 -4.140625 2.984375 -3.90625 2.265625 -2.90625 C 2.109375 -3.671875 2.03125 -3.890625 1.859375 -4.140625 L 0.375 -3.921875 L 0.375 -3.6875 C 0.5625 -3.703125 0.59375 -3.703125 0.6875 -3.703125 C 0.96875 -3.703125 1.09375 -3.546875 1.1875 -3.109375 L 1.453125 -1.71875 L 1.0625 -1.09375 C 0.875 -0.8125 0.78125 -0.71875 0.625 -0.71875 C 0.578125 -0.71875 0.53125 -0.75 0.4375 -0.796875 C 0.296875 -0.875 0.1875 -0.921875 0.078125 -0.921875 C -0.203125 -0.921875 -0.40625 -0.6875 -0.40625 -0.40625 C -0.40625 -0.09375 -0.171875 0.109375 0.15625 0.109375 C 0.609375 0.109375 0.8125 -0.0625 1.34375 -0.921875 L 1.546875 -1.28125 C 1.6875 -0.59375 1.765625 -0.375 1.953125 -0.109375 C 2.0625 0.03125 2.25 0.109375 2.421875 0.109375 C 2.859375 0.109375 3.234375 -0.1875 3.78125 -0.984375 Z M 3.59375 -1.109375 "/>
</g>
<g id="glyph-1-2">
<path d="M 2.859375 -1.265625 C 2.421875 -0.65625 2.140625 -0.453125 1.765625 -0.453125 C 1.375 -0.453125 1.109375 -0.78125 1.109375 -1.265625 C 1.109375 -1.828125 1.34375 -2.625 1.65625 -3.203125 C 1.890625 -3.625 2.15625 -3.84375 2.4375 -3.84375 C 2.53125 -3.84375 2.625 -3.78125 2.625 -3.6875 C 2.625 -3.65625 2.609375 -3.609375 2.5625 -3.53125 C 2.46875 -3.375 2.4375 -3.28125 2.4375 -3.171875 C 2.4375 -2.90625 2.65625 -2.703125 2.953125 -2.703125 C 3.28125 -2.703125 3.515625 -2.96875 3.515625 -3.34375 C 3.515625 -3.8125 3.109375 -4.140625 2.515625 -4.140625 C 1.25 -4.140625 -0.046875 -2.671875 -0.046875 -1.234375 C -0.046875 -0.4375 0.515625 0.109375 1.328125 0.109375 C 1.71875 0.109375 2.109375 -0.03125 2.40625 -0.28125 C 2.640625 -0.484375 2.796875 -0.65625 3.109375 -1.109375 Z M 2.859375 -1.265625 "/>
</g>
<g id="glyph-2-0">
</g>
<g id="glyph-2-1">
<path d="M 3.859375 -3.90625 L 0.875 -3.90625 L 0.875 -3.8125 C 1.265625 -3.78125 1.34375 -3.734375 1.34375 -3.5625 C 1.34375 -3.484375 1.3125 -3.328125 1.28125 -3.1875 L 0.53125 -0.53125 C 0.4375 -0.171875 0.390625 -0.140625 0.046875 -0.09375 L 0.046875 0 L 1.5625 0 L 1.5625 -0.09375 C 1.203125 -0.109375 1.09375 -0.171875 1.09375 -0.359375 C 1.09375 -0.40625 1.125 -0.5 1.15625 -0.625 L 1.53125 -1.96875 C 1.75 -1.953125 1.875 -1.9375 2.015625 -1.9375 C 2.25 -1.9375 2.28125 -1.9375 2.34375 -1.921875 C 2.421875 -1.859375 2.46875 -1.796875 2.46875 -1.671875 C 2.46875 -1.578125 2.453125 -1.5 2.421875 -1.3125 L 2.53125 -1.28125 L 2.984375 -2.6875 L 2.875 -2.71875 C 2.609375 -2.171875 2.578125 -2.171875 1.578125 -2.15625 L 1.96875 -3.546875 C 2.015625 -3.671875 2.09375 -3.703125 2.34375 -3.703125 C 3.34375 -3.703125 3.5625 -3.625 3.5625 -3.265625 C 3.5625 -3.21875 3.5625 -3.203125 3.546875 -3.125 C 3.546875 -3.078125 3.546875 -3.078125 3.546875 -3 L 3.671875 -2.984375 Z M 3.859375 -3.90625 "/>
</g>
<g id="glyph-2-2">
<path d="M 0.65625 -3.84375 C 1.015625 -3.84375 1.046875 -3.8125 1.046875 -3.6875 C 1.046875 -3.625 1.03125 -3.5625 1 -3.421875 C 0.984375 -3.390625 0.96875 -3.34375 0.96875 -3.3125 L 0.953125 -3.265625 L 0.140625 -0.28125 L 0.140625 -0.25 C 0.140625 -0.109375 0.59375 0.0625 0.9375 0.0625 C 1.84375 0.0625 2.828125 -0.984375 2.828125 -1.921875 C 2.828125 -2.34375 2.53125 -2.640625 2.140625 -2.640625 C 1.71875 -2.640625 1.40625 -2.390625 0.984375 -1.734375 C 1.296875 -2.875 1.328125 -3.03125 1.609375 -4.0625 L 1.578125 -4.09375 C 1.28125 -4.03125 1.0625 -4 0.65625 -3.953125 Z M 1.90625 -2.34375 C 2.15625 -2.34375 2.328125 -2.140625 2.328125 -1.828125 C 2.328125 -1.4375 2.015625 -0.796875 1.65625 -0.421875 C 1.4375 -0.203125 1.1875 -0.078125 0.921875 -0.078125 C 0.734375 -0.078125 0.65625 -0.140625 0.65625 -0.28125 C 0.65625 -0.640625 0.828125 -1.21875 1.078125 -1.65625 C 1.34375 -2.125 1.609375 -2.34375 1.90625 -2.34375 Z M 1.90625 -2.34375 "/>
</g>
<g id="glyph-2-3">
<path d="M 5.421875 -3.90625 L 4.3125 -3.90625 L 4.3125 -3.8125 C 4.640625 -3.78125 4.703125 -3.734375 4.703125 -3.578125 C 4.703125 -3.484375 4.65625 -3.328125 4.578125 -3.171875 L 3.453125 -0.96875 L 3.21875 -3.375 L 3.21875 -3.453125 C 3.21875 -3.703125 3.296875 -3.78125 3.625 -3.8125 L 3.625 -3.90625 L 2.203125 -3.90625 L 2.203125 -3.8125 C 2.546875 -3.796875 2.609375 -3.765625 2.640625 -3.46875 L 2.703125 -3.046875 L 1.671875 -0.96875 L 1.40625 -3.40625 C 1.40625 -3.421875 1.40625 -3.46875 1.40625 -3.484375 C 1.40625 -3.71875 1.46875 -3.765625 1.84375 -3.8125 L 1.84375 -3.90625 L 0.421875 -3.90625 L 0.421875 -3.8125 C 0.625 -3.78125 0.671875 -3.765625 0.734375 -3.71875 C 0.796875 -3.65625 0.828125 -3.53125 0.890625 -2.984375 L 1.265625 0.109375 L 1.375 0.109375 L 2.703125 -2.609375 L 2.734375 -2.609375 L 3.046875 0.109375 L 3.15625 0.109375 L 4.96875 -3.375 C 5.140625 -3.6875 5.1875 -3.734375 5.421875 -3.8125 Z M 5.421875 -3.90625 "/>
</g>
<g id="glyph-3-0">
</g>
<g id="glyph-3-1">
<path d="M 1.90625 -0.84375 C 1.609375 -0.4375 1.4375 -0.3125 1.171875 -0.3125 C 0.921875 -0.3125 0.734375 -0.515625 0.734375 -0.84375 C 0.734375 -1.21875 0.890625 -1.75 1.109375 -2.140625 C 1.265625 -2.421875 1.4375 -2.5625 1.625 -2.5625 C 1.6875 -2.5625 1.75 -2.53125 1.75 -2.46875 C 1.75 -2.4375 1.734375 -2.40625 1.703125 -2.359375 C 1.65625 -2.25 1.625 -2.1875 1.625 -2.125 C 1.625 -1.9375 1.78125 -1.8125 1.96875 -1.8125 C 2.1875 -1.8125 2.34375 -1.984375 2.34375 -2.21875 C 2.34375 -2.546875 2.078125 -2.765625 1.671875 -2.765625 C 0.828125 -2.765625 -0.03125 -1.78125 -0.03125 -0.828125 C -0.03125 -0.296875 0.34375 0.078125 0.890625 0.078125 C 1.15625 0.078125 1.40625 -0.015625 1.609375 -0.1875 C 1.765625 -0.328125 1.859375 -0.4375 2.078125 -0.734375 Z M 1.90625 -0.84375 "/>
</g>
<g id="glyph-3-2">
<path d="M 1.296875 -0.84375 L 1.203125 -0.71875 C 1.046875 -0.484375 0.921875 -0.359375 0.828125 -0.359375 C 0.78125 -0.359375 0.734375 -0.40625 0.734375 -0.453125 C 0.734375 -0.484375 0.765625 -0.6875 0.796875 -0.765625 L 1.328125 -2.765625 C 1.015625 -2.6875 0.59375 -2.640625 0.125 -2.59375 L 0.125 -2.4375 C 0.390625 -2.4375 0.484375 -2.390625 0.484375 -2.265625 C 0.484375 -2.21875 0.453125 -2.125 0.4375 -2.015625 L 0.09375 -0.734375 C 0.046875 -0.5625 0.015625 -0.40625 0.015625 -0.328125 C 0.015625 -0.109375 0.171875 0.046875 0.421875 0.046875 C 0.78125 0.046875 1 -0.125 1.421875 -0.765625 Z M 1.15625 -4.09375 C 0.953125 -4.09375 0.765625 -3.90625 0.765625 -3.703125 C 0.765625 -3.46875 0.9375 -3.296875 1.15625 -3.296875 C 1.390625 -3.296875 1.578125 -3.46875 1.578125 -3.6875 C 1.578125 -3.90625 1.375 -4.09375 1.15625 -4.09375 Z M 1.15625 -4.09375 "/>
</g>
<g id="glyph-4-0">
</g>
<g id="glyph-4-1">
<path d="M 1.078125 -0.703125 L 1 -0.609375 C 0.875 -0.40625 0.765625 -0.296875 0.6875 -0.296875 C 0.640625 -0.296875 0.609375 -0.34375 0.609375 -0.375 C 0.609375 -0.40625 0.640625 -0.578125 0.65625 -0.640625 L 1.109375 -2.296875 C 0.84375 -2.25 0.5 -2.1875 0.109375 -2.15625 L 0.109375 -2.03125 C 0.3125 -2.03125 0.40625 -1.984375 0.40625 -1.890625 C 0.40625 -1.84375 0.390625 -1.765625 0.359375 -1.6875 L 0.078125 -0.609375 C 0.03125 -0.46875 0.015625 -0.328125 0.015625 -0.265625 C 0.015625 -0.078125 0.15625 0.046875 0.359375 0.046875 C 0.65625 0.046875 0.84375 -0.109375 1.1875 -0.640625 Z M 0.96875 -3.421875 C 0.796875 -3.421875 0.640625 -3.265625 0.640625 -3.078125 C 0.640625 -2.890625 0.78125 -2.734375 0.96875 -2.734375 C 1.15625 -2.734375 1.3125 -2.890625 1.3125 -3.078125 C 1.3125 -3.265625 1.15625 -3.421875 0.96875 -3.421875 Z M 0.96875 -3.421875 "/>
</g>
<g id="glyph-5-0">
</g>
<g id="glyph-5-1">
<path d="M 1.65625 -3.171875 L 0 -3.171875 L 0 -2.8125 L 1.65625 -2.8125 Z M 1.65625 -3.171875 "/>
</g>
</g>
<clipPath id="clip-0">
<path clip-rule="nonzero" d="M 59 32 L 190.285156 32 L 190.285156 190.832031 L 59 190.832031 Z M 59 32 "/>
</clipPath>
</defs>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -9.962062 -0.00015625 L 159.405125 -0.00015625 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -9.962062 49.812344 L 159.405125 49.812344 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -9.962062 99.62875 L 159.405125 99.62875 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -9.962062 149.44125 L 159.405125 149.44125 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -0.001125 -9.961094 L -0.001125 159.406094 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 49.815281 -9.961094 L 49.815281 159.406094 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 99.627781 -9.961094 L 99.627781 159.406094 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 149.440281 -9.961094 L 149.440281 159.406094 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.390625%, 45.097351%, 69.802856%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 4.698094 -0.00015625 C 4.698094 2.593594 2.592625 4.695156 -0.001125 4.695156 C -2.594875 4.695156 -4.696437 2.593594 -4.696437 -0.00015625 C -4.696437 -2.593906 -2.594875 -4.695469 -0.001125 -4.695469 C 2.592625 -4.695469 4.698094 -2.593906 4.698094 -0.00015625 Z M 4.698094 -0.00015625 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.390625%, 45.097351%, 69.802856%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 4.698094 49.812344 C 4.698094 52.406094 2.592625 54.511562 -0.001125 54.511562 C -2.594875 54.511562 -4.696437 52.406094 -4.696437 49.812344 C -4.696437 47.218594 -2.594875 45.117031 -0.001125 45.117031 C 2.592625 45.117031 4.698094 47.218594 4.698094 49.812344 Z M 4.698094 49.812344 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.390625%, 45.097351%, 69.802856%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 4.698094 99.62875 C 4.698094 102.2225 2.592625 104.324062 -0.001125 104.324062 C -2.594875 104.324062 -4.696437 102.2225 -4.696437 99.62875 C -4.696437 97.035 -2.594875 94.929531 -0.001125 94.929531 C 2.592625 94.929531 4.698094 97.035 4.698094 99.62875 Z M 4.698094 99.62875 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.390625%, 45.097351%, 69.802856%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 4.698094 149.44125 C 4.698094 152.035 2.592625 154.136562 -0.001125 154.136562 C -2.594875 154.136562 -4.696437 152.035 -4.696437 149.44125 C -4.696437 146.8475 -2.594875 144.745937 -0.001125 144.745937 C 2.592625 144.745937 4.698094 146.8475 4.698094 149.44125 Z M 4.698094 149.44125 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(87.057495%, 56.077576%, 1.959229%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 54.510594 -0.00015625 C 54.510594 2.593594 52.409031 4.695156 49.815281 4.695156 C 47.221531 4.695156 45.116062 2.593594 45.116062 -0.00015625 C 45.116062 -2.593906 47.221531 -4.695469 49.815281 -4.695469 C 52.409031 -4.695469 54.510594 -2.593906 54.510594 -0.00015625 Z M 54.510594 -0.00015625 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(87.057495%, 56.077576%, 1.959229%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 54.510594 49.812344 C 54.510594 52.406094 52.409031 54.511562 49.815281 54.511562 C 47.221531 54.511562 45.116062 52.406094 45.116062 49.812344 C 45.116062 47.218594 47.221531 45.117031 49.815281 45.117031 C 52.409031 45.117031 54.510594 47.218594 54.510594 49.812344 Z M 54.510594 49.812344 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(87.057495%, 56.077576%, 1.959229%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 54.510594 99.62875 C 54.510594 102.2225 52.409031 104.324062 49.815281 104.324062 C 47.221531 104.324062 45.116062 102.2225 45.116062 99.62875 C 45.116062 97.035 47.221531 94.929531 49.815281 94.929531 C 52.409031 94.929531 54.510594 97.035 54.510594 99.62875 Z M 54.510594 99.62875 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.390625%, 45.097351%, 69.802856%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 54.510594 149.44125 C 54.510594 152.035 52.409031 154.136562 49.815281 154.136562 C 47.221531 154.136562 45.116062 152.035 45.116062 149.44125 C 45.116062 146.8475 47.221531 144.745937 49.815281 144.745937 C 52.409031 144.745937 54.510594 146.8475 54.510594 149.44125 Z M 54.510594 149.44125 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.782776%, 61.959839%, 45.097351%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 104.323094 -0.00015625 C 104.323094 2.593594 102.221531 4.695156 99.627781 4.695156 C 97.034031 4.695156 94.932469 2.593594 94.932469 -0.00015625 C 94.932469 -2.593906 97.034031 -4.695469 99.627781 -4.695469 C 102.221531 -4.695469 104.323094 -2.593906 104.323094 -0.00015625 Z M 104.323094 -0.00015625 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.782776%, 61.959839%, 45.097351%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 104.323094 49.812344 C 104.323094 52.406094 102.221531 54.511562 99.627781 54.511562 C 97.034031 54.511562 94.932469 52.406094 94.932469 49.812344 C 94.932469 47.218594 97.034031 45.117031 99.627781 45.117031 C 102.221531 45.117031 104.323094 47.218594 104.323094 49.812344 Z M 104.323094 49.812344 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(87.057495%, 56.077576%, 1.959229%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 104.323094 99.62875 C 104.323094 102.2225 102.221531 104.324062 99.627781 104.324062 C 97.034031 104.324062 94.932469 102.2225 94.932469 99.62875 C 94.932469 97.035 97.034031 94.929531 99.627781 94.929531 C 102.221531 94.929531 104.323094 97.035 104.323094 99.62875 Z M 104.323094 99.62875 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(87.057495%, 56.077576%, 1.959229%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 104.323094 149.44125 C 104.323094 152.035 102.221531 154.136562 99.627781 154.136562 C 97.034031 154.136562 94.932469 152.035 94.932469 149.44125 C 94.932469 146.8475 97.034031 144.745937 99.627781 144.745937 C 102.221531 144.745937 104.323094 146.8475 104.323094 149.44125 Z M 104.323094 149.44125 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.782776%, 61.959839%, 45.097351%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 154.1395 -0.00015625 C 154.1395 2.593594 152.034031 4.695156 149.440281 4.695156 C 146.846531 4.695156 144.744969 2.593594 144.744969 -0.00015625 C 144.744969 -2.593906 146.846531 -4.695469 149.440281 -4.695469 C 152.034031 -4.695469 154.1395 -2.593906 154.1395 -0.00015625 Z M 154.1395 -0.00015625 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.782776%, 61.959839%, 45.097351%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 154.1395 49.812344 C 154.1395 52.406094 152.034031 54.511562 149.440281 54.511562 C 146.846531 54.511562 144.744969 52.406094 144.744969 49.812344 C 144.744969 47.218594 146.846531 45.117031 149.440281 45.117031 C 152.034031 45.117031 154.1395 47.218594 154.1395 49.812344 Z M 154.1395 49.812344 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0.782776%, 61.959839%, 45.097351%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 154.1395 99.62875 C 154.1395 102.2225 152.034031 104.324062 149.440281 104.324062 C 146.846531 104.324062 144.744969 102.2225 144.744969 99.62875 C 144.744969 97.035 146.846531 94.929531 149.440281 94.929531 C 152.034031 94.929531 154.1395 97.035 154.1395 99.62875 Z M 154.1395 99.62875 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(87.057495%, 56.077576%, 1.959229%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 154.1395 149.44125 C 154.1395 152.035 152.034031 154.136562 149.440281 154.136562 C 146.846531 154.136562 144.744969 152.035 144.744969 149.44125 C 144.744969 146.8475 146.846531 144.745937 149.440281 144.745937 C 152.034031 144.745937 154.1395 146.8475 154.1395 149.44125 Z M 154.1395 149.44125 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0%, 0%, 0%)" fill-opacity="1" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 88.381687 65.75375 C 88.381687 68.3475 86.280125 70.449062 83.686375 70.449062 C 81.092625 70.449062 78.991062 68.3475 78.991062 65.75375 C 78.991062 63.16 81.092625 61.058437 83.686375 61.058437 C 86.280125 61.058437 88.381687 63.16 88.381687 65.75375 Z M 88.381687 65.75375 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<g clip-path="url(#clip-0)">
<path fill="none" stroke-width="1.99255" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 149.440281 109.589687 C 95.252781 100.035 59.073094 48.363125 68.627781 -5.824375 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
</g>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-dasharray="2.98883 2.98883" stroke-miterlimit="10" d="M 4.98325 144.460781 L 44.830906 104.609219 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-dasharray="2.98883 2.98883" stroke-miterlimit="10" d="M 54.79575 94.644375 L 94.647312 54.796719 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 55.842625 96.589687 L 79.702 72.726406 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0%, 0%, 0%)" fill-opacity="1" d="M 74.222656 71.777344 L 75.855469 76.683594 L 76.265625 73.820312 L 79.125 73.414062 "/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 53.799656 92.652187 L 77.659031 68.792812 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0%, 0%, 0%)" fill-opacity="1" d="M 100.125 103.660156 L 98.492188 98.757812 L 98.082031 101.617188 L 95.222656 102.023438 "/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 7.026219 145.406094 L 44.830906 107.5975 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0%, 0%, 0%)" fill-opacity="1" d="M 25.40625 22.960938 L 27.039062 27.863281 L 27.449219 25.003906 L 30.308594 24.59375 "/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-dasharray="2.98883 2.98883" stroke-miterlimit="10" d="M 19.924656 65.75375 L 83.686375 65.75375 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill="none" stroke-width="0.99628" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 24.905125 68.644375 L 24.905125 96.738125 " transform="matrix(1, 0, 0, -1, 20.423, 170.41)"/>
<path fill-rule="nonzero" fill="rgb(0%, 0%, 0%)" fill-opacity="1" d="M 45.328125 104.65625 L 47.640625 100.03125 L 45.328125 101.765625 L 43.019531 100.03125 "/>
<path fill-rule="nonzero" fill="rgb(0%, 0%, 0%)" fill-opacity="1" d="M 45.328125 70.78125 L 43.019531 75.40625 L 45.328125 73.671875 L 47.640625 75.40625 "/>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-0-1" x="39.103" y="89.761"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-1-1" x="24.961" y="17.349"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-2-1" x="29.594" y="18.694"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-2-1" x="33.748432" y="18.694"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-1-1" x="76.852" y="67.163"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-2-1" x="81.484" y="68.507"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-1-1" x="127.248" y="116.953"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-2-2" x="131.731" y="118.298"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-1-1" x="110.094" y="105.968"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-2-3" x="114.577" y="107.312"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-0-1" x="90.172" y="85.461"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-3-1" x="94.954" y="86.806"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-4-1" x="97.733" y="87.934"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-0-1" x="75.228" y="91.006"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-3-1" x="80.01" y="92.351"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-5-1" x="82.639" y="93.298"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-4-1" x="82.789" y="94.344"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-1-2" x="42.409" y="37.257"/>
</g>
<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
<use xlink:href="#glyph-3-2" x="46.539" y="38.602"/>
</g>
</svg>
...@@ -6,6 +6,9 @@ lbmpy ...@@ -6,6 +6,9 @@ lbmpy
:maxdepth: 2 :maxdepth: 2
sphinx/tutorials.rst sphinx/tutorials.rst
sphinx/methods.rst
sphinx/boundary_conditions.rst
sphinx/forcemodels.rst
sphinx/api.rst sphinx/api.rst
......
Source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
{ {
"data": { "data": {
"text/plain": [ "text/plain": [
"<module 'pycuda' from '/home/markus/miniconda3/envs/pystencils/lib/python3.8/site-packages/pycuda/__init__.py'>" "<module 'cupy' from '/home/markus/.local/lib/python3.11/site-packages/cupy/__init__.py'>"
] ]
}, },
"execution_count": 1, "execution_count": 1,
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
], ],
"source": [ "source": [
"import pytest\n", "import pytest\n",
"pytest.importorskip('pycuda')" "pytest.importorskip('cupy')"
] ]
}, },
{ {
...@@ -660,7 +660,7 @@ ...@@ -660,7 +660,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -674,7 +674,7 @@ ...@@ -674,7 +674,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.2" "version": "3.11.0rc1"
} }
}, },
"nbformat": 4, "nbformat": 4,
Source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -673,7 +673,7 @@ ...@@ -673,7 +673,7 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3 (ipykernel)",
"language": "python", "language": "python",
"name": "python3" "name": "python3"
}, },
...@@ -687,7 +687,7 @@ ...@@ -687,7 +687,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.2" "version": "3.11.4"
}, },
"vscode": { "vscode": {
"interpreter": { "interpreter": {
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
from lbmpy.session import * from lbmpy.session import *
from lbmpy.relaxationrates import * from lbmpy.relaxationrates import *
``` ```
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
# Tutorial 06: Modifying a LBM method: Smagorinsky model # Tutorial 06: Modifying a LBM method: Smagorinsky model
   
In this demo, we show how to modify a lattice Boltzmann method. As example we are going to add a simple turbulence model, by introducing a rule that locally computes the relaxation parameter dependent on the local strain rate tensor. The Smagorinsky model is implemented directly in *lbmpy* as well, however here we take the manual approach to demonstrate how a LB method can be changed in *lbmpy*. In this demo, we show how to modify a lattice Boltzmann method. As example we are going to add a simple turbulence model, by introducing a rule that locally computes the relaxation parameter dependent on the local strain rate tensor. The Smagorinsky model is implemented directly in *lbmpy* as well, however here we take the manual approach to demonstrate how a LB method can be changed in *lbmpy*.
   
## 1) Theoretical background ## 1) Theoretical background
   
Since we have *sympy* available, we want to start out with the basic model equations and derive the concrete equations ourselves. This approach is less error prone, since the calculations are done by the computer algebra system, and oftentimes this approach is also more general and easier to understand. Since we have *sympy* available, we want to start out with the basic model equations and derive the concrete equations ourselves. This approach is less error prone, since the calculations are done by the computer algebra system, and oftentimes this approach is also more general and easier to understand.
   
### a) Smagorinsky model ### a) Smagorinsky model
   
The basic idea of the Smagorinsky turbulence model is to safe compute time, by not resolving the smallest eddies of the flow on the grid, but model them by an artifical dissipation term. The basic idea of the Smagorinsky turbulence model is to safe compute time, by not resolving the smallest eddies of the flow on the grid, but model them by an artifical dissipation term.
The energy dissipation of small scale vortices is taken into account by introducing a "turbulent viscosity". This additional viscosity depends on local flow properties, namely the local shear rates. The larger the local shear rates the higher the turbulent viscosity and the more artifical dissipation is added. The energy dissipation of small scale vortices is taken into account by introducing a "turbulent viscosity". This additional viscosity depends on local flow properties, namely the local shear rates. The larger the local shear rates the higher the turbulent viscosity and the more artifical dissipation is added.
   
The total viscosity is The total viscosity is
   
$$\nu_{total} = \nu_0 + \underbrace{(C_S \Delta)^2 |S|}_{\nu_{t}}$$ $$\nu_{total} = \nu_0 + \underbrace{(C_S \Delta)^2 |S|}_{\nu_{t}}$$
   
where $\nu_0$ is the normal viscosity, $C_S$ is the Smagorinsky constant, not to be confused with the speed of sound! Typical values of the Smagorinsky constant are between 0.1 - 0.2. The filter length $\Delta$ is chosen as 1 in lattice coordinates. where $\nu_0$ is the normal viscosity, $C_S$ is the Smagorinsky constant, not to be confused with the speed of sound! Typical values of the Smagorinsky constant are between 0.1 - 0.2. The filter length $\Delta$ is chosen as 1 in lattice coordinates.
   
The quantity $|S|$ is computed from the local strain rate tensor $S$ that is given by The quantity $|S|$ is computed from the local strain rate tensor $S$ that is given by
   
$$S_{ij} = \frac{1}{2} \left( \partial_i u_j + \partial_j u_i \right)$$ $$S_{ij} = \frac{1}{2} \left( \partial_i u_j + \partial_j u_i \right)$$
   
and and
   
$$|S| = \sqrt{2 S_{ij} S_{ij}}$$ $$|S| = \sqrt{2 S_{ij} S_{ij}}$$
   
   
### b) LBM implementation of Smagorinsky model ### b) LBM implementation of Smagorinsky model
   
To add the Smagorinsky model to a LB scheme one has to first compute the strain rate tensor $S_{ij}$ in each cell, and compute the turbulent viscosity $\nu_t$ from it. Then the local relaxation rate has to be adapted to match the total viscosity $\nu_{total}$ instead of the standard viscosity $\nu_0$. To add the Smagorinsky model to a LB scheme one has to first compute the strain rate tensor $S_{ij}$ in each cell, and compute the turbulent viscosity $\nu_t$ from it. Then the local relaxation rate has to be adapted to match the total viscosity $\nu_{total}$ instead of the standard viscosity $\nu_0$.
   
A fortunate property of LB methods is, that the strain rate tensor can be computed locally from the non-equilibrium part of the distribution function. This is somewhat surprising, since the strain rate tensor contains first order derivatives. The strain rate tensor can be obtained by A fortunate property of LB methods is, that the strain rate tensor can be computed locally from the non-equilibrium part of the distribution function. This is somewhat surprising, since the strain rate tensor contains first order derivatives. The strain rate tensor can be obtained by
   
$$S_{ij} = - \frac{3 \omega_s}{2 \rho_{(0)}} \Pi_{ij}^{(neq)}$$ $$S_{ij} = - \frac{3 \omega_s}{2 \rho_{(0)}} \Pi_{ij}^{(neq)}$$
   
where $\omega_s$ is the relaxation rate that determines the viscosity, $\rho_{(0)}$ is $\rho$ in compressible models and $1$ for incompressible schemes. where $\omega_s$ is the relaxation rate that determines the viscosity, $\rho_{(0)}$ is $\rho$ in compressible models and $1$ for incompressible schemes.
$\Pi_{ij}^{(neq)}$ is the second order moment tensor of the non-equilibrium part of the distribution functions $f^{(neq)} = f - f^{(eq)}$ and can be computed as $\Pi_{ij}^{(neq)}$ is the second order moment tensor of the non-equilibrium part of the distribution functions $f^{(neq)} = f - f^{(eq)}$ and can be computed as
   
$$\Pi_{ij}^{(neq)} = \sum_q c_{qi} c_{qj} \; f_q^{(neq)}$$ $$\Pi_{ij}^{(neq)} = \sum_q c_{qi} c_{qj} \; f_q^{(neq)}$$
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
We first have to find a closed form for $S_{ij}$ since in the formula above, it depends on $\omega$, which should be adapated according to $S_{ij}$. We first have to find a closed form for $S_{ij}$ since in the formula above, it depends on $\omega$, which should be adapated according to $S_{ij}$.
So we compute $\omega$ and insert it into the formula for $S$: So we compute $\omega$ and insert it into the formula for $S$:
   
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
τ_0, ρ, ω, ω_total, ω_0 = sp.symbols("tau_0 rho omega omega_total omega_0", positive=True, real=True) τ_0, ρ, ω, ω_total, ω_0 = sp.symbols("tau_0 rho omega omega_total omega_0", positive=True, real=True)
ν_0, C_S, S, Π = sp.symbols("nu_0, C_S, |S|, Pi", positive=True, real=True) ν_0, C_S, S, Π = sp.symbols("nu_0, C_S, |S|, Pi", positive=True, real=True)
   
Seq = sp.Eq(S, 3 * ω / 2 * Π) Seq = sp.Eq(S, 3 * ω / 2 * Π)
Seq Seq
``` ```
   
%% Output %% Output
   
$\displaystyle |S| = \frac{3 \Pi \omega}{2}$ $\displaystyle |S| = \frac{3 \Pi \omega}{2}$
3⋅Π⋅ω 3⋅Π⋅ω
|S| = ───── |S| = ─────
2 2
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
Note that we left of the minus, since we took the absolute value of both tensor. The absolute value is defined as above, with the factor of two inside the square root. The $\rho_{(0)}$ has been left out, remembering that $\Pi^{(neq)}$ has to be divided by $\rho$ in case of compressible models|. Note that we left of the minus, since we took the absolute value of both tensor. The absolute value is defined as above, with the factor of two inside the square root. The $\rho_{(0)}$ has been left out, remembering that $\Pi^{(neq)}$ has to be divided by $\rho$ in case of compressible models|.
   
Next, we compute $\omega$ from the total viscosity as given by the Smagorinsky equation: Next, we compute $\omega$ from the total viscosity as given by the Smagorinsky equation:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
relaxation_rate_from_lattice_viscosity(ν_0 + C_S ** 2 * S) relaxation_rate_from_lattice_viscosity(ν_0 + C_S ** 2 * S)
``` ```
   
%% Output %% Output
   
$\displaystyle \frac{2}{6 C_{S}^{2} |S| + 6 \nu_{0} + 1}$ $\displaystyle \frac{2}{6 C_{S}^{2} |S| + 6 \nu_{0} + 1}$
2 2
───────────────────── ─────────────────────
2 2
6⋅C_S ⋅|S| + 6⋅ν₀ + 1 6⋅C_S ⋅|S| + 6⋅ν₀ + 1
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
and insert it into the equation for $|S|$ and insert it into the equation for $|S|$
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
Seq2 = Seq.subs(ω, relaxation_rate_from_lattice_viscosity(ν_0 + C_S **2 * S )) Seq2 = Seq.subs(ω, relaxation_rate_from_lattice_viscosity(ν_0 + C_S **2 * S ))
Seq2 Seq2
``` ```
   
%% Output %% Output
   
$\displaystyle |S| = \frac{3 \Pi}{6 C_{S}^{2} |S| + 6 \nu_{0} + 1}$ $\displaystyle |S| = \frac{3 \Pi}{6 C_{S}^{2} |S| + 6 \nu_{0} + 1}$
3⋅Π 3⋅Π
|S| = ───────────────────── |S| = ─────────────────────
2 2
6⋅C_S ⋅|S| + 6⋅ν₀ + 1 6⋅C_S ⋅|S| + 6⋅ν₀ + 1
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
This equation contains only known quantities, such that we can solve it for $|S|$. This equation contains only known quantities, such that we can solve it for $|S|$.
Additionally we substitute the lattice viscosity $\nu_0$ by the original relaxation time $\tau_0$. The resulting equations get simpler using relaxation times instead of rates. Additionally we substitute the lattice viscosity $\nu_0$ by the original relaxation time $\tau_0$. The resulting equations get simpler using relaxation times instead of rates.
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
solveRes = sp.solve(Seq2, S) solveRes = sp.solve(Seq2, S)
assert len(solveRes) == 1 assert len(solveRes) == 1
SVal = solveRes[0] SVal = solveRes[0]
SVal = SVal.subs(ν_0, lattice_viscosity_from_relaxation_rate(1 / τ_0)).expand() SVal = SVal.subs(ν_0, lattice_viscosity_from_relaxation_rate(1 / τ_0)).expand()
SVal SVal
``` ```
   
%% Output %% Output
   
$\displaystyle - \frac{\tau_{0}}{6 C_{S}^{2}} + \frac{\sqrt{72 C_{S}^{2} \Pi + 4 \tau_{0}^{2}}}{12 C_{S}^{2}}$ $\displaystyle - \frac{\tau_{0}}{6 C_{S}^{2}} + \frac{\sqrt{72 C_{S}^{2} \Pi + 4 \tau_{0}^{2}}}{12 C_{S}^{2}}$
___________________ ___________________
╱ 2 2 ╱ 2 2
τ₀ ╲╱ 72⋅C_S ⋅Π + 4⋅τ₀ τ₀ ╲╱ 72⋅C_S ⋅Π + 4⋅τ₀
- ────── + ────────────────────── - ────── + ──────────────────────
2 2 2 2
6⋅C_S 12⋅C_S 6⋅C_S 12⋅C_S
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
Knowning $|S|$ we can compute the total relaxation time using Knowning $|S|$ we can compute the total relaxation time using
   
$$\nu_{total} = \nu_0 +C_S^2 |S|$$ $$\nu_{total} = \nu_0 +C_S^2 |S|$$
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
τ_val = 1 / (relaxation_rate_from_lattice_viscosity(lattice_viscosity_from_relaxation_rate(1/τ_0) + C_S**2 * SVal)).cancel() τ_val = 1 / (relaxation_rate_from_lattice_viscosity(lattice_viscosity_from_relaxation_rate(1/τ_0) + C_S**2 * SVal)).cancel()
τ_val τ_val
``` ```
   
%% Output %% Output
   
$\displaystyle \frac{\tau_{0}}{2} + \frac{\sqrt{18 C_{S}^{2} \Pi + \tau_{0}^{2}}}{2}$ $\displaystyle \frac{\tau_{0}}{2} + \frac{\sqrt{18 C_{S}^{2} \Pi + \tau_{0}^{2}}}{2}$
_________________ _________________
╱ 2 2 ╱ 2 2
τ₀ ╲╱ 18⋅C_S ⋅Π + τ₀ τ₀ ╲╱ 18⋅C_S ⋅Π + τ₀
── + ──────────────────── ── + ────────────────────
2 2 2 2
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
To compute $\Pi^{(neq)}$ we use the following functions: To compute $\Pi^{(neq)}$ we use the following functions:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
def second_order_moment_tensor(function_values, stencil): def second_order_moment_tensor(function_values, stencil):
assert len(function_values) == len(stencil) assert len(function_values) == len(stencil)
dim = len(stencil[0]) dim = len(stencil[0])
return sp.Matrix(dim, dim, lambda i, j: sum(c[i] * c[j] * f for f, c in zip(function_values, stencil))) return sp.Matrix(dim, dim, lambda i, j: sum(c[i] * c[j] * f for f, c in zip(function_values, stencil)))
   
   
def frobenius_norm(matrix, factor=1): def frobenius_norm(matrix, factor=1):
return sp.sqrt(sum(i*i for i in matrix) * factor) return sp.sqrt(sum(i*i for i in matrix) * factor)
``` ```
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
In the next cell we construct equations that take an standard relaxation rate $\omega_0$ and compute a new relaxation rate $\omega_{total}$ according to the Smagorinksy model, using `τ_val` computed above In the next cell we construct equations that take an standard relaxation rate $\omega_0$ and compute a new relaxation rate $\omega_{total}$ according to the Smagorinksy model, using `τ_val` computed above
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
def smagorinsky_equations(ω_0, ω_total, method): def smagorinsky_equations(ω_0, ω_total, method):
f_neq = sp.Matrix(method.pre_collision_pdf_symbols) - method.get_equilibrium_terms() f_neq = sp.Matrix(method.pre_collision_pdf_symbols) - method.get_equilibrium_terms()
return [sp.Eq(τ_0, 1 / ω_0), return [sp.Eq(τ_0, 1 / ω_0),
sp.Eq(Π, frobenius_norm(second_order_moment_tensor(f_neq, method.stencil), factor=2)), sp.Eq(Π, frobenius_norm(second_order_moment_tensor(f_neq, method.stencil), factor=2)),
sp.Eq(ω_total, 1 / τ_val)] sp.Eq(ω_total, 1 / τ_val)]
   
   
smagorinsky_equations(ω_0, ω_total, create_lb_method()) smagorinsky_equations(ω_0, ω_total, create_lb_method())
``` ```
   
%% Output %% Output
   
$\displaystyle \left[ \tau_{0} = \frac{1}{\omega_{0}}, \ \Pi = \sqrt{4 \left(- f_{5} + f_{6} + f_{7} - f_{8} - u_{0} u_{1}\right)^{2} + 2 \left(- \frac{\delta_{\rho}}{3} + f_{1} + f_{2} + f_{5} + f_{6} + f_{7} + f_{8} - u_{1}^{2}\right)^{2} + 2 \left(- \frac{\delta_{\rho}}{3} + f_{3} + f_{4} + f_{5} + f_{6} + f_{7} + f_{8} - u_{0}^{2}\right)^{2}}, \ \omega_{total} = \frac{1}{\frac{\tau_{0}}{2} + \frac{\sqrt{18 C_{S}^{2} \Pi + \tau_{0}^{2}}}{2}}\right]$ $\displaystyle \left[ \tau_{0} = \frac{1}{\omega_{0}}, \ \Pi = \sqrt{4 \left(- f_{5} + f_{6} + f_{7} - f_{8} - u_{0} u_{1}\right)^{2} + 2 \left(- \frac{\delta_{\rho}}{3} + f_{1} + f_{2} + f_{5} + f_{6} + f_{7} + f_{8} - u_{1}^{2}\right)^{2} + 2 \left(- \frac{\delta_{\rho}}{3} + f_{3} + f_{4} + f_{5} + f_{6} + f_{7} + f_{8} - u_{0}^{2}\right)^{2}}, \ \omega_{total} = \frac{1}{\frac{\tau_{0}}{2} + \frac{\sqrt{18 C_{S}^{2} \Pi + \tau_{0}^{2}}}{2}}\right]$
⎡ ___________________________________________________________ ⎡ ___________________________________________________________
⎢ ╱ ⎢ ╱
⎢ 1 ╱ 2 ⎛ δᵨ ⎢ 1 ╱ 2 ⎛ δᵨ
⎢τ₀ = ──, Π = ╱ 4⋅(-f₅ + f₆ + f₇ - f₈ - u₀⋅u₁) + 2⋅⎜- ── + f₁ + f₂ + f₅ + ⎢τ₀ = ──, Π = ╱ 4⋅(-f₅ + f₆ + f₇ - f₈ - u₀⋅u₁) + 2⋅⎜- ── + f₁ + f₂ + f₅ +
⎢ ω₀ ╲╱ ⎝ 3 ⎢ ω₀ ╲╱ ⎝ 3
______________________________________________________________________ ______________________________________________________________________
2 2 2 2
2⎞ ⎛ δᵨ 2⎞ 2⎞ ⎛ δᵨ 2⎞
f₆ + f₇ + f₈ - u₁ ⎟ + 2⋅⎜- ── + f₃ + f₄ + f₅ + f₆ + f₇ + f₈ - u₀ ⎟ , ωₜₒₜₐₗ f₆ + f₇ + f₈ - u₁ ⎟ + 2⋅⎜- ── + f₃ + f₄ + f₅ + f₆ + f₇ + f₈ - u₀ ⎟ , ωₜₒₜₐₗ
⎠ ⎝ 3 ⎠ ⎠ ⎝ 3 ⎠
1 ⎥ 1 ⎥
= ─────────────────────────⎥ = ─────────────────────────⎥
_________________⎥ _________________⎥
╱ 2 2 ⎥ ╱ 2 2 ⎥
τ₀ ╲╱ 18⋅C_S ⋅Π + τ₀ ⎥ τ₀ ╲╱ 18⋅C_S ⋅Π + τ₀ ⎥
── + ────────────────────⎥ ── + ────────────────────⎥
2 2 ⎦ 2 2 ⎦
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
## 2) Application: Channel flow ## 2) Application: Channel flow
   
Next we modify a *lbmpy* scenario to use the Smagorinsky model. Next we modify a *lbmpy* scenario to use the Smagorinsky model.
We create a MRT method, where we fix all relaxation rates except the relaxation rate that controls the viscosity. We create a MRT method, where we fix all relaxation rates except the relaxation rate that controls the viscosity.
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
lbm_conifg = LBMConfig(stencil=Stencil.D2Q9, method=Method.MRT, force=(1e-6, 0), lbm_config = LBMConfig(stencil=Stencil.D2Q9, method=Method.MRT, force=(1e-6, 0),
force_model=ForceModel.LUO, relaxation_rates=[ω, 1.9, 1.9, 1.9]) force_model=ForceModel.LUO, relaxation_rates=[ω, 1.9, 1.9, 1.9])
   
method = create_lb_method(lbm_config=lbm_conifg) method = create_lb_method(lbm_config=lbm_config)
method method
``` ```
   
%% Output %% Output
   
<lbmpy.methods.momentbased.momentbasedmethod.MomentBasedLbMethod at 0x7f745d5c3b20> <lbmpy.methods.momentbased.momentbasedmethod.MomentBasedLbMethod at 0x7f745d5c3b20>
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
Only the collision rule has to be changed. Thus we first construct the collision rule, add the Smagorinsky equations and create a normal scenario from the modified collision rule. To avoid that the macroscopic quantity symbols in the Smagorinsky equations fall prey to optimization, we must disable simplification: Only the collision rule has to be changed. Thus we first construct the collision rule, add the Smagorinsky equations and create a normal scenario from the modified collision rule. To avoid that the macroscopic quantity symbols in the Smagorinsky equations fall prey to optimization, we must disable simplification:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
optimization = {'simplification' : False} optimization = {'simplification' : False}
collision_rule = create_lb_collision_rule(lb_method=method, optimization=optimization) collision_rule = create_lb_collision_rule(lb_method=method, optimization=optimization)
collision_rule = collision_rule.new_with_substitutions({ω: ω_total}) collision_rule = collision_rule.new_with_substitutions({ω: ω_total})
   
collision_rule.subexpressions += smagorinsky_equations(ω, ω_total, method) collision_rule.subexpressions += smagorinsky_equations(ω, ω_total, method)
collision_rule.topological_sort(sort_subexpressions=True, sort_main_assignments=False) collision_rule.topological_sort(sort_subexpressions=True, sort_main_assignments=False)
collision_rule collision_rule
``` ```
   
%% Output %% Output
   
AssignmentCollection: d_6, d_5, d_2, d_3, d_0, d_1, d_7, d_4, d_8 <- f(f_2, f_4, f_7, f_5, omega_total, f_6, f_3, f_1, f_0, f_8) AssignmentCollection: d_6, d_5, d_2, d_3, d_0, d_1, d_7, d_4, d_8 <- f(f_2, f_4, f_7, f_5, omega_total, f_6, f_3, f_1, f_0, f_8)
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
In the next cell the collision rule is simplified by extracting common subexpressions In the next cell the collision rule is simplified by extracting common subexpressions
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
from pystencils.simp import sympy_cse from pystencils.simp import sympy_cse
#collision_rule = sympy_cse(collision_rule) #collision_rule = sympy_cse(collision_rule)
``` ```
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
A channel scenario can be created from a modified collision rule: A channel scenario can be created from a modified collision rule:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
ch = create_channel((300, 100), force=1e-6, collision_rule=collision_rule, ch = create_channel((300, 100), force=1e-6, collision_rule=collision_rule,
kernel_params={"C_S": 0.12, "omega": 1.999}) kernel_params={"C_S": 0.12, "omega": 1.999})
``` ```
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
#show_code(ch.ast) #show_code(ch.ast)
``` ```
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
ch.run(5000) ch.run(5000)
``` ```
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
plt.figure(dpi=200) plt.figure(dpi=200)
plt.vector_field(ch.velocity[:, :]) plt.vector_field(ch.velocity[:, :])
np.max(ch.velocity[:, :]) np.max(ch.velocity[:, :])
``` ```
   
%% Output %% Output
   
$\displaystyle 0.00504266401703371$ $\displaystyle 0.00504266401703371$
0.00504266401703371 0.00504266401703371
   
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
## Appendix: Strain rate tensor formula from Chapman Enskog ## Appendix: Strain rate tensor formula from Chapman Enskog
   
The connection between $S_{ij}$ and $\Pi_{ij}^{(neq)}$ can be seen using a Chapman Enskog expansion. Since *lbmpy* has a module that automatically does this expansions we can have a look at it: The connection between $S_{ij}$ and $\Pi_{ij}^{(neq)}$ can be seen using a Chapman Enskog expansion. Since *lbmpy* has a module that automatically does this expansions we can have a look at it:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
from lbmpy.chapman_enskog import ChapmanEnskogAnalysis, CeMoment from lbmpy.chapman_enskog import ChapmanEnskogAnalysis, CeMoment
from lbmpy.chapman_enskog.chapman_enskog import remove_higher_order_u from lbmpy.chapman_enskog.chapman_enskog import remove_higher_order_u
compressible_model = create_lb_method(stencil=Stencil.D2Q9, compressible=True, zero_centered=False) compressible_model = create_lb_method(stencil=Stencil.D2Q9, compressible=True, zero_centered=False)
incompressible_model = create_lb_method(stencil=Stencil.D2Q9, compressible=False, zero_centered=False) incompressible_model = create_lb_method(stencil=Stencil.D2Q9, compressible=False, zero_centered=False)
   
ce_compressible = ChapmanEnskogAnalysis(compressible_model) ce_compressible = ChapmanEnskogAnalysis(compressible_model)
ce_incompressible = ChapmanEnskogAnalysis(incompressible_model) ce_incompressible = ChapmanEnskogAnalysis(incompressible_model)
``` ```
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
The Chapman Enskog analysis yields expresssions for the moment The Chapman Enskog analysis yields expresssions for the moment
   
$\Pi = \Pi^{(eq)} + \epsilon \Pi^{(1)} + \epsilon^2 \Pi^{(2)} \cdots$ $\Pi = \Pi^{(eq)} + \epsilon \Pi^{(1)} + \epsilon^2 \Pi^{(2)} \cdots$
and the strain rate tensor is related to $\Pi^{(1)}$. However the best approximation we have for $\Pi^{(1)}$ is and the strain rate tensor is related to $\Pi^{(1)}$. However the best approximation we have for $\Pi^{(1)}$ is
$\Pi^{(neq)}$. For details, see the paper "Shear stress in lattice Boltzmann simulations" by Krüger, Varnik and Raabe from 2009. $\Pi^{(neq)}$. For details, see the paper "Shear stress in lattice Boltzmann simulations" by Krüger, Varnik and Raabe from 2009.
   
Lets look at the values of $\Pi^{(1)}$ obtained from the Chapman enskog expansion: Lets look at the values of $\Pi^{(1)}$ obtained from the Chapman enskog expansion:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
Π_1_xy = CeMoment("\\Pi", moment_tuple=(1,1), superscript=1) Π_1_xy = CeMoment("\\Pi", moment_tuple=(1,1), superscript=1)
Π_1_xx = CeMoment("\\Pi", moment_tuple=(2,0), superscript=1) Π_1_xx = CeMoment("\\Pi", moment_tuple=(2,0), superscript=1)
Π_1_yy = CeMoment("\\Pi", moment_tuple=(0,2), superscript=1) Π_1_yy = CeMoment("\\Pi", moment_tuple=(0,2), superscript=1)
components = (Π_1_xx, Π_1_yy, Π_1_xy) components = (Π_1_xx, Π_1_yy, Π_1_xy)
   
Π_1_xy_val = ce_compressible.higher_order_moments[Π_1_xy] Π_1_xy_val = ce_compressible.higher_order_moments[Π_1_xy]
Π_1_xy_val Π_1_xy_val
``` ```
   
%% Output %% Output
   
$\displaystyle \frac{\rho u_{0}^{2} {\partial^{(1)}_{0} u_{1}} + 2 \rho u_{0} u_{1} {\partial^{(1)}_{0} u_{0}} + 2 \rho u_{0} u_{1} {\partial^{(1)}_{1} u_{1}} + \rho u_{1}^{2} {\partial^{(1)}_{1} u_{0}} - \frac{\rho {\partial^{(1)}_{1} u_{0}}}{3} - \frac{\rho {\partial^{(1)}_{0} u_{1}}}{3} + u_{0}^{2} u_{1} {\partial^{(1)}_{0} \rho} + u_{0} u_{1}^{2} {\partial^{(1)}_{1} \rho}}{\omega}$ $\displaystyle \frac{\rho u_{0}^{2} {\partial^{(1)}_{0} u_{1}} + 2 \rho u_{0} u_{1} {\partial^{(1)}_{0} u_{0}} + 2 \rho u_{0} u_{1} {\partial^{(1)}_{1} u_{1}} + \rho u_{1}^{2} {\partial^{(1)}_{1} u_{0}} - \frac{\rho {\partial^{(1)}_{1} u_{0}}}{3} - \frac{\rho {\partial^{(1)}_{0} u_{1}}}{3} + u_{0}^{2} u_{1} {\partial^{(1)}_{0} \rho} + u_{0} u_{1}^{2} {\partial^{(1)}_{1} \rho}}{\omega}$
2 2 ρ⋅D(u_0) 2 2 ρ⋅D(u_0)
ρ⋅u₀ ⋅D(u_1) + 2⋅ρ⋅u₀⋅u₁⋅D(u_0) + 2⋅ρ⋅u₀⋅u₁⋅D(u_1) + ρ⋅u₁ ⋅D(u_0) - ──────── - ρ⋅u₀ ⋅D(u_1) + 2⋅ρ⋅u₀⋅u₁⋅D(u_0) + 2⋅ρ⋅u₀⋅u₁⋅D(u_1) + ρ⋅u₁ ⋅D(u_0) - ──────── -
3 3
────────────────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────────
ω ω
ρ⋅D(u_1) 2 2 ρ⋅D(u_1) 2 2
──────── + u₀ ⋅u₁⋅D(rho) + u₀⋅u₁ ⋅D(rho) ──────── + u₀ ⋅u₁⋅D(rho) + u₀⋅u₁ ⋅D(rho)
3 3
───────────────────────────────────────── ─────────────────────────────────────────
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
This term has lots of higher order error terms in it. We assume that $u$ is small in lattice coordinates, so if we neglect all terms in $u$ that are quadratic or higher we get: This term has lots of higher order error terms in it. We assume that $u$ is small in lattice coordinates, so if we neglect all terms in $u$ that are quadratic or higher we get:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
remove_higher_order_u(Π_1_xy_val.expand()) remove_higher_order_u(Π_1_xy_val.expand())
``` ```
   
%% Output %% Output
   
$\displaystyle - \frac{\rho {\partial^{(1)}_{1} u_{0}}}{3 \omega} - \frac{\rho {\partial^{(1)}_{0} u_{1}}}{3 \omega}$ $\displaystyle - \frac{\rho {\partial^{(1)}_{1} u_{0}}}{3 \omega} - \frac{\rho {\partial^{(1)}_{0} u_{1}}}{3 \omega}$
ρ⋅D(u_0) ρ⋅D(u_1) ρ⋅D(u_0) ρ⋅D(u_1)
- ──────── - ──────── - ──────── - ────────
3⋅ω 3⋅ω 3⋅ω 3⋅ω
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
Putting these steps together into a function, we can display them for the different cases quickly: Putting these steps together into a function, we can display them for the different cases quickly:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
def get_Π_1(ce_analysis, component): def get_Π_1(ce_analysis, component):
val = ce_analysis.higher_order_moments[component] val = ce_analysis.higher_order_moments[component]
return remove_higher_order_u(val.expand()) return remove_higher_order_u(val.expand())
``` ```
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
Compressible case: Compressible case:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
tuple(get_Π_1(ce_compressible, Pi) for Pi in components) tuple(get_Π_1(ce_compressible, Pi) for Pi in components)
``` ```
   
%% Output %% Output
   
$\displaystyle \left( - \frac{2 \rho {\partial^{(1)}_{0} u_{0}}}{3 \omega}, \ - \frac{2 \rho {\partial^{(1)}_{1} u_{1}}}{3 \omega}, \ - \frac{\rho {\partial^{(1)}_{1} u_{0}}}{3 \omega} - \frac{\rho {\partial^{(1)}_{0} u_{1}}}{3 \omega}\right)$ $\displaystyle \left( - \frac{2 \rho {\partial^{(1)}_{0} u_{0}}}{3 \omega}, \ - \frac{2 \rho {\partial^{(1)}_{1} u_{1}}}{3 \omega}, \ - \frac{\rho {\partial^{(1)}_{1} u_{0}}}{3 \omega} - \frac{\rho {\partial^{(1)}_{0} u_{1}}}{3 \omega}\right)$
⎛-2⋅ρ⋅D(u_0) -2⋅ρ⋅D(u_1) ρ⋅D(u_0) ρ⋅D(u_1)⎞ ⎛-2⋅ρ⋅D(u_0) -2⋅ρ⋅D(u_1) ρ⋅D(u_0) ρ⋅D(u_1)⎞
⎜────────────, ────────────, - ──────── - ────────⎟ ⎜────────────, ────────────, - ──────── - ────────⎟
⎝ 3⋅ω 3⋅ω 3⋅ω 3⋅ω ⎠ ⎝ 3⋅ω 3⋅ω 3⋅ω 3⋅ω ⎠
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
Incompressible case: Incompressible case:
   
%% Cell type:code id: tags: %% Cell type:code id: tags:
   
``` python ``` python
tuple(get_Π_1(ce_incompressible, Pi) for Pi in components) tuple(get_Π_1(ce_incompressible, Pi) for Pi in components)
``` ```
   
%% Output %% Output
   
$\displaystyle \left( \frac{2 u_{0} {\partial^{(1)}_{0} \rho}}{3 \omega} - \frac{2 {\partial^{(1)}_{0} u_{0}}}{3 \omega}, \ \frac{2 u_{1} {\partial^{(1)}_{1} \rho}}{3 \omega} - \frac{2 {\partial^{(1)}_{1} u_{1}}}{3 \omega}, \ \frac{u_{0} {\partial^{(1)}_{1} \rho}}{3 \omega} + \frac{u_{1} {\partial^{(1)}_{0} \rho}}{3 \omega} - \frac{{\partial^{(1)}_{1} u_{0}}}{3 \omega} - \frac{{\partial^{(1)}_{0} u_{1}}}{3 \omega}\right)$ $\displaystyle \left( \frac{2 u_{0} {\partial^{(1)}_{0} \rho}}{3 \omega} - \frac{2 {\partial^{(1)}_{0} u_{0}}}{3 \omega}, \ \frac{2 u_{1} {\partial^{(1)}_{1} \rho}}{3 \omega} - \frac{2 {\partial^{(1)}_{1} u_{1}}}{3 \omega}, \ \frac{u_{0} {\partial^{(1)}_{1} \rho}}{3 \omega} + \frac{u_{1} {\partial^{(1)}_{0} \rho}}{3 \omega} - \frac{{\partial^{(1)}_{1} u_{0}}}{3 \omega} - \frac{{\partial^{(1)}_{0} u_{1}}}{3 \omega}\right)$
⎛2⋅u₀⋅D(rho) 2⋅D(u_0) 2⋅u₁⋅D(rho) 2⋅D(u_1) u₀⋅D(rho) u₁⋅D(rho) D(u_0 ⎛2⋅u₀⋅D(rho) 2⋅D(u_0) 2⋅u₁⋅D(rho) 2⋅D(u_1) u₀⋅D(rho) u₁⋅D(rho) D(u_0
⎜─────────── - ────────, ─────────── - ────────, ───────── + ───────── - ───── ⎜─────────── - ────────, ─────────── - ────────, ───────── + ───────── - ─────
⎝ 3⋅ω 3⋅ω 3⋅ω 3⋅ω 3⋅ω 3⋅ω 3⋅ω ⎝ 3⋅ω 3⋅ω 3⋅ω 3⋅ω 3⋅ω 3⋅ω 3⋅ω
) D(u_1)⎞ ) D(u_1)⎞
─ - ──────⎟ ─ - ──────⎟
3⋅ω ⎠ 3⋅ω ⎠
   
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
   
In the incompressible case has some terms $\partial \rho$ which are zero, since $\rho$ is assumed constant. In the incompressible case has some terms $\partial \rho$ which are zero, since $\rho$ is assumed constant.
   
Leaving out the error terms we finally obtain: Leaving out the error terms we finally obtain:
   
   
$$\Pi_{ij}^{(neq)} \approx \Pi_{ij}^{(1)} = -\frac{2 \rho_{(0)}}{3 \omega_s} \left( \partial_i u_j + \partial_j u_i \right)$$ $$\Pi_{ij}^{(neq)} \approx \Pi_{ij}^{(1)} = -\frac{2 \rho_{(0)}}{3 \omega_s} \left( \partial_i u_j + \partial_j u_i \right)$$
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# The conservative Allen-Cahn model for high Reynolds number, two phase flow with large-density and viscosity constrast # The conservative Allen-Cahn model for high Reynolds number, two phase flow with large-density and viscosity constrast
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
from pystencils.session import * from pystencils.session import *
from lbmpy.session import * from lbmpy.session import *
from pystencils.simp import sympy_cse
from pystencils.boundaries import BoundaryHandling from pystencils.boundaries import BoundaryHandling
from lbmpy.phasefield_allen_cahn.contact_angle import ContactAngle from lbmpy.phasefield_allen_cahn.contact_angle import ContactAngle
from lbmpy.phasefield_allen_cahn.kernel_equations import * from lbmpy.phasefield_allen_cahn.kernel_equations import *
from lbmpy.phasefield_allen_cahn.parameter_calculation import calculate_parameters_rti, AllenCahnParameters from lbmpy.phasefield_allen_cahn.parameter_calculation import calculate_parameters_rti, AllenCahnParameters
from lbmpy.advanced_streaming import LBMPeriodicityHandling from lbmpy.advanced_streaming import LBMPeriodicityHandling
from lbmpy.boundaries import NoSlip, LatticeBoltzmannBoundaryHandling from lbmpy.boundaries import NoSlip, LatticeBoltzmannBoundaryHandling
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If `pycuda` is installed the simulation automatically runs on GPU If `cupy` is installed the simulation automatically runs on GPU
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
try: try:
import pycuda import cupy
except ImportError: except ImportError:
pycuda = None cupy = None
gpu = False gpu = False
target = ps.Target.CPU target = ps.Target.CPU
print('No pycuda installed') print('No cupy installed')
if pycuda: if cupy:
gpu = True gpu = True
target = ps.Target.GPU target = ps.Target.GPU
``` ```
%% Output
No pycuda installed
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The conservative Allen-Cahn model (CACM) for two-phase flow is based on the work of Fakhari et al. (2017) [Improved locality of the phase-field lattice-Boltzmann model for immiscible fluids at high density ratios](http://dx.doi.org/10.1103/PhysRevE.96.053301). The model can be created for two-dimensional problems as well as three-dimensional problems, which have been described by Mitchell et al. (2018) [Development of a three-dimensional The conservative Allen-Cahn model (CACM) for two-phase flow is based on the work of Fakhari et al. (2017) [Improved locality of the phase-field lattice-Boltzmann model for immiscible fluids at high density ratios](http://dx.doi.org/10.1103/PhysRevE.96.053301). The model can be created for two-dimensional problems as well as three-dimensional problems, which have been described by Mitchell et al. (2018) [Development of a three-dimensional
phase-field lattice Boltzmann method for the study of immiscible fluids at high density ratios](http://dx.doi.org/10.1103/PhysRevE.96.053301). Furthermore, cascaded lattice Boltzmann methods can be combined with the model which was described in [A cascaded phase-field lattice Boltzmann model for the simulation of incompressible, immiscible fluids with high density contrast](http://dx.doi.org/10.1016/j.camwa.2019.08.018) phase-field lattice Boltzmann method for the study of immiscible fluids at high density ratios](http://dx.doi.org/10.1103/PhysRevE.96.053301). Furthermore, cascaded lattice Boltzmann methods can be combined with the model which was described in [A cascaded phase-field lattice Boltzmann model for the simulation of incompressible, immiscible fluids with high density contrast](http://dx.doi.org/10.1016/j.camwa.2019.08.018)
The CACM is suitable for simulating highly complex two phase flow problems with high-density ratios and high Reynolds numbers. In this tutorial, an overview is provided on how to derive the model with lbmpy. For this, the model is defined with two LBM populations. One for the interface tracking, which we call the phase-field LB step and one for recovering the hydrodynamic properties. The latter is called the hydrodynamic LB step. The CACM is suitable for simulating highly complex two phase flow problems with high-density ratios and high Reynolds numbers. In this tutorial, an overview is provided on how to derive the model with lbmpy. For this, the model is defined with two LBM populations. One for the interface tracking, which we call the phase-field LB step and one for recovering the hydrodynamic properties. The latter is called the hydrodynamic LB step.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Geometry Setup ## Geometry Setup
First of all, the stencils for the phase-field LB step as well as the stencil for the hydrodynamic LB step are defined. According to the stencils, the simulation can be performed in either 2D- or 3D-space. For 2D simulations, only the D2Q9 stencil is supported. For 3D simulations, the D3Q15, D3Q19 and the D3Q27 stencil are supported. Note here that the cascaded LBM can not be derived for D3Q15 stencils. First of all, the stencils for the phase-field LB step as well as the stencil for the hydrodynamic LB step are defined. According to the stencils, the simulation can be performed in either 2D- or 3D-space. For 2D simulations, only the D2Q9 stencil is supported. For 3D simulations, the D3Q15, D3Q19 and the D3Q27 stencil are supported. Note here that the cascaded LBM can not be derived for D3Q15 stencils.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
stencil_phase = LBStencil(Stencil.D2Q9) stencil_phase = LBStencil(Stencil.D2Q9)
stencil_hydro = LBStencil(Stencil.D2Q9) stencil_hydro = LBStencil(Stencil.D2Q9)
assert(len(stencil_phase[0]) == len(stencil_hydro[0])) assert(len(stencil_phase[0]) == len(stencil_hydro[0]))
dimensions = len(stencil_phase[0]) dimensions = len(stencil_phase[0])
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Definition of the domain size Definition of the domain size
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# domain # domain
L0 = 256 L0 = 256
domain_size = (L0, 4 * L0) domain_size = (L0, 4 * L0)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Parameter definition ## Parameter definition
The next step is to calculate all parameters which are needed for the simulation. In this example, a Rayleigh-Taylor instability test case is set up. The parameter calculation for this setup is already implemented in lbmpy and can be used with the dimensionless parameters which describe the problem. The next step is to calculate all parameters which are needed for the simulation. In this example, a Rayleigh-Taylor instability test case is set up. The parameter calculation for this setup is already implemented in lbmpy and can be used with the dimensionless parameters which describe the problem.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# time step # time step
timesteps = 8000 timesteps = 8000
# reference time # reference time
reference_time = 4000 reference_time = 4000
# calculate the parameters for the RTI # calculate the parameters for the RTI
parameters = calculate_parameters_rti(reference_length=L0, parameters = calculate_parameters_rti(reference_length=L0,
reference_time=reference_time, reference_time=reference_time,
density_heavy=1.0, density_heavy=1.0,
capillary_number=0.44, capillary_number=0.44,
reynolds_number=3000, reynolds_number=3000,
atwood_number=0.998, atwood_number=0.998,
peclet_number=1000, peclet_number=1000,
density_ratio=1000, density_ratio=1000,
viscosity_ratio=100) viscosity_ratio=100)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This function returns a `AllenCahnParameters` class. It is struct like class holding all parameters for the conservative Allen Cahn model: This function returns a `AllenCahnParameters` class. It is struct like class holding all parameters for the conservative Allen Cahn model:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
parameters parameters
``` ```
%% Output %% Output
<lbmpy.phasefield_allen_cahn.parameter_calculation.AllenCahnParameters at 0x126d30cd0> <lbmpy.phasefield_allen_cahn.parameter_calculation.AllenCahnParameters at 0x1329b88b0>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Fields ## Fields
As a next step all fields which are needed get defined. To do so, we create a `datahandling` object. More details about it can be found in the third tutorial of the [pystencils framework]( http://pycodegen.pages.walberla.net/pystencils/). This object holds all fields and manages the kernel runs. As a next step all fields which are needed get defined. To do so, we create a `datahandling` object. More details about it can be found in the third tutorial of the [pystencils framework]( http://pycodegen.pages.walberla.net/pystencils/). This object holds all fields and manages the kernel runs.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# create a datahandling object # create a datahandling object
dh = ps.create_data_handling((domain_size), periodicity=(True, False), parallel=False, default_target=target) dh = ps.create_data_handling((domain_size), periodicity=(True, False), parallel=False, default_target=target)
# pdf fields. g is used for hydrodynamics and h for the interface tracking # pdf fields. g is used for hydrodynamics and h for the interface tracking
g = dh.add_array("g", values_per_cell=len(stencil_hydro)) g = dh.add_array("g", values_per_cell=len(stencil_hydro))
dh.fill("g", 0.0, ghost_layers=True) dh.fill("g", 0.0, ghost_layers=True)
h = dh.add_array("h",values_per_cell=len(stencil_phase)) h = dh.add_array("h",values_per_cell=len(stencil_phase))
dh.fill("h", 0.0, ghost_layers=True) dh.fill("h", 0.0, ghost_layers=True)
g_tmp = dh.add_array("g_tmp", values_per_cell=len(stencil_hydro)) g_tmp = dh.add_array("g_tmp", values_per_cell=len(stencil_hydro))
dh.fill("g_tmp", 0.0, ghost_layers=True) dh.fill("g_tmp", 0.0, ghost_layers=True)
h_tmp = dh.add_array("h_tmp",values_per_cell=len(stencil_phase)) h_tmp = dh.add_array("h_tmp",values_per_cell=len(stencil_phase))
dh.fill("h_tmp", 0.0, ghost_layers=True) dh.fill("h_tmp", 0.0, ghost_layers=True)
# velocity field # velocity field
u = dh.add_array("u", values_per_cell=dh.dim) u = dh.add_array("u", values_per_cell=dh.dim)
dh.fill("u", 0.0, ghost_layers=True) dh.fill("u", 0.0, ghost_layers=True)
# phase-field # phase-field
C = dh.add_array("C") C = dh.add_array("C")
dh.fill("C", 0.0, ghost_layers=True) dh.fill("C", 0.0, ghost_layers=True)
C_tmp = dh.add_array("C_tmp") C_tmp = dh.add_array("C_tmp")
dh.fill("C_tmp", 0.0, ghost_layers=True) dh.fill("C_tmp", 0.0, ghost_layers=True)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As a next step the relaxation time is stated in a symbolic form. It is calculated via interpolation. As a next step the relaxation time is stated in a symbolic form. It is calculated via interpolation.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
rho_L = parameters.symbolic_density_light rho_L = parameters.symbolic_density_light
rho_H = parameters.symbolic_density_heavy rho_H = parameters.symbolic_density_heavy
density = rho_L + C.center * (rho_H - rho_L) density = rho_L + C.center * (rho_H - rho_L)
body_force = [0, 0, 0] body_force = [0, 0, 0]
body_force[1] = parameters.symbolic_gravitational_acceleration * density body_force[1] = parameters.symbolic_gravitational_acceleration * density
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Definition of the lattice Boltzmann methods ## Definition of the lattice Boltzmann methods
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
For both LB steps, a weighted orthogonal MRT (WMRT) method is used. It is also possible to change the method to a simpler SRT scheme or a more complicated CLBM scheme. The CLBM scheme can be obtained with `Method.CENTRAL_MOMENT`. Note here that the hydrodynamic LB step is formulated as an incompressible velocity-based LBM. Thus, the velocity terms can not be removed from the equilibrium in the central moment space. For both LB steps, a weighted orthogonal MRT (WMRT) method is used. It is also possible to change the method to a simpler SRT scheme or a more complicated CLBM scheme. The CLBM scheme can be obtained with `Method.CENTRAL_MOMENT`. Note here that the hydrodynamic LB step is formulated as an incompressible velocity-based LBM. Thus, the velocity terms can not be removed from the equilibrium in the central moment space.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
w_c = parameters.symbolic_omega_phi w_c = parameters.symbolic_omega_phi
config_phase = LBMConfig(stencil=stencil_phase, method=Method.MRT, compressible=True, config_phase = LBMConfig(stencil=stencil_phase, method=Method.MRT, compressible=True,
delta_equilibrium=False, delta_equilibrium=False,
force=sp.symbols("F_:2"), velocity_input=u, force=sp.symbols("F_:2"), velocity_input=u,
weighted=True, relaxation_rates=[0, w_c, w_c, 1, 1, 1, 1, 1, 1], weighted=True, relaxation_rates=[0, w_c, w_c, 1, 1, 1, 1, 1, 1],
output={'density': C_tmp}, kernel_type='stream_pull_collide') output={'density': C_tmp})
method_phase = create_lb_method(lbm_config=config_phase) method_phase = create_lb_method(lbm_config=config_phase)
method_phase method_phase
``` ```
%% Output %% Output
<lbmpy.methods.momentbased.momentbasedmethod.MomentBasedLbMethod at 0x126d44ee0> <lbmpy.methods.momentbased.momentbasedmethod.MomentBasedLbMethod at 0x1346b22e0>
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
omega = parameters.omega(C) omega = parameters.omega(C)
config_hydro = LBMConfig(stencil=stencil_hydro, method=Method.MRT, compressible=False, config_hydro = LBMConfig(stencil=stencil_hydro, method=Method.MRT, compressible=False,
weighted=True, relaxation_rates=[omega, 1, 1, 1], weighted=True, relaxation_rates=[omega, 1, 1, 1],
force=sp.symbols("F_:2"), force=sp.symbols("F_:2"), output={'velocity': u})
output={'velocity': u}, kernel_type='collide_stream_push')
method_hydro = create_lb_method(lbm_config=config_hydro) method_hydro = create_lb_method(lbm_config=config_hydro)
method_hydro method_hydro
``` ```
%% Output %% Output
<lbmpy.methods.momentbased.momentbasedmethod.MomentBasedLbMethod at 0x126d22eb0> <lbmpy.methods.momentbased.momentbasedmethod.MomentBasedLbMethod at 0x137772a30>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Initialization ## Initialization
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The probability distribution functions (pdfs) are initialised with the equilibrium distribution for the LB methods. The probability distribution functions (pdfs) are initialised with the equilibrium distribution for the LB methods.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
h_updates = initializer_kernel_phase_field_lb(method_phase, C, u, h, parameters) h_updates = initializer_kernel_phase_field_lb(method_phase, C, u, h, parameters)
g_updates = initializer_kernel_hydro_lb(method_hydro, 1, u, g) g_updates = initializer_kernel_hydro_lb(method_hydro, 1, u, g)
h_init = ps.create_kernel(h_updates, target=dh.default_target, cpu_openmp=True).compile() h_init = ps.create_kernel(h_updates, target=dh.default_target, cpu_openmp=True).compile()
g_init = ps.create_kernel(g_updates, target=dh.default_target, cpu_openmp=True).compile() g_init = ps.create_kernel(g_updates, target=dh.default_target, cpu_openmp=True).compile()
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Following this, the phase field is initialised directly in python. Following this, the phase field is initialised directly in python.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# initialize the domain # initialize the domain
def Initialize_distributions(): def Initialize_distributions():
Nx = domain_size[0] Nx = domain_size[0]
Ny = domain_size[1] Ny = domain_size[1]
for block in dh.iterate(ghost_layers=True, inner_ghost_layers=False): for block in dh.iterate(ghost_layers=True, inner_ghost_layers=False):
x = np.zeros_like(block.midpoint_arrays[0]) x = np.zeros_like(block.midpoint_arrays[0])
x[:, :] = block.midpoint_arrays[0] x[:, :] = block.midpoint_arrays[0]
y = np.zeros_like(block.midpoint_arrays[1]) y = np.zeros_like(block.midpoint_arrays[1])
y[:, :] = block.midpoint_arrays[1] y[:, :] = block.midpoint_arrays[1]
y -= 2 * L0 y -= 2 * L0
tmp = 0.1 * Nx * np.cos((2 * np.pi * x) / Nx) tmp = 0.1 * Nx * np.cos((2 * np.pi * x) / Nx)
init_values = 0.5 + 0.5 * np.tanh((y - tmp) / (parameters.interface_thickness / 2)) init_values = 0.5 + 0.5 * np.tanh((y - tmp) / (parameters.interface_thickness / 2))
block["C"][:, :] = init_values block["C"][:, :] = init_values
block["C_tmp"][:, :] = init_values block["C_tmp"][:, :] = init_values
if gpu: if gpu:
dh.all_to_gpu() dh.all_to_gpu()
dh.run_kernel(h_init, **parameters.symbolic_to_numeric_map) dh.run_kernel(h_init, **parameters.symbolic_to_numeric_map)
dh.run_kernel(g_init) dh.run_kernel(g_init)
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
Initialize_distributions() Initialize_distributions()
plt.scalar_field(dh.gather_array(C.name)) plt.scalar_field(dh.gather_array(C.name))
``` ```
%% Output %% Output
<matplotlib.image.AxesImage at 0x1417f86d0> <matplotlib.image.AxesImage at 0x137ad6bb0>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Source Terms ## Source Terms
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
For the Allen-Cahn LB step, the Allen-Cahn equation needs to be applied as a source term. Here, a simple forcing model is used which is directly applied in the moment space: For the Allen-Cahn LB step, the Allen-Cahn equation needs to be applied as a source term. Here, a simple forcing model is used which is directly applied in the moment space:
$$ $$
F_i^\phi (\boldsymbol{x}, t) = \Delta t \frac{\left[1 - 4 \left(\phi - \phi_0\right)^2\right]}{\xi} w_i \boldsymbol{c}_i \cdot \frac{\nabla \phi}{|{\nabla \phi}|}, F_i^\phi (\boldsymbol{x}, t) = \Delta t \frac{\left[1 - 4 \left(\phi - \phi_0\right)^2\right]}{\xi} w_i \boldsymbol{c}_i \cdot \frac{\nabla \phi}{|{\nabla \phi}|},
$$ $$
where $\phi$ is the phase-field, $\phi_0$ is the interface location, $\Delta t$ it the timestep size $\xi$ is the interface width, $\boldsymbol{c}_i$ is the discrete direction from stencil_phase and $w_i$ are the weights. Furthermore, the equilibrium needs to be shifted: where $\phi$ is the phase-field, $\phi_0$ is the interface location, $\Delta t$ it the timestep size $\xi$ is the interface width, $\boldsymbol{c}_i$ is the discrete direction from stencil_phase and $w_i$ are the weights. Furthermore, the equilibrium needs to be shifted:
$$ $$
\bar{h}^{eq}_\alpha = h^{eq}_\alpha - \frac{1}{2} F^\phi_\alpha \bar{h}^{eq}_\alpha = h^{eq}_\alpha - \frac{1}{2} F^\phi_\alpha
$$ $$
The hydrodynamic force is given by: The hydrodynamic force is given by:
$$ $$
F_i (\boldsymbol{x}, t) = \Delta t w_i \frac{\boldsymbol{c}_i \boldsymbol{F}}{\rho c_s^2}, F_i (\boldsymbol{x}, t) = \Delta t w_i \frac{\boldsymbol{c}_i \boldsymbol{F}}{\rho c_s^2},
$$ $$
where $\rho$ is the interpolated density and $\boldsymbol{F}$ is the source term which consists of the pressure force where $\rho$ is the interpolated density and $\boldsymbol{F}$ is the source term which consists of the pressure force
$$ $$
\boldsymbol{F}_p = -p^* c_s^2 \nabla \rho, \boldsymbol{F}_p = -p^* c_s^2 \nabla \rho,
$$ $$
the surface tension force: the surface tension force:
$$ $$
\boldsymbol{F}_s = \mu_\phi \nabla \phi \boldsymbol{F}_s = \mu_\phi \nabla \phi
$$ $$
and the viscous force term: and the viscous force term:
$$ $$
F_{\mu, i}^{\mathrm{MRT}} = - \frac{\nu}{c_s^2 \Delta t} \left[\sum_{\beta} c_{\beta i} c_{\beta j} \times \sum_{\alpha} \Omega_{\beta \alpha}(g_\alpha - g_\alpha^{\mathrm{eq}})\right] \frac{\partial \rho}{\partial x_j}. F_{\mu, i}^{\mathrm{MRT}} = - \frac{\nu}{c_s^2 \Delta t} \left[\sum_{\beta} c_{\beta i} c_{\beta j} \times \sum_{\alpha} \Omega_{\beta \alpha}(g_\alpha - g_\alpha^{\mathrm{eq}})\right] \frac{\partial \rho}{\partial x_j}.
$$ $$
In the above equations $p^*$ is the normalised pressure which can be obtained from the zeroth order moment of the hydrodynamic distribution function $g$. The lattice speed of sound is given with $c_s$ and the chemical potential is $\mu_\phi$. Furthermore, the viscosity is $\nu$ and $\Omega$ is the moment-based collision operator. Note here that the hydrodynamic equilibrium is also adjusted as shown above for the phase-field distribution functions. In the above equations $p^*$ is the normalised pressure which can be obtained from the zeroth order moment of the hydrodynamic distribution function $g$. The lattice speed of sound is given with $c_s$ and the chemical potential is $\mu_\phi$. Furthermore, the viscosity is $\nu$ and $\Omega$ is the moment-based collision operator. Note here that the hydrodynamic equilibrium is also adjusted as shown above for the phase-field distribution functions.
For CLBM methods the forcing is applied directly in the central moment space. This is done with the `CentralMomentMultiphaseForceModel`. Furthermore, the GUO force model is applied here to be consistent with [A cascaded phase-field lattice Boltzmann model for the simulation of incompressible, immiscible fluids with high density contrast](http://dx.doi.org/10.1016/j.camwa.2019.08.018). Here we refer to equation D.7 which can be derived for 3D stencils automatically with lbmpy. For CLBM methods the forcing is applied directly in the central moment space. This is done with the `CentralMomentMultiphaseForceModel`. Furthermore, the GUO force model is applied here to be consistent with [A cascaded phase-field lattice Boltzmann model for the simulation of incompressible, immiscible fluids with high density contrast](http://dx.doi.org/10.1016/j.camwa.2019.08.018). Here we refer to equation D.7 which can be derived for 3D stencils automatically with lbmpy.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
force_h = interface_tracking_force(C, stencil_phase, parameters) force_h = interface_tracking_force(C, stencil_phase, parameters)
hydro_force = hydrodynamic_force(g, C, method_hydro, parameters, body_force) hydro_force = hydrodynamic_force(C, method_hydro, parameters, body_force)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Definition of the LB update rules ## Definition of the LB update rules
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The update rule for the phase-field LB step is defined as: The update rule for the phase-field LB step is defined as:
$$ $$
h_i (\boldsymbol{x} + \boldsymbol{c}_i \Delta t, t + \Delta t) = h_i(\boldsymbol{x}, t) + \Omega_{ij}^h(\bar{h_j}^{eq} - h_j)|_{(\boldsymbol{x}, t)} + F_i^\phi(\boldsymbol{x}, t). h_i (\boldsymbol{x} + \boldsymbol{c}_i \Delta t, t + \Delta t) = h_i(\boldsymbol{x}, t) + \Omega_{ij}^h(\bar{h_j}^{eq} - h_j)|_{(\boldsymbol{x}, t)} + F_i^\phi(\boldsymbol{x}, t).
$$ $$
In our framework the pull scheme is applied as streaming step. Furthermore, the update of the phase-field is directly integrated into the kernel. As a result of this, a second temporary phase-field is needed. In our framework the pull scheme is applied as streaming step. Furthermore, the update of the phase-field is directly integrated into the kernel. As a result of this, a second temporary phase-field is needed.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
lbm_optimisation = LBMOptimisation(symbolic_field=h, symbolic_temporary_field=h_tmp) lbm_optimisation = LBMOptimisation(symbolic_field=h, symbolic_temporary_field=h_tmp)
allen_cahn_update_rule = create_lb_update_rule(lbm_config=config_phase, allen_cahn_update_rule = create_lb_update_rule(lbm_config=config_phase,
lbm_optimisation=lbm_optimisation) lbm_optimisation=lbm_optimisation)
allen_cahn_update_rule = add_interface_tracking_force(allen_cahn_update_rule, force_h) allen_cahn_update_rule = add_interface_tracking_force(allen_cahn_update_rule, force_h)
ast_kernel = ps.create_kernel(allen_cahn_update_rule, target=dh.default_target, cpu_openmp=True) ast_kernel = ps.create_kernel(allen_cahn_update_rule, target=dh.default_target, cpu_openmp=True)
kernel_allen_cahn_lb = ast_kernel.compile() kernel_allen_cahn_lb = ast_kernel.compile()
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The update rule for the hydrodynmaic LB step is defined as: The update rule for the hydrodynmaic LB step is defined as:
$$ $$
g_i (\boldsymbol{x} + \boldsymbol{c}_i \Delta t, t + \Delta t) = g_i(\boldsymbol{x}, t) + \Omega_{ij}^g(\bar{g_j}^{eq} - g_j)|_{(\boldsymbol{x}, t)} + F_i(\boldsymbol{x}, t). g_i (\boldsymbol{x} + \boldsymbol{c}_i \Delta t, t + \Delta t) = g_i(\boldsymbol{x}, t) + \Omega_{ij}^g(\bar{g_j}^{eq} - g_j)|_{(\boldsymbol{x}, t)} + F_i(\boldsymbol{x}, t).
$$ $$
Here, the push scheme is applied which is easier due to the data access required for the viscous force term. Furthermore, the velocity update is directly done in the kernel. Here, the push scheme is applied which is easier due to the data access required for the viscous force term. Furthermore, the velocity update is directly done in the kernel.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
force_Assignments = hydrodynamic_force_assignments(g, u, C, method_hydro, parameters, body_force) force_Assignments = hydrodynamic_force_assignments(u, C, method_hydro, parameters, body_force)
lbm_optimisation = LBMOptimisation(symbolic_field=g, symbolic_temporary_field=g_tmp) lbm_optimisation = LBMOptimisation(symbolic_field=g, symbolic_temporary_field=g_tmp)
hydro_lb_update_rule = create_lb_update_rule(lbm_config=config_hydro, hydro_lb_update_rule = create_lb_update_rule(lbm_config=config_hydro,
lbm_optimisation=lbm_optimisation) lbm_optimisation=lbm_optimisation)
hydro_lb_update_rule = add_hydrodynamic_force(hydro_lb_update_rule, force_Assignments, C, g, parameters) hydro_lb_update_rule = add_hydrodynamic_force(hydro_lb_update_rule, force_Assignments, C, g, parameters,
config_hydro)
ast_kernel = ps.create_kernel(hydro_lb_update_rule, target=dh.default_target, cpu_openmp=True) ast_kernel = ps.create_kernel(hydro_lb_update_rule, target=dh.default_target, cpu_openmp=True)
kernel_hydro_lb = ast_kernel.compile() kernel_hydro_lb = ast_kernel.compile()
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Boundary Conditions ## Boundary Conditions
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As a last step suitable boundary conditions are applied As a last step suitable boundary conditions are applied
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# periodic Boundarys for g, h and C # periodic Boundarys for g, h and C
periodic_BC_C = dh.synchronization_function(C.name, target=dh.default_target, optimization = {"openmp": True}) periodic_BC_C = dh.synchronization_function(C.name, target=dh.default_target, optimization = {"openmp": True})
periodic_BC_g = LBMPeriodicityHandling(stencil=stencil_hydro, data_handling=dh, pdf_field_name=g.name, periodic_BC_g = LBMPeriodicityHandling(stencil=stencil_hydro, data_handling=dh, pdf_field_name=g.name,
streaming_pattern='push') streaming_pattern=config_hydro.streaming_pattern)
periodic_BC_h = LBMPeriodicityHandling(stencil=stencil_phase, data_handling=dh, pdf_field_name=h.name, periodic_BC_h = LBMPeriodicityHandling(stencil=stencil_phase, data_handling=dh, pdf_field_name=h.name,
streaming_pattern='pull') streaming_pattern=config_phase.streaming_pattern)
# No slip boundary for the phasefield lbm # No slip boundary for the phasefield lbm
bh_allen_cahn = LatticeBoltzmannBoundaryHandling(method_phase, dh, 'h', bh_allen_cahn = LatticeBoltzmannBoundaryHandling(method_phase, dh, 'h',
target=dh.default_target, name='boundary_handling_h', target=dh.default_target, name='boundary_handling_h',
streaming_pattern='pull') streaming_pattern=config_phase.streaming_pattern)
# No slip boundary for the velocityfield lbm # No slip boundary for the velocityfield lbm
bh_hydro = LatticeBoltzmannBoundaryHandling(method_hydro, dh, 'g' , bh_hydro = LatticeBoltzmannBoundaryHandling(method_hydro, dh, 'g' ,
target=dh.default_target, name='boundary_handling_g', target=dh.default_target, name='boundary_handling_g',
streaming_pattern='push') streaming_pattern=config_hydro.streaming_pattern)
contact_angle = BoundaryHandling(dh, C.name, stencil_hydro, target=dh.default_target) contact_angle = BoundaryHandling(dh, C.name, stencil_hydro, target=dh.default_target)
contact = ContactAngle(90, parameters.interface_thickness) contact = ContactAngle(90, parameters.interface_thickness)
wall = NoSlip() wall = NoSlip()
if dimensions == 2: if dimensions == 2:
bh_allen_cahn.set_boundary(wall, make_slice[:, 0]) bh_allen_cahn.set_boundary(wall, make_slice[:, 0])
bh_allen_cahn.set_boundary(wall, make_slice[:, -1]) bh_allen_cahn.set_boundary(wall, make_slice[:, -1])
bh_hydro.set_boundary(wall, make_slice[:, 0]) bh_hydro.set_boundary(wall, make_slice[:, 0])
bh_hydro.set_boundary(wall, make_slice[:, -1]) bh_hydro.set_boundary(wall, make_slice[:, -1])
contact_angle.set_boundary(contact, make_slice[:, 0]) contact_angle.set_boundary(contact, make_slice[:, 0])
contact_angle.set_boundary(contact, make_slice[:, -1]) contact_angle.set_boundary(contact, make_slice[:, -1])
else: else:
bh_allen_cahn.set_boundary(wall, make_slice[:, 0, :]) bh_allen_cahn.set_boundary(wall, make_slice[:, 0, :])
bh_allen_cahn.set_boundary(wall, make_slice[:, -1, :]) bh_allen_cahn.set_boundary(wall, make_slice[:, -1, :])
bh_hydro.set_boundary(wall, make_slice[:, 0, :]) bh_hydro.set_boundary(wall, make_slice[:, 0, :])
bh_hydro.set_boundary(wall, make_slice[:, -1, :]) bh_hydro.set_boundary(wall, make_slice[:, -1, :])
contact_angle.set_boundary(contact, make_slice[:, 0, :]) contact_angle.set_boundary(contact, make_slice[:, 0, :])
contact_angle.set_boundary(contact, make_slice[:, -1, :]) contact_angle.set_boundary(contact, make_slice[:, -1, :])
bh_allen_cahn.prepare() bh_allen_cahn.prepare()
bh_hydro.prepare() bh_hydro.prepare()
contact_angle.prepare() contact_angle.prepare()
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Full timestep ## Full timestep
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# definition of the timestep for the immiscible fluids model # definition of the timestep for the immiscible fluids model
def timeloop(): def timeloop():
# Solve the interface tracking LB step with boundary conditions # Solve the interface tracking LB step with boundary conditions
periodic_BC_h() periodic_BC_h()
bh_allen_cahn() bh_allen_cahn()
dh.run_kernel(kernel_allen_cahn_lb, **parameters.symbolic_to_numeric_map) dh.run_kernel(kernel_allen_cahn_lb, **parameters.symbolic_to_numeric_map)
dh.swap("C", "C_tmp") # Solve the hydro LB step with boundary conditions
periodic_BC_g()
bh_hydro()
dh.run_kernel(kernel_hydro_lb, **parameters.symbolic_to_numeric_map)
dh.swap("C", "C_tmp")
# apply the three phase-phase contact angle # apply the three phase-phase contact angle
contact_angle() contact_angle()
# periodic BC of the phase-field # periodic BC of the phase-field
periodic_BC_C() periodic_BC_C()
# solve the hydro LB step with boundary conditions
dh.run_kernel(kernel_hydro_lb, **parameters.symbolic_to_numeric_map)
periodic_BC_g()
bh_hydro()
# field swaps # field swaps
dh.swap("h", "h_tmp") dh.swap("h", "h_tmp")
dh.swap("g", "g_tmp") dh.swap("g", "g_tmp")
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
Initialize_distributions() Initialize_distributions()
frames = 300 frames = 300
steps_per_frame = (timesteps//frames) + 1 steps_per_frame = (timesteps//frames) + 1
if 'is_test_run' not in globals(): if 'is_test_run' not in globals():
def run(): def run():
for i in range(steps_per_frame): for i in range(steps_per_frame):
timeloop() timeloop()
if gpu: if gpu:
dh.to_cpu("C") dh.to_cpu("C")
return dh.gather_array(C.name) return dh.gather_array(C.name)
animation = plt.scalar_field_animation(run, frames=frames, rescale=True) animation = plt.scalar_field_animation(run, frames=frames, rescale=True)
set_display_mode('video') set_display_mode('video')
res = display_animation(animation) res = display_animation(animation)
else: else:
timeloop() timeloop()
res = None res = None
res res
``` ```
%% Output %% Output
<IPython.core.display.HTML object> <IPython.core.display.HTML object>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Note that the video is played for 10 seconds while the simulation time is only 2 seconds! Note that the video is played for 10 seconds while the simulation time is only 2 seconds!
......