Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constituent updates #549

Open
wants to merge 185 commits into
base: main
Choose a base branch
from
Open

Conversation

peverwhee
Copy link
Collaborator

@peverwhee peverwhee commented Mar 18, 2024

Summary

Brings in optional metadata field to enable dynamic (run-time) constituents; also brings in a couple new methods for the constituent object.

closes #557

Description

Key dynamic constituent mods:

  • ccpp_capgen.py: create dynamic constituent dictionary (key=scheme name) and pass into API()
  • constituents.py: add handling of dynamic constituents to register constituents routine (including conditional call(s) to user-supplied routines); update error handling for register_constituents

Includes additional constituent object methods:

  • set_water_species
  • set_min_val
  • set_molar_mass

Includes fixes to ensure consistent order of generated code (use statements, variables, etc)

Also brings back the .github/workflow/python.yaml workflow that got lost somehow

User interface changes?: Yes, but optional
User can now use "dynamic_constituent_routine" metadata header field to point to a function contained within the module fortran.

Testing:
Modified advection test to include dynamic constituent routines.
Added doctests to ccpp_datafile

Generated code snippet

   subroutine test_host_ccpp_register_constituents(host_constituents, errflg, errmsg)
      ! Create constituent object for suites in 

      use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t
      ! Suite constituent interfaces
      use ccpp_cld_suite_cap,        only: cld_suite_constituents_num_consts
      use ccpp_cld_suite_cap,        only: cld_suite_constituents_const_name
      use ccpp_cld_suite_cap,        only: cld_suite_constituents_copy_const
      ! Dynamic constituent routines
      use cld_liq, only: cld_liq_dynamic_constituents
      use cld_ice, only: cld_ice_dynamic_constituents

      ! Dummy arguments
      type(ccpp_constituent_properties_t), target, intent(in)  :: host_constituents(:)
      character(len=512), intent(out)   :: errmsg ! ccpp_error_message
      integer,            intent(out)   :: errflg ! ccpp_error_code
      ! Local variables
      integer                                      :: num_suite_consts
      integer                                      :: num_consts
      integer                                      :: num_dyn_consts
      integer                                      :: index, index_start
      integer                                      :: field_ind
      type(ccpp_constituent_properties_t), pointer :: const_prop
      ! dynamic constituent props variable for cld_ice
      type(ccpp_constituent_properties_t), allocatable :: dyn_const_prop_0(:)
      ! dynamic constituent props variable for cld_liq
      type(ccpp_constituent_properties_t), allocatable :: dyn_const_prop_1(:)

      errflg = 0
      num_consts = size(host_constituents, 1)
      num_dyn_consts = 0
      ! Number of suite constants for cld_suite
      num_suite_consts = cld_suite_constituents_num_consts(errflg=errflg, errmsg=errmsg)
      if (errflg /= 0) then
         return
      end if
      num_consts = num_consts + num_suite_consts
      ! Add in dynamic constituents
      call cld_ice_dynamic_constituents(dyn_const_prop_0, errcode=errflg, errmsg=errmsg)
      if (errflg /= 0) then
         return
      end if
      num_dyn_consts = num_dyn_consts + size(dyn_const_prop_0)
      call cld_liq_dynamic_constituents(dyn_const_prop_1, errcode=errflg, errmsg=errmsg)
      if (errflg /= 0) then
         return
      end if
      num_dyn_consts = num_dyn_consts + size(dyn_const_prop_1)
      num_consts = num_consts + num_dyn_consts
      ! Pack dynamic_constituents array
      allocate(dynamic_constituents(num_dyn_consts), stat=errflg)
      if (errflg /= 0) then
         errmsg = 'failed to allocate dynamic_constituents'
         return
      end if
      index_start = 0
      do index = 1, size(dyn_const_prop_0, 1)
         dynamic_constituents(index + index_start) = dyn_const_prop_0(index)
      end do
      index_start = size(dyn_const_prop_0, 1)
      deallocate(dyn_const_prop_0)
      do index = 1, size(dyn_const_prop_1, 1)
         dynamic_constituents(index + index_start) = dyn_const_prop_1(index)
      end do
      index_start = size(dyn_const_prop_1, 1)
      deallocate(dyn_const_prop_1)
      ! Initialize constituent data and field object
      call test_host_constituents_obj%initialize_table(num_consts)
      ! Add host model constituent metadata
      do index = 1, size(host_constituents, 1)
         const_prop => host_constituents(index)
         call test_host_constituents_obj%new_field(const_prop, errcode=errflg, errmsg=errmsg)
         nullify(const_prop)
         if (errflg /= 0) then
            return
         end if
      end do

      ! Add dynamic constituent properties
      do index = 1, size(dynamic_constituents, 1)
         const_prop => dynamic_constituents(index)
         call test_host_constituents_obj%new_field(const_prop, errcode=errflg, errmsg=errmsg)
         nullify(const_prop)
         if (errflg /= 0) then
            return
         end if
      end do
      ! Add cld_suite constituent metadata
      num_suite_consts = cld_suite_constituents_num_consts(errflg=errflg, errmsg=errmsg)
      if (errflg /= 0) then
         return
      end if
      do index = 1, num_suite_consts
         allocate(const_prop, stat=errflg)
         if (errflg /= 0) then
            errmsg = "ERROR allocating const_prop"
            return
         end if
         call cld_suite_constituents_copy_const(index, const_prop, errflg=errflg, errmsg=errmsg)
         if (errflg /= 0) then
            return
         end if
         call test_host_constituents_obj%new_field(const_prop, errcode=errflg, errmsg=errmsg)
         nullify(const_prop)
         if (errflg /= 0) then
            return
         end if
      end do

      call test_host_constituents_obj%lock_table(errcode=errflg, errmsg=errmsg)
      if (errflg /= 0) then
         return
      end if
