/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2012-2016 OpenFOAM Foundation Copyright (C) 2020 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 "targetCoeffTrim.H" #include "geometricOneField.H" #include "addToRunTimeSelectionTable.H" using namespace Foam::constant; // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // namespace Foam { defineTypeNameAndDebug(targetCoeffTrim, 0); addToRunTimeSelectionTable(trimModel, targetCoeffTrim, dictionary); } // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * // template Foam::vector Foam::targetCoeffTrim::calcCoeffs ( const RhoFieldType& rho, const vectorField& U, const scalarField& thetag, vectorField& force ) const { rotor_.calculate(rho, U, thetag, force, false, false); const labelList& cells = rotor_.cells(); const vectorField& C = rotor_.mesh().C(); const List& x = rotor_.x(); const vector& origin = rotor_.coordSys().origin(); const vector& rollAxis = rotor_.coordSys().e1(); const vector& pitchAxis = rotor_.coordSys().e2(); const vector& yawAxis = rotor_.coordSys().e3(); scalar coeff1 = alpha_*sqr(rotor_.omega())*mathematical::pi; vector cf(Zero); forAll(cells, i) { label celli = cells[i]; vector fc = force[celli]; vector mc = fc^(C[celli] - origin); if (useCoeffs_) { scalar radius = x[i].x(); scalar coeff2 = rho[celli]*coeff1*pow4(radius); // add to coefficient vector cf[0] += (fc & yawAxis)/(coeff2 + ROOTVSMALL); cf[1] += (mc & pitchAxis)/(coeff2*radius + ROOTVSMALL); cf[2] += (mc & rollAxis)/(coeff2*radius + ROOTVSMALL); } else { cf[0] += fc & yawAxis; cf[1] += mc & pitchAxis; cf[2] += mc & rollAxis; } } reduce(cf, sumOp()); return cf; } template void Foam::targetCoeffTrim::correctTrim ( const RhoFieldType& rho, const vectorField& U, vectorField& force ) { if (rotor_.mesh().time().timeIndex() % calcFrequency_ == 0) { word calcType = "forces"; if (useCoeffs_) { calcType = "coefficients"; } Info<< type() << ":" << nl << " solving for target trim " << calcType << nl; const scalar rhoRef = rotor_.rhoRef(); // iterate to find new pitch angles to achieve target force scalar err = GREAT; label iter = 0; tensor J(Zero); vector old = Zero; while ((err > tol_) && (iter < nIter_)) { // cache initial theta vector vector theta0(theta_); // set initial values old = calcCoeffs(rho, U, thetag(), force); // construct Jacobian by perturbing the pitch angles // by +/-(dTheta_/2) for (label pitchI = 0; pitchI < 3; pitchI++) { theta_[pitchI] -= dTheta_/2.0; vector cf0 = calcCoeffs(rho, U, thetag(), force); theta_[pitchI] += dTheta_; vector cf1 = calcCoeffs(rho, U, thetag(), force); vector ddTheta = (cf1 - cf0)/dTheta_; J[pitchI + 0] = ddTheta[0]; J[pitchI + 3] = ddTheta[1]; J[pitchI + 6] = ddTheta[2]; theta_ = theta0; } // calculate the change in pitch angle vector vector dt = inv(J) & (target_/rhoRef - old); // update pitch angles vector thetaNew = theta_ + relax_*dt; // update error err = mag(thetaNew - theta_); // update for next iteration theta_ = thetaNew; iter++; } if (iter == nIter_) { Info<< " solution not converged in " << iter << " iterations, final residual = " << err << "(" << tol_ << ")" << endl; } else { Info<< " final residual = " << err << "(" << tol_ << "), iterations = " << iter << endl; } Info<< " current and target " << calcType << nl << " thrust = " << old[0]*rhoRef << ", " << target_[0] << nl << " pitch = " << old[1]*rhoRef << ", " << target_[1] << nl << " roll = " << old[2]*rhoRef << ", " << target_[2] << nl << " new pitch angles [deg]:" << nl << " theta0 = " << radToDeg(theta_[0]) << nl << " theta1c = " << radToDeg(theta_[1]) << nl << " theta1s = " << radToDeg(theta_[2]) << nl << endl; } } // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // Foam::targetCoeffTrim::targetCoeffTrim ( const fv::rotorDiskSource& rotor, const dictionary& dict ) : trimModel(rotor, dict, typeName), calcFrequency_(-1), useCoeffs_(true), target_(Zero), theta_(Zero), nIter_(50), tol_(1e-8), relax_(1.0), dTheta_(degToRad(0.1)), alpha_(1.0) { read(dict); } // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // void Foam::targetCoeffTrim::read(const dictionary& dict) { trimModel::read(dict); const dictionary& targetDict(coeffs_.subDict("target")); useCoeffs_ = targetDict.getOrDefault("useCoeffs", true); word ext = ""; if (useCoeffs_) { ext = "Coeff"; } targetDict.readEntry("thrust" + ext, target_[0]); targetDict.readEntry("pitch" + ext, target_[1]); targetDict.readEntry("roll" + ext, target_[2]); const dictionary& pitchAngleDict(coeffs_.subDict("pitchAngles")); theta_[0] = degToRad(pitchAngleDict.get("theta0Ini")); theta_[1] = degToRad(pitchAngleDict.get("theta1cIni")); theta_[2] = degToRad(pitchAngleDict.get("theta1sIni")); coeffs_.readEntry("calcFrequency", calcFrequency_); coeffs_.readIfPresent("nIter", nIter_); coeffs_.readIfPresent("tol", tol_); coeffs_.readIfPresent("relax", relax_); if (coeffs_.readIfPresent("dTheta", dTheta_)) { dTheta_ = degToRad(dTheta_); } coeffs_.readIfPresent("alpha", alpha_); } Foam::tmp Foam::targetCoeffTrim::thetag() const { const List& x = rotor_.x(); auto ttheta = tmp::New(x.size()); auto& t = ttheta.ref(); forAll(t, i) { scalar psi = x[i].y(); t[i] = theta_[0] + theta_[1]*cos(psi) + theta_[2]*sin(psi); } return ttheta; } void Foam::targetCoeffTrim::correct ( const vectorField& U, vectorField& force ) { correctTrim(geometricOneField(), U, force); } void Foam::targetCoeffTrim::correct ( const volScalarField rho, const vectorField& U, vectorField& force ) { correctTrim(rho, U, force); } // ************************************************************************* //