UNPKG

matrixlib-js

Version:

MatrixLib.js is a JavaScript simple matrix calculation library.

1,093 lines (1,018 loc) 28.1 kB
/** * # matrixLib.js - v0.3.1 * * Copyright (c) 2018 Kohei Katada. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const matrixlibJs = () => { 'use strict'; /* # util */ const util = { /** * # 引数がnullかundefinedではない事を確認する関数 * @param x * @returns {boolean} */ existy(x) { return x != null; }, /** * # 0と''もtrueとみなす真偽値を返す * @param x * @returns {boolean|*} */ truthy(x) { return (x !== false) && util.existy(x); }, /** * # 数値かどうかの真偽値を返す * @param num * @returns {boolean} */ isNumber(num) { return typeof num === 'number' && isFinite(num); }, /** * # 配列かどうかの真偽値を返す * @param arr * @returns {boolean} */ isArray(arr) { return Object.prototype.toString.call(arr) === '[object Array]'; }, /** * # 関数かどうかの真偽値を返す * @param fn * @returns {boolean} */ isFunction(fn, notArrow) { return Object.prototype.toString.call(fn) === '[object Function]' && (!notArrow || 'prototype' in fn); }, /** * # 配列の不要な要素を削除する関数 * @param arr * @param deleteValue * @returns {*} */ cleanArray(arr, deleteValue) { for (let i = 0; i < arr.length; i += 1) { if (arr[i] === deleteValue) { arr.splice(i, 1); i--; } } return arr; }, /** * # 正規乱数生成 * @param m * @param s * @returns {*} */ rnorm(m, s) { const a = 1 - Math.random(); const b = 1 - Math.random(); const c = Math.sqrt(-2 * Math.log(a)); if(0.5 - Math.random() > 0) { return c * Math.sin(Math.PI * 2 * b) * s + m; }else{ return c * Math.cos(Math.PI * 2 * b) * s + m; } } }; /* # math */ const math = { /* # math.util */ util: { /** * # 計算をループ処理する関数 */ caluclate(x, y, f) { /** * # f を実行する関数 * @param f * @param x * @param y * @returns {*} */ const execFunc = (f, x, y) => { return f(x, y); }; /** * # x を yArr に合わせてブロードキャストする関数 * @param x * @param yArr * @returns {*[]} */ function broadCastX(x, yArr) { if (!util.isArray(yArr)) { return [x, yArr]; } let bcX = []; yArr.forEach((val, idx, arr) => { if (util.isArray(val)) { bcX[idx] = []; val.forEach((_val, _idx, _arr) => { bcX[idx][_idx] = broadCastX(x, _val)[0]; }); } else { bcX[idx] = x; } }); return [bcX, yArr]; } /** * # y を xArr に合わせてブロードキャストする関数 * @param xArr * @param y * @returns {*[]} */ function broadCastY(xArr, y) { if (!util.isArray(xArr)) { return [xArr, y]; } let bcY = []; xArr.forEach((val, idx, arr) => { if (util.isArray(val)) { bcY[idx] = []; val.forEach((_val, _idx, _arr) => { bcY[idx][_idx] = broadCastY(_val, y)[1]; }); } else { bcY[idx] = y; } }); return [xArr, bcY]; } // 計算を実行 if (!util.isArray(x) && !util.isArray(y)) { // x, y がスカラー値だった場合 return execFunc(f, x, y); } if (!util.isArray(x) && util.isArray(y)) { // x がスカラー値の場合 const bc = broadCastX(x, y); return this.caluclate(bc[0], bc[1], f); } else if (!util.isArray(y) && util.isArray(x)) { // y がスカラー値の場合 const bc = broadCastY(x, y); return this.caluclate(bc[0], bc[1], f); } if (math.util.shape(x)[1] === 0 && math.util.shape(y)[1] === 0) { // xとyが1次元配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = execFunc(f, x[idx], y[idx]); }); return r; } if (math.util.shape(x)[1] === 0 && math.util.shape(y)[1] !== 0) { // xが1次元でyが2次元以上なら、 const bc = []; y.forEach((val, idx, arr) => { bc[idx] = x; }); return this.caluclate(bc, y, f); } if (math.util.shape(y)[1] === 0 && math.util.shape(x)[1] !== 0) { // yが1次元でxが2次元以上なら、 const bc = []; x.forEach((val, idx, arr) => { bc[idx] = y; }); return this.caluclate(x, bc, f); } if (math.util.shape(x)[1] !== 0 && math.util.shape(y)[1] !== 0 &&x.length === y.length) { // xとyの要素数が同じでxもyも2次元なら、 let r = []; x.forEach((val, idx, arr) => { r[idx] = this.caluclate(x[idx], y[idx], f); }); return r; } else { throw new Error('計算不可能です.'); } }, /** * # n次元配列の形状を返す * @param arr * @returns {[*,*]} */ shape(arr) { let row, col; if (!util.isArray(arr)) { return -1; } else if (util.isArray(arr[0])) { row = arr.length; col = arr[0].length; } else if (util.isArray(arr)) { row = arr.length; col = 0; } else { throw new Error(arr + 'が2次元以上の配列なのでshape()は使えません.'); } return [row, col]; } }, /* # math.calc */ calc: { /** * # 足し算 * @param x * @param y * @returns {*} */ plus(x, y) { return x + y; }, /** * # 引き算 * @param x * @param y * @returns {*} */ minus(x, y) { return x - y; }, /** * # 掛け算 * @param x * @param y * @returns {*} */ multi(x, y) { return x * y; }, /** * # 割り算 * @param x * @param y * @returns {*} */ div(x, y) { return x / y; }, /** * # 大なり * @param x * @param y * @returns {boolean} */ more(x, y) { return x > y; }, /** * # 小なり * @param x * @param y * @returns {boolean} */ less(x, y) { return x < y; }, /** * # 大なりイコール * @param x * @param y * @returns {boolean} */ moreEq(x, y) { return x >= y; }, /** * # 小なりイコール * @param x * @param y * @returns {boolean} */ lessEq(x, y) { return x <= y; }, /** * # 同値演算 * @param x * @param y * @returns {boolean} */ equal(x, y) { return x === y; }, /** * # 非同値演算 * @param x * @param y * @returns {boolean} */ notEqual(x, y) { return x !== y; }, /** * # 累乗する * @param x * @param n * @returns {*} */ pow(x, n) { return Math.pow(x, n); }, /** * # Ex を返す.(自然対数の底であるネイピア数(オイラー数)) * @param x * @returns {*} */ exp(x) { return Math.exp(x); }, /** * # 自然対数 (底は e) を返す. * @param x * @returns {*} */ log(x) { return Math.log(x); }, /** * # 引数、x, y を比べて大きい方の値を返す * @param x * @param y * @returns {number} */ max(x, y) { return Math.max(x, y); } }, /* # math.arr */ arr: { /* # math.arr.util */ util: { /** * # 1次元配列生成 * @param x {Number} * @param v {*} # 配列の各要素を初期化する値(関数だった場合は関数の戻り値で初期化) * @returns {Array} */ create(x, v) { let r = []; for (let i=0,l=x; i<l; i+=1) { r[i] = util.isFunction(v) ? v() : v; } return r; }, /** * # step単位の、minSizeからmaxSizeまでの数の1次元配列を生成 * @param minSize * @param maxSize * @param step * @param callback # 出来上がった配列に適用可能な関数 * @returns {Array} */ arange(minSize, maxSize, step, callback) { // 初期化 let arr = []; // step引数がないのに、maxSize引数がある場合 if (!step && !!maxSize) { throw new Error('第三引数のステップ数が指定されていないため計算できません.'); } // 引数がminSizeだけの場合 if (!step && !maxSize && !!minSize) { // 引数がminSizeだけで、負の値だった場合 if (minSize < 0) { throw new Error('負の値を指定するときは第二引数に範囲の最大値、第三引数にステップ数を指定してください.'); } // 引数がminSizeだけで、正の値だった場合 for (let i=0,l=minSize; i<l; i+=1) { arr[i] = i; } } // 引数が全てある場合 if (!!step) { if (maxSize <= minSize) { throw new Error('第二引数の値は第一引数の値より大きい値を指定してください.'); } let len = maxSize - minSize; for (let i=0,l=len/step; i<l; i+=1) { arr[i] = minSize + (step*i); } } // コールバック関数がある場合 if (!!callback) { for (let i=0,l=arr.length; i<l; i+=1) { arr[i] = callback(arr[i]); } return arr; } return arr; } } }, /* # math.matrix */ matrix: { /* # math.matrix.util */ util: { /** * # 行列を生成 * @param x {Array|Number} * @param y {Number} * @param v {*} # 行列の各要素を初期化する値(関数だった場合は関数の戻り値で初期化) * @returns {*} */ create(x, y, v) { let r1; if (util.isArray(x)) { if (!util.isArray(x[0])) { r1 = [x]; } else { r1 = x; } } let r2 = []; for (let i=0,l=x; i<l; i+=1) { r2[i] = (() => { return math.arr.util.create(y, v); })(); } let r; if (util.truthy(r1)) { r = r1; } else { r = r2; } return r; }, /** * # 行列の列を行に変換 * @param matrix * @returns {Array} */ colToRow(matrix) { if (math.util.shape(matrix)[1] === 0) { return matrix; } let r = []; for (let i=0,l=matrix[0].length; i<l; i+=1) { r[i] = []; for (let j=0,m=matrix.length; j<m; j+=1) { r[i][j] = matrix[j][i]; } } return r; } } }, /** * # n次元配列同士の足し算 * @param x * @param y * @returns {Array} */ plus(x, y) { return math.util.caluclate(x, y, math.calc.plus); }, /** * # n次元配列同士の引き算 (x - y) * @param x * @param y * @returns {Array} */ minus(x, y) { return math.util.caluclate(x, y, math.calc.minus); }, /** * # n次元配列同士の掛け算 * @param x * @param y * @returns {Array} */ multi(x, y) { return math.util.caluclate(x, y, math.calc.multi); }, /** * # n次元配列同士の割り算 (x / y) * @param x * @param y * @returns {Array} */ div(x, y) { return math.util.caluclate(x, y, math.calc.div); }, /** * # n次元配列同士の大なり (x > y) * @param x * @param y * @returns {*} */ more(x, y) { return math.util.caluclate(x, y, math.calc.more); }, /** * # n次元配列同士の小なり (x < y) * @param x * @param y * @returns {*} */ less(x, y) { return math.util.caluclate(x, y, math.calc.less); }, /** * # n次元配列同士の大なりイコール (x >= y) * @param x * @param y * @returns {*} */ moreEq(x, y) { return math.util.caluclate(x, y, math.calc.moreEq); }, /** * # n次元配列同士の小なりイコール (x <= y) * @param x * @param y * @returns {*} */ lessEq(x, y) { return math.util.caluclate(x, y, math.calc.lessEq); }, /** * # n次元配列同士の同値演算 (x === y) * @param x * @param y * @returns {*} */ equal(x, y) { return math.util.caluclate(x, y, math.calc.equal); }, /** * # n次元配列同士の非同値演算 (x !== y) * @param x * @param y * @returns {*} */ notEqual(x, y) { return math.util.caluclate(x, y, math.calc.notEqual); }, /** * n次元配列の総和を求める * @param xArr * @param axis * @returns {*} */ sum(xArr, axis) { if (!util.isArray(xArr)) { // スカラー値だった場合 return xArr; } if (!util.isArray(xArr[0])) { // 1次元配列だった場合 return xArr.reduce((pre, val, idx, arr) => { return math.calc.plus(pre, val); }, 0); } else if (!util.isArray(xArr[0][0])) { // 2次元配列だった場合 if (!util.truthy(axis)) { return this.sum(math.flatten(xArr)); } else if (axis === 0) { xArr = math.matrix.util.colToRow(xArr); let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.sum(val); }); return r; } else if (axis === 1) { let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.sum(val); }); return r; } } else { // 3次元以上の配列だった場合 if (!util.truthy(axis)) { return this.sum(math.flatten(xArr)); } else if (axis === 0) { xArr = math.matrix.util.colToRow(xArr); let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.sum(val, 0); }); return r; } else if (axis === 1) { let r = []; xArr.forEach((val, idx, arr) => { val = math.matrix.util.colToRow(val); r[idx] = this.sum(val, 1); }); return r; } else if (axis === 2) { let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.sum(val, 1); }); return r; } } }, /** * # n次元配列中の最大値のインデックスを取得 * @param xArr * @param axis * @returns {*} */ maxIdx(xArr, axis) { if (!util.isArray(xArr[0])) { // 1次元配列だった場合 return xArr.indexOf(Math.max.apply(null, xArr)); } else if (!util.isArray(xArr[0][0])) { // 2次元配列だった場合 if (!util.truthy(axis)) { return this.maxIdx(math.flatten(xArr)); } else if (axis === 0) { xArr = math.matrix.util.colToRow(xArr); let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.maxIdx(val); }); return r; } else if (axis === 1) { let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.maxIdx(val); }); return r; } } else { // 3次元以上の配列だった場合 if (!util.truthy(axis)) { return this.maxIdx(math.flatten(xArr)); } else if (axis === 0) { xArr = math.matrix.util.colToRow(xArr); let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.maxIdx(val, 0); }); return r; } else if (axis === 1) { let r = []; xArr.forEach((val, idx, arr) => { val = math.matrix.util.colToRow(val); r[idx] = this.maxIdx(val, 1); }); return r; } else if (axis === 2) { let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.maxIdx(val, 1); }); return r; } } }, /** * # 2つの配列の同じインデックスの要素の値が同じだった場合に、そのカウント数を取得する * @param x * @param y * @returns {number} */ matchCount(x, y) { if (!util.isArray(x[0])) { // 1次元配列だった場合 let count = 0; x.forEach((val, idx, arr) => { if (val === y[idx]) { count += 1; } }); return count; } else { // 2次元以上の配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = this.matchCount(val, y[idx]); }); return r; } }, /** * # 真偽値を数値1か0に変換する * @param x * @returns {Array} */ boolToInt(x) { if (!util.isArray(x)) { return !util.truthy(x) ? 0 : 1; } if (!util.isArray(x[0])) { // 1次元配列だった場合 let r = []; x.forEach((val, idx, arr) => { if (!util.truthy(val)) { r[idx] = 0; } else { r[idx] = 1; } }); return r; } else { // 2次元以上の配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = this.boolToInt(val); }); return r; } }, /** * # 真偽値を任意の値に変換する * @param x * @param callback * @param y * @returns {Array} */ valToAnyVal(x, callback, y) { if (!util.isArray(x)) { return callback(x, util.truthy(y) && y); } if (!util.isArray(x[0])) { // 1次元配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = callback(val, util.truthy(y) && util.truthy(y[idx]) && y[idx], idx, arr); }); return r; } else { // 2次元以上の配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = this.valToAnyVal(val, callback, util.truthy(y) && util.truthy(y[idx]) && y[idx]); }); return r; } }, /** * # 累乗する * @param x * @param n * @returns {*} */ pow(x, n) { return math.util.caluclate(x, n, math.calc.pow); }, /** * # Ex を返す.(自然対数の底であるネイピア数(オイラー数)) * @param x * @returns {*} */ exp(x) { if (!util.isArray(x)) { // スカラー値の場合 return math.calc.exp(x); } else if (!util.isArray(x[0])) { // 1次元配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = math.calc.exp(val); }); return r; } else { // 2次元以上の配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = this.exp(val); }); return r; } }, /** * # 自然対数 (底は e) を返す. * @param x * @returns {*} */ log(x) { if (!util.isArray(x)) { // スカラー値の場合 return math.calc.log(x); } else if (!util.isArray(x[0])) { // 1次元配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = math.calc.log(val); }); return r; } else { // 2次元以上の配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = this.log(val); }); return r; } }, /** * # 一番大きい数値を返す * @param xArr * @param axis * @returns {number} */ max(xArr, axis) { if (!util.isArray(xArr[0])) { // 1次元配列だった場合 return Math.max.apply(null, xArr); } else if (!util.isArray(xArr[0][0])) { // 2次元配列だった場合 if (!util.truthy(axis)) { return this.max(math.flatten(xArr)); } else if (axis === 0) { xArr = math.matrix.util.colToRow(xArr); let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.max(val); }); return r; } else if (axis === 1) { let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.max(val); }); return r; } } else { // 3次元以上の配列だった場合 if (!util.truthy(axis)) { return this.max(math.flatten(xArr)); } else if (axis === 0) { xArr = math.matrix.util.colToRow(xArr); let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.max(val, 0); }); return r; } else if (axis === 1) { let r = []; xArr.forEach((val, idx, arr) => { val = math.matrix.util.colToRow(val); r[idx] = this.max(val, 1); }); return r; } else if (axis === 2) { let r = []; xArr.forEach((val, idx, arr) => { r[idx] = this.max(val, 1); }); return r; } } }, /** * # 大きい方の値を返す * @param x * @param y * @returns {*} */ maximum(x, y) { return math.util.caluclate(x, y, math.calc.max); }, /** * # 多次元配列を1次元配列にする * @param x * @returns {*} */ flatten(x) { if (!util.isArray(x[0])) { // 1次元配列だった場合 return Array.prototype.concat.apply([], x);; } else { // 2次元以上の配列だった場合 let r = []; x.forEach((val, idx, arr) => { r[idx] = this.flatten(val); }); return Array.prototype.concat.apply([], r); } }, /** * # 行列の内積を計算 * @param x * @param y */ dot(x, y) { function Init () { let r = []; return { getRow(x, idx) { return x[idx]; }, getRowElem(row, idx) { return row[idx]; }, getCol(y, idx) { return math.matrix.util.colToRow(y)[idx]; }, getColElem(col, idx) { return col[idx]; }, setResultRowElem(r, rowIdx, colIdx, val) { if (!util.truthy(r[rowIdx])) { r[rowIdx] =[]; } return r[rowIdx][colIdx] = val; }, getResult() { return r; } }; } if (!util.isArray(x) && !util.isArray(y)) { // x, yがスカラ値なら return math.multi(x, y); } if ((math.util.shape(x)[1] === 0) && (math.util.shape(y)[1] === 0)) { // x, yが1次元なら、 if (x.length === y.length) { return math.sum(math.multi(x, y)); } throw new Error('x, yそれぞれの配列の要素数が違うため計算できません.'); } let array1dYflag = false; let array2dYflag = false; let array1dXflag = false; let array2dXflag = false; if (math.util.shape(x)[1] === 0) { // xが1次元ならブロードキャスト let r = []; y[0].forEach((val, idx, arr) => { r[idx] = []; y.forEach((_val, _idx, _arr) => { r[idx][_idx] = x[_idx]; }); }); x = r; array1dXflag = true; } if (math.util.shape(x)[0] === 1) { // xが2次元で要素が1つなら let r = []; y[0].forEach((val, idx, arr) => { r[idx] = []; y.forEach((_val, _idx, _arr) => { r[idx][_idx] = x[0][_idx]; }); }); x = r; array2dXflag = true; } if (math.util.shape(y)[1] === 0) { // yが1次元ならブロードキャスト let r = []; x[0].forEach((val, idx, arr) => { r[idx] = []; x.forEach((_val, _idx, _arr) => { r[idx][_idx] = y[idx]; }); }); y = r; array1dYflag = true; } if (math.util.shape(y)[1] === 1) { // yが2次元で1次元目の要素が1つなら let r = []; x[0].forEach((val, idx, arr) => { r[idx] = []; x.forEach((_val, _idx, _arr) => { r[idx][_idx] = y[idx][0]; }); }); y = r; array2dYflag = true; } // 内積の計算を実行 const calc = Init(); let xRow = []; for (let i = 0, l = x.length; i < l; i += 1) { xRow[i] = calc.getRow(x, i); let yCol = []; for (let j = 0, m = math.matrix.util.colToRow(y).length; j < m; j += 1) { yCol[j] = calc.getCol(y, j); calc.setResultRowElem(calc.getResult(), i, j, math.sum(math.multi(xRow[i], yCol[j]))); } } let r = calc.getResult(); // 結果を加工 if (util.truthy(array1dXflag)) { // xが1次元なら r = r[0]; array1dXflag = false; } if (util.truthy(array2dXflag)) { // xが2次元で要素が1つなら r = [r[0]]; array2dXflag = false; } if (util.truthy(array1dYflag)) { // yが1次元なら r = math.matrix.util.colToRow(r)[0]; array1dYflag = false; } if (util.truthy(array2dYflag)) { // yが2次元で1次元目の要素が1つなら r.forEach((val, idx, arr) => { r[idx] = [val[0]]; }); array2dYflag = false; } return r; } }; return { util: util, math: math }; };