Exemplo n.º 1
0
 def plot(self, pressure):
     import pylab as plt
     temperature = dimensionalise(self.temperature(pressure), u.kelvin)
     pressure = dimensionalise(pressure, u.pascal)
     plt.plot(temperature, pressure)
     plt.gca().invert_yaxis()
     plt.show()
Exemplo n.º 2
0
    def solve(self, dt, sigma=0):
        if rank == 0 and self.verbose:
            purple = "\033[0;35m"
            endcol = "\033[00m"
            print(purple + "Processing surface with Badlands" + endcol)
            sys.stdout.flush()

        np_surface = None
        if rank == 0:
            rg = self.badlands_model.recGrid
            if self.Model.mesh.dim == 2:
                zVals = rg.regZ.mean(axis=1)
                np_surface = np.column_stack((rg.regX, zVals))

            if self.Model.mesh.dim == 3:
                np_surface = np.column_stack((rg.rectX, rg.rectY, rg.rectZ))

        np_surface = comm.bcast(np_surface, root=0)
        comm.Barrier()

        # Get Velocity Field at the surface
        nd_coords = nd(np_surface * u.meter)
        tracer_velocity = self.Model.velocityField.evaluate_global(nd_coords)

        dt_years = dimensionalise(dt, u.years).magnitude

        if rank == 0:
            tracer_disp = dimensionalise(tracer_velocity * dt,
                                         u.meter).magnitude
            self._inject_badlands_displacement(self.time_years, dt_years,
                                               tracer_disp, sigma)

            # Run the Badlands model to the same time point
            self.badlands_model.run_to_time(self.time_years + dt_years)

        self.time_years += dt_years

        # TODO: Improve the performance of this function
        self._update_material_types()
        comm.Barrier()

        if rank == 0 and self.verbose:
            purple = "\033[0;35m"
            endcol = "\033[00m"
            print(purple + "Processing surface with Badlands...Done" + endcol)
            sys.stdout.flush()

        return
Exemplo n.º 3
0
    def _generate_dem(self):
        """
        Generate a badlands DEM. This can be used as the initial Badlands state.

        """

        # Calculate number of nodes from required resolution.
        nx = np.int((self.maxCoord[0] - self.minCoord[0]) / self.resolution)
        ny = np.int((self.maxCoord[1] - self.minCoord[1]) / self.resolution)
        nx += 1
        ny += 1

        x = np.linspace(self.minCoord[0], self.maxCoord[0], nx)
        y = np.linspace(self.minCoord[1], self.maxCoord[1], ny)

        coordsX, coordsY = np.meshgrid(x, y)

        dem = np.zeros((nx * ny, 3))
        dem[:, 0] = coordsX.flatten()
        dem[:, 1] = coordsY.flatten()

        coordsZ = self.surfElevation.evaluate(dem[:, :2])

        dem[:, 2] = coordsZ.flatten()
        return dimensionalise(dem, u.meter).magnitude
    def _init_model(self):
        self.Model.add_passive_tracers(name="surface",
                                       vertices=self.surfaceArray,
                                       advect=False)

        self.dx = dimensionalise(
            (self.surfaceArray[:, 0][1] - self.surfaceArray[:, 0][0]),
            u.kilometer).magnitude
        '''set up custom tracers for surface'''
        x_min_local = self.Model.mesh.data[:self.Model.mesh.nodesLocal,
                                           0].min()
        x_max_local = self.Model.mesh.data[:self.Model.mesh.nodesLocal,
                                           0].max()
        ''' create surface tracers to advect on each node'''
        self.surface_data_local = np.zeros_like(
            self.surfaceArray[(self.surfaceArray[:, 0] >= x_min_local)
                              & (self.surfaceArray[:, 0] <= x_max_local)])

        self.surface_data_local[:, 0] = self.surfaceArray[:, 0][
            (self.surfaceArray[:, 0] >= x_min_local)
            & (self.surfaceArray[:, 0] <= x_max_local)]
        self.surface_data_local[:, 1] = self.surfaceArray[:, 1][
            (self.surfaceArray[:, 0] >= x_min_local)
            & (self.surfaceArray[:, 0] <= x_max_local)]

        # # Spline original surface for no slip condition near boundary
        self.original_surface = interp1d(self.surfaceArray[:, 0],
                                         self.surfaceArray[:, 1],
                                         kind='cubic',
                                         fill_value='extrapolate')
