Creating an hexagonal grid

A quick note as to how to create an hexagonal grid.

Let's first initialize the notebook:

In [1]:
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
phi = (np.sqrt(5)+1)/2
fig_width = 21
%load_ext autoreload
%autoreload 2
In [2]:
import numpy as np
np.meshgrid?
Signature: np.meshgrid(*xi, **kwargs)
Docstring:
Return coordinate matrices from coordinate vectors.

Make N-D coordinate arrays for vectorized evaluations of
N-D scalar/vector fields over N-D grids, given
one-dimensional coordinate arrays x1, x2,..., xn.

.. versionchanged:: 1.9
   1-D and 0-D cases are allowed.

Parameters
----------
x1, x2,..., xn : array_like
    1-D arrays representing the coordinates of a grid.
indexing : {'xy', 'ij'}, optional
    Cartesian ('xy', default) or matrix ('ij') indexing of output.
    See Notes for more details.

    .. versionadded:: 1.7.0
sparse : bool, optional
    If True a sparse grid is returned in order to conserve memory.
    Default is False.

    .. versionadded:: 1.7.0
copy : bool, optional
    If False, a view into the original arrays are returned in order to
    conserve memory.  Default is True.  Please note that
    ``sparse=False, copy=False`` will likely return non-contiguous
    arrays.  Furthermore, more than one element of a broadcast array
    may refer to a single memory location.  If you need to write to the
    arrays, make copies first.

    .. versionadded:: 1.7.0

Returns
-------
X1, X2,..., XN : ndarray
    For vectors `x1`, `x2`,..., 'xn' with lengths ``Ni=len(xi)`` ,
    return ``(N1, N2, N3,...Nn)`` shaped arrays if indexing='ij'
    or ``(N2, N1, N3,...Nn)`` shaped arrays if indexing='xy'
    with the elements of `xi` repeated to fill the matrix along
    the first dimension for `x1`, the second for `x2` and so on.

Notes
-----
This function supports both indexing conventions through the indexing
keyword argument.  Giving the string 'ij' returns a meshgrid with
matrix indexing, while 'xy' returns a meshgrid with Cartesian indexing.
In the 2-D case with inputs of length M and N, the outputs are of shape
(N, M) for 'xy' indexing and (M, N) for 'ij' indexing.  In the 3-D case
with inputs of length M, N and P, outputs are of shape (N, M, P) for
'xy' indexing and (M, N, P) for 'ij' indexing.  The difference is
illustrated by the following code snippet::

    xv, yv = np.meshgrid(x, y, sparse=False, indexing='ij')
    for i in range(nx):
        for j in range(ny):
            # treat xv[i,j], yv[i,j]

    xv, yv = np.meshgrid(x, y, sparse=False, indexing='xy')
    for i in range(nx):
        for j in range(ny):
            # treat xv[j,i], yv[j,i]

In the 1-D and 0-D case, the indexing and sparse keywords have no effect.

See Also
--------
index_tricks.mgrid : Construct a multi-dimensional "meshgrid"
                 using indexing notation.
index_tricks.ogrid : Construct an open multi-dimensional "meshgrid"
                 using indexing notation.

Examples
--------
>>> nx, ny = (3, 2)
>>> x = np.linspace(0, 1, nx)
>>> y = np.linspace(0, 1, ny)
>>> xv, yv = np.meshgrid(x, y)
>>> xv
array([[0. , 0.5, 1. ],
       [0. , 0.5, 1. ]])
>>> yv
array([[0.,  0.,  0.],
       [1.,  1.,  1.]])
>>> xv, yv = np.meshgrid(x, y, sparse=True)  # make sparse output arrays
>>> xv
array([[0. ,  0.5,  1. ]])
>>> yv
array([[0.],
       [1.]])

`meshgrid` is very useful to evaluate functions on a grid.

>>> import matplotlib.pyplot as plt
>>> x = np.arange(-5, 5, 0.1)
>>> y = np.arange(-5, 5, 0.1)
>>> xx, yy = np.meshgrid(x, y, sparse=True)
>>> z = np.sin(xx**2 + yy**2) / (xx**2 + yy**2)
>>> h = plt.contourf(x,y,z)
>>> plt.show()
File:      /usr/local/lib/python3.7/site-packages/numpy/lib/function_base.py
Type:      function

first a rectangular grid

In [3]:
N = 121
N_X = int(np.sqrt(N))
N_Y = N // N_X
xv, yv = np.meshgrid(np.arange(N_X), np.arange(N_Y), sparse=False, indexing='xy')
xv, yv
Out[3]:
(array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10]]),
 array([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
        [ 1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1],
        [ 2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2],
        [ 3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3],
        [ 4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4],
        [ 5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5],
        [ 6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6],
        [ 7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7],
        [ 8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8],
        [ 9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9],
        [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]]))
In [4]:
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.scatter(xv, yv)
Out[4]:
<matplotlib.collections.PathCollection at 0x11adc56d0>
No description has been provided for this image

second an hexagonal grid

In [5]:
ratio = np.sqrt(3)/2 # cos(60°)
ratio
Out[5]:
0.8660254037844386
In [6]:
N_X = int(np.sqrt(N)/ratio)
N_Y = N // N_X
xv, yv = np.meshgrid(np.arange(N_X), np.arange(N_Y), sparse=False, indexing='xy')
In [7]:
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.scatter(xv, yv)
Out[7]:
<matplotlib.collections.PathCollection at 0x119e4f990>
No description has been provided for this image
In [8]:
xv = xv * ratio
xv, yv
Out[8]:
(array([[0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944],
        [0.        , 0.8660254 , 1.73205081, 2.59807621, 3.46410162,
         4.33012702, 5.19615242, 6.06217783, 6.92820323, 7.79422863,
         8.66025404, 9.52627944]]),
 array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
        [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
        [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
        [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
        [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
        [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7],
        [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
        [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]]))
In [9]:
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.scatter(xv, yv)
Out[9]:
<matplotlib.collections.PathCollection at 0x11d7b2cd0>
No description has been provided for this image

intercalate every two lines by half the width

In [10]:
xv[::2, :] += ratio/2
In [11]:
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.scatter(xv, yv)
Out[11]:
<matplotlib.collections.PathCollection at 0x11e326e10>
No description has been provided for this image

A bigger one for the show:

In [12]:
N = 2345

ratio = np.sqrt(3)/2 # cos(60°)
N_X = int(np.sqrt(N/ratio))
N_Y = N // N_X
xv, yv = np.meshgrid(np.arange(N_X), np.arange(N_Y), sparse=False, indexing='xy')
xv = xv * ratio
xv[::2, :] += ratio/2
In [13]:
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.scatter(xv, yv, s=1)
ax.axis('equal');
No description has been provided for this image

some book keeping for the notebook

In [14]:
%load_ext watermark
%watermark -i -h -m -v -p numpy,matplotlib  -r -g -b
2020-04-16T10:50:38+02:00

CPython 3.7.7
IPython 7.13.0

numpy 1.18.2
matplotlib 3.2.1

compiler   : Clang 11.0.0 (clang-1100.0.33.17)
system     : Darwin
release    : 19.4.0
machine    : x86_64
processor  : i386
CPU cores  : 4
interpreter: 64bit
host name  : ekla
Git hash   : 5674af04806088904631f89418e5a7ef04afd596
Git repo   : https://github.com/laurentperrinet/sciblog.git
Git branch : master