Skip to content

API reference

This is the API reference for the echoSMs package.

Each type of model is contained in a separate Python class (with name ending in Model), but with common calling signatures across all model classes, as defined in ScatterModelBase. There are also classes to provide ready access to the benchmark models and reference model definitions. There are also utility functions.

ScatterModelBase

Bases: ABC

Base class for a class that provides a scattering model.

All scattering models should inherit from this class, have a name that ends with 'Model', and provide initialisation and calculate_ts_single() functions.

Attributes:

Name Type Description
long_name str

The long name of the model.

short_name str

A short version of the model's long name, typically an ancronym.

analytical_type str

Whether the model implements an exact or an approximate model.

boundary_types list[str]

The types of boundary conditions that the model provides, e.g., 'fixed rigid', 'pressure release', 'fluid filled'

shapes list[str]

The target shapes that the model can represent.

max_ka float

An approximate maximum ka value that will result in accurate target strength results. Note that ka is often not the only parameter that determines the accuracy of the model (e.g., aspect ratio and incident angle can also affect the accuracy).

no_expand_parameters list[str]

The model parameters that are not expanded into Pandas DataFrame columns or Xarray DataArray coordinates. They will instead end up as a dict in the DataFrame or DataArray attrs attribute.

Source code in src/echosms/scattermodelbase.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@abc.abstractmethod
def __init__(self):
    """
    Attributes
    ----------
    long_name : str
        The long name of the model.
    short_name : str
        A short version of the model's long name, typically an ancronym.
    analytical_type : str
        Whether the model implements an ``exact`` or an ``approximate`` model.
    boundary_types : list[str]
        The types of boundary conditions that the model provides, e.g., 'fixed rigid',
        'pressure release', 'fluid filled'
    shapes : list[str]
        The target shapes that the model can represent.
    max_ka : float
        An approximate maximum ka value that will result in accurate target strength results. Note
        that ka is often not the only parameter that determines the accuracy of the model (e.g.,
        aspect ratio and incident angle can also affect the accuracy).
    no_expand_parameters : list[str]
        The model parameters that are not expanded into Pandas DataFrame columns or
        Xarray DataArray coordinates. They will instead end up as a dict in the DataFrame or
        DataArray `attrs` attribute.
    """
    self.long_name = ''
    self.short_name = ''
    self.analytical_type = ''
    self.boundary_types = []
    self.shapes = []
    self.max_ka = np.nan
    self.no_expand_parameters = []

calculate_ts(data, expand=False, inplace=False, multiprocess=False)

Calculate the target strength (TS) for many parameters.

Parameters:

Name Type Description Default
data Pandas DataFrame, Xarray DataArray or dict

Requirements for the different input data types are:

  • DataFrame: column names must match the function parameter names in calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
  • DataArray: dimension names must match the function parameter names in calculate_ts_single(). TS values will be calculated for all combinations of the coordinate variables.
  • dict: keys must match the function parameters in calculate_ts_single(). TS values will be calculated for all combinations of the dict values.
required
multiprocess bool

Split the ts calculation across CPU cores. Multiprocessing is currently provided by mapply with little customisation. For more sophisticated uses it may be preferred to use a multiprocessing package of your choice directly on the calculate_ts_single() method. See the code in this method (calculate_ts()) for an example.

False
expand bool

Only applicable if data is a dict. If True, will use as_dataframe() to expand the dict into a DataFrame with one column per dict key and return that, adding a column named ts for the results.

False
inplace bool

Only applicable if data is a DataFrame. If True, the results will be added to the input DataFrame in a column named ts. If a ts column already exists, it is overwritten.

False

Returns:

Type Description
None, list[float], Series, or DataFrame

The return type and value are determined by the type of the input variable (data) and the expand and inplace parameters:

  • dict input and expand=False returns a list of floats.
  • dict input and expand=True returns a DataFrame.
  • DataFrame input and inplace=False returns a Series.
  • DataFrame input and inplace=True modifies data and returns None.
  • DataArray input always modifies data and returns None.
Source code in src/echosms/scattermodelbase.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def calculate_ts(self, data, expand=False, inplace=False, multiprocess=False):
    """Calculate the target strength (TS) for many parameters.

    Parameters
    ----------
    data : Pandas DataFrame, Xarray DataArray or dict
        Requirements for the different input data types are:

        - **DataFrame**: column names must match the function parameter names in
          calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
        - **DataArray**: dimension names must match the function parameter names in
          calculate_ts_single(). TS values will be calculated for all combinations of the
          coordinate variables.
        - **dict**: keys must match the function parameters in calculate_ts_single().
          TS values will be calculated for all combinations of the dict values.

    multiprocess : bool
        Split the ts calculation across CPU cores. Multiprocessing is currently provided by
        [mapply](https://github.com/ddelange/mapply) with little customisation. For more
        sophisticated uses it may be preferred to use a multiprocessing package of your choice
        directly on the `calculate_ts_single()` method. See the code in this method
        (`calculate_ts()`) for an example.

    expand : bool
        Only applicable if `data` is a dict. If `True`, will use
        [`as_dataframe()`][echosms.utils.as_dataframe]
        to expand the dict into a DataFrame with one column per dict key
        and return that, adding a column named `ts` for the results.

    inplace : bool
        Only applicable if `data` is a DataFrame. If `True`, the results
        will be added to the input DataFrame in a column named `ts`. If a `ts` column
        already exists, it is overwritten.

    Returns
    -------
    : None, list[float], Series, or DataFrame
        The return type and value are determined by the type of the input variable (`data`) and
        the `expand` and `inplace` parameters:

        - dict input and `expand=False` returns a list of floats.
        - dict input and `expand=True` returns a DataFrame.
        - DataFrame input and `inplace=False` returns a Series.
        - DataFrame input and `inplace=True` modifies `data` and returns `None`.
        - DataArray input always modifies `data` and returns `None`.

    """
    match data:
        case dict():
            data_df = as_dataframe(data, self.no_expand_parameters)
        case pd.DataFrame():
            data_df = data
        case xr.DataArray():
            data_df = data.to_dataframe().reset_index()
            data_df.attrs = data.attrs
        case _:
            raise ValueError(f'Data type of {type(data)} is not supported'
                             ' (only dictionaries, Pandas DataFrames and'
                             ' Xarray DataArrays are).')

    # Get the non-expandable model parameters
    p = data_df.attrs['parameters'] if 'parameters' in data_df.attrs else {}

    # Note: the args argument in the apply call below requires a tuple. data_df.attrs is a
    # dict and the default behaviour is to make a tuple using the dict keys. The trailing comma
    # and parenthesis instead causes the tuple to have one entry of the dict.

    if multiprocess:
        from mapply.mapply import mapply
        ts = mapply(data_df, self.__ts_helper, args=(p,), axis=1)
    else:  # this uses just one CPU
        ts = data_df.apply(self.__ts_helper, args=(p,), axis=1)

    match data:
        case dict() if expand:
            data_df['ts'] = ts
            return data_df
        case dict():
            return ts.to_list()
        case pd.DataFrame() if inplace:
            data_df['ts'] = ts
            return None
        case pd.DataFrame():
            return ts.rename('ts', inplace=True)
        case xr.DataArray():
            data.values = ts.to_numpy().reshape(data.shape)
            return None
        case _:
            raise AssertionError('This code should never be reached - unsupported input data '
                                 f'type of {type(data)}.')

calculate_ts_single() abstractmethod

Calculate the TS for one parameter set.

Source code in src/echosms/scattermodelbase.py
157
158
159
@abc.abstractmethod
def calculate_ts_single(self):
    """Calculate the TS for one parameter set."""

DCMModel

Bases: ScatterModelBase

Modal series deformed cylinder model (DCM).

This class contains methods to calculate acoustic scatter from finite straight cylinders with various boundary conditions.

Source code in src/echosms/dcmmodel.py
18
19
20
21
22
23
24
25
def __init__(self):
    super().__init__()
    self.long_name = 'deformed cylinder model'
    self.short_name = 'dcm'
    self.analytical_type = 'approximate analytical'
    self.boundary_types = ['fixed rigid', 'pressure release', 'fluid filled']
    self.shapes = ['finite cylinder']
    self.max_ka = 20  # [1]

calculate_ts(data, expand=False, inplace=False, multiprocess=False)

Calculate the target strength (TS) for many parameters.

Parameters:

Name Type Description Default
data Pandas DataFrame, Xarray DataArray or dict

Requirements for the different input data types are:

  • DataFrame: column names must match the function parameter names in calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
  • DataArray: dimension names must match the function parameter names in calculate_ts_single(). TS values will be calculated for all combinations of the coordinate variables.
  • dict: keys must match the function parameters in calculate_ts_single(). TS values will be calculated for all combinations of the dict values.
required
multiprocess bool

Split the ts calculation across CPU cores. Multiprocessing is currently provided by mapply with little customisation. For more sophisticated uses it may be preferred to use a multiprocessing package of your choice directly on the calculate_ts_single() method. See the code in this method (calculate_ts()) for an example.

False
expand bool

Only applicable if data is a dict. If True, will use as_dataframe() to expand the dict into a DataFrame with one column per dict key and return that, adding a column named ts for the results.

False
inplace bool

Only applicable if data is a DataFrame. If True, the results will be added to the input DataFrame in a column named ts. If a ts column already exists, it is overwritten.

False

Returns:

Type Description
None, list[float], Series, or DataFrame

The return type and value are determined by the type of the input variable (data) and the expand and inplace parameters:

  • dict input and expand=False returns a list of floats.
  • dict input and expand=True returns a DataFrame.
  • DataFrame input and inplace=False returns a Series.
  • DataFrame input and inplace=True modifies data and returns None.
  • DataArray input always modifies data and returns None.
Source code in src/echosms/scattermodelbase.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def calculate_ts(self, data, expand=False, inplace=False, multiprocess=False):
    """Calculate the target strength (TS) for many parameters.

    Parameters
    ----------
    data : Pandas DataFrame, Xarray DataArray or dict
        Requirements for the different input data types are:

        - **DataFrame**: column names must match the function parameter names in
          calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
        - **DataArray**: dimension names must match the function parameter names in
          calculate_ts_single(). TS values will be calculated for all combinations of the
          coordinate variables.
        - **dict**: keys must match the function parameters in calculate_ts_single().
          TS values will be calculated for all combinations of the dict values.

    multiprocess : bool
        Split the ts calculation across CPU cores. Multiprocessing is currently provided by
        [mapply](https://github.com/ddelange/mapply) with little customisation. For more
        sophisticated uses it may be preferred to use a multiprocessing package of your choice
        directly on the `calculate_ts_single()` method. See the code in this method
        (`calculate_ts()`) for an example.

    expand : bool
        Only applicable if `data` is a dict. If `True`, will use
        [`as_dataframe()`][echosms.utils.as_dataframe]
        to expand the dict into a DataFrame with one column per dict key
        and return that, adding a column named `ts` for the results.

    inplace : bool
        Only applicable if `data` is a DataFrame. If `True`, the results
        will be added to the input DataFrame in a column named `ts`. If a `ts` column
        already exists, it is overwritten.

    Returns
    -------
    : None, list[float], Series, or DataFrame
        The return type and value are determined by the type of the input variable (`data`) and
        the `expand` and `inplace` parameters:

        - dict input and `expand=False` returns a list of floats.
        - dict input and `expand=True` returns a DataFrame.
        - DataFrame input and `inplace=False` returns a Series.
        - DataFrame input and `inplace=True` modifies `data` and returns `None`.
        - DataArray input always modifies `data` and returns `None`.

    """
    match data:
        case dict():
            data_df = as_dataframe(data, self.no_expand_parameters)
        case pd.DataFrame():
            data_df = data
        case xr.DataArray():
            data_df = data.to_dataframe().reset_index()
            data_df.attrs = data.attrs
        case _:
            raise ValueError(f'Data type of {type(data)} is not supported'
                             ' (only dictionaries, Pandas DataFrames and'
                             ' Xarray DataArrays are).')

    # Get the non-expandable model parameters
    p = data_df.attrs['parameters'] if 'parameters' in data_df.attrs else {}

    # Note: the args argument in the apply call below requires a tuple. data_df.attrs is a
    # dict and the default behaviour is to make a tuple using the dict keys. The trailing comma
    # and parenthesis instead causes the tuple to have one entry of the dict.

    if multiprocess:
        from mapply.mapply import mapply
        ts = mapply(data_df, self.__ts_helper, args=(p,), axis=1)
    else:  # this uses just one CPU
        ts = data_df.apply(self.__ts_helper, args=(p,), axis=1)

    match data:
        case dict() if expand:
            data_df['ts'] = ts
            return data_df
        case dict():
            return ts.to_list()
        case pd.DataFrame() if inplace:
            data_df['ts'] = ts
            return None
        case pd.DataFrame():
            return ts.rename('ts', inplace=True)
        case xr.DataArray():
            data.values = ts.to_numpy().reshape(data.shape)
            return None
        case _:
            raise AssertionError('This code should never be reached - unsupported input data '
                                 f'type of {type(data)}.')

