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.

Initialise.

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 acronym.

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
@abc.abstractmethod
def __init__(self):
    """Initialise.

    Attributes
    ----------
    long_name : str
        The long name of the model.
    short_name : str
        A short version of the model's long name, typically an acronym.
    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
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).')

    self.validate_parameters(data_df)

    # 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
@abc.abstractmethod
def calculate_ts_single(self):
    """Calculate the TS for one parameter set."""

validate_parameters(p) abstractmethod

Validate the model parameters.

Parameters:

Name Type Description Default
p dict

Dict containing the model parameters.

required

Raises:

Type Description
ValueError

If any of the model parameters are invalid.

KeyError

If any required model parameters are not present.

Source code in src/echosms/scattermodelbase.py
@abc.abstractmethod
def validate_parameters(self, p):
    """Validate the model parameters.

    Parameters
    ----------
    p : dict
        Dict containing the model parameters.

    Raises
    ------
    ValueError
        If any of the model parameters are invalid.
    KeyError
        If any required model parameters are not present.
    """

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
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
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).')

    self.validate_parameters(data_df)

    # 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, validate_parameters=True, **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 as per the echoSMs coordinate system [°].

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
validate_parameters bool

Whether to validate the model parameters.

True

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 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/dcmmodel.py
def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_type,
                        target_c=None, target_rho=None, validate_parameters=True,
                        **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 as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].
    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``.
    validate_parameters : bool
        Whether to validate the model parameters.

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

    Notes
    -----
    The class implements the code in Section B.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>
    """
    if validate_parameters:
        p = {'medium_c': medium_c, 'medium_rho': medium_rho, 'a': a, 'b': b, 'f': f,
             'boundary_type': boundary_type, 'target_c': target_c, 'target_rho': target_rho,
             'theta': theta}
        self.validate_parameters(p)

    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 = map(lambda m: (-1)**m * Neumann(m)*(jvp(m, Ka) / h1vp(m, Ka)), m)
        case 'pressure release':
            series = 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 = 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

validate_parameters(params)

Validate the model parameters.

See here for calling details.

Source code in src/echosms/dcmmodel.py
def validate_parameters(self, params):
    """Validate the model parameters.

    See [here][echosms.ScatterModelBase.validate_parameters] for calling details.
    """

    p = as_dict(params)
    super()._present_and_in(p, ['boundary_type'], self.boundary_types)
    super()._present_and_positive(p, ['medium_rho', 'medium_c', 'a', 'b', 'f'])

    for bt in np.atleast_1d(p['boundary_type']):
        if bt == 'fluid filled':
            super()._present_and_positive(p, ['target_c', 'target_rho'])

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
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
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).')

    self.validate_parameters(data_df)

    # 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, validate_parameters=True)

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

Pitch angle to calculate the scattering as per the echoSMs coordinate system [°].

required
phi float

Roll angle to calculate the scattering as per the echoSMs coordinate system [°].

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
validate_parameters bool

Whether to validate the model parameters.

True

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
def calculate_ts_single(self, theta, phi, f, target_rho, target_c, validate_parameters=True):
    """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
        Pitch angle to calculate the scattering as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].
    phi : float
        Roll angle to calculate the scattering as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].
    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].
    validate_parameters : bool
        Whether to validate the model parameters.

    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>

    """
    if validate_parameters:
        p = {'theta': theta, 'phi': phi, 'f': f, 'target_rho': f, 'target_c': target_c}
        self.validate_parameters(p)
    return None

validate_parameters(params)

Validate the model parameters.

See here for calling details.

Source code in src/echosms/dwbamodel.py
def validate_parameters(self, params):
    """Validate the model parameters.

    See [here][echosms.ScatterModelBase.validate_parameters] for calling details.
    """

    p = as_dict(params)

PT-DWBA

Bases: ScatterModelBase

Phase-tracking distorted-wave Born approximation scattering model.

Source code in src/echosms/ptdwbamodel.py
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
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).')

    self.validate_parameters(data_df)

    # 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, validate_parameters=True, **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.

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 arranged as per the echoSMs coordinate system, where

  • axis 0 (rows) is the x-axis
  • axis 1 (columns) is the y-axis
  • axis 2: (slices) is the z-axis

Increasing axes indices correspond to increasing x, y, and z values.

required
theta float

Pitch angle to calculate the scattering as per the echoSMs coordinate system [°].

