1//=======================================================================================
2// ____ ____ __ ______ __________ __ __ __ __
3// \ \ | | | | | _ \ |___ ___| | | | | / \ | |
4// \ \ | | | | | |_) | | | | | | | / \ | |
5// \ \ | | | | | _ / | | | | | | / /\ \ | |
6// \ \ | | | | | | \ \ | | | \__/ | / ____ \ | |____
7// \ \ | | |__| |__| \__\ |__| \________/ /__/ \__\ |_______|
8// \ \ | | ________________________________________________________________
9// \ \ | | | ______________________________________________________________|
10// \ \| | | | __ __ __ __ ______ _______
11// \ | | |_____ | | | | | | | | | _ \ / _____)
12// \ | | _____| | | | | | | | | | | \ \ \_______
13// \ | | | | |_____ | \_/ | | | | |_/ / _____ |
14// \ _____| |__| |________| \_______/ |__| |______/ (_______/
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.
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
26// SPDX-License-Identifier: GPL-3.0-or-later
27// SPDX-FileCopyrightText: Copyright © VirtualFluids Project contributors, see AUTHORS.md in root folder
29//! \addtogroup gpu_PreCollisionInteractor PreCollisionInteractor
30//! \ingroup gpu_core core
32//! \author Nils Horneff
33//======================================================================================
35#include "ActuatorFarmInlines.h"
36#include "ActuatorFarmStandaloneVAWT.h"
37#include "Cuda/CudaMemoryManager.h"
44#include <basics/constants/NumericConstants.h>
45#include <logger/Logger.h>
47using namespace vf::basics::constant;
50real interpMonotone(real x, const std::vector<real>& xs, const std::vector<real>& ys)
52 if (xs.empty() || ys.empty())
54 if (xs.size() != ys.size())
55 throw std::runtime_error("ActuatorFarmStandaloneVAWT::interpMonotone: xs and ys need same size.");
59 x = std::clamp(x, xs.front(), xs.back());
60 const auto it = std::upper_bound(xs.begin(), xs.end(), x);
61 const size_t i1 = static_cast<size_t>(it - xs.begin());
67 const size_t i0 = i1 - 1;
68 const real x0 = xs[i0];
69 const real x1 = xs[i1];
70 const real denom = std::max(x1 - x0, std::numeric_limits<real>::epsilon());
71 const real t = (x - x0) / denom;
72 return (c1o1 - t) * ys[i0] + t * ys[i1];
76using namespace vf::gpu;
78void ActuatorFarmStandaloneVAWT::init()
81 this->updateCoordinatesVAWT(c0o1, c0o1);
82 this->cudaMemoryManager->cudaCopyCoordsHtoD(this);
85std::vector<real> ActuatorFarmStandaloneVAWT::computeBladeRadii(const real diameter, const uint numberOfNodesPerBlade)
87 return std::vector<real>(numberOfNodesPerBlade, c1o2 * diameter);
90std::vector<real> ActuatorFarmStandaloneVAWT::computeBladeHeights(const real rotorHeight, const uint numberOfNodesPerBlade)
92 std::vector<real> bladeHeights(numberOfNodesPerBlade, c0o1);
93 if (numberOfNodesPerBlade == 0)
96 const real deltaZ = rotorHeight / static_cast<real>(numberOfNodesPerBlade);
97 for (uint i = 0; i < numberOfNodesPerBlade; ++i)
98 bladeHeights[i] = (static_cast<real>(i) + c1o2) * deltaZ;
102std::vector<real> ActuatorFarmStandaloneVAWT::solveGauss(std::vector<std::vector<real>> D, std::vector<real> b)
104 const uint n = static_cast<uint>(D.size());
105 constexpr real eps = real(1e-12);
107 for (uint k = 0; k < n; ++k) {
109 real maxAbs = std::fabs(D[k][k]);
110 for (uint row = k + 1; row < n; ++row) {
111 const real value = std::fabs(D[row][k]);
112 if (value > maxAbs) {
119 std::swap(D[pivotRow], D[k]);
120 std::swap(b[pivotRow], b[k]);
123 real pivot = D[k][k];
124 if (std::fabs(pivot) < eps) {
125 pivot = pivot >= c0o1 ? eps : -eps;
129 for (uint row = k + 1; row < n; ++row) {
130 const real lowerValue = D[row][k] / pivot;
131 D[row][k] = lowerValue;
132 for (uint col = k + 1; col < n; ++col)
133 D[row][col] -= lowerValue * D[k][col];
134 b[row] -= lowerValue * b[k];
138 for (int row = static_cast<int>(n) - 1; row >= 0; --row) {
139 for (uint col = static_cast<uint>(row) + 1; col < n; ++col)
140 b[static_cast<uint>(row)] -= D[static_cast<uint>(row)][col] * b[col];
142 real diagonal = D[static_cast<uint>(row)][static_cast<uint>(row)];
143 if (std::fabs(diagonal) < eps)
144 diagonal = diagonal >= c0o1 ? eps : -eps;
145 b[static_cast<uint>(row)] /= diagonal;
151std::vector<real> ActuatorFarmStandaloneVAWT::computeEndEffectsDistribution(
152 const real rotorHeight, const real bladeChord, const uint numberOfNodesPerBlade)
154 const uint n = numberOfNodesPerBlade;
155 std::vector<real> fEnd(n, c0o1);
159 std::vector<real> c(n, bladeChord);
160 std::vector<real> angleOfAttackRad(n, real(0.1));
161 std::vector<real> theta(n, c0o1);
162 std::vector<real> relVelMag(n, c1o1);
163 for (uint m = 0; m < n; ++m)
164 theta[m] = ((static_cast<real>(m) + c1o2) / static_cast<real>(n)) * cPi;
166 std::vector<std::vector<real>> D(n, std::vector<real>(n, c0o1));
167 for (uint i = 0; i < n; ++i) {
168 const real mode = static_cast<real>(i + 1);
169 for (uint m = 0; m < n; ++m) {
170 D[m][i] = ((c2o1 * rotorHeight) / (cPi * c[m])) * std::sin(mode * theta[m]) +
171 mode * std::sin(mode * theta[m]) / std::sin(theta[m]);
175 const std::vector<real> coefficients = solveGauss(std::move(D), std::move(angleOfAttackRad));
177 std::vector<real> circulation(n, c0o1);
178 std::vector<real> clDistribution(n, c0o1);
179 for (uint m = 0; m < n; ++m) {
181 for (uint i = 0; i < n; ++i) {
182 const real mode = static_cast<real>(i + 1);
183 sumA += coefficients[i] * std::sin(mode * theta[m]);
185 circulation[m] = c2o1 * rotorHeight * relVelMag[m] * sumA;
186 clDistribution[m] = circulation[m] / (c1o2 * c[m] * relVelMag[m]);
189 const real clMax = *std::max_element(clDistribution.begin(), clDistribution.end());
190 for (uint m = 0; m < n; ++m)
191 fEnd[m] = clDistribution[m] / clMax;
196real ActuatorFarmStandaloneVAWT::get_flowCurvature(const real vrel, const real xchord, const real rotorSpeed,
197 const real bladeChord)
199 return (xchord + c1o4) * bladeChord * rotorSpeed / vrel;
202void ActuatorFarmStandaloneVAWT::updateForcesAndCoordinates(real time, real deltaT)
204 this->updateCoordinatesVAWT(time, deltaT);
205 this->updateForcesVAWT(time, deltaT);
208void ActuatorFarmStandaloneVAWT::updateCoordinatesVAWT([[maybe_unused]] real time, const real deltaT)
210 const uint bladesPerTurbine = this->getNumberOfBladesPerTurbine();
211 const real deltaAzimuth = c2Pi / static_cast<real>(bladesPerTurbine);
214 for (uint turbine = 0; turbine < this->numberOfTurbines; ++turbine) {
215 const real azimuthNewRaw = this->azimuths[turbine] + deltaT * this->rotorSpeeds[turbine];
216 real azimuthNew = std::fmod(azimuthNewRaw, c2Pi);
217 if (azimuthNew < c0o1)
219 this->azimuths[turbine] = azimuthNew;
221 for (uint blade = 0; blade < bladesPerTurbine; ++blade) {
222 const real localAzimuthRad = azimuthNew + static_cast<real>(blade) * deltaAzimuth;
223 const real cosTheta = std::cos(localAzimuthRad);
224 const real sinTheta = std::sin(localAzimuthRad);
226 for (uint bladeNode = 0; bladeNode < this->numberOfPointsPerBlade; ++bladeNode) {
228 calcPointIndexInBladeArrays({ turbine, blade, bladeNode }, this->numberOfPointsPerBlade, bladesPerTurbine);
230 radius = this->bladeRadii[bladeNode];
231 const real localX = -sinTheta * radius;
232 const real localY = cosTheta * radius;
233 const real localZ = -this->rotorHeight * c1o2 + this->bladeHeights[bladeNode];
235 this->getAllBladeCoordsX()[node] = localX + this->turbinePosXH[turbine];
236 this->getAllBladeCoordsY()[node] = localY + this->turbinePosYH[turbine];
237 this->getAllBladeCoordsZ()[node] = localZ + this->turbinePosZH[turbine];
243void ActuatorFarmStandaloneVAWT::updateForcesVAWT([[maybe_unused]] real time, [[maybe_unused]] real deltaT)
245 if (this->numberOfPointsPerBlade == 0)
248 const uint bladesPerTurbine = this->getNumberOfBladesPerTurbine();
249 const real deltaAzimuth = c2Pi / static_cast<real>(bladesPerTurbine);
250 const real deltaZ = this->rotorHeight / static_cast<real>(this->numberOfPointsPerBlade);
252 const real dynamicPressureReference = c1o2 * this->para->getDensityRatio() * this->velocityInlet * this->velocityInlet;
254 for (uint turbine = 0; turbine < this->numberOfTurbines; ++turbine) {
255 const real rotorSpeed = this->rotorSpeeds[turbine];
256 const real azimuthRad = this->azimuths[turbine];
258 for (uint blade = 0; blade < bladesPerTurbine; ++blade) {
259 const real localAzimuthRad = azimuthRad + static_cast<real>(blade) * deltaAzimuth;
260 const real cosTheta = std::cos(localAzimuthRad);
261 const real sinTheta = std::sin(localAzimuthRad);
263 for (uint bladeNode = 0; bladeNode < this->numberOfPointsPerBlade; ++bladeNode) {
265 calcPointIndexInBladeArrays({ turbine, blade, bladeNode }, this->numberOfPointsPerBlade, bladesPerTurbine);
266 radius = this->bladeRadii[bladeNode];
268 const real velocityX = this->getAllBladeVelocitiesX()[node];
269 const real velocityY = this->getAllBladeVelocitiesY()[node];
271 const real velocityNormal = sinTheta * velocityX - cosTheta * velocityY;
272 const real velocityTangential = cosTheta * velocityX + sinTheta * velocityY + rotorSpeed * radius;
273 const real flowAngleRad = std::atan2(velocityNormal, velocityTangential);
274 const real vrelSq = velocityNormal * velocityNormal + velocityTangential * velocityTangential;
275 const real vrel = std::max(std::sqrt(vrelSq), real(1e-6));
277 real flowCurvatureCorrection = c0o1;
278 if (this->flagFlowCurvature)
279 flowCurvatureCorrection =
280 get_flowCurvature(vrel, this->bladeMountingPoint, rotorSpeed, this->bladeChord);
282 const real angleOfAttackRad = flowAngleRad + flowCurvatureCorrection - this->bladePitch*cPio180;
283 real polarLiftCoefficient = interpMonotone(angleOfAttackRad * c180oPi, this->polarAngleOfAttackDeg, this->polarLiftCoefficient);
284 const real polarDragCoefficient = interpMonotone(angleOfAttackRad * c180oPi, this->polarAngleOfAttackDeg, this->polarDragCoefficient);
286 if (this->flagEndEffects)
287 polarLiftCoefficient *= this->endEffectsDistribution[bladeNode];
289 const real dynamicPressure = c1o2 * this->para->getDensityRatio() * vrelSq;
290 const real lift = dynamicPressure * this->bladeChord * deltaZ * polarLiftCoefficient;
291 const real drag = dynamicPressure * this->bladeChord * deltaZ * polarDragCoefficient;
292 const real pn = lift * std::cos(flowAngleRad) + drag * std::sin(flowAngleRad);
293 const real pt = lift * std::sin(flowAngleRad) - drag * std::cos(flowAngleRad);
295 this->getAllBladeForcesX()[node] = -(pn * sinTheta - pt * cosTheta);
296 this->getAllBladeForcesY()[node] = -(-pn * cosTheta - pt * sinTheta);
297 this->getAllBladeForcesZ()[node] = c0o1;
298 if (this->useLocalSmearingWidth()) {
299 const real epsilonChord = c1o4 * this->bladeChord;
300 const real epsilonDrag = c1o2 * polarDragCoefficient * this->bladeChord;
301 const real epsilonMesh = c4o1 * this->para->getScaledLengthRatio(this->level);
302 this->getAllBladeLocalSmearingWidth()[node] = std::max(epsilonChord, std::max(epsilonDrag, epsilonMesh));
305 const real normalizedForceScale = std::max(dynamicPressureReference * radius * deltaZ, real(1e-12));
306 this->forceNormal[node] = pn / normalizedForceScale;
307 this->forceTangential[node] = pt / normalizedForceScale;
308 this->angleOfAttackDeg[node] = angleOfAttackRad*c180oPi;
310 real azimuthWrapped = std::fmod(localAzimuthRad, c2Pi);
311 if (azimuthWrapped < c0o1)
312 azimuthWrapped += c2Pi;
313 this->azimuthDeg[node] = azimuthWrapped*c180oPi;
319void ActuatorFarmStandaloneVAWT::appendOutputData(std::vector<std::string>& dataNames,
320 std::vector<std::vector<double>>& nodeData) const
322 const uint totalPoints = this->getTotalNumberOfPoints();
324 const auto appendField = [&](const std::string& name, const std::vector<real>& source) {
325 dataNames.push_back(name);
326 std::vector<double> values(totalPoints, 0.0);
327 for (size_t i = 0; i < static_cast<size_t>(this->numberOfBladePoints); ++i)
328 values[i] = source[i];
330 nodeData.push_back(std::move(values));
333 appendField("forceNormal", this->forceNormal);
334 appendField("forceTangential", this->forceTangential);
335 appendField("angleOfAttackDeg", this->angleOfAttackDeg);
336 appendField("azimuthDeg", this->azimuthDeg);