2  Optimizing the Aircraft Wing Weight Example

NoteNote
  • This section demonstrates optimization of the Aircraft Wing Weight Example (AWWE) function
  • We compare three optimization methods:
    • SpotOptim: Bayesian optimization with Gaussian Process surrogate
    • Nelder-Mead: Derivative-free simplex method from scipy.optimize
    • BFGS: Quasi-Newton method from scipy.optimize
  • The following Python packages are imported:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.optimize import minimize
from spotoptim import SpotOptim
import time
import pprint

2.1 The AWWE Objective Function

We use the same AWWE function from Chapter 25, which models the weight of an unpainted light aircraft wing. The function accepts inputs in the unit cube \([0,1]^9\) and returns the wing weight.

def wingwt(x):
    """
    Aircraft Wing Weight function.
    
    Args:
        x: array-like of 9 values in [0,1]
           [Sw, Wfw, A, L, q, l, Rtc, Nz, Wdg]
    
    Returns:
        Wing weight (scalar)
    """
    # Ensure x is a 2D array for batch evaluation
    x = np.atleast_2d(x)
    
    # Transform from unit cube to natural scales
    Sw = x[:, 0] * (200 - 150) + 150 
    Wfw = x[:, 1] * (300 - 220) + 220 
    A = x[:, 2] * (10 - 6) + 6 
    L = (x[:, 3] * (10 - (-10)) - 10) * np.pi/180
    q = x[:, 4] * (45 - 16) + 16 
    l = x[:, 5] * (1 - 0.5) + 0.5  
    Rtc = x[:, 6] * (0.18 - 0.08) + 0.08
    Nz = x[:, 7] * (6 - 2.5) + 2.5
    Wdg = x[:, 8] * (2500 - 1700) + 1700
    
    # Calculate weight on natural scale
    W = 0.036 * Sw**0.758 * Wfw**0.0035 * (A/np.cos(L)**2)**0.6 * q**0.006 
    W = W * l**0.04 * (100*Rtc/np.cos(L))**(-0.3) * (Nz*Wdg)**(0.49)
    
    return W.ravel()


# Wrapper for scipy.optimize (expects 1D input, returns scalar)
def wingwt_scipy(x):
    return float(wingwt(x.reshape(1, -1))[0])

2.2 Baseline Configuration

The baseline Cessna C172 Skyhawk configuration (coded in unit cube):

baseline_coded = np.array([0.48, 0.4, 0.38, 0.5, 0.62, 0.344, 0.4, 0.37, 0.38])
baseline_weight = wingwt(baseline_coded)[0]
print(f"Baseline wing weight: {baseline_weight:.2f} lb")
Baseline wing weight: 233.91 lb

2.3 Optimization Setup

We’ll optimize the AWWE function starting from the baseline configuration using three different methods:

  1. SpotOptim: Bayesian optimization (good for expensive black-box functions)
  2. Nelder-Mead: Derivative-free simplex method (robust but can be slow)
  3. BFGS: Quasi-Newton method (fast but requires smooth functions)
# Starting point (baseline configuration)
x0 = baseline_coded.copy()

# Bounds for all methods (unit cube)
bounds = [(0, 1)] * 9

# Number of function evaluations budget
max_evals = 30

print(f"Starting point: {x0}")
print(f"Starting weight: {baseline_weight:.2f} lb")
Starting point: [0.48  0.4   0.38  0.5   0.62  0.344 0.4   0.37  0.38 ]
Starting weight: 233.91 lb

2.4 Method 1: SpotOptim (Surrogate Model Based Optimization)

# Start timing
start_time = time.time()

# Configure SpotOptim
optimizer_spot = SpotOptim(
    fun=wingwt,
    bounds=bounds,
    x0=None,
    max_iter=max_evals,
    n_initial=10,  # Initial design points
    var_name=['Sw', 'Wfw', 'A', 'L', 'q', 'l', 'Rtc', 'Nz', 'Wdg'],
    acquisition='y',  # ei: Expected Improvement
    max_surrogate_points=100,
    seed=42,
    verbose=True,
    tensorboard_log=True,
    tensorboard_clean=True
)
Removed old TensorBoard logs: runs/spotoptim_20251220_003236
Cleaned 1 old TensorBoard log directory
TensorBoard logging enabled: runs/spotoptim_20251220_094012

