Skip to content

Required Vibration Data

Overview

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

Usage

Besides the name of the desired objects the requirements also needs arguments to determine which vibration data is needed. These arguments are in line with the ones of the perfdb.vibration.spectrum.get method and are detailed in the Class Definition section.

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

requirement = RequiredVibrationData(
    object_names=["LAN-LAN-01", "LAN-LAN-02"],
    data_type="spectrum",
    sensors=["4 - HSS - Radial"],
    acquisition_frequencies=["High"],
    spectrum_type="Envelope,
    spectrum_unit="Order",
    amplitude_type="Peak",
)

Keep in mind that this class supports returning both time series and spectrum data. This is specified in the data_type argument but the actual data availability will depend on the wind turbine model.

After instantiation and checking, the get_data method will return a DataFrame with the vibration data for the desired period. This DataFrame will not have an index and the columns available are object_name, sensor, acquisition_frequency, timestamp and value, where value is a 2D Numpy array with the vibration data (frequency x amplitude).

Database Requirements

This requirement expects that the vibration data is saved in table raw_data_values and the perfdb.vibration.spectrum.get method is configured to handle this data.

Class Definition

RequiredVibrationData(object_names, analysis_type, data_type='Vibration', sensors=None, acquisition_frequencies=None, variable_names=None, spectrum_type=None, spectrum_unit=None, amplitude_type=None, optional=False)

Subclass of CalculationRequirement that defines the vibration data that is required for the calculation.

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

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

Parameters:

  • object_names

    (list[str]) –

    List of the object names for which the vibration data is required.

  • analysis_type

    (Literal['timeseries', 'spectrum']) –

    Type of data to get.

  • data_type

    (Literal['Vibration'] = "Vibration",, default: 'Vibration' ) –

    Type of data to get. Can be either 'Vibration'. By default 'Vibration'.

  • sensors

    (list[VIBRATION_GE_ALLOWED_SENSOR_NAMES | VIBRATION_GAMESA_ALLOWED_SENSOR_NAMES] | None, default: None ) –

    List of the sensors to get the data for. The options are as shown below:

    • GE: "Planetary", "LSS", "HSS", "Generator RS", "Generator GS", "Main Bearing", "Tower Sway Axial", "Tower Sway Transverse"
    • Gamesa: "1 - Generator GS - Radial", "2 - Planetary - Axial", "3 - Main Bearing GS - Radial", "4 - HSS - Radial", "5 - Main Bearing RS - Axial", "6 - HSS - Axial", "7 - Generator RS - Axial", "8 - Generator RS - Radial"

    These must be specified with the matching manufacturer and cannot be mixed. If GE is selected only GE sensors are allowed and vice versa.

  • acquisition_frequencies

    (list[Literal['Low', 'High', 'Filter']] | None, default: None ) –

    Acquisition frequency, only applicable for Gamesa turbines. For GE leave as None.

  • spectrum_type

    (Literal['Normal', 'Envelope'] | None, default: None ) –

    What kind of spectrum should be returned. Only applicable for spectrum data.

  • spectrum_unit

    (Literal['Hz', 'Order'] | None, default: None ) –

    Unit of the spectrum. Only applicable for spectrum data. Can be one of ['Hz', 'Order'].

  • amplitude_type

    (Literal['RMS', 'Peak', 'Peak-to-Peak'] | None, default: None ) –

    Type of amplitude to return. Only applicable for spectrum data. Can be one of ["RMS", "Peak", "Peak-to-Peak"].

  • optional

    (bool, default: False ) –

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

