bireactive 0.3.1 → 0.3.2
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 +14 -7
- package/dist/core/_counts.js +5 -12
- package/dist/core/cell.d.ts +3 -3
- package/dist/core/cell.js +6 -7
- package/dist/core/derived-geometry.js +4 -7
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +3 -1
- package/dist/core/lenses/aggregates.d.ts +42 -52
- package/dist/core/lenses/aggregates.js +225 -116
- package/dist/core/lenses/geometry.d.ts +22 -4
- package/dist/core/lenses/geometry.js +59 -27
- package/dist/core/lenses/index.d.ts +5 -6
- package/dist/core/lenses/index.js +5 -6
- package/dist/core/lenses/memory.js +4 -17
- package/dist/core/lenses/numerical.d.ts +100 -0
- package/dist/core/lenses/{typed-factor.js → numerical.js} +136 -34
- package/dist/core/lenses/point-cloud.d.ts +67 -0
- package/dist/core/lenses/{closed-form-policies.js → point-cloud.js} +218 -81
- package/dist/core/lenses/snap.d.ts +1 -1
- package/dist/core/lenses/snap.js +3 -10
- package/dist/core/lenses/text.d.ts +40 -0
- package/dist/core/lenses/text.js +202 -0
- package/dist/core/lifecycle.js +3 -6
- package/dist/core/linalg.js +5 -11
- package/dist/core/optic.js +10 -15
- package/dist/core/optics.js +4 -8
- package/dist/core/store.d.ts +1 -2
- package/dist/core/store.js +7 -15
- package/dist/core/traits.d.ts +4 -7
- package/dist/core/traits.js +8 -12
- package/dist/core/values/anchor.js +0 -4
- package/dist/core/values/arr.d.ts +110 -0
- package/dist/core/values/arr.js +336 -0
- package/dist/core/values/audio.d.ts +8 -9
- package/dist/core/values/audio.js +7 -23
- package/dist/core/values/bool.d.ts +11 -11
- package/dist/core/values/bool.js +12 -22
- package/dist/core/values/box.d.ts +15 -20
- package/dist/core/values/box.js +20 -33
- package/dist/core/values/canvas.d.ts +18 -25
- package/dist/core/values/canvas.js +17 -48
- package/dist/core/values/color.d.ts +5 -7
- package/dist/core/values/color.js +5 -11
- package/dist/core/values/field.d.ts +6 -7
- package/dist/core/values/field.js +10 -35
- package/dist/core/values/flags.d.ts +1 -2
- package/dist/core/values/flags.js +1 -17
- package/dist/core/values/gpu.d.ts +6 -10
- package/dist/core/values/gpu.js +8 -22
- package/dist/core/values/matrix.d.ts +2 -4
- package/dist/core/values/matrix.js +2 -12
- package/dist/core/values/num.d.ts +19 -28
- package/dist/core/values/num.js +23 -41
- package/dist/core/values/pose.d.ts +2 -4
- package/dist/core/values/pose.js +3 -12
- package/dist/core/values/range.d.ts +18 -26
- package/dist/core/values/range.js +22 -39
- package/dist/core/values/reg/ambiguity.d.ts +8 -0
- package/dist/core/values/reg/ambiguity.js +131 -0
- package/dist/core/values/reg/engine.d.ts +91 -0
- package/dist/core/values/reg/engine.js +373 -0
- package/dist/core/values/reg/nfa.d.ts +42 -0
- package/dist/core/values/reg/nfa.js +391 -0
- package/dist/core/values/reg/regex.d.ts +7 -0
- package/dist/core/values/reg/regex.js +318 -0
- package/dist/core/values/reg/types.d.ts +60 -0
- package/dist/core/values/reg/types.js +3 -0
- package/dist/core/values/reg.d.ts +250 -0
- package/dist/core/values/reg.js +649 -0
- package/dist/core/values/str.d.ts +16 -60
- package/dist/core/values/str.js +133 -315
- package/dist/core/values/template.js +1 -24
- package/dist/core/values/transform.d.ts +3 -5
- package/dist/core/values/transform.js +3 -12
- package/dist/core/values/tri.d.ts +9 -10
- package/dist/core/values/tri.js +9 -15
- package/dist/core/values/vec.d.ts +9 -24
- package/dist/core/values/vec.js +9 -64
- package/dist/index.d.ts +0 -11
- package/dist/index.js +1 -11
- package/package.json +6 -7
- package/dist/coll.d.ts +0 -74
- package/dist/coll.js +0 -210
- package/dist/core/lenses/closed-form-policies.d.ts +0 -57
- package/dist/core/lenses/decompositions.d.ts +0 -14
- package/dist/core/lenses/decompositions.js +0 -224
- package/dist/core/lenses/domain-aggregates.d.ts +0 -42
- package/dist/core/lenses/domain-aggregates.js +0 -245
- package/dist/core/lenses/typed-factor.d.ts +0 -40
|
@@ -1,126 +1,235 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
// Trait-driven aggregate lenses: `mean`, `mix`, `spread`, etc. infer the value
|
|
2
|
+
// class from the first input, so they work for any class with the right traits
|
|
3
|
+
// (Vec, Color, Pose, Range, Box, …). All closed-form and cross-channel
|
|
4
|
+
// invariant by construction.
|
|
5
|
+
import { Num, reader, SKIP, } from "../index.js";
|
|
6
|
+
import { remember } from "./memory.js";
|
|
7
|
+
/** Equal-weight mean (writable of `inputs[0]`'s class); writes distribute
|
|
8
|
+
* the delta evenly. Class inferred from the first input; needs `linear`. */
|
|
9
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape
|
|
10
|
+
export function mean(inputs) {
|
|
11
|
+
if (inputs.length === 0)
|
|
12
|
+
throw new Error("mean: need ≥ 1 input");
|
|
13
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic class lookup
|
|
14
|
+
const Cls = inputs[0].constructor;
|
|
15
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic trait lookup
|
|
16
|
+
const lin = Cls.traits?.linear;
|
|
17
|
+
if (!lin)
|
|
18
|
+
throw new Error(`mean: ${Cls.name ?? "?"} has no traits.linear`);
|
|
19
|
+
const n = inputs.length;
|
|
20
|
+
const inv = 1 / n;
|
|
21
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape on Cls.lens
|
|
22
|
+
return Cls.lens(inputs,
|
|
23
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape
|
|
24
|
+
(vals) => {
|
|
25
|
+
let acc = vals[0];
|
|
26
|
+
for (let i = 1; i < n; i++)
|
|
27
|
+
acc = lin.add(acc, vals[i]);
|
|
28
|
+
return lin.scale(acc, inv);
|
|
29
|
+
},
|
|
30
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape
|
|
31
|
+
(target, vals) => {
|
|
32
|
+
let cur = vals[0];
|
|
33
|
+
for (let i = 1; i < n; i++)
|
|
34
|
+
cur = lin.add(cur, vals[i]);
|
|
35
|
+
cur = lin.scale(cur, inv);
|
|
36
|
+
const delta = lin.sub(target, cur);
|
|
37
|
+
const out = new Array(n);
|
|
38
|
+
for (let i = 0; i < n; i++)
|
|
39
|
+
out[i] = lin.add(vals[i], delta);
|
|
40
|
+
return out;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Weighted blend of K branches over any `Linear` type: reads the normalized
|
|
44
|
+
* weighted sum `Σ wᵢ·aᵢ`, writes the minimum-norm delta back into the
|
|
45
|
+
* branches (zero-weight branches untouched). Weights are read-only reactive
|
|
46
|
+
* controls — a one-hot is `select`, a `(1−t, t)` edge is `crossfade`. */
|
|
47
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape
|
|
48
|
+
export function mix(weights, branches) {
|
|
49
|
+
const K = branches.length;
|
|
50
|
+
if (K < 1)
|
|
51
|
+
throw new Error("mix: need ≥ 1 branch");
|
|
52
|
+
if (weights.length !== K)
|
|
53
|
+
throw new Error("mix: weights/branches length mismatch");
|
|
54
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic class lookup
|
|
55
|
+
const Cls = branches[0].constructor;
|
|
56
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic trait lookup
|
|
57
|
+
const lin = Cls.traits?.linear;
|
|
58
|
+
if (!lin)
|
|
59
|
+
throw new Error(`mix: ${Cls.name ?? "?"} has no traits.linear`);
|
|
60
|
+
const wf = weights.map(w => reader(w));
|
|
61
|
+
// Normalized weights + Σw². Degenerate (all-zero) weights fall back to
|
|
62
|
+
// uniform so the read stays defined.
|
|
63
|
+
const readW = () => {
|
|
64
|
+
const raw = wf.map(f => f());
|
|
65
|
+
let sum = 0;
|
|
66
|
+
for (const x of raw)
|
|
67
|
+
sum += x;
|
|
68
|
+
const w = Math.abs(sum) > 1e-12 ? raw.map(x => x / sum) : raw.map(() => 1 / K);
|
|
69
|
+
let sumSq = 0;
|
|
70
|
+
for (const x of w)
|
|
71
|
+
sumSq += x * x;
|
|
72
|
+
return { w, sumSq };
|
|
73
|
+
};
|
|
74
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape on Cls.lens
|
|
75
|
+
const combine = (vals, w) => {
|
|
76
|
+
let acc = lin.scale(vals[0], w[0]);
|
|
77
|
+
for (let i = 1; i < K; i++)
|
|
78
|
+
acc = lin.add(acc, lin.scale(vals[i], w[i]));
|
|
79
|
+
return acc;
|
|
23
80
|
};
|
|
81
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape on Cls.lens
|
|
82
|
+
return Cls.lens(branches,
|
|
83
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape
|
|
84
|
+
(vals) => combine(vals, readW().w),
|
|
85
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape
|
|
86
|
+
(target, vals) => {
|
|
87
|
+
const { w, sumSq } = readW();
|
|
88
|
+
const delta = lin.sub(target, combine(vals, w));
|
|
89
|
+
if (sumSq < 1e-12)
|
|
90
|
+
return vals.map(() => SKIP);
|
|
91
|
+
const inv = 1 / sumSq;
|
|
92
|
+
return vals.map((v, i) => w[i] === 0 ? SKIP : lin.add(v, lin.scale(delta, w[i] * inv)));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Two-branch router (mix simplex *vertex*): reads the live branch, writes
|
|
96
|
+
* flow entirely to it, the other is left put. Flipping `cond` snaps the
|
|
97
|
+
* output to the other branch's stored value. */
|
|
98
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape
|
|
99
|
+
export function select(cond, whenFalse, whenTrue) {
|
|
100
|
+
return mix([Num.derive(() => (cond.value ? 0 : 1)), Num.derive(() => (cond.value ? 1 : 0))], [whenFalse, whenTrue]);
|
|
101
|
+
}
|
|
102
|
+
/** Two-branch crossfade (mix simplex *edge*): `lerp(a, b, t)`. Writing
|
|
103
|
+
* keeps `t` fixed and splits the delta by influence. */
|
|
104
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape
|
|
105
|
+
export function crossfade(t, a, b) {
|
|
106
|
+
return mix([Num.derive(() => 1 - t.value), Num.derive(() => t.value)], [a, b]);
|
|
24
107
|
}
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
throw new Error("
|
|
108
|
+
/** Mean distance from the centroid (needs `Linear` + `Metric`); write scales
|
|
109
|
+
* the cluster's deviations so the new mean matches. The complement carries
|
|
110
|
+
* normalized deviations, so a collapse (spread → 0) reinflates the shape. */
|
|
111
|
+
export function spread(inputs) {
|
|
112
|
+
const K = inputs.length;
|
|
113
|
+
if (K < 1)
|
|
114
|
+
throw new Error("spread: need ≥ 1 input");
|
|
115
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic class lookup
|
|
116
|
+
const Cls = inputs[0].constructor;
|
|
117
|
+
const lin = Cls.traits?.linear;
|
|
118
|
+
const met = Cls.traits?.metric;
|
|
119
|
+
if (!lin || !met) {
|
|
120
|
+
throw new Error(`spread: ${Cls.name ?? "?"} needs Linear + Metric`);
|
|
32
121
|
}
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
122
|
+
const inv = 1 / K;
|
|
123
|
+
const centroid = (vals) => {
|
|
124
|
+
let acc = vals[0];
|
|
125
|
+
for (let i = 1; i < K; i++)
|
|
126
|
+
acc = lin.add(acc, vals[i]);
|
|
127
|
+
return lin.scale(acc, inv);
|
|
128
|
+
};
|
|
129
|
+
const meanSpread = (vals, ctr) => {
|
|
130
|
+
let total = 0;
|
|
131
|
+
for (let i = 0; i < K; i++)
|
|
132
|
+
total += met(vals[i], ctr);
|
|
133
|
+
return total * inv;
|
|
134
|
+
};
|
|
135
|
+
// Mean metric-distance from the centroid is a magnitude `remember`:
|
|
136
|
+
// writing it scales the cluster's deviations about the centroid, and a
|
|
137
|
+
// collapse (spread → 0) reinflates the remembered shape.
|
|
138
|
+
return remember(inputs, {
|
|
139
|
+
anchor: (vals) => centroid(vals),
|
|
140
|
+
feature: (vals, ctr) => meanSpread(vals, ctr),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/** Mean/spread decomposition: K values → {mean, spread}, i.e. centroid +
|
|
144
|
+
* uniform scale about it. `mean` ∘ `spread`; works for any
|
|
145
|
+
* Linear + Metric class (palettes, point clouds, poses, …). */
|
|
146
|
+
export function meanSpread(colors) {
|
|
147
|
+
return {
|
|
148
|
+
mean: mean(colors),
|
|
149
|
+
spread: spread(colors),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** (a, b) → {mean: (a+b)/2, diff: a−b}. Square linear iso; each write is the
|
|
153
|
+
* inverse change of basis, so mean and diff are cross-channel invariant. */
|
|
154
|
+
export function meanDiff(a, b) {
|
|
155
|
+
const mean = Num.lens([a, b], vals => (vals[0] + vals[1]) / 2, (target, vals) => {
|
|
156
|
+
const d = vals[0] - vals[1];
|
|
157
|
+
return [target + d / 2, target - d / 2];
|
|
158
|
+
});
|
|
159
|
+
const diff = Num.lens([a, b], vals => vals[0] - vals[1], (target, vals) => {
|
|
160
|
+
const m = (vals[0] + vals[1]) / 2;
|
|
161
|
+
return [m + target / 2, m - target / 2];
|
|
162
|
+
});
|
|
163
|
+
return { mean, diff };
|
|
164
|
+
}
|
|
165
|
+
/** Mean of N nums, clamped to `[lo, hi]` on read and write (writes are
|
|
166
|
+
* clamped before the delta is distributed). */
|
|
167
|
+
export function clampedMean(parents, lo, hi) {
|
|
168
|
+
const n = parents.length;
|
|
169
|
+
const inv = 1 / n;
|
|
170
|
+
return Num.lens(parents, vals => {
|
|
171
|
+
let s = 0;
|
|
172
|
+
for (let i = 0; i < n; i++)
|
|
173
|
+
s += vals[i];
|
|
174
|
+
const m = s * inv;
|
|
175
|
+
return m < lo ? lo : m > hi ? hi : m;
|
|
176
|
+
}, (target, vals) => {
|
|
177
|
+
const clamped = target < lo ? lo : target > hi ? hi : target;
|
|
178
|
+
let s = 0;
|
|
179
|
+
for (let i = 0; i < n; i++)
|
|
180
|
+
s += vals[i];
|
|
181
|
+
const cur = s * inv;
|
|
182
|
+
const delta = clamped - cur;
|
|
183
|
+
const out = new Array(n);
|
|
50
184
|
for (let i = 0; i < n; i++)
|
|
51
|
-
|
|
52
|
-
const k = dy / denom;
|
|
53
|
-
for (let i = 0; i < n; i++) {
|
|
54
|
-
if (weights[i] === 0) {
|
|
55
|
-
out[i] = SKIP;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
out[i] = xs[i] + weights[i] * J[i] * k;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
185
|
+
out[i] = vals[i] + delta;
|
|
61
186
|
return out;
|
|
62
187
|
});
|
|
63
188
|
}
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
export function
|
|
68
|
-
|
|
69
|
-
|
|
189
|
+
/** Num values as (i, valueᵢ) samples → {mean, slope}. Writing `mean` shifts
|
|
190
|
+
* all values; writing `slope` (least-squares) tilts them about the mean.
|
|
191
|
+
* Each preserves the other's reading. */
|
|
192
|
+
export function timeSeries(values) {
|
|
193
|
+
const N = values.length;
|
|
194
|
+
if (N < 2)
|
|
195
|
+
throw new Error("timeSeries: need ≥ 2 values");
|
|
196
|
+
const mean = Num.lens(values, (vals) => {
|
|
197
|
+
let s = 0;
|
|
198
|
+
for (let i = 0; i < N; i++)
|
|
199
|
+
s += vals[i];
|
|
200
|
+
return s / N;
|
|
201
|
+
}, (target, vals) => {
|
|
202
|
+
let s = 0;
|
|
203
|
+
for (let i = 0; i < N; i++)
|
|
204
|
+
s += vals[i];
|
|
205
|
+
const cur = s / N;
|
|
206
|
+
const delta = target - cur;
|
|
207
|
+
return vals.map(v => v + delta);
|
|
208
|
+
});
|
|
209
|
+
// Least-squares slope = Σ (i − idxMean)(v − mean) / Σ (i − idxMean)²,
|
|
210
|
+
// idxMean = (N−1)/2 constant. Write tilts about the mean:
|
|
211
|
+
// value_i = mean + (i − idxMean)·s.
|
|
212
|
+
const idxMean = (N - 1) / 2;
|
|
213
|
+
let denomSlope = 0;
|
|
214
|
+
for (let i = 0; i < N; i++) {
|
|
215
|
+
const di = i - idxMean;
|
|
216
|
+
denomSlope += di * di;
|
|
70
217
|
}
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const saved = xs[i];
|
|
87
|
-
xs[i] = saved + eps;
|
|
88
|
-
const ye = forward(xs);
|
|
89
|
-
xs[i] = saved;
|
|
90
|
-
Jx[i] = (ye.x - y0.x) / eps;
|
|
91
|
-
Jy[i] = (ye.y - y0.y) / eps;
|
|
92
|
-
}
|
|
93
|
-
// J·W·Jᵀ is the 2×2 [a b; b c]. Add damping to the diagonal, invert.
|
|
94
|
-
let a = damping;
|
|
95
|
-
let b = 0;
|
|
96
|
-
let c = damping;
|
|
97
|
-
for (let i = 0; i < n; i++) {
|
|
98
|
-
const w = weights[i];
|
|
99
|
-
a += w * Jx[i] * Jx[i];
|
|
100
|
-
b += w * Jx[i] * Jy[i];
|
|
101
|
-
c += w * Jy[i] * Jy[i];
|
|
102
|
-
}
|
|
103
|
-
const det = a * c - b * b;
|
|
104
|
-
if (Math.abs(det) < 1e-14) {
|
|
105
|
-
// Singular; leave inputs unchanged.
|
|
106
|
-
for (let i = 0; i < n; i++)
|
|
107
|
-
out[i] = SKIP;
|
|
108
|
-
return out;
|
|
109
|
-
}
|
|
110
|
-
const invA = c / det;
|
|
111
|
-
const invB = -b / det;
|
|
112
|
-
const invC = a / det;
|
|
113
|
-
const kx = invA * dx + invB * dy;
|
|
114
|
-
const ky = invB * dx + invC * dy;
|
|
115
|
-
for (let i = 0; i < n; i++) {
|
|
116
|
-
const w = weights[i];
|
|
117
|
-
if (w === 0) {
|
|
118
|
-
out[i] = SKIP;
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
out[i] = xs[i] + w * (Jx[i] * kx + Jy[i] * ky);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return out;
|
|
218
|
+
const slope = Num.lens(values, (vals) => {
|
|
219
|
+
let valMean = 0;
|
|
220
|
+
for (let i = 0; i < N; i++)
|
|
221
|
+
valMean += vals[i];
|
|
222
|
+
valMean /= N;
|
|
223
|
+
let num = 0;
|
|
224
|
+
for (let i = 0; i < N; i++)
|
|
225
|
+
num += (i - idxMean) * (vals[i] - valMean);
|
|
226
|
+
return num / denomSlope;
|
|
227
|
+
}, (target, vals) => {
|
|
228
|
+
let valMean = 0;
|
|
229
|
+
for (let i = 0; i < N; i++)
|
|
230
|
+
valMean += vals[i];
|
|
231
|
+
valMean /= N;
|
|
232
|
+
return vals.map((_, i) => valMean + (i - idxMean) * target);
|
|
125
233
|
});
|
|
234
|
+
return { mean, slope };
|
|
126
235
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Cell, type Read, type Writable } from "../cell.js";
|
|
1
|
+
import { type Cell, type Init, type Read, type Writable } from "../cell.js";
|
|
2
2
|
import { Num } from "../values/num.js";
|
|
3
3
|
import { Vec } from "../values/vec.js";
|
|
4
4
|
type V = {
|
|
@@ -24,7 +24,25 @@ export declare function pulleySum(a: Num, b: Num): Writable<Num>;
|
|
|
24
24
|
/** Difference of two nums: `a - b`. Writing the difference shifts
|
|
25
25
|
* both inputs symmetrically by ±half-delta. */
|
|
26
26
|
export declare function diff(a: Num, b: Num): Writable<Num>;
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
|
|
27
|
+
/** Policy for `polar`'s inverse — which inputs absorb a write:
|
|
28
|
+
*
|
|
29
|
+
* - `rotate` — c fixed; write r and a to land on target.
|
|
30
|
+
* - `translate` — r and a fixed; shift c by Δ.
|
|
31
|
+
* - `radial` — c and a fixed; project the drag onto the ray.
|
|
32
|
+
* - `circular` — c and r fixed; project the drag onto the circle. */
|
|
33
|
+
export type PolarPolicy = "rotate" | "translate" | "radial" | "circular";
|
|
34
|
+
/** Vec at a polar offset from `center`: `center + (r·cos a, r·sin a)`. Each
|
|
35
|
+
* input is a literal (new cell) or existing writable (passed through); for
|
|
36
|
+
* read-only sources use `Vec.derive`. `policy` selects which inputs absorb
|
|
37
|
+
* writes; lock one with `Num.pin`: `polar(c, Num.pin(100), a)`. */
|
|
38
|
+
export declare function polar(center: Init<Vec>, r: Init<Num>, a: Init<Num>, policy?: PolarPolicy): Writable<Vec>;
|
|
39
|
+
/** Cubic Bezier (p0..p3) → {start, end, startTangent, endTangent}. Moving an
|
|
40
|
+
* endpoint carries its control point (tangent preserved); writing a tangent
|
|
41
|
+
* moves only its control point. Square linear iso — all pairs invariant. */
|
|
42
|
+
export declare function bezierGestalt(p0: Writable<Vec>, p1: Writable<Vec>, p2: Writable<Vec>, p3: Writable<Vec>): {
|
|
43
|
+
start: Writable<Vec>;
|
|
44
|
+
end: Writable<Vec>;
|
|
45
|
+
startTangent: Writable<Vec>;
|
|
46
|
+
endTangent: Writable<Vec>;
|
|
47
|
+
};
|
|
30
48
|
export {};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// geometry.ts — geometric lens building blocks over the N-input
|
|
2
|
-
// `Cls.lens` / `Cls.derive` forms. All are a few lines on top of the engine.
|
|
3
1
|
import { SKIP } from "../cell.js";
|
|
4
|
-
import { Num } from "../values/num.js";
|
|
5
|
-
import { Vec } from "../values/vec.js";
|
|
6
|
-
import { rotateAbout } from "./closed-form-policies.js";
|
|
2
|
+
import { Num, num } from "../values/num.js";
|
|
3
|
+
import { nearestAngle, Vec, vec } from "../values/vec.js";
|
|
7
4
|
import { remember } from "./memory.js";
|
|
5
|
+
import { rotateAbout } from "./point-cloud.js";
|
|
8
6
|
/** Distance between two Vecs; writing scales them symmetrically about
|
|
9
7
|
* their midpoint (collapse to 0 reinflates the last direction). */
|
|
10
8
|
export function distance(a, b) {
|
|
@@ -72,27 +70,61 @@ export function diff(a, b) {
|
|
|
72
70
|
return [av + delta / 2, bv - delta / 2];
|
|
73
71
|
});
|
|
74
72
|
}
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
73
|
+
/** Vec at a polar offset from `center`: `center + (r·cos a, r·sin a)`. Each
|
|
74
|
+
* input is a literal (new cell) or existing writable (passed through); for
|
|
75
|
+
* read-only sources use `Vec.derive`. `policy` selects which inputs absorb
|
|
76
|
+
* writes; lock one with `Num.pin`: `polar(c, Num.pin(100), a)`. */
|
|
77
|
+
export function polar(center, r, a, policy = "rotate") {
|
|
78
|
+
const cSig = center instanceof Vec ? center : vec(center.x, center.y);
|
|
79
|
+
const rSig = num(r);
|
|
80
|
+
const aSig = num(a);
|
|
81
|
+
const project = (c, rv, av) => ({
|
|
82
|
+
x: c.x + rv * Math.cos(av),
|
|
83
|
+
y: c.y + rv * Math.sin(av),
|
|
84
|
+
});
|
|
85
|
+
let bwd;
|
|
86
|
+
switch (policy) {
|
|
87
|
+
case "rotate":
|
|
88
|
+
bwd = (p, [cv, , av]) => {
|
|
89
|
+
const dx = p.x - cv.x;
|
|
90
|
+
const dy = p.y - cv.y;
|
|
91
|
+
return [SKIP, Math.hypot(dx, dy), nearestAngle(Math.atan2(dy, dx), av)];
|
|
92
|
+
};
|
|
93
|
+
break;
|
|
94
|
+
case "translate":
|
|
95
|
+
bwd = (p, [cv, rv, av]) => {
|
|
96
|
+
const f = project(cv, rv, av);
|
|
97
|
+
return [{ x: cv.x + (p.x - f.x), y: cv.y + (p.y - f.y) }, SKIP, SKIP];
|
|
98
|
+
};
|
|
99
|
+
break;
|
|
100
|
+
case "radial":
|
|
101
|
+
bwd = (p, [cv, , av]) => {
|
|
102
|
+
const dx = p.x - cv.x;
|
|
103
|
+
const dy = p.y - cv.y;
|
|
104
|
+
return [SKIP, dx * Math.cos(av) + dy * Math.sin(av), SKIP];
|
|
105
|
+
};
|
|
106
|
+
break;
|
|
107
|
+
case "circular":
|
|
108
|
+
bwd = (p, [cv, , av]) => [SKIP, SKIP, nearestAngle(Math.atan2(p.y - cv.y, p.x - cv.x), av)];
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return Vec.lens([cSig, rSig, aSig], ([c, rv, av]) => project(c, rv, av), bwd);
|
|
112
|
+
}
|
|
113
|
+
/** Cubic Bezier (p0..p3) → {start, end, startTangent, endTangent}. Moving an
|
|
114
|
+
* endpoint carries its control point (tangent preserved); writing a tangent
|
|
115
|
+
* moves only its control point. Square linear iso — all pairs invariant. */
|
|
116
|
+
export function bezierGestalt(p0, p1, p2, p3) {
|
|
117
|
+
const start = Vec.lens([p0, p1], (vals) => vals[0], (target, vals) => {
|
|
118
|
+
const dx = target.x - vals[0].x;
|
|
119
|
+
const dy = target.y - vals[0].y;
|
|
120
|
+
return [target, { x: vals[1].x + dx, y: vals[1].y + dy }];
|
|
121
|
+
});
|
|
122
|
+
const end = Vec.lens([p2, p3], (vals) => vals[1], (target, vals) => {
|
|
123
|
+
const dx = target.x - vals[1].x;
|
|
124
|
+
const dy = target.y - vals[1].y;
|
|
125
|
+
return [{ x: vals[0].x + dx, y: vals[0].y + dy }, target];
|
|
97
126
|
});
|
|
127
|
+
const startTangent = Vec.lens([p0, p1], (vals) => ({ x: vals[1].x - vals[0].x, y: vals[1].y - vals[0].y }), (target, vals) => [SKIP, { x: vals[0].x + target.x, y: vals[0].y + target.y }]);
|
|
128
|
+
const endTangent = Vec.lens([p2, p3], (vals) => ({ x: vals[1].x - vals[0].x, y: vals[1].y - vals[0].y }), (target, vals) => [{ x: vals[1].x - target.x, y: vals[1].y - target.y }, SKIP]);
|
|
129
|
+
return { start, end, startTangent, endTangent };
|
|
98
130
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { bbox, meanDiff, procrustes } from "./decompositions.js";
|
|
4
|
-
export { bezierGestalt, crossfade, mean, meanSpread, mix, select, spread, timeSeries, } from "./domain-aggregates.js";
|
|
5
|
-
export { angle, clampedMean, diff, distance, pulleySum, reflection, vecLerp } from "./geometry.js";
|
|
1
|
+
export { clampedMean, crossfade, mean, meanDiff, meanSpread, mix, select, spread, timeSeries, } from "./aggregates.js";
|
|
2
|
+
export { angle, bezierGestalt, diff, distance, type PolarPolicy, polar, pulleySum, reflection, vecLerp, } from "./geometry.js";
|
|
6
3
|
export { type ContinuousOpts, continuous, type RememberOpts, remember, } from "./memory.js";
|
|
4
|
+
export { type ArgminOpts, type ArgminVecOpts, argminNum, argminVec, bundle, clampToDisc, type FactorOpts, type FactorResult, factor, factorTuple, type OutputSpec, type PackedInput, } from "./numerical.js";
|
|
5
|
+
export { bbox, bestFitCircle, bestFitLine, pca, procrustes, rotateAbout, scaleAbout, scaleAboutXY, total, } from "./point-cloud.js";
|
|
7
6
|
export { type ClosestOpts, hullWeights, nearestIndex } from "./snap.js";
|
|
8
|
-
export {
|
|
7
|
+
export { applyCaseMask, applyCasePattern, caseFold, caseMaskOf, parseWords, rebuildWords, } from "./text.js";
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { bbox, meanDiff, procrustes } from "./decompositions.js";
|
|
4
|
-
export { bezierGestalt, crossfade, mean, meanSpread, mix, select, spread, timeSeries, } from "./domain-aggregates.js";
|
|
5
|
-
export { angle, clampedMean, diff, distance, pulleySum, reflection, vecLerp } from "./geometry.js";
|
|
1
|
+
export { clampedMean, crossfade, mean, meanDiff, meanSpread, mix, select, spread, timeSeries, } from "./aggregates.js";
|
|
2
|
+
export { angle, bezierGestalt, diff, distance, polar, pulleySum, reflection, vecLerp, } from "./geometry.js";
|
|
6
3
|
export { continuous, remember, } from "./memory.js";
|
|
4
|
+
export { argminNum, argminVec, bundle, clampToDisc, factor, factorTuple, } from "./numerical.js";
|
|
5
|
+
export { bbox, bestFitCircle, bestFitLine, pca, procrustes, rotateAbout, scaleAbout, scaleAboutXY, total, } from "./point-cloud.js";
|
|
7
6
|
export { hullWeights, nearestIndex } from "./snap.js";
|
|
8
|
-
export {
|
|
7
|
+
export { applyCaseMask, applyCasePattern, caseFold, caseMaskOf, parseWords, rebuildWords, } from "./text.js";
|
|
@@ -1,20 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// across the closed-form-policy and aggregate lenses, each with its own
|
|
6
|
-
// degeneracy threshold. They factor into two combinators:
|
|
7
|
-
//
|
|
8
|
-
// remember — a magnitude/total view of a cluster: sources live at
|
|
9
|
-
// `anchorᵢ + shapeᵢ · scale`, `scale` is the writable
|
|
10
|
-
// scalar. The complement holds the normalized shape so a
|
|
11
|
-
// collapse to the anchor (scale → 0) reinflates it. Covers
|
|
12
|
-
// scaleAbout, bestFitCircle.radius, spreadOf, totalLens.
|
|
13
|
-
// continuous — a cyclic (mod-`period`) view lifted to its universal
|
|
14
|
-
// cover: the complement tracks the last emitted value and
|
|
15
|
-
// unwraps the raw reading to the nearest sheet, so the
|
|
16
|
-
// view never tears at a branch cut. Covers the eigenvector
|
|
17
|
-
// angle of bestFitLine; the primitive behind winding.
|
|
1
|
+
// Stateful-complement lens combinators: the backward pass depends on where
|
|
2
|
+
// the value has been, not just the current source. `remember` is a
|
|
3
|
+
// magnitude/total view with shape-memory; `continuous` is a winding-aware
|
|
4
|
+
// view of a cyclic reading.
|
|
18
5
|
import { Num, SKIP } from "../index.js";
|
|
19
6
|
/** Scalar shape-memory lens. Reads `feature(sources)`; writing it scales
|
|
20
7
|
* the cluster about `anchor` so the new feature matches, reinflating the
|