diff --git a/Applications/ApplicationsLib/ProjectData.cpp b/Applications/ApplicationsLib/ProjectData.cpp
index aab1e9e636889af11d003a79446fa064450e5be1..fc71924b38c7740bae5f0df4fb53f23dec048fd7 100644
--- a/Applications/ApplicationsLib/ProjectData.cpp
+++ b/Applications/ApplicationsLib/ProjectData.cpp
@@ -50,17 +50,18 @@ void readGeometry(std::string const& fname, GeoLib::GEOObjects & geo_objects)
 ProjectData::ProjectData() = default;
 
 ProjectData::ProjectData(BaseLib::ConfigTree const& project_config,
-	std::string const& path)
+	std::string const& project_directory,
+	std::string const& output_directory)
 {
 	// geometry
 	std::string const geometry_file = BaseLib::copyPathToFileName(
-			project_config.getConfParam<std::string>("geometry"), path
+			project_config.getConfParam<std::string>("geometry"), project_directory
 		);
 	detail::readGeometry(geometry_file, *_geoObjects);
 
 	// mesh
 	std::string const mesh_file = BaseLib::copyPathToFileName(
-			project_config.getConfParam<std::string>("mesh"), path
+			project_config.getConfParam<std::string>("mesh"), project_directory
 		);
 
 	MeshLib::Mesh* const mesh = FileIO::readMeshFromFile(mesh_file);
@@ -82,7 +83,7 @@ ProjectData::ProjectData(BaseLib::ConfigTree const& project_config,
 	parseProcesses(project_config.getConfSubtree("processes"));
 
 	// output
-	parseOutput(project_config.getConfSubtree("output"), path);
+	parseOutput(project_config.getConfSubtree("output"), output_directory);
 
 	// timestepping
 	parseTimeStepping(project_config.getConfSubtree("time_stepping"));
@@ -296,14 +297,12 @@ void ProjectData::parseProcesses(BaseLib::ConfigTree const& processes_config)
 }
 
 void ProjectData::parseOutput(BaseLib::ConfigTree const& output_config,
-	std::string const& path)
+	std::string const& output_directory)
 {
 	output_config.checkConfParam("type", "VTK");
 	DBUG("Parse output configuration:");
 
-	auto const file = output_config.getConfParam<std::string>("file");
-
-	_output_file_prefix = path + file;
+	_output = ProcessLib::Output<GlobalSetupType>::newInstance(output_config, output_directory);
 }
 
 void ProjectData::parseTimeStepping(BaseLib::ConfigTree const& timestepping_config)
diff --git a/Applications/ApplicationsLib/ProjectData.h b/Applications/ApplicationsLib/ProjectData.h
index 87ca99dfc66e45eb34bcf1531e3a871b720e6ed8..2082ed79d5f2b680946d1b05e44ed07a6385c496 100644
--- a/Applications/ApplicationsLib/ProjectData.h
+++ b/Applications/ApplicationsLib/ProjectData.h
@@ -22,6 +22,7 @@
 #include "ProcessLib/ProcessVariable.h"
 #include "ProcessLib/Process.h"
 #include "ProcessLib/Parameter.h"
+#include "ProcessLib/Output.h"
 
 #include "NumLib/ODESolver/Types.h"
 
@@ -60,10 +61,13 @@ public:
 	ProjectData();
 
 	/// Constructs project data by parsing provided configuration.
-	/// The additional  path is used to find files referenced in the
-	/// configuration.
+	///
+	/// \param config_tree Configuration as read from the prj file.
+	/// \param project_directory Where to look for files referenced in the \c config_tree.
+	/// \param output_directory  Where to write simulation output files to.
 	ProjectData(BaseLib::ConfigTree const& config_tree,
-	            std::string const& path);
+	            std::string const& project_directory,
+	            std::string const& output_directory);
 
 	ProjectData(ProjectData&) = delete;
 
@@ -131,10 +135,16 @@ public:
 		return _processes.end();
 	}
 
-	std::string const&
-	getOutputFilePrefix() const
+	ProcessLib::Output<GlobalSetupType> const&
+	getOutputControl() const
 	{
-		return _output_file_prefix;
+		return *_output;
+	}
+
+	ProcessLib::Output<GlobalSetupType>&
+	getOutputControl()
+	{
+		return *_output;
 	}
 
 	TimeLoop& getTimeLoop()
@@ -174,7 +184,8 @@ private:
 
 	/// Parses the output configuration.
 	/// Parses the file tag and sets output file prefix.
-	void parseOutput(BaseLib::ConfigTree const& output_config, std::string const& path);
+	void parseOutput(BaseLib::ConfigTree const& output_config,
+	                 std::string const& output_directory);
 
 	void parseTimeStepping(BaseLib::ConfigTree const& timestepping_config);
 
@@ -195,8 +206,7 @@ private:
 	/// Buffer for each parameter config passed to the process.
 	std::vector<std::unique_ptr<ProcessLib::ParameterBase>> _parameters;
 
-	/// Output file path with project prefix.
-	std::string _output_file_prefix;
+	std::unique_ptr<ProcessLib::Output<GlobalSetupType>> _output;
 
 	/// The time loop used to solve this project's processes.
 	std::unique_ptr<TimeLoop> _time_loop;
