source: trunk/LMDZ.COMMON/libf/evolution/output.F90 @ 4180

Last change on this file since 4180 was 4180, checked in by jbclement, 6 days ago

PEM:

  • Rework layering-related logic, especially clarify interactions between surface and subsurface water tendencies and disable CO2 lag resistance (inconsistent without updating pressure and mass balance + PCM).
  • Prevent simultaneous activation of layering and ice flows.
  • Add warning when flux_geo /= 0 while soil is disabled.
  • Add new utility function "is_lvl_enabled" for displaying.
  • Replace deprecated 'minieps' with 'eps'/'tol'.

JBC

File size: 19.5 KB
RevLine 
[4065]1MODULE output
[3991]2!-----------------------------------------------------------------------
3! NAME
[4065]4!     output
[3991]5!
6! DESCRIPTION
7!     Tools to write PEM diagnostic outputs.
8!
9! AUTHORS & DATE
[4065]10!     JB Clement, 12/2025
[3991]11!
12! NOTES
[4065]13!
[3991]14!-----------------------------------------------------------------------
[3088]15
[4065]16! DEPENDENCIES
17! ------------
[4180]18use numerics, only: dp, di, k4, eps
[4065]19
[3991]20! DECLARATION
21! -----------
[3088]22implicit none
23
[4065]24! PARAMETERS
25! ----------
[4110]26character(12),                             parameter, private :: diagdef_name = 'diagevo.def'
27integer(di),                               parameter          :: dim_ngrid = -1_di    ! Dimension ID to identify 'ngrid' for "diagevo.nc" logic
28logical(k4),                               protected, private :: is_diagdef = .false. ! Flag to know if "diagevo.def" exists
29logical(k4),                               protected, private :: is_diagevo = .false. ! Flag to know if "diagevo.nc" exists
30character(256), dimension(:), allocatable, protected, private :: outputs_name         ! Names of output variables defined in "diagevo.def"
31integer(di),                               protected, private :: n_outputs            ! Number of output variables defined in "diagevo.def"
32integer(di),                               protected, private :: output_rate          ! Output rate
33integer(di),                               protected, private :: writing_idt = -1_di  ! Current writing number of timestep
34integer(di),                               protected          :: dim_nlon, dim_nlat, dim_nalt, dim_interlayer, dim_nsoil, dim_time ! Dimensions ID to write in "diagevo.nc"
[3989]35
[3088]36contains
[3991]37!+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[3088]38
[4065]39!=======================================================================
40SUBROUTINE ini_output()
41!-----------------------------------------------------------------------
42! NAME
43!     ini_output
44!
45! DESCRIPTION
46!     Initialize the parameters of module 'output'.
47!
48! AUTHORS & DATE
49!     JB Clement, 12/2025
50!
51! NOTES
52!     At first call.
53!-----------------------------------------------------------------------
[3088]54
[4065]55! DECLARATION
56! -----------
57implicit none
58
59! CODE
60! ----
61n_outputs = 0
[4134]62allocate(outputs_name(0))
[4065]63call read_diagdef()
64
65END SUBROUTINE ini_output
66!=======================================================================
67
68!=======================================================================
69SUBROUTINE end_output()
70!-----------------------------------------------------------------------
71! NAME
72!     end_output
[3088]73!
[4065]74! DESCRIPTION
75!     Deallocate output arrays.
[3088]76!
[4065]77! AUTHORS & DATE
78!     JB Clement, 01/2026
[3088]79!
[4065]80! NOTES
[3088]81!
[3991]82!-----------------------------------------------------------------------
[4065]83
84! DECLARATION
85! -----------
86implicit none
87
88! CODE
89! ----
90if (allocated(outputs_name)) deallocate(outputs_name)
91
92END SUBROUTINE end_output
93!=======================================================================
94
95!=======================================================================
96SUBROUTINE set_output_config(output_rate_in)
97!-----------------------------------------------------------------------
[3991]98! NAME
[4065]99!     set_output_config
[3991]100!
101! DESCRIPTION
[4170]102!     Setter for "output" configuration parameters.
[3991]103!
104! AUTHORS & DATE
[4065]105!     JB Clement, 02/2026
[3991]106!
107! NOTES
[4065]108!
[3991]109!-----------------------------------------------------------------------
110
111! DEPENDENCIES
112! ------------
[4110]113use utility,  only: int2str
114use display,  only: print_msg, LVL_NFO
115use stoppage, only: stop_clean
[3088]116
[3991]117! DECLARATION
118! -----------
[3088]119implicit none
120
[3991]121! ARGUMENTS
122! ---------
[4065]123integer(di), intent(in) :: output_rate_in
[3088]124
[3991]125! CODE
126! ----
[4065]127output_rate = output_rate_in
[4110]128call print_msg('output_rate = '//int2str(output_rate),LVL_NFO)
129if (output_rate < 1) call stop_clean(__FILE__,__LINE__,'''output_rate'' must be positive!',1)
[3088]130
[4065]131END SUBROUTINE set_output_config
132!=======================================================================
[3088]133
[4065]134!=======================================================================
135SUBROUTINE append_name(var_name)
136!-----------------------------------------------------------------------
137! NAME
138!     append_name
139!
140! DESCRIPTION
[4110]141!     Append the variable name read in "diagevo.def" to 'outputs_name'.
[4065]142!
143! AUTHORS & DATE
144!     JB Clement, 01/2026
145!
146! NOTES
147!
148!-----------------------------------------------------------------------
[3088]149
[4065]150! DECLARATION
151! -----------
152implicit none
[3088]153
[4065]154! ARGUMENTS
155! ---------
156character(*), intent(in) :: var_name
[3088]157
[4065]158! LOCAL VARIABLES
159! ---------------
160character(256), dimension(:), allocatable :: tmp
[3088]161
[4065]162! CODE
163! ----
164allocate(tmp(n_outputs + 1))
165if (n_outputs > 0) tmp(1:n_outputs) = outputs_name
166tmp(n_outputs + 1) = var_name
[3088]167
[4065]168call move_alloc(tmp,outputs_name)
169n_outputs = n_outputs + 1
[3088]170
[4065]171END SUBROUTINE append_name
172!=======================================================================
[3088]173
[4065]174!=======================================================================
175SUBROUTINE read_diagdef()
176!-----------------------------------------------------------------------
177! NAME
178!     read_diagdef
179!
180! DESCRIPTION
[4110]181!     Read the variables names defined in "diagevo.def" to ouput.
[4065]182!
183! AUTHORS & DATE
184!     JB Clement, 01/2026
185!
186! NOTES
[4110]187!     > If no "diagevo.def", then no output.
188!     > If "diagevo.def" is empty, then all the possible coded outputs.
189!     > If "diagevo.def" holds a list of variable names, then these are
[4065]190!     outputted.
191!-----------------------------------------------------------------------
[3088]192
[4065]193! DEPENDENCIES
194! ------------
195use stoppage, only: stop_clean
196use utility,  only: is_id_1st_char, is_id_char, int2str
[4110]197use display,  only: print_msg, LVL_NFO, LVL_WRN
[3088]198
[4065]199! DECLARATION
200! -----------
201implicit none
[3088]202
[4065]203! LOCAL VARIABLES
204! ---------------
205integer(di)    :: i, iline, ierr, funit
206character(256) :: line
[3088]207
[4065]208! CODE
209! ----
210inquire(file = diagdef_name,exist = is_diagdef)
211if (is_diagdef) then
[4110]212    call print_msg('> Reading "'//diagdef_name//'"',LVL_NFO)
[4065]213    open(newunit = funit,file = diagdef_name,status = 'old',form = 'formatted',action = 'read',iostat = ierr)
214    if (ierr /= 0) call stop_clean(__FILE__,__LINE__,'error opening file "'//diagdef_name//'"!',ierr)
215    iline = 1
216    do
217        ! Read the line
218        read(funit,'(a)',iostat = ierr) line
219        if (ierr /= 0) exit ! Empty file or end of file
220        iline = iline + 1
[3088]221
[4065]222        ! Examine the line
223        line = trim(line)
224        if (len(line) == 0) cycle ! Ignore empty lines
[4110]225        if (.not. is_id_1st_char(line(1:1))) call print_msg('Invalid output name at line '//int2str(iline)//' in '//diagdef_name//'"!',LVL_WRN)
[4065]226        do i = 2,len(line)
[4110]227            if (.not. is_id_char(line(i:i))) call print_msg('Invalid output name at line '//int2str(iline)//' in '//diagdef_name//'"!',LVL_WRN)
[4065]228        end do
[3088]229
[4065]230        ! Fill in the table 'outputs_name'
231        call append_name(line)
232    end do
233    close(funit)
[4110]234    if (n_outputs == 0) call print_msg('There is a "'//diagdef_name//'" which is is empty. So all the possible coded variables are outputted.',LVL_WRN)
[4065]235    ! Create the output file
[4110]236    call create_diagevo()
[4065]237else
[4110]238    call print_msg('There is no "'//diagdef_name//'". So no variable is outputted.',LVL_WRN)
[4065]239end if
[3088]240
[4065]241END SUBROUTINE read_diagdef
242!=======================================================================
[3088]243
[4065]244!=======================================================================
245FUNCTION is_output_selected(var_name) RESULT(output_var)
246!-----------------------------------------------------------------------
247! NAME
248!     is_output_selected
249!
250! DESCRIPTION
[4110]251!     Read the variables names to output defined in "diagevo.def".
[4065]252!
253! AUTHORS & DATE
254!     JB Clement, 01/2026
255!
256! NOTES
[4110]257!     > If no "diagevo.def", then no output.
258!     > If "diagevo.def" is empty, then all the possible coded outputs.
259!     > If "diagevo.def" holds a list of variable names, then these are
[4065]260!     outputted.
261!-----------------------------------------------------------------------
[3088]262
[4065]263! DECLARATION
264! -----------
265implicit none
[3088]266
[4065]267! ARGUMENTS
268! ---------
269character(*), intent(in) :: var_name
270logical(k4)              :: output_var
[3088]271
[4065]272! LOCAL VARIABLES
273! ---------------
274integer(di) :: i
[3088]275
[4065]276! CODE
277! ----
278output_var = .false.
[3088]279
[4065]280! If diagdef exists but is empty, output everything
281if (n_outputs == 0) then
282    output_var = .true.
283    return
284end if
[3088]285
[4065]286do i = 1,n_outputs
287    if (trim(outputs_name(i)) == trim(var_name)) then
288        output_var = .true.
289        return
290    end if
291end do
[3088]292
[4065]293END FUNCTION is_output_selected
294!=======================================================================
[3088]295
[4065]296!=======================================================================
[4110]297SUBROUTINE create_diagevo()
[4065]298!-----------------------------------------------------------------------
299! NAME
[4110]300!     create_diagevo
[4065]301!
302! DESCRIPTION
[4110]303!     Create a NetCDF file "diagevo.nc" containing the diagnostic
[4065]304!     variables.
305!
306! AUTHORS & DATE
307!     JB Clement, 01/2026
308!
309! NOTES
310!
311!-----------------------------------------------------------------------
[3088]312
[4065]313! DEPENDENCIES
314! ------------
315use netcdf,     only: nf90_double, nf90_clobber, nf90_unlimited, nf90_global, &
316                      nf90_create, nf90_enddef, nf90_def_dim, nf90_def_var, nf90_put_att
[4110]317use io_netcdf,  only: check_nc, open_nc, close_nc, put_var_nc, diagevo_name, ncid_diagevo
[4065]318use geometry,   only: dim_init, ngrid, nlon, nlat, nlayer, nsoil, cell_area, longitudes, latitudes, vect2dyngrd
319use stoppage,   only: stop_clean
320use soil,       only: mlayer
321use atmosphere, only: ap, bp
[4110]322use display,    only: print_msg, LVL_NFO
[3088]323
[4065]324! DECLARATION
325! -----------
326implicit none
[3088]327
[4065]328! LOCAL VARIABLES
329! ---------------
330integer(di)                           :: varid ! Variable ID
331integer(di)                           :: nlon_loc
332real(dp), dimension(:,:), allocatable :: var_dyn
[3088]333
[4065]334! CODE
335! ----
336! Check if dimensions are well initialized
337if (.not. dim_init) call stop_clean(__FILE__,__LINE__,'dimensions are not initilized in the PEM!',1)
338if (ngrid == 1) then ! 1D
339    nlon_loc = 1
340else ! 3D
341    nlon_loc = nlon + 1
342end if
[3088]343
[4065]344! Create file
[4110]345call print_msg('> Creating "'//diagevo_name//'"',LVL_NFO)
346call check_nc(nf90_create(diagevo_name,nf90_clobber,ncid_diagevo),'creating '//diagevo_name) ! Enter define mode
[3088]347
[4065]348! Define dimensions
[4110]349call check_nc(nf90_def_dim(ncid_diagevo,'Time',NF90_UNLIMITED,dim_time),'defining dimension Time')
350call check_nc(nf90_def_dim(ncid_diagevo,'longitude',nlon_loc,dim_nlon),'defining dimension longitude')
351call check_nc(nf90_def_dim(ncid_diagevo,'latitude',nlat,dim_nlat),'defining dimension latitude')
352call check_nc(nf90_def_dim(ncid_diagevo,'altitude',nlayer,dim_nalt),'defining dimension altitude')
353call check_nc(nf90_def_dim(ncid_diagevo,'interlayer',nlayer + 1,dim_interlayer),'defining dimension interlayer')
354call check_nc(nf90_def_dim(ncid_diagevo,'subsurface_layers',nsoil,dim_nsoil),'defining dimension subsurface_layers')
[3088]355
[4065]356! Define variables
[4110]357call check_nc(nf90_def_var(ncid_diagevo,'Time',nf90_double,(/dim_time/),varid),'defining variable Time')
358call check_nc(nf90_put_att(ncid_diagevo,varid,'title','Year of run'),'putting title attribute for Time')
359call check_nc(nf90_put_att(ncid_diagevo,varid,'units','Planetary year'),'putting units attribute for Time')
[3088]360
[4110]361call check_nc(nf90_def_var(ncid_diagevo,'longitude',nf90_double,(/dim_nlon/),varid),'defining variable longitude')
362call check_nc(nf90_put_att(ncid_diagevo,varid,'title','Longitudes of the grid'),'putting title attribute for longitude')
[3088]363
[4110]364call check_nc(nf90_def_var(ncid_diagevo,'latitude',nf90_double,(/dim_nlat/),varid),'defining variable latitude')
365call check_nc(nf90_put_att(ncid_diagevo,varid,'title','Latitudes of the grid'),'putting title attribute for latitude')
[3088]366
[4110]367call check_nc(nf90_def_var(ncid_diagevo,'ap',nf90_double,(/dim_interlayer/),varid),'defining variable ap')
368call check_nc(nf90_put_att(ncid_diagevo,varid,'title','Hybrid pressure at interlayers'),'putting title attribute for ap')
[3088]369
[4110]370call check_nc(nf90_def_var(ncid_diagevo,'bp',nf90_double,(/dim_interlayer/),varid),'defining variable bp')
371call check_nc(nf90_put_att(ncid_diagevo,varid,'title','Hybrid sigma at interlayers'),'putting title attribute for bp')
[3088]372
[4110]373call check_nc(nf90_def_var(ncid_diagevo,'soildepth',nf90_double,(/dim_nsoil/),varid),'defining variable soildepth')
374call check_nc(nf90_put_att(ncid_diagevo,varid,'title','Depths of soil layers'),'putting title attribute for soildepth')
[3088]375
[4110]376call check_nc(nf90_def_var(ncid_diagevo,'cell_area',nf90_double,(/dim_nlon,dim_nlat/),varid),'defining variable cell_area')
377call check_nc(nf90_put_att(ncid_diagevo,varid,'title','Cell area'),'putting title attribute for cell_area')
[3088]378
[4065]379! Global attributes
[4110]380call check_nc(nf90_put_att(ncid_diagevo,nf90_global,'title','PEM diagnostic file'),'putting global attribute')
[3088]381
[4065]382! Leave define mode and putting variables defining dimensions
[4110]383call check_nc(nf90_enddef(ncid_diagevo),'leaving define mode')
[4065]384allocate(var_dyn(nlon_loc,nlat))
[4147]385call vect2dyngrd(longitudes,var_dyn)
[4152]386call put_var_nc('longitude',var_dyn(:,1))
[4147]387call vect2dyngrd(latitudes,var_dyn)
[4152]388call put_var_nc('latitude',var_dyn(1,:))
[4065]389call put_var_nc('ap',ap)
390call put_var_nc('bp',bp)
391call put_var_nc('soildepth',mlayer)
[4152]392call vect2dyngrd(cell_area,var_dyn,extensive = .true.)
[4065]393call put_var_nc('cell_area',var_dyn)
394deallocate(var_dyn)
[4110]395call close_nc(diagevo_name)
[3088]396
[4065]397! File creation done
[4110]398is_diagevo = .true.
[3088]399
[4110]400END SUBROUTINE create_diagevo
[4065]401!=======================================================================
[3088]402
[4065]403!=======================================================================
[4152]404SUBROUTINE write_diagevo(var_name,title,units,var,dimids,extensive)
[3991]405!-----------------------------------------------------------------------
406! NAME
[4110]407!     write_diagevo
[3991]408!
409! DESCRIPTION
[4110]410!     Write selected diagnostic variables to NetCDF file "diagevo.nc".
[3991]411!
412! AUTHORS & DATE
[4065]413!     JB Clement, 01/2026
[3991]414!
415! NOTES
[4065]416!     > Output rate controlled by 'output_rate' from "run.def" (PEM).
[4110]417!     > Variable selection controlled by the file "diagevo.def".
418!         - If no "diagevo.def", then no output.
419!         - If "diagevo.def" is empty, then all the possible coded
[4065]420!           outputs are done.
[4110]421!         - If "diagevo.def" holds a list of variable names, then these
[4065]422!           are outputted.
[4152]423!     > If 'var' is an array, then the argument 'dimids' is mandatory.
424!       'dimids' contains the dimension IDs of the variable in order.
[4065]425!       If the variable holds a dimension 'ngrid', then it must appear
426!       in the first position.
[4152]427!     > For extensive variables, the values at the poles must be
428!       averaged over the number of longitudes to be computed/outputted
429!       on the dynamical grid.
[3991]430!-----------------------------------------------------------------------
[3214]431
[3991]432! DEPENDENCIES
433! ------------
[4110]434use io_netcdf, only: open_nc, close_nc, def_var_nc, put_var_nc, diagevo_name
[4065]435use evolution, only: idt, n_yr_run
436use stoppage,  only: stop_clean
437use geometry,  only: vect2dyngrd, ngrid, nlon, nlat
[3171]438
[3991]439! DECLARATION
440! -----------
[3171]441implicit none
442
[3991]443! ARGUMENTS
444! ---------
[4065]445character(*),               intent(in)           :: var_name, title, units
446real(dp),    dimension(..), intent(in)           :: var ! Assumed-rank array
[4152]447integer(di), dimension(:),  intent(in), optional :: dimids
448logical(k4),                intent(in), optional :: extensive
[3171]449
[3991]450! LOCAL VARIABLES
451! ---------------
[4152]452integer(di), dimension(:),       allocatable :: dimids_nc
[4065]453integer(di)                                  :: idim_ngrid, nlon_loc, ndim, i, j
454real(dp),    dimension(:,:),     allocatable :: var_dyn
455real(dp),    dimension(:,:,:),   allocatable :: var1d_dyn
456real(dp),    dimension(:,:,:,:), allocatable :: var2d_dyn
[4110]457integer(di)                                  :: itime ! Current time index to record variables
[4152]458logical(k4)                                  :: extensive_l
[3171]459
[4110]460
[3991]461! CODE
462! ----
[4065]463! If no diagdef file, no output at all
464if (.not. is_diagdef) return
[3171]465
[4065]466! If variable not selected, return
467! Empty diagdef file means all the variables are ouputted
468if (.not. is_output_selected(var_name)) return
[3171]469
[4065]470! If output timing not met, return
[4180]471if (abs(mod(idt,output_rate)) > eps) return
[3171]472
[4065]473! If it is the first writing of the current timestep
474if (writing_idt /= idt) then
475    writing_idt = idt
[4110]476    call open_nc(diagevo_name,'write',itime) ! Get the current time index
[4065]477    call put_var_nc('Time',n_yr_run,itime)
478end if
[3532]479
[4065]480! Write the variable
[4152]481extensive_l = .false.
482if (present(extensive)) extensive_l = extensive
[4110]483call open_nc(diagevo_name,'write') ! Open diagevo_name only it is not done yet
[4065]484select rank (var) ! Assumed-rank array removes compile-time rank information which is required by the following subroutines using generic interface overload
485    rank(0) ! Scalar
486        call def_var_nc(var_name,title,units,(/dim_time/)) ! Define the variable only it is not done yet
487        call put_var_nc(var_name,var,itime)
488    rank default ! Arrays
[4152]489        if (.not. present(dimids)) call stop_clean(__FILE__,__LINE__,'The argument ''dimids'' must be present to output arrays!',1)
490        ndim = size(dimids)
491        if (any(dimids == dim_ngrid)) then
492            idim_ngrid = findloc(dimids,dim_ngrid,dim = 1)
[4065]493            if (idim_ngrid /= 1) call stop_clean(__FILE__,__LINE__,'The dimension ''ngrid'' must be the first one to output the array!',1)
[4152]494            allocate(dimids_nc(ndim + 2))
495            dimids_nc(1) = dim_nlon
496            dimids_nc(2) = dim_nlat
497            dimids_nc(3:ndim + 1) = dimids(2:ndim)
498            dimids_nc(ndim + 2) = dim_time
[4065]499            if (ngrid == 1) then ! 1D
500                nlon_loc = 1
501            else ! 3D
502                nlon_loc = nlon + 1
503            end if
[4152]504            call def_var_nc(var_name,title,units,dimids_nc) ! Define the variable only it is not done yet
[4065]505            select rank (var) ! Assumed-rank array removes compile-time rank information which is required by the following subroutines using generic interface overload
506                rank(1)
507                    allocate(var_dyn(nlon_loc,nlat))
[4152]508                    call vect2dyngrd(var,var_dyn,extensive_l)
[4065]509                    call put_var_nc(var_name,var_dyn,itime)
510                    deallocate(var_dyn)
511                rank(2)
512                    allocate(var1d_dyn(nlon_loc,nlat,size(var,2)))
513                    do i = 1,size(var,2)
[4152]514                        call vect2dyngrd(var(:,i),var1d_dyn(:,:,i),extensive_l)
[4065]515                    end do
516                    call put_var_nc(var_name,var1d_dyn,itime)
517                    deallocate(var1d_dyn)
518                rank(3)
519                    allocate(var2d_dyn(nlon_loc,nlat,size(var,2),size(var,3)))
520                    do i = 1,size(var,2)
521                        do j = 1,size(var,3)
[4152]522                            call vect2dyngrd(var(:,i,j),var2d_dyn(:,:,i,j),extensive_l)
[4065]523                        end do
524                    end do
525                    call put_var_nc(var_name,var2d_dyn,itime)
526                    deallocate(var2d_dyn)
527                rank default
528                    call stop_clean(__FILE__,__LINE__,'unsupported rank for the input array!',1)
529            end select
530        else ! No 'ngrid' dimension
[4152]531            allocate(dimids_nc(ndim + 1))
532            dimids_nc(1:ndim) = dimids(:)
533            dimids_nc(ndim + 1) = dim_time
534            call def_var_nc(var_name,title,units,dimids_nc) ! Define the variable only it is not done yet
[4065]535            select rank (var) ! Assumed-rank array removes compile-time rank information which is required by the following subroutines using generic interface overload
536                rank(1)
537                    call put_var_nc(var_name,var_dyn,itime)
538                rank(2)
539                    call put_var_nc(var_name,var1d_dyn,itime)
540                rank(3)
541                    call put_var_nc(var_name,var2d_dyn,itime)
542                rank(4)
543                    call put_var_nc(var_name,var2d_dyn,itime)
544                rank default
545                    call stop_clean(__FILE__,__LINE__,'unsupported rank for the input array!',1)
546            end select
547        end if
[4152]548        deallocate(dimids_nc)
[4065]549end select
[4110]550call close_nc(diagevo_name)
[3532]551
[4110]552END SUBROUTINE write_diagevo
[3991]553!=================================================================
[3171]554
[4065]555END MODULE output
Note: See TracBrowser for help on using the repository browser.