My goal is to cover the plotting area with lattice points.
In this example we are working in 2D. We call the set Λ ⊆ R^2 a lattice if there exists a basis B ⊆ R^2 with Λ = Λ(B). The set Λ(B) is a set of all integer linear combinations of the basis vectors, so Λ(B) = {xb1 + yb2 | x,y integers}.
In other words, we get a grid by calculating the integer combinations of the given basis vectors. For the standard basis E=[[1,0]^T, [0,1]^T] we can write the point [8,4]^T as 8 * [1,0]^T + 4 * [0,1]^T where both 8 and 4 are integers. The set of all integer combinations (hence the lattice Λ) then looks like this:
If we change the basis, this will result into a different lattice. Here is an example for b1=[2,3]^T, b2=[4,5]^T:
In order to produce these images I am using the following Python code:
import numpy as np
import matplotlib.pyplot as plt
from typing import List, Tuple
def plotLattice(ax: plt.Axes, basis_vectors: List[np.ndarray],
ldown: np.ndarray, rup: np.ndarray, color: str,
linewidth: float, alpha: float) -> List[np.ndarray]:
"""
Draws a two-dimensional lattice.
Args:
ax: The Matplotlib Axes instance to plot on.
basis_vectors: A list of two NumPy arrays representing the basis vectors of the lattice.
ldown: A NumPy array representing the lower left corner of the rectangular area to draw the lattice in.
rup: A NumPy array representing the upper right corner of the rectangular area to draw the lattice in.
color: A string representing the color of the lattice points and basis vectors.
linewidth: A float representing the linewidth of the lattice points.
alpha: A float representing the alpha value of the lattice points.
Returns:
A list of NumPy arrays representing the lattice points.
"""
# get the basis vectors
b1, b2 = np.array(basis_vectors[0]), np.array(basis_vectors[1])
# list to hold the lattice points
points = []
# upper bounds for the for loops
xmax, ymax = 0, 0
if b1[0] == 0:
xmax = np.floor(rup[0] / abs(b2[0]))
elif b2[0] == 0:
xmax = np.floor(rup[0] / abs(b1[0]))
else:
xmax = np.floor(rup[0] / min(abs(b1[0]),abs(b2[0])))
if b1[1] == 0:
ymax = np.floor(rup[1] / abs(b2[1]))
elif b2[1] == 0:
ymax = np.floor(rup[1] / abs(b1[1]))
else:
ymax = np.floor(rup[1] / min(abs(b1[1]),abs(b2[1])))
# increase the bounds by 1
xmax = int(xmax) + 1
ymax = int(ymax) + 1
# get the lower bounds for the for loops
xmin, ymin = -int(xmax), -int(ymax)
for i in range(xmin, int(xmax)):
for j in range(ymin, int(ymax)):
# make the linear combination
p = i * b1 + j * b2
# if the point is within the plotting area, plot it and add the point to the list
if ldown[0] <= p[0] <= rup[0] and ldown[1] <= p[1] <= rup[1]:
ax.scatter(p[0], p[1], color=color, linewidths=linewidth, alpha=alpha)
points.append(p)
# plot basis vectors
ax.quiver(0, 0, b1[0], b1[1], color=color, scale_units='xy', scale=1, alpha=1)
ax.quiver(0, 0, b2[0], b2[1], color=color, scale_units='xy', scale=1, alpha=1)
return points
if __name__ == '__main__':
# pick a basis
b1 = np.array([2, 3])
b2 = np.array([-4, 5])
basis_vectors = [b1, b2]
# define the plotting area
ldown = np.array([-15, -15])
rup = np.array([15, 15])
fig, ax = plt.subplots()
points = plotLattice(ax, basis_vectors, ldown, rup, 'blue', 3, 0.25)
# resize the plotting window
mngr = plt.get_current_fig_manager()
mngr.resize(960, 1080)
# tune axis
ax.set_aspect('equal')
ax.grid(True, which='both')
ax.set_xlim(ldown[0], rup[0])
ax.set_ylim(ldown[1], rup[1])
# show the plot
plt.show()
And now we get to the problem. For the basis vectors b1=[1,1], b2=[1,2] the code does not cover the plotting area:
We can increase the problem by choosing some not nicely orthogonal vectors:
So, the problem arises every time when the vectors are getting closer to each other, hence when the dot product is big. Now consider the example I picked before:
My approach was to take the minimum values of the absolute coordinate values and to estimate how much points I can have along one axis. This is also the approach you can see in the code. Taking the minimum of the x coordinates of b1=[1,1]
and b2=[1,2]
we get 1. Taking the minimum of the y coordinates we get 1. My plotting area is defined by the square which is given by the points ldown=[-15,-15]
and rup=[15,15]
. Hence I know that I can have maximum floor(rup[0]/1) = 15
points along the x-axis, and maximum floor(rup[1]/1) = 15
along the y-axis. Including the zero point it results in 31 points for each axis, so that I expect to see 31*31 = 961 points on the plot. Hence, I think that I am done and set xmax=15, xmin=-15, ymax=15, ymin=-15
.
But this gives me the result presented above. So, the calculation is wrong. Then I say, "Ok, I know that the point [15,-15]
has to be in the plot". Hence I can solve the system Bx = [15,-15]^T
. This results into the vector x=[45,-30]
. Now I can set xmax=45, ymin=-30
. Doing the same for the point [-15,15]
gives me the vector x=[-45,30]
. So I can set xmin=-45, ymin=-30
. The resulting plot is:
This looks almost well, but you can notice that the points [15,-15]
and [-15,15]
are missing in the plot. Hence I have to enlarge the bounds by 1 by setting xmax=46, xmin=-46, ymax=31, ymin=-31
. After that, the whole area is covered.
So, the drawback of this mechanism, is that I cheated a bit. Here, I just knew that the point [15,-15]
must be on the plot. I could solve the equations system and determine the bounds for the for
loop. Occasionally, this point was also the most distant point from the origin, so that I knew that covering it I should automatically cover the whole plotting plane. However, there are basis vectors for which I cannot determine such point, and we can just pick one of the previous plots:
Here, my approach would say that we can have min(2,4) = 2
points along the x-axis and min(3,5)=3
points along the y-axis. But I cannot simply say that the point [14,-9]=[7*2,-3*3]
is on the plot (because it is not). Moreover, I cannot even say which of the points [12,-12], [12,-15], [14,-12],[14-15]
are part of the plot, and which are not. Knowing the plot I see that [12,-15]
and [14,-12]
must be in the plot. Without knowing that I do not even know for which point I have to solve the Bx=b
system.
Choosing different basis or a different (not origin-centered) plotting area makes the problem surprisingly complex for me, - even though we are acting in a 2D plane only.
So, now when the problem is described, I can formulate it as follows: Given the points rup
and ldown
of the plotting area, a basis b1, b2
, define the bounds xmax, xmin, ymax, ymin
for the for
loops, so that the whole plotting area gets covered by the lattice points.
I am not even asking the code to be efficient at the moment, however a solution of the type xmax = sys.maxsize
or xmax = 100 * rup[0]
do not count.
What would be your approach?