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

8.2 KiB
Raw Blame History

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

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

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)

Lines to modify:

Stress Extraction (~line 602-648)

# 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)

# 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)

# 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:

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

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:

# 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

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