MAIA bb96820c
Multiphysics at AIA
Loading...
Searching...
No Matches
lptspherical.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 "lptspherical.h"
8#include <cmath>
9#include <functional>
10#include "lpt.h"
11
12using namespace std;
13using namespace maia::lpt;
14
15// reference values used for LPT non-dimensionalisation:
16template <MInt nDim>
18// L_ref / L_refGrid (length factor between the particle reference length
19// and the grid reference length and)
20template <MInt nDim>
22// reference Re-number: Re_ref = rho_ref * u_ref * L_ref / mu_ref
23template <MInt nDim>
25// reference Pr-number: Pr_ref = mu_ref * Cp_ref / lamda_ref
26template <MInt nDim>
28// reference Sc-number: Sc_ref = mu_ref / (rho_ref * D_ref)
29template <MInt nDim>
31// reference We-number: We_ref = rho_ref * u_ref^2 * L_ref / sigma_ref
32template <MInt nDim>
33std::array<MFloat, nDim> LPTSpherical<nDim>::s_Frm{};
34// modified reference Fr-number with : Fr_ref = u_ref / sqrt( g * L_ref)
35// the modified version is Frm = 1/ (Fr_ref^2) used to reduce computations
36
41template <MInt nDim>
43 //#ifdef LPT_DEBUG
44 const auto nan = std::numeric_limits<MFloat>::quiet_NaN();
45
46 for(MInt i = 0; i < nDim; i++) {
47 m_fluidVel[i] = nan;
48 m_oldFluidVel[i] = nan;
49 m_velocity[i] = nan;
50 m_oldVel[i] = nan;
51 m_position[i] = nan;
52 m_oldPos[i] = nan;
53 m_accel[i] = nan;
54 m_oldAccel[i] = nan;
55 }
56 //#endif
57}
58
59
64template <MInt nDim>
66 firstStep() = false;
67 hasCollided() = false;
68 hadWallColl() = false;
69 // m_creationTime = 0.0;
70 for(MInt i = 0; i < nDim; i++) {
71 ASSERT(!isnan(m_fluidVel[i]), "");
72 m_oldFluidVel[i] = m_fluidVel[i];
73 }
74 ASSERT(m_fluidDensity > 0, "");
75 m_oldFluidDensity = m_fluidDensity;
76}
77
78
83template <MInt nDim>
85 if(m_cellId < 0) {
86 mTerm(1, AT_, "coupling of invalid particle?");
87 }
88
89 if(!s_backPtr->m_heatCoupling && !s_backPtr->m_evaporation) {
90 // NOTE: set weights for later source term redistribution if not done for the evaporation yet!
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 // TODO: add debug-flags!
103 if((s_backPtr->m_heatCoupling && std::isnan(m_heatFlux)) || (s_backPtr->m_massCoupling && std::isnan(m_dM))) {
104 cerr << "Invalid energy equation!" << m_partId << " " << m_position[0] << " " << m_position[1] << " "
105 << m_position[2] << endl;
106 }
107 for(MInt i = 0; i < nDim; i++) {
108 if(std::isnan(m_accel[i])) {
109 cerr << "Invalid motion equation!" << m_partId << " " << m_position[0] << " " << m_position[1] << " "
110 << m_position[2] << endl;
111 isInvalid() = true;
112 return;
113 }
114 if(std::isnan(m_velocity[i])) {
115 cerr << "Invalid motion equation2!" << m_partId << " " << m_position[0] << " " << m_position[1] << " "
116 << m_position[2] << " " << m_densityRatio << " " << hadWallColl() << endl;
117 isInvalid() = true;
118 return;
119 }
120 }
121
122 if(s_backPtr->m_momentumCoupling) momentumCoupling();
123 if(s_backPtr->m_heatCoupling) heatCoupling();
124 if(s_backPtr->m_massCoupling) massCoupling();
125}
130template <MInt nDim>
132 if(m_creationTime > s_backPtr->m_timeStep) {
133 cerr << "Limiting creation time from " << m_creationTime << " to " << s_backPtr->m_timeStep - MFloatEps << endl;
134 m_creationTime = s_backPtr->m_timeStep - MFloatEps;
135 }
136
137 const MFloat relT = (s_backPtr->m_timeStep - m_creationTime) / s_backPtr->m_timeStep;
138
139 if(m_dM < 0 || relT < 0 || relT > 1) {
140 cerr << m_creationTime << " " << m_partId << " " << m_dM << " " << relT << " " << s_backPtr->m_timeStep << endl;
141 mTerm(1, AT_ "Invalid mass flux");
142 }
143
144 // NOTE: negative massFlux means going from liquid/disperse(LPT) to fluid/continous(FV)
145 const MFloat massFlux = -m_noParticles * m_dM * relT;
146
147 s_backPtr->m_sumEvapMass -= (massFlux * s_backPtr->m_timeStep);
148
149 if(!s_backPtr->m_couplingRedist) {
150 s_backPtr->a_massFlux(m_cellId) += massFlux;
151 } else {
152 for(MInt n = 0; n < (signed)m_neighborList.size(); n++) {
153 s_backPtr->a_massFlux(m_neighborList[n]) += m_redistWeight.at(n) * massFlux;
154 }
155 }
156}
157
158
163template <MInt nDim>
165 const MFloat relT = (s_backPtr->m_timeStep - m_creationTime) / s_backPtr->m_timeStep;
166
167 // NOTE: as the momentum coupling is applied after the evaporation, the original mass needs
168 // to be used for the force coupling!
169 const MFloat mass =
170 (s_backPtr->m_evaporation) ? m_dM * relT * s_backPtr->m_timeStep + sphericalMass() : sphericalMass();
171
172 array<MFloat, nDim> force{};
173 array<MFloat, 3> evapMom{F0, F0, F0};
174
175 for(MInt i = 0; i < nDim; i++) {
176 // momentum flux due to drag force
177 const MFloat invDensityRatio = (abs(m_densityRatio) <= MFloatEps) ? 1.0 : 1.0 / m_densityRatio;
178 force[i] = m_noParticles * mass * (m_accel[i] - ((1.0 - invDensityRatio) * s_Frm[i])) * relT;
179
180 // momentum flux due to mass change going into the continous/fluid(FV)
181 // its energy is considered in the heat-coupling
182 if(s_backPtr->m_evaporation) evapMom[i] = -m_noParticles * m_dM * m_velocity[i] * relT;
183 }
184
185 // work done by the momentum
186 // const MFloat work = std::inner_product(force.begin(), force.end(), m_velocity.begin(), 0.0);
187 const MFloat work = std::inner_product(force.begin(), force.end(), m_oldVel.begin(), 0.0);
188
189
190 if(!s_backPtr->m_couplingRedist) {
191 for(MInt i = 0; i < nDim; i++) {
192 s_backPtr->a_momentumFlux(m_cellId, i) += (force[i] + evapMom[i]);
193 }
194 s_backPtr->a_workFlux(m_cellId) += work;
195
196 } else {
197 ASSERT(m_redistWeight.size() == m_neighborList.size(),
198 "Invalid " + to_string(m_redistWeight.size()) + "!=" + to_string(m_neighborList.size()));
199 for(MInt n = 0; n < (signed)m_neighborList.size(); n++) {
200 // a weightHeat is used to distribute the source term to multiple cells...
201 for(MInt i = 0; i < nDim; i++) {
202 s_backPtr->a_momentumFlux(m_neighborList[n], i) += m_redistWeight.at(n) * (force[i] + evapMom[i]);
203 }
204 s_backPtr->a_workFlux(m_neighborList[n]) += m_redistWeight.at(n) * work;
205 }
206 }
207}
208
209
214template <MInt nDim>
216 const MFloat relT = (s_backPtr->m_timeStep - m_creationTime) / s_backPtr->m_timeStep;
217
218 // kin. energy change due to evaporation
219 MFloat velMagSquared = 0;
220 for(MInt i = 0; i < nDim; i++) {
221 velMagSquared += POW2(m_oldVel[i]);
222 }
223
224 // NOTE: must be negative if going trom the disperse/liquid(LPT) to the continous/fluid (FV)
225 const MFloat energySource = m_noParticles * (m_heatFlux - 0.5 * m_dM * velMagSquared);
226
227 if(s_backPtr->m_couplingRedist) {
228 for(MInt n = 0; n < (signed)m_neighborList.size(); n++) {
229 // a weight is used to distribute the source term to multiple cells
230 s_backPtr->a_heatFlux(m_neighborList[n]) += m_redistWeight.at(n) * energySource * relT;
231 }
232 } else {
233 s_backPtr->a_heatFlux(m_cellId) += energySource * relT;
234 }
235}
236
237
238template <MInt nDim>
240 const MFloat dt = s_backPtr->m_timeStep - m_creationTime;
241 ASSERT(dt > 0, "Timestep < 0");
242
243 // NOTE: at this point old means 1 time-Step ago and the current values will be updated below!
244 m_oldPos = m_position;
245 m_oldVel = m_velocity;
246 m_oldAccel = m_accel;
247 m_oldCellId = m_cellId;
248
249#ifdef LPT_DEBUG
250 MLong debugPartId = -1;
251 if(m_partId == debugPartId) {
252 cerr << "BT " << m_velocity[0] << " " << m_velocity[1] << " " << m_velocity[2] << endl;
253 }
254#endif
255
256 if(firstStep()) {
257 for(MInt i = 0; i < nDim; i++) {
258 m_accel[i] = s_Frm[i];
259 m_oldAccel[i] = s_Frm[i];
260 }
261 } /*else {
262 m_creationTime = 0;
263 } */
264
265 for(MInt i = 0; i < nDim; i++) {
266 ASSERT(!isnan(m_oldFluidVel[i]), "");
267 }
268 ASSERT(m_oldFluidDensity > 0, "");
269
270 // reduce variance between runs (round to 9 decimal places)
271 // TODO-timw labels:LPT,totest check if rounding is still necessary?
272 const MFloat invDensityRatio =
273 1.0 / (static_cast<MFloat>(ceil(s_backPtr->m_material->density(m_temperature) / m_oldFluidDensity * 1E9)) / 1E9);
274
275 m_densityRatio = 1 / invDensityRatio;
276
278 array<MFloat, nDim> predictedPos{};
279 array<MFloat, nDim> predictedVel{};
280
281 // predict position and veloctity
282 for(MInt i = 0; i < nDim; i++) {
283 predictedVel[i] = m_oldVel[i] + dt * m_oldAccel[i];
284 predictedPos[i] = m_oldPos[i] + dt * m_oldVel[i] * s_lengthFactor + 0.5 * dt * dt * m_oldAccel[i] * s_lengthFactor;
285 }
286
287#ifdef LPT_DEBUG
288 if(m_partId == debugPartId) {
289 cerr << "PT " << m_velocity[0] << " " << m_velocity[1] << " " << m_velocity[2] << endl;
290 }
291#endif
292
294 // find the predicted CellId at the predicted position around the previous cellId
295 const MInt predictedCellId = s_backPtr->grid().findContainingLeafCell(&predictedPos[0], m_cellId);
296
297 if(predictedCellId < 0 || m_cellId < 0) {
298 // particle has left the LPT bounding-box completely
299 //->to-be removed!
300 isInvalid() = true;
301 return;
302 }
303
304 ASSERT(m_cellId > -1, "Invalid cell!");
305 ASSERT(predictedCellId > -1, "Invalid predicetd cell!");
306 ASSERT(s_backPtr->c_isLeafCell(predictedCellId), "Invalid cell!");
307 ASSERT(predictedCellId > -1, "Invalid cell!");
308
309 array<MFloat, nDim> fluidVel{};
310 MFloat fluidDensity{};
311 vector<MFloat> predictedWeights(0);
312 interpolateVelocityAndDensity(predictedCellId, predictedPos.data(), fluidVel.data(), fluidDensity, predictedWeights);
313
314 const MFloat T = s_backPtr->a_fluidTemperature(m_cellId);
315
316 MFloat fluidViscosity = s_backPtr->m_material->dynViscosityFun(T);
317
319 if(s_backPtr->m_dragModelType == 0) {
320 for(MInt i = 0; i < nDim; i++) {
321 m_velocity[i] = predictedVel[i];
322 m_position[i] = predictedPos[i];
323 m_accel[i] = m_oldAccel[i];
324 }
325 } else {
326 switch(s_backPtr->m_motionEquationType) {
327 case 3:
328 case 0: {
329 // see also Crowe et al "Multiphase Flows with Droplets and Particles" 1998
330 // motion equation based on analytical solution of the simplified motion equation
331 // contained when assuming constant old-drag count!
332 // in this case the newton equation takes the form of:
333 // du/dt = a + b * u
334 // for which an exponential analytical solution can be found and
335 // defined by using the old-position at relative time t_old = 0.
336 // for further details contact t.wegmann@aia.rwth-aachen
337 const MFloat relVel_old = magRelVel(&m_oldFluidVel[0], &m_oldVel[0]);
338 const MFloat relVel = magRelVel(&fluidVel[0], &predictedVel[0]);
339
340 //#ifdef LPT_DEBUG
341 if(isnan(relVel)) {
342 cerr << "relVel " << fluidVel[0] << " " << fluidVel[1] << " " << fluidVel[2] << " " << predictedVel[0] << " "
343 << predictedVel[1] << " " << predictedVel[2] << " " << predictedCellId << endl;
344 mTerm(1, AT_, "Invalid relative velocity!");
345 }
346 //#endif
347
348 // get the particle drag based on velocities and density of the old position!
349 const MFloat DC_old = dragFactor(particleRe(relVel_old, m_oldFluidDensity, fluidViscosity) * s_Re) / s_Re
350 * fParticleRelTime(fluidViscosity);
351 // get the particle drag based on velocities and density of the predicted position
352 const MFloat DC = dragFactor(particleRe(relVel, fluidDensity, fluidViscosity) * s_Re) / s_Re
353 * fParticleRelTime(fluidViscosity);
354
355 // average the fluid velocity of old and predicted position
356 // increases stability for flow fields with high velocity gradients!
357 array<MFloat, nDim> avgFlowVel{};
358 for(MInt i = 0; i < nDim; i++) {
359 avgFlowVel[i] = 0.5 * (m_oldFluidVel[i] + fluidVel[i]);
360 }
361 const MFloat avgDC = s_backPtr->m_motionEquationType == 0 ? DC_old : 0.5 * (DC_old + DC);
362
363 // analytical solution assuming constant DC
364 for(MInt i = 0; i < nDim; i++) {
365 m_velocity[i] =
366 avgFlowVel[i] + (1.0 - invDensityRatio) * s_Frm[i] / avgDC
367 + exp(-avgDC * dt) * (m_oldVel[i] - avgFlowVel[i] + (invDensityRatio - 1.0) * s_Frm[i] / avgDC);
368
369 m_accel[i] = (m_velocity[i] - m_oldVel[i]) / dt;
370
371 m_position[i] = m_oldPos[i] + 0.5 * (m_oldVel[i] + m_velocity[i]) * dt * s_lengthFactor;
372 }
373 break;
374 }
375 case 1: {
376 // non-dimensional version based on:
377 // Baumgarten, "Mixture Formation in Internal Combustion Engines", 2005
378 const MFloat relVel = magRelVel(&fluidVel[0], &predictedVel[0]);
379
380 const MFloat Rep = particleRe(relVel, m_oldFluidDensity, fluidViscosity) * s_Re;
381
382 const MFloat CD = dragFactor(Rep) / s_Re * fParticleRelTime(fluidViscosity);
383
384 for(MInt i = 0; i < nDim; i++) {
385 m_accel[i] = CD * (fluidVel[i] - predictedVel[i]) + (1.0 - invDensityRatio) * s_Frm[i];
386 m_velocity[i] = m_oldVel[i] + dt * F1B2 * (m_accel[i] + m_oldAccel[i]);
387 m_position[i] = m_oldPos[i] + dt * F1B2 * (m_velocity[i] + m_oldVel[i]) * s_lengthFactor;
388 }
389 break;
390 }
391 case 2: {
392 // particle moves with fluid velocity
393 for(MInt i = 0; i < nDim; i++) {
394 m_position[i] = m_oldPos[i] + 0.5 * dt * (fluidVel[i] + m_oldVel[i]) * s_lengthFactor;
395 m_velocity[i] = fluidVel[i];
396 m_accel[i] = (m_velocity[i] - m_oldVel[i]) / dt;
397 }
398 break;
399 }
400 default: {
401 mTerm(1, AT_, "Unknown particle corrector equation type!");
402 }
403 }
404 }
405
406#ifdef LPT_DEBUG
407 if(m_partId == debugPartId) {
408 cerr << "AT " << m_velocity[0] << " " << m_velocity[1] << " " << m_velocity[2] << endl;
409 }
410#endif
411
412 // 3. update cellId based on the corrected position and set the new status!
413 checkCellChange(&m_oldPos[0]);
414
415#ifdef LPT_DEBUG
416 for(MInt i = 0; i < nDim; i++) {
417 if(std::isnan(m_accel[i])) {
418 cerr << "Nan acc. for " << s_backPtr->domainId() << " " << m_partId << " " << m_position[0] << " "
419 << m_position[1] << " " << m_position[nDim - 1] << " " << s_backPtr->a_isValidCell(m_cellId) << " "
420 << m_oldPos[0] << " " << m_oldPos[1] << " " << m_oldPos[nDim - 1] << " " << m_creationTime << " "
421 << fluidVel[0] << " " << fluidVel[1] << " " << fluidVel[nDim - 1] << fluidDensity << " " << T << " "
422 << fluidViscosity << " " << m_oldVel[0] << " " << m_oldVel[1] << " " << m_oldVel[nDim - 1] << endl;
423 }
424 }
425#endif
426}
427
428
434template <MInt nDim>
436 MFloat result = NAN;
437 switch(s_backPtr->m_dragModelType) {
438 case 0: { // drag force switched off
439 result = 0.0;
440 break;
441 }
442 case 1: { // linear Stokes drag
443 result = 1.0;
444 break;
445 }
446 case 2: { // nonlinear drag according to Schiller & Naumann
447 result = 1.0 + 0.15 * pow(partRe, 0.687);
448 break;
449 }
450 case 3: { // nonlinear drag according to Pinsky & Khain, J. Aerosol Sci. 28(7), 1177-1214 (1997).
451 result = 1.0 + 0.17 * pow(partRe, 0.632) + 1.0e-6 * pow(partRe, 2.25);
452 break;
453 }
454 case 4: {
455 // drag according to Carsten Baumgarten
456 // Mixture Formation in Internal Combustion Engines, 2005
457 // originates from Putnam 1961
458 // (C_d * Re_p / 24 )
459 if(partRe > 1000) {
460 // Newton flow regime Re from 1k to 250k
461 result = 0.424 * partRe / 24.0;
462 } else if(partRe <= 0.1) {
463 // Stokes flow
464 result = 1.0;
465 } else {
466 // transition regime
467 result = 1.0 + pow(partRe, 2.0 / 3.0) / 6.0;
468 }
469 break;
470 }
471 case 5: {
472 // drag according to Rowe for interphase momentum exchange, 1961
473 if(partRe >= 1000) {
474 result = 0.44 * partRe / 24.0;
475 } else {
476 result = 1.0 + 0.15 * pow(partRe, 0.687);
477 }
478 // limited drag to 10% fluid fraction
479 const MFloat fluidFractionUnlimited = 1 - s_backPtr->a_volumeFraction(m_cellId);
480 const MFloat fluidFraction = fluidFractionUnlimited < 0.1 ? 0.1 : fluidFractionUnlimited;
481 result *= pow(fluidFraction, -2.65) * (fluidFraction - POW2(fluidFraction));
482 break;
483 }
484 case 6: {
485 // drag corrected for fluid fraction
486 // limited drag to 10% fluid fraction
487 const MFloat fluidFractionUnlimited = 1 - s_backPtr->a_volumeFraction(m_cellId);
488 const MFloat fluidFraction = fluidFractionUnlimited < 0.1 ? 0.1 : fluidFractionUnlimited;
489 result = pow(fluidFraction, -2.65) + 1.0 / 6.0 * pow(partRe, 2.0 / 3.0) * pow(fluidFraction, -1.78);
490 break;
491 }
492 case 7: {
493 /* for testing const c_d of 1.83, according to Maier in Multisclae Simulation with a Two-Way
494 Coupled Lattice Boltzmann Methoc and Discrete Element Method (DOI: 10.1002/ceat.201600547) */
495 result = std::max(1.83 * partRe / 24, 0.1);
496 break;
497 }
498 default: {
499 mTerm(1, AT_, "Unknown particle drag method");
500 }
501 }
502 return result;
503}
504
505
506template <MInt nDim>
508 // on first time step for this particle:
509 // determine special timestep size which is used to get continuous particle generation
510 // i.e. the particle has not lived for the entire timeStep!
511 const MFloat dt = s_backPtr->m_timeStep - m_creationTime;
512 ASSERT(dt > 0, "Timestep < 0");
513
514 //#ifdef LPT_DEBUG
515 if(isnan(m_dM) || isnan(m_diameter) || isnan(m_temperature)) {
516 cerr << globalTimeStep << " " << m_dM << " " << m_diameter << " " << m_temperature << endl;
517 mTerm(1, AT_, "Nan input values for evaporation!");
518 }
519 //#endif
520
521 // sub-scripts:
522 // fluid : (continous) fluid phase
523 // liquid: (disperse, particle) liquid phase
524 // vap : vapour phase of the originally liquid particle
525 // mix : mixture of vapour and fluid phase at the bndry-state
526
527 // surf : surface state at the droplet interface
528 // bndry : (reference) boundray layer state (including fluid and vapour)
529
530
531 // NOTE: interpolate all fluid variables for evaporation to the particle position and
532 // already set weights for later source term redistribution!
533 // As in "Direct numerical simulation of a confined three-dimensional gas mixing layer
534 // with one evaporating hydrocarbon-droplet-laden stream", R.S. Miller and J. Bellan
535 m_redistWeight.clear();
536 MFloat p_fluid = -1;
537 MFloat T_fluid = -1;
538 MFloat Y_fluid = 0;
539 if(!s_backPtr->m_massCoupling) {
540 interpolateAllFluidVariables(m_cellId, m_position.data(), m_fluidVel.data(), m_fluidDensity, p_fluid, T_fluid,
541 m_redistWeight);
542 } else {
543 interpolateAllFluidVariables(m_cellId, m_position.data(), m_fluidVel.data(), m_fluidDensity, p_fluid, T_fluid,
544 Y_fluid, m_redistWeight);
545 }
546
547 // particles with small diameters are evaporating fully
548 if(m_diameter <= s_backPtr->m_sizeLimit) {
549 m_dM = sphericalMass() / dt;
550 m_diameter = 0.0;
551 isInvalid() = true;
552 fullyEvaporated() = true;
553 m_heatFlux = 0.0;
554 return;
555 }
556
557 if(hadWallColl()) {
558 // special post-wall collision treatment:
559 m_dM = 0;
560 m_heatFlux = 0.0;
561 return;
562 }
563
564 const MFloat volume = sphericalVolume();
565 ASSERT(volume > 0, "Invalid volume!");
566
567 static constexpr MFloat convEvap = 1E-10;
568 const MFloat oldDiameter = m_diameter;
569
570 MFloat previousTemp = m_temperature;
571 MInt heatStep = 0;
572
573 const MFloat oldMass = sphericalMass();
574 const MFloat oldTemperature = m_temperature;
575 const MFloat oldSheddedMass = 1.0 / 6.0 * PI * s_backPtr->m_material->density(m_temperature) * POW3(m_shedDiam);
576
577 // heat capacity of the liquid phase
578 const MFloat cp_liquid = s_backPtr->m_material->cp(m_temperature);
579
580 static constexpr MBool belanHarstad = true;
581 if(belanHarstad) {
582 // 0) get material properties:
583
584 // boiling point of the disperse phase
585 const MFloat T_Boil = s_backPtr->m_material->boilingPoint();
586 // referenece pressure for the boiling point of the disperse phase
587 const MFloat p_Boil = s_backPtr->m_material->bpRefPressure();
588
589 // specific gas constant of the vapour of the originally disperse phase
590 // NOTE: in the non-dimensional formulation: 1/gamma * M_ref / M_p
591 const MFloat gasConstant_vap = s_backPtr->m_material->gasConstant();
592
593 // molar weight ration (disperse/continuous) M_p / M_ref
594 const MFloat molWeightRatio = s_backPtr->m_material->molWeightRatio();
595
596 // NOTE: for the dimensional case this is just 1
597 const MFloat gammaMinusOne = s_backPtr->m_material->gammaMinusOne();
598
599 do {
600 previousTemp = m_temperature;
601
602 // particles with small diameters are evaporating fully
603 if(m_diameter <= s_backPtr->m_sizeLimit) {
604 m_dM = oldMass / dt;
605 m_diameter = 0.0;
606 m_temperature = oldTemperature;
607 isInvalid() = true;
608 fullyEvaporated() = true;
609 m_heatFlux = 0.0;
610 break;
611 }
612
613 // 1) use the 1/3-rule to interpolate boundary layer temperature:
614 const MFloat T_bndry = 2.0 / 3.0 * m_temperature + 1.0 / 3.0 * T_fluid;
615
616 // 2) use this reference temperture to calculate all material properties:
617
618 // viscosity of the continous phase
619 MFloat viscosity_fluid = s_backPtr->m_material->dynViscosityFun(T_bndry);
620 if(viscosity_fluid < 0) {
621 viscosity_fluid = numeric_limits<MFloat>::epsilon();
622 }
623
624 // diffusion coefficient
625 MFloat diffusionC = s_backPtr->m_material->diffusionCoefficient(T_bndry);
626 ASSERT(diffusionC > 0, "ERROR: Invalid diffusion coefficient.");
627
628 // latent heat of Evaporation of the liquid/disperse phase
629 // MFloat lH_ev = s_backPtr->m_material->latentHeatEvap(T_bndry);
630 MFloat lH_ev = s_backPtr->m_material->latentHeatEvap(m_temperature);
631
632 // thermal conductivity of the fluid/continous/gas phase
633 MFloat lamda_fluid = s_backPtr->m_material->airThermalConductivity(T_bndry);
634 if(lamda_fluid < 0) {
635 lamda_fluid = numeric_limits<MFloat>::epsilon();
636 }
637
638 // thermal condictivity of the liquid/disperse phase
639 const MFloat lamda_vap = s_backPtr->m_material->thermalConductivity(T_bndry);
640
641 // dynamic viscosity of the vapour phase
642 const MFloat viscosity_vap = s_backPtr->m_material->dynamicViscosity(T_bndry);
643
644 // density of the vapour from the originally liquid/disperse phase
645 const MFloat density_vap = p_fluid / (gasConstant_vap * T_bndry);
646
647 // Schmidt-number Sc: viscous diffusion rate / molecular diffusion rate
648 // NOTE: in dimensional form for Ranz-Marshall correlation!
649 const MFloat Sc = viscosity_fluid / (m_fluidDensity * diffusionC) * s_Sc;
650
651 // Prandtl-number of the fluid/continous/gas phase
652 const MFloat Pr = s_backPtr->m_material->airPrandtl(T_bndry) * s_Pr;
653
654 // 3) surface equilibrium mole fraction
655
656 // surface mole fraction is obtained from equilibrium assumption
657 // (partial pressure of disperse-material vapour is equal to the equilibrium vapour temperature
658 // at the drop temperature) which is used to solve the Clausius-Clapeyron
659 // equation for X_F,s (X_surf)
660 MFloat X_surf = p_Boil / p_fluid * exp(lH_ev / gasConstant_vap * (1.0 / T_Boil - 1.0 / m_temperature));
661
662 static constexpr MFloat X_surfLimit = 0.99999;
663 if(X_surf > X_surfLimit) {
664 X_surf = X_surfLimit;
665 } else if(X_surf < 0) {
666 X_surf = 1.0 - X_surfLimit;
667 }
668
669 // 4) non-equilibrium corrections to the surface mole-fraction
670 static constexpr MBool nonEquilibrium = true;
671 MFloat beta = NAN;
672 if(nonEquilibrium) {
673 /*
674 // 4.1) the beta-factor
675 // NOTE: in dimensional system
676 if(heatStep == 0) {
677 beta = (cp_fluid * oldDM) / (2.0 * PI * lamda_fluid * m_diameter) * s_Pr * s_Re;
678 } else {
679 beta = (cp_fluid * m_dM) / (2.0 * PI * lamda_fluid * m_diameter) * s_Pr * s_Re;
680 }
681 */
682
683 // blowing Re-number
684 const MFloat Re_b = m_dM / (PI * viscosity_fluid * m_diameter) * s_Re;
685 beta = 0.5 * Pr * Re_b;
686
687 // 4.2) the Langmuir-Knudsen law
688 // dimensionless Knudsen-layer parameter ( Lk / diameter)
689 const MFloat Lk_d =
690 viscosity_fluid * sqrt(2.0 * PI * m_temperature * gasConstant_vap) / (Sc * p_fluid * m_diameter * s_Re);
691
692 // non-equilibrium correction:
693 X_surf = X_surf - (2.0 * beta * Lk_d);
694
695 // limiting...
696 if(X_surf < 0) {
697 X_surf = 1.0 - X_surfLimit;
698 }
699 }
700
701 ASSERT(X_surf <= 1.0, "ERROR: Invalid corrected fuel surface mole concentration (>100%): " + to_string(X_surf));
702 ASSERT(X_surf >= 0.0, "ERROR: Invalid corrected fuel surface mole concentration (<0%): " + to_string(X_surf));
703
704 // 5) surface fuel mass fraction
705 MFloat Y_surf = X_surf * molWeightRatio / (X_surf * molWeightRatio + (1.0 - X_surf));
706
707 ASSERT(Y_surf <= 1.0, "ERROR: Invalid fuel surface mass concentration (>100%): " + to_string(Y_surf));
708 if(std::isnan(Y_surf)) {
709 cerr << "X_surf " << X_surf << " m_temperature " << m_temperature << " " << p_fluid << m_partId << " "
710 << m_position[0] << " " << m_position[1] << " " << m_position[2] << endl;
711 }
712
713 // 6) Spalding mass transfer number BM
714 // BM = (Y_F,s - Y_F,infty)/(1-Y_F,s)
715 // where Y is the fuel mass fraction at the surface (s) and
716 // Y_F,infty is the fuel mass fraction at the the far-field conditions
717 // for the droplet particle
718 MFloat BM = (Y_surf - Y_fluid) / (1.0 - Y_surf);
719 if(BM < MFloatEps) {
720 // cerr << "BM " << BM << " Y_surf " << Y_surf << " Y_inf " << Y_fluid << " "
721 // << oldDiameter << " " << beta << " " << X_surf << endl;
722 BM = 0;
723 }
724
725 // 7) Compute Mixture of disperse material-vapour and continous phase in the boundary layer
726 // based on Yuen and Chen, 1976 i.e. the 1/3-rule / the Wilke Rule (Edwards et al., 1979)
727
728 // boundary layer disperse-material vapour mass fraction values chosen as proposed by
729 MFloat Y_bndry = 2.0 / 3.0 * Y_surf + 1.0 / 3.0 * Y_fluid;
730
731 ASSERT(Y_bndry <= 1.0, "ERROR: Invalid fuel boundary layer mass concentration (>100%): " + to_string(Y_bndry));
732
733 if(std::isnan(Y_bndry)) {
734 cerr << "Y_surf " << Y_surf << " Y_f " << Y_fluid << endl;
735 }
736
737 // boundary layer disperse vapour mole fraction
738 MFloat X_bndry = -Y_bndry / (Y_bndry * molWeightRatio - Y_bndry - molWeightRatio);
739
740
741 // thermal conductivity of vapour+fluid mixture
742 MFloat lamda_vapFluid = POW2(1.0 + sqrt(lamda_vap / lamda_fluid) * pow(1 / molWeightRatio, 0.25))
743 / sqrt(8.0 * (1.0 + molWeightRatio));
744 if(isnan(lamda_vapFluid)) {
745 cerr << "lamda_vap " << lamda_vap << " lamda_fluid " << lamda_fluid << " lamda_vapFluid " << lamda_vapFluid
746 << endl;
747 }
748
749 MFloat lamda_fluidVap = POW2(1.0 + sqrt(lamda_fluid / lamda_vap) * pow(molWeightRatio, 0.25))
750 / sqrt(8.0 * (1.0 + 1 / molWeightRatio));
751
752 MFloat lamda_mix = X_bndry * lamda_vap / (X_bndry + (1.0 - X_bndry) * lamda_vapFluid)
753 + (1.0 - X_bndry) * lamda_fluid / (X_bndry * lamda_fluidVap + (1.0 - X_bndry));
754 if(isnan(lamda_mix)) {
755 cerr << "X_bndry " << X_bndry << " lamda_vap " << lamda_vap << " lamda_vapFluid " << lamda_vapFluid << endl;
756 }
757 ASSERT(!std::isnan(lamda_mix), "ERROR: lamda_mix is NaN!");
758
759 // mixture of dynamic viscosity
760 MFloat viscosity_vapFluid = POW2(1.0 + sqrt(viscosity_vap / viscosity_fluid) * pow(1 / molWeightRatio, 0.25))
761 / sqrt(8.0 * (1.0 + molWeightRatio));
762
763 MFloat viscosity_fluidVap = POW2(1.0 + sqrt(viscosity_fluid / viscosity_vap) * pow(molWeightRatio, 0.25))
764 / sqrt(8.0 * (1.0 + 1 / molWeightRatio));
765
766 MFloat viscosity_mix = X_bndry * viscosity_vap / (X_bndry + (1.0 - X_bndry) * viscosity_vapFluid)
767 + (1.0 - X_bndry) * viscosity_fluid / (X_bndry * viscosity_fluidVap + (1.0 - X_bndry));
768
769 ASSERT(!std::isnan(viscosity_mix),
770 "ERROR: viscosity_mix is NaN!" + to_string(X_bndry) + " " + to_string(viscosity_vapFluid));
771
772
773 // const MFloat density_mix = 1.0 / (Y_bndry / density_vap + (1.0 - Y_bndry) / m_fluidDensity);
774 // ASSERT(density_mix > 0, "ERROR: Invalid mixture density (<0).");
775 // debug!
776 // const MFloat density_mix = s_backPtr->m_material->density(m_temperature);
777 // const MFloat density_mix = m_fluidDensity;
778 const MFloat density_mix = Y_bndry * density_vap + (1.0 - Y_bndry) * m_fluidDensity;
779
780 // heat capacity of the mixture
781 // const MFloat cp_mix = Y_bndry * cp_liquid + (1-Y_bndry) * cp_fluid;
782
783 // 8) Compute Nu and Sh-number based on emperical correlations
784
785 // particle Reynolds-Number with slip-velocity
786 // NOTE: in dimensional formulation, following:
787 //"A comparative study of numerical models for Eulerian-Lagrangian simulations
788 // of turbulent evaporating sprays" D.I. Kolaitis, M.A. Founti
789 // Int. J. of Heat and Fluid Flow 27 (2006) 424-435
790 // is using the surface/film viscosity over the gas/fluid viscosity
791 MFloat Re = particleRe(m_fluidDensity, magRelVel(m_fluidVel.data(), &m_velocity[0]), viscosity_mix) * s_Re;
792
793 // Sherwood number (Sh) is calculated using the Ranz-Marshall correlation (1952)
794 // Sh = Convective Mass transfer / Diffusion rate
795 // Limitations of the Ranz-Marshall correlation Re < 200, Pr < 250, Sc < 250
796 // NOTE: using dimensional Re/Sc-numbers for the correlation!
797 const MFloat Sh = 2.0 + 0.552 * sqrt(Re) * pow(Sc, 1.0 / 3.0);
798
799 //#ifdef LPT_DEBUG
800 ASSERT(!isnan(Sh), "Sh is nan!" + to_string(Re) + " " + to_string(Sc));
801 //#endif
802
803 // Nusselt number Nu : Convective heat transfer / Conductive heat transfer
804 // NOTE: using dimensional Re/Pr-numbers for the correlation!
805 MFloat Nu = 2.0 + 0.552 * sqrt(Re) * pow(Pr, 1.0 / 3.0);
806
807 //#ifdef LPT_DEBUG
808 if(isnan(Nu)) cerr << " Re " << Re << " Pr " << Pr << endl;
809 //#endif
810
811 // non-equilibrium correction of Nusselt-number
812 if(nonEquilibrium) {
813 if(beta > MFloatEps) {
814 Nu = Nu * (beta / (exp(beta) - 1));
815 }
816#ifdef LPT_DEBUG
817 if(isnan(Nu)) cerr << "beta " << beta << endl;
818#endif
819 }
820
821 MFloat mass = oldMass;
822
823 // 9) solve evaporation equation (dm/dt)
824 if(s_backPtr->m_evaporation) {
825 MFloat A = 0;
840 if(BM < MFloatEps) {
841 mass = oldMass;
842 } else {
843 A = -2.0 / 3.0 * density_mix * PI * diffusionC * Sh * log(1 + BM)
844 * pow(6.0 / (PI * s_backPtr->m_material->density(m_temperature)), 1.0 / 3.0) * dt / (s_Sc * s_Re);
845
846 const MFloat B = pow(oldMass, 2.0 / 3.0);
847
848 //#ifdef LPT_DEBUG
849 if(isnan(B)) {
850 cerr << "oldMass " << oldMass << endl;
851 }
852 if(isnan(A)) {
853 cerr << "A " << A << " density_mix " << density_mix << " diffusion Coefficient " << diffusionC << " SH "
854 << Sh << " BM " << BM << endl;
855 }
856 //#endif
857
858 // limit mass to be > 0
859 if(fabs(A) > B) {
860 mass = 0.0;
861 } else {
862 mass = pow(B + A, 3.0 / 2.0);
863 }
864 }
865
866 ASSERT(!std::isnan(mass), "ERROR: Mass is NaN!");
867
868 // NOTE: the direction from m_dM is switched, instead of beeing negtive for evaporation
869 // the sign is changed here!
870 // TODO-timw labels:LPT switch m_dM around to match the description in the literature!
871 // use below and switch signs everywhere!
872 // m_dM = (mass - oldMass) / dt;
873 m_dM = (oldMass - mass) / dt;
874
875 constexpr static MBool limitCondensation = true;
876 if(limitCondensation && m_dM < 0) {
877 m_dM = 0;
878 mass = oldMass;
879 }
880
881 ASSERT(!std::isnan(m_dM), "ERROR: Evaporation rate is NaN!");
882 ASSERT(mass - oldMass <= 0 || abs(mass) < numeric_limits<MFloat>::epsilon(),
883 "ERROR: droplets are increasing in size due to condensation! m_dM " + to_string(m_dM) + " oldMass "
884 + to_string(oldMass) + " mass " + to_string(mass));
885
886
887 if(mass > 0) {
888 m_diameter = pow(mass / s_backPtr->m_material->density(m_temperature) * 6.0 / PI, 1.0 / 3.0);
889 // reduce shedded diameter consistently based on evaporated mass m_dM * dt
890 // relevant for KH-secondary break-up
891 const MFloat reducedSheddedMass = oldSheddedMass - m_dM * dt;
892 m_shedDiam = pow(reducedSheddedMass / s_backPtr->m_material->density(m_temperature) * 6.0 / PI, 1.0 / 3.0);
893
894 } else { // catch case in which particle evaporates completely
895 m_dM = oldMass / dt;
896 m_diameter = 0.0;
897 mass = 0.0;
898 isInvalid() = true;
899 fullyEvaporated() = true;
900 m_temperature = oldTemperature;
901 m_heatFlux = 0;
902 // cerr << "Evap. Completly " << mass << " " << oldMass << " " << BM
903 // << " " << oldTemperature << " " << m_temperature << " " << density_mix << " " <<
904 // A << " " << Sh << " " << Re << " " << Sc << " " << dt << endl;
905 // cerr << m_temperature << " " << oldTemperature << " " << T_bndry
906 // << " " << T_fluid
907 // << endl;
908 // cerr << "Fully evaporating particle with mass " << oldMass * m_noParticles << endl;
909 break;
910 }
911 }
912
913 // 10) solve temperature equation (dT/dt)
914
925 if(Nu > 100 * MFloatEps) {
926 const MFloat c1 =
927 PI * 0.5 * (m_diameter + oldDiameter) * Nu * lamda_mix * dt / (mass * cp_liquid) / (s_Pr * s_Re);
928 MFloat c2 =
929 T_fluid
930 - m_dM * lH_ev / (Nu * lamda_mix * PI * 0.5 * (m_diameter + oldDiameter)) * (s_Re * s_Pr * gammaMinusOne);
931 m_temperature = c2 + (oldTemperature - c2) * exp(-c1);
932 } else {
937 // negative sign due to changed sign of evaporation rate!
938 m_temperature = oldTemperature - m_dM / (mass * cp_liquid) * lH_ev * dt * gammaMinusOne;
939 }
940
941#ifdef LPT_DEBUG
942 if(std::isnan(m_temperature)) {
943 cerr << "m_diam " << m_diameter << " Nu " << Nu << " thCond " << lamda_mix << endl;
944 cerr << "m_t " << m_temperature << " oldT " << oldTemperature << endl;
945 cerr << " Re " << Re << " Pr " << Pr << " " << T_bndry << " " << magRelVel(m_fluidVel.data(), &m_velocity[0])
946 << " " << viscosity_mix << " " << m_fluidDensity << " " << 2.0 + 0.552 * sqrt(Re) * pow(Pr, 1.0 / 3.0)
947 << endl;
948 TERM(-1);
949 }
950#endif
951
952 if(m_temperature < 0) {
953 m_temperature = MFloatEps;
954 }
955
956 heatStep++;
957
958 } while(abs(m_temperature - previousTemp) > convEvap && heatStep < 100);
959
960 } else {
961 // constant evaporation rate used for reference/validation cases
962 static MFloat dm_dt = (oldMass / dt) / 100000.0;
963 m_dM = dm_dt;
964 if(m_dM * dt > oldMass) {
965 m_dM = oldMass / dt;
966 }
967 m_diameter = pow((oldMass - m_dM * dt) / s_backPtr->m_material->density(m_temperature) * 6.0 / PI, 1.0 / 3.0);
968 }
969
970
971 if(m_temperature < 0) {
972 TERMM(-1, "Invalid temperature!");
973 }
974
975 // store particle energy change in heat-flux
976 if(s_backPtr->m_heatCoupling) {
977 const MFloat mass = sphericalMass();
978 // see "Direct numerical simulation of a confined three-dimensional gas mixing layer with one
979 // evaporating hydrocarbon-droplet-laden stream" by R.S. Miller and J. Bellan
980 // in J. Fluid Mech. (1999) for reference!
981 m_heatFlux = mass * cp_liquid * (m_temperature - oldTemperature) / dt * 1 / s_backPtr->m_material->gammaMinusOne();
982
983
984 ASSERT(!std::isnan(m_heatFlux) && !std::isinf(m_heatFlux),
985 "ERROR: m_heatFlux is NaN or inf! " + to_string(m_temperature) + " " + to_string(oldTemperature) + " "
986 + to_string(volume));
987
988 if(s_backPtr->m_evaporation) {
989 m_heatFlux -= (m_dM * cp_liquid * m_temperature * 1 / s_backPtr->m_material->gammaMinusOne());
990 }
991
992
993 ASSERT(!std::isnan(m_heatFlux) && !std::isinf(m_heatFlux), "ERROR: m_heatFlux is NaN or inf!");
994 }
995}
996
997
998// Explicit instantiations for 2D and 3D
999template class LPTSpherical<3>;
void motionEquation() override
LPTSpherical()
constructor of spherical particles, used to invalidate values when debugging!
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 heatCoupling()
add the heat flux from the particle to the cell/all surrounding cells
void energyEquation() override
MFloat dragFactor(const MFloat partRe)
Calculate drag factor of the current particle for the given particle Reynolds number....
void massCoupling()
add the mass flux from the particle to the cell/all surrounding cells
void mTerm(const MInt errorCode, const MString &location, const MString &message)
Definition: functions.cpp:29
constexpr Real POW3(const Real x)
Definition: functions.h:123
constexpr Real POW2(const Real x)
Definition: functions.h:119
MInt globalTimeStep
int32_t MInt
Definition: maiatypes.h:62
double MFloat
Definition: maiatypes.h:52
int64_t MLong
Definition: maiatypes.h:64
bool MBool
Definition: maiatypes.h:58
Definition: lpt.h:47