Skip to content

Commit 25a69a9

Browse files
committed
Merge remote-tracking branch 'upstream/development' into hplin/fhist_c4
2 parents 583eedd + fcd5d7b commit 25a69a9

9 files changed

Lines changed: 780 additions & 126 deletions

File tree

‎cime_config/namelist_definition_cam.xml‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,38 @@
236236
<value>.false.</value>
237237
</values>
238238
</entry>
239+
<entry id="ncdata_check_exclude">
240+
<type>char*256(200)</type>
241+
<category>initial_conditions</category>
242+
<group>physics_nl</group>
243+
<desc>
244+
Ordered list of glob patterns ('*' matches any run of characters)
245+
excluding variables from the `{{ hilight }}ncdata_check{{ closehilight }}`
246+
comparison. These case-insensitive patterns are matched against
247+
the name reported in the physics check output.
248+
249+
For constituent-dimensioned variables, the per-constituent
250+
file variable name is used.
251+
252+
A gitignore-style keep is also supported:
253+
A pattern with a leading '!' keeps (still compares) the name,
254+
any other pattern excludes it; names matching no pattern are compared.
255+
The first pattern that matches a name decides.
256+
257+
Example: '!aerochem_vmr_post*', 'aerochem_vmr_*' excludes the aerochem_vmr rows except the aerochem_vmr_post* ones.
258+
'aerochem_vmr_*', '!aerochem_vmr_post*' excludes ALL aerochem_vmr rows, including the aerochem_vmr_post* ones.
259+
260+
Excluded rows are listed in the check output and are not silently dropped.
261+
262+
This is intended to be used in conjunction with ncdata_check_err=.true.
263+
in testmods to exclude any expected differences.
264+
265+
Default: '' (no exclusions)
266+
</desc>
267+
<values>
268+
<value>''</value>
269+
</values>
270+
</entry>
239271
<entry id="min_difference">
240272
<type>real</type>
241273
<category>diagnostics</category>

‎src/core_utils/string_core_utils.F90‎

Lines changed: 121 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ module string_core_utils
77
public :: core_int_date_to_yyyymmdd ! Convert encoded date integer to "yyyy-mm-dd" format
88
public :: core_int_seconds_to_hhmmss ! Convert integer seconds past midnight to "hh:mm:ss" format
99
public :: split ! Parse a string into tokens, one at a time
10-
public :: stringify ! Convert one or more values of any intrinsic data types to a character string for pretty printing
10+
public :: stringify ! Convert one or more values of any intrinsic data types to a character string.
1111
public :: tokenize ! Parse a string into tokens
1212
public :: increment_string ! Increment a string whose ending characters are digits.
1313
public :: last_non_digit ! Get position of last non-digit in the input string.
1414
public :: get_last_significant_char ! Get position of last significant (non-blank, non-null) character in string.
15+
public :: core_glob_match ! Match a string against a '*'-wildcard glob pattern.
16+
public :: core_glob_list_excluded ! First-match-wins exclusion decision over an ordered glob pattern list ('!' keeps).
1517

1618
interface tokenize
1719
module procedure tokenize_into_first_last
@@ -20,16 +22,16 @@ module string_core_utils
2022

2123
contains
2224

23-
character(len=10) pure function core_to_str(n)
25+
character(len=10) pure function core_to_str(n) result(str)
2426
! return default integer as a left justified string
2527

2628
integer, intent(in) :: n
27-
28-
write(core_to_str,'(i0)') n
29-
29+
30+
write(str,'(i0)') n
31+
3032
end function core_to_str
3133

32-
character(len=10) pure function core_int_date_to_yyyymmdd (date)
34+
character(len=10) pure function core_int_date_to_yyyymmdd (date) result(date_str)
3335
! Undefined behavior if date <= 0
3436

3537
! Input arguments
@@ -44,12 +46,12 @@ character(len=10) pure function core_int_date_to_yyyymmdd (date)
4446
month = (date - year*10000) / 100
4547
day = date - year*10000 - month*100
4648

47-
write(core_int_date_to_yyyymmdd, '(i4.4,A,i2.2,A,i2.2)') &
48-
year,'-',month,'-',day
49+
write(date_str, '(i4.4,A,i2.2,A,i2.2)') &
50+
year,'-',month,'-',day
4951

5052
end function core_int_date_to_yyyymmdd
5153

52-
character(len=8) pure function core_int_seconds_to_hhmmss (seconds)
54+
character(len=8) pure function core_int_seconds_to_hhmmss (seconds) result(time_str)
5355
! Undefined behavior if seconds outside [0, 86400]
5456

