Source: polyrat.js

/*
 * rational.js - Javascript tools and libraries based around rational numbers.
 * Copyright (C) 2013 Dylan Ferris
 *
 * This file is part of rational.js.
 *
 * rational.js is free software: you may redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * rational.js is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with rational.js.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
// for nodejs
if (typeof rat !== 'object') {
  var rat = require('../src/rat.js').rat;
}
if (typeof alpha !== 'object') {
  var alpha = require('../src/alpha.js').alpha;
}
*/

/**
 * @class Rational Polynumber (of arbitrary dimension)
 * @name polyrat
 * @requires rat alpha
 */
var polyrat = {};

/**
 * Creates a new, empty polyrat
 *
 * @returns {polyrat} a new polynumber
 */
polyrat.create = function() {
    return [
        [], // multi-dimensional array of coefficients
        [] // array of integers to offset the exponents of each dimension
    ];
};

/**
 * Creates a new polynumber initialized with the given array of either integers or rats
 *
 * @param {Array} array of values (of type "rat" --- not yet)
 * @returns {polyrat} a new polynumber
 */
polyrat.fromValues = function(a) {
    var out = polyrat.create();
    out[0] = a.slice();

    var d = polyrat.countDimensions(out);

    out[1] = new Array(d);

    var i=d;
    while (i--) out[1][i] = 0;

    /*
    if (d===1) {
        for (var i in out[0]) {
            if (typeof out[0][i] !== 'object') out[0][i] = rat.fromDecimal(out[0][i]);
        }
    }
    else if (d===2) {
        for (var j in out[0]) {
            for (var i in out[0][j]) {
                if (typeof out[0][j][i] !== 'object') out[0][j][i] = rat.fromDecimal(out[0][j][i]);
            }
        }
    }
    else if (d===3) {
        for (var k in out[0]) {
            for (var j in out[0][k]) {
                for (var i in out[0][k][j]) {
                    if (typeof out[0][k][j][i] !== 'object') out[0][k][j][i] = rat.fromDecimal(out[0][k][j][i]);
                }
            }
        }
    }
    else {
        // recursive function?
        alert('unsupported dimension '+d);
    }
    */

    return polyrat.normalize(out, out);
};

/**
 * Add another variable/dimension to a polynumber
 *
 * @param {polyrat} out the receiving number
 * @param {rat} a the first operand
 * @param {Array} array of values (of type "rat")
 * @returns {polyrat} a new polynumber
 */
polyrat.mergeDimension = function(out, a, m) {
    out[0] = [a.slice()];
    out[1].push(0);
    for (var i in m) out[0].push(m[i]);
    return out;
};

/**
 * Evaluate a polynomial for a certain variable(s)
 *
 * @param {bigrat} out the receiving number
 * @param {polyrat} a the polynumber to evaluate
 * @param {Array} array of values (of type "bigrat") to plug in to the polynomial
 * @returns {bigrat} resulting big rational number
 */
polyrat.evaluate = function(out, a, m) {
    //var result = 0;
    bigrat.copy(out, bigrat.ZERO);
    var d = polyrat.countDimensions(a);
    if (d===0) {
        return '0';
    }
    else if (d===1) {
        for (var i in a[0]) {
            if (!a[0][i]) continue;
            //result += a[0][i] * Math.pow(m[0], a[1][0] + +i);
            var p = bigrat.fromInteger(a[0][i]);
            var pi = bigrat.pow(bigrat.create(), bigrat.fromInteger(m[0]), a[1][0] + +i);
            bigrat.mul(p, p, pi);
            bigrat.add(out, out, p);
        }
    }
    else if (d===2) {
        for (var j in a[0]) {
            for (var i in a[0][j]) {
                if (!a[0][j][i]) continue;
                //result += a[0][j][i] * Math.pow(m[0], a[1][1] + +i) * Math.pow(m[1], a[1][0] + +j);
                var p = bigrat.fromInteger(a[0][j][i]);
                var pi = bigrat.pow(bigrat.create(), bigrat.fromInteger(m[0]), a[1][1] + +i);
                var pj = bigrat.pow(bigrat.create(), bigrat.fromInteger(m[1]), a[1][0] + +j);
                bigrat.mul(p, p, pi);
                bigrat.mul(p, p, pj);
                bigrat.add(out, out, p);
            }
        }
    }
    else if (d===3) {
        for (var k in a[0]) {
            for (var j in a[0][k]) {
                for (var i in a[0][k][j]) {
                    if (!a[0][k][j][i]) continue;
                    //result += a[0][k][j][i] * Math.pow(m[0], a[1][2] + +i) * Math.pow(m[1], a[1][1] + +j) * Math.pow(m[2], a[1][0] + +k);
                    var p = bigrat.fromInteger(a[0][k][j][i]);
                    var pi = bigrat.pow(bigrat.create(), bigrat.fromInteger(m[0]), a[1][2] + +i);
                    var pj = bigrat.pow(bigrat.create(), bigrat.fromInteger(m[1]), a[1][1] + +j);
                    var pk = bigrat.pow(bigrat.create(), bigrat.fromInteger(m[1]), a[1][0] + +k);
                    bigrat.mul(p, p, pi);
                    bigrat.mul(p, p, pj);
                    bigrat.mul(p, p, pk);
                    bigrat.add(out, out, p);
                }
            }
        }
    }
    else {
        // recursive function?
        alert('unsupported dimension '+d);
    }
    //return out = bigrat.fromInteger(parseInt(result));
    return out;
};

