diff --git a/LDDsimulation/LDDsimulation.py b/LDDsimulation/LDDsimulation.py
index bdbb20a198bef5e5060fd22fa8dae6872969f725..e250eeb30fc9fc34f09d81eb1a77e241375de1af 100644
--- a/LDDsimulation/LDDsimulation.py
+++ b/LDDsimulation/LDDsimulation.py
@@ -15,6 +15,7 @@ import helpers as hlp
 import pandas as pd
 import sys
 import copy
+# import multiprocessing as mp
 # import errno
 import h5py
 from termcolor import colored
@@ -204,37 +205,37 @@ class LDDsimulation(object):
         # minimal error has been achieved, i.e. the calculation can be considered
         # done or not.
 
-    def set_parameters(self,#
-                       use_case: str,
-                       output_dir: str,#
-                       subdomain_def_points: tp.List[tp.List[df.Point]],#
-                       # this is actually an array of bools
-                       isRichards: tp.Dict[int, bool],#
-                       interface_def_points: tp.List[tp.List[df.Point]],#
-                       outer_boundary_def_points: tp.Dict[int, tp.Dict[int, tp.List[df.Point]]],#
-                       adjacent_subdomains: tp.List[np.ndarray],#
-                       mesh_resolution: float,#
-                       viscosity: tp.Dict[int, tp.List[float]],#
-                       porosity: tp.Dict[int, tp.Dict[str, float]],#
-                       L: tp.Dict[int, tp.Dict[str, float]],#
-                       lambda_param: tp.Dict[int, tp.Dict[str, float]],#
-                       relative_permeability: tp.Dict[int, tp.Dict[str, tp.Callable[...,None]] ],#
-                       saturation: tp.Dict[int, tp.Callable[...,None]],#
-                       starttime: float,#
-                       timestep_size: tp.Dict[int, float],#
-                       number_of_timesteps: int,#
-                       number_of_timesteps_to_analyse: int,#
-                       sources: tp.Dict[int, tp.Dict[str, str]],#
-                       initial_conditions: tp.Dict[int, tp.Dict[str, str]],#
-                       dirichletBC_expression_strings: tp.Dict[int, tp.Dict[str, str]],#
-                       write2file: tp.Dict[str, bool],#
-                       exact_solution: tp.Dict[int, tp.Dict[str, str]] = None, #
-                       densities: tp.Dict[int, tp.Dict[str, float]] = None,#
-                       include_gravity: bool = False,#
-                       gravity_acceleration: float = 9.81,
-                       plot_timestep_every: int = 1,
-
-                       )-> None:
+    def set_parameters(
+        self,#
+        use_case: str,
+        output_dir: str,#
+        subdomain_def_points: tp.List[tp.List[df.Point]],#
+        # this is actually an array of bools
+        isRichards: tp.Dict[int, bool],#
+        interface_def_points: tp.List[tp.List[df.Point]],#
+        outer_boundary_def_points: tp.Dict[int, tp.Dict[int, tp.List[df.Point]]],#
+        adjacent_subdomains: tp.List[np.ndarray],#
+        mesh_resolution: float,#
+        viscosity: tp.Dict[int, tp.List[float]],#
+        porosity: tp.Dict[int, tp.Dict[str, float]],#
+        L: tp.Dict[int, tp.Dict[str, float]],#
+        lambda_param: tp.Dict[int, tp.Dict[str, float]],#
+        relative_permeability: tp.Dict[int, tp.Dict[str, tp.Callable[...,None]] ],#
+        saturation: tp.Dict[int, tp.Callable[...,None]],#
+        starttime: float,#
+        timestep_size: tp.Dict[int, float],#
+        number_of_timesteps: int,#
+        number_of_timesteps_to_analyse: int,#
+        sources: tp.Dict[int, tp.Dict[str, str]],#
+        initial_conditions: tp.Dict[int, tp.Dict[str, str]],#
+        dirichletBC_expression_strings: tp.Dict[int, tp.Dict[str, str]],#
+        write2file: tp.Dict[str, bool],#
+        exact_solution: tp.Dict[int, tp.Dict[str, str]] = None, #
+        densities: tp.Dict[int, tp.Dict[str, float]] = None,#
+        include_gravity: bool = False,#
+        gravity_acceleration: float = 9.81,
+        plot_timestep_every: int = 1,
+        )-> None:
 
         """ set parameters of an instance of class LDDsimulation"""
         self.use_case = use_case
