rye
Version:
JavaScript implemetation of Galois (finite) fields algebra for academic purposes
818 lines (698 loc) • 28.3 kB
JavaScript
(function(global) {
// --------------------------------------------------------------------
// Auxiliary functions
// --------------------------------------------------------------------
function baseExpand(num, base) {
if (num < base) {
return [num];
}
var modulo = num % base;
var result = baseExpand((num - modulo) / base, base);
result.unshift(modulo);
return result;
}
function baseContract(powers, base, accumulator) {
accumulator = accumulator || 1;
if(powers.length <= 0) {
return 0;
}
var result = powers[0] * accumulator;
accumulator *= base;
return (result + baseContract(powers.slice(1), base, accumulator));
}
// --------------------------------------------------------------------
// IntegerRing class
// This class represents the ring of integers. Its elements can be
// added, multiplied or divided by each other. Each element has also
// the norm property, and it can be inversed.
// The elements of this ring are just integer numbers.
// --------------------------------------------------------------------
function IntegerRing() {
// --------------------------------------------------------------------
// Factor set function
// Mnemonics: x -> { [y mod x] }
// This function returns elements of the ring by module of given element
// --------------------------------------------------------------------
this.factorSet = function(element) {
// Calculating the order of factor ring
var order = this.norm(element);
// Doing a check that element is not null
if(order === -Infinity) {
throw 'Factorization by zero!'
}
// Allocating an array of elements
var set = new Array(order);
// Filling it with numbers [0..x]
var i = 0;
for(i=0; i<order; ++i) {
set[i] = i;
}
// Returning result
return set;
};
// --------------------------------------------------------------------
// Equality function
// Mnemonics: (x,y) -> x==y
// This function compares two elements of the ring.
// --------------------------------------------------------------------
this.equal = function(x, y) {
return (x === y);
};
// --------------------------------------------------------------------
// Norm function
// Mnemonics: x -> |x|
// This function returns the norm of the given element. The norm is just
// an absolute value of number or minus infinity if number is null
// --------------------------------------------------------------------
this.norm = function(x) {
if(x === 0) {
// The norm of null number is minus infinity
return -Infinity;
}
// Returning absolute value
return Math.abs(x);
};
// --------------------------------------------------------------------
// Addition function
// Mnemonics: (x,y) -> x+y
// This function adds two elements of the ring
// --------------------------------------------------------------------
this.addition = this.add = function(x, y) {
return x+y;
};
// --------------------------------------------------------------------
// Multiplication function
// Mnemonics: (x,y) -> x*y;
// This function multiplicates two elements of the ring.
// --------------------------------------------------------------------
this.multiplication = this.mul = function(x, y) {
return x*y;
};
// --------------------------------------------------------------------
// Opposite element function
// Mnemonics: x -> -x
// This function returns inversed(in terms of addition) version of
// the given element.
// --------------------------------------------------------------------
this.opposite = this.opp = function(x) {
return -x;
};
// --------------------------------------------------------------------
// Modulo function
// Mnemonics: (x,y) -> x mod y
// This function retuns the modulo of division one integer to another.
// --------------------------------------------------------------------
this.modulo = this.mod = function(x, y) {
if( this.norm(y) === -Infinity) {
throw 'Division by zero!';
}
return x % y;
}
// --------------------------------------------------------------------
// Latex string function
// This function returns latex representation of the given integer
// --------------------------------------------------------------------
this.toLatex = function(x) {
return x.toString();
};
}
// --------------------------------------------------------------------
// PolynomRing class
// This class represents a ring of polynoms with coefficients from
// given field. 'Ring' means that this structure provides such operations:
// 1. Addition
// 2. Multiplication
// 3. Inversion in terms of addition(finding opposite element)
// All functions operate with instances of the Polynom class(not indexes
// like in fields classes, because ring is a infinite structure!).
// --------------------------------------------------------------------
function PolynomRing(field) {
// The field that contains coefficients values
this.field = field;
// --------------------------------------------------------------------
// This function constructs a polynom with coefficients from current
// field. Note that this polynom is ummutable, e.g. you can't change
// it's coefficients values!
// The first parameter is an array of indexes of this field's elements.
// --------------------------------------------------------------------
this.polynom = function (coefficients) {
// If coefficients are not defined then the polynom is null
if(coefficients === undefined || coefficients.length === 0) {
coefficients = [0];
}
// Creating an empty object
var polynomInstance = {};
// The ring to which this polynom belongs
polynomInstance.ring = this;
// The field in which coefficients are contained
polynomInstance.field = this.field;
// Calculating the degree of the polynom
var _degree = coefficients.length - 1;
// Skip all high-order nulls
while (_degree >= 0 && coefficients[_degree] === this.field.nullElement()) {
--_degree;
}
// The degree of the null polynom is minus infinity
if (_degree === -1) {
_degree = -Infinity;
}
// Copying indexes to the private array
var _coefficients = coefficients.slice(0,
(_degree === -Infinity) ? 1 : (_degree + 1));
// This function returns the degree of the polynom
polynomInstance.degree = function() {
return _degree;
};
// This function returns the array of the coefficients
polynomInstance.coefficients = polynomInstance.coefs = function() {
// Returning the copy of the original array
return _coefficients.slice();
};
// This function returns the value of specified coefficient
polynomInstance.coefficient = polynomInstance.coef = function(index) {
// The null polynom has null coefficients
if(_degree === -Infinity) {
return this.field.nullElement();
}
// We shouldn't throw error in case of bad index, because other
// coefficients in fact are null.
if (index >= 0 && index <= _degree) {
return _coefficients[index];
} else {
return this.field.nullElement();
}
};
// This function applicates given value to the polynom, i.e. it returns
// a value of the polynom in the specified point
polynomInstance.application = polynomInstance.app = function(value) {
// The value of the null polynom is null
if(_degree === -Infinity) {
return this.field.nullElement();
}
// This will hold result
var result = this.field.nullElement();
// The current power of x
var valuePower = this.field.oneElement();
// Calculating the sum of monomials applicated with this value
var currentPower = 0;
for (currentPower = 0; currentPower <= _degree; ++currentPower) {
result = this.field.add(result, this.field.mul(valuePower, this.coef(currentPower)));
// Calculating the next power of x
valuePower = this.field.mul(valuePower, value);
}
return result;
};
// Returning constructed object
return polynomInstance;
};
// --------------------------------------------------------------------
// Factor set function
// Mnemonics: (f) -> { [g(x) mod f(x)] }
// This function returns factor set of the ring by module of given
// element.
// --------------------------------------------------------------------
this.factorSet = function(element) {
// We cannot divide by the null polynom
if(element.degree() === -Infinity) {
throw 'Factorization by null element';
}
// Calculating the degree of the factor set
var order = Math.pow(this.field.order, element.degree());
// Allocating the array for set
var set = new Array(order);
var i=0;
for(i=0; i<order; ++i) {
// Filling the set
set[i] = this.polynom( baseExpand(i, this.field.order) );
}
return set;
};
// --------------------------------------------------------------------
// Equality function
// Mnemonics: (f,g) -> (f(x) == g(x))
// This function checks if two polynoms are equal
// --------------------------------------------------------------------
this.equal = function(f, g) {
// Checking that polynoms have the same field
if(f.field.order !== g.field.order) {
return false;
}
// Checking polynoms degrees
if(f.degree() !== g.degree()) {
return false;
}
// Checking the null polynom
var i = f.degree();
if(i === -Infinity) {
return true;
}
// Checking coefficients equality
for(; i >= 0; --i) {
if( f.coef(i) !== g.coef(i) ) {
return false;
}
}
return true;
};
// --------------------------------------------------------------------
// Euclidean norm function
// Mnemonics: f(x) -> deg(f(x))
// This function returns euclidean norm of the given element. For
// polynoms it's just a degree of a polynom.
// --------------------------------------------------------------------
this.norm = function(f) {
return f.degree();
};
// --------------------------------------------------------------------
// Addition function
// Mnemonics: (f(x), g(x)) -> (f(x) + g(x))
// This funcion adds two polynoms. Note that f and g are instances of
// internal class polynom, but not indexes, cause ring is an infinite
// structure
// --------------------------------------------------------------------
this.addition = this.add = function(f, g) {
// The degree is maximum degree of two polynoms
var degree = Math.max(f.degree(), g.degree());
// The case when both polynoms are null
if (degree === -Infinity) {
return this.polynom();
}
// Allocating an array for coefficients
var sumCoefs = new Array(degree + 1);
var i;
for (i = 0; i <= degree; ++i) {
// Calculate sum of the coeffients using field arithmetics
sumCoefs[i] = this.field.add(f.coef(i), g.coef(i));
}
// Return new polynom with calculated coefficients
return this.polynom(sumCoefs);
};
// --------------------------------------------------------------------
// Multiplication function
// Mnemonics: (f(x), g(x)) -> (f(x) * g(x))
// This function calculates the product of two polynoms
// --------------------------------------------------------------------
this.multiplication = this.mul = function(f, g) {
// Calculate the degree of product polynom (sum of degrees)
var degree = f.degree() + g.degree();
// The case when at least one of polynoms is null
if (degree === -Infinity) {
return this.polynom();
}
// Allocate memory for product coefficients
var productCoefs = new Array(degree + 1);
var i, j;
for (i = 0; i <= degree; ++i) {
var currentCoefficient = 0;
for (j = 0; j <= i; ++j) {
// Calculating coefficient using formula sum(i=1..n+m) f(j)*g(i-j)
currentCoefficient = field.add(
field.mul(f.coef(j), g.coef(i - j)),
currentCoefficient);
}
// Assigning current coefficient
productCoefs[i] = currentCoefficient;
}
// Returning new polynom with calculated coefficients
return this.polynom(productCoefs);
};
// --------------------------------------------------------------------
// Opposite function
// Mnemonics: f(x) -> -f(x)
// This function returns an opposite polynom to the specified
// --------------------------------------------------------------------
this.opposite = this.opp = function(f) {
var self = this;
// Coefficients of the opposite polynom are also opposite
var oppCoeffs = f.coefficients().map(function(coefficient) {
return self.field.opposite(coefficient);
});
return this.polynom(oppCoeffs);
};
// --------------------------------------------------------------------
// Module function
// Mnemonics: (f(x), g(x)) -> (f(x) mod g(x))
// This function calculates the module of division one polynom to another
// --------------------------------------------------------------------
this.modulo = this.mod = function(f, g) {
return this.divmod(f,g) [1];
};
// --------------------------------------------------------------------
// Integer division function
// Mnemonics: (f(x), g(x)) -> (f(x) div g(x))
// This function calculates the integer division one polynom to another
// --------------------------------------------------------------------
this.division = this.div = function(f, g) {
return this.divmod(f,g) [0];
};
// --------------------------------------------------------------------
// Full division function
// Mnemonics: (f(x), g(x)) -> [f(x) div g(x), f(x) mod g(x))]
// This function calculates the full division
// --------------------------------------------------------------------
this.divmod = function(f, g, div) {
// Calculating a degree of each polynom
var degreeF = f.degree();
var degreeG = g.degree();
// Integer division part
if(div === undefined) {
div = [];
}
// When degree of f (divident) is less than degree of g (divisor) than
// modulo is simply f
if (degreeF < degreeG) {
return [this.polynom(div), f];
}
// We can't divide by the null polynom
if( degreeG === -Infinity) {
throw 'Division by zero element of the ring!';
}
// Constructs an array for divisor polynom
var divisor = Array(degreeF + 1);
// Now we are trying to build polynom which will decrease the divident
// degree by one. To do that we shift divisor and multiply all its
// coefficient by special element(to fill highest coefficient of the
// divident(f) with null.
div.unshift(field.mul(f.coef(degreeF),
field.inv(g.coef(degreeG))));
var i;
for (i = 0; i <= degreeF; ++i) {
if (i >= degreeF - degreeG) {
var j = (i - degreeF + degreeG);
// Doing all transformation with coefficients(in field
// arithmetics of course)
var coef = field.mul(g.coef(j), div[0]);
divisor[i] = field.opp(coef);
}
// This will make shift
else {
divisor[i] = 0;
}
}
// Now adding this stuff to f and repeat this algorithm with a new,
// 'less-degreeful' divident
var dividedF = this.add(f, this.polynom(divisor));
// Don't forget to add nulls, if degree difference is more than one
if(dividedF.degree() !== -Infinity) {
var degreeDifference = f.degree() - dividedF.degree() - 1;
for(i = 0; i < degreeDifference; ++i) {
div.unshift(this.field.nullElement());
}
}
// Repeating algorithm
return this.divmod(dividedF, g, div);
};
// --------------------------------------------------------------------
// Latex string function
// This function returns latex representation of the given polynom
// Options:
// level - sets render depth
// modulo - include modulo or not
// --------------------------------------------------------------------
this.toLatex = function(f, options) {
// Parsing options
options = options || {};
options.level = (options.level === undefined) ? 1 : options.level;
options.modulo = (options.modulo === undefined) ? false : options.modulo;
// Options for the next level of rendering
var nextOptions = {
level : options.level-1,
modulo : options.modulo
};
// This will hold result
var latexStr = '';
if(f.degree() === -Infinity) {
// If polynom is null, just draw null element
return this.field.toLatex(this.field.nullElement(), nextOptions);
}
var monoms = [];
var i = 0;
for(i = 0; i <= f.degree(); ++i) {
// Will not render monoms with null coefficient
if(f.coef(i) !== this.field.nullElement() ) {
var currentPower = '';
if(i === 0) {
// Not render x at 0 degree
currentPower = '';
}
else if(i === 1) {
// Render only x at 1 degree
currentPower = 'x';
} else {
// Render powers of x at other degrees
currentPower = 'x^{'+i + '}';
}
var currentCoef = '';
if(options.level > 0) {
// If level is not null render cofficient completely
currentCoef = this.field.toLatex(f.coef(i), nextOptions);
} else {
if(f.coef(i) !== 1 || i == 0) {
// If level is null just render index
currentCoef = f.coef(i).toString();
}
}
// Constructing monom result
var currentMonom = currentCoef + currentPower;
monoms.unshift(currentMonom);
}
}
// The monoms are separated with plus
latexStr = monoms.join('+');
return latexStr;
};
}
// --------------------------------------------------------------------
// FactorRing class
// This class represents a ring that built as a factor of the given
// ring by module of pricipal ideal with given generator.
// --------------------------------------------------------------------
function FactorRing(ring, generator) {
// This will help us to access object inside a callback
var self = this;
// The ring that will be factorized
self.ring = ring;
// The base element of the ideal
self.generator = generator;
// Obtaining elements of the factor ring and it's order
var elementsTable = self.ring.factorSet(self.generator);
self.order = elementsTable.length;
// Allocating private data
var nullIndex = NaN; // The index of the null element
var oneIndex = NaN; // The index of the one element
// Allocating private tables. It will speed up some operations like
// finding an inverse element or opposite element
var additionTable = new Array(this.order);
var multiplicationTable = new Array(this.order);
var inverseTable = new Array(this.order);
var oppositeTable = new Array(this.order);
// --------------------------------------------------------------------
// Element-by-index function
// Mnemonics: i -> R(i)
// This function returns element of the ring by its index
// --------------------------------------------------------------------
self.element = function(i) {
return elementsTable[i];
};
// --------------------------------------------------------------------
// Index-by-element function
// Mnemonics: e -> I(e)
// This function returns the index of the given element
// --------------------------------------------------------------------
self.index = function(e) {
var i = 0;
for(i=0; i < this.order; ++i) {
if(self.ring.equal(e, self.element(i))) {
return i;
}
}
// Cannot find element!
return NaN;
};
// Now lets fill the addition table. Also we'll probably find
// the null element.
(function fillAdditionTable() {
var i = 0, j = 0;
for(i = 0; i < self.order; ++i) {
// Allocating row in current cell
additionTable[i] = new Array(self.order);
for(j = 0; j < self.order; ++j) {
// To be sure, that all table will be filled clearly.
additionTable[i][j] = NaN;
// Calculating sum element in terms of the ring
var sumIndex = self.index(
self.ring.mod(
self.ring.add(elementsTable[i], elementsTable[j]),
self.generator // Factorising
)
);
// Filling the table entry
additionTable[i][j] = sumIndex;
// To find null element we are using theorems:
// 1. a+a=a <=> a=1 (in ring)
// 2. There is only one null element in a ring
if( i === j && j === sumIndex) {
nullIndex = i;
}
}
}
}) ();
// Now lets fill the multiplication table. Also we'll probably find
// the one element.
(function fillMultiplicationTable() {
var i = 0, j = 0;
for(i = 0; i < self.order; ++i) {
// Allocating row in current cell
multiplicationTable[i] = new Array(self.order);
for(j = 0; j < self.order; ++j) {
// To be sure, that all table will be filled clearly
multiplicationTable[i][j] = NaN;
// Calculating multiplication in terms of the ring
var multIndex = self.index(
self.ring.mod(
self.ring.mul(elementsTable[i], elementsTable[j]),
self.generator // Factorising
));
// Filling the table entry
multiplicationTable[i][j] = multIndex;
// To find the one element using theorems:
// 1. a*a=a a!=0 <=> a=1
// 2. There is only one 'one' element(if exists)
if( i === j && j === multIndex && i !== nullIndex) {
oneIndex = i;
}
}
}
}) ();
// Now we are trying to fill the opposite and inverse table.
// Note, that not all the elements can be inversed(in this
// case the inversed index is NaN)
(function fillOppInvTables() {
var i = 0, j = 0;
for( i = 0; i < self.order; ++i) {
// Filling with NaNs
oppositeTable[i] = NaN;
inverseTable[i] = NaN;
for( j = 0; j < self.order; ++j) {
// Opposite element definition:
// b = (-a) <=> def a+b=b+a=0
if(additionTable[i][j] === nullIndex) {
oppositeTable[i] = j;
}
// Inverse element definition:
// b = 1/a <=> def b*a=1 (a!=0, b!=0)
if(multiplicationTable[i][j] === oneIndex) {
inverseTable[i] = j;
}
}
}
}) ();
// --------------------------------------------------------------------
// Null element index function
// Mnemonics: () -> (i that R(i)=0 (for each a a+R(i)=a mod B))
// This function returns the index of the null element
// --------------------------------------------------------------------
this.nullElement = function() {
return nullIndex;
};
// --------------------------------------------------------------------
// One element index function
// Mnemonics: () -> (i that R(i)=1 (for each a!=0 a*R(i)=a mod B))
// This function retruns the index of the one element
// --------------------------------------------------------------------
this.oneElement = function() {
return oneIndex;
};
// --------------------------------------------------------------------
// Addition function
// Mnemonics: (i,j) -> I(R(i) + R(j) mod B)
// This function adds two elements of the factor ring(by indexes)
// --------------------------------------------------------------------
this.addition = this.add = function(i, j) {
return additionTable[i][j];
};
// --------------------------------------------------------------------
// Multiplication function
// Mnemonics: (i,j) -> I(R(i)*R(j) mod B)
// This function multiplicates two elements of the factor ring (by
// indexes)
// --------------------------------------------------------------------
this.multiplication = this.mul = function(i, j) {
return multiplicationTable[i][j];
};
// --------------------------------------------------------------------
// Opposite element function
// Mnemonics: i -> (j that (R(i)+R(j) mod B) = R(0))
// This function returns the index of the opposite element of the given
// --------------------------------------------------------------------
this.opposite = this.opp = function(i) {
return oppositeTable[i];
};
// --------------------------------------------------------------------
// Inverse element function
// Mnemonics: i -> (j that (R(i)*R(j) mod B) = R(1))
// This function returns the index of the inversed element of the given
// --------------------------------------------------------------------
this.inverse = this.inv = function(i) {
return inverseTable[i];
};
// --------------------------------------------------------------------
// Latex string function
// This function returns latex representation of element of the ring.
// Options:
// level - sets render depth
// modulo - include modulo or not
// --------------------------------------------------------------------
this.toLatex = function(i, options) {
// Parsing options
options = options || {};
options.level = (options.level === undefined) ? 1 : options.level;
options.modulo = (options.modulo === undefined) ? false : options.modulo;
// Defining options for the next level of render
var nextOptions = {
level : options.level-1,
modulo : options.modulo
};
// If index is not a number just draw dash
if(isNaN(i)) {
return '-';
}
// If level is 0 than just draw index
if(options.level <= 0) {
return i.toString();
}
var latexStr = '';
// Rendering element of the ring
var elementStr = this.ring.toLatex(this.element(i), nextOptions);
// If module option is set draw modulo
if(options.modulo) {
// Rendering generator of the ideal
var generatorStr = this.ring.toLatex(this.generator, nextOptions);
// Appending to output
latexStr = '[' + elementStr + ']' + '_{' + generatorStr + '}';
} else {
latexStr = elementStr;
}
// Returning result
return latexStr;
};
}
// --------------------------------------------------------------------
// PrimeField class
// This class represents a prime field structure. It is actually a
// factorization of the ring of integers by given irreducible element.
// The irreducible element of this ring is just a prime number.
// So, in common case, this structure is not actually a field, but a
// factor ring.
// --------------------------------------------------------------------
function PrimeField(num) {
// Bulding the ring of integers
var ring = new IntegerRing();
// Factorizing it by given number
FactorRing.call(this, ring, num);
}
global.IntegerRing = IntegerRing;
global.PolynomRing = PolynomRing;
global.FactorRing = FactorRing;
global.PrimeField = PrimeField;
}) (typeof exports === 'undefined' ? this : exports);