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"
57 #pragma warning (disable : 4786 4788)
60 static const char *IdSrc = "$Id$";
61 static const char *IdHdr = ID_TRIM;
63 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
65 FGTrim::FGTrim(FGFDMExec *FDMExec,FGInitialCondition *FGIC, TrimMode tt ) {
69 max_sub_iterations=100;
71 A_Tolerance = Tolerance / 10;
84 cout << " Full Trim" << endl;
85 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha ));
86 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
87 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
88 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tHmgt,tBeta ));
89 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tVdot,tPhi ));
90 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tAileron ));
91 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tRdot,tRudder ));
94 cout << " Longitudinal Trim" << endl;
95 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha ));
96 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle ));
97 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim ));
100 cout << " Ground Trim" << endl;
101 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAltAGL ));
102 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tTheta ));
103 //TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tPhi ));
106 //cout << "TrimAxes.size(): " << TrimAxes.size() << endl;
107 sub_iterations=new float[TrimAxes.size()];
108 successful=new float[TrimAxes.size()];
109 solution=new bool[TrimAxes.size()];
112 if (debug_lvl & 2) cout << "Instantiated: FGTrim" << endl;
115 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
117 FGTrim::~FGTrim(void) {
118 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++) {
119 delete TrimAxes[current_axis];
121 delete[] sub_iterations;
124 if (debug_lvl & 2) cout << "Destroyed: FGTrim" << endl;
127 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
129 void FGTrim::TrimStats() {
132 cout << endl << " Trim Statistics: " << endl;
133 cout << " Total Iterations: " << total_its << endl;
135 cout << " Sub-iterations:" << endl;
136 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++) {
137 run_sum+=TrimAxes[current_axis]->GetRunCount();
138 snprintf(out,80," %5s: %3.0f average: %5.2f successful: %3.0f stability: %5.2f\n",
139 TrimAxes[current_axis]->GetStateName().c_str(),
140 sub_iterations[current_axis],
141 sub_iterations[current_axis]/float(total_its),
142 successful[current_axis],
143 TrimAxes[current_axis]->GetAvgStability() );
146 cout << " Run Count: " << run_sum << endl;
150 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
152 void FGTrim::Report(void) {
153 cout << " Trim Results: " << endl;
154 for(current_axis=0; current_axis<TrimAxes.size(); current_axis++)
155 TrimAxes[current_axis]->AxisReport();
159 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
161 void FGTrim::ReportState(void) {
162 char out[80], flap[10], gear[10];
164 cout << endl << " JSBSim State" << endl;
165 snprintf(out,80," Weight: %7.0f lbs. CG: %5.1f, %5.1f, %5.1f inches\n",
166 fdmex->GetMassBalance()->GetWeight(),
167 fdmex->GetMassBalance()->GetXYZcg(1),
168 fdmex->GetMassBalance()->GetXYZcg(2),
169 fdmex->GetMassBalance()->GetXYZcg(3));
171 if( fdmex->GetFCS()->GetDfPos() <= 0.01)
172 snprintf(flap,10,"Up");
174 snprintf(flap,10,"%2.0f",fdmex->GetFCS()->GetDfPos());
175 if(fdmex->GetGroundReactions()->GetGearUp() == true)
176 snprintf(gear,10,"Up");
178 snprintf(gear,10,"Down");
179 snprintf(out,80, " Flaps: %3s Gear: %4s\n",flap,gear);
181 snprintf(out,80, " Speed: %4.0f KCAS Mach: %5.2f\n",
182 fdmex->GetAuxiliary()->GetVcalibratedKTS(),
183 fdmex->GetState()->GetParameter(FG_MACH) );
185 snprintf(out,80, " Altitude: %7.0f ft. AGL Altitude: %7.0f ft.\n",
186 fdmex->GetPosition()->Geth(),
187 fdmex->GetPosition()->GetDistanceAGL() );
189 snprintf(out,80, " Angle of Attack: %6.2f deg Pitch Angle: %6.2f deg\n",
190 fdmex->GetState()->GetParameter(FG_ALPHA)*RADTODEG,
191 fdmex->GetRotation()->Gettht()*RADTODEG );
193 snprintf(out,80, " Flight Path Angle: %6.2f deg Climb Rate: %5.0f ft/min\n",
194 fdmex->GetPosition()->GetGamma()*RADTODEG,
195 fdmex->GetPosition()->Gethdot()*60 );
197 snprintf(out,80, " Normal Load Factor: %4.2f g's Pitch Rate: %5.2f deg/s\n",
198 fdmex->GetAerodynamics()->GetNlf(),
199 fdmex->GetState()->GetParameter(FG_PITCHRATE)*RADTODEG );
201 snprintf(out,80, " Heading: %3.0f deg true Sideslip: %5.2f deg\n",
202 fdmex->GetRotation()->Getpsi()*RADTODEG,
203 fdmex->GetState()->GetParameter(FG_BETA)*RADTODEG );
205 snprintf(out,80, " Bank Angle: %5.2f deg\n",
206 fdmex->GetRotation()->Getphi()*RADTODEG );
208 snprintf(out,80, " Elevator: %5.2f deg Left Aileron: %5.2f deg Rudder: %5.2f deg\n",
209 fdmex->GetState()->GetParameter(FG_ELEVATOR_POS)*RADTODEG,
210 fdmex->GetState()->GetParameter(FG_AILERON_POS)*RADTODEG,
211 fdmex->GetState()->GetParameter(FG_RUDDER_POS)*RADTODEG );
213 snprintf(out,80, " Throttle: %5.2f%c\n",
214 fdmex->GetFCS()->GetThrottlePos(0),'%' );
217 snprintf(out,80, " Wind Components: %5.2f kts head wind, %5.2f kts cross wind\n",
218 fdmex->GetAuxiliary()->GetHeadWind()*jsbFPSTOKTS,
219 fdmex->GetAuxiliary()->GetCrossWind()*jsbFPSTOKTS );
222 snprintf(out,80, " Ground Speed: %4.0f knots , Ground Track: %3.0f deg true\n",
223 fdmex->GetPosition()->GetVground()*jsbFPSTOKTS,
224 fdmex->GetPosition()->GetGroundTrack()*RADTODEG );
229 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
231 void FGTrim::ClearStates(void) {
235 vector<FGTrimAxis*>::iterator iAxes;
236 iAxes = TrimAxes.begin();
237 while (iAxes != TrimAxes.end()) {
243 cout << "TrimAxes.size(): " << TrimAxes.size() << endl;
246 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
248 bool FGTrim::AddState( State state, Control control ) {
253 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
254 while (iAxes != TrimAxes.end()) {
256 if( ta->GetStateType() == state )
261 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,state,control));
262 delete[] sub_iterations;
265 sub_iterations=new float[TrimAxes.size()];
266 successful=new float[TrimAxes.size()];
267 solution=new bool[TrimAxes.size()];
272 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
274 bool FGTrim::RemoveState( State state ) {
279 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
280 while (iAxes != TrimAxes.end()) {
282 if( ta->GetStateType() == state ) {
284 TrimAxes.erase(iAxes);
291 delete[] sub_iterations;
294 sub_iterations=new float[TrimAxes.size()];
295 successful=new float[TrimAxes.size()];
296 solution=new bool[TrimAxes.size()];
301 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
303 bool FGTrim::EditState( State state, Control new_control ){
308 vector <FGTrimAxis*>::iterator iAxes = TrimAxes.begin();
309 while (iAxes != TrimAxes.end()) {
311 if( ta->GetStateType() == state ) {
312 TrimAxes.insert(iAxes,1,new FGTrimAxis(fdmex,fgic,state,new_control));
314 TrimAxes.erase(iAxes+1);
320 cout << "Exit FGTrim::EditState(...)" << endl;
325 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
327 bool FGTrim::DoTrim(void) {
332 for(i=0;i < fdmex->GetGroundReactions()->GetNumGearUnits();i++){
333 fdmex->GetGroundReactions()->GetGearUnit(i)->SetReport(false);
336 fdmex->GetOutput()->Disable();
338 //clear the sub iterations counts & zero out the controls
339 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
340 //cout << current_axis << " " << TrimAxes[current_axis]->GetStateName()
341 //<< " " << TrimAxes[current_axis]->GetControlName()<< endl;
342 if(TrimAxes[current_axis]->GetStateType() == tQdot) {
344 TrimAxes[current_axis]->initTheta();
346 xlo=TrimAxes[current_axis]->GetControlMin();
347 xhi=TrimAxes[current_axis]->GetControlMax();
348 TrimAxes[current_axis]->SetControl((xlo+xhi)/2);
349 TrimAxes[current_axis]->Run();
350 //TrimAxes[current_axis]->AxisReport();
351 sub_iterations[current_axis]=0;
352 successful[current_axis]=0;
353 solution[current_axis]=false;
357 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
359 if(!solution[current_axis]) {
361 solution[current_axis]=true;
364 } else if(findInterval()) {
367 solution[current_axis]=false;
369 sub_iterations[current_axis]+=Nsub;
371 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
372 //these checks need to be done after all the axes have run
373 if(Debug > 0) TrimAxes[current_axis]->AxisReport();
374 if(TrimAxes[current_axis]->InTolerance()) {
376 successful[current_axis]++;
381 if((axis_count == TrimAxes.size()-1) && (TrimAxes.size() > 1)) {
382 //cout << TrimAxes.size()-1 << " out of " << TrimAxes.size() << "!" << endl;
383 //At this point we can check the input limits of the failed axis
384 //and declare the trim failed if there is no sign change. If there
385 //is, keep going until success or max iteration count
387 //Oh, well: two out of three ain't bad
388 for(current_axis=0;current_axis<TrimAxes.size();current_axis++) {
389 //these checks need to be done after all the axes have run
390 if(!TrimAxes[current_axis]->InTolerance()) {
392 // special case this for now -- if other cases arise proper
393 // support can be added to FGTrimAxis
394 if( (gamma_fallback) &&
395 (TrimAxes[current_axis]->GetStateType() == tUdot) &&
396 (TrimAxes[current_axis]->GetControlType() == tThrottle)) {
397 cout << " Can't trim udot with throttle, trying flight"
398 << " path angle. (" << N << ")" << endl;
399 if(TrimAxes[current_axis]->GetState() > 0)
400 TrimAxes[current_axis]->SetControlToMin();
402 TrimAxes[current_axis]->SetControlToMax();
403 TrimAxes[current_axis]->Run();
404 delete TrimAxes[current_axis];
405 TrimAxes[current_axis]=new FGTrimAxis(fdmex,fgic,tUdot,
408 cout << " Sorry, " << TrimAxes[current_axis]->GetStateName()
409 << " doesn't appear to be trimmable" << endl;
411 trim_failed=true; //force the trim to fail
416 } //all-but-one check
418 if(N > max_iterations)
420 } while((axis_count < TrimAxes.size()) && (!trim_failed));
421 if((!trim_failed) && (axis_count >= TrimAxes.size())) {
423 cout << endl << " Trim successful" << endl;
426 cout << endl << " Trim failed" << endl;
428 for(i=0;i < fdmex->GetGroundReactions()->GetNumGearUnits();i++){
429 fdmex->GetGroundReactions()->GetGearUnit(i)->SetReport(true);
431 fdmex->GetOutput()->Enable();
435 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
437 bool FGTrim::solve(void) {
439 float x1,x2,x3,f1,f2,f3,d,d0;
440 const float relax =0.9;
441 float eps=TrimAxes[current_axis]->GetSolverEps();
447 if( solutionDomain != 0) {
448 /* if(ahi > alo) { */
458 //max_sub_iterations=TrimAxes[current_axis]->GetIterationLimit();
459 while (!TrimAxes[current_axis]->InTolerance() && (fabs(d) > eps)
460 && (Nsub < max_sub_iterations)) {
463 x2=x1-d*d0*f1/(f3-f1);
464 TrimAxes[current_axis]->SetControl(x2);
465 TrimAxes[current_axis]->Run();
466 f2=TrimAxes[current_axis]->GetState();
468 cout << "FGTrim::solve Nsub,x1,x2,x3: " << Nsub << ", " << x1
469 << ", " << x2 << ", " << x3 << endl;
470 cout << " " << f1 << ", " << f2 << ", " << f3 << endl;
476 //cout << "Solution is between x1 and x2" << endl;
478 else if(f2*f3 <= 0.0) {
482 //cout << "Solution is between x2 and x3" << endl;
489 if(Nsub < max_sub_iterations) success=true;
494 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
496 produces an interval (xlo..xhi) on one side or the other of the current
497 control value in which a solution exists. This domain is, hopefully,
498 smaller than xmin..0 or 0..xmax and the solver will require fewer iterations
499 to find the solution. This is, hopefully, more efficient than having the
500 solver start from scratch every time. Maybe it isn't though...
501 This tries to take advantage of the idea that the changes from iteration to
502 iteration will be small after the first one or two top-level iterations.
504 assumes that changing the control will a produce significant change in the
505 accel i.e. checkLimits() has already been called.
507 if a solution is found above the current control, the function returns true
508 and xlo is set to the current control, xhi to the interval max it found, and
509 solutionDomain is set to 1.
510 if the solution lies below the current control, then the function returns
511 true and xlo is set to the interval min it found and xmax to the current
512 control. if no solution is found, then the function returns false.
515 in all cases, alo=accel(xlo) and ahi=accel(xhi) after the function exits.
516 no assumptions about the state of the sim after this function has run
519 bool FGTrim::findInterval(void) {
522 float current_control=TrimAxes[current_axis]->GetControl();
523 float current_accel=TrimAxes[current_axis]->GetState();;
524 float xmin=TrimAxes[current_axis]->GetControlMin();
525 float xmax=TrimAxes[current_axis]->GetControlMax();
526 float lastxlo,lastxhi,lastalo,lastahi;
528 step=0.025*fabs(xmax);
529 xlo=xhi=current_control;
530 alo=ahi=current_accel;
531 lastxlo=xlo;lastxhi=xhi;
532 lastalo=alo;lastahi=ahi;
538 if(xlo < xmin) xlo=xmin;
540 if(xhi > xmax) xhi=xmax;
541 TrimAxes[current_axis]->SetControl(xlo);
542 TrimAxes[current_axis]->Run();
543 alo=TrimAxes[current_axis]->GetState();
544 TrimAxes[current_axis]->SetControl(xhi);
545 TrimAxes[current_axis]->Run();
546 ahi=TrimAxes[current_axis]->GetState();
547 if(fabs(ahi-alo) <= TrimAxes[current_axis]->GetTolerance()) continue;
548 if(alo*ahi <=0) { //found interval with root
550 if(alo*current_accel <= 0) { //narrow interval down a bit
554 //xhi=current_control;
560 //xlo=current_control;
564 lastxlo=xlo;lastxhi=xhi;
565 lastalo=alo;lastahi=ahi;
566 if( !found && xlo==xmin && xhi==xmax ) continue;
568 cout << "FGTrim::findInterval: Nsub=" << Nsub << " Lo= " << xlo
569 << " Hi= " << xhi << " alo*ahi: " << alo*ahi << endl;
570 } while(!found && (Nsub <= max_sub_iterations) );
574 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
575 //checks to see which side of the current control value the solution is on
576 //and sets solutionDomain accordingly:
577 // 1 if solution is between the current and max
578 // -1 if solution is between the min and current
579 // 0 if there is no solution
581 //if changing the control produces no significant change in the accel then
582 //solutionDomain is set to zero and the function returns false
583 //if a solution is found, then xlo and xhi are set so that they bracket
584 //the solution, alo is set to accel(xlo), and ahi is set to accel(xhi)
585 //if there is no change or no solution then xlo=xmin, alo=accel(xmin) and
586 //xhi=xmax and ahi=accel(xmax)
587 //in all cases the sim is left such that the control=xmax and accel=ahi
589 bool FGTrim::checkLimits(void) {
591 float current_control=TrimAxes[current_axis]->GetControl();
592 float current_accel=TrimAxes[current_axis]->GetState();
593 xlo=TrimAxes[current_axis]->GetControlMin();
594 xhi=TrimAxes[current_axis]->GetControlMax();
596 TrimAxes[current_axis]->SetControl(xlo);
597 TrimAxes[current_axis]->Run();
598 alo=TrimAxes[current_axis]->GetState();
599 TrimAxes[current_axis]->SetControl(xhi);
600 TrimAxes[current_axis]->Run();
601 ahi=TrimAxes[current_axis]->GetState();
603 cout << "checkLimits() xlo,xhi,alo,ahi: " << xlo << ", " << xhi << ", "
604 << alo << ", " << ahi << endl;
606 solutionExists=false;
607 if(fabs(ahi-alo) > TrimAxes[current_axis]->GetTolerance()) {
608 if(alo*current_accel <= 0) {
613 } else if(current_accel*ahi < 0){
620 TrimAxes[current_axis]->SetControl(current_control);
621 TrimAxes[current_axis]->Run();
622 return solutionExists;
625 //YOU WERE WARNED, BUT YOU DID IT ANYWAY.