redscript-mc 2.2.1 → 2.3.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.
- package/CHANGELOG.md +20 -0
- package/README.md +18 -2
- package/dist/src/__tests__/tuner/engine.test.d.ts +4 -0
- package/dist/src/__tests__/tuner/engine.test.js +232 -0
- package/dist/src/tuner/adapters/ln-polynomial.d.ts +23 -0
- package/dist/src/tuner/adapters/ln-polynomial.js +142 -0
- package/dist/src/tuner/adapters/sqrt-newton.d.ts +28 -0
- package/dist/src/tuner/adapters/sqrt-newton.js +125 -0
- package/dist/src/tuner/cli.d.ts +5 -0
- package/dist/src/tuner/cli.js +168 -0
- package/dist/src/tuner/engine.d.ts +17 -0
- package/dist/src/tuner/engine.js +215 -0
- package/dist/src/tuner/metrics.d.ts +15 -0
- package/dist/src/tuner/metrics.js +51 -0
- package/dist/src/tuner/simulator.d.ts +35 -0
- package/dist/src/tuner/simulator.js +78 -0
- package/dist/src/tuner/types.d.ts +32 -0
- package/dist/src/tuner/types.js +6 -0
- package/docs/STDLIB_ROADMAP.md +142 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/tuner/engine.test.ts +260 -0
- package/src/stdlib/bigint.mcrs +155 -192
- package/src/stdlib/bits.mcrs +158 -0
- package/src/stdlib/color.mcrs +160 -0
- package/src/stdlib/geometry.mcrs +124 -0
- package/src/stdlib/list.mcrs +125 -0
- package/src/stdlib/math.mcrs +90 -0
- package/src/stdlib/math_hp.mcrs +65 -0
- package/src/stdlib/random.mcrs +67 -0
- package/src/stdlib/signal.mcrs +112 -0
- package/src/stdlib/vec.mcrs +27 -0
- package/src/tuner/adapters/ln-polynomial.ts +147 -0
- package/src/tuner/adapters/sqrt-newton.ts +135 -0
- package/src/tuner/cli.ts +158 -0
- package/src/tuner/engine.ts +272 -0
- package/src/tuner/metrics.ts +66 -0
- package/src/tuner/simulator.ts +69 -0
- package/src/tuner/types.ts +44 -0
- package/docs/ARCHITECTURE.zh.md +0 -1088
- package/docs/COMPILATION_STATS.md +0 -142
- package/docs/IMPLEMENTATION_GUIDE.md +0 -512
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RedScript will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.3.0] - 2026-03-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `stdlib/math.mcrs`: `ln` (SA-tuned atanh series, max_error < 0.0006), `sqrt_fx` (×10000 scale), `exp_fx` (Horner Taylor + 2^k scaling)
|
|
9
|
+
- `stdlib/math_hp.mcrs`: `sin_hp`/`cos_hp` using MC entity rotation trick (double precision), `init_trig()` bootstrap
|
|
10
|
+
- `stdlib/random.mcrs`: LCG (`next_lcg`, `random_range`, `random_bool`) + PCG (`pcg_next_lo/hi`, `pcg_output`)
|
|
11
|
+
- `stdlib/color.mcrs`: RGB packing/unpacking, `rgb_lerp`, HSL↔RGB conversion (`hsl_to_r/g/b`, `rgb_to_h/s/l`)
|
|
12
|
+
- `stdlib/bits.mcrs`: bitwise AND/OR/XOR/NOT, left/right shift, bit get/set/clear/toggle, popcount (all integer-simulated)
|
|
13
|
+
- `stdlib/list.mcrs`: `sort3`, min/max/avg for 3 and 5 values, weighted choice utilities
|
|
14
|
+
- `stdlib/geometry.mcrs`: AABB/sphere/cylinder contains checks, parabola physics, grid/tile helpers, angle normalization, MC sun angle
|
|
15
|
+
- `stdlib/signal.mcrs`: uniform, normal (12-sample approximation), exponential distribution, bernoulli trial, weighted2/3 choice
|
|
16
|
+
- `stdlib/bigint.mcrs`: 96-bit base-10000 arithmetic (add, sub, mul, div, cmp, int32↔bigint3 conversion)
|
|
17
|
+
- `src/tuner/`: hyperparameter search framework (Nelder-Mead + Simulated Annealing) for stdlib coefficient optimization
|
|
18
|
+
- `adapters/ln-polynomial.ts`: atanh series adapter
|
|
19
|
+
- `adapters/sqrt-newton.ts`: Newton iteration adapter
|
|
20
|
+
- CLI: `redscript tune --adapter <name> [--strategy nm|sa] [--budget N] [--out path]`
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `stdlib/vec.mcrs`: added 2D/3D add/sub/scale/neg component helpers
|
|
24
|
+
|
|
5
25
|
## [2.1.1] - 2026-03-16
|
|
6
26
|
|
|
7
27
|
### Added
|
package/README.md
CHANGED
|
@@ -306,8 +306,16 @@ redscript validate <file> Validate MC commands
|
|
|
306
306
|
RedScript ships a built-in standard library. Use the short form — no path needed:
|
|
307
307
|
|
|
308
308
|
```rs
|
|
309
|
-
import "stdlib/math" // fixed-point math
|
|
310
|
-
import "stdlib/
|
|
309
|
+
import "stdlib/math" // fixed-point math: ln, sqrt_fx, exp_fx, sin_fixed, cos_fixed...
|
|
310
|
+
import "stdlib/math_hp" // high-precision trig via entity rotation (init_trig required)
|
|
311
|
+
import "stdlib/vec" // 2D/3D vector: dot, cross, length, distance, atan2, rotate...
|
|
312
|
+
import "stdlib/random" // LCG & PCG random number generators
|
|
313
|
+
import "stdlib/color" // RGB/HSL color packing, blending, conversion
|
|
314
|
+
import "stdlib/bits" // bitwise AND/OR/XOR/NOT/shift/popcount (integer-simulated)
|
|
315
|
+
import "stdlib/list" // sort3, min/max/avg, weighted utilities
|
|
316
|
+
import "stdlib/geometry" // AABB/sphere contains, parabola physics, angle helpers
|
|
317
|
+
import "stdlib/signal" // normal/exponential distributions, bernoulli, weighted choice
|
|
318
|
+
import "stdlib/bigint" // 96-bit base-10000 arithmetic (add/sub/mul/div/cmp)
|
|
311
319
|
import "stdlib/combat" // damage, kill-check helpers
|
|
312
320
|
import "stdlib/player" // health, alive check, range
|
|
313
321
|
import "stdlib/cooldown" // per-player cooldown tracking
|
|
@@ -319,6 +327,8 @@ Custom library paths can be added with `--include <dir>` so your own modules wor
|
|
|
319
327
|
|
|
320
328
|
All stdlib files use `module library;` — only the functions you actually call are compiled in.
|
|
321
329
|
|
|
330
|
+
> Parts of the standard library are inspired by [kaer-3058/large_number](https://github.com/kaer-3058/large_number), a comprehensive math library for Minecraft datapacks.
|
|
331
|
+
|
|
322
332
|
```rs
|
|
323
333
|
import "stdlib/math" // abs, sign, min, max, clamp, lerp, isqrt, sqrt_fixed,
|
|
324
334
|
// pow_int, gcd, lcm, sin_fixed, cos_fixed, map, ceil_div,
|
|
@@ -444,6 +454,12 @@ See [CHANGELOG.md](./CHANGELOG.md) for the full release notes.
|
|
|
444
454
|
|
|
445
455
|
---
|
|
446
456
|
|
|
457
|
+
## Acknowledgements
|
|
458
|
+
|
|
459
|
+
Parts of the standard library are inspired by [kaer-3058/large_number](https://github.com/kaer-3058/large_number), a comprehensive math library for Minecraft datapacks. RedScript provides a higher-level, type-safe API over similar algorithms.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
447
463
|
<div align="center">
|
|
448
464
|
|
|
449
465
|
MIT License · Copyright © 2026 [bkmashiro](https://github.com/bkmashiro)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for the redscript tuner engine, simulator, and ln-polynomial adapter.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const engine_1 = require("../../tuner/engine");
|
|
7
|
+
const simulator_1 = require("../../tuner/simulator");
|
|
8
|
+
const metrics_1 = require("../../tuner/metrics");
|
|
9
|
+
const ln_polynomial_1 = require("../../tuner/adapters/ln-polynomial");
|
|
10
|
+
const sqrt_newton_1 = require("../../tuner/adapters/sqrt-newton");
|
|
11
|
+
// ─── simulator tests ──────────────────────────────────────────────────────────
|
|
12
|
+
describe('simulator', () => {
|
|
13
|
+
test('i32 truncates to int32', () => {
|
|
14
|
+
expect((0, simulator_1.i32)(3.7)).toBe(3);
|
|
15
|
+
expect((0, simulator_1.i32)(-3.7)).toBe(-3);
|
|
16
|
+
expect((0, simulator_1.i32)(2147483648)).toBe(-2147483648); // overflow wraps
|
|
17
|
+
expect((0, simulator_1.i32)(0)).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
test('fixedMul basic', () => {
|
|
20
|
+
// 10000 * 10000 / 10000 = 10000
|
|
21
|
+
expect((0, simulator_1.fixedMul)(10000, 10000, 10000)).toBe(10000);
|
|
22
|
+
// 5000 * 2 / 10000 = 1
|
|
23
|
+
expect((0, simulator_1.fixedMul)(5000, 2, 10000)).toBe(1);
|
|
24
|
+
});
|
|
25
|
+
test('fixedMul returns Infinity on overflow', () => {
|
|
26
|
+
expect((0, simulator_1.fixedMul)(2147483647, 2147483647, 1)).toBe(Infinity);
|
|
27
|
+
});
|
|
28
|
+
test('isOverflow detects out-of-range', () => {
|
|
29
|
+
expect((0, simulator_1.isOverflow)(2147483648)).toBe(true);
|
|
30
|
+
expect((0, simulator_1.isOverflow)(-2147483649)).toBe(true);
|
|
31
|
+
expect((0, simulator_1.isOverflow)(Infinity)).toBe(true);
|
|
32
|
+
expect((0, simulator_1.isOverflow)(NaN)).toBe(true);
|
|
33
|
+
expect((0, simulator_1.isOverflow)(0)).toBe(false);
|
|
34
|
+
expect((0, simulator_1.isOverflow)(2147483647)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
// ─── Nelder-Mead convergence test ────────────────────────────────────────────
|
|
38
|
+
describe('Nelder-Mead engine', () => {
|
|
39
|
+
test('converges to minimum of (x-3)^2', () => {
|
|
40
|
+
// Simple 1D minimization: minimize (x-3)^2
|
|
41
|
+
const mockAdapter = {
|
|
42
|
+
name: 'test-quadratic',
|
|
43
|
+
description: 'Minimize (x-3)^2',
|
|
44
|
+
params: [
|
|
45
|
+
{ name: 'x', range: [-10, 10], integer: false },
|
|
46
|
+
],
|
|
47
|
+
simulate(input, params) {
|
|
48
|
+
// Return the residual as a scaled integer
|
|
49
|
+
const x = params['x'];
|
|
50
|
+
return Math.round(x * 10000);
|
|
51
|
+
},
|
|
52
|
+
reference(_input) {
|
|
53
|
+
// Target: x = 3 → value 30000
|
|
54
|
+
return 30000;
|
|
55
|
+
},
|
|
56
|
+
sampleInputs() {
|
|
57
|
+
return [1]; // single input, target value is 3.0 (×10000 = 30000)
|
|
58
|
+
},
|
|
59
|
+
generateCode(params) {
|
|
60
|
+
return `// x = ${params['x']}`;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const result = (0, engine_1.search)(mockAdapter, 5000);
|
|
64
|
+
// Should converge close to x=3
|
|
65
|
+
expect(result.params['x']).toBeCloseTo(3.0, 1);
|
|
66
|
+
expect(result.maxError).toBeLessThan(0.1);
|
|
67
|
+
});
|
|
68
|
+
test('handles integer constraints', () => {
|
|
69
|
+
const mockAdapter = {
|
|
70
|
+
name: 'test-integer',
|
|
71
|
+
description: 'Integer parameter test',
|
|
72
|
+
params: [
|
|
73
|
+
{ name: 'n', range: [0, 10], integer: true },
|
|
74
|
+
],
|
|
75
|
+
simulate(input, params) {
|
|
76
|
+
// Should snap to integer 7
|
|
77
|
+
return Math.round(params['n'] * 10000);
|
|
78
|
+
},
|
|
79
|
+
reference(_input) {
|
|
80
|
+
return 70000; // 7.0 × 10000
|
|
81
|
+
},
|
|
82
|
+
sampleInputs() {
|
|
83
|
+
return [1];
|
|
84
|
+
},
|
|
85
|
+
generateCode() {
|
|
86
|
+
return '';
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const result = (0, engine_1.search)(mockAdapter, 2000);
|
|
90
|
+
// Should find n close to 7
|
|
91
|
+
expect(Math.round(result.params['n'])).toBe(7);
|
|
92
|
+
});
|
|
93
|
+
test('i32 overflow penalization', () => {
|
|
94
|
+
const mockAdapter = {
|
|
95
|
+
name: 'test-overflow',
|
|
96
|
+
description: 'Test overflow penalization',
|
|
97
|
+
params: [
|
|
98
|
+
{ name: 'scale', range: [1, 1000], integer: true },
|
|
99
|
+
],
|
|
100
|
+
simulate(_input, params) {
|
|
101
|
+
// Always overflow for any scale >= 500
|
|
102
|
+
if (params['scale'] >= 500)
|
|
103
|
+
return Infinity;
|
|
104
|
+
return params['scale'] * 10000;
|
|
105
|
+
},
|
|
106
|
+
reference(_input) {
|
|
107
|
+
return 2000000; // target: scale=200 → 2000000
|
|
108
|
+
},
|
|
109
|
+
sampleInputs() {
|
|
110
|
+
return [1];
|
|
111
|
+
},
|
|
112
|
+
generateCode() {
|
|
113
|
+
return '';
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const { maxError, mae, rmse } = (0, metrics_1.evaluate)(mockAdapter, { scale: 2147483647 });
|
|
117
|
+
expect(maxError).toBe(Infinity);
|
|
118
|
+
expect(mae).toBe(Infinity);
|
|
119
|
+
expect(rmse).toBe(Infinity);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// ─── ln-polynomial adapter tests ─────────────────────────────────────────────
|
|
123
|
+
describe('ln-polynomial adapter', () => {
|
|
124
|
+
const defaultParams = ln_polynomial_1.defaultParams; // { A1: 20000, A3: 6667, A5: 4000 }
|
|
125
|
+
test('sample inputs cover the valid range', () => {
|
|
126
|
+
const inputs = ln_polynomial_1.lnPolynomialAdapter.sampleInputs();
|
|
127
|
+
expect(inputs.length).toBeGreaterThan(50);
|
|
128
|
+
// All inputs should be positive
|
|
129
|
+
expect(inputs.every(x => x > 0)).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
test('reference matches Math.log', () => {
|
|
132
|
+
const SCALE = 10000;
|
|
133
|
+
// ln(1.0) = 0
|
|
134
|
+
expect(ln_polynomial_1.lnPolynomialAdapter.reference(SCALE)).toBeCloseTo(0, 5);
|
|
135
|
+
// ln(2.0) ≈ 0.6931 → 6931
|
|
136
|
+
expect(ln_polynomial_1.lnPolynomialAdapter.reference(2 * SCALE)).toBeCloseTo(6931.47, 0);
|
|
137
|
+
// ln(0.5) ≈ -0.6931 → -6931
|
|
138
|
+
expect(ln_polynomial_1.lnPolynomialAdapter.reference(5000)).toBeCloseTo(-6931.47, 0);
|
|
139
|
+
});
|
|
140
|
+
test('simulate produces reasonable output for x=1 (no error)', () => {
|
|
141
|
+
const result = ln_polynomial_1.lnPolynomialAdapter.simulate(10000, defaultParams);
|
|
142
|
+
// ln(1.0) = 0; allow some approximation error
|
|
143
|
+
expect(Math.abs(result)).toBeLessThan(500); // within 0.05
|
|
144
|
+
});
|
|
145
|
+
test('simulate returns Infinity for invalid input', () => {
|
|
146
|
+
const result = ln_polynomial_1.lnPolynomialAdapter.simulate(0, defaultParams);
|
|
147
|
+
expect(result).toBeLessThan(0); // negative sentinel or -MAX_INT
|
|
148
|
+
});
|
|
149
|
+
test('max_error < 0.001 with default atanh coefficients', () => {
|
|
150
|
+
const metrics = (0, metrics_1.evaluate)(ln_polynomial_1.lnPolynomialAdapter, defaultParams);
|
|
151
|
+
expect(metrics.maxError).toBeLessThan(0.001);
|
|
152
|
+
}, 10000);
|
|
153
|
+
test('search improves over default params', () => {
|
|
154
|
+
// Run a short search and confirm it doesn't get worse
|
|
155
|
+
const baseMetrics = (0, metrics_1.evaluate)(ln_polynomial_1.lnPolynomialAdapter, defaultParams);
|
|
156
|
+
const result = (0, engine_1.search)(ln_polynomial_1.lnPolynomialAdapter, 500); // short budget for test speed
|
|
157
|
+
// Either same or better
|
|
158
|
+
expect(result.maxError).toBeLessThanOrEqual(baseMetrics.maxError * 2);
|
|
159
|
+
expect(result.maxError).toBeLessThan(0.01);
|
|
160
|
+
}, 30000);
|
|
161
|
+
test('generateCode produces valid output', () => {
|
|
162
|
+
const meta = {
|
|
163
|
+
maxError: 0.00003,
|
|
164
|
+
mae: 0.000012,
|
|
165
|
+
rmse: 0.000015,
|
|
166
|
+
estimatedCmds: 38,
|
|
167
|
+
tuneDate: '2026-03-17',
|
|
168
|
+
budgetUsed: 5000,
|
|
169
|
+
};
|
|
170
|
+
const code = ln_polynomial_1.lnPolynomialAdapter.generateCode(defaultParams, meta);
|
|
171
|
+
expect(code).toContain('AUTO-GENERATED');
|
|
172
|
+
expect(code).toContain('ln-polynomial');
|
|
173
|
+
expect(code).toContain('fn ln');
|
|
174
|
+
expect(code).toContain('A1');
|
|
175
|
+
expect(code).toContain('A3');
|
|
176
|
+
expect(code).toContain('A5');
|
|
177
|
+
expect(code).toContain('2026-03-17');
|
|
178
|
+
});
|
|
179
|
+
test('searchSA achieves max_error < 0.001 on ln-polynomial', () => {
|
|
180
|
+
const result = (0, engine_1.searchSA)(ln_polynomial_1.lnPolynomialAdapter, 3000);
|
|
181
|
+
expect(result.maxError).toBeLessThan(0.001);
|
|
182
|
+
}, 30000);
|
|
183
|
+
});
|
|
184
|
+
// ─── sqrt-newton adapter tests ────────────────────────────────────────────────
|
|
185
|
+
describe('sqrt-newton adapter', () => {
|
|
186
|
+
test('simulate(10000, defaultParams) ≈ 10000 (sqrt(1.0)=1.0)', () => {
|
|
187
|
+
const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(10000, sqrt_newton_1.defaultParams);
|
|
188
|
+
// sqrt(1.0) * 10000 = 10000
|
|
189
|
+
expect(Math.abs(result - 10000)).toBeLessThan(10);
|
|
190
|
+
});
|
|
191
|
+
test('simulate(40000, defaultParams) ≈ 20000 (sqrt(4.0)=2.0)', () => {
|
|
192
|
+
const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(40000, sqrt_newton_1.defaultParams);
|
|
193
|
+
// sqrt(4.0) * 10000 = 20000
|
|
194
|
+
expect(Math.abs(result - 20000)).toBeLessThan(10);
|
|
195
|
+
});
|
|
196
|
+
test('simulate(0) returns 0', () => {
|
|
197
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.simulate(0, sqrt_newton_1.defaultParams)).toBe(0);
|
|
198
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.simulate(-1, sqrt_newton_1.defaultParams)).toBe(0);
|
|
199
|
+
});
|
|
200
|
+
test('simulate(250000, defaultParams) ≈ 50000 (sqrt(25.0)=5.0)', () => {
|
|
201
|
+
const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(250000, sqrt_newton_1.defaultParams);
|
|
202
|
+
expect(Math.abs(result - 50000)).toBeLessThan(10);
|
|
203
|
+
});
|
|
204
|
+
test('sample inputs are all positive', () => {
|
|
205
|
+
const inputs = sqrt_newton_1.sqrtNewtonAdapter.sampleInputs();
|
|
206
|
+
expect(inputs.length).toBeGreaterThan(50);
|
|
207
|
+
expect(inputs.every(x => x > 0)).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
test('reference matches Math.sqrt', () => {
|
|
210
|
+
const SCALE = 10000;
|
|
211
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.reference(SCALE)).toBe(SCALE); // sqrt(1.0)
|
|
212
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.reference(4 * SCALE)).toBe(2 * SCALE); // sqrt(4.0)
|
|
213
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.reference(9 * SCALE)).toBe(3 * SCALE); // sqrt(9.0)
|
|
214
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.reference(0)).toBe(0);
|
|
215
|
+
});
|
|
216
|
+
test('generateCode contains fn sqrt_fx', () => {
|
|
217
|
+
const meta = {
|
|
218
|
+
maxError: 1.5,
|
|
219
|
+
mae: 0.5,
|
|
220
|
+
rmse: 0.8,
|
|
221
|
+
estimatedCmds: 30,
|
|
222
|
+
tuneDate: '2026-03-17',
|
|
223
|
+
budgetUsed: 3000,
|
|
224
|
+
};
|
|
225
|
+
const code = sqrt_newton_1.sqrtNewtonAdapter.generateCode(sqrt_newton_1.defaultParams, meta);
|
|
226
|
+
expect(code).toContain('AUTO-GENERATED');
|
|
227
|
+
expect(code).toContain('sqrt-newton');
|
|
228
|
+
expect(code).toContain('fn sqrt_fx');
|
|
229
|
+
expect(code).toContain('2026-03-17');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
//# sourceMappingURL=engine.test.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ln(x) polynomial approximation adapter — atanh series form.
|
|
3
|
+
*
|
|
4
|
+
* Algorithm:
|
|
5
|
+
* Input x: fixed-point integer (×10000), e.g. 10000 = 1.0
|
|
6
|
+
* 1. Range reduction: find k s.t. xr ∈ [10000, 20000)
|
|
7
|
+
* 2. s = (xr - 10000) * 10000 / (xr + 10000) → s ∈ [0, 3333]
|
|
8
|
+
* 3. ln(xr/10000) ≈ A1*s/SCALE + A3*s³/SCALE² + A5*s⁵/SCALE³
|
|
9
|
+
* (coefficients absorb the factor of 2; theoretical: A1=20000, A3=6667, A5=4000)
|
|
10
|
+
* 4. ln(x/10000) = k * LN2 + ln(xr/10000)
|
|
11
|
+
*
|
|
12
|
+
* Intermediate overflow analysis (s ≤ 3333, SCALE = 10000):
|
|
13
|
+
* s² = s*s ≤ 11M — fits int32 (max ~2.1B)
|
|
14
|
+
* s2 = s²/SCALE ≤ 1111
|
|
15
|
+
* s3 = s*s2 ≤ 3.7M — fits int32
|
|
16
|
+
* s5 = s3*s2 ≤ 4.1M — fits int32
|
|
17
|
+
* A1*s ≤ 22000*3333 ≤ 73M — fits int32
|
|
18
|
+
* A3*s3 ≤ 7000*3703 ≤ 26M — fits int32
|
|
19
|
+
* A5*s5 ≤ 5000*4115 ≤ 21M — fits int32
|
|
20
|
+
*/
|
|
21
|
+
import { TunerAdapter } from '../types';
|
|
22
|
+
export declare const defaultParams: Record<string, number>;
|
|
23
|
+
export declare const lnPolynomialAdapter: TunerAdapter;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ln(x) polynomial approximation adapter — atanh series form.
|
|
4
|
+
*
|
|
5
|
+
* Algorithm:
|
|
6
|
+
* Input x: fixed-point integer (×10000), e.g. 10000 = 1.0
|
|
7
|
+
* 1. Range reduction: find k s.t. xr ∈ [10000, 20000)
|
|
8
|
+
* 2. s = (xr - 10000) * 10000 / (xr + 10000) → s ∈ [0, 3333]
|
|
9
|
+
* 3. ln(xr/10000) ≈ A1*s/SCALE + A3*s³/SCALE² + A5*s⁵/SCALE³
|
|
10
|
+
* (coefficients absorb the factor of 2; theoretical: A1=20000, A3=6667, A5=4000)
|
|
11
|
+
* 4. ln(x/10000) = k * LN2 + ln(xr/10000)
|
|
12
|
+
*
|
|
13
|
+
* Intermediate overflow analysis (s ≤ 3333, SCALE = 10000):
|
|
14
|
+
* s² = s*s ≤ 11M — fits int32 (max ~2.1B)
|
|
15
|
+
* s2 = s²/SCALE ≤ 1111
|
|
16
|
+
* s3 = s*s2 ≤ 3.7M — fits int32
|
|
17
|
+
* s5 = s3*s2 ≤ 4.1M — fits int32
|
|
18
|
+
* A1*s ≤ 22000*3333 ≤ 73M — fits int32
|
|
19
|
+
* A3*s3 ≤ 7000*3703 ≤ 26M — fits int32
|
|
20
|
+
* A5*s5 ≤ 5000*4115 ≤ 21M — fits int32
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.lnPolynomialAdapter = exports.defaultParams = void 0;
|
|
24
|
+
const simulator_1 = require("../simulator");
|
|
25
|
+
const SCALE = 10000;
|
|
26
|
+
const LN2_SCALED = 6931; // ln(2) * 10000
|
|
27
|
+
exports.defaultParams = {
|
|
28
|
+
A1: 20000,
|
|
29
|
+
A3: 6667,
|
|
30
|
+
A5: 4000,
|
|
31
|
+
};
|
|
32
|
+
exports.lnPolynomialAdapter = {
|
|
33
|
+
name: 'ln-polynomial',
|
|
34
|
+
description: 'ln(x) atanh series approximation using fixed-point int32 arithmetic',
|
|
35
|
+
params: [
|
|
36
|
+
{ name: 'A1', range: [18000, 22000], integer: true },
|
|
37
|
+
{ name: 'A3', range: [5000, 9000], integer: true },
|
|
38
|
+
{ name: 'A5', range: [2000, 6000], integer: true },
|
|
39
|
+
],
|
|
40
|
+
simulate(input, params) {
|
|
41
|
+
const A1 = (0, simulator_1.i32)(params['A1']);
|
|
42
|
+
const A3 = (0, simulator_1.i32)(params['A3']);
|
|
43
|
+
const A5 = (0, simulator_1.i32)(params['A5']);
|
|
44
|
+
let x = (0, simulator_1.i32)(input);
|
|
45
|
+
if (x <= 0)
|
|
46
|
+
return -2147483648;
|
|
47
|
+
// Step 1: range reduction to [10000, 20000)
|
|
48
|
+
let k = 0;
|
|
49
|
+
while (x < 10000) {
|
|
50
|
+
x = (0, simulator_1.i32)(x * 2);
|
|
51
|
+
k--;
|
|
52
|
+
}
|
|
53
|
+
while (x >= 20000) {
|
|
54
|
+
x = (0, simulator_1.i32)(x / 2);
|
|
55
|
+
k++;
|
|
56
|
+
}
|
|
57
|
+
// Step 2: s = (xr - SCALE) * SCALE / (xr + SCALE)
|
|
58
|
+
const num = (0, simulator_1.i32)((0, simulator_1.i32)(x - SCALE) * SCALE);
|
|
59
|
+
const den = (0, simulator_1.i32)(x + SCALE);
|
|
60
|
+
if (den === 0)
|
|
61
|
+
return Infinity;
|
|
62
|
+
const s = (0, simulator_1.i32)(num / den); // s ∈ [0, 3333]
|
|
63
|
+
// Step 3: atanh series (overflow-safe, each power divided by SCALE)
|
|
64
|
+
const s2 = (0, simulator_1.i32)((0, simulator_1.i32)(s * s) / SCALE); // s²/SCALE ∈ [0, 1111]
|
|
65
|
+
const s3 = (0, simulator_1.i32)((0, simulator_1.i32)(s * s2) / SCALE); // s³/SCALE² ∈ [0, 370]
|
|
66
|
+
const s5 = (0, simulator_1.i32)((0, simulator_1.i32)(s3 * s2) / SCALE); // s⁵/SCALE⁴ ∈ [0, 41]
|
|
67
|
+
const lnxr = (0, simulator_1.i32)((0, simulator_1.i32)(A1 * s / SCALE) +
|
|
68
|
+
(0, simulator_1.i32)(A3 * s3 / SCALE) +
|
|
69
|
+
(0, simulator_1.i32)(A5 * s5 / SCALE));
|
|
70
|
+
// Step 4: ln(x) = k*ln(2) + ln(xr)
|
|
71
|
+
return (0, simulator_1.i32)((0, simulator_1.i32)(k * LN2_SCALED) + lnxr);
|
|
72
|
+
},
|
|
73
|
+
reference(input) {
|
|
74
|
+
if (input <= 0)
|
|
75
|
+
return -Infinity;
|
|
76
|
+
return Math.log(input / SCALE) * SCALE;
|
|
77
|
+
},
|
|
78
|
+
sampleInputs() {
|
|
79
|
+
const inputs = [];
|
|
80
|
+
// logarithmic sampling from 0.01 to 100 (×10000: 100 to 1_000_000)
|
|
81
|
+
const logMin = Math.log10(100);
|
|
82
|
+
const logMax = Math.log10(1_000_000);
|
|
83
|
+
const steps = 200;
|
|
84
|
+
for (let i = 0; i <= steps; i++) {
|
|
85
|
+
const v = logMin + (i / steps) * (logMax - logMin);
|
|
86
|
+
inputs.push(Math.round(Math.pow(10, v)));
|
|
87
|
+
}
|
|
88
|
+
return inputs;
|
|
89
|
+
},
|
|
90
|
+
generateCode(params, meta) {
|
|
91
|
+
const A1 = Math.round(params['A1']);
|
|
92
|
+
const A3 = Math.round(params['A3']);
|
|
93
|
+
const A5 = Math.round(params['A5']);
|
|
94
|
+
return `// AUTO-GENERATED by redscript tune — DO NOT EDIT
|
|
95
|
+
// Adapter: ln-polynomial | Date: ${meta.tuneDate}
|
|
96
|
+
// max_error: ${meta.maxError.toFixed(6)} | mae: ${meta.mae.toFixed(6)} | estimated_cmds: ${meta.estimatedCmds}
|
|
97
|
+
// Run \`redscript tune --adapter ln-polynomial\` to regenerate
|
|
98
|
+
//
|
|
99
|
+
// atanh series coefficients (×10000 scale):
|
|
100
|
+
// A1 = ${A1} (theoretical 2×10000 = 20000)
|
|
101
|
+
// A3 = ${A3} (theoretical 2/3×10000 = 6667)
|
|
102
|
+
// A5 = ${A5} (theoretical 2/5×10000 = 4000)
|
|
103
|
+
|
|
104
|
+
fn ln(x: int): int {
|
|
105
|
+
// Input x: fixed-point ×10000; returns ln(x/10000)×10000
|
|
106
|
+
// Valid range: x ∈ [100, 1000000] (0.01 ~ 100.0)
|
|
107
|
+
// max_error: ${meta.maxError.toFixed(6)} (in ×10000 units, i.e. ${(meta.maxError / SCALE).toFixed(8)} in real units)
|
|
108
|
+
|
|
109
|
+
let scale: int = ${SCALE};
|
|
110
|
+
let ln2: int = ${LN2_SCALED};
|
|
111
|
+
let A1: int = ${A1};
|
|
112
|
+
let A3: int = ${A3};
|
|
113
|
+
let A5: int = ${A5};
|
|
114
|
+
|
|
115
|
+
// Step 1: range reduction — bring x into [scale, 2*scale)
|
|
116
|
+
let xr: int = x;
|
|
117
|
+
let k: int = 0;
|
|
118
|
+
while (xr < scale) {
|
|
119
|
+
xr = xr * 2;
|
|
120
|
+
k = k - 1;
|
|
121
|
+
}
|
|
122
|
+
while (xr >= scale * 2) {
|
|
123
|
+
xr = xr / 2;
|
|
124
|
+
k = k + 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Step 2: s = (xr - scale) * scale / (xr + scale)
|
|
128
|
+
let s: int = (xr - scale) * scale / (xr + scale);
|
|
129
|
+
|
|
130
|
+
// Step 3: atanh series ln(xr/scale) ≈ A1*s + A3*s³ + A5*s⁵ (all /scale)
|
|
131
|
+
let s2: int = s * s / scale;
|
|
132
|
+
let s3: int = s * s2;
|
|
133
|
+
let s5: int = s3 * s2;
|
|
134
|
+
let lnxr: int = A1 * s / scale + A3 * s3 / scale + A5 * s5 / scale;
|
|
135
|
+
|
|
136
|
+
// Step 4: ln(x) = k*ln(2) + ln(xr/scale)
|
|
137
|
+
return k * ln2 + lnxr;
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=ln-polynomial.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sqrt(x) Newton's method adapter — fixed-point ×10000 scale.
|
|
3
|
+
*
|
|
4
|
+
* Algorithm:
|
|
5
|
+
* Input x: fixed-point integer (×10000), e.g. 10000 = 1.0
|
|
6
|
+
* Target: return floor(sqrt(x/10000) * 10000)
|
|
7
|
+
*
|
|
8
|
+
* 1. If x <= 0, return 0
|
|
9
|
+
* 2. Initial guess: g = x >> INIT_SHIFT (default: x/2)
|
|
10
|
+
* 3. Newton iteration: g = (g + x * 10000 / g) / 2, repeated N times
|
|
11
|
+
* Overflow-safe: x * 10000 / g → (x * 100) / (g / 100)
|
|
12
|
+
* 4. Return g
|
|
13
|
+
*
|
|
14
|
+
* Valid range: x ∈ [1, 1_000_000] (real range 0.0001 to 100.0)
|
|
15
|
+
* Larger x needs more iterations; beyond 1M, convergence is not guaranteed
|
|
16
|
+
* within N=12 iterations from an x/2 initial guess.
|
|
17
|
+
*
|
|
18
|
+
* Overflow analysis (x ≤ 1_000_000, SCALE = 10000):
|
|
19
|
+
* x * 100 ≤ 100_000_000 — fits int32 (max ~2.1B) ✓
|
|
20
|
+
* g / 100 is never 0 for g ≥ 100 (guard included) ✓
|
|
21
|
+
*
|
|
22
|
+
* Parameters:
|
|
23
|
+
* N: iteration count, range [4, 12], integer
|
|
24
|
+
* INIT_SHIFT: initial guess right shift, range [0, 3], integer (1 → x/2)
|
|
25
|
+
*/
|
|
26
|
+
import { TunerAdapter } from '../types';
|
|
27
|
+
export declare const defaultParams: Record<string, number>;
|
|
28
|
+
export declare const sqrtNewtonAdapter: TunerAdapter;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* sqrt(x) Newton's method adapter — fixed-point ×10000 scale.
|
|
4
|
+
*
|
|
5
|
+
* Algorithm:
|
|
6
|
+
* Input x: fixed-point integer (×10000), e.g. 10000 = 1.0
|
|
7
|
+
* Target: return floor(sqrt(x/10000) * 10000)
|
|
8
|
+
*
|
|
9
|
+
* 1. If x <= 0, return 0
|
|
10
|
+
* 2. Initial guess: g = x >> INIT_SHIFT (default: x/2)
|
|
11
|
+
* 3. Newton iteration: g = (g + x * 10000 / g) / 2, repeated N times
|
|
12
|
+
* Overflow-safe: x * 10000 / g → (x * 100) / (g / 100)
|
|
13
|
+
* 4. Return g
|
|
14
|
+
*
|
|
15
|
+
* Valid range: x ∈ [1, 1_000_000] (real range 0.0001 to 100.0)
|
|
16
|
+
* Larger x needs more iterations; beyond 1M, convergence is not guaranteed
|
|
17
|
+
* within N=12 iterations from an x/2 initial guess.
|
|
18
|
+
*
|
|
19
|
+
* Overflow analysis (x ≤ 1_000_000, SCALE = 10000):
|
|
20
|
+
* x * 100 ≤ 100_000_000 — fits int32 (max ~2.1B) ✓
|
|
21
|
+
* g / 100 is never 0 for g ≥ 100 (guard included) ✓
|
|
22
|
+
*
|
|
23
|
+
* Parameters:
|
|
24
|
+
* N: iteration count, range [4, 12], integer
|
|
25
|
+
* INIT_SHIFT: initial guess right shift, range [0, 3], integer (1 → x/2)
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.sqrtNewtonAdapter = exports.defaultParams = void 0;
|
|
29
|
+
const simulator_1 = require("../simulator");
|
|
30
|
+
const SCALE = 10000;
|
|
31
|
+
exports.defaultParams = {
|
|
32
|
+
N: 8,
|
|
33
|
+
INIT_SHIFT: 1,
|
|
34
|
+
};
|
|
35
|
+
exports.sqrtNewtonAdapter = {
|
|
36
|
+
name: 'sqrt-newton',
|
|
37
|
+
description: 'sqrt(x) Newton iteration using fixed-point int32 arithmetic (×10000 scale)',
|
|
38
|
+
params: [
|
|
39
|
+
{ name: 'N', range: [4, 12], integer: true },
|
|
40
|
+
{ name: 'INIT_SHIFT', range: [0, 3], integer: true },
|
|
41
|
+
],
|
|
42
|
+
simulate(input, params) {
|
|
43
|
+
const N = Math.round(params['N']);
|
|
44
|
+
const INIT_SHIFT = Math.round(params['INIT_SHIFT']);
|
|
45
|
+
let x = (0, simulator_1.i32)(input);
|
|
46
|
+
if (x <= 0)
|
|
47
|
+
return 0;
|
|
48
|
+
// Initial guess: x >> INIT_SHIFT
|
|
49
|
+
let g = (0, simulator_1.i32)(x >> INIT_SHIFT);
|
|
50
|
+
if (g <= 0)
|
|
51
|
+
g = 1; // guard against zero/negative guess
|
|
52
|
+
// Newton iterations: g = (g + x * 10000 / g) / 2
|
|
53
|
+
// Overflow-safe for x ≤ 1_000_000: split x*10000/g as (x*100)/(g/100)
|
|
54
|
+
for (let iter = 0; iter < N; iter++) {
|
|
55
|
+
const gDiv = (0, simulator_1.i32)(g / 100);
|
|
56
|
+
if (gDiv <= 0)
|
|
57
|
+
break; // guard: g is too small, stop
|
|
58
|
+
const xdivg = (0, simulator_1.i32)((0, simulator_1.i32)(x * 100) / gDiv); // ≈ x * 10000 / g
|
|
59
|
+
g = (0, simulator_1.i32)((0, simulator_1.i32)(g + xdivg) / 2);
|
|
60
|
+
if (g <= 0) {
|
|
61
|
+
g = 1;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return g;
|
|
66
|
+
},
|
|
67
|
+
reference(input) {
|
|
68
|
+
if (input <= 0)
|
|
69
|
+
return 0;
|
|
70
|
+
return Math.floor(Math.sqrt(input / SCALE) * SCALE);
|
|
71
|
+
},
|
|
72
|
+
sampleInputs() {
|
|
73
|
+
const inputs = [];
|
|
74
|
+
// Logarithmic sampling from 0.01 to 100.0 (×10000: 100 to 1_000_000)
|
|
75
|
+
// Lower bound 100 avoids g/100=0 in overflow-safe Newton step.
|
|
76
|
+
const logMin = Math.log10(100);
|
|
77
|
+
const logMax = Math.log10(1_000_000);
|
|
78
|
+
const steps = 200;
|
|
79
|
+
for (let i = 0; i <= steps; i++) {
|
|
80
|
+
const v = logMin + (i / steps) * (logMax - logMin);
|
|
81
|
+
const x = Math.round(Math.pow(10, v));
|
|
82
|
+
inputs.push(x);
|
|
83
|
+
}
|
|
84
|
+
// Exact perfect squares (k=1..10 → x up to 100 * 10000 = 1_000_000)
|
|
85
|
+
for (let k = 1; k <= 10; k++) {
|
|
86
|
+
inputs.push(k * k * SCALE); // sqrt should be exactly k * SCALE
|
|
87
|
+
}
|
|
88
|
+
return inputs;
|
|
89
|
+
},
|
|
90
|
+
generateCode(params, meta) {
|
|
91
|
+
const N = Math.round(params['N']);
|
|
92
|
+
const INIT_SHIFT = Math.round(params['INIT_SHIFT']);
|
|
93
|
+
const iters = Array.from({ length: N }, (_, i) => ` g = (g + x * 100 / (g / 100)) / 2; // iteration ${i + 1}`).join('\n');
|
|
94
|
+
return `// AUTO-GENERATED by redscript tune — DO NOT EDIT
|
|
95
|
+
// Adapter: sqrt-newton | Date: ${meta.tuneDate}
|
|
96
|
+
// max_error: ${meta.maxError.toFixed(6)} | mae: ${meta.mae.toFixed(6)} | estimated_cmds: ${meta.estimatedCmds}
|
|
97
|
+
// Run \`redscript tune --adapter sqrt-newton\` to regenerate
|
|
98
|
+
//
|
|
99
|
+
// Parameters:
|
|
100
|
+
// N = ${N} (Newton iteration count)
|
|
101
|
+
// INIT_SHIFT = ${INIT_SHIFT} (initial guess = x >> ${INIT_SHIFT}, i.e. x / ${1 << INIT_SHIFT})
|
|
102
|
+
|
|
103
|
+
fn sqrt_fx(x: int): int {
|
|
104
|
+
// Input x: fixed-point ×10000, returns floor(sqrt(x/10000)*10000)
|
|
105
|
+
// Valid range: x ∈ [0, 1000000] (real range 0.0 to 100.0)
|
|
106
|
+
// max_error: ${meta.maxError.toFixed(6)} (in ×10000 units)
|
|
107
|
+
//
|
|
108
|
+
// Overflow-safe: x*10000/g computed as (x*100)/(g/100), valid for x ≤ 1_000_000
|
|
109
|
+
|
|
110
|
+
if (x <= 0) { return 0; }
|
|
111
|
+
|
|
112
|
+
// Initial guess: x / ${1 << INIT_SHIFT}
|
|
113
|
+
let g: int = x / ${1 << INIT_SHIFT};
|
|
114
|
+
if (g <= 0) { g = 1; }
|
|
115
|
+
|
|
116
|
+
// Newton iterations: g = (g + x * 10000 / g) / 2
|
|
117
|
+
// Split to avoid overflow: (x * 100) / (g / 100) ≈ x * 10000 / g
|
|
118
|
+
${iters}
|
|
119
|
+
|
|
120
|
+
return g;
|
|
121
|
+
}
|
|
122
|
+
`;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=sqrt-newton.js.map
|