From 3726642bdb9a96e7725e97806c90a7f615619888 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 7 Nov 2025 10:13:42 +1100 Subject: [PATCH 01/31] Add aggregator implementation Co-authored-by: Lachlan Whyborn --- CMakeLists.txt | 1 + src/util/aggregator.F90 | 614 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 615 insertions(+) create mode 100644 src/util/aggregator.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9618bd2ea..e36730558 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -299,6 +299,7 @@ else() src/offline/cbl_model_driver_offline.F90 src/offline/landuse_inout.F90 src/offline/spincasacnp.F90 + src/util/aggregator.F90 src/util/cable_climate_type_mod.F90 src/util/masks_cbl.F90 src/util/cable_array_utils.F90 diff --git a/src/util/aggregator.F90 b/src/util/aggregator.F90 new file mode 100644 index 000000000..838c164a7 --- /dev/null +++ b/src/util/aggregator.F90 @@ -0,0 +1,614 @@ +module aggregator_mod + + use iso_fortran_env, only: int32, real32, real64 + use cable_abort_module, only: cable_abort + + implicit none + private + + public :: aggregator_t + public :: aggregator_int32_0d_t + public :: aggregator_int32_1d_t + public :: aggregator_int32_2d_t + public :: aggregator_int32_3d_t + public :: aggregator_real32_0d_t + public :: aggregator_real32_1d_t + public :: aggregator_real32_2d_t + public :: aggregator_real32_3d_t + public :: aggregator_real64_0d_t + public :: aggregator_real64_1d_t + public :: aggregator_real64_2d_t + public :: aggregator_real64_3d_t + public :: new_aggregator + + type, abstract :: aggregator_t + integer :: counter = 0 + procedure(accumulate_data), pointer :: accumulate + procedure(reset_data), pointer :: reset + contains + procedure :: init => aggregator_init + procedure :: set_method => aggregator_set_method + procedure :: rank => aggregator_rank + procedure :: shape => aggregator_shape + end type aggregator_t + + abstract interface + subroutine accumulate_data(this) + import aggregator_t + class(aggregator_t), intent(inout) :: this + end subroutine accumulate_data + subroutine reset_data(this) + import aggregator_t + class(aggregator_t), intent(inout) :: this + end subroutine reset_data + end interface + + type, extends(aggregator_t) :: aggregator_int32_0d_t + integer(kind=int32), allocatable :: aggregated_data + integer(kind=int32), pointer :: source_data => null() + end type aggregator_int32_0d_t + + type, extends(aggregator_t) :: aggregator_int32_1d_t + integer(kind=int32), dimension(:), allocatable :: aggregated_data + integer(kind=int32), dimension(:), pointer :: source_data => null() + end type aggregator_int32_1d_t + + type, extends(aggregator_t) :: aggregator_int32_2d_t + integer(kind=int32), dimension(:,:), allocatable :: aggregated_data + integer(kind=int32), dimension(:,:), pointer :: source_data => null() + end type aggregator_int32_2d_t + + type, extends(aggregator_t) :: aggregator_int32_3d_t + integer(kind=int32), dimension(:,:,:), allocatable :: aggregated_data + integer(kind=int32), dimension(:,:,:), pointer :: source_data => null() + end type aggregator_int32_3d_t + + type, extends(aggregator_t) :: aggregator_real32_0d_t + real(kind=real32), allocatable :: aggregated_data + real(kind=real32), pointer :: source_data => null() + end type aggregator_real32_0d_t + + type, extends(aggregator_t) :: aggregator_real32_1d_t + real(kind=real32), dimension(:), allocatable :: aggregated_data + real(kind=real32), dimension(:), pointer :: source_data => null() + end type aggregator_real32_1d_t + + type, extends(aggregator_t) :: aggregator_real32_2d_t + real(kind=real32), dimension(:,:), allocatable :: aggregated_data + real(kind=real32), dimension(:,:), pointer :: source_data => null() + end type aggregator_real32_2d_t + + type, extends(aggregator_t) :: aggregator_real32_3d_t + real(kind=real32), dimension(:,:,:), allocatable :: aggregated_data + real(kind=real32), dimension(:,:,:), pointer :: source_data => null() + end type aggregator_real32_3d_t + + type, extends(aggregator_t) :: aggregator_real64_0d_t + real(kind=real64), allocatable :: aggregated_data + real(kind=real64), pointer :: source_data => null() + end type aggregator_real64_0d_t + + type, extends(aggregator_t) :: aggregator_real64_1d_t + real(kind=real64), dimension(:), allocatable :: aggregated_data + real(kind=real64), dimension(:), pointer :: source_data => null() + end type aggregator_real64_1d_t + + type, extends(aggregator_t) :: aggregator_real64_2d_t + real(kind=real64), dimension(:,:), allocatable :: aggregated_data + real(kind=real64), dimension(:,:), pointer :: source_data => null() + end type aggregator_real64_2d_t + + type, extends(aggregator_t) :: aggregator_real64_3d_t + real(kind=real64), dimension(:,:,:), allocatable :: aggregated_data + real(kind=real64), dimension(:,:,:), pointer :: source_data => null() + end type aggregator_real64_3d_t + + interface new_aggregator + module procedure new_aggregator_int32_0d_t + module procedure new_aggregator_int32_1d_t + module procedure new_aggregator_int32_2d_t + module procedure new_aggregator_int32_3d_t + module procedure new_aggregator_real32_0d + module procedure new_aggregator_real32_1d + module procedure new_aggregator_real32_2d + module procedure new_aggregator_real32_3d + module procedure new_aggregator_real64_0d + module procedure new_aggregator_real64_1d + module procedure new_aggregator_real64_2d + module procedure new_aggregator_real64_3d + end interface + +contains + + subroutine aggregator_init(this, method) + class(aggregator_t), intent(inout) :: this + character(len=*), intent(in) :: method + + select type (this) + type is (aggregator_int32_0d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_int32_1d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_int32_2d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_int32_3d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_real32_0d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_real32_1d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_real32_2d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_real32_3d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_real64_0d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_real64_1d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_real64_2d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + type is (aggregator_real64_3d_t) + if (.not. allocated(this%aggregated_data)) allocate(this%aggregated_data, mold=this%source_data) + end select + + call this%set_method(method) + + call this%reset() + + end subroutine aggregator_init + + subroutine aggregator_set_method(this, method) + class(aggregator_t), intent(inout) :: this + character(len=*), intent(in) :: method + + if (method == "mean") then + this%accumulate => mean_accumulate + this%reset => other_reset + elseif (method == "sum") then + this%accumulate => sum_accumulate + this%reset => other_reset + elseif (method == "point") then + this%accumulate => point_accumulate + this%reset => point_reset + elseif (method == "min") then + this%accumulate => min_accumulate + this%reset => min_reset + elseif (method == "max") then + this%accumulate => max_accumulate + this%reset => max_reset + else + call cable_abort("Aggregation method "//method//" is invalid.") + endif + + end subroutine aggregator_set_method + + integer function aggregator_rank(this) + class(aggregator_t), intent(in) :: this + + select type (this) + type is (aggregator_int32_0d_t) + aggregator_rank = 0 + type is (aggregator_int32_1d_t) + aggregator_rank = 1 + type is (aggregator_int32_2d_t) + aggregator_rank = 2 + type is (aggregator_int32_3d_t) + aggregator_rank = 3 + type is (aggregator_real32_0d_t) + aggregator_rank = 0 + type is (aggregator_real32_1d_t) + aggregator_rank = 1 + type is (aggregator_real32_2d_t) + aggregator_rank = 2 + type is (aggregator_real32_3d_t) + aggregator_rank = 3 + type is (aggregator_real64_0d_t) + aggregator_rank = 0 + type is (aggregator_real64_1d_t) + aggregator_rank = 1 + type is (aggregator_real64_2d_t) + aggregator_rank = 2 + type is (aggregator_real64_3d_t) + aggregator_rank = 3 + end select + + end function aggregator_rank + + function aggregator_shape(this) result(agg_shape) + class(aggregator_t), intent(in) :: this + integer, allocatable :: agg_shape(:) + + select type (this) + type is (aggregator_int32_0d_t) + agg_shape = shape(this%source_data) + type is (aggregator_int32_1d_t) + agg_shape = shape(this%source_data) + type is (aggregator_int32_2d_t) + agg_shape = shape(this%source_data) + type is (aggregator_int32_3d_t) + agg_shape = shape(this%source_data) + type is (aggregator_real32_0d_t) + agg_shape = shape(this%source_data) + type is (aggregator_real32_1d_t) + agg_shape = shape(this%source_data) + type is (aggregator_real32_2d_t) + agg_shape = shape(this%source_data) + type is (aggregator_real32_3d_t) + agg_shape = shape(this%source_data) + type is (aggregator_real64_0d_t) + agg_shape = shape(this%source_data) + type is (aggregator_real64_1d_t) + agg_shape = shape(this%source_data) + type is (aggregator_real64_2d_t) + agg_shape = shape(this%source_data) + type is (aggregator_real64_3d_t) + agg_shape = shape(this%source_data) + end select + + end function aggregator_shape + + subroutine mean_accumulate(this) + class(aggregator_t), intent(inout) :: this + + select type (this) + type is (aggregator_real32_0d_t) + this%aggregated_data = this%aggregated_data + (this%source_data - this%aggregated_data) / (this%counter + 1) + type is (aggregator_real32_1d_t) + this%aggregated_data = this%aggregated_data + (this%source_data - this%aggregated_data) / (this%counter + 1) + type is (aggregator_real32_2d_t) + this%aggregated_data = this%aggregated_data + (this%source_data - this%aggregated_data) / (this%counter + 1) + type is (aggregator_real32_3d_t) + this%aggregated_data = this%aggregated_data + (this%source_data - this%aggregated_data) / (this%counter + 1) + type is (aggregator_real64_0d_t) + this%aggregated_data = this%aggregated_data + (this%source_data - this%aggregated_data) / (this%counter + 1) + type is (aggregator_real64_1d_t) + this%aggregated_data = this%aggregated_data + (this%source_data - this%aggregated_data) / (this%counter + 1) + type is (aggregator_real64_2d_t) + this%aggregated_data = this%aggregated_data + (this%source_data - this%aggregated_data) / (this%counter + 1) + type is (aggregator_real64_3d_t) + this%aggregated_data = this%aggregated_data + (this%source_data - this%aggregated_data) / (this%counter + 1) + end select + + this%counter = this%counter + 1 + + end subroutine mean_accumulate + + subroutine sum_accumulate(this) + class(aggregator_t), intent(inout) :: this + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_int32_1d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_int32_2d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_int32_3d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_real32_0d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_real32_1d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_real32_2d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_real32_3d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_real64_0d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_real64_1d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_real64_2d_t) + this%aggregated_data = this%aggregated_data + this%source_data + type is (aggregator_real64_3d_t) + this%aggregated_data = this%aggregated_data + this%source_data + end select + + this%counter = this%counter + 1 + + end subroutine sum_accumulate + + subroutine point_accumulate(this) + class(aggregator_t), intent(inout) :: this + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = this%source_data + type is (aggregator_int32_1d_t) + this%aggregated_data = this%source_data + type is (aggregator_int32_2d_t) + this%aggregated_data = this%source_data + type is (aggregator_int32_3d_t) + this%aggregated_data = this%source_data + type is (aggregator_real32_0d_t) + this%aggregated_data = this%source_data + type is (aggregator_real32_1d_t) + this%aggregated_data = this%source_data + type is (aggregator_real32_2d_t) + this%aggregated_data = this%source_data + type is (aggregator_real32_3d_t) + this%aggregated_data = this%source_data + type is (aggregator_real64_0d_t) + this%aggregated_data = this%source_data + type is (aggregator_real64_1d_t) + this%aggregated_data = this%source_data + type is (aggregator_real64_2d_t) + this%aggregated_data = this%source_data + type is (aggregator_real64_3d_t) + this%aggregated_data = this%source_data + end select + + this%counter = this%counter + 1 + + end subroutine point_accumulate + + subroutine min_accumulate(this) + class(aggregator_t), intent(inout) :: this + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_int32_1d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_int32_2d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_int32_3d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_real32_0d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_real32_1d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_real32_2d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_real32_3d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_real64_0d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_real64_1d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_real64_2d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + type is (aggregator_real64_3d_t) + this%aggregated_data = min(this%aggregated_data, this%source_data) + end select + + this%counter = this%counter + 1 + + end subroutine min_accumulate + + subroutine max_accumulate(this) + class(aggregator_t), intent(inout) :: this + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_int32_1d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_int32_2d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_int32_3d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_real32_0d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_real32_1d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_real32_2d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_real32_3d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_real64_0d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_real64_1d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_real64_2d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + type is (aggregator_real64_3d_t) + this%aggregated_data = max(this%aggregated_data, this%source_data) + end select + + this%counter = this%counter + 1 + + end subroutine max_accumulate + + subroutine point_reset(this) + class(aggregator_t), intent(inout) :: this + end subroutine point_reset + + subroutine min_reset(this) + class(aggregator_t), intent(inout) :: this + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = huge(int(0_int32)) + type is (aggregator_int32_1d_t) + this%aggregated_data = huge(int(0_int32)) + type is (aggregator_int32_2d_t) + this%aggregated_data = huge(int(0_int32)) + type is (aggregator_int32_3d_t) + this%aggregated_data = huge(int(0_int32)) + type is (aggregator_real32_0d_t) + this%aggregated_data = huge(real(0.0_real32)) + type is (aggregator_real32_1d_t) + this%aggregated_data = huge(real(0.0_real32)) + type is (aggregator_real32_2d_t) + this%aggregated_data = huge(real(0.0_real32)) + type is (aggregator_real32_3d_t) + this%aggregated_data = huge(real(0.0_real32)) + type is (aggregator_real64_0d_t) + this%aggregated_data = huge(real(0.0_real64)) + type is (aggregator_real64_1d_t) + this%aggregated_data = huge(real(0.0_real64)) + type is (aggregator_real64_2d_t) + this%aggregated_data = huge(real(0.0_real64)) + type is (aggregator_real64_3d_t) + this%aggregated_data = huge(real(0.0_real64)) + end select + + this%counter = 0 + + end subroutine min_reset + + subroutine max_reset(this) + class(aggregator_t), intent(inout) :: this + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = -huge(int(0_int32)) + type is (aggregator_int32_1d_t) + this%aggregated_data = -huge(int(0_int32)) + type is (aggregator_int32_2d_t) + this%aggregated_data = -huge(int(0_int32)) + type is (aggregator_int32_3d_t) + this%aggregated_data = -huge(int(0_int32)) + type is (aggregator_real32_0d_t) + this%aggregated_data = -huge(real(0.0_real32)) + type is (aggregator_real32_1d_t) + this%aggregated_data = -huge(real(0.0_real32)) + type is (aggregator_real32_2d_t) + this%aggregated_data = -huge(real(0.0_real32)) + type is (aggregator_real32_3d_t) + this%aggregated_data = -huge(real(0.0_real32)) + type is (aggregator_real64_0d_t) + this%aggregated_data = -huge(real(0.0_real64)) + type is (aggregator_real64_1d_t) + this%aggregated_data = -huge(real(0.0_real64)) + type is (aggregator_real64_2d_t) + this%aggregated_data = -huge(real(0.0_real64)) + type is (aggregator_real64_3d_t) + this%aggregated_data = -huge(real(0.0_real64)) + end select + + this%counter = 0 + + end subroutine max_reset + + subroutine other_reset(this) + class(aggregator_t), intent(inout) :: this + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = 0_int32 + type is (aggregator_int32_1d_t) + this%aggregated_data = 0_int32 + type is (aggregator_int32_2d_t) + this%aggregated_data = 0_int32 + type is (aggregator_int32_3d_t) + this%aggregated_data = 0_int32 + type is (aggregator_real32_0d_t) + this%aggregated_data = 0.0_real32 + type is (aggregator_real32_1d_t) + this%aggregated_data = 0.0_real32 + type is (aggregator_real32_2d_t) + this%aggregated_data = 0.0_real32 + type is (aggregator_real32_3d_t) + this%aggregated_data = 0.0_real32 + type is (aggregator_real64_0d_t) + this%aggregated_data = 0.0_real64 + type is (aggregator_real64_1d_t) + this%aggregated_data = 0.0_real64 + type is (aggregator_real64_2d_t) + this%aggregated_data = 0.0_real64 + type is (aggregator_real64_3d_t) + this%aggregated_data = 0.0_real64 + end select + + this%counter = 0 + + end subroutine other_reset + + function new_aggregator_int32_0d_t(source_data) result(agg) + integer(kind=int32), intent(inout), target :: source_data + type(aggregator_int32_0d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_int32_0d_t + + function new_aggregator_int32_1d_t(source_data) result(agg) + integer(kind=int32), dimension(:), intent(inout), target :: source_data + type(aggregator_int32_1d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_int32_1d_t + + function new_aggregator_int32_2d_t(source_data) result(agg) + integer(kind=int32), dimension(:,:), intent(inout), target :: source_data + type(aggregator_int32_2d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_int32_2d_t + + function new_aggregator_int32_3d_t(source_data) result(agg) + integer(kind=int32), dimension(:,:,:), intent(inout), target :: source_data + type(aggregator_int32_3d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_int32_3d_t + + function new_aggregator_real32_0d(source_data) result(agg) + real(kind=real32), intent(inout), target :: source_data + type(aggregator_real32_0d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_real32_0d + + function new_aggregator_real32_1d(source_data) result(agg) + real(kind=real32), dimension(:), intent(inout), target :: source_data + type(aggregator_real32_1d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_real32_1d + + function new_aggregator_real32_2d(source_data) result(agg) + real(kind=real32), dimension(:,:), intent(inout), target :: source_data + type(aggregator_real32_2d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_real32_2d + + function new_aggregator_real32_3d(source_data) result(agg) + real(kind=real32), dimension(:,:,:), intent(inout), target :: source_data + type(aggregator_real32_3d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_real32_3d + + function new_aggregator_real64_0d(source_data) result(agg) + real(kind=real64), intent(inout), target :: source_data + type(aggregator_real64_0d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_real64_0d + + function new_aggregator_real64_1d(source_data) result(agg) + real(kind=real64), dimension(:), intent(inout), target :: source_data + type(aggregator_real64_1d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_real64_1d + + function new_aggregator_real64_2d(source_data) result(agg) + real(kind=real64), dimension(:,:), intent(inout), target :: source_data + type(aggregator_real64_2d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_real64_2d + + function new_aggregator_real64_3d(source_data) result(agg) + real(kind=real64), dimension(:,:,:), intent(inout), target :: source_data + type(aggregator_real64_3d_t) :: agg + + agg%source_data => source_data + + end function new_aggregator_real64_3d + +end module From 5926337925c13eeb87b016a2721b7e1446d3bff9 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Mon, 24 Nov 2025 17:27:04 +1100 Subject: [PATCH 02/31] src/offline/cable_define_types.F90: add tscrn_max_daily and tscrn_min_daily aggregators to canopy_type --- src/offline/cable_define_types.F90 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/offline/cable_define_types.F90 b/src/offline/cable_define_types.F90 index df411ca31..c720c918a 100644 --- a/src/offline/cable_define_types.F90 +++ b/src/offline/cable_define_types.F90 @@ -24,7 +24,8 @@ !#define UM_BUILD yes MODULE cable_def_types_mod -USE cable_climate_type_mod, ONLY: climate_type + USE cable_climate_type_mod, ONLY: climate_type + USE aggregator_mod, ONLY: aggregator_real32_1d_t, new_aggregator ! Contains all variables which are not subroutine-internal @@ -531,6 +532,8 @@ MODULE cable_def_types_mod ! vh_js ! !litter thermal conductivity (Wm-2K-1) and vapour diffusivity (m2s-1) REAL(r_2), DIMENSION(:), POINTER :: kthLitt, DvLitt + type(aggregator_real32_1d_t), allocatable :: tscrn_max_daily + type(aggregator_real32_1d_t), allocatable :: tscrn_min_daily END TYPE canopy_type @@ -1186,6 +1189,9 @@ SUBROUTINE alloc_canopy_type(var, mp) ALLOCATE (var % kthLitt(mp)) ALLOCATE (var % DvLitt(mp)) + var%tscrn_max_daily = new_aggregator(source_data=var%tscrn); CALL var%tscrn_max_daily%init(method="max") + var%tscrn_min_daily = new_aggregator(source_data=var%tscrn); CALL var%tscrn_min_daily%init(method="min") + END SUBROUTINE alloc_canopy_type ! ------------------------------------------------------------------------------ @@ -1811,6 +1817,9 @@ SUBROUTINE dealloc_canopy_type(var) DEALLOCATE (var % kthLitt) DEALLOCATE (var % DvLitt) + DEALLOCATE(var%tscrn_max_daily) + DEALLOCATE(var%tscrn_min_daily) + END SUBROUTINE dealloc_canopy_type ! ------------------------------------------------------------------------------ From 81661f9c054715d1fc7af2cb45d85ddcdcce64eb Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Tue, 25 Nov 2025 09:46:51 +1100 Subject: [PATCH 03/31] src/offline/cable_serial.F90: update tscrn_max_daily and tscrn_min_daily aggregators --- src/offline/cable_serial.F90 | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/offline/cable_serial.F90 b/src/offline/cable_serial.F90 index 14aa29449..357cd493c 100644 --- a/src/offline/cable_serial.F90 +++ b/src/offline/cable_serial.F90 @@ -581,6 +581,13 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi IF (l_laiFeedbk.AND.icycle>0) veg%vlai(:) = casamet%glai(:) IF (.NOT. allocated(c1)) ALLOCATE( c1(mp,nrb), rhoch(mp,nrb), xk(mp,nrb) ) + + if (ktau > kstart .and. mod(ktau - kstart, ktauday) == 0) then + ! Reset daily aggregators if previous time step was the end of day + call canopy%tscrn_max_daily%reset() + call canopy%tscrn_min_daily%reset() + end if + ! Call land surface scheme for this timestep, all grid points: CALL cbm( ktau, dels, air, bgc, canopy, met, bal, & rad, rough, soil, ssnow, sum_flux, veg, climate, xk, c1, rhoch ) @@ -595,8 +602,14 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi ssnow%rnof2 = ssnow%rnof2*dels ssnow%runoff = ssnow%runoff*dels + call canopy%tscrn_max_daily%accumulate() + call canopy%tscrn_min_daily%accumulate() - + if (mod(ktau - kstart + 1, ktauday) == 0) then + ! Normalise daily aggregators if current time step is the end of day + call canopy%tscrn_max_daily%normalise() + call canopy%tscrn_min_daily%normalise() + end if ELSE IF ( IS_CASA_TIME("dread", yyyy, ktau, kstart, & From 24a4b812ba8ff09fed9da3caf8206d9ddcc16d09 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Tue, 20 Jan 2026 20:53:14 +1100 Subject: [PATCH 04/31] Introduce cable_enum_t for improved type safety for enum data structures --- CMakeLists.txt | 1 + src/util/cable_enum.F90 | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/util/cable_enum.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index e36730558..edba7814f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,6 +303,7 @@ else() src/util/cable_climate_type_mod.F90 src/util/masks_cbl.F90 src/util/cable_array_utils.F90 + src/util/cable_enum.F90 src/util/netcdf/cable_netcdf_decomp_util.F90 src/util/netcdf/cable_netcdf.F90 src/util/netcdf/cable_netcdf_internal.F90 diff --git a/src/util/cable_enum.F90 b/src/util/cable_enum.F90 new file mode 100644 index 000000000..7bbb3c18d --- /dev/null +++ b/src/util/cable_enum.F90 @@ -0,0 +1,25 @@ +module cable_enum_mod + implicit none + + type :: cable_enum_t + integer :: value + contains + procedure, private :: cable_enum_eq + generic :: operator(==) => cable_enum_eq + procedure, private :: cable_enum_ne + generic :: operator(/=) => cable_enum_ne + end type + +contains + + elemental logical function cable_enum_eq(this, other) + class(cable_enum_t), intent(in) :: this, other + cable_enum_eq = (this%value == other%value) + end function + + elemental logical function cable_enum_ne(this, other) + class(cable_enum_t), intent(in) :: this, other + cable_enum_ne = (this%value /= other%value) + end function + +end module From 3094a2b1e7484d23a5eac241f9e11685ae5810a2 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Wed, 19 Nov 2025 09:38:35 +1100 Subject: [PATCH 05/31] Add parallel I/O output module implementation --- CMakeLists.txt | 7 + src/offline/cable_serial.F90 | 47 +- src/util/cable_grid_reductions.F90 | 282 +++++ src/util/cable_timing_utils.F90 | 60 ++ src/util/io/output/cable_output.F90 | 26 + src/util/io/output/cable_output_core.F90 | 789 ++++++++++++++ .../io/output/cable_output_definitions.F90 | 304 ++++++ src/util/io/output/cable_output_types.F90 | 59 ++ src/util/io/output/cable_output_utils.F90 | 960 ++++++++++++++++++ 9 files changed, 2526 insertions(+), 8 deletions(-) create mode 100644 src/util/cable_grid_reductions.F90 create mode 100644 src/util/cable_timing_utils.F90 create mode 100644 src/util/io/output/cable_output.F90 create mode 100644 src/util/io/output/cable_output_core.F90 create mode 100644 src/util/io/output/cable_output_definitions.F90 create mode 100644 src/util/io/output/cable_output_types.F90 create mode 100644 src/util/io/output/cable_output_utils.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index edba7814f..2f93095f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,11 +304,18 @@ else() src/util/masks_cbl.F90 src/util/cable_array_utils.F90 src/util/cable_enum.F90 + src/util/cable_grid_reductions.F90 + src/util/cable_timing_utils.F90 src/util/netcdf/cable_netcdf_decomp_util.F90 src/util/netcdf/cable_netcdf.F90 src/util/netcdf/cable_netcdf_internal.F90 src/util/netcdf/cable_netcdf_stub_types.F90 src/util/netcdf/nf90/cable_netcdf_nf90.F90 + src/util/io/output/cable_output_core.F90 + src/util/io/output/cable_output_definitions.F90 + src/util/io/output/cable_output_types.F90 + src/util/io/output/cable_output_utils.F90 + src/util/io/output/cable_output.F90 ) target_link_libraries(cable_common PRIVATE PkgConfig::NETCDF) diff --git a/src/offline/cable_serial.F90 b/src/offline/cable_serial.F90 index 357cd493c..b9c925c22 100644 --- a/src/offline/cable_serial.F90 +++ b/src/offline/cable_serial.F90 @@ -85,6 +85,7 @@ MODULE cable_serial patch_type,landpt,& defaultLAI, sdoy, smoy, syear, timeunits, calendar, & NO_CHECK + use cable_io_vars_module, only: patch USE cable_io_decomp_mod, ONLY: io_decomp_t USE cable_io_decomp_mod, ONLY: cable_io_decomp_init USE casa_ncdf_module, ONLY: is_casa_time @@ -112,6 +113,16 @@ MODULE cable_serial ncid_wd,ncid_mask USE cable_output_module, ONLY: create_restart,open_output_file, & write_output,close_output_file + use cable_output_prototype_v2_mod, only: cable_output_mod_init + use cable_output_prototype_v2_mod, only: cable_output_mod_end + use cable_output_prototype_v2_mod, only: cable_output_register_output_variables + use cable_output_prototype_v2_mod, only: cable_output_profiles_init + use cable_output_prototype_v2_mod, only: cable_output_update + use cable_output_prototype_v2_mod, only: cable_output_write + use cable_output_prototype_v2_mod, only: cable_output_write_parameters + use cable_output_prototype_v2_mod, only: cable_output_write_restart + use cable_output_prototype_v2_mod, only: cable_output_core_outputs + use cable_netcdf_mod, only: cable_netcdf_mod_init, cable_netcdf_mod_end USE cable_checks_module, ONLY: constant_check_range USE cable_write_module, ONLY: nullify_write USE cable_IO_vars_module, ONLY: timeunits,calendar @@ -273,10 +284,13 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi real(r_2), dimension(:,:,:), allocatable, save :: patchfrac_new type(io_decomp_t) :: io_decomp + + integer :: start_year ! END header ! INISTUFF + call cable_netcdf_mod_init(mpi_grp) ! outer loop - spinup loop no. ktau_tot : ktau = 0 @@ -462,6 +476,12 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi call cable_io_decomp_init(io_decomp) + if (.not. casaonly) then + call cable_output_mod_init(io_decomp) + call cable_output_register_output_variables(cable_output_core_outputs(canopy, soil)) + call cable_output_profiles_init() + end if + ENDIF ! CALL 1 ! globally (WRT code) accessible kend through USE cable_common_module @@ -572,6 +592,17 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi IF ( CABLE_USER%POPLUC) CALL POPLUC_set_patchfrac(POPLUC,LUC_EXPT) ENDIF + ! TODO(Sean): this is a hack for determining if the current time step + ! is the last of the month. Better way to do this? + IF(ktau == 1) THEN + !MC - use met%year(1) instead of CABLE_USER%YearStart for non-GSWP forcing and leap years + IF ( TRIM(cable_user%MetType) .EQ. '' ) THEN + start_year = met%year(1) + ELSE + start_year = CABLE_USER%YearStart + ENDIF + END IF + IF ( .NOT. CASAONLY ) THEN ! Feedback prognostic vcmax and daily LAI from casaCNP to CABLE @@ -605,13 +636,6 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi call canopy%tscrn_max_daily%accumulate() call canopy%tscrn_min_daily%accumulate() - if (mod(ktau - kstart + 1, ktauday) == 0) then - ! Normalise daily aggregators if current time step is the end of day - call canopy%tscrn_max_daily%normalise() - call canopy%tscrn_min_daily%normalise() - end if - - ELSE IF ( IS_CASA_TIME("dread", yyyy, ktau, kstart, & koffset, kend, ktauday, logn) ) THEN ! CLN READ FROM FILE INSTEAD ! WRITE(CYEAR,FMT="(I4)")CurYear + INT((ktau-kstart+koffset)/(LOY*ktauday)) @@ -735,6 +759,9 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi CALL write_output( dels, ktau, met, canopy, casaflux, casapool, casamet, & ssnow, rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) END SELECT + if (ktau == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) + call cable_output_update(ktau, dels, leaps, start_year, met) + call cable_output_write(ktau, dels, leaps, start_year, met, patch, landpt) ENDIF @@ -1017,16 +1044,20 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi IF ( .NOT. CASAONLY.and. .not. l_landuse ) THEN ! Write restart file if requested: - IF(output%restart) & + IF(output%restart) then CALL create_restart( logn, dels, ktau, soil, veg, ssnow, & canopy, rough, rad, bgc, bal, met ) + call cable_output_write_restart(current_time=ktau * dels) + end if !mpidiff IF (cable_user%CALL_climate) & CALL WRITE_CLIMATE_RESTART_NC ( climate, ktauday ) + call cable_output_mod_end() !--- LN ------------------------------------------[ ENDIF + call cable_netcdf_mod_end() IF ( TRIM(cable_user%MetType) .NE. "gswp" .AND. & diff --git a/src/util/cable_grid_reductions.F90 b/src/util/cable_grid_reductions.F90 new file mode 100644 index 000000000..44ae1d6f9 --- /dev/null +++ b/src/util/cable_grid_reductions.F90 @@ -0,0 +1,282 @@ +module cable_grid_reductions_mod + + use iso_fortran_env, only: int32, real32, real64 + + use cable_io_vars_module, only: patch_type, land_type + + implicit none + private + + public :: grid_cell_average + public :: first_patch_in_grid_cell + + interface first_patch_in_grid_cell + module procedure first_patch_in_grid_cell_int32_1d + module procedure first_patch_in_grid_cell_int32_2d + module procedure first_patch_in_grid_cell_int32_3d + module procedure first_patch_in_grid_cell_real32_1d + module procedure first_patch_in_grid_cell_real32_2d + module procedure first_patch_in_grid_cell_real32_3d + module procedure first_patch_in_grid_cell_real64_1d + module procedure first_patch_in_grid_cell_real64_2d + module procedure first_patch_in_grid_cell_real64_3d + end interface + + interface grid_cell_average + module procedure grid_cell_average_real32_1d + module procedure grid_cell_average_real32_2d + module procedure grid_cell_average_real32_3d + module procedure grid_cell_average_real64_1d + module procedure grid_cell_average_real64_2d + module procedure grid_cell_average_real64_3d + end interface + +contains + + subroutine grid_cell_average_real32_1d(input_array, output_array, patch, landpt) + real(kind=real32), intent(in) :: input_array(:) + real(kind=real32), intent(out) :: output_array(:) + type(patch_type), intent(in) :: patch(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, patch_index + + do land_index = 1, size(output_array) + output_array(land_index) = 0.0_real32 + do patch_index = landpt(land_index)%cstart, landpt(land_index)%cend + output_array(land_index) = output_array(land_index) + & + input_array(patch_index) * patch(patch_index)%frac + end do + end do + + end subroutine + + subroutine grid_cell_average_real32_2d(input_array, output_array, patch, landpt) + real(kind=real32), intent(in) :: input_array(:, :) + real(kind=real32), intent(out) :: output_array(:, :) + type(patch_type), intent(in) :: patch(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, patch_index, j + + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j) = 0.0_real32 + do patch_index = landpt(land_index)%cstart, landpt(land_index)%cend + output_array(land_index, j) = ( & + output_array(land_index, j) + input_array(patch_index, j) * patch(patch_index)%frac & + ) + end do + end do + end do + + end subroutine + + subroutine grid_cell_average_real32_3d(input_array, output_array, patch, landpt) + real(kind=real32), intent(in) :: input_array(:, :, :) + real(kind=real32), intent(out) :: output_array(:, :, :) + type(patch_type), intent(in) :: patch(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, patch_index, j, k + + do k = 1, size(output_array, 3) + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j, k) = 0.0_real32 + do patch_index = landpt(land_index)%cstart, landpt(land_index)%cend + output_array(land_index, j, k) = ( & + output_array(land_index, j, k) + & + input_array(patch_index, j, k) * patch(patch_index)%frac & + ) + end do + end do + end do + end do + + end subroutine + + subroutine grid_cell_average_real64_1d(input_array, output_array, patch, landpt) + real(kind=real64), intent(in) :: input_array(:) + real(kind=real64), intent(out) :: output_array(:) + type(patch_type), intent(in) :: patch(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, patch_index + + do land_index = 1, size(output_array) + output_array(land_index) = 0.0_real64 + do patch_index = landpt(land_index)%cstart, landpt(land_index)%cend + output_array(land_index) = output_array(land_index) + & + input_array(patch_index) * patch(patch_index)%frac + end do + end do + + end subroutine + + subroutine grid_cell_average_real64_2d(input_array, output_array, patch, landpt) + real(kind=real64), intent(in) :: input_array(:, :) + real(kind=real64), intent(out) :: output_array(:, :) + type(patch_type), intent(in) :: patch(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, patch_index, j + + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j) = 0.0_real64 + do patch_index = landpt(land_index)%cstart, landpt(land_index)%cend + output_array(land_index, j) = ( & + output_array(land_index, j) + input_array(patch_index, j) * patch(patch_index)%frac & + ) + end do + end do + end do + + end subroutine + + subroutine grid_cell_average_real64_3d(input_array, output_array, patch, landpt) + real(kind=real64), intent(in) :: input_array(:, :, :) + real(kind=real64), intent(out) :: output_array(:, :, :) + type(patch_type), intent(in) :: patch(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, patch_index, j, k + + do k = 1, size(output_array, 3) + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j, k) = 0.0_real64 + do patch_index = landpt(land_index)%cstart, landpt(land_index)%cend + output_array(land_index, j, k) = ( & + output_array(land_index, j, k) + & + input_array(patch_index, j, k) * patch(patch_index)%frac & + ) + end do + end do + end do + end do + + end subroutine + + subroutine first_patch_in_grid_cell_int32_1d(input_array, output_array, landpt) + integer(kind=int32), intent(in) :: input_array(:) + integer(kind=int32), intent(out) :: output_array(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index + + do land_index = 1, size(output_array) + output_array(land_index) = input_array(landpt(land_index)%cstart) + end do + + end subroutine + + subroutine first_patch_in_grid_cell_int32_2d(input_array, output_array, landpt) + integer(kind=int32), intent(in) :: input_array(:, :) + integer(kind=int32), intent(out) :: output_array(:, :) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, j + + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j) = input_array(landpt(land_index)%cstart, j) + end do + end do + + end subroutine + + subroutine first_patch_in_grid_cell_int32_3d(input_array, output_array, landpt) + integer(kind=int32), intent(in) :: input_array(:, :, :) + integer(kind=int32), intent(out) :: output_array(:, :, :) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, j, k + + do k = 1, size(output_array, 3) + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j, k) = input_array(landpt(land_index)%cstart, j, k) + end do + end do + end do + + end subroutine + + subroutine first_patch_in_grid_cell_real32_1d(input_array, output_array, landpt) + real(kind=real32), intent(in) :: input_array(:) + real(kind=real32), intent(out) :: output_array(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index + + do land_index = 1, size(output_array) + output_array(land_index) = input_array(landpt(land_index)%cstart) + end do + + end subroutine + + subroutine first_patch_in_grid_cell_real32_2d(input_array, output_array, landpt) + real(kind=real32), intent(in) :: input_array(:, :) + real(kind=real32), intent(out) :: output_array(:, :) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, j + + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j) = input_array(landpt(land_index)%cstart, j) + end do + end do + + end subroutine + + subroutine first_patch_in_grid_cell_real32_3d(input_array, output_array, landpt) + real(kind=real32), intent(in) :: input_array(:, :, :) + real(kind=real32), intent(out) :: output_array(:, :, :) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, j, k + + do k = 1, size(output_array, 3) + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j, k) = input_array(landpt(land_index)%cstart, j, k) + end do + end do + end do + + end subroutine + + subroutine first_patch_in_grid_cell_real64_1d(input_array, output_array, landpt) + real(kind=real64), intent(in) :: input_array(:) + real(kind=real64), intent(out) :: output_array(:) + type(land_type), intent(in) :: landpt(:) + integer :: land_index + + do land_index = 1, size(output_array) + output_array(land_index) = input_array(landpt(land_index)%cstart) + end do + + end subroutine + + subroutine first_patch_in_grid_cell_real64_2d(input_array, output_array, landpt) + real(kind=real64), intent(in) :: input_array(:, :) + real(kind=real64), intent(out) :: output_array(:, :) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, j + + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j) = input_array(landpt(land_index)%cstart, j) + end do + end do + + end subroutine + + subroutine first_patch_in_grid_cell_real64_3d(input_array, output_array, landpt) + real(kind=real64), intent(in) :: input_array(:, :, :) + real(kind=real64), intent(out) :: output_array(:, :, :) + type(land_type), intent(in) :: landpt(:) + integer :: land_index, j, k + + do k = 1, size(output_array, 3) + do j = 1, size(output_array, 2) + do land_index = 1, size(output_array, 1) + output_array(land_index, j, k) = input_array(landpt(land_index)%cstart, j, k) + end do + end do + end do + + end subroutine + +end module diff --git a/src/util/cable_timing_utils.F90 b/src/util/cable_timing_utils.F90 new file mode 100644 index 000000000..a3c0353cd --- /dev/null +++ b/src/util/cable_timing_utils.F90 @@ -0,0 +1,60 @@ +module cable_timing_utils_mod + use cable_abort_module, only: cable_abort + use cable_common_module, only: is_leapyear, current_year => CurYear + implicit none + private + + public :: time_step_matches + + integer, parameter :: seconds_per_hour = 3600 + integer, parameter :: hours_per_day = 24 + integer, parameter :: months_in_year = 12 + integer, parameter, dimension(months_in_year) :: & + daysm = [31,28,31,30,31,30,31,31,30,31,30,31], & + daysml = [31,29,31,30,31,30,31,31,30,31,30,31], & + lastday = [31,59,90,120,151,181,212,243,273,304,334,365], & + lastdayl = [31,60,91,121,152,182,213,244,274,305,335,366] + +contains + + function time_step_matches(dels, ktau, frequency, leaps, start_year) result(match) + real, intent(in) :: dels !! Model time step in seconds + integer, intent(in) :: ktau !! Current time step index + character(len=*), intent(in) :: frequency !! Frequency string: 'all', 'daily', 'monthly' + logical, intent(in) :: leaps !! Are we using leap years? + integer, intent(in) :: start_year !! Start year of the simulation + logical :: match + integer :: i + integer :: time_steps_per_day + integer :: last_day_of_month_in_accumulated_days(months_in_year) ! TODO(Sean): better variable name? + + select case (frequency) + ! TODO(Sean): implement case for custom hourly frequencies + case ('all') + match = .true. + case ('daily') + time_steps_per_day = seconds_per_hour * hours_per_day / int(dels) + match = mod(ktau, time_steps_per_day) == 0 + case ('monthly') + ! TODO(Sean): is there a better algorithm for monthly matching that doesn't involve looping over years? + last_day_of_month_in_accumulated_days = 0 + do i = start_year, current_year - 1 + if (leaps .and. is_leapyear(i)) then + last_day_of_month_in_accumulated_days = last_day_of_month_in_accumulated_days + 366 + else + last_day_of_month_in_accumulated_days = last_day_of_month_in_accumulated_days + 365 + end if + end do + if (leaps .and. is_leapyear(current_year)) then + last_day_of_month_in_accumulated_days = last_day_of_month_in_accumulated_days + lastdayl + else + last_day_of_month_in_accumulated_days = last_day_of_month_in_accumulated_days + lastday + end if + match = any(int(real(last_day_of_month_in_accumulated_days) * hours_per_day * seconds_per_hour / dels) == ktau) + case default + call cable_abort('Error: unknown frequency "' // trim(adjustl(frequency)) // '"', __FILE__, __LINE__) + end select + + end function + +end module diff --git a/src/util/io/output/cable_output.F90 b/src/util/io/output/cable_output.F90 new file mode 100644 index 000000000..66396c862 --- /dev/null +++ b/src/util/io/output/cable_output.F90 @@ -0,0 +1,26 @@ +module cable_output_prototype_v2_mod + + use cable_output_core_mod, only: cable_output_mod_init + use cable_output_core_mod, only: cable_output_mod_end + use cable_output_core_mod, only: cable_output_register_output_variables + use cable_output_core_mod, only: cable_output_profiles_init + use cable_output_core_mod, only: cable_output_update + use cable_output_core_mod, only: cable_output_write + use cable_output_core_mod, only: cable_output_write_parameters + use cable_output_core_mod, only: cable_output_write_restart + + use cable_output_types_mod, only: cable_output_variable_t + + use cable_output_definitions_mod, only: cable_output_core_outputs => core_outputs + + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PATCH + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOIL + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SNOW + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_RAD + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PLANTCARBON + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOILCARBON + + implicit none + public + +end module diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 new file mode 100644 index 000000000..d382063d1 --- /dev/null +++ b/src/util/io/output/cable_output_core.F90 @@ -0,0 +1,789 @@ +module cable_output_core_mod + + use iso_fortran_env, only: int32, real32, real64 + + use cable_def_types_mod, only: met_type + + use cable_io_vars_module, only: patch_type + use cable_io_vars_module, only: land_type + use cable_io_vars_module, only: output + use cable_io_vars_module, only: check + use cable_io_vars_module, only: ON_TIMESTEP + use cable_io_vars_module, only: ON_WRITE + + use aggregator_mod, only: aggregator_int32_0d_t + use aggregator_mod, only: aggregator_int32_1d_t + use aggregator_mod, only: aggregator_int32_2d_t + use aggregator_mod, only: aggregator_int32_3d_t + use aggregator_mod, only: aggregator_real32_0d_t + use aggregator_mod, only: aggregator_real32_1d_t + use aggregator_mod, only: aggregator_real32_2d_t + use aggregator_mod, only: aggregator_real32_3d_t + use aggregator_mod, only: aggregator_real64_0d_t + use aggregator_mod, only: aggregator_real64_1d_t + use aggregator_mod, only: aggregator_real64_2d_t + use aggregator_mod, only: aggregator_real64_3d_t + + use cable_netcdf_mod, only: cable_netcdf_decomp_t + use cable_netcdf_mod, only: cable_netcdf_file_t + use cable_netcdf_mod, only: cable_netcdf_create_file + use cable_netcdf_mod, only: CABLE_NETCDF_INT + use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT + use cable_netcdf_mod, only: CABLE_NETCDF_DOUBLE + use cable_netcdf_mod, only: CABLE_NETCDF_IOTYPE_CLASSIC + + use cable_abort_module, only: cable_abort + + use cable_checks_module, only: check_range + + use cable_io_decomp_mod, only: io_decomp_t + + use cable_timing_utils_mod, only: time_step_matches + + use cable_grid_reductions_mod, only: grid_cell_average + use cable_grid_reductions_mod, only: first_patch_in_grid_cell + + use cable_output_types_mod, only: cable_output_variable_t + use cable_output_types_mod, only: cable_output_profile_t + use cable_output_types_mod, only: FILL_VALUE_INT32 + use cable_output_types_mod, only: FILL_VALUE_REAL32 + use cable_output_types_mod, only: FILL_VALUE_REAL64 + + use cable_output_utils_mod, only: init_decomp_pointers + use cable_output_utils_mod, only: allocate_grid_reduction_buffers + use cable_output_utils_mod, only: deallocate_grid_reduction_buffers + use cable_output_utils_mod, only: check_invalid_frequency + use cable_output_utils_mod, only: dim_size + use cable_output_utils_mod, only: define_variables + use cable_output_utils_mod, only: set_global_attributes + use cable_output_utils_mod, only: associate_decomp_int32 + use cable_output_utils_mod, only: associate_decomp_real32 + use cable_output_utils_mod, only: associate_decomp_real64 + use cable_output_utils_mod, only: associate_temp_buffer_int32 + use cable_output_utils_mod, only: associate_temp_buffer_real32 + use cable_output_utils_mod, only: associate_temp_buffer_real64 + + use cable_output_definitions_mod, only: coordinate_variables + + implicit none + private + + public :: cable_output_mod_init + public :: cable_output_mod_end + public :: cable_output_register_output_variables + public :: cable_output_profiles_init + public :: cable_output_update + public :: cable_output_write + public :: cable_output_write_parameters + public :: cable_output_write_restart + + type(cable_output_profile_t), allocatable :: global_profile + + type(cable_output_variable_t), allocatable :: registered_output_variables(:) + +contains + + subroutine cable_output_mod_init(io_decomp) + class(io_decomp_t), intent(in) :: io_decomp + class(cable_netcdf_file_t), allocatable :: output_file + + call init_decomp_pointers(io_decomp) + call allocate_grid_reduction_buffers() + + end subroutine + + subroutine cable_output_register_output_variables(output_variables) + type(cable_output_variable_t), dimension(:), intent(in) :: output_variables + integer :: i + + do i = 1, size(output_variables) + associate(output_var => output_variables(i)) + if (all(output_var%reduction_method /= ["none", "grid_cell_average", "first_patch_in_grid_cell"])) then + call cable_abort("Invalid reduction method for variable " // trim(output_var%name), __FILE__, __LINE__) + end if + if (all(output_var%aggregation_method /= ["point", "mean", "max", "min", "sum"])) then + call cable_abort("Invalid aggregation method for variable " // trim(output_var%name), __FILE__, __LINE__) + end if + if (all(output_var%var_type /= [CABLE_NETCDF_INT, CABLE_NETCDF_FLOAT, CABLE_NETCDF_DOUBLE])) then + call cable_abort("Invalid variable type for variable " // trim(output_var%name), __FILE__, __LINE__) + end if + if (count(output_var%name == output_variables(:)%name) > 1) then + call cable_abort("Duplicate variable name found: " // trim(output_var%name), __FILE__, __LINE__) + end if + if (( & + .not. allocated(output_var%data_shape) .and. output_var%aggregator%rank() /= 0 & + ) .or. ( & + allocated(output_var%data_shape) .and. any(dim_size(output_var%data_shape) /= output_var%aggregator%shape()) & + )) then + call cable_abort("Data shape does not match aggregator shape for variable " // trim(output_var%name), __FILE__, __LINE__) + end if + if (output_var%reduction_method /= "none" .and. .not. output_var%distributed) then + call cable_abort("Grid cell reductions require distributed output for variable " // trim(output_var%name), __FILE__, __LINE__) + end if + end associate + end do + + registered_output_variables = output_variables + + end subroutine cable_output_register_output_variables + + subroutine cable_output_mod_end() + + if (allocated(global_profile%output_file)) call global_profile%output_file%close() + + deallocate(global_profile) + + call deallocate_grid_reduction_buffers() + + end subroutine + + subroutine cable_output_profiles_init() + class(cable_netcdf_file_t), allocatable :: output_file + integer :: i + + allocate(cable_output_profile_t::global_profile) + + global_profile%sampling_frequency = output%averaging + + global_profile%file_name = "test_output.nc" ! TODO(Sean): use filename from namelist + + global_profile%output_variables = [ & + coordinate_variables(), & + pack(registered_output_variables, registered_output_variables(:)%active) & + ] + + do i = 1, size(global_profile%output_variables) + associate(output_var => global_profile%output_variables(i)) + call check_invalid_frequency( & + sampling_frequency=global_profile%sampling_frequency, & + accumulation_frequency=output_var%accumulation_frequency, & + var_name=output_var%name, & + file_name=global_profile%file_name & + ) + end associate + end do + + global_profile%output_file = cable_netcdf_create_file(global_profile%file_name, iotype=CABLE_NETCDF_IOTYPE_CLASSIC) + + call define_variables(global_profile%output_file, global_profile%output_variables) + + call set_global_attributes(global_profile) + + call global_profile%output_file%end_def() + + do i = 1, size(global_profile%output_variables) + associate(output_variable => global_profile%output_variables(i)) + call output_variable%aggregator%init(method=output_variable%aggregation_method) + end associate + end do + + end subroutine + + subroutine cable_output_write_parameters(time_index, patch, landpt, met) + integer, intent(in) :: time_index + type(patch_type), intent(in) :: patch(:) + type(land_type), intent(in) :: landpt(:) + type(met_type), intent(in) :: met + + integer :: i + + do i = 1, size(global_profile%output_variables) + associate(output_variable => global_profile%output_variables(i)) + if (.not. output_variable%parameter) cycle + call check_variable_range(output_variable, time_index, met) + call output_variable%aggregator%accumulate() + call write_variable(output_variable, global_profile%output_file, patch, landpt) + call output_variable%aggregator%reset() + end associate + end do + + end subroutine cable_output_write_parameters + + subroutine cable_output_update(time_index, dels, leaps, start_year, met) + integer, intent(in) :: time_index + real, intent(in) :: dels + logical, intent(in) :: leaps + integer, intent(in) :: start_year + type(met_type), intent(in) :: met + + real :: current_time + integer :: i + + if (check%ranges == ON_TIMESTEP) then + do i = 1, size(global_profile%output_variables) + call check_variable_range(global_profile%output_variables(i), time_index, met) + end do + end if + + do i = 1, size(global_profile%output_variables) + associate(output_variable => global_profile%output_variables(i)) + if (time_step_matches(dels, time_index, output_variable%accumulation_frequency, leaps, start_year)) then + call output_variable%aggregator%accumulate() + end if + end associate + end do + + end subroutine cable_output_update + + subroutine cable_output_write(time_index, dels, leaps, start_year, met, patch, landpt) + integer, intent(in) :: time_index + real, intent(in) :: dels + logical, intent(in) :: leaps + integer, intent(in) :: start_year + type(met_type), intent(in) :: met + type(patch_type), intent(in) :: patch(:) + type(land_type), intent(in) :: landpt(:) + + real :: current_time + integer :: i + + if (time_step_matches(dels, time_index, global_profile%sampling_frequency, leaps, start_year)) then + + do i = 1, size(global_profile%output_variables) + associate(output_variable => global_profile%output_variables(i)) + if (output_variable%parameter) cycle + if (check%ranges == ON_WRITE) call check_variable_range(output_variable, time_index, met) + call write_variable(output_variable, global_profile%output_file, patch, landpt, global_profile%frame + 1) + call output_variable%aggregator%reset() + end associate + end do + + current_time = time_index * dels + call global_profile%output_file%put_var("time", (current_time + global_profile%previous_write_time) / 2.0, start=[global_profile%frame + 1]) + call global_profile%output_file%put_var("time_bnds", [global_profile%previous_write_time, current_time], start=[1, global_profile%frame + 1]) + global_profile%previous_write_time = current_time + global_profile%frame = global_profile%frame + 1 + + end if + + end subroutine cable_output_write + + subroutine cable_output_write_restart(current_time) + real, intent(in) :: current_time !! Current simulation time + class(cable_netcdf_file_t), allocatable :: restart_output_file + + type(cable_output_variable_t), allocatable :: restart_variables(:) + integer :: i + + restart_variables = [ & + coordinate_variables(restart=.true.), & + pack(registered_output_variables, registered_output_variables(:)%restart) & + ] + + restart_output_file = cable_netcdf_create_file("test_restart.nc", iotype=CABLE_NETCDF_IOTYPE_CLASSIC) ! TODO(Sean): use filename from namelist + + call define_variables(restart_output_file, restart_variables, restart=.true.) + + call restart_output_file%end_def() + + call restart_output_file%put_var("time", [current_time]) + + do i = 1, size(restart_variables) + call write_variable(restart_variables(i), restart_output_file, restart=.true.) + end do + + call restart_output_file%close() + + end subroutine cable_output_write_restart + + subroutine check_variable_range(output_variable, time_index, met) + type(cable_output_variable_t), intent(in) :: output_variable + integer, intent(in) :: time_index + type(met_type), intent(in) :: met + + select type (aggregator => output_variable%aggregator) + type is (aggregator_int32_0d_t) + ! TODO(Sean): implement range checking for integer types + type is (aggregator_int32_1d_t) + ! TODO(Sean): implement range checking for integer types + type is (aggregator_int32_2d_t) + ! TODO(Sean): implement range checking for integer types + type is (aggregator_int32_3d_t) + ! TODO(Sean): implement range checking for integer types + type is (aggregator_real32_0d_t) + ! TODO(Sean): implement range checking for scalars + type is (aggregator_real32_1d_t) + call check_range(output_variable%name, aggregator%source_data, output_variable%range, time_index, met) + type is (aggregator_real32_2d_t) + call check_range(output_variable%name, aggregator%source_data, output_variable%range, time_index, met) + type is (aggregator_real32_3d_t) + call check_range(output_variable%name, aggregator%source_data, output_variable%range, time_index, met) + type is (aggregator_real64_0d_t) + ! TODO(Sean): implement range checking for double precision types + type is (aggregator_real64_1d_t) + ! TODO(Sean): implement range checking for double precision types + type is (aggregator_real64_2d_t) + ! TODO(Sean): implement range checking for double precision types + type is (aggregator_real64_3d_t) + ! TODO(Sean): implement range checking for double precision types + class default + call cable_abort("Unexpected aggregator type", __FILE__, __LINE__) + end select + + end subroutine check_variable_range + + subroutine write_variable(output_variable, output_file, patch, landpt, frame, restart) + type(cable_output_variable_t), intent(inout), target :: output_variable + class(cable_netcdf_file_t), intent(inout) :: output_file + type(patch_type), intent(in), optional :: patch(:) + type(land_type), intent(in), optional :: landpt(:) + integer, intent(in), optional :: frame + logical, intent(in), optional :: restart + + class(cable_netcdf_decomp_t), pointer :: decomp => null() + integer :: i, ndims + logical :: restart_local + + integer(kind=int32), pointer :: write_buffer_int32_0d => null() + integer(kind=int32), pointer :: write_buffer_int32_1d(:) => null() + integer(kind=int32), pointer :: write_buffer_int32_2d(:, :) => null() + integer(kind=int32), pointer :: write_buffer_int32_3d(:, :, :) => null() + real(kind=real32), pointer :: write_buffer_real32_0d => null() + real(kind=real32), pointer :: write_buffer_real32_1d(:) => null() + real(kind=real32), pointer :: write_buffer_real32_2d(:, :) => null() + real(kind=real32), pointer :: write_buffer_real32_3d(:, :, :) => null() + real(kind=real64), pointer :: write_buffer_real64_0d => null() + real(kind=real64), pointer :: write_buffer_real64_1d(:) => null() + real(kind=real64), pointer :: write_buffer_real64_2d(:, :) => null() + real(kind=real64), pointer :: write_buffer_real64_3d(:, :, :) => null() + + integer(kind=int32), pointer :: temp_buffer_int32_1d(:) => null() + integer(kind=int32), pointer :: temp_buffer_int32_2d(:, :) => null() + integer(kind=int32), pointer :: temp_buffer_int32_3d(:, :, :) => null() + real(kind=real32), pointer :: temp_buffer_real32_1d(:) => null() + real(kind=real32), pointer :: temp_buffer_real32_2d(:, :) => null() + real(kind=real32), pointer :: temp_buffer_real32_3d(:, :, :) => null() + real(kind=real64), pointer :: temp_buffer_real64_1d(:) => null() + real(kind=real64), pointer :: temp_buffer_real64_2d(:, :) => null() + real(kind=real64), pointer :: temp_buffer_real64_3d(:, :, :) => null() + + restart_local = .false. + if (present(restart)) restart_local = restart + + if (.not. restart_local .and. output_variable%reduction_method /= "none") then + if (.not. present(patch) .or. .not. present(landpt)) then + call cable_abort("Optional arguments patch and landpt must be present for grid reductions", __FILE__, __LINE__) + end if + end if + + select type (aggregator => output_variable%aggregator) + type is (aggregator_int32_0d_t) + if (output_variable%reduction_method /= "none") then + call cable_abort("Grid cell reductions are not supported for scalar variables", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call cable_abort("Distributed writes are not supported for scalar variables", __FILE__, __LINE__) + end if + write_buffer_int32_0d => aggregator%aggregated_data + if (restart_local) write_buffer_int32_0d => aggregator%source_data + if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_int32_0d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_int32_0d) + end if + type is (aggregator_int32_1d_t) + if (restart_local) then + write_buffer_int32_1d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_int32_1d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call cable_abort("Reduction method grid_cell_average is not supported for integer variables", __FILE__, __LINE__) + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_int32(output_variable, temp_buffer_int32_1d=temp_buffer_int32_1d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_int32_1d, & + landpt=landpt) + write_buffer_int32_1d => temp_buffer_int32_1d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_int32(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_int32_1d, & + decomp=decomp, & + fill_value=FILL_VALUE_INT32, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_int32_1d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_int32_1d) + end if + type is (aggregator_int32_2d_t) + if (restart_local) then + write_buffer_int32_2d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_int32_2d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call cable_abort("Reduction method grid_cell_average is not supported for integer variables", __FILE__, __LINE__) + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_int32(output_variable, temp_buffer_int32_2d=temp_buffer_int32_2d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_int32_2d, & + landpt=landpt) + write_buffer_int32_2d => temp_buffer_int32_2d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_int32(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_int32_2d, & + decomp=decomp, & + fill_value=FILL_VALUE_INT32, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_int32_2d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_int32_2d) + end if + type is (aggregator_int32_3d_t) + if (restart_local) then + write_buffer_int32_3d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_int32_3d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call cable_abort("Reduction method grid_cell_average is not supported for integer variables", __FILE__, __LINE__) + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_int32(output_variable, temp_buffer_int32_3d=temp_buffer_int32_3d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_int32_3d, & + landpt=landpt) + write_buffer_int32_3d => temp_buffer_int32_3d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_int32(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_int32_3d, & + decomp=decomp, & + fill_value=FILL_VALUE_INT32, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_int32_3d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_int32_3d) + end if + type is (aggregator_real32_0d_t) + if (output_variable%reduction_method /= "none") then + call cable_abort("Grid cell reductions are not supported for scalar variables", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call cable_abort("Distributed writes are not supported for scalar variables", __FILE__, __LINE__) + end if + write_buffer_real32_0d => aggregator%aggregated_data + if (restart_local) write_buffer_real32_0d => aggregator%source_data + if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real32_0d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real32_0d) + end if + type is (aggregator_real32_1d_t) + if (restart_local) then + write_buffer_real32_1d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_real32_1d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_1d=temp_buffer_real32_1d) + call grid_cell_average( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real32_1d, & + landpt=landpt, & + patch=patch) + write_buffer_real32_1d => temp_buffer_real32_1d + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_1d=temp_buffer_real32_1d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real32_1d, & + landpt=landpt) + write_buffer_real32_1d => temp_buffer_real32_1d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_real32(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_real32_1d, & + decomp=decomp, & + fill_value=FILL_VALUE_REAL32, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real32_1d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real32_1d) + end if + type is (aggregator_real32_2d_t) + if (restart_local) then + write_buffer_real32_2d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_real32_2d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_2d=temp_buffer_real32_2d) + call grid_cell_average( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real32_2d, & + landpt=landpt, & + patch=patch) + write_buffer_real32_2d => temp_buffer_real32_2d + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_2d=temp_buffer_real32_2d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real32_2d, & + landpt=landpt) + write_buffer_real32_2d => temp_buffer_real32_2d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_real32(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_real32_2d, & + decomp=decomp, & + fill_value=FILL_VALUE_REAL32, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real32_2d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real32_2d) + end if + type is (aggregator_real32_3d_t) + if (restart_local) then + write_buffer_real32_3d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_real32_3d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_3d=temp_buffer_real32_3d) + call grid_cell_average( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real32_3d, & + landpt=landpt, & + patch=patch) + write_buffer_real32_3d => temp_buffer_real32_3d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_real32(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_real32_3d, & + decomp=decomp, & + fill_value=FILL_VALUE_REAL32, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real32_3d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real32_3d) + end if + type is (aggregator_real64_0d_t) + if (output_variable%reduction_method /= "none") then + call cable_abort("Grid cell reductions are not supported for scalar variables", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call cable_abort("Distributed writes are not supported for scalar variables", __FILE__, __LINE__) + end if + write_buffer_real64_0d => aggregator%aggregated_data + if (restart_local) write_buffer_real64_0d => aggregator%source_data + if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real64_0d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real64_0d) + end if + type is (aggregator_real64_1d_t) + if (restart_local) then + write_buffer_real64_1d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_real64_1d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_1d=temp_buffer_real64_1d) + call grid_cell_average( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real64_1d, & + landpt=landpt, & + patch=patch) + write_buffer_real64_1d => temp_buffer_real64_1d + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_1d=temp_buffer_real64_1d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real64_1d, & + landpt=landpt) + write_buffer_real64_1d => temp_buffer_real64_1d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_real64(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_real64_1d, & + decomp=decomp, & + fill_value=FILL_VALUE_REAL64, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real64_1d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real64_1d) + end if + type is (aggregator_real64_2d_t) + if (restart_local) then + write_buffer_real64_2d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_real64_2d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_2d=temp_buffer_real64_2d) + call grid_cell_average( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real64_2d, & + landpt=landpt, & + patch=patch) + write_buffer_real64_2d => temp_buffer_real64_2d + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_2d=temp_buffer_real64_2d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real64_2d, & + landpt=landpt) + write_buffer_real64_2d => temp_buffer_real64_2d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_real64(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_real64_2d, & + decomp=decomp, & + fill_value=FILL_VALUE_REAL64, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real64_2d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real64_2d) + end if + type is (aggregator_real64_3d_t) + if (restart_local) then + write_buffer_real64_3d => aggregator%source_data + else if (output_variable%reduction_method == "none") then + write_buffer_real64_3d => aggregator%aggregated_data + else if (output_variable%reduction_method == "grid_cell_average") then + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_3d=temp_buffer_real64_3d) + call grid_cell_average( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real64_3d, & + landpt=landpt, & + patch=patch) + write_buffer_real64_3d => temp_buffer_real64_3d + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_3d=temp_buffer_real64_3d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=temp_buffer_real64_3d, & + landpt=landpt) + write_buffer_real64_3d => temp_buffer_real64_3d + else + call cable_abort("Invalid reduction method", __FILE__, __LINE__) + end if + if (output_variable%distributed) then + call associate_decomp_real64(output_variable, decomp, restart=restart_local) + call output_file%write_darray( & + var_name=output_variable%name, & + values=write_buffer_real64_3d, & + decomp=decomp, & + fill_value=FILL_VALUE_REAL64, & + frame=frame) + else if (present(frame)) then + call output_file%inq_var_ndims(output_variable%name, ndims) + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real64_3d, & + start=[(1, i = 1, ndims - 1), frame]) + else + call output_file%put_var( & + var_name=output_variable%name, & + values=write_buffer_real64_3d) + end if + class default + call cable_abort("Unexpected aggregator type", __FILE__, __LINE__) + end select + + end subroutine write_variable + +end module diff --git a/src/util/io/output/cable_output_definitions.F90 b/src/util/io/output/cable_output_definitions.F90 new file mode 100644 index 000000000..ca4b2875c --- /dev/null +++ b/src/util/io/output/cable_output_definitions.F90 @@ -0,0 +1,304 @@ +module cable_output_definitions_mod + use cable_def_types_mod, only: canopy_type + use cable_def_types_mod, only: soil_parameter_type + use cable_def_types_mod, only: mvtype, mstype + + use cable_netcdf_mod, only: CABLE_NETCDF_INT + use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT + + use aggregator_mod, only: new_aggregator + + use cable_io_vars_module, only: output, patchout + use cable_io_vars_module, only: landpt_global + use cable_io_vars_module, only: patch + use cable_io_vars_module, only: lat_all, lon_all + use cable_io_vars_module, only: latitude, longitude + use cable_io_vars_module, only: metgrid + + use cable_output_types_mod, only: cable_output_variable_t + use cable_output_types_mod, only: DIM_PATCH => CABLE_OUTPUT_DIM_PATCH + use cable_output_types_mod, only: DIM_SOIL => CABLE_OUTPUT_DIM_SOIL + use cable_output_types_mod, only: DIM_RAD => CABLE_OUTPUT_DIM_RAD + use cable_output_types_mod, only: DIM_LAND => CABLE_OUTPUT_DIM_LAND + use cable_output_types_mod, only: DIM_LAND_GLOBAL => CABLE_OUTPUT_DIM_LAND_GLOBAL + use cable_output_types_mod, only: DIM_X => CABLE_OUTPUT_DIM_X + use cable_output_types_mod, only: DIM_Y => CABLE_OUTPUT_DIM_Y + + use cable_output_utils_mod, only: requires_x_y_output_grid + use cable_output_utils_mod, only: requires_land_output_grid + + use cable_checks_module, only: ranges + + use cable_abort_module, only: cable_abort + + implicit none + private + + public :: core_outputs + public :: coordinate_variables + +contains + + function core_outputs(canopy, soil) result(output_variables) + type(canopy_type), intent(inout) :: canopy + type(soil_parameter_type), intent(in) :: soil + + type(cable_output_variable_t), allocatable :: output_variables(:) + + output_variables = [ & + cable_output_variable_t( & + name="isoil", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_INT, & + units="-", & + long_name="Soil type", & + range=ranges%isoil, & + active=output%isoil, & + patchout=output%patch .or. patchout%isoil, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + restart=.true., & + aggregator=new_aggregator(soil%isoilm) & + ), & + cable_output_variable_t( & + name="swilt", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + units="1", & + long_name="", & + range=ranges%swilt, & + active=output%swilt, & + patchout=output%patch .or. patchout%swilt, & + reduction_method="grid_cell_average", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%swilt) & + ), & + cable_output_variable_t( & + name="albsoil", & + data_shape=[DIM_PATCH, DIM_RAD], & + var_type=CABLE_NETCDF_FLOAT, & + units="1", & + long_name="", & + range=ranges%albsoil, & + active=output%albsoil, & + patchout=output%patch .or. patchout%albsoil, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%albsoil) & + ), & + cable_output_variable_t( & + name="Qh", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + units="W/m^2", & + long_name="Surface sensible heat flux", & + range=ranges%Qh, & + active=output%Qh, & + patchout=output%patch .or. patchout%Qh, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fh) & + ), & + cable_output_variable_t( & + name="Tmx", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + units="oC", & + long_name="averaged daily maximum screen-level T", & + active=output%Tex .and. output%averaging == "monthly", & + patchout=output%patch .or. patchout%Tex, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + range=ranges%Tscrn, & + accumulation_frequency="daily", & + aggregator=new_aggregator(canopy%tscrn_max_daily%aggregated_data) & + ), & + cable_output_variable_t( & + name="nap", & + data_shape=[DIM_LAND_GLOBAL], & + var_type=CABLE_NETCDF_FLOAT, & + units="", & + long_name="", & + range=[-huge(0.0), huge(0.0)], & + active=.false., & + restart=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(landpt_global(:)%nap) & + ), & + cable_output_variable_t( & + name="patchfrac", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + units="", & + long_name="Fraction of vegetated grid cell area occupied by a vegetation/soil patch", & + range=[0.0, 1.0], & + active=.false., & + restart=.true., & + distributed=.true., & + aggregation_method="point", & + aggregator=new_aggregator(patch(:)%frac) & + ), & + cable_output_variable_t( & + name="mvtype", & + var_type=CABLE_NETCDF_FLOAT, & + units="", & + long_name="Number of vegetation types", & + range=[-huge(0.0), huge(0.0)], & + active=.false., & + restart=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(mvtype) & + ), & + cable_output_variable_t( & + name="mstype", & + var_type=CABLE_NETCDF_FLOAT, & + units="", & + long_name="Number of soil types", & + range=[-huge(0.0), huge(0.0)], & + active=.false., & + restart=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(mstype) & + ) & + ] + + end function core_outputs + + function coordinate_variables(restart) result(output_variables) + logical, intent(in), optional :: restart + + type(cable_output_variable_t), allocatable :: output_variables(:) + + if (present(restart)) then; if (restart) then + output_variables = [ & + cable_output_variable_t( & + name="latitude", & + data_shape=[DIM_LAND_GLOBAL], & + var_type=CABLE_NETCDF_FLOAT, & + units="degrees_north", & + long_name="", & + range=[-90.0, 90.0], & + active=.false., & + restart=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(latitude) & + ), & + cable_output_variable_t( & + name="longitude", & + data_shape=[DIM_LAND_GLOBAL], & + var_type=CABLE_NETCDF_FLOAT, & + units="degrees_east", & + long_name="", & + range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? + active=.false., & + restart=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(longitude) & + ) & + ] + return + end if; end if + + if (requires_x_y_output_grid(output%grid, metgrid)) then + output_variables = [ & + ! Define latitude and longitude variable (ALMA): + cable_output_variable_t( & + name="latitude", & + data_shape=[DIM_X, DIM_Y], & + var_type=CABLE_NETCDF_FLOAT, & + units="degrees_north", & + long_name="", & + range=[-90.0, 90.0], & + active=.true., & + parameter=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(lat_all) & + ), & + cable_output_variable_t( & + name="longitude", & + data_shape=[DIM_X, DIM_Y], & + var_type=CABLE_NETCDF_FLOAT, & + units="degrees_east", & + long_name="", & + range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? + active=.true., & + parameter=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(lon_all) & + ), & + ! Write "cordinate variables" to enable reading by GrADS: + cable_output_variable_t( & + name="x", & + data_shape=[DIM_X], & + var_type=CABLE_NETCDF_FLOAT, & + units="degrees_east", & + long_name="", & + ! comment="x coordinate variable for GrADS compatibility", & + range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? + active=.true., & + parameter=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(lon_all(:, 1)) & + ), & + cable_output_variable_t( & + name="y", & + data_shape=[DIM_Y], & + var_type=CABLE_NETCDF_FLOAT, & + units="degrees_north", & + long_name="", & + ! comment="y coordinate variable for GrADS compatibility", & + range=[-90.0, 90.0], & ! TODO(Sean): this depends on the met forcing input? + active=.true., & + parameter=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(lat_all(1, :)) & + ) & + ] + else if (requires_land_output_grid(output%grid, metgrid)) then + output_variables = [ & + cable_output_variable_t( & + name="local_lat", & + data_shape=[DIM_LAND_GLOBAL], & + var_type=CABLE_NETCDF_FLOAT, & + units="degrees_north", & + long_name="", & + range=[-90.0, 90.0], & + active=requires_land_output_grid(output%grid, metgrid), & + parameter=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(latitude) & + ), & + cable_output_variable_t( & + name="local_lon", & + data_shape=[DIM_LAND_GLOBAL], & + var_type=CABLE_NETCDF_FLOAT, & + units="degrees_east", & + long_name="", & + range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? + active=requires_land_output_grid(output%grid, metgrid), & + parameter=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(longitude) & + ) & + ] + else + call cable_abort("Unable to determine coordinate variables for output grid.", __FILE__, __LINE__) + end if + + end function + +end module diff --git a/src/util/io/output/cable_output_types.F90 b/src/util/io/output/cable_output_types.F90 new file mode 100644 index 000000000..875776a58 --- /dev/null +++ b/src/util/io/output/cable_output_types.F90 @@ -0,0 +1,59 @@ +module cable_output_types_mod + + use iso_fortran_env, only: int32, real32, real64 + + use aggregator_mod, only: aggregator_t + + use cable_netcdf_mod, only: cable_netcdf_file_t + + use cable_enum_mod, only: cable_enum_t + + implicit none + private + + type, extends(cable_enum_t), public :: cable_output_dim_t + end type + + type, public :: cable_output_variable_t + character(len=64) :: name + character(len=64) :: units + character(len=64) :: accumulation_frequency = "all" + character(len=64) :: reduction_method = "none" + character(len=64) :: aggregation_method + character(len=256) :: long_name + logical :: active + logical :: parameter = .false. + logical :: distributed = .true. + logical :: restart = .false. + logical :: patchout = .false. + integer :: var_type + real, dimension(2) :: range + type(cable_output_dim_t), allocatable :: data_shape(:) + class(aggregator_t), allocatable :: aggregator + end type + + type, public :: cable_output_profile_t + real :: previous_write_time = 0.0 + integer :: frame = 0 + character(len=64) :: sampling_frequency + character(len=256) :: file_name + class(cable_netcdf_file_t), allocatable :: output_file + type(cable_output_variable_t), allocatable :: output_variables(:) + end type + + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_PATCH = cable_output_dim_t(0) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_SOIL = cable_output_dim_t(1) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_SNOW = cable_output_dim_t(2) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_RAD = cable_output_dim_t(3) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_PLANTCARBON = cable_output_dim_t(4) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_SOILCARBON = cable_output_dim_t(5) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_LAND = cable_output_dim_t(6) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_LAND_GLOBAL = cable_output_dim_t(7) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_X = cable_output_dim_t(8) + type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_Y = cable_output_dim_t(9) + + integer(kind=int32), parameter, public :: FILL_VALUE_INT32 = -9999999_int32 + real(kind=real32), parameter, public :: FILL_VALUE_REAL32 = -1.0e+33_real32 + real(kind=real64), parameter, public :: FILL_VALUE_REAL64 = -1.0e+33_real64 + +end module diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 new file mode 100644 index 000000000..8a4dd33d2 --- /dev/null +++ b/src/util/io/output/cable_output_utils.F90 @@ -0,0 +1,960 @@ +module cable_output_utils_mod + + use iso_fortran_env, only: int32, real32, real64 + + use cable_common_module, only: filename + + use cable_def_types_mod, only: mp + use cable_def_types_mod, only: mp_global + use cable_def_types_mod, only: mland + use cable_def_types_mod, only: mland_global + use cable_def_types_mod, only: ms + use cable_def_types_mod, only: msn + use cable_def_types_mod, only: nrb + use cable_def_types_mod, only: ncs + use cable_def_types_mod, only: ncp + + use cable_io_vars_module, only: output + use cable_io_vars_module, only: metgrid + use cable_io_vars_module, only: xdimsize + use cable_io_vars_module, only: ydimsize + use cable_io_vars_module, only: max_vegpatches + use cable_io_vars_module, only: timeunits + use cable_io_vars_module, only: time_coord + use cable_io_vars_module, only: calendar + + use cable_abort_module, only: cable_abort + + use cable_netcdf_mod, only: cable_netcdf_decomp_t + use cable_netcdf_mod, only: cable_netcdf_file_t + use cable_netcdf_mod, only: MAX_LEN_DIM => CABLE_NETCDF_MAX_STR_LEN_DIM + use cable_netcdf_mod, only: CABLE_NETCDF_UNLIMITED + use cable_netcdf_mod, only: CABLE_NETCDF_INT + use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT + use cable_netcdf_mod, only: CABLE_NETCDF_DOUBLE + + use cable_io_decomp_mod, only: io_decomp_t + + use cable_output_types_mod, only: cable_output_dim_t + use cable_output_types_mod, only: cable_output_variable_t + use cable_output_types_mod, only: cable_output_profile_t + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PATCH + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOIL + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SNOW + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_RAD + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PLANTCARBON + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOILCARBON + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_LAND + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_LAND_GLOBAL + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_X + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_Y + use cable_output_types_mod, only: FILL_VALUE_INT32 + use cable_output_types_mod, only: FILL_VALUE_REAL32 + use cable_output_types_mod, only: FILL_VALUE_REAL64 + + implicit none + private + + public :: init_decomp_pointers + public :: allocate_grid_reduction_buffers + public :: deallocate_grid_reduction_buffers + public :: requires_x_y_output_grid + public :: requires_land_output_grid + public :: check_invalid_frequency + public :: dim_size + public :: infer_dim_names + public :: define_variables + public :: set_global_attributes + public :: associate_decomp_int32 + public :: associate_decomp_real32 + public :: associate_decomp_real64 + public :: associate_temp_buffer_int32 + public :: associate_temp_buffer_real32 + public :: associate_temp_buffer_real64 + + ! Decomposition pointers for each variable class and data type + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soil_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soil_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soil_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_snow_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_snow_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_snow_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_rad_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_rad_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_rad_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_plantcarbon_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_plantcarbon_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_plantcarbon_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soilcarbon_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soilcarbon_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soilcarbon_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soil_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soil_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soil_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_snow_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_snow_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_snow_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_rad_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_rad_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_rad_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_plantcarbon_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_plantcarbon_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_plantcarbon_real64 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soilcarbon_int32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soilcarbon_real32 + class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soilcarbon_real64 + + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_int32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_real32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_real64 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soil_int32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soil_real32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soil_real64 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_snow_int32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_snow_real32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_snow_real64 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_rad_int32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_rad_real32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_rad_real64 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_plantcarbon_int32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_plantcarbon_real32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_plantcarbon_real64 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soilcarbon_int32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soilcarbon_real32 + class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soilcarbon_real64 + + ! Temporary buffers for computing grid-cell averages for each variable class + integer(kind=int32), allocatable, target :: temp_buffer_land_int32(:) + real(kind=real32), allocatable, target :: temp_buffer_land_real32(:) + real(kind=real64), allocatable, target :: temp_buffer_land_real64(:) + integer(kind=int32), allocatable, target :: temp_buffer_land_soil_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_soil_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_soil_real64(:, :) + integer(kind=int32), allocatable, target :: temp_buffer_land_snow_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_snow_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_snow_real64(:, :) + integer(kind=int32), allocatable, target :: temp_buffer_land_rad_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_rad_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_rad_real64(:, :) + integer(kind=int32), allocatable, target :: temp_buffer_land_plantcarbon_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_plantcarbon_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_plantcarbon_real64(:, :) + integer(kind=int32), allocatable, target :: temp_buffer_land_soilcarbon_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_soilcarbon_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_soilcarbon_real64(:, :) + + type(io_decomp_t) :: io_decomp_global + +contains + + subroutine init_decomp_pointers(io_decomp) + type(io_decomp_t), intent(in), target :: io_decomp + + if (requires_x_y_output_grid(output%grid, metGrid)) then + output_decomp_base_int32 => io_decomp%land_to_x_y_int32 + output_decomp_base_real32 => io_decomp%land_to_x_y_real32 + output_decomp_base_real64 => io_decomp%land_to_x_y_real64 + output_decomp_base_soil_int32 => io_decomp%land_soil_to_x_y_soil_int32 + output_decomp_base_soil_real32 => io_decomp%land_soil_to_x_y_soil_real32 + output_decomp_base_soil_real64 => io_decomp%land_soil_to_x_y_soil_real64 + output_decomp_base_snow_int32 => io_decomp%land_snow_to_x_y_snow_int32 + output_decomp_base_snow_real32 => io_decomp%land_snow_to_x_y_snow_real32 + output_decomp_base_snow_real64 => io_decomp%land_snow_to_x_y_snow_real64 + output_decomp_base_rad_int32 => io_decomp%land_rad_to_x_y_rad_int32 + output_decomp_base_rad_real32 => io_decomp%land_rad_to_x_y_rad_real32 + output_decomp_base_rad_real64 => io_decomp%land_rad_to_x_y_rad_real64 + output_decomp_base_plantcarbon_int32 => io_decomp%land_plantcarbon_to_x_y_plantcarbon_int32 + output_decomp_base_plantcarbon_real32 => io_decomp%land_plantcarbon_to_x_y_plantcarbon_real32 + output_decomp_base_plantcarbon_real64 => io_decomp%land_plantcarbon_to_x_y_plantcarbon_real64 + output_decomp_base_soilcarbon_int32 => io_decomp%land_soilcarbon_to_x_y_soilcarbon_int32 + output_decomp_base_soilcarbon_real32 => io_decomp%land_soilcarbon_to_x_y_soilcarbon_real32 + output_decomp_base_soilcarbon_real64 => io_decomp%land_soilcarbon_to_x_y_soilcarbon_real64 + output_decomp_base_patch_int32 => io_decomp%patch_to_x_y_patch_int32 + output_decomp_base_patch_real32 => io_decomp%patch_to_x_y_patch_real32 + output_decomp_base_patch_real64 => io_decomp%patch_to_x_y_patch_real64 + output_decomp_base_patch_soil_int32 => io_decomp%patch_soil_to_x_y_patch_soil_int32 + output_decomp_base_patch_soil_real32 => io_decomp%patch_soil_to_x_y_patch_soil_real32 + output_decomp_base_patch_soil_real64 => io_decomp%patch_soil_to_x_y_patch_soil_real64 + output_decomp_base_patch_snow_int32 => io_decomp%patch_snow_to_x_y_patch_snow_int32 + output_decomp_base_patch_snow_real32 => io_decomp%patch_snow_to_x_y_patch_snow_real32 + output_decomp_base_patch_snow_real64 => io_decomp%patch_snow_to_x_y_patch_snow_real64 + output_decomp_base_patch_rad_int32 => io_decomp%patch_rad_to_x_y_patch_rad_int32 + output_decomp_base_patch_rad_real32 => io_decomp%patch_rad_to_x_y_patch_rad_real32 + output_decomp_base_patch_rad_real64 => io_decomp%patch_rad_to_x_y_patch_rad_real64 + output_decomp_base_patch_plantcarbon_int32 => io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_int32 + output_decomp_base_patch_plantcarbon_real32 => io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_real32 + output_decomp_base_patch_plantcarbon_real64 => io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_real64 + output_decomp_base_patch_soilcarbon_int32 => io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_int32 + output_decomp_base_patch_soilcarbon_real32 => io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_real32 + output_decomp_base_patch_soilcarbon_real64 => io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_real64 + else if (requires_land_output_grid(output%grid, metGrid)) then + output_decomp_base_int32 => io_decomp%land_to_land_int32 + output_decomp_base_real32 => io_decomp%land_to_land_real32 + output_decomp_base_real64 => io_decomp%land_to_land_real64 + output_decomp_base_soil_int32 => io_decomp%land_soil_to_land_soil_int32 + output_decomp_base_soil_real32 => io_decomp%land_soil_to_land_soil_real32 + output_decomp_base_soil_real64 => io_decomp%land_soil_to_land_soil_real64 + output_decomp_base_snow_int32 => io_decomp%land_snow_to_land_snow_int32 + output_decomp_base_snow_real32 => io_decomp%land_snow_to_land_snow_real32 + output_decomp_base_snow_real64 => io_decomp%land_snow_to_land_snow_real64 + output_decomp_base_rad_int32 => io_decomp%land_rad_to_land_rad_int32 + output_decomp_base_rad_real32 => io_decomp%land_rad_to_land_rad_real32 + output_decomp_base_rad_real64 => io_decomp%land_rad_to_land_rad_real64 + output_decomp_base_plantcarbon_int32 => io_decomp%land_plantcarbon_to_land_plantcarbon_int32 + output_decomp_base_plantcarbon_real32 => io_decomp%land_plantcarbon_to_land_plantcarbon_real32 + output_decomp_base_plantcarbon_real64 => io_decomp%land_plantcarbon_to_land_plantcarbon_real64 + output_decomp_base_soilcarbon_int32 => io_decomp%land_soilcarbon_to_land_soilcarbon_int32 + output_decomp_base_soilcarbon_real32 => io_decomp%land_soilcarbon_to_land_soilcarbon_real32 + output_decomp_base_soilcarbon_real64 => io_decomp%land_soilcarbon_to_land_soilcarbon_real64 + output_decomp_base_patch_int32 => io_decomp%patch_to_land_patch_int32 + output_decomp_base_patch_real32 => io_decomp%patch_to_land_patch_real32 + output_decomp_base_patch_real64 => io_decomp%patch_to_land_patch_real64 + output_decomp_base_patch_soil_int32 => io_decomp%patch_soil_to_land_patch_soil_int32 + output_decomp_base_patch_soil_real32 => io_decomp%patch_soil_to_land_patch_soil_real32 + output_decomp_base_patch_soil_real64 => io_decomp%patch_soil_to_land_patch_soil_real64 + output_decomp_base_patch_snow_int32 => io_decomp%patch_snow_to_land_patch_snow_int32 + output_decomp_base_patch_snow_real32 => io_decomp%patch_snow_to_land_patch_snow_real32 + output_decomp_base_patch_snow_real64 => io_decomp%patch_snow_to_land_patch_snow_real64 + output_decomp_base_patch_rad_int32 => io_decomp%patch_rad_to_land_patch_rad_int32 + output_decomp_base_patch_rad_real32 => io_decomp%patch_rad_to_land_patch_rad_real32 + output_decomp_base_patch_rad_real64 => io_decomp%patch_rad_to_land_patch_rad_real64 + output_decomp_base_patch_plantcarbon_int32 => io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_int32 + output_decomp_base_patch_plantcarbon_real32 => io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_real32 + output_decomp_base_patch_plantcarbon_real64 => io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_real64 + output_decomp_base_patch_soilcarbon_int32 => io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_int32 + output_decomp_base_patch_soilcarbon_real32 => io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_real32 + output_decomp_base_patch_soilcarbon_real64 => io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_real64 + else + call cable_abort("Error: Unable to determine output grid type", __FILE__, __LINE__) + end if + + restart_decomp_patch_int32 => io_decomp%patch_to_patch_int32 + restart_decomp_patch_real32 => io_decomp%patch_to_patch_real32 + restart_decomp_patch_real64 => io_decomp%patch_to_patch_real64 + restart_decomp_patch_soil_int32 => io_decomp%patch_soil_to_patch_soil_int32 + restart_decomp_patch_soil_real32 => io_decomp%patch_soil_to_patch_soil_real32 + restart_decomp_patch_soil_real64 => io_decomp%patch_soil_to_patch_soil_real64 + restart_decomp_patch_snow_int32 => io_decomp%patch_snow_to_patch_snow_int32 + restart_decomp_patch_snow_real32 => io_decomp%patch_snow_to_patch_snow_real32 + restart_decomp_patch_snow_real64 => io_decomp%patch_snow_to_patch_snow_real64 + restart_decomp_patch_rad_int32 => io_decomp%patch_rad_to_patch_rad_int32 + restart_decomp_patch_rad_real32 => io_decomp%patch_rad_to_patch_rad_real32 + restart_decomp_patch_rad_real64 => io_decomp%patch_rad_to_patch_rad_real64 + restart_decomp_patch_plantcarbon_int32 => io_decomp%patch_plantcarbon_to_patch_plantcarbon_int32 + restart_decomp_patch_plantcarbon_real32 => io_decomp%patch_plantcarbon_to_patch_plantcarbon_real32 + restart_decomp_patch_plantcarbon_real64 => io_decomp%patch_plantcarbon_to_patch_plantcarbon_real64 + restart_decomp_patch_soilcarbon_int32 => io_decomp%patch_soilcarbon_to_patch_soilcarbon_int32 + restart_decomp_patch_soilcarbon_real32 => io_decomp%patch_soilcarbon_to_patch_soilcarbon_real32 + restart_decomp_patch_soilcarbon_real64 => io_decomp%patch_soilcarbon_to_patch_soilcarbon_real64 + + end subroutine + + subroutine allocate_grid_reduction_buffers() + + allocate(temp_buffer_land_int32(mland)) + allocate(temp_buffer_land_real32(mland)) + allocate(temp_buffer_land_real64(mland)) + allocate(temp_buffer_land_soil_int32(mland, ms)) + allocate(temp_buffer_land_soil_real32(mland, ms)) + allocate(temp_buffer_land_soil_real64(mland, ms)) + allocate(temp_buffer_land_snow_int32(mland, msn)) + allocate(temp_buffer_land_snow_real32(mland, msn)) + allocate(temp_buffer_land_snow_real64(mland, msn)) + allocate(temp_buffer_land_rad_int32(mland, nrb)) + allocate(temp_buffer_land_rad_real32(mland, nrb)) + allocate(temp_buffer_land_rad_real64(mland, nrb)) + allocate(temp_buffer_land_plantcarbon_int32(mland, ncp)) + allocate(temp_buffer_land_plantcarbon_real32(mland, ncp)) + allocate(temp_buffer_land_plantcarbon_real64(mland, ncp)) + allocate(temp_buffer_land_soilcarbon_int32(mland, ncs)) + allocate(temp_buffer_land_soilcarbon_real32(mland, ncs)) + allocate(temp_buffer_land_soilcarbon_real64(mland, ncs)) + + end subroutine + + subroutine deallocate_grid_reduction_buffers() + + deallocate(temp_buffer_land_int32) + deallocate(temp_buffer_land_real32) + deallocate(temp_buffer_land_real64) + deallocate(temp_buffer_land_soil_int32) + deallocate(temp_buffer_land_soil_real32) + deallocate(temp_buffer_land_soil_real64) + deallocate(temp_buffer_land_snow_int32) + deallocate(temp_buffer_land_snow_real32) + deallocate(temp_buffer_land_snow_real64) + deallocate(temp_buffer_land_rad_int32) + deallocate(temp_buffer_land_rad_real32) + deallocate(temp_buffer_land_rad_real64) + deallocate(temp_buffer_land_plantcarbon_int32) + deallocate(temp_buffer_land_plantcarbon_real32) + deallocate(temp_buffer_land_plantcarbon_real64) + deallocate(temp_buffer_land_soilcarbon_int32) + deallocate(temp_buffer_land_soilcarbon_real32) + deallocate(temp_buffer_land_soilcarbon_real64) + + end subroutine + + logical function requires_x_y_output_grid(output_grid, met_grid) + character(len=*), intent(in) :: output_grid + character(len=*), intent(in) :: met_grid + requires_x_y_output_grid = (( & + output_grid == "default" .AND. met_grid == "mask" & + ) .OR. ( & + output_grid == "mask" .OR. output_grid == "ALMA" & + )) + end function + + logical function requires_land_output_grid(output_grid, met_grid) + character(len=*), intent(in) :: output_grid + character(len=*), intent(in) :: met_grid + requires_land_output_grid = ( & + output_grid == "land" .OR. (output_grid == "default" .AND. met_grid == "land") & + ) + end function + + elemental integer function dim_size(dim) + type(cable_output_dim_t), intent(in) :: dim + + select case (dim%value) + case (CABLE_OUTPUT_DIM_PATCH%value) + dim_size = mp + case (CABLE_OUTPUT_DIM_SOIL%value) + dim_size = ms + case (CABLE_OUTPUT_DIM_SNOW%value) + dim_size = msn + case (CABLE_OUTPUT_DIM_RAD%value) + dim_size = nrb + case (CABLE_OUTPUT_DIM_PLANTCARBON%value) + dim_size = ncp + case (CABLE_OUTPUT_DIM_SOILCARBON%value) + dim_size = ncs + case (CABLE_OUTPUT_DIM_LAND%value) + dim_size = mland + case (CABLE_OUTPUT_DIM_LAND_GLOBAL%value) + dim_size = mland_global + case (CABLE_OUTPUT_DIM_X%value) + dim_size = xdimsize + case (CABLE_OUTPUT_DIM_Y%value) + dim_size = ydimsize + case default + dim_size = -1 ! Unknown dimension + end select + + end function + + subroutine check_invalid_frequency(sampling_frequency, accumulation_frequency, var_name, file_name) + character(len=*), intent(in) :: sampling_frequency + character(len=*), intent(in) :: accumulation_frequency + character(len=*), intent(in) :: var_name + character(len=*), intent(in) :: file_name + + integer :: sampling_period_in_hours, accumulation_period_in_hours + + character(len=256) :: err_message + + err_message = ( & + "Invalid combination of sampling frequency '" // sampling_frequency // & + "' with accumulation frequency '" // accumulation_frequency // "' for variable '" // & + var_name // "' in file '" // file_name // "'" & + ) + + select case (sampling_frequency) + case ("all") + if (accumulation_frequency /= "all") call cable_abort(err_message, __FILE__, __LINE__) + case ("user") + read(sampling_frequency(5:7), *) sampling_period_in_hours + if (accumulation_frequency == "user") then + read(accumulation_frequency(5:7), *) accumulation_period_in_hours + if (sampling_period_in_hours < accumulation_period_in_hours) then + call cable_abort(err_message, __FILE__, __LINE__) + end if + else if (accumulation_frequency /= "all") then + call cable_abort(err_message, __FILE__, __LINE__) + end if + case ("daily") + if (.not. any(accumulation_frequency == ["all", "daily", "user"])) then + call cable_abort(err_message, __FILE__, __LINE__) + end if + case ("monthly") + if (.not. any(accumulation_frequency == ["all", "daily", "user", "monthly"])) then + call cable_abort(err_message, __FILE__, __LINE__) + end if + case default + call cable_abort("Invalid sampling frequency '" // sampling_frequency // & + "' for variable '" // var_name // "' in file '" // file_name // "'", __FILE__, __LINE__) + end select + + end subroutine check_invalid_frequency + + function infer_dim_names(output_variable, restart) result(dim_names) + type(cable_output_variable_t), intent(in) :: output_variable + logical, intent(in), optional :: restart + + character(MAX_LEN_DIM), allocatable :: dim_names(:) + logical :: restart_local + integer :: j + + restart_local = .false. + if (present(restart)) restart_local = restart + + allocate(dim_names(0)) + do j = 1, size(output_variable%data_shape) + if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_PATCH) then + if (restart_local) then + dim_names = [dim_names, "mp"] + else if (requires_land_output_grid(output%grid, metgrid)) then + if (output_variable%reduction_method == "none") then + dim_names = [dim_names, "land", "patch"] + else + dim_names = [dim_names, "land"] + end if + else if (requires_x_y_output_grid(output%grid, metgrid)) then + if (output_variable%reduction_method == "none") then + dim_names = [dim_names, "x", "y", "patch"] + else + dim_names = [dim_names, "x", "y"] + end if + end if + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_LAND) then + if (restart_local) then + dim_names = [dim_names, "mland"] + else if (requires_land_output_grid(output%grid, metgrid)) then + dim_names = [dim_names, "land"] + else if (requires_x_y_output_grid(output%grid, metgrid)) then + dim_names = [dim_names, "x", "y"] + end if + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_LAND_GLOBAL) then + if (restart_local) then + dim_names = [dim_names, "mland"] + else + dim_names = [dim_names, "land"] + end if + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_SOIL) then + dim_names = [dim_names, "soil"] + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_SNOW) then + dim_names = [dim_names, "snow"] + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_RAD) then + dim_names = [dim_names, "rad"] + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_PLANTCARBON) then + if (restart_local) then + dim_names = [dim_names, "plant_carbon_pools"] + else + dim_names = [dim_names, "plantcarbon"] + end if + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_SOILCARBON) then + if (restart_local) then + dim_names = [dim_names, "soil_carbon_pools"] + else + dim_names = [dim_names, "soilcarbon"] + end if + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_X) then + dim_names = [dim_names, "x"] + else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_Y) then + dim_names = [dim_names, "y"] + else + call cable_abort("Unexpected data shape for variable " // output_variable%name, __FILE__, __LINE__) + end if + end do + + if (.not. restart_local .and. .not. output_variable%parameter) dim_names = [dim_names, "time"] + + end function + + function infer_cell_methods(output_variable) result(cell_methods) + type(cable_output_variable_t), intent(in) :: output_variable + character(len=256) :: cell_methods + + if (.not. output_variable%parameter) then + select case (output_variable%aggregation_method) + case ("point") + cell_methods = "time: point" + case ("mean") + cell_methods = "time: mean" + case ("sum") + cell_methods = "time: sum" + case ("min") + cell_methods = "time: minimum" + case ("max") + cell_methods = "time: maximum" + case default + call cable_abort("Unexpected aggregation method '" // output_variable%aggregation_method // & + "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) + end select + end if + + select case (output_variable%reduction_method) + case ("none", "first_patch_in_grid_cell") + ! no additional cell methods + case ("grid_cell_average") + if (len_trim(cell_methods) > 0) then + cell_methods = cell_methods // " area: mean" + else + cell_methods = "area: mean" + end if + case default + call cable_abort("Unexpected reduction method '" // output_variable%reduction_method // & + "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) + end select + + end function + + subroutine define_variables(output_file, output_variables, restart) + class(cable_netcdf_file_t), intent(inout) :: output_file + type(cable_output_variable_t), intent(in) :: output_variables(:) + logical, intent(in), optional :: restart + + integer :: i, j + logical :: restart_local + + character(MAX_LEN_DIM), allocatable :: required_dimensions(:), dim_names(:) + + restart_local = .false. + if (present(restart)) restart_local = restart + + do i = 1, size(output_variables) + associate(output_var => output_variables(i)) + if (restart_local .and. .not. output_var%restart) cycle + if (.not. allocated(output_var%data_shape)) cycle + dim_names = infer_dim_names(output_var, restart_local) + if (.not. allocated(required_dimensions)) then + required_dimensions = dim_names + else + required_dimensions = [ & + required_dimensions, & + pack(dim_names, [( & + .not. any(dim_names(j) == required_dimensions), & + j = 1, & + size(dim_names) & + )]) & + ] + end if + end associate + end do + + do i = 1, size(required_dimensions) + select case (required_dimensions(i)) + case ("mp") + call output_file%def_dims(["mp"], [mp_global]) + case ("mland") + call output_file%def_dims(["mland"], [mland_global]) + case ("land") + call output_file%def_dims(["land"], [mland_global]) + case ("x") + call output_file%def_dims(["x"], [xdimsize]) + case ("y") + call output_file%def_dims(["y"], [ydimsize]) + case ("patch") + call output_file%def_dims(["patch"], [max_vegpatches]) + case ("soil") + call output_file%def_dims(["soil"], [ms]) + case ("rad") + call output_file%def_dims(["rad"], [nrb]) + case ("soil_carbon_pools") + call output_file%def_dims(["soil_carbon_pools"], [ncs]) + case ("soilcarbon") + call output_file%def_dims(["soilcarbon"], [ncs]) + case ("plant_carbon_pools") + call output_file%def_dims(["plant_carbon_pools"], [ncp]) + case ("plantcarbon") + call output_file%def_dims(["plantcarbon"], [ncp]) + case ("time") + ! time dimension defined separately below + case default + call cable_abort("Unexpected dimension name '" // required_dimensions(i) // "'", __FILE__, __LINE__) + end select + end do + + if (restart_local) then + call output_file%def_dims(["time"], [1]) + else + call output_file%def_dims(["time"], [CABLE_NETCDF_UNLIMITED]) + end if + + call output_file%def_var("time", ["time"], CABLE_NETCDF_DOUBLE) + call output_file%put_att("time", "units", timeunits) + call output_file%put_att("time", "coordinate", time_coord) + call output_file%put_att("time", "calendar", calendar) + + if (.not. restart_local) then + call output_file%def_dims(["nv"], [2]) + call output_file%def_var("time_bnds", ["nv", "time"], CABLE_NETCDF_DOUBLE) + call output_file%put_att("time", "bounds", "time_bnds") + end if + + do i = 1, size(output_variables) + associate(output_var => output_variables(i)) + if (restart_local .and. .not. output_var%restart) cycle + call output_file%def_var( & + var_name=output_var%name, & + dim_names=infer_dim_names(output_var, restart_local), & + type=output_var%var_type & + ) + call output_file%put_att(output_var%name, "units", output_var%units) + call output_file%put_att(output_var%name, "long_name", output_var%long_name) + select case (output_var%var_type) + case (CABLE_NETCDF_INT) + call output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_INT32) + call output_file%put_att(output_var%name, "missing_value", FILL_VALUE_INT32) + case (CABLE_NETCDF_FLOAT) + call output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_REAL32) + call output_file%put_att(output_var%name, "missing_value", FILL_VALUE_REAL32) + case (CABLE_NETCDF_DOUBLE) + call output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_REAL64) + call output_file%put_att(output_var%name, "missing_value", FILL_VALUE_REAL64) + end select + call output_file%put_att(output_var%name, "cell_methods", infer_cell_methods(output_var)) + end associate + end do + + end subroutine define_variables + + subroutine set_global_attributes(output_profile) + type(cable_output_profile_t), intent(inout) :: output_profile + + character(32) :: todaydate, nowtime + + call date_and_time(todaydate, nowtime) + todaydate = todaydate(1:4) // "/" // todaydate(5:6) // "/" // todaydate(7:8) + nowtime = nowtime(1:2) // ":" // nowtime(3:4) // ":" // nowtime(5:6) + call output_profile%output_file%put_att("Production", trim(todaydate) // " at " // trim(nowtime)) + call output_profile%output_file%put_att("Source", "CABLE LSM output file") + call output_profile%output_file%put_att("CABLE_input_file", trim(filename%met)) + + select case (output_profile%sampling_frequency) + case ("user") + call output_profile%output_file%put_att("Output_averaging", TRIM(output_profile%sampling_frequency(5:7)) // "-hourly output") + case ("all") + call output_profile%output_file%put_att("Output_averaging", "all timesteps recorded") + case ("daily") + call output_profile%output_file%put_att("Output_averaging", "daily") + case ("monthly") + call output_profile%output_file%put_att("Output_averaging", "monthly") + case default + call cable_abort("Invalid sampling frequency '" // output_profile%sampling_frequency // "'", __FILE__, __LINE__) + end select + + end subroutine set_global_attributes + + subroutine associate_decomp_int32(output_var, decomp, restart) + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + logical, intent(in), optional :: restart + + if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_int32 + else + decomp => output_decomp_base_int32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_int32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_soil_int32 + else + decomp => output_decomp_base_soil_int32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_soil_int32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_snow_int32 + else + decomp => output_decomp_base_snow_int32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_snow_int32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_rad_int32 + else + decomp => output_decomp_base_rad_int32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_rad_int32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_plantcarbon_int32 + else + decomp => output_decomp_base_plantcarbon_int32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_plantcarbon_int32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_soilcarbon_int32 + else + decomp => output_decomp_base_soilcarbon_int32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_soilcarbon_int32 + end if + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_int32 + + subroutine associate_decomp_real32(output_var, decomp, restart) + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + logical, intent(in), optional :: restart + + if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_real32 + else + decomp => output_decomp_base_real32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_real32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_soil_real32 + else + decomp => output_decomp_base_soil_real32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_soil_real32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_snow_real32 + else + decomp => output_decomp_base_snow_real32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_snow_real32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_rad_real32 + else + decomp => output_decomp_base_rad_real32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_rad_real32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_plantcarbon_real32 + else + decomp => output_decomp_base_plantcarbon_real32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_plantcarbon_real32 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_soilcarbon_real32 + else + decomp => output_decomp_base_soilcarbon_real32 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_soilcarbon_real32 + end if + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_real32 + + subroutine associate_decomp_real64(output_var, decomp, restart) + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + logical, intent(in), optional :: restart + + if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_real64 + else + decomp => output_decomp_base_real64 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_real64 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_soil_real64 + else + decomp => output_decomp_base_soil_real64 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_soil_real64 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_snow_real64 + else + decomp => output_decomp_base_snow_real64 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_snow_real64 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_rad_real64 + else + decomp => output_decomp_base_rad_real64 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_rad_real64 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_plantcarbon_real64 + else + decomp => output_decomp_base_plantcarbon_real64 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_plantcarbon_real64 + end if + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp_base_patch_soilcarbon_real64 + else + decomp => output_decomp_base_soilcarbon_real64 + end if + if (present(restart)) then + if (restart) decomp => restart_decomp_patch_soilcarbon_real64 + end if + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_real64 + + subroutine associate_temp_buffer_int32(output_var, temp_buffer_int32_1d, temp_buffer_int32_2d, temp_buffer_int32_3d) + type(cable_output_variable_t), intent(inout) :: output_var + integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_1d(:) + integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_2d(:,:) + integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_3d(:,:,:) + + if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (.not. present(temp_buffer_int32_1d)) call cable_abort( & + "temp_buffer_int32_1d must be provided for 1D data shape", __FILE__, __LINE__) + temp_buffer_int32_1d => temp_buffer_land_int32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_soil_int32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_rad_int32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_snow_int32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_rad_int32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_plantcarbon_int32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_soilcarbon_int32 + else + call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) + end if + + end subroutine associate_temp_buffer_int32 + + subroutine associate_temp_buffer_real32(output_var, temp_buffer_real32_1d, temp_buffer_real32_2d, temp_buffer_real32_3d) + type(cable_output_variable_t), intent(inout) :: output_var + real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_1d(:) + real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_2d(:,:) + real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_3d(:,:,:) + + if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (.not. present(temp_buffer_real32_1d)) call cable_abort( & + "temp_buffer_real32_1d must be provided for 1D data shape", __FILE__, __LINE__) + temp_buffer_real32_1d => temp_buffer_land_real32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_soil_real32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_rad_real32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_snow_real32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_rad_real32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_plantcarbon_real32 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_soilcarbon_real32 + else + call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) + end if + + end subroutine associate_temp_buffer_real32 + + subroutine associate_temp_buffer_real64(output_var, temp_buffer_real64_1d, temp_buffer_real64_2d, temp_buffer_real64_3d) + type(cable_output_variable_t), intent(inout) :: output_var + real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_1d(:) + real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_2d(:,:) + real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_3d(:,:,:) + + if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (.not. present(temp_buffer_real64_1d)) call cable_abort( & + "temp_buffer_real64_1d must be provided for 1D data shape", __FILE__, __LINE__) + temp_buffer_real64_1d => temp_buffer_land_real64 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_soil_real64 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_rad_real64 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_snow_real64 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_rad_real64 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_plantcarbon_real64 + else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_soilcarbon_real64 + else + call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) + end if + + end subroutine associate_temp_buffer_real64 + +end module From 10272c1c4136d3e0ac6b896b8540f0e1b56af368 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 23 Jan 2026 11:37:28 +1100 Subject: [PATCH 06/31] src/util/cable_timing_utils.F90: Implement case for custom hourly frequencies --- src/util/cable_timing_utils.F90 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/util/cable_timing_utils.F90 b/src/util/cable_timing_utils.F90 index a3c0353cd..e738035eb 100644 --- a/src/util/cable_timing_utils.F90 +++ b/src/util/cable_timing_utils.F90 @@ -25,16 +25,20 @@ function time_step_matches(dels, ktau, frequency, leaps, start_year) result(matc integer, intent(in) :: start_year !! Start year of the simulation logical :: match integer :: i - integer :: time_steps_per_day + integer :: time_steps_per_interval + integer :: interval_in_hours integer :: last_day_of_month_in_accumulated_days(months_in_year) ! TODO(Sean): better variable name? select case (frequency) - ! TODO(Sean): implement case for custom hourly frequencies + case ('user') + read(frequency(5:7), *) interval_in_hours + time_steps_per_interval = seconds_per_hour * interval_in_hours / int(dels) + match = mod(ktau, time_steps_per_interval) == 0 case ('all') match = .true. case ('daily') - time_steps_per_day = seconds_per_hour * hours_per_day / int(dels) - match = mod(ktau, time_steps_per_day) == 0 + time_steps_per_interval = seconds_per_hour * hours_per_day / int(dels) + match = mod(ktau, time_steps_per_interval) == 0 case ('monthly') ! TODO(Sean): is there a better algorithm for monthly matching that doesn't involve looping over years? last_day_of_month_in_accumulated_days = 0 From 5463b05692065223d52c1756db4963570f63df3c Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 23 Jan 2026 15:06:39 +1100 Subject: [PATCH 07/31] src/offline/cable_mpimaster.F90: Accumulate tscrn aggregators in master driver --- src/offline/cable_mpimaster.F90 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/offline/cable_mpimaster.F90 b/src/offline/cable_mpimaster.F90 index cccdcedea..5ee115bde 100644 --- a/src/offline/cable_mpimaster.F90 +++ b/src/offline/cable_mpimaster.F90 @@ -787,6 +787,12 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) ENDIF ENDIF + if (ktau > kstart .and. mod(ktau - kstart, ktauday) == 0) then + ! Reset daily aggregators if previous time step was the end of day + call canopy%tscrn_max_daily%reset() + call canopy%tscrn_min_daily%reset() + end if + ! MPI: receive this time step's results from the workers CALL master_receive (ocomm, oktau, recv_ts) ! CALL MPI_Waitall (wnp, recv_req, recv_stats, ierr) @@ -820,6 +826,9 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) ENDIF ENDIF + call canopy%tscrn_max_daily%accumulate() + call canopy%tscrn_min_daily%accumulate() + ELSE IF ( MOD((ktau-kstart+1+koffset),ktauday)==0 ) THEN CALL master_send_input (icomm, casa_dump_ts, iktau ) From c986a416a3dc18176a819c781a868be64994f713 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 30 Jan 2026 12:42:57 +1100 Subject: [PATCH 08/31] src/offline/cable_mpimaster.F90: Add output module to MPI master driver --- src/offline/cable_mpimaster.F90 | 49 +++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/offline/cable_mpimaster.F90 b/src/offline/cable_mpimaster.F90 index 5ee115bde..2551cee83 100644 --- a/src/offline/cable_mpimaster.F90 +++ b/src/offline/cable_mpimaster.F90 @@ -91,11 +91,22 @@ MODULE cable_mpimaster compare_consistency_check_values USE cable_mpicommon USE cable_IO_vars_module, ONLY : NO_CHECK + use cable_io_vars_module, only: patch USE cable_io_decomp_mod, ONLY: io_decomp_t, cable_io_decomp_init USE casa_cable USE casa_inout_module USE cable_checks_module, ONLY: constant_check_range USE cable_mpi_mod, ONLY: mpi_grp_t + use cable_output_prototype_v2_mod, only: cable_output_mod_init + use cable_output_prototype_v2_mod, only: cable_output_mod_end + use cable_output_prototype_v2_mod, only: cable_output_register_output_variables + use cable_output_prototype_v2_mod, only: cable_output_profiles_init + use cable_output_prototype_v2_mod, only: cable_output_update + use cable_output_prototype_v2_mod, only: cable_output_write + use cable_output_prototype_v2_mod, only: cable_output_write_parameters + use cable_output_prototype_v2_mod, only: cable_output_write_restart + use cable_output_prototype_v2_mod, only: cable_output_core_outputs + use cable_netcdf_mod, only: cable_netcdf_mod_init, cable_netcdf_mod_end IMPLICIT NONE @@ -334,6 +345,10 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) type(io_decomp_t) :: io_decomp + integer :: start_year + + call cable_netcdf_mod_init(mpi_grp_master) + ! END header ! outer loop - spinup loop no. ktau_tot : @@ -633,6 +648,13 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) ENDIF call cable_io_decomp_init(io_decomp) + + if (.not. casaonly) then + call cable_output_mod_init(io_decomp) + call cable_output_register_output_variables(cable_output_core_outputs(canopy, soil)) + call cable_output_profiles_init() + end if + ! MPI: mostly original serial code follows... ENDIF ! CALL1 @@ -769,6 +791,16 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) !$ CALL POPLUC_set_patchfrac(POPLUC,LUC_EXPT) !$ ENDIF + ! TODO(Sean): this is a hack for determining if the current time step + ! is the last of the month. Better way to do this? + IF(ktau == 1) THEN + !MC - use met%year(1) instead of CABLE_USER%YearStart for non-GSWP forcing and leap years + IF ( TRIM(cable_user%MetType) .EQ. '' ) THEN + start_year = met%year(1) + ELSE + start_year = CABLE_USER%YearStart + ENDIF + END IF IF ( .NOT. CASAONLY ) THEN @@ -871,11 +903,16 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) casamet,ssnow, & rad, bal, air, soil, veg, CSBOLTZ, & CEMLEAF, CEMSOIL ) + if (ktau_tot == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) + call cable_output_update(ktau_tot, dels, leaps, start_year, met) + call cable_output_write(ktau_tot, dels, leaps, start_year, met, patch, landpt) CASE DEFAULT CALL write_output( dels, ktau, met, canopy, casaflux, casapool, & casamet, ssnow, & rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) - + if (ktau_tot == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) + call cable_output_update(ktau_tot, dels, leaps, start_year, met) + call cable_output_write(ktau_tot, dels, leaps, start_year, met, patch, landpt) END SELECT END IF ENDIF @@ -1062,10 +1099,15 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) CASE ('plum', 'cru', 'gswp', 'gswp3') CALL write_output( dels, ktau_tot, met, canopy, casaflux, casapool, casamet, & ssnow, rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) + if (ktau_tot == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) + call cable_output_update(ktau_tot, dels, leaps, start_year, met) + call cable_output_write(ktau_tot, dels, leaps, start_year, met, patch, landpt) CASE DEFAULT CALL write_output( dels, ktau, met, canopy, casaflux, casapool, casamet, & ssnow, rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) - + if (ktau == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) + call cable_output_update(ktau, dels, leaps, start_year, met) + call cable_output_write(ktau, dels, leaps, start_year, met, patch, landpt) END SELECT END IF @@ -1267,6 +1309,7 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) if(.not.l_landuse) then CALL create_restart( logn, dels, ktau, soil, veg, ssnow, & canopy, rough, rad, bgc, bal, met ) + call cable_output_write_restart(current_time=ktau * dels) endif IF (cable_user%CALL_climate) THEN @@ -1330,7 +1373,9 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) call landuse_deallocate_mp(cend(mland),ms,msn,nrb,mplant,mlitter,msoil,mwood,lucmp) ENDIF + if (.not. casaonly) call cable_output_mod_end() + call cable_netcdf_mod_end() ! Close met data input file: IF ( TRIM(cable_user%MetType) .NE. "gswp" .AND. & From 4d59ba7b0afae607cccaa2cdc31a8b895aabfbc0 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Tue, 27 Jan 2026 14:50:39 +1100 Subject: [PATCH 09/31] src/util/io/output/cable_output_core.F90: fix time axis values for 'all' case --- src/util/io/output/cable_output_core.F90 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 index d382063d1..652017f8c 100644 --- a/src/util/io/output/cable_output_core.F90 +++ b/src/util/io/output/cable_output_core.F90 @@ -249,8 +249,15 @@ subroutine cable_output_write(time_index, dels, leaps, start_year, met, patch, l end do current_time = time_index * dels - call global_profile%output_file%put_var("time", (current_time + global_profile%previous_write_time) / 2.0, start=[global_profile%frame + 1]) + + if (global_profile%sampling_frequency == "all") then + call global_profile%output_file%put_var("time", current_time, start=[global_profile%frame + 1]) + else + call global_profile%output_file%put_var("time", (current_time + global_profile%previous_write_time) / 2.0, start=[global_profile%frame + 1]) + end if + call global_profile%output_file%put_var("time_bnds", [global_profile%previous_write_time, current_time], start=[1, global_profile%frame + 1]) + global_profile%previous_write_time = current_time global_profile%frame = global_profile%frame + 1 From 8610ad4f8ccff8334d72d52086065f6119d50ddf Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 30 Jan 2026 12:29:27 +1100 Subject: [PATCH 10/31] src/util/io/output/cable_output_utils.F90: remove unused variable --- src/util/io/output/cable_output_utils.F90 | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 8a4dd33d2..85b4d356d 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -149,8 +149,6 @@ module cable_output_utils_mod real(kind=real32), allocatable, target :: temp_buffer_land_soilcarbon_real32(:, :) real(kind=real64), allocatable, target :: temp_buffer_land_soilcarbon_real64(:, :) - type(io_decomp_t) :: io_decomp_global - contains subroutine init_decomp_pointers(io_decomp) From 64b3cf496be4d0e70417437dc0ac34c62420aa10 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 30 Jan 2026 12:44:25 +1100 Subject: [PATCH 11/31] src/offline/cable_serial.F90: fix guarding if condition for output module finalisation --- src/offline/cable_serial.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/offline/cable_serial.F90 b/src/offline/cable_serial.F90 index b9c925c22..790f825ae 100644 --- a/src/offline/cable_serial.F90 +++ b/src/offline/cable_serial.F90 @@ -1053,12 +1053,12 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi IF (cable_user%CALL_climate) & CALL WRITE_CLIMATE_RESTART_NC ( climate, ktauday ) - call cable_output_mod_end() !--- LN ------------------------------------------[ ENDIF - call cable_netcdf_mod_end() + if (.not. casaonly) call cable_output_mod_end() + call cable_netcdf_mod_end() IF ( TRIM(cable_user%MetType) .NE. "gswp" .AND. & TRIM(cable_user%MetType) .NE. "gswp3" .AND. & From 7e4e9325e5080c1b37358a19ac8213c81ddddc74 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Tue, 3 Feb 2026 11:29:40 +1100 Subject: [PATCH 12/31] src/util/io/output/cable_output_core.F90: fix non-standard array constructor syntax for strings --- src/util/io/output/cable_output_core.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 index 652017f8c..5ed325236 100644 --- a/src/util/io/output/cable_output_core.F90 +++ b/src/util/io/output/cable_output_core.F90 @@ -98,10 +98,10 @@ subroutine cable_output_register_output_variables(output_variables) do i = 1, size(output_variables) associate(output_var => output_variables(i)) - if (all(output_var%reduction_method /= ["none", "grid_cell_average", "first_patch_in_grid_cell"])) then + if (all(output_var%reduction_method /= [character(32) :: "none", "grid_cell_average", "first_patch_in_grid_cell"])) then call cable_abort("Invalid reduction method for variable " // trim(output_var%name), __FILE__, __LINE__) end if - if (all(output_var%aggregation_method /= ["point", "mean", "max", "min", "sum"])) then + if (all(output_var%aggregation_method /= [character(32) :: "point", "mean", "max", "min", "sum"])) then call cable_abort("Invalid aggregation method for variable " // trim(output_var%name), __FILE__, __LINE__) end if if (all(output_var%var_type /= [CABLE_NETCDF_INT, CABLE_NETCDF_FLOAT, CABLE_NETCDF_DOUBLE])) then From 9f6854da79c63a310714a23b5a91f24fd6fa8944 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Tue, 3 Feb 2026 11:30:01 +1100 Subject: [PATCH 13/31] src/util/io/output/cable_output_utils.F90: fix non-standard array constructor syntax for strings --- src/util/io/output/cable_output_utils.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 85b4d356d..808ed3fe8 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -377,11 +377,11 @@ subroutine check_invalid_frequency(sampling_frequency, accumulation_frequency, v call cable_abort(err_message, __FILE__, __LINE__) end if case ("daily") - if (.not. any(accumulation_frequency == ["all", "daily", "user"])) then + if (.not. any(accumulation_frequency == ["all ", "daily", "user "])) then call cable_abort(err_message, __FILE__, __LINE__) end if case ("monthly") - if (.not. any(accumulation_frequency == ["all", "daily", "user", "monthly"])) then + if (.not. any(accumulation_frequency == ["all ", "daily ", "user ", "monthly"])) then call cable_abort(err_message, __FILE__, __LINE__) end if case default @@ -582,7 +582,7 @@ subroutine define_variables(output_file, output_variables, restart) if (.not. restart_local) then call output_file%def_dims(["nv"], [2]) - call output_file%def_var("time_bnds", ["nv", "time"], CABLE_NETCDF_DOUBLE) + call output_file%def_var("time_bnds", ["nv ", "time"], CABLE_NETCDF_DOUBLE) call output_file%put_att("time", "bounds", "time_bnds") end if From 9d27f693ca1ddf46770dfe0a56e6649e7b04d237 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Wed, 4 Feb 2026 18:00:33 +1100 Subject: [PATCH 14/31] src/util/io/output/cable_output_utils.F90: bug fix in infer_dim_names procedure --- src/util/io/output/cable_output_utils.F90 | 107 +++++++++++----------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 808ed3fe8..90d097c8c 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -403,63 +403,66 @@ function infer_dim_names(output_variable, restart) result(dim_names) if (present(restart)) restart_local = restart allocate(dim_names(0)) - do j = 1, size(output_variable%data_shape) - if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_PATCH) then - if (restart_local) then - dim_names = [dim_names, "mp"] - else if (requires_land_output_grid(output%grid, metgrid)) then - if (output_variable%reduction_method == "none") then - dim_names = [dim_names, "land", "patch"] + if (allocated(output_variable%data_shape)) then + do j = 1, size(output_variable%data_shape) + select case (output_variable%data_shape(j)%value) + case (CABLE_OUTPUT_DIM_PATCH%value) + if (restart_local) then + dim_names = [dim_names, "mp"] + else if (requires_land_output_grid(output%grid, metgrid)) then + if (output_variable%reduction_method == "none") then + dim_names = [dim_names, "land", "patch"] + else + dim_names = [dim_names, "land"] + end if + else if (requires_x_y_output_grid(output%grid, metgrid)) then + if (output_variable%reduction_method == "none") then + dim_names = [dim_names, "x", "y", "patch"] + else + dim_names = [dim_names, "x", "y"] + end if + end if + case (CABLE_OUTPUT_DIM_LAND%value) + if (restart_local) then + dim_names = [dim_names, "mland"] + else if (requires_land_output_grid(output%grid, metgrid)) then + dim_names = [dim_names, "land"] + else if (requires_x_y_output_grid(output%grid, metgrid)) then + dim_names = [dim_names, "x", "y"] + end if + case (CABLE_OUTPUT_DIM_LAND_GLOBAL%value) + if (restart_local) then + dim_names = [dim_names, "mland"] else dim_names = [dim_names, "land"] end if - else if (requires_x_y_output_grid(output%grid, metgrid)) then - if (output_variable%reduction_method == "none") then - dim_names = [dim_names, "x", "y", "patch"] + case (CABLE_OUTPUT_DIM_SOIL%value) + dim_names = [dim_names, "soil"] + case (CABLE_OUTPUT_DIM_SNOW%value) + dim_names = [dim_names, "snow"] + case (CABLE_OUTPUT_DIM_RAD%value) + dim_names = [dim_names, "rad"] + case (CABLE_OUTPUT_DIM_PLANTCARBON%value) + if (restart_local) then + dim_names = [dim_names, "plant_carbon_pools"] else - dim_names = [dim_names, "x", "y"] + dim_names = [dim_names, "plantcarbon"] end if - end if - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_LAND) then - if (restart_local) then - dim_names = [dim_names, "mland"] - else if (requires_land_output_grid(output%grid, metgrid)) then - dim_names = [dim_names, "land"] - else if (requires_x_y_output_grid(output%grid, metgrid)) then - dim_names = [dim_names, "x", "y"] - end if - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_LAND_GLOBAL) then - if (restart_local) then - dim_names = [dim_names, "mland"] - else - dim_names = [dim_names, "land"] - end if - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_SOIL) then - dim_names = [dim_names, "soil"] - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_SNOW) then - dim_names = [dim_names, "snow"] - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_RAD) then - dim_names = [dim_names, "rad"] - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_PLANTCARBON) then - if (restart_local) then - dim_names = [dim_names, "plant_carbon_pools"] - else - dim_names = [dim_names, "plantcarbon"] - end if - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_SOILCARBON) then - if (restart_local) then - dim_names = [dim_names, "soil_carbon_pools"] - else - dim_names = [dim_names, "soilcarbon"] - end if - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_X) then - dim_names = [dim_names, "x"] - else if (output_variable%data_shape(j) == CABLE_OUTPUT_DIM_Y) then - dim_names = [dim_names, "y"] - else - call cable_abort("Unexpected data shape for variable " // output_variable%name, __FILE__, __LINE__) - end if - end do + case (CABLE_OUTPUT_DIM_SOILCARBON%value) + if (restart_local) then + dim_names = [dim_names, "soil_carbon_pools"] + else + dim_names = [dim_names, "soilcarbon"] + end if + case (CABLE_OUTPUT_DIM_X%value) + dim_names = [dim_names, "x"] + case (CABLE_OUTPUT_DIM_Y%value) + dim_names = [dim_names, "y"] + case default + call cable_abort("Unexpected data shape for variable " // output_variable%name, __FILE__, __LINE__) + end select + end do + end if if (.not. restart_local .and. .not. output_variable%parameter) dim_names = [dim_names, "time"] From a5464022571587c7d41d3cda792a3f315c992406 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Wed, 4 Feb 2026 18:01:17 +1100 Subject: [PATCH 15/31] src/util/io/output/cable_output_utils.F90: bug fix for comparing data_shape arrays --- src/util/io/output/cable_output_utils.F90 | 83 ++++++++++++----------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 90d097c8c..764f48dd2 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -317,6 +317,11 @@ logical function requires_land_output_grid(output_grid, met_grid) ) end function + logical function data_shape_eq(shape1, shape2) + type(cable_output_dim_t), dimension(:), intent(in) :: shape1, shape2 + data_shape_eq = size(shape1) == size(shape2) .and. all(shape1 == shape2) + end function + elemental integer function dim_size(dim) type(cable_output_dim_t), intent(in) :: dim @@ -648,7 +653,7 @@ subroutine associate_decomp_int32(output_var, decomp, restart) class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp logical, intent(in), optional :: restart - if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_int32 else @@ -657,7 +662,7 @@ subroutine associate_decomp_int32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_int32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_soil_int32 else @@ -666,7 +671,7 @@ subroutine associate_decomp_int32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_soil_int32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_snow_int32 else @@ -675,7 +680,7 @@ subroutine associate_decomp_int32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_snow_int32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_rad_int32 else @@ -684,7 +689,7 @@ subroutine associate_decomp_int32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_rad_int32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_plantcarbon_int32 else @@ -693,7 +698,7 @@ subroutine associate_decomp_int32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_plantcarbon_int32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_soilcarbon_int32 else @@ -713,7 +718,7 @@ subroutine associate_decomp_real32(output_var, decomp, restart) class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp logical, intent(in), optional :: restart - if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_real32 else @@ -722,7 +727,7 @@ subroutine associate_decomp_real32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_real32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_soil_real32 else @@ -731,7 +736,7 @@ subroutine associate_decomp_real32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_soil_real32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_snow_real32 else @@ -740,7 +745,7 @@ subroutine associate_decomp_real32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_snow_real32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_rad_real32 else @@ -749,7 +754,7 @@ subroutine associate_decomp_real32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_rad_real32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_plantcarbon_real32 else @@ -758,7 +763,7 @@ subroutine associate_decomp_real32(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_plantcarbon_real32 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_soilcarbon_real32 else @@ -778,7 +783,7 @@ subroutine associate_decomp_real64(output_var, decomp, restart) class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp logical, intent(in), optional :: restart - if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_real64 else @@ -787,7 +792,7 @@ subroutine associate_decomp_real64(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_real64 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_soil_real64 else @@ -796,7 +801,7 @@ subroutine associate_decomp_real64(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_soil_real64 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_snow_real64 else @@ -805,7 +810,7 @@ subroutine associate_decomp_real64(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_snow_real64 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_rad_real64 else @@ -814,7 +819,7 @@ subroutine associate_decomp_real64(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_rad_real64 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_plantcarbon_real64 else @@ -823,7 +828,7 @@ subroutine associate_decomp_real64(output_var, decomp, restart) if (present(restart)) then if (restart) decomp => restart_decomp_patch_plantcarbon_real64 end if - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then if (output_var%reduction_method == "none") then decomp => output_decomp_base_patch_soilcarbon_real64 else @@ -844,31 +849,31 @@ subroutine associate_temp_buffer_int32(output_var, temp_buffer_int32_1d, temp_bu integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_2d(:,:) integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_3d(:,:,:) - if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then if (.not. present(temp_buffer_int32_1d)) call cable_abort( & "temp_buffer_int32_1d must be provided for 1D data shape", __FILE__, __LINE__) temp_buffer_int32_1d => temp_buffer_land_int32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then if (.not. present(temp_buffer_int32_2d)) call cable_abort( & "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_int32_2d => temp_buffer_land_soil_int32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (.not. present(temp_buffer_int32_2d)) call cable_abort( & "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_int32_2d => temp_buffer_land_rad_int32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then if (.not. present(temp_buffer_int32_2d)) call cable_abort( & "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_int32_2d => temp_buffer_land_snow_int32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (.not. present(temp_buffer_int32_2d)) call cable_abort( & "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_int32_2d => temp_buffer_land_rad_int32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then if (.not. present(temp_buffer_int32_2d)) call cable_abort( & "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_int32_2d => temp_buffer_land_plantcarbon_int32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then if (.not. present(temp_buffer_int32_2d)) call cable_abort( & "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_int32_2d => temp_buffer_land_soilcarbon_int32 @@ -884,31 +889,31 @@ subroutine associate_temp_buffer_real32(output_var, temp_buffer_real32_1d, temp_ real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_2d(:,:) real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_3d(:,:,:) - if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then if (.not. present(temp_buffer_real32_1d)) call cable_abort( & "temp_buffer_real32_1d must be provided for 1D data shape", __FILE__, __LINE__) temp_buffer_real32_1d => temp_buffer_land_real32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then if (.not. present(temp_buffer_real32_2d)) call cable_abort( & "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real32_2d => temp_buffer_land_soil_real32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (.not. present(temp_buffer_real32_2d)) call cable_abort( & "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real32_2d => temp_buffer_land_rad_real32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then if (.not. present(temp_buffer_real32_2d)) call cable_abort( & "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real32_2d => temp_buffer_land_snow_real32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (.not. present(temp_buffer_real32_2d)) call cable_abort( & "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real32_2d => temp_buffer_land_rad_real32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then if (.not. present(temp_buffer_real32_2d)) call cable_abort( & "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real32_2d => temp_buffer_land_plantcarbon_real32 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then if (.not. present(temp_buffer_real32_2d)) call cable_abort( & "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real32_2d => temp_buffer_land_soilcarbon_real32 @@ -924,31 +929,31 @@ subroutine associate_temp_buffer_real64(output_var, temp_buffer_real64_1d, temp_ real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_2d(:,:) real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_3d(:,:,:) - if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH])) then + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then if (.not. present(temp_buffer_real64_1d)) call cable_abort( & "temp_buffer_real64_1d must be provided for 1D data shape", __FILE__, __LINE__) temp_buffer_real64_1d => temp_buffer_land_real64 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then if (.not. present(temp_buffer_real64_2d)) call cable_abort( & "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real64_2d => temp_buffer_land_soil_real64 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (.not. present(temp_buffer_real64_2d)) call cable_abort( & "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real64_2d => temp_buffer_land_rad_real64 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then if (.not. present(temp_buffer_real64_2d)) call cable_abort( & "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real64_2d => temp_buffer_land_snow_real64 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then if (.not. present(temp_buffer_real64_2d)) call cable_abort( & "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real64_2d => temp_buffer_land_rad_real64 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then if (.not. present(temp_buffer_real64_2d)) call cable_abort( & "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real64_2d => temp_buffer_land_plantcarbon_real64 - else if (all(output_var%data_shape == [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then if (.not. present(temp_buffer_real64_2d)) call cable_abort( & "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) temp_buffer_real64_2d => temp_buffer_land_soilcarbon_real64 From 90433a04e824dbe88fa11bd15afa4a4e7f3a3a20 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Wed, 4 Feb 2026 19:03:11 +1100 Subject: [PATCH 16/31] src/util/io/output/cable_output_utils.F90: fix cell_methods attribute --- src/util/io/output/cable_output_utils.F90 | 33 ++++++++++++++--------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 764f48dd2..cba5721a3 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -476,39 +476,48 @@ function infer_dim_names(output_variable, restart) result(dim_names) function infer_cell_methods(output_variable) result(cell_methods) type(cable_output_variable_t), intent(in) :: output_variable character(len=256) :: cell_methods + character(len=256) :: cell_methods_time, cell_methods_area if (.not. output_variable%parameter) then select case (output_variable%aggregation_method) case ("point") - cell_methods = "time: point" + cell_methods_time = "time: point" case ("mean") - cell_methods = "time: mean" + cell_methods_time = "time: mean" case ("sum") - cell_methods = "time: sum" + cell_methods_time = "time: sum" case ("min") - cell_methods = "time: minimum" + cell_methods_time = "time: minimum" case ("max") - cell_methods = "time: maximum" + cell_methods_time = "time: maximum" case default call cable_abort("Unexpected aggregation method '" // output_variable%aggregation_method // & "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) end select + else + cell_methods_time = "" end if select case (output_variable%reduction_method) - case ("none", "first_patch_in_grid_cell") - ! no additional cell methods + case ("none") + ! TODO(Sean): the cell_method for this case should be `area: point where + ! pft` where `pft` is a string-valued auxiliary coordinate variable + ! describing the labels for all patches: + cell_methods_area = "" + case ("first_patch_in_grid_cell") + ! TODO(Sean): the cell_method for this case should be `area: point where + ! ` where `` is the area type of the first patch in + ! the grid cell: + cell_methods_area = "" case ("grid_cell_average") - if (len_trim(cell_methods) > 0) then - cell_methods = cell_methods // " area: mean" - else - cell_methods = "area: mean" - end if + cell_methods_area = "area: mean where land" case default call cable_abort("Unexpected reduction method '" // output_variable%reduction_method // & "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) end select + cell_methods = adjustl(trim(cell_methods_time) // " " // trim(cell_methods_area)) + end function subroutine define_variables(output_file, output_variables, restart) From 7b30d24e66444f1a26475b75f67da48ae1eaf43a Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Thu, 5 Feb 2026 15:31:52 +1100 Subject: [PATCH 17/31] src/util/io/output/cable_output_core.F90: Remove redundant temp_buffer_* pointers --- src/util/io/output/cable_output_core.F90 | 86 ++++++++++-------------- 1 file changed, 34 insertions(+), 52 deletions(-) diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 index 5ed325236..36c744bd3 100644 --- a/src/util/io/output/cable_output_core.F90 +++ b/src/util/io/output/cable_output_core.F90 @@ -354,16 +354,6 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re real(kind=real64), pointer :: write_buffer_real64_2d(:, :) => null() real(kind=real64), pointer :: write_buffer_real64_3d(:, :, :) => null() - integer(kind=int32), pointer :: temp_buffer_int32_1d(:) => null() - integer(kind=int32), pointer :: temp_buffer_int32_2d(:, :) => null() - integer(kind=int32), pointer :: temp_buffer_int32_3d(:, :, :) => null() - real(kind=real32), pointer :: temp_buffer_real32_1d(:) => null() - real(kind=real32), pointer :: temp_buffer_real32_2d(:, :) => null() - real(kind=real32), pointer :: temp_buffer_real32_3d(:, :, :) => null() - real(kind=real64), pointer :: temp_buffer_real64_1d(:) => null() - real(kind=real64), pointer :: temp_buffer_real64_2d(:, :) => null() - real(kind=real64), pointer :: temp_buffer_real64_3d(:, :, :) => null() - restart_local = .false. if (present(restart)) restart_local = restart @@ -402,12 +392,11 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "grid_cell_average") then call cable_abort("Reduction method grid_cell_average is not supported for integer variables", __FILE__, __LINE__) else if (output_variable%reduction_method == "first_patch_in_grid_cell") then - call associate_temp_buffer_int32(output_variable, temp_buffer_int32_1d=temp_buffer_int32_1d) + call associate_temp_buffer_int32(output_variable, temp_buffer_int32_1d=write_buffer_int32_1d) call first_patch_in_grid_cell( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_int32_1d, & + output_array=write_buffer_int32_1d, & landpt=landpt) - write_buffer_int32_1d => temp_buffer_int32_1d else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if @@ -438,12 +427,11 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "grid_cell_average") then call cable_abort("Reduction method grid_cell_average is not supported for integer variables", __FILE__, __LINE__) else if (output_variable%reduction_method == "first_patch_in_grid_cell") then - call associate_temp_buffer_int32(output_variable, temp_buffer_int32_2d=temp_buffer_int32_2d) + call associate_temp_buffer_int32(output_variable, temp_buffer_int32_2d=write_buffer_int32_2d) call first_patch_in_grid_cell( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_int32_2d, & + output_array=write_buffer_int32_2d, & landpt=landpt) - write_buffer_int32_2d => temp_buffer_int32_2d else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if @@ -474,12 +462,11 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "grid_cell_average") then call cable_abort("Reduction method grid_cell_average is not supported for integer variables", __FILE__, __LINE__) else if (output_variable%reduction_method == "first_patch_in_grid_cell") then - call associate_temp_buffer_int32(output_variable, temp_buffer_int32_3d=temp_buffer_int32_3d) + call associate_temp_buffer_int32(output_variable, temp_buffer_int32_3d=write_buffer_int32_3d) call first_patch_in_grid_cell( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_int32_3d, & + output_array=write_buffer_int32_3d, & landpt=landpt) - write_buffer_int32_3d => temp_buffer_int32_3d else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if @@ -528,20 +515,18 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "none") then write_buffer_real32_1d => aggregator%aggregated_data else if (output_variable%reduction_method == "grid_cell_average") then - call associate_temp_buffer_real32(output_variable, temp_buffer_real32_1d=temp_buffer_real32_1d) + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_1d=write_buffer_real32_1d) call grid_cell_average( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real32_1d, & + output_array=write_buffer_real32_1d, & landpt=landpt, & patch=patch) - write_buffer_real32_1d => temp_buffer_real32_1d else if (output_variable%reduction_method == "first_patch_in_grid_cell") then - call associate_temp_buffer_real32(output_variable, temp_buffer_real32_1d=temp_buffer_real32_1d) + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_1d=write_buffer_real32_1d) call first_patch_in_grid_cell( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real32_1d, & + output_array=write_buffer_real32_1d, & landpt=landpt) - write_buffer_real32_1d => temp_buffer_real32_1d else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if @@ -570,20 +555,18 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "none") then write_buffer_real32_2d => aggregator%aggregated_data else if (output_variable%reduction_method == "grid_cell_average") then - call associate_temp_buffer_real32(output_variable, temp_buffer_real32_2d=temp_buffer_real32_2d) + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_2d=write_buffer_real32_2d) call grid_cell_average( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real32_2d, & + output_array=write_buffer_real32_2d, & landpt=landpt, & patch=patch) - write_buffer_real32_2d => temp_buffer_real32_2d else if (output_variable%reduction_method == "first_patch_in_grid_cell") then - call associate_temp_buffer_real32(output_variable, temp_buffer_real32_2d=temp_buffer_real32_2d) + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_2d=write_buffer_real32_2d) call first_patch_in_grid_cell( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real32_2d, & + output_array=write_buffer_real32_2d, & landpt=landpt) - write_buffer_real32_2d => temp_buffer_real32_2d else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if @@ -612,13 +595,18 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "none") then write_buffer_real32_3d => aggregator%aggregated_data else if (output_variable%reduction_method == "grid_cell_average") then - call associate_temp_buffer_real32(output_variable, temp_buffer_real32_3d=temp_buffer_real32_3d) + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_3d=write_buffer_real32_3d) call grid_cell_average( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real32_3d, & + output_array=write_buffer_real32_3d, & landpt=landpt, & patch=patch) - write_buffer_real32_3d => temp_buffer_real32_3d + else if (output_variable%reduction_method == "first_patch_in_grid_cell") then + call associate_temp_buffer_real32(output_variable, temp_buffer_real32_3d=write_buffer_real32_3d) + call first_patch_in_grid_cell( & + input_array=aggregator%aggregated_data, & + output_array=write_buffer_real32_3d, & + landpt=landpt) else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if @@ -667,20 +655,18 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "none") then write_buffer_real64_1d => aggregator%aggregated_data else if (output_variable%reduction_method == "grid_cell_average") then - call associate_temp_buffer_real64(output_variable, temp_buffer_real64_1d=temp_buffer_real64_1d) + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_1d=write_buffer_real64_1d) call grid_cell_average( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real64_1d, & + output_array=write_buffer_real64_1d, & landpt=landpt, & patch=patch) - write_buffer_real64_1d => temp_buffer_real64_1d else if (output_variable%reduction_method == "first_patch_in_grid_cell") then - call associate_temp_buffer_real64(output_variable, temp_buffer_real64_1d=temp_buffer_real64_1d) + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_1d=write_buffer_real64_1d) call first_patch_in_grid_cell( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real64_1d, & + output_array=write_buffer_real64_1d, & landpt=landpt) - write_buffer_real64_1d => temp_buffer_real64_1d else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if @@ -709,20 +695,18 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "none") then write_buffer_real64_2d => aggregator%aggregated_data else if (output_variable%reduction_method == "grid_cell_average") then - call associate_temp_buffer_real64(output_variable, temp_buffer_real64_2d=temp_buffer_real64_2d) + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_2d=write_buffer_real64_2d) call grid_cell_average( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real64_2d, & + output_array=write_buffer_real64_2d, & landpt=landpt, & patch=patch) - write_buffer_real64_2d => temp_buffer_real64_2d else if (output_variable%reduction_method == "first_patch_in_grid_cell") then - call associate_temp_buffer_real64(output_variable, temp_buffer_real64_2d=temp_buffer_real64_2d) + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_2d=write_buffer_real64_2d) call first_patch_in_grid_cell( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real64_2d, & + output_array=write_buffer_real64_2d, & landpt=landpt) - write_buffer_real64_2d => temp_buffer_real64_2d else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if @@ -751,20 +735,18 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re else if (output_variable%reduction_method == "none") then write_buffer_real64_3d => aggregator%aggregated_data else if (output_variable%reduction_method == "grid_cell_average") then - call associate_temp_buffer_real64(output_variable, temp_buffer_real64_3d=temp_buffer_real64_3d) + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_3d=write_buffer_real64_3d) call grid_cell_average( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real64_3d, & + output_array=write_buffer_real64_3d, & landpt=landpt, & patch=patch) - write_buffer_real64_3d => temp_buffer_real64_3d else if (output_variable%reduction_method == "first_patch_in_grid_cell") then - call associate_temp_buffer_real64(output_variable, temp_buffer_real64_3d=temp_buffer_real64_3d) + call associate_temp_buffer_real64(output_variable, temp_buffer_real64_3d=write_buffer_real64_3d) call first_patch_in_grid_cell( & input_array=aggregator%aggregated_data, & - output_array=temp_buffer_real64_3d, & + output_array=write_buffer_real64_3d, & landpt=landpt) - write_buffer_real64_3d => temp_buffer_real64_3d else call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if From 8c8ad663d25a803837b3615b9bde3a57e548050d Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Thu, 5 Feb 2026 15:32:25 +1100 Subject: [PATCH 18/31] src/util/io/output/cable_output_core.F90: Remove potential for implicit save --- src/util/io/output/cable_output_core.F90 | 41 ++++++++++++++++-------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 index 36c744bd3..c8af63505 100644 --- a/src/util/io/output/cable_output_core.F90 +++ b/src/util/io/output/cable_output_core.F90 @@ -337,22 +337,37 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re integer, intent(in), optional :: frame logical, intent(in), optional :: restart - class(cable_netcdf_decomp_t), pointer :: decomp => null() + class(cable_netcdf_decomp_t), pointer :: decomp integer :: i, ndims logical :: restart_local - integer(kind=int32), pointer :: write_buffer_int32_0d => null() - integer(kind=int32), pointer :: write_buffer_int32_1d(:) => null() - integer(kind=int32), pointer :: write_buffer_int32_2d(:, :) => null() - integer(kind=int32), pointer :: write_buffer_int32_3d(:, :, :) => null() - real(kind=real32), pointer :: write_buffer_real32_0d => null() - real(kind=real32), pointer :: write_buffer_real32_1d(:) => null() - real(kind=real32), pointer :: write_buffer_real32_2d(:, :) => null() - real(kind=real32), pointer :: write_buffer_real32_3d(:, :, :) => null() - real(kind=real64), pointer :: write_buffer_real64_0d => null() - real(kind=real64), pointer :: write_buffer_real64_1d(:) => null() - real(kind=real64), pointer :: write_buffer_real64_2d(:, :) => null() - real(kind=real64), pointer :: write_buffer_real64_3d(:, :, :) => null() + integer(kind=int32), pointer :: write_buffer_int32_0d + integer(kind=int32), pointer :: write_buffer_int32_1d(:) + integer(kind=int32), pointer :: write_buffer_int32_2d(:, :) + integer(kind=int32), pointer :: write_buffer_int32_3d(:, :, :) + real(kind=real32), pointer :: write_buffer_real32_0d + real(kind=real32), pointer :: write_buffer_real32_1d(:) + real(kind=real32), pointer :: write_buffer_real32_2d(:, :) + real(kind=real32), pointer :: write_buffer_real32_3d(:, :, :) + real(kind=real64), pointer :: write_buffer_real64_0d + real(kind=real64), pointer :: write_buffer_real64_1d(:) + real(kind=real64), pointer :: write_buffer_real64_2d(:, :) + real(kind=real64), pointer :: write_buffer_real64_3d(:, :, :) + + decomp => null() + + write_buffer_int32_0d => null() + write_buffer_int32_1d => null() + write_buffer_int32_2d => null() + write_buffer_int32_3d => null() + write_buffer_real32_0d => null() + write_buffer_real32_1d => null() + write_buffer_real32_2d => null() + write_buffer_real32_3d => null() + write_buffer_real64_0d => null() + write_buffer_real64_1d => null() + write_buffer_real64_2d => null() + write_buffer_real64_3d => null() restart_local = .false. if (present(restart)) restart_local = restart From a294717429e01a368289a157f269605cb107f7e1 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 6 Feb 2026 16:00:11 +1100 Subject: [PATCH 19/31] Add variable and global attributes list --- src/util/io/output/cable_output.F90 | 1 + .../io/output/cable_output_definitions.F90 | 141 +++++++++++------- src/util/io/output/cable_output_types.F90 | 9 +- src/util/io/output/cable_output_utils.F90 | 17 ++- 4 files changed, 111 insertions(+), 57 deletions(-) diff --git a/src/util/io/output/cable_output.F90 b/src/util/io/output/cable_output.F90 index 66396c862..5f3f24b57 100644 --- a/src/util/io/output/cable_output.F90 +++ b/src/util/io/output/cable_output.F90 @@ -9,6 +9,7 @@ module cable_output_prototype_v2_mod use cable_output_core_mod, only: cable_output_write_parameters use cable_output_core_mod, only: cable_output_write_restart + use cable_output_types_mod, only: cable_output_attribute_t use cable_output_types_mod, only: cable_output_variable_t use cable_output_definitions_mod, only: cable_output_core_outputs => core_outputs diff --git a/src/util/io/output/cable_output_definitions.F90 b/src/util/io/output/cable_output_definitions.F90 index ca4b2875c..985c18178 100644 --- a/src/util/io/output/cable_output_definitions.F90 +++ b/src/util/io/output/cable_output_definitions.F90 @@ -16,6 +16,7 @@ module cable_output_definitions_mod use cable_io_vars_module, only: metgrid use cable_output_types_mod, only: cable_output_variable_t + use cable_output_types_mod, only: attribute => cable_output_attribute_t use cable_output_types_mod, only: DIM_PATCH => CABLE_OUTPUT_DIM_PATCH use cable_output_types_mod, only: DIM_SOIL => CABLE_OUTPUT_DIM_SOIL use cable_output_types_mod, only: DIM_RAD => CABLE_OUTPUT_DIM_RAD @@ -50,8 +51,6 @@ function core_outputs(canopy, soil) result(output_variables) name="isoil", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_INT, & - units="-", & - long_name="Soil type", & range=ranges%isoil, & active=output%isoil, & patchout=output%patch .or. patchout%isoil, & @@ -59,112 +58,132 @@ function core_outputs(canopy, soil) result(output_variables) aggregation_method="point", & parameter=.true., & restart=.true., & - aggregator=new_aggregator(soil%isoilm) & + aggregator=new_aggregator(soil%isoilm), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Soil type") & + ] & ), & cable_output_variable_t( & name="swilt", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - units="1", & - long_name="", & range=ranges%swilt, & active=output%swilt, & patchout=output%patch .or. patchout%swilt, & reduction_method="grid_cell_average", & aggregation_method="point", & parameter=.true., & - aggregator=new_aggregator(soil%swilt) & + aggregator=new_aggregator(soil%swilt), & + metadata=[ & + attribute("units", "1"), & + attribute("long_name", "") & + ] & ), & cable_output_variable_t( & name="albsoil", & data_shape=[DIM_PATCH, DIM_RAD], & var_type=CABLE_NETCDF_FLOAT, & - units="1", & - long_name="", & range=ranges%albsoil, & active=output%albsoil, & patchout=output%patch .or. patchout%albsoil, & reduction_method="first_patch_in_grid_cell", & aggregation_method="point", & parameter=.true., & - aggregator=new_aggregator(soil%albsoil) & + aggregator=new_aggregator(soil%albsoil), & + metadata=[ & + attribute("units", "1"), & + attribute("long_name", "") & + ] & ), & cable_output_variable_t( & name="Qh", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - units="W/m^2", & - long_name="Surface sensible heat flux", & range=ranges%Qh, & active=output%Qh, & patchout=output%patch .or. patchout%Qh, & reduction_method="grid_cell_average", & aggregation_method="mean", & - aggregator=new_aggregator(canopy%fh) & + aggregator=new_aggregator(canopy%fh), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Surface sensible heat flux") & + ] & ), & cable_output_variable_t( & name="Tmx", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - units="oC", & - long_name="averaged daily maximum screen-level T", & active=output%Tex .and. output%averaging == "monthly", & patchout=output%patch .or. patchout%Tex, & reduction_method="grid_cell_average", & aggregation_method="mean", & range=ranges%Tscrn, & accumulation_frequency="daily", & - aggregator=new_aggregator(canopy%tscrn_max_daily%aggregated_data) & + aggregator=new_aggregator(canopy%tscrn_max_daily%aggregated_data), & + metadata=[ & + attribute("units", "oC"), & + attribute("long_name", "averaged daily maximum screen-level T") & + ] & ), & cable_output_variable_t( & name="nap", & data_shape=[DIM_LAND_GLOBAL], & var_type=CABLE_NETCDF_FLOAT, & - units="", & - long_name="", & range=[-huge(0.0), huge(0.0)], & active=.false., & restart=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(landpt_global(:)%nap) & + aggregator=new_aggregator(landpt_global(:)%nap), & + metadata=[ & + attribute("units", ""), & + attribute("long_name", "") & + ] & ), & cable_output_variable_t( & name="patchfrac", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - units="", & - long_name="Fraction of vegetated grid cell area occupied by a vegetation/soil patch", & range=[0.0, 1.0], & active=.false., & restart=.true., & distributed=.true., & aggregation_method="point", & - aggregator=new_aggregator(patch(:)%frac) & + aggregator=new_aggregator(patch(:)%frac), & + metadata=[ & + attribute("units", ""), & + attribute("long_name", "Fraction of vegetated grid cell area occupied by a vegetation/soil patch") & + ] & ), & cable_output_variable_t( & name="mvtype", & var_type=CABLE_NETCDF_FLOAT, & - units="", & - long_name="Number of vegetation types", & range=[-huge(0.0), huge(0.0)], & active=.false., & restart=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(mvtype) & + aggregator=new_aggregator(mvtype), & + metadata=[ & + attribute("units", ""), & + attribute("long_name", "Number of vegetation types") & + ] & ), & cable_output_variable_t( & name="mstype", & var_type=CABLE_NETCDF_FLOAT, & - units="", & - long_name="Number of soil types", & range=[-huge(0.0), huge(0.0)], & active=.false., & restart=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(mstype) & + aggregator=new_aggregator(mstype), & + metadata=[ & + attribute("units", ""), & + attribute("long_name", "Number of soil types") & + ] & ) & ] @@ -181,27 +200,31 @@ function coordinate_variables(restart) result(output_variables) name="latitude", & data_shape=[DIM_LAND_GLOBAL], & var_type=CABLE_NETCDF_FLOAT, & - units="degrees_north", & - long_name="", & range=[-90.0, 90.0], & active=.false., & restart=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(latitude) & + aggregator=new_aggregator(latitude), & + metadata=[ & + attribute("units", "degrees_north"), & + attribute("long_name", "") & + ] & ), & cable_output_variable_t( & name="longitude", & data_shape=[DIM_LAND_GLOBAL], & var_type=CABLE_NETCDF_FLOAT, & - units="degrees_east", & - long_name="", & range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? active=.false., & restart=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(longitude) & + aggregator=new_aggregator(longitude), & + metadata=[ & + attribute("units", "degrees_east"), & + attribute("long_name", "") & + ] & ) & ] return @@ -214,56 +237,64 @@ function coordinate_variables(restart) result(output_variables) name="latitude", & data_shape=[DIM_X, DIM_Y], & var_type=CABLE_NETCDF_FLOAT, & - units="degrees_north", & - long_name="", & range=[-90.0, 90.0], & active=.true., & parameter=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(lat_all) & + aggregator=new_aggregator(lat_all), & + metadata=[ & + attribute("units", "degrees_north"), & + attribute("long_name", "") & + ] & ), & cable_output_variable_t( & name="longitude", & data_shape=[DIM_X, DIM_Y], & var_type=CABLE_NETCDF_FLOAT, & - units="degrees_east", & - long_name="", & range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? active=.true., & parameter=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(lon_all) & + aggregator=new_aggregator(lon_all), & + metadata=[ & + attribute("units", "degrees_east"), & + attribute("long_name", "") & + ] & ), & ! Write "cordinate variables" to enable reading by GrADS: cable_output_variable_t( & name="x", & data_shape=[DIM_X], & var_type=CABLE_NETCDF_FLOAT, & - units="degrees_east", & - long_name="", & - ! comment="x coordinate variable for GrADS compatibility", & range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? active=.true., & parameter=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(lon_all(:, 1)) & + aggregator=new_aggregator(lon_all(:, 1)), & + metadata=[ & + attribute("units", "degrees_east"), & + attribute("long_name", ""), & + attribute("comment", "x coordinate variable for GrADS compatibility") & + ] & ), & cable_output_variable_t( & name="y", & data_shape=[DIM_Y], & var_type=CABLE_NETCDF_FLOAT, & - units="degrees_north", & - long_name="", & - ! comment="y coordinate variable for GrADS compatibility", & range=[-90.0, 90.0], & ! TODO(Sean): this depends on the met forcing input? active=.true., & parameter=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(lat_all(1, :)) & + aggregator=new_aggregator(lat_all(1, :)), & + metadata=[ & + attribute("units", "degrees_north"), & + attribute("long_name", ""), & + attribute("comment", "y coordinate variable for GrADS compatibility") & + ] & ) & ] else if (requires_land_output_grid(output%grid, metgrid)) then @@ -272,27 +303,31 @@ function coordinate_variables(restart) result(output_variables) name="local_lat", & data_shape=[DIM_LAND_GLOBAL], & var_type=CABLE_NETCDF_FLOAT, & - units="degrees_north", & - long_name="", & range=[-90.0, 90.0], & active=requires_land_output_grid(output%grid, metgrid), & parameter=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(latitude) & + aggregator=new_aggregator(latitude), & + metadata=[ & + attribute("units", "degrees_north"), & + attribute("long_name", "") & + ] & ), & cable_output_variable_t( & name="local_lon", & data_shape=[DIM_LAND_GLOBAL], & var_type=CABLE_NETCDF_FLOAT, & - units="degrees_east", & - long_name="", & range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? active=requires_land_output_grid(output%grid, metgrid), & parameter=.true., & distributed=.false., & aggregation_method="point", & - aggregator=new_aggregator(longitude) & + aggregator=new_aggregator(longitude), & + metadata=[ & + attribute("units", "degrees_east"), & + attribute("long_name", "") & + ] & ) & ] else diff --git a/src/util/io/output/cable_output_types.F90 b/src/util/io/output/cable_output_types.F90 index 875776a58..4d0904777 100644 --- a/src/util/io/output/cable_output_types.F90 +++ b/src/util/io/output/cable_output_types.F90 @@ -14,13 +14,16 @@ module cable_output_types_mod type, extends(cable_enum_t), public :: cable_output_dim_t end type + type, public :: cable_output_attribute_t + character(len=64) :: name + character(len=256) :: value + end type + type, public :: cable_output_variable_t character(len=64) :: name - character(len=64) :: units character(len=64) :: accumulation_frequency = "all" character(len=64) :: reduction_method = "none" character(len=64) :: aggregation_method - character(len=256) :: long_name logical :: active logical :: parameter = .false. logical :: distributed = .true. @@ -30,6 +33,7 @@ module cable_output_types_mod real, dimension(2) :: range type(cable_output_dim_t), allocatable :: data_shape(:) class(aggregator_t), allocatable :: aggregator + type(cable_output_attribute_t), allocatable :: metadata(:) end type type, public :: cable_output_profile_t @@ -39,6 +43,7 @@ module cable_output_types_mod character(len=256) :: file_name class(cable_netcdf_file_t), allocatable :: output_file type(cable_output_variable_t), allocatable :: output_variables(:) + type(cable_output_attribute_t), allocatable :: metadata(:) end type type(cable_output_dim_t), parameter, public :: CABLE_OUTPUT_DIM_PATCH = cable_output_dim_t(0) diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index cba5721a3..5363494fa 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -611,8 +611,11 @@ subroutine define_variables(output_file, output_variables, restart) dim_names=infer_dim_names(output_var, restart_local), & type=output_var%var_type & ) - call output_file%put_att(output_var%name, "units", output_var%units) - call output_file%put_att(output_var%name, "long_name", output_var%long_name) + if (allocated(output_var%metadata)) then + do j = 1, size(output_var%metadata) + call output_file%put_att(output_var%name, output_var%metadata(j)%name, output_var%metadata(j)%value) + end do + end if select case (output_var%var_type) case (CABLE_NETCDF_INT) call output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_INT32) @@ -634,6 +637,16 @@ subroutine set_global_attributes(output_profile) type(cable_output_profile_t), intent(inout) :: output_profile character(32) :: todaydate, nowtime + integer :: i + + if (allocated(output_profile%metadata)) then + do i = 1, size(output_profile%metadata) + call output_profile%output_file%put_att( & + att_name=output_profile%metadata(i)%name, & + att_value=output_profile%metadata(i)%value & + ) + end do + end if call date_and_time(todaydate, nowtime) todaydate = todaydate(1:4) // "/" // todaydate(5:6) // "/" // todaydate(7:8) From 0e594f51aa7779fc287a88fe56d78c31590dd792 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Mon, 9 Feb 2026 20:12:57 +1100 Subject: [PATCH 20/31] Split out decomposition and reduction buffer functionality from cable_output_utils_mod Introduce `grid_type` component and replace grid based `if (restart)` with `select case(grid_type)` --- CMakeLists.txt | 2 + src/offline/cable_mpimaster.F90 | 2 +- src/offline/cable_serial.F90 | 2 +- src/util/io/output/cable_output_core.F90 | 233 +++--- src/util/io/output/cable_output_decomp.F90 | 633 +++++++++++++++ .../io/output/cable_output_definitions.F90 | 27 +- .../output/cable_output_reduction_buffers.F90 | 220 +++++ src/util/io/output/cable_output_types.F90 | 1 + src/util/io/output/cable_output_utils.F90 | 767 +++--------------- 9 files changed, 1090 insertions(+), 797 deletions(-) create mode 100644 src/util/io/output/cable_output_decomp.F90 create mode 100644 src/util/io/output/cable_output_reduction_buffers.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f93095f7..f3d4a0139 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,7 +312,9 @@ else() src/util/netcdf/cable_netcdf_stub_types.F90 src/util/netcdf/nf90/cable_netcdf_nf90.F90 src/util/io/output/cable_output_core.F90 + src/util/io/output/cable_output_decomp.F90 src/util/io/output/cable_output_definitions.F90 + src/util/io/output/cable_output_reduction_buffers.F90 src/util/io/output/cable_output_types.F90 src/util/io/output/cable_output_utils.F90 src/util/io/output/cable_output.F90 diff --git a/src/offline/cable_mpimaster.F90 b/src/offline/cable_mpimaster.F90 index 2551cee83..a6ade0384 100644 --- a/src/offline/cable_mpimaster.F90 +++ b/src/offline/cable_mpimaster.F90 @@ -650,7 +650,7 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) call cable_io_decomp_init(io_decomp) if (.not. casaonly) then - call cable_output_mod_init(io_decomp) + call cable_output_mod_init() call cable_output_register_output_variables(cable_output_core_outputs(canopy, soil)) call cable_output_profiles_init() end if diff --git a/src/offline/cable_serial.F90 b/src/offline/cable_serial.F90 index 790f825ae..7251ee6ca 100644 --- a/src/offline/cable_serial.F90 +++ b/src/offline/cable_serial.F90 @@ -477,7 +477,7 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi call cable_io_decomp_init(io_decomp) if (.not. casaonly) then - call cable_output_mod_init(io_decomp) + call cable_output_mod_init() call cable_output_register_output_variables(cable_output_core_outputs(canopy, soil)) call cable_output_profiles_init() end if diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 index c8af63505..67c80097d 100644 --- a/src/util/io/output/cable_output_core.F90 +++ b/src/util/io/output/cable_output_core.F90 @@ -6,6 +6,7 @@ module cable_output_core_mod use cable_io_vars_module, only: patch_type use cable_io_vars_module, only: land_type + use cable_io_vars_module, only: metgrid use cable_io_vars_module, only: output use cable_io_vars_module, only: check use cable_io_vars_module, only: ON_TIMESTEP @@ -36,8 +37,6 @@ module cable_output_core_mod use cable_checks_module, only: check_range - use cable_io_decomp_mod, only: io_decomp_t - use cable_timing_utils_mod, only: time_step_matches use cable_grid_reductions_mod, only: grid_cell_average @@ -49,19 +48,22 @@ module cable_output_core_mod use cable_output_types_mod, only: FILL_VALUE_REAL32 use cable_output_types_mod, only: FILL_VALUE_REAL64 - use cable_output_utils_mod, only: init_decomp_pointers - use cable_output_utils_mod, only: allocate_grid_reduction_buffers - use cable_output_utils_mod, only: deallocate_grid_reduction_buffers + use cable_output_reduction_buffers_mod, only: allocate_grid_reduction_buffers + use cable_output_reduction_buffers_mod, only: deallocate_grid_reduction_buffers + use cable_output_reduction_buffers_mod, only: associate_temp_buffer_int32 + use cable_output_reduction_buffers_mod, only: associate_temp_buffer_real32 + use cable_output_reduction_buffers_mod, only: associate_temp_buffer_real64 + + use cable_output_decomp_mod, only: allocate_decompositions + use cable_output_decomp_mod, only: deallocate_decompositions + use cable_output_decomp_mod, only: associate_decomp_int32 + use cable_output_decomp_mod, only: associate_decomp_real32 + use cable_output_decomp_mod, only: associate_decomp_real64 + use cable_output_utils_mod, only: check_invalid_frequency use cable_output_utils_mod, only: dim_size use cable_output_utils_mod, only: define_variables use cable_output_utils_mod, only: set_global_attributes - use cable_output_utils_mod, only: associate_decomp_int32 - use cable_output_utils_mod, only: associate_decomp_real32 - use cable_output_utils_mod, only: associate_decomp_real64 - use cable_output_utils_mod, only: associate_temp_buffer_int32 - use cable_output_utils_mod, only: associate_temp_buffer_real32 - use cable_output_utils_mod, only: associate_temp_buffer_real64 use cable_output_definitions_mod, only: coordinate_variables @@ -83,15 +85,25 @@ module cable_output_core_mod contains - subroutine cable_output_mod_init(io_decomp) - class(io_decomp_t), intent(in) :: io_decomp + subroutine cable_output_mod_init() class(cable_netcdf_file_t), allocatable :: output_file - call init_decomp_pointers(io_decomp) + call allocate_decompositions() call allocate_grid_reduction_buffers() end subroutine + subroutine cable_output_mod_end() + + if (allocated(global_profile%output_file)) call global_profile%output_file%close() + + deallocate(global_profile) + + call deallocate_grid_reduction_buffers() + call deallocate_decompositions() + + end subroutine + subroutine cable_output_register_output_variables(output_variables) type(cable_output_variable_t), dimension(:), intent(in) :: output_variables integer :: i @@ -127,30 +139,34 @@ subroutine cable_output_register_output_variables(output_variables) end subroutine cable_output_register_output_variables - subroutine cable_output_mod_end() - - if (allocated(global_profile%output_file)) call global_profile%output_file%close() - - deallocate(global_profile) - - call deallocate_grid_reduction_buffers() - - end subroutine - subroutine cable_output_profiles_init() class(cable_netcdf_file_t), allocatable :: output_file integer :: i - allocate(cable_output_profile_t::global_profile) - - global_profile%sampling_frequency = output%averaging - - global_profile%file_name = "test_output.nc" ! TODO(Sean): use filename from namelist + character(32) :: grid_type + + if (output%grid == "land" .OR. (output%grid == "default" .AND. metgrid == "land")) then + grid_type = "land" + else if (( & + output%grid == "default" .AND. metgrid == "mask" & + ) .OR. ( & + output%grid == "mask" .OR. output%grid == "ALMA" & + )) then + grid_type = "mask" + else + call cable_abort("Unable to determine output grid type.", __FILE__, __LINE__) + end if - global_profile%output_variables = [ & - coordinate_variables(), & - pack(registered_output_variables, registered_output_variables(:)%active) & - ] + global_profile = cable_output_profile_t( & + sampling_frequency=output%averaging, & + grid_type=grid_type, & + file_name="test_output.nc", & ! TODO(Sean): use filename from namelist + output_file=cable_netcdf_create_file("test_output.nc", iotype=CABLE_NETCDF_IOTYPE_CLASSIC), & ! TODO(Sean): use filename from namelist + output_variables=[ & + coordinate_variables(grid_type), & + pack(registered_output_variables, registered_output_variables(:)%active) & + ] & + ) do i = 1, size(global_profile%output_variables) associate(output_var => global_profile%output_variables(i)) @@ -163,9 +179,7 @@ subroutine cable_output_profiles_init() end associate end do - global_profile%output_file = cable_netcdf_create_file(global_profile%file_name, iotype=CABLE_NETCDF_IOTYPE_CLASSIC) - - call define_variables(global_profile%output_file, global_profile%output_variables) + call define_variables(global_profile) call set_global_attributes(global_profile) @@ -192,7 +206,7 @@ subroutine cable_output_write_parameters(time_index, patch, landpt, met) if (.not. output_variable%parameter) cycle call check_variable_range(output_variable, time_index, met) call output_variable%aggregator%accumulate() - call write_variable(output_variable, global_profile%output_file, patch, landpt) + call write_variable(global_profile, output_variable, patch, landpt) call output_variable%aggregator%reset() end associate end do @@ -243,7 +257,7 @@ subroutine cable_output_write(time_index, dels, leaps, start_year, met, patch, l associate(output_variable => global_profile%output_variables(i)) if (output_variable%parameter) cycle if (check%ranges == ON_WRITE) call check_variable_range(output_variable, time_index, met) - call write_variable(output_variable, global_profile%output_file, patch, landpt, global_profile%frame + 1) + call write_variable(global_profile, output_variable, patch, landpt, frame=global_profile%frame + 1) call output_variable%aggregator%reset() end associate end do @@ -267,29 +281,32 @@ end subroutine cable_output_write subroutine cable_output_write_restart(current_time) real, intent(in) :: current_time !! Current simulation time - class(cable_netcdf_file_t), allocatable :: restart_output_file - type(cable_output_variable_t), allocatable :: restart_variables(:) + type(cable_output_profile_t), allocatable :: restart_output_profile integer :: i - restart_variables = [ & - coordinate_variables(restart=.true.), & - pack(registered_output_variables, registered_output_variables(:)%restart) & - ] - - restart_output_file = cable_netcdf_create_file("test_restart.nc", iotype=CABLE_NETCDF_IOTYPE_CLASSIC) ! TODO(Sean): use filename from namelist + restart_output_profile = cable_output_profile_t( & + sampling_frequency="none", & + grid_type="restart", & + file_name="test_restart.nc", & ! TODO(Sean): use filename from namelist + output_file=cable_netcdf_create_file("test_restart.nc", iotype=CABLE_NETCDF_IOTYPE_CLASSIC), & ! TODO(Sean): use filename from namelist + output_variables=[ & + coordinate_variables(grid_type="restart"), & + pack(registered_output_variables, registered_output_variables(:)%restart) & + ] & + ) - call define_variables(restart_output_file, restart_variables, restart=.true.) + call define_variables(restart_output_profile) - call restart_output_file%end_def() + call restart_output_profile%output_file%end_def() - call restart_output_file%put_var("time", [current_time]) + call restart_output_profile%output_file%put_var("time", [current_time]) - do i = 1, size(restart_variables) - call write_variable(restart_variables(i), restart_output_file, restart=.true.) + do i = 1, size(restart_output_profile%output_variables) + call write_variable(restart_output_profile, restart_output_profile%output_variables(i), restart=.true.) end do - call restart_output_file%close() + call restart_output_profile%output_file%close() end subroutine cable_output_write_restart @@ -329,9 +346,9 @@ subroutine check_variable_range(output_variable, time_index, met) end subroutine check_variable_range - subroutine write_variable(output_variable, output_file, patch, landpt, frame, restart) + subroutine write_variable(output_profile, output_variable, patch, landpt, frame, restart) + type(cable_output_profile_t), intent(inout) :: output_profile type(cable_output_variable_t), intent(inout), target :: output_variable - class(cable_netcdf_file_t), intent(inout) :: output_file type(patch_type), intent(in), optional :: patch(:) type(land_type), intent(in), optional :: landpt(:) integer, intent(in), optional :: frame @@ -389,13 +406,13 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re write_buffer_int32_0d => aggregator%aggregated_data if (restart_local) write_buffer_int32_0d => aggregator%source_data if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_int32_0d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_int32_0d) end if @@ -416,21 +433,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_int32(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_int32(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_int32_1d, & decomp=decomp, & fill_value=FILL_VALUE_INT32, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_int32_1d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_int32_1d) end if @@ -451,21 +468,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_int32(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_int32(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_int32_2d, & decomp=decomp, & fill_value=FILL_VALUE_INT32, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_int32_2d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_int32_2d) end if @@ -486,21 +503,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_int32(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_int32(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_int32_3d, & decomp=decomp, & fill_value=FILL_VALUE_INT32, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_int32_3d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_int32_3d) end if @@ -514,13 +531,13 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re write_buffer_real32_0d => aggregator%aggregated_data if (restart_local) write_buffer_real32_0d => aggregator%source_data if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real32_0d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real32_0d) end if @@ -546,21 +563,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_real32(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_real32(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_real32_1d, & decomp=decomp, & fill_value=FILL_VALUE_REAL32, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real32_1d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real32_1d) end if @@ -586,21 +603,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_real32(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_real32(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_real32_2d, & decomp=decomp, & fill_value=FILL_VALUE_REAL32, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real32_2d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real32_2d) end if @@ -626,21 +643,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_real32(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_real32(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_real32_3d, & decomp=decomp, & fill_value=FILL_VALUE_REAL32, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real32_3d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real32_3d) end if @@ -654,13 +671,13 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re write_buffer_real64_0d => aggregator%aggregated_data if (restart_local) write_buffer_real64_0d => aggregator%source_data if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real64_0d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real64_0d) end if @@ -686,21 +703,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_real64(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_real64(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_real64_1d, & decomp=decomp, & fill_value=FILL_VALUE_REAL64, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real64_1d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real64_1d) end if @@ -726,21 +743,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_real64(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_real64(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_real64_2d, & decomp=decomp, & fill_value=FILL_VALUE_REAL64, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real64_2d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real64_2d) end if @@ -766,21 +783,21 @@ subroutine write_variable(output_variable, output_file, patch, landpt, frame, re call cable_abort("Invalid reduction method", __FILE__, __LINE__) end if if (output_variable%distributed) then - call associate_decomp_real64(output_variable, decomp, restart=restart_local) - call output_file%write_darray( & + call associate_decomp_real64(output_profile, output_variable, decomp) + call output_profile%output_file%write_darray( & var_name=output_variable%name, & values=write_buffer_real64_3d, & decomp=decomp, & fill_value=FILL_VALUE_REAL64, & frame=frame) else if (present(frame)) then - call output_file%inq_var_ndims(output_variable%name, ndims) - call output_file%put_var( & + call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real64_3d, & start=[(1, i = 1, ndims - 1), frame]) else - call output_file%put_var( & + call output_profile%output_file%put_var( & var_name=output_variable%name, & values=write_buffer_real64_3d) end if diff --git a/src/util/io/output/cable_output_decomp.F90 b/src/util/io/output/cable_output_decomp.F90 new file mode 100644 index 000000000..60229bb21 --- /dev/null +++ b/src/util/io/output/cable_output_decomp.F90 @@ -0,0 +1,633 @@ +module cable_output_decomp_mod + + use cable_abort_module, only: cable_abort + + use cable_def_types_mod, only: mp + use cable_def_types_mod, only: mp_global + use cable_def_types_mod, only: mland + use cable_def_types_mod, only: mland_global + use cable_def_types_mod, only: ms + use cable_def_types_mod, only: msn + use cable_def_types_mod, only: nrb + use cable_def_types_mod, only: ncs + use cable_def_types_mod, only: ncp + + use cable_io_vars_module, only: xdimsize + use cable_io_vars_module, only: ydimsize + use cable_io_vars_module, only: max_vegpatches + use cable_io_vars_module, only: land_x, land_y + use cable_io_vars_module, only: landpt + use cable_io_vars_module, only: land_decomp_start + use cable_io_vars_module, only: patch_decomp_start + + use cable_netcdf_mod, only: cable_netcdf_decomp_t + use cable_netcdf_mod, only: CABLE_NETCDF_INT + use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT + use cable_netcdf_mod, only: CABLE_NETCDF_DOUBLE + + use cable_netcdf_decomp_util_mod, only: dim_spec_t + use cable_netcdf_decomp_util_mod, only: io_decomp_land_to_x_y + use cable_netcdf_decomp_util_mod, only: io_decomp_patch_to_x_y_patch + use cable_netcdf_decomp_util_mod, only: io_decomp_land_to_land + use cable_netcdf_decomp_util_mod, only: io_decomp_patch_to_land_patch + use cable_netcdf_decomp_util_mod, only: io_decomp_patch_to_patch + + use cable_output_types_mod, only: cable_output_variable_t + use cable_output_types_mod, only: cable_output_profile_t + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PATCH + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOIL + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SNOW + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_RAD + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PLANTCARBON + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOILCARBON + + use cable_output_utils_mod, only: data_shape_eq + + implicit none + private + + public :: allocate_decompositions + public :: deallocate_decompositions + public :: associate_decomp_int32 + public :: associate_decomp_real32 + public :: associate_decomp_real64 + + type :: cable_output_decomp_t + class(cable_netcdf_decomp_t), allocatable :: land_int32 + class(cable_netcdf_decomp_t), allocatable :: land_real32 + class(cable_netcdf_decomp_t), allocatable :: land_real64 + class(cable_netcdf_decomp_t), allocatable :: land_soil_int32 + class(cable_netcdf_decomp_t), allocatable :: land_soil_real32 + class(cable_netcdf_decomp_t), allocatable :: land_soil_real64 + class(cable_netcdf_decomp_t), allocatable :: land_snow_int32 + class(cable_netcdf_decomp_t), allocatable :: land_snow_real32 + class(cable_netcdf_decomp_t), allocatable :: land_snow_real64 + class(cable_netcdf_decomp_t), allocatable :: land_rad_int32 + class(cable_netcdf_decomp_t), allocatable :: land_rad_real32 + class(cable_netcdf_decomp_t), allocatable :: land_rad_real64 + class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_int32 + class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_real32 + class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_real64 + class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_int32 + class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_real32 + class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_real64 + class(cable_netcdf_decomp_t), allocatable :: patch_int32 + class(cable_netcdf_decomp_t), allocatable :: patch_real32 + class(cable_netcdf_decomp_t), allocatable :: patch_real64 + class(cable_netcdf_decomp_t), allocatable :: patch_soil_int32 + class(cable_netcdf_decomp_t), allocatable :: patch_soil_real32 + class(cable_netcdf_decomp_t), allocatable :: patch_soil_real64 + class(cable_netcdf_decomp_t), allocatable :: patch_snow_int32 + class(cable_netcdf_decomp_t), allocatable :: patch_snow_real32 + class(cable_netcdf_decomp_t), allocatable :: patch_snow_real64 + class(cable_netcdf_decomp_t), allocatable :: patch_rad_int32 + class(cable_netcdf_decomp_t), allocatable :: patch_rad_real32 + class(cable_netcdf_decomp_t), allocatable :: patch_rad_real64 + class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_int32 + class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_real32 + class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_real64 + class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_int32 + class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_real32 + class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_real64 + end type + + type(cable_output_decomp_t), target :: decomp_grid_x_y + type(cable_output_decomp_t), target :: decomp_grid_land + type(cable_output_decomp_t), target :: decomp_grid_restart + +contains + + subroutine allocate_decompositions() + + type(dim_spec_t), allocatable :: mem_shape_land(:) + type(dim_spec_t), allocatable :: mem_shape_land_soil(:) + type(dim_spec_t), allocatable :: mem_shape_land_snow(:) + type(dim_spec_t), allocatable :: mem_shape_land_rad(:) + type(dim_spec_t), allocatable :: mem_shape_land_plantcarbon(:) + type(dim_spec_t), allocatable :: mem_shape_land_soilcarbon(:) + type(dim_spec_t), allocatable :: mem_shape_patch(:) + type(dim_spec_t), allocatable :: mem_shape_patch_soil(:) + type(dim_spec_t), allocatable :: mem_shape_patch_snow(:) + type(dim_spec_t), allocatable :: mem_shape_patch_rad(:) + type(dim_spec_t), allocatable :: mem_shape_patch_plantcarbon(:) + type(dim_spec_t), allocatable :: mem_shape_patch_soilcarbon(:) + + type(dim_spec_t), allocatable :: var_shape_x_y(:) + type(dim_spec_t), allocatable :: var_shape_x_y_soil(:) + type(dim_spec_t), allocatable :: var_shape_x_y_snow(:) + type(dim_spec_t), allocatable :: var_shape_x_y_rad(:) + type(dim_spec_t), allocatable :: var_shape_x_y_plantcarbon(:) + type(dim_spec_t), allocatable :: var_shape_x_y_soilcarbon(:) + type(dim_spec_t), allocatable :: var_shape_x_y_patch(:) + type(dim_spec_t), allocatable :: var_shape_x_y_patch_soil(:) + type(dim_spec_t), allocatable :: var_shape_x_y_patch_snow(:) + type(dim_spec_t), allocatable :: var_shape_x_y_patch_rad(:) + type(dim_spec_t), allocatable :: var_shape_x_y_patch_plantcarbon(:) + type(dim_spec_t), allocatable :: var_shape_x_y_patch_soilcarbon(:) + + type(dim_spec_t), allocatable :: var_shape_land(:) + type(dim_spec_t), allocatable :: var_shape_land_soil(:) + type(dim_spec_t), allocatable :: var_shape_land_snow(:) + type(dim_spec_t), allocatable :: var_shape_land_rad(:) + type(dim_spec_t), allocatable :: var_shape_land_plantcarbon(:) + type(dim_spec_t), allocatable :: var_shape_land_soilcarbon(:) + type(dim_spec_t), allocatable :: var_shape_land_patch(:) + type(dim_spec_t), allocatable :: var_shape_land_patch_soil(:) + type(dim_spec_t), allocatable :: var_shape_land_patch_snow(:) + type(dim_spec_t), allocatable :: var_shape_land_patch_rad(:) + type(dim_spec_t), allocatable :: var_shape_land_patch_plantcarbon(:) + type(dim_spec_t), allocatable :: var_shape_land_patch_soilcarbon(:) + + type(dim_spec_t), allocatable :: var_shape_patch(:) + type(dim_spec_t), allocatable :: var_shape_patch_soil(:) + type(dim_spec_t), allocatable :: var_shape_patch_snow(:) + type(dim_spec_t), allocatable :: var_shape_patch_rad(:) + type(dim_spec_t), allocatable :: var_shape_patch_plantcarbon(:) + type(dim_spec_t), allocatable :: var_shape_patch_soilcarbon(:) + + mem_shape_land = [dim_spec_t('land', mland)] + mem_shape_land_soil = [dim_spec_t('land', mland), dim_spec_t('soil', ms)] + mem_shape_land_snow = [dim_spec_t('land', mland), dim_spec_t('snow', msn)] + mem_shape_land_rad = [dim_spec_t('land', mland), dim_spec_t('rad', nrb)] + mem_shape_land_plantcarbon = [dim_spec_t('land', mland), dim_spec_t('plantcarbon', ncp)] + mem_shape_land_soilcarbon = [dim_spec_t('land', mland), dim_spec_t('soilcarbon', ncs)] + mem_shape_patch = [dim_spec_t('patch', mp)] + mem_shape_patch_soil = [dim_spec_t('patch', mp), dim_spec_t('soil', ms)] + mem_shape_patch_snow = [dim_spec_t('patch', mp), dim_spec_t('snow', msn)] + mem_shape_patch_rad = [dim_spec_t('patch', mp), dim_spec_t('rad', nrb)] + mem_shape_patch_plantcarbon = [dim_spec_t('patch', mp), dim_spec_t('plantcarbon', ncp)] + mem_shape_patch_soilcarbon = [dim_spec_t('patch', mp), dim_spec_t('soilcarbon', ncs)] + + var_shape_x_y = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize)] + var_shape_x_y_soil = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('soil', ms)] + var_shape_x_y_snow = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('snow', msn)] + var_shape_x_y_rad = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('rad', nrb)] + var_shape_x_y_plantcarbon = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('plantcarbon', ncp)] + var_shape_x_y_soilcarbon = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('soilcarbon', ncs)] + var_shape_x_y_patch = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches)] + var_shape_x_y_patch_soil = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('soil', ms)] + var_shape_x_y_patch_snow = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('snow', msn)] + var_shape_x_y_patch_rad = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('rad', nrb)] + var_shape_x_y_patch_plantcarbon = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('plantcarbon', ncp)] + var_shape_x_y_patch_soilcarbon = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('soilcarbon', ncs)] + + var_shape_land = [dim_spec_t('land', mland_global)] + var_shape_land_soil = [dim_spec_t('land', mland_global), dim_spec_t('soil', ms)] + var_shape_land_snow = [dim_spec_t('land', mland_global), dim_spec_t('snow', msn)] + var_shape_land_rad = [dim_spec_t('land', mland_global), dim_spec_t('rad', nrb)] + var_shape_land_plantcarbon = [dim_spec_t('land', mland_global), dim_spec_t('plantcarbon', ncp)] + var_shape_land_soilcarbon = [dim_spec_t('land', mland_global), dim_spec_t('soilcarbon', ncs)] + var_shape_land_patch = [dim_spec_t('land', mland_global)] + var_shape_land_patch_soil = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('soil', ms)] + var_shape_land_patch_snow = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('snow', msn)] + var_shape_land_patch_rad = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('rad', nrb)] + var_shape_land_patch_plantcarbon = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('plantcarbon', ncp)] + var_shape_land_patch_soilcarbon = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('soilcarbon', ncs)] + + var_shape_patch = [dim_spec_t('patch', mp_global)] + var_shape_patch_soil = [dim_spec_t('patch', mp_global), dim_spec_t('soil', ms)] + var_shape_patch_snow = [dim_spec_t('patch', mp_global), dim_spec_t('snow', msn)] + var_shape_patch_rad = [dim_spec_t('patch', mp_global), dim_spec_t('rad', nrb)] + var_shape_patch_plantcarbon = [dim_spec_t('patch', mp_global), dim_spec_t('plantcarbon', ncp)] + var_shape_patch_soilcarbon = [dim_spec_t('patch', mp_global), dim_spec_t('soilcarbon', ncs)] + + decomp_grid_x_y%land_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land, var_shape_x_y, CABLE_NETCDF_INT) + decomp_grid_x_y%land_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land, var_shape_x_y, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%land_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land, var_shape_x_y, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%land_soil_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soil, var_shape_x_y_soil, CABLE_NETCDF_INT) + decomp_grid_x_y%land_soil_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soil, var_shape_x_y_soil, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%land_soil_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soil, var_shape_x_y_soil, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%land_snow_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_snow, var_shape_x_y_snow, CABLE_NETCDF_INT) + decomp_grid_x_y%land_snow_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_snow, var_shape_x_y_snow, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%land_snow_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_snow, var_shape_x_y_snow, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%land_rad_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_rad, var_shape_x_y_rad, CABLE_NETCDF_INT) + decomp_grid_x_y%land_rad_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_rad, var_shape_x_y_rad, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%land_rad_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_rad, var_shape_x_y_rad, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%land_plantcarbon_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_plantcarbon, var_shape_x_y_plantcarbon, CABLE_NETCDF_INT) + decomp_grid_x_y%land_plantcarbon_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_plantcarbon, var_shape_x_y_plantcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%land_plantcarbon_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_plantcarbon, var_shape_x_y_plantcarbon, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%land_soilcarbon_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soilcarbon, var_shape_x_y_soilcarbon, CABLE_NETCDF_INT) + decomp_grid_x_y%land_soilcarbon_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soilcarbon, var_shape_x_y_soilcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%land_soilcarbon_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soilcarbon, var_shape_x_y_soilcarbon, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%patch_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_x_y_patch, CABLE_NETCDF_INT) + decomp_grid_x_y%patch_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_x_y_patch, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%patch_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_x_y_patch, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%patch_soil_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_x_y_patch_soil, CABLE_NETCDF_INT) + decomp_grid_x_y%patch_soil_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_x_y_patch_soil, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%patch_soil_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_x_y_patch_soil, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%patch_snow_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_x_y_patch_snow, CABLE_NETCDF_INT) + decomp_grid_x_y%patch_snow_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_x_y_patch_snow, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%patch_snow_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_x_y_patch_snow, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%patch_rad_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_x_y_patch_rad, CABLE_NETCDF_INT) + decomp_grid_x_y%patch_rad_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_x_y_patch_rad, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%patch_rad_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_x_y_patch_rad, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%patch_plantcarbon_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_x_y_patch_plantcarbon, CABLE_NETCDF_INT) + decomp_grid_x_y%patch_plantcarbon_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_x_y_patch_plantcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%patch_plantcarbon_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_x_y_patch_plantcarbon, CABLE_NETCDF_DOUBLE) + decomp_grid_x_y%patch_soilcarbon_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_x_y_patch_soilcarbon, CABLE_NETCDF_INT) + decomp_grid_x_y%patch_soilcarbon_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_x_y_patch_soilcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_x_y%patch_soilcarbon_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_x_y_patch_soilcarbon, CABLE_NETCDF_DOUBLE) + + decomp_grid_land%land_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land, var_shape_land, CABLE_NETCDF_INT) + decomp_grid_land%land_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land, var_shape_land, CABLE_NETCDF_FLOAT) + decomp_grid_land%land_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land, var_shape_land, CABLE_NETCDF_DOUBLE) + decomp_grid_land%land_soil_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soil, var_shape_land_soil, CABLE_NETCDF_INT) + decomp_grid_land%land_soil_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soil, var_shape_land_soil, CABLE_NETCDF_FLOAT) + decomp_grid_land%land_soil_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soil, var_shape_land_soil, CABLE_NETCDF_DOUBLE) + decomp_grid_land%land_snow_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_snow, var_shape_land_snow, CABLE_NETCDF_INT) + decomp_grid_land%land_snow_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_snow, var_shape_land_snow, CABLE_NETCDF_FLOAT) + decomp_grid_land%land_snow_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_snow, var_shape_land_snow, CABLE_NETCDF_DOUBLE) + decomp_grid_land%land_rad_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_rad, var_shape_land_rad, CABLE_NETCDF_INT) + decomp_grid_land%land_rad_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_rad, var_shape_land_rad, CABLE_NETCDF_FLOAT) + decomp_grid_land%land_rad_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_rad, var_shape_land_rad, CABLE_NETCDF_DOUBLE) + decomp_grid_land%land_plantcarbon_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_plantcarbon, var_shape_land_plantcarbon, CABLE_NETCDF_INT) + decomp_grid_land%land_plantcarbon_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_plantcarbon, var_shape_land_plantcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_land%land_plantcarbon_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_plantcarbon, var_shape_land_plantcarbon, CABLE_NETCDF_DOUBLE) + decomp_grid_land%land_soilcarbon_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soilcarbon, var_shape_land_soilcarbon, CABLE_NETCDF_INT) + decomp_grid_land%land_soilcarbon_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soilcarbon, var_shape_land_soilcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_land%land_soilcarbon_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soilcarbon, var_shape_land_soilcarbon, CABLE_NETCDF_DOUBLE) + decomp_grid_land%patch_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_land_patch, CABLE_NETCDF_INT) + decomp_grid_land%patch_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_land_patch, CABLE_NETCDF_FLOAT) + decomp_grid_land%patch_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_land_patch, CABLE_NETCDF_DOUBLE) + decomp_grid_land%patch_soil_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_land_patch_soil, CABLE_NETCDF_INT) + decomp_grid_land%patch_soil_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_land_patch_soil, CABLE_NETCDF_FLOAT) + decomp_grid_land%patch_soil_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_land_patch_soil, CABLE_NETCDF_DOUBLE) + decomp_grid_land%patch_snow_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_land_patch_snow, CABLE_NETCDF_INT) + decomp_grid_land%patch_snow_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_land_patch_snow, CABLE_NETCDF_FLOAT) + decomp_grid_land%patch_snow_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_land_patch_snow, CABLE_NETCDF_DOUBLE) + decomp_grid_land%patch_rad_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_land_patch_rad, CABLE_NETCDF_INT) + decomp_grid_land%patch_rad_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_land_patch_rad, CABLE_NETCDF_FLOAT) + decomp_grid_land%patch_rad_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_land_patch_rad, CABLE_NETCDF_DOUBLE) + decomp_grid_land%patch_plantcarbon_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_land_patch_plantcarbon, CABLE_NETCDF_INT) + decomp_grid_land%patch_plantcarbon_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_land_patch_plantcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_land%patch_plantcarbon_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_land_patch_plantcarbon, CABLE_NETCDF_DOUBLE) + decomp_grid_land%patch_soilcarbon_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_land_patch_soilcarbon, CABLE_NETCDF_INT) + decomp_grid_land%patch_soilcarbon_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_land_patch_soilcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_land%patch_soilcarbon_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_land_patch_soilcarbon, CABLE_NETCDF_DOUBLE) + + decomp_grid_restart%patch_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch, var_shape_patch, CABLE_NETCDF_INT) + decomp_grid_restart%patch_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch, var_shape_patch, CABLE_NETCDF_FLOAT) + decomp_grid_restart%patch_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch, var_shape_patch, CABLE_NETCDF_DOUBLE) + decomp_grid_restart%patch_soil_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soil, var_shape_patch_soil, CABLE_NETCDF_INT) + decomp_grid_restart%patch_soil_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soil, var_shape_patch_soil, CABLE_NETCDF_FLOAT) + decomp_grid_restart%patch_soil_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soil, var_shape_patch_soil, CABLE_NETCDF_DOUBLE) + decomp_grid_restart%patch_snow_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_snow, var_shape_patch_snow, CABLE_NETCDF_INT) + decomp_grid_restart%patch_snow_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_snow, var_shape_patch_snow, CABLE_NETCDF_FLOAT) + decomp_grid_restart%patch_snow_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_snow, var_shape_patch_snow, CABLE_NETCDF_DOUBLE) + decomp_grid_restart%patch_rad_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_rad, var_shape_patch_rad, CABLE_NETCDF_INT) + decomp_grid_restart%patch_rad_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_rad, var_shape_patch_rad, CABLE_NETCDF_FLOAT) + decomp_grid_restart%patch_rad_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_rad, var_shape_patch_rad, CABLE_NETCDF_DOUBLE) + decomp_grid_restart%patch_plantcarbon_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_plantcarbon, var_shape_patch_plantcarbon, CABLE_NETCDF_INT) + decomp_grid_restart%patch_plantcarbon_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_plantcarbon, var_shape_patch_plantcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_restart%patch_plantcarbon_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_plantcarbon, var_shape_patch_plantcarbon, CABLE_NETCDF_DOUBLE) + decomp_grid_restart%patch_soilcarbon_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soilcarbon, var_shape_patch_soilcarbon, CABLE_NETCDF_INT) + decomp_grid_restart%patch_soilcarbon_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soilcarbon, var_shape_patch_soilcarbon, CABLE_NETCDF_FLOAT) + decomp_grid_restart%patch_soilcarbon_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soilcarbon, var_shape_patch_soilcarbon, CABLE_NETCDF_DOUBLE) + + end subroutine + + subroutine deallocate_decompositions() + + deallocate(decomp_grid_x_y%land_int32) + deallocate(decomp_grid_x_y%land_real32) + deallocate(decomp_grid_x_y%land_real64) + deallocate(decomp_grid_x_y%land_soil_int32) + deallocate(decomp_grid_x_y%land_soil_real32) + deallocate(decomp_grid_x_y%land_soil_real64) + deallocate(decomp_grid_x_y%land_snow_int32) + deallocate(decomp_grid_x_y%land_snow_real32) + deallocate(decomp_grid_x_y%land_snow_real64) + deallocate(decomp_grid_x_y%land_rad_int32) + deallocate(decomp_grid_x_y%land_rad_real32) + deallocate(decomp_grid_x_y%land_rad_real64) + deallocate(decomp_grid_x_y%land_plantcarbon_int32) + deallocate(decomp_grid_x_y%land_plantcarbon_real32) + deallocate(decomp_grid_x_y%land_plantcarbon_real64) + deallocate(decomp_grid_x_y%land_soilcarbon_int32) + deallocate(decomp_grid_x_y%land_soilcarbon_real32) + deallocate(decomp_grid_x_y%land_soilcarbon_real64) + deallocate(decomp_grid_x_y%patch_int32) + deallocate(decomp_grid_x_y%patch_real32) + deallocate(decomp_grid_x_y%patch_real64) + deallocate(decomp_grid_x_y%patch_soil_int32) + deallocate(decomp_grid_x_y%patch_soil_real32) + deallocate(decomp_grid_x_y%patch_soil_real64) + deallocate(decomp_grid_x_y%patch_snow_int32) + deallocate(decomp_grid_x_y%patch_snow_real32) + deallocate(decomp_grid_x_y%patch_snow_real64) + deallocate(decomp_grid_x_y%patch_rad_int32) + deallocate(decomp_grid_x_y%patch_rad_real32) + deallocate(decomp_grid_x_y%patch_rad_real64) + deallocate(decomp_grid_x_y%patch_plantcarbon_int32) + deallocate(decomp_grid_x_y%patch_plantcarbon_real32) + deallocate(decomp_grid_x_y%patch_plantcarbon_real64) + deallocate(decomp_grid_x_y%patch_soilcarbon_int32) + deallocate(decomp_grid_x_y%patch_soilcarbon_real32) + deallocate(decomp_grid_x_y%patch_soilcarbon_real64) + + deallocate(decomp_grid_land%land_int32) + deallocate(decomp_grid_land%land_real32) + deallocate(decomp_grid_land%land_real64) + deallocate(decomp_grid_land%land_soil_int32) + deallocate(decomp_grid_land%land_soil_real32) + deallocate(decomp_grid_land%land_soil_real64) + deallocate(decomp_grid_land%land_snow_int32) + deallocate(decomp_grid_land%land_snow_real32) + deallocate(decomp_grid_land%land_snow_real64) + deallocate(decomp_grid_land%land_rad_int32) + deallocate(decomp_grid_land%land_rad_real32) + deallocate(decomp_grid_land%land_rad_real64) + deallocate(decomp_grid_land%land_plantcarbon_int32) + deallocate(decomp_grid_land%land_plantcarbon_real32) + deallocate(decomp_grid_land%land_plantcarbon_real64) + deallocate(decomp_grid_land%land_soilcarbon_int32) + deallocate(decomp_grid_land%land_soilcarbon_real32) + deallocate(decomp_grid_land%land_soilcarbon_real64) + deallocate(decomp_grid_land%patch_int32) + deallocate(decomp_grid_land%patch_real32) + deallocate(decomp_grid_land%patch_real64) + deallocate(decomp_grid_land%patch_soil_int32) + deallocate(decomp_grid_land%patch_soil_real32) + deallocate(decomp_grid_land%patch_soil_real64) + deallocate(decomp_grid_land%patch_snow_int32) + deallocate(decomp_grid_land%patch_snow_real32) + deallocate(decomp_grid_land%patch_snow_real64) + deallocate(decomp_grid_land%patch_rad_int32) + deallocate(decomp_grid_land%patch_rad_real32) + deallocate(decomp_grid_land%patch_rad_real64) + deallocate(decomp_grid_land%patch_plantcarbon_int32) + deallocate(decomp_grid_land%patch_plantcarbon_real32) + deallocate(decomp_grid_land%patch_plantcarbon_real64) + deallocate(decomp_grid_land%patch_soilcarbon_int32) + deallocate(decomp_grid_land%patch_soilcarbon_real32) + deallocate(decomp_grid_land%patch_soilcarbon_real64) + + deallocate(decomp_grid_restart%patch_int32) + deallocate(decomp_grid_restart%patch_real32) + deallocate(decomp_grid_restart%patch_real64) + deallocate(decomp_grid_restart%patch_soil_int32) + deallocate(decomp_grid_restart%patch_soil_real32) + deallocate(decomp_grid_restart%patch_soil_real64) + deallocate(decomp_grid_restart%patch_snow_int32) + deallocate(decomp_grid_restart%patch_snow_real32) + deallocate(decomp_grid_restart%patch_snow_real64) + deallocate(decomp_grid_restart%patch_rad_int32) + deallocate(decomp_grid_restart%patch_rad_real32) + deallocate(decomp_grid_restart%patch_rad_real64) + deallocate(decomp_grid_restart%patch_plantcarbon_int32) + deallocate(decomp_grid_restart%patch_plantcarbon_real32) + deallocate(decomp_grid_restart%patch_plantcarbon_real64) + deallocate(decomp_grid_restart%patch_soilcarbon_int32) + deallocate(decomp_grid_restart%patch_soilcarbon_real32) + deallocate(decomp_grid_restart%patch_soilcarbon_real64) + + end subroutine + + subroutine associate_decomp_int32(output_profile, output_var, decomp) + type(cable_output_profile_t), intent(in) :: output_profile + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + type(cable_output_decomp_t), pointer :: output_decomp + + select case (output_profile%grid_type) + case ("restart") + call associate_decomp_restart_int32(output_var, decomp) + return + case ("mask") + output_decomp => decomp_grid_x_y + case ("land") + output_decomp => decomp_grid_land + case default + call cable_abort("Unsupported grid type for output profile " // output_profile%file_name, __FILE__, __LINE__) + end select + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_int32 + else + decomp => output_decomp%land_int32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_soil_int32 + else + decomp => output_decomp%land_soil_int32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_snow_int32 + else + decomp => output_decomp%land_snow_int32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_rad_int32 + else + decomp => output_decomp%land_rad_int32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_plantcarbon_int32 + else + decomp => output_decomp%land_plantcarbon_int32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_soilcarbon_int32 + else + decomp => output_decomp%land_soilcarbon_int32 + end if + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_int32 + + subroutine associate_decomp_real32(output_profile, output_var, decomp) + type(cable_output_profile_t), intent(in) :: output_profile + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + type(cable_output_decomp_t), pointer :: output_decomp + + select case (output_profile%grid_type) + case ("restart") + call associate_decomp_restart_real32(output_var, decomp) + return + case ("mask") + output_decomp => decomp_grid_x_y + case ("land") + output_decomp => decomp_grid_land + case default + call cable_abort("Unsupported grid type for output profile " // output_profile%file_name, __FILE__, __LINE__) + end select + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_real32 + else + decomp => output_decomp%land_real32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_soil_real32 + else + decomp => output_decomp%land_soil_real32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_snow_real32 + else + decomp => output_decomp%land_snow_real32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_rad_real32 + else + decomp => output_decomp%land_rad_real32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_plantcarbon_real32 + else + decomp => output_decomp%land_plantcarbon_real32 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_soilcarbon_real32 + else + decomp => output_decomp%land_soilcarbon_real32 + end if + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_real32 + + subroutine associate_decomp_real64(output_profile, output_var, decomp) + type(cable_output_profile_t), intent(in) :: output_profile + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + type(cable_output_decomp_t), pointer :: output_decomp + + select case (output_profile%grid_type) + case ("restart") + call associate_decomp_restart_real64(output_var, decomp) + return + case ("mask") + output_decomp => decomp_grid_x_y + case ("land") + output_decomp => decomp_grid_land + case default + call cable_abort("Unsupported grid type for output profile " // output_profile%file_name, __FILE__, __LINE__) + end select + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_real64 + else + decomp => output_decomp%land_real64 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_soil_real64 + else + decomp => output_decomp%land_soil_real64 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_snow_real64 + else + decomp => output_decomp%land_snow_real64 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_rad_real64 + else + decomp => output_decomp%land_rad_real64 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_plantcarbon_real64 + else + decomp => output_decomp%land_plantcarbon_real64 + end if + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (output_var%reduction_method == "none") then + decomp => output_decomp%patch_soilcarbon_real64 + else + decomp => output_decomp%land_soilcarbon_real64 + end if + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_real64 + + subroutine associate_decomp_restart_int32(output_var, decomp) + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + type(cable_output_decomp_t), pointer :: output_decomp + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + decomp => decomp_grid_restart%patch_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + decomp => decomp_grid_restart%patch_soil_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + decomp => decomp_grid_restart%patch_snow_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + decomp => decomp_grid_restart%patch_rad_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + decomp => decomp_grid_restart%patch_plantcarbon_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + decomp => decomp_grid_restart%patch_soilcarbon_int32 + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_restart_int32 + + subroutine associate_decomp_restart_real32(output_var, decomp) + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + decomp => decomp_grid_restart%patch_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + decomp => decomp_grid_restart%patch_soil_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + decomp => decomp_grid_restart%patch_snow_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + decomp => decomp_grid_restart%patch_rad_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + decomp => decomp_grid_restart%patch_plantcarbon_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + decomp => decomp_grid_restart%patch_soilcarbon_real32 + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_restart_real32 + + subroutine associate_decomp_restart_real64(output_var, decomp) + type(cable_output_variable_t), intent(in) :: output_var + class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + decomp => decomp_grid_restart%patch_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + decomp => decomp_grid_restart%patch_soil_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + decomp => decomp_grid_restart%patch_snow_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + decomp => decomp_grid_restart%patch_rad_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + decomp => decomp_grid_restart%patch_plantcarbon_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + decomp => decomp_grid_restart%patch_soilcarbon_real64 + else + call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + end if + + end subroutine associate_decomp_restart_real64 + +end module diff --git a/src/util/io/output/cable_output_definitions.F90 b/src/util/io/output/cable_output_definitions.F90 index 985c18178..afdddede1 100644 --- a/src/util/io/output/cable_output_definitions.F90 +++ b/src/util/io/output/cable_output_definitions.F90 @@ -13,7 +13,6 @@ module cable_output_definitions_mod use cable_io_vars_module, only: patch use cable_io_vars_module, only: lat_all, lon_all use cable_io_vars_module, only: latitude, longitude - use cable_io_vars_module, only: metgrid use cable_output_types_mod, only: cable_output_variable_t use cable_output_types_mod, only: attribute => cable_output_attribute_t @@ -25,9 +24,6 @@ module cable_output_definitions_mod use cable_output_types_mod, only: DIM_X => CABLE_OUTPUT_DIM_X use cable_output_types_mod, only: DIM_Y => CABLE_OUTPUT_DIM_Y - use cable_output_utils_mod, only: requires_x_y_output_grid - use cable_output_utils_mod, only: requires_land_output_grid - use cable_checks_module, only: ranges use cable_abort_module, only: cable_abort @@ -189,12 +185,14 @@ function core_outputs(canopy, soil) result(output_variables) end function core_outputs - function coordinate_variables(restart) result(output_variables) + function coordinate_variables(grid_type, restart) result(output_variables) + character(len=*), intent(in) :: grid_type logical, intent(in), optional :: restart type(cable_output_variable_t), allocatable :: output_variables(:) - if (present(restart)) then; if (restart) then + select case (grid_type) + case ("restart") output_variables = [ & cable_output_variable_t( & name="latitude", & @@ -227,10 +225,7 @@ function coordinate_variables(restart) result(output_variables) ] & ) & ] - return - end if; end if - - if (requires_x_y_output_grid(output%grid, metgrid)) then + case ("mask") output_variables = [ & ! Define latitude and longitude variable (ALMA): cable_output_variable_t( & @@ -297,14 +292,14 @@ function coordinate_variables(restart) result(output_variables) ] & ) & ] - else if (requires_land_output_grid(output%grid, metgrid)) then + case ("land") output_variables = [ & cable_output_variable_t( & name="local_lat", & data_shape=[DIM_LAND_GLOBAL], & var_type=CABLE_NETCDF_FLOAT, & range=[-90.0, 90.0], & - active=requires_land_output_grid(output%grid, metgrid), & + active=.true., & parameter=.true., & distributed=.false., & aggregation_method="point", & @@ -319,7 +314,7 @@ function coordinate_variables(restart) result(output_variables) data_shape=[DIM_LAND_GLOBAL], & var_type=CABLE_NETCDF_FLOAT, & range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? - active=requires_land_output_grid(output%grid, metgrid), & + active=.true., & parameter=.true., & distributed=.false., & aggregation_method="point", & @@ -330,9 +325,9 @@ function coordinate_variables(restart) result(output_variables) ] & ) & ] - else - call cable_abort("Unable to determine coordinate variables for output grid.", __FILE__, __LINE__) - end if + case default + call cable_abort("Unexpected grid type '" // grid_type // "'", __FILE__, __LINE__) + end select end function diff --git a/src/util/io/output/cable_output_reduction_buffers.F90 b/src/util/io/output/cable_output_reduction_buffers.F90 new file mode 100644 index 000000000..1fc9d4bb4 --- /dev/null +++ b/src/util/io/output/cable_output_reduction_buffers.F90 @@ -0,0 +1,220 @@ +module cable_output_reduction_buffers_mod + + use cable_abort_module, only: cable_abort + + use iso_fortran_env, only: int32, real32, real64 + + use cable_def_types_mod, only: mland + use cable_def_types_mod, only: ms + use cable_def_types_mod, only: msn + use cable_def_types_mod, only: nrb + use cable_def_types_mod, only: ncs + use cable_def_types_mod, only: ncp + + use cable_output_types_mod, only: cable_output_variable_t + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PATCH + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOIL + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SNOW + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_RAD + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PLANTCARBON + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOILCARBON + + use cable_output_utils_mod, only: data_shape_eq + + implicit none + private + + public :: allocate_grid_reduction_buffers + public :: deallocate_grid_reduction_buffers + public :: associate_temp_buffer_int32 + public :: associate_temp_buffer_real32 + public :: associate_temp_buffer_real64 + + integer(kind=int32), allocatable, target :: temp_buffer_land_int32(:) + real(kind=real32), allocatable, target :: temp_buffer_land_real32(:) + real(kind=real64), allocatable, target :: temp_buffer_land_real64(:) + integer(kind=int32), allocatable, target :: temp_buffer_land_soil_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_soil_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_soil_real64(:, :) + integer(kind=int32), allocatable, target :: temp_buffer_land_snow_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_snow_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_snow_real64(:, :) + integer(kind=int32), allocatable, target :: temp_buffer_land_rad_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_rad_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_rad_real64(:, :) + integer(kind=int32), allocatable, target :: temp_buffer_land_plantcarbon_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_plantcarbon_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_plantcarbon_real64(:, :) + integer(kind=int32), allocatable, target :: temp_buffer_land_soilcarbon_int32(:, :) + real(kind=real32), allocatable, target :: temp_buffer_land_soilcarbon_real32(:, :) + real(kind=real64), allocatable, target :: temp_buffer_land_soilcarbon_real64(:, :) + +contains + + subroutine allocate_grid_reduction_buffers() + + allocate(temp_buffer_land_int32(mland)) + allocate(temp_buffer_land_real32(mland)) + allocate(temp_buffer_land_real64(mland)) + allocate(temp_buffer_land_soil_int32(mland, ms)) + allocate(temp_buffer_land_soil_real32(mland, ms)) + allocate(temp_buffer_land_soil_real64(mland, ms)) + allocate(temp_buffer_land_snow_int32(mland, msn)) + allocate(temp_buffer_land_snow_real32(mland, msn)) + allocate(temp_buffer_land_snow_real64(mland, msn)) + allocate(temp_buffer_land_rad_int32(mland, nrb)) + allocate(temp_buffer_land_rad_real32(mland, nrb)) + allocate(temp_buffer_land_rad_real64(mland, nrb)) + allocate(temp_buffer_land_plantcarbon_int32(mland, ncp)) + allocate(temp_buffer_land_plantcarbon_real32(mland, ncp)) + allocate(temp_buffer_land_plantcarbon_real64(mland, ncp)) + allocate(temp_buffer_land_soilcarbon_int32(mland, ncs)) + allocate(temp_buffer_land_soilcarbon_real32(mland, ncs)) + allocate(temp_buffer_land_soilcarbon_real64(mland, ncs)) + + end subroutine + + subroutine deallocate_grid_reduction_buffers() + + deallocate(temp_buffer_land_int32) + deallocate(temp_buffer_land_real32) + deallocate(temp_buffer_land_real64) + deallocate(temp_buffer_land_soil_int32) + deallocate(temp_buffer_land_soil_real32) + deallocate(temp_buffer_land_soil_real64) + deallocate(temp_buffer_land_snow_int32) + deallocate(temp_buffer_land_snow_real32) + deallocate(temp_buffer_land_snow_real64) + deallocate(temp_buffer_land_rad_int32) + deallocate(temp_buffer_land_rad_real32) + deallocate(temp_buffer_land_rad_real64) + deallocate(temp_buffer_land_plantcarbon_int32) + deallocate(temp_buffer_land_plantcarbon_real32) + deallocate(temp_buffer_land_plantcarbon_real64) + deallocate(temp_buffer_land_soilcarbon_int32) + deallocate(temp_buffer_land_soilcarbon_real32) + deallocate(temp_buffer_land_soilcarbon_real64) + + end subroutine + + subroutine associate_temp_buffer_int32(output_var, temp_buffer_int32_1d, temp_buffer_int32_2d, temp_buffer_int32_3d) + type(cable_output_variable_t), intent(inout) :: output_var + integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_1d(:) + integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_2d(:,:) + integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_3d(:,:,:) + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + if (.not. present(temp_buffer_int32_1d)) call cable_abort( & + "temp_buffer_int32_1d must be provided for 1D data shape", __FILE__, __LINE__) + temp_buffer_int32_1d => temp_buffer_land_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_soil_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_rad_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_snow_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_rad_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_plantcarbon_int32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (.not. present(temp_buffer_int32_2d)) call cable_abort( & + "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_int32_2d => temp_buffer_land_soilcarbon_int32 + else + call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) + end if + + end subroutine associate_temp_buffer_int32 + + subroutine associate_temp_buffer_real32(output_var, temp_buffer_real32_1d, temp_buffer_real32_2d, temp_buffer_real32_3d) + type(cable_output_variable_t), intent(inout) :: output_var + real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_1d(:) + real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_2d(:,:) + real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_3d(:,:,:) + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + if (.not. present(temp_buffer_real32_1d)) call cable_abort( & + "temp_buffer_real32_1d must be provided for 1D data shape", __FILE__, __LINE__) + temp_buffer_real32_1d => temp_buffer_land_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_soil_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_rad_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_snow_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_rad_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_plantcarbon_real32 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (.not. present(temp_buffer_real32_2d)) call cable_abort( & + "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real32_2d => temp_buffer_land_soilcarbon_real32 + else + call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) + end if + + end subroutine associate_temp_buffer_real32 + + subroutine associate_temp_buffer_real64(output_var, temp_buffer_real64_1d, temp_buffer_real64_2d, temp_buffer_real64_3d) + type(cable_output_variable_t), intent(inout) :: output_var + real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_1d(:) + real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_2d(:,:) + real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_3d(:,:,:) + + if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then + if (.not. present(temp_buffer_real64_1d)) call cable_abort( & + "temp_buffer_real64_1d must be provided for 1D data shape", __FILE__, __LINE__) + temp_buffer_real64_1d => temp_buffer_land_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_soil_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_rad_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_snow_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_rad_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_plantcarbon_real64 + else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then + if (.not. present(temp_buffer_real64_2d)) call cable_abort( & + "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) + temp_buffer_real64_2d => temp_buffer_land_soilcarbon_real64 + else + call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) + end if + + end subroutine associate_temp_buffer_real64 + +end module diff --git a/src/util/io/output/cable_output_types.F90 b/src/util/io/output/cable_output_types.F90 index 4d0904777..e24a0cb09 100644 --- a/src/util/io/output/cable_output_types.F90 +++ b/src/util/io/output/cable_output_types.F90 @@ -40,6 +40,7 @@ module cable_output_types_mod real :: previous_write_time = 0.0 integer :: frame = 0 character(len=64) :: sampling_frequency + character(len=64) :: grid_type character(len=256) :: file_name class(cable_netcdf_file_t), allocatable :: output_file type(cable_output_variable_t), allocatable :: output_variables(:) diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 5363494fa..646840ccd 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -1,7 +1,5 @@ module cable_output_utils_mod - use iso_fortran_env, only: int32, real32, real64 - use cable_common_module, only: filename use cable_def_types_mod, only: mp @@ -14,8 +12,6 @@ module cable_output_utils_mod use cable_def_types_mod, only: ncs use cable_def_types_mod, only: ncp - use cable_io_vars_module, only: output - use cable_io_vars_module, only: metgrid use cable_io_vars_module, only: xdimsize use cable_io_vars_module, only: ydimsize use cable_io_vars_module, only: max_vegpatches @@ -25,16 +21,12 @@ module cable_output_utils_mod use cable_abort_module, only: cable_abort - use cable_netcdf_mod, only: cable_netcdf_decomp_t - use cable_netcdf_mod, only: cable_netcdf_file_t use cable_netcdf_mod, only: MAX_LEN_DIM => CABLE_NETCDF_MAX_STR_LEN_DIM use cable_netcdf_mod, only: CABLE_NETCDF_UNLIMITED use cable_netcdf_mod, only: CABLE_NETCDF_INT use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT use cable_netcdf_mod, only: CABLE_NETCDF_DOUBLE - use cable_io_decomp_mod, only: io_decomp_t - use cable_output_types_mod, only: cable_output_dim_t use cable_output_types_mod, only: cable_output_variable_t use cable_output_types_mod, only: cable_output_profile_t @@ -55,300 +47,52 @@ module cable_output_utils_mod implicit none private - public :: init_decomp_pointers - public :: allocate_grid_reduction_buffers - public :: deallocate_grid_reduction_buffers - public :: requires_x_y_output_grid - public :: requires_land_output_grid public :: check_invalid_frequency public :: dim_size public :: infer_dim_names public :: define_variables public :: set_global_attributes - public :: associate_decomp_int32 - public :: associate_decomp_real32 - public :: associate_decomp_real64 - public :: associate_temp_buffer_int32 - public :: associate_temp_buffer_real32 - public :: associate_temp_buffer_real64 - - ! Decomposition pointers for each variable class and data type - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soil_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soil_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soil_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_snow_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_snow_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_snow_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_rad_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_rad_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_rad_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_plantcarbon_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_plantcarbon_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_plantcarbon_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soilcarbon_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soilcarbon_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_soilcarbon_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soil_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soil_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soil_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_snow_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_snow_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_snow_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_rad_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_rad_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_rad_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_plantcarbon_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_plantcarbon_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_plantcarbon_real64 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soilcarbon_int32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soilcarbon_real32 - class(cable_netcdf_decomp_t), pointer :: output_decomp_base_patch_soilcarbon_real64 - - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_int32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_real32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_real64 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soil_int32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soil_real32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soil_real64 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_snow_int32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_snow_real32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_snow_real64 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_rad_int32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_rad_real32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_rad_real64 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_plantcarbon_int32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_plantcarbon_real32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_plantcarbon_real64 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soilcarbon_int32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soilcarbon_real32 - class(cable_netcdf_decomp_t), pointer :: restart_decomp_patch_soilcarbon_real64 - - ! Temporary buffers for computing grid-cell averages for each variable class - integer(kind=int32), allocatable, target :: temp_buffer_land_int32(:) - real(kind=real32), allocatable, target :: temp_buffer_land_real32(:) - real(kind=real64), allocatable, target :: temp_buffer_land_real64(:) - integer(kind=int32), allocatable, target :: temp_buffer_land_soil_int32(:, :) - real(kind=real32), allocatable, target :: temp_buffer_land_soil_real32(:, :) - real(kind=real64), allocatable, target :: temp_buffer_land_soil_real64(:, :) - integer(kind=int32), allocatable, target :: temp_buffer_land_snow_int32(:, :) - real(kind=real32), allocatable, target :: temp_buffer_land_snow_real32(:, :) - real(kind=real64), allocatable, target :: temp_buffer_land_snow_real64(:, :) - integer(kind=int32), allocatable, target :: temp_buffer_land_rad_int32(:, :) - real(kind=real32), allocatable, target :: temp_buffer_land_rad_real32(:, :) - real(kind=real64), allocatable, target :: temp_buffer_land_rad_real64(:, :) - integer(kind=int32), allocatable, target :: temp_buffer_land_plantcarbon_int32(:, :) - real(kind=real32), allocatable, target :: temp_buffer_land_plantcarbon_real32(:, :) - real(kind=real64), allocatable, target :: temp_buffer_land_plantcarbon_real64(:, :) - integer(kind=int32), allocatable, target :: temp_buffer_land_soilcarbon_int32(:, :) - real(kind=real32), allocatable, target :: temp_buffer_land_soilcarbon_real32(:, :) - real(kind=real64), allocatable, target :: temp_buffer_land_soilcarbon_real64(:, :) + public :: data_shape_eq contains - subroutine init_decomp_pointers(io_decomp) - type(io_decomp_t), intent(in), target :: io_decomp - - if (requires_x_y_output_grid(output%grid, metGrid)) then - output_decomp_base_int32 => io_decomp%land_to_x_y_int32 - output_decomp_base_real32 => io_decomp%land_to_x_y_real32 - output_decomp_base_real64 => io_decomp%land_to_x_y_real64 - output_decomp_base_soil_int32 => io_decomp%land_soil_to_x_y_soil_int32 - output_decomp_base_soil_real32 => io_decomp%land_soil_to_x_y_soil_real32 - output_decomp_base_soil_real64 => io_decomp%land_soil_to_x_y_soil_real64 - output_decomp_base_snow_int32 => io_decomp%land_snow_to_x_y_snow_int32 - output_decomp_base_snow_real32 => io_decomp%land_snow_to_x_y_snow_real32 - output_decomp_base_snow_real64 => io_decomp%land_snow_to_x_y_snow_real64 - output_decomp_base_rad_int32 => io_decomp%land_rad_to_x_y_rad_int32 - output_decomp_base_rad_real32 => io_decomp%land_rad_to_x_y_rad_real32 - output_decomp_base_rad_real64 => io_decomp%land_rad_to_x_y_rad_real64 - output_decomp_base_plantcarbon_int32 => io_decomp%land_plantcarbon_to_x_y_plantcarbon_int32 - output_decomp_base_plantcarbon_real32 => io_decomp%land_plantcarbon_to_x_y_plantcarbon_real32 - output_decomp_base_plantcarbon_real64 => io_decomp%land_plantcarbon_to_x_y_plantcarbon_real64 - output_decomp_base_soilcarbon_int32 => io_decomp%land_soilcarbon_to_x_y_soilcarbon_int32 - output_decomp_base_soilcarbon_real32 => io_decomp%land_soilcarbon_to_x_y_soilcarbon_real32 - output_decomp_base_soilcarbon_real64 => io_decomp%land_soilcarbon_to_x_y_soilcarbon_real64 - output_decomp_base_patch_int32 => io_decomp%patch_to_x_y_patch_int32 - output_decomp_base_patch_real32 => io_decomp%patch_to_x_y_patch_real32 - output_decomp_base_patch_real64 => io_decomp%patch_to_x_y_patch_real64 - output_decomp_base_patch_soil_int32 => io_decomp%patch_soil_to_x_y_patch_soil_int32 - output_decomp_base_patch_soil_real32 => io_decomp%patch_soil_to_x_y_patch_soil_real32 - output_decomp_base_patch_soil_real64 => io_decomp%patch_soil_to_x_y_patch_soil_real64 - output_decomp_base_patch_snow_int32 => io_decomp%patch_snow_to_x_y_patch_snow_int32 - output_decomp_base_patch_snow_real32 => io_decomp%patch_snow_to_x_y_patch_snow_real32 - output_decomp_base_patch_snow_real64 => io_decomp%patch_snow_to_x_y_patch_snow_real64 - output_decomp_base_patch_rad_int32 => io_decomp%patch_rad_to_x_y_patch_rad_int32 - output_decomp_base_patch_rad_real32 => io_decomp%patch_rad_to_x_y_patch_rad_real32 - output_decomp_base_patch_rad_real64 => io_decomp%patch_rad_to_x_y_patch_rad_real64 - output_decomp_base_patch_plantcarbon_int32 => io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_int32 - output_decomp_base_patch_plantcarbon_real32 => io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_real32 - output_decomp_base_patch_plantcarbon_real64 => io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_real64 - output_decomp_base_patch_soilcarbon_int32 => io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_int32 - output_decomp_base_patch_soilcarbon_real32 => io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_real32 - output_decomp_base_patch_soilcarbon_real64 => io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_real64 - else if (requires_land_output_grid(output%grid, metGrid)) then - output_decomp_base_int32 => io_decomp%land_to_land_int32 - output_decomp_base_real32 => io_decomp%land_to_land_real32 - output_decomp_base_real64 => io_decomp%land_to_land_real64 - output_decomp_base_soil_int32 => io_decomp%land_soil_to_land_soil_int32 - output_decomp_base_soil_real32 => io_decomp%land_soil_to_land_soil_real32 - output_decomp_base_soil_real64 => io_decomp%land_soil_to_land_soil_real64 - output_decomp_base_snow_int32 => io_decomp%land_snow_to_land_snow_int32 - output_decomp_base_snow_real32 => io_decomp%land_snow_to_land_snow_real32 - output_decomp_base_snow_real64 => io_decomp%land_snow_to_land_snow_real64 - output_decomp_base_rad_int32 => io_decomp%land_rad_to_land_rad_int32 - output_decomp_base_rad_real32 => io_decomp%land_rad_to_land_rad_real32 - output_decomp_base_rad_real64 => io_decomp%land_rad_to_land_rad_real64 - output_decomp_base_plantcarbon_int32 => io_decomp%land_plantcarbon_to_land_plantcarbon_int32 - output_decomp_base_plantcarbon_real32 => io_decomp%land_plantcarbon_to_land_plantcarbon_real32 - output_decomp_base_plantcarbon_real64 => io_decomp%land_plantcarbon_to_land_plantcarbon_real64 - output_decomp_base_soilcarbon_int32 => io_decomp%land_soilcarbon_to_land_soilcarbon_int32 - output_decomp_base_soilcarbon_real32 => io_decomp%land_soilcarbon_to_land_soilcarbon_real32 - output_decomp_base_soilcarbon_real64 => io_decomp%land_soilcarbon_to_land_soilcarbon_real64 - output_decomp_base_patch_int32 => io_decomp%patch_to_land_patch_int32 - output_decomp_base_patch_real32 => io_decomp%patch_to_land_patch_real32 - output_decomp_base_patch_real64 => io_decomp%patch_to_land_patch_real64 - output_decomp_base_patch_soil_int32 => io_decomp%patch_soil_to_land_patch_soil_int32 - output_decomp_base_patch_soil_real32 => io_decomp%patch_soil_to_land_patch_soil_real32 - output_decomp_base_patch_soil_real64 => io_decomp%patch_soil_to_land_patch_soil_real64 - output_decomp_base_patch_snow_int32 => io_decomp%patch_snow_to_land_patch_snow_int32 - output_decomp_base_patch_snow_real32 => io_decomp%patch_snow_to_land_patch_snow_real32 - output_decomp_base_patch_snow_real64 => io_decomp%patch_snow_to_land_patch_snow_real64 - output_decomp_base_patch_rad_int32 => io_decomp%patch_rad_to_land_patch_rad_int32 - output_decomp_base_patch_rad_real32 => io_decomp%patch_rad_to_land_patch_rad_real32 - output_decomp_base_patch_rad_real64 => io_decomp%patch_rad_to_land_patch_rad_real64 - output_decomp_base_patch_plantcarbon_int32 => io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_int32 - output_decomp_base_patch_plantcarbon_real32 => io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_real32 - output_decomp_base_patch_plantcarbon_real64 => io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_real64 - output_decomp_base_patch_soilcarbon_int32 => io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_int32 - output_decomp_base_patch_soilcarbon_real32 => io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_real32 - output_decomp_base_patch_soilcarbon_real64 => io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_real64 - else - call cable_abort("Error: Unable to determine output grid type", __FILE__, __LINE__) - end if - - restart_decomp_patch_int32 => io_decomp%patch_to_patch_int32 - restart_decomp_patch_real32 => io_decomp%patch_to_patch_real32 - restart_decomp_patch_real64 => io_decomp%patch_to_patch_real64 - restart_decomp_patch_soil_int32 => io_decomp%patch_soil_to_patch_soil_int32 - restart_decomp_patch_soil_real32 => io_decomp%patch_soil_to_patch_soil_real32 - restart_decomp_patch_soil_real64 => io_decomp%patch_soil_to_patch_soil_real64 - restart_decomp_patch_snow_int32 => io_decomp%patch_snow_to_patch_snow_int32 - restart_decomp_patch_snow_real32 => io_decomp%patch_snow_to_patch_snow_real32 - restart_decomp_patch_snow_real64 => io_decomp%patch_snow_to_patch_snow_real64 - restart_decomp_patch_rad_int32 => io_decomp%patch_rad_to_patch_rad_int32 - restart_decomp_patch_rad_real32 => io_decomp%patch_rad_to_patch_rad_real32 - restart_decomp_patch_rad_real64 => io_decomp%patch_rad_to_patch_rad_real64 - restart_decomp_patch_plantcarbon_int32 => io_decomp%patch_plantcarbon_to_patch_plantcarbon_int32 - restart_decomp_patch_plantcarbon_real32 => io_decomp%patch_plantcarbon_to_patch_plantcarbon_real32 - restart_decomp_patch_plantcarbon_real64 => io_decomp%patch_plantcarbon_to_patch_plantcarbon_real64 - restart_decomp_patch_soilcarbon_int32 => io_decomp%patch_soilcarbon_to_patch_soilcarbon_int32 - restart_decomp_patch_soilcarbon_real32 => io_decomp%patch_soilcarbon_to_patch_soilcarbon_real32 - restart_decomp_patch_soilcarbon_real64 => io_decomp%patch_soilcarbon_to_patch_soilcarbon_real64 - - end subroutine - - subroutine allocate_grid_reduction_buffers() - - allocate(temp_buffer_land_int32(mland)) - allocate(temp_buffer_land_real32(mland)) - allocate(temp_buffer_land_real64(mland)) - allocate(temp_buffer_land_soil_int32(mland, ms)) - allocate(temp_buffer_land_soil_real32(mland, ms)) - allocate(temp_buffer_land_soil_real64(mland, ms)) - allocate(temp_buffer_land_snow_int32(mland, msn)) - allocate(temp_buffer_land_snow_real32(mland, msn)) - allocate(temp_buffer_land_snow_real64(mland, msn)) - allocate(temp_buffer_land_rad_int32(mland, nrb)) - allocate(temp_buffer_land_rad_real32(mland, nrb)) - allocate(temp_buffer_land_rad_real64(mland, nrb)) - allocate(temp_buffer_land_plantcarbon_int32(mland, ncp)) - allocate(temp_buffer_land_plantcarbon_real32(mland, ncp)) - allocate(temp_buffer_land_plantcarbon_real64(mland, ncp)) - allocate(temp_buffer_land_soilcarbon_int32(mland, ncs)) - allocate(temp_buffer_land_soilcarbon_real32(mland, ncs)) - allocate(temp_buffer_land_soilcarbon_real64(mland, ncs)) - - end subroutine - - subroutine deallocate_grid_reduction_buffers() - - deallocate(temp_buffer_land_int32) - deallocate(temp_buffer_land_real32) - deallocate(temp_buffer_land_real64) - deallocate(temp_buffer_land_soil_int32) - deallocate(temp_buffer_land_soil_real32) - deallocate(temp_buffer_land_soil_real64) - deallocate(temp_buffer_land_snow_int32) - deallocate(temp_buffer_land_snow_real32) - deallocate(temp_buffer_land_snow_real64) - deallocate(temp_buffer_land_rad_int32) - deallocate(temp_buffer_land_rad_real32) - deallocate(temp_buffer_land_rad_real64) - deallocate(temp_buffer_land_plantcarbon_int32) - deallocate(temp_buffer_land_plantcarbon_real32) - deallocate(temp_buffer_land_plantcarbon_real64) - deallocate(temp_buffer_land_soilcarbon_int32) - deallocate(temp_buffer_land_soilcarbon_real32) - deallocate(temp_buffer_land_soilcarbon_real64) - - end subroutine - - logical function requires_x_y_output_grid(output_grid, met_grid) - character(len=*), intent(in) :: output_grid - character(len=*), intent(in) :: met_grid - requires_x_y_output_grid = (( & - output_grid == "default" .AND. met_grid == "mask" & - ) .OR. ( & - output_grid == "mask" .OR. output_grid == "ALMA" & - )) - end function - - logical function requires_land_output_grid(output_grid, met_grid) - character(len=*), intent(in) :: output_grid - character(len=*), intent(in) :: met_grid - requires_land_output_grid = ( & - output_grid == "land" .OR. (output_grid == "default" .AND. met_grid == "land") & - ) - end function - logical function data_shape_eq(shape1, shape2) type(cable_output_dim_t), dimension(:), intent(in) :: shape1, shape2 data_shape_eq = size(shape1) == size(shape2) .and. all(shape1 == shape2) end function - elemental integer function dim_size(dim) - type(cable_output_dim_t), intent(in) :: dim - - select case (dim%value) - case (CABLE_OUTPUT_DIM_PATCH%value) - dim_size = mp - case (CABLE_OUTPUT_DIM_SOIL%value) - dim_size = ms - case (CABLE_OUTPUT_DIM_SNOW%value) - dim_size = msn - case (CABLE_OUTPUT_DIM_RAD%value) - dim_size = nrb - case (CABLE_OUTPUT_DIM_PLANTCARBON%value) - dim_size = ncp - case (CABLE_OUTPUT_DIM_SOILCARBON%value) - dim_size = ncs - case (CABLE_OUTPUT_DIM_LAND%value) - dim_size = mland - case (CABLE_OUTPUT_DIM_LAND_GLOBAL%value) - dim_size = mland_global - case (CABLE_OUTPUT_DIM_X%value) - dim_size = xdimsize - case (CABLE_OUTPUT_DIM_Y%value) - dim_size = ydimsize - case default - dim_size = -1 ! Unknown dimension - end select + function dim_size(dims) + type(cable_output_dim_t), intent(in) :: dims(:) + integer, allocatable :: dim_size(:) + integer :: i + + allocate(dim_size(size(dims))) + do i = 1, size(dims) + select case (dims(i)%value) + case (CABLE_OUTPUT_DIM_PATCH%value) + dim_size(i) = mp + case (CABLE_OUTPUT_DIM_SOIL%value) + dim_size(i) = ms + case (CABLE_OUTPUT_DIM_SNOW%value) + dim_size(i) = msn + case (CABLE_OUTPUT_DIM_RAD%value) + dim_size(i) = nrb + case (CABLE_OUTPUT_DIM_PLANTCARBON%value) + dim_size(i) = ncp + case (CABLE_OUTPUT_DIM_SOILCARBON%value) + dim_size(i) = ncs + case (CABLE_OUTPUT_DIM_LAND%value) + dim_size(i) = mland + case (CABLE_OUTPUT_DIM_LAND_GLOBAL%value) + dim_size(i) = mland_global + case (CABLE_OUTPUT_DIM_X%value) + dim_size(i) = xdimsize + case (CABLE_OUTPUT_DIM_Y%value) + dim_size(i) = ydimsize + case default + call cable_abort("Unexpected dimension type", __FILE__, __LINE__) + end select + end do end function @@ -396,47 +140,51 @@ subroutine check_invalid_frequency(sampling_frequency, accumulation_frequency, v end subroutine check_invalid_frequency - function infer_dim_names(output_variable, restart) result(dim_names) + function infer_dim_names(output_profile, output_variable) result(dim_names) + type(cable_output_profile_t), intent(in) :: output_profile type(cable_output_variable_t), intent(in) :: output_variable - logical, intent(in), optional :: restart character(MAX_LEN_DIM), allocatable :: dim_names(:) - logical :: restart_local integer :: j - restart_local = .false. - if (present(restart)) restart_local = restart - allocate(dim_names(0)) if (allocated(output_variable%data_shape)) then do j = 1, size(output_variable%data_shape) select case (output_variable%data_shape(j)%value) case (CABLE_OUTPUT_DIM_PATCH%value) - if (restart_local) then + select case (output_profile%grid_type) + case ("restart") dim_names = [dim_names, "mp"] - else if (requires_land_output_grid(output%grid, metgrid)) then + case ("land") if (output_variable%reduction_method == "none") then dim_names = [dim_names, "land", "patch"] else dim_names = [dim_names, "land"] end if - else if (requires_x_y_output_grid(output%grid, metgrid)) then + case ("mask") if (output_variable%reduction_method == "none") then dim_names = [dim_names, "x", "y", "patch"] else dim_names = [dim_names, "x", "y"] end if - end if + case default + call cable_abort("Unexpected grid type '" // output_profile%grid_type // & + "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) + end select case (CABLE_OUTPUT_DIM_LAND%value) - if (restart_local) then + select case (output_profile%grid_type) + case ("restart") dim_names = [dim_names, "mland"] - else if (requires_land_output_grid(output%grid, metgrid)) then + case ("land") dim_names = [dim_names, "land"] - else if (requires_x_y_output_grid(output%grid, metgrid)) then + case ("mask") dim_names = [dim_names, "x", "y"] - end if + case default + call cable_abort("Unexpected grid type '" // output_profile%grid_type // & + "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) + end select case (CABLE_OUTPUT_DIM_LAND_GLOBAL%value) - if (restart_local) then + if (output_profile%grid_type == "restart") then dim_names = [dim_names, "mland"] else dim_names = [dim_names, "land"] @@ -448,13 +196,13 @@ function infer_dim_names(output_variable, restart) result(dim_names) case (CABLE_OUTPUT_DIM_RAD%value) dim_names = [dim_names, "rad"] case (CABLE_OUTPUT_DIM_PLANTCARBON%value) - if (restart_local) then + if (output_profile%grid_type == "restart") then dim_names = [dim_names, "plant_carbon_pools"] else dim_names = [dim_names, "plantcarbon"] end if case (CABLE_OUTPUT_DIM_SOILCARBON%value) - if (restart_local) then + if (output_profile%grid_type == "restart") then dim_names = [dim_names, "soil_carbon_pools"] else dim_names = [dim_names, "soilcarbon"] @@ -469,7 +217,7 @@ function infer_dim_names(output_variable, restart) result(dim_names) end do end if - if (.not. restart_local .and. .not. output_variable%parameter) dim_names = [dim_names, "time"] + if (output_profile%grid_type /= "restart" .and. .not. output_variable%parameter) dim_names = [dim_names, "time"] end function @@ -520,31 +268,24 @@ function infer_cell_methods(output_variable) result(cell_methods) end function - subroutine define_variables(output_file, output_variables, restart) - class(cable_netcdf_file_t), intent(inout) :: output_file - type(cable_output_variable_t), intent(in) :: output_variables(:) - logical, intent(in), optional :: restart + subroutine define_variables(output_profile) + type(cable_output_profile_t), intent(inout) :: output_profile integer :: i, j - logical :: restart_local character(MAX_LEN_DIM), allocatable :: required_dimensions(:), dim_names(:) - restart_local = .false. - if (present(restart)) restart_local = restart - - do i = 1, size(output_variables) - associate(output_var => output_variables(i)) - if (restart_local .and. .not. output_var%restart) cycle + do i = 1, size(output_profile%output_variables) + associate(output_var => output_profile%output_variables(i)) if (.not. allocated(output_var%data_shape)) cycle - dim_names = infer_dim_names(output_var, restart_local) + dim_names = infer_dim_names(output_profile, output_var) if (.not. allocated(required_dimensions)) then required_dimensions = dim_names else required_dimensions = [ & required_dimensions, & pack(dim_names, [( & - .not. any(dim_names(j) == required_dimensions), & + all(dim_names(j) /= required_dimensions), & j = 1, & size(dim_names) & )]) & @@ -556,29 +297,29 @@ subroutine define_variables(output_file, output_variables, restart) do i = 1, size(required_dimensions) select case (required_dimensions(i)) case ("mp") - call output_file%def_dims(["mp"], [mp_global]) + call output_profile%output_file%def_dims(["mp"], [mp_global]) case ("mland") - call output_file%def_dims(["mland"], [mland_global]) + call output_profile%output_file%def_dims(["mland"], [mland_global]) case ("land") - call output_file%def_dims(["land"], [mland_global]) + call output_profile%output_file%def_dims(["land"], [mland_global]) case ("x") - call output_file%def_dims(["x"], [xdimsize]) + call output_profile%output_file%def_dims(["x"], [xdimsize]) case ("y") - call output_file%def_dims(["y"], [ydimsize]) + call output_profile%output_file%def_dims(["y"], [ydimsize]) case ("patch") - call output_file%def_dims(["patch"], [max_vegpatches]) + call output_profile%output_file%def_dims(["patch"], [max_vegpatches]) case ("soil") - call output_file%def_dims(["soil"], [ms]) + call output_profile%output_file%def_dims(["soil"], [ms]) case ("rad") - call output_file%def_dims(["rad"], [nrb]) + call output_profile%output_file%def_dims(["rad"], [nrb]) case ("soil_carbon_pools") - call output_file%def_dims(["soil_carbon_pools"], [ncs]) + call output_profile%output_file%def_dims(["soil_carbon_pools"], [ncs]) case ("soilcarbon") - call output_file%def_dims(["soilcarbon"], [ncs]) + call output_profile%output_file%def_dims(["soilcarbon"], [ncs]) case ("plant_carbon_pools") - call output_file%def_dims(["plant_carbon_pools"], [ncp]) + call output_profile%output_file%def_dims(["plant_carbon_pools"], [ncp]) case ("plantcarbon") - call output_file%def_dims(["plantcarbon"], [ncp]) + call output_profile%output_file%def_dims(["plantcarbon"], [ncp]) case ("time") ! time dimension defined separately below case default @@ -586,48 +327,47 @@ subroutine define_variables(output_file, output_variables, restart) end select end do - if (restart_local) then - call output_file%def_dims(["time"], [1]) + if (output_profile%grid_type == "restart") then + call output_profile%output_file%def_dims(["time"], [1]) else - call output_file%def_dims(["time"], [CABLE_NETCDF_UNLIMITED]) + call output_profile%output_file%def_dims(["time"], [CABLE_NETCDF_UNLIMITED]) end if - call output_file%def_var("time", ["time"], CABLE_NETCDF_DOUBLE) - call output_file%put_att("time", "units", timeunits) - call output_file%put_att("time", "coordinate", time_coord) - call output_file%put_att("time", "calendar", calendar) + call output_profile%output_file%def_var("time", ["time"], CABLE_NETCDF_DOUBLE) + call output_profile%output_file%put_att("time", "units", timeunits) + call output_profile%output_file%put_att("time", "coordinate", time_coord) + call output_profile%output_file%put_att("time", "calendar", calendar) - if (.not. restart_local) then - call output_file%def_dims(["nv"], [2]) - call output_file%def_var("time_bnds", ["nv ", "time"], CABLE_NETCDF_DOUBLE) - call output_file%put_att("time", "bounds", "time_bnds") + if (output_profile%grid_type /= "restart") then + call output_profile%output_file%def_dims(["nv"], [2]) + call output_profile%output_file%def_var("time_bnds", ["nv ", "time"], CABLE_NETCDF_DOUBLE) + call output_profile%output_file%put_att("time", "bounds", "time_bnds") end if - do i = 1, size(output_variables) - associate(output_var => output_variables(i)) - if (restart_local .and. .not. output_var%restart) cycle - call output_file%def_var( & + do i = 1, size(output_profile%output_variables) + associate(output_var => output_profile%output_variables(i)) + call output_profile%output_file%def_var( & var_name=output_var%name, & - dim_names=infer_dim_names(output_var, restart_local), & + dim_names=infer_dim_names(output_profile, output_var), & type=output_var%var_type & ) if (allocated(output_var%metadata)) then do j = 1, size(output_var%metadata) - call output_file%put_att(output_var%name, output_var%metadata(j)%name, output_var%metadata(j)%value) + call output_profile%output_file%put_att(output_var%name, output_var%metadata(j)%name, output_var%metadata(j)%value) end do end if select case (output_var%var_type) case (CABLE_NETCDF_INT) - call output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_INT32) - call output_file%put_att(output_var%name, "missing_value", FILL_VALUE_INT32) + call output_profile%output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_INT32) + call output_profile%output_file%put_att(output_var%name, "missing_value", FILL_VALUE_INT32) case (CABLE_NETCDF_FLOAT) - call output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_REAL32) - call output_file%put_att(output_var%name, "missing_value", FILL_VALUE_REAL32) + call output_profile%output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_REAL32) + call output_profile%output_file%put_att(output_var%name, "missing_value", FILL_VALUE_REAL32) case (CABLE_NETCDF_DOUBLE) - call output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_REAL64) - call output_file%put_att(output_var%name, "missing_value", FILL_VALUE_REAL64) + call output_profile%output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_REAL64) + call output_profile%output_file%put_att(output_var%name, "missing_value", FILL_VALUE_REAL64) end select - call output_file%put_att(output_var%name, "cell_methods", infer_cell_methods(output_var)) + call output_profile%output_file%put_att(output_var%name, "cell_methods", infer_cell_methods(output_var)) end associate end do @@ -670,319 +410,4 @@ subroutine set_global_attributes(output_profile) end subroutine set_global_attributes - subroutine associate_decomp_int32(output_var, decomp, restart) - type(cable_output_variable_t), intent(in) :: output_var - class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp - logical, intent(in), optional :: restart - - if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_int32 - else - decomp => output_decomp_base_int32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_int32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_soil_int32 - else - decomp => output_decomp_base_soil_int32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_soil_int32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_snow_int32 - else - decomp => output_decomp_base_snow_int32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_snow_int32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_rad_int32 - else - decomp => output_decomp_base_rad_int32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_rad_int32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_plantcarbon_int32 - else - decomp => output_decomp_base_plantcarbon_int32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_plantcarbon_int32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_soilcarbon_int32 - else - decomp => output_decomp_base_soilcarbon_int32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_soilcarbon_int32 - end if - else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) - end if - - end subroutine associate_decomp_int32 - - subroutine associate_decomp_real32(output_var, decomp, restart) - type(cable_output_variable_t), intent(in) :: output_var - class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp - logical, intent(in), optional :: restart - - if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_real32 - else - decomp => output_decomp_base_real32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_real32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_soil_real32 - else - decomp => output_decomp_base_soil_real32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_soil_real32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_snow_real32 - else - decomp => output_decomp_base_snow_real32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_snow_real32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_rad_real32 - else - decomp => output_decomp_base_rad_real32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_rad_real32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_plantcarbon_real32 - else - decomp => output_decomp_base_plantcarbon_real32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_plantcarbon_real32 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_soilcarbon_real32 - else - decomp => output_decomp_base_soilcarbon_real32 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_soilcarbon_real32 - end if - else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) - end if - - end subroutine associate_decomp_real32 - - subroutine associate_decomp_real64(output_var, decomp, restart) - type(cable_output_variable_t), intent(in) :: output_var - class(cable_netcdf_decomp_t), pointer, intent(inout) :: decomp - logical, intent(in), optional :: restart - - if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_real64 - else - decomp => output_decomp_base_real64 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_real64 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_soil_real64 - else - decomp => output_decomp_base_soil_real64 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_soil_real64 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_snow_real64 - else - decomp => output_decomp_base_snow_real64 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_snow_real64 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_rad_real64 - else - decomp => output_decomp_base_rad_real64 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_rad_real64 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_plantcarbon_real64 - else - decomp => output_decomp_base_plantcarbon_real64 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_plantcarbon_real64 - end if - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then - if (output_var%reduction_method == "none") then - decomp => output_decomp_base_patch_soilcarbon_real64 - else - decomp => output_decomp_base_soilcarbon_real64 - end if - if (present(restart)) then - if (restart) decomp => restart_decomp_patch_soilcarbon_real64 - end if - else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) - end if - - end subroutine associate_decomp_real64 - - subroutine associate_temp_buffer_int32(output_var, temp_buffer_int32_1d, temp_buffer_int32_2d, temp_buffer_int32_3d) - type(cable_output_variable_t), intent(inout) :: output_var - integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_1d(:) - integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_2d(:,:) - integer(kind=int32), pointer, intent(inout), optional :: temp_buffer_int32_3d(:,:,:) - - if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then - if (.not. present(temp_buffer_int32_1d)) call cable_abort( & - "temp_buffer_int32_1d must be provided for 1D data shape", __FILE__, __LINE__) - temp_buffer_int32_1d => temp_buffer_land_int32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then - if (.not. present(temp_buffer_int32_2d)) call cable_abort( & - "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_int32_2d => temp_buffer_land_soil_int32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (.not. present(temp_buffer_int32_2d)) call cable_abort( & - "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_int32_2d => temp_buffer_land_rad_int32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then - if (.not. present(temp_buffer_int32_2d)) call cable_abort( & - "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_int32_2d => temp_buffer_land_snow_int32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (.not. present(temp_buffer_int32_2d)) call cable_abort( & - "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_int32_2d => temp_buffer_land_rad_int32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then - if (.not. present(temp_buffer_int32_2d)) call cable_abort( & - "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_int32_2d => temp_buffer_land_plantcarbon_int32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then - if (.not. present(temp_buffer_int32_2d)) call cable_abort( & - "temp_buffer_int32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_int32_2d => temp_buffer_land_soilcarbon_int32 - else - call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) - end if - - end subroutine associate_temp_buffer_int32 - - subroutine associate_temp_buffer_real32(output_var, temp_buffer_real32_1d, temp_buffer_real32_2d, temp_buffer_real32_3d) - type(cable_output_variable_t), intent(inout) :: output_var - real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_1d(:) - real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_2d(:,:) - real(kind=real32), pointer, intent(inout), optional :: temp_buffer_real32_3d(:,:,:) - - if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then - if (.not. present(temp_buffer_real32_1d)) call cable_abort( & - "temp_buffer_real32_1d must be provided for 1D data shape", __FILE__, __LINE__) - temp_buffer_real32_1d => temp_buffer_land_real32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then - if (.not. present(temp_buffer_real32_2d)) call cable_abort( & - "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real32_2d => temp_buffer_land_soil_real32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (.not. present(temp_buffer_real32_2d)) call cable_abort( & - "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real32_2d => temp_buffer_land_rad_real32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then - if (.not. present(temp_buffer_real32_2d)) call cable_abort( & - "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real32_2d => temp_buffer_land_snow_real32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (.not. present(temp_buffer_real32_2d)) call cable_abort( & - "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real32_2d => temp_buffer_land_rad_real32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then - if (.not. present(temp_buffer_real32_2d)) call cable_abort( & - "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real32_2d => temp_buffer_land_plantcarbon_real32 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then - if (.not. present(temp_buffer_real32_2d)) call cable_abort( & - "temp_buffer_real32_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real32_2d => temp_buffer_land_soilcarbon_real32 - else - call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) - end if - - end subroutine associate_temp_buffer_real32 - - subroutine associate_temp_buffer_real64(output_var, temp_buffer_real64_1d, temp_buffer_real64_2d, temp_buffer_real64_3d) - type(cable_output_variable_t), intent(inout) :: output_var - real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_1d(:) - real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_2d(:,:) - real(kind=real64), pointer, intent(inout), optional :: temp_buffer_real64_3d(:,:,:) - - if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH])) then - if (.not. present(temp_buffer_real64_1d)) call cable_abort( & - "temp_buffer_real64_1d must be provided for 1D data shape", __FILE__, __LINE__) - temp_buffer_real64_1d => temp_buffer_land_real64 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOIL])) then - if (.not. present(temp_buffer_real64_2d)) call cable_abort( & - "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real64_2d => temp_buffer_land_soil_real64 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (.not. present(temp_buffer_real64_2d)) call cable_abort( & - "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real64_2d => temp_buffer_land_rad_real64 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SNOW])) then - if (.not. present(temp_buffer_real64_2d)) call cable_abort( & - "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real64_2d => temp_buffer_land_snow_real64 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_RAD])) then - if (.not. present(temp_buffer_real64_2d)) call cable_abort( & - "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real64_2d => temp_buffer_land_rad_real64 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_PLANTCARBON])) then - if (.not. present(temp_buffer_real64_2d)) call cable_abort( & - "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real64_2d => temp_buffer_land_plantcarbon_real64 - else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then - if (.not. present(temp_buffer_real64_2d)) call cable_abort( & - "temp_buffer_real64_2d must be provided for 2D data shape", __FILE__, __LINE__) - temp_buffer_real64_2d => temp_buffer_land_soilcarbon_real64 - else - call cable_abort("Unexpected source data shape for grid reduction", __FILE__, __LINE__) - end if - - end subroutine associate_temp_buffer_real64 - end module From bb50434586ed3be9d32e2a60d10b032a92adeb4f Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Mon, 9 Feb 2026 20:14:34 +1100 Subject: [PATCH 21/31] Remove cable_io_decomp_mod --- CMakeLists.txt | 1 - src/offline/cable_io_decomp.F90 | 328 -------------------------------- src/offline/cable_mpimaster.F90 | 5 - src/offline/cable_serial.F90 | 6 - 4 files changed, 340 deletions(-) delete mode 100644 src/offline/cable_io_decomp.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index f3d4a0139..d2a737671 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,7 +268,6 @@ else() src/util/cable_common.F90 src/shared/casa_offline_inout.F90 src/shared/casa_ncdf.F90 - src/offline/cable_io_decomp.F90 src/offline/cable_iovars.F90 src/offline/cable_surface_types.F90 src/offline/cable_define_types.F90 diff --git a/src/offline/cable_io_decomp.F90 b/src/offline/cable_io_decomp.F90 deleted file mode 100644 index 4da99efe6..000000000 --- a/src/offline/cable_io_decomp.F90 +++ /dev/null @@ -1,328 +0,0 @@ -module cable_io_decomp_mod - use cable_def_types_mod, only: mp, mp_global - use cable_def_types_mod, only: mland, mland_global - use cable_def_types_mod, only: ms - use cable_def_types_mod, only: msn - use cable_def_types_mod, only: nrb - use cable_def_types_mod, only: ncp - use cable_def_types_mod, only: ncs - - use cable_io_vars_module, only: xdimsize, ydimsize - use cable_io_vars_module, only: land_x, land_y - use cable_io_vars_module, only: landpt - use cable_io_vars_module, only: max_vegpatches - use cable_io_vars_module, only: land_decomp_start - use cable_io_vars_module, only: patch_decomp_start - use cable_io_vars_module, only: output - use cable_io_vars_module, only: metGrid - - use cable_netcdf_decomp_util_mod, only: dim_spec_t - use cable_netcdf_decomp_util_mod, only: io_decomp_land_to_x_y - use cable_netcdf_decomp_util_mod, only: io_decomp_patch_to_x_y_patch - use cable_netcdf_decomp_util_mod, only: io_decomp_land_to_land - use cable_netcdf_decomp_util_mod, only: io_decomp_patch_to_land_patch - use cable_netcdf_decomp_util_mod, only: io_decomp_patch_to_patch - - use cable_netcdf_mod, only: cable_netcdf_decomp_t - use cable_netcdf_mod, only: CABLE_NETCDF_INT - use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT - use cable_netcdf_mod, only: CABLE_NETCDF_DOUBLE - - implicit none - private - - public :: & - io_decomp_t, & - cable_io_decomp_init - - type io_decomp_t - class(cable_netcdf_decomp_t), allocatable :: patch_to_x_y_patch_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_to_x_y_patch_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_to_x_y_patch_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_x_y_patch_soil_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_x_y_patch_soil_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_x_y_patch_soil_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_x_y_patch_snow_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_x_y_patch_snow_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_x_y_patch_snow_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_x_y_patch_rad_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_x_y_patch_rad_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_x_y_patch_rad_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_x_y_patch_plantcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_x_y_patch_plantcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_x_y_patch_plantcarbon_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_x_y_patch_soilcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_x_y_patch_soilcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_x_y_patch_soilcarbon_real64 - - class(cable_netcdf_decomp_t), allocatable :: patch_to_land_patch_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_to_land_patch_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_to_land_patch_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_land_patch_soil_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_land_patch_soil_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_land_patch_soil_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_land_patch_snow_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_land_patch_snow_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_land_patch_snow_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_land_patch_rad_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_land_patch_rad_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_land_patch_rad_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_land_patch_plantcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_land_patch_plantcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_land_patch_plantcarbon_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_land_patch_soilcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_land_patch_soilcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_land_patch_soilcarbon_real64 - - class(cable_netcdf_decomp_t), allocatable :: patch_to_patch_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_to_patch_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_to_patch_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_patch_soil_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_patch_soil_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_soil_to_patch_soil_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_patch_snow_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_patch_snow_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_snow_to_patch_snow_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_patch_rad_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_patch_rad_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_rad_to_patch_rad_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_patch_plantcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_patch_plantcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_plantcarbon_to_patch_plantcarbon_real64 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_patch_soilcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_patch_soilcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: patch_soilcarbon_to_patch_soilcarbon_real64 - - class(cable_netcdf_decomp_t), allocatable :: land_to_x_y_int32 - class(cable_netcdf_decomp_t), allocatable :: land_to_x_y_real32 - class(cable_netcdf_decomp_t), allocatable :: land_to_x_y_real64 - class(cable_netcdf_decomp_t), allocatable :: land_soil_to_x_y_soil_int32 - class(cable_netcdf_decomp_t), allocatable :: land_soil_to_x_y_soil_real32 - class(cable_netcdf_decomp_t), allocatable :: land_soil_to_x_y_soil_real64 - class(cable_netcdf_decomp_t), allocatable :: land_snow_to_x_y_snow_int32 - class(cable_netcdf_decomp_t), allocatable :: land_snow_to_x_y_snow_real32 - class(cable_netcdf_decomp_t), allocatable :: land_snow_to_x_y_snow_real64 - class(cable_netcdf_decomp_t), allocatable :: land_rad_to_x_y_rad_int32 - class(cable_netcdf_decomp_t), allocatable :: land_rad_to_x_y_rad_real32 - class(cable_netcdf_decomp_t), allocatable :: land_rad_to_x_y_rad_real64 - class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_to_x_y_plantcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_to_x_y_plantcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_to_x_y_plantcarbon_real64 - class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_to_x_y_soilcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_to_x_y_soilcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_to_x_y_soilcarbon_real64 - - class(cable_netcdf_decomp_t), allocatable :: land_to_land_int32 - class(cable_netcdf_decomp_t), allocatable :: land_to_land_real32 - class(cable_netcdf_decomp_t), allocatable :: land_to_land_real64 - class(cable_netcdf_decomp_t), allocatable :: land_soil_to_land_soil_int32 - class(cable_netcdf_decomp_t), allocatable :: land_soil_to_land_soil_real32 - class(cable_netcdf_decomp_t), allocatable :: land_soil_to_land_soil_real64 - class(cable_netcdf_decomp_t), allocatable :: land_snow_to_land_snow_int32 - class(cable_netcdf_decomp_t), allocatable :: land_snow_to_land_snow_real32 - class(cable_netcdf_decomp_t), allocatable :: land_snow_to_land_snow_real64 - class(cable_netcdf_decomp_t), allocatable :: land_rad_to_land_rad_int32 - class(cable_netcdf_decomp_t), allocatable :: land_rad_to_land_rad_real32 - class(cable_netcdf_decomp_t), allocatable :: land_rad_to_land_rad_real64 - class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_to_land_plantcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_to_land_plantcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: land_plantcarbon_to_land_plantcarbon_real64 - class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_to_land_soilcarbon_int32 - class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_to_land_soilcarbon_real32 - class(cable_netcdf_decomp_t), allocatable :: land_soilcarbon_to_land_soilcarbon_real64 - - end type io_decomp_t - -contains - - subroutine cable_io_decomp_init(io_decomp) - type(io_decomp_t), intent(out), target :: io_decomp - - type(dim_spec_t), allocatable :: mem_shape_land(:) - type(dim_spec_t), allocatable :: mem_shape_land_soil(:) - type(dim_spec_t), allocatable :: mem_shape_land_snow(:) - type(dim_spec_t), allocatable :: mem_shape_land_rad(:) - type(dim_spec_t), allocatable :: mem_shape_land_plantcarbon(:) - type(dim_spec_t), allocatable :: mem_shape_land_soilcarbon(:) - type(dim_spec_t), allocatable :: mem_shape_patch(:) - type(dim_spec_t), allocatable :: mem_shape_patch_soil(:) - type(dim_spec_t), allocatable :: mem_shape_patch_snow(:) - type(dim_spec_t), allocatable :: mem_shape_patch_rad(:) - type(dim_spec_t), allocatable :: mem_shape_patch_plantcarbon(:) - type(dim_spec_t), allocatable :: mem_shape_patch_soilcarbon(:) - - type(dim_spec_t), allocatable :: var_shape_x_y(:) - type(dim_spec_t), allocatable :: var_shape_x_y_soil(:) - type(dim_spec_t), allocatable :: var_shape_x_y_snow(:) - type(dim_spec_t), allocatable :: var_shape_x_y_rad(:) - type(dim_spec_t), allocatable :: var_shape_x_y_plantcarbon(:) - type(dim_spec_t), allocatable :: var_shape_x_y_soilcarbon(:) - type(dim_spec_t), allocatable :: var_shape_x_y_patch(:) - type(dim_spec_t), allocatable :: var_shape_x_y_patch_soil(:) - type(dim_spec_t), allocatable :: var_shape_x_y_patch_snow(:) - type(dim_spec_t), allocatable :: var_shape_x_y_patch_rad(:) - type(dim_spec_t), allocatable :: var_shape_x_y_patch_plantcarbon(:) - type(dim_spec_t), allocatable :: var_shape_x_y_patch_soilcarbon(:) - type(dim_spec_t), allocatable :: var_shape_land(:) - type(dim_spec_t), allocatable :: var_shape_land_soil(:) - type(dim_spec_t), allocatable :: var_shape_land_snow(:) - type(dim_spec_t), allocatable :: var_shape_land_rad(:) - type(dim_spec_t), allocatable :: var_shape_land_plantcarbon(:) - type(dim_spec_t), allocatable :: var_shape_land_soilcarbon(:) - type(dim_spec_t), allocatable :: var_shape_land_patch(:) - type(dim_spec_t), allocatable :: var_shape_land_patch_soil(:) - type(dim_spec_t), allocatable :: var_shape_land_patch_snow(:) - type(dim_spec_t), allocatable :: var_shape_land_patch_rad(:) - type(dim_spec_t), allocatable :: var_shape_land_patch_plantcarbon(:) - type(dim_spec_t), allocatable :: var_shape_land_patch_soilcarbon(:) - type(dim_spec_t), allocatable :: var_shape_patch(:) - type(dim_spec_t), allocatable :: var_shape_patch_soil(:) - type(dim_spec_t), allocatable :: var_shape_patch_snow(:) - type(dim_spec_t), allocatable :: var_shape_patch_rad(:) - type(dim_spec_t), allocatable :: var_shape_patch_plantcarbon(:) - type(dim_spec_t), allocatable :: var_shape_patch_soilcarbon(:) - - logical :: requires_land_output_grid, requires_x_y_output_grid - - mem_shape_land = [dim_spec_t('land', mland)] - mem_shape_land_soil = [dim_spec_t('land', mland), dim_spec_t('soil', ms)] - mem_shape_land_snow = [dim_spec_t('land', mland), dim_spec_t('snow', msn)] - mem_shape_land_rad = [dim_spec_t('land', mland), dim_spec_t('rad', nrb)] - mem_shape_land_plantcarbon = [dim_spec_t('land', mland), dim_spec_t('plantcarbon', ncp)] - mem_shape_land_soilcarbon = [dim_spec_t('land', mland), dim_spec_t('soilcarbon', ncs)] - mem_shape_patch = [dim_spec_t('patch', mp)] - mem_shape_patch_soil = [dim_spec_t('patch', mp), dim_spec_t('soil', ms)] - mem_shape_patch_snow = [dim_spec_t('patch', mp), dim_spec_t('snow', msn)] - mem_shape_patch_rad = [dim_spec_t('patch', mp), dim_spec_t('rad', nrb)] - mem_shape_patch_plantcarbon = [dim_spec_t('patch', mp), dim_spec_t('plantcarbon', ncp)] - mem_shape_patch_soilcarbon = [dim_spec_t('patch', mp), dim_spec_t('soilcarbon', ncs)] - - var_shape_x_y = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize)] - var_shape_x_y_soil = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('soil', ms)] - var_shape_x_y_snow = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('snow', msn)] - var_shape_x_y_rad = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('rad', nrb)] - var_shape_x_y_plantcarbon = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('plantcarbon', ncp)] - var_shape_x_y_soilcarbon = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('soilcarbon', ncs)] - var_shape_x_y_patch = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches)] - var_shape_x_y_patch_soil = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('soil', ms)] - var_shape_x_y_patch_snow = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('snow', msn)] - var_shape_x_y_patch_rad = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('rad', nrb)] - var_shape_x_y_patch_plantcarbon = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('plantcarbon', ncp)] - var_shape_x_y_patch_soilcarbon = [dim_spec_t('x', xdimsize), dim_spec_t('y', ydimsize), dim_spec_t('patch', max_vegpatches), dim_spec_t('soilcarbon', ncs)] - var_shape_land = [dim_spec_t('land', mland_global)] - var_shape_land_soil = [dim_spec_t('land', mland_global), dim_spec_t('soil', ms)] - var_shape_land_snow = [dim_spec_t('land', mland_global), dim_spec_t('snow', msn)] - var_shape_land_rad = [dim_spec_t('land', mland_global), dim_spec_t('rad', nrb)] - var_shape_land_plantcarbon = [dim_spec_t('land', mland_global), dim_spec_t('plantcarbon', ncp)] - var_shape_land_soilcarbon = [dim_spec_t('land', mland_global), dim_spec_t('soilcarbon', ncs)] - var_shape_land_patch = [dim_spec_t('land', mland_global)] - var_shape_land_patch_soil = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('soil', ms)] - var_shape_land_patch_snow = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('snow', msn)] - var_shape_land_patch_rad = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('rad', nrb)] - var_shape_land_patch_plantcarbon = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('plantcarbon', ncp)] - var_shape_land_patch_soilcarbon = [dim_spec_t('land', mland_global), dim_spec_t('patch', max_vegpatches), dim_spec_t('soilcarbon', ncs)] - var_shape_patch = [dim_spec_t('patch', mp_global)] - var_shape_patch_soil = [dim_spec_t('patch', mp_global), dim_spec_t('soil', ms)] - var_shape_patch_snow = [dim_spec_t('patch', mp_global), dim_spec_t('snow', msn)] - var_shape_patch_rad = [dim_spec_t('patch', mp_global), dim_spec_t('rad', nrb)] - var_shape_patch_plantcarbon = [dim_spec_t('patch', mp_global), dim_spec_t('plantcarbon', ncp)] - var_shape_patch_soilcarbon = [dim_spec_t('patch', mp_global), dim_spec_t('soilcarbon', ncs)] - - io_decomp%land_to_x_y_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land, var_shape_x_y, CABLE_NETCDF_INT) - io_decomp%land_to_x_y_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land, var_shape_x_y, CABLE_NETCDF_FLOAT) - io_decomp%land_to_x_y_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land, var_shape_x_y, CABLE_NETCDF_DOUBLE) - io_decomp%land_soil_to_x_y_soil_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soil, var_shape_x_y_soil, CABLE_NETCDF_INT) - io_decomp%land_soil_to_x_y_soil_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soil, var_shape_x_y_soil, CABLE_NETCDF_FLOAT) - io_decomp%land_soil_to_x_y_soil_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soil, var_shape_x_y_soil, CABLE_NETCDF_DOUBLE) - io_decomp%land_snow_to_x_y_snow_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_snow, var_shape_x_y_snow, CABLE_NETCDF_INT) - io_decomp%land_snow_to_x_y_snow_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_snow, var_shape_x_y_snow, CABLE_NETCDF_FLOAT) - io_decomp%land_snow_to_x_y_snow_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_snow, var_shape_x_y_snow, CABLE_NETCDF_DOUBLE) - io_decomp%land_rad_to_x_y_rad_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_rad, var_shape_x_y_rad, CABLE_NETCDF_INT) - io_decomp%land_rad_to_x_y_rad_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_rad, var_shape_x_y_rad, CABLE_NETCDF_FLOAT) - io_decomp%land_rad_to_x_y_rad_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_rad, var_shape_x_y_rad, CABLE_NETCDF_DOUBLE) - io_decomp%land_plantcarbon_to_x_y_plantcarbon_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_plantcarbon, var_shape_x_y_plantcarbon, CABLE_NETCDF_INT) - io_decomp%land_plantcarbon_to_x_y_plantcarbon_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_plantcarbon, var_shape_x_y_plantcarbon, CABLE_NETCDF_FLOAT) - io_decomp%land_plantcarbon_to_x_y_plantcarbon_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_plantcarbon, var_shape_x_y_plantcarbon, CABLE_NETCDF_DOUBLE) - io_decomp%land_soilcarbon_to_x_y_soilcarbon_int32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soilcarbon, var_shape_x_y_soilcarbon, CABLE_NETCDF_INT) - io_decomp%land_soilcarbon_to_x_y_soilcarbon_real32 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soilcarbon, var_shape_x_y_soilcarbon, CABLE_NETCDF_FLOAT) - io_decomp%land_soilcarbon_to_x_y_soilcarbon_real64 = io_decomp_land_to_x_y(land_x, land_y, mem_shape_land_soilcarbon, var_shape_x_y_soilcarbon, CABLE_NETCDF_DOUBLE) - - io_decomp%land_to_land_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land, var_shape_land, CABLE_NETCDF_INT) - io_decomp%land_to_land_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land, var_shape_land, CABLE_NETCDF_FLOAT) - io_decomp%land_to_land_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land, var_shape_land, CABLE_NETCDF_DOUBLE) - io_decomp%land_soil_to_land_soil_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soil, var_shape_land_soil, CABLE_NETCDF_INT) - io_decomp%land_soil_to_land_soil_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soil, var_shape_land_soil, CABLE_NETCDF_FLOAT) - io_decomp%land_soil_to_land_soil_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soil, var_shape_land_soil, CABLE_NETCDF_DOUBLE) - io_decomp%land_snow_to_land_snow_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_snow, var_shape_land_snow, CABLE_NETCDF_INT) - io_decomp%land_snow_to_land_snow_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_snow, var_shape_land_snow, CABLE_NETCDF_FLOAT) - io_decomp%land_snow_to_land_snow_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_snow, var_shape_land_snow, CABLE_NETCDF_DOUBLE) - io_decomp%land_rad_to_land_rad_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_rad, var_shape_land_rad, CABLE_NETCDF_INT) - io_decomp%land_rad_to_land_rad_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_rad, var_shape_land_rad, CABLE_NETCDF_FLOAT) - io_decomp%land_rad_to_land_rad_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_rad, var_shape_land_rad, CABLE_NETCDF_DOUBLE) - io_decomp%land_plantcarbon_to_land_plantcarbon_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_plantcarbon, var_shape_land_plantcarbon, CABLE_NETCDF_INT) - io_decomp%land_plantcarbon_to_land_plantcarbon_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_plantcarbon, var_shape_land_plantcarbon, CABLE_NETCDF_FLOAT) - io_decomp%land_plantcarbon_to_land_plantcarbon_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_plantcarbon, var_shape_land_plantcarbon, CABLE_NETCDF_DOUBLE) - io_decomp%land_soilcarbon_to_land_soilcarbon_int32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soilcarbon, var_shape_land_soilcarbon, CABLE_NETCDF_INT) - io_decomp%land_soilcarbon_to_land_soilcarbon_real32 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soilcarbon, var_shape_land_soilcarbon, CABLE_NETCDF_FLOAT) - io_decomp%land_soilcarbon_to_land_soilcarbon_real64 = io_decomp_land_to_land(land_decomp_start, mem_shape_land_soilcarbon, var_shape_land_soilcarbon, CABLE_NETCDF_DOUBLE) - - io_decomp%patch_to_x_y_patch_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_x_y_patch, CABLE_NETCDF_INT) - io_decomp%patch_to_x_y_patch_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_x_y_patch, CABLE_NETCDF_FLOAT) - io_decomp%patch_to_x_y_patch_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_x_y_patch, CABLE_NETCDF_DOUBLE) - io_decomp%patch_soil_to_x_y_patch_soil_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_x_y_patch_soil, CABLE_NETCDF_INT) - io_decomp%patch_soil_to_x_y_patch_soil_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_x_y_patch_soil, CABLE_NETCDF_FLOAT) - io_decomp%patch_soil_to_x_y_patch_soil_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_x_y_patch_soil, CABLE_NETCDF_DOUBLE) - io_decomp%patch_snow_to_x_y_patch_snow_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_x_y_patch_snow, CABLE_NETCDF_INT) - io_decomp%patch_snow_to_x_y_patch_snow_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_x_y_patch_snow, CABLE_NETCDF_FLOAT) - io_decomp%patch_snow_to_x_y_patch_snow_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_x_y_patch_snow, CABLE_NETCDF_DOUBLE) - io_decomp%patch_rad_to_x_y_patch_rad_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_x_y_patch_rad, CABLE_NETCDF_INT) - io_decomp%patch_rad_to_x_y_patch_rad_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_x_y_patch_rad, CABLE_NETCDF_FLOAT) - io_decomp%patch_rad_to_x_y_patch_rad_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_x_y_patch_rad, CABLE_NETCDF_DOUBLE) - io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_x_y_patch_plantcarbon, CABLE_NETCDF_INT) - io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_x_y_patch_plantcarbon, CABLE_NETCDF_FLOAT) - io_decomp%patch_plantcarbon_to_x_y_patch_plantcarbon_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_x_y_patch_plantcarbon, CABLE_NETCDF_DOUBLE) - io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_int32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_x_y_patch_soilcarbon, CABLE_NETCDF_INT) - io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_real32 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_x_y_patch_soilcarbon, CABLE_NETCDF_FLOAT) - io_decomp%patch_soilcarbon_to_x_y_patch_soilcarbon_real64 = io_decomp_patch_to_x_y_patch(land_x, land_y, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_x_y_patch_soilcarbon, CABLE_NETCDF_DOUBLE) - - io_decomp%patch_to_land_patch_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_land_patch, CABLE_NETCDF_INT) - io_decomp%patch_to_land_patch_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_land_patch, CABLE_NETCDF_FLOAT) - io_decomp%patch_to_land_patch_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch, var_shape_land_patch, CABLE_NETCDF_DOUBLE) - io_decomp%patch_soil_to_land_patch_soil_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_land_patch_soil, CABLE_NETCDF_INT) - io_decomp%patch_soil_to_land_patch_soil_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_land_patch_soil, CABLE_NETCDF_FLOAT) - io_decomp%patch_soil_to_land_patch_soil_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soil, var_shape_land_patch_soil, CABLE_NETCDF_DOUBLE) - io_decomp%patch_snow_to_land_patch_snow_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_land_patch_snow, CABLE_NETCDF_INT) - io_decomp%patch_snow_to_land_patch_snow_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_land_patch_snow, CABLE_NETCDF_FLOAT) - io_decomp%patch_snow_to_land_patch_snow_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_snow, var_shape_land_patch_snow, CABLE_NETCDF_DOUBLE) - io_decomp%patch_rad_to_land_patch_rad_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_land_patch_rad, CABLE_NETCDF_INT) - io_decomp%patch_rad_to_land_patch_rad_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_land_patch_rad, CABLE_NETCDF_FLOAT) - io_decomp%patch_rad_to_land_patch_rad_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_rad, var_shape_land_patch_rad, CABLE_NETCDF_DOUBLE) - io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_land_patch_plantcarbon, CABLE_NETCDF_INT) - io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_land_patch_plantcarbon, CABLE_NETCDF_FLOAT) - io_decomp%patch_plantcarbon_to_land_patch_plantcarbon_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_plantcarbon, var_shape_land_patch_plantcarbon, CABLE_NETCDF_DOUBLE) - io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_int32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_land_patch_soilcarbon, CABLE_NETCDF_INT) - io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_real32 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_land_patch_soilcarbon, CABLE_NETCDF_FLOAT) - io_decomp%patch_soilcarbon_to_land_patch_soilcarbon_real64 = io_decomp_patch_to_land_patch(land_decomp_start, landpt(:)%cstart, landpt(:)%nap, mem_shape_patch_soilcarbon, var_shape_land_patch_soilcarbon, CABLE_NETCDF_DOUBLE) - - io_decomp%patch_to_patch_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch, var_shape_patch, CABLE_NETCDF_INT) - io_decomp%patch_to_patch_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch, var_shape_patch, CABLE_NETCDF_FLOAT) - io_decomp%patch_to_patch_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch, var_shape_patch, CABLE_NETCDF_DOUBLE) - io_decomp%patch_soil_to_patch_soil_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soil, var_shape_patch_soil, CABLE_NETCDF_INT) - io_decomp%patch_soil_to_patch_soil_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soil, var_shape_patch_soil, CABLE_NETCDF_FLOAT) - io_decomp%patch_soil_to_patch_soil_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soil, var_shape_patch_soil, CABLE_NETCDF_DOUBLE) - io_decomp%patch_snow_to_patch_snow_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_snow, var_shape_patch_snow, CABLE_NETCDF_INT) - io_decomp%patch_snow_to_patch_snow_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_snow, var_shape_patch_snow, CABLE_NETCDF_FLOAT) - io_decomp%patch_snow_to_patch_snow_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_snow, var_shape_patch_snow, CABLE_NETCDF_DOUBLE) - io_decomp%patch_rad_to_patch_rad_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_rad, var_shape_patch_rad, CABLE_NETCDF_INT) - io_decomp%patch_rad_to_patch_rad_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_rad, var_shape_patch_rad, CABLE_NETCDF_FLOAT) - io_decomp%patch_rad_to_patch_rad_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_rad, var_shape_patch_rad, CABLE_NETCDF_DOUBLE) - io_decomp%patch_plantcarbon_to_patch_plantcarbon_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_plantcarbon, var_shape_patch_plantcarbon, CABLE_NETCDF_INT) - io_decomp%patch_plantcarbon_to_patch_plantcarbon_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_plantcarbon, var_shape_patch_plantcarbon, CABLE_NETCDF_FLOAT) - io_decomp%patch_plantcarbon_to_patch_plantcarbon_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_plantcarbon, var_shape_patch_plantcarbon, CABLE_NETCDF_DOUBLE) - io_decomp%patch_soilcarbon_to_patch_soilcarbon_int32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soilcarbon, var_shape_patch_soilcarbon, CABLE_NETCDF_INT) - io_decomp%patch_soilcarbon_to_patch_soilcarbon_real32 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soilcarbon, var_shape_patch_soilcarbon, CABLE_NETCDF_FLOAT) - io_decomp%patch_soilcarbon_to_patch_soilcarbon_real64 = io_decomp_patch_to_patch(patch_decomp_start, mem_shape_patch_soilcarbon, var_shape_patch_soilcarbon, CABLE_NETCDF_DOUBLE) - - end subroutine - -end module diff --git a/src/offline/cable_mpimaster.F90 b/src/offline/cable_mpimaster.F90 index a6ade0384..19d9791ba 100644 --- a/src/offline/cable_mpimaster.F90 +++ b/src/offline/cable_mpimaster.F90 @@ -92,7 +92,6 @@ MODULE cable_mpimaster USE cable_mpicommon USE cable_IO_vars_module, ONLY : NO_CHECK use cable_io_vars_module, only: patch - USE cable_io_decomp_mod, ONLY: io_decomp_t, cable_io_decomp_init USE casa_cable USE casa_inout_module USE cable_checks_module, ONLY: constant_check_range @@ -343,8 +342,6 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) integer, dimension(:), allocatable, save :: cstart,cend,nap real(r_2), dimension(:,:,:), allocatable, save :: patchfrac_new - type(io_decomp_t) :: io_decomp - integer :: start_year call cable_netcdf_mod_init(mpi_grp_master) @@ -647,8 +644,6 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) ktau = 0 ENDIF - call cable_io_decomp_init(io_decomp) - if (.not. casaonly) then call cable_output_mod_init() call cable_output_register_output_variables(cable_output_core_outputs(canopy, soil)) diff --git a/src/offline/cable_serial.F90 b/src/offline/cable_serial.F90 index 7251ee6ca..0c4b97b4f 100644 --- a/src/offline/cable_serial.F90 +++ b/src/offline/cable_serial.F90 @@ -86,8 +86,6 @@ MODULE cable_serial defaultLAI, sdoy, smoy, syear, timeunits, calendar, & NO_CHECK use cable_io_vars_module, only: patch - USE cable_io_decomp_mod, ONLY: io_decomp_t - USE cable_io_decomp_mod, ONLY: cable_io_decomp_init USE casa_ncdf_module, ONLY: is_casa_time USE cable_common_module, ONLY: ktau_gl, kend_gl, knode_gl, cable_user, & filename, myhome, & @@ -283,8 +281,6 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi integer, dimension(:), allocatable, save :: cstart,cend,nap real(r_2), dimension(:,:,:), allocatable, save :: patchfrac_new - type(io_decomp_t) :: io_decomp - integer :: start_year ! END header @@ -474,8 +470,6 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi ENDIF - call cable_io_decomp_init(io_decomp) - if (.not. casaonly) then call cable_output_mod_init() call cable_output_register_output_variables(cable_output_core_outputs(canopy, soil)) From 55f9be4b0f8f69335664cb9e52c87b0776f0d4cf Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Mon, 9 Feb 2026 20:26:51 +1100 Subject: [PATCH 22/31] Clean up sampling frequency check subroutine --- src/util/io/output/cable_output_core.F90 | 13 +--- src/util/io/output/cable_output_utils.F90 | 78 +++++++++++------------ 2 files changed, 41 insertions(+), 50 deletions(-) diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 index 67c80097d..913de3147 100644 --- a/src/util/io/output/cable_output_core.F90 +++ b/src/util/io/output/cable_output_core.F90 @@ -60,7 +60,7 @@ module cable_output_core_mod use cable_output_decomp_mod, only: associate_decomp_real32 use cable_output_decomp_mod, only: associate_decomp_real64 - use cable_output_utils_mod, only: check_invalid_frequency + use cable_output_utils_mod, only: check_sampling_frequency use cable_output_utils_mod, only: dim_size use cable_output_utils_mod, only: define_variables use cable_output_utils_mod, only: set_global_attributes @@ -168,16 +168,7 @@ subroutine cable_output_profiles_init() ] & ) - do i = 1, size(global_profile%output_variables) - associate(output_var => global_profile%output_variables(i)) - call check_invalid_frequency( & - sampling_frequency=global_profile%sampling_frequency, & - accumulation_frequency=output_var%accumulation_frequency, & - var_name=output_var%name, & - file_name=global_profile%file_name & - ) - end associate - end do + call check_sampling_frequency(global_profile) call define_variables(global_profile) diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 646840ccd..22e86f2d8 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -47,7 +47,7 @@ module cable_output_utils_mod implicit none private - public :: check_invalid_frequency + public :: check_sampling_frequency public :: dim_size public :: infer_dim_names public :: define_variables @@ -96,49 +96,49 @@ function dim_size(dims) end function - subroutine check_invalid_frequency(sampling_frequency, accumulation_frequency, var_name, file_name) - character(len=*), intent(in) :: sampling_frequency - character(len=*), intent(in) :: accumulation_frequency - character(len=*), intent(in) :: var_name - character(len=*), intent(in) :: file_name + subroutine check_sampling_frequency(output_profile) + type(cable_output_profile_t), intent(in) :: output_profile - integer :: sampling_period_in_hours, accumulation_period_in_hours + integer :: i, sampling_period_in_hours, accumulation_period_in_hours character(len=256) :: err_message - err_message = ( & - "Invalid combination of sampling frequency '" // sampling_frequency // & - "' with accumulation frequency '" // accumulation_frequency // "' for variable '" // & - var_name // "' in file '" // file_name // "'" & - ) - - select case (sampling_frequency) - case ("all") - if (accumulation_frequency /= "all") call cable_abort(err_message, __FILE__, __LINE__) - case ("user") - read(sampling_frequency(5:7), *) sampling_period_in_hours - if (accumulation_frequency == "user") then - read(accumulation_frequency(5:7), *) accumulation_period_in_hours - if (sampling_period_in_hours < accumulation_period_in_hours) then - call cable_abort(err_message, __FILE__, __LINE__) - end if - else if (accumulation_frequency /= "all") then - call cable_abort(err_message, __FILE__, __LINE__) - end if - case ("daily") - if (.not. any(accumulation_frequency == ["all ", "daily", "user "])) then - call cable_abort(err_message, __FILE__, __LINE__) - end if - case ("monthly") - if (.not. any(accumulation_frequency == ["all ", "daily ", "user ", "monthly"])) then - call cable_abort(err_message, __FILE__, __LINE__) - end if - case default - call cable_abort("Invalid sampling frequency '" // sampling_frequency // & - "' for variable '" // var_name // "' in file '" // file_name // "'", __FILE__, __LINE__) - end select + do i = 1, size(output_profile%output_variables) + associate(output_var => output_profile%output_variables(i)) + err_message = ( & + "Invalid combination of sampling frequency '" // output_profile%sampling_frequency // & + "' with accumulation frequency '" // output_var%accumulation_frequency // "' for variable '" // & + output_var%name // "' in file '" // output_profile%file_name // "'" & + ) + select case (output_profile%sampling_frequency) + case ("all") + if (output_var%accumulation_frequency /= "all") call cable_abort(err_message, __FILE__, __LINE__) + case ("user") + read(output_profile%sampling_frequency(5:7), *) sampling_period_in_hours + if (output_var%accumulation_frequency == "user") then + read(output_var%accumulation_frequency(5:7), *) accumulation_period_in_hours + if (sampling_period_in_hours < accumulation_period_in_hours) then + call cable_abort(err_message, __FILE__, __LINE__) + end if + else if (output_var%accumulation_frequency /= "all") then + call cable_abort(err_message, __FILE__, __LINE__) + end if + case ("daily") + if (.not. any(output_var%accumulation_frequency == ["all ", "daily", "user "])) then + call cable_abort(err_message, __FILE__, __LINE__) + end if + case ("monthly") + if (.not. any(output_var%accumulation_frequency == ["all ", "daily ", "user ", "monthly"])) then + call cable_abort(err_message, __FILE__, __LINE__) + end if + case default + call cable_abort("Invalid sampling frequency '" // output_profile%sampling_frequency // & + "' for variable '" // output_var%name // "' in file '" // output_profile%file_name // "'", __FILE__, __LINE__) + end select + end associate + end do - end subroutine check_invalid_frequency + end subroutine check_sampling_frequency function infer_dim_names(output_profile, output_variable) result(dim_names) type(cable_output_profile_t), intent(in) :: output_profile From 07f646e0e573d512d77a6dddc2152c630925474d Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Tue, 10 Feb 2026 12:14:03 +1100 Subject: [PATCH 23/31] src/util/io/output/cable_output_definitions.F90: remove restart argument --- src/util/io/output/cable_output_definitions.F90 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/util/io/output/cable_output_definitions.F90 b/src/util/io/output/cable_output_definitions.F90 index afdddede1..2a493c143 100644 --- a/src/util/io/output/cable_output_definitions.F90 +++ b/src/util/io/output/cable_output_definitions.F90 @@ -185,9 +185,8 @@ function core_outputs(canopy, soil) result(output_variables) end function core_outputs - function coordinate_variables(grid_type, restart) result(output_variables) + function coordinate_variables(grid_type) result(output_variables) character(len=*), intent(in) :: grid_type - logical, intent(in), optional :: restart type(cable_output_variable_t), allocatable :: output_variables(:) From fa05244a2f551ae6112da696cd5b480724a9c83e Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 20 Feb 2026 16:25:32 +1100 Subject: [PATCH 24/31] Add carbon molar mass physical constant --- documentation/docs/user_guide/constants/physical_constants.md | 2 +- src/params/cable_phys_constants_mod.F90 | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/docs/user_guide/constants/physical_constants.md b/documentation/docs/user_guide/constants/physical_constants.md index b4344b993..256b272a0 100644 --- a/documentation/docs/user_guide/constants/physical_constants.md +++ b/documentation/docs/user_guide/constants/physical_constants.md @@ -20,4 +20,4 @@ | cswat | 4.218E3 | \( J \cdot kg^{-1} \cdot K^{-1} \) | Specific heat for water at \( 0^{\circ}C \) | | density_liq | 1000.0 | \( kg \cdot m^{-3} \) | Density of liquid water | | density_ice | 921.0 | \( kg \cdot m^{-3} \) | Density of ice | - +| c_molar_mass | 1.201E-5 | \( \micro g / mol \) | Molar mass of carbon | diff --git a/src/params/cable_phys_constants_mod.F90 b/src/params/cable_phys_constants_mod.F90 index 09138981b..99af05651 100644 --- a/src/params/cable_phys_constants_mod.F90 +++ b/src/params/cable_phys_constants_mod.F90 @@ -42,6 +42,7 @@ MODULE cable_phys_constants_mod REAL, PARAMETER :: cswat = 4.218e3 ! specific heat for water at 0°C (J/kg/K) REAL, PARAMETER :: density_liq = 1000.0 ! density of liquid water REAL, PARAMETER :: density_ice = 921.0 ! density of ice +REAL, PARAMETER :: c_molar_mass = 1.201e-5 ! molar mass of carbon (ug/mol) ! Teten coefficients REAL, PARAMETER :: tetena = 6.106 ! Magnus Tetans (Murray 1967) From f8cf7430050c0fd87f8a1aadc1644b08160082ec Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 13 Feb 2026 19:21:57 +1100 Subject: [PATCH 25/31] Introduce diagnostic variables for CABLE outputs Note that some biogeophysics diagnostic variables overlap with CASA-CNP diagnostic variables, e.g. `canopy%fgpp` and `canopy%fnpp`. When CASA-CNP is enabled, these diagnostic variables are re-calculated using CASA-CNP variables before writing the diagnostic variables to the output file. The overriding of diagnostic variables is done to match the current behaviour of the output module - there may be scope to enable both CASA and non-CASA variants of these diagnostics in the future. --- src/offline/cable_define_types.F90 | 33 ++++++++++++++++++++++ src/offline/casa_cable.F90 | 3 +- src/offline/cbl_model_driver_offline.F90 | 6 +++- src/science/canopy/cable_canopy.F90 | 14 +++++++++ src/science/casa-cnp/biogeochem_casa.F90 | 6 ++++ src/science/casa-cnp/casa_cnp.F90 | 8 +++--- src/science/casa-cnp/casa_readbiome.F90 | 3 ++ src/science/casa-cnp/casa_sumcflux.F90 | 3 +- src/science/casa-cnp/casa_variable.F90 | 14 +++++++++ src/science/pop/POPLUC.F90 | 2 ++ src/science/soilsnow/cbl_soilsnow_main.F90 | 2 ++ 11 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/offline/cable_define_types.F90 b/src/offline/cable_define_types.F90 index c720c918a..bf8580998 100644 --- a/src/offline/cable_define_types.F90 +++ b/src/offline/cable_define_types.F90 @@ -265,6 +265,7 @@ MODULE cable_def_types_mod qssrf, & ! sublimation snage, & ! snow age snowd, & ! snow depth (liquid water) + totsdepth, & ! total snow depth (m) smelt, & ! snow melt ssdnn, & ! average snow density tss, & ! surface temperature (weighted soil, snow) @@ -454,12 +455,18 @@ MODULE cable_def_types_mod frpw, & ! plant respiration (woody component) (g C m-2 s-1) frpr, & ! plant respiration (root component) (g C m-2 s-1) frs, & ! soil respiration (g C m-2 s-1) + fra, & ! autotrophic respiration (g C m-2 s-1) fnee, & ! net carbon flux (g C m-2 s-1) frday, & ! daytime leaf resp fnv, & ! net rad. avail. to canopy (W/m2) fev, & ! latent hf from canopy (W/m2) epot, & ! total potential evaporation + et, & ! total evapotranspiration (kg/m2/s) + eint, & ! interception evaporation from wet canopy (kg/m2/s) + tveg, & ! vegation transpiration (kg/m2/s) + esoil, & ! soil evaporation (kg/m2/s) fnpp, & ! npp flux + fgpp, & ! gpp flux fevw_pot,& ! potential lat heat from canopy gswx_T, & ! ! stom cond for water cdtq, & ! drag coefficient for momentum @@ -475,6 +482,7 @@ MODULE cable_def_types_mod ghflux, & ! ground heat flux (W/m2) ??? precis, & ! throughfall to soil, after snow (mm) qscrn, & ! specific humudity at screen height (g/g) + qmom, & ! surface momentum flux rnet, & ! net radiation absorbed by surface (W/m2) rniso, & !isothermal net radiation absorbed by surface (W/m2) segg, & ! latent heatfl from soil mm @@ -556,6 +564,9 @@ MODULE cable_def_types_mod latitude,& ! latitude lwabv, & ! long wave absorbed by vegetation qssabs, & ! absorbed short-wave radiation for soil + swnet, & ! net shortwave radiation absorbed by surface (W/m^2) + lwnet, & ! net longwave radiation absorbed by surface (W/m^2) + rnet, & ! net radiation absorbed by surface (W/m^2) transd, & ! frac SW diffuse transmitted through canopy trad, & ! radiative temperature (soil and veg) otrad ! radiative temperature on previous timestep (ACCESS) @@ -920,6 +931,7 @@ SUBROUTINE alloc_soil_snow_type(var, mp) ALLOCATE( var% smass(mp,msn) ) ALLOCATE( var% snage(mp) ) ALLOCATE( var% snowd(mp) ) + ALLOCATE( var% totsdepth(mp) ) ALLOCATE( var% smelt(mp) ) ALLOCATE( var% ssdn(mp,msn) ) ALLOCATE( var% ssdnn(mp) ) @@ -1125,6 +1137,7 @@ SUBROUTINE alloc_canopy_type(var, mp) ALLOCATE( var% frpw(mp) ) ALLOCATE( var% frpr(mp) ) ALLOCATE( var% frs(mp) ) + ALLOCATE( var% fra(mp) ) ALLOCATE( var% fnee(mp) ) ALLOCATE( var% frday(mp) ) ALLOCATE( var% fnv(mp) ) @@ -1138,6 +1151,7 @@ SUBROUTINE alloc_canopy_type(var, mp) ALLOCATE( var% ghflux(mp) ) ALLOCATE( var% precis(mp) ) ALLOCATE( var% qscrn(mp) ) + ALLOCATE( var% qmom(mp) ) ALLOCATE( var% rnet(mp) ) ALLOCATE( var% rniso(mp) ) ALLOCATE( var% segg(mp) ) @@ -1157,7 +1171,12 @@ SUBROUTINE alloc_canopy_type(var, mp) ALLOCATE( var% ga_cor(mp) ) !REV_CORR variable ALLOCATE ( var % evapfbl(mp,ms) ) ALLOCATE( var% epot(mp) ) + ALLOCATE( var% et(mp) ) + ALLOCATE( var% eint(mp) ) + ALLOCATE( var% tveg(mp) ) + ALLOCATE( var% esoil(mp) ) ALLOCATE( var% fnpp(mp) ) + ALLOCATE( var% fgpp(mp) ) ALLOCATE( var% fevw_pot(mp) ) ALLOCATE( var% gswx_T(mp) ) ALLOCATE( var% cdtq(mp) ) @@ -1211,6 +1230,9 @@ SUBROUTINE alloc_radiation_type(var, mp) ALLOCATE( var% lwabv(mp) ) ALLOCATE( var% qcan(mp,mf,nrb) ) ALLOCATE( var% qssabs(mp) ) + ALLOCATE( var% swnet(mp) ) + ALLOCATE( var% lwnet(mp) ) + ALLOCATE( var% rnet(mp) ) ALLOCATE( var% rhocdf(mp,nrb) ) ALLOCATE( var% rniso(mp,mf) ) ALLOCATE( var% scalex(mp,mf) ) @@ -1571,6 +1593,7 @@ SUBROUTINE dealloc_soil_snow_type(var) DEALLOCATE( var% smass ) DEALLOCATE( var% snage ) DEALLOCATE( var% snowd ) + DEALLOCATE( var% totsdepth ) DEALLOCATE( var% smelt ) DEALLOCATE( var% ssdn ) DEALLOCATE( var% ssdnn ) @@ -1762,6 +1785,7 @@ SUBROUTINE dealloc_canopy_type(var) DEALLOCATE( var% frpw ) DEALLOCATE( var% frpr ) DEALLOCATE( var% frs ) + DEALLOCATE( var% fra ) DEALLOCATE( var% fnee ) DEALLOCATE( var% frday ) DEALLOCATE( var% fnv ) @@ -1775,6 +1799,7 @@ SUBROUTINE dealloc_canopy_type(var) DEALLOCATE( var% ghflux ) DEALLOCATE( var% precis ) DEALLOCATE( var% qscrn ) + DEALLOCATE( var% qmom ) DEALLOCATE( var% rnet ) DEALLOCATE( var% rniso ) DEALLOCATE( var% segg ) @@ -1794,7 +1819,12 @@ SUBROUTINE dealloc_canopy_type(var) DEALLOCATE( var% ga_cor ) !REV_CORR variable DEALLOCATE ( var % evapfbl ) DEALLOCATE( var% epot ) + DEALLOCATE( var% et ) + DEALLOCATE( var% eint ) + DEALLOCATE( var% tveg ) + DEALLOCATE( var% esoil ) DEALLOCATE( var% fnpp ) + DEALLOCATE( var% fgpp ) DEALLOCATE( var% fevw_pot ) DEALLOCATE( var% gswx_T ) DEALLOCATE( var% cdtq ) @@ -1838,6 +1868,9 @@ SUBROUTINE dealloc_radiation_type(var) DEALLOCATE( var% lwabv ) DEALLOCATE( var% qcan ) DEALLOCATE( var% qssabs ) + DEALLOCATE( var% swnet ) + DEALLOCATE( var% lwnet ) + DEALLOCATE( var% rnet ) DEALLOCATE( var% rhocdf ) DEALLOCATE( var% rniso ) DEALLOCATE( var% scalex ) diff --git a/src/offline/casa_cable.F90 b/src/offline/casa_cable.F90 index b85c0ea1c..2498df01b 100644 --- a/src/offline/casa_cable.F90 +++ b/src/offline/casa_cable.F90 @@ -540,7 +540,8 @@ SUBROUTINE sumcflux(ktau, kstart, kend, dels, bgc, canopy, & sum_flux%sumrs = sum_flux%sumrs+canopy%frs*dels endif ! Set net ecosystem exchange after adjustments to frs: - canopy%fnpp = -1.0* canopy%fpn - canopy%frp + canopy%fnpp = casaflux%cnpp / 86400.0 + canopy%fra = canopy%frp + canopy%frday IF (icycle <= 1) THEN canopy%fnee = canopy%fpn + canopy%frs + canopy%frp ELSE diff --git a/src/offline/cbl_model_driver_offline.F90 b/src/offline/cbl_model_driver_offline.F90 index 763e68075..751b85b07 100644 --- a/src/offline/cbl_model_driver_offline.F90 +++ b/src/offline/cbl_model_driver_offline.F90 @@ -180,6 +180,8 @@ SUBROUTINE cbm( ktau,dels, air, bgc, canopy, met, rad%reffbm, rad%reffdf & ) !EffSurfRefl_beam, EffSurfRefldif_ +rad%albedo_T = (rad%albedo(:, 1) + rad%albedo(:, 2)) * 0.5 + ssnow%otss_0 = ssnow%otss ! vh should be before call to canopy? ssnow%otss = ssnow%tss @@ -219,11 +221,13 @@ SUBROUTINE cbm( ktau,dels, air, bgc, canopy, met, CALL carbon_pl(dels, soil, ssnow, veg, canopy, bgc) - canopy%fnpp = -1.0* canopy%fpn - canopy%frp + canopy%fnpp = -1.0 * canopy%fpn - canopy%frp canopy%fnee = canopy%fpn + canopy%frs + canopy%frp + canopy%fra = canopy%frp + canopy%frday ENDIF + canopy%fgpp = -1.0 * canopy%fpn + canopy%frday END SUBROUTINE cbm diff --git a/src/science/canopy/cable_canopy.F90 b/src/science/canopy/cable_canopy.F90 index 96073e6dc..2be20df78 100644 --- a/src/science/canopy/cable_canopy.F90 +++ b/src/science/canopy/cable_canopy.F90 @@ -654,6 +654,14 @@ SUBROUTINE define_canopy(bal,rad,rough,air,met,dels,ssnow,soil,veg, canopy,clima !CABLE3 #335 - AM3 #250 - removing the transd weighting canopy%epot = (canopy%fevw_pot + ssnow%potev/ssnow%cls) * dels/air%rlam + canopy%et = canopy%fe / air%rlam + + canopy%eint = canopy%fevw / air%rlam + + canopy%tveg = canopy%fevc / air%rlam + + canopy%esoil = canopy%fes / air%rlam + rlower_limit = canopy%epot * air%rlam / dels WHERE (rlower_limit == 0 ) rlower_limit = 1.e-7 !prevent from 0. by adding 1.e-7 (W/m2) @@ -840,6 +848,8 @@ SUBROUTINE define_canopy(bal,rad,rough,air,met,dels,ssnow,soil,veg, canopy,clima qsurf(j) = 0.1*rsts(j)*ssnow%wetfac(j) + 0.9*met%qv(j) ENDIF + canopy%qmom(j) = air%rho(j) * canopy%us(j) ** 2.0 + canopy%qscrn(j) = met%qv(j) - qstar(j) * ftemp(j) IF( canopy%vlaiw(j) >CLAI_THRESH .AND. rough%hruff(j) > 0.01) THEN @@ -1031,6 +1041,10 @@ SUBROUTINE define_canopy(bal,rad,rough,air,met,dels,ssnow,soil,veg, canopy,clima + CCAPP*Crmair * (tlfy-met%tk) * sum_rad_gradis * & canopy%fwet ! YP nov2009 +rad%swnet = sum(rad%qcan(:, :, 1), 2) + SUM(rad%qcan(:, :, 2), 2) + rad%qssabs +rad%lwnet = met%fld - CSboltz * Cemleaf * canopy%tv**4 * (1 - rad%transd) - rad%flws * rad%transd +rad%rnet = rad%swnet + rad%lwnet + DEALLOCATE(cansat,gbhu) DEALLOCATE(dsx, fwsoil, tlfx, tlfy) DEALLOCATE(ecy, hcy, rny) diff --git a/src/science/casa-cnp/biogeochem_casa.F90 b/src/science/casa-cnp/biogeochem_casa.F90 index ab0f4ebb1..78f9834e2 100644 --- a/src/science/casa-cnp/biogeochem_casa.F90 +++ b/src/science/casa-cnp/biogeochem_casa.F90 @@ -171,6 +171,12 @@ SUBROUTINE biogeochem(ktau,dels,idoY,LALLOC,veg,soil,casabiome,casapool,casaflux casapool%Psoillab = max(casapool%Psoillab,0.1) ENDIF +casaflux%cnbp = casaflux%cnpp + casapool%dClabiledt - casaflux%Crsoil + +casaflux%cplant_turnover_tot = sum(casaflux%Cplant_turnover, 2) + +casapool%dCdt = casapool%ctot - casapool%ctot_0 + END SUBROUTINE biogeochem END MODULE biogeochem_mod diff --git a/src/science/casa-cnp/casa_cnp.F90 b/src/science/casa-cnp/casa_cnp.F90 index 685b248e3..326b4d8b8 100644 --- a/src/science/casa-cnp/casa_cnp.F90 +++ b/src/science/casa-cnp/casa_cnp.F90 @@ -2181,13 +2181,13 @@ SUBROUTINE casa_cnpbal(veg,casamet,casapool,casaflux,casabal) ENDIF ENDDO - - + casapool%cplanttot = sum(casapool%cplant, wood) + casapool%clittertot = sum(casapool%clitter, str) + casapool%csoiltot = sum(casapool%csoil, slow) + casapool%clittertot casapool%ctot_0 = SUM(casabal%cplantlast,2)+SUM(casabal%clitterlast,2) & + SUM(casabal%csoillast,2)+ casabal%clabilelast - casapool%ctot = SUM(casapool%cplant,2)+SUM(casapool%clitter,2) & - + SUM(casapool%csoil,2)+ casapool%clabile + casapool%ctot = casapool%cplanttot + casapool%csoiltot + casapool%clabile casabal%sumcbal = casabal%sumcbal + casabal%cbalance diff --git a/src/science/casa-cnp/casa_readbiome.F90 b/src/science/casa-cnp/casa_readbiome.F90 index fc9c88355..fdba821e2 100644 --- a/src/science/casa-cnp/casa_readbiome.F90 +++ b/src/science/casa-cnp/casa_readbiome.F90 @@ -408,6 +408,9 @@ SUBROUTINE casa_readbiome(veg,soil,casabiome,casapool,casaflux,casamet,phen) /(casaflux%kmlabp(:)+casapool%psoillab(:)) ENDIF + casapool%clittertot = sum(casapool%clitter, str) + casapool%cplanttot = sum(casapool%cplant, wood) + casapool%csoiltot = sum(casapool%csoil, slow) + casapool%clittertot END SUBROUTINE casa_readbiome diff --git a/src/science/casa-cnp/casa_sumcflux.F90 b/src/science/casa-cnp/casa_sumcflux.F90 index 6257c0794..9406fbe60 100644 --- a/src/science/casa-cnp/casa_sumcflux.F90 +++ b/src/science/casa-cnp/casa_sumcflux.F90 @@ -88,7 +88,8 @@ SUBROUTINE sumcflux(ktau, kstart, kend, dels, bgc, canopy, & sum_flux%sumrs = sum_flux%sumrs+canopy%frs*dels endif ! Set net ecosystem exchange after adjustments to frs: - canopy%fnpp = -1.0* canopy%fpn - canopy%frp + canopy%fnpp = casaflux%cnpp / 86400.0 + canopy%fra = canopy%frp + canopy%frday IF (icycle <= 1) THEN canopy%fnee = canopy%fpn + canopy%frs + canopy%frp ELSE diff --git a/src/science/casa-cnp/casa_variable.F90 b/src/science/casa-cnp/casa_variable.F90 index 2937f2f93..c1f4def45 100644 --- a/src/science/casa-cnp/casa_variable.F90 +++ b/src/science/casa-cnp/casa_variable.F90 @@ -84,6 +84,10 @@ MODULE casavariable TYPE casa_pool REAL(r_2), DIMENSION(:),POINTER :: Clabile, & dClabiledt, & + dCdt , & + Cplanttot, & + Clittertot, & + Csoiltot, & Ctot , & ! vh_js ! Ctot_0 REAL(r_2), DIMENSION(:,:),POINTER :: Cplant, & @@ -132,6 +136,7 @@ MODULE casavariable TYPE casa_flux REAL(r_2), DIMENSION(:),POINTER :: Cgpp, & Cnpp, & + Cnbp, & Crp, & Crgplant, & Nminfix, & @@ -142,6 +147,7 @@ MODULE casavariable ! vh_js ! the 3 variables below are needed for POP coupling to CASA stemnpp, & frac_sapwood, & + Cplant_turnover_tot, & sapwood_area REAL(r_2), DIMENSION(:,:),POINTER :: fracCalloc, & fracNalloc, & @@ -203,6 +209,7 @@ MODULE casavariable REAL(r_2), DIMENSION(:),POINTER :: FluxNtoclear REAL(r_2), DIMENSION(:),POINTER :: FluxPtoclear REAL(r_2), DIMENSION(:),POINTER :: CtransferLUC + REAL(r_2), DIMENSION(:),POINTER :: FluxCtoLUC REAL(r_2), DIMENSION(:),POINTER :: meangpp REAL(r_2), DIMENSION(:),POINTER :: meanrleaf END TYPE casa_flux @@ -364,6 +371,10 @@ SUBROUTINE alloc_casavariable(casabiome,casapool,casaflux, & ALLOCATE(casapool%Clabile(arraysize), & casapool%dClabiledt(arraysize), & + casapool%dCdt(arraysize), & + casapool%Cplanttot(arraysize), & + casapool%Clittertot(arraysize), & + casapool%Csoiltot(arraysize), & casapool%Cplant(arraysize,mplant), & casapool%Nplant(arraysize,mplant), & casapool%Pplant(arraysize,mplant), & @@ -412,6 +423,7 @@ SUBROUTINE alloc_casavariable(casabiome,casapool,casaflux, & ALLOCATE(casaflux%Cgpp(arraysize), & casaflux%Cnpp(arraysize), & + casaflux%Cnbp(arraysize), & casaflux%Crp(arraysize), & casaflux%Crgplant(arraysize), & casaflux%Nminfix(arraysize), & @@ -460,6 +472,7 @@ SUBROUTINE alloc_casavariable(casabiome,casapool,casaflux, & casaflux%fromStoCO2(arraysize,msoil), & casaflux%stemnpp(arraysize), & casaflux%frac_sapwood(arraysize), & + casaflux%Cplant_turnover_tot(arraysize), & casaflux%sapwood_area(arraysize), & casaflux%Cplant_turnover(arraysize,mplant) , & casaflux%Cplant_turnover_disturbance(arraysize) , & @@ -493,6 +506,7 @@ SUBROUTINE alloc_casavariable(casabiome,casapool,casaflux, & ) ALLOCATE(casaflux%CtransferLUC(arraysize), SOURCE=0.0_r_2) + ALLOCATE(casaflux%FluxCtoLUC(arraysize), SOURCE=0.0_r_2) ALLOCATE(casaflux%FluxCtoco2(arraysize), SOURCE=0.0_r_2) diff --git a/src/science/pop/POPLUC.F90 b/src/science/pop/POPLUC.F90 index 579551967..08686f73f 100644 --- a/src/science/pop/POPLUC.F90 +++ b/src/science/pop/POPLUC.F90 @@ -1037,6 +1037,8 @@ SUBROUTINE POP_LUC_CASA_transfer(POPLUC,POP,LUC_EXPT,casapool,casabal,casaflux,k 991 FORMAT(1166(e14.7,2x)) + casaflux%FluxCtoLUC = casaflux%FluxCtohwp + casaflux%FluxCtoclear + ! update total carbon pools and "last" pool values for use in carbon balance checks. casapool%ctot = SUM(casapool%cplant,2)+SUM(casapool%clitter,2)+ & SUM(casapool%csoil,2)+casapool%clabile diff --git a/src/science/soilsnow/cbl_soilsnow_main.F90 b/src/science/soilsnow/cbl_soilsnow_main.F90 index d630abf5c..587cb903d 100644 --- a/src/science/soilsnow/cbl_soilsnow_main.F90 +++ b/src/science/soilsnow/cbl_soilsnow_main.F90 @@ -193,6 +193,8 @@ SUBROUTINE soil_snow(dels, soil, ssnow, canopy, met, bal, veg) ssnow%wbliq = ssnow%wb - ssnow%wbice + ssnow%totsdepth = sum(ssnow%sdepth, dim=2) + ssnow%wbtot = 0.0 DO k = 1, ms ! tot moisture this timestep From d0f81c3dba521554c8aeff4c673394d29ea367cb Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 20 Feb 2026 12:29:48 +1100 Subject: [PATCH 26/31] Remove duplicate namelist variables --- src/offline/cable_driver_common.F90 | 8 ++++---- src/offline/cable_mpimaster.F90 | 2 +- src/offline/cable_mpiworker.F90 | 4 +--- src/offline/cable_serial.F90 | 7 +++---- src/util/cable_common.F90 | 10 ++++++---- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/offline/cable_driver_common.F90 b/src/offline/cable_driver_common.F90 index 93cffc798..fd7a79039 100644 --- a/src/offline/cable_driver_common.F90 +++ b/src/offline/cable_driver_common.F90 @@ -13,6 +13,10 @@ MODULE cable_driver_common_mod snmin, & cable_user, & gw_params, & + l_casacnp, & + l_landuse, & + l_laiFeedbk, & + l_vcmaxFeedbk, & cable_runtime USE cable_IO_vars_module, ONLY : & soilparmnew, & @@ -56,10 +60,6 @@ MODULE cable_driver_common_mod LOGICAL, SAVE, PUBLIC :: spinup = .FALSE. ! model spinup to soil state equilibrium? LOGICAL, SAVE, PUBLIC :: spincasa = .FALSE. ! TRUE: CASA-CNP Will spin mloop times, FALSE: no spin up LOGICAL, SAVE, PUBLIC :: CASAONLY = .FALSE. ! ONLY Run CASA-CNP - LOGICAL, SAVE, PUBLIC :: l_casacnp = .FALSE. ! using CASA-CNP with CABLE - LOGICAL, SAVE, PUBLIC :: l_landuse = .FALSE. ! using CASA-CNP with CABLE - LOGICAL, SAVE, PUBLIC :: l_laiFeedbk = .FALSE. ! using prognostic LAI - LOGICAL, SAVE, PUBLIC :: l_vcmaxFeedbk = .FALSE. ! using prognostic Vcmax REAL, SAVE, PUBLIC :: delsoilM ! allowed variation in soil moisture for spin up REAL, SAVE, PUBLIC :: delsoilT ! allowed variation in soil temperature for spin up diff --git a/src/offline/cable_mpimaster.F90 b/src/offline/cable_mpimaster.F90 index 19d9791ba..18d923fba 100644 --- a/src/offline/cable_mpimaster.F90 +++ b/src/offline/cable_mpimaster.F90 @@ -80,7 +80,6 @@ MODULE cable_mpimaster spinup, & spincasa, & CASAONLY, & - l_landuse, & delsoilM, & delsoilT, & delgwM, & @@ -191,6 +190,7 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) USE cable_common_module, ONLY: ktau_gl, kend_gl, knode_gl, cable_user, & cable_runtime, fileName, & CurYear, & + l_landuse, & IS_LEAPYEAR, calcsoilalbedo, & kwidth_gl USE casa_ncdf_module, ONLY: is_casa_time diff --git a/src/offline/cable_mpiworker.F90 b/src/offline/cable_mpiworker.F90 index 030f62af1..405573f24 100644 --- a/src/offline/cable_mpiworker.F90 +++ b/src/offline/cable_mpiworker.F90 @@ -70,13 +70,11 @@ MODULE cable_mpiworker spinup, & spincasa, & CASAONLY, & - l_laiFeedbk, & - l_vcmaxFeedbk, & delsoilM, & delsoilT, & LALLOC USE cable_mpicommon - USE cable_common_module, ONLY: cable_user + USE cable_common_module, ONLY: cable_user, l_laiFeedbk, l_vcmaxFeedbk USE casa_inout_module USE casa_cable USE bgcdriver_mod, ONLY : bgcdriver diff --git a/src/offline/cable_serial.F90 b/src/offline/cable_serial.F90 index 0c4b97b4f..c764dbe89 100644 --- a/src/offline/cable_serial.F90 +++ b/src/offline/cable_serial.F90 @@ -66,10 +66,6 @@ MODULE cable_serial spinup, & spincasa, & CASAONLY, & - l_casacnp, & - l_landuse, & - l_laiFeedbk, & - l_vcmaxFeedbk, & delsoilM, & delsoilT, & delgwM, & @@ -91,6 +87,9 @@ MODULE cable_serial filename, myhome, & CurYear, & IS_LEAPYEAR, & + l_landuse, & + l_laiFeedbk, & + l_vcmaxFeedbk, & kwidth_gl ! physical constants diff --git a/src/util/cable_common.F90 b/src/util/cable_common.F90 index 20106ae42..553cfc2cd 100644 --- a/src/util/cable_common.F90 +++ b/src/util/cable_common.F90 @@ -46,10 +46,12 @@ MODULE cable_common_module LOGICAL :: calcsoilalbedo = .FALSE. !---Lestevens Sept2012 !---CASACNP switches and cycle index - LOGICAL, SAVE :: l_casacnp,l_laiFeedbk,l_vcmaxFeedbk - LOGICAL :: l_luc = .FALSE. - LOGICAL :: l_thinforest = .FALSE. - LOGICAL :: l_landuse = .FALSE. + LOGICAL :: l_casacnp = .FALSE. + LOGICAL :: l_laiFeedbk = .FALSE. + LOGICAL :: l_vcmaxFeedbk = .FALSE. + LOGICAL :: l_luc = .FALSE. + LOGICAL :: l_thinforest = .FALSE. + LOGICAL :: l_landuse = .FALSE. !---CABLE runtime switches def in this type TYPE kbl_internal_switches From 74d2ac939ab33b324c81a7bd4608beb5c01f2a0a Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 20 Feb 2026 16:26:41 +1100 Subject: [PATCH 27/31] src/util/cable_timing_utils.F90: Add seconds_per_day constant --- src/util/cable_timing_utils.F90 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/util/cable_timing_utils.F90 b/src/util/cable_timing_utils.F90 index e738035eb..80827cbb2 100644 --- a/src/util/cable_timing_utils.F90 +++ b/src/util/cable_timing_utils.F90 @@ -5,9 +5,13 @@ module cable_timing_utils_mod private public :: time_step_matches + public :: seconds_per_hour + public :: hours_per_day + public :: seconds_per_day integer, parameter :: seconds_per_hour = 3600 integer, parameter :: hours_per_day = 24 + integer, parameter :: seconds_per_day = 86400 integer, parameter :: months_in_year = 12 integer, parameter, dimension(months_in_year) :: & daysm = [31,28,31,30,31,30,31,31,30,31,30,31], & @@ -54,7 +58,7 @@ function time_step_matches(dels, ktau, frequency, leaps, start_year) result(matc else last_day_of_month_in_accumulated_days = last_day_of_month_in_accumulated_days + lastday end if - match = any(int(real(last_day_of_month_in_accumulated_days) * hours_per_day * seconds_per_hour / dels) == ktau) + match = any(int(real(last_day_of_month_in_accumulated_days) * seconds_per_day / dels) == ktau) case default call cable_abort('Error: unknown frequency "' // trim(adjustl(frequency)) // '"', __FILE__, __LINE__) end select From 0b1c8d1b66ea1d7f6a70b0f6b22ede8e13abc0ec Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Tue, 10 Feb 2026 16:11:06 +1100 Subject: [PATCH 28/31] src/util/aggregator.F90: Add scale and offset procedures --- src/util/aggregator.F90 | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/util/aggregator.F90 b/src/util/aggregator.F90 index 838c164a7..71ebd2cc7 100644 --- a/src/util/aggregator.F90 +++ b/src/util/aggregator.F90 @@ -30,6 +30,8 @@ module aggregator_mod procedure :: set_method => aggregator_set_method procedure :: rank => aggregator_rank procedure :: shape => aggregator_shape + procedure :: scale => aggregator_scale + procedure :: offset => aggregator_offset end type aggregator_t abstract interface @@ -247,6 +249,72 @@ function aggregator_shape(this) result(agg_shape) end function aggregator_shape + subroutine aggregator_scale(this, scale) + class(aggregator_t), intent(inout) :: this + real, intent(in) :: scale + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_int32_1d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_int32_2d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_int32_3d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_real32_0d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_real32_1d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_real32_2d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_real32_3d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_real64_0d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_real64_1d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_real64_2d_t) + this%aggregated_data = this%aggregated_data * scale + type is (aggregator_real64_3d_t) + this%aggregated_data = this%aggregated_data * scale + end select + + end subroutine aggregator_scale + + subroutine aggregator_offset(this, offset) + class(aggregator_t), intent(inout) :: this + real, intent(in) :: offset + + select type (this) + type is (aggregator_int32_0d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_int32_1d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_int32_2d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_int32_3d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_real32_0d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_real32_1d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_real32_2d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_real32_3d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_real64_0d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_real64_1d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_real64_2d_t) + this%aggregated_data = this%aggregated_data + offset + type is (aggregator_real64_3d_t) + this%aggregated_data = this%aggregated_data + offset + end select + + end subroutine aggregator_offset + subroutine mean_accumulate(this) class(aggregator_t), intent(inout) :: this From 5be03682ff7471a03a66280c5a3fc4e38e0c7988 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Thu, 12 Feb 2026 16:54:58 +1100 Subject: [PATCH 29/31] src/util/aggregator.F90: Add aggregator_type procedure --- src/util/aggregator.F90 | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/util/aggregator.F90 b/src/util/aggregator.F90 index 71ebd2cc7..3152aed58 100644 --- a/src/util/aggregator.F90 +++ b/src/util/aggregator.F90 @@ -28,6 +28,7 @@ module aggregator_mod contains procedure :: init => aggregator_init procedure :: set_method => aggregator_set_method + procedure :: type => aggregator_type procedure :: rank => aggregator_rank procedure :: shape => aggregator_shape procedure :: scale => aggregator_scale @@ -184,6 +185,38 @@ subroutine aggregator_set_method(this, method) end subroutine aggregator_set_method + character(16) function aggregator_type(this) + class(aggregator_t), intent(in) :: this + + select type (this) + type is (aggregator_int32_0d_t) + aggregator_type = "int32" + type is (aggregator_int32_1d_t) + aggregator_type = "int32" + type is (aggregator_int32_2d_t) + aggregator_type = "int32" + type is (aggregator_int32_3d_t) + aggregator_type = "int32" + type is (aggregator_real32_0d_t) + aggregator_type = "real32" + type is (aggregator_real32_1d_t) + aggregator_type = "real32" + type is (aggregator_real32_2d_t) + aggregator_type = "real32" + type is (aggregator_real32_3d_t) + aggregator_type = "real32" + type is (aggregator_real64_0d_t) + aggregator_type = "real64" + type is (aggregator_real64_1d_t) + aggregator_type = "real64" + type is (aggregator_real64_2d_t) + aggregator_type = "real64" + type is (aggregator_real64_3d_t) + aggregator_type = "real64" + end select + + end function aggregator_type + integer function aggregator_rank(this) class(aggregator_t), intent(in) :: this From 632def51fa386fb20916a9b56a2f7cb00447887a Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Fri, 20 Feb 2026 12:36:16 +1100 Subject: [PATCH 30/31] Add all output and restart definitions, bug fixes and other quality of life changes In src/offline/cable_mpimaster.F90 and src/offline/cable_serial.F90, daily aggregators are reset immediately after writing instead of in the next iteration as this breaks for the last ktau iteration. New components have been added to `cable_output_variable_t`. These include `scale`, `offset` which are useful for applying unit conversions, and also `field_name` and `netcdf_name` to facilitate both restart variable names (`field_name`) and output variable names (`netcdf_name`). All components of the cable_output_variable_t except `field_name` and `aggregator` now have defaults. This cleans up the interface for defining output variables. Improved validation checks on registered output variables. A new `coordinate_variables` component has been added to `cable_output_profile_t` such that coordinate variables can be treated differently from the requested outputs due to differing metadata requirements. Removed start_year hack from drivers by overriding cable_user%yearstart to syear for the default MetType case. --- src/offline/cable_driver_common.F90 | 5 + src/offline/cable_mpimaster.F90 | 59 +- src/offline/cable_serial.F90 | 46 +- src/util/io/output/cable_output_core.F90 | 165 +- src/util/io/output/cable_output_decomp.F90 | 12 +- .../io/output/cable_output_definitions.F90 | 2750 ++++++++++++++++- src/util/io/output/cable_output_types.F90 | 20 +- src/util/io/output/cable_output_utils.F90 | 145 +- 8 files changed, 2868 insertions(+), 334 deletions(-) diff --git a/src/offline/cable_driver_common.F90 b/src/offline/cable_driver_common.F90 index fd7a79039..d94dd0ef1 100644 --- a/src/offline/cable_driver_common.F90 +++ b/src/offline/cable_driver_common.F90 @@ -263,6 +263,7 @@ SUBROUTINE cable_driver_init_site(site) END SUBROUTINE cable_driver_init_site SUBROUTINE cable_driver_init_default(dels, koffset, kend) + USE cable_io_vars_module, ONLY : syear !! Model initialisation routine (default met specific). REAL, INTENT(OUT) :: dels !! Time step size in seconds INTEGER, INTENT(OUT) :: koffset !! Timestep to start at @@ -277,6 +278,10 @@ SUBROUTINE cable_driver_init_default(dels, koffset, kend) STOP 991 END IF + ncciy = syear + cable_user%YearStart = syear + cable_user%YearEnd = syear + END SUBROUTINE cable_driver_init_default SUBROUTINE cable_driver_init_plume(dels, koffset, PLUME) diff --git a/src/offline/cable_mpimaster.F90 b/src/offline/cable_mpimaster.F90 index 18d923fba..ea0f4d843 100644 --- a/src/offline/cable_mpimaster.F90 +++ b/src/offline/cable_mpimaster.F90 @@ -342,8 +342,6 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) integer, dimension(:), allocatable, save :: cstart,cend,nap real(r_2), dimension(:,:,:), allocatable, save :: patchfrac_new - integer :: start_year - call cable_netcdf_mod_init(mpi_grp_master) ! END header @@ -454,6 +452,14 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) ENDIF CALL nullify_write() ! nullify pointers CALL open_output_file( dels, soil, veg, bgc, rough, met) + + call cable_output_mod_init() + call cable_output_register_output_variables([ & + cable_output_core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, casapool, casamet, rough, bgc) & + ]) + call cable_output_profiles_init() + call cable_output_write_parameters(kstart, patch, landpt, met) + ENDIF ssnow%otss_0 = ssnow%tgg(:,1) @@ -644,12 +650,6 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) ktau = 0 ENDIF - if (.not. casaonly) then - call cable_output_mod_init() - call cable_output_register_output_variables(cable_output_core_outputs(canopy, soil)) - call cable_output_profiles_init() - end if - ! MPI: mostly original serial code follows... ENDIF ! CALL1 @@ -786,17 +786,6 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) !$ CALL POPLUC_set_patchfrac(POPLUC,LUC_EXPT) !$ ENDIF - ! TODO(Sean): this is a hack for determining if the current time step - ! is the last of the month. Better way to do this? - IF(ktau == 1) THEN - !MC - use met%year(1) instead of CABLE_USER%YearStart for non-GSWP forcing and leap years - IF ( TRIM(cable_user%MetType) .EQ. '' ) THEN - start_year = met%year(1) - ELSE - start_year = CABLE_USER%YearStart - ENDIF - END IF - IF ( .NOT. CASAONLY ) THEN IF ( icycle > 0 ) THEN @@ -814,12 +803,6 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) ENDIF ENDIF - if (ktau > kstart .and. mod(ktau - kstart, ktauday) == 0) then - ! Reset daily aggregators if previous time step was the end of day - call canopy%tscrn_max_daily%reset() - call canopy%tscrn_min_daily%reset() - end if - ! MPI: receive this time step's results from the workers CALL master_receive (ocomm, oktau, recv_ts) ! CALL MPI_Waitall (wnp, recv_req, recv_stats, ierr) @@ -898,16 +881,14 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) casamet,ssnow, & rad, bal, air, soil, veg, CSBOLTZ, & CEMLEAF, CEMSOIL ) - if (ktau_tot == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) - call cable_output_update(ktau_tot, dels, leaps, start_year, met) - call cable_output_write(ktau_tot, dels, leaps, start_year, met, patch, landpt) + call cable_output_update(ktau_tot, dels, leaps, cable_user%yearstart, met) + call cable_output_write(ktau_tot, dels, leaps, cable_user%yearstart, met, patch, landpt) CASE DEFAULT CALL write_output( dels, ktau, met, canopy, casaflux, casapool, & casamet, ssnow, & rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) - if (ktau_tot == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) - call cable_output_update(ktau_tot, dels, leaps, start_year, met) - call cable_output_write(ktau_tot, dels, leaps, start_year, met, patch, landpt) + call cable_output_update(ktau, dels, leaps, cable_user%yearstart, met) + call cable_output_write(ktau, dels, leaps, cable_user%yearstart, met, patch, landpt) END SELECT END IF ENDIF @@ -1094,18 +1075,22 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) CASE ('plum', 'cru', 'gswp', 'gswp3') CALL write_output( dels, ktau_tot, met, canopy, casaflux, casapool, casamet, & ssnow, rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) - if (ktau_tot == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) - call cable_output_update(ktau_tot, dels, leaps, start_year, met) - call cable_output_write(ktau_tot, dels, leaps, start_year, met, patch, landpt) + call cable_output_update(ktau_tot, dels, leaps, cable_user%yearstart, met) + call cable_output_write(ktau_tot, dels, leaps, cable_user%yearstart, met, patch, landpt) CASE DEFAULT CALL write_output( dels, ktau, met, canopy, casaflux, casapool, casamet, & ssnow, rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) - if (ktau == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) - call cable_output_update(ktau, dels, leaps, start_year, met) - call cable_output_write(ktau, dels, leaps, start_year, met, patch, landpt) + call cable_output_update(ktau, dels, leaps, cable_user%yearstart, met) + call cable_output_write(ktau, dels, leaps, cable_user%yearstart, met, patch, landpt) END SELECT END IF + IF (.not. casaonly .and. ktau > kstart .and. mod(ktau - kstart + 1, ktauday) == 0) THEN + ! Reset daily aggregators if previous time step was the end of day + CALL canopy%tscrn_max_daily%reset() + CALL canopy%tscrn_min_daily%reset() + END IF + IF(cable_user%consistency_check) THEN count_bal = count_bal +1; diff --git a/src/offline/cable_serial.F90 b/src/offline/cable_serial.F90 index c764dbe89..e87c61380 100644 --- a/src/offline/cable_serial.F90 +++ b/src/offline/cable_serial.F90 @@ -279,8 +279,6 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi integer, dimension(:,:), allocatable, save :: landmask integer, dimension(:), allocatable, save :: cstart,cend,nap real(r_2), dimension(:,:,:), allocatable, save :: patchfrac_new - - integer :: start_year ! END header ! INISTUFF @@ -432,6 +430,14 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi ENDIF CALL nullify_write() ! nullify pointers CALL open_output_file( dels, soil, veg, bgc, rough, met) + + call cable_output_mod_init() + call cable_output_register_output_variables([ & + cable_output_core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, casapool, casamet, rough, bgc) & + ]) + call cable_output_profiles_init() + call cable_output_write_parameters(kstart, patch, landpt, met) + ENDIF ssnow%otss_0 = ssnow%tgg(:,1) @@ -469,12 +475,6 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi ENDIF - if (.not. casaonly) then - call cable_output_mod_init() - call cable_output_register_output_variables(cable_output_core_outputs(canopy, soil)) - call cable_output_profiles_init() - end if - ENDIF ! CALL 1 ! globally (WRT code) accessible kend through USE cable_common_module @@ -585,17 +585,6 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi IF ( CABLE_USER%POPLUC) CALL POPLUC_set_patchfrac(POPLUC,LUC_EXPT) ENDIF - ! TODO(Sean): this is a hack for determining if the current time step - ! is the last of the month. Better way to do this? - IF(ktau == 1) THEN - !MC - use met%year(1) instead of CABLE_USER%YearStart for non-GSWP forcing and leap years - IF ( TRIM(cable_user%MetType) .EQ. '' ) THEN - start_year = met%year(1) - ELSE - start_year = CABLE_USER%YearStart - ENDIF - END IF - IF ( .NOT. CASAONLY ) THEN ! Feedback prognostic vcmax and daily LAI from casaCNP to CABLE @@ -606,12 +595,6 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi IF (.NOT. allocated(c1)) ALLOCATE( c1(mp,nrb), rhoch(mp,nrb), xk(mp,nrb) ) - if (ktau > kstart .and. mod(ktau - kstart, ktauday) == 0) then - ! Reset daily aggregators if previous time step was the end of day - call canopy%tscrn_max_daily%reset() - call canopy%tscrn_min_daily%reset() - end if - ! Call land surface scheme for this timestep, all grid points: CALL cbm( ktau, dels, air, bgc, canopy, met, bal, & rad, rough, soil, ssnow, sum_flux, veg, climate, xk, c1, rhoch ) @@ -748,15 +731,22 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi CASE ('plum', 'cru', 'bios', 'gswp', 'gswp3', 'site') CALL write_output( dels, ktau_tot, met, canopy, casaflux, casapool, casamet, & ssnow, rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) + call cable_output_update(ktau_tot, dels, leaps, cable_user%yearstart, met) + call cable_output_write(ktau_tot, dels, leaps, cable_user%yearstart, met, patch, landpt) CASE DEFAULT CALL write_output( dels, ktau, met, canopy, casaflux, casapool, casamet, & ssnow, rad, bal, air, soil, veg, CSBOLTZ, CEMLEAF, CEMSOIL ) + call cable_output_update(ktau, dels, leaps, cable_user%yearstart, met) + call cable_output_write(ktau, dels, leaps, cable_user%yearstart, met, patch, landpt) END SELECT - if (ktau == kstart) call cable_output_write_parameters(kstart, patch, landpt, met) - call cable_output_update(ktau, dels, leaps, start_year, met) - call cable_output_write(ktau, dels, leaps, start_year, met, patch, landpt) ENDIF + IF (.not. casaonly .and. ktau > kstart .and. mod(ktau - kstart + 1, ktauday) == 0) THEN + ! Reset daily aggregators if it is the end of day + CALL canopy%tscrn_max_daily%reset() + CALL canopy%tscrn_min_daily%reset() + END IF + ! Check triggered by cable_user%consistency_check = .TRUE. in cable.nml IF(cable_user%consistency_check) THEN diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 index 913de3147..a7442ecc8 100644 --- a/src/util/io/output/cable_output_core.F90 +++ b/src/util/io/output/cable_output_core.F90 @@ -47,6 +47,7 @@ module cable_output_core_mod use cable_output_types_mod, only: FILL_VALUE_INT32 use cable_output_types_mod, only: FILL_VALUE_REAL32 use cable_output_types_mod, only: FILL_VALUE_REAL64 + use cable_output_types_mod, only: CABLE_OUTPUT_VAR_TYPE_UNDEFINED use cable_output_reduction_buffers_mod, only: allocate_grid_reduction_buffers use cable_output_reduction_buffers_mod, only: deallocate_grid_reduction_buffers @@ -60,7 +61,9 @@ module cable_output_core_mod use cable_output_decomp_mod, only: associate_decomp_real32 use cable_output_decomp_mod, only: associate_decomp_real64 + use cable_output_utils_mod, only: check_duplicate_variable_names use cable_output_utils_mod, only: check_sampling_frequency + use cable_output_utils_mod, only: var_name use cable_output_utils_mod, only: dim_size use cable_output_utils_mod, only: define_variables use cable_output_utils_mod, only: set_global_attributes @@ -110,27 +113,33 @@ subroutine cable_output_register_output_variables(output_variables) do i = 1, size(output_variables) associate(output_var => output_variables(i)) + if (count(output_var%field_name == output_variables(:)%field_name) > 1) then + call cable_abort("Duplicate field_name found: " // trim(output_var%field_name), __FILE__, __LINE__) + end if if (all(output_var%reduction_method /= [character(32) :: "none", "grid_cell_average", "first_patch_in_grid_cell"])) then - call cable_abort("Invalid reduction method for variable " // trim(output_var%name), __FILE__, __LINE__) + call cable_abort("Invalid reduction method for variable " // trim(output_var%field_name), __FILE__, __LINE__) end if if (all(output_var%aggregation_method /= [character(32) :: "point", "mean", "max", "min", "sum"])) then - call cable_abort("Invalid aggregation method for variable " // trim(output_var%name), __FILE__, __LINE__) + call cable_abort("Invalid aggregation method for variable " // trim(output_var%field_name), __FILE__, __LINE__) end if - if (all(output_var%var_type /= [CABLE_NETCDF_INT, CABLE_NETCDF_FLOAT, CABLE_NETCDF_DOUBLE])) then - call cable_abort("Invalid variable type for variable " // trim(output_var%name), __FILE__, __LINE__) + if (all(output_var%var_type /= [CABLE_OUTPUT_VAR_TYPE_UNDEFINED, CABLE_NETCDF_INT, CABLE_NETCDF_FLOAT, CABLE_NETCDF_DOUBLE])) then + call cable_abort("Invalid variable type for variable " // trim(output_var%field_name), __FILE__, __LINE__) end if - if (count(output_var%name == output_variables(:)%name) > 1) then - call cable_abort("Duplicate variable name found: " // trim(output_var%name), __FILE__, __LINE__) + if (.not. allocated(output_var%aggregator)) then + call cable_abort("Undefined aggregator for variable " // trim(output_var%field_name), __FILE__, __LINE__) end if if (( & .not. allocated(output_var%data_shape) .and. output_var%aggregator%rank() /= 0 & ) .or. ( & - allocated(output_var%data_shape) .and. any(dim_size(output_var%data_shape) /= output_var%aggregator%shape()) & + allocated(output_var%data_shape) .and. ( & + size(output_var%data_shape) /= size(output_var%aggregator%shape()) .or. & + any(dim_size(output_var%data_shape) /= output_var%aggregator%shape()) & + ) & )) then - call cable_abort("Data shape does not match aggregator shape for variable " // trim(output_var%name), __FILE__, __LINE__) + call cable_abort("Data shape does not match aggregator shape for variable " // trim(output_var%field_name), __FILE__, __LINE__) end if if (output_var%reduction_method /= "none" .and. .not. output_var%distributed) then - call cable_abort("Grid cell reductions require distributed output for variable " // trim(output_var%name), __FILE__, __LINE__) + call cable_abort("Grid cell reductions require distributed output for variable " // trim(output_var%field_name), __FILE__, __LINE__) end if end associate end do @@ -162,12 +171,19 @@ subroutine cable_output_profiles_init() grid_type=grid_type, & file_name="test_output.nc", & ! TODO(Sean): use filename from namelist output_file=cable_netcdf_create_file("test_output.nc", iotype=CABLE_NETCDF_IOTYPE_CLASSIC), & ! TODO(Sean): use filename from namelist - output_variables=[ & - coordinate_variables(grid_type), & - pack(registered_output_variables, registered_output_variables(:)%active) & - ] & + coordinate_variables=coordinate_variables(grid_type), & + output_variables=pack(registered_output_variables, registered_output_variables(:)%active) & ) + do i = 1, size(global_profile%output_variables) + associate(output_var => global_profile%output_variables(i)) + if (output_var%patchout) output_var%reduction_method = "none" + if (global_profile%sampling_frequency == "all") output_var%aggregation_method = "point" + end associate + end do + + call check_duplicate_variable_names(global_profile) + call check_sampling_frequency(global_profile) call define_variables(global_profile) @@ -176,6 +192,15 @@ subroutine cable_output_profiles_init() call global_profile%output_file%end_def() + do i = 1, size(global_profile%coordinate_variables) + associate(coordinate_variable => global_profile%coordinate_variables(i)) + call coordinate_variable%aggregator%init(method="point") + call coordinate_variable%aggregator%accumulate() + call write_variable(global_profile, coordinate_variable) + call coordinate_variable%aggregator%reset() + end associate + end do + do i = 1, size(global_profile%output_variables) associate(output_variable => global_profile%output_variables(i)) call output_variable%aggregator%init(method=output_variable%aggregation_method) @@ -248,6 +273,8 @@ subroutine cable_output_write(time_index, dels, leaps, start_year, met, patch, l associate(output_variable => global_profile%output_variables(i)) if (output_variable%parameter) cycle if (check%ranges == ON_WRITE) call check_variable_range(output_variable, time_index, met) + if (allocated(output_variable%scale)) call output_variable%aggregator%scale(output_variable%scale) + if (allocated(output_variable%offset)) call output_variable%aggregator%offset(output_variable%offset) call write_variable(global_profile, output_variable, patch, landpt, frame=global_profile%frame + 1) call output_variable%aggregator%reset() end associate @@ -281,18 +308,20 @@ subroutine cable_output_write_restart(current_time) grid_type="restart", & file_name="test_restart.nc", & ! TODO(Sean): use filename from namelist output_file=cable_netcdf_create_file("test_restart.nc", iotype=CABLE_NETCDF_IOTYPE_CLASSIC), & ! TODO(Sean): use filename from namelist - output_variables=[ & - coordinate_variables(grid_type="restart"), & - pack(registered_output_variables, registered_output_variables(:)%restart) & - ] & + coordinate_variables=coordinate_variables(grid_type="restart"), & + output_variables=pack(registered_output_variables, registered_output_variables(:)%restart) & ) - call define_variables(restart_output_profile) + call define_variables(restart_output_profile, restart=.true.) call restart_output_profile%output_file%end_def() call restart_output_profile%output_file%put_var("time", [current_time]) + do i = 1, size(restart_output_profile%coordinate_variables) + call write_variable(restart_output_profile, restart_output_profile%coordinate_variables(i), restart=.true.) + end do + do i = 1, size(restart_output_profile%output_variables) call write_variable(restart_output_profile, restart_output_profile%output_variables(i), restart=.true.) end do @@ -318,11 +347,11 @@ subroutine check_variable_range(output_variable, time_index, met) type is (aggregator_real32_0d_t) ! TODO(Sean): implement range checking for scalars type is (aggregator_real32_1d_t) - call check_range(output_variable%name, aggregator%source_data, output_variable%range, time_index, met) + call check_range(output_variable%field_name, aggregator%source_data, output_variable%range, time_index, met) type is (aggregator_real32_2d_t) - call check_range(output_variable%name, aggregator%source_data, output_variable%range, time_index, met) + call check_range(output_variable%field_name, aggregator%source_data, output_variable%range, time_index, met) type is (aggregator_real32_3d_t) - call check_range(output_variable%name, aggregator%source_data, output_variable%range, time_index, met) + call check_range(output_variable%field_name, aggregator%source_data, output_variable%range, time_index, met) type is (aggregator_real64_0d_t) ! TODO(Sean): implement range checking for double precision types type is (aggregator_real64_1d_t) @@ -348,6 +377,7 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, class(cable_netcdf_decomp_t), pointer :: decomp integer :: i, ndims logical :: restart_local + character(128) :: variable_name integer(kind=int32), pointer :: write_buffer_int32_0d integer(kind=int32), pointer :: write_buffer_int32_1d(:) @@ -386,6 +416,9 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, end if end if + variable_name = var_name(output_variable) + if (restart_local) variable_name = output_variable%field_name + select type (aggregator => output_variable%aggregator) type is (aggregator_int32_0d_t) if (output_variable%reduction_method /= "none") then @@ -397,14 +430,14 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, write_buffer_int32_0d => aggregator%aggregated_data if (restart_local) write_buffer_int32_0d => aggregator%source_data if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_0d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_0d) end if type is (aggregator_int32_1d_t) @@ -426,20 +459,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_int32(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_1d, & decomp=decomp, & fill_value=FILL_VALUE_INT32, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_1d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_1d) end if type is (aggregator_int32_2d_t) @@ -461,20 +494,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_int32(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_2d, & decomp=decomp, & fill_value=FILL_VALUE_INT32, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_2d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_2d) end if type is (aggregator_int32_3d_t) @@ -496,20 +529,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_int32(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_3d, & decomp=decomp, & fill_value=FILL_VALUE_INT32, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_3d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_int32_3d) end if type is (aggregator_real32_0d_t) @@ -522,14 +555,14 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, write_buffer_real32_0d => aggregator%aggregated_data if (restart_local) write_buffer_real32_0d => aggregator%source_data if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_0d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_0d) end if type is (aggregator_real32_1d_t) @@ -556,20 +589,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_real32(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_1d, & decomp=decomp, & fill_value=FILL_VALUE_REAL32, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_1d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_1d) end if type is (aggregator_real32_2d_t) @@ -596,20 +629,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_real32(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_2d, & decomp=decomp, & fill_value=FILL_VALUE_REAL32, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_2d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_2d) end if type is (aggregator_real32_3d_t) @@ -636,20 +669,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_real32(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_3d, & decomp=decomp, & fill_value=FILL_VALUE_REAL32, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_3d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real32_3d) end if type is (aggregator_real64_0d_t) @@ -662,14 +695,14 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, write_buffer_real64_0d => aggregator%aggregated_data if (restart_local) write_buffer_real64_0d => aggregator%source_data if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_0d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_0d) end if type is (aggregator_real64_1d_t) @@ -696,20 +729,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_real64(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_1d, & decomp=decomp, & fill_value=FILL_VALUE_REAL64, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_1d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_1d) end if type is (aggregator_real64_2d_t) @@ -736,20 +769,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_real64(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_2d, & decomp=decomp, & fill_value=FILL_VALUE_REAL64, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_2d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_2d) end if type is (aggregator_real64_3d_t) @@ -776,20 +809,20 @@ subroutine write_variable(output_profile, output_variable, patch, landpt, frame, if (output_variable%distributed) then call associate_decomp_real64(output_profile, output_variable, decomp) call output_profile%output_file%write_darray( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_3d, & decomp=decomp, & fill_value=FILL_VALUE_REAL64, & frame=frame) else if (present(frame)) then - call output_profile%output_file%inq_var_ndims(output_variable%name, ndims) + call output_profile%output_file%inq_var_ndims(variable_name, ndims) call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_3d, & start=[(1, i = 1, ndims - 1), frame]) else call output_profile%output_file%put_var( & - var_name=output_variable%name, & + var_name=variable_name, & values=write_buffer_real64_3d) end if class default diff --git a/src/util/io/output/cable_output_decomp.F90 b/src/util/io/output/cable_output_decomp.F90 index 60229bb21..d3d7d0a78 100644 --- a/src/util/io/output/cable_output_decomp.F90 +++ b/src/util/io/output/cable_output_decomp.F90 @@ -438,7 +438,7 @@ subroutine associate_decomp_int32(output_profile, output_var, decomp) decomp => output_decomp%land_soilcarbon_int32 end if else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + call cable_abort("Unsupported data shape for output variable " // output_var%field_name, __FILE__, __LINE__) end if end subroutine associate_decomp_int32 @@ -498,7 +498,7 @@ subroutine associate_decomp_real32(output_profile, output_var, decomp) decomp => output_decomp%land_soilcarbon_real32 end if else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + call cable_abort("Unsupported data shape for output variable " // output_var%field_name, __FILE__, __LINE__) end if end subroutine associate_decomp_real32 @@ -558,7 +558,7 @@ subroutine associate_decomp_real64(output_profile, output_var, decomp) decomp => output_decomp%land_soilcarbon_real64 end if else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + call cable_abort("Unsupported data shape for output variable " // output_var%field_name, __FILE__, __LINE__) end if end subroutine associate_decomp_real64 @@ -581,7 +581,7 @@ subroutine associate_decomp_restart_int32(output_var, decomp) else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then decomp => decomp_grid_restart%patch_soilcarbon_int32 else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + call cable_abort("Unsupported data shape for output variable " // output_var%field_name, __FILE__, __LINE__) end if end subroutine associate_decomp_restart_int32 @@ -603,7 +603,7 @@ subroutine associate_decomp_restart_real32(output_var, decomp) else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then decomp => decomp_grid_restart%patch_soilcarbon_real32 else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + call cable_abort("Unsupported data shape for output variable " // output_var%field_name, __FILE__, __LINE__) end if end subroutine associate_decomp_restart_real32 @@ -625,7 +625,7 @@ subroutine associate_decomp_restart_real64(output_var, decomp) else if (data_shape_eq(output_var%data_shape, [CABLE_OUTPUT_DIM_PATCH, CABLE_OUTPUT_DIM_SOILCARBON])) then decomp => decomp_grid_restart%patch_soilcarbon_real64 else - call cable_abort("Unsupported data shape for output variable " // output_var%name, __FILE__, __LINE__) + call cable_abort("Unsupported data shape for output variable " // output_var%field_name, __FILE__, __LINE__) end if end subroutine associate_decomp_restart_real64 diff --git a/src/util/io/output/cable_output_definitions.F90 b/src/util/io/output/cable_output_definitions.F90 index 2a493c143..2a144540d 100644 --- a/src/util/io/output/cable_output_definitions.F90 +++ b/src/util/io/output/cable_output_definitions.F90 @@ -1,8 +1,39 @@ module cable_output_definitions_mod + use cable_def_types_mod, only: met_type use cable_def_types_mod, only: canopy_type use cable_def_types_mod, only: soil_parameter_type + use cable_def_types_mod, only: soil_snow_type + use cable_def_types_mod, only: radiation_type + use cable_def_types_mod, only: veg_parameter_type + use cable_def_types_mod, only: balances_type + use cable_def_types_mod, only: roughness_type + use cable_def_types_mod, only: bgc_pool_type use cable_def_types_mod, only: mvtype, mstype + use cable_timing_utils_mod, only: seconds_per_day + + use cable_phys_constants_mod, only: c_molar_mass + + use casavariable, only: casa_flux + use casavariable, only: casa_pool + use casavariable, only: casa_met + + use casaparm, only: LEAF + use casaparm, only: WOOD + use casaparm, only: FROOT + + use casaparm, only: MIC + use casaparm, only: SLOW + use casaparm, only: PASS + + use casaparm, only: METB + use casaparm, only: STR + use casaparm, only: CWD + + use cable_common_module, only: cable_user + use cable_common_module, only: l_casacnp + use cable_common_module, only: gw_params + use cable_netcdf_mod, only: CABLE_NETCDF_INT use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT @@ -19,6 +50,9 @@ module cable_output_definitions_mod use cable_output_types_mod, only: DIM_PATCH => CABLE_OUTPUT_DIM_PATCH use cable_output_types_mod, only: DIM_SOIL => CABLE_OUTPUT_DIM_SOIL use cable_output_types_mod, only: DIM_RAD => CABLE_OUTPUT_DIM_RAD + use cable_output_types_mod, only: DIM_SNOW => CABLE_OUTPUT_DIM_SNOW + use cable_output_types_mod, only: DIM_PLANTCARBON => CABLE_OUTPUT_DIM_PLANTCARBON + use cable_output_types_mod, only: DIM_SOILCARBON => CABLE_OUTPUT_DIM_SOILCARBON use cable_output_types_mod, only: DIM_LAND => CABLE_OUTPUT_DIM_LAND use cable_output_types_mod, only: DIM_LAND_GLOBAL => CABLE_OUTPUT_DIM_LAND_GLOBAL use cable_output_types_mod, only: DIM_X => CABLE_OUTPUT_DIM_X @@ -34,300 +68,2686 @@ module cable_output_definitions_mod public :: core_outputs public :: coordinate_variables -contains +contains + + function core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, casapool, casamet, rough, bgc) result(output_variables) + real, intent(in) :: dels + type(met_type), intent(inout) :: met + type(canopy_type), intent(inout) :: canopy + type(soil_parameter_type), intent(inout) :: soil + type(soil_snow_type), intent(inout) :: ssnow + type(radiation_type), intent(inout) :: rad + type(veg_parameter_type), intent(inout) :: veg + type(balances_type), intent(inout) :: bal + type(casa_flux), intent(inout) :: casaflux + type(casa_pool), intent(inout) :: casapool + type(casa_met), intent(inout) :: casamet + type(roughness_type), intent(inout) :: rough + type(bgc_pool_type), intent(inout) :: bgc + + type(cable_output_variable_t), allocatable :: output_variables(:) + + output_variables = [ & + casa_cnp_outputs(casaflux, casapool, casamet), & + cable_output_variable_t( & + field_name="fsd", & + netcdf_name="SWdown", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SWdown, & + active=output%met .or. output%SWdown, & + patchout=output%patch .or. patchout%SWdown, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%ofsd), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Downward shortwave radiation") & + ] & + ), & + cable_output_variable_t( & + field_name="fld", & + netcdf_name="LWdown", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%LWdown, & + active=output%met .or. output%LWdown, & + patchout=output%patch .or. patchout%LWdown, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%fld), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Downward longwave radiation") & + ] & + ), & + cable_output_variable_t( & + field_name="precip", & + netcdf_name="Rainf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Rainf, & + active=output%met .or. output%Rainf, & + patchout=output%patch .or. patchout%Rainf, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%precip), & + scale=(1.0 / dels), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Rainfall+snowfall") & + ] & + ), & + cable_output_variable_t( & + field_name="precip_sn", & + netcdf_name="Snowf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Snowf, & + active=output%met .or. output%Snowf, & + patchout=output%patch .or. patchout%Snowf, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%precip_sn), & + scale=(1.0 / dels), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Snowfall") & + ] & + ), & + cable_output_variable_t( & + field_name="pmb", & + netcdf_name="PSurf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%PSurf, & + active=output%met .or. output%PSurf, & + patchout=output%patch .or. patchout%PSurf, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%pmb), & + metadata=[ & + attribute("units", "hPa"), & + attribute("long_name", "Surface air pressure") & + ] & + ), & + cable_output_variable_t( & + field_name="tk", & + netcdf_name="Tair", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Tair, & + active=output%met .or. output%Tair, & + patchout=output%patch .or. patchout%Tair, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%tk), & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Surface air temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="qv", & + netcdf_name="Qair", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qair, & + active=output%met .or. output%Qair, & + patchout=output%patch .or. patchout%Qair, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%qv), & + metadata=[ & + attribute("units", "kg/kg"), & + attribute("long_name", "Surface specific humidity") & + ] & + ), & + cable_output_variable_t( & + field_name="ua", & + netcdf_name="Wind", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Wind, & + active=output%met .or. output%Wind, & + patchout=output%patch .or. patchout%Wind, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%ua), & + metadata=[ & + attribute("units", "m/s"), & + attribute("long_name", "Scalar surface wind speed") & + ] & + ), & + cable_output_variable_t( & + field_name="ca", & + netcdf_name="CO2air", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%CO2air, & + active=output%met .or. output%CO2air, & + patchout=output%patch .or. patchout%CO2air, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + scale=1e6, & ! Convert to ppmv + aggregator=new_aggregator(met%ca), & + metadata=[ & + attribute("units", "ppmv"), & + attribute("long_name", "Surface air CO2 concentration") & + ] & + ), & + cable_output_variable_t( & + field_name="qmom", & + netcdf_name="Qmom", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qmom, & + active=output%flux .or. output%Qmom, & + patchout=output%patch .or. patchout%Qmom, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%qmom), & + metadata=[ & + attribute("units", "kg/m/s2"), & + attribute("long_name", "Surface momentum flux") & + ] & + ), & + cable_output_variable_t( & + field_name="fe", & + netcdf_name="Qle", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qle, & + active=output%flux .or. output%Qle, & + patchout=output%patch .or. patchout%Qle, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fe), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Surface latent heat flux") & + ] & + ), & + cable_output_variable_t( & + field_name="fh", & + netcdf_name="Qh", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qh, & + active=output%flux .or. output%Qh, & + patchout=output%patch .or. patchout%Qh, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fh), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Surface sensible heat flux") & + ] & + ), & + cable_output_variable_t( & + field_name="ga", & + netcdf_name="Qg", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qg, & + active=output%flux .or. output%Qg, & + patchout=output%patch .or. patchout%Qg, & + restart=.true., & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%ga), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Surface ground heat flux") & + ] & + ), & + cable_output_variable_t( & + field_name="rnof1", & + netcdf_name="Qs", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qs, & + active=output%flux .or. output%Qs, & + patchout=output%patch .or. patchout%Qs, & + restart=.true., & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%rnof1), & + scale=(1.0 / dels), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Surface runoff") & + ] & + ), & + cable_output_variable_t( & + field_name="rnof2", & + netcdf_name="Qsb", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qsb, & + active=output%flux .or. output%Qsb, & + patchout=output%patch .or. patchout%Qsb, & + restart=.true., & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%rnof2), & + scale=(1.0 / dels), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Subsurface runoff") & + ] & + ), & + cable_output_variable_t( & + field_name="et", & + netcdf_name="Evap", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Evap, & + active=output%flux .or. output%Evap, & + patchout=output%patch .or. patchout%Evap, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%et), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Total evapotranspiration") & + ] & + ), & + cable_output_variable_t( & + field_name="epot", & + netcdf_name="PotEvap", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%PotEvap, & + active=output%flux .or. output%PotEvap, & + patchout=output%patch .or. patchout%PotEvap, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%epot), & + scale=(1.0 / dels), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Potential evaporation") & + ] & + ), & + cable_output_variable_t( & + field_name="eint", & + netcdf_name="ECanop", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%ECanop, & + active=output%flux .or. output%ECanop, & + patchout=output%patch .or. patchout%ECanop, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%eint), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Wet canopy evaporation") & + ] & + ), & + cable_output_variable_t( & + field_name="tveg", & + netcdf_name="TVeg", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TVeg, & + active=output%flux .or. output%TVeg, & + patchout=output%patch .or. patchout%TVeg, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%tveg), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Vegetation transpiration") & + ] & + ), & + cable_output_variable_t( & + field_name="esoil", & + netcdf_name="ESoil", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%ESoil, & + active=output%flux .or. output%ESoil, & + patchout=output%patch .or. patchout%ESoil, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%esoil), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Evaporation from soil") & + ] & + ), & + cable_output_variable_t( & + field_name="fhv", & + netcdf_name="HVeg", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%HVeg, & + active=output%flux .or. output%HVeg, & + patchout=output%patch .or. patchout%HVeg, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fhv), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Sensible heat from vegetation") & + ] & + ), & + cable_output_variable_t( & + field_name="fhs", & + netcdf_name="HSoil", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%HSoil, & + active=output%flux .or. output%HSoil, & + patchout=output%patch .or. patchout%HSoil, & + restart=.true., & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fhs), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Sensible heat from soil") & + ] & + ), & + cable_output_variable_t( & + field_name="fns", & + netcdf_name="RnetSoil", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%HSoil, & + active=output%flux .or. output%RnetSoil, & + patchout=output%patch .or. patchout%RnetSoil, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fns), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Net radiation absorbed by ground") & + ] & + ), & + cable_output_variable_t( & + field_name="fnee", & + netcdf_name="NEE", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%flux .or. output%NEE, & + patchout=output%patch .or. patchout%NEE, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fnee), & + scale=(1.0 / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Net ecosystem exchange of CO2") & + ] & + ), & + cable_output_variable_t( & + field_name="wb", & + netcdf_name="SoilMoist", & + data_shape=[DIM_PATCH, DIM_SOIL], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SoilMoist, & + active=output%soil .or. output%SoilMoist, & + patchout=output%patch .or. patchout%SoilMoist, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%wb), & + restart=.true., & + metadata=[ & + attribute("units", "m^3/m^3"), & + attribute("long_name", "Average layer soil moisture") & + ] & + ), & + cable_output_variable_t( & + field_name="wbice", & + netcdf_name="SoilMoistIce", & + data_shape=[DIM_PATCH, DIM_SOIL], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SoilMoist, & + active=output%soil .or. output%SoilMoistIce, & + patchout=output%patch .or. patchout%SoilMoistIce, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%wbice), & + restart=.true., & + metadata=[ & + attribute("units", "m^3/m^3"), & + attribute("long_name", "Average layer frozen soil moisture") & + ] & + ), & + cable_output_variable_t( & + field_name="tgg", & + netcdf_name="SoilTemp", & + data_shape=[DIM_PATCH, DIM_SOIL], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SoilTemp, & + active=output%soil .or. output%SoilTemp, & + patchout=output%patch .or. patchout%SoilTemp, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%tgg), & + restart=.true., & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Average layer soil temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="gammzz", & + data_shape=[DIM_PATCH, DIM_SOIL], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + aggregation_method="mean", & + restart=.true., & + aggregator=new_aggregator(ssnow%gammzz), & + metadata=[ & + attribute("units", "J/kg/C"), & + attribute("long_name", "Heat capacity for each soil layer") & + ] & + ), & + cable_output_variable_t( & + field_name="ssdn", & + data_shape=[DIM_PATCH, DIM_SNOW], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + aggregation_method="mean", & + restart=.true., & + aggregator=new_aggregator(ssnow%ssdn), & + metadata=[ & + attribute("units", "kg/m^3"), & + attribute("long_name", "Average layer snow density") & + ] & + ), & + cable_output_variable_t( & + field_name="smass", & + data_shape=[DIM_PATCH, DIM_SNOW], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + aggregation_method="mean", & + restart=.true., & + aggregator=new_aggregator(ssnow%smass), & + metadata=[ & + attribute("units", "kg/m^2"), & + attribute("long_name", "Average layer snow mass") & + ] & + ), & + cable_output_variable_t( & + field_name="tgg1", & + netcdf_name="BaresoilT", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%BaresoilT, & + active=output%soil .or. output%BaresoilT, & + patchout=output%patch .or. patchout%BaresoilT, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%tgg(:, 1)), & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Bare soil temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="snowd", & + netcdf_name="SWE", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SWE, & + active=output%snow .or. output%SWE, & + patchout=output%patch .or. patchout%SWE, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%snowd), & + restart=.true., & + metadata=[ & + attribute("units", "kg/m^2"), & + attribute("long_name", "Snow water equivalent") & + ] & + ), & + cable_output_variable_t( & + field_name="smelt", & + netcdf_name="SnowMelt", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SnowMelt, & + active=output%snow .or. output%SnowMelt, & + patchout=output%patch .or. patchout%SnowMelt, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%smelt), & + scale=(1.0 / dels), & + metadata=[ & + attribute("units", "kg/m^2/s"), & + attribute("long_name", "Snow Melt Rate") & + ] & + ), & + cable_output_variable_t( & + field_name="tggsn", & + data_shape=[DIM_PATCH, DIM_SNOW], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + aggregation_method="mean", & + restart=.true., & + aggregator=new_aggregator(ssnow%tggsn), & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Average layer snow temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="tggsn1", & + netcdf_name="SnowT", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SnowT, & + active=output%snow .or. output%SnowT, & + patchout=output%patch .or. patchout%SnowT, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%tggsn(:, 1)), & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Snow surface temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="sdepth", & + data_shape=[DIM_PATCH, DIM_SNOW], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SnowDepth, & + active=.false., & + aggregation_method="mean", & + restart=.true., & + aggregator=new_aggregator(ssnow%sdepth), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Snow layer depth") & + ] & + ), & + cable_output_variable_t( & + field_name="totsdepth", & + netcdf_name="SnowDepth", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SnowDepth, & + active=output%snow .or. output%SnowDepth, & + patchout=output%patch .or. patchout%SnowDepth, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%totsdepth), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Snow depth") & + ] & + ), & + cable_output_variable_t( & + field_name="swnet", & + netcdf_name="SWnet", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SWnet, & + active=output%radiation .or. output%SWnet, & + patchout=output%patch .or. patchout%SWnet, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(rad%swnet), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Net shortwave radiation absorbed by surface") & + ] & + ), & + cable_output_variable_t( & + field_name="lwnet", & + netcdf_name="LWnet", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%LWnet, & + active=output%radiation .or. output%LWnet, & + patchout=output%patch .or. patchout%LWnet, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(rad%lwnet), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Net longwave radiation absorbed by surface") & + ] & + ), & + cable_output_variable_t( & + field_name="rnet", & + netcdf_name="Rnet", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Rnet, & + active=output%radiation .or. output%Rnet, & + patchout=output%patch .or. patchout%Rnet, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(rad%rnet), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Net radiation absorbed by surface") & + ] & + ), & + cable_output_variable_t( & + field_name="albedo_T", & + netcdf_name="Albedo", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Albedo, & + active=output%radiation .or. output%Albedo, & + patchout=output%patch .or. patchout%Albedo, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(rad%albedo_T), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Surface albedo") & + ] & + ), & + cable_output_variable_t( & + field_name="albedo_vis", & + netcdf_name="visAlbedo", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%visAlbedo, & + active=output%radiation .or. output%visAlbedo, & + patchout=output%patch .or. patchout%visAlbedo, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(rad%albedo(:, 1)), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Surface vis albedo") & + ] & + ), & + cable_output_variable_t( & + field_name="albedo_nir", & + netcdf_name="nirAlbedo", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%nirAlbedo, & + active=output%radiation .or. output%nirAlbedo, & + patchout=output%patch .or. patchout%nirAlbedo, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(rad%albedo(:, 2)), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Surface nir albedo") & + ] & + ), & + cable_output_variable_t( & + field_name="trad", & + netcdf_name="RadT", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%RadT, & + active=output%radiation .or. output%RadT, & + patchout=output%patch .or. patchout%RadT, & + restart=.true., & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(rad%trad), & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Radiative surface temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="tscrn", & + netcdf_name="Tscrn", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Tscrn, & + active=output%veg .or. output%Tscrn, & + patchout=output%patch .or. patchout%Tscrn, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%tscrn), & + metadata=[ & + attribute("units", "oC"), & + attribute("long_name", "screen level air temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="tscrn_max", & + netcdf_name="Txx", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Tscrn, & + active=output%veg .or. output%Tex, & + patchout=output%patch .or. patchout%Tex, & + reduction_method="grid_cell_average", & + aggregation_method="max", & + aggregator=new_aggregator(canopy%tscrn), & + metadata=[ & + attribute("units", "oC"), & + attribute("long_name", "max screen-level T in reporting period") & + ] & + ), & + cable_output_variable_t( & + field_name="tscrn_min", & + netcdf_name="Tnn", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Tscrn, & + active=output%veg .or. output%Tex, & + patchout=output%patch .or. patchout%Tex, & + reduction_method="grid_cell_average", & + aggregation_method="min", & + aggregator=new_aggregator(canopy%tscrn), & + metadata=[ & + attribute("units", "oC"), & + attribute("long_name", "min screen-level T in reporting period") & + ] & + ), & + cable_output_variable_t( & + field_name="tscrn_max_daily", & + netcdf_name="Tmx", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=(output%veg .or. output%Tex) .and. output%averaging == "monthly", & + patchout=output%patch .or. patchout%Tex, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + range=ranges%Tscrn, & + accumulation_frequency="daily", & + aggregator=new_aggregator(canopy%tscrn_max_daily%aggregated_data), & + metadata=[ & + attribute("units", "oC"), & + attribute("long_name", "averaged daily maximum screen-level T") & + ] & + ), & + cable_output_variable_t( & + field_name="tscrn_min_daily", & + netcdf_name="Tmn", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=(output%veg .or. output%Tex) .and. output%averaging == "monthly", & + patchout=output%patch .or. patchout%Tex, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + range=ranges%Tscrn, & + accumulation_frequency="daily", & + aggregator=new_aggregator(canopy%tscrn_min_daily%aggregated_data), & + metadata=[ & + attribute("units", "oC"), & + attribute("long_name", "averaged daily minimum screen-level T") & + ] & + ), & + cable_output_variable_t( & + field_name="qscrn", & + netcdf_name="Qscrn", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qscrn, & + active=output%veg .or. output%Qscrn, & + patchout=output%patch .or. patchout%Qscrn, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%qscrn), & + metadata=[ & + attribute("units", "kg/kg"), & + attribute("long_name", "screen level specific humidity") & + ] & + ), & + cable_output_variable_t( & + field_name="tv", & + netcdf_name="VegT", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%VegT, & + active=output%veg .or. output%VegT, & + patchout=output%patch .or. patchout%VegT, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%tv), & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Average vegetation temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="tvair", & + netcdf_name="CanT", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%CanT, & + active=output%veg .or. output%CanT, & + patchout=output%patch .or. patchout%CanT, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(met%tvair), & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Within-canopy temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="fwsoil", & + netcdf_name="Fwsoil", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Fwsoil, & + active=output%veg .or. output%Fwsoil, & + patchout=output%patch .or. patchout%Fwsoil, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fwsoil), & + metadata=[ & + attribute("units", "[-]"), & + attribute("long_name", "soil moisture modifier to stomatal conductance") & + ] & + ), & + cable_output_variable_t( & + field_name="cansto", & + netcdf_name="CanopInt", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%CanopInt, & + active=output%veg .or. output%CanopInt, & + patchout=output%patch .or. patchout%CanopInt, & + restart=.true., & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%cansto), & + metadata=[ & + attribute("units", "kg/m^2"), & + attribute("long_name", "Canopy intercepted water storage") & + ] & + ), & + cable_output_variable_t( & + field_name="vlai", & + netcdf_name="LAI", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%LAI, & + active=output%veg .or. output%LAI, & + patchout=output%patch .or. patchout%LAI, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(veg%vlai), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Leaf area index") & + ] & + ), & + cable_output_variable_t( & + field_name="ebal_tot", & + netcdf_name="Ebal", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Ebal, & + active=output%balances .or. output%Ebal, & + patchout=output%patch .or. patchout%Ebal, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(bal%ebal_tot), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Cumulative energy imbalance") & + ] & + ), & + cable_output_variable_t( & + field_name="wbal_tot", & + netcdf_name="Wbal", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Wbal, & + active=output%balances .or. output%Wbal, & + patchout=output%patch .or. patchout%Wbal, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(bal%wbal_tot), & + metadata=[ & + attribute("units", "kg/m^2"), & + attribute("long_name", "Cumulative water imbalance") & + ] & + ), & + cable_output_variable_t( & + field_name="wbtot0", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(bal%wbtot0), & + metadata=[ & + attribute("units", "mm"), & + attribute("long_name", "Initial time step soil water total") & + ] & + ), & + cable_output_variable_t( & + field_name="osnowd0", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(bal%osnowd0), & + metadata=[ & + attribute("units", "mm"), & + attribute("long_name", "Initial time step snow water total") & + ] & + ), & + cable_output_variable_t( & + field_name="frday", & + netcdf_name="LeafResp", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%AutoResp, & + active=output%carbon .or. output%LeafResp, & + patchout=output%patch .or. patchout%LeafResp, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%frday), & + scale=(1.0 / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Leaf respiration") & + ] & + ), & + cable_output_variable_t( & + field_name="frs", & + netcdf_name="HeteroResp", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%HeteroResp, & + active=output%carbon .or. output%HeteroResp, & + patchout=output%patch .or. patchout%HeteroResp, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%frs), & + scale=(1.0 / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Heterotrophic respiration") & + ] & + ), & + cable_output_variable_t( & + field_name="fgpp", & + netcdf_name="GPP", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%GPP, & + active=output%carbon .or. output%GPP, & + patchout=output%patch .or. patchout%GPP, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fgpp), & + scale=(1.0 / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Gross primary production") & + ] & + ), & + cable_output_variable_t( & + field_name="fnpp", & + netcdf_name="NPP", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NPP, & + active=output%carbon .or. output%NPP, & + patchout=output%patch .or. patchout%NPP, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fnpp), & + scale=(1.0 / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Net primary production") & + ] & + ), & + cable_output_variable_t( & + field_name="fra", & + netcdf_name="AutoResp", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%AutoResp, & + active=output%carbon .or. output%AutoResp, & + patchout=output%patch .or. patchout%AutoResp, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fra), & + scale=(1.0 / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Autotrophic respiration") & + ] & + ), & + cable_output_variable_t( & + field_name="wtd", & + netcdf_name="WatTable", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%WatTable, & + active=output%soil .or. output%WatTable, & + patchout=output%patch .or. patchout%WatTable, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%wtd), & + scale=1e-3, & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Water Table Depth") & + ] & + ), & + cable_output_variable_t( & + field_name="GWwb", & + netcdf_name="GWMoist", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%GWwb, & + active=output%soil .or. output%GWMoist, & + patchout=output%patch .or. patchout%GWMoist, & + restart=.true., & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%GWwb), & + metadata=[ & + attribute("units", "mm3/mm3"), & + attribute("long_name", "Aquifer moisture content") & + ] & + ), & + cable_output_variable_t( & + field_name="satfrac", & + netcdf_name="SatFrac", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%SatFrac, & + active=output%soil .or. output%SatFrac, & + patchout=output%patch .or. patchout%SatFrac, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%satfrac), & + metadata=[ & + attribute("units", "unitless"), & + attribute("long_name", "Saturated fraction of grid cell") & + ] & + ), & + cable_output_variable_t( & + field_name="Qrecharge", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Qrecharge, & + active=output%soil .or. output%Qrecharge, & + patchout=output%patch .or. patchout%Qrecharge, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(ssnow%Qrecharge), & + metadata=[ & + attribute("units", "mm/s"), & + attribute("long_name", "Recharge to or from Aquifer") & + ] & + ), & + cable_output_variable_t( & + field_name="tss", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(ssnow%tss), & + metadata=[ & + attribute("units", "K"), & + attribute("long_name", "Combined soil/snow temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="rtsoil", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(ssnow%rtsoil), & + metadata=[ & + attribute("units", "??"), & + attribute("long_name", "Turbulent resistance for soil") & + ] & + ), & + cable_output_variable_t( & + field_name="runoff", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(ssnow%runoff), & + metadata=[ & + attribute("units", "mm/timestep"), & + attribute("long_name", "Total runoff") & + ] & + ), & + cable_output_variable_t( & + field_name="ssdnn", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(ssnow%ssdnn), & + metadata=[ & + attribute("units", "kg/m^3"), & + attribute("long_name", "Average snow density") & + ] & + ), & + cable_output_variable_t( & + field_name="snage", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(ssnow%snage), & + metadata=[ & + attribute("units", "??"), & + attribute("long_name", "Snow age") & + ] & + ), & + cable_output_variable_t( & + field_name="osnowd", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(ssnow%osnowd), & + metadata=[ & + attribute("units", "mm"), & + attribute("long_name", "Previous time step snow depth in water equivalent") & + ] & + ), & + cable_output_variable_t( & + field_name="albsoilsn", & + data_shape=[DIM_PATCH, DIM_RAD], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%albsoiln, & + active=.false., & + aggregation_method="mean", & + restart=.true., & + aggregator=new_aggregator(ssnow%albsoilsn), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Combined soil/snow albedo") & + ] & + ), & + cable_output_variable_t( & + field_name="isflag", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_INT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(ssnow%isflag), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Snow layer scheme flag") & + ] & + ), & + cable_output_variable_t( & + field_name="ghflux", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(canopy%ghflux), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "????") & + ] & + ), & + cable_output_variable_t( & + field_name="sghflux", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(canopy%sghflux), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "????") & + ] & + ), & + cable_output_variable_t( & + field_name="dgdtg", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(canopy%dgdtg), & + metadata=[ & + attribute("units", "W/m^2/K"), & + attribute("long_name", "Derivative of ground heat flux wrt soil temperature") & + ] & + ), & + cable_output_variable_t( & + field_name="fev", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(canopy%fev), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Latent heat flux from vegetation") & + ] & + ), & + cable_output_variable_t( & + field_name="fes", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(canopy%fes), & + metadata=[ & + attribute("units", "W/m^2"), & + attribute("long_name", "Latent heat flux from soil") & + ] & + ), & + cable_output_variable_t( & + field_name="albedo", & + data_shape=[DIM_PATCH, DIM_RAD], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(rad%albedo), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Albedo for shortwave and NIR radiation") & + ] & + ), & + cable_output_variable_t( & + field_name="iveg", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_INT, & + range=ranges%iveg, & + active=output%params .or. output%iveg, & + patchout=output%patch .or. patchout%iveg, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + restart=.true., & + aggregator=new_aggregator(veg%iveg), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Vegetation type") & + ] & + ), & + cable_output_variable_t( & + field_name="patchfrac", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%patchfrac, & + active=(output%params .or. output%patchfrac) .and. (output%patch .or. patchout%patchfrac), & + patchout=output%patch .or. patchout%patchfrac, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + restart=.true., & + aggregator=new_aggregator(patch(:)%frac), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fractional cover of vegetation patches") & + ] & + ), & + cable_output_variable_t( & + field_name="isoil", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_INT, & + range=ranges%isoil, & + active=output%params .or. output%isoil, & + patchout=output%patch .or. patchout%isoil, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + restart=.true., & + aggregator=new_aggregator(soil%isoilm), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Soil type") & + ] & + ), & + cable_output_variable_t( & + field_name="bch", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%bch, & + active=output%params .or. output%bch, & + patchout=output%patch .or. patchout%bch, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%bch), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Parameter b, Campbell eqn 1985") & + ] & + ), & + cable_output_variable_t( & + field_name="clay", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%clay, & + active=output%params .or. output%clay, & + patchout=output%patch .or. patchout%clay, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%clay), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fraction of soil which is clay") & + ] & + ), & + cable_output_variable_t( & + field_name="sand", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%sand, & + active=output%params .or. output%sand, & + patchout=output%patch .or. patchout%sand, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%sand), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fraction of soil which is sand") & + ] & + ), & + cable_output_variable_t( & + field_name="silt", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%silt, & + active=output%params .or. output%silt, & + patchout=output%patch .or. patchout%silt, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%silt), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fraction of soil which is silt") & + ] & + ), & + cable_output_variable_t( & + field_name="ssat", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%ssat, & + active=output%params .or. output%ssat, & + patchout=output%patch .or. patchout%ssat, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%ssat), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fraction of soil volume which is water @ saturation") & + ] & + ), & + cable_output_variable_t( & + field_name="sfc", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%sfc, & + active=output%params .or. output%sfc, & + patchout=output%patch .or. patchout%sfc, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%sfc), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fraction of soil volume which is water @ field capacity") & + ] & + ), & + cable_output_variable_t( & + field_name="swilt", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%swilt, & + active=output%params .or. output%swilt, & + patchout=output%patch .or. patchout%swilt, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%swilt), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fraction of soil volume which is water @ wilting point") & + ] & + ), & + cable_output_variable_t( & + field_name="hyds", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%hyds, & + active=output%params .or. output%hyds, & + patchout=output%patch .or. patchout%hyds, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%hyds), & + metadata=[ & + attribute("units", "m/s"), & + attribute("long_name", "Hydraulic conductivity @ saturation") & + ] & + ), & + cable_output_variable_t( & + field_name="sucs", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%sucs, & + active=output%params .or. output%sucs, & + patchout=output%patch .or. patchout%sucs, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%sucs), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Suction @ saturation") & + ] & + ), & + cable_output_variable_t( & + field_name="css", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%css, & + active=output%params .or. output%css, & + patchout=output%patch .or. patchout%css, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%css), & + metadata=[ & + attribute("units", "J/kg/C"), & + attribute("long_name", "Heat capacity of soil minerals") & + ] & + ), & + cable_output_variable_t( & + field_name="rhosoil", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%rhosoil, & + active=output%params .or. output%rhosoil, & + patchout=output%patch .or. patchout%rhosoil, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%rhosoil), & + metadata=[ & + attribute("units", "kg/m^3"), & + attribute("long_name", "Density of soil minerals") & + ] & + ), & + cable_output_variable_t( & + field_name="rs20", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%rs20, & + active=output%params .or. output%rs20, & + patchout=output%patch .or. patchout%rs20, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%rs20), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Soil respiration coefficient at 20C") & + ] & + ), & + cable_output_variable_t( & + field_name="albsoil", & + data_shape=[DIM_PATCH, DIM_RAD], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%albsoil, & + active=output%params .or. output%albsoil, & + patchout=output%patch .or. patchout%albsoil, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + restart=.true., & + aggregator=new_aggregator(soil%albsoil), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Snow free shortwave soil reflectance fraction") & + ] & + ), & + cable_output_variable_t( & + field_name="hc", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%hc, & + active=output%params .or. output%hc, & + patchout=output%patch .or. patchout%hc, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%hc), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Height of canopy") & + ] & + ), & + cable_output_variable_t( & + field_name="canst1", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%canst1, & + active=output%params .or. output%canst1, & + patchout=output%patch .or. patchout%canst1, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%canst1), & + metadata=[ & + attribute("units", "mm/LAI"), & + attribute("long_name", "Max water intercepted by canopy") & + ] & + ), & + cable_output_variable_t( & + field_name="dleaf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%dleaf, & + active=output%params .or. output%dleaf, & + patchout=output%patch .or. patchout%dleaf, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%dleaf), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Characteristic length of leaf") & + ] & + ), & + cable_output_variable_t( & + field_name="frac4", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%frac4, & + active=output%params .or. output%frac4, & + patchout=output%patch .or. patchout%frac4, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%frac4), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fraction of plants which are C4") & + ] & + ), & + cable_output_variable_t( & + field_name="ejmax", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%ejmax, & + active=output%params .or. output%ejmax, & + patchout=output%patch .or. patchout%ejmax, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%ejmax), & + metadata=[ & + attribute("units", "mol/m^2/s"), & + attribute("long_name", "Max potential electron transport rate top leaf") & + ] & + ), & + cable_output_variable_t( & + field_name="vcmax", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%vcmax, & + active=output%params .or. output%vcmax, & + patchout=output%patch .or. patchout%vcmax, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%vcmax), & + metadata=[ & + attribute("units", "mol/m^2/s"), & + attribute("long_name", "Maximum RuBP carboxylation rate top leaf") & + ] & + ), & + cable_output_variable_t( & + field_name="rp20", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%rp20, & + active=output%params .or. output%rp20, & + patchout=output%patch .or. patchout%rp20, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%rp20), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Plant respiration coefficient at 20C") & + ] & + ), & + cable_output_variable_t( & + field_name="g0", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%g0, & + active=output%params .or. output%g0, & + patchout=output%patch .or. patchout%g0, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%g0), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "g0 term in Medlyn Stom Cond. Param") & + ] & + ), & + cable_output_variable_t( & + field_name="g1", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%g1, & + active=output%params .or. output%g1, & + patchout=output%patch .or. patchout%g1, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%g1), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "g1 term in Medlyn Stom Cond. Param") & + ] & + ), & + cable_output_variable_t( & + field_name="rpcoef", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%rpcoef, & + active=output%params .or. output%rpcoef, & + patchout=output%patch .or. patchout%rpcoef, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%rpcoef), & + metadata=[ & + attribute("units", "1/C"), & + attribute("long_name", "Temperature coef nonleaf plant respiration") & + ] & + ), & + cable_output_variable_t( & + field_name="shelrb", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%shelrb, & + active=output%params .or. output%shelrb, & + patchout=output%patch .or. patchout%shelrb, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%shelrb), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Sheltering factor") & + ] & + ), & + cable_output_variable_t( & + field_name="xfang", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%xfang, & + active=output%params .or. output%xfang, & + patchout=output%patch .or. patchout%xfang, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%xfang), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Leaf angle parameter") & + ] & + ), & + cable_output_variable_t( & + field_name="wai", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%wai, & + active=output%params .or. output%wai, & + patchout=output%patch .or. patchout%wai, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%wai), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Wood area index") & + ] & + ), & + cable_output_variable_t( & + field_name="vegcf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%vegcf, & + active=output%params .or. output%vegcf, & + patchout=output%patch .or. patchout%vegcf, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%vegcf), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "vegcf") & + ] & + ), & + cable_output_variable_t( & + field_name="extkn", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%extkn, & + active=output%params .or. output%extkn, & + patchout=output%patch .or. patchout%extkn, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%extkn), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Nitrogen extinction coef for vert. canopy profile") & + ] & + ), & + cable_output_variable_t( & + field_name="tminvj", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%tminvj, & + active=output%params .or. output%tminvj, & + patchout=output%patch .or. patchout%tminvj, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%tminvj), & + metadata=[ & + attribute("units", "C"), & + attribute("long_name", "Min temperature for the start of photosynthesis") & + ] & + ), & + cable_output_variable_t( & + field_name="tmaxvj", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%tmaxvj, & + active=output%params .or. output%tmaxvj, & + patchout=output%patch .or. patchout%tmaxvj, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%tmaxvj), & + metadata=[ & + attribute("units", "C"), & + attribute("long_name", "Max temperature for photosynthesis") & + ] & + ), & + cable_output_variable_t( & + field_name="vbeta", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%vbeta, & + active=output%params .or. output%vbeta, & + patchout=output%patch .or. patchout%vbeta, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%vbeta), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Stomatal sensitivity to soil water") & + ] & + ), & + cable_output_variable_t( & + field_name="xalbnir", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%xalbnir, & + active=output%params .or. output%xalbnir, & + patchout=output%patch .or. patchout%xalbnir, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%xalbnir), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Modifier for albedo in near ir band") & + ] & + ), & + cable_output_variable_t( & + field_name="meth", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%meth, & + active=output%params .or. output%meth, & + patchout=output%patch .or. patchout%meth, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%meth), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Canopy turbulence parameterisation choice") & + ] & + ), & + cable_output_variable_t( & + field_name="za_uv", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%za, & + active=output%params .or. output%za, & + patchout=output%patch .or. patchout%za, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(rough%za_uv), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Reference height (lowest atm. model layer) for momentum") & + ] & + ), & + cable_output_variable_t( & + field_name="za_tq", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%za, & + active=output%params .or. output%za, & + patchout=output%patch .or. patchout%za, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(rough%za_tq), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Reference height (lowest atm. model layer) for scalars") & + ] & + ), & + cable_output_variable_t( & + field_name="ratecp", & + data_shape=[DIM_PLANTCARBON], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%ratecp, & + active=output%params .or. output%ratecp, & + distributed=.false., & + parameter=.true., & + aggregator=new_aggregator(bgc%ratecp), & + metadata=[ & + attribute("units", "1/year"), & + attribute("long_name", "Plant carbon rate constant") & + ] & + ), & + cable_output_variable_t( & + field_name="ratecs", & + data_shape=[DIM_SOILCARBON], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%ratecs, & + active=output%params .or. output%ratecs, & + distributed=.false., & + parameter=.true., & + aggregator=new_aggregator(bgc%ratecs), & + metadata=[ & + attribute("units", "1/year"), & + attribute("long_name", "Soil carbon rate constant") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant", & + data_shape=[DIM_PATCH, DIM_PLANTCARBON], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(bgc%cplant), & + metadata=[ & + attribute("units", "gC/m^2"), & + attribute("long_name", "Plant carbon stores") & + ] & + ), & + cable_output_variable_t( & + field_name="csoil", & + data_shape=[DIM_PATCH, DIM_SOILCARBON], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + aggregator=new_aggregator(bgc%csoil), & + metadata=[ & + attribute("units", "gC/m^2"), & + attribute("long_name", "Soil carbon stores") & + ] & + ), & + cable_output_variable_t( & + field_name="zse", & + data_shape=[DIM_SOIL], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%zse, & + active=output%params .or. output%zse, & + distributed=.false., & + parameter=.true., & + restart=.true., & + aggregator=new_aggregator(soil%zse), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Depth of each soil layer") & + ] & + ), & + cable_output_variable_t( & + field_name="froot", & + data_shape=[DIM_PATCH, DIM_SOIL], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%froot, & + active=output%params .or. output%froot, & + patchout=output%patch .or. patchout%froot, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(veg%froot), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Fraction of roots in each soil layer") & + ] & + ), & + cable_output_variable_t( & + field_name="slope", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%slope, & + active=output%params .or. output%slope, & + patchout=output%patch .or. patchout%slope, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%slope), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Mean subgrid topographic slope") & + ] & + ), & + cable_output_variable_t( & + field_name="slope_std", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%slope_std, & + active=output%params .or. output%slope_std, & + patchout=output%patch .or. patchout%slope_std, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%slope_std), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Mean subgrid topographic slope_std") & + ] & + ), & + cable_output_variable_t( & + field_name="GWdz", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%GWdz, & + active=output%params .AND. cable_user%gw_model, & + patchout=output%patch .OR. patchout%GWdz, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(soil%GWdz), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Mean aquifer layer thickness") & + ] & + ), & + cable_output_variable_t( & + field_name="MaxHorzDrainRate", & + netcdf_name="Qhmax", & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%gw_default, & + active=output%params .AND. cable_user%gw_model, & + patchout=output%patch .OR. patchout%Qhmax, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(gw_params%MaxHorzDrainRate), & + metadata=[ & + attribute("units", "mm/s"), & + attribute("long_name", "Maximum subsurface drainage") & + ] & + ), & + cable_output_variable_t( & + field_name="EfoldHorzDrainRate", & + netcdf_name="QhmaxEfold", & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%gw_default, & + active=output%params .AND. cable_user%gw_model, & + patchout=output%patch .OR. patchout%QhmaxEfold, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(gw_params%EfoldHorzDrainRate), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Maximum subsurface drainage decay rate") & + ] & + ), & + cable_output_variable_t( & + field_name="MaxSatFraction", & + netcdf_name="SatFracmax", & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%gw_default, & + active=output%params .AND. cable_user%gw_model, & + patchout=output%patch .OR. patchout%SatFracmax, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(gw_params%MaxSatFraction), & + metadata=[ & + attribute("units", "-"), & + attribute("long_name", "Controls max saturated fraction") & + ] & + ), & + cable_output_variable_t( & + field_name="hkrz", & + netcdf_name="HKefold", & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%gw_default, & + active=output%params .AND. cable_user%gw_model, & + patchout=output%patch .OR. patchout%HKefold, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(gw_params%hkrz), & + metadata=[ & + attribute("units", "1/m"), & + attribute("long_name", "Rate HK decays with depth") & + ] & + ), & + cable_output_variable_t( & + field_name="zdepth", & + netcdf_name="HKdepth", & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%gw_default, & + active=output%params .AND. cable_user%gw_model, & + patchout=output%patch .OR. patchout%HKdepth, & + reduction_method="first_patch_in_grid_cell", & + aggregation_method="point", & + parameter=.true., & + aggregator=new_aggregator(gw_params%zdepth), & + metadata=[ & + attribute("units", "m"), & + attribute("long_name", "Depth at which HKsat(z) is HKsat(0)") & + ] & + ), & + cable_output_variable_t( & + field_name="nap", & + data_shape=[DIM_LAND_GLOBAL], & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(landpt_global(:)%nap), & + metadata=[ & + attribute("units", ""), & + attribute("long_name", "") & + ] & + ), & + cable_output_variable_t( & + field_name="mvtype", & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(mvtype), & + metadata=[ & + attribute("units", ""), & + attribute("long_name", "Number of vegetation types") & + ] & + ), & + cable_output_variable_t( & + field_name="mstype", & + var_type=CABLE_NETCDF_FLOAT, & + active=.false., & + restart=.true., & + distributed=.false., & + aggregation_method="point", & + aggregator=new_aggregator(mstype), & + metadata=[ & + attribute("units", ""), & + attribute("long_name", "Number of soil types") & + ] & + ) & + ] + + end function core_outputs - function core_outputs(canopy, soil) result(output_variables) - type(canopy_type), intent(inout) :: canopy - type(soil_parameter_type), intent(in) :: soil + function casa_cnp_outputs(casaflux, casapool, casamet) result(casa_output_variables) + type(casa_flux), intent(in) :: casaflux + type(casa_pool), intent(in) :: casapool + type(casa_met), intent(in) :: casamet + type(cable_output_variable_t), allocatable :: casa_output_variables(:) - type(cable_output_variable_t), allocatable :: output_variables(:) + if (.not. l_casacnp) then + allocate(casa_output_variables(0)) + return + end if - output_variables = [ & + casa_output_variables = [ & cable_output_variable_t( & - name="isoil", & + field_name="crmplant_froot", & + netcdf_name="RootResp", & data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_INT, & - range=ranges%isoil, & - active=output%isoil, & - patchout=output%patch .or. patchout%isoil, & - reduction_method="first_patch_in_grid_cell", & - aggregation_method="point", & - parameter=.true., & - restart=.true., & - aggregator=new_aggregator(soil%isoilm), & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%AutoResp, & + active=output%carbon .or. output%casa, & + patchout=output%patch .or. patchout%casa, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%crmplant(:, FROOT)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & metadata=[ & - attribute("units", "-"), & - attribute("long_name", "Soil type") & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Fine Root Autotrophic respiration") & ] & ), & cable_output_variable_t( & - name="swilt", & + field_name="crmplant_wood", & + netcdf_name="StemResp", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - range=ranges%swilt, & - active=output%swilt, & - patchout=output%patch .or. patchout%swilt, & + range=ranges%AutoResp, & + active=output%carbon .or. output%casa, & + patchout=output%patch .or. patchout%casa, & reduction_method="grid_cell_average", & - aggregation_method="point", & - parameter=.true., & - aggregator=new_aggregator(soil%swilt), & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%crmplant(:, WOOD)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & metadata=[ & - attribute("units", "1"), & - attribute("long_name", "") & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "StemWood Autotrophic respiration") & ] & ), & cable_output_variable_t( & - name="albsoil", & - data_shape=[DIM_PATCH, DIM_RAD], & + field_name="cnbp", & + netcdf_name="NBP", & + data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - range=ranges%albsoil, & - active=output%albsoil, & - patchout=output%patch .or. patchout%albsoil, & - reduction_method="first_patch_in_grid_cell", & - aggregation_method="point", & - parameter=.true., & - aggregator=new_aggregator(soil%albsoil), & + range=ranges%NEE, & + active=output%casa .or. output%NBP, & + patchout=output%patch .or. patchout%NBP, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%cnbp), & + scale=(1.0 / seconds_per_day / c_molar_mass), & metadata=[ & - attribute("units", "1"), & - attribute("long_name", "") & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Net Biosphere Production") & ] & ), & cable_output_variable_t( & - name="Qh", & + field_name="dCdt", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - range=ranges%Qh, & - active=output%Qh, & - patchout=output%patch .or. patchout%Qh, & + range=ranges%NEE, & + active=output%casa .or. output%dCdt, & + patchout=output%patch .or. patchout%dCdt, & reduction_method="grid_cell_average", & aggregation_method="mean", & - aggregator=new_aggregator(canopy%fh), & + aggregator=new_aggregator(casapool%dCdt), & + scale=(1.0 / seconds_per_day / c_molar_mass), & metadata=[ & - attribute("units", "W/m^2"), & - attribute("long_name", "Surface sensible heat flux") & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Carbon accumulation rate (uptake +ve)") & ] & ), & cable_output_variable_t( & - name="Tmx", & + field_name="csoiltot", & + netcdf_name="TotSoilCarb", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - active=output%Tex .and. output%averaging == "monthly", & - patchout=output%patch .or. patchout%Tex, & + range=ranges%TotSoilCarb, & + active=output%casa .or. output%TotSoilCarb, & + patchout=output%patch .or. patchout%TotSoilCarb, & reduction_method="grid_cell_average", & aggregation_method="mean", & - range=ranges%Tscrn, & - accumulation_frequency="daily", & - aggregator=new_aggregator(canopy%tscrn_max_daily%aggregated_data), & + aggregator=new_aggregator(casapool%csoiltot), & + scale=(1.0 / 1000.0), & metadata=[ & - attribute("units", "oC"), & - attribute("long_name", "averaged daily maximum screen-level T") & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Total Soil and Litter Carbon") & ] & ), & cable_output_variable_t( & - name="nap", & - data_shape=[DIM_LAND_GLOBAL], & + field_name="clittertot", & + netcdf_name="TotLittCarb", & + data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - range=[-huge(0.0), huge(0.0)], & - active=.false., & - restart=.true., & - distributed=.false., & - aggregation_method="point", & - aggregator=new_aggregator(landpt_global(:)%nap), & + range=ranges%TotLittCarb, & + active=output%casa .or. output%TotLittCarb, & + patchout=output%patch .or. patchout%TotLittCarb, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%clittertot), & + scale=(1.0 / 1000.0), & metadata=[ & - attribute("units", ""), & - attribute("long_name", "") & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Total Litter Carbon") & ] & ), & cable_output_variable_t( & - name="patchfrac", & + field_name="csoil_mic", & + netcdf_name="SoilCarbFast", & data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - range=[0.0, 1.0], & - active=.false., & - restart=.true., & - distributed=.true., & - aggregation_method="point", & - aggregator=new_aggregator(patch(:)%frac), & + range=ranges%TotLittCarb, & + active=output%casa .or. output%SoilCarbFast, & + patchout=output%patch .or. patchout%SoilCarbFast, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%csoil(:, MIC)), & + scale=(1.0 / 1000.0), & metadata=[ & - attribute("units", ""), & - attribute("long_name", "Fraction of vegetated grid cell area occupied by a vegetation/soil patch") & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Soil Carbon: Fast Turnover") & ] & ), & cable_output_variable_t( & - name="mvtype", & + field_name="csoil_slow", & + netcdf_name="SoilCarbSlow", & + data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - range=[-huge(0.0), huge(0.0)], & - active=.false., & - restart=.true., & - distributed=.false., & - aggregation_method="point", & - aggregator=new_aggregator(mvtype), & + range=ranges%TotSoilCarb, & + active=output%casa .or. output%SoilCarbSlow, & + patchout=output%patch .or. patchout%SoilCarbSlow, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%csoil(:, SLOW)), & + scale=(1.0 / 1000.0), & metadata=[ & - attribute("units", ""), & - attribute("long_name", "Number of vegetation types") & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Soil Carbon: Slow Turnover") & ] & ), & cable_output_variable_t( & - name="mstype", & + field_name="csoil_pass", & + netcdf_name="SoilCarbPassive", & + data_shape=[DIM_PATCH], & var_type=CABLE_NETCDF_FLOAT, & - range=[-huge(0.0), huge(0.0)], & - active=.false., & - restart=.true., & - distributed=.false., & + range=ranges%TotSoilCarb, & + active=output%casa .or. output%SoilCarbPassive, & + patchout=output%patch .or. patchout%SoilCarbPassive, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%csoil(:, PASS)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Soil Carbon: Passive") & + ] & + ), & + cable_output_variable_t( & + field_name="clitter_metb", & + netcdf_name="LittCarbMetabolic", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%LittCarbMetabolic, & + patchout=output%patch .or. patchout%LittCarbMetabolic, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%clitter(:, METB)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Litter Carbon: metabolic") & + ] & + ), & + cable_output_variable_t( & + field_name="clitter_str", & + netcdf_name="LittCarbStructural", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%LittCarbStructural, & + patchout=output%patch .or. patchout%LittCarbStructural, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%clitter(:, STR)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Litter Carbon: structural") & + ] & + ), & + cable_output_variable_t( & + field_name="clitter_cwd", & + netcdf_name="LittCarbCWD", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%LittCarbCWD, & + patchout=output%patch .or. patchout%LittCarbCWD, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%clitter(:, CWD)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Litter Carbon: CWD") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant_leaf", & + netcdf_name="PlantCarbLeaf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%PlantCarbLeaf, & + patchout=output%patch .or. patchout%PlantCarbLeaf, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%cplant(:, LEAF)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Plant Carbon: leaf") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant_wood", & + netcdf_name="PlantCarbWood", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%PlantCarbWood, & + patchout=output%patch .or. patchout%PlantCarbWood, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%cplant(:, WOOD)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Plant Carbon: wood (above- and below-ground)") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant_froot", & + netcdf_name="PlantCarbFineRoot", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%PlantCarbFineRoot, & + patchout=output%patch .or. patchout%PlantCarbFineRoot, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%cplant(:, FROOT)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Plant Carbon: Fine roots") & + ] & + ), & + cable_output_variable_t( & + field_name="cplanttot", & + netcdf_name="TotLivBiomass", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLivBiomass, & + active=output%casa .or. output%TotLivBiomass, & + patchout=output%patch .or. patchout%TotLivBiomass, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%cplanttot), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Total Biomass") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant_turnover_tot", & + netcdf_name="PlantTurnover", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnover, & + patchout=output%patch .or. patchout%PlantTurnover, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%cplant_turnover_tot), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Total Biomass Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_leaf", & + netcdf_name="PlantTurnoverLeaf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverLeaf, & + patchout=output%patch .or. patchout%PlantTurnoverLeaf, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover(:, LEAF)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Leaf Biomass Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_wood", & + netcdf_name="PlantTurnoverWood", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverWood, & + patchout=output%patch .or. patchout%PlantTurnoverWood, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover(:, WOOD)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Woody Biomass Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_froot", & + netcdf_name="PlantTurnoverFineRoot", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverFineRoot, & + patchout=output%patch .or. patchout%PlantTurnoverFineRoot, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover(:, FROOT)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "FineRoot Biomass Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_disturbance", & + netcdf_name="PlantTurnoverWoodDist", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverWoodDist, & + patchout=output%patch .or. patchout%PlantTurnoverWoodDist, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover_disturbance), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Woody Biomass Turnover (disturbance)") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_crowding", & + netcdf_name="PlantTurnoverWoodCrowding", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverWoodCrowding, & + patchout=output%patch .or. patchout%PlantTurnoverWoodCrowding, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover_crowding), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Woody Biomass Turnover (crowding)") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_resource_limitation", & + netcdf_name="PlantTurnoverWoodResourceLim", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverWoodResourceLim, & + patchout=output%patch .or. patchout%PlantTurnoverWoodResourceLim, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover_resource_limitation), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Woody Biomass Turnover (Resource Limitation)") & + ] & + ), & + cable_output_variable_t( & + field_name="areacell", & + netcdf_name="Area", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Area, & + active=output%casa .or. output%Area, & + patchout=output%patch .or. patchout%Area, & + reduction_method="grid_cell_average", & aggregation_method="point", & - aggregator=new_aggregator(mstype), & + aggregator=new_aggregator(casamet%areacell), & + scale=1e-6, & metadata=[ & - attribute("units", ""), & - attribute("long_name", "Number of soil types") & + attribute("units", "km2"), & + attribute("long_name", "Patch Area") & + ] & + ), & + cable_output_variable_t( & + field_name="FluxCtoLUC", & + netcdf_name="LandUseFlux", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=cable_user%POPLUC .or. output%LandUseFlux, & + patchout=output%patch .or. patchout%LandUseFlux, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%FluxCtoLUC), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Sum of wood harvest and clearing fluxes") & ] & ) & ] - end function core_outputs + end function casa_cnp_outputs - function coordinate_variables(grid_type) result(output_variables) + function coordinate_variables(grid_type) result(coord_variables) character(len=*), intent(in) :: grid_type - type(cable_output_variable_t), allocatable :: output_variables(:) + type(cable_output_variable_t), allocatable :: coord_variables(:) select case (grid_type) case ("restart") - output_variables = [ & + coord_variables = [ & cable_output_variable_t( & - name="latitude", & + field_name="latitude", & data_shape=[DIM_LAND_GLOBAL], & - var_type=CABLE_NETCDF_FLOAT, & - range=[-90.0, 90.0], & - active=.false., & - restart=.true., & distributed=.false., & - aggregation_method="point", & aggregator=new_aggregator(latitude), & - metadata=[ & - attribute("units", "degrees_north"), & - attribute("long_name", "") & - ] & + metadata=[attribute("units", "degrees_north")] & ), & cable_output_variable_t( & - name="longitude", & + field_name="longitude", & data_shape=[DIM_LAND_GLOBAL], & - var_type=CABLE_NETCDF_FLOAT, & - range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? - active=.false., & - restart=.true., & distributed=.false., & - aggregation_method="point", & aggregator=new_aggregator(longitude), & - metadata=[ & - attribute("units", "degrees_east"), & - attribute("long_name", "") & - ] & + metadata=[attribute("units", "degrees_east")] & ) & ] case ("mask") - output_variables = [ & - ! Define latitude and longitude variable (ALMA): + coord_variables = [ & cable_output_variable_t( & - name="latitude", & + field_name="lat_all", & + netcdf_name="latitude", & data_shape=[DIM_X, DIM_Y], & var_type=CABLE_NETCDF_FLOAT, & - range=[-90.0, 90.0], & - active=.true., & parameter=.true., & distributed=.false., & - aggregation_method="point", & aggregator=new_aggregator(lat_all), & - metadata=[ & - attribute("units", "degrees_north"), & - attribute("long_name", "") & - ] & + metadata=[attribute("units", "degrees_north")] & ), & cable_output_variable_t( & - name="longitude", & + field_name="lon_all", & + netcdf_name="longitude", & data_shape=[DIM_X, DIM_Y], & - var_type=CABLE_NETCDF_FLOAT, & - range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? - active=.true., & parameter=.true., & distributed=.false., & - aggregation_method="point", & aggregator=new_aggregator(lon_all), & - metadata=[ & - attribute("units", "degrees_east"), & - attribute("long_name", "") & - ] & + metadata=[attribute("units", "degrees_east")] & ), & - ! Write "cordinate variables" to enable reading by GrADS: cable_output_variable_t( & - name="x", & + field_name="x", & data_shape=[DIM_X], & - var_type=CABLE_NETCDF_FLOAT, & - range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? - active=.true., & parameter=.true., & distributed=.false., & - aggregation_method="point", & aggregator=new_aggregator(lon_all(:, 1)), & metadata=[ & attribute("units", "degrees_east"), & - attribute("long_name", ""), & attribute("comment", "x coordinate variable for GrADS compatibility") & ] & ), & cable_output_variable_t( & - name="y", & + field_name="y", & data_shape=[DIM_Y], & - var_type=CABLE_NETCDF_FLOAT, & - range=[-90.0, 90.0], & ! TODO(Sean): this depends on the met forcing input? - active=.true., & parameter=.true., & distributed=.false., & - aggregation_method="point", & aggregator=new_aggregator(lat_all(1, :)), & metadata=[ & attribute("units", "degrees_north"), & - attribute("long_name", ""), & attribute("comment", "y coordinate variable for GrADS compatibility") & ] & ) & ] case ("land") - output_variables = [ & + coord_variables = [ & cable_output_variable_t( & - name="local_lat", & + field_name="local_lat", & data_shape=[DIM_LAND_GLOBAL], & - var_type=CABLE_NETCDF_FLOAT, & - range=[-90.0, 90.0], & - active=.true., & parameter=.true., & distributed=.false., & - aggregation_method="point", & aggregator=new_aggregator(latitude), & - metadata=[ & - attribute("units", "degrees_north"), & - attribute("long_name", "") & - ] & + metadata=[attribute("units", "degrees_north")] & ), & cable_output_variable_t( & - name="local_lon", & + field_name="local_lon", & data_shape=[DIM_LAND_GLOBAL], & - var_type=CABLE_NETCDF_FLOAT, & - range=[-huge(0.0), huge(0.0)], & ! TODO(Sean): this depends on the met forcing input? - active=.true., & parameter=.true., & distributed=.false., & - aggregation_method="point", & aggregator=new_aggregator(longitude), & - metadata=[ & - attribute("units", "degrees_east"), & - attribute("long_name", "") & - ] & + metadata=[attribute("units", "degrees_east")] & ) & ] case default call cable_abort("Unexpected grid type '" // grid_type // "'", __FILE__, __LINE__) end select - end function + end function coordinate_variables end module diff --git a/src/util/io/output/cable_output_types.F90 b/src/util/io/output/cable_output_types.F90 index e24a0cb09..d6297fb52 100644 --- a/src/util/io/output/cable_output_types.F90 +++ b/src/util/io/output/cable_output_types.F90 @@ -11,6 +11,8 @@ module cable_output_types_mod implicit none private + integer, parameter, public :: CABLE_OUTPUT_VAR_TYPE_UNDEFINED = -1 + type, extends(cable_enum_t), public :: cable_output_dim_t end type @@ -20,17 +22,20 @@ module cable_output_types_mod end type type, public :: cable_output_variable_t - character(len=64) :: name - character(len=64) :: accumulation_frequency = "all" - character(len=64) :: reduction_method = "none" - character(len=64) :: aggregation_method - logical :: active + character(len=64) :: field_name + character(len=64) :: netcdf_name = "" + character(len=64) :: accumulation_frequency = "all" + character(len=64) :: reduction_method = "none" + character(len=64) :: aggregation_method = "point" + logical :: active = .true. logical :: parameter = .false. logical :: distributed = .true. logical :: restart = .false. logical :: patchout = .false. - integer :: var_type - real, dimension(2) :: range + integer :: var_type = CABLE_OUTPUT_VAR_TYPE_UNDEFINED + real, dimension(2) :: range = [-huge(0.0), huge(0.0)] + real, allocatable :: scale + real, allocatable :: offset type(cable_output_dim_t), allocatable :: data_shape(:) class(aggregator_t), allocatable :: aggregator type(cable_output_attribute_t), allocatable :: metadata(:) @@ -43,6 +48,7 @@ module cable_output_types_mod character(len=64) :: grid_type character(len=256) :: file_name class(cable_netcdf_file_t), allocatable :: output_file + type(cable_output_variable_t), allocatable :: coordinate_variables(:) type(cable_output_variable_t), allocatable :: output_variables(:) type(cable_output_attribute_t), allocatable :: metadata(:) end type diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 22e86f2d8..19eb7dfde 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -43,12 +43,15 @@ module cable_output_utils_mod use cable_output_types_mod, only: FILL_VALUE_INT32 use cable_output_types_mod, only: FILL_VALUE_REAL32 use cable_output_types_mod, only: FILL_VALUE_REAL64 + use cable_output_types_mod, only: CABLE_OUTPUT_VAR_TYPE_UNDEFINED implicit none private + public :: check_duplicate_variable_names public :: check_sampling_frequency public :: dim_size + public :: var_name public :: infer_dim_names public :: define_variables public :: set_global_attributes @@ -96,6 +99,53 @@ function dim_size(dims) end function + integer function netcdf_var_type(output_variable) + type(cable_output_variable_t), intent(in) :: output_variable + + if (output_variable%var_type /= CABLE_OUTPUT_VAR_TYPE_UNDEFINED) then + netcdf_var_type = output_variable%var_type + return + end if + + select case (output_variable%aggregator%type()) + case ("int32") + netcdf_var_type = CABLE_NETCDF_INT + case ("real32") + netcdf_var_type = CABLE_NETCDF_FLOAT + case ("real64") + netcdf_var_type = CABLE_NETCDF_DOUBLE + case default + call cable_abort("Unable to infer variable type for variable " // trim(output_variable%field_name), __FILE__, __LINE__) + end select + + end function + + elemental function var_name(output_variable) + type(cable_output_variable_t), intent(in) :: output_variable + character(len=128) :: var_name + + if (len_trim(output_variable%netcdf_name) > 0) then + var_name = output_variable%netcdf_name + else + var_name = output_variable%field_name + end if + + end function var_name + + subroutine check_duplicate_variable_names(output_profile) + type(cable_output_profile_t), intent(in) :: output_profile + integer :: i + + do i = 1, size(output_profile%output_variables) + associate(output_var => output_profile%output_variables(i)) + if (count(var_name(output_var) == var_name(output_profile%output_variables(:))) > 1) then + call cable_abort("Duplicate variable name found: " // trim(var_name(output_var)), __FILE__, __LINE__) + end if + end associate + end do + + end subroutine + subroutine check_sampling_frequency(output_profile) type(cable_output_profile_t), intent(in) :: output_profile @@ -108,7 +158,7 @@ subroutine check_sampling_frequency(output_profile) err_message = ( & "Invalid combination of sampling frequency '" // output_profile%sampling_frequency // & "' with accumulation frequency '" // output_var%accumulation_frequency // "' for variable '" // & - output_var%name // "' in file '" // output_profile%file_name // "'" & + output_var%field_name // "' in file '" // output_profile%file_name // "'" & ) select case (output_profile%sampling_frequency) case ("all") @@ -133,16 +183,17 @@ subroutine check_sampling_frequency(output_profile) end if case default call cable_abort("Invalid sampling frequency '" // output_profile%sampling_frequency // & - "' for variable '" // output_var%name // "' in file '" // output_profile%file_name // "'", __FILE__, __LINE__) + "' for variable '" // output_var%field_name // "' in file '" // output_profile%file_name // "'", __FILE__, __LINE__) end select end associate end do end subroutine check_sampling_frequency - function infer_dim_names(output_profile, output_variable) result(dim_names) + function infer_dim_names(output_profile, output_variable, time_axis) result(dim_names) type(cable_output_profile_t), intent(in) :: output_profile type(cable_output_variable_t), intent(in) :: output_variable + logical, intent(in), optional :: time_axis character(MAX_LEN_DIM), allocatable :: dim_names(:) integer :: j @@ -169,7 +220,7 @@ function infer_dim_names(output_profile, output_variable) result(dim_names) end if case default call cable_abort("Unexpected grid type '" // output_profile%grid_type // & - "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) + "' for variable '" // output_variable%field_name // "'", __FILE__, __LINE__) end select case (CABLE_OUTPUT_DIM_LAND%value) select case (output_profile%grid_type) @@ -181,7 +232,7 @@ function infer_dim_names(output_profile, output_variable) result(dim_names) dim_names = [dim_names, "x", "y"] case default call cable_abort("Unexpected grid type '" // output_profile%grid_type // & - "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) + "' for variable '" // output_variable%field_name // "'", __FILE__, __LINE__) end select case (CABLE_OUTPUT_DIM_LAND_GLOBAL%value) if (output_profile%grid_type == "restart") then @@ -212,12 +263,14 @@ function infer_dim_names(output_profile, output_variable) result(dim_names) case (CABLE_OUTPUT_DIM_Y%value) dim_names = [dim_names, "y"] case default - call cable_abort("Unexpected data shape for variable " // output_variable%name, __FILE__, __LINE__) + call cable_abort("Unexpected data shape for variable " // output_variable%field_name, __FILE__, __LINE__) end select end do end if - if (output_profile%grid_type /= "restart" .and. .not. output_variable%parameter) dim_names = [dim_names, "time"] + if (present(time_axis)) then; if (time_axis) then + dim_names = [dim_names, "time"] + end if; end if end function @@ -240,7 +293,7 @@ function infer_cell_methods(output_variable) result(cell_methods) cell_methods_time = "time: maximum" case default call cable_abort("Unexpected aggregation method '" // output_variable%aggregation_method // & - "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) + "' for variable '" // output_variable%field_name // "'", __FILE__, __LINE__) end select else cell_methods_time = "" @@ -261,24 +314,41 @@ function infer_cell_methods(output_variable) result(cell_methods) cell_methods_area = "area: mean where land" case default call cable_abort("Unexpected reduction method '" // output_variable%reduction_method // & - "' for variable '" // output_variable%name // "'", __FILE__, __LINE__) + "' for variable '" // output_variable%field_name // "'", __FILE__, __LINE__) end select cell_methods = adjustl(trim(cell_methods_time) // " " // trim(cell_methods_area)) end function - subroutine define_variables(output_profile) + subroutine define_variables(output_profile, restart) type(cable_output_profile_t), intent(inout) :: output_profile + logical, intent(in), optional :: restart + logical :: restart_local integer :: i, j character(MAX_LEN_DIM), allocatable :: required_dimensions(:), dim_names(:) + character(len=128) :: variable_name - do i = 1, size(output_profile%output_variables) - associate(output_var => output_profile%output_variables(i)) + type(cable_output_variable_t), allocatable :: all_output_variables(:) + + restart_local = .false. + if (present(restart)) restart_local = restart + + all_output_variables = [ & + output_profile%coordinate_variables, & + output_profile%output_variables & + ] + + do i = 1, size(all_output_variables) + associate(output_var => all_output_variables(i)) if (.not. allocated(output_var%data_shape)) cycle - dim_names = infer_dim_names(output_profile, output_var) + dim_names = infer_dim_names( & + output_profile, & + output_var, & + time_axis=(.not. (restart_local .or. output_var%parameter)) & + ) if (.not. allocated(required_dimensions)) then required_dimensions = dim_names else @@ -310,6 +380,8 @@ subroutine define_variables(output_profile) call output_profile%output_file%def_dims(["patch"], [max_vegpatches]) case ("soil") call output_profile%output_file%def_dims(["soil"], [ms]) + case ("snow") + call output_profile%output_file%def_dims(["snow"], [msn]) case ("rad") call output_profile%output_file%def_dims(["rad"], [nrb]) case ("soil_carbon_pools") @@ -344,30 +416,53 @@ subroutine define_variables(output_profile) call output_profile%output_file%put_att("time", "bounds", "time_bnds") end if + do i = 1, size(output_profile%coordinate_variables) + associate(coord_var => output_profile%coordinate_variables(i)) + variable_name = var_name(coord_var) + call output_profile%output_file%def_var( & + var_name=variable_name, & + dim_names=infer_dim_names(output_profile, coord_var), & + type=netcdf_var_type(coord_var) & + ) + if (allocated(coord_var%metadata)) then + do j = 1, size(coord_var%metadata) + call output_profile%output_file%put_att(variable_name, coord_var%metadata(j)%name, coord_var%metadata(j)%value) + end do + end if + end associate + end do + do i = 1, size(output_profile%output_variables) associate(output_var => output_profile%output_variables(i)) + variable_name = var_name(output_var) + if (restart_local) variable_name = output_var%field_name + dim_names = infer_dim_names( & + output_profile, & + output_var, & + time_axis=(.not. (restart_local .or. output_var%parameter)) & + ) call output_profile%output_file%def_var( & - var_name=output_var%name, & - dim_names=infer_dim_names(output_profile, output_var), & - type=output_var%var_type & + var_name=variable_name, & + dim_names=dim_names, & + type=netcdf_var_type(output_var) & ) if (allocated(output_var%metadata)) then do j = 1, size(output_var%metadata) - call output_profile%output_file%put_att(output_var%name, output_var%metadata(j)%name, output_var%metadata(j)%value) + call output_profile%output_file%put_att(variable_name, output_var%metadata(j)%name, output_var%metadata(j)%value) end do end if - select case (output_var%var_type) + select case (netcdf_var_type(output_var)) case (CABLE_NETCDF_INT) - call output_profile%output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_INT32) - call output_profile%output_file%put_att(output_var%name, "missing_value", FILL_VALUE_INT32) + call output_profile%output_file%put_att(variable_name, "_FillValue", FILL_VALUE_INT32) + call output_profile%output_file%put_att(variable_name, "missing_value", FILL_VALUE_INT32) case (CABLE_NETCDF_FLOAT) - call output_profile%output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_REAL32) - call output_profile%output_file%put_att(output_var%name, "missing_value", FILL_VALUE_REAL32) + call output_profile%output_file%put_att(variable_name, "_FillValue", FILL_VALUE_REAL32) + call output_profile%output_file%put_att(variable_name, "missing_value", FILL_VALUE_REAL32) case (CABLE_NETCDF_DOUBLE) - call output_profile%output_file%put_att(output_var%name, "_FillValue", FILL_VALUE_REAL64) - call output_profile%output_file%put_att(output_var%name, "missing_value", FILL_VALUE_REAL64) + call output_profile%output_file%put_att(variable_name, "_FillValue", FILL_VALUE_REAL64) + call output_profile%output_file%put_att(variable_name, "missing_value", FILL_VALUE_REAL64) end select - call output_profile%output_file%put_att(output_var%name, "cell_methods", infer_cell_methods(output_var)) + call output_profile%output_file%put_att(variable_name, "cell_methods", infer_cell_methods(output_var)) end associate end do From 78a6da81c175e19a9ea643df4dae97b9478ecc41 Mon Sep 17 00:00:00 2001 From: Sean Bryan Date: Wed, 25 Feb 2026 17:16:43 +1100 Subject: [PATCH 31/31] Split output variable declarations into multiple modules --- CMakeLists.txt | 3 +- src/offline/cable_diagnostics_casa.F90 | 485 ++++++++++++++ .../cable_diagnostics_core.F90} | 632 +----------------- src/offline/cable_mpimaster.F90 | 6 +- src/offline/cable_serial.F90 | 6 +- src/util/io/output/cable_output.F90 | 5 +- src/util/io/output/cable_output_core.F90 | 3 +- src/util/io/output/cable_output_utils.F90 | 98 +++ 8 files changed, 631 insertions(+), 607 deletions(-) create mode 100644 src/offline/cable_diagnostics_casa.F90 rename src/{util/io/output/cable_output_definitions.F90 => offline/cable_diagnostics_core.F90} (77%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2a737671..93ebd1337 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,6 +277,8 @@ else() src/offline/cable_abort.F90 src/offline/cable_checks.F90 src/offline/cable_cru_TRENDY.F90 + src/offline/cable_diagnostics_casa.F90 + src/offline/cable_diagnostics_core.F90 src/offline/cable_driver_common.F90 src/offline/cable_initialise.F90 src/offline/cable_input.F90 @@ -312,7 +314,6 @@ else() src/util/netcdf/nf90/cable_netcdf_nf90.F90 src/util/io/output/cable_output_core.F90 src/util/io/output/cable_output_decomp.F90 - src/util/io/output/cable_output_definitions.F90 src/util/io/output/cable_output_reduction_buffers.F90 src/util/io/output/cable_output_types.F90 src/util/io/output/cable_output_utils.F90 diff --git a/src/offline/cable_diagnostics_casa.F90 b/src/offline/cable_diagnostics_casa.F90 new file mode 100644 index 000000000..72937d685 --- /dev/null +++ b/src/offline/cable_diagnostics_casa.F90 @@ -0,0 +1,485 @@ +module cable_diagnostics_casa_mod + use casavariable, only: casa_flux + use casavariable, only: casa_pool + use casavariable, only: casa_met + + use casaparm, only: LEAF + use casaparm, only: WOOD + use casaparm, only: FROOT + + use casaparm, only: MIC + use casaparm, only: SLOW + use casaparm, only: PASS + + use casaparm, only: METB + use casaparm, only: STR + use casaparm, only: CWD + + use cable_timing_utils_mod, only: seconds_per_day + + use cable_phys_constants_mod, only: c_molar_mass + + use cable_common_module, only: l_casacnp + + use cable_common_module, only: cable_user + + use cable_io_vars_module, only: output, patchout + + use cable_output_prototype_v2_mod, only: cable_output_variable_t + use cable_output_prototype_v2_mod, only: attribute => cable_output_attribute_t + use cable_output_prototype_v2_mod, only: DIM_PATCH => CABLE_OUTPUT_DIM_PATCH + + use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT + + use aggregator_mod, only: new_aggregator + + use cable_checks_module, only: ranges + + implicit none + private + + public :: cable_diagnostics_casa + +contains + + function cable_diagnostics_casa(casaflux, casapool, casamet) result(casa_output_variables) + type(casa_flux), intent(in) :: casaflux + type(casa_pool), intent(in) :: casapool + type(casa_met), intent(in) :: casamet + type(cable_output_variable_t), allocatable :: casa_output_variables(:) + + if (.not. l_casacnp) then + allocate(casa_output_variables(0)) + return + end if + + casa_output_variables = [ & + cable_output_variable_t( & + field_name="crmplant_froot", & + netcdf_name="RootResp", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%AutoResp, & + active=output%carbon .or. output%casa, & + patchout=output%patch .or. patchout%casa, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%crmplant(:, FROOT)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Fine Root Autotrophic respiration") & + ] & + ), & + cable_output_variable_t( & + field_name="crmplant_wood", & + netcdf_name="StemResp", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%AutoResp, & + active=output%carbon .or. output%casa, & + patchout=output%patch .or. patchout%casa, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%crmplant(:, WOOD)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "StemWood Autotrophic respiration") & + ] & + ), & + cable_output_variable_t( & + field_name="cnbp", & + netcdf_name="NBP", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%NBP, & + patchout=output%patch .or. patchout%NBP, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%cnbp), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Net Biosphere Production") & + ] & + ), & + cable_output_variable_t( & + field_name="dCdt", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%dCdt, & + patchout=output%patch .or. patchout%dCdt, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%dCdt), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Carbon accumulation rate (uptake +ve)") & + ] & + ), & + cable_output_variable_t( & + field_name="csoiltot", & + netcdf_name="TotSoilCarb", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotSoilCarb, & + active=output%casa .or. output%TotSoilCarb, & + patchout=output%patch .or. patchout%TotSoilCarb, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%csoiltot), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Total Soil and Litter Carbon") & + ] & + ), & + cable_output_variable_t( & + field_name="clittertot", & + netcdf_name="TotLittCarb", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%TotLittCarb, & + patchout=output%patch .or. patchout%TotLittCarb, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%clittertot), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Total Litter Carbon") & + ] & + ), & + cable_output_variable_t( & + field_name="csoil_mic", & + netcdf_name="SoilCarbFast", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%SoilCarbFast, & + patchout=output%patch .or. patchout%SoilCarbFast, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%csoil(:, MIC)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Soil Carbon: Fast Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="csoil_slow", & + netcdf_name="SoilCarbSlow", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotSoilCarb, & + active=output%casa .or. output%SoilCarbSlow, & + patchout=output%patch .or. patchout%SoilCarbSlow, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%csoil(:, SLOW)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Soil Carbon: Slow Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="csoil_pass", & + netcdf_name="SoilCarbPassive", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotSoilCarb, & + active=output%casa .or. output%SoilCarbPassive, & + patchout=output%patch .or. patchout%SoilCarbPassive, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%csoil(:, PASS)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Soil Carbon: Passive") & + ] & + ), & + cable_output_variable_t( & + field_name="clitter_metb", & + netcdf_name="LittCarbMetabolic", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%LittCarbMetabolic, & + patchout=output%patch .or. patchout%LittCarbMetabolic, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%clitter(:, METB)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Litter Carbon: metabolic") & + ] & + ), & + cable_output_variable_t( & + field_name="clitter_str", & + netcdf_name="LittCarbStructural", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%LittCarbStructural, & + patchout=output%patch .or. patchout%LittCarbStructural, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%clitter(:, STR)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Litter Carbon: structural") & + ] & + ), & + cable_output_variable_t( & + field_name="clitter_cwd", & + netcdf_name="LittCarbCWD", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%LittCarbCWD, & + patchout=output%patch .or. patchout%LittCarbCWD, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%clitter(:, CWD)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Litter Carbon: CWD") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant_leaf", & + netcdf_name="PlantCarbLeaf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%PlantCarbLeaf, & + patchout=output%patch .or. patchout%PlantCarbLeaf, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%cplant(:, LEAF)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Plant Carbon: leaf") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant_wood", & + netcdf_name="PlantCarbWood", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%PlantCarbWood, & + patchout=output%patch .or. patchout%PlantCarbWood, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%cplant(:, WOOD)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Plant Carbon: wood (above- and below-ground)") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant_froot", & + netcdf_name="PlantCarbFineRoot", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLittCarb, & + active=output%casa .or. output%PlantCarbFineRoot, & + patchout=output%patch .or. patchout%PlantCarbFineRoot, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%cplant(:, FROOT)), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Plant Carbon: Fine roots") & + ] & + ), & + cable_output_variable_t( & + field_name="cplanttot", & + netcdf_name="TotLivBiomass", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%TotLivBiomass, & + active=output%casa .or. output%TotLivBiomass, & + patchout=output%patch .or. patchout%TotLivBiomass, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casapool%cplanttot), & + scale=(1.0 / 1000.0), & + metadata=[ & + attribute("units", "kg C/m^2"), & + attribute("long_name", "Total Biomass") & + ] & + ), & + cable_output_variable_t( & + field_name="cplant_turnover_tot", & + netcdf_name="PlantTurnover", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnover, & + patchout=output%patch .or. patchout%PlantTurnover, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%cplant_turnover_tot), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Total Biomass Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_leaf", & + netcdf_name="PlantTurnoverLeaf", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverLeaf, & + patchout=output%patch .or. patchout%PlantTurnoverLeaf, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover(:, LEAF)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Leaf Biomass Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_wood", & + netcdf_name="PlantTurnoverWood", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverWood, & + patchout=output%patch .or. patchout%PlantTurnoverWood, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover(:, WOOD)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Woody Biomass Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_froot", & + netcdf_name="PlantTurnoverFineRoot", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverFineRoot, & + patchout=output%patch .or. patchout%PlantTurnoverFineRoot, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover(:, FROOT)), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "FineRoot Biomass Turnover") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_disturbance", & + netcdf_name="PlantTurnoverWoodDist", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverWoodDist, & + patchout=output%patch .or. patchout%PlantTurnoverWoodDist, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover_disturbance), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Woody Biomass Turnover (disturbance)") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_crowding", & + netcdf_name="PlantTurnoverWoodCrowding", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverWoodCrowding, & + patchout=output%patch .or. patchout%PlantTurnoverWoodCrowding, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover_crowding), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Woody Biomass Turnover (crowding)") & + ] & + ), & + cable_output_variable_t( & + field_name="Cplant_turnover_resource_limitation", & + netcdf_name="PlantTurnoverWoodResourceLim", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%casa .or. output%PlantTurnoverWoodResourceLim, & + patchout=output%patch .or. patchout%PlantTurnoverWoodResourceLim, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%Cplant_turnover_resource_limitation), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Woody Biomass Turnover (Resource Limitation)") & + ] & + ), & + cable_output_variable_t( & + field_name="areacell", & + netcdf_name="Area", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%Area, & + active=output%casa .or. output%Area, & + patchout=output%patch .or. patchout%Area, & + reduction_method="grid_cell_average", & + aggregation_method="point", & + aggregator=new_aggregator(casamet%areacell), & + scale=1e-6, & + metadata=[ & + attribute("units", "km2"), & + attribute("long_name", "Patch Area") & + ] & + ), & + cable_output_variable_t( & + field_name="FluxCtoLUC", & + netcdf_name="LandUseFlux", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=cable_user%POPLUC .or. output%LandUseFlux, & + patchout=output%patch .or. patchout%LandUseFlux, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(casaflux%FluxCtoLUC), & + scale=(1.0 / seconds_per_day / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Sum of wood harvest and clearing fluxes") & + ] & + ) & + ] + + end function cable_diagnostics_casa + +end module diff --git a/src/util/io/output/cable_output_definitions.F90 b/src/offline/cable_diagnostics_core.F90 similarity index 77% rename from src/util/io/output/cable_output_definitions.F90 rename to src/offline/cable_diagnostics_core.F90 index 2a144540d..040a9c129 100644 --- a/src/util/io/output/cable_output_definitions.F90 +++ b/src/offline/cable_diagnostics_core.F90 @@ -1,4 +1,5 @@ -module cable_output_definitions_mod +module cable_diagnostics_core_mod + use cable_def_types_mod, only: met_type use cable_def_types_mod, only: canopy_type use cable_def_types_mod, only: soil_parameter_type @@ -10,68 +11,40 @@ module cable_output_definitions_mod use cable_def_types_mod, only: bgc_pool_type use cable_def_types_mod, only: mvtype, mstype - use cable_timing_utils_mod, only: seconds_per_day - use cable_phys_constants_mod, only: c_molar_mass - use casavariable, only: casa_flux - use casavariable, only: casa_pool - use casavariable, only: casa_met - - use casaparm, only: LEAF - use casaparm, only: WOOD - use casaparm, only: FROOT - - use casaparm, only: MIC - use casaparm, only: SLOW - use casaparm, only: PASS - - use casaparm, only: METB - use casaparm, only: STR - use casaparm, only: CWD - - use cable_common_module, only: cable_user - use cable_common_module, only: l_casacnp - use cable_common_module, only: gw_params - use cable_netcdf_mod, only: CABLE_NETCDF_INT use cable_netcdf_mod, only: CABLE_NETCDF_FLOAT use aggregator_mod, only: new_aggregator + use cable_common_module, only: cable_user + use cable_common_module, only: gw_params + use cable_io_vars_module, only: output, patchout use cable_io_vars_module, only: landpt_global use cable_io_vars_module, only: patch - use cable_io_vars_module, only: lat_all, lon_all - use cable_io_vars_module, only: latitude, longitude - use cable_output_types_mod, only: cable_output_variable_t - use cable_output_types_mod, only: attribute => cable_output_attribute_t - use cable_output_types_mod, only: DIM_PATCH => CABLE_OUTPUT_DIM_PATCH - use cable_output_types_mod, only: DIM_SOIL => CABLE_OUTPUT_DIM_SOIL - use cable_output_types_mod, only: DIM_RAD => CABLE_OUTPUT_DIM_RAD - use cable_output_types_mod, only: DIM_SNOW => CABLE_OUTPUT_DIM_SNOW - use cable_output_types_mod, only: DIM_PLANTCARBON => CABLE_OUTPUT_DIM_PLANTCARBON - use cable_output_types_mod, only: DIM_SOILCARBON => CABLE_OUTPUT_DIM_SOILCARBON - use cable_output_types_mod, only: DIM_LAND => CABLE_OUTPUT_DIM_LAND - use cable_output_types_mod, only: DIM_LAND_GLOBAL => CABLE_OUTPUT_DIM_LAND_GLOBAL - use cable_output_types_mod, only: DIM_X => CABLE_OUTPUT_DIM_X - use cable_output_types_mod, only: DIM_Y => CABLE_OUTPUT_DIM_Y + use cable_output_prototype_v2_mod, only: cable_output_variable_t + use cable_output_prototype_v2_mod, only: attribute => cable_output_attribute_t + use cable_output_prototype_v2_mod, only: DIM_PATCH => CABLE_OUTPUT_DIM_PATCH + use cable_output_prototype_v2_mod, only: DIM_SOIL => CABLE_OUTPUT_DIM_SOIL + use cable_output_prototype_v2_mod, only: DIM_RAD => CABLE_OUTPUT_DIM_RAD + use cable_output_prototype_v2_mod, only: DIM_SNOW => CABLE_OUTPUT_DIM_SNOW + use cable_output_prototype_v2_mod, only: DIM_PLANTCARBON => CABLE_OUTPUT_DIM_PLANTCARBON + use cable_output_prototype_v2_mod, only: DIM_SOILCARBON => CABLE_OUTPUT_DIM_SOILCARBON + use cable_output_prototype_v2_mod, only: DIM_LAND_GLOBAL => CABLE_OUTPUT_DIM_LAND_GLOBAL use cable_checks_module, only: ranges - use cable_abort_module, only: cable_abort - implicit none private - public :: core_outputs - public :: coordinate_variables + public :: cable_diagnostics_core contains - function core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, casapool, casamet, rough, bgc) result(output_variables) - real, intent(in) :: dels + function cable_diagnostics_core(met, canopy, soil, ssnow, rad, veg, bal, rough, bgc, dels) result(output_variables) type(met_type), intent(inout) :: met type(canopy_type), intent(inout) :: canopy type(soil_parameter_type), intent(inout) :: soil @@ -79,16 +52,13 @@ function core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, c type(radiation_type), intent(inout) :: rad type(veg_parameter_type), intent(inout) :: veg type(balances_type), intent(inout) :: bal - type(casa_flux), intent(inout) :: casaflux - type(casa_pool), intent(inout) :: casapool - type(casa_met), intent(inout) :: casamet type(roughness_type), intent(inout) :: rough type(bgc_pool_type), intent(inout) :: bgc + real, intent(in) :: dels type(cable_output_variable_t), allocatable :: output_variables(:) output_variables = [ & - casa_cnp_outputs(casaflux, casapool, casamet), & cable_output_variable_t( & field_name="fsd", & netcdf_name="SWdown", & @@ -467,23 +437,6 @@ function core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, c attribute("long_name", "Net radiation absorbed by ground") & ] & ), & - cable_output_variable_t( & - field_name="fnee", & - netcdf_name="NEE", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%flux .or. output%NEE, & - patchout=output%patch .or. patchout%NEE, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(canopy%fnee), & - scale=(1.0 / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Net ecosystem exchange of CO2") & - ] & - ), & cable_output_variable_t( & field_name="wb", & netcdf_name="SoilMoist", & @@ -1116,6 +1069,23 @@ function core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, c attribute("long_name", "Autotrophic respiration") & ] & ), & + cable_output_variable_t( & + field_name="fnee", & + netcdf_name="NEE", & + data_shape=[DIM_PATCH], & + var_type=CABLE_NETCDF_FLOAT, & + range=ranges%NEE, & + active=output%flux .or. output%NEE, & + patchout=output%patch .or. patchout%NEE, & + reduction_method="grid_cell_average", & + aggregation_method="mean", & + aggregator=new_aggregator(canopy%fnee), & + scale=(1.0 / c_molar_mass), & + metadata=[ & + attribute("units", "umol/m^2/s"), & + attribute("long_name", "Net ecosystem exchange of CO2") & + ] & + ), & cable_output_variable_t( & field_name="wtd", & netcdf_name="WatTable", & @@ -2216,538 +2186,6 @@ function core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, c ) & ] - end function core_outputs - - function casa_cnp_outputs(casaflux, casapool, casamet) result(casa_output_variables) - type(casa_flux), intent(in) :: casaflux - type(casa_pool), intent(in) :: casapool - type(casa_met), intent(in) :: casamet - type(cable_output_variable_t), allocatable :: casa_output_variables(:) - - if (.not. l_casacnp) then - allocate(casa_output_variables(0)) - return - end if - - casa_output_variables = [ & - cable_output_variable_t( & - field_name="crmplant_froot", & - netcdf_name="RootResp", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%AutoResp, & - active=output%carbon .or. output%casa, & - patchout=output%patch .or. patchout%casa, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%crmplant(:, FROOT)), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Fine Root Autotrophic respiration") & - ] & - ), & - cable_output_variable_t( & - field_name="crmplant_wood", & - netcdf_name="StemResp", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%AutoResp, & - active=output%carbon .or. output%casa, & - patchout=output%patch .or. patchout%casa, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%crmplant(:, WOOD)), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "StemWood Autotrophic respiration") & - ] & - ), & - cable_output_variable_t( & - field_name="cnbp", & - netcdf_name="NBP", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%NBP, & - patchout=output%patch .or. patchout%NBP, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%cnbp), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Net Biosphere Production") & - ] & - ), & - cable_output_variable_t( & - field_name="dCdt", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%dCdt, & - patchout=output%patch .or. patchout%dCdt, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%dCdt), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Carbon accumulation rate (uptake +ve)") & - ] & - ), & - cable_output_variable_t( & - field_name="csoiltot", & - netcdf_name="TotSoilCarb", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotSoilCarb, & - active=output%casa .or. output%TotSoilCarb, & - patchout=output%patch .or. patchout%TotSoilCarb, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%csoiltot), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Total Soil and Litter Carbon") & - ] & - ), & - cable_output_variable_t( & - field_name="clittertot", & - netcdf_name="TotLittCarb", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLittCarb, & - active=output%casa .or. output%TotLittCarb, & - patchout=output%patch .or. patchout%TotLittCarb, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%clittertot), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Total Litter Carbon") & - ] & - ), & - cable_output_variable_t( & - field_name="csoil_mic", & - netcdf_name="SoilCarbFast", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLittCarb, & - active=output%casa .or. output%SoilCarbFast, & - patchout=output%patch .or. patchout%SoilCarbFast, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%csoil(:, MIC)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Soil Carbon: Fast Turnover") & - ] & - ), & - cable_output_variable_t( & - field_name="csoil_slow", & - netcdf_name="SoilCarbSlow", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotSoilCarb, & - active=output%casa .or. output%SoilCarbSlow, & - patchout=output%patch .or. patchout%SoilCarbSlow, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%csoil(:, SLOW)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Soil Carbon: Slow Turnover") & - ] & - ), & - cable_output_variable_t( & - field_name="csoil_pass", & - netcdf_name="SoilCarbPassive", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotSoilCarb, & - active=output%casa .or. output%SoilCarbPassive, & - patchout=output%patch .or. patchout%SoilCarbPassive, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%csoil(:, PASS)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Soil Carbon: Passive") & - ] & - ), & - cable_output_variable_t( & - field_name="clitter_metb", & - netcdf_name="LittCarbMetabolic", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLittCarb, & - active=output%casa .or. output%LittCarbMetabolic, & - patchout=output%patch .or. patchout%LittCarbMetabolic, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%clitter(:, METB)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Litter Carbon: metabolic") & - ] & - ), & - cable_output_variable_t( & - field_name="clitter_str", & - netcdf_name="LittCarbStructural", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLittCarb, & - active=output%casa .or. output%LittCarbStructural, & - patchout=output%patch .or. patchout%LittCarbStructural, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%clitter(:, STR)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Litter Carbon: structural") & - ] & - ), & - cable_output_variable_t( & - field_name="clitter_cwd", & - netcdf_name="LittCarbCWD", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLittCarb, & - active=output%casa .or. output%LittCarbCWD, & - patchout=output%patch .or. patchout%LittCarbCWD, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%clitter(:, CWD)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Litter Carbon: CWD") & - ] & - ), & - cable_output_variable_t( & - field_name="cplant_leaf", & - netcdf_name="PlantCarbLeaf", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLittCarb, & - active=output%casa .or. output%PlantCarbLeaf, & - patchout=output%patch .or. patchout%PlantCarbLeaf, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%cplant(:, LEAF)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Plant Carbon: leaf") & - ] & - ), & - cable_output_variable_t( & - field_name="cplant_wood", & - netcdf_name="PlantCarbWood", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLittCarb, & - active=output%casa .or. output%PlantCarbWood, & - patchout=output%patch .or. patchout%PlantCarbWood, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%cplant(:, WOOD)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Plant Carbon: wood (above- and below-ground)") & - ] & - ), & - cable_output_variable_t( & - field_name="cplant_froot", & - netcdf_name="PlantCarbFineRoot", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLittCarb, & - active=output%casa .or. output%PlantCarbFineRoot, & - patchout=output%patch .or. patchout%PlantCarbFineRoot, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%cplant(:, FROOT)), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Plant Carbon: Fine roots") & - ] & - ), & - cable_output_variable_t( & - field_name="cplanttot", & - netcdf_name="TotLivBiomass", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%TotLivBiomass, & - active=output%casa .or. output%TotLivBiomass, & - patchout=output%patch .or. patchout%TotLivBiomass, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casapool%cplanttot), & - scale=(1.0 / 1000.0), & - metadata=[ & - attribute("units", "kg C/m^2"), & - attribute("long_name", "Total Biomass") & - ] & - ), & - cable_output_variable_t( & - field_name="cplant_turnover_tot", & - netcdf_name="PlantTurnover", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%PlantTurnover, & - patchout=output%patch .or. patchout%PlantTurnover, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%cplant_turnover_tot), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Total Biomass Turnover") & - ] & - ), & - cable_output_variable_t( & - field_name="Cplant_turnover_leaf", & - netcdf_name="PlantTurnoverLeaf", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%PlantTurnoverLeaf, & - patchout=output%patch .or. patchout%PlantTurnoverLeaf, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%Cplant_turnover(:, LEAF)), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Leaf Biomass Turnover") & - ] & - ), & - cable_output_variable_t( & - field_name="Cplant_turnover_wood", & - netcdf_name="PlantTurnoverWood", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%PlantTurnoverWood, & - patchout=output%patch .or. patchout%PlantTurnoverWood, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%Cplant_turnover(:, WOOD)), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Woody Biomass Turnover") & - ] & - ), & - cable_output_variable_t( & - field_name="Cplant_turnover_froot", & - netcdf_name="PlantTurnoverFineRoot", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%PlantTurnoverFineRoot, & - patchout=output%patch .or. patchout%PlantTurnoverFineRoot, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%Cplant_turnover(:, FROOT)), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "FineRoot Biomass Turnover") & - ] & - ), & - cable_output_variable_t( & - field_name="Cplant_turnover_disturbance", & - netcdf_name="PlantTurnoverWoodDist", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%PlantTurnoverWoodDist, & - patchout=output%patch .or. patchout%PlantTurnoverWoodDist, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%Cplant_turnover_disturbance), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Woody Biomass Turnover (disturbance)") & - ] & - ), & - cable_output_variable_t( & - field_name="Cplant_turnover_crowding", & - netcdf_name="PlantTurnoverWoodCrowding", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%PlantTurnoverWoodCrowding, & - patchout=output%patch .or. patchout%PlantTurnoverWoodCrowding, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%Cplant_turnover_crowding), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Woody Biomass Turnover (crowding)") & - ] & - ), & - cable_output_variable_t( & - field_name="Cplant_turnover_resource_limitation", & - netcdf_name="PlantTurnoverWoodResourceLim", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=output%casa .or. output%PlantTurnoverWoodResourceLim, & - patchout=output%patch .or. patchout%PlantTurnoverWoodResourceLim, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%Cplant_turnover_resource_limitation), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Woody Biomass Turnover (Resource Limitation)") & - ] & - ), & - cable_output_variable_t( & - field_name="areacell", & - netcdf_name="Area", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%Area, & - active=output%casa .or. output%Area, & - patchout=output%patch .or. patchout%Area, & - reduction_method="grid_cell_average", & - aggregation_method="point", & - aggregator=new_aggregator(casamet%areacell), & - scale=1e-6, & - metadata=[ & - attribute("units", "km2"), & - attribute("long_name", "Patch Area") & - ] & - ), & - cable_output_variable_t( & - field_name="FluxCtoLUC", & - netcdf_name="LandUseFlux", & - data_shape=[DIM_PATCH], & - var_type=CABLE_NETCDF_FLOAT, & - range=ranges%NEE, & - active=cable_user%POPLUC .or. output%LandUseFlux, & - patchout=output%patch .or. patchout%LandUseFlux, & - reduction_method="grid_cell_average", & - aggregation_method="mean", & - aggregator=new_aggregator(casaflux%FluxCtoLUC), & - scale=(1.0 / seconds_per_day / c_molar_mass), & - metadata=[ & - attribute("units", "umol/m^2/s"), & - attribute("long_name", "Sum of wood harvest and clearing fluxes") & - ] & - ) & - ] - - end function casa_cnp_outputs - - function coordinate_variables(grid_type) result(coord_variables) - character(len=*), intent(in) :: grid_type - - type(cable_output_variable_t), allocatable :: coord_variables(:) - - select case (grid_type) - case ("restart") - coord_variables = [ & - cable_output_variable_t( & - field_name="latitude", & - data_shape=[DIM_LAND_GLOBAL], & - distributed=.false., & - aggregator=new_aggregator(latitude), & - metadata=[attribute("units", "degrees_north")] & - ), & - cable_output_variable_t( & - field_name="longitude", & - data_shape=[DIM_LAND_GLOBAL], & - distributed=.false., & - aggregator=new_aggregator(longitude), & - metadata=[attribute("units", "degrees_east")] & - ) & - ] - case ("mask") - coord_variables = [ & - cable_output_variable_t( & - field_name="lat_all", & - netcdf_name="latitude", & - data_shape=[DIM_X, DIM_Y], & - var_type=CABLE_NETCDF_FLOAT, & - parameter=.true., & - distributed=.false., & - aggregator=new_aggregator(lat_all), & - metadata=[attribute("units", "degrees_north")] & - ), & - cable_output_variable_t( & - field_name="lon_all", & - netcdf_name="longitude", & - data_shape=[DIM_X, DIM_Y], & - parameter=.true., & - distributed=.false., & - aggregator=new_aggregator(lon_all), & - metadata=[attribute("units", "degrees_east")] & - ), & - cable_output_variable_t( & - field_name="x", & - data_shape=[DIM_X], & - parameter=.true., & - distributed=.false., & - aggregator=new_aggregator(lon_all(:, 1)), & - metadata=[ & - attribute("units", "degrees_east"), & - attribute("comment", "x coordinate variable for GrADS compatibility") & - ] & - ), & - cable_output_variable_t( & - field_name="y", & - data_shape=[DIM_Y], & - parameter=.true., & - distributed=.false., & - aggregator=new_aggregator(lat_all(1, :)), & - metadata=[ & - attribute("units", "degrees_north"), & - attribute("comment", "y coordinate variable for GrADS compatibility") & - ] & - ) & - ] - case ("land") - coord_variables = [ & - cable_output_variable_t( & - field_name="local_lat", & - data_shape=[DIM_LAND_GLOBAL], & - parameter=.true., & - distributed=.false., & - aggregator=new_aggregator(latitude), & - metadata=[attribute("units", "degrees_north")] & - ), & - cable_output_variable_t( & - field_name="local_lon", & - data_shape=[DIM_LAND_GLOBAL], & - parameter=.true., & - distributed=.false., & - aggregator=new_aggregator(longitude), & - metadata=[attribute("units", "degrees_east")] & - ) & - ] - case default - call cable_abort("Unexpected grid type '" // grid_type // "'", __FILE__, __LINE__) - end select - - end function coordinate_variables + end function cable_diagnostics_core end module diff --git a/src/offline/cable_mpimaster.F90 b/src/offline/cable_mpimaster.F90 index ea0f4d843..8e29b7b17 100644 --- a/src/offline/cable_mpimaster.F90 +++ b/src/offline/cable_mpimaster.F90 @@ -103,7 +103,8 @@ MODULE cable_mpimaster use cable_output_prototype_v2_mod, only: cable_output_write use cable_output_prototype_v2_mod, only: cable_output_write_parameters use cable_output_prototype_v2_mod, only: cable_output_write_restart - use cable_output_prototype_v2_mod, only: cable_output_core_outputs + use cable_diagnostics_core_mod, only: cable_diagnostics_core + use cable_diagnostics_casa_mod, only: cable_diagnostics_casa use cable_netcdf_mod, only: cable_netcdf_mod_init, cable_netcdf_mod_end IMPLICIT NONE @@ -455,7 +456,8 @@ SUBROUTINE mpidrv_master (comm, dels, koffset, kend, PLUME, CRU, mpi_grp_master) call cable_output_mod_init() call cable_output_register_output_variables([ & - cable_output_core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, casapool, casamet, rough, bgc) & + cable_diagnostics_core(met, canopy, soil, ssnow, rad, veg, bal, rough, bgc, dels=dels), & + cable_diagnostics_casa(casaflux, casapool, casamet) & ]) call cable_output_profiles_init() call cable_output_write_parameters(kstart, patch, landpt, met) diff --git a/src/offline/cable_serial.F90 b/src/offline/cable_serial.F90 index e87c61380..1f070ce57 100644 --- a/src/offline/cable_serial.F90 +++ b/src/offline/cable_serial.F90 @@ -118,7 +118,8 @@ MODULE cable_serial use cable_output_prototype_v2_mod, only: cable_output_write use cable_output_prototype_v2_mod, only: cable_output_write_parameters use cable_output_prototype_v2_mod, only: cable_output_write_restart - use cable_output_prototype_v2_mod, only: cable_output_core_outputs + use cable_diagnostics_core_mod, only: cable_diagnostics_core + use cable_diagnostics_casa_mod, only: cable_diagnostics_casa use cable_netcdf_mod, only: cable_netcdf_mod_init, cable_netcdf_mod_end USE cable_checks_module, ONLY: constant_check_range USE cable_write_module, ONLY: nullify_write @@ -433,7 +434,8 @@ SUBROUTINE serialdrv(NRRRR, dels, koffset, kend, GSWP_MID, PLUME, CRU, site, mpi call cable_output_mod_init() call cable_output_register_output_variables([ & - cable_output_core_outputs(dels, met, canopy, soil, ssnow, rad, veg, bal, casaflux, casapool, casamet, rough, bgc) & + cable_diagnostics_core(met, canopy, soil, ssnow, rad, veg, bal, rough, bgc, dels=dels), & + cable_diagnostics_casa(casaflux, casapool, casamet) & ]) call cable_output_profiles_init() call cable_output_write_parameters(kstart, patch, landpt, met) diff --git a/src/util/io/output/cable_output.F90 b/src/util/io/output/cable_output.F90 index 5f3f24b57..d7e250ebd 100644 --- a/src/util/io/output/cable_output.F90 +++ b/src/util/io/output/cable_output.F90 @@ -1,4 +1,4 @@ -module cable_output_prototype_v2_mod +module cable_output_prototype_v2_mod ! TODO(Sean): rename to cable_output_mod use cable_output_core_mod, only: cable_output_mod_init use cable_output_core_mod, only: cable_output_mod_end @@ -12,14 +12,13 @@ module cable_output_prototype_v2_mod use cable_output_types_mod, only: cable_output_attribute_t use cable_output_types_mod, only: cable_output_variable_t - use cable_output_definitions_mod, only: cable_output_core_outputs => core_outputs - use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PATCH use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOIL use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SNOW use cable_output_types_mod, only: CABLE_OUTPUT_DIM_RAD use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PLANTCARBON use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOILCARBON + use cable_output_types_mod, only: CABLE_OUTPUT_DIM_LAND_GLOBAL implicit none public diff --git a/src/util/io/output/cable_output_core.F90 b/src/util/io/output/cable_output_core.F90 index a7442ecc8..67d4bc70a 100644 --- a/src/util/io/output/cable_output_core.F90 +++ b/src/util/io/output/cable_output_core.F90 @@ -67,8 +67,7 @@ module cable_output_core_mod use cable_output_utils_mod, only: dim_size use cable_output_utils_mod, only: define_variables use cable_output_utils_mod, only: set_global_attributes - - use cable_output_definitions_mod, only: coordinate_variables + use cable_output_utils_mod, only: coordinate_variables implicit none private diff --git a/src/util/io/output/cable_output_utils.F90 b/src/util/io/output/cable_output_utils.F90 index 19eb7dfde..e9c43dcdb 100644 --- a/src/util/io/output/cable_output_utils.F90 +++ b/src/util/io/output/cable_output_utils.F90 @@ -18,6 +18,8 @@ module cable_output_utils_mod use cable_io_vars_module, only: timeunits use cable_io_vars_module, only: time_coord use cable_io_vars_module, only: calendar + use cable_io_vars_module, only: lat_all, lon_all + use cable_io_vars_module, only: latitude, longitude use cable_abort_module, only: cable_abort @@ -30,6 +32,7 @@ module cable_output_utils_mod use cable_output_types_mod, only: cable_output_dim_t use cable_output_types_mod, only: cable_output_variable_t use cable_output_types_mod, only: cable_output_profile_t + use cable_output_types_mod, only: attribute => cable_output_attribute_t use cable_output_types_mod, only: CABLE_OUTPUT_DIM_PATCH use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SOIL use cable_output_types_mod, only: CABLE_OUTPUT_DIM_SNOW @@ -45,9 +48,12 @@ module cable_output_utils_mod use cable_output_types_mod, only: FILL_VALUE_REAL64 use cable_output_types_mod, only: CABLE_OUTPUT_VAR_TYPE_UNDEFINED + use aggregator_mod, only: new_aggregator + implicit none private + public :: coordinate_variables public :: check_duplicate_variable_names public :: check_sampling_frequency public :: dim_size @@ -59,6 +65,98 @@ module cable_output_utils_mod contains + function coordinate_variables(grid_type) result(coord_variables) + character(len=*), intent(in) :: grid_type + + type(cable_output_variable_t), allocatable :: coord_variables(:) + + select case (grid_type) + case ("restart") + coord_variables = [ & + cable_output_variable_t( & + field_name="latitude", & + data_shape=[CABLE_OUTPUT_DIM_LAND_GLOBAL], & + distributed=.false., & + aggregator=new_aggregator(latitude), & + metadata=[attribute("units", "degrees_north")] & + ), & + cable_output_variable_t( & + field_name="longitude", & + data_shape=[CABLE_OUTPUT_DIM_LAND_GLOBAL], & + distributed=.false., & + aggregator=new_aggregator(longitude), & + metadata=[attribute("units", "degrees_east")] & + ) & + ] + case ("mask") + coord_variables = [ & + cable_output_variable_t( & + field_name="lat_all", & + netcdf_name="latitude", & + data_shape=[CABLE_OUTPUT_DIM_X, CABLE_OUTPUT_DIM_Y], & + var_type=CABLE_NETCDF_FLOAT, & + parameter=.true., & + distributed=.false., & + aggregator=new_aggregator(lat_all), & + metadata=[attribute("units", "degrees_north")] & + ), & + cable_output_variable_t( & + field_name="lon_all", & + netcdf_name="longitude", & + data_shape=[CABLE_OUTPUT_DIM_X, CABLE_OUTPUT_DIM_Y], & + parameter=.true., & + distributed=.false., & + aggregator=new_aggregator(lon_all), & + metadata=[attribute("units", "degrees_east")] & + ), & + cable_output_variable_t( & + field_name="x", & + data_shape=[CABLE_OUTPUT_DIM_X], & + parameter=.true., & + distributed=.false., & + aggregator=new_aggregator(lon_all(:, 1)), & + metadata=[ & + attribute("units", "degrees_east"), & + attribute("comment", "x coordinate variable for GrADS compatibility") & + ] & + ), & + cable_output_variable_t( & + field_name="y", & + data_shape=[CABLE_OUTPUT_DIM_Y], & + parameter=.true., & + distributed=.false., & + aggregator=new_aggregator(lat_all(1, :)), & + metadata=[ & + attribute("units", "degrees_north"), & + attribute("comment", "y coordinate variable for GrADS compatibility") & + ] & + ) & + ] + case ("land") + coord_variables = [ & + cable_output_variable_t( & + field_name="local_lat", & + data_shape=[CABLE_OUTPUT_DIM_LAND_GLOBAL], & + parameter=.true., & + distributed=.false., & + aggregator=new_aggregator(latitude), & + metadata=[attribute("units", "degrees_north")] & + ), & + cable_output_variable_t( & + field_name="local_lon", & + data_shape=[CABLE_OUTPUT_DIM_LAND_GLOBAL], & + parameter=.true., & + distributed=.false., & + aggregator=new_aggregator(longitude), & + metadata=[attribute("units", "degrees_east")] & + ) & + ] + case default + call cable_abort("Unexpected grid type '" // grid_type // "'", __FILE__, __LINE__) + end select + + end function coordinate_variables + logical function data_shape_eq(shape1, shape2) type(cable_output_dim_t), dimension(:), intent(in) :: shape1, shape2 data_shape_eq = size(shape1) == size(shape2) .and. all(shape1 == shape2)