@buley/hexgrid-3d 1.0.0 → 1.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/build_log.txt +500 -0
- package/build_src_log.txt +8 -0
- package/examples/basic-usage.tsx +19 -19
- package/package.json +1 -1
- package/public/hexgrid-worker.js +2350 -1638
- package/site/.eslintrc.json +3 -0
- package/site/DEPLOYMENT.md +196 -0
- package/site/INDEX.md +127 -0
- package/site/QUICK_START.md +86 -0
- package/site/README.md +85 -0
- package/site/SITE_SUMMARY.md +180 -0
- package/site/next.config.js +12 -0
- package/site/package.json +26 -0
- package/site/src/app/docs/page.tsx +148 -0
- package/site/src/app/examples/page.tsx +133 -0
- package/site/src/app/globals.css +160 -0
- package/site/src/app/layout.tsx +29 -0
- package/site/src/app/page.tsx +163 -0
- package/site/tsconfig.json +29 -0
- package/site/vercel.json +6 -0
- package/src/Snapshot.ts +790 -585
- package/src/adapters/DashAdapter.ts +57 -0
- package/src/adapters.ts +16 -18
- package/src/algorithms/AdvancedStatistics.ts +58 -24
- package/src/algorithms/BayesianStatistics.ts +43 -12
- package/src/algorithms/FlowField.ts +30 -6
- package/src/algorithms/FlowField3D.ts +573 -0
- package/src/algorithms/FluidSimulation.ts +19 -3
- package/src/algorithms/FluidSimulation3D.ts +664 -0
- package/src/algorithms/GraphAlgorithms.ts +19 -12
- package/src/algorithms/OutlierDetection.ts +72 -38
- package/src/algorithms/ParticleSystem.ts +12 -2
- package/src/algorithms/ParticleSystem3D.ts +567 -0
- package/src/algorithms/index.ts +14 -8
- package/src/compat.ts +10 -10
- package/src/components/HexGrid.tsx +10 -23
- package/src/components/NarrationOverlay.tsx +140 -52
- package/src/components/index.ts +2 -1
- package/src/features.ts +31 -31
- package/src/index.ts +11 -11
- package/src/lib/narration.ts +17 -0
- package/src/lib/stats-tracker.ts +25 -0
- package/src/lib/theme-colors.ts +12 -0
- package/src/math/HexCoordinates.ts +849 -4
- package/src/math/Matrix4.ts +2 -12
- package/src/math/Vector3.ts +49 -1
- package/src/math/index.ts +6 -6
- package/src/note-adapter.ts +50 -42
- package/src/ontology-adapter.ts +30 -23
- package/src/stores/uiStore.ts +34 -34
- package/src/types/shared-utils.d.ts +10 -0
- package/src/types.ts +110 -98
- package/src/utils/image-utils.ts +9 -6
- package/src/wasm/HexGridWasmWrapper.ts +436 -388
- package/src/wasm/index.ts +2 -2
- package/src/workers/hexgrid-math.ts +40 -35
- package/src/workers/hexgrid-worker.worker.ts +1992 -1018
- package/tsconfig.json +21 -14
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter for Zero-Copy Data Sync between Dash 2.0 and HexGrid-3D.
|
|
3
|
+
*
|
|
4
|
+
* This adapter subscribes to a Dash "LiveQuery" which returns pointers to shared memory (SharedArrayBuffer)
|
|
5
|
+
* or Float32Arrays. It then syncs this data directly to the HexGridWasm instance.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Placeholder types until we link the actual Dash package
|
|
9
|
+
interface DashQueryHandle {
|
|
10
|
+
ptr: number;
|
|
11
|
+
size: number;
|
|
12
|
+
buffer: SharedArrayBuffer | ArrayBuffer;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class DashAdapter {
|
|
16
|
+
private dash: any; // Will be typed when we link @buley/dash
|
|
17
|
+
|
|
18
|
+
constructor(dashInstance: any) {
|
|
19
|
+
this.dash = dashInstance;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Subscribe to a semantic query in Dash and sync results to HexGrid.
|
|
24
|
+
*
|
|
25
|
+
* @param query The vector search query
|
|
26
|
+
* @param gridInstance The WASM instance of the HexGrid
|
|
27
|
+
*/
|
|
28
|
+
bindSemanticSearch(query: string, particleSystem: any) {
|
|
29
|
+
console.log('[DashAdapter] Binding semantic search:', query);
|
|
30
|
+
|
|
31
|
+
// Hypothetical Zero-Copy API from Dash 2.0
|
|
32
|
+
if (this.dash.liveQueryPtr) {
|
|
33
|
+
this.dash.liveQueryPtr(`SELECT embedding FROM dash_vec_idx WHERE embedding MATCH '${query}'`).subscribe((handle: DashQueryHandle) => {
|
|
34
|
+
console.log(`[DashAdapter] Received ${handle.size} bytes from Dash.`);
|
|
35
|
+
|
|
36
|
+
// Assume the handle.buffer contains [pos, color, scale] interleaved or tightly packed
|
|
37
|
+
// For this MVP, we treat it as just positions
|
|
38
|
+
const floatView = new Float32Array(handle.buffer);
|
|
39
|
+
|
|
40
|
+
// Zero-Copy Injection logic would go here
|
|
41
|
+
// We can't strictly "inject" one buffer into 3 separate Float32Arrays unless they are contiguous in the SAB
|
|
42
|
+
// or we create views into offsets.
|
|
43
|
+
|
|
44
|
+
// Hypothetical offset logic:
|
|
45
|
+
const count = handle.size / 4 / 7; // pos + color + scale = 3+3+1 = 7 floats
|
|
46
|
+
|
|
47
|
+
// particleSystem.setSharedBuffers({
|
|
48
|
+
// positions: floatView.subarray(0, count * 3),
|
|
49
|
+
// colors: floatView.subarray(count * 3, count * 6),
|
|
50
|
+
// scales: floatView.subarray(count * 6, count * 7)
|
|
51
|
+
// });
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
console.warn('[DashAdapter] Dash instance does not support Zero-Copy liveQueryPtr yet.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/adapters.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Type-safe adapter pattern for converting domain objects to GridItems
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { GridItem } from './types'
|
|
5
|
+
import type { GridItem } from './types';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Options for adapter conversion
|
|
@@ -11,15 +11,15 @@ export interface AdapterOptions {
|
|
|
11
11
|
/**
|
|
12
12
|
* Custom velocity calculation override
|
|
13
13
|
*/
|
|
14
|
-
velocity?: number
|
|
14
|
+
velocity?: number;
|
|
15
15
|
/**
|
|
16
16
|
* Custom visual URL override
|
|
17
17
|
*/
|
|
18
|
-
visualUrl?: string
|
|
18
|
+
visualUrl?: string;
|
|
19
19
|
/**
|
|
20
20
|
* Additional metadata to merge
|
|
21
21
|
*/
|
|
22
|
-
metadata?: Record<string, unknown
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -29,37 +29,35 @@ export interface ItemAdapter<T> {
|
|
|
29
29
|
/**
|
|
30
30
|
* Convert a domain object to a GridItem
|
|
31
31
|
*/
|
|
32
|
-
toGridItem(data: T, options?: AdapterOptions): GridItem<T
|
|
32
|
+
toGridItem(data: T, options?: AdapterOptions): GridItem<T>;
|
|
33
33
|
/**
|
|
34
34
|
* Extract the original domain object from a GridItem
|
|
35
35
|
*/
|
|
36
|
-
fromGridItem(item: GridItem<T>): T
|
|
36
|
+
fromGridItem(item: GridItem<T>): T;
|
|
37
37
|
/**
|
|
38
38
|
* Calculate velocity for the item (optional)
|
|
39
39
|
*/
|
|
40
|
-
calculateVelocity?(data: T): number
|
|
40
|
+
calculateVelocity?(data: T): number;
|
|
41
41
|
/**
|
|
42
42
|
* Extract visual URL for the item (optional)
|
|
43
43
|
*/
|
|
44
|
-
extractVisualUrl?(data: T): string | undefined
|
|
44
|
+
extractVisualUrl?(data: T): string | undefined;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Helper to create adapters for common patterns
|
|
49
49
|
*/
|
|
50
|
-
export function createAdapter<T>(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
): ItemAdapter<T> {
|
|
50
|
+
export function createAdapter<T>(config: {
|
|
51
|
+
type: string;
|
|
52
|
+
toGridItem: (data: T, options?: AdapterOptions) => GridItem<T>;
|
|
53
|
+
fromGridItem: (item: GridItem<T>) => T;
|
|
54
|
+
calculateVelocity?: (data: T) => number;
|
|
55
|
+
extractVisualUrl?: (data: T) => string | undefined;
|
|
56
|
+
}): ItemAdapter<T> {
|
|
59
57
|
return {
|
|
60
58
|
toGridItem: config.toGridItem,
|
|
61
59
|
fromGridItem: config.fromGridItem,
|
|
62
60
|
calculateVelocity: config.calculateVelocity,
|
|
63
61
|
extractVisualUrl: config.extractVisualUrl,
|
|
64
|
-
}
|
|
62
|
+
};
|
|
65
63
|
}
|
|
@@ -21,10 +21,12 @@ export function theilIndex(values: number[]): number {
|
|
|
21
21
|
if (values.length === 0) return 0;
|
|
22
22
|
const avg = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
23
23
|
if (avg === 0) return 0;
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
return (
|
|
25
|
+
values.reduce((sum, val) => {
|
|
26
|
+
const ratio = val / avg;
|
|
27
|
+
return sum + (ratio === 0 ? 0 : ratio * Math.log(ratio));
|
|
28
|
+
}, 0) / values.length
|
|
29
|
+
);
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export function atkinsonIndex(values: number[], epsilon: number): number {
|
|
@@ -45,7 +47,10 @@ export function atkinsonIndex(values: number[], epsilon: number): number {
|
|
|
45
47
|
return 1 - eq / avg;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
export function paretoRatio(
|
|
50
|
+
export function paretoRatio(
|
|
51
|
+
values: number[],
|
|
52
|
+
topFraction: number
|
|
53
|
+
): {
|
|
49
54
|
ratioHeld: number;
|
|
50
55
|
paretoIndex: number;
|
|
51
56
|
} {
|
|
@@ -126,7 +131,7 @@ export function detectTrend(values: number[]): TrendResult {
|
|
|
126
131
|
let den = 0;
|
|
127
132
|
let ssRes = 0;
|
|
128
133
|
let ssTot = 0;
|
|
129
|
-
|
|
134
|
+
|
|
130
135
|
for (let i = 0; i < n; i++) {
|
|
131
136
|
const dx = i - xMean;
|
|
132
137
|
num += dx * (values[i] - yMean);
|
|
@@ -134,7 +139,7 @@ export function detectTrend(values: number[]): TrendResult {
|
|
|
134
139
|
}
|
|
135
140
|
const slope = den === 0 ? 0 : num / den;
|
|
136
141
|
const intercept = yMean - slope * xMean;
|
|
137
|
-
|
|
142
|
+
|
|
138
143
|
// Calculate R-squared
|
|
139
144
|
for (let i = 0; i < n; i++) {
|
|
140
145
|
const predicted = slope * i + intercept;
|
|
@@ -142,8 +147,9 @@ export function detectTrend(values: number[]): TrendResult {
|
|
|
142
147
|
ssTot += Math.pow(values[i] - yMean, 2);
|
|
143
148
|
}
|
|
144
149
|
const rSquared = ssTot === 0 ? 0 : 1 - ssRes / ssTot;
|
|
145
|
-
|
|
146
|
-
const direction =
|
|
150
|
+
|
|
151
|
+
const direction =
|
|
152
|
+
slope > 0.1 ? 'increasing' : slope < -0.1 ? 'decreasing' : 'stable';
|
|
147
153
|
return { slope, direction, rSquared };
|
|
148
154
|
}
|
|
149
155
|
|
|
@@ -151,7 +157,8 @@ export function detectChangePoints(values: number[]): number[] {
|
|
|
151
157
|
if (values.length < 3) return [];
|
|
152
158
|
const changes: number[] = [];
|
|
153
159
|
const avg = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
154
|
-
const variance =
|
|
160
|
+
const variance =
|
|
161
|
+
values.reduce((sum, val) => sum + (val - avg) ** 2, 0) / values.length;
|
|
155
162
|
const threshold = Math.sqrt(variance) * 1.5;
|
|
156
163
|
for (let i = 1; i < values.length; i++) {
|
|
157
164
|
if (Math.abs(values[i] - values[i - 1]) > threshold) {
|
|
@@ -173,7 +180,10 @@ export function movingAverage(values: number[], windowSize: number): number[] {
|
|
|
173
180
|
return result;
|
|
174
181
|
}
|
|
175
182
|
|
|
176
|
-
export function exponentialMovingAverage(
|
|
183
|
+
export function exponentialMovingAverage(
|
|
184
|
+
values: number[],
|
|
185
|
+
alpha: number
|
|
186
|
+
): number[] {
|
|
177
187
|
if (values.length === 0) return [];
|
|
178
188
|
const result: number[] = [];
|
|
179
189
|
let current = values[0] ?? 0;
|
|
@@ -205,7 +215,11 @@ export function sparkline(values: number[]): string {
|
|
|
205
215
|
.join('');
|
|
206
216
|
}
|
|
207
217
|
|
|
208
|
-
export function sparklineSvg(
|
|
218
|
+
export function sparklineSvg(
|
|
219
|
+
values: number[],
|
|
220
|
+
width: number = 100,
|
|
221
|
+
height: number = 20
|
|
222
|
+
): string {
|
|
209
223
|
if (values.length === 0) return '';
|
|
210
224
|
const min = Math.min(...values);
|
|
211
225
|
const max = Math.max(...values);
|
|
@@ -254,32 +268,48 @@ export function hellingerDistance(p: number[], q: number[]): number {
|
|
|
254
268
|
}
|
|
255
269
|
|
|
256
270
|
// Double Exponential Smoothing (Holt's method)
|
|
257
|
-
export function doubleExponentialSmoothing(
|
|
271
|
+
export function doubleExponentialSmoothing(
|
|
272
|
+
values: number[],
|
|
273
|
+
alpha: number = 0.3,
|
|
274
|
+
beta: number = 0.1
|
|
275
|
+
): number[] {
|
|
258
276
|
if (values.length === 0) return [];
|
|
259
277
|
const result: number[] = [];
|
|
260
278
|
let level = values[0] ?? 0;
|
|
261
279
|
let trend = 0;
|
|
262
|
-
|
|
280
|
+
|
|
263
281
|
result.push(level);
|
|
264
|
-
|
|
282
|
+
|
|
265
283
|
for (let i = 1; i < values.length; i++) {
|
|
266
284
|
const prevLevel = level;
|
|
267
285
|
level = alpha * values[i] + (1 - alpha) * (level + trend);
|
|
268
286
|
trend = beta * (level - prevLevel) + (1 - beta) * trend;
|
|
269
287
|
result.push(level + trend);
|
|
270
288
|
}
|
|
271
|
-
|
|
289
|
+
|
|
272
290
|
return result;
|
|
273
291
|
}
|
|
274
292
|
|
|
275
293
|
// Euler Characteristic (simplified for 2D)
|
|
276
|
-
export function eulerCharacteristic(
|
|
294
|
+
export function eulerCharacteristic(
|
|
295
|
+
vertices: number,
|
|
296
|
+
edges: number,
|
|
297
|
+
faces: number
|
|
298
|
+
): number {
|
|
277
299
|
return vertices - edges + faces;
|
|
278
300
|
}
|
|
279
301
|
|
|
280
302
|
// Estimate Betti Numbers (simplified - returns basic topological invariants)
|
|
281
|
-
export function estimateBettiNumbers(complex: {
|
|
282
|
-
|
|
303
|
+
export function estimateBettiNumbers(complex: {
|
|
304
|
+
vertices: number;
|
|
305
|
+
edges: number;
|
|
306
|
+
faces: number;
|
|
307
|
+
}): { b0: number; b1: number } {
|
|
308
|
+
const euler = eulerCharacteristic(
|
|
309
|
+
complex.vertices,
|
|
310
|
+
complex.edges,
|
|
311
|
+
complex.faces
|
|
312
|
+
);
|
|
283
313
|
// Simplified: b0 = number of connected components (assume 1 for now)
|
|
284
314
|
// b1 = edges - vertices + 1 (for a connected graph)
|
|
285
315
|
const b0 = 1;
|
|
@@ -303,7 +333,9 @@ export interface TerritoryStats {
|
|
|
303
333
|
compactness: number;
|
|
304
334
|
}
|
|
305
335
|
|
|
306
|
-
export function computeTerritoryStats(
|
|
336
|
+
export function computeTerritoryStats(
|
|
337
|
+
territories: Array<{ area: number; perimeter: number }>
|
|
338
|
+
): TerritoryStats {
|
|
307
339
|
if (territories.length === 0) {
|
|
308
340
|
return {
|
|
309
341
|
totalTerritories: 0,
|
|
@@ -313,11 +345,13 @@ export function computeTerritoryStats(territories: Array<{ area: number; perimet
|
|
|
313
345
|
compactness: 0,
|
|
314
346
|
};
|
|
315
347
|
}
|
|
316
|
-
|
|
317
|
-
const sizes = territories.map(t => t.area);
|
|
348
|
+
|
|
349
|
+
const sizes = territories.map((t) => t.area);
|
|
318
350
|
const totalSize = sizes.reduce((sum, s) => sum + s, 0);
|
|
319
|
-
const avgCompactness =
|
|
320
|
-
|
|
351
|
+
const avgCompactness =
|
|
352
|
+
territories.reduce((sum, t) => sum + compactness(t.area, t.perimeter), 0) /
|
|
353
|
+
territories.length;
|
|
354
|
+
|
|
321
355
|
return {
|
|
322
356
|
totalTerritories: territories.length,
|
|
323
357
|
averageSize: totalSize / territories.length,
|
|
@@ -47,7 +47,12 @@ export class KalmanFilter {
|
|
|
47
47
|
private processNoise: number;
|
|
48
48
|
private measurementNoise: number;
|
|
49
49
|
|
|
50
|
-
constructor(
|
|
50
|
+
constructor(
|
|
51
|
+
initialState: number,
|
|
52
|
+
initialUncertainty: number,
|
|
53
|
+
processNoise: number,
|
|
54
|
+
measurementNoise: number
|
|
55
|
+
) {
|
|
51
56
|
this.state = initialState;
|
|
52
57
|
this.uncertainty = initialUncertainty;
|
|
53
58
|
this.processNoise = processNoise;
|
|
@@ -56,7 +61,8 @@ export class KalmanFilter {
|
|
|
56
61
|
|
|
57
62
|
update(measurement: number): void {
|
|
58
63
|
const predictedUncertainty = this.uncertainty + this.processNoise;
|
|
59
|
-
const kalmanGain =
|
|
64
|
+
const kalmanGain =
|
|
65
|
+
predictedUncertainty / (predictedUncertainty + this.measurementNoise);
|
|
60
66
|
this.state = this.state + kalmanGain * (measurement - this.state);
|
|
61
67
|
this.uncertainty = (1 - kalmanGain) * predictedUncertainty;
|
|
62
68
|
}
|
|
@@ -106,7 +112,10 @@ export function bayesianWinProbability(wins: number, losses: number): number {
|
|
|
106
112
|
return wins / total;
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
export function bayesianConquestRate(
|
|
115
|
+
export function bayesianConquestRate(
|
|
116
|
+
successes: number,
|
|
117
|
+
trials: number
|
|
118
|
+
): number {
|
|
110
119
|
if (trials === 0) return 0;
|
|
111
120
|
return successes / trials;
|
|
112
121
|
}
|
|
@@ -121,7 +130,9 @@ export interface ProbabilitySnapshot {
|
|
|
121
130
|
probabilities: Array<{ label: string; probability: number }>;
|
|
122
131
|
}
|
|
123
132
|
|
|
124
|
-
export function generateProbabilitySnapshot(
|
|
133
|
+
export function generateProbabilitySnapshot(
|
|
134
|
+
labels: string[]
|
|
135
|
+
): ProbabilitySnapshot {
|
|
125
136
|
const probability = labels.length > 0 ? 1 / labels.length : 0;
|
|
126
137
|
return {
|
|
127
138
|
probabilities: labels.map((label) => ({ label, probability })),
|
|
@@ -138,7 +149,7 @@ export class DirichletDistribution {
|
|
|
138
149
|
|
|
139
150
|
mean(): number[] {
|
|
140
151
|
const sum = this.alphas.reduce((s, a) => s + a, 0);
|
|
141
|
-
return this.alphas.map(a => sum === 0 ? 0 : a / sum);
|
|
152
|
+
return this.alphas.map((a) => (sum === 0 ? 0 : a / sum));
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
|
|
@@ -222,19 +233,30 @@ export class HiddenMarkovModel {
|
|
|
222
233
|
}
|
|
223
234
|
|
|
224
235
|
// Bayesian A/B Test
|
|
225
|
-
export function bayesianABTest(
|
|
236
|
+
export function bayesianABTest(
|
|
237
|
+
successA: number,
|
|
238
|
+
trialsA: number,
|
|
239
|
+
successB: number,
|
|
240
|
+
trialsB: number
|
|
241
|
+
): number {
|
|
226
242
|
const probA = trialsA === 0 ? 0.5 : successA / trialsA;
|
|
227
243
|
const probB = trialsB === 0 ? 0.5 : successB / trialsB;
|
|
228
244
|
return probB - probA; // Difference in probabilities
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
// Bayes Factor
|
|
232
|
-
export function bayesFactor(
|
|
248
|
+
export function bayesFactor(
|
|
249
|
+
priorOdds: number,
|
|
250
|
+
likelihoodRatio: number
|
|
251
|
+
): number {
|
|
233
252
|
return priorOdds * likelihoodRatio;
|
|
234
253
|
}
|
|
235
254
|
|
|
236
255
|
// MAP Estimate (Maximum A Posteriori)
|
|
237
|
-
export function mapEstimate(
|
|
256
|
+
export function mapEstimate(
|
|
257
|
+
data: number[],
|
|
258
|
+
prior: { alpha: number; beta: number }
|
|
259
|
+
): number {
|
|
238
260
|
const sum = data.reduce((s, x) => s + x, 0);
|
|
239
261
|
const n = data.length;
|
|
240
262
|
const alpha = prior.alpha + sum;
|
|
@@ -252,7 +274,11 @@ export function learnMarkovChain(sequence: string[]): MarkovChain {
|
|
|
252
274
|
}
|
|
253
275
|
|
|
254
276
|
// Bootstrap Confidence Interval
|
|
255
|
-
export function bootstrapConfidenceInterval(
|
|
277
|
+
export function bootstrapConfidenceInterval(
|
|
278
|
+
data: number[],
|
|
279
|
+
iterations: number = 1000,
|
|
280
|
+
confidence: number = 0.95
|
|
281
|
+
): { lower: number; upper: number } {
|
|
256
282
|
const samples: number[] = [];
|
|
257
283
|
for (let i = 0; i < iterations; i++) {
|
|
258
284
|
const sample: number[] = [];
|
|
@@ -262,8 +288,8 @@ export function bootstrapConfidenceInterval(data: number[], iterations: number =
|
|
|
262
288
|
samples.push(sample.reduce((s, x) => s + x, 0) / sample.length);
|
|
263
289
|
}
|
|
264
290
|
samples.sort((a, b) => a - b);
|
|
265
|
-
const lowerIdx = Math.floor((1 - confidence) / 2 * samples.length);
|
|
266
|
-
const upperIdx = Math.floor((1 + confidence) / 2 * samples.length);
|
|
291
|
+
const lowerIdx = Math.floor(((1 - confidence) / 2) * samples.length);
|
|
292
|
+
const upperIdx = Math.floor(((1 + confidence) / 2) * samples.length);
|
|
267
293
|
return {
|
|
268
294
|
lower: samples[lowerIdx] ?? 0,
|
|
269
295
|
upper: samples[upperIdx] ?? 0,
|
|
@@ -271,7 +297,12 @@ export function bootstrapConfidenceInterval(data: number[], iterations: number =
|
|
|
271
297
|
}
|
|
272
298
|
|
|
273
299
|
// Monte Carlo Integration
|
|
274
|
-
export function monteCarloIntegrate(
|
|
300
|
+
export function monteCarloIntegrate(
|
|
301
|
+
fn: (x: number) => number,
|
|
302
|
+
a: number,
|
|
303
|
+
b: number,
|
|
304
|
+
samples: number = 1000
|
|
305
|
+
): number {
|
|
275
306
|
let sum = 0;
|
|
276
307
|
for (let i = 0; i < samples; i++) {
|
|
277
308
|
const x = a + Math.random() * (b - a);
|
|
@@ -30,7 +30,13 @@ export class FlowField2D {
|
|
|
30
30
|
this.sources = [];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
addSource(
|
|
33
|
+
addSource(
|
|
34
|
+
x: number,
|
|
35
|
+
y: number,
|
|
36
|
+
vx: number,
|
|
37
|
+
vy: number,
|
|
38
|
+
strength: number
|
|
39
|
+
): void {
|
|
34
40
|
this.sources.push({ x, y, vx, vy, strength });
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -55,7 +61,10 @@ export class FlowField2D {
|
|
|
55
61
|
return new Vector2(vx, vy);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
sampleFull(
|
|
64
|
+
sampleFull(
|
|
65
|
+
x: number,
|
|
66
|
+
y: number
|
|
67
|
+
): { velocity: Vector2; divergence: number; curl: number } {
|
|
59
68
|
const velocity = this.sample(x, y);
|
|
60
69
|
return {
|
|
61
70
|
velocity,
|
|
@@ -64,7 +73,11 @@ export class FlowField2D {
|
|
|
64
73
|
};
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
traceStreamline(
|
|
76
|
+
traceStreamline(
|
|
77
|
+
x: number,
|
|
78
|
+
y: number,
|
|
79
|
+
options: { maxLength: number; stepSize: number; maxSteps: number }
|
|
80
|
+
): Streamline {
|
|
68
81
|
const points: Vector2[] = [new Vector2(x, y)];
|
|
69
82
|
let current = new Vector2(x, y);
|
|
70
83
|
for (let i = 0; i < options.maxSteps; i++) {
|
|
@@ -106,7 +119,12 @@ export class HeatMap {
|
|
|
106
119
|
private resolution: number;
|
|
107
120
|
private data: Float32Array;
|
|
108
121
|
|
|
109
|
-
constructor(config: {
|
|
122
|
+
constructor(config: {
|
|
123
|
+
width: number;
|
|
124
|
+
height: number;
|
|
125
|
+
resolution: number;
|
|
126
|
+
kernelRadius: number;
|
|
127
|
+
}) {
|
|
110
128
|
this.width = Math.max(1, Math.round(config.width / config.resolution));
|
|
111
129
|
this.height = Math.max(1, Math.round(config.height / config.resolution));
|
|
112
130
|
this.resolution = config.resolution;
|
|
@@ -114,8 +132,14 @@ export class HeatMap {
|
|
|
114
132
|
}
|
|
115
133
|
|
|
116
134
|
addPoint(x: number, y: number, value: number): void {
|
|
117
|
-
const gridX = Math.min(
|
|
118
|
-
|
|
135
|
+
const gridX = Math.min(
|
|
136
|
+
this.width - 1,
|
|
137
|
+
Math.max(0, Math.floor(x / this.resolution))
|
|
138
|
+
);
|
|
139
|
+
const gridY = Math.min(
|
|
140
|
+
this.height - 1,
|
|
141
|
+
Math.max(0, Math.floor(y / this.resolution))
|
|
142
|
+
);
|
|
119
143
|
const index = gridY * this.width + gridX;
|
|
120
144
|
this.data[index] += value;
|
|
121
145
|
}
|