bireactive 0.2.4 → 0.3.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/dist/animation/anim.js +4 -0
- package/dist/automerge/doc-cell.d.ts +20 -0
- package/dist/automerge/doc-cell.js +80 -0
- package/dist/automerge/index.d.ts +3 -0
- package/dist/automerge/index.js +12 -0
- package/dist/automerge/reconcile.d.ts +5 -0
- package/dist/automerge/reconcile.js +63 -0
- package/dist/coll.d.ts +7 -7
- package/dist/core/_counts.d.ts +48 -0
- package/dist/core/_counts.js +58 -0
- package/dist/core/cell.d.ts +182 -123
- package/dist/core/cell.js +1140 -721
- package/dist/core/debug.d.ts +25 -0
- package/dist/core/debug.js +121 -0
- package/dist/core/index.d.ts +9 -14
- package/dist/core/index.js +9 -14
- package/dist/core/lenses/aggregates.d.ts +1 -1
- package/dist/core/lenses/aggregates.js +4 -3
- package/dist/core/lenses/closed-form-policies.js +14 -9
- package/dist/core/lenses/decompositions.js +3 -3
- package/dist/core/lenses/domain-aggregates.js +5 -5
- package/dist/core/lenses/geometry.d.ts +1 -1
- package/dist/core/lenses/geometry.js +6 -7
- package/dist/core/lenses/index.d.ts +1 -0
- package/dist/core/lenses/index.js +1 -0
- package/dist/core/lenses/memory.d.ts +2 -2
- package/dist/core/lenses/memory.js +3 -3
- package/dist/core/lenses/snap.d.ts +18 -0
- package/dist/core/lenses/snap.js +145 -0
- package/dist/core/lenses/typed-factor.js +4 -3
- package/dist/core/optic.d.ts +13 -0
- package/dist/core/optic.js +44 -0
- package/dist/core/optics.d.ts +10 -0
- package/dist/core/optics.js +30 -0
- package/dist/core/store.d.ts +10 -0
- package/dist/core/store.js +85 -0
- package/dist/core/traits.d.ts +1 -0
- package/dist/core/values/audio.js +4 -5
- package/dist/core/values/box.js +7 -7
- package/dist/core/values/canvas.js +15 -18
- package/dist/core/values/color.js +5 -5
- package/dist/core/values/field.d.ts +70 -0
- package/dist/core/values/field.js +230 -0
- package/dist/core/values/gpu.d.ts +4 -2
- package/dist/core/values/gpu.js +11 -4
- package/dist/core/values/matrix.js +7 -7
- package/dist/core/values/num.d.ts +1 -1
- package/dist/core/values/num.js +1 -1
- package/dist/core/values/pose.js +4 -4
- package/dist/core/values/range.js +6 -6
- package/dist/core/values/str.js +8 -8
- package/dist/core/values/template.d.ts +1 -1
- package/dist/core/values/template.js +2 -1
- package/dist/core/values/transform.js +7 -7
- package/dist/core/values/tri.js +3 -3
- package/dist/core/values/vec.js +8 -12
- package/dist/ext/timeline.js +2 -2
- package/dist/formats/cst.d.ts +127 -0
- package/dist/formats/cst.js +280 -0
- package/dist/formats/edn.d.ts +2 -0
- package/dist/formats/edn.js +301 -0
- package/dist/formats/index.d.ts +6 -0
- package/dist/formats/index.js +8 -0
- package/dist/formats/json.d.ts +2 -0
- package/dist/formats/json.js +332 -0
- package/dist/formats/lens.d.ts +8 -0
- package/dist/formats/lens.js +51 -0
- package/dist/formats/toml.d.ts +2 -0
- package/dist/formats/toml.js +526 -0
- package/dist/formats/yaml.d.ts +2 -0
- package/dist/formats/yaml.js +661 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx-dev-runtime.js +5 -0
- package/dist/jsx-runtime.d.ts +54 -0
- package/dist/jsx-runtime.js +219 -0
- package/dist/learn/data.d.ts +49 -0
- package/dist/learn/data.js +181 -0
- package/dist/learn/index.d.ts +3 -0
- package/dist/learn/index.js +6 -0
- package/dist/learn/lens-net.d.ts +63 -0
- package/dist/learn/lens-net.js +219 -0
- package/dist/learn/mlp.d.ts +77 -0
- package/dist/learn/mlp.js +292 -0
- package/dist/propagators/csp.d.ts +13 -0
- package/dist/propagators/csp.js +52 -0
- package/dist/propagators/flex.d.ts +31 -0
- package/dist/propagators/flex.js +189 -0
- package/dist/propagators/graph.d.ts +73 -0
- package/dist/propagators/graph.js +543 -0
- package/dist/propagators/index.d.ts +8 -6
- package/dist/propagators/index.js +15 -6
- package/dist/propagators/lattice.d.ts +45 -0
- package/dist/propagators/lattice.js +113 -0
- package/dist/propagators/layout.d.ts +1 -27
- package/dist/propagators/layout.js +6 -175
- package/dist/propagators/numeric.d.ts +17 -0
- package/dist/propagators/numeric.js +93 -0
- package/dist/propagators/solver.d.ts +51 -0
- package/dist/propagators/solver.js +175 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/lens.d.ts +121 -0
- package/dist/schema/lens.js +429 -0
- package/dist/shapes/annular-sector.js +4 -4
- package/dist/shapes/button.js +1 -1
- package/dist/shapes/circle.js +1 -1
- package/dist/shapes/drag-behaviors.d.ts +56 -0
- package/dist/shapes/drag-behaviors.js +102 -0
- package/dist/shapes/drag-spec.d.ts +52 -0
- package/dist/shapes/drag-spec.js +112 -0
- package/dist/shapes/handle.js +2 -2
- package/dist/shapes/index.d.ts +3 -1
- package/dist/shapes/index.js +3 -1
- package/dist/shapes/interaction.d.ts +2 -3
- package/dist/shapes/interaction.js +77 -56
- package/dist/shapes/label.js +7 -1
- package/dist/shapes/layout.d.ts +47 -1
- package/dist/shapes/layout.js +60 -2
- package/dist/shapes/rect.js +7 -7
- package/dist/shapes/shape.js +8 -8
- package/dist/web/diagram.js +2 -2
- package/package.json +24 -2
- package/dist/propagators/network.d.ts +0 -52
- package/dist/propagators/network.js +0 -185
- package/dist/propagators/propagator.d.ts +0 -12
- package/dist/propagators/propagator.js +0 -16
- package/dist/propagators/range.d.ts +0 -45
- package/dist/propagators/range.js +0 -147
- package/dist/propagators/relations.d.ts +0 -60
- package/dist/propagators/relations.js +0 -343
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// lens-net.ts — the MLP of `mlp.ts`, but wired as an actual lens DAG so that
|
|
2
|
+
// *training is a backward write*.
|
|
3
|
+
//
|
|
4
|
+
// Each dense layer is a multi-parent stateful lens over `[paramsCell, inputCell]`:
|
|
5
|
+
// fwd computes the activation `act(W·x + b)`,
|
|
6
|
+
// bwd receives the cotangent dL/da, deposits an SGD step on the weight cell,
|
|
7
|
+
// and passes dL/dx up to the previous layer.
|
|
8
|
+
// Composing the layers composes their backward passes in reverse, so writing
|
|
9
|
+
// the output cotangent to the `logits` cell makes the engine run reverse-mode
|
|
10
|
+
// backprop down the whole chain and land an update on every weight source.
|
|
11
|
+
// There is no optimizer object and no training loop inside the net: the
|
|
12
|
+
// statement `logits.value = cotangent` *is* one gradient step.
|
|
13
|
+
//
|
|
14
|
+
// The very same lens, run with the weights frozen (`cfg.frozen`), inverts
|
|
15
|
+
// instead of fits — the cotangent still flows to the input, so the input cell
|
|
16
|
+
// receives dL/dx. That is the gradient the "dream" ascends to paint a class
|
|
17
|
+
// prototype: inference-time inversion is just the backward leg with the
|
|
18
|
+
// parameters held fixed.
|
|
19
|
+
//
|
|
20
|
+
// `mlp.ts` stays the flat, fast reference (and the offline ground truth); this
|
|
21
|
+
// is the reactive realization the demos drive.
|
|
22
|
+
import { cell, lens, SKIP } from "../core/index.js";
|
|
23
|
+
import { actGrad, applyAct, gaussian, rng, softmax } from "./mlp.js";
|
|
24
|
+
const toF64 = (x) => x instanceof Float64Array ? x : Float64Array.from(x);
|
|
25
|
+
// Forward of one dense layer (the lens `fwd`).
|
|
26
|
+
function denseForward(p, inDim, outDim, act, x) {
|
|
27
|
+
const y = new Float64Array(outDim);
|
|
28
|
+
for (let o = 0; o < outDim; o++) {
|
|
29
|
+
let z = p.b[o];
|
|
30
|
+
const base = o * inDim;
|
|
31
|
+
for (let i = 0; i < inDim; i++)
|
|
32
|
+
z += p.W[base + i] * x[i];
|
|
33
|
+
y[o] = applyAct(act, z);
|
|
34
|
+
}
|
|
35
|
+
return y;
|
|
36
|
+
}
|
|
37
|
+
// Backward of one dense layer (the lens `bwd` math): given dL/da, return the
|
|
38
|
+
// input cotangent dL/dx and the parameter gradients gW/gb. Shared by the
|
|
39
|
+
// engine-routed training step and the frozen inversion pass so they can't drift.
|
|
40
|
+
function denseBackward(p, inDim, outDim, act, x, dOut) {
|
|
41
|
+
const dIn = new Float64Array(inDim);
|
|
42
|
+
const gW = new Float64Array(outDim * inDim);
|
|
43
|
+
const gb = new Float64Array(outDim);
|
|
44
|
+
for (let o = 0; o < outDim; o++) {
|
|
45
|
+
let z = p.b[o];
|
|
46
|
+
const base = o * inDim;
|
|
47
|
+
for (let i = 0; i < inDim; i++)
|
|
48
|
+
z += p.W[base + i] * x[i];
|
|
49
|
+
const dz = dOut[o] * actGrad(act, applyAct(act, z));
|
|
50
|
+
gb[o] = dz;
|
|
51
|
+
for (let i = 0; i < inDim; i++) {
|
|
52
|
+
gW[base + i] = dz * x[i];
|
|
53
|
+
dIn[i] = dIn[i] + p.W[base + i] * dz;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { dIn, gW, gb };
|
|
57
|
+
}
|
|
58
|
+
// Build one dense layer-lens over its weight source and input cell. Forward is
|
|
59
|
+
// the activation; backward steps the weights (unless frozen) and always returns
|
|
60
|
+
// dL/dx for the input parent — for a hidden layer that propagates the gradient
|
|
61
|
+
// up the chain; for the first layer it lands on the input cell (where the
|
|
62
|
+
// inversion pass reads it).
|
|
63
|
+
function denseLens(params, input, inDim, outDim, act, cfg) {
|
|
64
|
+
const parents = [params, input];
|
|
65
|
+
return lens(parents, {
|
|
66
|
+
init: () => null,
|
|
67
|
+
step: (_s, c) => c,
|
|
68
|
+
fwd: (s) => denseForward(s[0], inDim, outDim, act, s[1]),
|
|
69
|
+
bwd: (cot, s, c) => {
|
|
70
|
+
const { dIn, gW, gb } = denseBackward(s[0], inDim, outDim, act, s[1], cot);
|
|
71
|
+
let pUpd;
|
|
72
|
+
if (cfg.frozen) {
|
|
73
|
+
pUpd = SKIP;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const W = new Float64Array(s[0].W);
|
|
77
|
+
const b = new Float64Array(s[0].b);
|
|
78
|
+
const lr = cfg.lr;
|
|
79
|
+
for (let k = 0; k < W.length; k++)
|
|
80
|
+
W[k] = W[k] - lr * gW[k];
|
|
81
|
+
for (let o = 0; o < b.length; o++)
|
|
82
|
+
b[o] = b[o] - lr * gb[o];
|
|
83
|
+
pUpd = { W, b };
|
|
84
|
+
}
|
|
85
|
+
return { updates: [pUpd, dIn], complement: c };
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Build a net as a lens DAG. `dims` is `[in, h1, …, out]`; hidden layers use
|
|
90
|
+
* `hidden` activation, the output is `linear` (the squash folds into the loss). */
|
|
91
|
+
export function lensNet(dims, opts = {}) {
|
|
92
|
+
const hidden = opts.hidden ?? "tanh";
|
|
93
|
+
const r = rng(opts.seed ?? 1);
|
|
94
|
+
const cfg = { lr: opts.lr ?? 0.05, frozen: false };
|
|
95
|
+
const input = cell(new Float64Array(dims[0]));
|
|
96
|
+
const layers = [];
|
|
97
|
+
let x = input;
|
|
98
|
+
for (let i = 0; i + 1 < dims.length; i++) {
|
|
99
|
+
const inDim = dims[i];
|
|
100
|
+
const outDim = dims[i + 1];
|
|
101
|
+
const act = i + 2 < dims.length ? hidden : "linear";
|
|
102
|
+
const scale = act === "relu" ? Math.sqrt(2 / inDim) : Math.sqrt(1 / inDim);
|
|
103
|
+
const W = new Float64Array(outDim * inDim);
|
|
104
|
+
for (let k = 0; k < W.length; k++)
|
|
105
|
+
W[k] = gaussian(r) * scale;
|
|
106
|
+
const params = cell({ W, b: new Float64Array(outDim) });
|
|
107
|
+
const out = denseLens(params, x, inDim, outDim, act, cfg);
|
|
108
|
+
layers.push({ inDim, outDim, act, params, out });
|
|
109
|
+
x = out;
|
|
110
|
+
}
|
|
111
|
+
return { input, layers, logits: x, cfg, dims };
|
|
112
|
+
}
|
|
113
|
+
/** Logits for `x`, reading the current weight cells (no training, untracked).
|
|
114
|
+
* Applies any pending backward write first, so it always sees fresh weights. */
|
|
115
|
+
export function logitsOf(net, x) {
|
|
116
|
+
let a = toF64(x);
|
|
117
|
+
for (const L of net.layers)
|
|
118
|
+
a = denseForward(L.params.peek(), L.inDim, L.outDim, L.act, a);
|
|
119
|
+
return a;
|
|
120
|
+
}
|
|
121
|
+
/** Class probabilities: sigmoid for a 1-logit (binary) net, else softmax. */
|
|
122
|
+
export function probsOf(net, x) {
|
|
123
|
+
const z = logitsOf(net, x);
|
|
124
|
+
return z.length === 1 ? Float64Array.of(1 / (1 + Math.exp(-z[0]))) : softmax(z);
|
|
125
|
+
}
|
|
126
|
+
/** Argmax class for a multi-logit net, or `P ≥ 0.5` for a binary net. */
|
|
127
|
+
export function classifyOf(net, x) {
|
|
128
|
+
const p = probsOf(net, x);
|
|
129
|
+
if (p.length === 1)
|
|
130
|
+
return p[0] >= 0.5 ? 1 : 0;
|
|
131
|
+
let best = 0;
|
|
132
|
+
for (let i = 1; i < p.length; i++)
|
|
133
|
+
if (p[i] > p[best])
|
|
134
|
+
best = i;
|
|
135
|
+
return best;
|
|
136
|
+
}
|
|
137
|
+
/** Fraction of `data` classified correctly. */
|
|
138
|
+
export function accuracyOf(net, data) {
|
|
139
|
+
let ok = 0;
|
|
140
|
+
for (const s of data)
|
|
141
|
+
if (classifyOf(net, s.x) === s.y)
|
|
142
|
+
ok++;
|
|
143
|
+
return ok / Math.max(1, data.length);
|
|
144
|
+
}
|
|
145
|
+
// Cross-entropy loss + the cotangent dL/dlogits (the seed of the backward
|
|
146
|
+
// pass). Binary: BCE-with-logits, dz = σ(z) − y. Multi: softmax CE, dz = p − e_y.
|
|
147
|
+
function lossCotangent(z, y) {
|
|
148
|
+
if (z.length === 1) {
|
|
149
|
+
const p = 1 / (1 + Math.exp(-z[0]));
|
|
150
|
+
const eps = 1e-12;
|
|
151
|
+
return {
|
|
152
|
+
loss: -(y * Math.log(p + eps) + (1 - y) * Math.log(1 - p + eps)),
|
|
153
|
+
cot: Float64Array.of(p - y),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const p = softmax(z);
|
|
157
|
+
const cot = new Float64Array(z.length);
|
|
158
|
+
for (let k = 0; k < z.length; k++)
|
|
159
|
+
cot[k] = p[k] - (k === y ? 1 : 0);
|
|
160
|
+
return { loss: -Math.log(p[y] + 1e-12), cot };
|
|
161
|
+
}
|
|
162
|
+
/** Mean cross-entropy over a dataset (no update) — for monitoring/tests. */
|
|
163
|
+
export function meanLossOf(net, data) {
|
|
164
|
+
let total = 0;
|
|
165
|
+
for (const s of data)
|
|
166
|
+
total += lossCotangent(logitsOf(net, s.x), s.y).loss;
|
|
167
|
+
return total / Math.max(1, data.length);
|
|
168
|
+
}
|
|
169
|
+
/** Train one example with a single backward write: pin the input, read the
|
|
170
|
+
* prediction (the forward pull), then write the output cotangent to `logits`.
|
|
171
|
+
* The engine backpropagates and lands an SGD step on every weight cell. The
|
|
172
|
+
* step is forced before returning (while the input still holds this example),
|
|
173
|
+
* so callers may move on immediately. Returns the loss before the step. */
|
|
174
|
+
export function trainExample(net, x, y) {
|
|
175
|
+
net.cfg.frozen = false;
|
|
176
|
+
net.input.value = toF64(x);
|
|
177
|
+
const { loss, cot } = lossCotangent(net.logits.value, y);
|
|
178
|
+
net.logits.value = cot;
|
|
179
|
+
void net.logits.peek(); // force the backward now, while input === this example
|
|
180
|
+
return loss;
|
|
181
|
+
}
|
|
182
|
+
// Fisher–Yates over [0, n) using the provided uniform source.
|
|
183
|
+
function shuffled(n, r) {
|
|
184
|
+
const a = Array.from({ length: n }, (_, i) => i);
|
|
185
|
+
for (let i = n - 1; i > 0; i--) {
|
|
186
|
+
const j = Math.floor(r() * (i + 1));
|
|
187
|
+
const t = a[i];
|
|
188
|
+
a[i] = a[j];
|
|
189
|
+
a[j] = t;
|
|
190
|
+
}
|
|
191
|
+
return a;
|
|
192
|
+
}
|
|
193
|
+
/** Train one shuffled pass — one backward write per example (online SGD).
|
|
194
|
+
* Returns the mean loss over the epoch. */
|
|
195
|
+
export function trainEpoch(net, data, r) {
|
|
196
|
+
if (data.length === 0)
|
|
197
|
+
return 0;
|
|
198
|
+
let total = 0;
|
|
199
|
+
for (const idx of shuffled(data.length, r)) {
|
|
200
|
+
const s = data[idx];
|
|
201
|
+
total += trainExample(net, s.x, s.y);
|
|
202
|
+
}
|
|
203
|
+
return total / data.length;
|
|
204
|
+
}
|
|
205
|
+
/** Input-space gradient toward raising logit `cls`, by one frozen-weight
|
|
206
|
+
* backward write: with the weights held fixed the cotangent flows past them to
|
|
207
|
+
* the input cell, which then holds dL/dInput. Drives the "dream" / saliency. */
|
|
208
|
+
export function inputGradient(net, x, cls = 0) {
|
|
209
|
+
net.cfg.frozen = true;
|
|
210
|
+
net.input.value = toF64(x);
|
|
211
|
+
const z = net.logits.value; // forward
|
|
212
|
+
const seed = new Float64Array(z.length);
|
|
213
|
+
seed[Math.min(cls, z.length - 1)] = 1;
|
|
214
|
+
net.logits.value = seed;
|
|
215
|
+
void net.logits.peek(); // force the backward; the gradient lands on `input`
|
|
216
|
+
const g = net.input.peek();
|
|
217
|
+
net.cfg.frozen = false;
|
|
218
|
+
return g;
|
|
219
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Hidden/output nonlinearity. The output layer is `linear`; its squashing
|
|
2
|
+
* (sigmoid/softmax) is folded into the loss for numerically-stable grads. */
|
|
3
|
+
export type Activation = "tanh" | "relu" | "sigmoid" | "linear";
|
|
4
|
+
/** Deterministic PRNG (mulberry32) so init and data are reproducible. */
|
|
5
|
+
export declare function rng(seed: number): () => number;
|
|
6
|
+
/** Standard normal via Box–Muller, driven by a uniform source. */
|
|
7
|
+
export declare function gaussian(r: () => number): number;
|
|
8
|
+
interface Layer {
|
|
9
|
+
inDim: number;
|
|
10
|
+
outDim: number;
|
|
11
|
+
act: Activation;
|
|
12
|
+
W: Float64Array;
|
|
13
|
+
b: Float64Array;
|
|
14
|
+
gW: Float64Array;
|
|
15
|
+
gb: Float64Array;
|
|
16
|
+
mW: Float64Array;
|
|
17
|
+
vW: Float64Array;
|
|
18
|
+
mb: Float64Array;
|
|
19
|
+
vb: Float64Array;
|
|
20
|
+
inBuf: Float64Array;
|
|
21
|
+
preBuf: Float64Array;
|
|
22
|
+
outBuf: Float64Array;
|
|
23
|
+
}
|
|
24
|
+
/** A multilayer perceptron: a `pipe` of dense layers plus an Adam step clock. */
|
|
25
|
+
export interface MLP {
|
|
26
|
+
layers: Layer[];
|
|
27
|
+
/** Adam timestep (for bias correction). */
|
|
28
|
+
t: number;
|
|
29
|
+
lr: number;
|
|
30
|
+
beta1: number;
|
|
31
|
+
beta2: number;
|
|
32
|
+
l2: number;
|
|
33
|
+
}
|
|
34
|
+
/** Build an MLP. `dims` is `[in, h1, …, out]`; hidden layers use `hidden`
|
|
35
|
+
* activation, the output layer is `linear` (loss folds in the squashing). */
|
|
36
|
+
export declare function mlp(dims: readonly number[], opts?: {
|
|
37
|
+
seed?: number;
|
|
38
|
+
hidden?: Activation;
|
|
39
|
+
lr?: number;
|
|
40
|
+
l2?: number;
|
|
41
|
+
}): MLP;
|
|
42
|
+
/** Pointwise activation `a = σ(z)`. */
|
|
43
|
+
export declare function applyAct(act: Activation, z: number): number;
|
|
44
|
+
/** Activation derivative `σ'`, given the *output* `a` (cheap for tanh/sigmoid). */
|
|
45
|
+
export declare function actGrad(act: Activation, a: number): number;
|
|
46
|
+
/** Forward pass: logits (pre-squash output) for one input vector. */
|
|
47
|
+
export declare function forward(net: MLP, x: Float64Array | number[]): Float64Array;
|
|
48
|
+
/** Softmax of a logit vector (numerically stabilised). */
|
|
49
|
+
export declare function softmax(logits: Float64Array): Float64Array;
|
|
50
|
+
/** Class probabilities: sigmoid for a 1-logit (binary) net, else softmax.
|
|
51
|
+
* A binary net returns `[P(class 1)]`. */
|
|
52
|
+
export declare function predict(net: MLP, x: Float64Array | number[]): Float64Array;
|
|
53
|
+
/** Argmax class for a multi-logit net, or `prob ≥ 0.5` for a binary net. */
|
|
54
|
+
export declare function classify(net: MLP, x: Float64Array | number[]): number;
|
|
55
|
+
/** A labelled example: input vector + integer class. */
|
|
56
|
+
export interface Sample {
|
|
57
|
+
x: Float64Array | number[];
|
|
58
|
+
y: number;
|
|
59
|
+
}
|
|
60
|
+
/** One full-batch gradient step over `batch`. Returns the mean loss before
|
|
61
|
+
* the update. This is the dynamical step on the weights — call it from a
|
|
62
|
+
* clock to train. */
|
|
63
|
+
export declare function trainStep(net: MLP, batch: readonly Sample[]): number;
|
|
64
|
+
/** Mean cross-entropy over a dataset (no update) — for monitoring/tests. */
|
|
65
|
+
export declare function meanLoss(net: MLP, data: readonly Sample[]): number;
|
|
66
|
+
/** Fraction of `data` classified correctly. */
|
|
67
|
+
export declare function accuracy(net: MLP, data: readonly Sample[]): number;
|
|
68
|
+
/** Flattened parameter buffers `[W0, b0, W1, b1, …]` (live views). */
|
|
69
|
+
export declare function parameters(net: MLP): Float64Array[];
|
|
70
|
+
/** Mean-loss gradients over `batch` with no update and no weight decay,
|
|
71
|
+
* aligned with `parameters(net)`. For gradient checking / inspection. */
|
|
72
|
+
export declare function gradients(net: MLP, batch: readonly Sample[]): Float64Array[];
|
|
73
|
+
/** Input-space gradient of a chosen logit, by one backward pass with a unit
|
|
74
|
+
* seed. Drives the "dream" view (ascend pixels toward a class) and saliency.
|
|
75
|
+
* Leaves parameter-gradient accumulators dirty; not for use mid-train-step. */
|
|
76
|
+
export declare function inputGradient(net: MLP, x: Float64Array | number[], cls?: number): Float64Array;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// mlp.ts — a tiny dense neural net, written as a stack of parametric lenses.
|
|
2
|
+
//
|
|
3
|
+
// Backprop is the lens pattern: each layer is a forward map (compute the
|
|
4
|
+
// activation) paired with a backward map (pull a gradient back to the input
|
|
5
|
+
// and deposit gradients on the parameters). Composing layers composes their
|
|
6
|
+
// backward passes in reverse — reverse-mode autodiff *is* lens composition,
|
|
7
|
+
// exactly the `pipe` of the schema kit but over differentiable maps. The
|
|
8
|
+
// "complement" a layer needs for its backward pass is the cached forward
|
|
9
|
+
// activation, stashed on the layer during `forward`.
|
|
10
|
+
//
|
|
11
|
+
// Deliberately coarse-grained: one layer = one matmul over Float64Arrays, not
|
|
12
|
+
// a cell per scalar. The reactive/bidirectional payoff lives in the demos
|
|
13
|
+
// (live data, watch-it-learn); this core stays plain and fast so it is cheap
|
|
14
|
+
// to run and easy to test offline.
|
|
15
|
+
/** Deterministic PRNG (mulberry32) so init and data are reproducible. */
|
|
16
|
+
export function rng(seed) {
|
|
17
|
+
let a = seed >>> 0;
|
|
18
|
+
return () => {
|
|
19
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
20
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
21
|
+
t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
|
|
22
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Standard normal via Box–Muller, driven by a uniform source. */
|
|
26
|
+
export function gaussian(r) {
|
|
27
|
+
let u = 0;
|
|
28
|
+
let v = 0;
|
|
29
|
+
while (u === 0)
|
|
30
|
+
u = r();
|
|
31
|
+
while (v === 0)
|
|
32
|
+
v = r();
|
|
33
|
+
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
|
|
34
|
+
}
|
|
35
|
+
/** Build an MLP. `dims` is `[in, h1, …, out]`; hidden layers use `hidden`
|
|
36
|
+
* activation, the output layer is `linear` (loss folds in the squashing). */
|
|
37
|
+
export function mlp(dims, opts = {}) {
|
|
38
|
+
const hidden = opts.hidden ?? "tanh";
|
|
39
|
+
const r = rng(opts.seed ?? 1);
|
|
40
|
+
const layers = [];
|
|
41
|
+
for (let i = 0; i + 1 < dims.length; i++) {
|
|
42
|
+
const inDim = dims[i];
|
|
43
|
+
const outDim = dims[i + 1];
|
|
44
|
+
const act = i + 2 < dims.length ? hidden : "linear";
|
|
45
|
+
// He for relu, Xavier otherwise — keeps early activations well-scaled.
|
|
46
|
+
const scale = act === "relu" ? Math.sqrt(2 / inDim) : Math.sqrt(1 / inDim);
|
|
47
|
+
const W = new Float64Array(outDim * inDim);
|
|
48
|
+
for (let k = 0; k < W.length; k++)
|
|
49
|
+
W[k] = gaussian(r) * scale;
|
|
50
|
+
layers.push({
|
|
51
|
+
inDim,
|
|
52
|
+
outDim,
|
|
53
|
+
act,
|
|
54
|
+
W,
|
|
55
|
+
b: new Float64Array(outDim),
|
|
56
|
+
gW: new Float64Array(outDim * inDim),
|
|
57
|
+
gb: new Float64Array(outDim),
|
|
58
|
+
mW: new Float64Array(outDim * inDim),
|
|
59
|
+
vW: new Float64Array(outDim * inDim),
|
|
60
|
+
mb: new Float64Array(outDim),
|
|
61
|
+
vb: new Float64Array(outDim),
|
|
62
|
+
inBuf: new Float64Array(inDim),
|
|
63
|
+
preBuf: new Float64Array(outDim),
|
|
64
|
+
outBuf: new Float64Array(outDim),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
layers,
|
|
69
|
+
t: 0,
|
|
70
|
+
lr: opts.lr ?? 0.02,
|
|
71
|
+
beta1: 0.9,
|
|
72
|
+
beta2: 0.999,
|
|
73
|
+
l2: opts.l2 ?? 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Pointwise activation `a = σ(z)`. */
|
|
77
|
+
export function applyAct(act, z) {
|
|
78
|
+
switch (act) {
|
|
79
|
+
case "tanh":
|
|
80
|
+
return Math.tanh(z);
|
|
81
|
+
case "relu":
|
|
82
|
+
return z > 0 ? z : 0;
|
|
83
|
+
case "sigmoid":
|
|
84
|
+
return 1 / (1 + Math.exp(-z));
|
|
85
|
+
default:
|
|
86
|
+
return z;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Activation derivative `σ'`, given the *output* `a` (cheap for tanh/sigmoid). */
|
|
90
|
+
export function actGrad(act, a) {
|
|
91
|
+
switch (act) {
|
|
92
|
+
case "tanh":
|
|
93
|
+
return 1 - a * a;
|
|
94
|
+
case "relu":
|
|
95
|
+
return a > 0 ? 1 : 0;
|
|
96
|
+
case "sigmoid":
|
|
97
|
+
return a * (1 - a);
|
|
98
|
+
default:
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Forward through one layer (the lens `fwd`): caches input + output as the
|
|
103
|
+
// complement, returns the activation buffer (reused — copy if you must keep).
|
|
104
|
+
function forwardLayer(L, x) {
|
|
105
|
+
L.inBuf.set(x);
|
|
106
|
+
for (let o = 0; o < L.outDim; o++) {
|
|
107
|
+
let z = L.b[o];
|
|
108
|
+
const base = o * L.inDim;
|
|
109
|
+
for (let i = 0; i < L.inDim; i++)
|
|
110
|
+
z += L.W[base + i] * x[i];
|
|
111
|
+
L.preBuf[o] = z;
|
|
112
|
+
L.outBuf[o] = applyAct(L.act, z);
|
|
113
|
+
}
|
|
114
|
+
return L.outBuf;
|
|
115
|
+
}
|
|
116
|
+
// Backward through one layer (the lens `bwd`): given dL/da, accumulate the
|
|
117
|
+
// parameter gradients and return dL/dx for the previous layer.
|
|
118
|
+
function backwardLayer(L, dOut) {
|
|
119
|
+
const dIn = new Float64Array(L.inDim);
|
|
120
|
+
for (let o = 0; o < L.outDim; o++) {
|
|
121
|
+
const dz = dOut[o] * actGrad(L.act, L.outBuf[o]);
|
|
122
|
+
L.gb[o] = L.gb[o] + dz;
|
|
123
|
+
const base = o * L.inDim;
|
|
124
|
+
for (let i = 0; i < L.inDim; i++) {
|
|
125
|
+
L.gW[base + i] = L.gW[base + i] + dz * L.inBuf[i];
|
|
126
|
+
dIn[i] = dIn[i] + L.W[base + i] * dz;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return dIn;
|
|
130
|
+
}
|
|
131
|
+
/** Forward pass: logits (pre-squash output) for one input vector. */
|
|
132
|
+
export function forward(net, x) {
|
|
133
|
+
let a = x instanceof Float64Array ? x : Float64Array.from(x);
|
|
134
|
+
for (const L of net.layers)
|
|
135
|
+
a = forwardLayer(L, a);
|
|
136
|
+
return a;
|
|
137
|
+
}
|
|
138
|
+
/** Softmax of a logit vector (numerically stabilised). */
|
|
139
|
+
export function softmax(logits) {
|
|
140
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
141
|
+
for (const z of logits)
|
|
142
|
+
if (z > max)
|
|
143
|
+
max = z;
|
|
144
|
+
const out = new Float64Array(logits.length);
|
|
145
|
+
let sum = 0;
|
|
146
|
+
for (let i = 0; i < logits.length; i++) {
|
|
147
|
+
const e = Math.exp(logits[i] - max);
|
|
148
|
+
out[i] = e;
|
|
149
|
+
sum += e;
|
|
150
|
+
}
|
|
151
|
+
for (let i = 0; i < out.length; i++)
|
|
152
|
+
out[i] = out[i] / sum;
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
/** Class probabilities: sigmoid for a 1-logit (binary) net, else softmax.
|
|
156
|
+
* A binary net returns `[P(class 1)]`. */
|
|
157
|
+
export function predict(net, x) {
|
|
158
|
+
const logits = forward(net, x);
|
|
159
|
+
if (logits.length === 1)
|
|
160
|
+
return Float64Array.of(1 / (1 + Math.exp(-logits[0])));
|
|
161
|
+
return softmax(logits);
|
|
162
|
+
}
|
|
163
|
+
/** Argmax class for a multi-logit net, or `prob ≥ 0.5` for a binary net. */
|
|
164
|
+
export function classify(net, x) {
|
|
165
|
+
const p = predict(net, x);
|
|
166
|
+
if (p.length === 1)
|
|
167
|
+
return p[0] >= 0.5 ? 1 : 0;
|
|
168
|
+
let best = 0;
|
|
169
|
+
for (let i = 1; i < p.length; i++)
|
|
170
|
+
if (p[i] > p[best])
|
|
171
|
+
best = i;
|
|
172
|
+
return best;
|
|
173
|
+
}
|
|
174
|
+
// Cross-entropy loss + the gradient on the logits, written into `dLogit`.
|
|
175
|
+
// Binary (1 logit): BCE-with-logits, dz = sigmoid(z) − y.
|
|
176
|
+
// Multi (K logits): softmax CE, dz = softmax(z) − onehot(y).
|
|
177
|
+
function lossAndGrad(logits, y, dLogit) {
|
|
178
|
+
if (logits.length === 1) {
|
|
179
|
+
const z = logits[0];
|
|
180
|
+
const p = 1 / (1 + Math.exp(-z));
|
|
181
|
+
dLogit[0] = p - y;
|
|
182
|
+
const eps = 1e-12;
|
|
183
|
+
return -(y * Math.log(p + eps) + (1 - y) * Math.log(1 - p + eps));
|
|
184
|
+
}
|
|
185
|
+
const p = softmax(logits);
|
|
186
|
+
for (let k = 0; k < logits.length; k++)
|
|
187
|
+
dLogit[k] = p[k] - (k === y ? 1 : 0);
|
|
188
|
+
return -Math.log(p[y] + 1e-12);
|
|
189
|
+
}
|
|
190
|
+
function adamStep(net, param, g, m, v) {
|
|
191
|
+
const { lr, beta1, beta2, t } = net;
|
|
192
|
+
const bc1 = 1 - beta1 ** t;
|
|
193
|
+
const bc2 = 1 - beta2 ** t;
|
|
194
|
+
for (let i = 0; i < param.length; i++) {
|
|
195
|
+
const gi = g[i];
|
|
196
|
+
const mi = (m[i] = beta1 * m[i] + (1 - beta1) * gi);
|
|
197
|
+
const vi = (v[i] = beta2 * v[i] + (1 - beta2) * gi * gi);
|
|
198
|
+
param[i] = param[i] - (lr * (mi / bc1)) / (Math.sqrt(vi / bc2) + 1e-8);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** One full-batch gradient step over `batch`. Returns the mean loss before
|
|
202
|
+
* the update. This is the dynamical step on the weights — call it from a
|
|
203
|
+
* clock to train. */
|
|
204
|
+
export function trainStep(net, batch) {
|
|
205
|
+
for (const L of net.layers) {
|
|
206
|
+
L.gW.fill(0);
|
|
207
|
+
L.gb.fill(0);
|
|
208
|
+
}
|
|
209
|
+
const outDim = net.layers[net.layers.length - 1].outDim;
|
|
210
|
+
const dLogit = new Float64Array(outDim);
|
|
211
|
+
let total = 0;
|
|
212
|
+
for (const s of batch) {
|
|
213
|
+
const logits = forward(net, s.x);
|
|
214
|
+
total += lossAndGrad(logits, s.y, dLogit);
|
|
215
|
+
let g = dLogit;
|
|
216
|
+
for (let li = net.layers.length - 1; li >= 0; li--)
|
|
217
|
+
g = backwardLayer(net.layers[li], g);
|
|
218
|
+
}
|
|
219
|
+
const inv = 1 / Math.max(1, batch.length);
|
|
220
|
+
net.t += 1;
|
|
221
|
+
for (const L of net.layers) {
|
|
222
|
+
for (let i = 0; i < L.gW.length; i++)
|
|
223
|
+
L.gW[i] = L.gW[i] * inv + net.l2 * L.W[i];
|
|
224
|
+
for (let i = 0; i < L.gb.length; i++)
|
|
225
|
+
L.gb[i] = L.gb[i] * inv;
|
|
226
|
+
adamStep(net, L.W, L.gW, L.mW, L.vW);
|
|
227
|
+
adamStep(net, L.b, L.gb, L.mb, L.vb);
|
|
228
|
+
}
|
|
229
|
+
return total * inv;
|
|
230
|
+
}
|
|
231
|
+
/** Mean cross-entropy over a dataset (no update) — for monitoring/tests. */
|
|
232
|
+
export function meanLoss(net, data) {
|
|
233
|
+
const outDim = net.layers[net.layers.length - 1].outDim;
|
|
234
|
+
const dLogit = new Float64Array(outDim);
|
|
235
|
+
let total = 0;
|
|
236
|
+
for (const s of data)
|
|
237
|
+
total += lossAndGrad(forward(net, s.x), s.y, dLogit);
|
|
238
|
+
return total / Math.max(1, data.length);
|
|
239
|
+
}
|
|
240
|
+
/** Fraction of `data` classified correctly. */
|
|
241
|
+
export function accuracy(net, data) {
|
|
242
|
+
let ok = 0;
|
|
243
|
+
for (const s of data)
|
|
244
|
+
if (classify(net, s.x) === s.y)
|
|
245
|
+
ok++;
|
|
246
|
+
return ok / Math.max(1, data.length);
|
|
247
|
+
}
|
|
248
|
+
/** Flattened parameter buffers `[W0, b0, W1, b1, …]` (live views). */
|
|
249
|
+
export function parameters(net) {
|
|
250
|
+
const out = [];
|
|
251
|
+
for (const L of net.layers) {
|
|
252
|
+
out.push(L.W);
|
|
253
|
+
out.push(L.b);
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
/** Mean-loss gradients over `batch` with no update and no weight decay,
|
|
258
|
+
* aligned with `parameters(net)`. For gradient checking / inspection. */
|
|
259
|
+
export function gradients(net, batch) {
|
|
260
|
+
for (const L of net.layers) {
|
|
261
|
+
L.gW.fill(0);
|
|
262
|
+
L.gb.fill(0);
|
|
263
|
+
}
|
|
264
|
+
const outDim = net.layers[net.layers.length - 1].outDim;
|
|
265
|
+
const dLogit = new Float64Array(outDim);
|
|
266
|
+
for (const s of batch) {
|
|
267
|
+
lossAndGrad(forward(net, s.x), s.y, dLogit);
|
|
268
|
+
let g = dLogit;
|
|
269
|
+
for (let li = net.layers.length - 1; li >= 0; li--)
|
|
270
|
+
g = backwardLayer(net.layers[li], g);
|
|
271
|
+
}
|
|
272
|
+
const inv = 1 / Math.max(1, batch.length);
|
|
273
|
+
const out = [];
|
|
274
|
+
for (const L of net.layers) {
|
|
275
|
+
out.push(L.gW.map(v => v * inv));
|
|
276
|
+
out.push(L.gb.map(v => v * inv));
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
/** Input-space gradient of a chosen logit, by one backward pass with a unit
|
|
281
|
+
* seed. Drives the "dream" view (ascend pixels toward a class) and saliency.
|
|
282
|
+
* Leaves parameter-gradient accumulators dirty; not for use mid-train-step. */
|
|
283
|
+
export function inputGradient(net, x, cls = 0) {
|
|
284
|
+
forward(net, x);
|
|
285
|
+
const outDim = net.layers[net.layers.length - 1].outDim;
|
|
286
|
+
const seed = new Float64Array(outDim);
|
|
287
|
+
seed[Math.min(cls, outDim - 1)] = 1;
|
|
288
|
+
let g = seed;
|
|
289
|
+
for (let li = net.layers.length - 1; li >= 0; li--)
|
|
290
|
+
g = backwardLayer(net.layers[li], g);
|
|
291
|
+
return g;
|
|
292
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type LatticeCell } from "./lattice.js";
|
|
2
|
+
import { type Propagator } from "./solver.js";
|
|
3
|
+
type S<E> = LatticeCell<ReadonlySet<E>>;
|
|
4
|
+
/** "These cells hold DIFFERENT values." When one collapses to a
|
|
5
|
+
* singleton `{v}`, eliminate `v` from the others (naked-single
|
|
6
|
+
* propagation). N(N−1) narrowers. */
|
|
7
|
+
export declare function allDifferent<E>(...cells: S<E>[]): Propagator[];
|
|
8
|
+
/** `a` and `b` hold the same value: intersect both candidate sets.
|
|
9
|
+
* Unification, lifted to sets. */
|
|
10
|
+
export declare function same<E>(a: S<E>, b: S<E>): Propagator[];
|
|
11
|
+
/** Restrict `cell` to `allowed` (intersect). Self-applying. */
|
|
12
|
+
export declare function restrict<E>(cell: S<E>, allowed: Iterable<E>): Propagator;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// csp.ts — set-narrowing relations over candidate-set cells.
|
|
2
|
+
//
|
|
3
|
+
// The discrete sibling of `numeric.ts`: same monotone-narrowing model,
|
|
4
|
+
// finite-set lattice. Height is bounded by the universe size, so these
|
|
5
|
+
// terminate structurally — sudoku, map colouring, type unification.
|
|
6
|
+
import { merge } from "./lattice.js";
|
|
7
|
+
import { propagator } from "./solver.js";
|
|
8
|
+
/** "These cells hold DIFFERENT values." When one collapses to a
|
|
9
|
+
* singleton `{v}`, eliminate `v` from the others (naked-single
|
|
10
|
+
* propagation). N(N−1) narrowers. */
|
|
11
|
+
export function allDifferent(...cells) {
|
|
12
|
+
const props = [];
|
|
13
|
+
for (let i = 0; i < cells.length; i++) {
|
|
14
|
+
for (let j = 0; j < cells.length; j++) {
|
|
15
|
+
if (i === j)
|
|
16
|
+
continue;
|
|
17
|
+
const src = cells[i];
|
|
18
|
+
const dst = cells[j];
|
|
19
|
+
props.push(propagator([src], [dst], () => {
|
|
20
|
+
const sv = src.value;
|
|
21
|
+
if (sv.size !== 1)
|
|
22
|
+
return;
|
|
23
|
+
const [only] = sv;
|
|
24
|
+
if (!dst.value.has(only))
|
|
25
|
+
return;
|
|
26
|
+
const next = new Set(dst.value);
|
|
27
|
+
next.delete(only);
|
|
28
|
+
merge(dst, next);
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return props;
|
|
33
|
+
}
|
|
34
|
+
/** `a` and `b` hold the same value: intersect both candidate sets.
|
|
35
|
+
* Unification, lifted to sets. */
|
|
36
|
+
export function same(a, b) {
|
|
37
|
+
return [
|
|
38
|
+
propagator([a], [b], () => {
|
|
39
|
+
merge(b, a.value);
|
|
40
|
+
}),
|
|
41
|
+
propagator([b], [a], () => {
|
|
42
|
+
merge(a, b.value);
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
/** Restrict `cell` to `allowed` (intersect). Self-applying. */
|
|
47
|
+
export function restrict(cell, allowed) {
|
|
48
|
+
const allow = new Set(allowed);
|
|
49
|
+
return propagator([cell], [cell], () => {
|
|
50
|
+
merge(cell, allow);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Box, type Num as NumClass, type Read, type Writable } from "../core/index.js";
|
|
2
|
+
import { type Propagator } from "./solver.js";
|
|
3
|
+
type ValOrSig = number | Read<number>;
|
|
4
|
+
/** A flex child. A bare `Box` takes defaults (grow 1, shrink 1, no
|
|
5
|
+
* bounds); tag it to set per-item flex. `basis` seeds the size the
|
|
6
|
+
* distribution grows/shrinks from (defaults to the box's current main
|
|
7
|
+
* size). */
|
|
8
|
+
export type Item = Box | {
|
|
9
|
+
box: Box;
|
|
10
|
+
grow?: number;
|
|
11
|
+
shrink?: number;
|
|
12
|
+
min?: number;
|
|
13
|
+
max?: number;
|
|
14
|
+
basis?: number;
|
|
15
|
+
};
|
|
16
|
+
export interface FlexOpts {
|
|
17
|
+
/** Space between adjacent items. Default 0. */
|
|
18
|
+
gap?: ValOrSig;
|
|
19
|
+
/** Padding inside the container on every side. Default 0. */
|
|
20
|
+
padding?: ValOrSig;
|
|
21
|
+
/** Cross-axis placement. Default "stretch". */
|
|
22
|
+
align?: "start" | "center" | "end" | "stretch";
|
|
23
|
+
/** Set to `true` on the frame(s) where the content can't fit (Σmin >
|
|
24
|
+
* content). Drives "this layout is impossible" UI. */
|
|
25
|
+
report?: Writable<NumClass> | ((infeasible: boolean) => void);
|
|
26
|
+
}
|
|
27
|
+
/** Horizontal flex line over plain `Box` cells. */
|
|
28
|
+
export declare function row(c: Box, items: readonly Item[], opts?: FlexOpts): Propagator;
|
|
29
|
+
/** Vertical flex line over plain `Box` cells. */
|
|
30
|
+
export declare function col(c: Box, items: readonly Item[], opts?: FlexOpts): Propagator;
|
|
31
|
+
export {};
|