Source code in echo_energycalc/calculation_requirement_vibration_data.py
def __init__(
    self,
    object_names: list[str],
    analysis_type: Literal["timeseries", "spectrum"],
    data_type: Literal["Vibration"] = "Vibration",
    sensors: list[VIBRATION_GE_ALLOWED_SENSOR_NAMES | VIBRATION_GAMESA_ALLOWED_SENSOR_NAMES] | None = None,
    acquisition_frequencies: list[Literal["Low", "High", "Filter"]] | None = None,
    variable_names: Literal["Acceleration - X", "Acceleration - Y", "Position - X", "Position - Y"] | None = None,
    spectrum_type: Literal["Normal", "Envelope"] | None = None,
    spectrum_unit: Literal["Hz", "Order"] | None = None,
    amplitude_type: Literal["RMS", "Peak", "Peak-to-Peak"] | None = None,
    optional: bool = False,
) -> None:
    """
    Subclass of CalculationRequirement that defines the vibration data that is required for the calculation.

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

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

    Parameters
    ----------
    object_names : list[str]
        List of the object names for which the vibration data is required.
    analysis_type : Literal["timeseries", "spectrum"]
        Type of data to get.
    data_type : Literal['Vibration'] = "Vibration",
        Type of data to get. Can be either 'Vibration'. By default 'Vibration'.
    sensors : list[VIBRATION_GE_ALLOWED_SENSOR_NAMES | VIBRATION_GAMESA_ALLOWED_SENSOR_NAMES] | None
        List of the sensors to get the data for. The options are as shown below:

        - GE: "Planetary", "LSS", "HSS", "Generator RS", "Generator GS", "Main Bearing", "Tower Sway Axial", "Tower Sway Transverse"
        - Gamesa: "1 - Generator GS - Radial", "2 - Planetary - Axial", "3 - Main Bearing GS - Radial", "4 - HSS - Radial", "5 - Main Bearing RS - Axial", "6 - HSS - Axial", "7 - Generator RS - Axial", "8 - Generator RS - Radial"

        These must be specified with the matching manufacturer and cannot be mixed. If GE is selected only GE sensors are allowed and vice versa.
    acquisition_frequencies : list[Literal["Low", "High", "Filter"]] | None
        Acquisition frequency, only applicable for Gamesa turbines. For GE leave as None.
    spectrum_type : Literal["Normal", "Envelope"] | None
        What kind of spectrum should be returned. Only applicable for spectrum data.
    spectrum_unit : Literal['Hz', 'Order'] | None
        Unit of the spectrum. Only applicable for spectrum data. Can be one of ['Hz', 'Order'].
    amplitude_type : Literal["RMS", "Peak", "Peak-to-Peak"] | None
        Type of amplitude to return. Only applicable for spectrum data. Can be one of ["RMS", "Peak", "Peak-to-Peak"].
    optional : bool, optional
        Set to True if this is an optional requirement. by default False
    """
    super().__init__(optional)

    self._manufacturer = None

    # check if object_names is a list of strings
    if not isinstance(object_names, list):
        raise TypeError(f"object_names must be a list, not {type(object_names)}")
    if not all(isinstance(name, str) for name in object_names):
        raise TypeError(f"all object_names must be str, not {[type(name) for name in object_names]}")
    self._object_names = object_names
    # checking if analysis_type is valid
    if analysis_type not in ["timeseries", "spectrum"]:
        raise ValueError(f"analysis_type must be one of ['timeseries', 'spectrum'], not {analysis_type}")
    self._analysis_type = analysis_type
    # checking if data_type is valid
    if data_type not in ["Vibration"]:
        raise ValueError(f"data_type must be one of ['Vibration'], not {data_type}")
    self._data_type = data_type
    # we will only check sensors when actually getting the data, but lets check if not None
    if sensors is None:
        raise ValueError("sensors must be a list of sensors, not None")
    self._sensors = sensors
    # _acquisition_frequencies will be validated in check method
    self._acquisition_frequencies = acquisition_frequencies
    # _variable_names will be validated in check method
    self._variable_names = variable_names

    # values specific to spectrum
    if analysis_type == "spectrum":
        # checking if spectrum_type is valid
        if spectrum_type not in ["Normal", "Envelope"]:
            raise ValueError(f"spectrum_type must be one of ['Normal', 'Envelope'], not {spectrum_type}")
        self._spectrum_type = spectrum_type
        # checking if amplitude_type is valid
        if amplitude_type not in ["RMS", "Peak", "Peak-to-Peak"]:
            raise ValueError(f"amplitude_type must be one of ['RMS', 'Peak', 'Peak-to-Peak'], not {amplitude_type}")
        self._amplitude_type = amplitude_type
        # checking if spectrum_unit is valid
        if spectrum_unit not in ["Hz", "Order"]:
            raise ValueError(f"spectrum_unit must be one of ['Hz', 'Order'], not {spectrum_unit}")
        self._spectrum_unit = spectrum_unit
    else:
        # checking if any of the spectrum specific parameters are not None
        if any([spectrum_type, amplitude_type]):
            raise ValueError(
                "spectrum_type, amplitude_type must be None when analysis_type is timeseries",
            )
        self._spectrum_type = None
        self._amplitude_type = None
        self._spectrum_unit = None

acquisition_frequencies property

Acquisition frequency, only applicable for Gamesa turbines.

Returns:

  • list[Literal['Low', 'High', 'Filter']] | None

    Acquisition frequency, only applicable for Gamesa turbines.

amplitude_type property

Type of amplitude to return.

Returns:

  • Literal['RMS', 'Peak', 'Peak-to-Peak'] | None

    Type of amplitude to return.

analysis_type property

Type of analysis.

Returns:

  • Literal['timeseries', 'spectrum']

    Type of analysis.

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 pandas DataFrame or a dictionary.

Returns:

  • Any | None

    Returns the data required for the calculation.

data_type property

Type of data to get.

Returns:

  • Literal['Vibration']

    Type of data to get.

manufacturer property

Manufacturer of the wind turbine.

Returns:

  • str

    Manufacturer of the wind turbine.

objects property

List of the object names for which the vibration data is required.

Returns:

  • list[str]

    List of the object names for which the vibration data is required.

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.

sensors property

List of the sensors to get the data for.

Returns:

  • list[VIBRATION_GE_ALLOWED_SENSOR_NAMES | VIBRATION_GAMESA_ALLOWED_SENSOR_NAMES] | None

    List of the sensors to get the data for.

spectrum_type property

What kind of spectrum should be returned.

Returns:

  • Literal['Normal', 'Envelope'] | None

    What kind of spectrum should be returned.

spectrum_unit property

Unit of the spectrum.

