Files
Atomizer/optimization_engine/config/builder.py
Anto01 eabcc4c3ca refactor: Major reorganization of optimization_engine module structure
BREAKING CHANGE: Module paths have been reorganized for better maintainability.
Backwards compatibility aliases with deprecation warnings are provided.

New Structure:
- core/           - Optimization runners (runner, intelligent_optimizer, etc.)
- processors/     - Data processing
  - surrogates/   - Neural network surrogates
- nx/             - NX/Nastran integration (solver, updater, session_manager)
- study/          - Study management (creator, wizard, state, reset)
- reporting/      - Reports and analysis (visualizer, report_generator)
- config/         - Configuration management (manager, builder)
- utils/          - Utilities (logger, auto_doc, etc.)
- future/         - Research/experimental code

Migration:
- ~200 import changes across 125 files
- All __init__.py files use lazy loading to avoid circular imports
- Backwards compatibility layer supports old import paths with warnings
- All existing functionality preserved

To migrate existing code:
  OLD: from optimization_engine.nx_solver import NXSolver
  NEW: from optimization_engine.nx.solver import NXSolver

  OLD: from optimization_engine.runner import OptimizationRunner
  NEW: from optimization_engine.core.runner import OptimizationRunner

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 12:30:59 -05:00

404 lines
14 KiB
Python