Exemplo n.º 5
0
    def _determine_particle_state(self):
        # Given Badlands' mesh, determine if each particle in 'volume' is above
        # (False) or below (True) it.

        # To do this, for each X/Y pair in 'volume', we interpolate its Z value
        # relative to the mesh in blModel. Then, if the interpolated Z is
        # greater than the supplied Z (i.e. Badlands mesh is above particle
        # elevation) it's sediment (True). Else, it's air (False).

        # TODO: we only support air/sediment layers right now; erodibility
        # layers are not implemented

        known_xy = None
        known_z = None
        fact = dimensionalise(1.0, u.meter).magnitude
        if rank == 0:
            # points that we have known elevation for
            known_xy = self.badlands_model.recGrid.tinMesh['vertices'] / fact
            known_z = self.badlands_model.elevation / fact

        known_xy = comm.bcast(known_xy, root=0)
        known_z = comm.bcast(known_z, root=0)

        comm.Barrier()

        volume = self.Model.swarm.particleCoordinates.data

        interpolate_xy = volume[:, [0, 1]]

        # NOTE: we're using nearest neighbour interpolation. This should be
        # sufficient as Badlands will normally run at a much higher resolution
        # than Underworld. 'linear' interpolation is much, much slower.
        interpolate_z = griddata(points=known_xy,
                                 values=known_z,
                                 xi=interpolate_xy,
                                 method='nearest')

        # True for sediment, False for air
        flags = volume[:, 2] < interpolate_z

        return flags
Exemplo n.º 6
0
    def _determine_particle_state_2D(self):

        known_xy = None
        known_z = None
        xs = None
        ys = None
        fact = dimensionalise(1.0, u.meter).magnitude
        if rank == 0:
            # points that we have known elevation for
            known_xy = self.badlands_model.recGrid.tinMesh['vertices'] / fact
            # elevation for those points
            known_z = self.badlands_model.elevation / fact
            xs = self.badlands_model.recGrid.regX / fact
            ys = self.badlands_model.recGrid.regY / fact

        known_xy = comm.bcast(known_xy, root=0)
        known_z = comm.bcast(known_z, root=0)
        xs = comm.bcast(xs, root=0)
        ys = comm.bcast(ys, root=0)

        comm.Barrier()

        grid_x, grid_y = np.meshgrid(xs, ys)
        interpolate_z = griddata(known_xy,
                                 known_z, (grid_x, grid_y),
                                 method='nearest').T
        interpolate_z = interpolate_z.mean(axis=1)

        f = interp1d(xs, interpolate_z)

        uw_surface = self.Model.swarm.particleCoordinates.data
        bdl_surface = f(uw_surface[:, 0])

        flags = uw_surface[:, 1] < bdl_surface

        return flags
