diff --git a/benchmark-driver/benchmark-run-kind.cc b/benchmark-driver/benchmark-run-kind.cc
index 6897f9ca8d720483d59f784f8d8bae4cac62f97d..50885af700eec0d08889fef58503e84a3c32c2f8 100644
--- a/benchmark-driver/benchmark-run-kind.cc
+++ b/benchmark-driver/benchmark-run-kind.cc
@@ -2,7 +2,7 @@
 // Copyright © 2021 Florian Schmaus
 #include "benchmark-run-kind.hpp"
 
-#include <ios>
+#include <string>
 
 namespace mazstab {
 auto operator<<(std::ostream& out, const BenchmarkRunKind& benchmark_run_kind) -> std::ostream& {
diff --git a/benchmark-driver/benchmark-run-kind.hpp b/benchmark-driver/benchmark-run-kind.hpp
index b3c8b36eb76f258d8d4b6e04e55ce54f6c06ef3f..05d4a63953563da69af604f92590a08f6d5141f1 100644
--- a/benchmark-driver/benchmark-run-kind.hpp
+++ b/benchmark-driver/benchmark-run-kind.hpp
@@ -3,7 +3,6 @@
 #pragma once
 
 #include <istream>
-#include <ostream>
 
 namespace mazstab {
 enum class BenchmarkRunKind {
diff --git a/benchmark-runner/main/src/de/fau/cs/mazstab/Mazstab.scala b/benchmark-runner/main/src/de/fau/cs/mazstab/Mazstab.scala
index d6419b5fbe1f57634e6779d88994eda1c905a13d..2ce27d3c4a4a3757eb94b4e7b3a86fb001642549 100644
--- a/benchmark-runner/main/src/de/fau/cs/mazstab/Mazstab.scala
+++ b/benchmark-runner/main/src/de/fau/cs/mazstab/Mazstab.scala
@@ -2,57 +2,63 @@
 // Copyright © 2021 Florian Schmaus
 package de.fau.cs.mazstab
 
-import java.nio.file.Path
-
 import scala.collection._
 import scala.jdk.CollectionConverters._
 
 import net.jcazevedo.moultingyaml._
+import os._
 
-class MazstabContext(conf: MazstabConf) {
+class MazstabContext(val conf: MazstabConf) {
   private val postProcessedDirectory = conf
-    .experimentDirectory()
-    .resolve("post-processed")
-    .resolve(Mazstab.getTimestamp)
+    .experimentDirectory() / "post-processed" / Mazstab.getTimestamp
 
-  def getPostProcessedDirectory() = mkdirIfRequired(postProcessedDirectory)
+  def getPostProcessedDirectory() = {
+    os.makeDir(postProcessedDirectory)
+    postProcessedDirectory
+  }
 
   private val rawDataDirectory = conf
-    .experimentDirectory()
-    .resolve("raw-data")
+    .experimentDirectory() / "raw-data"
+
+  def getRawDataDirectory() = {
+    os.makeDir.all(rawDataDirectory)
+    rawDataDirectory
+  }
+
+  def getExperimentDescriptionYaml(): Path = conf
+    .experimentDirectory() / "description.yaml"
+
+  lazy val experimentDescription = {
+    import ExperimentDescriptionYamlProtocol._
+    os.read(getExperimentDescriptionYaml())
+      .parseYaml
+      .convertTo[ExperimentDescription]
+  }
 
-  def getRawDataDirectory() = mkdirIfRequired(rawDataDirectory)
 }
 
 object Mazstab {
-  implicit def nioPathToOsPath(nioPath: Path) = os.Path(nioPath)
-
   def main(args: Array[String]): Unit = {
     val conf = new MazstabConf(args)
+    val context = new MazstabContext(conf)
 
     if (conf.subcommand != Some(conf.postProcess)) {
       val mazstabRepository =
         org.eclipse.jgit.storage.file.FileRepositoryBuilder
-          .create(conf.rootDirectory().toFile)
+          .create(conf.rootDirectory().toIO)
 
       val startupMessage = s"""MazStab starting"
 Experiment directory: ${conf.experimentDirectory()}
 MazStab root: ${conf.rootDirectory()} (${mazstabRepository.getBranch})"""
       println(startupMessage)
 
-      val experimentDirectory = conf.experimentDirectory().toFile
-      if (!experimentDirectory.isDirectory) {
-        val mkdirExperimentDirectoryCreated = experimentDirectory.mkdirs
-        if (!mkdirExperimentDirectoryCreated)
-          throw new Exception("Could not create " + experimentDirectory)
-      }
+      os.makeDir(conf.experimentDirectory())
 
       val buildDir = buildMazstab(conf)
 
-      performBenchmark(buildDir, conf)
+      performBenchmark(buildDir, context)
     }
 
-    val context = new MazstabContext(conf)
     val processedResults = MazstabPostProcess(context)
 
     MazstabPgfPlots(context, processedResults)
@@ -71,11 +77,10 @@ MazStab root: ${conf.rootDirectory()} (${mazstabRepository.getBranch})"""
 
   def buildMazstab(conf: MazstabConf): Path = {
     val buildDir =
-      conf.buildDirectory.getOrElse(conf.experimentDirectory().resolve("build"))
+      conf.buildDirectory.getOrElse(conf.experimentDirectory() / "build")
     val buildDirString = buildDir.toString
-    val buildDirFile = buildDir.toFile
-    val buildDirIsDirectory = buildDirFile.isDirectory
-    val buildDirContainsNinja = buildDir.resolve("build.ninja").toFile.isFile
+    val buildDirIsDirectory = buildDir.toIO.isDirectory
+    val buildDirContainsNinja = (buildDir / "build.ninja").toIO.isFile
 
     if (!buildDirIsDirectory || !buildDirContainsNinja) {
       // "meson setup" will also create the build directory if it does
@@ -96,8 +101,8 @@ MazStab root: ${conf.rootDirectory()} (${mazstabRepository.getBranch})"""
     buildDir
   }
 
-  def performBenchmark(buildDir: Path, conf: MazstabConf): Unit = {
-    val benchmarkBinaries = buildDir.toFile.listFiles
+  def performBenchmark(buildDir: Path, context: MazstabContext): Unit = {
+    val benchmarkBinaries = buildDir.toIO.listFiles
       .filter(_.canExecute)
       .filterNot(_.isDirectory)
 //    ammonite.Main().run( "benchmarks" -> benchmarks )
@@ -113,36 +118,34 @@ MazStab root: ${conf.rootDirectory()} (${mazstabRepository.getBranch})"""
       runtimeSystems += rts
     }
 
-    val experimentDirectory = conf.experimentDirectory()
-
+    val conf = context.conf
     val threads = conf.threads()
     val experimentDescription = ExperimentDescription(
       benchmarks.to(collection.immutable.Set),
       runtimeSystems.to(collection.immutable.Set),
       threads
     )
-    val experimentDescriptionYamlFile =
-      experimentDirectory.resolve("description.yaml")
 
     {
+      val experimentDescriptionYamlFile = context.getExperimentDescriptionYaml()
+
       import ExperimentDescriptionYamlProtocol._
       os.write(
-        os.Path(experimentDescriptionYamlFile),
+        experimentDescriptionYamlFile,
         experimentDescription.toYaml.prettyPrint
       )
     }
 
-    val rawDataDir = experimentDirectory.resolve("raw-data")
-    rawDataDir.toFile.mkdir()
+    val rawDataDir = context.getRawDataDirectory()
 
     for (nthreads <- threads) {
       for (benchmark <- benchmarkBinaries) {
         val benchmarkName = benchmark.getName
         val outfileBasename = benchmarkName + "--" + nthreads
 
-        val outfileCsv = rawDataDir.resolve(outfileBasename + ".csv")
-        val outfileStdout = rawDataDir.resolve(outfileBasename + ".stdout")
-        val outfileStderr = rawDataDir.resolve(outfileBasename + ".stderr")
+        val outfileCsv = rawDataDir / (outfileBasename + ".csv")
+        val outfileStdout = rawDataDir / (outfileBasename + ".stdout")
+        val outfileStderr = rawDataDir / (outfileBasename + ".stderr")
 
         // format: off
         val benchmarkCommand = immutable.Seq(
@@ -158,8 +161,8 @@ MazStab root: ${conf.rootDirectory()} (${mazstabRepository.getBranch})"""
         )
         val benchmarkProcess =
           new java.lang.ProcessBuilder(benchmarkCommand: _*)
-            .redirectOutput(outfileStdout.toFile)
-            .redirectError(outfileStderr.toFile)
+            .redirectOutput(outfileStdout.toIO)
+            .redirectError(outfileStderr.toIO)
             .start
 
         benchmarkProcess.waitFor
diff --git a/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabConf.scala b/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabConf.scala
index b2074e47af500429c1c7fd793a3e358059bdc740..da3c4faf72aa24c28d21ddb027376329ae73afec 100644
--- a/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabConf.scala
+++ b/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabConf.scala
@@ -2,7 +2,7 @@
 // Copyright © 2021 Florian Schmaus
 package de.fau.cs.mazstab
 
-import java.nio.file.Path
+import os._
 
 import org.rogach.scallop._
 
@@ -33,33 +33,29 @@ object MazstabConf {
       case _    => throw new Exception(s"Invalid problem size: ${s}")
     }
   )
+
+  val osPathConverter = singleArgConverter[Path](s => toPath(s))
 }
 
 class MazstabConf(arguments: Seq[String]) extends ScallopConf(arguments) {
   val rootDirectory = opt[Path](
     name = "mazstab-root",
     required = true,
-    default = sys.env.get("MAZSTAB_ROOT").map(s => Path.of(s))
-  )
+    default = sys.env.get("MAZSTAB_ROOT").map(s => toPath(s))
+  )(MazstabConf.osPathConverter)
 
   val experimentDirectory = opt[Path](
     default = Some(
-      rootDirectory().resolve(
-        Path
-          .of(
-            "out",
-            Mazstab.getTimestamp
-          )
-      )
+      rootDirectory() / "out" / Mazstab.getTimestamp()
     ),
     name = "experiment-dir"
-  )
+  )(MazstabConf.osPathConverter)
 
   val buildDirectory = opt[Path](
     name = "build-dir",
     descr =
       "The MazStab build directory to use. If this directory already exists and if it contains a configured MazStab build, then it will be re-used"
-  )
+  )(MazstabConf.osPathConverter)
 
   val problemSize = opt[ProblemSize.Value](
     default =
diff --git a/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabPgfPlots.scala b/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabPgfPlots.scala
index 4230ffa18626aeed04cf74055ba12cac0992cf94..681fb69b5c5c724b6f601998d98cb0bfabffc633 100644
--- a/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabPgfPlots.scala
+++ b/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabPgfPlots.scala
@@ -3,6 +3,19 @@
 package de.fau.cs.mazstab
 
 object MazstabPgfPlots {
+  val pgfPlotHeader = """
+\documentclass{standalone}
+\input{common.tex}
+
+\begin{document}
+\begin{tikzpicture}
+"""
+
+  val pgfPlotFooter = """
+\end{tikzpicture}
+\end{document}
+"""
+
   def apply(
       context: MazstabContext,
       processedResults: Map[BenchmarkRunDescriptor, BenchmarkRunResult]
diff --git a/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabPostProcess.scala b/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabPostProcess.scala
index d95b244bb4d8228ee9100661f86927b9cac18532..a7970cba305cadec0a26c3f89a2d7cf7a3b30f52 100644
--- a/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabPostProcess.scala
+++ b/benchmark-runner/main/src/de/fau/cs/mazstab/MazstabPostProcess.scala
@@ -31,8 +31,7 @@ object MazstabPostProcess {
     val processedResults =
       mutable.Map[BenchmarkRunDescriptor, BenchmarkRunResult]()
 
-    val rawDataDir = context.getRawDataDirectory
-    val csvFiles = rawDataDir.toFile.listFiles
+    val csvFiles = context.getRawDataDirectory.toIO.listFiles
       .filter(_.isFile)
       .filter(_.getName.endsWith(".csv"))
 
@@ -68,6 +67,7 @@ object MazstabPostProcess {
       if (!previous.isEmpty) throw new Exception()
     }
 
+    // Convert mutable map to immutable using 'toMap'.
     processedResults.toMap
   }
 
diff --git a/benchmark-runner/main/src/de/fau/cs/mazstab/package.scala b/benchmark-runner/main/src/de/fau/cs/mazstab/package.scala
index a7d04eff52b714c7accdf780f84c55b340720605..577aecb51f3164e56e0e85614745491f2d386d90 100644
--- a/benchmark-runner/main/src/de/fau/cs/mazstab/package.scala
+++ b/benchmark-runner/main/src/de/fau/cs/mazstab/package.scala
@@ -2,15 +2,13 @@
 // Copyright © 2021 Florian Schmaus
 package de.fau.cs
 
-import java.nio.file.Path
-
 package object mazstab {
-  def mkdirIfRequired(dir: Path): Path = {
-    val dirAsFile = dir.toFile
-    if (!dirAsFile.isDirectory) {
-      val created = dirAsFile.mkdirs
-      if (created) throw new Exception()
+  def toPath(path: String): os.Path = {
+    val nioPath = java.nio.file.Path.of(path)
+    if (nioPath.isAbsolute) {
+      os.Path(nioPath)
+    } else {
+      os.Path(nioPath, os.pwd)
     }
-    dir
   }
 }