diff --git a/.flake8 b/.flake8
index f4cc20cdb9b463d730f1b33406c3ab41de5d367a..5701df707c82aa801b4a6bb80a98a56b42cb1e8c 100644
--- a/.flake8
+++ b/.flake8
@@ -3,4 +3,4 @@ max-line-length=120
 exclude=pystencils/jupyter.py,
         pystencils/plot.py
         pystencils/session.py
-ignore = W293 W503 W291 C901
+ignore = W293 W503 W291 C901 E741
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 25fd966970490ad8260b82ea35f137e0407562ba..9bf21bcfbc32fe15e0dc15aef4f2a70b7b489c7e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,6 +13,7 @@ tests-and-coverage:
       - $ENABLE_NIGHTLY_BUILDS
   image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
   script:
+    - env
     - pip list
     - export NUM_CORES=$(nproc --all)
     - mkdir -p ~/.config/matplotlib
diff --git a/conftest.py b/conftest.py
index d6b74818a5d90824dc09ab6faf802ec86e77c1da..eba25a7424432f630e75f642fcc7e755304ea08e 100644
--- a/conftest.py
+++ b/conftest.py
@@ -152,6 +152,7 @@ class IPyNbFile(pytest.File):
             notebook = nbformat.read(notebook_contents, 4)
             code, _ = exporter.from_notebook_node(notebook)
         yield IPyNbTest(self.name, self, code)
+        # pytest v 2.4>: yield IPyNbTest.from_parent(name=self.name, parent=self, code=code)
 
     def teardown(self):
         pass
@@ -161,3 +162,4 @@ 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)
+        # pytest v 2.4 >: return IPyNbFile.from_parent(fspath=path, parent=parent)
diff --git a/pystencils/assignment.py b/pystencils/assignment.py
index 147a829f4d6698dc67ffd9a50a7b043c34064f9c..d56d0f4d6d1421463cef4d9cc921d3d309e571cb 100644
--- a/pystencils/assignment.py
+++ b/pystencils/assignment.py
@@ -53,7 +53,7 @@ else:
             # Tuple of things that can be on the lhs of an assignment
             assignable = (sp.Symbol, MatrixSymbol, MatrixElement, sp.Indexed)
             if not isinstance(lhs, assignable):
-                raise TypeError("Cannot assign to lhs of type %s." % type(lhs))
+                raise TypeError(f"Cannot assign to lhs of type {type(lhs)}.")
             return sp.Rel.__new__(cls, lhs, rhs, **assumptions)
 
         __str__ = assignment_str
diff --git a/pystencils/astnodes.py b/pystencils/astnodes.py
index 7bce77e0dd99926d71ccbfc6bf2c5722e84c99f1..85c6033c513bb42646c76168eb13abc51f9b37ce 100644
--- a/pystencils/astnodes.py
+++ b/pystencils/astnodes.py
@@ -113,14 +113,14 @@ class Conditional(Node):
         return self.__repr__()
 
     def __repr__(self):
-        repr = 'if:({!r}) '.format(self.condition_expr)
+        result = f'if:({self.condition_expr!r}) '
         if self.true_block:
-            repr += '\n\t{}) '.format(self.true_block)
+            result += f'\n\t{self.true_block}) '
         if self.false_block:
-            repr = 'else: '.format(self.false_block)
-            repr += '\n\t{} '.format(self.false_block)
+            result = 'else: '
+            result += f'\n\t{self.false_block} '
 
-        return repr
+        return result
 
     def replace_by_true_block(self):
         """Replaces the conditional by its True block"""
@@ -264,7 +264,7 @@ class KernelFunction(Node):
 
     def __repr__(self):
         params = [p.symbol for p in self.get_parameters()]
-        return '{0} {1}({2})'.format(type(self).__name__, self.function_name, params)
+        return f'{type(self).__name__} {self.function_name}({params})'
 
     def compile(self, *args, **kwargs):
         if self._compile_function is None:
@@ -475,11 +475,11 @@ class LoopOverCoordinate(Node):
 
     @staticmethod
     def get_loop_counter_name(coordinate_to_loop_over):
-        return "%s_%s" % (LoopOverCoordinate.LOOP_COUNTER_NAME_PREFIX, coordinate_to_loop_over)
+        return f"{LoopOverCoordinate.LOOP_COUNTER_NAME_PREFIX}_{coordinate_to_loop_over}"
 
     @staticmethod
     def get_block_loop_counter_name(coordinate_to_loop_over):
-        return "%s_%s" % (LoopOverCoordinate.BlOCK_LOOP_COUNTER_NAME_PREFIX, coordinate_to_loop_over)
+        return f"{LoopOverCoordinate.BlOCK_LOOP_COUNTER_NAME_PREFIX}_{coordinate_to_loop_over}"
 
     @property
     def loop_counter_name(self):
@@ -612,7 +612,7 @@ class SympyAssignment(Node):
             replacement.parent = self
             self.rhs = replacement
         else:
-            raise ValueError('%s is not in args of %s' % (replacement, self.__class__))
+            raise ValueError(f'{replacement} is not in args of {self.__class__}')
 
     def __repr__(self):
         return repr(self.lhs) + " ← " + repr(self.rhs)
@@ -620,7 +620,7 @@ class SympyAssignment(Node):
     def _repr_html_(self):
         printed_lhs = sp.latex(self.lhs)
         printed_rhs = sp.latex(self.rhs)
-        return "${printed_lhs} \\leftarrow {printed_rhs}$".format(printed_lhs=printed_lhs, printed_rhs=printed_rhs)
+        return f"${printed_lhs} \\leftarrow {printed_rhs}$"
 
     def __hash__(self):
         return hash((self.lhs, self.rhs))
@@ -663,7 +663,7 @@ class ResolvedFieldAccess(sp.Indexed):
 
     def __str__(self):
         top = super(ResolvedFieldAccess, self).__str__()
-        return "%s (%s)" % (top, self.typed_symbol.dtype)
+        return f"{top} ({self.typed_symbol.dtype})"
 
     def __getnewargs__(self):
         return self.base, self.indices[0], self.field, self.offsets, self.idx_coordinate_values
@@ -740,7 +740,7 @@ def early_out(condition):
 
 
 def get_dummy_symbol(dtype='bool'):
-    return TypedSymbol('dummy%s' % uuid.uuid4().hex, create_type(dtype))
+    return TypedSymbol(f'dummy{uuid.uuid4().hex}', create_type(dtype))
 
 
 class SourceCodeComment(Node):
diff --git a/pystencils/backends/cbackend.py b/pystencils/backends/cbackend.py
index 46d9e3a77e6a163acec4fbfcf1ddf0f9a15f8712..bb25a6a4ca9e119886d3b553ab937b188bad1173 100644
--- a/pystencils/backends/cbackend.py
+++ b/pystencils/backends/cbackend.py
@@ -158,7 +158,7 @@ class CustomCodeNode(Node):
 class PrintNode(CustomCodeNode):
     # noinspection SpellCheckingInspection
     def __init__(self, symbol_to_print):
-        code = '\nstd::cout << "%s  =  " << %s << std::endl; \n' % (symbol_to_print.name, symbol_to_print.name)
+        code = f'\nstd::cout << "{symbol_to_print.name}  =  " << {symbol_to_print.name} << std::endl; \n'
         super(PrintNode, self).__init__(code, symbols_read=[symbol_to_print], symbols_defined=set())
         self.headers.append("<iostream>")
 
@@ -203,12 +203,12 @@ class CBackend:
         return str(node)
 
     def _print_KernelFunction(self, node):
-        function_arguments = ["%s %s" % (self._print(s.symbol.dtype), s.symbol.name) for s in node.get_parameters()]
+        function_arguments = [f"{self._print(s.symbol.dtype)} {s.symbol.name}" for s in node.get_parameters()]
         launch_bounds = ""
         if self._dialect == 'cuda':
             max_threads = node.indexing.max_threads_per_block()
             if max_threads:
-                launch_bounds = "__launch_bounds__({}) ".format(max_threads)
+                launch_bounds = f"__launch_bounds__({max_threads}) "
         func_declaration = "FUNC_PREFIX %svoid %s(%s)" % (launch_bounds, node.function_name,
                                                           ", ".join(function_arguments))
         if self._signatureOnly:
@@ -222,19 +222,19 @@ class CBackend:
         return "{\n%s\n}" % (self._indent + self._indent.join(block_contents.splitlines(True)))
 
     def _print_PragmaBlock(self, node):
-        return "%s\n%s" % (node.pragma_line, self._print_Block(node))
+        return f"{node.pragma_line}\n{self._print_Block(node)}"
 
     def _print_LoopOverCoordinate(self, node):
         counter_symbol = node.loop_counter_name
-        start = "int %s = %s" % (counter_symbol, self.sympy_printer.doprint(node.start))
-        condition = "%s < %s" % (counter_symbol, self.sympy_printer.doprint(node.stop))
-        update = "%s += %s" % (counter_symbol, self.sympy_printer.doprint(node.step),)
-        loop_str = "for (%s; %s; %s)" % (start, condition, update)
+        start = f"int {counter_symbol} = {self.sympy_printer.doprint(node.start)}"
+        condition = f"{counter_symbol} < {self.sympy_printer.doprint(node.stop)}"
+        update = f"{counter_symbol} += {self.sympy_printer.doprint(node.step)}"
+        loop_str = f"for ({start}; {condition}; {update})"
 
         prefix = "\n".join(node.prefix_lines)
         if prefix:
             prefix += "\n"