! Set the index for each active constituent
      do index = 1, SIZE(test_host_model_const_indices)
         call test_host_constituents_obj%const_index(field_ind,                                   &
              test_host_model_const_stdnames(index), errcode=errflg, errmsg=errmsg)
         if (errflg /= 0) then
            return
         end if
         if (field_ind > 0) then
            test_host_model_const_indices(index) = field_ind
         else
            errflg = 1
            errmsg = 'No field index for '//trim(test_host_model_const_stdnames(index))
            return
         end if
      end do

Steve Goldhaber and others added 30 commits July 13, 2022 21:45
Allow schema file to be passed to validate
Add Fortran comment write method
Add metadata table type and name to CCPP datatable variable entries
Improve generated constituent-handling code
Fix issues with output of long comments
Ensure more instance variables are 'private'
Have capgen create database object
Add line fill (target line length) interface to FortranWriter
New Fortran interface, indent_size
New Fortran interface, blank_line
Added insert function for verbatim copy to file
Allow arbitrary break for long lines
Make sure call list variables have an intent
Added link to constituent dictionary
Add metadata to CCPP constituent object DDT
Add Fortran writing unit tests and fix line break bugs
Added constituent props array host interface, improved unit conversion error
Use +ELLIPSIS instead of +IGNORE_EXCEPTION_DETAILS for doctests
Constituent cleanup, no function side effects in interface
Completed and optimized constituent accessor routines
Added minimum allowed value property
Add molecular weight as variable and constituent property
Fix missing property (advected) from database
Rename dir for var_action test to distinguish from ct_build
Copy link
Collaborator

@mwaxmonsky mwaxmonsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great @peverwhee!

Has a few suggestions and some things I just wanted to clarify but just let me know if these are not relevant and I can re-review. Thanks!

scripts/host_cap.py Outdated Show resolved Hide resolved
scripts/host_cap.py Outdated Show resolved Hide resolved
scripts/host_cap.py Outdated Show resolved Hide resolved
scripts/host_cap.py Outdated Show resolved Hide resolved
scripts/host_cap.py Outdated Show resolved Hide resolved
scripts/ccpp_capgen.py Show resolved Hide resolved
scripts/ccpp_capgen.py Show resolved Hide resolved
scripts/ccpp_capgen.py Outdated Show resolved Hide resolved
scripts/ccpp_datafile.py Outdated Show resolved Hide resolved
@peverwhee
Copy link
Collaborator Author

Some test suggestion:

  • Test for handling of duplicate compatible constituent
  • Test for handling of duplicate semi-compatible constituent (e.g., unit difference), that the framework cannot handle.
  • Test for handling of duplicate incompatible constituent

Added logic to the constituents object to check for compatibility before adding a new constituent. Right now, that logic only checks a logical and character properties (advected, water species, thermo active, units). And added the following tests to the advection_test:

  • Compatible constituent
  • incompatible constituent

