@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.
Files changed (242) hide show
  1. package/README.md +165 -0
  2. package/dist/Bivector4.d.ts +52 -0
  3. package/dist/Bivector4.d.ts.map +1 -0
  4. package/dist/Bivector4.js +105 -0
  5. package/dist/Bivector4.js.map +1 -0
  6. package/dist/CellGraph.d.ts +259 -0
  7. package/dist/CellGraph.d.ts.map +1 -0
  8. package/dist/CellGraph.js +637 -0
  9. package/dist/CellGraph.js.map +1 -0
  10. package/dist/Clifford4Internal.d.ts +35 -0
  11. package/dist/Clifford4Internal.d.ts.map +1 -0
  12. package/dist/Clifford4Internal.js +86 -0
  13. package/dist/Clifford4Internal.js.map +1 -0
  14. package/dist/Combinatorics.d.ts +61 -0
  15. package/dist/Combinatorics.d.ts.map +1 -0
  16. package/dist/Combinatorics.js +186 -0
  17. package/dist/Combinatorics.js.map +1 -0
  18. package/dist/CombinatoricsGenerators.d.ts +44 -0
  19. package/dist/CombinatoricsGenerators.d.ts.map +1 -0
  20. package/dist/CombinatoricsGenerators.js +148 -0
  21. package/dist/CombinatoricsGenerators.js.map +1 -0
  22. package/dist/ComplexMath.d.ts +46 -0
  23. package/dist/ComplexMath.d.ts.map +1 -0
  24. package/dist/ComplexMath.js +98 -0
  25. package/dist/ComplexMath.js.map +1 -0
  26. package/dist/ComplexNumber.d.ts +174 -0
  27. package/dist/ComplexNumber.d.ts.map +1 -0
  28. package/dist/ComplexNumber.js +443 -0
  29. package/dist/ComplexNumber.js.map +1 -0
  30. package/dist/Cycle.d.ts +42 -0
  31. package/dist/Cycle.d.ts.map +1 -0
  32. package/dist/Cycle.js +120 -0
  33. package/dist/Cycle.js.map +1 -0
  34. package/dist/Decimal.d.ts +45 -0
  35. package/dist/Decimal.d.ts.map +1 -0
  36. package/dist/Decimal.js +135 -0
  37. package/dist/Decimal.js.map +1 -0
  38. package/dist/Distributions.d.ts +55 -0
  39. package/dist/Distributions.d.ts.map +1 -0
  40. package/dist/Distributions.js +222 -0
  41. package/dist/Distributions.js.map +1 -0
  42. package/dist/DualNumber.d.ts +42 -0
  43. package/dist/DualNumber.d.ts.map +1 -0
  44. package/dist/DualNumber.js +91 -0
  45. package/dist/DualNumber.js.map +1 -0
  46. package/dist/Environment.d.ts +44 -0
  47. package/dist/Environment.d.ts.map +1 -0
  48. package/dist/Environment.js +129 -0
  49. package/dist/Environment.js.map +1 -0
  50. package/dist/Expression.d.ts +19 -0
  51. package/dist/Expression.d.ts.map +1 -0
  52. package/dist/Expression.js +43 -0
  53. package/dist/Expression.js.map +1 -0
  54. package/dist/FFT.d.ts +21 -0
  55. package/dist/FFT.d.ts.map +1 -0
  56. package/dist/FFT.js +136 -0
  57. package/dist/FFT.js.map +1 -0
  58. package/dist/GeometricAlgebra4.d.ts +73 -0
  59. package/dist/GeometricAlgebra4.d.ts.map +1 -0
  60. package/dist/GeometricAlgebra4.js +115 -0
  61. package/dist/GeometricAlgebra4.js.map +1 -0
  62. package/dist/Geometry.d.ts +41 -0
  63. package/dist/Geometry.d.ts.map +1 -0
  64. package/dist/Geometry.js +167 -0
  65. package/dist/Geometry.js.map +1 -0
  66. package/dist/Graph.d.ts +72 -0
  67. package/dist/Graph.d.ts.map +1 -0
  68. package/dist/Graph.js +337 -0
  69. package/dist/Graph.js.map +1 -0
  70. package/dist/Graph3DUtils.d.ts +80 -0
  71. package/dist/Graph3DUtils.d.ts.map +1 -0
  72. package/dist/Graph3DUtils.js +156 -0
  73. package/dist/Graph3DUtils.js.map +1 -0
  74. package/dist/GraphUtils.d.ts +67 -0
  75. package/dist/GraphUtils.d.ts.map +1 -0
  76. package/dist/GraphUtils.js +72 -0
  77. package/dist/GraphUtils.js.map +1 -0
  78. package/dist/GroupTheory.d.ts +73 -0
  79. package/dist/GroupTheory.d.ts.map +1 -0
  80. package/dist/GroupTheory.js +194 -0
  81. package/dist/GroupTheory.js.map +1 -0
  82. package/dist/IntUtils.d.ts +33 -0
  83. package/dist/IntUtils.d.ts.map +1 -0
  84. package/dist/IntUtils.js +185 -0
  85. package/dist/IntUtils.js.map +1 -0
  86. package/dist/Interval.d.ts +44 -0
  87. package/dist/Interval.d.ts.map +1 -0
  88. package/dist/Interval.js +165 -0
  89. package/dist/Interval.js.map +1 -0
  90. package/dist/Lattice.d.ts +48 -0
  91. package/dist/Lattice.d.ts.map +1 -0
  92. package/dist/Lattice.js +65 -0
  93. package/dist/Lattice.js.map +1 -0
  94. package/dist/Logic.d.ts +22 -0
  95. package/dist/Logic.d.ts.map +1 -0
  96. package/dist/Logic.js +34 -0
  97. package/dist/Logic.js.map +1 -0
  98. package/dist/MatrixMath.d.ts +71 -0
  99. package/dist/MatrixMath.d.ts.map +1 -0
  100. package/dist/MatrixMath.js +302 -0
  101. package/dist/MatrixMath.js.map +1 -0
  102. package/dist/NumberTheory.d.ts +49 -0
  103. package/dist/NumberTheory.d.ts.map +1 -0
  104. package/dist/NumberTheory.js +230 -0
  105. package/dist/NumberTheory.js.map +1 -0
  106. package/dist/Numerical.d.ts +85 -0
  107. package/dist/Numerical.d.ts.map +1 -0
  108. package/dist/Numerical.js +343 -0
  109. package/dist/Numerical.js.map +1 -0
  110. package/dist/Permutation.d.ts +43 -0
  111. package/dist/Permutation.d.ts.map +1 -0
  112. package/dist/Permutation.js +142 -0
  113. package/dist/Permutation.js.map +1 -0
  114. package/dist/Polygon.d.ts +64 -0
  115. package/dist/Polygon.d.ts.map +1 -0
  116. package/dist/Polygon.js +221 -0
  117. package/dist/Polygon.js.map +1 -0
  118. package/dist/PolynomialRing.d.ts +74 -0
  119. package/dist/PolynomialRing.d.ts.map +1 -0
  120. package/dist/PolynomialRing.js +231 -0
  121. package/dist/PolynomialRing.js.map +1 -0
  122. package/dist/Quaternion.d.ts +37 -0
  123. package/dist/Quaternion.d.ts.map +1 -0
  124. package/dist/Quaternion.js +106 -0
  125. package/dist/Quaternion.js.map +1 -0
  126. package/dist/Rational.d.ts +34 -0
  127. package/dist/Rational.d.ts.map +1 -0
  128. package/dist/Rational.js +136 -0
  129. package/dist/Rational.js.map +1 -0
  130. package/dist/RealMath.d.ts +112 -0
  131. package/dist/RealMath.d.ts.map +1 -0
  132. package/dist/RealMath.js +262 -0
  133. package/dist/RealMath.js.map +1 -0
  134. package/dist/Rotor4.d.ts +180 -0
  135. package/dist/Rotor4.d.ts.map +1 -0
  136. package/dist/Rotor4.js +388 -0
  137. package/dist/Rotor4.js.map +1 -0
  138. package/dist/SpecialFunctions.d.ts +32 -0
  139. package/dist/SpecialFunctions.d.ts.map +1 -0
  140. package/dist/SpecialFunctions.js +161 -0
  141. package/dist/SpecialFunctions.js.map +1 -0
  142. package/dist/SpecialOperator.d.ts +21 -0
  143. package/dist/SpecialOperator.d.ts.map +1 -0
  144. package/dist/SpecialOperator.js +26 -0
  145. package/dist/SpecialOperator.js.map +1 -0
  146. package/dist/Statistics.d.ts +50 -0
  147. package/dist/Statistics.d.ts.map +1 -0
  148. package/dist/Statistics.js +131 -0
  149. package/dist/Statistics.js.map +1 -0
  150. package/dist/StringEvaluator.d.ts +48 -0
  151. package/dist/StringEvaluator.d.ts.map +1 -0
  152. package/dist/StringEvaluator.js +190 -0
  153. package/dist/StringEvaluator.js.map +1 -0
  154. package/dist/Structure.d.ts +130 -0
  155. package/dist/Structure.d.ts.map +1 -0
  156. package/dist/Structure.js +394 -0
  157. package/dist/Structure.js.map +1 -0
  158. package/dist/Symbolic.d.ts +501 -0
  159. package/dist/Symbolic.d.ts.map +1 -0
  160. package/dist/Symbolic.js +4077 -0
  161. package/dist/Symbolic.js.map +1 -0
  162. package/dist/Type.d.ts +31 -0
  163. package/dist/Type.d.ts.map +1 -0
  164. package/dist/Type.js +47 -0
  165. package/dist/Type.js.map +1 -0
  166. package/dist/Utilities.d.ts +27 -0
  167. package/dist/Utilities.d.ts.map +1 -0
  168. package/dist/Utilities.js +47 -0
  169. package/dist/Utilities.js.map +1 -0
  170. package/dist/Vec4.d.ts +39 -0
  171. package/dist/Vec4.d.ts.map +1 -0
  172. package/dist/Vec4.js +82 -0
  173. package/dist/Vec4.js.map +1 -0
  174. package/dist/Vector.d.ts +83 -0
  175. package/dist/Vector.d.ts.map +1 -0
  176. package/dist/Vector.js +173 -0
  177. package/dist/Vector.js.map +1 -0
  178. package/dist/VectorCalculus.d.ts +42 -0
  179. package/dist/VectorCalculus.d.ts.map +1 -0
  180. package/dist/VectorCalculus.js +82 -0
  181. package/dist/VectorCalculus.js.map +1 -0
  182. package/dist/VectorUtils.d.ts +172 -0
  183. package/dist/VectorUtils.d.ts.map +1 -0
  184. package/dist/VectorUtils.js +706 -0
  185. package/dist/VectorUtils.js.map +1 -0
  186. package/dist/differentiation-rules.d.ts +21 -0
  187. package/dist/differentiation-rules.d.ts.map +1 -0
  188. package/dist/differentiation-rules.js +52 -0
  189. package/dist/differentiation-rules.js.map +1 -0
  190. package/dist/index.d.ts +52 -0
  191. package/dist/index.d.ts.map +1 -0
  192. package/dist/index.js +66 -0
  193. package/dist/index.js.map +1 -0
  194. package/package.json +68 -0
  195. package/src/Bivector4.ts +136 -0
  196. package/src/CellGraph.ts +669 -0
  197. package/src/Clifford4Internal.ts +90 -0
  198. package/src/Combinatorics.ts +183 -0
  199. package/src/CombinatoricsGenerators.ts +144 -0
  200. package/src/ComplexMath.ts +162 -0
  201. package/src/ComplexNumber.ts +491 -0
  202. package/src/Cycle.ts +126 -0
  203. package/src/Decimal.ts +145 -0
  204. package/src/Distributions.ts +269 -0
  205. package/src/DualNumber.ts +105 -0
  206. package/src/Environment.ts +127 -0
  207. package/src/Expression.ts +48 -0
  208. package/src/FFT.ts +146 -0
  209. package/src/GeometricAlgebra4.ts +157 -0
  210. package/src/Geometry.ts +173 -0
  211. package/src/Graph.ts +337 -0
  212. package/src/Graph3DUtils.ts +269 -0
  213. package/src/GraphUtils.ts +159 -0
  214. package/src/GroupTheory.ts +214 -0
  215. package/src/IntUtils.ts +200 -0
  216. package/src/Interval.ts +171 -0
  217. package/src/Lattice.ts +87 -0
  218. package/src/Logic.ts +38 -0
  219. package/src/MatrixMath.ts +326 -0
  220. package/src/NumberTheory.ts +228 -0
  221. package/src/Numerical.ts +390 -0
  222. package/src/Permutation.ts +148 -0
  223. package/src/Polygon.ts +229 -0
  224. package/src/PolynomialRing.ts +242 -0
  225. package/src/Quaternion.ts +125 -0
  226. package/src/Rational.ts +148 -0
  227. package/src/RealMath.ts +346 -0
  228. package/src/Rotor4.ts +440 -0
  229. package/src/SpecialFunctions.ts +152 -0
  230. package/src/SpecialOperator.ts +28 -0
  231. package/src/Statistics.ts +170 -0
  232. package/src/StringEvaluator.ts +208 -0
  233. package/src/Structure.ts +481 -0
  234. package/src/Symbolic.ts +4313 -0
  235. package/src/Type.ts +48 -0
  236. package/src/Utilities.ts +49 -0
  237. package/src/Vec4.ts +98 -0
  238. package/src/Vector.ts +189 -0
  239. package/src/VectorCalculus.ts +87 -0
  240. package/src/VectorUtils.ts +797 -0
  241. package/src/differentiation-rules.ts +73 -0
  242. package/src/index.ts +140 -0
