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来指定模型参数,配置文件的可读性明显得到了改善。由于配置文件通常是模型与用户(地球系统模式中的气候科学家)的“接口”的一部分,因此许多地球系统模式模型广泛使用这种技术可能并不奇怪。

posted @ 2022-04-05 09:43  chinagod  阅读(1685)  评论(0编辑  收藏  举报