ENH: support 'rpm' input for propeller functionObject (as per rotorDisk)

- update code style for forces/propeller.
- simplify coordinate handling in propeller functionObject
This commit is contained in:
Mark Olesen
2025-06-06 11:25:15 +02:00
parent 96872f031f
commit 9bc6f2f91f
6 changed files with 86 additions and 92 deletions

View File

@ -6,7 +6,7 @@
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2011-2016 OpenFOAM Foundation Copyright (C) 2011-2016 OpenFOAM Foundation
Copyright (C) 2015-2024 OpenCFD Ltd. Copyright (C) 2015-2025 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -205,11 +205,10 @@ void Foam::functionObjects::forces::reset()
} }
else else
{ {
constexpr bool updateAccessTime = false;
for (const label patchi : patchIDs_) for (const label patchi : patchIDs_)
{ {
force.boundaryFieldRef(updateAccessTime)[patchi] = Zero; force.boundaryField().constCast()[patchi] = Zero;
moment.boundaryFieldRef(updateAccessTime)[patchi] = Zero; moment.boundaryField().constCast()[patchi] = Zero;
} }
} }
} }
@ -222,88 +221,82 @@ Foam::functionObjects::forces::devRhoReff
const label patchi const label patchi
) const ) const
{ {
typedef incompressible::turbulenceModel icoTurbModel; typedef incompressible::turbulenceModel icoModel;
typedef compressible::turbulenceModel cmpTurbModel; typedef compressible::turbulenceModel cmpModel;
if (foundObject<icoTurbModel>(icoTurbModel::propertiesName)) if (const auto* turbp = cfindObject<icoModel>(icoModel::propertiesName))
{ {
const auto& turb = const auto& turb = *turbp;
lookupObject<icoTurbModel>(icoTurbModel::propertiesName);
return -rho(patchi)*turb.nuEff(patchi)*devTwoSymm(gradUp); return -rho(patchi)*turb.nuEff(patchi)*devTwoSymm(gradUp);
} }
else if (foundObject<cmpTurbModel>(cmpTurbModel::propertiesName))
if (const auto* turbp = cfindObject<cmpModel>(cmpModel::propertiesName))
{ {
const auto& turb = const auto& turb = *turbp;
lookupObject<cmpTurbModel>(cmpTurbModel::propertiesName);
return -turb.muEff(patchi)*devTwoSymm(gradUp); return -turb.muEff(patchi)*devTwoSymm(gradUp);
} }
else if (foundObject<fluidThermo>(fluidThermo::dictName))
if (const auto* thermop = cfindObject<fluidThermo>(fluidThermo::dictName))
{ {
const auto& thermo = lookupObject<fluidThermo>(fluidThermo::dictName); const auto& thermo = *thermop;
return -thermo.mu(patchi)*devTwoSymm(gradUp); return -thermo.mu(patchi)*devTwoSymm(gradUp);
} }
else if (foundObject<transportModel>("transportProperties"))
if (const auto* props = cfindObject<transportModel>("transportProperties"))
{ {
const auto& laminarT = const auto& laminarT = *props;
lookupObject<transportModel>("transportProperties");
return -rho(patchi)*laminarT.nu(patchi)*devTwoSymm(gradUp); return -rho(patchi)*laminarT.nu(patchi)*devTwoSymm(gradUp);
} }
else if (foundObject<dictionary>("transportProperties"))
{
const auto& transportProperties =
lookupObject<dictionary>("transportProperties");
const dimensionedScalar nu("nu", dimViscosity, transportProperties); if (const auto* props = cfindObject<dictionary>("transportProperties"))
{
const dimensionedScalar nu("nu", dimViscosity, *props);
return -rho(patchi)*nu.value()*devTwoSymm(gradUp); return -rho(patchi)*nu.value()*devTwoSymm(gradUp);
} }
else
{
FatalErrorInFunction
<< "No valid model for viscous stress calculation"
<< exit(FatalError);
return volSymmTensorField::null(); // Failed to resolve any model
} FatalErrorInFunction
<< "No valid model for viscous stress calculation"
<< exit(FatalError);
return nullptr;
} }
Foam::tmp<Foam::volScalarField> Foam::functionObjects::forces::mu() const Foam::tmp<Foam::volScalarField> Foam::functionObjects::forces::mu() const
{ {
if (foundObject<fluidThermo>(basicThermo::dictName)) if (const auto* thermop = cfindObject<fluidThermo>(basicThermo::dictName))
{ {
const auto& thermo = lookupObject<fluidThermo>(basicThermo::dictName); const auto& thermo = *thermop;
return thermo.mu(); return thermo.mu();
} }
else if (foundObject<transportModel>("transportProperties"))
if (const auto* props = cfindObject<transportModel>("transportProperties"))
{ {
const auto& laminarT = const auto& laminarT = *props;
lookupObject<transportModel>("transportProperties");
return rho()*laminarT.nu(); return rho()*laminarT.nu();
} }
else if (foundObject<dictionary>("transportProperties"))
{
const auto& transportProperties =
lookupObject<dictionary>("transportProperties");
const dimensionedScalar nu("nu", dimViscosity, transportProperties); if (const auto* props = cfindObject<dictionary>("transportProperties"))
{
const dimensionedScalar nu("nu", dimViscosity, *props);
return rho()*nu; return rho()*nu;
} }
else
{
FatalErrorInFunction
<< "No valid model for dynamic viscosity calculation"
<< exit(FatalError);
return volScalarField::null(); // Failed to resolve any model
} FatalErrorInFunction
<< "No valid model for dynamic viscosity calculation"
<< exit(FatalError);
return nullptr;
} }
@ -320,7 +313,7 @@ Foam::tmp<Foam::volScalarField> Foam::functionObjects::forces::rho() const
); );
} }
return (lookupObject<volScalarField>(rhoName_)); return lookupObject<volScalarField>(rhoName_);
} }
@ -367,18 +360,16 @@ void Foam::functionObjects::forces::addToPatchFields
const vectorField& fV const vectorField& fV
) )
{ {
constexpr bool updateAccessTime = false;
sumPatchForcesP_ += sum(fP); sumPatchForcesP_ += sum(fP);
sumPatchForcesV_ += sum(fV); sumPatchForcesV_ += sum(fV);
force().boundaryFieldRef(updateAccessTime)[patchi] += fP + fV; force().boundaryField().constCast()[patchi] += fP + fV;
const vectorField mP(Md^fP); const vectorField mP(Md^fP);
const vectorField mV(Md^fV); const vectorField mV(Md^fV);
sumPatchMomentsP_ += sum(mP); sumPatchMomentsP_ += sum(mP);
sumPatchMomentsV_ += sum(mV); sumPatchMomentsV_ += sum(mV);
moment().boundaryFieldRef(updateAccessTime)[patchi] += mP + mV; moment().boundaryField().constCast()[patchi] += mP + mV;
} }

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com \\ / A nd | www.openfoam.com
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2021-2024 OpenCFD Ltd. Copyright (C) 2021-2025 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -73,7 +73,16 @@ void Foam::functionObjects::propellerInfo::setCoordinateSystem
axis = dict.get<vector>("axis"); axis = dict.get<vector>("axis");
axis.normalise(); axis.normalise();
n_ = dict.get<scalar>("n"); // Can specify 'rpm' or 'n' (rev/s)
if (dict.readIfPresent("rpm", n_))
{
n_ /= 60; // -> rev/s
dict.readIfPresent("n", n_); // Optional if rpm was specified
}
else
{
n_ = dict.get<scalar>("n");
}
break; break;
} }
case rotationMode::MRF: case rotationMode::MRF:
@ -90,11 +99,12 @@ void Foam::functionObjects::propellerInfo::setCoordinateSystem
<< exit(FatalIOError); << exit(FatalIOError);
} }
const auto& mrf = MRFZones->MRFZoneList::getFromName(MRFName_); const auto& mrf = MRFZones->MRFZoneList::getFromName(MRFName_);
vector offset = dict.getOrDefault("originOffset", vector::zero);
origin = offset + mrf.origin(); dict.readIfPresent("originOffset", origin);
origin += mrf.origin();
axis = mrf.axis(); axis = mrf.axis();
// Convert rad/s to revolutions per second // Convert rad/s to rev/s
n_ = (mrf.Omega() & axis)/constant::mathematical::twoPi; n_ = (mrf.Omega() & axis)/constant::mathematical::twoPi;
break; break;
} }
@ -106,31 +116,22 @@ void Foam::functionObjects::propellerInfo::setCoordinateSystem
} }
} }
// Optional orientation axis for cylindrical coordinate system
vector alphaAxis; vector alphaAxis;
if (!dict.readIfPresent("alphaAxis", alphaAxis)) if (dict.readIfPresent("alphaAxis", alphaAxis))
{ {
// Value has not been set - find vector orthogonal to axis alphaAxis.normalise();
coordSysPtr_.reset
vector cand(Zero); (
scalar minDot = GREAT; new coordSystem::cylindrical(origin, axis, alphaAxis)
for (direction d = 0; d < 3; ++d) );
{ }
vector test(Zero); else
test[d] = 1; {
scalar dotp = mag(test & axis); // Use best-guess for an orthogonal second axis
if (dotp < minDot) coordSysPtr_.reset(new coordSystem::cylindrical(origin, axis));
{
minDot = dotp;
cand = test;
}
}
alphaAxis = axis ^ cand;
} }
alphaAxis.normalise();
coordSysPtr_.reset(new coordSystem::cylindrical(origin, axis, alphaAxis));
} }
@ -222,6 +223,8 @@ const Foam::volVectorField& Foam::functionObjects::propellerInfo::U() const
<< " . Available vector fields are: " << " . Available vector fields are: "
<< flatOutput(mesh_.sortedNames<volVectorField>()) << flatOutput(mesh_.sortedNames<volVectorField>())
<< exit(FatalError); << exit(FatalError);
return volVectorField::null();
} }
return *UPtr; return *UPtr;
@ -250,8 +253,8 @@ void Foam::functionObjects::propellerInfo::setSampleDiskGeometry
} }
const label nFace = nRadius*nTheta; const label nFace = nRadius*nTheta;
points.setSize(nPoint); points.resize_nocopy(nPoint);
faces.setSize(nFace); faces.resize_nocopy(nFace);
const point& origin = coordSys.origin(); const point& origin = coordSys.origin();
const scalar zCoord = 0; const scalar zCoord = 0;
@ -495,10 +498,9 @@ Foam::scalar Foam::functionObjects::propellerInfo::meanSampleDiskField
scalar sumArea = 0; scalar sumArea = 0;
scalar sumFieldArea = 0; scalar sumFieldArea = 0;
forAll(faces_, facei)
{
const face& f = faces_[facei];
for (const face& f : faces_)
{
bool valid = true; bool valid = true;
scalar faceValue = 0; scalar faceValue = 0;
for (const label pti : f) for (const label pti : f)

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com \\ / A nd | www.openfoam.com
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Copyright (C) 2021-2024 OpenCFD Ltd. Copyright (C) 2021-2025 OpenCFD Ltd.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -121,9 +121,10 @@ Usage
URef | Reference velocity | yes | URef | Reference velocity | yes |
rotationMode | Rotation mode (see below) | yes | rotationMode | Rotation mode (see below) | yes |
origin | Sample disk centre | no* | origin | Sample disk centre | no* |
n | Revolutions per second | no* |
axis | Propeller axis | no* | axis | Propeller axis | no* |
alphaAxis | Axis that defines alpha=0 dir | no | alphaAxis | Axis that defines alpha=0 dir | no |
n | Rotation speed [rev/sec] | no* |
rpm | Rotation speed [rev/min] | no* |
MRF | Name of MRF zone | no* | MRF | Name of MRF zone | no* |
originOffset | Origin offset for MRF mode | no | (0 0 0) originOffset | Origin offset for MRF mode | no | (0 0 0)
writePropellerPerformance| Write propeller performance text file | yes | writePropellerPerformance| Write propeller performance text file | yes |
@ -140,6 +141,7 @@ Usage
Note Note
- URef is a scalar Function1 type, i.e. supports constant, table, lookup values - URef is a scalar Function1 type, i.e. supports constant, table, lookup values
- rotationMode is used to set the origin, axis and revolutions per second - rotationMode is used to set the origin, axis and revolutions per second
(alternatively rpm)
- if set to 'specified' all 3 entries are required - if set to 'specified' all 3 entries are required
- note: origin is the sample disk origin - note: origin is the sample disk origin
- if set to 'MRF' only the MRF entry is required - if set to 'MRF' only the MRF entry is required
@ -173,9 +175,8 @@ SourceFiles
namespace Foam namespace Foam
{ {
template<class Type> // Forward Declarations
class Function1; template<class Type> class Function1;
class surfaceWriter; class surfaceWriter;
namespace functionObjects namespace functionObjects

View File

@ -114,7 +114,7 @@ Usage
type | Type name: rotorDiskSource | word | yes | - type | Type name: rotorDiskSource | word | yes | -
fields | Names of operand fields | wordList | yes | - fields | Names of operand fields | wordList | yes | -
rhoRef | Reference density for incompressible case | scalar | yes | - rhoRef | Reference density for incompressible case | scalar | yes | -
rpm | Rotational speed [rad/s] | scalar | yes | - rpm | Rotational speed [rev/min] | scalar | yes | -
nBlades | Number of rotor blades | label | yes | - nBlades | Number of rotor blades | label | yes | -
tipEffect | Ratio of blade radius beyond which lift=0 | scalar | yes | - tipEffect | Ratio of blade radius beyond which lift=0 | scalar | yes | -
refDirection | Reference direction used as reference <!-- refDirection | Reference direction used as reference <!--

View File

@ -8,10 +8,10 @@ then
./Allrun.pre ./Allrun.pre
restore0Dir
runApplication decomposePar runApplication decomposePar
restore0Dir -processor
runParallel $(getApplication) runParallel $(getApplication)
runApplication reconstructPar runApplication reconstructPar

View File

@ -27,8 +27,8 @@ propellerInfo1
// rotationMode = specified: // rotationMode = specified:
origin (0 -0.1 0); origin (0 -0.1 0);
n 25.15;
axis (0 1 0); axis (0 1 0);
n 25.15; // Or as rpm 1509;
// Optionally write wake text files // Optionally write wake text files
// Note: controlled by writeControl // Note: controlled by writeControl