/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2017 OpenFOAM Foundation Copyright (C) 2018-2019 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 "PairSpringSliderDashpot.H" // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // template void Foam::PairSpringSliderDashpot::findMinMaxProperties ( scalar& RMin, scalar& rhoMax, scalar& UMagMax ) const { RMin = VGREAT; rhoMax = -VGREAT; UMagMax = -VGREAT; for (const typename CloudType::parcelType& p : this->owner()) { // Finding minimum diameter to avoid excessive arithmetic scalar dEff = p.d(); if (useEquivalentSize_) { dEff *= cbrt(p.nParticle()*volumeFactor_); } RMin = min(dEff, RMin); rhoMax = max(p.rho(), rhoMax); UMagMax = max ( mag(p.U()) + mag(p.omega())*dEff/2, UMagMax ); } // Transform the minimum diameter into minimum radius // rMin = dMin/2 // then rMin into minimum R, // 1/RMin = 1/rMin + 1/rMin, // RMin = rMin/2 = dMin/4 RMin /= 4.0; // Multiply by two to create the worst-case relative velocity UMagMax = 2*UMagMax; } // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // template Foam::PairSpringSliderDashpot::PairSpringSliderDashpot ( const dictionary& dict, CloudType& cloud ) : PairModel(dict, cloud, typeName), Estar_(), Gstar_(), alpha_(this->coeffDict().getScalar("alpha")), b_(this->coeffDict().getScalar("b")), mu_(this->coeffDict().getScalar("mu")), cohesionEnergyDensity_ ( this->coeffDict().getScalar("cohesionEnergyDensity") ), cohesion_(false), collisionResolutionSteps_ ( this->coeffDict().getScalar("collisionResolutionSteps") ), volumeFactor_(1.0), useEquivalentSize_(Switch(this->coeffDict().lookup("useEquivalentSize"))) { if (useEquivalentSize_) { this->coeffDict().readEntry("volumeFactor", volumeFactor_); } scalar nu = this->owner().constProps().poissonsRatio(); scalar E = this->owner().constProps().youngsModulus(); Estar_ = E/(2.0*(1.0 - sqr(nu))); scalar G = E/(2.0*(1.0 + nu)); Gstar_ = G/(2.0*(2.0 - nu)); cohesion_ = (mag(cohesionEnergyDensity_) > VSMALL); } // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // template bool Foam::PairSpringSliderDashpot::controlsTimestep() const { return true; } template Foam::label Foam::PairSpringSliderDashpot::nSubCycles() const { if (!(this->owner().size())) { return 1; } scalar RMin; scalar rhoMax; scalar UMagMax; findMinMaxProperties(RMin, rhoMax, UMagMax); // Note: pi^(7/5)*(5/4)^(2/5) = 5.429675 scalar minCollisionDeltaT = 5.429675 *RMin *pow(rhoMax/(Estar_*sqrt(UMagMax) + VSMALL), 0.4) /collisionResolutionSteps_; return ceil(this->owner().time().deltaTValue()/minCollisionDeltaT); } template void Foam::PairSpringSliderDashpot::evaluatePair ( typename CloudType::parcelType& pA, typename CloudType::parcelType& pB ) const { vector r_AB = (pA.position() - pB.position()); scalar dAEff = pA.d(); if (useEquivalentSize_) { dAEff *= cbrt(pA.nParticle()*volumeFactor_); } scalar dBEff = pB.d(); if (useEquivalentSize_) { dBEff *= cbrt(pB.nParticle()*volumeFactor_); } scalar r_AB_mag = mag(r_AB); scalar normalOverlapMag = 0.5*(dAEff + dBEff) - r_AB_mag; if (normalOverlapMag > 0) { // Particles in collision // Force coefficient scalar forceCoeff = this->forceCoeff(pA, pB); vector rHat_AB = r_AB/(r_AB_mag + VSMALL); vector U_AB = pA.U() - pB.U(); // Effective radius scalar R = 0.5*dAEff*dBEff/(dAEff + dBEff); // Effective mass scalar M = pA.mass()*pB.mass()/(pA.mass() + pB.mass()); scalar kN = (4.0/3.0)*sqrt(R)*Estar_; scalar etaN = alpha_*sqrt(M*kN)*pow025(normalOverlapMag); // Normal force vector fN_AB = rHat_AB *(kN*pow(normalOverlapMag, b_) - etaN*(U_AB & rHat_AB)); // Cohesion force, energy density multiplied by the area of // particle-particle overlap if (cohesion_) { fN_AB += -cohesionEnergyDensity_ *overlapArea(dAEff/2.0, dBEff/2.0, r_AB_mag) *rHat_AB; } fN_AB *= forceCoeff; pA.f() += fN_AB; pB.f() += -fN_AB; vector USlip_AB = U_AB - (U_AB & rHat_AB)*rHat_AB - ((dAEff/2*pA.omega() + dBEff/2*pB.omega()) ^ rHat_AB); scalar deltaT = this->owner().mesh().time().deltaTValue(); vector& tangentialOverlap_AB = pA.collisionRecords().matchPairRecord ( pB.origProc(), pB.origId() ).collisionData(); vector& tangentialOverlap_BA = pB.collisionRecords().matchPairRecord ( pA.origProc(), pA.origId() ).collisionData(); vector deltaTangentialOverlap_AB = USlip_AB*deltaT; tangentialOverlap_AB += deltaTangentialOverlap_AB; tangentialOverlap_BA += -deltaTangentialOverlap_AB; scalar tangentialOverlapMag = mag(tangentialOverlap_AB); if (tangentialOverlapMag > VSMALL) { scalar kT = 8.0*sqrt(R*normalOverlapMag)*Gstar_; scalar etaT = etaN; // Tangential force vector fT_AB; if (kT*tangentialOverlapMag > mu_*mag(fN_AB)) { // Tangential force greater than sliding friction, // particle slips fT_AB = -mu_*mag(fN_AB)*USlip_AB/mag(USlip_AB); tangentialOverlap_AB = Zero; tangentialOverlap_BA = Zero; } else { fT_AB = - kT*tangentialOverlap_AB - etaT*USlip_AB; } fT_AB *= forceCoeff; pA.f() += fT_AB; pB.f() += -fT_AB; pA.torque() += (dAEff/2*-rHat_AB) ^ fT_AB; pB.torque() += (dBEff/2*rHat_AB) ^ -fT_AB; } } } // ************************************************************************* //