用PSO优化STAR-RIS辅助NOMA:模型、编码与实现
2026/9/18 15:21:56 网站建设 项目流程

简介:面向STAR-RIS辅助NOMA无线通信网络优化的一份可复现资源,重点解决智能反射面与大规模天线场景下信道信息获取困难、功率与波束联合设计复杂的问题。方案基于粒子群优化(PSO),在不依赖完整CSI的条件下,联合设计功率分配、基站波束成形以及STAR-RIS传输/反射波束成形,以最大化总可达速率并满足用户最低速率约束。资源为单个PDF文件,压缩包约830KB,内含完整Python实现与逐段解释,覆盖系统参数定义、信道建模、NOMA速率计算、目标函数、约束处理、PSO主循环及结果可视化,并讨论了与OMA及DDPG的对比和模式切换、时间切换等工作协议差异。已有281人学习下载,适合通信工程研究生、教师与科研人员用于课题复现、算法验证和教学案例参考。

1. 用PSO优化STAR-RIS辅助NOMA,先把问题定清楚

一个基站要覆盖两个用户,一个被楼宇遮挡只能靠反射路径到达,另一个在另一侧需要透射路径,传统智能表面只能反射一侧,覆盖不了另一侧。STAR-RIS同时透射和反射,把NOMA的用户配对从“找一个信道弱的、一个信道强的”变成“两个都弱但弱得不一样”,功率分配和智能表面系数也因此耦合在一起。这个优化问题是非凸的:功率分配系数要满足SIC解码,STAR-RIS每个单元的透射振幅和反射振幅又互相约束,相位还周期性缠绕。PSO算法不需要梯度信息,能把功率分配和表面参数放进同一个粒子向量里搜索,是这类问题里最省事的基线和对照方案。下面按“模型 → 编码 → 实现 → 对比实验”的顺序把这条链路完整走一遍。

2. STAR-RIS辅助NOMA的系统模型与优化目标

2.1 智能表面的两种典型工作协议

STAR-RIS每个单元都有透射和反射两种状态,常见协议有两种。模式切换协议里每个单元要么全透射、要么全反射,振幅只有0或1两个值,离散性好但搜索空间是二进制的;能量分裂协议让每个单元同时透射和反射一部分能量,透射振幅和反射振幅是连续可调的,约束条件是两者平方和为1。做功率分配和智能表面参数联合优化时,我一般选能量分裂协议,因为它给PSO提供了连续可调的搜索空间,也更贴合电磁超表面实际可调谐的范围。

两用户下行场景如下:基站部署在中间,STAR-RIS面向两侧,用户1在反射区,信道整体偏弱;用户2在透射区,信道整体偏强。基站发射总功率P,NOMA叠加编码后给用户1分配a1 * P,用户2分配a2 * P,功率分配系数满足a1 + a2 = 1。由于用户1信道弱,通常令a1 > a2

L个STAR-RIS单元的参数用两组向量描述:反射端振幅beta_r、反射相位theta_r、透射相位theta_t,透射振幅由耦合关系算出。系统参数下表固定下来:

参数取值说明
基站发射功率 P30 dBm典型小基站发射功率
噪声功率 sigma^2-90 dBm含接收机噪声和带宽折算
STAR-RIS单元数 L4减小编码维度,便于解释PSO行为
路径损耗指数2.2 / 2.8直射链路和反射链路分别设置
用户数 K2功率域NOMA最小验证场景
2.2 等效信道与SIC解码条件

用户k的等效信道由直射链路和STAR-RIS级联链路叠加,反射区用户1的等效信道为:

h1 = h_d1 + h_br^T * diag(beta_r * exp(1j*theta_r)) * h_r1

透射区用户2同理:

h2 = h_d2 + h_br^T * diag(beta_t * exp(1j*theta_t)) * h_r2