diff --git a/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.h b/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.h
index 2223b22953470a9da1203df333f7b02fdddd373e..d2cccfbcc8d6aaa8b9d7a7205c69f0714e024038 100644
--- a/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.h
+++ b/Applications/ApplicationsLib/UncoupledProcessesTimeLoop.h
@@ -29,7 +29,7 @@ template<typename Matrix, typename Vector>
 class UncoupledProcessesTimeLoop
 {
 public:
-    bool loop(ProjectData& project, std::string const& outdir);
+    bool loop(ProjectData& project);
 
     ~UncoupledProcessesTimeLoop();
 
@@ -151,11 +151,9 @@ private:
 
     //! Solves one timestep for the given \c process.
     bool solveOneTimeStepOneProcess(
-            unsigned const timestep,
             Vector& x, double const t, double const delta_t,
             SingleProcessData& process_data,
-            Process& process,
-            std::string const& output_file_name);
+            Process& process);
 
     //! Sets the EquationSystem for the given nonlinear solver,
     //! which is Picard or Newton depending on the NLTag.
@@ -290,11 +288,9 @@ template<typename Matrix, typename Vector>
 bool
 UncoupledProcessesTimeLoop<Matrix, Vector>::
 solveOneTimeStepOneProcess(
-        unsigned const timestep,
         Vector& x, double const t, double const delta_t,
         SingleProcessData& process_data,
-        typename UncoupledProcessesTimeLoop<Matrix, Vector>::Process& process,
-        std::string const& output_file_name)
+        typename UncoupledProcessesTimeLoop<Matrix, Vector>::Process& process)
 {
     auto& time_disc        =  process.getTimeDiscretization();
     auto& ode_sys          = *process_data.tdisc_ode_sys;
@@ -314,7 +310,6 @@ solveOneTimeStepOneProcess(
     time_disc.pushState(t, x, mat_strg);
 
     process.postTimestep(x);
-    process.output(output_file_name, timestep, x);
 
     return nonlinear_solver_succeeded;
 }
@@ -323,54 +318,79 @@ solveOneTimeStepOneProcess(
 template<typename Matrix, typename Vector>
 bool
 UncoupledProcessesTimeLoop<Matrix, Vector>::
-loop(ProjectData& project, std::string const& outdir)
+loop(ProjectData& project)
 {
     auto per_process_data = initInternalData(project);
 
+    auto& out_ctrl = project.getOutputControl();
+    out_ctrl.init(project.processesBegin(), project.processesEnd());
+
     auto const t0 = 0.0; // time of the IC
 
     // init solution storage
     setInitialConditions(project, t0, per_process_data);
 
+    // output initial conditions
+    {
+        unsigned pcs_idx = 0;
+        for (auto p = project.processesBegin(); p != project.processesEnd();
+             ++p, ++pcs_idx)
+        {
+            auto const& x0 = *_process_solutions[pcs_idx];
+            out_ctrl.doOutput(**p, 0, t0, x0);
+        }
+    }
+
     auto const delta_t = 1.0;
 
     // TODO only for now
     // Make sure there will be exactly one iteration of the loop below.
-    auto const t_end   = 1.5;
+    auto const t_end = 0.5;
 
-    double t;
+    double t, t_last;
     unsigned timestep = 1; // the first timestep really is number one
     bool nonlinear_solver_succeeded = true;
-    for (t=t0+delta_t; t<t_end+delta_t; t+=delta_t, ++timestep)
+
+    for (t=t0+delta_t, t_last=t0;
+         t<t_end+delta_t;
+         t_last=t, t+=delta_t, ++timestep)
     {
+        // TODO use process name
         unsigned pcs_idx = 0;
         for (auto p = project.processesBegin(); p != project.processesEnd();
              ++p, ++pcs_idx)
         {
-            std::string const& outpref = project.getOutputFilePrefix();
-            std::string const  output_file_name =
-                    BaseLib::joinPaths(outdir, outpref)
-                    + "_pcs_" + std::to_string(pcs_idx)
-                    + "_ts_"  + std::to_string(timestep)
-                    // + "_t_"   + std::to_string(t) // TODO: add that later
-                    + ".vtu";
-
             auto& x = *_process_solutions[pcs_idx];
 
             nonlinear_solver_succeeded = solveOneTimeStepOneProcess(
-                        timestep, x, t, delta_t,
-                        per_process_data[pcs_idx], **p, output_file_name);
+                        x, t, delta_t, per_process_data[pcs_idx], **p);
 
             if (!nonlinear_solver_succeeded) {
                 ERR("The nonlinear solver failed in timestep #%u at t = %g s"
                     " for process #%u.", timestep, t, pcs_idx);
+
+                // save unsuccessful solution
+                out_ctrl.doOutputAlways(**p, timestep, t, x);
+
                 break;
+            } else {
+                out_ctrl.doOutput(**p, timestep, t, x);
             }
         }
 
         if (!nonlinear_solver_succeeded) break;
+    }
 
-        break; // TODO only do a single timestep for now
+    // output last timestep
+    if (nonlinear_solver_succeeded)
+    {
+        unsigned pcs_idx = 0;
+        for (auto p = project.processesBegin(); p != project.processesEnd();
+             ++p, ++pcs_idx)
+        {
+            auto const& x = *_process_solutions[pcs_idx];
+            out_ctrl.doOutputLastTimestep(**p, timestep-1, t_last, x);
+        }
     }
 
     return nonlinear_solver_succeeded;
diff --git a/Applications/CLI/Tests.cmake b/Applications/CLI/Tests.cmake
index a9f658c5465b2bbd0d9f968da26fefb3406e10d5..49533168851b90000576b2d19fbc0ee53cd46588 100644
--- a/Applications/CLI/Tests.cmake
+++ b/Applications/CLI/Tests.cmake
@@ -12,7 +12,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS cube_${mesh_size}.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA cube_${mesh_size}_pcs_0_ts_1.vtu Linear_1_to_minus1 pressure
+			DIFF_DATA cube_${mesh_size}_pcs_0_ts_1_t_1.000000.vtu Linear_1_to_minus1 pressure
 			DATA cube_${mesh_size}.prj cube_1x1x1_hex_${mesh_size}.vtu cube_1x1x1.gml
 		)
 
@@ -23,7 +23,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS cube_${mesh_size}_neumann.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA cube_${mesh_size}_neumann_pcs_0_ts_1.vtu D1_left_front_N1_right pressure
+			DIFF_DATA cube_${mesh_size}_neumann_pcs_0_ts_1_t_1.000000.vtu D1_left_front_N1_right pressure
 			DATA cube_${mesh_size}_neumann.prj cube_1x1x1_hex_${mesh_size}.vtu cube_1x1x1.gml
 		)
 	endforeach()
