Source code for landlab.graph.hex.hex

```r"""
Examples
--------

::

* - *
/ \ / \
* - * - *
/ \ / \ / \
* - * - * - *
\ / \ / \ /
* - * - *
\ / \ /
* - *

>>> from landlab.graph import TriGraph
>>> graph = TriGraph((5, 2), node_layout="hex", sort=True)
>>> graph.number_of_nodes
14
>>> graph.x_of_node
array([ 1. ,  2. ,
0.5,  1.5,  2.5,
0. ,  1. ,  2. ,  3. ,
0.5,  1.5, 2.5,
1. ,  2. ])
29
>>> graph.number_of_patches
16

::

* - * - * - *
\ / \ / \ / \
* - * - * - *
/ \ / \ / \ /
* - * - * - *

>>> from landlab.graph import TriGraph
>>> graph = TriGraph((3, 4), orientation="horizontal", node_layout="rect", sort=True)
>>> graph.number_of_nodes
12
>>> graph.x_of_node.reshape((3, 4))
array([[ 0. ,  1. ,  2. ,  3. ],
[ 0.5,  1.5,  2.5,  3.5],
[ 0. ,  1. ,  2. ,  3. ]])
23
>>> graph.number_of_patches
12
"""

from functools import cached_property

import numpy as np

from ...core.utils import as_id_array
from ...utils.decorators import cache_result_in_object, make_return_array_immutable
from ..graph import Graph
from ..voronoi.voronoi import DelaunayGraph

[docs]class HorizontalRectTriGraphCython:
[docs]    @staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_lower_left=(0.0, 0.0)):
from .ext.hex import fill_xy_of_node_rect_horizontal

x_of_node = np.empty(shape[0] * shape[1], dtype=float)
y_of_node = np.empty(shape[0] * shape[1], dtype=float)
fill_xy_of_node_rect_horizontal(shape, x_of_node, y_of_node)

x_of_node[:] *= spacing
y_of_node[:] *= spacing * np.sin(np.pi / 3.0)
x_of_node[:] += xy_of_lower_left[0]
y_of_node[:] += xy_of_lower_left[1]

return x_of_node, y_of_node

[docs]class VerticalRectTriGraphCython:
[docs]    @staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_lower_left=(0.0, 0.0)):
from .ext.hex import fill_xy_of_node_rect_vertical

n_rows, n_cols = shape
x_spacing = np.sin(np.pi / 3.0) * spacing
y_spacing = spacing

x_of_node = np.empty(n_rows * n_cols, dtype=float)
y_of_node = np.empty(n_rows * n_cols, dtype=float)

fill_xy_of_node_rect_vertical(shape, x_of_node, y_of_node)

x_of_node *= x_spacing
y_of_node *= y_spacing
x_of_node += xy_of_lower_left[0]
y_of_node += xy_of_lower_left[1]

return x_of_node, y_of_node

x_of_node[:, (n_cols + 1) // 2 :] = (
np.arange(n_cols // 2) * x_spacing * 2.0 + x_spacing + xy_of_lower_left[0]
)
x_of_node[:, : (n_cols + 1) // 2] = (
np.arange((n_cols + 1) // 2) * x_spacing * 2.0 + xy_of_lower_left[0]
)

y_of_node[:, : (n_cols + 1) // 2] = (
np.arange(n_rows) * y_spacing + xy_of_lower_left[1]
).reshape((n_rows, 1))
y_of_node[:, (n_cols + 1) // 2 :] = (
np.arange(n_rows) * y_spacing + xy_of_lower_left[1] + y_spacing * 0.5
).reshape((n_rows, 1))

return x_of_node.reshape(-1), y_of_node.reshape(-1)

[docs]class HorizontalHexTriGraphCython:
[docs]    @staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_lower_left=(0.0, 0.0)):
from .ext.hex import fill_xy_of_node_hex_horizontal

n_rows, n_cols = shape
n_nodes = n_rows * n_cols + (n_rows // 2) ** 2

x_of_node = np.empty(n_nodes, dtype=float)
y_of_node = np.empty(n_nodes, dtype=float)

fill_xy_of_node_hex_horizontal(shape, x_of_node, y_of_node)

x_of_node[:] *= spacing
y_of_node[:] *= spacing * np.sin(np.pi / 3.0)
x_of_node[:] += xy_of_lower_left[0]
y_of_node[:] += xy_of_lower_left[1]

return x_of_node, y_of_node

[docs]class VerticalHexTriGraphCython:
[docs]    @staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_lower_left=(0.0, 0.0)):
from .ext.hex import fill_xy_of_node_hex_vertical

n_rows, n_cols = shape
n_nodes = n_cols * n_rows + (n_cols // 2) ** 2

x_of_node = np.empty(n_nodes, dtype=float)
y_of_node = np.empty(n_nodes, dtype=float)

fill_xy_of_node_hex_vertical(shape, x_of_node, y_of_node)

x_of_node[:] *= spacing * np.sin(np.pi / 3.0)
y_of_node[:] *= spacing
x_of_node[:] += xy_of_lower_left[0]
y_of_node[:] += xy_of_lower_left[1]

return x_of_node, y_of_node

[docs]class HorizontalRectTriGraph:
[docs]    @staticmethod
def number_of_nodes(shape):
n_rows, n_cols = shape
return n_rows * n_cols

[docs]    @staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_lower_left=(0.0, 0.0)):
n_rows, n_cols = shape
x_spacing, y_spacing = spacing, spacing * np.sin(np.pi / 3.0)

x_of_node, y_of_node = np.meshgrid(
np.arange(n_cols) * x_spacing + xy_of_lower_left[0],
np.arange(n_rows) * y_spacing + xy_of_lower_left[1],
)
x_of_node[1::2] += spacing * 0.5

return x_of_node.reshape(-1), y_of_node.reshape(-1)

[docs]    @staticmethod
def corner_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import HorizontalRectTriGraph
>>> HorizontalRectTriGraph.corner_nodes((3, 4))
(11, 8, 0, 3)
>>> HorizontalRectTriGraph.corner_nodes((3, 2))
(5, 4, 0, 1)
>>> HorizontalRectTriGraph.corner_nodes((7, 1))
(6, 6, 0, 0)
>>> HorizontalRectTriGraph.corner_nodes((1, 3))
(2, 0, 0, 2)
"""
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):
if 1 in shape:
return np.prod(shape)
return 2 * shape[0] + 2 * (shape[1] - 2)

[docs]    @staticmethod
def perimeter_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import HorizontalRectTriGraph
>>> HorizontalRectTriGraph.perimeter_nodes((3, 2))
array([1, 3, 5, 4, 2, 0])
"""
return np.concatenate(HorizontalRectTriGraph.nodes_at_edge(shape))

[docs]    @staticmethod
def nodes_at_edge(shape):
n_rows, n_cols = shape
if n_rows == n_cols == 1:
return (np.array([0]),) + (np.array([], dtype=int),) * 3
(
northeast,
northwest,
southwest,
southeast,
) = HorizontalRectTriGraph.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 VerticalRectTriGraph:
[docs]    @staticmethod
def number_of_nodes(shape):
n_rows, n_cols = shape
return n_rows * n_cols

[docs]    @staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_lower_left=(0.0, 0.0)):
n_rows, n_cols = shape
x_spacing, y_spacing = spacing * np.sin(np.pi / 3.0), spacing

x_of_node = np.empty((n_rows, n_cols), dtype=float)
y_of_node = np.empty((n_rows, n_cols), dtype=float)

x_of_node[:, (n_cols + 1) // 2 :] = (
np.arange(n_cols // 2) * x_spacing * 2.0 + x_spacing + xy_of_lower_left[0]
)
x_of_node[:, : (n_cols + 1) // 2] = (
np.arange((n_cols + 1) // 2) * x_spacing * 2.0 + xy_of_lower_left[0]
)

y_of_node[:, : (n_cols + 1) // 2] = (
np.arange(n_rows) * y_spacing + xy_of_lower_left[1]
).reshape((n_rows, 1))
y_of_node[:, (n_cols + 1) // 2 :] = (
np.arange(n_rows) * y_spacing + xy_of_lower_left[1] + y_spacing * 0.5
).reshape((n_rows, 1))

return x_of_node.reshape(-1), y_of_node.reshape(-1)

[docs]    @staticmethod
def corner_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import VerticalRectTriGraph
>>> VerticalRectTriGraph.corner_nodes((4, 3))
(10, 9, 0, 1)
>>> VerticalRectTriGraph.corner_nodes((4, 4))
(15, 12, 0, 3)
>>> VerticalRectTriGraph.corner_nodes((3, 2))
(5, 4, 0, 1)
>>> VerticalRectTriGraph.corner_nodes((7, 1))
(6, 6, 0, 0)
>>> VerticalRectTriGraph.corner_nodes((1, 3))
(1, 0, 0, 1)
>>> VerticalRectTriGraph.corner_nodes((2, 3))
(4, 3, 0, 1)
"""
n_rows, n_cols = shape
if n_cols % 2 == 0:
return (n_rows * n_cols - 1, n_cols * (n_rows - 1), 0, n_cols - 1)
else:
return (
n_rows * n_cols - 1 - n_cols // 2,
n_cols * (n_rows - 1),
0,
n_cols // 2,
)

[docs]    @staticmethod
def number_of_perimeter_nodes(shape):
if 1 in shape:
return np.prod(shape)
return 2 * shape[1] + 2 * (shape[0] - 2)

[docs]    @staticmethod
def perimeter_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import VerticalRectTriGraph
>>> VerticalRectTriGraph.perimeter_nodes((3, 2))
array([1, 3, 5, 4, 2, 0])
>>> VerticalRectTriGraph.perimeter_nodes((2, 3))
array([1, 4, 5, 3, 0, 2])
>>> VerticalRectTriGraph.perimeter_nodes((2, 4))
array([3, 7, 5, 6, 4, 0, 2, 1])
"""
return np.concatenate(VerticalRectTriGraph.nodes_at_edge(shape))

[docs]    @staticmethod
def nodes_at_edge(shape):
n_rows, n_cols = shape
if n_rows == n_cols == 1:
return (np.array([0]),) + (np.array([], dtype=int),) * 3
n_nodes = n_rows * n_cols
(
northeast,
northwest,
southwest,
southeast,
) = VerticalRectTriGraph.corner_nodes(shape)

if n_cols == 1:
southwest = northwest - n_cols

north = np.empty(n_cols - 1, dtype=int)
north[::2] = n_nodes - n_cols // 2 + np.arange(n_cols // 2)
north[1::2] = northwest + np.arange(1, n_cols - n_cols // 2)

if n_rows > 1:
south = np.empty(n_cols - 1, dtype=int)
south[::2] = np.arange(0, n_cols // 2)
south[1::2] = (n_cols + 1) // 2 + np.arange(n_cols - n_cols // 2 - 1)
else:
south = np.array([southwest], dtype=int)

return (
np.arange(southeast, northeast, n_cols),
north[::-1],
np.arange(northwest, southwest, -n_cols),
south,
)

[docs]class HorizontalHexTriGraph:
[docs]    @staticmethod
def number_of_nodes(shape):
n_rows, n_cols = shape
return n_rows * n_cols + (n_rows // 2) ** 2

[docs]    @staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_lower_left=(0.0, 0.0)):
n_rows, n_cols = shape
x_spacing, y_spacing = spacing, spacing * np.sin(np.pi / 3.0)

length_of_row = np.concatenate(
(
np.arange((n_rows + 2) // 2) + n_cols,
(n_rows + 2) // 2
+ n_cols
- 1
- np.arange(1, n_rows - (n_rows + 2) // 2 + 1),
)
)
offset_to_row = np.concatenate((np.array([0]), length_of_row)).cumsum()
rows = [
slice(start, end)
for start, end in zip(offset_to_row[:-1], offset_to_row[1:])
]

y_of_node = np.empty(HorizontalHexTriGraph.number_of_nodes(shape), dtype=float)
for row, inds in enumerate(rows):
y_of_node[inds] = row * y_spacing + xy_of_lower_left[1]

x_of_node = np.empty(HorizontalHexTriGraph.number_of_nodes(shape), dtype=float)

x_of_row = (
np.abs((n_rows + 2) // 2 - 1 - np.arange(n_rows)) * x_spacing * 0.5
+ xy_of_lower_left[0]
)
for row, inds in enumerate(rows):
x_of_node[inds] = x_of_row[row] + np.arange(length_of_row[row]) * x_spacing

return x_of_node.reshape(-1), y_of_node.reshape(-1)

[docs]    @staticmethod
def corner_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import HorizontalHexTriGraph
>>> HorizontalHexTriGraph.corner_nodes((3, 2))
(4, 6, 5, 2, 0, 1)
>>> HorizontalHexTriGraph.corner_nodes((7, 1))
(9, 15, 15, 6, 0, 0)
>>> HorizontalHexTriGraph.corner_nodes((6, 1))
(9, 14, 13, 6, 0, 0)
>>> HorizontalHexTriGraph.corner_nodes((4, 2))
(8, 11, 9, 5, 0, 1)
"""
n_rows, n_cols = shape

n_nodes_in_middle_row = n_rows // 2 + n_cols
n_nodes = n_rows * n_cols + (n_rows // 2) ** 2

east = (n_nodes_in_middle_row + n_cols) * ((n_rows // 2 + 1) // 2) - 1
if (n_rows // 2) % 2 == 0:
east += (n_nodes_in_middle_row + n_cols) // 2

return (
east,
n_nodes - 1,
n_nodes - (n_cols + (n_rows + 1) % 2),
east - (n_nodes_in_middle_row - 1),
0,
n_cols - 1,
)

[docs]    @staticmethod
def number_of_perimeter_nodes(shape):
if shape[0] == 1:
return shape[1]
return 2 * shape[0] + 2 * (shape[1] - 2) + (shape[0] + 1) % 2

[docs]    @staticmethod
def perimeter_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import HorizontalHexTriGraph
>>> HorizontalHexTriGraph.perimeter_nodes((3, 2))
array([4, 6, 5, 2, 0, 1])
>>> HorizontalHexTriGraph.perimeter_nodes((1, 3))
array([2, 1, 0])
"""
return np.concatenate(HorizontalHexTriGraph.nodes_at_edge(shape))

[docs]    @staticmethod
def nodes_at_edge(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import HorizontalHexTriGraph
>>> HorizontalHexTriGraph.nodes_at_edge((5, 3))
(array([11, 15]), array([18, 17]), array([16, 12]), array([7, 3]),
array([0, 1]), array([2, 6]))
>>> HorizontalHexTriGraph.nodes_at_edge((4, 3))
(array([11]), array([15, 14, 13]), array([12]), array([7, 3]), array([0, 1]),
array([2, 6]))
"""
n_rows, n_cols = shape
(
east,
northeast,
northwest,
west,
southwest,
southeast,
) = HorizontalHexTriGraph.corner_nodes(shape)

if n_rows == 1:
nodes_at_south_edge = np.asarray([southwest], dtype=int)
else:
nodes_at_south_edge = np.arange(southwest, southeast)

return (
as_id_array(
northeast
- np.arange(northeast - northwest + 1, east - west + 1).cumsum()[::-1]
),
np.arange(northeast, northwest, -1),
as_id_array(
west
+ np.arange(east - west + 1, northeast - northwest + 1, -1).cumsum()[
::-1
]
),
as_id_array(
southwest + np.arange(n_cols, n_rows // 2 + n_cols).cumsum()[::-1]
),
nodes_at_south_edge,
as_id_array(
east - np.arange(n_cols + n_rows // 2, n_cols, -1).cumsum()[::-1]
),
)

[docs]class VerticalHexTriGraph:
[docs]    @staticmethod
def number_of_nodes(shape):
n_rows, n_cols = shape
return n_rows * n_cols + (n_cols // 2) ** 2

[docs]    @staticmethod
def xy_of_node(shape, spacing=1.0, xy_of_lower_left=(0.0, 0.0)):
n_rows, n_cols = shape
x_spacing, y_spacing = spacing * np.sin(np.pi / 3.0), spacing

length_of_middle_rows = np.full(2 * n_rows - 1, n_cols // 2)
if n_cols % 2 == 1:
length_of_middle_rows[::2] += 1

length_of_row = np.concatenate(
(
np.arange(1, n_cols // 2 + 1),
length_of_middle_rows,
np.arange(n_cols // 2, 0, -1),
)
)
offset_to_row = np.concatenate((np.array([0]), length_of_row)).cumsum()
rows = [
slice(start, end)
for start, end in zip(offset_to_row[:-1], offset_to_row[1:])
]

y_of_node = np.empty(VerticalHexTriGraph.number_of_nodes(shape), dtype=float)
for row, inds in enumerate(rows):
y_of_node[inds] = row * y_spacing * 0.5

x_of_node = np.empty(VerticalHexTriGraph.number_of_nodes(shape), dtype=float)

x_of_middle_rows = np.zeros(2 * n_rows - 1)
x_of_middle_rows[1::2] += 1.0
x_of_row = (
np.concatenate(
(
np.arange(n_cols // 2, 0, -1),
x_of_middle_rows,
np.arange(1, n_cols // 2 + 1),
)
)
* x_spacing
)
for row, inds in enumerate(rows):
x_of_node[inds] = (
x_of_row[row] + np.arange(length_of_row[row]) * 2.0 * x_spacing
)

x_of_node += xy_of_lower_left[0]
y_of_node += xy_of_lower_left[1]

return x_of_node.reshape(-1), y_of_node.reshape(-1)

[docs]    @staticmethod
def corner_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import VerticalHexTriGraph
>>> VerticalHexTriGraph.corner_nodes((2, 5))
(10, 13, 8, 3, 0, 5)
>>> VerticalHexTriGraph.corner_nodes((2, 3))
(5, 6, 4, 1, 0, 2)
>>> VerticalHexTriGraph.corner_nodes((2, 4))
(10, 11, 7, 3, 0, 2)
>>> VerticalHexTriGraph.corner_nodes((2, 2))
(4, 4, 3, 1, 0, 0)
>>> VerticalHexTriGraph.corner_nodes((3, 1))
(2, 2, 2, 0, 0, 0)
>>> VerticalHexTriGraph.corner_nodes((1, 3))
(2, 3, 1, 1, 0, 2)
"""
n_rows, n_cols = shape
n_nodes = n_rows * n_cols + (n_cols // 2) ** 2

n = n_cols // 2
tri_nodes = (n + 1) * (n // 2)
if n % 2 == 1:
tri_nodes += (n + 1) // 2

if n_cols % 2 == 0:
southeast = tri_nodes - 1
southwest = tri_nodes
northwest = n_nodes - 1 - (tri_nodes - 1 + n_cols // 2)
northeast = n_nodes - 1 - (tri_nodes - n)

else:
southwest = tri_nodes
southeast = tri_nodes + n_cols // 2
northwest = n_nodes - (tri_nodes + (n_cols + 1) // 2)
northeast = n_nodes - 1 - tri_nodes

south = 0
north = n_nodes - 1

return (northeast, north, northwest, southwest, south, southeast)

[docs]    @staticmethod
def number_of_perimeter_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import VerticalHexTriGraph
>>> VerticalHexTriGraph.number_of_perimeter_nodes((2, 3))
6
>>> VerticalHexTriGraph.number_of_perimeter_nodes((2, 2))
5
"""
if shape[1] == 1:
return shape[0]
return 2 * shape[1] + 2 * (shape[0] - 2) + (shape[1] + 1) % 2

[docs]    @staticmethod
def perimeter_nodes(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import VerticalHexTriGraph
>>> VerticalHexTriGraph.perimeter_nodes((3, 7))
array([ 9, 16, 23, 26, 28, 29, 27, 24, 20, 13,  6,  3,  1,  0,  2,  5])
>>> VerticalHexTriGraph.perimeter_nodes((2, 3))
array([2, 5, 6, 4, 1, 0])
>>> VerticalHexTriGraph.perimeter_nodes((2, 4))
array([ 2, 6, 10, 11, 9, 7, 3, 1, 0])
>>> VerticalHexTriGraph.perimeter_nodes((2, 2))
array([0, 2, 4, 3, 1])
>>> VerticalHexTriGraph.perimeter_nodes((3, 1))
array([0, 1, 2])
"""
return np.concatenate(VerticalHexTriGraph.nodes_at_edge(shape))

[docs]    @staticmethod
def nodes_at_edge(shape):
"""
Examples
--------
>>> from landlab.graph.hex.hex import VerticalHexTriGraph
>>> VerticalHexTriGraph.nodes_at_edge((3, 7))
(array([ 9, 16]), array([23, 26, 28]), array([29, 27, 24]), array([20, 13]),
array([6, 3, 1]), array([0, 2, 5]))
>>> VerticalHexTriGraph.nodes_at_edge((2, 3))
(array([2]), array([5]), array([6]), array([4]), array([1]), array([0]))
>>> VerticalHexTriGraph.nodes_at_edge((2, 4))
(array([2, 6]), array([10]), array([11, 9]), array([7]), array([3, 1]), array([0]))
"""
n_rows, n_cols = shape
(
northeast,
north,
northwest,
southwest,
south,
southeast,
) = VerticalHexTriGraph.corner_nodes(shape)

if shape[1] == 1:
southwest = northwest - n_cols

return (
np.arange(southeast, northeast, n_cols),
as_id_array(north - np.arange(1, (n_cols + 1) // 2).cumsum())[::-1],
as_id_array(north - np.arange(1, (n_cols + 2) // 2).cumsum() + 1),
np.arange(northwest, southwest, -n_cols),
as_id_array(south + np.arange(1, (n_cols + 2) // 2).cumsum())[::-1],
as_id_array(south + np.arange(1, (n_cols + 1) // 2).cumsum() - 1),
)

[docs]class HexGraphExtras:
@property
@cache_result_in_object()
@make_return_array_immutable
def nodes_at_right_edge(self):
"""Get nodes along the right edge.

Examples
--------
>>> import numpy as np
>>> from landlab.graph import TriGraph
>>> graph = TriGraph((3, 4), node_layout='rect')
>>> graph.nodes_at_right_edge
array([ 3,  7, 11])
"""
return np.arange(
self.shape[1] - 1, self.shape[0] * self.shape[1], self.shape[1], dtype=int
)

@property
@cache_result_in_object()
@make_return_array_immutable
def nodes_at_top_edge(self):
"""Get nodes along the top edge.

Examples
--------
>>> import numpy as np
>>> from landlab.graph import TriGraph
>>> graph = TriGraph((3, 4), node_layout='rect')
>>> graph.nodes_at_top_edge
array([ 8,  9, 10, 11])
"""
return np.arange(
self.number_of_nodes - self.shape[1], self.number_of_nodes, dtype=int
)

@property
@cache_result_in_object()
@make_return_array_immutable
def nodes_at_left_edge(self):
"""Get nodes along the left edge.

Examples
--------
>>> import numpy as np
>>> from landlab.graph import TriGraph
>>> graph = TriGraph((3, 4), node_layout='rect')
>>> graph.nodes_at_left_edge
array([0, 4, 8])
"""
return np.arange(0, self.shape[0] * self.shape[1], self.shape[1], dtype=int)

@property
@cache_result_in_object()
@make_return_array_immutable
def nodes_at_bottom_edge(self):
"""Get nodes along the bottom edge.

Examples
--------
>>> import numpy as np
>>> from landlab.graph import TriGraph
>>> graph = TriGraph((3, 4), node_layout='rect')
>>> graph.nodes_at_bottom_edge
array([0, 1, 2, 3])
"""
return np.arange(self.shape[1], dtype=int)

@property
@cache_result_in_object()
@make_return_array_immutable

@cached_property

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).

Examples
--------
>>> from landlab import HexModelGrid
>>> import numpy as np
>>> grid = HexModelGrid((3, 2))
array([ 1, 16,  4, 16,  4,  1,  1,  4, 16,  4, 16,  1])
>>> grid = HexModelGrid((2, 3), orientation="vertical")
array([32,  2,  8,  2, 32,  8,  8, 32,  2,  8,  2, 32])
"""
code = np.round(self.angle_of_link * 6.0 / np.pi).astype(np.uint8)
code[code == 11] = 5
code[:] = 2**code

return code

@cached_property

Return IDs of links of the same orientation that are connected to

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
or it has no attached parallel neighbor in the given direction.

For instance, consider a 3x3 hex, in which link IDs are as shown::

o---17--o---18--o
/ .     / .     / .
11 12   13  14  15  16
/     . /     . /     .
o---8---o---9---o---10--o
.     / .     / .     /
2   3   4   5   6   7
. /     . /     . /
o---0---o---1---o

o-------o-------o
/ .     / .     / .
/   .   /   .   /   .
/     4 3     6 5     .
o-----9-o-8--10-o-9-----o
.    13 12   15 14    /
.   /   .   /   .   /
. /     . /     . /
o-------o-------o

The corresponding data structure would be mostly filled with -1, but
for the 11 active links, it would look like::

3: [[-1, 13],
4:  [-1, 12],
5:  [-1, 15],
6:  [-1, 14],
8:  [-1,  9],
9:  [ 8, 10],
10: [ 9, -1],
12: [ 4, -1],
13: [ 3, -1],
14: [ 6, -1],
15: [ 5, -1]]

Examples
--------
>>> from landlab import HexModelGrid
>>> grid = HexModelGrid((3, 3))
>>> pll[3:16]
array([[-1, 13],
[-1, 12],
[-1, 15],
[-1, 14],
[-1, -1],
[-1,  9],
[ 8, 10],
[ 9, -1],
[-1, -1],
[ 4, -1],
[ 3, -1],
[ 6, -1],
[ 5, -1]])
"""
if self.orientation == "horizontal":
else:

pll = np.full((self.number_of_links, 2), -1, dtype=int)

for col, orientation in enumerate(orientations):

else:

return pll

if self.orientation == "horizontal":
else:

links_at_node = np.full((self.number_of_nodes, 6), -1, dtype=int)

for col, orientation in enumerate(orientations):

else:

[docs]class TriGraph(HexGraphExtras, DelaunayGraph):

"""Graph of a structured grid of triangles.

Examples
--------
>>> import numpy as np
>>> from landlab.graph import TriGraph

>>> graph = TriGraph((3, 2))
>>> graph.number_of_nodes == 6
True
>>> np.round(graph.y_of_node * 2. / np.sqrt(3))
...     # doctest: +NORMALIZE_WHITESPACE
array([ 0.,  0.,  1.,  1.,  2.,  2.])
>>> graph.x_of_node # doctest: +NORMALIZE_WHITESPACE
array([ 0. ,  1. ,  0.5,  1.5,  0. ,  1. ])
"""

[docs]    def __init__(
self,
shape,
spacing=1.0,
xy_of_lower_left=(0.0, 0.0),
orientation="horizontal",
node_layout="rect",
sort=False,
):
"""Create a structured grid of triangles.

Parameters
----------
shape : tuple of int
Number of rows and columns of the hex grid. The first value
is the number of nodes in the first column and the second the
number of nodes in the first column.
spacing : float, optional
xy_of_lower_left : tuple of float, optional
Coordinates of lower-left corner of the grid.
orientation: {'horizontal', 'vertical'}
Specify if triangles should be laid out in rows or columns.
node_layout: {'rect', 'hex'}
Specify the overall layout of the nodes. Use *rect* for
the layout to approximate a rectangle and *hex* for
a hexagon.
"""
if node_layout not in ("rect", "hex"):
raise ValueError("node_layout not understood")

if orientation not in ("horizontal", "vertical"):
raise ValueError("orientation not understood")

layouts = {
"horizontal_hex": HorizontalHexTriGraph,
"vertical_hex": VerticalHexTriGraph,
"horizontal_rect": HorizontalRectTriGraph,
"vertical_rect": VerticalRectTriGraph,
}
layout = layouts["_".join([orientation, node_layout])]

try:
spacing = float(spacing)
except TypeError as exc:
raise TypeError("spacing must be a float") from exc

self._shape = tuple(shape)
self._spacing = spacing
self._orientation = orientation
self._node_layout = node_layout

x_of_node, y_of_node = layout.xy_of_node(
shape, spacing=spacing, xy_of_lower_left=xy_of_lower_left
)
self._perimeter_nodes = layout.perimeter_nodes(shape)

if 1 in shape:
Graph.__init__(
self,
(y_of_node, x_of_node),
zip(np.arange(len(y_of_node) - 1), np.arange(1, len(y_of_node)))
),
sort=False,
)
else:
DelaunayGraph.__init__(
self,
(y_of_node, x_of_node),
sort=False,
)

if sort:
self.sort()

@property
def shape(self):
return self._shape

@property
def spacing(self):
return self._spacing

@property
def orientation(self):
return self._orientation

@property
def node_layout(self):
return self._node_layout

@cached_property
@make_return_array_immutable
def perimeter_nodes(self):
return self._perimeter_nodes
```