MAIA bb96820c
Multiphysics at AIA
Loading...
Searching...
No Matches
lptellipsoidal.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The m-AIA AUTHORS
2//
3// This file is part of m-AIA (https://git.rwth-aachen.de/aia/m-AIA/m-AIA)
4//
5// SPDX-License-Identifier: LGPL-3.0-only
6
7#include "lptellipsoidal.h"
8#include <cmath>
9#include <functional>
10#include <numeric>
11#include "lpt.h"
12
13using namespace std;
14using namespace maia::lpt;
15
16// reference values used for LPT non-dimensionalisation:
17template <MInt nDim>
19// L_ref / L_refGrid (length factor between the particle reference length
20// and the grid reference length and)
21template <MInt nDim>
23// reference Re-number: Re_ref = rho_ref * u_ref * L_ref / mu_ref
24template <MInt nDim>
26// reference Pr-number: Pr_ref = mu_ref * Cp_ref / lamda_ref
27template <MInt nDim>
29// reference Sc-number: Sc_ref = mu_ref / (rho_ref * D_ref)
30template <MInt nDim>
31std::array<MFloat, nDim> LPTEllipsoidal<nDim>::s_Frm{};
32// modified reference Fr-number with : Fr_ref = u_ref / sqrt( g * L_ref)
33// the modified version is Frm = 1/ (Fr_ref^2) used to reduce computations
34
39template <MInt nDim>
41 const auto nan = std::numeric_limits<MFloat>::quiet_NaN();
42 for(MInt i = 0; i < nDim; i++) {
43 m_fluidVel[i] = nan;
44 m_oldFluidVel[i] = nan;
45 m_velocity[i] = nan;
46 m_oldVel[i] = nan;
47 m_position[i] = nan;
48 m_oldPos[i] = nan;
49 m_accel[i] = nan;
50 m_oldAccel[i] = nan;
51 m_angularVel[i] = nan;
52 m_oldAngularVel[i] = nan;
53 m_angularAccel[i] = nan;
54 m_oldAngularAccel[i] = nan;
55 }
56 for(MInt i = 0; i < 4; i++) {
57 m_quaternion[i] = nan;
58 m_oldQuaternion[i] = nan;
59 }
60}
61
62
67template <MInt nDim>
69 firstStep() = false;
70 hasCollided() = false;
71 hadWallColl() = false;
72 for(MInt i = 0; i < nDim; i++) {
73 ASSERT(!isnan(m_fluidVel[i]), "");
74 m_oldFluidVel[i] = m_fluidVel[i];
75 }
76 ASSERT(m_fluidDensity > 0, "");
77 m_oldFluidDensity = m_fluidDensity;
78}
79
84template <MInt nDim>
86 if(m_cellId < 0) {
87 mTerm(1, AT_, "coupling of invalid particle?");
88 }
89
90 if(!s_backPtr->m_heatCoupling && !s_backPtr->m_evaporation) {
91 m_redistWeight.clear();
92 interpolateVelocityAndDensity(m_cellId, m_position.data(), m_fluidVel.data(), m_fluidDensity, m_redistWeight);
93 }
94
95 // check that all variables have been exchanged/set correctly
96 for(MInt i = 0; i < nDim; i++) {
97 ASSERT(!isnan(m_oldFluidVel[i]), "");
98 }
99 ASSERT(m_oldFluidDensity > 0, "");
100
101 // check all source terms
102#ifdef LPT_DEBUG
103 for(MInt i = 0; i < nDim; i++) {
104 if(std::isnan(m_accel[i])) {
105 cerr << "Invalid motion equation! " << m_partId << " " << m_position[0] << " " << m_position[1] << " "
106 << m_position[2] << endl;
107 isInvalid() = true;
108 return;
109 }
110 if(std::isnan(m_velocity[i])) {
111 cerr << "Invalid motion equation2! " << m_partId << " " << m_position[0] << " " << m_position[1] << " "
112 << m_position[2] << " " << m_densityRatio << " " << hadWallColl() << endl;
113 isInvalid() = true;
114 return;
115 }
116 }
117#endif
118
119 if(s_backPtr->m_momentumCoupling) momentumCoupling();
120 if(s_backPtr->m_heatCoupling) heatCoupling();
121 if(s_backPtr->m_massCoupling) massCoupling();
122}
123
128template <MInt nDim>
130 mTerm(1, AT_, "Mass coupling not implemented for ellipsoidal particles");
131}
132
137template <MInt nDim>
139 const MFloat relT = (s_backPtr->m_timeStep - m_creationTime) / s_backPtr->m_timeStep;
140
141 const MFloat mass = particleMass();
142
143 array<MFloat, nDim> force{};
144
145 for(MInt i = 0; i < nDim; i++) {
146 // momentum flux due to drag force
147 const MFloat invDensityRatio = (abs(m_densityRatio) <= MFloatEps) ? 1.0 : 1.0 / m_densityRatio;
148 force[i] = mass * (m_accel[i] - ((1.0 - invDensityRatio) * s_Frm[i])) * relT;
149 }
150
151 // work done by the momentum
152 const MFloat work = std::inner_product(force.begin(), force.end(), m_oldVel.begin(), 0.0);
153
154
155 if(!s_backPtr->m_couplingRedist) {
156 for(MInt i = 0; i < nDim; i++) {
157 s_backPtr->a_momentumFlux(m_cellId, i) += (force[i]);
158 }
159 s_backPtr->a_workFlux(m_cellId) += work;
160
161 } else {
162 ASSERT(m_redistWeight.size() == m_neighborList.size(),
163 "Invalid " + to_string(m_redistWeight.size()) + "!=" + to_string(m_neighborList.size()));
164 for(MInt n = 0; n < (signed)m_neighborList.size(); n++) {
165 // a weightHeat is used to distribute the source term to multiple cells...
166 for(MInt i = 0; i < nDim; i++) {
167 s_backPtr->a_momentumFlux(m_neighborList[n], i) += m_redistWeight.at(n) * (force[i]);
168 }
169 s_backPtr->a_workFlux(m_neighborList[n]) += m_redistWeight.at(n) * work;
170 }
171 }
172}
173
178template <MInt nDim>
180 mTerm(1, AT_, "Heat coupling not tested for ellipsoidal particles");
181}
182
183
184template <MInt nDim>
186 const MFloat dt = s_backPtr->m_timeStep - m_creationTime;
187 ASSERT(dt > 0, "Timestep < 0");
188
189 // NOTE: at this point old means 1 time-Step ago and the current values will be updated below!
190 m_oldPos = m_position;
191 m_oldVel = m_velocity;
192 m_oldAccel = m_accel;
193 m_oldAngularVel = m_angularVel;
194 m_oldAngularAccel = m_angularAccel;
195 m_oldQuaternion = m_quaternion;
196 m_oldCellId = m_cellId;
197
198 if(firstStep()) {
199 for(MInt i = 0; i < nDim; i++) {
200 m_accel[i] = s_Frm[i];
201 m_oldAccel[i] = s_Frm[i];
202 }
203 }
204
205 for(MInt i = 0; i < nDim; i++) {
206 ASSERT(!isnan(m_oldFluidVel[i]), "");
207 }
208 ASSERT(m_oldFluidDensity > 0, "");
209
210 // reduce variance between runs (round to 9 decimal places)
211 // TODO-timw labels:LPT,totest check if rounding is still necessary?
212 const MFloat invDensityRatio =
213 F1 / (static_cast<MFloat>(ceil(s_backPtr->m_material->density() / m_oldFluidDensity * 1E9)) / 1E9);
214
215 m_densityRatio = 1 / invDensityRatio;
216
218 array<MFloat, nDim> predictedPos{};
219 array<MFloat, nDim> predictedVel{};
220 array<MFloat, nDim> predictedAngularVel{};
221 array<MFloat, nDim + 1> predictedQuaternion{};
222
223 MFloat q[3];
224 MFloat q0[3];
225 MFloat tq[3];
226 MFloat tq0[3];
227 MFloat w = m_oldQuaternion[0];
228 MFloat x = m_oldQuaternion[1];
229 MFloat y = m_oldQuaternion[2];
230 MFloat z = m_oldQuaternion[3];
231 for(MInt i = 0; i < 3; i++) {
232 q0[i] = m_oldAngularVel[i];
233 }
234
235 // predict position, velocity, orientation and angular velocity
236 for(MInt i = 0; i < nDim; i++) {
237 predictedVel[i] = m_oldVel[i] + dt * m_oldAccel[i];
238 predictedPos[i] = m_oldPos[i] + dt * (m_oldVel[i]) + F1B2 * POW2(dt) * (m_oldAccel[i]);
239 }
240 predictedQuaternion[0] = w + F1B2 * dt * (-x * q0[0] - y * q0[1] - z * q0[2]);
241 predictedQuaternion[1] = x + F1B2 * dt * (w * q0[0] - z * q0[1] + y * q0[2]);
242 predictedQuaternion[2] = y + F1B2 * dt * (z * q0[0] + w * q0[1] - x * q0[2]);
243 predictedQuaternion[3] = z + F1B2 * dt * (-y * q0[0] + x * q0[1] + w * q0[2]);
244 for(MInt i = 0; i < 3; i++) {
245 predictedAngularVel[i] = m_oldAngularVel[i] + dt * m_oldAngularAccel[i];
246 }
247
249 // find the predicted CellId at the predicted position around the previous cellId
250 const MInt predictedCellId = s_backPtr->grid().findContainingLeafCell(&predictedPos[0], m_cellId);
251
252 ASSERT(m_cellId > -1, "Invalid cell!");
253 ASSERT(predictedCellId > -1, "Invalid cell!");
254 ASSERT(s_backPtr->c_isLeafCell(predictedCellId), "Invalid cell!");
255
256 vector<MFloat> predictedWeights(0);
257 array<MFloat, nDim> fluidVel{};
258 array<MFloat, nDim * nDim> fluidVelGradient{};
259 array<MFloat, nDim * nDim> fluidVelGradientHat{};
260 std::fill(fluidVel.begin(), fluidVel.end(), std::numeric_limits<MFloat>::quiet_NaN());
261 std::fill(fluidVelGradient.begin(), fluidVelGradient.end(), std::numeric_limits<MFloat>::quiet_NaN());
262 std::fill(fluidVelGradientHat.begin(), fluidVelGradientHat.end(), std::numeric_limits<MFloat>::quiet_NaN());
263 interpolateVelocityAndVelocitySlopes(predictedCellId, predictedPos.data(), fluidVel.data(), fluidVelGradient.data(),
264 predictedWeights);
265
266 // calculate gradients according to principle axes of ellipsoid
267 MFloatScratchSpace R(3, 3, AT_, "R");
268 maia::math::computeRotationMatrix(R, &(m_quaternion[0]));
269 for(MInt i = 0; i < nDim; i++) {
270 maia::math::matrixVectorProduct(&fluidVelGradientHat[nDim * i], R, &fluidVelGradient[nDim * i]);
271 }
272
273 m_fluidVelMag = sqrt(std::inner_product(fluidVel.begin(), fluidVel.end(), fluidVel.begin(), F0));
274
275 const MFloat T = s_backPtr->a_fluidTemperature(m_cellId);
276
277 MFloat fluidViscosity = s_backPtr->m_material->dynViscosityFun(T);
278
280 if(s_backPtr->m_dragModelType == 0) {
281 // no drag or neglible small (are going to be deleted...)
282 for(MInt i = 0; i < nDim; i++) {
283 m_velocity[i] = predictedVel[i];
284 m_position[i] = predictedPos[i];
285 m_angularVel[i] = predictedAngularVel[i];
286 m_quaternion[i] = predictedQuaternion[i];
287 m_accel[i] = m_oldAccel[i];
288 m_angularAccel[i] = m_oldAngularAccel[i];
289 }
290 } else {
291 switch(s_backPtr->m_motionEquationType) {
292 case 0: {
293 // particle moves with fluid velocity, no rotational motion
294 for(MInt i = 0; i < nDim; i++) {
295 m_position[i] = m_oldPos[i] + F1B2 * dt * (fluidVel[i] + m_oldVel[i]) * s_lengthFactor;
296 m_velocity[i] = fluidVel[i];
297 m_accel[i] = (m_velocity[i] - m_oldVel[i]) / dt;
298 m_angularVel[i] = F0;
299 m_angularAccel[i] = F0;
300 }
301 break;
302 }
303 case 1: {
304 // spherical case copied from lptspherical
305 // serves for debugging to have a version independant of the strain, vorticity
306 const MFloat relVel = magRelVel(&fluidVel[0], &predictedVel[0]);
307
308 const MFloat Rep = particleRe(relVel, m_oldFluidDensity, fluidViscosity) * s_Re;
309
310 const MFloat CD = dragFactor(Rep) / s_Re * fParticleRelTime(fluidViscosity);
311
312 for(MInt i = 0; i < nDim; i++) {
313 m_accel[i] = CD * (fluidVel[i] - predictedVel[i]) + (1.0 - invDensityRatio) * s_Frm[i];
314 m_velocity[i] = m_oldVel[i] + dt * F1B2 * (m_accel[i] + m_oldAccel[i]);
315 m_position[i] = m_oldPos[i] + dt * F1B2 * (m_velocity[i] + m_oldVel[i]) * s_lengthFactor;
316 }
317 break;
318 }
319 case 2: {
320 // case derived for creeping flow
321 const MFloat fdtB2 = F1B2 * dt;
322 MFloatScratchSpace K(3, 3, AT_, "K");
323 MFloatScratchSpace W(4, 4, AT_, "W");
324 MFloatScratchSpace iW(4, 4, AT_, "iW");
325 MFloatScratchSpace rhs(4, AT_, "rhs");
326
327 MFloat vrel[3];
328 MFloat vrelhat[3];
329 MFloat tmp[3];
330 MFloat vort[3];
331 MFloat strain[3];
332
333 const MFloat beta = m_aspectRatio;
334 const MFloat beta2 = POW2(beta);
335 const MFloat fTauP = fParticleRelTime(fluidViscosity) / s_Re;
336
337 const MFloat linAccFac = (8.0 / 3.0) * pow(beta, F2B3) * fTauP;
338 const MFloat angAccFac = (40.0 / 9.0) * pow(beta, F2B3) * fTauP;
339
340 // integrate angular velocity
341 const MInt maxit = 100;
342 MFloat delta = F1;
343 MInt it = 0;
344
345 // set temporary variables for angular velocity (q) and acceleration (tq)
346 for(MInt i = 0; i < 3; i++) {
347 tq0[i] = m_oldAngularAccel[i];
348 q0[i] = m_oldAngularVel[i];
349 tq[i] = tq0[i];
350 q[i] = q0[i];
351 }
352
353 // compute vorticity and strain
354 for(MInt i = 0; i < 3; i++) {
355 MInt id0 = (i + 1) % 3;
356 MInt id1 = (id0 + 1) % 3;
357 strain[i] = F1B2 * (fluidVelGradientHat[3 * id1 + id0] + fluidVelGradientHat[3 * id0 + id1]);
358 vort[i] = F1B2 * (fluidVelGradientHat[3 * id1 + id0] - fluidVelGradientHat[3 * id0 + id1]);
359 }
360
361 W(0, 0) = F1 + fdtB2 * angAccFac / (m_shapeParams[1] + beta2 * m_shapeParams[3]);
362 W(1, 1) = F1 + fdtB2 * angAccFac / (m_shapeParams[1] + beta2 * m_shapeParams[3]);
363 W(2, 2) = F1 + fdtB2 * angAccFac / (F2 * m_shapeParams[1]);
364 W(2, 0) = F0;
365 W(2, 1) = F0;
366
367 // Newton iterations
368 while(delta > 1e-10 && it < maxit) {
369 W(0, 1) = -fdtB2 * q[2] * (beta2 - F1) / (beta2 + F1);
370 W(0, 2) = -fdtB2 * q[1] * (beta2 - F1) / (beta2 + F1);
371 W(1, 0) = -fdtB2 * q[2] * (F1 - beta2) / (beta2 + F1);
372 W(1, 2) = -fdtB2 * q[0] * (F1 - beta2) / (beta2 + F1);
373
374 maia::math::invert(W, iW, 3, 3);
375
376 // compute angular acceleration tq
377 tq[0] = q[1] * q[2] * (beta2 - F1) / (beta2 + F1)
378 + angAccFac * (((F1 - beta2) / (F1 + beta2)) * strain[0] + vort[0] - q[0])
379 / (m_shapeParams[1] + beta2 * m_shapeParams[3]);
380 tq[1] = q[0] * q[2] * (F1 - beta2) / (beta2 + F1)
381 + angAccFac * (((beta2 - F1) / (F1 + beta2)) * strain[1] + vort[1] - q[1])
382 / (m_shapeParams[1] + beta2 * m_shapeParams[3]);
383 tq[2] = angAccFac * (vort[2] - q[2]) / (F2 * m_shapeParams[1]);
384
385 for(MInt i = 0; i < 3; i++)
386 rhs[i] = q[i] - q0[i] - fdtB2 * (tq0[i] + tq[i]);
387
388 delta = F0;
389 for(MInt i = 0; i < 3; i++) {
390 const MFloat qq = q[i];
391 for(MInt j = 0; j < 3; j++) {
392 q[i] -= iW(i, j) * rhs(j);
393 }
394 delta = mMax(delta, fabs(q[i] - qq));
395 }
396 it++;
397 }
398
399 if(it >= maxit || it <= 0) {
400 cerr << "Newton iterations did not converge " << m_partId << endl;
401 }
402
403 for(MInt i = 0; i < 3; i++) {
404 m_angularVel[i] = q[i];
405 m_angularAccel[i] = tq[i];
406 }
407
408 // integrate quaternions
409 w = m_oldQuaternion[0];
410 x = m_oldQuaternion[1];
411 y = m_oldQuaternion[2];
412 z = m_oldQuaternion[3];
413
414 W(0, 0) = F0;
415 W(0, 1) = -q[0];
416 W(0, 2) = -q[1];
417 W(0, 3) = -q[2];
418 W(1, 0) = q[0];
419 W(1, 1) = F0;
420 W(1, 2) = q[2];
421 W(1, 3) = -q[1];
422 W(2, 0) = q[1];
423 W(2, 1) = -q[2];
424 W(2, 2) = F0;
425 W(2, 3) = q[0];
426 W(3, 0) = q[2];
427 W(3, 1) = q[1];
428 W(3, 2) = -q[0];
429 W(3, 3) = F0;
430
431 for(MInt i = 0; i < 4; i++) {
432 for(MInt j = 0; j < 4; j++) {
433 W(i, j) = -F1B2 * fdtB2 * W(i, j);
434 }
435 W(i, i) = F1;
436 }
437
438 rhs(0) = w + F1B2 * fdtB2 * (-x * q0[0] - y * q0[1] - z * q0[2]);
439 rhs(1) = x + F1B2 * fdtB2 * (w * q0[0] - z * q0[1] + y * q0[2]);
440 rhs(2) = y + F1B2 * fdtB2 * (z * q0[0] + w * q0[1] - x * q0[2]);
441 rhs(3) = z + F1B2 * fdtB2 * (-y * q0[0] + x * q0[1] + w * q0[2]);
442
443 maia::math::invert(W, iW, 4, 4);
444
445 for(MInt i = 0; i < 4; i++) {
446 m_quaternion[i] = F0;
447 for(MInt j = 0; j < 4; j++) {
448 m_quaternion[i] += iW(i, j) * rhs(j);
449 }
450 }
451
452 MFloat abs = F0;
453 for(MInt i = 0; i < 4; i++)
454 abs += POW2(m_quaternion[i]);
455 for(MInt i = 0; i < 4; i++)
456 m_quaternion[i] /= sqrt(abs);
457
458 // integrate linear motion
459 K.fill(F0);
460 K(0, 0) = F1 / (m_shapeParams[0] / POW2(m_semiMinorAxis) + m_shapeParams[1]);
461 K(1, 1) = K(0, 0);
462 K(2, 2) = F1 / (m_shapeParams[0] / POW2(m_semiMinorAxis) + beta2 * m_shapeParams[3]);
463 maia::math::computeRotationMatrix(R, &(m_quaternion[0]));
464
465 for(MInt i = 0; i < nDim; i++) {
466 vrel[i] = fluidVel[i] - predictedVel[i];
467 }
468
469 maia::math::matrixVectorProduct(vrelhat, R, vrel); // principal axes frame relative velocity
470 maia::math::matrixVectorProduct(tmp, K, vrelhat);
472
473 for(MInt i = 0; i < nDim; i++) {
474 m_accel[i] = linAccFac * vrel[i] + (1.0 - invDensityRatio) * s_Frm[i];
475 m_velocity[i] = m_oldVel[i] + dt * F1B2 * (m_accel[i] + m_oldAccel[i]);
476 m_position[i] = m_oldPos[i] + dt * F1B2 * (m_velocity[i] + m_oldVel[i]) * s_lengthFactor
477 + F1B4 * POW2(dt) * (m_accel[i] + m_oldAccel[i]);
478 }
479 break;
480 }
481 case 3: {
482 // case using new correlations functions by Konstantin (2020)
483 const MFloat fdtB2 = F1B2 * dt;
484 MFloatScratchSpace K(3, 3, AT_, "K");
485 MFloatScratchSpace W(4, 4, AT_, "W");
486 MFloatScratchSpace iW(4, 4, AT_, "iW");
487 MFloatScratchSpace rhs(4, AT_, "rhs");
488
489 MFloat vrel[3];
490 MFloat vrelhat[3];
491 MFloat tmp[3];
492 MFloat vort[3];
493 MFloat strain[3];
494
495 const MFloat beta = m_aspectRatio;
496 const MFloat beta2 = POW2(beta);
497 const MFloat eqRadius = F1B2 * m_eqDiameter;
498 const MFloat fTauP = fParticleRelTime(fluidViscosity) / s_Re;
499 const MFloat magRelVeloc = magRelVel(&fluidVel[0], &predictedVel[0]);
500 const MFloat Rep = particleRe(magRelVeloc, m_oldFluidDensity, fluidViscosity) * s_Re;
501
502 const MFloat angAccFac = (40.0 / 9.0) * pow(beta, F2B3) * fTauP;
503 const MFloat pitchingTorqueFac = (15.0 / 8.0) * invDensityRatio * pow(beta, F2B3) / POW2(eqRadius);
504 const MFloat addFacCd =
505 48.0 * fluidViscosity / (s_Re * s_backPtr->m_material->density() * POW2(m_eqDiameter)) * pow(beta, F2B3);
506 const MFloat momI[3] = {1.0 + POW2(beta), 1.0 + POW2(beta), 2.0}; // factors for moments of inertia
507
508 maia::math::computeRotationMatrix(R, &(m_quaternion[0]));
509
510 for(MInt i = 0; i < nDim; i++) {
511 vrel[i] = fluidVel[i] - predictedVel[i];
512 }
513
514 maia::math::matrixVectorProduct(vrelhat, R, vrel); // principal axes frame relative velocity
515 maia::math::matrixVectorProduct(tmp, K, vrelhat);
517
518 const MFloat magRelVelHat = sqrt(POW2(vrelhat[0]) + POW2(vrelhat[1]) + POW2(vrelhat[2]));
519
520 const auto nan = std::numeric_limits<MFloat>::quiet_NaN();
521 MFloat inclinationAngle = nan;
522 std::array<MFloat, 3> accHat = {nan, nan, nan};
523 std::array<MFloat, 3> accDragHat = {nan, nan, nan};
524 std::array<MFloat, 3> accLiftHat = {nan, nan, nan};
525 std::array<MFloat, 3> dirDHat = {nan, nan, nan};
526 std::array<MFloat, 3> dirTHat = {nan, nan, nan};
527 std::array<MFloat, 3> dirLHat = {nan, nan, nan};
528 std::array<MFloat, 3> dirDCrossZ = {nan, nan, nan};
529 std::array<MFloat, 3> dirZHat{nan, nan, nan};
530
531 // dirZHat
532 for(MInt i = 0; i < nDim; i++) {
533 i == m_particleMajorAxis ? dirZHat[i] = F1 : dirZHat[i] = F0;
534 }
535 // dirDhat
536 for(MInt i = 0; i < nDim; i++) {
537 dirDHat[i] = vrelhat[i] / magRelVelHat;
538 }
539 // dirDcrossZ = dirDhat x dirZhat
540 maia::math::cross(&dirDHat[0], &dirZHat[0], &dirDCrossZ[0]);
541 const MFloat dotProduct = std::inner_product(dirDHat.begin(), dirDHat.end(), dirZHat.begin(), F0);
542 inclinationAngle = acos(fabs(dotProduct));
543 MInt sgn = (dotProduct > 0 ? 1 : -1);
544 for(MInt i = 0; i < nDim; i++) {
545 dirTHat[i] = dirDCrossZ[i] / sqrt(POW2(dirDCrossZ[0]) + POW2(dirDCrossZ[1]) + POW2(dirDCrossZ[2])) * sgn;
546 }
547 // dirLHat = dirDhat x dirThat
548 maia::math::cross(&dirDHat[0], &dirTHat[0], &dirLHat[0]);
549
550 // integrate angular velocity
551 const MInt maxit = 100;
552 MFloat delta = F1;
553 MInt it = 0;
554
555 W(0, 0) = F1 + fdtB2 * angAccFac / (m_shapeParams[1] + beta2 * m_shapeParams[3]);
556 W(1, 1) = F1 + fdtB2 * angAccFac / (m_shapeParams[1] + beta2 * m_shapeParams[3]);
557 W(2, 2) = F1 + fdtB2 * angAccFac / (F2 * m_shapeParams[1]);
558 W(2, 0) = F0;
559 W(2, 1) = F0;
560
561 // set temporary variables for angular velocity (q) and acceleration (tq)
562 for(MInt i = 0; i < 3; i++) {
563 tq0[i] = m_oldAngularAccel[i];
564 q0[i] = m_oldAngularVel[i];
565 tq[i] = tq0[i];
566 q[i] = q0[i];
567 }
568
569 // compute pitching torque unless torque modelling is turned off
570 MFloat pitch[3] = {F0};
571 if(s_backPtr->m_torqueModelType > 0 && Rep > 1e-8) {
572 MFloat CT = torqueFactor(Rep, beta, inclinationAngle);
573 for(MInt i = 0; i < 3; i++) {
574 pitch[i] = pitchingTorqueFac * POW2(magRelVelHat) * CT / momI[i] * dirTHat[i];
575 if(pitchingTorqueFac <= F0 || CT <= F0 || momI[i] <= F0)
576 cout << "Calc pitching torque " << pitchingTorqueFac << " " << CT << " " << momI[i] << endl;
577 }
578 }
579
580 // compute vorticity and strain
581 for(MInt i = 0; i < 3; i++) {
582 MInt id0 = (i + 1) % 3;
583 MInt id1 = (id0 + 1) % 3;
584 strain[i] = F1B2 * (fluidVelGradientHat[3 * id1 + id0] + fluidVelGradientHat[3 * id0 + id1]);
585 vort[i] = F1B2 * (fluidVelGradientHat[3 * id1 + id0] - fluidVelGradientHat[3 * id0 + id1]);
586 }
587
588 // Newton iterations
589 while(delta > 1e-10 && it < maxit) {
590 W(0, 1) = -fdtB2 * q[2] * (beta2 - F1) / (beta2 + F1);
591 W(0, 2) = -fdtB2 * q[1] * (beta2 - F1) / (beta2 + F1);
592 W(1, 0) = -fdtB2 * q[2] * (F1 - beta2) / (beta2 + F1);
593 W(1, 2) = -fdtB2 * q[0] * (F1 - beta2) / (beta2 + F1);
594
595 maia::math::invert(W, iW, 3, 3);
596
597 tq[0] = q[1] * q[2] * (beta2 - F1) / (beta2 + F1)
598 + angAccFac * (((F1 - beta2) / (F1 + beta2)) * strain[0] + vort[0] - q[0])
599 / (m_shapeParams[1] + beta2 * m_shapeParams[3])
600 + pitch[0];
601 tq[1] = q[0] * q[2] * (F1 - beta2) / (beta2 + F1)
602 + angAccFac * (((beta2 - F1) / (F1 + beta2)) * strain[1] + vort[1] - q[1])
603 / (m_shapeParams[1] + beta2 * m_shapeParams[3])
604 + pitch[1];
605 tq[2] = angAccFac * (vort[2] - q[2]) / (F2 * m_shapeParams[1]) + pitch[2];
606
607 for(MInt i = 0; i < 3; i++)
608 rhs[i] = q[i] - q0[i] - fdtB2 * (tq0[i] + tq[i]);
609
610 delta = F0;
611 for(MInt i = 0; i < 3; i++) {
612 const MFloat qq = q[i];
613 for(MInt j = 0; j < 3; j++) {
614 q[i] -= iW(i, j) * rhs(j);
615 }
616 delta = mMax(delta, fabs(q[i] - qq));
617 }
618 it++;
619 }
620
621 if(it >= maxit || it <= 0) {
622 cerr << "Newton iterations did not converge " << m_partId << endl;
623 }
624
625 for(MInt i = 0; i < 3; i++) {
626 m_angularVel[i] = q[i];
627 m_angularAccel[i] = tq[i];
628 }
629
630 // integrate quaternions
631 w = m_oldQuaternion[0];
632 x = m_oldQuaternion[1];
633 y = m_oldQuaternion[2];
634 z = m_oldQuaternion[3];
635
636 W(0, 0) = F0;
637 W(0, 1) = -q[0];
638 W(0, 2) = -q[1];
639 W(0, 3) = -q[2];
640 W(1, 0) = q[0];
641 W(1, 1) = F0;
642 W(1, 2) = q[2];
643 W(1, 3) = -q[1];
644 W(2, 0) = q[1];
645 W(2, 1) = -q[2];
646 W(2, 2) = F0;
647 W(2, 3) = q[0];
648 W(3, 0) = q[2];
649 W(3, 1) = q[1];
650 W(3, 2) = -q[0];
651 W(3, 3) = F0;
652
653 for(MInt i = 0; i < 4; i++) {
654 for(MInt j = 0; j < 4; j++) {
655 W(i, j) = -F1B2 * fdtB2 * W(i, j);
656 }
657 W(i, i) = F1;
658 }
659
660 rhs(0) = w + F1B2 * fdtB2 * (-x * q0[0] - y * q0[1] - z * q0[2]);
661 rhs(1) = x + F1B2 * fdtB2 * (w * q0[0] - z * q0[1] + y * q0[2]);
662 rhs(2) = y + F1B2 * fdtB2 * (z * q0[0] + w * q0[1] - x * q0[2]);
663 rhs(3) = z + F1B2 * fdtB2 * (-y * q0[0] + x * q0[1] + w * q0[2]);
664
665 maia::math::invert(W, iW, 4, 4);
666
667 for(MInt i = 0; i < 4; i++) {
668 m_quaternion[i] = F0;
669 for(MInt j = 0; j < 4; j++) {
670 m_quaternion[i] += iW(i, j) * rhs(j);
671 }
672 }
673
674 MFloat abs = F0;
675 for(MInt i = 0; i < 4; i++)
676 abs += POW2(m_quaternion[i]);
677 for(MInt i = 0; i < 4; i++)
678 m_quaternion[i] /= sqrt(abs);
679
680 // integrate linear motion
681 K.fill(F0);
682 K(0, 0) = F1 / (m_shapeParams[0] / POW2(m_semiMinorAxis) + m_shapeParams[1]);
683 K(1, 1) = K(0, 0);
684 K(2, 2) = F1 / (m_shapeParams[0] / POW2(m_semiMinorAxis) + beta2 * m_shapeParams[3]);
685
686 // new drag correlations
687 MFloatScratchSpace accDrag(3, AT_, "accDrag");
688 accDrag.fill(F0);
689 MFloat facCL = liftFactor(Rep, beta, inclinationAngle, K(0, 0), K(2, 2));
690 MFloat facCD = dragFactor(Rep, beta, inclinationAngle, K(0, 0), K(2, 2));
691 for(MInt i = 0; i < nDim; i++) {
692 accLiftHat[i] = addFacCd * facCL * magRelVelHat * dirLHat[i];
693 accDragHat[i] = addFacCd * facCD * magRelVelHat * dirDHat[i];
694 accHat[i] = accDragHat[i] + accLiftHat[i];
695 }
696 maia::math::matrixVectorProductTranspose(&accDrag[0], R, &accHat[0]);
697
698 for(MInt i = 0; i < nDim; i++) {
699 m_accel[i] = accDrag[i] + (1.0 - invDensityRatio) * s_Frm[i];
700 m_velocity[i] = m_oldVel[i] + dt * F1B2 * (m_accel[i] + m_oldAccel[i]);
701 m_position[i] = m_oldPos[i] + dt * F1B2 * (m_velocity[i] + m_oldVel[i]) * s_lengthFactor
702 + F1B4 * POW2(dt) * (m_accel[i] + m_oldAccel[i]);
703 }
704 break;
705 }
706 default: {
707 mTerm(1, AT_, "Unknown particle corrector equation type!");
708 }
709 }
710 }
711
712#ifdef LPT_DEBUG
713 if(m_partId == debugPartId) {
714 cerr << "AT " << m_velocity[0] << " " << m_velocity[1] << " " << m_velocity[2] << endl;
715 }
716#endif
717
718 // 3. update cellId based on the corrected position and set the new status!
719 checkCellChange(&m_oldPos[0]);
720
721#ifdef LPT_DEBUG
722 for(MInt i = 0; i < nDim; i++) {
723 if(std::isnan(m_accel[i])) {
724 cerr << "Nan acc. for " << s_backPtr->domainId() << " " << m_partId << " " << m_position[0] << " "
725 << m_position[1] << " " << m_position[nDim - 1] << " " << s_backPtr->a_isValidCell(m_cellId) << " "
726 << m_oldPos[0] << " " << m_oldPos[1] << " " << m_oldPos[nDim - 1] << " " << m_creationTime << " "
727 << fluidVel[0] << " " << fluidVel[1] << " " << fluidVel[nDim - 1] << fluidDensity << " " << T << " "
728 << fluidViscosity << " " << m_oldVel[0] << " " << m_oldVel[1] << " " << m_oldVel[nDim - 1] << endl;
729 }
730 }
731#endif
732}
733
734
740template <MInt nDim>
741MFloat LPTEllipsoidal<nDim>::dragFactor(const MFloat partRe, const MFloat beta, const MFloat inclinationAngle,
742 const MFloat K0, const MFloat K2) {
743 MFloat result = NAN;
744 switch(s_backPtr->m_dragModelType) {
745 case 0: { // drag force switched off
746 result = F0;
747 break;
748 }
749 case 1: { // linear Stokes drag
750 result = F1;
751 break;
752 }
753 case 2: { // nonlinear drag according to Schiller & Naumann
754 result = 1.0 + 0.15 * pow(partRe, 0.687);
755 break;
756 }
757 case 3: { // new correlations by Konstantin (2020)
758 std::array<MFloat, 8> dCorrs{-0.007, 1.0, 1.17, -0.07, 0.047, 1.14, 0.7, -0.008};
759 MFloat fd0 = F1;
760 MFloat fd90 = F1;
761 fd0 = 1.0 + 0.15 * pow(partRe, 0.687)
762 + dCorrs[0] * pow(std::log(beta), dCorrs[1]) * pow(partRe, dCorrs[2] + dCorrs[3] * std::log(beta));
763 fd90 = 1.0 + 0.15 * pow(partRe, 0.687)
764 + dCorrs[4] * pow(log(beta), dCorrs[5]) * pow(partRe, dCorrs[6] + dCorrs[7] * log(beta));
765
766 MFloat fd0K2 = fd0 * K2;
767 MFloat f90K0 = fd90 * K0;
768 result = fd0K2 + (f90K0 - fd0K2) * POW2(sin(inclinationAngle));
769 break;
770 }
771 default: {
772 mTerm(1, AT_, "Unknown particle drag method");
773 }
774 }
775 return result;
776}
777
783template <MInt nDim>
784MFloat LPTEllipsoidal<nDim>::liftFactor(const MFloat partRe, const MFloat beta, const MFloat inclinationAngle,
785 const MFloat K0, const MFloat K2) {
786 MFloat result = NAN;
787 switch(s_backPtr->m_liftModelType) {
788 case 0: { // lift force switched off
789 result = F0;
790 break;
791 }
792 case 1: { // linear
793 result = F1;
794 break;
795 }
796 case 2: { // new correlations by Konstantin (2020)
797 std::array<MFloat, 6> lCorrs{0.01, 0.86, 1.77, 0.34, 0.88, -0.05};
798 MFloat flMax = F1;
799 MFloat flShift = F1;
800 if(partRe > 1) flShift += lCorrs[0] * pow(log(beta), lCorrs[1]) * pow(log(partRe), lCorrs[2]);
801 MFloat psi = F1B2 * M_PI * pow(inclinationAngle / M_PI * F2, flShift);
802 flMax = F1 + lCorrs[3] * pow(partRe, lCorrs[4] + lCorrs[5]);
803 MFloat ClMax = F1B2 * flMax * (K0 - K2);
804 MFloat Cl = F2 * sin(psi) * cos(psi) * ClMax;
805 result = Cl;
806 break;
807 }
808 default: {
809 mTerm(1, AT_, "Unknown particle drag method");
810 }
811 }
812 return result;
813}
814
820template <MInt nDim>
821MFloat LPTEllipsoidal<nDim>::torqueFactor(const MFloat partRe, const MFloat beta, const MFloat inclinationAngle) {
822 MFloat result = NAN;
823 switch(s_backPtr->m_torqueModelType) {
824 case 0: { // torque switched off
825 result = F0;
826 break;
827 }
828 case 1: { // linear
829 result = F1;
830 break;
831 }
832 case 2: { // new correlations by Konstantin (2020)
833 std::array<MFloat, 7> tCorrs{0.931, 0.675, 0.162, 0.657, 2.77, 0.178, 0.177};
834 MFloat CTMax = tCorrs[0] * pow(log(beta), tCorrs[1]) / pow(partRe, tCorrs[2])
835 + tCorrs[3] * pow(log(beta), tCorrs[4]) / pow(partRe, tCorrs[5] + tCorrs[6] * log(beta));
836 MFloat CT = F2 * sin(inclinationAngle) * cos(inclinationAngle) * CTMax;
837 result = CT;
838 break;
839 }
840 default: {
841 mTerm(1, AT_, "Unknown particle torque method");
842 }
843 }
844 return result;
845}
846
847
853template <MInt nDim>
855 MFloat beta = m_aspectRatio;
856 MFloat beta2 = beta * beta;
857 if(fabs(beta - F1) < 1e-14) {
858 // spherical
859 m_shapeParams[0] = F2 * POW2(m_semiMinorAxis);
860 m_shapeParams[1] = F2B3;
861 m_shapeParams[2] = F2B3;
862 m_shapeParams[3] = F2B3;
863 } else if(beta > F1) {
864 // prolate
865 MFloat kappa = log((beta - sqrt(beta2 - F1)) / (beta + sqrt(beta2 - F1)));
866 m_shapeParams[0] = -beta * POW2(m_semiMinorAxis) * kappa / sqrt(beta2 - F1);
867 m_shapeParams[1] = beta2 / (beta2 - F1) + beta * kappa / (F2 * pow(beta2 - F1, F3B2));
868 m_shapeParams[2] = beta2 / (beta2 - F1) + beta * kappa / (F2 * pow(beta2 - F1, F3B2));
869 m_shapeParams[3] = -F2 / (beta2 - F1) - beta * kappa / (pow(beta2 - F1, F3B2));
870 } else if(beta < F1) {
871 // oblate
872 MFloat kappa = F2 * atan2(beta, sqrt(F1 - beta2));
873 m_shapeParams[0] = beta * POW2(m_semiMinorAxis) * (PI - kappa) / sqrt(F1 - beta2);
874 m_shapeParams[1] = -beta * (kappa - PI + F2 * beta * sqrt(F1 - beta2)) / (F2 * pow(F1 - beta2, F3B2));
875 m_shapeParams[1] = -beta * (kappa - PI + F2 * beta * sqrt(F1 - beta2)) / (F2 * pow(F1 - beta2, F3B2));
876 m_shapeParams[1] = (beta * kappa - beta * PI + F2 * sqrt(F1 - beta2)) / (pow(F1 - beta2, F3B2));
877 } else {
878 // general triaxial ellipsoid
879 mTerm(1, "Generalize here");
880 }
881}
882
883
884template <MInt nDim>
886 mTerm(1, AT_, "Not yet implemented");
887}
888
889
895template <MInt nDim>
897 std::array<MFloat, nDim> majorAxisParticleFixed{}; // major axis in particle fixed coordinate system
898 for(MInt i = 0; i < nDim; i++) {
899 i == m_particleMajorAxis ? majorAxisParticleFixed[i] = maxParticleRadius() : majorAxisParticleFixed[i] = F0;
900 }
901 MFloatScratchSpace R(3, 3, AT_, "R");
902 maia::math::computeRotationMatrix(R, &m_quaternion[0]);
903 maia::math::matrixVectorProductTranspose(majorAxis, R, majorAxisParticleFixed.begin());
904}
905
906// Explicit instantiations
907template class LPTEllipsoidal<3>;
MFloat liftFactor(const MFloat partRe, const MFloat beta, const MFloat inclinationAngle, const MFloat K0, const MFloat K2)
Calculates the lift factor for current particle with given Re number, aspect ratio,...
LPTEllipsoidal()
constructor of ellipsoidal particles
MFloat torqueFactor(const MFloat partRe, const MFloat beta, const MFloat inclinationAngle)
Calculates the torque factor for current particle with given Re number, aspect ratio and inclination ...
void calculateMajorAxisOrientation(MFloat *majorAxis)
Calculate the orientation of the major axis in the world coordinate system.
void motionEquation() override
void heatCoupling()
add the heat flux from the particle to the cell/all surrounding cells
void initShapeParams()
Calcultes shape parameters for current particle (see Siewert, 2014)
void coupling() override
single particle coupling terms
void momentumCoupling()
add the momentum flux from the particle to the cell/all surrounding cells
void advanceParticle() override
advance to new timeStep
void energyEquation() override
MFloat dragFactor(const MFloat partRe, const MFloat beta=1, const MFloat inclinationAngle=0, const MFloat K0=0, const MFloat K2=0)
Calculates the drag factor for current particle with given Re number, aspect ratio and inclination an...
void massCoupling()
add the mass flux from the particle to the cell/all surrounding cells
This class is a ScratchSpace.
Definition: scratch.h:758
void fill(T val)
fill the scratch with a given value
Definition: scratch.h:311
iterator begin()
Definition: scratch.h:273
void mTerm(const MInt errorCode, const MString &location, const MString &message)
Definition: functions.cpp:29
constexpr Real POW2(const Real x)
Definition: functions.h:119
constexpr T mMax(const T &x, const T &y)
Definition: functions.h:94
int32_t MInt
Definition: maiatypes.h:62
double MFloat
Definition: maiatypes.h:52
Definition: lpt.h:47
void cross(const T *const u, const T *const v, T *const c)
Definition: maiamath.h:101
void matrixVectorProduct(MFloat *c, MFloatScratchSpace &A, MFloat *b)
c=A*b
Definition: maiamath.h:561
void invert(MFloat *A, const MInt m, const MInt n)
Definition: maiamath.cpp:171
void computeRotationMatrix(MFloatScratchSpace &R, MFloat *q)
rotation matrix co-rotating(~inertial) frame -> body-fixed frame
Definition: maiamath.h:533
void matrixVectorProductTranspose(MFloat *c, MFloatScratchSpace &A, MFloat *b)
c=A^t*b
Definition: maiamath.h:574
define array structures