targetCoeffTrim.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2012-2026 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "targetCoeffTrim.H"
27 #include "rotorDisk.H"
28 #include "geometricOneField.H"
30 
31 using namespace Foam::constant;
32 
33 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
34 
35 namespace Foam
36 {
38 
40 }
41 
42 
43 // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
44 
45 template<class RhoFieldType>
47 (
48  const RhoFieldType& rho,
49  const vectorField& U,
50  const scalarField& thetag,
52 ) const
53 {
54  rotor_.calculate(rho, U, thetag, force, false, false);
55 
56  const labelList& cells = rotor_.zone().zone();
57  const vectorField& C = rotor_.mesh().C();
58  const List<point>& x = rotor_.x();
59 
60  const vector& origin = rotor_.coordSys().origin();
61  const vector& rollAxis = rotor_.coordSys().R().e1();
62  const vector& pitchAxis = rotor_.coordSys().R().e2();
63  const vector& yawAxis = rotor_.coordSys().R().e3();
64 
65  scalar coeff1 = alpha_*sqr(rotor_.omega())*mathematical::pi;
66 
67  vector cf(Zero);
68  forAll(cells, i)
69  {
70  label celli = cells[i];
71 
72  vector fc = force[celli];
73  vector mc = fc^(C[celli] - origin);
74 
75  if (useCoeffs_)
76  {
77  scalar radius = x[i].x();
78  scalar coeff2 = rho[celli]*coeff1*pow4(radius);
79 
80  // add to coefficient vector
81  cf[0] += (fc & yawAxis)/(coeff2 + rootVSmall);
82  cf[1] += (mc & pitchAxis)/(coeff2*radius + rootVSmall);
83  cf[2] += (mc & rollAxis)/(coeff2*radius + rootVSmall);
84  }
85  else
86  {
87  cf[0] += fc & yawAxis;
88  cf[1] += mc & pitchAxis;
89  cf[2] += mc & rollAxis;
90  }
91  }
92 
93  reduce(cf, sumOp<vector>());
94 
95  return cf;
96 }
97 
98 
99 template<class RhoFieldType>
101 (
102  const RhoFieldType& rho,
103  const vectorField& U,
105 ) const
106 {
107  if (rotor_.mesh().time().timeIndex() % calcFrequency_ == 0)
108  {
109  word calcType = "forces";
110  if (useCoeffs_)
111  {
112  calcType = "coefficients";
113  }
114 
115  Info<< indent
116  << type() << ":" << nl
117  << " solving for target trim " << calcType << nl;
118 
119  const scalar rhoRef = rotor_.rhoRef();
120 
121  // iterate to find new pitch angles to achieve target force
122  scalar err = great;
123  label iter = 0;
124  tensor J(Zero);
125 
126  vector old = Zero;
127  while ((err > tol_) && (iter < nIter_))
128  {
129  // cache initial theta vector
130  vector theta0(theta_);
131 
132  // set initial values
133  old = calcCoeffs(rho, U, thetag(), force);
134 
135  // construct Jacobian by perturbing the pitch angles
136  // by +/-(dTheta_/2)
137  for (label pitchI = 0; pitchI < 3; pitchI++)
138  {
139  theta_[pitchI] -= dTheta_/2.0;
140  vector cf0 = calcCoeffs(rho, U, thetag(), force);
141 
142  theta_[pitchI] += dTheta_;
143  vector cf1 = calcCoeffs(rho, U, thetag(), force);
144 
145  vector ddTheta = (cf1 - cf0)/dTheta_;
146 
147  J[pitchI + 0] = ddTheta[0];
148  J[pitchI + 3] = ddTheta[1];
149  J[pitchI + 6] = ddTheta[2];
150 
151  theta_ = theta0;
152  }
153 
154  // calculate the change in pitch angle vector
155  vector dt = inv(J) & (target_/rhoRef - old);
156 
157  // update pitch angles
158  vector thetaNew = theta_ + relax_*dt;
159 
160  // update error
161  err = mag(thetaNew - theta_);
162 
163  // update for next iteration
164  theta_ = thetaNew;
165  iter++;
166  }
167 
168  if (iter == nIter_)
169  {
170  Info<< indent << "solution not converged in " << iter
171  << " iterations, final residual = " << err
172  << "(" << tol_ << ")" << endl;
173  }
174  else
175  {
176  Info<< indent << "final residual = " << err << "(" << tol_
177  << "), iterations = " << iter << endl;
178  }
179 
180  Info<< indent << "current and target " << calcType << nl
181  << indent << " thrust = " << old[0]*rhoRef
182  << ", " << target_[0] << nl
183  << indent << " pitch = " << old[1]*rhoRef
184  << ", " << target_[1] << nl
185  << indent << " roll = " << old[2]*rhoRef
186  << ", " << target_[2] << nl
187  << indent << " new pitch angles [deg]:" << nl
188  << indent << " theta0 = " << radToDeg(theta_[0]) << nl
189  << indent << " theta1c = " << radToDeg(theta_[1]) << nl
190  << indent << " theta1s = " << radToDeg(theta_[2]) << nl
191  << endl;
192  }
193 }
194 
195 
196 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
197 
199 (
200  const fv::rotorDisk& rotor,
201  const dictionary& dict
202 )
203 :
204  trimModel(rotor, dict, typeName),
205  calcFrequency_(-1),
206  useCoeffs_(true),
207  target_(Zero),
208  theta_(Zero),
209  nIter_(50),
210  tol_(1e-8),
211  relax_(1.0),
212  dTheta_(degToRad(0.1)),
213  alpha_(1.0)
214 {
215  read(dict);
216 }
217 
218 
219 // * * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * //
220 
222 {}
223 
224 
225 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
226 
228 {
230 
231  const dictionary& targetDict(coeffs_.subDict("target"));
232  useCoeffs_ = targetDict.lookupOrDefault<bool>("useCoeffs", true);
233  word ext = "";
234  if (useCoeffs_)
235  {
236  ext = "Coeff";
237  }
238 
239  target_[0] = targetDict.lookup<scalar>("thrust" + ext);
240  target_[1] = targetDict.lookup<scalar>("pitch" + ext);
241  target_[2] = targetDict.lookup<scalar>("roll" + ext);
242 
243  const dictionary& pitchAngleDict(coeffs_.subDict("pitchAngles"));
244  theta_[0] = pitchAngleDict.lookup<scalar>("theta0Ini", units::degrees);
245  theta_[1] = pitchAngleDict.lookup<scalar>("theta1cIni", units::degrees);
246  theta_[2] = pitchAngleDict.lookup<scalar>("theta1sIni", units::degrees);
247 
248  coeffs_.lookup("calcFrequency") >> calcFrequency_;
249 
250  coeffs_.readIfPresent("nIter", nIter_);
251  coeffs_.readIfPresent("tol", tol_);
252  coeffs_.readIfPresent("relax", relax_);
253 
254  coeffs_.readIfPresent("dTheta", units::degrees, dTheta_);
255 
256  alpha_ = coeffs_.lookup<scalar>("alpha");
257 }
258 
259 
261 {
262  const List<vector>& x = rotor_.x();
263 
264  tmp<scalarField> ttheta(new scalarField(x.size()));
265  scalarField& t = ttheta.ref();
266 
267  forAll(t, i)
268  {
269  scalar psi = x[i].y();
270  t[i] = theta_[0] + theta_[1]*cos(psi) + theta_[2]*sin(psi);
271  }
272 
273  return ttheta;
274 }
275 
276 
278 (
279  const vectorField& U,
281 ) const
282 {
283  correctTrim(geometricOneField(), U, force);
284 }
285 
286 
288 (
289  const volScalarField rho,
290  const vectorField& U,
292 ) const
293 {
294  correctTrim(rho, U, force);
295 }
296 
297 
298 // ************************************************************************* //
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:449
Macros for easy insertion into run-time selection tables.
Graphite solid properties.
Definition: C.H:51
C()
Construct null.
Definition: C.C:40
Generic GeometricField class.
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
T lookupOrDefault(const word &, const T &) const
Find and return a T, if not found return the given default.
ITstream & lookup(const word &, bool recursive=false, bool patternMatch=true) const
Find and return an entry data stream.
Definition: dictionary.C:669
Cell based momentum source which approximates the mean effects of rotor forces on a cylindrical regio...
Definition: rotorDisk.H:125
A class representing the concept of a GeometricField of 1 used to avoid unnecessary manipulations for...
Target trim forces/coefficients.
virtual ~targetCoeffTrim()
Destructor.
virtual tmp< scalarField > thetag() const
Return the geometric angle of attack [rad].
targetCoeffTrim(const fv::rotorDisk &rotor, const dictionary &dict)
Constructor.
void read(const dictionary &dict)
Read.
virtual void correct(const vectorField &U, vectorField &force) const
Correct the model.
void correctTrim(const RhoFieldType &rho, const vectorField &U, vectorField &force) const
Correct the model.
vector calcCoeffs(const RhoFieldType &rho, const vectorField &U, const scalarField &alphag, vectorField &force) const
Calculate the rotor force and moment coefficients vector.
A class for managing temporary objects.
Definition: tmp.H:55
T & ref() const
Return non-const reference or generate a fatal error.
Definition: tmpI.H:197
Trim model base class.
Definition: trimModel.H:54
virtual void read(const dictionary &dict)
Read.
Definition: trimModel.C:61
Template function which returns the un-mangled name of a given type. Useful for types which do not ha...
A class for handling words, derived from string.
Definition: word.H:63
const cellShapeList & cells
const volScalarField & psi
U
Definition: pEqn.H:72
rho
Definition: pEqn.H:1
Collection of constants.
const dimensionSet force
const unitSet degrees
Namespace for OpenFOAM.
static const zero Zero
Definition: zero.H:97
const doubleScalar e
Definition: doubleScalar.H:106
scalar radToDeg(const scalar rad)
Convert radians to degrees.
Definition: units.C:370
scalar degToRad(const scalar deg)
Convert degrees to radians.
Definition: units.C:364
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
addToRunTimeSelectionTable(polyPatch, mergedCyclicPolyPatch, word)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:288
dimensionedScalar sin(const dimensionedScalar &ds)
messageStream Info
void pow4(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
tmp< DimensionedField< typename outerProduct< Type, Type >::type, GeoMesh, Field >> sqr(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
void reduce(const List< UPstream::commsStruct > &comms, T &Value, const BinaryOp &bop, const int tag, const label comm)
void inv(pointPatchField< tensor > &, const pointPatchField< tensor > &)
tmp< DimensionedField< scalar, GeoMesh, Field > > mag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
defineTypeNameAndDebug(atmosphericBoundaryLayer, 0)
Ostream & indent(Ostream &os)
Indent stream.
Definition: Ostream.H:243
static const char nl
Definition: Ostream.H:297
dimensionedScalar cos(const dimensionedScalar &ds)
fileType type(const fileName &, const bool checkVariants=true, const bool followLink=true)
Return the file type: directory or file.
Definition: POSIX.C:488
dictionary dict
#define calcType(Type, GeoField)