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

Constituents #495

Merged
merged 13 commits into from
Nov 29, 2023
Merged

Constituents #495

merged 13 commits into from
Nov 29, 2023

Conversation

peverwhee
Copy link
Collaborator

Summary

Updates to bring in constituents object and constituent properties object.

Description

  • Meat of the updates are in src/ccpp_constituent_prop_mod.F90
  • Add interfaces to host cap to access constituent and const props objects
  • Add metadata properties specific to constituents (currently advected=True is the only way to note a constituent
  • Adds ddt object, support, and testing to achieve above

User interface changes?: Yes
Can now optionally add constituent via metadata property advected=True

Addresses #282

Testing:

  • Ran with CAM-SIMA (SE dycore and KESSLER physics)
  • All tests pass except unit conversion test

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 @gold2718 Looks fine to me. Thanks for this monster effort. I don't have anything that would hold up this PR from being merged, assuming the new tests all work as expected.

I'm a bit confused about the need for metadata for fields within the DDTs, but maybe this is because some DDTs have column data that may need to be referenced outside of their home DDT?

src/ccpp_constituent_prop_mod.meta Show resolved Hide resolved
test/unit_tests/sample_host_files/ddt1.meta Show resolved Hide resolved
@dustinswales
Copy link
Collaborator

@peverwhee Can you update this branch with updates from feature/capgen?

Copy link
Collaborator

@gold2718 gold2718 left a comment

Choose a reason for hiding this comment

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

Yowza, that is a lot to digest!
I have some question and a few suggested changes but overall, I think this is good.

scripts/ccpp_capgen.py Outdated Show resolved Hide resolved
scripts/host_model.py Show resolved Hide resolved
scripts/ccpp_suite.py Outdated Show resolved Hide resolved
src/ccpp_constituent_prop_mod.meta Show resolved Hide resolved
test/advection_test/test_host_data.F90 Show resolved Hide resolved
test/unit_tests/sample_host_files/ddt1.meta Show resolved Hide resolved
test/unit_tests/sample_host_files/ddt2.meta Show resolved Hide resolved
Copy link
Collaborator

@mkavulich mkavulich left a comment

Choose a reason for hiding this comment

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

I'll add these as comments since I don't understand enough of the big picture to call this a proper "review".

@@ -563,29 +563,39 @@ def parse_preamble_data(statements, pobj, spec_name, endmatch, run_env):
module=spec_name,
var_dict=var_dict)
mheaders.append(mheader)
if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG):
Copy link
Collaborator

Choose a reason for hiding this comment

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

This might be outside the scope of this PR specifically, but It seems like this same debug logic is used a lot in capgen, does it make sense to pull this out into a function so it can be a single line every time?

Copy link
Collaborator Author

@peverwhee peverwhee Nov 1, 2023

Choose a reason for hiding this comment

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

Added new function debug_on() routine to CCPPFrameworkEnv

ctx = context_string(pobj, nodir=True)
msg = 'Adding header {}{}'
run_env.logger.debug(msg.format(mheader.table_name, ctx))
# end if
Copy link
Collaborator

Choose a reason for hiding this comment

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

I understand the need for helper comments like this for very long or convoluted logic blocks, but here (and other if blocks that are only a few lines) it seems superfluous.

I see that this is already present in many places so probably doesn't need to be addressed in this PR, but I thought I would throw it out there.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would rather have consistent usage and have been saved more than once by these statements showing me where some code block was incorrectly indented.
This particular choice is part of the style guide of some projects I work on. Others use a blank line at the end of every block. I do not know any other project that has different rules for different sized blocks.

Copy link
Collaborator

Choose a reason for hiding this comment

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

My personal preference would be blank lines, but since this project has started out with # end if (lowercase nowadays, for consistency) it makes sense to continue using it. I shall add those missing statements in my PR #512 later!

Comment on lines 313 to 317
init_args = [f'std_name="{std_name}"', f'long_name="{long_name}"',
f'units="{units}"', f'vertical_dim="{vertical_dim}"',
f'advected={advect_str}',
f'errcode={errvar_names["ccpp_error_code"]}',
f'errmsg={errvar_names["ccpp_error_message"]}']
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm curious what our new minimum python version is? If we require Python 3.8 or newer then this and similar expressions can be greatly simplified

