/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2011-2016 OpenFOAM Foundation
\\/ M anipulation | Copyright (C) 2018 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 "rotorDiskSource.H"
#include "addToRunTimeSelectionTable.H"
#include "trimModel.H"
#include "fvMatrices.H"
#include "geometricOneField.H"
#include "syncTools.H"
#include "unitConversion.H"
using namespace Foam::constant;
// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
namespace Foam
{
namespace fv
{
defineTypeNameAndDebug(rotorDiskSource, 0);
addToRunTimeSelectionTable(option, rotorDiskSource, dictionary);
}
}
const Foam::Enum
<
Foam::fv::rotorDiskSource::geometryModeType
>
Foam::fv::rotorDiskSource::geometryModeTypeNames_
{
{ geometryModeType::gmAuto, "auto" },
{ geometryModeType::gmSpecified, "specified" },
};
const Foam::Enum
<
Foam::fv::rotorDiskSource::inletFlowType
>
Foam::fv::rotorDiskSource::inletFlowTypeNames_
{
{ inletFlowType::ifFixed, "fixed" },
{ inletFlowType::ifSurfaceNormal, "surfaceNormal" },
{ inletFlowType::ifLocal, "local" },
};
// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
void Foam::fv::rotorDiskSource::checkData()
{
// Set inflow type
switch (selectionMode())
{
case smCellSet:
case smCellZone:
case smAll:
{
// Set the profile ID for each blade section
profiles_.connectBlades(blade_.profileName(), blade_.profileID());
switch (inletFlow_)
{
case ifFixed:
{
coeffs_.readEntry("inletVelocity", inletVelocity_);
break;
}
case ifSurfaceNormal:
{
scalar UIn(coeffs_.get("inletNormalVelocity"));
inletVelocity_ = -coordSys_.e3()*UIn;
break;
}
case ifLocal:
{
break;
}
default:
{
FatalErrorInFunction
<< "Unknown inlet velocity type" << abort(FatalError);
}
}
break;
}
default:
{
FatalErrorInFunction
<< "Source cannot be used with '"
<< selectionModeTypeNames_[selectionMode()]
<< "' mode. Please use one of: " << nl
<< selectionModeTypeNames_[smCellSet] << nl
<< selectionModeTypeNames_[smCellZone] << nl
<< selectionModeTypeNames_[smAll]
<< exit(FatalError);
}
}
}
void Foam::fv::rotorDiskSource::setFaceArea(vector& axis, const bool correct)
{
area_ = 0.0;
static const scalar tol = 0.8;
const label nInternalFaces = mesh_.nInternalFaces();
const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
const vectorField& Sf = mesh_.Sf();
const scalarField& magSf = mesh_.magSf();
vector n = Zero;
// Calculate cell addressing for selected cells
labelList cellAddr(mesh_.nCells(), -1);
labelUIndList(cellAddr, cells_) = identity(cells_.size());
labelList nbrFaceCellAddr(mesh_.nFaces() - nInternalFaces, -1);
forAll(pbm, patchi)
{
const polyPatch& pp = pbm[patchi];
if (pp.coupled())
{
forAll(pp, i)
{
label facei = pp.start() + i;
label nbrFacei = facei - nInternalFaces;
label own = mesh_.faceOwner()[facei];
nbrFaceCellAddr[nbrFacei] = cellAddr[own];
}
}
}
// Correct for parallel running
syncTools::swapBoundaryFaceList(mesh_, nbrFaceCellAddr);
// Add internal field contributions
for (label facei = 0; facei < nInternalFaces; facei++)
{
const label own = cellAddr[mesh_.faceOwner()[facei]];
const label nbr = cellAddr[mesh_.faceNeighbour()[facei]];
if ((own != -1) && (nbr == -1))
{
vector nf = Sf[facei]/magSf[facei];
if ((nf & axis) > tol)
{
area_[own] += magSf[facei];
n += Sf[facei];
}
}
else if ((own == -1) && (nbr != -1))
{
vector nf = Sf[facei]/magSf[facei];
if ((-nf & axis) > tol)
{
area_[nbr] += magSf[facei];
n -= Sf[facei];
}
}
}
// Add boundary contributions
forAll(pbm, patchi)
{
const polyPatch& pp = pbm[patchi];
const vectorField& Sfp = mesh_.Sf().boundaryField()[patchi];
const scalarField& magSfp = mesh_.magSf().boundaryField()[patchi];
if (pp.coupled())
{
forAll(pp, j)
{
const label facei = pp.start() + j;
const label own = cellAddr[mesh_.faceOwner()[facei]];
const label nbr = nbrFaceCellAddr[facei - nInternalFaces];
const vector nf = Sfp[j]/magSfp[j];
if ((own != -1) && (nbr == -1) && ((nf & axis) > tol))
{
area_[own] += magSfp[j];
n += Sfp[j];
}
}
}
else
{
forAll(pp, j)
{
const label facei = pp.start() + j;
const label own = cellAddr[mesh_.faceOwner()[facei]];
const vector nf = Sfp[j]/magSfp[j];
if ((own != -1) && ((nf & axis) > tol))
{
area_[own] += magSfp[j];
n += Sfp[j];
}
}
}
}
if (correct)
{
reduce(n, sumOp());
axis = n/mag(n);
}
if (debug)
{
volScalarField area
(
IOobject
(
name_ + ":area",
mesh_.time().timeName(),
mesh_,
IOobject::NO_READ,
IOobject::NO_WRITE
),
mesh_,
dimensionedScalar(dimArea, Zero)
);
UIndirectList(area.primitiveField(), cells_) = area_;
Info<< type() << ": " << name_ << " writing field " << area.name()
<< endl;
area.write();
}
}
void Foam::fv::rotorDiskSource::createCoordinateSystem()
{
// Construct the local rotor coordinate system
vector origin(Zero);
vector axis(Zero);
vector refDir(Zero);
geometryModeType gm =
geometryModeTypeNames_.lookup("geometryMode", coeffs_);
switch (gm)
{
case gmAuto:
{
// Determine rotation origin (cell volume weighted)
scalar sumV = 0.0;
const scalarField& V = mesh_.V();
const vectorField& C = mesh_.C();
forAll(cells_, i)
{
const label celli = cells_[i];
sumV += V[celli];
origin += V[celli]*C[celli];
}
reduce(origin, sumOp());
reduce(sumV, sumOp());
origin /= sumV;
// Determine first radial vector
vector dx1(Zero);
scalar magR = -GREAT;
forAll(cells_, i)
{
const label celli = cells_[i];
vector test = C[celli] - origin;
if (mag(test) > magR)
{
dx1 = test;
magR = mag(test);
}
}
reduce(dx1, maxMagSqrOp());
magR = mag(dx1);
// Determine second radial vector and cross to determine axis
forAll(cells_, i)
{
const label celli = cells_[i];
vector dx2 = C[celli] - origin;
if (mag(dx2) > 0.5*magR)
{
axis = dx1 ^ dx2;
if (mag(axis) > SMALL)
{
break;
}
}
}
reduce(axis, maxMagSqrOp());
axis.normalise();
// Correct the axis direction using a point above the rotor
{
vector pointAbove(coeffs_.get("pointAbove"));
vector dir = pointAbove - origin;
dir.normalise();
if ((dir & axis) < 0)
{
axis *= -1.0;
}
}
coeffs_.readEntry("refDirection", refDir);
// Set the face areas and apply correction to calculated axis
// e.g. if cellZone is more than a single layer in thickness
setFaceArea(axis, true);
break;
}
case gmSpecified:
{
coeffs_.readEntry("origin", origin);
coeffs_.readEntry("axis", axis);
coeffs_.readEntry("refDirection", refDir);
setFaceArea(axis, false);
break;
}
default:
{
FatalErrorInFunction
<< "Unknown geometryMode " << geometryModeTypeNames_[gm]
<< ". Available geometry modes include "
<< geometryModeTypeNames_
<< exit(FatalError);
}
}
coordSys_ = coordSystem::cylindrical(origin, axis, refDir);
const scalar sumArea = gSum(area_);
const scalar diameter = Foam::sqrt(4.0*sumArea/mathematical::pi);
Info<< " Rotor gometry:" << nl
<< " - disk diameter = " << diameter << nl
<< " - disk area = " << sumArea << nl
<< " - origin = " << coordSys_.origin() << nl
<< " - r-axis = " << coordSys_.e1() << nl
<< " - psi-axis = " << coordSys_.e2() << nl
<< " - z-axis = " << coordSys_.e3() << endl;
}
void Foam::fv::rotorDiskSource::constructGeometry()
{
const pointUIndList cc(mesh_.C(), cells_);
// Optional: for later transform(), invTransform()
/// Rcyl_.reset(coordSys_.R(cc).ptr());
forAll(cells_, i)
{
if (area_[i] > ROOTVSMALL)
{
// Position in (planar) rotor coordinate system
x_[i] = coordSys_.localPosition(cc[i]);
// Cache max radius
rMax_ = max(rMax_, x_[i].x());
// Swept angle relative to rDir axis [radians] in range 0 -> 2*pi
scalar psi = x_[i].y();
// Blade flap angle [radians]
scalar beta =
flap_.beta0 - flap_.beta1c*cos(psi) - flap_.beta2s*sin(psi);
// Determine rotation tensor to convert from planar system into the
// rotor cone system
scalar c = cos(beta);
scalar s = sin(beta);
Rcone_[i] = tensor(c, 0, -s, 0, 1, 0, s, 0, c);
}
}
}
Foam::tmp Foam::fv::rotorDiskSource::inflowVelocity
(
const volVectorField& U
) const
{
switch (inletFlow_)
{
case ifFixed:
case ifSurfaceNormal:
{
return tmp::New(mesh_.nCells(), inletVelocity_);
break;
}
case ifLocal:
{
return U.primitiveField();
break;
}
default:
{
FatalErrorInFunction
<< "Unknown inlet flow specification" << abort(FatalError);
}
}
return tmp::New(mesh_.nCells(), Zero);
}
// * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * * //
Foam::fv::rotorDiskSource::rotorDiskSource
(
const word& name,
const word& modelType,
const dictionary& dict,
const fvMesh& mesh
)
:
cellSetOption(name, modelType, dict, mesh),
rhoRef_(1.0),
omega_(0.0),
nBlades_(0),
inletFlow_(ifLocal),
inletVelocity_(Zero),
tipEffect_(1.0),
flap_(),
x_(cells_.size(), Zero),
Rcone_(cells_.size(), I),
area_(cells_.size(), Zero),
coordSys_(),
rMax_(0.0),
trim_(trimModel::New(*this, coeffs_)),
blade_(coeffs_.subDict("blade")),
profiles_(coeffs_.subDict("profiles"))
{
read(dict);
}
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
Foam::fv::rotorDiskSource::~rotorDiskSource()
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
void Foam::fv::rotorDiskSource::addSup
(
fvMatrix& eqn,
const label fieldi
)
{
volVectorField force
(
IOobject
(
name_ + ":rotorForce",
mesh_.time().timeName(),
mesh_
),
mesh_,
dimensionedVector(eqn.dimensions()/dimVolume, Zero)
);
// Read the reference density for incompressible flow
coeffs_.readEntry("rhoRef", rhoRef_);
const vectorField Uin(inflowVelocity(eqn.psi()));
trim_->correct(Uin, force);
calculate(geometricOneField(), Uin, trim_->thetag(), force);
// Add source to rhs of eqn
eqn -= force;
if (mesh_.time().writeTime())
{
force.write();
}
}
void Foam::fv::rotorDiskSource::addSup
(
const volScalarField& rho,
fvMatrix& eqn,
const label fieldi
)
{
volVectorField force
(
IOobject
(
name_ + ":rotorForce",
mesh_.time().timeName(),
mesh_
),
mesh_,
dimensionedVector(eqn.dimensions()/dimVolume, Zero)
);
const vectorField Uin(inflowVelocity(eqn.psi()));
trim_->correct(rho, Uin, force);
calculate(rho, Uin, trim_->thetag(), force);
// Add source to rhs of eqn
eqn -= force;
if (mesh_.time().writeTime())
{
force.write();
}
}
bool Foam::fv::rotorDiskSource::read(const dictionary& dict)
{
if (cellSetOption::read(dict))
{
coeffs_.readEntry("fields", fieldNames_);
applied_.setSize(fieldNames_.size(), false);
// Read coordinate system/geometry invariant properties
omega_ = rpmToRads(coeffs_.get("rpm"));
coeffs_.readEntry("nBlades", nBlades_);
inletFlow_ = inletFlowTypeNames_.lookup("inletFlowType", coeffs_);
coeffs_.readEntry("tipEffect", tipEffect_);
const dictionary& flapCoeffs(coeffs_.subDict("flapCoeffs"));
flap_.beta0 = degToRad(flapCoeffs.get("beta0"));
flap_.beta1c = degToRad(flapCoeffs.get("beta1c"));
flap_.beta2s = degToRad(flapCoeffs.get("beta2s"));
// Create coordinate system
createCoordinateSystem();
// Read coordinate system dependent properties
checkData();
constructGeometry();
trim_->read(coeffs_);
if (debug)
{
writeField("thetag", trim_->thetag()(), true);
writeField("faceArea", area_, true);
}
return true;
}
return false;
}
// ************************************************************************* //