from netCDF4 import Dataset
import os
import math
import errno
import numpy as np
import subprocess
import argparse
import shutil
import glob
from .regrid import regrid_to_other_mesh
from .mask import extend_seaice_mask
from .regions import make_regions_file
[docs]
def gen_seaice_mesh_partition(meshFilename, regionFilename, nProcsArray,
                              mpasCullerLocation, outputPrefix, plotting,
                              metis, cullEquatorialRegion):
    """
    Generate graph partition(s) for the given MPAS-Seaice mesh and the given
    number(s) of processors and a file defining regions that each processor
    should own part of (typically a polar region and an equatorial region)
    Parameters
    ----------
    meshFilename : str
        The name of a file containing the MPAS-Seaice mesh
    regionFilename : str
        The name of a file containing a ``region`` field defining different
        regions that each processor should own part of
    nProcsArray : list or int
        The number(s) of processors to create graph partitions for
    mpasCullerLocation : str or None
        The directory for the ``MpasCellCuller.x`` tool or ``None`` to look in
        the user's path
    outputPrefix : str
        The prefix to prepend to each graph partition file
    plotting : bool
        Whether to create a NetCDF file ``partition_diag.nc`` to use for
        plotting the partitions
    metis : str
        The exectable to use for partitioning in each region
    cullEquatorialRegion : bool
        Whether to remove the equatorial region from the paritions
    """
    # arguments
    meshToolsDir = mpasCullerLocation
    if meshToolsDir is None:
        culler = shutil.which("MpasCellCuller.x")
        if culler is not None:
            meshToolsDir = os.path.dirname(culler)
        else:
            # no directory was provided and none
            this_dir = os.path.dirname(os.path.realpath(__file__))
            meshToolsDir = os.path.abspath(os.path.join(
                this_dir, "..", "..", "..", "mesh_tools",
                "mesh_conversion_tools"))
            culler = os.path.join(meshToolsDir, "MpasCellCuller.x")
        if not os.path.exists(culler):
            raise FileNotFoundError(
                "MpasCellCuller.x does not exist at the requested location.")
    plotFilename = "partition_diag.nc"
    # get regions
    regionFile = Dataset(regionFilename, "r")
    nRegions = regionFile.nRegions
    region = regionFile.variables["region"][:]
    regionFile.close()
    # diagnostics
    if plotting:
        shutil.copyfile(meshFilename, plotFilename)
    # load mesh file
    mesh = Dataset(meshFilename, "r")
    nCells = len(mesh.dimensions["nCells"])
    mesh.close()
    cellidsInRegion = []
    for iRegion in range(0, nRegions):
        # tmp file basename
        tmp = "%s_%2.2i_tmp" % (meshFilename, iRegion)
        # create precull file
        tmpFilenamesPrecull = tmp + "_precull.nc"
        shutil.copyfile(meshFilename, tmpFilenamesPrecull)
        # make cullCell variable
        cullCell = np.ones(nCells)
        for iCell in range(0, nCells):
            if region[iCell] == iRegion:
                cullCell[iCell] = 0
        # cull the mesh
        tmpFilenamesPostcull = tmp + "_postcull.nc"
        _cull_mesh(meshToolsDir, tmpFilenamesPrecull, tmpFilenamesPostcull,
                   cullCell)
        # get the cell IDs for this partition
        cellid = _get_cell_ids(tmpFilenamesPostcull, meshFilename)
        cellidsInRegion.append(cellid)
        # preserve the initial graph file
        os.rename("culled_graph.info", f"culled_graph_{iRegion}_tmp.info")
    if not isinstance(nProcsArray, (list, tuple, set)):
        # presumably, it's a single integer
        nProcsArray = [nProcsArray]
    for nProcs in nProcsArray:
        if cullEquatorialRegion:
            nBlocks = nRegions * nProcs
        else:
            nBlocks = nProcs
        combinedGraph = np.zeros(nCells)
        for iRegion in range(0, nRegions):
            # partition the culled grid
            try:
                graphFilename = "culled_graph_%i_tmp.info" % iRegion
                subprocess.call([metis, graphFilename, str(nProcs)])
            except OSError as e:
                if e.errno == errno.ENOENT:
                    raise FileNotFoundError(
                        "metis program %s not found" % metis)
                else:
                    print("metis error")
                    raise
            cellid = cellidsInRegion[iRegion]
            # load this partition
            graph = _load_partition(
                "culled_graph_%i_tmp.info.part.%i" %
                (iRegion, nProcs))
            # add this partition to the combined partition
            for iCellPartition in range(0, len(graph)):
                if cullEquatorialRegion:
                    combinedGraph[cellid[iCellPartition]] = \
                        
