diff --git a/pythonbindings/pyfluids-stubs/gpu/__init__.pyi b/pythonbindings/pyfluids-stubs/gpu/__init__.pyi
index 8f78fd3387dc59c94ddf2315635e674a7330dae9..15386f7b0df4d2d06e01f8621805af8790c32442 100644
--- a/pythonbindings/pyfluids-stubs/gpu/__init__.pyi
+++ b/pythonbindings/pyfluids-stubs/gpu/__init__.pyi
@@ -38,10 +38,11 @@ from typing import Callable, ClassVar, List, Optional
 from typing import overload, Union
 import numpy as np
 import numpy.typing as npt
-import basics
+import basics, parallel
 
 from . import grid_generator as grid_generator
 from . import probes as probes
+from . import Kernel as Kernel
 
 class PreCollisionInteractor:
     def __init__(self, *args, **kwargs) -> None: ...
@@ -51,10 +52,8 @@ class FileCollection:
     def __init__(self, *args, **kwargs) -> None: ...
 
 class ActuatorFarm(PreCollisionInteractor):
-    def __init__(self, number_of_blades_per_turbine: int, density: float, number_of_nodes_per_blade: int, epsilon: float, level: int, delta_t: float, delta_x: float, use_host_arrays: bool) -> None: ...
-    def add_turbine(self, posX: float, posY: float, posZ: float, diameter: float, omega: float, azimuth: float, yaw: float, bladeRadii: List[float]) -> None: ...
-    def calc_blade_forces(self) -> None: ...
-    def get_all_azimuths(self) -> npt.NDArray[np.float32]: ...
+    def __init__(self, diameter: float, blade_radii: npt.NDArray[np.float32],turbine_positions_x: npt.NDArray[np.float32], turbine_positions_y: npt.NDArray[np.float32], turbine_positions_z: npt.NDArray[np.float32], density: float, smearing_width: float, level: int, delta_t: float, delta_x: float, use_host_arrays: bool) -> None: ...
+    def update_forces_and_coordinates(self) -> None: ...
     def get_all_blade_coords_x(self) -> npt.NDArray[np.float32]: ...
     def get_all_blade_coords_x_device(self) -> int: ...
     def get_all_blade_coords_y(self) -> npt.NDArray[np.float32]: ...
@@ -67,20 +66,15 @@ class ActuatorFarm(PreCollisionInteractor):
     def get_all_blade_forces_y_device(self) -> int: ...
     def get_all_blade_forces_z(self) -> npt.NDArray[np.float32]: ...
     def get_all_blade_forces_z_device(self) -> int: ...
-    def get_all_blade_radii(self) -> npt.NDArray[np.float32]: ...
-    def get_all_blade_radii_device(self) -> int: ...
     def get_all_blade_velocities_x(self) -> npt.NDArray[np.float32]: ...
     def get_all_blade_velocities_x_device(self) -> int: ...
     def get_all_blade_velocities_y(self) -> npt.NDArray[np.float32]: ...
     def get_all_blade_velocities_y_device(self) -> int: ...
     def get_all_blade_velocities_z(self) -> npt.NDArray[np.float32]: ...
     def get_all_blade_velocities_z_device(self) -> int: ...
-    def get_all_omegas(self) -> npt.NDArray[np.float32]: ...
     def get_all_turbine_pos_x(self) -> npt.NDArray[np.float32]: ...
     def get_all_turbine_pos_y(self) -> npt.NDArray[np.float32]: ...
     def get_all_turbine_pos_z(self) -> npt.NDArray[np.float32]: ...
-    def get_all_yaws(self) -> npt.NDArray[np.float32]: ...
-    def get_turbine_azimuth(self, turbine: int) -> float: ...
     def get_turbine_blade_coords_x(self, turbine: int) -> npt.NDArray[np.float32]: ...
     def get_turbine_blade_coords_x_device(self, turbine: int) -> int: ...
     def get_turbine_blade_coords_y(self, turbine: int) -> npt.NDArray[np.float32]: ...
@@ -93,29 +87,20 @@ class ActuatorFarm(PreCollisionInteractor):
     def get_turbine_blade_forces_y_device(self, turbine: int) -> int: ...
     def get_turbine_blade_forces_z(self, turbine: int) -> npt.NDArray[np.float32]: ...
     def get_turbine_blade_forces_z_device(self, turbine: int) -> int: ...
-    def get_turbine_blade_radii(self, turbine: int) -> npt.NDArray[np.float32]: ...
-    def get_turbine_blade_radii_device(self, turbine: int) -> int: ...
     def get_turbine_blade_velocities_x(self, turbine: int) -> npt.NDArray[np.float32]: ...
     def get_turbine_blade_velocities_x_device(self, turbine: int) -> int: ...
     def get_turbine_blade_velocities_y(self, turbine: int) -> npt.NDArray[np.float32]: ...
     def get_turbine_blade_velocities_y_device(self, turbine: int) -> int: ...
     def get_turbine_blade_velocities_z(self, turbine: int) -> npt.NDArray[np.float32]: ...
     def get_turbine_blade_velocities_z_device(self, turbine: int) -> int: ...
-    def get_turbine_omega(self, turbine: int) -> float: ...
-    def get_turbine_pos(self, turbine: int) -> npt.NDArray[np.float32]: ...
-    def get_turbine_yaw(self, turbine: int) -> float: ...
-    def set_all_azimuths(self, azimuths: npt.NDArray[np.float32]) -> None: ...
     def set_all_blade_coords(self, blade_coords_x: npt.NDArray[np.float32], blade_coords_y: npt.NDArray[np.float32], blade_coords_z: npt.NDArray[np.float32]) -> None: ...
     def set_all_blade_forces(self, blade_forces_x: npt.NDArray[np.float32], blade_forces_y: npt.NDArray[np.float32], blade_forces_z: npt.NDArray[np.float32]) -> None: ...
     def set_all_blade_velocities(self, blade_velocities_x: npt.NDArray[np.float32], blade_velocities_y: npt.NDArray[np.float32], blade_velocities_z: npt.NDArray[np.float32]) -> None: ...
-    def set_all_omegas(self, omegas: npt.NDArray[np.float32]) -> None: ...
-    def set_all_yaws(self, yaws: npt.NDArray[np.float32]) -> None: ...
-    def set_turbine_azimuth(self, turbine: int, azimuth: float) -> None: ...
     def set_turbine_blade_coords(self, turbine: int, blade_coords_x: npt.NDArray[np.float32], blade_coords_y: npt.NDArray[np.float32], blade_coords_z: npt.NDArray[np.float32]) -> None: ...
     def set_turbine_blade_forces(self, turbine: int, blade_forces_x: npt.NDArray[np.float32], blade_forces_y: npt.NDArray[np.float32], blade_forces_z: npt.NDArray[np.float32]) -> None: ...
     def set_turbine_blade_velocities(self, turbine: int, blade_velocities_x: npt.NDArray[np.float32], blade_velocities_y: npt.NDArray[np.float32], blade_velocities_z: npt.NDArray[np.float32]) -> None: ...
-    def set_turbine_omega(self, turbine: int, omega: float) -> None: ...
-    def set_turbine_yaw(self, turbine: int, yaw: float) -> None: ...
+    def set_turbine_azimuth(self, turbine: int, azimuth: float) -> None: ...
+    def enable_output(self, output_name: str, t_start_out: int, t_out: int) -> None: ...
     @property
     def delta_t(self) -> float: ...
     @property
@@ -133,6 +118,8 @@ class ActuatorFarm(PreCollisionInteractor):
     @property
     def number_of_turbines(self) -> int: ...
 
+class ActuatorFarmStandalone(ActuatorFarm):
+    def __init__(self, diameter: float, number_of_nodes_per_blade: int, turbine_positions_x: npt.NDArray[np.float32], turbine_positions_y: npt.NDArray[np.float32], turbine_positions_z: npt.NDArray[np.float32], rotor_speeds: npt.NDArray[np.float32], density: float, smearing_width: float, level: int, delta_t: float, delta_x: float) -> None: ...
 
 class BoundaryConditionFactory:
     def __init__(self) -> None: ...
@@ -145,14 +132,6 @@ class BoundaryConditionFactory:
     def set_velocity_boundary_condition(self, boundary_condition_type: VelocityBC) -> None: ...
 
 
-class MpiCommunicator:
-    def __init__(self, *args, **kwargs) -> None: ...
-    @staticmethod
-    def get_instance() -> MpiCommunicator: ...
-    def get_number_of_process(self) -> int: ...
-    def get_pid(self) -> int: ...
-
-
 class CudaMemoryManager:
     def __init__(self, parameter: Parameter) -> None: ...
 
@@ -176,7 +155,7 @@ class FileType:
 class GridProvider:
     def __init__(self, *args, **kwargs) -> None: ...
     @staticmethod
-    def make_grid_generator(builder: grid_generator.GridBuilder, para: Parameter, cuda_memory_manager: CudaMemoryManager, communicator: MpiCommunicator) -> GridProvider: ...
+    def make_grid_generator(builder: grid_generator.GridBuilder, para: Parameter, cuda_memory_manager: CudaMemoryManager, communicator: parallel.Communicator) -> GridProvider: ...
 
 class MultipleGridBuilder:
     def __init__(self) -> None: ...
@@ -273,7 +252,7 @@ class Parameter:
     def set_initial_condition_perturbed_log_law(self, u_star: float, z0: float, length_x: float, length_z: float, height: float, velocity_ratio: float) -> None: ...
     def set_initial_condition_uniform(self, velocity_x: float, velocity_y: float, velocity_z: float) -> None: ...
     def set_is_body_force(self, is_body_force: bool) -> None: ...
-    def set_main_kernel(self, kernel: str) -> None: ...
+    def configure_main_kernel(self, kernel: str) -> None: ...
     def set_max_dev(self, max_dev: int) -> None: ...
     def set_max_level(self, number_of_levels: int) -> None: ...
     def set_outflow_pressure_correction_factor(self, correction_factor: float) -> None: ...
diff --git a/pythonbindings/src/gpu/submodules/actuator_farm.cpp b/pythonbindings/src/gpu/submodules/actuator_farm.cpp
index ee5275483f85559a15aefa769ba30a87af993355..c057d6bb15cc750adf8f6266fba7d27aede03b61 100644
--- a/pythonbindings/src/gpu/submodules/actuator_farm.cpp
+++ b/pythonbindings/src/gpu/submodules/actuator_farm.cpp
@@ -33,17 +33,18 @@
 
 #include <pybind11/pybind11.h>
 #include <pybind11/numpy.h>
-#include <gpu/core/PreCollisionInteractor/ActuatorFarm.h>
-#include <gpu/core/PreCollisionInteractor/PreCollisionInteractor.h>
+#include <gpu/VirtualFluids_GPU/PreCollisionInteractor/Actuator/ActuatorFarm.h>
+#include <gpu/VirtualFluids_GPU/PreCollisionInteractor/Actuator/ActuatorFarmStandalone.h>
+#include <gpu/VirtualFluids_GPU/PreCollisionInteractor/PreCollisionInteractor.h>
 
 
 class PyActuatorFarm : public ActuatorFarm 
 {
 public:
     using ActuatorFarm::ActuatorFarm; // Inherit constructors
-    void calcBladeForces() override 
+    void updateForcesAndCoordinates() override 
     { 
-        PYBIND11_OVERRIDE_NAME(void, ActuatorFarm, "calc_blade_forces", calcBladeForces); 
+        PYBIND11_OVERRIDE_PURE_NAME(void, ActuatorFarm, "update_forces_and_coordinates", updateForcesAndCoordinates); 
     }
 };
 
@@ -62,24 +63,31 @@ namespace actuator_farm
         using arr = py::array_t<real, py::array::c_style>;
         
         py::class_<ActuatorFarm, PreCollisionInteractor, PyActuatorFarm, std::shared_ptr<ActuatorFarm>>(parentModule, "ActuatorFarm", py::dynamic_attr())
