news 2026/11/3 8:32:05

从HITRAN到Python光谱计算:HAPI实战与吸收透射仿真

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从HITRAN到Python光谱计算:HAPI实战与吸收透射仿真

简介:这是一份基于HITRAN数据库的Python光谱处理库hapi源代码,面向分子光谱分析、大气遥感、环境监测及天体物理学研究等领域的科研人员、学生和Python开发者。hapi库封装了访问HITRAN数据库的接口,能够获取分子吸收谱线的频率、强度、压力展宽等关键参数,并支持光谱仿真与数据建模,可帮助用户快速检索谱线数据、模拟气体吸收特性、解析观测光谱。压缩包内容集中,共1个文件,为hapi.py主程序源代码,包体大小约327KB,导入后即可调用数据查询、光谱计算等核心函数,便于直接使用或二次开发。该资源目前已有630人浏览学习,具有一定参考热度。通过研读源码,读者可以完整了解HITRAN数据库的访问机制与光谱仿真实现思路,也能根据自身需求修改和扩展功能,将其嵌入科研或工程流程,显著提升光谱分析和建模效率,是学习分子光谱编程实现的实用参考。

1. HITRAN 数据库和 HAPI:为什么 Python 光谱计算先从这里入手

做激光吸收光谱测量的人,手里都有一本绕不开的黑皮书:HITRAN。它是分子吸收谱线的权威数据库,过去想算一条吸收曲线,得先下载 .par 文件,再用 Fortran 或 C 写读表程序,最后自己拼 Voigt 卷积。HAPI 把这个链路压成了一个 Python 接口:import hapi 之后,拉数据、查表、算吸收系数、出透射谱,几行代码就能跑完。下面沿着我做 TDLAS 气体检测时的路径讲清楚:HITRAN 数据库怎么被 HAPI 用起来,环境参数怎么设,混气浓度怎么乘,以及那些让你算完对不上实验的坑。适合正在做红外气体传感、大气遥感或燃烧诊断,需要快速拿到可复现光谱的工程师和学生。

2. 用 HAPI 读 HITRAN 的第一次计算:数据目录、fetch 与吸收系数

2.1 HITRAN 谱线文件的结构:一行为一条谱线

HAPI 读进去的 HITRAN 数据,本质上是一张扁平的谱线表,每行代表一条跃迁谱线。HITRAN 老版本用固定 100 字符的行,新版扩展到 160 字符,核心字段没有大变。了解这些字段不是为了手写解析器——HAPI 已经全做了——而是为了后面排查问题:为什么拉到的数据量不对、为什么线宽算出来很怪、为什么温度依赖指数会让仿真和实验对不上。

HITRAN 一条谱线的主要字段如下:

字段单位作用
真空波数cm⁻¹吸收峰位置
线强度 Scm⁻¹/(molecule·cm⁻²)296 K 下该跃迁的积分强度
爱因斯坦 A 系数s⁻¹自发辐射概率,辐射计算用
空气增宽半宽cm⁻¹/atm由空气碰撞导致的 Lorentz 半宽
自增宽半宽cm⁻¹/atm同种分子碰撞贡献
低态能量 E″cm⁻¹决定线强度随温度的变化
温度依赖指数 n无量纲展宽随温度变化的幂指数
压力位移cm⁻¹/atm高压下峰位移动

注意,HITRAN 给的都是单条谱线的参数,不是连续谱。要得到一条连续的吸收光谱,必须把若干条 Voigt 轮廓叠加起来,并考虑温度和压力对宽度的影响。HAPI 的absorptionCoefficient_Voigt做的就是这件事,但你得先把目标谱线拉到本地:

import hapi # 初始化本地数据目录,之后 fetch 的数据会存到这里 hapi.db_begin('hitran_local') # 拉取 CO2 两个主要同位素在 4100~4200 cm-1 的谱线 hapi.fetch('CO2', 1, 2, 4100, 4200) # 拉完之后将它读成 pandas DataFrame,方便预览和筛选 df = hapi.table('CO2') print(df.shape) print(df.head())