@@ -36,7 +36,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS cube_${mesh_size}.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA cube_${mesh_size}_pcs_0_ts_1.vtu Linear_1_to_minus1 pressure
+			DIFF_DATA cube_${mesh_size}_pcs_0_ts_1_t_1.000000.vtu Linear_1_to_minus1 pressure
 			DATA cube_${mesh_size}.prj cube_1x1x1_hex_${mesh_size}.vtu cube_1x1x1.gml
 		)
 
@@ -47,7 +47,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS cube_${mesh_size}_neumann.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA cube_${mesh_size}_neumann_pcs_0_ts_1.vtu D1_left_front_N1_right pressure
+			DIFF_DATA cube_${mesh_size}_neumann_pcs_0_ts_1_t_1.000000.vtu D1_left_front_N1_right pressure
 			DATA cube_${mesh_size}_neumann.prj cube_1x1x1_hex_${mesh_size}.vtu cube_1x1x1.gml
 		)
 	endforeach()
@@ -61,7 +61,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS square_${mesh_size}.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA square_${mesh_size}_pcs_0_ts_1.vtu Linear_1_to_minus1 pressure
+			DIFF_DATA square_${mesh_size}_pcs_0_ts_1_t_1.000000.vtu Linear_1_to_minus1 pressure
 			DATA square_${mesh_size}.prj square_1x1_quad_${mesh_size}.vtu square_1x1.gml
 		)
 
@@ -72,7 +72,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS square_${mesh_size}_neumann.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA square_${mesh_size}_neumann_pcs_0_ts_1.vtu D1_left_bottom_N1_right pressure
+			DIFF_DATA square_${mesh_size}_neumann_pcs_0_ts_1_t_1.000000.vtu D1_left_bottom_N1_right pressure
 			DATA square_${mesh_size}_neumann.prj square_1x1_quad_${mesh_size}.vtu square_1x1.gml
 		)
 	endforeach()
@@ -85,7 +85,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS square_${mesh_size}.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA square_${mesh_size}_pcs_0_ts_1.vtu Linear_1_to_minus1 pressure
+			DIFF_DATA square_${mesh_size}_pcs_0_ts_1_t_1.000000.vtu Linear_1_to_minus1 pressure
 			DATA square_${mesh_size}.prj square_1x1_quad_${mesh_size}.vtu square_1x1.gml
 		)
 
@@ -96,7 +96,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS square_${mesh_size}_neumann.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA square_${mesh_size}_neumann_pcs_0_ts_1.vtu D1_left_bottom_N1_right pressure
+			DIFF_DATA square_${mesh_size}_neumann_pcs_0_ts_1_t_1.000000.vtu D1_left_bottom_N1_right pressure
 			DATA square_${mesh_size}_neumann.prj square_1x1_quad_${mesh_size}.vtu square_1x1.gml
 		)
 	endforeach()
@@ -110,7 +110,7 @@ if(NOT OGS_USE_MPI)
 			EXECUTABLE_ARGS line_${mesh_size}.prj
 			WRAPPER time
 			TESTER vtkdiff
-			DIFF_DATA line_${mesh_size}_pcs_0_ts_1.vtu Linear_1_to_minus1 pressure
+			DIFF_DATA line_${mesh_size}_pcs_0_ts_1_t_1.000000.vtu Linear_1_to_minus1 pressure
 			DATA line_${mesh_size}.prj line_1_line_${mesh_size}.vtu line_1.gml
 		)
 
@@ -121,7 +121,7 @@ if(NOT OGS_USE_MPI)
 					EXECUTABLE_ARGS line_${mesh_size}_neumann.prj
 					WRAPPER time
 					TESTER vtkdiff
-					DIFF_DATA line_${mesh_size}_neumann_pcs_0_ts_1.vtu D1_left_N1_right pressure
+					DIFF_DATA line_${mesh_size}_neumann_pcs_0_ts_1_t_1.000000.vtu D1_left_N1_right pressure
 					DATA line_${mesh_size}_neumann.prj line_1_line_${mesh_size}.vtu line_1.gml
 				)
 		endforeach()
@@ -135,9 +135,9 @@ else()
 		WRAPPER_ARGS -np 3
 		TESTER diff
 		DIFF_DATA
