101 lines
3.4 KiB
Python
101 lines
3.4 KiB
Python
|
|
"""
|
||
|
|
Extract natural frequencies from modal analysis
|
||
|
|
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
||
|
|
|
||
|
|
Pattern: eigenvalue_extraction
|
||
|
|
Element Type: General
|
||
|
|
Result Type: eigenvalues
|
||
|
|
API: model.eigenvalues[subcase]
|
||
|
|
"""
|
||
|
|
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Dict, Any
|
||
|
|
import numpy as np
|
||
|
|
from pyNastran.op2.op2 import OP2
|
||
|
|
|
||
|
|
|
||
|
|
def extract_frequency(op2_file: Path, subcase: int = 1, mode_number: int = 1):
|
||
|
|
"""
|
||
|
|
Extract natural frequency results from modal analysis OP2 file.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
op2_file: Path to OP2 file
|
||
|
|
subcase: Subcase ID for modal analysis (default: 1)
|
||
|
|
mode_number: Which mode to extract (1-indexed, default: 1 for fundamental frequency)
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dictionary containing frequency data
|
||
|
|
"""
|
||
|
|
from pyNastran.op2.op2 import OP2
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
model = OP2()
|
||
|
|
model.read_op2(str(op2_file))
|
||
|
|
|
||
|
|
# Access eigenvalues from modal analysis
|
||
|
|
# NX Nastran often uses empty string '' as subcase key
|
||
|
|
available_subcases = list(model.eigenvalues.keys())
|
||
|
|
if not available_subcases:
|
||
|
|
raise ValueError(f"No eigenvalue data found in OP2 file: {op2_file}")
|
||
|
|
|
||
|
|
# Use the first available subcase (often '' for NX Nastran)
|
||
|
|
actual_subcase = available_subcases[0]
|
||
|
|
eigenvalues = model.eigenvalues[actual_subcase]
|
||
|
|
|
||
|
|
# Extract frequency data for the specified mode
|
||
|
|
# RealEigenvalues object has different attributes than expected
|
||
|
|
# Try to access frequencies - could be in different formats
|
||
|
|
|
||
|
|
# Mode number is 1-indexed, array is 0-indexed
|
||
|
|
mode_idx = mode_number - 1
|
||
|
|
|
||
|
|
# Try different attribute names for frequencies
|
||
|
|
if hasattr(eigenvalues, 'cycles'):
|
||
|
|
frequencies = eigenvalues.cycles
|
||
|
|
elif hasattr(eigenvalues, 'frequencies'):
|
||
|
|
frequencies = eigenvalues.frequencies
|
||
|
|
else:
|
||
|
|
# Try to get eigenvalues and convert to frequency
|
||
|
|
if hasattr(eigenvalues, 'eigenvalues'):
|
||
|
|
# eigenvalue (lambda) = (2*pi*f)^2, so f = sqrt(lambda) / (2*pi)
|
||
|
|
eigenvals = eigenvalues.eigenvalues
|
||
|
|
frequencies = np.sqrt(np.abs(eigenvals)) / (2.0 * np.pi)
|
||
|
|
else:
|
||
|
|
raise ValueError(f"Cannot find frequency data in eigenvalues object. Available attributes: {dir(eigenvalues)}")
|
||
|
|
|
||
|
|
if mode_idx >= len(frequencies):
|
||
|
|
raise ValueError(f"Mode {mode_number} not found. Only {len(frequencies)} modes available.")
|
||
|
|
|
||
|
|
frequency = frequencies[mode_idx] # Hz
|
||
|
|
|
||
|
|
# Try to get eigenvalue if available
|
||
|
|
if hasattr(eigenvalues, 'eigenvalues'):
|
||
|
|
eigenvalue = eigenvalues.eigenvalues[mode_idx]
|
||
|
|
elif hasattr(eigenvalues, 'eigrs'):
|
||
|
|
eigenvalue = eigenvalues.eigrs[mode_idx]
|
||
|
|
else:
|
||
|
|
eigenvalue = (2.0 * np.pi * frequency) ** 2 # Convert back from frequency
|
||
|
|
|
||
|
|
# Also return all frequencies for reference
|
||
|
|
all_frequencies = list(frequencies)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'frequency': float(frequency),
|
||
|
|
'mode_number': int(mode_number),
|
||
|
|
'eigenvalue': float(eigenvalue),
|
||
|
|
'all_frequencies': all_frequencies,
|
||
|
|
'n_modes': len(all_frequencies)
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
# Example usage
|
||
|
|
import sys
|
||
|
|
if len(sys.argv) > 1:
|
||
|
|
op2_file = Path(sys.argv[1])
|
||
|
|
mode_num = int(sys.argv[2]) if len(sys.argv) > 2 else 1
|
||
|
|
result = extract_frequency(op2_file, mode_number=mode_num)
|
||
|
|
print(f"Extraction result: {result}")
|
||
|
|
else:
|
||
|
|
print(f"Usage: python {sys.argv[0]} <op2_file> [mode_number]")
|