calculate_ts_single(medium_c, medium_rho, a, b, theta, f, boundary_type, target_c=None, target_rho=None, **kwargs)

Calculate the scatter from a finite cylinder using the modal series deformed cylinder model.

Parameters:

Name Type Description Default
medium_c float

Sound speed in the fluid medium surrounding the target [m/s].

required
medium_rho float

Density of the fluid medium surrounding the target [kg/m³].

required
a float

Radius of the cylinderical target [m].

required
b float

Length of the cylinderical target [m].

required
theta float

Pitch angle to calculate the scattering at [°]. An angle of 0 is head on, 90 is dorsal, and 180 is tail on.

required
f float

Frequency to calculate the scattering at [Hz].

required
boundary_type str

The model type. Supported model types are given in the boundary_types class attribute.

required
target_c float

Sound speed in the fluid inside the sphere [m/s]. Only required for boundary_type of fluid filled.

None
target_rho float

Density of the fluid inside the sphere [kg/m³]. Only required for boundary_type of fluid filled.

None

Returns:

Type Description
float

The target strength (re 1 m²) of the target [dB].

Notes

The class implements the code in Section B.1 of Ject et al. (2015).

References

Jech, J.M., Horne, J.K., Chu, D., Demer, D.A., Francis, D.T.I., Gorska, N., Jones, B., Lavery, A.C., Stanton, T.K., Macaulay, G.J., Reeder, D.B., Sawada, K., 2015. Comparisons among ten models of acoustic backscattering used in aquatic ecosystem research. Journal of the Acoustical Society of America 138, 3742–3764. https://doi.org/10.1121/1.4937607

Source code in src/echosms/dcmmodel.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_type,
                        target_c=None, target_rho=None, **kwargs):
    """
    Calculate the scatter from a finite cylinder using the modal series deformed cylinder model.

    Parameters
    ----------
    medium_c : float
        Sound speed in the fluid medium surrounding the target [m/s].
    medium_rho : float
        Density of the fluid medium surrounding the target [kg/m³].
    a : float
        Radius of the cylinderical target [m].
    b : float
        Length of the cylinderical target [m].
    theta : float
        Pitch angle to calculate the scattering at [°]. An angle of 0 is head on,
        90 is dorsal, and 180 is tail on.
    f : float
        Frequency to calculate the scattering at [Hz].
    boundary_type : str
        The model type. Supported model types are given in the `boundary_types` class attribute.
    target_c : float, optional
        Sound speed in the fluid inside the sphere [m/s].
        Only required for `boundary_type` of ``fluid filled``.
    target_rho : float, optional
        Density of the fluid inside the sphere [kg/m³].
        Only required for `boundary_type` of ``fluid filled``.

    Returns
    -------
    : float
        The target strength (re 1 m²) of the target [dB].

    Notes
    -----
    The class implements the code in Section B.1 of Ject et al. (2015).

    References
    ----------
    Jech, J.M., Horne, J.K., Chu, D., Demer, D.A., Francis, D.T.I., Gorska, N., Jones, B.,
    Lavery, A.C., Stanton, T.K., Macaulay, G.J., Reeder, D.B., Sawada, K., 2015.
    Comparisons among ten models of acoustic backscattering used in aquatic ecosystem
    research. Journal of the Acoustical Society of America 138, 3742–3764.
    <https://doi.org/10.1121/1.4937607>
    """
    if theta == 0.0:
        return nan

    theta_rad = theta*pi/180.
    kL = wavenumber(medium_c, f)*b
    K = wavenumber(medium_c, f) * sin(theta_rad)
    Ka = K*a

    m = range(30)  # TODO this needs to vary with f

    match boundary_type:
        case 'fixed rigid':
            series = list(map(lambda m: (-1)**m * Neumann(m)*(jvp(m, Ka) / h1vp(m, Ka)), m))
        case 'pressure release':
            series = list(map(lambda m: (-1)**m * Neumann(m)*(jv(m, Ka) / hankel1(m, Ka)), m))
        case 'fluid filled':
            g = target_rho/medium_rho
            h = target_c/medium_c
            gh = g*h
            Kda = K/h*a

            def Cm(m):
                numer = (jvp(m, Kda)*yv(m, Ka)) / (jv(m, Kda)*jvp(m, Ka))\
                    - gh*(yvp(m, Ka)/jvp(m, Ka))
                denom = (jvp(m, Kda)*jv(m, Ka)) / (jv(m, Kda)*jvp(m, Ka)) - gh
                return numer/denom

            series = list(map(lambda m: 1j**(2*m) * Neumann(m) / (1 + 1j*Cm(m)), m))
        case _:
            raise ValueError(f'The {self.long_name} model does not support '
                             f'a model type of "{boundary_type}".')

    fbs = 1j*b/pi * (sin(kL*cos(theta_rad)) / (kL*cos(theta_rad))) * sum(series)
    return 20*log10(abs(fbs))  # ts

DWBA models

There are several models that use the distorted-wave Born approximation, documented below:

DWBA

Bases: ScatterModelBase

Distorted-wave Born approximation scattering model.

Note

The DWBA model is not yet functional.

Source code in src/echosms/dwbamodel.py
14
15
16
17
18
19
20
21
def __init__(self):
    super().__init__()
    self.long_name = 'distorted-wave Born approximation'
    self.short_name = 'dwba'
    self.analytical_type = 'approximate'
    self.boundary_types = 'weakly scattering'
    self.shapes = ['any']
    self.max_ka = 20

calculate_ts(data, expand=False, inplace=False, multiprocess=False)

Calculate the target strength (TS) for many parameters.

Parameters:

Name Type Description Default
data Pandas DataFrame, Xarray DataArray or dict

Requirements for the different input data types are:

  • DataFrame: column names must match the function parameter names in calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
  • DataArray: dimension names must match the function parameter names in calculate_ts_single(). TS values will be calculated for all combinations of the coordinate variables.
  • dict: keys must match the function parameters in calculate_ts_single(). TS values will be calculated for all combinations of the dict values.
required
multiprocess bool

Split the ts calculation across CPU cores. Multiprocessing is currently provided by mapply with little customisation. For more sophisticated uses it may be preferred to use a multiprocessing package of your choice directly on the calculate_ts_single() method. See the code in this method (calculate_ts()) for an example.

False
expand bool

Only applicable if data is a dict. If True, will use as_dataframe() to expand the dict into a DataFrame with one column per dict key and return that, adding a column named ts for the results.

False
inplace bool

Only applicable if data is a DataFrame. If True, the results will be added to the input DataFrame in a column named ts. If a ts column already exists, it is overwritten.

False

Returns:

Type Description
None, list[float], Series, or DataFrame

The return type and value are determined by the type of the input variable (data) and the expand and inplace parameters:

  • dict input and expand=False returns a list of floats.
  • dict input and expand=True returns a DataFrame.
  • DataFrame input and inplace=False returns a Series.
  • DataFrame input and inplace=True modifies data and returns None.
  • DataArray input always modifies data and returns None.
Source code in src/echosms/scattermodelbase.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def calculate_ts(self, data, expand=False, inplace=False, multiprocess=False):
    """Calculate the target strength (TS) for many parameters.

    Parameters
    ----------
    data : Pandas DataFrame, Xarray DataArray or dict
        Requirements for the different input data types are:

        - **DataFrame**: column names must match the function parameter names in
          calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
        - **DataArray**: dimension names must match the function parameter names in
          calculate_ts_single(). TS values will be calculated for all combinations of the
          coordinate variables.
        - **dict**: keys must match the function parameters in calculate_ts_single().
          TS values will be calculated for all combinations of the dict values.

    multiprocess : bool
        Split the ts calculation across CPU cores. Multiprocessing is currently provided by
        [mapply](https://github.com/ddelange/mapply) with little customisation. For more
        sophisticated uses it may be preferred to use a multiprocessing package of your choice
        directly on the `calculate_ts_single()` method. See the code in this method
        (`calculate_ts()`) for an example.

    expand : bool
        Only applicable if `data` is a dict. If `True`, will use
        [`as_dataframe()`][echosms.utils.as_dataframe]
        to expand the dict into a DataFrame with one column per dict key
        and return that, adding a column named `ts` for the results.

    inplace : bool
        Only applicable if `data` is a DataFrame. If `True`, the results
        will be added to the input DataFrame in a column named `ts`. If a `ts` column
        already exists, it is overwritten.

    Returns
    -------
    : None, list[float], Series, or DataFrame
        The return type and value are determined by the type of the input variable (`data`) and
        the `expand` and `inplace` parameters:

        - dict input and `expand=False` returns a list of floats.
        - dict input and `expand=True` returns a DataFrame.
        - DataFrame input and `inplace=False` returns a Series.
        - DataFrame input and `inplace=True` modifies `data` and returns `None`.
        - DataArray input always modifies `data` and returns `None`.

    """
    match data:
        case dict():
            data_df = as_dataframe(data, self.no_expand_parameters)
        case pd.DataFrame():
            data_df = data
        case xr.DataArray():
            data_df = data.to_dataframe().reset_index()
            data_df.attrs = data.attrs
        case _:
            raise ValueError(f'Data type of {type(data)} is not supported'
                             ' (only dictionaries, Pandas DataFrames and'
                             ' Xarray DataArrays are).')

    # Get the non-expandable model parameters
    p = data_df.attrs['parameters'] if 'parameters' in data_df.attrs else {}

    # Note: the args argument in the apply call below requires a tuple. data_df.attrs is a
    # dict and the default behaviour is to make a tuple using the dict keys. The trailing comma
    # and parenthesis instead causes the tuple to have one entry of the dict.

    if multiprocess:
        from mapply.mapply import mapply
        ts = mapply(data_df, self.__ts_helper, args=(p,), axis=1)
    else:  # this uses just one CPU
        ts = data_df.apply(self.__ts_helper, args=(p,), axis=1)

    match data:
        case dict() if expand:
            data_df['ts'] = ts
            return data_df
        case dict():
            return ts.to_list()
        case pd.DataFrame() if inplace:
            data_df['ts'] = ts
            return None
        case pd.DataFrame():
            return ts.rename('ts', inplace=True)
        case xr.DataArray():
            data.values = ts.to_numpy().reshape(data.shape)
            return None
        case _:
            raise AssertionError('This code should never be reached - unsupported input data '
                                 f'type of {type(data)}.')

calculate_ts_single(theta, phi, f, target_rho, target_c)

Distorted-wave Born approximation scattering model.

Implements the distorted-wave Born approximation model for calculating the acoustic backscatter from weakly scattering bodies.

Parameters:

Name Type Description Default
theta float

Incident wave pitch angle [°].

required
phi float

Incident wave roll angle [°].

required
f float

Frequency to run the model at [Hz]

required
target_rho iterable[float]

Densities of each material. Must have at least the same number of entries as unique integers in volume [kg/m³].

required
target_c iterable[float]

Sound speed of each material. Must have at least the same number of entries as unique integers in volume [m/s].

required

Returns:

Type Description
float

The target strength (re 1 m²) [dB] of the target.

Notes

This class implements the method presented in Chu et al. (1993).

References

Chu, D., Foote, K. G., & Stanton, T. K. (1993). Further analysis of target strength measurements of Antarctic krill at 38 and 120 kHz: Comparison with deformed cylinder model and inference or orientation distribution. The Journal of the Acoustical Society of America, 93(5), 2985–2988. https://doi.org/10.1121/1.405818

Source code in src/echosms/dwbamodel.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def calculate_ts_single(self, theta, phi, f, target_rho, target_c):
    """Distorted-wave Born approximation scattering model.

    Implements the distorted-wave Born approximation
    model for calculating the acoustic backscatter from weakly scattering bodies.

    Parameters
    ----------
    theta : float
        Incident wave pitch angle [°].

    phi : float
        Incident wave roll angle [°].

    f : float
        Frequency to run the model at [Hz]

    target_rho : iterable[float]
        Densities of each material. Must have at least the same number of entries as unique
        integers in `volume` [kg/m³].

    target_c : iterable[float]
        Sound speed of each material. Must have at least the same number of entries as unique
        integers in `volume` [m/s].

    Returns
    -------
    : float
        The target strength (re 1 m²) [dB] of the target.

    Notes
    -----
    This class implements the method presented in Chu et al. (1993).

    References
    ----------
    Chu, D., Foote, K. G., & Stanton, T. K. (1993). Further analysis of target strength
    measurements of Antarctic krill at 38 and 120 kHz: Comparison with deformed cylinder
    model and inference or orientation distribution. The Journal of the Acoustical Society
    of America, 93(5), 2985–2988. <https://doi.org/10.1121/1.405818>

    """
    return None

PT-DWBA

Bases: ScatterModelBase

Phase-tracking distorted-wave Born approximation scattering model.

Source code in src/echosms/ptdwbamodel.py
11
12
13
14
15
16
17
18
19
def __init__(self):
    super().__init__()
    self.long_name = 'phase-tracking distorted-wave Born approximation'
    self.short_name = 'pt-dwba'
    self.analytical_type = 'approximate'
    self.boundary_types = 'weakly scattering'
    self.shapes = ['unrestricted voxel-based']
    self.max_ka = 20
    self.no_expand_parameters = ['volume', 'voxel_size', 'rho', 'c']

