主题036:鲁棒性优化设计

一、引言

1.1 什么是鲁棒性优化

鲁棒性优化(Robust Optimization, RO)是一种在不确定性环境下寻求最优解的优化方法。与传统的确定性优化不同,鲁棒性优化考虑设计参数、环境条件或模型本身的不确定性,寻找对这些不确定性不敏感的最优设计。

核心思想:一个好的设计不仅要在名义条件下表现优异,还要在参数波动、制造误差、环境变化等不确定因素的影响下保持稳定性能。
在这里插入图片描述

1.2 鲁棒性优化的重要性

在实际工程应用中,不确定性无处不在:

  • 制造误差:加工精度限制导致实际尺寸与设计值存在偏差
  • 材料分散性:材料性能参数(弹性模量、强度等)存在批次差异
  • 环境变化:工作温度、湿度、载荷等环境条件波动
  • 模型不确定性:仿真模型简化带来的误差
  • 老化退化:长期使用导致的性能衰减

传统优化的局限性

  • 确定性优化得到的"最优解"往往位于约束边界
  • 微小的参数扰动可能导致约束违反或性能急剧下降
  • 实际产品的合格率和可靠性难以保证

鲁棒性优化的优势

  • 提高产品质量的一致性
  • 降低对制造精度的苛刻要求
  • 增强产品适应环境变化的能力
  • 提高整体可靠性和经济性

1.3 鲁棒性优化的发展历程

早期阶段(1970s-1980s)

  • Taguchi方法在工业界的广泛应用
  • 强调减少产品质量的变异性
  • 通过正交试验设计优化稳健性

理论发展阶段(1990s-2000s)

  • 鲁棒优化数学理论的建立
  • Ben-Tal和Nemirovski的凸优化鲁棒方法
  • 随机规划与鲁棒优化的结合

工程应用阶段(2000s至今)

  • 结构鲁棒性优化设计
  • 多学科鲁棒优化
  • 基于仿真的鲁棒性优化
  • 智能优化算法在鲁棒优化中的应用

二、鲁棒性优化的数学基础

2.1 不确定性建模

2.1.1 不确定性的分类

按来源分类

  1. 偶然不确定性(Aleatory Uncertainty)

    • 固有的随机性,不可消除
    • 如材料性能的随机波动
    • 通常用概率方法描述
  2. 认知不确定性(Epistemic Uncertainty)

    • 由于知识不足导致
    • 如模型简化带来的误差
    • 可用区间分析、模糊理论描述

按表现形式分类

  1. 参数不确定性:设计参数或环境参数的波动
  2. 模型不确定性:数学模型与实际系统的差异
  3. 载荷不确定性:外部载荷的随机变化
2.1.2 不确定性描述方法

概率方法
当不确定性具有统计规律时,用随机变量描述:

X ~ N(μ, σ²)  # 正态分布
X ~ U(a, b)   # 均匀分布

区间方法
当只知道参数变化范围时:

x ∈ [x^L, x^U]  # 区间不确定性

模糊方法
当不确定性具有模糊性时,使用模糊集或可能性理论。

凸集方法
用凸集描述不确定性集合:

U = {u : ||u|| ≤ ρ}  # 范数有界不确定性

2.2 鲁棒性优化的数学模型

2.2.1 一般形式

考虑不确定性的优化问题可表示为:

min    f(x, ξ)
s.t.   g_j(x, ξ) ≤ 0,  j = 1, ..., m
       x ∈ X

其中:

  • x:设计变量
  • ξ:不确定性参数
  • f:目标函数(可能依赖于不确定性)
  • g_j:约束函数
2.2.2 鲁棒性优化的分类

1. 鲁棒可行性问题
寻找对所有不确定性都可行的解:

min    f(x)
s.t.   g_j(x, ξ) ≤ 0,  ∀ξ ∈ U,  j = 1, ..., m

2. 鲁棒最优性问题
在最坏情况下优化目标:

min    max_{ξ ∈ U} f(x, ξ)
s.t.   g_j(x, ξ) ≤ 0,  ∀ξ ∈ U,  j = 1, ..., m

3. 可调节鲁棒优化
考虑决策可以分阶段进行:

min    c^T x + max_{ξ ∈ U} min_{y(ξ)} d^T y(ξ)
s.t.   Ax + By(ξ) ≤ b,  ∀ξ ∈ U

2.3 鲁棒性度量指标

2.3.1 性能鲁棒性

衡量目标函数对不确定性的敏感程度:

均值性能

μ_f = E[f(x, ξ)]

性能方差

σ_f² = Var[f(x, ξ)]

信噪比(Taguchi)

SN = 10 log(μ_f² / σ_f²)
2.3.2 可行性鲁棒性

衡量约束在不确定性下的满足程度:

约束满足概率

P(g_j(x, ξ) ≤ 0) ≥ R_j

最坏情况约束裕度

Δg_j = min_{ξ ∈ U} (-g_j(x, ξ))
2.3.3 灵敏度指标

一阶灵敏度

S_i = ∂f/∂ξ_i

二阶灵敏度(曲率)

H_ij = ∂²f/∂ξ_i∂ξ_j

三、鲁棒性优化方法

3.1 基于梯度的鲁棒性优化

3.1.1 一阶泰勒展开法

假设不确定性较小,对目标函数进行一阶泰勒展开:

f(x, ξ) ≈ f(x, μ_ξ) + ∇_ξ f(x, μ_ξ)^T (ξ - μ_ξ)

均值近似

μ_f ≈ f(x, μ_ξ)

方差近似

σ_f² ≈ ∇_ξ f(x, μ_ξ)^T Σ_ξ ∇_ξ f(x, μ_ξ)

其中 Σ_ξ 是 ξ 的协方差矩阵。

优化问题

min    f(x, μ_ξ) + w·σ_f
s.t.   g_j(x, μ_ξ) + k·σ_{g_j} ≤ 0

其中 w 和 k 是权重系数。

3.1.2 二阶泰勒展开法

对于非线性较强的问题,使用二阶展开:

f(x, ξ) ≈ f(x, μ_ξ) + ∇_ξ f^T (ξ - μ_ξ) + 1/2 (ξ - μ_ξ)^T H (ξ - μ_ξ)

均值修正

μ_f ≈ f(x, μ_ξ) + 1/2 tr(H Σ_ξ)

方差修正

σ_f² ≈ ∇_ξ f^T Σ_ξ ∇_ξ f + 1/2 tr(H Σ_ξ H Σ_ξ)

3.2 基于抽样的鲁棒性优化

3.2.1 蒙特卡洛模拟法

通过大量随机抽样估计统计特性:

μ_f ≈ (1/N) Σ_{i=1}^N f(x, ξ_i)
σ_f² ≈ (1/(N-1)) Σ_{i=1}^N (f(x, ξ_i) - μ_f)²

优点

  • 适用于任意复杂的非线性问题
  • 可以估计完整的概率分布

缺点

  • 计算成本高
  • 需要大量样本保证精度
3.2.2 拉丁超立方抽样(LHS)

分层抽样方法,提高抽样效率:

1. 将每个变量的取值范围分成N个等概率区间
2. 在每个区间随机抽取一个样本
3. 组合各变量的样本,形成N个样本点

优点

  • 样本覆盖更均匀
  • 方差估计更稳定
  • 所需样本数少于纯随机抽样

3.3 最坏情况优化(Worst-Case Optimization)

3.3.1 基本思想

在最坏的不确定性情况下优化性能:

min    max_{ξ ∈ U} f(x, ξ)
s.t.   max_{ξ ∈ U} g_j(x, ξ) ≤ 0,  j = 1, ..., m
3.3.2 区间不确定性处理

对于区间不确定性 ξ ∈ [ξ^L, ξ^U],最坏情况分析:

线性函数

max f(x, ξ) = f(x, ξ^U)  (若系数为正)

非线性函数
需要求解内部最大化问题:

ξ* = argmax_{ξ ∈ U} f(x, ξ)
3.3.3 保守性控制

最坏情况优化往往过于保守,可通过以下方式调节:

预算不确定性

U(Γ) = {ξ : Σ_i |ξ_i - μ_i|/σ_i ≤ Γ}

其中 Γ 是不确定性预算,控制保守程度。

3.4 基于Taguchi方法的鲁棒性设计

3.4.1 Taguchi质量损失函数

质量损失与偏离目标值的平方成正比:

L(y) = k(y - m)²

其中:

  • y:实际性能值
  • m:目标值
  • k:损失系数
3.4.2 三阶段设计

1. 系统设计
确定基本设计方案和结构。

2. 参数设计
优化设计参数,使性能对噪声不敏感。

3. 容差设计
确定各参数的允许偏差范围。

3.4.3 正交试验设计

使用正交表安排试验,高效评估因素效应:

L_9(3^4):9次试验,4个因素,每个因素3个水平
L_16(4^5):16次试验,5个因素,每个因素4个水平

