rlab
Version:
Javascript scientific library like R
578 lines (481 loc) • 14.7 kB
JavaScript
// module : Field & Group Theory
// 注意: 箭頭函數會自動將 this 變數綁定到其定義時所在的物件,因此以下很多地方不能用箭頭函數。
// 參考: https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions
var N, A, S, R;
module.exports = A = S = N = R = require("./set");
// var N = R.Number = {}
// var A = R.Algebra = {}
// var S = R.Set;
// A.Integer=require("./integer");
var eq = R.eq, extend=R.extend;
// ========== Group & Field Property ================
// ref:https://en.wikipedia.org/wiki/Group_(mathematics)
// 封閉性:For all a, b in G, a • b, is also in G
A.closability=function(set, op, a, b) { return set.has(op(a,b)) }
// 結合性:For all a, b and c in G, (a • b) • c = a • (b • c).
A.associativity=function(set, op, a, b, c) {
return eq(op(op(a,b),c), op(a,op(b,c)))
}
// 單位元素:Identity element
A.identity=function(set, op, e, a) { return eq(op(e,a),a) }
// 反元素:Inverse element
A.inversability=function(e, inv, a) {
return eq(op(a,inv(a)),e);
}
// 交換性:
A.commutative=function(op,a,b) {
return eq(op(a,b),op(b,a));
}
// 左分配律:
A.ldistribute=function(add, mul, a,b,c) {
return eq(mul(a, add(b,c)), add(mul(a,b), mul(a,c)));
}
// 右分配律:
A.rdistribute=function(add, mul, a,b,c) {
return A.ldistribute(b,c,a);
}
// 封閉性
A.isClose=function(g) {
var a=g.random(), b=g.random();
return A.closability(g, a, b);
}
// 結合性
A.isAssociate=function(g) {
var a=g.random(), b=g.random(), c=g.random();
return A.associativity(g, g.op, a, b, c);
}
// 單位元素
A.isIdentify=function(g) {
var a=g.random(), b=g.random(), c=g.random();
return A.associativity(g, g.op, a, b, c);
}
// 反元素
A.isInversable=function(g) {
var a=g.random(), b=g.random(), c=g.random();
return A.associativity(g, g.op, a, b, c);
}
// 左分配律
A.isLeftDistribute=function(f, m, add, mul) {
var a=f.random(), b=g.random(), c=g.random();
return A.ldistribute(add, mul, a, b, c);
}
// 右分配律
A.isRightDistribute=function(f, m, add, mul) {
var a=f.random(), b=f.random(), c=g.random();
return A.rdistribute(add, mul, a, b, c);
}
// 原群
A.isMagma = function(g) { return A.isClose(g) }
// 半群
A.isSemiGroup = function(g) { return A.isClose(g) && A.isAssociate(g) }
// 么半群
A.isMonoid = function(g) { return A.isSemiGroup(g) && A.isIdentify(g) }
// 群
A.isGroup = function(g) { return A.isMonoid(g) && A.isInversable(g) }
// 交換群
A.isAbelGroup = function(g) { return A.isGroup(g) && A.isCommutable(g) }
// 擬群
A.isQuasiGroup = function(g) { return A.isClose(g) && A.isInversable(g) }
// 環群
A.isLoop = function(g) { return A.isQuasiGroup(g) && A.isIdentify(g) }
// 環 : 沒有乘法反元素的體。
A.isRing = function(f) {
return isAbelGroup(f.addSet) && isSemiGroup(f.mulSet);
}
// 體 : 具有加減乘除結構的集合
A.isField = function(f) {
return isAbelGroup(f.addSet) && isAbelGroup(f.mulSet);
}
// 模 : 向量的抽象化
A.isModule = function(r,m) {
return A.isRing(r)&&A.isAbelGroup(m);
// &&A.isLeftDistribute(r.madd, m.add);
}
// ========== Group =================
A.SemiGroup={
power:function(x,n) {
var p=this.e;
for (var i=0;i<n;i++) {
p=this.op(p,x);
}
return p;
},
leftCoset:function(g, H) {
var set = new Set();
for (var i in H)
set.add(this.op(g,H[i]));
return set;
},
rightCoset:function(H, g) {
var set = new Set();
for (var i in H)
set.add(this.op(g,H[i]));
return set;
},
} // 半群
A.Monoid={} // 么半群
extend(A.Monoid, A.SemiGroup)
A.Group={
// inv:function(x) { return x.inv() },
}
extend(A.Group, A.Monoid);
A.AbelGroup = {} // 交換群
extend(A.AbelGroup, A.Group);
// PermutationGroup
A.PermutationGroup={
op:function(x,y) {
var z = [];
for (var i in x)
z[i] = y[x[i]];
return z;
},
inv:function(x) {
var nx = [];
for (var i in x) {
nx[x[i]] = i;
}
return nx;
},
}
extend(A.PermutationGroup, A.Group);
// 循環群 Cyclic Group : a group that is generated by a single element (g)
A.CyclicGroup={
G:[], // g:g,
op:function(x,y) {},
inv:function(x) {},
create(g) {
var t = e;
for (var i=0; !t.eq(e); i++) {
G[i]=t;
t=op(g,G[i]);
}
}
}
extend(A.CyclicGroup, A.Group);
// NormalSubGroup : 正規子群
A.isNormalSubGroup=function(G,H,g,h) {
return H.has(G.op(G.op(g,h), G.inv(g)));
}
// 商群 Quotent Group : aggregating similar elements of a larger group using an equivalence relation that preserves the group structure
A.QuotentGroup={
eq:function(x,y) {},
op:function(x,y) {},
inv:function(x) {},
}
extend(A.QuotentGroup, A.Group);
// Normal SubGroup : gH = Hg
// https://en.wikipedia.org/wiki/Normal_subgroup
A.NormalSubGroup={
op:function(x,y) {},
inv:function(x) {},
}
extend(A.NormalSubGroup, A.Group);
// 群同構第一定理: 給定 GG和 G ′ 兩個群,和 f : G → G ′ 群同態。則 Ker f 是一個 G 的正規子群。
// 群同構第二定理:給定群 G 、其正規子群 N、其子群 H,則 N ∩ H 是 H 的正規子群,且我們有群同構如下: H / ( H ∩ N ) ≃ H N / N
// 群同構第三定理: 給定群 G, N 和 M,M 為 G 的正規子群,滿足 M 包含於 N ,則 N / M 是 G / M 的正規子群,且有如下的群同構: ( G / M ) / ( N / M ) ≃ G / N .
// ========== Field =================
A.Ring = { // Ring (環) : 可能沒有乘法單位元素和反元素的 Field
neg:function(x) { return this.addSet.inv(x) },
add:function(x,y) { return this.addSet.op(x,y) },
sub:function(x,y) { return this.addSet.op(x, this.addSet.inv(y)) },
mul:function(x,y) { return this.mulSet.op(x,y) },
power:function(x,n) { return this.mulSet.power(x,n) },
init:function(addSet, mulSet) {
this.addSet = addSet;
this.mulSet = mulSet;
this.zero = addSet.e;
},
// Ideal (理想): 子環,且 i·r ∈ I (左理想), r·i ∈ I (右理想)
ideal:function(i) {}, // https://en.wikipedia.org/wiki/Ideal_(ring_theory)
}
// (F,+,*) : (F,+), (F-0,*) 均為交換群。
A.Field = {
div:function(x,y) { return this.mulSet.op(x, this.mulSet.inv(y)) },
inv:function(x) { return this.mulSet.inv(x) },
init:function(addSet, mulSet) {
A.Ring.init.call(this, addSet, mulSet);
this.one = mulSet.e;
},
}
extend(A.Field, A.Ring);
// https://zh-classical.wikipedia.org/wiki/%E6%A8%A1_(%E4%BB%A3%E6%95%B8)
A.Module = A.Field;// Module(模) : (R +) is Ring, (R × M → M)
// ========== Float Field =================
A.FloatAddGroup={
e:0,
op:function(x,y) { return x+y },
inv:function(x) { return -x},
}
extend(A.FloatAddGroup, A.AbelGroup, S.Float);
A.FloatMulGroup={
e:1,
op:function(x,y) { return x*y },
inv:function(x) { return 1/x},
}
extend(A.FloatMulGroup, A.AbelGroup, S.Float);
A.FloatField=extend({}, A.Field, S.Float);
A.FloatField.init(A.FloatAddGroup, A.FloatMulGroup);
// ========== Finite Field =================
A.FiniteAddGroup={
e:0,
op:function(x,y) { return (x+y)%this.n },
inv:function(x) { return (this.n-x) }
}
extend(A.FiniteAddGroup, A.AbelGroup);
A.FiniteMulGroup={
e:1,
op:function(x,y) { return (x*y)%this.n },
inv:function(x) { return this.invMap[x] },
setOrder:function(n) {
this.n = n;
let invMap = new Map();
for (var x=1; x<n; x++) {
var y = this.op(x,x);
invMap.set(x,y);
}
this.invMap = invMap;
}
}
extend(A.FiniteMulGroup, A.AbelGroup);
A.FiniteField=extend({}, A.Field);
A.FiniteField.create=function(n) {
var finiteField = extend(S.Finite(n), A.FiniteField);
var addSet = extend(S.Finite(n), {n:n}, A.FiniteAddGroup);
var mulSet = extend(S.Finite(n), {n:n}, A.FiniteMulGroup);
finiteField.init(addSet, mulSet);
mulSet.setOrder(n);
return finiteField;
}
class MathObj {
constructor() {}
str() { return this.toString() }
}
A.MathObj = MathObj;
// =========== Field Object ==============
class FieldObj extends MathObj {
constructor(field) {
super();
this.field = field;
var p = Object.getPrototypeOf(this);
p.zero = field.zero;
p.one = field.one;
}
add(y) { return this.field.add(this,y) }
mul(y) { return this.field.mul(this,y) }
neg() { return this.field.neg(this) }
inv() { return this.field.inv(this) }
div(y) { return this.field.div(this,y) }
sub(y) { return this.field.sub(this,y) }
power(n) { return this.field.power(this,n) }
isZero(x) { return this.field.isZero(this) }
isOne(x) { return this.field.isOne(this) }
eq(y) { return this.field.eq(this, y) }
neq(y) { return this.field.neq(this, y) }
mod(y) { return this.field.mod(this, y) }
}
A.FieldObj = FieldObj;
// =========== Complex Field ==============
A.ComplexField=extend({}, A.Field);
class Complex extends FieldObj {
constructor(a,b) {
super(A.ComplexField);
this.a = a; this.b = b;
}
conj() { return new Complex(this.a, -1*this.b); }
str() {
var op = (this.b<0)?'':'+';
return R.nstr(this.a)+op+R.nstr(this.b)+'i';
}
toString() { return this.str() }
toPolar() {
var a=this.a, b=this.b, r=Math.sqrt(a*a+b*b);
var theta = Math.acos(a/r);
return {r:r, theta:theta}
}
power(k) {
var p = this.toPolar();
return Complex.polarToComplex(Math.pow(p.r,k), k*p.theta);
}
sqrt() {
return this.power(1/2);
}
static toComplex(o) {
if (R.isFloat(o))
return new Complex(o, 0);
else if (o instanceof Complex)
return o;
console.log('o=', o);
throw Error('toComplex fail');
}
static polarToComplex(r,theta) {
var a=r*Math.cos(theta), b=r*Math.sin(theta);
return new Complex(a, b);
}
static parse(s) {
var m = s.match(/^([^\+]*)(\+(.*))?$/);
var a = parseFloat(m[1]);
var b = typeof m[3]==='undefined'?1:parseFloat(m[3]);
return new Complex(a, b)
}
}
R.Complex = Complex;
R.polarToComplex = Complex.polarToComplex;
R.toComplex = Complex.toComplex;
var C = (a,b)=>new Complex(a,b);
var enumComplex=[C(1,0),C(0,1),C(0,0),C(2,3),C(-5,4),C(-10,-7)];
S.ComplexSet=new S.create(enumComplex);
S.ComplexSet.has = (a)=>a instanceof Complex;
A.ComplexAddGroup={
e:new Complex(0,0),
op:function(x,y) {
x = Complex.toComplex(x), y=Complex.toComplex(y);
return new Complex(x.a+y.a, x.b+y.b)
},
inv:function(x) {
x = Complex.toComplex(x);
return new Complex(-x.a, -x.b)
}
}
extend(A.ComplexAddGroup, A.AbelGroup, S.ComplexSet);
A.ComplexMulGroup={
e:new Complex(1,0),
op:function(x,y) {
x = Complex.toComplex(x), y=Complex.toComplex(y);
return new Complex(x.a*y.a-x.b*y.b, x.a*y.b+x.b*y.a);
},
inv:function(x) {
x = Complex.toComplex(x);
var a=x.a,b=x.b, r=a*a+b*b;
return new Complex(a/r, -b/r);
}
}
extend(A.ComplexMulGroup, A.AbelGroup, S.ComplexSet);
extend(A.ComplexField, S.ComplexSet);
A.ComplexField.init(A.ComplexAddGroup, A.ComplexMulGroup);
// =========== Ratio Field ==============
A.RatioField=extend({}, A.Field);
class Ratio extends FieldObj {
constructor(a,b) {
super(A.RatioField);
this.a = a; this.b = b;
}
reduce() {
var a = this.a, b=this.b;
var c = R.gcd(a, b);
return new Ratio(a/c, b/c);
}
toString() { return this.a+'/'+this.b; }
static parse(s) {
var m = s.match(/^(\d+)(\/(\d+))?$/);
var a = parseInt(m[1]);
var b = typeof m[3]==='undefined'?1:parseInt(m[3]);
return new Ratio(a, b)
}
}
N.Ratio = Ratio;
A.RatioAddGroup={
e:new Ratio(0,1),
op:function(x,y) { return new Ratio(x.a*y.b+x.b*y.a, x.b*y.b) },
inv:function(x) { return new Ratio(-x.a, x.b); },
}
extend(A.RatioAddGroup, A.AbelGroup);
A.RatioMulGroup={
e:new Ratio(1,1),
op:function(x,y) { return new Ratio(x.a*y.a, x.b*y.b) },
inv:function(x) { return new Ratio(x.b, x.a) },
}
extend(A.RatioMulGroup, A.AbelGroup);
A.RatioField.init(A.RatioAddGroup, A.RatioMulGroup);
// Function
R.isField = A.isField=function(x) {
return R.isBool(x) || R.isNumber(x) || x instanceof A.FieldObj;
}
R.parse = N.parse = function(s) {
if (s.indexOf(';')>=0) {
var m = split(s, ";"), matrix;
for (var i=0; i<m.length; i++) {
matrix[i] = N.parse(m[i]);
}
return matrix;
} if (s.indexOf(',')>=0) {
var a = split(s, ","), array;
for (var i=0; i<a.length; i++) {
array[i] = N.parse(a[i]);
}
return array;
}
else if (s.indexOf('/')>=0)
return N.Ratio.parse(s);
else if (s.indexOf('i')>=0)
return N.Complex.parse(s);
else {
return parseFloat(s);
}
}
N.op = function(op,x,y) {
if (y instanceof N.Complex) {
x = x.toComplex();
switch (op) {
case 'add':return x.add(y);
case 'sub':return x.sub(y);
case 'mul':return x.mul(y);
case 'div':return x.div(y);
case 'sqrt':return x.sqrt();
case 'power':return x.power(y);
}
} else if (y instanceof Array) {
switch (op) {
case 'add':return y.add(x);
case 'sub':return y.sub(x).neg();
case 'mul':return y.mul(x);
case 'div':return y.div(x).inv();
}
} else {
switch (op) {
case 'add':return x+y;
case 'sub':return x-y;
case 'mul':return x*y;
case 'div':return x/y;
case 'sqrt':return (x>=0)?Math.sqrt(x):x.toComplex().sqrt(x);
case 'power':return (y>=0)?Math.pow(x,y):x.toComplex().power(x,y);
}
}
throw Error('N.op:invalid '+op);
}
N.neg=function(x) { return -x }
N.inv=function(x) { return 1/x }
N.add=function(x,y) { return N.op('add', x, y) }
N.sub=function(x,y) { return N.op('sub', x, y) }
N.mul=function(x,y) { return N.op('mul', x, y) }
N.div=function(x,y) { return N.op('div', x, y) }
N.mod=function(x,y) { return x%y }
N.sqrt=function(x) { return N.op('sqrt', x) }
N.power=function(x,y) { return N.op('power', x, y) }
N.eval=function(x) { return f(x) }
R.mixThisMap(Number.prototype, N, {
add:'add',
sub:'sub',
mul:'mul',
div:'div',
mod:'mod',
neg:'neg',
inv:'inv',
sqrt:'sqrt',
power:'power',
eval:'eval',
toComplex:'toComplex',
});
R.mixThis(Number.prototype, Math, [
'log',
'exp',
'abs',
'sin',
'cos',
'tan',
'asin',
'acos',
'atan',
'ceil',
'floor',
'round',
]);