-
Notifications
You must be signed in to change notification settings - Fork 42
Add MALI mesh convergence tests #690
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
Merged
trhille
merged 26 commits into
MPAS-Dev:main
from
matthewhoffman:landice/mesh_convergence_test
Apr 13, 2026
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
e51e3bf
Add landice mesh_convergence test case
matthewhoffman a812a87
Correct passiceTracer to have no vertical dim
matthewhoffman fcc862a
Remove velo cap to allow prescribed velocity to be used
matthewhoffman 2882850
Add Halfar mesh convergence test
matthewhoffman 8335a2c
Make each test case in the group define its own output stream contents
matthewhoffman 4e27b35
Get both convergence tests working together
matthewhoffman 9224dac
Add dome center error convergence in addition to rmse
matthewhoffman ef32615
Minor tweaks to configurations
matthewhoffman 42f1207
Add thickness advection version of horizontal_advection test
matthewhoffman 0059100
Fix name of passive tracer variable
andrewdnolan 9e6536f
Update analysis output files
andrewdnolan 10b9f06
Upate tracer field in the streams file
andrewdnolan 1a70fa1
Change restart output interval from 30 days to 30 years.
trhille 8abf2de
Update passiveTracer to passiveTracer2d in analysis.py
trhille fdb7016
Change clobber mode from truncate to overwrite
trhille f9c2f07
Fix isort issue from linter
trhille 96e0807
Suppress warnings with decode_timedelta=False
trhille fb2de39
Make 2, 4, 8, 16km the default resolution for horizontal_advection
trhille 8dfb101
Make third-order space and time default for convergence tests
trhille c814164
Create documentation for landice/mesh_convergence cases
trhille 688e4bb
Update MALI-Dev submodule to include recent bug fixes to RK
trhille 10006b9
Fix error when building docs
trhille c347683
Apply suggestions from code review
trhille a8ad846
Fix typo in landice dome setup
trhille 09c2e82
Raise ValueError instead of sys.exit
trhille 46607bc
Fix bug in landice mesh convergence plots
trhille File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Submodule MALI-Dev
updated
12 files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| from compass.landice.tests.mesh_convergence.halfar import Halfar | ||
| from compass.landice.tests.mesh_convergence.horizontal_advection import ( | ||
| HorizontalAdvection, | ||
| ) | ||
| from compass.landice.tests.mesh_convergence.horizontal_advection_thickness import ( # noqa | ||
| HorizontalAdvectionThickness, | ||
| ) | ||
| from compass.testgroup import TestGroup | ||
|
|
||
|
|
||
| class MeshConvergence(TestGroup): | ||
| """ | ||
| A test group for convergence tests with MALI | ||
| """ | ||
| def __init__(self, mpas_core): | ||
| """ | ||
| mpas_core : compass.landice.LandIce | ||
| the MPAS core that this test group belongs to | ||
| """ | ||
| super().__init__(mpas_core=mpas_core, name='mesh_convergence') | ||
|
|
||
| self.add_test_case(HorizontalAdvection(test_group=self)) | ||
| self.add_test_case(HorizontalAdvectionThickness(test_group=self)) | ||
| self.add_test_case(Halfar(test_group=self)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| from compass.step import Step | ||
|
|
||
|
|
||
| class ConvAnalysis(Step): | ||
| """ | ||
| A step for visualizing and/or analyzing the output from a convergence test | ||
| case | ||
|
|
||
| Attributes | ||
| ---------- | ||
| resolutions : list of int | ||
| The resolutions of the meshes that have been run | ||
| """ | ||
| def __init__(self, test_case, resolutions): | ||
| """ | ||
| Create the step | ||
|
|
||
| Parameters | ||
| ---------- | ||
| test_case : compass.TestCase | ||
| The test case this step belongs to | ||
|
|
||
| resolutions : list of int | ||
| The resolutions of the meshes that have been run | ||
| """ | ||
| super().__init__(test_case=test_case, name='analysis') | ||
| self.resolutions = resolutions | ||
|
|
||
| # typically, the analysis will rely on the output from the forward | ||
| # steps | ||
| for resolution in resolutions: | ||
| self.add_input_file( | ||
| filename='{}km_output.nc'.format(resolution), | ||
| target='../{}km/forward/output.nc'.format(resolution)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| from mpas_tools.io import write_netcdf | ||
| from mpas_tools.mesh.conversion import convert, cull | ||
| from mpas_tools.planar_hex import make_planar_hex_mesh | ||
| from mpas_tools.translate import center | ||
|
|
||
| from compass.model import make_graph_file | ||
| from compass.step import Step | ||
|
|
||
|
|
||
| class ConvInit(Step): | ||
| """ | ||
| A step for creating a mesh for a given resolution in a mesh convergence | ||
| test case. A child class of this step should then create an appropriate | ||
| initial condition. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| resolution : int | ||
| The resolution of the test case | ||
| """ | ||
| def __init__(self, test_case, resolution): | ||
| """ | ||
| Create the step | ||
|
|
||
| Parameters | ||
| ---------- | ||
| test_case : compass.TestCase | ||
| The test case this step belongs to | ||
|
|
||
| resolution : int | ||
| The resolution of the test case | ||
| """ | ||
| super().__init__(test_case=test_case, | ||
| name='{}km_init'.format(resolution), | ||
| subdir='{}km/init'.format(resolution)) | ||
|
|
||
| for file in ['mesh.nc', 'graph.info']: | ||
| self.add_output_file(file) | ||
|
|
||
| self.resolution = resolution | ||
|
|
||
| def run(self): | ||
| """ | ||
| Run this step of the test case | ||
| """ | ||
| logger = self.logger | ||
| config = self.config | ||
| resolution = float(self.resolution) | ||
|
|
||
| section = config['mesh_convergence'] | ||
| nx_1km = section.getint('nx_1km') | ||
| ny_1km = section.getint('ny_1km') | ||
| nx = int(nx_1km / resolution) | ||
| ny = int(ny_1km / resolution) | ||
| dc = resolution * 1e3 | ||
| nonperiodic = section.getboolean('nonperiodic') | ||
|
|
||
| ds_mesh = make_planar_hex_mesh(nx=nx, ny=ny, dc=dc, | ||
| nonperiodic_x=nonperiodic, | ||
| nonperiodic_y=nonperiodic) | ||
|
|
||
| ds_mesh = cull(ds_mesh, logger=logger) | ||
| ds_mesh = convert(ds_mesh, logger=logger) | ||
| center(ds_mesh) | ||
|
|
||
| write_netcdf(ds_mesh, 'mesh.nc') | ||
| make_graph_file('mesh.nc', 'graph.info') |
143 changes: 143 additions & 0 deletions
143
compass/landice/tests/mesh_convergence/conv_test_case.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| from compass.config import CompassConfigParser | ||
| from compass.landice.tests.mesh_convergence.forward import Forward | ||
| from compass.testcase import TestCase | ||
|
|
||
|
|
||
| class ConvTestCase(TestCase): | ||
| """ | ||
| A test case for various convergence tests on in MALI with planar, | ||
| doubly periodic meshes | ||
|
|
||
| Attributes | ||
| ---------- | ||
| resolutions : list of int | ||
| """ | ||
| def __init__(self, test_group, name): | ||
| """ | ||
| Create test case for creating a MALI mesh | ||
|
|
||
| Parameters | ||
| ---------- | ||
| test_group : compass.ocean.tests.mesh_convergence.MeshConvergence | ||
| The test group that this test case belongs to | ||
|
|
||
| name : str | ||
| The name of the test case | ||
| """ | ||
| super().__init__(test_group=test_group, name=name) | ||
| self.resolutions = None | ||
|
|
||
| # add the steps with default resolutions so they can be listed | ||
| config = CompassConfigParser() | ||
| module = 'compass.landice.tests.mesh_convergence' | ||
| config.add_from_package(module, 'mesh_convergence.cfg') | ||
| self._setup_steps(config) | ||
|
|
||
| def configure(self): | ||
| """ | ||
| Set config options for the test case | ||
| """ | ||
| config = self.config | ||
| # set up the steps again in case a user has provided new resolutions | ||
| self._setup_steps(config) | ||
|
|
||
| self.update_cores() | ||
|
|
||
| def update_cores(self): | ||
| """ Update the number of cores and min_tasks for each forward step """ | ||
|
|
||
| config = self.config | ||
|
|
||
| goal_cells_per_core = config.getfloat('mesh_convergence', | ||
| 'goal_cells_per_core') | ||
| max_cells_per_core = config.getfloat('mesh_convergence', | ||
| 'max_cells_per_core') | ||
|
|
||
| section = config['mesh_convergence'] | ||
| nx_1km = section.getint('nx_1km') | ||
| ny_1km = section.getint('ny_1km') | ||
|
|
||
| for resolution in self.resolutions: | ||
| nx = int(nx_1km / resolution) | ||
| ny = int(ny_1km / resolution) | ||
| # a heuristic based on | ||
| cell_count = nx * ny | ||
| # ideally, about 300 cells per core | ||
| # (make it a multiple of 4 because...it looks better?) | ||
| ntasks = max(1, 4 * round(cell_count / (4 * goal_cells_per_core))) | ||
| # In a pinch, about 3000 cells per core | ||
| min_tasks = max(1, round(cell_count / max_cells_per_core)) | ||
| step = self.steps[f'{resolution}km_forward'] | ||
| step.ntasks = ntasks | ||
| step.min_tasks = min_tasks | ||
|
|
||
| config.set('mesh_convergence', f'{resolution}km_ntasks', | ||
| str(ntasks)) | ||
| config.set('mesh_convergence', f'{resolution}km_min_tasks', | ||
| str(min_tasks)) | ||
|
|
||
| def _setup_steps(self, config): | ||
| """ | ||
| setup steps given resolutions | ||
|
|
||
| Parameters | ||
| ---------- | ||
| config : compass.config.CompassConfigParser | ||
| The config options containing the resolutions | ||
| """ | ||
|
|
||
| resolutions = config.getlist('mesh_convergence', 'resolutions', | ||
| dtype=int) | ||
|
|
||
| if self.resolutions is not None and self.resolutions == resolutions: | ||
| return | ||
|
|
||
| # start fresh with no steps | ||
| self.steps = dict() | ||
| self.steps_to_run = list() | ||
|
|
||
| self.resolutions = resolutions | ||
|
|
||
| for resolution in resolutions: | ||
| self.add_step(self.create_init(resolution=resolution)) | ||
| self.add_step(Forward(test_case=self, resolution=resolution)) | ||
|
|
||
| self.add_step(self.create_analysis(resolutions=resolutions)) | ||
|
|
||
| def create_init(self, resolution): | ||
| """ | ||
|
|
||
| Child class must override this to return an instance of a | ||
| ConvergenceInit step | ||
|
|
||
| Parameters | ||
| ---------- | ||
| resolution : int | ||
| The resolution of the step | ||
|
|
||
| Returns | ||
| ------- | ||
| init : compass.landice.tests.mesh_convergence.convergence_init.ConvergenceInit # noqa | ||
| The init step object | ||
| """ | ||
|
|
||
| pass | ||
|
|
||
| def create_analysis(self, resolutions): | ||
| """ | ||
|
|
||
| Child class must override this to return an instance of a | ||
| ConvergenceInit step | ||
|
|
||
| Parameters | ||
| ---------- | ||
| resolutions : list of int | ||
| The resolutions of the other steps in the test case | ||
|
|
||
| Returns | ||
| ------- | ||
| analysis : compass.landice.tests.mesh_convergence.conv_analysis.ConvAnalysis # noqa | ||
| The init step object | ||
| """ | ||
|
|
||
| pass |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.