required
phi float

Roll angle to calculate the scattering as per the echoSMs coordinate system [°].

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 irrelevant.

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
validate_parameters bool

Whether to validate the model parameters.

True

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
def calculate_ts_single(self, volume, theta, phi, f, voxel_size, rho, c,
                        validate_parameters=True, **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.

    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 arranged as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems), where

        - axis 0 (rows) is the _x_-axis
        - axis 1 (columns) is the _y_-axis
        - axis 2: (slices) is the _z_-axis

        Increasing axes indices correspond to increasing _x_, _y_, and _z_ values.

    theta : float
        Pitch angle to calculate the scattering as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].

    phi : float
        Roll angle to calculate the scattering as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].

    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 irrelevant.

    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].
    validate_parameters : bool
        Whether to validate the model parameters.

    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>
    """
    if validate_parameters:
        p = {'volume': volume, 'theta': theta, 'phi': phi, 'f': f,
             'voxel_size': voxel_size, 'rho': rho, 'c': c}
        self.validate_parameters(p)

    # Make sure things are numpy arrays
    rho = np.atleast_1d(rho)
    c = np.atleast_1d(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 < -0.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

    # Convert echoSMs rotation angles (which are intrinsic) into extrinsic as
    # that is what ndimage.rotate() below uses.
    if phi == 0.0:  # short circuit the coordinate transformation if we can
        pitch = theta-90
        roll = 0.0
    else:
        rot = R.from_euler('ZYX', (0, theta-90, -phi), degrees=True)
        # for backscatter we don't care about yaw
        _, pitch, roll = rot.as_euler('zyz', degrees=True)

    v = ndimage.rotate(volume, pitch, axes=(0, 2), order=0)
    v = ndimage.rotate(v, roll, axes=(1, 2), 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

    # cumulative summation of phase along the z-direction
    phase = dph.cumsum(axis=2) - 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()))

validate_parameters(params)

Validate the model parameters.

See here for calling details.

Source code in src/echosms/ptdwbamodel.py
def validate_parameters(self, params):
    """Validate the model parameters.

    See [here][echosms.ScatterModelBase.validate_parameters] for calling details.
    """

    p = as_dict(params)

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
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
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).')

    self.validate_parameters(data_df)

    # 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, validate_parameters=True)

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

Pitch angle to calculate the scattering as per the echoSMs coordinate system [°].

required
phi float

Roll angle to calculate the scattering as per the echoSMs coordinate system [°].

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
validate_parameters bool

Whether to validate the model parameters.

True

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
def calculate_ts_single(self, theta, phi, f, target_rho, target_c, validate_parameters=True):
    """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
        Pitch angle to calculate the scattering as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].
    phi : float
        Roll angle to calculate the scattering as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].
    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].
    validate_parameters : bool
        Whether to validate the model parameters.

    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>
    """
    if validate_parameters:
        p = {'theta': theta, 'phi': phi, 'f': f, 'target_rho': f, 'target_c': target_c}
        self.validate_parameters(p)

    return None

validate_parameters(params)

Validate the model parameters.

See here for calling details.

Source code in src/echosms/sdwbamodel.py
def validate_parameters(self, params):
    """Validate the model parameters.

    See [here][echosms.ScatterModelBase.validate_parameters] for calling details.
    """

    p = as_dict(params)

ESModel

Bases: ScatterModelBase

Elastic sphere (ES) scattering model.

This class calculates acoustic backscatter from elastic spheres.

Source code in src/echosms/esmodel.py
def __init__(self):
    super().__init__()
    self.long_name = 'elastic sphere'
    self.short_name = 'es'
    self.analytical_type = 'exact'
    self.boundary_types = ['elastic']
    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
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).')

    self.validate_parameters(data_df)

    # 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, validate_parameters=True, **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
validate_parameters bool

Whether to validate the model parameters.

True

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
def calculate_ts_single(self, medium_c, medium_rho, a, f,
                        target_longitudinal_c, target_transverse_c, target_rho,
                        validate_parameters=True,
                        **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³].
    validate_parameters : bool
        Whether to validate the model parameters.

    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.
    """
    if validate_parameters:
        p = {'medium_c': medium_c, 'medium_rho': medium_rho, 'a': a, 'f': f,
             'target_longitudinal_c': target_longitudinal_c,
             'target_transverse_c': target_transverse_c,
             'target_rho': target_rho}
        self.validate_parameters(p)

    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 arbitrary
    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 * sum(map(S, n))

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