Also added unit tests to test parsing and fortran vs metadata comparisons

@peverwhee peverwhee requested a review from gold2718 April 29, 2024 19:43
Copy link
Collaborator

@climbfuji climbfuji left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment I have is that this PR is quite big and it contains a lot of somewhat unrelated changes. It would be better imo to separate out these different changes into individual PRs: f-strings, sorted, the changes to the water/minval/units, and the dynamic constituents?

I am not suggesting to break up this PR, but maybe consider submitting smaller, less intimidating to review changes in the future (in full awareness that I am guilty of these catchall PRs from time to time ...).

I am also not suggesting to make any changes to the style of the comments. This could be a separate PR where we clean up just the formatting and re-enable the linter(s) in CI.

Overall this looks very good to me, but I don't understand it well enough. I requested a couple of small changes and I will approve afterwards, but someone with a better understanding should be reviewing and approving this (and of course the full testing needs to be done once the reviews are finalized) before it gets merged.

Thanks for all the hard work that went into this PR!

pytest.ini Outdated Show resolved Hide resolved
scripts/ccpp_datafile.py Show resolved Hide resolved
scripts/ccpp_datafile.py Outdated Show resolved Hide resolved
src/ccpp_hash_table.F90 Outdated Show resolved Hide resolved
test/advection_test/test_host.F90 Outdated Show resolved Hide resolved
@peverwhee peverwhee requested a review from climbfuji May 16, 2024 21:17
Copy link
Collaborator

@climbfuji climbfuji left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing my comments. This is a non-expert approval which on its own is not sufficient for merging ;-)

Copy link
Collaborator

@dustinswales dustinswales left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peverwhee Some minor comments, but overall looks good. Thanks for these changes!
I will start testing in the UFS using this branch and push to get on the commit queue asap.

@@ -629,7 +679,24 @@ def capgen(run_env, return_db=False):
scheme_files = [const_prop_mod] + scheme_files
# end if
# Next, parse the scheme files
# We always need to parse the ccpp_constituent_prop_ptr_t DDT
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we aren't using the constituents feature?

cap.write(f"integer{spc} :: num_consts", 2)
cap.write(f"integer{spc} :: num_dyn_consts", 2)
cap.write(f"integer{spc} :: index, index_start", 2)
cap.write(f"integer{spc} :: field_ind", 2)
cap.write(f"type({CONST_PROP_TYPE}), pointer :: const_prop", 2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be cap.write(f"type({CONST_PROP_TYPE}), pointer :: const_prop => null()", 2)

character(len=*), parameter :: subname = 'ccp_model_const_add_metadata'
character(len=errmsg_len) :: error
character(len=*), parameter :: subname = 'ccp_model_const_add_metadata'
type(ccpp_constituent_properties_t), pointer :: cprop
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialize to null()

!!XXgoldyXX: Add check on key to see if incompatible item already there.
! Check to see if standard name is already in the table
call field_data%standard_name(standard_name, errcode, errmsg)
cprop => this%find_const(standard_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that you nullify cprop at the start of this%find_const().
Is it best to pass in a null pointer, and return the null pointer if condition not met, otherwise return associated pointer. A purely stylistic question.

cprop => this%find_const(standard_name)
if (associated(cprop)) then
! Standard name already in table, let's see if the existing constituent is the same
cindex = cprop%const_index()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used.

call test_host_ccpp_deallocate_dynamic_constituents()
deallocate(host_constituents)
allocate(host_constituents(2))
call host_constituents(1)%instantiate(std_name="specific_humidity", &
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably don't understand the test here, but how is it allowed to have two host constituents with the same standard_name?

errflg = 0
end if
! Check moist mixing ratio for a dynamic constituent
call const_props(index_dyn2)%is_dry(const_log, errflg, errmsg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be calling "is_moist"?

@@ -0,0 +1,96 @@
! Test parameterization with no vertical level
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the description?
Also, is this test exercising duplicates?

@@ -0,0 +1,75 @@
! Test parameterization with no vertical level
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the description?

@@ -6,7 +6,7 @@ scriptdir="$( cd $( dirname $0 ); pwd -P )"
##
## Option default values
##
defdir="ct_build"
defdir="va_build"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

VarDictionary doctests broken for python3.12
7 participants