2.5 Design Table

pprint.pprint(optimizer_spot.print_design_table())
| name   | type   |   lower |   upper |   default | transform   |
|--------|--------|---------|---------|-----------|-------------|
| Sw     | float  |  0.0000 |  1.0000 |    0.5000 | -           |
| Wfw    | float  |  0.0000 |  1.0000 |    0.5000 | -           |
| A      | float  |  0.0000 |  1.0000 |    0.5000 | -           |
| L      | float  |  0.0000 |  1.0000 |    0.5000 | -           |
| q      | float  |  0.0000 |  1.0000 |    0.5000 | -           |
| l      | float  |  0.0000 |  1.0000 |    0.5000 | -           |
| Rtc    | float  |  0.0000 |  1.0000 |    0.5000 | -           |
| Nz     | float  |  0.0000 |  1.0000 |    0.5000 | -           |
| Wdg    | float  |  0.0000 |  1.0000 |    0.5000 | -           |
('| name   | type   |   lower |   upper |   default | transform   |\n'
 '|--------|--------|---------|---------|-----------|-------------|\n'
 '| Sw     | float  |  0.0000 |  1.0000 |    0.5000 | -           |\n'
 '| Wfw    | float  |  0.0000 |  1.0000 |    0.5000 | -           |\n'
 '| A      | float  |  0.0000 |  1.0000 |    0.5000 | -           |\n'
 '| L      | float  |  0.0000 |  1.0000 |    0.5000 | -           |\n'
 '| q      | float  |  0.0000 |  1.0000 |    0.5000 | -           |\n'
 '| l      | float  |  0.0000 |  1.0000 |    0.5000 | -           |\n'
 '| Rtc    | float  |  0.0000 |  1.0000 |    0.5000 | -           |\n'
 '| Nz     | float  |  0.0000 |  1.0000 |    0.5000 | -           |\n'
 '| Wdg    | float  |  0.0000 |  1.0000 |    0.5000 | -           |')

2.6 Run optimization

result_spot = optimizer_spot.optimize()
Initial best: f(x) = 212.219714
Iteration 1: New best f(x) = 142.977744
Iteration 2: New best f(x) = 140.904175
Iteration 3: New best f(x) = 120.465251
Iteration 4: f(x) = 121.575842
Iteration 5: New best f(x) = 120.101053
Iteration 6: New best f(x) = 119.997018
Iteration 7: f(x) = 120.018134
Iteration 8: New best f(x) = 119.958665
Iteration 9: f(x) = 120.386522
Iteration 10: New best f(x) = 119.691338
Iteration 11: New best f(x) = 119.530080
Iteration 12: f(x) = 119.539469
Iteration 13: New best f(x) = 119.503677
Iteration 14: f(x) = 119.503691
Iteration 15: New best f(x) = 119.503672
Iteration 16: f(x) = 119.503718
Iteration 17: f(x) = 119.503685
Iteration 18: f(x) = 119.503693
Iteration 19: f(x) = 119.503726
Iteration 20: f(x) = 119.503749
TensorBoard writer closed. View logs with: tensorboard --logdir=runs/spotoptim_20251220_094012
# End timing
spot_time = time.time() - start_time

print(f"\nSpotOptim Results:")
print(f"  Best weight: {result_spot.fun:.4f} lb")
print(f"  Function evaluations: {result_spot.nfev}")
print(f"  Time elapsed: {spot_time:.2f} seconds")
print(f"  Success: {result_spot.success}")

SpotOptim Results:
  Best weight: 119.5037 lb
  Function evaluations: 30
  Time elapsed: 5.96 seconds
  Success: True
optimizer_spot.print_best()

Best Solution Found:
--------------------------------------------------
  Sw: 0.0000
  Wfw: 0.0000
  A: 0.0000
  L: 0.5000
  q: 0.0000
  l: 0.0000
  Rtc: 1.0000
  Nz: 0.0000
  Wdg: 0.0000
  Objective Value: 119.5037
  Total Evaluations: 30

2.7 Result Table

