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