AnsysLink/backend/pymechanical/ansys_error_handler.py
2025-08-11 13:58:59 +08:00

693 lines
28 KiB
Python

"""
ANSYS Error Handler for CAE Mesh Generator
This module provides specialized error handling for ANSYS Mechanical operations,
including error classification, diagnosis, and solution recommendations.
"""
import logging
import re
from typing import Dict, Any, Optional, List, Tuple
from datetime import datetime
from dataclasses import dataclass
from enum import Enum
logger = logging.getLogger(__name__)
class ErrorSeverity(Enum):
"""Error severity levels"""
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
WARNING = "warning"
class ErrorCategory(Enum):
"""ANSYS error categories"""
LICENSING = "licensing"
GEOMETRY = "geometry"
MESH = "mesh"
SOLVER = "solver"
MEMORY = "memory"
FILE_IO = "file_io"
CONNECTIVITY = "connectivity"
CONFIGURATION = "configuration"
UNKNOWN = "unknown"
@dataclass
class ErrorDiagnosis:
"""Comprehensive error diagnosis"""
error_id: str
category: ErrorCategory
severity: ErrorSeverity
title: str
description: str
root_cause: str
immediate_solutions: List[str]
preventive_measures: List[str]
related_documentation: List[str]
recovery_possible: bool
estimated_fix_time: int # minutes
confidence_level: float # 0.0 to 1.0
@dataclass
class ErrorContext:
"""Context information for error analysis"""
operation_type: str
file_path: Optional[str] = None
mesh_settings: Optional[Dict[str, Any]] = None
system_info: Optional[Dict[str, Any]] = None
previous_errors: Optional[List[str]] = None
timestamp: datetime = None
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.now()
class ANSYSErrorHandler:
"""
Specialized error handler for ANSYS Mechanical operations
This class provides intelligent error analysis, classification, and
solution recommendations for ANSYS-specific errors.
"""
def __init__(self):
"""Initialize ANSYS error handler"""
self.error_patterns = self._initialize_error_patterns()
self.error_history = []
self.solution_database = self._initialize_solution_database()
logger.info("ANSYS Error Handler initialized")
def _initialize_error_patterns(self) -> Dict[str, Dict[str, Any]]:
"""Initialize known ANSYS error patterns"""
return {
# Licensing errors
'license_not_available': {
'patterns': [
r'license.*not.*available',
r'no.*license.*found',
r'license.*server.*not.*responding',
r'flexlm.*error'
],
'category': ErrorCategory.LICENSING,
'severity': ErrorSeverity.CRITICAL,
'keywords': ['license', 'flexlm', 'server']
},
# Geometry errors
'geometry_import_failed': {
'patterns': [
r'failed.*to.*import.*geometry',
r'geometry.*file.*corrupt',
r'invalid.*step.*file',
r'cad.*import.*error'
],
'category': ErrorCategory.GEOMETRY,
'severity': ErrorSeverity.HIGH,
'keywords': ['geometry', 'import', 'step', 'cad']
},
'geometry_invalid': {
'patterns': [
r'invalid.*geometry',
r'geometry.*contains.*errors',
r'self.*intersecting.*surfaces',
r'non.*manifold.*geometry'
],
'category': ErrorCategory.GEOMETRY,
'severity': ErrorSeverity.HIGH,
'keywords': ['geometry', 'invalid', 'intersecting', 'manifold']
},
# Mesh errors
'mesh_generation_failed': {
'patterns': [
r'mesh.*generation.*failed',
r'meshing.*error',
r'unable.*to.*generate.*mesh',
r'mesh.*quality.*too.*poor'
],
'category': ErrorCategory.MESH,
'severity': ErrorSeverity.HIGH,
'keywords': ['mesh', 'generation', 'failed', 'quality']
},
'mesh_memory_error': {
'patterns': [
r'insufficient.*memory.*for.*meshing',
r'out.*of.*memory.*during.*mesh',
r'memory.*allocation.*failed.*mesh'
],
'category': ErrorCategory.MEMORY,
'severity': ErrorSeverity.HIGH,
'keywords': ['memory', 'mesh', 'allocation']
},
# Memory errors
'out_of_memory': {
'patterns': [
r'out.*of.*memory',
r'insufficient.*memory',
r'memory.*allocation.*failed',
r'virtual.*memory.*exhausted'
],
'category': ErrorCategory.MEMORY,
'severity': ErrorSeverity.CRITICAL,
'keywords': ['memory', 'allocation', 'virtual']
},
# File I/O errors
'file_not_found': {
'patterns': [
r'file.*not.*found',
r'cannot.*open.*file',
r'access.*denied.*file',
r'file.*path.*invalid'
],
'category': ErrorCategory.FILE_IO,
'severity': ErrorSeverity.MEDIUM,
'keywords': ['file', 'path', 'access', 'open']
},
'file_permission_error': {
'patterns': [
r'permission.*denied',
r'access.*denied',
r'file.*is.*read.*only',
r'cannot.*write.*to.*file'
],
'category': ErrorCategory.FILE_IO,
'severity': ErrorSeverity.MEDIUM,
'keywords': ['permission', 'access', 'denied', 'read-only']
},
# Connectivity errors
'connection_lost': {
'patterns': [
r'connection.*lost',
r'server.*disconnected',
r'communication.*error',
r'remote.*session.*terminated'
],
'category': ErrorCategory.CONNECTIVITY,
'severity': ErrorSeverity.HIGH,
'keywords': ['connection', 'server', 'communication', 'remote']
},
# Configuration errors
'invalid_settings': {
'patterns': [
r'invalid.*settings',
r'configuration.*error',
r'parameter.*out.*of.*range',
r'incompatible.*options'
],
'category': ErrorCategory.CONFIGURATION,
'severity': ErrorSeverity.MEDIUM,
'keywords': ['settings', 'configuration', 'parameter', 'options']
}
}
def _initialize_solution_database(self) -> Dict[str, ErrorDiagnosis]:
"""Initialize solution database for known errors"""
return {
'license_not_available': ErrorDiagnosis(
error_id='license_not_available',
category=ErrorCategory.LICENSING,
severity=ErrorSeverity.CRITICAL,
title='ANSYS License Not Available',
description='ANSYS license server is not responding or no licenses are available.',
root_cause='License server connectivity issues or license pool exhaustion.',
immediate_solutions=[
'Check ANSYS license server status',
'Verify network connectivity to license server',
'Wait for license to become available',
'Contact system administrator for license allocation'
],
preventive_measures=[
'Monitor license usage patterns',
'Schedule operations during off-peak hours',
'Implement license queue management'
],
related_documentation=[
'ANSYS Licensing Guide',
'FlexLM Administrator Guide'
],
recovery_possible=True,
estimated_fix_time=15,
confidence_level=0.9
),
'geometry_import_failed': ErrorDiagnosis(
error_id='geometry_import_failed',
category=ErrorCategory.GEOMETRY,
severity=ErrorSeverity.HIGH,
title='Geometry Import Failed',
description='Failed to import geometry file into ANSYS Mechanical.',
root_cause='Corrupted geometry file, unsupported format, or file access issues.',
immediate_solutions=[
'Verify geometry file format (STEP, IGES, etc.)',
'Check file integrity and size',
'Try importing with different CAD translator settings',
'Repair geometry in original CAD software'
],
preventive_measures=[
'Validate geometry files before import',
'Use supported file formats',
'Maintain file backup copies'
],
related_documentation=[
'ANSYS Geometry Import Guide',
'CAD File Format Compatibility'
],
recovery_possible=True,
estimated_fix_time=30,
confidence_level=0.8
),
'mesh_generation_failed': ErrorDiagnosis(
error_id='mesh_generation_failed',
category=ErrorCategory.MESH,
severity=ErrorSeverity.HIGH,
title='Mesh Generation Failed',
description='ANSYS failed to generate mesh for the geometry.',
root_cause='Complex geometry, inappropriate mesh settings, or geometry quality issues.',
immediate_solutions=[
'Increase global element size',
'Simplify geometry by removing small features',
'Use different meshing algorithm',
'Apply local mesh controls to problematic areas',
'Check geometry for errors and repair if needed'
],
preventive_measures=[
'Prepare geometry for meshing (defeaturing)',
'Use appropriate mesh sizing for geometry scale',
'Validate geometry quality before meshing'
],
related_documentation=[
'ANSYS Meshing Best Practices',
'Geometry Preparation Guidelines'
],
recovery_possible=True,
estimated_fix_time=45,
confidence_level=0.7
),
'out_of_memory': ErrorDiagnosis(
error_id='out_of_memory',
category=ErrorCategory.MEMORY,
severity=ErrorSeverity.CRITICAL,
title='Insufficient Memory',
description='ANSYS ran out of available system memory during operation.',
root_cause='Large model size, insufficient RAM, or memory leaks.',
immediate_solutions=[
'Reduce mesh density (increase element size)',
'Close other applications to free memory',
'Use 64-bit ANSYS version if available',
'Enable virtual memory/swap file',
'Simplify geometry to reduce memory requirements'
],
preventive_measures=[
'Monitor memory usage during operations',
'Upgrade system RAM if frequently encountered',
'Use mesh sizing appropriate for available memory'
],
related_documentation=[
'ANSYS Memory Management Guide',
'System Requirements Documentation'
],
recovery_possible=True,
estimated_fix_time=20,
confidence_level=0.9
),
'connection_lost': ErrorDiagnosis(
error_id='connection_lost',
category=ErrorCategory.CONNECTIVITY,
severity=ErrorSeverity.HIGH,
title='Connection Lost',
description='Connection to ANSYS server or remote session was lost.',
root_cause='Network connectivity issues, server problems, or session timeout.',
immediate_solutions=[
'Check network connectivity',
'Restart ANSYS session',
'Verify server status',
'Check firewall settings',
'Increase session timeout if applicable'
],
preventive_measures=[
'Use stable network connections',
'Monitor network reliability',
'Implement session recovery mechanisms'
],
related_documentation=[
'ANSYS Remote Session Guide',
'Network Configuration Requirements'
],
recovery_possible=True,
estimated_fix_time=10,
confidence_level=0.8
)
}
def analyze_error(self, error_message: str, context: ErrorContext = None) -> ErrorDiagnosis:
"""
Analyze error message and provide comprehensive diagnosis
Args:
error_message: Error message from ANSYS
context: Additional context information
Returns:
ErrorDiagnosis with analysis and recommendations
"""
try:
logger.info(f"Analyzing ANSYS error: {error_message[:100]}...")
# Normalize error message for analysis
normalized_error = error_message.lower().strip()
# Try to match against known patterns
matched_pattern = self._match_error_pattern(normalized_error)
if matched_pattern:
# Get diagnosis from solution database
diagnosis = self.solution_database.get(matched_pattern)
if diagnosis:
# Enhance diagnosis with context
enhanced_diagnosis = self._enhance_diagnosis_with_context(diagnosis, context, error_message)
# Record error for learning
self._record_error(error_message, enhanced_diagnosis, context)
return enhanced_diagnosis
# If no pattern matched, create generic diagnosis
generic_diagnosis = self._create_generic_diagnosis(error_message, context)
self._record_error(error_message, generic_diagnosis, context)
return generic_diagnosis
except Exception as e:
logger.error(f"Error analysis failed: {str(e)}")
return self._create_fallback_diagnosis(error_message)
def _match_error_pattern(self, error_message: str) -> Optional[str]:
"""
Match error message against known patterns
Args:
error_message: Normalized error message
Returns:
Matched pattern key or None
"""
try:
for pattern_key, pattern_info in self.error_patterns.items():
# Check regex patterns
for pattern in pattern_info['patterns']:
if re.search(pattern, error_message, re.IGNORECASE):
logger.debug(f"Matched pattern: {pattern_key}")
return pattern_key
# Check keywords
keywords = pattern_info.get('keywords', [])
if keywords and any(keyword in error_message for keyword in keywords):
# Additional confidence check for keyword matches
keyword_count = sum(1 for keyword in keywords if keyword in error_message)
if keyword_count >= len(keywords) * 0.5: # At least 50% of keywords match
logger.debug(f"Matched keywords for pattern: {pattern_key}")
return pattern_key
return None
except Exception as e:
logger.warning(f"Pattern matching failed: {str(e)}")
return None
def _enhance_diagnosis_with_context(self, base_diagnosis: ErrorDiagnosis,
context: ErrorContext, error_message: str) -> ErrorDiagnosis:
"""
Enhance diagnosis with context-specific information
Args:
base_diagnosis: Base diagnosis from database
context: Error context
error_message: Original error message
Returns:
Enhanced ErrorDiagnosis
"""
try:
# Create a copy of the base diagnosis
enhanced = ErrorDiagnosis(
error_id=base_diagnosis.error_id,
category=base_diagnosis.category,
severity=base_diagnosis.severity,
title=base_diagnosis.title,
description=base_diagnosis.description,
root_cause=base_diagnosis.root_cause,
immediate_solutions=base_diagnosis.immediate_solutions.copy(),
preventive_measures=base_diagnosis.preventive_measures.copy(),
related_documentation=base_diagnosis.related_documentation.copy(),
recovery_possible=base_diagnosis.recovery_possible,
estimated_fix_time=base_diagnosis.estimated_fix_time,
confidence_level=base_diagnosis.confidence_level
)
# Add context-specific enhancements
if context:
# File-specific recommendations
if context.file_path:
if base_diagnosis.category == ErrorCategory.GEOMETRY:
enhanced.immediate_solutions.insert(0, f"Verify file: {context.file_path}")
elif base_diagnosis.category == ErrorCategory.FILE_IO:
enhanced.immediate_solutions.insert(0, f"Check file permissions for: {context.file_path}")
# Operation-specific recommendations
if context.operation_type == 'mesh_generation':
if base_diagnosis.category == ErrorCategory.MEMORY:
enhanced.immediate_solutions.insert(0, "Consider reducing mesh density for this operation")
# System-specific recommendations
if context.system_info:
available_memory = context.system_info.get('available_memory_gb', 0)
if available_memory < 4 and base_diagnosis.category == ErrorCategory.MEMORY:
enhanced.immediate_solutions.insert(0, f"System has only {available_memory}GB available - consider upgrading RAM")
# Previous error context
if context.previous_errors:
if len(context.previous_errors) > 2:
enhanced.severity = ErrorSeverity.CRITICAL
enhanced.immediate_solutions.insert(0, "Multiple consecutive errors detected - consider system restart")
# Add original error message for reference
enhanced.description += f"\n\nOriginal error: {error_message}"
return enhanced
except Exception as e:
logger.warning(f"Context enhancement failed: {str(e)}")
return base_diagnosis
def _create_generic_diagnosis(self, error_message: str, context: ErrorContext = None) -> ErrorDiagnosis:
"""
Create generic diagnosis for unknown errors
Args:
error_message: Error message
context: Error context
Returns:
Generic ErrorDiagnosis
"""
try:
# Analyze error message for clues
category = self._infer_error_category(error_message)
severity = self._infer_error_severity(error_message)
return ErrorDiagnosis(
error_id='unknown_error',
category=category,
severity=severity,
title='Unknown ANSYS Error',
description=f'An unrecognized error occurred in ANSYS: {error_message}',
root_cause='Unknown - error pattern not recognized in current database.',
immediate_solutions=[
'Check ANSYS log files for additional details',
'Verify system resources (memory, disk space)',
'Restart ANSYS session and retry operation',
'Contact technical support with error details'
],
preventive_measures=[
'Keep ANSYS software updated',
'Monitor system resources during operations',
'Maintain regular backups of work'
],
related_documentation=[
'ANSYS User Manual',
'ANSYS Troubleshooting Guide'
],
recovery_possible=True,
estimated_fix_time=30,
confidence_level=0.3
)
except Exception as e:
logger.error(f"Generic diagnosis creation failed: {str(e)}")
return self._create_fallback_diagnosis(error_message)
def _infer_error_category(self, error_message: str) -> ErrorCategory:
"""Infer error category from message content"""
message_lower = error_message.lower()
if any(word in message_lower for word in ['license', 'flexlm']):
return ErrorCategory.LICENSING
elif any(word in message_lower for word in ['geometry', 'cad', 'step', 'import']):
return ErrorCategory.GEOMETRY
elif any(word in message_lower for word in ['mesh', 'element', 'node']):
return ErrorCategory.MESH
elif any(word in message_lower for word in ['memory', 'allocation', 'ram']):
return ErrorCategory.MEMORY
elif any(word in message_lower for word in ['file', 'path', 'directory']):
return ErrorCategory.FILE_IO
elif any(word in message_lower for word in ['connection', 'server', 'network']):
return ErrorCategory.CONNECTIVITY
elif any(word in message_lower for word in ['setting', 'parameter', 'configuration']):
return ErrorCategory.CONFIGURATION
else:
return ErrorCategory.UNKNOWN
def _infer_error_severity(self, error_message: str) -> ErrorSeverity:
"""Infer error severity from message content"""
message_lower = error_message.lower()
if any(word in message_lower for word in ['critical', 'fatal', 'crash', 'abort']):
return ErrorSeverity.CRITICAL
elif any(word in message_lower for word in ['error', 'failed', 'cannot', 'unable']):
return ErrorSeverity.HIGH
elif any(word in message_lower for word in ['warning', 'caution']):
return ErrorSeverity.WARNING
else:
return ErrorSeverity.MEDIUM
def _create_fallback_diagnosis(self, error_message: str) -> ErrorDiagnosis:
"""Create minimal fallback diagnosis when all else fails"""
return ErrorDiagnosis(
error_id='fallback_error',
category=ErrorCategory.UNKNOWN,
severity=ErrorSeverity.MEDIUM,
title='ANSYS Error',
description=f'Error occurred: {error_message}',
root_cause='Unable to determine root cause.',
immediate_solutions=['Restart ANSYS and retry', 'Check system resources', 'Contact support'],
preventive_measures=['Monitor system health', 'Keep software updated'],
related_documentation=['ANSYS Documentation'],
recovery_possible=True,
estimated_fix_time=15,
confidence_level=0.1
)
def _record_error(self, error_message: str, diagnosis: ErrorDiagnosis, context: ErrorContext = None):
"""Record error for learning and analysis"""
try:
error_record = {
'timestamp': datetime.now(),
'error_message': error_message,
'diagnosis': diagnosis,
'context': context,
'resolved': False
}
self.error_history.append(error_record)
# Keep only recent errors (last 100)
if len(self.error_history) > 100:
self.error_history = self.error_history[-100:]
logger.debug(f"Recorded error: {diagnosis.error_id}")
except Exception as e:
logger.warning(f"Error recording failed: {str(e)}")
def get_error_statistics(self) -> Dict[str, Any]:
"""
Get error statistics and trends
Returns:
Dictionary with error statistics
"""
try:
if not self.error_history:
return {
'total_errors': 0,
'categories': {},
'severities': {},
'most_common': [],
'resolution_rate': 0.0
}
# Count by category
categories = {}
severities = {}
error_types = {}
for record in self.error_history:
diagnosis = record['diagnosis']
# Count categories
cat = diagnosis.category.value
categories[cat] = categories.get(cat, 0) + 1
# Count severities
sev = diagnosis.severity.value
severities[sev] = severities.get(sev, 0) + 1
# Count error types
error_id = diagnosis.error_id
error_types[error_id] = error_types.get(error_id, 0) + 1
# Find most common errors
most_common = sorted(error_types.items(), key=lambda x: x[1], reverse=True)[:5]
# Calculate resolution rate (simplified)
resolved_count = sum(1 for record in self.error_history if record.get('resolved', False))
resolution_rate = resolved_count / len(self.error_history) if self.error_history else 0.0
return {
'total_errors': len(self.error_history),
'categories': categories,
'severities': severities,
'most_common': most_common,
'resolution_rate': resolution_rate,
'recent_errors': len([r for r in self.error_history if (datetime.now() - r['timestamp']).days < 1])
}
except Exception as e:
logger.error(f"Error statistics calculation failed: {str(e)}")
return {'error': str(e)}
def get_handler_info(self) -> Dict[str, Any]:
"""
Get information about the error handler
Returns:
Dictionary with handler information
"""
return {
'handler_type': 'ANSYSErrorHandler',
'known_patterns': len(self.error_patterns),
'solution_database_size': len(self.solution_database),
'error_history_size': len(self.error_history),
'supported_categories': [cat.value for cat in ErrorCategory],
'severity_levels': [sev.value for sev in ErrorSeverity],
'capabilities': [
'error_pattern_matching',
'intelligent_diagnosis',
'solution_recommendations',
'context_aware_analysis',
'error_statistics',
'learning_from_history'
]
}