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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM