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
  • Sparse
  • WallLaw
  • improved_comm
  • master
  • release/0.2.1
  • release/0.2.10
  • release/0.2.11
  • release/0.2.12
  • release/0.2.13
  • release/0.2.14
  • release/0.2.15
  • release/0.2.2
  • release/0.2.3
  • release/0.2.4
  • release/0.2.5
  • release/0.2.6
  • release/0.2.7
  • release/0.2.8
  • release/0.2.9
  • release/0.3.0
  • release/0.3.1
  • release/0.3.2
  • release/0.3.3
  • release/0.3.4
  • release/0.4.0
  • release/0.4.1
  • release/0.4.2
  • release/0.4.3
  • release/0.4.4
  • release/1.0
  • release/1.0.1
  • release/1.1
  • release/1.1.1
  • release/1.2
  • release/1.3
  • release/1.3.1
  • release/1.3.2
  • release/1.3.3
  • release/1.3.4
  • release/1.3.5
  • release/1.3.6
  • release/1.3.7
42 results

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
  • iMEM
  • master
2 results
Show changes
Commits on Source (49)
Showing
with 1570 additions and 366 deletions
lbmpy/_version.py export-subst src/lbmpy/_version.py export-subst
...@@ -21,6 +21,9 @@ doc/bibtex.json ...@@ -21,6 +21,9 @@ doc/bibtex.json
/src/lbmpy/phasefield/simplex_projection.*.so /src/lbmpy/phasefield/simplex_projection.*.so
/src/lbmpy/phasefield/simplex_projection.c /src/lbmpy/phasefield/simplex_projection.c
test-report
report.xml
# macOS # macOS
**/.DS_Store **/.DS_Store
*.uuid *.uuid
......
stages: stages:
- pretest - "Code Quality"
- test - "Tests"
- "Prerelease-Tests"
- integration
- nightly
- docs
- deploy - deploy
# -------------------------- Code Quality --------------------------------------------------------------------------------
# -------------------------- Pre Tests --------------------------------------------------------------------------------
# Linter for code formatting
flake8-lint:
stage: "Code Quality"
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/nox:alpine
script:
- nox -s lint
tags:
- docker
# -------------------------- 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: testsuite-cuda-py3.10:
stage: pretest stage: "Tests"
except: image: i10git.cs.fau.de:5005/pycodegen/pycodegen/nox:ubuntu24.04-cuda12.6
variables: needs: []
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
script: script:
# - pip install sympy --upgrade
- export NUM_CORES=$(nproc --all)
- mkdir -p ~/.config/matplotlib - mkdir -p ~/.config/matplotlib
- echo "backend:template" > ~/.config/matplotlib/matplotlibrc - echo "backend:template" > ~/.config/matplotlib/matplotlibrc
- mkdir public - nox -s "testsuite_gpu-3.10(cupy12)"
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
- env
- pip list
- 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
tags: tags:
- docker - docker
- cuda11 - cuda
- cudaComputeCapability6.1
- AVX - AVX
coverage: /Total coverage:\s\d+.\d+\%/ coverage: /Total coverage:\s\d+.\d+\%/
artifacts: artifacts:
when: always when: always
paths: paths:
- coverage_report - coverage_report
- test-report
reports: reports:
coverage_report: coverage_report:
coverage_format: cobertura coverage_format: cobertura
path: coverage.xml path: coverage.xml
junit: report.xml junit: report.xml
testsuite-cpu-py3.13:
stage: "Tests"
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/nox:alpine
needs: []
script:
- mkdir -p ~/.config/matplotlib
- echo "backend:template" > ~/.config/matplotlib/matplotlibrc
- nox -s "testsuite_cpu-3.13"
tags:
- docker
- AVX
artifacts:
when: always
paths:
- test-report
reports:
junit: report.xml
# Normal test with longruns # Normal test with longruns
tests-and-coverage-with-longrun: tests-and-coverage-with-longrun:
stage: test stage: "Tests"
when: manual when: manual
allow_failure: true allow_failure: true
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
...@@ -57,163 +86,117 @@ tests-and-coverage-with-longrun: ...@@ -57,163 +86,117 @@ tests-and-coverage-with-longrun:
- py.test -v -n $NUM_CORES - py.test -v -n $NUM_CORES
tags: tags:
- docker - docker
- cuda11 - cuda
- cudaComputeCapability6.1
- AVX - AVX
minimal-conda: # -------------------------- Nightly and Pre-Release Tests --------------------------------------------------------------------------------
stage: pretest
except:
variables:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/minimal_conda
script:
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
- pip install -e .
- python quicktest.py
tags:
- docker
# Linter for code formatting
flake8-lint:
stage: pretest
except:
variables:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
script:
- flake8 src/lbmpy
tags:
- docker
- cuda11
# -------------------------- Tests -------------------------------------------------------------------------------------
# pipeline with latest python version # Test against latest pystencils 2.0 development version
latest-python: pystencils-2.0dev:
stage: test stage: "Prerelease-Tests"
except: image: i10git.cs.fau.de:5005/pycodegen/pycodegen/nox:ubuntu24.04-cuda12.6
variables: allow_failure: true
- $ENABLE_NIGHTLY_BUILDS needs: []
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/latest_python
before_script:
- pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
script: script:
- env
- pip list
- export NUM_CORES=$(nproc --all)
- mkdir -p ~/.config/matplotlib - mkdir -p ~/.config/matplotlib
- echo "backend:template" > ~/.config/matplotlib/matplotlibrc - echo "backend:template" > ~/.config/matplotlib/matplotlibrc
- mkdir public - nox -s "testsuite_pystencils2(cupy12)"
- py.test -v -n $NUM_CORES -m "not longrun" --junitxml=report.xml
tags: tags:
- docker - docker
- AVX - AVX
- cuda
- cudaComputeCapability6.1
artifacts: artifacts:
when: always when: always
paths:
- test-report
reports: reports:
junit: report.xml junit: report.xml
# Minimal tests in windows environment
#minimal-windows:
# stage: test
# except:
# variables:
# - $ENABLE_NIGHTLY_BUILDS
# tags:
# - win
# script:
# - export NUM_CORES=$(nproc --all)
# - export MPLBACKEND=Agg
# - source /cygdrive/c/Users/build/Miniconda3/Scripts/activate
# - source activate 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"
# - pip install sympy==1.9
# - py.test -v -m "not (notebook or longrun)"
minimal-sympy-master: minimal-sympy-master:
stage: test stage: "Prerelease-Tests"
except: needs: []
variables: image: i10git.cs.fau.de:5005/pycodegen/pycodegen/nox:alpine
- $ENABLE_NIGHTLY_BUILDS
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 - nox -s quicktest -P 3.13 -- --sympy-master
- python -m pip install --upgrade git+https://github.com/sympy/sympy.git
- pip list
- python quicktest.py
allow_failure: true allow_failure: true
tags: tags:
- docker - docker
- cuda
ubuntu: # pycodegen-integration:
stage: test # image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
except: # stage: integration
variables: # when: manual
- $ENABLE_NIGHTLY_BUILDS # allow_failure: true
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/ubuntu # script:
# - env
# - pip list
# - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pycodegen.git
# - cd pycodegen
# - git submodule sync --recursive
# - git submodule update --init --recursive
# - git submodule foreach git fetch origin # compare the latest master version!
# - git submodule foreach git reset --hard origin/master
# - cd lbmpy
# - git remote add test $CI_REPOSITORY_URL
# - git fetch test
# - git reset --hard $CI_COMMIT_SHA
# - cd ..
# - pip install -e pystencils/
# - pip install -e lbmpy/
# - ./install_walberla.sh
# # build all integration tests
# - cd walberla/build/
# - make -j $NUM_CORES MicroBenchmarkGpuLbm LbCodeGenerationExample
# - cd apps/benchmarks/UniformGridGPU
# - make -j $NUM_CORES
# - cd ../UniformGridCPU
# - make -j $NUM_CORES
# tags:
# - docker
# - cuda
# - cudaComputeCapability6.1
# - AVX
# -------------------- Scheduled Tasks --------------------------------------------------------------------------
nightly-sympy:
stage: nightly
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/latest_python
before_script: before_script:
# - apt-get -y remove python3-sympy - pip install -e .
- ln -s /usr/include/locale.h /usr/include/xlocale.h - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
# - pip3 install `grep -Eo 'sympy[>=]+[0-9\.]+' setup.py | sed 's/>/=/g'` - pip install --upgrade --pre sympy
- pip3 install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
script: script:
- env
- pip list
- export NUM_CORES=$(nproc --all) - export NUM_CORES=$(nproc --all)
- mkdir -p ~/.config/matplotlib - mkdir -p ~/.config/matplotlib
- echo "backend:template" > ~/.config/matplotlib/matplotlibrc - echo "backend:template" > ~/.config/matplotlib/matplotlibrc
- env - mkdir public
- pip3 list
- pytest -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 - AVX
- cuda
- cudaComputeCapability6.1
artifacts: artifacts:
when: always when: always
reports: reports:
junit: report.xml junit: report.xml
pycodegen-integration:
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
stage: test
when: manual
allow_failure: true
script:
- env
- pip list
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pycodegen.git
- cd pycodegen
- git submodule sync --recursive
- git submodule update --init --recursive
- git submodule foreach git fetch origin # compare the latest master version!
- git submodule foreach git reset --hard origin/master
- cd lbmpy
- git remote add test $CI_REPOSITORY_URL
- git fetch test
- git reset --hard $CI_COMMIT_SHA
- cd ..
- pip install -e pystencils/
- pip install -e lbmpy/
- ./install_walberla.sh
# build all integration tests
- cd walberla/build/
- make -j $NUM_CORES MicroBenchmarkGpuLbm LbCodeGenerationExample
- cd apps/benchmarks/UniformGridGPU
- make -j $NUM_CORES
- cd ../UniformGridCPU
- make -j $NUM_CORES
tags:
- docker
- cuda11
- AVX
# -------------------- Documentation and deploy ------------------------------------------------------------------------ # -------------------- Documentation and deploy ------------------------------------------------------------------------
build-documentation: build-documentation:
stage: test stage: docs
needs: []
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/documentation image: i10git.cs.fau.de:5005/pycodegen/pycodegen/documentation
before_script: before_script:
- pip install -e . - pip install -e .
...@@ -224,7 +207,8 @@ build-documentation: ...@@ -224,7 +207,8 @@ build-documentation:
- sphinx-build -W -b html doc html_doc - sphinx-build -W -b html doc html_doc
tags: tags:
- docker - docker
- cuda11 - cuda
- cudaComputeCapability6.1
artifacts: artifacts:
paths: paths:
- html_doc - html_doc
...@@ -232,7 +216,10 @@ build-documentation: ...@@ -232,7 +216,10 @@ build-documentation:
pages: pages:
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
rules:
- if: '$CI_PIPELINE_SOURCE != "schedule" && $CI_PROJECT_PATH == "pycodegen/lbmpy" && $CI_COMMIT_BRANCH == "master"'
stage: deploy stage: deploy
needs: ["testsuite-cuda-py3.10", "build-documentation"]
script: script:
- ls -l - ls -l
- mv coverage_report html_doc - mv coverage_report html_doc
...@@ -242,5 +229,3 @@ pages: ...@@ -242,5 +229,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
......
...@@ -5,13 +5,18 @@ import runpy ...@@ -5,13 +5,18 @@ import runpy
import sys import sys
import warnings import warnings
import platform import platform
import pathlib
import nbformat import nbformat
from nbconvert import PythonExporter import nbconvert
import sympy import sympy
from lbmpy._compat import IS_PYSTENCILS_2
# Trigger config file reading / creation once - to avoid race conditions when multiple instances are creating it # Trigger config file reading / creation once - to avoid race conditions when multiple instances are creating it
# at the same time # at the same time
from pystencils.cpu import cpujit if not IS_PYSTENCILS_2:
from pystencils.cpu import cpujit
# trigger cython imports - there seems to be a problem when multiple processes try to compile the same cython file # trigger cython imports - there seems to be a problem when multiple processes try to compile the same cython file
# at the same time # at the same time
...@@ -19,9 +24,9 @@ try: ...@@ -19,9 +24,9 @@ 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'))
...@@ -44,6 +49,13 @@ collect_ignore = [os.path.join(SCRIPT_FOLDER, "doc", "conf.py"), ...@@ -44,6 +49,13 @@ 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('_local_tmp') add_path_to_ignore('_local_tmp')
if IS_PYSTENCILS_2:
# TODO: Fix these step-by-step
collect_ignore += [
os.path.join(SCRIPT_FOLDER, "doc", "notebooks", "10_tutorial_conservative_allen_cahn_two_phase.ipynb"),
os.path.join(SCRIPT_FOLDER, "tests", "test_compiled_in_boundaries.ipynb")
]
try: try:
import cupy import cupy
except ImportError: except ImportError:
...@@ -106,22 +118,29 @@ class IPyNbTest(pytest.Item): ...@@ -106,22 +118,29 @@ 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):
exporter = PythonExporter() exporter = nbconvert.PythonExporter()
exporter.exclude_markdown = True exporter.exclude_markdown = True
exporter.exclude_input_prompt = True exporter.exclude_input_prompt = True
...@@ -140,10 +159,19 @@ class IPyNbFile(pytest.File): ...@@ -140,10 +159,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)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="53.913788mm"
height="53.913788mm"
viewBox="0 0 53.913788 53.913788"
version="1.1"
id="svg834"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="lbmpy-logo.svg"
inkscape:export-filename="/local/bauer/code/lbmpy/lbmpy/doc/img/logo.png"
inkscape:export-xdpi="70.669998"
inkscape:export-ydpi="70.669998"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs828">
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1421"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
inkscape:connector-curvature="0" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-6"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-7"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-7"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-6-9"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-7-4"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-7-5"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2-6"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2-5"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3-7"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2-6-3"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2-5-9"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3-7-4"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2-6-3-0"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2-5-9-6"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#c6c6c6;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3-7-4-0"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2-2"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2-8"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3-4"
is_visible="true"
lpeversion="0" />
<filter
y="-0.25"
height="1.5"
inkscape:menu-tooltip="Darkens the edge with an inner blur and adds a flexible glow"
inkscape:menu="Shadows and Glows"
inkscape:label="Dark And Glow"
style="color-interpolation-filters:sRGB"
id="filter4608">
<feGaussianBlur
stdDeviation="5"
result="result6"
id="feGaussianBlur4610" />
<feComposite
result="result8"
in="SourceGraphic"
operator="atop"
in2="result6"
id="feComposite4612" />
<feComposite
result="result9"
operator="over"
in2="SourceAlpha"
in="result8"
id="feComposite4614" />
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "
result="result10"
id="feColorMatrix4616" />
<feBlend
in="result10"
mode="normal"
in2="result6"
id="feBlend4618" />
</filter>
<filter
y="-0.25"
height="1.5"
inkscape:menu-tooltip="Darkens the edge with an inner blur and adds a flexible glow"
inkscape:menu="Shadows and Glows"
inkscape:label="Dark And Glow"
style="color-interpolation-filters:sRGB"
id="filter4632">
<feGaussianBlur
stdDeviation="5"
result="result6"
id="feGaussianBlur4634" />
<feComposite
result="result8"
in="SourceGraphic"
operator="atop"
in2="result6"
id="feComposite4636" />
<feComposite
result="result9"
operator="over"
in2="SourceAlpha"
in="result8"
id="feComposite4638" />
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "
result="result10"
id="feColorMatrix4640" />
<feBlend
in="result10"
mode="normal"
in2="result6"
id="feBlend4642" />
</filter>
<filter
y="-0.25"
height="1.5"
inkscape:menu-tooltip="Darkens the edge with an inner blur and adds a flexible glow"
inkscape:menu="Shadows and Glows"
inkscape:label="Dark And Glow"
style="color-interpolation-filters:sRGB"
id="filter4620">
<feGaussianBlur
stdDeviation="5"
result="result6"
id="feGaussianBlur4622" />
<feComposite
result="result8"
in="SourceGraphic"
operator="atop"
in2="result6"
id="feComposite4624" />
<feComposite
result="result9"
operator="over"
in2="SourceAlpha"
in="result8"
id="feComposite4626" />
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "
result="result10"
id="feColorMatrix4628" />
<feBlend
in="result10"
mode="normal"
in2="result6"
id="feBlend4630" />
</filter>
<filter
y="-0.25"
height="1.5"
inkscape:menu-tooltip="Darkens the edge with an inner blur and adds a flexible glow"
inkscape:menu="Shadows and Glows"
inkscape:label="Dark And Glow"
style="color-interpolation-filters:sRGB"
id="filter4596">
<feGaussianBlur
stdDeviation="5"
result="result6"
id="feGaussianBlur4598" />
<feComposite
result="result8"
in="SourceGraphic"
operator="atop"
in2="result6"
id="feComposite4600" />
<feComposite
result="result9"
operator="over"
in2="SourceAlpha"
in="result8"
id="feComposite4602" />
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "
result="result10"
id="feColorMatrix4604" />
<feBlend
in="result10"
mode="normal"
in2="result6"
id="feBlend4606" />
</filter>
<inkscape:path-effect
effect="spiro"
id="path-effect4188-5"
is_visible="true"
lpeversion="0" />
<inkscape:path-effect
effect="spiro"
id="path-effect4188"
is_visible="true"
lpeversion="0" />
<filter
y="-0.25"
height="1.5"
inkscape:menu-tooltip="Darkens the edge with an inner blur and adds a flexible glow"
inkscape:menu="Shadows and Glows"
inkscape:label="Dark And Glow"
style="color-interpolation-filters:sRGB"
id="filter4608-0">
<feGaussianBlur
stdDeviation="5"
result="result6"
id="feGaussianBlur4610-2" />
<feComposite
result="result8"
in="SourceGraphic"
operator="atop"
in2="result6"
id="feComposite4612-5" />
<feComposite
result="result9"
operator="over"
in2="SourceAlpha"
in="result8"
id="feComposite4614-7" />
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "
result="result10"
id="feColorMatrix4616-6" />
<feBlend
in="result10"
mode="normal"
in2="result6"
id="feBlend4618-9" />
</filter>
<filter
y="-0.25"
height="1.5"
inkscape:menu-tooltip="Darkens the edge with an inner blur and adds a flexible glow"
inkscape:menu="Shadows and Glows"
inkscape:label="Dark And Glow"
style="color-interpolation-filters:sRGB"
id="filter4632-1">
<feGaussianBlur
stdDeviation="5"
result="result6"
id="feGaussianBlur4634-9" />
<feComposite
result="result8"
in="SourceGraphic"
operator="atop"
in2="result6"
id="feComposite4636-8" />
<feComposite
result="result9"
operator="over"
in2="SourceAlpha"
in="result8"
id="feComposite4638-7" />
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "
result="result10"
id="feColorMatrix4640-6" />
<feBlend
in="result10"
mode="normal"
in2="result6"
id="feBlend4642-5" />
</filter>
<filter
y="-0.25"
height="1.5"
inkscape:menu-tooltip="Darkens the edge with an inner blur and adds a flexible glow"
inkscape:menu="Shadows and Glows"
inkscape:label="Dark And Glow"
style="color-interpolation-filters:sRGB"
id="filter4620-1">
<feGaussianBlur
stdDeviation="5"
result="result6"
id="feGaussianBlur4622-1" />
<feComposite
result="result8"
in="SourceGraphic"
operator="atop"
in2="result6"
id="feComposite4624-4" />
<feComposite
result="result9"
operator="over"
in2="SourceAlpha"
in="result8"
id="feComposite4626-8" />
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "
result="result10"
id="feColorMatrix4628-5" />
<feBlend
in="result10"
mode="normal"
in2="result6"
id="feBlend4630-7" />
</filter>
<filter
y="-0.25"
height="1.5"
inkscape:menu-tooltip="Darkens the edge with an inner blur and adds a flexible glow"
inkscape:menu="Shadows and Glows"
inkscape:label="Dark And Glow"
style="color-interpolation-filters:sRGB"
id="filter4596-6">
<feGaussianBlur
stdDeviation="5"
result="result6"
id="feGaussianBlur4598-6" />
<feComposite
result="result8"
in="SourceGraphic"
operator="atop"
in2="result6"
id="feComposite4600-9" />
<feComposite
result="result9"
operator="over"
in2="SourceAlpha"
in="result8"
id="feComposite4602-1" />
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "
result="result10"
id="feColorMatrix4604-4" />
<feBlend
in="result10"
mode="normal"
in2="result6"
id="feBlend4606-3" />
</filter>
<inkscape:path-effect
effect="spiro"
id="path-effect4188-5-6"
is_visible="true"
lpeversion="0" />
<inkscape:path-effect
effect="spiro"
id="path-effect4188-7"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-3"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1421-6"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
inkscape:connector-curvature="0" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-75"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-3"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-5"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-6"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-6-2"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-7-9"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-7-1"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2-27"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2-0"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3-9"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2-6-36"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2-5-0"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3-7-6"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2-6-3-2"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2-5-9-61"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3-7-4-8"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-2-2-7"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-2-8-9"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-3-4-2"
is_visible="true"
lpeversion="0" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send-8-6-9-0"
style="overflow:visible"
inkscape:isstock="true">
<path
inkscape:connector-curvature="0"
id="path1421-2-7-4-2"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
style="fill:#dddddd;fill-opacity:1;fill-rule:evenodd;stroke:#dddddd;stroke-width:1pt;stroke-opacity:1"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<inkscape:path-effect
effect="spiro"
id="path-effect1404-7-7-5-3"
is_visible="true"
lpeversion="0" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
inkscape:cx="158.57143"
inkscape:cy="78.571429"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1664"
inkscape:window-height="1113"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1886"
originx="-9.840785"
originy="-227.28709"
spacingy="1"
spacingx="1"
units="mm" />
</sodipodi:namedview>
<metadata
id="metadata831">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-9.8407853,-15.799119)">
<rect
style="opacity:1;fill:#646ecb;fill-opacity:1;stroke:#d2d2d2;stroke-width:0.509157;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect1396"
width="53.404633"
height="53.404633"
x="10.095364"
y="16.053698"
ry="3.0735996"
inkscape:export-xdpi="188.45"
inkscape:export-ydpi="188.45" />
<path
style="font-weight:bold;font-size:16.9333px;line-height:125%;font-family:'Latin Modern Mono Light';-inkscape-font-specification:'Latin Modern Mono Light, Bold';letter-spacing:0px;word-spacing:0px;fill:#ffffff;stroke-width:0.264583px"
d="m 21.505785,62.578241 c 0,-0.626532 -0.474132,-0.643466 -0.795865,-0.643466 h -2.015062 v -8.26345 c 0,-0.609599 -0.118533,-0.812798 -0.795865,-0.812798 h -2.607729 c -0.321732,0 -0.812798,0.01693 -0.812798,0.643465 0,0.609599 0.507999,0.626532 0.795865,0.626532 h 2.015063 v 7.806251 h -1.99813 c -0.321732,0 -0.812798,0.01693 -0.812798,0.643466 0,0.609599 0.507999,0.626532 0.795865,0.626532 h 5.435589 c 0.3048,0 0.795865,-0.01693 0.795865,-0.626532 z m 9.228643,-3.064927 c 0,-2.116663 -1.54093,-3.776126 -3.352793,-3.776126 -0.728132,0 -1.405464,0.237066 -1.964263,0.643465 v -2.709328 c 0,-0.609599 -0.118533,-0.812798 -0.795865,-0.812798 h -1.202265 c -0.321732,0 -0.812798,0.01693 -0.812798,0.643465 0,0.609599 0.507999,0.626532 0.795865,0.626532 h 0.609599 v 8.263451 c 0,0.406399 0.01693,0.812798 0.711198,0.812798 0.524933,0 0.660399,-0.237066 0.677332,-0.575732 0.643466,0.541865 1.303865,0.677332 1.79493,0.677332 1.862663,0 3.53906,-1.625597 3.53906,-3.793059 z m -1.388531,0 c 0,1.49013 -1.083731,2.523061 -2.184395,2.523061 -1.202265,0 -1.727197,-1.371597 -1.727197,-2.116662 v -1.202265 c 0,-0.914398 0.897465,-1.710263 1.862663,-1.710263 1.151464,0 2.048929,1.151465 2.048929,2.506129 z m 10.820373,3.064927 c 0,-0.558799 -0.321733,-0.643466 -0.931331,-0.643466 v -3.860792 c 0,-0.355599 -0.03387,-2.336795 -1.608664,-2.336795 -0.474132,0 -1.117598,0.186266 -1.608663,0.745065 -0.287867,-0.491066 -0.745066,-0.745065 -1.269998,-0.745065 -0.491066,0 -0.948265,0.169333 -1.337731,0.474132 -0.118533,-0.372533 -0.440265,-0.389466 -0.745065,-0.389466 h -0.524932 c -0.3048,0 -0.812799,0.03387 -0.812799,0.626532 0,0.558799 0.321733,0.643466 0.931332,0.643466 v 4.842923 c -0.609599,0 -0.931332,0.08467 -0.931332,0.643466 0,0.609599 0.524933,0.626532 0.812799,0.626532 h 1.43933 c 0.3048,0 0.761999,-0.01693 0.761999,-0.626532 0,-0.558799 -0.270933,-0.643466 -0.880532,-0.643466 v -2.827861 c 0,-1.354664 0.575732,-2.099729 1.219198,-2.099729 0.321733,0 0.474132,0.270933 0.474132,1.219198 v 3.708392 c -0.355599,0.01693 -0.677332,0.1016 -0.677332,0.643466 0,0.609599 0.474133,0.626532 0.761999,0.626532 h 1.236131 c 0.304799,0 0.761998,-0.01693 0.761998,-0.626532 0,-0.558799 -0.287866,-0.643466 -0.897465,-0.643466 v -2.827861 c 0,-1.354664 0.592666,-2.099729 1.219198,-2.099729 0.338666,0 0.474132,0.270933 0.474132,1.219198 v 3.708392 c -0.338666,0.01693 -0.660398,0.1016 -0.660398,0.643466 0,0.609599 0.474132,0.626532 0.745065,0.626532 h 1.236131 c 0.304799,0 0.812798,-0.01693 0.812798,-0.626532 z m 9.829776,-3.064927 c 0,-2.116663 -1.540931,-3.776126 -3.352794,-3.776126 -0.728132,0 -1.422397,0.237066 -1.981196,0.660398 -0.01693,-0.389466 -0.169333,-0.575732 -0.778932,-0.575732 H 42.68086 c -0.321733,0 -0.812798,0.03387 -0.812798,0.643466 0,0.609598 0.507999,0.626532 0.795865,0.626532 h 0.609598 v 8.500516 H 42.68086 c -0.321733,0 -0.812798,0.01693 -0.812798,0.643466 0,0.609598 0.507999,0.626532 0.795865,0.626532 h 2.624661 c 0.287866,0 0.795865,-0.01693 0.795865,-0.626532 0,-0.626532 -0.491065,-0.643466 -0.812798,-0.643466 h -0.592666 v -2.963327 c 0.643466,0.558799 1.286931,0.677332 1.777997,0.677332 1.862663,0 3.53906,-1.625597 3.53906,-3.793059 z m -1.388531,0 c 0,1.49013 -1.083731,2.523061 -2.184396,2.523061 -1.202264,0 -1.727196,-1.371597 -1.727196,-2.116662 v -1.202265 c 0,-0.914398 0.897465,-1.710263 1.862663,-1.710263 1.151464,0 2.048929,1.151465 2.048929,2.506129 z M 59.08922,56.46532 c 0,-0.626533 -0.474133,-0.643466 -0.795865,-0.643466 h -1.930397 c -0.304799,0 -0.795865,0.03387 -0.795865,0.626532 0,0.626532 0.474133,0.643466 0.795865,0.643466 h 0.270933 l -1.456264,4.419591 -1.676396,-4.419591 h 0.220133 c 0.304799,0 0.795865,-0.01693 0.795865,-0.626532 0,-0.626533 -0.474133,-0.643466 -0.795865,-0.643466 h -1.930397 c -0.321732,0 -0.795865,0.01693 -0.795865,0.643466 0,0.609598 0.491066,0.626532 0.795865,0.626532 H 52.2143 l 2.370662,5.977455 c -0.06773,0.186266 -0.423333,1.371597 -0.609599,1.744129 -0.338666,0.643466 -0.863598,1.032932 -1.185331,1.032932 0.01693,-0.06773 0.186266,-0.118533 0.186266,-0.372533 0,-0.491066 -0.355599,-0.846665 -0.846665,-0.846665 -0.524932,0 -0.846665,0.355599 -0.846665,0.846665 0,0.761999 0.609599,1.490131 1.490131,1.490131 1.69333,0 2.523062,-2.252129 2.590795,-2.438396 l 2.523061,-7.433718 h 0.4064 c 0.304799,0 0.795865,-0.01693 0.795865,-0.626532 z"
id="text1392"
aria-label="lbm py" />
<path
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.845195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-6-9)"
d="M 36.797679,33.475 H 23.568513"
id="path1402-8-0-4"
inkscape:connector-curvature="0"
inkscape:path-effect="#path-effect1404-7-7-5"
inkscape:original-d="m 36.797679,33.475 c -4.259608,0.03317 -8.970011,-0.03362 -13.229166,0"
sodipodi:nodetypes="cc"
inkscape:export-xdpi="188.45"
inkscape:export-ydpi="188.45" />
<g
id="g9842"
inkscape:export-xdpi="188.45"
inkscape:export-ydpi="188.45">
<path
sodipodi:nodetypes="cc"
inkscape:original-d="m 36.797679,33.475 c 2.23e-4,-4.259735 2.23e-4,-8.969879 0,-13.229167"
inkscape:path-effect="#path-effect1404"
inkscape:connector-curvature="0"
id="path1402"
d="M 36.797679,33.475 V 20.245833"
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.845195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send)" />
<path
sodipodi:nodetypes="cc"
inkscape:original-d="m 36.797679,33.475 c 4.259736,2.23e-4 8.969879,2.23e-4 13.229167,0"
inkscape:path-effect="#path-effect1404-7"
inkscape:connector-curvature="0"
id="path1402-8"
d="M 36.797679,33.475 H 50.026846"
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.845195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8)" />
<path
sodipodi:nodetypes="cc"
inkscape:original-d="m 36.797679,33.475 c 0.03317,4.259607 -0.03362,8.97001 0,13.229166"
inkscape:path-effect="#path-effect1404-7-7"
inkscape:connector-curvature="0"
id="path1402-8-0"
d="M 36.797679,33.475 V 46.704166"
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.845195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-6)" />
<path
sodipodi:nodetypes="cc"
inkscape:original-d="m 36.797679,33.475 c 4.259736,2.23e-4 8.13389,-13.228083 12.393178,-13.228306"
inkscape:path-effect="#path-effect1404-7-3"
inkscape:connector-curvature="0"
id="path1402-8-9"
d="M 36.797679,33.475 49.190857,20.246694"
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.845195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-2)" />
<path
sodipodi:nodetypes="cc"
inkscape:original-d="M 36.797679,33.475 C 32.537943,33.475223 27.827801,20.246056 23.568513,20.245833"
inkscape:path-effect="#path-effect1404-7-3-7"
inkscape:connector-curvature="0"
id="path1402-8-9-4"
d="M 36.797679,33.475 23.568513,20.245833"
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.845195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-2-6)" />
<path
sodipodi:nodetypes="cc"
inkscape:original-d="M 36.797679,33.475 C 32.537943,33.474776 27.827801,46.703943 23.568513,46.704166"
inkscape:path-effect="#path-effect1404-7-3-7-4"
inkscape:connector-curvature="0"
id="path1402-8-9-4-4"
d="M 36.797679,33.475 23.568513,46.704166"
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.845195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-2-6-3)" />
<path
sodipodi:nodetypes="cc"
inkscape:original-d="m 36.797679,33.475 c 4.259736,-2.23e-4 7.646962,13.228943 11.90625,13.229166"
inkscape:path-effect="#path-effect1404-7-3-4"
inkscape:connector-curvature="0"
id="path1402-8-9-3"
d="m 36.797679,33.475 11.90625,13.229166"
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.845195;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-2-2)" />
</g>
</g>
</svg>
...@@ -2,23 +2,23 @@ ...@@ -2,23 +2,23 @@
<!-- Created with Inkscape (http://www.inkscape.org/) --> <!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="53.913788mm" width="53.913788mm"
height="53.913788mm" height="53.913788mm"
viewBox="0 0 53.913788 53.913788" viewBox="0 0 53.913788 53.913788"
version="1.1" version="1.1"
id="svg834" id="svg834"
inkscape:version="0.92.3 (2405546, 2018-03-11)" inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="logo.svg" sodipodi:docname="logo.svg"
inkscape:export-filename="/local/bauer/code/lbmpy/lbmpy/doc/img/logo.png" inkscape:export-filename="/local/bauer/code/lbmpy/lbmpy/doc/img/logo.png"
inkscape:export-xdpi="70.669998" inkscape:export-xdpi="70.669998"
inkscape:export-ydpi="70.669998"> inkscape:export-ydpi="70.669998"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs828"> id="defs828">
<marker <marker
...@@ -641,25 +641,31 @@ ...@@ -641,25 +641,31 @@
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="1.4" inkscape:zoom="1.4"
inkscape:cx="158.26067" inkscape:cx="158.21429"
inkscape:cy="-4.9825309" inkscape:cy="251.78571"
inkscape:document-units="mm" inkscape:document-units="mm"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="false" showgrid="false"
inkscape:window-width="1214" inkscape:window-width="1557"
inkscape:window-height="1052" inkscape:window-height="1122"
inkscape:window-x="1482" inkscape:window-x="0"
inkscape:window-y="524" inkscape:window-y="0"
inkscape:window-maximized="0" inkscape:window-maximized="0"
fit-margin-top="0" fit-margin-top="0"
fit-margin-left="0" fit-margin-left="0"
fit-margin-right="0" fit-margin-right="0"
fit-margin-bottom="0"> fit-margin-bottom="0"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid <inkscape:grid
type="xygrid" type="xygrid"
id="grid1886" id="grid1886"
originx="-9.8407853" originx="-9.8407853"
originy="-227.28709" /> originy="-227.28709"
spacingy="1"
spacingx="1"
units="mm" />
</sodipodi:namedview> </sodipodi:namedview>
<metadata <metadata
id="metadata831"> id="metadata831">
...@@ -688,21 +694,11 @@ ...@@ -688,21 +694,11 @@
ry="3.0735996" ry="3.0735996"
inkscape:export-xdpi="188.45" inkscape:export-xdpi="188.45"
inkscape:export-ydpi="188.45" /> inkscape:export-ydpi="188.45" />
<text <path
xml:space="preserve" style="font-weight:bold;font-size:16.9333px;line-height:125%;font-family:'Latin Modern Mono Light';-inkscape-font-specification:'Latin Modern Mono Light, Bold';letter-spacing:0px;word-spacing:0px;fill:#ffffff;stroke-width:0.264583px"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:125%;font-family:'Latin Modern Mono Light';-inkscape-font-specification:'Latin Modern Mono Light, ';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 21.505785,62.578241 c 0,-0.626532 -0.474132,-0.643466 -0.795865,-0.643466 h -2.015062 v -8.26345 c 0,-0.609599 -0.118533,-0.812798 -0.795865,-0.812798 h -2.607729 c -0.321732,0 -0.812798,0.01693 -0.812798,0.643465 0,0.609599 0.507999,0.626532 0.795865,0.626532 h 2.015063 v 7.806251 h -1.99813 c -0.321732,0 -0.812798,0.01693 -0.812798,0.643466 0,0.609599 0.507999,0.626532 0.795865,0.626532 h 5.435589 c 0.3048,0 0.795865,-0.01693 0.795865,-0.626532 z m 9.228643,-3.064927 c 0,-2.116663 -1.54093,-3.776126 -3.352793,-3.776126 -0.728132,0 -1.405464,0.237066 -1.964263,0.643465 v -2.709328 c 0,-0.609599 -0.118533,-0.812798 -0.795865,-0.812798 h -1.202265 c -0.321732,0 -0.812798,0.01693 -0.812798,0.643465 0,0.609599 0.507999,0.626532 0.795865,0.626532 h 0.609599 v 8.263451 c 0,0.406399 0.01693,0.812798 0.711198,0.812798 0.524933,0 0.660399,-0.237066 0.677332,-0.575732 0.643466,0.541865 1.303865,0.677332 1.79493,0.677332 1.862663,0 3.53906,-1.625597 3.53906,-3.793059 z m -1.388531,0 c 0,1.49013 -1.083731,2.523061 -2.184395,2.523061 -1.202265,0 -1.727197,-1.371597 -1.727197,-2.116662 v -1.202265 c 0,-0.914398 0.897465,-1.710263 1.862663,-1.710263 1.151464,0 2.048929,1.151465 2.048929,2.506129 z m 10.820373,3.064927 c 0,-0.558799 -0.321733,-0.643466 -0.931331,-0.643466 v -3.860792 c 0,-0.355599 -0.03387,-2.336795 -1.608664,-2.336795 -0.474132,0 -1.117598,0.186266 -1.608663,0.745065 -0.287867,-0.491066 -0.745066,-0.745065 -1.269998,-0.745065 -0.491066,0 -0.948265,0.169333 -1.337731,0.474132 -0.118533,-0.372533 -0.440265,-0.389466 -0.745065,-0.389466 h -0.524932 c -0.3048,0 -0.812799,0.03387 -0.812799,0.626532 0,0.558799 0.321733,0.643466 0.931332,0.643466 v 4.842923 c -0.609599,0 -0.931332,0.08467 -0.931332,0.643466 0,0.609599 0.524933,0.626532 0.812799,0.626532 h 1.43933 c 0.3048,0 0.761999,-0.01693 0.761999,-0.626532 0,-0.558799 -0.270933,-0.643466 -0.880532,-0.643466 v -2.827861 c 0,-1.354664 0.575732,-2.099729 1.219198,-2.099729 0.321733,0 0.474132,0.270933 0.474132,1.219198 v 3.708392 c -0.355599,0.01693 -0.677332,0.1016 -0.677332,0.643466 0,0.609599 0.474133,0.626532 0.761999,0.626532 h 1.236131 c 0.304799,0 0.761998,-0.01693 0.761998,-0.626532 0,-0.558799 -0.287866,-0.643466 -0.897465,-0.643466 v -2.827861 c 0,-1.354664 0.592666,-2.099729 1.219198,-2.099729 0.338666,0 0.474132,0.270933 0.474132,1.219198 v 3.708392 c -0.338666,0.01693 -0.660398,0.1016 -0.660398,0.643466 0,0.609599 0.474132,0.626532 0.745065,0.626532 h 1.236131 c 0.304799,0 0.812798,-0.01693 0.812798,-0.626532 z m 9.829776,-3.064927 c 0,-2.116663 -1.540931,-3.776126 -3.352794,-3.776126 -0.728132,0 -1.422397,0.237066 -1.981196,0.660398 -0.01693,-0.389466 -0.169333,-0.575732 -0.778932,-0.575732 H 42.68086 c -0.321733,0 -0.812798,0.03387 -0.812798,0.643466 0,0.609598 0.507999,0.626532 0.795865,0.626532 h 0.609598 v 8.500516 H 42.68086 c -0.321733,0 -0.812798,0.01693 -0.812798,0.643466 0,0.609598 0.507999,0.626532 0.795865,0.626532 h 2.624661 c 0.287866,0 0.795865,-0.01693 0.795865,-0.626532 0,-0.626532 -0.491065,-0.643466 -0.812798,-0.643466 h -0.592666 v -2.963327 c 0.643466,0.558799 1.286931,0.677332 1.777997,0.677332 1.862663,0 3.53906,-1.625597 3.53906,-3.793059 z m -1.388531,0 c 0,1.49013 -1.083731,2.523061 -2.184396,2.523061 -1.202264,0 -1.727196,-1.371597 -1.727196,-2.116662 v -1.202265 c 0,-0.914398 0.897465,-1.710263 1.862663,-1.710263 1.151464,0 2.048929,1.151465 2.048929,2.506129 z M 59.08922,56.46532 c 0,-0.626533 -0.474133,-0.643466 -0.795865,-0.643466 h -1.930397 c -0.304799,0 -0.795865,0.03387 -0.795865,0.626532 0,0.626532 0.474133,0.643466 0.795865,0.643466 h 0.270933 l -1.456264,4.419591 -1.676396,-4.419591 h 0.220133 c 0.304799,0 0.795865,-0.01693 0.795865,-0.626532 0,-0.626533 -0.474133,-0.643466 -0.795865,-0.643466 h -1.930397 c -0.321732,0 -0.795865,0.01693 -0.795865,0.643466 0,0.609598 0.491066,0.626532 0.795865,0.626532 H 52.2143 l 2.370662,5.977455 c -0.06773,0.186266 -0.423333,1.371597 -0.609599,1.744129 -0.338666,0.643466 -0.863598,1.032932 -1.185331,1.032932 0.01693,-0.06773 0.186266,-0.118533 0.186266,-0.372533 0,-0.491066 -0.355599,-0.846665 -0.846665,-0.846665 -0.524932,0 -0.846665,0.355599 -0.846665,0.846665 0,0.761999 0.609599,1.490131 1.490131,1.490131 1.69333,0 2.523062,-2.252129 2.590795,-2.438396 l 2.523061,-7.433718 h 0.4064 c 0.304799,0 0.795865,-0.01693 0.795865,-0.626532 z"
x="13.547134"
y="63.204773"
id="text1392" id="text1392"
inkscape:export-xdpi="188.45" aria-label="lbm py" />
inkscape:export-ydpi="188.45"><tspan
sodipodi:role="line"
id="tspan1390"
x="13.547134"
y="63.204773"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:16.93333244px;font-family:'Latin Modern Mono Light';-inkscape-font-specification:'Latin Modern Mono Light, Bold';fill:#ffffff;stroke-width:0.26458332px">lbm<tspan
style="font-size:2.82222223px"
id="tspan1398"> </tspan>py</tspan></text>
<path <path
style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.84519458;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-6-9)" style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:0.84519458;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-6-9)"
d="M 36.797679,33.475 H 23.568513" d="M 36.797679,33.475 H 23.568513"
......
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
%% 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)$$
......
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -276,4 +276,42 @@ journal = {Communications in Computational Physics} ...@@ -276,4 +276,42 @@ journal = {Communications in Computational Physics}
url = {https://doi.org/10.1063/1.1399290}, url = {https://doi.org/10.1063/1.1399290},
} }
@Article{rozema15,
doi = {10.1063/1.4928700},
year = {2015},
month = {aug},
publisher = {AIP Publishing},
volume = {27},
number = {8},
author = {Wybe Rozema and Hyun J. Bae and Parviz Moin and Roel Verstappen},
title = {Minimum-dissipation models for large-eddy simulation},
journal = {Physics of Fluids}
}
@article{Han2021,
doi = {10.1088/1873-7005/ac1782},
url = {https://dx.doi.org/10.1088/1873-7005/ac1782} ,
year = {2021},
month = {aug},
publisher = {IOP Publishing},
volume = {53},
number = {4},
pages = {045506},
author = {Mengtao Han and Ryozo Ooka and Hideki Kikumoto},
title = {A wall function approach in lattice Boltzmann method: algorithm and validation using turbulent channel flow},
journal = {Fluid Dynamics Research}
}
@article{Maronga2020,
author = {Maronga, Bj{\"o}rn and Knigge, Christoph and Raasch, Siegfried},
year = {2020},
title = {{An Improved Surface Boundary Condition for Large-Eddy Simulations Based on Monin--Obukhov Similarity Theory: Evaluation and Consequences for Grid Convergence in Neutral and Stable Conditions}},
pages = {297--325},
volume = {174},
number = {2},
issn = {0006-8314},
journal = {{Boundary-layer meteorology}},
doi = {10.1007/s10546-019-00485-w}
}
@Comment{jabref-meta: databaseType:bibtex;} @Comment{jabref-meta: databaseType:bibtex;}
from __future__ import annotations
from typing import Sequence
from argparse import ArgumentParser
import os
import nox
import subprocess
import re
nox.options.sessions = ["lint", "typecheck"]
def get_cuda_version(session: nox.Session) -> None | tuple[int, ...]:
query_args = ["nvcc", "--version"]
try:
query_result = subprocess.run(query_args, capture_output=True)
except FileNotFoundError:
return None
matches = re.findall(r"release \d+\.\d+", str(query_result.stdout))
if matches:
match = matches[0]
version_string = match.split()[-1]
try:
return tuple(int(v) for v in version_string.split("."))
except ValueError:
pass
session.warn("nvcc was found, but I am unable to determine the CUDA version.")
return None
def install_cupy(
session: nox.Session, cupy_version: str, skip_if_no_cuda: bool = False
):
if cupy_version is not None:
cuda_version = get_cuda_version(session)
if cuda_version is None or cuda_version[0] not in (11, 12):
if skip_if_no_cuda:
session.skip(
"No compatible installation of CUDA found - Need either CUDA 11 or 12"
)
else:
session.warn(
"Running without cupy: no compatbile installation of CUDA found. Need either CUDA 11 or 12."
)
return
cuda_major = cuda_version[0]
cupy_package = f"cupy-cuda{cuda_major}x=={cupy_version}"
session.install(cupy_package)
def check_external_doc_dependencies(session: nox.Session):
dot_args = ["dot", "--version"]
try:
_ = subprocess.run(dot_args, capture_output=True)
except FileNotFoundError:
session.error(
"Unable to build documentation: "
"Command `dot` from the `graphviz` package (https://www.graphviz.org/) is not available"
)
def editable_install(session: nox.Session, opts: Sequence[str] = ()):
if opts:
opts_str = "[" + ",".join(opts) + "]"
else:
opts_str = ""
session.install("-e", f".{opts_str}")
def install_pystencils_master(session: nox.Session):
session.install("git+https://i10git.cs.fau.de/pycodegen/pystencils.git@master")
def install_sympy_master(session: nox.Session):
session.install("--upgrade", "git+https://github.com/sympy/sympy.git@master")
@nox.session(python="3.10", tags=["qa", "code-quality"])
def lint(session: nox.Session):
"""Lint code using flake8"""
session.install("flake8")
session.run("flake8", "src/lbmpy")
@nox.session(python="3.10", tags=["qa", "code-quality"])
def typecheck(session: nox.Session):
"""Run MyPy for static type checking"""
editable_install(session)
session.install("mypy")
session.run("mypy", "src/lbmpy")
def run_testsuite(session: nox.Session, coverage: bool = True):
num_cores = os.cpu_count()
args = [
"pytest",
"-v",
"-n",
str(num_cores),
"-m",
"not longrun",
"--html",
"test-report/index.html",
"--junitxml=report.xml",
]
if coverage:
args += [
"--cov-report=term",
"--cov=.",
]
session.run(*args)
if coverage:
session.run("coverage", "html")
session.run("coverage", "xml")
@nox.session(python=["3.10", "3.11", "3.12", "3.13"])
def testsuite_cpu(session: nox.Session):
install_pystencils_master(session)
editable_install(session, ["alltrafos", "use_cython", "interactive", "tests"])
run_testsuite(session, coverage=False)
@nox.session(python=["3.10", "3.11", "3.12", "3.13"])
@nox.parametrize("cupy_version", ["12", "13"], ids=["cupy12", "cupy13"])
def testsuite_gpu(session: nox.Session, cupy_version: str | None):
install_cupy(session, cupy_version, skip_if_no_cuda=True)
install_pystencils_master(session)
editable_install(session, ["alltrafos", "use_cython", "interactive", "tests"])
run_testsuite(session)
@nox.parametrize("cupy_version", [None, "12", "13"], ids=["cpu", "cupy12", "cupy13"])
@nox.session(python="3.10", tags=["test"])
def testsuite_pystencils2(session: nox.Session, cupy_version: str | None):
if cupy_version is not None:
install_cupy(session, cupy_version, skip_if_no_cuda=True)
session.install(
"git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
)
editable_install(session, ["alltrafos", "use_cython", "interactive", "tests"])
run_testsuite(session)
@nox.session
def quicktest(session: nox.Session):
parser = ArgumentParser()
parser.add_argument(
"--sympy-master", action="store_true", help="Use latest SymPy master revision"
)
args = parser.parse_args(session.posargs)
install_pystencils_master(session)
editable_install(session)
if args.sympy_master:
install_sympy_master(session)
session.run("python", "quicktest.py")
...@@ -11,7 +11,7 @@ authors = [ ...@@ -11,7 +11,7 @@ authors = [
] ]
license = { file = "COPYING.txt" } license = { file = "COPYING.txt" }
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = ["pystencils>=1.3", "sympy>=1.6,<=1.11.1", "numpy>=1.8.0", "appdirs", "joblib"] dependencies = ["pystencils>=1.3", "sympy>=1.12", "numpy>=1.8.0", "appdirs", "joblib", "packaging"]
classifiers = [ classifiers = [
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
"Framework :: Jupyter", "Framework :: Jupyter",
...@@ -69,8 +69,7 @@ tests = [ ...@@ -69,8 +69,7 @@ tests = [
[build-system] [build-system]
requires = [ requires = [
"setuptools>=69", "setuptools>=69",
"versioneer>=0.29", "versioneer[toml]>=0.29",
"tomli; python_version < '3.11'",
] ]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
...@@ -87,7 +86,7 @@ namespaces = false ...@@ -87,7 +86,7 @@ namespaces = false
# resulting files. # resulting files.
VCS = "git" VCS = "git"
style = "pep440" style = "pep440"
versionfile_source = "lbmpy/_version.py" versionfile_source = "src/lbmpy/_version.py"
versionfile_build = "lbmpy/_version.py" versionfile_build = "lbmpy/_version.py"
tag_prefix = "release/" tag_prefix = "release/"
parentdir_prefix = "lbmpy-" parentdir_prefix = "lbmpy-"
...@@ -3,7 +3,12 @@ testpaths = src tests doc/notebooks ...@@ -3,7 +3,12 @@ testpaths = src tests doc/notebooks
pythonpath = src pythonpath = src
python_files = test_*.py *_test.py scenario_*.py python_files = test_*.py *_test.py scenario_*.py
norecursedirs = *.egg-info .git .cache .ipynb_checkpoints htmlcov norecursedirs = *.egg-info .git .cache .ipynb_checkpoints htmlcov
addopts = --doctest-modules --durations=20 --cov-config pytest.ini addopts =
--doctest-modules --durations=20
--cov-config pytest.ini
--ignore=src/lbmpy/custom_code_nodes.py
--ignore=src/lbmpy/lookup_tables.py
--ignore=src/lbmpy/phasefield_allen_cahn/contact_angle.py
markers = markers =
longrun: tests only run at night since they have large execution time longrun: tests only run at night since they have large execution time
notebook: mark for notebooks notebook: mark for notebooks
...@@ -24,7 +29,10 @@ omit = doc/* ...@@ -24,7 +29,10 @@ omit = doc/*
setup.py setup.py
conftest.py conftest.py
versioneer.py versioneer.py
quicktest.py
noxfile.py
src/lbmpy/_version.py src/lbmpy/_version.py
src/lbmpy/_compat.py
venv/ venv/
[report] [report]
......
...@@ -7,11 +7,12 @@ from .creationfunctions import ( ...@@ -7,11 +7,12 @@ from .creationfunctions import (
LBMConfig, LBMConfig,
LBMOptimisation, LBMOptimisation,
) )
from .enums import Stencil, Method, ForceModel, CollisionSpace from .enums import Stencil, Method, ForceModel, CollisionSpace, SubgridScaleModel
from .lbstep import LatticeBoltzmannStep from .lbstep import LatticeBoltzmannStep
from .macroscopic_value_kernels import ( from .macroscopic_value_kernels import (
pdf_initialization_assignments, pdf_initialization_assignments,
macroscopic_values_getter, macroscopic_values_getter,
strain_rate_tensor_getter,
compile_macroscopic_values_getter, compile_macroscopic_values_getter,
compile_macroscopic_values_setter, compile_macroscopic_values_setter,
create_advanced_velocity_setter_collision_rule, create_advanced_velocity_setter_collision_rule,
...@@ -38,9 +39,11 @@ __all__ = [ ...@@ -38,9 +39,11 @@ __all__ = [
"Method", "Method",
"ForceModel", "ForceModel",
"CollisionSpace", "CollisionSpace",
"SubgridScaleModel",
"LatticeBoltzmannStep", "LatticeBoltzmannStep",
"pdf_initialization_assignments", "pdf_initialization_assignments",
"macroscopic_values_getter", "macroscopic_values_getter",
"strain_rate_tensor_getter",
"compile_macroscopic_values_getter", "compile_macroscopic_values_getter",
"compile_macroscopic_values_setter", "compile_macroscopic_values_setter",
"create_advanced_velocity_setter_collision_rule", "create_advanced_velocity_setter_collision_rule",
...@@ -54,7 +57,5 @@ __all__ = [ ...@@ -54,7 +57,5 @@ __all__ = [
] ]
from ._version import get_versions from . import _version
__version__ = _version.get_versions()['version']
__version__ = get_versions()["version"]
del get_versions
from pystencils import __version__ as ps_version
# Determine if we're running pystencils 1.x or 2.x
version_tokes = ps_version.split(".")
PYSTENCILS_VERSION_MAJOR = int(version_tokes[0])
IS_PYSTENCILS_2 = PYSTENCILS_VERSION_MAJOR == 2
if IS_PYSTENCILS_2:
from pystencils.defaults import DEFAULTS
def get_loop_counter_symbol(coord: int):
return DEFAULTS.spatial_counters[coord]
def get_supported_instruction_sets():
from pystencils import Target
vector_targets = Target.available_vector_cpu_targets()
isas = []
for target in vector_targets:
tokens = target.name.split("_")
isas.append(tokens[-1].lower())
return isas
else:
from pystencils.backends.simd_instruction_sets import (
get_supported_instruction_sets as get_supported_instruction_sets_,
)
get_supported_instruction_sets = get_supported_instruction_sets_
def get_loop_counter_symbol(coord: int):
from pystencils.astnodes import LoopOverCoordinate
return LoopOverCoordinate.get_loop_counter_symbol(coord)
def import_guard_pystencils1(feature):
if IS_PYSTENCILS_2:
raise ImportError(
f"The following feature is not yet available when running pystencils 2.x: {feature}"
)
return True
...@@ -5,8 +5,9 @@ ...@@ -5,8 +5,9 @@
# directories (produced by setup.py build) will contain a much shorter file # directories (produced by setup.py build) will contain a much shorter file
# that just contains the computed version number. # that just contains the computed version number.
# This file is released into the public domain. Generated by # This file is released into the public domain.
# versioneer-0.19 (https://github.com/python-versioneer/python-versioneer) # Generated by versioneer-0.29
# https://github.com/python-versioneer/python-versioneer
"""Git implementation of _version.py.""" """Git implementation of _version.py."""
...@@ -15,9 +16,11 @@ import os ...@@ -15,9 +16,11 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
from typing import Any, Callable, Dict, List, Optional, Tuple
import functools
def get_keywords(): def get_keywords() -> Dict[str, str]:
"""Get the keywords needed to look up the version information.""" """Get the keywords needed to look up the version information."""
# these strings will be replaced by git during git-archive. # these strings will be replaced by git during git-archive.
# setup.py/versioneer.py will grep for the variable names, so they must # setup.py/versioneer.py will grep for the variable names, so they must
...@@ -33,8 +36,15 @@ def get_keywords(): ...@@ -33,8 +36,15 @@ def get_keywords():
class VersioneerConfig: class VersioneerConfig:
"""Container for Versioneer configuration parameters.""" """Container for Versioneer configuration parameters."""
VCS: str
style: str
tag_prefix: str
parentdir_prefix: str
versionfile_source: str
verbose: bool
def get_config():
def get_config() -> VersioneerConfig:
"""Create, populate and return the VersioneerConfig() object.""" """Create, populate and return the VersioneerConfig() object."""
# these strings are filled in when 'setup.py versioneer' creates # these strings are filled in when 'setup.py versioneer' creates
# _version.py # _version.py
...@@ -43,7 +53,7 @@ def get_config(): ...@@ -43,7 +53,7 @@ def get_config():
cfg.style = "pep440" cfg.style = "pep440"
cfg.tag_prefix = "release/" cfg.tag_prefix = "release/"
cfg.parentdir_prefix = "lbmpy-" cfg.parentdir_prefix = "lbmpy-"
cfg.versionfile_source = "lbmpy/_version.py" cfg.versionfile_source = "src/lbmpy/_version.py"
cfg.verbose = False cfg.verbose = False
return cfg return cfg
...@@ -52,13 +62,13 @@ class NotThisMethod(Exception): ...@@ -52,13 +62,13 @@ class NotThisMethod(Exception):
"""Exception raised if a method is not valid for the current scenario.""" """Exception raised if a method is not valid for the current scenario."""
LONG_VERSION_PY = {} LONG_VERSION_PY: Dict[str, str] = {}
HANDLERS = {} HANDLERS: Dict[str, Dict[str, Callable]] = {}
def register_vcs_handler(vcs, method): # decorator def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator
"""Create decorator to mark a method as the handler of a VCS.""" """Create decorator to mark a method as the handler of a VCS."""
def decorate(f): def decorate(f: Callable) -> Callable:
"""Store f in HANDLERS[vcs][method].""" """Store f in HANDLERS[vcs][method]."""
if vcs not in HANDLERS: if vcs not in HANDLERS:
HANDLERS[vcs] = {} HANDLERS[vcs] = {}
...@@ -67,22 +77,35 @@ def register_vcs_handler(vcs, method): # decorator ...@@ -67,22 +77,35 @@ def register_vcs_handler(vcs, method): # decorator
return decorate return decorate
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, def run_command(
env=None): commands: List[str],
args: List[str],
cwd: Optional[str] = None,
verbose: bool = False,
hide_stderr: bool = False,
env: Optional[Dict[str, str]] = None,
) -> Tuple[Optional[str], Optional[int]]:
"""Call the given command(s).""" """Call the given command(s)."""
assert isinstance(commands, list) assert isinstance(commands, list)
p = None process = None
for c in commands:
popen_kwargs: Dict[str, Any] = {}
if sys.platform == "win32":
# This hides the console window if pythonw.exe is used
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
popen_kwargs["startupinfo"] = startupinfo
for command in commands:
try: try:
dispcmd = str([c] + args) dispcmd = str([command] + args)
# remember shell=False, so use git.cmd on windows, not just git # remember shell=False, so use git.cmd on windows, not just git
p = subprocess.Popen([c] + args, cwd=cwd, env=env, process = subprocess.Popen([command] + args, cwd=cwd, env=env,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=(subprocess.PIPE if hide_stderr stderr=(subprocess.PIPE if hide_stderr
else None)) else None), **popen_kwargs)
break break
except EnvironmentError: except OSError as e:
e = sys.exc_info()[1]
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
continue continue
if verbose: if verbose:
...@@ -93,16 +116,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, ...@@ -93,16 +116,20 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
if verbose: if verbose:
print("unable to find command, tried %s" % (commands,)) print("unable to find command, tried %s" % (commands,))
return None, None return None, None
stdout = p.communicate()[0].strip().decode() stdout = process.communicate()[0].strip().decode()
if p.returncode != 0: if process.returncode != 0:
if verbose: if verbose:
print("unable to run %s (error)" % dispcmd) print("unable to run %s (error)" % dispcmd)
print("stdout was %s" % stdout) print("stdout was %s" % stdout)
return None, p.returncode return None, process.returncode
return stdout, p.returncode return stdout, process.returncode
def versions_from_parentdir(parentdir_prefix, root, verbose): def versions_from_parentdir(
parentdir_prefix: str,
root: str,
verbose: bool,
) -> Dict[str, Any]:
"""Try to determine the version from the parent directory name. """Try to determine the version from the parent directory name.
Source tarballs conventionally unpack into a directory that includes both Source tarballs conventionally unpack into a directory that includes both
...@@ -111,15 +138,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): ...@@ -111,15 +138,14 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
""" """
rootdirs = [] rootdirs = []
for i in range(3): for _ in range(3):
dirname = os.path.basename(root) dirname = os.path.basename(root)
if dirname.startswith(parentdir_prefix): if dirname.startswith(parentdir_prefix):
return {"version": dirname[len(parentdir_prefix):], return {"version": dirname[len(parentdir_prefix):],
"full-revisionid": None, "full-revisionid": None,
"dirty": False, "error": None, "date": None} "dirty": False, "error": None, "date": None}
else: rootdirs.append(root)
rootdirs.append(root) root = os.path.dirname(root) # up a level
root = os.path.dirname(root) # up a level
if verbose: if verbose:
print("Tried directories %s but none started with prefix %s" % print("Tried directories %s but none started with prefix %s" %
...@@ -128,39 +154,42 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): ...@@ -128,39 +154,42 @@ def versions_from_parentdir(parentdir_prefix, root, verbose):
@register_vcs_handler("git", "get_keywords") @register_vcs_handler("git", "get_keywords")
def git_get_keywords(versionfile_abs): def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
"""Extract version information from the given file.""" """Extract version information from the given file."""
# the code embedded in _version.py can just fetch the value of these # the code embedded in _version.py can just fetch the value of these
# keywords. When used from setup.py, we don't want to import _version.py, # keywords. When used from setup.py, we don't want to import _version.py,
# so we do it with a regexp instead. This function is not used from # so we do it with a regexp instead. This function is not used from
# _version.py. # _version.py.
keywords = {} keywords: Dict[str, str] = {}
try: try:
f = open(versionfile_abs, "r") with open(versionfile_abs, "r") as fobj:
for line in f.readlines(): for line in fobj:
if line.strip().startswith("git_refnames ="): if line.strip().startswith("git_refnames ="):
mo = re.search(r'=\s*"(.*)"', line) mo = re.search(r'=\s*"(.*)"', line)
if mo: if mo:
keywords["refnames"] = mo.group(1) keywords["refnames"] = mo.group(1)
if line.strip().startswith("git_full ="): if line.strip().startswith("git_full ="):
mo = re.search(r'=\s*"(.*)"', line) mo = re.search(r'=\s*"(.*)"', line)
if mo: if mo:
keywords["full"] = mo.group(1) keywords["full"] = mo.group(1)
if line.strip().startswith("git_date ="): if line.strip().startswith("git_date ="):
mo = re.search(r'=\s*"(.*)"', line) mo = re.search(r'=\s*"(.*)"', line)
if mo: if mo:
keywords["date"] = mo.group(1) keywords["date"] = mo.group(1)
f.close() except OSError:
except EnvironmentError:
pass pass
return keywords return keywords
@register_vcs_handler("git", "keywords") @register_vcs_handler("git", "keywords")
def git_versions_from_keywords(keywords, tag_prefix, verbose): def git_versions_from_keywords(
keywords: Dict[str, str],
tag_prefix: str,
verbose: bool,
) -> Dict[str, Any]:
"""Get version information from git keywords.""" """Get version information from git keywords."""
if not keywords: if "refnames" not in keywords:
raise NotThisMethod("no keywords at all, weird") raise NotThisMethod("Short version file found")
date = keywords.get("date") date = keywords.get("date")
if date is not None: if date is not None:
# Use only the last line. Previous lines may contain GPG signature # Use only the last line. Previous lines may contain GPG signature
...@@ -179,11 +208,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): ...@@ -179,11 +208,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
if verbose: if verbose:
print("keywords are unexpanded, not using") print("keywords are unexpanded, not using")
raise NotThisMethod("unexpanded keywords, not a git-archive tarball") raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
refs = set([r.strip() for r in refnames.strip("()").split(",")]) refs = {r.strip() for r in refnames.strip("()").split(",")}
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
# just "foo-1.0". If we see a "tag: " prefix, prefer those. # just "foo-1.0". If we see a "tag: " prefix, prefer those.
TAG = "tag: " TAG = "tag: "
tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
if not tags: if not tags:
# Either we're using git < 1.8.3, or there really are no tags. We use # Either we're using git < 1.8.3, or there really are no tags. We use
# a heuristic: assume all version tags have a digit. The old git %d # a heuristic: assume all version tags have a digit. The old git %d
...@@ -192,7 +221,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): ...@@ -192,7 +221,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
# between branches and tags. By ignoring refnames without digits, we # between branches and tags. By ignoring refnames without digits, we
# filter out many common branch names like "release" and # filter out many common branch names like "release" and
# "stabilization", as well as "HEAD" and "master". # "stabilization", as well as "HEAD" and "master".
tags = set([r for r in refs if re.search(r'\d', r)]) tags = {r for r in refs if re.search(r'\d', r)}
if verbose: if verbose:
print("discarding '%s', no digits" % ",".join(refs - tags)) print("discarding '%s', no digits" % ",".join(refs - tags))
if verbose: if verbose:
...@@ -201,6 +230,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): ...@@ -201,6 +230,11 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
# sorting will prefer e.g. "2.0" over "2.0rc1" # sorting will prefer e.g. "2.0" over "2.0rc1"
if ref.startswith(tag_prefix): if ref.startswith(tag_prefix):
r = ref[len(tag_prefix):] r = ref[len(tag_prefix):]
# Filter out refs that exactly match prefix or that don't start
# with a number once the prefix is stripped (mostly a concern
# when prefix is '')
if not re.match(r'\d', r):
continue
if verbose: if verbose:
print("picking %s" % r) print("picking %s" % r)
return {"version": r, return {"version": r,
...@@ -216,7 +250,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): ...@@ -216,7 +250,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
@register_vcs_handler("git", "pieces_from_vcs") @register_vcs_handler("git", "pieces_from_vcs")
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): def git_pieces_from_vcs(
tag_prefix: str,
root: str,
verbose: bool,
runner: Callable = run_command
) -> Dict[str, Any]:
"""Get version from 'git describe' in the root of the source tree. """Get version from 'git describe' in the root of the source tree.
This only gets called if the git-archive 'subst' keywords were *not* This only gets called if the git-archive 'subst' keywords were *not*
...@@ -227,8 +266,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): ...@@ -227,8 +266,15 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
if sys.platform == "win32": if sys.platform == "win32":
GITS = ["git.cmd", "git.exe"] GITS = ["git.cmd", "git.exe"]
out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, # GIT_DIR can interfere with correct operation of Versioneer.
hide_stderr=True) # It may be intended to be passed to the Versioneer-versioned project,
# but that should not change where we get our version from.
env = os.environ.copy()
env.pop("GIT_DIR", None)
runner = functools.partial(runner, env=env)
_, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
hide_stderr=not verbose)
if rc != 0: if rc != 0:
if verbose: if verbose:
print("Directory %s not under git control" % root) print("Directory %s not under git control" % root)
...@@ -236,24 +282,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): ...@@ -236,24 +282,57 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
# if there isn't one, this yields HEX[-dirty] (no NUM) # if there isn't one, this yields HEX[-dirty] (no NUM)
describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", describe_out, rc = runner(GITS, [
"--always", "--long", "describe", "--tags", "--dirty", "--always", "--long",
"--match", "%s*" % tag_prefix], "--match", f"{tag_prefix}[[:digit:]]*"
cwd=root) ], cwd=root)
# --long was added in git-1.5.5 # --long was added in git-1.5.5
if describe_out is None: if describe_out is None:
raise NotThisMethod("'git describe' failed") raise NotThisMethod("'git describe' failed")
describe_out = describe_out.strip() describe_out = describe_out.strip()
full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
if full_out is None: if full_out is None:
raise NotThisMethod("'git rev-parse' failed") raise NotThisMethod("'git rev-parse' failed")
full_out = full_out.strip() full_out = full_out.strip()
pieces = {} pieces: Dict[str, Any] = {}
pieces["long"] = full_out pieces["long"] = full_out
pieces["short"] = full_out[:7] # maybe improved later pieces["short"] = full_out[:7] # maybe improved later
pieces["error"] = None pieces["error"] = None
branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
cwd=root)
# --abbrev-ref was added in git-1.6.3
if rc != 0 or branch_name is None:
raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
branch_name = branch_name.strip()
if branch_name == "HEAD":
# If we aren't exactly on a branch, pick a branch which represents
# the current commit. If all else fails, we are on a branchless
# commit.
branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
# --contains was added in git-1.5.4
if rc != 0 or branches is None:
raise NotThisMethod("'git branch --contains' returned error")
branches = branches.split("\n")
# Remove the first line if we're running detached
if "(" in branches[0]:
branches.pop(0)
# Strip off the leading "* " from the list of branches.
branches = [branch[2:] for branch in branches]
if "master" in branches:
branch_name = "master"
elif not branches:
branch_name = None
else:
# Pick the first branch that is returned. Good or bad.
branch_name = branches[0]
pieces["branch"] = branch_name
# parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
# TAG might have hyphens. # TAG might have hyphens.
git_describe = describe_out git_describe = describe_out
...@@ -270,7 +349,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): ...@@ -270,7 +349,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
# TAG-NUM-gHEX # TAG-NUM-gHEX
mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
if not mo: if not mo:
# unparseable. Maybe git-describe is misbehaving? # unparsable. Maybe git-describe is misbehaving?
pieces["error"] = ("unable to parse git-describe output: '%s'" pieces["error"] = ("unable to parse git-describe output: '%s'"
% describe_out) % describe_out)
return pieces return pieces
...@@ -295,13 +374,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): ...@@ -295,13 +374,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
else: else:
# HEX: no tags # HEX: no tags
pieces["closest-tag"] = None pieces["closest-tag"] = None
count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
cwd=root) pieces["distance"] = len(out.split()) # total number of commits
pieces["distance"] = int(count_out) # total number of commits
# commit date: see ISO-8601 comment in git_versions_from_keywords() # commit date: see ISO-8601 comment in git_versions_from_keywords()
date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
cwd=root)[0].strip()
# Use only the last line. Previous lines may contain GPG signature # Use only the last line. Previous lines may contain GPG signature
# information. # information.
date = date.splitlines()[-1] date = date.splitlines()[-1]
...@@ -310,14 +387,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): ...@@ -310,14 +387,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
return pieces return pieces
def plus_or_dot(pieces): def plus_or_dot(pieces: Dict[str, Any]) -> str:
"""Return a + if we don't already have one, else return a .""" """Return a + if we don't already have one, else return a ."""
if "+" in pieces.get("closest-tag", ""): if "+" in pieces.get("closest-tag", ""):
return "." return "."
return "+" return "+"
def render_pep440(pieces): def render_pep440(pieces: Dict[str, Any]) -> str:
"""Build up version string, with post-release "local version identifier". """Build up version string, with post-release "local version identifier".
Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
...@@ -342,23 +419,71 @@ def render_pep440(pieces): ...@@ -342,23 +419,71 @@ def render_pep440(pieces):
return rendered return rendered
def render_pep440_pre(pieces): def render_pep440_branch(pieces: Dict[str, Any]) -> str:
"""TAG[.post0.devDISTANCE] -- No -dirty. """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
The ".dev0" means not master branch. Note that .dev0 sorts backwards
(a feature branch will appear "older" than the master branch).
Exceptions: Exceptions:
1: no tags. 0.post0.devDISTANCE 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
""" """
if pieces["closest-tag"]: if pieces["closest-tag"]:
rendered = pieces["closest-tag"] rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
if pieces["branch"] != "master":
rendered += ".dev0"
rendered += plus_or_dot(pieces)
rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
else:
# exception #1
rendered = "0"
if pieces["branch"] != "master":
rendered += ".dev0"
rendered += "+untagged.%d.g%s" % (pieces["distance"],
pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
return rendered
def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
"""Split pep440 version string at the post-release segment.
Returns the release segments before the post-release and the
post-release version number (or -1 if no post-release segment is present).
"""
vc = str.split(ver, ".post")
return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
def render_pep440_pre(pieces: Dict[str, Any]) -> str:
"""TAG[.postN.devDISTANCE] -- No -dirty.
Exceptions:
1: no tags. 0.post0.devDISTANCE
"""
if pieces["closest-tag"]:
if pieces["distance"]: if pieces["distance"]:
rendered += ".post0.dev%d" % pieces["distance"] # update the post release segment
tag_version, post_version = pep440_split_post(pieces["closest-tag"])
rendered = tag_version
if post_version is not None:
rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
else:
rendered += ".post0.dev%d" % (pieces["distance"])
else:
# no commits, use the tag as the version
rendered = pieces["closest-tag"]
else: else:
# exception #1 # exception #1
rendered = "0.post0.dev%d" % pieces["distance"] rendered = "0.post0.dev%d" % pieces["distance"]
return rendered return rendered
def render_pep440_post(pieces): def render_pep440_post(pieces: Dict[str, Any]) -> str:
"""TAG[.postDISTANCE[.dev0]+gHEX] . """TAG[.postDISTANCE[.dev0]+gHEX] .
The ".dev0" means dirty. Note that .dev0 sorts backwards The ".dev0" means dirty. Note that .dev0 sorts backwards
...@@ -385,7 +510,36 @@ def render_pep440_post(pieces): ...@@ -385,7 +510,36 @@ def render_pep440_post(pieces):
return rendered return rendered
def render_pep440_old(pieces): def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
"""TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
The ".dev0" means not master branch.
Exceptions:
1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += ".post%d" % pieces["distance"]
if pieces["branch"] != "master":
rendered += ".dev0"
rendered += plus_or_dot(pieces)
rendered += "g%s" % pieces["short"]
if pieces["dirty"]:
rendered += ".dirty"
else:
# exception #1
rendered = "0.post%d" % pieces["distance"]
if pieces["branch"] != "master":
rendered += ".dev0"
rendered += "+g%s" % pieces["short"]
if pieces["dirty"]:
rendered += ".dirty"
return rendered
def render_pep440_old(pieces: Dict[str, Any]) -> str:
"""TAG[.postDISTANCE[.dev0]] . """TAG[.postDISTANCE[.dev0]] .
The ".dev0" means dirty. The ".dev0" means dirty.
...@@ -407,7 +561,7 @@ def render_pep440_old(pieces): ...@@ -407,7 +561,7 @@ def render_pep440_old(pieces):
return rendered return rendered
def render_git_describe(pieces): def render_git_describe(pieces: Dict[str, Any]) -> str:
"""TAG[-DISTANCE-gHEX][-dirty]. """TAG[-DISTANCE-gHEX][-dirty].
Like 'git describe --tags --dirty --always'. Like 'git describe --tags --dirty --always'.
...@@ -427,7 +581,7 @@ def render_git_describe(pieces): ...@@ -427,7 +581,7 @@ def render_git_describe(pieces):
return rendered return rendered
def render_git_describe_long(pieces): def render_git_describe_long(pieces: Dict[str, Any]) -> str:
"""TAG-DISTANCE-gHEX[-dirty]. """TAG-DISTANCE-gHEX[-dirty].
Like 'git describe --tags --dirty --always -long'. Like 'git describe --tags --dirty --always -long'.
...@@ -447,7 +601,7 @@ def render_git_describe_long(pieces): ...@@ -447,7 +601,7 @@ def render_git_describe_long(pieces):
return rendered return rendered
def render(pieces, style): def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
"""Render the given version pieces into the requested style.""" """Render the given version pieces into the requested style."""
if pieces["error"]: if pieces["error"]:
return {"version": "unknown", return {"version": "unknown",
...@@ -461,10 +615,14 @@ def render(pieces, style): ...@@ -461,10 +615,14 @@ def render(pieces, style):
if style == "pep440": if style == "pep440":
rendered = render_pep440(pieces) rendered = render_pep440(pieces)
elif style == "pep440-branch":
rendered = render_pep440_branch(pieces)
elif style == "pep440-pre": elif style == "pep440-pre":
rendered = render_pep440_pre(pieces) rendered = render_pep440_pre(pieces)
elif style == "pep440-post": elif style == "pep440-post":
rendered = render_pep440_post(pieces) rendered = render_pep440_post(pieces)
elif style == "pep440-post-branch":
rendered = render_pep440_post_branch(pieces)
elif style == "pep440-old": elif style == "pep440-old":
rendered = render_pep440_old(pieces) rendered = render_pep440_old(pieces)
elif style == "git-describe": elif style == "git-describe":
...@@ -479,7 +637,7 @@ def render(pieces, style): ...@@ -479,7 +637,7 @@ def render(pieces, style):
"date": pieces.get("date")} "date": pieces.get("date")}
def get_versions(): def get_versions() -> Dict[str, Any]:
"""Get version information or return default if unable to do so.""" """Get version information or return default if unable to do so."""
# I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
# __file__, we can work backwards from there to the root. Some # __file__, we can work backwards from there to the root. Some
...@@ -500,7 +658,7 @@ def get_versions(): ...@@ -500,7 +658,7 @@ def get_versions():
# versionfile_source is the relative path from the top of the source # versionfile_source is the relative path from the top of the source
# tree (where the .git directory might live) to this file. Invert # tree (where the .git directory might live) to this file. Invert
# this to find the root from __file__. # this to find the root from __file__.
for i in cfg.versionfile_source.split('/'): for _ in cfg.versionfile_source.split('/'):
root = os.path.dirname(root) root = os.path.dirname(root)
except NameError: except NameError:
return {"version": "0+unknown", "full-revisionid": None, return {"version": "0+unknown", "full-revisionid": None,
......