source: trunk/LMDZ.COMMON/libf/evolution/utility.F90 @ 4074

Last change on this file since 4074 was 4065, checked in by jbclement, 2 weeks ago

PEM:
Major refactor following the previous ones (r3989 and r3991) completing the large structural reorganization and cleanup of the PEM codebase. This revision introduces newly designed modules, standardizes interfaces with explicit ini/end APIs and adds native NetCDF I/O together with explicit PCM/PEM adapters. In detail:

  • Some PEM models were corrected or improved:
    • Frost/perennial ice semantics are clarified via renaming;
    • Soil temperature remapping clarified, notably by removing the rescaling of temperature deviation;
    • Geothermal flux for the PCM is computed based on the PEM state;
  • New explicit PEM/PCM adapters ("set_*"/"build4PCM_*") to decouple PEM internal representation from PCM file layouts and reconstruct consistent fields returned to the PCM;
  • New explicit build/teardown routines that centralize allocation and initialization ordering, reducing accidental use of uninitialized data and making the model lifecycle explicit;
  • Add native read/write helpers for NetCDF that centralize all low-level NetCDF interactions with major improvements (and more simplicity) compared to legacy PEM/PCM I/O (see the modules "io_netcdf" and "output"). They support reading, creation and writing of "diagevol.nc" (renamed from "diagpem.nc") and start/restart files;
  • Provide well-focused modules ("numerics"/"maths"/"utility"/"display") to host commonly-used primitives:
    • "numerics" defines numerical types and constants for reproducibility, portability across compilers and future transitions (e.g. quadruple precision experiments);
    • "display" provides a single controlled interface for runtime messages, status output and diagnostics, avoiding direct 'print'/'write' to enable silent mode, log redirection, and MPI-safe output in the future.
    • "utility" (new module) hosts generic helpers used throughout the code (e.g. "int2str" or "real2str");
  • Add modules "clim_state_init"/"clim_state_rec" which provide robust read/write logic for "start/startfi/startpem", including 1D fallbacks, mesh conversions and dimension checks. NetCDF file creation is centralized and explicit. Restart files are now self-consistent and future-proof, requiring changes only to affected variables;
  • Add module "atmosphere" which computes pressure fields, reconstructs potential temperature and air mass. It also holds the whole logic to define sigma or hybrid coordinates for altitudes;
  • Add module "geometry" to centrilize dimensions logic and grid conversions routines (including 2 new ones "dyngrd2vect"/"vect2dyngrd");
  • Add module "slopes" to isolate slopes handling;
  • Add module "surface" to isolate surface management. Notably, albedo and emissivity are now fully reconstructed following the PCM settings;
  • Add module "allocation" to check the dimension initialization and centrilize allocation/deallocation;
  • Finalize module decomposition and renaming to consolidate domain-based modules, purpose-based routines and physics/process-based variables;
  • The main program now drives a clearer sequence of conceptual steps (initialization / reading / evolution / update / build / writing) and fails explicitly instead of silently defaulting;
  • Ice table logic is made restart-safe;
  • 'Open'/'read' intrinsic logic is made safe and automatic;
  • Improve discoverability and standardize the data handling (private vs protected vs public);
  • Apply consistent documentation/header style already introduced;
  • Update deftank scripts to reflect new names and launch wrappers;

This revision is a structural milestone aiming to be behavior-preserving where possible. It has been tested via compilation and short integration runs. However, due to extensive renames, moves, and API changes, full validation is still ongoing.
Note: the revision includes one (possibly two) easter egg hidden in the code for future archaeologists of the PEM. No physics were harmed.
JBC