-			quad_20x10_GroundWaterFlow_result_pcs_0_ts_1_0.vtu
-			quad_20x10_GroundWaterFlow_result_pcs_0_ts_1_1.vtu
-			quad_20x10_GroundWaterFlow_result_pcs_0_ts_1_2.vtu
+			quad_20x10_GroundWaterFlow_result_pcs_0_ts_1_t_1.000000_0.vtu
+			quad_20x10_GroundWaterFlow_result_pcs_0_ts_1_t_1.000000_1.vtu
+			quad_20x10_GroundWaterFlow_result_pcs_0_ts_1_t_1.000000_2.vtu
 	)
 
 	AddTest(
@@ -148,9 +148,9 @@ else()
 		WRAPPER_ARGS -np 3
 		TESTER diff
 		DIFF_DATA
-			cube_1e3_pcs_0_ts_1_0.vtu
-			cube_1e3_pcs_0_ts_1_1.vtu
-			cube_1e3_pcs_0_ts_1_2.vtu
+			cube_1e3_pcs_0_ts_1_t_1.000000_0.vtu
+			cube_1e3_pcs_0_ts_1_t_1.000000_1.vtu
+			cube_1e3_pcs_0_ts_1_t_1.000000_2.vtu
 	)
 
 	AddTest(
@@ -161,8 +161,8 @@ else()
 		WRAPPER_ARGS -np 3
 		TESTER diff
 		DIFF_DATA
-			cube_1e3_neumann_pcs_0_ts_1_0.vtu
-			cube_1e3_neumann_pcs_0_ts_1_1.vtu
-			cube_1e3_neumann_pcs_0_ts_1_2.vtu
+			cube_1e3_neumann_pcs_0_ts_1_t_1.000000_0.vtu
+			cube_1e3_neumann_pcs_0_ts_1_t_1.000000_1.vtu
+			cube_1e3_neumann_pcs_0_ts_1_t_1.000000_2.vtu
 	)
 endif()
diff --git a/Applications/CLI/ogs.cpp b/Applications/CLI/ogs.cpp
index d46243b5c68694d85eb2c18e73f1201183e3099f..3702a7ce167240e4e4c54865e5a551bedb039dc9 100644
--- a/Applications/CLI/ogs.cpp
+++ b/Applications/CLI/ogs.cpp
@@ -81,7 +81,8 @@ int main(int argc, char *argv[])
 	auto project_config = BaseLib::makeConfigTree(
 	    project_arg.getValue(), !nonfatal_arg.getValue(), "OpenGeoSysProject");
 
-	ProjectData project(*project_config, BaseLib::extractPath(project_arg.getValue()));
+	ProjectData project(*project_config, BaseLib::extractPath(project_arg.getValue()),
+	                    outdir_arg.getValue());
 
 	project_config.checkAndInvalidate();
 
@@ -99,7 +100,7 @@ int main(int argc, char *argv[])
 	INFO("Solve processes.");
 
 	auto& time_loop = project.getTimeLoop();
-	time_loop.loop(project, outdir_arg.getValue());
+	time_loop.loop(project);
 
 	return 0;
 }
diff --git a/AssemblerLib/LocalDataInitializer.h b/AssemblerLib/LocalDataInitializer.h
index 46f69d426960b4194af1976edd1956cff138b14a..7dd42997176d425bbd00968b67c5790cf20c023e 100644
--- a/AssemblerLib/LocalDataInitializer.h
+++ b/AssemblerLib/LocalDataInitializer.h
@@ -111,6 +111,20 @@ static_assert(false, "The macro OGS_MAX_ELEMENT_ORDER is undefined.");
 #endif
 
 
+// Generates a lambda that creates a new LocalAssembler of type LAData<SHAPE_FCT>
+#define OGS_NEW_LOCAL_ASSEMBLER(SHAPE_FCT) \
+    [](MeshLib::Element const& e, \
+       std::size_t const local_matrix_size, \
+       unsigned const integration_order, \
+       ConstructorArgs&&... args) \
+    { \
+        return new LAData<SHAPE_FCT>{ \
+            e, local_matrix_size, integration_order, \
+            std::forward<ConstructorArgs>(args)... \
+        }; \
+    }
+
+
 namespace AssemblerLib
 {
 
@@ -124,7 +138,8 @@ template <
     template <typename, typename, typename, typename, unsigned> class LocalAssemblerData_,
     typename GlobalMatrix_,
     typename GlobalVector_,
-    unsigned GlobalDim>
+    unsigned GlobalDim,
+    typename... ConstructorArgs>
 class LocalDataInitializer
 {
     template <typename ShapeFunction_>
@@ -137,7 +152,6 @@ class LocalDataInitializer
                 IntegrationMethod<ShapeFunction_>,
                 GlobalMatrix_, GlobalVector_, GlobalDim>;
 
-
 public:
     LocalDataInitializer()
     {
@@ -146,19 +160,19 @@ public:
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_LINE) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 0 && OGS_MAX_ELEMENT_ORDER >= 1
         _builder[std::type_index(typeid(MeshLib::Point))] =
-            [](){ return new LAData<NumLib::ShapePoint1>; };
+                OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapePoint1);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_LINE) != 0 \
         && OGS_MAX_ELEMENT_DIM >= 1 && OGS_MAX_ELEMENT_ORDER >= 1
         _builder[std::type_index(typeid(MeshLib::Line))] =
-            [](){ return new LAData<NumLib::ShapeLine2>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeLine2);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_LINE) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 1 && OGS_MAX_ELEMENT_ORDER >= 2
         _builder[std::type_index(typeid(MeshLib::Line3))] =
-            [](){ return new LAData<NumLib::ShapeLine3>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeLine3);
 #endif
 
 
@@ -167,27 +181,27 @@ public:
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_QUAD) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 1
         _builder[std::type_index(typeid(MeshLib::Quad))] =
-            [](){ return new LAData<NumLib::ShapeQuad4>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeQuad4);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_CUBOID) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 1
         _builder[std::type_index(typeid(MeshLib::Hex))] =
-            [](){ return new LAData<NumLib::ShapeHex8>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeHex8);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_QUAD) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 2
         _builder[std::type_index(typeid(MeshLib::Quad8))] =
-            [](){ return new LAData<NumLib::ShapeQuad8>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeQuad8);
         _builder[std::type_index(typeid(MeshLib::Quad9))] =
