- Consolidate surrogates module to processors/surrogates/ - Move ensemble_surrogate.py to proper location - Add deprecation shim for old import path - Create tests/ directory with pytest structure - Move test files from archive/test_scripts/ - Add conftest.py with shared fixtures 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
165 lines
5.3 KiB
Python
165 lines
5.3 KiB
Python
"""
|
|
Test script for Protocol 10 v2.0 Adaptive Characterization.
|
|
|
|
This script demonstrates the new adaptive characterization feature that
|
|
intelligently determines when enough landscape exploration has been done.
|
|
|
|
Expected behavior:
|
|
- Simple problems: Stop at ~10-15 trials
|
|
- Complex problems: Continue to ~20-30 trials
|
|
"""
|
|
|
|
import numpy as np
|
|
import optuna
|
|
from pathlib import Path
|
|
from optimization_engine.processors.adaptive_characterization import CharacterizationStoppingCriterion
|
|
from optimization_engine.reporting.landscape_analyzer import LandscapeAnalyzer
|
|
|
|
|
|
def simple_smooth_function(trial):
|
|
"""Simple smooth quadratic function (should stop early ~10-15 trials)."""
|
|
x = trial.suggest_float('x', -10, 10)
|
|
y = trial.suggest_float('y', -10, 10)
|
|
|
|
# Simple quadratic bowl
|
|
return (x - 3)**2 + (y + 2)**2
|
|
|
|
|
|
def complex_multimodal_function(trial):
|
|
"""Complex multimodal function (should need more trials ~20-30)."""
|
|
x = trial.suggest_float('x', -5, 5)
|
|
y = trial.suggest_float('y', -5, 5)
|
|
|
|
# Rastrigin function (multimodal, many local minima)
|
|
A = 10
|
|
n = 2
|
|
return A * n + ((x**2 - A * np.cos(2 * np.pi * x)) +
|
|
(y**2 - A * np.cos(2 * np.pi * y)))
|
|
|
|
|
|
def test_adaptive_characterization(
|
|
objective_function,
|
|
function_name: str,
|
|
expected_trials_range: tuple
|
|
):
|
|
"""Test adaptive characterization on a given function."""
|
|
|
|
print(f"\n{'='*70}")
|
|
print(f" TESTING: {function_name}")
|
|
print(f" Expected trials: {expected_trials_range[0]}-{expected_trials_range[1]}")
|
|
print(f"{'='*70}\n")
|
|
|
|
# Setup tracking directory
|
|
tracking_dir = Path(f"test_results/adaptive_char_{function_name.lower().replace(' ', '_')}")
|
|
tracking_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Create components
|
|
analyzer = LandscapeAnalyzer(min_trials_for_analysis=10)
|
|
stopping_criterion = CharacterizationStoppingCriterion(
|
|
min_trials=10,
|
|
max_trials=30,
|
|
confidence_threshold=0.85,
|
|
check_interval=5,
|
|
verbose=True,
|
|
tracking_dir=tracking_dir
|
|
)
|
|
|
|
# Create study
|
|
study = optuna.create_study(
|
|
study_name=f"test_{function_name.lower().replace(' ', '_')}",
|
|
direction='minimize',
|
|
sampler=optuna.samplers.RandomSampler()
|
|
)
|
|
|
|
# Run adaptive characterization
|
|
check_interval = 5
|
|
while not stopping_criterion.should_stop(study):
|
|
# Run batch of trials
|
|
study.optimize(objective_function, n_trials=check_interval)
|
|
|
|
# Analyze landscape
|
|
landscape = analyzer.analyze(study)
|
|
|
|
# Update stopping criterion
|
|
if landscape.get('ready', False):
|
|
completed_trials = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE]
|
|
stopping_criterion.update(landscape, len(completed_trials))
|
|
|
|
# Print results
|
|
completed_trials = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE]
|
|
actual_trials = len(completed_trials)
|
|
|
|
print(stopping_criterion.get_summary_report())
|
|
|
|
# Verify expectation
|
|
in_range = expected_trials_range[0] <= actual_trials <= expected_trials_range[1]
|
|
status = "PASS" if in_range else "FAIL"
|
|
|
|
print(f"\n{'='*70}")
|
|
print(f" RESULT: {status}")
|
|
print(f" Actual trials: {actual_trials}")
|
|
print(f" Expected range: {expected_trials_range[0]}-{expected_trials_range[1]}")
|
|
print(f" In range: {'YES' if in_range else 'NO'}")
|
|
print(f" Stop reason: {stopping_criterion.stop_reason}")
|
|
print(f" Final confidence: {stopping_criterion.final_confidence:.1%}")
|
|
print(f"{'='*70}\n")
|
|
|
|
return {
|
|
'function': function_name,
|
|
'expected_range': expected_trials_range,
|
|
'actual_trials': actual_trials,
|
|
'in_range': in_range,
|
|
'stop_reason': stopping_criterion.stop_reason,
|
|
'confidence': stopping_criterion.final_confidence
|
|
}
|
|
|
|
|
|
def main():
|
|
"""Run all adaptive characterization tests."""
|
|
|
|
print("\n" + "="*70)
|
|
print(" PROTOCOL 10 v2.0: ADAPTIVE CHARACTERIZATION TESTS")
|
|
print("="*70)
|
|
|
|
results = []
|
|
|
|
# Test 1: Simple smooth function (should stop early)
|
|
result1 = test_adaptive_characterization(
|
|
objective_function=simple_smooth_function,
|
|
function_name="Simple Smooth Quadratic",
|
|
expected_trials_range=(10, 20)
|
|
)
|
|
results.append(result1)
|
|
|
|
# Test 2: Complex multimodal function (should need more trials)
|
|
result2 = test_adaptive_characterization(
|
|
objective_function=complex_multimodal_function,
|
|
function_name="Complex Multimodal (Rastrigin)",
|
|
expected_trials_range=(15, 30)
|
|
)
|
|
results.append(result2)
|
|
|
|
# Summary
|
|
print("\n" + "="*70)
|
|
print(" TEST SUMMARY")
|
|
print("="*70)
|
|
|
|
for result in results:
|
|
status = "PASS" if result['in_range'] else "FAIL"
|
|
print(f"\n [{status}] {result['function']}")
|
|
print(f" Expected: {result['expected_range'][0]}-{result['expected_range'][1]} trials")
|
|
print(f" Actual: {result['actual_trials']} trials")
|
|
print(f" Confidence: {result['confidence']:.1%}")
|
|
|
|
# Overall pass/fail
|
|
all_passed = all(r['in_range'] for r in results)
|
|
overall_status = "ALL TESTS PASSED" if all_passed else "SOME TESTS FAILED"
|
|
|
|
print(f"\n{'='*70}")
|
|
print(f" {overall_status}")
|
|
print(f"{'='*70}\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|