@holoscript/std 2.1.0 → 6.0.3
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/__tests__/EconomicPrimitives.test.d.ts +2 -0
- package/dist/__tests__/EconomicPrimitives.test.d.ts.map +1 -0
- package/dist/__tests__/EconomicTraits.test.d.ts +2 -0
- package/dist/__tests__/EconomicTraits.test.d.ts.map +1 -0
- package/dist/__tests__/SimulationLabPrimitives.test.d.ts +2 -0
- package/dist/__tests__/SimulationLabPrimitives.test.d.ts.map +1 -0
- package/dist/__tests__/collections.test.d.ts +2 -0
- package/dist/__tests__/collections.test.d.ts.map +1 -0
- package/dist/__tests__/events.test.d.ts +2 -0
- package/dist/__tests__/events.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/materials.test.d.ts +2 -0
- package/dist/__tests__/materials.test.d.ts.map +1 -0
- package/dist/__tests__/math.test.d.ts +2 -0
- package/dist/__tests__/math.test.d.ts.map +1 -0
- package/dist/__tests__/physics.test.d.ts +2 -0
- package/dist/__tests__/physics.test.d.ts.map +1 -0
- package/dist/__tests__/spatial.test.d.ts +2 -0
- package/dist/__tests__/spatial.test.d.ts.map +1 -0
- package/dist/__tests__/standard-library-fundamentals.test.d.ts +2 -0
- package/dist/__tests__/standard-library-fundamentals.test.d.ts.map +1 -0
- package/dist/__tests__/string-and-time-utilities.test.d.ts +2 -0
- package/dist/__tests__/string-and-time-utilities.test.d.ts.map +1 -0
- package/dist/__tests__/string-decoupled.test.d.ts +2 -0
- package/dist/__tests__/string-decoupled.test.d.ts.map +1 -0
- package/dist/__tests__/string.test.d.ts +2 -0
- package/dist/__tests__/string.test.d.ts.map +1 -0
- package/dist/__tests__/time.test.d.ts +2 -0
- package/dist/__tests__/time.test.d.ts.map +1 -0
- package/dist/chunk-7HVUYGPS.js +679 -0
- package/dist/chunk-7HVUYGPS.js.map +1 -0
- package/dist/chunk-P4O3WNG2.js +287 -0
- package/dist/chunk-P4O3WNG2.js.map +1 -0
- package/dist/chunk-PFX2JM4X.js +683 -0
- package/dist/chunk-PFX2JM4X.js.map +1 -0
- package/dist/chunk-W2Q3LUCM.js +348 -0
- package/dist/chunk-W2Q3LUCM.js.map +1 -0
- package/dist/chunk-WS6W35YC.js +443 -0
- package/dist/chunk-WS6W35YC.js.map +1 -0
- package/dist/chunk-ZXVZAQCZ.js +470 -0
- package/dist/chunk-ZXVZAQCZ.js.map +1 -0
- package/dist/collections.cjs +715 -0
- package/dist/collections.cjs.map +1 -0
- package/dist/collections.d.ts.map +1 -1
- package/dist/collections.js +16 -719
- package/dist/collections.js.map +1 -1
- package/dist/events.d.ts +32 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/index.cjs +4101 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +21 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1270 -157
- package/dist/index.js.map +1 -1
- package/dist/materials.d.ts +50 -0
- package/dist/materials.d.ts.map +1 -0
- package/dist/math.cjs +591 -0
- package/dist/math.cjs.map +1 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +51 -537
- package/dist/math.js.map +1 -1
- package/dist/physics.d.ts +59 -0
- package/dist/physics.d.ts.map +1 -0
- package/dist/spatial.d.ts +82 -0
- package/dist/spatial.d.ts.map +1 -0
- package/dist/string.basic.test.d.ts +5 -0
- package/dist/string.basic.test.d.ts.map +1 -0
- package/dist/string.cjs +422 -0
- package/dist/string.cjs.map +1 -0
- package/dist/string.d.ts +5 -1
- package/dist/string.d.ts.map +1 -1
- package/dist/string.js +106 -442
- package/dist/string.js.map +1 -1
- package/dist/string.test.d.ts +5 -0
- package/dist/string.test.d.ts.map +1 -0
- package/dist/time.cjs +512 -0
- package/dist/time.cjs.map +1 -0
- package/dist/time.d.ts.map +1 -1
- package/dist/time.js +41 -528
- package/dist/time.js.map +1 -1
- package/dist/traits/ARTraits.d.ts +14 -0
- package/dist/traits/ARTraits.d.ts.map +1 -0
- package/dist/traits/EconomicPrimitives.cjs +335 -0
- package/dist/traits/EconomicPrimitives.cjs.map +1 -0
- package/dist/traits/EconomicPrimitives.d.ts +345 -0
- package/dist/traits/EconomicPrimitives.d.ts.map +1 -0
- package/dist/traits/EconomicPrimitives.js +55 -0
- package/dist/traits/EconomicPrimitives.js.map +1 -0
- package/dist/traits/EconomicTraits.cjs +471 -0
- package/dist/traits/EconomicTraits.cjs.map +1 -0
- package/dist/traits/EconomicTraits.d.ts +71 -0
- package/dist/traits/EconomicTraits.d.ts.map +1 -0
- package/dist/traits/EconomicTraits.js +17 -0
- package/dist/traits/EconomicTraits.js.map +1 -0
- package/dist/traits/IoTTraits.d.ts +14 -0
- package/dist/traits/IoTTraits.d.ts.map +1 -0
- package/dist/traits/SimulationLabPrimitives.d.ts +187 -0
- package/dist/traits/SimulationLabPrimitives.d.ts.map +1 -0
- package/dist/traits/SimulationLabTraits.d.ts +42 -0
- package/dist/traits/SimulationLabTraits.d.ts.map +1 -0
- package/dist/traits/VRRTraits.d.ts +150 -0
- package/dist/traits/VRRTraits.d.ts.map +1 -0
- package/dist/traits/__tests__/ARTraits.test.d.ts +2 -0
- package/dist/traits/__tests__/ARTraits.test.d.ts.map +1 -0
- package/dist/traits/__tests__/EconomicPrimitives.test.d.ts +2 -0
- package/dist/traits/__tests__/EconomicPrimitives.test.d.ts.map +1 -0
- package/dist/traits/__tests__/EconomicTraits.test.d.ts +2 -0
- package/dist/traits/__tests__/EconomicTraits.test.d.ts.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/error-utf8.log +6 -0
- package/error.log +0 -0
- package/package.json +42 -19
- package/src/__tests__/EconomicPrimitives.test.ts +690 -0
- package/src/__tests__/EconomicTraits.test.ts +425 -0
- package/src/__tests__/SimulationLabPrimitives.test.ts +415 -0
- package/src/__tests__/collections.test.ts +182 -0
- package/src/__tests__/events.test.ts +135 -0
- package/src/__tests__/index.test.ts +14 -0
- package/src/__tests__/materials.test.ts +84 -0
- package/src/__tests__/math.test.ts +246 -0
- package/src/__tests__/physics.test.ts +83 -0
- package/src/__tests__/spatial.test.ts +333 -0
- package/src/__tests__/standard-library-fundamentals.test.ts +1301 -0
- package/src/__tests__/string-and-time-utilities.test.ts +1070 -0
- package/src/__tests__/string-decoupled.test.ts +14 -0
- package/src/__tests__/string.test.ts +164 -0
- package/src/__tests__/time.test.ts +110 -0
- package/src/collections.ts +8 -2
- package/src/events.ts +88 -0
- package/src/index.ts +161 -11
- package/src/materials.ts +109 -0
- package/src/math.ts +30 -14
- package/src/physics.ts +141 -0
- package/src/spatial.ts +320 -0
- package/src/string.basic.test.ts +14 -0
- package/src/string.test.ts +335 -0
- package/src/string.ts +45 -13
- package/src/time.ts +10 -11
- package/src/traits/ARTraits.ts +103 -0
- package/src/traits/EconomicPrimitives.ts +755 -0
- package/src/traits/EconomicTraits.ts +552 -0
- package/src/traits/IoTTraits.ts +102 -0
- package/src/traits/SimulationLabPrimitives.ts +650 -0
- package/src/traits/SimulationLabTraits.ts +191 -0
- package/src/traits/VRRTraits.ts +326 -0
- package/src/traits/__tests__/ARTraits.test.ts +14 -0
- package/src/traits/__tests__/EconomicPrimitives.test.ts +14 -0
- package/src/traits/__tests__/EconomicTraits.test.ts +14 -0
- package/src/types.ts +47 -12
- package/std_error.log +5 -0
- package/tsup.config.ts +17 -0
- package/vitest.config.ts +10 -0
- package/dist/types.js +0 -198
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
mean,
|
|
4
|
+
variance,
|
|
5
|
+
standardDeviation,
|
|
6
|
+
standardError,
|
|
7
|
+
cohensD,
|
|
8
|
+
tTest,
|
|
9
|
+
oneSampleTTest,
|
|
10
|
+
mannWhitneyU,
|
|
11
|
+
chiSquaredTest,
|
|
12
|
+
expandSweep,
|
|
13
|
+
generateSweepCombinations,
|
|
14
|
+
extractMetric,
|
|
15
|
+
groupByParameter,
|
|
16
|
+
summarizeMetrics,
|
|
17
|
+
isParameterRange,
|
|
18
|
+
} from '../traits/SimulationLabPrimitives.js';
|
|
19
|
+
import type {
|
|
20
|
+
ParameterRange,
|
|
21
|
+
ParameterSet,
|
|
22
|
+
SimulationMetrics,
|
|
23
|
+
} from '../traits/SimulationLabPrimitives.js';
|
|
24
|
+
import {
|
|
25
|
+
SimulationLabTraits,
|
|
26
|
+
getSimulationTraitNames,
|
|
27
|
+
getSimulationTrait,
|
|
28
|
+
} from '../traits/SimulationLabTraits.js';
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// BASIC STATISTICS
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
describe('Basic Statistics', () => {
|
|
35
|
+
describe('mean', () => {
|
|
36
|
+
it('should return 0 for empty array', () => {
|
|
37
|
+
expect(mean([])).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
it('should calculate correct mean', () => {
|
|
40
|
+
expect(mean([1, 2, 3, 4, 5])).toBe(3);
|
|
41
|
+
});
|
|
42
|
+
it('should handle single element', () => {
|
|
43
|
+
expect(mean([42])).toBe(42);
|
|
44
|
+
});
|
|
45
|
+
it('should handle negative numbers', () => {
|
|
46
|
+
expect(mean([-10, 10])).toBe(0);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('variance', () => {
|
|
51
|
+
it('should return 0 for single element', () => {
|
|
52
|
+
expect(variance([5])).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
it('should return 0 for identical values', () => {
|
|
55
|
+
expect(variance([3, 3, 3, 3])).toBe(0);
|
|
56
|
+
});
|
|
57
|
+
it('should calculate sample variance with Bessel correction', () => {
|
|
58
|
+
// [1,2,3,4,5]: mean=3, sum of sq diffs=10, n-1=4 -> var=2.5
|
|
59
|
+
expect(variance([1, 2, 3, 4, 5])).toBe(2.5);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('standardDeviation', () => {
|
|
64
|
+
it('should be sqrt of variance', () => {
|
|
65
|
+
const data = [1, 2, 3, 4, 5];
|
|
66
|
+
expect(standardDeviation(data)).toBeCloseTo(Math.sqrt(2.5), 10);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('standardError', () => {
|
|
71
|
+
it('should return 0 for empty array', () => {
|
|
72
|
+
expect(standardError([])).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
it('should equal sd / sqrt(n)', () => {
|
|
75
|
+
const data = [1, 2, 3, 4, 5];
|
|
76
|
+
const sd = standardDeviation(data);
|
|
77
|
+
expect(standardError(data)).toBeCloseTo(sd / Math.sqrt(5), 10);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('cohensD', () => {
|
|
82
|
+
it('should return 0 for identical groups', () => {
|
|
83
|
+
expect(cohensD([5, 5, 5], [5, 5, 5])).toBe(0);
|
|
84
|
+
});
|
|
85
|
+
it('should return positive d when group1 > group2', () => {
|
|
86
|
+
const d = cohensD([10, 11, 12], [1, 2, 3]);
|
|
87
|
+
expect(d).toBeGreaterThan(0);
|
|
88
|
+
});
|
|
89
|
+
it('should return large d for well-separated groups', () => {
|
|
90
|
+
const d = cohensD([100, 101, 102], [1, 2, 3]);
|
|
91
|
+
expect(Math.abs(d)).toBeGreaterThan(2); // Large effect
|
|
92
|
+
});
|
|
93
|
+
it('should handle insufficient data', () => {
|
|
94
|
+
expect(cohensD([5], [3])).toBe(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// PARAMETER SWEEP
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
describe('Parameter Sweep', () => {
|
|
104
|
+
describe('isParameterRange', () => {
|
|
105
|
+
it('should identify range', () => {
|
|
106
|
+
const r: ParameterRange = { name: 'x', min: 0, max: 10, step: 1 };
|
|
107
|
+
expect(isParameterRange(r)).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
it('should reject set', () => {
|
|
110
|
+
const s: ParameterSet = { name: 'x', values: [1, 2, 3] };
|
|
111
|
+
expect(isParameterRange(s)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('expandSweep', () => {
|
|
116
|
+
it('should expand range correctly', () => {
|
|
117
|
+
const r: ParameterRange = { name: 'x', min: 0, max: 1, step: 0.5 };
|
|
118
|
+
const values = expandSweep(r);
|
|
119
|
+
expect(values).toEqual([0, 0.5, 1.0]);
|
|
120
|
+
});
|
|
121
|
+
it('should return values for discrete set', () => {
|
|
122
|
+
const s: ParameterSet = { name: 'x', values: [1, 5, 10] };
|
|
123
|
+
expect(expandSweep(s)).toEqual([1, 5, 10]);
|
|
124
|
+
});
|
|
125
|
+
it('should handle zero step', () => {
|
|
126
|
+
const r: ParameterRange = { name: 'x', min: 5, max: 10, step: 0 };
|
|
127
|
+
expect(expandSweep(r)).toEqual([5]);
|
|
128
|
+
});
|
|
129
|
+
it('should handle reversed range', () => {
|
|
130
|
+
const r: ParameterRange = { name: 'x', min: 10, max: 5, step: 1 };
|
|
131
|
+
expect(expandSweep(r)).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
it('should handle single-value range', () => {
|
|
134
|
+
const r: ParameterRange = { name: 'x', min: 5, max: 5, step: 1 };
|
|
135
|
+
expect(expandSweep(r)).toEqual([5]);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('generateSweepCombinations', () => {
|
|
140
|
+
it('should return [{}] for empty sweeps', () => {
|
|
141
|
+
expect(generateSweepCombinations([])).toEqual([{}]);
|
|
142
|
+
});
|
|
143
|
+
it('should generate all values for single sweep', () => {
|
|
144
|
+
const combos = generateSweepCombinations([{ name: 'x', values: [1, 2, 3] }]);
|
|
145
|
+
expect(combos).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
|
|
146
|
+
});
|
|
147
|
+
it('should generate Cartesian product for two sweeps', () => {
|
|
148
|
+
const combos = generateSweepCombinations([
|
|
149
|
+
{ name: 'x', values: [1, 2] },
|
|
150
|
+
{ name: 'y', values: [10, 20] },
|
|
151
|
+
]);
|
|
152
|
+
expect(combos).toHaveLength(4);
|
|
153
|
+
expect(combos).toContainEqual({ x: 1, y: 10 });
|
|
154
|
+
expect(combos).toContainEqual({ x: 1, y: 20 });
|
|
155
|
+
expect(combos).toContainEqual({ x: 2, y: 10 });
|
|
156
|
+
expect(combos).toContainEqual({ x: 2, y: 20 });
|
|
157
|
+
});
|
|
158
|
+
it('should handle three sweeps', () => {
|
|
159
|
+
const combos = generateSweepCombinations([
|
|
160
|
+
{ name: 'a', values: [1, 2] },
|
|
161
|
+
{ name: 'b', values: [3, 4] },
|
|
162
|
+
{ name: 'c', values: [5, 6] },
|
|
163
|
+
]);
|
|
164
|
+
expect(combos).toHaveLength(8); // 2 * 2 * 2
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// =============================================================================
|
|
170
|
+
// STATISTICAL TESTS
|
|
171
|
+
// =============================================================================
|
|
172
|
+
|
|
173
|
+
describe('Statistical Tests', () => {
|
|
174
|
+
describe('tTest (Welch)', () => {
|
|
175
|
+
it('should detect significantly different means', () => {
|
|
176
|
+
const group1 = Array.from({ length: 100 }, (_, i) => 100 + Math.sin(i));
|
|
177
|
+
const group2 = Array.from({ length: 100 }, (_, i) => 50 + Math.sin(i));
|
|
178
|
+
const result = tTest(group1, group2);
|
|
179
|
+
expect(result.significant).toBe(true);
|
|
180
|
+
expect(result.pValue).toBeLessThan(0.05);
|
|
181
|
+
expect(result.testName).toBe('welch_t_test');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should NOT detect difference in identical groups', () => {
|
|
185
|
+
const data = Array.from({ length: 50 }, (_, i) => Math.sin(i) * 10);
|
|
186
|
+
const result = tTest(data, data);
|
|
187
|
+
expect(result.significant).toBe(false);
|
|
188
|
+
expect(result.pValue).toBeGreaterThan(0.05);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should handle small samples gracefully', () => {
|
|
192
|
+
const result = tTest([1], [2]);
|
|
193
|
+
expect(result.pValue).toBe(1);
|
|
194
|
+
expect(result.significant).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should report positive effect size when group1 > group2', () => {
|
|
198
|
+
const g1 = [100, 110, 120, 130, 140];
|
|
199
|
+
const g2 = [10, 20, 30, 40, 50];
|
|
200
|
+
const result = tTest(g1, g2);
|
|
201
|
+
expect(result.effectSize).toBeGreaterThan(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should respect directional hypotheses', () => {
|
|
205
|
+
const high = Array.from({ length: 50 }, () => 100 + Math.random() * 5);
|
|
206
|
+
const low = Array.from({ length: 50 }, () => 50 + Math.random() * 5);
|
|
207
|
+
const greater = tTest(high, low, 0.05, 'greater');
|
|
208
|
+
const less = tTest(high, low, 0.05, 'less');
|
|
209
|
+
expect(greater.pValue).toBeLessThan(less.pValue);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('oneSampleTTest', () => {
|
|
214
|
+
it('should detect mean different from population', () => {
|
|
215
|
+
const data = Array.from({ length: 100 }, (_, i) => 50 + Math.sin(i));
|
|
216
|
+
const result = oneSampleTTest(data, 0);
|
|
217
|
+
expect(result.significant).toBe(true);
|
|
218
|
+
expect(result.testName).toBe('one_sample_t_test');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should NOT reject when sample comes from population', () => {
|
|
222
|
+
// Data centered around 100
|
|
223
|
+
const data = Array.from({ length: 100 }, (_, i) => 100 + Math.sin(i) * 0.01);
|
|
224
|
+
const result = oneSampleTTest(data, 100);
|
|
225
|
+
expect(result.significant).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('mannWhitneyU', () => {
|
|
230
|
+
it('should detect difference in ranked groups', () => {
|
|
231
|
+
const high = [90, 91, 92, 93, 94, 95, 96, 97, 98, 99];
|
|
232
|
+
const low = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
233
|
+
const result = mannWhitneyU(high, low);
|
|
234
|
+
expect(result.significant).toBe(true);
|
|
235
|
+
expect(result.testName).toBe('mann_whitney_u');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should handle empty groups', () => {
|
|
239
|
+
const result = mannWhitneyU([], [1, 2, 3]);
|
|
240
|
+
expect(result.significant).toBe(false);
|
|
241
|
+
expect(result.pValue).toBe(1);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should report effect size', () => {
|
|
245
|
+
const high = [90, 91, 92, 93, 94];
|
|
246
|
+
const low = [1, 2, 3, 4, 5];
|
|
247
|
+
const result = mannWhitneyU(high, low);
|
|
248
|
+
expect(Math.abs(result.effectSize)).toBeGreaterThan(0.5);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('chiSquaredTest', () => {
|
|
253
|
+
it('should NOT reject when observed matches expected', () => {
|
|
254
|
+
const observed = [50, 50, 50, 50];
|
|
255
|
+
const expected = [50, 50, 50, 50];
|
|
256
|
+
const result = chiSquaredTest(observed, expected);
|
|
257
|
+
expect(result.significant).toBe(false);
|
|
258
|
+
expect(result.statistic).toBe(0);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should reject when observed differs significantly from expected', () => {
|
|
262
|
+
const observed = [100, 10, 10, 10];
|
|
263
|
+
const expected = [32.5, 32.5, 32.5, 32.5];
|
|
264
|
+
const result = chiSquaredTest(observed, expected);
|
|
265
|
+
expect(result.significant).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should handle mismatched lengths', () => {
|
|
269
|
+
const result = chiSquaredTest([1, 2], [1]);
|
|
270
|
+
expect(result.significant).toBe(false);
|
|
271
|
+
expect(result.sampleSize).toBe(0);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should calculate Cramers V effect size', () => {
|
|
275
|
+
const observed = [100, 10, 10, 10];
|
|
276
|
+
const expected = [32.5, 32.5, 32.5, 32.5];
|
|
277
|
+
const result = chiSquaredTest(observed, expected);
|
|
278
|
+
expect(result.effectSize).toBeGreaterThan(0);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// =============================================================================
|
|
284
|
+
// METRIC AGGREGATION
|
|
285
|
+
// =============================================================================
|
|
286
|
+
|
|
287
|
+
describe('Metric Aggregation', () => {
|
|
288
|
+
const mockMetrics: SimulationMetrics[] = [
|
|
289
|
+
{ values: { score: 100, time: 5 }, seed: 1, params: { difficulty: 1 }, durationMs: 10 },
|
|
290
|
+
{ values: { score: 120, time: 6 }, seed: 2, params: { difficulty: 1 }, durationMs: 12 },
|
|
291
|
+
{ values: { score: 80, time: 8 }, seed: 3, params: { difficulty: 2 }, durationMs: 15 },
|
|
292
|
+
{ values: { score: 70, time: 9 }, seed: 4, params: { difficulty: 2 }, durationMs: 14 },
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
describe('extractMetric', () => {
|
|
296
|
+
it('should extract named metric values', () => {
|
|
297
|
+
expect(extractMetric(mockMetrics, 'score')).toEqual([100, 120, 80, 70]);
|
|
298
|
+
});
|
|
299
|
+
it('should return empty for unknown metric', () => {
|
|
300
|
+
expect(extractMetric(mockMetrics, 'unknown')).toEqual([]);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('groupByParameter', () => {
|
|
305
|
+
it('should group by parameter value', () => {
|
|
306
|
+
const groups = groupByParameter(mockMetrics, 'difficulty');
|
|
307
|
+
expect(groups.size).toBe(2);
|
|
308
|
+
expect(groups.get(1)!).toHaveLength(2);
|
|
309
|
+
expect(groups.get(2)!).toHaveLength(2);
|
|
310
|
+
});
|
|
311
|
+
it('should handle missing parameter', () => {
|
|
312
|
+
const groups = groupByParameter(mockMetrics, 'nonexistent');
|
|
313
|
+
expect(groups.size).toBe(0);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('summarizeMetrics', () => {
|
|
318
|
+
it('should produce correct summary', () => {
|
|
319
|
+
const summary = summarizeMetrics(mockMetrics, 'score');
|
|
320
|
+
expect(summary.n).toBe(4);
|
|
321
|
+
expect(summary.mean).toBeCloseTo(92.5, 1);
|
|
322
|
+
expect(summary.min).toBe(70);
|
|
323
|
+
expect(summary.max).toBe(120);
|
|
324
|
+
expect(summary.std).toBeGreaterThan(0);
|
|
325
|
+
});
|
|
326
|
+
it('should handle missing metric', () => {
|
|
327
|
+
const summary = summarizeMetrics(mockMetrics, 'nonexistent');
|
|
328
|
+
expect(summary.n).toBe(0);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// =============================================================================
|
|
334
|
+
// TRAIT DEFINITIONS
|
|
335
|
+
// =============================================================================
|
|
336
|
+
|
|
337
|
+
describe('SimulationLab Traits', () => {
|
|
338
|
+
it('should define @simulation_lab trait', () => {
|
|
339
|
+
expect(SimulationLabTraits.simulation_lab).toBeDefined();
|
|
340
|
+
expect(SimulationLabTraits.simulation_lab.name).toBe('@simulation_lab');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should require hypothesis and metrics params', () => {
|
|
344
|
+
const trait = SimulationLabTraits.simulation_lab;
|
|
345
|
+
expect(trait.params!.hypothesis.required).toBe(true);
|
|
346
|
+
expect(trait.params!.metrics.required).toBe(true);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should validate correct params', () => {
|
|
350
|
+
const valid = SimulationLabTraits.simulation_lab.validator!({
|
|
351
|
+
hypothesis: 'Test hypothesis',
|
|
352
|
+
metrics: ['score'],
|
|
353
|
+
epochs: 100,
|
|
354
|
+
confidence_level: 0.95,
|
|
355
|
+
direction: 'greater',
|
|
356
|
+
});
|
|
357
|
+
expect(valid).toBe(true);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should reject missing hypothesis', () => {
|
|
361
|
+
const invalid = SimulationLabTraits.simulation_lab.validator!({
|
|
362
|
+
metrics: ['score'],
|
|
363
|
+
});
|
|
364
|
+
expect(invalid).toBe(false);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should reject empty metrics', () => {
|
|
368
|
+
const invalid = SimulationLabTraits.simulation_lab.validator!({
|
|
369
|
+
hypothesis: 'Test',
|
|
370
|
+
metrics: [],
|
|
371
|
+
});
|
|
372
|
+
expect(invalid).toBe(false);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('should reject invalid direction', () => {
|
|
376
|
+
const invalid = SimulationLabTraits.simulation_lab.validator!({
|
|
377
|
+
hypothesis: 'Test',
|
|
378
|
+
metrics: ['x'],
|
|
379
|
+
direction: 'sideways',
|
|
380
|
+
});
|
|
381
|
+
expect(invalid).toBe(false);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should compose with all economic traits', () => {
|
|
385
|
+
const trait = SimulationLabTraits.simulation_lab;
|
|
386
|
+
expect(trait.composesWith).toContain('@tradeable');
|
|
387
|
+
expect(trait.composesWith).toContain('@pid_controlled');
|
|
388
|
+
expect(trait.composesWith).toContain('@bonding_curved');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should use worker thread for batch execution', () => {
|
|
392
|
+
const hints = SimulationLabTraits.simulation_lab.compiler_hints;
|
|
393
|
+
expect(hints?.thread_safety).toBe('worker_thread');
|
|
394
|
+
expect(hints?.batch_mode).toBe(true);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe('getSimulationTraitNames', () => {
|
|
398
|
+
it('should return trait names', () => {
|
|
399
|
+
const names = getSimulationTraitNames();
|
|
400
|
+
expect(names).toContain('@simulation_lab');
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe('getSimulationTrait', () => {
|
|
405
|
+
it('should find by name with @', () => {
|
|
406
|
+
expect(getSimulationTrait('@simulation_lab')).toBeDefined();
|
|
407
|
+
});
|
|
408
|
+
it('should find by name without @', () => {
|
|
409
|
+
expect(getSimulationTrait('simulation_lab')).toBeDefined();
|
|
410
|
+
});
|
|
411
|
+
it('should return undefined for unknown', () => {
|
|
412
|
+
expect(getSimulationTrait('@unknown')).toBeUndefined();
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { List } from '../collections.js';
|
|
3
|
+
|
|
4
|
+
describe('List', () => {
|
|
5
|
+
describe('construction', () => {
|
|
6
|
+
it('should create empty list', () => {
|
|
7
|
+
const list = new List<number>();
|
|
8
|
+
expect(list.length).toBe(0);
|
|
9
|
+
expect(list.isEmpty).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should create from array', () => {
|
|
13
|
+
const list = new List([1, 2, 3]);
|
|
14
|
+
expect(list.length).toBe(3);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should create with List.of()', () => {
|
|
18
|
+
const list = List.of(1, 2, 3);
|
|
19
|
+
expect(list.length).toBe(3);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should create with List.from()', () => {
|
|
23
|
+
const list = List.from([1, 2, 3]);
|
|
24
|
+
expect(list.length).toBe(3);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('List.range()', () => {
|
|
29
|
+
it('should create range with positive step', () => {
|
|
30
|
+
const list = List.range(0, 5, 1);
|
|
31
|
+
expect(list.toArray()).toEqual([0, 1, 2, 3, 4]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should create range with default step', () => {
|
|
35
|
+
const list = List.range(0, 3);
|
|
36
|
+
expect(list.toArray()).toEqual([0, 1, 2]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should create range with custom step', () => {
|
|
40
|
+
const list = List.range(0, 10, 2);
|
|
41
|
+
expect(list.toArray()).toEqual([0, 2, 4, 6, 8]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should create range with negative step', () => {
|
|
45
|
+
const list = List.range(5, 0, -1);
|
|
46
|
+
expect(list.toArray()).toEqual([5, 4, 3, 2, 1]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('List.repeat()', () => {
|
|
51
|
+
it('should create list with repeated values', () => {
|
|
52
|
+
const list = List.repeat('a', 3);
|
|
53
|
+
expect(list.toArray()).toEqual(['a', 'a', 'a']);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should create empty list with count 0', () => {
|
|
57
|
+
const list = List.repeat('a', 0);
|
|
58
|
+
expect(list.isEmpty).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('accessors', () => {
|
|
63
|
+
const list = List.of(10, 20, 30, 40, 50);
|
|
64
|
+
|
|
65
|
+
it('should get element by positive index', () => {
|
|
66
|
+
expect(list.get(0)).toBe(10);
|
|
67
|
+
expect(list.get(2)).toBe(30);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should get element by negative index', () => {
|
|
71
|
+
expect(list.get(-1)).toBe(50);
|
|
72
|
+
expect(list.get(-2)).toBe(40);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return undefined for out of bounds', () => {
|
|
76
|
+
expect(list.get(10)).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should get first element', () => {
|
|
80
|
+
expect(list.first()).toBe(10);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should get last element', () => {
|
|
84
|
+
expect(list.last()).toBe(50);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should return undefined for first/last on empty list', () => {
|
|
88
|
+
const empty = new List<number>();
|
|
89
|
+
expect(empty.first()).toBeUndefined();
|
|
90
|
+
expect(empty.last()).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('map', () => {
|
|
95
|
+
it('should map over elements', () => {
|
|
96
|
+
const list = List.of(1, 2, 3);
|
|
97
|
+
const doubled = list.map((x) => x * 2);
|
|
98
|
+
expect(doubled.toArray()).toEqual([2, 4, 6]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should receive index in map callback', () => {
|
|
102
|
+
const list = List.of('a', 'b', 'c');
|
|
103
|
+
const indexed = list.map((_, i) => i);
|
|
104
|
+
expect(indexed.toArray()).toEqual([0, 1, 2]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return new List instance', () => {
|
|
108
|
+
const list = List.of(1, 2, 3);
|
|
109
|
+
const mapped = list.map((x) => x);
|
|
110
|
+
expect(mapped).not.toBe(list);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('filter', () => {
|
|
115
|
+
it('should filter elements', () => {
|
|
116
|
+
const list = List.of(1, 2, 3, 4, 5);
|
|
117
|
+
const evens = list.filter((x) => x % 2 === 0);
|
|
118
|
+
expect(evens.toArray()).toEqual([2, 4]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return empty list when nothing matches', () => {
|
|
122
|
+
const list = List.of(1, 3, 5);
|
|
123
|
+
const evens = list.filter((x) => x % 2 === 0);
|
|
124
|
+
expect(evens.isEmpty).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('flatMap', () => {
|
|
129
|
+
it('should flatMap with arrays', () => {
|
|
130
|
+
const list = List.of(1, 2, 3);
|
|
131
|
+
const result = list.flatMap((x) => [x, x * 10]);
|
|
132
|
+
expect(result.toArray()).toEqual([1, 10, 2, 20, 3, 30]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should flatMap with Lists', () => {
|
|
136
|
+
const list = List.of(1, 2);
|
|
137
|
+
const result = list.flatMap((x) => List.of(x, x + 1));
|
|
138
|
+
expect(result.toArray()).toEqual([1, 2, 2, 3]);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('reduce', () => {
|
|
143
|
+
it('should reduce with initial value', () => {
|
|
144
|
+
const list = List.of(1, 2, 3, 4);
|
|
145
|
+
const sum = list.reduce((acc, x) => acc + x, 0);
|
|
146
|
+
expect(sum).toBe(10);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('immutability', () => {
|
|
151
|
+
it('operations should return new List instances', () => {
|
|
152
|
+
const original = List.of(1, 2, 3);
|
|
153
|
+
const mapped = original.map((x) => x * 2);
|
|
154
|
+
const filtered = original.filter((x) => x > 1);
|
|
155
|
+
|
|
156
|
+
expect(original.toArray()).toEqual([1, 2, 3]);
|
|
157
|
+
expect(mapped.toArray()).toEqual([2, 4, 6]);
|
|
158
|
+
expect(filtered.toArray()).toEqual([2, 3]);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('toArray', () => {
|
|
163
|
+
it('should return array copy', () => {
|
|
164
|
+
const list = List.of(1, 2, 3);
|
|
165
|
+
const arr = list.toArray();
|
|
166
|
+
expect(arr).toEqual([1, 2, 3]);
|
|
167
|
+
arr.push(4);
|
|
168
|
+
expect(list.length).toBe(3); // Original unchanged
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('iteration', () => {
|
|
173
|
+
it('should be iterable', () => {
|
|
174
|
+
const list = List.of(1, 2, 3);
|
|
175
|
+
const result: number[] = [];
|
|
176
|
+
for (const item of list) {
|
|
177
|
+
result.push(item);
|
|
178
|
+
}
|
|
179
|
+
expect(result).toEqual([1, 2, 3]);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|