-            [](){ return new LAData<NumLib::ShapeQuad9>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeQuad9);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_CUBOID) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
         _builder[std::type_index(typeid(MeshLib::Hex20))] =
-            [](){ return new LAData<NumLib::ShapeHex20>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeHex20);
 #endif
 
 
@@ -196,25 +210,25 @@ public:
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_TRI) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 1
         _builder[std::type_index(typeid(MeshLib::Tri))] =
-            [](){ return new LAData<NumLib::ShapeTri3>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeTri3);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_SIMPLEX) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 1
         _builder[std::type_index(typeid(MeshLib::Tet))] =
-            [](){ return new LAData<NumLib::ShapeTet4>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeTet4);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_TRI) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 2
         _builder[std::type_index(typeid(MeshLib::Tri6))] =
-            [](){ return new LAData<NumLib::ShapeTri6>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeTri6);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_SIMPLEX) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
         _builder[std::type_index(typeid(MeshLib::Tet10))] =
-            [](){ return new LAData<NumLib::ShapeTet10>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapeTet10);
 #endif
 
 
@@ -223,13 +237,13 @@ public:
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PRISM) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 1
         _builder[std::type_index(typeid(MeshLib::Prism))] =
-            [](){ return new LAData<NumLib::ShapePrism6>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapePrism6);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PRISM) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
         _builder[std::type_index(typeid(MeshLib::Prism15))] =
-            [](){ return new LAData<NumLib::ShapePrism15>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapePrism15);
 #endif
 
         // /// Pyramids //////////////////////////////////////////////////
@@ -237,28 +251,31 @@ public:
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PYRAMID) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 1
         _builder[std::type_index(typeid(MeshLib::Pyramid))] =
-            [](){ return new LAData<NumLib::ShapePyra5>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapePyra5);
 #endif
 
 #if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PYRAMID) != 0 \
     && OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
         _builder[std::type_index(typeid(MeshLib::Pyramid13))] =
-            [](){ return new LAData<NumLib::ShapePyra13>; };
+            OGS_NEW_LOCAL_ASSEMBLER(NumLib::ShapePyra13);
 #endif
     }
 