pprint.pprint(optimizer_spot.print_results_table(show_importance=True))
| name   | type   |   default |   lower |   upper |   tuned | transform   |   importance | stars   |
|--------|--------|-----------|---------|---------|---------|-------------|--------------|---------|
| Sw     | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           |         9.52 |         |
| Wfw    | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           |        12.76 | .       |
| A      | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           |        14.11 | .       |
| L      | float  |    0.5000 |  0.0000 |  1.0000 |  0.5000 | -           |         4.30 |         |
| q      | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           |         6.10 |         |
| l      | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           |        11.05 | .       |
| Rtc    | float  |    0.5000 |  0.0000 |  1.0000 |  1.0000 | -           |        13.87 | .       |
| Nz     | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           |        14.03 | .       |
| Wdg    | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           |        14.27 | .       |

Interpretation: ***: >99%, **: >75%, *: >50%, .: >10%
('| name   | type   |   default |   lower |   upper |   tuned | transform   '
 '|   importance | stars   |\n'
 '|--------|--------|-----------|---------|---------|---------|-------------|--------------|---------|\n'
 '| Sw     | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           '
 '|         9.52 |         |\n'
 '| Wfw    | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           '
 '|        12.76 | .       |\n'
 '| A      | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           '
 '|        14.11 | .       |\n'
 '| L      | float  |    0.5000 |  0.0000 |  1.0000 |  0.5000 | -           '
 '|         4.30 |         |\n'
 '| q      | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           '
 '|         6.10 |         |\n'
 '| l      | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           '
 '|        11.05 | .       |\n'
 '| Rtc    | float  |    0.5000 |  0.0000 |  1.0000 |  1.0000 | -           '
 '|        13.87 | .       |\n'
 '| Nz     | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           '
 '|        14.03 | .       |\n'
 '| Wdg    | float  |    0.5000 |  0.0000 |  1.0000 |  0.0000 | -           '
 '|        14.27 | .       |\n'
 '\n'
 'Interpretation: ***: >99%, **: >75%, *: >50%, .: >10%')

2.8 Progress of the Optimization

optimizer_spot.plot_progress(log_y=False)

2.9 Contour Plots of Most Important Hyperparameters

optimizer_spot.plot_important_hyperparameter_contour(max_imp=3)
Plotting surrogate contours for top 3 most important parameters:
  Wdg: importance = 14.27% (type: float)
  A: importance = 14.11% (type: float)
  Nz: importance = 14.03% (type: float)

Generating 3 surrogate plots...
  Plotting Wdg vs A

  Plotting Wdg vs Nz

  Plotting A vs Nz

2.10 Method 2: Nelder-Mead Simplex

print("\n" + "=" * 60)
print("Running Nelder-Mead Simplex...")
print("=" * 60)

# Start timing
start_time = time.time()

# Run optimization
result_nm = minimize(
    wingwt_scipy,
    x0=x0,
    method='Nelder-Mead',
    bounds=bounds,
    options={'maxfev': max_evals, 'disp': True}
)

# End timing
nm_time = time.time() - start_time

print(f"\nNelder-Mead Results:")
print(f"  Best weight: {result_nm.fun:.4f} lb")
print(f"  Function evaluations: {result_nm.nfev}")
print(f"  Time elapsed: {nm_time:.2f} seconds")
print(f"  Success: {result_nm.success}")

============================================================
Running Nelder-Mead Simplex...
============================================================

Nelder-Mead Results:
  Best weight: 220.5449 lb
  Function evaluations: 30
  Time elapsed: 0.00 seconds
  Success: False

2.11 Method 3: BFGS (Quasi-Newton)

print("\n" + "=" * 60)
print("Running BFGS (Quasi-Newton)...")
print("=" * 60)


# Start timing
start_time = time.time()

# Run optimization
result_bfgs = minimize(
    wingwt_scipy,
    x0=x0,
    method='L-BFGS-B',  # Bounded BFGS
    bounds=bounds,
    options={'maxfun': max_evals, 'disp': True}
)

# End timing
bfgs_time = time.time() - start_time

print(f"\nBFGS Results:")
print(f"  Best weight: {result_bfgs.fun:.4f} lb")
print(f"  Function evaluations: {result_bfgs.nfev}")
print(f"  Time elapsed: {bfgs_time:.2f} seconds")
print(f"  Success: {result_bfgs.success}")

============================================================
Running BFGS (Quasi-Newton)...
============================================================

