笔记

用 ZOS-API 在 Zemax 非序列模式里仿真马赫–曾德尔干涉条纹

A Mach–Zehnder interferometer in OpticStudio non-sequential mode, driven from Python

ZemaxZOS-API非序列干涉仪Python

Zemax OpticStudio 的非序列(non-sequential,NSC)模式可以做相干光线追迹,算出真实的干涉条纹。本文以马赫–曾德尔(Mach–Zehnder,MZ)干涉仪为例,用 Python 通过 ZOS-API 搭模型、追迹、读出条纹,并和解析公式对照。

1. 光路#

光轴沿 +Z+Z,干涉仪在 YY–ZZ 平面内成方形回路:准直光经 BS1 分成两束,一束经 M1(上臂),一束经 M2(下臂),在 BS2 重新合束,由探测器接收。

光路示意图。四个 45° 元件的 TiltAboutX 都取 45°,使法线同向,方形回路才会闭合;M1 额外微倾 0.005°。
元件 位置 (x,y,z)(x, y, z) / mm 类型 要点
光源 (0, 0, 0) Source Rectangle 准直,半宽 8 mm
BS1 (0, 0, 50) Rectangular Volume,N-BK7,厚 0.3 mm 前表面 I.50,后表面 AR
M1 (0, 50, 50) Rectangle,MIRROR TiltAboutX = 45.005°
M2 (0, 0, 100) Rectangle,MIRROR TiltAboutX = 45°
BS2 (0, 50, 100) 同 BS1
探测器 (0, 50, 150) Detector Rectangle 24 × 24 mm,200 × 200 像素

Zemax 导出的 3D Layout(SplitNSCRays = True 才会画出分光后的两条臂):

Zemax 原生 3D Layout 导出:光源 → BS1 分束 → 上、下两臂 → BS2 合束 → 探测器。

2. 解析参考#

两束等频率、偏振一致的平面波干涉,探测器上的强度

I=I02[1+Vcos⁡(2πΔλ)],V=2I1I2I1+I2, I = \frac{I_0}{2}\Big[\,1 + V\cos\!\Big(\frac{2\pi\,\Delta}{\lambda}\Big)\Big], \qquad V = \frac{2\sqrt{I_1 I_2}}{I_1 + I_2},

Δ\Delta 是两臂光程差,VV 是条纹可见度(两臂强度相等时 V=1V = 1)。

如果一面镜子微倾 ε\varepsilon,反射光方向改变 2ε2\varepsilon,两束平面波之间有夹角 γ=2ε\gamma = 2\varepsilon,探测器上出现直条纹,周期

Λ=λ2sin⁡(γ/2)≈λ2ε. \Lambda = \frac{\lambda}{2\sin(\gamma/2)} \approx \frac{\lambda}{2\varepsilon}.

本例 λ=632.8nm\lambda = 632.8\,\mathrm{nm}(He-Ne),ε=0.005∘=8.7×10−5rad\varepsilon = 0.005^\circ = 8.7\times10^{-5}\,\mathrm{rad},得到 Λ=3.63mm\Lambda = 3.63\,\mathrm{mm}。光束宽 16 mm,所以预期约 4.4 条条纹。Zemax 里实际仿真约 5 条,可见度接近 100%,与这个估计一致。

解析参考。a:扫光程差,两臂等强(V = 1)与不等强(V = 0.6);b:预期的条纹图样;c:中心剖面,16 mm 孔径内约 4.4 条。

条纹方向(按几何推断,以你的探测器坐标为准):镜子绕 XX 轴倾斜,光束方向的改变发生在 YY–ZZ 平面内,所以探测器上条纹沿 yy 方向周期变化,条纹本身平行于 xx 轴。

3. 三个必须做对的地方#

三点缺一不可,只要有一处不对,探测器上就是均匀一片。

(1)光源:准直、填满孔径的 Source Rectangle。

  • Source Distance = 0(准直平行光),采样用 Sobol,偏振固定(RandomPolarization = False,Jx = 1)。
  • 两臂是整体重叠的平面波,倾斜镜子才会产生夹角、形成条纹。
  • 点光源或窄高斯光源做不出条纹:倾斜镜子只会把两个光斑平移开,几乎不重叠。

(2)分束镜:必须是带材料的玻璃实体,不能是裸面。

  • 裸面不能镀膜。用 RectangularVolume(薄玻璃板)加 N-BK7 材料,前表面镀 I.50(理想 50/50),后表面镀 AR;按 GetFaceData(f).FaceName 区分 Front / Back。
  • 不要用镀膜库里名为 BEAMSPLIT 的涂层,那是按波长分光的二向色镜,不是振幅 50/50。