/**
 * Calculates the derivates of a polynumber
 *
 * @param {polyrat} a polynumber to take the derivatives of
 * @returns {Array} array of polyrats, corresponding to the sub-derivatives of the input
 */
polyrat.derivatives = function(a) {
    // MF78 @ 10:00
    return [
//      polyrat.clone(a),
    ];
};

/**
 * Calls a function for each non-zero element in the polynumber
 *
 * @param {polyrat} a rational polynumber
 * @param {function} callback function
 */
polyrat.forEachNonZeroElement = function(a, callback) {
    var t = a.slice();
    var d = 0;
    while (typeof t[0] === 'object') {
        t = t[0].slice();
        d++;
    }
    for (var i=d+1; i>0; --i) {
        print(new Array(i).join('[0]'));
        t = eval('(a'+(new Array(i).join('[0]'))+')');
        for (var c in t) {
            for (var j=d+2; j>0; --j) {
                print(t[c]);
            }
        }
    }
    return '';
};

/**
 * Returns a string representation (polynomial format, in ascending order by degree, then alphabetically) -- not yet!
 *
 * @param {polyrat} a rational polynumber to represent as a string
 * @returns {String} string representation of the number
 */
polyrat.str = function(a) {
    var s = '';
    var vars = new alpha.iterator;

    var _i = vars.next();
    var _j = vars.next();
    var _k = vars.next();

    var d = polyrat.countDimensions(a);
    if (d===0) {
        s += a.toString();
    }
    else if (d===1) {
        for (var i in a[0]) {
            if (!a[0][i]) continue;
            if (s) s += ' ' + (a[0][i]<0?'-':'+') + ' ';
            else if (a[0][i]<0) s += '-';
            var c = Math.abs(a[0][i]);
            var i_power = a[1][0] + +i;
            if (c!==1||(i_power===0)) s += c;
            if (i_power!==0) s += _i + (i_power === 1 ? '' : '<sup>'+i_power+'</sup>');
        }
    }
    else if (d===2) {
        for (var j in a[0]) {
            for (var i in a[0][j]) {
                if (!a[0][j][i]) continue;
                if (s) s += ' ' + (a[0][j][i]<0?'-':'+') + ' ';
                else if (a[0][j][i]<0) s += '-';
                var c = Math.abs(a[0][j][i]);
                var i_power = a[1][1] + +i;
                var j_power = a[1][0] + +j;
                if (c!==1||(j_power===0&&i_power===0)) s += c;
                if (i_power!==0) s += _i + (i_power === 1 ? '' : '<sup>'+i_power+'</sup>');
                if (j_power!==0) s += _j + (j_power === 1 ? '' : '<sup>'+j_power+'</sup>');
            }
        }
    }
    else if (d===3) {
        for (var k in a[0]) {
            for (var j in a[0][k]) {
                for (var i in a[0][k][j]) {
                    if (!a[0][k][j][i]) continue;
                    if (s) s += ' ' + (a[0][k][j][i]<0?'-':'+') + ' ';
                    else if (a[0][k][j][i]<0) s += '-';
                    var c = Math.abs(a[0][k][j][i]);
                    var i_power = a[1][2] + +i;
                    var j_power = a[1][1] + +j;
                    var k_power = a[1][0] + +k;
                    if (c!==1||(k_power===0&&j_power===0&&i_power===0)) s += c;
                    if (i_power!==0) s += _i + (i_power === 1 ? '' : '<sup>'+i_power+'</sup>');
                    if (j_power!==0) s += _j + (j_power === 1 ? '' : '<sup>'+j_power+'</sup>');
                    if (k_power!==0) s += _k + (k_power === 1 ? '' : '<sup>'+k_power+'</sup>');
                }
            }
        }
    }
    else {
        alert('unsupported dimension '+d);
    }
    if (s==='') s = '0';
    return s;
};