其中h_br是基站到STAR-RIS的信道向量,h_r1h_r2是STAR-RIS到两个用户的信道向量。透射振幅beta_t = sqrt(1 - beta_r^2),逐单元按能量分裂协议计算。两级联信道的模值其实是相位对齐的结果,改变theta_rtheta_t会直接改变等效信道的强度,这就是PSO能同时优化功率分配和相位的原因。

NOMA的SIC顺序由等效信道模值决定:用户2先解码用户1的信号,再从残差里解码自己的信号。用户1直接解码自己,把用户2的信号当成干扰。两个用户的可达速率写成:

R1 = log2(1 + |h1|^2 * a1 * P / (|h1|^2 * a2 * P + 1)) R2 = log2(1 + |h2|^2 * a2 * P / 1)

这里做了单位化的噪声功率简化,Psigma^2一起折算成信噪比。用户2能成功执行SIC的条件是它解码用户1的速率不低于用户1自身的速率,通常表现为|h2|^2 > |h1|^2。注意这个不等号不是预设的,它由STAR-RIS的相位配置决定,所以优化时必须在适应度函数里显式检查。信道生成的numpy代码如下:

import numpy as np def generate_channel(L, seed=0): rng = np.random.default_rng(seed) # 基站到STAR-RIS信道,L个单元,复数高斯信道 h_br = (rng.standard_normal(L) + 1j*rng.standard_normal(L)) / np.sqrt(2) # STAR-RIS到两个用户的信道,反射区与透射区分别设置 h_r1 = (rng.standard_normal(L) + 1j*rng.standard_normal(L)) / np.sqrt(2) h_r2 = (rng.standard_normal(L) + 1j*rng.standard_normal(L)) / np.sqrt(2) # 直射链路,弱信道,幅度远低于级联链路 h_d1 = 0.1 * (rng.standard_normal() + 1j*rng.standard_normal()) / np.sqrt(2) h_d2 = 0.3 * (rng.standard_normal() + 1j*rng.standard_normal()) / np.sqrt(2) return h_br, h_r1, h_r2, h_d1, h_d2

h_brh_r1h_r2除以 sqrt(2)让实部虚部方差归一,直射链路乘0.1和0.3模拟遮挡差异。两个直射链路设置不同强度,是为了避免第二用户天然过强导致功率分配问题退化。想验证更极端的场景,把h_d2设成0即可完全模拟透射区被遮挡的情况。

2.3 优化问题P1的数学表述

把两个用户的速率加起来得到和速率,优化目标就清楚了:

maximize R1(a, beta_r, theta_r, theta_t) + R2(a, beta_r, theta_r, theta_t) subject to a1 + a2 = 1 # 功率分配归一化 0 <= a1, a2 <= 1 beta_r[l]^2 + beta_t[l]^2 = 1 # 能量分裂约束 0 <= beta_r[l] <= 1 0 <= theta_r[l], theta_t[l] < 2*pi |h2|^2 > |h1|^2 # SIC可行条件

这个问题的困难点不在变量个数,而在变量之间的耦合。功率分配系数直接影响SIC条件,SIC条件又依赖相位配置,相位配置又受振幅约束限制。L=4时有1个功率变量(另一个由归一化约束消掉)、4个振幅变量、8个相位变量,总共13个自由度,但目标函数没有凸性可言,梯度信息在相位周期边界还会跳变。PSO把所有变量压成一维数组,靠种群迭代逼近可行域局部最优,在这种中小规模参数空间里非常顺手。

3. PSO求解联合优化问题:编码与约束处理

3.1 为什么不用凸优化和梯度法

STAR-RIS系数的相位项exp(1j*theta)让目标函数呈现周期性和高度非线性。固定相位后,功率分配子问题可以用连续凸近似或分式规划处理;固定功率后,相位优化又变成无约束平滑问题。但两者交替优化常见的问题是收敛到鞍点:相位更新依赖功率分配的结果,功率分配又反过来依赖相位,两个子问题各自的局部最优凑不成全局最优。

