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

DRAFT TO DISCUSS - add CCPP register phase #582

Draft
wants to merge 6 commits into
base: develop
Choose a base branch
from

Conversation

peverwhee
Copy link
Collaborator

@peverwhee peverwhee commented Aug 5, 2024

Overview

This PR adds a new phase, register, that can be called by a host model and used by schemes to perform any set up that needs to happen BEFORE the grid is established.

NOTE: this PR also removes the old dynamic_constituent_routine metadata implementation for runtime constituents.

Description

I have implemented it as an "optional" phase, by which I mean that it is not required that a host model call this phase (though I'm happy to be overruled!). As a result, the register phase does not change the CCPP "state" (but will produce an error if it is called after the init phase).

More:

Dynamic/run-time constituent handling:

  • If a scheme has run-time constituents, those shall be allocated, instantiated, and returned from the scheme's register phase. This metadata is required (the framework determines that there are runtime constituents from a scheme if there is a ccpp_constituent_properties_t variable required):
[ <unique dynamic constituent local name> ]
  standard_name = <some unique standard name>
  dimensions = (:)
  type = ccpp_constituent_properties_t
  intent = out
  allocatable = true
  • The standard name doesn't really matter but MUST be different from other runtime constituent standard names in the scheme; it may be easiest to standardize this to something like dynamic_constituents_for_<scheme>
  • The framework will then compile all scheme constituents into module-level variables in the host cap called <suite>_dynamic_constituents, which are then used to pack and initialize the module level constituents object <host>_constituents_obj.
  • If there are no dynamic constituents registered by any schemes within a suite, that suite's dynamic constituents array is allocated to 0.

Generated host cap code examples

  1. Multiple schemes have dynamic constituents:
subroutine test_host_ccpp_physics_register(suite_name, errmsg, errflg)

      use ccpp_cld_suite_cap, only: cld_suite_register

      character(len=*)                         :: suite_name
      character(len=512)                       :: errmsg
      integer                                  :: errflg
      type(ccpp_constituent_properties_t),allocatable          :: dyn_const(:)
      type(ccpp_constituent_properties_t),allocatable          :: dyn_const_ice(:)
      integer                                  :: num_dyn_consts
      integer                                  :: const_index

      errflg = 0
      errmsg = ""
      if (trim(suite_name) == 'cld_suite') then
         call cld_suite_register(errflg=errflg, errmsg=errmsg, dyn_const=dyn_const,               &
              dyn_const_ice=dyn_const_ice)
         allocate(cld_suite_dynamic_constituents(0+size(dyn_const)+size(dyn_const_ice)))
         ! Pack the suite-level dynamic, run-time constituents array
         num_dyn_consts = 0
         do const_index = 1, size(dyn_const)
            cld_suite_dynamic_constituents(num_dyn_consts + const_index) = dyn_const(const_index)
         end do
         num_dyn_consts = num_dyn_consts + size(dyn_const)
         deallocate(dyn_const)
         do const_index = 1, size(cld_suite_dynamic_constituents)
            call cld_suite_dynamic_constituents(const_index)%standard_name(stdname,               &
                 errcode=errflg, errmsg=errmsg)
         end do
         do const_index = 1, size(dyn_const_ice)
            cld_suite_dynamic_constituents(num_dyn_consts + const_index) =                        &
                 dyn_const_ice(const_index)
         end do
         num_dyn_consts = num_dyn_consts + size(dyn_const_ice)
         deallocate(dyn_const_ice)
      else
         write(errmsg, '(3a)')"No suite named ", trim(suite_name), "found"
         errflg = 1
      end if

   end subroutine test_host_ccpp_physics_register
  1. No schemes have dynamic constituents:
subroutine test_host_ccpp_physics_register(suite_name, errmsg, errflg)

      use ccpp_ddt_suite_cap,  only: ddt_suite_register
      use ccpp_temp_suite_cap, only: temp_suite_register

      character(len=*)                         :: suite_name
      character(len=512)                       :: errmsg
      integer                                  :: errflg

      errflg = 0
      errmsg = ""
      if (trim(suite_name) == 'ddt_suite') then
         call ddt_suite_register(errflg=errflg, errmsg=errmsg)
         ! Suite does not return dynamic constituents; allocate to zero
         allocate(ddt_suite_dynamic_constituents(0))
      else if (trim(suite_name) == 'temp_suite') then
         call temp_suite_register(errflg=errflg, errmsg=errmsg, config_var=config_var)
         ! Suite does not return dynamic constituents; allocate to zero
         allocate(temp_suite_dynamic_constituents(0))
      else
         write(errmsg, '(3a)')"No suite named ", trim(suite_name), "found"
         errflg = 1
      end if

   end subroutine test_host_ccpp_physics_register

Misc notes

Since this phase is called before the grid is initialized, variables are not allocated at this time (that still happens in init) and no variables with horizontal and vertical dimensions can be passed in.

UI Changes

User interface changes?: Yes, but they're optional
If a host model wishes to utilize schemes' register phases, they must add a call to <host_model>_ccpp_physics_register(suite_name, errmsg, errflg)

Testing

test removed: removed unit tests for dyn_const_routines (old implementation of runtime constituent handling)
unit tests: Removed old dynamic constituents testing
system tests: Updated capgen and advection tests to include register phases (with and without dynamic constituents)
manual testing:

This update needs some additional testing and error handling (will implement during/after discussion), such as:

  • Test/handle when a scheme tries to output runtime constituents from a non-register phase
  • Test/handle when two schemes have the same standard name for their dynamic constituents?
  • Test/handle when scheme tries to use the wrong constituents DDT?

Fixes:
closes #572

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.

This is great work! Unfortunately, I don't have any way to test this, therefore relying on CI.

@@ -594,21 +576,6 @@ def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False):
# end for
# end for
# Check for duplicate dynamic constituent routine names
Copy link
Collaborator