/**
 * Evaluate a polynomial for a certain variable(s)
 *
 * @param {polyrat} a polynumber
 * @returns {String} javascript formula to evaluate the polynomial
 */
polyrat.getJSFormula = function(a) {
    var f = '';
    var d = polyrat.countDimensions(a);
    if (d===0) {
        return '0';
    }
    else if (d===1) {
        for (var i in a[0]) {
            if (!a[0][i]) continue;
            if (f) f += '+';
            f += a[0][i];
            var i_power = a[1][0] + +i;
            if (i_power===0) continue;
            else if (i_power===1) f += '*m[0]';
            else f += '*Math.pow(m[0],' + i_power + ')';
        }
    }
    else if (d===2) {
        for (var j in a[0]) {
            for (var i in a[0][j]) {
                if (!a[0][j][i]) continue;
                if (f) f += '+';
                f += a[0][j][i];
                var i_power = a[1][1] + +i;
                var j_power = a[1][0] + +j;
                if (i_power!==0) {
                    if (i_power===1) f += '*m[0]';
                    else f += '*Math.pow(m[0],' + i_power + ')';
                }
                if (j_power!==0) {
                    if (j_power===1) f += '*m[1]';
                    else f += '*Math.pow(m[1],' + j_power + ')';
                }
            }
        }
    }
    else if (d===3) {
        for (var k in a[0]) {
            for (var j in a[0][k]) {
                for (var i in a[0][k][j]) {
                    if (!a[0][k][j][i]) continue;
                    if (f) f += '+';
                    f += a[0][k][j][i];
                    var i_power = a[1][2] + +i;
                    var j_power = a[1][1] + +j;
                    var k_power = a[1][0] + +k;
                    if (i_power!==0) {
                        if (i_power===1) f += '*m[0]';
                        else f += '*Math.pow(m[0],' + i_power + ')';
                    }
                    if (j_power!==0) {
                        if (j_power===1) f += '*m[1]';
                        else f += '*Math.pow(m[1],' + j_power + ')';
                    }
                    if (k_power!==0) {
                        if (k_power===1) f += '*m[2]';
                        else f += '*Math.pow(m[2],' + k_power + ')';
                    }
                }
            }
        }
    }
    else {
        alert('unsupported dimension '+d);
    }
    if (f==='') f = '0';
    return f;
};

// this is used in getGLSLFormula as a quick hack, shouldn't actually be here...
String.prototype.repeat = function(n) {return new Array(n+1).join(this);}

/**
 * Evaluate a polynomial for a certain variable(s), modified to work in GLSL (to run on GPU)
 *
 * @param {polyrat} a polynumber
 * @returns {String} GLSL formula to evaluate the polynomial
 */
polyrat.getGLSLFormula = function(a) {
    var f = '';
    var d = polyrat.countDimensions(a);
    if (d===0) {
        return '0.0';
    }
    else if (d===1) {
        for (var i in a[0]) {
            if (!a[0][i]) continue;
            if (f) f += '+';
            f += a[0][i]+'.0';
            var i_power = a[1][0] + +i;
            if (i_power===0) continue;
            else if (i_power===1) f += '*m_0';
            else if (i_power>1) f += ('*m_0').repeat(i_power);
            else f += '*pow(1.0'+('*m_0').repeat(Math.abs(i_power))+',-1.0)';
        }
    }
    else if (d===2) {
        for (var j in a[0]) {
            for (var i in a[0][j]) {
                if (!a[0][j][i]) continue;
                if (f) f += '+';
                f += a[0][j][i]+'.0';
                var i_power = a[1][1] + +i;
                var j_power = a[1][0] + +j;
                if (i_power!==0) {
                    if (i_power===1) f += '*m_0';
                    else if (i_power>1) f += ('*m_0').repeat(i_power);
                    else f += '*pow(1.0'+('*m_0').repeat(Math.abs(i_power))+',-1.0)';
                }
                if (j_power!==0) {
                    if (j_power===1) f += '*m_1';
                    else if (j_power>1) f += ('*m_1').repeat(j_power);
                    else f += '*pow(1.0'+('*m_1').repeat(Math.abs(j_power))+',-1.0)';
                }
            }
        }
    }
    else {
        alert('unsupported dimension '+d);
    }
    if (f==='') f = '0.0';
    return f;
};

/**
 * Returns a string representation (JSON)
 *
 * @param {polyrat} a rational polynumber to represent as a string
 * @returns {String} JSON representation of the number
 */
polyrat.toJSON = function(a) {
    return JSON.stringify(a);
};

/**
 * Creates a polynumber from it's JSON representation
 *
 * @param {String} JSON representation of the number
 * @returns {polyrat} a rational polynumber
 */
polyrat.fromJSON = function(a) {
    return JSON.parse(a);
};

