]> git.mxchange.org Git - flightgear.git/blob - src/FDM/LaRCsim/IO360.cxx
Adapt to revised logging API.
[flightgear.git] / src / FDM / LaRCsim / IO360.cxx
1 // IO360.cxx - a piston engine model currently for the IO360 engine fitted to the C172
2 //             but with the potential to model other naturally aspirated piston engines
3 //             given appropriate config input.
4 //
5 // Written by David Luff, started 2000.
6 // Based on code by Phil Schubert, started 1999.
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include <simgear/compiler.h>
27
28 #include <math.h>
29
30 #include <fstream>
31 #include <iostream>
32
33 #include <Main/fg_props.hxx>
34
35 #include "IO360.hxx"
36 #include "ls_constants.h"
37
38
39 //*************************************************************************************
40 // Initialise the engine model
41 void FGNewEngine::init(double dt) {
42
43     // These constants should probably be moved eventually
44     CONVERT_CUBIC_INCHES_TO_METERS_CUBED = 1.638706e-5;
45     CONVERT_HP_TO_WATTS = 745.6999;
46
47     // Properties of working fluids
48     Cp_air = 1005;      // J/KgK
49     Cp_fuel = 1700;     // J/KgK
50     calorific_value_fuel = 47.3e6;  // W/Kg  Note that this is only an approximate value
51     rho_fuel = 800;     // kg/m^3 - an estimate for now
52     R_air = 287.3;
53
54     // environment inputs
55     p_amb_sea_level = 101325;   // Pascals              
56
57     // Control inputs  - ARE THESE NEEDED HERE???
58     Throttle_Lever_Pos = 75;
59     Propeller_Lever_Pos = 75;
60     Mixture_Lever_Pos = 100;
61
62     //misc
63     IAS = 0;
64     time_step = dt;
65
66     // Engine Specific Variables that should be read in from a config file
67     MaxHP = 200;    //Lycoming IO360 -A-C-D series
68 //  MaxHP = 180;    //Current Lycoming IO360 ?
69 //  displacement = 520;  //Continental IO520-M
70     displacement = 360;  //Lycoming IO360
71     displacement_SI = displacement * CONVERT_CUBIC_INCHES_TO_METERS_CUBED;
72     engine_inertia = 0.2;  //kgm^2 - value taken from a popular family saloon car engine - need to find an aeroengine value !!!!!
73     prop_inertia = 0.05;  //kgm^2 - this value is a total guess - dcl
74     Max_Fuel_Flow = 130;  // Units??? Do we need this variable any more??
75
76     // Engine specific variables that maybe should be read in from config but are pretty generic and won't vary much for a naturally aspirated piston engine.
77     Max_Manifold_Pressure = 28.50;  //Inches Hg. An approximation - should be able to find it in the engine performance data
78     Min_Manifold_Pressure = 6.5;    //Inches Hg. This is a guess corresponding to approx 0.24 bar MAP (7 in Hg) - need to find some proper data for this
79     Max_RPM = 2700;
80     Min_RPM = 600;                  //Recommended idle from Continental data sheet
81     Mag_Derate_Percent = 5;
82     Gear_Ratio = 1;
83     n_R = 2;         // Number of crank revolutions per power cycle - 2 for a 4 stroke engine.
84
85     // Various bits of housekeeping describing the engines initial state.
86     running = false;
87     cranking = false;
88     crank_counter = false;
89     starter = false;
90
91     // Initialise Engine Variables used by this instance
92     if(running)
93         RPM = 600;
94     else
95         RPM = 0;
96     Percentage_Power = 0;
97     Manifold_Pressure = 29.96; // Inches
98     Fuel_Flow_gals_hr = 0;
99 //    Torque = 0;
100     Torque_SI = 0;
101     CHT = 298.0;                        //deg Kelvin
102     CHT_degF = (CHT * 1.8) - 459.67;    //deg Fahrenheit
103     Mixture = 14;
104     Oil_Pressure = 0;   // PSI
105     Oil_Temp = 85;      // Deg C
106     current_oil_temp = 298.0;   //deg Kelvin
107     /**** one of these is superfluous !!!!***/
108     HP = 0;
109     RPS = 0;
110     Torque_Imbalance = 0;
111
112     // Initialise Propellor Variables used by this instance
113     FGProp1_RPS = 0;
114     // Hardcode propellor for now - the following two should be read from config eventually
115     prop_diameter = 1.8;         // meters
116     blade_angle = 23.0;          // degrees
117 }
118
119 //*****************************************************************************
120 // update the engine model based on current control positions
121 void FGNewEngine::update() {
122
123 /*
124     // Hack for testing - should output every 5 seconds
125     static int count1 = 0;
126     if(count1 == 0) {
127 //      cout << "P_atmos = " << p_amb << "  T_atmos = " << T_amb << '\n';
128 //      cout << "Manifold pressure = " << Manifold_Pressure << "  True_Manifold_Pressure = " << True_Manifold_Pressure << '\n';
129 //      cout << "p_amb_sea_level = " << p_amb_sea_level << '\n';
130 //      cout << "equivalence_ratio = " << equivalence_ratio << '\n';
131 //      cout << "combustion_efficiency = " << combustion_efficiency << '\n';
132 //      cout << "AFR = " << 14.7 / equivalence_ratio << '\n';
133 //      cout << "Mixture lever = " << Mixture_Lever_Pos << '\n';
134 //      cout << "n = " << RPM << " rpm\n";
135 //      cout << "T_amb = " << T_amb << '\n';
136 //      cout << "running = " << running << '\n';
137 //      cout << "fuel = " << fgGetFloat("/consumables/fuel/tank[0]/level-gal_us") << '\n';
138 //      cout << "Percentage_Power = " << Percentage_Power << '\n';
139 //      cout << "current_oil_temp = " << current_oil_temp << '\n';
140 //      cout << "EGT = " << EGT << '\n';
141     }
142     count1++;
143     if(count1 == 100)
144         count1 = 0;
145 */
146
147     // Check parameters that may alter the operating state of the engine. 
148     // (spark, fuel, starter motor etc)
149
150     // Check for spark
151     bool Magneto_Left = false;
152     bool Magneto_Right = false;
153     // Magneto positions:
154     // 0 -> off
155     // 1 -> left only
156     // 2 -> right only
157     // 3 -> both
158     if(mag_pos != 0) {
159         spark = true;
160     } else {
161         spark = false;
162     }  // neglects battery voltage, master on switch, etc for now.
163     if((mag_pos == 1) || (mag_pos > 2)) 
164         Magneto_Left = true;
165     if(mag_pos > 1)
166         Magneto_Right = true;
167  
168     // crude check for fuel
169     if((fgGetFloat("/consumables/fuel/tank[0]/level-gal_us") > 0) || (fgGetFloat("/consumables/fuel/tank[1]/level-gal_us") > 0)) {
170         fuel = true;
171     } else {
172         fuel = false;
173     }  // Need to make this better, eg position of fuel selector switch.
174
175     // Check if we are turning the starter motor
176     if(cranking != starter) {
177         // This check saves .../cranking from getting updated every loop - they only update when changed.
178         cranking = starter;
179         crank_counter = 0;
180     }
181     // Note that although /engines/engine[0]/starter and /engines/engine[0]/cranking might appear to be duplication it is
182     // not since the starter may be engaged with the battery voltage too low for cranking to occur (or perhaps the master 
183     // switch just left off) and the sound manager will read .../cranking to determine wether to play a cranking sound.
184     // For now though none of that is implemented so cranking can be set equal to .../starter without further checks.
185
186 //    int Alternate_Air_Pos =0; // Off = 0. Reduces power by 3 % for same throttle setting
187     // DCL - don't know what this Alternate_Air_Pos is - this is a leftover from the Schubert code.
188
189     //Check mode of engine operation
190     if(cranking) {
191         crank_counter++;
192         if(RPM <= 480) {
193             RPM += 100;
194             if(RPM > 480)
195                 RPM = 480;
196         } else {
197             // consider making a horrible noise if the starter is engaged with the engine running
198         }
199     }
200     if((!running) && (spark) && (fuel) && (crank_counter > 120)) {
201         // start the engine if revs high enough
202         if(RPM > 450) {
203             // For now just instantaneously start but later we should maybe crank for a bit
204             running = true;
205 //          RPM = 600;
206         }
207     }
208     if( (running) && ((!spark)||(!fuel)) ) {
209         // Cut the engine
210         // note that we only cut the power - the engine may continue to spin if the prop is in a moving airstream
211         running = false;
212     }
213
214     // Now we've ascertained whether the engine is running or not we can start to do the engine calculations 'proper'
215
216     // Calculate Sea Level Manifold Pressure
217     Manifold_Pressure = Calc_Manifold_Pressure( Throttle_Lever_Pos, Max_Manifold_Pressure, Min_Manifold_Pressure );
218     // cout << "manifold pressure = " << Manifold_Pressure << endl;
219
220     //Then find the actual manifold pressure (the calculated one is the sea level pressure)
221     True_Manifold_Pressure = Manifold_Pressure * p_amb / p_amb_sea_level;
222
223     //Do the fuel flow calculations
224     Calc_Fuel_Flow_Gals_Hr();
225
226     //Calculate engine power
227     Calc_Percentage_Power(Magneto_Left, Magneto_Right);
228     HP = Percentage_Power * MaxHP / 100.0;
229     Power_SI = HP * CONVERT_HP_TO_WATTS;
230
231     // FMEP calculation.  For now we will just use this during motored operation.
232     // Eventually we will calculate IMEP and use the FMEP all the time to give BMEP (maybe!)
233     if(!running) {
234         // This FMEP data is from the Patton paper, assumes fully warm conditions.
235         FMEP = 1e-12*pow(RPM,4) - 1e-8*pow(RPM,3) + 5e-5*pow(RPM,2) - 0.0722*RPM + 154.85;
236         // Gives FMEP in kPa - now convert to Pa
237         FMEP *= 1000;
238     } else {
239         FMEP = 0.0;
240     }
241     // Is this total FMEP or friction FMEP ???
242
243     Torque_FMEP = (FMEP * displacement_SI) / (2.0 * LS_PI * n_R);
244
245     // Calculate Engine Torque. Check for div by zero since percentage power correlation does not guarantee zero power at zero rpm.
246     // However this is problematical since there is a resistance to movement even at rest
247     // Ie this is a dynamics equation not a statics one.  This can be solved by going over to MEP based torque calculations.
248     if(RPM == 0) {
249         Torque_SI = 0 - Torque_FMEP;
250     }
251     else {
252         Torque_SI = ((Power_SI * 60.0) / (2.0 * LS_PI * RPM)) - Torque_FMEP;  //Torque = power / angular velocity
253         // cout << Torque << " Nm\n";
254     }
255
256     //Calculate Exhaust gas temperature
257     if(running)
258         Calc_EGT();
259     else
260         EGT = 298.0;
261
262     // Calculate Cylinder Head Temperature
263     Calc_CHT();
264     
265     // Calculate oil temperature
266     current_oil_temp = Calc_Oil_Temp(current_oil_temp);
267     
268     // Calculate Oil Pressure
269     Oil_Pressure = Calc_Oil_Press( Oil_Temp, RPM );
270     
271     // Now do the Propeller Calculations
272     Do_Prop_Calcs();
273     
274 // Now do the engine - prop torque balance to calculate final RPM
275     
276     //Calculate new RPM from torque balance and inertia.
277     Torque_Imbalance = Torque_SI - prop_torque;  //This gives a +ve value when the engine torque exeeds the prop torque
278     // (Engine torque is +ve when it acts in the direction of engine revolution, prop torque is +ve when it opposes the direction of engine revolution)
279     
280     angular_acceleration = Torque_Imbalance / (engine_inertia + prop_inertia);
281     angular_velocity_SI += (angular_acceleration * time_step);
282     // Don't let the engine go into reverse
283     if(angular_velocity_SI < 0)
284         angular_velocity_SI = 0;
285     RPM = (angular_velocity_SI * 60) / (2.0 * LS_PI);
286
287     // And finally a last check on the engine state after doing the torque balance with the prop - have we stalled?
288     if(running) { 
289         //Check if we have stalled the engine
290         if (RPM == 0) {
291             running = false;
292         } else if((RPM <= 480) && (cranking)) {
293             //Make sure the engine noise dosn't play if the engine won't start due to eg mixture lever pulled out.
294             running = false;
295             EGT = 298.0;
296         }
297     }
298
299     // And finally, do any unit conversions from internal units to output units
300     EGT_degF = (EGT * 1.8) - 459.67;
301     CHT_degF = (CHT * 1.8) - 459.67;
302 }
303
304 //*****************************************************************************************************
305
306 // FGNewEngine member functions
307
308 ////////////////////////////////////////////////////////////////////////////////////////////
309 // Return the combustion efficiency as a function of equivalence ratio
310 //
311 // Combustion efficiency values from Heywood, 
312 // "Internal Combustion Engine Fundamentals", ISBN 0-07-100499-8
313 ////////////////////////////////////////////////////////////////////////////////////////////
314 float FGNewEngine::Lookup_Combustion_Efficiency(float thi_actual)
315 {
316     const int NUM_ELEMENTS = 11;
317     float thi[NUM_ELEMENTS] = {0.0, 0.9, 1.0, 1.05, 1.1, 1.15, 1.2, 1.3, 1.4, 1.5, 1.6};  //array of equivalence ratio values
318     float neta_comb[NUM_ELEMENTS] = {0.98, 0.98, 0.97, 0.95, 0.9, 0.85, 0.79, 0.7, 0.63, 0.57, 0.525};  //corresponding array of combustion efficiency values
319     float neta_comb_actual = 0.0f;
320     float factor;
321
322     int i;
323     int j = NUM_ELEMENTS;  //This must be equal to the number of elements in the lookup table arrays
324
325     for(i=0;i<j;i++)
326     {
327         if(i == (j-1)) {
328             // Assume linear extrapolation of the slope between the last two points beyond the last point
329             float dydx = (neta_comb[i] - neta_comb[i-1]) / (thi[i] - thi[i-1]);
330             neta_comb_actual = neta_comb[i] + dydx * (thi_actual - thi[i]);
331             return neta_comb_actual;
332         }
333         if(thi_actual == thi[i]) {
334             neta_comb_actual = neta_comb[i];
335             return neta_comb_actual;
336         }
337         if((thi_actual > thi[i]) && (thi_actual < thi[i + 1])) {
338             //do linear interpolation between the two points
339             factor = (thi_actual - thi[i]) / (thi[i+1] - thi[i]);
340             neta_comb_actual = (factor * (neta_comb[i+1] - neta_comb[i])) + neta_comb[i];
341             return neta_comb_actual;
342         }
343     }
344
345     //if we get here something has gone badly wrong
346     SG_LOG(SG_AIRCRAFT, SG_ALERT, "error in FGNewEngine::Lookup_Combustion_Efficiency");
347     return neta_comb_actual;
348 }
349
350 ////////////////////////////////////////////////////////////////////////////////////////////
351 // Return the percentage of best mixture power available at a given mixture strength
352 //
353 // Based on data from "Technical Considerations for Catalysts for the European Market"
354 // by H S Gandi, published 1988 by IMechE
355 //
356 // Note that currently no attempt is made to set a lean limit on stable combustion
357 ////////////////////////////////////////////////////////////////////////////////////////////
358 float FGNewEngine::Power_Mixture_Correlation(float thi_actual)
359 {
360     float AFR_actual = 14.7 / thi_actual;
361     // thi and thi_actual are equivalence ratio
362     const int NUM_ELEMENTS = 13;
363     // The lookup table is in AFR because the source data was.  I added the two end elements to make sure we are almost always in it.
364     float AFR[NUM_ELEMENTS] = {(14.7/1.6), 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, (14.7/0.6)};  //array of equivalence ratio values
365     float mixPerPow[NUM_ELEMENTS] = {78, 86, 93.5, 98, 100, 99, 96.4, 92.5, 88, 83, 78.5, 74, 58};  //corresponding array of combustion efficiency values
366     float mixPerPow_actual = 0.0f;
367     float factor;
368     float dydx;
369
370     int i;
371     int j = NUM_ELEMENTS;  //This must be equal to the number of elements in the lookup table arrays
372
373     for(i=0;i<j;i++)
374     {
375         if(i == (j-1)) {
376             // Assume linear extrapolation of the slope between the last two points beyond the last point
377             dydx = (mixPerPow[i] - mixPerPow[i-1]) / (AFR[i] - AFR[i-1]);
378             mixPerPow_actual = mixPerPow[i] + dydx * (AFR_actual - AFR[i]);
379             return mixPerPow_actual;
380         }
381         if((i == 0) && (AFR_actual < AFR[i])) {
382             // Assume linear extrapolation of the slope between the first two points for points before the first point
383             dydx = (mixPerPow[1] - mixPerPow[0]) / (AFR[1] - AFR[0]);
384             mixPerPow_actual = mixPerPow[0] + dydx * (AFR_actual - AFR[0]);
385             return mixPerPow_actual;
386         }
387         if(AFR_actual == AFR[i]) {
388             mixPerPow_actual = mixPerPow[i];
389             return mixPerPow_actual;
390         }
391         if((AFR_actual > AFR[i]) && (AFR_actual < AFR[i + 1])) {
392             //do linear interpolation between the two points
393             factor = (AFR_actual - AFR[i]) / (AFR[i+1] - AFR[i]);
394             mixPerPow_actual = (factor * (mixPerPow[i+1] - mixPerPow[i])) + mixPerPow[i];
395             return mixPerPow_actual;
396         }
397     }
398
399     //if we get here something has gone badly wrong
400     SG_LOG(SG_AIRCRAFT, SG_ALERT, "error in FGNewEngine::Power_Mixture_Correlation");
401     return mixPerPow_actual;
402 }
403
404 // Calculate Cylinder Head Temperature
405 // Crudely models the cylinder head as an arbitary lump of arbitary size and area with one third of combustion energy
406 // as heat input and heat output as a function of airspeed and temperature.  Could be improved!!!
407 void FGNewEngine::Calc_CHT()
408 {
409     float h1 = -95.0;   //co-efficient for free convection
410     float h2 = -3.95;   //co-efficient for forced convection
411     float h3 = -0.05;   //co-efficient for forced convection due to prop backwash
412     float v_apparent;   //air velocity over cylinder head in m/s
413     float v_dot_cooling_air;
414     float m_dot_cooling_air;
415     float temperature_difference;
416     float arbitary_area = 1.0;
417     float dqdt_from_combustion;
418     float dqdt_forced;      //Rate of energy transfer to/from cylinder head due to forced convection (Joules) (sign convention: to cylinder head is +ve)
419     float dqdt_free;        //Rate of energy transfer to/from cylinder head due to free convection (Joules) (sign convention: to cylinder head is +ve)
420     float dqdt_cylinder_head;       //Overall energy change in cylinder head
421     float CpCylinderHead = 800.0;   //FIXME - this is a guess - I need to look up the correct value
422     float MassCylinderHead = 8.0;   //Kg - this is a guess - it dosn't have to be absolutely accurate but can be varied to alter the warm-up rate
423     float HeatCapacityCylinderHead;
424     float dCHTdt;
425
426     // The above values are hardwired to give reasonable results for an IO360 (C172 engine)
427     // Now adjust to try to compensate for arbitary sized engines - this is currently untested
428     arbitary_area *= (MaxHP / 180.0);
429     MassCylinderHead *= (MaxHP / 180.0);
430
431     temperature_difference = CHT - T_amb;
432
433     v_apparent = IAS * 0.5144444;  //convert from knots to m/s
434     v_dot_cooling_air = arbitary_area * v_apparent;
435     m_dot_cooling_air = v_dot_cooling_air * rho_air;
436
437     //Calculate rate of energy transfer to cylinder head from combustion
438     dqdt_from_combustion = m_dot_fuel * calorific_value_fuel * combustion_efficiency * 0.33;
439     
440     //Calculate rate of energy transfer from cylinder head due to cooling  NOTE is calculated as rate to but negative
441     dqdt_forced = (h2 * m_dot_cooling_air * temperature_difference) + (h3 * RPM * temperature_difference);
442     dqdt_free = h1 * temperature_difference;
443     
444     //Calculate net rate of energy transfer to or from cylinder head
445     dqdt_cylinder_head = dqdt_from_combustion + dqdt_forced + dqdt_free;
446     
447     HeatCapacityCylinderHead = CpCylinderHead * MassCylinderHead;
448     
449     dCHTdt = dqdt_cylinder_head / HeatCapacityCylinderHead;
450     
451     CHT += (dCHTdt * time_step);
452 }
453
454 // Calculate exhaust gas temperature
455 void FGNewEngine::Calc_EGT()
456 {
457     combustion_efficiency = Lookup_Combustion_Efficiency(equivalence_ratio);  //The combustion efficiency basically tells us what proportion of the fuels calorific value is released
458
459     //now calculate energy release to exhaust
460     //We will assume a three way split of fuel energy between useful work, the coolant system and the exhaust system
461     //This is a reasonable first suck of the thumb estimate for a water cooled automotive engine - whether it holds for an air cooled aero engine is probably open to question
462     //Regardless - it won't affect the variation of EGT with mixture, and we can always put a multiplier on EGT to get a reasonable peak value.
463     enthalpy_exhaust = m_dot_fuel * calorific_value_fuel * combustion_efficiency * 0.33;
464     heat_capacity_exhaust = (Cp_air * m_dot_air) + (Cp_fuel * m_dot_fuel);
465     delta_T_exhaust = enthalpy_exhaust / heat_capacity_exhaust;
466
467     EGT = T_amb + delta_T_exhaust;
468
469     //The above gives the exhaust temperature immediately prior to leaving the combustion chamber
470     //Now derate to give a more realistic figure as measured downstream
471     //For now we will aim for a peak of around 400 degC (750 degF)
472
473     EGT *= 0.444 + ((0.544 - 0.444) * Percentage_Power / 100.0);
474 }
475
476 // Calculate Manifold Pressure based on Throttle lever Position
477 float FGNewEngine::Calc_Manifold_Pressure ( float LeverPosn, float MaxMan, float MinMan)
478 {
479     float Inches;
480
481     //Note that setting the manifold pressure as a function of lever position only is not strictly accurate
482     //MAP is also a function of engine speed. (and ambient pressure if we are going for an actual MAP model)
483     Inches = MinMan + (LeverPosn * (MaxMan - MinMan) / 100);
484
485     //allow for idle bypass valve or slightly open throttle stop
486     if(Inches < MinMan)
487         Inches = MinMan;
488
489     //Check for stopped engine (crudest way of compensating for engine speed)
490     if(RPM == 0)
491         Inches = 29.92;
492
493     return Inches;
494 }
495
496 // Calculate fuel flow in gals/hr
497 void FGNewEngine::Calc_Fuel_Flow_Gals_Hr()
498 {
499     //DCL - calculate mass air flow into engine based on speed and load - separate this out into a function eventually
500     //t_amb is actual temperature calculated from altitude
501     //calculate density from ideal gas equation
502     rho_air = p_amb / ( R_air * T_amb );
503     rho_air_manifold = rho_air * Manifold_Pressure / 29.6;  //This is a bit of a roundabout way of calculating this but it works !!  If we put manifold pressure into SI units we could do it simpler.
504     //calculate ideal engine volume inducted per second
505     swept_volume = (displacement_SI * (RPM / 60)) / 2;  //This equation is only valid for a four stroke engine
506     //calculate volumetric efficiency - for now we will just use 0.8, but actually it is a function of engine speed and the exhaust to manifold pressure ratio
507     //Note that this is cylinder vol eff - the throttle drop is already accounted for in the MAP calculation
508     volumetric_efficiency = 0.8;
509     //Now use volumetric efficiency to calculate actual air volume inducted per second
510     v_dot_air = swept_volume * volumetric_efficiency;
511     //Now calculate mass flow rate of air into engine
512     m_dot_air = v_dot_air * rho_air_manifold;
513
514 //**************
515
516     //DCL - now calculate fuel flow into engine based on air flow and mixture lever position
517     //assume lever runs from no flow at fully out to thi = 1.3 at fully in at sea level
518     //also assume that the injector linkage is ideal - hence the set mixture is maintained at a given altitude throughout the speed and load range
519     thi_sea_level = 1.3 * ( Mixture_Lever_Pos / 100.0 );
520     equivalence_ratio = thi_sea_level * p_amb_sea_level / p_amb; //ie as we go higher the mixture gets richer for a given lever position
521     m_dot_fuel = m_dot_air / 14.7 * equivalence_ratio;
522     Fuel_Flow_gals_hr = (m_dot_fuel / rho_fuel) * 264.172 * 3600.0;  // Note this assumes US gallons
523 }
524
525 // Calculate the percentage of maximum rated power delivered as a function of Manifold pressure multiplied by engine speed (rpm)
526 // This is not necessarilly the best approach but seems to work for now.
527 // May well need tweaking at the bottom end if the prop model is changed.
528 void FGNewEngine::Calc_Percentage_Power(bool mag_left, bool mag_right)
529 {
530     // For a given Manifold Pressure and RPM calculate the % Power
531     // Multiply Manifold Pressure by RPM
532     float ManXRPM = True_Manifold_Pressure * RPM;
533
534 /*
535 //  Phil's %power correlation
536     //  Calculate % Power
537     Percentage_Power = (+ 7E-09 * ManXRPM * ManXRPM) + ( + 7E-04 * ManXRPM) - 0.1218;
538     // cout << Percentage_Power <<  "%" << "\t";
539 */
540
541 // DCL %power correlation - basically Phil's correlation modified to give slighty less power at the low end
542 // might need some adjustment as the prop model is adjusted
543 // My aim is to match the prop model and engine model at the low end to give the manufacturer's recommended idle speed with the throttle closed - 600rpm for the Continental IO520
544     //  Calculate % Power for Nev's prop model
545     //Percentage_Power = (+ 6E-09 * ManXRPM * ManXRPM) + ( + 8E-04 * ManXRPM) - 1.8524;
546
547     // Calculate %power for DCL prop model
548     Percentage_Power = (7e-9 * ManXRPM * ManXRPM) + (7e-4 * ManXRPM) - 1.0;
549
550     // Power de-rating for altitude has been removed now that we are basing the power
551     // on the actual manifold pressure, which takes air pressure into account.  However - this fails to
552     // take the temperature into account - this is TODO.
553
554     // Adjust power for temperature - this is temporary until the power is done as a function of mass flow rate induced
555     // Adjust for Temperature - Temperature above Standard decrease
556     // power by 7/120 % per degree F increase, and incease power for
557     // temps below at the same ratio
558     float T_amb_degF = (T_amb * 1.8) - 459.67;
559     float T_amb_sea_lev_degF = (288 * 1.8) - 459.67; 
560     Percentage_Power = Percentage_Power + ((T_amb_sea_lev_degF - T_amb_degF) * 7 /120);
561
562     //DCL - now adjust power to compensate for mixture
563     Percentage_of_best_power_mixture_power = Power_Mixture_Correlation(equivalence_ratio);
564     Percentage_Power = Percentage_Power * Percentage_of_best_power_mixture_power / 100.0;
565
566     // Now Derate engine for the effects of Bad/Switched off magnetos
567     //if (Magneto_Left == 0 && Magneto_Right == 0) {
568     if(!running) {
569         // cout << "Both OFF\n";
570         Percentage_Power = 0;
571     } else if (mag_left && mag_right) {
572         // cout << "Both On    ";
573     } else if (mag_left == 0 || mag_right== 0) {
574         // cout << "1 Magneto Failed   ";
575         Percentage_Power = Percentage_Power * ((100.0 - Mag_Derate_Percent)/100.0);
576         //  cout << FGEng1_Percentage_Power <<  "%" << "\t";
577     }
578 /*
579     //DCL - stall the engine if RPM drops below 450 - this is possible if mixture lever is pulled right out
580     //This is a kludge that I should eliminate by adding a total fmep estimation.
581     if(RPM < 450)
582         Percentage_Power = 0;
583 */
584     if(Percentage_Power < 0)
585         Percentage_Power = 0;
586 }
587
588 // Calculate Oil Temperature in degrees Kelvin
589 float FGNewEngine::Calc_Oil_Temp (float oil_temp)
590 {
591     float idle_percentage_power = 2.3;  // approximately
592     float target_oil_temp;          // Steady state oil temp at the current engine conditions
593     float time_constant;            // The time constant for the differential equation
594     if(running) {
595         target_oil_temp = 363;
596         time_constant = 500;        // Time constant for engine-on idling.
597         if(Percentage_Power > idle_percentage_power) {
598             time_constant /= ((Percentage_Power / idle_percentage_power) / 10.0);       // adjust for power 
599         }
600     } else {
601         target_oil_temp = 298;
602         time_constant = 1000;  // Time constant for engine-off; reflects the fact that oil is no longer getting circulated
603     }
604
605     float dOilTempdt = (target_oil_temp - oil_temp) / time_constant;
606
607     oil_temp += (dOilTempdt * time_step);
608
609     return (oil_temp);
610 }
611
612 // Calculate Oil Pressure
613 float FGNewEngine::Calc_Oil_Press (float Oil_Temp, float Engine_RPM)
614 {
615     float Oil_Pressure = 0;                     //PSI
616     float Oil_Press_Relief_Valve = 60;  //PSI
617     float Oil_Press_RPM_Max = 1800;
618     float Design_Oil_Temp = 85;         //Celsius
619     float Oil_Viscosity_Index = 0.25;   // PSI/Deg C
620 //    float Temp_Deviation = 0;         // Deg C
621
622     Oil_Pressure = (Oil_Press_Relief_Valve / Oil_Press_RPM_Max) * Engine_RPM;
623
624     // Pressure relief valve opens at Oil_Press_Relief_Valve PSI setting
625     if (Oil_Pressure >= Oil_Press_Relief_Valve) {
626         Oil_Pressure = Oil_Press_Relief_Valve;
627     }
628
629     // Now adjust pressure according to Temp which affects the viscosity
630
631     Oil_Pressure += (Design_Oil_Temp - Oil_Temp) * Oil_Viscosity_Index;
632
633     return Oil_Pressure;
634 }
635
636
637 // Propeller calculations.
638 void FGNewEngine::Do_Prop_Calcs()
639 {
640     float Gear_Ratio = 1.0;
641     float forward_velocity;             // m/s
642     float prop_power_consumed_SI;       // Watts
643     double J;                           // advance ratio - dimensionless
644     double Cp_20;                   // coefficient of power for 20 degree blade angle
645     double Cp_25;                   // coefficient of power for 25 degree blade angle
646     double Cp;                      // Our actual coefficient of power
647     double neta_prop_20;
648     double neta_prop_25;
649     double neta_prop;               // prop efficiency
650
651     FGProp1_RPS = RPM * Gear_Ratio / 60.0; 
652     angular_velocity_SI = 2.0 * LS_PI * RPM / 60.0;
653     forward_velocity = IAS * 0.514444444444;        // Convert to m/s
654     
655     if(FGProp1_RPS == 0)
656         J = 0;
657     else
658         J = forward_velocity / (FGProp1_RPS * prop_diameter);
659     //cout << "advance_ratio = " << J << '\n';
660     
661     //Cp correlations based on data from McCormick
662     Cp_20 = 0.0342*J*J*J*J - 0.1102*J*J*J + 0.0365*J*J - 0.0133*J + 0.064;
663     Cp_25 = 0.0119*J*J*J*J - 0.0652*J*J*J + 0.018*J*J - 0.0077*J + 0.0921;
664     
665     //cout << "Cp_20 = " << Cp_20 << '\n';
666     //cout << "Cp_25 = " << Cp_25 << '\n';
667     
668     //Assume that the blade angle is between 20 and 25 deg - reasonable for fixed pitch prop but won't hold for variable one !!!
669     Cp = Cp_20 + ( (Cp_25 - Cp_20) * ((blade_angle - 20)/(25 - 20)) );
670     //cout << "Cp = " << Cp << '\n';
671     //cout << "RPM = " << RPM << '\n';
672     //cout << "angular_velocity_SI = " << angular_velocity_SI << '\n';
673     
674     prop_power_consumed_SI = Cp * rho_air * pow(FGProp1_RPS,3.0f) * pow(float(prop_diameter),5.0f);
675     //cout << "prop HP consumed = " << prop_power_consumed_SI / 745.699 << '\n';
676     if(angular_velocity_SI == 0)
677         prop_torque = 0;
678         // However this can give problems - if rpm == 0 but forward velocity increases the prop should be able to generate a torque to start the engine spinning
679         // Unlikely to happen in practice - but I suppose someone could move the lever of a stopped large piston engine from feathered to windmilling.
680         // This *does* give problems - if the plane is put into a steep climb whilst windmilling the engine friction will eventually stop it spinning.
681         // When put back into a dive it never starts re-spinning again.  Although it is unlikely that anyone would do this in real life, they might well do it in a sim!!!
682     else
683         prop_torque = prop_power_consumed_SI / angular_velocity_SI;
684     
685     // calculate neta_prop here
686     neta_prop_20 = 0.1328*J*J*J*J - 1.3073*J*J*J + 0.3525*J*J + 1.5591*J + 0.0007;
687     neta_prop_25 = -0.3121*J*J*J*J + 0.4234*J*J*J - 0.7686*J*J + 1.5237*J - 0.0004;
688     neta_prop = neta_prop_20 + ( (neta_prop_25 - neta_prop_20) * ((blade_angle - 20)/(25 - 20)) );
689     
690     // Check for zero forward velocity to avoid divide by zero
691     if(forward_velocity < 0.0001)
692         prop_thrust = 0.0;
693         // I don't see how this works - how can the plane possibly start from rest ???
694         // Hmmmm - it works because the forward_velocity at present never drops below about 0.03 even at rest
695         // We can't really rely on this in the future - needs fixing !!!!
696         // The problem is that we're doing this calculation backwards - we're working out the thrust from the power consumed and the velocity, which becomes invalid as velocity goes to zero.
697         // It would be far more natural to work out the power consumed from the thrust - FIXME!!!!!.
698     else
699         prop_thrust = neta_prop * prop_power_consumed_SI / forward_velocity;       //TODO - rename forward_velocity to IAS_SI
700     //cout << "prop_thrust = " << prop_thrust << '\n';
701 }