BFGS Results:
  Best weight: 119.5037 lb
  Function evaluations: 60
  Time elapsed: 0.00 seconds
  Success: False

2.12 Comparison of Results

# Create comparison DataFrame
comparison = pd.DataFrame({
    'Method': ['Baseline', 'SpotOptim', 'Nelder-Mead', 'BFGS'],
    'Best Weight (lb)': [
        baseline_weight,
        result_spot.fun,
        result_nm.fun,
        result_bfgs.fun
    ],
    'Improvement (%)': [
        0.0,
        (baseline_weight - result_spot.fun) / baseline_weight * 100,
        (baseline_weight - result_nm.fun) / baseline_weight * 100,
        (baseline_weight - result_bfgs.fun) / baseline_weight * 100
    ],
    'Function Evals': [
        1,
        result_spot.nfev,
        result_nm.nfev,
        result_bfgs.nfev
    ],
    'Time (s)': [
        0.0,
        spot_time,
        nm_time,
        bfgs_time
    ],
    'Success': [
        True,
        result_spot.success,
        result_nm.success,
        result_bfgs.success
    ]
})

print("\n" + "=" * 80)
print("OPTIMIZATION COMPARISON")
print("=" * 80)
print(comparison.to_string(index=False))
print("=" * 80)

================================================================================
OPTIMIZATION COMPARISON
================================================================================
     Method  Best Weight (lb)  Improvement (%)  Function Evals  Time (s)  Success
   Baseline        233.908405         0.000000               1  0.000000     True
  SpotOptim        119.503672        48.910057              30  5.956134     True
Nelder-Mead        220.544928         5.713124              30  0.000865    False
       BFGS        119.503672        48.910057              60  0.001519    False
================================================================================

2.13 Visualization: Convergence Plots

2.13.1 SpotOptim Convergence

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# Plot 1: Best value over iterations
y_history = optimizer_spot.y_
best_so_far = np.minimum.accumulate(y_history)

ax1.plot(range(len(y_history)), y_history, 'o', alpha=0.5, label='Evaluated points')
ax1.plot(range(len(best_so_far)), best_so_far, 'r-', linewidth=2, label='Best so far')
ax1.axhline(y=baseline_weight, color='g', linestyle='--', label='Baseline')
ax1.set_xlabel('Function Evaluation')
ax1.set_ylabel('Wing Weight (lb)')
ax1.set_title('SpotOptim Convergence')
ax1.legend()
ax1.grid(True, alpha=0.3)

# Plot 2: Improvement over baseline
improvement = (baseline_weight - best_so_far) / baseline_weight * 100
ax2.plot(range(len(improvement)), improvement, 'b-', linewidth=2)
ax2.set_xlabel('Function Evaluation')
ax2.set_ylabel('Improvement over Baseline (%)')
ax2.set_title('SpotOptim Improvement Progress')
ax2.grid(True, alpha=0.3)
ax2.axhline(y=0, color='k', linestyle='-', alpha=0.3)

plt.tight_layout()
plt.show()

2.14 Optimal Parameter Values

Let’s examine the optimal parameter values found by each method:

# Parameter names
param_names = ['Sw', 'Wfw', 'A', 'L', 'q', 'l', 'Rtc', 'Nz', 'Wdg']

# Transform from unit cube to natural scales
def decode_params(x):
    scales = [
        (150, 200),      # Sw
        (220, 300),      # Wfw
        (6, 10),         # A
        (-10, 10),       # L (degrees)
        (16, 45),        # q
        (0.5, 1),        # l
        (0.08, 0.18),    # Rtc
        (2.5, 6),        # Nz
        (1700, 2500)     # Wdg
    ]
    decoded = []
    for i, (low, high) in enumerate(scales):
        decoded.append(x[i] * (high - low) + low)
    return decoded

# Create comparison table
baseline_decoded = decode_params(baseline_coded)
spot_decoded = decode_params(result_spot.x)
nm_decoded = decode_params(result_nm.x)
bfgs_decoded = decode_params(result_bfgs.x)

param_comparison = pd.DataFrame({
    'Parameter': param_names,
    'Baseline': baseline_decoded,
    'SpotOptim': spot_decoded,
    'Nelder-Mead': nm_decoded,
    'BFGS': bfgs_decoded
})

