Source code for landlab.graph.structured_quad.structured_quad

from abc import ABC
from abc import abstractmethod
from functools import cached_property

import numpy as np

from ...grid.linkorientation import LinkOrientation
from ...utils.decorators import read_only_array
from ..graph import Graph


[docs] class StructuredQuadLayout(ABC):
[docs] @staticmethod def corner_nodes(shape): n_rows, n_cols = shape return (n_rows * n_cols - 1, (n_rows - 1) * n_cols, 0, n_cols - 1)
[docs] @staticmethod @abstractmethod def perimeter_nodes(shape): ...
[docs] @staticmethod @abstractmethod def patches_at_node(shape): ...
[docs] class StructuredQuadLayoutCython(StructuredQuadLayout):
[docs] @staticmethod def perimeter_nodes(shape): from .ext.at_node import fill_perimeter_nodes n_perimeter_nodes = 2 * shape[0] + 2 * (shape[1] - 2) perimeter_nodes = np.empty(n_perimeter_nodes, dtype=int) fill_perimeter_nodes(shape, perimeter_nodes) return perimeter_nodes
[docs] @staticmethod def patches_at_node(shape): from .ext.at_node import fill_patches_at_node n_nodes = shape[0] * shape[1] patches_at_node = np.empty((n_nodes, 4), dtype=int) fill_patches_at_node(shape, patches_at_node) return patches_at_node
[docs] class StructuredQuadLayoutPython(StructuredQuadLayout):
[docs] @staticmethod def perimeter_nodes(shape): n_rows, n_cols = shape ( northeast, northwest, southwest, southeast, ) = StructuredQuadLayout.corner_nodes(shape) return np.concatenate( ( np.arange(southeast, northeast, n_cols), np.arange(northeast, northwest, -1), np.arange(northwest, southwest, -n_cols), np.arange(southwest, southeast, 1), ) )
[docs] @staticmethod def patches_at_node(shape): n_rows, n_cols = shape patches_at_node = np.empty((4, n_rows * n_cols), dtype=int) ne = (slice(n_rows - 1), slice(n_cols - 1)) nw = (slice(n_rows - 1), slice(1, n_cols)) sw = (slice(1, n_rows), slice(1, n_cols)) se = (slice(1, n_rows), slice(n_cols - 1)) patches = np.arange((n_rows - 1) * (n_cols - 1), dtype=int).reshape( (n_rows - 1, n_cols - 1) ) for col, nodes in enumerate((ne, nw, sw, se)): patches_at_node[col].reshape((n_rows, n_cols))[nodes] = patches ( northeast, northwest, southwest, southeast, ) = StructuredQuadLayout.corner_nodes(shape) bottom = slice(southwest, southeast + 1) top = slice(northwest, northeast + 1) left = slice(southwest, northwest + n_cols, n_cols) right = slice(southeast, northeast + n_cols, n_cols) patches_at_node[0, right] = -1 patches_at_node[0, top] = -1 patches_at_node[1, left] = -1 patches_at_node[1, top] = -1 patches_at_node[2, left] = -1 patches_at_node[2, bottom] = -1 patches_at_node[3, right] = -1 patches_at_node[3, bottom] = -1 return patches_at_node.T
[docs] class StructuredQuadGraphTopology: _layout = StructuredQuadLayoutCython def __init__(self, shape): self._shape = tuple(shape) @property def shape(self): return self._shape @property def number_of_node_rows(self): return self._shape[0] @property def number_of_node_columns(self): return self._shape[1] @cached_property @read_only_array def nodes(self): """A shaped array of node ids. Returns ------- ndarray Node IDs in an array shaped as *number_of_node_rows* by *number_of_node_columns*. """ return np.arange(self.shape[0] * self.shape[1]).reshape(self.shape) @cached_property @read_only_array def nodes_at_right_edge(self): return np.arange(self.shape[1] - 1, np.prod(self.shape), self.shape[1]) @cached_property @read_only_array def nodes_at_top_edge(self): return np.arange(self.number_of_nodes - self.shape[1], np.prod(self.shape)) @cached_property @read_only_array def nodes_at_left_edge(self): return np.arange(0, np.prod(self.shape), self.shape[1]) @cached_property @read_only_array def nodes_at_bottom_edge(self): return np.arange(self.shape[1])
[docs] def nodes_at_edge(self, edge): if edge not in ("right", "top", "left", "bottom"): raise ValueError("value for edge not understood") return getattr(self, f"nodes_at_{edge}_edge")
@property def nodes_at_corners_of_grid(self): """Nodes at corners of grid. The nodes at at the corners of the grid. The nodes are returned counterclockwise starting with the upper-right. Return ------ tuple of int Nodes at the four corners. Examples -------- >>> from landlab.graph import UniformRectilinearGraph >>> graph = UniformRectilinearGraph((4, 5)) >>> graph.nodes_at_corners_of_grid (19, 15, 0, 4) """ return ( self.number_of_nodes - 1, self.number_of_nodes - self.number_of_node_columns, 0, self.number_of_node_columns - 1, ) @cached_property @read_only_array def nodes_at_link(self): return self._layout.nodes_at_link(self.shape) @cached_property def horizontal_links(self): return self._layout.horizontal_links(self.shape) @cached_property def vertical_links(self): return self._layout.vertical_links(self.shape) @property def corner_nodes(self): n_rows, n_cols = self.shape return np.asarray( (n_rows * n_cols - 1, (n_rows - 1) * n_cols, 0, n_cols - 1), dtype=int ) @cached_property def perimeter_nodes(self): return self._layout.perimeter_nodes(self.shape) @cached_property def links_at_node(self): return self._layout.links_at_node(self.shape) @cached_property def link_dirs_at_node(self): return self._layout.link_dirs_at_node(self.shape) @cached_property @read_only_array def patches_at_link(self): return self._layout.patches_at_link(self.shape) @cached_property @read_only_array def patches_at_node(self): return self._layout.patches_at_node(self.shape)
[docs] class StructuredQuadGraphExtras(StructuredQuadGraphTopology, Graph): def __init__(self, node_y_and_x, sort=False): StructuredQuadGraphTopology.__init__(self, node_y_and_x[0].shape) Graph.__init__( self, node_y_and_x, links=StructuredQuadLayoutCython.nodes_at_link(self.shape), patches=StructuredQuadLayoutCython.links_at_patch(self.shape), sort=sort, ) @property def nodes_at_link(self): return self.ds["nodes_at_link"].values @cached_property def orientation_of_link(self): """Return array of link orientation codes (one value per link). Orientation codes are defined by :class:`~.LinkOrientation`; 1 = E, 2 = ENE, 4 = NNE, 8 = N, 16 = NNW, 32 = ESE (using powers of 2 allows for future applications that might want additive combinations). """ orientation_of_link = np.full( self.number_of_links, LinkOrientation.E, dtype=np.uint8 ) orientation_of_link[self.vertical_links] = LinkOrientation.N return orientation_of_link @cached_property def parallel_links_at_link(self): """Return similarly oriented links connected to each link. Return IDs of links of the same orientation that are connected to each given link's tail or head node. The data structure is a *numpy* array of shape ``(n_links, 2)`` containing the IDs of the "tail-wise" (connected to tail node) and "head-wise" (connected to head node) links, or -1 if the link is inactive (e.g., on the perimeter) or it has no attached parallel neighbor in the given direction. For instance, consider a 3x4 raster, in which link IDs are as shown:: .-14-.-15-.-16-. | | | | 10 11 12 13 | | | | .--7-.--8-.--9-. | | | | 3 4 5 6 | | | | .--0-.--1-.--2-. Here's a mapping of the tail-wise (shown at left or bottom of links) and head-wise (shown at right or top of links) links:: .----.----.----. | | | | | | | | | 4 5 | .---8.7--9.8---. | 11 12 | | | | | | | | | .----.----.----. So the corresponding data structure would be mostly filled with -1, but for the 7 active links, it would look like:: 4: [[-1, 11], 5: [-1, 12], 7: [-1, 8], 8: [ 7, 9], 9: [ 8, -1], 11: [ 4, -1], 12: [ 5, -1]] Examples -------- >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((3, 4)) >>> pll = grid.parallel_links_at_link >>> pll[4:13, :] array([[-1, 11], [-1, 12], [-1, 13], [-1, 8], [ 7, 9], [ 8, -1], [ 3, -1], [ 4, -1], [ 5, -1]]) """ plinks_at_link = np.full((self.number_of_links, 2), -1, dtype=int) plinks_at_link[self.vertical_links, 0] = self.links_at_node[ self.node_at_link_tail[self.vertical_links], 3 ] plinks_at_link[self.vertical_links, 1] = self.links_at_node[ self.node_at_link_head[self.vertical_links], 1 ] plinks_at_link[self.horizontal_links, 0] = self.links_at_node[ self.node_at_link_tail[self.horizontal_links], 2 ] plinks_at_link[self.horizontal_links, 1] = self.links_at_node[ self.node_at_link_head[self.horizontal_links], 0 ] return plinks_at_link
[docs] class StructuredQuadGraph(StructuredQuadGraphExtras): def __init__(self, coords, shape=None, sort=False): node_y, node_x = ( np.asarray(coords[0], dtype=float), np.asarray(coords[1], dtype=float), ) if shape: node_y.shape = shape node_x.shape = shape else: node_x.shape = node_y.shape if node_y.shape != node_x.shape: raise ValueError("shape mismatch in node x and y coordinates") StructuredQuadGraphExtras.__init__(self, (node_y, node_x), sort=sort)
[docs] @staticmethod def setup_node_y_and_x(yx_at_node, shape=None): node_y = np.asarray(yx_at_node[0], dtype=float) node_x = np.asarray(yx_at_node[1], dtype=float) if shape: node_y.shape = shape node_x.shape = shape else: node_x.shape = node_y.shape if node_y.shape != node_x.shape: raise ValueError("shape mismatch in node x and y coordinates") return (node_y, node_x)
[docs] class RectilinearGraph(StructuredQuadGraphExtras): """Graph of a rectlinear grid of nodes. Examples -------- >>> from landlab.graph import RectilinearGraph >>> graph = RectilinearGraph(([0, 1, 2, 3], [1, 4, 8])) >>> graph.number_of_nodes 12 >>> graph.y_of_node.reshape(graph.shape) array([[ 0., 0., 0.], [ 1., 1., 1.], [ 2., 2., 2.], [ 3., 3., 3.]]) >>> graph.x_of_node.reshape(graph.shape) array([[ 1., 4., 8.], [ 1., 4., 8.], [ 1., 4., 8.], [ 1., 4., 8.]]) """ def __init__(self, nodes, sort=False): rows = np.asarray(nodes[0], dtype=float) cols = np.asarray(nodes[1], dtype=float) node_y_and_x = np.meshgrid(rows, cols, indexing="ij") StructuredQuadGraphExtras.__init__(self, node_y_and_x, sort=sort)
[docs] @staticmethod def setup_node_y_and_x(coords): rows = np.asarray(coords[0], dtype=float) cols = np.asarray(coords[1], dtype=float) return np.meshgrid(rows, cols, indexing="ij")
[docs] class UniformRectilinearGraph(StructuredQuadGraphExtras): """Graph of a structured grid of quadrilaterals. Examples -------- >>> from landlab.graph import UniformRectilinearGraph >>> graph = UniformRectilinearGraph((4, 3), spacing=(1, 2), origin=(-1, 0)) >>> graph.number_of_nodes 12 >>> graph.y_of_node.reshape(graph.shape) array([[-1., -1., -1.], [ 0., 0., 0.], [ 1., 1., 1.], [ 2., 2., 2.]]) >>> graph.x_of_node.reshape(graph.shape) array([[ 0., 2., 4.], [ 0., 2., 4.], [ 0., 2., 4.], [ 0., 2., 4.]]) >>> graph.links_at_node array([[ 0, 2, -1, -1], [ 1, 3, 0, -1], [-1, 4, 1, -1], [ 5, 7, -1, 2], [ 6, 8, 5, 3], [-1, 9, 6, 4], [10, 12, -1, 7], [11, 13, 10, 8], [-1, 14, 11, 9], [15, -1, -1, 12], [16, -1, 15, 13], [-1, -1, 16, 14]]) >>> graph.link_dirs_at_node array([[-1, -1, 0, 0], [-1, -1, 1, 0], [ 0, -1, 1, 0], [-1, -1, 0, 1], [-1, -1, 1, 1], [ 0, -1, 1, 1], [-1, -1, 0, 1], [-1, -1, 1, 1], [ 0, -1, 1, 1], [-1, 0, 0, 1], [-1, 0, 1, 1], [ 0, 0, 1, 1]], dtype=int8) >>> graph.nodes_at_link array([[ 0, 1], [ 1, 2], [ 0, 3], [ 1, 4], [ 2, 5], [ 3, 4], [ 4, 5], [ 3, 6], [ 4, 7], [ 5, 8], [ 6, 7], [ 7, 8], [ 6, 9], [ 7, 10], [ 8, 11], [ 9, 10], [10, 11]]) >>> graph.links_at_patch array([[ 3, 5, 2, 0], [ 4, 6, 3, 1], [ 8, 10, 7, 5], [ 9, 11, 8, 6], [13, 15, 12, 10], [14, 16, 13, 11]]) >>> graph.nodes_at_patch array([[ 4, 3, 0, 1], [ 5, 4, 1, 2], [ 7, 6, 3, 4], [ 8, 7, 4, 5], [10, 9, 6, 7], [11, 10, 7, 8]]) """ def __init__(self, shape, spacing=1.0, origin=0.0, sort=False): spacing = np.broadcast_to(spacing, 2) origin = np.broadcast_to(origin, 2) rows = np.arange(shape[0], dtype=float) * spacing[0] + origin[0] cols = np.arange(shape[1], dtype=float) * spacing[1] + origin[1] node_y_and_x = np.meshgrid(rows, cols, indexing="ij") StructuredQuadGraphExtras.__init__(self, node_y_and_x, sort=sort) self._spacing = tuple(spacing) self._origin = tuple(origin) @property def spacing(self): return self._spacing @property def origin(self): return self._origin @property def dx(self): return self._spacing[1] @property def dy(self): return self._spacing[0]