用 ZOS-API 在 Zemax 非序列模式里仿真马赫–曾德尔干涉条纹
A Mach–Zehnder interferometer in OpticStudio non-sequential mode, driven from Python
Zemax OpticStudio 的非序列(non-sequential,NSC)模式可以做相干光线追迹,算出真实的干涉条纹。本文以马赫–曾德尔(Mach–Zehnder,MZ)干涉仪为例,用 Python 通过 ZOS-API 搭模型、追迹、读出条纹,并和解析公式对照。
1. 光路#
光轴沿 ,干涉仪在 – 平面内成方形回路:准直光经 BS1 分成两束,一束经 M1(上臂),一束经 M2(下臂),在 BS2 重新合束,由探测器接收。
| 元件 | 位置 / 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 才会画出分光后的两条臂):
2. 解析参考#
两束等频率、偏振一致的平面波干涉,探测器上的强度
是两臂光程差, 是条纹可见度(两臂强度相等时 )。
如果一面镜子微倾 ,反射光方向改变 ,两束平面波之间有夹角 ,探测器上出现直条纹,周期
本例 (He-Ne),,得到 。光束宽 16 mm,所以预期约 4.4 条条纹。Zemax 里实际仿真约 5 条,可见度接近 100%,与这个估计一致。
条纹方向(按几何推断,以你的探测器坐标为准):镜子绕 轴倾斜,光束方向的改变发生在 – 平面内,所以探测器上条纹沿 方向周期变化,条纹本身平行于 轴。
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);两臂强度不等 |
| 条纹数与解析值不符 | 镜子倾角单位或方向搞错;对照 检查 |
| 条纹噪点多 | 分析光线数太少,或探测器像素太细;增加光线数或用更粗的像素 |
| 光路没闭合 | 四个 45° 元件的 TiltAboutX 没有同号 |
同样的方法也可以用于不等臂、加样品(比如玻璃板)的干涉仪。只是加了样品后,要在解析模型里把额外的光程和反射损失算进 和 ,才能和仿真对照。