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