/home/runner/work/amr-wind/amr-wind/amr-wind/wind_energy/actuator/turbine/external/turbine_external_utils.H Source File

AMR-Wind API: /home/runner/work/amr-wind/amr-wind/amr-wind/wind_energy/actuator/turbine/external/turbine_external_utils.H Source File
AMR-Wind API v0.1.0
CFD solver for wind plant simulations
Loading...
Searching...
No Matches
turbine_external_utils.H
Go to the documentation of this file.
1#ifndef TURBINE_EXTERNAL_UTILS_H
2#define TURBINE_EXTERNAL_UTILS_H
3
4#include <numbers>
11#include "AMReX_REAL.H"
12
13using namespace amrex::literals;
14
16
18{
19 const auto x = eps.x();
20 const auto y = eps.y();
21 eps.x() = y;
22 eps.y() = x;
23}
24
25template <typename datatype>
26void make_component_views(datatype& data)
27{
28 auto& grid = data.grid();
29 auto& tdata = data.meta();
30 const int num_blades = tdata.num_blades;
31 const int num_pts_blade = tdata.num_pts_blade;
32 const int num_vel_pts_blade = tdata.num_vel_pts_blade;
33
34 for (int ib = 0; ib < num_blades; ++ib) {
36
37 // This might be another problem! for Kynema vs OpenFAST
38 // Only relevant for disks, I think. Would need to adjust start_vel
39 const auto start = ib * num_pts_blade + 1;
40 const auto start_vel = ib * num_vel_pts_blade;
41 // clang-format off
43 grid.pos, start, num_pts_blade);
45 grid.force, start, num_pts_blade);
47 grid.epsilon, start, num_pts_blade);
49 grid.orientation, start, num_pts_blade);
51 tdata.chord, start, num_pts_blade);
53 tdata.vel_rel, start, num_pts_blade);
55 grid.vel, start_vel, num_vel_pts_blade);
57 grid.vel_pos, start_vel, num_vel_pts_blade);
58 // clang-format on
59
60 tdata.blades.emplace_back(cv);
61 }
62 if (tdata.num_pts_tower > 0) {
63 const int num_pts_tower = tdata.num_pts_tower;
64 const int ntwr_start = num_blades * num_pts_blade + 1;
65 auto& cv = tdata.tower;
66
67 cv.pos = ::amr_wind::utils::slice(grid.pos, ntwr_start, num_pts_tower);
68 cv.force =
69 ::amr_wind::utils::slice(grid.force, ntwr_start, num_pts_tower);
70 cv.epsilon =
71 ::amr_wind::utils::slice(grid.epsilon, ntwr_start, num_pts_tower);
72 cv.orientation = ::amr_wind::utils::slice(
73 grid.orientation, ntwr_start, num_pts_tower);
74 cv.chord =
75 ::amr_wind::utils::slice(tdata.chord, ntwr_start, num_pts_tower);
76 }
77 {
78 auto& cv = tdata.hub;
79 cv.pos = ::amr_wind::utils::slice(grid.pos, 0, 1);
80 cv.force = ::amr_wind::utils::slice(grid.force, 0, 1);
81 cv.epsilon = ::amr_wind::utils::slice(grid.epsilon, 0, 1);
82 cv.orientation = ::amr_wind::utils::slice(grid.orientation, 0, 1);
83 cv.chord = ::amr_wind::utils::slice(tdata.chord, 0, 1);
84 }
85}
86
87template <typename datatype>
88void init_epsilon(datatype& data)
89{
90 auto& tdata = data.meta();
91
92 // Swap order of epsilon based on turbine orientation
93 // Input order is (chord, span, thickness)
94 // Output order should depend on turbine type; currently ...
95 // -- this function is correct for Kynema
96 // -- this function is erroneous for OpenFAST but has been in use
97 // (Does not matter when epsilon is uniform)
98 swap_epsilon(tdata.eps_inp);
99 swap_epsilon(tdata.eps_min);
100 swap_epsilon(tdata.eps_chord);
101 swap_epsilon(tdata.eps_tower);
102
103 {
104 const auto& cd = tdata.nacelle_cd;
105 const auto& area = tdata.nacelle_area;
106 const auto eps =
107 std::sqrt(2.0_rt / std::numbers::pi_v<amrex::Real> * cd * area);
108
109 auto& nac_eps = data.grid().epsilon[0];
110 nac_eps.x() = amrex::max<amrex::Real>(eps, tdata.eps_min.x());
111 nac_eps.y() = amrex::max<amrex::Real>(eps, tdata.eps_min.y());
112 nac_eps.z() = amrex::max<amrex::Real>(eps, tdata.eps_min.z());
113 }
114
115 for (int ib = 0; ib < tdata.num_blades; ++ib) {
116 auto& cv = tdata.blades[ib];
117
118 for (int i = 0; i < tdata.num_pts_blade; ++i) {
119 const auto eps_crd = tdata.eps_chord * cv.chord[i];
120
121 for (int n = 0; n < AMREX_SPACEDIM; ++n) {
122 cv.epsilon[i][n] = amrex::max<amrex::Real>(
123 tdata.eps_min[n], tdata.eps_inp[n], eps_crd[n]);
124 }
125 }
126 }
127 {
128 auto& cv = tdata.tower;
129 for (int i = 0; i < tdata.num_pts_tower; ++i) {
130 for (int n = 0; n < AMREX_SPACEDIM; ++n) {
131 cv.epsilon[i][n] = amrex::max<amrex::Real>(
132 tdata.eps_min[n], tdata.eps_inp[n], tdata.eps_tower[n]);
133 }
134 }
135 }
136}
137
138template <typename datatype>
139void compute_nacelle_force(datatype& data)
140{
141 if (!data.info().is_root_proc) {
142 return;
143 }
144
145 const auto& cd = data.meta().nacelle_cd;
146 const auto& area = data.meta().nacelle_area;
147 const auto& cd_area = cd * area;
148 const auto& ext_tdata = data.meta().ext_data;
149 const auto& rho = data.meta().density;
150
151 const auto& eps = data.grid().epsilon[0].x();
152 // This assumes a static nacelle
153 vs::Vector vel{
154 ext_tdata.fluid_velocity(0)[0], ext_tdata.fluid_velocity(1)[0],
155 ext_tdata.fluid_velocity(2)[0]};
156 amrex::Real correction = 0.0_rt;
157 if (eps > 0.0_rt) {
158 amrex::Real fac =
159 1.0_rt -
160 (cd_area) / (2.0_rt * ::amr_wind::utils::two_pi() * eps * eps);
161 correction = 1.0_rt / fac;
162 }
163 amrex::Real coeff =
164 0.5_rt * rho * cd_area * vs::mag(vel) * correction * correction;
165
166 for (int dir = 0; dir < AMREX_SPACEDIM; ++dir) {
167 ext_tdata.force(dir)[0] = static_cast<float>(coeff * vel[dir]);
168 }
169}
170
171template <typename datatype>
172void ext_step(datatype& data)
173{
174 if (!data.info().is_root_proc) {
175 return;
176 }
177
178 auto& meta = data.meta();
179 auto& tf = data.meta().ext_data;
180 if (tf.is_solution0) {
181 meta.ext_ptr->init_solution(tf.tid_local);
182 } else {
183 meta.ext_ptr->advance_turbine(tf.tid_local);
184 }
185
186 meta.ext_ptr->get_hub_stats(tf.tid_local);
187
188 // Populate nacelle force into the OpenFAST data structure so that it
189 // gets broadcasted to all influenced processes in subsequent scattering
190 // of data.
192}
193
194template <typename datatype>
195void scatter_data(datatype& data)
196{
197 if (!data.info().actuator_in_proc) {
198 return;
199 }
200
201 // Create an MPI transfer buffer that packs all data in one contiguous
202 // array. 3 floats for the position vector, 3 floats for the force
203 // vector, and 9 floats for the orientation matrix = 15 floats per
204 // actuator node.
205 const auto dsize = data.grid().pos.size() * 15;
206 amrex::Vector<float> buf(dsize);
207
208 // Copy data into MPI send/recv buffer from the OpenFAST data structure.
209 // Note, other procs do not have a valid data in those pointers.
210 if (data.info().is_root_proc) {
211 BL_PROFILE("amr-wind::actuator::external::compute_force_op::scatter1");
212 const auto& ext_tdata = data.meta().ext_data;
213 auto it = buf.begin();
214 for (int dir = 0; dir < AMREX_SPACEDIM; ++dir) {
215 std::copy(
216 ext_tdata.force(dir),
217 ext_tdata.force(dir) + ext_tdata.length_force(dir), it);
218 std::advance(it, ext_tdata.length_force(dir));
219 }
220 for (int dir = 0; dir < AMREX_SPACEDIM; ++dir) {
221 std::copy(
222 ext_tdata.position_at_force(dir),
223 ext_tdata.position_at_force(dir) +
224 ext_tdata.length_position_at_force(dir),
225 it);
226 std::advance(it, ext_tdata.length_position_at_force(dir));
227 }
228
229 // clang-format off
230 std::copy(ext_tdata.orientation(),
231 ext_tdata.orientation() + ext_tdata.length_orientation(), it);
232 // clang-format on
233 }
234
235 // Broadcast data to all influenced procs from the root process
236 const auto& procs = data.info().procs;
237 const int tag = 1001;
238 if (data.info().is_root_proc) {
239 BL_PROFILE("amr-wind::actuator::external::compute_force_op::scatter2");
240 for (const int ip : procs) {
241 if (ip == data.info().root_proc) {
242 continue;
243 }
244
245 amrex::ParallelDescriptor::Send(
246 buf.data(), dsize, ip, tag, data.meta().tcomm);
247 }
248 } else {
249 BL_PROFILE("amr-wind::actuator::external::compute_force_op::scatter2");
250 amrex::ParallelDescriptor::Recv(
251 buf.data(), dsize, data.info().root_proc, tag, data.meta().tcomm);
252 }
253
254 // Populate the actuator grid data structures with data from the MPI
255 // send/recv buffer.
256 {
257 BL_PROFILE("amr-wind::actuator::external::compute_force_op::scatter3");
258 const auto& bp = data.info().base_pos;
259 auto& grid = data.grid();
260 const auto& npts = grid.pos.size();
261 const auto& rho = data.meta().density;
262 const size_t ifx = 0;
263 const size_t ify = ifx + npts;
264 const size_t ifz = ify + npts;
265 const size_t ipx = ifz + npts;
266 const size_t ipy = ipx + npts;
267 const size_t ipz = ipy + npts;
268 const size_t iori = ipz + npts;
269
270 for (int i = 0; i < npts; ++i) {
271 // Aerodynamic force vectors. Flip sign to get force on fluid.
272 // Divide by density as the source term computation will
273 // multiply by density before adding to momentum equation.
274 //
275 grid.force[i].x() = -static_cast<amrex::Real>(buf[ifx + i]) / rho;
276 grid.force[i].y() = -static_cast<amrex::Real>(buf[ify + i]) / rho;
277 grid.force[i].z() = -static_cast<amrex::Real>(buf[ifz + i]) / rho;
278
279 // Position vectors of the actuator nodes. Add shift to base
280 // locations.
281 grid.pos[i].x() = static_cast<amrex::Real>(buf[ipx + i]) + bp.x();
282 grid.pos[i].y() = static_cast<amrex::Real>(buf[ipy + i]) + bp.y();
283 grid.pos[i].z() = static_cast<amrex::Real>(buf[ipz + i]) + bp.z();
284
285 // Copy over the orientation matrix
286 //
287 // Note that we transpose the orientation matrix when copying
288 // from external to AMR-Wind Tensor data structure. This is done
289 // so that post-multiplication of vector transforms from global
290 // to local reference frame.
291 const auto off =
292 static_cast<int>(iori) + i * AMREX_SPACEDIM * AMREX_SPACEDIM;
293 for (int j = 0; j < AMREX_SPACEDIM; ++j) {
294 for (int k = 0; k < AMREX_SPACEDIM; ++k) {
295 grid.orientation[i][j * AMREX_SPACEDIM + k] =
296 static_cast<amrex::Real>(
297 buf[off + j + k * AMREX_SPACEDIM]);
298 }
299 }
300 }
301
302 // Extract the rotor center of rotation
303 auto& meta = data.meta();
304 meta.rot_center = grid.pos[0];
305
306 // Rotor non-rotating reference frame
307 const auto xvec = grid.orientation[0].x().unit();
308 const auto yvec = vs::Vector::khat() ^ xvec;
309 const auto zvec = xvec ^ yvec;
310 meta.rotor_frame.rows(xvec, yvec.unit(), zvec.unit());
311 }
312}
313
314} // namespace amr_wind::actuator::external
315
316#endif /* TURBINE_EXTERNAL_UTILS_H */
Definition turbine_external_utils.H:15
void scatter_data(datatype &data)
Definition turbine_external_utils.H:195
void swap_epsilon(vs::Vector &eps)
Definition turbine_external_utils.H:17
void ext_step(datatype &data)
Definition turbine_external_utils.H:172
void make_component_views(datatype &data)
Definition turbine_external_utils.H:26
void compute_nacelle_force(datatype &data)
Definition turbine_external_utils.H:139
void init_epsilon(datatype &data)
Definition turbine_external_utils.H:88
Definition bluff_body_ops.cpp:18
Slice< T > slice(std::vector< T > &vec, const size_t start, const size_t count)
Definition Slice.H:71
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE constexpr amrex::Real two_pi()
Return .
Definition trig_ops.H:19
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE T mag(const TensorT< T > &t)
Definition tensorI.H:182
VectorT< amrex::Real > Vector
Definition vector.H:145
Definition actuator_types.H:129
VecSlice vel
Definition actuator_types.H:136
VecSlice epsilon
Definition actuator_types.H:132
VecSlice vel_rel
Definition actuator_types.H:137
VecSlice force
Definition actuator_types.H:131
TensorSlice orientation
Definition actuator_types.H:133
RealSlice chord
Definition actuator_types.H:139
VecSlice pos
Definition actuator_types.H:130
VecSlice vel_pos
Definition actuator_types.H:135
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE T & y() &
Definition vector.H:98
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE T & x() &
Definition vector.H:97
AMREX_GPU_HOST_DEVICE static AMREX_FORCE_INLINE constexpr VectorT< amrex::Real > khat(const amrex::Real &z=Traits::one())
Definition vector.H:80