(3)读数:相干辐照度只能用 Detector Viewer 读。

  • GetAllDetectorData 读的是探测器累积的非相干数据,没有条纹。
  • 要用 New_DetectorViewer(),DataType = CoherentIrradiance;而且设置对象要显式转换到 IAS_DetectorViewer 接口,否则属性设不进去。
  • 追迹时要打开光线分裂和偏振:SplitNSCRays = True、UsePolarization = True。

4. 代码#

核心步骤如下(new、place、setp 是脚本里的几个小封装:加对象并改类型、设位置和倾角、按列标题设参数)。

# 1) collimated source filling the aperture
src = new(OT.SourceRectangle, "collimated source"); place(src, 0, 0, 0)
setp("Analysis Rays", 400000, integer=True)
setp("Power", 1.0); setp("X Half Width", 8.0); setp("Y Half Width", 8.0)
setp("Source Distance", 0.0)                             # 0 = collimated
sd = src.SourcesData
sd.RandomPolarization = False; sd.Jx = 1.0; sd.Jy = 0.0
sd.SamplingMethod = SSM.Sobol

# 2) beam splitters: glass solids, front I.50, back AR
bs1 = new(OT.RectangularVolume, "BS1"); place(bs1, 0, 0, L, tx=45)
for k, v in ((1, HALF), (2, HALF), (3, 0.3), (4, HALF), (5, HALF)):
    cell(bs1, k).DoubleValue = v                         # x/y half widths, thickness
bs1.Material = "N-BK7"
csd = bs1.CoatScatterData
for f in range(csd.NumberOfFaces):
    name = csd.GetFaceData(f).FaceName or ""
    csd.GetFaceData(f).Coating = "I.50" if "Front" in name else "AR"

# 3) mirrors; M1 tilted by 0.005 deg
m1 = new(OT.Rectangle, "M1"); place(m1, 0, L, L, tx=45.005); m1.Material = "MIRROR"
m2 = new(OT.Rectangle, "M2"); place(m2, 0, 0, 2 * L, tx=45); m2.Material = "MIRROR"

# 4) trace with ray splitting and polarization
t = TheSystem.Tools.OpenNSCRayTrace()
t.SplitNSCRays = True; t.ScatterNSCRays = False; t.UsePolarization = True
t.ClearDetectors(0); t.RunAndWaitForCompletion(); t.Close()

# 5) read the *coherent* irradiance through Detector Viewer
dv = TheSystem.Analyses.New_DetectorViewer()
s = ZOSAPI.Analysis.Settings.RayTracing.IAS_DetectorViewer(dv.GetSettings())   # explicit cast
s.Surface.SetSurfaceNumber(1); s.Detector.SetDetectorNumber(6)    # the detector is object #6
s.DataType = DVD.CoherentIrradiance
dv.ApplyAndWaitForCompletion()
grid = dv.GetResults().GetDataGrid(0)                    # grid.Z(i, j); NaN outside the pupil

两点说明:

  • 非序列对象的参数按列标题查找比按固定列号更稳,所以 setp 先把光源的列标题读出来建一张表。
  • 连接方式:用交互扩展(Interactive Extension)时,每次脚本连接都会用掉这一次连接,下次要重新点一次。要多轮脚本自动化,改用 Standalone 模式(CreateNewApplication())更省事。

5. 排障表#

现象 常见原因
探测器上一片均匀 光源不是准直(Source Distance 不为 0);读数用了 GetAllDetectorData;没有打开 SplitNSCRays
3D Layout 里只有一条臂 SplitNSCRays 没有勾,光线没有分裂
条纹可见度明显低于 1 偏振没固定(RandomPolarization = True);分束镜不是 50/50(比如用了 BEAMSPLIT);两臂强度不等
条纹数与解析值不符 镜子倾角单位或方向搞错;对照 Λ≈λ/(2ε)\Lambda \approx \lambda/(2\varepsilon) 检查
条纹噪点多 分析光线数太少,或探测器像素太细;增加光线数或用更粗的像素
光路没闭合 四个 45° 元件的 TiltAboutX 没有同号

同样的方法也可以用于不等臂、加样品(比如玻璃板)的干涉仪。只是加了样品后,要在解析模型里把额外的光程和反射损失算进 Δ\Delta 和 VV,才能和仿真对照。