From 85f5c9048cd6e402cfcca272305cdba57e8cab18 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 29 May 2025 04:12:44 -0500 Subject: [PATCH 1/2] Move tests out of `mpas_tools` --- conda_package/docs/api.rst | 10 -- conda_package/docs/cime.rst | 3 +- conda_package/recipe/meta.yaml | 4 +- .../{mpas_tools => }/tests/__init__.py | 0 .../tests/test_cime_constants.py | 14 +- .../{mpas_tools => }/tests/test_conversion.py | 17 +- .../{mpas_tools => }/tests/test_depth.py | 129 +++++++++------ .../{mpas_tools => }/tests/test_mesh_mask.py | 156 ++++++++++++------ .../tests/test_signed_distance.py | 65 +++----- .../{mpas_tools => }/tests/test_transects.py | 76 +++++---- .../tests/test_viz_transects.py | 0 11 files changed, 277 insertions(+), 197 deletions(-) rename conda_package/{mpas_tools => }/tests/__init__.py (100%) rename conda_package/{mpas_tools => }/tests/test_cime_constants.py (86%) rename conda_package/{mpas_tools => }/tests/test_conversion.py (95%) rename conda_package/{mpas_tools => }/tests/test_depth.py (53%) rename conda_package/{mpas_tools => }/tests/test_mesh_mask.py (60%) rename conda_package/{mpas_tools => }/tests/test_signed_distance.py (56%) rename conda_package/{mpas_tools => }/tests/test_transects.py (61%) rename conda_package/{mpas_tools => }/tests/test_viz_transects.py (100%) diff --git a/conda_package/docs/api.rst b/conda_package/docs/api.rst index 48f8e598e..35632d9d4 100644 --- a/conda_package/docs/api.rst +++ b/conda_package/docs/api.rst @@ -392,13 +392,3 @@ Visualization :toctree: generated/ register_sci_viz_colormaps - -Tests -===== - -.. currentmodule:: mpas_tools.tests.test_cime_constants - -.. autosummary:: - :toctree: generated/ - - test_cime_constants diff --git a/conda_package/docs/cime.rst b/conda_package/docs/cime.rst index 30a525dd5..f40e39cfe 100644 --- a/conda_package/docs/cime.rst +++ b/conda_package/docs/cime.rst @@ -8,8 +8,7 @@ sync with `CIME `_, which provides infrastructure and utilities for Earth System Models such at E3SM. Currently, we sync only those constants given numerical values in CIME, not those that are derivied from other constants. Constants are checked against their values on CIME's -master branch during tests of the conda build. See -:py:func:`mpas_tools.tests.test_cime_constants.test_cime_constants`. +master branch during tests of the conda build. Some of the constants most likely to be useful in MPAS-Tools, COMPASS and other related projects are: diff --git a/conda_package/recipe/meta.yaml b/conda_package/recipe/meta.yaml index d41c5f041..b0d0b372e 100644 --- a/conda_package/recipe/meta.yaml +++ b/conda_package/recipe/meta.yaml @@ -65,7 +65,7 @@ test: - mesh_tools/mesh_conversion_tools/test/Arctic_Ocean.geojson - mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc - mesh_tools/mesh_conversion_tools/test/land_mask_final.nc - - conda_package/mpas_tools/tests/* + - conda_package/tests/* imports: - mpas_tools - mpas_tools.mesh.conversion @@ -87,7 +87,7 @@ test: - MpasMaskCreator.x mesh.nc arctic_mask.nc -f mesh_tools/mesh_conversion_tools/test/Arctic_Ocean.geojson - planar_hex --nx=30 --ny=20 --dc=1000. --npx --npy --outFileName='nonperiodic_mesh_30x20_1km.nc' - MpasCellCuller.x nonperiodic_mesh_30x20_1km.nc culled_nonperiodic_mesh_30x20_1km.nc - - python -m pytest conda_package/mpas_tools/tests + - python -m pytest conda_package/tests - mark_horns_for_culling --help - set_lat_lon_fields_in_planar_grid --help - create_scrip_file_from_mpas_mesh --help diff --git a/conda_package/mpas_tools/tests/__init__.py b/conda_package/tests/__init__.py similarity index 100% rename from conda_package/mpas_tools/tests/__init__.py rename to conda_package/tests/__init__.py diff --git a/conda_package/mpas_tools/tests/test_cime_constants.py b/conda_package/tests/test_cime_constants.py similarity index 86% rename from conda_package/mpas_tools/tests/test_cime_constants.py rename to conda_package/tests/test_cime_constants.py index 25f0e140d..800f742f4 100644 --- a/conda_package/mpas_tools/tests/test_cime_constants.py +++ b/conda_package/tests/test_cime_constants.py @@ -1,6 +1,7 @@ -from mpas_tools.cime.constants import constants import requests +from mpas_tools.cime.constants import constants + def test_cime_constants(e3sm_tag='master'): """ @@ -14,7 +15,8 @@ def test_cime_constants(e3sm_tag='master'): resp = requests.get( f'https://raw.githubusercontent.com/E3SM-Project/E3SM/{e3sm_tag}/' - f'share/util/shr_const_mod.F90') + f'share/util/shr_const_mod.F90' + ) text = resp.text @@ -32,10 +34,10 @@ def test_cime_constants(e3sm_tag='master'): print(f'parsed: {constant} = {value}') if constant in constants: if isinstance(value, float): - print('verifying {}'.format(constant)) + print(f'verifying {constant}') assert value == constants[constant] else: - print('skipping verification for {}'.format(constant)) + print(f'skipping verification for {constant}') found[constant] = True else: @@ -46,7 +48,7 @@ def test_cime_constants(e3sm_tag='master'): all_found = True for constant in found: if not found[constant]: - print('{} was not found!'.format(constant)) + print(f'{constant} was not found!') all_found = False assert all_found @@ -60,7 +62,7 @@ def _parse_value(line): end = line.find('=') key = line[start:end] - line = line[end+1:] + line = line[end + 1 :] if '!' in line: line, _ = line.split('!', 1) diff --git a/conda_package/mpas_tools/tests/test_conversion.py b/conda_package/tests/test_conversion.py similarity index 95% rename from conda_package/mpas_tools/tests/test_conversion.py rename to conda_package/tests/test_conversion.py index ba28bcaab..093847d31 100755 --- a/conda_package/mpas_tools/tests/test_conversion.py +++ b/conda_package/tests/test_conversion.py @@ -1,26 +1,31 @@ #!/usr/bin/env python -from mpas_tools.mesh.conversion import convert, cull, mask -from mpas_tools.io import write_netcdf import matplotlib + +from mpas_tools.io import write_netcdf +from mpas_tools.mesh.conversion import convert, cull, mask + matplotlib.use('Agg') -from geometric_features import read_feature_collection import xarray +from geometric_features import read_feature_collection def test_conversion(): dsMesh = xarray.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc') + 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc' + ) dsMesh = convert(dsIn=dsMesh) write_netcdf(dsMesh, 'mesh.nc') dsMask = xarray.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/land_mask_final.nc') + 'mesh_tools/mesh_conversion_tools/test/land_mask_final.nc' + ) dsCulled = cull(dsIn=dsMesh, dsMask=dsMask) write_netcdf(dsCulled, 'culled_mesh.nc') fcMask = read_feature_collection( - 'mesh_tools/mesh_conversion_tools/test/Arctic_Ocean.geojson') + 'mesh_tools/mesh_conversion_tools/test/Arctic_Ocean.geojson' + ) dsMask = mask(dsMesh=dsMesh, fcMask=fcMask) write_netcdf(dsMask, 'antarctic_mask.nc') diff --git a/conda_package/mpas_tools/tests/test_depth.py b/conda_package/tests/test_depth.py similarity index 53% rename from conda_package/mpas_tools/tests/test_depth.py rename to conda_package/tests/test_depth.py index 0394b8a5e..222b954e0 100644 --- a/conda_package/mpas_tools/tests/test_depth.py +++ b/conda_package/tests/test_depth.py @@ -1,11 +1,17 @@ #!/usr/bin/env python +import os + import numpy import xarray -import os from mpas_tools.io import write_netcdf -from mpas_tools.ocean.depth import compute_depth, compute_zmid, add_depth, \ - add_zmid, write_time_varying_zmid +from mpas_tools.ocean.depth import ( + add_depth, + add_zmid, + compute_depth, + compute_zmid, + write_time_varying_zmid, +) def create_3d_mesh(): @@ -14,19 +20,25 @@ def create_3d_mesh(): dsMesh = xarray.open_dataset(outFileName) else: dsMesh = xarray.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc') + 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc' + ) nCells = dsMesh.sizes['nCells'] nVertLevels = 10 - zmax = 1000. - layerThickness = zmax/nVertLevels - dsMesh['refBottomDepth'] = \ - ('nVertLevels', numpy.linspace(layerThickness, zmax, nVertLevels)) - dsMesh['maxLevelCell'] = \ - ('nCells', nVertLevels*numpy.ones(nCells, dtype=int)) - dsMesh['bottomDepth'] = ('nCells', zmax*numpy.ones(nCells)) - dsMesh['layerThickness'] = \ - (('Time', 'nCells', 'nVertLevels'), - layerThickness*numpy.ones((1, nCells, nVertLevels))) + zmax = 1000.0 + layerThickness = zmax / nVertLevels + dsMesh['refBottomDepth'] = ( + 'nVertLevels', + numpy.linspace(layerThickness, zmax, nVertLevels), + ) + dsMesh['maxLevelCell'] = ( + 'nCells', + nVertLevels * numpy.ones(nCells, dtype=int), + ) + dsMesh['bottomDepth'] = ('nCells', zmax * numpy.ones(nCells)) + dsMesh['layerThickness'] = ( + ('Time', 'nCells', 'nVertLevels'), + layerThickness * numpy.ones((1, nCells, nVertLevels)), + ) write_netcdf(dsMesh, 'test_depth_mesh.nc') return dsMesh @@ -35,23 +47,30 @@ def create_3d_mesh(): def test_compute_depth(): dsMesh = create_3d_mesh() depth, depth_bnds = compute_depth(dsMesh.refBottomDepth) - assert numpy.all(numpy.isclose(depth, numpy.linspace(50., 950., 10))) - assert numpy.all(numpy.isclose(depth_bnds[:, 0], - numpy.linspace(0., 900., 10))) - assert numpy.all(numpy.isclose(depth_bnds[:, 1], - numpy.linspace(100., 1000., 10))) + assert numpy.all(numpy.isclose(depth, numpy.linspace(50.0, 950.0, 10))) + assert numpy.all( + numpy.isclose(depth_bnds[:, 0], numpy.linspace(0.0, 900.0, 10)) + ) + assert numpy.all( + numpy.isclose(depth_bnds[:, 1], numpy.linspace(100.0, 1000.0, 10)) + ) def test_compute_zmid(): dsMesh = create_3d_mesh() - zMid = compute_zmid(dsMesh.bottomDepth, dsMesh.maxLevelCell, - dsMesh.layerThickness, depth_dim='nVertLevels') + zMid = compute_zmid( + dsMesh.bottomDepth, + dsMesh.maxLevelCell, + dsMesh.layerThickness, + depth_dim='nVertLevels', + ) assert zMid.dims == ('Time', 'nCells', 'nVertLevels') depth = zMid.isel(Time=0, nCells=0) - assert numpy.all(numpy.isclose(depth.values, - numpy.linspace(-50., -950., 10))) + assert numpy.all( + numpy.isclose(depth.values, numpy.linspace(-50.0, -950.0, 10)) + ) def test_add_depth(): @@ -65,15 +84,18 @@ def test_add_depth(): # test adding depth coordinate once to the mesh and once to the input file, # with the mesh passed in separately - for in_filename, coord_filename in [(mesh_filename, None), - ('test_depth_in.nc', mesh_filename)]: + for in_filename, coord_filename in [ + (mesh_filename, None), + ('test_depth_in.nc', mesh_filename), + ]: add_depth(in_filename, out_filename, coordFileName=coord_filename) dsOut = xarray.open_dataset(out_filename) assert 'depth' in dsOut.dims depth = dsOut.depth - assert numpy.all(numpy.isclose(depth.values, - numpy.linspace(50., 950., 10))) + assert numpy.all( + numpy.isclose(depth.values, numpy.linspace(50.0, 950.0, 10)) + ) def test_add_zmid(): @@ -87,8 +109,10 @@ def test_add_zmid(): # test adding zMid once to the mesh and once to the input file, with the # mesh passed in separately - for in_filename, coord_filename in [(mesh_filename, None), - ('test_depth_in.nc', mesh_filename)]: + for in_filename, coord_filename in [ + (mesh_filename, None), + ('test_depth_in.nc', mesh_filename), + ]: add_zmid(in_filename, out_filename, coordFileName=coord_filename) dsOut = xarray.open_dataset(out_filename) assert 'depth' in dsOut.dims @@ -97,12 +121,12 @@ def test_add_zmid(): assert zMid.dims == ('nCells', 'depth') depth = zMid.isel(nCells=0) - assert numpy.all(numpy.isclose(depth.values, - numpy.linspace(-50., -950., 10))) + assert numpy.all( + numpy.isclose(depth.values, numpy.linspace(-50.0, -950.0, 10)) + ) def test_write_time_varying_zmid(): - dsMesh = create_3d_mesh() nCells = dsMesh.sizes['nCells'] nVertLevels = dsMesh.sizes['nVertLevels'] @@ -110,41 +134,50 @@ def test_write_time_varying_zmid(): in_filename = 'test_depth_in.nc' out_filename = 'test_depth_out.nc' - layerThickness = 100. + layerThickness = 100.0 # test adding zMid once to the mesh and once to the input file, with the # mesh passed in separately, each one without and once with a prefix - for coord_filename, prefix in [(None, ''), - (mesh_filename, ''), - (None, 'timeMonthly_avg_'), - (mesh_filename, 'timeMonthly_avg_')]: + for coord_filename, prefix in [ + (None, ''), + (mesh_filename, ''), + (None, 'timeMonthly_avg_'), + (mesh_filename, 'timeMonthly_avg_'), + ]: print(coord_filename, prefix) if coord_filename is None: dsIn = dsMesh.drop_vars('layerThickness') else: dsIn = xarray.Dataset() - layerThicknessVar = '{}layerThickness'.format(prefix) - dsIn[layerThicknessVar] = \ - (('Time', 'nCells', 'nVertLevels'), - layerThickness*numpy.ones((2, nCells, nVertLevels))) - dsIn['{}temperature'.format(prefix)] = \ - xarray.ones_like(dsIn[layerThicknessVar]) + layerThicknessVar = f'{prefix}layerThickness' + dsIn[layerThicknessVar] = ( + ('Time', 'nCells', 'nVertLevels'), + layerThickness * numpy.ones((2, nCells, nVertLevels)), + ) + dsIn[f'{prefix}temperature'] = xarray.ones_like( + dsIn[layerThicknessVar] + ) write_netcdf(dsIn, in_filename) dsIn.close() - write_time_varying_zmid(in_filename, out_filename, - coordFileName=coord_filename, prefix=prefix) + write_time_varying_zmid( + in_filename, + out_filename, + coordFileName=coord_filename, + prefix=prefix, + ) dsOut = xarray.open_dataset(out_filename) assert 'depth' in dsOut.dims - zMid = dsOut['{}zMid'.format(prefix)] + zMid = dsOut[f'{prefix}zMid'] assert zMid.dims == ('Time', 'nCells', 'depth') depth = zMid.isel(Time=0, nCells=0) - assert numpy.all(numpy.isclose(depth.values, - numpy.linspace(-50., -950., 10))) + assert numpy.all( + numpy.isclose(depth.values, numpy.linspace(-50.0, -950.0, 10)) + ) dsOut.close() diff --git a/conda_package/mpas_tools/tests/test_mesh_mask.py b/conda_package/tests/test_mesh_mask.py similarity index 60% rename from conda_package/mpas_tools/tests/test_mesh_mask.py rename to conda_package/tests/test_mesh_mask.py index ec8e347a3..4805d1823 100644 --- a/conda_package/mpas_tools/tests/test_mesh_mask.py +++ b/conda_package/tests/test_mesh_mask.py @@ -1,55 +1,79 @@ #!/usr/bin/env python import multiprocessing + import numpy as np -import xarray as xr import pyproj - -from geometric_features import GeometricFeatures, FeatureCollection +import xarray as xr +from geometric_features import FeatureCollection, GeometricFeatures from mpas_tools.cime.constants import constants from mpas_tools.io import write_netcdf from mpas_tools.mesh.conversion import convert, cull -from mpas_tools.mesh.mask import compute_mpas_region_masks, \ - compute_mpas_transect_masks, compute_mpas_flood_fill_mask, \ - compute_lon_lat_region_masks, compute_projection_grid_region_masks +from mpas_tools.mesh.mask import ( + compute_lon_lat_region_masks, + compute_mpas_flood_fill_mask, + compute_mpas_region_masks, + compute_mpas_transect_masks, + compute_projection_grid_region_masks, +) def test_compute_mpas_region_masks(): ds_mesh, _ = _get_mesh() gf = GeometricFeatures() - fc_mask = gf.read(componentName='ocean', objectType='region', - featureNames=['North Atlantic Ocean']) + fc_mask = gf.read( + componentName='ocean', + objectType='region', + featureNames=['North Atlantic Ocean'], + ) pool = _get_pool() ds_masks = compute_mpas_region_masks( - ds_mesh, fc_mask, maskTypes=('cell', 'edge', 'vertex'), pool=pool) + ds_mesh, fc_mask, maskTypes=('cell', 'edge', 'vertex'), pool=pool + ) for dim in ['nCells', 'nEdges', 'nVertices', 'nRegions']: assert dim in ds_masks.dims - for var in ['regionCellMasks', 'regionEdgeMasks', 'regionVertexMasks', - 'regionNames']: + for var in [ + 'regionCellMasks', + 'regionEdgeMasks', + 'regionVertexMasks', + 'regionNames', + ]: assert var in ds_masks.data_vars def test_compute_mpas_transect_masks_no_edge_sign(): ds_mesh, earth_radius = _get_mesh() gf = GeometricFeatures() - fc_mask = gf.read(componentName='ocean', objectType='transect', - featureNames=['Drake Passage']) + fc_mask = gf.read( + componentName='ocean', + objectType='transect', + featureNames=['Drake Passage'], + ) pool = _get_pool() ds_masks = compute_mpas_transect_masks( - ds_mesh, fc_mask, earth_radius, maskTypes=('cell', 'edge', 'vertex'), - pool=pool, addEdgeSign=False) + ds_mesh, + fc_mask, + earth_radius, + maskTypes=('cell', 'edge', 'vertex'), + pool=pool, + addEdgeSign=False, + ) # write_netcdf(ds_masks, 'transect_masks.nc') for dim in ['nCells', 'nEdges', 'nVertices', 'nTransects']: assert dim in ds_masks.dims - for var in ['transectCellMasks', 'transectEdgeMasks', - 'transectVertexMasks', 'transectNames']: + for var in [ + 'transectCellMasks', + 'transectEdgeMasks', + 'transectVertexMasks', + 'transectNames', + ]: assert var in ds_masks.data_vars @@ -57,13 +81,21 @@ def test_compute_mpas_transect_masks_edge_sign(): ds_mesh, earth_radius = _get_mesh() # write_netcdf(ds_mesh, 'mesh.nc') gf = GeometricFeatures() - fc_mask = gf.read(componentName='ocean', objectType='transect', - featureNames=['Drake Passage']) + fc_mask = gf.read( + componentName='ocean', + objectType='transect', + featureNames=['Drake Passage'], + ) pool = _get_pool() ds_masks = compute_mpas_transect_masks( - ds_mesh, fc_mask, earth_radius, maskTypes=('edge',), - pool=pool, addEdgeSign=True) + ds_mesh, + fc_mask, + earth_radius, + maskTypes=('edge',), + pool=pool, + addEdgeSign=True, + ) # write_netcdf(ds_masks, 'transect_edge_mask.nc') for dim in ['nEdges', 'nTransects']: @@ -76,33 +108,31 @@ def test_compute_mpas_transect_masks_edge_sign(): def test_compute_mpas_flood_fill_mask(): ds_mesh, earth_radius = _get_mesh() gf = GeometricFeatures() - fc_mask = gf.read(componentName='ocean', objectType='region', - featureNames=['Global Ocean 15S to 15N']) + fc_mask = gf.read( + componentName='ocean', + objectType='region', + featureNames=['Global Ocean 15S to 15N'], + ) pool = _get_pool() ds_mask = compute_mpas_region_masks( - ds_mesh, fc_mask, maskTypes=('cell',), pool=pool) + ds_mesh, fc_mask, maskTypes=('cell',), pool=pool + ) ds_mesh = cull(ds_mesh, ds_mask) # write_netcdf(ds_mesh, 'culled_mesh.nc') feature = { - "type": "Feature", - "properties": { - "name": "Mid North Atlantic", - "tags": "", - "object": "point", - "component": "ocean", - "author": "Xylar Asay-Davis" - }, - "geometry": { - "type": "Point", - "coordinates": [ - -40.000000, - 40.000000 - ] - } - } + 'type': 'Feature', + 'properties': { + 'name': 'Mid North Atlantic', + 'tags': '', + 'object': 'point', + 'component': 'ocean', + 'author': 'Xylar Asay-Davis', + }, + 'geometry': {'type': 'Point', 'coordinates': [-40.000000, 40.000000]}, + } fc_seed = FeatureCollection() fc_seed.add_feature(feature) @@ -117,8 +147,11 @@ def test_compute_lon_lat_region_masks(): lon = np.linspace(-180, 180, 37) lat = np.linspace(-90, 90, 19) gf = GeometricFeatures() - fc_mask = gf.read(componentName='ocean', objectType='region', - featureNames=['North Atlantic Ocean']) + fc_mask = gf.read( + componentName='ocean', + objectType='region', + featureNames=['North Atlantic Ocean'], + ) pool = _get_pool() ds_mask = compute_lon_lat_region_masks(lon, lat, fc_mask, pool=pool) # write_netcdf(ds_mask, 'mask_lon_lat.nc') @@ -134,20 +167,25 @@ def test_compute_projection_grid_region_masks(): x = np.linspace(-6000e3, 6000e3, 121) y = np.linspace(-6000e3, 6000e3, 121) - projection = pyproj.Proj('+proj=stere +lat_ts=-71.0 +lat_0=-90 +lon_0=0.0 ' - '+k_0=1.0 +x_0=0.0 +y_0=0.0 +ellps=WGS84') + projection = pyproj.Proj( + '+proj=stere +lat_ts=-71.0 +lat_0=-90 +lon_0=0.0 ' + '+k_0=1.0 +x_0=0.0 +y_0=0.0 +ellps=WGS84' + ) lat_lon_projection = pyproj.Proj(proj='latlong', datum='WGS84') - transformer = pyproj.Transformer.from_proj(projection, - lat_lon_projection) + transformer = pyproj.Transformer.from_proj(projection, lat_lon_projection) x_grid, y_grid = np.meshgrid(x, y) lon, lat = transformer.transform(x_grid, y_grid) gf = GeometricFeatures() - fc_mask = gf.read(componentName='landice', objectType='region', - featureNames=['ISMIP6 Basin A-Ap']) + fc_mask = gf.read( + componentName='landice', + objectType='region', + featureNames=['ISMIP6 Basin A-Ap'], + ) pool = _get_pool() ds_mask = compute_projection_grid_region_masks( - lon, lat, fc_mask, pool=pool) + lon, lat, fc_mask, pool=pool + ) ds_mask['lon'] = (('y', 'x'), lon) ds_mask['lat'] = (('y', 'x'), lat) write_netcdf(ds_mask, 'mask_proj.nc') @@ -168,12 +206,24 @@ def _get_pool(): def _get_mesh(): ds_mesh = xr.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc') + 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc' + ) earth_radius = constants['SHR_CONST_REARTH'] ds_mesh.attrs['sphere_radius'] = earth_radius - for coord in ['xCell', 'yCell', 'zCell', 'xVertex', 'yVertex', 'zVertex', - 'xEdge', 'yEdge', 'zEdge', 'dcEdge', 'dvEdge']: - ds_mesh[coord] = earth_radius*ds_mesh[coord] + for coord in [ + 'xCell', + 'yCell', + 'zCell', + 'xVertex', + 'yVertex', + 'zVertex', + 'xEdge', + 'yEdge', + 'zEdge', + 'dcEdge', + 'dvEdge', + ]: + ds_mesh[coord] = earth_radius * ds_mesh[coord] ds_mesh = convert(ds_mesh) return ds_mesh, earth_radius diff --git a/conda_package/mpas_tools/tests/test_signed_distance.py b/conda_package/tests/test_signed_distance.py similarity index 56% rename from conda_package/mpas_tools/tests/test_signed_distance.py rename to conda_package/tests/test_signed_distance.py index 86810dc84..33fc21df6 100644 --- a/conda_package/mpas_tools/tests/test_signed_distance.py +++ b/conda_package/tests/test_signed_distance.py @@ -2,21 +2,23 @@ import numpy as np import xarray as xr - from geometric_features import FeatureCollection from mpas_tools.cime.constants import constants from mpas_tools.io import write_netcdf -from mpas_tools.mesh.creation.signed_distance import \ - signed_distance_from_geojson, distance_from_geojson, mask_from_geojson +from mpas_tools.mesh.creation.signed_distance import ( + distance_from_geojson, + mask_from_geojson, + signed_distance_from_geojson, +) def test_signed_distance_from_geojson(): - lon, lat, fc_mask, earth_radius = _get_lon_lat_fc() signed_distance = signed_distance_from_geojson( - fc_mask, lon, lat, earth_radius, max_length=5.) + fc_mask, lon, lat, earth_radius, max_length=5.0 + ) ds = xr.Dataset() ds['lon'] = ('lon', lon) @@ -29,7 +31,8 @@ def test_distance_from_geojson(): lon, lat, fc_mask, earth_radius = _get_lon_lat_fc() distance = distance_from_geojson( - fc_mask, lon, lat, earth_radius, max_length=5.) + fc_mask, lon, lat, earth_radius, max_length=5.0 + ) ds = xr.Dataset() ds['lon'] = ('lon', lon) @@ -51,47 +54,31 @@ def test_mask_from_geojson(): def _get_lon_lat_fc(): - lon = np.linspace(-180, 180, 37) lat = np.linspace(-90, 90, 19) earth_radius = constants['SHR_CONST_REARTH'] feature = { - "type": "Feature", - "properties": { - "name": "North Atlantic", - "tags": "", - "object": "region", - "component": "ocean", - "author": "Xylar Asay-Davis" + 'type': 'Feature', + 'properties': { + 'name': 'North Atlantic', + 'tags': '', + 'object': 'region', + 'component': 'ocean', + 'author': 'Xylar Asay-Davis', }, - "geometry": { - "type": "Polygon", - "coordinates": [ + 'geometry': { + 'type': 'Polygon', + 'coordinates': [ [ - [ - -39.53161633291441, - 57.08649995213068 - ], - [ - -69.30597933223675, - 28.03212363054105 - ], - [ - -33.914428822900334, - 13.4287331666755 - ], - [ - -15.9735991802836, - 39.731395665957876 - ], - [ - -39.53161633291441, - 57.08649995213068 - ] + [-39.53161633291441, 57.08649995213068], + [-69.30597933223675, 28.03212363054105], + [-33.914428822900334, 13.4287331666755], + [-15.9735991802836, 39.731395665957876], + [-39.53161633291441, 57.08649995213068], ] - ] - } + ], + }, } fc_mask = FeatureCollection() fc_mask.add_feature(feature) diff --git a/conda_package/mpas_tools/tests/test_transects.py b/conda_package/tests/test_transects.py similarity index 61% rename from conda_package/mpas_tools/tests/test_transects.py rename to conda_package/tests/test_transects.py index 1ac57ce59..c53449a09 100755 --- a/conda_package/mpas_tools/tests/test_transects.py +++ b/conda_package/tests/test_transects.py @@ -8,7 +8,7 @@ cartesian_to_great_circle_distance, lon_lat_to_cartesian, subdivide_great_circle, - subdivide_planar + subdivide_planar, ) from mpas_tools.vector import Vector @@ -17,7 +17,8 @@ def test_subdivide_great_circle(): _, _, x, y, z, earth_radius = _get_transect() max_res = 10e3 x_div, y_div, z_div, d, d_div = subdivide_great_circle( - x, y, z, max_res, earth_radius) + x, y, z, max_res, earth_radius + ) assert np.amax(d_div <= max_res) @@ -28,8 +29,8 @@ def test_cartesian_to_great_circle_distance(): def test_subdivide_planar(): - x = 1e3*np.array([-10., 0., 10.]) - y = 1e3*np.array([-10., 0., 10.]) + x = 1e3 * np.array([-10.0, 0.0, 10.0]) + y = 1e3 * np.array([-10.0, 0.0, 10.0]) max_res = 1e3 x_div, y_div, d, d_div = subdivide_planar(y, x, max_res) assert np.amax(d_div <= max_res) @@ -51,52 +52,65 @@ def test_angular_distance_first_second(): def test_intersects_scalar(): - a1 = _lon_lat_to_vector(-10., -10.) - a2 = _lon_lat_to_vector(10., 10.) - b1 = _lon_lat_to_vector(-10., 10.) - b2 = _lon_lat_to_vector(10., -10.) + a1 = _lon_lat_to_vector(-10.0, -10.0) + a2 = _lon_lat_to_vector(10.0, 10.0) + b1 = _lon_lat_to_vector(-10.0, 10.0) + b2 = _lon_lat_to_vector(10.0, -10.0) assert Vector.intersects(a1, a2, b1, b2) def test_intersects_array(): - a1 = _lon_lat_to_vector(np.array([-10., -5.]), np.array([-10., -5.])) - a2 = _lon_lat_to_vector(np.array([-5., 10.]), np.array([-5., 10.])) - b1 = _lon_lat_to_vector(-10., 10.) - b2 = _lon_lat_to_vector(10., -10.) + a1 = _lon_lat_to_vector(np.array([-10.0, -5.0]), np.array([-10.0, -5.0])) + a2 = _lon_lat_to_vector(np.array([-5.0, 10.0]), np.array([-5.0, 10.0])) + b1 = _lon_lat_to_vector(-10.0, 10.0) + b2 = _lon_lat_to_vector(10.0, -10.0) result = Vector.intersects(a1, a2, b1, b2) assert np.all(result == np.array([False, True])) def test_intersection_scalar(): - a1 = _lon_lat_to_vector(-10., -10.) - a2 = _lon_lat_to_vector(10., 10.) - b1 = _lon_lat_to_vector(-10., 10.) - b2 = _lon_lat_to_vector(10., -10.) + a1 = _lon_lat_to_vector(-10.0, -10.0) + a2 = _lon_lat_to_vector(10.0, 10.0) + b1 = _lon_lat_to_vector(-10.0, 10.0) + b2 = _lon_lat_to_vector(10.0, -10.0) earth_radius = constants['SHR_CONST_REARTH'] - cross = _lon_lat_to_vector(0., 0.) + cross = _lon_lat_to_vector(0.0, 0.0) point = Vector.intersection(a1, a2, b1, b2) - assert np.all(np.isclose( - [earth_radius*point.x, earth_radius*point.y, earth_radius*point.z], - [cross.x, cross.y, cross.z])) + assert np.all( + np.isclose( + [ + earth_radius * point.x, + earth_radius * point.y, + earth_radius * point.z, + ], + [cross.x, cross.y, cross.z], + ) + ) def test_intersection_array(): - a1 = _lon_lat_to_vector(np.array([-10.]), np.array([-10.])) - a2 = _lon_lat_to_vector(np.array([10.]), np.array([10.])) - b1 = _lon_lat_to_vector(-10., 10.) - b2 = _lon_lat_to_vector(10., -10.) + a1 = _lon_lat_to_vector(np.array([-10.0]), np.array([-10.0])) + a2 = _lon_lat_to_vector(np.array([10.0]), np.array([10.0])) + b1 = _lon_lat_to_vector(-10.0, 10.0) + b2 = _lon_lat_to_vector(10.0, -10.0) earth_radius = constants['SHR_CONST_REARTH'] - cross = _lon_lat_to_vector(0., 0.) + cross = _lon_lat_to_vector(0.0, 0.0) point = Vector.intersection(a1, a2, b1, b2) - assert np.all(np.isclose( - [earth_radius*point.x[0], earth_radius*point.y[0], - earth_radius*point.z[0]], - [cross.x, cross.y, cross.z])) + assert np.all( + np.isclose( + [ + earth_radius * point.x[0], + earth_radius * point.y[0], + earth_radius * point.z[0], + ], + [cross.x, cross.y, cross.z], + ) + ) def _get_transect(): - lon = np.array([-10., 0., 10.]) - lat = np.array([-10., 0., 10.]) + lon = np.array([-10.0, 0.0, 10.0]) + lat = np.array([-10.0, 0.0, 10.0]) earth_radius = constants['SHR_CONST_REARTH'] x, y, z = lon_lat_to_cartesian(lon, lat, earth_radius, degrees=True) return lon, lat, x, y, z, earth_radius diff --git a/conda_package/mpas_tools/tests/test_viz_transects.py b/conda_package/tests/test_viz_transects.py similarity index 100% rename from conda_package/mpas_tools/tests/test_viz_transects.py rename to conda_package/tests/test_viz_transects.py From 1ef9529a721c350fe69a60d0d473dcf641648f69 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 29 May 2025 04:54:16 -0500 Subject: [PATCH 2/2] Use a utility to find test data files --- conda_package/tests/test_conversion.py | 10 ++++---- conda_package/tests/test_depth.py | 4 ++- conda_package/tests/test_mesh_mask.py | 6 ++--- conda_package/tests/test_viz_transects.py | 6 ++--- conda_package/tests/util.py | 31 +++++++++++++++++++++++ 5 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 conda_package/tests/util.py diff --git a/conda_package/tests/test_conversion.py b/conda_package/tests/test_conversion.py index 093847d31..348da88cf 100755 --- a/conda_package/tests/test_conversion.py +++ b/conda_package/tests/test_conversion.py @@ -5,6 +5,8 @@ from mpas_tools.io import write_netcdf from mpas_tools.mesh.conversion import convert, cull, mask +from .util import get_test_data_file + matplotlib.use('Agg') import xarray from geometric_features import read_feature_collection @@ -12,19 +14,17 @@ def test_conversion(): dsMesh = xarray.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc' + get_test_data_file('mesh.QU.1920km.151026.nc') ) dsMesh = convert(dsIn=dsMesh) write_netcdf(dsMesh, 'mesh.nc') - dsMask = xarray.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/land_mask_final.nc' - ) + dsMask = xarray.open_dataset(get_test_data_file('land_mask_final.nc')) dsCulled = cull(dsIn=dsMesh, dsMask=dsMask) write_netcdf(dsCulled, 'culled_mesh.nc') fcMask = read_feature_collection( - 'mesh_tools/mesh_conversion_tools/test/Arctic_Ocean.geojson' + get_test_data_file('Arctic_Ocean.geojson') ) dsMask = mask(dsMesh=dsMesh, fcMask=fcMask) write_netcdf(dsMask, 'antarctic_mask.nc') diff --git a/conda_package/tests/test_depth.py b/conda_package/tests/test_depth.py index 222b954e0..4e8fc8098 100644 --- a/conda_package/tests/test_depth.py +++ b/conda_package/tests/test_depth.py @@ -13,6 +13,8 @@ write_time_varying_zmid, ) +from .util import get_test_data_file + def create_3d_mesh(): outFileName = 'test_depth_mesh.nc' @@ -20,7 +22,7 @@ def create_3d_mesh(): dsMesh = xarray.open_dataset(outFileName) else: dsMesh = xarray.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc' + get_test_data_file('mesh.QU.1920km.151026.nc') ) nCells = dsMesh.sizes['nCells'] nVertLevels = 10 diff --git a/conda_package/tests/test_mesh_mask.py b/conda_package/tests/test_mesh_mask.py index 4805d1823..97fb9035e 100644 --- a/conda_package/tests/test_mesh_mask.py +++ b/conda_package/tests/test_mesh_mask.py @@ -18,6 +18,8 @@ compute_projection_grid_region_masks, ) +from .util import get_test_data_file + def test_compute_mpas_region_masks(): ds_mesh, _ = _get_mesh() @@ -205,9 +207,7 @@ def _get_pool(): def _get_mesh(): - ds_mesh = xr.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc' - ) + ds_mesh = xr.open_dataset(get_test_data_file('mesh.QU.1920km.151026.nc')) earth_radius = constants['SHR_CONST_REARTH'] ds_mesh.attrs['sphere_radius'] = earth_radius for coord in [ diff --git a/conda_package/tests/test_viz_transects.py b/conda_package/tests/test_viz_transects.py index 8fded2d56..e012f2d33 100755 --- a/conda_package/tests/test_viz_transects.py +++ b/conda_package/tests/test_viz_transects.py @@ -12,6 +12,8 @@ make_triangle_tree, ) +from .util import get_test_data_file + def test_mesh_to_triangles(): _, ds_tris = _get_triangles() @@ -129,9 +131,7 @@ def test_find_planar_transect_cells_and_weights(): def _get_triangles(): - ds_mesh = xr.open_dataset( - 'mesh_tools/mesh_conversion_tools/test/mesh.QU.1920km.151026.nc' - ) + ds_mesh = xr.open_dataset(get_test_data_file('mesh.QU.1920km.151026.nc')) earth_radius = constants['SHR_CONST_REARTH'] ds_mesh.attrs['sphere_radius'] = earth_radius for coord in [ diff --git a/conda_package/tests/util.py b/conda_package/tests/util.py new file mode 100644 index 000000000..337b2a349 --- /dev/null +++ b/conda_package/tests/util.py @@ -0,0 +1,31 @@ +import os + + +def get_test_data_file(filename): + """ + Get the full path to a data file in the tests/data directory. + + Parameters + ---------- + filename : str + The name of the data file. + + Returns + ------- + str + The full relative path to the data file. + """ + + local_path = os.path.join( + 'mesh_tools', 'mesh_conversion_tools', 'test', filename + ) + repo_path = os.path.join('..', '..', local_path) + if os.path.exists(local_path): + return local_path + elif os.path.exists(repo_path): + return repo_path + + raise FileNotFoundError( + f"Data file '{filename}' not found in expected locations: " + f'{local_path} or {repo_path}' + )