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)
63 static const char *IdSrc = "$Id$";
64 static const char *IdHdr = ID_TRIM;
66 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
68 FGTrim::FGTrim(FGFDMExec *FDMExec,TrimMode tt) {
72 max_sub_iterations=100;
74 A_Tolerance = Tolerance / 10;
88 if (debug_lvl & 2) cout << "Instantiated: FGTrim" << endl;
91 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93 FGTrim::~FGTrim(void) {
94 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++) {
95 delete TrimAxes[current_axis];
97 delete[] sub_iterations;
100 if (debug_lvl & 2) cout << "Destroyed: FGTrim" << endl;
103 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
105 void FGTrim::TrimStats() {
108 cout << endl << " Trim Statistics: " << endl;
109 cout << " Total Iterations: " << total_its << endl;
111 cout << " Sub-iterations:" << endl;
112 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++) {
113 run_sum+=TrimAxes[current_axis]->GetRunCount();
114 snprintf(out,80," %5s: %3.0f average: %5.2f successful: %3.0f stability: %5.2f\n",
115 TrimAxes[current_axis]->GetStateName().c_str(),
116 sub_iterations[current_axis],
117 sub_iterations[current_axis]/double(total_its),
118 successful[current_axis],
119 TrimAxes[current_axis]->GetAvgStability() );
122 cout << " Run Count: " << run_sum << endl;
126 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
128 void FGTrim::Report(void) {
129 cout << " Trim Results: " << endl;
130 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++)
131 TrimAxes[current_axis]->AxisReport();
135 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
137 void FGTrim::ClearStates(void) {
141 vector<FGTrimAxis*>::iterator iAxes;
142 iAxes = TrimAxes.begin();
143 while (iAxes != TrimAxes.end()) {
149 //cout << "TrimAxes.size(): " << TrimAxes.size() << endl;
152 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
154 bool FGTrim::AddState( State state, Control control ) {
159 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
160 while (iAxes != TrimAxes.end()) {
162 if( ta->GetStateType() == state )
167 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,state,control));
168 delete[] sub_iterations;
171 sub_iterations=new double[TrimAxes.size()];
172 successful=new double[TrimAxes.size()];
173 solution=new bool[TrimAxes.size()];
178 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
180 bool FGTrim::RemoveState( State state ) {
185 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
186 while (iAxes != TrimAxes.end()) {
188 if( ta->GetStateType() == state ) {
190 TrimAxes.erase(iAxes);
197 delete[] sub_iterations;
200 sub_iterations=new double[TrimAxes.size()];
201 successful=new double[TrimAxes.size()];
202 solution=new bool[TrimAxes.size()];
207 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
209 bool FGTrim::EditState( State state, Control new_control ){
214 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
215 while (iAxes != TrimAxes.end()) {
217 if( ta->GetStateType() == state ) {
218 TrimAxes.insert(iAxes,1,new FGTrimAxis(fdmex,fgic,state,new_control));
220 TrimAxes.erase(iAxes+1);
230 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
232 bool FGTrim::DoTrim(void) {
237 for(i=0;i < fdmex->GetGroundReactions()->GetNumGearUnits();i++){
238 fdmex->GetGroundReactions()->GetGearUnit(i)->SetReport(false);
241 fdmex->GetOutput()->Disable();
243 //clear the sub iterations counts & zero out the controls
244 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
245 //cout << current_axis << " " << TrimAxes[current_axis]->GetStateName()
246 //<< " " << TrimAxes[current_axis]->GetControlName()<< endl;
247 if(TrimAxes[current_axis]->GetStateType() == tQdot) {
248 if(mode == tGround) {
249 TrimAxes[current_axis]->initTheta();
252 xlo=TrimAxes[current_axis]->GetControlMin();
253 xhi=TrimAxes[current_axis]->GetControlMax();
254 TrimAxes[current_axis]->SetControl((xlo+xhi)/2);
255 TrimAxes[current_axis]->Run();
256 //TrimAxes[current_axis]->AxisReport();
257 sub_iterations[current_axis]=0;
258 successful[current_axis]=0;
259 solution[current_axis]=false;
263 if(mode == tPullup ) {
264 cout << "Setting pitch rate and nlf... " << endl;
266 cout << "pitch rate done ... " << endl;
267 TrimAxes[0]->SetStateTarget(targetNlf);
268 cout << "nlf done" << endl;
269 } else if (mode == tTurn) {
271 //TrimAxes[0]->SetStateTarget(targetNlf);
276 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
280 if(!solution[current_axis]) {
282 solution[current_axis]=true;
285 } else if(findInterval()) {
288 solution[current_axis]=false;
290 sub_iterations[current_axis]+=Nsub;
292 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
293 //these checks need to be done after all the axes have run
294 if(Debug > 0) TrimAxes[current_axis]->AxisReport();
295 if(TrimAxes[current_axis]->InTolerance()) {
297 successful[current_axis]++;
302 if((axis_count == TrimAxes.size()-1) && (TrimAxes.size() > 1)) {
303 //cout << TrimAxes.size()-1 << " out of " << TrimAxes.size() << "!" << endl;
304 //At this point we can check the input limits of the failed axis
305 //and declare the trim failed if there is no sign change. If there
306 //is, keep going until success or max iteration count
308 //Oh, well: two out of three ain't bad
309 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
310 //these checks need to be done after all the axes have run
311 if(!TrimAxes[current_axis]->InTolerance()) {
313 // special case this for now -- if other cases arise proper
314 // support can be added to FGTrimAxis
315 if( (gamma_fallback) &&
316 (TrimAxes[current_axis]->GetStateType() == tUdot) &&
317 (TrimAxes[current_axis]->GetControlType() == tThrottle)) {
318 cout << " Can't trim udot with throttle, trying flight"
319 << " path angle. (" << N << ")" << endl;
320 if(TrimAxes[current_axis]->GetState() > 0)
321 TrimAxes[current_axis]->SetControlToMin();
323 TrimAxes[current_axis]->SetControlToMax();
324 TrimAxes[current_axis]->Run();
325 delete TrimAxes[current_axis];
326 TrimAxes[current_axis]=new FGTrimAxis(fdmex,fgic,tUdot,
329 cout << " Sorry, " << TrimAxes[current_axis]->GetStateName()
330 << " doesn't appear to be trimmable" << endl;
332 trim_failed=true; //force the trim to fail
337 } //all-but-one check
339 if(N > max_iterations)
341 } while((axis_count < TrimAxes.size()) && (!trim_failed));
342 if((!trim_failed) && (axis_count >= TrimAxes.size())) {
345 cout << endl << " Trim successful" << endl;
349 cout << endl << " Trim failed" << endl;
351 for(i=0;i < fdmex->GetGroundReactions()->GetNumGearUnits();i++){
352 fdmex->GetGroundReactions()->GetGearUnit(i)->SetReport(true);
354 fdmex->GetOutput()->Enable();
358 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
360 bool FGTrim::solve(void) {
362 double x1,x2,x3,f1,f2,f3,d,d0;
363 const double relax =0.9;
364 double eps=TrimAxes[current_axis]->GetSolverEps();
370 if( solutionDomain != 0) {
371 /* if(ahi > alo) { */
380 //max_sub_iterations=TrimAxes[current_axis]->GetIterationLimit();
381 while ( (TrimAxes[current_axis]->InTolerance() == false )
382 && (fabs(d) > eps) && (Nsub < max_sub_iterations)) {
385 x2=x1-d*d0*f1/(f3-f1);
386 TrimAxes[current_axis]->SetControl(x2);
387 TrimAxes[current_axis]->Run();
388 f2=TrimAxes[current_axis]->GetState();
390 cout << "FGTrim::solve Nsub,x1,x2,x3: " << Nsub << ", " << x1
391 << ", " << x2 << ", " << x3 << endl;
392 cout << " " << f1 << ", " << f2 << ", " << f3 << endl;
398 //cout << "Solution is between x1 and x2" << endl;
400 else if(f2*f3 <= 0.0) {
404 //cout << "Solution is between x2 and x3" << endl;
411 if(Nsub < max_sub_iterations) success=true;
416 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
418 produces an interval (xlo..xhi) on one side or the other of the current
419 control value in which a solution exists. This domain is, hopefully,
420 smaller than xmin..0 or 0..xmax and the solver will require fewer iterations
421 to find the solution. This is, hopefully, more efficient than having the
422 solver start from scratch every time. Maybe it isn't though...
423 This tries to take advantage of the idea that the changes from iteration to
424 iteration will be small after the first one or two top-level iterations.
426 assumes that changing the control will a produce significant change in the
427 accel i.e. checkLimits() has already been called.
429 if a solution is found above the current control, the function returns true
430 and xlo is set to the current control, xhi to the interval max it found, and
431 solutionDomain is set to 1.
432 if the solution lies below the current control, then the function returns
433 true and xlo is set to the interval min it found and xmax to the current
434 control. if no solution is found, then the function returns false.
437 in all cases, alo=accel(xlo) and ahi=accel(xhi) after the function exits.
438 no assumptions about the state of the sim after this function has run
441 bool FGTrim::findInterval(void) {
444 double current_control=TrimAxes[current_axis]->GetControl();
445 double current_accel=TrimAxes[current_axis]->GetState();;
446 double xmin=TrimAxes[current_axis]->GetControlMin();
447 double xmax=TrimAxes[current_axis]->GetControlMax();
448 double lastxlo,lastxhi,lastalo,lastahi;
450 step=0.025*fabs(xmax);
451 xlo=xhi=current_control;
452 alo=ahi=current_accel;
453 lastxlo=xlo;lastxhi=xhi;
454 lastalo=alo;lastahi=ahi;
460 if(xlo < xmin) xlo=xmin;
462 if(xhi > xmax) xhi=xmax;
463 TrimAxes[current_axis]->SetControl(xlo);
464 TrimAxes[current_axis]->Run();
465 alo=TrimAxes[current_axis]->GetState();
466 TrimAxes[current_axis]->SetControl(xhi);
467 TrimAxes[current_axis]->Run();
468 ahi=TrimAxes[current_axis]->GetState();
469 if(fabs(ahi-alo) <= TrimAxes[current_axis]->GetTolerance()) continue;
470 if(alo*ahi <=0) { //found interval with root
472 if(alo*current_accel <= 0) { //narrow interval down a bit
476 //xhi=current_control;
482 //xlo=current_control;
486 lastxlo=xlo;lastxhi=xhi;
487 lastalo=alo;lastahi=ahi;
488 if( !found && xlo==xmin && xhi==xmax ) continue;
490 cout << "FGTrim::findInterval: Nsub=" << Nsub << " Lo= " << xlo
491 << " Hi= " << xhi << " alo*ahi: " << alo*ahi << endl;
492 } while(!found && (Nsub <= max_sub_iterations) );
496 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
497 //checks to see which side of the current control value the solution is on
498 //and sets solutionDomain accordingly:
499 // 1 if solution is between the current and max
500 // -1 if solution is between the min and current
501 // 0 if there is no solution
503 //if changing the control produces no significant change in the accel then
504 //solutionDomain is set to zero and the function returns false
505 //if a solution is found, then xlo and xhi are set so that they bracket
506 //the solution, alo is set to accel(xlo), and ahi is set to accel(xhi)
507 //if there is no change or no solution then xlo=xmin, alo=accel(xmin) and
508 //xhi=xmax and ahi=accel(xmax)
509 //in all cases the sim is left such that the control=xmax and accel=ahi
511 bool FGTrim::checkLimits(void) {
513 double current_control=TrimAxes[current_axis]->GetControl();
514 double current_accel=TrimAxes[current_axis]->GetState();
515 xlo=TrimAxes[current_axis]->GetControlMin();
516 xhi=TrimAxes[current_axis]->GetControlMax();
518 TrimAxes[current_axis]->SetControl(xlo);
519 TrimAxes[current_axis]->Run();
520 alo=TrimAxes[current_axis]->GetState();
521 TrimAxes[current_axis]->SetControl(xhi);
522 TrimAxes[current_axis]->Run();
523 ahi=TrimAxes[current_axis]->GetState();
525 cout << "checkLimits() xlo,xhi,alo,ahi: " << xlo << ", " << xhi << ", "
526 << alo << ", " << ahi << endl;
528 solutionExists=false;
529 if(fabs(ahi-alo) > TrimAxes[current_axis]->GetTolerance()) {
530 if(alo*current_accel <= 0) {
535 } else if(current_accel*ahi < 0){
542 TrimAxes[current_axis]->SetControl(current_control);
543 TrimAxes[current_axis]->Run();
544 return solutionExists;
547 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
549 void FGTrim::setupPullup() {
551 FGColumnVector3 vPQR;
552 g=fdmex->GetInertial()->gravity();
553 cgamma=cos(fgic->GetFlightPathAngleRadIC());
554 cout << "setPitchRateInPullup(): " << g << ", " << cgamma << ", "
555 << fgic->GetVtrueFpsIC() << endl;
556 q=g*(targetNlf-cgamma)/fgic->GetVtrueFpsIC();
557 cout << targetNlf << ", " << q << endl;
558 fdmex->GetRotation()->SetPQR(0,q,0);
559 cout << "setPitchRateInPullup() complete" << endl;
563 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
565 void FGTrim::setupTurn(void){
567 phi = fgic->GetRollAngleRadIC();
568 if( fabs(phi) > 0.001 && fabs(phi) < 1.56 ) {
569 targetNlf = 1 / cos(phi);
570 g = fdmex->GetInertial()->gravity();
571 psidot = g*tan(phi) / fgic->GetUBodyFpsIC();
572 cout << targetNlf << ", " << psidot << endl;
577 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
579 void FGTrim::updateRates(void){
580 if( mode == tTurn ) {
581 double phi = fgic->GetRollAngleRadIC();
582 double g = fdmex->GetInertial()->gravity();
584 if(fabs(phi) > 0.001 && fabs(phi) < 1.56 ) {
585 theta=fgic->GetPitchAngleRadIC();
586 phi=fgic->GetRollAngleRadIC();
587 psidot = g*tan(phi) / fgic->GetUBodyFpsIC();
588 p=-psidot*sin(theta);
589 q=psidot*cos(theta)*sin(phi);
590 r=psidot*cos(theta)*cos(phi);
594 fdmex->GetRotation()->SetPQR(p,q,r);
595 } else if( mode == tPullup && fabs(targetNlf-1) > 0.01) {
597 FGColumnVector3 vPQR;
598 g=fdmex->GetInertial()->gravity();
599 cgamma=cos(fgic->GetFlightPathAngleRadIC());
600 q=g*(targetNlf-cgamma)/fgic->GetVtrueFpsIC();
601 fdmex->GetRotation()->SetPQR(0,q,0);
605 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
607 void FGTrim::setDebug(void) {
608 if(debug_axis == tAll ||
609 TrimAxes[current_axis]->GetStateType() == debug_axis ) {
618 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
620 void FGTrim::SetMode(TrimMode tt) {
626 cout << " Full Trim" << endl;
627 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha ));
628 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
629 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
630 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tHmgt,tBeta ));
631 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tVdot,tPhi ));
632 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tAileron ));
633 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tRdot,tRudder ));
637 cout << " Longitudinal Trim" << endl;
638 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha ));
639 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
640 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
644 cout << " Ground Trim" << endl;
645 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAltAGL ));
646 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tTheta ));
647 //TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tPhi ));
650 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tNlf,tAlpha ));
651 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
652 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
653 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tHmgt,tBeta ));
654 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tVdot,tPhi ));
655 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tAileron ));
656 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tRdot,tRudder ));
659 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha ));
660 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
661 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
662 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tVdot,tBeta ));
663 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tAileron ));
664 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tRdot,tRudder ));
670 //cout << "TrimAxes.size(): " << TrimAxes.size() << endl;
671 sub_iterations=new double[TrimAxes.size()];
672 successful=new double[TrimAxes.size()];
673 solution=new bool[TrimAxes.size()];
676 //YOU WERE WARNED, BUT YOU DID IT ANYWAY.