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

Add baroclinic gyre tests #547

Merged
merged 39 commits into from
Aug 17, 2024
Merged

Conversation

alicebarthel
Copy link
Contributor

@alicebarthel alicebarthel commented Feb 28, 2023

This PR aims to add the MITgcm baroclinic test case to compass. Started as a Work In Progress [WIP] PR to allow discussion and debugging before merging.

Checklist

  • User's Guide has been updated
  • Developer's Guide has been updated
  • API documentation in the Developer's Guide (api.rst) has any new or modified class, method and/or functions listed
  • Documentation has been built locally and changes look as expected
  • Document (in a comment titled Testing in this PR) any testing that was used to verify the changes

@alicebarthel alicebarthel added enhancement New feature or request ocean in progress This PR is not ready for review or merging labels Feb 28, 2023
@alicebarthel alicebarthel force-pushed the add-mitgcm-baroclinic-gyre branch 2 times, most recently from 4ccb04f to a3baa9c Compare March 7, 2023 17:35
@xylar
Copy link
Collaborator

xylar commented Mar 7, 2023

Nice! All tests passing!

compass/ocean/__init__.py Outdated Show resolved Hide resolved
"""
super().__init__(mpas_core=mpas_core, name='mitgcm_baroclinic_gyre')

for resolution in ['80km']:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider adding a comment that resolutions here must be added with the 'km' suffix (since later you chop off the last 2 characters to get the float)

Copy link
Collaborator

Choose a reason for hiding this comment

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

An alternative would be to provide the resolution as a float and add the km suffix to the subdirectory. I think that might be cleaner. (I know we were following ziso, so that one should probably be fixed in a similar way.)

Copy link
Collaborator

Choose a reason for hiding this comment

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

By the way, I have something along these lines here, where the resolution is always given in meters

if resolution >= 1e3:
res_name = f'{int(resolution/1e3)}km'
else:
res_name = f'{int(resolution)}m'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cbegeman Working on this resolution change now. A few questions/suggestions:

  1. Are you satisfied with resolution in meters by default? It makes sense for turbulence closure since we go to fine resolution. Is it best to be consistent across cases? Happy to switch mine, just wanted to check.
  2. the comment blocks in your forward (L12, L25) and initial_state (L22, L34) still carry comments where resolution is expected to be string, though it is the float that is passed (as far as I understand). It may be good to propagate the update.
  3. The run in forward still assumes a string resolution with units. I couldn't find a call for it so maybe it's just stale code, but worth updating in case it ever get exercised.

Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. If the resolution variable is only present in your case-specific python code (e.g., not in the config file), I think you can use whatever units are convenient for your cases. We care more about standardizing dc (in config files) and the resolution directory name across cases.
  2. Thanks for pointing this out. I'm not actively developing that branch (and case) any more because we didn't have success validating mpas_les. If I return to it, I'll fix this up.
  3. Same as (2)

bottT = temperature[0, 200, -1].values
surfT = temperature[0, 200, 0].values
print(f'bottom T: {bottT} and surface : {surfT}')
salinity = 34.0 * xr.ones_like(temperature)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider making certain initial temperature and salinity parameters a part of the mitgcm_baroclinic_gyre section in your config file

Copy link
Collaborator

Choose a reason for hiding this comment

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

The initial temperature is a function of depth. @alicebarthel has constructed a function that's a pretty good approximation of the table of numbers used in the MITgcm version of the test case. It's full of "magic" numbers so they wouldn't be super easy to add to the config file. But maybe there would be a way to give the top (z=0) and bottom (z=bottom_depth) temperature and have them vary between those two with your prescribed functional form? That would seem cleaner to me.

I wouldn't hardcode sampling bottT and surfT at the 200th grid cell here. Either you should take grid cell 0 or you'll want to have a number somehow based on nCells. We don't necessarily know that the mesh will have a 200th grid cell (though it's obviously highly likely) since we're flexible about the resolution.

Regarding the initial salinity, I agree for sure that that needs to be a config option rather than a magic number here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point @xylar re. cell 200. @cbegeman I like the idea of it being more general (setting T in cfg) but I have not come up with a satisfying way to do that at this stage. For the original purpose of matching MITgcm, hard-coding the structure was easiest. It can extend to a deeper set-up, but approximates their values over 0-1800m. I'll look to see about setting it from top and bottom T keeping the existing functional form. We could do a prettier functional form with, say, a thermocline depth parameter setting the change in T=f(z) shape, but I have not looked into it that closely.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@alicebarthel, couldn't you have config options that are the temperatures in the middle of the top and bottom layers respectively? Then, couldn't you take your existing functional form but stretch it linearly so it hits the desired top and bottom temperatures?

Alternatively, if you want to be very particular about having the same temperature profile independent of resolution, couldn't you do the same but instead of the middle of the top and bottom layers, you would use z = 0 and z=1800 m. The default values would be chosen such that the temperature in the middle of the top and bottom layers is quite close to the value you currently get with your functional form.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved the constant salinity value to the cfg file. The initial temperature profile is now set (still with a "magical" functional form and a fitted parameter) from the top and bottom temperature set at the surface (z=0m) and bottom (z = bottom_depth). The default values approximate the mitgcm stratification, but we could add a table to reach to be more exact.

@cbegeman
Copy link
Collaborator

cbegeman commented Mar 7, 2023

@alicebarthel I think this is coming together really nicely! I left only minor comments at this point.

@alicebarthel
Copy link
Contributor Author

Thanks @cbegeman and @xylar for your comments so far!

@xylar xylar self-assigned this Mar 29, 2023
@alicebarthel
Copy link
Contributor Author

alicebarthel commented Mar 28, 2024

I made some offline plots for sanity checks on the initial and forcing files. The more useful ones are these 3:
initial_temperature_fz
forcing_windStressZonal(1)
forcing_temperatureSurfaceRestoringValue

They contain numbers (mean, std) on the other variables to flag if something is modified in other fields.

@alicebarthel
Copy link
Contributor Author

alicebarthel commented Mar 29, 2024

I made some (offline) plots to check the simulation state -- as a sanity check. They still need minor adjustments (e.g. I am unsure about the surface heat flux calculation) but are promising. At this stage, the "mean" surface state was calculated from 30-day snapshots over year 3 (the last year of the 3_year_test).
baroclinicgyre_80km_meansurfacestate_yr3
I plotted a spin-up plot up to year 33. As expected, 3 years is far from enough for the spin-up (the MITgcm example show year 100).
baroclinicgyre_80km_KEft_toyr33
I'll update when I have later results.

Copy link
Collaborator

@xylar xylar left a comment

Choose a reason for hiding this comment

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

I had some issues mainly with the docs. A few things to clean up there.

compass/ocean/tests/baroclinic_gyre/forward.py Outdated Show resolved Hide resolved
compass/ocean/tests/baroclinic_gyre/forward.py Outdated Show resolved Hide resolved
docs/developers_guide/ocean/api.rst Outdated Show resolved Hide resolved
docs/users_guide/ocean/test_groups/baroclinic_gyre.rst Outdated Show resolved Hide resolved
docs/developers_guide/ocean/api.rst Outdated Show resolved Hide resolved
docs/users_guide/ocean/test_groups/baroclinic_gyre.rst Outdated Show resolved Hide resolved
docs/users_guide/ocean/test_groups/baroclinic_gyre.rst Outdated Show resolved Hide resolved
docs/users_guide/ocean/test_groups/baroclinic_gyre.rst Outdated Show resolved Hide resolved
@xylar
Copy link
Collaborator

xylar commented Mar 29, 2024

I successfully ran the 80km/performance_test, 80km/3_year_test and 20km/performance_test. I didn't try to run the 20km/3_year_test.

@xylar
Copy link
Collaborator

xylar commented Mar 29, 2024

@alicebarthel, the outputs you posted above look great to me! I think we're almost there...

@alicebarthel
Copy link
Contributor Author

Thanks @xylar, that's super helpful. Attached are updated mean state plots for year 100 of the 80km. It looks nicely spun-up to me, at least dynamically:
baroclinicgyre_80km_KEft_toyr100
baroclinicgyre_80km_meansurfacestate_yr100

@cbegeman
Copy link
Collaborator

cbegeman commented Apr 1, 2024

@alicebarthel This is awesome progress! Is this ready for review? Are you planning on adding a visualization or analysis step to aid comparison with MITgcm https://mitgcm.readthedocs.io/en/latest/examples/baroclinic_gyre/baroclinic_gyre.html#model-solution? The heat flux figure you posted looks like a good comparison with Fig 2.7. Do you think we want the analogs to Figs 4.8 or 4.9?

@alicebarthel
Copy link
Contributor Author

Thanks @cbegeman! For now, all the analysis is offline. Yes, there are a few more plots I want to make, including Figure 4.9b. Figure 4.8 may be good too - although at this stage the SSH contour looks good enough for me. Adding the barotropic streamfunction could be nice but feels lower priority for me.
A question for you two:
3 years is not enough for a full spin-up. The plots for MITgcm were made for year 100.

  1. Does it make sense to keep the 3_year_test even if it does not bring things to equilibrium? The expectation is for users to run it further as needed. For 80km, it looks like ~50 years is needed.
  2. Does it make sense to add an analysis/visualization step if the default run is not usually spun-up?

At this stage, I am focusing on:

  • Testing/debugging the restart capabilities. I have had issues but I suspect this could be a simple storage issue at this stage.
  • enhancing the offline analysis to increase our confidence of the spun-up state (including at 20-km resolution) and include a readily available AMOC estimate.

I welcome comments and suggestions as I work on this.

@cbegeman
Copy link
Collaborator

cbegeman commented Apr 1, 2024

Does it make sense to keep the 3_year_test even if it does not bring things to equilibrium? The expectation is for users to run it further as needed. For 80km, it looks like ~50 years is needed.
Does it make sense to add an analysis/visualization step if the default run is not usually spun-up?

How about keeping the long test and having analysis/viz steps included in that test (and plot the last time slice in the output) but not include these steps in the performance test? I think it's reasonable to have the users change the duration. I didn't read through the docs yet, but let's make sure that ~50 year duration is recommended there.

Comment on lines 115 to 119
if self.name == '3_year_test':
replacements = {'config_do_restart': '.true.',
'config_start_time': "'file'"}
self.update_namelist_at_runtime(replacements)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't working because the name of the step is always forward, not 3_year_test. One fix is:

Suggested change
if self.name == '3_year_test':
replacements = {'config_do_restart': '.true.',
'config_start_time': "'file'"}
self.update_namelist_at_runtime(replacements)
if self.test_case.name == '3_year_test':
replacements = {'config_do_restart': '.true.',
'config_start_time': "'file'"}
self.update_namelist_at_runtime(replacements)

Or I think it might be more logical to store the long argument as an attribute and use it here:

Suggested change
if self.name == '3_year_test':
replacements = {'config_do_restart': '.true.',
'config_start_time': "'file'"}
self.update_namelist_at_runtime(replacements)
if self.long:
replacements = {'config_do_restart': '.true.',
'config_start_time': "'file'"}
self.update_namelist_at_runtime(replacements)

Comment on lines 80 to 81
print(f'analytical bottom T: {val_bot} at \
depth : {bottom_depth}')
Copy link
Collaborator

@xylar xylar Aug 12, 2024

Choose a reason for hiding this comment

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

Here and in similar places, this is the preferred way to continue an f-string on a subsequent line:

Suggested change
print(f'analytical bottom T: {val_bot} at \
depth : {bottom_depth}')
print(f'analytical bottom T: {val_bot} at '
f'depth : {bottom_depth}')

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @xylar , I modified these. Let me know if I missed others.

@alicebarthel
Copy link
Contributor Author

@xylar @cbegeman This is ready for a review. I ran the performance and 3_year tests on Chrysalis without issue. I also ran the 3_year test for longer (50years, which takes ~2hours for 80km set-up).
Thanks @xylar for working with me on the heat fluxes, this looks much better!
Results after 3 years:
meansurfacestate_last1years
spinup_ft
Results after 50 years (average over last 10years):
meansurfacestate_last10years
spinup_ft
time_avg_moc_last10years

@xylar
Copy link
Collaborator

xylar commented Aug 14, 2024

@alicebarthel, that looks awesome! I'm glad we got the surface restoring worked out.

I'll review it tomorrow.

@xylar xylar changed the title Add mitgcm baroclinic gyre - WIP Add baroclinic gyre tests Aug 15, 2024
@xylar xylar removed the in progress This PR is not ready for review or merging label Aug 15, 2024
Copy link
Collaborator

@xylar xylar left a comment

Choose a reason for hiding this comment

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

@alicebarthel, this is some amazing work! It's very nearly there!

I was able to run the 80km tests without a hitch! I'm running the 20 km tests now. In the meantime, I have several small suggestions that you could make, or we could discuss further.

compass/ocean/tests/baroclinic_gyre/initial_state.py Outdated Show resolved Hide resolved
compass/ocean/tests/baroclinic_gyre/initial_state.py Outdated Show resolved Hide resolved
Comment on lines 94 to 103
BaroclinicGyre

GyreTestCase
GyreTestCase.configure

forward.Forward
forward.Forward.run

initial_state.InitialState
initial_state.InitialState.run
Copy link
Collaborator

Choose a reason for hiding this comment

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

The other classes should be added here as well (CullMesh, Moc and Viz).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Is this supposed to be alphabetical or does the order matter?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, alphabetical is easiest.

At this stage, the test case is available at 80-km and 20-km horizontal
resolution. By default, the 15 vertical layers vary linearly in thickness with depth, from 50m at the surface to 190m at depth (full depth: 1800m).

The test group includes 2 test cases, called ``performance_test`` for a short (10-day) run, and ``3_year_test`` for a 3-year simulation. Note that 3 years are insufficient to bring the standard test case to full equilibrium. Both test cases have 2 steps,
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
The test group includes 2 test cases, called ``performance_test`` for a short (10-day) run, and ``3_year_test`` for a 3-year simulation. Note that 3 years are insufficient to bring the standard test case to full equilibrium. Both test cases have 2 steps,
The test group includes 2 test cases, called ``performance_test`` for a short (3-time-step) run, and ``3_year_test`` for a 3-year simulation. Note that 3 years are insufficient to bring the standard test case to full equilibrium. Both test cases have 2 steps,

Comment on lines +13 to +14
Framework
---------
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems like cull_mesh, moc and viz need to be added to this.

Comment on lines 64 to 68
maxloc = 'at lat = {} and z = {}m'.format(
latbins[idx[-1]].values, int(dsMesh.refInterfaces[idx[0]].values))
maxval = 'max MOC = {:.1e} at lat={}-{}'.format(
round(np.max(moc[:, 175]), 1),
latbins[175 - 1].values, latbins[175].values)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ideally, break this into several lines where you store these various inputs in variables and then use f-strings rather than the .format() methods to put them in place.

compass/ocean/tests/baroclinic_gyre/viz.py Outdated Show resolved Hide resolved
compass/ocean/tests/baroclinic_gyre/viz.py Outdated Show resolved Hide resolved
compass/ocean/tests/baroclinic_gyre/viz.py Outdated Show resolved Hide resolved
compass/ocean/tests/baroclinic_gyre/viz.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@cbegeman cbegeman left a comment

Choose a reason for hiding this comment

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

I think this looks great! I just visually inspected the code. Would you like me to run tests on any particular machine or build docs?

# Number of vertical levels
vert_levels = 15

# Total water column depth
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
# Total water column depth
# Total water column depth in m

# the type of vertical grid
grid_type = linear_dz

# the linear rate of thickness increase for linear_dz
Copy link
Collaborator

Choose a reason for hiding this comment

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

units?

restoring_temp_timescale = 30.

# config options for the mean state visualization
[mean_state_viz]
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
[mean_state_viz]
[baroclinic_gyre_viz]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I modified this to include both viz and moc calculation parameters.

compass/ocean/tests/baroclinic_gyre/cull_mesh.py Outdated Show resolved Hide resolved
compass/ocean/tests/baroclinic_gyre/initial_state.py Outdated Show resolved Hide resolved
compass/ocean/tests/baroclinic_gyre/initial_state.py Outdated Show resolved Hide resolved
:py:class:`compass.mesh.QuasiUniformSphericalMeshStep`. Then, the mesh is
culled to keep a single ocean basin (with lat, lon bounds set in `.cfg` file). A vertical grid is generated,
with 15 layers of thickness that increases linearly with depth (10m increase by default), from 50m at the surface to 190m at depth (full depth: 1800m).
Finally, the initial temperature field is initialized with a vertical profile to approximate the discrete values set in the `MITgcm test case <https://mitgcm.readthedocs.io/en/latest/examples/baroclinic_gyre/baroclinic_gyre.html>`_, uniform in horizontal space. The surface and bottom values are set in the `.cfg` file. Salinity is set to a constant value (34 psu, set in the `.cfg` file) and initial
Copy link
Collaborator

Choose a reason for hiding this comment

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

Break up into multiple lines here and elsewhere. I think max length should be 89 char.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, 79 characters actually. The width of a default terminal is 80, so that's the reasoning.

@@ -12,6 +12,7 @@ coming months.
:titlesonly:

baroclinic_channel
baroclinic_gyre
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xylar Is this what you meant in your review of the users_guide above?

Copy link
Collaborator

@xylar xylar 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! Thanks for making the requested changes.

@xylar xylar merged commit 12f956f into MPAS-Dev:main Aug 17, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request ocean
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants