CESM 2.1.2 Weathering Guideline

CESM 2.1.2 Weathering 模型运行指南

背景

最近,搞到了某论文Transforming US agriculture for carbon removal with enhanced weathering | Nature的CESM_2.1.2_weathering模型文件压缩包CLM5.0.25 with improved N cycling to quantify soil N2O, NO and NH3 emissions from enhanced rock weathering with croplands | Zenodo

论文很新(2025年),但祖传代码历史悠久,代码面临的运行环境早已今非昔比。为了让远古代码在现代计算机上运行,特写此文。

祖传代码包含两个部分:

  • CESM (Community Earth System Model) 是一个由美国国家大气研究中心(NCAR)主导、社区协作开发的全耦合全球地球系统模型。它能够对地球的过去、现在和未来的系统状态进行最先进的计算机模拟。

  • CIME (Common Infrastructure for Modeling the Earth) 则是支撑 CESM 运行的通用软件基础设施,负责模型的配置、编译、测试和运行。

由以上可知,模型的运行依赖CIME的正确运行,后文专门针对CIME进行。

环境准备

搭建运行环境,这里选择 Ubuntu 26.04.1 LTS x64 平台。

要在 Ubuntu 上从零开始配置环境,整个过程可以拆解为四个核心步骤:安装系统依赖、获取模型代码、配置机器文件、创建并运行案例

安装系统依赖

首先更新系统到最新

1
2
sudo apt update
sudo apt upgrade

安装核心编译工具和依赖,以下命令涵盖了代码运行所需的所有工具和库。

1
2
3
4
5
6
7
sudo apt install -y gcc g++ gfortran build-essential cmake git subversion \
perl vim python3-pip libxml2-utils unzip wget csh \
libopenmpi-dev openmpi-bin openmpi-common \
libhdf5-dev hdf5-tools \
libnetcdf-dev libnetcdff-dev netcdf-bin \
libblas-dev liblapack-dev \
libpnetcdf-dev nco cdo

古老文件的部分代码仍然依赖python2.7版本,在较新的Ubuntu系统上需要处理python兼容性问题。幸好,有现成的方案可以拿来用,只需一个软件包即可。

1
sudo apt install -y python-is-python3

获取模型代码

论文使用的是经过修改的 CESM2.1.2,而非标准版, 必须从 Zenodo 下载包含 EW 氮循环修改的版本。

1
2
3
wget https://zenodo.org/records/8111541/files/cesm2.1.2_enhancedweathering.zip
unzip cesm2.1.2_enhancedweathering.zip
cd cesm2.1.2_enhancedweathering

配置机器文件

创建配置文件目录

1
mkdir -p ~/.cime

配置文件有两个config_machines.xmlconfig_compilers.xml,两个配置文件分别定义了运行CIME所需的系统环境和编译器环境。

分别创建两个空文件

1
2
3
cd ~/.cime
touch config_machines.xml
touch config_compilers.xml

此处为第一坑。

创建并运行case

进入 cime 目录,创建一个基础案例来验证环境。

1
./scripts/create_newcase --case mycase --compset X --res f19_g16

设置、构建和提交case

1
2
3
4
cd mycase
./case.setup
./case.build
./case.submit

如果 case.build 成功完成,说明基础环境已配置正确。

此处有很多坑。

如果你能顺利走到这一步,恭喜你,可以去买彩票了。因为你成功跳过了以下几个大坑。

第零坑

运行CIME的脚本你会发现python报错ModuleNotFoundError: No module named 'imp',想要用pip安装却找不到。没错,imp模块已被标记弃用,并且在3.12版本中被移除。因此,最后支持imp模块的python版本是3.11。

ubuntu包管理器安装的python版本是最新的,无法运行CIME的脚本,所以这里要用到miniconda创建一个CIME专用的低版本python环境,或者安装一个pycharm,通过pycharm配置所需的conda环境,安装教程见官方文档,此处不赘述。

第一坑

CIME的配置文件是特异于机器和系统的,也就是说,理论上每台机器每种系统都要单独针对进行配置。

这里以mmj所用的Ubuntu系统为例,详细介绍配置文件的含义和配置方法。

