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