calculate_ts(data, expand=False, inplace=False, multiprocess=False)

Calculate the target strength (TS) for many parameters.

Parameters:

Name Type Description Default
data Pandas DataFrame, Xarray DataArray or dict

Requirements for the different input data types are:

  • DataFrame: column names must match the function parameter names in calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
  • DataArray: dimension names must match the function parameter names in calculate_ts_single(). TS values will be calculated for all combinations of the coordinate variables.
  • dict: keys must match the function parameters in calculate_ts_single(). TS values will be calculated for all combinations of the dict values.
required
multiprocess bool

Split the ts calculation across CPU cores. Multiprocessing is currently provided by mapply with little customisation. For more sophisticated uses it may be preferred to use a multiprocessing package of your choice directly on the calculate_ts_single() method. See the code in this method (calculate_ts()) for an example.

False
expand bool

Only applicable if data is a dict. If True, will use as_dataframe() to expand the dict into a DataFrame with one column per dict key and return that, adding a column named ts for the results.

False
inplace bool

Only applicable if data is a DataFrame. If True, the results will be added to the input DataFrame in a column named ts. If a ts column already exists, it is overwritten.

False

Returns:

Type Description
None, list[float], Series, or DataFrame

The return type and value are determined by the type of the input variable (data) and the expand and inplace parameters:

  • dict input and expand=False returns a list of floats.
  • dict input and expand=True returns a DataFrame.
  • DataFrame input and inplace=False returns a Series.
  • DataFrame input and inplace=True modifies data and returns None.
  • DataArray input always modifies data and returns None.
Source code in src/echosms/scattermodelbase.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def calculate_ts(self, data, expand=False, inplace=False, multiprocess=False):
    """Calculate the target strength (TS) for many parameters.

    Parameters
    ----------
    data : Pandas DataFrame, Xarray DataArray or dict
        Requirements for the different input data types are:

        - **DataFrame**: column names must match the function parameter names in
          calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
        - **DataArray**: dimension names must match the function parameter names in
          calculate_ts_single(). TS values will be calculated for all combinations of the
          coordinate variables.
        - **dict**: keys must match the function parameters in calculate_ts_single().
          TS values will be calculated for all combinations of the dict values.

    multiprocess : bool
        Split the ts calculation across CPU cores. Multiprocessing is currently provided by
        [mapply](https://github.com/ddelange/mapply) with little customisation. For more
        sophisticated uses it may be preferred to use a multiprocessing package of your choice
        directly on the `calculate_ts_single()` method. See the code in this method
        (`calculate_ts()`) for an example.

    expand : bool
        Only applicable if `data` is a dict. If `True`, will use
        [`as_dataframe()`][echosms.utils.as_dataframe]
        to expand the dict into a DataFrame with one column per dict key
        and return that, adding a column named `ts` for the results.

    inplace : bool
        Only applicable if `data` is a DataFrame. If `True`, the results
        will be added to the input DataFrame in a column named `ts`. If a `ts` column
        already exists, it is overwritten.

    Returns
    -------
    : None, list[float], Series, or DataFrame
        The return type and value are determined by the type of the input variable (`data`) and
        the `expand` and `inplace` parameters:

        - dict input and `expand=False` returns a list of floats.
        - dict input and `expand=True` returns a DataFrame.
        - DataFrame input and `inplace=False` returns a Series.
        - DataFrame input and `inplace=True` modifies `data` and returns `None`.
        - DataArray input always modifies `data` and returns `None`.

    """
    match data:
        case dict():
            data_df = as_dataframe(data, self.no_expand_parameters)
        case pd.DataFrame():
            data_df = data
        case xr.DataArray():
            data_df = data.to_dataframe().reset_index()
            data_df.attrs = data.attrs
        case _:
            raise ValueError(f'Data type of {type(data)} is not supported'
                             ' (only dictionaries, Pandas DataFrames and'
                             ' Xarray DataArrays are).')

    # Get the non-expandable model parameters
    p = data_df.attrs['parameters'] if 'parameters' in data_df.attrs else {}

    # Note: the args argument in the apply call below requires a tuple. data_df.attrs is a
    # dict and the default behaviour is to make a tuple using the dict keys. The trailing comma
    # and parenthesis instead causes the tuple to have one entry of the dict.

    if multiprocess:
        from mapply.mapply import mapply
        ts = mapply(data_df, self.__ts_helper, args=(p,), axis=1)
    else:  # this uses just one CPU
        ts = data_df.apply(self.__ts_helper, args=(p,), axis=1)

    match data:
        case dict() if expand:
            data_df['ts'] = ts
            return data_df
        case dict():
            return ts.to_list()
        case pd.DataFrame() if inplace:
            data_df['ts'] = ts
            return None
        case pd.DataFrame():
            return ts.rename('ts', inplace=True)
        case xr.DataArray():
            data.values = ts.to_numpy().reshape(data.shape)
            return None
        case _:
            raise AssertionError('This code should never be reached - unsupported input data '
                                 f'type of {type(data)}.')

calculate_ts_single(volume, theta, phi, f, voxel_size, rho, c, **kwargs)

Phase-tracking distorted-wave Born approximation scattering model.

Implements the phase-tracking distorted-wave Born approximation model for calculating the acoustic backscatter from weakly scattering bodies.

Warning

Input parameters theta, phi, and the orientation of volume do not currently follow the echoSMs coordinate system.

Parameters:

Name Type Description Default
volume Numpy ndarray[int]

The object to be modelled as a 3D volume of voxels. Array contents should be 0 for the surrounding medium, then increasing by 1 for each additional material type (i.e., 1, 2, 3, etc). volume should be ordered thus:

  • axis 0: height direction, increasing towards the underside of the organism
  • axis 1: across direction, increasing towards the left side of the organism
  • axis 2: along direction, increasing towards the tail of the organism
required
theta float

Incident wave pitch angle [°].

required
phi float

Incident wave roll angle [°].

required
f float

Frequency to run the model at [Hz]

required
voxel_size iterable[float]

The size of the voxels in volume [m], ordered (x, y, z). This code assumes that the voxels are cubes so y and z are currently irrevelant.

required
rho iterable[float]

Densities of each material. Must have at least the same number of entries as unique integers in volume [kg/m³].

required
c iterable[float]

Sound speed of each material. Must have at least the same number of entries as unique integers in volume [m/s].

required

Returns:

Type Description
float

The target strength (re 1 m²) [dB] of the target.

Notes

This class implements the method presented in Jones et. al. (2009). The code is based closely on the Matlab code in Jones (2006).

References

Jones, B. A. (2006). Acoustic scattering of broadband echolocation signals from prey of Blainville's beaked whales: Modeling and analysis. Master of Science, Massachusetts Institute of Technology. https://doi.org/10.1575/1912/1283

Jones, B. A., Lavery, A. C., & Stanton, T. K. (2009). Use of the distorted wave Born approximation to predict scattering by inhomogeneous objects: Application to squid. The Journal of the Acoustical Society of America, 125(1), 73-88. https://doi.org/10.1121/1.3021298

Source code in src/echosms/ptdwbamodel.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
def calculate_ts_single(self, volume, theta, phi, f, voxel_size, rho, c, **kwargs):
    """Phase-tracking distorted-wave Born approximation scattering model.

    Implements the phase-tracking distorted-wave Born approximation
    model for calculating the acoustic backscatter from weakly scattering bodies.

    Warning
    -------
    Input parameters `theta`, `phi`, and the orientation of `volume` do not currently follow the
    echoSMs [coordinate system](conventions.md#coordinate-systems).

    Parameters
    ----------
    volume : Numpy ndarray[int]
        The object to be modelled as a 3D volume of voxels. Array contents should be 0
        for the surrounding medium, then increasing by 1 for each additional material
        type (i.e., 1, 2, 3, etc). `volume` should be ordered thus:

        - axis 0: height direction, increasing towards the underside of the organism
        - axis 1: across direction, increasing towards the left side of the organism
        - axis 2: along direction, increasing towards the tail of the organism

    theta : float
        Incident wave pitch angle [°].

    phi : float
        Incident wave roll angle [°].

    f : float
        Frequency to run the model at [Hz]

    voxel_size : iterable[float]
        The size of the voxels in `volume` [m], ordered (_x_, _y_, _z_).
        This code assumes that the voxels are cubes so _y_ and _z_ are currently irrevelant.

    rho : iterable[float]
        Densities of each material. Must have at least the same number of entries as unique
        integers in `volume` [kg/m³].

    c : iterable[float]
        Sound speed of each material. Must have at least the same number of entries as unique
        integers in `volume` [m/s].

    Returns
    -------
    : float
        The target strength (re 1 m²) [dB] of the target.

    Notes
    -----
    This class implements the method presented in Jones et. al. (2009). The code is
    based closely on the Matlab code in Jones (2006).

    References
    ----------
    Jones, B. A. (2006). Acoustic scattering of broadband echolocation signals
    from prey of Blainville's beaked whales: Modeling and analysis. Master of Science,
    Massachusetts Institute of Technology. <https://doi.org/10.1575/1912/1283>

    Jones, B. A., Lavery, A. C., & Stanton, T. K. (2009). Use of the distorted
    wave Born approximation to predict scattering by inhomogeneous objects:
    Application to squid. The Journal of the Acoustical Society of America,
    125(1), 73-88. <https://doi.org/10.1121/1.3021298>
    """
    # Make sure things are numpy arrays
    rho = np.array(rho)
    c = np.array(c)
    voxel_size = np.array(voxel_size)

    # volume of the voxels [m^3]
    dv = voxel_size.prod()

    # input parameter checks
    if not len(volume.shape) == 3:
        raise TypeError('The volume input variable must be 3-dimensional.')

    if not voxel_size.shape[0] == 3:
        raise TypeError('The voxel_size input variable must contain 3 items.')

    if not np.any(voxel_size > 0):
        raise ValueError('All voxel_size values must be positive.')

    if f < 0.0:
        raise ValueError('The f input variable must contain only positive values.')

    if (theta < -180.0) or (theta > 180.0):
        raise ValueError('The theta (pitch) angle must be between -180.0 and +180.0')

    if (phi < -180.0) or (phi > 180.0):
        raise ValueError('The phi (roll) angle must be between -180.0 and +180.0')

    if volume.min() != 0:
        raise ValueError('The volume input variable must contain zeros.')

    categories = np.unique(volume)
    if not len(categories == (volume.max() + 1)):
        raise ValueError('The integers in volume must include all values in the series '
                         '(0, 1, 2, ..., n), where n is the largest integer in volume.')

    if not len(rho) >= len(categories):
        raise ValueError('The target_rho variable must contain at least as many values as '
                         'unique integers in the volume variable.')

    if not len(c) >= len(categories):
        raise ValueError('The target_c variable must contain at least as many values '
                         'as unique integers in the volume variable.')

    # density and sound speed ratios for all object materials
    g = rho[1:] / rho[0]
    h = c[1:] / c[0]

    # Do the pitch and roll rotations
    v = ndimage.rotate(volume, -theta, axes=(0, 2), order=0)
    v = ndimage.rotate(v, -phi, axes=(0, 1), order=0)

    categories = np.unique(v)  # or just take the max?

    # wavenumbers in the various media
    k = 2.0*np.pi * f / c

    # DWBA coefficients
    # amplitudes in media 1,2,...,n
    Cb = 1.0/(g * h**2) + 1.0/g - 2.0  # gamma_kappa - gamma_rho
    Ca = k[0]**2 * Cb / (4.0*np.pi)  # summation coefficient

    # Differential phase for each voxel.
    dph = np.zeros(v.shape)
    masks = []
    for i, category in enumerate(categories):
        masks.append(np.isin(v, category))
        dph[masks[i]] = k[i] * voxel_size[0]
    masks.pop(0)  # don't need to keep the category[0] mask

    # cummulative summation of phase along the x-direction
    phase = dph.cumsum(axis=0) - dph/2.0
    dA = np.zeros(phase.shape, dtype=np.complex128)

    # differential phases for each voxel
    for i, m in enumerate(masks):
        dA[m] = Ca[i] * np.exp(2.0*1j*phase[m]) * dv

    # Convert to TS
    return 20.0 * np.log10(np.abs(dA.sum()))

SDWBA

Bases: ScatterModelBase

Stochastic distorted-wave Born approximation scattering model.

Note

The SDWBA model is not yet functional.

Source code in src/echosms/sdwbamodel.py
14
15
16
17
18
19
20
21
def __init__(self):
    super().__init__()
    self.long_name = "stochastic distorted-wave Born approximation"
    self.short_name = "sdwba"
    self.analytical_type = "approximate"
    self.boundary_types = "weakly scattering"
    self.shapes = ["any"]
    self.max_ka = 20

calculate_ts(data, expand=False, inplace=False, multiprocess=False)

Calculate the target strength (TS) for many parameters.

Parameters:

Name Type Description Default
data Pandas DataFrame, Xarray DataArray or dict