先贴出第一个配置文件config_machines.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0"?>
<config_machines version="2.0">
<machine MACH="Dr_lxx">
<DESC>AMD Ryzen 7 5800HS Creator Edition, 4 cores, OpenMPI, no batch system</DESC>
<NODENAME_REGEX>(?i)^Dr\.lxx$</NODENAME_REGEX>
<OS>LINUX</OS>
<COMPILERS>mmj-gnu</COMPILERS>
<MPILIBS>openmpi</MPILIBS>
<PROJECT>none</PROJECT>
<SAVE_TIMING_DIR> </SAVE_TIMING_DIR>
<SAVE_TIMING_DIR_PROJECTS> </SAVE_TIMING_DIR_PROJECTS>
<CIME_OUTPUT_ROOT>$ENV{HOME}/mmj-research/cesm_output</CIME_OUTPUT_ROOT>
<DIN_LOC_ROOT>$ENV{HOME}/mmj-research/cesm_inputdata</DIN_LOC_ROOT>
<DIN_LOC_ROOT_CLMFORC>$ENV{HOME}/mmj-research/cesm_inputdata/atm/datm7</DIN_LOC_ROOT_CLMFORC>
<DOUT_S_ROOT>$ENV{HOME}/mmj-research/cesm_archive/$CASE</DOUT_S_ROOT>
<BASELINE_ROOT>$ENV{HOME}/mmj-research/cesm_baselines</BASELINE_ROOT>
<CCSM_CPRNC>$ENV{HOME}/mmj-research/cesm_tools/cprnc/cprnc</CCSM_CPRNC>
<GMAKE>gmake</GMAKE>
<GMAKE_J>4</GMAKE_J>
<TESTS> </TESTS>
<BATCH_SYSTEM>none</BATCH_SYSTEM>
<SUPPORTED_BY>user@localhost</SUPPORTED_BY>
<MAX_TASKS_PER_NODE>4</MAX_TASKS_PER_NODE>
<MAX_MPITASKS_PER_NODE>4</MAX_MPITASKS_PER_NODE>
<PROJECT_REQUIRED>FALSE</PROJECT_REQUIRED>

<mpirun mpilib="openmpi">
<executable>/usr/bin/mpirun</executable>
<arguments>
<arg name="num_tasks"> -np $TOTALPES</arg>
</arguments>
</mpirun>
<module_system type="none"/>
<environment_variables>
<env name="NETCDF_PATH">/home/lxx/mmj-research/netcdf-cime</env>
</environment_variables>
</machine>
</config_machines>

这个 config_machines.xml 文件是 CIME 用来描述本地机器(或集群)硬件、软件环境和运行方式的配置文件。CIME 在创建、配置、编译和运行案例时,会读取这个文件,从而知道“在这台机器上应该用什么编译器、用什么 MPI、路径放在哪里、怎么提交任务”。

下面逐部分解释各项配置的含义。

  1. 文件整体结构
1
2
3
4
5
6
<?xml version="1.0"?>
<config_machines version="2.0">
<machine MACH="Dr_lxx">
...
</machine>
</config_machines>
  • <machine MACH="Dr_lxx">:定义一个机器,MACH 是机器名称,可自由填写方便识别。
  1. 机器标识和描述
1
2
3
<DESC>AMD Ryzen 7 5800HS Creator Edition, 4 cores, OpenMPI, no batch system</DESC>
<NODENAME_REGEX>localhost</NODENAME_REGEX>
<OS>LINUX</OS>
  • DESC:机器描述,仅用于显示,不影响运行。

  • NODENAME_REGEX:用正则表达式匹配当前主机名。只有当运行 CIME 命令的机器主机名匹配这个正则时,CIME 才会使用这个机器配置。在单台机器上使用时,可以直接填写主机名。

    用以下命令查询主机名

    1
    hostname
  • OS:操作系统类型,LINUX 表示 Linux。CIME 支持 LINUXAIXDARWIN 等。

  1. 编译器和 MPI
1
2
<COMPILERS>mmj-gnu</COMPILERS>
<MPILIBS>openmpi</MPILIBS>
  • COMPILERS:指定可用的编译器集合名称。这里的 mmj-gnu 是自定义名称,必须与 ~/.cime/config_compilers.xml 中定义的编译器名称一致。
  • MPILIBS:指定 MPI 库,openmpi 表示使用 OpenMPI。

CIME 会组合 MACH + COMPILER + MPILIB 来查找对应的编译配置。例如 Dr_lxx + mmj-gnu + openmpi

  1. 项目与计时
