/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2015-2025 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 "pressurePIDControlInletVelocityFvPatchVectorField.H" #include "volFields.H" #include "addToRunTimeSelectionTable.H" #include "fvPatchFieldMapper.H" #include "surfaceFields.H" #include "linear.H" #include "steadyStateDdtScheme.H" #include "syncTools.H" // * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * * // const Foam::surfaceScalarField& Foam::pressurePIDControlInletVelocityFvPatchVectorField::facePressure() const { const word pfName(pName_ + "f"); const volScalarField& p = db().lookupObject(pName_); surfaceScalarField* pfPtr = db().getObjectPtr(pfName); if (!pfPtr) { pfPtr = new surfaceScalarField(pfName, linearInterpolate(p)); pfPtr->store(); } surfaceScalarField& pf = *pfPtr; if (!pf.upToDate(p)) { pf = linearInterpolate(p); } return pf; } // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // Foam::pressurePIDControlInletVelocityFvPatchVectorField:: pressurePIDControlInletVelocityFvPatchVectorField ( const fvPatch& p, const DimensionedField& iF ) : fixedValueFvPatchField(p, iF), upstreamName_(), downstreamName_(), deltaP_(1), shapeFactor_(0), pName_("p"), phiName_("phi"), rhoName_("none"), P_(0), I_(0), D_(0), Q_(- gSum(*this & patch().Sf())), error_(0), errorIntegral_(0), oldQ_(0), oldError_(0), oldErrorIntegral_(0), timeIndex_(db().time().timeIndex()) {} Foam::pressurePIDControlInletVelocityFvPatchVectorField:: pressurePIDControlInletVelocityFvPatchVectorField ( const pressurePIDControlInletVelocityFvPatchVectorField& ptf, const fvPatch& p, const DimensionedField& iF, const fvPatchFieldMapper& mapper ) : fixedValueFvPatchField(ptf, p, iF, mapper), upstreamName_(ptf.upstreamName_), downstreamName_(ptf.downstreamName_), deltaP_(ptf.deltaP_), shapeFactor_(ptf.shapeFactor_), pName_(ptf.pName_), phiName_(ptf.phiName_), rhoName_(ptf.rhoName_), P_(ptf.P_), I_(ptf.I_), D_(ptf.D_), Q_(ptf.Q_), error_(ptf.error_), errorIntegral_(ptf.errorIntegral_), oldQ_(ptf.oldQ_), oldError_(ptf.oldError_), oldErrorIntegral_(ptf.oldErrorIntegral_), timeIndex_(ptf.timeIndex_) {} Foam::pressurePIDControlInletVelocityFvPatchVectorField:: pressurePIDControlInletVelocityFvPatchVectorField ( const fvPatch& p, const DimensionedField& iF, const dictionary& dict ) : fixedValueFvPatchField(p, iF, dict), upstreamName_(dict.lookup("upstream")), downstreamName_(dict.lookup("downstream")), deltaP_(dict.get("deltaP")), shapeFactor_(dict.getOrDefault("shapeFactor", 0)), pName_(dict.getOrDefault("p", "p")), phiName_(dict.getOrDefault("phi", "phi")), rhoName_(dict.getOrDefault("rho", "none")), P_(dict.get("P")), I_(dict.get("I")), D_(dict.get("D")), Q_(- gSum(*this & patch().Sf())), error_(dict.getOrDefault("error", 0)), errorIntegral_(dict.getOrDefault("errorIntegral", 0)), oldQ_(0), oldError_(0), oldErrorIntegral_(0), timeIndex_(db().time().timeIndex()) {} Foam::pressurePIDControlInletVelocityFvPatchVectorField:: pressurePIDControlInletVelocityFvPatchVectorField ( const pressurePIDControlInletVelocityFvPatchVectorField& ptf ) : fixedValueFvPatchField(ptf), upstreamName_(ptf.upstreamName_), downstreamName_(ptf.downstreamName_), deltaP_(ptf.deltaP_), shapeFactor_(ptf.shapeFactor_), pName_(ptf.pName_), phiName_(ptf.phiName_), rhoName_(ptf.rhoName_), P_(ptf.P_), I_(ptf.I_), D_(ptf.D_), Q_(ptf.Q_), error_(ptf.error_), errorIntegral_(ptf.errorIntegral_), oldQ_(ptf.oldQ_), oldError_(ptf.oldError_), oldErrorIntegral_(ptf.oldErrorIntegral_), timeIndex_(ptf.timeIndex_) {} Foam::pressurePIDControlInletVelocityFvPatchVectorField:: pressurePIDControlInletVelocityFvPatchVectorField ( const pressurePIDControlInletVelocityFvPatchVectorField& ptf, const DimensionedField& iF ) : fixedValueFvPatchField(ptf, iF), upstreamName_(ptf.upstreamName_), downstreamName_(ptf.downstreamName_), deltaP_(ptf.deltaP_), shapeFactor_(ptf.shapeFactor_), pName_(ptf.pName_), phiName_(ptf.phiName_), rhoName_(ptf.rhoName_), P_(ptf.P_), I_(ptf.I_), D_(ptf.D_), Q_(ptf.Q_), error_(ptf.error_), errorIntegral_(ptf.errorIntegral_), oldQ_(ptf.oldQ_), oldError_(ptf.oldError_), oldErrorIntegral_(ptf.oldErrorIntegral_), timeIndex_(ptf.timeIndex_) {} // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // void Foam::pressurePIDControlInletVelocityFvPatchVectorField::updateCoeffs() { if (updated()) { return; } // Get the mesh const fvMesh& mesh(patch().boundaryMesh().mesh()); // Get the time step const scalar deltaT(db().time().deltaTValue()); // Get the flux field const auto& phi = db().lookupObject(phiName_); // Update the old-time quantities if (timeIndex_ != db().time().timeIndex()) { timeIndex_ = db().time().timeIndex(); oldQ_ = Q_; oldError_ = error_; oldErrorIntegral_ = errorIntegral_; } // Get the density scalar rho = 1; if (phi.dimensions() == dimVolume/dimTime) { // do nothing ... } else if (phi.dimensions() == dimMass/dimTime) { const auto& rhop = patch().lookupPatchField(rhoName_); rho = gWeightedAverage(patch().magSf(), rhop); } else { FatalErrorInFunction << "The dimensions of the field " << phiName_ << "are not recognised. The dimensions are " << phi.dimensions() << ". The dimensions should be either " << dimVolume/dimTime << " for an incompressible case, or " << dimMass/dimTime << " for a compressible case." << exit(FatalError); } // Patch properties const scalar patchA = gSum(patch().magSf()); Q_ = - gSum(*this & patch().Sf()); // Face-zone properties (a is upstream, b is downstream) scalar Aa, Ab; vector xa, xb; faceZoneAverage(upstreamName_, mesh.Cf(), Aa, xa); faceZoneAverage(downstreamName_, mesh.Cf(), Ab, xb); const scalar L = mag(xa - xb); const scalar LbyALinear = L/(Aa - Ab)*log(Aa/Ab); const scalar LbyAStep = L/2*(1/Aa + 1/Ab); const scalar LbyA = (1 - shapeFactor_)*LbyALinear + shapeFactor_*LbyAStep; // Initialise the pressure drop. If the pressure field does not exist, the // pressure drop is assumed to be that specified. This removes the error, // so there is no control and the analytic inlet velocity is applied. This // scenario only ever going to be applicable to potentialFoam. scalar deltaP = deltaP_; if (db().foundObject(pName_)) { scalar pa, pb; faceZoneAverage(upstreamName_, facePressure(), Aa, pa); faceZoneAverage(downstreamName_, facePressure(), Ab, pb); deltaP = pa - pb; } else { WarningInFunction << "The pressure field name, 'p' is \"" << pName_ << "\", " << "but a field of that name was not found. The inlet velocity " << "will be set to an analytical value calculated from the " << "specified pressure drop. No PID control will be done and " << "transient effects will be ignored. This behaviour is designed " << "to be appropriate for potentialFoam solutions. If you are " << "getting this warning from another solver, you have probably " << "specified an incorrect pressure name." << endl << endl; } // Target and measured flow rates scalar QTarget, QMeasured; const scalar a = (1/sqr(Ab) - 1/sqr(Aa))/(2*rho); if (!mesh.steady() && db().foundObject(pName_)) { const scalar b = LbyA/deltaT; const scalar c = - LbyA/deltaT*oldQ_ /* - deltaP */; QTarget = (- b + sqrt(sqr(b) - 4*a*(c - deltaP_)))/(2*a); QMeasured = (- b + sqrt(sqr(b) - 4*a*(c - deltaP)))/(2*a); } else { QTarget = sqrt(deltaP_/a); QMeasured = sqrt(deltaP/a); } // Errors error_ = QTarget - QMeasured; errorIntegral_ = oldErrorIntegral_ + 0.5*(error_ + oldError_); const scalar errorDifferential = oldError_ - error_; // Update the field operator== ( - patch().nf() *( QTarget + P_*error_ + I_*errorIntegral_ + D_*errorDifferential )/patchA ); // Log output if (debug) { const dimensionSet pDimensions(phi.dimensions()*dimVelocity/dimArea); const scalar error = deltaP/deltaP_ - 1; const scalar newQ = - gSum(*this & patch().Sf()); Info<< "pressurePIDControlInletVelocityFvPatchField " << patch().name() << endl << " " << dimensionedScalar("U", dimVelocity, newQ/patchA) << endl << " " << dimensionedScalar("deltaP", pDimensions, deltaP) << " (" << mag(error)*100 << "% " << (error < 0 ? "below" : "above") << " the target)" << endl; } fixedValueFvPatchField::updateCoeffs(); } void Foam::pressurePIDControlInletVelocityFvPatchVectorField::write ( Ostream& os ) const { fvPatchField::write(os); os.writeEntry("deltaP", deltaP_); os.writeEntry("upstream", upstreamName_); os.writeEntry("downstream", downstreamName_); os.writeEntry("shapeFactor", shapeFactor_); os.writeEntryIfDifferent("p", "p", pName_); os.writeEntryIfDifferent("rho", "none", rhoName_); os.writeEntry("P", P_); os.writeEntry("I", I_); os.writeEntry("D", D_); os.writeEntry("error", error_); os.writeEntry("errorIntegral", errorIntegral_); fvPatchField::writeValueEntry(os); } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // namespace Foam { makePatchTypeField ( fvPatchVectorField, pressurePIDControlInletVelocityFvPatchVectorField ); } // ************************************************************************* //