validate_parameters(params)

Validate the model parameters.

See here for calling details.

Source code in src/echosms/esmodel.py
def validate_parameters(self, params):
    """Validate the model parameters.

    See [here][echosms.ScatterModelBase.validate_parameters] for calling details.
    """

    p = as_dict(params)
    super()._present_and_in(p, ['boundary_type'], self.boundary_types)
    super()._present_and_positive(p, ['medium_rho', 'medium_c', 'a', 'f',
                                      'target_longitudinal_c',
                                      'target_transverse_c', 'target_rho'])

KAModel

Bases: ScatterModelBase

Kirchhoff approximation (KA) scattering model.

This class calculates acoustic scatter from arbitrary surfaces.

Source code in src/echosms/kamodel.py
def __init__(self):
    super().__init__()
    self.long_name = 'Kirchhoff approximation'
    self.short_name = 'ka'
    self.analytical_type = 'approximate'
    self.boundary_types = ['pressure release']
    self.shapes = ['closed surfaces']
    self.max_ka = 20  # [1]
    self.no_expand_parameters = ['mesh']

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
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).')

    self.validate_parameters(data_df)

    # 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, theta, phi, f, mesh, boundary_type, validate_parameters=True, **kwargs)

Calculate the scatter using the ka 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
theta float

Pitch angle to calculate the scattering as per the echoSMs coordinate system [°].

required
phi float

Roll angle to calculate the scattering as per the echoSMs coordinate system [°].

required
f float

Frequency to calculate the scattering at [Hz].

required
mesh Any

The triangular mesh that defines the scattering surface. This parameter must provide attributes with names of:

  • triangles_center (the position of the centre of each triangular face [m]),
  • face_normals (the outward-pointing unit normals for each triangular face),
  • area_faces (the area of each triangular face [m²]).

A suitable library for creating and manipulating triangular meshes is trimesh.

required
boundary_type str

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

required
validate_parameters bool

Whether to validate the model parameters.

True

Returns:

Type Description
float

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

Notes

The class implements the code in Foote (1985).

References

Foote, K. G. (1985). Rather-high-frequency sound scattering of swimbladdered fish. The Journal of the Acoustical Society of America, 78(2), 688–700. https://doi.org/10.1121/1.392438

Source code in src/echosms/kamodel.py
def calculate_ts_single(self, medium_c, theta, phi, f, mesh,
                        boundary_type, validate_parameters=True, **kwargs) -> float:
    """
    Calculate the scatter using the ka model for one set of parameters.

    Parameters
    ----------
    medium_c : float
        Sound speed in the fluid medium surrounding the target [m/s].
    theta : float
        Pitch angle to calculate the scattering as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].
    phi : float
        Roll angle to calculate the scattering as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].
    f : float
        Frequency to calculate the scattering at [Hz].
    mesh : Any
        The triangular mesh that defines the scattering surface. This parameter must provide
        attributes with names of:

        - `triangles_center` (the position of the centre of each triangular face [m]),
        - `face_normals` (the outward-pointing unit normals for each triangular face),
        - `area_faces` (the area of each triangular face [m²]).

        A suitable library for creating and manipulating triangular meshes
        is [trimesh](https://trimesh.org).
    boundary_type : str
        The boundary type. Supported types are given in the `boundary_types` class variable.
    validate_parameters : bool
        Whether to validate the model parameters.

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

    Notes
    -----
    The class implements the code in Foote (1985).

    References
    ----------
    Foote, K. G. (1985). Rather-high-frequency sound scattering of swimbladdered fish.
    The Journal of the Acoustical Society of America, 78(2), 688–700.
    <https://doi.org/10.1121/1.392438>

    """
    if validate_parameters:
        p = {'medium_c': medium_c, 'theta': theta, 'phi': phi, 'f': f, 'mesh': mesh}
        self.validate_parameters(p)

    if boundary_type not in self.boundary_types:
        raise ValueError(f'The {self.long_name} model does not support '
                         f'a model type of "{boundary_type}".')

    # This model keeps the organism fixed and varies the incident wave vector. So need
    # to convert the theta and phi echoSMs coordinate sytem Tait-Bryan angles
    # into an (x,y,z) vector.

    # Acoustic wave incident vector and its' norm
    rot = R.from_euler('ZYX', (0, theta-90, -phi), degrees=True)
    k_norm = rot.as_matrix() @ np.array([[0, 0, 1]]).T
    k = k_norm * wavenumber(medium_c, f)

    r = mesh.triangles_center  # position vector of each surface element
    dS = mesh.area_faces.reshape((-1, 1))  # [m^2]

    kn_nn = mesh.face_normals @ k_norm

    fbs = 1./wavelength(medium_c, f)\
        * np.sum(np.exp(2j*r @ k) * np.heaviside(kn_nn, 0.5) * kn_nn * dS)

    return 10*log10(abs(fbs)**2)  # ts

