Source code for landlab.graph.framed_voronoi.framed_voronoi

""" Implementation of the FramedVoronoiGraph and its static layout:
HorizontalRectVoronoiGraph. This pattern is inspired from the developments of the HexModelGrid

.. codeauthor:: sebastien lenard
"""

from functools import cached_property

import numpy as np

from ...utils.decorators import make_return_array_immutable
from ..graph import Graph
from ..voronoi.voronoi import DelaunayGraph

[docs]class HorizontalRectVoronoiGraph: """The horizontal rectangular frame for the FramedVoronoiGraph."""
[docs] @staticmethod def number_of_nodes(shape): """ Parameters ---------- shape: tuple of int Number of rows and number of columns Returns ------- int Number of nodes Examples -------- >>> from landlab.graph.framed_voronoi.framed_voronoi import HorizontalRectVoronoiGraph >>> HorizontalRectVoronoiGraph.number_of_nodes((3, 2)) 6 """ n_rows, n_cols = shape return n_rows * n_cols
[docs] @staticmethod def xy_of_node( shape, xy_spacing=(1.0, 1.0), xy_of_lower_left=(0.0, 0.0), xy_min_spacing=(0.5, 0.5), seed=200, ): """The x and y coordinates of the graph's nodes. Calculation of the x-y coordinates is done following these steps: 1. Generate a rectangular, regular meshgrid. 2. Move the coordinates of the core nodes over a random distance around their initial position, within a threshold calculated from *xy_spacing* and *xy_min_spacing*. 3. Rectify the y-coordinates of the nodes of the left and right to ensure that the leftmost node of a row has a lower y than the rightmost node. This ensures that the ids of these nodes are not modified by subsequent sorting operations on the graph and make it possible to get the perimeter nodes in simple way. Parameters ---------- shape : tuple of int Number of rows and columns of nodes. xy_spacing : float or tuple of float, optional Node spacing along x and y coordinates. If ``float``, same spacing at *x* and *y*. xy_of_lower_left : tuple, optional Minimum *x*-of-node and *y*-of-node values. Depending on the grid, a node may not be present at this location. xy_min_spacing: float or tuple of float, optional Final minimal spacing between nodes. Random moves of the core nodes around their initial positions cannot be above this threshold: ``(xy_spacing - xy_min_spacing) / 2``. If ``float``, same minimal spacing for *x* and *y*. seed: int, optional Seed used to generate the random *x* and *y* moves. When set, controls a pseudo-randomness of moves to ensure reproducibility. When ``None``, seed is random and the moves of coordinates are completely random. Returns ------- x_of_node, y_of_node : ndarray of float The arrays of *x* and *y* coordinates. Examples -------- >>> from landlab.graph.framed_voronoi.framed_voronoi import HorizontalRectVoronoiGraph >>> x_of_node, y_of_node = HorizontalRectVoronoiGraph.xy_of_node((3, 3), seed=200) Coordinates of the lower left node, >>> x_of_node[0], y_of_node[0] (0.0, 0.0) *x* coordinates of the left and right edges, >>> x_of_node[3], x_of_node[5] (0.0, 2.0) *y* coordinate of the middle row of the left edge, >>> y_of_node[3] 0.749 """ n_rows, n_cols = shape max_move = ( (xy_spacing[0] - xy_min_spacing[0]) / 2, (xy_spacing[1] - xy_min_spacing[1]) / 2, ) if max_move[0] < 0.0 or max_move[1] < 0.0: raise ValueError("minimum spacing must be greater than node spacing") if np.allclose(max_move, 0.0): raise ValueError("at least one of x and y moves must be greater than zero") # Generation of a rectangular grid, coordinates must be float x_of_node, y_of_node = np.meshgrid( np.arange(n_cols, dtype=float) * xy_spacing[0] + xy_of_lower_left[0], np.arange(n_rows, dtype=float) * xy_spacing[1] + xy_of_lower_left[1], ) # Randomly move the coordinates of the core nodes of the grid. Move # below +/- (spacing - min_spacing) / 2 xy_random_generator = np.random.default_rng(seed=seed) x_moves = xy_random_generator.uniform(-max_move[0], max_move[0], shape) y_moves = xy_random_generator.uniform(-max_move[1], max_move[1], shape) x_of_node[1:-1, 1:-1] += x_moves[1:-1, 1:-1] y_of_node[1:-1, 1:-1] += y_moves[1:-1, 1:-1] # Control the node id attribution for left and right edge. For instance, for a 3x3 grid, # make sure that node 3 is at the left of the 2nd row and node 5 at the right. # For this, for each core row, set y of the leftmost node as the minimal y of the row # and set y of the rightmost node as the maximal y of the row for i in range(1, n_rows - 1): y_of_node[i, 0] -= max_move[1] + 1.0e-3 y_of_node[i, n_cols - 1] += max_move[1] + 1.0e-3 return x_of_node.reshape(-1), y_of_node.reshape(-1)
[docs] @staticmethod def corner_nodes(shape): """ Parameters ---------- shape: tuple of int Number of rows and number of columns Returns ------- ndarray of int Ids of the corner nodes Examples -------- >>> from landlab.graph.framed_voronoi.framed_voronoi import HorizontalRectVoronoiGraph >>> HorizontalRectVoronoiGraph.corner_nodes((3, 4)) (11, 8, 0, 3) """ n_rows, n_cols = shape return (n_rows * n_cols - 1, n_cols * (n_rows - 1), 0, n_cols - 1)
[docs] @staticmethod def number_of_perimeter_nodes(shape): """ Parameters ---------- shape: tuple of int Number of rows and number of columns Returns ------- int Number of perimeter nodes Examples -------- >>> from landlab.graph.framed_voronoi.framed_voronoi import HorizontalRectVoronoiGraph >>> HorizontalRectVoronoiGraph.number_of_perimeter_nodes((3, 4)) 10 """ if 1 in shape: return np.prod(shape) return 2 * shape[0] + 2 * (shape[1] - 2)
[docs] @staticmethod def perimeter_nodes(shape): """ Parameters ---------- shape: tuple of int Number of rows and number of columns Returns ------- ndarray of int Ids of the perimeter nodes Examples -------- >>> from landlab.graph.framed_voronoi.framed_voronoi import HorizontalRectVoronoiGraph >>> HorizontalRectVoronoiGraph.perimeter_nodes((3, 3)) array([2, 5, 8, 7, 6, 3, 0, 1]) """ return np.concatenate(HorizontalRectVoronoiGraph.nodes_at_edge(shape))
[docs] @staticmethod def nodes_at_edge(shape): """ Parameters ---------- shape: tuple of int Number of rows and number of columns Returns ------- right, top, left, bottom : ndarray of int For each edge give the ids of the nodes present at the edge Examples -------- >>> from landlab.graph.framed_voronoi.framed_voronoi import HorizontalRectVoronoiGraph >>> HorizontalRectVoronoiGraph.nodes_at_edge((3, 3)) (array([2, 5]), array([8, 7]), array([6, 3]), array([0, 1])) """ n_rows, n_cols = shape if n_rows == n_cols == 1: return (np.array([0]),) + (np.array([], dtype=int),) * 3 ( northeast, northwest, southwest, southeast, ) = HorizontalRectVoronoiGraph.corner_nodes(shape) if n_rows > 1: south = np.arange(southwest, southeast) else: south = np.array([southwest], dtype=int) if n_cols > 1: west = np.arange(northwest, southwest, -n_cols) else: west = np.array([northwest], dtype=int) return ( np.arange(southeast, northeast, n_cols), np.arange(northeast, northwest, -1), west, south, )
[docs]class FramedVoronoiGraph(DelaunayGraph): """VoronoiDelaunay graph based on a fixed lattice. Graph of an unstructured grid of Voronoi Delaunay cells and irregular patches. It is a special type of :class`~.VoronoiDelaunayGraph` in which the initial set of points is arranged in a fixed lattice (e.g. like a :class:`~.RasterModelGrid`) named here "layout" and the core points are then moved from their initial position by a random distance, lower than a certain threshold. Examples -------- >>> from landlab.graph import FramedVoronoiGraph >>> graph = FramedVoronoiGraph((3, 3), seed=200) >>> graph.number_of_nodes 9 >>> graph.x_of_node[2:4] # doctest: +NORMALIZE_WHITESPACE array([ 2., 0.]) >>> graph.y_of_node[2:4] # doctest: +NORMALIZE_WHITESPACE array([ 0. , 0.749]) >>> graph.y_of_node[5] # doctest: +NORMALIZE_WHITESPACE 1.2509999999999999 >>> graph.number_of_links 16 >>> graph.number_of_patches 8 """