print("\n" + "=" * 100)
print("OPTIMAL PARAMETER VALUES (Natural Scale)")
print("=" * 100)
print(param_comparison.to_string(index=False))
print("=" * 100)

====================================================================================================
OPTIMAL PARAMETER VALUES (Natural Scale)
====================================================================================================
Parameter  Baseline   SpotOptim  Nelder-Mead    BFGS
       Sw   174.000  150.000000   173.923947  150.00
      Wfw   252.000  220.000000   254.534988  220.00
        A     7.520    6.000000     7.484111    6.00
        L     0.000    0.000531    -0.126137    0.00
        q    33.980   16.000000    34.156818   16.00
        l     0.672    0.500000     0.674613    0.50
      Rtc     0.120    0.180000     0.124872    0.18
       Nz     3.795    2.500000     3.427058    2.50
      Wdg  2004.000 1700.000000  2028.938270 1700.00
====================================================================================================

2.15 Analysis of Optimal Solutions

# Calculate percentage changes from baseline
changes_spot = [(spot_decoded[i] - baseline_decoded[i]) / baseline_decoded[i] * 100 
                for i in range(len(param_names))]
changes_nm = [(nm_decoded[i] - baseline_decoded[i]) / baseline_decoded[i] * 100 
              for i in range(len(param_names))]
changes_bfgs = [(bfgs_decoded[i] - baseline_decoded[i]) / baseline_decoded[i] * 100 
                for i in range(len(param_names))]

# Visualization
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

for ax, changes, method in zip(axes, 
                                 [changes_spot, changes_nm, changes_bfgs],
                                 ['SpotOptim', 'Nelder-Mead', 'BFGS']):
    colors = ['red' if c < 0 else 'green' for c in changes]
    ax.barh(param_names, changes, color=colors, alpha=0.6)
    ax.axvline(x=0, color='black', linestyle='-', linewidth=0.5)
    ax.set_xlabel('Change from Baseline (%)')
    ax.set_title(f'{method}: Parameter Changes')
    ax.grid(True, alpha=0.3, axis='x')

plt.tight_layout()
plt.show()

2.16 Key Insights from Optimal Solutions

# Find parameters with largest changes for each method
def analyze_changes(decoded, baseline_decoded, method_name):
    changes = {param_names[i]: decoded[i] - baseline_decoded[i] 
               for i in range(len(param_names))}
    sorted_changes = sorted(changes.items(), key=lambda x: abs(x[1]), reverse=True)
    
    print(f"\n{method_name} - Top 5 Parameter Changes:")
    print("-" * 50)
    for param, change in sorted_changes[:5]:
        idx = param_names.index(param)
        pct = change / baseline_decoded[idx] * 100
        print(f"  {param:5s}: {change:+8.2f} ({pct:+6.1f}%)")

analyze_changes(spot_decoded, baseline_decoded, "SpotOptim")
analyze_changes(nm_decoded, baseline_decoded, "Nelder-Mead")
analyze_changes(bfgs_decoded, baseline_decoded, "BFGS")

SpotOptim - Top 5 Parameter Changes:
--------------------------------------------------
  Wdg  :  -304.00 ( -15.2%)
  Wfw  :   -32.00 ( -12.7%)
  Sw   :   -24.00 ( -13.8%)
  q    :   -17.98 ( -52.9%)
  A    :    -1.52 ( -20.2%)

Nelder-Mead - Top 5 Parameter Changes:
--------------------------------------------------
  Wdg  :   +24.94 (  +1.2%)
  Wfw  :    +2.53 (  +1.0%)
  Nz   :    -0.37 (  -9.7%)
  q    :    +0.18 (  +0.5%)
  L    :    -0.13 (  -inf%)

BFGS - Top 5 Parameter Changes:
--------------------------------------------------
  Wdg  :  -304.00 ( -15.2%)
  Wfw  :   -32.00 ( -12.7%)
  Sw   :   -24.00 ( -13.8%)
  q    :   -17.98 ( -52.9%)
  A    :    -1.52 ( -20.2%)

2.17 Method Efficiency Comparison