validate_parameters(params)

Validate the model parameters.

See here for calling details.

Source code in src/echosms/kamodel.py
def validate_parameters(self, params):
    """Validate the model parameters.

    See [here][echosms.ScatterModelBase.validate_parameters] for calling details.
    """

    p = as_dict(params)
    super()._present_and_in(p, ['boundary_type'], self.boundary_types)
    super()._present_and_positive(p, ['medium_c', 'f'])

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
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
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).')

    self.validate_parameters(data_df)

    # 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, boundary_type, target_c=None, target_rho=None, shell_c=None, shell_rho=None, shell_thickness=None, validate_parameters=True, **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
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
validate_parameters bool

Whether to validate the model parameters.

True

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
def calculate_ts_single(self, medium_c, medium_rho, a, f, boundary_type,
                        target_c=None, target_rho=None,
                        shell_c=None, shell_rho=None, shell_thickness=None,
                        validate_parameters=True,
                        **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].
    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.
    validate_parameters : bool
        Whether to validate the model parameters.

    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>
    """
    if validate_parameters:
        p = {'medium_c': medium_c, 'medium_rho': medium_rho, 'a': a, 'f': f,
             'boundary_type': boundary_type, 'target_c': target_c, 'target_rho': target_rho,
             'shell_c': shell_c, 'shell_rho': shell_rho, 'shell_thickness': shell_thickness}
        self.validate_parameters(p)

    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

validate_parameters(params)

Validate the model parameters.

See here for calling details.

Source code in src/echosms/mssmodel.py
def validate_parameters(self, params):
    """Validate the model parameters.

    See [here][echosms.ScatterModelBase.validate_parameters] for calling details.

    """
    p = as_dict(params)
    super()._present_and_in(p, ['boundary_type'], self.boundary_types)
    super()._present_and_positive(p, ['medium_rho', 'a', 'f'])

    for bt in np.atleast_1d(p['boundary_type']):
        match bt:
            case 'fluid filled':
                super()._present_and_positive(p, ['target_c', 'target_rho'])
            case 'fluid shell fluid interior':
                super()._present_and_positive(p, ['target_c', 'target_rho', 'shell_c',
                                                  'shell_rho', 'shell_thickness'])
            case 'fluid shell pressure release interior':
                super()._present_and_positive(p, ['shell_c', 'shell_rho', 'shell_thickness'])

PSMSModel

Bases: ScatterModelBase

Prolate spheroidal modal series (PSMS) scattering model.

Note

The fluid filled boundary type implementation is currently only accurate for weakly scattering interiors. Support for strongly scattering (e.g., gas-filled) will come later.

Source code in src/echosms/psmsmodel.py
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
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).')

    self.validate_parameters(data_df)

    # 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, validate_parameters=True)

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 as per the echoSMs coordinate system [°].

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
validate_parameters bool

Whether to validate the input parameters.

True

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
def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_type,
                        target_c=None, target_rho=None, validate_parameters=True):
    """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 as per the echoSMs
        [coordinate system](https://ices-tools-dev.github.io/echoSMs/
        conventions/#coordinate-systems) [°].
    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
        Sound speed in the fluid inside the target [m/s].
        Only required for `boundary_type` of ``fluid filled``.
    target_rho : float
        Density of the fluid inside the target [kg/m³].
        Only required for `boundary_type` of ``fluid filled``.
    validate_parameters : bool
        Whether to validate the input parameters.

    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.
    """
    if validate_parameters:
        p = {'medium_c': medium_c, 'medium_rho': medium_rho, 'a': a, 'b': b,
             'theta': theta, 'f': f, 'boundary_type': boundary_type,
             'target_c': target_c, 'target_rho': target_rho}
        self.validate_parameters(p)

    if boundary_type not in self.boundary_types:
        raise ValueError(f'The {self.long_name} model does not support '
                         f'a model type of "{boundary_type}".')

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

    km = wavenumber(medium_c, f)
    hm = km*q

    if boundary_type == 'fluid filled':
        g = target_rho / medium_rho
        ht = wavenumber(target_c, f)*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*km*b))
    n_max = int(m_max + np.ceil(hm/2))

    f_sca = 0.0
    for m in range(m_max+1):
        epsilon_m = Neumann(m)
        cos_term = np.cos(m*(phi_sca - phi_inc))
        for n in range(m, n_max+1):
            Smn_inc, _ = pro_ang1(m, n, hm, np.cos(theta_inc))
            Smn_sca, _ = pro_ang1(m, n, hm, np.cos(theta_sca))
            # The Meixner-Schäfke normalisation scheme for the angular function of the first
            # kind. Refer to eqn 21.7.11 in Abramowitz, M., and Stegun, I. A. (1964).
            # Handbook of Mathematical Functions with Formulas, Graphs, and Mathematical Tables
            # (Dover, New York), 10th ed.
            N_mn = 2/(2*n+1) * factorial(n+m) / factorial(n-m)

            R1m, dR1m = pro_rad1(m, n, hm, xim)
            R2m, dR2m = pro_rad2(m, n, hm, xim)

            match boundary_type:
                case 'fluid filled':
                    # Note: we can implement the simpler equations if impedances are
                    # similar between the medium and the target. The gas-filled
                    # condition does not meet that, so we have two paths here. The simplified
                    # equations are quicker, so it is worth to do.
                    if (abs(1.0-target_c/medium_c) <= 0.01) and (abs(1.0-g) <= 0.01):
                        Amn = PSMSModel._fluidfilled_approx(m, n, hm, ht, xim, g)
                    else:
                        Amn = PSMSModel._fluidfilled_exact(m, n, hm, ht, xim, g, theta_inc)
                case 'pressure release':
                    Amn = -R1m/(R1m + 1j*R2m)
                case 'fixed rigid':
                    Amn = -dR1m/(dR1m + 1j*dR2m)

            f_sca += epsilon_m * (Smn_inc / N_mn) * Smn_sca * Amn * cos_term

    return 20*np.log10(np.abs(-2j / km * f_sca))

validate_parameters(params)

Validate the model parameters.

See here for calling details.

Source code in src/echosms/psmsmodel.py
def validate_parameters(self, params):
    """Validate the model parameters.

    See [here][echosms.ScatterModelBase.validate_parameters] for calling details.
    """

    p = as_dict(params)
    super()._present_and_in(p, ['boundary_type'], self.boundary_types)
    super()._present_and_positive(p, ['medium_c', 'medium_rho', 'a', 'b', 'f'])

    for bt in np.atleast_1d(p['boundary_type']):
        match bt:
            case 'fluid filled':
                super()._present_and_positive(p, ['target_c', 'target_rho'])

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 (for example, entries have been added for all known calibration sphere sizes).

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
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
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
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 definitions 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
def specification(self, name):
    """Model definitions 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.

Notes

The column names in the source benchmark files have been changed to be the same as those used in the ReferenceModels model definitions.

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
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)

    # Change the column names to match the reference model names used in ReferenceModels
    self.angle_dataset.rename(columns=BenchmarkData.a_rename, inplace=True)
    self.freq_dataset.rename(columns=BenchmarkData.f_rename, inplace=True)

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
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
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
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