1
2
3
<PROJECT>none</PROJECT>
<SAVE_TIMING_DIR> </SAVE_TIMING_DIR>
<SAVE_TIMING_DIR_PROJECTS> </SAVE_TIMING_DIR_PROJECTS>
  • PROJECT:项目编号,用于作业记账。无批处理系统时设为 none
  • SAVE_TIMING_DIR:保存性能计时数据的目录,留空表示不保存。
  • SAVE_TIMING_DIR_PROJECTS:与项目相关的计时保存,留空即可。
  1. 路径设置
1
2
3
4
5
<CIME_OUTPUT_ROOT>$ENV{HOME}/mmj-research/cesm_output</CIME_OUTPUT_ROOT>
<DIN_LOC_ROOT>$ENV{HOME}/mmj-research/cesm_inputdata</DIN_LOC_ROOT>
<DIN_LOC_ROOT_CLMFORC>$ENV{HOME}/mmj-research/cesm_inputdata/atm/datm7</DIN_LOC_ROOT_CLMFORC>
<DOUT_S_ROOT>$ENV{HOME}/mmj-research/cesm_archive/$CASE</DOUT_S_ROOT>
<BASELINE_ROOT>$ENV{HOME}/mmj-research/cesm_baselines</BASELINE_ROOT>
  • CIME_OUTPUT_ROOT:所有案例输出、编译文件、运行目录的根目录。CIME 会在此下面为每个案例创建子目录。
  • DIN_LOC_ROOT输入数据根目录。CESM 需要大量输入数据(如地表数据、气候强迫数据)。CIME 会从这里查找或下载数据。
  • DIN_LOC_ROOT_CLMFORC:CLM 离线强迫数据的根目录,通常指向 DIN_LOC_ROOT/atm/datm7
  • DOUT_S_ROOT:案例归档目录。$CASE 会被替换为案例名。
  • BASELINE_ROOT:用于存放测试基线结果的目录。

$ENV{HOME} 表示环境变量 HOME,即你的用户主目录。

  1. 工具路径
1
2
3
4
<CCSM_CPRNC>$ENV{HOME}/mmj-research/cesm_tools/cprnc/cprnc</CCSM_CPRNC>
<GMAKE>gmake</GMAKE>
<GMAKE_J>4</GMAKE_J>
<TESTS> </TESTS>
  • CCSM_CPRNCcprnc 工具的路径,用于比较 NetCDF 文件。如果不需要测试,可以留空或指向正确路径。
  • GMAKE:GNU make 命令名,通常是 gmakemake。在 Ubuntu 上通常用 make,但写成 gmake 也可以,只要系统有该命令或符号链接。
  • GMAKE_J:并行编译的并行任务数。一般对应CPU的总核心数,设为 4,对应 4 核 CPU,可以加快编译。
  • TESTS:测试类型列表,通常留空。
  1. 批处理系统
1
2
<BATCH_SYSTEM>none</BATCH_SYSTEM>
<SUPPORTED_BY>user@localhost</SUPPORTED_BY>
  • BATCH_SYSTEM:批处理系统类型。none 表示没有作业调度系统(如 PBS、Slurm),直接运行。
  • SUPPORTED_BY:维护者联系方式,仅信息用途。
  1. 资源限制
1
2
3
<MAX_TASKS_PER_NODE>4</MAX_TASKS_PER_NODE>
<MAX_MPITASKS_PER_NODE>4</MAX_MPITASKS_PER_NODE>
<PROJECT_REQUIRED>FALSE</PROJECT_REQUIRED>
  • MAX_TASKS_PER_NODE:每个节点允许的最大逻辑任务数(包括超线程)。对应于逻辑CPU总数。
  • MAX_MPITASKS_PER_NODE:每个节点允许的最大 MPI 任务数。通常与物理核心数一致,设为 4。
  • PROJECT_REQUIRED:提交作业是否需要项目号。FALSE 表示不需要。
  1. MPI 运行配置
1
2
3
4
5
6
<mpirun mpilib="openmpi">
<executable>/usr/bin/mpirun</executable>
<arguments>
<arg name="num_tasks"> -np $TOTALPES</arg>
</arguments>
</mpirun>
  • 这段告诉 CIME 如何用 OpenMPI 启动模型。
  • executablempirun 的完整路径。 /usr/bin/mpirun,通常 OpenMPI 安装后就在这里。
  • arguments:传给 mpirun 的参数。-np $TOTALPES 表示启动 $TOTALPES 个 MPI 进程。$TOTALPES 由 CIME 根据案例的 PE 布局自动计算。
  • 如果需要其他参数(如 --oversubscribe--bind-to none),可以在这里添加。
  1. 模块系统
