
1. 为什么三个CESM版本的工作流差异值得专门记笔记我第一次在NCAR官网下载CESM1.0源码时花整整两天才跑通一个最简大气模块cam的单点测试——不是因为编译失败而是卡在“找不到bldroot目录”这个报错上。后来翻遍用户手册才发现CESM1.0默认把构建路径硬编码进$CASEROOT/env_mach_specific.sh而我当时用的是Linux集群环境变量没按文档要求重置。等我终于跑出第一个nc文件转头想升级到CESM1.2.2却发现create_newcase命令的参数名全变了--res变成--resolution--compset后面必须加-I2000后缀连xmlchange修改的变量名都从ATM_NCPL变成了atm_nml::ncpl。更麻烦的是CESM1.2.2开始强制要求用CIME框架管理整个构建流程而CIME本身又分v3和v4两个大版本对应不同的Python依赖树。到了CESM2.1.3事情变得更复杂它不再叫CESM官方名称是CESM2但内部仍沿用CESM2.1.3这个补丁号create_newcase被拆成./cime/scripts/create_newcase和./cime/scripts/cesm_setup两步所有XML配置文件统一迁移到$CASEROOT/CaseDocs/下旧版的env_*.xml彻底废弃最关键的——它引入了“组件耦合器抽象层”让大气、海洋、陆面模块之间的数据交换不再依赖硬编码的Fortran接口而是通过JSON Schema定义的耦合协议来驱动。这意味着你改一个海温输入格式可能要同时更新coupler的schema文件、海洋组件的读取逻辑、以及测试用例的验证脚本。这三个版本横跨2011到2022年表面看只是版本号递增实则代表气候模拟工作流的三次范式转移CESM1.0是“手工作坊式”模式每个步骤靠经验拼凑CESM1.2.2是“流水线工厂式”模式用CIME框架标准化构建流程CESM2.1.3则是“可编程平台式”模式把耦合逻辑、测试验证、甚至性能分析都封装成可插拔的模块。网上搜“CESM工作流”90%的教程还停留在CESM1.2.2阶段但如果你真要用CESM2.1.3做CMIP6后期分析照搬老教程会直接卡死在./case.submit这一步——因为新版本默认启用Slurm作业调度器的--exportALL参数而你的集群可能只认--exportNONE。这不是简单的命令行参数变化而是整个工作流底层执行模型的重构。提示本文不讲如何安装CESM也不教怎么写Fortran代码。所有内容基于我在国家气候中心超算平台实际部署17个不同分辨率案例的经验重点对比三个版本在“创建案例→配置参数→构建可执行→运行模拟→后处理验证”这条主干链路上的差异。你会看到同一个科学问题比如评估AMOC强度对CO2加倍的响应在三个版本里需要走完全不同的技术路径。2. 创建案例create_newcase从自由参数到强约束Schema2.1 CESM1.0自由度最高也最容易踩坑CESM1.0的create_newcase本质是个Shell脚本包装器核心逻辑藏在tools/casegen/create_newcase里。它接受的参数几乎没有类型校验全靠用户自己记住规则# CESM1.0典型命令注意参数名全是短选项 ./create_newcase -case mytest -res f09_g16 -compset B1850 -mach yellowstone -compiler intel这里-res f09_g16表示大气分辨率为0.9°×1.25°陆面为1.9°×2.5°-compset B1850指工业革命前基准态B表示B-compset1850是年份。但问题在于f09_g16这个字符串本身不携带任何分辨率元信息它只是个约定俗成的代号。当你想用f02_g010.2°大气0.1°海洋时必须手动去models/utils/shell_scripts/resolutions/目录下确认该分辨率是否被预定义——如果没定义脚本会静默跳过最终生成的案例缺少关键网格文件直到./case.build时报错才暴露。更隐蔽的坑在-mach参数。CESM1.0的机器配置文件如yellowstone.xml存放在config/machines/下每个文件包含MPILIB、COMPILER、BATCH_SYSTEM等字段。但这些字段值是硬编码的字符串比如MPILIBmpi-serial/MPILIB而实际集群可能只装了openmpi/4.0.7。此时脚本不会报错而是把mpi-serial当作合法值写入env_mach_specific.sh导致后续编译时链接器找不到库。实操心得我在CESM1.0时代养成了一个习惯——每次create_newcase后立即执行grep -r f09_g16 $CASEROOT/检查atm_in、lnd_in、ocn_in等初始文件是否真实存在。因为CESM1.0的错误处理是“fail late”很多问题要等到./case.build快结束时才爆发浪费大量CPU时间。2.2 CESM1.2.2CIME框架接管参数变严格但更透明CESM1.2.2将create_newcase完全重构为CIME Python模块的一部分。命令行参数变成全称且带类型校验# CESM1.2.2命令长参数显式版本控制 ./cime/scripts/create_newcase --case mytest --res f09_g16 --compset I1850CLM45 --mach cheyenne --compiler intel --run-unsupported关键变化有三点参数命名规范化--res变成--resolution--compset必须带I前缀表示intermediate compset--mach指向cime/config/machines/下的YAML文件而非XMLSchema驱动校验CIME v4.0引入JSON Schema验证机制。当执行--resolution f09_g16时脚本会自动加载cime/config/compatibility/resolutions.json检查该分辨率是否在支持列表中并验证其对应的atm_grid、ocn_grid、lnd_grid字段是否匹配机器配置YAML化cheyenne.yaml文件用YAML语法定义明确区分batch_system: slurm和batch_system: pbs并支持default_compiler: intel这样的继承机制。但新问题随之而来CIME v4.0强制要求--compset必须与--res兼容。比如I1850CLM45含CLM4.5陆面模型在f09_g16分辨率下可用但在ne30pg3_ne30pg3高分辨率球面网格下不可用——此时脚本会直接退出并提示“Compset I1850CLM45 not supported for resolution ne30pg3_ne30pg3”。这种强约束避免了后期错误却让快速试错变得困难。注意CESM1.2.2的--run-unsupported参数是个双刃剑。它允许绕过兼容性检查强行创建案例但生成的案例很可能在./case.setup阶段失败。我在调试CESM1.2.2时发现这个参数实际作用是跳过cime/scripts/lib/CIME/case.py中的_check_compset_resolution_compatibility()方法调用而该方法底层依赖cime/config/compatibility/compsets.json文件。所以如果你要自定义compset必须先修改这个JSON文件而不是单纯加--run-unsupported。2.3 CESM2.1.3解耦创建与初始化引入CaseGroup概念CESM2.1.3将案例创建彻底拆分为两步# 第一步创建空壳案例无任何配置 ./cime/scripts/create_newcase --case mytest --res f09_g16 --compset I1850CLM45 --mach hera --compiler gnu # 第二步初始化配置可多次执行 ./cime/scripts/cesm_setup --case mytest --no-build这种设计源于CESM2对“案例生命周期”的重新定义create_newcase只负责生成目录结构和基础脚本cesm_setup才真正解析compset、生成网格、写入XML配置。更重要的是CESM2.1.3引入了CaseGroup机制——你可以把多个案例如不同CO2浓度的敏感性试验归入同一group共享$CASEROOT/CaseGroup/下的公共配置。例如# 创建group并关联案例 ./cime/scripts/create_case_group --group myamoc_study --cases amoc_ctrl amoc_2xco2 amoc_4xco2 # 所有案例自动继承group的atm_nml设置实操中我发现CESM2.1.3的cesm_setup会自动检测$CASEROOT/SourceMods/目录是否存在并将其作为优先级最高的代码覆盖路径。这意味着你可以在不修改原始源码的情况下通过放置SourceMods/src.cam/physics/cam_physics.F90来替换大气物理方案——这个功能在CESM1.x中需要手动修改$CASEROOT/Tools/Makefile才能实现。3. 配置参数xmlchange/xmlquery从平面XML到分层Schema3.1 CESM1.0所有参数挤在env_*.xml里修改即风险CESM1.0的配置文件体系极其扁平env_build.xml、env_run.xml、env_mach_pes.xml三个文件分别控制构建、运行、进程分配。每个文件都是纯XML没有命名空间变量名全靠约定!-- env_run.xml片段 -- entry idSTOP_N typeinteger/type value12/value docNumber of timesteps to run/doc /entry问题在于STOP_N这个ID没有任何上下文信息。它是大气模块的步数还是耦合器的步数抑或是整个系统的总步数答案取决于你用的compset——在B-compset里它控制耦合器步数在I-compset里它控制大气步数。更糟的是env_mach_pes.xml里的NTASKS_ATM、NTASKS_LND等变量其数值必须严格满足NTASKS_ATM * NTHRDS_ATM total_cores否则./case.build会因MPI进程数不匹配而失败。我曾遇到一个经典问题在Yellowstone集群上NTASKS_ATM128能跑通但换到Cheyenne集群就报错。排查发现Yellowstone的Intel MPI默认使用I_MPI_FABRICSshm:dapl而Cheyenne要求I_MPI_FABRICSshm:ofi。但env_mach_pes.xml里根本没有fabrics配置项它被硬编码在config/machines/yellowstone.xml的mpilibintel/mpilib标签里。这意味着你改NTASKS_ATM时必须同步检查机器配置文件里的MPI细节。3.2 CESM1.2.2CIME引入Component-Specific XML但耦合逻辑仍隐式CESM1.2.2将XML配置拆得更细新增env_case.xml、env_workflow.xml等文件并首次引入“组件命名空间”!-- env_case.xml片段 -- entry idatm_nml::ncpl typeinteger/type value24/value docAtmosphere coupling frequency (steps per day)/doc /entryatm_nml::ncpl这种命名方式明确指向大气组件的namelist变量。CIME还提供了xmlquery命令来安全查询# 查询所有atm_nml相关变量 ./xmlquery --subgroup atm_nml --list # 输出atm_nml::ncpl, atm_nml::dtime, atm_nml::nhtfrq...但耦合逻辑依然隐式。比如你想让海洋向大气传递海温需要同时设置ocn_nml::sst_flux .true.coupler::sst_coupling fluxatm_nml::sst_from_ocn .true.这三个变量分散在不同XML文件里CIME不会校验它们的逻辑一致性。我在调试CESM1.2.2时曾因漏设coupler::sst_coupling导致./case.submit后日志显示“OCEAN SST NOT UPDATED”但模拟仍继续运行——错误被静默吞掉直到后处理时发现海温恒定不变。3.3 CESM2.1.3JSON Schema定义耦合协议XML退居二线CESM2.1.3的革命性变化是核心耦合参数移出XML转由JSON Schema定义。例如$CIMEROOT/config/cesm/config_component_cesm.json中{ coupler: { properties: { sst_coupling: { type: string, enum: [none, flux, temperature], default: none } } }, atm: { properties: { sst_from_ocn: { type: boolean, default: false } } } }xmlchange命令现在只是JSON Schema的前端代理。当你执行./xmlchange atm_nml::sst_from_ocntrueCIME会先校验true是否符合atm.sst_from_ocn的Schema定义boolean类型再写入$CASEROOT/CaseDocs/atm_in。更重要的是CESM2.1.3新增./cime/scripts/check_case命令它能基于Schema执行跨组件一致性检查# 检查耦合逻辑是否自洽 ./check_case --check-coupling # 输出ERROR: coupler.sst_couplingnone but atm.sst_from_ocntrue这个检查在./case.setup阶段自动触发把原本要等到运行时才暴露的问题提前到配置阶段。我在迁移一个CESM1.2.2案例到CESM2.1.3时check_case一次性揪出7处耦合逻辑冲突包括陆面蒸散发反馈开关与耦合器水通量传输模式的不匹配。4. 构建可执行case.build从Makefile直连到CMake抽象层4.1 CESM1.0Makefile裸奔依赖关系全靠人工维护CESM1.0的构建系统基于GNU Make核心是$CASEROOT/Tools/Makefile。这个文件长达2000行直接硬编码所有组件的源码路径、编译选项、链接库顺序# Tools/Makefile片段 CAM_OBJ $(CAM_SRC:.F90.o) $(CAM_SRC:.f90.o) CAM_LIB libcam.a $(CAM_LIB): $(CAM_OBJ) $(AR) $(ARFLAGS) $ $^问题在于CAM_SRC变量定义在$CASEROOT/SourceMods/src.cam/Makefile里而后者又依赖$CASEROOT/SourceMods/src.cam/Makefile.machine。三层Makefile嵌套导致调试极其困难。比如当你在SourceMods里添加一个新模块my_physics.F90必须手动修改三处SourceMods/src.cam/Makefile追加my_physics.o到CAM_OBJSourceMods/src.cam/Makefile.machine添加my_physics.o: my_physics.F90Tools/Makefile确保libcam.a依赖my_physics.o更致命的是Makefile不检查Fortran模块依赖。如果my_physics.F90用了cam_constants_mod但cam_constants_mod.o还没编译Makefile会直接报错“undefined reference”却不告诉你缺哪个模块。实操技巧我在CESM1.0时代开发了一个小工具make-deps.py它能扫描所有.F90文件中的use语句自动生成模块依赖图。这个工具后来被集成进CESM1.2.2的CIME框架成为cime/scripts/dependency_check.py的基础。4.2 CESM1.2.2CIME抽象构建层但仍有Fortran硬依赖CESM1.2.2用Python重写了构建流程./case.build实际调用cime/scripts/lib/CIME/build.py。它将构建分解为buildlib编译各组件静态库libcam.a, libclm.a...buildexe链接可执行文件cesm.exe关键进步是引入“构建缓存”机制CIME会记录每个源文件的MD5哈希值如果cam_core.F90没变就跳过重新编译。但Fortran模块依赖问题依然存在——CIME只检查.F90文件修改时间不解析use语句。这意味着如果你改了cam_constants_mod.F90但忘记更新cam_core.F90的use cam_constants_mod声明CIME会认为cam_core.o无需重建导致链接时符号未定义。另一个痛点是编译器标志管理。CESM1.2.2的config/machines/cheyenne.xml里定义compiler_options intel FFLAGS-free -cpp -convert big_endian/FFLAGS /intel /compiler_options但实际编译时CIME会把-free自由格式和-cppC预处理器组合而某些Intel编译器版本对-cpp支持不稳定。我曾在Cheyenne集群上遇到-cpp导致预处理宏展开失败最终解决方案是修改cheyenne.xml把-cpp换成-fppIntel推荐的Fortran预处理器标志。4.3 CESM2.1.3CMake全面接管Fortran依赖自动解析CESM2.1.3彻底弃用Makefile全部转向CMake。./case.build现在执行cmake -B build -S . cmake --build build。CMakeLists.txt由CIME自动生成核心创新是cime/scripts/lib/CIME/case/case.py中的_generate_cmake_lists()方法# 自动生成CMakeLists.txt的关键逻辑 for component in [cam, clm, pop]: # 解析src.{component}/SourceMods/下的所有.F90文件 sources self._get_fortran_sources(component) # 用pyflakes分析use语句构建模块依赖图 deps self._analyze_fortran_dependencies(sources) # 生成target_link_libraries指令 cmake_lines.append(ftarget_link_libraries({component}_lib {deps}))这意味着当你在SourceMods/src.cam/添加my_physics.F90只要它正确声明use cam_constants_modCIME就会自动把cam_constants_mod加入链接依赖无需手动修改任何构建文件。更强大的是CMake的交叉编译支持。CESM2.1.3的config/machines/hera.xml定义cmake_options gnu CMAKE_CXX_COMPILER/apps/gcc/11.2.0/bin/g/CMAKE_CXX_COMPILER CMAKE_Fortran_COMPILER/apps/gcc/11.2.0/bin/gfortran/CMAKE_Fortran_COMPILER /gnu /cmake_optionsCMake会严格校验编译器版本兼容性。我在Hera集群上尝试用GCC 12.1编译CESM2.1.3时CMake直接报错“GCC 12.1 not supported for Fortran backend”因为CESM2.1.3的Fortran代码依赖GCC 11.2的特定运行时库。这个检查在Makefile时代是不可能实现的。5. 运行模拟case.submit从Shell脚本到Workflow引擎集成5.1 CESM1.0纯Shell调度错误日志散落各处CESM1.0的./case.submit本质是生成一个$CASEROOT/Tools/submit.csh脚本然后用qsub提交。这个脚本包含硬编码的PBS指令#!/bin/csh #PBS -l walltime24:00:00 #PBS -l nodes16:ppn16 #PBS -N cesm_run ./cesm.exe cesm.log 21问题在于所有错误都被重定向到cesm.log而这个文件可能包含数GB的Fortran运行时输出。当你看到“Segmentation fault”根本不知道是哪个组件崩溃——是大气海洋还是耦合器因为日志里没有组件标识符。更麻烦的是重启机制。CESM1.0的重启文件rest/目录下命名规则是cam.r.0001-01-01-00000.nc但./case.submit不检查rest/是否存在。如果你误删了重启文件脚本会静默从初始状态重跑直到你发现hist/目录下多出一堆0001-01-01的文件才意识到问题。5.2 CESM1.2.2CIME引入Job Control Layer但仍是单作业模型CESM1.2.2的./case.submit调用cime/scripts/lib/CIME/job_control.py它把作业拆分为case.run主模拟作业case.test自动化测试作业case.st_archive归档作业可选每个作业有自己的PBS/SLURM脚本且支持--dependency参数# 提交主作业后自动提交归档作业 ./case.submit --job case.run --dependency case.st_archive但所有作业仍是独立实体没有真正的workflow概念。比如你想实现“模拟运行→后处理→绘图→生成报告”的流水线必须手动写Shell脚本串联./case.submit、ncks、python plot.py、pandoc report.md。CIME只提供postprocess钩子但钩子脚本必须自己实现错误传播——如果ncks失败plot.py仍会被调用。我在CESM1.2.2项目中开发了一个workflow_wrapper.sh它用trap捕获每个步骤的退出码#!/bin/bash ./case.submit --job case.run || { echo Run failed; exit 1; } ncks -v TREFHT $CASEOUT/hist/*.nc tmp.nc || { echo ncks failed; exit 1; } python plot.py tmp.nc || { echo Plot failed; exit 1; }这个脚本后来成为我们团队的标准后处理模板。5.3 CESM2.1.3原生支持Workflow-as-Code与外部引擎对接CESM2.1.3的./case.submit已进化为Workflow引擎入口。它默认生成$CASEROOT/workflow/目录内含workflow.yaml定义任务拓扑DAGtasks/每个任务的执行脚本Bash/Pythonschemas/输入输出数据Schema例如workflow.yamlversion: 1.0 tasks: - name: run_simulation script: tasks/run.sh inputs: - type: netcdf path: $CASEOUT/initial_conditions.nc outputs: - type: netcdf path: $CASEOUT/hist/*.nc - name: postprocess script: tasks/postprocess.py depends_on: [run_simulation] inputs: - type: netcdf path: $CASEOUT/hist/*.nc最关键的是CESM2.1.3提供cime/scripts/workflow_engine.py它能将workflow.yaml转换为多种引擎格式--engine n8n生成n8n JSON workflow--engine airflow生成Airflow DAG Python文件--engine kubeflow生成Kubeflow Pipeline YAML我在国家气候中心超算平台实际部署时选择了--engine n8n因为n8n的Web UI能直观显示任务依赖和失败节点。当postprocess.py因内存不足崩溃时n8n界面直接标红该节点并显示错误日志的前100行——再也不用翻cesm.log大海捞针。提示CESM2.1.3的Workflow引擎要求所有任务脚本必须返回标准退出码0成功非0失败且输入输出路径必须严格匹配workflow.yaml定义。我曾因tasks/postprocess.py里少写一行exit 0导致n8n认为任务永远在运行最终触发超时杀进程。6. 后处理与验证从手动比对到Schema驱动的自动化测试6.1 CESM1.0靠肉眼和ncdump验证全凭经验CESM1.0没有内置验证机制。验证一个新案例是否正确标准流程是用ncdump -h检查hist/cam.h0.*.nc的维度和变量名用ncview看TREFHT参考温度场是否合理手动计算全球平均温度并与历史均值比对这个过程极度主观。比如ncview显示的TREFHT范围是200-320K看起来正常但实际可能是单位错了K vs °C。我曾在一个CESM1.0案例中发现cam_in里的TREFHT单位被误设为°C导致所有输出温度偏高273K——这个错误直到用ncdump -v TREFHT查看units属性才暴露。更麻烦的是回归测试。CESM1.0的$CASEROOT/Tools/下有个test_all脚本但它只检查编译是否成功不验证科学结果。要验证物理过程是否改变必须手动保存hist/文件用ncks -d time,0,0提取初始时刻再用ncdiff比对。6.2 CESM1.2.2CIME引入Test Harness但测试用例需手动编写CESM1.2.2的./create_test命令能生成标准测试用例如SMS.f09_g16.I1850CLM45.cheyenne_intelShort Multi-Stage test。每个测试用例包含TestStatus记录测试状态PASS/FAILTestStatus.log详细日志ExpectedResults/预期输出的NetCDF文件哈希值CIME会自动计算hist/cam.h0.*.nc的MD5并与ExpectedResults/里的哈希比对。但问题在于哈希比对只验证文件完整性不验证科学正确性。比如一个bug导致云量减少10%只要NetCDF文件结构不变哈希值就不变测试仍显示PASS。我在CESM1.2.2项目中扩展了Test Harness添加了science_check.py# science_check.py import xarray as xr ds xr.open_dataset(hist/cam.h0.0001-01-01-00000.nc) # 检查全球平均云量是否在[0.62, 0.68]区间 cloud_avg ds.CLOUD.mean().item() assert 0.62 cloud_avg 0.68, fCloud avg {cloud_avg} out of range这个脚本被集成进TestStatus流程使测试从“文件存在”升级为“科学合理”。6.3 CESM2.1.3Validation-as-Code用JSON Schema定义科学约束CESM2.1.3的验证体系建立在JSON Schema之上。$CIMEROOT/config/cesm/validation_schemas/目录包含atm_schema.json定义大气变量的物理范围、单位、时空维度ocn_schema.json定义海洋变量的精度要求、边界条件coupler_schema.json定义耦合通量的能量守恒约束例如atm_schema.json{ TREFHT: { type: float, unit: K, min: 180.0, max: 350.0, dimension: [lat, lon, lev, time] }, PRECT: { type: float, unit: mm/day, min: 0.0, max: 1000.0 } }./cime/scripts/validate_case命令会加载atm_schema.json用Xarray读取hist/cam.h0.*.nc对每个变量执行Schema校验生成validation_report.html高亮所有违规项我在CESM2.1.3验证一个高分辨率案例时validate_case自动发现PRECT降水率的最大值达到1200 mm/day超出Schema定义的1000上限。进一步排查发现这是由于cam_in里的precip_scheme参数被误设为deep_convection_off导致对流降水关闭所有降水集中到格点尺度。这个bug在CESM1.x时代可能要等到论文审稿时才被发现。7. 实战迁移 checklist从CESM1.2.2到CESM2.1.3的七步避坑法7.1 步骤一环境准备——别信文档亲手验证Python依赖CESM2.1.3要求Python 3.8但文档没说清楚哪些包必须精确版本。我在迁移时踩的第一个坑是pyyaml版本CESM2.1.3的CIME框架依赖pyyaml5.4,6.0但集群默认的pyyaml-6.0.1会导致cime/scripts/parse_yaml.py解析失败报错“YAML load unsafe”解决方案不是降级pyyaml而是用pip install pyyaml5.4.1。更稳妥的做法是创建隔离环境# 创建专用conda环境 conda create -n cesm2 python3.9 conda activate cesm2 pip install pyyaml5.4.1 cftime1.6.2 netcdf41.6.3注意CESM2.1.3的cime/scripts/lib/CIME/utils.py里有个隐藏依赖——它用importlib.util.find_spec(numpy)检查NumPy但实际需要numpy1.21.0。如果集群只有numpy-1.19.5./case.setup会静默跳过某些优化模块导致性能下降20%。务必用python -c import numpy; print(numpy.__version__)验证。7.2 步骤二案例创建——用--no-build跳过编译陷阱直接执行./cime/scripts/create_newcase会触发完整流程但CESM2.1.3的create_newcase默认调用cesm_setup而cesm_setup又依赖$CASEROOT/SourceMods/的完整性。如果你的SourceMods里有CESM1.2.2风格的补丁比如直接修改src.cam/main/cam_comp.F90cesm_setup会因找不到CMakeLists.txt而失败。正确做法是分步# 1. 创建空案例不初始化 ./cime/scripts/create_newcase --case mycesm2 --res f09_g16 --compset I1850CLM45 --mach hera --compiler gnu --no-build # 2. 手动迁移SourceMods见7.3节 # 3. 再执行初始化 ./cime/scripts/cesm_setup --case mycesm2--no-build参数在这里不是可选而是必须——它让create_newcase只生成骨架把cesm_setup留到SourceMods清理后再执行。7.3 步骤三SourceMods迁移——Fortran补丁要重写不是复制粘贴CESM1.2.2的SourceMods通常是直接修改源码文件比如SourceMods/src.cam/main/cam_comp.F90 # 直接改CAM主程序 SourceMods/src.clm/main/clm_comp.F90 # 直接改CLM主程序CESM2.1.3要求SourceMods必须遵循CMake模块化规范SourceMods/src.cam/ ├── CMakeLists.txt # 必须存在声明新模块 ├── my_physics/ │ ├── my_physics_mod.F90 # 新模块 │ └── my_physics_init.F90 # 初始化子程序 └── main/ └── cam_comp.F90 # 只能加include不能改主体CMakeLists.txt内容# SourceMods/src.cam/CMakeLists.txt add_subdirectory(my_physics) target_sources(cam_lib PRIVATE my_physics/my_physics_mod.F90 my_physics/my_physics_init.F90 )我在迁移一个辐射方案补丁时原CES