graph[iCellPartition] + nProcs * iRegion
                else:
                    combinedGraph[cellid[iCellPartition]] = \
                        
graph[iCellPartition]
        # output the cell partition file
        cellPartitionFile = open("%s.part.%i" % (outputPrefix, nBlocks), "w")
        for iCell in range(0, nCells):
            cellPartitionFile.write("%i\n" % (combinedGraph[iCell]))
        cellPartitionFile.close()
        # output block partition file
        if cullEquatorialRegion:
            blockPartitionFile = open(
                "%s.part.%i" %
                (outputPrefix, nProcs), "w")
            for iRegion in range(0, nRegions):
                for iProc in range(0, nProcs):
                    blockPartitionFile.write("%i\n" % iProc)
            blockPartitionFile.close()
        # diagnostics
        if plotting:
            plottingFile = Dataset(plotFilename, "a")
            partitionVariable = plottingFile.createVariable(
                "partition_%i" % nProcs, "i4", ("nCells",))
            partitionVariable[:] = combinedGraph
            plottingFile.close()
    subprocess.run("rm *tmp*", shell=True) 
[docs]
def prepare_partitions():
    """
    An entry point for performing preparatory work for making seaice partitions
    """
    # parsing input
    parser = argparse.ArgumentParser(
        description="Perform preparatory work for making seaice partitions.")
    parser.add_argument("-i", "--inputmesh", dest="meshFilenameSrc",
                        required=True,
                        help="MPAS mesh file for source regridding mesh.")
    parser.add_argument("-p", "--presence", dest="filenameData",
                        required=True,
                        help="Input ice presence file for source mesh.")
    parser.add_argument("-m", "--outputmesh", dest="meshFilenameDst",
                        required=True,
                        help="MPAS mesh file for destination regridding mesh.")
    parser.add_argument("-o", "--outputDir", dest="outputDir",
                        required=True,
                        help="Output directory for temporary files and "
                             "partition files.")
    parser.add_argument("-w", "--weightsFilename", dest="weightsFilename",
                        required=False,
                        help="A mapping file between the input and output "
                             "MPAS meshes.  One will be generated if it is "
                             "not supplied.")
    args = parser.parse_args()
    # Check if output directory exists
    if not os.path.isdir(args.outputDir):
        raise FileNotFoundError("ERROR: Output directory does not exist.")
    # 1) Regrid the ice presence from the input data mesh to the grid of choice
    print("Regrid to desired mesh...")
    filenameOut = args.outputDir + "/icePresent_regrid.nc"
    regrid_to_other_mesh(
        meshFilenameSrc=args.meshFilenameSrc,
        filenameData=args.filenameData,
        meshFilenameDst=args.meshFilenameDst,
        filenameOut=filenameOut,
        generateWeights=(args.weightsFilename is None),
        weightsFilename=args.weightsFilename)
    # 2) create icePresence variable
    print("fix_regrid_output...")
    # check executable exists
    if shutil.which("fix_regrid_output.exe") is not None:
        # it's in the system path
        executable = "fix_regrid_output.exe"
    elif os.path.exists("./fix_regrid_output.exe"):
        # found in local path
        executable = "./fix_regrid_output.exe"
    else:
        raise FileNotFoundError("fix_regrid_output.exe could not be found.")
    inputFile = args.outputDir + "/icePresent_regrid.nc"
    outputFile = args.outputDir + "/icePresent_regrid_modify.nc"
    subprocess.call([executable, inputFile, args.meshFilenameDst, outputFile])
    # 3) create variable icePresenceExtended
    print("extend_seaice_mask...")
    filenamePresence = args.outputDir + "/icePresent_regrid_modify.nc"
    extend_seaice_mask(args.meshFilenameDst, filenamePresence, 0.0, False)
    # 4) Make the regions file from the icePresenceExtended variable
    print("make_regions_file...")
    filenameIcePresent = args.outputDir + "/icePresent_regrid_modify.nc"
    filenameOut = args.outputDir + "/regions.nc"
    make_regions_file(filenameIcePresent=filenameIcePresent,
                      filenameMesh=args.meshFilenameDst,
                      regionType="two_region_eq",
                      varname="icePresenceExtended",
                      limit=0.5, filenameOut=filenameOut) 
