diff --git a/steps/ApplyBeam.cc b/steps/ApplyBeam.cc
index 605c472062003e95ab6481b30b9ef5b9184b3079..528d227554e89d6cded8ec15279e8440403e0b90 100644
--- a/steps/ApplyBeam.cc
+++ b/steps/ApplyBeam.cc
@@ -48,6 +48,152 @@ using dp3::base::DPBuffer;
 using dp3::base::DPInfo;
 using dp3::common::operator<<;
 
+namespace {
+
+// ApplyBeam is templated on the type of the data, could be complex<double>
+// or complex<float>
+template <typename T>
+void ApplyBeamToData(const DPInfo& info, const size_t n_stations, T* data0,
+                     float* weight0, std::vector<aocommon::MC2x2>& beam_values,
+                     bool doUpdateWeights) {
+  /*
+    Applies the beam to each baseline and each frequency of the
+    model patch
+  */
+  const size_t n_channels = info.chanFreqs().size();
+  const size_t n_baselines = info.nbaselines();
+
+  // Apply beam for channel ch on all baselines
+  // For mode=ARRAY_FACTOR, too much work is done here
+  // because we know that r and l are diagonal
+  for (size_t bl = 0; bl < n_baselines; ++bl) {
+    // If the beam is the same for all stations (i.e. when n_stations = 1),
+    // all baselines will have the same beam values
+    size_t index_left = (n_stations == 1 ? 0 : n_channels * info.getAnt1()[bl]);
+    size_t index_right =
+        (n_stations == 1 ? 0 : n_channels * info.getAnt2()[bl]);
+    for (size_t ch = 0; ch < n_channels; ++ch) {
+      T* data = data0 + bl * 4 * n_channels + ch * 4;
+      const aocommon::MC2x2F mat(data);
+
+      const aocommon::MC2x2F left(beam_values[index_left + ch]);
+      const aocommon::MC2x2F right(beam_values[index_right + ch]);
+      const aocommon::MC2x2F result = left * mat.MultiplyHerm(right);
+      result.AssignTo(data);
+      if (doUpdateWeights) {
+        dp3::steps::ApplyCal::ApplyWeights(
+            left, right, weight0 + bl * 4 * n_channels + ch * 4);
+      }
+    }
+  }
+}
+
+template <typename T>
+void ApplyBeamToDataAndAdd(const DPInfo& info, const size_t n_stations,
+                           T* data0, T* model_data, float* weight0,
+                           std::vector<aocommon::MC2x2>& beam_values,
+                           bool doUpdateWeights) {
+  /*
+    Applies the beam to each baseline and each frequency of the
+    model patch and sum the contribution to the model data
+  */
+  const size_t n_channels = info.chanFreqs().size();
+  const size_t n_baselines = info.nbaselines();
+
+  // Apply beam for channel ch on all baselines
+  // For mode=ARRAY_FACTOR, too much work is done here
+  // because we know that r and l are diagonal
+  for (size_t bl = 0; bl < n_baselines; ++bl) {
+    // If the beam is the same for all stations (i.e. when n_stations = 1),
+    // all baselines will have the same beam values
+    const size_t index_left =
+        (n_stations == 1 ? 0 : n_channels * info.getAnt1()[bl]);
+    const size_t index_right =
+        (n_stations == 1 ? 0 : n_channels * info.getAnt2()[bl]);
+    for (size_t ch = 0; ch < n_channels; ++ch) {
+      T* data_ptr = data0 + bl * 4 * n_channels + ch * 4;
+      T* model_data_ptr = model_data + bl * 4 * n_channels + ch * 4;
+      const aocommon::MC2x2F data(data_ptr);
+      const aocommon::MC2x2F model_data(model_data_ptr);
+
+      const aocommon::MC2x2F left(beam_values[index_left + ch]);
+      const aocommon::MC2x2F right(beam_values[index_right + ch]);
+      const aocommon::MC2x2F result =
+          model_data + left * data.MultiplyHerm(right);
+      result.AssignTo(model_data_ptr);
+      if (doUpdateWeights) {
+        dp3::steps::ApplyCal::ApplyWeights(
+            left, right, weight0 + bl * 4 * n_channels + ch * 4);
+      }
+    }
+  }
+}
+
+size_t ComputeBeam(const DPInfo& info, double time,
+                   const everybeam::vector3r_t& srcdir,
+                   const everybeam::telescope::Telescope* telescope,
+                   std::vector<aocommon::MC2x2>& beam_values, bool invert,
+                   everybeam::CorrectionMode mode, std::mutex* mutex) {
+  /*
+    Compute the beam values for each station in a specific direction
+    and store them into beam_values
+
+    For convenience it returns the number of stations the beam
+    was computed for.
+  */
+  const size_t n_channels = info.chanFreqs().size();
+
+  std::unique_ptr<everybeam::pointresponse::PointResponse> point_response =
+      telescope->GetPointResponse(time);
+
+  const std::vector<size_t> station_indices =
+      dp3::base::SelectStationIndices(*telescope, info.antennaNames());
+
+  const size_t n_stations = station_indices.size();
+
+  // Apply the beam values of both stations to the ApplyBeamed data.
+  for (size_t ch = 0; ch < n_channels; ++ch) {
+    switch (mode) {
+      case everybeam::CorrectionMode::kFull:
+      case everybeam::CorrectionMode::kElement:
+        // Fill beam_values for channel ch
+        for (size_t st = 0; st < n_stations; ++st) {
+          beam_values[n_channels * st + ch] = point_response->Response(
+              mode, station_indices[st], info.chanFreqs()[ch], srcdir, mutex);
+          if (invert) {
+            // Terminate if the matrix is not invertible.
+            [[maybe_unused]] bool status =
+                beam_values[n_channels * st + ch].Invert();
+            assert(status);
+          }
+        }
+        break;
+      case everybeam::CorrectionMode::kArrayFactor: {
+        aocommon::MC2x2 af_tmp;
+        // Fill beam_values for channel ch
+        for (size_t st = 0; st < n_stations; ++st) {
+          af_tmp = point_response->Response(
+              mode, station_indices[st], info.chanFreqs()[ch], srcdir, mutex);
+
+          if (invert) {
+            af_tmp[0] = 1. / af_tmp[0];
+            af_tmp[3] = 1. / af_tmp[3];
+          }
+          beam_values[n_channels * st + ch] = af_tmp;
+        }
+        break;
+      }
+      case everybeam::CorrectionMode::kNone:  // this should not happen
+        for (size_t st = 0; st < n_stations; ++st) {
+          beam_values[n_channels * st + ch] = aocommon::MC2x2::Unity();
+        }
+        break;
+    }
+  }
+  return n_stations;
+}
+}  // namespace
+
 namespace dp3 {
 namespace steps {
 
@@ -265,8 +411,8 @@ void ApplyBeam::finish() {
   getNextStep()->finish();
 }
 
-// applyBeam is templated on the type of the data, could be complex<double> or
-// complex<float>
+// applyBeam is templated on the type of the data, could be complex<double>
+// or complex<float>
 template <typename T>
 void ApplyBeam::applyBeam(const DPInfo& info, double time, T* data0,
                           float* weight0, const everybeam::vector3r_t& srcdir,
@@ -274,81 +420,43 @@ void ApplyBeam::applyBeam(const DPInfo& info, double time, T* data0,
                           std::vector<aocommon::MC2x2>& beam_values,
                           bool invert, everybeam::CorrectionMode mode,
                           bool doUpdateWeights, std::mutex* mutex) {
-  // Get the beam values for each station.
-  const size_t n_channels = info.chanFreqs().size();
-  const size_t n_baselines = info.nbaselines();
-
-  std::unique_ptr<everybeam::pointresponse::PointResponse> point_response =
-      telescope->GetPointResponse(time);
-
-  const std::vector<size_t> station_indices =
-      base::SelectStationIndices(*telescope, info.antennaNames());
-
-  const size_t n_stations = station_indices.size();
+  const size_t n_stations = ComputeBeam(info, time, srcdir, telescope,
+                                        beam_values, invert, mode, mutex);
+  ApplyBeamToData(info, n_stations, data0, weight0, beam_values,
+                  doUpdateWeights);
+}
 
-  // Apply the beam values of both stations to the ApplyBeamed data.
-  for (size_t ch = 0; ch < n_channels; ++ch) {
-    switch (mode) {
-      case everybeam::CorrectionMode::kFull:
-      case everybeam::CorrectionMode::kElement:
-        // Fill beam_values for channel ch
-        for (size_t st = 0; st < n_stations; ++st) {
-          beam_values[n_channels * st + ch] = point_response->Response(
-              mode, station_indices[st], info.chanFreqs()[ch], srcdir, mutex);
-          if (invert) {
-            // Terminate if the matrix is not invertible.
-            [[maybe_unused]] bool status =
-                beam_values[n_channels * st + ch].Invert();
-            assert(status);
-          }
-        }
-        break;
-      case everybeam::CorrectionMode::kArrayFactor: {
-        aocommon::MC2x2 af_tmp;
-        // Fill beam_values for channel ch
-        for (size_t st = 0; st < n_stations; ++st) {
-          af_tmp = point_response->Response(
-              mode, station_indices[st], info.chanFreqs()[ch], srcdir, mutex);
+// applyBeam is templated on the type of the data, could be complex<double>
+// or complex<float>
+template <typename T>
+void ApplyBeam::ApplyBeamAndAddToModel(
+    const DPInfo& info, double time, T* data0, T* model_data, float* weight0,
+    const everybeam::vector3r_t& srcdir,
+    const everybeam::telescope::Telescope* telescope,
+    std::vector<aocommon::MC2x2>& beam_values, bool invert,
+    everybeam::CorrectionMode mode, bool doUpdateWeights, std::mutex* mutex) {
+  const size_t n_stations = ComputeBeam(info, time, srcdir, telescope,
+                                        beam_values, invert, mode, mutex);
 
-          if (invert) {
-            af_tmp[0] = 1. / af_tmp[0];
-            af_tmp[3] = 1. / af_tmp[3];
-          }
-          beam_values[n_channels * st + ch] = af_tmp;
-        }
-        break;
-      }
-      case everybeam::CorrectionMode::kNone:  // this should not happen
-        for (size_t st = 0; st < n_stations; ++st) {
-          beam_values[n_channels * st + ch] = aocommon::MC2x2::Unity();
-        }
-        break;
-    }
+  ApplyBeamToDataAndAdd(info, n_stations, data0, model_data, weight0,
+                        beam_values, doUpdateWeights);
+}
 
-    // Apply beam for channel ch on all baselines
-    // For mode=ARRAY_FACTOR, too much work is done here because we know
-    // that r and l are diagonal
-    for (size_t bl = 0; bl < n_baselines; ++bl) {
-      T* data = data0 + bl * 4 * n_channels + ch * 4;
-      const aocommon::MC2x2F mat(data);
+template void ApplyBeam::ApplyBeamAndAddToModel(
+    const DPInfo& info, double time, std::complex<double>* data0,
+    std::complex<double>* model_data, float* weight0,
+    const everybeam::vector3r_t& srcdir,
+    const everybeam::telescope::Telescope* telescope,
+    std::vector<aocommon::MC2x2>& beam_values, bool invert,
+    everybeam::CorrectionMode mode, bool doUpdateWeights, std::mutex* mutex);
 
-      // If the beam is the same for all stations (i.e. when n_stations = 1),
-      // all baselines will have the same beam values
-      size_t index_left =
-          (n_stations == 1 ? ch : n_channels * info.getAnt1()[bl] + ch);
-      size_t index_right =
-          (n_stations == 1 ? ch : n_channels * info.getAnt2()[bl] + ch);
-      const aocommon::MC2x2F left(beam_values[index_left]);
-      const aocommon::MC2x2F right(beam_values[index_right]);
-      const aocommon::MC2x2F result = left * mat.MultiplyHerm(right);
-      result.AssignTo(data);
-      if (doUpdateWeights) {
-        ApplyCal::ApplyWeights(left, right,
-                               weight0 + bl * 4 * n_channels + ch * 4);
-      }
-    }
-  }
-}
+template void ApplyBeam::ApplyBeamAndAddToModel(
+    const DPInfo& info, double time, std::complex<float>* data0,
+    std::complex<float>* model_data, float* weight0,
+    const everybeam::vector3r_t& srcdir,
+    const everybeam::telescope::Telescope* telescope,
+    std::vector<aocommon::MC2x2>& beam_values, bool invert,
+    everybeam::CorrectionMode mode, bool doUpdateWeights, std::mutex* mutex);
 
 template void ApplyBeam::applyBeam(
     const DPInfo& info, double time, std::complex<double>* data0,
diff --git a/steps/ApplyBeam.h b/steps/ApplyBeam.h
index e0c49a07257e934645aa55a28c2eb862b7eb94bc..c2f99316838dc6a1ac4a041e5c362d5a5dbdc9ea 100644
--- a/steps/ApplyBeam.h
+++ b/steps/ApplyBeam.h
@@ -85,6 +85,16 @@ class ApplyBeam : public Step {
                         everybeam::CorrectionMode mode,
                         bool doUpdateWeights = false,
                         std::mutex* mutex = nullptr);
+
+  template <typename T>
+  static void ApplyBeamAndAddToModel(
+      const base::DPInfo& info, double time, T* data0, T* model_data,
+      float* weight0, const everybeam::vector3r_t& srcdir,
+      const everybeam::telescope::Telescope* telescope,
+      std::vector<aocommon::MC2x2>& beamValues, bool invert,
+      everybeam::CorrectionMode mode, bool doUpdateWeights = false,
+      std::mutex* mutex = nullptr);
+
   // This method applies the beam for processing when parallelizing over
   // baselines. Because the beam is a per-antenna effect, this requires
   // synchronisation, which is performed with the provided barrier.
diff --git a/steps/OnePredict.cc b/steps/OnePredict.cc
index 9d5b032868002c4fc61ad287961c4facff164506..e600b079f77f0546018ece20add1db163169b1e6 100644
--- a/steps/OnePredict.cc
+++ b/steps/OnePredict.cc
@@ -749,18 +749,18 @@ void OnePredict::addBeamToData(
         info(), time, data.data(), srcdir, telescope_.get(),
         predict_buffer_->GetScalarBeamValues(thread), false, beam_mode_,
         &mutex_);
+
+    // Add temporary buffer to Model
+    model_data += data;
   } else {
     const common::ScopedMicroSecondAccumulator<decltype(apply_beam_time_)>
         scoped_time{apply_beam_time_};
     float* dummyweight = nullptr;
-    ApplyBeam::applyBeam(info(), time, data.data(), dummyweight, srcdir,
-                         telescope_.get(),
-                         predict_buffer_->GetFullBeamValues(thread), false,
-                         beam_mode_, false, &mutex_);
+    ApplyBeam::ApplyBeamAndAddToModel(
+        info(), time, data.data(), model_data.data(), dummyweight, srcdir,
+        telescope_.get(), predict_buffer_->GetFullBeamValues(thread), false,
+        beam_mode_, false, &mutex_);
   }
-
-  // Add temporary buffer to Model
-  model_data += data;
 }
 
 void OnePredict::addBeamToData(