Module quagmire.mesh.commonmesh

Routines common to all mesh types.

CommonMesh implements the following functionality:

  • creating Quagmire mesh variables
  • setting and reading node labels on the PETSc DM
  • saving the mesh and mesh variables to HDF5 files
  • handling global and local synchronisation operations

Supply a PETSc DM object (created from quagmire.tools.meshtools) to initialise the object.

Expand source code
# Copyright 2016-2020 Louis Moresi, Ben Mather, Romain Beucher
# 
# This file is part of Quagmire.
# 
# Quagmire is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or any later version.
# 
# Quagmire is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public License
# along with Quagmire.  If not, see <http://www.gnu.org/licenses/>.

"""
Routines common to all mesh types.

<img src="https://raw.githubusercontent.com/underworldcode/quagmire/dev/docs/images/quagmire-flowchart-commonmesh.png" style="width: 321px; float:right">

`CommonMesh` implements the following functionality:

- creating Quagmire mesh variables
- setting and reading node labels on the PETSc DM
- saving the mesh and mesh variables to HDF5 files
- handling global and local synchronisation operations

Supply a `PETSc DM` object (created from `quagmire.tools.meshtools`) to initialise the object.
"""

import numpy as np
from mpi4py import MPI
import sys,petsc4py
petsc4py.init(sys.argv)
from petsc4py import PETSc
# comm = MPI.COMM_WORLD
from time import perf_counter

try: range = xrange
except: pass


