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