from functools import cached_property
import numpy as np
from ...core.utils import as_id_array
from ...utils.decorators import read_only_array
from ..voronoi.voronoi import DelaunayGraph
[docs]
class RadialGraphLayout:
[docs]
@staticmethod
def number_of_nodes(shape):
return np.sum(np.arange(1, shape[0] + 1)) * shape[1] + 1
[docs]
@staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_center=(0.0, 0.0)):
"""Create the node layout for a radial grid.
Examples
--------
>>> import numpy as np
>>> from landlab.graph.radial.radial import RadialGraphLayout
>>> x, y = RadialGraphLayout.xy_of_node((1, 6))
>>> x
array([ 0. , 1. , 0.5, -0.5, -1. , -0.5, 0.5])
>>> np.round(y / np.sin(np.pi / 3.0))
array([ 0., 0., 1., 1., 0., -1., -1.])
"""
n_rings, n_points = shape
n_nodes = RadialGraphLayout.number_of_nodes(shape)
x = np.empty((n_nodes,), dtype=float)
y = np.empty((n_nodes,), dtype=float)
x[0] = y[0] = 0.0
offset = 1
for ring in range(1, n_rings + 1):
rho = spacing * ring
d_theta = np.pi * 2 / (ring * shape[1])
theta = np.arange(ring * shape[1]) * d_theta
y[offset : offset + len(theta)] = rho * np.sin(theta)
x[offset : offset + len(theta)] = rho * np.cos(theta)
offset += len(theta)
x = np.round(x, decimals=6)
y = np.round(y, decimals=6)
x += xy_of_center[0]
y += xy_of_center[1]
return (x, y)
[docs]
class RadialGraph(RadialGraphExtras, DelaunayGraph):
"""Graph of a series of points on concentric circles.
Examples
--------
>>> import numpy as np
>>> from landlab.graph import RadialGraph
>>> graph = RadialGraph((1, 4), sort=True)
>>> graph.number_of_nodes
5
>>> graph.y_of_node
array([-1., 0., 0., 0., 1.])
>>> graph.x_of_node
array([ 0., -1., 0., 1., 0.])
"""
[docs]
def __init__(self, shape, spacing=1.0, xy_of_center=(0.0, 0.0), sort=False):
"""Create a structured grid of triangles arranged radially.
Parameters
----------
shape : tuple of int
Shape of the graph as number of rings and number of points
in the first ring.
spacing : float, optional
Spacing between rings.
xy_of_center : tuple of float, optional
Coordinates of the node at the center of the grid.
"""
try:
spacing = float(spacing)
except TypeError as exc:
raise TypeError("spacing must be a float") from exc
xy_of_center = tuple(np.broadcast_to(xy_of_center, 2))
x_of_node, y_of_node = RadialGraphLayout.xy_of_node(
shape, spacing=spacing, xy_of_center=xy_of_center
)
self._ring_spacing = spacing
self._shape = tuple(shape)
self._xy_of_center = xy_of_center
DelaunayGraph.__init__(self, (y_of_node, x_of_node))
if sort:
self.sort()
@property
def xy_of_center(self):
return self._xy_of_center
@property
def number_of_rings(self):
"""Number of node rings in grid.
Returns
-------
int
The number of node rings in the radial grid (not counting the
center node).
Examples
--------
>>> import numpy as np
>>> from landlab.graph import RadialGraph
>>> graph = RadialGraph((1, 4))
>>> graph.number_of_rings
1
:meta landlab: info-grid
"""
return self._shape[0]
@property
def spacing_of_rings(self):
"""Fixed distance between rings.
Returns
-------
ndarray of float
The distance from the center node of each node.
>>> from landlab.graph import RadialGraph
>>> graph = RadialGraph((2, 6), spacing=2.0)
>>> graph.spacing_of_rings
2.0
:meta landlab: info-grid, quantity
"""
return self._ring_spacing
@cached_property
def radius_at_node(self):
"""Distance for center node to each node.
Returns
-------
ndarray of float
The distance from the center node of each node.
>>> from landlab.graph import RadialGraph
>>> graph = RadialGraph((2, 6), sort=True)
>>> np.round(graph.radius_at_node, 3)
array([2., 2., 2., 2., 2., 1., 1., 2., 1., 0., 1., 2., 1.,
1., 2., 2., 2., 2., 2.])
:meta landlab: info-node, quantity
"""
return np.sqrt(
np.square(self.x_of_node - self._xy_of_center[0])
+ np.square(self.y_of_node - self._xy_of_center[1])
)
@cached_property
def number_of_nodes_in_ring(self):
"""Number of nodes in each ring.
Returns
-------
ndarray of int
Number of nodes in each ring, excluding the center node.
>>> from landlab.graph import RadialGraph
>>> graph = RadialGraph((4, 6))
>>> graph.number_of_nodes_in_ring
array([ 6, 12, 24, 48])
:meta landlab: info-node, quantity
"""
return as_id_array(self._shape[1] * 2 ** np.arange(self.number_of_rings))