class CommonMesh(object):
    """
    Build routines on top of a PETSc DM mesh object common to:

    - `quagmire.mesh.pixmesh.PixMesh`
    - `quagmire.mesh.trimesh.TriMesh`
    - `quagmire.mesh.strimesh.sTriMesh`

    The above classes inherit `CommonMesh` to:

    - create `quagmire.mesh.basemesh.MeshVariable` objects
    - save the mesh and mesh variables to HDF5 files
    - retrieving and setting labels on the DM
    - synchronise local mesh information to all processors

    Parameters
    ----------
    DM : PETSc DM object
        Build this mesh object using one of the functions in
        `quagmire.tools.meshtools`
    verbose : bool
        Flag toggles verbose output
    *args : optional arguments
    **kwargs : optional keyword arguments

    Attributes
    ----------
    dm : PETSc DM object
        structured Cartesian grid or unstructured Cartesian/
        spherical mesh object
    log : PETSc log object
        contains logs for performance benchmarks
    gvec : PETSc global vector
        used to synchronise vectors across multiple processors
    lvec : PETSc local vector
        used to synchromise local information to the global vector
    sizes : tuple
        size of the local and global domains
    comm : object
        MPI COMM object for controlling global communications
    rank : int
        COMM rank is hte number assigned to each processor
    """

    def __init__(self, dm, verbose=True,  *args, **kwargs):
        self.mesh_type = 'FlatMesh'

        self.timings = dict() # store times

        self.log = PETSc.Log()
        self.log.begin()

        self.verbose = verbose

        self.dm = dm
        self.gvec = dm.createGlobalVector()
        self.lvec = dm.createLocalVector()
        self.sect = dm.getDefaultSection()
        self.sizes = self.gvec.getSizes(), self.gvec.getSizes()

        self.comm = self.dm.comm
        self.rank = self.dm.comm.rank

        lgmap_r = dm.getLGMap()
        l2g = lgmap_r.indices.copy()
        offproc = l2g < 0

        l2g[offproc] = -(l2g[offproc] + 1)
        lgmap_c = PETSc.LGMap().create(l2g, comm=self.dm.comm)

        self.lgmap_row = lgmap_r
        self.lgmap_col = lgmap_c


        ## Attach a coordinate system to the mesh:

        import quagmire

        if isinstance(self, (quagmire.mesh.trimesh.TriMesh, quagmire.mesh.pixmesh.PixMesh)):
            self.coordinates = quagmire.function.coordinates.CartesianCoordinates2D()
            self.geometry    = self.coordinates
            self.coordinate_system = self.coordinates
        else:
            self.coordinates = quagmire.function.coordinates.SphericalSurfaceLonLat2D()
            self.geometry    = self.coordinates
            self.coordinate_system = self.coordinates


        return

    def __len__(self):
        return self.npoints

    def add_variable(self, name=None, lname=None, locked=False):
        """
        Create a Quagmire mesh variable.

        Parameters
        ----------
        name : str
            name for the mesh variable
        locked : bool (default: False)
            lock the mesh variable from accidental modification

        Returns
        -------
        MeshVariable : object
            Instantiate a `quagmire.mesh.basemesh.MeshVariable`.
        """
        from quagmire.mesh import MeshVariable
        return MeshVariable(name=name, mesh=self, lname=lname, locked=locked)

    def get_label(self, label):
        """
        Retrieves all points in the DM that is marked with a specific label.
        e.g. "boundary", "coarse"

        Parameters
        ----------
        label : str
            retrieve indices on the DM marked with `label`.

        Returns
        -------
        indices : list of ints
            list of indices corresponding to the label
        """
        pStart, pEnd = self.dm.getDepthStratum(0)


        labels = []
        for i in range(self.dm.getNumLabels()):
            labels.append(self.dm.getLabelName(i))

        if label not in labels:
            raise ValueError("There is no {} label in the DM".format(label))


        stratSize = self.dm.getStratumSize(label, 1)
        if stratSize > 0:
            labelIS = self.dm.getStratumIS(label, 1)
            pt_range = np.logical_and(labelIS.indices >= pStart, labelIS.indices < pEnd)
            indices = labelIS.indices[pt_range] - pStart
        else:
            indices = np.zeros((0,), dtype=np.int)

        return indices



    def set_label(self, label, indices):
        """
        Marks local indices in the DM with a label.

        Parameters
        ----------
        label : str
            mark indices on the DM with `label`.
        indices : list of ints
            indices on the DM
        """
        pStart, pEnd = self.dm.getDepthStratum(0)
        indices += pStart

        labels = []
        for i in range(self.dm.getNumLabels()):
            labels.append(self.dm.getLabelName(i))

        if label not in labels:
            self.dm.createLabel(label)
        for ind in indices:
            self.dm.setLabelValue(label, ind, 1)
        return


    def get_boundary(self, marker="boundary"):
        """
        Find the nodes on the boundary from the DM
        If marker does not exist then the convex hull is used.

        Parameters
        ----------
        marker : str (default: 'boundary')
            name of the boundary label
        
        Returns
        -------
        mask : array of bools, shape (n,)
            mask of interior nodes
        """

        pStart, pEnd = self.dm.getDepthStratum(0)
        bmask = np.ones(self.npoints, dtype=bool)


        try:
            boundary_indices = self.get_label(marker)

        except ValueError:
            self.dm.markBoundaryFaces(marker) # marks line segments
            boundary_indices = self.tri.convex_hull()
            for ind in boundary_indices:
                self.dm.setLabelValue(marker, ind + pStart, 1)


        bmask[boundary_indices] = False
        return bmask


    def save_quagmire_project(self, file):

        import h5py
        from mpi4py import MPI
        comm = MPI.COMM_WORLD

        file = str(file)
        if not file.endswith('.h5'):
            file += '.h5'

        # first save the mesh
        self.save_mesh_to_hdf5(file)

        # then save the topography
        self.topography.save(file, append=True)

        # handle saving radius to file for spherical mesh
        if np.array(self._radius).size == self.npoints:
            radius_meshVariable = self.add_variable('radius')
            radius_meshVariable.data = self._radius
            radius_meshVariable.save(file, append=True)
            radius = False
        else:
            radius = self._radius

        # now save important parameters we need to reconstruct
        # data structures. For this we need to crack open the HDF5
        # file we just saved and write attributes on a 'quagmire' group

        if comm.rank == 0:
            with h5py.File(file, mode='r+') as h5:
                quag = h5.create_group('quagmire')
                quag.attrs['id']                  = self.id
                quag.attrs['verbose']             = self.verbose
                quag.attrs['radius']              = radius
                quag.attrs['downhill_neighbours'] = self.downhill_neighbours
                quag.attrs['topography_modified'] = self._topography_modified_count

        return

    def xdmf(self, hdf5_filename, xdmf_filename=None):
        """
        Creates an xdmf file associating the saved HDF5 file with the mesh

        If no xdmf filename is not provided, a xdmf file is generated from
        the hdf5 filename with a trailing `.xdmf` extension.
        """
        from quagmire.tools.generate_xdmf import generateXdmf
        from quagmire import mpi_rank

        hdf5_filename = str(hdf5_filename)
        if not hdf5_filename.endswith('.h5'):
            hdf5_filename += '.h5'

        if mpi_rank == 0:
            generateXdmf(hdf5_filename, xdmfFilename=xdmf_filename)
        return


    def save(self, hdf5_filename):
        return self.save_quagmire_project(hdf5_filename)


    def save_mesh_to_hdf5(self, hdf5_filename):
        """
        Saves mesh information stored in the DM to HDF5 filename
        If the filename already exists, it is overwritten.

        Parameters
        ----------
        filename : str
            Save the mesh to an HDF5 filename with this name
        """
        hdf5_filename = str(hdf5_filename)
        if not hdf5_filename.endswith('.h5'):
            hdf5_filename += '.h5'

        ViewHDF5 = PETSc.Viewer()
        ViewHDF5.createHDF5(hdf5_filename, mode='w')
        ViewHDF5.view(obj=self.dm)
        ViewHDF5.destroy()
        ViewHDF5 = None


        if self.id.startswith("pixmesh"):
            import h5py
            from mpi4py import MPI
            comm = MPI.COMM_WORLD

            (minX, maxX), (minY, maxY) = self.dm.getBoundingBox()
            resX, resY = self.dm.getSizes()

            if comm.rank == 0:
                with h5py.File(hdf5_filename, mode='r+') as h5:
                    geom = h5.create_group('geometry')
                    geom.attrs['minX'] = minX
                    geom.attrs['maxX'] = maxX
                    geom.attrs['minY'] = minY
                    geom.attrs['maxY'] = maxY
                    geom.attrs['resX'] = resX
                    geom.attrs['resY'] = resY


    def save_field_to_hdf5(self, hdf5_filename, *args, **kwargs):
        """
        Saves data on the mesh to an HDF5 file
        e.g. height, rainfall, sea level, etc.

        Pass these as arguments or keyword arguments for
        their names to be saved to the hdf5 file

        Parameters
        ----------
        hdf5_filename : str
            Save the mesh variables to an HDF5 file with this name
        *args : arguments
        **kwargs : keyword arguments
        """
        import os.path

        hdf5_filename = str(hdf5_filename)
        if not hdf5_filename.endswith('.h5'):
            hdf5_filename += '.h5'

        kwdict = kwargs
        for i, arg in enumerate(args):
            key = "arr_{}".format(i)
            if key in list(kwdict.keys()):
                raise ValueError("Cannot use un-named variables\
                                  and keyword: {}".format(key))
            kwdict[key] = arg

        vec = self.dm.createGlobalVec()

        if os.path.isfile(hdf5_filename):
            mode = "a"
        else:
            mode = "w"


        for key in kwdict:
            val = kwdict[key]
            try:
                vec.setArray(val)
            except:
                self.lvec.setArray(val)
                self.dm.localToGlobal(self.lvec, vec)

            vec.setName(key)
            if self.rank == 0 and self.verbose:
                print("Saving {} to hdf5".format(key))

            ViewHDF5 = PETSc.Viewer()
            ViewHDF5.createHDF5(hdf5_filename, mode=mode)
            ViewHDF5.view(obj=vec)
            ViewHDF5.destroy()
            mode = "a"

        vec.destroy()
        vec = None


    def _gather_root(self):
        """
        MPI gather operation to root process
        """
        self.tozero, self.zvec = PETSc.Scatter.toZero(self.gvec)


        # Gather x,y points
        pts = self.tri.points
        self.lvec.setArray(pts[:,0])
        self.dm.localToGlobal(self.lvec, self.gvec)
        self.tozero.scatter(self.gvec, self.zvec)

        self.root_x = self.zvec.array.copy()

        self.lvec.setArray(pts[:,1])
        self.dm.localToGlobal(self.lvec, self.gvec)
        self.tozero.scatter(self.gvec, self.zvec)

        self.root_y = self.zvec.array.copy()

        self.root = True # yes we have gathered everything


    def gather_data(self, data):
        """
        Gather data on root process
        """

        # check if we already gathered pts on root
        if not self.root:
            self._gather_root()

        self.lvec.setArray(data)
        self.dm.localToGlobal(self.lvec, self.gvec)
        self.tozero.scatter(self.gvec, self.zvec)

        return self.zvec.array.copy()

    def scatter_data(self, data):
        """
        Scatter data to all processes
        """

        toAll, zvec = PETSc.Scatter.toAll(self.gvec)

        self.lvec.setArray(data)
        self.dm.localToGlobal(self.lvec, self.gvec)
        toAll.scatter(self.gvec, zvec)

        return zvec.array.copy()

    def sync(self, vector):
        """
        Synchronise the local domain with the global domain

        Parameters
        ----------
        vector : array of floats, shape (n,)
            local vector to be synchronised

        Returns
        -------
        vector : array of floats, shape (n,)
            local vector synchronised with the global mesh
        """

        if self.dm.comm.Get_size() == 1:
            return vector
        else:

            # Is this the same under 3.10 ?

            self.lvec.setArray(vector)
            # self.dm.localToLocal(self.lvec, self.gvec)
            self.dm.localToGlobal(self.lvec, self.gvec)
            self.dm.globalToLocal(self.gvec, self.lvec)

            return self.lvec.array.copy()

