diff --git a/conda_package/mpas_tools/io.py b/conda_package/mpas_tools/io.py index d9e5133d1..78606ef07 100644 --- a/conda_package/mpas_tools/io.py +++ b/conda_package/mpas_tools/io.py @@ -13,6 +13,7 @@ default_engine = None default_char_dim_name = 'StrLen' default_fills = netCDF4.default_fillvals +default_nchar = 64 def write_netcdf( @@ -23,6 +24,7 @@ def write_netcdf( engine=None, char_dim_name=None, logger=None, + nchar=None, ): """ Write an xarray.Dataset to a file with NetCDF4 fill values and the given @@ -31,9 +33,9 @@ def write_netcdf( Note: the ``NETCDF3_64BIT_DATA`` format is handled as a special case because xarray output with this format is not performant. First, the file - is written in `NETCDF4` format, which supports larger files and variables. - Then, the `ncks` command is used to convert the file to the - `NETCDF3_64BIT_DATA` format. + is written in ``NETCDF4`` format, which supports larger files and + variables. Then, the ``ncks`` command is used to convert the file to the + ``NETCDF3_64BIT_DATA`` format. Note: All int64 variables are automatically converted to int32 for MPAS compatibility. @@ -63,15 +65,19 @@ def write_netcdf( ``mpas_tools.io.default_engine`` char_dim_name : str, optional - The name of the dimension used for character strings, or None to let - xarray figure this out. Default is + The name of the dimension used for character strings. Default is ``mpas_tools.io.default_char_dim_name``, which can be modified but which defaults to ``'StrLen'`` + nchar : int, optional + The number of characters to use for string variables. If None, the + default is ``mpas_tools.io.default_nchar``, which can be modified but + which defaults to 64. + logger : logging.Logger, optional - A logger to write messages to write the output of `ncks` conversion - calls to. If None, `ncks` output is suppressed. This is only - relevant if `format` is 'NETCDF3_64BIT_DATA' + A logger to write messages to write the output of ``ncks`` conversion + calls to. If None, ``ncks`` output is suppressed. This is only + relevant if ``format`` is 'NETCDF3_64BIT_DATA' """ # noqa: E501 if format is None: format = default_format @@ -85,31 +91,43 @@ def write_netcdf( if char_dim_name is None: char_dim_name = default_char_dim_name - # Convert int64 variables to int32 for MPAS compatibility - for var in list(ds.data_vars.keys()) + list(ds.coords.keys()): - if ds[var].dtype == numpy.int64: - attrs = ds[var].attrs.copy() - ds[var] = ds[var].astype(numpy.int32) - ds[var].attrs = attrs + if nchar is None: + nchar = default_nchar + + numpyFillValues = {} + for fillType in fillValues: + # drop string fill values + if not fillType.startswith('S'): + numpyFillValues[numpy.dtype(fillType)] = fillValues[fillType] encodingDict = {} variableNames = list(ds.data_vars.keys()) + list(ds.coords.keys()) for variableName in variableNames: - isNumeric = numpy.issubdtype(ds[variableName].dtype, numpy.number) - if isNumeric and numpy.any(numpy.isnan(ds[variableName])): - dtype = ds[variableName].dtype - for fillType in fillValues: - if dtype == numpy.dtype(fillType): - encodingDict[variableName] = { - '_FillValue': fillValues[fillType] - } - break - else: - encodingDict[variableName] = {'_FillValue': None} - - isString = numpy.issubdtype(ds[variableName].dtype, numpy.bytes_) - if isString and char_dim_name is not None: - encodingDict[variableName] = {'char_dim_name': char_dim_name} + var = ds[variableName] + encodingDict[variableName] = {} + dtype = var.dtype + + # Convert int64 variables to int32 for MPAS compatibility + if dtype == numpy.int64: + encodingDict[variableName]['dtype'] = 'int32' + + # add fill values + if dtype in numpyFillValues: + if numpy.any(numpy.isnan(var)): + # only add fill values if they're needed + fill = numpyFillValues[dtype] + else: + fill = None + encodingDict[variableName]['_FillValue'] = fill + + isString = numpy.issubdtype(dtype, numpy.bytes_) or numpy.issubdtype( + dtype, numpy.str_ + ) + if isString: + # set the encoding for string variables + encodingDict[variableName].update( + {'dtype': f'|S{nchar}', 'char_dim_name': char_dim_name} + ) update_history(ds) diff --git a/conda_package/mpas_tools/mesh/mask.py b/conda_package/mpas_tools/mesh/mask.py index b30e64f87..b0506b3bb 100644 --- a/conda_package/mpas_tools/mesh/mask.py +++ b/conda_package/mpas_tools/mesh/mask.py @@ -13,7 +13,7 @@ from shapely.strtree import STRtree from mpas_tools.cime.constants import constants -from mpas_tools.io import write_netcdf +from mpas_tools.io import default_nchar, write_netcdf from mpas_tools.logging import LoggingContext from mpas_tools.parallel import create_pool from mpas_tools.transects import ( @@ -100,7 +100,7 @@ def compute_mpas_region_masks( # create shapely geometry for lon and lat points = [shapely.geometry.Point(x, y) for x, y in zip(lon, lat)] - regionNames, masks, properties, nchar = _compute_region_masks( + regionNames, masks, properties = _compute_region_masks( fcMask, points, logger, @@ -133,7 +133,6 @@ def compute_mpas_region_masks( ds=dsMasks, properties=properties, dim='nRegions', - nchar=nchar, ) if logger is not None: @@ -339,17 +338,15 @@ def compute_mpas_transect_masks( polygons, nPolygons, duplicatePolygons = _get_polygons( dsMesh, maskType ) - transectNames, masks, properties, nchar, shapes = ( - _compute_transect_masks( - fcMask, - polygons, - logger, - pool, - chunkSize, - showProgress, - subdivisionResolution, - earthRadius, - ) + transectNames, masks, properties, shapes = _compute_transect_masks( + fcMask, + polygons, + logger, + pool, + chunkSize, + showProgress, + subdivisionResolution, + earthRadius, ) if logger is not None: @@ -393,7 +390,6 @@ def compute_mpas_transect_masks( ds=dsMasks, properties=properties, dim='nTransects', - nchar=nchar, ) if logger is not None: @@ -723,7 +719,7 @@ def compute_lon_lat_region_masks( # create shapely geometry for lon and lat points = [shapely.geometry.Point(x, y) for x, y in zip(Lon, Lat)] - regionNames, masks, properties, nchar = _compute_region_masks( + regionNames, masks, properties = _compute_region_masks( fcMask, points, logger, @@ -757,7 +753,6 @@ def compute_lon_lat_region_masks( ds=dsMasks, properties=properties, dim='nRegions', - nchar=nchar, ) if logger is not None: @@ -959,7 +954,7 @@ def compute_projection_grid_region_masks( points = [ shapely.geometry.Point(x, y) for x, y in zip(lon.ravel(), lat.ravel()) ] - regionNames, masks, properties, nchar = _compute_region_masks( + regionNames, masks, properties = _compute_region_masks( fcMask, points, logger, @@ -990,7 +985,6 @@ def compute_projection_grid_region_masks( ds=dsMasks, properties=properties, dim='nRegions', - nchar=nchar, ) if logger is not None: @@ -1171,10 +1165,11 @@ def _compute_mask_from_shapes( return mask -def _add_properties(ds, properties, dim, nchar): +def _add_properties(ds, properties, dim): """ Add properties to the dataset from a dictionary of properties """ + nchar = default_nchar for name, prop_list in properties.items(): if name not in ds: if isinstance(prop_list[0], str): @@ -1186,7 +1181,7 @@ def _add_properties(ds, properties, dim, nchar): for index, value in enumerate(prop_list): ds[name][index] = value else: - ds[name] = ((dim,), properties[prop_list]) + ds[name] = ((dim,), prop_list) def _get_region_names_and_properties(fc): @@ -1208,19 +1203,16 @@ def _get_region_names_and_properties(fc): propertyNames.add(propertyName) properties = {} - nchar = 0 for propertyName in propertyNames: properties[propertyName] = [] for feature in fc.features: if propertyName in feature['properties']: propertyVal = feature['properties'][propertyName] properties[propertyName].append(propertyVal) - if isinstance(propertyVal, str): - nchar = max(nchar, len(propertyVal)) else: properties[propertyName].append('') - return regionNames, properties, nchar + return regionNames, properties def _compute_region_masks( @@ -1231,7 +1223,7 @@ def _compute_region_masks( a set of regions. """ - regionNames, properties, nchar = _get_region_names_and_properties(fcMask) + regionNames, properties = _get_region_names_and_properties(fcMask) masks = [] @@ -1253,11 +1245,9 @@ def _compute_region_masks( showProgress=showProgress, ) - nchar = max(nchar, len(name)) - masks.append(mask) - return regionNames, masks, properties, nchar + return regionNames, masks, properties def _contains(shapes, points): @@ -1355,7 +1345,7 @@ def _compute_transect_masks( a set of transects. """ - transectNames, properties, nchar = _get_region_names_and_properties(fcMask) + transectNames, properties = _get_region_names_and_properties(fcMask) masks = [] shapes = [] @@ -1405,12 +1395,10 @@ def _compute_transect_masks( showProgress=showProgress, ) - nchar = max(nchar, len(name)) - masks.append(mask) shapes.append(shape) - return transectNames, masks, properties, nchar, shapes + return transectNames, masks, properties, shapes def _intersects(shape, polygons): diff --git a/conda_package/mpas_tools/ocean/moc.py b/conda_package/mpas_tools/ocean/moc.py index afa8544e5..41b16485c 100755 --- a/conda_package/mpas_tools/ocean/moc.py +++ b/conda_package/mpas_tools/ocean/moc.py @@ -7,15 +7,18 @@ from geometric_features.aggregation.ocean import moc import mpas_tools.mesh.conversion -from mpas_tools.io import write_netcdf - - -def make_moc_basins_and_transects(gf, mesh_filename, - mask_and_transect_filename, - geojson_filename=None, - mask_filename=None, - logger=None, - dir=None): +from mpas_tools.io import default_nchar, write_netcdf + + +def make_moc_basins_and_transects( + gf, + mesh_filename, + mask_and_transect_filename, + geojson_filename=None, + mask_filename=None, + logger=None, + dir=None, +): """ Builds features defining the ocean basins and southern transects used in computing the meridional overturning circulation (MOC) @@ -59,16 +62,19 @@ def make_moc_basins_and_transects(gf, mesh_filename, fcMOC.to_geojson(geojson_filename) dsMesh = xarray.open_dataset(mesh_filename) - dsMasks = mpas_tools.mesh.conversion.mask(dsMesh=dsMesh, fcMask=fcMOC, - logger=logger, dir=dir) + dsMasks = mpas_tools.mesh.conversion.mask( + dsMesh=dsMesh, fcMask=fcMOC, logger=logger, dir=dir + ) if mask_filename is not None: write_netcdf(dsMasks, mask_filename, char_dim_name='StrLen') - dsMasksAndTransects = add_moc_southern_boundary_transects(dsMasks, dsMesh, - logger=logger) - write_netcdf(dsMasksAndTransects, mask_and_transect_filename, - char_dim_name='StrLen') + dsMasksAndTransects = add_moc_southern_boundary_transects( + dsMasks, dsMesh, logger=logger + ) + write_netcdf( + dsMasksAndTransects, mask_and_transect_filename, char_dim_name='StrLen' + ) def add_moc_southern_boundary_transects(dsMask, dsMesh, logger=None): @@ -97,14 +103,21 @@ def add_moc_southern_boundary_transects(dsMask, dsMesh, logger=None): logger.addHandler(logging.StreamHandler(sys.stdout)) logger.setLevel(logging.INFO) - southernBoundaryEdges, southernBoundaryEdgeSigns, \ - southernBoundaryVertices = \ - _extract_southern_boundary(dsMesh, dsMask, latBuffer=3.*numpy.pi/180., - logger=logger) - - _add_transects_to_moc(dsMesh, dsMask, southernBoundaryEdges, - southernBoundaryEdgeSigns, - southernBoundaryVertices) + ( + southernBoundaryEdges, + southernBoundaryEdgeSigns, + southernBoundaryVertices, + ) = _extract_southern_boundary( + dsMesh, dsMask, latBuffer=3.0 * numpy.pi / 180.0, logger=logger + ) + + _add_transects_to_moc( + dsMesh, + dsMask, + southernBoundaryEdges, + southernBoundaryEdgeSigns, + southernBoundaryVertices, + ) if useStdout: logger.handlers = [] @@ -124,19 +137,33 @@ def moc_southern_boundary_extractor(): is applied only to vertices and edges, not cells, because the need for southern boundary transect data on cells is not foreseen. """ - parser = \ - argparse.ArgumentParser(description=description, - formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('-f', '--in_file', dest='in_file', - help='Input file with MOC masks', metavar='IN_FILE', - required=True) - parser.add_argument('-m', '--mesh_file', dest='mesh_file', - help='Input mesh file', metavar='MESH_FILE', - required=True) - parser.add_argument('-o', '--out_file', dest='out_file', - help='Output file for MOC masks and southern-boundary ' - 'transects', metavar='OUT_FILE', - required=True) + parser = argparse.ArgumentParser( + description=description, formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + '-f', + '--in_file', + dest='in_file', + help='Input file with MOC masks', + metavar='IN_FILE', + required=True, + ) + parser.add_argument( + '-m', + '--mesh_file', + dest='mesh_file', + help='Input mesh file', + metavar='MESH_FILE', + required=True, + ) + parser.add_argument( + '-o', + '--out_file', + dest='out_file', + help='Output file for MOC masks and southern-boundary transects', + metavar='OUT_FILE', + required=True, + ) args = parser.parse_args() dsMasks = xarray.open_dataset(args.in_file) @@ -162,14 +189,15 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer, logger): assert mocMask.sizes['nCells'] == nCells # convert to python zero-based indices - cellsOnEdge = mesh.variables['cellsOnEdge'].values-1 - verticesOnEdge = mesh.variables['verticesOnEdge'].values-1 - edgesOnVertex = mesh.variables['edgesOnVertex'].values-1 + cellsOnEdge = mesh.variables['cellsOnEdge'].values - 1 + verticesOnEdge = mesh.variables['verticesOnEdge'].values - 1 + edgesOnVertex = mesh.variables['edgesOnVertex'].values - 1 latEdge = mesh.variables['latEdge'].values - cellsOnEdgeInRange = numpy.logical_and(cellsOnEdge >= 0, - cellsOnEdge < nCells) + cellsOnEdgeInRange = numpy.logical_and( + cellsOnEdge >= 0, cellsOnEdge < nCells + ) southernBoundaryEdges = [] southernBoundaryEdgeSigns = [] @@ -183,20 +211,23 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer, logger): # land cells are outside not in the MOC region cellsOnEdgeMask = numpy.zeros(cellsOnEdge.shape, bool) # set mask values for cells that are in range (not land) - cellsOnEdgeMask[cellsOnEdgeInRange] = \ + cellsOnEdgeMask[cellsOnEdgeInRange] = ( cellMask[cellsOnEdge[cellsOnEdgeInRange]] == 1 + ) logger.info(' computing edge sign...') edgeSign = numpy.zeros(nEdges) # positive sign if the first cell on edge is in the region - mask = numpy.logical_and(cellsOnEdgeMask[:, 0], - numpy.logical_not(cellsOnEdgeMask[:, 1])) - edgeSign[mask] = -1. + mask = numpy.logical_and( + cellsOnEdgeMask[:, 0], numpy.logical_not(cellsOnEdgeMask[:, 1]) + ) + edgeSign[mask] = -1.0 # negative sign if the second cell on edge is in the region - mask = numpy.logical_and(cellsOnEdgeMask[:, 1], - numpy.logical_not(cellsOnEdgeMask[:, 0])) - edgeSign[mask] = 1. - isMOCBoundaryEdge = edgeSign != 0. + mask = numpy.logical_and( + cellsOnEdgeMask[:, 1], numpy.logical_not(cellsOnEdgeMask[:, 0]) + ) + edgeSign[mask] = 1.0 + isMOCBoundaryEdge = edgeSign != 0.0 edgesMOCBoundary = numpy.arange(nEdges)[isMOCBoundaryEdge] logger.info(' done.') @@ -209,9 +240,11 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer, logger): # Note: it is possible but unlikely that the southern-most point is # not within bulk region of the MOC mask if the region is not a single # shape - edgeSequence, edgeSequenceSigns, vertexSequence = \ - _get_edge_sequence_on_boundary(startEdge, edgeSign, edgesOnVertex, - verticesOnEdge) + edgeSequence, edgeSequenceSigns, vertexSequence = ( + _get_edge_sequence_on_boundary( + startEdge, edgeSign, edgesOnVertex, verticesOnEdge + ) + ) logger.info(f' done: {len(edgeSequence)} edges in transect.') @@ -221,13 +254,15 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer, logger): # edge sequence that is above the possible region of the soutehrn # boundary - belowToAbove = \ - numpy.logical_and(numpy.logical_not(aboveSouthernBoundary[0:-1]), - aboveSouthernBoundary[1:]) + belowToAbove = numpy.logical_and( + numpy.logical_not(aboveSouthernBoundary[0:-1]), + aboveSouthernBoundary[1:], + ) - aboveToBelow = \ - numpy.logical_and(aboveSouthernBoundary[0:-1], - numpy.logical_not(aboveSouthernBoundary[1:])) + aboveToBelow = numpy.logical_and( + aboveSouthernBoundary[0:-1], + numpy.logical_not(aboveSouthernBoundary[1:]), + ) startIndices = numpy.arange(1, len(edgeSequence))[belowToAbove] endIndices = numpy.arange(1, len(edgeSequence))[aboveToBelow] @@ -247,27 +282,36 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer, logger): aboveLength = endIndices - startIndices longest = numpy.argmax(aboveLength) # we want all the indices in the sequence *not* part of the longest - indices = numpy.arange(endIndices[longest], - startIndices[longest] + len(edgeSequence)) + indices = numpy.arange( + endIndices[longest], startIndices[longest] + len(edgeSequence) + ) indices = numpy.mod(indices, len(edgeSequence)) southernBoundaryEdges.append(edgeSequence[indices]) southernBoundaryEdgeSigns.append(edgeSequenceSigns[indices]) # we want one extra vertex in the vertex sequence - indices = numpy.arange(endIndices[longest], - startIndices[longest] + len(edgeSequence) + 1) + indices = numpy.arange( + endIndices[longest], startIndices[longest] + len(edgeSequence) + 1 + ) indices = numpy.mod(indices, len(edgeSequence)) southernBoundaryVertices.append(vertexSequence[indices]) - return southernBoundaryEdges, southernBoundaryEdgeSigns, \ - southernBoundaryVertices + return ( + southernBoundaryEdges, + southernBoundaryEdgeSigns, + southernBoundaryVertices, + ) -def _add_transects_to_moc(mesh, mocMask, southernBoundaryEdges, - southernBoiundaryEdgeSigns, - southernBoundaryVertices): +def _add_transects_to_moc( + mesh, + mocMask, + southernBoundaryEdges, + southernBoiundaryEdgeSigns, + southernBoundaryVertices, +): """ Creates transect fields in mocMask from the edges, edge signs and vertices defining the southern boundaries. Mesh info (nEdges and @@ -279,100 +323,132 @@ def _add_transects_to_moc(mesh, mocMask, southernBoundaryEdges, nEdges = mesh.sizes['nEdges'] nVertices = mesh.sizes['nVertices'] - maxEdgesInTransect = numpy.amax([len(southernBoundaryEdges[iTransect]) - for iTransect in range(nTransects)]) - - maxVerticesInTransect = \ - numpy.amax([len(southernBoundaryVertices[iTransect]) - for iTransect in range(nTransects)]) - - transectEdgeMasks = numpy.zeros((nEdges, nTransects), - numpy.int32) - transectEdgeMaskSigns = numpy.zeros((nEdges, nTransects), - numpy.int32) - transectEdgeGlobalIDs = numpy.zeros((nTransects, maxEdgesInTransect), - numpy.int32) - transectVertexMasks = numpy.zeros((nVertices, nTransects), - numpy.int32) - transectVertexGlobalIDs = numpy.zeros((nTransects, maxVerticesInTransect), - numpy.int32) + maxEdgesInTransect = numpy.amax( + [ + len(southernBoundaryEdges[iTransect]) + for iTransect in range(nTransects) + ] + ) + + maxVerticesInTransect = numpy.amax( + [ + len(southernBoundaryVertices[iTransect]) + for iTransect in range(nTransects) + ] + ) + + transectEdgeMasks = numpy.zeros((nEdges, nTransects), numpy.int32) + transectEdgeMaskSigns = numpy.zeros((nEdges, nTransects), numpy.int32) + transectEdgeGlobalIDs = numpy.zeros( + (nTransects, maxEdgesInTransect), numpy.int32 + ) + transectVertexMasks = numpy.zeros((nVertices, nTransects), numpy.int32) + transectVertexGlobalIDs = numpy.zeros( + (nTransects, maxVerticesInTransect), numpy.int32 + ) for iTransect in range(nTransects): transectEdgeMasks[southernBoundaryEdges[iTransect], iTransect] = 1 - transectEdgeMaskSigns[southernBoundaryEdges[iTransect], iTransect] \ - = southernBoiundaryEdgeSigns[iTransect] + transectEdgeMaskSigns[southernBoundaryEdges[iTransect], iTransect] = ( + southernBoiundaryEdgeSigns[iTransect] + ) transectCount = len(southernBoundaryEdges[iTransect]) - transectEdgeGlobalIDs[iTransect, 0:transectCount] \ - = southernBoundaryEdges[iTransect] + 1 + transectEdgeGlobalIDs[iTransect, 0:transectCount] = ( + southernBoundaryEdges[iTransect] + 1 + ) transectVertexMasks[southernBoundaryVertices[iTransect], iTransect] = 1 transectCount = len(southernBoundaryVertices[iTransect]) - transectVertexGlobalIDs[iTransect, 0:transectCount] \ - = southernBoundaryVertices[iTransect] + 1 - - mocMask['transectEdgeMasks'] = \ - (('nEdges', 'nTransects'), transectEdgeMasks) - mocMask['transectEdgeMaskSigns'] = (('nEdges', 'nTransects'), - transectEdgeMaskSigns) - mocMask['transectEdgeGlobalIDs'] = (('nTransects', 'maxEdgesInTransect'), - transectEdgeGlobalIDs) - - mocMask['transectVertexMasks'] = (('nVertices', 'nTransects'), - transectVertexMasks) - mocMask['transectVertexGlobalIDs'] = \ - (('nTransects', 'maxVerticesInTransect'), transectVertexGlobalIDs) + transectVertexGlobalIDs[iTransect, 0:transectCount] = ( + southernBoundaryVertices[iTransect] + 1 + ) + + mocMask['transectEdgeMasks'] = ( + ('nEdges', 'nTransects'), + transectEdgeMasks, + ) + mocMask['transectEdgeMaskSigns'] = ( + ('nEdges', 'nTransects'), + transectEdgeMaskSigns, + ) + mocMask['transectEdgeGlobalIDs'] = ( + ('nTransects', 'maxEdgesInTransect'), + transectEdgeGlobalIDs, + ) + + mocMask['transectVertexMasks'] = ( + ('nVertices', 'nTransects'), + transectVertexMasks, + ) + mocMask['transectVertexGlobalIDs'] = ( + ('nTransects', 'maxVerticesInTransect'), + transectVertexGlobalIDs, + ) if 'nRegionsInGroup' not in mocMask: nRegions = mocMask.sizes['nRegions'] nRegionGroups = 2 - nRegionsInGroup = nRegions*numpy.ones(nRegionGroups, dtype=numpy.int32) - regionsInGroup = numpy.zeros((nRegionGroups, nRegions), - dtype=numpy.int32) + nRegionsInGroup = nRegions * numpy.ones( + nRegionGroups, dtype=numpy.int32 + ) + regionsInGroup = numpy.zeros( + (nRegionGroups, nRegions), dtype=numpy.int32 + ) regionGroupNames = ['MOCBasinRegionsGroup', 'all'] regionNames = mocMask.regionNames.values - nChar = 64 for index in range(nRegionGroups): - regionsInGroup[index, :] = numpy.arange(1, nRegions+1) + regionsInGroup[index, :] = numpy.arange(1, nRegions + 1) mocMask['nRegionsInGroup'] = (('nRegionGroups',), nRegionsInGroup) - mocMask['regionsInGroup'] = (('nRegionGroups', 'maxRegionsInGroup'), - regionsInGroup) + mocMask['regionsInGroup'] = ( + ('nRegionGroups', 'maxRegionsInGroup'), + regionsInGroup, + ) - mocMask['regionGroupNames'] = \ - (('nRegionGroups',), numpy.zeros((nRegionGroups,), - dtype=f'|S{nChar}')) + mocMask['regionGroupNames'] = ( + ('nRegionGroups',), + numpy.zeros((nRegionGroups,), dtype=f'|S{default_nchar}'), + ) for index in range(nRegionGroups): mocMask['regionGroupNames'][index] = regionGroupNames[index] # we need to make sure the region names use the same string length - mocMask['regionNames'] = \ - (('nRegions',), numpy.zeros((nRegions,), - dtype=f'|S{nChar}')) + mocMask['regionNames'] = ( + ('nRegions',), + numpy.zeros((nRegions,), dtype=f'|S{default_nchar}'), + ) for index in range(nRegions): mocMask['regionNames'][index] = regionNames[index] mocMask['transectNames'] = mocMask.regionNames.rename( - {'nRegions': 'nTransects'}) + {'nRegions': 'nTransects'} + ) mocMask['nTransectsInGroup'] = mocMask.nRegionsInGroup.rename( - {'nRegionGroups': 'nTransectGroups'}) + {'nRegionGroups': 'nTransectGroups'} + ) mocMask['transectsInGroup'] = mocMask.regionsInGroup.rename( - {'nRegionGroups': 'nTransectGroups', - 'maxRegionsInGroup': 'maxTransectsInGroup'}) + { + 'nRegionGroups': 'nTransectGroups', + 'maxRegionsInGroup': 'maxTransectsInGroup', + } + ) mocMask['transectGroupNames'] = mocMask.regionGroupNames.rename( - {'nRegionGroups': 'nTransectGroups'}) + {'nRegionGroups': 'nTransectGroups'} + ) -def _get_edge_sequence_on_boundary(startEdge, edgeSign, edgesOnVertex, - verticesOnEdge): +def _get_edge_sequence_on_boundary( + startEdge, edgeSign, edgesOnVertex, verticesOnEdge +): """ Follows the boundary from a starting edge to produce a sequence of edges that form a closed loop. @@ -390,8 +466,8 @@ def _get_edge_sequence_on_boundary(startEdge, edgeSign, edgesOnVertex, edgeSequence = [] vertexSequence = [] while True: - assert edgeSign[iEdge] == 1. or edgeSign[iEdge] == -1. - if edgeSign[iEdge] == 1.: + assert edgeSign[iEdge] == 1.0 or edgeSign[iEdge] == -1.0 + if edgeSign[iEdge] == 1.0: v = 0 else: v = 1