Skip to content
cfd-lab:~/zh/posts/2026-06-07-drl-synthetic…online
NOTE #067DAY SUN 논문리뷰DATE 2026.06.07READ 4 min readWORDS 1,871#DRL#Reinforcement-Learning#Flow-Control#Synthetic-Jet#Airfoil#논문리뷰

[论文综述] DQN 用合成射流驯服翼型涡 — 基于强化学习的主动流动控制

DQN 与 Dueling DQN 调控合成射流以提升升力、降低阻力

在大迎角翼型的尾部,涡交替剥落。升力每个周期都在起伏,机翼随之抖动。工程师通常用外形设计或固定周期强迫(periodic forcing)来抑制这种振动。Hammouda 等(2026)选择了另一条路。他们在机翼上开了一个小的合成射流(synthetic jet,净排量为零、一吹一吸的作动器)孔,让强化学习智能体自行决定其喷射速度。今天我们来看这篇论文如何把涡脱落(vortex shedding)翻译成强化学习问题,再用 ε-greedy Q 学习亲手把同一思路跑起来。

这篇论文的坐标#

  • 标题: Application of deep reinforcement learning for aerodynamic control around an angled airfoil via synthetic jet
  • 作者: N. Ghezaiel Hammouda, R. Khan, L. Mostafa 等 (Scientific Reports, 2026)
  • 设定: 雷诺数(惯性力/黏性力之比)100、马赫数 0.2 的弱可压层流。大迎角翼型,前缘附近设有合成射流。
  • 核心结果: Dueling DQN 收敛最可靠,在减少涡脱落的同时提升升力、降低阻力。

Re 100 时流动为层流,但大迎角下机翼尾部会周期性脱涡。这种脱涡正是升力与阻力振荡的元凶。

把涡翻译成强化学习问题#

强化学习(通过试错学习使奖励最大化的策略)只需定义三样东西即可启动。

  • 状态(state): 散布在翼型周围与尾流中的虚拟传感器读到的压力与速度。论文指出,在压力之外再加上速度会让学习更快。
  • 动作(action): 合成射流的喷射速度 UaU_a。离散化为 0 到 20 m/s、间隔 1 m/s 的 21 个整数档位,因为 DQN 要求离散动作集。
  • 奖励(reward): 削减阻力、提升升力的一行函数。
r=R1CDac+R2CLacr = R_1 - \langle C_D \rangle_{ac} + R_2\,\langle C_L \rangle_{ac}

其中 CDac\langle C_D \rangle_{ac}CLac\langle C_L \rangle_{ac} 是在一个动作区间内平均的阻力与升力系数。R1R_1R2R_2 是使奖励保持为正并平衡升力与阻力权重的常数,论文取 R1=3R_1=3R2=0.2R_2=0.2。一次动作对应一个涡脱落周期,训练共 300 个回合,每回合 25 个周期。

ε-greedy:在探索与利用之间#

智能体用动作价值函数 Q(s,a)Q(s,a) 估计每个动作的价值。核心是 Bellman 更新。

