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