A 2-by-2 matrix maps a point to a point, and a matrix-vector product is a weighted sum of the columns of the matrix. If the matrix has rank 2, its action can be reversed: the inverse matrix sends every output point back to its input point.
In this chapter we use these tools to solve a concrete geometric problem - determining whether a point is inside or outside a triangle.
Given a triangle on a 2d-plane with vertices [x1,y1], [x2,y2] and [x3,y3], and given a point [x,y], how can you find out whether the point is inside or outside the triangle? Before we address how to solve this problem, think about how you would approach it for a minute or two.
This problem seemingly has no connection with linear algebra but, as we will see below, can be solved elegantly with the tools of linear algebra that we have learnt so far.
Let's say the vertices of are , and . And we want to know whether or not [2,1] is inside the triangle.
We will also keep implementing a function to do the same as we make progress:
def is_point_in_triangle(vertices, point):
'''
vertices: [[x1,y1],[x2,y2],[x3,y3]]
point: [p,q]
'''
...
The canvas shows the same triangle and point again. Now consider a second copy of the problem where one of the vertices of the triangle ([3,0]) lies on the origin, and everything else keeps its position relative to that vertex: .
These two problems are actually the same problem - [-3,0], so the position of the point relative to the vertices of the triangle is unchanged. But it is easier to find the answer of the second problem, which is also the answer of the first problem. So we will now focus on how to solve the second problem: is the point [-1,1] inside the triangle with vertices [0,0], [-1,3] and [-7,-2]?
This is easy: if we want to move the vertex [x1,y1] to the origin, we subtract [x1,y1] from all three vertices and the point.
def is_point_in_triangle(vertices, point):
'''
vertices: [[x1,y1],[x2,y2],[x3,y3]]
point: [p,q]
'''
x1,y1 = vertices[0]
x2,y2 = vertices[1]
x3,y3 = vertices[2]
shifted_vertices = [[x1-x1,y1-y1],[x2-x1,y2-y1],[x3-x1,y3-y1]]
shifted_point = [point[0]-x1, point[1]-y1]
Let's name the vertices and the point first: A = [0,0], B = [-1,3], C = [-7,-2] and P = [-1,1].
We want to look at the sides of the triangle AB, AC and the point P as vectors. The side AB can be seen as in the 2-dimensional space. Similarly AC can be seen as . is the vector .
Next we would like to get to the point P by taking weighted sums of the vectors AB and AC.
Remember that this walk can be represented algebraically as a matrix-vector multiplication where the matrix has the vectors AB and AC as its columns, and the vector is the weights for each column:
On the canvas, is times the vector AB, is times the vector AC placed tip-to-tail, and lands on the weighted sum.
We want to observe for what values of the output point lies inside the triangle. It is this insight that leads to the solution. Before we reveal the pattern for the weights, let's play with it a bit and get a feel for it:
Let's also write a function for matrix multiplication:
def scale_vector(scale, vector):
return [i*scale for i in vector]
def add_vectors(array_of_vectors):
dimensionality = len(array_of_vectors[0])
output = [0]*dimensionality
for vector in array_of_vectors:
output = [i+j for (i,j) in zip(output, vector)]
return output
def matMul(columns_of_matrix, weights):
scaled_columns = [scale_vector(scale, vector)
for (scale, vector) in zip(weights, columns_of_matrix)]
return add_vectors(scaled_columns)
Here are some observations about the output point of the matrix multiplication above:
The output point lies inside the triangle only when , and .
Now the question is: if we want to find out whether the point lies inside the triangle or not, we need to find out for which values of we have
This brings us to the final solution. We know that the matrix maps to . Then the matrix that maps back to is the inverse of .
We saw how to invert a 2-by-2 matrix in the last chapter. This is how a function to do so looks like:
def get_inverse_of(matrix2d):
x1,y1 = matrix2d[0]
x2,y2 = matrix2d[1]
scale = 1/(x1*y2 - x2*y1)
return [[y2*scale, -y1*scale],[-x2*scale, x1*scale]]
Let's see what the inverse of this matrix is:
It is important to understand this part:
Multiplying this inverse matrix with a vector gives us the weights for which the weighted sum of the vectors
ABandACequals .
Now if these weights follow these three rules, it means that the point lies inside the triangle:
Let's write a function to check for these conditions:
def weights_are_valid(w1,w2):
return w1 >= 0 and w2 >= 0 and w1+w2 <= 1
Note that in this example we have
Thus and . Since these values satisfy the three conditions above, the point [-1,1] indeed lies inside the triangle. You can verify this on the canvas: and watch the output arrow land exactly on the gold point P.
Below is a full implementation of a function that takes as input vertices of a triangle and a point. It returns True if the point is inside the triangle and False otherwise.
def is_point_in_triangle(vertices, point):
'''
vertices: [[x1,y1],[x2,y2],[x3,y3]]
point: [p,q]
'''
x1,y1 = vertices[0]
x2,y2 = vertices[1]
x3,y3 = vertices[2]
shifted_vertices = [[x1-x1,y1-y1],[x2-x1,y2-y1],[x3-x1,y3-y1]]
shifted_point = [point[0]-x1, point[1]-y1]
# create a matrix with sides AB and AC as vectors
matrix = [shifted_vertices[1], shifted_vertices[2]]
inverse_matrix = get_inverse_of(matrix)
# get the weights needed to scale the vectors AB and AC
w1, w2 = matMul(inverse_matrix, shifted_point)
return weights_are_valid(w1,w2)
Once you figure out how to determine whether or not a point is inside a given triangle, you can easily extend this idea to polygons. All you need to do is to break down the polygon into a set of triangles. Then a given point is inside the polygon if it is inside any of these triangles.
For example, take with vertices [-1,-2], [3,-2], [4,3], [-3,2], [-4,0]. We can by drawing diagonals from one vertex. Now the question "is the point inside the pentagon?" becomes "is the point inside any of these three triangles?"
This is what a function to do so would look like:
def is_point_in_polygon(vertices, point):
triangles = decompose_polygon_into_triangles(vertices)
for triangle in triangles:
if is_point_in_triangle(triangle, point):
return True
return False
How will you implement the function decompose_polygon_into_triangles? Does the function work for all polygons? What if a polygon is not convex?
In the next chapter, we will look at what it means to multiply a matrix with another matrix.
← 4. Rank and reversibility · 6. Matrix-matrix multiplication →