-    /// Sets the provided data_ptr to the newly created local assembler data and
-    /// calls init() forwarding all remaining arguments.
-    template <typename ...Args_>
-    void operator()(const MeshLib::Element& e,
-        LocalAssemblerDataInterface_<GlobalMatrix_, GlobalVector_>*& data_ptr, Args_&&... args)
+    /// Sets the provided data_ptr to the newly created local assembler data.
+    void operator()(
+            const MeshLib::Element& e,
+            LocalAssemblerDataInterface_<GlobalMatrix_, GlobalVector_>*& data_ptr,
+            std::size_t const local_matrix_size,
+            unsigned const integration_order,
+            ConstructorArgs&&... args)
     {
         auto const type_idx = std::type_index(typeid(e));
         auto it = _builder.find(type_idx);
 
         if (it != _builder.end()) {
-            data_ptr = it->second();
-            data_ptr->init(e, std::forward<Args_>(args)...);
+            data_ptr = it->second(
+                           e, local_matrix_size, integration_order,
+                           std::forward<ConstructorArgs>(args)...);
         } else {
             ERR("You are trying to build a local assembler for an unknown mesh element type (%s)."
                 " Maybe you have disabled this mesh element type in your build configuration.",
@@ -271,8 +288,12 @@ private:
     /// Mapping of element types to local assembler constructors.
     std::unordered_map<
         std::type_index,
-        std::function<LocalAssemblerDataInterface_<GlobalMatrix_, GlobalVector_>*()>
-            > _builder;
+        std::function<LocalAssemblerDataInterface_<GlobalMatrix_, GlobalVector_>*(
+            MeshLib::Element const& e,
+            std::size_t const local_matrix_size,
+            unsigned const integration_order,
+            ConstructorArgs&&...)>
+        > _builder;
 };
 
 }   // namespace AssemblerLib
@@ -286,5 +307,6 @@ private:
 #undef ENABLED_ELEMENT_TYPE_TRI
 #undef ENABLED_ELEMENT_TYPE_QUAD
 #undef OGS_ENABLED_ELEMENTS
+#undef OGS_NEW_LOCAL_ASSEMBLER
 
 #endif  // ASSEMBLER_LIB_LOCALDATAINITIALIZER_H_
diff --git a/FileIO/VtkIO/PVDFile.cpp b/FileIO/VtkIO/PVDFile.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..14a6430aa237d13456d8fede8afbd6efec76600c
--- /dev/null
+++ b/FileIO/VtkIO/PVDFile.cpp
@@ -0,0 +1,43 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include "PVDFile.h"
+
+#include <fstream>
+#include <iomanip>
+#include <limits>
+#include <logog/include/logog.hpp>
+
+namespace FileIO
+{
+
+void PVDFile::addVTUFile(const std::string &vtu_fname, double timestep)
+{
+    _datasets.push_back(std::make_pair(timestep, vtu_fname));
+
+    std::ofstream fh(_pvd_filename.c_str());
+    if (!fh) {
+        ERR("could not open file `%s'", _pvd_filename.c_str());
+        std::abort();
+    }
+
+    fh << std::setprecision(std::numeric_limits<double>::digits10);
+
+    fh << "<?xml version=\"1.0\"?>\n"
+           "<VTKFile type=\"Collection\" version=\"0.1\" byte_order=\"LittleEndian\""
+           " compressor=\"vtkZLibDataCompressor\">\n"
+           "  <Collection>\n";
+
+    for (auto const& pair : _datasets)
+        fh << "    <DataSet timestep=\"" << pair.first << "\" group=\"\" part=\"0\" file=\"" << pair.second << "\"/>\n";
+
+    fh << "  </Collection>\n</VTKFile>\n";
+}
+
+}
diff --git a/FileIO/VtkIO/PVDFile.h b/FileIO/VtkIO/PVDFile.h
new file mode 100644
index 0000000000000000000000000000000000000000..4cf48618d62daa5a77b8102c3c8dd3e9cb4d91ec
--- /dev/null
+++ b/FileIO/VtkIO/PVDFile.h
@@ -0,0 +1,38 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef FILEIO_VTK_PVDFILE_H
+#define FILEIO_VTK_PVDFILE_H
+
+#include <string>
+#include <vector>
+
+namespace FileIO
+{
+
+/*! Writes a basic PVD file for use with Paraview.
+ *
+ */
+class PVDFile
+{
+public:
+    //! Set a PVD file path
+    explicit PVDFile(std::string const& pvd_fname) : _pvd_filename(pvd_fname) {}
+
+    //! Add a VTU file to this PVD file.
+    void addVTUFile(std::string const& vtu_fname, double timestep);
+
+private:
+    std::string const _pvd_filename;
+    std::vector<std::pair<double, std::string>> _datasets; // a vector of (time, VTU file name)
+};
+
+} // namespace FileIO
+
+#endif // FILEIO_VTK_PVDFILE_H
diff --git a/ProcessLib/GroundwaterFlowFEM.h b/ProcessLib/GroundwaterFlowFEM.h
index 6fabf1b3ea9929c6d57ceafc424158f615b9e965..812c0496be55632d33ce9b99b90a270cfc1e3450 100644
--- a/ProcessLib/GroundwaterFlowFEM.h
+++ b/ProcessLib/GroundwaterFlowFEM.h
@@ -32,11 +32,6 @@ class LocalAssemblerDataInterface
 public:
     virtual ~LocalAssemblerDataInterface() = default;
 
-    virtual void init(MeshLib::Element const& e,
-            std::size_t const local_matrix_size,
-            Parameter<double, MeshLib::Element const&> const& hydraulic_conductivity,
-            unsigned const integration_order) = 0;
-
     virtual void assemble(double const t, std::vector<double> const& local_x) = 0;
 
     virtual void addToGlobal(AssemblerLib::LocalToGlobalIndexMap::RowColumnIndices const&,
@@ -59,11 +54,11 @@ public:
 
     /// The hydraulic_conductivity factor is directly integrated into the local
     /// element matrix.
-    void init(MeshLib::Element const& e,
-              std::size_t const local_matrix_size,
-              Parameter<double, MeshLib::Element const&> const&
-                  hydraulic_conductivity,
-              unsigned const integration_order) override
+    LocalAssemblerData(MeshLib::Element const& e,
+                       std::size_t const local_matrix_size,
+                       unsigned const integration_order,
+                       Parameter<double, MeshLib::Element const&> const&
+                       hydraulic_conductivity)
     {
         _integration_order = integration_order;
 
diff --git a/ProcessLib/GroundwaterFlowProcess.h b/ProcessLib/GroundwaterFlowProcess.h
index e7641fafcec211305eb7d59c81a95369af6fb527..528711d1f879e6be162324a3f97ef8fc4b7f2e87 100644
--- a/ProcessLib/GroundwaterFlowProcess.h
+++ b/ProcessLib/GroundwaterFlowProcess.h
@@ -80,7 +80,8 @@ public:
             GroundwaterFlow::LocalAssemblerData,
             typename GlobalSetup::MatrixType,
             typename GlobalSetup::VectorType,
-            GlobalDim>;
+            GlobalDim,
+            Parameter<double, MeshLib::Element const&> const&>;
 
         LocalDataInitializer initializer;
 
@@ -97,8 +98,8 @@ public:
                 local_asm_builder,
                 this->_mesh.getElements(),
                 _local_assemblers,
-                _hydraulic_conductivity,
-                this->_integration_order);
+                this->_integration_order,
+                _hydraulic_conductivity);
     }
 
     // TODO remove, but put "gw_" somewhere
diff --git a/ProcessLib/NeumannBc.h b/ProcessLib/NeumannBc.h
index d9faba93024aa1aa0138891d2d407bd658c29426..8cc1349e0edd4551014c1de20f33a4cdf4bc969f 100644
--- a/ProcessLib/NeumannBc.h
+++ b/ProcessLib/NeumannBc.h
@@ -131,7 +131,8 @@ private:
             LocalNeumannBcAsmDataInterface,
             LocalNeumannBcAsmData,
             GlobalMatrix, GlobalVector,
-            GlobalDim>;
+            GlobalDim,
+            std::function<double (MeshLib::Element const&)> const&>;
 
         LocalDataInitializer initializer;
 
@@ -155,8 +156,8 @@ private:
                 local_asm_builder,
                 _elements,
                 _local_assemblers,
