JSBSim Flight Dynamics Model 1.3.0 (09 Apr 2026)
An Open Source Flight Dynamics and Control Software Library in C++
Loading...
Searching...
No Matches
FGTrim.cpp
1/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2
3 Header: FGTrim.cpp
4 Author: Tony Peden
5 Date started: 9/8/99
6
7 --------- Copyright (C) 1999 Anthony K. Peden (apeden@earthlink.net) ---------
8
9 This program is free software; you can redistribute it and/or modify it under
10 the terms of the GNU Lesser General Public License as published by the Free
11 Software Foundation; either version 2 of the License, or (at your option) any
12 later version.
13
14 This program is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
17 details.
18
19 You should have received a copy of the GNU Lesser General Public License along
20 with this program; if not, write to the Free Software Foundation, Inc., 59
21 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23 Further information about the GNU Lesser General Public License can also be
24 found on the world wide web at http://www.gnu.org.
25
26 HISTORY
27--------------------------------------------------------------------------------
289/8/99 TP Created
29
30FUNCTIONAL DESCRIPTION
31--------------------------------------------------------------------------------
32
33This class takes the given set of IC's and finds the angle of attack, elevator,
34and throttle setting required to fly steady level. This is currently for in-air
35conditions only. It is implemented using an iterative, one-axis-at-a-time
36scheme. */
37
38// !!!!!!! BEWARE ALL YE WHO ENTER HERE !!!!!!!
39
40/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
41INCLUDES
42%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
43
44#include <iomanip>
45#include "FGTrim.h"
46#include "models/FGInertial.h"
47#include "models/FGAccelerations.h"
48#include "models/FGMassBalance.h"
49#include "models/FGFCS.h"
50#include "input_output/FGLog.h"
51
52#if _MSC_VER
53#pragma warning (disable : 4786 4788)
54#endif
55
56using namespace std;
57
58namespace JSBSim {
59
60//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
61
62FGTrim::FGTrim(FGFDMExec *FDMExec,TrimMode tt)
63 : fgic(FDMExec)
64{
65
66 Nsub=0;
67 max_iterations=60;
68 max_sub_iterations=100;
69 Tolerance=1E-3;
70 A_Tolerance = Tolerance / 10;
71
72 Debug=0;DebugLevel=0;
73 fdmex=FDMExec;
74 fgic = *fdmex->GetIC();
75 total_its=0;
76 gamma_fallback=false;
77 mode=tt;
78 xlo=xhi=alo=ahi=0.0;
79 targetNlf=fgic.GetTargetNlfIC();
80 debug_axis=tAll;
81 SetMode(tt);
82 if (debug_lvl & 2) {
83 FGLogging log(LogLevel::DEBUG);
84 log << "Instantiated: FGTrim\n";
85 }
86}
87
88//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89
90FGTrim::~FGTrim(void) {
91 if (debug_lvl & 2) {
92 FGLogging log(LogLevel::DEBUG);
93 log << "Destroyed: FGTrim\n";
94 }
95}
96
97//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98
100 int run_sum=0;
101 FGLogging out(LogLevel::STDOUT);
102 out << "\n Trim Statistics:\n";
103 out << " Total Iterations: " << total_its << "\n";
104 if( total_its > 0) {
105 out << " Sub-iterations:\n";
106 for (unsigned int current_axis=0; current_axis<TrimAxes.size(); current_axis++) {
107 run_sum += TrimAxes[current_axis].GetRunCount();
108 out << " " << setw(5) << TrimAxes[current_axis].GetStateName().c_str()
109 << ": " << setprecision(3) << sub_iterations[current_axis]
110 << " average: " << setprecision(5) << sub_iterations[current_axis]/double(total_its)
111 << " successful: " << setprecision(3) << successful[current_axis]
112 << " stability: " << setprecision(5) << TrimAxes[current_axis].GetAvgStability()
113 << "\n";
114 }
115 out << " Run Count: " << run_sum << "\n";
116 }
117}
118
119//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
120
121void FGTrim::Report(void) {
122 FGLogging out(LogLevel::STDOUT);
123 out << " Trim Results:\n";
124 for(unsigned int current_axis=0; current_axis<TrimAxes.size(); current_axis++)
125 TrimAxes[current_axis].AxisReport();
126}
127
128//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
129
131 mode=tCustom;
132 TrimAxes.clear();
133 //FGLogging log(LogLevel::INFO);
134 //log << "TrimAxes.size(): " << TrimAxes.size() << "\n";
135}
136
137//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
138
139bool FGTrim::AddState( State state, Control control ) {
140 mode = tCustom;
141 vector <FGTrimAxis>::iterator iAxes = TrimAxes.begin();
142 for (; iAxes != TrimAxes.end(); ++iAxes) {
143 if (iAxes->GetStateType() == state)
144 return false;
145 }
146
147 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,state,control));
148 sub_iterations.resize(TrimAxes.size());
149 successful.resize(TrimAxes.size());
150 solution.resize(TrimAxes.size());
151
152 return true;
153}
154
155//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
156
158 bool result=false;
159
160 mode = tCustom;
161 vector <FGTrimAxis>::iterator iAxes = TrimAxes.begin();
162 while (iAxes != TrimAxes.end()) {
163 if( iAxes->GetStateType() == state ) {
164 iAxes = TrimAxes.erase(iAxes);
165 result=true;
166 continue;
167 }
168 ++iAxes;
169 }
170 if(result) {
171 sub_iterations.resize(TrimAxes.size());
172 successful.resize(TrimAxes.size());
173 solution.resize(TrimAxes.size());
174 }
175 return result;
176}
177
178//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
179
180bool FGTrim::EditState( State state, Control new_control ){
181 mode = tCustom;
182 vector <FGTrimAxis>::iterator iAxes = TrimAxes.begin();
183 while (iAxes != TrimAxes.end()) {
184 if( iAxes->GetStateType() == state ) {
185 *iAxes = FGTrimAxis(fdmex,&fgic,state,new_control);
186 return true;
187 }
188 ++iAxes;
189 }
190 return false;
191}
192
193//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
194
195bool FGTrim::DoTrim(void) {
196 bool trim_failed=false;
197 unsigned int N = 0;
198 unsigned int axis_count = 0;
199 auto FCS = fdmex->GetFCS();
200 auto GroundReactions = fdmex->GetGroundReactions();
201 vector<double> throttle0 = FCS->GetThrottleCmd();
202 double elevator0 = FCS->GetDeCmd();
203 double aileron0 = FCS->GetDaCmd();
204 double rudder0 = FCS->GetDrCmd();
205 double PitchTrim0 = FCS->GetPitchTrimCmd();
206
207 for(int i=0;i < GroundReactions->GetNumGearUnits();i++)
208 GroundReactions->GetGearUnit(i)->SetReport(false);
209
210 fdmex->SetTrimStatus(true);
211 fdmex->SuspendIntegration();
212
213 fgic.SetPRadpsIC(0.0);
214 fgic.SetQRadpsIC(0.0);
215 fgic.SetRRadpsIC(0.0);
216
217 if (mode == tGround) {
218 fdmex->Initialize(&fgic);
219 fdmex->Run();
220 trimOnGround();
221 double theta = fgic.GetThetaRadIC();
222 double phi = fgic.GetPhiRadIC();
223 // Take opportunity of the first approx. found by trimOnGround() to
224 // refine the control limits.
225 TrimAxes[0].SetControlLimits(0., fgic.GetAltitudeAGLFtIC());
226 TrimAxes[1].SetControlLimits(theta - 5.0 * degtorad, theta + 5.0 * degtorad);
227 TrimAxes[2].SetControlLimits(phi - 30.0 * degtorad, phi + 30.0 * degtorad);
228 }
229
230 //clear the sub iterations counts & zero out the controls
231 for(unsigned int current_axis=0;current_axis<TrimAxes.size();current_axis++) {
232 //FGLogging log(LogLevel::INFO);
233 //log << current_axis << " " << TrimAxes[current_axis]->GetStateName()
234 //<< " " << TrimAxes[current_axis]->GetControlName()<< "\n";
235 xlo=TrimAxes[current_axis].GetControlMin();
236 xhi=TrimAxes[current_axis].GetControlMax();
237 TrimAxes[current_axis].SetControl((xlo+xhi)/2);
238 TrimAxes[current_axis].Run();
239 //TrimAxes[current_axis].AxisReport();
240 sub_iterations[current_axis]=0;
241 successful[current_axis]=0;
242 solution[current_axis]=false;
243 }
244
245 if(mode == tPullup ) {
246 FGLogging log(LogLevel::INFO);
247 log << "Setting pitch rate and nlf...\n";
248 setupPullup();
249 log << "pitch rate done ...\n";
250 TrimAxes[0].SetStateTarget(targetNlf);
251 log << "nlf done\n";
252 } else if (mode == tTurn) {
253 setupTurn();
254 //TrimAxes[0].SetStateTarget(targetNlf);
255 }
256
257 do {
258 axis_count=0;
259 for(unsigned int current_axis=0;current_axis<TrimAxes.size();current_axis++) {
260 setDebug(TrimAxes[current_axis]);
261 updateRates();
262 Nsub=0;
263 if(!solution[current_axis]) {
264 if(checkLimits(TrimAxes[current_axis])) {
265 solution[current_axis]=true;
266 solve(TrimAxes[current_axis]);
267 }
268 } else if(findInterval(TrimAxes[current_axis])) {
269 solve(TrimAxes[current_axis]);
270 } else {
271 solution[current_axis]=false;
272 }
273 sub_iterations[current_axis]+=Nsub;
274 }
275 for(unsigned int current_axis=0;current_axis<TrimAxes.size();current_axis++) {
276 //these checks need to be done after all the axes have run
277 if(Debug > 0) TrimAxes[current_axis].AxisReport();
278 if(TrimAxes[current_axis].InTolerance()) {
279 axis_count++;
280 successful[current_axis]++;
281 }
282 }
283
284 if((axis_count == TrimAxes.size()-1) && (TrimAxes.size() > 1)) {
285 //FGLogging log(LogLevel::INFO);
286 //log << TrimAxes.size()-1 << " out of " << TrimAxes.size() << "!" << "\n";
287 //At this point we can check the input limits of the failed axis
288 //and declare the trim failed if there is no sign change. If there
289 //is, keep going until success or max iteration count
290
291 //Oh, well: two out of three ain't bad
292 for(unsigned int current_axis=0;current_axis<TrimAxes.size();current_axis++) {
293 //these checks need to be done after all the axes have run
294 if(!TrimAxes[current_axis].InTolerance()) {
295 if(!checkLimits(TrimAxes[current_axis])) {
296 // special case this for now -- if other cases arise proper
297 // support can be added to FGTrimAxis
298 if( (gamma_fallback) &&
299 (TrimAxes[current_axis].GetStateType() == tUdot) &&
300 (TrimAxes[current_axis].GetControlType() == tThrottle)) {
301 FGLogging log(LogLevel::WARN);
302 log << " Can't trim udot with throttle, trying flight"
303 << " path angle. (" << N << ")\n";
304 if(TrimAxes[current_axis].GetState() > 0)
305 TrimAxes[current_axis].SetControlToMin();
306 else
307 TrimAxes[current_axis].SetControlToMax();
308 TrimAxes[current_axis].Run();
309 TrimAxes[current_axis]=FGTrimAxis(fdmex,&fgic,tUdot,tGamma);
310 } else {
311 FGLogging log(LogLevel::ERROR);
312 log << " Sorry, " << TrimAxes[current_axis].GetStateName()
313 << " doesn't appear to be trimmable\n";
314 //total_its=k;
315 trim_failed=true; //force the trim to fail
316 } //gamma_fallback
317 }
318 } //solution check
319 } //for loop
320 } //all-but-one check
321 N++;
322 if(N > max_iterations)
323 trim_failed=true;
324 } while((axis_count < TrimAxes.size()) && (!trim_failed));
325
326 if((!trim_failed) && (axis_count >= TrimAxes.size())) {
327 total_its=N;
328 if (debug_lvl > 0) {
329 FGLogging log(LogLevel::DEBUG);
330 log << "\n Trim successful\n";
331 }
332 } else { // The trim has failed
333 total_its=N;
334
335 // Restore the aircraft parameters to their initial values
336 fgic = *fdmex->GetIC();
337 FCS->SetDeCmd(elevator0);
338 FCS->SetDaCmd(aileron0);
339 FCS->SetDrCmd(rudder0);
340 FCS->SetPitchTrimCmd(PitchTrim0);
341 for (unsigned int i=0; i < throttle0.size(); i++)
342 FCS->SetThrottleCmd(i, throttle0[i]);
343
344 fdmex->Initialize(&fgic);
345 fdmex->Run();
346
347 // If WOW is true we must make sure there are no gears into the ground.
348 if (GroundReactions->GetWOW())
349 trimOnGround();
350
351 if (debug_lvl > 0) {
352 FGLogging log(LogLevel::DEBUG);
353 log << "\n Trim failed\n";
354 }
355 }
356
357 fdmex->GetPropagate()->InitializeDerivatives();
358 fdmex->ResumeIntegration();
359 fdmex->SetTrimStatus(false);
360
361 for(int i=0;i < GroundReactions->GetNumGearUnits();i++)
362 GroundReactions->GetGearUnit(i)->SetReport(true);
363
364 return !trim_failed;
365}
366
367//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
368// Trim the aircraft on the ground. The algorithm is looking for a stable
369// position of the aicraft. Assuming the aircaft is a rigid body and the ground
370// a plane: we need to find the translations and rotations of the aircraft that
371// will move 3 non-colinear points in contact with the ground.
372// The algorithm proceeds in three stages (one for each point):
373// 1. Look for the contact point closer to or deeper into the ground. Move the
374// aircraft along the vertical direction so that only this contact point
375// remains in contact with the ground.
376// 2. The forces applied on the aircraft (most likely the gravity) will generate
377// a moment on the aircraft around the point in contact. The rotation axis is
378// therefore the moment axis. The 2nd stage thus consists in determining the
379// minimum rotation angle around the first point in contact that will place a
380// second contact point on the ground.
381// 3. At this stage, 2 points are in contact with the ground: the rotation axis
382// is therefore the vector generated by the 2 points. Like stage #2, the
383// rotation direction will be driven by the moment around the axis formed by
384// the 2 points in contact. The rotation angle is obtained similarly to stage
385// #2: it is the minimum angle that will place a third contact point on the
386// ground.
387// The calculations below do not account for the compression of the landing
388// gears meaning that the position found is close to the real position but not
389// strictly equal to it.
390
391void FGTrim::trimOnGround(void)
392{
393 auto GroundReactions = fdmex->GetGroundReactions();
394 auto Propagate = fdmex->GetPropagate();
395 auto MassBalance = fdmex->GetMassBalance();
396 auto Accelerations = fdmex->GetAccelerations();
397 vector<ContactPoints> contacts;
398 FGLocation CGLocation = Propagate->GetLocation();
399 FGMatrix33 Tec2b = Propagate->GetTec2b();
400 FGMatrix33 Tb2l = Propagate->GetTb2l();
401 double hmin = 1E+10;
402 int contactRef = -1;
403
404 // Build the list of the aircraft contact points and take opportunity of the
405 // loop to find which one is closer to (or deeper into) the ground.
406 for (int i = 0; i < GroundReactions->GetNumGearUnits(); ++i) {
407 ContactPoints c;
408 auto gear = GroundReactions->GetGearUnit(i);
409
410 // Skip the retracted landing gears
411 if (!gear->GetGearUnitDown())
412 continue;
413
414 c.location = gear->GetBodyLocation();
415 FGLocation gearLoc = CGLocation.LocalToLocation(Tb2l * c.location);
416
417 FGColumnVector3 normal, vDummy;
418 FGLocation lDummy;
419 double height = fdmex->GetInertial()->GetContactPoint(gearLoc, lDummy,
420 normal, vDummy,
421 vDummy);
422
423 if (gear->IsBogey() && !GroundReactions->GetSolid())
424 continue;
425
426 c.normal = Tec2b * normal;
427 contacts.push_back(c);
428
429 if (height < hmin) {
430 hmin = height;
431 contactRef = contacts.size() - 1;
432 }
433 }
434
435 if (contacts.size() < 3)
436 return;
437
438 // Remove the contact point that is closest to the ground from the list:
439 // the rotation axis will be going thru this point so we need to remove it
440 // to avoid divisions by zero that could result from the computation of
441 // the rotations.
442 FGColumnVector3 contact0 = contacts[contactRef].location;
443 contacts.erase(contacts.begin() + contactRef);
444
445 // Update the initial conditions: this should remove the forces generated
446 // by overcompressed landing gears
447 fgic.SetAltitudeASLFtIC(fgic.GetAltitudeASLFtIC() - hmin);
448 fdmex->Initialize(&fgic);
449 fdmex->Run();
450
451 // Compute the rotation axis: it is obtained from the direction of the
452 // moment measured at the contact point 'contact0'
453 FGColumnVector3 force = MassBalance->GetMass() * Accelerations->GetUVWdot();
454 FGColumnVector3 moment = MassBalance->GetJ() * Accelerations->GetPQRdot()
455 + force * contact0;
456 FGColumnVector3 rotationAxis = moment.Normalize();
457
458 // Compute the rotation parameters: angle and the first point to come into
459 // contact with the ground when the rotation is applied.
460 RotationParameters rParam = calcRotation(contacts, rotationAxis, contact0);
461 FGQuaternion q0(rParam.angleMin, rotationAxis);
462
463 // Apply the computed rotation to all the contact points
464 FGMatrix33 rot = q0.GetTInv();
465 vector<ContactPoints>::iterator iter;
466 for (iter = contacts.begin(); iter != contacts.end(); ++iter)
467 iter->location = contact0 + rot * (iter->location - contact0);
468
469 // Remove the second point to come in contact with the ground from the list.
470 // The reason is the same than above: avoid divisions by zero when the next
471 // rotation will be computed.
472 FGColumnVector3 contact1 = rParam.contactRef->location;
473 contacts.erase(rParam.contactRef);
474
475 // Compute the rotation axis: now there are 2 points in contact with the
476 // ground so the only option for the aircraft is to rotate around the axis
477 // generated by these 2 points.
478 rotationAxis = contact1 - contact0;
479 // Make sure that the rotation orientation is consistent with the moment.
480 if (DotProduct(rotationAxis, moment) < 0.0)
481 rotationAxis = contact0 - contact1;
482
483 rotationAxis.Normalize();
484
485 // Compute the rotation parameters
486 rParam = calcRotation(contacts, rotationAxis, contact0);
487 FGQuaternion q1(rParam.angleMin, rotationAxis);
488
489 // Update the aircraft orientation
490 FGColumnVector3 euler = (fgic.GetOrientation() * q0 * q1).GetEuler();
491
492 fgic.SetPhiRadIC(euler(1));
493 fgic.SetThetaRadIC(euler(2));
494 fgic.SetPsiRadIC(euler(3));
495}
496
497//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
498// Given a set of points and a rotation axis, this routine computes for each
499// point the rotation angle that would drive the point in contact with the
500// plane. It returns the minimum angle as well as the point with which this
501// angle has been obtained.
502// The rotation axis is defined by a vector 'u' and a point 'M0' on the axis.
503// Since we are in the body frame, the position if 'M0' is measured from the CG
504// hence the name 'GM0'.
505
506FGTrim::RotationParameters FGTrim::calcRotation(vector<ContactPoints>& contacts,
507 const FGColumnVector3& u,
508 const FGColumnVector3& GM0)
509{
510 RotationParameters rParam;
511 vector<ContactPoints>::iterator iter;
512
513 rParam.angleMin = 3.0 * M_PI;
514
515 for (iter = contacts.begin(); iter != contacts.end(); ++iter) {
516 // Below the processed contact point is named 'M'
517 // Construct an orthonormal basis (u, v, t). The ground normal is obtained
518 // from iter->normal.
519 FGColumnVector3 t = u * iter->normal;
520 double length = t.Magnitude();
521 t /= length; // Normalize the tangent
522 FGColumnVector3 v = t * u;
523 FGColumnVector3 MM0 = GM0 - iter->location;
524 // d0 is the distance from the circle center 'C' to the reference point 'M0'
525 double d0 = DotProduct(MM0, u);
526 // Compute the square of the circle radius i.e. the square of the distance
527 // between 'C' and 'M'.
528 double sqrRadius = DotProduct(MM0, MM0) - d0 * d0;
529 // Compute the distance from the circle center 'C' to the line made by the
530 // intersection between the ground and the plane that contains the circle.
531 double DistPlane = d0 * DotProduct(u, iter->normal) / length;
532 // The coordinate of the point of intersection 'P' between the circle and
533 // the ground is (0, DistPlane, alpha) in the basis (u, v, t)
534 double mag = sqrRadius - DistPlane * DistPlane;
535 if (mag < 0) {
536 FGLogging log(LogLevel::WARN);
537 log << "FGTrim::calcRotation DistPlane^2 larger than sqrRadius\n";
538 }
539 double alpha = sqrt(max(mag, 0.0));
540 FGColumnVector3 CP = alpha * t + DistPlane * v;
541 // The transformation is now constructed: we can extract the angle using the
542 // classical formulas (cosine is obtained from the dot product and sine from
543 // the cross product).
544 double cosine = -DotProduct(MM0, CP) / sqrRadius;
545 double sine = DotProduct(MM0 * u, CP) / sqrRadius;
546 double angle = atan2(sine, cosine);
547 if (angle < 0.0) angle += 2.0 * M_PI;
548 if (angle < rParam.angleMin) {
549 rParam.angleMin = angle;
550 rParam.contactRef = iter;
551 }
552 }
553
554 return rParam;
555}
556
557//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
558
559bool FGTrim::solve(FGTrimAxis& axis) {
560
561 double x1,x2,x3,f1,f2,f3,d,d0;
562 const double relax =0.9;
563 double eps=axis.GetSolverEps();
564
565 x1=x2=x3=0;
566 d=1;
567 bool success=false;
568 //initializations
569 if( solutionDomain != 0) {
570 /* if(ahi > alo) { */
571 x1=xlo;f1=alo;
572 x3=xhi;f3=ahi;
573 /* } else {
574 x1=xhi;f1=ahi;
575 x3=xlo;f3=alo;
576 } */
577 d0=fabs(x3-x1);
578 //iterations
579 //max_sub_iterations=axis.GetIterationLimit();
580 while ( (axis.InTolerance() == false )
581 && (fabs(d) > eps) && (Nsub < max_sub_iterations)) {
582 Nsub++;
583 d=(x3-x1)/d0;
584 x2=x1-d*d0*f1/(f3-f1);
585 axis.SetControl(x2);
586 axis.Run();
587 f2=axis.GetState();
588 if(Debug > 1) {
589 FGLogging log(LogLevel::DEBUG);
590 log << "FGTrim::solve Nsub,x1,x2,x3: " << Nsub << ", " << x1
591 << ", " << x2 << ", " << x3 << "\n";
592 log << " " << f1 << ", " << f2 << ", " << f3 << "\n";
593 }
594 if(f1*f2 <= 0.0) {
595 x3=x2;
596 f3=f2;
597 f1=relax*f1;
598 //FGLogging log(LogLevel::INFO);
599 //log << "Solution is between x1 and x2" << "\n";
600 }
601 else if(f2*f3 <= 0.0) {
602 x1=x2;
603 f1=f2;
604 f3=relax*f3;
605 //FGLogging log(LogLevel::INFO);
606 //log << "Solution is between x2 and x3" << "\n";
607
608 }
609 //FGLogging log(LogLevel::INFO);
610 //log << i << "\n";
611
612
613 }//end while
614 if(Nsub < max_sub_iterations) success=true;
615 }
616 return success;
617}
618
619//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
620/*
621 produces an interval (xlo..xhi) on one side or the other of the current
622 control value in which a solution exists. This domain is, hopefully,
623 smaller than xmin..0 or 0..xmax and the solver will require fewer iterations
624 to find the solution. This is, hopefully, more efficient than having the
625 solver start from scratch every time. Maybe it isn't though...
626 This tries to take advantage of the idea that the changes from iteration to
627 iteration will be small after the first one or two top-level iterations.
628
629 assumes that changing the control will a produce significant change in the
630 accel i.e. checkLimits() has already been called.
631
632 if a solution is found above the current control, the function returns true
633 and xlo is set to the current control, xhi to the interval max it found, and
634 solutionDomain is set to 1.
635 if the solution lies below the current control, then the function returns
636 true and xlo is set to the interval min it found and xmax to the current
637 control. if no solution is found, then the function returns false.
638
639
640 in all cases, alo=accel(xlo) and ahi=accel(xhi) after the function exits.
641 no assumptions about the state of the sim after this function has run
642 can be made.
643*/
644bool FGTrim::findInterval(FGTrimAxis& axis) {
645 bool found=false;
646 double step;
647 double current_control=axis.GetControl();
648 double current_accel=axis.GetState();;
649 double xmin=axis.GetControlMin();
650 double xmax=axis.GetControlMax();
651 double lastxlo,lastxhi,lastalo,lastahi;
652
653 step=0.025*fabs(xmax);
654 xlo=xhi=current_control;
655 alo=ahi=current_accel;
656 lastxlo=xlo;lastxhi=xhi;
657 lastalo=alo;lastahi=ahi;
658 do {
659
660 Nsub++;
661 step*=2;
662 xlo-=step;
663 if(xlo < xmin) xlo=xmin;
664 xhi+=step;
665 if(xhi > xmax) xhi=xmax;
666 axis.SetControl(xlo);
667 axis.Run();
668 alo=axis.GetState();
669 axis.SetControl(xhi);
670 axis.Run();
671 ahi=axis.GetState();
672 if(fabs(ahi-alo) <= axis.GetTolerance()) continue;
673 if(alo*ahi <=0) { //found interval with root
674 found=true;
675 if(alo*current_accel <= 0) { //narrow interval down a bit
676 solutionDomain=-1;
677 xhi=lastxlo;
678 ahi=lastalo;
679 //xhi=current_control;
680 //ahi=current_accel;
681 } else {
682 solutionDomain=1;
683 xlo=lastxhi;
684 alo=lastahi;
685 //xlo=current_control;
686 //alo=current_accel;
687 }
688 }
689 lastxlo=xlo;lastxhi=xhi;
690 lastalo=alo;lastahi=ahi;
691 if( !found && xlo==xmin && xhi==xmax ) continue;
692 if(Debug > 1) {
693 FGLogging log(LogLevel::DEBUG);
694 log << "FGTrim::findInterval: Nsub=" << Nsub << " Lo= " << xlo
695 << " Hi= " << xhi << " alo*ahi: " << alo*ahi << "\n";
696 }
697 } while(!found && (Nsub <= max_sub_iterations) );
698 return found;
699}
700
701//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
702//checks to see which side of the current control value the solution is on
703//and sets solutionDomain accordingly:
704// 1 if solution is between the current and max
705// -1 if solution is between the min and current
706// 0 if there is no solution
707//
708//if changing the control produces no significant change in the accel then
709//solutionDomain is set to zero and the function returns false
710//if a solution is found, then xlo and xhi are set so that they bracket
711//the solution, alo is set to accel(xlo), and ahi is set to accel(xhi)
712//if there is no change or no solution then xlo=xmin, alo=accel(xmin) and
713//xhi=xmax and ahi=accel(xmax)
714//in all cases the sim is left such that the control=xmax and accel=ahi
715
716bool FGTrim::checkLimits(FGTrimAxis& axis)
717{
718 bool solutionExists;
719 double current_control=axis.GetControl();
720 double current_accel=axis.GetState();
721 xlo=axis.GetControlMin();
722 xhi=axis.GetControlMax();
723
724 axis.SetControl(xlo);
725 axis.Run();
726 alo=axis.GetState();
727 axis.SetControl(xhi);
728 axis.Run();
729 ahi=axis.GetState();
730 if(Debug > 1) {
731 FGLogging log(LogLevel::DEBUG);
732 log << "checkLimits() xlo,xhi,alo,ahi: " << xlo << ", " << xhi << ", "
733 << alo << ", " << ahi << "\n";
734 }
735 solutionDomain=0;
736 solutionExists=false;
737 if(fabs(ahi-alo) > axis.GetTolerance()) {
738 if(alo*current_accel <= 0) {
739 solutionExists=true;
740 solutionDomain=-1;
741 xhi=current_control;
742 ahi=current_accel;
743 } else if(current_accel*ahi < 0){
744 solutionExists=true;
745 solutionDomain=1;
746 xlo=current_control;
747 alo=current_accel;
748 }
749 }
750 axis.SetControl(current_control);
751 axis.Run();
752 return solutionExists;
753}
754
755//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
756
757void FGTrim::setupPullup() {
758 double g,q,cgamma;
759 g=fdmex->GetInertial()->GetGravity().Magnitude();
760 cgamma=cos(fgic.GetFlightPathAngleRadIC());
761 FGLogging log(LogLevel::INFO);
762 log << "setPitchRateInPullup(): " << g << ", " << cgamma << ", "
763 << fgic.GetVtrueFpsIC() << "\n";
764 q=g*(targetNlf-cgamma)/fgic.GetVtrueFpsIC();
765 log << targetNlf << ", " << q << "\n";
766 fgic.SetQRadpsIC(q);
767 log << "setPitchRateInPullup() complete\n";
768
769}
770
771//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
772
773void FGTrim::setupTurn(void){
774 double g,phi;
775 phi = fgic.GetPhiRadIC();
776 if( fabs(phi) > 0.001 && fabs(phi) < 1.56 ) {
777 targetNlf = 1 / cos(phi);
778 g = fdmex->GetInertial()->GetGravity().Magnitude();
779 psidot = g*tan(phi) / fgic.GetUBodyFpsIC();
780 FGLogging log(LogLevel::INFO);
781 log << targetNlf << ", " << psidot << "\n";
782 }
783
784}
785
786//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
787
788void FGTrim::updateRates(void){
789 const double vTrue = fgic.GetVtrueFpsIC();
790
791 if (mode == tTurn)
792 {
793 const double phi = fgic.GetPhiRadIC(); // bank angle
794 const double g = fdmex->GetInertial()->GetGravity().Magnitude();
795 double p = 0.0;
796 double q = 0.0;
797 double r = 0.0;
798
799 if (fabs(phi) > 0.001 && fabs(phi) < 1.56) { // < ~90°
800 const double theta = fgic.GetThetaRadIC();
801 psidot = g * tan(phi) / vTrue;
802 p = -psidot * sin(theta);
803 q = psidot * cos(theta) * sin(phi);
804 r = psidot * cos(theta) * cos(phi);
805 }
806 fgic.SetPRadpsIC(p);
807 fgic.SetQRadpsIC(q);
808 fgic.SetRRadpsIC(r);
809
810 } else if (mode == tPullup && fabs(targetNlf - 1.0) > 0.01) {
811 const double g = fdmex->GetInertial()->GetGravity().Magnitude();
812 const double cgamma = cos(fgic.GetFlightPathAngleRadIC());
813 const double q = g * (targetNlf - cgamma) / vTrue;
814 fgic.SetQRadpsIC(q);
815 }
816}
817
818//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
819
820void FGTrim::setDebug(FGTrimAxis& axis) {
821 if(debug_axis == tAll ||
822 axis.GetStateType() == debug_axis ) {
823 Debug=DebugLevel;
824 return;
825 } else {
826 Debug=0;
827 return;
828 }
829}
830
831//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
832
833void FGTrim::SetMode(TrimMode tt) {
834 ClearStates();
835 mode=tt;
836 switch(tt) {
837 case tFull:
838 if (debug_lvl > 0) {
839 FGLogging log(LogLevel::DEBUG);
840 log << " Full Trim\n";
841 }
842 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tWdot,tAlpha));
843 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tUdot,tThrottle ));
844 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tQdot,tPitchTrim ));
845 //TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tHmgt,tBeta ));
846 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tVdot,tPhi ));
847 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tPdot,tAileron ));
848 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tRdot,tRudder ));
849 break;
850 case tLongitudinal:
851 if (debug_lvl > 0) {
852 FGLogging log(LogLevel::DEBUG);
853 log << " Longitudinal Trim\n";
854 }
855 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tWdot,tAlpha ));
856 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tUdot,tThrottle ));
857 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tQdot,tPitchTrim ));
858 break;
859 case tGround:
860 if (debug_lvl > 0) {
861 FGLogging log(LogLevel::DEBUG);
862 log << " Ground Trim\n";
863 }
864 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tWdot,tAltAGL ));
865 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tQdot,tTheta ));
866 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tPdot,tPhi ));
867 break;
868 case tPullup:
869 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tNlf,tAlpha ));
870 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tUdot,tThrottle ));
871 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tQdot,tPitchTrim ));
872 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tHmgt,tBeta ));
873 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tVdot,tPhi ));
874 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tPdot,tAileron ));
875 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tRdot,tRudder ));
876 break;
877 case tTurn:
878 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tWdot,tAlpha ));
879 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tUdot,tThrottle ));
880 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tQdot,tPitchTrim ));
881 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tVdot,tBeta ));
882 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tPdot,tAileron ));
883 TrimAxes.push_back(FGTrimAxis(fdmex,&fgic,tRdot,tRudder ));
884 break;
885 case tCustom:
886 case tNone:
887 break;
888 }
889 //FGLogging log(LogLevel::INFO);
890 //log << "TrimAxes.size(): " << TrimAxes.size() << "\n";
891 sub_iterations.resize(TrimAxes.size());
892 successful.resize(TrimAxes.size());
893 solution.resize(TrimAxes.size());
894}
895//YOU WERE WARNED, BUT YOU DID IT ANYWAY.
896}
This class implements a 3 element column vector.
Encapsulates the JSBSim simulation executive.
Definition FGFDMExec.h:185
std::shared_ptr< FGInitialCondition > GetIC(void) const
Returns a pointer to the FGInitialCondition object.
Definition FGFDMExec.h:390
std::shared_ptr< FGFCS > GetFCS(void) const
Returns the FGFCS pointer.
std::shared_ptr< FGPropagate > GetPropagate(void) const
Returns the FGPropagate pointer.
bool Run(void)
This function executes each scheduled model in succession.
std::shared_ptr< FGGroundReactions > GetGroundReactions(void) const
Returns the FGGroundReactions pointer.
std::shared_ptr< FGMassBalance > GetMassBalance(void) const
Returns the FGAircraft pointer.
void SuspendIntegration(void)
Suspends the simulation and sets the delta T to zero.
Definition FGFDMExec.h:556
std::shared_ptr< FGInertial > GetInertial(void) const
Returns the FGInertial pointer.
void Initialize(const FGInitialCondition *FGIC)
Initializes the simulation with initial conditions.
std::shared_ptr< FGAccelerations > GetAccelerations(void) const
Returns the FGAccelerations pointer.
void ResumeIntegration(void)
Resumes the simulation by resetting delta T to the correct value.
Definition FGFDMExec.h:559
void SetPsiRadIC(double psi)
Sets the initial heading angle.
double GetPhiRadIC(void) const
Gets the initial roll angle.
double GetAltitudeASLFtIC(void) const
Gets the initial altitude above sea level.
double GetTargetNlfIC(void) const
Gets the target normal load factor set from IC.
const FGQuaternion & GetOrientation(void) const
Gets the initial orientation.
void SetPRadpsIC(double P)
Sets the initial body axis roll rate.
double GetAltitudeAGLFtIC(void) const
Gets the initial altitude above ground level.
void SetThetaRadIC(double theta)
Sets the initial pitch angle.
double GetVtrueFpsIC(void) const
Gets the initial true velocity.
void SetRRadpsIC(double R)
Sets the initial body axis yaw rate.
void SetQRadpsIC(double Q)
Sets the initial body axis pitch rate.
double GetUBodyFpsIC(void) const
Gets the initial body axis X velocity.
double GetFlightPathAngleRadIC(void) const
Gets the initial flight path angle.
double GetThetaRadIC(void) const
Gets the initial pitch angle.
void SetPhiRadIC(double phi)
Sets the initial roll angle.
void SetAltitudeASLFtIC(double altitudeASL)
Sets the altitude above sea level initial condition in feet.
FGLocation holds an arbitrary location in the Earth centered Earth fixed reference frame (ECEF).
Definition FGLocation.h:152
FGLocation LocalToLocation(const FGColumnVector3 &lvec) const
Conversion from Local frame coordinates to a location in the earth centered and fixed frame.
Definition FGLocation.h:326
Handles matrix math operations.
Definition FGMatrix33.h:70
bool AddState(State state, Control control)
Add a state-control pair to the current configuration.
Definition FGTrim.cpp:139
void ClearStates(void)
Clear all state-control pairs from the current configuration.
Definition FGTrim.cpp:130
bool RemoveState(State state)
Remove a specific state-control pair from the current configuration.
Definition FGTrim.cpp:157
void Report(void)
Print the results of the trim.
Definition FGTrim.cpp:121
void SetMode(TrimMode tm)
Clear all state-control pairs and set a predefined trim mode.
Definition FGTrim.cpp:833
bool DoTrim(void)
Execute the trim.
Definition FGTrim.cpp:195
bool EditState(State state, Control new_control)
Change the control used to zero a state previously configured.
Definition FGTrim.cpp:180
FGTrim(FGFDMExec *FDMExec, TrimMode tm=tGround)
Initializes the trimming class.
Definition FGTrim.cpp:62
void TrimStats()
Iteration statistics.
Definition FGTrim.cpp:99
Main namespace for the JSBSim Flight Dynamics Model.
Definition FGFDMExec.cpp:71
State
Models an aircraft axis for purposes of trimming.
Definition FGTrimAxis.h:82
double DotProduct(const FGColumnVector3 &v1, const FGColumnVector3 &v2)
Dot product of two vectors Compute and return the euclidean dot (or scalar) product of two vectors v1...