主题014:水平集方法(Level Set Method)

1. 引言

1.1 什么是水平集方法

水平集方法(Level Set Method)是由Osher和Sethian于1988年提出的一种用于追踪界面演化的数值技术。在结构优化领域,Allaire、Wang等人于2000年代初将其引入拓扑优化,开创了基于几何描述的优化新范式。

核心思想
使用隐式函数 ϕ(x)\phi(\mathbf{x})ϕ(x) 描述结构边界:

  • ϕ(x)>0\phi(\mathbf{x}) > 0ϕ(x)>0:实体材料区域
  • ϕ(x)<0\phi(\mathbf{x}) < 0ϕ(x)<0:空腔区域
  • ϕ(x)=0\phi(\mathbf{x}) = 0ϕ(x)=0:结构边界(零水平集)
    在这里插入图片描述

1.2 水平集 vs 传统方法

特性 水平集方法 SIMP ESO/BESO
边界描述 隐式(光滑) 像素化 像素化
灰度单元
边界光滑性
拓扑变化 自动处理 需要过滤 离散变化
实现复杂度
计算成本

1.3 本章学习目标

通过本章学习,你将掌握:

  1. 水平集方法的数学基础
  2. Hamilton-Jacobi方程与边界演化
  3. 形状灵敏度分析
  4. 水平集的重新初始化技术
  5. Python实现与工程应用

2. 理论基础

2.1 水平集函数

2.1.1 定义与性质

水平集函数 ϕ(x,t)\phi(\mathbf{x}, t)ϕ(x,t) 是一个定义在设计域 Ω\OmegaΩ 上的标量场,满足:

Ωsolid={x:ϕ(x)>0}\Omega_{solid} = \{\mathbf{x} : \phi(\mathbf{x}) > 0\}Ωsolid={x:ϕ(x)>0}
Ωvoid={x:ϕ(x)<0}\Omega_{void} = \{\mathbf{x} : \phi(\mathbf{x}) < 0\}Ωvoid={x:ϕ(x)<0}
Γ={x:ϕ(x)=0}\Gamma = \{\mathbf{x} : \phi(\mathbf{x}) = 0\}Γ={x:ϕ(x)=0}

理想性质

  1. 符号距离函数∣∇ϕ∣=1|\nabla \phi| = 1∣∇ϕ=1
  2. 光滑性ϕ∈C1\phi \in C^1ϕC1
  3. 正则性:避免尖锐特征
2.1.2 初始化方法

带孔洞的初始设计

# 创建多个圆形孔洞
phi = np.ones((nely + 1, nelx + 1))
for hole in holes:
    dist = np.sqrt((X - cx)**2 + (Y - cy)**2)
    phi = np.minimum(phi, dist - r)  # 孔洞内部phi < 0

符号距离函数
ϕ(x)=±min⁡y∈Γ∥x−y∥\phi(\mathbf{x}) = \pm \min_{\mathbf{y} \in \Gamma} \|\mathbf{x} - \mathbf{y}\|ϕ(x)=±yΓminxy

2.2 Hamilton-Jacobi方程

2.2.1 边界演化方程

结构边界随时间演化遵循Hamilton-Jacobi方程:
∂ϕ∂t+Vn∣∇ϕ∣=0\frac{\partial \phi}{\partial t} + V_n |\nabla \phi| = 0tϕ+Vn∣∇ϕ=0

其中:

  • VnV_nVn:边界的法向速度
  • ∣∇ϕ∣|\nabla \phi|∣∇ϕ:水平集梯度模长
2.2.2 速度场的确定

速度场由形状灵敏度决定:
Vn=−∂J∂ΩV_n = -\frac{\partial J}{\partial \Omega}Vn=ΩJ

对于柔度最小化问题:
Vn=−u:σV_n = -\mathbf{u} : \boldsymbol{\sigma}Vn=u:σ

其中:

  • u\mathbf{u}u:位移场
  • σ\boldsymbol{\sigma}σ:应力张量
2.2.3 数值求解

迎风格式(Upwind Scheme)
根据速度方向选择差分方向:

∂ϕ∂x≈{ϕi,j−ϕi−1,jΔxVx>0ϕi+1,j−ϕi,jΔxVx<0\frac{\partial \phi}{\partial x} \approx \begin{cases} \frac{\phi_{i,j} - \phi_{i-1,j}}{\Delta x} & V_x > 0 \\ \frac{\phi_{i+1,j} - \phi_{i,j}}{\Delta x} & V_x < 0 \end{cases}xϕ{Δxϕi,jϕi1,jΔxϕi+1,jϕi,jVx>0Vx<0

时间步进
ϕn+1=ϕn−Δt⋅Vn∣∇ϕ∣\phi^{n+1} = \phi^n - \Delta t \cdot V_n |\nabla \phi|ϕn+1=ϕnΔtVn∣∇ϕ

稳定性条件(CFL条件):
Δt≤Δxmax⁡∣Vn∣\Delta t \leq \frac{\Delta x}{\max|V_n|}ΔtmaxVnΔx

2.3 形状灵敏度分析

2.3.1 形状导数

目标函数 JJJ 对边界形状的导数:
dJdΩ=∫ΓG(u,p)VndΓ\frac{dJ}{d\Omega} = \int_{\Gamma} G(\mathbf{u}, \mathbf{p}) V_n d\GammadΩdJ=ΓG(u,p)VndΓ

其中:

  • GGG:形状梯度
  • p\mathbf{p}p:伴随变量
2.3.2 柔度最小化

对于柔度 C=FTUC = \mathbf{F}^T \mathbf{U}C=FTU
G=−u:σG = -\mathbf{u} : \boldsymbol{\sigma}G=u:σ

物理意义

  • 高应变能区域:应保留材料(Vn<0V_n < 0Vn<0,边界向内移动)
  • 低应变能区域:可删除材料(Vn>0V_n > 0Vn>0,边界向外移动)
2.3.3 体积约束处理

使用Lagrange乘子法:
L=C+λ(V−V∗)\mathcal{L} = C + \lambda (V - V^*)L=C+λ(VV)

速度场修正:
Vneff=Vn+λV_n^{eff} = V_n + \lambdaVneff=Vn+λ

其中 λ\lambdaλ 通过二分法确定,满足体积约束。

2.4 重新初始化

2.4.1 为什么需要重新初始化

演化过程中水平集函数会偏离符号距离函数:

  • 梯度模长 ∣∇ϕ∣|\nabla \phi|∣∇ϕ 不再等于1
  • 导致数值不稳定
  • 影响边界精度
2.4.2 重新初始化方程

求解到稳态:
∂ϕ∂τ+S(ϕ0)(∣∇ϕ∣−1)=0\frac{\partial \phi}{\partial \tau} + S(\phi_0)(|\nabla \phi| - 1) = 0τϕ+S(ϕ0)(∣∇ϕ1)=0

其中:

  • τ\tauτ:伪时间
  • S(ϕ0)S(\phi_0)S(ϕ0):符号函数
2.4.3 简化方法

高斯平滑法

phi_smooth = gaussian_filter(phi, sigma=1.0)
phi_new = np.sign(phi) * np.abs(phi_smooth)

快速行进法(Fast Marching Method)
精确求解距离函数,计算复杂度 O(Nlog⁡N)O(N \log N)O(NlogN)


3. Python实现

3.1 水平集优化器类

class LevelSetTopologyOptimizer:
    """
    水平集拓扑优化器
    
    基于Hamilton-Jacobi方程的边界演化
    """
    
    def __init__(self, nelx, nely, volfrac, tau=0.1):
        """
        初始化水平集优化器
        
        参数:
            nelx: x方向单元数
            nely: y方向单元数
            volfrac: 目标体积分数
            tau: 时间步长
        """
        self.nelx = nelx
        self.nely = nely
        self.volfrac = volfrac
        self.tau = tau
        self.nu = 0.3
        
        # 创建网格
        self.x = np.linspace(0, nelx, nelx + 1)
        self.y = np.linspace(0, nely, nely + 1)
        self.X, self.Y = np.meshgrid(self.x, self.y)
        
        # 初始化水平集函数
        self.phi = self._initialize_level_set()

3.2 水平集初始化

def _initialize_level_set(self):
    """初始化水平集函数"""
    # 创建带孔洞的初始设计
    phi = np.ones((self.nely + 1, self.nelx + 1))
    
    # 添加一些初始孔洞
    n_holes = 5
    np.random.seed(42)
    for _ in range(n_holes):
        cx = np.random.uniform(10, self.nelx - 10)
        cy = np.random.uniform(5, self.nely - 5)
        r = np.random.uniform(3, 6)
        
        dist = np.sqrt((self.X - cx)**2 + (self.Y - cy)**2)
        phi = np.minimum(phi, dist - r)
    
    # 确保边界有材料
    phi[0, :] = 1.0
    phi[-1, :] = 1.0
    phi[:, 0] = 1.0
    phi[:, -1] = 1.0
    
    return phi

3.3 密度转换

def _phi_to_density(self, phi):
    """将水平集函数转换为单元密度"""
    x_elem = np.zeros((self.nely, self.nelx))
    
    for elx in range(self.nelx):
        for ely in range(self.nely):
            # 单元四个节点的水平集值
            phi_nodes = [
                phi[ely, elx],
                phi[ely+1, elx],
                phi[ely+1, elx+1],
                phi[ely, elx+1]
            ]
            # 平均值作为单元密度
            phi_avg = np.mean(phi_nodes)
            # 平滑Heaviside函数
            epsilon = 0.5
            x_elem[ely, elx] = 1.0 / (1.0 + np.exp(-phi_avg / epsilon))
    
    return x_elem

3.4 水平集演化

def _evolve_level_set(self, phi, velocity, dt):
    """
    演化水平集函数
    
    使用简单的迎风差分求解 Hamilton-Jacobi 方程
    """
    dx = 1.0
    dy = 1.0
    
    phi_new = phi.copy()
    
    # 内部节点更新
    for i in range(1, self.nelx):
        for j in range(1, self.nely):
            # 计算梯度(迎风格式)
            if velocity[j, i] > 0:
                phi_x = (phi[j, i] - phi[j, i-1]) / dx
            else:
                phi_x = (phi[j, i+1] - phi[j, i]) / dx
            
            if velocity[j, i] > 0:
                phi_y = (phi[j, i] - phi[j-1, i]) / dy
            else:
                phi_y = (phi[j+1, i] - phi[j, i]) / dy
            
            # 更新水平集
            phi_new[j, i] = phi[j, i] - dt * velocity[j, i] * np.sqrt(phi_x**2 + phi_y**2)
    
    return phi_new

3.5 重新初始化

def _reinitialize(self, phi, n_iter=10):
    """
    重新初始化水平集函数为符号距离函数
    """
    # 简化的重新初始化:使用高斯平滑
    phi_smooth = gaussian_filter(phi, sigma=1.0)
    
    # 保持符号
    phi_new = np.sign(phi) * np.abs(phi_smooth)
    
    return phi_new

4. 案例研究

4.1 悬臂梁优化

问题设置

  • 设计域:60×20悬臂梁
  • 目标体积分数:40%
  • 初始设计:带5个随机孔洞

优化过程

  • 每代演化水平集函数
  • 每5代重新初始化
  • 共30代收敛

结果分析

  • 最终柔度:约940
  • 体积分数:81%(未达到目标,需调整参数)
  • 边界光滑清晰

4.2 参数敏感性分析

时间步长 τ\tauτ

  • 过大:数值不稳定
  • 过小:收敛缓慢
  • 推荐:0.05-0.2

重新初始化频率

  • 每代:计算成本高
  • 每5-10代:平衡选择
  • 从不:数值不稳定

Heaviside参数 ϵ\epsilonϵ

  • 控制边界过渡区宽度
  • 推荐:0.5-1.0

4.3 水平集 vs SIMP对比

指标 水平集 SIMP
边界质量 光滑 锯齿状
灰度单元
计算时间
实现难度
拓扑变化 自动 依赖过滤

选择建议

  • 工程应用:SIMP(成熟稳定)
  • 学术研究:水平集(边界精确)
  • 混合方法:SIMP+水平集(两阶段)

5. 进阶主题

5.1 多相水平集

使用多个水平集函数描述多材料:
ϕ1,ϕ2,...,ϕn\phi_1, \phi_2, ..., \phi_nϕ1,ϕ2,...,ϕn

材料区域:
Ωi={x:ϕi(x)>0 且 ϕj(x)<0,∀j≠i}\Omega_i = \{\mathbf{x} : \phi_i(\mathbf{x}) > 0 \text{ 且 } \phi_j(\mathbf{x}) < 0, \forall j \neq i\}Ωi={x:ϕi(x)>0  ϕj(x)<0,j=i}

5.2 参数化水平集

使用基函数展开:
ϕ(x)=∑i=1Nαiψi(x)\phi(\mathbf{x}) = \sum_{i=1}^N \alpha_i \psi_i(\mathbf{x})ϕ(x)=i=1Nαiψi(x)

其中 ψi\psi_iψi 可以是:

  • 径向基函数(RBF)
  • 紧支径向基函数(CS-RBF)
  • 小波基函数

优势:

  • 降低设计变量维度
  • 提高光滑性
  • 便于制造约束处理

5.3 与等几何分析结合

等几何分析(IGA)

  • 使用NURBS基函数
  • 精确几何描述
  • 与水平集天然契合

优势

  • 消除几何误差
  • 高阶连续性
  • 适合薄壳结构

5.4 制造约束集成

最小尺寸约束
通过速度场修正:
Vneff=Vn+VnsizeV_n^{eff} = V_n + V_n^{size}Vneff=Vn+Vnsize

悬空约束
检测局部梯度方向,限制边界演化方向。


"""
水平集方法(Level Set Method)拓扑优化仿真
=============================================
基于隐式边界描述的拓扑优化方法

核心思想:
- 使用隐式函数 φ(x) 描述结构边界
- φ > 0: 实体材料
- φ < 0: 空腔
- φ = 0: 边界(零水平集)
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.sparse import coo_matrix
from scipy.sparse.linalg import spsolve
from scipy.ndimage import gaussian_filter
import matplotlib
matplotlib.use('Agg')

# 设置中文字体
plt.rcParams['font.size'] = 10


def lk():
    """计算4节点四边形单元的单元刚度矩阵"""
    E = 1.0
    nu = 0.3
    k = np.array([1/2-nu/6, 1/8+nu/8, -1/4-nu/12, -1/8+3*nu/8,
                  -1/4+nu/12, -1/8-nu/8, nu/6, 1/8-3*nu/8])
    KE = E / (1-nu**2) * np.array([
        [k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7]],
        [k[1], k[0], k[7], k[6], k[5], k[4], k[3], k[2]],
        [k[2], k[7], k[0], k[5], k[6], k[3], k[4], k[1]],
        [k[3], k[6], k[5], k[0], k[7], k[2], k[1], k[4]],
        [k[4], k[5], k[6], k[7], k[0], k[1], k[2], k[3]],
        [k[5], k[4], k[3], k[2], k[1], k[0], k[7], k[6]],
        [k[6], k[3], k[4], k[1], k[2], k[7], k[0], k[5]],
        [k[7], k[2], k[1], k[4], k[3], k[6], k[5], k[0]]
    ])
    return KE


class LevelSetTopologyOptimizer:
    """
    水平集拓扑优化器
    
    基于Hamilton-Jacobi方程的边界演化
    """
    
    def __init__(self, nelx, nely, volfrac, tau=0.1):
        """
        初始化水平集优化器
        
        参数:
            nelx: x方向单元数
            nely: y方向单元数
            volfrac: 目标体积分数
            tau: 时间步长
        """
        self.nelx = nelx
        self.nely = nely
        self.volfrac = volfrac
        self.tau = tau
        self.nu = 0.3
        
        # 创建网格
        self.x = np.linspace(0, nelx, nelx + 1)
        self.y = np.linspace(0, nely, nely + 1)
        self.X, self.Y = np.meshgrid(self.x, self.y)
        
        # 初始化水平集函数(带孔洞的初始设计)
        self.phi = self._initialize_level_set()
        
        # 历史记录
        self.history = {
            'phi': [],
            'compliance': [],
            'volume': [],
            'boundary_length': []
        }
        
        self._prepare_fem()
    
    def _initialize_level_set(self):
        """初始化水平集函数"""
        # 创建带孔洞的初始设计
        phi = np.ones((self.nely + 1, self.nelx + 1))
        
        # 添加一些初始孔洞
        n_holes = 5
        np.random.seed(42)
        for _ in range(n_holes):
            cx = np.random.uniform(10, self.nelx - 10)
            cy = np.random.uniform(5, self.nely - 5)
            r = np.random.uniform(3, 6)
            
            dist = np.sqrt((self.X - cx)**2 + (self.Y - cy)**2)
            phi = np.minimum(phi, dist - r)
        
        # 确保边界有材料
        phi[0, :] = 1.0
        phi[-1, :] = 1.0
        phi[:, 0] = 1.0
        phi[:, -1] = 1.0
        
        return phi
    
    def _prepare_fem(self):
        """准备有限元分析"""
        self.ndof = 2 * (self.nelx + 1) * (self.nely + 1)
        self.KE = lk()
        
        # 构建自由度映射
        self.edofMat = np.zeros((self.nelx * self.nely, 8), dtype=int)
        for elx in range(self.nelx):
            for ely in range(self.nely):
                el = ely + elx * self.nely
                n1 = (self.nely + 1) * elx + ely
                n2 = (self.nely + 1) * (elx + 1) + ely
                self.edofMat[el, :] = [2*n1+2, 2*n1+3, 2*n2+2, 2*n2+3,
                                        2*n2, 2*n2+1, 2*n1, 2*n1+1]
        
        self.iK = np.kron(self.edofMat, np.ones((8, 1))).flatten()
        self.jK = np.kron(self.edofMat, np.ones((1, 8))).flatten()
        
        # 边界条件:左端固定
        fixeddofs = np.union1d(np.arange(0, 2*(self.nely+1), 2),
                                np.arange(1, 2*(self.nely+1), 2))
        alldofs = np.arange(self.ndof)
        self.freedofs = np.setdiff1d(alldofs, fixeddofs)
    
    def _phi_to_density(self, phi):
        """将水平集函数转换为单元密度"""
        # 使用节点水平集的平均值作为单元密度
        x_elem = np.zeros((self.nely, self.nelx))
        
        for elx in range(self.nelx):
            for ely in range(self.nely):
                # 单元四个节点的水平集值
                phi_nodes = [
                    phi[ely, elx],
                    phi[ely+1, elx],
                    phi[ely+1, elx+1],
                    phi[ely, elx+1]
                ]
                # 平均值作为单元密度(使用平滑过渡)
                phi_avg = np.mean(phi_nodes)
                # 平滑Heaviside函数
                epsilon = 0.5
                x_elem[ely, elx] = 1.0 / (1.0 + np.exp(-phi_avg / epsilon))
        
        return x_elem
    
    def _fem_analysis(self, x):
        """执行有限元分析"""
        E = 1e-9 + x * (1.0 - 1e-9)
        
        sK = ((self.KE.flatten()[np.newaxis]).T * E.flatten('F')).flatten(order='F')
        K = coo_matrix((sK, (self.iK, self.jK)),
                      shape=(self.ndof, self.ndof)).tocsc()
        K = (K + K.T) / 2
        
        F = np.zeros(self.ndof)
        F[-1] = -1.0
        
        U = np.zeros(self.ndof)
        try:
            U[self.freedofs] = spsolve(
                K[self.freedofs, :][:, self.freedofs],
                F[self.freedofs])
        except:
            U[self.freedofs] = 0
        
        return U, E
    
    def _compute_sensitivity(self, U, x):
        """计算形状灵敏度"""
        ce = np.zeros((self.nely, self.nelx))
        
        for el in range(self.nelx * self.nely):
            edofs = self.edofMat[el, :]
            Ue = U[edofs]
            ce.flat[el] = Ue @ self.KE @ Ue
        
        # 将单元灵敏度映射到节点
        sensitivity = np.zeros((self.nely + 1, self.nelx + 1))
        
        for elx in range(self.nelx):
            for ely in range(self.nely):
                val = ce[ely, elx]
                # 分配到四个节点
                sensitivity[ely, elx] += val / 4
                sensitivity[ely+1, elx] += val / 4
                sensitivity[ely+1, elx+1] += val / 4
                sensitivity[ely, elx+1] += val / 4
        
        return sensitivity
    
    def _compute_curvature(self, phi):
        """计算水平集的曲率(用于正则化)"""
        # 使用中心差分计算梯度
        dx = 1.0
        dy = 1.0
        
        phi_x = (phi[:, 2:] - phi[:, :-2]) / (2 * dx)
        phi_y = (phi[2:, :] - phi[:-2, :]) / (2 * dy)
        
        phi_xx = (phi[:, 2:] - 2*phi[:, 1:-1] + phi[:, :-2]) / (dx**2)
        phi_yy = (phi[2:, :] - 2*phi[1:-1, :] + phi[:-2, :]) / (dy**2)
        
        phi_xy = (phi[2:, 2:] - phi[2:, :-2] - phi[:-2, 2:] + phi[:-2, :-2]) / (4 * dx * dy)
        
        # 计算曲率(在内部点)
        curvature = np.zeros_like(phi)
        phi_x_pad = np.zeros_like(phi)
        phi_y_pad = np.zeros_like(phi)
        phi_x_pad[:, 1:-1] = phi_x
        phi_y_pad[1:-1, :] = phi_y
        
        grad_mag = np.sqrt(phi_x_pad**2 + phi_y_pad**2 + 1e-10)
        
        # 简化的曲率计算
        curvature[1:-1, 1:-1] = (phi_xx + phi_yy) / 2.0
        
        return curvature
    
    def _evolve_level_set(self, phi, velocity, dt):
        """
        演化水平集函数
        
        使用简单的迎风差分求解 Hamilton-Jacobi 方程
        """
        dx = 1.0
        dy = 1.0
        
        phi_new = phi.copy()
        
        # 内部节点更新
        for i in range(1, self.nelx):
            for j in range(1, self.nely):
                # 计算梯度(迎风格式)
                if velocity[j, i] > 0:
                    phi_x = (phi[j, i] - phi[j, i-1]) / dx
                else:
                    phi_x = (phi[j, i+1] - phi[j, i]) / dx
                
                if velocity[j, i] > 0:
                    phi_y = (phi[j, i] - phi[j-1, i]) / dy
                else:
                    phi_y = (phi[j+1, i] - phi[j, i]) / dy
                
                # 更新水平集
                phi_new[j, i] = phi[j, i] - dt * velocity[j, i] * np.sqrt(phi_x**2 + phi_y**2)
        
        return phi_new
    
    def _reinitialize(self, phi, n_iter=10):
        """
        重新初始化水平集函数为符号距离函数
        
        保持零水平集不变,将φ恢复为距离函数
        """
        # 简化的重新初始化:使用高斯平滑
        phi_smooth = gaussian_filter(phi, sigma=1.0)
        
        # 保持符号
        phi_new = np.sign(phi) * np.abs(phi_smooth)
        
        return phi_new
    
    def optimize(self, max_iter=50):
        """执行水平集优化"""
        print("="*70)
        print("水平集方法拓扑优化")
        print("="*70)
        print(f"\n优化参数:")
        print(f"  网格: {self.nelx} x {self.nely}")
        print(f"  目标体积分数: {self.volfrac}")
        print(f"  时间步长: {self.tau}")
        
        phi = self.phi.copy()
        loop = 0
        
        print("\n开始优化...")
        print(f"{'迭代':>6} {'柔度':>12} {'体积分数':>12} {'边界长度':>12}")
        print("-"*50)
        
        while loop < max_iter:
            loop += 1
            
            # 转换密度场
            x = self._phi_to_density(phi)
            
            # 有限元分析
            U, E = self._fem_analysis(x)
            
            # 计算灵敏度
            sensitivity = self._compute_sensitivity(U, x)
            
            # 总柔度
            c = np.sum(E * x * sensitivity[:-1, :-1])
            
            # 当前体积
            vol = np.mean(x)
            
            # 计算速度场(基于灵敏度和体积约束)
            # Lagrange乘子处理体积约束
            lambda_vol = 0.0
            if vol > self.volfrac:
                lambda_vol = 0.1  # 惩罚超体积
            
            # 速度 = -灵敏度 + 体积惩罚
            velocity = -sensitivity + lambda_vol
            
            # 演化水平集
            phi = self._evolve_level_set(phi, velocity, self.tau)
            
            # 定期重新初始化
            if loop % 5 == 0:
                phi = self._reinitialize(phi)
            
            # 计算边界长度(近似)
            boundary = np.sum(np.abs(phi[1:-1, 1:-1]) < 0.5)
            
            # 记录历史
            self.history['phi'].append(phi.copy())
            self.history['compliance'].append(c)
            self.history['volume'].append(vol)
            self.history['boundary_length'].append(boundary)
            
            if loop % 5 == 0 or loop == 1:
                print(f"{loop:>6} {c:>12.4f} {vol:>12.4f} {boundary:>12}")
        
        print("-"*50)
        print(f"\n优化完成!")
        print(f"  总迭代: {loop}")
        print(f"  最终柔度: {c:.4f}")
        print(f"  最终体积分数: {vol:.4f}")
        
        self.phi_opt = phi
        return phi
    
    def visualize_result(self, filename='level_set_result.png'):
        """可视化优化结果"""
        fig, axes = plt.subplots(2, 2, figsize=(14, 10))
        
        # 最终水平集函数
        ax = axes[0, 0]
        im = ax.contourf(self.X, self.Y, self.phi_opt, levels=20, cmap='RdBu')
        ax.contour(self.X, self.Y, self.phi_opt, levels=[0], colors='k', linewidths=2)
        ax.set_title('Level Set Function φ')
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        plt.colorbar(im, ax=ax)
        
        # 最终拓扑
        ax = axes[0, 1]
        x_final = self._phi_to_density(self.phi_opt)
        im = ax.imshow(x_final, cmap='gray_r', origin='lower',
                      extent=[0, self.nelx, 0, self.nely], vmin=0, vmax=1)
        ax.set_title('Final Topology')
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        plt.colorbar(im, ax=ax, label='Density')
        
        # 收敛曲线
        ax = axes[1, 0]
        ax.plot(self.history['compliance'], 'b-', linewidth=2)
        ax.set_xlabel('Iteration')
        ax.set_ylabel('Compliance')
        ax.set_title('Compliance Convergence')
        ax.grid(True)
        
        # 体积变化
        ax = axes[1, 1]
        ax.plot(self.history['volume'], 'r-', linewidth=2, label='Current')
        ax.axhline(y=self.volfrac, color='k', linestyle='--', label='Target')
        ax.set_xlabel('Iteration')
        ax.set_ylabel('Volume Fraction')
        ax.set_title('Volume Evolution')
        ax.legend()
        ax.grid(True)
        
        plt.tight_layout()
        plt.savefig(filename, dpi=150, bbox_inches='tight')
        print(f"\n结果已保存: {filename}")


class SimplifiedLevelSet:
    """
    简化版水平集方法(基于密度梯度)
    
    适用于教学演示的简化实现
    """
    
    def __init__(self, nelx, nely, volfrac):
        self.nelx = nelx
        self.nely = nely
        self.volfrac = volfrac
        
        # 初始化密度场(带孔洞)
        self.x = np.ones((nely, nelx))
        
        # 创建初始孔洞
        n_holes = 4
        np.random.seed(42)
        for _ in range(n_holes):
            cx = int(np.random.uniform(nelx * 0.2, nelx * 0.8))
            cy = int(np.random.uniform(nely * 0.2, nely * 0.8))
            r = int(np.random.uniform(2, 5))
            
            for i in range(max(0, cx-r), min(nelx, cx+r)):
                for j in range(max(0, cy-r), min(nely, cy+r)):
                    if (i-cx)**2 + (j-cy)**2 < r**2:
                        self.x[j, i] = 0.001
        
        self.history = {'compliance': [], 'volume': []}
        self._prepare_fem()
    
    def _prepare_fem(self):
        """准备有限元分析"""
        self.ndof = 2 * (self.nelx + 1) * (self.nely + 1)
        self.KE = lk()
        
        self.edofMat = np.zeros((self.nelx * self.nely, 8), dtype=int)
        for elx in range(self.nelx):
            for ely in range(self.nely):
                el = ely + elx * self.nely
                n1 = (self.nely + 1) * elx + ely
                n2 = (self.nely + 1) * (elx + 1) + ely
                self.edofMat[el, :] = [2*n1+2, 2*n1+3, 2*n2+2, 2*n2+3,
                                        2*n2, 2*n2+1, 2*n1, 2*n1+1]
        
        self.iK = np.kron(self.edofMat, np.ones((8, 1))).flatten()
        self.jK = np.kron(self.edofMat, np.ones((1, 8))).flatten()
        
        fixeddofs = np.union1d(np.arange(0, 2*(self.nely+1), 2),
                                np.arange(1, 2*(self.nely+1), 2))
        alldofs = np.arange(self.ndof)
        self.freedofs = np.setdiff1d(alldofs, fixeddofs)
    
    def _fem_analysis(self, x):
        """执行有限元分析"""
        E = 1e-9 + x * (1.0 - 1e-9)
        
        sK = ((self.KE.flatten()[np.newaxis]).T * E.flatten('F')).flatten(order='F')
        K = coo_matrix((sK, (self.iK, self.jK)),
                      shape=(self.ndof, self.ndof)).tocsc()
        K = (K + K.T) / 2
        
        F = np.zeros(self.ndof)
        F[-1] = -1.0
        
        U = np.zeros(self.ndof)
        try:
            U[self.freedofs] = spsolve(
                K[self.freedofs, :][:, self.freedofs],
                F[self.freedofs])
        except:
            U[self.freedofs] = 0
        
        return U, E
    
    def optimize(self, max_iter=50):
        """执行简化水平集优化"""
        print("="*70)
        print("简化水平集方法")
        print("="*70)
        
        x = self.x.copy()
        
        print(f"\n开始优化...")
        print(f"{'迭代':>6} {'柔度':>12} {'体积分数':>12}")
        print("-"*35)
        
        for loop in range(1, max_iter + 1):
            # 有限元分析
            U, E = self._fem_analysis(x)
            
            # 计算灵敏度
            ce = np.zeros((self.nely, self.nelx))
            for el in range(self.nelx * self.nely):
                edofs = self.edofMat[el, :]
                Ue = U[edofs]
                ce.flat[el] = Ue @ self.KE @ Ue
            
            c = np.sum(E * ce)
            vol = np.mean(x > 0.5)
            
            # 基于灵敏度的边界演化
            # 找到边界单元(密度在0.1-0.9之间)
            boundary = (x > 0.1) & (x < 0.9)
            solid = x > 0.9
            void = x < 0.1
            
            # 演化规则
            # 实体单元中灵敏度低的变为边界
            low_sens = solid & (ce < np.percentile(ce[solid], 20))
            x[low_sens] = 0.5
            
            # 边界单元根据灵敏度决定方向
            high_sens_boundary = boundary & (ce > np.median(ce))
            x[high_sens_boundary] = 1.0
            
            low_sens_boundary = boundary & (ce <= np.median(ce))
            x[low_sens_boundary] = 0.001
            
            # 体积控制
            if vol > self.volfrac * 1.05:
                # 删除更多
                solid_indices = np.where(solid)
                if len(solid_indices[0]) > 0:
                    n_remove = int(0.02 * len(solid_indices[0]))
                    remove_idx = np.argsort(ce[solid])[:n_remove]
                    x[solid_indices[0][remove_idx], solid_indices[1][remove_idx]] = 0.001
            
            self.history['compliance'].append(c)
            self.history['volume'].append(vol)
            
            if loop % 10 == 0:
                print(f"{loop:>6} {c:>12.4f} {vol:>12.4f}")
        
        self.x_opt = x
        return x


def compare_methods():
    """比较水平集方法与SIMP"""
    print("\n" + "="*70)
    print("水平集方法与SIMP对比")
    print("="*70)
    
    nelx, nely = 60, 20
    volfrac = 0.4
    
    # 水平集方法
    print("\n【方法1】水平集方法")
    lsm = LevelSetTopologyOptimizer(nelx, nely, volfrac, tau=0.1)
    phi = lsm.optimize(max_iter=30)
    lsm.visualize_result('level_set_result.png')
    
    print("\n" + "="*70)
    print("对比总结")
    print("="*70)
    print("\n水平集方法特点:")
    print("  - 边界光滑清晰,无灰度单元")
    print("  - 基于几何描述,物理意义明确")
    print("  - 需要定期重新初始化")
    print("  - 实现复杂度较高")
    print("\n与SIMP对比:")
    print("  - SIMP: 连续密度,易实现,有灰度")
    print("  - LSM: 隐式边界,边界清晰,计算复杂")


def main():
    """主函数"""
    print("="*70)
    print("水平集方法(Level Set Method)拓扑优化仿真")
    print("="*70)
    
    # 方法对比
    compare_methods()
    
    print("\n所有仿真完成!")
    print("="*70)


if __name__ == "__main__":
    main()

Logo

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

更多推荐