@@ -486,7 +487,12 @@ class LDDsimulation(object):
                         {phase: error_calculated}
                         )
                     if debug:
-                        print(f"time = {time} (at timestep {self.timestep_num}): subsequent error on subdomain {sd_index} for {phase} phase in iteration {iteration} = ", subsequent_iter_error[phase])
+                        debugstring = "time = {} (at timestep".format(time) +\
+                            "{}): subsequent ".formal(self.timestep_num) +\
+                            "error on subdomain {} ".format(sd_index) +\
+                            "for {} phase in ".format(phase) +\
+                            "iteration {} = ".format(iteration)
+                        print(debugstring, subsequent_iter_error[phase])
 
                 # calculate the maximum over phase of subsequent errors for the
                 # stopping criterion.
diff --git a/LDDsimulation/helpers.py b/LDDsimulation/helpers.py
index efcaf68185d27d7afc70960804cb3fe38adfdcf2..a2a937406b04da916c5062c8404bb87f65072742 100644
--- a/LDDsimulation/helpers.py
+++ b/LDDsimulation/helpers.py
@@ -10,10 +10,125 @@ import matplotlib.pyplot as plt
 import numpy as np
 import typing as tp
 import sympy as sym
+import os
+import pandas as pd
+import LDDsimulation as ldd
+
+
+# RUN #########################################################################
+def run_simulation(
+        parameter,
+        starttime: float = 0.0,
+        mesh_resolution: float = 32,
+        ) -> None:
+    """Setup and run LDD simulation
+
+    Setup and run LDD simulation for starttime starttime and mesh resolution
+    mesh_resolution.
+
+    PARAMETERS
+    starttime       #type float     number >= 0 specifying the starting point
+                                    of the simulation.
+    mesh_resolution #type float     mesh resolution parameter for Fenics.
+    parameter       #type tp.Dict   Dictionary of all parameters of the LDD
+                                    simulation.
+    """
+    # parameter = kwargs
+    # initialise LDD simulation class
+    simulation = ldd.LDDsimulation(
+        tol=parameter["tol"],
+        LDDsolver_tol=parameter["solver_tol"],
+        debug=parameter["debugflag"],
+        max_iter_num=parameter["max_iter_num"],
+        FEM_Lagrange_degree=parameter["FEM_Lagrange_degree"],
+        mesh_study=parameter["mesh_study"]
+        )
+
+    simulation.set_parameters(
+        use_case=parameter["use_case"],
+        output_dir=parameter["output_string"],
+        subdomain_def_points=parameter["subdomain_def_points"],
+        isRichards=parameter["isRichards"],
+        interface_def_points=parameter["interface_def_points"],
+        outer_boundary_def_points=parameter["outer_boundary_def_points"],
+        adjacent_subdomains=parameter["adjacent_subdomains"],
+        mesh_resolution=mesh_resolution,  # parameter["mesh_resolution"],
+        viscosity=parameter["viscosity"],
+        porosity=parameter["porosity"],
+        L=parameter["L"],
+        lambda_param=parameter["lambda_param"],
+        relative_permeability=parameter["relative_permeability"],
+        saturation=parameter["sat_pressure_relationship"],
+        starttime=starttime,  # parameter["starttime"],
+        number_of_timesteps=parameter["number_of_timesteps"],
+        number_of_timesteps_to_analyse=parameter["number_of_timesteps_to_analyse"],
+        plot_timestep_every=parameter["plot_timestep_every"],
+        timestep_size=parameter["timestep_size"],
+        sources=parameter["source_expression"],
+        initial_conditions=parameter["initial_condition"],
+        dirichletBC_expression_strings=parameter["dirichletBC"],
+        exact_solution=parameter["exact_solution"],
+        densities=parameter["densities"],
+        include_gravity=parameter["include_gravity"],
+        gravity_acceleration=parameter["gravity_acceleration"],
+        write2file=parameter["write_to_file"],
+        )
+
+    simulation.initialise()
+    output_dir = simulation.output_dir
+    # simulation.write_exact_solution_to_xdmf()
+    output = simulation.run(analyse_condition=parameter["analyse_condition"])
+
+    for subdomain_index, subdomain_output in output.items():
+        mesh_h = subdomain_output['mesh_size']
+        for phase, error_dict in subdomain_output['errornorm'].items():
+            filename = output_dir \
+                + "subdomain{}".format(subdomain_index)\
+                + "-space-time-errornorm-{}-phase_meshrez{}.csv".format(
+                    phase,
+                    mesh_resolution
+                    )
+            # for errortype, errornorm in error_dict.items():
+
+            # eocfile = open("eoc_filename", "a")
+            # eocfile.write( str(mesh_h) + " " + str(errornorm) + "\n" )
+            # eocfile.close()
+            # if subdomain.isRichards:mesh_h
+            data_dict = {
+                'mesh_parameter': mesh_resolution,
+                'mesh_h': mesh_h,
+            }
+            for norm_type, errornorm in error_dict.items():
+                data_dict.update(
+                    {norm_type: errornorm}
+                )
+            errors = pd.DataFrame(data_dict, index=[mesh_resolution])
+            # check if file exists
+            if os.path.isfile(filename) is True:
+                with open(filename, 'a') as f:
+                    errors.to_csv(
+                        f,
+                        header=False,
+                        sep='\t',
+                        encoding='utf-8',
+                        index=False
+                        )
+            else:
+                errors.to_csv(
+                    filename,
+                    sep='\t',
+                    encoding='utf-8',
+                    index=False
+                    )
+
+
+def info(title):
+    """show process ids of multiprocessing simulation"""
+    print(title)
+    print('module name:', __name__)
+    print('parent process:', os.getppid())
+    print('process id:', os.getpid())
 