-        return "%s%s\n%s" % (prefix, loop_str, self._print(node.body))
+        return f"{prefix}{loop_str}\n{self._print(node.body)}"
 
     def _print_SympyAssignment(self, node):
         if node.is_declaration:
@@ -262,7 +262,7 @@ class CBackend:
                     instr = 'maskStore' if aligned else 'maskStoreU'
                     printed_mask = self.sympy_printer.doprint(mask)
                     if self._vector_instruction_set['dataTypePrefix']['double'] == '__mm256d':
-                        printed_mask = "_mm256_castpd_si256({})".format(printed_mask)
+                        printed_mask = f"_mm256_castpd_si256({printed_mask})"
 
                 rhs_type = get_type_of_expression(node.rhs)
                 if type(rhs_type) is not VectorType:
@@ -274,7 +274,7 @@ class CBackend:
                                                                   self.sympy_printer.doprint(rhs),
                                                                   printed_mask) + ';'
             else:
-                return "%s = %s;" % (self.sympy_printer.doprint(node.lhs), self.sympy_printer.doprint(node.rhs))
+                return f"{self.sympy_printer.doprint(node.lhs)} = {self.sympy_printer.doprint(node.rhs)};"
 
     def _print_TemporaryMemoryAllocation(self, node):
         align = 64
@@ -314,7 +314,7 @@ class CBackend:
             raise ValueError("Problem with Conditional inside vectorized loop - use vec_any or vec_all")
         condition_expr = self.sympy_printer.doprint(node.condition_expr)
         true_block = self._print_Block(node.true_block)
-        result = "if (%s)\n%s " % (condition_expr, true_block)
+        result = f"if ({condition_expr})\n{true_block} "
         if node.false_block:
             false_block = self._print_Block(node.false_block)
             result += "else " + false_block
@@ -343,7 +343,7 @@ class CustomSympyPrinter(CCodePrinter):
         if expr.exp.is_integer and expr.exp.is_number and 0 < expr.exp < 8:
             return "(" + self._print(sp.Mul(*[expr.base] * expr.exp, evaluate=False)) + ")"
         elif expr.exp.is_integer and expr.exp.is_number and - 8 < expr.exp < 0:
-            return "1 / ({})".format(self._print(sp.Mul(*[expr.base] * (-expr.exp), evaluate=False)))
+            return f"1 / ({self._print(sp.Mul(*([expr.base] * -expr.exp), evaluate=False))})"
         else:
             return super(CustomSympyPrinter, self)._print_Pow(expr)
 
@@ -362,10 +362,10 @@ class CustomSympyPrinter(CCodePrinter):
         return result.replace("\n", "")
 
     def _print_Abs(self, expr):
-        if expr.is_integer:
-            return 'abs({0})'.format(self._print(expr.args[0]))
+        if expr.args[0].is_integer:
+            return f'abs({self._print(expr.args[0])})'
         else:
-            return 'fabs({0})'.format(self._print(expr.args[0]))
+            return f'fabs({self._print(expr.args[0])})'
 
     def _print_Type(self, node):
         return str(node)
@@ -382,37 +382,37 @@ class CustomSympyPrinter(CCodePrinter):
             return expr.to_c(self._print)
         if isinstance(expr, reinterpret_cast_func):
             arg, data_type = expr.args
-            return "*((%s)(& %s))" % (self._print(PointerType(data_type, restrict=False)), self._print(arg))
+            return f"*(({self._print(PointerType(data_type, restrict=False))})(& {self._print(arg)}))"
         elif isinstance(expr, address_of):
             assert len(expr.args) == 1, "address_of must only have one argument"
-            return "&(%s)" % self._print(expr.args[0])
+            return f"&({self._print(expr.args[0])})"
         elif isinstance(expr, cast_func):
             arg, data_type = expr.args
             if isinstance(arg, sp.Number) and arg.is_finite:
                 return self._typed_number(arg, data_type)
             else:
-                return "((%s)(%s))" % (data_type, self._print(arg))
+                return f"(({data_type})({self._print(arg)}))"
         elif isinstance(expr, fast_division):
-            return "({})".format(self._print(expr.args[0] / expr.args[1]))
+            return f"({self._print(expr.args[0] / expr.args[1])})"
         elif isinstance(expr, fast_sqrt):
-            return "({})".format(self._print(sp.sqrt(expr.args[0])))
+            return f"({self._print(sp.sqrt(expr.args[0]))})"
         elif isinstance(expr, vec_any) or isinstance(expr, vec_all):
             return self._print(expr.args[0])
         elif isinstance(expr, fast_inv_sqrt):
-            return "({})".format(self._print(1 / sp.sqrt(expr.args[0])))
+            return f"({self._print(1 / sp.sqrt(expr.args[0]))})"
         elif isinstance(expr, sp.Abs):
-            return "abs({})".format(self._print(expr.args[0]))
+            return f"abs({self._print(expr.args[0])})"
         elif isinstance(expr, sp.Mod):
             if expr.args[0].is_integer and expr.args[1].is_integer:
-                return "({} % {})".format(self._print(expr.args[0]), self._print(expr.args[1]))
+                return f"({self._print(expr.args[0])} % {self._print(expr.args[1])})"
             else:
-                return "fmod({}, {})".format(self._print(expr.args[0]), self._print(expr.args[1]))
+                return f"fmod({self._print(expr.args[0])}, {self._print(expr.args[1])})"
         elif expr.func in infix_functions:
-            return "(%s %s %s)" % (self._print(expr.args[0]), infix_functions[expr.func], self._print(expr.args[1]))
+            return f"({self._print(expr.args[0])} {infix_functions[expr.func]} {self._print(expr.args[1])})"
         elif expr.func == int_power_of_2:
-            return "(1 << (%s))" % (self._print(expr.args[0]))
+            return f"(1 << ({self._print(expr.args[0])}))"
         elif expr.func == int_div:
-            return "((%s) / (%s))" % (self._print(expr.args[0]), self._print(expr.args[1]))
+            return f"(({self._print(expr.args[0])}) / ({self._print(expr.args[1])}))"
         else:
             name = expr.name if hasattr(expr, 'name') else expr.__class__.__name__
             arg_str = ', '.join(self._print(a) for a in expr.args)
@@ -540,14 +540,14 @@ class VectorizedCustomSympyPrinter(CustomSympyPrinter):
                 result = self.instruction_set['/'].format(self._print(expr.args[0]), self._print(expr.args[1]))
             return result
         elif expr.func == fast_sqrt:
-            return "({})".format(self._print(sp.sqrt(expr.args[0])))
+            return f"({self._print(sp.sqrt(expr.args[0]))})"
         elif expr.func == fast_inv_sqrt:
             result = self._scalarFallback('_print_Function', expr)
             if not result:
                 if self.instruction_set['rsqrt']:
                     return self.instruction_set['rsqrt'].format(self._print(expr.args[0]))
                 else:
-                    return "({})".format(self._print(1 / sp.sqrt(expr.args[0])))
+                    return f"({self._print(1 / sp.sqrt(expr.args[0]))})"
         elif isinstance(expr, vec_any):
             expr_type = get_type_of_expression(expr.args[0])
             if type(expr_type) is not VectorType:
diff --git a/pystencils/backends/cuda_backend.py b/pystencils/backends/cuda_backend.py
index d590d65b4082e72745658f0b06eb152d64872944..ef8f22ea2e24ef00bf6a9ff1c020f0dbae2a131f 100644
--- a/pystencils/backends/cuda_backend.py
+++ b/pystencils/backends/cuda_backend.py
@@ -98,7 +98,7 @@ class CudaSympyPrinter(CustomSympyPrinter):
         if isinstance(expr, fast_division):
             return "__fdividef(%s, %s)" % tuple(self._print(a) for a in expr.args)
         elif isinstance(expr, fast_sqrt):
-            return "__fsqrt_rn(%s)" % tuple(self._print(a) for a in expr.args)
+            return f"__fsqrt_rn({tuple(self._print(a) for a in expr.args)})"
         elif isinstance(expr, fast_inv_sqrt):
-            return "__frsqrt_rn(%s)" % tuple(self._print(a) for a in expr.args)
+            return f"__frsqrt_rn({tuple(self._print(a) for a in expr.args)})"
         return super()._print_Function(expr)
diff --git a/pystencils/backends/dot.py b/pystencils/backends/dot.py
index 64610c47af6148d6008780ba80530d678e518486..83ae1c705d8600b06b5d5d85c31f7151a3f9beb0 100644
--- a/pystencils/backends/dot.py
+++ b/pystencils/backends/dot.py
@@ -57,7 +57,7 @@ def __shortened(node):
         params = node.get_parameters()
         param_names = [p.field_name for p in params if p.is_field_pointer]
         param_names += [p.symbol.name for p in params if not p.is_field_parameter]
-        return "Func: %s (%s)" % (node.function_name, ",".join(param_names))
+        return f"Func: {node.function_name} ({','.join(param_names)})"
     elif isinstance(node, SympyAssignment):
         return repr(node.lhs)
     elif isinstance(node, Block):
