Source code for uwacan.source_models

  1"""Collection of underwater noise source models.
  2
  3These source models typically return levels in dB,
  4but not consistently of the same type.
  5See the documentation of each model to know if it is a
  6ship level (monopole or radiated noise) or an environmental level.
  7Since the models can be evaluated at arbitrary frequencies, they should
  8all be power spectral density levels.
  9
 10.. autosummary::
 11    :toctree: generated
 12
 13    bureau_veritas_advanced
 14    bureau_veritas_controlled
 15    wales_heitmeyer
 16    jomopans_echo
 17
 18"""
 19
 20import numpy as np
 21import xarray as xr
 22
 23
 24def class_limit_curve(frequency, breakpoints, limits):
 25    """Evaluate a class limit curve defined by frequency breakpoints and limits.
 26
 27    With N breakpoints, there are N+1 regions and corresponding limits.
 28
 29    Parameters
 30    ----------
 31    frequency : array_like
 32        Input frequency data.
 33    breakpoints : list of float
 34        The frequency breakpoints, in ascending order.
 35    limits : list with the limits
 36        Each limit is either a callable which takes the frequency as the input,
 37        a single value valid within the entire range, or an array_like with the
 38        same shape as frequency.
 39
 40    Returns
 41    -------
 42    array_like
 43        Array of the same shape as `frequency`.
 44
 45    Examples
 46    --------
 47    >>> class_limit_curve(
 48    ...     np.array([50, 100, 200, 400, 800, 1600]),
 49    ...     [150, 800],
 50    ...     [10, 20, 30],
 51    ... )
 52    [10, 10, 20, 20, 30, 30]
 53    """
 54    conditions = [frequency < b for b in breakpoints] + [np.full(np.shape(frequency), True)]
 55    limits = [limit(frequency) if callable(limit) else limit for limit in limits]
 56    levels = np.select(conditions, limits)
 57    try:
 58        wrapper = frequency.__array_wrap__
 59    except AttributeError:
 60        return levels
 61    else:
 62        return wrapper(levels)
 63
 64
[docs] 65def bureau_veritas_advanced(frequency=None): 66 """Calculate the advanced vessel limit from Bureau Veritas. 67 68 This ship level is a radiated noise level, as a spectral density level. 69 """ 70 if frequency is None: 71 frequency = 10 ** (np.arange(10, 48) / 10) # Decidecade bands from 10 Hz to 50 kHz 72 frequency = xr.DataArray(frequency, coords={"frequency": frequency}) 73 return class_limit_curve( 74 frequency=frequency, 75 breakpoints=[50, 1e3], 76 limits=[ 77 lambda f: 174 - 11 * np.log10(f), 78 lambda f: 155.3 - 18 * np.log10(f / 50), 79 lambda f: 131.9 - 22 * np.log10(f / 1000), 80 ], 81 )
82 83
[docs] 84def bureau_veritas_controlled(frequency=None): 85 """Calculate the controlled vessel limit from Bureau Veritas. 86 87 This ship level is a radiated noise level, as a spectral density level. 88 """ 89 if frequency is None: 90 frequency = 10 ** (np.arange(10, 48) / 10) # Decidecade bands from 10 Hz to 50 kHz 91 frequency = xr.DataArray(frequency, coords={"frequency": frequency}) 92 return class_limit_curve( 93 frequency=frequency, 94 breakpoints=[50, 1e3], 95 limits=[ 96 lambda f: 169 - 2 * np.log10(f), 97 lambda f: 165.6 - 20 * np.log10(f / 50), 98 lambda f: 139.6 - 20 * np.log10(f / 1000), 99 ], 100 )
101 102def dnv_silence_transit(frequency = None): 103 """Calculate the environmental transit limit from DNV. 104 105 This ship level is a radiated noise level, as a spectral density level. 106 """ 107 if frequency is None: 108 frequency = 10 ** (np.arange(10, 48) / 10) # Decidecade bands from 10 Hz to 50 kHz 109 frequency = xr.DataArray(frequency, coords={"frequency": frequency}) 110 return class_limit_curve( 111 frequency=frequency, 112 breakpoints=[1000], 113 limits=[ 114 lambda f: 183 - 5 * np.log10(f), 115 lambda f: 168 - 12 * np.log10(f / 1000), 116 ], 117 ) - 10*np.log10(frequency * 0.231) 118 119 120
[docs] 121def wales_heitmeyer(frequency): 122 """Calculate the Wales-Heitmeyer source model. 123 124 Note that this is a monopole source level model, 125 with unknown validity for the source depths. 126 127 Returns 128 ------- 129 L : array_like 130 The calculated source level, as a spectral density level. 131 """ 132 return 230 - 10 * np.log10(frequency**3.594) + 10 * np.log10((1 + (frequency / 340) ** 2) ** 0.917)
133 134
[docs] 135def jomopans_echo(frequency, ship_class, speed, length): 136 """Calculate Jomopans-ECHO source model. 137 138 Make sure to use the correct units for speed and length. 139 Note that this is a monopole source level model, assuming 140 a source depth of 6 meters. 141 142 Parameters 143 ---------- 144 frequency : array_like 145 The frequencies at which to evaluate 146 ship_class : str 147 Which ship class to use. 148 speed : float 149 The ship speed, in knots 150 length : float 151 The ship length, in meters 152 153 154 Returns 155 ------- 156 L : array_like 157 The calculated source level, as a spectral density level. 158 """ 159 K = 191 160 K_lf = 208 161 162 D = 3 163 match ship_class: 164 case "fishing": 165 v_class = 6.4 166 case "tug": 167 v_class = 3.7 168 case "naval": 169 v_class = 11.1 170 case "recreational": 171 v_class = 10.6 172 case "research": 173 v_class = 8.0 174 case "cruise": 175 v_class = 17.1 176 D = 4 177 case "passenger": 178 v_class = 9.7 179 case "bulker": 180 v_class = 13.9 181 D_lf = 0.8 182 case "container": 183 v_class = 18 184 D_lf = 0.8 185 case "vehicle": 186 v_class = 15.8 187 D_lf = 1 188 case "tanker": 189 v_class = 12.4 190 D_lf = 1 191 case "other": 192 v_class = 7.4 193 case "dredger": 194 v_class = 9.5 195 case _: 196 raise ValueError(f"Unknown ship class '{ship_class}'") 197 198 f1 = 480 / v_class 199 200 baseline = K - 20 * np.log10(f1) - 10 * np.log10((1 - frequency / f1) ** 2 + D**2) 201 if ship_class in {"container", "vehicle", "bulker", "tanker"}: 202 f_lf = 600 / v_class 203 lf_baseline = ( 204 K_lf 205 - 40 * np.log10(f_lf) 206 + 10 * np.log10(frequency) 207 - 10 * np.log10((1 - (frequency / f_lf) ** 2) ** 2 + D_lf**2) 208 ) 209 baseline = xr.where(frequency < 100, lf_baseline, baseline) 210 l = length * 3.28084 / 300 211 return baseline + 60 * np.log10(speed / v_class) + 20 * np.log10(l)