diff --git a/NAMESPACE b/NAMESPACE
index 497e9a369bf72f481f36c72d14bb39173467f929..2a20d65225811de469c0c720bdab9a40687b41e4 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -51,6 +51,7 @@ export(PP.rate.terminalBonus)
 export(PP.rate.terminalBonusFund)
 export(PP.rate.totalInterest)
 export(PP.rate.totalInterest2)
+export(PVfactory)
 export(PaymentTimeEnum)
 export(ProfitComponentsEnum)
 export(ProfitParticipation)
diff --git a/R/HelperFunctions.R b/R/HelperFunctions.R
index 4f68bb23194392768dac4759acdac36032ff3538..ef96a531ed5a3ae0e0348f7fd1752bf5e7dc23b4 100644
--- a/R/HelperFunctions.R
+++ b/R/HelperFunctions.R
@@ -278,185 +278,348 @@ mergeValues3D = function(starting, ending, t) {
     abind::abind(starting[1:t,,,], ending[-1:-t,,,], along = 1)
   }
 }
-# Caution: px is not neccessarily 1-qx, because we might also have dread diseases so that px=1-qx-ix! However, the ix is not used for the survival present value
-calculatePVSurvival = function(px = 1 - qx, qx = 1 - px, advance = NULL, arrears = NULL, ..., m = 1, mCorrection = list(alpha = 1, beta = 0), v = 1, start = 0) {
-  init = advance[1]*0;
-  if (!any(advance != 0 ) && !any(arrears != 0)) {
-    return(init);
-  }
-  # assuming advance and arrears have the same dimensions...
-  l = max(length(qx), dim(advance)[[1]], dim(arrears)[[1]]);
-  p = pad0(px, l, value=0);
-  if (missing(advance)) {
-    advance = arrears * 0
-  }
-  if (missing(arrears)) {
-    arrears = advance * 0
-  }
-  advance = pad0(advance, l, value = init);
-  arrears = pad0(arrears, l, value = init);
-
-  # TODO: Make this work for matrices (i.e. currently advance and arrears are assumed to be one-dimensional vectors)
-  # TODO: Replace loop by better way (using Reduce?)
-  res = rep(0, l + 1);
-  advcoeff = mCorrection$alpha - mCorrection$beta*(1-p*v);
-  arrcoeff = mCorrection$alpha - (mCorrection$beta + 1/m)*(1-p*v);
-  for (i in l:(start + 1)) {
-    # coefficients for the payments (including corrections for payments during the year (using the alpha(m) and beta(m)):
-    # The actual recursion:
-    res[i] = advance[i]*advcoeff[i] + arrears[i]*arrcoeff[i] + v*p[i]*res[i+1];
-  }
-  res[1:l]
-}
-# Caution: px is not neccessarily 1-qx, because we might also have dread diseases so that px=1-qx-ix! However, the ix is not used for the survival present value
-calculatePVSurvival2D = function(px = 1 - qx, qx = 1 - px, advance = NULL, arrears = NULL, ..., m = 1, mCorrection = list(alpha = 1, beta = 0), v = 1, start = 0) {
-# browser()
-  if (!any(advance != 0 ) && !any(arrears != 0)) {
-    return(advance * 0);
-  }
-
-  l = max(length(qx), dim(advance)[[1]], dim(arrears)[[1]]);
-  p = pad0(px, l, value=0);
-  if (missing(advance)) {
-    advance = arrears * 0
-  }
-  if (missing(arrears)) {
-    arrears = advance * 0
-  }
-  # assuming advance and arrears have the same dimensions...
-  advance = padArray(advance, len = l);
-  arrears = padArray(arrears, len = l);
-
-
-  # TODO: Make this work for arbitrary dimensions
-  # TODO: Replace loop by better way (using Reduce?)
-  res = advance * 0;
-  advcoeff = mCorrection$alpha - mCorrection$beta*(1-p*v);
-  arrcoeff = mCorrection$alpha - (mCorrection$beta + 1/m)*(1-p*v);
-  for (i in (l-1):start) {
-    # coefficients for the payments (including corrections for payments during the year (using the alpha(m) and beta(m)):
-    # The actual recursion:
-    res[i,] = advance[i,]*advcoeff[i] + arrears[i,]*arrcoeff[i] + v*p[i]*res[i+1,];
-  }
-  res[1:l,]
-}
-
-
-calculatePVGuaranteed = function(advance, arrears = c(0), ..., m = 1, mCorrection = list(alpha = 1, beta = 0), v = 1, start = 0) {
-  # assuming advance and arrears have the same dimensions...
-  init = advance[1]*0;
-  if (!any(advance != 0 ) && !any(arrears != 0)) {
-    return(init);
-  }
-  l = max(length(advance), length(arrears));
-  advance = pad0(advance, l, value = init);
-  arrears = pad0(arrears, l, value = init);
-
-  # TODO: Make this work for matrices (i.e. currently advance and arrears are assumed to be one-dimensional vectors)
-  # TODO: Replace loop by better way (using Reduce?)
-  res = rep(0, l + 1);
-  advcoeff = mCorrection$alpha - mCorrection$beta * (1 - v);
-  arrcoeff = mCorrection$alpha - (mCorrection$beta + 1 / m) * (1 - v);
-  for (i in l:(start + 1)) {
-    # coefficients for the payments (including corrections for payments during the year (using the alpha(m) and beta(m)):
-    # The actual recursion:
-    res[i] = advance[i]*advcoeff + arrears[i]*arrcoeff + v*res[i + 1];
-  }
-  res[1:l]
-}
 