# Calculate efficiency metrics
efficiency = pd.DataFrame({
    'Method': ['SpotOptim', 'Nelder-Mead', 'BFGS'],
    'Weight Reduction (lb)': [
        baseline_weight - result_spot.fun,
        baseline_weight - result_nm.fun,
        baseline_weight - result_bfgs.fun
    ],
    'Evals to Best': [
        np.argmin(optimizer_spot.y_) + 1,
        result_nm.nfev,
        result_bfgs.nfev
    ],
    'Time per Eval (ms)': [
        spot_time / result_spot.nfev * 1000,
        nm_time / result_nm.nfev * 1000,
        bfgs_time / result_bfgs.nfev * 1000
    ]
})

print("\n" + "=" * 80)
print("METHOD EFFICIENCY METRICS")
print("=" * 80)
print(efficiency.to_string(index=False))
print("=" * 80)

================================================================================
METHOD EFFICIENCY METRICS
================================================================================
     Method  Weight Reduction (lb)  Evals to Best  Time per Eval (ms)
  SpotOptim             114.404734             25          198.537795
Nelder-Mead              13.363478             30            0.028833
       BFGS             114.404734             60            0.025316
================================================================================

2.18 Visualization: 2D Slices of Optimal Solutions

Let’s visualize how the optimal solutions compare in the most important 2D subspaces:

# Create 2D slices showing optimal points
fig, axes = plt.subplots(2, 2, figsize=(14, 12))

# Important parameter pairs based on sensitivity analysis
pairs = [
    (7, 2),  # Nz vs A (load factor vs aspect ratio)
    (0, 8),  # Sw vs Wdg (wing area vs gross weight)
    (7, 0),  # Nz vs Sw (load factor vs wing area)
    (2, 6)   # A vs Rtc (aspect ratio vs thickness ratio)
]

for ax, (i, j) in zip(axes.flat, pairs):
    # Create meshgrid for contour plot
    x_range = np.linspace(0, 1, 50)
    y_range = np.linspace(0, 1, 50)
    X, Y = np.meshgrid(x_range, y_range)
    
    # Evaluate function on grid (fixing other parameters at baseline)
    Z = np.zeros_like(X)
    for ii in range(X.shape[0]):
        for jj in range(X.shape[1]):
            point = baseline_coded.copy()
            point[i] = X[ii, jj]
            point[j] = Y[ii, jj]
            Z[ii, jj] = wingwt(point)[0]
    
    # Plot contours
    contour = ax.contourf(X, Y, Z, levels=20, cmap='viridis', alpha=0.6)
    ax.contour(X, Y, Z, levels=10, colors='black', alpha=0.3, linewidths=0.5)
    
    # Plot optimal points
    ax.plot(baseline_coded[i], baseline_coded[j], 'go', markersize=12, 
            label='Baseline', markeredgecolor='black', markeredgewidth=1.5)
    ax.plot(result_spot.x[i], result_spot.x[j], 'r*', markersize=15, 
            label='SpotOptim', markeredgecolor='black', markeredgewidth=1)
    ax.plot(result_nm.x[i], result_nm.x[j], 'bs', markersize=10, 
            label='Nelder-Mead', markeredgecolor='black', markeredgewidth=1)
    ax.plot(result_bfgs.x[i], result_bfgs.x[j], 'c^', markersize=10, 
            label='BFGS', markeredgecolor='black', markeredgewidth=1)
    
    ax.set_xlabel(param_names[i])
    ax.set_ylabel(param_names[j])
    ax.set_title(f'{param_names[j]} vs {param_names[i]}')
    ax.legend(loc='best', fontsize=8)
    ax.grid(True, alpha=0.3)
    
    # Add colorbar
    plt.colorbar(contour, ax=ax, label='Wing Weight (lb)')

plt.tight_layout()
plt.show()

2.19 Conclusion

This analysis demonstrates the application of different optimization methods to the Aircraft Wing Weight Example. Key takeaways:

  1. SpotOptim provides efficient global optimization with good exploration of the design space
  2. Nelder-Mead offers robust derivative-free optimization but may require more evaluations
  3. BFGS converges quickly for smooth problems but can get trapped in local minima

For aircraft design problems with expensive simulations, Bayesian optimization (SpotOptim) offers the best balance of efficiency and solution quality, making it particularly suitable for real-world engineering applications.

2.20 Jupyter Notebook

Note