梯度法在复平面相位上会遇到更实际的问题:相位是周期变量,thetatheta + 2*pi是同一个物理点,但普通梯度下降会把theta拉到边界外,投影映射又会破坏梯度的连续迭代逻辑。PSO的速度-位置更新公式天然支持把相位变换到[0, 2*pi),粒子群自身的位置散布也能绕过局部静止点,因此把PSO当作这个系统的基线求解器是合理的思路。

3.2 粒子向量结构与约束修复

粒子位置x采用一个一维实向量拼接设计:

x = [ a1(1维), beta_r(L维), theta_r(L维), theta_t(L维) ]

维度为1 + 3La2不进入粒子向量,由归一化约束直接算出a2 = 1 - a1,避免等式约束在PSO更新中被反复破坏。透射振幅同理,beta_t不占维度,修复时用sqrt(1 - beta_r^2)计算。

约束修复在每次位置更新之后立刻执行,顺序不能乱:

def repair(x, L): # 提取变量 a1 = x[0] beta_r = x[1:1+L] theta_r = x[1+L:1+2*L] theta_t = x[1+2*L:1+3*L] # 功率分配系数限制在[0,1],确保a2非负 a1 = np.clip(a1, 1e-6, 1 - 1e-6) a2 = 1.0 - a1 # 反射振幅限制在[0,1],透射振幅由能量分裂约束推出 beta_r = np.clip(beta_r, 0.0, 1.0) beta_t = np.sqrt(1.0 - beta_r**2) # 相位模到[0, 2*pi),避免周期性导致的速度累积 theta_r = np.mod(theta_r, 2*np.pi) theta_t = np.mod(theta_t, 2*np.pi) repaired = np.concatenate([[a1], beta_r, theta_r, theta_t]) return repaired, a2, beta_t

clipmod的作用要区分开:振幅约束是物理限制,越界直接截断;相位约束是周期性,取模才不会丢信息。a11e-6做两端保护,避免传给对数函数时出现0功率的退化情况。修复函数返回所有派生变量,后续适应度计算直接复用返回值。

3.3 PSO速度-位置更新公式与核心代码

标准PSO更新公式按惯性权重w、个体学习因子c1、社会学习因子c2组织:

v = w * v + c1 * r1 * (pbest - x) + c2 * r2 * (gbest - x) x = x + v

r1r2[0,1]均匀分布的随机数,每次迭代重新采样。速度更新后的位置必须马上调用repair函数,否则约束在连续几次迭代中会被逐步拉出可行域。核心更新代码:

def pso_update(x, v, pbest, gbest, L, w, c1, c2, rng): r1 = rng.random(x.shape) r2 = rng.random(x.shape) v = w * v + c1 * r1 * (pbest - x) + c2 * r2 * (gbest - x) # 可选:限制最大速度,防止粒子飞出搜索空间 v = np.clip(v, -0.5, 0.5) x_new = x + v x_new, _, _ = repair(x_new, L) return x_new, v

速度限幅[-0.5, 0.5]是我在做STAR-RIS相位优化时加上的。不限制速度时,相位变量在取模后仍可能因为单次步长过大而在[0, 2*pi)两端跳来跳去,限制速度能显著提升前100代的收敛稳定性。超参数的经验取值放在固定表格里:

超参数推荐值调参方向
种群规模 N30变量维度超过20时适当增大
迭代次数 T200看收敛曲线平台期位置
惯性权重 w0.6线性衰减到0.3更稳
学习因子 c11.5偏个体探索
学习因子 c21.5偏群体收敛

4. 完整实现:主循环、参数设置与结果解读

4.1 适应度函数与PSO主循环装配

适应度函数需要把修复后变量还原成系统参数,计算两个用户的SIC条件和可达速率。SIC检查失败时直接返回很小的值(比如-1e6),否则PSO会把SIC不可行的粒子当成可行解保留下来。完整适应度函数:

def fitness(x, h_br, h_r1, h_r2, h_d1, h_d2, P, L): x, a2, beta_t = repair(x, L) a1 = x[0] beta_r = x[1:1+L] theta_r = x[1+L:1+2*L] theta_t = x[1+2*L:1+3*L] # 级联信道,只保留反射或透射单元的作用 h1 = h_d1 + np.sum(h_br * beta_r * np.exp(1j*theta_r) * h_r1) h2 = h_d2 + np.sum(h_br * beta_t * np.exp(1j*theta_t) * h_r2) # SIC可行条件,信道强度不满足直接放弃 if np.abs(h2)**2 <= np.abs(h1)**2: return -1e6 # 单位化噪声,P按线性功率比折算 r1 = np.log2(1 + np.abs(h1)**2 * a1 * P / (np.abs(h1)**2 * a2 * P + 1)) r2 = np.log2(1 + np.abs(h2)**2 * a2 * P / 1) return r1 + r2

repairfitness里再调用一次是故意的,因为主循环里的位置可能来自上一轮的随机初始化,没有经过修复函数。这样设计虽然让修复逻辑跑了两次,但能保证无论哪个入口拿到粒子都不会越界。h1h2np.sum完成L个STAR-RIS单元的级联累加,比显式构造对角矩阵省内存。

主循环的逻辑是标准的PSO骨架:先初始化种群,评估一次适应度,然后迭代更新位置、修复、重评估、刷新个体与全局最优。

def run_pso(h_br, h_r1, h_r2, h_d1, h_d2, P=30.0, L=4, N=30, T=200, w=0.6, c1=1.5, c2=1.5, seed=0): rng = np.random.default_rng(seed) # 功率、反射振幅、反射相位、透射相位的边界采样 a1_init = rng.random((N, 1)) beta_init = rng.random((N, L)) theta_init = rng.uniform(0, 2*np.pi, (N, 2*L)) x_pop = np.concatenate([a1_init, beta_init, theta_init], axis=1) v_pop = rng.normal(0, 0.1, x_pop.shape) pbest = x_pop.copy() gbest = x_pop[0].copy() fitness_history = [] for _ in range(T): for i in range(N): x_pop[i], _ = pso_update( x_pop[i], v_pop[i], pbest[i], gbest, L, w, c1, c2, rng ) # 重新评估适应度,保留可行最优 fit_i = fitness(x_pop[i], h_br, h_r1, h_r2, h_d1, h_d2, P, L) if fit_i > fitness(pbest[i], h_br, h_r1, h_r2, h_d1, h_d2, P, L): pbest[i] = x_pop[i].copy() if fit_i > fitness(gbest, h_br, h_r1, h_r2, h_d1, h_d2, P, L): gbest = x_pop[i].copy() best_fit = fitness(gbest, h_br, h_r1, h_r2, h_d1, h_d2, P, L) fitness_history.append(best_fit) return gbest, fitness_history

第41行到第46行的fitness调用次数偏多,每次比较都重新计算当前最优值。更快的做法是缓存pbestgbest的适应度值,用变量保存而不是反复调用函数。上面代码保留多次调用是为了让逻辑更直白,实际跑实验时我会缓存,评估次数能省三分之一左右。

4.2 关键参数速查表

初始化和运行时参数分为两组,一组是信道和系统参数,另一组是PSO的搜索参数。下面这张表可以作为调试前的默认配置:

参数推荐值作用
用户数 K2本文所有示例固定双用户
STAR-RIS单元数 L4小规模模型验证算法行为
种群规模 N30粒子个数,维度3L+1的2倍以上
迭代次数 T200观察收敛曲线的平台期
惯性权重 w0.6 → 0.3线性衰减优于常数权重
学习因子 c1, c21.5, 1.5经典参数组合
速度限幅[-0.5, 0.5]相位变量需要小步长

L=4时粒子维度是13,N=30已经足够覆盖;把L增加到8以后,粒子维度升到25,建议把N一并放到50。PSO种群太小容易早熟,种群太大又会让单次评估的计算量拖慢实验。迭代次数先按300设置,观察收敛曲线进入平台期后回缩。