Exemplo n.º 7
0
    def _init_model(self):

        if self.minCoord:
            self.minCoord = tuple([nd(val) for val in self.minCoord])
        else:
            self.minCoord = self.Model.mesh.minCoord

        if self.maxCoord:
            self.maxCoord = tuple([nd(val) for val in self.maxCoord])
        else:
            self.maxCoord = self.Model.mesh.maxCoord

        if self.Model.mesh.dim == 2:
            self.minCoord = (self.minCoord[0],
                             self.aspectRatio2d * self.minCoord[0])
            self.maxCoord = (self.maxCoord[0],
                             self.aspectRatio2d * self.maxCoord[0])

        if rank == 0:
            from pyBadlands.model import Model as BadlandsModel
            self.badlands_model = BadlandsModel()
            self.badlands_model.load_xml(self.XML)

            if self.restartStep:
                self.badlands_model.input.restart = True
                self.badlands_model.input.rstep = self.restartStep
                self.badlands_model.input.rfolder = self.restartFolder
                self.badlands_model.input.outDir = self.restartFolder
                self.badlands_model.outputStep = self.restartStep

                # Parse xmf for the last timestep time
                import xml.etree.ElementTree as etree
                xmf = (self.restartFolder + "/xmf/tin.time" +
                       str(self.restartStep) + ".xmf")
                tree = etree.parse(xmf)
                root = tree.getroot()
                self.time_years = float(root[0][0][0].attrib["Value"])

            # Create Initial DEM
            self._demfile = _tempdir + "/dem.csv"
            self.dem = self._generate_dem()
            np.savetxt(self._demfile, self.dem)

            # Build Mesh
            self.badlands_model.build_mesh(self._demfile, verbose=False)

            self.badlands_model.input.outDir = self.outputDir
            self.badlands_model.input.disp3d = True  # enable 3D displacements
            self.badlands_model.input.region = 0  # TODO: check what this does
            self.badlands_model.input.tStart = self.time_years
            self.badlands_model.tNow = self.time_years

            # Override the checkpoint/display interval in the Badlands model to
            # ensure BL and UW are synced
            self.badlands_model.input.tDisplay = (dimensionalise(
                self.checkpoint_interval, u.years).magnitude)

            # Set Badlands minimal distance between nodes before regridding
            self.badlands_model.force.merge3d = (
                self.badlands_model.input.Afactor *
                self.badlands_model.recGrid.resEdges * 0.5)

            # Bodge Badlands to perform an initial checkpoint
            # FIXME: we need to run the model for at least one
            # iteration before this is generated.
            # It would be nice if this wasn't the case.
            self.badlands_model.force.next_display = 0

        comm.Barrier()

        self._disp_inserted = False

        # Transfer the initial DEM state to Underworld
        self._update_material_types()
        comm.Barrier()
Exemplo n.º 8
0
    def save(self, filename, collective=False, units=None, time=None):
        """
        Save the swarm variable to disk.

        Parameters
        ----------
        filename : str
            The filename for the saved file. Relative or absolute paths may be
            used, but all directories must exist.
        swarmHandle :uw.utils.SavedFileData , optional
            The saved swarm file handle. If provided, a reference to the swarm file
            is made. Currently this doesn't provide any extra functionality.

        Returns
        -------
        underworld.utils.SavedFileData
            Data object relating to saved file. This only needs to be retained
            if you wish to create XDMF files and can be ignored otherwise.

        Notes
        -----
        This method must be called collectively by all processes.

        Example
        -------
        First create the swarm, populate, then add a variable:

        >>> mesh = uw.mesh.FeMesh_Cartesian( elementType='Q1/dQ0', elementRes=(16,16), minCoord=(0.,0.), maxCoord=(1.,1.) )
        >>> swarm = uw.swarm.Swarm(mesh)
        >>> swarm.populate_using_layout(uw.swarm.layouts.PerCellGaussLayout(swarm,2))
        >>> svar = swarm.add_variable("int",1)

        Write something to variable

        >>> import numpy as np
        >>> svar.data[:,0] = np.arange(swarm.particleLocalCount)

        Save to a file:

        >>> ignoreMe = swarm.save("saved_swarm.h5")
        >>> ignoreMe = svar.save("saved_swarm_variable.h5")

        Now let's try and reload. First create a new swarm and swarm variable,
        and then load both:

        >>> clone_swarm = uw.swarm.Swarm(mesh)
        >>> clone_svar = clone_swarm.add_variable("int",1)
        >>> clone_swarm.load("saved_swarm.h5")
        >>> clone_svar.load("saved_swarm_variable.h5")

        Now check for equality:

        >>> import numpy as np
        >>> np.allclose(svar.data,clone_svar.data)
        True

        >>> # clean up:
        >>> if uw.mpi.rank == 0:
        ...     import os;
        ...     os.remove( "saved_swarm.h5" )
        ...     os.remove( "saved_swarm_variable.h5" )

        """

        if not isinstance(filename, str):
            raise TypeError("'filename' parameter must be of type 'str'")

        # setup mpi basic vars
        comm = MPI.COMM_WORLD
        rank = comm.rank

        # allgather the number of particles each proc has
        swarm = self.swarm
        procCount = comm.allgather(swarm.particleLocalCount)
        particleGlobalCount = np.sum(procCount)

        # calculate the hdf5 file offset
        offset = 0
        for i in range(comm.rank):
            offset += procCount[i]

        # open parallel hdf5 file
        with h5py.File(name=filename,
                       mode="w",
                       driver='mpio',
                       comm=MPI.COMM_WORLD) as h5f:
            # write the entire local swarm to the appropriate offset position
            globalShape = (particleGlobalCount, self.data.shape[1])
            dset = h5f.create_dataset("data",
                                      shape=globalShape,
                                      dtype=self.data.dtype)
            fact = 1.0
            if units:
                fact = dimensionalise(1.0, units=units).magnitude

            if collective:
                with dset.collective:
                    dset[offset:offset +
                         swarm.particleLocalCount] = self.data[:] * fact
            else:
                dset[offset:offset +
                     swarm.particleLocalCount] = self.data[:] * fact

        # let's reopen in serial to write the attrib.
        # not sure if this really is necessary.
        comm.Barrier()
        if comm.rank == 0:
            with h5py.File(name=filename, mode="a") as h5f:
                # attribute of the proc offsets - used for loading from checkpoint
                h5f.attrs["proc_offset"] = procCount
                h5f.attrs['units'] = str(units)
                h5f.attrs['time'] = str(time)
                h5f.attrs["git commit"] = __git_revision__

        return uw.utils.SavedFileData(self, filename)
