diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000000000000000000000000000000000000..a2cf044e31522de52c481840728da972d9ade173
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,5 @@
+[flake8]
+max-line-length=120
+exclude=lbmpy/plot2d.py
+        lbmpy/session.py
+ignore = W293 W503 W291
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..73c1ace7cf8cf1d79984375aa3f6db811804a3be
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+__pycache__
+.ipynb_checkpoints
+.coverage
+*.pyc
+*.vti
+/build
+/dist
+/*.egg-info
+.cache
+_build
+/.idea
+.cache
+_local_tmp
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9ca03994bb92d1413807a2d0fc95e68204726c9c
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,133 @@
+stages:
+  - test
+  - deploy
+
+
+# --------------------------  Tests ------------------------------------------------------------------------------------
+
+# Normal test - runs on every commit all but "long run" tests
+tests-and-coverage:
+  stage: test
+  except:
+    variables:
+      - $ENABLE_NIGHTLY_BUILDS
+  image: i10git.cs.fau.de:5005/software/pystencils/full
+  script:
+    - export NUM_CORES=$(nproc --all)
+    - mkdir -p ~/.config/matplotlib
+    - echo "backend:template" > ~/.config/matplotlib/matplotlibrc
+    - mkdir public
+    - py.test -v -n $NUM_CORES --cov-report html --cov-report term --cov=. -m "not longrun"
+  tags:
+    - docker
+    - cuda
+    - AVX
+  artifacts:
+    when: always
+    paths:
+      - coverage_report
+
+# Nightly test  - runs "long run" jobs only
+test-longrun:
+  stage: test
+  only:
+    variables:
+      - $ENABLE_NIGHTLY_BUILDS
+  image: i10git.cs.fau.de:5005/software/pystencils/full
+  script:
+    - export NUM_CORES=$(nproc --all)
+    - mkdir -p ~/.config/matplotlib
+    - echo "backend:template" > ~/.config/matplotlib/matplotlibrc
+    - py.test -v -n $NUM_CORES --cov-report html --cov-report term --cov=.
+  tags:
+    - docker
+    - cuda
+    - AVX
+  artifacts:
+    paths:
+      - coverage_report
+
+# Minimal tests in windows environment
+minimal-windows:
+  stage: test
+  except:
+    variables:
+      - $ENABLE_NIGHTLY_BUILDS
+  tags:
+    - win
+  script:
+    - source /cygdrive/c/Users/build/Miniconda3/Scripts/activate
+    - source activate pystencils_dev
+    - env
+    - conda env list
+    - python -c "import numpy"
+    - python setup.py quicktest
+
+minimal-ubuntu:
+  stage: test
+  except:
+    variables:
+      - $ENABLE_NIGHTLY_BUILDS
+  image: i10git.cs.fau.de:5005/software/pystencils/minimal_ubuntu
+  script:
+    - python3 setup.py quicktest
+  tags:
+    - docker
+
+minimal-conda:
+  stage: test
+  except:
+    variables:
+      - $ENABLE_NIGHTLY_BUILDS
+  image: i10git.cs.fau.de:5005/software/pystencils/minimal_conda
+  script:
+    - python setup.py quicktest
+  tags:
+    - docker
+
+
+# -------------------- Linter & Documentation --------------------------------------------------------------------------
+
+
+flake8-lint:
+  stage: test
+  except:
+    variables:
+      - $ENABLE_NIGHTLY_BUILDS
+  image: i10git.cs.fau.de:5005/software/pystencils/full
+  script:
+    - flake8 lbmpy
+  tags:
+    - docker
+    - cuda
+
+
+build-documentation:
+  stage: test
+  image: i10git.cs.fau.de:5005/software/pystencils/full
+  script:
+    - export PYTHONPATH=`pwd`
+    - mkdir html_doc
+    - sphinx-build -W -b html doc  html_doc
+  tags:
+    - docker
+    - cuda
+  artifacts:
+    paths:
+      - html_doc
+
+
+pages:
+  image: i10git.cs.fau.de:5005/software/pystencils/full
+  stage: deploy
+  script:
+    - ls -l
+    - mv coverage_report html_doc
+    - mv html_doc public  # folder has to be named "public" for gitlab to publish it
+  artifacts:
+    paths:
+      - public
+  tags:
+    - docker
+  only:
+    - master@pycodegen/lbmpy
diff --git a/conftest.py b/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c17a79a683ac93fa0afa4f30a1a75d86a66b379
--- /dev/null
+++ b/conftest.py
@@ -0,0 +1,117 @@
+import os
+import pytest
+import tempfile
+import runpy
+import sys
+# Trigger config file reading / creation once - to avoid race conditions when multiple instances are creating it
+# at the same time
+from pystencils.cpu import cpujit
+
+# trigger cython imports - there seems to be a problem when multiple processes try to compile the same cython file
+# at the same time
+try:
+    import pyximport
+    pyximport.install(language_level=3)
+except ImportError:
+    pass
+
+
+SCRIPT_FOLDER = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.abspath('lbmpy'))
+
+
+def add_path_to_ignore(path):
+    if not os.path.exists(path):
+        return
+    global collect_ignore
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, path, f) for f in os.listdir(os.path.join(SCRIPT_FOLDER, path))]
+
+
+collect_ignore = [os.path.join(SCRIPT_FOLDER, "doc", "conf.py"),
+                  os.path.join(SCRIPT_FOLDER, "doc", "img", "mb_discretization", "maxwell_boltzmann_stencil_plot.py")]
+add_path_to_ignore('pystencils_tests/benchmark')
+add_path_to_ignore('_local_tmp')
+
+
+try:
+    import pycuda
+except ImportError:
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, "lbmpy_tests/test_cpu_gpu_equivalence.py")]
+
+try:
+    import waLBerla
+except ImportError:
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, "lbmpy_tests/test_serial_scenarios.py")]
+    collect_ignore += [os.path.join(SCRIPT_FOLDER, "lbmpy_tests/test_datahandling_parallel.py")]
+
+
+collect_ignore += [os.path.join(SCRIPT_FOLDER, 'setup.py')]
+
+for root, sub_dirs, files in os.walk('.'):
+    for f in files:
+        if f.endswith(".ipynb") and not any(f.startswith(k) for k in ['demo', 'tutorial', 'test', 'doc']):
+            collect_ignore.append(f)
+
+
+import nbformat
+from nbconvert import PythonExporter
+
+
+class IPythonMockup:
+    def run_line_magic(self, *args, **kwargs):
+        pass
+
+    def run_cell_magic(self, *args, **kwargs):
+        pass
+
+    def magic(self, *args, **kwargs):
+        pass
+
+    def __bool__(self):
+        return False
+
+
+class IPyNbTest(pytest.Item):
+    def __init__(self, name, parent, code):
+        super(IPyNbTest, self).__init__(name, parent)
+        self.code = code
+        self.add_marker('notebook')
+
+    def runtest(self):
+        global_dict = {'get_ipython': lambda: IPythonMockup(),
+                       'is_test_run': True}
+
+        # disable matplotlib output
+        exec("import matplotlib.pyplot as p; "
+             "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
+        # plot is created. This warning is suppressed here
+        exec("import warnings;"
+             "warnings.filterwarnings('ignore', 'Adding an axes using the same arguments as a previous.*')",
+             global_dict)
+        with tempfile.NamedTemporaryFile() as f:
+            f.write(self.code.encode())
+            f.flush()
+            runpy.run_path(f.name, init_globals=global_dict, run_name=self.name)
+
+
+class IPyNbFile(pytest.File):
+    def collect(self):
+        exporter = PythonExporter()
+        exporter.exclude_markdown = True
+        exporter.exclude_input_prompt = True
+
+        notebook_contents = self.fspath.open()
+        notebook = nbformat.read(notebook_contents, 4)
+        code, _ = exporter.from_notebook_node(notebook)
+        yield IPyNbTest(self.name, self, code)
+
+    def teardown(self):
+        pass
+
+
+def pytest_collect_file(path, parent):
+    glob_exprs = ["*demo*.ipynb", "*tutorial*.ipynb", "test_*.ipynb"]
+    if any(path.fnmatch(g) for g in glob_exprs):
+        return IPyNbFile(path, parent)
diff --git a/doc/conf.py b/doc/conf.py
index a56be9f74dcba06275d602dcd3f0349a6e7cbb0c..befdb443869eead8001a173274d827b54a45a641 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -1,13 +1,59 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 #
+import datetime
+import sphinx_rtd_theme
 import os
 import sys
 
-sys.path.insert(0, os.path.abspath('..'))
-sys.path.insert(0, os.path.abspath('../../pystencils'))
-sys.path.insert(0, os.path.abspath('../..'))
-from sphinx_doc_conf import *
+sys.path.insert(0, os.path.abspath('.'))
+from version_from_git import version_number_from_git
+
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.doctest',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.mathjax',
+    'sphinx.ext.napoleon',
+    'nbsphinx',
+    'sphinxcontrib.bibtex',
+    'sphinx_autodoc_typehints',
+]
+
+add_module_names = False
+templates_path = ['_templates']
+source_suffix = '.rst'
+master_doc = 'index'
+
+copyright = '{}, Martin Bauer'.format(datetime.datetime.now().year)
+author = 'Martin Bauer'
+version = version_number_from_git()
+release = version_number_from_git()
+language = None
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints']
+default_role = 'any'
+pygments_style = 'sphinx'
+todo_include_todos = False
+
+# Options for HTML output
+
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+html_theme = 'sphinx_rtd_theme'
+htmlhelp_basename = 'pystencilsdoc'
+html_sidebars = {'**': ['globaltoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html']}
+
+# NbSphinx configuration
+nbsphinx_execute = 'never'
+nbsphinx_codecell_lexer = 'python3'
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'python': ('https://docs.python.org/3.6', None),
+                       'numpy': ('https://docs.scipy.org/doc/numpy/', None),
+                       'matplotlib': ('https://matplotlib.org/', None),
+                       'sympy': ('https://docs.sympy.org/latest/', None),
+                       }
+
+autodoc_member_order = 'bysource'
 
 project = 'lbmpy'
 html_logo = "img/logo.png"
diff --git a/doc/img/logo.svg b/doc/img/logo.svg
index ee0a4c64dd0320a8e3bd425bef42de4eb10deffb..54e8e0e4e659eaf4ff47574cc09f86ba420d1543 100644
--- a/doc/img/logo.svg
+++ b/doc/img/logo.svg
@@ -480,6 +480,158 @@
        effect="spiro"
        id="path-effect4188-7"
        is_visible="true" />
+    <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:1.00000003pt;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" />
+    <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:1.00000003pt;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" />
+    <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:1.00000003pt;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" />
+    <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:1.00000003pt;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" />
+    <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:1.00000003pt;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" />
+    <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:1.00000003pt;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" />
+    <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:1.00000003pt;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" />
+    <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:1.00000003pt;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" />
   </defs>
   <sodipodi:namedview
      id="base"
@@ -488,16 +640,16 @@
      borderopacity="1.0"
      inkscape:pageopacity="0.0"
      inkscape:pageshadow="2"
-     inkscape:zoom="0.98994949"
-     inkscape:cx="159.4121"
-     inkscape:cy="-32.506835"
+     inkscape:zoom="1.4"
+     inkscape:cx="158.26067"
+     inkscape:cy="-4.9825309"
      inkscape:document-units="mm"
      inkscape:current-layer="layer1"
      showgrid="false"
-     inkscape:window-width="498"
-     inkscape:window-height="394"
-     inkscape:window-x="2210"
-     inkscape:window-y="646"
+     inkscape:window-width="1214"
+     inkscape:window-height="1052"
+     inkscape:window-x="1482"
+     inkscape:window-y="524"
      inkscape:window-maximized="0"
      fit-margin-top="0"
      fit-margin-left="0"
@@ -622,5 +774,87 @@
          d="m 36.797679,33.475 11.90625,13.229166"
          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-2-2)" />
     </g>
+    <rect
+       style="opacity:1;fill:#646ecb;fill-opacity:1;stroke:#d2d2d2;stroke-width:0.5091567;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect1396-7"
+       width="53.404633"
+       height="53.404633"
+       x="9.9747782"
+       y="82.509102"
+       ry="3.0735996"
+       inkscape:export-xdpi="188.45"
+       inkscape:export-ydpi="188.45" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#dddddd;stroke-width:1.22452438;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Send-8-6-9-0)"
+       d="M 36.299116,109.56879 H 17.132601"
+       id="path1402-8-0-4-2"
+       inkscape:connector-curvature="0"
+       inkscape:path-effect="#path-effect1404-7-7-5-3"
+       inkscape:original-d="m 36.299116,109.56879 c -6.171351,0.0481 -12.995819,-0.0487 -19.166515,0"
+       sodipodi:nodetypes="cc"
+       inkscape:export-xdpi="188.45"
+       inkscape:export-ydpi="188.45" />
+    <g
+       id="g9842-8"
+       inkscape:export-xdpi="188.45"
+       inkscape:export-ydpi="188.45"
+       transform="matrix(1.4488076,0,0,1.4488076,-17.013641,61.069964)">
+      <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-75"
+         inkscape:connector-curvature="0"
+         id="path1402-9"
+         d="M 36.797679,33.475 V 20.245833"
+         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-3)" />
+      <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-6"
+         inkscape:connector-curvature="0"
+         id="path1402-8-7"
+         d="M 36.797679,33.475 H 50.026846"
+         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-3)" />
+      <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-1"
+         inkscape:connector-curvature="0"
+         id="path1402-8-0-3"
+         d="M 36.797679,33.475 V 46.704166"
+         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-2)" />
+      <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-9"
+         inkscape:connector-curvature="0"
+         id="path1402-8-9-6"
+         d="M 36.797679,33.475 49.190857,20.246694"
+         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-2-27)" />
+      <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-6"
+         inkscape:connector-curvature="0"
+         id="path1402-8-9-4-1"
+         d="M 36.797679,33.475 23.568513,20.245833"
+         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-2-6-36)" />
+      <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-8"
+         inkscape:connector-curvature="0"
+         id="path1402-8-9-4-4-2"
+         d="M 36.797679,33.475 23.568513,46.704166"
+         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-2-6-3-2)" />
+      <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-2"
+         inkscape:connector-curvature="0"
+         id="path1402-8-9-3-9"
+         d="m 36.797679,33.475 11.90625,13.229166"
+         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-2-2-7)" />
+    </g>
   </g>
 </svg>
diff --git a/doc/img/logo_no_text.png b/doc/img/logo_no_text.png
new file mode 100644
index 0000000000000000000000000000000000000000..66896326cbaa7a5784178fc5845666aabfd9bc91
Binary files /dev/null and b/doc/img/logo_no_text.png differ
diff --git a/doc/version_from_git.py b/doc/version_from_git.py
new file mode 100644
index 0000000000000000000000000000000000000000..6392ad7732848485cf1cfd6aa919a208ce0a5b43
--- /dev/null
+++ b/doc/version_from_git.py
@@ -0,0 +1,31 @@
+import subprocess
+
+
+def version_number_from_git(tag_prefix='release/', sha_length=10, version_format="{version}.dev{commits}+{sha}"):
+    def get_released_versions():
+        tags = sorted(subprocess.getoutput('git tag').split('\n'))
+        versions = [t[len(tag_prefix):] for t in tags if t.startswith(tag_prefix)]
+        return versions
+
+    def tag_from_version(v):
+        return tag_prefix + v
+
+    def increment_version(v):
+        parsed_version = [int(i) for i in v.split('.')]
+        parsed_version[-1] += 1
+        return '.'.join(str(i) for i in parsed_version)
+
+    latest_release = get_released_versions()[-1]
+    commits_since_tag = subprocess.getoutput('git rev-list {}..HEAD --count'.format(tag_from_version(latest_release)))
+    sha = subprocess.getoutput('git rev-parse HEAD')[:sha_length]
+    is_dirty = len(subprocess.getoutput("git status --untracked-files=no -s")) > 0
+
+    if int(commits_since_tag) == 0:
+        version_string = latest_release
+    else:
+        next_version = increment_version(latest_release)
+        version_string = version_format.format(version=next_version, commits=commits_since_tag, sha=sha)
+
+    if is_dirty:
+        version_string += ".dirty"
+    return version_string
diff --git a/lbmpy_tests/test_chapman_enskog.py b/lbmpy_tests/test_chapman_enskog.py
index 47b2cb37e6fbd297f5888f276bee83a2a16c5937..8cabf4f2ba5488cb49103a10133fef52f74e0c1c 100644
--- a/lbmpy_tests/test_chapman_enskog.py
+++ b/lbmpy_tests/test_chapman_enskog.py
@@ -1,9 +1,7 @@
 import pytest
 import sympy as sp
 import functools
-from sympy.abc import a, b, x, y, z
-from pystencils.fd import expand_diff_products, combine_diff_products, Diff, \
-    expand_diff_linear, normalize_diff_order
+from pystencils.fd import Diff, normalize_diff_order
 from pystencils.sympyextensions import multidimensional_sum
 from lbmpy.chapman_enskog.chapman_enskog_higher_order import determine_higher_order_moments, get_solvability_conditions
 from lbmpy.chapman_enskog.chapman_enskog_steady_state import SteadyStateChapmanEnskogAnalysisSRT, \
@@ -15,29 +13,6 @@ from lbmpy.creationfunctions import create_lb_method
 from lbmpy.forcemodels import Guo
 
 
-def test_derivative_expand_collect():
-    original = Diff(x*y*z)
-    result = combine_diff_products(combine_diff_products(expand_diff_products(original))).expand()
-    assert original == result
-
-    original = -3 * y * z * Diff(x) + 2 * x * z * Diff(y)
-    result = expand_diff_products(combine_diff_products(original)).expand()
-    assert original == result
-
-    original = a + b * Diff(x ** 2 * y * z)
-    expanded = expand_diff_products(original)
-    collect_res = combine_diff_products(combine_diff_products(combine_diff_products(expanded)))
-    assert collect_res == original
-
-
-def test_diff_expand_using_linearity():
-    eps = sp.symbols("epsilon")
-    funcs = [a, b]
-    test = Diff(eps * Diff(a+b))
-    result = expand_diff_linear(test, functions=funcs)
-    assert result == eps * Diff(Diff(a)) + eps * Diff(Diff(b))
-
-
 def test_srt():
     for stencil in ['D2Q9', 'D3Q19', 'D3Q27']:
         for continuous_eq in (False, True):
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000000000000000000000000000000000000..2d69c7f60c6319ca21d470f78676aa8b17a7e44b
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,37 @@
+[pytest]
+python_files = test_*.py *_test.py scenario_*.py
+norecursedirs = *.egg-info .git .cache .ipynb_checkpoints htmlcov
+addopts = --doctest-modules --durations=20  --cov-config pytest.ini
+
+[run]
+branch = True
+source = lbmpy
+         lbmpy_tests
+
+omit = doc/*
+       lbmpy_tests/*
+       setup.py
+       conftest.py
+
+[report]
+exclude_lines =
+       # Have to re-enable the standard pragma
+       pragma: no cover
+
+       def __repr__
+
+       # Don't complain if tests don't hit defensive assertion code:
+       raise AssertionError
+       raise NotImplementedError
+       #raise ValueError
+
+       # Don't complain if non-runnable code isn't run:
+       if 0:
+       if False:
+       if __name__ == .__main__.:
+
+skip_covered = True
+fail_under = 90
+
+[html]
+directory = coverage_report
diff --git a/setup.py b/setup.py
index 8e4c3b010788f22efa6ab27842a80618d973accc..71278613b8c6123ec3055744e63e4b3ae50cd92a 100644
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,53 @@
 import os
 import sys
+import io
 from setuptools import setup, find_packages
-sys.path.insert(0, os.path.abspath('..'))
-from custom_pypi_index.pypi_index import get_current_dev_version_from_git
+import distutils
+from contextlib import redirect_stdout
+from importlib import import_module
+sys.path.insert(0, os.path.abspath('doc'))
+from version_from_git import version_number_from_git
+
+
+quick_tests = [
+    'test_serial_scenarios.test_ldc_mrt',
+    'test_force_on_boundary.test_force_on_boundary',
+]
+
+
+class SimpleTestRunner(distutils.cmd.Command):
+    """A custom command to run selected tests"""
+
+    description = 'run some quick tests'
+    user_options = []
+
+    @staticmethod
+    def _run_tests_in_module(test):
+        """Short test runner function - to work also if py.test is not installed."""
+        test = 'lbmpy_tests.' + test
+        mod, function_name = test.rsplit('.', 1)
+        if isinstance(mod, str):
+            mod = import_module(mod)
+
+        func = getattr(mod, function_name)
+        print("   -> %s in %s" % (function_name, mod.__name__))
+        with redirect_stdout(io.StringIO()):
+            func()
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        """Run command."""
+        for test in quick_tests:
+            self._run_tests_in_module(test)
 
 
 setup(name='lbmpy',
-      version=get_current_dev_version_from_git(),
+      version=version_number_from_git(),
       description='Code Generation for Lattice Boltzmann Methods',
       author='Martin Bauer',
       license='AGPLv3',
@@ -30,4 +71,7 @@ setup(name='lbmpy',
             'interactive': ['pystencils[interactive]', 'scipy', 'scikit-image', 'cython'],
             'doc': ['pystencils[doc]'],
       },
+      cmdclass={
+          'quicktest': SimpleTestRunner
+      }
       )