Skip to content

Required Vibration Frequencies

Overview

The RequiredVibrationFrequencies class is a subclass of CalculationRequirement that is used to get the vibration frequencies for a list of objects. This requirement is used to check if the vibration frequencies are present for a list of objects and to get the vibration frequencies for the desired period.

Usage

This requirements needs the name of the desired objects and the frequency names as they are defined in the attributes_def table in the database. We assume that the attribute name must start by the prefix FREQ- in the database, but when asking for the frequencies in this requirement, the prefix should not be used.

Below there is an example of how to instantiate this requirement:

Python
requirement = RequiredVibrationFrequencies(
    frequencies={
        "LAN-LAN-01": ["HSS-RF", "GEN_BRG_RS-CF"],
        "LAN-LAN-02": ["GEN_BRG_RS-CF"]
    }
)

Database Requirements

This requirement expects that the vibration frequencies are defined in the attributes_def table in the database. The attribute name must start by the prefix FREQ-. After these attributes are defined, they must be associated to their respective subcomponent models in the subcomponent_model_attributes table. Finally, to associate the respective frequency to the object (wind turbine) we need an event in the events table that marks the installation or replacement of the component in the wind turbine.

THis structure allows for us to have not only the vibration frequencies for the correct model of gearbox, generator, etc that is installed in the wind turbine, but also have any changes of these frequencies in time (applicable for example when a component is replaced for a new one of a different model).

Class Definition

RequiredVibrationFrequencies(frequencies, optional=False)

Subclass of CalculationRequirement that defines the vibration frequencies that are required for the calculation.

This will check the performance database for the existence of the required frequencies for the wanted objects.

Arguments here are aligned with the arguments from perfdb.vibration.frequencies.get method.

Parameters:

  • frequencies

    (dict[str, list[str]]) –

    Dictionary with the object names as keys and the list of frequencies as values.

    The name of the frequencies must be like the ones in the database, ignoring the "FREQ-" prefix.

    Example: {"CLE-CLE1-01"["HSS-RF", "GEN_BRG_RS-CF"]}

  • optional

    (bool, default: False ) –

    Set to True if this is an optional requirement. by default False

Source code in echo_energycalc/calculation_requirement_vibration_frequencies.py
Python
def __init__(
    self,
    frequencies: dict[str, list[str]],
    optional: bool = False,
) -> None:
    """
    Subclass of CalculationRequirement that defines the vibration frequencies that are required for the calculation.

    This will check the performance database for the existence of the required frequencies for the wanted objects.

    Arguments here are aligned with the arguments from perfdb.vibration.frequencies.get method.

    Parameters
    ----------
    frequencies : dict[str, list[str]]
        Dictionary with the object names as keys and the list of frequencies as values.

        The name of the frequencies must be like the ones in the database, ignoring the "FREQ-" prefix.

        Example: `{"CLE-CLE1-01"["HSS-RF", "GEN_BRG_RS-CF"]}`
    optional : bool, optional
        Set to True if this is an optional requirement. by default False
    """
    super().__init__(optional)

    # check if object_names are valid
    if not isinstance(frequencies, dict):
        raise TypeError(f"frequencies must be a dictionary, not {type(frequencies)}")
    if not all(isinstance(k, str) for k in frequencies):
        raise TypeError("Keys of frequencies must be strings")
    if not all(isinstance(v, list) for v in frequencies.values()):
        raise TypeError("Values of frequencies must be lists")

    self._frequencies = frequencies

checked property

Attribute that defines if the requirement has been checked. It's value will start as False and will be set to True after the check method is called.

Returns:

  • bool

    True if the requirement has been checked.

data property

Attribute used to store the data required for the calculation.

Initially it is None and will be set with the data acquired by the get_data method. The data type will depend on the subclass implementation, but usually it will be a polars DataFrame or a dictionary.

Returns:

  • Any | None

    Returns the data required for the calculation.

fetched property

Attribute that defines if get_data() has been called on this requirement.

True even when the fetch returned no data (e.g. an optional requirement that found nothing). Use this to distinguish "never fetched" from "fetched but empty/None".