-
-calculatePVDeath = function(px, qx, benefits, ..., v = 1, start = 0) {
-  init = benefits[1]*0; # Preserve the possible array structure of the benefits -> vectorized calculations possible!
-  if (!any(benefits != 0 )) {
-    return(init);
-  }
-  l = max(length(qx), length(benefits));
-  q = pad0(qx, l, value = 1);
-  p = pad0(px, l, value = 0);
-  benefits = pad0(benefits, l, value = init);
-
-  # TODO: Make this work for matrices (i.e. currently benefits are assumed to be one-dimensional vectors)
-  # TODO: Replace loop by better way (using Reduce?)
-  res = rep(init, l + 1);
-  for (i in l:(start + 1)) {
-    # Caution: p_x is not neccessarily 1-q_x, because we might also have dread diseases, so that px=1-qx-ix!
-    res[i] = v * q[i] * benefits[i] + v * p[i] * res[i + 1];
-  }
-  res[1:l]
-}
-
-calculatePVDisease = function(px = 1 - qx - ix, qx = 1 - ix - px, ix = 1 - px - qx, benefits, ..., v = 1, start = 0) {
-  init = benefits[1] * 0;
-  if (!any(benefits != 0 )) {
-    return(init);
-  }
-  l = min(length(ix), length(qx), length(benefits));
-  qx = pad0(qx, l, value = 1);
-  ix = pad0(ix, l, value = 0);
-  px = pad0(px, l, value = 0);
-  benefits = pad0(benefits, l, value = init);
-
-  # TODO: Make this work for matrices (i.e. currently benefits are assumed to be one-dimensional vectors)
-  # TODO: Replace loop by better way (using Reduce?)
-  res = rep(init, l + 1);
-  for (i in l:(start + 1)) {
-    res[i] = v * ix[i] * benefits[i] + v * px[i] * res[i + 1];
-  }
-  res[1:l]
-}
-
-# TODO: So far, we are assuming, the costs array has sufficient time steps and does not need to be padded!
-calculatePVCosts = function(px = 1 - qx, qx = 1 - px, costs, ..., v = 1, start = 0) {
-  l = max(length(qx), dim(costs)[1]);
-  p = pad0(px, l, value = 0);
-  costs = costs[1:l,,,];
-
-  # NOTICE: Costs that apply to benefits are a special case and cannot be handled
-  # here. Instead, their PV is calculated like the death/survival/invalidity benefits
-  # in the tariff's PV calculation function. The values calculated here automatically
-  # WILL be ignored
-
-  # Take the array structure from the cash flow array and initialize it with 0
-  res = costs*0;
-
-  # only calculate the loop if we have any relevant cost parameter != 0...
-
-  # survival cash flows (until death)
-  if (any(costs[,,,"survival"] != 0)) {
-    prev = res[1,,,"survival"]*0;
-    # Backward recursion starting from the last time:
-    # Survival Cash flows
-    for (i in l:(start + 1)) {
-      # cat("values at iteration ", i, ": ", v, q[i], costs[i,,], prev);
-      res[i,,,"survival"] = costs[i,,,"survival"] + v*p[i]*prev;
-      prev = res[i,,,"survival"];
-    }
-  }
-  # guaranteed cash flows (even after death)
-  if (any(costs[,,,"guaranteed"] != 0)) {
-    prev = res[1,,,"guaranteed"]*0;
-    for (i in l:(start + 1)) {
-      res[i,,,"guaranteed"] = costs[i,,,"guaranteed"] + v*prev;
-      prev = res[i,,,"guaranteed"];
-    }
-  }
-  # Cash flows only after death
-  # This case is more complicated, as we have two possible states of
-  # payments (present value conditional on active, but payments only when
-  # dead => need to write the Thiele difference equations as a pair of
-  # recursive equations rather than a single recursive formula...)
-  if (any(costs[,,,"after.death"] != 0)) {
-    prev = res[1,,,"after.death"]*0;
-    prev.dead = res[1,,,1]*0
-    for (i in l:(start + 1)) {
-      res[i,,,"after.death"] = p[i] * v * prev + (1 - p[i]) * v * prev.dead
-      prev = res[i,,,"after.death"]
-      prev.dead = costs[i,,,"after.death"] + v * prev.dead
+# PVfactory (R6Class for present values with arbitrary dimensions) ####
+#' @export
+PVfactory = R6Class(
+  "PVfactory",
+
+  ######################### PUBLIC METHODS ################################# #
+  public  = list(
+    initialize = function(qx, m = 1, mCorrection = list(alpha = 1, beta = 0), v = 1) {
+      private$qx = qx;
+      private$m = m;
+      private$mCorrection = mCorrection;
+      private$v = v;
+    },
+    guaranteed = function(advance = NULL, arrears = NULL, start = 0, ..., m = private$m, mCorrection = private$mCorrection, v = private$v) {
+      # General Note: Since the CF vectors can have an arbitrary number of
+      # dimensions, we cannot directly access them via advance[1,..]. Rather,
+      # we have to construct the `[` function manually as a quoted expression,
+      # inserting the required number of dimensions and then evaluating that
+      # expression. This makes this function a little harder to read, but the
+      # performance should not take a hit and it is implemented in a very
+      # general way.
+
+      cfs = list(advance, arrears)
+      # https://stackoverflow.com/a/16896422/920231
+      deflt = cfs[!unlist(lapply(cfs, is.null))][[1]] * 0
+
+      if (missing(advance)     || is.null(advance))     advance = deflt
+      if (missing(arrears)     || is.null(arrears))     arrears = deflt
+
+      l = max(unlist(lapply(cfs, function(cf) if(!is.null(dim(cf))) dim(cf)[[1]] else length(cf))))
+
+      # TODO: Make sure all CF tensors have the same number of dimensions
+      dms = if (is.null(dim(advance))) length(advance + 1) else dim(advance);
+
+      # Resulting PV tensor has one timestep more than the CF tensors!
+      dms[1] = dms[1] + 1
+      dmNr = if (is.null(dim(advance))) 1 else length(dim(advance))
+
+      # To be able to access the CF tensors in arbitrary dimensions, we
+      # construct the [..] operator manually by quoting it and then inserting
+      # arguments matching the number of dimensions
+      Qadv     = Quote(advance[]    )[c(1,2,rep(3, dmNr))];
+      Qarr     = Quote(arrears[]    )[c(1,2,rep(3, dmNr))];
+      Qres     = Quote(res[])[c(1,2,rep(3, dmNr))]; # Access the correct number of dimensions
+      QresAss  = Quote(res <- tmp)
+
+      VL = function(quoted, time) {
+        eval(quoted %>% `[<-`(3, time))
+      }
+
+      init = VL(Qadv, 1) * 0;
+
+      # assuming advance and arrears have the same dimensions...
+      # TODO: Pad to length l
+      # advance = pad0(advance, l, value = init);
+      # arrears = pad0(arrears, l, value = init);
+
+      # TODO: Replace loop by better way (using Reduce?)
+      res = array(0, dim = dms)
+
+      # Starting value for the recursion:
+      tmp = init
+      QresAss[[2]] = Qres %>% `[<-`(3, dms[[1]])
+      eval(QresAss)
+
+      advcoeff = mCorrection$alpha - mCorrection$beta * (1 - v);
+      arrcoeff = mCorrection$alpha - (mCorrection$beta + 1/m) * ( 1 - v);
+      for (i in l:(start + 1)) {
+        # coefficients for the payments (including corrections for payments during the year (using the alpha(m) and beta(m)):
+        # The actual recursion:
+        tmp = VL(Qadv, i) * advcoeff + VL(Qarr, i) * arrcoeff + v * VL(Qres, i+1);
+        # Assign tmp to the slice res[i, ....]
+        QresAss[[2]] = Qres %>% `[<-`(3, i)
+        eval(QresAss)
+      }
+      VL(Qres, list(1:l))
+    },
+
+    survival = function(advance = NULL, arrears = NULL, start = 0, ..., m = private$m, mCorrection = private$mCorrection, v = private$v) {
+      # General Note: Since the CF vectors can have an arbitrary number of
+      # dimensions, we cannot directly access them via advance[1,..]. Rather,
+      # we have to construct the `[` function manually as a quoted expression,
+      # inserting the required number of dimensions and then evaluating that
+      # expression. This makes this function a little harder to read, but the
+      # performance should not take a hit and it is implemented in a very
+      # general way.
+
+      cfs = list(advance, arrears)
+      # https://stackoverflow.com/a/16896422/920231
+      deflt = cfs[!unlist(lapply(cfs, is.null))][[1]] * 0
+
+      if (missing(advance)     || is.null(advance))     advance = deflt
+      if (missing(arrears)     || is.null(arrears))     arrears = deflt
+
+      l = max(unlist(lapply(cfs, function(cf) if(!is.null(dim(cf))) dim(cf)[[1]] else length(cf))))
+
+      # TODO: Make sure all CF tensors have the same number of dimensions
+      dms = if (is.null(dim(advance))) length(advance) else dim(advance);
+
+      # Resulting PV tensor has one timestep more than the CF tensors!
+      dms[1] = dms[1] + 1
+      dmNr = if (is.null(dim(advance))) 1 else length(dim(advance))
+
+      # To be able to access the CF tensors in arbitrary dimensions, we
+      # construct the [..] operator manually by quoting it and then inserting
+      # arguments matching the number of dimensions
+      Qadv     = Quote(advance[]    )[c(1,2,rep(3, dmNr))];
+      Qarr     = Quote(arrears[]    )[c(1,2,rep(3, dmNr))];
+      Qres     = Quote(res[])[c(1,2,rep(3, dmNr))]; # Access the correct number of dimensions
+      QresAss  = Quote(res <- tmp)
+
+      VL = function(quoted, time) {
+        eval(quoted %>% `[<-`(3, time))
+      }
+
+      init = VL(Qadv, 1) * 0;
+
+      # assuming advance and arrears have the same dimensions...
+      p = pad0(private$qx$px, l, value=0);
+      # TODO: Pad to length l
+      # advance = pad0(advance, l, value = init);
+      # arrears = pad0(arrears, l, value = init);
+
+      # TODO: Replace loop by better way (using Reduce?)
+      res = array(0, dim = dms)
+
+      # Starting value for the recursion:
+      tmp = init
+      QresAss[[2]] = Qres %>% `[<-`(3, dms[[1]])
+      eval(QresAss)
+
+      advcoeff = mCorrection$alpha - mCorrection$beta * (1 - p * v);
+      arrcoeff = mCorrection$alpha - (mCorrection$beta + 1/m) * (1 - p * v);
+      for (i in l:(start + 1)) {
+        # coefficients for the payments (including corrections for payments during the year (using the alpha(m) and beta(m)):
+        # The actual recursion:
+        tmp = VL(Qadv, i) * advcoeff[i] + VL(Qarr, i) * arrcoeff[i] + v * p[i] * VL(Qres, i+1);
+        # Assign tmp to the slice res[i, ....]
+        QresAss[[2]] = Qres %>% `[<-`(3, i)
+        eval(QresAss)
+      }
+      VL(Qres, list(1:l))
+    },
+
+    death = function(benefits, start = 0, ..., v = private$v) {
+      # General Note: Since the CF vectors can have an arbitrary number of
+      # dimensions, we cannot directly access them via advance[1,..]. Rather,
+      # we have to construct the `[` function manually as a quoted expression,
+      # inserting the required number of dimensions and then evaluating that
+      # expression. This makes this function a little harder to read, but the
+      # performance should not take a hit and it is implemented in a very
+      # general way.
+
+      cfs = list(benefits)
+      if (missing(benefits) || is.null(benefits)) return(0);
+
+      l = max(unlist(lapply(cfs, function(cf) if(!is.null(dim(cf))) dim(cf)[[1]] else length(cf))))
+
+      # TODO: Make sure all CF tensors have the same number of dimensions
+      dms = if (is.null(dim(benefits))) length(benefits) else dim(benefits);
+
+      # Resulting PV tensor has one timestep more than the CF tensors!
+      dms[1] = dms[1] + 1
+      dmNr = if (is.null(dim(benefits))) 1 else length(dim(benefits))
+
+      # To be able to access the CF tensors in arbitrary dimensions, we
+      # construct the [..] operator manually by quoting it and then inserting
+      # arguments matching the number of dimensions
+      Qben     = Quote(benefits[]    )[c(1,2,rep(3, dmNr))];
+      Qres     = Quote(res[])[c(1,2,rep(3, dmNr))]; # Access the correct number of dimensions
+      QresAss  = Quote(res <- tmp)
+
+      VL = function(quoted, time) {
+        eval(quoted %>% `[<-`(3, time))
+      }
+
+      init = VL(Qben, 1) * 0;
+
+      # assuming advance and arrears have the same dimensions...
+      p = pad0(private$qx$px, l, value = 0);
+      q = pad0(private$qx$qx, l, value = 1);
+      # TODO: Pad to length l
+      # benefits = pad0(benefits, l, value = init);
+
+      # TODO: Replace loop by better way (using Reduce?)
+      res = array(0, dim = dms)
+
+      # Starting value for the recursion:
+      tmp = init
+      QresAss[[2]] = Qres %>% `[<-`(3, dms[[1]])
+      eval(QresAss)
+
+      for (i in l:(start + 1)) {
+        # coefficients for the payments (including corrections for payments during the year (using the alpha(m) and beta(m)):
+        # The actual recursion:
+        tmp = v * q[i] * VL(Qben, i) + v * p[i] * VL(Qres, i+1);
+        # Assign tmp to the slice res[i, ....]
+        QresAss[[2]] = Qres %>% `[<-`(3, i)
+        eval(QresAss)
+      }
+      VL(Qres, list(1:l))
+    },
+    disease = function(benefits, start = 0, ..., v = private$v) {
+      # General Note: Since the CF vectors can have an arbitrary number of
+      # dimensions, we cannot directly access them via advance[1,..]. Rather,
+      # we have to construct the `[` function manually as a quoted expression,
+      # inserting the required number of dimensions and then evaluating that
+      # expression. This makes this function a little harder to read, but the
+      # performance should not take a hit and it is implemented in a very
+      # general way.
+
+      cfs = list(benefits)
+      if (missing(benefits) || is.null(benefits)) return(0);
+
+      l = max(unlist(lapply(cfs, function(cf) if(!is.null(dim(cf))) dim(cf)[[1]] else length(cf))))
+
+      # TODO: Make sure all CF tensors have the same number of dimensions
+      dms = if (is.null(dim(benefits))) length(benefits) else dim(benefits);
+
+      # Resulting PV tensor has one timestep more than the CF tensors!
+      dms[1] = dms[1] + 1
+      dmNr = if (is.null(dim(benefits))) 1 else length(dim(benefits))
+
+      # To be able to access the CF tensors in arbitrary dimensions, we
+      # construct the [..] operator manually by quoting it and then inserting
+      # arguments matching the number of dimensions
+      Qben     = Quote(benefits[]    )[c(1,2,rep(3, dmNr))];
+      Qres     = Quote(res[])[c(1,2,rep(3, dmNr))]; # Access the correct number of dimensions
+      QresAss  = Quote(res <- tmp)
+
+      VL = function(quoted, time) {
+        eval(quoted %>% `[<-`(3, time))
+      }
+
+      init = VL(Qben, 1) * 0;
+
+      # assuming advance and arrears have the same dimensions...
+      p = pad0(private$qx$px, l, value = 0);
+      ix = pad0(private$qx$ix, l, value = 0);
+      # TODO: Pad to length l
+      # benefits = pad0(benefits, l, value = init);
+
+      # TODO: Replace loop by better way (using Reduce?)
+      res = array(0, dim = dms)
+
+      # Starting value for the recursion:
+      tmp = init
+      QresAss[[2]] = Qres %>% `[<-`(3, dms[[1]])
+      eval(QresAss)
+
+      for (i in l:(start + 1)) {
+        # coefficients for the payments (including corrections for payments during the year (using the alpha(m) and beta(m)):
+        # The actual recursion:
+        tmp = v * ix[i] * VL(Qben, i) + v * p[i] * VL(Qres, i+1);
+        # Assign tmp to the slice res[i, ....]
+        QresAss[[2]] = Qres %>% `[<-`(3, i)
+        eval(QresAss)
+      }
+      VL(Qres, list(1:l))
+    },
+    # Cash flows only after death
+    # This case is more complicated, as we have two possible states of
+    # payments (present value conditional on active, but payments only when
+    # dead => need to write the Thiele difference equations as a pair of
+    # recursive equations rather than a single recursive formula...)
+    afterDeath = function(advance = NULL, arrears = NULL, start = 0, ..., m = private$m, mCorrection = private$mCorrection, v = private$v) {
+      # General Note: Since the CF vectors can have an arbitrary number of
+      # dimensions, we cannot directly access them via advance[1,..]. Rather,
+      # we have to construct the `[` function manually as a quoted expression,
+      # inserting the required number of dimensions and then evaluating that
+      # expression. This makes this function a little harder to read, but the
+      # performance should not take a hit and it is implemented in a very
+      # general way.
+
+      cfs = list(advance, arrears)
+      # https://stackoverflow.com/a/16896422/920231
+      deflt = cfs[!unlist(lapply(cfs, is.null))][[1]] * 0
+
+      if (missing(advance)     || is.null(advance))     advance = deflt
+      if (missing(arrears)     || is.null(arrears))     arrears = deflt
+
+      l = max(unlist(lapply(cfs, function(cf) if(!is.null(dim(cf))) dim(cf)[[1]] else length(cf))))
+
+      # TODO: Make sure all CF tensors have the same number of dimensions
+      dms = if (is.null(dim(advance))) length(advance) else dim(advance);
+
+      # Resulting PV tensor has one timestep more than the CF tensors!
+      dms[1] = dms[1] + 1
+      dmNr = if (is.null(dim(advance))) 1 else length(dim(advance))
+
+      # To be able to access the CF tensors in arbitrary dimensions, we
+      # construct the [..] operator manually by quoting it and then inserting
+      # arguments matching the number of dimensions
+      Qadv     = Quote(advance[]    )[c(1,2,rep(3, dmNr))];
+      Qarr     = Quote(arrears[]    )[c(1,2,rep(3, dmNr))];
+      Qres     = Quote(res[])[c(1,2,rep(3, dmNr))]; # Access the correct number of dimensions
+      QresAss  = Quote(res <- tmp)
+
+      VL = function(quoted, time) {
+        eval(quoted %>% `[<-`(3, time))
+      }
+
+      init = VL(Qadv, 1) * 0;
+
+      # assuming advance and arrears have the same dimensions...
+      p = pad0(private$qx$px, l, value=0);
+      q = pad0(private$qx$qx, l, value=0);
+      # TODO: Pad to length l
+      # advance = pad0(advance, l, value = init);
+      # arrears = pad0(arrears, l, value = init);
+
+      # TODO: Replace loop by better way (using Reduce?)
+      res = array(0, dim = dms)
+
+      # Starting value for the recursion:
+      prev = init;
+      prev.dead = init;
+      QresAss[[2]] = Qres %>% `[<-`(3, dms[[1]])
+      eval(QresAss)
+
+      advcoeff = mCorrection$alpha - mCorrection$beta * (1 - v);
+      arrcoeff = mCorrection$alpha - (mCorrection$beta + 1/m) * (1 - v);
+      for (i in l:(start + 1)) {
+        # The actual recursion:
+        tmp = p[i] * v * prev + q[i] * v * prev.dead;
+        # Assign tmp to the slice res[i, ....]
+        QresAss[[2]] = Qres %>% `[<-`(3, i)
+        eval(QresAss)
+        prev = tmp
+        prev.dead = VL(Qadv, i) * advcoeff[i] + VL(Qarr, i) * arrcoeff[i] + v * prev.dead;
+      }
+      VL(Qres, list(1:l))
     }
-  }
-  res
-}
+  ),
+  private = list(
+    qx = data.frame(Alter = c(), qx = c(), ix = c(), px = c()),
+    m = 1,
+    mCorrection = list(alpha = 1, beta = 0),
+    v = 1
+  )
+);
 
 
 
diff --git a/R/InsuranceParameters.R b/R/InsuranceParameters.R
index 72c8f1916dfa0538c898ddfa89cbf17f73cee98a..cd44b442c0ed63d186f18c7b52daccd73eb2f4fe 100644
--- a/R/InsuranceParameters.R
+++ b/R/InsuranceParameters.R
@@ -42,7 +42,7 @@ setCost = function(costs, type, basis = "SumInsured", frequency = "PolicyPeriod"
 #' Initialize a cost matrix with dimensions: {CostType, Basis, Period}, where:
 #' \describe{
 #'     \item{CostType:}{alpha, Zillmer, beta, gamma, gamma_nopremiums, unitcosts}
-#'     \item{Basis:}{SumInsured, SumPremiums, GrossPremium, NetPremium, Constant}
+#'     \item{Basis:}{SumInsured, SumPremiums, GrossPremium, NetPremium, Benefits, Constant}
 #'     \item{Period:}{once, PremiumPeriod, PremiumFree, PolicyPeriod, CommissionPeriod}
 #' }
 #' This cost structure can then be modified for non-standard costs using the [setCost()] function.
@@ -100,8 +100,8 @@ setCost = function(costs, type, basis = "SumInsured", frequency = "PolicyPeriod"
 initializeCosts = function(costs, alpha, Zillmer, alpha.commission, beta, gamma, gamma.paidUp, gamma.premiumfree, gamma.contract, gamma.afterdeath, gamma.fullcontract, unitcosts, unitcosts.PolicyPeriod) {
     if (missing(costs)) {
         dimnm = list(
-            type = c("alpha", "Zillmer", "beta", "gamma", "gamma_nopremiums", "unitcosts"),
-            basis = c("SumInsured", "SumPremiums", "GrossPremium", "NetPremium", "Constant", "Reserve"),
+          type = c("alpha", "Zillmer", "beta", "gamma", "gamma_nopremiums", "unitcosts"),
+            basis = c("SumInsured", "SumPremiums", "GrossPremium", "NetPremium", "Benefits", "Constant", "Reserve"),
             frequency = c("once", "PremiumPeriod", "PremiumFree", "PolicyPeriod", "AfterDeath", "FullContract", "CommissionPeriod")
         );
         costs = array(
diff --git a/R/InsuranceTarif.R b/R/InsuranceTarif.R
index 4bf183ad5ad6374671c3fbbfb6e35abbbc19484e..8021a9cce975d8a3b0660fbdee942cc2a6155139 100644
--- a/R/InsuranceTarif.R
+++ b/R/InsuranceTarif.R
@@ -706,55 +706,34 @@ InsuranceTarif = R6Class(
       }
 
       qq = self$getTransitionProbabilities(params, values);
-      qx = pad0(qq$qx, values$int$l, value = 1); # After maxAge of the table, use qx=1, also after contract period, just in case
-      ix = pad0(qq$ix, values$int$l);
-      px = pad0(qq$px, values$int$l);
 
       i = params$ActuarialBases$i;
       v = 1/(1 + i);
-            benefitFreqCorr = correctionPaymentFrequency(
-              i = i, m = params$ContractData$benefitFrequency,
-              order = valueOrFunction(params$ActuarialBases$benefitFrequencyOrder, params = params, values = values));
-            premiumFreqCorr = correctionPaymentFrequency(
-              i = i, m = params$ContractData$premiumFrequency,
-              order = valueOrFunction(params$ActuarialBases$premiumFrequencyOrder, params = params, values = values));
-
-      pvRefund = calculatePVDeath(px, qx, values$cashFlows$death_GrossPremium, v = v);
-      pvRefundPast = calculatePVDeath(
-        px, qx,
-        values$cashFlows$death_Refund_past,
-        v = v) *
+      benefitFreqCorr = correctionPaymentFrequency(
+        i = i, m = params$ContractData$benefitFrequency,
+        order = valueOrFunction(params$ActuarialBases$benefitFrequencyOrder, params = params, values = values));
+      premiumFreqCorr = correctionPaymentFrequency(
+        i = i, m = params$ContractData$premiumFrequency,
+          order = valueOrFunction(params$ActuarialBases$premiumFrequencyOrder, params = params, values = values));
+
+      pvf = PVfactory$new(qx = qq, m = params$ContractData$benefitFrequency, mCorrection = benefitFreqCorr, v = v);
+
+      pvRefund = pvf$death(values$cashFlows$death_GrossPremium);
+      pvRefundPast = pvf$death(values$cashFlows$death_Refund_past) *
         (values$cashFlows[,"death_GrossPremium"] - values$cashFlows[,"premiums_advance"]);
 
       pv = cbind(
-        premiums = calculatePVSurvival(
-          px, qx,
-          values$cashFlows$premiums_advance, values$cashFlows$premiums_arrears,
-          m = params$ContractData$premiumFrequency, mCorrection = premiumFreqCorr,
-          v = v),
-        additional_capital = calculatePVSurvival(px, qx, values$cashFlows$additional_capital, 0, v = v),
-        guaranteed = calculatePVGuaranteed(
-          values$cashFlows$guaranteed_advance, values$cashFlows$guaranteed_arrears,
-          m = params$ContractData$benefitFrequency, mCorrection = benefitFreqCorr,
-          v = v),
-        survival = calculatePVSurvival(
-          px, qx,
-          values$cashFlows$survival_advance, values$cashFlows$survival_arrears,
-          m = params$ContractData$benefitFrequency, mCorrection = benefitFreqCorr,
-          v = v),
-        death_SumInsured = calculatePVDeath(
-          px, qx,
-          values$cashFlows$death_SumInsured,
-          v = v),
-        disease_SumInsured = calculatePVDisease(
-          px, qx, ix,
-          values$cashFlows$disease_SumInsured, v = v),
+        premiums = pvf$survival(values$cashFlows$premiums_advance, values$cashFlows$premiums_arrears,
+          m = params$ContractData$premiumFrequency, mCorrection = premiumFreqCorr),
+        additional_capital = pvf$survival(advance = values$cashFlows$additional_capital),
+        guaranteed = pvf$guaranteed(values$cashFlows$guaranteed_advance, values$cashFlows$guaranteed_arrears),
+        survival = pvf$survival(values$cashFlows$survival_advance, values$cashFlows$survival_arrears),
+        death_SumInsured = pvf$death(values$cashFlows$death_SumInsured),
+        disease_SumInsured = pvf$disease(values$cashFlows$disease_SumInsured),
         death_GrossPremium = pvRefund,
         death_Refund_past = pvRefundPast,
         death_Refund_future = pvRefund - pvRefundPast,
-        death_PremiumFree = calculatePVDeath(
-          px, qx,
-          values$cashFlows$death_PremiumFree, v = v)
+        death_PremiumFree = pvf$death(values$cashFlows$death_PremiumFree)
       );
 
       rownames(pv) <- pad0(rownames(qq), values$int$l);
@@ -772,10 +751,49 @@ InsuranceTarif = R6Class(
       }
       len = values$int$l;
       q = self$getTransitionProbabilities(params, values);
-      qx = pad0(q$qx, len);
-      px = pad0(q$px, len);
-      v = 1/(1 + params$ActuarialBases$i)
-      pvc = calculatePVCosts(px, qx, values$cashFlowsCosts, v = v);
+      i = params$ActuarialBases$i
+      v = 1/(1 + i)
+      pvf = PVfactory(qx = q, v = v)
+
+      costs = values$cashFlowsCosts;
+      pvc = costs * 0;
+
+      # survival cash flows (until death)
+      if (any(costs[,,,"survival"] != 0)) {
+        pvc[,,,"survival"] = pvf$survival(advance = costs[,,,"survival"]);
+      }
+      # guaranteed cash flows (even after death)
+      if (any(costs[,,,"guaranteed"] != 0)) {
+        pvc[,,,"guaranteed"] = pvf$guaranteed(advance = costs[,,,"guaranteed"]);
+      }
+
+      # Cash flows only after death
+      if (any(costs[,,,"after.death"] != 0)) {
+        pvc[,,,"after.death"] = pvf$afterDeath(advance = costs[,,,"after.death"]);
+      }
+
+      # Costs based on actual benefits need to be calculated separately
+      # (need to be recalculated with the benefit CFs multiplied by the cost
+      # rate for each type and each year). In most cases, the cost rates will be
+      # constant, but in general we can not assume this => need to re-calculate
+      if (any(values$cashFlowsCosts[,,"Benefits",] != 0)) {
+        bFreq = params$ContractData$benefitFrequency
+        benefitFreqCorr = correctionPaymentFrequency(
+          i = i, m = bFreq,
+          order = valueOrFunction(params$ActuarialBases$benefitFrequencyOrder, params = params, values = values));
+        pvfben = PVfactory$new(qx = q, m = bFreq, mCorrection = benefitFreqCorr, v = v);
+
+        cfCosts = values$cashFlowsCosts[,,"Benefits",];
+        cf = values$cashFlows;
+
+        # Guaranteed + Survival + Death cover + disease
+        pvc[,,"Benefits",] =
+          pvf$guaranteed(cf$guaranteed_advance * cfCosts, cf$guaranteed_arrears * cfCosts) +
+          pvf$survival(cf$survival_advance * cfCosts, cf$survival_arrears * cfCosts) +
+          pvf$death(cf$death_SumInsured * cfCosts) +
+          pvf$disease(cf$disease_SumInsured * cfCosts);
+      }
+
       applyHook(hook = params$Hooks$adjustPresentValuesCosts, val = pvc, params = params, values = values, presentValues = presentValues)
     },
 
@@ -1264,8 +1282,9 @@ InsuranceTarif = R6Class(
           ZillmerVerteilungCoeff = pad0((0:r)/r, len, 1);
         } else {
           q = self$getTransitionProbabilities(params, values);
+          pvf = PVfactory$new(qx = q, v = 1/(1 + params$ActuarialBases$i))
           # vector of all รค_{x+t, r-t}
-          pvAlphaTmp = calculatePVSurvival(q = pad0(q$qx, len), advance = pad0(rep(1,r), len), v = 1/(1 + params$ActuarialBases$i));
+          pvAlphaTmp = pvf$survival(advance = pad0(rep(1,r), len))
           ZillmerVerteilungCoeff = (1 - pvAlphaTmp/pvAlphaTmp[[1]]);
         }
         alphaRefund = ZillmerSoFar - ZillmerVerteilungCoeff * ZillmerTotal;
@@ -1673,8 +1692,15 @@ InsuranceTarif = R6Class(
     #' @param ... currently unused
     calculatePresentValues = function(cf, params, values) {
       len = dim(cf)[1];
-      q = self$getTransitionProbabilities(params, values)
-      calculatePVSurvival2D(px = pad0(q$px, len), advance = cf, v = 1/(1 + params$ActuarialBases$i));
+      qq = self$getTransitionProbabilities(params, values);
+      i = params$ActuarialBases$i;
+      premFreq = params$ContractData$premiumFrequency;
+      premFreqCorr = correctionPaymentFrequency(
+        i = i, m = premFreq,
+        order = valueOrFunction(params$ActuarialBases$premiumFrequencyOrder, params = params, values = values));
+
+      pvf = PVfactory$new(qx = qq, m = premFreq, mCorrection = premFreqCorr, v = 1/(1 + i));
+      pvf$survival(advance = cf)
     },
 
         #' @description Calculate the premium frequency loading, i.e. the surcharge
diff --git a/man/initializeCosts.Rd b/man/initializeCosts.Rd
index ae23aba2dd933c550787b274c369ddbbfea758f8..372e32576ed7eae2f06c93ae3f44c60c0738b870 100644
--- a/man/initializeCosts.Rd
+++ b/man/initializeCosts.Rd
@@ -58,7 +58,7 @@ even if the insured has already dies (for term-fix insurances)}
 Initialize a cost matrix with dimensions: {CostType, Basis, Period}, where:
 \describe{
 \item{CostType:}{alpha, Zillmer, beta, gamma, gamma_nopremiums, unitcosts}
-\item{Basis:}{SumInsured, SumPremiums, GrossPremium, NetPremium, Constant}
+\item{Basis:}{SumInsured, SumPremiums, GrossPremium, NetPremium, Benefits, Constant}
 \item{Period:}{once, PremiumPeriod, PremiumFree, PolicyPeriod, CommissionPeriod}
 }
 This cost structure can then be modified for non-standard costs using the \code{\link[=setCost]{setCost()}} function.
diff --git a/tests/testthat/test-PVfactory.R b/tests/testthat/test-PVfactory.R
new file mode 100644
index 0000000000000000000000000000000000000000..82385867eb7216cefdb7b4dad62d364b7874cdbc
--- /dev/null
+++ b/tests/testthat/test-PVfactory.R
@@ -0,0 +1,90 @@
+test_that("multiplication works", {
+    library(MortalityTables)
+    mortalityTables.load("Austria_Census")
+
+    # Sample data: Austrian population mortality, interest 3%
+    ags = 40:100
+    v = 1/1.03
+    qx = deathProbabilities(mort.AT.census.2011.unisex, ages = ags)
+    qq = data.frame(x = ags, qx = qx, ix = 0, px = 1 - qx)
+    pvf = PVfactory$new(qx = qq, m = 1, v = v)
+    # For invalidity, simply use half the death prob (split qx into qx and ix, leave px unchanged!)
+    qqI = qq
+    qqI$ix = qqI$qx/2
+    qqI$qx = qqI$ix
+    pvfI = PVfactory$new(qx = qqI, m = 1, v = v)
+
+    #################################### #
+    # Cash Flow Definitions
+    #################################### #
+    # Single payment after 10 years
+    cf1 = c(rep(0, 10), 1)
+    # Annual payments for 10 years
+    cfN = rep(1, 10)
+
+    #################################### #
+    # Guaranteed  PV
+    #################################### #
+    PV1adv = v^(10:0)
+    PV1arr = v^(11:1)
+    PVNadv = (1 - v^(10:1))/(1-v)
+    PVNarr = v * PVNadv
+
+    # Check basic present values for correctness (one-dimensional CF vector)
+    expect_equal(as.vector(pvf$guaranteed(advance = cf1)), PV1adv)
+    expect_equal(as.vector(pvf$guaranteed(arrears = cf1)), PV1arr)
+    expect_equal(as.vector(pvf$guaranteed(advance = cfN)), PVNadv)
+    expect_equal(as.vector(pvf$guaranteed(arrears = cfN)), PVNarr)
+
+    # Same cash flows, either understood paid in advance at next timestep or in arrears of previous timestep => same present values
+    expect_equal(c(pvf$guaranteed(advance = c(1,cfN))), 1 + c(pvf$guaranteed(arrears = cfN), 0))
+
+    # Check cash flow arrays
+    # PV of single payment is v^(n-t), PV of annuity is (1-v^(n-t))/(1-v)
+    # Use CF array with those two cash flows
+    cf2d = array(c(cf1, cfN, 0), dim = c(length(cf1),2))
+    expect_equal(pvf$guaranteed(advance = cf2d), array(c(PV1adv, PVNadv, 0), dim = c(length(cf1), 2)))
+
+    # two-dimensional cashflows at each time => 3-dimensional tensor
+    cf3d = array(c(cf1, cfN, 0, cf1, cfN, 0, cfN, 0, cf1), dim = c(length(cf1), 2, 3))
+    expect_equal(pvf$guaranteed(advance = cf3d), array(c(PV1adv, PVNadv, 0, PV1adv, PVNadv, 0, PVNadv, 0, PV1adv), dim = c(length(cf1), 2, 3)))
+
+
+
+    #################################### #
+    # Survival PV
+    #################################### #
+    PV1adv.sv = Reduce(`*`, 1-qx[1:10], init = 1, right = TRUE, accumulate = TRUE) * (v^(10:0))
+    PV1arr.sv = head(Reduce(`*`, 1-qx[1:11], init = 1, right = TRUE, accumulate = TRUE), -1) * (v^(11:1))
+    PVNadv.sv = head(Reduce(function(p, pv1) {pv1 * v * p + 1}, 1-qx[1:10], init = 0, right = TRUE, accumulate = TRUE), -1)
+    PVNarr.sv = head(Reduce(function(p, pv1) { v * p * (1 + pv1)}, 1-qx[1:10], init = 0, right = TRUE, accumulate = TRUE), -1)
+
+    # check basic PV
+    expect_equal(as.vector(pvf$survival(advance = cf1)), PV1adv.sv)
+    expect_equal(as.vector(pvf$survival(arrears = cf1)), PV1arr.sv)
+    expect_equal(as.vector(pvf$survival(advance = cfN)), PVNadv.sv)
+    expect_equal(as.vector(pvf$survival(arrears = cfN)), PVNarr.sv)
+
+    # Check cash flow arrays
+    expect_equal(pvf$survival(advance = cf2d), array(c(PV1adv.sv, PVNadv.sv, 0), dim = c(length(cf1), 2)))
+    expect_equal(pvf$survival(arrears = cf2d), array(c(PV1arr.sv, PVNarr.sv, 0), dim = c(length(cf1), 2)))
+
+    # two-dimensional cashflows at each time => 3-dimensional tensor
+    expect_equal(pvf$survival(advance = cf3d), array(c(PV1adv.sv, PVNadv.sv, 0, PV1adv.sv, PVNadv.sv, 0, PVNadv.sv, 0, PV1adv.sv), dim = c(length(cf1), 2, 3)))
+    expect_equal(pvf$survival(arrears = cf3d), array(c(PV1arr.sv, PVNarr.sv, 0, PV1arr.sv, PVNarr.sv, 0, PVNarr.sv, 0, PV1arr.sv), dim = c(length(cf1), 2, 3)))
+
+
+
+    #################################### #
+    # Death PV
+    #################################### #
+    PVN.death = head(Reduce(function(q, pv1) {q * v + (1 - q) * v * pv1}, qx[1:10], init = 0, right = TRUE, accumulate = TRUE), -1)
+    expect_equal(as.vector(pvf$death(benefits = cfN)), PVN.death)
+
+    #################################### #
+    # Disease PV
+    #################################### #
+    # Death and disease probabilities are equal, so the PV should be equal. If death() is implemented correctly, this can detect errors in
+    expect_equal(pvfI$disease(benefits = cfN), pvfI$death(benefits = cfN))
+
+})