Classes

class CommonMesh (dm, verbose=True, *args, **kwargs)

Build routines on top of a PETSc DM mesh object common to:

The above classes inherit CommonMesh to:

  • create MeshVariable objects
  • save the mesh and mesh variables to HDF5 files
  • retrieving and setting labels on the DM
  • synchronise local mesh information to all processors

Parameters

DM : PETSc DM object
Build this mesh object using one of the functions in quagmire.tools.meshtools
verbose : bool
Flag toggles verbose output
*args : optional arguments
 
**kwargs : optional keyword arguments
 

Attributes

dm : PETSc DM object
structured Cartesian grid or unstructured Cartesian/ spherical mesh object
log : PETSc log object
contains logs for performance benchmarks
gvec : PETSc global vector
used to synchronise vectors across multiple processors
lvec : PETSc local vector
used to synchromise local information to the global vector
sizes : tuple
size of the local and global domains
comm : object
MPI COMM object for controlling global communications
rank : int
COMM rank is hte number assigned to each processor
Expand source code
class CommonMesh(object):
    """
    Build routines on top of a PETSc DM mesh object common to:

    - `quagmire.mesh.pixmesh.PixMesh`
    - `quagmire.mesh.trimesh.TriMesh`
    - `quagmire.mesh.strimesh.sTriMesh`

    The above classes inherit `CommonMesh` to:

    - create `quagmire.mesh.basemesh.MeshVariable` objects
    - save the mesh and mesh variables to HDF5 files
    - retrieving and setting labels on the DM
    - synchronise local mesh information to all processors

    Parameters
    ----------
    DM : PETSc DM object
        Build this mesh object using one of the functions in
        `quagmire.tools.meshtools`
    verbose : bool
        Flag toggles verbose output
    *args : optional arguments
    **kwargs : optional keyword arguments

    Attributes
    ----------
    dm : PETSc DM object
        structured Cartesian grid or unstructured Cartesian/
        spherical mesh object
    log : PETSc log object
        contains logs for performance benchmarks
    gvec : PETSc global vector
        used to synchronise vectors across multiple processors
    lvec : PETSc local vector
        used to synchromise local information to the global vector
    sizes : tuple
        size of the local and global domains
    comm : object
        MPI COMM object for controlling global communications
    rank : int
        COMM rank is hte number assigned to each processor
    """

    def __init__(self, dm, verbose=True,  *args, **kwargs):
        self.mesh_type = 'FlatMesh'

        self.timings = dict() # store times

        self.log = PETSc.Log()
        self.log.begin()

        self.verbose = verbose

        self.dm = dm
        self.gvec = dm.createGlobalVector()
        self.lvec = dm.createLocalVector()
        self.sect = dm.getDefaultSection()
        self.sizes = self.gvec.getSizes(), self.gvec.getSizes()

        self.comm = self.dm.comm
        self.rank = self.dm.comm.rank

        lgmap_r = dm.getLGMap()
        l2g = lgmap_r.indices.copy()
        offproc = l2g < 0

        l2g[offproc] = -(l2g[offproc] + 1)
        lgmap_c = PETSc.LGMap().create(l2g, comm=self.dm.comm)

        self.lgmap_row = lgmap_r
        self.lgmap_col = lgmap_c


        ## Attach a coordinate system to the mesh:

        import quagmire

        if isinstance(self, (quagmire.mesh.trimesh.TriMesh, quagmire.mesh.pixmesh.PixMesh)):
            self.coordinates = quagmire.function.coordinates.CartesianCoordinates2D()
            self.geometry    = self.coordinates
            self.coordinate_system = self.coordinates
        else:
            self.coordinates = quagmire.function.coordinates.SphericalSurfaceLonLat2D()
            self.geometry    = self.coordinates
            self.coordinate_system = self.coordinates


        return

    def __len__(self):
        return self.npoints

    def add_variable(self, name=None, lname=None, locked=False):
        """
        Create a Quagmire mesh variable.

        Parameters
        ----------
        name : str
            name for the mesh variable
        locked : bool (default: False)
            lock the mesh variable from accidental modification

        Returns
        -------
        MeshVariable : object
            Instantiate a `quagmire.mesh.basemesh.MeshVariable`.
        """
        from quagmire.mesh import MeshVariable
        return MeshVariable(name=name, mesh=self, lname=lname, locked=locked)

    def get_label(self, label):
        """
        Retrieves all points in the DM that is marked with a specific label.
        e.g. "boundary", "coarse"

        Parameters
        ----------
        label : str
            retrieve indices on the DM marked with `label`.

        Returns
        -------
        indices : list of ints
            list of indices corresponding to the label
        """
        pStart, pEnd = self.dm.getDepthStratum(0)


        labels = []
        for i in range(self.dm.getNumLabels()):
            labels.append(self.dm.getLabelName(i))

        if label not in labels:
            raise ValueError("There is no {} label in the DM".format(label))


        stratSize = self.dm.getStratumSize(label, 1)
        if stratSize > 0:
            labelIS = self.dm.getStratumIS(label, 1)
            pt_range = np.logical_and(labelIS.indices >= pStart, labelIS.indices < pEnd)
            indices = labelIS.indices[pt_range] - pStart
        else:
            indices = np.zeros((0,), dtype=np.int)

        return indices



    def set_label(self, label, indices):
        """
        Marks local indices in the DM with a label.

        Parameters
        ----------
        label : str
            mark indices on the DM with `label`.
        indices : list of ints
            indices on the DM
        """
        pStart, pEnd = self.dm.getDepthStratum(0)
        indices += pStart

        labels = []
        for i in range(self.dm.getNumLabels()):
            labels.append(self.dm.getLabelName(i))

        if label not in labels:
            self.dm.createLabel(label)
        for ind in indices:
            self.dm.setLabelValue(label, ind, 1)
        return


    def get_boundary(self, marker="boundary"):
        """
        Find the nodes on the boundary from the DM
        If marker does not exist then the convex hull is used.

        Parameters
        ----------
        marker : str (default: 'boundary')
            name of the boundary label
        
        Returns
        -------
        mask : array of bools, shape (n,)
            mask of interior nodes
        """

        pStart, pEnd = self.dm.getDepthStratum(0)
        bmask = np.ones(self.npoints, dtype=bool)


        try:
            boundary_indices = self.get_label(marker)

        except ValueError:
            self.dm.markBoundaryFaces(marker) # marks line segments
            boundary_indices = self.tri.convex_hull()
            for ind in boundary_indices:
                self.dm.setLabelValue(marker, ind + pStart, 1)


        bmask[boundary_indices] = False
        return bmask


    def save_quagmire_project(self, file):

        import h5py
        from mpi4py import MPI
        comm = MPI.COMM_WORLD

        file = str(file)
        if not file.endswith('.h5'):
            file += '.h5'

        # first save the mesh
        self.save_mesh_to_hdf5(file)

        # then save the topography
        self.topography.save(file, append=True)

        # handle saving radius to file for spherical mesh
        if np.array(self._radius).size == self.npoints:
            radius_meshVariable = self.add_variable('radius')
            radius_meshVariable.data = self._radius
            radius_meshVariable.save(file, append=True)
            radius = False
        else:
            radius = self._radius

        # now save important parameters we need to reconstruct
        # data structures. For this we need to crack open the HDF5
        # file we just saved and write attributes on a 'quagmire' group

        if comm.rank == 0:
            with h5py.File(file, mode='r+') as h5:
                quag = h5.create_group('quagmire')
                quag.attrs['id']                  = self.id
                quag.attrs['verbose']             = self.verbose
                quag.attrs['radius']              = radius
                quag.attrs['downhill_neighbours'] = self.downhill_neighbours
                quag.attrs['topography_modified'] = self._topography_modified_count

        return

    def xdmf(self, hdf5_filename, xdmf_filename=None):
        """
        Creates an xdmf file associating the saved HDF5 file with the mesh

        If no xdmf filename is not provided, a xdmf file is generated from
        the hdf5 filename with a trailing `.xdmf` extension.
        """
        from quagmire.tools.generate_xdmf import generateXdmf
        from quagmire import mpi_rank

        hdf5_filename = str(hdf5_filename)
        if not hdf5_filename.endswith('.h5'):
            hdf5_filename += '.h5'

        if mpi_rank == 0:
            generateXdmf(hdf5_filename, xdmfFilename=xdmf_filename)
        return


    def save(self, hdf5_filename):
        return self.save_quagmire_project(hdf5_filename)


    def save_mesh_to_hdf5(self, hdf5_filename):
        """
        Saves mesh information stored in the DM to HDF5 filename
        If the filename already exists, it is overwritten.

        Parameters
        ----------
        filename : str
            Save the mesh to an HDF5 filename with this name
        """
        hdf5_filename = str(hdf5_filename)
        if not hdf5_filename.endswith('.h5'):
            hdf5_filename += '.h5'

        ViewHDF5 = PETSc.Viewer()
        ViewHDF5.createHDF5(hdf5_filename, mode='w')
        ViewHDF5.view(obj=self.dm)
        ViewHDF5.destroy()
        ViewHDF5 = None


        if self.id.startswith("pixmesh"):
            import h5py
            from mpi4py import MPI
            comm = MPI.COMM_WORLD

            (minX, maxX), (minY, maxY) = self.dm.getBoundingBox()
            resX, resY = self.dm.getSizes()

            if comm.rank == 0:
                with h5py.File(hdf5_filename, mode='r+') as h5:
                    geom = h5.create_group('geometry')
                    geom.attrs['minX'] = minX
                    geom.attrs['maxX'] = maxX
                    geom.attrs['minY'] = minY
                    geom.attrs['maxY'] = maxY
                    geom.attrs['resX'] = resX
                    geom.attrs['resY'] = resY


    def save_field_to_hdf5(self, hdf5_filename, *args, **kwargs):
        """
        Saves data on the mesh to an HDF5 file
        e.g. height, rainfall, sea level, etc.

        Pass these as arguments or keyword arguments for
        their names to be saved to the hdf5 file

        Parameters
        ----------
        hdf5_filename : str
            Save the mesh variables to an HDF5 file with this name
        *args : arguments
        **kwargs : keyword arguments
        """
        import os.path

        hdf5_filename = str(hdf5_filename)
        if not hdf5_filename.endswith('.h5'):
            hdf5_filename += '.h5'

        kwdict = kwargs
        for i, arg in enumerate(args):
            key = "arr_{}".format(i)
            if key in list(kwdict.keys()):
                raise ValueError("Cannot use un-named variables\
                                  and keyword: {}".format(key))
            kwdict[key] = arg

        vec = self.dm.createGlobalVec()

        if os.path.isfile(hdf5_filename):
            mode = "a"
        else:
            mode = "w"


        for key in kwdict:
            val = kwdict[key]
            try:
                vec.setArray(val)
            except:
                self.lvec.setArray(val)
                self.dm.localToGlobal(self.lvec, vec)

            vec.setName(key)
            if self.rank == 0 and self.verbose:
                print("Saving {} to hdf5".format(key))

            ViewHDF5 = PETSc.Viewer()
            ViewHDF5.createHDF5(hdf5_filename, mode=mode)
            ViewHDF5.view(obj=vec)
            ViewHDF5.destroy()
            mode = "a"

        vec.destroy()
        vec = None


    def _gather_root(self):
        """
        MPI gather operation to root process
        """
        self.tozero, self.zvec = PETSc.Scatter.toZero(self.gvec)


        # Gather x,y points
        pts = self.tri.points
        self.lvec.setArray(pts[:,0])
        self.dm.localToGlobal(self.lvec, self.gvec)
        self.tozero.scatter(self.gvec, self.zvec)

        self.root_x = self.zvec.array.copy()

        self.lvec.setArray(pts[:,1])
        self.dm.localToGlobal(self.lvec, self.gvec)
        self.tozero.scatter(self.gvec, self.zvec)

        self.root_y = self.zvec.array.copy()

        self.root = True # yes we have gathered everything


    def gather_data(self, data):
        """
        Gather data on root process
        """

        # check if we already gathered pts on root
        if not self.root:
            self._gather_root()

        self.lvec.setArray(data)
        self.dm.localToGlobal(self.lvec, self.gvec)
        self.tozero.scatter(self.gvec, self.zvec)

        return self.zvec.array.copy()

    def scatter_data(self, data):
        """
        Scatter data to all processes
        """

        toAll, zvec = PETSc.Scatter.toAll(self.gvec)

        self.lvec.setArray(data)
        self.dm.localToGlobal(self.lvec, self.gvec)
        toAll.scatter(self.gvec, zvec)

        return zvec.array.copy()

    def sync(self, vector):
        """
        Synchronise the local domain with the global domain

        Parameters
        ----------
        vector : array of floats, shape (n,)
            local vector to be synchronised

        Returns
        -------
        vector : array of floats, shape (n,)
            local vector synchronised with the global mesh
        """

        if self.dm.comm.Get_size() == 1:
            return vector
        else:

            # Is this the same under 3.10 ?

            self.lvec.setArray(vector)
            # self.dm.localToLocal(self.lvec, self.gvec)
            self.dm.localToGlobal(self.lvec, self.gvec)
            self.dm.globalToLocal(self.gvec, self.lvec)

            return self.lvec.array.copy()