Requirements for the different input data types are:

  • DataFrame: column names must match the function parameter names in calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
  • DataArray: dimension names must match the function parameter names in calculate_ts_single(). TS values will be calculated for all combinations of the coordinate variables.
  • dict: keys must match the function parameters in calculate_ts_single(). TS values will be calculated for all combinations of the dict values.
required
multiprocess bool

Split the ts calculation across CPU cores. Multiprocessing is currently provided by mapply with little customisation. For more sophisticated uses it may be preferred to use a multiprocessing package of your choice directly on the calculate_ts_single() method. See the code in this method (calculate_ts()) for an example.

False
expand bool

Only applicable if data is a dict. If True, will use as_dataframe() to expand the dict into a DataFrame with one column per dict key and return that, adding a column named ts for the results.

False
inplace bool

Only applicable if data is a DataFrame. If True, the results will be added to the input DataFrame in a column named ts. If a ts column already exists, it is overwritten.

False

Returns:

Type Description
None, list[float], Series, or DataFrame

The return type and value are determined by the type of the input variable (data) and the expand and inplace parameters:

  • dict input and expand=False returns a list of floats.
  • dict input and expand=True returns a DataFrame.
  • DataFrame input and inplace=False returns a Series.
  • DataFrame input and inplace=True modifies data and returns None.
  • DataArray input always modifies data and returns None.
Source code in src/echosms/scattermodelbase.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def calculate_ts(self, data, expand=False, inplace=False, multiprocess=False):
    """Calculate the target strength (TS) for many parameters.

    Parameters
    ----------
    data : Pandas DataFrame, Xarray DataArray or dict
        Requirements for the different input data types are:

        - **DataFrame**: column names must match the function parameter names in
          calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
        - **DataArray**: dimension names must match the function parameter names in
          calculate_ts_single(). TS values will be calculated for all combinations of the
          coordinate variables.
        - **dict**: keys must match the function parameters in calculate_ts_single().
          TS values will be calculated for all combinations of the dict values.

    multiprocess : bool
        Split the ts calculation across CPU cores. Multiprocessing is currently provided by
        [mapply](https://github.com/ddelange/mapply) with little customisation. For more
        sophisticated uses it may be preferred to use a multiprocessing package of your choice
        directly on the `calculate_ts_single()` method. See the code in this method
        (`calculate_ts()`) for an example.

    expand : bool
        Only applicable if `data` is a dict. If `True`, will use
        [`as_dataframe()`][echosms.utils.as_dataframe]
        to expand the dict into a DataFrame with one column per dict key
        and return that, adding a column named `ts` for the results.

    inplace : bool
        Only applicable if `data` is a DataFrame. If `True`, the results
        will be added to the input DataFrame in a column named `ts`. If a `ts` column
        already exists, it is overwritten.

    Returns
    -------
    : None, list[float], Series, or DataFrame
        The return type and value are determined by the type of the input variable (`data`) and
        the `expand` and `inplace` parameters:

        - dict input and `expand=False` returns a list of floats.
        - dict input and `expand=True` returns a DataFrame.
        - DataFrame input and `inplace=False` returns a Series.
        - DataFrame input and `inplace=True` modifies `data` and returns `None`.
        - DataArray input always modifies `data` and returns `None`.

    """
    match data:
        case dict():
            data_df = as_dataframe(data, self.no_expand_parameters)
        case pd.DataFrame():
            data_df = data
        case xr.DataArray():
            data_df = data.to_dataframe().reset_index()
            data_df.attrs = data.attrs
        case _:
            raise ValueError(f'Data type of {type(data)} is not supported'
                             ' (only dictionaries, Pandas DataFrames and'
                             ' Xarray DataArrays are).')

    # Get the non-expandable model parameters
    p = data_df.attrs['parameters'] if 'parameters' in data_df.attrs else {}

    # Note: the args argument in the apply call below requires a tuple. data_df.attrs is a
    # dict and the default behaviour is to make a tuple using the dict keys. The trailing comma
    # and parenthesis instead causes the tuple to have one entry of the dict.

    if multiprocess:
        from mapply.mapply import mapply
        ts = mapply(data_df, self.__ts_helper, args=(p,), axis=1)
    else:  # this uses just one CPU
        ts = data_df.apply(self.__ts_helper, args=(p,), axis=1)

    match data:
        case dict() if expand:
            data_df['ts'] = ts
            return data_df
        case dict():
            return ts.to_list()
        case pd.DataFrame() if inplace:
            data_df['ts'] = ts
            return None
        case pd.DataFrame():
            return ts.rename('ts', inplace=True)
        case xr.DataArray():
            data.values = ts.to_numpy().reshape(data.shape)
            return None
        case _:
            raise AssertionError('This code should never be reached - unsupported input data '
                                 f'type of {type(data)}.')

calculate_ts_single(theta, phi, f, target_rho, target_c)

Stochastic distorted-wave Born approximation scattering model.

Implements the stochastic distorted-wave Born approximation model for calculating the acoustic backscatter from weakly scattering bodies.

Parameters:

Name Type Description Default
theta float

Incident wave pitch angle [°].

required
phi float

Incident wave roll angle [°].

required
f float

Frequency to run the model at [Hz]

required
target_rho iterable[float]

Densities of each material. Must have at least the same number of entries as unique integers in volume [kg/m³].

required
target_c iterable[float]

Sound speed of each material. Must have at least the same number of entries as unique integers in volume [m/s].

required

Returns:

Type Description
float

The target strength (re 1 m²) [dB] of the target.

Notes

This class implements the method presented in Demer & Conti (2003), Demer & Conti (2004), and Conti & Demer (2006).

References

Demer, D. A., & Conti, S. G. (2003). Reconciling theoretical versus empirical target strengths of krill: Effects of phase variability on the distorted-wave Born approximation. ICES Journal of Marine Science, 60, 429-434. https://doi.org/10.1016/S1054-3139(03)00002-X

Demer, D. A., & Conti, S. G. (2004). Reconciling theoretical versus empirical target strengths of krill: Effects of phase variability on the distorted-wave Born approximation. ICES Journal of Marine Science, 61(1), 157-158. https://doi.org/10.1016/j.icesjms.2003.12.003

Conti, S. G., & Demer, D. A. (2006). Improved parameterization of the SDWBA for estimating krill target strength. ICES Journal of Marine Science, 63(5), 928-935. https://doi.org/10.1016/j.icesjms.2006.02.007

Source code in src/echosms/sdwbamodel.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def calculate_ts_single(self, theta, phi, f, target_rho, target_c):
    """Stochastic distorted-wave Born approximation scattering model.

    Implements the stochastic distorted-wave Born approximation
    model for calculating the acoustic backscatter from weakly scattering bodies.

    Parameters
    ----------
    theta : float
        Incident wave pitch angle [°].

    phi : float
        Incident wave roll angle [°].

    f : float
        Frequency to run the model at [Hz]

    target_rho : iterable[float]
        Densities of each material. Must have at least the same number of entries as unique
        integers in `volume` [kg/m³].

    target_c : iterable[float]
        Sound speed of each material. Must have at least the same number of entries as unique
        integers in `volume` [m/s].

    Returns
    -------
    : float
        The target strength (re 1 m²) [dB] of the target.

    Notes
    -----
    This class implements the method presented in Demer & Conti (2003), Demer & Conti (2004),
    and Conti & Demer (2006).

    References
    ----------
    Demer, D. A., & Conti, S. G. (2003). Reconciling theoretical versus empirical target
    strengths of krill: Effects of phase variability on the distorted-wave Born approximation.
    ICES Journal of Marine Science, 60, 429-434.
    <https://doi.org/10.1016/S1054-3139(03)00002-X>

    Demer, D. A., & Conti, S. G. (2004). Reconciling theoretical versus empirical
    target strengths of krill: Effects of phase variability on the distorted-wave Born
    approximation. ICES Journal of Marine Science, 61(1), 157-158.
    <https://doi.org/10.1016/j.icesjms.2003.12.003>

    Conti, S. G., & Demer, D. A. (2006). Improved parameterization of the SDWBA for estimating
    krill target strength. ICES Journal of Marine Science, 63(5), 928-935.
    <https://doi.org/10.1016/j.icesjms.2006.02.007>
    """
    return None

ESModel

Bases: ScatterModelBase

Elastic sphere (ES) scattering model.

This class calculates acoustic backscatter from elastic spheres.

Source code in src/echosms/esmodel.py
18
19
20
21
22
23
24
25
def __init__(self):
    super().__init__()
    self.long_name = 'elastic sphere'
    self.short_name = 'es'
    self.analytical_type = 'exact'
    self.boundary_types = ['elastic sphere']
    self.shapes = ['sphere']
    self.max_ka = 20  # [1]

calculate_ts(data, expand=False, inplace=False, multiprocess=False)

Calculate the target strength (TS) for many parameters.

Parameters:

Name Type Description Default
data Pandas DataFrame, Xarray DataArray or dict

Requirements for the different input data types are:

  • DataFrame: column names must match the function parameter names in calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
  • DataArray: dimension names must match the function parameter names in calculate_ts_single(). TS values will be calculated for all combinations of the coordinate variables.
  • dict: keys must match the function parameters in calculate_ts_single(). TS values will be calculated for all combinations of the dict values.
required
multiprocess bool

Split the ts calculation across CPU cores. Multiprocessing is currently provided by mapply with little customisation. For more sophisticated uses it may be preferred to use a multiprocessing package of your choice directly on the calculate_ts_single() method. See the code in this method (calculate_ts()) for an example.

False
expand bool

Only applicable if data is a dict. If True, will use as_dataframe() to expand the dict into a DataFrame with one column per dict key and return that, adding a column named ts for the results.

False
inplace bool

Only applicable if data is a DataFrame. If True, the results will be added to the input DataFrame in a column named ts. If a ts column already exists, it is overwritten.

False

Returns:

Type Description
None, list[float], Series, or DataFrame

The return type and value are determined by the type of the input variable (data) and the expand and inplace parameters:

  • dict input and expand=False returns a list of floats.
  • dict input and expand=True returns a DataFrame.
  • DataFrame input and inplace=False returns a Series.
  • DataFrame input and inplace=True modifies data and returns None.
  • DataArray input always modifies data and returns None.
Source code in src/echosms/scattermodelbase.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def calculate_ts(self, data, expand=False, inplace=False, multiprocess=False):
    """Calculate the target strength (TS) for many parameters.

    Parameters
    ----------
    data : Pandas DataFrame, Xarray DataArray or dict
        Requirements for the different input data types are:

        - **DataFrame**: column names must match the function parameter names in
          calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
        - **DataArray**: dimension names must match the function parameter names in
          calculate_ts_single(). TS values will be calculated for all combinations of the
          coordinate variables.
        - **dict**: keys must match the function parameters in calculate_ts_single().
          TS values will be calculated for all combinations of the dict values.

    multiprocess : bool
        Split the ts calculation across CPU cores. Multiprocessing is currently provided by
        [mapply](https://github.com/ddelange/mapply) with little customisation. For more
        sophisticated uses it may be preferred to use a multiprocessing package of your choice
        directly on the `calculate_ts_single()` method. See the code in this method
        (`calculate_ts()`) for an example.

    expand : bool
        Only applicable if `data` is a dict. If `True`, will use
        [`as_dataframe()`][echosms.utils.as_dataframe]
        to expand the dict into a DataFrame with one column per dict key
        and return that, adding a column named `ts` for the results.

    inplace : bool
        Only applicable if `data` is a DataFrame. If `True`, the results
        will be added to the input DataFrame in a column named `ts`. If a `ts` column
        already exists, it is overwritten.

    Returns
    -------
    : None, list[float], Series, or DataFrame
        The return type and value are determined by the type of the input variable (`data`) and
        the `expand` and `inplace` parameters:

        - dict input and `expand=False` returns a list of floats.
        - dict input and `expand=True` returns a DataFrame.
        - DataFrame input and `inplace=False` returns a Series.
        - DataFrame input and `inplace=True` modifies `data` and returns `None`.
        - DataArray input always modifies `data` and returns `None`.

    """
    match data:
        case dict():
            data_df = as_dataframe(data, self.no_expand_parameters)
        case pd.DataFrame():
            data_df = data
        case xr.DataArray():
            data_df = data.to_dataframe().reset_index()
            data_df.attrs = data.attrs
        case _:
            raise ValueError(f'Data type of {type(data)} is not supported'
                             ' (only dictionaries, Pandas DataFrames and'
                             ' Xarray DataArrays are).')

    # Get the non-expandable model parameters
    p = data_df.attrs['parameters'] if 'parameters' in data_df.attrs else {}

    # Note: the args argument in the apply call below requires a tuple. data_df.attrs is a
    # dict and the default behaviour is to make a tuple using the dict keys. The trailing comma
    # and parenthesis instead causes the tuple to have one entry of the dict.

    if multiprocess:
        from mapply.mapply import mapply
        ts = mapply(data_df, self.__ts_helper, args=(p,), axis=1)
    else:  # this uses just one CPU
        ts = data_df.apply(self.__ts_helper, args=(p,), axis=1)

    match data:
        case dict() if expand:
            data_df['ts'] = ts
            return data_df
        case dict():
            return ts.to_list()
        case pd.DataFrame() if inplace:
            data_df['ts'] = ts
            return None
        case pd.DataFrame():
            return ts.rename('ts', inplace=True)
        case xr.DataArray():
            data.values = ts.to_numpy().reshape(data.shape)
            return None
        case _:
            raise AssertionError('This code should never be reached - unsupported input data '
                                 f'type of {type(data)}.')

