@johnhenry/math 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/dist/Bivector4.d.ts +52 -0
- package/dist/Bivector4.d.ts.map +1 -0
- package/dist/Bivector4.js +105 -0
- package/dist/Bivector4.js.map +1 -0
- package/dist/CellGraph.d.ts +259 -0
- package/dist/CellGraph.d.ts.map +1 -0
- package/dist/CellGraph.js +637 -0
- package/dist/CellGraph.js.map +1 -0
- package/dist/Clifford4Internal.d.ts +35 -0
- package/dist/Clifford4Internal.d.ts.map +1 -0
- package/dist/Clifford4Internal.js +86 -0
- package/dist/Clifford4Internal.js.map +1 -0
- package/dist/Combinatorics.d.ts +61 -0
- package/dist/Combinatorics.d.ts.map +1 -0
- package/dist/Combinatorics.js +186 -0
- package/dist/Combinatorics.js.map +1 -0
- package/dist/CombinatoricsGenerators.d.ts +44 -0
- package/dist/CombinatoricsGenerators.d.ts.map +1 -0
- package/dist/CombinatoricsGenerators.js +148 -0
- package/dist/CombinatoricsGenerators.js.map +1 -0
- package/dist/ComplexMath.d.ts +46 -0
- package/dist/ComplexMath.d.ts.map +1 -0
- package/dist/ComplexMath.js +98 -0
- package/dist/ComplexMath.js.map +1 -0
- package/dist/ComplexNumber.d.ts +174 -0
- package/dist/ComplexNumber.d.ts.map +1 -0
- package/dist/ComplexNumber.js +443 -0
- package/dist/ComplexNumber.js.map +1 -0
- package/dist/Cycle.d.ts +42 -0
- package/dist/Cycle.d.ts.map +1 -0
- package/dist/Cycle.js +120 -0
- package/dist/Cycle.js.map +1 -0
- package/dist/Decimal.d.ts +45 -0
- package/dist/Decimal.d.ts.map +1 -0
- package/dist/Decimal.js +135 -0
- package/dist/Decimal.js.map +1 -0
- package/dist/Distributions.d.ts +55 -0
- package/dist/Distributions.d.ts.map +1 -0
- package/dist/Distributions.js +222 -0
- package/dist/Distributions.js.map +1 -0
- package/dist/DualNumber.d.ts +42 -0
- package/dist/DualNumber.d.ts.map +1 -0
- package/dist/DualNumber.js +91 -0
- package/dist/DualNumber.js.map +1 -0
- package/dist/Environment.d.ts +44 -0
- package/dist/Environment.d.ts.map +1 -0
- package/dist/Environment.js +129 -0
- package/dist/Environment.js.map +1 -0
- package/dist/Expression.d.ts +19 -0
- package/dist/Expression.d.ts.map +1 -0
- package/dist/Expression.js +43 -0
- package/dist/Expression.js.map +1 -0
- package/dist/FFT.d.ts +21 -0
- package/dist/FFT.d.ts.map +1 -0
- package/dist/FFT.js +136 -0
- package/dist/FFT.js.map +1 -0
- package/dist/GeometricAlgebra4.d.ts +73 -0
- package/dist/GeometricAlgebra4.d.ts.map +1 -0
- package/dist/GeometricAlgebra4.js +115 -0
- package/dist/GeometricAlgebra4.js.map +1 -0
- package/dist/Geometry.d.ts +41 -0
- package/dist/Geometry.d.ts.map +1 -0
- package/dist/Geometry.js +167 -0
- package/dist/Geometry.js.map +1 -0
- package/dist/Graph.d.ts +72 -0
- package/dist/Graph.d.ts.map +1 -0
- package/dist/Graph.js +337 -0
- package/dist/Graph.js.map +1 -0
- package/dist/Graph3DUtils.d.ts +80 -0
- package/dist/Graph3DUtils.d.ts.map +1 -0
- package/dist/Graph3DUtils.js +156 -0
- package/dist/Graph3DUtils.js.map +1 -0
- package/dist/GraphUtils.d.ts +67 -0
- package/dist/GraphUtils.d.ts.map +1 -0
- package/dist/GraphUtils.js +72 -0
- package/dist/GraphUtils.js.map +1 -0
- package/dist/GroupTheory.d.ts +73 -0
- package/dist/GroupTheory.d.ts.map +1 -0
- package/dist/GroupTheory.js +194 -0
- package/dist/GroupTheory.js.map +1 -0
- package/dist/IntUtils.d.ts +33 -0
- package/dist/IntUtils.d.ts.map +1 -0
- package/dist/IntUtils.js +185 -0
- package/dist/IntUtils.js.map +1 -0
- package/dist/Interval.d.ts +44 -0
- package/dist/Interval.d.ts.map +1 -0
- package/dist/Interval.js +165 -0
- package/dist/Interval.js.map +1 -0
- package/dist/Lattice.d.ts +48 -0
- package/dist/Lattice.d.ts.map +1 -0
- package/dist/Lattice.js +65 -0
- package/dist/Lattice.js.map +1 -0
- package/dist/Logic.d.ts +22 -0
- package/dist/Logic.d.ts.map +1 -0
- package/dist/Logic.js +34 -0
- package/dist/Logic.js.map +1 -0
- package/dist/MatrixMath.d.ts +71 -0
- package/dist/MatrixMath.d.ts.map +1 -0
- package/dist/MatrixMath.js +302 -0
- package/dist/MatrixMath.js.map +1 -0
- package/dist/NumberTheory.d.ts +49 -0
- package/dist/NumberTheory.d.ts.map +1 -0
- package/dist/NumberTheory.js +230 -0
- package/dist/NumberTheory.js.map +1 -0
- package/dist/Numerical.d.ts +85 -0
- package/dist/Numerical.d.ts.map +1 -0
- package/dist/Numerical.js +343 -0
- package/dist/Numerical.js.map +1 -0
- package/dist/Permutation.d.ts +43 -0
- package/dist/Permutation.d.ts.map +1 -0
- package/dist/Permutation.js +142 -0
- package/dist/Permutation.js.map +1 -0
- package/dist/Polygon.d.ts +64 -0
- package/dist/Polygon.d.ts.map +1 -0
- package/dist/Polygon.js +221 -0
- package/dist/Polygon.js.map +1 -0
- package/dist/PolynomialRing.d.ts +74 -0
- package/dist/PolynomialRing.d.ts.map +1 -0
- package/dist/PolynomialRing.js +231 -0
- package/dist/PolynomialRing.js.map +1 -0
- package/dist/Quaternion.d.ts +37 -0
- package/dist/Quaternion.d.ts.map +1 -0
- package/dist/Quaternion.js +106 -0
- package/dist/Quaternion.js.map +1 -0
- package/dist/Rational.d.ts +34 -0
- package/dist/Rational.d.ts.map +1 -0
- package/dist/Rational.js +136 -0
- package/dist/Rational.js.map +1 -0
- package/dist/RealMath.d.ts +112 -0
- package/dist/RealMath.d.ts.map +1 -0
- package/dist/RealMath.js +262 -0
- package/dist/RealMath.js.map +1 -0
- package/dist/Rotor4.d.ts +180 -0
- package/dist/Rotor4.d.ts.map +1 -0
- package/dist/Rotor4.js +388 -0
- package/dist/Rotor4.js.map +1 -0
- package/dist/SpecialFunctions.d.ts +32 -0
- package/dist/SpecialFunctions.d.ts.map +1 -0
- package/dist/SpecialFunctions.js +161 -0
- package/dist/SpecialFunctions.js.map +1 -0
- package/dist/SpecialOperator.d.ts +21 -0
- package/dist/SpecialOperator.d.ts.map +1 -0
- package/dist/SpecialOperator.js +26 -0
- package/dist/SpecialOperator.js.map +1 -0
- package/dist/Statistics.d.ts +50 -0
- package/dist/Statistics.d.ts.map +1 -0
- package/dist/Statistics.js +131 -0
- package/dist/Statistics.js.map +1 -0
- package/dist/StringEvaluator.d.ts +48 -0
- package/dist/StringEvaluator.d.ts.map +1 -0
- package/dist/StringEvaluator.js +190 -0
- package/dist/StringEvaluator.js.map +1 -0
- package/dist/Structure.d.ts +130 -0
- package/dist/Structure.d.ts.map +1 -0
- package/dist/Structure.js +394 -0
- package/dist/Structure.js.map +1 -0
- package/dist/Symbolic.d.ts +501 -0
- package/dist/Symbolic.d.ts.map +1 -0
- package/dist/Symbolic.js +4077 -0
- package/dist/Symbolic.js.map +1 -0
- package/dist/Type.d.ts +31 -0
- package/dist/Type.d.ts.map +1 -0
- package/dist/Type.js +47 -0
- package/dist/Type.js.map +1 -0
- package/dist/Utilities.d.ts +27 -0
- package/dist/Utilities.d.ts.map +1 -0
- package/dist/Utilities.js +47 -0
- package/dist/Utilities.js.map +1 -0
- package/dist/Vec4.d.ts +39 -0
- package/dist/Vec4.d.ts.map +1 -0
- package/dist/Vec4.js +82 -0
- package/dist/Vec4.js.map +1 -0
- package/dist/Vector.d.ts +83 -0
- package/dist/Vector.d.ts.map +1 -0
- package/dist/Vector.js +173 -0
- package/dist/Vector.js.map +1 -0
- package/dist/VectorCalculus.d.ts +42 -0
- package/dist/VectorCalculus.d.ts.map +1 -0
- package/dist/VectorCalculus.js +82 -0
- package/dist/VectorCalculus.js.map +1 -0
- package/dist/VectorUtils.d.ts +172 -0
- package/dist/VectorUtils.d.ts.map +1 -0
- package/dist/VectorUtils.js +706 -0
- package/dist/VectorUtils.js.map +1 -0
- package/dist/differentiation-rules.d.ts +21 -0
- package/dist/differentiation-rules.d.ts.map +1 -0
- package/dist/differentiation-rules.js +52 -0
- package/dist/differentiation-rules.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
- package/src/Bivector4.ts +136 -0
- package/src/CellGraph.ts +669 -0
- package/src/Clifford4Internal.ts +90 -0
- package/src/Combinatorics.ts +183 -0
- package/src/CombinatoricsGenerators.ts +144 -0
- package/src/ComplexMath.ts +162 -0
- package/src/ComplexNumber.ts +491 -0
- package/src/Cycle.ts +126 -0
- package/src/Decimal.ts +145 -0
- package/src/Distributions.ts +269 -0
- package/src/DualNumber.ts +105 -0
- package/src/Environment.ts +127 -0
- package/src/Expression.ts +48 -0
- package/src/FFT.ts +146 -0
- package/src/GeometricAlgebra4.ts +157 -0
- package/src/Geometry.ts +173 -0
- package/src/Graph.ts +337 -0
- package/src/Graph3DUtils.ts +269 -0
- package/src/GraphUtils.ts +159 -0
- package/src/GroupTheory.ts +214 -0
- package/src/IntUtils.ts +200 -0
- package/src/Interval.ts +171 -0
- package/src/Lattice.ts +87 -0
- package/src/Logic.ts +38 -0
- package/src/MatrixMath.ts +326 -0
- package/src/NumberTheory.ts +228 -0
- package/src/Numerical.ts +390 -0
- package/src/Permutation.ts +148 -0
- package/src/Polygon.ts +229 -0
- package/src/PolynomialRing.ts +242 -0
- package/src/Quaternion.ts +125 -0
- package/src/Rational.ts +148 -0
- package/src/RealMath.ts +346 -0
- package/src/Rotor4.ts +440 -0
- package/src/SpecialFunctions.ts +152 -0
- package/src/SpecialOperator.ts +28 -0
- package/src/Statistics.ts +170 -0
- package/src/StringEvaluator.ts +208 -0
- package/src/Structure.ts +481 -0
- package/src/Symbolic.ts +4313 -0
- package/src/Type.ts +48 -0
- package/src/Utilities.ts +49 -0
- package/src/Vec4.ts +98 -0
- package/src/Vector.ts +189 -0
- package/src/VectorCalculus.ts +87 -0
- package/src/VectorUtils.ts +797 -0
- package/src/differentiation-rules.ts +73 -0
- package/src/index.ts +140 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { Polygon } from "./Polygon.ts";
|
|
2
|
+
import type { Vector } from "./Vector.ts";
|
|
3
|
+
import { VectorUtils } from "./VectorUtils.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GraphUtils — helpers for turning functions and points into 2D drawing data.
|
|
7
|
+
*
|
|
8
|
+
* Ported from Mallory's ActionScript `GraphUtils`, which mutated Flash
|
|
9
|
+
* `DisplayObject`s and drew into a `Graphics` context. Since neither exists
|
|
10
|
+
* outside Flash, the drawing operations are re-targeted to emit renderer-
|
|
11
|
+
* agnostic data — placement descriptors and vector paths — which the original
|
|
12
|
+
* comments themselves noted would be a cleaner separation. `if(point.x)`-style
|
|
13
|
+
* truthiness checks (which dropped legitimate zero coordinates) are fixed.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface Placement2D {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
scaleX: number;
|
|
20
|
+
scaleY: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface BarPlacement2D {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
scaleY: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface StrokeStyle {
|
|
32
|
+
thickness: number;
|
|
33
|
+
color: number;
|
|
34
|
+
alpha: number;
|
|
35
|
+
pixelHinting: boolean;
|
|
36
|
+
scaleMode: string;
|
|
37
|
+
caps: string | null;
|
|
38
|
+
joints: string | null;
|
|
39
|
+
miterLimit: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface FillStyle {
|
|
43
|
+
color: number;
|
|
44
|
+
alpha: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface PathCommand {
|
|
48
|
+
op: "moveTo" | "lineTo";
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface Path2D {
|
|
54
|
+
stroke: StrokeStyle;
|
|
55
|
+
fill?: FillStyle;
|
|
56
|
+
commands: PathCommand[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const point = (p: Vector<number>) => ({ x: p.x as number, y: p.y as number, z: p.z as number, t: p.t as number });
|
|
60
|
+
|
|
61
|
+
export class GraphUtils {
|
|
62
|
+
/** Sample `unaryOperation` across `[xMin, xMax]` in `xStep` increments. */
|
|
63
|
+
static singleRangeVector<T>(unaryOperation: (x: number) => T, xMin = -10, xMax = 10, xStep = 1): Vector<T> {
|
|
64
|
+
const xValues = VectorUtils.arithmeticSequence(xMin, xMax, xStep);
|
|
65
|
+
return VectorUtils.transform(xValues, unaryOperation);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Placement descriptor for a point. With `altProperties`, maps each component
|
|
70
|
+
* to a named property; otherwise x/y position and (from z) a uniform scale.
|
|
71
|
+
*/
|
|
72
|
+
static placement(p: Vector<number>, altProperties: string[] | null = null): Placement2D | Record<string, number> {
|
|
73
|
+
if (altProperties) {
|
|
74
|
+
const out: Record<string, number> = {};
|
|
75
|
+
const max = Math.max(p.length, altProperties.length);
|
|
76
|
+
for (let i = 0; i < max; i++) out[altProperties[i] as string] = p[i] as number;
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
const { x, y, z } = point(p);
|
|
80
|
+
const scale = z !== 0 ? Math.abs(z) : 1;
|
|
81
|
+
return { x, y, scaleX: scale, scaleY: scale };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Placement descriptor for a bar in a bar graph. */
|
|
85
|
+
static barPlacement(p: Vector<number>, barWidth = 40): BarPlacement2D {
|
|
86
|
+
const { x, y } = point(p);
|
|
87
|
+
return { x, y: 0, width: barWidth, height: y, scaleY: y < 0 ? -1 : 1 };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static placements(
|
|
91
|
+
points: Vector<Vector<number>>,
|
|
92
|
+
altProperties: string[] | null = null,
|
|
93
|
+
): Array<Placement2D | Record<string, number>> {
|
|
94
|
+
return [...points].map((p) => GraphUtils.placement(p, altProperties));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static barPlacements(points: Vector<Vector<number>>, barWidth = 40): BarPlacement2D[] {
|
|
98
|
+
return [...points].map((p) => GraphUtils.barPlacement(p, barWidth));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A poly-line path through the given points. */
|
|
102
|
+
static vectorToCurve(
|
|
103
|
+
points: Vector<Vector<number>>,
|
|
104
|
+
thickness = 0,
|
|
105
|
+
color = 0,
|
|
106
|
+
alpha = 1,
|
|
107
|
+
pixelHinting = false,
|
|
108
|
+
scaleMode = "normal",
|
|
109
|
+
caps: string | null = null,
|
|
110
|
+
joints: string | null = null,
|
|
111
|
+
miterLimit = 3,
|
|
112
|
+
): Path2D {
|
|
113
|
+
const stroke: StrokeStyle = { thickness, color, alpha, pixelHinting, scaleMode, caps, joints, miterLimit };
|
|
114
|
+
const commands: PathCommand[] = [];
|
|
115
|
+
if (points.length > 0) {
|
|
116
|
+
const first = point(points[0] as Vector<number>);
|
|
117
|
+
commands.push({ op: "moveTo", x: first.x, y: first.y });
|
|
118
|
+
for (let i = 1; i < points.length; i++) {
|
|
119
|
+
const pt = point(points[i] as Vector<number>);
|
|
120
|
+
commands.push({ op: "lineTo", x: pt.x, y: pt.y });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { stroke, commands };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** A filled, closed path tracing a polygon's edges. */
|
|
127
|
+
static polygonToCurve(
|
|
128
|
+
polygon: Polygon,
|
|
129
|
+
strokeThickness = 0,
|
|
130
|
+
strokeColor = 0,
|
|
131
|
+
strokeAlpha = 1,
|
|
132
|
+
fillColor = 0xffffff,
|
|
133
|
+
fillAlpha = 1,
|
|
134
|
+
pixelHinting = false,
|
|
135
|
+
scaleMode = "normal",
|
|
136
|
+
caps: string | null = null,
|
|
137
|
+
joints: string | null = null,
|
|
138
|
+
miterLimit = 3,
|
|
139
|
+
): Path2D {
|
|
140
|
+
const stroke: StrokeStyle = {
|
|
141
|
+
thickness: strokeThickness,
|
|
142
|
+
color: strokeColor,
|
|
143
|
+
alpha: strokeAlpha,
|
|
144
|
+
pixelHinting,
|
|
145
|
+
scaleMode,
|
|
146
|
+
caps,
|
|
147
|
+
joints,
|
|
148
|
+
miterLimit,
|
|
149
|
+
};
|
|
150
|
+
const commands: PathCommand[] = [];
|
|
151
|
+
const start = point(polygon.vertex(0));
|
|
152
|
+
commands.push({ op: "moveTo", x: start.x, y: start.y });
|
|
153
|
+
for (let i = 1; i <= polygon.edgeCount; i++) {
|
|
154
|
+
const pt = point(polygon.vertex(i));
|
|
155
|
+
commands.push({ op: "lineTo", x: pt.x, y: pt.y });
|
|
156
|
+
}
|
|
157
|
+
return { stroke, fill: { color: fillColor, alpha: fillAlpha }, commands };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { Permutation } from "./Permutation.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GroupTheory — algorithms over a finite group given as a set of elements plus a
|
|
5
|
+
* binary operation and an equality test. Composes with {@link Structure} (pass
|
|
6
|
+
* `structure.multiply`/`structure.add`) and {@link Permutation} (pass
|
|
7
|
+
* `Permutation.compose`). Covers Cayley tables, axiom checking, element orders,
|
|
8
|
+
* generated subgroups, cosets, Lagrange's index, and group actions/orbits.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type BinOp<T> = (a: T, b: T) => T;
|
|
12
|
+
type Eq<T> = (a: T, b: T) => boolean;
|
|
13
|
+
|
|
14
|
+
const contains = <T>(arr: readonly T[], x: T, eq: Eq<T>): boolean => arr.some((y) => eq(x, y));
|
|
15
|
+
|
|
16
|
+
export class GroupTheory {
|
|
17
|
+
/** The Cayley (multiplication) table: `table[i][j] = op(elements[i], elements[j])`. */
|
|
18
|
+
static cayleyTable<T>(elements: readonly T[], op: BinOp<T>): T[][] {
|
|
19
|
+
return elements.map((a) => elements.map((b) => op(a, b)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** True when the set is closed under `op`. */
|
|
23
|
+
static isClosed<T>(elements: readonly T[], op: BinOp<T>, eq: Eq<T>): boolean {
|
|
24
|
+
for (const a of elements) for (const b of elements) if (!contains(elements, op(a, b), eq)) return false;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The identity element, or `null` if none exists. */
|
|
29
|
+
static findIdentity<T>(elements: readonly T[], op: BinOp<T>, eq: Eq<T>): T | null {
|
|
30
|
+
for (const e of elements) {
|
|
31
|
+
if (elements.every((a) => eq(op(e, a), a) && eq(op(a, e), a))) return e;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** True when `op` is associative over the set (O(n³) check). */
|
|
37
|
+
static isAssociative<T>(elements: readonly T[], op: BinOp<T>, eq: Eq<T>): boolean {
|
|
38
|
+
for (const a of elements)
|
|
39
|
+
for (const b of elements) for (const c of elements) if (!eq(op(op(a, b), c), op(a, op(b, c)))) return false;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** True when every element has an inverse relative to `identity`. */
|
|
44
|
+
static hasInverses<T>(elements: readonly T[], op: BinOp<T>, eq: Eq<T>, identity: T): boolean {
|
|
45
|
+
return elements.every((a) => elements.some((b) => eq(op(a, b), identity) && eq(op(b, a), identity)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Verify all four group axioms (closure, associativity, identity, inverses). */
|
|
49
|
+
static isGroup<T>(elements: readonly T[], op: BinOp<T>, eq: Eq<T>): boolean {
|
|
50
|
+
if (!GroupTheory.isClosed(elements, op, eq)) return false;
|
|
51
|
+
const id = GroupTheory.findIdentity(elements, op, eq);
|
|
52
|
+
if (id === null) return false;
|
|
53
|
+
return GroupTheory.isAssociative(elements, op, eq) && GroupTheory.hasInverses(elements, op, eq, id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** True when the group is also abelian (commutative). */
|
|
57
|
+
static isAbelian<T>(elements: readonly T[], op: BinOp<T>, eq: Eq<T>): boolean {
|
|
58
|
+
for (const a of elements) for (const b of elements) if (!eq(op(a, b), op(b, a))) return false;
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The order of an element: the least `k ≥ 1` with `elementᵏ = identity`. */
|
|
63
|
+
static elementOrder<T>(element: T, op: BinOp<T>, eq: Eq<T>, identity: T, max = 100000): number {
|
|
64
|
+
let acc = element;
|
|
65
|
+
for (let k = 1; k <= max; k++) {
|
|
66
|
+
if (eq(acc, identity)) return k;
|
|
67
|
+
acc = op(acc, element);
|
|
68
|
+
}
|
|
69
|
+
throw new Error("Element order exceeded the search bound (is the group finite?).");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The subgroup generated by `generators`: the closure of the generators (and
|
|
74
|
+
* the identity) under `op`. `identity` may be supplied when it is not itself a
|
|
75
|
+
* generator; otherwise closure alone recovers it for a finite group — except
|
|
76
|
+
* when `generators` is empty, in which case there is nothing to multiply out
|
|
77
|
+
* and `identity` must be supplied explicitly to get the trivial subgroup
|
|
78
|
+
* `[identity]` back (omitting it returns `[]`, since no identity can be
|
|
79
|
+
* discovered from zero elements and `op` alone).
|
|
80
|
+
*/
|
|
81
|
+
static closure<T>(generators: readonly T[], op: BinOp<T>, eq: Eq<T>, identity?: T, cap = 100000): T[] {
|
|
82
|
+
const elements: T[] = [];
|
|
83
|
+
if (identity !== undefined) elements.push(identity);
|
|
84
|
+
for (const g of generators) if (!contains(elements, g, eq)) elements.push(g);
|
|
85
|
+
let changed = true;
|
|
86
|
+
while (changed) {
|
|
87
|
+
changed = false;
|
|
88
|
+
for (let i = 0; i < elements.length; i++) {
|
|
89
|
+
for (const g of generators) {
|
|
90
|
+
const p = op(elements[i] as T, g);
|
|
91
|
+
if (!contains(elements, p, eq)) {
|
|
92
|
+
elements.push(p);
|
|
93
|
+
changed = true;
|
|
94
|
+
if (elements.length > cap) throw new Error("Closure exceeded the size cap.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return elements;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** True when `sub` is a subgroup of `group` under `op`. */
|
|
103
|
+
static isSubgroup<T>(sub: readonly T[], group: readonly T[], op: BinOp<T>, eq: Eq<T>): boolean {
|
|
104
|
+
if (!sub.every((s) => contains(group, s, eq))) return false;
|
|
105
|
+
return GroupTheory.isGroup(sub, op, eq);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The distinct left cosets `gH` of subgroup `H` in `group`. */
|
|
109
|
+
static leftCosets<T>(subgroup: readonly T[], group: readonly T[], op: BinOp<T>, eq: Eq<T>): T[][] {
|
|
110
|
+
const cosets: T[][] = [];
|
|
111
|
+
const seen: T[] = [];
|
|
112
|
+
for (const g of group) {
|
|
113
|
+
if (contains(seen, g, eq)) continue;
|
|
114
|
+
const coset = subgroup.map((h) => op(g, h));
|
|
115
|
+
cosets.push(coset);
|
|
116
|
+
for (const c of coset) if (!contains(seen, c, eq)) seen.push(c);
|
|
117
|
+
}
|
|
118
|
+
return cosets;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Lagrange index `[G : H] = |G| / |H|`. */
|
|
122
|
+
static index<T>(group: readonly T[], subgroup: readonly T[]): number {
|
|
123
|
+
return group.length / subgroup.length;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The orbit of `x` under a group action, given the generators and the action map. */
|
|
127
|
+
static orbit<T, X>(x: X, generators: readonly T[], action: (g: T, x: X) => X, eqX: Eq<X>, cap = 100000): X[] {
|
|
128
|
+
const orbit: X[] = [x];
|
|
129
|
+
let changed = true;
|
|
130
|
+
while (changed) {
|
|
131
|
+
changed = false;
|
|
132
|
+
for (let i = 0; i < orbit.length; i++) {
|
|
133
|
+
for (const g of generators) {
|
|
134
|
+
const y = action(g, orbit[i] as X);
|
|
135
|
+
if (!contains(orbit, y, eqX)) {
|
|
136
|
+
orbit.push(y);
|
|
137
|
+
changed = true;
|
|
138
|
+
if (orbit.length > cap) throw new Error("Orbit exceeded the size cap.");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return orbit;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The cyclic group Zₙ as `{0, …, n-1}` under addition mod n. */
|
|
147
|
+
static cyclicGroup(n: number): { elements: number[]; op: BinOp<number>; identity: number } {
|
|
148
|
+
return {
|
|
149
|
+
elements: Array.from({ length: n }, (_, i) => i),
|
|
150
|
+
op: (a, b) => (a + b) % n,
|
|
151
|
+
identity: 0,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The dihedral group Dₙ (order 2n): symmetries of a regular n-gon, as
|
|
157
|
+
* permutations of its n vertices `{0, …, n-1}`. n rotations `ρₖ: i ↦ (i+k)
|
|
158
|
+
* mod n` plus n reflections `σₖ: i ↦ (k-i) mod n` (reflecting through the
|
|
159
|
+
* axis fixing vertex k/2 when k is even, or the edge midpoint between
|
|
160
|
+
* ⌊k/2⌋ and ⌈k/2⌉ when k is odd). For n=3 this is exactly (element-for-
|
|
161
|
+
* element, not just isomorphic) {@link symmetricGroup}(3) — the two
|
|
162
|
+
* constructions coincide since S₃ has only 6 permutations total, matching
|
|
163
|
+
* D₃'s own order 2·3=6.
|
|
164
|
+
*/
|
|
165
|
+
static dihedralGroup(n: number): Array<Permutation<number>> {
|
|
166
|
+
const domain = Array.from({ length: n }, (_, i) => i);
|
|
167
|
+
const mod = (x: number) => ((x % n) + n) % n;
|
|
168
|
+
const rotations = Array.from(
|
|
169
|
+
{ length: n },
|
|
170
|
+
(_, k) =>
|
|
171
|
+
new Permutation(
|
|
172
|
+
domain,
|
|
173
|
+
domain.map((i) => mod(i + k)),
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
const reflections = Array.from(
|
|
177
|
+
{ length: n },
|
|
178
|
+
(_, k) =>
|
|
179
|
+
new Permutation(
|
|
180
|
+
domain,
|
|
181
|
+
domain.map((i) => mod(k - i)),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
return [...rotations, ...reflections];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The stabilizer of `x` under a group action: the elements of `group`
|
|
189
|
+
* fixing `x`. Complements {@link orbit} — the orbit-stabilizer theorem
|
|
190
|
+
* (`|orbit(x)| · |stabilizer(x)| = |G|`) holds for any finite group action.
|
|
191
|
+
*/
|
|
192
|
+
static stabilizer<T, X>(x: X, group: readonly T[], action: (g: T, x: X) => X, eqX: Eq<X>): T[] {
|
|
193
|
+
return group.filter((g) => eqX(action(g, x), x));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The symmetric group Sₙ: every permutation of `{0, …, n-1}`. */
|
|
197
|
+
static symmetricGroup(n: number): Array<Permutation<number>> {
|
|
198
|
+
const domain = Array.from({ length: n }, (_, i) => i);
|
|
199
|
+
const results: Array<Permutation<number>> = [];
|
|
200
|
+
const permute = (arr: number[], k: number) => {
|
|
201
|
+
if (k === arr.length) {
|
|
202
|
+
results.push(new Permutation(domain, [...arr]));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
for (let i = k; i < arr.length; i++) {
|
|
206
|
+
[arr[k], arr[i]] = [arr[i], arr[k]];
|
|
207
|
+
permute(arr, k + 1);
|
|
208
|
+
[arr[k], arr[i]] = [arr[i], arr[k]];
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
permute([...domain], 0);
|
|
212
|
+
return results;
|
|
213
|
+
}
|
|
214
|
+
}
|
package/src/IntUtils.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IntUtils — convert integers (and numeric strings) into English words.
|
|
3
|
+
*
|
|
4
|
+
* Ported from Mallory's ActionScript `IntUtils`. The peculiar comma-joined
|
|
5
|
+
* grouping of the original output is preserved so behaviour is unchanged, but
|
|
6
|
+
* two spelling bugs are fixed: "fourty" → "forty" and "ninty" → "ninety".
|
|
7
|
+
*
|
|
8
|
+
* Passing a string lets you exceed JS's safe-integer range, exactly as the AS3
|
|
9
|
+
* version did to sidestep ActionScript's number limits.
|
|
10
|
+
*/
|
|
11
|
+
export class IntUtils {
|
|
12
|
+
private static readonly ONES = [
|
|
13
|
+
"zero",
|
|
14
|
+
"one",
|
|
15
|
+
"two",
|
|
16
|
+
"three",
|
|
17
|
+
"four",
|
|
18
|
+
"five",
|
|
19
|
+
"six",
|
|
20
|
+
"seven",
|
|
21
|
+
"eight",
|
|
22
|
+
"nine",
|
|
23
|
+
"ten",
|
|
24
|
+
"eleven",
|
|
25
|
+
"twelve",
|
|
26
|
+
"thirteen",
|
|
27
|
+
"fourteen",
|
|
28
|
+
"fifteen",
|
|
29
|
+
"sixteen",
|
|
30
|
+
"seventeen",
|
|
31
|
+
"eighteen",
|
|
32
|
+
"nineteen",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// index 2..9 => tens words (forty/ninety fixed from AS3 "fourty"/"ninty").
|
|
36
|
+
private static readonly TENS: Record<number, string> = {
|
|
37
|
+
2: "twenty",
|
|
38
|
+
3: "thirty",
|
|
39
|
+
4: "forty",
|
|
40
|
+
5: "fifty",
|
|
41
|
+
6: "sixty",
|
|
42
|
+
7: "seventy",
|
|
43
|
+
8: "eighty",
|
|
44
|
+
9: "ninety",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Accepted group suffixes (units, thousands, millions, …). */
|
|
48
|
+
static readonly ActualNames = [
|
|
49
|
+
"",
|
|
50
|
+
"-thousand",
|
|
51
|
+
"-million",
|
|
52
|
+
"-billion",
|
|
53
|
+
"-trillion",
|
|
54
|
+
"-quadrillion",
|
|
55
|
+
"-quintillion",
|
|
56
|
+
"-sextillion",
|
|
57
|
+
"-septillion",
|
|
58
|
+
"-octillion",
|
|
59
|
+
"-nonillion",
|
|
60
|
+
"-decillion",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/** Whimsical fall-back names once the real ones run out. */
|
|
64
|
+
static readonly FakeNames = ["ZILLION!!!", "-BAJILLION!!!", "-AGAZILLION!!!"];
|
|
65
|
+
|
|
66
|
+
private static toInt(s: string): number {
|
|
67
|
+
const n = Number.parseInt(s, 10);
|
|
68
|
+
return Number.isNaN(n) ? 0 : n;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Words for the last three digits (0–999) of a number. */
|
|
72
|
+
static toWordsTriple(num = 0): string {
|
|
73
|
+
if (num < 20) return IntUtils.ONES[num] ?? "";
|
|
74
|
+
|
|
75
|
+
const numString = num.toString();
|
|
76
|
+
const lowerPlace = IntUtils.toInt(numString.charAt(numString.length - 1));
|
|
77
|
+
const tensPlace = IntUtils.toInt(numString.charAt(numString.length - 2));
|
|
78
|
+
|
|
79
|
+
let intString = "";
|
|
80
|
+
if (lowerPlace !== 0) {
|
|
81
|
+
const tens = IntUtils.TENS[tensPlace];
|
|
82
|
+
if (tens) intString = `${tens}-${IntUtils.toWordsTriple(lowerPlace)}`;
|
|
83
|
+
} else {
|
|
84
|
+
intString = IntUtils.TENS[tensPlace] ?? "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (num < 100) return intString;
|
|
88
|
+
|
|
89
|
+
const lastTwo = IntUtils.toInt(`${tensPlace}${lowerPlace}`);
|
|
90
|
+
const hundredsPlace = IntUtils.toInt(numString.charAt(numString.length - 3));
|
|
91
|
+
|
|
92
|
+
if (lastTwo !== 0) {
|
|
93
|
+
return `${IntUtils.toWordsTriple(hundredsPlace)}-hundred-${IntUtils.toWordsTriple(lastTwo)}`;
|
|
94
|
+
}
|
|
95
|
+
return `${IntUtils.toWordsTriple(hundredsPlace)}-hundred`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Suffix for the group at `index` (0 = units, 1 = thousands, …). */
|
|
99
|
+
static numberGroupNames(index = 0): string {
|
|
100
|
+
return index < IntUtils.ActualNames.length
|
|
101
|
+
? (IntUtils.ActualNames[index] as string)
|
|
102
|
+
: (IntUtils.FakeNames[0] as string);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Convert an integer (or its string form) to English words. */
|
|
106
|
+
static toWords(num: number | string): string {
|
|
107
|
+
let negative = false;
|
|
108
|
+
let value = num;
|
|
109
|
+
|
|
110
|
+
if (typeof value === "string") {
|
|
111
|
+
if (value.charAt(0) === "-") {
|
|
112
|
+
negative = true;
|
|
113
|
+
value = value.substring(1);
|
|
114
|
+
}
|
|
115
|
+
} else if (value < 0) {
|
|
116
|
+
negative = true;
|
|
117
|
+
value = value * -1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let numString = value.toString().split(",").join("");
|
|
121
|
+
const groups: number[] = [];
|
|
122
|
+
while (numString.length > 0) {
|
|
123
|
+
const take = numString.length > 2 ? 3 : numString.length > 1 ? 2 : 1;
|
|
124
|
+
groups.unshift(IntUtils.toInt(numString.substring(numString.length - take)));
|
|
125
|
+
numString = numString.substr(0, numString.length - take);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (groups.length === 1) {
|
|
129
|
+
const single = IntUtils.toWordsTriple(groups[0]);
|
|
130
|
+
return negative ? `negative ${single}` : single;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const parts = groups
|
|
134
|
+
.map((g, i) => (g === 0 ? null : IntUtils.toWordsTriple(g) + IntUtils.numberGroupNames(groups.length - i - 1)))
|
|
135
|
+
.filter((p): p is string => p !== null);
|
|
136
|
+
|
|
137
|
+
const joined = parts.join(",");
|
|
138
|
+
return negative ? `negative ${joined}` : joined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Irregular ordinal suffixes for the last word of a words-string (one..twelve
|
|
142
|
+
// don't all follow the plain "+th" rule: eight/nine/twelve drop a letter).
|
|
143
|
+
private static readonly ORDINAL_IRREGULARS: Record<string, string> = {
|
|
144
|
+
one: "first",
|
|
145
|
+
two: "second",
|
|
146
|
+
three: "third",
|
|
147
|
+
four: "fourth",
|
|
148
|
+
five: "fifth",
|
|
149
|
+
eight: "eighth",
|
|
150
|
+
nine: "ninth",
|
|
151
|
+
twelve: "twelfth",
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Ordinal words: "twenty-first", "one-hundredth", "twentieth", "eighth", … */
|
|
155
|
+
static toWordsOrdinal(num: number | string): string {
|
|
156
|
+
const originalString = IntUtils.toWords(num);
|
|
157
|
+
const lastHyphen = originalString.lastIndexOf("-");
|
|
158
|
+
const prefix = lastHyphen === -1 ? "" : originalString.substring(0, lastHyphen + 1);
|
|
159
|
+
const lastWord = lastHyphen === -1 ? originalString : originalString.substring(lastHyphen + 1);
|
|
160
|
+
|
|
161
|
+
const irregular = IntUtils.ORDINAL_IRREGULARS[lastWord];
|
|
162
|
+
if (irregular) return `${prefix}${irregular}`;
|
|
163
|
+
|
|
164
|
+
// Tens words (twenty, thirty, … ninety) take "-ieth" in place of the "y".
|
|
165
|
+
if (lastWord.endsWith("y")) return `${prefix}${lastWord.slice(0, -1)}ieth`;
|
|
166
|
+
|
|
167
|
+
return `${originalString}th`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Ordinal words with a leading "one-" stripped ("one-hundredth" → "hundredth"). */
|
|
171
|
+
static toWordsOrdinalLazy(num: number | string): string {
|
|
172
|
+
const ordinal = IntUtils.toWordsOrdinal(num);
|
|
173
|
+
if (ordinal.substring(0, 4) === "one-") return ordinal.substring(4);
|
|
174
|
+
return ordinal;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Convert a decimal number (or string) to English words. */
|
|
178
|
+
static toWordsDecimal(num: number | string): string | null {
|
|
179
|
+
const str = num.toString();
|
|
180
|
+
if (str.lastIndexOf(".") === -1) return IntUtils.toWords(str);
|
|
181
|
+
|
|
182
|
+
const integer = str.split(".")[0] ?? "";
|
|
183
|
+
const decimal = str.split(".")[1] ?? "";
|
|
184
|
+
|
|
185
|
+
const pluralFraction = decimal.length > 0 && decimal.charAt(decimal.length - 1) !== "1";
|
|
186
|
+
|
|
187
|
+
let finalString: string;
|
|
188
|
+
if ((!integer || IntUtils.toWords(integer) === "zero") && decimal.length > 0) {
|
|
189
|
+
finalString = `${IntUtils.toWords(decimal)} ${IntUtils.toWordsOrdinalLazy(10 ** decimal.length)}`;
|
|
190
|
+
} else if ((!decimal || IntUtils.toWords(decimal) === "zero") && integer.length > 0) {
|
|
191
|
+
finalString = IntUtils.toWords(integer);
|
|
192
|
+
} else if (!(integer || decimal)) {
|
|
193
|
+
return null;
|
|
194
|
+
} else {
|
|
195
|
+
finalString = `${IntUtils.toWords(integer)} and ${IntUtils.toWords(decimal)} ${IntUtils.toWordsOrdinalLazy(10 ** decimal.length)}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return pluralFraction ? `${finalString}s` : finalString;
|
|
199
|
+
}
|
|
200
|
+
}
|