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.
- pyBADA/TCL.py +8731 -0
- pyBADA/__init__.py +0 -0
- pyBADA/aircraft/BADA3/DUMMY/BADA.GPF +113 -0
- pyBADA/aircraft/BADA3/DUMMY/BZJT__.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/BZJT__.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/BZJT__.PTD +139 -0
- pyBADA/aircraft/BADA3/DUMMY/BZJT__.PTF +73 -0
- pyBADA/aircraft/BADA3/DUMMY/GA____.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/GA____.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/GA____.PTD +71 -0
- pyBADA/aircraft/BADA3/DUMMY/GA____.PTF +39 -0
- pyBADA/aircraft/BADA3/DUMMY/J2H___.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/J2H___.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/J2H___.PTD +131 -0
- pyBADA/aircraft/BADA3/DUMMY/J2H___.PTF +69 -0
- pyBADA/aircraft/BADA3/DUMMY/J2M___.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/J2M___.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/J2M___.PTD +123 -0
- pyBADA/aircraft/BADA3/DUMMY/J2M___.PTF +65 -0
- pyBADA/aircraft/BADA3/DUMMY/J4H___.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/J4H___.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/J4H___.PTD +139 -0
- pyBADA/aircraft/BADA3/DUMMY/J4H___.PTF +73 -0
- pyBADA/aircraft/BADA3/DUMMY/ReleaseSummary +35 -0
- pyBADA/aircraft/BADA3/DUMMY/SYNONYM.NEW +155 -0
- pyBADA/aircraft/BADA3/DUMMY/TP2M__.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/TP2M__.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/TP2M__.PTD +99 -0
- pyBADA/aircraft/BADA3/DUMMY/TP2M__.PTF +53 -0
- pyBADA/aircraft/BADA4/DUMMY/ACM_BADA4.xsd +556 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST.ATF +106 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST.xml +244 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA+20.PTD +182 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA+20.PTF +41 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA.PTD +182 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA.PTF +41 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP.ATF +191 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP.xml +540 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA+20.PTD +218 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA+20.PTF +49 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA.PTD +218 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA.PTF +49 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/LRC.dat +38 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/MEC.dat +58 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/MRC.dat +38 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/OPTALT.dat +37 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN.ATF +204 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN.xml +648 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA+20.PTD +281 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA+20.PTF +63 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA.PTD +281 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA.PTF +63 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/ECON.OPT +37 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/LRC.OPT +38 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/MEC.OPT +58 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/MRC.OPT +38 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/OPTALT.OPT +37 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus.ATF +238 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus.xml +737 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA+20.PTD +281 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA+20.PTF +63 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA.PTD +281 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA.PTF +63 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/ECON.OPT +37 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/LRC.OPT +36 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/MEC.OPT +56 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/MRC.OPT +36 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/OPTALT.OPT +37 -0
- pyBADA/aircraft/BADA4/DUMMY/GPF.xml +130 -0
- pyBADA/aircraft/BADA4/DUMMY/GPF_BADA4.xsd +75 -0
- pyBADA/aircraft/BADA4/DUMMY/aircraft_model_default.xml +311 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH.ATF +97 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH.xml +82 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA+20.PTD +632 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA+20.PTF +71 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTD +632 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTF +71 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/LRC.OPT +142 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/MEC.OPT +142 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/MRC.OPT +142 -0
- pyBADA/aircraft.py +414 -0
- pyBADA/atmosphere.py +345 -0
- pyBADA/bada3.py +4566 -0
- pyBADA/bada4.py +5327 -0
- pyBADA/badaE.py +3317 -0
- pyBADA/badaH.py +3632 -0
- pyBADA/configuration.py +98 -0
- pyBADA/constants.py +64 -0
- pyBADA/conversions.py +197 -0
- pyBADA/data/magneticDeclinationGridData.json +247067 -0
- pyBADA/flightTrajectory.py +929 -0
- pyBADA/geodesic.py +760 -0
- pyBADA/magnetic.py +119 -0
- pyBADA/trajectoryPrediction.py +175 -0
- pybada-0.1.0.dist-info/METADATA +57 -0
- pybada-0.1.0.dist-info/RECORD +99 -0
- pybada-0.1.0.dist-info/WHEEL +4 -0
- pybada-0.1.0.dist-info/licenses/AUTHORS +2 -0
- 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)})"
|