Source code for landlab.grid.diagonals

#! /usr/bin/env python
import contextlib

import numpy as np

from ..utils.decorators import cache_result_in_object
from ..utils.decorators import make_return_array_immutable
from .decorators import return_readonly_id_array
from .linkstatus import LinkStatus
from .linkstatus import set_status_at_link


[docs] def create_nodes_at_diagonal(shape, out=None): """Create array of tail and head nodes for diagonals. Parameters ---------- shape : tuple of *(n_rows, n_cols)* Shape as number of node rows and node columns. out : ndarray of shape *(n_diagonals, 2)*, optional Output buffer to place nodes at each diagonal. Returns ------- out : ndarray of shape *(n_diagonals, 2)* Tail and head node for each diagonal. Examples -------- >>> from landlab.grid.diagonals import create_nodes_at_diagonal >>> create_nodes_at_diagonal((3, 4)) array([[ 0, 5], [ 1, 4], [ 1, 6], [ 2, 5], [ 2, 7], [ 3, 6], [ 4, 9], [ 5, 8], [ 5, 10], [ 6, 9], [ 6, 11], [ 7, 10]]) """ shape = np.asarray(shape) n_diagonals = np.prod(shape - 1) * 2 n_nodes = np.prod(shape) if out is None: out = np.empty((n_diagonals, 2), dtype=int) nodes = np.arange(n_nodes).reshape(shape) out[::2, 0] = nodes[:-1, :-1].flat out[::2, 1] = nodes[1:, 1:].flat out[1::2, 0] = nodes[:-1, 1:].flat out[1::2, 1] = nodes[1:, :-1].flat return out
[docs] def create_diagonals_at_node(shape, out=None): """Create array of diagonals at node. Create an array that gives the diagonal touching each node for a structured grid of quadrilaterals. For each node, links are ordered clockwise from axis=1. Parameters ---------- shape : tuple of *(n_rows, n_cols)* Shape as number of node rows and node columns. out : ndarray of shape *(n_nodes, 4)*, optional Output buffer to place diagonal ids at each node. Returns ------- out : ndarray of shape *(n_nodes, 4)* Diagonals at node with -1 for missing diagonals. Examples -------- >>> from landlab.grid.diagonals import create_diagonals_at_node >>> create_diagonals_at_node((3, 4)) array([[ 0, -1, -1, -1], [ 2, 1, -1, -1], [ 4, 3, -1, -1], [-1, 5, -1, -1], [ 6, -1, -1, 1], [ 8, 7, 0, 3], [10, 9, 2, 5], [-1, 11, 4, -1], [-1, -1, -1, 7], [-1, -1, 6, 9], [-1, -1, 8, 11], [-1, -1, 10, -1]]) """ shape = np.asarray(shape) n_diagonals = np.prod(shape - 1) * 2 n_nodes = np.prod(shape) if out is None: out = np.full((n_nodes, 4), -1, dtype=int) diagonals = np.full(shape + 1, -1, dtype=int) diagonals[1:-1, 1:-1] = np.arange(0, n_diagonals, 2).reshape(shape - 1) out[:, 0] = diagonals[1:, 1:].flat out[:, 2] = diagonals[:-1, :-1].flat diagonals[1:-1, 1:-1] = np.arange(1, n_diagonals, 2).reshape(shape - 1) out[:, 1] = diagonals[1:, :-1].flat out[:, 3] = diagonals[:-1, 1:].flat return out
[docs] class DiagonalsMixIn: """Add diagonals to a structured quad grid.""" @property @cache_result_in_object() def number_of_diagonals(self): """Number of diagonals in the grid. Examples -------- >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((4, 3)) >>> grid.number_of_diagonals 12 """ return 2 * np.prod(np.asarray(self.shape) - 1) @property @cache_result_in_object() @make_return_array_immutable def diagonals_at_node(self): """Diagonals attached to nodes. Returns ------- ndarray of int, shape `(n_nodes, 4)` Diagonals at each node. Examples -------- >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((4, 3)) >>> grid.diagonals_at_node.shape == (grid.number_of_nodes, 4) True >>> grid.diagonals_at_node array([[ 0, -1, -1, -1], [ 2, 1, -1, -1], [-1, 3, -1, -1], [ 4, -1, -1, 1], [ 6, 5, 0, 3], [-1, 7, 2, -1], [ 8, -1, -1, 5], [10, 9, 4, 7], [-1, 11, 6, -1], [-1, -1, -1, 9], [-1, -1, 8, 11], [-1, -1, 10, -1]]) :meta landlab: info-node, info-link, connectivity """ return create_diagonals_at_node(self.shape) @property @cache_result_in_object() @make_return_array_immutable def diagonal_dirs_at_node(self): """Directions of diagonals attached to nodes. Array of diagonal directions relative to each node. If the diagonal is directed away from the node the direction is -1, if the diagonal is directed toward the node its direction is 1. Each node is assumed to have exactly four diagonals attached to it. However, perimeter nodes will have fewer diagonals (corner nodes will only have one diagonal and edge nodes two). In such cases, placeholders of 0 are used. Examples -------- >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((4, 3)) >>> grid.diagonal_dirs_at_node.shape == (grid.number_of_nodes, 4) True >>> grid.diagonal_dirs_at_node array([[-1, 0, 0, 0], [-1, -1, 0, 0], [ 0, -1, 0, 0], [-1, 0, 0, 1], [-1, -1, 1, 1], [ 0, -1, 1, 0], [-1, 0, 0, 1], [-1, -1, 1, 1], [ 0, -1, 1, 0], [ 0, 0, 0, 1], [ 0, 0, 1, 1], [ 0, 0, 1, 0]], dtype=int8) The lower-right corner node only has one diagonal. >>> grid.diagonal_dirs_at_node[2] array([ 0, -1, 0, 0], dtype=int8) A node on one of the edges has two diagonals. >>> grid.diagonal_dirs_at_node[3] array([-1, 0, 0, 1], dtype=int8) """ diagonals_at_node = self.diagonals_at_node dirs_at_node = np.zeros_like(diagonals_at_node, dtype=np.int8) dirs_at_node[diagonals_at_node >= 0] = 1 dirs_at_node[:, (0, 1)] *= -1 return dirs_at_node @property @cache_result_in_object() @make_return_array_immutable def diagonal_adjacent_nodes_at_node(self): """Get adjacent nodes along diagonals. Order is the landlab standard, counterclockwise starting from east. Examples -------- >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((4, 3)) >>> grid.diagonal_adjacent_nodes_at_node array([[ 4, -1, -1, -1], [ 5, 3, -1, -1], [-1, 4, -1, -1], [ 7, -1, -1, 1], [ 8, 6, 0, 2], [-1, 7, 1, -1], [10, -1, -1, 4], [11, 9, 3, 5], [-1, 10, 4, -1], [-1, -1, -1, 7], [-1, -1, 6, 8], [-1, -1, 7, -1]]) :meta landlab: info-node, connectivity """ node_is_at_tail = np.choose( self.diagonal_dirs_at_node + 1, np.array((1, -1, 0), dtype=np.int8) ) out = self.nodes_at_diagonal[self.diagonals_at_node, node_is_at_tail] out[node_is_at_tail == -1] = -1 return out @property @cache_result_in_object() @make_return_array_immutable def d8_adjacent_nodes_at_node(self): return np.vstack( ( super().adjacent_nodes_at_node, self.diagonal_adjacent_nodes_at_node, ) ) @property @cache_result_in_object() @make_return_array_immutable def nodes_at_diagonal(self): """Nodes at diagonal tail and head. Examples -------- >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((4, 3)) >>> grid.nodes_at_diagonal.shape == (grid.number_of_diagonals, 2) True >>> grid.nodes_at_diagonal array([[ 0, 4], [ 1, 3], [ 1, 5], [ 2, 4], [ 3, 7], [ 4, 6], [ 4, 8], [ 5, 7], [ 6, 10], [ 7, 9], [ 7, 11], [ 8, 10]]) The first column is diagonal tail and the second the head. >>> grid.diagonals_at_node[3] array([ 4, -1, -1, 1]) >>> grid.nodes_at_diagonal[(4, 1),] array([[3, 7], [1, 3]]) >>> grid.diagonal_dirs_at_node[3] array([-1, 0, 0, 1], dtype=int8) """ return create_nodes_at_diagonal(self.shape) @property @cache_result_in_object() def number_of_d8(self): """Number of links and diagonals. Examples -------- >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((4, 3)) >>> grid.number_of_d8 29 """ return super().number_of_links + self.number_of_diagonals @property @cache_result_in_object() @make_return_array_immutable def nodes_at_d8(self): return np.vstack((self.nodes_at_link, self.nodes_at_diagonal)) @property @cache_result_in_object() @make_return_array_immutable def d8s_at_node(self): """Links and diagonals attached to nodes. Returns ------- ndarray of int, shape `(n_nodes, 8)` Links and diagonals at each node. Examples -------- >>> import numpy as np >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((3, 4)) >>> grid.d8s_at_node.shape == (grid.number_of_nodes, 8) True >>> grid.d8s_at_node array([[ 0, 3, -1, -1, 17, -1, -1, -1], [ 1, 4, 0, -1, 19, 18, -1, -1], [ 2, 5, 1, -1, 21, 20, -1, -1], [-1, 6, 2, -1, -1, 22, -1, -1], [ 7, 10, -1, 3, 23, -1, -1, 18], [ 8, 11, 7, 4, 25, 24, 17, 20], [ 9, 12, 8, 5, 27, 26, 19, 22], [-1, 13, 9, 6, -1, 28, 21, -1], [14, -1, -1, 10, -1, -1, -1, 24], [15, -1, 14, 11, -1, -1, 23, 26], [16, -1, 15, 12, -1, -1, 25, 28], [-1, -1, 16, 13, -1, -1, 27, -1]]) >>> np.all(grid.d8s_at_node[:, :4] == grid.links_at_node) True >>> diagonals_at_node = grid.d8s_at_node[:, 4:] - grid.number_of_links >>> diagonals_at_node[grid.d8s_at_node[:, 4:] == -1] = -1 >>> np.all(diagonals_at_node == grid.diagonals_at_node) True :meta landlab: info-node, info-link, connectivity """ diagonals_at_node = self.diagonals_at_node.copy() diagonals_at_node[diagonals_at_node >= 0] += self.number_of_links return np.hstack((super().links_at_node, diagonals_at_node)) @property @cache_result_in_object() @make_return_array_immutable def d8_dirs_at_node(self): return np.hstack((super().link_dirs_at_node, self.diagonal_dirs_at_node)) @property # @cache_result_in_object() @make_return_array_immutable def d8_status_at_node(self): return self.status_at_d8[self.d8s_at_node] @property @cache_result_in_object() @make_return_array_immutable def length_of_diagonal(self): return np.sqrt( np.power(np.diff(self.xy_of_node[self.nodes_at_diagonal], axis=1), 2.0).sum( axis=2 ) ).flatten() @property @cache_result_in_object() @make_return_array_immutable def length_of_d8(self): """Length of links and diagonals. Return the lengths links and diagonals in the grid. Links are listed first and then diagonals. Returns ------- ndarray of float Link lengths. Examples -------- >>> from landlab import RasterModelGrid >>> grid = RasterModelGrid((3, 3), xy_spacing=(4, 3)) >>> grid.length_of_link array([ 4., 4., 3., 3., 3., 4., 4., 3., 3., 3., 4., 4.]) >>> grid.length_of_d8 array([ 4., 4., 3., 3., 3., 4., 4., 3., 3., 3., 4., 4., 5., 5., 5., 5., 5., 5., 5., 5.]) :meta landlab: info-link, quantity """ return np.hstack((super().length_of_link, self.length_of_diagonal))
[docs] def reset_status_at_node(self): super().reset_status_at_node() attrs = [ "_status_at_diagonal", "_diagonal_status_at_node", "_active_diagonals", "_active_diagonal_dirs_at_node", "_status_at_d8", "_active_d8", "_active_d8_dirs_at_node", ] for attr in attrs: with contextlib.suppress(KeyError): del self.__dict__[attr]
@property @cache_result_in_object() @make_return_array_immutable def status_at_diagonal(self): """Status at diagonals. Examples -------- >>> from landlab import LinkStatus, NodeStatus, RasterModelGrid >>> grid = RasterModelGrid((4, 3)) An inactive link (or diagonal) is one that joins two boundary nodes or has one end that is a closed boundary. >>> grid.status_at_node array([1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], dtype=uint8) >>> NodeStatus.CORE, NodeStatus.FIXED_VALUE (<NodeStatus.CORE: 0>, <NodeStatus.FIXED_VALUE: 1>) Diagonals that connect two boundary nodes are inactive. >>> grid.status_at_diagonal array([0, 4, 4, 0, 0, 0, 0, 0, 4, 0, 0, 4], dtype=uint8) >>> LinkStatus.ACTIVE, LinkStatus.INACTIVE (<LinkStatus.ACTIVE: 0>, <LinkStatus.INACTIVE: 4>) By setting a node to closed that wasn't before, a new link becomes inactive. >>> grid.status_at_node[5] = NodeStatus.CLOSED >>> grid.status_at_diagonal array([0, 4, 4, 0, 0, 0, 0, 4, 4, 0, 0, 4], dtype=uint8) """ return set_status_at_link(self.status_at_node[self.nodes_at_diagonal]) @property @cache_result_in_object() @make_return_array_immutable def diagonal_status_at_node(self): return self.status_at_diagonal[self.diagonals_at_node] @property @cache_result_in_object() @return_readonly_id_array def active_diagonals(self): return np.where(self.status_at_diagonal == LinkStatus.ACTIVE)[0] @property @cache_result_in_object() @make_return_array_immutable def active_diagonal_dirs_at_node(self): return np.choose( self.diagonal_status_at_node == LinkStatus.ACTIVE, (0, self.diagonal_dirs_at_node), ) @property @cache_result_in_object() @make_return_array_immutable def status_at_d8(self): return np.hstack((super().status_at_link, self.status_at_diagonal)) @property @cache_result_in_object() @return_readonly_id_array def active_d8(self): return np.where(self.status_at_d8 == LinkStatus.ACTIVE)[0] @property @cache_result_in_object() @make_return_array_immutable def active_d8_dirs_at_node(self): return np.choose( self.d8_status_at_node == LinkStatus.ACTIVE, (0, self.d8_dirs_at_node) )