Returns:

  • bool

    True if get_data() has been called at least once.

frequencies property

Dictionary with the object names as keys and the list of frequencies as values.

Returns:

  • dict[str, list[str]]

    Dictionary with the object names as keys and the list of frequencies as values.

optional property

Attribute that defines if the requirement is optional.

If optional is True, the requirement is only validated to check if it could exist, not if it is actually present. This is useful for requirements that are not necessary for all calculations, but are useful for some of them.

Returns:

  • bool

    True if the requirement is optional.

check()

Check that the requirement is met.

This concrete implementation handles two concerns automatically so that subclasses only need to implement _do_check():

  1. Already-checked guard — returns True immediately if check() has already succeeded for this instance, avoiding redundant DB round-trips when _fetch_requirements() iterates requirements on every _compute() call.
  2. Per-thread caching — when _check_cache_key() returns a non-None key, the result produced by _do_check() is stored in a thread-local cache and reused by subsequent instances in the same thread with the same key. Because the cache is never shared across threads, no locking is needed and concurrent Polars operations inside _do_check cannot deadlock.

The optional guard is intentionally delegated to _do_check() because different subclasses have different optional semantics (see _do_check docs).

Returns:

  • bool

    True if the requirement is met; raises on unmet non-optional requirements.

Source code in echo_energycalc/calculation_requirements_core.py
Python
def check(self) -> bool:
    """
    Check that the requirement is met.

    This concrete implementation handles two concerns automatically so that
    subclasses only need to implement ``_do_check()``:

    1. **Already-checked guard** — returns ``True`` immediately if ``check()`` has
       already succeeded for this instance, avoiding redundant DB round-trips when
       ``_fetch_requirements()`` iterates requirements on every ``_compute()`` call.
    2. **Per-thread caching** — when ``_check_cache_key()`` returns a non-None key,
       the result produced by ``_do_check()`` is stored in a thread-local cache and
       reused by subsequent instances in the same thread with the same key. Because
       the cache is never shared across threads, no locking is needed and concurrent
       Polars operations inside ``_do_check`` cannot deadlock.

    The **optional guard** is intentionally delegated to ``_do_check()`` because
    different subclasses have different optional semantics (see ``_do_check`` docs).

    Returns
    -------
    bool
        True if the requirement is met; raises on unmet non-optional requirements.
    """
    if self._checked:
        return True

    cache_key = self._check_cache_key()

    if cache_key is not None:
        _tl = type(self)._cache_local  # noqa: SLF001
        if not hasattr(_tl, "cache"):
            _tl.cache = {}
        cached = _tl.cache.get(cache_key)
        if cached is None:
            self._do_check()
            _tl.cache[cache_key] = self._get_cache_value()
            cached = _tl.cache[cache_key]
        else:
            logger.debug("Cache hit for %s (key=%s)", type(self).__name__, cache_key)
        self._set_from_cache(cached)
    else:
        self._do_check()

    self._checked = True
    return True

get_data(**kwargs)

Method used to get the required vibration frequencies for the desired objects.

This will not do anything other than call the check() method because the check() method already gets the data.

Returns:

  • DataFrame

    DataFrame with columns: object_name, component_type_name, subcomponent_type_name, start_date, and the frequency columns. Component model and subcomponent model will also be available as columns.

    start_date indicates when the frequencies started to be valid.

Source code in echo_energycalc/calculation_requirement_vibration_frequencies.py
Python
def get_data(self, **kwargs) -> pl.DataFrame:  # noqa: ARG002
    """
    Method used to get the required vibration frequencies for the desired objects.

    This will not do anything other than call the check() method because the check() method already gets the data.

    Returns
    -------
    pl.DataFrame
        DataFrame with columns: object_name, component_type_name, subcomponent_type_name, start_date, and the frequency columns. Component model and subcomponent model will also be available as columns.

        start_date indicates when the frequencies started to be valid.
    """
    if not self._checked:
        self.check()

    self._fetched = True
    return self.data