Package Exports
- algebra
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (algebra) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
algebra
Vectors, Matrices; Real, Complex, Quaternion; custom groups and rings for Node.js
New: checkout matrices and vectors made of strings, with cyclic algebra.
NOTA BENE Imagine all code examples below as written in some REPL where expected output is documented as a comment.
Table Of Contents
Status
algebra is under development, but API should not change until version 1.0.
I am currently adding more tests and examples to achieve a stable version.
Many functionalities of previous versions are now in separated atomic packages:
- algebra-cyclic
- algebra-group
- algebra-ring
- cayley-dickson
- indices-permutations
- laplace-determinant
- matrix-multiplication
- multidim-array-index
- tensor-contraction
- tensor-permutation
Features
- Real, Complex, Quaternion, Octonion numbers.
- Vector and Matrix spaces over any field (included Real numbers, of course :).
- Expressive syntax.
- Everything is a Tensor.
- Immutable objects.
Installation
With npm do
npm install algebra
With bower do
bower install algebra
or use a CDN adding this to your HTML page
<script src="https://cdn.rawgit.com/fibo/algebra/master/dist/algebra.js"></script>
Quick start
This is a 60 seconds tutorial to get your hands dirty with algebra.
First of all, import algebra package.
var algebra = require('algebra')
Try it out
All code in the examples below should be contained into a single file, like test/quickStart.js.
Scalars
Use the Real numbers as scalars.
var R = algebra.Real
Every operator is implemented both as a static function and as an object method.
Static operators return raw data, while class methods return object instances.
Use static addition operator to add three numbers.
R.add(1, 2, 3) // 1 + 2 + 3 = 6
Create two real number objects: x = 2, y = -2
var x = new R(2)
var y = new R(-2)
The value r is the result of x multiplied by y.
// 2 * (-2) = -4
var r = x.mul(y)
r // Scalar { data: -4 }
// x and y are not changed
x.data // 2
y.data // -2
Raw numbers are coerced, operators can be chained when it makes sense. Of course you can reassign x, for example, x value will be 0.1: x -> x + 3 -> x * 2 -> x ^-1
// ((2 + 3) * 2)^(-1) = 0.1
x = x.add(3).mul(2).inv()
x // Scalar { data: 0.1 }
Comparison operators equal and notEqual are available, but they cannot be chained.
x.equal(0.1) // true
x.notEqual(Math.PI) // true
You can also play with Complexes.
var C = algebra.Complex
var z1 = new C([1, 2])
var z2 = new C([3, 4])
z1 = z1.mul(z2)
z1 // Scalar { data: [-5, 10] }
z1 = z1.conj().mul([2, 0])
z1.data // [-10, -20]
Vectors
Create vector space of dimension 2 over Reals.
var R2 = algebra.VectorSpace(R)(2)
Create two vectors and add them.
var v1 = new R2([0, 1])
var v2 = new R2([1, -2])
// v1 -> v1 + v2 -> [0, 1] + [1, -2] = [1, -1]
v1 = v1.add(v2)
v1 // Vector { data: [1, -1] }
Matrices
Create space of matrices 3 x 2 over Reals.
var R3x2 = algebra.MatrixSpace(R)(3, 2)
Create a matrix.
// | 1 1 |
// m1 = | 0 1 |
// | 1 0 |
//
var m1 = new R3x2([1, 1,
0, 1,
1, 0])
Multiply m1 by v1, the result is a vector v3 with dimension 3. In fact we are multiplying a 3 x 2 matrix by a 2 dimensional vector, but v1 is traited as a column vector so it is like a 2 x 1 matrix.
Then, following the row by column multiplication law we have
// 3 x 2 by 2 x 1 which gives a 3 x 1
// ↑ ↑
// +------+----→ by removing the middle indices.
//
// | 1 1 |
// v3 = m1 * v1 = | 0 1 | * [1 , -1] = [0, -1, 1]
// | 1 0 |
var v3 = m1.mul(v1)
v3.data // [0, -1, 1]
Let's try with two square matrices 2 x 2.
var R2x2 = algebra.MatrixSpace(R)(2, 2)
var m2 = new R2x2([1, 0,
0, 2])
var m3 = new R2x2([0, -1,
1, 0])
m2 = m2.mul(m3)
m2 // Matrix { data: [0, -1, 2, 0] }
Since m2 is a square matrix we can calculate its determinant.
m2.determinant // Scalar { data: 2 }
API
About operators
All operators are implemented as static methods and as object methods. In both cases, operands are coerced to raw data. As an example, consider addition of vectors in a plane.
var R2 = algebra.R2
var vector1 = new R2([1, 2])
var vector2 = new R2([3, 4])
The following static methods, give the same result: [4, 6]
.
R2.addition(vector1, [3, 4])
R2.addition([1, 2], vector2)
R2.addition(vector1, vector2)
The following object methods, give the same result: a vector instance with data [4, 6]
.
var vector3 = vector1.addition([3, 4])
var vector4 = vector1.addition(vector2)
R2.equal(vector3, vector4) // true
Operators can be chained and accept multiple arguments when it makes sense.
vector1.addition(vector1, vector1).equality([3, 6]) // true
Objects are immutable
vector1.data // still [1, 2]
Cyclic
Cyclic(elements)
Create an algebra cyclic ring, by passing its elements. The elements are provided as a string or an array, which lenght must be a prime number. This is necessary, otherwise the result would be a wild land where you can find zero divisor beasts.
Let's create a cyclic ring containing lower case letters, numbers and the blank char. How many are they? They are 26 + 10 + 1 = 37, that is prime! We like it.
var Cyclic = algebra.Cyclic
// The elements String or Array length must be prime.
var elements = ' abcdefghijklmnopqrstuvwyxz0123456789'
var Alphanum = Cyclic(elements)
Operators derive from modular arithmetic
var a = new Alphanum('a')
Alphanum.addition('a', 'b') // 'c'
You can also create element instances, and do any kind of operations.
var x = new Alphanum('a')
var y = x.add('c', 'a', 't')
.mul('i', 's')
.add('o', 'n')
.sub('t', 'h', 'e')
.div('t', 'a', 'b', 'l', 'e')
y.data // 's'
Yes, they are scalars so you can build vector or matrix spaces on top of them.
var VectorStrings2 = algebra.VectorSpace(Alphanum)(2)
var MatrixStrings2x2 = algebra.MatrixSpace(Alphanum)(2)
var vectorOfStrings = new VectorStrings2(['o', 'k'])
var matrixOfStrings = new MatrixStrings2x2(['c', 'o',
'o', 'l'])
matrixOfStrings.mul(vectorOfStrings).data // ['x', 'y']
Note that, in the particular example above, since the matrix is simmetric it commutes with the vector, hence changing the order of the operands the result is still the same.
vectorOfStrings.mul(matrixOfStrings).data // ['x', 'y']
CompositionAlgebra
A composition algebra is one of ℝ, ℂ, ℍ, O: Real, Complex, Quaternion, Octonion. A generic function is provided to iterate the Cayley-Dickson construction over any field.
CompositionAlgebra(field[, num])
- num can be 1, 2, 4 or 8
Let's use for example the algebra.Boole
which implements Boolean Algebra
by exporting an object with all the stuff needed by algebra-ring npm package.
var CompositionAlgebra = algebra.CompositionAlgebra
var Boole = algebra.Boole
var Bit = CompositionAlgebra(Boole)
Bit.contains(false) // true
Bit.contains(4) // false
var bit = new Bit(true)
Bit.addition(false).data // true
Not so exciting, let's build something more interesting. Let's pass a second parameter, that is used to build a Composition algebra over the given field. It is something experimental also for me, right now I am writing this but I still do not know how it will behave. My idea (idea feliz) is that
A byte is an octonion of bits
Maybe we can discover some new byte operator, taken from octonion rich algebra structure. Create an octonion algebra over the binary field, a.k.a Z2 and create the eight units.
// n must be a power of two
var Byte = CompositionAlgebra(Boole, 8)
// Use a single char var for better indentation.
var t = true
var f = false
var byte1 = new Byte([t, t, t, t, t, t, t, t])
var byte2 = new Byte([t, t, t, t, t, t, t, t])
var byte3 = new Byte([t, t, t, t, t, t, t, t])
var byte4 = new Byte([t, t, t, t, t, t, t, t])
var byte5 = new Byte([t, t, t, t, t, t, t, t])
var byte6 = new Byte([t, t, t, t, t, t, t, t])
var byte7 = new Byte([t, t, t, t, t, t, t, t])
var byte8 = new Byte([t, t, t, t, t, t, t, t])
The first one corresponds to one, while the rest are immaginary units, but since the underlying field is Z2, -1 corresponds to 1.
bytet.mul(bytet).data // [t, t, t, t, t, t, t, t]
byte2.mul(byte2).data // [t, t, t, t, t, t, t, t]
byte3.mul(byte3).data // [t, t, t, t, t, t, t, t]
byte4.mul(byte4).data // [t, t, t, t, t, t, t, t]
byte5.mul(byte5).data // [t, t, t, t, t, t, t, t]
byte6.mul(byte6).data // [t, t, t, t, t, t, t, t]
byte7.mul(byte7).data // [t, t, t, t, t, t, t, t]
byte8.mul(byte8).data // [t, t, t, t, t, t, t, t]
Keeping in mind that Byte space defined above is an algebra, i.e. it has composition laws well defined, you maybe already noticed that, for example byte2 could be seen as corresponding to 4, but in this strange structure we created, 4 * 4 = 2.
You can play around with this structure.
var max = byte1.add(byte2).add(byte3).add(byte4)
.add(byte5).add(byte6).add(byte7).add(byte8)
max.data // [t, t, t, t, t, t, t, t]
Scalar
The scalars are the building blocks, they are the elements you can use to create vectors, matrices, tensors. They are the underneath set enriched with a ring structure which consists of two binary operators that generalize the arithmetic operations of addition and multiplication. A ring that has the commutativity property is called abelian (in honour to Abel) or also a field.
Ok, let's make a simple example. Real numbers, with common addition and multiplication are a scalar field.
Scalar attributes
Scalar.one
Is the neutral element for multiplication operator.
Scalar.zero
Is the neutral element for addition operator.
Scalar order
It is always 0 for scalars, see also Tensor order.
Scalar.order
scalar.order
scalar.data
Scalar operators
Scalar set operators
Scalar.contains(scalar1, scalar2[, scalar3, … ])
scalar1.belongsTo(Scalar)
Scalar equality
Scalar.equality(scalar1, scalar2)
scalar1.equality(scalar2)
Scalar disequality
Scalar.disequality(scalar1, scalar2)
scalar1.disequality(scalar2)
Scalar addition
Scalar.addition(scalar1, scalar2[, scalar3, … ])
scalar1.addition(scalar2[, scalar3, … ])
Scalar subtraction
Scalar.subtraction(scalar1, scalar2[, … ])
scalar1.subtraction(scalar2[, scalar3, … ])
Scalar multiplication
Scalar.multiplication(scalar1, scalar2[, scalar3, … ])
scalar1.multiplication(scalar2[, scalar3, … ])
Scalar division
Scalar.division(scalar1, scalar2[, scalar3, … ])
scalar1.division(scalar2[, scalar3, … ])
Scalar negation
Scalar.negation(scalar)
scalar.negation()
Scalar inversion
Scalar.inversion(scalar)
scalar.inversion()
Scalar conjugation
Scalar.conjugation(scalar)
scalar.conjugation()
Real
Inherits everything from Scalar.
var Real = algebra.Real
Real.addition(1, 2) // 3
var pi = new Real(Math.PI)
var twoPi = pi.mul(2)
Real.subtraction(twoPi, 2 * Math.PI) // 0
Complex
Inherits everything from Scalar.
It is said the Gauss brain is uncommonly big and folded, much more than the Einstein brain (both are conserved and studied). Gauss was one of the biggest mathematicians and discovered many important results in many mathematic areas. One of its biggest intuitions, in my opinion, was to realize that the Complex number field is geometrically a plane. The Complex numbers are an extension on the Real numbers, they have a real part and an imaginary part. The imaginary numbers, as named by Descartes later, were discovered by italian mathematicians Cardano, Bombelli among others as a trick to solve third order equations.
Complex numbers are a goldmine for mathematics, they are incredibly rich of deepest beauty: just as a divulgative example, take a look to the Mandelbrot set, but please trust me, this is nothing compared to the divine nature of Complex numbers.
The first thing I noticed when I started to study the Complex numbers is conjugation. Every Complex number has its conjugate, that is its simmetric counterparte respect to the Real numbers line.
var Complex = algebra.Complex
var complex1 = new Complex([1, 2])
complex1.conjugation() // Complex { data: [1, -2] }
Quaternion
Inherits everything from Scalar.
Octonion
Inherits everything from Scalar.
Common spaces
R
The real line.
It is in alias of Real.
var R = algebra.R
R2
The real plane.
var R2 = algebra.R2
It is in alias of VectorSpace(Real)(2)
.
R3
The real space.
var R3 = algebra.R3
It is in alias of VectorSpace(Real)(3)
.
R2x2
Real square matrices of rank 2.
var R2x2 = algebra.R2x2
It is in alias of MatrixSpace(Real)(2)
.
C
The complex numbers.
It is in alias of Complex.
var C = algebra.C
H
Usually it is used the H in honour of Sir Hamilton.
It is in alias of Quaternion.
var H = algebra.H
Vector
A Vector extends the concept of number, since it is defined as a tuple
of numbers.
For example, the Cartesian plane
is a set where every point has two coordinates, the famous (x, y)
that
is in fact a vector of dimension 2.
A Scalar itself can be identified with a vector of dimension 1.
We have already seen an implementation of the plain: R2.
If you want to find the position of an airplain, you need latitute, longitude but also altitude, hence three coordinates. That is a 3-ple, a tuple with three numbers, a vector of dimension 3.
An implementation of the vector space of dimension 3 over reals is given by R3.
A Vector class inherits everything from Tensor.
VectorSpace(Scalar)(dimension)
Vector dimension
Strictly speaking, dimension of a Vector is the number of its elements.
Vector.dimension
It is a static class attribute.
R2.dimension // 2
R3.dimension // 3
vector.dimension
It is also defined as a static instance attribute.
var vector = new R2([1, 1])
vector.dimension // 2
Vector norm
The norm, at the end, is the square of the vector length: the good old Pythagorean theorem. It is usually defined as the sum of the squares of the coordinates. Anyway, it must be a function that, given an element, returns a positive real number. For example in Complex numbers it is defined as the multiplication of an element and its conjugate: it works as a well defined norm. It is a really important property since it shapes a metric space. In the Euclidean topology it gives us the common sense of space, but it is also important in other spaces, like a functional space. In fact a norm gives us a distance defined as its square root, thus it defines a metric space and hence a topology: a lot of good stuff.
Vector.norm()
Is a static operator that returns the square of the lenght of the vector.
R2.norm([3, 4]).data // 25
vector.norm
This implements a static attribute that returns the square of the length of the vector instance.
var vector = new R2([1, 2])
vector.norm.data // 5
Vector addition
Vector.addition(vector1, vector2)
R2.addition([2, 1], [1, 2]) // [3, 3]
vector1.addition(vector2)
var vector1 = new R2([2, 1])
var vector2 = new R2([2, 2])
var vector3 = vector1.addition(vector2)
vector3 // Vector { data: [4, 3] }
Vector cross product
It is defined only in dimension three. See Cross product on wikipedia.
Vector.crossProduct(vector1, vector2)
R3.crossProduct([3, -3, 1], [4, 9, 2]) // [-15, 2, 39]
vector1.crossProduct(vector2)
var vector1 = new R3([3, -3, 1])
var vector2 = new R3([4, 9, 2])
var vector3 = vector1.crossProduct(vector2)
vector3 // Vector { data: [-15, 2, 39] }
Matrix
A Matrix class inherits everything from Tensor.
MatrixSpace(Scalar)(numRows[, numCols])
Matrix.isSquare
Matrix.numCols
Matrix.numRows
Matrix multiplication
Matrix.multiplication(matrix1, matrix2)
matrix1.multiplication(matrix2)
Matrix inversion
It is defined only for square matrices which determinant is not zero.
Matrix.inversion(matrix)
matrix.inversion
Matrix determinant
It is defined only for square matrices.
Matrix.determinant(matrix)
matrix.determinant
Matrix adjoint
Matrix.adjoint(matrix1)
matrix.adjoint
Tensor
TensorSpace(Scalar)(indices)
Tensor.one
Tensor.zero
tensor.data
Tensor indices
Tensor.indices
tensor.indices
Tensor order
It represents the number of varying indices.
- A scalar has order 0.
- A vector has order 1.
- A matrix has order 2.
Tensor.order
tensor.order
Tensor.contains(tensor1, tensor2[, tensor3, … ])
Tensor equality
var T2x2x2 = TensorSpace(Real)([2, 2, 2])
var tensor1 = new T2x2x2([1, 2, 3, 4, 5, 6, 7, 8])
var tensor2 = new T2x2x2([2, 3, 4, 5, 6, 7, 8, 9])
Tensor.equality(tensor1, tensor2)
T2x2x2.equality(tensor1, tensor1) // true
T2x2x2.equality(tensor1, tensor2) // false
tensor1.equality(tensor2)
tensor1.equality(tensor1) // true
tensor1.equality(tensor2) // false