@@ -65,7 +65,7 @@ def __shortened(node):
     elif isinstance(node, Conditional):
         return repr(node)
     else:
-        raise NotImplementedError("Cannot handle node type %s" % (type(node),))
+        raise NotImplementedError(f"Cannot handle node type {type(node)}")
 
 
 def print_dot(node, view=False, short=False, **kwargs):
diff --git a/pystencils/backends/opencl_backend.py b/pystencils/backends/opencl_backend.py
index 3ab7f820ea30adebf584977625b2e559f897ca27..1813d63ef6ce5f50c3fae276f6b3d00e8e5aff6c 100644
--- a/pystencils/backends/opencl_backend.py
+++ b/pystencils/backends/opencl_backend.py
@@ -98,7 +98,7 @@ class OpenClSympyPrinter(CudaSympyPrinter):
         if isinstance(expr, fast_division):
             return "native_divide(%s, %s)" % tuple(self._print(a) for a in expr.args)
         elif isinstance(expr, fast_sqrt):
-            return "native_sqrt(%s)" % tuple(self._print(a) for a in expr.args)
+            return f"native_sqrt({tuple(self._print(a) for a in expr.args)})"
         elif isinstance(expr, fast_inv_sqrt):
-            return "native_rsqrt(%s)" % tuple(self._print(a) for a in expr.args)
+            return f"native_rsqrt({tuple(self._print(a) for a in expr.args)})"
         return CustomSympyPrinter._print_Function(self, expr)
diff --git a/pystencils/backends/simd_instruction_sets.py b/pystencils/backends/simd_instruction_sets.py
index be8523e90913d7f62eb908ace8cbdf0189c31fbf..7e2be4dee3d1d6a0203c91dad91369ae1a9891a6 100644
--- a/pystencils/backends/simd_instruction_sets.py
+++ b/pystencils/backends/simd_instruction_sets.py
@@ -51,7 +51,7 @@ def get_vector_instruction_set(data_type='double', instruction_set='avx'):
         })
 
     for comparison_op, constant in comparisons.items():