calculate_ts_single(medium_c, medium_rho, a, f, target_longitudinal_c, target_transverse_c, target_rho, **kwargs)

Calculate the backscatter from an elastic sphere for one set of parameters.

Parameters:

Name Type Description Default
medium_c float

Sound speed in the fluid medium surrounding the sphere [m/s].

required
medium_rho float

Density of the fluid medium surrounding the sphere [kg/m³].

required
a float

Radius of the sphere [m].

required
f float

Frequency to calculate the scattering at [Hz].

required
target_longitudinal_c float

Longitudinal sound speed in the material inside the sphere [m/s].

required
target_transverse_c float

Transverse sound speed in the material inside the sphere [m/s].

required
target_rho float

Density of the material inside the sphere [kg/m³].

required

Returns:

Type Description
float

The target strength (re 1 m²) of the sphere [dB].

Notes

The class implements the code in MacLennan (1981).

References

MacLennan, D. N. (1981). The Theory of Solid Spheres as Sonar Calibration Targets. Scottish Fisheries Research Report Number 22. Department of Agriculture and Fisheries for Scotland.

Source code in src/echosms/esmodel.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def calculate_ts_single(self, medium_c, medium_rho, a, f,
                        target_longitudinal_c, target_transverse_c, target_rho,
                        **kwargs) -> float:
    """
    Calculate the backscatter from an elastic sphere for one set of parameters.

    Parameters
    ----------
    medium_c : float
        Sound speed in the fluid medium surrounding the sphere [m/s].
    medium_rho : float
        Density of the fluid medium surrounding the sphere [kg/m³].
    a : float
        Radius of the sphere [m].
    f : float
        Frequency to calculate the scattering at [Hz].
    target_longitudinal_c : float
        Longitudinal sound speed in the material inside the sphere [m/s].
    target_transverse_c : float
        Transverse sound speed in the material inside the sphere [m/s].
    target_rho : float
        Density of the material inside the sphere [kg/m³].

    Returns
    -------
    : float
        The target strength (re 1 m²) of the sphere [dB].

    Notes
    -----
    The class implements the code in MacLennan (1981).

    References
    ----------
    MacLennan, D. N. (1981). The Theory of Solid Spheres as Sonar Calibration Targets.
    Scottish Fisheries Research Report Number 22. Department of Agriculture and Fisheries
    for Scotland.
    """
    q = wavenumber(medium_c, f)*a
    q1 = q*medium_c/target_longitudinal_c
    q2 = q*medium_c/target_transverse_c
    alpha = 2. * (target_rho/medium_rho) * (target_transverse_c/medium_c)**2
    beta = (target_rho/medium_rho) * (target_longitudinal_c/medium_c)**2 - alpha

    # Use n instead of l (ell) because l looks like 1.
    def S(n):
        A2 = (n**2 + n-2) * spherical_jn(n, q2) + q2**2 * spherical_jnpp(n, q2)
        A1 = 2*n*(n+1) * (q1*spherical_jn(n, q1, True) - spherical_jn(n, q1))
        B2 = A2*q1**2 * (beta*spherical_jn(n, q1) - alpha*spherical_jnpp(n, q1))\
            - A1*alpha * (spherical_jn(n, q2) - q2*spherical_jn(n, q2, True))
        B1 = q * (A2*q1*spherical_jn(n, q1, True) - A1*spherical_jn(n, q2))
        eta_n = atan(-(B2*spherical_jn(n, q, True) - B1*spherical_jn(n, q))
                     / (B2*spherical_yn(n, q, True) - B1*spherical_yn(n, q)))

        return (-1)**n * (2*n+1) * sin(eta_n) * exp(1j*eta_n)

    # Estimate the number of terms to use in the summation
    n_max = round(q+10)
    tol = 1e-10  # somewhat arbituary
    while abs(S(n_max)) > tol:
        n_max += 10

    if n_max > 200:
        warn('TS results may be inaccurate because the modal series required a large '
             f'number ({n_max}) of terms to converge.')

    n = range(n_max)

    f_inf = -2.0/q * np.sum(list(map(S, n)))

    return 10*log10(a**2 * abs(f_inf)**2 / 4.0)

MSSModel

Bases: ScatterModelBase

Modal series solution (MSS) scattering model.

This class calculates acoustic scatter from spheres and shells with various boundary conditions, as listed in the boundary_types class attribute.

Source code in src/echosms/mssmodel.py
19
20
21
22
23
24
25
26
27
28
def __init__(self):
    super().__init__()
    self.long_name = 'modal series solution'
    self.short_name = 'mss'
    self.analytical_type = 'exact'
    self.boundary_types = ['fixed rigid', 'pressure release', 'fluid filled',
                           'fluid shell fluid interior',
                           'fluid shell pressure release interior']
    self.shapes = ['sphere']
    self.max_ka = 20  # [1]

calculate_ts(data, expand=False, inplace=False, multiprocess=False)

Calculate the target strength (TS) for many parameters.

Parameters:

Name Type Description Default
data Pandas DataFrame, Xarray DataArray or dict

Requirements for the different input data types are:

  • DataFrame: column names must match the function parameter names in calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
  • DataArray: dimension names must match the function parameter names in calculate_ts_single(). TS values will be calculated for all combinations of the coordinate variables.
  • dict: keys must match the function parameters in calculate_ts_single(). TS values will be calculated for all combinations of the dict values.
required
multiprocess bool

Split the ts calculation across CPU cores. Multiprocessing is currently provided by mapply with little customisation. For more sophisticated uses it may be preferred to use a multiprocessing package of your choice directly on the calculate_ts_single() method. See the code in this method (calculate_ts()) for an example.

False
expand bool

Only applicable if data is a dict. If True, will use as_dataframe() to expand the dict into a DataFrame with one column per dict key and return that, adding a column named ts for the results.

False
inplace bool

Only applicable if data is a DataFrame. If True, the results will be added to the input DataFrame in a column named ts. If a ts column already exists, it is overwritten.

False

Returns:

Type Description
None, list[float], Series, or DataFrame

The return type and value are determined by the type of the input variable (data) and the expand and inplace parameters:

  • dict input and expand=False returns a list of floats.
  • dict input and expand=True returns a DataFrame.
  • DataFrame input and inplace=False returns a Series.
  • DataFrame input and inplace=True modifies data and returns None.
  • DataArray input always modifies data and returns None.
Source code in src/echosms/scattermodelbase.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def calculate_ts(self, data, expand=False, inplace=False, multiprocess=False):
    """Calculate the target strength (TS) for many parameters.

    Parameters
    ----------
    data : Pandas DataFrame, Xarray DataArray or dict
        Requirements for the different input data types are:

        - **DataFrame**: column names must match the function parameter names in
          calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
        - **DataArray**: dimension names must match the function parameter names in
          calculate_ts_single(). TS values will be calculated for all combinations of the
          coordinate variables.
        - **dict**: keys must match the function parameters in calculate_ts_single().
          TS values will be calculated for all combinations of the dict values.

    multiprocess : bool
        Split the ts calculation across CPU cores. Multiprocessing is currently provided by
        [mapply](https://github.com/ddelange/mapply) with little customisation. For more
        sophisticated uses it may be preferred to use a multiprocessing package of your choice
        directly on the `calculate_ts_single()` method. See the code in this method
        (`calculate_ts()`) for an example.

    expand : bool
        Only applicable if `data` is a dict. If `True`, will use
        [`as_dataframe()`][echosms.utils.as_dataframe]
        to expand the dict into a DataFrame with one column per dict key
        and return that, adding a column named `ts` for the results.

    inplace : bool
        Only applicable if `data` is a DataFrame. If `True`, the results
        will be added to the input DataFrame in a column named `ts`. If a `ts` column
        already exists, it is overwritten.

    Returns
    -------
    : None, list[float], Series, or DataFrame
        The return type and value are determined by the type of the input variable (`data`) and
        the `expand` and `inplace` parameters:

        - dict input and `expand=False` returns a list of floats.
        - dict input and `expand=True` returns a DataFrame.
        - DataFrame input and `inplace=False` returns a Series.
        - DataFrame input and `inplace=True` modifies `data` and returns `None`.
        - DataArray input always modifies `data` and returns `None`.

    """
    match data:
        case dict():
            data_df = as_dataframe(data, self.no_expand_parameters)
        case pd.DataFrame():
            data_df = data
        case xr.DataArray():
            data_df = data.to_dataframe().reset_index()
            data_df.attrs = data.attrs
        case _:
            raise ValueError(f'Data type of {type(data)} is not supported'
                             ' (only dictionaries, Pandas DataFrames and'
                             ' Xarray DataArrays are).')

    # Get the non-expandable model parameters
    p = data_df.attrs['parameters'] if 'parameters' in data_df.attrs else {}

    # Note: the args argument in the apply call below requires a tuple. data_df.attrs is a
    # dict and the default behaviour is to make a tuple using the dict keys. The trailing comma
    # and parenthesis instead causes the tuple to have one entry of the dict.

    if multiprocess:
        from mapply.mapply import mapply
        ts = mapply(data_df, self.__ts_helper, args=(p,), axis=1)
    else:  # this uses just one CPU
        ts = data_df.apply(self.__ts_helper, args=(p,), axis=1)

    match data:
        case dict() if expand:
            data_df['ts'] = ts
            return data_df
        case dict():
            return ts.to_list()
        case pd.DataFrame() if inplace:
            data_df['ts'] = ts
            return None
        case pd.DataFrame():
            return ts.rename('ts', inplace=True)
        case xr.DataArray():
            data.values = ts.to_numpy().reshape(data.shape)
            return None
        case _:
            raise AssertionError('This code should never be reached - unsupported input data '
                                 f'type of {type(data)}.')

calculate_ts_single(medium_c, medium_rho, a, theta, f, boundary_type, target_c=None, target_rho=None, shell_c=None, shell_rho=None, shell_thickness=None, **kwargs)

Calculate the scatter using the mss model for one set of parameters.

Parameters:

Name Type Description Default
medium_c float

Sound speed in the fluid medium surrounding the target [m/s].

required
medium_rho float

Density of the fluid medium surrounding the target [kg/m³].

required
a float

Radius of the spherical target [m].

required
theta float

Pitch angle to calculate the scattering at [°]. An angle of 0 is head on, 90 is dorsal, and 180 is tail on.

required
f float

Frequency to calculate the scattering at [Hz].

required
boundary_type str

The boundary type. Supported types are given in the boundary_types class variable.

required
target_c float

Sound speed in the fluid inside the sphere [m/s]. Only required for boundary_type of fluid filled.

None
target_rho float

Density of the fluid inside the sphere [kg/m³]. Only required for boundary_type of fluid filled.

None
shell_c float

Sound speed in the spherical shell [m/s]. Only required for boundary_types that include a fluid shell.

None
shell_rho float

Density in the spherical shell [kg/m³]. Only required for boundary_types that include a fluid shell.

None
shell_thickness float

Thickness of the spherical shell [m]. This value is subtracted from a to give the radius of the interior sphere. Only required for boundary_types that include a fluid shell.

None

Returns:

Type Description
float

The target strength (re 1 m²) of the target [dB].

Notes

The class implements the code in Section A.1 of Jech et al. (2015).

References

Jech, J.M., Horne, J.K., Chu, D., Demer, D.A., Francis, D.T.I., Gorska, N., Jones, B., Lavery, A.C., Stanton, T.K., Macaulay, G.J., Reeder, D.B., Sawada, K., 2015. Comparisons among ten models of acoustic backscattering used in aquatic ecosystem research. Journal of the Acoustical Society of America 138, 3742–3764. https://doi.org/10.1121/1.4937607

Source code in src/echosms/mssmodel.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def calculate_ts_single(self, medium_c, medium_rho, a, theta, f, boundary_type,
                        target_c=None, target_rho=None,
                        shell_c=None, shell_rho=None, shell_thickness=None,
                        **kwargs) -> float:
    """
    Calculate the scatter using the mss model for one set of parameters.

    Parameters
    ----------
    medium_c : float
        Sound speed in the fluid medium surrounding the target [m/s].
    medium_rho : float
        Density of the fluid medium surrounding the target [kg/m³].
    a : float
        Radius of the spherical target [m].
    theta : float
        Pitch angle to calculate the scattering at [°]. An angle of 0 is head on,
        90 is dorsal, and 180 is tail on.
    f : float
        Frequency to calculate the scattering at [Hz].
    boundary_type : str
        The boundary type. Supported types are given in the `boundary_types` class variable.
    target_c : float, optional
        Sound speed in the fluid inside the sphere [m/s].
        Only required for `boundary_type` of ``fluid filled``.
    target_rho : float, optional
        Density of the fluid inside the sphere [kg/m³].
        Only required for `boundary_type` of ``fluid filled``.
    shell_c : float, optional
        Sound speed in the spherical shell [m/s].
        Only required for `boundary_type`s that include a fluid shell.
    shell_rho : float, optional
        Density in the spherical shell [kg/m³].
        Only required for `boundary_type`s that include a fluid shell.
    shell_thickness : float, optional
        Thickness of the spherical shell [m]. This value is subtracted from ``a`` to give
        the radius of the interior sphere.
        Only required for `boundary_type`s that include a fluid shell.

    Returns
    -------
    : float
        The target strength (re 1 m²) of the target [dB].

    Notes
    -----
    The class implements the code in Section A.1 of Jech et al. (2015).

    References
    ----------
    Jech, J.M., Horne, J.K., Chu, D., Demer, D.A., Francis, D.T.I., Gorska, N.,
    Jones, B., Lavery, A.C., Stanton, T.K., Macaulay, G.J., Reeder, D.B., Sawada, K., 2015.
    Comparisons among ten models of acoustic backscattering used in aquatic ecosystem
    research. Journal of the Acoustical Society of America 138, 3742–3764.
    <https://doi.org/10.1121/1.4937607>
    """
    k0 = wavenumber(medium_c, f)
    ka = k0*a
    n = np.arange(0, round(ka+20))

    match boundary_type:
        case 'fixed rigid':
            A = list(map(lambda x: -spherical_jn(x, ka, True) / h1(x, ka, True), n))
        case 'pressure release':
            A = list(map(lambda x: -spherical_jn(x, ka) / h1(x, ka), n))
        case 'fluid filled':
            k1a = wavenumber(target_c, f)*a
            gh = target_rho/medium_rho * target_c/medium_c

            def Cn_fr(n):
                return\
                    ((spherical_jn(n, k1a, True)*spherical_yn(n, ka))
                        / (spherical_jn(n, k1a)*spherical_jn(n, ka, True))
                        - gh*(spherical_yn(n, ka, True)/spherical_jn(n, ka, True)))\
                    / ((spherical_jn(n, k1a, True)*spherical_jn(n, ka))
                       / (spherical_jn(n, k1a)*spherical_jn(n, ka, True))-gh)

            A = -1/(1 + 1j*np.asarray(list(map(Cn_fr, n)), dtype=complex))
        case 'fluid shell fluid interior':
            b = a - shell_thickness

            g21 = shell_rho / medium_rho
            h21 = shell_c / medium_c
            g32 = target_rho / shell_rho
            h32 = target_c / shell_c

            k1a = wavenumber(medium_c, f) * a
            k2 = wavenumber(shell_c, f)
            k3b = wavenumber(target_c, f) * b

            def Cn_fsfi(n):
                (b1, b2, a11, a21, a12, a22, a32, a13, a23, a33) =\
                    MSSModel.__eqn9(n, k1a, g21, h21, k2*a, k2*b, k3b, h32, g32)
                return (b1*a22*a33 + a13*b2*a32 - a12*b2*a33 - b1*a23*a32)\
                    / (a11*a22*a33 + a13*a21*a32 - a12*a21*a33 - a11*a23*a32)

            A = list(map(Cn_fsfi, n))
        case 'fluid shell pressure release interior':
            b = a - shell_thickness

            g21 = shell_rho / medium_rho
            h21 = shell_c / medium_c

            k1a = wavenumber(medium_c, f) * a
            k2 = wavenumber(shell_c, f)
            ksa = k2 * a  # ksa is used in the paper, but isn't that the same as k2a?

            def Cn_fspri(n):
                (b1, b2, d1, d2, a11, a21) = MSSModel.__eqn10(n, k1a, g21, h21, ksa, k2*a, k2*b)
                return (b1*d2-d1*b2) / (a11*d2-d1*a21)

            A = list(map(Cn_fspri, n))
        case _:
            raise ValueError(f'The {self.long_name} model does not support '
                             f'a model type of "{boundary_type}".')

    fbs = -1j/k0 * np.sum((-1)**n * (2*n+1) * A)
    return 20*log10(abs(fbs))  # ts

PSMSModel

Bases: ScatterModelBase

Prolate spheroidal modal series (PSMS) scattering model.

Source code in src/echosms/psmsmodel.py
12
13
14
15
16
17
18
19
def __init__(self):
    super().__init__()
    self.long_name = 'prolate spheroidal modal series'
    self.short_name = 'psms'
    self.analytical_type = 'exact'
    self.boundary_types = ['fixed rigid', 'pressure release', 'fluid filled']
    self.shapes = ['prolate spheroid']
    self.max_ka = 10  # [1]

calculate_ts(data, expand=False, inplace=False, multiprocess=False)

Calculate the target strength (TS) for many parameters.

Parameters:

Name Type Description Default
data Pandas DataFrame, Xarray DataArray or dict

Requirements for the different input data types are:

  • DataFrame: column names must match the function parameter names in calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
  • DataArray: dimension names must match the function parameter names in calculate_ts_single(). TS values will be calculated for all combinations of the coordinate variables.
  • dict: keys must match the function parameters in calculate_ts_single(). TS values will be calculated for all combinations of the dict values.
required
multiprocess bool

Split the ts calculation across CPU cores. Multiprocessing is currently provided by mapply with little customisation. For more sophisticated uses it may be preferred to use a multiprocessing package of your choice directly on the calculate_ts_single() method. See the code in this method (calculate_ts()) for an example.

False
expand bool

Only applicable if data is a dict. If True, will use as_dataframe() to expand the dict into a DataFrame with one column per dict key and return that, adding a column named ts for the results.

False
inplace bool

Only applicable if data is a DataFrame. If True, the results will be added to the input DataFrame in a column named ts. If a ts column already exists, it is overwritten.

False

Returns:

Type Description
None, list[float], Series, or DataFrame

The return type and value are determined by the type of the input variable (data) and the expand and inplace parameters:

  • dict input and expand=False returns a list of floats.
  • dict input and expand=True returns a DataFrame.
  • DataFrame input and inplace=False returns a Series.
  • DataFrame input and inplace=True modifies data and returns None.
  • DataArray input always modifies data and returns None.
Source code in src/echosms/scattermodelbase.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def calculate_ts(self, data, expand=False, inplace=False, multiprocess=False):
    """Calculate the target strength (TS) for many parameters.

    Parameters
    ----------
    data : Pandas DataFrame, Xarray DataArray or dict
        Requirements for the different input data types are:

        - **DataFrame**: column names must match the function parameter names in
          calculate_ts_single(). One TS value will be calculated for each row in the DataFrame.
        - **DataArray**: dimension names must match the function parameter names in
          calculate_ts_single(). TS values will be calculated for all combinations of the
          coordinate variables.
        - **dict**: keys must match the function parameters in calculate_ts_single().
          TS values will be calculated for all combinations of the dict values.

    multiprocess : bool
        Split the ts calculation across CPU cores. Multiprocessing is currently provided by
        [mapply](https://github.com/ddelange/mapply) with little customisation. For more
        sophisticated uses it may be preferred to use a multiprocessing package of your choice
        directly on the `calculate_ts_single()` method. See the code in this method
        (`calculate_ts()`) for an example.

    expand : bool
        Only applicable if `data` is a dict. If `True`, will use
        [`as_dataframe()`][echosms.utils.as_dataframe]
        to expand the dict into a DataFrame with one column per dict key
        and return that, adding a column named `ts` for the results.

    inplace : bool
        Only applicable if `data` is a DataFrame. If `True`, the results
        will be added to the input DataFrame in a column named `ts`. If a `ts` column
        already exists, it is overwritten.

    Returns
    -------
    : None, list[float], Series, or DataFrame
        The return type and value are determined by the type of the input variable (`data`) and
        the `expand` and `inplace` parameters:

        - dict input and `expand=False` returns a list of floats.
        - dict input and `expand=True` returns a DataFrame.
        - DataFrame input and `inplace=False` returns a Series.
        - DataFrame input and `inplace=True` modifies `data` and returns `None`.
        - DataArray input always modifies `data` and returns `None`.

    """
    match data:
        case dict():
            data_df = as_dataframe(data, self.no_expand_parameters)
        case pd.DataFrame():
            data_df = data
        case xr.DataArray():
            data_df = data.to_dataframe().reset_index()
            data_df.attrs = data.attrs
        case _:
            raise ValueError(f'Data type of {type(data)} is not supported'
                             ' (only dictionaries, Pandas DataFrames and'
                             ' Xarray DataArrays are).')

    # Get the non-expandable model parameters
    p = data_df.attrs['parameters'] if 'parameters' in data_df.attrs else {}

    # Note: the args argument in the apply call below requires a tuple. data_df.attrs is a
    # dict and the default behaviour is to make a tuple using the dict keys. The trailing comma
    # and parenthesis instead causes the tuple to have one entry of the dict.

    if multiprocess:
        from mapply.mapply import mapply
        ts = mapply(data_df, self.__ts_helper, args=(p,), axis=1)
    else:  # this uses just one CPU
        ts = data_df.apply(self.__ts_helper, args=(p,), axis=1)

    match data:
        case dict() if expand:
            data_df['ts'] = ts
            return data_df
        case dict():
            return ts.to_list()
        case pd.DataFrame() if inplace:
            data_df['ts'] = ts
            return None
        case pd.DataFrame():
            return ts.rename('ts', inplace=True)
        case xr.DataArray():
            data.values = ts.to_numpy().reshape(data.shape)
            return None
        case _:
            raise AssertionError('This code should never be reached - unsupported input data '
                                 f'type of {type(data)}.')

calculate_ts_single(medium_c, medium_rho, a, b, theta, f, boundary_type, target_c=None, target_rho=None)

Prolate spheroid modal series (PSMS) solution model.

Parameters:

Name Type Description Default
medium_c float

Sound speed in the fluid medium surrounding the target [m/s].

required
medium_rho float

Density of the fluid medium surrounding the target [kg/m³].

required
a float

Prolate spheroid major axis radius [m].

required
b float

Prolate spheroid minor axis radius [m].

required
theta float

Pitch angle to calculate the scattering at [°]. An angle of 0 is head on, 90 is dorsal, and 180 is tail on.

required
f float

Frequency to calculate the scattering at [Hz].

required
boundary_type str

The model type. Supported model types are given in the boundary_types class variable.

required
target_c float

Sound speed in the fluid inside the target [m/s]. Only required for boundary_type of fluid filled.

None
target_rho float

Density of the fluid inside the target [kg/m³]. Only required for boundary_type of fluid filled.

None

Returns:

Type Description
float

The target strength (re 1 m²) of the target [dB].

Notes

The backscattered target strength of a pressure release or fluid-filled prolate spheroid is calculated using the PSMS method of Furusawa (1988) and corrections in Furusawa et al. (1994).

References

Furusawa, M. (1988). "Prolate spheroidal models for predicting general trends of fish target strength," J. Acoust. Soc. Jpn. 9, 13-24. Furusawa, M., Miyanohana, Y., Ariji, M., and Sawada, Y. (1994). “Prediction of krill target strength by liquid prolate spheroid model,” Fish. Sci., 60, 261–265.

Source code in src/echosms/psmsmodel.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_type,
                        target_c=None, target_rho=None):
    """Prolate spheroid modal series (PSMS) solution model.

    Parameters
    ----------
    medium_c : float
        Sound speed in the fluid medium surrounding the target [m/s].
    medium_rho : float
        Density of the fluid medium surrounding the target [kg/m³].
    a : float
        Prolate spheroid major axis radius [m].
    b : float
        Prolate spheroid minor axis radius [m].
    theta : float
        Pitch angle to calculate the scattering at [°]. An angle of 0 is head on,
        90 is dorsal, and 180 is tail on.
    f : float
        Frequency to calculate the scattering at [Hz].
    boundary_type : str
        The model type. Supported model types are given in the `boundary_types` class variable.
    target_c : float, optional
        Sound speed in the fluid inside the target [m/s].
        Only required for `boundary_type` of ``fluid filled``.
    target_rho : float, optional
        Density of the fluid inside the target [kg/m³].
        Only required for `boundary_type` of ``fluid filled``.

    Returns
    -------
    : float
        The target strength (re 1 m²) of the target [dB].

    Notes
    -----
    The backscattered target strength of a pressure release or fluid-filled prolate spheroid
    is calculated using the PSMS method of Furusawa (1988) and corrections in
    Furusawa et al. (1994).

    References
    ----------
    Furusawa, M. (1988). "Prolate spheroidal models for predicting general
        trends of fish target strength," J. Acoust. Soc. Jpn. 9, 13-24.
    Furusawa, M., Miyanohana, Y., Ariji, M., and Sawada, Y. (1994).
        “Prediction of krill target strength by liquid prolate spheroid
        model,” Fish. Sci., 60, 261–265.
    """
    match boundary_type:
        case 'pressure release' | 'fluid filled':
            pass
        case 'fixed rigid':
            raise ValueError(f'Model type "{boundary_type}" has not yet been implemented '
                             f'for the {self.long_name} model.')
        case _:
            raise ValueError(f'The {self.long_name} model does not support '
                             f'a model type of "{boundary_type}".')

    if boundary_type == 'fluid filled':
        hc = target_c / medium_c
        rh = target_rho / medium_rho

    xiw = (1.0 - (b/a)**2)**(-.5)
    q = a/xiw  # semi-focal length

    kw = wavenumber(medium_c, f)
    hw = kw*q

    # Phi, the port/starboard angle is fixed for this code
    phi_inc = np.pi  # incident direction
    phi_sca = np.pi + phi_inc  # scattered direction

    theta_inc = np.deg2rad(theta)  # incident direction
    theta_sca = np.pi - theta_inc  # scattered direction

    # Approximate limits on the summations
    m_max = int(np.ceil(2*kw*b))
    n_max = int(m_max + np.ceil(hw/2))

    f_sc = 0.0
    for m in range(m_max+1):
        epsilon_m = Neumann(m)
        for n in range(m, n_max+1):
            Smn_inc, _ = pro_ang1(m, n, hw, np.cos(theta_inc))
            Smn_sca, _ = pro_ang1(m, n, hw, np.cos(theta_sca))
            match boundary_type:
                case 'fluid filled':
                    r_type1A, dr_type1A = pro_rad1(m, n, hw, xiw)
                    r_type2A, dr_type2A = pro_rad2(m, n, hw, xiw)
                    r_type1B, dr_type1B = pro_rad1(m, n, hw/hc, xiw)

                    eeA = r_type1A - rh*r_type1B/dr_type1B*dr_type1A
                    eeB = eeA + 1j*(r_type2A - rh*r_type1B/dr_type1B*dr_type2A)
                    Amn = -eeA/eeB  # Furusawa (1988) Eq. 5 p 15
                case 'pressure release':
                    r_type1, _ = pro_rad1(m, n, hw, xiw)
                    r_type2, _ = pro_rad2(m, n, hw, xiw)
                    Amn = -r_type1/(r_type1 + 1j*r_type2)
                case 'fixed rigid':
                    pass  # see eqn of (3) of Furusawa, 1988

            # This definition of the norm of S is in Yeh (1967), and is equation
            # 21.7.11 in Abramowitz & Stegun (10th printing) as the
            # Meixner-Schãfke normalisation scheme. Note that the RHS of
            # 21.7.11 doesn't give correct results compared to doing the actual
            # integration.
            #
            # Yeh, C. (1967). "Scattering of Acoustic Waves by a Penetrable
            #   Prolate Spheroid I Liquid Prolate Spheroid," J. Acoust. Soc.
            #   Am. 42, 518-521.
            #
            # Abramowitz, M., and Stegun, I. A. (1964). Handbook of
            #   Mathematical Functions with Formulas, Graphs, and Mathematical
            #   Tables (Dover, New York), 10th ed.

            n_mn = quad(PSMSModel._aswfa2, -1.0, 1.0, args=(m, n, hw), epsrel=1e-5)

            f_sc += epsilon_m / n_mn[0]\
                * Smn_inc * Amn * Smn_sca * np.cos(m*(phi_sca - phi_inc))

    return 20*np.log10(np.abs(-2j / kw * f_sc))

ReferenceModels

Provide access to reference scattering model parameters.

Reference models are the models and parameters defined in Jech et al. (2015). The parameters are stored in a TOML-formatted file in the echoSMs repository and this class provides easy access to the data in that file. Additional reference models may be defined in the future and added to the TOML file.

Attributes:

Name Type Description
definitions dict

A dict representation of the target definitions.toml file.

Raises:

Type Description
TOMLDecodeError

If the target definitions.toml file is not valid TOML.

KeyError

If the target definitions.toml file has multiple target entries with the same name.

References

Jech, J.M., Horne, J.K., Chu, D., Demer, D.A., Francis, D.T.I., Gorska, N., Jones, B., Lavery, A.C., Stanton, T.K., Macaulay, G.J., Reeder, D.B., Sawada, K., 2015. Comparisons among ten models of acoustic backscattering used in aquatic ecosystem research. Journal of the Acoustical Society of America 138, 3742–3764. https://doi.org/10.1121/1.4937607

Source code in src/echosms/referencemodels.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def __init__(self):
    self.defs_filename = Path(__file__).parent/Path('resources')/Path('target definitions.toml')

    self.definitions = []

    with open(self.defs_filename, 'rb') as f:
        try:
            self.definitions = tomllib.load(f)
        except tomllib.TOMLDecodeError as e:
            raise SyntaxError(f'Error while parsing file "{self.defs_filename.name}"') from e

    # Flag duplicate target names
    pda = pd.Series(self.names())
    duplicates = list(pda[pda.duplicated()])
    if duplicates:
        raise KeyError(f'The "{self.defs_filename.name}" file has multiple targets '
                       f'with the same name: '+', '.join(duplicates))

    # Substitute parameters names in the target section by the values of those
    # parameters.
    for t in self.definitions['target']:
        for k, v in t.items():
            try:
                t[k] = self.definitions['parameters'][v]
            except (KeyError, TypeError):
                pass

names()

Names of all model definitions.

Returns:

Type Description
iterable of str

All model names in the target definitions.toml file.

Source code in src/echosms/referencemodels.py
68
69
70
71
72
73
74
75
76
def names(self):
    """Names of all model definitions.

    Returns
    -------
    : iterable of str
        All model names in the ``target definitions.toml`` file.
    """
    return [n['name'] for n in self.definitions['target']]

parameters(name)

Model parameters for a particular model.

Model parameters are a subset of the model specification where the metadata items have been removed.

Parameters:

Name Type Description Default
name str

The name of a model in the target definitions.toml file.

required

Returns:

Type Description
dict

The model parameters for the requested model or an empty set if no model with that name.

Source code in src/echosms/referencemodels.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def parameters(self, name):
    """Model parameters for a particular model.

    Model parameters are a subset of the model specification where the metadata items have
    been removed.

    Parameters
    ----------
    name : str
        The name of a model in the ``target definitions.toml`` file.

    Returns
    -------
    : dict
        The model parameters for the requested model or an empty set if no model with that name.

    """
    s = self.specification(name)

    if not s:
        return []

    # Remove the entries that are not parameters
    p = s.copy()
    for k in ['name', 'shape', 'description', 'source', 'benchmark_model']:
        p.pop(k, None)
    return p

specification(name)

Model defintions for a particular model.

Parameters:

Name Type Description Default
name str

The name of a model in the target definitions.toml file.

required

Returns:

Type Description
dict

The model definitions for the requested model or an empty set if no model with that name.

Source code in src/echosms/referencemodels.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def specification(self, name):
    """Model defintions for a particular model.

    Parameters
    ----------
    name : str
        The name of a model in the ``target definitions.toml`` file.

    Returns
    -------
    : dict
        The model definitions for the requested model or an empty set if no model
        with that name.
    """
    s = [t for t in self.definitions['target'] if t['name'] == name]
    if not s:
        return s

    return s[0]

BenchmarkData

Convenient interface to the benchmark dataset.

This dataset contains the TS results from Jech et al. (2015).

Attributes:

Name Type Description
angle_dataset Pandas DataFrame

The angle dataset from the benchmark model runs.

freq_dataset Pandas DataFrame

The frequency dataset from the benchmark model runs.

References

Jech, J.M., Horne, J.K., Chu, D., Demer, D.A., Francis, D.T.I., Gorska, N., Jones, B., Lavery, A.C., Stanton, T.K., Macaulay, G.J., Reeder, D.B., Sawada, K., 2015. Comparisons among ten models of acoustic backscattering used in aquatic ecosystem research. Journal of the Acoustical Society of America 138, 3742–3764. https://doi.org/10.1121/1.4937607

Source code in src/echosms/benchmarkdata.py
28
29
30
31
32
33
34
35
36
def __init__(self):

    data_directory = Path(__file__).parent/Path('resources')/Path('BenchMark_Data')

    angle_data_file = data_directory/'Benchmark_Angle_TS.csv'
    freq_data_file = data_directory/'Benchmark_Frequency_TS.csv'

    self.angle_dataset = pd.read_csv(angle_data_file)
    self.freq_dataset = pd.read_csv(freq_data_file)

Utilities

Miscellaneous utility functions.

Neumann(m)

Neumann number.

Parameters:

Name Type Description Default
m int

The input integer.

required

Returns:

Type Description
int

The Neumann number.

Source code in src/echosms/utils.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def Neumann(m: int) -> int:
    """Neumann number.

    Parameters
    ----------
    m :
        The input integer.

    Returns
    -------
    :
        The Neumann number.
    """
    if m == 0:
        return 1
    return 2

as_dataarray(params, no_expand=[])

Convert model parameters from dict form to a Xarray DataArray.

Parameters:

Name Type Description Default
params dict

The model parameters.

required
no_expand list

Key values of the non-expandable model parameters in params.

[]

Returns:

Type Description
DataArray

Returns a multi-dimensional DataArray generated from the Cartesian product of all expandable items in the input dict. Non-expandable items are added to the DataArray attrs property. Expandable items are those that can be sensibly expanded into DataArray coordinates. Not all models have non-expandable items. The array is named ts, the values are initialised to nan, the dimension names are the dict keys, and the coordinate variables are the dict values.

Source code in src/echosms/utils.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def as_dataarray(params: dict, no_expand: list = []) -> xr.DataArray:
    """Convert model parameters from dict form to a Xarray DataArray.

    Parameters
    ----------
    params :
        The model parameters.

    no_expand :
        Key values of the non-expandable model parameters in `params`.

    Returns
    -------
    :
        Returns a multi-dimensional DataArray generated from the Cartesian product of all
        expandable items in the input dict. Non-expandable items are added to the DataArray
        attrs property. Expandable items are those that can be sensibly expanded into
        DataArray coordinates. Not all models have non-expandable items.
        The array is named `ts`, the values are initialised to `nan`, the
        dimension names are the dict keys, and the coordinate variables are the dict values.

    """
    expand, nexpand = split_dict(params, no_expand)

    # Convert scalars to iterables so xarray is happy
    for k, v in expand.items():
        if not isinstance(v, Iterable) or isinstance(v, str):
            expand[k] = [v]

    sz = [len(v) for k, v in expand.items()]
    return xr.DataArray(data=np.full(sz, np.nan), coords=expand, name='ts',
                        attrs={'units': 'dB', 'dB_reference': '1 m^2',
                               'parameters': nexpand})

as_dataframe(params, no_expand=[])

Convert model parameters from dict form to a Pandas DataFrame.

Parameters:

Name Type Description Default
params dict

The model parameters.

required
no_expand list

Key values of the non-expandable model parameters in params.

[]

Returns:

Type Description
DataFrame

Returns a Pandas DataFrame generated from the Cartesian product of all expandable items in the input dict. DataFrame column names are obtained from the dict keys. Non-expandable items are added to the DataFrame attrs property. Expandable items are those that can be sensibly expanded into DataFrame columns. Not all models have non-expandable items.

Source code in src/echosms/utils.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def as_dataframe(params: dict, no_expand: list = []) -> pd.DataFrame:
    """Convert model parameters from dict form to a Pandas DataFrame.

    Parameters
    ----------
    params :
        The model parameters.

    no_expand :
        Key values of the non-expandable model parameters in `params`.

    Returns
    -------
    :
        Returns a Pandas DataFrame generated from the Cartesian product of all expandable
        items in the input dict. DataFrame column names are obtained from the dict keys.
        Non-expandable items are added to the DataFrame attrs property. Expandable items are
        those that can be sensibly expanded into DataFrame columns. Not all models have
        non-expandable items.

    """
    expand, nexpand = split_dict(params, no_expand)

    # Use meshgrid to do the Cartesian product then create a Pandas DataFrame from that, having
    # flattened the multidimensional arrays and using a dict to provide column names.
    # This preserves the differing dtypes in each column compared to other ways of
    # constructing the DataFrame).
    df = pd.DataFrame({k: t.flatten()
                       for k, t in zip(expand.keys(), np.meshgrid(*tuple(expand.values())))})
    df.attrs = {'parameters': nexpand}
    return df

h1(n, z, derivative=False)

Spherical Hankel function of the first kind or its' derivative.

Parameters:

Name Type Description Default
n int

Order (n ≥ 0).

required
z float

Argument of the Hankel function.

required
derivative

if True, the value of the derivative (rather than the function itself) is returned.

False

Returns:

Type Description
complex

Value of the spherical Hankel function

Raises:

Type Description
ValueError

For negative n values.

Notes

The value of the Hankel function is calculated from spherical Bessel functions [1].

The derivative is computed from spherical Hankel functions [2].

References

[1] https://dlmf.nist.gov/10.47.E10

[2] https://dlmf.nist.gov/10.51.E2