3.5 随机规划方法

3.5.1 两阶段随机规划

决策分为两个阶段:

第一阶段(在不确定性实现前):
做出"这里-and-now"决策 x。

第二阶段(在不确定性实现后):
根据实际实现 ξ,做出"等待-and-see"决策 y(ξ)。

数学模型

min    c^T x + E[Q(x, ξ)]
s.t.   Ax = b, x ≥ 0

其中 Q(x, ξ) = min_y {q(ξ)^T y : Wy = h(ξ) - T(ξ)x, y ≥ 0}
3.5.2 机会约束规划

允许约束以一定概率不满足:

min    f(x)
s.t.   P(g_j(x, ξ) ≤ 0) ≥ 1 - α_j,  j = 1, ..., m

其中 α_j 是允许的风险水平。

四、鲁棒性优化的求解策略

4.1 双层优化方法

鲁棒性优化常可表述为双层优化问题:

上层:优化设计变量 x
下层:在最坏情况下评估性能

min_x    f(x, ξ*(x))
s.t.     g_j(x, ξ*(x)) ≤ 0

其中 ξ*(x) = argmax_{ξ ∈ U} f(x, ξ)

求解策略

  1. 交替求解上下层问题
  2. 使用KKT条件将下层问题转化为约束
  3. 近似方法(如泰勒展开)

4.2 序列优化方法

4.2.1 序列鲁棒优化(SRO)
迭代 k:
1. 在当前设计点 x_k 进行不确定性分析
2. 构建局部鲁棒性近似模型
3. 求解近似鲁棒优化问题,得到 x_{k+1}
4. 检查收敛性
4.2.2 信赖域方法

在信赖域内建立代理模型:

min    f̃_k(x) + w·σ̃_f(x)
s.t.   ||x - x_k|| ≤ Δ_k
       g̃_j(x) + k·σ̃_{g_j}(x) ≤ 0

其中 f̃_k 是局部代理模型,Δ_k 是信赖域半径。

4.3 智能优化算法

4.3.1 遗传算法(GA)

适应度函数设计

Fitness(x) = -[f(x, μ_ξ) + w·σ_f(x)] - P·max(0, g_max(x))

特殊算子

  • 不确定性抽样评估个体鲁棒性
  • 多目标处理(均值和方差)
4.3.2 粒子群优化(PSO)

速度更新考虑不确定性

v_i^{k+1} = w·v_i^k + c1·r1·(pbest_i - x_i^k) + c2·r2·(gbest - x_i^k) + c3·r3·∇_x σ_f
4.3.3 多目标进化算法

将鲁棒性优化转化为多目标问题:

min    [f(x, μ_ξ), σ_f(x)]
s.t.   μ_{g_j}(x) + k·σ_{g_j}(x) ≤ 0

使用NSGA-II、MOEA/D等算法求解Pareto前沿。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
鲁棒性优化设计仿真程序
主题036:多场耦合优化仿真

本程序包含4个鲁棒性优化仿真案例:
1. 悬臂梁鲁棒性优化设计(泰勒展开法)
2. 多目标鲁棒性优化(Pareto前沿)
3. 最坏情况优化与保守性分析
4. 基于抽样的鲁棒性优化对比

作者:仿真专家
日期:2026-02-28
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, FancyBboxPatch, Circle, Ellipse
from matplotlib.collections import PatchCollection
import matplotlib.animation as animation
from scipy.optimize import minimize, differential_evolution, NonlinearConstraint
from scipy.stats import norm, uniform
import os
import warnings
from PIL import Image

warnings.filterwarnings('ignore')

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.dpi'] = 150

# 创建输出目录
output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'output')
os.makedirs(output_dir, exist_ok=True)

print("=" * 80)
print("鲁棒性优化设计仿真")
print("主题036:多场耦合优化仿真")
print("=" * 80)


# =============================================================================
# 案例1:悬臂梁鲁棒性优化设计(泰勒展开法)
# =============================================================================
print("\n" + "=" * 80)
print("案例1:悬臂梁鲁棒性优化设计")
print("=" * 80)