Choose a reason for hiding this comment

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

this comment is no longer valid?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

indeed! removed.

@@ -555,6 +566,22 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env):
spart_args = spart.call_list.variable_list()
for sp_var in spart_args:
stdname = sp_var.get_prop_value('standard_name')
# Skip any dynamic constituent objects in register phases
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this comment is confusing, because the next block 570-584 does something ONLY for the constituents and ONLY in the register phase (and then bails out, i.e. skips whatever comes later).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch! that comment was outdated. Updated to:

# Special handling for run-time constituents in register phase

call_str = suite_part_call_list(host_model, const_dict, spart, False,
dyn_const=True)
stmt = "call {}_{}({})"
cap.write(stmt.format(suite.name, stage, call_str), 3)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why no f-string in line 700 and then remove line 699?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changed to f-string

self.call_list.add_variable(newvar, self.run_env,
exists_ok=exists_ok,
gen_unique=gen_unique,
adjust_intent=True)
# We need to make sure that this variable's dimensions are available
for vardim in newvar.get_dim_stdnames(include_constants=False):
if vardim == '':
Copy link
Collaborator

Choose a reason for hiding this comment

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

How can vardim be empty? This should be an error, or?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

vardim can be empty if the variable is allocatable. I added a check only allow this when the variable is allocatable and to otherwise throw an error

for vardim in newvar.get_dim_stdnames(include_constants=False):
    # Unnamed dimensions are ok for allocatable variables
    if vardim == '' and newvar.get_prop_value('allocatable'):
        continue
    elif vardim == '':
        emsg = f"{self.name}: Cannot have unnamed/empty string dimension"
        raise ParseInternalError(emsg.format(self.name,
                                             vardim, stdname))
    # end if
...

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! I'll leave the PR unapproved for now, since it is still in draft mode and I don't have any other tests than what is in CI. Once it is ready for review and others have tested it successfully, I'll approve.

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.

2 participants