"""
Module choice_interf
Choice interface: used to set the required parameters for the noise calculation and call the physics-based methods from
the module choice_physics.
"""
import sys
import math
import numpy as np
import choice.choice_physics as choice_physics
import choice.choice_aux as choice_aux
import choice.choice_data as choice_data
from scipy.interpolate import interp1d
maxNoPts = 3 # Maximum number of operating points
version_num = '1.0'
[docs]def get_version_num(fname):
""" Writes CHOICE version number in output file. """
with open(fname, 'w') as f:
f.write('! Choice version: ' + version_num)
[docs]class WeightChoice:
"""
Instantiate the engine size and architecture data.
:param dict weightFile: Engine sizing data.
"""
def __init__(self, weightFile):
""" Constructs all the necessary attributes for the noise calculation, from the provided weightAircraft file."""
self.MtipD_fan = weightFile.get('MtipFan')
if self.MtipD_fan is not None and (self.MtipD_fan > 3.0 or self.MtipD_fan < 0.7):
print('unexpected value on MtipD_fan in WeightChoice in choice_interf')
self.xnlD_fan = weightFile.get('xnl')
if self.xnlD_fan is not None and self.xnlD_fan > 600:
print('xnl from weightFile very large. Unit should always be rps!', 'setFan', 'SetWeightChoice')
self.rss_fan = weightFile.get('FanRss')
if self.rss_fan is not None and (self.rss_fan > 300.0 or self.rss_fan < 5.0):
print('unexpected value on rss_fan in WeightChoice in choice_interf')
self.N_rotors_fan = int(weightFile.get('FanR1BNb'))
self.N_stators_fan = int(weightFile.get('FanVsOgvNb'))
self.A2_fan = weightFile.get('FanA2')
if self.A2_fan is not None and (self.A2_fan > 50.0 or self.A2_fan < 0.1):
print('unexpected value on A2_fan in WeightChoice in choice_interf')
self.D1_fan = weightFile.get('FanR1BDiaOuter')
if self.D1_fan is not None and (self.D1_fan < 0.1 or self.D1_fan > 10.0):
print('unexpected value on D1_fan in WeightChoice in choice_interf')
self.n_stages_LPC = weightFile.get('stages_LPC')
self.gbx_ratio = weightFile.get('GBX_ratio')
self.MtipD_lpc = weightFile.get('MtipLpc')
self.xnlD_lpc = self.xnlD_fan * self.gbx_ratio
self.rss_lpc = weightFile.get('RSS_compr')
self.N_rotors_lpc = int(weightFile.get('N_compr'))
self.N_stators_lpc = int(weightFile.get('S_compr'))
self.D1_lpc = 2 * weightFile.get('r_compr')
self.Dh1_lpc = 2 * weightFile.get('rh_compr')
self.gbx_ratio = weightFile.get('GBX_ratio')
self.N_rotors_lpt = weightFile.get('LptStgLastBNb')
self.SRS_lpt = weightFile.get('SRS')
self.n_stages_lpt = weightFile.get('stages_LPT')
self.De_lpt = weightFile.get('LptStgLastDiaOuter')
self.Ae_lpt = weightFile.get('LptStgLastAExit')
# Build-in combustor model data, derived from the noise prediction method development literature
self.type_comb = 'SAC'
self.Nfmax_comb = 40 # total number of DAC fuel nozzles
self.Aec_comb = 0.13926166 / (choice_data.ft2m ** 2) # combustor exit area (ft^2)
self.De_comb = 10.543032 / choice_data.ft2m # exhaust nozzle exit plane effective diameter (ft)
self.Dh_comb = 4.218432 / choice_data.ft2m # exhaust nozzle exit plane hydraulic diameter (ft)
self.Lc_comb = 0.13968984 / choice_data.ft2m # combustor nominal length (ft)
self.h_comb = 0.06348984 / choice_data.ft2m # annulus height at combustor exit (ft)
if weightFile.get('CombType') is not None:
self.type_comb = weightFile.get('CombType')
if weightFile.get('maxNo_nozzles_dac') is not None:
self.Nfmax_comb = weightFile.get('maxNo_nozzles_dac')
if weightFile.get('A_comb_exit') is not None:
self.Aec_comb = weightFile.get('A_comb_exit') / (choice_data.ft2m ** 2)
if weightFile.get('Deff_comb') is not None:
self.De_comb = weightFile.get('Deff_comb') / choice_data.ft2m
if weightFile.get('Dhyd_comb') is not None:
self.Dh_comb = weightFile.get('Dhyd_comb') / choice_data.ft2m
if weightFile.get('Lc_comb') is not None:
self.Lc_comb = weightFile.get('Lc_comb') / choice_data.ft2m
if weightFile.get('h_annulus_comb') is not None:
self.h_comb = weightFile.get('h_annulus_comb') / choice_data.ft2m
self.A_core_caj = weightFile.get('A_core')
self.A_bypass_caj = weightFile.get('A_bypass')
self.MtipD_ff = weightFile.get('Mtipff')
if self.MtipD_ff is not None and (self.MtipD_ff > 3.0 or self.MtipD_ff < 0.7):
print('unexpected value on MtipD_ff in WeightChoice in choice_interf')
self.xnlD_ff = weightFile.get('xnlff')
if self.xnlD_ff is not None and self.xnlD_ff > 600:
print('xnlff from weightFile very large. Unit should always be rps!', 'setff', 'SetWeightChoice')
self.rss_ff = weightFile.get('ffRss')
if self.rss_ff is not None and (self.rss_ff > 300.0 or self.rss_ff) < 5.0:
print('unexpected value on rss_ff in WeightChoice in choice_interf')
self.N_rotors_ff = weightFile.get('ffR1BNb')
self.N_stators_ff = weightFile.get('ffVsOgvNb')
self.A2_ff = weightFile.get('ffA2')
if self.A2_ff is not None and (self.A2_ff > 50.0 or self.A2_ff < 0.1):
print('unexpected value on A2_ff in WeightChoice in choice_interf')
self.D1_ff = weightFile.get('ffR1BDiaOuter')
if self.D1_ff is not None and (self.D1_ff < 0.1 or self.D1_ff > 10.0):
print('unexpected value on D1_ff in WeightChoice in choice_interf')
self.A_core_caj_ffn = weightFile.get('A_core_ffn')
self.A_bypass_caj_ffn = weightFile.get('A_bypass_ffn')
[docs]class NoiseChoice:
"""
Instantiate the required input arguments for the noise prediction cases to be run.
:param dict noiseFile: Definition of the noise cases to be performed.
"""
def __init__(self, noiseFile):
""" Constructs all the necessary attributes for the noise calculation, from the provided inputNoise file. """
self.opPnt = noiseFile.get('point').split()
self.nops = len(self.opPnt)
if self.nops > maxNoPts: sys.exit('number of noise points greater than allowed')
self.trajectory_performance = False
if 'true' in noiseFile.get('trajectory_performance'): self.trajectory_performance = True
self.use_trajectory_preparser = False
if 'true' in noiseFile.get('use_trajectory_preparser'): self.use_trajectory_preparser = True
self.gen_noise_source_matr = False
if 'true' in noiseFile.get('generate_noise_sources'): self.gen_noise_source_matr = True
self.plot_source_spectra = False
if 'true' in noiseFile.get('plot_source_spectra'):
self.plot_source_spectra = True
self.directivity_plot = float(noiseFile.get('directivity_angle_to_plot'))
else:
self.directivity_plot = None
self.use_ground_reflection = True
if 'false' in noiseFile.get('use_ground_reflection'): self.use_ground_reflection = False
self.use_spherical_spreading = True
if 'false' in noiseFile.get('use_spherical_spreading'): self.use_spherical_spreading = False
self.use_atmospheric_attenuation = True
if 'false' in noiseFile.get('use_atmospheric_attenuation'): self.use_atmospheric_attenuation = False
self.use_suppression_factor = False
self.S_fan_inlet = 0
self.S_fan_dis = 0
self.S_lpt = 0
if 'true' in noiseFile.get('use_suppression_factor'):
self.use_suppression_factor = True
self.S_fan_inlet = noiseFile.get('fan_inlet_suppression')
self.S_fan_dis = noiseFile.get('fan_dis_suppression')
self.S_lpt = noiseFile.get('lpt_suppression')
# type_nozzles only affects the total EPNL calculation. If it is set to mix, the fan discharge noise source
# will be removed. The default is separate nozzles.
self.type_nozzles = "separate"
self.type_nozzles = noiseFile.get('type_nozzles') # "separate" or "mix", other type will not be recognized
self.fan_distortion = False
if noiseFile.get('fan_distortion') == 1: self.fan_distortion = True
self.ff_distortion = False
if noiseFile.get('ff_distortion') == 1: self.ff_distortion = True
self.fuselage_fan = False
if 'true' in noiseFile.get('fuselage_fan'): self.fuselage_fan = True
self.xmic = np.zeros(3)
self.ymic = np.zeros(3)
self.zmic = np.zeros(3)
self.dt_mic = noiseFile.get('dt_mic')
self.elevation = noiseFile.get('ground_elevation')
self.Nf_comb_ign = np.zeros(3)
self.Nf_comb_pattern = np.zeros(3)
if self.nops == 1:
self.xmic[0] = noiseFile.get('xmic')
self.ymic[0] = noiseFile.get('ymic')
self.zmic[0] = noiseFile.get('zmic')
# Combustor ignited nozzles
self.Nf_comb_ign[0] = int(noiseFile.get('comb_ign_nozzles'))
self.Nf_comb_pattern = float(noiseFile.get('dac_nozzle_pattern'))
elif self.nops > 1:
if self.nops > maxNoPts: sys.stop('number of noise points greater than allowed')
self.xmic = np.array([float(xm) for xm in noiseFile.get('xmic').split()])
if len(self.xmic) != self.nops: sys.exit('xmic data not consistent with number of points')
self.ymic = np.array([float(ym) for ym in noiseFile.get('ymic').split()])
if len(self.ymic) != self.nops: sys.exit('ymic data not consistent with number of points')
self.zmic = np.array([float(zm) for zm in noiseFile.get('zmic').split()])
if len(self.zmic) != self.nops: sys.exit('zmic data not consistent with number of points')
# Combustor ignited nozzles
self.Nf_comb_ign = np.array([int(nf) for nf in noiseFile.get('comb_ign_nozzles').split()])
self.Nf_comb_pattern = np.array([float(nf) for nf in noiseFile.get('dac_nozzle_pattern').split()])
self.Sw_airfrm = noiseFile.get('wing_area') # wing area m2
self.span_airfrm = noiseFile.get('wing_span') # wing span m
self.ND = int(noiseFile.get('jet_aircraft_coef')) # coefficient for te noise (1 -> +6 dB)
self.flap_type = noiseFile.get('flap_type')
self.NoFlap = int(noiseFile.get('noFlaps'))
if self.NoFlap == 1:
self.S_flap_airfrm = noiseFile.get('flap_area') # flap area m2
self.span_flap_airfrm = noiseFile.get('flap_span') # flap span m
else:
self.S_flap_airfrm = np.array([float(sf) for sf in noiseFile.get('flap_area').split()])
self.span_flap_airfrm = np.array([float(bf) for bf in noiseFile.get('flap_span').split()])
self.slat_type = noiseFile.get('leading_edge_type')
self.span_hor_tail = noiseFile.get('horizontal_tail_span') # horizontal tail span m
self.Sht = noiseFile.get('horizontal_tail_area') # horizontal tail area m2
self.span_ver_tail = noiseFile.get('vertical_tail_span') # vertical tail span m
self.Svt = noiseFile.get('vertical_tail_area') # vertical tail area m2
self.no_engines = int(noiseFile.get('no_engines'))
self.h_engine = noiseFile.get('engine_height')
self.dtIsa = noiseFile.get('dtIsa')
self.total_weight_airfrm = noiseFile.get('aircraft_mass')
self.nlg_airfrm = int(noiseFile.get('noLG'))
if self.nlg_airfrm == 1:
self.d_wheel_airfrm[0] = noiseFile.get('d_wheel') / choice_data.ft2m
self.d_strut_airfrm[0] = noiseFile.get('d_strut') / choice_data.ft2m
self.d_wire_airfrm[0] = noiseFile.get('d_wire') / choice_data.ft2m
self.N_wheels_airfrm[0] = noiseFile.get('Nwheels')
self.N_struts_airfrm[0] = noiseFile.get('Nstruts')
else:
self.d_wheel_airfrm = np.array([float(dw) for dw in noiseFile.get('d_wheel').split()]) / choice_data.ft2m
self.d_strut_airfrm = np.array([float(ds) for ds in noiseFile.get('d_strut').split()]) / choice_data.ft2m
self.d_wire_airfrm = np.array([float(dw) for dw in noiseFile.get('d_wire').split()]) / choice_data.ft2m
self.N_wheels_airfrm = np.array([float(nw) for nw in noiseFile.get('Nwheels').split()])
self.N_struts_airfrm = np.array([float(ns) for ns in noiseFile.get('Nstruts').split()])
self.defl_flap_airfrm_vec = np.zeros(3)
self.defl_slat_airfrm_vec = np.zeros(3)
self.LandingGear = np.zeros(3)
if self.nops == 1:
self.defl_flap_airfrm_vec[0] = noiseFile.get('defl_flap')
self.defl_slat_airfrm_vec[0] = noiseFile.get('defl_slat')
self.LandingGear[0] = noiseFile.get('LandingGear_vec')
else:
self.defl_flap_airfrm_vec = np.array([float(df) for df in noiseFile.get('defl_flap').split()])
self.defl_slat_airfrm_vec = np.array([float(ds) for ds in noiseFile.get('defl_slat').split()])
self.LandingGear = np.array([int(lg) for lg in noiseFile.get('LandingGear_vec').split()])
[docs]class Trajectory:
"""
Instantiate the trajectory data of the aircraft.
:param int ipnt: Operating point (trajectory) number in order of appearance in the inputNoise.txt
:param str opPnt: Operating point (trajectory), e.g. Approach, Cutback, Sideline
:param NoiseChoice noise_choice: Noise prediction input data
"""
def __init__(self, ipnt, opPnt, noise_choice, input_folder):
""" Constructs all the necessary attributes for the trajectory object to be used in the noise calculation,
for the selected trajectories. Computes distance to mic (r), flight path angle (clgr), time and angle between
aircraft and microphone (xsi). """
self.read_trajectory_input(opPnt, input_folder, noise_choice.h_engine) # read trajectory data from file
if 'Approach' in opPnt and self.x[0]>0: self.x = self.x - self.x[-1]
self.r = np.sqrt((self.x - noise_choice.xmic[ipnt]) ** 2 +
(self.y - noise_choice.ymic[ipnt] - noise_choice.elevation) ** 2 +
noise_choice.zmic[ipnt] ** 2) # distance along trajectory to microphone
self.clgr = np.zeros(self.n_traj_pts) # flight path angle along trajectory
for i in range(self.n_traj_pts - 1):
if self.y[i + 1] != 0:
self.clgr[i] = np.rad2deg(math.atan((self.y[i + 1] - self.y[i]) / (self.x[i + 1] - self.x[i])))
if np.isnan(self.clgr[i]): sys.exit('not a number (NaN) computed for clgr')
self.clgr[self.n_traj_pts - 1] = self.clgr[self.n_traj_pts - 2]
self.get_xsi(noise_choice.xmic[ipnt], noise_choice.ymic[ipnt] + noise_choice.elevation, noise_choice.zmic[ipnt])
if np.isnan(self.xsi).any(): sys.exit('not a number computed for xsi')
self.xsid = np.rad2deg(self.xsi)
# time is computed for 2D trajectory
self.time = np.zeros(self.n_traj_pts)
for i in range(1, self.n_traj_pts):
if math.sqrt((self.y[i] - self.y[i - 1]) ** 2 + (self.x[i] - self.x[i - 1]) ** 2) == 0:
self.time[i] = self.time[i - 1] + 1
# aircraft is not moving - impossible to compute time => set a fictitious time
else:
# use average velocity
self.V_avg = (self.Va[i] + self.Va[i - 1]) / 2
self.time[i] = self.time[i - 1] + math.sqrt(
(self.y[i] - self.y[i - 1]) ** 2 + (self.x[i] - self.x[i - 1]) ** 2) / self.V_avg
if np.isnan(self.time[i]): sys.exit('not a number computed for time')
[docs] def set_time_vector(self, ipnt, noise_choice):
"""
Computes the retarded time.
:param int ipnt: Operating point (trajectory) number in order of appearance in the inputNoise.txt
:param NoiseChoice noise_choice: NoiseChoice object
"""
dt = noise_choice.dt_mic
# create tmic (starting from when the first noise reaches the microphone from xstart (i.e. r[0]/a[0]) to when
# the noise reaches the microphone from the last point)
self.tmic = np.arange(self.r[0] / self.a[0], self.time[-1] + self.r[-1] / self.a[-1], dt)
self.n_times = len(self.tmic)
self.t_source = np.zeros(self.n_times)
self.x_source = np.zeros(self.n_times)
self.y_source = np.zeros(self.n_times)
self.va_source = np.zeros(self.n_times)
self.a_source = np.zeros(self.n_times)
self.r1 = np.zeros(self.n_times)
self.clgr_source = np.zeros(self.n_times)
self.x_source[0] = self.x[0] - noise_choice.xmic[ipnt] # use mic-related coordinate system
self.y_source[0] = self.y[0] - (noise_choice.ymic[ipnt] + noise_choice.elevation) # use mic-related coordinate system
self.va_source[0] = self.Va[0]
self.a_source[0] = self.a[0]
self.clgr_source[0] = self.clgr[0]
self.z_source = noise_choice.zmic[ipnt]
self.r1[0] = math.sqrt(self.x_source[0] ** 2 + self.y_source[0] ** 2 + self.z_source ** 2)
# distance between source and microphone.
for i in range(1, self.n_times):
if self.va_source[i - 1] == 0:
dx = 0
else:
dx = self.get_dx(self.x_source[i - 1], self.y_source[i - 1], self.z_source, dt,
self.clgr_source[i - 1], self.a_source[i - 1], self.va_source[i - 1],
self.r1[i - 1] / self.a_source[i - 1] + dt)
# update using dx
self.x_source[i] = self.x_source[i - 1] + dx
dy = dx * math.tan(np.radians(self.clgr_source[i - 1]))
self.y_source[i] = self.y_source[i - 1] + dy
dr = math.sqrt(dx ** 2 + dy ** 2)
self.r1[i] = math.sqrt(self.x_source[i] ** 2 + self.y_source[i] ** 2 + self.z_source ** 2)
if dr == 0:
self.t_source[i] = self.t_source[i - 1] + dt
else:
self.t_source[i] = self.t_source[i - 1] + dr / self.va_source[i - 1]
# interpolate
self.clgr_source[i] = interp1d(self.x, self.clgr, fill_value="extrapolate")(
self.x_source[i] + noise_choice.xmic[ipnt])
self.va_source[i] = interp1d(self.x, self.Va, fill_value="extrapolate")(
self.x_source[i] + noise_choice.xmic[ipnt])
self.a_source[i] = interp1d(self.x, self.a, fill_value="extrapolate")(
self.x_source[i] + noise_choice.xmic[ipnt])
[docs] def set_velocity(self, dtIsa):
"""
Computes ambient temperature (ta), speed of sound (a) and Mach number (Ma) along the trajectory
:param float dtIsa: Deviation from ISA temperature
"""
R = choice_aux.get_R(0.0)
self.pa = choice_physics.AtmosphericEffects.get_p_ambient(self.y)
self.ta = choice_physics.AtmosphericEffects.get_t_ambient(self.y, dtIsa)
self.a = choice_physics.AtmosphericEffects.get_sound_speed(self.ta)
self.Ma = self.Va / self.a
[docs] @staticmethod
def get_dx(x1, y1, z1, dt, gamma, c0, Va, tnext):
"""
Computes distance travelled until the location of the next noise sample generation.
:param float x1: x coordinate of first sample (in relation to microphone)
:param float y1: y coordinate (height) of first sample (in relation to microphone)
:param float z1: z coordinate of microphone
:param float dt: Time interval between sounds reaching the microphone
:param float gamma: Flight path angle
:param float c0: Speed of sound at aircraft location
:param float Va: Aircraft speed
:param float tnext: Next sampling time
:return float: The distance that the aircraft travelled
"""
solution_found = False
tg = math.tan(np.radians(gamma))
r1 = math.sqrt(x1 ** 2 + y1 ** 2 + z1 ** 2)
a = ((1.0 + tg ** 2) * (c0 ** 2 - Va ** 2)) / Va ** 2
b = -2.0 * (r1 + c0 * dt) * c0 * math.sqrt(1.0 + tg ** 2) / Va - 2.0 * x1 - 2.0 * y1 * tg
c = (c0 ** 2) * (dt ** 2) + 2 * r1 * c0 * dt
sol1 = (-b + math.sqrt(b ** 2 - 4.0 * a * c)) / (2.0 * a)
sol2 = (-b - math.sqrt(b ** 2 - 4.0 * a * c)) / (2.0 * a)
# pick solution
x2 = x1 + sol1
y2 = y1 + sol1 * tg
z2 = z1
dr = math.sqrt(sol1 ** 2 + (sol1 * tg) ** 2)
attempt1 = math.sqrt(x2 ** 2 + y2 ** 2 + z2 ** 2) / c0 + dr / Va
res = abs(attempt1 - tnext) / max(1.0, tnext)
if res < 1000 * sys.float_info.epsilon:
dx = sol1
solution_found = True
x2 = x1 + sol2
y2 = y1 + sol2 * tg
dr = math.sqrt(sol2 ** 2 + (sol2 * tg) ** 2)
attempt2 = math.sqrt(x2 ** 2 + y2 ** 2 + z2 ** 2) / c0 + dr / Va
res = abs(attempt2 - tnext) / max(1.0, tnext)
if res < 1000 * sys.float_info.epsilon:
dx = sol2
solution_found = True
if not solution_found:
print('failed to establish solution in get_dx in choice_interf')
return
else:
return dx
[docs] def get_xsi(self, xmic, ymic, zmic):
"""
Computes the angle between the direction of the aircraft's motion and the sound propagation path.
:param float xmic: Microphone/observer horizontal distance (m)
:param float ymic: Microphone/observer height (m)
:param float zmic: Microphone/observer lateral horizontal distance (m)
"""
if zmic > 0:
ex = (xmic - self.x) / np.sqrt((xmic - self.x)**2 + zmic**2 + (ymic - self.y)**2)
ey = (self.y - ymic) / np.sqrt((xmic - self.x)**2 + zmic**2 + (ymic - self.y)**2)
ez = zmic / np.sqrt((xmic - self.x)**2 + zmic**2 + (ymic - self.y)**2)
ex_body = ex * np.cos(np.deg2rad(self.clgr)) - ey * np.sin(np.deg2rad(self.clgr))
ey_body = ex * np.sin(np.deg2rad(self.clgr)) + ey * np.cos(np.deg2rad(self.clgr))
ez_body = ez
fi = np.arccos(ex_body)
self.xsi = fi
self.phi = np.degrees(np.arctan2(ey, ez))
ex_body_w_alpha = ex * np.cos(np.deg2rad(self.clgr + self.alpha)) - \
ey * np.sin(np.deg2rad(self.clgr + self.alpha))
self.xsi_alpha = np.arccos(ex_body_w_alpha)
else:
fi = np.arctan((xmic - self.x) / (self.y - ymic))
fi[self.y <= ymic] = math.pi / 2
psi = math.pi / 2 - fi
self.xsi = psi + np.deg2rad(self.clgr)
self.xsi_alpha = psi + np.deg2rad(self.clgr + self.alpha)
[docs] @classmethod
def set(cls, ipnt, opPnt, noise_choice, input_folder):
"""
Sets the required trajectory details for the noise calculation.
:param int ipnt: Operating point (trajectory) number in order of appearance in the inputNoise.txt
:param str opPnt: Operating point (trajectory), e.g. Approach, Cutback, Sideline
:param NoiseChoice noise_choice: NoiseChoice object
:return Trajectory: A trajectory object
"""
traj = cls(ipnt, opPnt, noise_choice, input_folder) # Compute r, clgr, xsi and time
traj.set_velocity(noise_choice.dtIsa) # compute ta, a and Ma
traj.set_time_vector(ipnt, noise_choice) # compute "retarded" vectors at source
return traj
[docs]def set_rotational_speeds_choice(mpd, weightChoice, comp):
"""
Estimates absolute speeds from relative rotating speeds (from performance) using several points.
:param MultptPerfData mpd: Multipoint performance data
:param WeightChoice weightChoice: Engine sizing data
:param str comp: Engine component
:return MultptPerfData: Updated multipoint performance data
"""
N = {}
if comp == 'Fan':
param = 'NL'
xnlD = weightChoice.xnlD_fan
elif comp == 'Fuselage_fan':
param = 'NFF'
xnlD = weightChoice.xnlD_ff
for key in mpd.__dict__:
N[key] = mpd.__dict__[key].get(param)
if len(mpd.perf_file_sl) > 0: N["perf_file_to"] = N["perf_file_sl"] # use sideline data instead of take-off if
# sideline exists.
# form ratios and use the WEICO value
for key in N.keys():
N[key] = xnlD * (N[key] / N["perf_file_toc"])
mpd.__dict__[key][param] = N[key]
return mpd
[docs]class Prms:
"""
Instantiate rms acoustic pressure for each aircraft noise component.
:param int npts: Number of trajectory points
"""
def __init__(self, npts):
""" Initializes the prms matrix for each component. """
nfreq = choice_data.nfreq
nthet = choice_data.nthet
self.Fan_inlet_tone = np.zeros((nfreq, nthet, npts))
self.Fan_discharge_tone = np.zeros((nfreq, nthet, npts))
self.Fan_inlet_broadband = np.zeros((nfreq, nthet, npts))
self.Fan_discharge_broadband = np.zeros((nfreq, nthet, npts))
self.Fan_inlet_combination = np.zeros((nfreq, nthet, npts))
self.Fan_inlet = np.zeros((nfreq, nthet, npts))
self.Fan_discharge = np.zeros((nfreq, nthet, npts))
self.Lpc_inlet_tone = np.zeros((nfreq, nthet, npts))
self.Lpc_discharge_tone = np.zeros((nfreq, nthet, npts))
self.Lpc_inlet_broadband = np.zeros((nfreq, nthet, npts))
self.Lpc_discharge_broadband = np.zeros((nfreq, nthet, npts))
self.Lpc_inlet_combination = np.zeros((nfreq, nthet, npts))
self.Lpc_inlet = np.zeros((nfreq, nthet, npts))
self.Lpc_discharge = np.zeros((nfreq, nthet, npts))
self.Comb = np.zeros((nfreq, nthet, npts))
self.Lpt = np.zeros((nfreq, nthet, npts))
self.Caj = np.zeros((nfreq, nthet, npts))
self.Airfrm = np.zeros((nfreq, nthet, npts))
self.Airfrm_wing = np.zeros((nfreq, nthet, npts))
self.Airfrm_hor_tail = np.zeros((nfreq, nthet, npts))
self.Airfrm_slats = np.zeros((nfreq, nthet, npts))
self.Airfrm_flaps = np.zeros((nfreq, nthet, npts))
self.Airfrm_nose_lg = np.zeros((nfreq, nthet, npts))
self.Airfrm_main_lg = np.zeros((nfreq, nthet, npts))
self.Airfrm_lg = np.zeros((nfreq, nthet, npts))
self.Ff_inlet_tone = np.zeros((nfreq, nthet, npts))
self.Ff_discharge_tone = np.zeros((nfreq, nthet, npts))
self.Ff_inlet_broadband = np.zeros((nfreq, nthet, npts))
self.Ff_discharge_broadband = np.zeros((nfreq, nthet, npts))
self.Ff_inlet_combination = np.zeros((nfreq, nthet, npts))
self.Ff_inlet = np.zeros((nfreq, nthet, npts))
self.Ff_discharge = np.zeros((nfreq, nthet, npts))
self.Caj_ffn = np.zeros((nfreq, nthet, npts))
[docs]class NoiseSources:
"""
Instantiate noise source object
:param Prms prms: Acoustic pressure arrays for each aircraft noise component
:param ndarray theta: 1D array containg directivity angles
:param ndarray fband: 1D array containg the 1/3 octave band frequencies
"""
def __init__(self, prms, theta, fband):
self.prms = prms
self.theta = theta
self.fband = fband
[docs] @classmethod
def compute(cls, traj, modules, noise, weight, performance, nop, output_folder, ext=None):
"""
Compute the acoustic pressure for each noise component.
:param Trajectory traj: A Trajectory object with the trajectory data
:param list modules: Fan, Lpc, Lpt, etc.
:param NoiseChoice noise: A NoiseChoice object with the required input arguments for the noise prediction
:param WeightChoice weight: A WeightChoice object with the engine size and architecture
:param PerformanceChoice performance: A PerformanceChoice object with the engine performance data
:param int nop: Number of operating point
:param str output_folder: Output folder path
:param str ext: File extension for noise source matrices
:return NoiseSources: A Prms object with the rms acoustic pressure for each component, the directivity angles
and the 1/3 octave band frequencies
"""
theta = np.array([0.0 + i * 5.0 for i in range(choice_data.nthet)])
[fband, f, freq] = choice_aux.set_frequencies(choice_data.nb, choice_data.nfreq, float(choice_data.fmin),
float(choice_data.fmax))
# flyover plane
prms = Prms(traj.n_traj_pts)
operatingPoint = noise.opPnt[nop].strip()
airframe = choice_physics.Airframe(operatingPoint, noise.N_wheels_airfrm, noise.N_struts_airfrm,
noise.nlg_airfrm, noise.d_wheel_airfrm, noise.d_strut_airfrm,
noise.d_wire_airfrm, noise.NoFlap, noise.S_flap_airfrm,
noise.span_flap_airfrm, noise.Sw_airfrm, noise.span_airfrm, noise.ND,
noise.span_hor_tail, noise.Sht, noise.span_ver_tail, noise.Svt,
noise.flap_type, noise.slat_type, theta, fband)
if 'Cold_nozzle' in modules: jet = choice_physics.Jet(weight.A_core_caj, weight.A_bypass_caj,
noise.type_nozzles, theta, fband)
if 'Comb' in modules:
comb = choice_physics.Combustor(weight.type_comb, weight.Aec_comb, weight.De_comb, weight.Dh_comb,
weight.Lc_comb, weight.h_comb, weight.Nfmax_comb, theta, fband)
if 'Lpt' in modules:
turb = choice_physics.Turbine(weight.N_rotors_lpt, weight.SRS_lpt, theta, fband, f)
if 'Fan' in modules:
fan = choice_physics.FanCompressor('Fan', weight.MtipD_fan, weight.N_rotors_fan, weight.N_stators_fan,
weight.rss_fan, theta, fband, f, noise.fan_distortion)
if 'Ipc' in modules:
lpc = choice_physics.FanCompressor('Ipc', weight.MtipD_lpc, weight.N_rotors_lpc, weight.N_stators_lpc,
weight.rss_lpc, theta, fband, f)
if 'Lpc' in modules:
lpc = choice_physics.FanCompressor('Lpc', weight.MtipD_lpc, weight.N_rotors_lpc, weight.N_stators_lpc,
weight.rss_lpc, theta, fband, f)
if 'Fuselage_fan' in modules:
ffan = choice_physics.FanCompressor('Fuselage_fan', weight.MtipD_ff, weight.N_rotors_ff,
weight.N_stators_ff, weight.rss_ff, theta, fband, f,
noise.ff_distortion)
if 'Ff_nozzle' in modules: ffjet = choice_physics.Jet(weight.A_core_caj_ffn, weight.A_bypass_caj_ffn, 'mix')
# evaluate component noise models
for i in range(traj.n_traj_pts): # for all points along trajectory - evaluate noise sources
pa = choice_physics.AtmosphericEffects.get_p_ambient(traj.y[i])
for module in modules: # evaluate all noise sources in this engine model
if module.rstrip() == 'Fan':
temp = fan.calc(operatingPoint, performance.Mtip_fan[i], performance.Mu_fan[i],
performance.dt_fan[i], performance.xnl_fan[i], performance.g1_fan[i])
if noise.use_suppression_factor:
prms.Fan_inlet[:, :, i] = fan.suppression(temp[5], noise.S_fan_inlet)
prms.Fan_discharge[:, :, i] = fan.suppression(temp[6], noise.S_fan_dis)
prms.Fan_inlet_tone[:, :, i] = fan.suppression(temp[0], noise.S_fan_inlet)
prms.Fan_discharge_tone[:, :, i] = fan.suppression(temp[1], noise.S_fan_dis)
prms.Fan_inlet_broadband[:, :, i] = fan.suppression(temp[2], noise.S_fan_inlet)
prms.Fan_discharge_broadband[:, :, i] = fan.suppression(temp[3], noise.S_fan_dis)
prms.Fan_inlet_combination[:, :, i] = fan.suppression(temp[4], noise.S_fan_inlet)
else:
prms.Fan_inlet[:, :, i] = temp[5]
prms.Fan_discharge[:, :, i] = temp[6]
prms.Fan_inlet_tone[:, :, i] = temp[0]
prms.Fan_discharge_tone[:, :, i] = temp[1]
prms.Fan_inlet_broadband[:, :, i] = temp[2]
prms.Fan_discharge_broadband[:, :, i] = temp[3]
prms.Fan_inlet_combination[:, :, i] = temp[4]
elif module == 'Ipc' or module == 'Lpc':
temp = lpc.calc(operatingPoint, performance.Mtip_lpc[i], performance.Mu_lpc[i],
performance.dt_lpc[i], performance.xnl_lpc[i], performance.g1_lpc[i])
prms.Lpc_inlet_tone[:, :, i] = temp[0]
prms.Lpc_inlet_broadband[:, :, i] = temp[2]
prms.Lpc_inlet_combination[:, :, i] = temp[4]
prms.Lpc_inlet[:, :, i] = temp[5]
elif module == 'Lpt':
prms.Lpt[:, :, i] = turb.calc(performance.Vtr_lpt[i], performance.Texit_lpt[i],
performance.xnl_lpt[i], performance.mcore_lpt[i],
performance.Cax_lpt[i])
if noise.use_suppression_factor:
prms.Lpt[:, :, i] = turb.suppression(prms.Lpt[:, :, i], noise.S_lpt)
elif module == 'Comb':
prms.Comb[:, :, i] = comb.calc(min(noise.Nf_comb_ign[nop], weight.Nfmax_comb),
noise.Nf_comb_pattern, pa, performance.P3_comb[i],
performance.P4_comb[i], performance.P7_comb[i], traj.ta[i],
performance.T3_comb[i], performance.T4_comb[i],
performance.T5_comb[i], performance.W3_comb[i])
elif module == 'Cold_nozzle':
prms.Caj[:, :, i] = jet.calc(performance.dmdt_1_caj[i], performance.dmdt_2_caj[i],
performance.v_1_caj[i], performance.v_2_caj[i], performance.T_1_caj[i],
performance.T_2_caj[i], traj.ta[i], pa)
elif module == 'Fuselage_fan':
temp = ffan.calc(module, operatingPoint, performance.Mtip_ff[i], performance.Mu_ff[i],
performance.dt_ff[i], performance.xnl_ff[i], performance.g1_ff[i])
prms.Ff_inlet_tone[:, :, i] = temp[0]
prms.Ff_discharge_tone[:, :, i] = temp[1]
prms.Ff_inlet_broadband[:, :, i] = temp[2]
prms.Ff_discharge_broadband[:, :, i] = temp[3]
prms.Ff_inlet_combination[:, :, i] = temp[4]
prms.Ff_inlet[:, :, i] = temp[5]
prms.Ff_discharge[:, :, i] = temp[6]
elif module == 'ff_nozzle':
prms.Caj_ffn[:, :, i] = ffjet.calc(performance.dmdt_1_caj_ffn[i], 0.0, performance.v_1_caj_ffn[i],
0.0, performance.T_1_caj_ffn[i], 0.0, traj.ta[i], pa)
else:
pass
if hasattr(traj, 'phi'):
temp = airframe.calc(traj.ta[i], traj.y[i], traj.Ma[i], traj.Va[i], 90 - traj.phi[i],
performance.defl_flap_airfrm[i], performance.defl_slat_airfrm[i],
performance.LandingGear[i])
else:
temp = airframe.calc(traj.ta[i], traj.y[i], traj.Ma[i], traj.Va[i], 0, performance.defl_flap_airfrm[i],
performance.defl_slat_airfrm[i], performance.LandingGear[i])
prms.Airfrm[:, :, i] = temp[0]
prms.Airfrm_wing[:, :, i] = temp[1]
prms.Airfrm_hor_tail[:, :, i] = temp[2]
prms.Airfrm_slats[:, :, i] = temp[3]
prms.Airfrm_flaps[:, :, i] = temp[4]
prms.Airfrm_nose_lg[:, :, i] = temp[5]
prms.Airfrm_main_lg[:, :, i] = temp[6]
prms.Airfrm_lg[:, :, i] = temp[7]
# include multiple engines
for key in prms.__dict__:
if 'Airfrm' not in key:
prms.__dict__[key] = cls.include_multiple_engines(noise.no_engines, prms.__dict__[key])
if noise.gen_noise_source_matr:
choice_aux.gen_noise_source_matr_subr(output_folder, operatingPoint, choice_data.nfreq, choice_data.nthet,
traj.n_traj_pts, prms, ext)
if noise.plot_source_spectra:
i_plot = np.argmin(abs(traj.xsid - noise.directivity_plot))
choice_aux.plot_source(fband, theta, prms, noise.directivity_plot, i_plot, modules)
choice_aux.plot_airframe_source(fband, theta, prms.Airfrm, prms.Airfrm_wing, prms.Airfrm_hor_tail,
prms.Airfrm_flaps, prms.Airfrm_slats, prms.Airfrm_main_lg, prms.Airfrm_lg,
prms.Airfrm_nose_lg, noise.directivity_plot, i_plot)
return cls(prms, theta, fband)
[docs] @staticmethod
def include_multiple_engines(no_engines, prms):
""" Account for multiple engines on the aircraft. """
return np.sqrt(float(no_engines) * prms ** 2)
[docs]class NoiseMatrices:
"""
A class that defines the noise level for each aircraft noise component.
:param list modules: Fan, Lpc, Lpt, etc.
"""
def __init__(self, modules=None):
""" Initializes the noise level matrix for each component. """
if modules is not None:
if 'Fan' in modules:
self.Fan_inlet = []
self.Fan_discharge = []
self.Fan_inlet_tone = []
self.Fan_discharge_tone = []
self.Fan_inlet_broadband = []
self.Fan_discharge_broadband = []
self.Fan_inlet_combination = []
if 'Ipc' or 'Lpc' in modules:
self.Lpc_inlet = []
self.Lpc_inlet_tone = []
self.Lpc_inlet_broadband = []
self.Lpc_inlet_combination = []
if 'Lpt' in modules: self.Lpt = []
if 'Comb' in modules: self.Comb = []
if 'Cold_nozzle' in modules: self.Caj = []
if 'Fuselage_fan' in modules:
self.Ff_inlet = []
self.Ff_discharge = []
if 'Ff_nozzle' in modules:
self.Caj_ffn = []
self.Airfrm = []
[docs]def interpolate_to_t_source(traj, modules, prms):
"""
Computes the Sound Pressure Level for the times that the sound reaches the microphone.
:param Trajectory traj: A Trajectory object with the trajectory data
:param list modules: Fan, Lpc, Lpt, etc.
:param Prms prms: A Prms object with the rms acoustic pressure for each component
:return: For the times that the sound reaches the microphone, a NoiseMatrices object with the Sound Pressure Level
for each component, the observation angle, the Mach number and the angle of attack
"""
traj.xsii = interp1d(traj.time, traj.xsi, fill_value="extrapolate")(traj.t_source)
traj.Mai = interp1d(traj.time, traj.Ma, fill_value="extrapolate")(traj.t_source)
traj.Tai = interp1d(traj.time, traj.ta, fill_value="extrapolate")(traj.t_source)
traj.xsi_alphai = interp1d(traj.time, traj.xsi_alpha, fill_value="extrapolate")(traj.t_source)
traj.alphai = interp1d(traj.time, traj.alpha, fill_value="extrapolate")(traj.t_source)
traj.pai = interp1d(traj.time, traj.pa, fill_value="extrapolate")(traj.t_source)
if hasattr(traj, 'phi'):
traj.phii = interp1d(traj.time, traj.phi, fill_value="extrapolate")(traj.t_source)
SPLi = NoiseMatrices(modules)
for key in SPLi.__dict__:
SPLi.__dict__[key] = compute_SPLi(source_interpolation(prms.__dict__[key], traj))
return [SPLi, traj]
[docs]def compute_SPLi(prmsi):
""" Computes Sound Pressure Level matrix from rms acoustic pressure. """
prmsi[prmsi <= 0] = choice_data.p0
return 20.0 * np.log10(prmsi / choice_data.p0)
[docs]def source_interpolation(prms, traj):
""" Interpolate the rms acoustic pressure for the times that reach the microphone. """
return np.array([[interp1d(traj.time, prms[ifr, jth, :], fill_value="extrapolate")(traj.t_source) for jth in
range(choice_data.nthet)] for ifr in range(choice_data.nfreq)])
class GroundNoise:
def __init__(self, SPLp, fobs):
self.SPLp = SPLp
self.fobs = fobs
@classmethod
def compute_flight_effects(cls, use_ground_refl, spherical_spr, atm_atten, traj, ymic, dTisa, elevation, SPLi,
theta, fband):
"""
Computes the sound pressure level matrices along the trajectory accounting for propagation effects.
:param bool use_ground_refl: True to account for ground reflection or False else
:param bool spherical_spr: True to account for spherical spreading or False else
:param bool atm_atten: True to account for atmospheric attenuation or False else
:param Trajectory traj: A Trajectory object with the trajectory data
:param float ymic: Microphone height (m)
:param float dTisa: Deviation from ISA temperature (K)
:param float elevation: Ground elevation at microphone location (m)
:param ndarray SPLi: 3D array containing Sound Pressure Level
:param ndarray theta: 1D array containing directivity angles (deg)
:param ndarray fband: 1D array containing 1/3 octave band frequencies (Hz)
:return GroundNoise: Sound Pressure Level accounting for flight effects and shifted frequency due to aircraft
motion
"""
propagation = choice_physics.PropagationEffects(ymic, use_ground_refl, spherical_spr, atm_atten, fband,
traj.xsii, traj.Mai, traj.xsi_alphai, dTisa, elevation)
if hasattr(traj, 'phii'):
phi = traj.phii
else:
phi = np.nan
SPLp = NoiseMatrices()
prmsp = NoiseMatrices()
for key in SPLi.__dict__:
[SPLp.__dict__[key], prmsp.__dict__[key]] = \
propagation.flightEffects(traj.n_times, theta, traj.x_source, traj.y_source, traj.r1, traj.Tai,
SPLi.__dict__[key], key, phi)
return cls(SPLp, propagation.fobs)
[docs]class NoiseMatricesCerification:
"""
A class that defines the noise level for each aircraft noise component.
:param list modules: Fan, Lpc, Lpt, etc.
"""
def __init__(self, modules=None):
""" Initializes the noise level matrix for each component. """
if modules is not None:
if 'Fan' in modules:
self.Fan_inlet = []
self.Fan_discharge = []
if 'Ipc' or 'Lpc' in modules: self.Lpc_inlet = []
if 'Lpt' in modules: self.Lpt = []
if 'Comb' in modules: self.Comb = []
if 'Cold_nozzle' in modules: self.Caj = []
if 'Fuselage_fan' in modules:
self.Ff_inlet = []
self.Ff_discharge = []
if 'Ff_nozzle' in modules:
self.Caj_ffn = []
self.Airfrm = []
class CertificationData:
@staticmethod
def compute(n_times, fobs, fband, SPLp, modules, dt_mic):
"""
Computes the Effective Perceived Noise Level (EPNL) for each aircraft noise source and for the total
aircraft.
:param int n_times: Number of points
:param ndarray fobs: 1D array containing Doppler shifted frequency (Hz)
:param ndarray fband: 1D array containing 1/3 octave band frequencies (Hz)
:param NoiseSources SPLp: Sound Pressure Level (dB)
:param list modules: Fan, Lpc, Lpt, etc.
:param float dt_mic: Sampling time interval or timestep at microphone (sec)
:return NoiseSources: EPNL for each aircraft noise source and for the total aircraft
"""
PNL = NoiseMatricesCerification(modules)
PNLT = NoiseMatricesCerification(modules)
EPNL = NoiseMatricesCerification(modules)
SPLp_total = np.zeros_like(SPLp.Airfrm)
for key in PNL.__dict__:
PNL.__dict__[key] = choice_physics.PerceivedNoiseMetrics.getPNL(n_times, fobs, SPLp.__dict__[key])
PNLT.__dict__[key] = choice_physics.PerceivedNoiseMetrics.getPNLT(n_times, fband, PNL.__dict__[key],
SPLp.__dict__[key])
EPNL.__dict__[key] = choice_physics.PerceivedNoiseMetrics.getEPNL(PNLT.__dict__[key], dt_mic)
SPLp_total += 10 ** (SPLp.__dict__[key] / 10)
SPLp_total = 10 * np.log10(SPLp_total)
PNL.tot = choice_physics.PerceivedNoiseMetrics.getPNL(n_times, fobs, SPLp_total)
PNLT.tot = choice_physics.PerceivedNoiseMetrics.getPNLT(n_times, fband, PNL.tot, SPLp_total)
EPNL.tot = choice_physics.PerceivedNoiseMetrics.getEPNL(PNLT.tot, dt_mic)
return EPNL
[docs]def certificationLimits(noEngines, MTOW):
""" Computes EPNL certification limits. """
EPNL = choice_aux.chapter3(noEngines, MTOW)
EPNL_cum = EPNL.lateral + EPNL.cutback + EPNL.approach