Subclasses

Methods

def add_variable(self, name=None, lname=None, locked=False)

Create a Quagmire mesh variable.

Parameters

name : str
name for the mesh variable
locked : bool (default: False)
lock the mesh variable from accidental modification

Returns

MeshVariable : object
Instantiate a MeshVariable.
Expand source code
def add_variable(self, name=None, lname=None, locked=False):
    """
    Create a Quagmire mesh variable.

    Parameters
    ----------
    name : str
        name for the mesh variable
    locked : bool (default: False)
        lock the mesh variable from accidental modification

    Returns
    -------
    MeshVariable : object
        Instantiate a `quagmire.mesh.basemesh.MeshVariable`.
    """
    from quagmire.mesh import MeshVariable
    return MeshVariable(name=name, mesh=self, lname=lname, locked=locked)
def gather_data(self, data)

Gather data on root process

Expand source code
def gather_data(self, data):
    """
    Gather data on root process
    """

    # check if we already gathered pts on root
    if not self.root:
        self._gather_root()

    self.lvec.setArray(data)
    self.dm.localToGlobal(self.lvec, self.gvec)
    self.tozero.scatter(self.gvec, self.zvec)

    return self.zvec.array.copy()
def get_boundary(self, marker='boundary')

Find the nodes on the boundary from the DM If marker does not exist then the convex hull is used.

