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"
55 static const char *IdSrc = "$Header$";
56 static const char *IdHdr = ID_TRIM;
58 /*******************************************************************************/
60 FGTrim::FGTrim(FGFDMExec *FDMExec,FGInitialCondition *FGIC, TrimMode tt ) {
64 max_sub_iterations=100;
66 A_Tolerance = Tolerance / 10;
79 cout << " Full 6-DOF Trim" << endl;
80 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha,Tolerance));
81 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle,Tolerance));
82 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim,A_Tolerance));
83 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tVdot,tPhi,Tolerance));
84 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tAileron,A_Tolerance));
85 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tRdot,tRudder,A_Tolerance));
88 cout << " Longitudinal Trim" << endl;
89 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAlpha,Tolerance));
90 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tUdot,tThrottle,Tolerance));
91 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tPitchTrim,A_Tolerance));
94 cout << " Ground Trim" << endl;
95 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tWdot,tAltAGL,Tolerance));
96 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tQdot,tTheta,A_Tolerance));
97 TrimAxes.push_back(new FGTrimAxis(fdmex,fgic,tPdot,tPhi,A_Tolerance));
100 //cout << "NumAxes: " << TrimAxes.size() << endl;
101 NumAxes=TrimAxes.size();
102 sub_iterations=new float[NumAxes];
103 successful=new float[NumAxes];
104 solution=new bool[NumAxes];
108 /******************************************************************************/
110 FGTrim::~FGTrim(void) {
111 for(current_axis=0; current_axis<NumAxes; current_axis++) {
112 delete TrimAxes[current_axis];
114 delete[] sub_iterations;
119 /******************************************************************************/
121 void FGTrim::TrimStats() {
124 cout << endl << " Trim Statistics: " << endl;
125 cout << " Total Iterations: " << total_its << endl;
127 cout << " Sub-iterations:" << endl;
128 for(current_axis=0; current_axis<NumAxes; current_axis++) {
129 run_sum+=TrimAxes[current_axis]->GetRunCount();
130 sprintf(out," %5s: %3.0f average: %5.2f successful: %3.0f stability: %5.2f\n",
131 TrimAxes[current_axis]->GetAccelName().c_str(),
132 sub_iterations[current_axis],
133 sub_iterations[current_axis]/float(total_its),
134 successful[current_axis],
135 TrimAxes[current_axis]->GetAvgStability() );
138 cout << " Run Count: " << run_sum << endl;
142 /******************************************************************************/
144 void FGTrim::Report(void) {
145 cout << " Trim Results: " << endl;
146 for(current_axis=0; current_axis<NumAxes; current_axis++)
147 TrimAxes[current_axis]->AxisReport();
151 /******************************************************************************/
153 void FGTrim::ReportState(void) {
154 char out[80], flap[10], gear[10];
156 cout << endl << " JSBSim State" << endl;
157 sprintf(out," Weight: %7.0f lbs. CG: %5.1f, %5.1f, %5.1f inches\n",
158 fdmex->GetAircraft()->GetWeight(),
159 fdmex->GetAircraft()->GetXYZcg()(1),
160 fdmex->GetAircraft()->GetXYZcg()(2),
161 fdmex->GetAircraft()->GetXYZcg()(3) );
163 if( fdmex->GetFCS()->GetDfPos() <= 0.01)
166 sprintf(flap,"%2.0f",fdmex->GetFCS()->GetDfPos());
167 if(fdmex->GetAircraft()->GetGearUp() == true)
170 sprintf(gear,"Down");
171 sprintf(out, " Flaps: %3s Gear: %4s\n",flap,gear);
173 sprintf(out, " Speed: %4.0f KCAS Mach: %5.2f\n",
174 fdmex->GetAuxiliary()->GetVcalibratedKTS(),
175 fdmex->GetState()->GetParameter(FG_MACH),
176 fdmex->GetPosition()->Geth() );
178 sprintf(out, " Altitude: %7.0f ft. AGL Altitude: %7.0f ft.\n",
179 fdmex->GetPosition()->Geth(),
180 fdmex->GetPosition()->GetDistanceAGL() );
182 sprintf(out, " Angle of Attack: %6.2f deg Pitch Angle: %6.2f deg\n",
183 fdmex->GetState()->GetParameter(FG_ALPHA)*RADTODEG,
184 fdmex->GetRotation()->Gettht()*RADTODEG );
186 sprintf(out, " Flight Path Angle: %6.2f deg Climb Rate: %5.0f ft/min\n",
187 fdmex->GetPosition()->GetGamma()*RADTODEG,
188 fdmex->GetPosition()->Gethdot()*60 );
190 sprintf(out, " Normal Load Factor: %4.2f g's Pitch Rate: %5.2f deg/s\n",
191 fdmex->GetAircraft()->GetNlf(),
192 fdmex->GetState()->GetParameter(FG_PITCHRATE)*RADTODEG );
194 sprintf(out, " Heading: %3.0f deg true Sideslip: %5.2f deg\n",
195 fdmex->GetRotation()->Getpsi()*RADTODEG,
196 fdmex->GetState()->GetParameter(FG_BETA)*RADTODEG );
198 sprintf(out, " Bank Angle: %3.0f deg\n",
199 fdmex->GetRotation()->Getphi()*RADTODEG );
201 sprintf(out, " Elevator: %5.2f deg Left Aileron: %5.2f deg Rudder: %5.2f deg\n",
202 fdmex->GetState()->GetParameter(FG_ELEVATOR_POS)*RADTODEG,
203 fdmex->GetState()->GetParameter(FG_AILERON_POS)*RADTODEG,
204 fdmex->GetState()->GetParameter(FG_RUDDER_POS)*RADTODEG );
206 sprintf(out, " Throttle: %5.2f%c\n",
207 fdmex->GetFCS()->GetThrottlePos(0),'%' );
211 /******************************************************************************/
213 bool FGTrim::DoTrim(void) {
218 for(int i=0;i < fdmex->GetAircraft()->GetNumGearUnits();i++){
219 fdmex->GetAircraft()->GetGearUnit(i)->SetReport(false);
222 fdmex->GetOutput()->Disable();
224 //clear the sub iterations counts & zero out the controls
225 for(current_axis=0;current_axis<NumAxes;current_axis++) {
226 //cout << current_axis << " " << TrimAxes[current_axis]->GetAccelName()
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;
239 for(current_axis=0;current_axis<NumAxes;current_axis++) {
241 if(!solution[current_axis]) {
243 solution[current_axis]=true;
246 } else if(findInterval()) {
249 solution[current_axis]=false;
251 sub_iterations[current_axis]+=Nsub;
253 for(current_axis=0;current_axis<NumAxes;current_axis++) {
254 //these checks need to be done after all the axes have run
255 if(Debug > 0) TrimAxes[current_axis]->AxisReport();
256 if(TrimAxes[current_axis]->InTolerance()) {
258 successful[current_axis]++;
263 if((axis_count == NumAxes-1) && (NumAxes > 1)) {
264 //cout << NumAxes-1 << " out of " << NumAxes << "!" << endl;
265 //At this point we can check the input limits of the failed axis
266 //and declare the trim failed if there is no sign change. If there
267 //is, keep going until success or max iteration count
269 //Oh, well: two out of three ain't bad
270 for(current_axis=0;current_axis<NumAxes;current_axis++) {
271 //these checks need to be done after all the axes have run
272 if(!TrimAxes[current_axis]->InTolerance()) {
274 // special case this for now -- if other cases arise proper
275 // support can be added to FGTrimAxis
276 if( (gamma_fallback) &&
277 (TrimAxes[current_axis]->GetAccelType() == tUdot) &&
278 (TrimAxes[current_axis]->GetControlType() == tThrottle)) {
279 cout << " Can't trim udot with throttle, trying flight"
280 << " path angle. (" << N << ")" << endl;
281 if(TrimAxes[current_axis]->GetAccel() > 0)
282 TrimAxes[current_axis]->SetControlToMin();
284 TrimAxes[current_axis]->SetControlToMax();
285 TrimAxes[current_axis]->Run();
286 delete TrimAxes[current_axis];
287 TrimAxes[current_axis]=new FGTrimAxis(fdmex,fgic,tUdot,
290 cout << " Sorry, " << TrimAxes[current_axis]->GetAccelName()
291 << " doesn't appear to be trimmable" << endl;
293 trim_failed=true; //force the trim to fail
298 } //all-but-one check
300 if(N > max_iterations)
302 } while((axis_count < NumAxes) && (!trim_failed));
303 if((!trim_failed) && (axis_count >= NumAxes)) {
305 cout << endl << " Trim successful" << endl;
308 cout << endl << " Trim failed" << endl;
310 for(int i=0;i < fdmex->GetAircraft()->GetNumGearUnits();i++){
311 fdmex->GetAircraft()->GetGearUnit(i)->SetReport(true);
313 fdmex->GetOutput()->Enable();
317 /******************************************************************************/
319 bool FGTrim::solve(void) {
321 float x1,x2,x3,f1,f2,f3,d,d0;
322 const float relax =0.9;
323 float eps=TrimAxes[current_axis]->GetSolverEps();
329 if( solutionDomain != 0) {
330 /* if(ahi > alo) { */
340 //max_sub_iterations=TrimAxes[current_axis]->GetIterationLimit();
341 while (!TrimAxes[current_axis]->InTolerance() && (fabs(d) > eps)
342 && (Nsub < max_sub_iterations)) {
345 x2=x1-d*d0*f1/(f3-f1);
346 TrimAxes[current_axis]->SetControl(x2);
347 TrimAxes[current_axis]->Run();
348 f2=TrimAxes[current_axis]->GetAccel();
350 cout << "FGTrim::solve Nsub,x1,x2,x3: " << Nsub << ", " << x1
351 << ", " << x2 << ", " << x3 << endl;
352 cout << " " << f1 << ", " << f2 << ", " << f3 << endl;
358 //cout << "Solution is between x1 and x2" << endl;
360 else if(f2*f3 <= 0.0) {
364 //cout << "Solution is between x2 and x3" << endl;
371 if(Nsub < max_sub_iterations) success=true;
376 /******************************************************************************/
378 produces an interval (xlo..xhi) on one side or the other of the current
379 control value in which a solution exists. This domain is, hopefully,
380 smaller than xmin..0 or 0..xmax and the solver will require fewer iterations
381 to find the solution. This is, hopefully, more efficient than having the
382 solver start from scratch every time. Maybe it isn't though...
383 This tries to take advantage of the idea that the changes from iteration to
384 iteration will be small after the first one or two top-level iterations.
386 assumes that changing the control will a produce significant change in the
387 accel i.e. checkLimits() has already been called.
389 if a solution is found above the current control, the function returns true
390 and xlo is set to the current control, xhi to the interval max it found, and
391 solutionDomain is set to 1.
392 if the solution lies below the current control, then the function returns
393 true and xlo is set to the interval min it found and xmax to the current
394 control. if no solution is found, then the function returns false.
397 in all cases, alo=accel(xlo) and ahi=accel(xhi) after the function exits.
398 no assumptions about the state of the sim after this function has run
401 bool FGTrim::findInterval(void) {
404 float current_control=TrimAxes[current_axis]->GetControl();
405 float current_accel=TrimAxes[current_axis]->GetAccel();;
406 float xmin=TrimAxes[current_axis]->GetControlMin();
407 float xmax=TrimAxes[current_axis]->GetControlMax();
408 float lastxlo,lastxhi,lastalo,lastahi;
410 step=0.025*fabs(xmax);
411 xlo=xhi=current_control;
412 alo=ahi=current_accel;
413 lastxlo=xlo;lastxhi=xhi;
414 lastalo=alo;lastahi=ahi;
420 if(xlo < xmin) xlo=xmin;
422 if(xhi > xmax) xhi=xmax;
423 TrimAxes[current_axis]->SetControl(xlo);
424 TrimAxes[current_axis]->Run();
425 alo=TrimAxes[current_axis]->GetAccel();
426 TrimAxes[current_axis]->SetControl(xhi);
427 TrimAxes[current_axis]->Run();
428 ahi=TrimAxes[current_axis]->GetAccel();
429 if(fabs(ahi-alo) <= TrimAxes[current_axis]->GetTolerance()) continue;
430 if(alo*ahi <=0) { //found interval with root
432 if(alo*current_accel <= 0) { //narrow interval down a bit
436 //xhi=current_control;
442 //xlo=current_control;
446 lastxlo=xlo;lastxhi=xhi;
447 lastalo=alo;lastahi=ahi;
448 if( !found && xlo==xmin && xhi==xmax ) continue;
450 cout << "FGTrim::findInterval: Nsub=" << Nsub << " Lo= " << xlo
451 << " Hi= " << xhi << " alo*ahi: " << alo*ahi << endl;
452 } while(!found && (Nsub <= max_sub_iterations) );
456 /******************************************************************************/
457 //checks to see which side of the current control value the solution is on
458 //and sets solutionDomain accordingly:
459 // 1 if solution is between the current and max
460 // -1 if solution is between the min and current
461 // 0 if there is no solution
463 //if changing the control produces no significant change in the accel then
464 //solutionDomain is set to zero and the function returns false
465 //if a solution is found, then xlo and xhi are set so that they bracket
466 //the solution, alo is set to accel(xlo), and ahi is set to accel(xhi)
467 //if there is no change or no solution then xlo=xmin, alo=accel(xmin) and
468 //xhi=xmax and ahi=accel(xmax)
469 //in all cases the sim is left such that the control=xmax and accel=ahi
471 bool FGTrim::checkLimits(void) {
473 float current_control=TrimAxes[current_axis]->GetControl();
474 float current_accel=TrimAxes[current_axis]->GetAccel();
475 xlo=TrimAxes[current_axis]->GetControlMin();
476 xhi=TrimAxes[current_axis]->GetControlMax();
478 TrimAxes[current_axis]->SetControl(xlo);
479 TrimAxes[current_axis]->Run();
480 alo=TrimAxes[current_axis]->GetAccel();
481 TrimAxes[current_axis]->SetControl(xhi);
482 TrimAxes[current_axis]->Run();
483 ahi=TrimAxes[current_axis]->GetAccel();
485 cout << "checkLimits() xlo,xhi,alo,ahi: " << xlo << ", " << xhi << ", "
486 << alo << ", " << ahi << endl;
488 solutionExists=false;
489 if(fabs(ahi-alo) > TrimAxes[current_axis]->GetTolerance()) {
490 if(alo*current_accel < 0) {
495 } else if(current_accel*ahi < 0){
502 TrimAxes[current_axis]->SetControl(current_control);
503 TrimAxes[current_axis]->Run();
504 return solutionExists;
510 //YOU WERE WARNED, BUT YOU DID IT ANYWAY.