From 3f92081da77d2982616f673771b6f0f613160ec1 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Tue, 6 Dec 2022 09:18:34 -0600 Subject: [PATCH 01/20] Create overflow test group --- compass/ocean/__init__.py | 2 ++ compass/ocean/tests/overflow/__init__.py | 16 ++++++++++++++++ .../ocean/tests/overflow/default/__init__.py | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 compass/ocean/tests/overflow/__init__.py create mode 100644 compass/ocean/tests/overflow/default/__init__.py diff --git a/compass/ocean/__init__.py b/compass/ocean/__init__.py index 3bc4d2bf8e..3c50d5125e 100644 --- a/compass/ocean/__init__.py +++ b/compass/ocean/__init__.py @@ -11,6 +11,7 @@ from compass.ocean.tests.isomip_plus import IsomipPlus from compass.ocean.tests.merry_go_round import MerryGoRound from compass.ocean.tests.nonhydro import Nonhydro +from compass.ocean.tests.overflow import Overflow from compass.ocean.tests.planar_convergence import PlanarConvergence from compass.ocean.tests.soma import Soma from compass.ocean.tests.sphere_transport import SphereTransport @@ -43,6 +44,7 @@ def __init__(self): self.add_test_group(IsomipPlus(mpas_core=self)) self.add_test_group(MerryGoRound(mpas_core=self)) self.add_test_group(Nonhydro(mpas_core=self)) + self.add_test_group(Overflow(mpas_core=self)) self.add_test_group(PlanarConvergence(mpas_core=self)) self.add_test_group(Soma(mpas_core=self)) self.add_test_group(SphereTransport(mpas_core=self)) diff --git a/compass/ocean/tests/overflow/__init__.py b/compass/ocean/tests/overflow/__init__.py new file mode 100644 index 0000000000..be3b76d6da --- /dev/null +++ b/compass/ocean/tests/overflow/__init__.py @@ -0,0 +1,16 @@ +from compass.testgroup import TestGroup +from compass.ocean.tests.overflow.default import Default + +class Overflow(TestGroup): + """ + A test group for General Ocean Turbulence Model (GOTM) test cases + """ + + def __init__(self, mpas_core): + """ + mpas_core : compass.MpasCore + the MPAS core that this test group belongs to + """ + super().__init__(mpas_core=mpas_core, name='overflow') + + self.add_test_case(Default(test_group=self)) diff --git a/compass/ocean/tests/overflow/default/__init__.py b/compass/ocean/tests/overflow/default/__init__.py new file mode 100644 index 0000000000..0fb8036bc6 --- /dev/null +++ b/compass/ocean/tests/overflow/default/__init__.py @@ -0,0 +1,18 @@ +from compass.testcase import TestCase + + +class Default(TestCase): + """ + The default test case for the overflow test + """ + + def __init__(self, test_group): + """ + Create the test case + + Parameters + ---------- + test_group : compass.ocean.tests.overflow.Overflow + The test group that this test case belongs to + """ + super().__init__(test_group=test_group, name='default') From 6173505de9af42c81e66581c523ce806e5ab1d90 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Tue, 6 Dec 2022 15:44:05 -0600 Subject: [PATCH 02/20] Add namelists --- compass/ocean/tests/overflow/namelist.forward | 7 +++++++ compass/ocean/tests/overflow/namelist.init | 4 ++++ 2 files changed, 11 insertions(+) create mode 100644 compass/ocean/tests/overflow/namelist.forward create mode 100644 compass/ocean/tests/overflow/namelist.init diff --git a/compass/ocean/tests/overflow/namelist.forward b/compass/ocean/tests/overflow/namelist.forward new file mode 100644 index 0000000000..acf8304364 --- /dev/null +++ b/compass/ocean/tests/overflow/namelist.forward @@ -0,0 +1,7 @@ +config_dt='0000_00:03:00' +config_btr_dt='0000_00:00:09' +config_run_duration='0000_00:12:00' +config_use_cvmix_convection=.true. +config_implicit_bottom_drag_coeff=1.0e-2 +config_use_mom_del2=.true. +config_mom_del2=1.0e3 diff --git a/compass/ocean/tests/overflow/namelist.init b/compass/ocean/tests/overflow/namelist.init new file mode 100644 index 0000000000..0cdcbfff78 --- /dev/null +++ b/compass/ocean/tests/overflow/namelist.init @@ -0,0 +1,4 @@ +config_init_configuration='overflow' +config_overflow_use_distances=.true. +config_overflow_layer_type='sigma' +config_overflow_vert_levels=50 From eadb1df6484758966c210371083e4de20740f2b7 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Tue, 6 Dec 2022 15:44:19 -0600 Subject: [PATCH 03/20] Add initial state --- .../ocean/tests/overflow/default/__init__.py | 2 + compass/ocean/tests/overflow/initial_state.py | 85 +++++++++++++++++++ compass/ocean/tests/overflow/overflow.cfg | 16 ++++ 3 files changed, 103 insertions(+) create mode 100644 compass/ocean/tests/overflow/initial_state.py create mode 100644 compass/ocean/tests/overflow/overflow.cfg diff --git a/compass/ocean/tests/overflow/default/__init__.py b/compass/ocean/tests/overflow/default/__init__.py index 0fb8036bc6..e90d3b900c 100644 --- a/compass/ocean/tests/overflow/default/__init__.py +++ b/compass/ocean/tests/overflow/default/__init__.py @@ -1,4 +1,5 @@ from compass.testcase import TestCase +from compass.ocean.tests.overflow.initial_state import InitialState class Default(TestCase): @@ -16,3 +17,4 @@ def __init__(self, test_group): The test group that this test case belongs to """ super().__init__(test_group=test_group, name='default') + self.add_step(InitialState(test_case=self)) diff --git a/compass/ocean/tests/overflow/initial_state.py b/compass/ocean/tests/overflow/initial_state.py new file mode 100644 index 0000000000..ea12d9e168 --- /dev/null +++ b/compass/ocean/tests/overflow/initial_state.py @@ -0,0 +1,85 @@ +import xarray +import numpy +import matplotlib.pyplot as plt + +from mpas_tools.planar_hex import make_planar_hex_mesh +from mpas_tools.io import write_netcdf +from mpas_tools.mesh.conversion import convert, cull + +from compass.step import Step +from compass.model import run_model + + +class InitialState(Step): + """ + A step for creating a mesh and initial condition for overflow test + cases + """ + def __init__(self, test_case): + """ + Create the step + + Parameters + ---------- + test_case : compass.ocean.tests.overflow.default.Default + The test case this step belongs to + """ + super().__init__(test_case=test_case, name='initial_state', ntasks=1, + min_tasks=1, openmp_threads=1) + + self.add_namelist_file('compass.ocean.tests.overflow', + 'namelist.init', mode='init') + + self.add_streams_file('compass.ocean.tests.overflow', + 'streams.init', mode='init') + + for file in ['base_mesh.nc', 'culled_mesh.nc', 'culled_graph.info', + 'ocean.nc']: + self.add_output_file(file) + + self.add_model_as_input() + + def run(self): + """ + Run this step of the test case + """ + config = self.config + logger = self.logger + + section = config['overflow'] + nx = section.getint('nx') + ny = section.getint('ny') + dc = section.getfloat('dc') + + self.update_namelist_at_runtime( + {'config_overflow_dc': f'{dc}'}) + + logger.info(' * Make planar hex mesh') + dsMesh = make_planar_hex_mesh(nx=nx, ny=ny, dc=dc, nonperiodic_x=True, + nonperiodic_y=True) + logger.info(' * Completed Make planar hex mesh') + write_netcdf(dsMesh, 'base_mesh.nc') + + logger.info(' * Cull mesh boundaries') + dsMesh = cull(dsMesh, logger=logger) + logger.info(' * Convert mesh') + dsMesh = convert(dsMesh, graphInfoFileName='culled_graph.info', + logger=logger) + logger.info(' * Completed Convert mesh') + write_netcdf(dsMesh, 'culled_mesh.nc') + + run_model(self, namelist='namelist.ocean', + streams='streams.ocean') + + postrun_data = xarray.open_dataset('ocean.nc') + logger.info(' * Cull mesh dam boundaries') + postrun_data_cull = cull(postrun_data, logger=logger) + logger.info(' * Convert mesh') + postrun_data_cull = convert(postrun_data_cull, + graphInfoFileName='culled_graph.info', + logger=logger) + write_netcdf(postrun_data_cull, 'culled_mesh.nc') + + run_model(self, namelist='namelist.ocean', + streams='streams.ocean') + diff --git a/compass/ocean/tests/overflow/overflow.cfg b/compass/ocean/tests/overflow/overflow.cfg new file mode 100644 index 0000000000..d8fd500026 --- /dev/null +++ b/compass/ocean/tests/overflow/overflow.cfg @@ -0,0 +1,16 @@ +# Options related to the vertical grid +[vertical_grid] + +# Number of vertical levels +vert_levels = 1 + +# Options related to the overflow case +[overflow] + +# the number of grid cells in x and y +nx = 4 +ny = 24 + +# the size of grid cells (m) +dc = 10000.0 + From 94bbb3df428b5585ab3350ce27df824ba54aa8c3 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Tue, 6 Dec 2022 18:09:30 -0600 Subject: [PATCH 04/20] Add streams --- compass/ocean/tests/overflow/streams.forward | 23 +++++++++++++++ compass/ocean/tests/overflow/streams.init | 30 ++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 compass/ocean/tests/overflow/streams.forward create mode 100644 compass/ocean/tests/overflow/streams.init diff --git a/compass/ocean/tests/overflow/streams.forward b/compass/ocean/tests/overflow/streams.forward new file mode 100644 index 0000000000..985893b8f0 --- /dev/null +++ b/compass/ocean/tests/overflow/streams.forward @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/compass/ocean/tests/overflow/streams.init b/compass/ocean/tests/overflow/streams.init new file mode 100644 index 0000000000..8d30f9dde6 --- /dev/null +++ b/compass/ocean/tests/overflow/streams.init @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From 134f0ff49e120c0b0e4fef450eba685361e219e7 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Wed, 4 Jan 2023 12:30:12 -0800 Subject: [PATCH 05/20] Add forward step --- .../ocean/tests/overflow/default/__init__.py | 2 + compass/ocean/tests/overflow/forward.py | 81 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 compass/ocean/tests/overflow/forward.py diff --git a/compass/ocean/tests/overflow/default/__init__.py b/compass/ocean/tests/overflow/default/__init__.py index e90d3b900c..05fe53a14f 100644 --- a/compass/ocean/tests/overflow/default/__init__.py +++ b/compass/ocean/tests/overflow/default/__init__.py @@ -1,5 +1,6 @@ from compass.testcase import TestCase from compass.ocean.tests.overflow.initial_state import InitialState +from compass.ocean.tests.overflow.forward import Forward class Default(TestCase): @@ -18,3 +19,4 @@ def __init__(self, test_group): """ super().__init__(test_group=test_group, name='default') self.add_step(InitialState(test_case=self)) + self.add_step(Forward(test_case=self, ntasks=4, openmp_threads=1)) diff --git a/compass/ocean/tests/overflow/forward.py b/compass/ocean/tests/overflow/forward.py new file mode 100644 index 0000000000..a2d3bf53e7 --- /dev/null +++ b/compass/ocean/tests/overflow/forward.py @@ -0,0 +1,81 @@ +from compass.model import run_model +from compass.step import Step + + +class Forward(Step): + """ + A step for performing forward MPAS-Ocean runs as part of overflow + test cases. + + Attributes + ---------- + resolution : str + The resolution of the test case + """ + def __init__(self, test_case, resolution, name='forward', subdir=None, + ntasks=1, min_tasks=None, openmp_threads=1, nu=None): + """ + Create a new test case + + Parameters + ---------- + test_case : compass.TestCase + The test case this step belongs to + + resolution : str + The resolution of the test case + + name : str + the name of the test case + + subdir : str, optional + the subdirectory for the step. The default is ``name`` + + ntasks : int, optional + the number of tasks the step would ideally use. If fewer tasks + are available on the system, the step will run on all available + tasks as long as this is not below ``min_tasks`` + + min_tasks : int, optional + the number of tasks the step requires. If the system has fewer + than this number of tasks, the step will fail + + openmp_threads : int, optional + the number of OpenMP threads the step will use + + """ + self.resolution = resolution + if min_tasks is None: + min_tasks = ntasks + super().__init__(test_case=test_case, name=name, subdir=subdir, + ntasks=ntasks, min_tasks=min_tasks, + openmp_threads=openmp_threads) + self.add_namelist_file('compass.ocean.tests.overflow', + 'namelist.forward') + if nu is not None: + # update the viscosity to the requested value + options = {'config_mom_del2': '{}'.format(nu)} + self.add_namelist_options(options) + + # make sure output is double precision + self.add_streams_file('compass.ocean.streams', 'streams.output') + + self.add_streams_file('compass.ocean.tests.overflow', + 'streams.forward') + + self.add_input_file(filename='init.nc', + target='../initial_state/ocean.nc') + self.add_input_file(filename='graph.info', + target='../initial_state/culled_graph.info') + + self.add_model_as_input() + + self.add_output_file(filename='output.nc') + + # no setup() is needed + + def run(self): + """ + Run this step of the test case + """ + run_model(self) From 9d7a7465a377e0ab7907d732a940bfe6681ea51b Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Wed, 4 Jan 2023 13:45:23 -0800 Subject: [PATCH 06/20] Add rpe test --- compass/ocean/tests/overflow/__init__.py | 36 ++++- .../ocean/tests/overflow/default/__init__.py | 15 +- compass/ocean/tests/overflow/forward.py | 13 +- .../ocean/tests/overflow/rpe_test/__init__.py | 66 ++++++++ .../ocean/tests/overflow/rpe_test/analysis.py | 146 ++++++++++++++++++ .../tests/overflow/rpe_test/namelist.forward | 1 + .../tests/overflow/rpe_test/streams.forward | 21 +++ 7 files changed, 286 insertions(+), 12 deletions(-) create mode 100644 compass/ocean/tests/overflow/rpe_test/__init__.py create mode 100644 compass/ocean/tests/overflow/rpe_test/analysis.py create mode 100644 compass/ocean/tests/overflow/rpe_test/namelist.forward create mode 100644 compass/ocean/tests/overflow/rpe_test/streams.forward diff --git a/compass/ocean/tests/overflow/__init__.py b/compass/ocean/tests/overflow/__init__.py index be3b76d6da..f11689fecd 100644 --- a/compass/ocean/tests/overflow/__init__.py +++ b/compass/ocean/tests/overflow/__init__.py @@ -1,5 +1,6 @@ from compass.testgroup import TestGroup from compass.ocean.tests.overflow.default import Default +from compass.ocean.tests.overflow.rpe_test import RpeTest class Overflow(TestGroup): """ @@ -13,4 +14,37 @@ def __init__(self, mpas_core): """ super().__init__(mpas_core=mpas_core, name='overflow') - self.add_test_case(Default(test_group=self)) + self.add_test_case(Default(test_group=self, resolution='10km')) + self.add_test_case(RpeTest(test_group=self, resolution='1km')) + +def configure(resolution, config): + """ + Modify the configuration options for one of the overflow test cases + + Parameters + ---------- + resolution : str + The resolution of the test case + + config : compass.config.CompassConfigParser + Configuration options for this test case + """ + res_params = {'10km': {'nx': 4, + 'ny': 24, + 'dc': 10e3}, + '1km': {'nx': 4, + 'ny': 200, + 'dc': 1e3}} + + comment = {'nx': 'the number of mesh cells in the x direction', + 'ny': 'the number of mesh cells in the y direction', + 'dc': 'the distance between adjacent cell centers'} + + if resolution not in res_params: + raise ValueError(f'Unsupported resolution {resolution}. Supported ' + f'values are: {list(res_params)}') + + res_params = res_params[resolution] + for param in res_params: + config.set('overflow', param, str(res_params[param]), + comment=comment[param]) diff --git a/compass/ocean/tests/overflow/default/__init__.py b/compass/ocean/tests/overflow/default/__init__.py index 05fe53a14f..bdc1ec385e 100644 --- a/compass/ocean/tests/overflow/default/__init__.py +++ b/compass/ocean/tests/overflow/default/__init__.py @@ -1,14 +1,20 @@ from compass.testcase import TestCase from compass.ocean.tests.overflow.initial_state import InitialState from compass.ocean.tests.overflow.forward import Forward +from compass.ocean.tests import overflow class Default(TestCase): """ The default test case for the overflow test + + Attributes + ---------- + resolution : str + The resolution of the test case """ - def __init__(self, test_group): + def __init__(self, test_group, resolution): """ Create the test case @@ -18,5 +24,12 @@ def __init__(self, test_group): The test group that this test case belongs to """ super().__init__(test_group=test_group, name='default') + self.resolution = resolution self.add_step(InitialState(test_case=self)) self.add_step(Forward(test_case=self, ntasks=4, openmp_threads=1)) + + def configure(self): + """ + Modify the configuration options for this test case. + """ + overflow.configure(self.resolution, self.config) diff --git a/compass/ocean/tests/overflow/forward.py b/compass/ocean/tests/overflow/forward.py index a2d3bf53e7..5276bffff5 100644 --- a/compass/ocean/tests/overflow/forward.py +++ b/compass/ocean/tests/overflow/forward.py @@ -6,13 +6,8 @@ class Forward(Step): """ A step for performing forward MPAS-Ocean runs as part of overflow test cases. - - Attributes - ---------- - resolution : str - The resolution of the test case """ - def __init__(self, test_case, resolution, name='forward', subdir=None, + def __init__(self, test_case, name='forward', subdir=None, ntasks=1, min_tasks=None, openmp_threads=1, nu=None): """ Create a new test case @@ -22,9 +17,6 @@ def __init__(self, test_case, resolution, name='forward', subdir=None, test_case : compass.TestCase The test case this step belongs to - resolution : str - The resolution of the test case - name : str the name of the test case @@ -44,7 +36,6 @@ def __init__(self, test_case, resolution, name='forward', subdir=None, the number of OpenMP threads the step will use """ - self.resolution = resolution if min_tasks is None: min_tasks = ntasks super().__init__(test_case=test_case, name=name, subdir=subdir, @@ -65,6 +56,8 @@ def __init__(self, test_case, resolution, name='forward', subdir=None, self.add_input_file(filename='init.nc', target='../initial_state/ocean.nc') + self.add_input_file(filename='mesh.nc', + target='../initial_state/culled_mesh.nc') self.add_input_file(filename='graph.info', target='../initial_state/culled_graph.info') diff --git a/compass/ocean/tests/overflow/rpe_test/__init__.py b/compass/ocean/tests/overflow/rpe_test/__init__.py new file mode 100644 index 0000000000..ff425b5f8f --- /dev/null +++ b/compass/ocean/tests/overflow/rpe_test/__init__.py @@ -0,0 +1,66 @@ +from compass.testcase import TestCase +from compass.ocean.tests.overflow.initial_state import InitialState +from compass.ocean.tests.overflow.forward import Forward +#from compass.ocean.tests.overflow.rpe_test.analysis import Analysis +from compass.ocean.tests import overflow + + +class RpeTest(TestCase): + """ + The reference potential energy (RPE) test case for the overflow + test group performs a 40h integration of the model forward in time at + 5 different values of the viscosity at the given resolution. + + Attributes + ---------- + resolution : str + The resolution of the test case + """ + + def __init__(self, test_group, resolution='1km'): + """ + Create the test case + + Parameters + ---------- + test_group : compass.ocean.tests.overflow.Overflow + The test group that this test case belongs to + + """ + name = 'rpe_test' + subdir = f'{name}' + super().__init__(test_group=test_group, name=name, + subdir=subdir) + + nus = [1, 5, 10, 20, 200] + + self.resolution = resolution + + self.add_step( + InitialState(test_case=self)) + + for index, nu in enumerate(nus): + name = 'rpe_test_{}_nu_{}'.format(index + 1, nu) + step = Forward( + test_case=self, name=name, subdir=name, + ntasks=144, min_tasks=36, + nu=float(nu)) + + step.add_namelist_file( + 'compass.ocean.tests.overflow.rpe_test', + 'namelist.forward') + step.add_streams_file( + 'compass.ocean.tests.overflow.rpe_test', + 'streams.forward') + self.add_step(step) + + #self.add_step( + # Analysis(test_case=self, resolution=resolution, nus=nus)) + + def configure(self): + """ + Modify the configuration options for this test case. + """ + overflow.configure(self.resolution, self.config) + + # no run() is needed because we're doing the default: running all steps diff --git a/compass/ocean/tests/overflow/rpe_test/analysis.py b/compass/ocean/tests/overflow/rpe_test/analysis.py new file mode 100644 index 0000000000..eddcd4ea15 --- /dev/null +++ b/compass/ocean/tests/overflow/rpe_test/analysis.py @@ -0,0 +1,146 @@ +import numpy as np +import xarray +import matplotlib.pyplot as plt +import cmocean + +from compass.step import Step +from compass.ocean.rpe import compute_rpe + + +class Analysis(Step): + """ + A step for plotting the results of a series of RPE runs in the overflow + test group + + Attributes + ---------- + resolution : str + The resolution of the test case + + nus : list of float + A list of viscosities + """ + def __init__(self, test_case, resolution, nus): + """ + Create the step + + Parameters + ---------- + test_case : compass.TestCase + The test case this step belongs to + + resolution : str + The resolution of the test case + + nus : list of float + A list of viscosities + """ + super().__init__(test_case=test_case, name='analysis') + self.resolution = resolution + self.nus = nus + + self.add_input_file( + filename='initial_state.nc', + target='../initial_state/ocean.nc') + + for index, nu in enumerate(nus): + self.add_input_file( + filename='output_{}.nc'.format(index+1), + target='../rpe_test_{}_nu_{}/output.nc'.format(index+1, nu)) + + self.add_output_file( + filename='sections_overflow_{}.png'.format(resolution)) + self.add_output_file(filename='rpe_t.png') + + def run(self): + """ + Run this step of the test case + """ + section = self.config['overflow'] + nx = section.getint('nx') + ny = section.getint('ny') + rpe = compute_rpe() + _plot(nx, ny, self.outputs[0], self.nus, rpe) + + +def _plot(nx, ny, filename, nus, rpe): + """ + Plot section of the overflow at different viscosities + + Parameters + ---------- + nx : int + The number of cells in the x direction + + ny : int + The number of cells in the y direction (before culling) + + filename : str + The output file name + + nus : list of float + The viscosity values + + rpe : float, dim len(nu) x len(time) + """ + + plt.switch_backend('Agg') + nanosecondsPerDay = 8.64e13 + num_files = len(nus) + time = 40/24 + + ds = xarray.open_dataset('output_1.nc') + times = ds.daysSinceStartOfSim.values + times = times.tolist() + times = np.divide(times, nanosecondsPerDay) + + fig = plt.figure() + for i in range(num_files): + rpe_norm = np.divide((rpe[i, :]-rpe[i, 0]), rpe[i, 0]) + plt.plot(times, rpe_norm, + label="$\\nu_h=${}".format(nus[i])) + plt.xlabel('Time, days') + plt.ylabel('RPE-RPE(0)/RPE(0)') + plt.legend() + plt.savefig('rpe_t.png') + plt.close(fig) + + fig, axs = plt.subplots(1, num_files, figsize=( + 2.1 * num_files, 5.0), constrained_layout=True) + + for iCol in range(num_files): + ds = xarray.open_dataset('output_{}.nc'.format(iCol + 1)) + times = ds.daysSinceStartOfSim.values + times = np.divide(times.tolist(), nanosecondsPerDay) + tidx = np.argmin(np.abs(times-time)) + var = ds.temperature.values + var1 = np.reshape(var[tidx, :, 0], [ny, nx]) + # flip in y-dir + var = np.flipud(var1) + + # Every other row in y needs to average two neighbors in x on + # planar hex mesh + var_avg = var + for j in range(0, ny, 2): + for i in range(1, nx - 2): + var_avg[j, i] = (var[j, i + 1] + var[j, i]) / 2.0 + + ax = axs[iCol] + dis = ax.imshow( + var_avg, + extent=[0, 160, 0, 500], + cmap='cmo.thermal', + vmin=11.8, + vmax=13.0) + ax.set_title(f'hour {int(times[tidx]*24.)}, ', + f'$\\nu_h=${nus[iCol]}') + ax.set_xticks(np.arange(0, 161, step=40)) + ax.set_yticks(np.arange(0, 501, step=50)) + + ax.set_xlabel('x, km') + if iCol == 0: + ax.set_ylabel('y, km') + if iCol == num_files - 1: + fig.colorbar(dis, ax=axs[num_files - 1], aspect=40) + + plt.savefig(filename) diff --git a/compass/ocean/tests/overflow/rpe_test/namelist.forward b/compass/ocean/tests/overflow/rpe_test/namelist.forward new file mode 100644 index 0000000000..ccfccb1d60 --- /dev/null +++ b/compass/ocean/tests/overflow/rpe_test/namelist.forward @@ -0,0 +1 @@ +config_run_duration='0000_40:00:00' diff --git a/compass/ocean/tests/overflow/rpe_test/streams.forward b/compass/ocean/tests/overflow/rpe_test/streams.forward new file mode 100644 index 0000000000..3db4ac17d7 --- /dev/null +++ b/compass/ocean/tests/overflow/rpe_test/streams.forward @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + From 3bb19d1fd0af7e6bfb00bb09cd6146eecba01987 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Wed, 11 Jan 2023 15:49:58 -0600 Subject: [PATCH 07/20] Fixup resolution dependencies --- compass/ocean/tests/overflow/__init__.py | 31 +++++++------------ .../ocean/tests/overflow/default/__init__.py | 3 +- .../ocean/tests/overflow/rpe_test/__init__.py | 12 +++---- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/compass/ocean/tests/overflow/__init__.py b/compass/ocean/tests/overflow/__init__.py index f11689fecd..064deab232 100644 --- a/compass/ocean/tests/overflow/__init__.py +++ b/compass/ocean/tests/overflow/__init__.py @@ -15,7 +15,7 @@ def __init__(self, mpas_core): super().__init__(mpas_core=mpas_core, name='overflow') self.add_test_case(Default(test_group=self, resolution='10km')) - self.add_test_case(RpeTest(test_group=self, resolution='1km')) + self.add_test_case(RpeTest(test_group=self, resolution='2km')) def configure(resolution, config): """ @@ -29,22 +29,15 @@ def configure(resolution, config): config : compass.config.CompassConfigParser Configuration options for this test case """ - res_params = {'10km': {'nx': 4, - 'ny': 24, - 'dc': 10e3}, - '1km': {'nx': 4, - 'ny': 200, - 'dc': 1e3}} + width = 40 # km + length = 200 # km + dc = float(resolution[:-2]) + nx = int(width/dc) + ny = int(length/dc) - comment = {'nx': 'the number of mesh cells in the x direction', - 'ny': 'the number of mesh cells in the y direction', - 'dc': 'the distance between adjacent cell centers'} - - if resolution not in res_params: - raise ValueError(f'Unsupported resolution {resolution}. Supported ' - f'values are: {list(res_params)}') - - res_params = res_params[resolution] - for param in res_params: - config.set('overflow', param, str(res_params[param]), - comment=comment[param]) + config.set('overflow', 'nx', str(nx), + comment='the number of mesh cells in the x direction') + config.set('overflow', 'ny', str(ny), + comment='the number of mesh cells in the y direction') + config.set('overflow', 'dc', str(dc*1e3), + comment='the distance between adjacent cell centers') diff --git a/compass/ocean/tests/overflow/default/__init__.py b/compass/ocean/tests/overflow/default/__init__.py index bdc1ec385e..0270831431 100644 --- a/compass/ocean/tests/overflow/default/__init__.py +++ b/compass/ocean/tests/overflow/default/__init__.py @@ -23,7 +23,8 @@ def __init__(self, test_group, resolution): test_group : compass.ocean.tests.overflow.Overflow The test group that this test case belongs to """ - super().__init__(test_group=test_group, name='default') + super().__init__(test_group=test_group, name='default', + subdir=f'{resolution}/default') self.resolution = resolution self.add_step(InitialState(test_case=self)) self.add_step(Forward(test_case=self, ntasks=4, openmp_threads=1)) diff --git a/compass/ocean/tests/overflow/rpe_test/__init__.py b/compass/ocean/tests/overflow/rpe_test/__init__.py index ff425b5f8f..893103fd0b 100644 --- a/compass/ocean/tests/overflow/rpe_test/__init__.py +++ b/compass/ocean/tests/overflow/rpe_test/__init__.py @@ -1,7 +1,7 @@ from compass.testcase import TestCase from compass.ocean.tests.overflow.initial_state import InitialState from compass.ocean.tests.overflow.forward import Forward -#from compass.ocean.tests.overflow.rpe_test.analysis import Analysis +from compass.ocean.tests.overflow.rpe_test.analysis import Analysis from compass.ocean.tests import overflow @@ -28,11 +28,11 @@ def __init__(self, test_group, resolution='1km'): """ name = 'rpe_test' - subdir = f'{name}' + subdir = f'{resolution}/{name}' super().__init__(test_group=test_group, name=name, subdir=subdir) - nus = [1, 5, 10, 20, 200] + nus = [1, 5, 10, 100, 1000] self.resolution = resolution @@ -43,7 +43,7 @@ def __init__(self, test_group, resolution='1km'): name = 'rpe_test_{}_nu_{}'.format(index + 1, nu) step = Forward( test_case=self, name=name, subdir=name, - ntasks=144, min_tasks=36, + ntasks=64, min_tasks=16, openmp_threads=1, nu=float(nu)) step.add_namelist_file( @@ -54,8 +54,8 @@ def __init__(self, test_group, resolution='1km'): 'streams.forward') self.add_step(step) - #self.add_step( - # Analysis(test_case=self, resolution=resolution, nus=nus)) + self.add_step( + Analysis(test_case=self, resolution=resolution, nus=nus)) def configure(self): """ From ad8aa76525b58fea8d42950f049bc57e189e84a3 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Wed, 11 Jan 2023 15:51:25 -0600 Subject: [PATCH 08/20] Make domain periodic in y-dim --- compass/ocean/tests/overflow/initial_state.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compass/ocean/tests/overflow/initial_state.py b/compass/ocean/tests/overflow/initial_state.py index ea12d9e168..4b4349ddfb 100644 --- a/compass/ocean/tests/overflow/initial_state.py +++ b/compass/ocean/tests/overflow/initial_state.py @@ -56,7 +56,7 @@ def run(self): logger.info(' * Make planar hex mesh') dsMesh = make_planar_hex_mesh(nx=nx, ny=ny, dc=dc, nonperiodic_x=True, - nonperiodic_y=True) + nonperiodic_y=False) logger.info(' * Completed Make planar hex mesh') write_netcdf(dsMesh, 'base_mesh.nc') @@ -82,4 +82,3 @@ def run(self): run_model(self, namelist='namelist.ocean', streams='streams.ocean') - From e3dc3b34cd887c7e4566c407cd6a5b05161d58e8 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Wed, 11 Jan 2023 15:58:44 -0600 Subject: [PATCH 09/20] fixup analysis --- .../ocean/tests/overflow/rpe_test/analysis.py | 101 +++++++++++++----- 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/compass/ocean/tests/overflow/rpe_test/analysis.py b/compass/ocean/tests/overflow/rpe_test/analysis.py index eddcd4ea15..8b02a69b55 100644 --- a/compass/ocean/tests/overflow/rpe_test/analysis.py +++ b/compass/ocean/tests/overflow/rpe_test/analysis.py @@ -58,13 +58,14 @@ def run(self): """ section = self.config['overflow'] nx = section.getint('nx') - ny = section.getint('ny') + ny = section.getint('ny') - 2 rpe = compute_rpe() _plot(nx, ny, self.outputs[0], self.nus, rpe) def _plot(nx, ny, filename, nus, rpe): """ + TODO change section from nx vs ny to ny vs. nz Plot section of the overflow at different viscosities Parameters @@ -87,7 +88,7 @@ def _plot(nx, ny, filename, nus, rpe): plt.switch_backend('Agg') nanosecondsPerDay = 8.64e13 num_files = len(nus) - time = 40/24 + time = 6/24 ds = xarray.open_dataset('output_1.nc') times = ds.daysSinceStartOfSim.values @@ -105,42 +106,84 @@ def _plot(nx, ny, filename, nus, rpe): plt.savefig('rpe_t.png') plt.close(fig) - fig, axs = plt.subplots(1, num_files, figsize=( - 2.1 * num_files, 5.0), constrained_layout=True) + # prep mesh quantities + ds = xarray.open_dataset('../initial_state/ocean.nc') + ds = ds.sortby('yEdge') + xEdge = ds.xEdge + yEdge = ds.yEdge + cellsOnEdge = ds.cellsOnEdge + nVertLevels = ds.sizes['nVertLevels'] + + xEdge_mid = np.median(xEdge) + edgeMask_x = np.equal(xEdge, xEdge_mid) + yEdge_x = yEdge[edgeMask_x] + cellsOnEdge_x = cellsOnEdge[edgeMask_x, :] + cell1Index = np.subtract(cellsOnEdge_x[:, 0], 1) + cell2Index = np.subtract(cellsOnEdge_x[:, 1], 1) + nEdges_x = len(yEdge_x) + + fig, axs = plt.subplots(num_files, 1, figsize=( + 5.0, 2.1 * num_files), constrained_layout=True) + fig.suptitle(f'Temperature, Overflow test case') for iCol in range(num_files): + + ax = axs[iCol] ds = xarray.open_dataset('output_{}.nc'.format(iCol + 1)) + + # Get the output times again + # Don't assume that the output times are the same for all files times = ds.daysSinceStartOfSim.values times = np.divide(times.tolist(), nanosecondsPerDay) tidx = np.argmin(np.abs(times-time)) - var = ds.temperature.values - var1 = np.reshape(var[tidx, :, 0], [ny, nx]) - # flip in y-dir - var = np.flipud(var1) - - # Every other row in y needs to average two neighbors in x on - # planar hex mesh - var_avg = var - for j in range(0, ny, 2): - for i in range(1, nx - 2): - var_avg[j, i] = (var[j, i + 1] + var[j, i]) / 2.0 - - ax = axs[iCol] - dis = ax.imshow( - var_avg, - extent=[0, 160, 0, 500], - cmap='cmo.thermal', - vmin=11.8, - vmax=13.0) - ax.set_title(f'hour {int(times[tidx]*24.)}, ', + ds = ds.isel(Time=tidx) + + ds = ds.sortby('yEdge') + + # Compute the layer interfaces depths across the cross-section + layerThickness = ds.layerThickness + layerThickness_cell1 = layerThickness[cell1Index, :] + layerThickness_cell2 = layerThickness[cell2Index, :] + layerThickness_x = layerThickness_cell2[1:, :] + + ssh = ds.ssh + ssh_cell1 = ssh[cell1Index] + ssh_cell2 = ssh[cell2Index] + ssh_x = ssh_cell2[1:] + + zIndex = xarray.DataArray(data=np.arange(nVertLevels), + dims='nVertLevels') + + zEdgeInterface = np.zeros((nEdges_x, nVertLevels + 1)) + zEdgeInterface[:, 0] = 0.5 * (ssh_cell1.values + ssh_cell2.values) + for zIndex in range(nVertLevels): + thickness1 = layerThickness_cell1.isel(nVertLevels=zIndex) + thickness1 = thickness1.fillna(0.) + thickness2 = layerThickness_cell2.isel(nVertLevels=zIndex) + thickness2 = thickness2.fillna(0.) + zEdgeInterface[:, zIndex + 1] = \ + zEdgeInterface[:, zIndex] - \ + 0.5 * (thickness1.values + thickness2.values) + + _, yEdges_mesh = np.meshgrid(zEdgeInterface[0, :], yEdge_x) + + # Retrieve the temperature field + temperature = ds.temperature + temperature_x = temperature[cell2Index[1:], :] + + # Plot + dis = ax.pcolormesh(np.divide(yEdges_mesh, 1e3), + zEdgeInterface, + temperature_x.values, cmap='viridis', + vmin=10, vmax=20) + ax.set_title(f'hour {int(times[tidx]*24.)}, ' f'$\\nu_h=${nus[iCol]}') - ax.set_xticks(np.arange(0, 161, step=40)) - ax.set_yticks(np.arange(0, 501, step=50)) - ax.set_xlabel('x, km') + ax.set_ylabel('z (m)') + ax.set_xlim([10, 100]) if iCol == 0: - ax.set_ylabel('y, km') + fig.colorbar(dis) if iCol == num_files - 1: - fig.colorbar(dis, ax=axs[num_files - 1], aspect=40) + ax.set_xlabel('y (km)') plt.savefig(filename) From 6673a07d317c0ea3ddac8f53a949313f8c7952be Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Wed, 11 Jan 2023 16:56:02 -0600 Subject: [PATCH 10/20] Fix dt --- compass/ocean/tests/overflow/namelist.forward | 2 +- compass/ocean/tests/overflow/rpe_test/namelist.forward | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compass/ocean/tests/overflow/namelist.forward b/compass/ocean/tests/overflow/namelist.forward index acf8304364..8af77137dc 100644 --- a/compass/ocean/tests/overflow/namelist.forward +++ b/compass/ocean/tests/overflow/namelist.forward @@ -1,4 +1,4 @@ -config_dt='0000_00:03:00' +config_dt='0000_00:01:00' config_btr_dt='0000_00:00:09' config_run_duration='0000_00:12:00' config_use_cvmix_convection=.true. diff --git a/compass/ocean/tests/overflow/rpe_test/namelist.forward b/compass/ocean/tests/overflow/rpe_test/namelist.forward index ccfccb1d60..28fc7c23a5 100644 --- a/compass/ocean/tests/overflow/rpe_test/namelist.forward +++ b/compass/ocean/tests/overflow/rpe_test/namelist.forward @@ -1 +1,2 @@ config_run_duration='0000_40:00:00' +config_dt = '0000_00:00:10' From 9d5bd375a1cb14efef91a50cffd22d4e7138f1f7 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Thu, 12 Jan 2023 16:42:59 -0600 Subject: [PATCH 11/20] cleanup __init__.py --- compass/ocean/tests/overflow/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/compass/ocean/tests/overflow/__init__.py b/compass/ocean/tests/overflow/__init__.py index 064deab232..8a818d3036 100644 --- a/compass/ocean/tests/overflow/__init__.py +++ b/compass/ocean/tests/overflow/__init__.py @@ -1,22 +1,24 @@ from compass.testgroup import TestGroup from compass.ocean.tests.overflow.default import Default from compass.ocean.tests.overflow.rpe_test import RpeTest - -class Overflow(TestGroup): - """ + + +class Overflow(TestGroup): + """ A test group for General Ocean Turbulence Model (GOTM) test cases """ - def __init__(self, mpas_core): + def __init__(self, mpas_core): """ - mpas_core : compass.MpasCore + mpas_core : compass.MpasCore the MPAS core that this test group belongs to """ super().__init__(mpas_core=mpas_core, name='overflow') - + self.add_test_case(Default(test_group=self, resolution='10km')) self.add_test_case(RpeTest(test_group=self, resolution='2km')) + def configure(resolution, config): """ Modify the configuration options for one of the overflow test cases @@ -29,8 +31,8 @@ def configure(resolution, config): config : compass.config.CompassConfigParser Configuration options for this test case """ - width = 40 # km - length = 200 # km + width = 40 # km + length = 200 # km dc = float(resolution[:-2]) nx = int(width/dc) ny = int(length/dc) From 93e009681747927b644de8305831800a152cf8b6 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Fri, 13 Jan 2023 12:21:32 -0600 Subject: [PATCH 12/20] Update docs --- docs/developers_guide/ocean/api.rst | 24 ++++++ .../ocean/test_groups/index.rst | 1 + .../ocean/test_groups/overflow.rst | 79 ++++++++++++++++++ .../ocean/test_groups/images/overflow.png | Bin 0 -> 27681 bytes docs/users_guide/ocean/test_groups/index.rst | 1 + .../ocean/test_groups/overflow.rst | 66 +++++++++++++++ 6 files changed, 171 insertions(+) create mode 100644 docs/developers_guide/ocean/test_groups/overflow.rst create mode 100644 docs/users_guide/ocean/test_groups/images/overflow.png create mode 100644 docs/users_guide/ocean/test_groups/overflow.rst diff --git a/docs/developers_guide/ocean/api.rst b/docs/developers_guide/ocean/api.rst index 7b8c8f016b..6e46d0087c 100644 --- a/docs/developers_guide/ocean/api.rst +++ b/docs/developers_guide/ocean/api.rst @@ -513,6 +513,30 @@ nonhydro solitary_wave.visualize.Visualize.setup solitary_wave.visualize.Visualize.run +overflow +~~~~~~~~ + +.. currentmodule:: compass.ocean.tests.overflow + +.. autosummary:: + :toctree: generated/ + + Overflow + + default.Default + + rpe_test.RpeTest + + rpe_test.analysis.Analysis + rpe_test.analysis.Analysis.run + + forward.Forward + forward.Forward.run + + initial_state.InitialState + initial_state.InitialState.run + + planar_convergence ~~~~~~~~~~~~~~~~~~ diff --git a/docs/developers_guide/ocean/test_groups/index.rst b/docs/developers_guide/ocean/test_groups/index.rst index 29955cadee..3831a104b1 100644 --- a/docs/developers_guide/ocean/test_groups/index.rst +++ b/docs/developers_guide/ocean/test_groups/index.rst @@ -19,6 +19,7 @@ Test groups isomip_plus merry_go_round nonhydro + overflow planar_convergence soma sphere_transport diff --git a/docs/developers_guide/ocean/test_groups/overflow.rst b/docs/developers_guide/ocean/test_groups/overflow.rst new file mode 100644 index 0000000000..45a9cb2f98 --- /dev/null +++ b/docs/developers_guide/ocean/test_groups/overflow.rst @@ -0,0 +1,79 @@ +.. _dev_ocean_overflow: + +overflow +======== + +The ``overflow`` test group +(:py:class:`compass.ocean.tests.overflow.Overflow`) +implements variants of the continental shelf overflow test case. Here, +we describe the shared framework for this test group and the 2 test cases. + +.. _dev_ocean_overflow_framework: + +framework +--------- + +The shared config options for the ``overflow`` test group +are described in :ref:`ocean_overflow` in the User's Guide. + +Additionally, the test group has a shared ``namelist.forward`` file with +a few common namelist options related to time step, run duration, viscosity, +and drag, as well as a shared ``streams.forward`` file that defines ``mesh``, +``input``, and ``output`` streams. + +initial_state +~~~~~~~~~~~~~ + +The class :py:class:`compass.ocean.tests.overflow.initial_state.InitialState` +defines a step for setting up the initial state for each test case. + +First, a mesh appropriate for the resolution is generated using +:py:func:`mpas_tools.planar_hex.make_planar_hex_mesh()`. Then, the mesh is +culled to remove periodicity in the y direction. The ocean model is then run +in ``init`` mode to generate the vertical grid and populate initial conditions. + +forward +~~~~~~~ + +The class :py:class:`compass.ocean.tests.overflow.forward.Forward` +defines a step for running MPAS-Ocean from the initial condition produced in +the ``initial_state`` step. If ``nu`` is provided as an argument to the +constructor, the associate namelist option (``config_mom_del2``) will be given +this value. Namelist and streams files are also generated. MPAS-Ocean is run +(including updating PIO namelist options and generating a graph partition) in +``run()``. + +.. _dev_ocean_overflow_default: + +default +------- + +The :py:class:`compass.ocean.tests.overflow.default.Default` +test performs a 12-minute run on 4 cores. It doesn't contain any +:ref:`dev_validation`. + +.. _dev_ocean_overflow_rpe_test: + +rpe_test +-------- + +The :py:class:`compass.ocean.tests.overflow.rpe_test.RpeTest` +performs a longer (40 hour) integration of the model forward in time at 5 +different values of the viscosity. These ``nu`` values are later added as +arguments to the ``Forward`` steps' constructors when they are added to the +test case: + +.. code-block:: python + + step = Forward( + test_case=self, name=name, subdir=name, ntasks=4, + openmp_threads=1, nu=float(nu)) + ... + self.add_step(step) + +The ``analysis`` step defined by +:py:class:`compass.ocean.tests.overflow.rpe_test.analysis.Analysis` +makes plots of the final results with each value of the viscosity. + +This test is resource intensive enough that it is not used in regression +testing. diff --git a/docs/users_guide/ocean/test_groups/images/overflow.png b/docs/users_guide/ocean/test_groups/images/overflow.png new file mode 100644 index 0000000000000000000000000000000000000000..a879edf1d00f48fc0d1bda420aac65dce8cc5fa5 GIT binary patch literal 27681 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