-        base_names[comparison_op] = 'cmp[0, 1, %s]' % (constant,)
+        base_names[comparison_op] = f'cmp[0, 1, {constant}]'
 
     headers = {
         'avx512': ['<immintrin.h>'],
@@ -89,16 +89,16 @@ def get_vector_instruction_set(data_type='double', instruction_set='avx'):
         name = function_shortcut[:function_shortcut.index('[')]
 
         if intrinsic_id == 'makeVecConst':
-            arg_string = "({})".format(",".join(["{0}"] * result['width']))
+            arg_string = f"({','.join(['{0}'] * result['width'])})"
         elif intrinsic_id == 'makeVec':
             params = ["{" + str(i) + "}" for i in reversed(range(result['width']))]
-            arg_string = "({})".format(",".join(params))
+            arg_string = f"({','.join(params)})"
         elif intrinsic_id == 'makeVecBool':
-            params = ["(({{{i}}} ? -1.0 : 0.0)".format(i=i) for i in reversed(range(result['width']))]
-            arg_string = "({})".format(",".join(params))
+            params = [f"(({{{i}}} ? -1.0 : 0.0)" for i in reversed(range(result['width']))]
+            arg_string = f"({','.join(params)})"
         elif intrinsic_id == 'makeVecConstBool':
             params = ["(({0}) ? -1.0 : 0.0)" for _ in range(result['width'])]
-            arg_string = "({})".format(",".join(params))
+            arg_string = f"({','.join(params)})"
         else:
             args = function_shortcut[function_shortcut.index('[') + 1: -1]
             arg_string = "("
@@ -141,9 +141,9 @@ def get_vector_instruction_set(data_type='double', instruction_set='avx'):
         result['bool'] = "__mmask%d" % (size,)
 
         params = " | ".join(["({{{i}}} ? {power} : 0)".format(i=i, power=2 ** i) for i in range(8)])
-        result['makeVecBool'] = "__mmask8(({}) )".format(params)
+        result['makeVecBool'] = f"__mmask8(({params}) )"
         params = " | ".join(["({{0}} ? {power} : 0)".format(power=2 ** i) for i in range(8)])
-        result['makeVecConstBool'] = "__mmask8(({}) )".format(params)
+        result['makeVecConstBool'] = f"__mmask8(({params}) )"
 
     if instruction_set == 'avx' and data_type == 'float':
         result['rsqrt'] = "_mm256_rsqrt_ps({0})"
diff --git a/pystencils/boundaries/boundaryhandling.py b/pystencils/boundaries/boundaryhandling.py
index e164006350582bb1b05aa30c3570f07b53ae52e4..b80d3c72f707fdb75cc6c777729f8334772a8446 100644
--- a/pystencils/boundaries/boundaryhandling.py
+++ b/pystencils/boundaries/boundaryhandling.py
@@ -66,13 +66,13 @@ class FlagInterface:
                 self._used_flags.add(flag)
                 assert self._is_power_of_2(flag)
                 return flag
-        raise ValueError("All available {} flags are reserved".format(self.max_bits))
+        raise ValueError(f"All available {self.max_bits} flags are reserved")
 
     def reserve_flag(self, flag):
         assert self._is_power_of_2(flag)
         flag = self.dtype(flag)
         if flag in self._used_flags:
-            raise ValueError("The flag {flag} is already reserved".format(flag=flag))
+            raise ValueError(f"The flag {flag} is already reserved")
         self._used_flags.add(flag)
         return flag
 
@@ -392,12 +392,12 @@ class BoundaryDataSetter:
 
     def __setitem__(self, key, value):
         if key not in self.boundary_data_names:
-            raise KeyError("Invalid boundary data name %s. Allowed are %s" % (key, self.boundary_data_names))
+            raise KeyError(f"Invalid boundary data name {key}. Allowed are {self.boundary_data_names}")
         self.index_array[key] = value
 
     def __getitem__(self, item):
         if item not in self.boundary_data_names:
-            raise KeyError("Invalid boundary data name %s. Allowed are %s" % (item, self.boundary_data_names))
+            raise KeyError(f"Invalid boundary data name {item}. Allowed are {self.boundary_data_names}")
         return self.index_array[item]
 
 
@@ -437,7 +437,7 @@ class BoundaryOffsetInfo(CustomCodeNode):
 
     @staticmethod
     def _offset_symbols(dim):
-        return [TypedSymbol("c%s" % (d,), create_type(np.int64)) for d in ['x', 'y', 'z'][:dim]]
+        return [TypedSymbol(f"c{d}", create_type(np.int64)) for d in ['x', 'y', 'z'][:dim]]
 
     INV_DIR_SYMBOL = TypedSymbol("invdir", "int")
 
diff --git a/pystencils/cpu/cpujit.py b/pystencils/cpu/cpujit.py
index 07a8a84d9c6d10ed8580a7ef83d6720b522cfd98..5b011535826fbb364595f75ea25f2922980ad6c0 100644
--- a/pystencils/cpu/cpujit.py
+++ b/pystencils/cpu/cpujit.py
@@ -362,7 +362,7 @@ def create_function_boilerplate_code(parameter_info, name, insert_checks=True):
             field = param.fields[0]
             pre_call_code += template_extract_array.format(name=field.name)
             post_call_code += template_release_buffer.format(name=field.name)
-            parameters.append("({dtype} *)buffer_{name}.buf".format(dtype=str(field.dtype), name=field.name))
+            parameters.append(f"({str(field.dtype)} *)buffer_{field.name}.buf")
 
             if insert_checks:
                 np_dtype = field.dtype.numpy_dtype
@@ -375,12 +375,12 @@ def create_function_boilerplate_code(parameter_info, name, insert_checks=True):
                     pre_call_code += template_check_array.format(cond=dtype_cond, what="data type", name=field.name,
                                                                  expected=str(field.dtype.numpy_dtype))
 
-                item_size_cond = "buffer_{name}.itemsize == {size}".format(name=field.name, size=item_size)
+                item_size_cond = f"buffer_{field.name}.itemsize == {item_size}"
                 pre_call_code += template_check_array.format(cond=item_size_cond, what="itemsize", name=field.name,
                                                              expected=item_size)
 
                 if field.has_fixed_shape:
-                    shape_cond = ["buffer_{name}.shape[{i}] == {s}".format(s=s, name=field.name, i=i)
+                    shape_cond = [f"buffer_{field.name}.shape[{i}] == {s}"
                                   for i, s in enumerate(field.spatial_shape)]
                     shape_cond = " && ".join(shape_cond)
                     pre_call_code += template_check_array.format(cond=shape_cond, what="shape", name=field.name,
@@ -403,7 +403,7 @@ def create_function_boilerplate_code(parameter_info, name, insert_checks=True):
             parameters.append("buffer_{name}.strides[{i}] / {bytes}".format(bytes=item_size, i=param.symbol.coordinate,
                                                                             name=field.name))
         elif param.is_field_shape:
-            parameters.append("buffer_{name}.shape[{i}]".format(i=param.symbol.coordinate, name=param.field_name))
+            parameters.append(f"buffer_{param.field_name}.shape[{param.symbol.coordinate}]")
         else:
             extract_function, target_type = type_mapping[param.symbol.dtype.numpy_dtype.type]
             if np.issubdtype(param.symbol.dtype.numpy_dtype, np.complexfloating):
@@ -490,8 +490,8 @@ class ExtensionModuleCode:
         includes = "\n".join(["#include %s" % (include_file,) for include_file in header_list])
         print(includes, file=file)
         print("\n", file=file)
-        print("#define RESTRICT %s" % (restrict_qualifier,), file=file)
-        print("#define FUNC_PREFIX %s" % (function_prefix,), file=file)
+        print(f"#define RESTRICT {restrict_qualifier}", file=file)
+        print(f"#define FUNC_PREFIX {function_prefix}", file=file)
         print("\n", file=file)
 
         for ast, name in zip(self._ast_nodes, self._function_names):
@@ -541,7 +541,7 @@ def compile_module(code, code_hash, base_dir):
             import sysconfig
             config_vars = sysconfig.get_config_vars()
             py_lib = os.path.join(config_vars["installed_base"], "libs",
-                                  "python{}.lib".format(config_vars["py_version_nodot"]))
+                                  f"python{config_vars['py_version_nodot']}.lib")
             run_compile_step(['link.exe', py_lib, '/DLL', '/out:' + lib_file, object_file])
         elif platform.system().lower() == 'darwin':
             with atomic_file_write(lib_file) as file_name:
diff --git a/pystencils/cpu/kernelcreation.py b/pystencils/cpu/kernelcreation.py
index 38ce169af754fae27d80cadfa993ac83a03e0999..457944db6d5a6a4d18a6e0be48f78ef42876c757 100644
--- a/pystencils/cpu/kernelcreation.py
+++ b/pystencils/cpu/kernelcreation.py
@@ -129,7 +129,7 @@ def create_indexed_kernel(assignments: AssignmentOrAstNodeList, index_fields, fu
                 rhs = idx_field[0](name)
                 lhs = TypedSymbol(name, BasicType(data_type.get_element_type(name)))
                 return SympyAssignment(lhs, rhs)
-        raise ValueError("Index %s not found in any of the passed index fields" % (name,))
+        raise ValueError(f"Index {name} not found in any of the passed index fields")
 
     coordinate_symbol_assignments = [get_coordinate_symbol_assignment(n)
                                      for n in coordinate_names[:spatial_coordinates]]
@@ -173,7 +173,7 @@ def add_openmp(ast_node, schedule="static", num_threads=True, collapse=None, ass
 
     assert type(ast_node) is ast.KernelFunction
     body = ast_node.body
-    threads_clause = "" if num_threads and isinstance(num_threads, bool) else " num_threads(%s)" % (num_threads,)
+    threads_clause = "" if num_threads and isinstance(num_threads, bool) else f" num_threads({num_threads})"
     wrapper_block = ast.PragmaBlock('#pragma omp parallel' + threads_clause, body.take_child_nodes())
     body.append(wrapper_block)
 
@@ -204,7 +204,7 @@ def add_openmp(ast_node, schedule="static", num_threads=True, collapse=None, ass
                 except TypeError:
                     pass
 
-        prefix = "#pragma omp for schedule(%s)" % (schedule,)
+        prefix = f"#pragma omp for schedule({schedule})"
         if collapse:
             prefix += " collapse(%d)" % (collapse, )
         loop_to_parallelize.prefix_lines.append(prefix)
diff --git a/pystencils/cpu/msvc_detection.py b/pystencils/cpu/msvc_detection.py
index 94b2bcf5bc978c70c083ab3c2d0a6a6e0b2aea62..9cc1fc5ad64a9e50d24788f6e4eea8135cbf7a2c 100644
--- a/pystencils/cpu/msvc_detection.py
+++ b/pystencils/cpu/msvc_detection.py
@@ -71,7 +71,7 @@ def normalize_msvc_version(version):
 
 def get_environment_from_vc_vars_file(vc_vars_file, arch):
     out = subprocess.check_output(
-        'cmd /u /c "{}" {} && set'.format(vc_vars_file, arch),
+        f'cmd /u /c "{vc_vars_file}" {arch} && set',
         stderr=subprocess.STDOUT,
     ).decode('utf-16le', errors='replace')
 
diff --git a/pystencils/cpu/vectorization.py b/pystencils/cpu/vectorization.py
index 12b492cf452e27220b3013ac6339aeaf6073c051..0ee5200a069bdc00e1bb751ba5fb914b0707a62b 100644
--- a/pystencils/cpu/vectorization.py
+++ b/pystencils/cpu/vectorization.py
@@ -115,7 +115,7 @@ def vectorize_inner_loops_and_adapt_load_stores(ast_node, vector_width, assume_a
                     break
                 typed_symbol = base.label
                 assert type(typed_symbol.dtype) is PointerType, \
-                    "Type of access is {}, {}".format(typed_symbol.dtype, indexed)
+                    f"Type of access is {typed_symbol.dtype}, {indexed}"
 
                 vec_type = VectorType(typed_symbol.dtype.base_type, vector_width)
                 use_aligned_access = aligned_access and assume_aligned
diff --git a/pystencils/data_types.py b/pystencils/data_types.py
index 786351f528573179543900bde760f675ddfe10a6..365ef8aa787733d61b4e8b5367eda1fe6daac51d 100644
--- a/pystencils/data_types.py
+++ b/pystencils/data_types.py
@@ -396,7 +396,7 @@ def ctypes_from_llvm(data_type):
     elif isinstance(data_type, ir.VoidType):
         return None  # Void type is not supported by ctypes
     else:
-        raise NotImplementedError('Data type %s of %s is not supported yet' % (type(data_type), data_type))
+        raise NotImplementedError(f'Data type {type(data_type)} of {data_type} is not supported yet')
 
 
 def to_llvm_type(data_type, nvvm_target=False):
@@ -603,7 +603,7 @@ class BasicType(Type):
         elif name == 'bool':
             return 'bool'
         else:
-            raise NotImplementedError("Can map numpy to C name for %s" % (name,))
+            raise NotImplementedError(f"Can map numpy to C name for {name}")
 
     def __init__(self, dtype, const=False):
         self.const = const
diff --git a/pystencils/datahandling/parallel_datahandling.py b/pystencils/datahandling/parallel_datahandling.py
index 535933300f73977d3ede92e714f2a381be92fa50..4d3fcdf7a68aeaaebbdf5fd2618432b9c0d455cc 100644
--- a/pystencils/datahandling/parallel_datahandling.py
+++ b/pystencils/datahandling/parallel_datahandling.py
@@ -383,7 +383,7 @@ class ParallelDataHandling(DataHandling):
         if not os.path.exists(directory):
             os.mkdir(directory)
         if os.path.isfile(directory):
-            raise RuntimeError("Trying to save to {}, but file exists already".format(directory))
+            raise RuntimeError(f"Trying to save to {directory}, but file exists already")
 
         for field_name, data_name in self._field_name_to_cpu_data_name.items():
             self.blocks.writeBlockData(data_name, os.path.join(directory, field_name + ".dat"))
diff --git a/pystencils/datahandling/serial_datahandling.py b/pystencils/datahandling/serial_datahandling.py
index d4af43f189333a260b9a266b83c21335cd4a1c44..e5244c05a166306347a9b5e948145305f9a870bc 100644
--- a/pystencils/datahandling/serial_datahandling.py
+++ b/pystencils/datahandling/serial_datahandling.py
@@ -407,7 +407,7 @@ class SerialDataHandling(DataHandling):
 
         time_running = time.perf_counter() - self._start_time
         spacing = 7 - len(str(int(time_running)))
-        message = "[{: <8}]{}({:.3f} sec) {} ".format(level, spacing * '-', time_running, message)
+        message = f"[{level: <8}]{spacing * '-'}({time_running:.3f} sec) {message} "
         print(message, flush=True)
 
     def log_on_root(self, *args, level='INFO'):
@@ -428,7 +428,7 @@ class SerialDataHandling(DataHandling):
         file_contents = np.load(file)
         for arr_name, arr_contents in self.cpu_arrays.items():
             if arr_name not in file_contents:
-                print("Skipping read data {} because there is no data with this name in data handling".format(arr_name))
+                print(f"Skipping read data {arr_name} because there is no data with this name in data handling")
                 continue
             if file_contents[arr_name].shape != arr_contents.shape:
                 print("Skipping read data {} because shapes don't match. "
diff --git a/pystencils/display_utils.py b/pystencils/display_utils.py
index cb09197bf175b43c4896072693ea11253976691f..32531cdc5e2bba37b46e5708859ed60e8150dea5 100644
--- a/pystencils/display_utils.py
+++ b/pystencils/display_utils.py
@@ -30,7 +30,7 @@ def highlight_cpp(code: str):
     from pygments.lexers import CppLexer
 
     css = HtmlFormatter().get_style_defs('.highlight')
-    css_tag = "<style>{css}</style>".format(css=css)
+    css_tag = f"<style>{css}</style>"
     display(HTML(css_tag))
     return HTML(highlight(code, CppLexer(), HtmlFormatter()))
 
diff --git a/pystencils/fd/derivation.py b/pystencils/fd/derivation.py
index e0e7a65ad31236f869ef287344ae52f3e3a7c528..23579f488716c97e98fe7f066266ad2f648ff458 100644
--- a/pystencils/fd/derivation.py
+++ b/pystencils/fd/derivation.py
@@ -107,7 +107,7 @@ class FiniteDifferenceStencilDerivation:
     @staticmethod
     def symbolic_weight(*args):
         str_args = [str(e) for e in args]
-        return sp.Symbol("w_({})".format(",".join(str_args)))
+        return sp.Symbol(f"w_({','.join(str_args)})")
 
     def error_term_dict(self, order):
         error_terms = defaultdict(lambda: 0)
diff --git a/pystencils/fd/derivative.py b/pystencils/fd/derivative.py
index 31daf4294f4ffa70c766632c4126dd7403690251..0e2890ec558db92a364c6912b19b26cb676f7217 100644
--- a/pystencils/fd/derivative.py
+++ b/pystencils/fd/derivative.py
@@ -109,7 +109,7 @@ class Diff(sp.Expr):
         return result
 
     def __str__(self):
-        return "D(%s)" % self.arg
+        return f"D({self.arg})"
 
     def interpolated_access(self, offset, **kwargs):
         """Represents an interpolated access on a spatially differentiated field
diff --git a/pystencils/fd/finitedifferences.py b/pystencils/fd/finitedifferences.py
index 317e8e02537159e20a2c9ea75bc7cc308e1cb69e..1a119f71b5060831e1597f1290e20f149c4c6f39 100644
--- a/pystencils/fd/finitedifferences.py
+++ b/pystencils/fd/finitedifferences.py
@@ -193,7 +193,7 @@ class Advection(sp.Function):
         return self.scalar.spatial_dimensions
 
     def _latex(self, printer):
-        name_suffix = "_%s" % self.scalar_index if self.scalar_index is not None else ""
+        name_suffix = f"_{self.scalar_index}" if self.scalar_index is not None else ""
         if isinstance(self.vector, Field):
             return r"\nabla \cdot(%s %s)" % (printer.doprint(sp.Symbol(self.vector.name)),
                                              printer.doprint(sp.Symbol(self.scalar.name + name_suffix)))
@@ -240,7 +240,7 @@ class Diffusion(sp.Function):
         return self.scalar.spatial_dimensions
 
     def _latex(self, printer):
-        name_suffix = "_%s" % self.scalar_index if self.scalar_index is not None else ""
+        name_suffix = f"_{self.scalar_index}" if self.scalar_index is not None else ""
         coeff = self.diffusion_coeff
         diff_coeff = sp.Symbol(coeff.name) if isinstance(coeff, Field) else coeff
         return r"div(%s \nabla %s)" % (printer.doprint(diff_coeff),
@@ -273,7 +273,7 @@ class Transient(sp.Function):
         return None if len(self.args) <= 1 else int(self.args[1])
 
     def _latex(self, printer):
-        name_suffix = "_%s" % self.scalar_index if self.scalar_index is not None else ""
+        name_suffix = f"_{self.scalar_index}" if self.scalar_index is not None else ""
         return r"\partial_t %s" % (printer.doprint(sp.Symbol(self.scalar.name + name_suffix)),)
 
 
diff --git a/pystencils/field.py b/pystencils/field.py
index 6b85dead11778bf5aa6a820d4f34e8b547a95df4..9fe59d1f4528d446bf5574e69be5fb787e5bd797 100644
--- a/pystencils/field.py
+++ b/pystencils/field.py
@@ -583,7 +583,7 @@ class Field(AbstractField):
             }
         }
         if not self.index_shape[0] in stencils[self.spatial_dimensions]:
-            raise ValueError("No known stencil has {} staggered points".format(self.index_shape[0]))
+            raise ValueError(f"No known stencil has {self.index_shape[0]} staggered points")
         return stencils[self.spatial_dimensions][self.index_shape[0]]
 
     @property
@@ -706,7 +706,7 @@ class Field(AbstractField):
                 offset_name = hashlib.md5(pickle.dumps(offsets_and_index)).hexdigest()[:12]
                 superscript = None
 
-            symbol_name = "%s_%s" % (field_name, offset_name)
+            symbol_name = f"{field_name}_{offset_name}"
             if superscript is not None:
                 symbol_name += "^" + superscript
 
@@ -871,9 +871,9 @@ class Field(AbstractField):
                 offset_str = ",".join([sp.latex(self._staggered_offset(self.offsets, self.index[0])[i])
                                        for i in range(len(self.offsets))])
             if self.is_absolute_access:
-                offset_str = "\\mathbf{}".format(offset_str)
+                offset_str = f"\\mathbf{offset_str}"
             elif self.field.spatial_dimensions > 1:
-                offset_str = "({})".format(offset_str)
+                offset_str = f"({offset_str})"
 
             if FieldType.is_staggered(self._field):
                 if self.index and self.field.index_dimensions > 1:
@@ -894,18 +894,18 @@ class Field(AbstractField):
                 offset_str = ",".join([sp.latex(self._staggered_offset(self.offsets, self.index[0])[i])
                                        for i in range(len(self.offsets))])
             if self.is_absolute_access:
-                offset_str = "[abs]{}".format(offset_str)
+                offset_str = f"[abs]{offset_str}"
 
             if FieldType.is_staggered(self._field):
                 if self.index and self.field.index_dimensions > 1:
-                    return "%s[%s](%s)" % (n, offset_str, self.index[1:] if len(self.index) > 2 else self.index[1])
+                    return f"{n}[{offset_str}]({self.index[1:] if len(self.index) > 2 else self.index[1]})"
                 else:
-                    return "%s[%s]" % (n, offset_str)
+                    return f"{n}[{offset_str}]"
             else:
                 if self.index and self.field.index_dimensions > 0:
-                    return "%s[%s](%s)" % (n, offset_str, self.index if len(self.index) > 1 else self.index[0])
+                    return f"{n}[{offset_str}]({self.index if len(self.index) > 1 else self.index[0]})"
                 else:
-                    return "%s[%s]" % (n, offset_str)
+                    return f"{n}[{offset_str}]"
 
 
 def get_layout_from_strides(strides: Sequence[int], index_dimension_ids: Optional[List[int]] = None):
diff --git a/pystencils/gpucuda/indexing.py b/pystencils/gpucuda/indexing.py
index eb212119ac8505ac5bf4db3112ef645b8e793f80..ae5db1b984d1ddc6f33ec0437b3f9fdc44ea48a4 100644
--- a/pystencils/gpucuda/indexing.py
+++ b/pystencils/gpucuda/indexing.py
@@ -305,7 +305,7 @@ def indexing_creator_from_params(gpu_indexing, gpu_indexing_params):
         elif gpu_indexing == 'line':
             indexing_creator = LineIndexing
         else:
-            raise ValueError("Unknown GPU indexing %s. Valid values are 'block' and 'line'" % (gpu_indexing,))
+            raise ValueError(f"Unknown GPU indexing {gpu_indexing}. Valid values are 'block' and 'line'")
         if gpu_indexing_params:
             indexing_creator = partial(indexing_creator, **gpu_indexing_params)
         return indexing_creator
diff --git a/pystencils/gpucuda/kernelcreation.py b/pystencils/gpucuda/kernelcreation.py
index 33db3ad56da5d5e26f43403cf354c3814b2805e9..52a4dc8bd90e6caa8dd462b0ced27ce33e6c1ae2 100644
--- a/pystencils/gpucuda/kernelcreation.py
+++ b/pystencils/gpucuda/kernelcreation.py
@@ -131,7 +131,7 @@ def created_indexed_cuda_kernel(assignments,
                 rhs = ind_f[0](name)
                 lhs = TypedSymbol(name, BasicType(data_type.get_element_type(name)))
                 return SympyAssignment(lhs, rhs)
-        raise ValueError("Index %s not found in any of the passed index fields" % (name,))
+        raise ValueError(f"Index {name} not found in any of the passed index fields")
 
     coordinate_symbol_assignments = [get_coordinate_symbol_assignment(n)
                                      for n in coordinate_names[:spatial_coordinates]]
diff --git a/pystencils/integer_set_analysis.py b/pystencils/integer_set_analysis.py
index 3560ba6ca1ad3f10ba456564aa74669273ef8dae..82af791caf805877089ba957afcff517669f4b6b 100644
--- a/pystencils/integer_set_analysis.py
+++ b/pystencils/integer_set_analysis.py
@@ -36,13 +36,12 @@ def isl_iteration_set(node: ast.Node):
         loop_start_str = remove_brackets(str(loop.start))
         loop_stop_str = remove_brackets(str(loop.stop))
         ctr_name = loop.loop_counter_name
-        set_string_description = "{} >= {} and {} < {}".format(ctr_name, loop_start_str, ctr_name, loop_stop_str)
+        set_string_description = f"{ctr_name} >= {loop_start_str} and {ctr_name} < {loop_stop_str}"
         conditions.append(remove_brackets(set_string_description))
 
     symbol_names = ','.join(degrees_of_freedom)
     condition_str = ' and '.join(conditions)
-    set_description = "{{ [{symbol_names}] : {condition_str} }}".format(symbol_names=symbol_names,
-                                                                        condition_str=condition_str)
+    set_description = f"{{ [{symbol_names}] : {condition_str} }}"
     return degrees_of_freedom, isl.BasicSet(set_description)
 
 
@@ -53,8 +52,7 @@ def simplify_loop_counter_dependent_conditional(conditional):
     if dofs_in_condition.issubset(dofs_in_loops):
         symbol_names = ','.join(dofs_in_loops)
         condition_str = remove_brackets(str(conditional.condition_expr))
-        condition_set = isl.BasicSet("{{ [{symbol_names}] : {condition_str} }}".format(symbol_names=symbol_names,
-                                                                                       condition_str=condition_str))
+        condition_set = isl.BasicSet(f"{{ [{symbol_names}] : {condition_str} }}")
 
         if condition_set.is_empty():
             conditional.replace_by_false_block()
diff --git a/pystencils/interpolation_astnodes.py b/pystencils/interpolation_astnodes.py
index 76bd340b7e2c9bd66d9c82f77bc0399208017e52..c230d0115bf858213e3071986abb70ed86d23f96 100644
--- a/pystencils/interpolation_astnodes.py
+++ b/pystencils/interpolation_astnodes.py
@@ -126,7 +126,7 @@ class Interpolator(object):
         return InterpolatorAccess(self.symbol, *[sp.S(o) for o in offset])
 
     def __str__(self):
-        return '%s_interpolator_%s' % (self.field.name, self.reproducible_hash)
+        return f'{self.field.name}_interpolator_{self.reproducible_hash}'
 
     def __repr__(self):
         return self.__str__()
@@ -186,7 +186,7 @@ class InterpolatorAccess(TypedSymbol):
         return super()._hashable_content() + ((self.symbol, self.field, tuple(self.offsets), self.symbol.interpolator))
 
     def __str__(self):
-        return '%s_interpolator(%s)' % (self.field.name, ', '.join(str(o) for o in self.offsets))
+        return f"{self.field.name}_interpolator({', '.join(str(o) for o in self.offsets)})"
 
     def __repr__(self):
         return self.__str__()
@@ -437,7 +437,7 @@ class TextureCachedField(Interpolator):
         return obj
 
     def __str__(self):
-        return '%s_texture_%s' % (self.field.name, self.reproducible_hash)
+        return f'{self.field.name}_texture_{self.reproducible_hash}'
 
     def __repr__(self):
         return self.__str__()
diff --git a/pystencils/jupyter.py b/pystencils/jupyter.py
index f977e87c0b9c1b835eccf6670c4e4ec63eccd086..5e86e0c2cae812a69fd8a38c59639cbbadeaf798 100644
--- a/pystencils/jupyter.py
+++ b/pystencils/jupyter.py
@@ -44,17 +44,10 @@ def log_progress(sequence, every=None, size=None, name='Items'):
         for index, record in enumerate(sequence, 1):
             if index == 1 or index % every == 0:
                 if is_iterator:
-                    label.value = '{name}: {index} / ?'.format(
-                        name=name,
-                        index=index
-                    )
+                    label.value = f'{name}: {index} / ?'
                 else:
                     progress.value = index
-                    label.value = u'{name}: {index} / {size}'.format(
-                        name=name,
-                        index=index,
-                        size=size
-                    )
+                    label.value = f'{name}: {index} / {size}'
             yield record
     except:
         progress.bar_style = 'danger'
@@ -62,10 +55,7 @@ def log_progress(sequence, every=None, size=None, name='Items'):
     else:
         progress.bar_style = 'success'
         progress.value = index
-        label.value = "{name}: {index}".format(
-            name=name,
-            index=str(index or '?')
-        )
+        label.value = f"{name}: {str(index or '?')}"
 
 
 VIDEO_TAG = """<video controls width="80%">
diff --git a/pystencils/kerncraft_coupling/kerncraft_interface.py b/pystencils/kerncraft_coupling/kerncraft_interface.py
index 37a5109de80b0c975a5cb30043316e1c1e8e9e9e..bd1771493434d1166bf899cc8a8188994bbd2101 100644
--- a/pystencils/kerncraft_coupling/kerncraft_interface.py
+++ b/pystencils/kerncraft_coupling/kerncraft_interface.py
@@ -140,7 +140,7 @@ class PyStencilsKerncraftKernel(KernelCode):
         """
         code = generate_benchmark(self.kernel_ast, likwid=type_ == 'likwid', openmp=openmp)
         if as_filename:
-            fp, already_available = self._get_intermediate_file('kernel_{}.c'.format(type_),
+            fp, already_available = self._get_intermediate_file(f'kernel_{type_}.c',
                                                                 machine_and_compiler_dependent=False)
             if not already_available:
                 fp.write(code)
diff --git a/pystencils/kernelcreation.py b/pystencils/kernelcreation.py
index 2c8aabaa530f985f7b645a41a29a9537a655fa07..b158754c8716a2d0bdae495ceda59341f945c8d8 100644
--- a/pystencils/kernelcreation.py
+++ b/pystencils/kernelcreation.py
@@ -128,7 +128,7 @@ def create_kernel(assignments,
             ast._backend = 'opencl'
         return ast
     else:
-        raise ValueError("Unknown target %s. Has to be one of 'cpu', 'gpu' or 'llvm' " % (target,))
+        raise ValueError(f"Unknown target {target}. Has to be one of 'cpu', 'gpu' or 'llvm' ")
 
     if use_auto_for_assignments:
         for a in ast.atoms(SympyAssignment):
@@ -214,7 +214,7 @@ def create_indexed_kernel(assignments,
             ast._backend = 'opencl'
         return ast
     else:
-        raise ValueError("Unknown target %s. Has to be either 'cpu' or 'gpu'" % (target,))
+        raise ValueError(f"Unknown target {target}. Has to be either 'cpu' or 'gpu'")
 
 
 def create_staggered_kernel(assignments, target='cpu', gpu_exclusive_conditions=False, **kwargs):
diff --git a/pystencils/kernelparameters.py b/pystencils/kernelparameters.py
index 9284a1ee5d14dd97c63f44574e020f1bff24d4e5..3257522e419bf921b13010215e44a51a5290ce80 100644
--- a/pystencils/kernelparameters.py
+++ b/pystencils/kernelparameters.py
@@ -29,7 +29,7 @@ class FieldStrideSymbol(TypedSymbol):
         return obj
 
     def __new_stage2__(cls, field_name, coordinate):
-        name = "_stride_{name}_{i}".format(name=field_name, i=coordinate)
+        name = f"_stride_{field_name}_{coordinate}"
         obj = super(FieldStrideSymbol, cls).__xnew__(cls, name, STRIDE_DTYPE, positive=True)
         obj.field_name = field_name
         obj.coordinate = coordinate
@@ -54,7 +54,7 @@ class FieldShapeSymbol(TypedSymbol):
 
     def __new_stage2__(cls, field_names, coordinate):
         names = "_".join([field_name for field_name in field_names])
-        name = "_size_{names}_{i}".format(names=names, i=coordinate)
+        name = f"_size_{names}_{coordinate}"
         obj = super(FieldShapeSymbol, cls).__xnew__(cls, name, SHAPE_DTYPE, positive=True)
         obj.field_names = tuple(field_names)
         obj.coordinate = coordinate
@@ -77,7 +77,7 @@ class FieldPointerSymbol(TypedSymbol):
         return obj
 
     def __new_stage2__(cls, field_name, field_dtype, const):
-        name = "_data_{name}".format(name=field_name)
+        name = f"_data_{field_name}"
         dtype = PointerType(get_base_type(field_dtype), const=const, restrict=True)
         obj = super(FieldPointerSymbol, cls).__xnew__(cls, name, dtype)
         obj.field_name = field_name
diff --git a/pystencils/llvm/llvm.py b/pystencils/llvm/llvm.py
index 1d5223e9509f274e002d755fa19a1a56f9ec016e..a0de12689d620abefbd35e168bdac61f44544462 100644
--- a/pystencils/llvm/llvm.py
+++ b/pystencils/llvm/llvm.py
@@ -97,7 +97,7 @@ class LLVMPrinter(Printer):
             # look up parameter with name s
             val = self.func_arg_map.get(s.name)
         if not val:
-            raise LookupError("Symbol not found: %s" % s)
+            raise LookupError(f"Symbol not found: {s}")
         return val
 
     def _print_Pow(self, expr):
diff --git a/pystencils/placeholder_function.py b/pystencils/placeholder_function.py
index 18a0574b82292ecd9da0bff50f68d86811b472c7..8b8aa6ed0ed39d59f97c960d7efdd73f07cf38ef 100644
--- a/pystencils/placeholder_function.py
+++ b/pystencils/placeholder_function.py
@@ -34,7 +34,7 @@ def to_placeholder_function(expr, name):
     """
     symbols = list(expr.atoms(sp.Symbol))
     symbols.sort(key=lambda e: e.name)
-    derivative_symbols = [sp.Symbol("_d{}_d{}".format(name, s.name)) for s in symbols]
+    derivative_symbols = [sp.Symbol(f"_d{name}_d{s.name}") for s in symbols]
     derivatives = [sp.diff(expr, s) for s in symbols]
 
     assignments = [Assignment(sp.Symbol(name), expr)]
diff --git a/pystencils/rng.py b/pystencils/rng.py
index 85bc7c1b16e3f30bfdf23b6036e2e5849ccfc85a..bbc28bbd2c7d98cf931149d7e5572cda7fa33442 100644
--- a/pystencils/rng.py
+++ b/pystencils/rng.py
@@ -13,7 +13,7 @@ def _get_rng_template(name, data_type, num_vars):
         c_type = "double"
     template = "\n"
     for i in range(num_vars):
-        template += "{{result_symbols[{}].dtype}} {{result_symbols[{}].name}};\n".format(i, i)
+        template += f"{{result_symbols[{i}].dtype}} {{result_symbols[{i}].name}};\n"
     template += ("{}_{}{}({{parameters}}, " + ", ".join(["{{result_symbols[{}].name}}"] * num_vars) + ");\n") \
         .format(name, c_type, num_vars, *tuple(range(num_vars)))
     return template
@@ -36,15 +36,15 @@ class RNGBase(CustomCodeNode):
         if keys is None:
             keys = (0,) * self._num_keys
         if len(keys) != self._num_keys:
-            raise ValueError("Provided {} keys but need {}".format(len(keys), self._num_keys))
+            raise ValueError(f"Provided {len(keys)} keys but need {self._num_keys}")
         if len(offsets) != 3:
-            raise ValueError("Provided {} offsets but need {}".format(len(offsets), 3))
+            raise ValueError(f"Provided {len(offsets)} offsets but need {3}")
         self.result_symbols = tuple(TypedSymbol(sp.Dummy().name, self._data_type) for _ in range(self._num_vars))
         symbols_read = [s for s in keys if isinstance(s, sp.Symbol)]
         super().__init__("", symbols_read=symbols_read, symbols_defined=self.result_symbols)
         self._time_step = time_step
         self._offsets = offsets
-        self.headers = ['"{}_rand.h"'.format(self._name)]
+        self.headers = [f'"{self._name}_rand.h"']
         self.keys = tuple(keys)
         self._args = sp.sympify((dim, time_step, keys))
         self._dim = dim
diff --git a/pystencils/runhelper/db.py b/pystencils/runhelper/db.py
index bc3aa0a9ec799d57165bb797a059dc78f85f08e3..21b75c4ba91a53588db8637651bfda090c4044e6 100644
--- a/pystencils/runhelper/db.py
+++ b/pystencils/runhelper/db.py
@@ -120,7 +120,7 @@ class Database:
         Returns:
             pandas data frame
         """
-        from pandas.io.json import json_normalize
+        from pandas import json_normalize
 
         query_result = self.filter_params(parameter_query)
         attributes = [e.attributes for e in query_result]
diff --git a/pystencils/runhelper/parameterstudy.py b/pystencils/runhelper/parameterstudy.py
index 0ad27fd5bdd426b74c515a37d500da29eb805864..f4d8327d335125f7f57a8d622e1fd37855c3d9dd 100644
--- a/pystencils/runhelper/parameterstudy.py
+++ b/pystencils/runhelper/parameterstudy.py
@@ -215,7 +215,7 @@ class ParameterStudy:
             def log_message(self, fmt, *args):
                 return
 
-        print("Listening to connections on {}:{}. Scenarios to simulate: {}".format(ip, port, len(filtered_runs)))
+        print(f"Listening to connections on {ip}:{port}. Scenarios to simulate: {len(filtered_runs)}")
         server = HTTPServer((ip, port), ParameterStudyServer)
         while len(ParameterStudyServer.currently_running) > 0 or len(ParameterStudyServer.runs) > 0:
             server.handle_request()
@@ -241,7 +241,7 @@ class ParameterStudy:
         from urllib.error import URLError
         import time
         parameter_update = {} if parameter_update is None else parameter_update
-        url = "http://{}:{}".format(server, port)
+        url = f"http://{server}:{port}"
         client_name = client_name.format(hostname=socket.gethostname(), pid=os.getpid())
         start_time = time.time()
         while True:
@@ -265,7 +265,7 @@ class ParameterStudy:
                           'client_name': client_name}
                 urlopen(url + '/result', data=json.dumps(answer).encode())
             except URLError:
-                print("Cannot connect to server {}  retrying in 5 seconds...".format(url))
+                print(f"Cannot connect to server {url}  retrying in 5 seconds...")
                 sleep(5)
 
     def run_from_command_line(self, argv: Optional[Sequence[str]] = None) -> None:
diff --git a/pystencils/simp/assignment_collection.py b/pystencils/simp/assignment_collection.py
index 706008c859bd0cdc9f2f3b892557242a5aa01184..696038dd59f7faad83e74dbeecdfcfb038ea127a 100644
--- a/pystencils/simp/assignment_collection.py
+++ b/pystencils/simp/assignment_collection.py
@@ -378,10 +378,10 @@ class AssignmentCollection:
     def __str__(self):
         result = "Subexpressions:\n"
         for eq in self.subexpressions:
-            result += "\t{eq}\n".format(eq=eq)
+            result += f"\t{eq}\n"
         result += "Main Assignments:\n"
         for eq in self.main_assignments:
-            result += "\t{eq}\n".format(eq=eq)
+            result += f"\t{eq}\n"
         return result
 
     def __iter__(self):
@@ -446,6 +446,6 @@ class SymbolGen:
         return self
 
     def __next__(self):
-        name = "{}_{}".format(self._symbol, self._ctr)
+        name = f"{self._symbol}_{self._ctr}"
         self._ctr += 1
         return sp.Symbol(name)
diff --git a/pystencils/simp/simplificationstrategy.py b/pystencils/simp/simplificationstrategy.py
index 2c70c25c75534f12d6a404b531c218345be8a0af..cc601aa56981124dd75199f333a8c005fbdcb95d 100644
--- a/pystencils/simp/simplificationstrategy.py
+++ b/pystencils/simp/simplificationstrategy.py
@@ -92,7 +92,7 @@ class SimplificationStrategy:
             assignment_collection = t(assignment_collection)
             end_time = timeit.default_timer()
             op = assignment_collection.operation_count
-            time_str = "%.2f ms" % ((end_time - start_time) * 1000,)
+            time_str = f"{(end_time - start_time) * 1000:.2f} ms"
             total = op['adds'] + op['muls'] + op['divs']
             report.add(ReportElement(t.__name__, time_str, op['adds'], op['muls'], op['divs'], total))
         return report
@@ -129,7 +129,7 @@ class SimplificationStrategy:
 
             def _repr_html_(self):
                 def print_assignment_collection(title, c):
-                    text = '<h5 style="padding-bottom:10px">%s</h5> <div style="padding-left:20px;">' % (title, )
+                    text = f'<h5 style="padding-bottom:10px">{title}</h5> <div style="padding-left:20px;">'
                     if self.restrict_symbols:
                         text += "\n".join(["$$" + sp.latex(e) + '$$'
                                            for e in c.new_filtered(self.restrict_symbols).main_assignments])
@@ -151,5 +151,5 @@ class SimplificationStrategy:
     def __repr__(self):
         result = "Simplification Strategy:\n"
         for t in self._rules:
-            result += " - %s\n" % (t.__name__,)
+            result += f" - {t.__name__}\n"
         return result
diff --git a/pystencils/transformations.py b/pystencils/transformations.py
index 5b44eb3e1b0b5c0622a4369bbcc8c77ebd16e616..b3f9431bbf3035aaabd25f6eb430c738dddaf3a7 100644
--- a/pystencils/transformations.py
+++ b/pystencils/transformations.py
@@ -137,7 +137,7 @@ def get_common_shape(field_set):
         fixed_field_names = ",".join([f.name for f in field_set if f.has_fixed_shape])
         var_field_names = ",".join([f.name for f in field_set if not f.has_fixed_shape])
         msg = "Mixing fixed-shaped and variable-shape fields in a single kernel is not possible\n"
-        msg += "Variable shaped: %s \nFixed shaped:    %s" % (var_field_names, fixed_field_names)
+        msg += f"Variable shaped: {var_field_names} \nFixed shaped:    {fixed_field_names}"
         raise ValueError(msg)
 
     shape_set = set([f.spatial_shape for f in field_set])
@@ -326,7 +326,7 @@ def parse_base_pointer_info(base_pointer_specification, loop_order, spatial_dime
                 index = int(element[len("index"):])
                 add_new_element(spatial_dimensions + index)
             else:
-                raise ValueError("Unknown specification %s" % (element,))
+                raise ValueError(f"Unknown specification {element}")
 
         result.append(new_group)
 
@@ -902,13 +902,12 @@ class KernelConstraintsCheck:
             self._field_writes[fai].add(lhs.offsets)
             if self.check_double_write_condition and len(self._field_writes[fai]) > 1:
                 raise ValueError(
-                    "Field {} is written at two different locations".format(
-                        lhs.field.name))
+                    f"Field {lhs.field.name} is written at two different locations")
         elif isinstance(lhs, sp.Symbol):
             if self.scopes.is_defined_locally(lhs):
-                raise ValueError("Assignments not in SSA form, multiple assignments to {}".format(lhs.name))
+                raise ValueError(f"Assignments not in SSA form, multiple assignments to {lhs.name}")
             if lhs in self.scopes.free_parameters:
-                raise ValueError("Symbol {} is written, after it has been read".format(lhs.name))
+                raise ValueError(f"Symbol {lhs.name} is written, after it has been read")
             self.scopes.define_symbol(lhs)
 
     def _update_accesses_rhs(self, rhs):
@@ -1281,7 +1280,7 @@ def loop_blocking(ast_node: ast.KernelFunction, block_size) -> int:
             loop_stops[coord] = loop.stop
         else:
             assert loop.start == loop_starts[coord] and loop.stop == loop_stops[coord], \
-                "Multiple loops over coordinate {} with different loop bounds".format(coord)
+                f"Multiple loops over coordinate {coord} with different loop bounds"
 
     # Create the outer loops that iterate over the blocks
     outer_loop = None
diff --git a/pystencils_tests/test_abs.py b/pystencils_tests/test_abs.py
new file mode 100644
index 0000000000000000000000000000000000000000..53917bcc60a8b409490c492b8281d90b44003c25
--- /dev/null
+++ b/pystencils_tests/test_abs.py
@@ -0,0 +1,19 @@
+import sympy
+
+import pystencils
+from pystencils.data_types import cast_func, create_type
+
+
+def test_abs():
+    x, y, z = pystencils.fields('x, y, z:  float64[2d]')
+
+    default_int_type = create_type('int64')
+
+    assignments = pystencils.AssignmentCollection({
+        x[0, 0]: sympy.Abs(cast_func(y[0, 0], default_int_type))
+    })
+
+    ast = pystencils.create_kernel(assignments, target="gpu")
+    code = pystencils.get_code_str(ast)
+    print(code)
+    assert 'fabs(' not in code
diff --git a/pystencils_tests/test_blocking.py b/pystencils_tests/test_blocking.py
index 579ba41997cec4990ad4da00ae811cdb0b564bd2..b2b815b1a317d9f3228e9cc780c66336f378f782 100644
--- a/pystencils_tests/test_blocking.py
+++ b/pystencils_tests/test_blocking.py
@@ -19,7 +19,7 @@ def check_equivalence(assignments, src_arr):
             with_blocking = ps.create_kernel(assignments, cpu_blocking=(8, 16, 4), cpu_openmp=openmp,
                                              cpu_vectorize_info=vectorization).compile()
             without_blocking = ps.create_kernel(assignments).compile()
-            print("  openmp {}, vectorization {}".format(openmp, vectorization))
+            print(f"  openmp {openmp}, vectorization {vectorization}")
             dst_arr = np.zeros_like(src_arr)
             ref_arr = np.zeros_like(src_arr)
             np.copyto(src_arr, np.random.rand(*src_arr.shape))
diff --git a/pystencils_tests/test_buffer_gpu.py b/pystencils_tests/test_buffer_gpu.py
index f9ee96e9b19c147d95f006de4221980900375cb1..f63254cd45797f2b2de8b6601fceb23bb0a94d54 100644
--- a/pystencils_tests/test_buffer_gpu.py
+++ b/pystencils_tests/test_buffer_gpu.py
@@ -39,7 +39,8 @@ def _generate_fields(dt=np.uint8, stencil_directions=1, layout='numpy'):
 
         gpu_src_arr = gpuarray.to_gpu(src_arr)
         gpu_dst_arr = gpuarray.zeros_like(gpu_src_arr)
-        gpu_buffer_arr = gpuarray.zeros(np.prod(src_arr.shape), dtype=dt)
+        size = int(np.prod(src_arr.shape))
+        gpu_buffer_arr = gpuarray.zeros(size, dtype=dt)
 
         fields.append((src_arr, gpu_src_arr, gpu_dst_arr, gpu_buffer_arr))
     return fields
diff --git a/pystencils_tests/test_interpolation.py b/pystencils_tests/test_interpolation.py
index 477765bb31289dbfdc48927e3cc55f10d49f16a0..19201c7c9016be156f2ed73828eb65f1b9d6d229 100644
--- a/pystencils_tests/test_interpolation.py
+++ b/pystencils_tests/test_interpolation.py
@@ -125,7 +125,7 @@ def test_rotate_interpolation_gpu(dtype, address_mode, use_textures):
     else:
         lenna_gpu = gpuarray.to_gpu(
             np.ascontiguousarray(lenna, dtype))
-    x_f, y_f = pystencils.fields('x,y: %s [2d]' % type_map[dtype], ghost_layers=0)
+    x_f, y_f = pystencils.fields(f'x,y: {type_map[dtype]} [2d]', ghost_layers=0)
 
     transformed = scale * \
         sympy.rot_axis3(rotation_angle)[:2, :2] * sympy.Matrix((x_, y_)) - sympy.Matrix([2, 2])
@@ -173,7 +173,7 @@ def test_shift_interpolation_gpu(address_mode, dtype, use_textures):
         lenna_gpu = gpuarray.to_gpu(
             np.ascontiguousarray(lenna, dtype))
 
-    x_f, y_f = pystencils.fields('x,y: %s [2d]' % type_map[dtype], ghost_layers=0)
+    x_f, y_f = pystencils.fields(f'x,y: {type_map[dtype]} [2d]', ghost_layers=0)
 
     if use_textures:
         transformed = scale * sympy.rot_axis3(rotation_angle)[:2, :2] * sympy.Matrix((x_, y_)) + shift
diff --git a/pystencils_tests/test_kerncraft_coupling.py b/pystencils_tests/test_kerncraft_coupling.py
index 058c35c2a284da6d84928505587e6fbf12477843..0040006097bc5f48461105cb1d0462313c18bd1a 100644
--- a/pystencils_tests/test_kerncraft_coupling.py
+++ b/pystencils_tests/test_kerncraft_coupling.py
@@ -14,7 +14,7 @@ SCRIPT_FOLDER = os.path.dirname(os.path.realpath(__file__))
 INPUT_FOLDER = os.path.join(SCRIPT_FOLDER, "kerncraft_inputs")
 
 
-@pytest.mark.kernkraft
+@pytest.mark.kerncraft
 def test_compilation():
     machine_file_path = os.path.join(INPUT_FOLDER, "default_machine_file.yaml")
     machine = kerncraft.machinemodel.MachineModel(path_to_yaml=machine_file_path)
@@ -36,7 +36,7 @@ def test_compilation():
     print(mine)
 
 
-@pytest.mark.kernkraft
+@pytest.mark.kerncraft
 def analysis(kernel, model='ecmdata'):
     machine_file_path = os.path.join(INPUT_FOLDER, "default_machine_file.yaml")
     machine = kerncraft.machinemodel.MachineModel(path_to_yaml=machine_file_path)
@@ -54,7 +54,7 @@ def analysis(kernel, model='ecmdata'):
     return model
 
 
-@pytest.mark.kernkraft
+@pytest.mark.kerncraft
 def test_3d_7pt_iaca():
     # Make sure you use the intel compiler
     size = [20, 200, 200]
@@ -82,7 +82,7 @@ def test_3d_7pt_iaca():
     # assert reference.results['cl throughput'] == analysis.results['cl throughput']
 
 
-@pytest.mark.kernkraft
+@pytest.mark.kerncraft
 def test_2d_5pt():
     size = [30, 50, 3]
     kernel_file_path = os.path.join(INPUT_FOLDER, "2d-5pt.c")
@@ -104,7 +104,7 @@ def test_2d_5pt():
         assert e1 == e2
 
 
-@pytest.mark.kernkraft
+@pytest.mark.kerncraft
 def test_3d_7pt():
     size = [30, 50, 50]
     kernel_file_path = os.path.join(INPUT_FOLDER, "3d-7pt.c")
diff --git a/pystencils_tests/test_loop_cutting.py b/pystencils_tests/test_loop_cutting.py
index 0291074b87c051bcf381883e474078c663274ac1..daf803cbdab91417671dff53ae18561ed3df6011 100644
--- a/pystencils_tests/test_loop_cutting.py
+++ b/pystencils_tests/test_loop_cutting.py
@@ -112,7 +112,7 @@ def test_staggered_iteration_manual():
 
 def test_staggered_gpu():
     dim = 2
-    f = ps.fields("f: double[{dim}D]".format(dim=dim))
+    f = ps.fields(f"f: double[{dim}D]")
     s = ps.fields("s({dim}): double[{dim}D]".format(dim=dim), field_type=FieldType.STAGGERED)
     expressions = [(f[0, 0] + f[-1, 0]) / 2,
                    (f[0, 0] + f[0, -1]) / 2]
diff --git a/pytest.ini b/pytest.ini
index 0070425966626449378fcd1ff9eabe468256a112..2795fb9d85e18838ddc963f307d232ef1a6365bf 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -4,6 +4,7 @@ norecursedirs = *.egg-info .git .cache .ipynb_checkpoints htmlcov
 addopts = --doctest-modules --durations=20  --cov-config pytest.ini
 markers =
        kerncraft: tests depending on kerncraft
+       notebook: mark for notebooks
 
 [run]
 branch = True
@@ -38,7 +39,7 @@ exclude_lines =
        if __name__ == .__main__.:
 
 skip_covered = True
-fail_under = 74
+fail_under = 75
 
 [html]
 directory = coverage_report