"""
Optimization Configuration Builder
Helps users build multi-objective optimization configurations by:
1. Discovering available design variables from FEA model
2. Listing available objectives and constraints
3. Creating structured optimization_config.json
Supports:
- Multi-objective optimization (minimize weight + stress simultaneously)
- Constraints (max displacement, stress limits, mass limits)
- User selection of which objectives/constraints to apply
"""
from pathlib import Path
from typing import Dict, Any, List
import json
class OptimizationConfigBuilder:
"""
Interactive builder for optimization configurations.
Workflow:
1. Discover model capabilities (design variables, analysis type)
2. Present available objectives/constraints to user
3. Build configuration based on user selections
"""
# Available objectives that can be extracted from OP2 files
AVAILABLE_OBJECTIVES = {
'minimize_mass': {
'description': 'Minimize total mass (weight reduction)',
'extractor': 'mass_extractor',
'metric': 'total_mass',
'units': 'kg',
'direction': 'minimize',
'typical_weight': 5.0 # Higher priority in multi-objective
},
'minimize_max_stress': {
'description': 'Minimize maximum von Mises stress',
'extractor': 'stress_extractor',
'metric': 'max_von_mises',
'units': 'MPa',
'direction': 'minimize',
'typical_weight': 10.0 # Very important - failure prevention
},
'minimize_max_displacement': {
'description': 'Minimize maximum displacement (increase stiffness)',
'extractor': 'displacement_extractor',
'metric': 'max_displacement',
'units': 'mm',
'direction': 'minimize',
'typical_weight': 3.0
},
'minimize_volume': {
'description': 'Minimize total volume (material usage)',
'extractor': 'volume_extractor',
'metric': 'total_volume',
'units': 'mm^3',
'direction': 'minimize',
'typical_weight': 4.0
}
}
# Available constraints
AVAILABLE_CONSTRAINTS = {
'max_stress_limit': {
'description': 'Maximum allowable von Mises stress',
'extractor': 'stress_extractor',
'metric': 'max_von_mises',
'units': 'MPa',
'typical_value': 200.0, # Below yield strength with safety factor
'constraint_type': 'upper_bound'
},
'max_displacement_limit': {
'description': 'Maximum allowable displacement',
'extractor': 'displacement_extractor',
'metric': 'max_displacement',
'units': 'mm',
'typical_value': 1.0, # Stiffness requirement
'constraint_type': 'upper_bound'
},
'min_mass_limit': {
'description': 'Minimum required mass (structural integrity)',
'extractor': 'mass_extractor',
'metric': 'total_mass',
'units': 'kg',
'typical_value': 0.3,
'constraint_type': 'lower_bound'
},
'max_mass_limit': {
'description': 'Maximum allowable mass (weight budget)',
'extractor': 'mass_extractor',
'metric': 'total_mass',
'units': 'kg',
'typical_value': 0.5,
'constraint_type': 'upper_bound'
}
}
def __init__(self, model_discovery_result: Dict[str, Any]):
"""
Initialize with model discovery results.
Args:
model_discovery_result: Output from discover_fea_model()
"""
self.model_info = model_discovery_result
self.config = {
'design_variables': [],
'objectives': [],
'constraints': [],
'optimization_settings': {
'n_trials': 100,
'sampler': 'TPE',
'n_startup_trials': 20
}
}
def list_available_design_variables(self) -> List[Dict[str, Any]]:
"""
List all available design variables from model.
Returns:
List of design variable options
"""
if 'expressions' not in self.model_info:
return []
design_vars = []
for expr in self.model_info['expressions']:
if expr['value'] is not None: # Only variables with known values
design_vars.append({
'name': expr['name'],
'current_value': expr['value'],
'units': expr['units'],
'type': expr.get('type', 'Unknown'),
'suggested_bounds': self._suggest_bounds(expr)
})
return design_vars
def _suggest_bounds(self, expr: Dict[str, Any]) -> tuple:
"""
Suggest reasonable optimization bounds for a design variable.
Args:
expr: Expression dictionary
Returns:
(lower_bound, upper_bound)
"""
value = expr['value']
expr_type = expr.get('type', '').lower()
if 'angle' in expr_type or 'degrees' in expr.get('units', '').lower():
# Angles: ±15 degrees
return (max(0, value - 15), min(180, value + 15))
elif 'thickness' in expr['name'].lower() or 'dimension' in expr_type:
# Dimensions: ±30%
return (value * 0.7, value * 1.3)
elif 'radius' in expr['name'].lower() or 'diameter' in expr['name'].lower():
# Radii/diameters: ±25%
return (value * 0.75, value * 1.25)
else:
# Default: ±20%
return (value * 0.8, value * 1.2)
def list_available_objectives(self) -> Dict[str, Dict[str, Any]]:
"""
List all available optimization objectives.
Returns:
Dictionary of objective options
"""
return self.AVAILABLE_OBJECTIVES.copy()
def list_available_constraints(self) -> Dict[str, Dict[str, Any]]:
"""
List all available constraints.
Returns:
Dictionary of constraint options
"""
return self.AVAILABLE_CONSTRAINTS.copy()
def add_design_variable(self, name: str, lower_bound: float, upper_bound: float):
"""
Add a design variable to the configuration.
Args:
name: Expression name from model
lower_bound: Minimum value
upper_bound: Maximum value
"""
# Verify variable exists in model
expr = next((e for e in self.model_info['expressions'] if e['name'] == name), None)
if not expr:
raise ValueError(f"Design variable '{name}' not found in model")
self.config['design_variables'].append({
'name': name,
'type': 'continuous',
'bounds': [lower_bound, upper_bound],
'units': expr.get('units', ''),
'initial_value': expr['value']
})
def add_objective(self, objective_key: str, weight: float = None, target: float = None):
"""
Add an objective to the configuration.
Args:
objective_key: Key from AVAILABLE_OBJECTIVES
weight: Importance weight (for multi-objective)
target: Target value (optional, for goal programming)
"""
if objective_key not in self.AVAILABLE_OBJECTIVES:
raise ValueError(f"Unknown objective: {objective_key}")
obj_info = self.AVAILABLE_OBJECTIVES[objective_key]
objective = {
'name': objective_key,
'description': obj_info['description'],
'extractor': obj_info['extractor'],
'metric': obj_info['metric'],
'direction': obj_info['direction'],
'weight': weight or obj_info['typical_weight']
}
if target is not None:
objective['target'] = target
self.config['objectives'].append(objective)
def add_constraint(self, constraint_key: str, limit_value: float):
"""
Add a constraint to the configuration.
Args:
constraint_key: Key from AVAILABLE_CONSTRAINTS
limit_value: Constraint limit value
"""
if constraint_key not in self.AVAILABLE_CONSTRAINTS:
raise ValueError(f"Unknown constraint: {constraint_key}")
const_info = self.AVAILABLE_CONSTRAINTS[constraint_key]
constraint = {
'name': constraint_key,
'description': const_info['description'],
'extractor': const_info['extractor'],
'metric': const_info['metric'],
'type': const_info['constraint_type'],
'limit': limit_value,
'units': const_info['units']
}
self.config['constraints'].append(constraint)
def set_optimization_settings(self, n_trials: int = None, sampler: str = None):
"""
Configure optimization algorithm settings.
Args:
n_trials: Number of optimization iterations
sampler: 'TPE', 'CMAES', 'GP', etc.
"""
if n_trials:
self.config['optimization_settings']['n_trials'] = n_trials
if sampler:
self.config['optimization_settings']['sampler'] = sampler
def build(self) -> Dict[str, Any]:
"""
Build and validate the configuration.
Returns:
Complete optimization configuration
"""
# Validation
if not self.config['design_variables']:
raise ValueError("At least one design variable is required")
if not self.config['objectives']:
raise ValueError("At least one objective is required")
# Add metadata
self.config['model_info'] = {
'sim_file': self.model_info.get('sim_file', ''),
'solutions': self.model_info.get('solutions', [])
}
return self.config
def save(self, output_path: Path):
"""
Save configuration to JSON file.
Args:
output_path: Path to save configuration
"""
config = self.build()
with open(output_path, 'w') as f:
json.dump(config, f, indent=2)
print(f"Configuration saved to: {output_path}")
def print_summary(self):
"""Print a human-readable summary of the configuration."""
print("\n" + "="*60)
print("OPTIMIZATION CONFIGURATION SUMMARY")
print("="*60)
print(f"\nModel: {self.model_info.get('sim_file', 'Unknown')}")
print(f"\nDesign Variables ({len(self.config['design_variables'])}):")
for dv in self.config['design_variables']:
print(f"{dv['name']}: [{dv['bounds'][0]:.2f}, {dv['bounds'][1]:.2f}] {dv['units']}")
print(f"\nObjectives ({len(self.config['objectives'])}):")
for obj in self.config['objectives']:
print(f"{obj['description']} (weight: {obj['weight']:.1f})")
print(f"\nConstraints ({len(self.config['constraints'])}):")
for const in self.config['constraints']:
operator = '<=' if const['type'] == 'upper_bound' else '>='
print(f"{const['description']}: {const['metric']} {operator} {const['limit']} {const['units']}")
print(f"\nOptimization Settings:")
print(f" • Trials: {self.config['optimization_settings']['n_trials']}")
print(f" • Sampler: {self.config['optimization_settings']['sampler']}")
print("="*60 + "\n")
# Example usage
if __name__ == "__main__":
from optimization_engine.model_discovery import discover_fea_model
# Step 1: Discover model
print("Step 1: Discovering FEA model...")
model_result = discover_fea_model("tests/Bracket_sim1.sim")
# Step 2: Create builder
builder = OptimizationConfigBuilder(model_result)
# Step 3: Show available options
print("\n" + "="*60)
print("AVAILABLE DESIGN VARIABLES:")
print("="*60)
for dv in builder.list_available_design_variables():
print(f"\n{dv['name']}")
print(f" Current value: {dv['current_value']} {dv['units']}")
print(f" Suggested bounds: {dv['suggested_bounds']}")
print("\n" + "="*60)
print("AVAILABLE OBJECTIVES:")
print("="*60)
for key, obj in builder.list_available_objectives().items():
print(f"\n{key}")
print(f" Description: {obj['description']}")
print(f" Default weight: {obj['typical_weight']}")
print("\n" + "="*60)
print("AVAILABLE CONSTRAINTS:")
print("="*60)
for key, const in builder.list_available_constraints().items():
print(f"\n{key}")
print(f" Description: {const['description']}")
print(f" Typical value: {const['typical_value']} {const['units']}")
# Step 4: Build a multi-objective configuration
print("\n" + "="*60)
print("BUILDING CONFIGURATION:")
print("="*60)
# Add design variables
builder.add_design_variable('tip_thickness', 15.0, 25.0)
builder.add_design_variable('support_angle', 20.0, 40.0)
builder.add_design_variable('support_blend_radius', 5.0, 15.0)
# Add objectives: minimize weight AND minimize stress
builder.add_objective('minimize_mass', weight=5.0)
builder.add_objective('minimize_max_stress', weight=10.0)
# Add constraints: max displacement < 1.0 mm, max stress < 200 MPa
builder.add_constraint('max_displacement_limit', limit_value=1.0)
builder.add_constraint('max_stress_limit', limit_value=200.0)
# Set optimization settings
builder.set_optimization_settings(n_trials=150, sampler='TPE')
# Print summary
builder.print_summary()
# Save configuration
builder.save(Path('optimization_config.json'))
print("\nConfiguration ready for optimization!")