Parameters

marker : str (default: 'boundary')
name of the boundary label

Returns

mask : array of bools, shape (n,)
mask of interior nodes
Expand source code
def get_boundary(self, marker="boundary"):
    """
    Find the nodes on the boundary from the DM
    If marker does not exist then the convex hull is used.

    Parameters
    ----------
    marker : str (default: 'boundary')
        name of the boundary label
    
    Returns
    -------
    mask : array of bools, shape (n,)
        mask of interior nodes
    """

    pStart, pEnd = self.dm.getDepthStratum(0)
    bmask = np.ones(self.npoints, dtype=bool)


    try:
        boundary_indices = self.get_label(marker)

    except ValueError:
        self.dm.markBoundaryFaces(marker) # marks line segments
        boundary_indices = self.tri.convex_hull()
        for ind in boundary_indices:
            self.dm.setLabelValue(marker, ind + pStart, 1)


    bmask[boundary_indices] = False
    return bmask
def get_label(self, label)

Retrieves all points in the DM that is marked with a specific label. e.g. "boundary", "coarse"

Parameters

label : str
retrieve indices on the DM marked with label.

Returns

indices : list of ints
list of indices corresponding to the label
Expand source code
def get_label(self, label):
    """
    Retrieves all points in the DM that is marked with a specific label.
    e.g. "boundary", "coarse"

    Parameters
    ----------
    label : str
        retrieve indices on the DM marked with `label`.

    Returns
    -------
    indices : list of ints
        list of indices corresponding to the label
    """
    pStart, pEnd = self.dm.getDepthStratum(0)


    labels = []
    for i in range(self.dm.getNumLabels()):
        labels.append(self.dm.getLabelName(i))

    if label not in labels:
        raise ValueError("There is no {} label in the DM".format(label))


    stratSize = self.dm.getStratumSize(label, 1)
    if stratSize > 0:
        labelIS = self.dm.getStratumIS(label, 1)
        pt_range = np.logical_and(labelIS.indices >= pStart, labelIS.indices < pEnd)
        indices = labelIS.indices[pt_range] - pStart
    else:
        indices = np.zeros((0,), dtype=np.int)

    return indices