这里db_begin的参数是本地路径字符串,整个脚本只执行一次即可;fetch的第一个参数是 HITRAN 内部约定的分子名,第二、三个参数是同位素序号范围,第四、五个是波数范围,单位是 cm⁻¹;table返回一个 DataFrame,后面所有筛选、存档都以它为起点。如果fetch时连接失败,程序会直接报错,可以先到 HITRANonline 网页确认该分子在这个波段确实有可下载的谱线再重试。

2.2 第一次算出吸收系数:Environment 和 Diluent 两个参数

谱线拉到之后,计算吸收系数是 HAPI 最核心的一步。常见做法是先生成等间距波数网格,再调用吸收系数函数:

# 生成等间距波数网格:范围 4100~4200 cm-1,步长 0.01 cm-1 grid = hapi.linear_wavenumber_grid(4100, 4200, 0.01) # 计算 CO2 在 296 K、1 atm 下的吸收系数 nu, coef = hapi.absorptionCoefficient_Voigt( 'CO2', grid, hapi.Environment(296, 101325), # 温度 K,压力 Pa hapi.Diluent('air') # 空气增宽背景 ) print(len(nu), nu[0], nu[-1]) print(coef.max())

grid是一维数组,范围和端点都包含;步长 0.01 cm⁻¹ 在 4100~4200 之间就是 10001 个点,对第一次跑通来说足够。Environment构造时温度用开尔文,压力用帕斯卡,101325 是 1 atm,这也是后面最容易踩的单位坑。Diluent('air')表示谱线的碰撞展宽由空气分子贡献,适用于大气或烟气场景;如果你在做高纯样品池,就改成hapi.Diluent('self')用自增宽。coef的物理意义是纯气体的吸收系数,单位是 cm⁻¹,这个细节到第三章算混气透射率时会再次出现。

选 4100~4200 cm⁻¹ 这个窗口不是随意的:CO₂ 在 4.3 μm 附近有强吸收带,这个区间谱线密度适中,拉数据快,计算也不会因为谱线数量太多而卡住。第一次跑通后,你可以换成 H₂O 的 6800~6900 cm⁻¹ 窗口,那是水汽近红外检测的常用区域,谱线强且彼此分开,适合观察单条线型。

2.3 别急着画图:先看 describe 和列名

很多新手拉完数据直接plot(nu, coef),画出来发现不对又来群里问,其实 10 秒钟的检查就能避免。我一般先看 DataFrame 的统计信息和半宽相关列名:

print(df.describe()) print([c for c in df.columns if 'gamma' in c.lower()])

HAPI 不同版本的列名略有差异,gamma相关列通常是空气/自增宽半宽。先describe能快速确认数量级正常,比如线强度列不是全 0、波数范围落在目标区间内;再检查coef.max(),如果它是 0,多半是fetch没拉到数据,或者拉到的数据波数范围和你的网格没交集。这一步是 10 秒的后悔药,比后面调半天横坐标强得多。数据确认没问题后,建议先把 DataFrame 存成 pickle 或 csv,后面反复计算就不用每次联网拉取了。

提示:HITRAN 的波数值是真空波数,和空气中的波长换算时要考虑空气折射率。在近红外波段,这个偏移通常只影响小数点后第四位,做一般仿真可以忽略,但高精度波长标定时别忽略。

3. 温度压力与 Voigt 线型:把单条谱线变成一张可用的吸收光谱

3.1 为什么必须用 Voigt 线型:黑匣子里面的两条展宽路径

即使 HITRAN 把谱线参数给全了,吸收光谱也不是在谱线位置插一根细线就完事。分子在气体里不是静止的:微观速度分布让吸收频率发生多普勒移动,单条谱线被抹成高斯形,宽度正比于开尔文温度的平方根;同时分子间碰撞打断能级寿命,产生 Lorentz 形,宽度近似正比于压力和碰撞截面。真实气体里两种机制同时存在,理论线型是两者的卷积,也就是 Voigt。低压下 Voigt 趋近高斯,高压下趋近 Lorentz,中间区域没有简单的解析表达式,只能按数值卷积算。

