Points and vectors

In the previous post, we took a closer look at how we represent problems numerically. In this post, we'll intoduce some additional "type safety" into this prcess by distinguishing between two types of quantities: points and vectors. You might also call these absolute and relative quantities, or affine and vector quantities. In the next post, we'll use this perspective to build a draggable popup.

Introduction

Let's start with the example of temperature. Suppose the temperatures in a given week are as follows:

MTWRF

Looking at the Celsius row, we might be tempted to say something like "it was twice as hot on Wednesday as it was on Tuesday", having the equation

25=102\cdot 5 = 10

in mind. But if we write the same equation in Fahrenheit, we'd get

241=50\color{red} 2\cdot 41 = 50

which is wrong!

On the other hand, we could say "it got twice as hot from Monday to Wednesday as it did from Monday to Tuesday". In the Celsius case, we'd be thinking of the equation

2(50)=100;2 \cdot (5 - 0) = 10 - 0;

in the Fahrenheit case, we'd have the equation

2(4132)=5032,2\cdot(41 - 32) = 50 - 32,

and this equation is true.

What's going on?

Operations

There are two distinct things we can mean by "temperature": an absolute temperature (how hot an object is, or the temperature outside), or a relative temperature, representing the difference between two absolute temperatures. The formulas for converting between these are different: for relative temperatures, we just multiply or divide by 59,\frac59, whereas for absolute temperatures we also have to offset by 32.32.

Here are some more examples of absolute vs relative quantities:

  • an absolute time is a datetime like "3:23pm April 19 1943"; a relative time is a duration, like "4 minutes and 33 seconds".

  • an absolute position is a specific point in space, like the top of the Eiffel tower. A relative position is something like "2km north and 500m east".

We will call relative quantities vectors, and absolute quantities points. The "allowed" (or "meaningful") operations between these are

  • pointpoint=vector\ptxt{point} - \ptxt{\red point} = \vtxt{\green vector}

  • point+vector=point\ptxt{point} + \vtxt{vector} = \ptxt{point}

  • vector+vector=vector\vtxt{vector} + \vtxt{vector} = \vtxt{vector}

  • numbervector=vector\text{\blue{number}} \cdot \vtxt{vector} = \vtxt{vector}

Here are some demos of that in 2d and 3d space:

Access source on GitHubSource

When you're working with numbers (i.e. once you've picked a coordinate system), you can't really tell the difference between points and vectors: you can add and multiply points willy-nilly. You'll run into problems, though, when you try to change coordinate systems: if you do an "illegal" operation, you'll get different answers in different coordinate systems.

Let's make a definition.

Let's try to give a slightly more precise definition (although I don't want to be fully rigorous in the main series).

If vectors represent differences between points, we might think to define points first, and then vectors. But look back at the operations we can do: all the operations involving points also involve vectors, but adding and scaling of vectors doesn't involve points. So we can talk about vectors without mentioning points, but not vice versa.

Second, if the distinguishing property of vectors is that they can be added together with other vectors, and scaled by numbers to produce other vectors, then it doesn't make sense to talk about individual vectors in isolation.

These operations are required to satisfy several axioms, such as v+0V=vv+0_V=v and u+v=v+u.u+v=v+u.

It might be startling to think of functions as vectors—but this perspective turns out to be very useful, for example when solving systems of differential equations. One of the benefits of the abstract perspective is that it allows us to extend the tools of linear algebra to situations we wouldn't have originally thought of.

In most situations there will be an obvious choice for the "zero vector", "addition", and "scalar multiplication". However, it should be stressed that these are additional data that needs to be specified along with the set V.V.

Code

Graphics

Every graphics library will have methods for dealing with vectors. For example, in THREE.js the relevant class is THREE.Vector3.

Few (if any) graphics libraries explicitly make the distinction between points and vectors. Still, remembering the distinction is helpful for understanding how to convert between different coordinate systems.

In computer graphics, the ambient "affine space" is called world space. The vector space centered at a particular object is called object space. (See below for what I mean by "centered at".)

DOM

Another example of points vs vectors comes from my favorite DOM method, getBoundingClientRect(). The return type of this is a DOMRect, which (ignoring readonly) is

interface DOMRect {
    height: number;
    width: number;
    x: number;
    y: number;
  
    readonly bottom: number;
    readonly left: number;
    readonly right: number;
    readonly top: number;
}

To distinguish between points and vectors, we could type this more precisely as

interface DOMRect {
    height: Vector1;
    width: Vector1;
    x: Point1;
    y: Point1;
  
    readonly bottom: Point1;
    readonly left: Point1;
    readonly right: Point1;
    readonly top: Point1;
}

Here a Vector1 and a Point1 are both just numbers, but we're not allowed to add two Point1s together.

TypeScript