def case1_cantilever_robust():
    """悬臂梁鲁棒性优化 - 基于泰勒展开法"""
    print("\n【问题描述】")
    print("设计矩形截面悬臂梁,在考虑材料属性和载荷不确定性的情况下,")
    print("最小化重量同时保证挠度约束的鲁棒性满足。")
    
    # 参数设置
    L = 2.0  # 梁长度 (m)
    rho = 7850  # 钢材密度 (kg/m³)
    
    # 不确定性参数
    E_mean = 200e9  # 弹性模量均值 (Pa)
    sigma_E = 20e9  # 弹性模量标准差 (Pa, COV=0.1)
    
    P_mean = 10e3  # 载荷均值 (N)
    sigma_P = 2e3  # 载荷标准差 (N, COV=0.2)
    
    delta_allow = 0.01  # 许用挠度 (m)
    
    # 设计变量: x = [b, h] (宽度和高度)
    
    print(f"\n【参数设置】")
    print(f"  梁长度 L = {L} m")
    print(f"  密度 ρ = {rho} kg/m³")
    print(f"  弹性模量 E ~ N({E_mean/1e9:.0f}, {sigma_E/1e9:.1f}²) GPa")
    print(f"  载荷 P ~ N({P_mean/1e3:.0f}, {sigma_P/1e3:.0f}²) kN")
    print(f"  许用挠度 δ_allow = {delta_allow*1000:.1f} mm")
    
    # 确定性优化(对比基准)
    print(f"\n【确定性优化】")
    
    def objective_det(x):
        b, h = x
        return rho * L * b * h  # 重量
    
    def constraint_det(x):
        b, h = x
        I = b * h**3 / 12  # 惯性矩
        delta = P_mean * L**3 / (3 * E_mean * I)
        return delta_allow - delta
    
    result_det = minimize(
        objective_det,
        [0.05, 0.1],
        method='SLSQP',
        bounds=[(0.01, 0.5), (0.02, 0.5)],
        constraints={'type': 'ineq', 'fun': constraint_det}
    )
    
    b_det, h_det = result_det.x
    weight_det = result_det.fun
    I_det = b_det * h_det**3 / 12
    delta_det = P_mean * L**3 / (3 * E_mean * I_det)
    
    print(f"  最优设计: b={b_det*1000:.2f}mm, h={h_det*1000:.2f}mm")
    print(f"  重量: {weight_det:.2f} kg")
    print(f"  挠度: {delta_det*1000:.2f} mm")
    
    # 鲁棒性优化 - 一阶泰勒展开法
    print(f"\n【鲁棒性优化(一阶泰勒展开法)】")
    
    def compute_robust_metrics(x):
        """计算鲁棒性指标(均值和标准差)"""
        b, h = x
        I = b * h**3 / 12
        
        # 名义挠度
        delta_nom = P_mean * L**3 / (3 * E_mean * I)
        
        # 对E和P的偏导数
        d_delta_dE = -P_mean * L**3 / (3 * E_mean**2 * I)
        d_delta_dP = L**3 / (3 * E_mean * I)
        
        # 挠度的标准差(一阶近似)
        sigma_delta = np.sqrt((d_delta_dE * sigma_E)**2 + (d_delta_dP * sigma_P)**2)
        
        return delta_nom, sigma_delta
    
    def objective_robust(x, w_sigma=1000):
        """鲁棒目标函数:重量 + 权重×挠度标准差"""
        b, h = x
        weight = rho * L * b * h
        _, sigma_delta = compute_robust_metrics(x)
        return weight + w_sigma * sigma_delta
    
    def constraint_robust(x, k=3):
        """鲁棒约束:均值 + k×标准差 ≤ 许用值"""
        delta_mean, sigma_delta = compute_robust_metrics(x)
        return delta_allow - (delta_mean + k * sigma_delta)
    
    # 不同权重下的鲁棒优化
    weights = [0, 500, 1000, 2000]
    results_robust = []
    
    for w in weights:
        result = minimize(
            lambda x: objective_robust(x, w),
            [0.05, 0.1],
            method='SLSQP',
            bounds=[(0.01, 0.5), (0.02, 0.5)],
            constraints={'type': 'ineq', 'fun': constraint_robust}
        )
        
        b, h = result.x
        weight = rho * L * b * h
        delta_mean, sigma_delta = compute_robust_metrics(result.x)
        
        results_robust.append({
            'w': w,
            'b': b,
            'h': h,
            'weight': weight,
            'delta_mean': delta_mean,
            'sigma_delta': sigma_delta,
            'success': result.success
        })
        
        print(f"  权重w={w:4d}: b={b*1000:.2f}mm, h={h*1000:.2f}mm, "
              f"W={weight:.2f}kg, δ_mean={delta_mean*1000:.2f}mm, σ_δ={sigma_delta*1000:.3f}mm")
    
    # 蒙特卡洛验证
    print(f"\n【蒙特卡洛验证(n=10000)】")
    
    np.random.seed(42)
    n_mc = 10000
    
    E_samples = np.random.normal(E_mean, sigma_E, n_mc)
    P_samples = np.random.normal(P_mean, sigma_P, n_mc)
    
    for i, res in enumerate([results_robust[0], results_robust[-1]]):
        b, h = res['b'], res['h']
        I = b * h**3 / 12
        
        delta_samples = P_samples * L**3 / (3 * E_samples * I)
        
        delta_mean_mc = np.mean(delta_samples)
        delta_std_mc = np.std(delta_samples)
        violation_rate = np.sum(delta_samples > delta_allow) / n_mc * 100
        
        label = "确定性设计" if i == 0 else "鲁棒设计"
        print(f"  {label}:")
        print(f"    蒙特卡洛均值: {delta_mean_mc*1000:.2f} mm")
        print(f"    蒙特卡洛标准差: {delta_std_mc*1000:.3f} mm")
        print(f"    约束违反率: {violation_rate:.2f}%")
    
    # ========== 可视化 ==========
    fig = plt.figure(figsize=(16, 12))
    
    # 子图1: 悬臂梁示意图
    ax1 = fig.add_subplot(2, 3, 1)
    
    # 绘制梁(确定性设计)
    y_beam = np.linspace(0, L, 50)
    x_beam = np.zeros_like(y_beam)
    ax1.fill_betweenx(y_beam, x_beam - b_det/2, x_beam + b_det/2, 
                      alpha=0.3, color='blue', label='确定性设计')
    
    # 绘制梁(鲁棒设计)
    b_rob = results_robust[-1]['b']
    h_rob = results_robust[-1]['h']
    ax1.fill_betweenx(y_beam, x_beam - b_rob/2, x_beam + b_rob/2, 
                      alpha=0.3, color='red', label='鲁棒设计')
    
    # 固定端
    ax1.plot([-0.05, 0.05], [0, 0], 'k-', linewidth=3)
    ax1.plot([-0.03, -0.03], [-0.05, 0], 'k-', linewidth=2)
    ax1.plot([0.03, 0.03], [-0.05, 0], 'k-', linewidth=2)
    
    # 载荷箭头
    ax1.arrow(0, L*0.9, 0.1, 0, head_width=0.08, head_length=0.03, 
              fc='green', ec='green', linewidth=2)
    ax1.text(0.15, L*0.9, 'P', fontsize=12, color='green')
    
    ax1.set_xlim(-0.15, 0.25)
    ax1.set_ylim(-0.1, L*1.1)
    ax1.set_aspect('equal')
    ax1.set_xlabel('宽度方向 (m)', fontsize=11)
    ax1.set_ylabel('长度方向 (m)', fontsize=11)
    ax1.set_title('悬臂梁设计对比', fontsize=13, fontweight='bold')
    ax1.legend(fontsize=10)
    ax1.grid(True, alpha=0.3)
    
    # 子图2: 重量-鲁棒性权衡
    ax2 = fig.add_subplot(2, 3, 2)
    
    weights_plot = [r['w'] for r in results_robust]
    weights_val = [r['weight'] for r in results_robust]
    sigmas = [r['sigma_delta']*1000 for r in results_robust]
    
    ax2_twin = ax2.twinx()
    
    line1 = ax2.plot(weights_plot, weights_val, 'b-o', linewidth=2, markersize=8, label='重量')
    line2 = ax2_twin.plot(weights_plot, sigmas, 'r-s', linewidth=2, markersize=8, label='挠度标准差')
    
    ax2.set_xlabel('鲁棒性权重 w', fontsize=11)
    ax2.set_ylabel('重量 (kg)', fontsize=11, color='blue')
    ax2_twin.set_ylabel('挠度标准差 (mm)', fontsize=11, color='red')
    ax2.tick_params(axis='y', labelcolor='blue')
    ax2_twin.tick_params(axis='y', labelcolor='red')
    ax2.set_title('重量-鲁棒性权衡曲线', fontsize=13, fontweight='bold')
    ax2.grid(True, alpha=0.3)
    
    lines = line1 + line2
    labels = [l.get_label() for l in lines]
    ax2.legend(lines, labels, fontsize=10)
    
    # 子图3: 截面尺寸演化
    ax3 = fig.add_subplot(2, 3, 3)
    
    bs = [r['b']*1000 for r in results_robust]
    hs = [r['h']*1000 for r in results_robust]
    
    x_pos = np.arange(len(weights_plot))
    width = 0.35
    
    bars1 = ax3.bar(x_pos - width/2, bs, width, label='宽度 b', color='steelblue', edgecolor='navy')
    bars2 = ax3.bar(x_pos + width/2, hs, width, label='高度 h', color='coral', edgecolor='darkred')
    
    ax3.set_xlabel('鲁棒性权重', fontsize=11)
    ax3.set_ylabel('尺寸 (mm)', fontsize=11)
    ax3.set_title('截面尺寸随权重变化', fontsize=13, fontweight='bold')
    ax3.set_xticks(x_pos)
    ax3.set_xticklabels([f'{w}' for w in weights_plot])
    ax3.legend(fontsize=10)
    ax3.grid(True, alpha=0.3, axis='y')
    
    # 子图4: 挠度分布对比(蒙特卡洛)
    ax4 = fig.add_subplot(2, 3, 4)
    
    # 确定性设计的挠度分布
    I_det = b_det * h_det**3 / 12
    delta_samples_det = P_samples * L**3 / (3 * E_samples * I_det)
    
    # 鲁棒设计的挠度分布
    I_rob = b_rob * h_rob**3 / 12
    delta_samples_rob = P_samples * L**3 / (3 * E_samples * I_rob)
    
    ax4.hist(delta_samples_det*1000, bins=50, alpha=0.5, color='blue', 
             label='确定性设计', density=True)
    ax4.hist(delta_samples_rob*1000, bins=50, alpha=0.5, color='red', 
             label='鲁棒设计', density=True)
    ax4.axvline(x=delta_allow*1000, color='k', linestyle='--', linewidth=2, 
                label=f'许用值={delta_allow*1000:.1f}mm')
    
    ax4.set_xlabel('挠度 (mm)', fontsize=11)
    ax4.set_ylabel('概率密度', fontsize=11)
    ax4.set_title('挠度分布对比(蒙特卡洛)', fontsize=13, fontweight='bold')
    ax4.legend(fontsize=10)
    ax4.grid(True, alpha=0.3)
    
    # 子图5: 灵敏度分析
    ax5 = fig.add_subplot(2, 3, 5)
    
    # 计算不同设计点对E和P的灵敏度
    designs = ['确定性', '鲁棒(w=500)', '鲁棒(w=2000)']
    indices = [0, 1, 3]
    
    sens_E = []
    sens_P = []
    
    for idx in indices:
        b, h = results_robust[idx]['b'], results_robust[idx]['h']
        I = b * h**3 / 12
        
        d_delta_dE = -P_mean * L**3 / (3 * E_mean**2 * I)
        d_delta_dP = L**3 / (3 * E_mean * I)
        
        sens_E.append(abs(d_delta_dE) * sigma_E)
        sens_P.append(abs(d_delta_dP) * sigma_P)
    
    x = np.arange(len(designs))
    width = 0.35
    
    bars1 = ax5.bar(x - width/2, sens_E, width, label='对E的灵敏度', color='skyblue', edgecolor='navy')
    bars2 = ax5.bar(x + width/2, sens_P, width, label='对P的灵敏度', color='lightcoral', edgecolor='darkred')
    
    ax5.set_ylabel('灵敏度系数', fontsize=11)
    ax5.set_title('设计对不确定性的灵敏度', fontsize=13, fontweight='bold')
    ax5.set_xticks(x)
    ax5.set_xticklabels(designs, fontsize=10)
    ax5.legend(fontsize=10)
    ax5.grid(True, alpha=0.3, axis='y')
    
    # 子图6: 约束满足概率
    ax6 = fig.add_subplot(2, 3, 6)
    
    violation_rates = []
    for res in results_robust:
        b, h = res['b'], res['h']
        I = b * h**3 / 12
        delta_samples = P_samples * L**3 / (3 * E_samples * I)
        violation_rate = np.sum(delta_samples > delta_allow) / n_mc * 100
        violation_rates.append(violation_rate)
    
    colors = ['green' if v < 1 else 'orange' if v < 5 else 'red' for v in violation_rates]
    bars = ax6.bar(range(len(weights_plot)), violation_rates, color=colors, edgecolor='black')
    
    ax6.axhline(y=1, color='r', linestyle='--', linewidth=2, label='1%阈值')
    ax6.set_xlabel('鲁棒性权重', fontsize=11)
    ax6.set_ylabel('约束违反率 (%)', fontsize=11)
    ax6.set_title('约束满足概率分析', fontsize=13, fontweight='bold')
    ax6.set_xticks(range(len(weights_plot)))
    ax6.set_xticklabels([f'{w}' for w in weights_plot])
    ax6.legend(fontsize=10)
    ax6.grid(True, alpha=0.3, axis='y')
    
    # 添加数值标签
    for i, (bar, rate) in enumerate(zip(bars, violation_rates)):
        ax6.text(bar.get_x() + bar.get_width()/2., bar.get_height() + 0.1,
                f'{rate:.2f}%', ha='center', fontsize=9)
    
    plt.suptitle('案例1:悬臂梁鲁棒性优化设计', fontsize=14, fontweight='bold', y=0.98)
    plt.tight_layout(rect=[0, 0, 1, 0.96])
    
    output_file = os.path.join(output_dir, 'case1_cantilever_robust.png')
    plt.savefig(output_file, dpi=150, bbox_inches='tight', facecolor='white')
    print(f"\n✓ 图像已保存: {output_file}")
    plt.close()
    
    return results_robust

