2025-11-26 12:01:50 -05:00
|
|
|
"""
|
|
|
|
|
Neural Network Only Optimization
|
|
|
|
|
|
|
|
|
|
This script runs multi-objective optimization using ONLY the neural network
|
|
|
|
|
surrogate (no FEA). This demonstrates the speed improvement from NN predictions.
|
|
|
|
|
|
|
|
|
|
Objectives:
|
|
|
|
|
- Minimize mass
|
|
|
|
|
- Maximize frequency (minimize -frequency)
|
|
|
|
|
"""
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
import time
|
|
|
|
|
import json
|
|
|
|
|
import optuna
|
|
|
|
|
from optuna.samplers import NSGAIISampler
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
# Add project paths
|
|
|
|
|
project_root = Path(__file__).parent
|
|
|
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
|
sys.path.insert(0, str(project_root / 'atomizer-field'))
|
|
|
|
|
|
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
|
|
|
from optimization_engine.processors.surrogates.simple_mlp_surrogate import SimpleSurrogate
|
2025-11-26 12:01:50 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
print("="*60)
|
|
|
|
|
print("Neural Network Only Optimization (Simple MLP)")
|
|
|
|
|
print("="*60)
|
|
|
|
|
|
|
|
|
|
# Load surrogate
|
|
|
|
|
print("\n[1] Loading neural surrogate...")
|
|
|
|
|
model_path = project_root / "simple_mlp_surrogate.pt"
|
|
|
|
|
|
|
|
|
|
if not model_path.exists():
|
|
|
|
|
print(f"ERROR: Model not found at {model_path}")
|
|
|
|
|
print("Run 'python optimization_engine/simple_mlp_surrogate.py' first to train")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
surrogate = SimpleSurrogate.load(model_path)
|
|
|
|
|
|
|
|
|
|
if not surrogate:
|
|
|
|
|
print("ERROR: Could not load neural surrogate")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
print(f" Design variables: {surrogate.design_var_names}")
|
|
|
|
|
|
|
|
|
|
# Define bounds (from UAV arm study)
|
|
|
|
|
bounds = {
|
|
|
|
|
'beam_half_core_thickness': (1.0, 5.0),
|
|
|
|
|
'beam_face_thickness': (0.5, 3.0),
|
|
|
|
|
'holes_diameter': (0.5, 5.0),
|
|
|
|
|
'hole_count': (0.0, 6.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print(f" Bounds: {bounds}")
|
|
|
|
|
|
|
|
|
|
# Create Optuna study
|
|
|
|
|
print("\n[2] Creating Optuna study...")
|
|
|
|
|
storage_path = project_root / "nn_only_optimization_study.db"
|
|
|
|
|
|
|
|
|
|
# Remove old study if exists
|
|
|
|
|
if storage_path.exists():
|
|
|
|
|
storage_path.unlink()
|
|
|
|
|
|
|
|
|
|
storage = optuna.storages.RDBStorage(f"sqlite:///{storage_path}")
|
|
|
|
|
|
|
|
|
|
study = optuna.create_study(
|
|
|
|
|
study_name="nn_only_optimization",
|
|
|
|
|
storage=storage,
|
|
|
|
|
directions=["minimize", "minimize"], # mass, -frequency (minimize both)
|
|
|
|
|
sampler=NSGAIISampler()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Track stats
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
trial_times = []
|
|
|
|
|
|
|
|
|
|
def objective(trial: optuna.Trial):
|
|
|
|
|
trial_start = time.time()
|
|
|
|
|
|
|
|
|
|
# Suggest parameters
|
|
|
|
|
params = {}
|
|
|
|
|
for name, (low, high) in bounds.items():
|
|
|
|
|
if name == 'hole_count':
|
|
|
|
|
params[name] = trial.suggest_int(name, int(low), int(high))
|
|
|
|
|
else:
|
|
|
|
|
params[name] = trial.suggest_float(name, low, high)
|
|
|
|
|
|
|
|
|
|
# Predict with NN
|
|
|
|
|
results = surrogate.predict(params)
|
|
|
|
|
|
|
|
|
|
mass = results['mass']
|
|
|
|
|
frequency = results['frequency']
|
|
|
|
|
|
|
|
|
|
trial_time = (time.time() - trial_start) * 1000
|
|
|
|
|
trial_times.append(trial_time)
|
|
|
|
|
|
|
|
|
|
# Log progress every 100 trials
|
|
|
|
|
if trial.number % 100 == 0:
|
|
|
|
|
print(f" Trial {trial.number}: mass={mass:.1f}g, freq={frequency:.2f}Hz, time={trial_time:.1f}ms")
|
|
|
|
|
|
|
|
|
|
# Return objectives: minimize mass, minimize -frequency (= maximize frequency)
|
|
|
|
|
return mass, -frequency
|
|
|
|
|
|
|
|
|
|
# Run optimization
|
|
|
|
|
n_trials = 1000 # Much faster with NN!
|
|
|
|
|
print(f"\n[3] Running {n_trials} trials...")
|
|
|
|
|
|
|
|
|
|
study.optimize(objective, n_trials=n_trials, show_progress_bar=True)
|
|
|
|
|
|
|
|
|
|
total_time = time.time() - start_time
|
|
|
|
|
|
|
|
|
|
# Results
|
|
|
|
|
print("\n" + "="*60)
|
|
|
|
|
print("RESULTS")
|
|
|
|
|
print("="*60)
|
|
|
|
|
|
|
|
|
|
print(f"\nTotal time: {total_time:.1f}s for {n_trials} trials")
|
|
|
|
|
print(f"Average time per trial: {np.mean(trial_times):.1f}ms")
|
|
|
|
|
print(f"Trials per second: {n_trials/total_time:.1f}")
|
|
|
|
|
|
|
|
|
|
# Get Pareto front
|
|
|
|
|
pareto_front = study.best_trials
|
|
|
|
|
print(f"\nPareto front size: {len(pareto_front)} designs")
|
|
|
|
|
|
|
|
|
|
print("\nTop 5 Pareto-optimal designs:")
|
|
|
|
|
for i, trial in enumerate(pareto_front[:5]):
|
|
|
|
|
mass = trial.values[0]
|
|
|
|
|
freq = -trial.values[1] # Convert back to positive
|
|
|
|
|
print(f" {i+1}. Mass={mass:.1f}g, Freq={freq:.2f}Hz")
|
|
|
|
|
print(f" Params: {trial.params}")
|
|
|
|
|
|
|
|
|
|
# Save results
|
|
|
|
|
results_file = project_root / "nn_optimization_results.json"
|
|
|
|
|
results = {
|
|
|
|
|
'n_trials': n_trials,
|
|
|
|
|
'total_time_s': total_time,
|
|
|
|
|
'avg_trial_time_ms': np.mean(trial_times),
|
|
|
|
|
'trials_per_second': n_trials/total_time,
|
|
|
|
|
'pareto_front_size': len(pareto_front),
|
|
|
|
|
'pareto_designs': [
|
|
|
|
|
{
|
|
|
|
|
'mass': t.values[0],
|
|
|
|
|
'frequency': -t.values[1],
|
|
|
|
|
'params': t.params
|
|
|
|
|
}
|
|
|
|
|
for t in pareto_front
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
with open(results_file, 'w') as f:
|
|
|
|
|
json.dump(results, f, indent=2)
|
|
|
|
|
|
|
|
|
|
print(f"\nResults saved to: {results_file}")
|
|
|
|
|
print(f"Study database: {storage_path}")
|
|
|
|
|
print("\nView in Optuna dashboard:")
|
|
|
|
|
print(f" optuna-dashboard sqlite:///{storage_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|