-# from termcolor import colored
-# from colormap import Colormap
-#import fenicstools as ft
 
 def print_once(*args,**kwargs):
     """When running in mpi only print arguments only once."""
@@ -23,22 +138,22 @@ def print_once(*args,**kwargs):
 
 
 def generate_exact_solution_expressions(
-                                        symbols: tp.Dict[int, int],
-                                        isRichards: tp.Dict[int, bool],
-                                        symbolic_pressure: tp.Dict[int, tp.Dict[str, str]],
-                                        symbolic_capillary_pressure: tp.Dict[int, str],
-                                        viscosity: tp.Dict[int, tp.List[float]],#
-                                        porosity: tp.Dict[int, tp.Dict[str, float]],#
-                                        relative_permeability: tp.Dict[int, tp.Dict[str, tp.Callable[...,None]] ],#
-                                        relative_permeability_prime: tp.Dict[int, tp.Dict[str, tp.Callable[...,None]] ],
-                                        saturation_pressure_relationship: tp.Dict[int, tp.Callable[...,None]] = None,#
-                                        saturation_pressure_relationship_prime: tp.Dict[int, tp.Callable[...,None]] = None,#
-                                        symbolic_S_pc_relationship: tp.Dict[int, tp.Callable[...,None]] = None,#
-                                        symbolic_S_pc_relationship_prime: tp.Dict[int, tp.Callable[...,None]] = None,#
-                                        densities: tp.Dict[int, tp.Dict[str, float]] = None,#
-                                        gravity_acceleration: float = 9.81,
-                                        include_gravity: bool = False,
-                                        ) -> tp.Dict[str, tp.Dict[int, tp.Dict[str, str]] ]:
+        symbols: tp.Dict[int, int],
+        isRichards: tp.Dict[int, bool],
+        symbolic_pressure: tp.Dict[int, tp.Dict[str, str]],
+        symbolic_capillary_pressure: tp.Dict[int, str],
+        viscosity: tp.Dict[int, tp.List[float]],#
+        porosity: tp.Dict[int, tp.Dict[str, float]],#
+        relative_permeability: tp.Dict[int, tp.Dict[str, tp.Callable[...,None]] ],#
+        relative_permeability_prime: tp.Dict[int, tp.Dict[str, tp.Callable[...,None]] ],
+        saturation_pressure_relationship: tp.Dict[int, tp.Callable[...,None]] = None,#
+        saturation_pressure_relationship_prime: tp.Dict[int, tp.Callable[...,None]] = None,#
+        symbolic_S_pc_relationship: tp.Dict[int, tp.Callable[...,None]] = None,#
+        symbolic_S_pc_relationship_prime: tp.Dict[int, tp.Callable[...,None]] = None,#
+        densities: tp.Dict[int, tp.Dict[str, float]] = None,#
+        gravity_acceleration: float = 9.81,
+        include_gravity: bool = False,
+    ) -> tp.Dict[str, tp.Dict[int, tp.Dict[str, str]] ]:
     """ Generate exact solution, source and initial condition code from symbolic expressions"""
 
     output = {
@@ -133,3 +248,60 @@ def generate_exact_solution_expressions(
                 )
 
     return output
+
+
+def generate_exact_DirichletBC(
+        isRichards: tp.Dict[int, bool],
+        outer_boundary_def_points: tp.Dict[int, tp.Dict[int, tp.List[df.Point]]],
+        exact_solution: tp.Dict[int, tp.Dict[str, str]]
+    ) -> tp.Dict[int, tp.Dict[str, str]]:
+    """generate Dirichlet BC from exact solution"""
+    dirichletBC = dict()
+    # similarly to the outer boundary dictionary, if a patch has no outer boundary
+    # None should be written instead of an expression.
+    # This is a bit of a brainfuck:
+    # dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind.
+    # Since a domain patch can have several disjoint outer boundary parts, the
+    # expressions need to get an enumaration index which starts at 0.
+    # So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of
+    # subdomain ind and boundary part j.
+    # Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting']
+    # return the actual expression needed for the dirichlet condition for both
+    # phases if present.
+
+    # subdomain index: {outer boudary part index: {phase: expression}}
+    for subdomain in isRichards.keys():
+        # subdomain can have no outer boundary
+        if outer_boundary_def_points[subdomain] is None:
+            dirichletBC.update({subdomain: None})
+        else:
+            dirichletBC.update({subdomain: dict()})
+            # set the dirichlet conditions to be the same code as exact solution on
+            # the subdomain.
+            for outer_boundary_ind in outer_boundary_def_points[subdomain].keys():
+                dirichletBC[subdomain].update(
+                    {outer_boundary_ind: exact_solution[subdomain]}
+                    )
+
+    return dirichletBC
+
+
+def generate_exact_symbolic_pc(
+                isRichards: tp.Dict[int, bool],
+                symbolic_pressure: tp.Dict[int, tp.Dict[str, str]]
+            ) -> tp.Dict[str, tp.Dict[str, str]]:
+    """generate symbolic capillary pressure from exact solution"""
+    
+    symbolic_pc = dict()
+    for subdomain, isR in isRichards.items():
+        if isR:
+            symbolic_pc.update(
+                {subdomain: -symbolic_pressure[subdomain]['wetting'].copy()}
+                )
+        else:
+            symbolic_pc.update(
+                {subdomain: symbolic_pressure[subdomain]['nonwetting'].copy()
+                            - symbolic_pressure[subdomain]['wetting'].copy()}
+                )
+
+    return symbolic_pc
diff --git a/Two-phase-Richards/multi-patch/layered_soil_with_inner_patch/TP-R-layered_soil_with_inner_patch-realistic.py b/Two-phase-Richards/multi-patch/layered_soil_with_inner_patch/TP-R-layered_soil_with_inner_patch-realistic.py
index 405bc4251d86ee640a956a0b052909fcf8742387..fb77415e1106e64f7b4456a745984e499f1307dd 100755
--- a/Two-phase-Richards/multi-patch/layered_soil_with_inner_patch/TP-R-layered_soil_with_inner_patch-realistic.py
+++ b/Two-phase-Richards/multi-patch/layered_soil_with_inner_patch/TP-R-layered_soil_with_inner_patch-realistic.py
@@ -13,6 +13,7 @@ import datetime
 import os
 import pandas as pd
 import multiprocessing as mp
+import domainSubstructuring as dss
 
 
 # init sympy session
@@ -162,216 +163,12 @@ output_string = "./output/{}-{}_timesteps{}_P{}".format(
     datestr, use_case, number_of_timesteps, FEM_Lagrange_degree
     )
 
-
 # DOMAIN AND INTERFACES  #######################################################
-# global domain
-subdomain0_vertices = [df.Point(-1.0,-1.0), #
-                        df.Point(1.0,-1.0),#
-                        df.Point(1.0,1.0),#
-                        df.Point(-1.0,1.0)]
-
-interface12_vertices = [df.Point(-1.0, 0.8),
-                        df.Point(0.3, 0.8),
-                        df.Point(0.5, 0.9),
-                        df.Point(0.8, 0.7),
-                        df.Point(1.0, 0.65)]
-
-
-                        # interface23
-interface23_vertices = [df.Point(-1.0, 0.0),
-                        df.Point(-0.35, 0.0),
-                        # df.Point(6.5, 4.5),
-                        df.Point(0.0, 0.0)]
-
-interface24_vertices = [interface23_vertices[2],
-                        df.Point(0.6, 0.0),
-                        ]
-
-interface25_vertices = [interface24_vertices[1],
-                        df.Point(1.0, 0.0)
-                        ]
-
-
-interface32_vertices = [interface23_vertices[2],
-                        interface23_vertices[1],
-                        interface23_vertices[0]]
-
-
-interface36_vertices = [df.Point(-1.0, -0.6),
-                        df.Point(-0.6, -0.45)]
-
-
-interface46_vertices = [interface36_vertices[1],
-                        df.Point(0.3, -0.25)]
-
-interface56_vertices = [interface46_vertices[1],
-                        df.Point(0.65, -0.6),
-                        df.Point(1.0, -0.7)]
-
-
-
-
-interface34_vertices = [interface36_vertices[1],
-                        interface23_vertices[2]]
-
-# Interface 45 needs to be split, because of the shape. There can be triangles
-# with two facets on the interface and this creates a rogue dof type error when
-# integrating over that particular interface. Accordingly, the lambda_param
-# dictionary has two entries for that interface.
-interface45_vertices_a = [interface56_vertices[0],
-                        df.Point(0.7, -0.2),#df.Point(0.7, -0.2),
-                        ]
-interface45_vertices_b = [df.Point(0.7, -0.2),#df.Point(0.7, -0.2),
-                        interface25_vertices[0]
-                        ]
-
-# interface_vertices introduces a global numbering of interfaces.
-interface_def_points = [interface12_vertices,
-                        interface23_vertices,
-                        interface24_vertices,
-                        interface25_vertices,
-                        interface34_vertices,
-                        interface36_vertices,
-                        interface45_vertices_a,
-                        interface45_vertices_b,
-                        interface46_vertices,
-                        interface56_vertices,
-                        ]
-adjacent_subdomains = [[1,2],
-                       [2,3],
-                       [2,4],
-                       [2,5],
-                       [3,4],
-                       [3,6],
-                       [4,5],
-                       [4,5],
-                       [4,6],
-                       [5,6]
-                       ]
-
-# subdomain1.
-subdomain1_vertices = [interface12_vertices[0],
-                        interface12_vertices[1],
-                        interface12_vertices[2],
-                        interface12_vertices[3],
-                        interface12_vertices[4], # southern boundary, 12 interface
-                        subdomain0_vertices[2], # eastern boundary, outer boundary
-                        subdomain0_vertices[3]] # northern boundary, outer on_boundary
-
-# vertex coordinates of the outer boundaries. If it can not be specified as a
-# polygon, use an entry per boundary polygon. This information is used for defining
-# the Dirichlet boundary conditions. If a domain is completely internal, the
-# dictionary entry should be 0: None
-subdomain1_outer_boundary_verts = {
-    0: [subdomain1_vertices[4], #
-        subdomain1_vertices[5], # eastern boundary, outer boundary
-        subdomain1_vertices[6],
-        subdomain1_vertices[0]]
-}
-
-#subdomain1
-subdomain2_vertices = [interface23_vertices[0],
-                        interface23_vertices[1],
-                        interface23_vertices[2],
-                        interface24_vertices[1],
-                        interface25_vertices[1], # southern boundary, 23 interface
-                        subdomain1_vertices[4], # eastern boundary, outer boundary
-                        subdomain1_vertices[3],
-                        subdomain1_vertices[2],
-                        subdomain1_vertices[1],
-                        subdomain1_vertices[0] ] # northern boundary, 12 interface
-
-subdomain2_outer_boundary_verts = {
-    0: [subdomain2_vertices[9],
-        subdomain2_vertices[0]],
-    1: [subdomain2_vertices[4],
-        subdomain2_vertices[5]]
-}
-
-
-subdomain3_vertices = [interface36_vertices[0],
-                       interface36_vertices[1],
-                       # interface34_vertices[0],
-                       interface34_vertices[1],
-                       # interface32_vertices[0],
-                       interface32_vertices[1],
-                       interface32_vertices[2]
-                       ]
-
-subdomain3_outer_boundary_verts = {
-    0: [subdomain3_vertices[4],
-        subdomain3_vertices[0]]
-}
-
-
-# subdomain3
-subdomain4_vertices = [interface46_vertices[0],
-                       interface46_vertices[1],
-                       # interface45_vertices[1],
-                       interface45_vertices_a[1],
-                       interface24_vertices[1],
-                       interface24_vertices[0],
-                       interface34_vertices[1]
-                       ]
-
-subdomain4_outer_boundary_verts = None
-
-subdomain5_vertices = [interface56_vertices[0],
-                       interface56_vertices[1],
-                       interface56_vertices[2],
-                       interface25_vertices[1],
-                       interface25_vertices[0],
-                       interface45_vertices_b[1],
-                       interface45_vertices_b[0]
-]
-
-
-subdomain5_outer_boundary_verts = {
-    0: [subdomain5_vertices[2],
-        subdomain5_vertices[3]]
-}
-
-
-
-subdomain6_vertices = [subdomain0_vertices[0],
-                       subdomain0_vertices[1], # southern boundary, outer boundary
-                       interface56_vertices[2],
-                       interface56_vertices[1],
-                       interface56_vertices[0],
-                       interface36_vertices[1],
-                       interface36_vertices[0]
-                       ]
-
-subdomain6_outer_boundary_verts = {
-    0: [subdomain6_vertices[6],
-        subdomain6_vertices[0],
-        subdomain6_vertices[1],
-        subdomain6_vertices[2]]
-}
-
-
-subdomain_def_points = [subdomain0_vertices,#
-                      subdomain1_vertices,#
-                      subdomain2_vertices,#
-                      subdomain3_vertices,#
-                      subdomain4_vertices,
-                      subdomain5_vertices,
-                      subdomain6_vertices
-                      ]
-
-
-# if a subdomain has no outer boundary write None instead, i.e.
-# i: None
-# if i is the index of the inner subdomain.
-outer_boundary_def_points = {
-    # subdomain number
-    1: subdomain1_outer_boundary_verts,
-    2: subdomain2_outer_boundary_verts,
-    3: subdomain3_outer_boundary_verts,
-    4: subdomain4_outer_boundary_verts,
-    5: subdomain5_outer_boundary_verts,
-    6: subdomain6_outer_boundary_verts
-}
+substructuring = dss.layeredSoilInnerPatch()
+interface_def_points = substructuring.interface_def_points
+adjacent_subdomains = substructuring.adjacent_subdomains
+subdomain_def_points = substructuring.subdomain_def_points
+outer_boundary_def_points = substructuring.outer_boundary_def_points
 
 # MODEL CONFIGURATION #########################################################
 isRichards = {
@@ -879,15 +676,10 @@ p_e_sym = {
         'nonwetting': (-1.0 -t*(1.0 + x**2) - sym.sqrt(2+t**2)**2)*y**2 },
 }
 
-
-pc_e_sym = dict()
-for subdomain, isR in isRichards.items():
-    if isR:
-        pc_e_sym.update({subdomain: -p_e_sym[subdomain]['wetting'].copy()})
-    else:
-        pc_e_sym.update({subdomain: p_e_sym[subdomain]['nonwetting'].copy()
-                                        - p_e_sym[subdomain]['wetting'].copy()})
-
+pc_e_sym = hlp.generate_exact_symbolic_pc(
+                isRichards=isRichards,
+                symbolic_pressure=p_e_sym
+            )
 
 symbols = {"x": x,
            "y": y,
@@ -914,154 +706,24 @@ exact_solution = exact_solution_example['exact_solution']
 initial_condition = exact_solution_example['initial_condition']
 
 # BOUNDARY CONDITIONS #########################################################
-# Dictionary of dirichlet boundary conditions.
-dirichletBC = dict()
-# similarly to the outer boundary dictionary, if a patch has no outer boundary
-# None should be written instead of an expression.
-# This is a bit of a brainfuck:
-# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind.
-# Since a domain patch can have several disjoint outer boundary parts, the
-# expressions need to get an enumaration index which starts at 0.
-# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of
-# subdomain ind and boundary part j.
-# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting']
-# return the actual expression needed for the dirichlet condition for both
-# phases if present.
-
-# subdomain index: {outer boudary part index: {phase: expression}}
-for subdomain in isRichards.keys():
-    # subdomain can have no outer boundary
-    if outer_boundary_def_points[subdomain] is None:
-        dirichletBC.update({subdomain: None})
-    else:
-        dirichletBC.update({subdomain: dict()})
-        # set the dirichlet conditions to be the same code as exact solution on
-        # the subdomain.
-        for outer_boundary_ind in outer_boundary_def_points[subdomain].keys():
-            dirichletBC[subdomain].update(
-                {outer_boundary_ind: exact_solution[subdomain]}
-                )
-
+# Dictionary of dirichlet boundary conditions. If an exact solution case is
+# used, use the hlp.generate_exact_DirichletBC() method to generate the
+# Dirichlet Boundary conditions from the exact solution.
+dirichletBC = hlp.generate_exact_DirichletBC(
+        isRichards=isRichards,
+        outer_boundary_def_points=outer_boundary_def_points,
+        exact_solution=exact_solution
+    )
+# If no exact solution is provided you need to provide a dictionary of boundary
+# conditions. See the definiton of hlp.generate_exact_DirichletBC() to see
+# the structure.
 
 # LOG FILE OUTPUT #############################################################
 # read this file and print it to std out. This way the simulation can produce a
 # log file with ./TP-R-layered_soil.py | tee simulation.log
-# f = open(thisfile, 'r')
-# print(f.read())
-# f.close()
-
-
-# RUN #########################################################################
-def run_simulation(
-        parameter,
-        starttime: float = 0.0,
-        mesh_resolution: float = 32,
-        ) -> None:
-    """Setup and run LDD simulation
-
-    Setup and run LDD simulation for starttime starttime and mesh resolution
-    mesh_resolution.
-
-    PARAMETERS
-    starttime       #type float     number >= 0 specifying the starting point
-                                    of the simulation.
-    mesh_resolution #type float     mesh resolution parameter for Fenics.
-    """
-    # parameter = kwargs
-    # initialise LDD simulation class
-    simulation = ldd.LDDsimulation(
-        tol=parameter["tol"],
-        LDDsolver_tol=parameter["solver_tol"],
-        debug=parameter["debugflag"],
-        max_iter_num=parameter["max_iter_num"],
-        FEM_Lagrange_degree=parameter["FEM_Lagrange_degree"],
-        mesh_study=parameter["mesh_study"]
-        )
-
-    simulation.set_parameters(
-        use_case=parameter["use_case"],
-        output_dir=parameter["output_string"],
-        subdomain_def_points=parameter["subdomain_def_points"],
-        isRichards=parameter["isRichards"],
-        interface_def_points=parameter["interface_def_points"],
-        outer_boundary_def_points=parameter["outer_boundary_def_points"],
-        adjacent_subdomains=parameter["adjacent_subdomains"],
-        mesh_resolution=mesh_resolution,  # parameter["mesh_resolution"],
-        viscosity=parameter["viscosity"],
-        porosity=parameter["porosity"],
-        L=parameter["L"],
-        lambda_param=parameter["lambda_param"],
-        relative_permeability=parameter["relative_permeability"],
-        saturation=parameter["sat_pressure_relationship"],
-        starttime=starttime,  # parameter["starttime"],
-        number_of_timesteps=parameter["number_of_timesteps"],
-        number_of_timesteps_to_analyse=parameter["number_of_timesteps_to_analyse"],
-        plot_timestep_every=parameter["plot_timestep_every"],
-        timestep_size=parameter["timestep_size"],
-        sources=parameter["source_expression"],
-        initial_conditions=parameter["initial_condition"],
-        dirichletBC_expression_strings=parameter["dirichletBC"],
-        exact_solution=parameter["exact_solution"],
-        densities=parameter["densities"],
-        include_gravity=parameter["include_gravity"],
-        gravity_acceleration=parameter["gravity_acceleration"],
-        write2file=parameter["write_to_file"],
-        )
-
-    simulation.initialise()
-    output_dir = simulation.output_dir
-    # simulation.write_exact_solution_to_xdmf()
-    output = simulation.run(analyse_condition=parameter["analyse_condition"])
-
-    for subdomain_index, subdomain_output in output.items():
-        mesh_h = subdomain_output['mesh_size']
-        for phase, error_dict in subdomain_output['errornorm'].items():
-            filename = output_dir \
-                + "subdomain{}".format(subdomain_index)\
-                + "-space-time-errornorm-{}-phase_meshrez{}.csv".format(
-                    phase,
-                    mesh_resolution
-                    )
-            # for errortype, errornorm in error_dict.items():
-
-            # eocfile = open("eoc_filename", "a")
-            # eocfile.write( str(mesh_h) + " " + str(errornorm) + "\n" )
-            # eocfile.close()
-            # if subdomain.isRichards:mesh_h
-            data_dict = {
-                'mesh_parameter': mesh_resolution,
-                'mesh_h': mesh_h,
-            }
-            for norm_type, errornorm in error_dict.items():
-                data_dict.update(
-                    {norm_type: errornorm}
-                )
-            errors = pd.DataFrame(data_dict, index=[mesh_resolution])
-            # check if file exists
-            if os.path.isfile(filename) is True:
-                with open(filename, 'a') as f:
-                    errors.to_csv(
-                        f,
-                        header=False,
-                        sep='\t',
-                        encoding='utf-8',
-                        index=False
-                        )
-            else:
-                errors.to_csv(
-                    filename,
-                    sep='\t',
-                    encoding='utf-8',
-                    index=False
-                    )
-
-
-def info(title):
-    """show process ids of multiprocessing simulation"""
-    print(title)
-    print('module name:', __name__)
-    print('parent process:', os.getppid())
-    print('process id:', os.getpid())
+f = open(thisfile, 'r')
+print(f.read())
+f.close()
 
 
 # MAIN ########################################################################
@@ -1108,11 +770,9 @@ if __name__ == '__main__':
     for starttime in starttimes:
         for mesh_resolution, solver_tol in resolutions.items():
             simulation_parameter.update({"solver_tol": solver_tol})
-            simulation_parameter.update({"mesh_resolution": mesh_resolution})
-            simulation_parameter.update({"starttime": starttime})
-            info(simulation_parameter["use_case"])
+            hlp.info(simulation_parameter["use_case"])
             LDDsim = mp.Process(
-                        target=run_simulation,
+                        target=hlp.run_simulation,
                         args=(
                             simulation_parameter,
                             starttime,