1
<module_system type="none"/>
  • 表示不使用环境模块(Environment Modules)系统。如果用 module load 来加载编译器、MPI 等,需要改成对应的模块系统类型并配置模块名。对于 Ubuntu 本地安装,通常设为 none
  1. 环境变量
1
2
3
<environment_variables>
<env name="NETCDF_PATH">/home/lxx/mmj-research/netcdf-cime</env>
</environment_variables>
  • 设置运行和编译时需要的环境变量。
  • 这里设置了 NETCDF_PATH,指向自定义安装的 NetCDF 目录。CIME 和编译脚本会用这个变量来查找 NetCDF。
  • 注意:仅设置 NETCDF_PATH 不一定足够。编译时链接 NetCDF 还需要在 config_compilers.xml 中通过 SLIBS 指定 -lnetcdff -lnetcdf 等。这个环境变量更多是给脚本或运行时使用。
  1. 这个文件如何与 config_compilers.xml 配合

CIME 在编译时,会同时读取:

  • ~/.cime/config_machines.xml(本文件):定义机器、MPI、路径、资源。
  • ~/.cime/config_compilers.xml:定义编译器标志、链接库、MPI 包装器等。

两者通过 MACHCOMPILERMPILIB 组合关联。例如:

  • 机器:Dr_lxx
  • 编译器:mmj-gnu
  • MPI:openmpi

第二坑

这里贴出config_compilers.xml文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="UTF-8"?>
<config_compilers version="2.0">
<compiler COMPILER="mmj-gnu">
<CFLAGS>
<base> -std=gnu99 </base>
<append compile_threaded="true"> -fopenmp </append>
<append DEBUG="TRUE"> -g -Wall -Og -fbacktrace -ffpe-trap=invalid,zero,overflow -fcheck=bounds </append>
<append DEBUG="FALSE"> -O </append>
</CFLAGS>
<CPPDEFS>
<!-- http://gcc.gnu.org/onlinedocs/gfortran/ -->
<append> -DFORTRANUNDERSCORE -DNO_R16 -DCPRGNU</append>
</CPPDEFS>
<CXX_LINKER>FORTRAN</CXX_LINKER>
<FC_AUTO_R8>
<base> -fdefault-real-8 </base>
</FC_AUTO_R8>
<FFLAGS>
<!-- -ffree-line-length-none and -ffixed-line-length-none need to be in FFLAGS rather than in FIXEDFLAGS/FREEFLAGS
so that these are passed to cmake builds (cmake builds don't use FIXEDFLAGS and FREEFLAGS). -->
<base> -fconvert=big-endian -ffree-line-length-none -ffixed-line-length-none -fallow-argument-mismatch </base>
<append compile_threaded="true"> -fopenmp </append>
<!-- Ideally, we would also have 'invalid' in the ffpe-trap list. But at
least with some versions of gfortran (confirmed with 5.4.0, 6.3.0 and
7.1.0), gfortran's isnan (which is called in cime via the
CPRGNU-specific shr_infnan_isnan) causes a floating point exception
when called on a signaling NaN. -->
<append DEBUG="TRUE"> -g -Wall -Og -fbacktrace -ffpe-trap=zero,overflow -fcheck=bounds </append>
<append DEBUG="FALSE"> -O </append>
</FFLAGS>
<FFLAGS_NOOPT>
<base> -O0 </base>
</FFLAGS_NOOPT>
<FIXEDFLAGS>
<base> -ffixed-form </base>
</FIXEDFLAGS>
<FREEFLAGS>
<base> -ffree-form </base>
</FREEFLAGS>
<HAS_F2008_CONTIGUOUS>FALSE</HAS_F2008_CONTIGUOUS>
<LDFLAGS>
<append compile_threaded="true"> -fopenmp </append>
</LDFLAGS>
<MPICC> mpicc </MPICC>
<MPICXX> mpicxx </MPICXX>
<MPIFC> mpif90 </MPIFC>
<SCC> gcc </SCC>
<SCXX> g++ </SCXX>
<SFC> gfortran </SFC>
<SUPPORTS_CXX>TRUE</SUPPORTS_CXX>
<SLIBS>
<append>-L/usr/lib/x86_64-linux-gnu -L/usr/lib/x86_64-linux-gnu/hdf5/serial -lnetcdff -lnetcdf -lm </append>
</SLIBS>
</compiler>

</config_compilers>