results_case1 = case1_cantilever_robust()


# =============================================================================
# 案例2:多目标鲁棒性优化(Pareto前沿)
# =============================================================================
print("\n" + "=" * 80)
print("案例2:多目标鲁棒性优化")
print("=" * 80)

def case2_multiobjective_robust():
    """多目标鲁棒性优化 - 生成Pareto前沿"""
    print("\n【问题描述】")
    print("通过多目标优化方法,同时优化性能和鲁棒性,")
    print("生成Pareto前沿,展示性能与鲁棒性的权衡关系。")
    
    # 问题设置:一个简单的数学优化问题
    # 目标1: f1 = x1^2 + x2^2 (性能,越小越好)
    # 目标2: f2 = 不确定性引起的方差 (鲁棒性,越小越好)
    # 约束: g = x1 + x2 >= 1
    
    # 不确定性:目标函数系数存在不确定性
    # f_actual = (c1 + δc1)*x1^2 + (c2 + δc2)*x2^2
    # 其中 δc1 ~ N(0, σc1^2), δc2 ~ N(0, σc2^2)
    
    c1, c2 = 1.0, 1.0
    sigma_c1, sigma_c2 = 0.2, 0.2
    
    print(f"\n【参数设置】")
    print(f"  名义系数: c1={c1}, c2={c2}")
    print(f"  不确定性: σc1={sigma_c1}, σc2={sigma_c2}")
    print(f"  约束: x1 + x2 >= 1")
    
    # 使用加权和方法生成Pareto前沿
    print(f"\n【生成Pareto前沿】")
    
    n_points = 50
    weights_f1 = np.linspace(0, 1, n_points)
    
    pareto_front = []
    
    for w1 in weights_f1:
        w2 = 1 - w1
        
        def objective(x):
            x1, x2 = x
            # 名义性能
            f1 = c1 * x1**2 + c2 * x2**2
            # 方差(鲁棒性度量)- 一阶近似
            # Var(f) ≈ (∂f/∂c1)^2 * σc1^2 + (∂f/∂c2)^2 * σc2^2
            # ∂f/∂c1 = x1^2, ∂f/∂c2 = x2^2
            f2 = (x1**2)**2 * sigma_c1**2 + (x2**2)**2 * sigma_c2**2
            return w1 * f1 + w2 * f2
        
        def constraint(x):
            return x[0] + x[1] - 1  # >= 0
        
        result = minimize(
            objective,
            [0.5, 0.5],
            method='SLSQP',
            bounds=[(0, 2), (0, 2)],
            constraints={'type': 'ineq', 'fun': constraint}
        )
        
        if result.success:
            x1, x2 = result.x
            f1 = c1 * x1**2 + c2 * x2**2
            f2 = (x1**2)**2 * sigma_c1**2 + (x2**2)**2 * sigma_c2**2
            pareto_front.append({'x': [x1, x2], 'f1': f1, 'f2': f2, 'w1': w1})
    
    print(f"  Pareto前沿点数: {len(pareto_front)}")
    
    # 提取Pareto前沿数据
    f1_values = [p['f1'] for p in pareto_front]
    f2_values = [p['f2'] for p in pareto_front]
    
    # 蒙特卡洛验证
    print(f"\n【蒙特卡洛验证】")
    
    np.random.seed(42)
    n_mc = 5000
    
    # 选择几个代表性的Pareto解进行验证
    indices = [0, len(pareto_front)//4, len(pareto_front)//2, 3*len(pareto_front)//4, len(pareto_front)-1]
    
    mc_results = []
    for idx in indices:
        p = pareto_front[idx]
        x1, x2 = p['x']
        
        # 生成不确定性样本
        c1_samples = np.random.normal(c1, sigma_c1, n_mc)
        c2_samples = np.random.normal(c2, sigma_c2, n_mc)
        
        # 计算实际目标函数值
        f_samples = c1_samples * x1**2 + c2_samples * x2**2
        
        mc_results.append({
            'idx': idx,
            'x': [x1, x2],
            'f1_nominal': p['f1'],
            'f1_mean_mc': np.mean(f_samples),
            'f1_std_mc': np.std(f_samples),
            'f2_analytical': p['f2']
        })
    
    for r in mc_results:
        print(f"  解 {r['idx']}: x=({r['x'][0]:.3f}, {r['x'][1]:.3f}), "
              f"f1_nominal={r['f1_nominal']:.4f}, f1_mean_mc={r['f1_mean_mc']:.4f}, "
              f"f1_std={r['f1_std_mc']:.4f}")
    
    # ========== 可视化 ==========
    fig = plt.figure(figsize=(16, 10))
    
    # 子图1: Pareto前沿(目标空间)
    ax1 = fig.add_subplot(2, 3, 1)
    
    scatter = ax1.scatter(f1_values, f2_values, c=weights_f1, cmap='viridis', 
                         s=50, edgecolors='black', linewidth=0.5)
    plt.colorbar(scatter, ax=ax1, label='性能权重 w₁')
    
    # 标记极端点
    ax1.scatter(f1_values[0], f2_values[0], c='red', s=200, marker='*', 
               label='最鲁棒', zorder=5)
    ax1.scatter(f1_values[-1], f2_values[-1], c='blue', s=200, marker='*', 
               label='最优性能', zorder=5)
    
    ax1.set_xlabel('性能目标 f₁', fontsize=11)
    ax1.set_ylabel('方差目标 f₂', fontsize=11)
    ax1.set_title('Pareto前沿(目标空间)', fontsize=13, fontweight='bold')
    ax1.legend(fontsize=10)
    ax1.grid(True, alpha=0.3)
    
    # 子图2: 设计空间中的Pareto解
    ax2 = fig.add_subplot(2, 3, 2)
    
    x1_values = [p['x'][0] for p in pareto_front]
    x2_values = [p['x'][1] for p in pareto_front]
    
    scatter2 = ax2.scatter(x1_values, x2_values, c=weights_f1, cmap='viridis', 
                          s=50, edgecolors='black', linewidth=0.5)
    plt.colorbar(scatter2, ax=ax2, label='性能权重 w₁')
    
    # 绘制约束边界
    x1_boundary = np.linspace(0, 2, 100)
    x2_boundary = 1 - x1_boundary
    ax2.plot(x1_boundary, x2_boundary, 'r--', linewidth=2, label='约束边界')
    ax2.fill_between(x1_boundary, 0, x2_boundary, alpha=0.2, color='red', label='不可行域')
    
    ax2.set_xlabel('x₁', fontsize=11)
    ax2.set_ylabel('x₂', fontsize=11)
    ax2.set_title('Pareto解(设计空间)', fontsize=13, fontweight='bold')
    ax2.set_xlim(0, 2)
    ax2.set_ylim(0, 2)
    ax2.legend(fontsize=10)
    ax2.grid(True, alpha=0.3)
    
    # 子图3: 权重-目标值关系
    ax3 = fig.add_subplot(2, 3, 3)
    
    ax3.plot(weights_f1, f1_values, 'b-', linewidth=2, label='性能 f₁')
    ax3_twin = ax3.twinx()
    ax3_twin.plot(weights_f1, f2_values, 'r-', linewidth=2, label='方差 f₂')
    
    ax3.set_xlabel('性能权重 w₁', fontsize=11)
    ax3.set_ylabel('性能 f₁', fontsize=11, color='blue')
    ax3_twin.set_ylabel('方差 f₂', fontsize=11, color='red')
    ax3.tick_params(axis='y', labelcolor='blue')
    ax3_twin.tick_params(axis='y', labelcolor='red')
    ax3.set_title('权重与目标值关系', fontsize=13, fontweight='bold')
    ax3.grid(True, alpha=0.3)
    
    # 子图4: 蒙特卡洛验证对比
    ax4 = fig.add_subplot(2, 3, 4)
    
    indices_plot = [r['idx'] for r in mc_results]
    f1_nominal_plot = [r['f1_nominal'] for r in mc_results]
    f1_mean_mc_plot = [r['f1_mean_mc'] for r in mc_results]
    
    x = np.arange(len(indices_plot))
    width = 0.35
    
    bars1 = ax4.bar(x - width/2, f1_nominal_plot, width, label='名义值', 
                    color='steelblue', edgecolor='navy')
    bars2 = ax4.bar(x + width/2, f1_mean_mc_plot, width, label='蒙特卡洛均值', 
                    color='coral', edgecolor='darkred')
    
    ax4.set_xlabel('Pareto解编号', fontsize=11)
    ax4.set_ylabel('性能 f₁', fontsize=11)
    ax4.set_title('名义值与蒙特卡洛验证对比', fontsize=13, fontweight='bold')
    ax4.set_xticks(x)
    ax4.set_xticklabels([f'{i}' for i in indices_plot])
    ax4.legend(fontsize=10)
    ax4.grid(True, alpha=0.3, axis='y')
    
    # 子图5: 不确定性影响可视化
    ax5 = fig.add_subplot(2, 3, 5)
    
    # 选择两个极端解进行比较
    p_robust = pareto_front[0]  # 最鲁棒
    p_performance = pareto_front[-1]  # 最优性能
    
    # 生成大量样本
    c1_samples = np.random.normal(c1, sigma_c1, 10000)
    c2_samples = np.random.normal(c2, sigma_c2, 10000)
    
    f_robust_samples = c1_samples * p_robust['x'][0]**2 + c2_samples * p_robust['x'][1]**2
    f_perf_samples = c1_samples * p_performance['x'][0]**2 + c2_samples * p_performance['x'][1]**2
    
    ax5.hist(f_robust_samples, bins=50, alpha=0.5, color='green', 
             label=f'鲁棒解 (σ={np.std(f_robust_samples):.3f})', density=True)
    ax5.hist(f_perf_samples, bins=50, alpha=0.5, color='orange', 
             label=f'性能解 (σ={np.std(f_perf_samples):.3f})', density=True)
    
    ax5.axvline(x=p_robust['f1'], color='green', linestyle='--', linewidth=2)
    ax5.axvline(x=p_performance['f1'], color='orange', linestyle='--', linewidth=2)
    
    ax5.set_xlabel('实际性能值', fontsize=11)
    ax5.set_ylabel('概率密度', fontsize=11)
    ax5.set_title('不确定性下的性能分布', fontsize=13, fontweight='bold')
    ax5.legend(fontsize=10)
    ax5.grid(True, alpha=0.3)
    
    # 子图6: 鲁棒性指标对比
    ax6 = fig.add_subplot(2, 3, 6)
    
    f1_stds = [r['f1_std_mc'] for r in mc_results]
    f2_analytical = [r['f2_analytical'] for r in mc_results]
    
    ax6.scatter(f2_analytical, f1_stds, c='blue', s=100, edgecolors='black', linewidth=2)
    
    # 绘制y=x参考线
    max_val = max(max(f2_analytical), max(f1_stds))
    ax6.plot([0, max_val], [0, max_val], 'r--', linewidth=2, label='y=x')
    
    ax6.set_xlabel('解析方差 f₂', fontsize=11)
    ax6.set_ylabel('蒙特卡洛标准差', fontsize=11)
    ax6.set_title('鲁棒性指标验证', fontsize=13, fontweight='bold')
    ax6.legend(fontsize=10)
    ax6.grid(True, alpha=0.3)
    
    plt.suptitle('案例2:多目标鲁棒性优化', fontsize=14, fontweight='bold', y=0.98)
    plt.tight_layout(rect=[0, 0, 1, 0.96])
    
    output_file = os.path.join(output_dir, 'case2_multiobjective_robust.png')
    plt.savefig(output_file, dpi=150, bbox_inches='tight', facecolor='white')
    print(f"\n✓ 图像已保存: {output_file}")
    plt.close()
    
    return pareto_front

pareto_front = case2_multiobjective_robust()


# =============================================================================
# 案例3:最坏情况优化与保守性分析
# =============================================================================
print("\n" + "=" * 80)
print("案例3:最坏情况优化与保守性分析")
print("=" * 80)

def case3_worst_case_optimization():
    """最坏情况优化 - 区间不确定性分析"""
    print("\n【问题描述】")
    print("考虑区间不确定性,使用最坏情况优化方法,")
    print("分析不同保守性水平对优化结果的影响。")
    
    # 问题设置:桁架结构优化
    # 设计变量:杆件截面积 A1, A2
    # 不确定性:载荷 P 在 [P_nom - ΔP, P_nom + ΔP] 范围内变化
    
    L1, L2 = 1.0, 1.0  # 杆件长度 (m)
    rho = 7850  # 密度 (kg/m³)
    E = 200e9  # 弹性模量 (Pa)
    sigma_allow = 250e6  # 许用应力 (Pa)
    
    P_nom = 100e3  # 名义载荷 (N)
    Delta_P = 20e3  # 载荷不确定范围 (N)
    
    print(f"\n【参数设置】")
    print(f"  杆件1长度 L1 = {L1} m")
    print(f"  杆件2长度 L2 = {L2} m")
    print(f"  密度 ρ = {rho} kg/m³")
    print(f"  弹性模量 E = {E/1e9:.0f} GPa")
    print(f"  许用应力 σ_allow = {sigma_allow/1e6:.0f} MPa")
    print(f"  名义载荷 P = {P_nom/1e3:.0f} ± {Delta_P/1e3:.0f} kN")
    
    # 两杆桁架结构
    # 杆件1:与水平方向夹角 θ1 = 60°
    # 杆件2:与水平方向夹角 θ2 = 45°
    theta1 = np.radians(60)
    theta2 = np.radians(45)
    
    # 确定性优化
    print(f"\n【确定性优化】")
    
    def weight_det(A):
        A1, A2 = A
        return rho * (A1 * L1 + A2 * L2)
    
    def stress_constraint_det(A):
        A1, A2 = A
        # 节点平衡方程求解内力
        N2 = P_nom / (np.sin(theta1) * np.cos(theta2)/np.cos(theta1) + np.sin(theta2))
        N1 = N2 * np.cos(theta2) / np.cos(theta1)
        
        sigma1 = abs(N1) / A1
        sigma2 = abs(N2) / A2
        
        return min(sigma_allow - sigma1, sigma_allow - sigma2)
    
    result_det = minimize(
        weight_det,
        [1e-4, 1e-4],
        method='SLSQP',
        bounds=[(1e-5, 1e-2), (1e-5, 1e-2)],
        constraints={'type': 'ineq', 'fun': stress_constraint_det}
    )
    
    A1_det, A2_det = result_det.x
    weight_det_val = result_det.fun
    
    print(f"  最优设计: A1={A1_det*1e4:.2f} cm², A2={A2_det*1e4:.2f} cm²")
    print(f"  重量: {weight_det_val:.2f} kg")
    
    # 最坏情况优化(不同保守性水平)
    print(f"\n【最坏情况优化】")
    
    gammas = [0.0, 0.25, 0.5, 0.75, 1.0]
    results_wc = []
    
    for gamma in gammas:
        # 有效不确定范围
        Delta_P_eff = gamma * Delta_P
        P_min = P_nom - Delta_P_eff
        P_max = P_nom + Delta_P_eff
        
        def worst_case_constraint(A):
            A1, A2 = A
            # 最坏情况:最大载荷
            P_wc = P_max
            
            N2 = P_wc / (np.sin(theta1) * np.cos(theta2)/np.cos(theta1) + np.sin(theta2))
            N1 = N2 * np.cos(theta2) / np.cos(theta1)
            
            sigma1 = abs(N1) / A1
            sigma2 = abs(N2) / A2
            
            return min(sigma_allow - sigma1, sigma_allow - sigma2)
        
        result = minimize(
            weight_det,
            [1e-4, 1e-4],
            method='SLSQP',
            bounds=[(1e-5, 1e-2), (1e-5, 1e-2)],
            constraints={'type': 'ineq', 'fun': worst_case_constraint}
        )
        
        if result.success:
            A1, A2 = result.x
            weight_val = result.fun
            
            # 计算在最坏情况下的应力
            N2_wc = P_max / (np.sin(theta1) * np.cos(theta2)/np.cos(theta1) + np.sin(theta2))
            N1_wc = N2_wc * np.cos(theta2) / np.cos(theta1)
            sigma1_wc = abs(N1_wc) / A1
            sigma2_wc = abs(N2_wc) / A2
            
            results_wc.append({
                'gamma': gamma,
                'A1': A1,
                'A2': A2,
                'weight': weight_val,
                'sigma1_wc': sigma1_wc,
                'sigma2_wc': sigma2_wc
            })
            
            print(f"  γ={gamma:.2f}: A1={A1*1e4:.2f}cm², A2={A2*1e4:.2f}cm², "
                  f"W={weight_val:.2f}kg, σ_max={max(sigma1_wc, sigma2_wc)/1e6:.1f}MPa")
    
    # ========== 可视化 ==========
    fig = plt.figure(figsize=(16, 10))
    
    # 子图1: 桁架结构示意图
    ax1 = fig.add_subplot(2, 3, 1)
    
    # 绘制桁架(确定性设计)
    # 节点坐标
    node0 = np.array([0, 0])  # 固定节点
    node1 = np.array([L1 * np.cos(theta1), L1 * np.sin(theta1)])  # 连接节点
    node2 = np.array([L2 * np.cos(theta2), -L2 * np.sin(theta2)])  # 另一个固定节点
    
    # 绘制杆件
    ax1.plot([node0[0], node1[0]], [node0[1], node1[1]], 'b-', linewidth=8, alpha=0.5, label='杆件1')
    ax1.plot([node0[0], node2[0]], [node0[1], node2[1]], 'r-', linewidth=8, alpha=0.5, label='杆件2')
    
    # 绘制节点
    ax1.plot(*node0, 'ko', markersize=15)
    ax1.plot(*node1, 'go', markersize=12)
    ax1.plot(*node2, 'ko', markersize=15)
    
    # 绘制支座
    ax1.plot([node1[0]-0.1, node1[0]+0.1], [node1[1], node1[1]], 'k-', linewidth=3)
    
    # 载荷箭头
    ax1.arrow(node0[0], node0[1]+0.1, 0, -0.15, head_width=0.05, head_length=0.05, 
              fc='green', ec='green', linewidth=2)
    ax1.text(node0[0]+0.1, node0[1]-0.1, 'P', fontsize=12, color='green')
    
    ax1.set_xlim(-0.3, 1.2)
    ax1.set_ylim(-0.5, 1.0)
    ax1.set_aspect('equal')
    ax1.set_xlabel('x (m)', fontsize=11)
    ax1.set_ylabel('y (m)', fontsize=11)
    ax1.set_title('两杆桁架结构', fontsize=13, fontweight='bold')
    ax1.legend(fontsize=10)
    ax1.grid(True, alpha=0.3)
    
    # 子图2: 保守性-重量关系
    ax2 = fig.add_subplot(2, 3, 2)
    
    gammas_plot = [r['gamma'] for r in results_wc]
    weights_plot = [r['weight'] for r in results_wc]
    
    ax2.plot(gammas_plot, weights_plot, 'b-o', linewidth=2, markersize=8)
    ax2.fill_between(gammas_plot, weights_plot, alpha=0.3, color='blue')
    
    ax2.set_xlabel('保守性参数 γ', fontsize=11)
    ax2.set_ylabel('重量 (kg)', fontsize=11)
    ax2.set_title('保守性-重量权衡', fontsize=13, fontweight='bold')
    ax2.grid(True, alpha=0.3)
    
    # 添加数值标签
    for g, w in zip(gammas_plot, weights_plot):
        ax2.text(g, w+0.5, f'{w:.2f}', ha='center', fontsize=9)
    
    # 子图3: 截面积随保守性变化
    ax3 = fig.add_subplot(2, 3, 3)
    
    A1s = [r['A1']*1e4 for r in results_wc]
    A2s = [r['A2']*1e4 for r in results_wc]
    
    x = np.arange(len(gammas_plot))
    width = 0.35
    
    bars1 = ax3.bar(x - width/2, A1s, width, label='A1', color='steelblue', edgecolor='navy')
    bars2 = ax3.bar(x + width/2, A2s, width, label='A2', color='coral', edgecolor='darkred')
    
    ax3.set_xlabel('保守性参数 γ', fontsize=11)
    ax3.set_ylabel('截面积 (cm²)', fontsize=11)
    ax3.set_title('截面积随保守性变化', fontsize=13, fontweight='bold')
    ax3.set_xticks(x)
    ax3.set_xticklabels([f'{g:.2f}' for g in gammas_plot])
    ax3.legend(fontsize=10)
    ax3.grid(True, alpha=0.3, axis='y')
    
    # 子图4: 最坏情况应力分析
    ax4 = fig.add_subplot(2, 3, 4)
    
    sigma1s = [r['sigma1_wc']/1e6 for r in results_wc]
    sigma2s = [r['sigma2_wc']/1e6 for r in results_wc]
    
    ax4.plot(gammas_plot, sigma1s, 'b-o', linewidth=2, markersize=8, label='杆件1应力')
    ax4.plot(gammas_plot, sigma2s, 'r-s', linewidth=2, markersize=8, label='杆件2应力')
    ax4.axhline(y=sigma_allow/1e6, color='k', linestyle='--', linewidth=2, label='许用应力')
    
    ax4.set_xlabel('保守性参数 γ', fontsize=11)
    ax4.set_ylabel('应力 (MPa)', fontsize=11)
    ax4.set_title('最坏情况应力分析', fontsize=13, fontweight='bold')
    ax4.legend(fontsize=10)
    ax4.grid(True, alpha=0.3)
    
    # 子图5: 载荷不确定性影响
    ax5 = fig.add_subplot(2, 3, 5)
    
    # 在不同保守性水平下,分析载荷变化对重量的影响
    P_range = np.linspace(P_nom - Delta_P, P_nom + Delta_P, 100)
    
    # 安全地选择要显示的结果
    n_results = len(results_wc)
    if n_results >= 3:
        selected_indices = [0, n_results // 2, n_results - 1]
    elif n_results >= 2:
        selected_indices = [0, n_results - 1]
    else:
        selected_indices = [0]
    
    colors = ['blue', 'green', 'red']
    for i, idx in enumerate(selected_indices):
        res = results_wc[idx]
        A1, A2 = res['A1'], res['A2']
        
        stresses = []
        for P in P_range:
            N2 = P / (np.sin(theta1) * np.cos(theta2)/np.cos(theta1) + np.sin(theta2))
            N1 = N2 * np.cos(theta2) / np.cos(theta1)
            sigma1 = abs(N1) / A1
            sigma2 = abs(N2) / A2
            stresses.append(max(sigma1, sigma2) / 1e6)
        
        label = f"γ={res['gamma']:.1f}"
        color = colors[i % len(colors)]
        ax5.plot(P_range/1e3, stresses, color=color, linewidth=2, label=label)
    
    ax5.axhline(y=sigma_allow/1e6, color='k', linestyle='--', linewidth=2, label='许用应力')
    ax5.fill_betweenx([0, sigma_allow/1e6*1.2], P_nom/1e3 - Delta_P/1e3, P_nom/1e3 + Delta_P/1e3, 
                      alpha=0.2, color='gray', label='不确定范围')
    
    ax5.set_xlabel('载荷 (kN)', fontsize=11)
    ax5.set_ylabel('最大应力 (MPa)', fontsize=11)
    ax5.set_title('载荷-应力关系', fontsize=13, fontweight='bold')
    ax5.legend(fontsize=10)
    ax5.grid(True, alpha=0.3)
    
    # 子图6: 安全裕度分析
    ax6 = fig.add_subplot(2, 3, 6)
    
    safety_margins = []
    for res in results_wc:
        sigma_max = max(res['sigma1_wc'], res['sigma2_wc'])
        margin = (sigma_allow - sigma_max) / sigma_allow * 100
        safety_margins.append(margin)
    
    colors = ['red' if m < 0 else 'orange' if m < 5 else 'green' for m in safety_margins]
    bars = ax6.bar(range(len(gammas_plot)), safety_margins, color=colors, edgecolor='black')
    
    ax6.axhline(y=0, color='k', linestyle='-', linewidth=1)
    ax6.set_xlabel('保守性参数 γ', fontsize=11)
    ax6.set_ylabel('安全裕度 (%)', fontsize=11)
    ax6.set_title('安全裕度分析', fontsize=13, fontweight='bold')
    ax6.set_xticks(range(len(gammas_plot)))
    ax6.set_xticklabels([f'{g:.2f}' for g in gammas_plot])
    ax6.grid(True, alpha=0.3, axis='y')
    
    # 添加数值标签
    for i, (bar, margin) in enumerate(zip(bars, safety_margins)):
        ax6.text(bar.get_x() + bar.get_width()/2., bar.get_height() + (1 if margin >= 0 else -3),
                f'{margin:.1f}%', ha='center', fontsize=9)
    
    plt.suptitle('案例3:最坏情况优化与保守性分析', fontsize=14, fontweight='bold', y=0.98)
    plt.tight_layout(rect=[0, 0, 1, 0.96])
    
    output_file = os.path.join(output_dir, 'case3_worst_case.png')
    plt.savefig(output_file, dpi=150, bbox_inches='tight', facecolor='white')
    print(f"\n✓ 图像已保存: {output_file}")
    plt.close()
    
    return results_wc

results_case3 = case3_worst_case_optimization()


# =============================================================================
# 案例4:基于抽样的鲁棒性优化对比
# =============================================================================
print("\n" + "=" * 80)
print("案例4:基于抽样的鲁棒性优化对比")
print("=" * 80)

def case4_sampling_based_robust():
    """基于抽样的鲁棒性优化 - 不同抽样方法对比"""
    print("\n【问题描述】")
    print("比较不同抽样方法(蒙特卡洛、LHS、Sobol序列)")
    print("在鲁棒性优化中的效率和精度。")
    
    # 问题设置:悬臂梁优化
    L = 2.0
    rho = 7850
    sigma_allow = 250e6
    
    # 不确定性参数
    E_mean, sigma_E = 200e9, 20e9
    P_mean, sigma_P = 10e3, 2e3
    
    print(f"\n【参数设置】")
    print(f"  梁长度 L = {L} m")
    print(f"  弹性模量 E ~ N({E_mean/1e9:.0f}, {sigma_E/1e9:.1f}²) GPa")
    print(f"  载荷 P ~ N({P_mean/1e3:.0f}, {sigma_P/1e3:.0f}²) kN")
    print(f"  许用应力 σ_allow = {sigma_allow/1e6:.0f} MPa")
    
    # 定义抽样方法
    def lhs_sampling(n, E_mean, sigma_E, P_mean, sigma_P):
        """拉丁超立方抽样"""
        # 生成LHS样本
        u1 = np.random.rand(n)
        u2 = np.random.rand(n)
        
        # 分层
        strata1 = np.floor(u1 * n).astype(int)
        strata2 = np.floor(u2 * n).astype(int)
        
        # 在层内随机化
        v1 = (strata1 + np.random.rand(n)) / n
        v2 = (strata2 + np.random.rand(n)) / n
        
        # 转换为正态分布
        E_samples = norm.ppf(v1, E_mean, sigma_E)
        P_samples = norm.ppf(v2, P_mean, sigma_P)
        
        return E_samples, P_samples
    
    def sobol_sampling(n, E_mean, sigma_E, P_mean, sigma_P):
        """Sobol序列抽样(简化实现)"""
        # 使用简单的低差异序列近似
        i = np.arange(1, n+1)
        
        # Van der Corput序列
        def van_der_corput(n, base=2):
            result = np.zeros(n)
            for i in range(n):
                n_temp = i + 1
                f = 1.0
                r = 0.0
                while n_temp > 0:
                    f = f / base
                    r = r + f * (n_temp % base)
                    n_temp = n_temp // base
                result[i] = r
            return result
        
        v1 = van_der_corput(n, 2)
        v2 = van_der_corput(n, 3)
        
        # 转换为正态分布
        E_samples = norm.ppf(v1, E_mean, sigma_E)
        P_samples = norm.ppf(v2, P_mean, sigma_P)
        
        return E_samples, P_samples
    
    # 计算应力的函数
    def compute_stress(b, h, E, P):
        I = b * h**3 / 12
        M_max = P * L
        sigma = M_max * (h/2) / I
        return sigma
    
    def weight(b, h):
        return rho * L * b * h
    
    # 不同抽样方法对比
    print(f"\n【抽样方法对比】")
    
    sampling_methods = {
        '蒙特卡洛': lambda n: (np.random.normal(E_mean, sigma_E, n),
                                np.random.normal(P_mean, sigma_P, n)),
        'LHS': lambda n: lhs_sampling(n, E_mean, sigma_E, P_mean, sigma_P),
        'Sobol': lambda n: sobol_sampling(n, E_mean, sigma_E, P_mean, sigma_P)
    }
    
    # 不同样本量
    sample_sizes = [50, 100, 200, 500, 1000]
    
    results_comparison = {}
    
    for method_name, sampler in sampling_methods.items():
        print(f"\n【{method_name}方法】")
        
        method_results = []
        
        for n in sample_sizes:
            # 生成样本
            E_samples, P_samples = sampler(n)
            
            # 定义鲁棒目标函数(使用样本估计)
            def robust_objective(x):
                b, h = x
                stresses = [compute_stress(b, h, E, P) for E, P in zip(E_samples, P_samples)]
                mean_stress = np.mean(stresses)
                std_stress = np.std(stresses)
                weight_val = weight(b, h)
                # 鲁棒目标:重量 + 惩罚项
                penalty = max(0, mean_stress + 3*std_stress - sigma_allow) * 1000
                return weight_val + penalty
            
            # 优化
            result = minimize(
                robust_objective,
                [0.05, 0.1],
                method='SLSQP',
                bounds=[(0.01, 0.5), (0.02, 0.5)]
            )
            
            b, h = result.x
            w_val = weight(b, h)
            
            # 用大量样本验证
            E_val = np.random.normal(E_mean, sigma_E, 10000)
            P_val = np.random.normal(P_mean, sigma_P, 10000)
            stresses_val = [compute_stress(b, h, E, P) for E, P in zip(E_val, P_val)]
            mean_stress_val = np.mean(stresses_val)
            std_stress_val = np.std(stresses_val)
            violation_rate = np.sum(np.array(stresses_val) > sigma_allow) / 10000 * 100
            
            method_results.append({
                'n': n,
                'b': b,
                'h': h,
                'weight': w_val,
                'mean_stress': mean_stress_val,
                'std_stress': std_stress_val,
                'violation_rate': violation_rate
            })
            
            print(f"  n={n:4d}: b={b*1000:.2f}mm, h={h*1000:.2f}mm, "
                  f"W={w_val:.2f}kg, σ_mean={mean_stress_val/1e6:.1f}MPa, "
                  f"违反率={violation_rate:.2f}%")
        
        results_comparison[method_name] = method_results
    
    # ========== 可视化 ==========
    fig = plt.figure(figsize=(16, 12))
    
    # 子图1: 重量收敛性对比
    ax1 = fig.add_subplot(2, 3, 1)
    
    for method_name, results in results_comparison.items():
        ns = [r['n'] for r in results]
        weights = [r['weight'] for r in results]
        ax1.plot(ns, weights, 'o-', linewidth=2, markersize=8, label=method_name)
    
    ax1.set_xlabel('样本量 n', fontsize=11)
    ax1.set_ylabel('重量 (kg)', fontsize=11)
    ax1.set_title('重量收敛性对比', fontsize=13, fontweight='bold')
    ax1.set_xscale('log')
    ax1.legend(fontsize=10)
    ax1.grid(True, alpha=0.3)
    
    # 子图2: 约束违反率对比
    ax2 = fig.add_subplot(2, 3, 2)
    
    for method_name, results in results_comparison.items():
        ns = [r['n'] for r in results]
        violations = [r['violation_rate'] for r in results]
        ax2.plot(ns, violations, 's-', linewidth=2, markersize=8, label=method_name)
    
    ax2.axhline(y=1, color='r', linestyle='--', linewidth=2, label='1%阈值')
    ax2.set_xlabel('样本量 n', fontsize=11)
    ax2.set_ylabel('约束违反率 (%)', fontsize=11)
    ax2.set_title('约束违反率对比', fontsize=13, fontweight='bold')
    ax2.set_xscale('log')
    ax2.legend(fontsize=10)
    ax2.grid(True, alpha=0.3)
    
    # 子图3: 应力均值对比
    ax3 = fig.add_subplot(2, 3, 3)
    
    for method_name, results in results_comparison.items():
        ns = [r['n'] for r in results]
        mean_stresses = [r['mean_stress']/1e6 for r in results]
        ax3.plot(ns, mean_stresses, '^-', linewidth=2, markersize=8, label=method_name)
    
    ax3.axhline(y=sigma_allow/1e6, color='k', linestyle='--', linewidth=2, label='许用应力')
    ax3.set_xlabel('样本量 n', fontsize=11)
    ax3.set_ylabel('应力均值 (MPa)', fontsize=11)
    ax3.set_title('应力均值收敛性', fontsize=13, fontweight='bold')
    ax3.set_xscale('log')
    ax3.legend(fontsize=10)
    ax3.grid(True, alpha=0.3)
    
    # 子图4: 截面尺寸对比(n=1000时)
    ax4 = fig.add_subplot(2, 3, 4)
    
    methods = list(results_comparison.keys())
    bs = [results_comparison[m][-1]['b']*1000 for m in methods]
    hs = [results_comparison[m][-1]['h']*1000 for m in methods]
    
    x = np.arange(len(methods))
    width = 0.35
    
    bars1 = ax4.bar(x - width/2, bs, width, label='宽度 b', color='steelblue', edgecolor='navy')
    bars2 = ax4.bar(x + width/2, hs, width, label='高度 h', color='coral', edgecolor='darkred')
    
    ax4.set_ylabel('尺寸 (mm)', fontsize=11)
    ax4.set_title('截面尺寸对比(n=1000)', fontsize=13, fontweight='bold')
    ax4.set_xticks(x)
    ax4.set_xticklabels(methods)
    ax4.legend(fontsize=10)
    ax4.grid(True, alpha=0.3, axis='y')
    
    # 添加数值标签
    for i, (b, h) in enumerate(zip(bs, hs)):
        ax4.text(i - width/2, b + 1, f'{b:.1f}', ha='center', fontsize=9)
        ax4.text(i + width/2, h + 1, f'{h:.1f}', ha='center', fontsize=9)
    
    # 子图5: 样本分布可视化(2D散点图)
    ax5 = fig.add_subplot(2, 3, 5)
    
    np.random.seed(42)
    n_plot = 100
    
    # 蒙特卡洛
    E_mc = np.random.normal(E_mean, sigma_E, n_plot)
    P_mc = np.random.normal(P_mean, sigma_P, n_plot)
    ax5.scatter(E_mc/1e9, P_mc/1e3, alpha=0.5, s=30, label='蒙特卡洛', color='blue')
    
    # LHS
    E_lhs, P_lhs = lhs_sampling(n_plot, E_mean, sigma_E, P_mean, sigma_P)
    ax5.scatter(E_lhs/1e9, P_lhs/1e3, alpha=0.5, s=30, label='LHS', color='red')
    
    ax5.set_xlabel('弹性模量 E (GPa)', fontsize=11)
    ax5.set_ylabel('载荷 P (kN)', fontsize=11)
    ax5.set_title('样本分布对比(n=100)', fontsize=13, fontweight='bold')
    ax5.legend(fontsize=10)
    ax5.grid(True, alpha=0.3)
    
    # 子图6: 效率-精度权衡
    ax6 = fig.add_subplot(2, 3, 6)
    
    # 计算收敛速度(以n=1000为参考)
    ref_weight = results_comparison['蒙特卡洛'][-1]['weight']
    
    for method_name, results in results_comparison.items():
        ns = [r['n'] for r in results]
        errors = [abs(r['weight'] - ref_weight) / ref_weight * 100 for r in results]
        ax6.plot(ns, errors, 'o-', linewidth=2, markersize=8, label=method_name)
    
    ax6.set_xlabel('样本量 n', fontsize=11)
    ax6.set_ylabel('相对误差 (%)', fontsize=11)
    ax6.set_title('收敛速度对比', fontsize=13, fontweight='bold')
    ax6.set_xscale('log')
    ax6.set_yscale('log')
    ax6.legend(fontsize=10)
    ax6.grid(True, alpha=0.3)
    
    plt.suptitle('案例4:基于抽样的鲁棒性优化对比', fontsize=14, fontweight='bold', y=0.98)
    plt.tight_layout(rect=[0, 0, 1, 0.96])
    
    output_file = os.path.join(output_dir, 'case4_sampling_comparison.png')
    plt.savefig(output_file, dpi=150, bbox_inches='tight', facecolor='white')
    print(f"\n✓ 图像已保存: {output_file}")
    plt.close()
    
    return results_comparison

results_case4 = case4_sampling_based_robust()


# =============================================================================
# 生成Pareto前沿演化GIF动画
# =============================================================================
print("\n" + "=" * 80)
print("生成Pareto前沿演化GIF动画")
print("=" * 80)

def create_pareto_evolution_gif():
    """创建Pareto前沿演化过程的GIF动画"""
    print("\n【生成动画】")
    
    # 重新计算Pareto前沿(用于动画)
    c1, c2 = 1.0, 1.0
    sigma_c1, sigma_c2 = 0.2, 0.2
    
    n_frames = 30
    weights_f1 = np.linspace(0, 1, n_frames)
    
    # 存储每一帧的数据
    frames_data = []
    
    for frame, w1 in enumerate(weights_f1):
        w2 = 1 - w1
        
        def objective(x):
            x1, x2 = x
            f1 = c1 * x1**2 + c2 * x2**2
            f2 = (x1**2)**2 * sigma_c1**2 + (x2**2)**2 * sigma_c2**2
            return w1 * f1 + w2 * f2
        
        def constraint(x):
            return x[0] + x[1] - 1
        
        result = minimize(
            objective,
            [0.5, 0.5],
            method='SLSQP',
            bounds=[(0, 2), (0, 2)],
            constraints={'type': 'ineq', 'fun': constraint}
        )
        
        if result.success:
            x1, x2 = result.x
            f1 = c1 * x1**2 + c2 * x2**2
            f2 = (x1**2)**2 * sigma_c1**2 + (x2**2)**2 * sigma_c2**2
            frames_data.append({'frame': frame, 'w1': w1, 'f1': f1, 'f2': f2, 'x': [x1, x2]})
    
    # 创建动画帧
    temp_dir = os.path.join(output_dir, 'temp_frames')
    os.makedirs(temp_dir, exist_ok=True)
    
    frame_files = []
    
    for i, data in enumerate(frames_data):
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
        
        # 左图:目标空间中的Pareto前沿演化
        f1_vals = [d['f1'] for d in frames_data[:i+1]]
        f2_vals = [d['f2'] for d in frames_data[:i+1]]
        w1_vals = [d['w1'] for d in frames_data[:i+1]]
        
        scatter = ax1.scatter(f1_vals, f2_vals, c=w1_vals, cmap='viridis', 
                             s=100, edgecolors='black', linewidth=1.5)
        
        # 高亮当前点
        ax1.scatter(data['f1'], data['f2'], c='red', s=300, marker='*', 
                   edgecolors='black', linewidth=2, zorder=5)
        
        ax1.set_xlabel('性能目标 f₁', fontsize=12)
        ax1.set_ylabel('方差目标 f₂', fontsize=12)
        ax1.set_title(f'Pareto前沿演化 (w₁={data["w1"]:.2f})', fontsize=14, fontweight='bold')
        ax1.set_xlim(0, 2.5)
        ax1.set_ylim(0, 0.25)
        ax1.grid(True, alpha=0.3)
        plt.colorbar(scatter, ax=ax1, label='性能权重 w₁')
        
        # 右图:设计空间中的解演化
        x1_vals = [d['x'][0] for d in frames_data[:i+1]]
        x2_vals = [d['x'][1] for d in frames_data[:i+1]]
        
        scatter2 = ax2.scatter(x1_vals, x2_vals, c=w1_vals, cmap='viridis', 
                              s=100, edgecolors='black', linewidth=1.5)
        
        # 高亮当前点
        ax2.scatter(data['x'][0], data['x'][1], c='red', s=300, marker='*', 
                   edgecolors='black', linewidth=2, zorder=5)
        
        # 绘制约束边界
        x1_boundary = np.linspace(0, 2, 100)
        x2_boundary = 1 - x1_boundary
        ax2.plot(x1_boundary, x2_boundary, 'r--', linewidth=2, label='约束边界')
        ax2.fill_between(x1_boundary, 0, x2_boundary, alpha=0.2, color='red')
        
        ax2.set_xlabel('x₁', fontsize=12)
        ax2.set_ylabel('x₂', fontsize=12)
        ax2.set_title(f'设计空间演化 (w₁={data["w1"]:.2f})', fontsize=14, fontweight='bold')
        ax2.set_xlim(0, 2)
        ax2.set_ylim(0, 2)
        ax2.grid(True, alpha=0.3)
        ax2.legend(fontsize=10)
        plt.colorbar(scatter2, ax=ax2, label='性能权重 w₁')
        
        plt.suptitle(f'多目标鲁棒性优化演化过程 (帧 {i+1}/{len(frames_data)})', 
                     fontsize=15, fontweight='bold')
        plt.tight_layout(rect=[0, 0, 1, 0.95])
        
        frame_file = os.path.join(temp_dir, f'frame_{i:03d}.png')
        plt.savefig(frame_file, dpi=100, bbox_inches='tight', facecolor='white')
        frame_files.append(frame_file)
        plt.close()
        
        if (i + 1) % 10 == 0:
            print(f"  已生成 {i+1}/{len(frames_data)} 帧")
    
    # 创建GIF
    print("\n【合成GIF动画】")
    
    images = []
    for frame_file in frame_files:
        img = Image.open(frame_file)
        images.append(img)
    
    gif_file = os.path.join(output_dir, 'pareto_evolution.gif')
    images[0].save(
        gif_file,
        save_all=True,
        append_images=images[1:],
        duration=200,
        loop=0
    )
    
    print(f"✓ GIF动画已保存: {gif_file}")
    
    # 清理临时文件
    for frame_file in frame_files:
        os.remove(frame_file)
    os.rmdir(temp_dir)
    
    return gif_file

gif_file = create_pareto_evolution_gif()


# =============================================================================
# 程序结束
# =============================================================================
print("\n" + "=" * 80)
print("所有仿真案例已完成!")
print("=" * 80)
print(f"\n生成的文件:")
print(f"  1. {os.path.join(output_dir, 'case1_cantilever_robust.png')}")
print(f"  2. {os.path.join(output_dir, 'case2_multiobjective_robust.png')}")
print(f"  3. {os.path.join(output_dir, 'case3_worst_case.png')}")
print(f"  4. {os.path.join(output_dir, 'case4_sampling_comparison.png')}")
print(f"  5. {os.path.join(output_dir, 'pareto_evolution.gif')}")
print("\n" + "=" * 80)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