meent开源库实战:RCWA/TMM原理、实现与超表面优化避坑指南
1. 项目概述与核心价值
如果你正在设计光子晶体、超表面或者任何带有周期性微纳结构的光学器件,那么“仿真”这一步几乎是绕不开的。无论是想优化一个光栅耦合器的耦合效率,还是设计一个能将特定波长光高效偏转的衍射元件,你都需要一个可靠的工具来预测光与结构相互作用的结果。在众多电磁仿真方法中,严格耦合波分析(RCWA)因其在处理周期性结构时的天然优势和计算效率,成为了这个领域的“标准答案”之一。然而,从教科书上的矩阵方程到真正能跑出可靠结果的代码,中间隔着巨大的鸿沟——数值稳定性、计算效率、以及如何将复杂的数学公式转化为清晰可维护的程序逻辑,这些都是实践中会遇到的真实挑战。
最近,我在一个超表面优化项目中深入使用了meent这个开源Python库。它完整实现了RCWA结合传输矩阵法(TMM)的求解流程,并且原生支持自动微分(AD),这对于基于梯度的器件逆向设计来说简直是“神器”。更重要的是,它的代码结构相当清晰,把卷积矩阵生成、特征值求解、场连接等核心步骤都模块化了,非常适合用来学习和理解RCWA的底层实现。本文将结合我的使用经验,带你从RCWA/TMM的核心原理出发,一步步拆解meent的实现,并分享在实战中关于精度、速度和稳定性的那些“坑”与技巧。无论你是刚接触计算电磁学的学生,还是需要快速上手一个可靠仿真工具的研究者,相信都能从中找到有价值的内容。
2. RCWA与TMM:从物理方程到矩阵求解
2.1 核心思想:在傅里叶空间中求解麦克斯韦方程
RCWA的基本思想非常直观:对于一个在x和y方向具有周期性的结构(光栅),其中的电磁场也可以展开成具有相同空间频率的傅里叶级数。这就是弗洛奎特-布洛赫定理的体现。我们将介电常数分布ε(x, y)也进行傅里叶展开。这样,时谐麦克斯韦方程组中的旋度方程,在傅里叶空间中就从微分方程变成了代数方程。
具体来说,我们从麦克斯韦旋度方程出发:∇ × E = -jωμH和∇ × H = jωεE。 在傅里叶空间中,空间微分算符∇变成了波矢k的乘法运算。对于周期性结构,k可以写为入射波矢k_inc加上倒格矢的整数倍(n, m),即k_x, k_y构成对角矩阵Kx和Ky。介电常数及其倒数在傅里叶空间中的表示,则是一个托普利茨(Toeplitz)矩阵,也称为卷积矩阵,记作[ε]和[1/ε]。
将电场E和磁场H的傅里叶级数系数向量代入方程,经过一系列推导(核心是消去z方向的导数,得到关于横向场分量的方程),最终可以化归为一个标准的特征值问题:∂^2/∂z^2 [E_x, E_y]^T = -k0^2 * A * [E_x, E_y]^T其中矩阵A由Kx,Ky,[ε],[1/ε]等构成。求解这个特征值问题,我们就能得到每一层光栅中可能存在的传播模式(特征模)及其在z方向的传播常数(特征值)。
注意:符号约定的重要性在推导中,一个非常容易出错的地方是符号约定。例如,原文公式中提到的负号调整,是为了确保旋度运算
E × H的方向与波传播方向k_g,z一致。不同的文献可能采用e^(jωt)或e^(-iωt)的时谐约定,这会影响到所有虚数单位j的符号,进而影响特征值问题的矩阵构造。meent内部采用e^(-iωt)约定(这是物理学中的常见约定),如果你从采用e^(jωt)约定的工程文献推导公式,直接套用可能会导致结果错误。在阅读代码和公式时,务必先确认其时空谐约定。
2.2 层间连接:传输矩阵法(TMM)的精髓
求解出单层的特征模后,我们得到了该层内上行波和下行波的系数。但一个器件通常是多层结构(比如基底、光栅层、覆盖层)。TMM的作用就是优雅地处理这些层之间的边界条件。
在每一层的界面处,电磁场的切向分量(E_x, E_y, H_x, H_y)必须连续。我们将这些条件用矩阵形式写出。对于第ℓ层,其界面上的场可以用该层的特征向量矩阵W_ℓ、特征值矩阵q_ℓ以及层厚度d_ℓ构成的相位矩阵X_ℓ来表示。
以原文中的公式为例,在输入边界(z=0)和输出边界(z=d)处,我们可以建立联系入射场I、反射场系数R、透射场系数T以及各层内部模式系数c^±的大型线性方程组。通过消去内部系数c^±,最终可以得到一个简洁的传输矩阵关系:[I; R] = M_total * [T; 0]其中M_total是所有层传输矩阵的连乘。从这个关系式中,我们就能直接解出我们关心的反射系数R和透射系数T。
2.3 衍射效率的计算:从复振幅到真实功率
得到复振幅系数R和T后,我们最终需要的是可测量的物理量——衍射效率(DE),即衍射到某一级次的光功率与入射光功率的比值。这里需要用到坡印廷矢量进行时间平均计算。
对于第(n, m)级衍射,其反射效率DE_r和透射效率DE_t的计算公式如原文所示。公式中包含了Re(k_z / (k0 n cosθ))这样的因子,它本质上是z方向波矢的归一化,确保了对于倏逝波(k_z为虚数),其衍射效率为零,因为倏逝波不携带能量传播。这是RCWA计算中一个非常重要的自洽性检查点:所有传播级次(k_z为实数)的衍射效率之和应等于1(无损耗时),或小于1(有材料吸收时)。
3. meent库实战:从安装到第一个仿真
3.1 环境搭建与基础概念
meent可以通过pip直接安装:pip install meent。它支持NumPy、JAX和PyTorch三种后端。选择哪个后端取决于你的需求:
- NumPy (backend=0):最稳定,兼容性最好,但不支持自动微分(AD)。
- JAX (backend=1):支持AD,并且通过JIT(即时编译)可以获得不错的性能,但在处理超大矩阵(高傅里叶级数)时,首次编译开销和内存管理可能成为瓶颈。
- PyTorch (backend=2):支持AD,生态完善,GPU支持良好,是进行梯度优化研究的推荐选择。
初始化一个仿真实例非常简单:
import meent import numpy as np # 定义一个一维光栅结构(2层,每层10个像素) ucell = np.array([ [[1, 1, 1, 3.48, 3.48, 3.48, 3.48, 1, 1, 1]], # 第一层:空气(1)和硅(3.48) [[1, 3.48, 3.48, 1, 1, 1, 1, 3.48, 3.48, 1]], # 第二层 ]) # 形状为 (层数, Y方向像素数, X方向像素数)。对于1D光栅,Y方向为1。 # 创建仿真器 mee = meent.call_mee( backend=0, # 使用NumPy后端 grating_type=0, # 1D光栅,非锥形入射 pol=0, # TE偏振 (pol=0 -> ψ=90°) n_I=1.0, # 上覆层(通常是空气)折射率 n_II=1.45, # 衬底(例如二氧化硅)折射率 theta=0., # 入射角(弧度),0表示正入射 phi=0., # 方位角(弧度) wavelength=900, # 真空波长,单位nm fourier_order=10, # 傅里叶截断阶数,实际使用21个谐波(-10到10) period=[1000], # 光栅周期,单位nm ucell=ucell, # 定义的结构 thickness=[200, 300], # 每层的厚度,单位nm,顺序从上到下 fft_type=0, # 使用离散傅里叶级数(DFS)进行栅格建模 )这段代码定义了一个简单的双层一维光栅。ucell是一个三维数组,直接定义了每个像素点的介电常数(或折射率的平方)。fft_type=0表示使用离散傅里叶变换(DFS)来处理这种像素化的结构。
3.2 两种结构建模方式:栅格与矢量
meent提供了两种定义结构的方式,对应不同的应用场景和精度需求。
栅格建模 (Raster Modeling,fft_type=0 或 1)这就是上面例子中使用的方式。你将设计区域离散化为一个个小像素(体素),并为每个像素赋值介电常数。这种方式非常灵活,可以描述任意形状,是拓扑优化(Topological Optimization)的天然选择,因为每个像素都可以作为一个独立的设计变量。
fft_type=0: 使用离散傅里叶级数(DFS)。计算速度快,但当像素尺寸与特征尺寸相比不够精细时,会产生“阶梯误差”和吉布斯现象,导致精度下降。fft_type=1: 使用连续傅里叶级数(CFS)处理栅格。它通过解析积分计算傅里叶系数,精度高于DFS,尤其对于高对比度结构,但计算量稍大,且当前版本不支持反向传播(AD)。
矢量建模 (Vector Modeling,fft_type=2)这种方式直接描述几何形状的边界。例如,你可以定义一个旋转矩形、椭圆等。meent内部会将这些形状分解为多个与坐标轴对齐的子矩形,然后利用CFS和sinc函数精确计算其傅里叶系数。
import torch # 使用PyTorch后端以支持后续梯度计算 mee = meent.call_mee(backend=2, grating_type=2, fft_type=2, period=[1000, 1000], ...) # 定义一个旋转矩形作为结构 length_x = torch.tensor(400.0, requires_grad=True) # 将尺寸设为可求导变量 length_y = torch.tensor(600.0, requires_grad=True) angle = torch.tensor(30 * np.pi / 180, requires_grad=True) center = [500, 500] # 单元中心坐标 n_split = [20, 20] # 将形状近似为20x20个小矩形,精度更高 n_index = 3.48 # 结构材料折射率(硅) # 创建旋转矩形对象 rect_obj = mee.rectangle_rotate(*center, length_x, length_y, *n_split, n_index, angle) # 定义层信息:基底折射率 + 对象列表 layer_info_list = [[1.0, [rect_obj]]] # 基底为空气(1.0),上面有一个硅矩形 mee.draw(layer_info_list) # 可视化结构矢量建模的精度通常高于栅格建模,特别是对于光滑边界。它适用于形状导数(Shape Derivative)优化,即优化结构的尺寸、位置、旋转角度等参数,而保持拓扑不变。n_split参数控制着近似精度,值越大,分解的子矩形越多,形状越精确,但计算量也越大。
实操心得:如何选择建模方式?
- 做拓扑优化(像素级变化):无脑选栅格建模 (
fft_type=0)。你可以方便地将ucell的每个元素作为优化变量。- 做形状/尺寸优化(如优化圆柱半径、光栅占空比):优先选矢量建模 (
fft_type=2)。它得到的梯度(形状导数)更准确,且设计变量更少,优化问题更简洁。- 追求最高精度进行验证计算:如果结构是简单几何形状,用矢量建模+高
n_split。如果是复杂形状,可以尝试栅格建模+fft_type=1(CFS),并与商业软件结果交叉验证。- 处理结构重叠:在矢量建模中,
layer_info_list中对象的顺序决定了堆叠层次。列表后面的对象会覆盖前面的对象。这为你设计复杂多层结构提供了灵活性。
3.3 运行仿真与结果提取
设置好结构后,运行仿真只需要一行代码:
de_ri, de_ti = mee.conv_solve()de_ri和de_ti分别是反射和透射的衍射效率数组。对于1D光栅,它们是一维数组,索引对应衍射级次。例如,de_ti[10]对应透射的+10级衍射效率。0级通常对应镜面反射或直接透射。
如果你想进一步分析器件内部的电磁场分布,可以使用conv_solve_field方法或先conv_solve再calculate_field:
# 方法1:一次性计算效率和场 de_ri, de_ti, field_cell = mee.conv_solve_field(res_x=50, res_y=50, res_z=30) # 方法2:分两步 de_ri, de_ti = mee.conv_solve() field_cell = mee.calculate_field(res_x=50, res_y=50, res_z=30)field_cell是一个四维数组,其形状和含义取决于光栅类型:
- 1D光栅 (TE或TM):形状为
(总Z层数, res_y, res_x, 3)。最后一维的3个分量分别是:- TE偏振:
[Ey, Hx, Hz] - TM偏振:
[Hy, Ex, Ez]
- TE偏振:
- 1D锥形入射或2D光栅:形状为
(总Z层数, res_y, res_x, 6)。最后一维是[Ex, Ey, Ez, Hx, Hy, Hz]六个场分量。
你可以方便地使用matplotlib等工具将场分布可视化,这对于理解器件内部的光场增强、模式耦合等物理现象至关重要。
4. 深入核心:数值实现的关键技术与避坑指南
4.1 卷积矩阵生成:精度与效率的权衡
RCWA中至关重要的一步是将实空间的介电常数分布ε(x,y)转换到傅里叶空间,形成卷积矩阵[ε]和[1/ε]。meent在convolution_matrix.py中实现了三种方法,对应不同的fft_type。
离散傅里叶级数 (DFS,fft_type=0)这是最直接的方法:对离散的像素值进行FFT,得到傅里叶系数。然而,对于突变界面(如从空气到硅),DFS会引入严重的吉布斯振荡,导致收敛缓慢甚至错误。meent提供了一个improve_dft选项(默认开启),其原理是通过上采样(复制像素)来增加采样频率,从而缓解混叠效应。这被称为“增强型DFS”。在我的测试中,开启此选项能将与高精度参考解(如Reticolo)的误差降低约3个数量级。
连续傅里叶级数 - 栅格 (CFS-raster,fft_type=1)这种方法将每个像素视为一个均匀的矩形块,并对其进行解析傅里叶变换。对于每个谐波(m, n),其傅里叶系数ε_{m,n}可以通过二维sinc函数精确计算。这本质上是对阶梯近似结构的精确傅里叶分析,精度远高于DFS,是meent中精度最高的栅格建模选项。但请注意,它目前不支持自动微分。
连续傅里叶级数 - 矢量 (CFS-vector,fft_type=2)这是精度最高的方法。它直接对解析的几何形状(如矩形、椭圆)进行傅里叶变换。meent会将旋转后的形状分解为多个与坐标轴对齐的小矩形,然后对每个小矩形进行解析积分并求和。n_split参数控制分解的精细程度,值越大,对原始形状的近似越好,计算量也越大。
避坑指南:傅里叶截断阶数 (Fourier Order) 的选择这是RCWA计算中最重要的参数之一,直接决定了计算量和精度。选择过小,结果不收敛;选择过大,计算时间激增(复杂度 ~ O(N^3)),且可能引入数值误差。
- 经验法则:对于折射率对比度不高的结构(如硅/空气),通常取
period/wavelength * 10作为起始点进行扫描。例如,周期1000nm,波长900nm,可以从order=10开始。- 收敛性测试:必须进行。针对你的具体结构,逐步增加
fourier_order,观察关心的衍射效率(如+1级透射)是否趋于稳定。当连续增加阶数,结果变化小于你的容忍度(如1e-3)时,即可认为收敛。- 矢量建模的额外考虑:在矢量建模中,由于边界是光滑的,通常比栅格建模收敛得更快,可以用更低的阶数获得相同精度。
- 内存警告:傅里叶阶数
N会生成(2N+1)^2大小的矩阵(2D光栅)。N=100时,矩阵维度是201x201,进行特征值分解和矩阵求逆操作对内存和算力要求很高。务必根据你的硬件条件合理设置。
4.2 增强透射矩阵法 (ETM):攻克数值不稳定性
在标准的TMM实现中(如原文公式61),需要计算包含X_ℓ矩阵逆的项。X_ℓ是一个对角矩阵,其元素为exp(-k0 * q * d),其中q是特征值。对于厚层或衰减很快的倏逝波模式,q的实部很大,导致exp(-k0 * q * d)的值极其微小,甚至低于机器精度。求逆这样的矩阵会引发严重的数值不稳定,结果可能溢出或产生巨大误差。
meent采用了增强透射矩阵法(ETM)来解决这一问题。其核心思想是避免直接对X_ℓ求逆。如原文第G.5节所示,通过巧妙的变量代换(T = A^{-1} X T'),将问题转化为从最后一层向前一层的递推关系,在递推过程中,X_ℓ只参与乘法运算,而不需要求逆。这极大地提升了数值稳定性,使得仿真厚层结构或高折射率对比度结构成为可能。
在实际使用中,你无需手动操作,meent的_solve()方法内部已经实现了ETM。但了解这一原理非常重要,因为当你自己编写RCWA代码或使用其他某些库时,可能会遇到“矩阵奇异”的报错,其根源往往就在这里。
4.3 特征值问题的求解:后端性能深度剖析
RCWA中最耗时的步骤是求解广义特征值问题(对于各向同性介质,可化为标准特征值问题)。meent支持三种后端,其性能差异显著,选择不当会极大影响工作效率。
我基于一个固定的1D光栅结构,在三种不同配置的服务器上,对64位和32位精度下的NumPy、JAX、PyTorch后端在CPU和GPU上的性能进行了全面测试。以下是我的核心发现和建议:
1. 后端选择策略
- 追求极致稳定与兼容性:选NumPy (CPU)。它的线性代数库(如OpenBLAS或MKL)经过多年优化,非常稳健,是交叉验证结果的“金标准”。缺点是慢,且不支持AD。
- 进行梯度优化研究:首选PyTorch。它的生态完善,GPU支持好,自动微分接口直观。在我的测试中,PyTorch CPU版本在大多数情况下比NumPy快1.5-2倍。PyTorch GPU版本在中小规模矩阵运算上也能带来加速,但对于最大的特征值分解(Eig)这一步,由于PyTorch/JAX目前仍调用CPU的LAPACK库,数据在CPU/GPU间传输会产生开销,导致加速比并不理想,有时甚至更慢。
- 尝试JAX:JAX的JIT编译理论上能带来性能提升,但在处理RCWA这种动态形状(矩阵维度随
fourier_order变化)的问题时,每次参数变化都可能触发重新编译,产生显著开销。在测试中,JAX在处理大阶数(FTO>200)时经常因内存问题失败。目前不建议用于生产环境的大规模计算,除非你的问题规模固定且可完全静态编译。
2. 精度选择:64位 vs 32位
- 32位 (float32/complex64):计算速度通常更快,内存占用减半。对于许多光学仿真,如果设计目标效率在1%量级,32位精度可能足够。
- 64位 (float64/complex128):强烈推荐用于最终验证和发表结果。32位精度只有约7位有效数字,在计算大量级数求和、处理非常小或非常大的特征值时,累积误差可能不可忽视。特别是当你的优化目标涉及极高效率(如99.9%)时,32位误差可能导致错误结论。
- 一个关键陷阱:NumPy的
eig函数在输入32位数组时,内部计算仍以64位进行,最后将结果截断为32位返回。因此,NumPy的32位模式并不会节省特征值分解的计算时间,节省的只是内存拷贝和后续矩阵运算的时间。而JAX和PyTorch的32位eig是真正的32位运算。
3. 硬件利用心得
- CPU核心数:RCWA的矩阵运算(特征值分解、矩阵乘法)是高度并行的。确保你的NumPy/PyTorch链接了多线程BLAS库(如OpenBLAS, MKL),并设置合适的线程数(如
export OMP_NUM_THREADS=8)。 - GPU的适用场景:当前,GPU的加速收益主要体现在卷积矩阵生成、场分布重建等可并行操作上。对于最耗时的特征值分解,由于缺乏专用的GPU实现,瓶颈仍在CPU。因此,不要期望换上GPU就能获得数量级的提升。只有当你的工作流包含大量重复的、小规模矩阵运算(如优化迭代中前向计算)时,GPU+PyTorch的组合才可能显示出优势。
下表总结了不同场景下的后端选择建议:
| 应用场景 | 推荐后端 | 精度 | 关键理由 |
|---|---|---|---|
| 结果验证、交叉比对 | NumPy (CPU) | 64位 | 最稳定,作为基准参考 |
| 梯度优化(研究) | PyTorch (CPU/GPU) | 64位(验证)/32位(迭代) | AD支持好,生态成熟,CPU版本已较快 |
| 大规模参数扫描(无梯度) | NumPy (CPU) | 32位/64位 | 稳定,内存管理可靠 |
| 探索性代码开发 | JAX (CPU) | 64位 | 语法简洁,适合快速原型 |
5. 常见问题排查与实战技巧
5.1 仿真结果不收敛或异常
这是最常见的问题。请按照以下清单逐步排查:
- 检查傅里叶阶数:这是首要怀疑对象。逐步增加
fourier_order,观察结果是否趋向一个稳定值。如果结果剧烈振荡或不收敛,很可能阶数太低。 - 检查网格分辨率(栅格建模):对于
fft_type=0,确保ucell的像素数足够多,能够准确描述结构特征。一个经验法则是,每个波长范围内至少需要10-20个像素。可以尝试增加像素数或切换到fft_type=1(CFS-raster) 看结果是否变化。 - 检查材料参数:确认折射率是复数(
n + ik)吗?k是消光系数,代表吸收。如果材料有吸收(k>0),所有衍射级次效率之和应小于1。如果k=0但效率和大于1,肯定出错了。 - 检查边界条件:确认
n_I(上覆层)和n_II(衬底)设置正确。特别是当光从衬底入射时,不要弄反。 - 验证能量守恒:计算所有传播级次(
k_z为实数)的反射效率sum(de_ri)和透射效率sum(de_ti)之和。在无吸收的情况下,它应该非常接近1(例如0.999~1.001)。如果偏差很大(>1%),说明计算可能有问题。 - 与简单案例对比:仿真一个已知解析解或结果非常简单的结构,如均匀介质层(应只有0级反射和透射)或一个非常浅的光栅(可用微扰论验证)。用
meent计算,看结果是否合理。
5.2 自动微分(AD)梯度计算相关问题
meent支持通过JAX和PyTorch后端进行自动微分,这是其核心优势之一。
梯度为None或错误:
- 确保参数
requires_grad=True:在PyTorch中,需要将优化变量用torch.tensor(..., requires_grad=True)定义。 - 检查计算图:确保整个仿真流程(从结构参数到最终目标函数,如衍射效率)都在同一个后端框架内完成。避免中途混用NumPy数组。
- 使用正确的损失函数:AD计算的是标量损失函数相对于输入参数的梯度。确保你的目标(如
-de_ti[1]表示最大化+1级透射)是一个标量。
- 确保参数
梯度爆炸或消失:
- 这在拓扑优化中很常见。像素的介电常数作为变量,梯度可能非常大。尝试使用梯度裁剪(
torch.nn.utils.clip_grad_norm_)或更小的学习率。 - 考虑在优化中引入平滑滤波或投影步骤,以稳定优化过程并获得更物理可实现的结构。
- 这在拓扑优化中很常见。像素的介电常数作为变量,梯度可能非常大。尝试使用梯度裁剪(
形状导数 vs 拓扑导数:
- 拓扑导数:对应于栅格建模 (
fft_type=0)。梯度是目标函数对每个像素介电常数的导数。优化会自由地改变每个像素的材料,拓扑(连通性)不固定。 - 形状导数:对应于矢量建模 (
fft_type=2)。梯度是目标函数对几何参数(如长度、半径、角度)的导数。优化过程中,结构的拓扑保持不变。 - 根据你的制造约束(是灰度曝光还是限定几何形状)选择合适的优化方式。
- 拓扑导数:对应于栅格建模 (
5.3 性能优化技巧
- 预热运行:对于JAX和PyTorch,第一次运行会因为编译或图构建而较慢。在正式计时或优化循环开始前,先使用一组典型参数运行几次仿真,让系统完成编译。
- 批量计算:如果需要扫描波长或角度,尽量使用向量化操作。
meent的部分函数支持批量输入,或者你可以用for循环结合vmap(JAX) 或torch.vmap(PyTorch) 来实现并行计算,这比串行循环快得多。 - 控制内存:大傅里叶阶数会消耗巨量内存。如果遇到内存错误(OOM),首先尝试降低
fourier_order到刚好收敛的程度。其次,可以尝试使用32位精度。在PyTorch中,使用torch.cuda.empty_cache()及时清理GPU缓存。 - 选择性计算:如果只关心反射或透射,
meent的conv_solve是最高效的。只有需要分析场分布时,才调用calculate_field,因为场重建的计算量和内存消耗也很大。
6. 案例:一维超光栅衍射器优化
让我们用一个简化的案例来串联所有知识点。目标是设计一个位于二氧化硅衬底上的一维硅超光栅,在正入射TM偏振光(波长900nm)下,将光高效地衍射到+1级透射方向。
import torch import meent import numpy as np import matplotlib.pyplot as plt # 1. 初始化仿真器 (使用PyTorch后端以支持AD) period = 1000 # nm wavelength = 900 # nm n_silicon = 3.48 + 0.001j # 硅在900nm附近的复折射率,微小吸收 n_sio2 = 1.45 fourier_order = 15 # 初步测试后确定的收敛阶数 # 2. 定义可优化结构 (栅格建模,拓扑优化) num_pixels = 64 # 初始化设计变量:用一个可训练的Tensor表示每个像素的“材料密度”,范围[0,1] rho = torch.rand(num_pixels, requires_grad=True) * 0.5 + 0.25 # 初始值在0.25-0.75之间 rho = torch.nn.Parameter(rho) # 将其注册为模型参数 # 3. 定义优化器 optimizer = torch.optim.Adam([rho], lr=0.05) # 使用Heaviside投影将连续密度二值化,并加入平滑滤波避免棋盘格效应 beta = 1.0 eta = 0.5 def projection(x, beta, eta): """可微分的二值化投影函数""" return torch.tanh(beta * eta) + torch.tanh(beta * (x - eta)) / torch.tanh(beta * (1 - eta)) # 4. 优化循环 num_iterations = 200 efficiency_history = [] for i in range(num_iterations): optimizer.zero_grad() # 将密度变量投影到[0,1]并二值化 rho_proj = projection(rho, beta, eta) # 可选:应用卷积滤波进行平滑 # rho_filtered = ... # 根据二值化密度生成介电常数分布:硅或空气 epsilon = rho_proj * (n_silicon**2 - 1.0) + 1.0 # 空气介电常数为1 # 构建单元结构 (单层,1D) ucell = epsilon[None, None, :] # 形状变为 (1, 1, num_pixels) # 创建仿真实例 mee = meent.call_mee( backend=2, # PyTorch grating_type=0, pol=1.0, # TM偏振 (pol=1 -> ψ=0°) n_I=1.0, # 上覆空气 n_II=n_sio2, # 衬底二氧化硅 theta=0.0, phi=0.0, wavelength=wavelength, fourier_order=fourier_order, period=[period], ucell=ucell.detach().cpu().numpy(), # meent接收numpy数组 thickness=[300], # 光栅层厚度300nm fft_type=0, improve_dft=True, ) # 运行仿真 de_ri, de_ti = mee.conv_solve() # 目标:最大化+1级透射效率 target_efficiency = de_ti[fourier_order + 1] # 索引对应+1级 loss = -target_efficiency # 最小化负效率 # 反向传播 loss.backward() optimizer.step() # 记录并打印 efficiency_history.append(target_efficiency.item()) if i % 20 == 0: print(f'Iter {i:3d}, Efficiency: {target_efficiency.item():.4f}, Loss: {loss.item():.4f}') # 逐渐增加二值化强度 if i % 40 == 0 and i > 0: beta = min(beta * 1.2, 50) # 缓慢增加beta,使投影更接近阶跃 # 5. 结果可视化 plt.figure(figsize=(12, 4)) plt.subplot(1, 3, 1) plt.plot(efficiency_history) plt.xlabel('Iteration') plt.ylabel('+1st Order Transmission Efficiency') plt.grid(True) plt.subplot(1, 3, 2) final_structure = projection(rho, beta, eta).detach().cpu().numpy() plt.bar(range(num_pixels), final_structure) plt.xlabel('Pixel Index') plt.ylabel('Silicon Density (0=Air, 1=Si)') plt.title('Optimized Binary Structure') # 6. 验证最终性能 (用高精度模式再算一次) mee_verify = meent.call_mee( backend=0, # 换用更稳定的NumPy验证 grating_type=0, pol=1.0, n_I=1.0, n_II=n_sio2, theta=0.0, phi=0.0, wavelength=wavelength, fourier_order=30, # 使用更高的阶数验证收敛性 period=[period], # 将连续密度二值化为0或1 ucell=(final_structure > 0.5).astype(float)[None, None, :] * (n_silicon**2 - 1) + 1, thickness=[300], fft_type=1, # 使用更高精度的CFS-raster ) de_ri_final, de_ti_final = mee_verify.conv_solve() final_eff = de_ti_final[30 + 1] # +1级 plt.subplot(1, 3, 3) orders = range(-5, 6) # 显示-5到+5级 plt.bar(orders, de_ti_final[30 + np.array(orders)], alpha=0.7, label='Transmission') plt.bar(orders, de_ri_final[30 + np.array(orders)], alpha=0.7, label='Reflection', bottom=de_ti_final[30 + np.array(orders)]) plt.xlabel('Diffraction Order') plt.ylabel('Efficiency') plt.legend() plt.title(f'Final Spectrum, +1 order T = {final_eff:.3f}') plt.tight_layout() plt.show()这个案例展示了使用meent进行拓扑优化的完整流程:定义变量、构建仿真、计算目标、反向传播、更新结构。其中包含了可微分投影、平滑滤波等实用技巧来获得物理上更合理的二值化结构。最后,我们用更高精度(更高傅里叶阶数和CFS方法)验证了优化结果的性能。
通过这个从原理到实践,从单次仿真到优化设计的完整旅程,我们可以看到,meent不仅是一个功能强大的RCWA仿真器,更是一个连接物理模型、数值计算和优化算法的研究平台。理解其背后的原理,能帮助你在遇到问题时快速定位;掌握其使用的技巧,则能让你在光子器件设计的探索中更加得心应手。