Returns:

  • Literal['Hz', 'Order'] | None

    Unit of the spectrum.

variable_names property

Variable names to get the data for.

Returns:

  • Literal['Acceleration - X', 'Acceleration - Y', 'Position - X', 'Position - Y'] | None

    Variable names to get the data for.

check()

Method used to check if all required vibration data is present in the database for each object.

This will raise an error if any of the required vibration data is missing.

Returns:

  • bool

    Returns True if all required vibration data is present in the database for each object.

Source code in echo_energycalc/calculation_requirement_vibration_data.py
def check(self) -> bool:
    """
    Method used to check if all required vibration data is present in the database for each object.

    This will raise an error if any of the required vibration data is missing.

    Returns
    -------
    bool
        Returns True if all required vibration data is present in the database for each object.
    """
    if self.optional:
        return True

    # checking if objects are present in the database
    objs = self._perfdb.objects.instances.get(
        object_names=self._object_names,
        get_attributes=True,
        output_type="DataFrame",
        attribute_names=["manufacturer"],
    )
    if len(objs) != len(self._object_names):
        wrong_objs = set(self._object_names) - set(objs.index)
        raise ValueError(f"Objects {wrong_objs} are not present in the database")

    # checking if all objects have manufacturer attribute set
    if "manufacturer" not in objs.columns:
        raise ValueError("Objects do not have manufacturer attribute set")
    if objs["manufacturer"].isna().any():
        worng_objs = objs[objs["manufacturer"].isna()].index.to_list()
        raise ValueError(f"Objects {worng_objs} do not have manufacturer attribute set")
    # checking if all objects have the same manufacturer
    if len(objs["manufacturer"].unique()) != 1:
        raise ValueError("Objects have different manufacturers")
    self._manufacturer = objs["manufacturer"].iloc[0]

    # validating acquisition_frequencies
    if self._manufacturer == "Gamesa":
        if self._acquisition_frequencies is None:
            raise ValueError("acquisition_frequencies must be specified for Gamesa turbines in case of vibration sensors")
        if not all(freq in ["Low", "High", "Filter"] for freq in self._acquisition_frequencies):
            raise ValueError("acquisition_frequencies must be a list of ['Low', 'High', 'Filter']")
    elif self._acquisition_frequencies is not None:
        raise ValueError("acquisition_frequencies must be None for manufacturers different than Gamesa")
    elif self._variable_names is not None:
        raise ValueError("variable_names must be None for manufacturers different than Gamesa")

    # checking if the values passed are correct would involve duplicating the logic in perfdb.vibration.spectrum.get that is already fairly complex, so not worth it
    # this way we are returning True and only checking when actually getting the data

    self._checked = True

    return True

get_data(period, **kwargs)

Method used to get the vibration data for the required sensors and period.

Parameters:

  • period

    (DateTimeRange) –

    Desired period for the vibration data.

Returns:

  • DataFrame

    DataFrame with the vibration data for the required sensors and period.

Source code in echo_energycalc/calculation_requirement_vibration_data.py
def get_data(self, period: DateTimeRange, **kwargs) -> DataFrame:  # noqa: ARG002
    """
    Method used to get the vibration data for the required sensors and period.

    Parameters
    ----------
    period : DateTimeRange
        Desired period for the vibration data.

    Returns
    -------
    DataFrame
        DataFrame with the vibration data for the required sensors and period.
    """
    # checking if necessary keyword arguments are present
    if not isinstance(period, DateTimeRange):
        raise TypeError(f"period must be a DateTimeRange object, not {type(period)}")

    # check if requirement has been checked
    if not self._checked:
        self.check()

    try:
        # getting the data
        if self.analysis_type == "timeseries":
            df = self._perfdb.vibration.timeseries.get(
                period=period,
                object_names=self.objects,
                data_type=self.data_type,
                manufacturer=self.manufacturer,
                sensors=self.sensors,
                acquisition_frequencies=self.acquisition_frequencies,
                variable_names=self.variable_names,
            )
        else:
            df = self._perfdb.vibration.spectrum.get(
                period=period,
                object_names=self.objects,
                data_type=self.data_type,
                manufacturer=self.manufacturer,
                sensors=self.sensors,
                acquisition_frequencies=self.acquisition_frequencies,
                variable_names=self.variable_names,
                spectrum_type=self.spectrum_type,
                amplitude_type=self.amplitude_type,
                unit=self.spectrum_unit,
            )
            df = df.reset_index()[["object_name", "sensor", "acquisition_frequency", "timestamp", "value"]].copy()

        # adding relevant columns
        df["analysis_type"] = "timeseries"
        df["spectrum_type"] = NA if self.analysis_type == "timeseries" else self.spectrum_type
        df = df.reset_index()[
            ["object_name", "analysis_type", "spectrum_type", "sensor", "acquisition_frequency", "timestamp", "value"]
        ].copy()
        # data conversion
        df = df.astype({"object_name": "string[pyarrow]", "analysis_type": "string[pyarrow]"})

        self._data = df

    except Exception as e:
        if self.optional:
            self._data = None
        else:
            raise e

    return self.data