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來指定模型參數,配置文件的可讀性明顯得到了改善。由於配置文件通常是模型與用戶(地球系統模式中的氣候科學家)的“接口”的一部分,因此許多地球系統模式模型廣泛使用這種技術可能並不奇怪。