-                elementValueLookup,
-                _integration_order);
+                _integration_order,
+                elementValueLookup);
 
         DBUG("Create global assembler.");
         _global_assembler.reset(
diff --git a/ProcessLib/NeumannBcAssembler.h b/ProcessLib/NeumannBcAssembler.h
index 9e610e15ce3b31378de7aabef01e38fefa7b857f..fe124ef9996aa3659552fd23af1c3e0eddf672c1 100644
--- a/ProcessLib/NeumannBcAssembler.h
+++ b/ProcessLib/NeumannBcAssembler.h
@@ -25,11 +25,6 @@ class LocalNeumannBcAsmDataInterface
 public:
     virtual ~LocalNeumannBcAsmDataInterface() = default;
 
-    virtual void init(MeshLib::Element const& e,
-            std::size_t const local_matrix_size,
-            std::function<double (MeshLib::Element const&)> const& value_lookup,
-            unsigned const integration_order) = 0;
-
     virtual void assemble(const double t) = 0;
 
     virtual void addToGlobal(AssemblerLib::LocalToGlobalIndexMap::RowColumnIndices const&,
@@ -53,11 +48,11 @@ public:
 
     /// The neumann_bc_value factor is directly integrated into the local
     /// element matrix.
-    void
-    init(MeshLib::Element const& e,
-        std::size_t const local_matrix_size,
-        std::function<double (MeshLib::Element const&)> const& value_lookup,
-        unsigned const integration_order) override
+    LocalNeumannBcAsmData(
+            MeshLib::Element const& e,
+            std::size_t const local_matrix_size,
+            unsigned const integration_order,
+            std::function<double (MeshLib::Element const&)> const& value_lookup)
     {
         using FemType = NumLib::TemplateIsoparametric<
             ShapeFunction, ShapeMatricesType>;
diff --git a/ProcessLib/Output.cpp b/ProcessLib/Output.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0646f2bd9a5fdd07b19d4473ed0fcd28f33a3744
--- /dev/null
+++ b/ProcessLib/Output.cpp
@@ -0,0 +1,147 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include<fstream>
+#include "Output.h"
+
+#include<cassert>
+#include<vector>
+
+#include <logog/include/logog.hpp>
+
+#include "BaseLib/FileTools.h"
+
+namespace
+{
+//! Determines if there should be output at the given \c timestep.
+template<typename CountsSteps>
+bool shallDoOutput(unsigned timestep, CountsSteps const& repeats_each_steps)
+{
+    unsigned each_steps = 1;
+
+    for (auto const& pair : repeats_each_steps)
+    {
+        each_steps = pair.each_steps;
+
+        if (timestep > pair.repeat * each_steps)
+        {
+            timestep -= pair.repeat * each_steps;
+        } else {
+            break;
+        }
+    }
+
+    return timestep % each_steps == 0;
+}
+}
+
+namespace ProcessLib
+{
+
+template<typename GlobalSetup>
+std::unique_ptr<Output<GlobalSetup>> Output<GlobalSetup>::
+newInstance(const BaseLib::ConfigTree &config, std::string const& output_directory)
+{
+    std::unique_ptr<Output> out{ new Output{
+        BaseLib::joinPaths(output_directory,
+                           config.getConfParam<std::string>("prefix"))}};
+
+    if (auto const timesteps = config.getConfSubtreeOptional("timesteps"))
+    {
+        for (auto pair : timesteps->getConfSubtreeList("pair"))
+        {
+            auto repeat     = pair.getConfParam<unsigned>("repeat");
+            auto each_steps = pair.getConfParam<unsigned>("each_steps");
+
+            assert(repeat != 0 && each_steps != 0);
+            out->_repeats_each_steps.emplace_back(repeat, each_steps);
+        }
+
+        if (out->_repeats_each_steps.empty()) {
+            ERR("You have not given any pair (<repeat/>, <each_steps/>) that defines"
+                    " at which timesteps output shall be written. Aborting.");
+            std::abort();
+        }
+    }
+    else
+    {
+        out->_repeats_each_steps.emplace_back(1, 1);
+    }
+
+    return out;
+}
+
+template<typename GlobalSetup>
+void Output<GlobalSetup>::
+init(typename Output<GlobalSetup>::ProcessIter first,
+     typename Output<GlobalSetup>::ProcessIter last)
+{
+    for (unsigned pcs_idx = 0; first != last; ++first, ++pcs_idx)
+    {
+        auto const filename = _output_file_prefix
+                              + "_pcs_" + std::to_string(pcs_idx)
+                              + ".pvd";
+        _single_process_data.emplace(std::piecewise_construct,
+                std::forward_as_tuple(&**first),
+                std::forward_as_tuple(pcs_idx, filename));
+    }
+}
+
+template<typename GlobalSetup>
+void Output<GlobalSetup>::
+doOutputAlways(Process<GlobalSetup> const& process,
+               unsigned timestep,
+               const double t,
+               typename GlobalSetup::VectorType const& x)
+{
+    auto spd_it = _single_process_data.find(&process);
+    if (spd_it == _single_process_data.end()) {
+        ERR("The given process is not contained in the output configuration."
+            " Aborting.");
+        std::abort();
+    }
+    auto& spd = spd_it->second;
+
+    std::string const output_file_name =
+            _output_file_prefix + "_pcs_" + std::to_string(spd.process_index)
+            + "_ts_" + std::to_string(timestep)
+            + "_t_"  + std::to_string(t)
+            + ".vtu";
+    DBUG("output to %s", output_file_name.c_str());
+    process.output(output_file_name, timestep, x);
+    spd.pvd_file.addVTUFile(output_file_name, t);
+}
+
+template<typename GlobalSetup>
+void Output<GlobalSetup>::
+doOutput(Process<GlobalSetup> const& process,
+         unsigned timestep,
+         const double t,
+         typename GlobalSetup::VectorType const& x)
+{
+    if (shallDoOutput(timestep, _repeats_each_steps))
+        doOutputAlways(process, timestep, t, x);
+}
+
+template<typename GlobalSetup>
+void Output<GlobalSetup>::
+doOutputLastTimestep(Process<GlobalSetup> const& process,
+                     unsigned timestep,
+                     const double t,
+                     typename GlobalSetup::VectorType const& x)
+{
+    if (!shallDoOutput(timestep, _repeats_each_steps))
+        doOutputAlways(process, timestep, t, x);
+}
+
+
+// explicit instantiation
+template class Output<GlobalSetupType>;
+
+}
diff --git a/ProcessLib/Output.h b/ProcessLib/Output.h
new file mode 100644
index 0000000000000000000000000000000000000000..b83826acac758cca7a43419a5ff933f6f0eb6a43
--- /dev/null
+++ b/ProcessLib/Output.h
@@ -0,0 +1,98 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef PROCESSLIB_OUTPUT_H
+#define PROCESSLIB_OUTPUT_H
+
+#include "BaseLib/ConfigTree.h"
+#include "FileIO/VtkIO/PVDFile.h"
+#include "Process.h"
+
+namespace ProcessLib
+{
+
+/*! Manages writing the solution of processes to disk.
+ *
+ * This class decides at which timesteps output is written
+ * and initiates the writing process.
+ */
+template<typename GlobalSetup>
+class Output
+{
+public:
+    static std::unique_ptr<Output>
+    newInstance(const BaseLib::ConfigTree& config,
+                const std::string& output_directory);
+
+    using ProcessIter = std::vector<std::unique_ptr<ProcessLib::Process<GlobalSetupType>>>
+                        ::const_iterator;
+
+    //! Opens a PVD file for each process.
+    void init(ProcessIter first, ProcessIter last);
+
+    //! Writes output for the given \c process if it should be written in the
+    //! given \c timestep.
+    void doOutput(
+            Process<GlobalSetup> const& process, unsigned timestep,
+            const double t,
+            typename GlobalSetup::VectorType const& x);
+
+    //! Writes output for the given \c process if it has not been written yet.
+    //! This method is intended for doing output after the last timestep in order
+    //! to make sure that its results are written.
+    void doOutputLastTimestep(
+            Process<GlobalSetup> const& process, unsigned timestep,
+            const double t,
+            typename GlobalSetup::VectorType const& x);
+
+    //! Writes output for the given \c process.
+    //! This method will always write.
+    //! It is intended to write output in error handling routines.
+    void doOutputAlways(
+            Process<GlobalSetup> const& process, unsigned timestep,
+            const double t,
+            typename GlobalSetup::VectorType const& x);
+
+    struct PairRepeatEachSteps
+    {
+        explicit PairRepeatEachSteps(unsigned c, unsigned e)
+            : repeat(c), each_steps(e)
+        {}
+
+        const unsigned repeat;     //!< Apply \c each_steps \c repeat times.
+        const unsigned each_steps; //!< Do output every \c each_steps timestep.
+    };
+private:
+    struct SingleProcessData
+    {
+        SingleProcessData(unsigned process_index_,
+                          std::string const& filename)
+            : process_index(process_index_)
+            , pvd_file(filename)
+        {}
+
+        const unsigned process_index;
+        FileIO::PVDFile pvd_file;
+    };
+
+    Output(std::string const& prefix)
+        : _output_file_prefix(prefix)
+    {}
+
+    std::string _output_file_prefix;
+
+    //! Describes after which timesteps to write output.
+    std::vector<PairRepeatEachSteps> _repeats_each_steps;
+
+    std::map<Process<GlobalSetup> const*, SingleProcessData> _single_process_data;
+};
+
+}
+
+#endif // PROCESSLIB_OUTPUT_H
diff --git a/Tests/Data b/Tests/Data
index 879c870ddea452ecf3d8e0bd5a769d15b73f8be1..2464c6e74e85924556091735120db4cedf6abdf6 160000
--- a/Tests/Data
+++ b/Tests/Data
@@ -1 +1 @@
-Subproject commit 879c870ddea452ecf3d8e0bd5a769d15b73f8be1
+Subproject commit 2464c6e74e85924556091735120db4cedf6abdf6
diff --git a/scripts/cmake/test/AddTest.cmake b/scripts/cmake/test/AddTest.cmake
index db228b0d42b66c2c194bc50379720eb840711d56..66a9aa3175a085c0a9479b7a3dbab05e8ccac87e 100644
--- a/scripts/cmake/test/AddTest.cmake
+++ b/scripts/cmake/test/AddTest.cmake
@@ -19,11 +19,11 @@
 #
 #   diff-tester
 #     - DIFF_DATA <list of files to diff>
-#       # the given file is compared to [filename]_expected.[extension]
+#       # the given file is compared to a file with the same name from Tests/Data
 #
 #   numdiff-tester
 #     - DIFF_DATA <list of files to numdiff>
-#       # the given file is compared to [filename]_expected.[extension]
+#       # the given file is compared to a file with the same name from Tests/Data
 #
 #   vtkdiff-tester
 #     - DIFF_DATA <vtk file> <data array a name> <data array b name>
@@ -115,9 +115,7 @@ function (AddTest)
 
 	if(AddTest_TESTER STREQUAL "diff" OR AddTest_TESTER STREQUAL "numdiff")
 		foreach(FILE ${AddTest_DIFF_DATA})
-			get_filename_component(FILE_NAME ${FILE} NAME_WE)
-			get_filename_component(FILE_EXT ${FILE} EXT)
-			set(FILE_EXPECTED ${FILE_NAME}_expected${FILE_EXT})
+			get_filename_component(FILE_EXPECTED ${FILE} NAME)
 			set(TESTER_COMMAND ${TESTER_COMMAND} "${SELECTED_DIFF_TOOL_PATH} \
 				${TESTER_ARGS} ${AddTest_SOURCE_PATH}/${FILE_EXPECTED} \
 				${AddTest_BINARY_PATH}/${FILE}")