As we discussed in a previous post^{[1]} complex numbers are one way we can extend the concept of number into two dimensions. A more general approach is that of vectors; mathematical entities with the properties of direction and length, or magnitude, which generalise the concept of number to any number of dimensions. We typically represent them as a list of Cartesian coordinates, or elements, representing the distance of the vector from zero along each axis. For example
Given a vector expressed in the usual coordinate form we can recover its magnitude by taking the square root of the sum of the squares of the elements. For the above vector, this is
To add or subtract a pair of vectors we add or subtract the elements in the same positions in each vector. Given vectors \(\mathbf{v}\) and \(\mathbf{w}\)
In terms of distance and direction addition means starting at the vector of all zeros, written as \(\mathbf{0}\), travel the direction and distance of one of the vectors followed by those of the other, as illustrated by figure 1 which represents the sum
Vector multiplication is a little odd compared to that of real and complex numbers in that it doesn't result in a vector but rather the scalar sum of the products of each pair of elements in the same position in each vector
Another is that if
As usual a
We also have a set of conversion methods, with
Next, we have the construction of
Finally, we have construction from an object. The number of dimensions are set to the
Note that our overload mechanism dispatches calls to
The first implementation of the vector operations we have defined above is the magnitude,
Next, scalar multiplication and division are given in listing 7.
Finally, we have vector addition and multiplication as shown in listing 8.
The implementations of the remaining operations are given in Vector.js.
That's pretty much all there is to say about elementary vector arithmetic. However, things get decidedly more interesting when matrices are thrown into the mix, as we shall find in the next post...
\[
\mathbf{v} =
\begin{pmatrix}
v_0\\
v_1\\
v_2
\end{pmatrix}
\]
means go \(v_0\) units in the first direction, \(v_1\) units in the second and \(v_2\) units in the third and you'll have reached your destination. Nevertheless, if I were to pop outside, point in some direction and say "one kilometre thataway" then, aside from getting some odd looks from the neighbours, I'd have described a vector just as effectively.Given a vector expressed in the usual coordinate form we can recover its magnitude by taking the square root of the sum of the squares of the elements. For the above vector, this is
\[
\mathbf{v} = \sqrt{v_0^2 + v_1^2 + v_2^2}
\]
The direction is a bit harder to nail down since our vectors may have several dimensions so we can't use a single angle like we did for the argument of a complex number. Typically, we simply use the vector that has a magnitude of one, a unit vector, and points in the same direction
\[
\hat{\mathbf{v}} = \frac{\mathbf{v}}{\mathbf{v}}
\]
The basic rules of arithmetic for vectors are fairly straightforward. To multiply or divide a vector by a regular number, or scalar, we simply repeat the operator for each of its elements. For example, given a vector \(\mathbf{v}\) and a scalar \(x\) we have
\[
\begin{align*}
(\mathbf{v} \times x)_i &= v_i \times x\\
(x \times \mathbf{v})_i &= v_i \times x\\
\\
(\mathbf{v} \div x)_i &= v_i \div x
\end{align*}
\]
where a subscript of \(i\) means the \(i^{th}\) element. Note that these operations preserve the direction of the vector for positive scalars and reverse it for negative scalars whilst multiplying or dividing the magnitude by the absolute value of the scalar.
\[
\begin{align*}
\mathrm{v} \times x &= \left(\sum_i \left(v_i \times x\right)^2\right)^\frac{1}{2}\\
&= \left(\sum_i v_i^2 \times x^2\right)^\frac{1}{2}\\
&= \left(x^2 \times \sum_i v_i^2\right)^\frac{1}{2}\\
&= x \times \left(\sum_i v_i^2\right)^\frac{1}{2}
\end{align*}
\]
where \(\sum\) is the summation sign.To add or subtract a pair of vectors we add or subtract the elements in the same positions in each vector. Given vectors \(\mathbf{v}\) and \(\mathbf{w}\)
\[
\begin{align*}
(\mathbf{v} + \mathbf{w})_i &= v_i + w_i\\
(\mathbf{v}  \mathbf{w})_i &= v_i  w_i
\end{align*}
\]
Figure 1: Vector Addition
\[
\begin{pmatrix}
1\\
2
\end{pmatrix}
+
\begin{pmatrix}
1.5\\
1
\end{pmatrix}
=
\begin{pmatrix}
2.5\\
3
\end{pmatrix}
\]
with the first vector drawn in blue, the second in green and their sum in black. Note that subtraction simply means go in the opposite direction of the second vector.Vector multiplication is a little odd compared to that of real and complex numbers in that it doesn't result in a vector but rather the scalar sum of the products of each pair of elements in the same position in each vector
\[
\mathbf{v} \times \mathbf{w} = \sum_i v_i \times w_i
\]
This also has an interpretation in terms of direction and magnitude but it's not as simple as those we've already described. If we rotate the axes so that \(\mathbf{v}\) lies on the first axis then \(\mathbf{v} \times \mathbf{w}\) is the product of the first element of \(\mathbf{w}\) using the new axes and the magnitude of \(\mathbf{v}\). One consequence of this is that
\[
\mathbf{v} \times \mathbf{v} = \mathbf{v} \times \mathbf{v}
\]
which can trivially be confirmed using the definitions of these operations in terms of the vectors' elements.Another is that if
\[
\mathbf{v} \times \mathbf{w} = 0
\]
then \(\mathbf{v}\) and \(\mathbf{w}\) are at right angles to each other, or orthogonal.
A Vector Class
Given that vectors can be represented as arrays of coordinate elements, it makes sense to represent those with an array as is done by thestate
member of the ak.vector
class illustrated in listing 1.Listing 1: ak.vector
ak.VECTOR_T = 'ak.vector'; function Vector(){} Vector.prototype = {TYPE: ak.VECTOR_T, valueOf: function(){return ak.NaN;}}; ak.vector = function() { var v = new Vector(); var state = []; var arg0 = arguments[0]; constructors[ak.nativeType(arg0)](state, arg0, arguments); v.dims = function() {return state.length;}; v.at = function(i) {return state[Number(i)];}; v.toArray = function() {return state.slice(0);}; v.toString = function() {return '['+state.toString()+']';}; v.toExponential = function(d) { return '['+state.map(function(x){return x.toExponential(d);})+']'; }; v.toFixed = function(d) { return '['+state.map(function(x){return x.toFixed(d);})+']'; }; v.toPrecision = function(d) { return '['+state.map(function(x){return x.toPrecision(d);})+']'; }; return Object.freeze(v); }; var constructors = {};
As usual a
constructors
object is used to dispatch initialisation of the state
to appropriate constructor methods. The number of dimensions of the vector
is returned by the dims
method and access to its elements by at
, with the programming convention that the first element is indexed with zero rather than the mathematical convention of one.We also have a set of conversion methods, with
toArray
returning a JavaScript array containing the same elements as the vector
and string conversions equivalent to those supported by native Number
objects.
ak.vector Constructors
The most obvious candidate for constructingvector
objects is with native Array
objects, as shown in listing 2.Listing 2: ak.vector Array Constructor
constructors[ak.ARRAY_T] = function(state, arr) { var n = arr.length; var i; state.length = n; for(i=0;i<n;++i) state[i] = Number(arr[i]); };
Next, we have the construction of
vector
objects with a given dimension, illustrated in listing 3. By default the elements are set to zero, but if a number is passed as the second argument we set their value to it instead and if a function is passed we populate each element with the result of calling that function with its index.Listing 3: ak.vector Dimension Constructors
constructors[ak.NUMBER_T] = function(state, n, args) { var arg1 = args[1]; state.length = n; constructors[ak.NUMBER_T][ak.nativeType(arg1)](state, arg1); }; constructors[ak.NUMBER_T][ak.FUNCTION_T] = function(state, func) { var n = state.length; var i; for(i=0;i<n;++i) state[i] = Number(func(i)); }; constructors[ak.NUMBER_T][ak.NUMBER_T] = function(state, val) { var n = state.length; var i; val = Number(val); for(i=0;i<n;++i) state[i] = val; }; constructors[ak.NUMBER_T][ak.UNDEFINED_T] = function(state) { var n = state.length; var i; for(i=0;i<n;++i) state[i] = 0; };
Finally, we have construction from an object. The number of dimensions are set to the
dims
member, or its result if it's a method, and the elements to the result of the at
method for their indices, as given in listing 4.Listing 4: ak.vector Object Constructor
constructors[ak.OBJECT_T] = function(state, obj) { var n = obj.dims; var i; n = (ak.nativeType(n)===ak.FUNCTION_T) ? Number(n()) : Number(n); state.length = n; for(i=0;i<n;++i) state[i] = Number(obj.at(i)); };
ak.vector Overloads
Compared to ordinary and complex numbers, vectors have relatively few overloaded operations as illustrated by listing 5.Listing 5: ak.vector Overloads
ak.overload(ak.abs, ak.VECTOR_T, abs); ak.overload(ak.neg, ak.VECTOR_T, neg); ak.overload(ak.add, [ak.VECTOR_T, ak.VECTOR_T], add); ak.overload(ak.dist, [ak.VECTOR_T, ak.VECTOR_T], dist); ak.overload(ak.div, [ak.VECTOR_T, ak.NUMBER_T], div); ak.overload(ak.eq, [ak.VECTOR_T, ak.VECTOR_T], eq); ak.overload(ak.mul, [ak.NUMBER_T, ak.VECTOR_T], mulRV); ak.overload(ak.mul, [ak.VECTOR_T, ak.NUMBER_T], mulVR); ak.overload(ak.mul, [ak.VECTOR_T, ak.VECTOR_T], mul); ak.overload(ak.ne, [ak.VECTOR_T, ak.VECTOR_T], ne); ak.overload(ak.sub, [ak.VECTOR_T, ak.VECTOR_T], sub);
Note that our overload mechanism dispatches calls to
ak
operations based upon the types of their arguments with ak.overload
adding a dispatch to its first argument for the type(s) identified by its second to the function given as its third.The first implementation of the vector operations we have defined above is the magnitude,
abs
, as given in listing 6.Listing 6: ak.vector Abs
function abs(v) { var s = 0; var n = v.dims(); var i, x; for(i=0;i<n;++i) { x = v.at(i); s += x*x; } return Math.sqrt(s); }
Next, scalar multiplication and division are given in listing 7.
Listing 7: ak.vector Scalar Multiplication And Division
function mulRV(r, v) { var n, i; v = v.toArray(); n = v.length; for(i=0;i<n;++i) v[i] *= r; return ak.vector(v); } function mulVR(v, r) { var n, i; v = v.toArray(); n = v.length; for(i=0;i<n;++i) v[i] *= r; return ak.vector(v); } function div(v, r) { var n, i; v = v.toArray(); n = v.length; for(i=0;i<n;++i) v[i] /= r; return ak.vector(v); }
Finally, we have vector addition and multiplication as shown in listing 8.
Listing 8: ak.vector Addition And Multiplication
function add(v0, v1) { var n, i; v0 = v0.toArray(); n = v0.length; if(v1.dims()!==n) throw new Error('dimensions mismatch in ak.vector add'); for(i=0;i<n;++i) v0[i] += v1.at(i); return ak.vector(v0); } function mul(v0, v1) { var s = 0; var n = v0.dims(); var i; if(v1.dims()!==n) throw new Error('dimensions mismatch in ak.vector mul'); for(i=0;i<n;++i) s += v0.at(i) * v1.at(i); return s; }
The implementations of the remaining operations are given in Vector.js.
Using ak.vector
Examples of the operations we have defined here are provided by program 1.
Program 1: Vector Operations



That's pretty much all there is to say about elementary vector arithmetic. However, things get decidedly more interesting when matrices are thrown into the mix, as we shall find in the next post...
Leave a comment