Files
Atomizer/optimization_engine/validators/simulation_validator.py

215 lines
7.3 KiB
Python
Raw Normal View History

fix: Remove arbitrary aspect ratio validation and add comprehensive pruning diagnostics **Validation Changes (simulation_validator.py)**: - Removed arbitrary aspect ratio limits (5.0-50.0) for circular_plate model - User requirement: validation rules must be proposed, not automatic - Validator now returns empty rules for circular_plate - Relies solely on Optuna parameter bounds (user-defined feasibility) - Fixed Unicode encoding issues in pruning_logger.py **Root Cause Analysis**: - 18-20% pruning in Protocol 10 tests was NOT validation failures - All pruned trials had valid aspect ratios within bounds - Root cause: pyNastran FATAL flag false positives - Simulations succeeded but pyNastran rejected OP2 files **New Modules**: - pruning_logger.py: Comprehensive trial failure tracking - Logs validation, simulation, and OP2 extraction failures - Analyzes F06 files to detect false positives - Generates pruning_history.json and pruning_summary.json - op2_extractor.py: Robust multi-strategy OP2 extraction - Standard OP2 read - Lenient read (debug=False) - F06 fallback parsing - Handles pyNastran FATAL flag issues **Documentation**: - SESSION_SUMMARY_NOV20.md: Complete session documentation - FIX_VALIDATOR_PRUNING.md: Deprecated, retained for historical reference - PRUNING_DIAGNOSTICS.md: Usage guide for pruning diagnostics - STUDY_CONTINUATION_STANDARD.md: API documentation **Impact**: - Clean separation: parameter bounds = feasibility, validator = genuine failures - Expected pruning reduction from 18% to <2% with robust extraction - ~4-5 minutes saved per 50-trial study - All optimization trials contribute valid data **User Requirements Established**: 1. No arbitrary checks without user approval 2. Validation rules must be visible in optimization_config.json 3. Parameter bounds already define feasibility constraints 4. Physics-based constraints need clear justification
2025-11-20 20:25:33 -05:00
"""
Simulation Validator - Validates design parameters before running FEA simulations.
This module helps prevent simulation failures by:
1. Checking if geometry will be valid
2. Validating parameter combinations
3. Providing actionable error messages
4. Detecting likely failure modes
Usage:
validator = SimulationValidator(model_type='circular_plate')
is_valid, warnings = validator.validate(design_variables)
if not is_valid:
print(f"Invalid parameters: {warnings}")
"""
from typing import Dict, Tuple, List
from pathlib import Path
class SimulationValidator:
"""Validates design parameters before running simulations."""
def __init__(self, model_type: str = 'generic', verbose: bool = True):
"""
Initialize validator for specific model type.
Args:
model_type: Type of FEA model ('circular_plate', 'beam', etc.)
verbose: Print validation warnings
"""
self.model_type = model_type
self.verbose = verbose
# Model-specific validation rules
self.validation_rules = self._get_validation_rules(model_type)
def _get_validation_rules(self, model_type: str) -> Dict:
"""Get validation rules for specific model type."""
if model_type == 'circular_plate':
# NOTE: Only use parameter bounds for validation
# No arbitrary aspect ratio checks - let Optuna explore the full parameter space
# Modal analysis is robust and doesn't need strict aspect ratio limits
return {}
# Generic rules for unknown models
return {}
def validate(
self,
design_variables: Dict[str, float],
strict: bool = False
) -> Tuple[bool, List[str]]:
"""
Validate design variables before simulation.
Args:
design_variables: Dict of parameter names to values
strict: If True, reject on soft limit violations (warnings)
Returns:
(is_valid, warnings_list)
- is_valid: True if parameters are acceptable
- warnings_list: List of warning/error messages
"""
warnings = []
is_valid = True
# Check each parameter
for param_name, value in design_variables.items():
if param_name not in self.validation_rules:
continue # No rules for this parameter
rules = self.validation_rules[param_name]
# Hard limits (always reject)
if value < rules.get('min', float('-inf')):
is_valid = False
warnings.append(
f"INVALID: {param_name}={value:.2f} < min={rules['min']:.2f}. "
f"{rules.get('reason', '')}"
)
if value > rules.get('max', float('inf')):
is_valid = False
warnings.append(
f"INVALID: {param_name}={value:.2f} > max={rules['max']:.2f}. "
f"{rules.get('reason', '')}"
)
# Soft limits (warnings, may cause issues)
if 'soft_min' in rules and value < rules['soft_min']:
msg = (
f"WARNING: {param_name}={value:.2f} < recommended={rules['soft_min']:.2f}. "
f"{rules.get('reason', 'May cause simulation issues')}"
)
warnings.append(msg)
if strict:
is_valid = False
if 'soft_max' in rules and value > rules['soft_max']:
msg = (
f"WARNING: {param_name}={value:.2f} > recommended={rules['soft_max']:.2f}. "
f"{rules.get('reason', 'May cause simulation issues')}"
)
warnings.append(msg)
if strict:
is_valid = False
# Model-specific combined checks can be added here if needed
# For now, rely only on parameter bounds (no arbitrary physics checks)
# Print warnings if verbose
if self.verbose and warnings:
print(f"\n[VALIDATOR] Validation results:")
for warning in warnings:
print(f" {warning}")
return is_valid, warnings
def _validate_circular_plate_aspect_ratio(
self,
design_variables: Dict[str, float]
) -> tuple[bool, List[str]]:
"""Check circular plate aspect ratio (diameter/thickness).
Returns:
(is_valid, warnings): Tuple of validation status and warning messages
"""
warnings = []
is_valid = True
diameter = design_variables.get('inner_diameter')
thickness = design_variables.get('plate_thickness')
if diameter and thickness:
aspect_ratio = diameter / thickness
rules = self.validation_rules.get('aspect_ratio', {})
min_aspect = rules.get('min', 0)
max_aspect = rules.get('max', float('inf'))
if aspect_ratio > max_aspect:
is_valid = False # FIX: Make this a hard rejection
warnings.append(
f"INVALID: Aspect ratio {aspect_ratio:.1f} > {max_aspect:.1f}. "
f"Very thin plate will cause numerical instability."
)
elif aspect_ratio < min_aspect:
is_valid = False # FIX: Make this a hard rejection
warnings.append(
f"INVALID: Aspect ratio {aspect_ratio:.1f} < {min_aspect:.1f}. "
f"Very thick plate will have poor mesh quality."
)
return is_valid, warnings
def suggest_corrections(
self,
design_variables: Dict[str, float]
) -> Dict[str, float]:
"""
Suggest corrected parameters that are more likely to succeed.
Args:
design_variables: Original parameters
Returns:
Corrected parameters (clamped to safe ranges)
"""
corrected = design_variables.copy()
for param_name, value in design_variables.items():
if param_name not in self.validation_rules:
continue
rules = self.validation_rules[param_name]
# Clamp to soft limits (safer range)
soft_min = rules.get('soft_min', rules.get('min', float('-inf')))
soft_max = rules.get('soft_max', rules.get('max', float('inf')))
if value < soft_min:
corrected[param_name] = soft_min
if self.verbose:
print(f"[VALIDATOR] Corrected {param_name}: {value:.2f} -> {soft_min:.2f}")
if value > soft_max:
corrected[param_name] = soft_max
if self.verbose:
print(f"[VALIDATOR] Corrected {param_name}: {value:.2f} -> {soft_max:.2f}")
return corrected
def validate_before_simulation(
design_variables: Dict[str, float],
model_type: str = 'circular_plate',
strict: bool = False
) -> Tuple[bool, List[str]]:
"""
Convenience function for quick validation.
Args:
design_variables: Parameters to validate
model_type: Type of FEA model
strict: Reject on warnings (not just errors)
Returns:
(is_valid, warnings)
"""
validator = SimulationValidator(model_type=model_type, verbose=False)
return validator.validate(design_variables, strict=strict)