-        .def(py::init<  const uint,
+        .def(py::init<  const real,
+                        const std::vector<real>,
+                        const std::vector<real>,
+                        const std::vector<real>,
+                        const std::vector<real>,
                         const real,
-                        const uint,
                         const real,
-                        int,
+                        const int,
                         const real,
                         const real,
                         const bool>(), 
-                        py::arg("number_of_blades_per_turbine"), 
+                        py::arg("diameter"),
+                        py::arg("blade_radii"),
+                        py::arg("turbine_positions_x"),
+                        py::arg("turbine_positions_y"),
+                        py::arg("turbine_positions_z"),
                         py::arg("density"), 
-                        py::arg("number_of_nodes_per_blade"), 
-                        py::arg("epsilon"),
+                        py::arg("smearing_width"),
                         py::arg("level"), 
                         py::arg("delta_t"), 
                         py::arg("delta_x"),
                         py::arg("use_host_arrays"))
-        .def_property_readonly("number_of_turbines", &ActuatorFarm::getNumberOfTurbines)
         .def_property_readonly("number_of_nodes_per_blade", &ActuatorFarm::getNumberOfNodesPerBlade)
+        .def_property_readonly("number_of_turbines", &ActuatorFarm::getNumberOfTurbines)
+        .def_property_readonly("number_of_nodes_per_turbine", &ActuatorFarm::getNumberOfNodesPerTurbine)
         .def_property_readonly("number_of_blades_per_turbine", &ActuatorFarm::getNumberOfBladesPerTurbine)
         .def_property_readonly("number_of_grid_nodes", &ActuatorFarm::getNumberOfGridNodes)
         .def_property_readonly("number_of_indices", &ActuatorFarm::getNumberOfIndices)
@@ -87,22 +95,13 @@ namespace actuator_farm
         .def_property_readonly("delta_t", &ActuatorFarm::getDeltaT)
         .def_property_readonly("delta_x", &ActuatorFarm::getDeltaX)
 
-        .def("add_turbine", &ActuatorFarm::addTurbine, py::arg("posX"), py::arg("posY"), py::arg("posZ"), py::arg("diameter"), py::arg("omega"), py::arg("azimuth"), py::arg("yaw"), py::arg("bladeRadii"))
-
         .def("get_turbine_pos", [](ActuatorFarm& al, uint turbine){ 
             real position[3] = {al.getTurbinePosX(turbine), al.getTurbinePosY(turbine), al.getTurbinePosZ(turbine)}; return arr(3,  position);
             }, py::arg("turbine"))
-        .def("get_turbine_azimuth", &ActuatorFarm::getTurbineAzimuth, py::arg("turbine"))
-        .def("get_turbine_yaw", &ActuatorFarm::getTurbineYaw, py::arg("turbine"))
-        .def("get_turbine_omega", &ActuatorFarm::getTurbineOmega, py::arg("turbine"))
-        .def("get_all_azimuths", [](ActuatorFarm& al){ return arr(al.getNumberOfTurbines(), al.getAllAzimuths()); } )
-        .def("get_all_yaws", [](ActuatorFarm& al){ return arr(al.getNumberOfTurbines(), al.getAllYaws()); } )
-        .def("get_all_omegas", [](ActuatorFarm& al){ return arr(al.getNumberOfTurbines(), al.getAllOmegas()); } )
         .def("get_all_turbine_pos_x", [](ActuatorFarm& al){ return arr(al.getNumberOfTurbines(), al.getAllTurbinePosX()); } )
         .def("get_all_turbine_pos_y", [](ActuatorFarm& al){ return arr(al.getNumberOfTurbines(), al.getAllTurbinePosY()); } )
         .def("get_all_turbine_pos_z", [](ActuatorFarm& al){ return arr(al.getNumberOfTurbines(), al.getAllTurbinePosZ()); } )
     
-        .def("get_all_blade_radii", [](ActuatorFarm& al){ return arr({al.getNumberOfTurbines(), al.getNumberOfNodesPerBlade()}, al.getAllBladeRadii()); } )
         .def("get_all_blade_coords_x", [](ActuatorFarm& al){ return arr({al.getNumberOfTurbines(), al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getAllBladeCoordsX()); } )
         .def("get_all_blade_coords_y", [](ActuatorFarm& al){ return arr({al.getNumberOfTurbines(), al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getAllBladeCoordsY()); } )
         .def("get_all_blade_coords_z", [](ActuatorFarm& al){ return arr({al.getNumberOfTurbines(), al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getAllBladeCoordsZ()); } )        
@@ -113,7 +112,6 @@ namespace actuator_farm
         .def("get_all_blade_forces_y", [](ActuatorFarm& al){ return arr({al.getNumberOfTurbines(), al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getAllBladeForcesY()); } )
         .def("get_all_blade_forces_z", [](ActuatorFarm& al){ return arr({al.getNumberOfTurbines(), al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getAllBladeForcesZ()); } )
 
-        .def("get_turbine_blade_radii", [](ActuatorFarm& al, uint turbine){ return arr(al.getNumberOfNodesPerBlade(), al.getTurbineBladeRadiiDevice(turbine)); } , py::arg("turbine"))
         .def("get_turbine_blade_coords_x", [](ActuatorFarm& al, uint turbine){ return arr({al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getTurbineBladeCoordsXDevice(turbine)); }, py::arg("turbine") )
         .def("get_turbine_blade_coords_y", [](ActuatorFarm& al, uint turbine){ return arr({al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getTurbineBladeCoordsYDevice(turbine)); }, py::arg("turbine") )
         .def("get_turbine_blade_coords_z", [](ActuatorFarm& al, uint turbine){ return arr({al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getTurbineBladeCoordsZDevice(turbine)); }, py::arg("turbine") )        
@@ -124,7 +122,6 @@ namespace actuator_farm
         .def("get_turbine_blade_forces_y", [](ActuatorFarm& al, uint turbine){ return arr({al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getTurbineBladeForcesYDevice(turbine)); }, py::arg("turbine") )
         .def("get_turbine_blade_forces_z", [](ActuatorFarm& al, uint turbine){ return arr({al.getNumberOfBladesPerTurbine(), al.getNumberOfNodesPerBlade()}, al.getTurbineBladeForcesZDevice(turbine)); }, py::arg("turbine") )
 
-        .def("get_all_blade_radii_device", [](ActuatorFarm& al) -> intptr_t { return arr_to_cp(al.getAllBladeRadiiDevice()); } )
         .def("get_all_blade_coords_x_device", [](ActuatorFarm& al) -> intptr_t { return arr_to_cp(al.getAllBladeCoordsXDevice()); } )
         .def("get_all_blade_coords_y_device", [](ActuatorFarm& al) -> intptr_t { return arr_to_cp(al.getAllBladeCoordsYDevice()); } )
         .def("get_all_blade_coords_z_device", [](ActuatorFarm& al) -> intptr_t { return arr_to_cp(al.getAllBladeCoordsZDevice()); } )        
@@ -135,7 +132,6 @@ namespace actuator_farm
         .def("get_all_blade_forces_y_device", [](ActuatorFarm& al) -> intptr_t { return arr_to_cp(al.getAllBladeForcesYDevice()); } )
         .def("get_all_blade_forces_z_device", [](ActuatorFarm& al) -> intptr_t { return arr_to_cp(al.getAllBladeForcesZDevice()); } )
 
-        .def("get_turbine_blade_radii_device", [](ActuatorFarm& al, uint turbine) -> intptr_t { return arr_to_cp(al.getTurbineBladeRadiiDevice(turbine)); }, py::arg("turbine") )
         .def("get_turbine_blade_coords_x_device", [](ActuatorFarm& al, uint turbine) -> intptr_t { return arr_to_cp(al.getTurbineBladeCoordsXDevice(turbine)); }, py::arg("turbine") )
         .def("get_turbine_blade_coords_y_device", [](ActuatorFarm& al, uint turbine) -> intptr_t { return arr_to_cp(al.getTurbineBladeCoordsYDevice(turbine)); }, py::arg("turbine") )
         .def("get_turbine_blade_coords_z_device", [](ActuatorFarm& al, uint turbine) -> intptr_t { return arr_to_cp(al.getTurbineBladeCoordsZDevice(turbine)); }, py::arg("turbine") )        
@@ -146,14 +142,6 @@ namespace actuator_farm
         .def("get_turbine_blade_forces_y_device", [](ActuatorFarm& al, uint turbine) -> intptr_t { return arr_to_cp(al.getTurbineBladeForcesYDevice(turbine)); }, py::arg("turbine") )
         .def("get_turbine_blade_forces_z_device", [](ActuatorFarm& al, uint turbine) -> intptr_t { return arr_to_cp(al.getTurbineBladeForcesZDevice(turbine)); }, py::arg("turbine") )
 
-        .def("set_all_azimuths", [](ActuatorFarm& al, arr azimuths){ al.setAllAzimuths(np_to_arr(azimuths)); }, py::arg("azimuths"))
-        .def("set_all_yaws", [](ActuatorFarm& al, arr yaws){ al.setAllYaws(np_to_arr(yaws)); }, py::arg("yaws"))
-        .def("set_all_omegas", [](ActuatorFarm& al, arr omegas){ al.setAllOmegas(np_to_arr(omegas)); }, py::arg("omegas"))
-
-        .def("set_turbine_azimuth", &ActuatorFarm::setTurbineAzimuth, py::arg("turbine"), py::arg("azimuth"))
-        .def("set_turbine_yaw", &ActuatorFarm::setTurbineYaw, py::arg("turbine"), py::arg("yaw"))
-        .def("set_turbine_omega", &ActuatorFarm::setTurbineOmega, py::arg("turbine"), py::arg("omega"))
-
         .def("set_all_blade_coords", [](ActuatorFarm& al, arr coordsX, arr coordsY, arr coordsZ){ 
             al.setAllBladeCoords(np_to_arr(coordsX), np_to_arr(coordsY), np_to_arr(coordsZ)); 
         }, py::arg("blade_coords_x"), py::arg("blade_coords_y"), py::arg("blade_coords_z") )
@@ -172,6 +160,34 @@ namespace actuator_farm
         .def("set_turbine_blade_forces", [](ActuatorFarm& al, uint turbine, arr forcesX, arr forcesY, arr forcesZ){ 
             al.setTurbineBladeForces(turbine, np_to_arr(forcesX), np_to_arr(forcesY), np_to_arr(forcesZ)); 
         }, py::arg("turbine"), py::arg("blade_forces_x"), py::arg("blade_forces_y"), py::arg("blade_forces_z") )
-        .def("calc_blade_forces", &ActuatorFarm::calcBladeForces);
+        .def("update_forces_and_coordinates", &ActuatorFarm::updateForcesAndCoordinates)
+        .def("enable_output", &ActuatorFarm::enableOutput, py::arg("output_name"), py::arg("t_start_out"), py::arg("t_out"))
+        .def("set_turbine_azimuth", &ActuatorFarm::setTurbineAzimuth, py::arg("turbine"), py::arg("azimuth"));
+
+        py::class_<ActuatorFarmStandalone, ActuatorFarm, std::shared_ptr<ActuatorFarmStandalone>>(parentModule, "ActuatorFarmStandalone")
+        .def(py::init<  const real,
+                        const uint,
+                        const std::vector<real>,
+                        const std::vector<real>,
+                        const std::vector<real>,
+                        const std::vector<real>,
+                        const real,
+                        const real,
+                        const int,
+                        const real,
+                        const real>(), 
+                        py::arg("diameter"),
+                        py::arg("number_of_nodes_per_blade"), 
+                        py::arg("turbine_positions_x"),
+                        py::arg("turbine_positions_y"),
+                        py::arg("turbine_positions_z"),
+                        py::arg("rotor_speeds"),
+                        py::arg("density"), 
+                        py::arg("smearing_width"),
+                        py::arg("level"), 
+                        py::arg("delta_t"), 
+                        py::arg("delta_x"));
     }
+
+
 }
\ No newline at end of file
diff --git a/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarm.cu b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarm.cu
index fac6b3cb70c8bb2694f89e886b9192dfeed86782..b0602d58a4aa3964d7ddc4edb12984b9fdcf01dc 100644
--- a/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarm.cu
+++ b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarm.cu
@@ -42,7 +42,7 @@
 #include <basics/constants/NumericConstants.h>
 #include <basics/writer/WbWriterVtkXmlBinary.h>
 
-#include "gpu/core/GPU/GeometryUtils.h"
+#include "VirtualFluids_GPU/GPU/GeometryUtils.h"
 #include "LBM/GPUHelperFunctions/KernelUtilities.h"
 #include "Parameter/Parameter.h"
 #include "Parameter/CudaStreamManager.h"
@@ -51,351 +51,187 @@
 
 using namespace vf::basics::constant;
 
-__host__ __device__ __forceinline__ real distSqrd(real distX, real distY, real distZ)
+struct GridData
 {
-    return distX * distX + distY * distY + distZ * distZ;
-}
-
-void swapArrays(real* &arr1, real* &arr2)
-{
-    real* tmp = arr1;
-    arr1 = arr2;
-    arr2 = tmp;
-}
-
-__host__ __device__ __inline__ void rotateFromBladeToGlobal(
-                            real bladeCoordX_BF, real bladeCoordY_BF, real bladeCoordZ_BF, 
-                            real& bladeCoordX_GF, real& bladeCoordY_GF, real& bladeCoordZ_GF,
-                            real azimuth, real yaw)
-{
-    real tmpX, tmpY, tmpZ;
-
-    rotateAboutX3D(azimuth, bladeCoordX_BF, bladeCoordY_BF, bladeCoordZ_BF, tmpX, tmpY, tmpZ);
-    rotateAboutZ3D(yaw, tmpX, tmpY, tmpZ, bladeCoordX_GF, bladeCoordY_GF, bladeCoordZ_GF);
-
-}
-
-__host__ __device__ __inline__ void rotateFromGlobalToBlade(
-                            real& bladeCoordX_BF, real& bladeCoordY_BF, real& bladeCoordZ_BF, 
-                            real bladeCoordX_GF, real bladeCoordY_GF, real bladeCoordZ_GF,
-                            real azimuth, real yaw)
+    const uint* indices;
+    const uint nIndices;
+    const real* coordsX, * coordsY, *coordsZ;
+    const uint* neighborsX, * neighborsY, * neighborsZ, * neighborsWSB;
+    const real* vx, *vy, *vz;
+    real* fx, *fy, *fz;
+    const real inverseDeltaX, velocityRatio;
+};
+
+struct TurbineData
 {
-    real tmpX, tmpY, tmpZ;
-
-    invRotateAboutZ3D(yaw, bladeCoordX_GF, bladeCoordY_GF, bladeCoordZ_GF, tmpX, tmpY, tmpZ);
-    invRotateAboutX3D(azimuth, tmpX, tmpY, tmpZ, bladeCoordX_BF, bladeCoordY_BF, bladeCoordZ_BF);
-}
+    const real* posX, *posY, *posZ;
+    const uint numberOfTurbines;
+    const real smearingWidth, factorGaussian;
+};
 
-__host__ __device__ __inline__ void calcCoordinateOfBladeNodeInGridFrameOfReference(uint bladeNodeIndex, uint turbine, real localAzimuth, real yaw,
-                                                                                    const real* bladeCoordsX, const real* bladeCoordsY, const real* bladeCoordsZ,
-                                                                                    const real* turbPosX, const real* turbPosY, const real* turbPosZ,
-                                                                                    real& bladeCoordX_GF, real& bladeCoordY_GF, real& bladeCoordZ_GF)
+struct ComponentData
 {
+    const real referenceLength;
+    const uint numberOfNodesPerTurbine;
+    const real* coordsX, * coordsY, * coordsZ;
+    real* velocitiesX, * velocitiesY, * velocitiesZ;
+    const real* forcesX, * forcesY, * forcesZ;
+    uint* gridIndices;
+};
 
-    const real bladeCoordX_BF = bladeCoordsX[bladeNodeIndex];
-    const real bladeCoordY_BF = bladeCoordsY[bladeNodeIndex];
-    const real bladeCoordZ_BF = bladeCoordsZ[bladeNodeIndex];
 
-    rotateFromBladeToGlobal(bladeCoordX_BF, bladeCoordY_BF, bladeCoordZ_BF, bladeCoordX_GF, bladeCoordY_GF,
-                            bladeCoordZ_GF, localAzimuth, yaw);
-
-    bladeCoordX_GF += turbPosX[turbine];
-    bladeCoordY_GF += turbPosY[turbine];
-    bladeCoordZ_GF += turbPosZ[turbine];
-}
-
-__global__ void interpolateVelocities(real* gridCoordsX, real* gridCoordsY, real* gridCoordsZ,
-                                      uint* neighborsX, uint* neighborsY, uint* neighborsZ, uint* neighborsWSB,
-                                      real* vx, real* vy, real* vz,
-                                      real* bladeCoordsX, real* bladeCoordsY, real* bladeCoordsZ,
-                                      real* bladeVelocitiesX, real* bladeVelocitiesY, real* bladeVelocitiesZ,
-                                      uint numberOfTurbines, uint numberOfBlades, uint numberOfBladeNodes,
-                                      real* azimuths, real* yaws, real* omegas,
-                                      real* turbPosX, real* turbPosY, real* turbPosZ,
-                                      uint* bladeIndices, real velocityRatio, real invDeltaX)
+__global__ void interpolateVelocities(const GridData gridData, const TurbineData turbineData, ComponentData componentData)
 {
-
-    ////////////////////////////////////////////////////////////////////////////////
-    //! - Get node index coordinates from threadIdx, blockIdx, blockDim and gridDim.
-    //!
     const unsigned nodeIndex = vf::gpu::getNodeIndex();
 
-    if (nodeIndex >= numberOfBladeNodes * numberOfBlades * numberOfTurbines) return;
-
-    auto [turbine, blade, bladeNodeIndexOnBlade] = calcTurbineBladeAndBladeNode(nodeIndex, numberOfBladeNodes, numberOfBlades);
-
-    const real localAzimuth = azimuths[turbine] + blade * c2Pi / numberOfBlades;
-    const real yaw = yaws[turbine];
+    if (nodeIndex >= componentData.numberOfNodesPerTurbine * turbineData.numberOfTurbines) return;
 
-    real bladeCoordX_GF, bladeCoordY_GF, bladeCoordZ_GF;
-    calcCoordinateOfBladeNodeInGridFrameOfReference(nodeIndex, turbine, localAzimuth, yaw,
-                                                    bladeCoordsX, bladeCoordsY, bladeCoordsZ,
-                                                    turbPosX, turbPosY, turbPosZ,
-                                                    bladeCoordX_GF, bladeCoordY_GF, bladeCoordZ_GF);
+    const real coordX = componentData.coordsX[nodeIndex];
+    const real coordY = componentData.coordsY[nodeIndex];
+    const real coordZ = componentData.coordsZ[nodeIndex];
 
     uint k, ke, kn, kt;
     uint kne, kte, ktn, ktne;
 
-    k = findNearestCellBSW(bladeIndices[nodeIndex],
-                           gridCoordsX, gridCoordsY, gridCoordsZ,
-                           bladeCoordX_GF, bladeCoordY_GF, bladeCoordZ_GF,
-                           neighborsX, neighborsY, neighborsZ, neighborsWSB);
+    k = findNearestCellBSW(componentData.gridIndices[nodeIndex],
+                           gridData.coordsX, gridData.coordsY, gridData.coordsZ,
+                           coordX, coordY, coordZ,
+                           gridData.neighborsX, gridData.neighborsY, gridData.neighborsZ, gridData.neighborsWSB);
 
-    bladeIndices[nodeIndex] = k;
+    componentData.gridIndices[nodeIndex] = k;
 
-    getNeighborIndicesOfBSW(k, ke, kn, kt, kne, kte, ktn, ktne, neighborsX, neighborsY, neighborsZ);
+    getNeighborIndicesOfBSW(k, ke, kn, kt, kne, kte, ktn, ktne, gridData.neighborsX, gridData.neighborsY, gridData.neighborsZ);
 
     real dW, dE, dN, dS, dT, dB;
 
-    real distX = invDeltaX * (bladeCoordX_GF - gridCoordsX[k]);
-    real distY = invDeltaX * (bladeCoordY_GF - gridCoordsY[k]);
-    real distZ = invDeltaX * (bladeCoordZ_GF - gridCoordsZ[k]);
+    const real distX = gridData.inverseDeltaX * (coordX - gridData.coordsX[k]);
+    const real distY = gridData.inverseDeltaX * (coordY - gridData.coordsY[k]);
+    const real distZ = gridData.inverseDeltaX * (coordZ - gridData.coordsZ[k]);
 
     getInterpolationWeights(dW, dE, dN, dS, dT, dB, distX, distY, distZ);
 
-    real bladeVelX_GF = trilinearInterpolation(dW, dE, dN, dS, dT, dB, k, ke, kn, kt, kne, kte, ktn, ktne, vx) * velocityRatio;
-    real bladeVelY_GF = trilinearInterpolation(dW, dE, dN, dS, dT, dB, k, ke, kn, kt, kne, kte, ktn, ktne, vy) * velocityRatio;
-    real bladeVelZ_GF = trilinearInterpolation(dW, dE, dN, dS, dT, dB, k, ke, kn, kt, kne, kte, ktn, ktne, vz) * velocityRatio;
-
-    real bladeVelX_BF, bladeVelY_BF, bladeVelZ_BF;
-
-    rotateFromGlobalToBlade(bladeVelX_BF, bladeVelY_BF, bladeVelZ_BF,
-                            bladeVelX_GF, bladeVelY_GF, bladeVelZ_GF,
-                            localAzimuth, yaw);
-
-    bladeVelocitiesX[nodeIndex] = bladeVelX_BF;
-    bladeVelocitiesY[nodeIndex] = bladeVelY_BF + omegas[turbine] * bladeCoordsZ[nodeIndex];
-    bladeVelocitiesZ[nodeIndex] = bladeVelZ_BF;
+    componentData.velocitiesX[nodeIndex] = trilinearInterpolation(dW, dE, dN, dS, dT, dB, k, ke, kn, kt, kne, kte, ktn, ktne, gridData.vx) * gridData.velocityRatio;
+    componentData.velocitiesY[nodeIndex] = trilinearInterpolation(dW, dE, dN, dS, dT, dB, k, ke, kn, kt, kne, kte, ktn, ktne, gridData.vy) * gridData.velocityRatio;
+    componentData.velocitiesZ[nodeIndex] = trilinearInterpolation(dW, dE, dN, dS, dT, dB, k, ke, kn, kt, kne, kte, ktn, ktne, gridData.vz) * gridData.velocityRatio;
 }
 
-__global__ void applyBodyForces(real* gridCoordsX, real* gridCoordsY, real* gridCoordsZ,
-                                real* gridForcesX, real* gridForcesY, real* gridForcesZ, 
-                                real* bladeCoordsX, real* bladeCoordsY, real* bladeCoordsZ, 
-                                real* bladeForcesX, real* bladeForcesY, real* bladeForcesZ,
-                                const uint numberOfTurbines, const uint numberOfBlades, const uint numberOfBladeNodes,
-                                real* azimuths, real* yaws, real* diameters,
-                                real* turbPosX, real* turbPosY, real* turbPosZ,
-                                uint* gridIndices, uint nIndices, 
-                                const real invEpsilonSqrd, const real factorGaussian)
+__global__ void applyBodyForces(GridData gridData, const TurbineData turbineData, const ComponentData componentData)
 {
 
     const uint index = vf::gpu::getNodeIndex();
 
-    if (index >= nIndices) return;
-
-    uint gridIndex = gridIndices[index];
-
-    real gridCoordX_GF = gridCoordsX[gridIndex];
-    real gridCoordY_GF = gridCoordsY[gridIndex];
-    real gridCoordZ_GF = gridCoordsZ[gridIndex];
-
-    real gridForceX_RF = c0o1;
-    real gridForceY_RF = c0o1;
-    real gridForceZ_RF = c0o1;
+    if (index >= gridData.nIndices) return;
 
-    real dAzimuth = c2Pi / numberOfBlades;
+    const uint gridIndex = gridData.indices[index];
+    const real gridCoordX = gridData.coordsX[gridIndex];
+    const real gridCoordY = gridData.coordsY[gridIndex];
+    const real gridCoordZ = gridData.coordsZ[gridIndex];
 
-    for (uint turbine = 0; turbine < numberOfTurbines; turbine++) {
-        real radius = c1o2 * diameters[turbine];
-        real gridCoordX_RF = gridCoordX_GF - turbPosX[turbine];
-        real gridCoordY_RF = gridCoordY_GF - turbPosY[turbine];
-        real gridCoordZ_RF = gridCoordZ_GF - turbPosZ[turbine];
+    real gridForceX = c0o1;
+    real gridForceY = c0o1;
+    real gridForceZ = c0o1;
 
-        if (distSqrd(gridCoordX_RF, gridCoordY_RF, gridCoordZ_RF) * invEpsilonSqrd > radius * radius * invEpsilonSqrd + c7o1) continue;
+    for (uint turbine = 0; turbine < turbineData.numberOfTurbines; turbine++) {
+        const real distToHubX = gridCoordX - turbineData.posX[turbine];
+        const real distToHubY = gridCoordY - turbineData.posY[turbine];
+        const real distToHubZ = gridCoordZ - turbineData.posZ[turbine];
 
-        real azimuth = azimuths[turbine];
-        real yaw = yaws[turbine];
+        if (!inBoundingSphere(distToHubX, distToHubY, distToHubZ, componentData.referenceLength, turbineData.smearingWidth)) continue;
 
-        for (uint blade = 0; blade < numberOfBlades; blade++) {
-            real localAzimuth = azimuth + blade * dAzimuth;
+        for (uint turbineNode = 0; turbineNode < componentData.numberOfNodesPerTurbine; turbineNode++) {
+            const uint node = turbine*componentData.numberOfNodesPerTurbine + turbineNode;
 
-            real gridCoordX_BF, gridCoordY_BF, gridCoordZ_BF;
+            const real distX = componentData.coordsX[node] - gridCoordX;
+            const real distY = componentData.coordsY[node] - gridCoordY;
+            const real distZ = componentData.coordsZ[node] - gridCoordZ;
 
-            rotateFromGlobalToBlade(gridCoordX_BF, gridCoordY_BF, gridCoordZ_BF,
-                                    gridCoordX_RF, gridCoordY_RF, gridCoordZ_RF,
-                                    localAzimuth, yaw);
-
-            uint node;
-            uint nextNode = calcNodeIndexInBladeArrays({ /*.turbine = */ turbine, /* .blade = */ blade, /*.bladeNode =*/0 }, numberOfBladeNodes, numberOfBlades);
-
-            real last_z = c0o1;
-            real current_z = c0o1;
-            real next_z = bladeCoordsZ[nextNode];
-
-            real x, y, dz, eta, forceX_RF, forceY_RF, forceZ_RF;
-
-            for (uint bladeNode = 0; bladeNode < numberOfBladeNodes - 1; bladeNode++) {
-                node = nextNode;
-                nextNode = calcNodeIndexInBladeArrays({ /*.turbine = */ turbine, /* .blade = */ blade, /*.bladeNode =*/bladeNode + 1 }, numberOfBladeNodes, numberOfBlades);
-
-                x = bladeCoordsX[node];
-                y = bladeCoordsY[node];
-                last_z = current_z;
-                current_z = next_z;
-                next_z = bladeCoordsZ[nextNode];
-
-                dz = c1o2 * (next_z - last_z);
-
-                eta = dz * factorGaussian * exp(-distSqrd(x - gridCoordX_BF, y - gridCoordY_BF, current_z - gridCoordZ_BF) * invEpsilonSqrd);
-                rotateFromBladeToGlobal(bladeForcesX[node], bladeForcesY[node], bladeForcesZ[node], forceX_RF, forceY_RF, forceZ_RF, localAzimuth, yaw);
-
-                gridForceX_RF += forceX_RF * eta;
-                gridForceY_RF += forceY_RF * eta;
-                gridForceZ_RF += forceZ_RF * eta;
-            }
-
-            // Handle last node separately
-
-            node = nextNode;
-
-            x = bladeCoordsX[node];
-            y = bladeCoordsY[node];
-            last_z = current_z;
-            current_z = next_z;
-
-            dz = c1o2 * (radius - last_z);
-
-            eta = dz * factorGaussian * exp(-distSqrd(x - gridCoordX_BF, y - gridCoordY_BF, current_z - gridCoordZ_BF) * invEpsilonSqrd);
-
-            rotateFromBladeToGlobal(bladeForcesX[node], bladeForcesY[node], bladeForcesZ[node],
-                                    forceX_RF, forceY_RF, forceZ_RF,
-                                    localAzimuth, yaw);
-
-            gridForceX_RF += forceX_RF * eta;
-            gridForceY_RF += forceY_RF * eta;
-            gridForceZ_RF += forceZ_RF * eta;
+            const real eta = gaussianSmearing(distX, distY, distZ, turbineData.smearingWidth, turbineData.factorGaussian);
+            gridForceX += componentData.forcesX[node] * eta;
+            gridForceY += componentData.forcesY[node] * eta;
+            gridForceZ += componentData.forcesZ[node] * eta;
         }
     }
-
-    gridForcesX[gridIndex] += gridForceX_RF;
-    gridForcesY[gridIndex] += gridForceY_RF;
-    gridForcesZ[gridIndex] += gridForceZ_RF;
+    gridData.fx[gridIndex] += gridForceX;
+    gridData.fy[gridIndex] += gridForceY;
+    gridData.fz[gridIndex] += gridForceZ;
 }
 
-void ActuatorFarm::addTurbine(real posX, real posY, real posZ, real diameter, real omega, real azimuth, real yaw, std::vector<real> bladeRadii)
-{
-    preInitPosX.push_back(posX);
-    preInitPosY.push_back(posY);
-    preInitPosZ.push_back(posZ);
-    preInitOmegas.push_back(omega);
-    preInitAzimuths.push_back(azimuth);
-    preInitYaws.push_back(yaw);
-    preInitDiameters.push_back(diameter);
-    preInitBladeRadii.push_back(bladeRadii);
-}
-
-void ActuatorFarm::init(Parameter *para, GridProvider *gridProvider, CudaMemoryManager *cudaMemoryManager)
+void ActuatorFarm::init(Parameter *para, GridProvider *gridProvider, CudaMemoryManager *cudaManager)
 {
     if (!para->getIsBodyForce()) throw std::runtime_error("try to allocate ActuatorFarm but BodyForce is not set in Parameter.");
     this->forceRatio = para->getForceRatio();
-    this->initTurbineGeometries(cudaMemoryManager);
-    this->initBladeCoords(cudaMemoryManager);
-    this->initBladeIndices(para, cudaMemoryManager);
-    this->initBladeVelocities(cudaMemoryManager);
-    this->initBladeForces(cudaMemoryManager);
-    this->initBoundingSpheres(para, cudaMemoryManager);
+    this->initTurbineGeometries(cudaManager);
+    this->initBladeCoords(cudaManager);
+    this->initBladeIndices(para, cudaManager);
+    this->initBladeVelocities(cudaManager);
+    this->initBladeForces(cudaManager);
+    this->initBoundingSpheres(para, cudaManager);
     this->streamIndex = para->getStreamManager()->registerAndLaunchStream(CudaStreamIndex::ActuatorFarm);
 }
 
-void ActuatorFarm::interact(Parameter *para, CudaMemoryManager *cudaMemoryManager, int level, unsigned int t)
+void ActuatorFarm::interact(Parameter *para, CudaMemoryManager *cudaManager, int level, unsigned int t)
 {
     if (level != this->level) return;
 
     cudaStream_t stream = para->getStreamManager()->getStream(CudaStreamIndex::ActuatorFarm, this->streamIndex);
 
-    if (useHostArrays) cudaMemoryManager->cudaCopyBladeCoordsHtoD(this);
+    if (useHostArrays) cudaManager->cudaCopyBladeCoordsHtoD(this);
 
-    vf::cuda::CudaGrid bladeGrid = vf::cuda::CudaGrid(para->getParH(level)->numberofthreads, this->numberOfGridNodes);
-
-    // if (t == 0) this->writeBladeCoordsToVtkForDebug("output/ALM/bladecoords_" + std::to_string(t));
+    if(this->writeOutput && ((t-this->tStartOut) % this->tOut == 0))
+    {
+        if(!useHostArrays){
+            cudaManager->cudaCopyBladeCoordsDtoH(this);
+            cudaManager->cudaCopyBladeVelocitiesDtoH(this);
+            cudaManager->cudaCopyBladeForcesDtoH(this);
+        }
+        this->write(this->getFilename(para, t));
+    }
 
-    interpolateVelocities<<< bladeGrid.grid, bladeGrid.threads, 0, stream >>>(
+    const GridData gridData {
+        this->boundingSphereIndicesD, this->numberOfIndices,
         para->getParD(this->level)->coordinateX, para->getParD(this->level)->coordinateY, para->getParD(this->level)->coordinateZ,
         para->getParD(this->level)->neighborX, para->getParD(this->level)->neighborY, para->getParD(this->level)->neighborZ, para->getParD(this->level)->neighborInverse,
         para->getParD(this->level)->velocityX, para->getParD(this->level)->velocityY, para->getParD(this->level)->velocityZ,
-        this->bladeCoordsXDCurrentTimestep, this->bladeCoordsYDCurrentTimestep, this->bladeCoordsZDCurrentTimestep,  
-        this->bladeVelocitiesXDCurrentTimestep, this->bladeVelocitiesYDCurrentTimestep, this->bladeVelocitiesZDCurrentTimestep,
-        this->numberOfTurbines, this->numberOfBlades, this->numberOfBladeNodes,
-        this->azimuthsD, this->yawsD, this->omegasD, 
+        para->getParD(this->level)->forceX_SP,para->getParD(this->level)->forceY_SP,para->getParD(this->level)->forceZ_SP,
+        this->invDeltaX, para->getVelocityRatio()};
+
+    const TurbineData turbineData {
         this->turbinePosXD, this->turbinePosYD, this->turbinePosZD,
-        this->bladeIndicesD, para->getVelocityRatio(), this->invDeltaX);
+        this->numberOfTurbines,
+        this->smearingWidth, this->factorGaussian};
+    
+    const ComponentData bladeData {
+        this->diameter, this->numberOfNodesPerTurbine,
+        this->bladeCoordsXDCurrentTimestep, this->bladeCoordsYDCurrentTimestep, this->bladeCoordsZDCurrentTimestep, 
+        this->bladeVelocitiesXDCurrentTimestep, this->bladeVelocitiesYDCurrentTimestep, this->bladeVelocitiesZDCurrentTimestep, 
+        this->bladeForcesXDCurrentTimestep, this->bladeForcesYDCurrentTimestep, this->bladeForcesZDCurrentTimestep,
+        this->bladeIndicesD};
 
+    vf::cuda::CudaGrid bladeGrid = vf::cuda::CudaGrid(para->getParH(level)->numberofthreads, this->numberOfGridNodes);
+    interpolateVelocities<<< bladeGrid.grid, bladeGrid.threads, 0, stream >>>(gridData, turbineData, bladeData);
     cudaStreamSynchronize(stream);
-    if (useHostArrays) cudaMemoryManager->cudaCopyBladeVelocitiesDtoH(this);
-    this->calcBladeForces();
+
+    if (useHostArrays) cudaManager->cudaCopyBladeVelocitiesDtoH(this);
+    
+    this->updateForcesAndCoordinates();
     this->swapDeviceArrays();
 
-    if (useHostArrays) cudaMemoryManager->cudaCopyBladeForcesHtoD(this);
+    if (useHostArrays) cudaManager->cudaCopyBladeForcesHtoD(this);
 
     vf::cuda::CudaGrid sphereGrid = vf::cuda::CudaGrid(para->getParH(level)->numberofthreads, this->numberOfIndices);
 
-    applyBodyForces<<<sphereGrid.grid, sphereGrid.threads, 0, stream>>>(
-        para->getParD(this->level)->coordinateX, para->getParD(this->level)->coordinateY, para->getParD(this->level)->coordinateZ,
-        para->getParD(this->level)->forceX_SP, para->getParD(this->level)->forceY_SP, para->getParD(this->level)->forceZ_SP,
-        this->bladeCoordsXDCurrentTimestep, this->bladeCoordsYDCurrentTimestep, this->bladeCoordsZDCurrentTimestep,
-        this->bladeForcesXDCurrentTimestep, this->bladeForcesYDCurrentTimestep, this->bladeForcesZDCurrentTimestep,
-        this->numberOfTurbines, this->numberOfBlades, this->numberOfBladeNodes,
-        this->azimuthsD, this->yawsD, this->diametersD,
-        this->turbinePosXD, this->turbinePosYD, this->turbinePosZD,
-        this->boundingSphereIndicesD, this->numberOfIndices,
-        this->invEpsilonSqrd, this->factorGaussian);
-    cudaMemoryManager->cudaCopyBladeOrientationsHtoD(this);
+    applyBodyForces<<<sphereGrid.grid, sphereGrid.threads, 0, stream>>>(gridData, turbineData, bladeData);
     cudaStreamSynchronize(stream);
 }
 
-void ActuatorFarm::free(Parameter *para, CudaMemoryManager *cudaMemoryManager)
-{
-    cudaMemoryManager->cudaFreeBladeGeometries(this);
-    cudaMemoryManager->cudaFreeBladeOrientations(this);
-    cudaMemoryManager->cudaFreeBladeCoords(this);
-    cudaMemoryManager->cudaFreeBladeVelocities(this);
-    cudaMemoryManager->cudaFreeBladeForces(this);
-    cudaMemoryManager->cudaFreeBladeIndices(this);
-    cudaMemoryManager->cudaFreeSphereIndices(this);
-}
-
-void ActuatorFarm::calcForcesEllipticWing()
-{
-    real u_rel, v_rel, u_rel_sq;
-    real phi;
-    real Cl = c1o1;
-    real Cd = c0o1;
-    real c0 = 20 * c1o10;
-    real c, Cn, Ct;
-
-    for (uint turbine = 0; turbine < this->numberOfTurbines; turbine++)
-    {
-        real diameter = this->diametersH[turbine];
-        for (uint blade = 0; blade < this->numberOfBlades; blade++)
-        {
-            for (uint bladeNode = 0; bladeNode < this->numberOfBladeNodes; bladeNode++)
-            {
-                uint node = calcNodeIndexInBladeArrays({ /*.turbine = */ turbine, /* .blade = */ blade, /*.bladeNode =*/bladeNode }, this->numberOfBladeNodes, this->numberOfBlades);
-
-                u_rel = this->bladeVelocitiesXH[node];
-                v_rel = this->bladeVelocitiesYH[node];
-                u_rel_sq = u_rel * u_rel + v_rel * v_rel;
-                phi = atan2(u_rel, v_rel);
-
-                real tmp = c4o1 * this->bladeRadiiH[bladeNode] / diameter - c1o1;
-                c = c0 * sqrt(c1o1 - tmp * tmp);
-                Cn = Cl * cos(phi) + Cd * sin(phi);
-                Ct = Cl * sin(phi) - Cd * cos(phi);
-                real fx = c1o2 * u_rel_sq * c * this->density * Cn;
-                real fy = c1o2 * u_rel_sq * c * this->density * Ct;
-                this->bladeForcesXH[node] = -fx;
-                this->bladeForcesYH[node] = -fy;
-                this->bladeForcesZH[node] = c0o1;
-                // printf("u %f v %f fx %f fy %f \n", u_rel, v_rel, fx, fy);
-            }
-        }
-        azimuthsH[turbine] = azimuthsH[turbine] + deltaT * omegasH[turbine];
-    }
-}
-
-void ActuatorFarm::calcBladeForces()
+void ActuatorFarm::free(Parameter *para, CudaMemoryManager *cudaManager)
 {
-    this->calcForcesEllipticWing();
+    cudaManager->cudaFreeBladeGeometries(this);
+    cudaManager->cudaFreeBladeCoords(this);
+    cudaManager->cudaFreeBladeVelocities(this);
+    cudaManager->cudaFreeBladeForces(this);
+    cudaManager->cudaFreeBladeIndices(this);
+    cudaManager->cudaFreeSphereIndices(this);
 }
 
 void ActuatorFarm::getTaggedFluidNodes(Parameter *para, GridProvider *gridProvider)
@@ -404,111 +240,101 @@ void ActuatorFarm::getTaggedFluidNodes(Parameter *para, GridProvider *gridProvid
     gridProvider->tagFluidNodeIndices(indicesInSphere, CollisionTemplate::AllFeatures, this->level);
 }
 
-void ActuatorFarm::initTurbineGeometries(CudaMemoryManager *cudaMemoryManager)
+void ActuatorFarm::initTurbineGeometries(CudaMemoryManager *cudaManager)
 {
-    this->numberOfTurbines = uint(this->preInitDiameters.size());
-    this->numberOfGridNodes = numberOfTurbines * numberOfBladeNodes * numberOfBlades;
+    this->numberOfGridNodes = this->numberOfTurbines * this->numberOfNodesPerTurbine;
 
-    cudaMemoryManager->cudaAllocBladeGeometries(this);
-    cudaMemoryManager->cudaAllocBladeOrientations(this);
+    cudaManager->cudaAllocBladeGeometries(this);
 
-    for (uint turbine = 0; turbine < this->numberOfTurbines; turbine++) {
-        for (uint node = 0; node < this->numberOfBladeNodes; node++) {
-            this->bladeRadiiH[calcNodeIndexInBladeArrays({ /*.turbine = */ turbine, /* .blade = */ 0, /*.bladeNode =*/node }, numberOfBladeNodes, 1)] = this->preInitBladeRadii[turbine][node];
-        }
-    }
-    std::copy(preInitPosX.begin(), preInitPosX.end(), turbinePosXH);
-    std::copy(preInitPosY.begin(), preInitPosY.end(), turbinePosYH);
-    std::copy(preInitPosZ.begin(), preInitPosZ.end(), turbinePosZH);
-    std::copy(preInitDiameters.begin(), preInitDiameters.end(), diametersH);
-
-    cudaMemoryManager->cudaCopyBladeGeometriesHtoD(this);
-    std::copy(preInitAzimuths.begin(), preInitAzimuths.end(), this->azimuthsH);
-    std::copy(preInitOmegas.begin(), preInitOmegas.end(), this->omegasH);
-    std::copy(preInitYaws.begin(), preInitYaws.end(), this->yawsH);
-
-    cudaMemoryManager->cudaCopyBladeOrientationsHtoD(this);
-    this->factorGaussian = pow(this->epsilon * sqrt(cPi), -c3o1) / this->forceRatio;
+    std::copy(initialTurbinePositionsX.begin(), initialTurbinePositionsX.end(), turbinePosXH);
+    std::copy(initialTurbinePositionsY.begin(), initialTurbinePositionsY.end(), turbinePosYH);
+    std::copy(initialTurbinePositionsZ.begin(), initialTurbinePositionsZ.end(), turbinePosZH);
+
+    cudaManager->cudaCopyBladeGeometriesHtoD(this);
+
+    this->factorGaussian = pow(this->smearingWidth * sqrt(cPi), -c3o1) / this->forceRatio;
 }
 
-void ActuatorFarm::initBladeCoords(CudaMemoryManager *cudaMemoryManager)
+void ActuatorFarm::initBladeCoords(CudaMemoryManager *cudaManager)
 {
-    cudaMemoryManager->cudaAllocBladeCoords(this);
+    cudaManager->cudaAllocBladeCoords(this);
 
-    for (uint turbine = 0; turbine < numberOfTurbines; turbine++)
+    for (uint turbine = 0; turbine < this->numberOfTurbines; turbine++)
     {
-        for (uint blade = 0; blade < this->numberOfBlades; blade++)
+        for (uint blade = 0; blade < ActuatorFarm::numberOfBlades; blade++)
         {
-            for (uint bladeNode = 0; bladeNode < this->numberOfBladeNodes; bladeNode++) {
-                uint node = calcNodeIndexInBladeArrays({ /*.turbine = */ turbine, /* .blade = */ blade, /*.bladeNode =*/bladeNode }, this->numberOfBladeNodes, this->numberOfBlades);
-
-                this->bladeCoordsXH[node] = c0o1;
-                this->bladeCoordsYH[node] = c0o1;
-                this->bladeCoordsZH[node] = this->bladeRadiiH[calcNodeIndexInBladeArrays({ /*.turbine = */ turbine, /* .blade = */ 0, /*.bladeNode =*/bladeNode }, numberOfBladeNodes, 1)];
+            const real local_azimuth = this->azimuths[turbine] + blade * c2Pi / ActuatorFarm::numberOfBlades;
+
+            for (uint bladeNode = 0; bladeNode < this->numberOfNodesPerBlade; bladeNode++) {
+                const uint node = calcNodeIndexInBladeArrays({ turbine, blade, bladeNode }, this->numberOfNodesPerBlade, ActuatorFarm::numberOfBlades);
+                
+                real x, y, z;
+                rotateFromBladeToGlobal(c0o1, c0o1, this->bladeRadii[bladeNode], x, y, z, local_azimuth);
+                bladeCoordsXH[node] = x + this->turbinePosXH[turbine];
+                bladeCoordsYH[node] = y + this->turbinePosYH[turbine];
+                bladeCoordsZH[node] = z + this->turbinePosZH[turbine];
             }
         }
     }
-    cudaMemoryManager->cudaCopyBladeCoordsHtoD(this);
+    cudaManager->cudaCopyBladeCoordsHtoD(this);
     swapArrays(this->bladeCoordsXDCurrentTimestep, this->bladeCoordsXDPreviousTimestep);
     swapArrays(this->bladeCoordsYDCurrentTimestep, this->bladeCoordsYDPreviousTimestep);
     swapArrays(this->bladeCoordsZDCurrentTimestep, this->bladeCoordsZDPreviousTimestep);
-    cudaMemoryManager->cudaCopyBladeCoordsHtoD(this);
+    cudaManager->cudaCopyBladeCoordsHtoD(this);
 }
 
-void ActuatorFarm::initBladeVelocities(CudaMemoryManager *cudaMemoryManager)
+void ActuatorFarm::initBladeVelocities(CudaMemoryManager *cudaManager)
 {
-    cudaMemoryManager->cudaAllocBladeVelocities(this);
+    cudaManager->cudaAllocBladeVelocities(this);
 
     std::fill_n(this->bladeVelocitiesXH, this->numberOfGridNodes, c0o1);
     std::fill_n(this->bladeVelocitiesYH, this->numberOfGridNodes, c0o1);
     std::fill_n(this->bladeVelocitiesZH, this->numberOfGridNodes, c0o1);
 
-    cudaMemoryManager->cudaCopyBladeVelocitiesHtoD(this);
+    cudaManager->cudaCopyBladeVelocitiesHtoD(this);
     swapArrays(this->bladeVelocitiesXDCurrentTimestep, this->bladeVelocitiesXDPreviousTimestep);
     swapArrays(this->bladeVelocitiesYDCurrentTimestep, this->bladeVelocitiesYDPreviousTimestep);
     swapArrays(this->bladeVelocitiesZDCurrentTimestep, this->bladeVelocitiesZDPreviousTimestep);
-    cudaMemoryManager->cudaCopyBladeVelocitiesHtoD(this);
+    cudaManager->cudaCopyBladeVelocitiesHtoD(this);
 }
 
-void ActuatorFarm::initBladeForces(CudaMemoryManager *cudaMemoryManager)
+void ActuatorFarm::initBladeForces(CudaMemoryManager *cudaManager)
 {
-    cudaMemoryManager->cudaAllocBladeForces(this);
+    cudaManager->cudaAllocBladeForces(this);
 
     std::fill_n(this->bladeForcesXH, this->numberOfGridNodes, c0o1);
     std::fill_n(this->bladeForcesYH, this->numberOfGridNodes, c0o1);
     std::fill_n(this->bladeForcesZH, this->numberOfGridNodes, c0o1);
 
-    cudaMemoryManager->cudaCopyBladeForcesHtoD(this);
+    cudaManager->cudaCopyBladeForcesHtoD(this);
     swapArrays(this->bladeForcesXDCurrentTimestep, this->bladeForcesXDPreviousTimestep);
     swapArrays(this->bladeForcesYDCurrentTimestep, this->bladeForcesYDPreviousTimestep);
     swapArrays(this->bladeForcesZDCurrentTimestep, this->bladeForcesZDPreviousTimestep);
-    cudaMemoryManager->cudaCopyBladeForcesHtoD(this);
+    cudaManager->cudaCopyBladeForcesHtoD(this);
 }
 
-void ActuatorFarm::initBladeIndices(Parameter *para, CudaMemoryManager *cudaMemoryManager)
+void ActuatorFarm::initBladeIndices(Parameter *para, CudaMemoryManager *cudaManager)
 {
-    cudaMemoryManager->cudaAllocBladeIndices(this);
+    cudaManager->cudaAllocBladeIndices(this);
 
     std::fill_n(this->bladeIndicesH, this->numberOfGridNodes, 1);
 
-    cudaMemoryManager->cudaCopyBladeIndicesHtoD(this);
+    cudaManager->cudaCopyBladeIndicesHtoD(this);
 }
 
-void ActuatorFarm::initBoundingSpheres(Parameter *para, CudaMemoryManager *cudaMemoryManager)
+void ActuatorFarm::initBoundingSpheres(Parameter *para, CudaMemoryManager *cudaManager)
 {
     std::vector<int> nodesInSpheres;
+    const real sphereRadius = getBoundingSphereRadius(this->diameter, this->smearingWidth);
+    const real sphereRadiusSqrd = sphereRadius * sphereRadius;
+    const uint minimumNumberOfNodesPerSphere = (uint)(c4o3 * cPi * pow(sphereRadius - this->deltaX, c3o1) / pow(this->deltaX, c3o1));
 
     for (uint turbine = 0; turbine < this->numberOfTurbines; turbine++) {
-        real sphereRadius = c1o2 * this->diametersH[turbine] + c4o1 * this->epsilon;
-
-        real posX = this->turbinePosXH[turbine];
-        real posY = this->turbinePosYH[turbine];
-        real posZ = this->turbinePosZH[turbine];
 
-        real sphereRadiusSqrd = sphereRadius * sphereRadius;
+        const real posX = this->turbinePosXH[turbine];
+        const real posY = this->turbinePosYH[turbine];
+        const real posZ = this->turbinePosZH[turbine];
 
-        uint minimumNumberOfNodesPerSphere =
-            (uint)(c4o3 * cPi * pow(sphereRadius - this->deltaX, c3o1) / pow(this->deltaX, c3o1));
         uint nodesInThisSphere = 0;
 
         for (size_t pos = 1; pos <= para->getParH(this->level)->numberOfNodes; pos++) {
@@ -529,65 +355,50 @@ void ActuatorFarm::initBoundingSpheres(Parameter *para, CudaMemoryManager *cudaM
 
     this->numberOfIndices = uint(nodesInSpheres.size());
 
-    cudaMemoryManager->cudaAllocSphereIndices(this);
+    cudaManager->cudaAllocSphereIndices(this);
     std::copy(nodesInSpheres.begin(), nodesInSpheres.end(), this->boundingSphereIndicesH);
-    cudaMemoryManager->cudaCopySphereIndicesHtoD(this);
-}
-
-void ActuatorFarm::setAllAzimuths(real *_azimuths)
-{
-    std::copy_n(_azimuths, this->numberOfTurbines, this->azimuthsH);
+    cudaManager->cudaCopySphereIndicesHtoD(this);
 }
 
-void ActuatorFarm::setAllOmegas(real *_omegas)
-{
-    std::copy_n(_omegas, this->numberOfTurbines, this->omegasH);
-}
-
-void ActuatorFarm::setAllYaws(real *_yaws)
-{
-    std::copy_n(_yaws, this->numberOfTurbines, this->yawsH);
-}
-
-void ActuatorFarm::setAllBladeCoords(real *_bladeCoordsX, real *_bladeCoordsY, real *_bladeCoordsZ)
+void ActuatorFarm::setAllBladeCoords(const real * _bladeCoordsX, const real * _bladeCoordsY, const real *  _bladeCoordsZ)
 {
     std::copy_n(_bladeCoordsX, this->numberOfGridNodes, this->bladeCoordsXH);
     std::copy_n(_bladeCoordsY, this->numberOfGridNodes, this->bladeCoordsYH);
     std::copy_n(_bladeCoordsZ, this->numberOfGridNodes, this->bladeCoordsZH);
 }
 
-void ActuatorFarm::setAllBladeVelocities(real *_bladeVelocitiesX, real *_bladeVelocitiesY, real *_bladeVelocitiesZ)
+void ActuatorFarm::setAllBladeVelocities(const real *_bladeVelocitiesX, const real *_bladeVelocitiesY, const real *_bladeVelocitiesZ)
 {
     std::copy_n(_bladeVelocitiesX, this->numberOfGridNodes, this->bladeVelocitiesXH);
     std::copy_n(_bladeVelocitiesY, this->numberOfGridNodes, this->bladeVelocitiesYH);
     std::copy_n(_bladeVelocitiesZ, this->numberOfGridNodes, this->bladeVelocitiesZH);
 }
 
-void ActuatorFarm::setAllBladeForces(real *_bladeForcesX, real *_bladeForcesY, real *_bladeForcesZ)
+void ActuatorFarm::setAllBladeForces(const real *_bladeForcesX, const real *_bladeForcesY, const real *_bladeForcesZ)
 {
     std::copy_n(_bladeForcesX, this->numberOfGridNodes, this->bladeForcesXH);
     std::copy_n(_bladeForcesY, this->numberOfGridNodes, this->bladeForcesYH);
     std::copy_n(_bladeForcesZ, this->numberOfGridNodes, this->bladeForcesZH);
 }
-void ActuatorFarm::setTurbineBladeCoords(uint turbine, real *_bladeCoordsX, real *_bladeCoordsY, real *_bladeCoordsZ)
+void ActuatorFarm::setTurbineBladeCoords(uint turbine, const real *_bladeCoordsX, const real *_bladeCoordsY, const real *_bladeCoordsZ)
 {
-    std::copy_n(_bladeCoordsX, numberOfBladeNodes * numberOfBlades, &this->bladeCoordsXH[turbine * numberOfBladeNodes * numberOfBlades]);
-    std::copy_n(_bladeCoordsY, numberOfBladeNodes * numberOfBlades, &this->bladeCoordsYH[turbine * numberOfBladeNodes * numberOfBlades]);
-    std::copy_n(_bladeCoordsZ, numberOfBladeNodes * numberOfBlades, &this->bladeCoordsZH[turbine * numberOfBladeNodes * numberOfBlades]);
+    std::copy_n(_bladeCoordsX, this->numberOfNodesPerTurbine, &this->bladeCoordsXH[turbine * this->numberOfNodesPerTurbine]);
+    std::copy_n(_bladeCoordsY, this->numberOfNodesPerTurbine, &this->bladeCoordsYH[turbine * this->numberOfNodesPerTurbine]);
+    std::copy_n(_bladeCoordsZ, this->numberOfNodesPerTurbine, &this->bladeCoordsZH[turbine * this->numberOfNodesPerTurbine]);
 }
 
-void ActuatorFarm::setTurbineBladeVelocities(uint turbine, real *_bladeVelocitiesX, real *_bladeVelocitiesY, real *_bladeVelocitiesZ)
+void ActuatorFarm::setTurbineBladeVelocities(uint turbine, const real *_bladeVelocitiesX, const real *_bladeVelocitiesY, const real *_bladeVelocitiesZ)
 {
-    std::copy_n(_bladeVelocitiesX, numberOfBladeNodes * numberOfBlades, &this->bladeVelocitiesXH[turbine * numberOfBladeNodes * numberOfBlades]);
-    std::copy_n(_bladeVelocitiesY, numberOfBladeNodes * numberOfBlades, &this->bladeVelocitiesYH[turbine * numberOfBladeNodes * numberOfBlades]);
-    std::copy_n(_bladeVelocitiesZ, numberOfBladeNodes * numberOfBlades, &this->bladeVelocitiesZH[turbine * numberOfBladeNodes * numberOfBlades]);
+    std::copy_n(_bladeVelocitiesX, this->numberOfNodesPerTurbine, &this->bladeVelocitiesXH[turbine * this->numberOfNodesPerTurbine]);
+    std::copy_n(_bladeVelocitiesY, this->numberOfNodesPerTurbine, &this->bladeVelocitiesYH[turbine * this->numberOfNodesPerTurbine]);
+    std::copy_n(_bladeVelocitiesZ, this->numberOfNodesPerTurbine, &this->bladeVelocitiesZH[turbine * this->numberOfNodesPerTurbine]);
 }
 
-void ActuatorFarm::setTurbineBladeForces(uint turbine, real *_bladeForcesX, real *_bladeForcesY, real *_bladeForcesZ)
+void ActuatorFarm::setTurbineBladeForces(uint turbine, const real *_bladeForcesX, const real *_bladeForcesY, const real *_bladeForcesZ)
 {
-    std::copy_n(_bladeForcesX, numberOfBladeNodes * numberOfBlades, &this->bladeForcesXH[turbine * numberOfBladeNodes * numberOfBlades]);
-    std::copy_n(_bladeForcesY, numberOfBladeNodes * numberOfBlades, &this->bladeForcesYH[turbine * numberOfBladeNodes * numberOfBlades]);
-    std::copy_n(_bladeForcesZ, numberOfBladeNodes * numberOfBlades, &this->bladeForcesZH[turbine * numberOfBladeNodes * numberOfBlades]);
+    std::copy_n(_bladeForcesX, this->numberOfNodesPerTurbine, &this->bladeForcesXH[turbine * this->numberOfNodesPerTurbine]);
+    std::copy_n(_bladeForcesY, this->numberOfNodesPerTurbine, &this->bladeForcesYH[turbine * this->numberOfNodesPerTurbine]);
+    std::copy_n(_bladeForcesZ, this->numberOfNodesPerTurbine, &this->bladeForcesZH[turbine * this->numberOfNodesPerTurbine]);
 }
 
 void ActuatorFarm::swapDeviceArrays()
@@ -605,25 +416,32 @@ void ActuatorFarm::swapDeviceArrays()
     swapArrays(this->bladeForcesZDPreviousTimestep, this->bladeForcesZDCurrentTimestep);
 }
 
-void ActuatorFarm::writeBladeCoordsToVtkForDebug(const std::string &filename)
+std::string ActuatorFarm::getFilename(Parameter* para, uint t) const
 {
-    std::vector<real> coordsX(numberOfGridNodes);
-    std::vector<real> coordsY(numberOfGridNodes);
-    std::vector<real> coordsZ(numberOfGridNodes);
-
-    for (uint nodeIndex = 0; nodeIndex < numberOfGridNodes; nodeIndex++) {
-        auto [turbine, blade, bladeNodeIndexOnBlade] = calcTurbineBladeAndBladeNode(nodeIndex, numberOfBladeNodes, numberOfBlades);
-
-        const real localAzimuth = this->getTurbineAzimuth(turbine) + blade * c2Pi / numberOfBlades;
-        const real yaw = this->getTurbineYaw(turbine);
-        real bladeCoordX_GF;
-        real bladeCoordY_GF;
-        real bladeCoordZ_GF;
-        calcCoordinateOfBladeNodeInGridFrameOfReference(nodeIndex, turbine, localAzimuth, yaw, this->bladeCoordsXH, this->bladeCoordsYH, this->bladeCoordsZH, this->turbinePosXH, this->turbinePosYH, this->turbinePosZH, bladeCoordX_GF, bladeCoordY_GF, bladeCoordZ_GF);
-        coordsX[nodeIndex] = bladeCoordX_GF;
-        coordsY[nodeIndex] = bladeCoordY_GF;
-        coordsZ[nodeIndex] = bladeCoordZ_GF;
-    }
+    return para->getOutputPath() + this->outputName + "_t_" + std::to_string(t);
+}
 
-    WbWriterVtkXmlBinary::getInstance()->writePolyLines(filename, coordsX, coordsY, coordsZ);
-}
\ No newline at end of file
+void ActuatorFarm::write(const std::string &filename)
+{
+    std::vector<std::string> dataNames = {
+        "bladeVelocitiesX",
+        "bladeVelocitiesY",
+        "bladeVelocitiesZ",
+        "bladeForcesX",
+        "bladeForcesY",
+        "bladeForcesZ",
+    };
+    std::vector<UbTupleFloat3> nodes(numberOfGridNodes);
+    std::vector<std::vector<double>> nodeData(6);
+    for(auto& data : nodeData) data.resize(numberOfGridNodes);
+    for(uint i=0; i<numberOfGridNodes; i++){
+        nodes[i] = UbTupleFloat3(this->bladeCoordsXH[i], this->bladeCoordsYH[i], this->bladeCoordsZH[i]);
+        nodeData[0][i] = this->bladeVelocitiesXH[i];
+        nodeData[1][i] = this->bladeVelocitiesYH[i];
+        nodeData[2][i] = this->bladeVelocitiesZH[i];
+        nodeData[3][i] = this->bladeForcesXH[i];
+        nodeData[4][i] = this->bladeForcesYH[i];
+        nodeData[5][i] = this->bladeForcesZH[i];
+    }
+    WbWriterVtkXmlBinary::getInstance()->writeNodesWithNodeData(filename, nodes, dataNames, nodeData);
+}
diff --git a/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarm.h b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarm.h
index 74f23f3e6a4dc9e3e798ca13aa3632171d0e3bd0..c94e9f885f2c6815880d14cc83f03d1811a6798d 100644
--- a/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarm.h
+++ b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarm.h
@@ -1,146 +1,141 @@
 #ifndef ActuatorFarm_H
 #define ActuatorFarm_H
 
-#include "PreCollisionInteractor.h"
-#include <basics/PointerDefinitions.h>
+#include "PreCollisionInteractor/PreCollisionInteractor.h"
 #include "basics/constants/NumericConstants.h"
+#include "basics/DataTypes.h"
 #include <stdexcept>
 
-using namespace vf::basics::constant;
-
 class Parameter;
 class GridProvider;
 using namespace vf::basics::constant;
 
+
 class ActuatorFarm : public PreCollisionInteractor
 {
 public:
     ActuatorFarm(
-        const uint _nBlades,
-        const real _density,
-        const uint _nBladeNodes,
-        const real _epsilon,
-        int _level,
-        const real _deltaT,
-        const real _deltaX,
-        const bool _useHostArrays
+        const real diameter,
+        const std::vector<real> bladeRadii,
+        const std::vector<real> turbinePositionsX,
+        const std::vector<real> turbinePositionsY,
+        const std::vector<real> turbinePositionsZ,
+        const real density,
+        const real smearingWidth,
+        const int level,
+        const real deltaT,
+        const real deltaX,
+        const bool useHostArrays
     ) :
-        numberOfBlades(_nBlades),
-        density(_density),
-        numberOfBladeNodes(_nBladeNodes), 
-        epsilon(_epsilon),
-        level(_level),
-        useHostArrays(_useHostArrays),
-        numberOfTurbines(0),
-        numberOfGridNodes(0),
-        PreCollisionInteractor()
+        diameter(diameter),
+        bladeRadii(bladeRadii),
+        numberOfNodesPerBlade(static_cast<uint>(bladeRadii.size())),
+        numberOfNodesPerTurbine(numberOfNodesPerBlade*numberOfBlades),
+        numberOfTurbines(static_cast<uint>(turbinePositionsX.size())),
+        initialTurbinePositionsX(turbinePositionsX),
+        initialTurbinePositionsY(turbinePositionsY),
+        initialTurbinePositionsZ(turbinePositionsZ),
+        density(density),
+        smearingWidth(smearingWidth),
+        level(level),
+        useHostArrays(useHostArrays),
+        deltaT(deltaT*exp2(-level)),
+        deltaX(deltaX*exp2(-level)),
+        invDeltaX(c1o1/deltaX)
     {
-        this->deltaT = _deltaT*exp2(-this->level);
-        this->deltaX = _deltaX*exp2(-this->level);
-        this->invEpsilonSqrd = 1/(epsilon*epsilon);
-        this->invDeltaX = c1o1/this->deltaX;
-     
-        if(this->epsilon<this->deltaX)
-            throw std::runtime_error("ActuatorFarm::ActuatorFarm: epsilon needs to be larger than dx!");
+        if(this->smearingWidth < this->deltaX)
+            throw std::runtime_error("ActuatorFarm::ActuatorFarm: smearing width needs to be larger than dx!");
+        if(numberOfTurbines != turbinePositionsY.size() || numberOfTurbines != turbinePositionsZ.size())
+            throw std::runtime_error("ActuatorFarm::ActuatorFarm: turbine positions need to have the same length!");
+        azimuths = std::vector<real>(numberOfTurbines, 0.0);
     }
 
     ~ActuatorFarm() override = default;
-    void addTurbine(real turbinePosX, real turbinePosY, real turbinePosZ, real diameter, real omega, real azimuth, real yaw, std::vector<real> bladeRadii);
     void init(Parameter* para, GridProvider* gridProvider, CudaMemoryManager* cudaManager) override;
     void interact(Parameter* para, CudaMemoryManager* cudaManager, int level, uint t) override;
     void free(Parameter* para, CudaMemoryManager* cudaManager) override;
     void getTaggedFluidNodes(Parameter *para, GridProvider* gridProvider) override;
 
-    void write(uint t);
-
-    real getDensity(){ return this->density; };
-    real getDeltaT(){ return this->deltaT; };
-    real getDeltaX(){ return this->deltaX; };
-
-    uint getNumberOfTurbines(){ return this->numberOfTurbines; };
-    uint getNumberOfNodesPerBlade(){ return this->numberOfBladeNodes; };
-    uint getNumberOfBladesPerTurbine(){ return this->numberOfBlades; };
-
-    uint getNumberOfIndices(){ return this->numberOfIndices; };
-    uint getNumberOfGridNodes(){ return this->numberOfGridNodes; };
-
-    real* getAllAzimuths(){ return azimuthsH; };
-    real* getAllOmegas(){ return omegasH; };
-    real* getAllYaws(){ return yawsH; };
-
-    real* getAllTurbinePosX(){ return turbinePosXH; };
-    real* getAllTurbinePosY(){ return turbinePosYH; };
-    real* getAllTurbinePosZ(){ return turbinePosZH; };
-
-    real getTurbineAzimuth(uint turbine){ return azimuthsH[turbine]; };
-    real getTurbineOmega  (uint turbine){ return omegasH[turbine];   };
-    real getTurbineYaw    (uint turbine){ return yawsH[turbine];     };
-
-    real getTurbinePosX(uint turbine){ return turbinePosXH[turbine]; };
-    real getTurbinePosY(uint turbine){ return turbinePosYH[turbine]; };
-    real getTurbinePosZ(uint turbine){ return turbinePosZH[turbine]; };
-
-    real* getAllBladeRadii(){ return this->bladeRadiiH; };
-    real* getAllBladeCoordsX(){ return this->bladeCoordsXH; };
-    real* getAllBladeCoordsY(){ return this->bladeCoordsYH; };
-    real* getAllBladeCoordsZ(){ return this->bladeCoordsZH; };
-    real* getAllBladeVelocitiesX(){ return this->bladeVelocitiesXH; };
-    real* getAllBladeVelocitiesY(){ return this->bladeVelocitiesYH; };
-    real* getAllBladeVelocitiesZ(){ return this->bladeVelocitiesZH; };
-    real* getAllBladeForcesX(){ return this->bladeForcesXH; };
-    real* getAllBladeForcesY(){ return this->bladeForcesYH; };
-    real* getAllBladeForcesZ(){ return this->bladeForcesZH; };
-
-    real* getTurbineBladeRadii(uint turbine){ return &this->bladeRadiiH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeCoordsX(uint turbine){ return &this->bladeCoordsXH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeCoordsY(uint turbine){ return &this->bladeCoordsYH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeCoordsZ(uint turbine){ return &this->bladeCoordsZH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeVelocitiesX(uint turbine){ return &this->bladeVelocitiesXH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeVelocitiesY(uint turbine){ return &this->bladeVelocitiesYH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeVelocitiesZ(uint turbine){ return &this->bladeVelocitiesZH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeForcesX(uint turbine){ return &this->bladeForcesXH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeForcesY(uint turbine){ return &this->bladeForcesYH[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeForcesZ(uint turbine){ return &this->bladeForcesZH[turbine*numberOfBladeNodes*numberOfBlades]; };
-
-    real* getAllBladeRadiiDevice(){ return this->bladeRadiiD; };
-    real* getAllBladeCoordsXDevice(){ return this->bladeCoordsXDCurrentTimestep; };
-    real* getAllBladeCoordsYDevice(){ return this->bladeCoordsYDCurrentTimestep; };
-    real* getAllBladeCoordsZDevice(){ return this->bladeCoordsZDCurrentTimestep; };
-    real* getAllBladeVelocitiesXDevice(){ return this->bladeVelocitiesXDCurrentTimestep; };
-    real* getAllBladeVelocitiesYDevice(){ return this->bladeVelocitiesYDCurrentTimestep; };
-    real* getAllBladeVelocitiesZDevice(){ return this->bladeVelocitiesZDCurrentTimestep; };
-    real* getAllBladeForcesXDevice(){ return this->bladeForcesXDCurrentTimestep; };
-    real* getAllBladeForcesYDevice(){ return this->bladeForcesYDCurrentTimestep; };
-    real* getAllBladeForcesZDevice(){ return this->bladeForcesZDCurrentTimestep; };
-
-    real* getTurbineBladeRadiiDevice(uint turbine){ return &this->bladeRadiiD[turbine*numberOfBladeNodes]; };
-    real* getTurbineBladeCoordsXDevice(uint turbine){ return &this->bladeCoordsXDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeCoordsYDevice(uint turbine){ return &this->bladeCoordsYDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeCoordsZDevice(uint turbine){ return &this->bladeCoordsZDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeVelocitiesXDevice(uint turbine){ return &this->bladeVelocitiesXDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeVelocitiesYDevice(uint turbine){ return &this->bladeVelocitiesYDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeVelocitiesZDevice(uint turbine){ return &this->bladeVelocitiesZDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeForcesXDevice(uint turbine){ return &this->bladeForcesXDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeForcesYDevice(uint turbine){ return &this->bladeForcesYDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-    real* getTurbineBladeForcesZDevice(uint turbine){ return &this->bladeForcesZDCurrentTimestep[turbine*numberOfBladeNodes*numberOfBlades]; };
-
-    void setAllAzimuths(real* _azimuth);
-    void setAllOmegas(real* _omegas);
-    void setAllYaws(real* yaws);
-    
-    void setTurbineAzimuth(uint turbine, real azimuth){ azimuthsH[turbine] = azimuth; };
-    void setTurbineYaw(uint turbine, real yaw){ yawsH[turbine] = yaw; };
-    void setTurbineOmega(uint turbine, real omega){ omegasH[turbine] = omega; };
-
-    void setAllBladeCoords(real* _bladeCoordsX, real* _bladeCoordsY, real* _bladeCoordsZ);
-    void setAllBladeVelocities(real* _bladeVelocitiesX, real* _bladeVelocitiesY, real* _bladeVelocitiesZ);
-    void setAllBladeForces(real* _bladeForcesX, real* _bladeForcesY, real* _bladeForcesZ);
-
-    void setTurbineBladeCoords(uint turbine, real* _bladeCoordsX, real* _bladeCoordsY, real* _bladeCoordsZ);
-    void setTurbineBladeVelocities(uint turbine, real* _bladeVelocitiesX, real* _bladeVelocitiesY, real* _bladeVelocitiesZ);
-    void setTurbineBladeForces(uint turbine, real* _bladeForcesX, real* _bladeForcesY, real* _bladeForcesZ);
-
-    virtual void calcBladeForces();
+    void enableOutput(const std::string outputName, uint tStart, uint tOut) {
+        this->outputName = outputName;
+        this->writeOutput = true;
+        this->tStartOut = tStart;
+        this->tOut = tOut;
+    }
+
+    void write(const std::string& filename);
+
+    real getDensity() const { return this->density; };
+    real getDeltaT() const { return this->deltaT; };
+    real getDeltaX() const { return this->deltaX; };
+
+    uint getNumberOfTurbines() const { return this->numberOfTurbines; };
+    uint getNumberOfNodesPerTurbine() const { return this->numberOfNodesPerTurbine; };
+    uint getNumberOfNodesPerBlade() const { return this->numberOfNodesPerBlade; };
+    uint getNumberOfBladesPerTurbine() const { return ActuatorFarm::numberOfBlades; };
+
+    uint getNumberOfIndices() const { return this->numberOfIndices; };
+    uint getNumberOfGridNodes() const { return this->numberOfGridNodes; };
+
+    real* getAllTurbinePosX() const { return turbinePosXH; };
+    real* getAllTurbinePosY() const { return turbinePosYH; };
+    real* getAllTurbinePosZ() const { return turbinePosZH; };
+
+    real getTurbinePosX(uint turbine) const { return turbinePosXH[turbine]; };
+    real getTurbinePosY(uint turbine) const { return turbinePosYH[turbine]; };
+    real getTurbinePosZ(uint turbine) const { return turbinePosZH[turbine]; };
+
+    real* getAllBladeCoordsX() const { return this->bladeCoordsXH; };
+    real* getAllBladeCoordsY() const { return this->bladeCoordsYH; };
+    real* getAllBladeCoordsZ() const { return this->bladeCoordsZH; };
+    real* getAllBladeVelocitiesX() const { return this->bladeVelocitiesXH; };
+    real* getAllBladeVelocitiesY() const { return this->bladeVelocitiesYH; };
+    real* getAllBladeVelocitiesZ() const { return this->bladeVelocitiesZH; };
+    real* getAllBladeForcesX() const { return this->bladeForcesXH; };
+    real* getAllBladeForcesY() const { return this->bladeForcesYH; };
+    real* getAllBladeForcesZ() const { return this->bladeForcesZH; };
+
+    real* getTurbineBladeCoordsX(uint turbine) const { return &this->bladeCoordsXH[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeCoordsY(uint turbine) const { return &this->bladeCoordsYH[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeCoordsZ(uint turbine) const { return &this->bladeCoordsZH[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeVelocitiesX(uint turbine) const { return &this->bladeVelocitiesXH[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeVelocitiesY(uint turbine) const { return &this->bladeVelocitiesYH[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeVelocitiesZ(uint turbine) const { return &this->bladeVelocitiesZH[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeForcesX(uint turbine) const { return &this->bladeForcesXH[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeForcesY(uint turbine) const { return &this->bladeForcesYH[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeForcesZ(uint turbine) const { return &this->bladeForcesZH[turbine*numberOfNodesPerTurbine]; };
+
+    real* getAllBladeCoordsXDevice() const { return this->bladeCoordsXDCurrentTimestep; };
+    real* getAllBladeCoordsYDevice() const { return this->bladeCoordsYDCurrentTimestep; };
+    real* getAllBladeCoordsZDevice() const { return this->bladeCoordsZDCurrentTimestep; };
+    real* getAllBladeVelocitiesXDevice() const { return this->bladeVelocitiesXDCurrentTimestep; };
+    real* getAllBladeVelocitiesYDevice() const { return this->bladeVelocitiesYDCurrentTimestep; };
+    real* getAllBladeVelocitiesZDevice() const { return this->bladeVelocitiesZDCurrentTimestep; };
+    real* getAllBladeForcesXDevice() const { return this->bladeForcesXDCurrentTimestep; };
+    real* getAllBladeForcesYDevice() const { return this->bladeForcesYDCurrentTimestep; };
+    real* getAllBladeForcesZDevice() const { return this->bladeForcesZDCurrentTimestep; };
+
+    real* getTurbineBladeCoordsXDevice(uint turbine) const { return &this->bladeCoordsXDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeCoordsYDevice(uint turbine) const { return &this->bladeCoordsYDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeCoordsZDevice(uint turbine) const { return &this->bladeCoordsZDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeVelocitiesXDevice(uint turbine) const { return &this->bladeVelocitiesXDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeVelocitiesYDevice(uint turbine) const { return &this->bladeVelocitiesYDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeVelocitiesZDevice(uint turbine) const { return &this->bladeVelocitiesZDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeForcesXDevice(uint turbine) const { return &this->bladeForcesXDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeForcesYDevice(uint turbine) const { return &this->bladeForcesYDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+    real* getTurbineBladeForcesZDevice(uint turbine) const { return &this->bladeForcesZDCurrentTimestep[turbine*numberOfNodesPerTurbine]; };
+
+    void setAllBladeCoords(const real* _bladeCoordsX, const real* _bladeCoordsY, const real* _bladeCoordsZ);
+    void setAllBladeVelocities(const real* _bladeVelocitiesX, const real* _bladeVelocitiesY, const real* _bladeVelocitiesZ);
+    void setAllBladeForces(const real* _bladeForcesX, const real* _bladeForcesY, const real* _bladeForcesZ);
+
+    void setTurbineBladeCoords(uint turbine, const real* _bladeCoordsX, const real* _bladeCoordsY, const real* _bladeCoordsZ);
+    void setTurbineBladeVelocities(uint turbine, const real* _bladeVelocitiesX, const real* _bladeVelocitiesY, const real* _bladeVelocitiesZ);
+    void setTurbineBladeForces(uint turbine, const real* _bladeForcesX, const real* _bladeForcesY, const real* _bladeForcesZ);
+
+    void setTurbineAzimuth(uint turbine, real azimuth){azimuths[turbine] = azimuth;}
+
+    virtual void updateForcesAndCoordinates()=0;
 
 private:
     void initTurbineGeometries(CudaMemoryManager* cudaManager);
@@ -149,20 +144,10 @@ private:
     void initBladeVelocities(CudaMemoryManager* cudaManager);
     void initBladeForces(CudaMemoryManager* cudaManager);
     void initBladeIndices(Parameter* para, CudaMemoryManager* cudaManager);
-
-    void calcForcesEllipticWing();
-    void rotateBlades(real angle, uint turbineID);
-
-    void writeBladeCoords(uint t);
-    void writeBladeCoordsToVtkForDebug(const std::string& filename);
-    void writeBladeForces(uint t);
-    void writeBladeVelocities(uint t);
-
+    std::string getFilename(Parameter* para, uint t) const;
     void swapDeviceArrays();
 
 public:
-    real* bladeRadiiH;
-    real* bladeRadiiD;
     real* bladeCoordsXH, * bladeCoordsYH, * bladeCoordsZH;
     real* bladeCoordsXDPreviousTimestep, * bladeCoordsYDPreviousTimestep, * bladeCoordsZDPreviousTimestep;
     real* bladeCoordsXDCurrentTimestep, * bladeCoordsYDCurrentTimestep, * bladeCoordsZDCurrentTimestep;    
@@ -176,23 +161,30 @@ public:
     uint* bladeIndicesD; 
     uint* boundingSphereIndicesH;
     uint* boundingSphereIndicesD;
-    real* turbinePosXH, *turbinePosYH, *turbinePosZH, *omegasH, *azimuthsH, *yawsH, *diametersH;
-    real* turbinePosXD, *turbinePosYD, *turbinePosZD, *omegasD, *azimuthsD, *yawsD, *diametersD;
-    
-private:
-    std::vector<real> preInitPosX, preInitPosY, preInitPosZ, preInitDiameters, preInitOmegas, preInitAzimuths, preInitYaws;
-    std::vector<std::vector<real>> preInitBladeRadii;
+    real* turbinePosXH, *turbinePosYH, *turbinePosZH;
+    real* turbinePosXD, *turbinePosYD, *turbinePosZD;
+
+protected:
+    static constexpr uint numberOfBlades{3};
+    std::vector<real> bladeRadii, initialTurbinePositionsX, initialTurbinePositionsY, initialTurbinePositionsZ;
+    std::vector<real> azimuths;
+    const real diameter;
     const bool useHostArrays;
     const real density;
-    real deltaT, deltaX;
-    const uint numberOfBladeNodes, numberOfBlades;
-    uint numberOfTurbines;
-    const real epsilon; // in m
+    const real deltaT, deltaX, invDeltaX;
+    const uint numberOfTurbines, numberOfNodesPerBlade, numberOfNodesPerTurbine;
+    const real smearingWidth; // in m
     const int level;
-    uint numberOfIndices;
-    uint numberOfGridNodes;
-    real forceRatio, factorGaussian, invEpsilonSqrd, invDeltaX;
+    uint numberOfIndices{0};
+    uint numberOfGridNodes{0};
+
+    real forceRatio, factorGaussian;
     int streamIndex;
+
+    bool writeOutput{false};
+    std::string outputName;
+    uint tOut{0};
+    uint tStartOut{0};
 };
 
 #endif
diff --git a/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmInlines.h b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmInlines.h
index cc151088db963f00d3d39af705c80d22337701c9..6a7113fa5f3a61a35598aa837a4cebe2975b2588 100644
--- a/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmInlines.h
+++ b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmInlines.h
@@ -26,7 +26,7 @@
 //  You should have received a copy of the GNU General Public License along
 //  with VirtualFluids (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
 //
-//! \file ActuatorFarm.cu
+//! \file ActuatorFarmInlines.cu
 //! \ingroup PreCollisionInteractor
 //! \author Henrik Asmuth, Henry Korb, Anna Wellmann
 //======================================================================================
@@ -35,6 +35,10 @@
 #define ACTUATOR_FARM_INLINES
 
 #include "basics/DataTypes.h"
+#include "basics/constants/NumericConstants.h"
+#include "VirtualFluids_GPU/GPU/GeometryUtils.h"
+
+using namespace vf::basics::constant;
 
 struct TurbineNodeIndex {
     uint turbine;
@@ -57,11 +61,12 @@ __host__ __device__ __inline__ void calcTurbineBladeAndBladeNode(uint node, uint
 {
     // see https://git.rz.tu-bs.de/irmb/VirtualFluids_dev/-/merge_requests/248 for visualization
     turbine = node / (numberOfNodesPerBlade * numberOfBlades);
-    uint x_off = turbine * numberOfNodesPerBlade * numberOfBlades;
+    const uint x_off = turbine * numberOfNodesPerBlade * numberOfBlades;
     blade = (node - x_off) / numberOfNodesPerBlade;
-    uint y_off = numberOfNodesPerBlade * blade + x_off;
+    const uint y_off = numberOfNodesPerBlade * blade + x_off;
     bladeNode = node - y_off;
 }
+
 __host__ __device__ __inline__ TurbineNodeIndex calcTurbineBladeAndBladeNode(uint node, uint numberOfNodesPerBlade, uint numberOfBlades)
 {
     uint turbine;
@@ -71,4 +76,47 @@ __host__ __device__ __inline__ TurbineNodeIndex calcTurbineBladeAndBladeNode(uin
     return { /*.turbine = */ turbine, /*.blade = */ blade, /*.bladeNode = */ bladeNode }; // Designated initializers are a C++ 20 feature
 }
 
+__host__ __device__ __inline__ void rotateFromBladeToGlobal(
+                            real bladeCoordX_BF, real bladeCoordY_BF, real bladeCoordZ_BF, 
+                            real& bladeCoordX_GF, real& bladeCoordY_GF, real& bladeCoordZ_GF,
+                            real azimuth)
+{
+    rotateAboutX3D(azimuth, bladeCoordX_BF, bladeCoordY_BF, bladeCoordZ_BF, bladeCoordX_GF, bladeCoordY_GF, bladeCoordZ_GF);
+}
+
+__host__ __device__ __inline__ void rotateFromGlobalToBlade(
+                            real& bladeCoordX_BF, real& bladeCoordY_BF, real& bladeCoordZ_BF, 
+                            real bladeCoordX_GF, real bladeCoordY_GF, real bladeCoordZ_GF,
+                            real azimuth)
+{
+    invRotateAboutX3D(azimuth, bladeCoordX_GF, bladeCoordY_GF, bladeCoordZ_GF, bladeCoordX_BF, bladeCoordY_BF, bladeCoordZ_BF);
+}
+__host__ __device__ __inline__ real distSqrd(real distX, real distY, real distZ)
+{
+    return distX * distX + distY * distY + distZ * distZ;
+}
+
+__host__ __device__ __inline__ real getBoundingSphereRadius(real diameter, real smearingWidth)
+{
+    return c1o2 * diameter + c4o1 * smearingWidth;
+}
+
+__host__ __device__ __inline__ bool inBoundingSphere(real distX, real distY, real distZ, real diameter, real smearingWidth)
+{
+    const real boundingSphereRadius = getBoundingSphereRadius(diameter, smearingWidth);
+    return distSqrd(distX, distY, distZ) < boundingSphereRadius * boundingSphereRadius;
+}
+
+__host__ __device__ __inline__ real gaussianSmearing(real distX, real distY, real distZ, real epsilon, real factorGaussian)
+{
+    return factorGaussian * exp(-distSqrd(distX, distY, distZ) / (epsilon * epsilon));
+}
+
+__inline__ void swapArrays(real* &arr1, real* &arr2)
+{
+    real* tmp = arr1;
+    arr1 = arr2;
+    arr2 = tmp;
+}
+
 #endif
diff --git a/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmStandalone.cu b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmStandalone.cu
new file mode 100644
index 0000000000000000000000000000000000000000..228795b8d891bd09715f520be5ae981711c80fb7
--- /dev/null
+++ b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmStandalone.cu
@@ -0,0 +1,126 @@
+//=======================================================================================
+// ____          ____    __    ______     __________   __      __       __        __
+// \    \       |    |  |  |  |   _   \  |___    ___| |  |    |  |     /  \      |  |
+//  \    \      |    |  |  |  |  |_)   |     |  |     |  |    |  |    /    \     |  |
+//   \    \     |    |  |  |  |   _   /      |  |     |  |    |  |   /  /\  \    |  |
+//    \    \    |    |  |  |  |  | \  \      |  |     |   \__/   |  /  ____  \   |  |____
+//     \    \   |    |  |__|  |__|  \__\     |__|      \________/  /__/    \__\  |_______|
+//      \    \  |    |   ________________________________________________________________
+//       \    \ |    |  |  ______________________________________________________________|
+//        \    \|    |  |  |         __          __     __     __     ______      _______
+//         \         |  |  |_____   |  |        |  |   |  |   |  |   |   _  \    /  _____)
+//          \        |  |   _____|  |  |        |  |   |  |   |  |   |  | \  \   \_______
+//           \       |  |  |        |  |_____   |   \_/   |   |  |   |  |_/  /    _____  |
+//            \ _____|  |__|        |________|   \_______/    |__|   |______/    (_______/
+//
+//  This file is part of VirtualFluids. VirtualFluids is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  VirtualFluids is distributed in the hope that it will be useful, but WITHOUT
+//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+//  for more details.
+//
+//  You should have received a copy of the GNU General Public License along
+//  with VirtualFluids (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file ActuatorFarm.cu
+//! \ingroup PreCollisionInteractor
+//! \author Henrik Asmuth, Henry Korb
+//======================================================================================
+#include "ActuatorFarmInlines.h"
+#include "ActuatorFarmStandalone.h"
+
+#include <cuda.h>
+#include <cuda_runtime.h>
+#include <helper_cuda.h>
+
+#include <logger/Logger.h>
+#include <cuda_helper/CudaGrid.h>
+#include <basics/constants/NumericConstants.h>
+#include <basics/writer/WbWriterVtkXmlBinary.h>
+
+#include "VirtualFluids_GPU/GPU/GeometryUtils.h"
+#include "LBM/GPUHelperFunctions/KernelUtilities.h"
+#include "Parameter/Parameter.h"
+#include "Parameter/CudaStreamManager.h"
+#include "DataStructureInitializer/GridProvider.h"
+#include "GPU/CudaMemoryManager.h"
+
+using namespace vf::basics::constant;
+
+
+std::vector<real> ActuatorFarmStandalone::computeBladeRadii(const real diameter, const uint numberOfNodesPerBlade){
+    const real dr = c1o2 * diameter / numberOfNodesPerBlade;
+    std::vector<real>blade_radii(numberOfNodesPerBlade);
+    for(uint node=0; node<numberOfNodesPerBlade; node++)
+        blade_radii[node] = dr * (node+0.5);
+    return blade_radii;
+}
+
+void ActuatorFarmStandalone::updateForcesAndCoordinates()
+{
+    const real lift_coefficient = c1o1;
+    const real drag_coefficient = c0o1;
+    const real c0 = 20 * c1o10;
+    const real delta_azimuth = c2Pi / this->numberOfBlades;
+
+    for (uint turbine = 0; turbine < this->numberOfTurbines; turbine++)
+    {
+        const real rotor_speed = this->rotorSpeeds[turbine];
+        const real azimuth_old = this->azimuths[turbine];
+        const real azimuth_new = azimuth_old + deltaT * rotor_speed;
+        this->azimuths[turbine] = azimuth_new > c2Pi ? azimuth_new - c2Pi : azimuth_new;
+
+        for (uint blade = 0; blade < this->numberOfBlades; blade++)
+        {
+            const real local_azimuth_new = azimuth_new + blade*delta_azimuth;
+
+            real last_node_radius = c0o1;
+            real current_node_radius = c0o1;
+            real next_node_radius = this->bladeRadii[0];
+
+            for (uint bladeNode = 0; bladeNode < this->numberOfNodesPerBlade; bladeNode++)
+            {
+                const uint node = calcNodeIndexInBladeArrays({ turbine, blade, bladeNode }, this->numberOfNodesPerBlade, this->numberOfBlades);
+
+                real u_rel, v_rel, w_rel;
+                rotateFromGlobalToBlade(u_rel, v_rel, w_rel,
+                                        this->bladeVelocitiesXH[node],
+                                        this->bladeVelocitiesYH[node],
+                                        this->bladeVelocitiesZH[node],
+                                        azimuth_old + delta_azimuth);
+                
+                last_node_radius = current_node_radius;
+                current_node_radius = next_node_radius;
+                next_node_radius = bladeNode < this->numberOfNodesPerBlade-1 ? this->bladeRadii[bladeNode+1] : this->diameter * c1o2;
+
+                const real dr = c1o2 * (next_node_radius - last_node_radius);
+
+                v_rel += current_node_radius * rotor_speed;
+                const real u_rel_sq = u_rel * u_rel + v_rel * v_rel;
+                const real phi = atan2(u_rel, v_rel);
+
+                const real tmp = c4o1 * current_node_radius / this->diameter - c1o1;
+                const real chord = c0 * sqrt(c1o1 - tmp * tmp);
+                const real normal_coefficient = lift_coefficient * cos(phi) + drag_coefficient * sin(phi);
+                const real tangential_coefficient = lift_coefficient * sin(phi) - drag_coefficient * cos(phi);
+                const real fx = -c1o2 * u_rel_sq * chord * this->density * normal_coefficient * dr;
+                const real fy = -c1o2 * u_rel_sq * chord * this->density * tangential_coefficient * dr;
+
+                rotateFromBladeToGlobal(fx, fy, c0o1,
+                                        this->bladeForcesXH[node], this->bladeForcesYH[node], this->bladeForcesZH[node],
+                                        local_azimuth_new);
+                rotateFromBladeToGlobal(c0o1, c0o1, current_node_radius,
+                                        this->bladeCoordsXH[node], this->bladeCoordsYH[node], this->bladeCoordsZH[node],
+                                        local_azimuth_new);
+                bladeCoordsXH[node] += this->turbinePosXH[turbine];
+                bladeCoordsYH[node] += this->turbinePosYH[turbine];
+                bladeCoordsZH[node] += this->turbinePosZH[turbine];
+            }
+        }
+    }
+}
+
diff --git a/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmStandalone.h b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmStandalone.h
new file mode 100644
index 0000000000000000000000000000000000000000..78f0ebf67e0cfd60683be7f36f25bf97eb839035
--- /dev/null
+++ b/src/gpu/core/PreCollisionInteractor/Actuator/ActuatorFarmStandalone.h
@@ -0,0 +1,34 @@
+#ifndef ActuatorFarmStandalone_H
+#define ActuatorFarmStandalone_H
+
+#include "ActuatorFarm.h"
+#include "basics/DataTypes.h"
+
+class ActuatorFarmStandalone : public ActuatorFarm
+{
+public:
+    ActuatorFarmStandalone(
+        const real diameter,
+        const uint numberOfNodesPerBlade,
+        const std::vector<real> turbinePositionsX,
+        const std::vector<real> turbinePositionsY,
+        const std::vector<real> turbinePositionsZ,
+        const std::vector<real> rotorSpeeds,
+        const real density,
+        const real smearingWidth,
+        const int level,
+        const real deltaT,
+        const real deltaX
+    ) : rotorSpeeds(rotorSpeeds),
+        ActuatorFarm(diameter, computeBladeRadii(diameter, numberOfNodesPerBlade), turbinePositionsX, turbinePositionsY, turbinePositionsZ, density, smearingWidth, level, deltaT, deltaX, true)
+    {}
+
+    void updateForcesAndCoordinates() override;
+    static std::vector<real> computeBladeRadii(const real diameter, const uint numberOfNodesPerBlade);
+    
+private:
+    std::vector<real> rotorSpeeds;
+
+};
+
+#endif