4.3 收敛曲线的形态与可行性判断

跑完主循环后,把fitness_history直接画成随迭代次数的曲线。典型特征是第一轮随机初始化后,可行粒子的和速率就有一个基础值,因为a1a2是由随机采样归一化得到的,即使STAR-RIS相位没有对齐,功率分配也已经满足了NOMA的基本约束。前20代曲线会快速上升,大量粒子把功率分配和相位从随机区域拽到可行区域;之后进入慢速提升阶段,每代只提升零点几个bit/s/Hz;最后50代基本走平。

判断优化结果是否合理,不要只看最后一代的数值。要把最终粒子代表的a1a2拿出来看:正常情况下a1应该明显大于a2,因为用户1的信道差需要更多功率才能维持SIC解码。如果优化结束后a1小于0.5,大概率是SIC检查条件没有生效,或者用户2透射信道建模得太弱。另一个判断点是beta_r的分布,能量分裂协议下,反射和透射振幅应该呈现明显的分组特征,而不是全部挤在0.5附近。

4.4 三个典型踩坑场景

坑一:SIC条件检查放在fitness最后而不是开头。有些人先算R1R2,最后再判断SIC,这样失效粒子仍然会产生一个有限的正和速率,PSO会把这些无效解留在种群里,收敛曲线看似正常,实际相位配置完全不可用。

坑二:相位取模和速度更新顺序颠倒。如果先取模再做速度更新,速度向量里累积的相位差会被取模操作抹掉,粒子在相位环上原地打转,曲线表现为前几十代几乎不上升。先更新速度再取模,保持迭代的一致性。

坑三:粒子维度覆盖不完全。不少实现只优化功率分配和反射相位,把透射相位当成固定值,反而丢掉了STAR-RIS最关键的透射自由度。L=4时透射相位就是4个变量,去掉之后系统退化成普通RIS,标题里的“STAR”就名不副实了。

5. 收敛曲线与对比实验:迭代次数怎么定

做PSO和强化学对比实验时,最常见的问题是横轴数量级对不上。PSO的横轴是迭代次数,强化学习是训练回合数,直接画在一起,PSO看起来几百代就收敛,强化学习几千回合还在震荡,但两者实际算力消耗完全不同。正确的对齐方式是让横轴代表“评估次数”:PSO每迭代一次要做N次候选解评估,所以横轴换算成iteration * population_size;强化学习则按环境步数或update次数折算。这样两条曲线的横轴都代表相同的优化算力消耗,收敛速度的对比才有意义。

迭代次数本身怎么定,我的经验是先跑一个短预算比如150代,把收敛曲线打印出来看平台位置。如果150代末尾还在明显上升,继续加代到300;如果曲线在50代就已经走平,说明搜索空间相对简单或种群已经聚集。更严谨的做法是设置早停条件:连续20代最佳适应度改进小于1e-4就停止。注意网格搜索式的“固定迭代次数”只适合论文图表展示,工程验证建议用早停加固定次数双轨确认。

横轴对齐与早停的检查代码可以这样组织:

def early_stop(fitness_history, patience=20, tol=1e-4): if len(fitness_history) < patience: return False recent = np.array(fitness_history[-patience:]) return np.max(np.abs(np.diff(recent))) < tol # 在run_pso的循环里,每迭代一次判断一次

另一个能提升对比实验说服力的技巧是热启动。把上一次跑出的gbest作为下一次种群的初始位置,叠加一个协方差很小的高斯扰动,PSO会在已找到的最优邻域继续精细搜索。此时收敛曲线的起点就是上一次的末点,横轴按累计评估次数对齐,能直观看到热启动比冷启动省掉了前期探索的那一大段。保存上一轮的gbest时同时保存它对应的fitness_history,后续绘图用同一个累计横轴函数统一折算,就不需要人为对齐两个实验的步长口径。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询