VirtualFluids 0.2.0
Parallel CFD LBM Solver
Loading...
Searching...
No Matches
ActuatorFarmStandalone.cu
Go to the documentation of this file.
1//=======================================================================================
2// ____ ____ __ ______ __________ __ __ __ __
3// \ \ | | | | | _ \ |___ ___| | | | | / \ | |
4// \ \ | | | | | |_) | | | | | | | / \ | |
5// \ \ | | | | | _ / | | | | | | / /\ \ | |
6// \ \ | | | | | | \ \ | | | \__/ | / ____ \ | |____
7// \ \ | | |__| |__| \__\ |__| \________/ /__/ \__\ |_______|
8// \ \ | | ________________________________________________________________
9// \ \ | | | ______________________________________________________________|
10// \ \| | | | __ __ __ __ ______ _______
11// \ | | |_____ | | | | | | | | | _ \ / _____)
12// \ | | _____| | | | | | | | | | | \ \ \_______
13// \ | | | | |_____ | \_/ | | | | |_/ / _____ |
14// \ _____| |__| |________| \_______/ |__| |______/ (_______/
15//
16// This file is part of VirtualFluids. VirtualFluids is free software: you can
17// redistribute it and/or modify it under the terms of the GNU General Public
18// License as published by the Free Software Foundation, either version 3 of
19// the License, or (at your option) any later version.
20//
21// VirtualFluids is distributed in the hope that it will be useful, but WITHOUT
22// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
23// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24// for more details.
25//
26// SPDX-License-Identifier: GPL-3.0-or-later
27// SPDX-FileCopyrightText: Copyright © VirtualFluids Project contributors, see AUTHORS.md in root folder
28//
29//! \addtogroup gpu_PreCollisionInteractor PreCollisionInteractor
30//! \ingroup gpu_core core
31//! \{
32//! \author Henrik Asmuth, Henry Korb, Nils Horneff
33//======================================================================================
34#include "ActuatorFarmInlines.h"
35#include "ActuatorFarmStandalone.h"
36
37#include <cmath>
38#include <cuda.h>
39#include <cuda_runtime.h>
40#include <helper_cuda.h>
41
42#include <basics/constants/NumericConstants.h>
43#include <basics/writer/WbWriterVtkXmlBinary.h>
44#include <cuda_helper/CudaGrid.h>
45#include <logger/Logger.h>
46
47using namespace vf::basics::constant;
48namespace vf::gpu {
49
50std::vector<real> ActuatorFarmStandalone::computeBladeRadii(const real diameter, const uint numberOfPointsPerBlade)
51{
52 const real dr = c1o2 * diameter / numberOfPointsPerBlade;
53 std::vector<real> bladeRadii(numberOfPointsPerBlade);
54 for (uint point = 0; point < numberOfPointsPerBlade; point++)
55 bladeRadii[point] = dr * (point + c1o2);
56 return bladeRadii;
57}
58
59void ActuatorFarmStandalone::updateForcesAndCoordinates([[maybe_unused]] real time, real deltaT)
60{
61 this->updateBladeForcesAndCoordinates(deltaT);
62 if (numberOfHubPoints > 0)
63 this->updateHubForces();
64 if (numberOfTowerPoints > 0)
65 this->updateTowerForces();
66}
67
68void ActuatorFarmStandalone::updateBladeForcesAndCoordinates(real deltaT)
69{
70 const real c0 = c20o1 * c1o10;
71 const real deltaAzimuth = c2Pi / this->numberOfBlades;
72 const real liftCoefficient = c1o1;
73 const real dragCoefficient = c0o1;
74 const bool overrideNormalCoefficient = this->bladeNormalCoefficients.has_value();
75
76 for (uint turbine = 0; turbine < this->numberOfTurbines; turbine++) {
77 const real rotorSpeed = this->rotorSpeeds[turbine];
78 const real azimuthOld = this->azimuths[turbine];
79 const real azimuthNew = azimuthOld + deltaT * rotorSpeed;
80 this->azimuths[turbine] = azimuthNew > c2Pi ? azimuthNew - c2Pi : azimuthNew;
81
82 for (uint blade = 0; blade < this->numberOfBlades; blade++) {
83 const real localAzimuthOld = azimuthOld + blade * deltaAzimuth;
84 const real localAzimuthNew = azimuthNew + blade * deltaAzimuth;
85
86 real lastPointRadius = c0o1;
87 real currentPointradius = c0o1;
88 real nextPointradius = this->bladeRadii[0];
89
90 for (uint bladePoint = 0; bladePoint < this->numberOfPointsPerBlade; bladePoint++) {
91 const uint point = calcPointIndexInBladeArrays({ turbine, blade, bladePoint }, this->numberOfPointsPerBlade,
92 this->numberOfBlades);
93
94 real uRel, vRel, wRel;
95 rotateFromGlobalToBlade(uRel, vRel, wRel, this->getAllBladeVelocitiesX()[point],
96 this->getAllBladeVelocitiesY()[point], this->getAllBladeVelocitiesZ()[point],
97 localAzimuthOld);
98
99 lastPointRadius = currentPointradius;
100 currentPointradius = nextPointradius;
101 nextPointradius = bladePoint < this->numberOfPointsPerBlade - 1 ? this->bladeRadii[bladePoint + c1o1]
102 : this->diameter * c1o2;
103
104 const real dr = c1o2 * (nextPointradius - lastPointRadius);
105
106 vRel += currentPointradius * rotorSpeed;
107 const real uRelSq = uRel * uRel + vRel * vRel;
108 const real phi = std::atan2(uRel, vRel);
109
110 const real tmp = c4o1 * currentPointradius / this->diameter - c1o1;
111 const real chord = c0 * std::sqrt(c1o1 - tmp * tmp);
112 const real normalCoefficient = liftCoefficient * std::cos(phi) + dragCoefficient * std::sin(phi);
113 const real tangentialCoefficient = liftCoefficient * std::sin(phi) - dragCoefficient * std::cos(phi);
114 const real appliedNormalCoefficient = overrideNormalCoefficient
115 ? this->bladeNormalCoefficients.value()[bladePoint]
116 : normalCoefficient;
117 const real appliedTangentialCoefficient = overrideNormalCoefficient
118 ? c0o1
119 : tangentialCoefficient;
120 const real fx = -c1o2 * uRelSq * chord * para->getDensityRatio() * appliedNormalCoefficient * dr;
121 const real fy = -c1o2 * uRelSq * chord * para->getDensityRatio() * appliedTangentialCoefficient * dr;
122
123 rotateFromBladeToGlobal(fx, fy, c0o1, this->getAllBladeForcesX()[point], this->getAllBladeForcesY()[point],
124 this->getAllBladeForcesZ()[point], localAzimuthNew);
125 rotateFromBladeToGlobal(c0o1, c0o1, currentPointradius, this->getAllBladeCoordsX()[point],
126 this->getAllBladeCoordsY()[point], this->getAllBladeCoordsZ()[point],
127 localAzimuthNew);
128 getAllBladeCoordsX()[point] += this->turbinePosXH[turbine];
129 getAllBladeCoordsY()[point] += this->turbinePosYH[turbine];
130 getAllBladeCoordsZ()[point] += this->turbinePosZH[turbine];
131 }
132 }
133 }
134}
135
136void ActuatorFarmStandalone::updateHubForces()
137{
138 // Streamlined cylinder physics
139 const real segmentLength = hubLength / static_cast<real>(numberOfHubPointsPerTurbine);
140 const real surfaceAreaPerSegment = c2o1 * cPi * hubRadius * segmentLength; // Cylinder surface area per segment
141 const real frontBackArea = cPi * hubRadius * hubRadius; // Cross-sectional area for pressure drag
142
143 for (uint pointIndex = 0; pointIndex < numberOfHubPoints; pointIndex++) {
144 // Get sampled velocity components
145 const real velX = this->getAllHubVelocitiesX()[pointIndex];
146 const real velY = this->getAllHubVelocitiesY()[pointIndex];
147 const real velZ = this->getAllHubVelocitiesZ()[pointIndex];
148
149 const real velocityMagnitude = std::hypot(velX, velY, velZ);
150
151 // SKIN FRICTION DRAG (dominant for streamlined cylinder)
152 real forceSkinX = c0o1, forceSkinY = c0o1, forceSkinZ = c0o1;
153 if (velocityMagnitude > c10eM12) { // Avoid division by zero
154 const real skinFrictionMagnitude = this->hubSkinFrictionCoeff.value() * c1o2 * para->getDensityRatio() *
155 surfaceAreaPerSegment * velocityMagnitude * velocityMagnitude;
156 // Skin friction acts opposite to velocity direction
157 forceSkinX = -skinFrictionMagnitude * (velX / velocityMagnitude);
158 forceSkinY = -skinFrictionMagnitude * (velY / velocityMagnitude);
159 forceSkinZ = -skinFrictionMagnitude * (velZ / velocityMagnitude);
160 }
161
162 // SMALL PRESSURE DRAG (front/back faces contribution)
163 const real pressureDragAreaPerSegment = frontBackArea / static_cast<real>(numberOfHubPointsPerTurbine);
164 const real forcePressureX = -c1o2 * para->getDensityRatio() * this->hubDragCoeff.value() *
165 pressureDragAreaPerSegment * velX * std::abs(velX);
166
167 // Minor crossflow pressure effects
168 const real crossflowFactor = c1o10; // Much smaller than main pressure drag
169 const real forcePressureY = -crossflowFactor * c1o2 * para->getDensityRatio() * this->hubDragCoeff.value() *
170 pressureDragAreaPerSegment * velY * std::abs(velY);
171 const real forcePressureZ = -crossflowFactor * c1o2 * para->getDensityRatio() * this->hubDragCoeff.value() *
172 pressureDragAreaPerSegment * velZ * std::abs(velZ);
173
174 // TOTAL FORCE
175 this->getAllHubForcesX()[pointIndex] = forceSkinX + forcePressureX;
176 this->getAllHubForcesY()[pointIndex] = forceSkinY + forcePressureY;
177 this->getAllHubForcesZ()[pointIndex] = forceSkinZ + forcePressureZ;
178 }
179}
180
181void ActuatorFarmStandalone::updateTowerForces()
182{
183 for (uint turbine = 0; turbine < this->numberOfTurbines; turbine++) {
184 const real segmentHeight = towerHeights[turbine] / static_cast<real>(numberOfTowerPointsPerTurbine);
185 const real projectedArea = c2o1 * towerRadius * segmentHeight; // Crossflow projected area
186
187 for (uint point = 0; point < numberOfTowerPointsPerTurbine; point++) {
188 const uint pointIndex = turbine * numberOfTowerPointsPerTurbine + point;
189 const real velX = this->getAllTowerVelocitiesX()[pointIndex];
190 const real velY = this->getAllTowerVelocitiesY()[pointIndex];
191 const real velZ = this->getAllTowerVelocitiesZ()[pointIndex];
192
193 // MAIN EFFECT: Horizontal crossflow drag (dominates wake formation)
194 const real velHorizontalMag = std::hypot(velX, velY);
195 real forceX = c0o1, forceY = c0o1, forceZ = c0o1;
196
197 if (velHorizontalMag > c10eM12) {
198 const real dragMagnitude = c1o2 * para->getDensityRatio() * this->towerDragCoeff.value() * projectedArea *
199 velHorizontalMag * velHorizontalMag;
200 forceX = -dragMagnitude * (velX / velHorizontalMag);
201 forceY = -dragMagnitude * (velY / velHorizontalMag);
202 }
203
204 forceZ = c0o1 * velZ; // Simplification: No vertical drag. Can be implemented if needed.
205
206 // No viscous effects
207 this->getAllTowerForcesX()[pointIndex] = forceX;
208 this->getAllTowerForcesY()[pointIndex] = forceY;
209 this->getAllTowerForcesZ()[pointIndex] = forceZ;
210 }
211 }
212}
213}
214
215//! \}