source: trunk/LMDZ.COMMON/libf/evolution/io_netcdf.F90 @ 4175

Last change on this file since 4175 was 4152, checked in by jbclement, 3 weeks ago

PEM:

  • Fix outputs "diagevo.nc" for 3D data.
  • Fix sign in computing exchanges due to adsorption/ice table and in balancing H2O flux from/into atmosphere.
  • Few cleanings.

JBC

File size: 41.8 KB
Line 
1MODULE io_netcdf
2!-----------------------------------------------------------------------
3! NAME
4!     io_netcdf
5!
6! DESCRIPTION
7!     Provides input/output procedures and parameters for the PEM.
8!
9! AUTHORS & DATE
10!     JB Clement, 12/2025
11!
12! NOTES
13!
14!-----------------------------------------------------------------------
15
16! DEPEDENCIES
17! -----------
18use numerics, only: dp, di, k4, minieps
19use netcdf,   only: nf90_double, nf90_noerr, nf90_strerror, nf90_write, nf90_nowrite,                &
20                    nf90_open, nf90_close, nf90_redef, nf90_enddef, nf90_inquire, nf90_max_var_dims, &
21                    nf90_inq_dimid, nf90_inquire_dimension, nf90_inq_varid, nf90_inquire_variable,   &
22                    nf90_def_var, nf90_get_var, nf90_put_var, nf90_get_att, nf90_put_att
23use stoppage, only: stop_clean
24
25! DECLARATION
26! -----------
27implicit none
28
29! PARAMETERS
30! ----------
31character(8),  parameter :: start_name     = 'start.nc'
32character(11), parameter :: start1D_name   = 'start1D.txt'
33character(10), parameter :: startfi_name   = 'startfi.nc'
34character(11), parameter :: startevo_name  = 'startevo.nc'
35character(19), parameter :: xios_day_name1 = 'xoutdaily4pem_y1.nc'  ! XIOS daily output file, second to last PCM year
36character(19), parameter :: xios_day_name2 = 'xoutdaily4pem_y2.nc'  ! XIOS daily output file, last PCM year
37character(20), parameter :: xios_yr_name1  = 'xoutyearly4pem_y1.nc' ! XIOS yearly output file, second to last PCM year
38character(20), parameter :: xios_yr_name2  = 'xoutyearly4pem_y2.nc' ! XIOS yearly output file, last PCM year
39character(10), parameter :: diagevo_name   = 'diagevo.nc'
40
41! VARIABLES
42! ---------
43logical(k4), protected, private :: open_ncfile = .false.  ! Flag to true if a NetCDF file is already open
44logical(k4), protected, private :: open_diagevo = .false. ! Flag to true if "diagevo.nc" is already open
45integer(di), protected, private :: ncid_file              ! File ID
46integer(di), protected, private :: varid                  ! Variable ID
47integer(di)                     :: ncid_diagevo           ! File ID specific to "diagevo.nc"
48
49! INTERFACES
50! ----------
51interface get_var_nc
52    module procedure get_var0d_nc, get_var1d_nc, get_var2d_nc, get_var3d_nc, get_var4d_nc
53end interface get_var_nc
54
55interface put_var_nc
56    module procedure put_var0d_nc, put_var1d_nc, put_var2d_nc, put_var3d_nc, put_var4d_nc
57end interface put_var_nc
58
59interface check_valid_var_nc
60    module procedure check_valid_var0d_nc, check_valid_var1d_nc, check_valid_var2d_nc, check_valid_var3d_nc, check_valid_var4d_nc
61end interface check_valid_var_nc
62
63contains
64!+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
65
66!=======================================================================
67SUBROUTINE check_nc(ierr,msg,found)
68!-----------------------------------------------------------------------
69! NAME
70!     check_nc
71!
72! DESCRIPTION
73!     NetCDF error handler.
74!
75! AUTHORS & DATE
76!     JB Clement, 12/2025
77!
78! NOTES
79!
80!-----------------------------------------------------------------------
81
82! DEPENDENCIES
83! ------------
84use display, only: print_msg, LVL_ERR
85
86! ARGUMENTS
87! ---------
88integer(di),  intent(in)            :: ierr
89character(*), intent(in)            :: msg
90logical(k4),  intent(out), optional :: found
91
92! LOCAL VARIABLES
93! ---------------
94logical(k4) :: tmp_found
95
96! CODE
97! ----
98if (ierr == nf90_noerr) then
99    tmp_found = .true.
100else
101    tmp_found = .false.
102end if
103
104if (present(found)) then
105    found = tmp_found
106else
107    if (.not. tmp_found) then
108        call print_msg(trim(nf90_strerror(ierr)),LVL_ERR)
109        call stop_clean(__FILE__,__LINE__,'NetCDF error when '//trim(msg),ierr)
110    end if
111end if
112
113END SUBROUTINE check_nc
114!=======================================================================
115
116!=======================================================================
117SUBROUTINE open_nc(filename,mode,itime)
118!-----------------------------------------------------------------------
119! NAME
120!     open_nc
121!
122! DESCRIPTION
123!     Open a netCDF file for reading.
124
125! AUTHORS & DATE
126!     JB Clement, 12/2025
127
128! NOTES
129!
130!-----------------------------------------------------------------------
131
132! DECLARATION
133! -----------
134implicit none
135
136! ARGUMENTS
137! ---------
138character(*), intent(in)            :: filename, mode
139integer(di),  intent(out), optional :: itime
140
141! CODE
142! ----
143! Diagevol logic
144if (adjustl(trim(filename)) == diagevo_name) then
145    if (adjustl(trim(mode)) == 'read') then
146        call stop_clean(__FILE__,__LINE__,'opening mode "read" cannot be used with '//trim(filename)//'"!',1)
147    else if (adjustl(trim(mode)) == 'write') then
148        if (.not. open_diagevo) then
149            call check_nc(nf90_open(trim(filename),nf90_write,ncid_diagevo),'opening file '//trim(filename)//' to write')
150            if (present(itime)) call get_next_itime_nc('Time',itime) ! Next time index
151            open_diagevo = .true.
152        end if
153        return
154    else
155        call stop_clean(__FILE__,__LINE__,'opening mode "'//mode//'" unknown!',1)
156    end if
157end if
158
159! Standard logic
160if (open_ncfile) then ! A file is already opened
161    call stop_clean(__FILE__,__LINE__,'a NetCDF file is already opened!',1)
162else
163    if (adjustl(trim(mode)) == 'read') then
164        call check_nc(nf90_open(trim(filename),nf90_nowrite,ncid_file),'opening file '//trim(filename)//' to read')
165    else if (adjustl(trim(mode)) == 'write') then
166        call check_nc(nf90_open(trim(filename),nf90_write,ncid_file),'opening file '//trim(filename)//' to write')
167        if (present(itime)) call get_next_itime_nc('Time',itime) ! Next time index
168    else
169        call stop_clean(__FILE__,__LINE__,'opening mode "'//mode//'" unknown!',1)
170    end if
171    open_ncfile = .true.
172end if
173
174END SUBROUTINE open_nc
175!=======================================================================
176
177!=======================================================================
178SUBROUTINE close_nc(filename)
179!-----------------------------------------------------------------------
180! NAME
181!     close_nc
182!
183! DESCRIPTION
184!     Open a netCDF file.
185
186! AUTHORS & DATE
187!     JB Clement, 12/2025
188
189! NOTES
190!
191!-----------------------------------------------------------------------
192
193! DECLARATION
194! -----------
195implicit none
196
197! ARGUMENTS
198! ---------
199character(*), intent(in) :: filename
200
201! CODE
202! ----
203if (adjustl(trim(filename)) == diagevo_name) then ! Diagevol logic
204    call check_nc(nf90_close(ncid_diagevo),'closing file '//trim(filename))
205    open_diagevo = .false.
206else ! Standard logic
207    call check_nc(nf90_close(ncid_file),'closing file '//trim(filename))
208    open_ncfile = .false.
209end if
210
211END SUBROUTINE close_nc
212!=======================================================================
213
214!=======================================================================
215SUBROUTINE get_dim_nc(dim_name,d,found)
216!-----------------------------------------------------------------------
217! NAME
218!     get_dim_nc
219!
220! DESCRIPTION
221!     Read a 0D variable from open NetCDF file.
222!
223! AUTHORS & DATE
224!     JB Clement, 12/2025
225!
226! NOTES
227!
228!-----------------------------------------------------------------------
229
230! DECLARATION
231! -----------
232implicit none
233
234! ARGUMENTS
235! ---------
236character(*), intent(in)            :: dim_name ! Variable name
237integer(di),  intent(out)           :: d        ! Output dimension
238logical(k4),  intent(out), optional :: found
239
240! LOCAL VARIABLES
241! ---------------
242integer(di) :: dimid ! Dimension ID
243
244! CODE
245! ----
246if (present(found)) then
247    call check_nc(nf90_inq_dimid(ncid_file,dim_name,dimid),'inquiring '//dim_name,found)
248    call check_nc(nf90_inquire_dimension(ncid_file,dimid,len = d),'getting '//dim_name,found)
249else
250    call check_nc(nf90_inq_dimid(ncid_file,dim_name,dimid),'inquiring '//dim_name)
251    call check_nc(nf90_inquire_dimension(ncid_file,dimid,len = d),'getting '//dim_name)
252end if
253
254END SUBROUTINE get_dim_nc
255!=======================================================================
256
257!=======================================================================
258SUBROUTINE def_var_nc(var_name,title,units,dimids)
259!-----------------------------------------------------------------------
260! NAME
261!     def_var_nc
262!
263! DESCRIPTION
264!     Define a variable into open NetCDF file.
265!
266! AUTHORS & DATE
267!     JB Clement, 01/2026
268!
269! NOTES
270!
271!-----------------------------------------------------------------------
272
273! DECLARATION
274! -----------
275implicit none
276
277! ARGUMENTS
278! ---------
279character(*),              intent(in) :: var_name, title, units ! Variable|title|units name
280integer(di), dimension(:), intent(in) :: dimids                 ! Dimensions IDs
281
282! LOCAL VARIABLES
283! ---------------
284logical(k4) :: found
285integer(di) :: ncid
286
287! CODE
288! ----
289! Diagevol logic priority over standard logic
290if (open_diagevo) then
291    ncid = ncid_diagevo
292else
293    ncid = ncid_file
294end if
295
296! Check if the variable exists
297call check_nc(nf90_inq_varid(ncid,var_name,varid),'inquiring '//var_name,found)
298if (found) return ! Variable is already defined
299
300! Enter define mode
301call check_nc(nf90_redef(ncid),'entering define mode')
302
303! Define variable
304call check_nc(nf90_def_var(ncid,var_name,NF90_DOUBLE,dimids,varid),'defining variable '//var_name)
305call check_nc(nf90_put_att(ncid,varid,'title',title),'putting title attribute for '//var_name)
306call check_nc(nf90_put_att(ncid,varid,'units',units),'putting units attribute for '//var_name)
307
308! Leave define mode
309call check_nc(nf90_enddef(ncid),'leaving define mode')
310
311END SUBROUTINE def_var_nc
312!=======================================================================
313
314!=======================================================================
315SUBROUTINE get_var0d_nc(var_name,var,found)
316!-----------------------------------------------------------------------
317! NAME
318!     get_var0d_nc
319!
320! DESCRIPTION
321!     Read a 0D variable from open NetCDF file.
322!
323! AUTHORS & DATE
324!     JB Clement, 12/2025
325!
326! NOTES
327!
328!-----------------------------------------------------------------------
329
330! DECLARATION
331! -----------
332implicit none
333
334! ARGUMENTS
335! ---------
336character(*), intent(in)            :: var_name ! Variable name
337real(dp),     intent(out)           :: var      ! Output variable
338logical(k4),  intent(out), optional :: found
339
340! CODE
341! ----
342! Read
343if (present(found)) then
344    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name,found)
345    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name,found)
346else
347    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name)
348    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name)
349end if
350
351! Check the variable validity
352call check_valid_var_nc(var_name,var)
353
354END SUBROUTINE get_var0d_nc
355!=======================================================================
356
357!=======================================================================
358SUBROUTINE get_var1d_nc(var_name,var,found)
359!-----------------------------------------------------------------------
360! NAME
361!     get_var1d_nc
362!
363! DESCRIPTION
364!     Read a 1D variable from open NetCDF file.
365!
366! AUTHORS & DATE
367!     JB Clement, 12/2025
368!
369! NOTES
370!
371!-----------------------------------------------------------------------
372
373! DECLARATION
374! -----------
375implicit none
376
377! ARGUMENTS
378! ---------
379character(*),           intent(in)            :: var_name ! Variable name
380real(dp), dimension(:), intent(out)           :: var      ! Output variable
381logical(k4),            intent(out), optional :: found
382
383! CODE
384! ----
385! Read
386if (present(found)) then
387    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name,found)
388    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name,found)
389else
390    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name)
391    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name)
392end if
393
394! Check the variable validity
395call check_valid_var_nc(var_name,var)
396
397END SUBROUTINE get_var1d_nc
398!=======================================================================
399
400!=======================================================================
401SUBROUTINE get_var2d_nc(var_name,var,found)
402!-----------------------------------------------------------------------
403! NAME
404!     get_var2d_nc
405!
406! DESCRIPTION
407!     Read a 2D variable from open NetCDF file.
408!
409! AUTHORS & DATE
410!     JB Clement, 12/2025
411!
412! NOTES
413!
414!-----------------------------------------------------------------------
415
416! DECLARATION
417! -----------
418implicit none
419
420! ARGUMENTS
421! ---------
422character(*),             intent(in)            :: var_name ! Variable name
423real(dp), dimension(:,:), intent(out)           :: var      ! Output variable
424logical(k4),              intent(out), optional :: found
425
426! CODE
427! ----
428! Read
429if (present(found)) then
430    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name,found)
431    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name,found)
432else
433    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name)
434    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name)
435end if
436
437! Check the variable validity
438call check_valid_var_nc(var_name,var)
439
440END SUBROUTINE get_var2d_nc
441!=======================================================================
442
443!=======================================================================
444SUBROUTINE get_var3d_nc(var_name,var,found)
445!-----------------------------------------------------------------------
446! NAME
447!     get_var3d_nc
448!
449! DESCRIPTION
450!     Read a 3D variable from open NetCDF file.
451!
452! AUTHORS & DATE
453!     JB Clement, 12/2025
454!
455! NOTES
456!
457!-----------------------------------------------------------------------
458
459! DECLARATION
460! -----------
461implicit none
462
463! ARGUMENTS
464! ---------
465character(*),               intent(in)            :: var_name ! Variable name
466real(dp), dimension(:,:,:), intent(out)           :: var      ! Output variable
467logical(k4),                intent(out), optional :: found
468
469! CODE
470! ----
471! Read
472if (present(found)) then
473    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name,found)
474    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name,found)
475else
476    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name)
477    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name)
478end if
479
480! Check the variable validity
481call check_valid_var_nc(var_name,var)
482
483END SUBROUTINE get_var3d_nc
484!=======================================================================
485
486!=======================================================================
487SUBROUTINE get_var4d_nc(var_name,var,found)
488!-----------------------------------------------------------------------
489! NAME
490!     get_var4d_nc
491!
492! DESCRIPTION
493!     Read a 4D variable from open NetCDF file.
494!
495! AUTHORS & DATE
496!     JB Clement, 12/2025
497!
498! NOTES
499!
500!-----------------------------------------------------------------------
501
502! DECLARATION
503! -----------
504implicit none
505
506! ARGUMENTS
507! ---------
508character(*),                 intent(in)            :: var_name ! Variable name
509real(dp), dimension(:,:,:,:), intent(out)           :: var      ! Output variable
510logical(k4),                  intent(out), optional :: found
511
512! CODE
513! ----
514! Read
515if (present(found)) then
516    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name,found)
517    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name,found)
518else
519    call check_nc(nf90_inq_varid(ncid_file,var_name,varid),'inquiring '//var_name)
520    call check_nc(nf90_get_var(ncid_file,varid,var),'getting '//var_name)
521end if
522
523! Check the variable validity
524call check_valid_var_nc(var_name,var)
525
526END SUBROUTINE get_var4d_nc
527!=======================================================================
528
529!=======================================================================
530SUBROUTINE put_var0d_nc(var_name,var,itime)
531!-----------------------------------------------------------------------
532! NAME
533!     put_var0d_nc
534!
535! DESCRIPTION
536!     Write a 0D variable into open NetCDF file.
537!
538! AUTHORS & DATE
539!     JB Clement, 01/2026
540!
541! NOTES
542!
543!-----------------------------------------------------------------------
544
545! DECLARATION
546! -----------
547implicit none
548
549! ARGUMENTS
550! ---------
551character(*), intent(in)           :: var_name ! Variable name
552real(dp),     intent(in)           :: var      ! Input variable
553integer(di),  intent(in), optional :: itime    ! Current time index
554
555! LOCAL VARIABLES
556! ---------------
557integer(di)                               :: ndims, unlimdimid, ncid
558integer(di), dimension(nf90_max_var_dims) :: dimids
559logical(k4)                               :: has_time
560
561! CODE
562! ----
563! Check the variable validity
564call check_valid_var_nc(var_name,var)
565
566! Diagevol logic priority over standard logic
567if (open_diagevo) then
568    ncid = ncid_diagevo
569else
570    ncid = ncid_file
571end if
572
573! Check if the variable holds a Time dimension (unlimited dim)
574call check_nc(nf90_inq_varid(ncid,var_name,varid),'inquiring '//var_name)
575call check_nc(nf90_inquire_variable(ncid,varid,ndims = ndims,dimids = dimids),'inquiring dims of '//var_name)
576if (ndims > 0) then
577    call check_nc(nf90_inquire(ncid,unlimitedDimId = unlimdimid),'inquiring unlimited dim')
578    has_time = (dimids(ndims) == unlimdimid)
579else
580    has_time = .false.
581end if
582
583if (present(itime)) then ! Time-dependent write
584    if (.not. has_time) call stop_clean(__FILE__,__LINE__,'itime present but variable has no Time dimension: '//var_name,1)
585    ! For 0D variable with time, just write at the time index
586    call check_nc(nf90_put_var(ncid,varid,var,start = (/itime/)),'putting '//var_name)
587else ! Static write
588    if (has_time) call stop_clean(__FILE__,__LINE__,'itime absent but variable is time-dependent: '//var_name,1)
589    call check_nc(nf90_put_var(ncid,varid,var),'putting '//var_name)
590end if
591
592END SUBROUTINE put_var0d_nc
593!=======================================================================
594
595!=======================================================================
596SUBROUTINE put_var1d_nc(var_name,var,itime)
597!-----------------------------------------------------------------------
598! NAME
599!     put_var1d_nc
600!
601! DESCRIPTION
602!     Write a 1D variable into open NetCDF file.
603!
604! AUTHORS & DATE
605!     JB Clement, 01/2026
606!
607! NOTES
608!
609!-----------------------------------------------------------------------
610
611! DECLARATION
612! -----------
613implicit none
614
615! ARGUMENTS
616! ---------
617character(*),           intent(in)           :: var_name ! Variable name
618real(dp), dimension(:), intent(in)           :: var      ! Input variable
619integer(di),            intent(in), optional :: itime    ! Current time index
620
621! LOCAL VARIABLES
622! ---------------
623integer(di)                               :: i, ndims, unlimdimid, ncid
624integer(di), dimension(nf90_max_var_dims) :: dimids
625integer(di), dimension(:), allocatable    :: strt, cnt
626logical(k4)                               :: has_time
627
628! CODE
629! ----
630! Check the variable validity
631call check_valid_var_nc(var_name,var)
632
633! Diagevol logic priority over standard logic
634if (open_diagevo) then
635    ncid = ncid_diagevo
636else
637    ncid = ncid_file
638end if
639
640! Check if the variable holds a Time dimension (unlimited dim)
641call check_nc(nf90_inq_varid(ncid,var_name,varid),'inquiring '//var_name)
642call check_nc(nf90_inquire_variable(ncid,varid,ndims = ndims,dimids = dimids),'inquiring dims of '//var_name)
643if (ndims > 0) then
644    call check_nc(nf90_inquire(ncid,unlimitedDimId = unlimdimid),'inquiring unlimited dim')
645    has_time = (dimids(ndims) == unlimdimid)
646else
647    has_time = .false.
648end if
649
650if (present(itime)) then ! Time-dependent write
651    if (.not. has_time) call stop_clean(__FILE__,__LINE__,'itime present but variable has no Time dimension: '//var_name,1)
652    allocate(strt(ndims),cnt(ndims))
653    strt(:) = 1
654    cnt(:) = 1
655    strt(ndims) = itime
656    do i = 1,ndims - 1
657        cnt(i) = size(var,i)
658    end do
659    call check_nc(nf90_put_var(ncid,varid,var,start = strt,count = cnt),'putting '//var_name)
660    deallocate(strt,cnt)
661else ! Static write
662    if (has_time) call stop_clean(__FILE__,__LINE__,'itime absent but variable is time-dependent: '//var_name,1)
663    call check_nc(nf90_put_var(ncid,varid,var),'putting '//var_name)
664end if
665
666END SUBROUTINE put_var1d_nc
667!=======================================================================
668
669!=======================================================================
670SUBROUTINE put_var2d_nc(var_name,var,itime)
671!-----------------------------------------------------------------------
672! NAME
673!     put_var2d_nc
674!
675! DESCRIPTION
676!     Write a 2D variable into open NetCDF file.
677!
678! AUTHORS & DATE
679!     JB Clement, 01/2026
680!
681! NOTES
682!
683!-----------------------------------------------------------------------
684
685! DECLARATION
686! -----------
687implicit none
688
689! ARGUMENTS
690! ---------
691character(*),             intent(in)           :: var_name ! Variable name
692real(dp), dimension(:,:), intent(in)           :: var      ! Input variable
693integer(di),              intent(in), optional :: itime    ! Current time index
694
695! LOCAL VARIABLES
696! ---------------
697integer(di)                               :: i, ndims, unlimdimid, ncid
698integer(di), dimension(nf90_max_var_dims) :: dimids
699integer(di), dimension(:), allocatable    :: strt, cnt
700logical(k4)                               :: has_time
701
702! CODE
703! ----
704! Check the variable validity
705call check_valid_var_nc(var_name,var)
706
707! Diagevol logic priority over standard logic
708if (open_diagevo) then
709    ncid = ncid_diagevo
710else
711    ncid = ncid_file
712end if
713
714! Check if the variable holds a Time dimension (unlimited dim)
715call check_nc(nf90_inq_varid(ncid,var_name,varid),'inquiring '//var_name)
716call check_nc(nf90_inquire_variable(ncid,varid,ndims = ndims,dimids = dimids),'inquiring dims of '//var_name)
717if (ndims > 0) then
718    call check_nc(nf90_inquire(ncid,unlimitedDimId = unlimdimid),'inquiring unlimited dim')
719    has_time = (dimids(ndims) == unlimdimid)
720else
721    has_time = .false.
722end if
723
724if (present(itime)) then ! Time-dependent write
725    if (.not. has_time) call stop_clean(__FILE__,__LINE__,'itime present but variable has no Time dimension: '//var_name,1)
726    allocate(strt(ndims),cnt(ndims))
727    strt(:) = 1
728    cnt(:) = 1
729    strt(ndims) = itime
730    do i = 1,ndims - 1
731        cnt(i) = size(var,i)
732    end do
733    call check_nc(nf90_put_var(ncid,varid,var,start = strt,count = cnt),'putting '//var_name)
734    deallocate(strt,cnt)
735else ! Static write
736    if (has_time) call stop_clean(__FILE__,__LINE__,'itime absent but variable is time-dependent: '//var_name,1)
737    call check_nc(nf90_put_var(ncid,varid,var),'putting '//var_name)
738end if
739
740END SUBROUTINE put_var2d_nc
741!=======================================================================
742
743!=======================================================================
744SUBROUTINE put_var3d_nc(var_name,var,itime)
745!-----------------------------------------------------------------------
746! NAME
747!     put_var3d_nc
748!
749! DESCRIPTION
750!     Write a 3D variable into open NetCDF file.
751!
752! AUTHORS & DATE
753!     JB Clement, 01/2026
754!
755! NOTES
756!
757!-----------------------------------------------------------------------
758
759! DECLARATION
760! -----------
761implicit none
762
763! ARGUMENTS
764! ---------
765character(*),               intent(in)           :: var_name ! Variable name
766real(dp), dimension(:,:,:), intent(in)           :: var      ! Input variable
767integer(di),                intent(in), optional :: itime    ! Current time index
768
769! LOCAL VARIABLES
770! ---------------
771integer(di)                               :: i, ndims, unlimdimid, ncid
772integer(di), dimension(nf90_max_var_dims) :: dimids
773integer(di), dimension(:), allocatable    :: strt, cnt
774logical(k4)                               :: has_time
775
776! CODE
777! ----
778! Check the variable validity
779call check_valid_var_nc(var_name,var)
780
781! Diagevol logic priority over standard logic
782if (open_diagevo) then
783    ncid = ncid_diagevo
784else
785    ncid = ncid_file
786end if
787
788! Check if the variable holds a Time dimension (unlimited dim)
789call check_nc(nf90_inq_varid(ncid,var_name,varid),'inquiring '//var_name)
790call check_nc(nf90_inquire_variable(ncid,varid,ndims = ndims,dimids = dimids),'inquiring dims of '//var_name)
791if (ndims > 0) then
792    call check_nc(nf90_inquire(ncid,unlimitedDimId = unlimdimid),'inquiring unlimited dim')
793    has_time = (dimids(ndims) == unlimdimid)
794else
795    has_time = .false.
796end if
797
798if (present(itime)) then ! Time-dependent write
799    if (.not. has_time) call stop_clean(__FILE__,__LINE__,'itime present but variable has no Time dimension: '//var_name,1)
800    allocate(strt(ndims),cnt(ndims))
801    strt(:) = 1
802    cnt(:) = 1
803    strt(ndims) = itime
804    do i = 1,ndims - 1
805        cnt(i) = size(var,i)
806    end do
807    call check_nc(nf90_put_var(ncid,varid,var,start = strt,count = cnt),'putting '//var_name)
808    deallocate(strt,cnt)
809else ! Static write
810    if (has_time) call stop_clean(__FILE__,__LINE__,'itime absent but variable is time-dependent: '//var_name,1)
811    call check_nc(nf90_put_var(ncid,varid,var),'putting '//var_name)
812end if
813
814END SUBROUTINE put_var3d_nc
815!=======================================================================
816
817!=======================================================================
818SUBROUTINE put_var4d_nc(var_name,var,itime)
819!-----------------------------------------------------------------------
820! NAME
821!     put_var4d_nc
822!
823! DESCRIPTION
824!     Write a 4D variable into open NetCDF file.
825!
826! AUTHORS & DATE
827!     JB Clement, 01/2026
828!
829! NOTES
830!
831!-----------------------------------------------------------------------
832
833! DECLARATION
834! -----------
835implicit none
836
837! ARGUMENTS
838! ---------
839character(*),                 intent(in)           :: var_name ! Variable name
840real(dp), dimension(:,:,:,:), intent(in)           :: var      ! Input variable
841integer(di),                  intent(in), optional :: itime    ! Current time index
842
843! LOCAL VARIABLES
844! ---------------
845integer(di)                               :: i, ndims, unlimdimid, ncid
846integer(di), dimension(nf90_max_var_dims) :: dimids
847integer(di), dimension(:), allocatable    :: strt, cnt
848logical(k4)                               :: has_time
849
850! CODE
851! ----
852! Check the variable validity
853call check_valid_var_nc(var_name,var)
854
855! Diagevol logic priority over standard logic
856if (open_diagevo) then
857    ncid = ncid_diagevo
858else
859    ncid = ncid_file
860end if
861
862! Check if the variable holds a Time dimension (unlimited dim)
863call check_nc(nf90_inq_varid(ncid,var_name,varid),'inquiring '//var_name)
864call check_nc(nf90_inquire_variable(ncid,varid,ndims = ndims,dimids = dimids),'inquiring dims of '//var_name)
865if (ndims > 0) then
866    call check_nc(nf90_inquire(ncid,unlimitedDimId = unlimdimid),'inquiring unlimited dim')
867    has_time = (dimids(ndims) == unlimdimid)
868else
869    has_time = .false.
870end if
871
872if (present(itime)) then ! Time-dependent write
873    if (.not. has_time) call stop_clean(__FILE__,__LINE__,'itime present but variable has no Time dimension: '//var_name,1)
874    allocate(strt(ndims),cnt(ndims))
875    strt(:) = 1
876    cnt(:) = 1
877    strt(ndims) = itime
878    do i = 1,ndims - 1
879        cnt(i) = size(var,i)
880    end do
881    call check_nc(nf90_put_var(ncid,varid,var,start = strt,count = cnt),'putting '//var_name)
882    deallocate(strt,cnt)
883else ! Static write
884    if (has_time) call stop_clean(__FILE__,__LINE__,'itime absent but variable is time-dependent: '//var_name,1)
885    call check_nc(nf90_put_var(ncid,varid,var),'putting '//var_name)
886end if
887
888END SUBROUTINE put_var4d_nc
889!=======================================================================
890
891!=======================================================================
892SUBROUTINE get_next_itime_nc(dim_name,itime)
893!-----------------------------------------------------------------------
894! NAME
895!     get_next_itime_nc
896!
897! DESCRIPTION
898!     Get the next time index in a NetCDF file to record variables.
899!
900! AUTHORS & DATE
901!     JB Clement, 01/2026
902!
903! NOTES
904!     In most cases, dim_name = 'Time'.
905!-----------------------------------------------------------------------
906
907! DECLARATION
908! -----------
909implicit none
910
911! ARGUMENTS
912! ---------
913character(*), intent(in)  :: dim_name ! Dimension name
914integer(di),  intent(out) :: itime    ! Time index
915
916! LOCAL VARIABLES
917! ---------------
918integer(di) :: length
919logical(k4) :: found
920
921! CODE
922! ----
923! Get dimension length
924call get_dim_nc(dim_name,length,found)
925if (.not. found) call stop_clean(__FILE__,__LINE__,'dimension '//dim_name//' not found',1)
926
927! Next time index
928itime = length + 1
929
930END SUBROUTINE get_next_itime_nc
931!=======================================================================
932
933!=======================================================================
934SUBROUTINE check_valid_var0d_nc(var_name,var)
935!-----------------------------------------------------------------------
936! NAME
937!     check_valid_var0d_nc
938!
939! DESCRIPTION
940!     Check the validity of a 0D variable read from a NetCDF file.
941!
942! AUTHORS & DATE
943!     JB Clement, 02/2026
944!
945! NOTES
946!
947!-----------------------------------------------------------------------
948
949! DEPENDENCIES
950! ------------
951use numerics, only: largest_nb
952use stoppage, only: stop_clean
953use utility,  only: real2str
954
955! DECLARATION
956! -----------
957implicit none
958
959! ARGUMENTS
960! ---------
961character(*), intent(in) :: var_name ! Variable name
962real(dp),     intent(in) :: var      ! Input variable
963
964! LOCAL VARIABLES
965! ---------------
966logical(k4)            :: has_fill, has_range, has_valid_min, has_valid_max
967real(dp)               :: fill_value, valid_min, valid_max
968real(dp), dimension(2) :: valid_range
969integer(di)            :: ncid
970
971! CODE
972! ----
973! Diagevol logic priority over standard logic
974if (open_diagevo) then
975    ncid = ncid_diagevo
976else
977    ncid = ncid_file
978end if
979
980! NaN
981if (var /= var) call stop_clean(__FILE__,__LINE__,'NaN detected in variable '//var_name//'!',1)
982
983! Infinite
984if (abs(var) > largest_nb) call stop_clean(__FILE__,__LINE__,'Infs detected in variable '//var_name//'!',1)
985
986! Fill value
987has_fill = .false.
988call check_nc(nf90_get_att(ncid,varid,"_FillValue",fill_value),'getting fill value',has_fill)
989if (.not. has_fill) call check_nc(nf90_get_att(ncid,varid,"missing_value",fill_value),'getting missing value',has_fill)
990if (has_fill) then
991    if (abs(var - fill_value) < minieps) call stop_clean(__FILE__,__LINE__,'Fill values ('//real2str(fill_value)//') detected in '//var_name//'!',1)
992end if
993
994! Valid range
995has_range = .false.
996call check_nc(nf90_get_att(ncid,varid,"valid_range",valid_range),'getting valid range',has_range)
997if (has_range) then
998    valid_min = valid_range(1)
999    valid_max = valid_range(2)
1000else! valid_min / valid_max (fallback)
1001    has_valid_min = .false.
1002    has_valid_max = .false.
1003    call check_nc(nf90_get_att(ncid,varid,"valid_min",valid_min),'getting valid min',has_valid_min)
1004    call check_nc(nf90_get_att(ncid,varid,"valid_max",valid_max),'getting valid max',has_valid_max)
1005    if (has_valid_min .or. has_valid_max) then
1006        has_range = .true.
1007        if (.not. has_valid_min) valid_min = -largest_nb
1008        if (.not. has_valid_max) valid_max = largest_nb
1009    end if
1010end if
1011if (has_range) then
1012    if (var < valid_min .or. var > valid_max) call stop_clean(__FILE__,__LINE__,'Values outside valid range ('//real2str(valid_min)//','//real2str(valid_max)//') detected in '//var_name//'!',1)
1013end if
1014
1015END SUBROUTINE check_valid_var0d_nc
1016!=======================================================================
1017
1018!=======================================================================
1019SUBROUTINE check_valid_var1d_nc(var_name,var)
1020!-----------------------------------------------------------------------
1021! NAME
1022!     check_valid_var1d_nc
1023!
1024! DESCRIPTION
1025!     Check the validity of a 1D variable read from a NetCDF file.
1026!
1027! AUTHORS & DATE
1028!     JB Clement, 02/2026
1029!
1030! NOTES
1031!
1032!-----------------------------------------------------------------------
1033
1034! DEPENDENCIES
1035! ------------
1036use numerics, only: largest_nb
1037use stoppage, only: stop_clean
1038use utility,  only: real2str
1039
1040! DECLARATION
1041! -----------
1042implicit none
1043
1044! ARGUMENTS
1045! ---------
1046character(*),           intent(in) :: var_name ! Variable name
1047real(dp), dimension(:), intent(in) :: var      ! Input variable
1048
1049! LOCAL VARIABLES
1050! ---------------
1051logical(k4)            :: has_fill, has_range, has_valid_min, has_valid_max
1052real(dp)               :: fill_value, valid_min, valid_max
1053real(dp), dimension(2) :: valid_range
1054integer(di)            :: ncid
1055
1056! CODE
1057! ----
1058! Diagevol logic priority over standard logic
1059if (open_diagevo) then
1060    ncid = ncid_diagevo
1061else
1062    ncid = ncid_file
1063end if
1064
1065! NaN
1066if (any(var /= var)) call stop_clean(__FILE__,__LINE__,'NaN detected in variable '//var_name//'!',1)
1067
1068! Infinite
1069if (any(abs(var) > largest_nb)) call stop_clean(__FILE__,__LINE__,'Infs detected in variable '//var_name//'!',1)
1070
1071! Fill value
1072has_fill = .false.
1073call check_nc(nf90_get_att(ncid,varid,"_FillValue",fill_value),'getting fill value',has_fill)
1074if (.not. has_fill) call check_nc(nf90_get_att(ncid,varid,"missing_value",fill_value),'getting missing value',has_fill)
1075if (has_fill) then
1076    if (any(abs(var - fill_value) < minieps)) call stop_clean(__FILE__,__LINE__,'Fill values ('//real2str(fill_value)//') detected in '//var_name//'!',1)
1077end if
1078
1079! Valid range
1080has_range = .false.
1081call check_nc(nf90_get_att(ncid,varid,"valid_range",valid_range),'getting valid range',has_range)
1082if (has_range) then
1083    valid_min = valid_range(1)
1084    valid_max = valid_range(2)
1085else! valid_min / valid_max (fallback)
1086    has_valid_min = .false.
1087    has_valid_max = .false.
1088    call check_nc(nf90_get_att(ncid,varid,"valid_min",valid_min),'getting valid min',has_valid_min)
1089    call check_nc(nf90_get_att(ncid,varid,"valid_max",valid_max),'getting valid max',has_valid_max)
1090    if (has_valid_min .or. has_valid_max) then
1091        has_range = .true.
1092        if (.not. has_valid_min) valid_min = -largest_nb
1093        if (.not. has_valid_max) valid_max = largest_nb
1094    end if
1095end if
1096if (has_range) then
1097    if (any(var < valid_min) .or. any(var > valid_max)) call stop_clean(__FILE__,__LINE__,'Values outside valid range ('//real2str(valid_min)//','//real2str(valid_max)//') detected in '//var_name//'!',1)
1098end if
1099
1100END SUBROUTINE check_valid_var1d_nc
1101!=======================================================================
1102
1103!=======================================================================
1104SUBROUTINE check_valid_var2d_nc(var_name,var)
1105!-----------------------------------------------------------------------
1106! NAME
1107!     check_valid_var2d_nc
1108!
1109! DESCRIPTION
1110!     Check the validity of a 1D variable read from a NetCDF file.
1111!
1112! AUTHORS & DATE
1113!     JB Clement, 02/2026
1114!
1115! NOTES
1116!
1117!-----------------------------------------------------------------------
1118
1119! DEPENDENCIES
1120! ------------
1121use numerics, only: largest_nb
1122use stoppage, only: stop_clean
1123use utility,  only: real2str
1124
1125! DECLARATION
1126! -----------
1127implicit none
1128
1129! ARGUMENTS
1130! ---------
1131character(*),             intent(in) :: var_name ! Variable name
1132real(dp), dimension(:,:), intent(in) :: var      ! Input variable
1133
1134! LOCAL VARIABLES
1135! ---------------
1136logical(k4)            :: has_fill, has_range, has_valid_min, has_valid_max
1137real(dp)               :: fill_value, valid_min, valid_max
1138real(dp), dimension(2) :: valid_range
1139integer(di)            :: ncid
1140
1141! CODE
1142! ----
1143! Diagevol logic priority over standard logic
1144if (open_diagevo) then
1145    ncid = ncid_diagevo
1146else
1147    ncid = ncid_file
1148end if
1149
1150! NaN
1151if (any(var /= var)) call stop_clean(__FILE__,__LINE__,'NaN detected in variable '//var_name//'!',1)
1152
1153! Infinite
1154if (any(abs(var) > largest_nb)) call stop_clean(__FILE__,__LINE__,'Infs detected in variable '//var_name//'!',1)
1155
1156! Fill value
1157has_fill = .false.
1158call check_nc(nf90_get_att(ncid,varid,"_FillValue",fill_value),'getting fill value',has_fill)
1159if (.not. has_fill) call check_nc(nf90_get_att(ncid,varid,"missing_value",fill_value),'getting missing value',has_fill)
1160if (has_fill) then
1161    if (any(abs(var - fill_value) < minieps)) call stop_clean(__FILE__,__LINE__,'Fill values ('//real2str(fill_value)//') detected in '//var_name//'!',1)
1162end if
1163
1164! Valid range
1165has_range = .false.
1166call check_nc(nf90_get_att(ncid,varid,"valid_range",valid_range),'getting valid range',has_range)
1167if (has_range) then
1168    valid_min = valid_range(1)
1169    valid_max = valid_range(2)
1170else! valid_min / valid_max (fallback)
1171    has_valid_min = .false.
1172    has_valid_max = .false.
1173    call check_nc(nf90_get_att(ncid,varid,"valid_min",valid_min),'getting valid min',has_valid_min)
1174    call check_nc(nf90_get_att(ncid,varid,"valid_max",valid_max),'getting valid max',has_valid_max)
1175    if (has_valid_min .or. has_valid_max) then
1176        has_range = .true.
1177        if (.not. has_valid_min) valid_min = -largest_nb
1178        if (.not. has_valid_max) valid_max = largest_nb
1179    end if
1180end if
1181if (has_range) then
1182    if (any(var < valid_min) .or. any(var > valid_max)) call stop_clean(__FILE__,__LINE__,'Values outside valid range ('//real2str(valid_min)//','//real2str(valid_max)//') detected in '//var_name//'!',1)
1183end if
1184
1185END SUBROUTINE check_valid_var2d_nc
1186!=======================================================================
1187
1188!=======================================================================
1189SUBROUTINE check_valid_var3d_nc(var_name,var)
1190!-----------------------------------------------------------------------
1191! NAME
1192!     check_valid_var3d_nc
1193!
1194! DESCRIPTION
1195!     Check the validity of a 1D variable read from a NetCDF file.
1196!
1197! AUTHORS & DATE
1198!     JB Clement, 02/2026
1199!
1200! NOTES
1201!
1202!-----------------------------------------------------------------------
1203
1204! DEPENDENCIES
1205! ------------
1206use numerics, only: largest_nb
1207use stoppage, only: stop_clean
1208use utility,  only: real2str
1209
1210! DECLARATION
1211! -----------
1212implicit none
1213
1214! ARGUMENTS
1215! ---------
1216character(*),               intent(in) :: var_name ! Variable name
1217real(dp), dimension(:,:,:), intent(in) :: var      ! Input variable
1218
1219! LOCAL VARIABLES
1220! ---------------
1221logical(k4)            :: has_fill, has_range, has_valid_min, has_valid_max
1222real(dp)               :: fill_value, valid_min, valid_max
1223real(dp), dimension(2) :: valid_range
1224integer(di)            :: ncid
1225
1226! CODE
1227! ----
1228! Diagevol logic priority over standard logic
1229if (open_diagevo) then
1230    ncid = ncid_diagevo
1231else
1232    ncid = ncid_file
1233end if
1234
1235! NaN
1236if (any(var /= var)) call stop_clean(__FILE__,__LINE__,'NaN detected in variable '//var_name//'!',1)
1237
1238! Infinite
1239if (any(abs(var) > largest_nb)) call stop_clean(__FILE__,__LINE__,'Infs detected in variable '//var_name//'!',1)
1240
1241! Fill value
1242has_fill = .false.
1243call check_nc(nf90_get_att(ncid,varid,"_FillValue",fill_value),'getting fill value',has_fill)
1244if (.not. has_fill) call check_nc(nf90_get_att(ncid,varid,"missing_value",fill_value),'getting missing value',has_fill)
1245if (has_fill) then
1246    if (any(abs(var - fill_value) < minieps)) call stop_clean(__FILE__,__LINE__,'Fill values ('//real2str(fill_value)//') detected in '//var_name//'!',1)
1247end if
1248
1249! Valid range
1250has_range = .false.
1251call check_nc(nf90_get_att(ncid,varid,"valid_range",valid_range),'getting valid range',has_range)
1252if (has_range) then
1253    valid_min = valid_range(1)
1254    valid_max = valid_range(2)
1255else! valid_min / valid_max (fallback)
1256    has_valid_min = .false.
1257    has_valid_max = .false.
1258    call check_nc(nf90_get_att(ncid,varid,"valid_min",valid_min),'getting valid min',has_valid_min)
1259    call check_nc(nf90_get_att(ncid,varid,"valid_max",valid_max),'getting valid max',has_valid_max)
1260    if (has_valid_min .or. has_valid_max) then
1261        has_range = .true.
1262        if (.not. has_valid_min) valid_min = -largest_nb
1263        if (.not. has_valid_max) valid_max = largest_nb
1264    end if
1265end if
1266if (has_range) then
1267    if (any(var < valid_min) .or. any(var > valid_max)) call stop_clean(__FILE__,__LINE__,'Values outside valid range ('//real2str(valid_min)//','//real2str(valid_max)//') detected in '//var_name//'!',1)
1268end if
1269
1270END SUBROUTINE check_valid_var3d_nc
1271!=======================================================================
1272
1273!=======================================================================
1274SUBROUTINE check_valid_var4d_nc(var_name,var)
1275!-----------------------------------------------------------------------
1276! NAME
1277!     check_valid_var4d_nc
1278!
1279! DESCRIPTION
1280!     Check the validity of a 1D variable read from a NetCDF file.
1281!
1282! AUTHORS & DATE
1283!     JB Clement, 02/2026
1284!
1285! NOTES
1286!
1287!-----------------------------------------------------------------------
1288
1289! DEPENDENCIES
1290! ------------
1291use numerics, only: largest_nb
1292use stoppage, only: stop_clean
1293use utility,  only: real2str
1294
1295! DECLARATION
1296! -----------
1297implicit none
1298
1299! ARGUMENTS
1300! ---------
1301character(*),                 intent(in) :: var_name ! Variable name
1302real(dp), dimension(:,:,:,:), intent(in) :: var      ! Input variable
1303
1304! LOCAL VARIABLES
1305! ---------------
1306logical(k4)            :: has_fill, has_range, has_valid_min, has_valid_max
1307real(dp)               :: fill_value, valid_min, valid_max
1308real(dp), dimension(2) :: valid_range
1309integer(di)            :: ncid
1310
1311! CODE
1312! ----
1313! Diagevol logic priority over standard logic
1314if (open_diagevo) then
1315    ncid = ncid_diagevo
1316else
1317    ncid = ncid_file
1318end if
1319
1320! NaN
1321if (any(var /= var)) call stop_clean(__FILE__,__LINE__,'NaN detected in variable '//var_name//'!',1)
1322
1323! Infinite
1324if (any(abs(var) > largest_nb)) call stop_clean(__FILE__,__LINE__,'Infs detected in variable '//var_name//'!',1)
1325
1326! Fill value
1327has_fill = .false.
1328call check_nc(nf90_get_att(ncid,varid,"_FillValue",fill_value),'getting fill value',has_fill)
1329if (.not. has_fill) call check_nc(nf90_get_att(ncid,varid,"missing_value",fill_value),'getting missing value',has_fill)
1330if (has_fill) then
1331    if (any(abs(var - fill_value) < minieps)) call stop_clean(__FILE__,__LINE__,'Fill values ('//real2str(fill_value)//') detected in '//var_name//'!',1)
1332end if
1333
1334! Valid range
1335has_range = .false.
1336call check_nc(nf90_get_att(ncid,varid,"valid_range",valid_range),'getting valid range',has_range)
1337if (has_range) then
1338    valid_min = valid_range(1)
1339    valid_max = valid_range(2)
1340else! valid_min / valid_max (fallback)
1341    has_valid_min = .false.
1342    has_valid_max = .false.
1343    call check_nc(nf90_get_att(ncid,varid,"valid_min",valid_min),'getting valid min',has_valid_min)
1344    call check_nc(nf90_get_att(ncid,varid,"valid_max",valid_max),'getting valid max',has_valid_max)
1345    if (has_valid_min .or. has_valid_max) then
1346        has_range = .true.
1347        if (.not. has_valid_min) valid_min = -largest_nb
1348        if (.not. has_valid_max) valid_max = largest_nb
1349    end if
1350end if
1351if (has_range) then
1352    if(any(var < valid_min) .or. any(var > valid_max)) call stop_clean(__FILE__,__LINE__,'Values outside valid range ('//real2str(valid_min)//','//real2str(valid_max)//') detected in '//var_name//'!',1)
1353end if
1354
1355END SUBROUTINE check_valid_var4d_nc
1356!=======================================================================
1357
1358END MODULE io_netcdf
Note: See TracBrowser for help on using the repository browser.