HAPI 的absorptionCoefficient_Voigt把这个黑匣子封装好了:传入网格、Environment和Diluent,它逐条谱线计算 Voigt 轮廓再累加。只算一条线的时候看不出它的价值,算几百条线叠出来的带谱时,手写卷积非常容易翻车。理解这个背后的机制仍然有用:当你发现仿真谱线型不对,第一个要判断的就是当前压力处于哪个展宽主导区。低压气体池三四千帕的时候多普勒展宽占主导,HAPI 算出来的是接近高斯的光滑峰;常压烟气测量时碰撞展宽占主导,峰形更宽更矮,左右不对称的概率也更高。

3.2 用两组环境对比:压力怎么改变峰的“高矮胖瘦”

一次计算看不出问题,两组环境参数放一起对比,Voigt 的行为就非常直观:

import numpy as np grid = hapi.linear_wavenumber_grid(4100, 4200, 0.01) # 同一温度,两个压力:50 kPa 和 101.325 kPa _, coef_50kpa = hapi.absorptionCoefficient_Voigt( 'CO2', grid, hapi.Environment(296, 50000), hapi.Diluent('air')) _, coef_1atm = hapi.absorptionCoefficient_Voigt( 'CO2', grid, hapi.Environment(296, 101325), hapi.Diluent('air')) # 峰值对比:低压峰更高更窄,高压峰更低更宽 i50, i1 = np.argmax(coef_50kpa), np.argmax(coef_1atm) print('峰值位置', grid[i50], grid[i1]) print('峰值系数', coef_50kpa[i50], coef_1atm[i1]) # 积分面积应接近一致:压力只改形状,不改总强度 area_50 = np.trapz(coef_50kpa, grid) area_1 = np.trapz(coef_1atm, grid) print('面积比', area_50 / area_1)

变量名前加下划线是表示丢弃第一个返回值,因为两次调用里的nu和grid完全一样。理论上这两个area应该非常接近,因为谱线积分强度不变,压力改变的只是能量在频率上的分布方式。如果面积比明显偏离 1,比如差出 30%,基本可以断定压力单位或温度参数有问题,先回头检查Environment再继续。这个“尖峰对矮宽峰”的对比也是判断计算有没有入误区的最快方法。实际测量里,低压气体池抽到几千帕正是为了让吸收峰变尖、避免邻近线重叠,TDLAS 选线时经常利用这个趋势。

3.3 从吸收系数到透射率:Beer-Lambert 与浓度乘法的漏网之鱼

HAPI 算出来的coef默认对应纯气体。Beer-Lambert 定律写出来是 I/I₀ = exp(-k·L),k 是吸收系数,L 是光程长度。但如果你的气体池里只有 5% CO₂、其余是氮气,就不能直接把coef代进去。我一般先把coef乘待测组分的体积分数 x,再算指数:

L = 100 # 光程 100 cm x_co2 = 0.05 # 混合气里 CO2 占 5% trans = np.exp(-coef * x_co2 * L) # 找最深吸收位置(透射率最低处) idx = np.argmin(trans) print(grid[idx], trans[idx])

x_co2可以是 0.05,也可以是 400e-6(400 ppm),看你模拟的对象。如果光路是开放大气,x 是沿程体积分数,L 是整条光路长度,算出来的是路径积分透射率;如果要进一步做开放光路浓度反演,还得把温度压力梯度考虑进来,HAPI 的Environment描述的是均匀气体池,别拿它硬套非均匀光路。HAPI 本身也提供透射率和辐射率的封装函数,但我更习惯直接用 numpy 算透射率,少记一层函数签名,也方便对数组做各种后续处理,比如插值到仪器波长网格。

4. HAPI 排查与避坑:五个让光谱计算翻车的现场

这一章写的都是我在实际项目里见过或踩过的坑,每条按“现象 → 原因 → 解决”整理。如果你照着第三章的代码跑出来和实验对不上,先按这五条排查,命中率很高。

4.1 fetch 返回空:先确认波段有谱线,再检查本地目录

现象:fetch执行完没有报错,但df.shape是 (0, N);或者执行时报 HTTP 错误,程序中断。

原因:第一种多半是选错了分子或波数范围,比如 CO 在 4100~4200 cm⁻¹ 只有很少弱线,强度低于 HITRANonline 的下载阈值;第二种情况是本地目录没写权限,或者网络连接失败。还有一种更隐蔽的场景:db_begin指向了之前建过的目录,里面已经有旧数据,fetch拉的是增量,但拉完后的表和新旧数据混在一起,行数看着对不上。