Suggested change
init_args = [f'std_name="{std_name}"', f'long_name="{long_name}"',
f'units="{units}"', f'vertical_dim="{vertical_dim}"',
f'advected={advect_str}',
f'errcode={errvar_names["ccpp_error_code"]}',
f'errmsg={errvar_names["ccpp_error_message"]}']
init_args = [f'{std_name=}', f'{long_name=}',
f'{units=}', f'{vertical_dim=}',
f'{advect_str=}',
f'errcode={errvar_names["ccpp_error_code"]}',
f'errmsg={errvar_names["ccpp_error_message"]}']

Copy link
Collaborator

Choose a reason for hiding this comment

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

Well that is a very interesting feature I had not noticed creeping in. Very Fortran friendly :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

BTW, I think line 315 has to stay as is. It is a Python string but a Fortran literal.

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 new fancy python 3.8 version!

astat = 1
call set_errvars(astat, subname, &
errcode=errcode, errmsg=errmsg, &
errmsg2=" ERROR: num_advected_vars index out of bounds")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this error message also print num_vars and the current index?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yep! done

if (check) then
index_advect = index_advect + 1
if (index_advect > this%num_advected_vars) then
call set_errvars(1, subname, &
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here in these two blocks, print the OOB index and the limit it has exceeded

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yep! done

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.

Great work @peverwhee!! Just wanted to clarify a few things.

scripts/constituents.py Outdated Show resolved Hide resolved
scripts/fortran_tools/fortran_write.py Outdated Show resolved Hide resolved
scripts/host_model.py Show resolved Hide resolved
scripts/host_model.py Outdated Show resolved Hide resolved
Comment on lines 795 to 796
else:
element_names = None
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 else needed since we don't enter the if block that assigns element_names=[]?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

agreed. removed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But if element_names is not given a value, what is returned at the end?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good point. At the end of the function if element_names is not defined, python throws an exception.

Would a better suggestion have been to set element_names = None by default before starting the if checks?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't know if it is "better" but it is sufficient. The nice thing about raising exceptions in logic corners that should never be hit is that you can find out if future coding ever takes this routine out of its design range.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

thanks for this discussion, guys. I thoroughly confused myself and will blame the recursion (it definitely isn't a me problem!). Updated to initialize element_names to None at the top of the routine and then also raise an exception if we get to the else of if ddt_lib and (dtitle in ddt_lib):

scripts/ccpp_suite.py Outdated Show resolved Hide resolved
scripts/ddt_library.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_model.py Show resolved Hide resolved
scripts/ddt_library.py Outdated Show resolved Hide resolved
scripts/fortran_tools/parse_fortran_file.py Outdated Show resolved Hide resolved
scripts/metavar.py Show resolved Hide resolved
scripts/metavar.py Show resolved Hide resolved
Comment on lines 795 to 796
else:
element_names = None
Copy link
Collaborator

Choose a reason for hiding this comment

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

But if element_names is not given a value, what is returned at the end?

Copy link
Collaborator

@gold2718 gold2718 left a comment

Choose a reason for hiding this comment

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

Finished a review of ac18c44 and resolved all the conversations I started. However, I still see a few things I think should be changed.


if (associated(this%prop)) then
call this%prop%vertical_dimension(dimname)
is_2d = len_trim(dimname) == 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

For CAM at least, 2-D means there is no vertical dimension so there is no name for it. It might be cleaner and more understandable if a is_2d method was added to the prop obect.

src/ccpp_constituent_prop_mod.F90 Outdated Show resolved Hide resolved
src/ccpp_constituent_prop_mod.F90 Outdated Show resolved Hide resolved
src/ccpp_constituent_prop_mod.F90 Outdated Show resolved Hide resolved
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 a very high-level review. I was not looking at every single of the 10000-ish lines in the modified/added constituent code, but rather trying to get a bit of an understanding and look at things I know about (and care about more ...).

I am a little concerned about how complicated and extensive this code is, but as long as none of this is mandatory and host models can use capgen without the constituents logic (not saying the should, but at least for transitioning from prebuild to capgen that would be good) then that's all ok.

My only real concern is the newly added debug_on feature, which is inconsistent with how the arguments to capgen and prebuild are called and therefore should be renamed to just verbose. I started suggesting those changes until I got tired, and I certainly overlooked a few.

@@ -275,6 +276,11 @@ def kind_types(self):
CCPPFrameworkEnv object."""
return self.__kind_dict.keys()

def debug_on(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should not be debug_on but simply verbose to match the flag that gets passed to capgen, and also to avoid confusion with what the --debug switch for capgen is doing (see PR #512)

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 call! Changed to a verbose property

@@ -427,7 +427,7 @@ def analyze(self, host_model, scheme_library, ddt_library, run_env):
phase = RUN_PHASE_NAME
# end if
lmsg = "Group {}, schemes = {}"
if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG):
if run_env.debug_on():
Copy link
Collaborator

@climbfuji climbfuji Nov 9, 2023

Choose a reason for hiding this comment

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

See below, should be just run_env.verbose() or even better a property run_env.verbose. This way it's consistent with #512 where I added run_env.debug.

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!

@@ -490,7 +490,7 @@ def write(self, output_dir, run_env):
(calling the group caps one after another)"""
# Set name of module and filename of cap
filename = '{module_name}.F90'.format(module_name=self.module)
if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG):
if run_env.debug_on():
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ditto

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!

cap.write("", 0)
cap.write("! Dummy arguments", 2)
cap.comment("Update constituent field info from <const_array>", 2)
cap.blank_line()
Copy link
Collaborator

Choose a reason for hiding this comment

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

These (new?) additions blank_line and comment are useful!

if ((not recur) and
run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG)):
run_env.debug_on()):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
run_env.debug_on()):
run_env.verbose()):

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!