The above discussion of axioms and operations might have been a little abstract or confusing. The following code is an approximate TypeScript equivalent of what I was saying above. In particular, note that the operations add(), scale(), sub() have to be specified/implemented.

// helpers
// https://stackoverflow.com/a/43674389

interface Type<T> {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  new (...args: any[]): T;
}

function staticImplements<T>() {
  return <U extends T>(constructor: U, _context: ClassDecoratorContext) => {
    constructor;
  };
}

// vector types
interface Vector<V> {
  /** Add two vectors together */
  add(other: V): V;

  /** Scale a vector by a number */
  scale(factor: number): V;
}

interface VectorStatic<V> extends Type<Vector<V>> {
  zero: V;
}

// implementation
@staticImplements<VectorStatic<Vector2>>()
export class Vector2 {
  constructor(
    public x: number,
    public y: number,
  ) {}

  static zero = new Vector2(0, 0);

  /** Add two vectors together */
  add(other: Vector2) {
    return new Vector2(this.x + other.x, this.y + other.y);
  }
  
  /** Scale a vector by a number */
  scale(factor: number) {
    return new Vector2(this.x * factor, this.y * factor);
  }

  /** Whether two vectors are equal */
  static eq(u: Vector2, v: Vector2) {
    return u.x === v.x && u.y === v.y;
  }
}

// affine types
interface Affine<V, A> {
  /** Add a vector to a point to get another point */
  add(other: V): A;

  
  /** Subtract two points to get a vector */
  sub(other: A): V;
}

@staticImplements<Type<Affine<Vector2, Point2>>>()
export class Point2 {
  constructor(
    public x: number,
    public y: number,
  ) {}

  /** Add a vector to a point to get another point */
  add(other: Vector2) {
    return new Point2(this.x + other.x, this.y + other.y);
  }

  /** Subtract two points to get a vector */
  sub(other: Point2) {
    return new Vector2(this.x - other.x, this.y * other.y);
  }
  
  /** Whether this point is equal to another */
  eq(other: Point2) {
    return this.x === other.x && this.y === other.y;
  }
}

Note we have to do a little bit of decorator weirdness to express this in TypeScript. Also, TypeScript has no way of expressing the constraints (axioms) these operations need to specify, e.g.

a+(u+v)=(a+u)+vaA; u,vVa+(u+v) = (a+u)+v\quad a\In A;\ u,\,v\In V

The closest code equivalent would be

function assertVectorSpaceAxioms(u: Vector2, v: Vector2, w: Vector2, r: number) {
  // zero vector is identity element for addition
  // u + 0 = u
  assert(Vector2.eq(
    u.add(Vector2.zero),
    u,
  );
  // 0 + u = u
  assert(Vector2.eq(
    Vector2.zero.add(u), // 0 + v
    u,
  );

  // associativity: (u + v) + w = u + (v + w)
  assert(Vector2.eq(
    u.add(v).add(w), // (u + v) + w
    u.add(v.add(w)), // u + (v + w)
  ));

  // commutativity: u + v = v + u
  assert(Vector2.eq(
    u.add(v),
    v.add(u)
  ));

  // ...and so on (see Exercises)
}

These limitations are deficiencies of TypeScript, not of mathematics.

Dictionary

One of the reasons the distinction between points and vectors is confusing is that it's possible to convert between the two. However, there are many different ways to do this—more precisely, every point gives a different way to convert between points and vectors. It works as follows:

Let VV be a vector space, let AA be an affine space over V,V, and let O\O be a point of A.A. The choice of O\O gives a way to go back and forth between AA and V:V:

fO ⁣:AVgO ⁣:VAfO(a)=aOgO(v)=O+v\begin{align*} f_\O\colon & A\to V & g_\O\colon & V\to A\\ f_\O(a) &= a-\O & g_\O(v) &= \O+v \end{align*}

We include the subscript O\O to emphasize that these functions depend on the choice of "origin" point O.\O. These functions are bijections:

gO(fO(a))=gO(aO)fO(gO(v))=fO(O+v)=O+(aO)=(O+v)O=a=v\begin{align*} g_\O(f_\O(a)) &= g_\O(a-\O) & f_\O(g_\O(v)) &= f_\O(\O+v)\\ &= \O + (a-\O) &&= (\O+v)-\O\\ &= a &&= v \end{align*}

so no information is lost in this process.

The rub is that this dictionary depends on the chosen point O\O. In some situations there may be an obvious "origin" point, but in others there may be none, and in most there are several. For example, in computer graphics our "default" origin is the top-left corner of the screen. But if you want to animate a fireball circling around a character, you want to position the fireball relative to that character, meaning the center of that character's bounding box will be your origin.

Note that every vector space is an affine space over itself: we already know how to add vectors to vectors, and we can define subtraction of vectors by combining addition and scalar multiplication:

uv:=u+(1v).u - v \Defeq u + (-1\cdot v).