简介:gcmfaces是一套面向Matlab与Octave的开源工具箱,专为处理全球气候模型(GCM)的海洋环流数据而设计,主要服务于海洋科学和气候研究领域的科研人员、研究生及工程师。其核心在于对face分块网格结构的支持,能高效读取NetCDF输出、完成切片与重采样,并提供流线图、等值面等二维/三维可视化,以及涡度、散度、温盐梯度等物理量计算功能,借助并行方式显著提升大数据集处理效率。该工具箱支持用户自定义扩展函数,便于根据实际研究流程定制分析模块。资源包内含317个文件,以296个m函数脚本为主体,辅以rst文档、PDF使用指南、yaml配置、bin数据、bib参考文献及readme说明,整体压缩后仅3MB,部署轻便。已有118人学习下载,适合需要快速上手gcmfaces、开展GCM输出数据分析或进行模型诊断的研究者,也适合希望扩展自定义处理函数、深入海洋环流模拟流程的进阶用户。
1. gcmfaces 是给谁用的:绕不开的全球环流后处理工具箱
全球海洋模式的输出常常不是一张“平面地图”。MITgcm 这类全球环流模式采用立方球网格把地球切成六块,输出数据按面存放,普通 lat/lon 后处理工具一开文件就抓瞎。gcmfaces 正是 ECCO 项目在长期全球模拟中打磨出来的 Matlab/Octave 工具箱,它把六个面的网格结构、掩膜和插值统一抽象成一套接口,让读数据、体积积分、输运诊断和画图都围绕同一套网格对象进行。它主要面向需要处理全球海洋模型输出、又不想自己从零搭一套后处理链路的海洋科学与气候工程人员,下文按“网格结构—环境搭建—核心操作—诊断案例—排错”逐层展开。
2. gcmfaces 的网格骨架:立方球、faces 与掩膜变量
2.1 为什么全球模式要切六个面而不是用经纬网格
传统经纬网格沿经线把全球网格化,在极点区域网格收敛成奇点:经度方向的格距趋近于零,模式不得不做滤波或干脆放弃极区计算。立方球网格先把地球表面投影到内切立方体的六个平面上,再在每个面上独立生成准均匀的二维网格。这种做法的直接收益是两个方向的分辨率不随位置剧烈变化,模式时间步长可以统一;代价是数据存储、边界对接和诊断工具全部要重新适配。
下表列出的两类网格差异,是理解 gcmfaces 存在必要性的关键:
| 特性 | 经纬网格 | 立方球网格 |
|---|---|---|
| 极点处理 | 需要特殊汇合与滤波 | 极点落在面内,无奇点 |
| 空间分辨率 | 高纬过度加密 | 六面上近似均匀 |
| 数据布局 | 单个二维场 | 6 个面单独存储 |
| 常用后处理 | 直接切片、绘图 | 需要 faces 感知的工具 |
因此,做全球环流诊断时最先遇到的往往不是物理问题而是数据结构问题:一个温度场在 gcmfaces 里不是一个普通 2D/3D 数组,而是包含 6 个面的 cell 结构。这个差异决定了后面所有脚本怎么写。
2.2 mygrid:gcmfaces 的全局网格对象
gcmfaces 在初始化时把一套叫 mygrid 的全局变量装进工作区,里面保存每个面的中心坐标、角点坐标、湿度掩膜和分层几何。习惯上所有 gcmfaces 脚本开头都有这两行:
global mygrid gcmfaces_global;gcmfaces_global 负责读取网格文件并组装 mygrid,核心字段包括:
- XC、YC:每个网格面中心点的经度和纬度
- XG、YG:网格角点坐标,用于计算格距和面积
- hFacC:垂直方向上每一层格子的水体填充比例
- maskC、maskW、maskS:干湿掩膜,分别对应格心、东西界面、南北界面
下面这段代码可以快速确认初始化结果:
global mygrid gcmfaces_global; fprintf('面数: %d\n', mygrid.nFaces); for f = 1:mygrid.nFaces fprintf('面 %d: %d x %d\n', f, size(mygrid.XC{f}, 1), size(mygrid.XC{f}, 2)); end这段代码在 Matlab 和 Octave 下都能直接运行。nFaces 一般是 6,但部分网格会把一个面继续细分,此时面数多于 6。脚本如果写死 6,换一套网格就会出错,通过 mygrid.nFaces 循环遍历更稳妥。
2.3 掩膜不是“可有可无”的附属数组
maskC 中 1 表示海洋,0 表示陆地。gcmfaces 的很多内部算子会自动应用掩膜,但自己写循环时很容易把陆地点也统计进去。比如算全球平均温度,正确做法是按水体体积加权:
global mygrid % gcmfaces_vol 返回每个网格格子的实际体积 vol = gcmfaces_vol(mygrid.maskC); total_vol = 0; s = 0; for f = 1:mygrid.nFaces total_vol = total_vol + sum(sum(vol{f}, 1), 2); s = s + sum(sum(THETA{f} .* vol{f}, 1), 2); end mean_theta = s / total_vol;这里的 THETA 是三维 faces 结构,.乘是按面逐点相乘。如果直接把 THETA 在全部网格点上做算术平均,结果会被高纬密集格子主导,物理意义完全不对。这是新手写 gcmfaces 脚本最常见的错误来源。
注意:不同版本里体积计算函数名可能不同。如果当前版本没有 gcmfaces_vol,用各面面积乘以层厚自己构造体积场也是一样的。
3. 在 Matlab 与 Octave 里把 gcmfaces 装起来并跑通
3.1 下载、解压与首次运行前的路径设置
gcmfaces 以源码包形式发布,常见做法是把压缩包解压到专门目录,比如~/tools/gcmfaces,然后在脚本或 startup.m 里把整个目录加入搜索路径:
addpath(genpath('/home/user/tools/gcmfaces'));genpath 会把所有子目录加进来,避免手动逐层添加。Windows 用户注意路径中不要出现中文目录,部分内部函数对非 ASCII 路径处理并不可靠。安装 gcmfaces 不需要安装器,路径设置不生效的典型特征是调用 gcmfaces_global 时报Undefined function。这也和 matlab 安装教程里常被忽略的一步类似:工具箱本身没问题,是搜索路径没配对。
3.2 初始化网格与冒烟测试
路径设好后的第一步不是去读海量数据,而是先确认网格能正常加载。最小冒烟测试可以这样写:
global mygrid gcmfaces_global; assert(logical(exist('mygrid', 'var'))); assert(mygrid.nFaces == 6); % 把掩膜传入体积函数,验证非零总体积 vol = gcmfaces_vol(mygrid.maskC); total_vol = 0; for f = 1:mygrid.nFaces total_vol = total_vol + sum(sum(vol{f}, 1), 2); end fprintf('网格总体积: %g 10^6 km^3\n', total_vol / 1e15);如果输出结果在 1330 附近(单位是 (10^6) km³),说明网格装载正常。如果拿到 0 或 NaN,多半是 maskC 或 hFacC 读取失败,后面所有计算都不可信。内存有限时可以只跑工具箱自带 test 脚本,它通常只加载小网格,跑通后再加载真实网格。
3.3 Octave 兼容性:能跑的部分与需要绕开的部分
Octave 在纯计算路径上与 gcmfaces 兼容得相当好,但有四个边界需要提前知道。
- 绘图:gcmfaces 自带的 face 拼接绘图函数在 Octave 下可能遇到句柄属性不识别的问题,稳定做法是先把数据插值到经纬度网格,再用 pcolor 自己画。
- MEX 文件:如果网格生成或插值依赖编译好的 MEX 文件,Octave 需要自行编译,Windows 上通常要装 MinGW-w64。
- 字符串处理:老版本 Octave 对某些新式字符串函数支持滞后,遇到
convertCharsToStrings报错时把相关行改为 char 处理。 - 路径缓存:多次 addpath 后 Octave 不刷新函数缓存,改动 gcmfaces 源码后要执行
rehash或clear functions。
检查环境是否就绪:
disp(version); which gcmfaces_global;如果 which 返回路径,说明搜索路径已生效;如果返回 not found,说明 addpath 没执行或拼错了目录。
3.4 卸载与残留路径的清理
很多用户是在旧版 Matlab 里装了 gcmfaces,之后升级或换电脑,于是出现启动时找不到 gcmfaces_global 的报错。这和常见的“win工具箱怎么卸载”情况类似:问题不在工具箱本身,而是 pathdef.m 中残留旧路径。清理方式:
% 查看当前路径 path % 删除包含 gcmfaces 的项 rmpath(genpath('/old/path/to/gcmfaces')); savepath如果 savepath 因权限失败,Windows 上可以找到matlabroot/toolbox/local/pathdef.m手动删除相关行。Octave 没有 pathdef.m,检查~/.octaverc是否有旧 addpath 残留即可。
4. 上手 gcmfaces:读数据、插值与诊断绘图
4.1 读取 MITgcm 原生输出
读取模型原生输出的常用入口是 gcmfaces_load,它面向 MITgcm 二进制输出(state 系列、pickup 系列)直接把数据装配成 faces 结构:
fld = gcmfaces_load({'THETA', 'V'}, ... 'dir', '/data/ecco/run/', ... 'nDims', 3, ... 'tiles', mygrid.tiles);参数含义:
- 第一个参数是要加载的变量名列表,THETA 是位温,V 是经向速度
dir指向数据目录,目录下应有按面拆分的文件或能被内部逻辑识别的命名格式nDims表示变量维数,3 代表三维场,2 代表二维场tiles取自 mygrid.tiles,告诉装配逻辑每个文件对应哪个面
如果模型输出是 NetCDF 格式,需要先用标准工具把数据拆成六面结构,再逐一塞进 faces cell。社区里更常见的做法是保持二进制并配合 gcmfaces_load,因为它在面边界上的处理最完善。加载后记得确认 fld 的 faces 数与 mygrid.nFaces 一致,否则后续逐面运算会越界。
4.2 插值到经纬度网格:全球图的入口
诊断输出和画图都倾向于地理网格。gcmfaces_interp 是 faces 到普通网格插值的核心函数:
lon = -180:1:180; lat = -90:1:90; [XT, YT] = meshgrid(lon, lat); THETA_ll = gcmfaces_interp(THETA, XT, YT); V_ll = gcmfaces_interp(V, XT, YT);这段代码在指定经纬度集合上做空间插值,返回普通 2D/3D 数组。使用要点有三个:输出网格越细,插值越慢,1/4 度数据插到 0.5 度网格就可能吃掉几 GB 内存,先上 2 度粗网格验证流程再加密;矢量场插值要注意面边界上方向的一致性,某些版本提供vectorInterp选项,标量场不需要;插值后近海岸会有空洞,绘图时把陆地掩膜叠加在地理网格上即可。
4.3 面平均与全局积分
有了 faces 对象,体积积分的写法是统一的。例如计算 100 米以浅的平均温度:
global mygrid % 构造深度掩膜:只保留前 10 层,其余层设 0 depth_fac = mygrid.hFacC; for f = 1:mygrid.nFaces tmp = depth_fac{f}; depth_fac{f} = zeros(size(tmp)); depth_fac{f}(:, :, 1:min(10, size(tmp, 3))) = 1; end vol_100m = gcmfaces_vol(depth_fac); num = 0; den = 0; for f = 1:mygrid.nFaces num = num + sum(sum(sum(THETA{f} .* vol_100m{f}, 1), 2), 3); den = den + sum(sum(sum(vol_100m{f}, 1), 2), 3); end mean_100m = num / den;这段代码的关键在于深度掩膜也要按面拆开处理,而不是直接在整个三维数组上切层。层数 10 是示意值,实际应依据网格 param 文件中的分层厚度换算。
4.4 画一张不拼接错位的全球图
快速预览时可以直接调 gcmfaces_plot 类接口,不过它在不同 Octave 版本上表现不稳定。稳妥路径是先插值到经纬度,再用 Matlab 自带绘图:
pcolor(lon, lat, squeeze(THETA_ll(:, :, 1))); shading interp; hold on; landmask = ~isnan(squeeze(THETA_ll(:, :, 1))); contour(lon, lat, landmask, [0.5 0.5], 'k'); colorbar; xlabel('Longitude'); ylabel('Latitude');展示时注意首尾经度拼接:插值网格的 lon 从 -180 到 180 时,180°E 和 -180°W 交界处会有白缝,绘图前把 lon 扩展一位并复制首列到末列即可。colormap 用 matlab 图像处理相关工具箱自带的 parula 或 jet 都够用,不需要额外装包。
5. 用 gcmfaces 计算一条全球经向翻转环流(MOC)曲线
5.1 MOC 的思路与数据准备
MOC(Meridional Overturning Circulation)描述全球海洋在经向剖面上的翻转结构。大西洋 MOC 在 26°N 附近实测约 17 Sv,这是模型诊断里最常拿来对标的量级。从 gcmfaces 计算一条全球 MOC 曲线,可以同时验证插值路径和质量守恒:理论上一整圈经向输运积分应为 0,如果明显偏了,说明 faces 拼接时出了问题。
数据准备建议用时间平均场,而不是单一时次的快照。先在 faces 结构上做时间平均,再插值到经纬度网格,避免“先插值再平均”累积插值误差。
% 已有 V 随时间变化的 faces 结构,先做时间平均 V_mean = V; % 如果 V 是 time×face 结构,先 squeeze 掉时间维 V_ll = gcmfaces_interp(V_mean, XT, YT); fprintf('插值后尺寸: %d x %d x %d\n', size(V_ll));这里的 XT、YT 沿用 4.2 节定义的 1 度网格。检查 V_ll 的第三维深度层数与模型输出一致,不一致时要回到 gcmfaces_load 的 nDims 设置排查。
5.2 在纬向做累积
核心循环按层对经度加权求和,再按纬度从南向北累积:
nz = size(V_ll, 3); dx = 111e3 * cosd(lat); % 经度方向实际长度,随纬度变化 psi = zeros(length(lat), nz); for k = 1:nz v_k = squeeze(V_ll(:, :, k)); % nlat x nlon % 经度方向积分,dx 随纬度变化 trans = sum(v_k .* repmat(dx(:), 1, length(lon)), 2); % 从南到北累积 psi(:, k) = cumsum(trans); end dz = 10; % 示意:均匀层厚 10m,需按实际网格替换 psi = psi * dz * 1e-6; % 转成 Sv这里必须用 repmat 把 ddx 扩展到经度维,因为 dx 是每个月度对应的实际距离。实际模型的层厚不均匀,要把 dz 替换为每层实际厚度的列向量后再乘。这段代码的效率不是最优,但胜在逻辑完全透明,逐面排查错误时很容易定位。
5.3 验证:量级与转向特征
画成等值线图后重点看两个特征:
figure; contourf(lat, 1:nz, psi'); set(gca, 'YDir', 'reverse'); colorbar; xlabel('Latitude'); ylabel('Depth level'); title('Global MOC streamfunction (Sv)');一个合理的全球 MOC 曲线在大西洋深水区应有约 15 到 20 Sv 的向北输运峰,40°S 附近有明显的循环反转。如果最大值只有 1 Sv 或图像完全是高频噪声,优先检查两处:V 单位是否已是 m/s;插值网格经度步长是否过细,把经度步长加到 2 度再试。如果全纬度积分不为零,则说明质量守恒被破坏,回头检查 5.1 步骤里的面边界处理。
6. gcmfaces 排错技巧与 Octave 环境边界
6.1 报错信息的含义
| 报错现象 | 常见原因 | 排查动作 |
|---|---|---|
| Undefined function gcmfaces_global | 路径未加入或缓存未刷新 | addpath 后执行 rehash |
| Index exceeds array bounds | 用第 1 个面的尺寸去索引其他面 | 用 mygrid.nFaces 循环遍历 |
| NaN 出现在插值结果 | 目标点落在陆地掩膜内 | 对插值结果再做最近邻填充 |
| Out of memory | 高分辨率数据一次装载多个变量 | 逐个变量读、先转 single、分块插值 |
6.2 降低内存占用的小技巧
内存是 gcmfaces 应用中最常见的瓶颈。一个 1/4 度的全球三维场在 double 精度下接近 2 GB,几个变量同时装载就会吃光普通工作站。我的习惯是数据尽早转 single,gcmfaces 的大多数运算能保持 single 精度,混用时注意显式转换;细网格插值按经纬度范围分块,每块调用一次 gcmfaces_interp 再拼接;时间序列循环处理时用 clear 显式删除不再需要的 faces 对象,不要等到 out of memory 发生。
6.3 工具箱卸载与长期维护
多台机器同步环境时,把 gcmfaces 放在统一路径并维护一个 startup.m 能节省大量“换机器就报错”的时间。Windows 用户卸载旧版 Matlab 后常遇到残留路径问题,原因和 win工具箱卸载残留一模一样:pathdef.m 里记录的绝对路径已不存在,启动时仍然逐一扫描。验证命令只有一行:
which gcmfaces_global返回 not found 说明路径没生效,返回一个已不存在的路径说明有残留,用 rmpath 清掉即可。Octave 用户还需要注意以包形式安装时执行一次pkg rebuild,让函数缓存与改动后的源码保持一致。
本文还有配套的精品资源,点击获取