File size: 9.5 KB
Line 
1MODULE utility
2!-----------------------------------------------------------------------
3! NAME
4!     utility
5!
6! DESCRIPTION
7!     Contains some utility functions.
8!
9! AUTHORS & DATE
10!     JB Clement, 2023-2025
11!
12! NOTES
13!
14!-----------------------------------------------------------------------
15
16! DEPENDENCIES
17! ------------
18use numerics, only: dp, qp, di, li, k4, minieps
19
20! DECLARATION
21! -----------
22implicit none
23
24! INTERFACES
25! ----------
26interface int2str
27    module procedure int2str_di, int2str_li
28end interface int2str
29
30interface real2str
31    module procedure real2str_dp, real2str_qp
32end interface real2str
33
34contains
35!+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
36
37!=======================================================================
38FUNCTION int2str_di(i) RESULT(str)
39!-----------------------------------------------------------------------
40! NAME
41!     int2str_di
42!
43! DESCRIPTION
44!     Convert a default integer into a string.
45!
46! AUTHORS & DATE
47!     JB Clement, 2023-2025
48!
49! NOTES
50!
51!-----------------------------------------------------------------------
52
53! DEPENDENCIES
54! ------------
55use stoppage, only: stop_clean
56
57! DECLARATION
58! -----------
59implicit none
60
61! ARGUMENTS
62! ---------
63integer(di), intent(in) :: i
64
65! LOCAL VARIABLES
66! ---------------
67character(20)             :: str_tmp
68character(:), allocatable :: str
69
70! CODE
71! ----
72if (nb_digits(real(i,dp)) > len(str_tmp)) call stop_clean(__FILE__,__LINE__,'invalid integer for conversion!',1)
73write(str_tmp,'(i0)') i
74str = trim(adjustl(str_tmp))
75
76END FUNCTION int2str_di
77!=======================================================================
78
79!=======================================================================
80FUNCTION int2str_li(i) RESULT(str)
81!-----------------------------------------------------------------------
82! NAME
83!     int2str_li
84!
85! DESCRIPTION
86!     Convert a long integer into a string.
87!
88! AUTHORS & DATE
89!     JB Clement, 2023-2025
90!
91! NOTES
92!
93!-----------------------------------------------------------------------
94
95! DEPENDENCIES
96! ------------
97use stoppage, only: stop_clean
98
99! DECLARATION
100! -----------
101implicit none
102
103! ARGUMENTS
104! ---------
105integer(li), intent(in) :: i
106
107! LOCAL VARIABLES
108! ---------------
109character(20)             :: str_tmp
110character(:), allocatable :: str
111
112! CODE
113! ----
114if (nb_digits(real(i,dp)) > len(str_tmp)) call stop_clean(__FILE__,__LINE__,'invalid integer for conversion!',1)
115write(str_tmp,'(i0)') i
116str = trim(adjustl(str_tmp))
117
118END FUNCTION int2str_li
119!=======================================================================
120
121!=======================================================================
122FUNCTION real2str_dp(x,frmt) RESULT(str)
123!-----------------------------------------------------------------------
124! NAME
125!     real2str_dp
126!
127! DESCRIPTION
128!     Convert a double precision real into a string.
129!
130! AUTHORS & DATE
131!     JB Clement, 02/2026
132!
133! NOTES
134!
135!-----------------------------------------------------------------------
136
137! DEPENDENCIES
138! ------------
139use stoppage, only: stop_clean
140
141! DECLARATION
142! -----------
143implicit none
144
145! ARGUMENTS
146! ---------
147real(dp),     intent(in)           :: x
148character(*), intent(in), optional :: frmt
149
150! LOCAL VARIABLES
151! ---------------
152!~ integer(di)               :: len_trimmed
153character(32)             :: str_tmp
154character(:), allocatable :: str
155
156! CODE
157! ----
158if (present(frmt)) then
159    write(str_tmp,frmt) x
160else
161    write(str_tmp,'(G0)') x
162end if
163
164!~ ! Remove trailing zeros after decimal
165!~ len_trimmed = len_trim(tmp)
166!~ do while (len_trimmed > 0)
167!~     if (str_tmp(len_trimmed:len_trimmed) == '0' .or. str_tmp(len_trimmed:len_trimmed) == ' ') then
168!~     len_trimmed = len_trimmed - 1
169!~     else
170!~         exit
171!~     end if
172!~ end do
173!~ str = str_tmp(1:len_trimmed)
174str = trim(adjustl(str_tmp))
175
176END FUNCTION real2str_dp
177!=======================================================================
178
179!=======================================================================
180FUNCTION real2str_qp(x,frmt) RESULT(str)
181!-----------------------------------------------------------------------
182! NAME
183!     real2str_qp
184!
185! DESCRIPTION
186!     Convert a quadruple precision real into a string.
187!
188! AUTHORS & DATE
189!     JB Clement, 02/2026
190!
191! NOTES
192!
193!-----------------------------------------------------------------------
194
195! DEPENDENCIES
196! ------------
197use stoppage, only: stop_clean
198
199! DECLARATION
200! -----------
201implicit none
202
203! ARGUMENTS
204! ---------
205real(qp),     intent(in)           :: x
206character(*), intent(in), optional :: frmt
207
208! LOCAL VARIABLES
209! ---------------
210!~ integer(di)               :: len_trimmed
211character(32)             :: str_tmp
212character(:), allocatable :: str
213
214! CODE
215! ----
216if (present(frmt)) then
217    write(str_tmp,frmt) x
218else
219    write(str_tmp,'(G0)') x
220end if
221
222!~ ! Remove trailing zeros after decimal
223!~ len_trimmed = len_trim(tmp)
224!~ do while (len_trimmed > 0)
225!~     if (str_tmp(len_trimmed:len_trimmed) == '0' .or. str_tmp(len_trimmed:len_trimmed) == ' ') then
226!~     len_trimmed = len_trimmed - 1
227!~     else
228!~         exit
229!~     end if
230!~ end do
231!~ str = str_tmp(1:len_trimmed)
232str = trim(adjustl(str_tmp))
233
234END FUNCTION real2str_qp
235!=======================================================================
236
237!=======================================================================
238FUNCTION bool2str(l) RESULT(str)
239!-----------------------------------------------------------------------
240! NAME
241!     bool2str
242!
243! DESCRIPTION
244!     Convert a logical into a string.
245!
246! AUTHORS & DATE
247!     JB Clement, 02/2026
248!
249! NOTES
250!
251!-----------------------------------------------------------------------
252
253! DECLARATION
254! -----------
255implicit none
256
257! ARGUMENTS
258! ---------
259logical(k4), intent(in) :: l
260
261! LOCAL VARIABLES
262! ---------------
263character(5) :: str
264
265! CODE
266! ----
267str = merge('true ','false',l)
268
269END FUNCTION bool2str
270!=======================================================================
271
272!=======================================================================
273FUNCTION nb_digits(x) RESULT(idigits)
274!-----------------------------------------------------------------------
275! NAME
276!     nb_digits
277!
278! DESCRIPTION
279!     Give the number of digits for the integer part of a real number.
280!
281! AUTHORS & DATE
282!     JB Clement, 2023-2025
283!
284! NOTES
285!
286!-----------------------------------------------------------------------
287
288! DECLARATION
289! -----------
290implicit none
291
292! ARGUMENTS
293! ---------
294real(dp), intent(in) :: x
295
296! LOCAL VARIABLES
297! ---------------
298integer(di) :: idigits
299
300! CODE
301! ----
302idigits = 1
303! If x /= 0 then:
304if (abs(x) >= minieps) idigits = int(log10(abs(x))) + 1
305
306END FUNCTION nb_digits
307!=======================================================================
308
309!=======================================================================
310FUNCTION format_duration(secs) RESULT(str)
311!-----------------------------------------------------------------------
312! NAME
313!     format_duration
314!
315! DESCRIPTION
316!     Converts a duration in seconds into a compact Xd HH:MM:SS format.
317!
318! AUTHORS & DATE
319!     JB Clement, 01/2026
320!
321! NOTES
322!
323!-----------------------------------------------------------------------
324
325! DECLARATION
326! -----------
327implicit none
328
329! ARGUMENTS
330! ---------
331real(dp), intent(in) :: secs
332
333! LOCAL VARIABLES
334! ---------------
335integer(di)               :: days, hours, minutes, seconds
336character(:), allocatable :: str
337character(32)             :: tmp ! Work buffer
338
339! CODE
340! ----
341days = int(secs/86400._dp,di)
342hours = int(mod(secs,86400._dp)/3600._dp,di)
343minutes = int(mod(secs,3600._dp)/60._dp,di)
344seconds = int(mod(secs,60._dp),di)
345
346if (days > 0_li) then
347   write(tmp,'(i0,"d ",i2.2,":",i2.2,":",i2.2)') days, hours, minutes, seconds
348else
349   write(tmp,'(i2.2,":",i2.2,":",i2.2)') hours, minutes, seconds
350end if
351
352str = trim(adjustl(tmp))
353
354END FUNCTION  format_duration
355!=======================================================================
356
357!=======================================================================
358FUNCTION is_id_1st_char(c) RESULT(valid_c)
359!-----------------------------------------------------------------------
360! NAME
361!     is_id_1st_char
362!
363! DESCRIPTION
364!     Detects valid first character for Fortran identifer.
365!
366! AUTHORS & DATE
367!     JB Clement, 01/2026
368!
369! NOTES
370!     A variable name is a Fortran identifier if it starts with a letter
371!     or _ and followed by letters, digits or _.
372!-----------------------------------------------------------------------
373
374! DECLARATION
375! -----------
376implicit none
377
378! ARGUMENTS
379! ---------
380character(1), intent(in) :: c
381
382! LOCAL VARIABLES
383! ---------------
384logical(k4) :: valid_c
385
386! CODE
387! ----
388valid_c = ('A' <= c .and. c <= 'Z') .or. ('a' <= c .and. c <= 'z') .or. (c == '_')
389
390END FUNCTION  is_id_1st_char
391!=======================================================================
392
393!=======================================================================
394FUNCTION is_id_char(c) RESULT(valid_c)
395!-----------------------------------------------------------------------
396! NAME
397!     is_id_char
398!
399! DESCRIPTION
400!     Detects valid character for Fortran identifer.
401!
402! AUTHORS & DATE
403!     JB Clement, 01/2026
404!
405! NOTES
406!     A variable name is a Fortran identifier if it starts with a letter
407!     or _ and followed by letters, digits or _.
408!-----------------------------------------------------------------------
409
410! DECLARATION
411! -----------
412implicit none
413
414! ARGUMENTS
415! ---------
416character(1), intent(in) :: c
417
418! LOCAL VARIABLES
419! ---------------
420logical(k4) :: valid_c
421
422! CODE
423! ----
424valid_c = is_id_1st_char(c) .or. ('0' <= c .and. c <= '9')
425
426END FUNCTION  is_id_char
427!=======================================================================
428
429END MODULE utility
Note: See TracBrowser for help on using the repository browser.