landlab

Source code for landlab.grid.voronoi

#! /usr/env/python
"""
Python implementation of VoronoiDelaunayGrid, a class used to create and manage
unstructured, irregular grids for 2D numerical models.

Do NOT add new documentation here. Grid documentation is now built in a semi-
automated fashion. To modify the text seen on the web, edit the files
`docs/text_for_[gridfile].py.txt`.
"""
import numpy as np
from six.moves import range

from landlab.grid.base import (ModelGrid, CORE_NODE, BAD_INDEX_VALUE,
                               INACTIVE_LINK)
from landlab.core.utils import (as_id_array, sort_points_by_x_then_y,
                                argsort_points_by_x_then_y,
                                anticlockwise_argsort_points)
from .decorators import return_readonly_id_array

from scipy.spatial import Voronoi


[docs]def simple_poly_area(x, y): """Calculates and returns the area of a 2-D simple polygon. Input vertices must be in sequence (clockwise or counterclockwise). *x* and *y* are arrays that give the x- and y-axis coordinates of the polygon's vertices. Parameters ---------- x : ndarray x-coordinates of of polygon vertices. y : ndarray y-coordinates of of polygon vertices. Returns ------- out : float Area of the polygon Examples -------- >>> import numpy as np >>> from landlab.grid.voronoi import simple_poly_area >>> x = np.array([3., 1., 1., 3.]) >>> y = np.array([1.5, 1.5, 0.5, 0.5]) >>> simple_poly_area(x, y) 2.0 If the input coordinate arrays are 2D, calculate the area of each polygon. Note that when used in this mode, all polygons must have the same number of vertices, and polygon vertices are listed column-by-column. >>> x = np.array([[ 3., 1., 1., 3.], ... [-2., -2., -1., -1.]]).T >>> y = np.array([[1.5, 1.5, 0.5, 0.5], ... [ 0., 1., 2., 0.]]).T >>> simple_poly_area(x, y) array([ 2. , 1.5]) """ # For short arrays (less than about 100 elements) it seems that the # Python sum is faster than the numpy sum. Likewise for the Python # built-in abs. return .5 * abs(sum(x[:-1] * y[1:] - x[1:] * y[:-1]) + x[-1] * y[0] - x[0] * y[-1])
[docs]class VoronoiDelaunayGrid(ModelGrid): """ This inherited class implements an unstructured grid in which cells are Voronoi polygons and nodes are connected by a Delaunay triangulation. Uses scipy.spatial module to build the triangulation. Create an unstructured grid from points whose coordinates are given by the arrays *x*, *y*. Parameters ---------- x : array_like x-coordinate of points y : array_like y-coordinate of points reorient_links (optional) : bool whether to point all links to the upper-right quadrant Returns ------- VoronoiDelaunayGrid A newly-created grid. Examples -------- >>> from numpy.random import rand >>> from landlab.grid import VoronoiDelaunayGrid >>> x, y = rand(25), rand(25) >>> vmg = VoronoiDelaunayGrid(x, y) # node_x_coords, node_y_coords >>> vmg.number_of_nodes 25 >>> import numpy as np >>> x = [0, 0.1, 0.2, 0.3, ... 1, 1.1, 1.2, 1.3, ... 2, 2.1, 2.2, 2.3,] >>> y = [0, 1, 2, 3, ... 0, 1, 2, 3, ... 0, 1, 2, 3] >>> vmg = VoronoiDelaunayGrid(x, y) >>> vmg.node_x # doctest: +NORMALIZE_WHITESPACE array([ 0. , 1. , 2. , 0.1, 1.1, 2.1, 0.2, 1.2, 2.2, 0.3, 1.3, 2.3]) >>> vmg.node_y # doctest: +NORMALIZE_WHITESPACE array([ 0., 0., 0., 1., 1., 1., 2., 2., 2., 3., 3., 3.]) """ def __init__(self, x=None, y=None, reorient_links=True, **kwds): """ Create a Voronoi Delaunay grid from a set of points. Create an unstructured grid from points whose coordinates are given by the arrays *x*, *y*. Parameters ---------- x : array_like x-coordinate of points y : array_like y-coordinate of points reorient_links (optional) : bool whether to point all links to the upper-right quadrant Returns ------- VoronoiDelaunayGrid A newly-created grid. Examples -------- >>> from numpy.random import rand >>> from landlab.grid import VoronoiDelaunayGrid >>> x, y = rand(25), rand(25) >>> vmg = VoronoiDelaunayGrid(x, y) # node_x_coords, node_y_coords >>> vmg.number_of_nodes 25 """ if (x is not None) and (y is not None): self._initialize(x, y, reorient_links) super(VoronoiDelaunayGrid, self).__init__(**kwds) def _initialize(self, x, y, reorient_links=True): """ Creates an unstructured grid around the given (x,y) points. """ x = np.asarray(x, dtype=float).reshape((-1, )) y = np.asarray(y, dtype=float).reshape((-1, )) if x.size != y.size: raise ValueError('x and y arrays must have the same size') # Make a copy of the points in a 2D array (useful for calls to geometry # routines, but takes extra memory space). pts = np.zeros((len(x), 2)) pts[:, 0] = x pts[:, 1] = y self.pts = sort_points_by_x_then_y(pts) x = self.pts[:, 0] y = self.pts[:, 1] # NODES AND CELLS: Set up information pertaining to nodes and cells: # - number of nodes # - node x, y coordinates # - default boundary status # - interior and boundary nodes # - nodes associated with each cell and active cell # - cells and active cells associated with each node # (or BAD_VALUE_INDEX if none) # # Assumptions we make here: # - all interior (non-perimeter) nodes have cells (this should be # guaranteed in a Delaunay triangulation, but there may be # special cases) # - all cells are active (later we'll build a mechanism for the user # specify a subset of cells as active) # self._node_x = x self._node_y = y [self._node_status, self._core_nodes, self._boundary_nodes] = \ self._find_perimeter_nodes_and_BC_set(pts) [self._cell_at_node, self._node_at_cell] = \ self._node_to_cell_connectivity(self._node_status, self.number_of_cells) active_cell_at_node = self.cell_at_node[self.core_nodes] # ACTIVE CELLS: Construct Voronoi diagram and calculate surface area of # each active cell. vor = Voronoi(self.pts) self.vor = vor self._area_of_cell = np.zeros(self.number_of_cells) for node in self._node_at_cell: xv = vor.vertices[vor.regions[vor.point_region[node]], 0] yv = vor.vertices[vor.regions[vor.point_region[node]], 1] self._area_of_cell[self.cell_at_node[node]] = ( simple_poly_area(xv, yv)) # LINKS: Construct Delaunay triangulation and construct lists of link # "from" and "to" nodes. (self._node_at_link_tail, self._node_at_link_head, _, self._face_width) = \ self._create_links_and_faces_from_voronoi_diagram(vor) self._status_at_link = np.full(len(self._node_at_link_tail), INACTIVE_LINK, dtype=int) # Sort them by midpoint coordinates self._sort_links_by_midpoint() # Optionally re-orient links so that they all point within upper-right # semicircle if reorient_links: self._reorient_links_upper_right() # LINKS: Calculate link lengths self._link_length = calculate_link_lengths(self.pts, self.node_at_link_tail, self.node_at_link_head) # LINKS: inlink and outlink matrices # SOON TO BE DEPRECATED self._setup_inlink_and_outlink_matrices() # ACTIVE LINKS: Create list of active links, as well as "from" and "to" # nodes of active links. self._reset_link_status_list() # NODES & LINKS: IDs and directions of links at each node self._create_links_and_link_dirs_at_node() # LINKS: set up link unit vectors and node unit-vector sums self._create_link_unit_vectors() # create link x, y: self._create_link_face_coords() self._create_neighbors() @property def number_of_patches(self): """Number of patches. Returns the number of patches over the grid. LLCATS: PINF """ try: return self._number_of_patches except AttributeError: self._create_patches_from_delaunay_diagram(self.pts, self.vor) return self._number_of_patches @property def nodes_at_patch(self): """Get the four nodes at the corners of each patch in a regular grid. LLCATS: PINF NINF CONN """ try: return self._nodes_at_patch except AttributeError: self._create_patches_from_delaunay_diagram(self.pts, self.vor) return self._nodes_at_patch @property @return_readonly_id_array def patches_at_node(self): """ Return a (nnodes, max_voronoi_polygon_sides) array of patches at nodes. The patches are returned in LL standard order (ccw from E), with any nonexistent patches recorded after the ids of existing faces. Nonexistent patches are ID'ed as -1. Examples -------- >>> from landlab import HexModelGrid >>> mg = HexModelGrid(3, 3) >>> mg.patches_at_node # doctest: +SKIP array([[ 0, 2, -1, -1, -1, -1], [ 1, 3, 0, -1, -1, -1], [ 4, 1, -1, -1, -1, -1], [ 5, 2, -1, -1, -1, -1], [ 6, 8, 5, 2, 0, 3], [ 7, 9, 6, 3, 1, 4], [ 7, 4, -1, -1, -1, -1], [ 5, 8, -1, -1, -1, -1], [ 8, 6, 9, -1, -1, -1], [ 9, 7, -1, -1, -1, -1]]) LLCATS: NINF PINF CONN """ try: return self._patches_at_node except AttributeError: self._create_patches_from_delaunay_diagram(self.pts, self.vor) return self._patches_at_node @property @return_readonly_id_array def links_at_patch(self): """Returns the links forming each patch. Examples -------- >>> from landlab import HexModelGrid >>> mg = HexModelGrid(3, 2) >>> mg.links_at_patch array([[ 3, 2, 0], [ 5, 1, 2], [ 6, 3, 4], [ 8, 7, 5], [10, 9, 6], [11, 8, 9]]) LLCATS: LINF PINF CONN """ try: return self._links_at_patch except AttributeError: self._create_patches_from_delaunay_diagram(self.pts, self.vor) return self._links_at_patch @property @return_readonly_id_array def patches_at_link(self): """Returns the patches adjoined to each link. Examples -------- >>> from landlab import HexModelGrid >>> mg = HexModelGrid(3, 2) >>> mg.patches_at_link array([[ 0, -1], [ 1, -1], [ 0, 1], [ 0, 2], [ 2, -1], [ 1, 3], [ 2, 4], [ 3, -1], [ 3, 5], [ 4, 5], [ 4, -1], [ 5, -1]]) LLCATS: PINF LINF CONN """ try: return self._patches_at_link except AttributeError: self._create_patches_from_delaunay_diagram(self.pts, self.vor) return self._patches_at_link def _find_perimeter_nodes_and_BC_set(self, pts): """ Uses a convex hull to locate the perimeter nodes of the Voronoi grid, then sets them as fixed value boundary nodes. It then sets/updates the various relevant node lists held by the grid, and returns *node_status*, *core_nodes*, *boundary_nodes*. """ # Calculate the convex hull for the set of points from scipy.spatial import ConvexHull hull = ConvexHull(pts, qhull_options='Qc') # see below why we use 'Qt' # The ConvexHull object lists the edges that form the hull. We need to # get from this list of edges the unique set of nodes. To do this, we # first flatten the list of vertices that make up all the hull edges # ("simplices"), so it becomes a 1D array. With that, we can use the # set() function to turn the array into a set, which removes duplicate # vertices. Then we turn it back into an array, which now contains the # set of IDs for the nodes that make up the convex hull. # The next thing to worry about is the fact that the mesh perimeter # might contain nodes that are co-planar (that is, co-linear in our 2D # world). For example, if you make a set of staggered points for a # hexagonal lattice using make_hex_points(), there will be some # co-linear points along the perimeter. The ones of these that don't # form convex corners won't be included in convex_hull_nodes, but they # are nonetheless part of the perimeter and need to be included in # the list of boundary_nodes. To deal with this, we pass the 'Qt' # option to ConvexHull, which makes it generate a list of coplanar # points. We include these in our set of boundary nodes. convex_hull_nodes = np.array(list(set(hull.simplices.flatten()))) coplanar_nodes = hull.coplanar[:, 0] boundary_nodes = as_id_array(np.concatenate( (convex_hull_nodes, coplanar_nodes))) # Now we'll create the "node_status" array, which contains the code # indicating whether the node is interior and active (=0) or a # boundary (=1). This means that all perimeter (convex hull) nodes are # initially flagged as boundary code 1. An application might wish to # change this so that, for example, some boundaries are inactive. node_status = np.zeros(len(pts[:, 0]), dtype=np.int8) node_status[boundary_nodes] = 1 # It's also useful to have a list of interior nodes core_nodes = as_id_array(np.where(node_status == 0)[0]) # save the arrays and update the properties self._node_status = node_status self._core_cells = np.arange(len(core_nodes), dtype=np.int) self._node_at_cell = core_nodes self._boundary_nodes = boundary_nodes # Return the results return node_status, core_nodes, boundary_nodes def _create_cell_areas_array(self): """Set up an array of cell areas.""" self._cell_areas = self.active_cell_areas return self._cell_areas @staticmethod def _node_to_cell_connectivity(node_status, ncells): """Set up node connectivity. Creates and returns the following arrays: * For each node, the ID of the corresponding cell, or BAD_INDEX_VALUE if the node has no cell. * For each cell, the ID of the corresponding node. Parameters ---------- node_status : ndarray of ints 1D array containing the boundary status code for each node. ncells : ndarray of ints Number of cells (must equal the number of occurrences of CORE_NODE in node_status). Examples -------- >>> from landlab import VoronoiDelaunayGrid as vdg >>> import numpy as np >>> from landlab.grid import BAD_INDEX_VALUE >>> ns = np.array([1, 0, 0, 1, 0]) # 3 interior, 2 boundary nodes >>> [node_cell, cell_node] = vdg._node_to_cell_connectivity(ns, 3) >>> node_cell[1:3] array([0, 1]) >>> node_cell[0] == BAD_INDEX_VALUE True >>> cell_node array([1, 2, 4]) """ assert ncells == np.count_nonzero(node_status == CORE_NODE), \ 'ncells must equal number of CORE_NODE values in node_status' cell = 0 node_cell = np.ones(len(node_status), dtype=int) * BAD_INDEX_VALUE cell_node = np.zeros(ncells, dtype=int) for node in range(len(node_cell)): if node_status[node] == CORE_NODE: node_cell[node] = cell cell_node[cell] = node cell += 1 return node_cell, cell_node @staticmethod def _create_links_from_triangulation(tri): """Create links from a Delaunay triangulation. From a Delaunay Triangulation of a set of points, contained in a scipy.spatial.Delaunay object "tri", creates and returns: * a numpy array containing the ID of the "from" node for each link * a numpy array containing the ID of the "to" node for each link * the number of links in the triangulation Examples -------- >>> from scipy.spatial import Delaunay >>> import numpy as np >>> from landlab.grid import VoronoiDelaunayGrid as vdg >>> pts = np.array([[ 0., 0.], [ 1., 0.], [ 1., 0.87], ... [-0.5, 0.87], [ 0.5, 0.87], [ 0., 1.73], ... [ 1., 1.73]]) >>> dt = Delaunay(pts) >>> [myfrom,myto,nl] = vdg._create_links_from_triangulation(dt) >>> print myfrom, myto, nl # doctest: +SKIP [5 3 4 6 4 3 0 4 1 1 2 6] [3 4 5 5 6 0 4 1 0 2 4 2] 12 """ # Calculate how many links there will be and create the arrays. # # The number of links equals 3 times the number of triangles minus # half the number of shared links. Finding out the number of shared # links is easy: for every shared link, there is an entry in the # tri.neighbors array that is > -1 (indicating that the triangle has a # neighbor opposite a given vertex; in other words, two triangles are # sharing an edge). num_shared_links = np.count_nonzero(tri.neighbors > -1) num_links = 3 * tri.nsimplex - num_shared_links // 2 link_fromnode = np.zeros(num_links, dtype=int) link_tonode = np.zeros(num_links, dtype=int) # Sweep through the list of triangles, assigning "from" and "to" nodes # to the list of links. # # The basic algorithm works as follows. For each triangle, we will add # its 3 edges as links. However, we have to make sure that each shared # edge is added only once. To do this, we keep track of whether or not # each triangle has been processed yet using a boolean array called # "tridone". When we look at a given triangle, we check each vertex in # turn. If there is no neighboring triangle opposite that vertex, then # we need to add the corresponding edge. If there is a neighboring # triangle but we haven't processed it yet, we also need to add the # edge. If neither condition is true, then this edge has already been # added, so we skip it. link_id = 0 tridone = np.zeros(tri.nsimplex, dtype=bool) for t in range(tri.nsimplex): # loop over triangles for i in range(0, 3): # loop over vertices & neighbors if tri.neighbors[t, i] == -1 or not tridone[ tri.neighbors[t, i]]: link_fromnode[link_id] = tri.simplices[ t, np.mod(i + 1, 3)] link_tonode[link_id] = tri.simplices[ t, np.mod(i + 2, 3)] link_id += 1 tridone[t] = True # save the results # self.node_at_link_tail = link_fromnode # self.node_at_link_head = link_tonode # Return the results return link_fromnode, link_tonode, num_links @staticmethod def _is_valid_voronoi_ridge(vor, n): SUSPICIOUSLY_BIG = 40000000.0 return (vor.ridge_vertices[n][0] != -1 and vor.ridge_vertices[n][1] != -1 and np.amax(np.abs(vor.vertices[ vor.ridge_vertices[n]])) < SUSPICIOUSLY_BIG) @staticmethod def _create_links_and_faces_from_voronoi_diagram(vor): """ From a Voronoi diagram object created by scipy.spatial.Voronoi(), builds and returns: 1. Arrays of link tail and head nodes 2. Array of link IDs for each active link 3. Array containing with of each face Parameters ---------- vor : scipy.spatial.Voronoi Voronoi object initialized with the grid nodes. Returns ------- out : tuple of ndarrays - link_fromnode = "from" node for each link (len=num_links) - link_tonode = "to" node for each link (len=num_links) - active_links = link ID for each active link (len=num_active_links) - face_width = width of each face (len=num_active_links Examples -------- >>> import numpy as np >>> from landlab.grid import VoronoiDelaunayGrid as vdg >>> pts = np.array([[0., 0.], [1., 0.], [-0.5, 0.87], [0.5, 0.87], ... [1.5, 0.87], [0., 1.73], [1., 1.73]]) >>> from scipy.spatial import Voronoi >>> vor = Voronoi(pts) >>> [tn,hn,al,fw] = vdg._create_links_and_faces_from_voronoi_diagram( ... vor) >>> tn array([0, 0, 0, 1, 1, 2, 3, 2, 3, 6, 6, 6]) >>> hn array([1, 2, 3, 3, 4, 3, 4, 5, 5, 3, 4, 5]) >>> al array([2, 3, 5, 6, 8, 9]) >>> fw array([ 0.57669199, 0.57669199, 0.575973 , 0.575973 , 0.57836419, 0.57836419]) """ # Each Voronoi "ridge" corresponds to a link. The Voronoi object has an # attribute ridge_points that contains the IDs of the nodes on either # side (including ridges that have one of their endpoints undefined). # So, we set the number of links equal to the number of ridges. num_links = len(vor.ridge_points) # Create the arrays for link from and to nodes link_fromnode = -np.ones(num_links, dtype=int) link_tonode = -np.ones(num_links, dtype=int) # Ridges along the perimeter of the grid will have one of their # endpoints undefined. The endpoints of each ridge are contained in # vor.ridge_vertices, and an undefined vertex is flagged with -1. # Ridges with both vertices defined correspond to faces and active # links, while ridges with an undefined vertex correspond to inactive # links. So, to find the number of active links, we subtract from the # total number of links the number of occurrences of an undefined # vertex. num_active_links = num_links \ - np.count_nonzero(np.array(vor.ridge_vertices) == -1) # Create arrays for active links and width of faces (which are Voronoi # ridges). active_links = -np.ones(num_active_links, dtype=int) face_width = -np.ones(num_active_links) # Find the order to sort by link midpoints link_midpoints = np.zeros((num_links, 2)) for i in range(num_links): link_midpoints[i][:] = (vor.points[vor.ridge_points[i, 0]] + vor.points[vor.ridge_points[i, 1]])/2. ind = argsort_points_by_x_then_y(link_midpoints) # Loop through the list of ridges. For each ridge, there is a link, and # its "from" and "to" nodes are the associated "points". In addition, # if the ridge endpoints are defined, we have a face and an active # link, so we add them to our arrays as well. j = 0 for i in range(num_links): link_fromnode[i] = vor.ridge_points[ind[i], 0] link_tonode[i] = vor.ridge_points[ind[i], 1] face_corner1 = vor.ridge_vertices[ind[i]][0] face_corner2 = vor.ridge_vertices[ind[i]][1] # means it's a valid face if VoronoiDelaunayGrid._is_valid_voronoi_ridge(vor, ind[i]): dx = vor.vertices[face_corner2, 0] - \ vor.vertices[face_corner1, 0] dy = vor.vertices[face_corner2, 1] - \ vor.vertices[face_corner1, 1] face_width[j] = np.sqrt(dx * dx + dy * dy) active_links[j] = i j += 1 return link_fromnode, link_tonode, active_links, face_width def _reorient_links_upper_right(self): """Reorient links to all point within the upper-right semi-circle. Notes ----- "Upper right semi-circle" means that the angle of the link with respect to the vertical (measured clockwise) falls between -45 and +135. More precisely, if :math:`\theta' is the angle, :math:`-45 \ge \theta < 135`. For example, the link could point up and left as much as -45, but not -46. It could point down and right as much as 134.9999, but not 135. It will never point down and left, or up-but-mostly-left, or right-but-mostly-down. Examples -------- >>> from landlab.grid import HexModelGrid >>> hg = HexModelGrid(3, 2, 1., reorient_links=True) >>> hg.node_at_link_tail array([0, 0, 0, 1, 1, 2, 3, 2, 3, 3, 4, 5]) >>> hg.node_at_link_head array([1, 2, 3, 3, 4, 3, 4, 5, 5, 6, 6, 6]) """ # Calculate the horizontal (dx) and vertical (dy) link offsets link_dx = self.node_x[self.node_at_link_head] - \ self.node_x[self.node_at_link_tail] link_dy = self.node_y[self.node_at_link_head] - \ self.node_y[self.node_at_link_tail] # Calculate the angle, clockwise, with respect to vertical, then rotate # by 45 degrees counter-clockwise (by adding pi/4) link_angle = np.arctan2(link_dx, link_dy) + np.pi / 4 # The range of values should be -180 to +180 degrees (but in radians). # It won't be after the above operation, because angles that were # > 135 degrees will now have values > 180. To correct this, we # subtract 360 (i.e., 2 pi radians) from those that are > 180 (i.e., # > pi radians). link_angle -= 2 * np.pi * (link_angle >= np.pi) # Find locations where the angle is negative; these are the ones we # want to flip (flip_locs, ) = np.where(link_angle < 0.) # If there are any flip locations, proceed to switch their fromnodes # and tonodes; otherwise, we're done if len(flip_locs) > 0: # Temporarily story the fromnode for these fromnode_temp = self.node_at_link_tail[flip_locs] # The fromnodes now become the tonodes, and vice versa self._node_at_link_tail[ flip_locs] = self.node_at_link_head[flip_locs] self._node_at_link_head[flip_locs] = fromnode_temp def _create_patches_from_delaunay_diagram(self, pts, vor): """ Uses a delaunay diagram drawn from the provided points to generate an array of patches and patch-node-link connectivity. Returns ... DEJH, 10/3/14, modified May 16. """ from scipy.spatial import Delaunay from landlab.core.utils import anticlockwise_argsort_points_multiline from .cfuncs import find_rows_containing_ID, \ create_patches_at_element, create_links_at_patch tri = Delaunay(pts) assert np.array_equal(tri.points, vor.points) nodata = -1 self._nodes_at_patch = as_id_array(tri.simplices) # self._nodes_at_patch = np.empty_like(_nodes_at_patch) self._number_of_patches = tri.simplices.shape[0] # get the patches in order: patches_xy = np.empty((self._number_of_patches, 2), dtype=float) patches_xy[:, 0] = np.mean(self.node_x[self._nodes_at_patch], axis=1) patches_xy[:, 1] = np.mean(self.node_y[self._nodes_at_patch], axis=1) orderforsort = argsort_points_by_x_then_y(patches_xy) self._nodes_at_patch = self._nodes_at_patch[orderforsort, :] patches_xy = patches_xy[orderforsort, :] # get the nodes around the patch in order: nodes_xy = np.empty((3, 2), dtype=float) # perform a CCW sort without a line-by-line loop: patch_nodes_x = self.node_x[self._nodes_at_patch] patch_nodes_y = self.node_y[self._nodes_at_patch] anticlockwise_argsort_points_multiline(patch_nodes_x, patch_nodes_y, out=self._nodes_at_patch) # need to build a squared off, masked array of the patches_at_node # the max number of patches for a node in the grid is the max sides of # the side-iest voronoi region. max_dimension = len(max(vor.regions, key=len)) self._patches_at_node = np.full( (self.number_of_nodes, max_dimension), nodata, dtype=int) self._nodes_at_patch = as_id_array(self._nodes_at_patch) self._patches_at_node = as_id_array(self._patches_at_node) create_patches_at_element(self._nodes_at_patch, self.number_of_nodes, self._patches_at_node) # build the patch-link connectivity: self._links_at_patch = np.empty((self._number_of_patches, 3), dtype=int) create_links_at_patch(self._nodes_at_patch, self._links_at_node, self._number_of_patches, self._links_at_patch) patch_links_x = self.x_of_link[self._links_at_patch] patch_links_y = self.y_of_link[self._links_at_patch] anticlockwise_argsort_points_multiline(patch_links_x, patch_links_y, out=self._links_at_patch) self._patches_at_link = np.empty((self.number_of_links, 2), dtype=int) self._patches_at_link.fill(-1) create_patches_at_element(self._links_at_patch, self.number_of_links, self._patches_at_link) # a sort of the links will be performed here once we have corners self._patches_created = True def _create_neighbors(self): """Create the _neighbors_at_node property. """ self._neighbors_at_node = self.links_at_node.copy() nodes_at_link = np.empty((self.number_of_links, 2)) nodes_at_link[:, 0] = self.node_at_link_tail nodes_at_link[:, 1] = self.node_at_link_head both_nodes = nodes_at_link[self.links_at_node] nodes = np.arange(self.number_of_nodes, dtype=int) # ^we have to do this, as for a hex it's possible that mg.nodes is # returned not just in ID order. for i in range(both_nodes.shape[1]): centernottail = np.not_equal(both_nodes[:, i, 0], nodes) centernothead = np.not_equal(both_nodes[:, i, 1], nodes) self._neighbors_at_node[centernottail, i] = both_nodes[ centernottail, i, 0] self._neighbors_at_node[centernothead, i] = both_nodes[ centernothead, i, 1] # restamp the missing links: self._neighbors_at_node[ self.links_at_node == BAD_INDEX_VALUE] = BAD_INDEX_VALUE
[docs] def save(self, path, clobber=False): """Save a grid and fields. This method uses cPickle to save a Voronoi grid as a cPickle file. At the time of coding, this is the only convenient output format for Voronoi grids, but support for netCDF is likely coming. All fields will be saved, along with the grid. The recommended suffix for the save file is '.grid'. This will be added to your save if you don't include it. This method is equivalent to :py:func:`~landlab.io.native_landlab.save_grid`, and :py:func:`~landlab.io.native_landlab.load_grid` can be used to load these files. Caution: Pickling can be slow, and can produce very large files. Caution 2: Future updates to Landlab could potentially render old saves unloadable. Parameters ---------- path : str Path to output file. clobber : bool (defaults to false) Set to true to allow overwriting Examples -------- >>> from landlab import VoronoiDelaunayGrid >>> import numpy as np >>> import os >>> x = np.random.rand(20) >>> y = np.random.rand(20) >>> vmg = VoronoiDelaunayGrid(x,y) >>> vmg.save('./mytestsave.grid') >>> os.remove('mytestsave.grid') #to remove traces of this test LLCATS: GINF """ import os from six.moves import cPickle if os.path.exists(path) and not clobber: raise ValueError('file exists') (base, ext) = os.path.splitext(path) if ext != '.grid': ext = ext + '.grid' path = base + ext with open(path, 'wb') as fp: cPickle.dump(self, fp)
if __name__ == '__main__': import doctest doctest.testmod()