这个 config_compilers.xml 文件是 CIME 用来定义编译器如何工作的配置文件。它告诉 CIME:用哪个 Fortran/C/C++ 编译器、用什么编译选项、链接哪些库。下面逐部分解释。

  1. 文件整体结构
1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<config_compilers version="2.0">
<compiler COMPILER="mmj-gnu">
...
</compiler>
</config_compilers>
  • <compiler COMPILER="mmj-gnu">:定义一个编译器配置,名称为 mmj-gnu。这个名字必须和 config_machines.xml<COMPILERS>mmj-gnu</COMPILERS> 一致。
  1. C 编译器标志
1
2
3
4
5
6
<CFLAGS>
<base> -std=gnu99 </base>
<append compile_threaded="true"> -fopenmp </append>
<append DEBUG="TRUE"> -g -Wall -Og -fbacktrace -ffpe-trap=invalid,zero,overflow -fcheck=bounds </append>
<append DEBUG="FALSE"> -O </append>
</CFLAGS>
  • <base>:基础编译标志,始终生效。-std=gnu99 表示使用 GNU C99 标准。
  • <append compile_threaded="true">:只在启用多线程(OpenMP)时添加。-fopenmp 开启 OpenMP 支持。
  • <append DEBUG="TRUE">:调试模式下添加。包含调试符号 -g、警告 -Wall、优化级别 -Og、回溯 -fbacktrace、浮点异常捕获 -ffpe-trap=invalid,zero,overflow、边界检查 -fcheck=bounds
  • <append DEBUG="FALSE">:非调试模式下添加。-O 表示基础优化。

CIME 的 DEBUG 模式由 create_newcase--debug 参数控制,默认是 FALSE。

  1. C 预处理器定义
1
2
3
<CPPDEFS>
<append> -DFORTRANUNDERSCORE -DNO_R16 -DCPRGNU</append>
</CPPDEFS>
  • -DFORTRANUNDERSCORE:告诉 C 代码,Fortran 符号名使用下划线后缀(如 foo_)。
  • -DNO_R16:禁用 real*16 支持。
  • -DCPRGNU:标识使用 GNU 编译器,CIME 用它来选择特定的代码路径。

这些宏定义在 C 和 Fortran 预处理阶段使用。

  1. C++ 链接器
1
<CXX_LINKER>FORTRAN</CXX_LINKER>
  • 表示 C++ 代码的最终链接由 Fortran 链接器mpif90)完成,而不是 C++ 链接器。这是因为 CESM 的可执行文件通常以 Fortran 为主程序,用 Fortran 链接器能自动带上 Fortran 运行时库。
  1. Fortran 自动双精度
1
2
3
<FC_AUTO_R8>
<base> -fdefault-real-8 </base>
</FC_AUTO_R8>
  • -fdefault-real-8:将所有 REAL 类型自动提升为 8 字节(双精度)。这是 CESM 的常见要求,避免单精度导致的数值问题。
  1. Fortran 编译标志(核心)
1
2
3
4
5
6
<FFLAGS>
<base> -fconvert=big-endian -ffree-line-length-none -ffixed-line-length-none -fallow-argument-mismatch </base>
<append compile_threaded="true"> -fopenmp </append>
<append DEBUG="TRUE"> -g -Wall -Og -fbacktrace -ffpe-trap=zero,overflow -fcheck=bounds </append>
<append DEBUG="FALSE"> -O </append>
</FFLAGS>
  • -fconvert=big-endian:无格式 Fortran 文件使用大端字节序。CESM 的很多输入数据是大端格式。

  • -ffree-line-length-none:自由格式源代码行长度无限制。

  • -ffixed-line-length-none:固定格式源代码行长度无限制。

  • -fallow-argument-mismatch:允许 MPI 调用中参数类型不匹配。这是解决编译错误的关键标志

    从 GCC 10 开始,gfortran 对参数不匹配的检查从警告升级为错误,古老代码使用更新的编译器会编译不通过,添加该选项可以确保编译器兼容旧代码。

  • -fopenmp:多线程时开启 OpenMP。

  • DEBUG=TRUE 时:-g -Wall -Og -fbacktrace -ffpe-trap=zero,overflow -fcheck=bounds。注意这里去掉了 invalid,因为注释解释了原因。

  • DEBUG=FALSE 时:-O 基础优化。

注释里的重要说明-ffree-line-length-none-ffixed-line-length-none 必须放在 FFLAGS 而非 FIXEDFLAGS/FREEFLAGS 中,因为 CMake 构建不使用后两者。

  1. 无优化 Fortran 标志
