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