/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2017-2022 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. OpenFOAM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenFOAM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenFOAM. If not, see . \*---------------------------------------------------------------------------*/ #include "MassTransferPhaseSystem.H" #include "HashPtrTable.H" #include "fvcDiv.H" #include "fvmSup.H" #include "fvMatrix.H" #include "volFields.H" #include "fundamentalConstants.H" using namespace Foam::multiphaseInter; // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // template Foam::MassTransferPhaseSystem::MassTransferPhaseSystem ( const fvMesh& mesh ) : BasePhaseSystem(mesh) { this->generatePairsAndSubModels("massTransferModel", massTransferModels_); forAllConstIters(massTransferModels_, iterModel) { if (!dmdt_.found(iterModel()->pair())) { dmdt_.set ( iterModel()->pair(), new volScalarField ( IOobject ( IOobject::groupName("dmdt",iterModel()->pair().name()), this->mesh().time().timeName(), this->mesh(), IOobject::NO_READ, IOobject::AUTO_WRITE, IOobject::REGISTER ), this->mesh(), dimensionedScalar(dimDensity/dimTime, Zero) ) ); } } } // * * * * * * * * * * * * * * Protected Member Functions * * * * * * * * * // template Foam::tmp Foam::MassTransferPhaseSystem::calculateL ( const volScalarField& dmdtNetki, const phasePairKey& keyik, const phasePairKey& keyki, const volScalarField& T ) const { auto tL = volScalarField::New ( "tL", IOobject::NO_REGISTER, this->mesh(), dimensionedScalar(dimEnergy/dimMass, Zero) ); auto& L = tL.ref(); if (massTransferModels_.found(keyik)) { const autoPtr& interfacePtr = massTransferModels_[keyik]; word speciesName = interfacePtr->transferSpecie(); const word species(speciesName.substr(0, speciesName.find('.'))); L -= neg(dmdtNetki)*interfacePtr->L(species, T); } if (massTransferModels_.found(keyki)) { const autoPtr& interfacePtr = massTransferModels_[keyki]; word speciesName = interfacePtr->transferSpecie(); const word species(speciesName.substr(0, speciesName.find('.'))); L -= pos(dmdtNetki)*interfacePtr->L(species, T); } return tL; } // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * // template Foam::tmp Foam::MassTransferPhaseSystem::dmdt ( const phasePairKey& key ) const { auto tdmdt = volScalarField::New ( "dmdt", IOobject::NO_REGISTER, this->mesh(), dimensionedScalar(dimDensity/dimTime, Zero) ); auto& dmdt = tdmdt.ref(); if (dmdt_.found(key)) { dmdt = *dmdt_[key]; } return tdmdt; } template Foam::tmp Foam::MassTransferPhaseSystem::heatTransfer ( const volScalarField& T ) { auto teqn = tmp::New(T, dimEnergy/dimTime); auto& eqn = teqn.ref(); forAllConstIters(this->phaseModels_, iteri) { const phaseModel& phasei = iteri()(); auto iterk = iteri; for (++iterk; iterk != this->phaseModels_.end(); ++iterk) { if (iteri()().name() != iterk()().name()) { const phaseModel& phasek = iterk()(); // Phase i to phase k const phasePairKey keyik(phasei.name(), phasek.name(), true); // Phase k to phase i const phasePairKey keyki(phasek.name(), phasei.name(), true); // Net mass transfer from k to i phase auto tdmdtNetki = volScalarField::New ( "tdmdtYki", IOobject::NO_REGISTER, this->mesh(), dimensionedScalar(dimDensity/dimTime, Zero) ); auto& dmdtNetki = tdmdtNetki.ref(); auto tSp = volScalarField::New ( "Sp", IOobject::NO_REGISTER, this->mesh(), dimensionedScalar(dimDensity/dimTime/dimTemperature, Zero) ); auto& Sp = tSp.ref(); auto tSu = volScalarField::New ( "Su", IOobject::NO_REGISTER, this->mesh(), dimensionedScalar(dimDensity/dimTime, Zero) ); auto& Su = tSu.ref(); if (massTransferModels_.found(keyik)) { autoPtr& interfacePtr = massTransferModels_[keyik]; dmdtNetki -= *dmdt_[keyik]; tmp KSp = interfacePtr->KSp(interfaceCompositionModel::T, T); if (KSp.valid()) { Sp += KSp.ref(); } tmp KSu = interfacePtr->KSu(interfaceCompositionModel::T, T); if (KSu.valid()) { Su += KSu.ref(); } // If linearization is not provided used full explicit if (!KSp.valid() && !KSu.valid()) { Su += *dmdt_[keyik]; } } // Looking for mass transfer in the other direction (k to i) if (massTransferModels_.found(keyki)) { autoPtr& interfacePtr = massTransferModels_[keyki]; dmdtNetki += *dmdt_[keyki]; tmp KSp = interfacePtr->KSp(interfaceCompositionModel::T, T); if (KSp.valid()) { Sp += KSp.ref(); } tmp KSu = interfacePtr->KSu(interfaceCompositionModel::T, T); if (KSu.valid()) { Su += KSu.ref(); } // If linearization is not provided used full explicit if (!KSp.valid() && !KSu.valid()) { Su += *dmdt_[keyki]; } } tmp L = calculateL(dmdtNetki, keyik, keyki, T); //eqn -= dmdtNetki*L; eqn -= fvm::Sp(Sp*L.ref(), T) + Su*L.ref(); } } } return teqn; } template Foam::tmp Foam::MassTransferPhaseSystem::volTransfer ( const volScalarField& p ) { auto teqn = tmp::New(p, dimVolume/dimTime); auto& eqn = teqn.ref(); auto tSp = volScalarField::New ( "Sp", IOobject::NO_REGISTER, this->mesh(), dimensionedScalar(dimless/dimTime/dimPressure, Zero) ); auto& Sp = tSp.ref(); auto tSu = volScalarField::New ( "Su", IOobject::NO_REGISTER, this->mesh(), dimensionedScalar(dimless/dimTime, Zero) ); auto& Su = tSu.ref(); forAllConstIters(this->totalPhasePairs(), iter) { const phasePair& pair = iter()(); const phaseModel& phase1 = pair.phase1(); const phaseModel& phase2 = pair.phase2(); const phasePairKey key12 ( phase1.name(), phase2.name(), true ); if (massTransferModels_.found(key12)) { autoPtr& interfacePtr = massTransferModels_[key12]; tmp KSp = interfacePtr->KSp(interfaceCompositionModel::P, p); if (KSp.valid()) { Sp -= KSp.ref() *( - this->coeffs(phase1.name()) + this->coeffs(phase2.name()) ); } tmp KSu = interfacePtr->KSu(interfaceCompositionModel::P, p); if (KSu.valid()) { Su -= KSu.ref() *( - this->coeffs(phase1.name()) + this->coeffs(phase2.name()) ); } // If linearization is not provided used full explicit if (!KSp.valid() && !KSu.valid()) { Su -= *dmdt_[key12] *( - this->coeffs(phase1.name()) + this->coeffs(phase2.name()) ); } } const phasePairKey key21 ( phase2.name(), phase1.name(), true ); if (massTransferModels_.found(key21)) { autoPtr& interfacePtr = massTransferModels_[key21]; tmp KSp = interfacePtr->KSp(interfaceCompositionModel::P, p); if (KSp.valid()) { Sp += KSp.ref() *( - this->coeffs(phase1.name()) + this->coeffs(phase2.name()) ); } tmp KSu = interfacePtr->KSu(interfaceCompositionModel::P, p); if (KSu.valid()) { Su += KSu.ref() *( - this->coeffs(phase1.name()) + this->coeffs(phase2.name()) ); } // If linearization is not provided used full explicit if (!KSp.valid() && !KSu.valid()) { Su += *dmdt_[key21] *( - this->coeffs(phase1.name()) + this->coeffs(phase2.name()) ); } } } eqn += fvm::Sp(Sp, p) + Su; return teqn; } template void Foam::MassTransferPhaseSystem::correctMassSources ( const volScalarField& T ) { forAllConstIters(this->phaseModels_, iteri) { const phaseModel& phasei = iteri()(); auto iterk = iteri; for (++iterk; iterk != this->phaseModels_.end(); ++iterk) { if (iteri()().name() != iterk()().name()) { const phaseModel& phasek = iterk()(); // Phase i to phase k const phasePairKey keyik(phasei.name(), phasek.name(), true); // Phase k to phase i const phasePairKey keyki(phasek.name(), phasei.name(), true); if (massTransferModels_.found(keyik)) { autoPtr& interfacePtr = massTransferModels_[keyik]; tmp Kexp = interfacePtr->Kexp(T); *dmdt_[keyik] = Kexp.ref(); } if (massTransferModels_.found(keyki)) { autoPtr& interfacePtr = massTransferModels_[keyki]; // Explicit temperature mass transfer rate const tmp Kexp = interfacePtr->Kexp(T); *dmdt_[keyki] = Kexp.ref(); } } } } } template void Foam::MassTransferPhaseSystem::alphaTransfer ( SuSpTable& Su, SuSpTable& Sp ) { // This term adds/subtracts alpha*div(U) as a source term // for alpha, substituting div(U) = mDot(1/rho1 - 1/rho2) bool includeDivU(true); forAllConstIters(this->totalPhasePairs(), iter) { const phasePair& pair = iter()(); const phaseModel& phase1 = pair.phase1(); const phaseModel& phase2 = pair.phase2(); const volScalarField& alpha1 = pair.phase1(); const volScalarField& alpha2 = pair.phase2(); tmp tCoeffs1 = this->coeffs(phase1.name()); const volScalarField& coeffs1 = tCoeffs1(); tmp tCoeffs2 = this->coeffs(phase2.name()); const volScalarField& coeffs2 = tCoeffs2(); // Phase 1 to phase 2 const phasePairKey key12 ( phase1.name(), phase2.name(), true ); tmp tdmdt12(this->dmdt(key12)); volScalarField& dmdt12 = tdmdt12.ref(); if (massTransferModels_.found(key12)) { autoPtr& interfacePtr = massTransferModels_[key12]; tmp KSu = interfacePtr->KSu(interfaceCompositionModel::alpha, phase1); if (KSu.valid()) { dmdt12 = KSu.ref(); } includeDivU = interfacePtr->includeDivU(); } // Phase 2 to phase 1 const phasePairKey key21 ( phase2.name(), phase1.name(), true ); tmp tdmdt21(this->dmdt(key21)); volScalarField& dmdt21 = tdmdt21.ref(); if (massTransferModels_.found(key21)) { autoPtr& interfacePtr = massTransferModels_[key21]; tmp KSu = interfacePtr->KSu(interfaceCompositionModel::alpha, phase2); if (KSu.valid()) { dmdt21 = KSu.ref(); } includeDivU = interfacePtr->includeDivU(); } volScalarField::Internal& SpPhase1 = Sp[phase1.name()]; volScalarField::Internal& SuPhase1 = Su[phase1.name()]; volScalarField::Internal& SpPhase2 = Sp[phase2.name()]; volScalarField::Internal& SuPhase2 = Su[phase2.name()]; const volScalarField dmdtNet(dmdt21 - dmdt12); const volScalarField coeffs12(coeffs1 - coeffs2); const surfaceScalarField& phi = this->phi(); if (includeDivU) { SuPhase1 += fvc::div(phi)*clamp(alpha1, zero_one{}); SuPhase2 += fvc::div(phi)*clamp(alpha2, zero_one{}); } // NOTE: dmdtNet is distributed in terms = // Source for phase 1 = // dmdtNet/rho1 // - alpha1*dmdtNet(1/rho1 - 1/rho2) forAll(dmdtNet, celli) { scalar dmdt21 = dmdtNet[celli]; scalar coeffs12Cell = coeffs12[celli]; scalar alpha1Limited = clamp(alpha1[celli], zero_one{}); // exp. SuPhase1[celli] += coeffs1[celli]*dmdt21; if (includeDivU) { if (dmdt21 > 0) { if (coeffs12Cell > 0) { // imp SpPhase1[celli] -= dmdt21*coeffs12Cell; } else if (coeffs12Cell < 0) { // exp SuPhase1[celli] -= dmdt21*coeffs12Cell*alpha1Limited; } } else if (dmdt21 < 0) { if (coeffs12Cell > 0) { // exp SuPhase1[celli] -= dmdt21*coeffs12Cell*alpha1Limited; } else if (coeffs12Cell < 0) { // imp SpPhase1[celli] -= dmdt21*coeffs12Cell; } } } } forAll(dmdtNet, celli) { scalar dmdt12 = -dmdtNet[celli]; scalar coeffs21Cell = -coeffs12[celli]; scalar alpha2Limited = clamp(alpha2[celli], zero_one{}); // exp SuPhase2[celli] += coeffs2[celli]*dmdt12; if (includeDivU) { if (dmdt12 > 0) { if (coeffs21Cell > 0) { // imp SpPhase2[celli] -= dmdt12*coeffs21Cell; } else if (coeffs21Cell < 0) { // exp SuPhase2[celli] -= dmdt12*coeffs21Cell*alpha2Limited; } } else if (dmdt12 < 0) { if (coeffs21Cell > 0) { // exp SuPhase2[celli] -= coeffs21Cell*dmdt12*alpha2Limited; } else if (coeffs21Cell < 0) { // imp SpPhase2[celli] -= dmdt12*coeffs21Cell; } } } } // Update ddtAlphaMax this->ddtAlphaMax_ = max(gMax((dmdt21*coeffs1)()), gMax((dmdt12*coeffs2)())); } } template void Foam::MassTransferPhaseSystem::massSpeciesTransfer ( const phaseModel& phase, volScalarField::Internal& Su, volScalarField::Internal& Sp, const word speciesName ) { // Fill the volumetric mass transfer for species forAllConstIters(massTransferModels_, iter) { if (iter()->transferSpecie() == speciesName) { // Explicit source Su += this->Su()[phase.name()] + this->Sp()[phase.name()]*phase.oldTime(); } } } template bool Foam::MassTransferPhaseSystem::includeVolChange() { bool includeVolChange(true); forAllIters(massTransferModels_, iter) { if (!iter()->includeVolChange()) { includeVolChange = false; } } return includeVolChange; } // ************************************************************************* //