Files
Atomizer/atomizer-field/UNIT_CONVERSION_REPORT.md
Antoine d5ffba099e feat: Merge Atomizer-Field neural network module into main repository
Permanently integrates the Atomizer-Field GNN surrogate system:
- neural_models/: Graph Neural Network for FEA field prediction
- batch_parser.py: Parse training data from FEA exports
- train.py: Neural network training pipeline
- predict.py: Inference engine for fast predictions

This enables 600x-2200x speedup over traditional FEA by replacing
expensive simulations with millisecond neural network predictions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 15:31:33 -05:00

300 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Unit Conversion Issue - Analysis and Fix
**Date:** November 24, 2025
**Issue:** Stresses displaying 1000× too large
---
## Root Cause Identified
### BDF File Unit System
The BDF file contains: **`PARAM UNITSYS MN-MM`**
This defines the Nastran unit system as:
- **Length:** mm (millimeter)
- **Force:** MN (MegaNewton) = 1,000,000 N
- **Mass:** tonne (1000 kg)
- **Stress:** Pa (Pascal) = N/m² *[NOT MPa!]*
- **Energy:** MN-mm = 1,000 N-m = 1 kJ
### Material Properties Confirm This
Young's modulus from BDF: **E = 200,000,000**
- If units were MPa: E = 200 GPa (way too high for steel ~200 GPa)
- If units are Pa: E = 200 MPa (way too low!)
- **Actual: E = 200,000,000 Pa = 200 GPa** ✓ (correct for steel)
### What pyNastran Returns
pyNastran reads the OP2 file and returns data **in the same units as the BDF**:
- Displacement: mm ✓
- Force/Reactions: **MN** (not N!)
- Stress: **Pa** (not MPa!)
---
## Current vs Actual Values
### Stress Values
| What we claimed | Actual value | Correct interpretation |
|----------------|--------------|------------------------|
| 117,000 MPa | 117,000 Pa | **117 kPa = 0.117 MPa** ✓ |
| 46,000 MPa (mean) | 46,000 Pa | **46 kPa = 0.046 MPa** ✓ |
**Correct stress values are 1000× smaller!**
### Force Values
| What we claimed | Actual value | Correct interpretation |
|----------------|--------------|------------------------|
| 2.73 MN (applied) | 2.73 MN | **2.73 MN = 2,730,000 N** ✓ |
| 150 MN (reaction) | 150 MN | **150 MN = 150,000,000 N** ✓ |
**Force values are correctly stored, but labeled as N instead of MN**
---
## Impact
### What's Wrong:
1. **Stress units incorrectly labeled as "MPa"** - should be "Pa"
2. **Force/reaction units incorrectly labeled as "N"** - should be "MN"
3. **Visualization shows stress 1000× too high**
4. **Reports show unrealistic values** (117 GPa stress would destroy steel!)
### What's Correct:
1. ✅ Displacement values (19.5 mm)
2. ✅ Material properties (E = 200 GPa)
3. ✅ Geometry (mm)
4. ✅ Actual numerical values from pyNastran
---
## Solution
### Option 1: Convert to Standard Units (Recommended)
Convert all data to consistent engineering units:
- Length: mm → mm ✓
- Force: MN → **N** (divide by 1e6)
- Stress: Pa → **MPa** (divide by 1e6)
- Mass: tonne → kg (multiply by 1000)
**Benefits:**
- Standard engineering units (mm, N, MPa, kg)
- Matches what users expect
- No confusion in reports/visualization
**Changes Required:**
- Parser: Convert forces (divide by 1e6)
- Parser: Convert stress (divide by 1e6)
- Update metadata to reflect actual units
### Option 2: Use Native Units (Not Recommended)
Keep MN-MM-tonne-Pa system throughout
**Issues:**
- Non-standard units confuse users
- Harder to interpret values
- Requires careful labeling everywhere
---
## Implementation Plan
### 1. Fix Parser ([neural_field_parser.py](neural_field_parser.py))
**Lines to modify:**
#### Stress Extraction (~line 602-648)
```python
# CURRENT (wrong):
stress_data = stress.data[0, :, :]
stress_results[f"{elem_type}_stress"] = {
"data": stress_data.tolist(),
"units": "MPa" # WRONG!
}
# FIX:
stress_data = stress.data[0, :, :] / 1e6 # Convert Pa → MPa
stress_results[f"{elem_type}_stress"] = {
"data": stress_data.tolist(),
"units": "MPa" # Now correct!
}
```
#### Force Extraction (~line 464-507)
```python
# CURRENT (partially wrong):
"magnitude": float(load.mag), # This is in MN, not N!
# FIX:
"magnitude": float(load.mag) * 1e6, # Convert MN → N
```
#### Reaction Forces (~line 538-568)
```python
# CURRENT (wrong):
reactions = grid_point_force.data[0] # In MN!
# FIX:
reactions = grid_point_force.data[0] * 1e6 # Convert MN → N
```
### 2. Update Unit Detection
Add UNITSYS parameter detection:
```python
def detect_units(self):
"""Detect Nastran unit system from PARAM cards"""
if hasattr(self.bdf, 'params') and 'UNITSYS' in self.bdf.params:
unitsys = str(self.bdf.params['UNITSYS'].values[0])
if 'MN' in unitsys:
return {
'length': 'mm',
'force': 'MN',
'stress': 'Pa',
'mass': 'tonne',
'needs_conversion': True
}
# Default units
return {
'length': 'mm',
'force': 'N',
'stress': 'MPa',
'mass': 'kg',
'needs_conversion': False
}
```
### 3. Add Unit Conversion Function
```python
def convert_to_standard_units(self, data, unit_system):
"""Convert from Nastran units to standard engineering units"""
if not unit_system['needs_conversion']:
return data
# Convert forces: MN → N (multiply by 1e6)
if 'loads' in data:
for force in data['loads']['point_forces']:
force['magnitude'] *= 1e6
# Convert stress: Pa → MPa (divide by 1e6)
if 'results' in data and 'stress' in data['results']:
for stress_type, stress_data in data['results']['stress'].items():
if isinstance(stress_data, dict) and 'data' in stress_data:
stress_data['data'] = np.array(stress_data['data']) / 1e6
stress_data['units'] = 'MPa'
# Convert reactions: MN → N (multiply by 1e6)
# (Handle in HDF5 write)
return data
```
### 4. Update HDF5 Writing
Apply conversions when writing to HDF5:
```python
# Reactions
if 'reactions' in self.neural_field_data['results']:
reactions_data = np.array(self.neural_field_data['results']['reactions']['data'])
if unit_system['force'] == 'MN':
reactions_data *= 1e6 # MN → N
hf.create_dataset('results/reactions', data=reactions_data)
```
---
## Testing Plan
### 1. Create Unit Conversion Test
```python
def test_unit_conversion():
"""Verify units are correctly converted"""
parser = NastranToNeuralFieldParser('test_case_beam')
data = parser.parse_all()
# Check stress units
stress = data['results']['stress']['cquad4_stress']
assert stress['units'] == 'MPa'
max_stress = np.max(stress['data'][:, -1]) # Von Mises
assert max_stress < 500, f"Stress {max_stress} MPa too high!"
# Check force units
force = data['loads']['point_forces'][0]
assert force['magnitude'] < 1e7, "Force should be in N"
print("[OK] Units correctly converted")
```
### 2. Expected Values After Fix
| Property | Before (wrong) | After (correct) |
|----------|---------------|-----------------|
| Max stress | 117,000 MPa | **117 MPa** ✓ |
| Mean stress | 46,000 MPa | **46 MPa** ✓ |
| Applied force | 2.73 MN | **2,730,000 N** |
| Max reaction | 150 MN | **150,000,000 N** |
### 3. Validation Checks
- ✓ Stress < 500 MPa (reasonable for steel)
- ✓ Force magnitude matches applied loads
- ✓ Material E = 200 GPa (correct for steel)
- ✓ Displacement still 19.5 mm
---
## Risk Assessment
### Low Risk:
- ✅ Only affects numerical scaling
- ✅ No changes to data structure
- ✅ Easy to verify with test
- ✅ Can be fixed with multiplication/division
### What Could Go Wrong:
- ⚠ Other BDF files might use different UNITSYS
- ⚠ Some files might already be in correct units
- ⚠ Need to handle multiple unit systems
### Mitigation:
- Always check PARAM UNITSYS first
- Add unit system detection
- Log conversions clearly
- Add validation checks
---
## Recommendations
### Immediate Actions:
1.**Update parser to detect UNITSYS**
2.**Add unit conversion for stress (Pa → MPa)**
3.**Add unit conversion for forces (MN → N)**
4.**Update metadata to reflect conversions**
5.**Add validation checks**
### Long-term:
- Support multiple Nastran unit systems
- Add unit conversion utilities
- Document unit assumptions clearly
- Add warnings for unusual values
---
## Conclusion
**The system is working correctly** - pyNastran is reading the data accurately.
**The issue is labeling** - we incorrectly assumed MPa when Nastran uses Pa.
**The fix is simple** - divide stress by 1e6, multiply forces by 1e6, update labels.
**After fix:**
- Stress: 117 MPa (reasonable for steel) ✓
- Force: 2.73 MN = 2,730 kN (reasonable for large beam) ✓
- All other values unchanged ✓
**System will be production-ready after this fix!** 🚀
---
*Unit Conversion Analysis v1.0*
*Issue: 1000× stress error*
*Root cause: MN-MM unit system misinterpretation*
*Fix: Scale factors + label corrections*