def save(self, hdf5_filename)
Expand source code
def save(self, hdf5_filename):
    return self.save_quagmire_project(hdf5_filename)
def save_field_to_hdf5(self, hdf5_filename, *args, **kwargs)

Saves data on the mesh to an HDF5 file e.g. height, rainfall, sea level, etc.

Pass these as arguments or keyword arguments for their names to be saved to the hdf5 file

Parameters

hdf5_filename : str
Save the mesh variables to an HDF5 file with this name
*args : arguments
 
**kwargs : keyword arguments
 
Expand source code
def save_field_to_hdf5(self, hdf5_filename, *args, **kwargs):
    """
    Saves data on the mesh to an HDF5 file
    e.g. height, rainfall, sea level, etc.

    Pass these as arguments or keyword arguments for
    their names to be saved to the hdf5 file

    Parameters
    ----------
    hdf5_filename : str
        Save the mesh variables to an HDF5 file with this name
    *args : arguments
    **kwargs : keyword arguments
    """
    import os.path

    hdf5_filename = str(hdf5_filename)
    if not hdf5_filename.endswith('.h5'):
        hdf5_filename += '.h5'

    kwdict = kwargs
    for i, arg in enumerate(args):
        key = "arr_{}".format(i)
        if key in list(kwdict.keys()):
            raise ValueError("Cannot use un-named variables\
                              and keyword: {}".format(key))
        kwdict[key] = arg

    vec = self.dm.createGlobalVec()

    if os.path.isfile(hdf5_filename):
        mode = "a"
    else:
        mode = "w"


    for key in kwdict:
        val = kwdict[key]
        try:
            vec.setArray(val)
        except:
            self.lvec.setArray(val)
            self.dm.localToGlobal(self.lvec, vec)

        vec.setName(key)
        if self.rank == 0 and self.verbose:
            print("Saving {} to hdf5".format(key))

        ViewHDF5 = PETSc.Viewer()
        ViewHDF5.createHDF5(hdf5_filename, mode=mode)
        ViewHDF5.view(obj=vec)
        ViewHDF5.destroy()
        mode = "a"

    vec.destroy()
    vec = None
