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>
This commit is contained in:
100
optimization_engine/extractors/extract_frequency.py
Normal file
100
optimization_engine/extractors/extract_frequency.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
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]")
|
||||
Reference in New Issue
Block a user