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