&APHGf|1rG+rH|_CUqbLMnA-zV- zOsRNSMk{J{F7uiU#kHpW$|uExNo+&z7`^7L%n*$&j023Z|)lBK!jcHC`e%JknGh;HT0zMm-zSNrHwXZA2I9 zJ8O?}+o8dM2-RM%q)TQ#R&70I$RITX^pc6U@NMsg<(5}_%K>~H>q8f7h(nMWAA=gp zK+K^5QS-k(@^|BS!b@v)xtJ}%fz+x{v^07cx*SF<4OZCP%;V}mR|((Lj4ojGBWl9S z%!F2YVTdxTr!}X*l#H71h1M+I#Qp6BEr)8q*cK)C8W-V7+(9wN%gFN^hdpTM-#$J3 zA|O%%-=iw+42XX+%0)BIlQ2^7LB)Np9QQbgbl-ZchdOcbzN^%$?Z!(}hx@8aEkk8G zKt}o26*|HggwGt>+M)O=5>nRe`fa9Z2>lV4pGZX1kAVTGH@hK~v>lvuuF(B(g=HCa zFLTylYZunbScNGnzQi%!n-GVV&g7Xy7*E-Xa@6)-D}-j{0I*oB%e@jeVSmG~t9Cm+ zw0U1>zVXUj3Rc967QQU3&r@zLl2?z@MX<4JkD-pMEgKpxhVHKrujwZdt+Dmu#dqLk zU<4xZlkbq{W(dF%Mm+G0QEV~gTX=^%0|v8_#{dMK+8Lt^bVb z$ahi{^>IjBO*jayBjB2HdNv7^2ApBG06D=?Iq{k?Mq_MMEOhfyju^cJa-_&@P=G9q zh|d(a5X$;W->7W8Y6G7bL5mGD{tbvo9h?uOMK}3rhlBE`SY|ywA>#uywYkD)yNjjRUgUuBK=6OeX4i2oHC zHZ&Oj=90sxo(j03RF(xVZfPWICXEyn){yR{Z?Iwt`e5r%%t4#$?CZtD1}xw3pRwK` zZjpBHa$Q8_DYpVB^yY3CT@XY-$&z5+F8G`Ko0J`Np+*y4J-Gc2KpYumtoJZ8BT|PU zCS5{B`&`_. + +The domain is periodic on the zonal boundaries and solid on the meridional +boundaries. Salinity is constant throughout the domain (at 35 PSU). The +initial temperature is bimodal with low temperature throughout the continental +shelf region of 10 deg C and high temperature over the slope and deep ocean of +20 deg C. This perturbation initiates slumping of the cold, denser water mass +and flow down the slope as a bottom boundary current. + +.. image:: images/overflow.png + :width: 500 px + :align: center + +By default, the sigma coordinate is used. There appears to be an +implementation error for other vertical coordinate options. For the default +test case, the horizontal resolution is 10 km. For the RPE test case, the +horizontal resolution is 2 km. + +The test group includes 2 test cases. All test cases have 2 steps, +``initial_state``, which defines the mesh and initial conditions for the model, +and ``forward``, which performs time integration of the model. For the RPE +test, there is an additional ``analysis`` step which computes the RPE through +time in relation to the initial RPE and visualizes vertical cross-sections +through the center of the domain. + +config options +-------------- + +All 3 test cases share the same set of config options: + +.. code-block:: cfg + + # Options related to the overflow case + [overflow] + + # the number of grid cells in x and y + nx = 4 + ny = 24 + + # the size of grid cells (m) + dc = 10000.0 + +default +------- + +``ocean/overflow/default`` is the default version of the +overflow test case for a short (12 min) test run and validation of +prognostic variables for regression testing. + +rpe_test +-------- + +Since mixing is a strong function of horizontal viscosity, this test case +``ocean/overflow/rpe_test`` performs 40-hour integrations of the model forward +in time at 5 different values of the viscosity (with steps named +``rpe_test_1_nu_1``, ``rpe_test_2_nu_5``, etc.). Results of these tests have +been used to evaluate spurious dissipation in relation to different models and +vertical grid choices +(`Petersen et al. 2015 `_). From 94affdad3fcb729fcac5bf5a1a7a05c211c58bd5 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Fri, 13 Jan 2023 16:05:22 -0600 Subject: [PATCH 13/20] Update cfg file --- compass/ocean/tests/overflow/overflow.cfg | 7 ------- 1 file changed, 7 deletions(-) diff --git a/compass/ocean/tests/overflow/overflow.cfg b/compass/ocean/tests/overflow/overflow.cfg index d8fd500026..fa7c9173d4 100644 --- a/compass/ocean/tests/overflow/overflow.cfg +++ b/compass/ocean/tests/overflow/overflow.cfg @@ -1,9 +1,3 @@ -# Options related to the vertical grid -[vertical_grid] - -# Number of vertical levels -vert_levels = 1 - # Options related to the overflow case [overflow] @@ -13,4 +7,3 @@ ny = 24 # the size of grid cells (m) dc = 10000.0 - From 56b62c0f650d23c05427af6a2ea46ecf99bcdd19 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Fri, 13 Jan 2023 17:30:18 -0600 Subject: [PATCH 14/20] Add validate step --- compass/ocean/tests/overflow/default/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compass/ocean/tests/overflow/default/__init__.py b/compass/ocean/tests/overflow/default/__init__.py index 0270831431..72dec23a3f 100644 --- a/compass/ocean/tests/overflow/default/__init__.py +++ b/compass/ocean/tests/overflow/default/__init__.py @@ -2,6 +2,7 @@ from compass.ocean.tests.overflow.initial_state import InitialState from compass.ocean.tests.overflow.forward import Forward from compass.ocean.tests import overflow +from compass.validate import compare_variables class Default(TestCase): @@ -34,3 +35,11 @@ def configure(self): Modify the configuration options for this test case. """ overflow.configure(self.resolution, self.config) + + def validate(self): + """ + Validate variables against a baseline + """ + compare_variables(test_case=self, + variables=['layerThickness', 'temperature'], + filename1='forward/output.nc') From 79cc5539a2d0d0169e7ba40e7995e206b09a786f Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Mon, 16 Jan 2023 14:51:10 -0600 Subject: [PATCH 15/20] Aesthetic changes: fix case --- compass/ocean/tests/overflow/initial_state.py | 10 +-- .../ocean/tests/overflow/rpe_test/analysis.py | 75 +++++++++---------- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/compass/ocean/tests/overflow/initial_state.py b/compass/ocean/tests/overflow/initial_state.py index 4b4349ddfb..43fc90dc00 100644 --- a/compass/ocean/tests/overflow/initial_state.py +++ b/compass/ocean/tests/overflow/initial_state.py @@ -55,18 +55,18 @@ def run(self): {'config_overflow_dc': f'{dc}'}) logger.info(' * Make planar hex mesh') - dsMesh = make_planar_hex_mesh(nx=nx, ny=ny, dc=dc, nonperiodic_x=True, + ds_mesh = make_planar_hex_mesh(nx=nx, ny=ny, dc=dc, nonperiodic_x=True, nonperiodic_y=False) logger.info(' * Completed Make planar hex mesh') - write_netcdf(dsMesh, 'base_mesh.nc') + write_netcdf(ds_mesh, 'base_mesh.nc') logger.info(' * Cull mesh boundaries') - dsMesh = cull(dsMesh, logger=logger) + ds_mesh = cull(ds_mesh, logger=logger) logger.info(' * Convert mesh') - dsMesh = convert(dsMesh, graphInfoFileName='culled_graph.info', + ds_mesh = convert(ds_mesh, graphInfoFileName='culled_graph.info', logger=logger) logger.info(' * Completed Convert mesh') - write_netcdf(dsMesh, 'culled_mesh.nc') + write_netcdf(ds_mesh, 'culled_mesh.nc') run_model(self, namelist='namelist.ocean', streams='streams.ocean') diff --git a/compass/ocean/tests/overflow/rpe_test/analysis.py b/compass/ocean/tests/overflow/rpe_test/analysis.py index 8b02a69b55..be9e67b072 100644 --- a/compass/ocean/tests/overflow/rpe_test/analysis.py +++ b/compass/ocean/tests/overflow/rpe_test/analysis.py @@ -65,7 +65,6 @@ def run(self): def _plot(nx, ny, filename, nus, rpe): """ - TODO change section from nx vs ny to ny vs. nz Plot section of the overflow at different viscosities Parameters @@ -109,18 +108,18 @@ def _plot(nx, ny, filename, nus, rpe): # prep mesh quantities ds = xarray.open_dataset('../initial_state/ocean.nc') ds = ds.sortby('yEdge') - xEdge = ds.xEdge - yEdge = ds.yEdge - cellsOnEdge = ds.cellsOnEdge - nVertLevels = ds.sizes['nVertLevels'] - - xEdge_mid = np.median(xEdge) - edgeMask_x = np.equal(xEdge, xEdge_mid) - yEdge_x = yEdge[edgeMask_x] - cellsOnEdge_x = cellsOnEdge[edgeMask_x, :] - cell1Index = np.subtract(cellsOnEdge_x[:, 0], 1) - cell2Index = np.subtract(cellsOnEdge_x[:, 1], 1) - nEdges_x = len(yEdge_x) + x_edge = ds.xEdge + y_edge = ds.yEdge + cells_on_edge = ds.cellsOnEdge + n_vert_levels = ds.sizes['nVertLevels'] + + x_edge_mid = np.median(x_edge) + edge_mask_x = np.equal(x_edge, x_edge_mid) + y_edge_x = y_edge[edge_mask_x] + cells_on_edge_x = cells_on_edge[edge_mask_x, :] + cell1_index = np.subtract(cells_on_edge_x[:, 0], 1) + cell2_index = np.subtract(cells_on_edge_x[:, 1], 1) + nEdges_x = len(y_edge_x) fig, axs = plt.subplots(num_files, 1, figsize=( 5.0, 2.1 * num_files), constrained_layout=True) @@ -135,54 +134,54 @@ def _plot(nx, ny, filename, nus, rpe): # Don't assume that the output times are the same for all files times = ds.daysSinceStartOfSim.values times = np.divide(times.tolist(), nanosecondsPerDay) - tidx = np.argmin(np.abs(times-time)) - ds = ds.isel(Time=tidx) + t_idx = np.argmin(np.abs(times-time)) + ds = ds.isel(Time=t_idx) ds = ds.sortby('yEdge') # Compute the layer interfaces depths across the cross-section - layerThickness = ds.layerThickness - layerThickness_cell1 = layerThickness[cell1Index, :] - layerThickness_cell2 = layerThickness[cell2Index, :] - layerThickness_x = layerThickness_cell2[1:, :] + layer_thickness = ds.layerThickness + layer_thickness_cell1 = layer_thickness[cell1_index, :] + layer_thickness_cell2 = layer_thickness[cell2_index, :] + layer_thickness_x = layer_thickness_cell2[1:, :] ssh = ds.ssh - ssh_cell1 = ssh[cell1Index] - ssh_cell2 = ssh[cell2Index] + ssh_cell1 = ssh[cell1_index] + ssh_cell2 = ssh[cell2_index] ssh_x = ssh_cell2[1:] - zIndex = xarray.DataArray(data=np.arange(nVertLevels), - dims='nVertLevels') + z_index = xarray.DataArray(data=np.arange(n_vert_levels), + dims='nVertLevels') - zEdgeInterface = np.zeros((nEdges_x, nVertLevels + 1)) - zEdgeInterface[:, 0] = 0.5 * (ssh_cell1.values + ssh_cell2.values) - for zIndex in range(nVertLevels): - thickness1 = layerThickness_cell1.isel(nVertLevels=zIndex) + z_interface = np.zeros((nEdges_x, n_vert_levels + 1)) + z_interface[:, 0] = 0.5 * (ssh_cell1.values + ssh_cell2.values) + for z_index in range(n_vert_levels): + thickness1 = layer_thickness_cell1.isel(nVertLevels=z_index) thickness1 = thickness1.fillna(0.) - thickness2 = layerThickness_cell2.isel(nVertLevels=zIndex) + thickness2 = layer_thickness_cell2.isel(nVertLevels=z_index) thickness2 = thickness2.fillna(0.) - zEdgeInterface[:, zIndex + 1] = \ - zEdgeInterface[:, zIndex] - \ + z_interface[:, z_index + 1] = \ + z_interface[:, z_index] - \ 0.5 * (thickness1.values + thickness2.values) - _, yEdges_mesh = np.meshgrid(zEdgeInterface[0, :], yEdge_x) + _, y_edges_mesh = np.meshgrid(z_interface[0, :], y_edge_x) # Retrieve the temperature field temperature = ds.temperature - temperature_x = temperature[cell2Index[1:], :] + temperature_x = temperature[cell2_index[1:], :] # Plot - dis = ax.pcolormesh(np.divide(yEdges_mesh, 1e3), - zEdgeInterface, - temperature_x.values, cmap='viridis', - vmin=10, vmax=20) - ax.set_title(f'hour {int(times[tidx]*24.)}, ' + p = ax.pcolormesh(np.divide(y_edges_mesh, 1e3), + z_interface, + temperature_x.values, + cmap='viridis', vmin=10, vmax=20) + ax.set_title(f'hour {int(times[t_idx]*24.)}, ' f'$\\nu_h=${nus[iCol]}') ax.set_ylabel('z (m)') ax.set_xlim([10, 100]) if iCol == 0: - fig.colorbar(dis) + fig.colorbar(p) if iCol == num_files - 1: ax.set_xlabel('y (km)') From 4a474eb6decd6d02e97f11501ee37dee81887d08 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Mon, 30 Jan 2023 09:38:05 -0600 Subject: [PATCH 16/20] Remove grid specs from cfg file --- compass/ocean/tests/overflow/__init__.py | 5 +++-- compass/ocean/tests/overflow/overflow.cfg | 9 ++++----- compass/ocean/tests/overflow/rpe_test/analysis.py | 12 ++---------- docs/users_guide/ocean/test_groups/overflow.rst | 12 +++++++----- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/compass/ocean/tests/overflow/__init__.py b/compass/ocean/tests/overflow/__init__.py index 8a818d3036..68740d2d4b 100644 --- a/compass/ocean/tests/overflow/__init__.py +++ b/compass/ocean/tests/overflow/__init__.py @@ -31,8 +31,9 @@ def configure(resolution, config): config : compass.config.CompassConfigParser Configuration options for this test case """ - width = 40 # km - length = 200 # km + width = config.getint('overflow', 'width') + length = config.getint('overflow', 'length') + dc = float(resolution[:-2]) nx = int(width/dc) ny = int(length/dc) diff --git a/compass/ocean/tests/overflow/overflow.cfg b/compass/ocean/tests/overflow/overflow.cfg index fa7c9173d4..2abe5fd8c5 100644 --- a/compass/ocean/tests/overflow/overflow.cfg +++ b/compass/ocean/tests/overflow/overflow.cfg @@ -1,9 +1,8 @@ # Options related to the overflow case [overflow] -# the number of grid cells in x and y -nx = 4 -ny = 24 +# The width of the domain in the across-slope dimension (km) +width = 40 -# the size of grid cells (m) -dc = 10000.0 +# The length of the domain in the along-slope dimension (km) +length = 200 diff --git a/compass/ocean/tests/overflow/rpe_test/analysis.py b/compass/ocean/tests/overflow/rpe_test/analysis.py index be9e67b072..d68e1bf46d 100644 --- a/compass/ocean/tests/overflow/rpe_test/analysis.py +++ b/compass/ocean/tests/overflow/rpe_test/analysis.py @@ -56,24 +56,16 @@ def run(self): """ Run this step of the test case """ - section = self.config['overflow'] - nx = section.getint('nx') - ny = section.getint('ny') - 2 rpe = compute_rpe() - _plot(nx, ny, self.outputs[0], self.nus, rpe) + _plot(self.outputs[0], self.nus, rpe) -def _plot(nx, ny, filename, nus, rpe): +def _plot(filename, nus, rpe): """ Plot section of the overflow at different viscosities Parameters ---------- - nx : int - The number of cells in the x direction - - ny : int - The number of cells in the y direction (before culling) filename : str The output file name diff --git a/docs/users_guide/ocean/test_groups/overflow.rst b/docs/users_guide/ocean/test_groups/overflow.rst index f249e1c318..cb37c44e87 100644 --- a/docs/users_guide/ocean/test_groups/overflow.rst +++ b/docs/users_guide/ocean/test_groups/overflow.rst @@ -40,12 +40,14 @@ All 3 test cases share the same set of config options: # Options related to the overflow case [overflow] - # the number of grid cells in x and y - nx = 4 - ny = 24 + # The width of the domain in the across-slope dimension (km) + width = 40 - # the size of grid cells (m) - dc = 10000.0 + # The length of the domain in the along-slope dimension (km) + length = 200 + + # Viscosity values to test for rpe test case + viscosities = 1, 5, 10, 100, 1000 default ------- From 9c1de77acb5bcf80720111a14233d2b035568baf Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Mon, 30 Jan 2023 09:48:40 -0600 Subject: [PATCH 17/20] Cleanup formatted strings --- compass/ocean/tests/overflow/forward.py | 2 +- compass/ocean/tests/overflow/rpe_test/__init__.py | 2 +- compass/ocean/tests/overflow/rpe_test/analysis.py | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/compass/ocean/tests/overflow/forward.py b/compass/ocean/tests/overflow/forward.py index 5276bffff5..bf3e9e1643 100644 --- a/compass/ocean/tests/overflow/forward.py +++ b/compass/ocean/tests/overflow/forward.py @@ -45,7 +45,7 @@ def __init__(self, test_case, name='forward', subdir=None, 'namelist.forward') if nu is not None: # update the viscosity to the requested value - options = {'config_mom_del2': '{}'.format(nu)} + options = {'config_mom_del2': f'{nu}'} self.add_namelist_options(options) # make sure output is double precision diff --git a/compass/ocean/tests/overflow/rpe_test/__init__.py b/compass/ocean/tests/overflow/rpe_test/__init__.py index 893103fd0b..a37808018c 100644 --- a/compass/ocean/tests/overflow/rpe_test/__init__.py +++ b/compass/ocean/tests/overflow/rpe_test/__init__.py @@ -40,7 +40,7 @@ def __init__(self, test_group, resolution='1km'): InitialState(test_case=self)) for index, nu in enumerate(nus): - name = 'rpe_test_{}_nu_{}'.format(index + 1, nu) + name = f'rpe_test_{index + 1}_nu_{nu}' step = Forward( test_case=self, name=name, subdir=name, ntasks=64, min_tasks=16, openmp_threads=1, diff --git a/compass/ocean/tests/overflow/rpe_test/analysis.py b/compass/ocean/tests/overflow/rpe_test/analysis.py index d68e1bf46d..04b2156133 100644 --- a/compass/ocean/tests/overflow/rpe_test/analysis.py +++ b/compass/ocean/tests/overflow/rpe_test/analysis.py @@ -45,11 +45,11 @@ def __init__(self, test_case, resolution, nus): for index, nu in enumerate(nus): self.add_input_file( - filename='output_{}.nc'.format(index+1), - target='../rpe_test_{}_nu_{}/output.nc'.format(index+1, nu)) + filename=f'output_{index+1}.nc', + target=f'../rpe_test_{index+1}_nu_{nu}/output.nc') self.add_output_file( - filename='sections_overflow_{}.png'.format(resolution)) + filename=f'sections_overflow_{resolution}.png') self.add_output_file(filename='rpe_t.png') def run(self): @@ -90,7 +90,7 @@ def _plot(filename, nus, rpe): for i in range(num_files): rpe_norm = np.divide((rpe[i, :]-rpe[i, 0]), rpe[i, 0]) plt.plot(times, rpe_norm, - label="$\\nu_h=${}".format(nus[i])) + label=f"$\\nu_h=${nus[i]}") plt.xlabel('Time, days') plt.ylabel('RPE-RPE(0)/RPE(0)') plt.legend() @@ -115,12 +115,12 @@ def _plot(filename, nus, rpe): fig, axs = plt.subplots(num_files, 1, figsize=( 5.0, 2.1 * num_files), constrained_layout=True) - fig.suptitle(f'Temperature, Overflow test case') + fig.suptitle('Temperature, Overflow test case') for iCol in range(num_files): ax = axs[iCol] - ds = xarray.open_dataset('output_{}.nc'.format(iCol + 1)) + ds = xarray.open_dataset(f'output_{iCol + 1}.nc') # Get the output times again # Don't assume that the output times are the same for all files From 93ee77abab8985931f2ae304bff738e0259babf5 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Mon, 30 Jan 2023 09:51:26 -0600 Subject: [PATCH 18/20] Cleanup initial state --- compass/ocean/tests/overflow/initial_state.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/compass/ocean/tests/overflow/initial_state.py b/compass/ocean/tests/overflow/initial_state.py index 43fc90dc00..e830de85f2 100644 --- a/compass/ocean/tests/overflow/initial_state.py +++ b/compass/ocean/tests/overflow/initial_state.py @@ -1,6 +1,4 @@ import xarray -import numpy -import matplotlib.pyplot as plt from mpas_tools.planar_hex import make_planar_hex_mesh from mpas_tools.io import write_netcdf @@ -21,7 +19,7 @@ def __init__(self, test_case): Parameters ---------- - test_case : compass.ocean.tests.overflow.default.Default + test_case : compass.testcase.Testcase The test case this step belongs to """ super().__init__(test_case=test_case, name='initial_state', ntasks=1, @@ -56,7 +54,7 @@ def run(self): logger.info(' * Make planar hex mesh') ds_mesh = make_planar_hex_mesh(nx=nx, ny=ny, dc=dc, nonperiodic_x=True, - nonperiodic_y=False) + nonperiodic_y=False) logger.info(' * Completed Make planar hex mesh') write_netcdf(ds_mesh, 'base_mesh.nc') @@ -64,7 +62,7 @@ def run(self): ds_mesh = cull(ds_mesh, logger=logger) logger.info(' * Convert mesh') ds_mesh = convert(ds_mesh, graphInfoFileName='culled_graph.info', - logger=logger) + logger=logger) logger.info(' * Completed Convert mesh') write_netcdf(ds_mesh, 'culled_mesh.nc') From 72b93eb728720fc9320aa6ea9437702a6540f8c5 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Mon, 30 Jan 2023 09:53:49 -0600 Subject: [PATCH 19/20] Move nus list to cfg --- compass/ocean/tests/overflow/overflow.cfg | 3 +++ .../ocean/tests/overflow/rpe_test/__init__.py | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/compass/ocean/tests/overflow/overflow.cfg b/compass/ocean/tests/overflow/overflow.cfg index 2abe5fd8c5..c3d1883005 100644 --- a/compass/ocean/tests/overflow/overflow.cfg +++ b/compass/ocean/tests/overflow/overflow.cfg @@ -6,3 +6,6 @@ width = 40 # The length of the domain in the along-slope dimension (km) length = 200 + +# Viscosity values to test for rpe test case +viscosities = 1, 5, 10, 100, 1000 diff --git a/compass/ocean/tests/overflow/rpe_test/__init__.py b/compass/ocean/tests/overflow/rpe_test/__init__.py index a37808018c..7cd0ac1903 100644 --- a/compass/ocean/tests/overflow/rpe_test/__init__.py +++ b/compass/ocean/tests/overflow/rpe_test/__init__.py @@ -32,15 +32,27 @@ def __init__(self, test_group, resolution='1km'): super().__init__(test_group=test_group, name=name, subdir=subdir) - nus = [1, 5, 10, 100, 1000] - self.resolution = resolution + self.nus = None + + + def configure(self): + """ + Modify the configuration options for this test case. + """ + config = self.config + resolution = self.resolution + + overflow.configure(resolution, config) + + nus = config.getlist('overflow', 'viscosities', dtype=float) + self.nus = nus self.add_step( InitialState(test_case=self)) for index, nu in enumerate(nus): - name = f'rpe_test_{index + 1}_nu_{nu}' + name = f'rpe_test_{index + 1}_nu_{int(nu)}' step = Forward( test_case=self, name=name, subdir=name, ntasks=64, min_tasks=16, openmp_threads=1, @@ -56,11 +68,4 @@ def __init__(self, test_group, resolution='1km'): self.add_step( Analysis(test_case=self, resolution=resolution, nus=nus)) - - def configure(self): - """ - Modify the configuration options for this test case. - """ - overflow.configure(self.resolution, self.config) - # no run() is needed because we're doing the default: running all steps From 8dee5775e66dba6ab614f421ce6190d7d81bdbc0 Mon Sep 17 00:00:00 2001 From: Carolyn Begeman Date: Mon, 30 Jan 2023 09:59:02 -0600 Subject: [PATCH 20/20] Cleanup rpe analysis --- compass/ocean/tests/overflow/rpe_test/analysis.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/compass/ocean/tests/overflow/rpe_test/analysis.py b/compass/ocean/tests/overflow/rpe_test/analysis.py index 04b2156133..ea4a09fe45 100644 --- a/compass/ocean/tests/overflow/rpe_test/analysis.py +++ b/compass/ocean/tests/overflow/rpe_test/analysis.py @@ -46,7 +46,7 @@ def __init__(self, test_case, resolution, nus): for index, nu in enumerate(nus): self.add_input_file( filename=f'output_{index+1}.nc', - target=f'../rpe_test_{index+1}_nu_{nu}/output.nc') + target=f'../rpe_test_{index+1}_nu_{int(nu)}/output.nc') self.add_output_file( filename=f'sections_overflow_{resolution}.png') @@ -73,7 +73,8 @@ def _plot(filename, nus, rpe): nus : list of float The viscosity values - rpe : float, dim len(nu) x len(time) + rpe : numpy.ndarray + The reference potential energy with size len(nu) x len(time) """ plt.switch_backend('Agg') @@ -135,15 +136,10 @@ def _plot(filename, nus, rpe): layer_thickness = ds.layerThickness layer_thickness_cell1 = layer_thickness[cell1_index, :] layer_thickness_cell2 = layer_thickness[cell2_index, :] - layer_thickness_x = layer_thickness_cell2[1:, :] ssh = ds.ssh ssh_cell1 = ssh[cell1_index] ssh_cell2 = ssh[cell2_index] - ssh_x = ssh_cell2[1:] - - z_index = xarray.DataArray(data=np.arange(n_vert_levels), - dims='nVertLevels') z_interface = np.zeros((nEdges_x, n_vert_levels + 1)) z_interface[:, 0] = 0.5 * (ssh_cell1.values + ssh_cell2.values)