pyBADA 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. pyBADA/TCL.py +8731 -0
  2. pyBADA/__init__.py +0 -0
  3. pyBADA/aircraft/BADA3/DUMMY/BADA.GPF +113 -0
  4. pyBADA/aircraft/BADA3/DUMMY/BZJT__.APF +25 -0
  5. pyBADA/aircraft/BADA3/DUMMY/BZJT__.OPF +61 -0
  6. pyBADA/aircraft/BADA3/DUMMY/BZJT__.PTD +139 -0
  7. pyBADA/aircraft/BADA3/DUMMY/BZJT__.PTF +73 -0
  8. pyBADA/aircraft/BADA3/DUMMY/GA____.APF +25 -0
  9. pyBADA/aircraft/BADA3/DUMMY/GA____.OPF +61 -0
  10. pyBADA/aircraft/BADA3/DUMMY/GA____.PTD +71 -0
  11. pyBADA/aircraft/BADA3/DUMMY/GA____.PTF +39 -0
  12. pyBADA/aircraft/BADA3/DUMMY/J2H___.APF +25 -0
  13. pyBADA/aircraft/BADA3/DUMMY/J2H___.OPF +61 -0
  14. pyBADA/aircraft/BADA3/DUMMY/J2H___.PTD +131 -0
  15. pyBADA/aircraft/BADA3/DUMMY/J2H___.PTF +69 -0
  16. pyBADA/aircraft/BADA3/DUMMY/J2M___.APF +25 -0
  17. pyBADA/aircraft/BADA3/DUMMY/J2M___.OPF +61 -0
  18. pyBADA/aircraft/BADA3/DUMMY/J2M___.PTD +123 -0
  19. pyBADA/aircraft/BADA3/DUMMY/J2M___.PTF +65 -0
  20. pyBADA/aircraft/BADA3/DUMMY/J4H___.APF +25 -0
  21. pyBADA/aircraft/BADA3/DUMMY/J4H___.OPF +61 -0
  22. pyBADA/aircraft/BADA3/DUMMY/J4H___.PTD +139 -0
  23. pyBADA/aircraft/BADA3/DUMMY/J4H___.PTF +73 -0
  24. pyBADA/aircraft/BADA3/DUMMY/ReleaseSummary +35 -0
  25. pyBADA/aircraft/BADA3/DUMMY/SYNONYM.NEW +155 -0
  26. pyBADA/aircraft/BADA3/DUMMY/TP2M__.APF +25 -0
  27. pyBADA/aircraft/BADA3/DUMMY/TP2M__.OPF +61 -0
  28. pyBADA/aircraft/BADA3/DUMMY/TP2M__.PTD +99 -0
  29. pyBADA/aircraft/BADA3/DUMMY/TP2M__.PTF +53 -0
  30. pyBADA/aircraft/BADA4/DUMMY/ACM_BADA4.xsd +556 -0
  31. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST.ATF +106 -0
  32. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST.xml +244 -0
  33. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA+20.PTD +182 -0
  34. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA+20.PTF +41 -0
  35. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA.PTD +182 -0
  36. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA.PTF +41 -0
  37. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP.ATF +191 -0
  38. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP.xml +540 -0
  39. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA+20.PTD +218 -0
  40. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA+20.PTF +49 -0
  41. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA.PTD +218 -0
  42. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA.PTF +49 -0
  43. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/LRC.dat +38 -0
  44. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/MEC.dat +58 -0
  45. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/MRC.dat +38 -0
  46. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/OPTALT.dat +37 -0
  47. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN.ATF +204 -0
  48. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN.xml +648 -0
  49. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA+20.PTD +281 -0
  50. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA+20.PTF +63 -0
  51. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA.PTD +281 -0
  52. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA.PTF +63 -0
  53. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/ECON.OPT +37 -0
  54. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/LRC.OPT +38 -0
  55. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/MEC.OPT +58 -0
  56. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/MRC.OPT +38 -0
  57. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/OPTALT.OPT +37 -0
  58. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus.ATF +238 -0
  59. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus.xml +737 -0
  60. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA+20.PTD +281 -0
  61. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA+20.PTF +63 -0
  62. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA.PTD +281 -0
  63. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA.PTF +63 -0
  64. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/ECON.OPT +37 -0
  65. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/LRC.OPT +36 -0
  66. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/MEC.OPT +56 -0
  67. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/MRC.OPT +36 -0
  68. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/OPTALT.OPT +37 -0
  69. pyBADA/aircraft/BADA4/DUMMY/GPF.xml +130 -0
  70. pyBADA/aircraft/BADA4/DUMMY/GPF_BADA4.xsd +75 -0
  71. pyBADA/aircraft/BADA4/DUMMY/aircraft_model_default.xml +311 -0
  72. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH.ATF +97 -0
  73. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH.xml +82 -0
  74. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA+20.PTD +632 -0
  75. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA+20.PTF +71 -0
  76. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTD +632 -0
  77. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTF +71 -0
  78. pyBADA/aircraft/BADAH/DUMMY/DUMH/LRC.OPT +142 -0
  79. pyBADA/aircraft/BADAH/DUMMY/DUMH/MEC.OPT +142 -0
  80. pyBADA/aircraft/BADAH/DUMMY/DUMH/MRC.OPT +142 -0
  81. pyBADA/aircraft.py +414 -0
  82. pyBADA/atmosphere.py +345 -0
  83. pyBADA/bada3.py +4566 -0
  84. pyBADA/bada4.py +5327 -0
  85. pyBADA/badaE.py +3317 -0
  86. pyBADA/badaH.py +3632 -0
  87. pyBADA/configuration.py +98 -0
  88. pyBADA/constants.py +64 -0
  89. pyBADA/conversions.py +197 -0
  90. pyBADA/data/magneticDeclinationGridData.json +247067 -0
  91. pyBADA/flightTrajectory.py +929 -0
  92. pyBADA/geodesic.py +760 -0
  93. pyBADA/magnetic.py +119 -0
  94. pyBADA/trajectoryPrediction.py +175 -0
  95. pybada-0.1.0.dist-info/METADATA +57 -0
  96. pybada-0.1.0.dist-info/RECORD +99 -0
  97. pybada-0.1.0.dist-info/WHEEL +4 -0
  98. pybada-0.1.0.dist-info/licenses/AUTHORS +2 -0
  99. pybada-0.1.0.dist-info/licenses/LICENCE.txt +287 -0