Source code in src/echosms/utils.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def h1(n: int, z: float, derivative=False) -> complex:
    """Spherical Hankel function of the first kind or its' derivative.

    Parameters
    ----------
    n :
        Order (n ≥ 0).
    z :
        Argument of the Hankel function.
    derivative :
        if True, the value of the derivative (rather than the function itself) is returned.

    Returns
    -------
    :
        Value of the spherical Hankel function

    Raises
    ------
    ValueError
        For negative n values.

    Notes
    -----
    The value of the Hankel function is calculated from spherical Bessel functions [1].

    The derivative is computed from spherical Hankel functions [2].

    References
    ----------
    [1] <https://dlmf.nist.gov/10.47.E10>

    [2] <https://dlmf.nist.gov/10.51.E2>
    """
    if n < 0:
        raise ValueError('Negative n values are not supported for spherical Hankel functions.')

    if not derivative:
        return spherical_jn(n, z) + 1j*spherical_yn(n, z)
    return -h1(n+1, z) + (n/z) * h1(n, z)

pro_ang1(m, n, c, x)

Prolate spheroidal angular function of the first kind and derivative.

Calculates the prolate spheroidal angular function of the first kind and its' derivative with respect to x.

Parameters:

Name Type Description Default
m int

The order parameter.

required
n int

The degree parameter (≥ m).

required
c float

The size parameter.

required
x float

The eta parameter.

required

Returns:

Type Description
tuple[float, float]

The value of the prolate spheroidal angular function and its' derivative.

Notes

This method uses the prolate spheroidal wave function code for non complex arguments (van Buren & Boisvert, 2002, and van Buren & Boisvert, 2024), available on github. This code is in Fortran90 and was interfaced to Python using numpy.f2py then wrapped with the current method to provide a similar calling convention as the Scipy function of the same name.

References

Van Buren, A. L., & Boisvert, J. E. (2002). Accurate calculation of prolate spheroidal radial functions of the first kind and their first derivatives. Quarterly of Applied Mathematics, 60(3), 589-599. https://doi.org/10.1090/qam/1914443

Van Buren, A. L., & Boisvert, J. E. (2004). Improved Calculation of Prolate Spheroidal Radial Functions of the Second Kind and Their First Derivatives. Quarterly of Applied Mathematics, 62(3), 493-507. https://doi.org/10.1090/qam/2086042

Source code in src/echosms/utils.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def pro_ang1(m: int, n: int, c: float, x: float) -> tuple[float, float]:
    """Prolate spheroidal angular function of the first kind and derivative.

    Calculates the prolate spheroidal angular function of the first kind and its'
    derivative with respect to `x`.

    Parameters
    ----------
    m :
        The order parameter.
    n :
        The degree parameter (≥ `m`).
    c :
        The size parameter.
    x :
        The eta parameter.

    Returns
    -------
    :
        The value of the prolate spheroidal angular function and its' derivative.

    Notes
    -----
    This method uses the prolate spheroidal wave function code for non complex
    arguments (van Buren & Boisvert, 2002, and van Buren & Boisvert, 2024), available on
    [github](https://github.com/MathieuandSpheroidalWaveFunctions). This code is in Fortran90
    and was interfaced to Python using `numpy.f2py` then wrapped with the current method to
    provide a similar calling convention as the Scipy function of the same name.

    References
    ----------
    Van Buren, A. L., & Boisvert, J. E. (2002). Accurate calculation of prolate spheroidal
    radial functions of the first kind and their first derivatives. Quarterly of Applied
    Mathematics, 60(3), 589-599. <https://doi.org/10.1090/qam/1914443>

    Van Buren, A. L., & Boisvert, J. E. (2004). Improved Calculation of Prolate Spheroidal
    Radial Functions of the Second Kind and Their First Derivatives. Quarterly of Applied
    Mathematics, 62(3), 493-507. <https://doi.org/10.1090/qam/2086042>
    """
    a = prolate_swf.profcn(c=c, m=m, lnum=n-m+2, x1=0.0, ioprad=0, iopang=2, iopnorm=0, arg=[x])
    p = swf_t._make(a)
    s = p.s1c * np.float_power(10.0, p.is1e)
    sp = p.s1dc * np.float_power(10.0, p.is1de)

    # print(m,n,c,x,s, sp)

    return s[n-m][0], sp[n-m][0]

pro_rad1(m, n, c, x)

Prolate spheroidal radial function of the first kind and derivative.

Calculates the prolate spheroidal radial function of the first kind and its' derivative with respect to x.

Parameters:

Name Type Description Default
m int

The order parameter.

required
n int

The degree parameter (≥ m).

required
c float

The size parameter.

required
x float

The radial coordinate.

required

Returns:

Type Description
tuple[float, float]

The value of the prolate spheroidal radial function and its' derivative.

Notes

This method uses the prolate spheroidal wave function code for non complex arguments (van Buren & Boisvert, 2002, and van Buren & Boisvert, 2024), available on github. This code is in Fortran90 and was interfaced to Python using numpy.f2py then wrapped with the current method to provide a similar calling convention as the Scipy function of the same name.

References

Van Buren, A. L., & Boisvert, J. E. (2002). Accurate calculation of prolate spheroidal radial functions of the first kind and their first derivatives. Quarterly of Applied Mathematics, 60(3), 589-599. https://doi.org/10.1090/qam/1914443

Van Buren, A. L., & Boisvert, J. E. (2004). Improved Calculation of Prolate Spheroidal Radial Functions of the Second Kind and Their First Derivatives. Quarterly of Applied Mathematics, 62(3), 493-507. https://doi.org/10.1090/qam/2086042

Source code in src/echosms/utils.py
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
def pro_rad1(m: int, n: int, c: float, x: float) -> tuple[float, float]:
    """Prolate spheroidal radial function of the first kind and derivative.

    Calculates the prolate spheroidal radial function of the first kind and its'
    derivative with respect to `x`.

    Parameters
    ----------
    m :
        The order parameter.
    n :
        The degree parameter (≥ `m`).
    c :
        The size parameter.
    x :
        The radial coordinate.

    Returns
    -------
    :
        The value of the prolate spheroidal radial function and its' derivative.

    Notes
    -----
    This method uses the prolate spheroidal wave function code for non complex
    arguments (van Buren & Boisvert, 2002, and van Buren & Boisvert, 2024), available on
    [github](https://github.com/MathieuandSpheroidalWaveFunctions). This code is in Fortran90
    and was interfaced to Python using `numpy.f2py` then wrapped with the current method to
    provide a similar calling convention as the Scipy function of the same name.

    References
    ----------
    Van Buren, A. L., & Boisvert, J. E. (2002). Accurate calculation of prolate spheroidal
    radial functions of the first kind and their first derivatives. Quarterly of Applied
    Mathematics, 60(3), 589-599. <https://doi.org/10.1090/qam/1914443>

    Van Buren, A. L., & Boisvert, J. E. (2004). Improved Calculation of Prolate Spheroidal
    Radial Functions of the Second Kind and Their First Derivatives. Quarterly of Applied
    Mathematics, 62(3), 493-507. <https://doi.org/10.1090/qam/2086042>
    """
    a = prolate_swf.profcn(c=c, m=m, lnum=n-m+2, x1=x-1.0, ioprad=1, iopang=0, iopnorm=0, arg=[0])
    p = swf_t._make(a)
    s = p.r1c * np.float_power(10.0, p.ir1e)
    sp = p.r1dc * np.float_power(10.0, p.ir1de)

    # print(m,n,c,x,s, sp)

    return s[n-m], sp[n-m]

pro_rad2(m, n, c, x)

Prolate spheroidal radial function of the second kind and derivative.

Calculates the prolate spheroidal radial function of the second kind and its' derivative with respect to x.

Parameters:

Name Type Description Default
m

The order parameter.

required
n

The degree parameter (≥ m).

required
c

The size parameter.

required
x

The radial coordinate.

required

Returns:

Type Description

The value of the prolate spheroidal radial function and its' derivative.

Notes

This method uses the prolate spheroidal wave function code for non complex arguments (van Buren & Boisvert, 2002, and van Buren & Boisvert, 2024), available on github. This code is in Fortran90 and was interfaced to Python using numpy.f2py then wrapped with the current method to provide a similar calling convention as the Scipy function of the same name.

References

Van Buren, A. L., & Boisvert, J. E. (2002). Accurate calculation of prolate spheroidal radial functions of the first kind and their first derivatives. Quarterly of Applied Mathematics, 60(3), 589-599. https://doi.org/10.1090/qam/1914443

Van Buren, A. L., & Boisvert, J. E. (2004). Improved Calculation of Prolate Spheroidal Radial Functions of the Second Kind and Their First Derivatives. Quarterly of Applied Mathematics, 62(3), 493-507. https://doi.org/10.1090/qam/2086042

Source code in src/echosms/utils.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
def pro_rad2(m, n, c, x):
    """Prolate spheroidal radial function of the second kind and derivative.

    Calculates the prolate spheroidal radial function of the second kind and its'
    derivative with respect to `x`.

    Parameters
    ----------
    m :
        The order parameter.
    n :
        The degree parameter (≥ `m`).
    c :
        The size parameter.
    x :
        The radial coordinate.

    Returns
    -------
    :
        The value of the prolate spheroidal radial function and its' derivative.

    Notes
    -----
    This method uses the prolate spheroidal wave function code for non complex
    arguments (van Buren & Boisvert, 2002, and van Buren & Boisvert, 2024), available on
    [github](https://github.com/MathieuandSpheroidalWaveFunctions). This code is in Fortran90
    and was interfaced to Python using `numpy.f2py` then wrapped with the current method to
    provide a similar calling convention as the Scipy function of the same name.

    References
    ----------
    Van Buren, A. L., & Boisvert, J. E. (2002). Accurate calculation of prolate spheroidal
    radial functions of the first kind and their first derivatives. Quarterly of Applied
    Mathematics, 60(3), 589-599. <https://doi.org/10.1090/qam/1914443>

    Van Buren, A. L., & Boisvert, J. E. (2004). Improved Calculation of Prolate Spheroidal
    Radial Functions of the Second Kind and Their First Derivatives. Quarterly of Applied
    Mathematics, 62(3), 493-507. <https://doi.org/10.1090/qam/2086042>
    """
    ioprad = 1 if x-1.0 < 1e-10 else 2

    # Add +2 to lnum instead of +1 as it exposes a bug in the Fortran code - if n = 0, zeros
    # are returned instead of the correct value.
    a = prolate_swf.profcn(c=c, m=m, lnum=n-m+2, x1=x-1.0,
                           ioprad=ioprad, iopang=0, iopnorm=0, arg=[0])
    p = swf_t._make(a)

    if ioprad == 1:
        s = np.inf
        sp = np.inf
    else:
        s = p.r2c * np.float_power(10.0, p.ir2e)
        sp = p.r2dc * np.float_power(10.0, p.ir2de)

    # print(m,n,c,x,s, sp)

    return s[n-m], sp[n-m]

spherical_jnpp(n, z)

Second derivative of the spherical Bessel function.

Parameters:

Name Type Description Default
n int

Order (n ≥ 0)

required
z float

Argument of the Bessel function.

required

Returns:

Type Description
float

The second derivative of the spherical Bessel function.

Source code in src/echosms/utils.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def spherical_jnpp(n: int, z: float) -> float:
    """Second derivative of the spherical Bessel function.

    Parameters
    ----------
    n :
        Order (n ≥ 0)
    z :
        Argument of the Bessel function.

    Returns
    -------
    :
        The second derivative of the spherical Bessel function.

    """
    return 1./z**2 * ((n**2-n-z**2)*spherical_jn(n, z) + 2.*z*spherical_jn(n+1, z))

split_dict(d, s)

Split a dict into two dicts based on a list of keys.

Parameters:

Name Type Description Default
d dict

Dict to be split.

required
s list

List of dict keys to use for splitting d.

required

Returns:

Type Description
tuple(dict, dict)

The input dict split into two dicts based on the keys in s. The first tuple item contains the items that do not have keys in s.

Source code in src/echosms/utils.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def split_dict(d: dict, s: list) -> tuple[dict, dict]:
    """Split a dict into two dicts based on a list of keys.

    Parameters
    ----------
    d : dict
        Dict to be split.

    s: list
        List of dict keys to use for splitting `d`.

    Returns
    -------
    : tuple(dict, dict)
        The `input` dict split into two dicts based on the keys in `s`. The first tuple item
        contains the items that do not have keys in `s`.
    """
    contains = {k: v for k, v in d.items() if k in s}
    ncontains = {k: v for k, v in d.items() if k not in s}
    return ncontains, contains

wavenumber(c, f)

Calculate the acoustic wavenumber.

Parameters:

Name Type Description Default
c float

Sound speed [m/s]

required
f float

Frequency [Hz]

required

Returns:

Type Description
float

The acoustic wavenumber [m⁻¹].

Source code in src/echosms/utils.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def wavenumber(c: float, f: float) -> float:
    """Calculate the acoustic wavenumber.

    Parameters
    ----------
    c :
        Sound speed [m/s]

    f :
        Frequency [Hz]

    Returns
    -------
    :
        The acoustic wavenumber [m⁻¹].
    """
    return 2*np.pi*f/c