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