Files
Atomizer/optimization_engine/extractors/extract_frequency.py
Anto01 2b3573ec42 feat: Add AtomizerField training data export and intelligent model discovery
Major additions:
- Training data export system for AtomizerField neural network training
- Bracket stiffness optimization study with 50+ training samples
- Intelligent NX model discovery (auto-detect solutions, expressions, mesh)
- Result extractors module for displacement, stress, frequency, mass
- User-generated NX journals for advanced workflows
- Archive structure for legacy scripts and test outputs
- Protocol documentation and dashboard launcher

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 12:01:50 -05:00

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]")