@holoscript/std 2.1.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -0
- package/dist/__tests__/EconomicPrimitives.test.d.ts.map +1 -0
- package/dist/__tests__/EconomicTraits.test.d.ts.map +1 -0
- package/dist/__tests__/SimulationLabPrimitives.test.d.ts.map +1 -0
- package/dist/__tests__/Sprint33.test.d.ts.map +1 -0
- package/dist/__tests__/Sprint56.test.d.ts.map +1 -0
- package/dist/__tests__/collections.test.d.ts.map +1 -0
- package/dist/__tests__/events.test.d.ts.map +1 -0
- package/dist/__tests__/materials.test.d.ts.map +1 -0
- package/dist/__tests__/math.test.d.ts.map +1 -0
- package/dist/__tests__/physics.test.d.ts.map +1 -0
- package/dist/__tests__/spatial.test.d.ts.map +1 -0
- package/dist/__tests__/string-decoupled.test.d.ts.map +1 -0
- package/dist/__tests__/string.test.d.ts.map +1 -0
- package/dist/__tests__/time.test.d.ts.map +1 -0
- package/dist/collections.d.ts.map +1 -1
- package/dist/events.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -9
- package/dist/materials.d.ts.map +1 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/physics.d.ts.map +1 -0
- package/dist/spatial.d.ts.map +1 -0
- package/dist/string.d.ts.map +1 -1
- package/dist/time.d.ts.map +1 -1
- package/dist/traits/ARTraits.d.ts.map +1 -0
- package/dist/traits/EconomicPrimitives.d.ts.map +1 -0
- package/dist/traits/EconomicTraits.d.ts.map +1 -0
- package/dist/traits/IoTTraits.d.ts.map +1 -0
- package/dist/traits/SimulationLabPrimitives.d.ts.map +1 -0
- package/dist/traits/SimulationLabTraits.d.ts.map +1 -0
- package/dist/traits/VRRTraits.d.ts.map +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +17 -6
- 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__/Sprint33.test.ts +1301 -0
- package/src/__tests__/Sprint56.test.ts +1070 -0
- package/src/__tests__/collections.test.ts +182 -0
- package/src/__tests__/events.test.ts +135 -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__/string-decoupled.test.ts +16 -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 +19 -2
- package/src/time.ts +9 -10
- 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/types.ts +47 -12
- package/vitest.config.ts +10 -0
- package/dist/collections.d.ts +0 -177
- package/dist/collections.js +0 -720
- package/dist/collections.js.map +0 -1
- package/dist/index.d.ts +0 -89
- package/dist/index.js.map +0 -1
- package/dist/math.d.ts +0 -162
- package/dist/math.js +0 -539
- package/dist/math.js.map +0 -1
- package/dist/string.d.ts +0 -208
- package/dist/string.js +0 -443
- package/dist/string.js.map +0 -1
- package/dist/time.d.ts +0 -215
- package/dist/time.js +0 -530
- package/dist/time.js.map +0 -1
- package/dist/types.d.ts +0 -193
- 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
|
+
});
|