Q(s,a)Q(s,a)+α[r+γmaxaQ(s,a)Q(s,a)]Q(s,a) \leftarrow Q(s,a) + \alpha\left[\, r + \gamma \max_{a'} Q(s',a') - Q(s,a) \,\right]

α\alpha 是学习率,γ\gamma 是折减未来奖励的折扣因子,maxaQ(s,a)\max_{a'}Q(s',a') 是从下一状态可达的最佳价值。

难点在于如何尝试尚不知道价值的动作。ε-greedy 策略给出了答案。以概率 1ϵ1-\epsilon 选取目前看起来最好的动作(利用),以概率 ϵ\epsilon 选取随机动作(探索)。ϵ\epsilon 越大探索越多,越小则越快定下来。

在下面的模拟中亲自操作一下。柱条是 21 个射流速度各自的估计价值 QQ,黄色为探索、青色为利用刚选中的动作。

0jet velocity action (m/s)20
steps: 0 · best action: 0 m/s · avg reward: 0.00■ explore■ exploit

ϵ\epsilon 设到接近 0,就能看到智能体被困在偶然一开始显得不错的动作上。在 0.2 附近,它会迅速逼近 12 m/s 附近的真正最优。探索过多(0.8)时,即便已知好值,仍会不停往别处试探。

作为动作的合成射流#

合成射流靠振动膜片,从同一个孔把空气吹出再吸回。净排出质量为零,但动量被注入边界层。把这一注入量无量纲化的就是动量系数。

Cμ=ρjUa2dj12ρU2cC_\mu = \frac{\rho_j\,U_a^2\,d_j}{\tfrac{1}{2}\,\rho_\infty\,U_\infty^2\,c}

ρj\rho_jUaU_adjd_j 是射流的密度、速度与孔径,ρ\rho_\inftyUU_\inftycc 是自由来流密度、速度与弦长。论文中孔位于前缘附近吸力面 x/c=0.1x/c=0.1 处,直径 0.2 mm。当射流向边界层注入动量时,分离(boundary layer separation)被推迟,涡脱落随之减弱。

在下面的模拟中亲自操作一下。提高射流速度,看看尾流涡如何变化。

Ua=0U_a = 0 时,强涡交替剥落,CLC_L 的摆幅很大。把速度提到 15–20 m/s,涡变淡,尾流稳定,升力振荡明显减小。这正是奖励函数所要奖励的状态。

动手实现:用 Q 学习开启射流#

我们不照搬论文的 DQN,而是用只保留核心思想的表(table)型 Q 学习复现同样的控制。状态是离散化的升力振荡幅度,动作是射流速度。

import numpy as np
 
class SyntheticJetEnv:
    """一维现象学翼型-尾流环境。
 
    状态  : 离散化的升力振荡幅度 (0..n_bins-1)
    动作  : 射流速度档位 {0,1,...,20} m/s
    奖励  : R1 - <Cd> + R2*<Cl>  (论文 式4)
    """
    def __init__(self, n_bins=6, peak=12, R1=3.0, R2=0.2, seed=0):
        self.n_bins, self.peak = n_bins, peak
        self.R1, self.R2 = R1, R2
        self.rng = np.random.default_rng(seed)
        self.amp = 1.0  # 归一化脱涡幅度 (1 = 无控制)
 
    def reset(self):
        self.amp = 1.0
        return self._bin()
 
    def _bin(self):
        return min(self.n_bins - 1, int(self.amp * self.n_bins))
 
    def step(self, action):
        ctrl = action / 20.0                       # 控制权限 0..1
        target = max(0.05, 1.0 - 0.8 * ctrl)       # 射流压低幅度
        self.amp += 0.5 * (target - self.amp)      # 一阶松弛
        cl = 1.8 + 0.2 * ctrl - 0.4 * self.amp     # 升力系数
        cd = 0.085 - 0.006 * ctrl + 0.02 * self.amp  # 阻力系数
        waste = 0.01 * max(0, action - self.peak)  # 过度喷射的惩罚
        reward = self.R1 - cd + self.R2 * cl - waste
        reward += self.rng.normal(0, 0.05)
        return self._bin(), reward
 
def epsilon_greedy(q_row, eps, rng):
    if rng.random() < eps:
        return int(rng.integers(len(q_row)))      # 探索
    return int(np.argmax(q_row))                  # 利用
 
def train_jet_controller(episodes=300, steps=25, alpha=0.1, gamma=0.9, eps0=0.3):
    env = SyntheticJetEnv()
    n_actions = 21
    Q = np.zeros((env.n_bins, n_actions))
    rng = np.random.default_rng(1)
    history = []
    for ep in range(episodes):
        s = env.reset()
        eps = eps0 * (1 - ep / episodes)          # 线性衰减
        total = 0.0
        for _ in range(steps):
            a = epsilon_greedy(Q[s], eps, rng)
            s2, r = env.step(a)
            Q[s, a] += alpha * (r + gamma * Q[s2].max() - Q[s, a])
            s, total = s2, total + r
        history.append(total / steps)
    best = int(np.argmax(Q.sum(axis=0)))
    return Q, history, best
 
if __name__ == "__main__":
    Q, hist, best = train_jet_controller()
    print(f"episode   1 avg reward = {hist[0]:.3f}")
    print(f"episode 300 avg reward = {hist[-1]:.3f}")
    print(f"learned jet velocity   = {best} m/s")

输出如下。

episode   1 avg reward = 3.12
episode 300 avg reward = 3.25
learned jet velocity   = 12 m/s

智能体起初随机游走,300 个回合后自行发现 12 m/s 附近是升力收益与喷射浪费之间的平衡点。这直接源自论文的奖励形式与动作空间。

DQN 的兄弟:Double 与 Dueling#

论文比较了三种 DQN 变体。

  • 原始 DQN: max\max 运算往往高估价值。
  • Double DQN: 用不同网络分别做动作选择与价值评估,抑制高估。
  • Dueling DQN: 把 QQ 拆成状态价值 V(s)V(s) 与优势 A(s,a)A(s,a)
Q(s,a)=V(s)+(A(s,a)1AaA(s,a))Q(s,a) = V(s) + \left( A(s,a) - \frac{1}{|\mathcal{A}|}\sum_{a'} A(s,a') \right)

V(s)V(s) 学习"这个状态有多好",A(s,a)A(s,a) 学习"在其中这个动作比平均好多少"。当许多动作价值相近时——比如射流速度 11 与 13 m/s 几乎相同时——只需学一次状态价值,从而稳定训练。这正是论文中 Dueling DQN 给出最一致学习曲线与最佳性能的原因。

5 层 × 128 神经元的网络在 300 个回合内收敛,开启主动控制后 CLC_L 从 1.79 升至约 2.0,尾流随之稳定。

要记住的要点#

  • 把流动控制翻译为 RL 的配方: 状态 = 传感器压力与速度,动作 = 射流速度(离散),奖励 = R1CD+R2CLR_1 - \langle C_D\rangle + R_2\langle C_L\rangle
  • 合成射流以净质量为零只向边界层注入动量,推迟分离、减弱涡脱落。
  • Dueling DQN 凭借 Q=V+AQ = V + A 的拆分,在许多动作彼此相似的流动控制问题上收敛最稳。

如果对您有帮助,请分享。