### ===================================================================================================================
### Functionality for the equipments in the vibration simulation
### ===================================================================================================================
# Copyright ©2026 Haskoning Nederland B.V.
### ===================================================================================================================
### 1. Import modules
### ===================================================================================================================
# General imports
from pathlib import Path
from typing import Literal, Optional, Union, List
# References for functions and classes in the haskoning_atr_tools package
from haskoning_atr_tools.signal_processing import FrequencyDomainData
from haskoning_atr_tools.vibration_contour_plot.helper_functions import load_data_from_file, v_to_db
from haskoning_atr_tools.vibration_contour_plot.equipment.equipment import Equipment
### ===================================================================================================================
### 2. Function to create Equipment instances from file
### ===================================================================================================================
[docs]
def create_equipments_from_file(
project, file_path: Union[Path, str], sheet_name: Union[str, int] = 0,
activity_filter: Optional[Literal['continuous', 'intermittent', 'emergency']] = None,
column_mapping: Optional[dict[str, str]] = None) -> List[Equipment]:
"""
Function to create equipments from file for vibration analysis.
Input:
- project (obj): Project object containing collections of objects and project variables.
- file_path (Path or str): Path to file containing the equipment data for vibration analysis. The file can be
provided as Excel-file or csv-file. In case of Excel, the sheet name should be provided for the worksheet with
the equipment data. In both cases the header should be provided in row 1. When providing a column 'Calculate'
the user can specify to use the equipment or not (overrules other filters). If column is not present, all
equipments will be loaded, depending on other filters applied.
- sheet_name (str or int): Sheet name or index of sheet with the equipment data. Default value is 0, selecting
the first sheet in the Excel file. Input is ignored for csv-files.
- activity_filter (list): Optional input to limit the equipments loaded in the vibration simulation based on the
activity. Provide one or more of the following activity types in the list: 'continuous', 'intermittent', or
'emergency'. Default value is None, all equipments are loaded.
- column_mapping (dict): Optional input to specify other column headers for the input of the file. This might be
convenient if the data is provided with other column names. Default value is None, the default names are used
for loading the equipments from the file.
Output:
- The equipments are created and added to the project, based on the provided filters.
- Returns list of instances of Equipment class created in the function.
"""
default_optional_column_mapping = {
'calculate': 'Calculate',
'center_frequency': 'Center frequency [Hz]',
'angular_frequency': 'Angular frequency [rad/s]',
'rpm': 'RPM'}
# Read file and handle the default user options
df, column_mapping, defaults_used = load_data_from_file(
file_path=file_path, sheet_name=sheet_name, column_mapping=column_mapping,
default_column_mapping={
'name': 'Name',
'id': 'ID',
'x': 'X coordinate [m]',
'y': 'Y coordinate [m]',
'z': 'Z coordinate [m]',
'vibration_source_level': 'Vibration source level',
'unit': 'Unit',
'min_frequency': 'Min frequency [Hz]',
'max_frequency': 'Max frequency [Hz]',
'flat_noise_percent': 'Flat noise [%]',
'activity': 'Activity'},
default_optional_column_mapping=default_optional_column_mapping)
# Validate mapping
if not any([v in column_mapping for v in ['center_frequency', 'angular_frequency', 'rpm']]):
if defaults_used:
expected_columns = [
default_optional_column_mapping[v] for v in ['center_frequency', 'angular_frequency', 'rpm']]
raise KeyError(
f"ERROR: Missing input for either the {', '.join(expected_columns)}. Please provide file with "
f"the missing inputs.")
raise KeyError(
"ERROR: Missing input for either the 'center_frequency', 'angular_frequency' or 'rpm'. Please provide "
" file with the missing inputs.")
if sum(1 for v in ['center_frequency', 'angular_frequency', 'rpm'] if v in column_mapping) != 1:
raise ValueError(
"ERROR: Please select one from 'center_frequency', 'angular_frequency' or 'rpm' to be provided in the "
"file.")
# Loop through rows with equipment data
equipments_from_file = []
for i, row in df.iterrows():
# Check optional inputs
if 'calculate' in column_mapping:
if not row[column_mapping['calculate']]:
continue
# Check for activity filter
if activity_filter and row[column_mapping['activity']].lower() not in activity_filter:
continue
# Check the name
equipment_name = row[column_mapping['name']].replace('/', '_').replace('\\', '_').replace('\n', '_')
# Check the ID as unique identifier for the Target
equipment_identifier = row[column_mapping['id']]
if equipment_identifier in [e.id for e in project.equipment_list]:
raise ValueError(
f"ERROR: Please provide unique ID for the equipment. Duplicate ID: {equipment_identifier}.")
# Add the optional inputs
kwargs = {
f'_{k}': row[column_mapping[k]] for k in ['center_frequency', 'angular_frequency', 'rpm']
if k in column_mapping}
# Check the vibration source level unit
if row[column_mapping['unit']] == 'm/s':
vibration_source_level = v_to_db(velocity=row[column_mapping['vibration_source_level']])
elif row[column_mapping['unit']] in ['dB', 'decibel']:
vibration_source_level = row[column_mapping['vibration_source_level']]
else:
raise NotImplementedError(
f"ERROR: The unit for the vibration source level '{row[column_mapping['unit']]}' is not supported. "
f"Please select from 'dB' or 'm/s'.")
# Create the instance of the equipment, validation executed in class
equipment = Equipment(
name=equipment_name,
id=equipment_identifier,
coordinates=(row[column_mapping['x']], row[column_mapping['y']], row[column_mapping['z']]),
vibration_source_level=vibration_source_level,
min_frequency=row[column_mapping['min_frequency']],
max_frequency=row[column_mapping['max_frequency']],
flat_noise_percent=row[column_mapping['flat_noise_percent']],
activity=row[column_mapping['activity']].lower(),
**kwargs)
project.equipment_list.append(equipment)
equipments_from_file.append(equipment)
return equipments_from_file
### ===================================================================================================================
### 3. Function to create the frequency data for all equipment
### ===================================================================================================================
[docs]
def create_frequency_domain_data_from_equipments(project, sampling_rate: float) -> List[FrequencyDomainData]:
"""
Function to create frequency domain data for all the equipments defined in the project. The frequency domain data
contains the combination of the harmonic component and the white noise component. The white noise component is not
included in the part where the harmonic component is defined.
Input:
- project (obj): Project object containing collections of objects and project variables.
- sampling_rate (float): Frequency sampling rate, in [Hz].
Output:
- The harmonic, white noise and combined frequency domain data are created for all the equipments and added to
the respective lists in project, 'fdd_harmonic_collection', 'fdd_noise_collection' and
'fdd_sources_collection'.
- Returns list of the combined frequency domain data.
"""
if not project.equipment_list:
raise RuntimeError("ERROR: No vibration equipments available. Please create the equipments first.")
return \
[eq.create_frequency_domain_data(project=project, sampling_rate=sampling_rate) for eq in project.equipment_list]
### ===================================================================================================================
### 4. End of script
### ===================================================================================================================