Source code for haskoning_atr_tools.vibration_contour_plot.mesh.mesh_node

### ===================================================================================================================
###  Class definition for MeshNode for the vibration simulation
### ===================================================================================================================
# Copyright ©2026 Haskoning Nederland B.V.

### ===================================================================================================================
###  1. Import modules
### ===================================================================================================================

# General imports
import numpy as np
from dataclasses import dataclass
from typing import List, Dict

# References for functions and classes in the haskoning_atr_tools package
from haskoning_atr_tools.vibration_contour_plot.helper_functions import fem_compare_coordinates, v_to_db, db_to_v
from haskoning_atr_tools.signal_processing import FrequencyDomainData, TimeDomainData


### ===================================================================================================================
###  2. MeshNode class
### ===================================================================================================================

[docs] @dataclass class MeshNode: def __init__(self, id: int, coordinates: List[float]): self.id = id self.coordinates = coordinates self.x = self.coordinates[0] self.y = self.coordinates[1] self.z = self.coordinates[2] if self.coordinates[2] else 0 self.frequency_domain_data = None self.time_domain_data = None self._vibration_level: Dict = {} self._vibration_level_unit: str = 'velocity' def __eq__(self, other, precision: int = 6): """Overrides the default implementation""" if isinstance(other, MeshNode): return self.id == other.id and fem_compare_coordinates(self.coordinates, other.coordinates) return False @property def project(self): return self.__project @project.setter def project(self, new_project): self.__project = new_project @property def id(self): return self.__id @id.setter def id(self, new_id: int): if new_id and not isinstance(new_id, int): raise ValueError( f"ERROR: The mesh_element requires an integer for id input argument, provided: {new_id}.") elif new_id and new_id < 1: raise ValueError( f"ERROR: The meshnode requires a positive integer, minimum 1 for id input argument, provided: " f"{new_id}.") self.__id = new_id @property def coordinates(self): return self.__coordinates @coordinates.setter def coordinates(self, new_coordinates): if not isinstance(new_coordinates, list): raise TypeError( f"ERROR: Meshnode input for coordinates of {self.__class__.__name__} must be a list.") for item in new_coordinates: if not isinstance(item, (float, int)): raise TypeError( f"ERROR: Meshnode input for coordinates of {self.__class__.__name__} must be a list of coordinates " f"as floats or integers, representing the coordinates in x-, y- and z-direction in [m].") if len(new_coordinates) == 2: new_coordinates.append(0) if len(new_coordinates) != 3: raise ValueError( f"ERROR: Meshnode input for coordinates of {self.__class__.__name__} '{new_coordinates}' consists of " f"{len(new_coordinates)} coordinates, 3 should be provided (x, y and z coordinate in [m]).") self.__coordinates = new_coordinates @property def frequency_domain_data(self): return self.__frequency_domain_data @frequency_domain_data.setter def frequency_domain_data(self, new_frequency_domain_data: FrequencyDomainData): if new_frequency_domain_data and not isinstance(new_frequency_domain_data, FrequencyDomainData): raise ValueError( f"ERROR: The provided frequency domain data is not a {type(FrequencyDomainData)}.") self.__frequency_domain_data = new_frequency_domain_data @property def time_domain_data(self): return self.__time_domain_data @time_domain_data.setter def time_domain_data(self, new_time_domain_data: TimeDomainData): if new_time_domain_data and not isinstance(new_time_domain_data, TimeDomainData): raise ValueError( f"ERROR: The provided frequency domain data is not a {type(TimeDomainData)}.") self.__time_domain_data = new_time_domain_data @property def vibration_level(self): return self._vibration_level @property def vibration_level_unit(self): return self._vibration_level_unit @vibration_level_unit.setter def vibration_level_unit(self, new_unit: str): if not isinstance(new_unit, str): raise TypeError("vibration_level_unit must be a string.") self._vibration_level_unit = new_unit def calculate_total_level_per_frequency(self, amplitude_type="velocity"): # Square Root of Sum of Squares should be done based on n velocity # 1. Initialize a dictionary to hold the SUM OF SQUARES for each frequency sum_of_squares_per_frequency = dict() for source_name, source_level_dict in self._vibration_level.items(): for freq, level in source_level_dict.items(): if self.vibration_level_unit == "decibel": level = db_to_v(level) # 2. Square the level before adding it to the sum level_squared = level ** 2 if freq not in sum_of_squares_per_frequency: sum_of_squares_per_frequency[freq] = level_squared else: # Add the squared level to the running sum for this frequency sum_of_squares_per_frequency[freq] += level_squared # 3. Calculate the final total level by taking the square root of the sums total_level_per_frequency = dict() for freq, sum_sq in sum_of_squares_per_frequency.items(): # Use math.sqrt() for the square root calculation total_level_per_frequency[freq] = np.sqrt(sum_sq) if amplitude_type == "decibel": total_level_per_frequency[freq] = v_to_db(total_level_per_frequency[freq]) return total_level_per_frequency
### =================================================================================================================== ### 3. End of script ### ===================================================================================================================