@@ -1271,7 +1287,7 @@ def __init__(self, index_name, context, parent, run_env, items=None):
# self._local_dim_name is the variable name for self._dim_name
self._local_dim_name = None
super().__init__(index_name, context, parent, run_env)
if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG):
if run_env.debug_on():
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if run_env.debug_on():
if run_env.verbose():

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!

end if

end function ccp_is_initialized
end function ccp_is_instantiated
Copy link
Collaborator

Choose a reason for hiding this comment

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

I have a feeling that I asked this question 2 +/- years ago when the constituents where added originally. What does ccp and ccpt stand for? Should we be concerned about confusion with ccpp that also shows up as acronym in the same part of the code? (This is just for my curiosity or for the future, not really related to this PR)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, this is just me trying to save space with recursive acronyms (hey, I remember the first infinintely-recursive acronym I ran across, GNU, back when it was still pretty new in 1984).
I figured they are harmless because they are all defined within a type with a well defined name so it should be clear what they are for. However, for the record:
CCP stands for Ccpp_Constituent_Properties_t
and
CCPT stands for Ccpp_Constituent_prop_Ptr_T

These acronyms should not show up outside this module (can they even be called from outside?).


if (associated(this%prop)) then
call this%prop%vertical_dimension(dimname)
is_2d = len_trim(dimname) == 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

Another point: While many models move to one horizontal dimension and one vertical dimension, we all have a basic understanding that 2d is something on a surface and 3d something in a volume, therefore this makes sense.

test/advection_test/test_host.F90 Show resolved Hide resolved
@gold2718
Copy link
Collaborator

My only real concern is the newly added debug_on feature

I second this. I think the name probably came from the name of the logging level but it is certainly verbose!

Copy link
Collaborator

@gold2718 gold2718 left a comment

Choose a reason for hiding this comment

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

9db9477 looks good, thanks!

@climbfuji
Copy link
Collaborator

@peverwhee We were wondering in our tag up today when/if you were planning to make the debug_on() --> verbose() change so that we can approve and merge?

@peverwhee
Copy link
Collaborator Author

@climbfuji Yes! I will try to finish up addressing the remaining comments tomorrow.

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 making the debug_on -> verbose change!

@peverwhee peverwhee merged commit e86d0a7 into NCAR:feature/capgen Nov 29, 2023
9 checks passed
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.

6 participants