diff --git a/CMakeLists.txt b/CMakeLists.txt index 8145417f6..1719daa30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,6 +249,7 @@ else() src/offline/spincasacnp.F90 src/util/cable_climate_type_mod.F90 src/util/masks_cbl.F90 + src/util/netcdf_utils.F90 ) target_link_libraries(cable_common PRIVATE PkgConfig::NETCDF) diff --git a/src/util/netcdf_utils.F90 b/src/util/netcdf_utils.F90 new file mode 100644 index 000000000..d3035657c --- /dev/null +++ b/src/util/netcdf_utils.F90 @@ -0,0 +1,207 @@ +! CSIRO Open Source Software License Agreement (variation of the BSD / MIT License) +! Copyright (c) 2015, Commonwealth Scientific and Industrial Research Organisation +! (CSIRO) ABN 41 687 119 230. + +MODULE netcdf_utils + !! Module for common NetCDF utility procedures. + USE netcdf + USE iso_fortran_env, ONLY: int32, real32, real64 + + IMPLICIT NONE + + PRIVATE + + PUBLIC :: to_netcdf + + INTERFACE to_netcdf + !* Overloads for the `to_netcdf` subroutine. + ! + ! Usage: + ! ```fortran + ! CALL to_netcdf("data.nc", values) + ! ``` + ! where `values` is an array and its type is supported by the following overloads. + ! + ! **WARNING:** this subroutine should **not** be used to write standard + ! CABLE outputs as the resulting NetCDF files do not include the required + ! metadata. This subroutine is to be used for debugging purposes only. New + ! outputs for the CABLE model should be implemented in cable_output.F90. + MODULE PROCEDURE to_netcdf_int32_1d, to_netcdf_int32_2d, to_netcdf_int32_3d + MODULE PROCEDURE to_netcdf_real32_1d, to_netcdf_real32_2d, to_netcdf_real32_3d + MODULE PROCEDURE to_netcdf_real64_1d, to_netcdf_real64_2d, to_netcdf_real64_3d + END INTERFACE to_netcdf + +CONTAINS + + SUBROUTINE check(status) + !! Check NetCDF error status. + INTEGER, INTENT(IN) :: status !! Error status + IF (status /= NF90_NOERR) THEN + PRINT *, trim(nf90_strerror(status)) + STOP + END IF + END SUBROUTINE check + + SUBROUTINE to_netcdf_int32_1d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + INTEGER (kind=int32), DIMENSION(:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(1), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_INT, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_int32_1d + + SUBROUTINE to_netcdf_int32_2d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + INTEGER (kind=int32), DIMENSION(:,:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(2), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_INT, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_int32_2d + + SUBROUTINE to_netcdf_int32_3d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + INTEGER (kind=int32), DIMENSION(:,:,:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(3), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_INT, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_int32_3d + + SUBROUTINE to_netcdf_real32_1d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + REAL (kind=real32), DIMENSION(:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(1), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_FLOAT, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_real32_1d + + SUBROUTINE to_netcdf_real32_2d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + REAL (kind=real32), DIMENSION(:,:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(2), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_FLOAT, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_real32_2d + + SUBROUTINE to_netcdf_real32_3d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + REAL (kind=real32), DIMENSION(:,:,:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(3), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_FLOAT, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_real32_3d + + SUBROUTINE to_netcdf_real64_1d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + REAL (kind=real64), DIMENSION(:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(1), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_DOUBLE, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_real64_1d + + SUBROUTINE to_netcdf_real64_2d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + REAL (kind=real64), DIMENSION(:,:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(2), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_DOUBLE, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_real64_2d + + SUBROUTINE to_netcdf_real64_3d(filename, values) + !* Dump values to NetCDF file. + ! Any existing dataset with the same filename will be overwritten. + CHARACTER (len=*), INTENT(IN) :: filename + REAL (kind=real64), DIMENSION(:,:,:), INTENT(IN) :: values + INTEGER :: ncid, varid, dimids(3), i + CHARACTER (len=2) :: dimc + CALL check( nf90_create(filename, NF90_CLOBBER, ncid) ) + DO i = 1, size(shape(values)) + WRITE (dimc, "(I2.2)") i + CALL check( nf90_def_dim(ncid, "dim" // dimc, size(values, i), dimids(i)) ) + END DO + CALL check( nf90_def_var(ncid, "values", NF90_DOUBLE, dimids, varid) ) + CALL check( nf90_enddef(ncid) ) + CALL check( nf90_put_var(ncid, varid, values) ) + CALL check( nf90_close(ncid) ) + END SUBROUTINE to_netcdf_real64_3d + +END MODULE netcdf_utils