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