解决:先到 HITRANonline 网页按分子和波数范围搜索,确认该波段存在可下载的谱线;再看db_begin目录是否有写入权限;最后把目录换成新路径重跑一次,排除旧缓存干扰。数据拉到后顺手df.to_pickle('co2_band.pkl')存档,后面每次计算直接读本地文件,不再依赖网络。

4.2 峰位对不上:HITRAN 的波数和光学仪器的波长是两个单位

现象:代码里网格在 6500 附近,画图却找不到 1.5 μm 附近的吸收峰,或者峰位刚好左右反过来。

原因:HITRAN 所有谱线位置都用真空波数 cm⁻¹,而很多仪器和文献显示波长 nm。6500 cm⁻¹ 对应约 1538 nm,如果直接把 6500 当 nm 去和仪器对照,自然全部错位。

解决:用换算公式wavelength_nm = 1e7 / nu,numpy 数组可以直接这样除:

wavelength_nm = 1e7 / nu

注意波数从大到小对应波长从小到大,画图时横坐标顺序和仪器扫描方向经常是反的。我踩过这个坑之后养成了一个习惯:每次算完先打印峰值点的波数和波长,和已知谱线位置对一眼再画图,而不是直接丢进绘图函数。

4.3 峰宽差几个数量级:Environment 的压力单位不是 atm

现象:算出的谱线比实测窄很多,峰高却高得离谱,看起来像一根针插在背景上。

原因:Environment的压力单位是 Pa,但有人脑子里想着 atm,直接在第二个参数里填 1。于是碰撞展宽被低估了 101325 倍,Voigt 线型里 Lorentz 部分几乎消失,谱线退化成纯多普勒线。

解决:把 1 改成 101325。如果你从别处拿到以 atm 为单位的数据,确认单位后统一乘 101325 再传。顺带也要检查半宽参数的单位,HITRAN 的空气增宽半宽是 cm⁻¹/atm,HAPI 内部会按压力单位处理,但你自己手写展宽公式时特别容易在单位换算里迷糊。

4.4 网格太密导致计算卡死:先粗扫再细扫

现象:网格步长设 0.001,波数范围 4000~4500,跑了几分钟没结果,内存肉眼可见地涨。

原因:50 万个波数点,叠加几百条谱线,每个点都要做 Voigt 卷积,这个计算量不是普通 Python 循环能扛住的。HAPI 内部虽然有优化,但网格规模和谱线数量一起来,照样会卡。

解决:分两步走。先用 0.05 或 0.1 的步长跑完整波段,确认主要吸收线的位置,再对每条目标线做 ±0.5 cm⁻¹ 的小窗口、0.001 步长细扫。fetch范围也按窗口来,不要在第一步就一把拉完几千个波数。如果项目需要快速迭代,建议只保留线强度大于 1e-23 的谱线参与计算,弱线对宽谱带的贡献通常可以忽略。

4.5 算出的吸收太强:直接 exp(-coef·L) 是纯气体的透射率

现象:同一浓度下仿真吸收比实测大很多,或者标定时怎么补偿都补偿不过来。

原因:就是第三章说的那个乘法漏了。HAPI 的Environment只有温度和总压,没有组分分压,coef对应的是纯气体。烟气里 CO₂ 通常只有百分之几,水汽往往只有 1%~3%,直接代进去等于把整个光路当成了纯 CO₂ 池子。

解决:算透射率前先乘体积分数:trans = np.exp(-coef * x * L)。如果做痕量气体检测,x 在 ppb 量级,透射率非常接近 1,这是正常的,后期再用拟合算法把 x 当未知数反演。比这个更隐蔽的是数据版本问题:HAPI 默认拉的是当前 HITRANonline 版本,和某些论文里用的 HITRAN 2012 版数据在弱线上可能差几个百分点,做高精度对比前先把fetch得到的版本元信息记录下来。

5. 验证你的 HAPI 光谱:一个从 HITRAN 到 npz 的最小闭环

5.1 从 HITRAN 到 npz:一个能直接跑的最小验证脚本

最后一个章节给一个可复用的验证脚本,选水汽 6800~6900 cm⁻¹ 窗口,约 1.45~1.47 μm,是 TDLAS 测湿度的常用区域。脚本把拉数据、算吸收系数、算透射率、存盘放在一起,跑完得到一个 npz 文件,后续校准和反演直接读它:

import numpy as np import hapi # 独立目录:路径不同就不会被旧数据污染 hapi.db_begin('hitran_h2o_demo') hapi.fetch('H2O', 1, 1, 6800, 6900) grid = hapi.linear_wavenumber_grid(6800, 6900, 0.01) nu, coef = hapi.absorptionCoefficient_Voigt( 'H2O', grid, hapi.Environment(296, 101325), hapi.Diluent('air')) # 积分面积守恒检查:压力只改形状,不应明显改变面积 area = np.trapz(coef, nu) print('吸收系数面积:', area) # 400 ppm 水汽、100 cm 光程的透射率 trans = np.exp(-coef * 400e-6 * 100) # 存盘,后续校准和拟合直接读 np.savez('h2o_1450nm.npz', nu=nu, coef=coef, trans=trans)

np.trapz是老版本 numpy 通用的写法,新环境如果提示弃用,改成np.trapezoid即可。这个面积值本身没有绝对标准,更好的验证是和df里的线强度总量对比:吸收系数对波数的积分约等于该窗口内所有谱线强度贡献之和,数量级一致就说明单位没搞错。

5.2 怎么确认结果没算错:先打一个点,再画整条线

我每次新写一个光谱计算脚本,都会先打印峰值位置和峰值透射率,再把数组头和尾各打三个点,看边界是不是干净的 1.0 透射率。如果边界出现负值或明显突变,多半是网格范围没有覆盖所有谱线的尾部,Voigt 轮廓被截断了。这时候加宽波数范围,而不是加密步长,通常就能解决。再拿这个仿真谱和 HITRANonline 网页的快速浏览谱叠一张图,量级一致就可以放心继续做反演。最后说一条我的血泪教训:最早用 HAPI 算水汽谱,我把波数当成了波长,整套曲线横坐标反着画,峰值对到了一条完全不同的谱线,之后半天都在查空气池还是标定问题。后来无论多急,第一件事永远都是打印峰值点的波数和波长,对上了再画图。希望这个验证习惯也能帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 8:31:02

工程制图视角选择:第一视角与第三视角对比与应用

1. 工程制图视角基础:为什么视角选择如此重要 在机械设计领域,图纸就是工程师的语言。而视角选择错误,相当于用错误的语法书写技术文档——轻则导致加工返工,重则造成整批零件报废。去年某汽车零部件供应商就因视角标注错误&#…

作者头像 李华
网站建设 2026/11/3 8:30:20

五款永久免费无限制的开源神器:VLC、7-Zip、OBS等深度解析

1. 这五款“永久免费无限制”的开源工具,到底在解决什么真实问题?你有没有过这样的时刻:视频文件打不开,双击提示“不支持该格式”,临时找播放器又怕带广告、弹窗、甚至偷偷上传你的文件;压缩包解压到一半报…

作者头像 李华
网站建设 2026/11/3 8:28:49

安卓权限安全优化与动态行为分析实践

1. 安卓权限安全判定的底层逻辑变迁在2015年之前,安卓安全检测主要采用静态分析技术,简单检查Manifest文件中声明的权限列表。只要开发者不申请明显高危权限(如READ_SMS),基本不会触发安全警告。但近年来,随…

作者头像 李华
网站建设 2026/11/3 8:27:59

应用分身与应用双开:原理、开通、数据隔离及多开方案对比

手机上同时挂两个账号这件事,几乎每个人都会在某个阶段撞上:一个是工作号、一个是私人号,或者一个主用一个纯养号,再或者一个国内区一个海外区。早年的解决方式很粗暴——包里揣两台手机,或者装个来路不明的多开工具&a…

作者头像 李华
网站建设 2026/11/3 8:27:57

SpringBoot+微信小程序构建高校就业管理系统实践

1. 项目概述这个毕业生就业信息管理系统是我去年指导的一个本科毕业设计项目,采用SpringBoot后端微信小程序的架构实现。系统主要解决高校就业指导中心、院系辅导员和毕业生三方在就业信息管理中的痛点:数据分散、统计困难、沟通效率低。系统上线后实际部…

作者头像 李华