5557
! Input arguments
@@ -64,8 +66,8 @@ character(len=8) pure function core_int_seconds_to_hhmmss (seconds)
6466
minutes = (seconds - hours*3600) / 60
6567
secs = (seconds - hours*3600 - minutes*60)
6668

67-
write(core_int_seconds_to_hhmmss,'(i2.2,A,i2.2,A,i2.2)') &
68-
hours,':',minutes,':',secs
69+
write(time_str,'(i2.2,A,i2.2,A,i2.2)') &
70+
hours,':',minutes,':',secs
6971

7072
end function core_int_seconds_to_hhmmss
7173

@@ -116,12 +118,12 @@ end subroutine split
116118
!> If `value` contains zero element or is of unsupported data types, an empty character string is produced.
117119
!> If `separator` is not supplied, it defaults to ", " (i.e., a comma and a space).
118120
!> (KCW, 2024-02-04)
119-
pure function stringify(value, separator)
121+
pure function stringify(value, separator) result(str)
120122
use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64
121123

122124
class(*), intent(in) :: value(:)
123125
character(*), optional, intent(in) :: separator
124-
character(:), allocatable :: stringify
126+
character(:), allocatable :: str
125127

126128
integer, parameter :: sizelimit = 1024
127129

@@ -138,7 +140,7 @@ pure function stringify(value, separator)
138140
n = min(size(value), sizelimit)
139141

140142
if (n == 0) then
141-
stringify = ''
143+
str = ''
142144

143145
return
144146
end if
@@ -220,12 +222,12 @@ pure function stringify(value, separator)
220222

221223
write(buffer, format) value
222224
class default
223-
stringify = ''
225+
str = ''
224226

225227
return
226228
end select
227229

228-
stringify = trim(buffer)
230+
str = trim(buffer)
229231
end function stringify
230232

231233
!> Parse a string into tokens. Each character in `set` is a token delimiter.
@@ -305,7 +307,7 @@ end subroutine tokenize_into_tokens_separator
305307
! 0 success
306308
! -1 error: no trailing digits in string
307309
! -2 error: incremented integer is out of range
308-
integer function increment_string(s, inc)
310+
integer function increment_string(s, inc) result(status)
309311
integer, intent(in) :: inc ! value to increment string (may be negative)
310312
character(len=*), intent(inout) :: s ! string with trailing digits
311313

@@ -323,7 +325,7 @@ integer function increment_string(s, inc)
323325
ndigit = lstr - lnd
324326

325327
if(ndigit == 0) then
326-
increment_string = -1
328+
status = -1
327329
return
328330
end if
329331

@@ -338,46 +340,46 @@ integer function increment_string(s, inc)
338340

339341
! Increment the integer
340342
ival = ival + inc
341-
if( ival < 0 .or. ival > 10**ndigit-1 ) then
342-
increment_string = -2
343+
if(ival < 0 .or. ival > 10**ndigit-1) then
344+
status = -2
343345
return
344346
end if
345347

346348
! Overwrite trailing digits
347349
pow = ndigit
348350
do i = lnd+1,lstr
349-
digit = MOD( ival,10**pow ) / 10**(pow-1)
350-
s(i:i) = CHAR( ICHAR('0') + digit )
351+
digit = MOD(ival,10**pow) / 10**(pow-1)
352+
s(i:i) = CHAR(ICHAR('0') + digit)
351353
pow = pow - 1
352354
end do
353355

354-
increment_string = 0
356+
status = 0
355357

356358
end function increment_string
357359

358360
! Get position of last non-digit in the input string.
359361
! Return values:
360362
! > 0 => position of last non-digit
361363
! = 0 => token is all digits (or empty)
362-
integer pure function last_non_digit(s)
364+
integer pure function last_non_digit(s) result(pos)
363365
character(len=*), intent(in) :: s
364366
integer :: n, nn, digit
365367

366368
n = get_last_significant_char(s)
367369
if(n == 0) then ! empty string
368-
last_non_digit = 0
370+
pos = 0
369371
return
370372
end if
371373

372374
do nn = n,1,-1
373375
digit = ICHAR(s(nn:nn)) - ICHAR('0')
374376
if( digit < 0 .or. digit > 9 ) then
375-
last_non_digit = nn
377+
pos = nn
376378
return
377379
end if
378380
end do
379381

380-
last_non_digit = 0 ! all characters are digits
382+
pos = 0 ! all characters are digits
381383

382384
end function last_non_digit
383385

