Fortran 笔记之 Namelist I/O
Namelist I/O
参考自Introduction to Modern Fortran for the Earth System Sciences
在从简单的ASCII文件读取数据时,必须确保以正确的顺序将值读入正确的变量,以匹配输入文件的内容。由于没有简单的方法在文件本身中记录数据,因此处理此类数据可能会变得令人沮丧且容易出错。Fortran中namelist I/O的概念旨在帮助解决这些情况,尤其是当涉及少量数据时(例如在地球系统模式中加载/保存模式参数时)。
当使用namelist时,程序在两个地方需要考虑:namelist 组(group)(出现在Fortran代码中)和 .nml 文件(存储数据的地方)。我们将在下面讨论这两个问题,然后提供一个更现实的示例。
定义和使用namelist组
在Fortran应用中,namelist组是通过语句(statements)定义的。语句(statements)仅在程序单元的声明部分(declarations part)出现。declaring这样一个组的语法是:
! Declarations for var1 , ..., varn
namelist/namelist_group_name/ var1 [, var2 , ... , varn ]
! Other declarations ...
! Executable statements of the (sub)program
本质上,这告诉编译器var1…varn应该在使用此namelist的I/O语句中被视为一个单元。为了举例说明,我们将如何定义一个组,该组将两个标量变量(逻辑和实数类型)、一个数组和一个用户定义的类型链接在一起:
8 ! user-defined DT 9 type GeoLocation 10 real :: mLon, mLat 11 end type GeoLocation 12 13 ! 变量声明 Variable-declarations 14 logical :: flag = .false. 15 integer :: inFileID=0, outFileID=0 16 real :: threshold = 0.8 17 real, dimension(10) :: array = 4.8 18 type(GeoLocation) :: myPos = GeoLocation(8.81, 53.08) 19 20 ! namelist组(将变量绑定到一起,用于namelist IO) namelist-group (binds variables together, for namelist I/O). 21 namelist/my_namelist/ flag, threshold, array, myPos
Listing 5.5 src/Chapter5/demo_namelist.f90 (excerpt)
一旦定义了namelist,就可以在read-和write-语句中使用它。例如,我们可以在文件中写入当前程序状态:
26 ! 将当前数据写入到namelist文件中 Write current data-values to a namelist-file
27 open(newunit=outFileID, file="demo_namelist_write.nml")
28 write(outFileID, nml=my_namelist)
29 close(outFileID)
Listing 5.6 src/Chapter5/demo_namelist.f90 (excerpt)
其中,在上面的write语句中,我们有nml=my_namelist,而不是通常的格式说明符;此外,没有指定要写入的元素列表(这将会写入完整的namelist)。
当然,也可以从预先存在的namelist文件中读取,从而允许我们基于该文件更新namelist中的部分(或全部)数据。对于我们的测试程序,这看起来像:
31 ! Update (read) *some* values in the namelist, from another file
32 open(newunit=inFileID, file="demo_namelist_read.nml")
33 read(inFileID, nml=my_namelist)
34 close(inFileID)
Listing 5.7 src/Chapter5/demo_namelist.f90 (excerpt)
其中可能的输入文件(由我们使用常规文本编辑器创建)为:
4 &my_namelist 5 ! Comments can be added on distinct lines... 6 myPos%mLon = 9.72, ! ...or at the end of a line. 7 myPos%mLat = 52.37, 8 array = 6*9.1, ! shorthand-notation for constant 9 ! sections in an array. 10 array(1) = 2.9 ! overrides previous specification for 11 ! first array element 12 /
Listing 5.8 src/Chapter5/demo_namelist_read.nml (excerpt)—a simple namelist file
请注意,我们可以按任何顺序指定namelist的组件,甚至可以省略其中的一些组件。这些功能总结如下。
namelist文件的结构 在创建(或解释)新namelist时,如Listing 5.8所示,有几个简单的语法规则要考虑:
- 首先,namelist的名称应该出现在字符( & )后面,且没有任何空格(在我们的例子中是my_namelist)。
- 其次,真实信息信息被指定为键值对(key-value pairs)(例如 var_name=var_value)。每一对可以出现在不同的行上,或者其中的几对可以聚合在一行中,用逗号( ,)分隔。
- 最后,用一条斜线( /)标志着namelist规范的结束。
其他需要注意的:
- 在整个文件中,可以(甚至建议)像在普通Fortran代码中那样编写注释,以便更好地记录数据条目
- 在这样的文件中,在相应的namelist中只指定部分变量是完全可以接受的。如果是这种情况,未指定的变量将不受read语句的影响。这项功能对于地球系统模式来说非常方便,因为它允许用户编写简短的输入文件,只包含他们需要更改的参数
- 对于需要用常量值初始化的大型数组,可以使用简写符号n_repetitions*value;例如,Lisiting 5.8中的第8行相当于:
array = 9.1, 9.1, 9.1, 9.1, 9.1, 9.1, ! <-------- 6 repetitions --------> ! NOTE: array (7:10) - elements are not affected.
- 一个变量可以指定多次。在这种情况下,规范可以解释为顺序赋值(因此最后将取最后一个值)
- 不必按照代码中namelist组定义中出现的顺序指定变量。Fortran运行时系统将自动处理文件的解析。
文件本身(通常提供.nml扩展名)是可读的ASCII格式,这对于大量数据来说是无效的(我们在第5.2.2节中讨论了解决方案)
示例:将名称扩散的热扩散程序简化为命名者
更复杂的例子,让我们考虑如何改进第4.1节中讨论的应用程序读取模式参数的过程。在该版本的代码中,参数是在非描述性ASCII文件中指定的,文件如下:
100. 75. 50. 25. 200 1.15E-6 30.
Listing 5.9 src/Chapter4/config_file_formatted.in —previous version of input file, for the heat diffusion solver (Chap. 4)
这不是一种可靠的方法,因为(在文件本身中)没有关于每行输入代表什么的信息。我们可以通过修改Config-类型的构造函数(=初始值设定项)来轻松改进这一点。我们需要做的更改(相对于程序src/Chapter4/solve_heat_diffusion.f90)实际上是最小的,并且集中在初始化函数( createConfig)中:
48 module Config_class 49 use NumericKinds 50 implicit none 51 private 52 53 type, public :: Config 54 real(RK) :: mDiffusivity = 1.15E-6_RK, & ! sandstone 55 ! NOTE: "physical" units here (Celsius) 56 mTempA = 100._RK, & 57 mTempB = 75._RK, & 58 mTempC = 50._RK, & 59 mTempD = 25._RK, & 60 mSideLength = 30._RK 61 integer(IK) :: mNx = 200 ! # of points for square side-length 62 end type Config 63 64 ! Generic IFACE for user-defined CTOR 65 interface Config 66 module procedure createConfig 67 end interface Config 68 69 contains 70 type(Config) function createConfig( cfgFilePath ) 71 character(len=*), intent(in) :: cfgFilePath 72 integer :: cfgFileID 73 74 ! 常量作为保护标记,使我们可以检查是否从namelist中获取了值。
75 ! 注:“-9999”是一个整数,可以用单精度/双精度IEEE实数的尾数*精确*表示。这意味着表达式: 76 ! int(aReal, IK) == MISS
77 ! 将会在以下情况下为真: (a) ‘aReal'被用MISS初始化,且(b) 其他说明(如NAMELIST-IO)没有修改'aReal'的值。
78 ! Constant to act as safeguard-marker, allowing us to check if values were actually obtained from the NAMELIST. 79 ! NOTE: '-9999' is an integer which can be *exactly* represented in the mantissa of single-/double-precision IEEE reals. This means that the expression:
80 ! int(aReal, IK) == MISS
81 ! will be TRUE as long as
82 ! (a) 'aReal' was initialized with MISS and
83 ! (b) other instructions (e.g. NAMELIST-I/O here) did not modify the value of 'aReal'.
84 integer(IK), parameter :: MISS = -9999 85 86 ! We need local-variables, to mirror the ones in the NAMELIST 87 real :: sideLength=MISS, diffusivity=MISS, & 88 tempA=MISS, tempB=MISS, tempC=MISS, tempD=MISS 89 integer :: nX = MISS 90 ! NAMELIST definition 91 namelist/heat_diffusion_ade_params/ sideLength, diffusivity, nX, & 92 tempA, tempB, tempC, tempD 93 94 open( newunit=cfgFileID, file=trim(cfgFilePath), status='old', action='read' ) 95 read(cfgFileID, nml=heat_diffusion_ade_params) 96 close(cfgFileID) 97 98 ! For diagnostics: echo information back to terminal. 99 write(*,'(">> START: Namelist we read <<")') 100 write(*, nml=heat_diffusion_ade_params) 101 write(*,'(">> END: Namelist we read <<")') 102 103 ! Assimilate data read from NAMELIST into new object's internal state. 104 ! NOTE: Here, we make use of the safeguard-constant, so that default values 105 ! (from the type-definition) are overwritten only if the user provided 106 ! replacement values (in the NAMELIST). 107 if( int(sideLength, IK) /= MISS ) createConfig%mSideLength = sideLength 108 if( int(diffusivity, IK) /= MISS ) createConfig%mDiffusivity = diffusivity 109 if( nX /= MISS ) createConfig%mNx = nX 110 if( int(tempA, IK) /= MISS ) createConfig%mTempA = tempA 111 if( int(tempB, IK) /= MISS ) createConfig%mTempB = tempB 112 if( int(tempC, IK) /= MISS ) createConfig%mTempC = tempC 113 if( int(tempD, IK) /= MISS ) createConfig%mTempD = tempD 114 end function createConfig 115 end module Config_class
Listing 5.10 src/Chapter5/solve_heat_diffusion_v2.f90 (excerpt
作为namelistI/O的必要基础设施,我们添加了几个局部变量(第86-89行),它们被打包到namelist定义中(第90-92行)。在第94–96行中,使用namelist,作为调试工具,namelist组中变量的最终状态打印在屏幕上。
新代码的其余部分(第74-84行和第103-113行)是考虑不完整的namelist的可能性所必需的。如前所述,此功能对于简化与代码的交互非常有用。例如,如果用户只需要更改材料的扩散率(同时将所有其他参数保持为默认值),则输入文件应仅包含新扩散率的条目。然而,为了支持这种配置的部分更新,我们需要一种机制来检查参数的值是否确实是从namelist文件中获得的。我们这里的简单方法是使用一个特殊值(MISS=-9999)初始化namelist组的所有数字成员,已知该值位于模拟参数的有效范围之外。请注意,MISS是一个整数,但它也可用于将浮点变量标记为“dirty(脏)”(未初始化)。所有局部变量将在此状态下启动,并且只有在namelist读取命令(第95行)期间更新时,才会作为模拟参数传输。
作为基于namelist的输入文件示例,我们有:
1 &heat_diffusion_ade_params 2 ! Physical parameters. 3 diffusivity = 1.15e-6, ! thermal-diffusivity coeff (m^2/s) 4 ! NOTE: commenting-out line below will cause default-value to be picked 5 sideLength = 10. ! length of square-side (m) 6 7 ! Constant-temperature boundary conditions. 8 tempA = 100., 9 tempB = 75., 10 tempC = 50., 11 tempD = 25., 12 13 ! Numerical parameters. 14 nX = 300 15 /
Listing 5.11 src/Chapter5/heat_diffusion_config.nml – input file for the new version of the heat diffusion solver
通过使用namelist来指定模型参数,配置文件的可读性明显得到了改善。由于配置文件通常是模型与用户(地球系统模式中的气候科学家)的“接口”的一部分,因此许多地球系统模式模型广泛使用这种技术可能并不奇怪。