pyBADA/badaE.py ADDED
@@ -0,0 +1,3317 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ pyBADA
4
+ Generic BADAE aircraft performance module
5
+ Developped @EUROCONTROL (EIH) by Henrich Glaser-Opitz, PhD
6
+ 2024
7
+ """
8
+
9
+ __author__ = "Henrich Glaser-Opitz"
10
+ __copyright__ = "Copyright 2024, EUROCONTROL (EIH)"
11
+ __license__ = "BADA Eurocontrol"
12
+ __version__ = "1.0.0"
13
+ __maintainer__ = "Henrich Glaser-Opitz"
14
+ __email__ = "henrich.glaser-opitz@eurocontrol.int"
15
+ __status__ = "Development"
16
+ __docformat__ = "reStructuredText"
17
+
18
+
19
+ import xml.etree.ElementTree as ET
20
+ from datetime import date
21
+ import os
22
+ import numpy as np
23
+ from math import sqrt, pow, pi, cos, atan, radians, isnan
24
+
25
+ from scipy.optimize import fminbound
26
+
27
+ from pyBADA import constants as const
28
+ from pyBADA import conversions as conv
29
+ from pyBADA import atmosphere as atm
30
+ from pyBADA.aircraft import Helicopter, BadaFamily
31
+
32
+
33
+ def proper_round(num, dec=0):
34
+ num = str(num)[: str(num).index(".") + dec + 2]
35
+ if num[-1] >= "5":
36
+ return float(num[: -2 - (not dec)] + str(int(num[-2 - (not dec)]) + 1))
37
+ return float(num[:-1])
38
+
39
+
40
+ def checkArgument(argument, **kwargs):
41
+ if kwargs.get(argument) is not None:
42
+ return kwargs.get(argument)
43
+ else:
44
+ raise TypeError("Missing " + argument + " argument")
45
+
46
+
47
+ class Parse(object):
48
+ """This class implements the BADAE parsing mechanism to parse xml BADAE files.
49
+
50
+ :param filePath: path to the folder with BADAE xml formatted file.
51
+ :param acName: ICAO aircraft designation
52
+ :type filePath: str.
53
+ :type acName: str
54
+ """
55
+
56
+ def __init__(self):
57
+ pass
58
+
59
+ def parse(self, filePath, badaFamily, badaVersion, acName):
60
+ """This function parses BADAE xml formatted file
61
+
62
+ :param filename: path to the BADAE xml formatted file.
63
+ :type filename: str.
64
+ :raises: IOError
65
+ """
66
+
67
+ self.filePath = filePath
68
+ self.acName = acName
69
+ self.BADAVersion = badaVersion
70
+ selfBADAFamily = badaFamily
71
+
72
+ acXmlFile = (
73
+ os.path.join(filePath, badaFamily, badaVersion, acName, acName) + ".xml"
74
+ )
75
+
76
+ try:
77
+ tree = ET.parse(acXmlFile)
78
+ root = tree.getroot()
79
+ except:
80
+ raise IOError(acXmlFile + " not found or in correct format")
81
+
82
+ # Parse general aircraft data
83
+ self.model = root.find("model").text # aircraft model
84
+ self.engineType = root.find("type").text # aircraft type
85
+ self.engines = root.find("engine").text # engine type
86
+
87
+ self.ICAO_desig = {} # ICAO designator and WTC
88
+ self.ICAO_desig["designator"] = root.find("ICAO").find("designator").text
89
+ self.ICAO_desig["WTC"] = root.find("ICAO").find("WTC").text
90
+
91
+ # Parse Aerodynamic Forces Model
92
+ AFM = root.find("AFM") # get AFM
93
+
94
+ self.MR_radius = float(AFM.find("MR_radius").text) # Main rotor radius
95
+
96
+ CRS = AFM.find("CRS")
97
+ self.crs = []
98
+ for i in CRS.findall("crs"):
99
+ self.crs.append(float(i.text))
100
+
101
+ CPreq = AFM.find("CPreq")
102
+ self.cpr = []
103
+ for i in CPreq.findall("cpr"):
104
+ self.cpr.append(float(i.text))
105
+
106
+ # Parse engine data
107
+ PFM = root.find("PFM") # get PFM
108
+
109
+ self.n_eng = int(PFM.find("n_eng").text) # number of engines
110
+
111
+ EEM = PFM.find("EEM") # get EEM
112
+ self.P0 = float(EEM.find("P0").text)
113
+
114
+ CVoc = EEM.find("CVoc")
115
+ self.cVoc = [] # Matrix of open-circuit voltage coefficients [V]
116
+ for i in CVoc.findall("cVoc"):
117
+ self.cVoc.append(float(i.text))
118
+
119
+ CR0 = EEM.find("CR0")
120
+ self.cR0 = [] # matrix of first internal resistance coefficients [ohm]
121
+ for i in CR0.findall("cR0"):
122
+ self.cR0.append(float(i.text))
123
+
124
+ CRi = EEM.find("CRi")
125
+ self.cRi = [] # matrix of second internal resistance coefficients [ohm]
126
+ for i in CRi.findall("cRi"):
127
+ self.cRi.append(float(i.text))
128
+
129
+ self.Imax = float(
130
+ EEM.find("Imax").text
131
+ ) # Maximum output current of the battery [A]
132
+ self.Vmin = float(
133
+ EEM.find("Vmin").text
134
+ ) # Minimum operating voltage of the motor [V]
135
+ self.capacity = float(EEM.find("capacity").text) # Battery capacity [Wh]
136
+ self.eta = float(EEM.find("eta").text) # Efficiency of the motor [-]
137
+
138
+ self.Pmax_ = {}
139
+ # Maximum take-off (MTKF)
140
+ MTKF = EEM.find("MTKF")
141
+ self.Pmax_["MTKF"] = float(MTKF.find("Pmax").text)
142
+
143
+ # Maximum continuous (MCNT)
144
+ MCNT = EEM.find("MCNT")
145
+ self.Pmax_["MCNT"] = float(MCNT.find("Pmax").text)
146
+
147
+ # Parse Aircraft Limitation Model (ALM)
148
+ ALM = root.find("ALM") # get ALM
149
+ self.hmo = float(ALM.find("GLM").find("hmo").text)
150
+ self.vne = float(ALM.find("KLM").find("vne").text)
151
+ self.MTOW = float(ALM.find("DLM").find("MTOW").text)
152
+ self.OEW = float(ALM.find("DLM").find("OEW").text)
153
+ self.MFL = float(ALM.find("DLM").find("MFL").text)
154
+
155
+
156
+ class BADAE(Helicopter):
157
+ """This class implements the part of BADAE performance model that will be used in other classes following the BADAE manual.
158
+
159
+ :param AC: parsed aircraft.
160
+ :type AC: badaE.Parse.
161
+ """
162
+
163
+ def __init__(self, AC):
164
+ Helicopter.__init__(object)
165
+
166
+ self.AC = AC
167
+
168
+ def RotorSpd_reg(self, tas):
169
+ """This function computes rotor rotational speed regression corresponding to the TAS speed
170
+
171
+ :param tas: true airspeed (TAS) [kt].
172
+ :type tas: float.
173
+ :return: rotor rotational speed regression [rad/s].
174
+ :rtype: float.
175
+ """
176
+
177
+ rotorSpd_reg = (
178
+ pow(tas, 5) * self.AC.crs[0]
179
+ + pow(tas, 4) * self.AC.crs[1]
180
+ + pow(tas, 3) * self.AC.crs[2]
181
+ + pow(tas, 2) * self.AC.crs[3]
182
+ + pow(tas, 1) * self.AC.crs[4]
183
+ + self.AC.crs[5]
184
+ )
185
+
186
+ return rotorSpd_reg
187
+
188
+ def RotorSpd(self, tas):
189
+ """This function computes rotor rotational speed corresponding to the TAS speed
190
+
191
+ :param tas: true airspeed (TAS) [m s^-1].
192
+ :type tas: float.
193
+ :return: rotor rotational speed [rad/min].
194
+ :rtype: float.
195
+ """
196
+
197
+ # check if rotor speed is variable or constant
198
+ if any(self.AC.crs[0:-1]):
199
+ # regresion was performed using TAS in [kt] and rots speed in rad/s
200
+ TAS = conv.ms2kt(tas)
201
+
202
+ if TAS <= 0:
203
+ rotorSpd = self.RotorSpd_reg(tas=0.0)
204
+ elif TAS >= 100:
205
+ rotorSpd = self.RotorSpd_reg(tas=100.0)
206
+ else:
207
+ rotorSpd = self.RotorSpd_reg(tas=TAS)
208
+
209
+ rotorSpd = rotorSpd * 60 / (2 * pi)
210
+ else:
211
+ rotorSpd = self.AC.crs[-1]
212
+
213
+ return rotorSpd
214
+
215
+ def TipSpd(self, tas):
216
+ """This function computes rotor blade tip speed corresponding to the TAS speed
217
+
218
+ :param tas: true airspeed (TAS) [m s^-1].
219
+ :type tas: float.
220
+ :return: rotor blade tip speed [m s^-1].
221
+ :rtype: float.
222
+ """
223
+
224
+ tipSpd = self.RotorSpd(tas=tas) * (2 * pi / 60) * self.AC.MR_radius
225
+
226
+ return tipSpd
227
+
228
+ def Const2(self, tas):
229
+ """This function computes constant2
230
+
231
+ :param tas: true airspeed (TAS) [m s^-1].
232
+ :type tas: float.
233
+ :return: const2 [N].
234
+ :rtype: float.
235
+ """
236
+
237
+ const2 = (
238
+ const.rho_0 * pi * pow(self.AC.MR_radius, 2) * pow(self.TipSpd(tas=tas), 2)
239
+ )
240
+
241
+ return const2
242
+
243
+ def Const3(self, tas):
244
+ """This function computes constant3
245
+
246
+ :param tas: true airspeed (TAS) [m s^-1].
247
+ :type tas: float.
248
+ :return: const3 [W].
249
+ :rtype: float.
250
+ """
251
+
252
+ const3 = self.Const2(tas=tas) * self.TipSpd(tas=tas)
253
+
254
+ return const3
255
+
256
+ def mu(self, tas):
257
+ """This function computes the advance ratio
258
+
259
+ :param tas: true airspeed (TAS) [m/s].
260
+ :param gamma: flight path angle [rad].
261
+ :type tas: float.
262
+ :type gamma: float.
263
+ :return: mu: advance ratio [-].
264
+ :rtype: float.
265
+ """
266
+
267
+ mu = tas / self.TipSpd(tas=tas)
268
+
269
+ return mu
270
+
271
+ def CT(self, tas, sigma, mass, phi=0.0):
272
+ """This function computes the thrust coefficient
273
+
274
+ :param tas: true airspeed (TAS) [m/s]
275
+ :param sigma: Normalised density [-].
276
+ :param mass: aircraft mass [kg].
277
+ :param phi: bank angle [deg].
278
+ :type tas: float.
279
+ :type sigma: float.
280
+ :type mass: float.
281
+ :type phi: float.
282
+ :return: thrust coefficient [-].
283
+ :rtype: float.
284
+ """
285
+
286
+ CT = (mass * const.g) / (sigma * self.Const2(tas=tas) * cos(radians(phi)))
287
+
288
+ return CT
289
+
290
+ def Thrust(self, tas, sigma, mass, phi=0.0):
291
+ """This function computes the thrust
292
+
293
+ :param tas: true airspeed (TAS) [m/s]
294
+ :param sigma: Normalised density [-].
295
+ :param mass: aircraft mass [kg].
296
+ :type tas: float.
297
+ :type sigma: float.
298
+ :type mass: float.
299
+ :return: thrust [N].
300
+ :rtype: float.
301
+ """
302
+
303
+ CT = self.CT(sigma=sigma, mass=mass, tas=tas, phi=phi)
304
+ thrust = sigma * self.Const2(tas) * CT
305
+
306
+ return thrust
307
+
308
+ def CPreq(self, mu, CT):
309
+ """This function computes the power required coefficient
310
+
311
+ :param mu: advance ratio [-].
312
+ :param Ct: thrust coefficient [-].
313
+ :type mu: float
314
+ :type Ct: float
315
+ :return: power required coefficient [-]
316
+ :rtype: float
317
+ """
318
+
319
+ CPreq = (
320
+ self.AC.cpr[0]
321
+ + self.AC.cpr[1] * pow(mu, 2)
322
+ + self.AC.cpr[2] * CT * sqrt(sqrt(pow(mu, 4) + pow(CT, 2)) - pow(mu, 2))
323
+ + self.AC.cpr[3] * pow(mu, 3)
324
+ + self.AC.cpr[4] * pow(CT, 2) * pow(mu, 3)
325
+ )
326
+
327
+ return CPreq
328
+
329
+ def Preq(self, tas, sigma, mass, phi=0.0):
330
+ """This function computes the power required
331
+
332
+ :param sigma: Normalised density [-].
333
+ :param tas: true airspeed (TAS) [m/s]
334
+ :param gamma: flight path angle [rad]
335
+ :param mass: aircraft mass [kg].
336
+ :param phi: bank angle [deg].
337
+ :type sigma: float.
338
+ :type tas: float.
339
+ :type gamma: float.
340
+ :type mass: float
341
+ :type phi: float
342
+ :returns: power required [W].
343
+ :rtype: float
344
+ """
345
+
346
+ # gamma = checkArgument('gamma', **kwargs)
347
+
348
+ rho = sigma * const.rho_0
349
+
350
+ # mu = self.mu(tas=tas,gamma=gamma)
351
+ mu = self.mu(tas=tas)
352
+ CT = self.CT(sigma=sigma, mass=mass, tas=tas, phi=phi)
353
+ CPreq = self.CPreq(mu=mu, CT=CT)
354
+ Preq = sigma * self.Const3(tas=tas) * CPreq
355
+
356
+ return Preq
357
+
358
+ def Peng_target(self, Preq, ROCD, mass, ESF, temp, DeltaTemp):
359
+ """This function computes the targeted required power
360
+
361
+ :param temp: atmoshpere temperature [K].
362
+ :param DeltaTemp: ISA temperature deviation [K].
363
+ :param ROCD: rate of climb [m s^-1]
364
+ :param mass: aircraft mass [kg].
365
+ :param Preq: required power [W].
366
+ :param ESF: energy share factor [-].
367
+ :type temp: float.
368
+ :type DeltaTemp: float.
369
+ :type ROCD: float.
370
+ :type mass: float.
371
+ :type Preq: float.
372
+ :type ESF: float.
373
+ :returns: Peng [W].
374
+ :rtype: float
375
+ """
376
+
377
+ temp_const = temp / (temp - DeltaTemp)
378
+ Peng_target = (ROCD / ESF) * mass * const.g * temp_const + Preq
379
+
380
+ return Peng_target
381
+
382
+ def Vocbat(self, SOC):
383
+ """This function computes the battery open-circuit voltage
384
+
385
+ :param SOC: State of charge [%]
386
+ :type SOC: float.
387
+ :return: battery open-circuit voltage [V].
388
+ :rtype: float.
389
+ """
390
+
391
+ # avoid computations with negative SOC
392
+ if SOC <= 0:
393
+ vocBat = float("Nan")
394
+ else:
395
+ vocBat = (
396
+ self.AC.cVoc[0]
397
+ + self.AC.cVoc[1] * pow(SOC, self.AC.cVoc[2])
398
+ + self.AC.cVoc[3] * SOC / (SOC + 0.1)
399
+ + self.AC.cVoc[4] / (100.1 - SOC)
400
+ )
401
+
402
+ return max(vocBat, 0)
403
+
404
+ def Vbat(self, SOC, I):
405
+ """This function computes the battery voltage
406
+
407
+ :param I: required current [A] .
408
+ :param SOC: State of charge [%]
409
+ :type I: float.
410
+ :type SOC: float.
411
+ :return: battery voltage [V].
412
+ :rtype: float.
413
+ """
414
+
415
+ vBat = self.Vocbat(SOC=SOC) - self.Rtbat(SOC=SOC) * I
416
+
417
+ return vBat
418
+
419
+ def Pbat_fromCurrent(self, SOC, I):
420
+ """This function computes battery power delivered with current I
421
+
422
+ :param I: required current [A] .
423
+ :param SOC: State of charge [%]
424
+ :type I: float.
425
+ :type SOC: float.
426
+ :return: battery power [W].
427
+ :rtype: float.
428
+ """
429
+
430
+ Vbat = self.Vbat(SOC=SOC, I=I)
431
+ Pbat = Vbat * I
432
+
433
+ return Pbat
434
+
435
+ def Ibat(self, SOC, P):
436
+ """This function computes battery current required to deliver electrical power P
437
+
438
+ :param P: electrical power [W] .
439
+ :param SOC: State of charge [%]
440
+ :type P: float.
441
+ :type SOC: float.
442
+ :return: battery current required [A].
443
+ :rtype: float.
444
+ """
445
+
446
+ # avoid computations with negative SOC
447
+ if SOC <= 0:
448
+ Ibat = float("Nan")
449
+ else:
450
+ V0 = self.Vocbat(SOC=SOC)
451
+ Rt = self.Rtbat(SOC=SOC)
452
+
453
+ if (V0 * V0 - 4 * P * Rt) < 0:
454
+ Ibat = float("Nan")
455
+ else:
456
+ Ibat = (V0 - sqrt(V0 * V0 - 4 * P * Rt)) / (2 * Rt)
457
+
458
+ return Ibat
459
+
460
+ def PbatLoss_fromCurrent(self, SOC, I):
461
+ """This function computes power loss in the battery when delivering current I
462
+
463
+ :param I: required current [A].
464
+ :param SOC: State of charge [%]
465
+ :type I: float.
466
+ :type SOC: float.
467
+ :return: battery power loss [W].
468
+ :rtype: float.
469
+ """
470
+
471
+ # NB: not clear why, but the Vahana software computes the losses using only second internal resistance rather than the total one
472
+ PLoss = self.Ribat(SOC=SOC) * I * I
473
+
474
+ return PLoss
475
+
476
+ def PbatLoss(self, SOC, P):
477
+ """This function computes power loss in the battery when delivering electrical power P
478
+
479
+ :param P: electrical power [W] .
480
+ :param SOC: State of charge [%]
481
+ :type P: float.
482
+ :type SOC: float.
483
+ :return: battery power loss [W].
484
+ :rtype: float.
485
+ """
486
+
487
+ I = self.Ibat(P=P, SOC=SOC)
488
+ PbatLoss = self.PbatLoss_fromCurrent(I=I, SOC=SOC)
489
+
490
+ return PbatLoss
491
+
492
+ def Ribat(self, SOC):
493
+ """This function computes battery second internal resistance
494
+
495
+ :param SOC: State of charge [%]
496
+ :type SOC: float.
497
+ :return: battery second internal resistance [ohm].
498
+ :rtype: float.
499
+ """
500
+
501
+ Ri = self.AC.cRi[0] + self.AC.cRi[1] * SOC + self.AC.cRi[2] * pow(SOC, 2)
502
+
503
+ return Ri
504
+
505
+ def R0bat(self, SOC):
506
+ """This function computes battery first internal resistance
507
+
508
+ :param SOC: State of charge [%]
509
+ :type SOC: float.
510
+ :return: battery first internal resistance [ohm].
511
+ :rtype: float.
512
+ """
513
+
514
+ R0bat = (
515
+ self.AC.cR0[0]
516
+ + self.AC.cR0[1] * pow(SOC, self.AC.cR0[2])
517
+ + self.AC.cR0[3] * SOC / (SOC + 0.1)
518
+ + self.AC.cR0[4] / (100.1 - SOC)
519
+ )
520
+
521
+ return R0bat
522
+
523
+ def Rtbat(self, SOC):
524
+ """This function computes battery total internal resistance
525
+
526
+ :param SOC: State of charge [%]
527
+ :type SOC: float.
528
+ :return: battery total internal resistance [ohm].
529
+ :rtype: float.
530
+ """
531
+
532
+ Rtbat = self.R0bat(SOC=SOC) + self.Ribat(SOC=SOC)
533
+
534
+ return Rtbat
535
+
536
+ def PavBat(self, SOC):
537
+ """This function computes electrical power available from the battery
538
+
539
+ :param SOC: State of charge [%]
540
+ :type SOC: float.
541
+ :return: electrical power available [W].
542
+ :rtype: float.
543
+ """
544
+
545
+ # Compute the max power limit from Vmin
546
+ Pmax_V = (
547
+ self.AC.Vmin * (self.Vocbat(SOC=SOC) - self.AC.Vmin) / self.Rtbat(SOC=SOC)
548
+ )
549
+
550
+ # Compute the max power limit from Imax
551
+ Pmax_I = self.AC.Imax * self.Vbat(SOC=SOC, I=self.AC.Imax)
552
+
553
+ # Compute most limiting value
554
+ Pmax = max(Pmax_V, Pmax_I)
555
+
556
+ # Replace negative values by zeros
557
+ PavBat = max(Pmax, 0)
558
+
559
+ return PavBat
560
+
561
+ def PavEng(self, SOC):
562
+ """This function computes mechanical power available from the engine
563
+
564
+ :param SOC: State of charge [%]
565
+ :type SOC: float.
566
+ :return: mechanical power available [W].
567
+ :rtype: float.
568
+ """
569
+
570
+ Pavbat = self.PavBat(SOC=SOC)
571
+ PavEng = self.AC.eta * Pavbat
572
+
573
+ return PavEng
574
+
575
+ def Pbat(self, SOC, Preq):
576
+ """This function computes total power consumption in the battery
577
+
578
+ :param SOC: State of charge [%]
579
+ :param Preq: mechanical Power required [W]
580
+ :type SOC: float.
581
+ :type Preq: float.
582
+ :return: power consumption [W].
583
+ :rtype: float.
584
+ """
585
+
586
+ # compute electrical power
587
+ Pelec = Preq / self.AC.eta
588
+ # compute teh losses in the battery
589
+ Ploss = self.PbatLoss(P=Pelec, SOC=SOC)
590
+
591
+ Pbat = Pelec + Ploss
592
+
593
+ return Pbat
594
+
595
+ def SOCrate(self, SOC, Preq):
596
+ """This function computes SOC rate (SOC = State of charge)
597
+
598
+ :param SOC: State of charge [%]
599
+ :param Preq: mechanical Power required [W]
600
+ :type SOC: float.
601
+ :type Preq: float.
602
+ :return: discharge rate [%/h].
603
+ :rtype: float.
604
+ """
605
+
606
+ Pbat = self.Pbat(Preq=Preq, SOC=SOC)
607
+ SOCrate = 100 * Pbat / self.AC.capacity
608
+
609
+ return SOCrate
610
+
611
+ def CPav(self, SOC):
612
+ """This function computes the power available coefficient
613
+
614
+ :param SOC: State of charge [%]
615
+ :type SOC: float.
616
+ :return: power available coefficient.
617
+ :rtype: [-].
618
+ """
619
+
620
+ Pav = self.PavEng(SOC=SOC)
621
+ CPav = Pav / self.Const3(tas=0.0)
622
+
623
+ return CPav
624
+
625
+ def Pmax(self, rating):
626
+ """This function computes the maximum all-engine power [W]
627
+
628
+ :param rating: throttle setting {MTKF,MCNT}.
629
+ :type rating: str.
630
+ :return: maximum all-engine power.
631
+ :rtype: float.
632
+ :raise: ValueError.
633
+ """
634
+
635
+ if rating not in self.AC.Pmax_.keys():
636
+ raise ValueError("Unknown engine rating " + rating)
637
+ return self.AC.Pmax_[rating]
638
+
639
+ def Pav(self, rating, SOC=100.0):
640
+ """This function computes the power available
641
+
642
+ :param rating: throttle setting {MTKF,MCNT}.
643
+ :param SOC: State of charge [%]
644
+ :type rating: str.
645
+ :type SOC: float.
646
+ :return: power available.
647
+ :rtype: [W].
648
+ :raise: ValueError.
649
+ """
650
+
651
+ Pmax = self.Pmax(rating=rating)
652
+ CPav = self.CPav(SOC=SOC)
653
+ Pav = min(Pmax, self.Const3(tas=0.0) * CPav)
654
+
655
+ return Pav
656
+
657
+ def Q(self, Peng):
658
+ """This function computes the torque value (expressed in percentage of of a reference torque)
659
+
660
+ :param Peng: all-engine power [W].
661
+ :type Peng: float.
662
+ :return: torque value [%].
663
+ :rtype: float.
664
+ """
665
+
666
+ Q = Peng / self.AC.P0
667
+
668
+ return Q
669
+
670
+ def ff(self):
671
+ """This function computes the fuel flow
672
+
673
+ :return fuel flow [kg/s]
674
+ :rtype float
675
+ """
676
+
677
+ if self.AC.type == "ELECTRIC":
678
+ ff = 0.0
679
+ else:
680
+ raise ValueError("Unknown engine type")
681
+
682
+ return ff
683
+
684
+ def ROCD(self, Peng, Preq, mass, ESF, theta, DeltaTemp):
685
+ """This function computes the Rate of Climb or Descent
686
+
687
+ :param theta: normalised temperature [-].
688
+ :param Peng: Peng: all-engine power [W].
689
+ :param Preq: power required [W].
690
+ :param mass: actual aircraft mass [kg].
691
+ :param ESF: energy share factor [-].
692
+ :param DeltaTemp: deviation with respect to ISA [K]
693
+ :type theta: float.
694
+ :type Peng: float.
695
+ :type Preq: float.
696
+ :type mass: float.
697
+ :type ESF: float.
698
+ :type DeltaTemp: float.
699
+ :returns: rate of climb/descend [m/s].
700
+ :rtype: float
701
+ """
702
+
703
+ temp = theta * const.temp_0
704
+ ROCD = ((temp - DeltaTemp) / temp) * (Peng - Preq) * ESF / (mass * const.g)
705
+
706
+ return ROCD
707
+
708
+
709
+ class FlightEnvelope(BADAE):
710
+ """This class is a BADAE aircraft subclass and implements the flight envelope caclulations
711
+ following the BADAE manual.
712
+
713
+ :param AC: parsed aircraft.
714
+ :type AC: badaE.Parse.
715
+ """
716
+
717
+ def __init__(self, AC):
718
+ BADAE.__init__(self, AC)
719
+
720
+ def maxAltitude(self):
721
+ """This function computes the maximum altitude
722
+
723
+ :returns: maximum altitude [m].
724
+ :rtype: float
725
+ """
726
+
727
+ hMax = conv.ft2m(self.AC.hmo)
728
+ return hMax
729
+
730
+ def VMax(self):
731
+ """This function computes the maximum speed
732
+
733
+ :returns: maximum CAS speed [m s^-1].
734
+ :rtype: float.
735
+ """
736
+
737
+ Vmax = conv.kt2ms(self.AC.vne)
738
+ return Vmax
739
+
740
+
741
+ class Optimization(BADAE):
742
+ """This class implements the BADAE optimization following the BADAE manual.
743
+
744
+ :param AC: parsed aircraft.
745
+ :type AC: badaE.Parse.
746
+ """
747
+
748
+ def __init__(self, AC):
749
+ BADAE.__init__(self, AC)
750
+ self.flightEnvelope = FlightEnvelope(AC)
751
+
752
+ def MRC(self, h, mass, DeltaTemp, wS):
753
+ """This function computes the TAS reperesenting Maximum Range Cruise (MRC) for given flight conditions
754
+
755
+ :param h: altitude [m].
756
+ :param mass: aircraft weight [kg].
757
+ :param DeltaTemp: deviation with respect to ISA [K]
758
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
759
+ :type h: float.
760
+ :type mass: float.
761
+ :type DeltaTemp: float.
762
+ :type wS: float.
763
+ :returns: Maximum Range Cruise (MRC) in TAS [m s^-1]
764
+ :rtype: float.
765
+ """
766
+
767
+ MRC = float("Nan")
768
+
769
+ return MRC
770
+
771
+ def LRC(self, h, mass, DeltaTemp, wS):
772
+ """This function computes the TAS reperesenting Long Range Cruise (LRC) for given flight conditions
773
+
774
+ :param h: altitude [m].
775
+ :param mass: aircraft weight [kg].
776
+ :param DeltaTemp: deviation with respect to ISA [K]
777
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
778
+ :type h: float.
779
+ :type mass: float.
780
+ :type DeltaTemp: float.
781
+ :type wS: float.
782
+ :returns: Long Range Cruise (LRC) in M [-]
783
+ :rtype: float.
784
+ """
785
+
786
+ LRC = float("Nan")
787
+
788
+ return LRC
789
+
790
+ def MEC(self, h, mass, DeltaTemp, wS):
791
+ """This function computes the TAS reperesenting Maximum Endurance Cruise (MEC) for given flight conditions
792
+
793
+ :param h: altitude [m].
794
+ :param mass: aircraft weight [kg].
795
+ :param DeltaTemp: deviation with respect to ISA [K]
796
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
797
+ :type h: float.
798
+ :type mass: float.
799
+ :type DeltaTemp: float.
800
+ :type wS: float.
801
+ :returns: Maximum Endurance Cruise (MEC) in TAS [m s^-1]
802
+ :rtype: float.
803
+ """
804
+
805
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
806
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
807
+ sigma = atm.sigma(theta=theta, delta=delta)
808
+
809
+ # max TAS speed limitation
810
+ Vmax = atm.cas2Tas(cas=self.flightEnvelope.VMax(), delta=delta, sigma=sigma)
811
+
812
+ def f(TAS):
813
+ Preq = self.Preq(sigma=sigma, tas=TAS[0], mass=mass)
814
+
815
+ # minimize Preq -> const function
816
+ return Preq
817
+
818
+ epsilon = 0.01
819
+ mec = float(
820
+ fminbound(f, x1=np.array([0]), x2=np.array([Vmax + epsilon]), disp=False)
821
+ )
822
+
823
+ return mec
824
+
825
+ def parseOPT(self, filename):
826
+ """This function parses BADA4 OPT ascii formatted files
827
+
828
+ :param filename: path to the ___.OPT ascii formatted file.
829
+ :type filename: str.
830
+ :returns: dictionary of Delta temperature and data from the OPT file for that delta temperature [kt]
831
+ :rtype: dict.
832
+ """
833
+
834
+ file = open(filename, "r")
835
+ lines = file.readlines()
836
+
837
+ DeltaTempPos = {}
838
+
839
+ # create a dictionary for list of DeltaTemp available in OPT file mapped to the line number in the file
840
+ for k in range(len(lines)):
841
+ line = lines[k]
842
+ if "DeltaT:" in line:
843
+ DeltaTempPos[int(line.split(":")[1].strip())] = k
844
+
845
+ self.tableTypes = lines[7].split(":")[1].strip()
846
+ self.tableDimension = lines[9].split(":")[1].strip()
847
+
848
+ DeltaTempDict = {}
849
+
850
+ if self.tableTypes == "3D":
851
+ self.tableDimensionColumns = int(self.tableDimension.split("x")[2])
852
+ self.tableDimensionRows = int(self.tableDimension.split("x")[1])
853
+ self.DeltaTempNum = int(self.tableDimension.split("x")[0])
854
+
855
+ for DeltaTemp in DeltaTempPos:
856
+ var_1 = []
857
+ var_2 = []
858
+ var_3 = []
859
+
860
+ startIdx = DeltaTempPos[DeltaTemp] + 1
861
+ var_2 = [
862
+ float(i)
863
+ for i in list(
864
+ filter(None, lines[startIdx].split("|")[1].strip().split(" "))
865
+ )
866
+ ]
867
+
868
+ for j in range(startIdx + 3, startIdx + 3 + self.tableDimensionRows, 1):
869
+ var_1.append(float(lines[j].split("|")[0].strip()))
870
+
871
+ str_list = list(
872
+ filter(None, lines[j].split("|")[1].strip().split(" "))
873
+ )
874
+ for k in range(len(str_list)):
875
+ if str_list[k] == "-":
876
+ str_list[k] = float("Nan")
877
+
878
+ var_3.extend([float(i) for i in str_list])
879
+
880
+ DeltaTempDict[DeltaTemp] = [var_1, var_2, var_3]
881
+
882
+ return DeltaTempDict
883
+
884
+ def findNearestIdx(self, value, array):
885
+ """This function returns indices of the nearest value in the array.
886
+ if the value is lower/higher than lowest/highest value in array, only one idx is returned
887
+ otherwise if the value is somewhere in between, 2 closest (left and right) idx are returned
888
+
889
+ .. note::
890
+ array used in this fuction is expected to be sorted (design of OPT files)
891
+
892
+ :param value: value to which the array value will be comapred
893
+ :param array: list of values
894
+ :type value: float.
895
+ :type array: array of float.
896
+ :returns: nearest indices
897
+ :rtype: list[float].
898
+ """
899
+
900
+ nearestIdx = list()
901
+
902
+ idx = np.searchsorted(array, value, side="left")
903
+
904
+ if idx == len(array):
905
+ nearestIdx = idx - 1
906
+
907
+ elif idx == 0 or value == array[idx]:
908
+ nearestIdx = idx
909
+
910
+ elif value < array[idx] or value > array[idx]:
911
+ nearestIdx = [idx - 1, idx]
912
+
913
+ return nearestIdx
914
+
915
+ def calculateOPTparam(self, var_1, var_2, detaTauList):
916
+ """This function calculate the OPT value by either selecting the existing value, or interpolating between closest 2 values
917
+
918
+ .. note::array used in this fuction is expected to be sorted (design of OPT files)
919
+
920
+ :param var_1: value of the first optimizing factor.
921
+ :param var_2: value of the second optimizing factor.
922
+ :param detaTauList: list of values belonging to specified DeltaTemp from OPT file.
923
+ :type var_1: float.
924
+ :type var_2: float.
925
+ :type detaTauList: list[float].
926
+ """
927
+
928
+ var_1_list = detaTauList[0]
929
+ var_2_list = detaTauList[1]
930
+ var_3_list = detaTauList[2]
931
+
932
+ nearestIdx_1 = np.array(self.findNearestIdx(var_1, var_1_list))
933
+ nearestIdx_2 = np.array(self.findNearestIdx(var_2, var_2_list))
934
+
935
+ # if nearestIdx_1 & nearestIdx_2 [1] [1]
936
+ if (nearestIdx_1.size == 1) & (nearestIdx_2.size == 1):
937
+ return var_3_list[
938
+ nearestIdx_1 * (self.tableDimensionColumns) + nearestIdx_2
939
+ ]
940
+
941
+ # if nearestIdx_1 & nearestIdx_2 [1] [1,2]
942
+ if (nearestIdx_1.size == 1) & (nearestIdx_2.size == 2):
943
+ varTemp_1 = var_3_list[
944
+ nearestIdx_1 * (self.tableDimensionColumns) + nearestIdx_2[0]
945
+ ]
946
+ varTemp_2 = var_3_list[
947
+ nearestIdx_1 * (self.tableDimensionColumns) + nearestIdx_2[1]
948
+ ]
949
+
950
+ # interpolation between the 2 found points
951
+ interpVar = np.interp(
952
+ var_2,
953
+ [var_2_list[nearestIdx_2[0]], var_2_list[nearestIdx_2[1]]],
954
+ [varTemp_1, varTemp_2],
955
+ )
956
+ return interpVar
957
+
958
+ # if nearestIdx_1 & nearestIdx_2 [1,2] [1]
959
+ if (nearestIdx_1.size == 2) & (nearestIdx_2.size == 1):
960
+ varTemp_1 = var_3_list[
961
+ nearestIdx_1[0] * (self.tableDimensionColumns) + nearestIdx_2
962
+ ]
963
+ varTemp_2 = var_3_list[
964
+ nearestIdx_1[1] * (self.tableDimensionColumns) + nearestIdx_2
965
+ ]
966
+
967
+ # interpolation between the 2 found points
968
+ interpVar = np.interp(
969
+ var_1,
970
+ [var_1_list[nearestIdx_1[0]], var_1_list[nearestIdx_1[1]]],
971
+ [varTemp_1, varTemp_2],
972
+ )
973
+ return interpVar
974
+
975
+ # if nearestIdx_1 & nearestIdx_2 [1,2] [1,2]
976
+ if (nearestIdx_1.size == 2) & (nearestIdx_2.size == 2):
977
+ varTemp_1 = var_3_list[
978
+ nearestIdx_1[0] * (self.tableDimensionColumns) + nearestIdx_2[0]
979
+ ]
980
+ varTemp_2 = var_3_list[
981
+ nearestIdx_1[0] * (self.tableDimensionColumns) + nearestIdx_2[1]
982
+ ]
983
+
984
+ varTemp_3 = var_3_list[
985
+ nearestIdx_1[1] * (self.tableDimensionColumns) + nearestIdx_2[0]
986
+ ]
987
+ varTemp_4 = var_3_list[
988
+ nearestIdx_1[1] * (self.tableDimensionColumns) + nearestIdx_2[1]
989
+ ]
990
+
991
+ # interpolation between the 4 found points
992
+ interpVar_1 = np.interp(
993
+ var_2,
994
+ [var_2_list[nearestIdx_2[0]], var_2_list[nearestIdx_2[1]]],
995
+ [varTemp_1, varTemp_2],
996
+ )
997
+ interpVar_2 = np.interp(
998
+ var_2,
999
+ [var_2_list[nearestIdx_2[0]], var_2_list[nearestIdx_2[1]]],
1000
+ [varTemp_3, varTemp_4],
1001
+ )
1002
+ interpVar_3 = np.interp(
1003
+ var_1,
1004
+ [var_1_list[nearestIdx_1[0]], var_1_list[nearestIdx_1[1]]],
1005
+ [interpVar_1, interpVar_2],
1006
+ )
1007
+
1008
+ return interpVar_3
1009
+
1010
+ def getOPTParam(self, optParam, var_1, var_2, DeltaTemp):
1011
+ """This function returns value of the OPT parameter based on the input value from OPT file
1012
+ like LRC, MEC, MRC
1013
+
1014
+ .. note::
1015
+ array used in this fuction is expected to be sorted (design of BADA OPT files)
1016
+
1017
+ :param optParam: name of optimization file {LRC,MEC,MRC}.
1018
+ :param var_1: value of the first optimizing factor.
1019
+ :param var_2: value of the second optimizing factor.
1020
+ :param DeltaTemp: deviation with respect to ISA [K]
1021
+ :type optParam: string.
1022
+ :type var_1: float.
1023
+ :type var_2: float.
1024
+ :type DeltaTemp: float.
1025
+ """
1026
+
1027
+ filename = self.AC.filePath + "/" + self.AC.acName + "/" + optParam + ".OPT"
1028
+ detaTauDict = self.parseOPT(filename=filename)
1029
+
1030
+ if DeltaTemp in detaTauDict:
1031
+ # value of DeltaTemp exist in the OPT file
1032
+ optVal = self.calculateOPTparam(var_1, var_2, detaTauDict[DeltaTemp])
1033
+ else:
1034
+ # value of DeltaTemp does not exist in OPT file - will be interpolated. But only within the range of <-20;20>
1035
+ nearestIdx = np.array(self.findNearestIdx(DeltaTemp, list(detaTauDict)))
1036
+
1037
+ if nearestIdx.size == 1:
1038
+ # DeltaTemp value is either outside of the <-20;20> DeltaTemp range
1039
+ DeltaTemp_new = list(detaTauDict)[nearestIdx]
1040
+ optVal = self.calculateOPTparam(
1041
+ var_1, var_2, detaTauDict[DeltaTemp_new]
1042
+ )
1043
+ else:
1044
+ # DeltaTemp value is within the <-20;20> DeltaTemp range
1045
+ # calculate the interpolation between 2 closest DeltaTemp values from the OPT file
1046
+ DeltaTemp_new_1 = list(detaTauDict)[nearestIdx[0]]
1047
+ DeltaTemp_new_2 = list(detaTauDict)[nearestIdx[1]]
1048
+
1049
+ optVal_1 = self.calculateOPTparam(
1050
+ var_1, var_2, detaTauDict[DeltaTemp_new_1]
1051
+ )
1052
+ optVal_2 = self.calculateOPTparam(
1053
+ var_1, var_2, detaTauDict[DeltaTemp_new_2]
1054
+ )
1055
+
1056
+ optVal = np.interp(
1057
+ DeltaTemp, [DeltaTemp_new_1, DeltaTemp_new_2], [optVal_1, optVal_2]
1058
+ )
1059
+
1060
+ return optVal
1061
+
1062
+
1063
+ class ARPM(BADAE):
1064
+ """This class is a BADAE aircraft subclass and implements the Airline Procedure Model (ARPM)
1065
+ following the BADAE user manual.
1066
+
1067
+ :param AC: parsed aircraft.
1068
+ :type AC: badaE.Parse.
1069
+ """
1070
+
1071
+ def __init__(self, AC):
1072
+ BADAE.__init__(self, AC)
1073
+ self.flightEnvelope = FlightEnvelope(AC)
1074
+ self.OPT = Optimization(AC)
1075
+
1076
+ def takeoff(self, h, mass, DeltaTemp, rating="ARPM", speedLimit=None):
1077
+ """This function computes parameters for the takeoff ARPM
1078
+
1079
+ :param h: altitude [m].
1080
+ :param mass: aircraft weight [kg].
1081
+ :param DeltaTemp: deviation with respect to ISA [K]
1082
+ :param rating: engine rating {MTKF,MCNT,ARPM} [-].
1083
+ :param speedLimit: decision to apply or not the speed limit {applyLimit,''} [-].
1084
+ :type h: float.
1085
+ :type mass: float.
1086
+ :type DeltaTemp: float.
1087
+ :type rating: string.
1088
+ :type speedLimit: string.
1089
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1090
+ :rtype: float.
1091
+ """
1092
+
1093
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1094
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
1095
+ sigma = atm.sigma(theta=theta, delta=delta)
1096
+
1097
+ temp = theta * const.temp_0
1098
+
1099
+ # control parameters
1100
+ tas = 0
1101
+ ROCD = conv.ft2m(100) / 60 # [m/s]
1102
+
1103
+ # check for speed envelope limitations
1104
+ eps = 1e-6 # float calculation precision
1105
+ maxSpeed = atm.cas2Tas(cas=self.flightEnvelope.VMax(), delta=delta, sigma=sigma)
1106
+ minSpeed = 0
1107
+ limitation = ""
1108
+
1109
+ # empty envelope - keep the original calculated TAS speed
1110
+ if maxSpeed < minSpeed:
1111
+ if (tas - eps) > maxSpeed and (tas - eps) > minSpeed:
1112
+ limitation = "V"
1113
+ elif (tas + eps) < minSpeed and (tas + eps) < maxSpeed:
1114
+ limitation = "v"
1115
+ else:
1116
+ limitation = "vV"
1117
+
1118
+ elif minSpeed > (tas + eps):
1119
+ if speedLimit == "applyLimit":
1120
+ tas = minSpeed
1121
+ limitation = "C"
1122
+ else:
1123
+ limitation = "v"
1124
+
1125
+ elif maxSpeed < (tas - eps):
1126
+ if speedLimit == "applyLimit":
1127
+ tas = maxSpeed
1128
+ limitation = "C"
1129
+ else:
1130
+ limitation = "V"
1131
+
1132
+ ESF = self.esf(flightEvolution="constTAS")
1133
+
1134
+ Preq = self.Preq(sigma=sigma, tas=tas, mass=mass)
1135
+
1136
+ if rating == "ARPM":
1137
+ Peng_target = self.Peng_target(
1138
+ temp=temp, DeltaTemp=DeltaTemp, ROCD=ROCD, mass=mass, Preq=Preq, ESF=ESF
1139
+ )
1140
+ Pav = self.Pav(rating="MTKF")
1141
+ Peng = min(Peng_target, Pav)
1142
+
1143
+ ROCD_TEM = self.ROCD(
1144
+ Peng=Peng,
1145
+ Preq=Preq,
1146
+ mass=mass,
1147
+ ESF=ESF,
1148
+ theta=theta,
1149
+ DeltaTemp=DeltaTemp,
1150
+ )
1151
+
1152
+ if ROCD_TEM < ROCD:
1153
+ ROCD = ROCD_TEM
1154
+
1155
+ elif rating == "MTKF":
1156
+ Pav = self.Pav(rating="MTKF")
1157
+ Peng = Pav
1158
+ ROCD = self.ROCD(
1159
+ Peng=Peng,
1160
+ Preq=Preq,
1161
+ mass=mass,
1162
+ ESF=ESF,
1163
+ theta=theta,
1164
+ DeltaTemp=DeltaTemp,
1165
+ )
1166
+
1167
+ elif rating == "MCNT":
1168
+ Pav = self.Pav(rating="MCNT")
1169
+ Peng = Pav
1170
+ ROCD = self.ROCD(
1171
+ Peng=Peng,
1172
+ Preq=Preq,
1173
+ mass=mass,
1174
+ ESF=ESF,
1175
+ theta=theta,
1176
+ DeltaTemp=DeltaTemp,
1177
+ )
1178
+
1179
+ if Pav < Peng:
1180
+ limitation += "P"
1181
+
1182
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1183
+
1184
+ def accelerationToClimb(self):
1185
+ pass
1186
+
1187
+ def climb(self, h, mass, DeltaTemp, rating="ARPM", speedLimit=None):
1188
+ """This function computes parameters for the climb ARPM
1189
+
1190
+ :param h: altitude [m].
1191
+ :param mass: aircraft weight [kg].
1192
+ :param DeltaTemp: deviation with respect to ISA [K]
1193
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
1194
+ :param rating: engine rating {MTKF,MCNT,ARPM} [-].
1195
+ :param speedLimit: decision to apply or not the speed limit {applyLimit,''} [-].
1196
+ :type h: float.
1197
+ :type mass: float.
1198
+ :type DeltaTemp: float.
1199
+ :type wS: float.
1200
+ :type rating: string.
1201
+ :type speedLimit: string.
1202
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1203
+ :rtype: float.
1204
+ """
1205
+
1206
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1207
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
1208
+ sigma = atm.sigma(theta=theta, delta=delta)
1209
+
1210
+ temp = theta * const.temp_0
1211
+
1212
+ MEC = self.OPT.MEC(mass=mass, h=h, DeltaTemp=DeltaTemp, wS=0)
1213
+ # MEC = conv.kt2ms(self.OPT.getOPTParam('MEC', conv.m2ft(h), mass, DeltaTemp))
1214
+
1215
+ # control parameters
1216
+ tas = MEC
1217
+ ROCD = conv.ft2m(1000) / 60 # [m/s]
1218
+
1219
+ # check for speed envelope limitations
1220
+ eps = 1e-6 # float calculation precision
1221
+ maxSpeed = atm.cas2Tas(cas=self.flightEnvelope.VMax(), delta=delta, sigma=sigma)
1222
+ minSpeed = 0
1223
+ limitation = ""
1224
+
1225
+ # empty envelope - keep the original calculated TAS speed
1226
+ if maxSpeed < minSpeed:
1227
+ if (tas - eps) > maxSpeed and (tas - eps) > minSpeed:
1228
+ limitation = "V"
1229
+ elif (tas + eps) < minSpeed and (tas + eps) < maxSpeed:
1230
+ limitation = "v"
1231
+ else:
1232
+ limitation = "vV"
1233
+
1234
+ elif minSpeed > (tas + eps):
1235
+ if speedLimit == "applyLimit":
1236
+ tas = minSpeed
1237
+ limitation = "C"
1238
+ else:
1239
+ limitation = "v"
1240
+
1241
+ elif maxSpeed < (tas - eps):
1242
+ if speedLimit == "applyLimit":
1243
+ tas = maxSpeed
1244
+ limitation = "C"
1245
+ else:
1246
+ limitation = "V"
1247
+
1248
+ ESF = self.esf(flightEvolution="constTAS")
1249
+ Preq = self.Preq(sigma=sigma, tas=tas, mass=mass)
1250
+
1251
+ if rating == "ARPM":
1252
+ Peng_target = self.Peng_target(
1253
+ temp=temp, DeltaTemp=DeltaTemp, ROCD=ROCD, mass=mass, Preq=Preq, ESF=ESF
1254
+ )
1255
+ Pav = self.Pav(rating="MTKF")
1256
+ Peng = min(Peng_target, Pav)
1257
+
1258
+ ROCD_TEM = self.ROCD(
1259
+ Peng=Peng,
1260
+ Preq=Preq,
1261
+ mass=mass,
1262
+ ESF=ESF,
1263
+ theta=theta,
1264
+ DeltaTemp=DeltaTemp,
1265
+ )
1266
+
1267
+ if ROCD_TEM < ROCD:
1268
+ ROCD = ROCD_TEM
1269
+
1270
+ elif rating == "MTKF":
1271
+ Pav = self.Pav(rating="MTKF")
1272
+ Peng = Pav
1273
+ ROCD = self.ROCD(
1274
+ Peng=Peng,
1275
+ Preq=Preq,
1276
+ mass=mass,
1277
+ ESF=ESF,
1278
+ theta=theta,
1279
+ DeltaTemp=DeltaTemp,
1280
+ )
1281
+
1282
+ elif rating == "MCNT":
1283
+ Pav = self.Pav(rating="MCNT")
1284
+ Peng = Pav
1285
+ ROCD = self.ROCD(
1286
+ Peng=Peng,
1287
+ Preq=Preq,
1288
+ mass=mass,
1289
+ ESF=ESF,
1290
+ theta=theta,
1291
+ DeltaTemp=DeltaTemp,
1292
+ )
1293
+
1294
+ if Pav < Peng:
1295
+ limitation += "P"
1296
+
1297
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1298
+
1299
+ def accelerationToCruise(self):
1300
+ pass
1301
+
1302
+ def cruise(self, h, mass, DeltaTemp, speedLimit=None):
1303
+ """This function computes parameters for the cruise ARPM
1304
+
1305
+ :param h: altitude [m].
1306
+ :param mass: aircraft weight [kg].
1307
+ :param DeltaTemp: deviation with respect to ISA [K]
1308
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
1309
+ :param speedLimit: decision to apply or not the speed limit {applyLimit,''} [-].
1310
+ :type h: float.
1311
+ :type mass: float.
1312
+ :type DeltaTemp: float.
1313
+ :type wS: float.
1314
+ :type speedLimit: string.
1315
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1316
+ :rtype: float.
1317
+ """
1318
+
1319
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1320
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
1321
+ sigma = atm.sigma(theta=theta, delta=delta)
1322
+
1323
+ # LRC = self.OPT.LRC(mass=mass, h=h, DeltaTemp=DeltaTemp, wS=0)
1324
+ # LRC = conv.kt2ms(self.OPT.getOPTParam('LRC', conv.m2ft(h), mass, DeltaTemp))
1325
+ MEC = self.OPT.MEC(mass=mass, h=h, DeltaTemp=DeltaTemp, wS=0)
1326
+ # MEC = conv.kt2ms(self.OPT.getOPTParam('MEC', conv.m2ft(h), mass, DeltaTemp))
1327
+
1328
+ # control parameters
1329
+ tas = MEC
1330
+ ROCD = 0 # [m/s]
1331
+
1332
+ # check for speed envelope limitations
1333
+ eps = 1e-6 # float calculation precision
1334
+ maxSpeed = atm.cas2Tas(cas=self.flightEnvelope.VMax(), delta=delta, sigma=sigma)
1335
+ minSpeed = 0
1336
+ limitation = ""
1337
+
1338
+ # empty envelope - keep the original calculated TAS speed
1339
+ if maxSpeed < minSpeed:
1340
+ if (tas - eps) > maxSpeed and (tas - eps) > minSpeed:
1341
+ limitation = "V"
1342
+ elif (tas + eps) < minSpeed and (tas + eps) < maxSpeed:
1343
+ limitation = "v"
1344
+ else:
1345
+ limitation = "vV"
1346
+
1347
+ elif minSpeed > (tas + eps):
1348
+ if speedLimit == "applyLimit":
1349
+ tas = minSpeed
1350
+ limitation = "C"
1351
+ else:
1352
+ limitation = "v"
1353
+
1354
+ elif maxSpeed < (tas - eps):
1355
+ if speedLimit == "applyLimit":
1356
+ tas = maxSpeed
1357
+ limitation = "C"
1358
+ else:
1359
+ limitation = "V"
1360
+
1361
+ # ESF is N/A for cruise
1362
+ ESF = 0
1363
+
1364
+ Preq = self.Preq(sigma=sigma, tas=tas, mass=mass)
1365
+ Pav = self.Pav(rating="MCNT")
1366
+ Peng = min(Preq, Pav)
1367
+
1368
+ if Pav < Peng:
1369
+ limitation += "P"
1370
+
1371
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1372
+
1373
+ def descent(self, h, mass, DeltaTemp, speedLimit=None):
1374
+ """This function computes parameters for the descent ARPM
1375
+
1376
+ :param h: altitude [m].
1377
+ :param mass: aircraft weight [kg].
1378
+ :param DeltaTemp: deviation with respect to ISA [K]
1379
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
1380
+ :param speedLimit: decision to apply or not the speed limit {applyLimit,''} [-].
1381
+ :type h: float.
1382
+ :type mass: float.
1383
+ :type DeltaTemp: float.
1384
+ :type wS: float.
1385
+ :type speedLimit: string.
1386
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1387
+ :rtype: float.
1388
+ """
1389
+
1390
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1391
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
1392
+ sigma = atm.sigma(theta=theta, delta=delta)
1393
+
1394
+ temp = theta * const.temp_0
1395
+
1396
+ # LRC = self.OPT.LRC(mass=mass, h=h, DeltaTemp=DeltaTemp, wS=0)
1397
+ # LRC = conv.kt2ms(self.OPT.getOPTParam('LRC', conv.m2ft(h), mass, DeltaTemp))
1398
+ MEC = self.OPT.MEC(mass=mass, h=h, DeltaTemp=DeltaTemp, wS=0)
1399
+ # MEC = conv.kt2ms(self.OPT.getOPTParam('MEC', conv.m2ft(h), mass, DeltaTemp))
1400
+
1401
+ # control parameters
1402
+ tas = MEC
1403
+ ROCD = conv.ft2m(-500) / 60 # [m/s]
1404
+
1405
+ # check for speed envelope limitations
1406
+ eps = 1e-6 # float calculation precision
1407
+ maxSpeed = atm.cas2Tas(cas=self.flightEnvelope.VMax(), delta=delta, sigma=sigma)
1408
+ minSpeed = 0
1409
+ limitation = ""
1410
+
1411
+ # empty envelope - keep the original calculated TAS speed
1412
+ if maxSpeed < minSpeed:
1413
+ if (tas - eps) > maxSpeed and (tas - eps) > minSpeed:
1414
+ limitation = "V"
1415
+ elif (tas + eps) < minSpeed and (tas + eps) < maxSpeed:
1416
+ limitation = "v"
1417
+ else:
1418
+ limitation = "vV"
1419
+
1420
+ elif minSpeed > (tas + eps):
1421
+ if speedLimit == "applyLimit":
1422
+ tas = minSpeed
1423
+ limitation = "C"
1424
+ else:
1425
+ limitation = "v"
1426
+
1427
+ elif maxSpeed < (tas - eps):
1428
+ if speedLimit == "applyLimit":
1429
+ tas = maxSpeed
1430
+ limitation = "C"
1431
+ else:
1432
+ limitation = "V"
1433
+
1434
+ ESF = self.esf(flightEvolution="constTAS")
1435
+
1436
+ Pav = Pav = self.Pav(
1437
+ rating="MTKF"
1438
+ ) # verify if Pav is calualted based on MTKF rating
1439
+ Preq = self.Preq(sigma=sigma, tas=tas, mass=mass)
1440
+ Peng_target = self.Peng_target(
1441
+ temp=temp, DeltaTemp=DeltaTemp, ROCD=ROCD, mass=mass, Preq=Preq, ESF=ESF
1442
+ )
1443
+ Peng = Peng_target
1444
+
1445
+ if Pav < Peng:
1446
+ limitation += "P"
1447
+
1448
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1449
+
1450
+ def decelerationToApproach(self):
1451
+ pass
1452
+
1453
+ def approach(self, h, mass, DeltaTemp, speedLimit=None):
1454
+ """This function computes parameters for the approach ARPM
1455
+
1456
+ :param h: altitude [m].
1457
+ :param mass: aircraft weight [kg].
1458
+ :param DeltaTemp: deviation with respect to ISA [K]
1459
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
1460
+ :param speedLimit: decision to apply or not the speed limit {applyLimit,''} [-].
1461
+ :type h: float.
1462
+ :type mass: float.
1463
+ :type DeltaTemp: float.
1464
+ :type wS: float.
1465
+ :type speedLimit: string.
1466
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1467
+ :rtype: float.
1468
+ """
1469
+
1470
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1471
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
1472
+ sigma = atm.sigma(theta=theta, delta=delta)
1473
+
1474
+ temp = theta * const.temp_0
1475
+
1476
+ MEC = self.OPT.MEC(mass=mass, h=h, DeltaTemp=DeltaTemp, wS=0)
1477
+ # MEC = conv.kt2ms(self.OPT.getOPTParam('MEC', conv.m2ft(h), mass, DeltaTemp))
1478
+
1479
+ # control parameters
1480
+ tas = MEC
1481
+ ROCD = conv.ft2m(-300) / 60 # [m/s]
1482
+
1483
+ # check for speed envelope limitations
1484
+ eps = 1e-6 # float calculation precision
1485
+ maxSpeed = atm.cas2Tas(cas=self.flightEnvelope.VMax(), delta=delta, sigma=sigma)
1486
+ minSpeed = 0
1487
+ limitation = ""
1488
+
1489
+ # empty envelope - keep the original calculated TAS speed
1490
+ if maxSpeed < minSpeed:
1491
+ if (tas - eps) > maxSpeed and (tas - eps) > minSpeed:
1492
+ limitation = "V"
1493
+ elif (tas + eps) < minSpeed and (tas + eps) < maxSpeed:
1494
+ limitation = "v"
1495
+ else:
1496
+ limitation = "vV"
1497
+
1498
+ elif minSpeed > (tas + eps):
1499
+ if speedLimit == "applyLimit":
1500
+ tas = minSpeed
1501
+ limitation = "C"
1502
+ else:
1503
+ limitation = "v"
1504
+
1505
+ elif maxSpeed < (tas - eps):
1506
+ if speedLimit == "applyLimit":
1507
+ tas = maxSpeed
1508
+ limitation = "C"
1509
+ else:
1510
+ limitation = "V"
1511
+
1512
+ ESF = self.esf(flightEvolution="constTAS")
1513
+
1514
+ Pav = Pav = self.Pav(
1515
+ rating="MTKF"
1516
+ ) # verify if Pav is calualted based on MTKF rating
1517
+ Preq = self.Preq(sigma=sigma, tas=tas, mass=mass)
1518
+ Peng_target = self.Peng_target(
1519
+ temp=temp, DeltaTemp=DeltaTemp, ROCD=ROCD, mass=mass, Preq=Preq, ESF=ESF
1520
+ )
1521
+ Peng = Peng_target
1522
+
1523
+ if Pav < Peng:
1524
+ limitation += "P"
1525
+
1526
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1527
+
1528
+ def decelerationToFinalApproach(self):
1529
+ pass
1530
+
1531
+ def finalApproach(self, h, mass, DeltaTemp, speedLimit=None):
1532
+ """This function computes parameters for the final approach ARPM
1533
+
1534
+ :param h: altitude [m].
1535
+ :param mass: aircraft weight [kg].
1536
+ :param DeltaTemp: deviation with respect to ISA [K]
1537
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
1538
+ :param speedLimit: decision to apply or not the speed limit {applyLimit,''} [-].
1539
+ :type h: float.
1540
+ :type mass: float.
1541
+ :type DeltaTemp: float.
1542
+ :type wS: float.
1543
+ :type speedLimit: string.
1544
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1545
+ :rtype: float.
1546
+ """
1547
+
1548
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1549
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
1550
+ sigma = atm.sigma(theta=theta, delta=delta)
1551
+
1552
+ temp = theta * const.temp_0
1553
+
1554
+ # control parameters
1555
+ tas = conv.kt2ms(30)
1556
+ ROCD = conv.ft2m(-200) / 60 # [m/s]
1557
+
1558
+ # check for speed envelope limitations
1559
+ eps = 1e-6 # float calculation precision
1560
+ maxSpeed = atm.cas2Tas(cas=self.flightEnvelope.VMax(), delta=delta, sigma=sigma)
1561
+ minSpeed = 0
1562
+ limitation = ""
1563
+
1564
+ # empty envelope - keep the original calculated TAS speed
1565
+ if maxSpeed < minSpeed:
1566
+ if (tas - eps) > maxSpeed and (tas - eps) > minSpeed:
1567
+ limitation = "V"
1568
+ elif (tas + eps) < minSpeed and (tas + eps) < maxSpeed:
1569
+ limitation = "v"
1570
+ else:
1571
+ limitation = "vV"
1572
+
1573
+ elif minSpeed > (tas + eps):
1574
+ if speedLimit == "applyLimit":
1575
+ tas = minSpeed
1576
+ limitation = "C"
1577
+ else:
1578
+ limitation = "v"
1579
+
1580
+ elif maxSpeed < (tas - eps):
1581
+ if speedLimit == "applyLimit":
1582
+ tas = maxSpeed
1583
+ limitation = "C"
1584
+ else:
1585
+ limitation = "V"
1586
+
1587
+ ESF = self.esf(flightEvolution="constTAS")
1588
+
1589
+ Pav = Pav = self.Pav(
1590
+ rating="MTKF"
1591
+ ) # verify if Pav is calualted based on MTKF rating
1592
+ Preq = self.Preq(sigma=sigma, tas=tas, mass=mass)
1593
+ Peng_target = self.Peng_target(
1594
+ temp=temp, DeltaTemp=DeltaTemp, ROCD=ROCD, mass=mass, Preq=Preq, ESF=ESF
1595
+ )
1596
+ Peng = Peng_target
1597
+
1598
+ if Pav < Peng:
1599
+ limitation += "P"
1600
+
1601
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1602
+
1603
+ def decelerationToLanding(self):
1604
+ pass
1605
+
1606
+ def landing(self, h, mass, DeltaTemp):
1607
+ """This function computes parameters for the landing ARPM
1608
+
1609
+ :param h: altitude [m].
1610
+ :param mass: aircraft weight [kg].
1611
+ :param DeltaTemp: deviation with respect to ISA [K]
1612
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
1613
+ :type h: float.
1614
+ :type mass: float.
1615
+ :type DeltaTemp: float.
1616
+ :type wS: float.
1617
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1618
+ :rtype: float.
1619
+ """
1620
+
1621
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1622
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
1623
+ sigma = atm.sigma(theta=theta, delta=delta)
1624
+
1625
+ temp = theta * const.temp_0
1626
+
1627
+ # control parameters
1628
+ tas = 0
1629
+ ROCD = conv.ft2m(-100) / 60 # [m/s]
1630
+
1631
+ limitation = ""
1632
+
1633
+ ESF = self.esf(flightEvolution="constTAS")
1634
+
1635
+ Pav = self.Pav(rating="MTKF") # verify if Pav is calualted based on MTKF rating
1636
+ Preq = self.Preq(sigma=sigma, tas=tas, mass=mass)
1637
+ Peng_target = self.Peng_target(
1638
+ temp=temp, DeltaTemp=DeltaTemp, ROCD=ROCD, mass=mass, Preq=Preq, ESF=ESF
1639
+ )
1640
+ Peng = Peng_target
1641
+
1642
+ if Pav < Peng:
1643
+ limitation += "P"
1644
+
1645
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1646
+
1647
+ def hover(self, h, mass, DeltaTemp):
1648
+ """This function computes parameters for the hover ARPM
1649
+
1650
+ :param h: altitude [m].
1651
+ :param mass: aircraft weight [kg].
1652
+ :param DeltaTemp: deviation with respect to ISA [K]
1653
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
1654
+ :type h: float.
1655
+ :type mass: float.
1656
+ :type DeltaTemp: float.
1657
+ :type wS: float.
1658
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1659
+ :rtype: float.
1660
+ """
1661
+
1662
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1663
+ delta = atm.delta(h=h, DeltaTemp=DeltaTemp)
1664
+ sigma = atm.sigma(theta=theta, delta=delta)
1665
+
1666
+ # control parameters
1667
+ tas = 0
1668
+ ROCD = 0 # [m/s]
1669
+
1670
+ limitation = ""
1671
+
1672
+ # ESF is N/A for cruise
1673
+ ESF = 0
1674
+
1675
+ Pav = self.Pav(rating="MTKF")
1676
+ Preq = self.Preq(sigma=sigma, tas=tas, mass=mass)
1677
+ Peng = Preq
1678
+
1679
+ if Pav < Peng:
1680
+ limitation += "P"
1681
+
1682
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1683
+
1684
+ def ARPMProcedure(self, phase, h, mass, DeltaTemp, rating="ARPM", speedLimit=None):
1685
+ """This function computes parameters for the ARPM
1686
+
1687
+ :param h: altitude [m].
1688
+ :param mass: aircraft weight [kg].
1689
+ :param DeltaTemp: deviation with respect to ISA [K]
1690
+ :param wS: longitudinal wind speed (TAS) [m s^-1].
1691
+ :param rating: engine rating {MTKF,MCNT,ARPM} [-].
1692
+ :param speedLimit: decision to apply or not the speed limit {applyLimit,''} [-].
1693
+ :type h: float.
1694
+ :type mass: float.
1695
+ :type DeltaTemp: float.
1696
+ :type wS: float.
1697
+ :type rating: string.
1698
+ :type speedLimit: string.
1699
+ :returns: [Pav, Peng, Preq, tas, ROCD, ESF, limitation] [W, W, W, m/s, m/s, -, -]
1700
+ :rtype: float.
1701
+ """
1702
+
1703
+ if phase == "Climb":
1704
+ if h <= conv.ft2m(5):
1705
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.takeoff(
1706
+ h=h,
1707
+ mass=mass,
1708
+ DeltaTemp=DeltaTemp,
1709
+ rating=rating,
1710
+ speedLimit=speedLimit,
1711
+ )
1712
+ elif h > conv.ft2m(5):
1713
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.climb(
1714
+ h=h,
1715
+ mass=mass,
1716
+ DeltaTemp=DeltaTemp,
1717
+ rating=rating,
1718
+ speedLimit=speedLimit,
1719
+ )
1720
+
1721
+ elif phase == "Cruise":
1722
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.cruise(
1723
+ h=h, mass=mass, DeltaTemp=DeltaTemp, speedLimit=speedLimit
1724
+ )
1725
+
1726
+ elif phase == "Descent":
1727
+ if h >= conv.ft2m(500):
1728
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.descent(
1729
+ h=h, mass=mass, DeltaTemp=DeltaTemp, speedLimit=speedLimit
1730
+ )
1731
+ elif h < conv.ft2m(500) and h >= conv.ft2m(150):
1732
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.approach(
1733
+ h=h, mass=mass, DeltaTemp=DeltaTemp, speedLimit=speedLimit
1734
+ )
1735
+ elif h < conv.ft2m(150) and h >= conv.ft2m(5):
1736
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.finalApproach(
1737
+ h=h, mass=mass, DeltaTemp=DeltaTemp, speedLimit=speedLimit
1738
+ )
1739
+ elif h < conv.ft2m(5):
1740
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.landing(
1741
+ h=h, mass=mass, DeltaTemp=DeltaTemp
1742
+ )
1743
+
1744
+ elif phase == "Hover":
1745
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.hover(
1746
+ h=h, mass=mass, DeltaTemp=DeltaTemp
1747
+ )
1748
+
1749
+ return [Pav, Peng, Preq, tas, ROCD, ESF, limitation]
1750
+
1751
+
1752
+ class PTD(BADAE):
1753
+ """This class implements the PTD file creator for BADAE aircraft following BADAE manual.
1754
+
1755
+ :param AC: parsed aircraft.
1756
+ :type AC: badaE.Parse.
1757
+ """
1758
+
1759
+ def __init__(self, AC):
1760
+ BADAE.__init__(self, AC)
1761
+ self.flightEnvelope = FlightEnvelope(AC)
1762
+ self.ARPM = ARPM(AC)
1763
+
1764
+ def create(self, saveToPath, DeltaTemp):
1765
+ """This function creates the BADA4 PTD file
1766
+
1767
+ :param saveToPath: path to directory where PTF should be stored [-]
1768
+ :param DeltaTemp: deviation from ISA temperature [K]
1769
+ :type saveToPath: string.
1770
+ :type DeltaTemp: float.
1771
+ :returns: NONE
1772
+ """
1773
+
1774
+ # 3 different mass levels [kg]
1775
+ massList = [
1776
+ self.AC.OEW,
1777
+ self.AC.OEW + 0.7 * (self.AC.MTOW - self.AC.OEW),
1778
+ self.AC.MTOW,
1779
+ ]
1780
+ max_alt_ft = self.AC.hmo
1781
+
1782
+ # original PTD altitude list
1783
+ altitudeList = list(range(0, 500, 100))
1784
+ altitudeList.extend(range(500, 3000, 500))
1785
+ altitudeList.extend(range(3000, int(max_alt_ft), 1000))
1786
+ altitudeList.append(max_alt_ft)
1787
+
1788
+ CLList_ARPM = []
1789
+ CLList_MTKF = []
1790
+ CLList_MCNT = []
1791
+ CLList = []
1792
+ DESList = []
1793
+ CRList = []
1794
+ HOVERList = []
1795
+
1796
+ for mass in massList:
1797
+ CLList_ARPM.append(
1798
+ self.PTD_climb(
1799
+ mass=mass,
1800
+ altitudeList=altitudeList,
1801
+ DeltaTemp=DeltaTemp,
1802
+ rating="ARPM",
1803
+ )
1804
+ )
1805
+ CLList_MTKF.append(
1806
+ self.PTD_climb(
1807
+ mass=mass,
1808
+ altitudeList=altitudeList,
1809
+ DeltaTemp=DeltaTemp,
1810
+ rating="MTKF",
1811
+ )
1812
+ )
1813
+ CLList_MCNT.append(
1814
+ self.PTD_climb(
1815
+ mass=mass,
1816
+ altitudeList=altitudeList,
1817
+ DeltaTemp=DeltaTemp,
1818
+ rating="MCNT",
1819
+ )
1820
+ )
1821
+ CRList.append(
1822
+ self.PTD_cruise(
1823
+ mass=mass, altitudeList=altitudeList, DeltaTemp=DeltaTemp
1824
+ )
1825
+ )
1826
+ DESList.append(
1827
+ self.PTD_descent(
1828
+ mass=mass, altitudeList=altitudeList, DeltaTemp=DeltaTemp
1829
+ )
1830
+ )
1831
+ HOVERList.append(
1832
+ self.PTD_hover(
1833
+ mass=mass, altitudeList=altitudeList, DeltaTemp=DeltaTemp
1834
+ )
1835
+ )
1836
+
1837
+ self.save2PTD(
1838
+ saveToPath=saveToPath,
1839
+ CLList_ARPM=CLList_ARPM,
1840
+ CLList_MTKF=CLList_MTKF,
1841
+ CLList_MCNT=CLList_MCNT,
1842
+ CRList=CRList,
1843
+ DESList=DESList,
1844
+ HOVERList=HOVERList,
1845
+ DeltaTemp=DeltaTemp,
1846
+ )
1847
+
1848
+ def save2PTD(
1849
+ self,
1850
+ saveToPath,
1851
+ CLList_ARPM,
1852
+ CLList_MTKF,
1853
+ CLList_MCNT,
1854
+ CRList,
1855
+ DESList,
1856
+ HOVERList,
1857
+ DeltaTemp,
1858
+ ):
1859
+ """This function saves data to PTD file
1860
+
1861
+ :param saveToPath: path to directory where PTD should be stored [-]
1862
+ :param CLList_ARPM: list of PTD data in CLIMB for BADA ARPM[-].
1863
+ :param CLList_MTKF: list of PTD data in CLIMB for BADA MTKF rating[-].
1864
+ :param CLList_MCNT: list of PTD data in CLIMB for BADA MCNT rating[-].
1865
+ :param CRList: list of PTD data in CRUISE [-].
1866
+ :param DESList: list of PTD data in DESCENT [-].
1867
+ :param HOVERList: list of PTD data in HOVER [-].
1868
+ :param DeltaTemp: deviation from ISA temperature [K]
1869
+ :type saveToPath: string.
1870
+ :type CLList_ARPM: list.
1871
+ :type CLList_MTKF: list.
1872
+ :type CLList_MCNT: list.
1873
+ :type CRList: list.
1874
+ :type DESList: list.
1875
+ :type HOVERList: list.
1876
+ :type DeltaTemp: float.
1877
+ :returns: NONE
1878
+ """
1879
+
1880
+ newpath = saveToPath
1881
+ if not os.path.exists(newpath):
1882
+ os.makedirs(newpath)
1883
+
1884
+ if DeltaTemp == 0.0:
1885
+ ISA = ""
1886
+ elif DeltaTemp > 0.0:
1887
+ ISA = "+" + str(int(DeltaTemp))
1888
+ elif DeltaTemp < 0.0:
1889
+ ISA = str(int(DeltaTemp))
1890
+
1891
+ filename = saveToPath + self.AC.acName + "_ISA" + ISA + ".PTD"
1892
+
1893
+ file = open(filename, "w")
1894
+ file.write("BADA PERFORMANCE FILE RESULTS\n")
1895
+ file = open(filename, "a")
1896
+ file.write("=============================\n=============================\n\n")
1897
+ file.write("Low mass CLIMB (MTKF)\n")
1898
+ file.write("=====================\n\n")
1899
+ file.write(
1900
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
1901
+ )
1902
+ file.write(
1903
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
1904
+ )
1905
+
1906
+ # low mass
1907
+ list_mass = CLList_MTKF[0]
1908
+ for k in range(0, len(list_mass[0])):
1909
+ file.write(
1910
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
1911
+ % (
1912
+ list_mass[0][k],
1913
+ list_mass[1][k],
1914
+ list_mass[2][k],
1915
+ list_mass[3][k],
1916
+ list_mass[4][k],
1917
+ list_mass[5][k],
1918
+ list_mass[6][k],
1919
+ list_mass[7][k],
1920
+ list_mass[8][k],
1921
+ list_mass[9][k],
1922
+ list_mass[10][k],
1923
+ list_mass[11][k],
1924
+ list_mass[12][k],
1925
+ list_mass[13][k],
1926
+ list_mass[14][k],
1927
+ list_mass[15][k],
1928
+ )
1929
+ )
1930
+
1931
+ file.write("\n\nMedium mass CLIMB (MTKF)\n")
1932
+ file.write("========================\n\n")
1933
+ file.write(
1934
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
1935
+ )
1936
+ file.write(
1937
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
1938
+ )
1939
+
1940
+ # medium mass
1941
+ list_mass = CLList_MTKF[1]
1942
+ for k in range(0, len(list_mass[0])):
1943
+ file.write(
1944
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
1945
+ % (
1946
+ list_mass[0][k],
1947
+ list_mass[1][k],
1948
+ list_mass[2][k],
1949
+ list_mass[3][k],
1950
+ list_mass[4][k],
1951
+ list_mass[5][k],
1952
+ list_mass[6][k],
1953
+ list_mass[7][k],
1954
+ list_mass[8][k],
1955
+ list_mass[9][k],
1956
+ list_mass[10][k],
1957
+ list_mass[11][k],
1958
+ list_mass[12][k],
1959
+ list_mass[13][k],
1960
+ list_mass[14][k],
1961
+ list_mass[15][k],
1962
+ )
1963
+ )
1964
+
1965
+ file.write("\n\nHigh mass CLIMB (MTKF)\n")
1966
+ file.write("======================\n\n")
1967
+ file.write(
1968
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
1969
+ )
1970
+ file.write(
1971
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
1972
+ )
1973
+
1974
+ # high mass
1975
+ list_mass = CLList_MTKF[2]
1976
+ for k in range(0, len(list_mass[0])):
1977
+ file.write(
1978
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
1979
+ % (
1980
+ list_mass[0][k],
1981
+ list_mass[1][k],
1982
+ list_mass[2][k],
1983
+ list_mass[3][k],
1984
+ list_mass[4][k],
1985
+ list_mass[5][k],
1986
+ list_mass[6][k],
1987
+ list_mass[7][k],
1988
+ list_mass[8][k],
1989
+ list_mass[9][k],
1990
+ list_mass[10][k],
1991
+ list_mass[11][k],
1992
+ list_mass[12][k],
1993
+ list_mass[13][k],
1994
+ list_mass[14][k],
1995
+ list_mass[15][k],
1996
+ )
1997
+ )
1998
+
1999
+ file.write("\n\nLow mass CLIMB (MCNT)\n")
2000
+ file.write("=====================\n\n")
2001
+ file.write(
2002
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2003
+ )
2004
+ file.write(
2005
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2006
+ )
2007
+
2008
+ # low mass
2009
+ list_mass = CLList_MCNT[0]
2010
+ for k in range(0, len(list_mass[0])):
2011
+ file.write(
2012
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2013
+ % (
2014
+ list_mass[0][k],
2015
+ list_mass[1][k],
2016
+ list_mass[2][k],
2017
+ list_mass[3][k],
2018
+ list_mass[4][k],
2019
+ list_mass[5][k],
2020
+ list_mass[6][k],
2021
+ list_mass[7][k],
2022
+ list_mass[8][k],
2023
+ list_mass[9][k],
2024
+ list_mass[10][k],
2025
+ list_mass[11][k],
2026
+ list_mass[12][k],
2027
+ list_mass[13][k],
2028
+ list_mass[14][k],
2029
+ list_mass[15][k],
2030
+ )
2031
+ )
2032
+
2033
+ file.write("\n\nMedium mass CLIMB (MCNT)\n")
2034
+ file.write("========================\n\n")
2035
+ file.write(
2036
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2037
+ )
2038
+ file.write(
2039
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2040
+ )
2041
+
2042
+ # medium mass
2043
+ list_mass = CLList_MCNT[1]
2044
+ for k in range(0, len(list_mass[0])):
2045
+ file.write(
2046
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2047
+ % (
2048
+ list_mass[0][k],
2049
+ list_mass[1][k],
2050
+ list_mass[2][k],
2051
+ list_mass[3][k],
2052
+ list_mass[4][k],
2053
+ list_mass[5][k],
2054
+ list_mass[6][k],
2055
+ list_mass[7][k],
2056
+ list_mass[8][k],
2057
+ list_mass[9][k],
2058
+ list_mass[10][k],
2059
+ list_mass[11][k],
2060
+ list_mass[12][k],
2061
+ list_mass[13][k],
2062
+ list_mass[14][k],
2063
+ list_mass[15][k],
2064
+ )
2065
+ )
2066
+
2067
+ file.write("\n\nHigh mass CLIMB (MCNT)\n")
2068
+ file.write("======================\n\n")
2069
+ file.write(
2070
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2071
+ )
2072
+ file.write(
2073
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2074
+ )
2075
+
2076
+ # high mass
2077
+ list_mass = CLList_MCNT[2]
2078
+ for k in range(0, len(list_mass[0])):
2079
+ file.write(
2080
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2081
+ % (
2082
+ list_mass[0][k],
2083
+ list_mass[1][k],
2084
+ list_mass[2][k],
2085
+ list_mass[3][k],
2086
+ list_mass[4][k],
2087
+ list_mass[5][k],
2088
+ list_mass[6][k],
2089
+ list_mass[7][k],
2090
+ list_mass[8][k],
2091
+ list_mass[9][k],
2092
+ list_mass[10][k],
2093
+ list_mass[11][k],
2094
+ list_mass[12][k],
2095
+ list_mass[13][k],
2096
+ list_mass[14][k],
2097
+ list_mass[15][k],
2098
+ )
2099
+ )
2100
+
2101
+ file.write("\n\nLow mass CLIMB (ARPM)\n")
2102
+ file.write("=====================\n\n")
2103
+ file.write(
2104
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2105
+ )
2106
+ file.write(
2107
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2108
+ )
2109
+
2110
+ # low mass
2111
+ list_mass = CLList_ARPM[0]
2112
+ for k in range(0, len(list_mass[0])):
2113
+ file.write(
2114
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2115
+ % (
2116
+ list_mass[0][k],
2117
+ list_mass[1][k],
2118
+ list_mass[2][k],
2119
+ list_mass[3][k],
2120
+ list_mass[4][k],
2121
+ list_mass[5][k],
2122
+ list_mass[6][k],
2123
+ list_mass[7][k],
2124
+ list_mass[8][k],
2125
+ list_mass[9][k],
2126
+ list_mass[10][k],
2127
+ list_mass[11][k],
2128
+ list_mass[12][k],
2129
+ list_mass[13][k],
2130
+ list_mass[14][k],
2131
+ list_mass[15][k],
2132
+ )
2133
+ )
2134
+
2135
+ file.write("\n\nMedium mass CLIMB (ARPM)\n")
2136
+ file.write("========================\n\n")
2137
+ file.write(
2138
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2139
+ )
2140
+ file.write(
2141
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2142
+ )
2143
+
2144
+ # medium mass
2145
+ list_mass = CLList_ARPM[1]
2146
+ for k in range(0, len(list_mass[0])):
2147
+ file.write(
2148
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2149
+ % (
2150
+ list_mass[0][k],
2151
+ list_mass[1][k],
2152
+ list_mass[2][k],
2153
+ list_mass[3][k],
2154
+ list_mass[4][k],
2155
+ list_mass[5][k],
2156
+ list_mass[6][k],
2157
+ list_mass[7][k],
2158
+ list_mass[8][k],
2159
+ list_mass[9][k],
2160
+ list_mass[10][k],
2161
+ list_mass[11][k],
2162
+ list_mass[12][k],
2163
+ list_mass[13][k],
2164
+ list_mass[14][k],
2165
+ list_mass[15][k],
2166
+ )
2167
+ )
2168
+
2169
+ file.write("\n\nHigh mass CLIMB (ARPM)\n")
2170
+ file.write("======================\n\n")
2171
+ file.write(
2172
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2173
+ )
2174
+ file.write(
2175
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2176
+ )
2177
+
2178
+ # high mass
2179
+ list_mass = CLList_ARPM[2]
2180
+ for k in range(0, len(list_mass[0])):
2181
+ file.write(
2182
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2183
+ % (
2184
+ list_mass[0][k],
2185
+ list_mass[1][k],
2186
+ list_mass[2][k],
2187
+ list_mass[3][k],
2188
+ list_mass[4][k],
2189
+ list_mass[5][k],
2190
+ list_mass[6][k],
2191
+ list_mass[7][k],
2192
+ list_mass[8][k],
2193
+ list_mass[9][k],
2194
+ list_mass[10][k],
2195
+ list_mass[11][k],
2196
+ list_mass[12][k],
2197
+ list_mass[13][k],
2198
+ list_mass[14][k],
2199
+ list_mass[15][k],
2200
+ )
2201
+ )
2202
+
2203
+ file.write("\n\nLow mass DESCENT\n")
2204
+ file.write("================\n\n")
2205
+ file.write(
2206
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2207
+ )
2208
+ file.write(
2209
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2210
+ )
2211
+
2212
+ # low mass
2213
+ list_mass = DESList[0]
2214
+ for k in range(0, len(list_mass[0])):
2215
+ file.write(
2216
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2217
+ % (
2218
+ list_mass[0][k],
2219
+ list_mass[1][k],
2220
+ list_mass[2][k],
2221
+ list_mass[3][k],
2222
+ list_mass[4][k],
2223
+ list_mass[5][k],
2224
+ list_mass[6][k],
2225
+ list_mass[7][k],
2226
+ list_mass[8][k],
2227
+ list_mass[9][k],
2228
+ list_mass[10][k],
2229
+ list_mass[11][k],
2230
+ list_mass[12][k],
2231
+ list_mass[13][k],
2232
+ list_mass[14][k],
2233
+ list_mass[15][k],
2234
+ )
2235
+ )
2236
+
2237
+ file.write("\n\nMedium mass DESCENT\n")
2238
+ file.write("===================\n\n")
2239
+ file.write(
2240
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2241
+ )
2242
+ file.write(
2243
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2244
+ )
2245
+
2246
+ # medium mass
2247
+ list_mass = DESList[1]
2248
+ for k in range(0, len(list_mass[0])):
2249
+ file.write(
2250
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2251
+ % (
2252
+ list_mass[0][k],
2253
+ list_mass[1][k],
2254
+ list_mass[2][k],
2255
+ list_mass[3][k],
2256
+ list_mass[4][k],
2257
+ list_mass[5][k],
2258
+ list_mass[6][k],
2259
+ list_mass[7][k],
2260
+ list_mass[8][k],
2261
+ list_mass[9][k],
2262
+ list_mass[10][k],
2263
+ list_mass[11][k],
2264
+ list_mass[12][k],
2265
+ list_mass[13][k],
2266
+ list_mass[14][k],
2267
+ list_mass[15][k],
2268
+ )
2269
+ )
2270
+
2271
+ file.write("\n\nHigh mass DESCENT\n")
2272
+ file.write("=================\n\n")
2273
+ file.write(
2274
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2275
+ )
2276
+ file.write(
2277
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2278
+ )
2279
+
2280
+ # high mass
2281
+ list_mass = DESList[2]
2282
+ for k in range(0, len(list_mass[0])):
2283
+ file.write(
2284
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2285
+ % (
2286
+ list_mass[0][k],
2287
+ list_mass[1][k],
2288
+ list_mass[2][k],
2289
+ list_mass[3][k],
2290
+ list_mass[4][k],
2291
+ list_mass[5][k],
2292
+ list_mass[6][k],
2293
+ list_mass[7][k],
2294
+ list_mass[8][k],
2295
+ list_mass[9][k],
2296
+ list_mass[10][k],
2297
+ list_mass[11][k],
2298
+ list_mass[12][k],
2299
+ list_mass[13][k],
2300
+ list_mass[14][k],
2301
+ list_mass[15][k],
2302
+ )
2303
+ )
2304
+
2305
+ file.write("\n\nLow mass CRUISE\n")
2306
+ file.write("===============\n\n")
2307
+ file.write(
2308
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2309
+ )
2310
+ file.write(
2311
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2312
+ )
2313
+
2314
+ # low mass
2315
+ list_mass = CRList[0]
2316
+ for k in range(0, len(list_mass[0])):
2317
+ file.write(
2318
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2319
+ % (
2320
+ list_mass[0][k],
2321
+ list_mass[1][k],
2322
+ list_mass[2][k],
2323
+ list_mass[3][k],
2324
+ list_mass[4][k],
2325
+ list_mass[5][k],
2326
+ list_mass[6][k],
2327
+ list_mass[7][k],
2328
+ list_mass[8][k],
2329
+ list_mass[9][k],
2330
+ list_mass[10][k],
2331
+ list_mass[11][k],
2332
+ list_mass[12][k],
2333
+ list_mass[13][k],
2334
+ list_mass[14][k],
2335
+ list_mass[15][k],
2336
+ )
2337
+ )
2338
+
2339
+ file.write("\n\nMedium mass CRUISE\n")
2340
+ file.write("==================\n\n")
2341
+ file.write(
2342
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2343
+ )
2344
+ file.write(
2345
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2346
+ )
2347
+
2348
+ # medium mass
2349
+ list_mass = CRList[1]
2350
+ for k in range(0, len(list_mass[0])):
2351
+ file.write(
2352
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2353
+ % (
2354
+ list_mass[0][k],
2355
+ list_mass[1][k],
2356
+ list_mass[2][k],
2357
+ list_mass[3][k],
2358
+ list_mass[4][k],
2359
+ list_mass[5][k],
2360
+ list_mass[6][k],
2361
+ list_mass[7][k],
2362
+ list_mass[8][k],
2363
+ list_mass[9][k],
2364
+ list_mass[10][k],
2365
+ list_mass[11][k],
2366
+ list_mass[12][k],
2367
+ list_mass[13][k],
2368
+ list_mass[14][k],
2369
+ list_mass[15][k],
2370
+ )
2371
+ )
2372
+
2373
+ file.write("\n\nHigh mass CRUISE\n")
2374
+ file.write("================\n\n")
2375
+ file.write(
2376
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2377
+ )
2378
+ file.write(
2379
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2380
+ )
2381
+
2382
+ # high mass
2383
+ list_mass = CRList[2]
2384
+ for k in range(0, len(list_mass[0])):
2385
+ file.write(
2386
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2387
+ % (
2388
+ list_mass[0][k],
2389
+ list_mass[1][k],
2390
+ list_mass[2][k],
2391
+ list_mass[3][k],
2392
+ list_mass[4][k],
2393
+ list_mass[5][k],
2394
+ list_mass[6][k],
2395
+ list_mass[7][k],
2396
+ list_mass[8][k],
2397
+ list_mass[9][k],
2398
+ list_mass[10][k],
2399
+ list_mass[11][k],
2400
+ list_mass[12][k],
2401
+ list_mass[13][k],
2402
+ list_mass[14][k],
2403
+ list_mass[15][k],
2404
+ )
2405
+ )
2406
+
2407
+ file.write("\n\nLow mass HOVER\n")
2408
+ file.write("==============\n\n")
2409
+ file.write(
2410
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2411
+ )
2412
+ file.write(
2413
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2414
+ )
2415
+
2416
+ # low mass
2417
+ list_mass = HOVERList[0]
2418
+ for k in range(0, len(list_mass[0])):
2419
+ file.write(
2420
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2421
+ % (
2422
+ list_mass[0][k],
2423
+ list_mass[1][k],
2424
+ list_mass[2][k],
2425
+ list_mass[3][k],
2426
+ list_mass[4][k],
2427
+ list_mass[5][k],
2428
+ list_mass[6][k],
2429
+ list_mass[7][k],
2430
+ list_mass[8][k],
2431
+ list_mass[9][k],
2432
+ list_mass[10][k],
2433
+ list_mass[11][k],
2434
+ list_mass[12][k],
2435
+ list_mass[13][k],
2436
+ list_mass[14][k],
2437
+ list_mass[15][k],
2438
+ )
2439
+ )
2440
+
2441
+ file.write("\n\nMedium mass HOVER\n")
2442
+ file.write("=================\n\n")
2443
+ file.write(
2444
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2445
+ )
2446
+ file.write(
2447
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2448
+ )
2449
+
2450
+ # medium mass
2451
+ list_mass = HOVERList[1]
2452
+ for k in range(0, len(list_mass[0])):
2453
+ file.write(
2454
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2455
+ % (
2456
+ list_mass[0][k],
2457
+ list_mass[1][k],
2458
+ list_mass[2][k],
2459
+ list_mass[3][k],
2460
+ list_mass[4][k],
2461
+ list_mass[5][k],
2462
+ list_mass[6][k],
2463
+ list_mass[7][k],
2464
+ list_mass[8][k],
2465
+ list_mass[9][k],
2466
+ list_mass[10][k],
2467
+ list_mass[11][k],
2468
+ list_mass[12][k],
2469
+ list_mass[13][k],
2470
+ list_mass[14][k],
2471
+ list_mass[15][k],
2472
+ )
2473
+ )
2474
+
2475
+ file.write("\n\nHigh mass HOVER\n")
2476
+ file.write("===============\n\n")
2477
+ file.write(
2478
+ " FL T p rho a TAS CAS M mass Peng Preq Fuel ESF ROCD gamma Lim\n"
2479
+ )
2480
+ file.write(
2481
+ "[-] [K] [Pa] [kg/m3] [m/s] [kt] [kt] [-] [kg] [W] [W] [kgm] [-] [fpm] [deg] \n"
2482
+ )
2483
+
2484
+ # high mass
2485
+ list_mass = HOVERList[2]
2486
+ for k in range(0, len(list_mass[0])):
2487
+ file.write(
2488
+ "%3d %7.2f %7.0f %6.3f %6.1f %7.2f %7.2f %6.3f %7.0f %8.0f %8.0f %7.2f %6.3f %6.0f %7.2f %s\n"
2489
+ % (
2490
+ list_mass[0][k],
2491
+ list_mass[1][k],
2492
+ list_mass[2][k],
2493
+ list_mass[3][k],
2494
+ list_mass[4][k],
2495
+ list_mass[5][k],
2496
+ list_mass[6][k],
2497
+ list_mass[7][k],
2498
+ list_mass[8][k],
2499
+ list_mass[9][k],
2500
+ list_mass[10][k],
2501
+ list_mass[11][k],
2502
+ list_mass[12][k],
2503
+ list_mass[13][k],
2504
+ list_mass[14][k],
2505
+ list_mass[15][k],
2506
+ )
2507
+ )
2508
+
2509
+ def PTD_climb(self, mass, altitudeList, DeltaTemp, rating):
2510
+ """This function calculates the BADAE PTD data in CLIMB
2511
+
2512
+ :param mass: aircraft mass [kg]
2513
+ :param altitudeList: aircraft altitude list [ft]
2514
+ :param DeltaTemp: deviation from ISA temperature [K]
2515
+ :param rating: engine rating {MTKF,MCNT,ARPM}[-]
2516
+ :type mass: float.
2517
+ :type altitudeList: list of int.
2518
+ :type DeltaTemp: float.
2519
+ :type rating: string.
2520
+ :returns: list of PTD CLIMB data [-]
2521
+ :rtype: list
2522
+ """
2523
+
2524
+ FL_complet = []
2525
+ T_complet = []
2526
+ p_complet = []
2527
+ rho_complet = []
2528
+ a_complet = []
2529
+ TAS_complet = []
2530
+ CAS_complet = []
2531
+ M_complet = []
2532
+ mass_complet = []
2533
+ Peng_complet = []
2534
+ Preq_complet = []
2535
+ ff_comlet = []
2536
+ ESF_complet = []
2537
+ ROCD_complet = []
2538
+ gamma_complet = []
2539
+ Lim_complet = []
2540
+
2541
+ phase = "Climb"
2542
+
2543
+ for h in altitudeList:
2544
+ H_m = conv.ft2m(h) # altitude [m]
2545
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
2546
+
2547
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.ARPM.ARPMProcedure(
2548
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=mass, rating=rating
2549
+ )
2550
+
2551
+ cas = atm.tas2Cas(tas=tas, delta=delta, sigma=sigma)
2552
+ M = atm.tas2Mach(v=tas, theta=theta)
2553
+ a = atm.aSound(theta=theta)
2554
+ FL = h / 100
2555
+
2556
+ ff = self.ff() / 60 # [kg/min]
2557
+
2558
+ temp = theta * const.temp_0
2559
+ temp_const = (temp) / (temp - DeltaTemp)
2560
+ dhdt = ROCD * temp_const
2561
+
2562
+ if tas == 0:
2563
+ if ROCD >= 0:
2564
+ gamma = 90
2565
+ else:
2566
+ gamma = -90
2567
+ else:
2568
+ gamma = conv.rad2deg(atan(dhdt / tas))
2569
+
2570
+ FL_complet.append(proper_round(FL))
2571
+ T_complet.append(temp)
2572
+ p_complet.append(delta * const.p_0)
2573
+ rho_complet.append(sigma * const.rho_0)
2574
+ a_complet.append(a)
2575
+ TAS_complet.append(conv.ms2kt(tas))
2576
+ CAS_complet.append(conv.ms2kt(cas))
2577
+ M_complet.append(M)
2578
+ mass_complet.append(proper_round(mass))
2579
+ Peng_complet.append(Peng)
2580
+ Preq_complet.append(Preq)
2581
+ ff_comlet.append(ff)
2582
+ ESF_complet.append(ESF)
2583
+ ROCD_complet.append(conv.m2ft(ROCD) * 60)
2584
+ gamma_complet.append(gamma)
2585
+ Lim_complet.append(limitation)
2586
+
2587
+ CLList = [
2588
+ FL_complet,
2589
+ T_complet,
2590
+ p_complet,
2591
+ rho_complet,
2592
+ a_complet,
2593
+ TAS_complet,
2594
+ CAS_complet,
2595
+ M_complet,
2596
+ mass_complet,
2597
+ Peng_complet,
2598
+ Preq_complet,
2599
+ ff_comlet,
2600
+ ESF_complet,
2601
+ ROCD_complet,
2602
+ gamma_complet,
2603
+ Lim_complet,
2604
+ ]
2605
+
2606
+ return CLList
2607
+
2608
+ def PTD_descent(self, mass, altitudeList, DeltaTemp):
2609
+ """This function calculates the BADAE PTD data in DESCENT
2610
+
2611
+ :param mass: aircraft mass [kg]
2612
+ :param altitudeList: aircraft altitude list [ft]
2613
+ :param DeltaTemp: deviation from ISA temperature [K]
2614
+ :type mass: float.
2615
+ :type altitudeList: list of int.
2616
+ :type DeltaTemp: float.
2617
+ :returns: list of PTD CLIMB data [-]
2618
+ :rtype: list
2619
+ """
2620
+
2621
+ FL_complet = []
2622
+ T_complet = []
2623
+ p_complet = []
2624
+ rho_complet = []
2625
+ a_complet = []
2626
+ TAS_complet = []
2627
+ CAS_complet = []
2628
+ M_complet = []
2629
+ mass_complet = []
2630
+ Peng_complet = []
2631
+ Preq_complet = []
2632
+ ff_comlet = []
2633
+ ESF_complet = []
2634
+ ROCD_complet = []
2635
+ gamma_complet = []
2636
+ Lim_complet = []
2637
+
2638
+ phase = "Descent"
2639
+
2640
+ for h in altitudeList:
2641
+ H_m = conv.ft2m(h) # altitude [m]
2642
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
2643
+
2644
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.ARPM.ARPMProcedure(
2645
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=mass
2646
+ )
2647
+
2648
+ cas = atm.tas2Cas(tas=tas, delta=delta, sigma=sigma)
2649
+ M = atm.tas2Mach(v=tas, theta=theta)
2650
+ a = atm.aSound(theta=theta)
2651
+ FL = h / 100
2652
+
2653
+ ff = self.ff() / 60 # [kg/min]
2654
+
2655
+ temp = theta * const.temp_0
2656
+ temp_const = (temp) / (temp - DeltaTemp)
2657
+ dhdt = ROCD * temp_const
2658
+ if tas == 0:
2659
+ gamma = -90
2660
+ else:
2661
+ gamma = conv.rad2deg(atan(dhdt / tas))
2662
+
2663
+ FL_complet.append(proper_round(FL))
2664
+ T_complet.append(temp)
2665
+ p_complet.append(delta * const.p_0)
2666
+ rho_complet.append(sigma * const.rho_0)
2667
+ a_complet.append(a)
2668
+ TAS_complet.append(conv.ms2kt(tas))
2669
+ CAS_complet.append(conv.ms2kt(cas))
2670
+ M_complet.append(M)
2671
+ mass_complet.append(proper_round(mass))
2672
+ Peng_complet.append(Peng)
2673
+ Preq_complet.append(Preq)
2674
+ ff_comlet.append(ff)
2675
+ ESF_complet.append(ESF)
2676
+ ROCD_complet.append((-1) * conv.m2ft(ROCD) * 60)
2677
+ gamma_complet.append(gamma)
2678
+ Lim_complet.append(limitation)
2679
+
2680
+ DESList = [
2681
+ FL_complet,
2682
+ T_complet,
2683
+ p_complet,
2684
+ rho_complet,
2685
+ a_complet,
2686
+ TAS_complet,
2687
+ CAS_complet,
2688
+ M_complet,
2689
+ mass_complet,
2690
+ Peng_complet,
2691
+ Preq_complet,
2692
+ ff_comlet,
2693
+ ESF_complet,
2694
+ ROCD_complet,
2695
+ gamma_complet,
2696
+ Lim_complet,
2697
+ ]
2698
+
2699
+ return DESList
2700
+
2701
+ def PTD_cruise(self, mass, altitudeList, DeltaTemp):
2702
+ """This function calculates the BADAE PTD data in Cruise
2703
+
2704
+ :param mass: aircraft mass [kg]
2705
+ :param altitudeList: aircraft altitude list [ft]
2706
+ :param DeltaTemp: deviation from ISA temperature [K]
2707
+ :type mass: float.
2708
+ :type altitudeList: list of int.
2709
+ :type DeltaTemp: float.
2710
+ :returns: list of PTD CLIMB data [-]
2711
+ :rtype: list
2712
+ """
2713
+
2714
+ FL_complet = []
2715
+ T_complet = []
2716
+ p_complet = []
2717
+ rho_complet = []
2718
+ a_complet = []
2719
+ TAS_complet = []
2720
+ CAS_complet = []
2721
+ M_complet = []
2722
+ mass_complet = []
2723
+ Peng_complet = []
2724
+ Preq_complet = []
2725
+ ff_comlet = []
2726
+ ESF_complet = []
2727
+ ROCD_complet = []
2728
+ gamma_complet = []
2729
+ Lim_complet = []
2730
+
2731
+ phase = "Cruise"
2732
+
2733
+ for h in altitudeList:
2734
+ H_m = conv.ft2m(h) # altitude [m]
2735
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
2736
+
2737
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.ARPM.ARPMProcedure(
2738
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=mass
2739
+ )
2740
+
2741
+ cas = atm.tas2Cas(tas=tas, delta=delta, sigma=sigma)
2742
+ M = atm.tas2Mach(v=tas, theta=theta)
2743
+ a = atm.aSound(theta=theta)
2744
+ FL = h / 100
2745
+
2746
+ ff = self.ff() / 60 # [kg/min]
2747
+
2748
+ temp = theta * const.temp_0
2749
+ gamma = 0
2750
+
2751
+ FL_complet.append(proper_round(FL))
2752
+ T_complet.append(temp)
2753
+ p_complet.append(delta * const.p_0)
2754
+ rho_complet.append(sigma * const.rho_0)
2755
+ a_complet.append(a)
2756
+ TAS_complet.append(conv.ms2kt(tas))
2757
+ CAS_complet.append(conv.ms2kt(cas))
2758
+ M_complet.append(M)
2759
+ mass_complet.append(proper_round(mass))
2760
+ Peng_complet.append(Peng)
2761
+ Preq_complet.append(Preq)
2762
+ ff_comlet.append(ff)
2763
+ ESF_complet.append(ESF)
2764
+ ROCD_complet.append(conv.m2ft(ROCD) * 60)
2765
+ gamma_complet.append(gamma)
2766
+ Lim_complet.append(limitation)
2767
+
2768
+ CRList = [
2769
+ FL_complet,
2770
+ T_complet,
2771
+ p_complet,
2772
+ rho_complet,
2773
+ a_complet,
2774
+ TAS_complet,
2775
+ CAS_complet,
2776
+ M_complet,
2777
+ mass_complet,
2778
+ Peng_complet,
2779
+ Preq_complet,
2780
+ ff_comlet,
2781
+ ESF_complet,
2782
+ ROCD_complet,
2783
+ gamma_complet,
2784
+ Lim_complet,
2785
+ ]
2786
+
2787
+ return CRList
2788
+
2789
+ def PTD_hover(self, mass, altitudeList, DeltaTemp):
2790
+ """This function calculates the BADAE PTD data in Cruise
2791
+
2792
+ :param mass: aircraft mass [kg]
2793
+ :param altitudeList: aircraft altitude list [ft]
2794
+ :param DeltaTemp: deviation from ISA temperature [K]
2795
+ :type mass: float.
2796
+ :type altitudeList: list of int.
2797
+ :type DeltaTemp: float.
2798
+ :returns: list of PTD CLIMB data [-]
2799
+ :rtype: list
2800
+ """
2801
+
2802
+ FL_complet = []
2803
+ T_complet = []
2804
+ p_complet = []
2805
+ rho_complet = []
2806
+ a_complet = []
2807
+ TAS_complet = []
2808
+ CAS_complet = []
2809
+ M_complet = []
2810
+ mass_complet = []
2811
+ Peng_complet = []
2812
+ Preq_complet = []
2813
+ ff_comlet = []
2814
+ ESF_complet = []
2815
+ ROCD_complet = []
2816
+ gamma_complet = []
2817
+ Lim_complet = []
2818
+
2819
+ phase = "Hover"
2820
+
2821
+ for h in altitudeList:
2822
+ H_m = conv.ft2m(h) # altitude [m]
2823
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
2824
+
2825
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.ARPM.ARPMProcedure(
2826
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=mass
2827
+ )
2828
+
2829
+ cas = atm.tas2Cas(tas=tas, delta=delta, sigma=sigma)
2830
+ M = atm.tas2Mach(v=tas, theta=theta)
2831
+ a = atm.aSound(theta=theta)
2832
+ FL = h / 100
2833
+
2834
+ ff = self.ff() / 60 # [kg/min]
2835
+
2836
+ temp = theta * const.temp_0
2837
+ gamma = 0
2838
+
2839
+ FL_complet.append(proper_round(FL))
2840
+ T_complet.append(temp)
2841
+ p_complet.append(delta * const.p_0)
2842
+ rho_complet.append(sigma * const.rho_0)
2843
+ a_complet.append(a)
2844
+ TAS_complet.append(conv.ms2kt(tas))
2845
+ CAS_complet.append(conv.ms2kt(cas))
2846
+ M_complet.append(M)
2847
+ mass_complet.append(proper_round(mass))
2848
+ Peng_complet.append(Peng)
2849
+ Preq_complet.append(Preq)
2850
+ ff_comlet.append(ff)
2851
+ ESF_complet.append(ESF)
2852
+ ROCD_complet.append(conv.m2ft(ROCD) * 60)
2853
+ gamma_complet.append(gamma)
2854
+ Lim_complet.append(limitation)
2855
+
2856
+ HOVERList = [
2857
+ FL_complet,
2858
+ T_complet,
2859
+ p_complet,
2860
+ rho_complet,
2861
+ a_complet,
2862
+ TAS_complet,
2863
+ CAS_complet,
2864
+ M_complet,
2865
+ mass_complet,
2866
+ Peng_complet,
2867
+ Preq_complet,
2868
+ ff_comlet,
2869
+ ESF_complet,
2870
+ ROCD_complet,
2871
+ gamma_complet,
2872
+ Lim_complet,
2873
+ ]
2874
+
2875
+ return HOVERList
2876
+
2877
+
2878
+ class PTF(BADAE):
2879
+ """This class implements the PTF file creator for BADAE aircraft following BADAE manual.
2880
+
2881
+ :param AC: parsed aircraft.
2882
+ :type AC: badaE.Parse.
2883
+ """
2884
+
2885
+ def __init__(self, AC):
2886
+ BADAE.__init__(self, AC)
2887
+ self.flightEnvelope = FlightEnvelope(AC)
2888
+ self.ARPM = ARPM(AC)
2889
+
2890
+ def create(self, saveToPath, DeltaTemp):
2891
+ """This function creates the BADA4 PTF file
2892
+
2893
+ :param saveToPath: path to directory where PTF should be stored [-]
2894
+ :param DeltaTemp: deviation from ISA temperature [K]
2895
+ :type saveToPath: string.
2896
+ :type DeltaTemp: float.
2897
+ :returns: NONE
2898
+ """
2899
+
2900
+ # 3 different mass levels [kg]
2901
+ massList = [
2902
+ self.AC.OEW,
2903
+ self.AC.OEW + 0.7 * (self.AC.MTOW - self.AC.OEW),
2904
+ self.AC.MTOW,
2905
+ ]
2906
+ max_alt_ft = self.AC.hmo
2907
+
2908
+ # original PTF altitude list
2909
+ altitudeList = list(range(0, 500, 100))
2910
+ altitudeList.extend(range(500, 3000, 500))
2911
+ altitudeList.extend(range(3000, int(max_alt_ft), 1000))
2912
+ altitudeList.append(max_alt_ft)
2913
+
2914
+ CRList = self.PTF_cruise(
2915
+ massList=massList, altitudeList=altitudeList, DeltaTemp=DeltaTemp
2916
+ )
2917
+ CLList = self.PTF_climb(
2918
+ massList=massList,
2919
+ altitudeList=altitudeList,
2920
+ DeltaTemp=DeltaTemp,
2921
+ rating="ARPM",
2922
+ )
2923
+ DESList = self.PTF_descent(
2924
+ massList=massList, altitudeList=altitudeList, DeltaTemp=DeltaTemp
2925
+ )
2926
+
2927
+ self.save2PTF(
2928
+ saveToPath=saveToPath,
2929
+ altitudeList=altitudeList,
2930
+ massList=massList,
2931
+ CRList=CRList,
2932
+ CLList=CLList,
2933
+ DESList=DESList,
2934
+ DeltaTemp=DeltaTemp,
2935
+ )
2936
+
2937
+ def save2PTF(
2938
+ self, saveToPath, altitudeList, CLList, CRList, DESList, DeltaTemp, massList
2939
+ ):
2940
+ """This function saves data to PTF file
2941
+
2942
+ :param saveToPath: path to directory where PTF should be stored [-]
2943
+ :param CRList: list of PTF data in CRUISE [-].
2944
+ :param CLList: list of PTF data in CLIMB [-].
2945
+ :param DESList: list of PTF data in DESCENT [-].
2946
+ :param DeltaTemp: deviation from ISA temperature [K]
2947
+ :type saveToPath: string.
2948
+ :type CRList: list.
2949
+ :type CLList: list.
2950
+ :type DESList: list.
2951
+ :type DeltaTemp: float.
2952
+ :returns: NONE
2953
+ """
2954
+
2955
+ newpath = saveToPath
2956
+ if not os.path.exists(newpath):
2957
+ os.makedirs(newpath)
2958
+
2959
+ if DeltaTemp == 0.0:
2960
+ ISA = ""
2961
+ elif DeltaTemp > 0.0:
2962
+ ISA = "+" + str(int(DeltaTemp))
2963
+ elif DeltaTemp < 0.0:
2964
+ ISA = str(int(DeltaTemp))
2965
+
2966
+ filename = saveToPath + self.AC.acName + "_ISA" + ISA + ".PTF"
2967
+
2968
+ today = date.today()
2969
+ d3 = today.strftime("%b %d %Y")
2970
+
2971
+ acModel = self.AC.model
2972
+
2973
+ file = open(filename, "w")
2974
+ file.write(
2975
+ "BADA PERFORMANCE FILE %s\n\n" % (d3)
2976
+ )
2977
+ file = open(filename, "a")
2978
+ file.write("AC/Type: %s\n\n" % (acModel))
2979
+ file.write(
2980
+ " Speeds: Masses [kg]: Temperature: ISA%s\n"
2981
+ % (ISA)
2982
+ )
2983
+ file.write(
2984
+ " climb - MEC low - %.0f\n"
2985
+ % (proper_round(massList[0]))
2986
+ )
2987
+ file.write(
2988
+ " cruise - MEC nominal - %-4.0f Max Alt. [ft]:%7d\n"
2989
+ % (proper_round(massList[1]), altitudeList[-1])
2990
+ )
2991
+ file.write(
2992
+ " descent - MEC high - %0.f\n"
2993
+ % (proper_round(massList[2]))
2994
+ )
2995
+ file.write(
2996
+ "======================================================================================================\n"
2997
+ )
2998
+ file.write(
2999
+ " FL | CRUISE | CLIMB | DESCENT \n"
3000
+ )
3001
+ file.write(
3002
+ " | TAS fuel | TAS ROCD fuel | TAS ROCD fuel \n"
3003
+ )
3004
+ file.write(
3005
+ " | [kts] [kg/min] | [kts] [fpm] [kg/min] | [kts] [fpm] [kg/min]\n"
3006
+ )
3007
+ file.write(
3008
+ " | nom lo nom hi | nom lo nom hi nom | nom lo nom hi nom \n"
3009
+ )
3010
+ file.write(
3011
+ "======================================================================================================\n"
3012
+ )
3013
+
3014
+ for k in range(0, len(altitudeList)):
3015
+ FL = proper_round(altitudeList[k] / 100)
3016
+ file.write(
3017
+ "%3.0f | %s %s %s %s | %3.0f %5.0f %5.0f %5.0f %5.1f | %3.0f %5.0f %5.0f %5.0f %5.1f\n"
3018
+ % (
3019
+ FL,
3020
+ CRList[0][k],
3021
+ CRList[1][k],
3022
+ CRList[2][k],
3023
+ CRList[3][k],
3024
+ CLList[0][k],
3025
+ CLList[1][k],
3026
+ CLList[2][k],
3027
+ CLList[3][k],
3028
+ CLList[4][k],
3029
+ DESList[0][k],
3030
+ DESList[1][k],
3031
+ DESList[2][k],
3032
+ DESList[3][k],
3033
+ DESList[4][k],
3034
+ )
3035
+ )
3036
+ file.write(
3037
+ " | | | \n"
3038
+ )
3039
+
3040
+ file.write(
3041
+ "======================================================================================================\n"
3042
+ )
3043
+
3044
+ def PTF_cruise(self, massList, altitudeList, DeltaTemp):
3045
+ """This function calculates the BADAE PTF data in CRUISE
3046
+
3047
+ :param massList: list of aircraft mass [kg]
3048
+ :param altitudeList: aircraft altitude list [ft]
3049
+ :param DeltaTemp: deviation from ISA temperature [K]
3050
+ :type massList: list.
3051
+ :type altitudeList: list of int.
3052
+ :type DeltaTemp: float.
3053
+ :returns: list of PTF CRUISE data [-]
3054
+ :rtype: list
3055
+ """
3056
+
3057
+ TAS_CR_complet = []
3058
+ FF_CR_LO_complet = []
3059
+ FF_CR_NOM_complet = []
3060
+ FF_CR_HI_complet = []
3061
+
3062
+ phase = "Cruise"
3063
+ massNominal = massList[1]
3064
+
3065
+ for h in altitudeList:
3066
+ H_m = conv.ft2m(h) # altitude [m]
3067
+
3068
+ [
3069
+ Pav,
3070
+ Peng,
3071
+ Preq,
3072
+ tas_nominal,
3073
+ ROCD,
3074
+ ESF,
3075
+ limitation,
3076
+ ] = self.ARPM.ARPMProcedure(
3077
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=massNominal
3078
+ )
3079
+
3080
+ ff = []
3081
+ for mass in massList:
3082
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.ARPM.ARPMProcedure(
3083
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=mass
3084
+ )
3085
+
3086
+ if isnan(tas):
3087
+ ff.append("(P)")
3088
+
3089
+ else:
3090
+ ff.append(self.ff() / 60) # [kg/min]
3091
+
3092
+ TAS_CR_complet.append(f"{conv.ms2kt(tas_nominal):3.0f}")
3093
+ if isinstance(ff[0], str):
3094
+ FF_CR_LO_complet.append(" " + ff[0] + " ")
3095
+ else:
3096
+ FF_CR_LO_complet.append(f"{ff[0]:5.1f}")
3097
+ if isinstance(ff[1], str):
3098
+ FF_CR_NOM_complet.append(" " + ff[1] + " ")
3099
+ else:
3100
+ FF_CR_NOM_complet.append(f"{ff[1]:5.1f}")
3101
+ if isinstance(ff[2], str):
3102
+ FF_CR_HI_complet.append(" " + ff[2] + " ")
3103
+ else:
3104
+ FF_CR_HI_complet.append(f"{ff[2]:5.1f}")
3105
+
3106
+ CRList = [TAS_CR_complet, FF_CR_LO_complet, FF_CR_NOM_complet, FF_CR_HI_complet]
3107
+
3108
+ return CRList
3109
+
3110
+ def PTF_climb(self, massList, altitudeList, DeltaTemp, rating):
3111
+ """This function calculates the BADAE PTF data in CLIMB
3112
+
3113
+ :param massList: list of aircraft mass [kg]
3114
+ :param altitudeList: aircraft altitude list [ft]
3115
+ :param DeltaTemp: deviation from ISA temperature [K]
3116
+ :param rating: engine rating {MTKF,MCNT,ARPM}[-]
3117
+ :type massList: list.
3118
+ :type altitudeList: list of int.
3119
+ :type DeltaTemp: float.
3120
+ :type rating: string.
3121
+ :returns: list of PTF CLIMB data [-]
3122
+ :rtype: list
3123
+ """
3124
+
3125
+ TAS_CL_complet = []
3126
+ ROCD_CL_LO_complet = []
3127
+ ROCD_CL_NOM_complet = []
3128
+ ROCD_CL_HI_complet = []
3129
+ FF_CL_NOM_complet = []
3130
+
3131
+ phase = "Climb"
3132
+ massNominal = massList[1]
3133
+
3134
+ for h in altitudeList:
3135
+ H_m = conv.ft2m(h) # altitude [m]
3136
+
3137
+ [
3138
+ Pav,
3139
+ Peng,
3140
+ Preq,
3141
+ tas_nominal,
3142
+ ROCD,
3143
+ ESF,
3144
+ limitation,
3145
+ ] = self.ARPM.ARPMProcedure(
3146
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=massNominal, rating=rating
3147
+ )
3148
+
3149
+ ff_nominal = self.ff() / 60 # [kg/min]
3150
+
3151
+ ROC = []
3152
+ for mass in massList:
3153
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.ARPM.ARPMProcedure(
3154
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=mass, rating=rating
3155
+ )
3156
+
3157
+ ROC.append(conv.m2ft(ROCD) * 60)
3158
+
3159
+ TAS_CL_complet.append(conv.ms2kt(tas_nominal))
3160
+ ROCD_CL_LO_complet.append(ROC[0])
3161
+ ROCD_CL_NOM_complet.append(ROC[1])
3162
+ ROCD_CL_HI_complet.append(ROC[2])
3163
+ FF_CL_NOM_complet.append(ff_nominal)
3164
+
3165
+ CLList = [
3166
+ TAS_CL_complet,
3167
+ ROCD_CL_LO_complet,
3168
+ ROCD_CL_NOM_complet,
3169
+ ROCD_CL_HI_complet,
3170
+ FF_CL_NOM_complet,
3171
+ ]
3172
+
3173
+ return CLList
3174
+
3175
+ def PTF_descent(self, massList, altitudeList, DeltaTemp):
3176
+ """This function calculates the BADAE PTF data in DESCENT
3177
+
3178
+ :param massList: list of aircraft mass [kg]
3179
+ :param altitudeList: aircraft altitude list [ft]
3180
+ :param DeltaTemp: deviation from ISA temperature [K]
3181
+ :type massList: list.
3182
+ :type altitudeList: list of int.
3183
+ :type DeltaTemp: float.
3184
+ :returns: list of PTF DESCENT data [-]
3185
+ :rtype: list
3186
+ """
3187
+
3188
+ TAS_DES_complet = []
3189
+ ROCD_DES_LO_complet = []
3190
+ ROCD_DES_NOM_complet = []
3191
+ ROCD_DES_HI_complet = []
3192
+ FF_DES_NOM_complet = []
3193
+
3194
+ phase = "Descent"
3195
+ massNominal = massList[1]
3196
+
3197
+ for h in altitudeList:
3198
+ H_m = conv.ft2m(h) # altitude [m]
3199
+
3200
+ [
3201
+ Pav,
3202
+ Peng,
3203
+ Preq,
3204
+ tas_nominal,
3205
+ ROCD,
3206
+ ESF,
3207
+ limitation,
3208
+ ] = self.ARPM.ARPMProcedure(
3209
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=massNominal
3210
+ )
3211
+
3212
+ ff_nominal = self.ff() / 60 # [kg/min]
3213
+
3214
+ ROD = []
3215
+ ff_gamma_list = []
3216
+ for mass in massList:
3217
+ [Pav, Peng, Preq, tas, ROCD, ESF, limitation] = self.ARPM.ARPMProcedure(
3218
+ phase=phase, h=H_m, DeltaTemp=DeltaTemp, mass=mass
3219
+ )
3220
+
3221
+ ROD.append(-conv.m2ft(ROCD) * 60)
3222
+
3223
+ TAS_DES_complet.append(conv.ms2kt(tas_nominal))
3224
+ ROCD_DES_LO_complet.append(ROD[0])
3225
+ ROCD_DES_NOM_complet.append(ROD[1])
3226
+ ROCD_DES_HI_complet.append(ROD[2])
3227
+ FF_DES_NOM_complet.append(ff_nominal)
3228
+
3229
+ DESList = [
3230
+ TAS_DES_complet,
3231
+ ROCD_DES_LO_complet,
3232
+ ROCD_DES_NOM_complet,
3233
+ ROCD_DES_HI_complet,
3234
+ FF_DES_NOM_complet,
3235
+ ]
3236
+
3237
+ return DESList
3238
+
3239
+
3240
+ class BadaEAircraft(BADAE, BadaFamily):
3241
+ """This class implements the BADAE performance model following the BADAE manual.
3242
+
3243
+ :param filePath: path to the folder with BADAE xml formatted file.
3244
+ :param acName: ICAO aircraft designation
3245
+ :type filePath: str.
3246
+ :type acName: str
3247
+ """
3248
+
3249
+ def __init__(self, filePath, badaVersion, acName):
3250
+ AC_parsed = Parse()
3251
+ self.ACModelAvailable = False
3252
+
3253
+ self.BADAFamily = BadaFamily(BADAE=True)
3254
+ self.BADAFamilyName = "BADAE"
3255
+ self.BADAVersion = badaVersion
3256
+
3257
+ acXmlFile = (
3258
+ os.path.join(filePath, "BADAE", badaVersion, acName, acName) + ".xml"
3259
+ )
3260
+
3261
+ if os.path.isfile(acXmlFile):
3262
+ self.ACModelAvailable = True
3263
+
3264
+ AC_parsed.parse(
3265
+ filePath=filePath,
3266
+ badaFamily="BADAE",
3267
+ badaVersion=badaVersion,
3268
+ acName=acName,
3269
+ )
3270
+
3271
+ self.filePath = filePath
3272
+ self.acName = acName
3273
+
3274
+ self.model = AC_parsed.model
3275
+ self.engineType = AC_parsed.engineType
3276
+ self.engines = AC_parsed.engines
3277
+ self.ICAO_desig = AC_parsed.ICAO_desig
3278
+ self.WTC = AC_parsed.ICAO_desig["WTC"]
3279
+ self.ICAO = AC_parsed.ICAO_desig["designator"]
3280
+ self.MR_radius = AC_parsed.MR_radius
3281
+ self.crs = AC_parsed.crs
3282
+ self.cpr = AC_parsed.cpr
3283
+ self.n_eng = AC_parsed.n_eng
3284
+ self.P0 = AC_parsed.P0
3285
+ self.cVoc = AC_parsed.cVoc
3286
+ self.cR0 = AC_parsed.cR0
3287
+ self.cRi = AC_parsed.cRi
3288
+ self.Imax = AC_parsed.Imax
3289
+ self.Vmin = AC_parsed.Vmin
3290
+ self.capacity = AC_parsed.capacity
3291
+ self.eta = AC_parsed.eta
3292
+ self.Pmax_ = AC_parsed.Pmax_
3293
+ self.hmo = AC_parsed.hmo
3294
+ self.vne = AC_parsed.vne
3295
+ self.MTOW = AC_parsed.MTOW
3296
+ self.OEW = AC_parsed.OEW
3297
+ self.MFL = AC_parsed.MFL
3298
+
3299
+ self.VMO = None
3300
+ self.MMO = None
3301
+
3302
+ BADAE.__init__(self, AC_parsed)
3303
+ self.flightEnvelope = FlightEnvelope(AC_parsed)
3304
+ self.OPT = Optimization(AC_parsed)
3305
+ self.ARPM = ARPM(AC_parsed)
3306
+ self.PTD = PTD(AC_parsed)
3307
+ self.PTF = PTF(AC_parsed)
3308
+
3309
+ self.BADAFamily = BadaFamily(BADAE=True)
3310
+ self.BADAFamilyName = "BADAE"
3311
+
3312
+ else:
3313
+ # AC name cannot be found
3314
+ raise ValueError(acName + " Cannot be found")
3315
+
3316
+ def __str__(self):
3317
+ return f"(BADAE, AC_name: {self.acName}, searched_AC_name: {self.SearchedACName}, model_ICAO: {self.ICAO}, ID: {id(self.AC)})"