-
Mads M. Pedersen authoredMads M. Pedersen authored
iso.py 10.29 KiB
import numpy as np
from py_wake.site.distance import StraightDistance
from numpy import newaxis as na
from py_wake.utils.grid_interpolator import GridInterpolator
class ISONoiseModel:
def __init__(self, src_x, src_y, src_h, freqs, sound_power_level, elevation_function=None):
"""ISONoise model based on
DSF/ISO/DIS 9613-2
Acoustics – Attenuation of sound during propagation – Part 2:
Engineering method for the prediction of sound pressure levels outdoors
DS/ISO 9613-1:1993
Akustik. Måling og beskrivelse af ekstern støj. Lydudbredelsesdæmpning udendørs. Del 1:
Metode til beregning af luftabsorption
The code is a vectorized version of the implementaion made by Camilla Marie Nyborg <cmny@dtu.dk>
The model models the sound pressure level from a number of sound sources at a number of receivers taking into
account
- spherical geometrical spreading
- ground reflection/absorption
- atmospheric absorption (DS/ISO 9613-1:1993)
Parameters
----------
src_x : array_like
x coordinate of sound sources
src_y : array_like
y coordinate of sound sources
src_h : array_like or float
height of sound sources (typically wind turbine hub height)
freqs : array_like
Frequencies of sound_power_level
sound_power_level : array_like
Emitted sound power level, dim=(n_sources, n_freq)
"""
if elevation_function:
src_h += elevation_function(src_x, src_y)
self.elevation_function = elevation_function
self.src_x, self.src_y = src_x, src_y
self.src_h = np.zeros_like(src_x) + src_h
self.n_src = len(src_x)
self.distance = StraightDistance()
self.freqs = freqs
self.sound_power_level = sound_power_level
def ground_eff(self, ground_distance_ij, distance_ij, ground_type):
# Ground effects ISO
freqs_init = np.array([63.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 8000.0])
src_h_ij = self.src_h[:, na]
rec_h_ij = self.distance.dst_h_j[na]
hei_check_ij = 30.0 * (src_h_ij + rec_h_ij)
q_ij = np.where(ground_distance_ij <= hei_check_ij, 0, 1 - hei_check_ij / ground_distance_ij)
Gs = Gr = Gm = ground_type
# for f=63Hz Am =-3*q, for other frequencies Am = -3 * q * (1-Gm)
fak = np.where(freqs_init == 63.0, 1, (1 - Gm))
Am_fij = -3.0 * q_ij[na] * fak[:, na, na]
# 125 Hz
a_source_ij = 1.5 + 3.0 * np.exp(-0.12 * (src_h_ij - 5.0)**2) * (1.0 - np.exp(-ground_distance_ij / 50.0)) + \
5.7 * np.exp(-0.09 * src_h_ij**2) * \
(1.0 - np.exp(-2.8 * (10.0**(-6.0)) * (ground_distance_ij**2)))
a_rec_ij = 1.5 + 3.0 * np.exp(-0.12 * (rec_h_ij - 5.0)**2) * (1.0 - np.exp(-ground_distance_ij / 50.0)) + \
5.7 * np.exp(-0.09 * rec_h_ij**2) * (1.0 - np.exp(-2.8 * (10.0**(-6.0)) * (ground_distance_ij**2)))
# 250 Hz
b_source_ij = 1.5 + 8.6 * np.exp(-0.09 * src_h_ij**2) * (1.0 - np.exp(-ground_distance_ij / 50.0))
b_rec_ij = 1.5 + 8.6 * np.exp(-0.09 * rec_h_ij**2) * (1.0 - np.exp(-ground_distance_ij / 50.0))
# 500 Hz
c_source_ij = 1.5 + 14.0 * np.exp(-0.46 * src_h_ij**2) * (1.0 - np.exp(-ground_distance_ij / 50.0))
c_rec_ij = 1.5 + 14.0 * np.exp(-0.46 * rec_h_ij**2) * (1.0 - np.exp(-ground_distance_ij / 50.0))
# 1000 Hz
d_source_ij = 1.5 + 5.0 * np.exp(-0.9 * src_h_ij**2) * (1.0 - np.exp(-ground_distance_ij / 50.0))
d_rec_ij = 1.5 + 5.0 * np.exp(-0.9 * rec_h_ij**2) * (1.0 - np.exp(-ground_distance_ij / 50.0))
zeros = np.zeros_like(ground_distance_ij)
As_fij = np.array([zeros - 1.5, # 63Hz
-1.5 + Gs * a_source_ij, # 125Hz
-1.5 + Gs * b_source_ij, # 250Hz
-1.5 + Gs * c_source_ij, # 500Hz
-1.5 + Gs * d_source_ij, # 1000Hz
zeros - 1.5 * (1.0 - Gs), # 2000Hz
zeros - 1.5 * (1.0 - Gs) # 8000Hz
])
Ar_fij = np.array([zeros - 1.5, # 63 Hz
-1.5 + Gr * a_rec_ij, # 125 Hz
-1.5 + Gr * b_rec_ij, # 250 Hz
-1.5 + Gr * c_rec_ij, # 500 Hz
-1.5 + Gr * d_rec_ij, # 1000 Hz
zeros - 1.5 * (1.0 - Gr), # 2000 Hz
zeros - 1.5 * (1.0 - Gr) # 8000 Hz
])
# Interpolation to other frequencies are actually not following the standard completely.
ip = GridInterpolator([freqs_init], np.moveaxis([As_fij, Ar_fij, Am_fij], 0, -1))
# interpolate to freq and sum up As+Ar+Am
Agr_ijf = np.moveaxis(ip(np.array([self.freqs]).T).sum(-1), 0, -1)
# Area of sphere: 4pi R^2
# 10*log_10(4pi) ~ 11
# 10 * log_10(R^2) = 20 * log_10(R) =
# reference area = 1.0 m^2
Adiv_ij = 20.0 * np.log10(distance_ij / 1.0) + 11.0 # The geometrical spreading (divergence)
ISO_ground_ijf = - Adiv_ij[:, :, na] - Agr_ijf
return ISO_ground_ijf
def atmab(self, distance_ij, T0, RH0):
# Atmospheric absorption
T_0 = 293.15
T_01 = 273.16
T = T0 + 273.15
p_s0 = 1.0
psat = p_s0 * 10.0**(-6.8346 * (T_01 / T)**1.261 + 4.6151)
h = p_s0 * (RH0) * (psat / p_s0)
F_rO = 1.0 / p_s0 * (24.0 + 4.04 * (10**4.0) * h * (0.02 + h) / (0.391 + h))
F_rN = 1.0 / p_s0 * (T_0 / T)**(0.5) * (9.0 + 280 * h *
np.exp(-4.17 * ((T_0 / T)**(1.0 / 3.0) - 1.0)))
alpha_ps = self.freqs**2.0 / p_s0 * (1.84 * (10**(-11)) * (T / T_0)**(0.5) + (T / T_0)**(-5.0 / 2.0) *
(0.01275 * np.exp(-2239.1 / T) / (F_rO + self.freqs**2.0 / F_rO) +
0.1068 * np.exp(-3352 / T) / (F_rN + self.freqs**2.0 / F_rN)))
ISO_alpha = alpha_ps[na, na] * 20.0 / np.log(10.0) * distance_ij[:, :, na]
return ISO_alpha
def transmission_loss(self, rec_x, rec_y, rec_h, ground_type, Temp, RHum):
# transmission loss = ground effects + atmospheric absorption
rec_h = np.zeros_like(rec_x) + rec_h
if self.elevation_function:
rec_h += self.elevation_function(rec_x, rec_y)
self.distance.setup(self.src_x, self.src_y, self.src_h, [rec_x, rec_y, rec_h])
ground_distance_ij = np.sqrt(self.distance.dx_ij**2 + self.distance.dy_ij**2)
distance_ij = np.sqrt(self.distance.dx_ij**2 + self.distance.dy_ij**2 + self.distance.dh_ij**2)
atm_abs_ijf = self.atmab(distance_ij, T0=Temp, RH0=RHum) # The atmospheric absorption term
ground_eff_ijf = self.ground_eff(ground_distance_ij, distance_ij, ground_type)
return ground_eff_ijf - atm_abs_ijf # Delta_SPL
def __call__(self, rec_x, rec_y, rec_h, Temp, RHum, ground_type):
"""Calculate the sound pressure level at a list of reveicers
Parameters
----------
rec_x : array_like
x coordinate, [m], of receivers
rec_y : array_like
y coordinate, [m], of receivers
rec_h : array_like or float
height, [m], of receivers (typically 2m)
Temp : float
Temperature (deg celcius)
RHum : float
Relative humidity (%)
ground_type : float
Factor describing the amount of ground absorption, 0=hard reflective ground,
e.g. paving, water, ice and concrete. 1= soft porous absorbing ground,
e.g. grass, trees, other vegetation and farming land
Returns
-------
total_spl : array_like
Total sound pressure level at each receiver
spl : array_like
Sound pressure level of freqs, dim=(n_receiver, n_freq)
"""
# Computing total transmission loss
Delta_SPL_ijf = self.transmission_loss(rec_x, rec_y, rec_h, ground_type, Temp, RHum)
sound_power_ijxxf = self.sound_power_level[:, na]
I, J, F = Delta_SPL_ijf.shape
shape = [I, J] + [1] * (len(sound_power_ijxxf.shape) - len(Delta_SPL_ijf.shape)) + [F]
Delta_SPL_ijxxf = Delta_SPL_ijf.reshape(shape)
# Add negative transmission loss to emitted sound and sum over sources to get sound pressure level
spl_jxxf = 10.0 * np.log10(np.sum(10.0**((self.sound_power_level[:, na] + Delta_SPL_ijxxf) / 10.0), axis=0))
# Sum over frequencies to get total sound pressure level
total_spl_jxx = 10.0 * np.log10(np.sum(10.0**(spl_jxxf / 10.0), axis=-1))
return total_spl_jxx, spl_jxxf
def main():
if __name__ == '__main__':
import matplotlib.pyplot as plt
from py_wake.deficit_models.gaussian import ZongGaussian
from py_wake.flow_map import XYGrid
from py_wake.turbulence_models.crespo import CrespoHernandez
from py_wake.site._site import UniformSite
from py_wake.examples.data.swt_dd_142_4100_noise.swt_dd_142_4100 import SWT_DD_142_4100
from py_wake.utils.layouts import rectangle
from py_wake.utils.plotting import setup_plot
wt = SWT_DD_142_4100()
wfm = ZongGaussian(UniformSite(), wt, turbulenceModel=CrespoHernandez())
x, y = rectangle(5, 5, 5 * wt.diameter())
sim_res = wfm(x, y, wd=270, ws=8, mode=0)
nm = sim_res.noise_model()
ax1, ax2, ax3 = plt.subplots(1, 3, figsize=(16, 4))[1]
sim_res.flow_map().plot_wake_map(ax=ax1)
ax1.plot([x[0]], [1000], '.', label='Receiver 1')
ax1.plot([x[-1]], [1000], '.', label='Receiver 2')
ax1.legend()
total_sp_jlk, spl_jlkf = nm(rec_x=[x[0], x[-1]], rec_y=[1000, 1000], rec_h=2, Temp=20, RHum=80, ground_type=0.0)
ax2.plot(nm.freqs, spl_jlkf[0, 0, 0], label='Receiver 1')
ax2.plot(nm.freqs, spl_jlkf[1, 0, 0], label='Receiver 2')
setup_plot(xlabel='Frequency [Hz]', ylabel='Sound pressure level [dB]', ax=ax2)
plt.tight_layout()
nmap = sim_res.noise_map(grid=XYGrid(x=np.linspace(-1000, 5000, 100), y=np.linspace(-1000, 1000, 50), h=2))
nmap['Total sound pressure level'].squeeze().plot(ax=ax3)
wt.plot(x, y, ax=ax3)
plt.show()
main()