Exemplo n.º 9
0
    def save(self, filename, units=None, time=None):
        """
        Save the mesh to disk

        Parameters
        ----------
        filename : string
            The name of the output file.

        Returns
        -------
        underworld.utils.SavedFileData
            Data object relating to saved file. This only needs to be retained
            if you wish to create XDMF files and can be ignored otherwise.

        Notes
        -----
        This method must be called collectively by all processes.

        Example
        -------
        First create the mesh:

        >>> mesh = uw.mesh.FeMesh_Cartesian( elementType='Q1/dQ0', elementRes=(16,16), minCoord=(0.,0.), maxCoord=(1.,1.) )

        Save to a file (note that the 'ignoreMe' object isn't really required):

        >>> ignoreMe = mesh.save("saved_mesh.h5")

        Now let's try and reload. First create new mesh (note the different spatial size):

        >>> clone_mesh = uw.mesh.FeMesh_Cartesian( elementType='Q1/dQ0', elementRes=(16,16), minCoord=(0.,0.), maxCoord=(1.5,1.5) )

        Confirm clone mesh is different from original mesh:

        >>> import numpy as np
        >>> np.allclose(mesh.data,clone_mesh.data)
        False

        Now reload using saved file:

        >>> clone_mesh.load("saved_mesh.h5")

        Now check for equality:

        >>> np.allclose(mesh.data,clone_mesh.data)
        True

        >>> # clean up:
        >>> if uw.mpi.rank == 0:
        ...     import os;
        ...     os.remove( "saved_mesh.h5" )

        """

        if hasattr(self.generator, 'geometryMesh'):
            raise RuntimeError("Cannot save this mesh as it's a subMesh. "
                                + "Most likely you only need to save its geometryMesh")
        if not isinstance(filename, str):
            raise TypeError("'filename', must be of type 'str'")

        h5f = h5py.File(name=filename, mode="w", driver='mpio', comm=MPI.COMM_WORLD)

        fact = 1.0
        if units:
            fact = dimensionalise(1.0, units=units).magnitude
            h5f.attrs['units'] = str(units)

        # save attributes and simple data - MUST be parallel as driver is mpio
        h5f.attrs['dimensions'] = self.dim
        h5f.attrs['mesh resolution'] = self.elementRes
        h5f.attrs['max'] = tuple([fact*x for x in self.maxCoord])
        h5f.attrs['min'] = tuple([fact*x for x in self.minCoord])
        h5f.attrs['regular'] = self._cself.isRegular
        h5f.attrs['elementType'] = self.elementType
        h5f.attrs['time'] = str(time)
        h5f.attrs["git commit"] = __git_revision__

        # write the vertices
        globalShape = ( self.nodesGlobal, self.data.shape[1] )
        dset = h5f.create_dataset("vertices",
                                  shape=globalShape,
                                  dtype=self.data.dtype)

        local = self.nodesLocal
        # write to the dset using the local set of global node ids
        with dset.collective:
            dset[self.data_nodegId[0:local],:] = self.data[0:local] * fact

        # write the element node connectivity
        globalShape = ( self.elementsGlobal, self.data_elementNodes.shape[1] )
        dset = h5f.create_dataset("en_map",
                                  shape=globalShape,
                                  dtype=self.data_elementNodes.dtype)

        local = self.elementsLocal
        # write to the dset using the local set of global node ids
        with dset.collective:
            dset[self.data_elgId[0:local], :] = self.data_elementNodes[0:local]

        h5f.close()

        # return our file handle
        return uw.utils.SavedFileData(self, filename)
    def solve(self, dt):

        root_proc = 0

        z_max_local = self.Model.mesh.data[:self.Model.mesh.nodesLocal,
                                           1].max()
        z_min_local = self.Model.mesh.data[:self.Model.mesh.nodesLocal,
                                           1].min()

        ### collect surface data on each node
        if z_max_local >= self.surface_data_local[:, 1].min():
            ### gets the x and z data of the surface tracers
            x_data = np.ascontiguousarray(self.surface_data_local[:, 0].copy())
            z_data = np.ascontiguousarray(self.surface_data_local[:, 1].copy())

            ### Get the velocity of the surface tracers
            tracer_velocity = self.Model.velocityField.evaluate(
                self.surface_data_local)
            vx = np.ascontiguousarray(tracer_velocity[:, 0])
            vz = np.ascontiguousarray(tracer_velocity[:, 1])
        else:
            ### creates dummy data on nodes without the surface
            x_data = np.array([None], dtype='float64')
            z_data = np.array([None], dtype='float64')
            vx = np.array([None], dtype='float64')
            vz = np.array([None], dtype='float64')

        ### Collect local array sizes using the high-level mpi4py gather
        sendcounts = np.array(comm.gather(len(x_data), root=root_proc))

        comm.barrier()

        if rank == root_proc:
            ### creates dummy data on all nodes to store the surface
            # surface_data = np.zeros((npoints,2))
            x_surface_data = np.zeros((sum(sendcounts)), dtype='float64')
            z_surface_data = np.zeros((sum(sendcounts)), dtype='float64')
            vx_data = np.zeros((sum(sendcounts)), dtype='float64')
            vz_data = np.zeros((sum(sendcounts)), dtype='float64')
            surface_data = np.zeros((sum(sendcounts), 4), dtype='float64')
        else:
            x_surface_data = None
            z_surface_data = None
            vx_data = None
            vz_data = None
            surface_data = None

        ### store the surface spline on each node
        f1 = None

        comm.barrier()

        ## gather x values, can't do them together
        comm.Gatherv(sendbuf=x_data,
                     recvbuf=(x_surface_data, sendcounts),
                     root=root_proc)
        ## gather z values
        comm.Gatherv(sendbuf=z_data,
                     recvbuf=(z_surface_data, sendcounts),
                     root=root_proc)

        ### gather velocity values
        comm.Gatherv(sendbuf=vx, recvbuf=(vx_data, sendcounts), root=root_proc)

        comm.Gatherv(sendbuf=vz, recvbuf=(vz_data, sendcounts), root=root_proc)

        if rank == root_proc:
            ### Put back into combined array
            surface_data[:, 0] = x_surface_data
            surface_data[:, 1] = z_surface_data
            surface_data[:, 2] = vx_data
            surface_data[:, 3] = vz_data

            ### remove dummy data
            surface_data = surface_data[~np.isnan(surface_data[:, 0])]

            ### sort by x values
            surface_data = surface_data[np.argsort(surface_data[:, 0])]

            # # Advect top surface
            x2 = surface_data[:, 0] + (surface_data[:, 2] * dt)
            z2 = surface_data[:, 1] + (surface_data[:, 3] * dt)

            # # Spline top surface
            f = interp1d(x2, z2, kind='cubic', fill_value='extrapolate')

            ### update surface tracer position
            # surface_data[:,0] = (surface_data[:,0])
            surface_data[:, 1] = f(surface_data[:, 0])

            ### gets the x and y coordinates from the tracers
            x = dimensionalise(surface_data[:, 0], u.kilometer).magnitude
            z = dimensionalise(surface_data[:, 1], u.kilometer).magnitude

            ### time to diffuse surface based on Model dt
            total_time = (dimensionalise(dt, u.year)).magnitude
            '''Velocity surface process'''
            '''erosion dt for vel model'''

            Vel_for_surface = max(
                abs(self.erosionRate * u.kilometer / u.year),
                abs(self.sedimentationRate * u.kilometer / u.year),
                abs(
                    dimensionalise(self.Model.velocityField.data.max(),
                                   u.kilometer / u.year)))

            surface_dt_vel = (0.2 * (self.dx / Vel_for_surface.magnitude))

            surface_time = min(surface_dt_vel, total_time)

            nts = math.ceil(total_time / surface_time)
            surface_dt = total_time / nts

            print('SP total time:',
                  round(total_time, 2),
                  'years, timestep:',
                  round(surface_dt, 2),
                  'years, No. of its:',
                  nts,
                  flush=True)

            ### Velocity erosion/sedimentation rates for the surface
            for i in range(nts):
                Ve_loop = np.where(z <= 0., 0., self.erosionRate)
                Vs_loop = np.where(z >= 0., 0., self.sedimentationRate)

                dzdt = Vs_loop + Ve_loop

                z[:] += dzdt * surface_dt

            x_nd = nd(x * u.kilometer)

            z_nd = nd(z * u.kilometer)
            ''' updates material near to boundary back to original coordinates '''
            z_original_surface = self.original_surface(x_nd)
            z_nd[(x_nd < nd(self.updateSurfaceLB * u.kilometer)) |
                 (x_nd > (nd(self.Model.maxCoord[0]) -
                          (nd(self.updateSurfaceRB * u.kilometer)))
                  )] = z_original_surface[
                      (x_nd < nd(self.updateSurfaceLB * u.kilometer)) |
                      (x_nd > (nd(self.Model.maxCoord[0]) -
                               (nd(self.updateSurfaceRB * u.kilometer))))]

            ### creates function for the new surface that has eroded, to be broadcast back to nodes
            f1 = interp1d(x_nd, z_nd, fill_value='extrapolate', kind='cubic')

        comm.barrier()
        '''broadcast the new surface'''
        ### broadcast function for the surface
        f1 = comm.bcast(f1, root=root_proc)

        comm.barrier()
        ''' replaces the new diffused surface data, only changes z as x values don't change '''
        ### update the surface on individual nodes
        self.surface_data_local[:, 1] = f1(self.surface_data_local[:, 0])
        ### update the global surface tracers
        with self.Model.surface_tracers.deform_swarm():
            self.Model.surface_tracers.data[:, 1] = f1(
                self.Model.surface_tracers.data[:, 0])
        '''Erode surface/deposit sed based on the surface'''
        ### update the material on each node according to the spline function for the surface
        self.Model.materialField.data[
            (self.Model.swarm.data[:, 1] > f1(self.Model.swarm.data[:, 0]))
            & (self.Model.materialField.data[:, 0] != self.airIndex
               )] = self.airIndex
        self.Model.materialField.data[
            (self.Model.swarm.data[:, 1] < f1(self.Model.swarm.data[:, 0]))
            & (self.Model.materialField.data[:, 0] == self.airIndex
               )] = self.sedimentIndex

        comm.barrier()

        return
Exemplo n.º 11
0
    def save(self, filename, meshHandle=None, units=None, time=None):
        """
        Save the MeshVariable to disk.

        Parameters
        ----------
        filename : string
            The name of the output file. Relative or absolute paths may be
            used, but all directories must exist.
        meshHandle :uw.utils.SavedFileData , optional
            The saved mesh file handle. If provided, a link is created within the
            mesh variable file to this saved mesh file. Important for checkpoint when
            the mesh deforms.

        Notes
        -----
        This method must be called collectively by all processes.

        Returns
        -------
        underworld.utils.SavedFileData
            Data object relating to saved file. This only needs to be retained
            if you wish to create XDMF files and can be ignored otherwise.

        Example
        -------
        First create the mesh add a variable:

        >>> mesh = uw.mesh.FeMesh_Cartesian( elementType='Q1/dQ0', elementRes=(16,16), minCoord=(0.,0.), maxCoord=(1.,1.) )
        >>> var = uw.mesh.MeshVariable( mesh=mesh, nodeDofCount=1, dataType="double" )

        Write something to variable

        >>> import numpy as np
        >>> var.data[:,0] = np.arange(var.data.shape[0])

        Save to a file (note that the 'ignoreMe' object isn't really required):

        >>> ignoreMe = var.save("saved_mesh_variable.h5")

        Now let's try and reload.

        >>> clone_var = uw.mesh.MeshVariable( mesh=mesh, nodeDofCount=1, dataType="double" )
        >>> clone_var.load("saved_mesh_variable.h5")

        Now check for equality:

        >>> np.allclose(var.data,clone_var.data)
        True

        >>> # clean up:
        >>> if uw.mpi.rank == 0:
        ...     import os;
        ...     os.remove( "saved_mesh_variable.h5" )

        """
        if not isinstance(filename, str):
            raise TypeError("Expected 'filename' to be provided as a string")

        mesh = self.mesh
        h5f = h5py.File(name=filename,
                        mode="w",
                        driver='mpio',
                        comm=MPI.COMM_WORLD)

        # ugly global shape def
        globalShape = (mesh.nodesGlobal, self.data.shape[1])
        # create dataset
        dset = h5f.create_dataset("data",
                                  shape=globalShape,
                                  dtype=self.data.dtype)
        fact = 1.0
        if units:
            fact = dimensionalise(1.0, units=units).magnitude
            if units == "degC":
                fact = dimensionalise(1.0, units=u.degK).magnitude
            # Save unit type as attribute
            h5f.attrs['units'] = str(units)

        if time:
            h5f.attrs['time'] = str(time)

        h5f.attrs["git commit"] = __git_revision__

        # write to the dset using the global node ids
        local = mesh.nodesLocal

        with dset.collective:
            if units == "degC":
                dset[mesh.data_nodegId[0:local], :] = self.data[
                    0:local] * fact - 273.15
            else:
                dset[mesh.data_nodegId[0:local], :] = self.data[0:local] * fact

        # save a hdf5 attribute to the elementType used for this field - maybe useful
        h5f.attrs["elementType"] = np.string_(mesh.elementType)

        if hasattr(mesh.generator, "geometryMesh"):
            mesh = mesh.generator.geometryMesh

        if meshHandle:
            if not isinstance(meshHandle, (str, uw.utils.SavedFileData)):
                raise TypeError(
                    "Expected 'meshHandle' to be of type 'uw.utils.SavedFileData'"
                )

            if isinstance(meshHandle, str):
                # DEPRECATION check
                import warnings
                warnings.warn(
                    "'meshHandle' paramater should be of type uw.utils.SaveFileData. Please update your models. "
                    +
                    "Accepting 'meshHandle' as a string parameter will be removed in the next release."
                )
                meshFilename = meshHandle
            else:
                meshFilename = meshHandle.filename

            if not os.path.exists(meshFilename):
                raise ValueError(
                    "You are trying to link against the mesh file '{}'\n\
                                  that does not appear to exist. If you need to link \n\
                                  against a mesh file, please make sure it is created first."
                    .format(meshFilename))
            # set reference to mesh (all procs must call following)
            h5f["mesh"] = h5py.ExternalLink(meshFilename, "./")

        h5f.close()

        # return our file handle
        return uw.utils.SavedFileData(self, filename)