1
2
3
<FFLAGS_NOOPT>
<base> -O0 </base>
</FFLAGS_NOOPT>
  • 当 CIME 需要编译某些不做优化的源文件时(如性能分析或调试),使用 -O0
  • 这个设置用于 NOOPT 模式的编译。
  1. 固定格式和自由格式标志
1
2
3
4
5
6
<FIXEDFLAGS>
<base> -ffixed-form </base>
</FIXEDFLAGS>
<FREEFLAGS>
<base> -ffree-form </base>
</FREEFLAGS>
  • -ffixed-form:告诉编译器该文件是 Fortran 固定格式(.F.f)。
  • -ffree-form:告诉编译器该文件是自由格式(.F90)。
  • CIME 根据文件扩展名自动选择,一般不需要手动设置。
  1. Fortran 2008 连续属性支持
1
<HAS_F2008_CONTIGUOUS>FALSE</HAS_F2008_CONTIGUOUS>
  • 表示当前编译器不完全支持 Fortran 2008 的 CONTIGUOUS 属性。
  • 设为 FALSE 后,CIME 会使用替代方案或避免使用该特性。
  1. 链接标志
1
2
3
<LDFLAGS>
<append compile_threaded="true"> -fopenmp </append>
</LDFLAGS>
  • 链接时添加的标志。多线程时加 -fopenmp,确保链接 OpenMP 运行时库。
  1. MPI 包装器
1
2
3
<MPICC> mpicc  </MPICC>
<MPICXX> mpicxx </MPICXX>
<MPIFC> mpif90 </MPIFC>
  • MPICC:MPI C 编译器包装器,通常是 mpicc
  • MPICXX:MPI C++ 编译器包装器,通常是 mpicxx
  • MPIFC:MPI Fortran 编译器包装器,通常是 mpif90

这些命令会自动包含 MPI 头文件和库路径。

  1. 底层编译器
1
2
3
<SCC> gcc </SCC>
<SCXX> g++ </SCXX>
<SFC> gfortran </SFC>
  • SCC: C 编译器,gcc
  • SCXX:C++ 编译器,g++
  • SFC: Fortran 编译器,gfortran

这些用于不依赖 MPI 的代码部分。

  1. C++ 支持
