@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,170 @@
1
+ import { jitter, roundTo } from "./RealMath.ts";
2
+ import { Vector } from "./Vector.ts";
3
+ import { VectorUtils } from "./VectorUtils.ts";
4
+
5
+ /**
6
+ * Statistics — order-dependent descriptive statistics and simple linear
7
+ * regression over `number[]`-like {@link Vector}s. Split out of `RealMath`/
8
+ * `ComplexMath` because these operations (median, percentiles, IQR, outlier
9
+ * detection) rely on a total order and don't generalize to complex numbers.
10
+ *
11
+ * Plain exported functions (no wrapping class), matching the "namespace
12
+ * class" fix applied to {@link RealMath}.
13
+ *
14
+ * Bug fixes inherited from the RealMath/ComplexMath AS3 port:
15
+ * - `sort` is numeric, not the default lexicographic Array sort.
16
+ * - `variance` is sample variance (divides by N-1), not population variance.
17
+ */
18
+
19
+ type RVec = Vector<number>;
20
+ type Unary = (x: number) => number;
21
+
22
+ /** Numeric sort (bug fix: the AS3 default sort was lexicographic). */
23
+ export function sort(list: RVec, ascending = true): RVec {
24
+ const sorted = list.clone().sort((a, b) => (a as number) - (b as number)) as RVec;
25
+ return ascending ? sorted : sorted.reversed();
26
+ }
27
+
28
+ export function minimum(list: RVec): number {
29
+ return sort(list, true)[0] as number;
30
+ }
31
+
32
+ export function maximum(list: RVec): number {
33
+ return sort(list, false)[0] as number;
34
+ }
35
+
36
+ export function percentile(list: RVec, n: number): number {
37
+ const ordered = sort(list);
38
+ // Clamp to the valid 1-based rank range [1, length]: the raw n*(length+1)
39
+ // formula lands on 0 (n=0) or length+1 (n=1), one step outside the array on
40
+ // each end, so percentile 0/1 (min/max) would otherwise index out of bounds.
41
+ const theIndex = Math.min(Math.max(n * (ordered.length + 1), 1), ordered.length);
42
+ if (theIndex === Math.floor(theIndex)) {
43
+ return ordered[theIndex - 1] as number;
44
+ }
45
+ return ((ordered[Math.floor(theIndex - 1)] as number) + (ordered[Math.ceil(theIndex - 1)] as number)) / 2;
46
+ }
47
+
48
+ export function median(list: RVec): number {
49
+ return percentile(list, 0.5);
50
+ }
51
+ export function q1(list: RVec): number {
52
+ return percentile(list, 0.25);
53
+ }
54
+ export function q3(list: RVec): number {
55
+ return percentile(list, 0.75);
56
+ }
57
+
58
+ export function fiveNumberSummary(list: RVec): RVec {
59
+ return Vector.fromArray([minimum(list), q1(list), median(list), q3(list), maximum(list)]);
60
+ }
61
+
62
+ export function sum(list: RVec): number {
63
+ return VectorUtils.collapse(list, (a, b) => a + b, 0) as number;
64
+ }
65
+
66
+ export function product(list: RVec): number {
67
+ return VectorUtils.collapse(list, (a, b) => a * b, 1) as number;
68
+ }
69
+
70
+ export function mean(list: RVec): number {
71
+ return sum(list) / list.length;
72
+ }
73
+
74
+ /** Sample variance (bug fix: AS3 divided by N despite the `n<2 -> NaN` guard). */
75
+ export function variance(list: RVec): number {
76
+ if (list.length < 2) return NaN;
77
+ const m = mean(list);
78
+ let s = 0;
79
+ for (const x of list) s += ((x as number) - m) ** 2;
80
+ return s / (list.length - 1);
81
+ }
82
+
83
+ export function standardDeviation(list: RVec): number {
84
+ return Math.sqrt(variance(list));
85
+ }
86
+
87
+ /** Population variance (divides by N; complements the sample {@link variance}). */
88
+ export function populationVariance(list: RVec): number {
89
+ if (list.length < 1) return NaN;
90
+ const m = mean(list);
91
+ let s = 0;
92
+ for (const x of list) s += ((x as number) - m) ** 2;
93
+ return s / list.length;
94
+ }
95
+
96
+ export function populationStandardDeviation(list: RVec): number {
97
+ return Math.sqrt(populationVariance(list));
98
+ }
99
+
100
+ export function zScore(x: number, mean_ = 0, sDev = 1): number {
101
+ return (x - mean_) / sDev;
102
+ }
103
+
104
+ export function invertZScore(z: number, mean_ = 0, sDev = 1): number {
105
+ return z * sDev + mean_;
106
+ }
107
+
108
+ export function zScoreList(list: RVec): RVec {
109
+ const m = mean(list);
110
+ const sd = standardDeviation(list);
111
+ return VectorUtils.transform(list, (x) => zScore(x, m, sd));
112
+ }
113
+
114
+ export function roundedList(list: RVec, precision: number): RVec {
115
+ return VectorUtils.transform(list, (x) => roundTo(x, precision));
116
+ }
117
+
118
+ export function jitteredList(list: RVec, magnitude = 0.1): RVec {
119
+ return VectorUtils.transform(list, (x) => jitter(x, magnitude));
120
+ }
121
+
122
+ export function isOutlier(list: RVec, value: number, distance = 1.5): boolean {
123
+ const range = distance * interQuartileRange(list);
124
+ return value < q1(list) - range || value > q3(list) + range;
125
+ }
126
+
127
+ export function interQuartileRange(list: RVec): number {
128
+ return q3(list) - q1(list);
129
+ }
130
+
131
+ export function outliers(list: RVec, distance = 1.5): RVec {
132
+ return VectorUtils.filter(list, (x) => isOutlier(list, x, distance));
133
+ }
134
+
135
+ export function outliersRemoved(list: RVec, distance = 1.5): RVec {
136
+ return VectorUtils.filter(list, (x) => !isOutlier(list, x, distance));
137
+ }
138
+
139
+ // -- bivariate ------------------------------------------------------------------
140
+
141
+ export function correlation(x: RVec, y: RVec): number {
142
+ if (x.length < 2) return NaN;
143
+ const xMean = mean(x);
144
+ const yMean = mean(y);
145
+ const xSD = standardDeviation(x);
146
+ const ySD = standardDeviation(y);
147
+ let s = 0;
148
+ for (let i = 0; i < x.length; i++) {
149
+ s += (((x[i] as number) - xMean) / xSD) * (((y[i] as number) - yMean) / ySD);
150
+ }
151
+ return s / (x.length - 1);
152
+ }
153
+
154
+ export function linRegSlope(x: RVec, y: RVec): number {
155
+ return (correlation(x, y) * standardDeviation(y)) / standardDeviation(x);
156
+ }
157
+
158
+ export function linRegIntercept(x: RVec, y: RVec): number {
159
+ return mean(y) - linRegSlope(x, y) * mean(x);
160
+ }
161
+
162
+ export function linearRegression(x: RVec, y: RVec): RVec {
163
+ return Vector.fromArray([linRegSlope(x, y), linRegIntercept(x, y)]);
164
+ }
165
+
166
+ export function linearRegressionFunction(x: RVec, y: RVec): Unary {
167
+ const slope = linRegSlope(x, y);
168
+ const intercept = linRegIntercept(x, y);
169
+ return (a: number) => slope * a + intercept;
170
+ }
@@ -0,0 +1,208 @@
1
+ import { ComplexNumber } from "./ComplexNumber.ts";
2
+ import { Environment } from "./Environment.ts";
3
+ import { Expression } from "./Expression.ts";
4
+ import { SpecialOperator } from "./SpecialOperator.ts";
5
+ import { Vector } from "./Vector.ts";
6
+
7
+ /**
8
+ * StringEvaluator — a small recursive evaluator for mathematical expression
9
+ * strings such as `"3+4*2"`, `"sin(pi/2)"` or `"[1,2,3]"`. Ported from Mallory's
10
+ * ActionScript `StringEvaluator`.
11
+ *
12
+ * Operator precedence follows the AS3 design (encoded by the internal operator
13
+ * list order): `%` binds loosest, then `+`, `-`, `*`, `/`, and `^` binds tightest.
14
+ *
15
+ * Bug fixes from the AS3 original:
16
+ * - Associativity: the AS3 splitter was uniformly right-associative, so
17
+ * `10-2-3` evaluated as `10-(2-3)` and `8/4/2` as `8/(4/2)`. Non-power
18
+ * operators are now left-associative (`^` stays right-associative, as in
19
+ * standard notation).
20
+ * - The unresolved-parameter guard was dead code (`(p is String) is String`
21
+ * is always false); the intended symbolic passthrough is implemented, so an
22
+ * unbound `f(x)` returns the string `"f(x)"` instead of calling `f("x")`.
23
+ */
24
+ export class StringEvaluator {
25
+ /** Matches an acceptable variable/function name. */
26
+ static readonly VarMatch = /[_a-zA-Z]+[_a-zA-Z0-9]*/g;
27
+
28
+ private static readonly operatorList: SpecialOperator[] = [
29
+ SpecialOperator.Modulous,
30
+ SpecialOperator.Plus,
31
+ SpecialOperator.Minus,
32
+ SpecialOperator.Times,
33
+ SpecialOperator.Divided,
34
+ SpecialOperator.Power,
35
+ ];
36
+
37
+ /** Evaluate `expression` within `environment`. */
38
+ static evaluate(expression: unknown, environment: Environment): unknown {
39
+ if (typeof expression !== "string") return expression;
40
+
41
+ const expr = expression.split("\r").join("").split(" ").join("");
42
+
43
+ if (ComplexNumber.isComplex(expr)) return new ComplexNumber(expr);
44
+
45
+ if (StringEvaluator.unSurroundOK(expr, "[", "]")) {
46
+ return StringEvaluator.vectorEvaluate(StringEvaluator.unSurround(expr), environment);
47
+ }
48
+
49
+ if (StringEvaluator.unSurroundOK(expr, "(", ")")) {
50
+ return StringEvaluator.evaluate(StringEvaluator.unSurround(expr), environment);
51
+ }
52
+
53
+ const pCount = StringEvaluator.countChar(expr, "(") - StringEvaluator.countChar(expr, ")");
54
+ if (pCount > 0) return new Error(`Missing ${pCount} ')'.`);
55
+ if (pCount < 0) return new Error(`Missing ${-pCount} '('.`);
56
+
57
+ for (const op of StringEvaluator.operatorList) {
58
+ const leftAssociative = op !== SpecialOperator.Power;
59
+ const parts = StringEvaluator.splitBinary(expr, op.rep, leftAssociative);
60
+ if (parts.length === 2) {
61
+ return StringEvaluator.functionEvaluate(op.funct as string, parts, environment);
62
+ }
63
+ }
64
+
65
+ const pPosition = expr.indexOf("(");
66
+ if (pPosition > 0) {
67
+ const functionName = expr.substr(0, pPosition);
68
+ const args = StringEvaluator.seperateArray(expr.substr(pPosition + 1, expr.length - pPosition - 2));
69
+ return StringEvaluator.functionEvaluate(functionName, args, environment);
70
+ }
71
+
72
+ return environment.retrieve(expr);
73
+ }
74
+
75
+ private static functionEvaluate(fName: string, parameters: unknown[], environment: Environment): unknown {
76
+ const params = parameters.map((p) => StringEvaluator.evaluate(p, environment));
77
+
78
+ if (!environment.existKey(fName)) return `${fName}(${params})`;
79
+
80
+ const f = environment.retrieve(fName);
81
+
82
+ if (f instanceof Environment) return f.retrieve(String(params[0]));
83
+
84
+ // Symbolic passthrough for any still-unresolved (string) parameter.
85
+ for (const p of params) {
86
+ if (typeof p === "string" && p === StringEvaluator.evaluate(p, environment)) {
87
+ return `${fName}(${params})`;
88
+ }
89
+ }
90
+
91
+ if (f instanceof Expression) return f.evaluate(params, environment);
92
+
93
+ if (typeof f === "function") return (f as (...a: unknown[]) => unknown)(...params);
94
+ return f;
95
+ }
96
+
97
+ static vectorEvaluate(expression: string, environment: Environment): Vector<unknown> {
98
+ const out = Vector.fromArray<unknown>(StringEvaluator.seperateArray(expression));
99
+ for (let i = 0; i < out.length; i++) out[i] = StringEvaluator.evaluate(out[i], environment);
100
+ return out;
101
+ }
102
+
103
+ // -- helpers -------------------------------------------------------------
104
+
105
+ private static countChar(expression: string, char = " "): number {
106
+ let count = 0;
107
+ for (const c of expression) if (c === char) count++;
108
+ return count;
109
+ }
110
+
111
+ private static isSurrounded(expression: string, leftBracket = "(", rightBracket = ")"): boolean {
112
+ return expression.charAt(0) === leftBracket && expression.charAt(expression.length - 1) === rightBracket;
113
+ }
114
+
115
+ private static unSurroundOK(expression: string, leftBracket = "(", rightBracket = ")"): boolean {
116
+ if (!StringEvaluator.isSurrounded(expression, leftBracket, rightBracket)) return false;
117
+ const inner = expression.substring(1, expression.length - 1);
118
+ // If the first inner closer precedes its opener, the brackets were not a pair.
119
+ return inner.lastIndexOf(leftBracket) <= inner.lastIndexOf(rightBracket);
120
+ }
121
+
122
+ private static unSurround(expression: string): string {
123
+ return expression.substring(1, expression.length - 1);
124
+ }
125
+
126
+ /** Split at top-level `splitter`s, rejoining pieces that fall inside brackets. */
127
+ private static seperateArray(expression: string, splitter = ","): string[] {
128
+ const exArray = expression.split(splitter);
129
+ for (const [open, close] of [
130
+ ["(", ")"],
131
+ ["[", "]"],
132
+ ["{", "}"],
133
+ ] as const) {
134
+ for (let i = 0; i < exArray.length - 1; i++) {
135
+ if (
136
+ StringEvaluator.countChar(exArray[i] as string, open) > StringEvaluator.countChar(exArray[i] as string, close)
137
+ ) {
138
+ exArray[i] = `${exArray[i]}${splitter}${exArray[i + 1]}`;
139
+ exArray.splice(i + 1, 1);
140
+ i--;
141
+ }
142
+ }
143
+ }
144
+ return exArray;
145
+ }
146
+
147
+ /**
148
+ * Split an expression across the top-level occurrences of a binary operator
149
+ * into `[left, right]`. Left-associative by default (split at the last
150
+ * occurrence); right-associative (split at the first) when `leftAssociative`
151
+ * is false, used for `^`.
152
+ */
153
+ private static splitBinary(expression: string, splitter = "+", leftAssociative = true): string[] {
154
+ const pieces = StringEvaluator.seperateArray(expression, splitter);
155
+ if (pieces.length <= 1) return pieces;
156
+ if (leftAssociative) {
157
+ const right = pieces[pieces.length - 1] as string;
158
+ const left = pieces.slice(0, -1).join(splitter);
159
+ return [left, right];
160
+ }
161
+ const left = pieces[0] as string;
162
+ const right = pieces.slice(1).join(splitter);
163
+ return [left, right];
164
+ }
165
+
166
+ /**
167
+ * Build an {@link Environment} pre-populated with the arithmetic operators,
168
+ * common {@link ComplexNumber} instance methods and the constants `pi`, `e`,
169
+ * `i`, `phi` — enough to evaluate ordinary numeric expressions out of the box.
170
+ */
171
+ static mathEnvironment(): Environment {
172
+ const env = new Environment();
173
+ const cn = (x: unknown) => (x instanceof ComplexNumber ? x : new ComplexNumber(x as number));
174
+
175
+ env.assignImmutable("add", (a: unknown, b: unknown) => cn(a).add(cn(b)));
176
+ env.assignImmutable("subtract", (a: unknown, b: unknown) => cn(a).subtract(cn(b)));
177
+ env.assignImmutable("multiply", (a: unknown, b: unknown) => cn(a).multiply(cn(b)));
178
+ env.assignImmutable("divide", (a: unknown, b: unknown) => cn(a).divide(cn(b)));
179
+ env.assignImmutable("power", (a: unknown, b: unknown) => cn(a).power(cn(b)));
180
+ env.assignImmutable("mod", (a: unknown, b: unknown) => {
181
+ const x = cn(a).value;
182
+ const y = cn(b).value;
183
+ return new ComplexNumber(((x % y) + y) % y, 0);
184
+ });
185
+
186
+ const unary: Record<string, (z: ComplexNumber) => ComplexNumber> = {
187
+ sin: (z) => z.sine(),
188
+ cos: (z) => z.cosine(),
189
+ tan: (z) => z.tangent(),
190
+ sqrt: (z) => z.squareRoot(),
191
+ exp: (z) => ComplexNumber.E.power(z),
192
+ ln: (z) => z.logarithm(),
193
+ log: (z) => z.logarithm(10),
194
+ abs: (z) => new ComplexNumber(z.magnitude(), 0),
195
+ conj: (z) => z.conjugate(),
196
+ neg: (z) => z.neg(),
197
+ };
198
+ for (const [name, fn] of Object.entries(unary)) {
199
+ env.assignImmutable(name, (a: unknown) => fn(cn(a)));
200
+ }
201
+
202
+ env.assignImmutable("pi", ComplexNumber.PI);
203
+ env.assignImmutable("e", ComplexNumber.E);
204
+ env.assignImmutable("i", ComplexNumber.I);
205
+ env.assignImmutable("phi", ComplexNumber.PHI);
206
+ return env;
207
+ }
208
+ }