@@ -0,0 +1,148 @@
1
+ import { Cycle } from "./Cycle.ts";
2
+ import { NumberTheory } from "./NumberTheory.ts";
3
+
4
+ /**
5
+ * Permutation — a bijection described by a domain array and the codomain array
6
+ * it maps onto (element-wise). Ported from Mallory's ActionScript `Permutation`.
7
+ *
8
+ * Bug fixes from the AS3 original:
9
+ * - the constructor silently ignored mismatched-length inputs; it now throws.
10
+ * - `order` wrapped cycle lengths in `ComplexNumber` then read `.value` off an
11
+ * `int` (yielding `undefined`); it now takes the LCM of the plain lengths and
12
+ * returns 1 for the identity.
13
+ * - `cycles` no longer mutates any shared state (it works on local copies).
14
+ */
15
+ export class Permutation<T = unknown> {
16
+ private readonly _domain: T[];
17
+ private readonly _coDomain: T[];
18
+ // Lazily-built domain -> codomain lookup, cached on first use. The instance is otherwise
19
+ // immutable (domain/coDomain are never mutated after construction), so this is safe to cache
20
+ // for the lifetime of the object.
21
+ private _lookup: Map<T, T> | null = null;
22
+
23
+ constructor(input: T[], output: T[]) {
24
+ if (input.length !== output.length) {
25
+ throw new Error("Permutation domain and codomain must have equal length.");
26
+ }
27
+ this._domain = [...input];
28
+ this._coDomain = [...output];
29
+ }
30
+
31
+ get domain(): T[] {
32
+ return [...this._domain];
33
+ }
34
+
35
+ get coDomain(): T[] {
36
+ return [...this._coDomain];
37
+ }
38
+
39
+ /** The domain -> codomain lookup map, built once (on first access) and reused thereafter. */
40
+ private get lookup(): Map<T, T> {
41
+ if (this._lookup === null) {
42
+ this._lookup = new Map(this._domain.map((d, i) => [d, this._coDomain[i] as T]));
43
+ }
44
+ return this._lookup;
45
+ }
46
+
47
+ /** Map an element through the permutation (identity outside the domain). */
48
+ apply(element: T): T {
49
+ const map = this.lookup;
50
+ return map.has(element) ? (map.get(element) as T) : element;
51
+ }
52
+
53
+ /** The inverse permutation (swap domain and codomain). */
54
+ inverse(): Permutation<T> {
55
+ return new Permutation(this.coDomain, this.domain);
56
+ }
57
+
58
+ /** Raise the permutation to an integer power, via binary exponentiation (`O(log p)` compositions). */
59
+ power(pow: number): Permutation<T> {
60
+ if (pow === 0) return Permutation.Identity as Permutation<T>;
61
+ if (pow === 1) return this;
62
+ if (pow === -1) return this.inverse();
63
+
64
+ let base: Permutation<T> = this;
65
+ let p = pow;
66
+ if (pow < -1) {
67
+ base = this.inverse();
68
+ p = -pow;
69
+ }
70
+
71
+ // Repeated squaring: result accumulates the product of base^(2^i) for each set bit of p.
72
+ let result: Permutation<T> | undefined;
73
+ while (p > 0) {
74
+ if (p % 2 === 1) result = result === undefined ? base : Permutation.compose(result, base);
75
+ p = Math.floor(p / 2);
76
+ if (p > 0) base = Permutation.compose(base, base);
77
+ }
78
+ return result as Permutation<T>;
79
+ }
80
+
81
+ toString(): string {
82
+ const parts = this._domain.map((d, i) => `${d} -> ${this._coDomain[i]}`);
83
+ return `(${parts.join(",")})`;
84
+ }
85
+
86
+ /** Decompose into disjoint cycles. */
87
+ cycles(): Array<Cycle<T>> {
88
+ const domain = [...this._domain];
89
+ const coDomain = [...this._coDomain];
90
+ const result: Array<Cycle<T>> = [];
91
+
92
+ while (domain.length > 0) {
93
+ const start = domain[0] as T;
94
+ let current = start;
95
+ const tempArray: T[] = [];
96
+ do {
97
+ tempArray.push(current);
98
+ current = this.apply(current);
99
+ for (let i = 0; i < coDomain.length; i++) {
100
+ if (current === coDomain[i]) {
101
+ domain.splice(i, 1);
102
+ coDomain.splice(i, 1);
103
+ break;
104
+ }
105
+ }
106
+ } while (current !== start);
107
+ result.push(new Cycle(tempArray));
108
+ }
109
+ return result;
110
+ }
111
+
112
+ /** The order of the permutation (LCM of its cycle lengths). */
113
+ order(): number {
114
+ const lengths = this.cycles().map((c) => c.length);
115
+ if (lengths.length === 0) return 1;
116
+ return Number(NumberTheory.lcmList(lengths));
117
+ }
118
+
119
+ static readonly Identity: Permutation<unknown> = new Permutation<unknown>([], []);
120
+
121
+ /** Compose two permutations: `(sigma ∘ tao)(x) = sigma(tao(x))`. */
122
+ static compose<T>(sigma: Permutation<T>, tao: Permutation<T>): Permutation<T> {
123
+ // Set preserves insertion order and dedupes in O(n), matching the old O(n^2)
124
+ // lastIndexOf/splice loop's "first occurrence wins" semantics exactly.
125
+ const newDomain = Array.from(new Set(sigma.domain.concat(tao.domain)));
126
+ const newCoDomain = newDomain.map((x) => sigma.apply(tao.apply(x)));
127
+ return new Permutation(newDomain, newCoDomain);
128
+ }
129
+
130
+ /** Compose a list of permutations left-to-right. */
131
+ static composeList<T>(list: Array<Permutation<T>>): Permutation<T> {
132
+ if (list.length === 0) return Permutation.Identity as Permutation<T>;
133
+ if (list.length === 1) return list[0] as Permutation<T>;
134
+ return list.reduce((acc, p) => Permutation.compose(acc, p));
135
+ }
136
+
137
+ /** Structural equality (same action on every domain element of either). */
138
+ static equal<T>(sigma: Permutation<T>, tao: Permutation<T>): boolean {
139
+ for (const d of sigma.domain) if (sigma.apply(d) !== tao.apply(d)) return false;
140
+ for (const d of tao.domain) if (tao.apply(d) !== sigma.apply(d)) return false;
141
+ return true;
142
+ }
143
+
144
+ /** Whether two permutations commute. */
145
+ static commute<T>(sigma: Permutation<T>, tao: Permutation<T>): boolean {
146
+ return Permutation.equal(Permutation.compose(sigma, tao), Permutation.compose(tao, sigma));
147
+ }
148
+ }
package/src/Polygon.ts ADDED
@@ -0,0 +1,229 @@
1
+ import { NumberTheory } from "./NumberTheory.ts";
2
+ import { distanceVector } from "./RealMath.ts";
3
+ import { Vector } from "./Vector.ts";
4
+
5
+ /** A 2D point stored as a {@link Vector} `[x, y]`. */
6
+ type Point = Vector<number>;
7
+
8
+ /**
9
+ * Polygon — an ordered list of vertex points ({@link Vector} subclass). Ported
10
+ * from Mallory's ActionScript `Polygon`.
11
+ *
12
+ * Bug fixes from the AS3 original:
13
+ * - `perimeter` accumulated into a `uint`, truncating the fractional length.
14
+ * - `area` only summed two triangles, so it was wrong for pentagons and up; it
15
+ * now uses the shoelace formula, correct for any simple polygon.
16
+ * - `angle` was stubbed to `return 0` (with dead code calling classes that do
17
+ * not exist); it now computes the interior angle via the law of cosines it
18
+ * documented.
19
+ */
20
+ export class Polygon extends Vector<Point> {
21
+ /** The vertex at `index`, wrapping around the polygon. */
22
+ vertex(index: number): Point {
23
+ return this[Number(NumberTheory.mod(index, this.length))] as Point;
24
+ }
25
+
26
+ get vertexCount(): number {
27
+ return this.length;
28
+ }
29
+
30
+ get edgeCount(): number {
31
+ if (this.vertexCount < 3) return this.length - 1;
32
+ return this.vertexCount;
33
+ }
34
+
35
+ /** The edge starting at `index` as a two-vertex polygon. */
36
+ edge(index: number): Polygon {
37
+ return new Polygon(this.vertex(index), this.vertex(index + 1));
38
+ }
39
+
40
+ /** Total perimeter length (bug fix: no longer truncated to an integer). */
41
+ perimeter(): number {
42
+ let per = 0;
43
+ for (let i = 0; i < this.edgeCount; i++) {
44
+ per += distanceVector(this.vertex(i), this.vertex(i + 1));
45
+ }
46
+ return per;
47
+ }
48
+
49
+ /** Heron's-formula area of the triangle on the first three vertices. */
50
+ private triArea(): number {
51
+ const a = distanceVector(this.vertex(0), this.vertex(1));
52
+ const b = distanceVector(this.vertex(1), this.vertex(2));
53
+ const c = distanceVector(this.vertex(2), this.vertex(0));
54
+ const s = (a + b + c) / 2;
55
+ return Math.sqrt(s * (s - a) * (s - b) * (s - c));
56
+ }
57
+
58
+ /**
59
+ * Area of the polygon via the shoelace formula (bug fix: the AS3 version only
60
+ * summed two triangles and was wrong for polygons with more than four sides).
61
+ */
62
+ area(): number {
63
+ if (this.vertexCount < 3) return 0;
64
+ if (this.vertexCount === 3) return this.triArea();
65
+ let sum = 0;
66
+ for (let i = 0; i < this.vertexCount; i++) {
67
+ const current = this.vertex(i);
68
+ const next = this.vertex(i + 1);
69
+ sum += (current.x as number) * (next.y as number) - (next.x as number) * (current.y as number);
70
+ }
71
+ return Math.abs(sum) / 2;
72
+ }
73
+
74
+ /**
75
+ * The area centroid (center of mass) as an `[x, y]` point. Complements
76
+ * {@link perimeter} and {@link area}. Falls back to the vertex average for a
77
+ * degenerate (zero-area) polygon.
78
+ */
79
+ centroid(): Point {
80
+ let signedArea = 0;
81
+ let cx = 0;
82
+ let cy = 0;
83
+ for (let i = 0; i < this.vertexCount; i++) {
84
+ const cur = this.vertex(i);
85
+ const next = this.vertex(i + 1);
86
+ const cross = (cur.x as number) * (next.y as number) - (next.x as number) * (cur.y as number);
87
+ signedArea += cross;
88
+ cx += ((cur.x as number) + (next.x as number)) * cross;
89
+ cy += ((cur.y as number) + (next.y as number)) * cross;
90
+ }
91
+ if (signedArea === 0) {
92
+ // Degenerate: average the vertices.
93
+ let sx = 0;
94
+ let sy = 0;
95
+ for (let i = 0; i < this.vertexCount; i++) {
96
+ sx += this.vertex(i).x as number;
97
+ sy += this.vertex(i).y as number;
98
+ }
99
+ const n = this.vertexCount || 1;
100
+ return Vector.fromArray([sx / n, sy / n]);
101
+ }
102
+ return Vector.fromArray([cx / (3 * signedArea), cy / (3 * signedArea)]);
103
+ }
104
+
105
+ /**
106
+ * Interior angle (radians) at vertex `i`, via the law of cosines
107
+ * (bug fix: the AS3 method always returned 0).
108
+ */
109
+ angle(i: number): number {
110
+ const ab = distanceVector(this.vertex(i), this.vertex(i + 1));
111
+ const bc = distanceVector(this.vertex(i), this.vertex(i - 1));
112
+ const ac = distanceVector(this.vertex(i + 1), this.vertex(i - 1));
113
+ return Math.acos((ab * ab + bc * bc - ac * ac) / (2 * ab * bc));
114
+ }
115
+
116
+ /** True when `point` lies inside the polygon (even-odd ray-casting rule). */
117
+ contains(point: Point): boolean {
118
+ const px = point.x as number;
119
+ const py = point.y as number;
120
+ let inside = false;
121
+ const n = this.vertexCount;
122
+ for (let i = 0, j = n - 1; i < n; j = i++) {
123
+ const vi = this.vertex(i);
124
+ const vj = this.vertex(j);
125
+ const xi = vi.x as number;
126
+ const yi = vi.y as number;
127
+ const xj = vj.x as number;
128
+ const yj = vj.y as number;
129
+ if (yi > py !== yj > py && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) inside = !inside;
130
+ }
131
+ return inside;
132
+ }
133
+
134
+ /** True when the polygon is convex (all turns share the same orientation). */
135
+ isConvex(): boolean {
136
+ const n = this.vertexCount;
137
+ if (n < 3) return false;
138
+ let sign = 0;
139
+ for (let i = 0; i < n; i++) {
140
+ const a = this.vertex(i);
141
+ const b = this.vertex(i + 1);
142
+ const c = this.vertex(i + 2);
143
+ const cross =
144
+ ((b.x as number) - (a.x as number)) * ((c.y as number) - (b.y as number)) -
145
+ ((b.y as number) - (a.y as number)) * ((c.x as number) - (b.x as number));
146
+ if (cross !== 0) {
147
+ const s = Math.sign(cross);
148
+ if (sign === 0) sign = s;
149
+ else if (s !== sign) return false;
150
+ }
151
+ }
152
+ return true;
153
+ }
154
+
155
+ /** True when no two non-adjacent edges cross (the polygon is simple). */
156
+ isSimple(): boolean {
157
+ const n = this.vertexCount;
158
+ if (n < 3) return false;
159
+ for (let i = 0; i < n; i++) {
160
+ const a1 = this.vertex(i);
161
+ const a2 = this.vertex(i + 1);
162
+ for (let j = i + 1; j < n; j++) {
163
+ // Skip edges that share a vertex (adjacent, or the wrap-around pair).
164
+ if (j === i || j === i + 1 || (i === 0 && j === n - 1)) continue;
165
+ const b1 = this.vertex(j);
166
+ const b2 = this.vertex(j + 1);
167
+ if (Polygon.segmentsIntersect(a1, a2, b1, b2)) return false;
168
+ }
169
+ }
170
+ return true;
171
+ }
172
+
173
+ private static orient(a: Point, b: Point, c: Point): number {
174
+ return (
175
+ ((b.x as number) - (a.x as number)) * ((c.y as number) - (a.y as number)) -
176
+ ((b.y as number) - (a.y as number)) * ((c.x as number) - (a.x as number))
177
+ );
178
+ }
179
+
180
+ private static onSegment(a: Point, b: Point, p: Point): boolean {
181
+ return (
182
+ Math.min(a.x as number, b.x as number) <= (p.x as number) &&
183
+ (p.x as number) <= Math.max(a.x as number, b.x as number) &&
184
+ Math.min(a.y as number, b.y as number) <= (p.y as number) &&
185
+ (p.y as number) <= Math.max(a.y as number, b.y as number)
186
+ );
187
+ }
188
+
189
+ private static segmentsIntersect(p1: Point, p2: Point, p3: Point, p4: Point): boolean {
190
+ const d1 = Polygon.orient(p3, p4, p1);
191
+ const d2 = Polygon.orient(p3, p4, p2);
192
+ const d3 = Polygon.orient(p1, p2, p3);
193
+ const d4 = Polygon.orient(p1, p2, p4);
194
+ if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) return true;
195
+ if (d1 === 0 && Polygon.onSegment(p3, p4, p1)) return true;
196
+ if (d2 === 0 && Polygon.onSegment(p3, p4, p2)) return true;
197
+ if (d3 === 0 && Polygon.onSegment(p1, p2, p3)) return true;
198
+ if (d4 === 0 && Polygon.onSegment(p1, p2, p4)) return true;
199
+ return false;
200
+ }
201
+
202
+ /**
203
+ * A regular n-gon: `n` vertices evenly spaced on a circle of `radius`
204
+ * around `center`, starting at `startAngle` (default `-pi/2`, i.e. the
205
+ * first vertex points straight up — the conventional orientation for a
206
+ * polygon drawn on a screen y-down or math y-up axis alike) and
207
+ * proceeding counterclockwise in standard math convention.
208
+ */
209
+ static regular(n: number, radius = 1, center: Point = Vector.fromArray([0, 0]), startAngle = -Math.PI / 2): Polygon {
210
+ if (n < 3) throw new Error(`Polygon.regular: n must be at least 3, got ${n}`);
211
+ const cx = center.x as number;
212
+ const cy = center.y as number;
213
+ const vertices = Array.from({ length: n }, (_, i) => {
214
+ const theta = startAngle + (2 * Math.PI * i) / n;
215
+ return Vector.fromArray([cx + radius * Math.cos(theta), cy + radius * Math.sin(theta)]) as Point;
216
+ });
217
+ return new Polygon(...vertices);
218
+ }
219
+
220
+ /** A clone of the polygon (deep-clones nested vertex vectors by default). */
221
+ override clone(deep = true): Polygon {
222
+ const out = new Polygon(this.length);
223
+ for (let i = 0; i < out.length; i++) {
224
+ const el = this[i];
225
+ out[i] = deep && el instanceof Vector ? (el.clone() as Point) : (el as Point);
226
+ }
227
+ return out;
228
+ }
229
+ }
@@ -0,0 +1,242 @@
1
+ import type { Structure } from "./Structure.ts";
2
+
3
+ /**
4
+ * PolynomialRing — polynomials with coefficients in an arbitrary {@link Structure}
5
+ * (a finite field, the rationals, quaternions, whatever the structure supplies),
6
+ * following {@link GroupTheory}'s idiom of accepting the algebra as a constructor
7
+ * parameter rather than duplicating field logic. This generalizes long division,
8
+ * GCD, differentiation, and evaluation to any field — e.g. polynomials over
9
+ * `Structure.integersModulo(7)`, or plain real polynomials via
10
+ * `new PolynomialRing(Structure.realField())`.
11
+ *
12
+ * Coefficients are plain `T[]` arrays in ascending power (`p[i]` is the
13
+ * coefficient of `x^i`), trimmed of trailing zero coefficients. The zero
14
+ * polynomial is `[]`, with degree `-1`.
15
+ *
16
+ * {@link parsePolynomial}/{@link polynomialToString} below are real-number-only
17
+ * helpers for the common `"3*x^2-2*x+1"` string notation — parsing/formatting
18
+ * needs decimal syntax, which doesn't generalize to an arbitrary `Structure`.
19
+ */
20
+ export class PolynomialRing<T> {
21
+ readonly structure: Structure<T>;
22
+
23
+ constructor(structure: Structure<T>) {
24
+ this.structure = structure;
25
+ }
26
+
27
+ /** Drop trailing coefficients equal to the structure's zero. */
28
+ private trim(p: T[]): T[] {
29
+ let n = p.length;
30
+ while (n > 0 && this.structure.equality(p[n - 1] as T, this.structure.zero)) n--;
31
+ return p.slice(0, n);
32
+ }
33
+
34
+ /** `-1` for the zero polynomial, else the index of the highest non-zero coefficient. */
35
+ degree(p: T[]): number {
36
+ return this.trim(p).length - 1;
37
+ }
38
+
39
+ zero(): T[] {
40
+ return [];
41
+ }
42
+
43
+ one(): T[] {
44
+ return [this.structure.one];
45
+ }
46
+
47
+ private pad(p: T[], length: number): T[] {
48
+ const result = p.slice();
49
+ while (result.length < length) result.push(this.structure.zero);
50
+ return result;
51
+ }
52
+
53
+ add(a: T[], b: T[]): T[] {
54
+ const length = Math.max(a.length, b.length);
55
+ const pa = this.pad(a, length);
56
+ const pb = this.pad(b, length);
57
+ return this.trim(pa.map((x, i) => this.structure.add(x, pb[i] as T)));
58
+ }
59
+
60
+ negate(p: T[]): T[] {
61
+ return p.map((x) => this.structure.negative(x));
62
+ }
63
+
64
+ subtract(a: T[], b: T[]): T[] {
65
+ return this.add(a, this.negate(b));
66
+ }
67
+
68
+ scale(p: T[], scalar: T): T[] {
69
+ return this.trim(p.map((x) => this.structure.multiply(scalar, x)));
70
+ }
71
+
72
+ multiply(a: T[], b: T[]): T[] {
73
+ if (a.length === 0 || b.length === 0) return [];
74
+ const result: T[] = new Array(a.length + b.length - 1).fill(this.structure.zero);
75
+ for (let i = 0; i < a.length; i++) {
76
+ for (let j = 0; j < b.length; j++) {
77
+ result[i + j] = this.structure.add(result[i + j] as T, this.structure.multiply(a[i] as T, b[j] as T));
78
+ }
79
+ }
80
+ return this.trim(result);
81
+ }
82
+
83
+ /** Polynomial long division over the structure's field operations. */
84
+ divmod(a: T[], b: T[]): { quotient: T[]; remainder: T[] } {
85
+ const bd = this.degree(b);
86
+ if (bd < 0) throw new Error("PolynomialRing: division by the zero polynomial");
87
+ let remainder = this.trim(a);
88
+ const leadInv = this.structure.reciprocal(b[bd] as T);
89
+ const quotient: T[] = new Array(Math.max(this.degree(remainder) - bd + 1, 0)).fill(this.structure.zero);
90
+ while (this.degree(remainder) >= bd) {
91
+ const rd = this.degree(remainder);
92
+ const shift = rd - bd;
93
+ const coeff = this.structure.multiply(remainder[rd] as T, leadInv);
94
+ quotient[shift] = coeff;
95
+ for (let i = 0; i <= bd; i++) {
96
+ remainder[i + shift] = this.structure.subtract(
97
+ remainder[i + shift] as T,
98
+ this.structure.multiply(coeff, b[i] as T),
99
+ );
100
+ }
101
+ remainder = this.trim(remainder);
102
+ }
103
+ return { quotient: this.trim(quotient), remainder };
104
+ }
105
+
106
+ divide(a: T[], b: T[]): T[] {
107
+ return this.divmod(a, b).quotient;
108
+ }
109
+
110
+ mod(a: T[], b: T[]): T[] {
111
+ return this.divmod(a, b).remainder;
112
+ }
113
+
114
+ /** Rescale so the leading coefficient is the structure's one. No-op on the zero polynomial. */
115
+ private makeMonic(p: T[]): T[] {
116
+ const d = this.degree(p);
117
+ if (d < 0) return p;
118
+ return this.scale(p, this.structure.reciprocal(p[d] as T));
119
+ }
120
+
121
+ /** Monic GCD via the Euclidean algorithm (repeated `divmod`). */
122
+ gcd(a: T[], b: T[]): T[] {
123
+ let x = this.trim(a);
124
+ let y = this.trim(b);
125
+ while (this.degree(y) >= 0) {
126
+ const { remainder } = this.divmod(x, y);
127
+ x = y;
128
+ y = remainder;
129
+ }
130
+ return this.makeMonic(x);
131
+ }
132
+
133
+ /**
134
+ * The derivative polynomial (`i·c_i`, well-defined over any ring: `i` is
135
+ * built up as a running "`n` copies of the structure's `one`, added
136
+ * together" accumulator rather than being reconstructed from scratch for
137
+ * each coefficient — that would cost O(i) additions per coefficient, i.e.
138
+ * O(n²) additions overall for a degree-n polynomial).
139
+ */
140
+ derivative(p: T[]): T[] {
141
+ const trimmed = this.trim(p);
142
+ const out: T[] = [];
143
+ let n = this.structure.zero; // accumulates to `i` as an element of the structure
144
+ for (let i = 1; i < trimmed.length; i++) {
145
+ n = this.structure.add(n, this.structure.one);
146
+ out.push(this.structure.multiply(n, trimmed[i] as T));
147
+ }
148
+ return this.trim(out);
149
+ }
150
+
151
+ /**
152
+ * An antiderivative with zero constant of integration (`c_i / (i+1)`, via the
153
+ * structure's `reciprocal`). `i+1` is built up as a running accumulator (see
154
+ * {@link derivative}) instead of being reconstructed from scratch per term.
155
+ */
156
+ antiderivative(p: T[]): T[] {
157
+ const trimmed = this.trim(p);
158
+ const out: T[] = [this.structure.zero];
159
+ let n = this.structure.zero; // accumulates to `i+1` as an element of the structure
160
+ for (let i = 0; i < trimmed.length; i++) {
161
+ n = this.structure.add(n, this.structure.one);
162
+ out.push(this.structure.multiply(trimmed[i] as T, this.structure.reciprocal(n)));
163
+ }
164
+ return this.trim(out);
165
+ }
166
+
167
+ /** Evaluate at `x` via Horner's method, using the structure's field operations. */
168
+ evaluate(p: T[], x: T): T {
169
+ let result = this.structure.zero;
170
+ for (let i = p.length - 1; i >= 0; i--) {
171
+ result = this.structure.add(this.structure.multiply(result, x), p[i] as T);
172
+ }
173
+ return result;
174
+ }
175
+
176
+ equal(a: T[], b: T[]): boolean {
177
+ const ta = this.trim(a);
178
+ const tb = this.trim(b);
179
+ return ta.length === tb.length && ta.every((x, i) => this.structure.equality(x, tb[i] as T));
180
+ }
181
+
182
+ toString(p: T[], variable = "x"): string {
183
+ const trimmed = this.trim(p);
184
+ if (trimmed.length === 0) return "0";
185
+ const terms: string[] = [];
186
+ for (let i = trimmed.length - 1; i >= 0; i--) {
187
+ const c = trimmed[i] as T;
188
+ if (this.structure.equality(c, this.structure.zero)) continue;
189
+ if (i === 0) terms.push(`${c}`);
190
+ else if (i === 1) terms.push(`${c}*${variable}`);
191
+ else terms.push(`${c}*${variable}^${i}`);
192
+ }
193
+ return terms.join("+");
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Parse a real-coefficient polynomial string such as `"3*x^2-2*x+1"` into
199
+ * ascending-power coefficients (inverse of {@link polynomialToString}).
200
+ */
201
+ export function parsePolynomial(str: string, variable = "x"): number[] {
202
+ const s = str.replace(/\s+/g, "");
203
+ if (s === "") return [0];
204
+ const terms = s
205
+ .replace(/-/g, "+-")
206
+ .split("+")
207
+ .filter((t) => t !== "");
208
+ const p: number[] = [];
209
+ for (const term of terms) {
210
+ let coef: number;
211
+ let exp: number;
212
+ const vi = term.indexOf(variable);
213
+ if (vi === -1) {
214
+ coef = Number(term);
215
+ exp = 0;
216
+ } else {
217
+ const coefPart = term.slice(0, vi).replace(/\*$/, "");
218
+ coef = coefPart === "" || coefPart === "+" ? 1 : coefPart === "-" ? -1 : Number(coefPart);
219
+ const rest = term.slice(vi + variable.length);
220
+ exp = rest.startsWith("^") ? Number(rest.slice(1)) : 1;
221
+ }
222
+ p[exp] = (p[exp] ?? 0) + coef;
223
+ }
224
+ for (let i = 0; i < p.length; i++) if (p[i] === undefined) p[i] = 0;
225
+ return p;
226
+ }
227
+
228
+ /**
229
+ * Render ascending-power real coefficients as a human-readable string such as
230
+ * `3*x^2+2*x+1` (inverse of {@link parsePolynomial}).
231
+ */
232
+ export function polynomialToString(coeffs: number[], variable = "x", descending = true): string {
233
+ const term = (coef: number, i: number): string => {
234
+ if (i === 0) return String(coef);
235
+ if (i === 1) return `${coef}*${variable}`;
236
+ return `${coef}*${variable}^${i}`;
237
+ };
238
+ const indices = descending
239
+ ? Array.from({ length: coeffs.length }, (_, i) => coeffs.length - 1 - i)
240
+ : Array.from({ length: coeffs.length }, (_, i) => i);
241
+ return indices.map((i) => term(coeffs[i] as number, i)).join("+");
242
+ }