[docs]
def create_partitions():
    """
    An entry point for creating sea-ice partitions
    """
    # parsing input
    parser = argparse.ArgumentParser(description='Create sea-ice partitions.')
    parser.add_argument(
        '-m', '--outputmesh', dest="meshFilename", required=True,
        help='MPAS mesh file for destination regridding mesh.')
    parser.add_argument(
        '-o', '--outputDir', dest="outputDir", required=True,
        help='Output directory for temporary files and partition files.')
    parser.add_argument(
        '-c', '--cullerDir', dest="mpasCullerLocation", required=False,
        help='Location of MPAS MpasCellCuller.x executable.')
    parser.add_argument(
        '-p', '--prefix', dest="outputPrefix", required=False,
        help='prefix for output partition filenames.', default="graph.info")
    parser.add_argument(
        '-x', '--plotting', dest="plotting", required=False,
        help='create diagnostic plotting file of partitions',
        action='store_true')
    parser.add_argument(
        '-g', '--metis', dest="metis", required=False,
        help='name of metis utility', default="gpmetis")
    parser.add_argument(
        '-n', '--nProcs', dest="nProcsArray", nargs='*', required=False,
        help='list of the number of processors to create partition for.',
        type=int)
    parser.add_argument(
        '-f', '--nProcsFile', dest="nProcsFile", required=False,
        help='number of processors to create partition for.')
    args = parser.parse_args()
    # number of processors
    if args.nProcsArray is None and args.nProcsFile is None:
        raise ValueError("Must specify nProcs or nProcsFile")
    if args.nProcsArray is not None and args.nProcsFile is not None:
        raise ValueError("Can't specify both nProcs or nProcsFile")
    if args.nProcsFile is not None:
        with open(args.nProcsFile, "r") as fileNProcs:
            nProcsLines = fileNProcs.readlines()
            nProcsArray = [int(line) for line in nProcsLines
                           if line.split() != '']
    else:
        nProcsArray = args.nProcsArray
    # create partitions
    regionFilename = args.outputDir + "/regions.nc"
    outputPrefix = args.outputDir + "/" + args.outputPrefix
    gen_seaice_mesh_partition(args.meshFilename, regionFilename, nProcsArray,
                              args.mpasCullerLocation, outputPrefix,
                              args.plotting, args.metis,
                              cullEquatorialRegion=False) 
def simple_partitions():
    """
    An entry point for creating sea-ice partitions on LCRC (Anvil and
    Chrysalis)
    """
    data_dir = '/lcrc/group/e3sm/public_html/mpas_standalonedata/' \
               'mpas-seaice/partition'
    # parsing input
    parser = argparse.ArgumentParser(
        description='Create sea-ice partitions on LCRC.')
    parser.add_argument(
        '-m', '--mesh', dest="meshFilename", required=True,
        help='MPAS-Seaice mesh file.')
    parser.add_argument(
        '-p', '--prefix', dest="outputPrefix", required=True,
        help='prefix for output partition filenames.')
    parser.add_argument(
        '-n', '--nprocs', dest="nProcsArray", nargs='*', required=True,
        help='list of the number of processors to create partition for.',
        type=int)
    parser.add_argument(
        '-d', '--datadir', dest="dataDir", required=False,
        default=data_dir,
        help='Directory with seaice_QU60km_polar.nc and '
             'icePresent_QU60km_polar.nc.')
    args = parser.parse_args()
    meshFilenameDst = os.path.abspath(args.meshFilename)
    tmpdir = 'tmp_seaice_part_dir'
    try:
        shutil.rmtree(tmpdir)
    except FileNotFoundError:
        pass
    os.makedirs(tmpdir)
    cwd = os.getcwd()
    os.chdir(tmpdir)
    # make a local link to the mesh file
    basename = os.path.basename(meshFilenameDst)
    command = ['ln', '-s', meshFilenameDst, basename]
    subprocess.run(command, check=True)
    meshFilenameDst = basename
    # 1) Regrid the ice presence from the input data mesh to the grid of choice
    print("Regrid to desired mesh...")
    filenameOut = "icePresent_regrid.nc"
    meshFilenameSrc = os.path.join(data_dir, 'seaice_QU60km_polar.nc')
    filenameData = os.path.join(data_dir, 'icePresent_QU60km_polar.nc')
    regrid_to_other_mesh(
        meshFilenameSrc=meshFilenameSrc,
        filenameData=filenameData,
        meshFilenameDst=meshFilenameDst,
        filenameOut=filenameOut,
        generateWeights=True,
        weightsFilename=None)
    # 2) create icePresence variable
    print("fix_regrid_output...")
    inputFile = "icePresent_regrid.nc"
    outputFile = "icePresent_regrid_modify.nc"
    subprocess.call(["fix_regrid_output.exe", inputFile, meshFilenameDst,
                     outputFile])
    # 3) create variable icePresenceExtended
    print("extend_seaice_mask...")
    filenamePresence = "icePresent_regrid_modify.nc"
    extend_seaice_mask(meshFilenameDst, filenamePresence, 0.0, False)
    # 4) Make the regions file from the icePresenceExtended variable
    print("make_regions_file...")
    filenameIcePresent = "icePresent_regrid_modify.nc"
    filenameOut = "regions.nc"
    make_regions_file(filenameIcePresent=filenameIcePresent,
                      filenameMesh=meshFilenameDst,
                      regionType="two_region_eq",
                      varname="icePresenceExtended",
                      limit=0.5,
                      filenameOut=filenameOut)
    nProcsArray = args.nProcsArray
    # create partitions
    regionFilename = "regions.nc"
    outputPrefix = os.path.join(cwd, args.outputPrefix)
    gen_seaice_mesh_partition(meshFilename=meshFilenameDst,
                              regionFilename=regionFilename,
                              nProcsArray=nProcsArray,
                              mpasCullerLocation=None,
                              outputPrefix=outputPrefix,
                              plotting=False,
                              metis="gpmetis",
                              cullEquatorialRegion=False)
    for file in glob.glob(f'{outputPrefix}*'):
        command = ['chmod', 'ug+rw', file]
        subprocess.run(command, check=True)
        command = ['chmod', 'o+r', file]
        subprocess.run(command, check=True)
    os.chdir(cwd)
    shutil.rmtree(tmpdir)
