sandialabs / sdynpy

A Structural Dynamics Python Library
Other
22 stars 0 forks source link

sample time signal contents #11

Open Mahyarasadi opened 1 year ago

Mahyarasadi commented 1 year ago

Hi, I would like to fit acceleration time signals to an FRF object. Looking in the example files, a file has been mentioned which is not in the standard package filename = r'rattlesnake_modal_data.nc4' Is there any source or other example with the files to learn how to format my time signal measurements?

dprohe commented 1 year ago

Apologies, I must have missed this issue popping up, so I'm sorry if this is too late to help.

This is relatively simple in SDynPy. You should create a TimeHistoryArray object for your signals from which you can compute frequency response functions relatively easily. I assume you start from a NumPy array where you have acceleration and force signals? You can see on the Showcase documentation page how you can construct these functions. Also in that same page, you can see how you can compute FRFs from time histories.

I'll show a more complete example here. I will use the demo package to generate some signals that will create meaningful FRFs, strip them down into just NumPy arrays, which I assume is your starting point, then re-assemble them back into SDynPy objects and compute FRFs. It might look like a lot of code, but the initial portion is just to generate some time data to play with.

Here is a snippet of code to generate acceleration and force signals from a plate object that SDynPy includes in its demo package, and then reduce down to just the NumPy arrays that you have.

#%% Import SDynPy, Numpy, and Matplotlib
import sdynpy as sdpy
import numpy as np
import matplotlib.pyplot as plt

#%% Pull Geometry and System from the Demo package
from sdynpy.demo.beam_plate import geometry,system

#%% Select degrees of freedom
response_dofs = sdpy.coordinate_array(
    node = geometry.node.id,
    direction = 3)

excitation_dofs = sdpy.coordinate_array(
    string_array = ['41Z+','23Z+','1Z+'])

geometry.plot_coordinate(response_dofs,label_dofs=True)
geometry.plot_coordinate(excitation_dofs, label_dofs = True)

#%% Perform a test

# Reduce to modal system of equations for faster integration
modes = system.eigensolution(num_modes = 50)
# Add damping
modes.damping = 0.01

# Create a modal system from the modes
modal_system = modes.system()

# Simulate a modal test using burst random excitation
responses, references = modal_system.simulate_test(
    bandwidth = 1000,
    frame_length = 4000,
    num_averages = 5,
    excitation = 'burst random',
    references = excitation_dofs,
    responses = response_dofs,
    signal_fraction = 0.5
    )

#%% Extract just the ordinates as numpy arrays and get the time step as well
dt = np.mean(np.diff(responses.abscissa))
responses = responses.ordinate
references = references.ordinate

At this point, I have my accelerations stored in the variables responses, my forces stored in the variable references, and a time step dt. All are NumPy arrays, except for dt which is a scalar. I believe this is the point that you are starting from. My responses have shape of (45, 20000) (45 channels, 20,000 time steps), and my responses have shape of (3, 20000) (3 shakers, 20,000 time steps).

The first thing we will do is to construct a TimeHistoryArray from these data. While we could do this using the TimeHistoryArray constructor, there is a helper function data_array that will make this easier, similarly to how you can construct a Numpy np.ndarray using its constructor, but it's easier to use the helper function np.array.

Looking at the data_array function, we can see that we need to specify a FunctionTypes, then the abscissa (independent variable, in this case the time steps), the ordinate (the acceleration or force data), coordinate information (i.e. the node and direction associated with each signal, see coordinate_array), and then any comments we would like to include (optional). This will look something like this:

# Set up coordinate informations
response_nodes = np.arange(45)+1 # gives the numbers 1-45
reference_nodes = [41, 23, 1] # Nodes at which my shakers are oriented
direction = 'Z+' # Both my shakers and accelerometers were pointed in the Z direction
# Construct CoordinateArray objects that the TimeHistoryArray is looking for
response_coordinates = sdpy.coordinate_array(response_nodes,direction)
reference_coordinates = sdpy.coordinate_array(reference_nodes,direction)

# Create the TimeHistoryArrays
responses_sd = sdpy.data_array(
    sdpy.data.FunctionTypes.TIME_RESPONSE, # The function type
    np.arange(responses.shape[1])*dt, # The abscissa, which will be broadcast out to the size of the ordinate
    responses, # The ordinate, which are my accelerations
    response_coordinates[:, np.newaxis] # response coordinates, which needs to be nchannels x 1, hence the np.newaxis
    )
references_sd = sdpy.data_array(
    sdpy.data.FunctionTypes.TIME_RESPONSE, # The function type
    np.arange(references.shape[1])*dt, # The abscissa, which will be broadcast out to the size of the ordinate
    references, # The ordinate, which are my forces
    reference_coordinates [:, np.newaxis] # reference coordinates, which needs to be nchannels x 1, hence the np.newaxis
    )

At this point you should be able to easily plot the responses by using their plot method.

responses_sd.plot()

image

We can now compute FRFs from the time histories using the TransferFunctionArray.from_time_data static method. Look at the documentation for the calling signature. We will need to specify the reference data, response data, and samples per measurement frame. There are other options if you wanted to apply a window function or use overlap, but those are not applicable for a burst random signal.

frfs = sdpy.TransferFunctionArray.from_time_data(references_sd,responses_sd,
   samples_per_average = 4000)

Because there are a lot of FRFs (45x3), it is useful to use SDynPy's GUIPlot to quickly pop through all of them to make sure they look good. This allows you to graphically select which function to look at. Also note that the coordinate information has been propagated from the time histories to the FRFs, which makes bookkeeping easier for you.

sdpy.GUIPlot(frfs)

image

Hope this helps!