/**
 * Normalize a polynumber
 *
 * @param {polyrat} out the receiving number
 * @param {polyrat} a number to normalize
 * @returns {polyrat} out
 */
polyrat.normalize = function(out, a) {
    return out = a;
};

/**
 * Performs a function on each element of the two polyrats, outputting the results to a new polyrat
 *
 * @param {polyrat} out the receiving number
 * @param {polyrat} a the first operand
 * @param {polyrat} b the second operand
 * @param {function} operation to perform
 * @returns {polyrat} out
 */
polyrat.binary_operation = function(out, a, b, operation) {
    out = polyrat.create();
    var     a_dimensions = polyrat.countDimensions(a),
            b_dimensions = polyrat.countDimensions(b);
    if (a[1][i]!==b[1][i]) {
        alert('adding polyrats only works if the offsets are the same (for now)');
    }
    if (a_dimensions===1&&b_dimensions===1) {
        for (var i in a[0]) {
            out[0][i] = typeof b[0][i] === 'undefined' ? a[0][i] : operation(a[0][i], b[0][i]);
        }
        for (var i in b[0]) {
            if (typeof a[0][i] === 'undefined') out[0][i] = b[0][i];
        }
        for (var i in out[0]) {
            if (typeof a[0][i] === 'undefined' && typeof b[0][i] === 'undefined') out[0][i] = null;
        }
    }
    else {
        alert('unsupported dimension '+a_dimensions);
    }
    var i=a_dimensions;
    while (i--) out[1][i] = a[1][i];
    return out;
};

/**
 * Adds two polyrats
 *
 * @param {polyrat} out the receiving number
 * @param {polyrat} a the first operand
 * @param {polyrat} b the second operand
 * @returns {polyrat} out
 */
polyrat.add = function(out, a, b) {
    return polyrat.binary_operation(out, a, b,
        function(a, b){ return a + b; }
    );
};

/**
 * Subtracts two polyrats
 *
 * @param {polyrat} out the receiving number
 * @param {polyrat} a the first operand
 * @param {polyrat} b the second operand
 * @returns {polyrat} out
 */
polyrat.subtract = function(out, a, b) {
    return polyrat.binary_operation(out, a, b,
        function(a, b){ return a - b; }
    );
};

/**
 * Alias for {@link polyrat.subtract}
 * @function
 */
polyrat.sub = polyrat.subtract;

/**
 * Multiplies two polyrats
 *
 * @param {polyrat} out the receiving number
 * @param {polyrat} a the first operand
 * @param {polyrat} b the second operand
 * @returns {polyrat} out
 */
polyrat.multiply = function(out, a, b) {
    return out;
};

/**
 * Alias for {@link polyrat.multiply}
 * @function
 */
polyrat.mul = polyrat.multiply;

/**
 * Divides two polyrats
 *
 * @param {polyrat} out the receiving number
 * @param {polyrat} a the first operand
 * @param {polyrat} b the second operand
 * @returns {polyrat} out
 */
polyrat.divide = function(out, a, b) {
    return out;
};

/**
 * Alias for {@link polyrat.divide}
 * @function
 */
polyrat.div = polyrat.divide;

/**
 * Returns true when the first polyrat is equal to the second
 *
 * @param {polyrat} a the first operand
 * @param {polyrat} b the second operand
 * @returns {Bool} true when the two polyrats are equal
 */
polyrat.equals = function(a, b) {
    return a[0] === b[0] && a[1] === b[1];
};

/**
 * Count the number of dimensions of an array
 *
 * @param {polyrat} a number to count the dimensions of
 * @returns {Integer} number of dimensions
 */
polyrat.countDimensions = function(a) {
    return typeof a[0]==='object' ? polyrat._countDimensions(a[0], 1) : 0;
};

/**
 * Count the number of dimensions of an array recursively
 *
 * @param {polyrat} an array to count the dimensions of
 * @param {polyrat} c (counter used during recursion)
 * @returns {Integer} number of dimensions
 */
polyrat._countDimensions = function(a, c) {
    return typeof a[0]==='object' ? polyrat._countDimensions(a[0], ++c) : c;
};

/**
 * Zero, the additive identity
 *
 * @property ZERO
 * @type polyrat
 * @static
 * @final
 */
polyrat.ZERO = polyrat.fromValues([0]);

/**
 * One, the multiplicative identity
 *
 * @property IDENTITY
 * @type polyrat
 * @static
 * @final
 */
polyrat.IDENTITY = polyrat.fromValues([1]);

/**
 * Alias for {@link polyrat.IDENTITY}
 * @static
 */
polyrat.ONE = polyrat.IDENTITY;

if(typeof(exports) !== 'undefined') {
    exports.polyrat = polyrat;
}