# ---------------------------------------------------------------------
# Private functions
# ---------------------------------------------------------------------
def _degree_to_radian(degree):
    return (degree * math.pi) / 180.0
def _add_cell_cull_array(filename, cullCell):
    mesh = Dataset(filename, "a")
    cullCellVariable = mesh.createVariable("cullCell", "i4", ("nCells",))
    cullCellVariable[:] = cullCell
    mesh.close()
def _cull_mesh(meshToolsDir, filenameIn, filenameOut, cullCell):
    _add_cell_cull_array(filenameIn, cullCell)
    executable = "MpasCellCuller.x"
    if meshToolsDir is not None:
        executable = os.path.join(meshToolsDir, executable)
    subprocess.run([executable, filenameIn, filenameOut, "-c"], check=True)
def _load_partition(graphFilename):
    graphFile = open(graphFilename, "r")
    lines = graphFile.readlines()
    graphFile.close()
    partition = []
    for line in lines:
        partition.append(int(line))
    return partition
def _get_cell_ids(culledFilename, originalFilename):
    culledFile = Dataset(culledFilename, "r")
    nCellsCulled = len(culledFile.dimensions["nCells"])
    originalFile = Dataset(originalFilename, "r")
    nCellsOriginal = len(originalFile.dimensions["nCells"])
    cellid = np.zeros(nCellsCulled, dtype=int)
    cellMapFile = open("cellMapForward.txt", "r")
    cellMapLines = cellMapFile.readlines()
    cellMapFile.close()
    iCellOriginal = 0
    for cellMapLine in cellMapLines:
        if iCellOriginal % 1000 == 0:
            print(iCellOriginal, " of ", nCellsOriginal)
        try:
            cellMap = int(cellMapLine)
        except ValueError:
            # There are blank lines to skip
            continue
        if cellMap != -1:
            cellid[cellMap] = iCellOriginal
        iCellOriginal = iCellOriginal + 1
    return cellid
def _get_cell_ids_orig(culledFilename, originalFilename):
    culledFile = Dataset(culledFilename, "r")
    latCellCulled = culledFile.variables["latCell"][:]
    lonCellCulled = culledFile.variables["lonCell"][:]
    nCellsCulled = len(culledFile.dimensions["nCells"])
    originalFile = Dataset(originalFilename, "r")
    latCellOriginal = originalFile.variables["latCell"][:]
    lonCellOriginal = originalFile.variables["lonCell"][:]
    nCellsOriginal = len(originalFile.dimensions["nCells"])
    cellid = np.zeros(nCellsCulled, dtype=int)
    for iCellCulled in range(0, nCellsCulled):
        if iCellCulled % 1000 == 0:
            print("iCellCulled: ", iCellCulled, "of ", nCellsCulled)
        for iCellOriginal in range(0, nCellsOriginal):
            if (latCellCulled[iCellCulled] == latCellOriginal[iCellOriginal] and
                    lonCellCulled[iCellCulled] == lonCellOriginal[iCellOriginal]):
                cellid[iCellCulled] = iCellOriginal
                break
    return cellid