as_dict(params)

Convert model parameters from DataFrame or DataArray to dict.

Parameters:

Name Type Description Default
params dict | DataFrame | DataArray

The model parameters

required

Raises:

Type Description
TypeError:

If the input data type is not supported.

Returns:

Type Description
dict

A dict containing the model parameters.

Source code in src/echosms/utils.py
def as_dict(params: dict | pd.DataFrame | xr.DataArray) -> dict:
    """Convert model parameters from DataFrame or DataArray to dict.

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

    Raises
    ------
    TypeError:
        If the input data type is not supported.

    Returns
    -------
    :
        A dict containing the model parameters.
    """
    if isinstance(params, dict):
        return params

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

    if isinstance(params, xr.DataArray):
        return dict(zip(params.coords, params.indexes.values())) | p
    elif isinstance(params, pd.DataFrame):
        return params.to_dict(orient='list') | p

    raise TypeError('Only dict, DataFrame, or DataArray are accepted.')

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
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, eta, norm=False)

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 eta.

Parameters:

Name Type Description Default
m int

The order parameter (≥ 0)

required
n int

The degree parameter (≥ m).

required
c float

The size parameter.

required
eta float

The angular coordinate, η, where |η| ≤ 1.

required
norm

If False, returned values are not normalised (i.e., the Meixner-Schäfke normlalisation scheme is used). For large m this norm becomes very large. If True the returned values are scaled by the square root of the normalisation of the corresponding Legendre function. This avoids the large values that occur when norm is False.

