Vectors
Vectors as lists, functions and arrows; addition, scaling, dot product and convex combination, on the robot's position.
Do this lesson in the simulatorWhere is the robot, which way is it facing, and is that marker in front of it or behind? Each of those answers is a vector. A vector is a data structure as well as a piece of maths: an ordered list of numbers that a program can add, scale and multiply. In this lesson BugBot's position and heading become vectors, and the dot product tells it what is ahead.
Three ways to see a vector
As a list of numbers. [2.0, 3.14159, -1.0, 2.718281828] is a vector with four components. Because each component is a real number, it is called a 4-vector over ℝ, and the set of all such vectors is written ℝ⁴. In a program it is a one-dimensional array. BugBot's position, [x, y], is a 2-vector over ℝ, a member of ℝ².
As a function. A vector can be read as a function that maps each index to a value. For the 4-vector above, the domain is the set of indexes {0, 1, 2, 3} and the co-domain is ℝ:
0 ↦ 2.0, 1 ↦ 3.14159, 2 ↦ -1.0, 3 ↦ 2.718281828
A dictionary represents that function directly: {0: 2.0, 1: 3.14159, 2: -1.0, 3: 2.718281828}. This is useful when most components are zero, as in the word-count vectors of the last lesson: store only the ones that are not.
As an arrow. A 2-vector or 3-vector can be drawn as an arrow from the origin to the point it names. The arrow has a magnitude (its length) and a direction. For [x, y] the magnitude is √(x² + y²), by Pythagoras.
import math
v_list = [2.0, 3.14159, -1.0, 2.718281828] # a vector as a list (a 1D array)
v_dict = {0: 2.0, 1: 3.14159, 2: -1.0, 3: 2.718281828} # the same vector as a function: index -> value
print(v_list[1], v_dict[1])
p = [30, 40] # a position as an arrow from the origin
print("magnitude", math.sqrt(p[0] ** 2 + p[1] ** 2))
Adding and scaling
Vector addition adds matching components: [3, 4] + [1, 2] = [4, 6]. As arrows, it puts the second arrow on the end of the first, so adding a vector to a position translates it: moves it without turning it.
Scalar-vector multiplication multiplies every component by one number, the scalar: 2 × [3, 4] = [6, 8]. It scales the arrow: the same direction, twice as long. A negative scalar reverses the direction.
Convex combination
A convex combination of two vectors u and v is
αu + βv, where α ≥ 0, β ≥ 0 and α + β = 1
Every convex combination lies on the straight line segment between the points u and v. With α = 1 you get u, with β = 1 you get v, and with α = β = 0.5 you get the midpoint. So convex combinations let a robot find any point between two places: 25% of the way, halfway, three quarters.
Dot product
The dot product of two vectors of the same size multiplies matching components and adds the results. The answer is a single number, not a vector:
u · v = u₀v₀ + u₁v₁ + ... + uₙ₋₁vₙ₋₁
For u = [3, 4] and v = [1, 2]: u · v = 3 × 1 + 4 × 2 = 11.
Its most useful property connects it to the angle θ between the arrows:
u · v = |u| × |v| × cos θ
- Finding the angle: θ = cos⁻¹((u · v) / (|u| × |v|)). For the example, |u| = 5 and |v| = √5 ≈ 2.236, so cos θ = 11 / 11.18 ≈ 0.984 and θ ≈ 10.3°.
- Perpendicular: if u · v = 0 the vectors are at right angles.
- In front or behind: if u · v > 0 the angle is less than 90°; if it is negative, more than 90°.
- Magnitude: u · u = |u|², so |u| = √(u · u).
- How far along: if
his a unit vector (magnitude 1), thenh · dis how fardreaches in the direction ofh.
BugBot's vectors
position() gives the robot's position vector p. Its heading, θ degrees clockwise from the direction it started in, gives a unit heading vector h = [sin θ, cos θ]: at 0° that is [0, 1], straight ahead, and at 90° it is [1, 0], to the right. For a target at t, the displacement from the robot is d = t - p, and h · d says whether the target is ahead, and how far along the heading it is.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
import math
def dot(a, b):
return a[0] * b[0] + a[1] * b[1]
turn_right(30, angle=60)
theta = math.radians(heading())
h = [math.sin(theta), math.cos(theta)]
p = list(position())
print("heading vector", [round(h[0], 2), round(h[1], 2)])
targets = {"cone": [30, 20], "box": [-20, 10], "wall": [10, -40]}
for name in targets:
t = targets[name]
d = [t[0] - p[0], t[1] - p[1]]
along = dot(h, d)
angle = math.degrees(math.acos(along / math.sqrt(dot(d, d))))
print(name, "is", "ahead" if along > 0 else "behind", "at", round(angle), "degrees off the heading")
d = [targets["cone"][0] - p[0], targets["cone"][1] - p[1]]
forward(50, distance=dot(h, d)) # drive as far along the heading as the cone reaches
print("now at", position())
The cone is almost straight ahead, so driving h · d along the heading brings the robot very close to it. The other two targets have negative dot products: they are behind.
Task: meet on the line
Two beacons are at u = [40, 20] and v = [-20, 60], in cm from the robot's start (x to the right, y forward). Write these three functions yourself, for vectors of any length, without numpy:
add(a, b): returns a new list, the vector sum ofaandb.scale(k, a): returns a new list, the numberktimes the vectora.dot(a, b): returns the dot product ofaandb, a single number.
Then print, in this order:
u.v = <dot product>, which is a whole number.angle = <angle> degrees, the angle betweenuandvrounded to a whole number withround(). You will needmath.acos,math.degreesandmath.sqrt.w = <w>, wherew = 0.25u + 0.75v, built withscaleandadd, printed as a Python list, for examplew = [1.0, 2.0].
Finally drive to w: slide sideways by its x component (left if it is negative) and then forward by its y component.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
import math
u = [40, 20]
v = [-20, 60]
Challenges
- Is
wreally on the segment between the beacons? Check thatw - uis a positive scalar multiple ofv - u. - Find two different whole-number vectors whose dot product with
[3, 4]is zero. What do they have in common? - Rewrite
dotso it takes two vectors stored as dictionaries, where missing indexes mean zero.