1
<SUPPORTS_CXX>TRUE</SUPPORTS_CXX>
  • 表示该编译器配置支持 C++ 编译。
  • 某些 CESM 组件(如 CISM)需要 C++。
  1. 系统库链接(关键
1
2
3
<SLIBS>
<append>-L/usr/lib/x86_64-linux-gnu -L/usr/lib/x86_64-linux-gnu/hdf5/serial -lnetcdff -lnetcdf -lm </append>
</SLIBS>
  • -L/usr/lib/x86_64-linux-gnu:指定库搜索路径。
  • -L/usr/lib/x86_64-linux-gnu/hdf5/serial:HDF5 串行库路径。
  • -lnetcdff:链接 NetCDF Fortran 库。
  • -lnetcdf:链接 NetCDF C 库。
  • -lm:链接数学库。

顺序很重要-lnetcdff 必须在 -lnetcdf 之前,因为 Fortran 库依赖 C 库。

每个系统的系统库可能存在差异,通过以下命令获得系统中netcdf库的具体参数

1
2
nc-config --all #NetCDF C
nf-config --all #NetCDF Fortran
  1. 与machines配置的关联

config_machines.xml 中:

1
2
<COMPILERS>mmj-gnu</COMPILERS>
<MPILIBS>openmpi</MPILIBS>

CIME 会查找:

  • COMPILER="mmj-gnu"<compiler> 块(本文件)
  • MPILIB="openmpi" 对应的 MPI 设置(在 config_machines.xml<mpirun> 中)

第三坑

在针对不同机器做出不同的配置文件后,接下来是创建并运行case,创建基础案例来验证环境。

在linux系统上会遇到权限Permission Denied问题,使用chmod命令解决,通过以下命令给scripts下所有的脚本加上运行权限。

1
chmod +x ./scripts/*

创建一个基础案例来验证环境。

1
./scripts/create_newcase --case mycase --compset X --res f19_g16

设置权限

1
chmod +x ./mycase/*

设置、构建和提交case

1
2
cd mycase
./case.setup

如果CIME配置文件格式没问题,这一步会很快成功。

第四坑

接下来是编译的环节,如果compilers配置正确,编译过程会少很多麻烦。

最初在编译的时候,遇到了以下几个问题:

  • 旧代码不兼容新编译器,报错Error: Type mismatch between actual argument at (1) and actual argument at (2) (INTEGER(4)/LOGICAL(4)).

    已通过添加-fallow-argument-mismatch编译开关解决。

  • 系统库文件找不到的报错,已通过设置machines配置SLIBS解决。

  • 权限问题Permission Denied,通过chmod解决。

    很多地方都会运行脚本,在cime目录下,运行批量设置权限的命令

    1
    2
    3
    4
    chmod +x ./src/externals/mct/configure
    chmod +x ./src/externals/genf90/genf90.pl
    find ./ -name "buildlib" -exec chmod +x {} \;
    find ./ -name "buildexe" -exec chmod +x {} \;
  • 编译pio时怎么也找不到NetCDF的问题。

    NetCDF 本身已经安装正确:

    1
    2
    nc-config --prefix -> /usr
    nf-config --prefix -> /usr

    但 PIO 的 CMake 检测失败:

    1
    2
    Could NOT find NetCDF_C
    Could NOT find NetCDF_Fortran

    原因是 Ubuntu 把库文件放在:

    1
    /usr/lib/x86_64-linux-gnu/

    而 CESM2.1.2 这套旧版 CMake 检测程序把 /usr 当作普通前缀,只会优先检查:

    1
    /usr/lib/

    因此,虽然 nc-config 能找到 NetCDF,PIO 的 find_package(NetCDF) 仍然找不到它。

    解决问题的方法是创建一套与旧CIME兼容的专用NetCDF目录。

    1
    2
    3
    mkdir ~/netcdf-cime
    ln -s /usr/include ~/netcdf-cime/include
    ln -s /usr/lib/x86_64-linux-gnu ~/netcdf-cime/lib

    然后确保machines配置中NETCDF_PATH指向该目录,例如

    1
    <env name="NETCDF_PATH">/home/lxx/netcdf-cime</env>

第五坑

构建成功后进行submit操作

1
./case.submit

这个过程中CIME会自动下载地图文件,然而古老的代码面对现今的服务器早已无所适从,整个坏掉了。

根据文章提供的文件下载方法,编写了自动下载补全缺失资源的脚本cesm_inputdata_downloader.py

将该文件放在mycase目录中,运行脚本自动下载文件

1
python cesm_inputdata_downloader.py

注意:下载文件的过程视网络环境可能需要魔法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python3
"""Download missing CESM inputdata files from the HTTPS inputdata repository.

Run this script from a CESM case directory, next to case.submit and
check_input_data. It replaces the old CESM FTP/GFTP downloader.
"""

from __future__ import annotations

import argparse
import os
import re
import subprocess
import sys
import time
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import quote
from urllib.request import Request, urlopen


DEFAULT_REPO = "https://svn-ccsm-inputdata.cgd.ucar.edu/trunk/inputdata"
INPUTDATA_RE = re.compile(r"inputdata/(?P<relative>[^'\"\s]+)")


def run_check(case_dir: Path) -> str:
"""Return check_input_data output without stopping on missing files."""
result = subprocess.run(
["./check_input_data"],
cwd=case_dir,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=False,
)
return result.stdout


def missing_relative_paths(check_output: str) -> list[str]:
"""Extract missing inputdata paths from check_input_data output."""
paths: set[str] = set()
for line in check_output.splitlines():
if "missing file" not in line:
continue
match = INPUTDATA_RE.search(line)
if match:
relative = match.group("relative").strip().rstrip("'")
if relative and not relative.startswith("null/"):
paths.add(relative)
return sorted(paths)


def download_one(relative: str, input_root: Path, repo: str, retries: int) -> bool:
target = input_root / relative
target.parent.mkdir(parents=True, exist_ok=True)
part = target.with_name(target.name + ".part")
url = repo.rstrip("/") + "/" + quote(relative, safe="/")

if target.exists() and target.stat().st_size > 0:
print(f"[skip] {relative}")
return True

for attempt in range(1, retries + 1):
try:
offset = part.stat().st_size if part.exists() else 0
headers = {"User-Agent": "cesm-inputdata-downloader/1.0"}
if offset:
headers["Range"] = f"bytes={offset}-"

print(f"[download {attempt}/{retries}] {relative}")
with urlopen(Request(url, headers=headers), timeout=60) as response:
status = getattr(response, "status", 200)
if offset and status != 206:
offset = 0
part.unlink(missing_ok=True)
mode = "ab" if offset else "wb"
with part.open(mode) as output:
while True:
chunk = response.read(1024 * 1024)
if not chunk:
break
output.write(chunk)

part.replace(target)
print(f"[done] {target}")
return True
except (HTTPError, URLError, TimeoutError, OSError) as exc:
print(f"[error] {relative}: {exc}", file=sys.stderr)
if attempt < retries:
time.sleep(2 ** attempt)

return False


def main() -> int:
parser = argparse.ArgumentParser(
description="Download missing CESM inputdata files over HTTPS."
)
parser.add_argument(
"--case",
type=Path,
default=Path.cwd(),
help="CESM case directory (default: current directory)",
)
parser.add_argument(
"--input-root",
type=Path,
default=None,
help="DIN_LOC_ROOT; otherwise read it from xmlquery",
)
parser.add_argument("--repo", default=DEFAULT_REPO, help="inputdata HTTPS root")
parser.add_argument("--retries", type=int, default=3)
parser.add_argument(
"--submit",
action="store_true",
help="run ./case.submit after the final input check succeeds",
)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()

case_dir = args.case.expanduser().resolve()
if not (case_dir / "check_input_data").is_file():
print(f"ERROR: {case_dir} is not a CESM case directory", file=sys.stderr)
return 2

input_root = args.input_root
if input_root is None:
query = subprocess.run(
["./xmlquery", "DIN_LOC_ROOT", "--value"],
cwd=case_dir,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=False,
)
value = query.stdout.strip().splitlines()[-1] if query.stdout.strip() else ""
if ":" in value:
value = value.rsplit(":", 1)[-1].strip()
input_root = Path(value).expanduser() if value else None
if input_root is None or str(input_root) in {"", "UNSET", "None"}:
print("ERROR: could not determine DIN_LOC_ROOT; use --input-root", file=sys.stderr)
return 2
input_root = input_root.resolve()

print(f"Case: {case_dir}")
print(f"Input root: {input_root}")
print(f"Repository: {args.repo}")

first_check = run_check(case_dir)
print(first_check, end="")
missing = missing_relative_paths(first_check)
if not missing:
print("No downloadable missing input files were found.")
elif args.dry_run:
print("Files that would be downloaded:")
print("\n".join(missing))
return 0
else:
failed = [
path
for path in missing
if not download_one(path, input_root, args.repo, max(1, args.retries))
]
if failed:
print("Download failed for:", file=sys.stderr)
print("\n".join(failed), file=sys.stderr)
return 1

final_check = run_check(case_dir)
print(final_check, end="")
remaining = missing_relative_paths(final_check)
if remaining:
print("Input files are still missing:", file=sys.stderr)
print("\n".join(remaining), file=sys.stderr)
return 1

print("Input-data check passed (null/null stub entries are ignored).")
if args.submit:
return subprocess.run(["./case.submit"], cwd=case_dir, check=False).returncode
return 0


if __name__ == "__main__":
raise SystemExit(main())

总结

本文记录了在 Ubuntu 26.04.1 LTS 上运行 CESM 2.1.2 enhanced weathering 模型的完整过程。核心是围绕 CIME 基础设施进行环境搭建与配置:先安装 GCC、gfortran、OpenMPI、NetCDF、HDF5 等依赖,从 Zenodo 下载修改版 CESM2.1.2,再在 ~/.cime 下编写 config_machines.xmlconfig_compilers.xml,定义本机路径、编译器、MPI、资源限制和链接库;随后通过 create_newcasecase.setupcase.buildcase.submit 创建并运行基础案例。过程中作者总结了从“第零坑”到“第五坑”的典型问题及解决方案:Python 3.12 移除 imp 模块需用低版本 Python/conda;机器和编译器配置需针对本机定制;脚本需批量 chmod 赋权;旧 Fortran 代码需加 -fallow-argument-mismatch;PIO/CMake 找不到 NetCDF 时需创建兼容的 netcdf-cime 软链接目录并设置 NETCDF_PATH;旧输入数据下载失效时可用自写的 cesm_inputdata_downloader.py 通过 HTTPS 补全。最终,在逐一解决环境、配置、权限、编译、库路径和输入数据问题后,这套祖传 CESM/CIME 代码成功在现代 Ubuntu 上运行起来。

终于运行成功了,加个鸡腿。

后记

本文中频繁出现lxxmmj等缩写,其中lxx指本文作者,mmj指喵喵酱(宠物的名字)。