Modal Analysis¶
This guide covers modal analysis capabilities in pydvma.
Overview¶
Modal analysis identifies the natural frequencies, damping ratios, and mode shapes of structures. pydvma provides tools for:
- Natural frequency identification
- Damping estimation from decay measurements
- Single-degree-of-freedom (SDOF) fitting
- Mode shape visualization
Damping from Free Decay (Sonogram Method)¶
Overview¶
The sonogram-based damping estimation analyzes free decay responses to extract modal parameters. This is particularly useful for impact tests or other transient responses.
Basic Usage¶
import pydvma as dvma
# Load or record time data (e.g., from impact test)
time_data = dataset.time_data_list[0]
# Calculate damping from sonogram
fn, Qn, fit_data = dvma.calculate_damping_from_sono(
time_data,
n_chan=0, # Channel to analyze
nperseg=512, # FFT segment length
start_time=None # Auto-detect start, or specify time
)
# Results
print(f"Natural frequencies: {fn} Hz")
print(f"Q factors: {Qn}")
print(f"Damping ratios: {1/(2*Qn)}")
Understanding the Results¶
The function returns three values:
- fn: Natural frequencies in Hz
- Qn: Quality factors (Q = 1/(2ζ) where ζ is damping ratio)
- fit_data: Dictionary containing fit visualization data
# Calculate damping ratio from Q factor
zeta = 1 / (2 * Qn)
# Calculate damped natural frequency
fn_damped = fn * np.sqrt(1 - zeta**2)
Visualization¶
The fit data can be visualized to assess fit quality:
import matplotlib.pyplot as plt
# fit_data contains:
# - 't': time axis
# - 'fits': list of fit dictionaries
for fit in fit_data['fits']:
plt.figure()
plt.plot(fit['t_fit'], fit['real_data'], 'x',
label='Data')
plt.plot(fit['t_fit'], fit['real_fit'], '-',
label=f"Fit: {fit['f_peak']:.1f} Hz, Q={fit['Qn']:.0f}")
plt.xlabel('Time (s)')
plt.ylabel('Log amplitude')
plt.legend()
plt.title('Damping Fit')
plt.show()
Method Details¶
The algorithm:
- Computes a sonogram (short-time Fourier transform)
- Identifies frequency peaks in the initial spectrum
- Tracks the decay of each peak over time
- Fits an exponential decay model to extract damping
- Returns natural frequencies and damping ratios
Tips for Good Results¶
Segment Length Selection
# Longer segments = better frequency resolution
nperseg = 1024 # Good for closely spaced modes
# Shorter segments = better time resolution
nperseg = 256 # Good for rapidly decaying signals
Data Quality - Ensure good signal-to-noise ratio - Use appropriate sensor range to avoid clipping - Record sufficient decay duration (several periods) - Minimize background noise
Start Time
# Auto-detect (default)
fn, Qn, fit_data = dvma.calculate_damping_from_sono(time_data, n_chan=0)
# Specify start time manually
fn, Qn, fit_data = dvma.calculate_damping_from_sono(
time_data,
n_chan=0,
start_time=0.01 # Start analysis at 0.01 seconds
)
SDOF Modal Fitting¶
Single Channel Modal Fitting¶
Fit modal parameters from FRF data for a single channel:
# Calculate transfer function
tf_data = dvma.calculate_tf(time_data, ch_in=0)
# Select frequency range around a mode
freq_range = [180, 220] # Hz
# Perform single-channel modal fit (returns scipy.optimize.OptimizeResult)
result = dvma.modal_fit_single_channel(
tf_data,
freq_range=freq_range,
channel=0,
measurement_type='acc' # 'acc', 'vel', or 'dsp'
)
# Access fitted parameters
fn, zeta, modal_constant = result.x[0], result.x[1], result.x[2]
print(f"Natural frequency: {fn:.2f} Hz")
print(f"Damping ratio: {zeta:.4f}")
print(f"Modal constant: {modal_constant}")
The optimized parameter vector is ordered as [fn, zeta, an, phase, Rk, Rm].
Multi-Channel Modal Fitting¶
For fitting a single mode across all channels from a list of transfer functions:
# Fit a single mode for all channels (returns single ModalData object)
modal_data = dvma.modal_fit_all_channels(
tf_data_list,
freq_range=[180, 220],
measurement_type='acc' # 'acc', 'vel', or 'dsp'
)
# Review results (fn and zn are arrays, one element per fitted mode)
print(f"Natural frequency: {modal_data.fn[0]:.2f} Hz")
print(f"Damping ratio: {modal_data.zn[0]:.4f}")
print(f"Modal constants: {modal_data.an}")
Beyond SDOF: not yet built in¶
Mode-shape extraction, the Modal Assurance Criterion (MAC), and
Operating Deflection Shape (ODS) plotting are common next steps after
SDOF fitting. pydvma doesn't ship those as helpers yet — they're on
the roadmap (see TODO.md: "mode-shape plotter", "MAC helper", "ODS
helper"). Starter recipes that operate on TfData / ModalData
output live in dev/mode-shape-sketches.md; they're suitable as a
basis when these are built up into proper APIs.
Experimental Modal Analysis Workflow¶
Complete Example¶
import pydvma as dvma
import numpy as np
import matplotlib.pyplot as plt
# 1. Setup
settings = dvma.MySettings()
settings.fs = 10000
settings.stored_time = 1.0
settings.pretrig_samples = 1000
settings.channels = 2 # Force and response
# 2. Acquire data (multiple impacts)
time_data_list = dvma.TimeDataList()
n_averages = 5
for i in range(n_averages):
input("Press Enter for next impact...")
data = dvma.log_data(settings, test_name=f"impact_{i}")
time_data_list.append(data.time_data_list[0])
# 3. Calculate averaged FRF
tf_data = dvma.calculate_tf_averaged(time_data_list, ch_in=0)
# 4. Plot FRF and coherence
f = tf_data.freq_axis
H = tf_data.tf_data[:, 0]
coh = tf_data.tf_coherence[:, 0]
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
ax1.semilogy(f, np.abs(H))
ax1.set_ylabel('|H(f)| (m/s²/N)')
ax1.set_title('Frequency Response Function')
ax1.grid(True)
ax1.set_xlim([0, 500])
ax2.plot(f, coh)
ax2.set_xlabel('Frequency (Hz)')
ax2.set_ylabel('Coherence')
ax2.set_ylim([0, 1])
ax2.grid(True)
ax2.set_xlim([0, 500])
plt.tight_layout()
plt.show()
# 5. Identify modes
from scipy.signal import find_peaks
# Find peaks in FRF
magnitude = np.abs(H)
peaks, properties = find_peaks(magnitude, height=np.max(magnitude)*0.1)
natural_frequencies = f[peaks]
print("Identified natural frequencies:")
for fn in natural_frequencies:
print(f" {fn:.2f} Hz")
# 6. Extract damping (from time data)
fn, Qn, fit_data = dvma.calculate_damping_from_sono(
time_data_list[0],
n_chan=1,
nperseg=512
)
print("\nDamping analysis:")
for i, (freq, Q) in enumerate(zip(fn, Qn)):
zeta = 1/(2*Q)
print(f" Mode {i+1}: f={freq:.2f} Hz, ζ={zeta:.4f}, Q={Q:.1f}")
References and Further Reading¶
- Ewins, D.J. (2000). Modal Testing: Theory, Practice and Application
- Maia, N.M.M. & Silva, J.M.M. (1997). Theoretical and Experimental Modal Analysis
- Inman, D.J. (2013). Engineering Vibration
Next Steps¶
- Learn about Plotting and Visualization
- See complete Examples
- Check API Reference