def save_mesh_to_hdf5(self, hdf5_filename)

Saves mesh information stored in the DM to HDF5 filename If the filename already exists, it is overwritten.

Parameters

filename : str
Save the mesh to an HDF5 filename with this name
Expand source code
def save_mesh_to_hdf5(self, hdf5_filename):
    """
    Saves mesh information stored in the DM to HDF5 filename
    If the filename already exists, it is overwritten.

    Parameters
    ----------
    filename : str
        Save the mesh to an HDF5 filename with this name
    """
    hdf5_filename = str(hdf5_filename)
    if not hdf5_filename.endswith('.h5'):
        hdf5_filename += '.h5'

    ViewHDF5 = PETSc.Viewer()
    ViewHDF5.createHDF5(hdf5_filename, mode='w')
    ViewHDF5.view(obj=self.dm)
    ViewHDF5.destroy()
    ViewHDF5 = None


    if self.id.startswith("pixmesh"):
        import h5py
        from mpi4py import MPI
        comm = MPI.COMM_WORLD

        (minX, maxX), (minY, maxY) = self.dm.getBoundingBox()
        resX, resY = self.dm.getSizes()

        if comm.rank == 0:
            with h5py.File(hdf5_filename, mode='r+') as h5:
                geom = h5.create_group('geometry')
                geom.attrs['minX'] = minX
                geom.attrs['maxX'] = maxX
                geom.attrs['minY'] = minY
                geom.attrs['maxY'] = maxY
                geom.attrs['resX'] = resX
                geom.attrs['resY'] = resY
