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()
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
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')
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
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
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()
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)
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
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)