VirtualFluids 0.2.0
Parallel CFD LBM Solver
Loading...
Searching...
No Matches
ActuatorFarmStandaloneVAWT.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 Nils Horneff
33//======================================================================================
34
35#include "ActuatorFarmInlines.h"
36#include "ActuatorFarmStandaloneVAWT.h"
37#include "Cuda/CudaMemoryManager.h"
38
39#include <algorithm>
40#include <cmath>
41#include <limits>
42#include <stdexcept>
43
44#include <basics/constants/NumericConstants.h>
45#include <logger/Logger.h>
46
47using namespace vf::basics::constant;
48
49namespace {
50real interpMonotone(real x, const std::vector<real>& xs, const std::vector<real>& ys)
51{
52 if (xs.empty() || ys.empty())
53 return c0o1;
54 if (xs.size() != ys.size())
55 throw std::runtime_error("ActuatorFarmStandaloneVAWT::interpMonotone: xs and ys need same size.");
56 if (xs.size() == 1)
57 return ys[0];
58
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());
62 if (i1 == 0)
63 return ys[0];
64 if (i1 >= xs.size())
65 return ys.back();
66
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];
73}
74} // namespace
75
76using namespace vf::gpu;
77
78void ActuatorFarmStandaloneVAWT::init()
79{
80 ActuatorFarm::init();
81 this->updateCoordinatesVAWT(c0o1, c0o1);
82 this->cudaMemoryManager->cudaCopyCoordsHtoD(this);
83}
84
85std::vector<real> ActuatorFarmStandaloneVAWT::computeBladeRadii(const real diameter, const uint numberOfNodesPerBlade)
86{
87 return std::vector<real>(numberOfNodesPerBlade, c1o2 * diameter);
88}
89
90std::vector<real> ActuatorFarmStandaloneVAWT::computeBladeHeights(const real rotorHeight, const uint numberOfNodesPerBlade)
91{
92 std::vector<real> bladeHeights(numberOfNodesPerBlade, c0o1);
93 if (numberOfNodesPerBlade == 0)
94 return bladeHeights;
95
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;
99 return bladeHeights;
100}
101
102std::vector<real> ActuatorFarmStandaloneVAWT::solveGauss(std::vector<std::vector<real>> D, std::vector<real> b)
103{
104 const uint n = static_cast<uint>(D.size());
105 constexpr real eps = real(1e-12);
106
107 for (uint k = 0; k < n; ++k) {
108 uint pivotRow = 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) {
113 maxAbs = value;
114 pivotRow = row;
115 }
116 }
117
118 if (pivotRow != k) {
119 std::swap(D[pivotRow], D[k]);
120 std::swap(b[pivotRow], b[k]);
121 }
122
123 real pivot = D[k][k];
124 if (std::fabs(pivot) < eps) {
125 pivot = pivot >= c0o1 ? eps : -eps;
126 D[k][k] = pivot;
127 }
128
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];
135 }
136 }
137
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];
141
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;
146 }
147
148 return b;
149}
150
151std::vector<real> ActuatorFarmStandaloneVAWT::computeEndEffectsDistribution(
152 const real rotorHeight, const real bladeChord, const uint numberOfNodesPerBlade)
153{
154 const uint n = numberOfNodesPerBlade;
155 std::vector<real> fEnd(n, c0o1);
156 if (n == 0)
157 return fEnd;
158
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;
165
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]);
172 }
173 }
174
175 const std::vector<real> coefficients = solveGauss(std::move(D), std::move(angleOfAttackRad));
176
177 std::vector<real> circulation(n, c0o1);
178 std::vector<real> clDistribution(n, c0o1);
179 for (uint m = 0; m < n; ++m) {
180 real sumA = c0o1;
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]);
184 }
185 circulation[m] = c2o1 * rotorHeight * relVelMag[m] * sumA;
186 clDistribution[m] = circulation[m] / (c1o2 * c[m] * relVelMag[m]);
187 }
188
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;
192
193 return fEnd;
194}
195
196real ActuatorFarmStandaloneVAWT::get_flowCurvature(const real vrel, const real xchord, const real rotorSpeed,
197 const real bladeChord)
198{
199 return (xchord + c1o4) * bladeChord * rotorSpeed / vrel;
200}
201
202void ActuatorFarmStandaloneVAWT::updateForcesAndCoordinates(real time, real deltaT)
203{
204 this->updateCoordinatesVAWT(time, deltaT);
205 this->updateForcesVAWT(time, deltaT);
206}
207
208void ActuatorFarmStandaloneVAWT::updateCoordinatesVAWT([[maybe_unused]] real time, const real deltaT)
209{
210 const uint bladesPerTurbine = this->getNumberOfBladesPerTurbine();
211 const real deltaAzimuth = c2Pi / static_cast<real>(bladesPerTurbine);
212 real radius = c0o1;
213
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)
218 azimuthNew += c2Pi;
219 this->azimuths[turbine] = azimuthNew;
220
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);
225
226 for (uint bladeNode = 0; bladeNode < this->numberOfPointsPerBlade; ++bladeNode) {
227 const uint node =
228 calcPointIndexInBladeArrays({ turbine, blade, bladeNode }, this->numberOfPointsPerBlade, bladesPerTurbine);
229
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];
234
235 this->getAllBladeCoordsX()[node] = localX + this->turbinePosXH[turbine];
236 this->getAllBladeCoordsY()[node] = localY + this->turbinePosYH[turbine];
237 this->getAllBladeCoordsZ()[node] = localZ + this->turbinePosZH[turbine];
238 }
239 }
240 }
241}
242
243void ActuatorFarmStandaloneVAWT::updateForcesVAWT([[maybe_unused]] real time, [[maybe_unused]] real deltaT)
244{
245 if (this->numberOfPointsPerBlade == 0)
246 return;
247
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);
251 real radius = c0o1;
252 const real dynamicPressureReference = c1o2 * this->para->getDensityRatio() * this->velocityInlet * this->velocityInlet;
253
254 for (uint turbine = 0; turbine < this->numberOfTurbines; ++turbine) {
255 const real rotorSpeed = this->rotorSpeeds[turbine];
256 const real azimuthRad = this->azimuths[turbine];
257
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);
262
263 for (uint bladeNode = 0; bladeNode < this->numberOfPointsPerBlade; ++bladeNode) {
264 const uint node =
265 calcPointIndexInBladeArrays({ turbine, blade, bladeNode }, this->numberOfPointsPerBlade, bladesPerTurbine);
266 radius = this->bladeRadii[bladeNode];
267
268 const real velocityX = this->getAllBladeVelocitiesX()[node];
269 const real velocityY = this->getAllBladeVelocitiesY()[node];
270
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));
276
277 real flowCurvatureCorrection = c0o1;
278 if (this->flagFlowCurvature)
279 flowCurvatureCorrection =
280 get_flowCurvature(vrel, this->bladeMountingPoint, rotorSpeed, this->bladeChord);
281
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);
285
286 if (this->flagEndEffects)
287 polarLiftCoefficient *= this->endEffectsDistribution[bladeNode];
288
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);
294
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));
303 }
304
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;
309
310 real azimuthWrapped = std::fmod(localAzimuthRad, c2Pi);
311 if (azimuthWrapped < c0o1)
312 azimuthWrapped += c2Pi;
313 this->azimuthDeg[node] = azimuthWrapped*c180oPi;
314 }
315 }
316 }
317}
318
319void ActuatorFarmStandaloneVAWT::appendOutputData(std::vector<std::string>& dataNames,
320 std::vector<std::vector<double>>& nodeData) const
321{
322 const uint totalPoints = this->getTotalNumberOfPoints();
323
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];
329
330 nodeData.push_back(std::move(values));
331 };
332
333 appendField("forceNormal", this->forceNormal);
334 appendField("forceTangential", this->forceTangential);
335 appendField("angleOfAttackDeg", this->angleOfAttackDeg);
336 appendField("azimuthDeg", this->azimuthDeg);
337}
338
339//! \}