def save_quagmire_project(self, file)
Expand source code
def save_quagmire_project(self, file):

    import h5py
    from mpi4py import MPI
    comm = MPI.COMM_WORLD

    file = str(file)
    if not file.endswith('.h5'):
        file += '.h5'

    # first save the mesh
    self.save_mesh_to_hdf5(file)

    # then save the topography
    self.topography.save(file, append=True)

    # handle saving radius to file for spherical mesh
    if np.array(self._radius).size == self.npoints:
        radius_meshVariable = self.add_variable('radius')
        radius_meshVariable.data = self._radius
        radius_meshVariable.save(file, append=True)
        radius = False
    else:
        radius = self._radius

    # now save important parameters we need to reconstruct
    # data structures. For this we need to crack open the HDF5
    # file we just saved and write attributes on a 'quagmire' group

    if comm.rank == 0:
        with h5py.File(file, mode='r+') as h5:
            quag = h5.create_group('quagmire')
            quag.attrs['id']                  = self.id
            quag.attrs['verbose']             = self.verbose
            quag.attrs['radius']              = radius
            quag.attrs['downhill_neighbours'] = self.downhill_neighbours
            quag.attrs['topography_modified'] = self._topography_modified_count

    return
def scatter_data(self, data)

Scatter data to all processes

Expand source code
def scatter_data(self, data):
    """
    Scatter data to all processes
    """

    toAll, zvec = PETSc.Scatter.toAll(self.gvec)

    self.lvec.setArray(data)
    self.dm.localToGlobal(self.lvec, self.gvec)
    toAll.scatter(self.gvec, zvec)

    return zvec.array.copy()
def set_label(self, label, indices)

Marks local indices in the DM with a label.

Parameters

label : str
mark indices on the DM with label.
indices : list of ints
indices on the DM
Expand source code
def set_label(self, label, indices):
    """
    Marks local indices in the DM with a label.

    Parameters
    ----------
    label : str
        mark indices on the DM with `label`.
    indices : list of ints
        indices on the DM
    """
    pStart, pEnd = self.dm.getDepthStratum(0)
    indices += pStart

    labels = []
    for i in range(self.dm.getNumLabels()):
        labels.append(self.dm.getLabelName(i))

    if label not in labels:
        self.dm.createLabel(label)
    for ind in indices:
        self.dm.setLabelValue(label, ind, 1)
    return
def sync(self, vector)

Synchronise the local domain with the global domain

Parameters

vector : array of floats, shape (n,)
local vector to be synchronised

Returns

vector : array of floats, shape (n,)
local vector synchronised with the global mesh
Expand source code
def sync(self, vector):
    """
    Synchronise the local domain with the global domain

    Parameters
    ----------
    vector : array of floats, shape (n,)
        local vector to be synchronised

    Returns
    -------
    vector : array of floats, shape (n,)
        local vector synchronised with the global mesh
    """

    if self.dm.comm.Get_size() == 1:
        return vector
    else:

        # Is this the same under 3.10 ?

        self.lvec.setArray(vector)
        # self.dm.localToLocal(self.lvec, self.gvec)
        self.dm.localToGlobal(self.lvec, self.gvec)
        self.dm.globalToLocal(self.gvec, self.lvec)

        return self.lvec.array.copy()
def xdmf(self, hdf5_filename, xdmf_filename=None)

Creates an xdmf file associating the saved HDF5 file with the mesh

If no xdmf filename is not provided, a xdmf file is generated from the hdf5 filename with a trailing .xdmf extension.

Expand source code
def xdmf(self, hdf5_filename, xdmf_filename=None):
    """
    Creates an xdmf file associating the saved HDF5 file with the mesh

    If no xdmf filename is not provided, a xdmf file is generated from
    the hdf5 filename with a trailing `.xdmf` extension.
    """
    from quagmire.tools.generate_xdmf import generateXdmf
    from quagmire import mpi_rank

    hdf5_filename = str(hdf5_filename)
    if not hdf5_filename.endswith('.h5'):
        hdf5_filename += '.h5'

    if mpi_rank == 0:
        generateXdmf(hdf5_filename, xdmfFilename=xdmf_filename)
    return