@@ -386,13 +388,13 @@ end function last_non_digit
386388
! Return values:
387389
! > 0 => position of last significant character
388390
! = 0 => no significant characters in string
389-
integer pure function get_last_significant_char(cs)
391+
integer pure function get_last_significant_char(cs) result(pos)
390392
character(len=*), intent(in) :: cs ! Input character string
391393
integer :: l, n
392394

393395
l = LEN(cs)
394396
if( l == 0 ) then
395-
get_last_significant_char = 0
397+
pos = 0
396398
return
397399
end if
398400

@@ -401,8 +403,97 @@ integer pure function get_last_significant_char(cs)
401403
exit
402404
end if
403405
end do
404-
get_last_significant_char = n
406+
pos = n
405407

406408
end function get_last_significant_char
407409

410+
!> Match `string` against a glob `pattern` in which `*` matches any run of
411+
!> characters, including an empty one; every other character, including `?`,
412+
!> matches only itself. Trailing blanks in both arguments are not significant
413+
!> (leading and embedded blanks are). An empty pattern matches only an empty
414+
!> string.
415+
pure logical function core_glob_match(string, pattern) result(is_match)
416+
character(len=*), intent(in) :: string
417+
character(len=*), intent(in) :: pattern
418+
419+
integer :: ls, lp ! significant lengths of string/pattern
420+
integer :: s, p ! current positions in string/pattern
421+
integer :: star_p ! position of the most recent '*' in pattern (0 = none seen)
422+
integer :: star_s ! string position currently tried as that star's first unmatched character
423+
424+
ls = len_trim(string)
425+
lp = len_trim(pattern)
426+
427+
s = 1
428+
p = 1
429+
star_p = 0
430+
star_s = 0
431+
432+
do while (s <= ls)
433+
if (p <= lp) then
434+
if (pattern(p:p) == '*') then
435+
! Record the star and first try matching it to nothing
436+
star_p = p
437+
star_s = s
438+
p = p + 1
439+
cycle
440+
else if (pattern(p:p) == string(s:s)) then
441+
p = p + 1
442+
s = s + 1
443+
cycle
444+
end if
445+
end if
446+
! Mismatch: backtrack to the most recent star and extend its match
447+
! by one character; with no star to extend, the match fails.
448+
if (star_p > 0) then
449+
star_s = star_s + 1
450+
s = star_s
451+
p = star_p + 1
452+
else
453+
is_match = .false.
454+
return
455+
end if
456+
end do
457+
458+
! String fully consumed; the pattern matches if only stars remain
459+
do while (p <= lp)
460+
if (pattern(p:p) /= '*') exit
461+
p = p + 1
462+
end do
463+
is_match = (p > lp)
464+
465+
end function core_glob_match
466+
467+
!> Decide whether `name` is excluded by an ordered list of glob `patterns`
468+
!> (see `core_glob_match`). Patterns are evaluated in order and the FIRST
469+
!> pattern whose glob matches decides: a pattern with a leading `!` keeps
470+
!> the name (not excluded), any other pattern excludes it. A name matching
471+
!> no pattern is not excluded; blank patterns are skipped, so fixed-size
472+
!> namelist arrays can be passed directly. This enables gitignore-style
473+
!> lists such as ['!aero_post*', 'aero_*'], which excludes the `aero_`
474+
!> names except those beginning with `aero_post`.
475+
pure logical function core_glob_list_excluded(name, patterns) result(excluded)
476+
character(len=*), intent(in) :: name
477+
character(len=*), intent(in) :: patterns(:)
478+
479+
integer :: i
480+
481+
excluded = .false.
482+
do i = 1, size(patterns)
483+
if (len_trim(patterns(i)) == 0) cycle
484+
if (patterns(i)(1:1) == '!') then
485+
if (core_glob_match(name, patterns(i)(2:))) then
486+
! Keep-verb: a match means the name stays compared
487+
return
488+
end if
489+
else
490+
if (core_glob_match(name, patterns(i))) then
491+
excluded = .true.
492+
return
493+
end if
494+
end if
495+
end do
496+
497+
end function core_glob_list_excluded
498+
408499
end module string_core_utils

‎src/data/registry.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@
633633
units="kg kg-1" type="real" constituent="true">
634634
<long_name>Water vapor mass mixing ratio with respect to moist air plus all airborne condensates</long_name>
635635
<dimensions>horizontal_dimension vertical_layer_dimension</dimensions>
636-
<ic_file_input_names>Q cnst_Q</ic_file_input_names>
636+
<ic_file_input_names>Q cnst_Q qv</ic_file_input_names>
637637
</variable>
638638
<variable local_name="cldliq"
639639
standard_name="cloud_liquid_water_mixing_ratio_wrt_moist_air_and_condensed_water"

0 commit comments

Comments
 (0)