False

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
def pro_ang1(m: int, n: int, c: float, eta: float, norm=False) -> 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 `eta`.

    Parameters
    ----------
    m :
        The order parameter (≥ 0)
    n :
        The degree parameter (≥ `m`).
    c :
        The size parameter.
    eta :
        The angular coordinate, η, where |η| ≤ 1.
    norm :
        If `False`, returned values are not normalised (i.e., the Meixner-Schäfke normlalisation
        scheme is used). For large `m` this norm becomes very large. If `True` the returned values
        are scaled by the square root of the normalisation of the corresponding Legendre function.
        This avoids the large values that occur when `norm` is `False`.

    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>
    """
    if m < 0:
        raise ValueError('The m parameter must be positive.')
    if n < m:
        raise ValueError('The n parameter must be greater than or equal to the m parameter.')
    if abs(eta) > 1.0:
        raise ValueError('The eta parameter must be less than or equal to 1')

    a = prolate_swf.profcn(c=c, m=m, lnum=n-m+2, x1=0.0, ioprad=0, iopang=2,
                           iopnorm=int(norm), arg=[eta])
    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)

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

pro_rad1(m, n, c, xi)

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 xi.

Parameters:

Name Type Description Default
m int

The order parameter (≥ 0).

required
n int

The degree parameter (≥ m).

required
c float

The size parameter.

required
xi float

The radial coordinate, ξ, where ξ ≥ 1.

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
def pro_rad1(m: int, n: int, c: float, xi: 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 `xi`.

    Parameters
    ----------
    m :
        The order parameter (≥ 0).
    n :
        The degree parameter (≥ `m`).
    c :
        The size parameter.
    xi :
        The radial coordinate, ξ, where ξ ≥ 1.

    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>
    """
    if m < 0:
        raise ValueError('The m parameter must be positive.')
    if n < m:
        raise ValueError('The n parameter must be greater than or equal to the m parameter.')
    if xi < 1.0:
        raise ValueError('The xi parameter must be greater than or equal to 1')

    a = prolate_swf.profcn(c=c, m=m, lnum=n-m+2, x1=xi-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)

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

pro_rad2(m, n, c, xi)

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 xi.

Parameters:

Name Type Description Default
m int

The order parameter (≥ 0).

required
n int

The degree parameter (≥ m).

required
c float

The size parameter.

required
xi float

The radial coordinate, ξ, where ξ ≥ 1.

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
def pro_rad2(m: int, n: int, c: float, xi: float) -> tuple[float, float]:
    """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 `xi`.

    Parameters
    ----------
    m :
        The order parameter (≥ 0).
    n :
        The degree parameter (≥ `m`).
    c :
        The size parameter.
    xi :
        The radial coordinate, ξ, where ξ ≥ 1.

    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>
    """
    if m < 0:
        raise ValueError('The m parameter must be positive.')
    if n < m:
        raise ValueError('The n parameter must be greater than or equal to the m parameter.')
    if xi < 1.0:
        raise ValueError('The xi parameter must be greater than or equal to 1')

    ioprad = 1 if xi-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=xi-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)

    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
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
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

wavelength(c, f)

Calculate the acoustic wavelength.

Parameters:

Name Type Description Default
c float

Sound speed [m/s]

required
f float

Frequency [Hz]

required

Returns:

Type Description
float

The acoustic wavelength [m].

Source code in src/echosms/utils.py
def wavelength(c: float, f: float) -> float:
    """Calculate the acoustic wavelength.

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

    f :
        Frequency [Hz]

    Returns
    -------
    :
        The acoustic wavelength [m].
    """
    return c/f

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
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