1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
7 --------- Copyright (C) 1999 Anthony K. Peden (apeden@earthlink.net) ---------
9 This program is free software; you can redistribute it and/or modify it under
10 the terms of the GNU General Public License as published by the Free Software
11 Foundation; either version 2 of the License, or (at your option) any later
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 General Public License for more
19 You should have received a copy of the GNU 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.
23 Further information about the GNU General Public License can also be found on
24 the world wide web at http://www.gnu.org.
28 --------------------------------------------------------------------------------
32 FUNCTIONAL DESCRIPTION
33 --------------------------------------------------------------------------------
35 This class takes the given set of IC's and finds the angle of attack, elevator,
36 and throttle setting required to fly steady level. This is currently for in-air
37 conditions only. It is implemented using an iterative, one-axis-at-a-time
40 // !!!!!!! BEWARE ALL YE WHO ENTER HERE !!!!!!!
43 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
45 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
49 #include "FGFDMExec.h"
50 #include "FGAtmosphere.h"
51 #include "FGInitialCondition.h"
53 #include "FGAircraft.h"
54 #include "FGMassBalance.h"
55 #include "FGAerodynamics.h"
56 #include "FGColumnVector3.h"
58 #pragma warning (disable : 4786 4788)
61 static const char *IdSrc = "$Id$";
62 static const char *IdHdr = ID_TRIM;
64 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
66 FGTrim::FGTrim(FGFDMExec *FDMExec,FGInitialCondition *FGIC, TrimMode tt ) {
70 max_sub_iterations=100;
72 A_Tolerance = Tolerance / 10;
87 cout << " Full Trim" << endl;
88 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha ));
89 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
90 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
91 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tHmgt,tBeta ));
92 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tVdot,tPhi ));
93 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tAileron ));
94 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tRdot,tRudder ));
97 cout << " Longitudinal Trim" << endl;
98 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha ));
99 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
100 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
103 cout << " Ground Trim" << endl;
104 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAltAGL ));
105 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tTheta ));
106 //TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tPhi ));
109 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tNlf,tAlpha ));
110 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
111 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
112 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tHmgt,tBeta ));
113 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tVdot,tPhi ));
114 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tAileron ));
115 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tRdot,tRudder ));
118 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha ));
119 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
120 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
121 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tVdot,tBeta ));
122 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tAileron ));
123 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tRdot,tRudder ));
129 //cout << "TrimAxes.size(): " << TrimAxes.size() << endl;
130 sub_iterations=new double[TrimAxes.size()];
131 successful=new double[TrimAxes.size()];
132 solution=new bool[TrimAxes.size()];
135 if (debug_lvl & 2) cout << "Instantiated: FGTrim" << endl;
138 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
140 FGTrim::~FGTrim(void) {
141 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++) {
142 delete TrimAxes[current_axis];
144 delete[] sub_iterations;
147 if (debug_lvl & 2) cout << "Destroyed: FGTrim" << endl;
150 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
152 void FGTrim::TrimStats() {
155 cout << endl << " Trim Statistics: " << endl;
156 cout << " Total Iterations: " << total_its << endl;
158 cout << " Sub-iterations:" << endl;
159 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++) {
160 run_sum+=TrimAxes[current_axis]->GetRunCount();
161 snprintf(out,80," %5s: %3.0f average: %5.2f successful: %3.0f stability: %5.2f\n",
162 TrimAxes[current_axis]->GetStateName().c_str(),
163 sub_iterations[current_axis],
164 sub_iterations[current_axis]/double(total_its),
165 successful[current_axis],
166 TrimAxes[current_axis]->GetAvgStability() );
169 cout << " Run Count: " << run_sum << endl;
173 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
175 void FGTrim::Report(void) {
176 cout << " Trim Results: " << endl;
177 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++)
178 TrimAxes[current_axis]->AxisReport();
182 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
184 void FGTrim::ClearStates(void) {
188 vector<FGTrimAxis*>::iterator iAxes;
189 iAxes = TrimAxes.begin();
190 while (iAxes != TrimAxes.end()) {
196 cout << "TrimAxes.size(): " << TrimAxes.size() << endl;
199 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
201 bool FGTrim::AddState( State state, Control control ) {
206 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
207 while (iAxes != TrimAxes.end()) {
209 if( ta->GetStateType() == state )
214 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,state,control));
215 delete[] sub_iterations;
218 sub_iterations=new double[TrimAxes.size()];
219 successful=new double[TrimAxes.size()];
220 solution=new bool[TrimAxes.size()];
225 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
227 bool FGTrim::RemoveState( State state ) {
232 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
233 while (iAxes != TrimAxes.end()) {
235 if( ta->GetStateType() == state ) {
237 TrimAxes.erase(iAxes);
244 delete[] sub_iterations;
247 sub_iterations=new double[TrimAxes.size()];
248 successful=new double[TrimAxes.size()];
249 solution=new bool[TrimAxes.size()];
254 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
256 bool FGTrim::EditState( State state, Control new_control ){
261 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
262 while (iAxes != TrimAxes.end()) {
264 if( ta->GetStateType() == state ) {
265 TrimAxes.insert(iAxes,1,new FGTrimAxis(fdmex,fgic,state,new_control));
267 TrimAxes.erase(iAxes+1);
277 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
279 bool FGTrim::DoTrim(void) {
284 for(i=0;i < fdmex->GetGroundReactions()->GetNumGearUnits();i++){
285 fdmex->GetGroundReactions()->GetGearUnit(i)->SetReport(false);
288 fdmex->GetOutput()->Disable();
290 //clear the sub iterations counts & zero out the controls
291 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
292 //cout << current_axis << " " << TrimAxes[current_axis]->GetStateName()
293 //<< " " << TrimAxes[current_axis]->GetControlName()<< endl;
294 if(TrimAxes[current_axis]->GetStateType() == tQdot) {
296 TrimAxes[current_axis]->initTheta();
298 xlo=TrimAxes[current_axis]->GetControlMin();
299 xhi=TrimAxes[current_axis]->GetControlMax();
300 TrimAxes[current_axis]->SetControl((xlo+xhi)/2);
301 TrimAxes[current_axis]->Run();
302 //TrimAxes[current_axis]->AxisReport();
303 sub_iterations[current_axis]=0;
304 successful[current_axis]=0;
305 solution[current_axis]=false;
309 if(mode == tPullup ) {
310 cout << "Setting pitch rate and nlf... " << endl;
312 cout << "pitch rate done ... " << endl;
313 TrimAxes[0]->SetStateTarget(targetNlf);
314 cout << "nlf done" << endl;
315 } else if (mode == tTurn) {
317 //TrimAxes[0]->SetStateTarget(targetNlf);
322 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
326 if(!solution[current_axis]) {
328 solution[current_axis]=true;
331 } else if(findInterval()) {
334 solution[current_axis]=false;
336 sub_iterations[current_axis]+=Nsub;
338 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
339 //these checks need to be done after all the axes have run
340 if(Debug > 0) TrimAxes[current_axis]->AxisReport();
341 if(TrimAxes[current_axis]->InTolerance()) {
343 successful[current_axis]++;
348 if((axis_count == TrimAxes.size()-1) && (TrimAxes.size() > 1)) {
349 //cout << TrimAxes.size()-1 << " out of " << TrimAxes.size() << "!" << endl;
350 //At this point we can check the input limits of the failed axis
351 //and declare the trim failed if there is no sign change. If there
352 //is, keep going until success or max iteration count
354 //Oh, well: two out of three ain't bad
355 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
356 //these checks need to be done after all the axes have run
357 if(!TrimAxes[current_axis]->InTolerance()) {
359 // special case this for now -- if other cases arise proper
360 // support can be added to FGTrimAxis
361 if( (gamma_fallback) &&
362 (TrimAxes[current_axis]->GetStateType() == tUdot) &&
363 (TrimAxes[current_axis]->GetControlType() == tThrottle)) {
364 cout << " Can't trim udot with throttle, trying flight"
365 << " path angle. (" << N << ")" << endl;
366 if(TrimAxes[current_axis]->GetState() > 0)
367 TrimAxes[current_axis]->SetControlToMin();
369 TrimAxes[current_axis]->SetControlToMax();
370 TrimAxes[current_axis]->Run();
371 delete TrimAxes[current_axis];
372 TrimAxes[current_axis]=new FGTrimAxis(fdmex,fgic,tUdot,
375 cout << " Sorry, " << TrimAxes[current_axis]->GetStateName()
376 << " doesn't appear to be trimmable" << endl;
378 trim_failed=true; //force the trim to fail
383 } //all-but-one check
385 if(N > max_iterations)
387 } while((axis_count < TrimAxes.size()) && (!trim_failed));
388 if((!trim_failed) && (axis_count >= TrimAxes.size())) {
390 cout << endl << " Trim successful" << endl;
393 cout << endl << " Trim failed" << endl;
395 for(i=0;i < fdmex->GetGroundReactions()->GetNumGearUnits();i++){
396 fdmex->GetGroundReactions()->GetGearUnit(i)->SetReport(true);
398 fdmex->GetOutput()->Enable();
402 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
404 bool FGTrim::solve(void) {
406 double x1,x2,x3,f1,f2,f3,d,d0;
407 const double relax =0.9;
408 double eps=TrimAxes[current_axis]->GetSolverEps();
414 if( solutionDomain != 0) {
415 /* if(ahi > alo) { */
424 //max_sub_iterations=TrimAxes[current_axis]->GetIterationLimit();
425 while ( (TrimAxes[current_axis]->InTolerance() == false )
426 && (fabs(d) > eps) && (Nsub < max_sub_iterations)) {
429 x2=x1-d*d0*f1/(f3-f1);
430 TrimAxes[current_axis]->SetControl(x2);
431 TrimAxes[current_axis]->Run();
432 f2=TrimAxes[current_axis]->GetState();
434 cout << "FGTrim::solve Nsub,x1,x2,x3: " << Nsub << ", " << x1
435 << ", " << x2 << ", " << x3 << endl;
436 cout << " " << f1 << ", " << f2 << ", " << f3 << endl;
442 //cout << "Solution is between x1 and x2" << endl;
444 else if(f2*f3 <= 0.0) {
448 //cout << "Solution is between x2 and x3" << endl;
455 if(Nsub < max_sub_iterations) success=true;
460 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
462 produces an interval (xlo..xhi) on one side or the other of the current
463 control value in which a solution exists. This domain is, hopefully,
464 smaller than xmin..0 or 0..xmax and the solver will require fewer iterations
465 to find the solution. This is, hopefully, more efficient than having the
466 solver start from scratch every time. Maybe it isn't though...
467 This tries to take advantage of the idea that the changes from iteration to
468 iteration will be small after the first one or two top-level iterations.
470 assumes that changing the control will a produce significant change in the
471 accel i.e. checkLimits() has already been called.
473 if a solution is found above the current control, the function returns true
474 and xlo is set to the current control, xhi to the interval max it found, and
475 solutionDomain is set to 1.
476 if the solution lies below the current control, then the function returns
477 true and xlo is set to the interval min it found and xmax to the current
478 control. if no solution is found, then the function returns false.
481 in all cases, alo=accel(xlo) and ahi=accel(xhi) after the function exits.
482 no assumptions about the state of the sim after this function has run
485 bool FGTrim::findInterval(void) {
488 double current_control=TrimAxes[current_axis]->GetControl();
489 double current_accel=TrimAxes[current_axis]->GetState();;
490 double xmin=TrimAxes[current_axis]->GetControlMin();
491 double xmax=TrimAxes[current_axis]->GetControlMax();
492 double lastxlo,lastxhi,lastalo,lastahi;
494 step=0.025*fabs(xmax);
495 xlo=xhi=current_control;
496 alo=ahi=current_accel;
497 lastxlo=xlo;lastxhi=xhi;
498 lastalo=alo;lastahi=ahi;
504 if(xlo < xmin) xlo=xmin;
506 if(xhi > xmax) xhi=xmax;
507 TrimAxes[current_axis]->SetControl(xlo);
508 TrimAxes[current_axis]->Run();
509 alo=TrimAxes[current_axis]->GetState();
510 TrimAxes[current_axis]->SetControl(xhi);
511 TrimAxes[current_axis]->Run();
512 ahi=TrimAxes[current_axis]->GetState();
513 if(fabs(ahi-alo) <= TrimAxes[current_axis]->GetTolerance()) continue;
514 if(alo*ahi <=0) { //found interval with root
516 if(alo*current_accel <= 0) { //narrow interval down a bit
520 //xhi=current_control;
526 //xlo=current_control;
530 lastxlo=xlo;lastxhi=xhi;
531 lastalo=alo;lastahi=ahi;
532 if( !found && xlo==xmin && xhi==xmax ) continue;
534 cout << "FGTrim::findInterval: Nsub=" << Nsub << " Lo= " << xlo
535 << " Hi= " << xhi << " alo*ahi: " << alo*ahi << endl;
536 } while(!found && (Nsub <= max_sub_iterations) );
540 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
541 //checks to see which side of the current control value the solution is on
542 //and sets solutionDomain accordingly:
543 // 1 if solution is between the current and max
544 // -1 if solution is between the min and current
545 // 0 if there is no solution
547 //if changing the control produces no significant change in the accel then
548 //solutionDomain is set to zero and the function returns false
549 //if a solution is found, then xlo and xhi are set so that they bracket
550 //the solution, alo is set to accel(xlo), and ahi is set to accel(xhi)
551 //if there is no change or no solution then xlo=xmin, alo=accel(xmin) and
552 //xhi=xmax and ahi=accel(xmax)
553 //in all cases the sim is left such that the control=xmax and accel=ahi
555 bool FGTrim::checkLimits(void) {
557 double current_control=TrimAxes[current_axis]->GetControl();
558 double current_accel=TrimAxes[current_axis]->GetState();
559 xlo=TrimAxes[current_axis]->GetControlMin();
560 xhi=TrimAxes[current_axis]->GetControlMax();
562 TrimAxes[current_axis]->SetControl(xlo);
563 TrimAxes[current_axis]->Run();
564 alo=TrimAxes[current_axis]->GetState();
565 TrimAxes[current_axis]->SetControl(xhi);
566 TrimAxes[current_axis]->Run();
567 ahi=TrimAxes[current_axis]->GetState();
569 cout << "checkLimits() xlo,xhi,alo,ahi: " << xlo << ", " << xhi << ", "
570 << alo << ", " << ahi << endl;
572 solutionExists=false;
573 if(fabs(ahi-alo) > TrimAxes[current_axis]->GetTolerance()) {
574 if(alo*current_accel <= 0) {
579 } else if(current_accel*ahi < 0){
586 TrimAxes[current_axis]->SetControl(current_control);
587 TrimAxes[current_axis]->Run();
588 return solutionExists;
591 void FGTrim::setupPullup() {
593 FGColumnVector3 vPQR;
594 g=fdmex->GetInertial()->gravity();
595 cgamma=cos(fgic->GetFlightPathAngleRadIC());
596 cout << "setPitchRateInPullup(): " << g << ", " << cgamma << ", "
597 << fgic->GetVtrueFpsIC() << endl;
598 q=g*(targetNlf-cgamma)/fgic->GetVtrueFpsIC();
599 cout << targetNlf << ", " << q << endl;
600 fdmex->GetRotation()->SetPQR(0,q,0);
601 cout << "setPitchRateInPullup() complete" << endl;
605 void FGTrim::setupTurn(void){
607 phi = fgic->GetRollAngleRadIC();
608 if( fabs(phi) > 0.001 && fabs(phi) < 1.56 ) {
609 targetNlf = 1 / cos(phi);
610 g = fdmex->GetInertial()->gravity();
611 psidot = g*tan(phi) / fgic->GetUBodyFpsIC();
612 cout << targetNlf << ", " << psidot << endl;
617 void FGTrim::updateRates(void){
618 if( mode == tTurn ) {
619 double phi = fgic->GetRollAngleRadIC();
620 double g = fdmex->GetInertial()->gravity();
622 if(fabs(phi) > 0.001 && fabs(phi) < 1.56 ) {
623 theta=fgic->GetPitchAngleRadIC();
624 phi=fgic->GetRollAngleRadIC();
625 psidot = g*tan(phi) / fgic->GetUBodyFpsIC();
626 p=-psidot*sin(theta);
627 q=psidot*cos(theta)*sin(phi);
628 r=psidot*cos(theta)*cos(phi);
632 fdmex->GetRotation()->SetPQR(p,q,r);
633 } else if( mode == tPullup && fabs(targetNlf-1) > 0.01) {
635 FGColumnVector3 vPQR;
636 g=fdmex->GetInertial()->gravity();
637 cgamma=cos(fgic->GetFlightPathAngleRadIC());
638 q=g*(targetNlf-cgamma)/fgic->GetVtrueFpsIC();
639 fdmex->GetRotation()->SetPQR(0,q,0);
643 void FGTrim::setDebug(void) {
644 if(debug_axis == tAll ||
645 TrimAxes[current_axis]->GetStateType() == debug_axis ) {
654 //YOU WERE WARNED, BUT YOU DID IT ANYWAY.