Source code for haskoning_atr_tools.signal_processing_tool.frequency_domain.create

### ===================================================================================================================
###   Create Frequency Domain Data
### ===================================================================================================================
# Copyright ©2025 Haskoning Nederland B.V.

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

# General imports
import numpy as np
from pathlib import Path
from typing import Union, List, Optional

# References for functions and classes in the haskoning_atr_tools package
from haskoning_atr_tools.signal_processing_tool.config import ATRConfigSignalProcessingTool as ATRConfig
from haskoning_atr_tools.signal_processing_tool.utils import read_columns_from_file
from haskoning_atr_tools.signal_processing_tool.frequency_domain.collection import FrequencyDomainCollection
from haskoning_atr_tools.signal_processing_tool.frequency_domain.data import FrequencyDomainData


### ===================================================================================================================
###   2. Functions to create spectra for frequency domain
### ===================================================================================================================

[docs] def create_frequency_domain_data( project, name: str, values: List[float], frequencies: List[float], amplitude_type: str, value_units: str, phase_angles: List[float] = None, spectrum_type: str = 'amplitude', frequency_units: str = 'Hz', phase_angle_units: str = 'degrees', signal_duration: float = None, window_used: str = None, collection_name: str = 'Unknown') -> FrequencyDomainData: """ Creates a new frequency domain data object and adds it to the specified collection in the project. Input: - project (obj): Project object containing collections of objects and project variables. - name (str): The name of the new frequency domain data. - values (list of floats): A list of values for the frequency domain data. These can be amplitudes, power density, or energy density. - frequencies (list of floats): A list of frequencies corresponding to the values. - amplitude_type (str): The type of the frequency domain data. For example: velocity, acceleration, or pressure. - value_units (str): The units of the values. - phase_angles (list of floats): Optional input for the phase angle values for the frequency domain data. Default value is None. - spectrum_type (str): The type of spectrum, select from 'amplitude', 'power', 'psd', 'energy', or 'esd'. Default value is 'amplitude'. - frequency_units (str): The units of the frequency values. Default value is 'Hz'. - phase_angle_units (str): The units of the phase angles. Default value 'degrees'. - signal_duration (float): Optional input for the duration of the signal, in [s]. Default value is None. - window_used (str): Optional input for the window used if used in conversion from time domain to frequency domain. Default value is None. - collection_name (str): The name of the collection to which this data will be added. Defaults to 'Unknown Collection'. Output: - Returns the newly created frequency domain data object based on the provided spectrum values. """ # Retrieve the collection from the project collection = project.frequency_domain_collections.get(collection_name) if not collection: # If the collection does not exist, create a new one collection = create_frequency_domain_collection( project=project, name=collection_name, frequency_domain_data_list=[]) project.frequency_domain_collections[collection_name] = collection # Initialise the FrequencyDomainData object with the appropriate attribute based on spectrum_type if spectrum_type in ['amplitude', 'amplitudes']: new_frequency_domain_data = FrequencyDomainData( amplitudes=values, amplitude_units=value_units, amplitude_type=amplitude_type, phase_angles=phase_angles, frequencies=frequencies, phase_angle_units=phase_angle_units, frequency_units=frequency_units, window_used=window_used, name=name, signal_duration=signal_duration, parent=collection) elif spectrum_type in ['power', 'psd']: new_frequency_domain_data = FrequencyDomainData( power_density=values, power_units=value_units, amplitude_type=amplitude_type, phase_angles=phase_angles, frequencies=frequencies, phase_angle_units=phase_angle_units, frequency_units=frequency_units, window_used=window_used, name=name, signal_duration=signal_duration, parent=collection) elif spectrum_type in ['energy', 'esd']: new_frequency_domain_data = FrequencyDomainData( energy_density=values, energy_units=value_units, amplitude_type=amplitude_type, phase_angles=phase_angles, frequencies=frequencies, phase_angle_units=phase_angle_units, frequency_units=frequency_units, window_used=window_used, name=name, signal_duration=signal_duration, parent=collection) else: raise ValueError("Invalid spectrum_type. Must be 'amplitude', 'power', 'psd', 'energy', or 'esd'.") return new_frequency_domain_data
[docs] def create_harmonic_spectrum_frequency_domain_data( project, name: str, amplitudes: Union[float, List[float]], frequencies: Union[float, List[float]], amplitude_units: str, phase_angles: Optional[Union[float, List[float]]] = None, lowest_frequency: float = 0.1, highest_frequency: float = 1000, sampling_rate: float = 0.1, amplitude_type: str = 'Harmonic Spectrum', frequency_units: str = 'Hz', phase_angle_units: str = 'degrees', signal_duration: float = None, window_used: str = None, collection_name: str = 'Unknown Collection') -> FrequencyDomainData: """ Creates a harmonic spectrum FrequencyDomainData object. Input: - project (obj): Project object containing collections of objects and project variables. - name (str): The name of the new frequency domain data. - amplitudes (float or list of floats): Amplitudes of the harmonics. Can be provided as a single float or a list of floats. - frequencies (float or list of floats): Frequencies of the harmonics. Can be provided as a single float or a list of floats. - amplitude_units (str): The units of the amplitudes. - phase_angles (float or list of floats): Optional input for the phase angle values for the harmonics. Can be a single float or a list of floats. Defaults to None. - lowest_frequency (float): Lowest frequency in the spectrum. Defaults to 0.1 Hz. - highest_frequency (float): Highest frequency in the spectrum. Defaults to 1000 Hz. - sampling_rate (float): Frequency sampling rate. Defaults to 0.1. - amplitude_type (str): The type of the frequency domain data. Default value is 'Harmonic Spectrum'. - spectrum_type (str): The type of spectrum, select from 'amplitude', 'power', 'psd', 'energy', or 'esd'. Default value is 'amplitude'. - frequency_units (str): The units of the frequency values. Default value is 'Hz'. - phase_angle_units (str): The units of the phase angles. Default value 'degrees'. - signal_duration (float): Optional input for the duration of the signal, in [s]. Default value is None. - window_used (str): Optional input for the window used if used in conversion from time domain to frequency domain. Default value is None. - collection_name (str): The name of the collection to which this data will be added. Defaults to 'Unknown Collection'. Output: - Returns the newly created frequency domain data object based on the harmonic spectrum. - FrequencyDomainData is added to the specified FrequencyDomainCollection in the project. """ # Ensure that the input parameters are lists if isinstance(amplitudes, float): amplitudes = [amplitudes] if isinstance(frequencies, float): frequencies = [frequencies] if phase_angles is None: phase_angles = np.zeros(len(amplitudes)).tolist() elif isinstance(phase_angles, float): phase_angles = [phase_angles] # Convert phase angles to radians phase_angles = [np.deg2rad(phase_angle) for phase_angle in phase_angles] # Set up frequency domain as amplitudes at specific frequencies num_points = int((highest_frequency - lowest_frequency) / sampling_rate) + 1 new_frequencies = np.linspace(lowest_frequency, highest_frequency, num_points) new_frequencies = np.round(new_frequencies, 4) # round frequencies to avoid low decimal points new_amplitudes = np.zeros(len(new_frequencies)) new_phase_angles = np.zeros(len(new_frequencies)) # Find the indices of frequencies matching or closest to harmonic_frequencies for amp, freq, phase in zip(amplitudes, frequencies, phase_angles): idx = (np.abs(new_frequencies - freq)).argmin() new_amplitudes[idx] = amp new_phase_angles[idx] = phase # Create the FrequencyDomainData object with the harmonic spectrum return create_frequency_domain_data( project=project, name=name, values=new_amplitudes.tolist(), frequencies=new_frequencies.tolist(), amplitude_type=amplitude_type, value_units=amplitude_units, phase_angles=new_phase_angles.tolist(), spectrum_type='amplitude', frequency_units=frequency_units, phase_angle_units=phase_angle_units, signal_duration=signal_duration, window_used=window_used, collection_name=collection_name)
[docs] def create_white_noise_spectrum_frequency_domain_data( project, name: str, amplitude_units: str, lowest_frequency: float = 0.1, highest_frequency: float = 1000, sampling_rate: float = 0.1, noise_amplitude: float = 1, amplitude_type: str = 'White Noise Spectrum', frequency_units: str = 'Hz', signal_duration: float = None, window_used: str = None, collection_name: str = 'Unknown Collection') -> FrequencyDomainData: """ Creates a white noise spectrum FrequencyDomainData object. Input: - project (obj): Project object containing collections of objects and project variables. - name (str): The name of the new frequency domain data. - amplitude_units (str): The units of the amplitudes. - lowest_frequency (float): Lowest frequency in the spectrum, in [Hz]. Defaults to 0.1 Hz. - highest_frequency (float): Highest frequency in the spectrum, in [Hz]. Defaults to 1000 Hz. - sampling_rate (float): Frequency sampling rate, in [Hz]. Defaults to 0.1 Hz. - noise_amplitude (float): Amplitude of the white noise. Defaults to 1. - amplitude_type (str): The type of the frequency domain data. Default value is 'White Noise Spectrum'. - frequency_units (str): The units of the frequency values. Default value is 'Hz'. - signal_duration (float): Optional input for the duration of the signal, in [s]. Default value is None. - window_used (str): Optional input for the window used if used in conversion from time domain to frequency domain. Default value is None. - collection_name (str): The name of the collection to which this data will be added. Defaults to 'Unknown Collection'. Output: - Returns the newly created frequency domain data object based on the white noise spectrum. - FrequencyDomainData is added to the specified FrequencyDomainCollection in the project. """ # Generate frequencies num_points = int((highest_frequency - lowest_frequency) / sampling_rate) + 1 frequencies = np.linspace(start=lowest_frequency, stop=highest_frequency, num=num_points) # Generate amplitudes and phase angles amplitudes = np.full(len(frequencies), noise_amplitude) phase_angles = np.zeros(len(frequencies)) # Create the FrequencyDomainData object with the white noise spectrum return create_frequency_domain_data( project=project, name=name, values=amplitudes.tolist(), frequencies=frequencies.tolist(), amplitude_type=amplitude_type, phase_angles=phase_angles.tolist(), value_units=amplitude_units, spectrum_type='amplitude', frequency_units=frequency_units, signal_duration=signal_duration, window_used=window_used, collection_name=collection_name)
[docs] def create_frequency_domain_data_from_file( project, file: Union[str, Path], frequency_column: Union[str, int], value_column: Union[str, int], value_units: str, name: Optional[str] = None, spectrum_type: str = 'amplitude', amplitude_type: str = 'Spectrum', frequency_units: str = 'Hz', skip_rows: int = 0, header: bool = True, signal_duration: Optional[float] = ATRConfig.DURATION, collection_name: str = 'Unknown Collection') \ -> FrequencyDomainData: """ Creates a FrequencyDomainData object from a file (CSV or Excel). Input: - project (obj): Project object containing collections of objects and project variables. - file (Path or str): The file path to the data file. - frequency_column (str or int): The name or index of the column containing frequency values. - value_column (str or int): The name or index of the column containing values for the spectrum. - value_units (str): The units of the values. - name (str): The name of the new frequency domain data. Default value is the name of the file. - spectrum_type (str): The type of spectrum, select from 'amplitude', 'power', 'psd', 'energy', or 'esd'. Default value is 'amplitude'. - amplitude_type (str): The type of the frequency domain data. Default value is 'Spectrum'. - frequency_units (str): The units of the frequency values. Default value is 'Hz'. - skip_rows (int): Optional input for the number of rows to skip at the beginning of the file. Defaults to 0. - header (bool): Select whether the file contains a header row. Defaults to True. - duration (float): Optional input for the duration of the signal, in [s]. Default value is 10 seconds. - collection_name (str): The name of the collection to which this data will be added. Defaults to 'Unknown Collection'. Output: - Returns the newly created frequency domain data object based on the imported spectrum. - FrequencyDomainData is added to the specified FrequencyDomainCollection in the project. """ # Check file if isinstance(file, str): file = Path(file) if not file.exists(): raise FileNotFoundError( f"ERROR: The signal can't be imported, because the file was not found: {file.as_posix()}. File running in " f"{Path.cwd().as_posix()}.") # Determine the name if name == 'use header' and header: try: # If value_column is a string, use it directly as the name if isinstance(value_column, str): name = value_column else: # Read the header row to find the name value_column_data = read_columns_from_file(file=file, skip_rows=skip_rows, columns=[value_column]) name = value_column_data[0][0] except Exception as e: raise Exception(f"ERROR: An error occurred while reading the file: {e}") elif not name: # Default to the file name if no name is provided name = file.stem # Adjust skip_rows if header is True if header: skip_rows += 1 # Read the data from the file frequencies, values = read_columns_from_file( file=file, skip_rows=skip_rows, columns=[frequency_column, value_column]) # Use create_frequency_domain_data to create the FrequencyDomainData object return create_frequency_domain_data( project=project, name=name, values=values, frequencies=frequencies, spectrum_type=spectrum_type, value_units=value_units, amplitude_type=amplitude_type, frequency_units=frequency_units, collection_name=collection_name, signal_duration=signal_duration)
### =================================================================================================================== ### 3. Function to create frequency domain collection ### ===================================================================================================================
[docs] def create_frequency_domain_collection( project, name: str, frequency_domain_data_list: List[FrequencyDomainData]) -> FrequencyDomainCollection: """ Creates a new frequency domain collection and adds it to the project. Input: - name (str): The name of the new frequency domain collection. - frequency_domain_data_list (list of obj): A list of frequency domain data objects to include in the collection. Output: - Returns the newly created frequency domain collection. - The collection is added to the frequency domain collections in project. """ collection = FrequencyDomainCollection(name=name, parent=project) for frequency_domain_data in frequency_domain_data_list: collection.add_frequency_domain_data_to_collection(frequency_domain_data) return project.add(collection)
### =================================================================================================================== ### 3. End of script ### ===================================================================================================================