Merge remote-tracking branch 'origin/main' into hands/variants-c2a-main
This commit is contained in:
commit
0f6edfbfbb
72 changed files with 78062 additions and 892 deletions
|
|
@ -583,6 +583,87 @@ all `MIT OR Apache-2.0`; plus `cfg-if`, `libc`, `cc` (build) and `windows-sys` o
|
|||
stacker compiles C at build time, so a C toolchain becomes a build requirement (gcc in the rust image, MSVC on
|
||||
Starfire). cultcache-rs is MIT. Written by hand, the wrapper would be serde_stacker's ~650 lines of forwarding.
|
||||
|
||||
### Body facts, revision 2 (verdict `cut-decode-stack-safety.s1`)
|
||||
|
||||
Imagination pass `imagination-decode-stack-r2`, session `self-2026-10-14-eureka`, on branch
|
||||
`hands/decode-stack-safety` at `2505c0c4`. Probes live under the session scratchpad,
|
||||
`imagination-decode-stack-r2/` (`gen.py`, `gen2.py`, `driver*.rs.in`, jobs `jobP1` to `jobP4`), run on Yggdrasil
|
||||
in the rust image (rustc 1.95.0). None of it is committed. `jobP1` patches a copy of serde_stacker 0.1.14 so
|
||||
each of its five `maybe_grow` calls (`de.rs:476-520`, the wrapper visitor's container entries) records the
|
||||
stack pointer, and reads the door's red zone and segment from the environment. Shapes: `wide` is the committed
|
||||
`Wide32` (32 slots of `[u64; 32]`, 8 KiB inline); `bigN` is 32 slots of an N-array record (32·N KiB inline:
|
||||
32, 64, 128, 256 KiB), each with an `Option<Box<Self>>` slot; `lim` is 31 slots of a 2-array record (15,880 B).
|
||||
A caller is one `#[inline(never)]` function holding the `Result<T>`.
|
||||
|
||||
D10. **The gap between checks is linear in the record's inline size.** Stack used between two consecutive
|
||||
checks, the worst over a 6-level decode on a 1 GiB thread (every gap in a shape is the same value):
|
||||
|
||||
| Shape (inline) | wide (8 KiB) | big4 (32) | big8 (64) | big16 (128) | big32 (256) |
|
||||
|---|---|---|---|---|---|
|
||||
| debug gap | 115 KiB | 451 | 899 | 1,795 | 3,587 |
|
||||
| debug frames before the first check | 131 KiB | 515 | 1,027 | 2,051 | 4,099 |
|
||||
| release gap | 48 KiB | 192 | 384 | 768 | 1,536 |
|
||||
| release frames before the first check | 48 KiB | 192 | 384 | 768 | 1,536 |
|
||||
|
||||
So the gap is 14·S + 3 KiB in debug and 6·S in release, for S = `size_of` of the record entered: the frames
|
||||
between a container check and the next one each hold the record being built or a `Result` of it (rmp-serde's
|
||||
`any_inner`, the wrapper visitor, the derive's `visit_seq` with one `Option` per slot, the seed). D4's 128-144
|
||||
KiB debug gap for `fat` is this line at 8 KiB plus a margin. The stack touched below the deepest check is 6 KiB
|
||||
in debug and 0 in release, so the excursions between checks are the gaps above.
|
||||
|
||||
D11. **Why the thread size decided, in two ways.** (a) Before the first check the decode has already used the
|
||||
second row of D10: `rmp-serde`'s `deserialize_tuple`, `any_inner` and the wrapper's frames sit between
|
||||
`decode_payload` and the wrapper's first `maybe_grow`, and each holds `Result<T>`. A thread smaller than that
|
||||
row aborts whatever the red zone: `wide` aborts on every thread up to 144 KiB in debug. (b) Mid-decode, a check
|
||||
with between R and the gap remaining lets the next gap overrun; after the first grow the positions in each
|
||||
segment are fixed, so which check first grows (set by the thread size) decides, which is the non-monotonic
|
||||
pattern Soul measured. Thread sweeps at the bound (threads from 64 KiB in steps of a 64th of the span, plus
|
||||
1, 1.5, 2, 3, 4 and 64 MiB):
|
||||
|
||||
| Profile, shape | R = 256 KiB | R = gap - 48 KiB | R = gap rounded up + 64 KiB |
|
||||
|---|---|---|---|
|
||||
| debug wide | 6 of 65 sweep threads abort (64-144 KiB); none at 1-64 MiB | all 65; and at 1-4 MiB | 6 (64-144 KiB); none at 1-64 MiB |
|
||||
| debug big4 | 39 of 65; and at 1.5, 2, 3, 4, 64 MiB | 29; and at 2, 64 MiB | 24, the lowest from 64 KiB; none at 1-64 MiB |
|
||||
| release wide | none | 14, spread from 112 to 864 KiB; and at 1, 4 MiB | none |
|
||||
| release big4 | 9 (64-192 KiB) | 18, including 448-480 KiB | 9 (64-192 KiB) |
|
||||
| release big8 | 40; and at 1.5, 2, 3, 64 MiB | 20; and at 1.5 MiB | 17, the lowest from 64 KiB; none at 1-64 MiB |
|
||||
|
||||
With R at least the gap, the abort counts match the threads smaller than the frames before the first check
|
||||
(D10's second row plus the harness) in every shape and profile, big16 and big32 included (segments 4·R); with
|
||||
R below the gap, threads well above those frames abort mid-decode.
|
||||
|
||||
D12. **Checking at the top of the door** (`jobP4`). `decode_payload` patched so its whole body runs inside
|
||||
`stacker::maybe_grow(R, segment, ..)` before the walk and the wrapper, R = 512 KiB, segment 2 MiB. Every
|
||||
thread from 24 KiB to 1 MiB in 8 KiB steps, then 1.5, 2, 3, 4, 8 and 64 MiB:
|
||||
|
||||
| Profile | wide (8 KiB), 1 level | wide, 255 levels | lim (15,880 B), 1 level | lim, 254 levels |
|
||||
|---|---|---|---|---|
|
||||
| debug: smallest thread that decodes | 40 KiB | 40 KiB | 72 KiB | 72 KiB |
|
||||
| release: smallest thread that decodes | 24 KiB | 24 KiB | 40 KiB | 40 KiB |
|
||||
|
||||
No thread at or above the smallest one aborts, in any of the four columns, in either profile. lim's gap is
|
||||
220 KiB in debug and 93 KiB in release (D10's lines give 220 and 93), so R = 512 KiB is 2.3 times the debug
|
||||
gap of a record at 16 KiB. What a thread needs is now the caller's own frames, which hold the returned value
|
||||
and do not depend on the payload's depth.
|
||||
|
||||
D13. **No red zone or placement removes the bound.** Every frame that holds a value of type T sits between the
|
||||
check before T's container and the check inside it, and serde's own impls add frames that hold T before T's
|
||||
first check: `Box<T>`'s `Deserialize` holds the `Result<T>` it boxes, and the `Option` check before it sized
|
||||
nothing larger than a pointer. A per-type red zone (a forked wrapper sized by `size_of::<V::Value>()`, or a
|
||||
grow in the derive sized by `size_of::<Self>()`) still leaves that frame of about one to three T outside any
|
||||
sized check. So the stack a decode needs between checks is bounded only by bounding the inline size of the
|
||||
records it builds. The derive is the one place that sees every record type it produces: a `const` assertion on
|
||||
`size_of::<Self>()` refuses an oversized record at compile time. A field of a type the derive did not produce
|
||||
(a fixed array of arrays, a large tuple, a plain serde struct) behind a `Box` or `Vec` is not reached by it.
|
||||
|
||||
D14. **Build closure and wasm, measured by Soul (`soul-decode-stack-s1` jobD, `cargo tree -e normal,build`
|
||||
at `2505c0c4` against `ae6ef5a8`).** Added on `x86_64-unknown-linux-gnu`: serde_stacker 0.1.14, stacker 0.1.25,
|
||||
psm 0.1.32, cc 1.8.0, find-msvc-tools 0.1.14, shlex 2.0.1, object 0.39.1 (`Apache-2.0 OR MIT`), all
|
||||
`MIT OR Apache-2.0` except ar_archive_writer 0.5.3 (`Apache-2.0 WITH LLVM-exception`, build-only, via psm).
|
||||
On `x86_64-pc-windows-msvc` the same plus libc 0.2.190 and windows-sys 0.61.2 (`MIT OR Apache-2.0`, beside the
|
||||
crate's own windows-sys 0.59). `fs2 0.4` (8 call sites in `lib.rs`, the store's file locks) has no wasi
|
||||
backend, so cultcache-rs does not build for `wasm32-wasip1`; D8 measured serde_stacker in a separate crate.
|
||||
|
||||
### Authority, and why
|
||||
|
||||
The door owns stack safety as it owns the bound: `decode_payload` runs the bound walk, then rmp-serde through
|
||||
|
|
@ -591,8 +672,12 @@ door descend through" is met there, because every derived level is entered throu
|
|||
`maybe_grow` in the derive would be a second owner of the same check, needs a red zone of 128 KiB in release
|
||||
and 256 KiB in debug for fat where the wrapper needs 32 and 144 KiB (D4), and still misses foreign recursive types. The derive is unchanged.
|
||||
|
||||
The thread's size no longer decides whether an accepted payload decodes, on targets where stacker switches
|
||||
stacks. rmp-serde's 1024 counter stays a backstop that never decides. Callers that decode a payload with
|
||||
Revision 2 (D10-D13): the door's first act is a check, so no frame holding T precedes it, and the red zone is
|
||||
set against the gap of the largest record the derive may produce. That size is a public constant the derive
|
||||
asserts at compile time, because nothing at decode time can bound a frame that holds a T before T's own check
|
||||
(D13). The thread's size then decides nothing beyond the caller's own frames, which hold the returned value
|
||||
whatever the payload's depth (D12). Envelope and store decodes enter through the same grown decoder, without
|
||||
the payload walk, so what they accept is unchanged. rmp-serde's 1024 counter stays a backstop that never decides. Callers that decode a payload with
|
||||
`rmp_serde::from_slice` directly bypass the door, the bound and the grow alike; cultnet-rs re-decodes
|
||||
door-checked `rmpv::Value`s that way (follow-up `rs-redecode-outside-door-r2`).
|
||||
|
||||
|
|
|
|||
|
|
@ -1646,3 +1646,289 @@ regenerated GLSL in one merge.
|
|||
- The `0.77` in `packages/cultmath/docs/design.md:376` and `NoiseGradTests.cs:195`
|
||||
is the upstream tie jump and is accurate; it is a different 0.77 from the
|
||||
range-ceiling ratio at line 1347 of this file.
|
||||
|
||||
## Pass 7: the tape unparks (ruling `tape-unparked-for-trophic-graphs`)
|
||||
|
||||
Written by Imagination (agent `imagination-tape-unpark`, session
|
||||
`self-2026-10-14-eureka`) against CultLib `origin/main` `9ec66d77`. The operator,
|
||||
2026-10-14: "Unpark the tape". Her choice (`aetheria-fauna:ruling:trophic-graph-tape-ruled`)
|
||||
scopes tape steps 2 and 4 of `docs/cultmath-tape-target.md`: a graph document, a
|
||||
C# evaluator and an HLSL interpreter. Pruning (step 3) and meshing (step 5) stay
|
||||
where the target sequences them. The first consumer is Aetheria's trophic
|
||||
placement graph (`aetheria-fauna:target:r4`; Aetheria map section r4 at
|
||||
`4354ef2c`, rows B28-B36). `aetheria-fauna:ruling:skein-filament-construction-ruled`
|
||||
picked `skein-web`, so fauna needs all five CultMath gaps that section names.
|
||||
|
||||
The as-built forbidden writer "an evaluator over `CultMath.Expr`" (Pass 5 and
|
||||
the as-built section above) is lifted for the point evaluator and the HLSL
|
||||
interpreter only. Interval and affine evaluation over the graph belong to pruning
|
||||
and stay parked.
|
||||
|
||||
### Body facts (Pass 7)
|
||||
|
||||
| # | Question | Result |
|
||||
|---|---|---|
|
||||
| T1 | Does `CultMath.Expr` exist? | No. `git log --all -S "CultMath.Expr"` in CultLib finds only two map commits (`32ba1bfa`, `4b34b12b`); in Aetheria, nothing. `packages/cultmath/src/CultMath/` has no `Expr` folder. It exists as ruling `material-ir-cultmath-now` and spec `cut-expr-ir.r1` (base `a7966142`), which no report cites. |
|
||||
| T2 | Is the material consumer that shaped `expr-ir` r1 live? | Not now. `aetheria-release:ruling:demo-material-scope` (2026-10-06): demo hulls ship baked maps, "the material graph comes after the demo". The cones, frontiers and HLSL emitter of r1 served incremental per-ship bakes (`materials-baked-per-ship-incremental`) and generated kernels (`material-mod-graphs-same-lowering`). |
|
||||
| T3 | Does `expr-ir` need `cycles-converters`? | Only for `cycles_*` ops. The trophic graph uses none (Aetheria `trophic_probe.py` at `4354ef2c`: arithmetic, smoothstep, exp, hypot, floor, sin, hashing, Voronoi, segment distance). |
|
||||
| T4 | Who owns CultMath values' wire shape? | `GameCult.Caching.MessagePack`: `CultMathResolver` is the canonical MessagePack shape of every public CultMath value type, in the default chain (`CultDocumentMessagePackSerialization.cs:61`). CultMath references nothing (`CultMath.asmdef` `references: []`). |
|
||||
| T5 | What do the coverage tests demand of a new public type? | `CultMathSerializationTests.cs:187-196` and `CultMathJsonTests.cs:93-99,210-218`: every exported non-enum struct in the CultMath assembly has a MessagePack formatter, a JSON converter and a dictionary-key round trip, and `CultMathResolver` formats nothing else. A public struct in `CultMath.Expr` would have to be formatted; a class or enum is not covered, and `CultMathResolver` must not list a class. |
|
||||
| T6 | Can the HLSL mirror cover `out` parameters or buffers? | No. `HlslSourceCompatibilityTests.cs:120-131` builds each argument with `Activator.CreateInstance` over the parameter type, which a by-ref type refuses; `design.md:96-99` drops functions taking GPU resources. So an HLSL twin of `distance_to_segment` needs an `out`-free overload, and the interpreter's buffer reads are outside the mirror. |
|
||||
| T7 | What does the trophic graph need beyond scalar ops? | (a) Stencils: a predator is a mean over 8 taps of its prey at offset positions (`stencil`, B36); in a graph that is input substitution, as Fidget's `remap_xyz`. (b) Site spans: the slack layer and the strands read every well (`well_slack`, `pairs`, `strands`): a per-batch array input. (c) A per-site weight: the lattice node raises each cell's weight by its proximity to the wells (`lattice_web`, `prox`), a function evaluated at the site, not at the query point. |
|
||||
|
||||
### The model (SKILL step 0b)
|
||||
|
||||
| Kind | What names it | What happens to it over time | Who decides |
|
||||
|---|---|---|---|
|
||||
| Expression graph (`ExprGraph`, a sealed immutable class) | Nothing of its own: it is a value. The consumer record that holds it names it (an Aetheria species record's key). Its content hash is derived, for caches only. | Built by the builder or decoded; validated on both paths by one validator; never mutated. An edit builds a new graph. The persisted shape carries a format version; a graph naming an unknown op, a newer format, or a forward operand is refused whole at decode, never partly loaded. | `CultMath.Expr` owns shape, validation and op vocabulary. `GameCult.Caching.MessagePack` owns the wire shape (`CultMathExprResolver`, beside `CultMathResolver`: T4). The consumer's store owns the record, its key and when it changes. Sub-graph references are the consumer store's and are inlined before Build, so the graph stays flat (precedent `aetheria-release:ruling:material-graph-home-modular`). |
|
||||
| Node | Its index in the graph's topological order; an operand references only a lower index. | Exists only inside one graph value; any rebuild may renumber it. The builder hash-conses identical nodes and drops nodes no output reaches, so one expression always builds to one byte string. An editor that needs stable node ids keeps its own map. | The builder. |
|
||||
| Port | Input ports: the operand positions of the op's signature in the op table. Output: a node is one scalar; a multi-result op (cellular's fields) is read only through `Pick(node, k)` nodes and evaluated once. Graph ports: named inputs (vary per point: position, time), named params (uniform per batch, each with a default), named outputs; names unique per kind, `[A-Za-z_][A-Za-z0-9_]*`. | Graph port names are the graph's contract; renaming one makes a new graph. | The author, through the builder; the evaluator binds names to indices once per bind. |
|
||||
| Op vocabulary | A stable string per op: the CultMath function's name, with an arity suffix where overloads differ (`snoise2`, `snoise3`). Written into persisted graphs. | Append-only. A released op's meaning follows its CultMath function under semver (a value change, as the snoise tie, is a Breaking release note); removing an op is a major bump. | `CultMath.Expr`: one op table, read by the C# evaluator and later by the bytecode lowering. No second table. |
|
||||
| Parameter set (bindings) | The consumer's record (fauna: per-species settings); inside it, param name to value. | Edited freely by the consumer. At bind, a missing name takes the graph's default; an unknown name is refused, typed. CultMath persists no parameter-set type. | The consumer's store. |
|
||||
| Site span (input kind) | A span-input name in the graph. | Bound per evaluation batch by the caller (wells per tick); never persisted in the graph. C#: `ReadOnlySpan<float4>`; HLSL: `StructuredBuffer<float4>` plus a count. Read only by span-taking ops; no op produces one. Lands with `expr-trophic-ops` as an append-only format addition. | The caller binds the content; `CultMath.Expr` owns the kind. |
|
||||
| Bytecode (the lowered tape) | (graph content hash, bytecode format version). | Derived on demand, cache-only; never stored as truth or shipped as a document; rebuilt when the graph or CultMath changes. | `CultMath.Expr`'s lowering (the interpreter cut). Its register model is question `tape-register-model`. |
|
||||
| HLSL interpreter source (`shaders/CultMath.Tape.hlsl`) | The file, versioned by the CultMath release tag; it declares the bytecode format version it reads. | Changes only in releases under semver; a format change bumps the version and the lowering together. Copied byte-identical into the Unity package (`UnityPackageShaderIsIdenticalToTheCanonicalShader`). | CultMath. |
|
||||
| Compiled HLSL artifact | Under the interpreter there is no per-graph compiled HLSL. The one compiled artifact is the consumer's compute kernel that includes `CultMath.Tape.hlsl`, named by the consumer's build. | Compiled by the consumer's build (Unity), never generated per graph. Per-graph generated kernels (the r1 emitter) return only with the material line, as a derived artifact pinned against the C# evaluator. | The consumer's build; CultMath owns only the included source. |
|
||||
| Evaluation results | Derived. | Cache-only, the consumer's. | The consumer. |
|
||||
|
||||
Gap (5) of the fauna map, "CultMath-owned parity for a consumer's combined HLSL",
|
||||
changes shape under the tape: a consumer's composition is a graph, not HLSL, so
|
||||
the parity CultMath owns is graph parity, the C# evaluator against the HLSL
|
||||
interpreter on the consumer's own graph. That harness belongs to the interpreter
|
||||
cut. `aetheria-fauna:follow_up:flow-parity-under-cultmath` (FlowField's `Tri3D`)
|
||||
resolves the same way: `Tri3D` becomes a CultMath function and op, with no
|
||||
Aetheria twin.
|
||||
|
||||
### Decisions (defaults, not forks)
|
||||
|
||||
- **`CultMath.Expr` is the tape's graph; there is one IR.** `expr-ir` r1 is
|
||||
superseded by r2, the first tape cut, under the same label: ruling
|
||||
`material-ir-cultmath-now` names the owner and the scalar SSA shape, and the
|
||||
tape ruling consumes that graph. r2 drops what only the material line used
|
||||
(cones, frontiers, the HLSL emitter: T2) and the `cycles-converters`
|
||||
dependency (T3). Those return as lines when their consumers do.
|
||||
- **Scalar SSA**, as ruled: vector arguments are decomposed operands, and
|
||||
multi-result ops are read through `Pick`. Fidget's tapes are scalar too.
|
||||
- **The graph is a class; its wire shape is a resolver beside `CultMathResolver`**
|
||||
(T4, T5). CultMath stays reference-free and the coverage tests stay as they
|
||||
are. No JSON shape in the first cut (JSON is debug and xenos export).
|
||||
- **Stencils are substitution at build time** (T7a), not a loop in the tape.
|
||||
Each stencil level multiplies the graph by its tap count, as B36 measured.
|
||||
- **`cycles-noise` r1 is withdrawn**: base `a7966142`, it waits on the
|
||||
`cycles-converters` merge and is not on this session's frontier. It is a line
|
||||
below.
|
||||
|
||||
### Questions (admitted in the mind)
|
||||
|
||||
- `tape-register-model`: the HLSL interpreter's register model. Gates the
|
||||
interpreter cut only.
|
||||
- `lattice-site-weight-source`: how a weighted lattice node gets a per-site
|
||||
weight (T7c). Gates fauna gap (1) only.
|
||||
|
||||
Prior art in hand: `F:\Projects\aetheria-material-path-prior-art.md` section 4
|
||||
(GPU interpreters: Fidget's crates, Dolphin ubershaders, Laine et al. on
|
||||
megakernel register pressure; no measured interpreter-versus-kernel timing was
|
||||
found) and section 6 (IR placement); Aetheria
|
||||
`docs/research/2026-10-14-trophic-dag-filaments-prior-art.md` (Voronoi,
|
||||
filaments). Neither covers a GPU tape interpreter's register storage (dynamic
|
||||
indexing of local arrays and scratch spill, groupshared memory, Fidget-wgpu's
|
||||
`tape_interpreter.wgsl`, Keeter's MPR 2020) or per-site weight fields in
|
||||
lattice Voronoi. Both questions lack that prior art; Self runs Eyes before
|
||||
putting them.
|
||||
|
||||
### Cut sequence (Pass 7; one line each, mapped when about to run)
|
||||
|
||||
Fauna's first cut, `fauna-trophic-habitat`, evaluates the habitat graph in C# at
|
||||
candidates. It needs `expr-ir` r2, gaps (2) and (4) with (3) under the strands,
|
||||
gap (1), and their ops registered (line 5). It does not need the interpreter or
|
||||
gap (5); its presenter does. The critical path is `cycles-converters` merge,
|
||||
then line 3 beside the ruling on `lattice-site-weight-source` and line 4, then
|
||||
line 5; line 1 runs now, off that path.
|
||||
|
||||
1. `expr-ir` r2 (spec admitted): the graph, builder with substitution, decode
|
||||
validation, wire shape, C# evaluator. Touches no file `cycles-converters`
|
||||
touches (new `Expr` folder, `GameCult.Caching.MessagePack`), so it runs on
|
||||
`origin/main` now and does not wait on that merge.
|
||||
2. (No sibling spec.) Gap (3) alone was the candidate. It and every node cut
|
||||
append GLSL fixture families, bump the mirror's compared count and edit
|
||||
`CultMath.hlsl`, and so does `cycles-converters` (`53f4c213`:
|
||||
`GlslMirrorTests.cs`, `HlslSourceCompatibilityTests.cs`, `glsl-parity.json`,
|
||||
`CultMath.hlsl`). Families append after the previous cut's, so these cuts
|
||||
map on the merged head, not beside the branch. Gap (3) folds into line 3,
|
||||
whose strand kernel is its only consumer.
|
||||
3. `trophic-nodes` (CultLib): gap (3), an `out`-free
|
||||
`distance_to_segment(float2, float2, float2)` in C# with its HLSL twin (T6;
|
||||
`point` is reserved in HLSL, so the parameter is `point_value`); gap (2), a
|
||||
weighted diagram over a site span (F1, F2, first-order border distance;
|
||||
multiplicative and power weights); and gap (4), the soft Gabriel gate
|
||||
(computed per tick on the CPU from a site span; the per-point strand kernel
|
||||
over the gated edge span has the HLSL twin), with the span convention of
|
||||
the model page. Waits on the `cycles-converters` merge and its map pass.
|
||||
4. `lattice-weighted-cellular` (CultLib): gap (1), 2D, `pcg3d` cells, zone
|
||||
time, smooth-min blend, a stated weight range sizing its window. Waits on
|
||||
question `lattice-site-weight-source`.
|
||||
5. `expr-trophic-ops` (CultLib): the span input kind and ops for 2-4 in the op
|
||||
table; a CultMath Unity tag for fauna. Waits on 1, 3 and 4. Fauna's first cut
|
||||
waits on this.
|
||||
6. `tape-interpreter` (CultLib): bytecode lowering, `CultMath.Tape.hlsl`, and
|
||||
the graph-parity harness (gap 5): C# evaluator against the interpreter on a
|
||||
graph corpus and on a consumer's graph, dxc compile plus a device run, held
|
||||
to tolerances measured per op under `cycles-kernel-reference-ruled`'s
|
||||
stance. Waits on 1 and `tape-register-model`. Fauna's presenter waits on it.
|
||||
7. `expr-cycles-ops`: `cycles_*` ops, after `cycles-converters` merges.
|
||||
8. `cycles-noise`, re-mapped from the withdrawn r1, after `cycles-converters`
|
||||
merges.
|
||||
9. Material line, when the material graph returns after the demo: cones,
|
||||
frontiers and an HLSL emitter over the same graph.
|
||||
10. `cultmath-unity-math-parity` (follow-up): square, exp2, exp10, log2, log10.
|
||||
11. Pruning (tape step 3) and meshing (step 5): parked as the target says.
|
||||
|
||||
## Pass 8: volumetric prioritized (ruling `volumetric-work-prioritized`)
|
||||
|
||||
Written by Imagination (agent `imagination-volumetric-frontier`, session
|
||||
`self-2026-10-14-eureka`) against CultLib `main` `dfc720e7`. The operator,
|
||||
2026-10-14, relayed by the Aetheria Self: "I want that volumetric work
|
||||
prioritized, since it's a totally different authoring framework for the nebulae
|
||||
and I'll need to build on it to get it up to par". The ruling names three pieces
|
||||
(the interval- and affine-culled march, the site ground, the integration-native
|
||||
noise domain) and leaves their order to this Self. Later the same day she pointed
|
||||
at "the last nebula playground" as having sophisticated noise. Facts on her two
|
||||
playgrounds were extracted by the Aetheria Self's Eyes into
|
||||
`F:\Projects\eureka-handoff-aux-2026-10-14\eyes-nebula-labs\facts.md`.
|
||||
|
||||
### The frontier as found
|
||||
|
||||
| Piece | Record | State |
|
||||
| --- | --- | --- |
|
||||
| Interval ops | `cut-interval-ops.r4`, reports h1-h4, Soul s1-s3 (s3 all Holds) | Merged to main (`fb567472`). Open Soul findings: `cut-interval-ops.s1.lod-weights-never-exercised` (Low), `cut-interval-ops.s1.shipped-void-two-scenes` (Medium, pre-existing; Pass 4 picked the one shipped void, but the finding was never closed), `cut-interval-ops.s3.lodstep-footprint-branch-unpinned` (Low). |
|
||||
| GLSL lowering | `cut-glsl-lowering.r2`, h1-h4, s1-s3 (s3 all Holds) | Merged (`bb583e89`). Follow-ups `iv-exp-gpu-widening`, `glsl-fixture-uncovered` and `glsl-browser-parity-run` are open. |
|
||||
| Affine forms and shaders | `cut-affine-forms.r2` (h1-h3, s1-s3), `cut-affine-shaders.r2` (h1-h3, s1-s3) | Merged (`156782da`). Soul's s3 on affine-forms has one Falsified claim and no open finding; Self carried it as follow-up `gate-detail-scale-sensitivity`. |
|
||||
| Snoise tie | `cut-snoise-tie.r1`, h1-h2, s1 (all Holds) | Merged (`48aadda0`). It re-pinned `SNOISE_LIPSCHITZ` from 10.099261 to 7.9640074 (Pass 6). |
|
||||
| Site ground | No document in the mind. The file-era spec `docs/cultmath-interval-ground-cut-site-ground.r3.spec.json` (repo `GameCult/gamecult-site`, base `535d170`) and the stardust spec | Never run. The gamecult-site `origin/main` `af6e206` has no ground shader and vendors no CultMath GLSL. Follow-ups `site-ground-affine-and-centred-warp`, `site-ground-dense-check-blind-past-body` and `wells-oracle-shortfall-affine-forms` wait on its next revision. No site-ground spec was ever admitted to the mind, so its first spec there is `cut-site-ground.r1`, whatever the file era numbered it. |
|
||||
| Noise domain | Ruling `integration-native-noise-domain`; `docs/research/integration-native-noise-domain.md` (`114bb4f3`, on main) | The probe ran on 2026-10-03. There is no cut. Its source (`DomainProbe/Program.cs`, scratch commit `5ec9f529`, never merged) survived only in a temp scratchpad. It is now copied, with its images and log, to `F:\Projects\eureka-handoff-aux-2026-10-14\imagination-volumetric-frontier\`. |
|
||||
| Operator's playgrounds | Artifacts "Kernel Nebula Lab" (`c09d2762`, version 1791066352) and "Nebula Span Lab" (`be6a9e31`, version 1791074878, the later one) | Facts file above. The Kernel Lab is the probe's kernel cascade in a page and has no noise function. The Span Lab is the "last nebula playground". |
|
||||
|
||||
No volumetric spec is in force without a report, so none is stale on an old
|
||||
base. Ruling `wells-overhead-plus-floor`, which the brief named as in force, is
|
||||
superseded by `affine-wells-contract-cheaper-than-interval`. The next spec does
|
||||
not touch (c), so it cites neither. Target `r1` is stale for this line: its
|
||||
`not_in_scope` still parks affine forms, and no invariant covers the noise
|
||||
domain. The target revision is Self's.
|
||||
|
||||
### The Span Lab is the framework
|
||||
|
||||
The Span Lab's density is a ramp, `ext · clamp((F − s)/B, 0, cap)`. Here `s` is a
|
||||
height pushed down by power brushes. It is then domain-warped twice by octave sums
|
||||
of `snoise`, each plain, ridged (`1 − 2|n|`) or curl, with an anisotropic stretch
|
||||
along a flow axis. The lab's own words: "The span integrator linearises the whole
|
||||
warp chain per span, bounds the error with a curvature bound, and integrates
|
||||
optical depth in closed form". Its bound is `0.5 K h²` from a hierarchical chain of
|
||||
speed, acceleration, Jacobian and second-derivative bounds. Its empty and saturated
|
||||
proofs use the same model, or the later warps' ranges alone.
|
||||
|
||||
That is ruling `integration-native-noise-domain`'s domain, "built for interval
|
||||
integration", made with the noise she likes. It is also the "totally different
|
||||
authoring framework" she will build on. The kernel cascade (the probe and the Kernel
|
||||
Lab) is the earlier exploration, and it stays a line below.
|
||||
|
||||
**Correction to the brief's expectation.** The next cut is not site-ground. The
|
||||
site ground's field is open: the Span Lab framework, the kernel cascade, or r3's
|
||||
marched void. The r3 machinery (the interval pre-pass, the centred warp, the
|
||||
debug-versus-dense readback) is shaped for a marched field. Question
|
||||
`site-ground-field` asks this.
|
||||
|
||||
### `SNOISE_LIPSCHITZ`: 10.099 in the lab, 7.964 in CultMath
|
||||
|
||||
- **Current: 7.9640074.** It is set in `math.Interval.cs:21`, `CultMath.Interval.hlsl`
|
||||
and the generated GLSL. `MeasureLipschitz` (seed 0x11B5) refined 7.2400064, and
|
||||
7.2400064 x 1.10 = 7.9640074. That measurement is on the tied kernel, re-pinned by
|
||||
`cut-snoise-tie` (Pass 6 table; Soul's `cut-snoise-tie.s1` all Holds).
|
||||
- **Stale: 10.099261.** The same measurement on the pre-tie kernel refined 9.181147.
|
||||
On that kernel snoise jumps by 0.21 to 0.72 inside a 1e-4 ball near lattice
|
||||
diagonals, so gradient ascent climbed a near-discontinuity. Pass 6 recorded 926
|
||||
`af_snoise` misses around that spike before the fix and 0 after.
|
||||
- **The lab embeds the pre-tie kernel.** Its `cultmath_snoise` uses
|
||||
`step(x0.yzx, x0.xyz)`, the 0.3.0 cyclic tie (`SPAN` line 244). CultMath's has the
|
||||
three-component Gustavson order (`CultMath.glsl:90`). Its pins
|
||||
`SN_L = 10.099261`, `SN_H = 56.050385` and `SN_T = 540` were measured on that
|
||||
kernel. `SN_T` came from the lab's own hill climb, 487.1 x 1.11.
|
||||
- So the lab's constant agrees with the lab's kernel.
|
||||
- But the kernel is discontinuous, so no derivative bound holds across the tie
|
||||
set. The lab's "proved" span bounds and its bound audit can miss there.
|
||||
- `SN_H` survived the tie fix unchanged (`MeasureHessian`, same witness). `SN_T`
|
||||
has never been measured on the tied kernel.
|
||||
- **Nothing the interval march or the site ground reads is stale.**
|
||||
- CultLib's march and every `iv_*` and `af_*` function read 7.9640074 in all three
|
||||
runtimes.
|
||||
- The site ground has no code.
|
||||
- The file-era specs and Passes 1-5 cite 10.099261 only as history (as-built
|
||||
section).
|
||||
- The stale copy lives only in the lab page. It is recorded as follow-up
|
||||
`span-lab-pre-tie-kernel`.
|
||||
|
||||
### What of the Span Lab belongs in CultMath (the noise domain)
|
||||
|
||||
One owner each, and the one rule each protects. CultMath owns noise primitives and
|
||||
their bounds. The consumer owns its composition: brushes, the ramp, which warps
|
||||
and in what order. The as-built negative "a flow or envelope function in CultMath"
|
||||
stands.
|
||||
|
||||
| Piece (lab) | Owner | Rule it protects | Line |
|
||||
| --- | --- | --- | --- |
|
||||
| `cultmath_snoise_gh`, the analytic Hessian (`makeGH`, string-spliced from the pre-tie grad kernel) | `math.snoise_hessian` in C#, HLSL and GLSL | One snoise kernel. Value, gradient and Hessian come from the same tie order, so no consumer splices its own derivative from an old kernel | Cut `snoise-hessian` (admitted) |
|
||||
| `SN_T`, the third-derivative tensor norm pin | `SNOISE_THIRD` beside `SNOISE_HESSIAN` | One measured pin per derivative order, re-measured with the kernel | Cut `snoise-hessian` |
|
||||
| Closed-form optical depth of a clamped linear ramp over a span (`compField` clamp split) | `math.integral_clamp_linear` | One closed form, with the clamp split at the exact crossings | Cut `snoise-hessian` (generic math; no fork) |
|
||||
| Curl of a three-potential octave sum (`dispVec` mode 2, `curlK`), with its Jacobian from the Hessian | A CultMath curl primitive | Divergence-free by construction. Its bounds come from `L` and `H` | Waits on the curl ruling (not ruled); needs `snoise_hessian` |
|
||||
| Warp-stage octave field: per-component offsets, per-octave offset, anisotropic stretch `Mop`, ridged `1 − 2|n|` with its fold deviation | CultMath stage functions returning value, Jacobian and span bounds | A stage's bounds compose from the noise pins by the chain rule; a consumer never re-derives them | Waits on question `span-chain-shape` |
|
||||
| Span evaluator: linearise a composed chain, the curvature bound `K`, the empty and saturated proofs | Per `span-chain-shape` | One composition rule for bounds | Waits on `span-chain-shape` |
|
||||
| Brushes, the ramp, `ampG`, `brushPoint`, the chain's order, the scroll animation | The consumer (site, Aetheria) | The authored field is the consumer's | Never CultMath |
|
||||
|
||||
`1 − 2|n|` is the affine image `2(1 − |n|) − 1` of `ridged_grad`'s octave term.
|
||||
Adding it as a variant is a naming choice inside the stage-function cut, not a fork.
|
||||
|
||||
### The noise domain and a curl node (not decided: no ruling)
|
||||
|
||||
The Aetheria Self is testing curl of simplex (`snoise_grad`) as a replacement for
|
||||
`Tri3D` flow noise. The Span Lab already has curl as a warp mode. The domain's
|
||||
owner would serve a curl node like this:
|
||||
|
||||
- It is one CultMath primitive: the curl of three offset `snoise` (or octave-sum)
|
||||
potentials from analytic gradients, with the `cultmath_` HLSL twin, the lowered
|
||||
GLSL and a fixture family. Aetheria's flow law and the nebula's warp both call
|
||||
it. Each consumer keeps its own composition and scaling: the lab's
|
||||
`curlK = 0.25/F`, or Aetheria's amplitude and scroll.
|
||||
- Its Jacobian, which the span evaluator and the kernel cascade's per-kernel affine
|
||||
warp both need, is a difference of Hessian entries. That is why `snoise-hessian`
|
||||
comes first, and why the curl node is not in it.
|
||||
- Bounds without new constants:
|
||||
- Each curl component is a difference of two partials, so its magnitude per
|
||||
octave is at most `2 L` times the amplitude.
|
||||
- Its variation over a ball is bounded through `SNOISE_HESSIAN`.
|
||||
- Its curvature, which the span bound needs, is bounded through `SNOISE_THIRD`.
|
||||
- The as-built line "a flow or envelope function in CultMath" was written against
|
||||
the site's flow law. A curl basis is a noise primitive like `snoise_grad`. If the
|
||||
curl node is ruled, the line needs that clarification, not a waiver.
|
||||
|
||||
### Kernel cascade (kept as a line)
|
||||
|
||||
The probe found the cascade exact, and 0.48x the shipped march's cost at 4.2x its
|
||||
msf on the void. But it "cannot be marched", its look is fibres, and its GPU cost
|
||||
is traversal, which is unmeasured. Optical depth through it is a sum of per-kernel
|
||||
segment integrals, so it is order-independent. Additive blending of each kernel's
|
||||
screen footprint would give exact transmittance in WebGL2, with no compute and no
|
||||
sorting. Emission is not order-independent. This is a hypothesis with no probe
|
||||
behind it. The cascade becomes a cut only if `site-ground-field` or the operator's
|
||||
nebula work picks it.
|
||||
|
||||
### Cut sequence (volumetric line)
|
||||
|
||||
1. `snoise-hessian` (spec admitted): `snoise_hessian`, `SNOISE_THIRD` re-measured
|
||||
on the tied kernel, `integral_clamp_linear`, in C#, HLSL and GLSL with fixture
|
||||
families. It touches no `expr-ir` file. Its provenance prose goes in code
|
||||
comments and the README, not `design.md`, which `expr-ir` edits.
|
||||
2. Question `span-chain-shape`, then the warp-stage and span-bound cut it picks.
|
||||
3. The curl primitive, once the operator rules on curl (the Aetheria flow test).
|
||||
4. Question `site-ground-field`, then `cut-site-ground.r1` on the field it picks.
|
||||
The marched option carries all three site follow-ups; the other options
|
||||
withdraw the two marched-only ones.
|
||||
5. The kernel cascade, if picked.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
# CultMath tapes: interval-pruned field evaluation on CPU and GPU
|
||||
|
||||
Status: steps 2-5 (tape, pruning, HLSL interpreter, meshing) are **parked**
|
||||
(2026-10-02; ruling tape-target-unparks-when-asura-stable). The affine-forms
|
||||
Status: steps 2 and 4 (graph, C# evaluator, HLSL interpreter) unparked on
|
||||
2026-10-14 (ruling tape-unparked-for-trophic-graphs, which supersedes
|
||||
tape-target-unparks-when-asura-stable); their model page and cut sequence are
|
||||
`docs/cultmath-interval-ground-cut.md`, "Pass 7". Steps 3 and 5 (pruning,
|
||||
meshing) stay parked. The affine-forms
|
||||
piece was unparked on 2026-10-03 (ruling affine-forms-unparked) and is on main
|
||||
(156782da), alongside step 1's intervals; its map and as-built record are in
|
||||
`docs/cultmath-interval-ground-cut.md`. This note records the direction for the
|
||||
|
|
|
|||
84
docs/research/lattice-site-weight.md
Normal file
84
docs/research/lattice-site-weight.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Facts for `cultmath-tapes:question:lattice-site-weight-source`
|
||||
|
||||
Agent eyes-lattice-site-weight, session self-2026-10-14-eureka, 2026-10-14. Facts only, no recommendation.
|
||||
Marks: [src] source code, [doc] repo doc, [web] web page, [abs] absence found, [probe] measured by the scripts beside this file.
|
||||
The eureka-state view of the question id failed (`view` wants a PipelineRef, not the id string); the question text was taken from the brief.
|
||||
|
||||
## 1. What the fauna probe did (Aetheria 4354ef2c, read via `git show`)
|
||||
|
||||
- [src] `docs/research/2026-10-14-trophic-dag-probe/trophic_probe.py` `lattice_web` (lines 95-119): per lattice cell `(cx,cz)` in a (2*WIN+1)^2 window (WIN=2): jittered site `(c+.5)*LAT` plus a drifting offset; weight `wt = (hash*.5+.5) * (1 + BOOST*prox)`, `prox = max over wells of exp(-(|site - well| / (2.2*.103*r))^2)`, BOOST 1.6; `d = hypot(...) * wt`; F1, F2 and a polynomial smooth-min `m` accumulated in loop order; output `max(soft^2, exp(-((f2-f1)/.10)^2))`. Wells orbit with zone time, so the site weight is a function of time.
|
||||
- [src] The weight is read at the site's lattice position `(cx+.5)*LAT`, not at the query point and not at the drifted site.
|
||||
- [doc] `docs/aetheria-fauna-map.md` B34: largest presence step for `lattice` .124 (8 steps over .1); "not discontinuities: a traced member's web value rises .03 per step for 5 steps, a thin weighted border sweeping a fixed candidate"; window 7x7 changes nothing; boost .6 removes the steps (.047); reading the lattice drift at the member's cycle start removes them at full boost (.011, `cyc.log`).
|
||||
- [doc] Map r4 gap (1): "a 2D weighted lattice cellular with the smooth-min blend (her node) on `pcg3d` cells and zone time"; "The search window follows the weight range ... The CultMath node states the weight range it supports" (map lines 438, 444-446).
|
||||
- [doc] Prior art doc `docs/research/2026-10-14-trophic-dag-filaments-prior-art.md` 1.1.7 [M]: power diagram evaluated pointwise by the same neighbour search "only if the search window is widened with the weight range"; marked "Neither derivation checked against a source".
|
||||
|
||||
## 2. Literature and implementations
|
||||
|
||||
- [web] Wikipedia, Weighted Voronoi diagram (https://en.wikipedia.org/wiki/Weighted_Voronoi_diagram): multiplicative = distance divided by positive site weight (circular Dirichlet tessellation, edges circular arcs and line segments); additive = weight subtracted from distance (hyperbolic Dirichlet tessellation, hyperbola arcs and segments); power diagram = weight subtracted from squared distance. A multiplicatively weighted cell may be non-convex, disconnected or hold holes. The page says nothing on continuity.
|
||||
- [web] Sangil Lee, "Understanding the Variations of Cellular Noise" (https://sangillee.com/_posts/2025-04-18-Cellular-noises/): weighted Voronoi as effective distance `(1/w)*|x - seed|` with `w = 0.2 + 0.8 * random3(cell).z`, i.e. the per-site weight is a component of the hash of the lattice cell id, computed inside the noise function; the weighted example loops 5x5 instead of 3x3 (no reason given).
|
||||
- [web] Inigo Quilez, Voronoise (https://iquilezles.org/articles/voronoise/): `hash3(cell id)` yields jitter in `.xy` and a per-cell value in `.z`; 5x5 window; notes `min()` is discontinuous and the smooth blend fixes that. No weighted variant.
|
||||
- [web] Worley noise, Wikipedia and The Book of Shaders (https://thebookofshaders.com/12/): the standard is current cell plus neighbours (3x3), with a wider pass for exact borders; Worley's original used a variable number of points per cell. Nothing found on per-point weight attributes beyond the Lee article; the continuity guidance in the search results was general-knowledge reasoning, not sourced.
|
||||
- [abs] No source found stating a continuity theorem for weighted Worley; no source found for a node that takes its per-site weight from an external field function. The only per-site weight mechanism found in shader writing is the hash of the cell id.
|
||||
- [probe] Continuity as observed (section 4): F1 stays continuous as long as the window contains the true winner; failure occurs when it does not.
|
||||
|
||||
## 3. CultMath today
|
||||
|
||||
- [src] `F:\Projects\CultLib\packages\cultmath\src\CultMath\math.cs:909` `public static CultCellular cellular(float3 p)`: one argument, 3D, unweighted; sites `neighbor + jitter` with jitter from `pcg3d(int3(neighbor))` through `cellular_unit`; fixed 5x5x5 window with a per-offset lower-bound prune (`max(0,|d|-1)`), whose exactness proof (comment above the function) relies on the jitter being in [0,1) and on equal weights; returns `CultCellular { nearest = (grad F1, F1), edge = (grad(F2-F1), F2-F1), id = pcg4d of the winning cell mapped to [0,1) }` (`CultCellular.cs`). Hash is of the integer cell, never the float bits.
|
||||
- [src] `math.cs:831-847` `smin_grad(float4 a, float4 b, float k)`: quadratic smooth-min with analytic gradient (value-and-gradient form).
|
||||
- [src] `Voronoi.cs` (`Voronoi.SampleTones`, native `cultmath_apollonian_voronoi_tones`) is a UI-tone colour sampler over a hash-jittered field with a hard-coded 5x5 loop; takes x, y, tone, span; not a weighted-cell function.
|
||||
- [abs] No function in CultMath takes a weight, a callback, or a site span; no weighted cellular, no per-site hash output exposed besides `id`. (Map B28 states the same.) HLSL twin at `shaders/CultMath.hlsl:440-503`.
|
||||
- [doc] `packages/cultmath/docs/design.md` ~265-340: `cellular` section, search-radius proof and pruned/unpruned test pin.
|
||||
- Fact for composition: `CultCellular.id` is already a per-cell hash scalar available to the caller after the call, but only for the winning cell, after F1/F2 are decided.
|
||||
|
||||
## 4. Probe: where does the weight come from, 2D, 4 orbiting wells
|
||||
|
||||
Scripts: `siteweight_probe.py`, `siteweight_probe2.py`; raw numbers `probe_results.json`, `probe_results2.json`. numpy, scratch; hash is a lowbias32-style integer hash (stand-in for pcg3d). Units: lattice cell = 1; domain [1,7]^2, 500x500 grid (600 for shape rows); wells with radii .6-1.2 cells orbiting a centre; weight `wt = base(.5..1) * (1 + B*prox)`, B = 1.6, WIN = 2, the fauna probe's smooth-min and border output.
|
||||
|
||||
Correction (2026-10-14, from the Aetheria Self's body-Voronoi probe, Aetheria 05a44541): the fauna probe's `trophic_probe.py` weights at the cell centre `(cx+.5)*LAT` (section 1 above), which is the `cellc` variant below, not `site`. An earlier label here called `site` the fauna probe's method.
|
||||
|
||||
Variants: `base` (no field); `site` (field at the jittered site position); `qpt` (field at the query point, same factor on every candidate); `cellc` (node calls the field at the integer cell centre, `cell + .5`, no jitter); `site_t0` (as `site` but field read at t=0 while wells have moved); `warp` (extra: query scaled `q*(1+B*prox(q))`).
|
||||
|
||||
### 4a. Winner identity, fraction of grid pixels whose winning cell differs (t=0 / t=8)
|
||||
|
||||
| variant | vs `site` | vs `base` |
|
||||
|---|---|---|
|
||||
| base | .147 / .156 | 0 / 0 |
|
||||
| qpt | .147 / .156 | 0 / 0 (identical argmin to base) |
|
||||
| cellc | .127 / .114 | .185 / .147 |
|
||||
| site_t0 | 0 / .226 | .147 / .147 |
|
||||
| warp | .675 / .646 | .668 / .639 |
|
||||
| site | 0 / 0 | .147 / .156 |
|
||||
|
||||
Mean absolute difference of the border output vs `site`: base .169/.186, qpt .168/.180, cellc .164/.156, site_t0 0/.234, warp .310/.280. Number of distinct cells on the grid: 53 for base, site and qpt; 54 cellc; 143/128 warp.
|
||||
|
||||
[probe] `qpt` leaves every winner unchanged because the factor is common to all candidates; it changes F1, F2 and the smooth-min scale, which moves the border line (border pixel fraction near wells .164 vs .300 for base, far .250 vs .281).
|
||||
|
||||
### 4b. Cell size and border near wells (pixels per cell, mean; "near" = within 1 well radius), t=0
|
||||
|
||||
| | near | far | border fraction near / far |
|
||||
|---|---|---|---|
|
||||
| base | 10951 | 10515 | .300 / .281 |
|
||||
| site | 9672 | 11439 | .270 / .253 |
|
||||
| qpt | 10951 | 10515 | .164 / .250 |
|
||||
| cellc | 11562 | 12274 | .274 / .262 |
|
||||
| warp | 3447 | 8566 | .291 / .302 |
|
||||
|
||||
### 4c. Pop measure, dense spatial line (6001 points over 6 cells, step 1e-3 cell), max |step| of F1 and of output, window sweep vs window 6 reference
|
||||
|
||||
- With the true winner inside the window, every variant's max F1 step is small (site .0010-.0033, qpt .0019-.0079, cellc .0009-.0041, warp .0054-.022, base .0007-.0013 per 1e-3).
|
||||
- WIN=1 (3x3): `site` F1 error vs window 6 up to .174 (diagonal line, B=1.6; max output step .95); `cellc` .155-.194; `base` .0065; `qpt` .0092-.0113; `warp` none seen. At WIN=2 and B=1.6 all variants' F1 error is 0.
|
||||
- Weight range: `site` at B=6 (weight ratio about 12:1), t=8, horizontal line: WIN=2 error .0169 (output step .371); WIN=3 error 0.
|
||||
- Note: even `base` (weights .5-1, ratio 2) shows F1 error .0065 at WIN=1.
|
||||
|
||||
### 4d. Pop measure, time at fixed query points (wells move), step refinement (worst point from a dt=.01 scan, refined)
|
||||
|
||||
| variant | dt .01 max output step | dt 1e-3 | dt 1e-4 | dt 1e-5 | max F1 step at dt 1e-5 |
|
||||
|---|---|---|---|---|---|
|
||||
| site | .1083 | .01125 | .00113 | .000113 | 2.9e-6 |
|
||||
| qpt | .00461 | .000461 | 4.6e-5 | 4.6e-6 | 4.1e-6 |
|
||||
| cellc | .1060 | .0204 | .00206 | .000207 | 5.2e-6 |
|
||||
|
||||
[probe] All three steps scale linearly with dt: no discontinuity in time for any variant when the window holds the winner. Winner-id changes over t in [0,20] at 4 query points (dt .01): base 0, qpt 0, site 17, cellc 15, site_t0 0.
|
||||
|
||||
### 4e. Caveats of the probe
|
||||
Static smooth proximity field, 4 wells, 2D, one seed of the hash; 3 boost values in the pop table only B=1.6 and 6.0; no gradient-bearing outputs measured; `cellc` field is sampled at the cell centre, so it differs from `site` by the jitter distance; no visual images rendered, only the numeric measures above. The `warp` variant is an addition outside the three asked and is reported for completeness.
|
||||
559
docs/research/lattice-site-weight/probe_results.json
Normal file
559
docs/research/lattice-site-weight/probe_results.json
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
{
|
||||
"grid": {
|
||||
"t=0.0": {
|
||||
"base": {
|
||||
"winner_differs_from_site": 0.14684,
|
||||
"winner_differs_from_base": 0.0,
|
||||
"out_absdiff_vs_site_mean": 0.1692938271424024,
|
||||
"out_absdiff_vs_site_max": 0.9999999999999667,
|
||||
"n_cells": 53
|
||||
},
|
||||
"site": {
|
||||
"winner_differs_from_site": 0.0,
|
||||
"winner_differs_from_base": 0.14684,
|
||||
"out_absdiff_vs_site_mean": 0.0,
|
||||
"out_absdiff_vs_site_max": 0.0,
|
||||
"n_cells": 53
|
||||
},
|
||||
"qpt": {
|
||||
"winner_differs_from_site": 0.14684,
|
||||
"winner_differs_from_base": 0.0,
|
||||
"out_absdiff_vs_site_mean": 0.1679024745847925,
|
||||
"out_absdiff_vs_site_max": 1.0,
|
||||
"n_cells": 53
|
||||
},
|
||||
"cellc": {
|
||||
"winner_differs_from_site": 0.126984,
|
||||
"winner_differs_from_base": 0.184648,
|
||||
"out_absdiff_vs_site_mean": 0.16449204766510442,
|
||||
"out_absdiff_vs_site_max": 0.99999757371696,
|
||||
"n_cells": 54
|
||||
},
|
||||
"warp": {
|
||||
"winner_differs_from_site": 0.6748,
|
||||
"winner_differs_from_base": 0.667952,
|
||||
"out_absdiff_vs_site_mean": 0.31037303493350155,
|
||||
"out_absdiff_vs_site_max": 1.0,
|
||||
"n_cells": 143
|
||||
},
|
||||
"site_t0": {
|
||||
"winner_differs_from_site": 0.0,
|
||||
"winner_differs_from_base": 0.14684,
|
||||
"out_absdiff_vs_site_mean": 0.0,
|
||||
"out_absdiff_vs_site_max": 0.0,
|
||||
"n_cells": 53
|
||||
},
|
||||
"site_window2_vs_window5_winner_differs": 0.0
|
||||
},
|
||||
"t=8.0": {
|
||||
"base": {
|
||||
"winner_differs_from_site": 0.155996,
|
||||
"winner_differs_from_base": 0.0,
|
||||
"out_absdiff_vs_site_mean": 0.18560227222422834,
|
||||
"out_absdiff_vs_site_max": 0.9999999025356109,
|
||||
"n_cells": 53
|
||||
},
|
||||
"site": {
|
||||
"winner_differs_from_site": 0.0,
|
||||
"winner_differs_from_base": 0.155996,
|
||||
"out_absdiff_vs_site_mean": 0.0,
|
||||
"out_absdiff_vs_site_max": 0.0,
|
||||
"n_cells": 53
|
||||
},
|
||||
"qpt": {
|
||||
"winner_differs_from_site": 0.155996,
|
||||
"winner_differs_from_base": 0.0,
|
||||
"out_absdiff_vs_site_mean": 0.17977625463968147,
|
||||
"out_absdiff_vs_site_max": 0.999999999999996,
|
||||
"n_cells": 53
|
||||
},
|
||||
"cellc": {
|
||||
"winner_differs_from_site": 0.114268,
|
||||
"winner_differs_from_base": 0.146552,
|
||||
"out_absdiff_vs_site_mean": 0.1556440909167879,
|
||||
"out_absdiff_vs_site_max": 0.9999919751523085,
|
||||
"n_cells": 54
|
||||
},
|
||||
"warp": {
|
||||
"winner_differs_from_site": 0.646396,
|
||||
"winner_differs_from_base": 0.639384,
|
||||
"out_absdiff_vs_site_mean": 0.2802002446118991,
|
||||
"out_absdiff_vs_site_max": 1.0,
|
||||
"n_cells": 128
|
||||
},
|
||||
"site_t0": {
|
||||
"winner_differs_from_site": 0.226044,
|
||||
"winner_differs_from_base": 0.14684,
|
||||
"out_absdiff_vs_site_mean": 0.23360220832359216,
|
||||
"out_absdiff_vs_site_max": 1.0,
|
||||
"n_cells": 53
|
||||
},
|
||||
"site_window2_vs_window5_winner_differs": 0.0
|
||||
}
|
||||
},
|
||||
"line_pop": {
|
||||
"horizontal z=2.6 (near well 0)": {
|
||||
"base t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.000679179751586656,
|
||||
"maxjump_out_per_step": 0.011527150729375713,
|
||||
"maxjump_f1_with_window6": 0.000679179751586656,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"base t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.000679179751586656,
|
||||
"maxjump_out_per_step": 0.011527150729375713,
|
||||
"maxjump_f1_with_window6": 0.000679179751586656,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0010050426622043585,
|
||||
"maxjump_out_per_step": 0.016002826669016756,
|
||||
"maxjump_f1_with_window6": 0.0010050426622043585,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.0010050426622043585,
|
||||
"maxjump_out_per_step": 0.016002826669016756,
|
||||
"maxjump_f1_with_window6": 0.0010050426622043585,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=0.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.001918579519211816,
|
||||
"maxjump_out_per_step": 0.03595224397909913,
|
||||
"maxjump_f1_with_window6": 0.001918579519211816,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=0.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.001918579519211816,
|
||||
"maxjump_out_per_step": 0.03595224397909913,
|
||||
"maxjump_f1_with_window6": 0.001918579519211816,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0019043178713147313,
|
||||
"maxjump_out_per_step": 0.016796471326461004,
|
||||
"maxjump_f1_with_window6": 0.0019043178713147313,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.0019043178713147313,
|
||||
"maxjump_out_per_step": 0.016796471326461004,
|
||||
"maxjump_f1_with_window6": 0.0019043178713147313,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=0.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0056772408954191,
|
||||
"maxjump_out_per_step": 0.04338667429132481,
|
||||
"maxjump_f1_with_window6": 0.0056772408954191,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=0.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.0056772408954191,
|
||||
"maxjump_out_per_step": 0.04338667429132481,
|
||||
"maxjump_f1_with_window6": 0.0056772408954191,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.000899555983544964,
|
||||
"maxjump_out_per_step": 0.028931107704223913,
|
||||
"maxjump_f1_with_window6": 0.000899555983544964,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.000899555983544964,
|
||||
"maxjump_out_per_step": 0.028998803253634242,
|
||||
"maxjump_f1_with_window6": 0.000899555983544964,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=0.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.001589550701590614,
|
||||
"maxjump_out_per_step": 0.02212547196092063,
|
||||
"maxjump_f1_with_window6": 0.001589550701590614,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=0.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.001589550701590614,
|
||||
"maxjump_out_per_step": 0.02212547196092063,
|
||||
"maxjump_f1_with_window6": 0.001589550701590614,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.005413713480415305,
|
||||
"maxjump_out_per_step": 0.11861272322439464,
|
||||
"maxjump_f1_with_window6": 0.005413713480415305,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.005413713480415305,
|
||||
"maxjump_out_per_step": 0.6979479036971218,
|
||||
"maxjump_f1_with_window6": 0.005413713480415305,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=0.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.019199908363967788,
|
||||
"maxjump_out_per_step": 0.28876383821796175,
|
||||
"maxjump_f1_with_window6": 0.019199908363967788,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=0.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.019199908363967788,
|
||||
"maxjump_out_per_step": 0.28876383821796175,
|
||||
"maxjump_f1_with_window6": 0.019199908363967788,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"base t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.000679179751586656,
|
||||
"maxjump_out_per_step": 0.011527150729375713,
|
||||
"maxjump_f1_with_window6": 0.000679179751586656,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"base t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.000679179751586656,
|
||||
"maxjump_out_per_step": 0.011527150729375713,
|
||||
"maxjump_f1_with_window6": 0.000679179751586656,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0011605444949153165,
|
||||
"maxjump_out_per_step": 0.03721603977227228,
|
||||
"maxjump_f1_with_window6": 0.0011605444949153165,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.0011605444949153165,
|
||||
"maxjump_out_per_step": 0.037218919278060314,
|
||||
"maxjump_f1_with_window6": 0.0011605444949153165,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=8.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.01726416929635244,
|
||||
"maxjump_out_per_step": 0.37104796823449204,
|
||||
"maxjump_f1_with_window6": 0.0024839789447830274,
|
||||
"f1_err_vs_win6": 0.016881040623005106
|
||||
},
|
||||
"site t=8.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.0024839789447830274,
|
||||
"maxjump_out_per_step": 0.045046050707719165,
|
||||
"maxjump_f1_with_window6": 0.0024839789447830274,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0019988841823728176,
|
||||
"maxjump_out_per_step": 0.01873902874992317,
|
||||
"maxjump_f1_with_window6": 0.0019988841823728176,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.0019988841823728176,
|
||||
"maxjump_out_per_step": 0.01873902874992317,
|
||||
"maxjump_f1_with_window6": 0.0019988841823728176,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=8.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.005628071367035137,
|
||||
"maxjump_out_per_step": 0.04897917252830741,
|
||||
"maxjump_f1_with_window6": 0.005628071367035137,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=8.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.005628071367035137,
|
||||
"maxjump_out_per_step": 0.04897917252830741,
|
||||
"maxjump_f1_with_window6": 0.005628071367035137,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0013893624582810293,
|
||||
"maxjump_out_per_step": 0.0186819346008138,
|
||||
"maxjump_f1_with_window6": 0.0013893624582810293,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.0013893624582810293,
|
||||
"maxjump_out_per_step": 0.02079886198517822,
|
||||
"maxjump_f1_with_window6": 0.0013893624582810293,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=8.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0033405760083177194,
|
||||
"maxjump_out_per_step": 0.07493559630083158,
|
||||
"maxjump_f1_with_window6": 0.0033405760083177194,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=8.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.0033405760083177194,
|
||||
"maxjump_out_per_step": 0.07493559630083158,
|
||||
"maxjump_f1_with_window6": 0.0033405760083177194,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.005131686955684001,
|
||||
"maxjump_out_per_step": 0.10245451309831222,
|
||||
"maxjump_f1_with_window6": 0.005131686955684001,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.005131686955684001,
|
||||
"maxjump_out_per_step": 0.5048292489051344,
|
||||
"maxjump_f1_with_window6": 0.005131686955684001,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=8.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.01955931573768127,
|
||||
"maxjump_out_per_step": 0.2975581851502386,
|
||||
"maxjump_f1_with_window6": 0.01955931573768127,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=8.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.01955931573768127,
|
||||
"maxjump_out_per_step": 0.2975581851502386,
|
||||
"maxjump_f1_with_window6": 0.01955931573768127,
|
||||
"f1_err_vs_win6": 0.0
|
||||
}
|
||||
},
|
||||
"diagonal": {
|
||||
"base t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.001275910800458191,
|
||||
"maxjump_out_per_step": 0.03528728288006466,
|
||||
"maxjump_f1_with_window6": 0.001275910800458191,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"base t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.006834249882349042,
|
||||
"maxjump_out_per_step": 0.45027124784270633,
|
||||
"maxjump_f1_with_window6": 0.001275910800458191,
|
||||
"f1_err_vs_win6": 0.006469535309243879
|
||||
},
|
||||
"site t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0018160559768694018,
|
||||
"maxjump_out_per_step": 0.036994428279204405,
|
||||
"maxjump_f1_with_window6": 0.0018160559768694018,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.17389687729083692,
|
||||
"maxjump_out_per_step": 0.9512571305077048,
|
||||
"maxjump_f1_with_window6": 0.0018160559768694018,
|
||||
"f1_err_vs_win6": 0.17352236500589702
|
||||
},
|
||||
"site t=0.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0032951968389483,
|
||||
"maxjump_out_per_step": 0.08101849084444412,
|
||||
"maxjump_f1_with_window6": 0.0032951968389483,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=0.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.0032951968389483,
|
||||
"maxjump_out_per_step": 0.08049836455119364,
|
||||
"maxjump_f1_with_window6": 0.0032951968389483,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0026472302788482782,
|
||||
"maxjump_out_per_step": 0.03868824720262809,
|
||||
"maxjump_f1_with_window6": 0.0026472302788482782,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.010707146916472454,
|
||||
"maxjump_out_per_step": 0.21289370234612265,
|
||||
"maxjump_f1_with_window6": 0.0026472302788482782,
|
||||
"f1_err_vs_win6": 0.009199392603544854
|
||||
},
|
||||
"qpt t=0.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.007883634786070015,
|
||||
"maxjump_out_per_step": 0.07213687421025916,
|
||||
"maxjump_f1_with_window6": 0.007883634786070015,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=0.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.007883634786070015,
|
||||
"maxjump_out_per_step": 0.07213687421025916,
|
||||
"maxjump_f1_with_window6": 0.007883634786070015,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0021042254469000876,
|
||||
"maxjump_out_per_step": 0.03605934023336077,
|
||||
"maxjump_f1_with_window6": 0.0021042254469000876,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.15454958335751312,
|
||||
"maxjump_out_per_step": 0.47361732317771077,
|
||||
"maxjump_f1_with_window6": 0.0021042254469000876,
|
||||
"f1_err_vs_win6": 0.15418392181872642
|
||||
},
|
||||
"cellc t=0.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.004089971897996092,
|
||||
"maxjump_out_per_step": 0.04544931913493744,
|
||||
"maxjump_f1_with_window6": 0.004089971897996092,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=0.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.004089971897996092,
|
||||
"maxjump_out_per_step": 0.04544931913493744,
|
||||
"maxjump_f1_with_window6": 0.004089971897996092,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=0.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.007786414559647126,
|
||||
"maxjump_out_per_step": 0.10615772398185219,
|
||||
"maxjump_f1_with_window6": 0.007786414559647126,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=0.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.007786414559647126,
|
||||
"maxjump_out_per_step": 0.10615772398185219,
|
||||
"maxjump_f1_with_window6": 0.007786414559647126,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=0.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0220788526153487,
|
||||
"maxjump_out_per_step": 0.2726388272791984,
|
||||
"maxjump_f1_with_window6": 0.0220788526153487,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=0.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.0220788526153487,
|
||||
"maxjump_out_per_step": 0.2726388272791984,
|
||||
"maxjump_f1_with_window6": 0.0220788526153487,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"base t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.001275910800458191,
|
||||
"maxjump_out_per_step": 0.03528728288006466,
|
||||
"maxjump_f1_with_window6": 0.001275910800458191,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"base t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.006834249882349042,
|
||||
"maxjump_out_per_step": 0.45027124784270633,
|
||||
"maxjump_f1_with_window6": 0.001275910800458191,
|
||||
"f1_err_vs_win6": 0.006469535309243879
|
||||
},
|
||||
"site t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.00155291340314212,
|
||||
"maxjump_out_per_step": 0.04226739191047357,
|
||||
"maxjump_f1_with_window6": 0.00155291340314212,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.11344119297302535,
|
||||
"maxjump_out_per_step": 0.3917028634066005,
|
||||
"maxjump_f1_with_window6": 0.00155291340314212,
|
||||
"f1_err_vs_win6": 0.11285505036112697
|
||||
},
|
||||
"site t=8.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.002453426564246408,
|
||||
"maxjump_out_per_step": 0.03688685308461992,
|
||||
"maxjump_f1_with_window6": 0.002453426564246408,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"site t=8.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.002453426564246408,
|
||||
"maxjump_out_per_step": 0.03688685308461992,
|
||||
"maxjump_f1_with_window6": 0.002453426564246408,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0018171966866530331,
|
||||
"maxjump_out_per_step": 0.03560130205583023,
|
||||
"maxjump_f1_with_window6": 0.0018171966866530331,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.011573594040749535,
|
||||
"maxjump_out_per_step": 0.44945714067888365,
|
||||
"maxjump_f1_with_window6": 0.0018171966866530331,
|
||||
"f1_err_vs_win6": 0.011263134181283219
|
||||
},
|
||||
"qpt t=8.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.004178672365624969,
|
||||
"maxjump_out_per_step": 0.06700822884555246,
|
||||
"maxjump_f1_with_window6": 0.004178672365624969,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"qpt t=8.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.004178672365624969,
|
||||
"maxjump_out_per_step": 0.06700822884555246,
|
||||
"maxjump_f1_with_window6": 0.004178672365624969,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0013657606892926166,
|
||||
"maxjump_out_per_step": 0.03848639655536479,
|
||||
"maxjump_f1_with_window6": 0.0013657606892926166,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.19448286979973972,
|
||||
"maxjump_out_per_step": 0.9778972151563999,
|
||||
"maxjump_f1_with_window6": 0.0013657606892926166,
|
||||
"f1_err_vs_win6": 0.1940193666886757
|
||||
},
|
||||
"cellc t=8.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.0016119831862634326,
|
||||
"maxjump_out_per_step": 0.05348368104496215,
|
||||
"maxjump_f1_with_window6": 0.0016119831862634326,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"cellc t=8.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.0016119831862634326,
|
||||
"maxjump_out_per_step": 0.05348368104496215,
|
||||
"maxjump_f1_with_window6": 0.0016119831862634326,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=8.0 B=1.6 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.00389655575000758,
|
||||
"maxjump_out_per_step": 0.06278112824237458,
|
||||
"maxjump_f1_with_window6": 0.00389655575000758,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=8.0 B=1.6 WIN=1": {
|
||||
"maxjump_f1_per_step": 0.00389655575000758,
|
||||
"maxjump_out_per_step": 0.4429137542853424,
|
||||
"maxjump_f1_with_window6": 0.00389655575000758,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=8.0 B=6.0 WIN=2": {
|
||||
"maxjump_f1_per_step": 0.012386023943556368,
|
||||
"maxjump_out_per_step": 0.1972931166165931,
|
||||
"maxjump_f1_with_window6": 0.012386023943556368,
|
||||
"f1_err_vs_win6": 0.0
|
||||
},
|
||||
"warp t=8.0 B=6.0 WIN=3": {
|
||||
"maxjump_f1_per_step": 0.012386023943556368,
|
||||
"maxjump_out_per_step": 0.1972931166165931,
|
||||
"maxjump_f1_with_window6": 0.012386023943556368,
|
||||
"f1_err_vs_win6": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"time_pop": {
|
||||
"base": {
|
||||
"maxjump_f1_per_dt": 0.0,
|
||||
"maxjump_out_per_dt": 0.0,
|
||||
"winner_changes_total": 0
|
||||
},
|
||||
"site": {
|
||||
"maxjump_f1_per_dt": 0.006243969970252006,
|
||||
"maxjump_out_per_dt": 0.10827554277384255,
|
||||
"winner_changes_total": 17
|
||||
},
|
||||
"qpt": {
|
||||
"maxjump_f1_per_dt": 0.004880867091880958,
|
||||
"maxjump_out_per_dt": 0.004611044852681684,
|
||||
"winner_changes_total": 0
|
||||
},
|
||||
"cellc": {
|
||||
"maxjump_f1_per_dt": 0.006179970448791816,
|
||||
"maxjump_out_per_dt": 0.10604425529368433,
|
||||
"winner_changes_total": 15
|
||||
},
|
||||
"site_t0": {
|
||||
"maxjump_f1_per_dt": 0.0,
|
||||
"maxjump_out_per_dt": 0.0,
|
||||
"winner_changes_total": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
140
docs/research/lattice-site-weight/probe_results2.json
Normal file
140
docs/research/lattice-site-weight/probe_results2.json
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"timejump site dt=0.01": {
|
||||
"q": [
|
||||
2.3,
|
||||
2.8
|
||||
],
|
||||
"t_worst": 9.91,
|
||||
"max_out_step": 0.10827554277384255,
|
||||
"max_f1_step": 0.0028572323710521763
|
||||
},
|
||||
"timejump site dt=0.001": {
|
||||
"q": [
|
||||
2.3,
|
||||
2.8
|
||||
],
|
||||
"t_worst": 9.91,
|
||||
"max_out_step": 0.011248851985623975,
|
||||
"max_f1_step": 0.00028847749905558384
|
||||
},
|
||||
"timejump site dt=0.0001": {
|
||||
"q": [
|
||||
2.3,
|
||||
2.8
|
||||
],
|
||||
"t_worst": 9.91,
|
||||
"max_out_step": 0.00113067110268672,
|
||||
"max_f1_step": 2.8874698390524145e-05
|
||||
},
|
||||
"timejump site dt=1e-05": {
|
||||
"q": [
|
||||
2.3,
|
||||
2.8
|
||||
],
|
||||
"t_worst": 9.91,
|
||||
"max_out_step": 0.00011316362556801352,
|
||||
"max_f1_step": 2.8877387294734547e-06
|
||||
},
|
||||
"timejump qpt dt=0.01": {
|
||||
"q": [
|
||||
5.9,
|
||||
1.9
|
||||
],
|
||||
"t_worst": 16.21,
|
||||
"max_out_step": 0.00461104485268235,
|
||||
"max_f1_step": 0.004042127425204045
|
||||
},
|
||||
"timejump qpt dt=0.001": {
|
||||
"q": [
|
||||
5.9,
|
||||
1.9
|
||||
],
|
||||
"t_worst": 16.21,
|
||||
"max_out_step": 0.0004611240505579539,
|
||||
"max_f1_step": 0.000406360115853599
|
||||
},
|
||||
"timejump qpt dt=0.0001": {
|
||||
"q": [
|
||||
5.9,
|
||||
1.9
|
||||
],
|
||||
"t_worst": 16.21,
|
||||
"max_out_step": 4.611242486959877e-05,
|
||||
"max_f1_step": 4.065726355373389e-05
|
||||
},
|
||||
"timejump qpt dt=1e-05": {
|
||||
"q": [
|
||||
5.9,
|
||||
1.9
|
||||
],
|
||||
"t_worst": 16.21,
|
||||
"max_out_step": 4.611242504171109e-06,
|
||||
"max_f1_step": 4.065938649877587e-06
|
||||
},
|
||||
"timejump cellc dt=0.01": {
|
||||
"q": [
|
||||
5.9,
|
||||
1.9
|
||||
],
|
||||
"t_worst": 0.39,
|
||||
"max_out_step": 0.10604425529368433,
|
||||
"max_f1_step": 0.005110972264514513
|
||||
},
|
||||
"timejump cellc dt=0.001": {
|
||||
"q": [
|
||||
5.9,
|
||||
1.9
|
||||
],
|
||||
"t_worst": 0.39,
|
||||
"max_out_step": 0.020376801353283347,
|
||||
"max_f1_step": 0.0005172341605852449
|
||||
},
|
||||
"timejump cellc dt=0.0001": {
|
||||
"q": [
|
||||
5.9,
|
||||
1.9
|
||||
],
|
||||
"t_worst": 0.39,
|
||||
"max_out_step": 0.002064834070920063,
|
||||
"max_f1_step": 5.178482072565327e-05
|
||||
},
|
||||
"timejump cellc dt=1e-05": {
|
||||
"q": [
|
||||
5.9,
|
||||
1.9
|
||||
],
|
||||
"t_worst": 0.39,
|
||||
"max_out_step": 0.00020688477103125713,
|
||||
"max_f1_step": 5.179096147611695e-06
|
||||
},
|
||||
"shape base": {
|
||||
"mean_cell_pixels_near_wells": 10951.06215310558,
|
||||
"mean_cell_pixels_far": 10515.08654105133,
|
||||
"border_frac_near": 0.3004222385907398,
|
||||
"border_frac_far": 0.28125746844758714
|
||||
},
|
||||
"shape site": {
|
||||
"mean_cell_pixels_near_wells": 9671.733834071645,
|
||||
"mean_cell_pixels_far": 11438.643308839986,
|
||||
"border_frac_near": 0.2704401863244494,
|
||||
"border_frac_far": 0.25280623954417336
|
||||
},
|
||||
"shape qpt": {
|
||||
"mean_cell_pixels_near_wells": 10951.06215310558,
|
||||
"mean_cell_pixels_far": 10515.08654105133,
|
||||
"border_frac_near": 0.1644136900747995,
|
||||
"border_frac_far": 0.2500635410778843
|
||||
},
|
||||
"shape cellc": {
|
||||
"mean_cell_pixels_near_wells": 11562.292900789493,
|
||||
"mean_cell_pixels_far": 12273.640744435888,
|
||||
"border_frac_near": 0.27424759572989177,
|
||||
"border_frac_far": 0.2618651108270203
|
||||
},
|
||||
"shape warp": {
|
||||
"mean_cell_pixels_near_wells": 3446.9220259152826,
|
||||
"mean_cell_pixels_far": 8566.10048594699,
|
||||
"border_frac_near": 0.29084665580811486,
|
||||
"border_frac_far": 0.3016702768114897
|
||||
}
|
||||
}
|
||||
123
docs/research/lattice-site-weight/siteweight_probe.py
Normal file
123
docs/research/lattice-site-weight/siteweight_probe.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
"""Scratch probe: where does a weighted lattice Voronoi cell get its weight?
|
||||
2D, lattice cell = 1 unit, wells with a proximity field, numpy only.
|
||||
Variants of the per-site weight wt_i (multiplies the distance to site i, as the fauna probe's lattice_web):
|
||||
base : wt = hashed .5..1, no field (reference, the unweighted-by-field node)
|
||||
site : wt = base*(1+B*prox(site position, t)) (fauna probe, 4354ef2c)
|
||||
qpt : wt = base*(1+B*prox(query position, t)) (common factor for every candidate site)
|
||||
cellc : wt = base*(1+B*prox(lattice cell centre, t)) (node calls the field at the integer cell id)
|
||||
site_t0: as site, but field read at t=0 (stale time) while wells have moved
|
||||
warp : base weights, query warped q' = q*(1+B*prox(q)) (domain scaling, a different construct)
|
||||
"""
|
||||
import numpy as np, sys, json
|
||||
rng_seed = 7
|
||||
def hash01(ix, iz, k):
|
||||
# integer hash (lowbias32-style), stand-in for pcg3d; deterministic, vectorised
|
||||
x = (ix.astype(np.uint64)*np.uint64(73856093) ^ iz.astype(np.uint64)*np.uint64(19349663) ^ np.uint64(k*83492791+rng_seed)) & np.uint64(0xFFFFFFFF)
|
||||
x ^= x >> np.uint64(16); x = (x*np.uint64(0x7feb352d)) & np.uint64(0xFFFFFFFF)
|
||||
x ^= x >> np.uint64(15); x = (x*np.uint64(0x846ca68b)) & np.uint64(0xFFFFFFFF)
|
||||
x ^= x >> np.uint64(16)
|
||||
return (x >> np.uint64(8)).astype(np.float64)/16777216.0
|
||||
|
||||
W0 = np.array([[2.0,2.5],[5.5,4.0],[3.5,6.0],[6.5,1.5]]); WS = np.array([0.8,1.2,0.6,1.0])
|
||||
OM = np.array([0.30,-0.22,0.17,-0.41]) # well orbit rate, rad per unit t (wells orbit a centre)
|
||||
CEN = np.array([4.0,4.0])
|
||||
def wells(t):
|
||||
r = W0-CEN; c, s = np.cos(OM*t), np.sin(OM*t)
|
||||
return CEN + np.stack([r[:,0]*c-r[:,1]*s, r[:,0]*s+r[:,1]*c], 1)
|
||||
def prox(x, z, t):
|
||||
w = wells(t); out = 0
|
||||
for k in range(len(w)):
|
||||
out = np.maximum(out, np.exp(-(np.hypot(x-w[k,0], z-w[k,1])/WS[k])**2))
|
||||
return out
|
||||
|
||||
KW=.35
|
||||
def node(qx, qz, variant, t=0.0, B=1.6, WIN=2, tfield=None):
|
||||
"""returns f1, f2, winning cell id (ix*10007+iz), soft border value"""
|
||||
qx0, qz0 = qx, qz
|
||||
if variant == 'warp':
|
||||
s = 1+B*prox(qx, qz, t); qx, qz = qx*s, qz*s
|
||||
nx, nz = np.floor(qx), np.floor(qz)
|
||||
m = np.full(qx.shape, 8.0); f1 = m.copy(); f2 = m.copy(); wid = np.zeros(qx.shape)
|
||||
pq = prox(qx0, qz0, t)
|
||||
tf = t if tfield is None else tfield
|
||||
for j in range(-WIN, WIN+1):
|
||||
for i in range(-WIN, WIN+1):
|
||||
cx, cz = nx+i, nz+j
|
||||
ox, oz = hash01(cx, cz, 11), hash01(cx, cz, 12)
|
||||
base = hash01(cx, cz, 13)*.5+.5
|
||||
sx, sz = cx+ox, cz+oz
|
||||
if variant in ('base','warp'): wt = base
|
||||
elif variant == 'site': wt = base*(1+B*prox(sx, sz, tf))
|
||||
elif variant == 'site_t0': wt = base*(1+B*prox(sx, sz, 0.0))
|
||||
elif variant == 'cellc': wt = base*(1+B*prox(cx+.5, cz+.5, tf))
|
||||
elif variant == 'qpt': wt = base*(1+B*pq)
|
||||
d = np.hypot(sx-qx, sz-qz)*wt
|
||||
upd1 = d < f1; upd2 = (~upd1) & (d < f2)
|
||||
wid = np.where(upd1, cx*10007+cz, wid)
|
||||
f2 = np.where(upd1, f1, np.where(upd2, d, f2)); f1 = np.where(upd1, d, f1)
|
||||
h = (lambda u: u*u*(3-2*u))(np.clip(.5+.5*(m-d)/KW, 0, 1))
|
||||
m = m+(d-m)*h-h*(1-h)*KW/(1+3*KW)
|
||||
gap0 = .25*KW/(1+3*KW)
|
||||
soft = np.clip((f1-m)/gap0, 0, 1)
|
||||
out = np.maximum(soft**2, np.exp(-((f2-f1)/.10)**2))
|
||||
return f1, f2, wid, out
|
||||
|
||||
def grid(n=500, lo=1.0, hi=7.0):
|
||||
xs = np.linspace(lo, hi, n); X, Z = np.meshgrid(xs, xs); return X, Z
|
||||
|
||||
def main():
|
||||
R = {}
|
||||
X, Z = grid()
|
||||
for t in (0.0, 8.0):
|
||||
ref = {v: node(X, Z, v, t) for v in ('base','site','qpt','cellc','warp')}
|
||||
ref['site_t0'] = node(X, Z, 'site_t0', t)
|
||||
# large-window truth for 'site' (exact argmin unless window too small)
|
||||
truth = node(X, Z, 'site', t, WIN=5)[2]
|
||||
key = f"t={t}"; R[key] = {}
|
||||
for v, (f1, f2, wid, out) in ref.items():
|
||||
R[key][v] = dict(
|
||||
winner_differs_from_site=float((wid != ref['site'][2]).mean()),
|
||||
winner_differs_from_base=float((wid != ref['base'][2]).mean()),
|
||||
out_absdiff_vs_site_mean=float(np.abs(out-ref['site'][3]).mean()),
|
||||
out_absdiff_vs_site_max=float(np.abs(out-ref['site'][3]).max()),
|
||||
n_cells=int(len(np.unique(wid))))
|
||||
R[key]['site_window2_vs_window5_winner_differs'] = float((ref['site'][2] != truth).mean())
|
||||
# cell count density near wells for site vs base
|
||||
# pop measure: dense line, step 1e-3 units; also jumps in time at fixed q
|
||||
ts = np.linspace(1.0, 7.0, 6001) # 1e-3
|
||||
lines = {'horizontal z=2.6 (near well 0)': (ts, np.full_like(ts, 2.6)),
|
||||
'diagonal': (ts, 1.0+ts*0.9-0.5)}
|
||||
P = {}
|
||||
for ln, (lx, lz) in lines.items():
|
||||
P[ln] = {}
|
||||
for t in (0.0, 8.0):
|
||||
for v in ('base','site','qpt','cellc','warp'):
|
||||
for B, WIN in ((1.6,2),(1.6,1),(6.0,2),(6.0,3)):
|
||||
if v in ('base',) and B != 1.6: continue
|
||||
f1, f2, wid, out = node(lx, lz, v, t, B, WIN)
|
||||
ref = node(lx, lz, v, t, B, 6)
|
||||
P[ln][f"{v} t={t} B={B} WIN={WIN}"] = dict(
|
||||
maxjump_f1_per_step=float(np.abs(np.diff(f1)).max()),
|
||||
maxjump_out_per_step=float(np.abs(np.diff(out)).max()),
|
||||
maxjump_f1_with_window6=float(np.abs(np.diff(ref[0])).max()),
|
||||
f1_err_vs_win6=float(np.abs(f1-ref[0]).max()))
|
||||
# time pop: fixed query points, t stepped at 1e-2; wells move
|
||||
tt = np.linspace(0, 20, 2001)
|
||||
qs = [(2.3,2.8),(4.7,3.9),(3.5,5.5),(5.9,1.9)]
|
||||
T = {}
|
||||
for v in ('base','site','qpt','cellc','site_t0'):
|
||||
mj = 0; mo = 0; changes = 0
|
||||
for (qx, qz) in qs:
|
||||
f1s=[];outs=[];wids=[]
|
||||
for t in tt:
|
||||
a,b,w,o = node(np.array([qx]), np.array([qz]), v, t)
|
||||
f1s.append(a[0]); outs.append(o[0]); wids.append(w[0])
|
||||
mj=max(mj,np.abs(np.diff(f1s)).max()); mo=max(mo,np.abs(np.diff(outs)).max())
|
||||
changes += int((np.diff(wids)!=0).sum())
|
||||
T[v] = dict(maxjump_f1_per_dt=float(mj), maxjump_out_per_dt=float(mo), winner_changes_total=changes)
|
||||
json.dump(dict(grid=R, line_pop=P, time_pop=T), open('probe_results.json','w'), indent=1)
|
||||
print(json.dumps(R, indent=1)); print(json.dumps(T, indent=1))
|
||||
for ln in P:
|
||||
print(ln)
|
||||
for k,v in P[ln].items(): print(' ', k, {a: round(b,5) for a,b in v.items()})
|
||||
main()
|
||||
37
docs/research/lattice-site-weight/siteweight_probe2.py
Normal file
37
docs/research/lattice-site-weight/siteweight_probe2.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"""Follow-up: (1) continuity refinement: does the max time-jump of 'site' scale with dt? (2) cell size near vs far from wells,
|
||||
border-pixel fraction per variant."""
|
||||
import numpy as np, json, importlib.util, sys, io, contextlib
|
||||
spec = importlib.util.spec_from_file_location('p', 'siteweight_probe.py')
|
||||
src = open('siteweight_probe.py').read().replace('\nmain()\n', '\n') # import without running main
|
||||
ns = {}; exec(compile(src, 'p', 'exec'), ns)
|
||||
node, prox, grid, wells = ns['node'], ns['prox'], ns['grid'], ns['wells']
|
||||
res = {}
|
||||
# (1) worst time step at dt=.01 for each variant, then refine dt inside +-.02 of it (continuity: step should shrink ~ with dt)
|
||||
qs = [(2.3,2.8),(4.7,3.9),(3.5,5.5),(5.9,1.9)]
|
||||
def series(v, qx, qz, tt):
|
||||
f1 = np.empty(len(tt)); out = np.empty(len(tt))
|
||||
for k, t in enumerate(tt):
|
||||
a,b,w,o = node(np.array([qx]), np.array([qz]), v, t); f1[k]=a[0]; out[k]=o[0]
|
||||
return f1, out
|
||||
for v in ('site','qpt','cellc'):
|
||||
tt = np.arange(0, 20.0001, 0.01); best = (0, None, None)
|
||||
for qx, qz in qs:
|
||||
f1, out = series(v, qx, qz, tt); i = int(np.argmax(np.abs(np.diff(out))))
|
||||
if abs(out[i+1]-out[i]) > best[0]: best = (abs(out[i+1]-out[i]), (qx,qz), tt[i])
|
||||
_, q, t0 = best
|
||||
for dt in (0.01, 0.001, 0.0001, 0.00001):
|
||||
t2 = np.arange(t0-0.02, t0+0.03, dt); f1, out = series(v, q[0], q[1], t2)
|
||||
res[f"timejump {v} dt={dt}"] = dict(q=q, t_worst=float(t0), max_out_step=float(np.abs(np.diff(out)).max()), max_f1_step=float(np.abs(np.diff(f1)).max()))
|
||||
print(v, dt, res[f"timejump {v} dt={dt}"], flush=True)
|
||||
# (2) cell area near vs far wells, border fraction
|
||||
X, Z = grid(600)
|
||||
w = wells(0.0)
|
||||
near = np.min([np.hypot(X-w[k,0], Z-w[k,1])/ns['WS'][k] for k in range(4)], axis=0) < 1.0
|
||||
for v in ('base','site','qpt','cellc','warp'):
|
||||
f1,f2,wid,out = node(X, Z, v, 0.0)
|
||||
ids, cnt = np.unique(wid, return_counts=True); area = dict(zip(ids, cnt))
|
||||
pa = np.array([area[i] for i in wid.ravel()]).reshape(wid.shape)
|
||||
res[f"shape {v}"] = dict(mean_cell_pixels_near_wells=float(pa[near].mean()), mean_cell_pixels_far=float(pa[~near].mean()),
|
||||
border_frac_near=float((out[near]>.5).mean()), border_frac_far=float((out[~near]>.5).mean()))
|
||||
print(v, res[f"shape {v}"])
|
||||
json.dump(res, open('probe_results2.json','w'), indent=1)
|
||||
80
docs/research/span-chain-and-site-field.md
Normal file
80
docs/research/span-chain-and-site-field.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Facts for `cultmath-tapes:question:span-chain-shape` and `cultmath-tapes:question:site-ground-field` (2026-10-14)
|
||||
|
||||
Eyes, read only. Tags: [src] primary source text read (paper or code); [doc] vendor or library documentation, or a committed CultLib doc; [web] web summary, second-hand where noted (WebFetch/WebSearch summarisers, not the paper text); [abs] derived by reasoning, not a measurement. Not repeated here: `docs/research/integration-native-noise-domain.md`, `culling-efficiency-oracle-prior-art.md` (its section 3 already carries Keeter MPR, Heidrich 1998, Gamito and Maddock 2007, Knoll Tables 1-2 fps ratios, Sharp and Jacobson, Mitchell timings), `tape-register-model.md` (Fidget and MPR tape, allocator, interpreter overhead). Raw downloads and pdftotext output: `w/` beside this file (not meant for commit).
|
||||
|
||||
## Part A. span-chain-shape
|
||||
|
||||
### A1. Taylor models and Taylor forms (Berz, Makino; Neumaier's review)
|
||||
|
||||
- [src] Neumaier, *Taylor forms: use and limits* (2002, `https://arnold-neumaier.at/ms/taylor.pdf`, read via pdftotext). Taylor forms "compute recursively a high order polynomial approximation to a multivariate Taylor expansion, with a remainder term that rigorously bounds the approximation error" (section 1). Storage and work are proportional to C(n+d, d) coefficients for degree d in n variables, times the number of arithmetic operations. Centered forms are "the degree 1 case of Taylor forms".
|
||||
- [src] Same, section 1: "Rigorous multivariate Taylor arithmetic with remainder, for the four elementary operations and composition exists at least since 1984 (Eckmann et al.)". Composition gives "arbitrary analytic standard functions for which polynomial enclosures with error bounds are available" (section 3). Berz's group popularised the variant named Taylor models from 1996; COSY INFINITY is the implementation. Two coefficient conventions: Eckmann et al. use narrow interval coefficients with a norm-bound remainder; COSY uses floating-point coefficients plus an interval remainder.
|
||||
- [src] Same, section 3: first-order Taylor models "are a simplified version" of slope-based centered forms in which "the width of the linear coefficients is moved to the remainder term"; "comparisons between first order Taylor models and the mean value form ... are not available". Slopes are more accurate than simple Taylor-form implementations because of the subdistributive law.
|
||||
- [src] Same, section 4: interval evaluation has linear approximation order; centered forms have quadratic approximation order (overestimation proportional to box width squared) "under mild conditions excluding near-singular cases". Near stationary points a Taylor form of narrow intervals "suffers from the same problem as other centered forms".
|
||||
- [src] Same, section 3: "Over sufficiently wide boxes, the Taylor form shares the fate of any centered form, that it usually gives a large overestimation and may even be poorer than naive interval evaluation. It is not designed for such applications".
|
||||
- [src] Same, section 7: three overestimation sources: wrapping (depth of the computational graph, "long sequences of nested operations depending on a limited number of variables only"), cancellation, dependence. "The amount of wrapping is related to the level of nestedness, i.e., the depth of the reduced computational tree obtained by combining linear combinations into single nodes", but the size and sign of the coefficients decide how much occurs; a complete analysis is "possible only for well-structured simple cases".
|
||||
- [src] Same, section 11, worked example f(x)=1/(1-x) - 1/(2-x) ... at x=[-r,r]: naive interval radius 5r/4+O(r^3); centered form 3r/4+9r^2/8; arbitrary-order Taylor expansion gives radius 3r/4+7r^2/16, "marginal additional improvement factor of 1-11r/12 only". Neumaier's reading: when the quadratic approximation property is already effective, higher order gives little; check the computable overestimation bound (his Theorem 8.1) against the width and raise the order only if it is too large.
|
||||
- [web] (second-hand, via search summary of Makino's 2004 slides, `https://rec.ce.gatech.edu/rec2004/presentations/Makino.pdf`, not read in full) If the remainder bounds of f and g are O(h^(n+1)) then those of f+g and f*g are too; intrinsic functions are composed from binary operations plus an extra interval whose width scales at least with the (n+1)-st power of a bound B of a function that scales at least linearly with h; hence order n+1 scaling survives composition.
|
||||
- [web] (second-hand) Caltech paper `https://resolver.caltech.edu/CaltechAUTHORS:20130919-113604950` ("Convergence analysis of Taylor models and McCormick-Taylor models"): proves convergence order of q-th order Taylor models is at least q+1 for addition, multiplication and composition under mild assumptions; the summary says the bounds are verified sharp on simple numerical examples. That paper is on McCormick-Taylor variants, so constants may differ from COSY's.
|
||||
- [web] (second-hand) The remainder interval in validated ODE integration "can never decrease", so the dependency impact grows with integration steps; shrink wrapping and preconditioning are added for that (Neumaier section 13-14 discuss wrapping in discrete dynamical systems and ODE enclosures, [src], not applicable to a feed-forward chain except as the definition of wrapping above).
|
||||
- [src] Streeter and Dillon, *Automatically Bounding the Taylor Remainder Series* (arXiv 2212.11429v3, abstract read): takes a function in symbolic form built from known atomic functions, a reference point, a trust region and an order k; derives sharp polynomial bounds on the remainder of exp, log, etc.; "combines those bounds through the function's composition" with "an interval arithmetic variant of Taylor-mode automatic differentiation". Open-source JAX implementation. The abstract page gives no comparison against interval arithmetic or Taylor models and no benchmark numbers.
|
||||
- [abs] The Span Lab's per-span model (linearise the chain, bound the error with 0.5 K h^2, K from a chain of speed, acceleration, Jacobian and second-derivative bounds; map Pass 8) has the structure of a first-order Taylor model with a Lagrange-form remainder from a second-derivative bound. Its remainder order is h^2, equal to the "quadratic approximation order" Neumaier states for centered forms. [doc] for the lab's construction: `F:\Projects\eureka-handoff-aux-2026-10-14\eyes-nebula-labs\facts.md` section 1a and `docs/cultmath-interval-ground-cut.md` Pass 8.
|
||||
|
||||
### A2. Mitchell 1990, Knoll et al. 2009, Gamito and Maddock: how a chain is represented
|
||||
|
||||
- [src] Mitchell, *Robust Ray Intersection with Interval Arithmetic* (Graphics Interface 1990, pp. 68-74, `https://graphicsinterface.org/wp-content/uploads/gi1990-8.pdf`, pdftotext read). The surface is a ray-substituted one-variable function f(t)=F(S+tD). The test is a three-valued root inclusion ("yes", "no", "maybe"; bisect on "maybe"): Step 1 evaluate the interval extension of f, no zero means no root; Step 2 evaluate the interval extension of the derivative, no zero means monotonic, so refine by a standard method; Step 3 both contain zero, bisect; Step 4 stop at machine-accuracy width.
|
||||
- [src] Same, section 3: "Given an expression F(x,y,z) in symbolic postfix form, a simple interpreter can compute an interval evaluation. By application of the chain rule for differentiation, the value of grad F can be computed concurrently." Derivative of f is the interval extension of D . grad F. Monotonic functions have trivial interval extensions (e^[a,b] = [e^a, e^b]); abs is [0, max(|a|,|b|)]; sine and cosine use known extrema locations to give "exact upper and lower bounds". Division must return [-inf, inf] when the divisor interval contains zero (superquadric |x|^0.75 has a derivative singularity at 0). Rounding: single precision was "sufficient" for the images; outward rounding is "safer but more computationally expensive". So the chain is one compiled postfix expression, value and gradient bounds computed together, with bounds per elementary node.
|
||||
- [src] Knoll, Hijazi, Kensler, Schott, Hansen, Hagen, *Fast Ray Tracing of Arbitrary Implicit Surfaces with Interval and Affine Arithmetic* (CGF 28(1), 2009; `https://www.sci.utah.edu/~knolla/cgrtia.pdf`, pdftotext read): the user supplies an implicit function in text; "we use a simple recursive-descent parser to generate these routines in the output Cg shader": one routine for f and one for its inclusion function (interval or affine extension), generated by shader metaprogramming and compiled as one shader. "Because the shader compiler identifies common subexpressions, [inline code] is seldom necessary for improving performance" (section 5.1). Whole expression as one generated function, not stage functions.
|
||||
- [src] Same: GPU algorithm is stackless interval bisection with a fixed per-ray maximum depth derived from a user precision epsilon; benchmarked at 1024x1024 on an NVIDIA 8800GTX (GPU) and a 4-core Xeon 2.33 GHz (SSE). RAA "generally 1.5-2x faster than interval arithmetic" for moderate cross-multiplication, "3 to 4 times faster" for high-overestimation forms (Barth decic, Horner forms); IA stays "far more efficient for superquadrics" because of an efficient integer-power rule; "IA is currently required for extensions of division, transcendentals, and fractional powers" (section 5.4 as cited). Quality: "RAA generally converges far more quickly than IA, given lesser bound overestimation at low epsilon"; RAA "has worse numerical conditioning" than IA.
|
||||
- [src] Same, section 6.1.3 text: GPU implementations of the Mitchell algorithm (interval evaluation then Newton-style refinement) "perform far worse than naive bisection, particularly at higher epsilon", attributed to "the high cost of evaluating the gradient interval, and both worse instruction-level coherence on the GPU and spatial coherence in the SSE CPU algorithm"; interval Newton and quasi-Newton variants "empirically suggested far worse results" on the SIMD CPU implementation. The paper's Table 1 figures (e.g. Mitchell surface IA 63/34/19/13 fps vs RAA 80/64/59/56 fps across four epsilon values; Barth decic IA 29/13/7.1/4.9 vs RAA 25/19/17/16) are in the figure caption region of the extracted text; the table layout is mangled and I did not re-derive the row mapping.
|
||||
- [doc] Already committed: Gamito and Maddock 2007 (noise ray casting, reduced affine arithmetic, evaluations per ray) in `culling-efficiency-oracle-prior-art.md` section 3. The Gamito paper `https://eprints.whiterose.ac.uk/id/eprint/3562/1/gamito4.pdf` (downloaded, 10 pages) describes procedural noise as "a sum of kernel functions ... that depend on the displacement vectors between x and the node points", each evaluated in affine arithmetic; I did not find a warp chain in it.
|
||||
- [src] Zanni, *Synchronized-tracing of implicit surfaces* (arXiv 2304.09673, text read in part): states two representations of a field expression on GPU: "function calls directly hard coded inside a shader ... good performance can be achieved for reasonable size expressions at the expense of shader recompilation each time the expression is modified", versus the expression as data with an interpreter (post-order tree, Grasberger 2016; tape for an arithmetic DAG, Keeter 2020). It uses node-level interpreters rather than a math-expression interpreter: they "decrease the number of switch executions and require less storage of data" and "can support fully procedural nodes", at the price of a more complex shader that needs recompiling if the primitive/operator family changes. Its blobtree has "space deformation moved to the leaves (our implementation only supports rotation and translation of primitives)". It restates that Hart's sphere tracing uses a Lipschitz bound, and that Segment tracing (Galin et al. 2020) uses "directional Lipschitz bound on ray subintervals", with a variant for deformed surfaces (Seyb et al. 2019).
|
||||
- [web] (abstract only; the paper PDF at diglib7.eg.org returned an HTML login page to curl) Galin, Guerin, Paris, Peytavie, *Segment Tracing Using Local Lipschitz Bounds* (CGF 39(2), 2020, pp. 545-554, `https://diglib.eg.org/handle/10.1111/cgf13951`): computes the Lipschitz bound locally over a ray segment; "describe the computation of the Lipschitz bound for different operators and primitives"; reports significantly fewer field-function queries than previous methods without extra acceleration structures; no figures in the abstract. I did not read its per-operator rules.
|
||||
|
||||
### A3. Keeter MPR and Fidget: stage functions vs one compiled tree
|
||||
|
||||
- [doc] Already committed in `tape-register-model.md` section 1: MPR and Fidget evaluate the whole shape as one flat clause tape (clause = opcode plus output slot plus up to two input slots), interval arithmetic over the tape picks min/max branches and records choices, then the tape is shortened per tile (6056 clauses to a mean of 356 in 64^2 tiles and 28 in 8^2 subtiles; `culling-efficiency-oracle-prior-art.md` section 3). Interpreter overhead versus a compiled kernel about 19x (58 ms/MP vs 3 ms/MP) on the 6056-clause model. Fidget main branch: wgpu interpreter does not support register spills.
|
||||
- [doc] `culling-efficiency-oracle-prior-art.md` section 3 also records Keeter's text that many smooth blends "lead to less useful interval evaluation results" (MPR section 5) and that reduced affine arithmetic is named as a way "to more tightly bound intervals" (section 6).
|
||||
- [abs] None of the sources read for A1-A3 evaluates a chain of nested warps of noise. In every one, the chain is one expression (postfix in Mitchell, generated shader code in Knoll, flat tape in MPR/Fidget) and bounds come from per-node rules; no source reads as exposing "stage with its own bound" as a unit of composition except Zanni's node-level interpreter (blobtree nodes) and Segment tracing's per-operator Lipschitz rules (abstract only).
|
||||
|
||||
### A4. Closed-form span integration and pre-integration (Engel 2001)
|
||||
|
||||
- [src] Engel, Kraus, Ertl, *High-Quality Pre-Integrated Volume Rendering Using Hardware-Accelerated Pixel Shading* (HWWS 2001, pp. 9-16, `https://www3.cs.stonybrook.edu/~mueller/teaching/cse616/engel.pdf`, pdftotext read). The per-segment opacity and colour between a front sample sf and back sample sb over segment length d are precomputed into lookup tables of three parameters (sf, sb, d); the tables depend on the transfer function so "any modification of the transfer functions requires an update of the lookup tables" (section 3.5). Reductions: assume a constant segment length (2D table in sf, sb; "a good approximation for most perspective projections"), or approximate the d dependence as linear; local transfer function change recomputes only entries with sf <= s <= sb or the reverse; and the integral can be accelerated by cumulative integral functions (so each table entry is a difference of two 1D integrals). The paper states an analytic evaluation of the volume rendering integral "is possible in some cases, in particular for linear interpolation and piecewise linear transfer functions", "not feasible in general". Test hardware GeForce3 64 MB; the tables are lookups in the pixel shader.
|
||||
- [src] Same: the sampling of the scalar field along the ray is decoupled from the transfer function, so the Nyquist limit of the scalar field alone applies ("the Nyquist frequency for this sampling is not affected by the transfer functions"); the segment between the two samples is treated through the table, with the scalar assumed varying between sf and sb.
|
||||
- [web] (second-hand) Rottger et al. 2003, `https://diglib.eg.org/items/99a1d638-d8c8-4ad9-bd8d-1c041524c3e2` ("Smart Hardware-Accelerated Volume Rendering"): cited by a search summary for oversampling needed even with pre-integration; not read.
|
||||
- [abs] Engel's pre-integration is exact for the integral of the transfer function composed with the assumed in-segment interpolant of the scalar; the error against the true field is the interpolant error over the span (the thing the Span Lab's 0.5 K h^2 bounds). Engel's quantity that is tabulated is a function of the span endpoints' scalar values; the Span Lab's closed form is the clamp of a linear function integrated exactly (map Pass 8 `integral_clamp_linear`).
|
||||
|
||||
## Part B. site-ground-field
|
||||
|
||||
### B1. WebGL2 constraints for splat and kernel rendering
|
||||
|
||||
- [web] (search summary) WebGL2 has no compute shaders and no storage buffers; GPU sorting must be emulated by transform feedback or render-to-texture passes. Bitonic sort passes: "1/2 (log^2 n + log n)" passes, arithmetic heavy (a 2004 GPU particle engine paper, `https://www.cs.cit.tum.de/fileadmin/w00cfj/cg/Research/Publications/2004/GPU_Particle_Engine/uf_sig04_slides.pdf`, not read). A texture-based sort library's capacity is "max texture width squared", example 4096^2 = 16,777,216. I found no WebGL2 GPU radix sort implementation or measurement in this pass.
|
||||
- [web] Splat viewers in practice sort on the CPU in a worker and read order back to the GPU. antimatter15/splat (`https://github.com/antimatter15/splat`, README fetched): "splat sorting is done asynchronously on the cpu in a webworker", sort at roughly 4 fps while render is 60 fps, about 150 ms to sort about 1M splats on the author's machine, WebGL 1.0 "WebGL 2.0 adds nothing essential", demo about 1M splats; artifacts appear "when jumping between camera orientations on opposite sides, because the sort hasn't caught up". The author tried order-independent approaches: stochastic transparency looked grainy, weighted blended OIT "didn't work".
|
||||
- [doc] Spark (World Labs, three.js, WebGL2; `https://sparkjs.dev/docs/system-design/` and `https://sparkjs.dev/docs/performance/`): each viewpoint "reads back a list of all splat viewpoint distances from the GPU, then determines the splat draw order using an efficient bucket sort algorithm, run in a background worker thread", drawn in one instanced call, "the sort order lags the render by at least one frame, but possibly more on older devices" (search summary of the system-design page; a launch post says "two-pass radix sort in a Web Worker", sources disagree). Budgets (performance page): Quest 3 1M splats or less; Android phone 1-2M; iPhone 1-3M; computer 1-5M. Splats "must be blended back-to-front, which is the main reason rendering is demanding on mobile GPUs"; each splat is two triangles covering a Gaussian out to sqrt(8) standard deviations; ~1M splats make the per-splat transform overhead (SplatAccumulator) a bottleneck on some systems; "500K splats from a Trellis object at small screen scale can bottleneck GPU rendering and blending". `antialias` should stay false; high pixel ratio costs blending throughput.
|
||||
- [web] (search summary, PlayCanvas docs) default renderer sorts on the CPU in a worker; the GPU sort path is "experimental, and WebGPU only"; WebGL devices fall back to CPU sorting. A mobile 3DGS paper summary (`https://arxiv.org/pdf/2511.16298`) states sorting "dominates the computations in 3DGS on mobile platforms"; not read.
|
||||
- [src] `EXT_float_blend` (Khronos registry, `https://registry.khronos.org/webgl/extensions/EXT_float_blend/`): without it, blending with a 32-bit float draw buffer is `INVALID_OPERATION`; with it, drawing to 32-bit float buffers with blending enabled is allowed. The page does not address 16-bit float blending. `EXT_color_buffer_float` (`.../EXT_color_buffer_float/`) makes RGBA16F, RGBA32F and R11F_G11F_B10F color-renderable and says output is not clamped; the page does not address blending. [web] Esri (`https://www.esri.com/en-us/software-engineering/blog/articles/support-for-transparency-and-emissive-materials-on-limited-webgl-devices`): their float OIT needs EXT_float_blend, "not available on a significant subset of iOS devices"; float buffers cost 30 bytes/pixel for transparency plus emission, "about 110 MB on an 11-inch iPad"; their fallback "OIT8" packs accumulation into RGBA8 fixed point (each fragment divided by 16 on write, multiplied on composite): range 0-16, about 4 bits of precision per fragment, banding hidden by dithering, values overflow toward white after roughly 16 overlapping surfaces, emission capped at 16. No frame-time figures. I did not find a device-population percentage for EXT_float_blend or EXT_color_buffer_float (webglstats pages returned blog index text).
|
||||
- [web] Weighted blended OIT (McGuire and Bavoil 2013): two accumulation targets (accumulated premultiplied colour*weight and alpha product/revealage) plus a composite; sources describe it as order-independent, bounded memory, approximate ("fragment colors and depths are combined approximately", luma.gl docs; "not physically correct", Open CASCADE write-up). Cesium: needs only MRT and float textures. One third-party engine note reports poor handling of near-opaque layers (single report). Not read from the paper.
|
||||
- [web] Depth peeling (Everitt 2001): one geometry pass per layer; dual depth peeling N/2+1 passes; multi-layer depth peeling "up to 8x speed-up ... at the price of several MRT buffers". The only WebGL material found was a 2012 WebGL1 thread (no MRT, no MIN/MAX blend) and no WebGL2 measurement.
|
||||
- [web] Moment-based OIT (Munstermann, Krumpen, Klein, Peters, I3D 2018, `https://momentsingraphics.de/I3D2018.html`, `https://cg.ivd.kit.edu/mboit.php`, abstract and teaser read): works on the logarithm of transmittance so contributions sum additively; a first additive pass builds 4/6/8 power moments or up to 4 trigonometric moments; a second additive pass composites; storage 112 bits per pixel (six power moments) or 224 bits (three trigonometric); Peters' postscript notes half memory with rasterizer ordered views (not WebGL2). No WebGL2 port found. [web] Moment-based 3D Gaussian splatting (arXiv 2512.11800, summary read): order-independent, no sorting, two additive passes (moments, then quadrature of radiance) plus normalisation; trained with Vulkan/Slang, not WebGL; PSNR below sorted 3DGS on all three datasets (e.g. MipNeRF-360 25.96 vs 27.43) though qualitatively better on translucent overlaps per the authors; no frame rates reported.
|
||||
|
||||
### B2. 3DGRT and EVER
|
||||
|
||||
- [web] 3DGRT, Moenne-Loccoz et al., *3D Gaussian Ray Tracing* (arXiv 2407.07090, HTML fetched through a summariser, first 100 000 of 123 271 characters): BVH over particles each wrapped in a regular icosahedron (20 triangles) scaled to where response exceeds alpha_min = 0.01, hardware ray tracing; the any-hit program keeps the k = 16 closest hits by insertion, shaded in batches; hit ordering uses the bounding-primitive intersection distance, sample point is the analytic maximum along the ray. Table 2, one RTX 6000 Ada, fps: 3DGS 238 / 319 / 267 (MipNeRF360 / Tanks&Temples / Deep Blending); 3DGRT reference 55 / 143 / 77; 3DGRT 78 / 190 / 119. The text calls forward rendering "only about 2x" slower while the table implies about 3x to 4.3x. BVH build above 30 ms per million particles for icosahedra (Figure 9); training about 5x slower per step; memory 383 / 489 / 287 MB vs 3DGS checkpoint 763 / 422 / 704 MB. Generalised kernels (degree n=2... "generalized Gaussians") roughly double speed (277 vs 143 fps on Tanks&Temples). Limitations: slower than rasterisation for pinhole cameras, BVH rebuild for dynamic scenes, no automatic antialiasing, stochastic sampling prevents SSIM losses.
|
||||
- [web] EVER, Mai et al., *Exact Volumetric Ellipsoid Rendering for Real-time View Synthesis* (arXiv 2410.01804, HTML fetched through a summariser): constant-density ellipsoids each with a single (view-dependent) colour; between consecutive ray intersection points colour and density are constant so the volume rendering integral is evaluated in closed form; overlap handled by tracking running density and premultiplied colour totals as the ray enters and exits each ellipsoid; per-ray sorting via NVIDIA OptiX BVH (rebuilt each training step, 2-20 ms), multi-hit queue borrowed from 3DGRT. RTX 4090 at 720p unless noted: about 36 fps Mip-NeRF 360 outdoor, 66 indoor, 30 Zip-NeRF; at dataset test resolution 36 / 24 / 44 fps vs 3DGS 224 / 559 / 560; tracing 20-71 ms per frame; "about 10x slower than 3DGS and uses more memory"; densification cap 7M primitives. Model is emission and absorption only, no scattering; popping metric LIP7 0.0 vs 0.0315 for 3DGS on Tanks&Temples. Anisotropic rotated ellipsoids enlarge their bounding boxes; an anisotropy regulariser trades quality for speed.
|
||||
- [web] WebGPU ray tracing status (search summary, non-authoritative blogs): hardware ray tracing and ray query "not yet part of the WebGPU specification, though proposals exist"; no browser shipping it found; a software compute-shader tracer exists (WebRTX, Chrome-tested, unstable spec). No WebGL2 route to either 3DGRT (OptiX/RT cores) or EVER.
|
||||
- [doc] The committed probe's kernel cascade is a CPU C# ray walker (hash grid, Amanatides-Woo DDA, early exit at sum of tau >= 7) over compact-support kernels with closed-form line integrals (`integration-native-noise-domain.md`, sections "Composition" and "Measurements"). Its measured cost ratios are CPU single-thread ratios against the march, same run: void L2 0.48x, L3 1.24x, L4 1.81x, L6 2.57x of the march cost; wells L2 0.23x, L6 1.20x; integrals are about 0.4 us of a 74 us pixel and traversal dominates (93 roots visited for 8 hits at L6). Kernel Lab page (`c09d2762`) is described in `eyes-nebula-labs/facts.md` section 1b: budget KBUDGET 420000 kernels and IDXCAP 4096*3072 bin entries, two-phase crossfade (2 copies per kernel). This pass found no WebGL2 measurement of either field.
|
||||
|
||||
### B3. Comparison basis: marched procedural field (span framework) vs kernel cascade, orbiting-camera backdrop
|
||||
|
||||
- [doc] Site constraints from the file-era spec (`F:\Projects\CultLib\docs\cultmath-interval-ground-cut-site-ground.r3.spec.json`, never run): fullscreen WebGL2 backdrop behind the masthead; sun orbit period 72 s, camera follows low inside the hollow; half-float history for TAA; shared fallback path for "slow device, hidden tab, reduced motion, missing WebGL2 and missing half-float"; per-frame sample cap S_budget and a controller that adapts S and dpr to measured GPU ms; the operator-verification list includes the phone ("read the logged GPU ms per pass and the controller's final S/dpr; say whether it got warm after five minutes"). The CSS wash is the stated fallback ground. `gamecult-site` `origin/main` `af6e206`: no ground shader, no vendored CultMath GLSL (map Pass 8).
|
||||
- [abs] Per-frame cost scaling. Marched field (full-screen fragment work): cost ~ pixels x samples per pixel x field cost per sample; the Span Lab replaces fixed steps by spans, so samples ~ spans per ray, each span a chain evaluation (value, Jacobian, bound) rather than one field sample (the lab's `maxSpans` cap 8..1000, default 400; its `tol` controls span count; `facts.md` section 3). Cost is independent of any scene object count, so fill rate (pixels x dpr) is the knob; the r3 spec scales dpr and S. Kernel cascade as rasterised splats: vertex work ~ kernel count K (sort and transform, Spark: ~1M is a bottleneck on some systems), fragment work ~ sum over kernels of covered pixels (overdraw ~ kernels per pixel x pixels; the probe's CPU walk saw 20-36 kernels per pixel at L2-L6 on the void), blend traffic on a float target per fragment (16F or 32F per B1). Cost scales with detail level (kernels per level multiply in the cascade: 8, 12, 8, 7), the frame knob is kernel LOD plus resolution scale.
|
||||
- [abs] Memory. Marched field: no per-scene geometry; state is uniforms (the lab's SPEC has about 60 scalars), a 4 Rw flow texture in r3, TAA history textures (colour, transmittance, depth), pre-pass mask. Kernel cascade: per-kernel record (the lab bakes centre, axes, peak, tint, flow vector, children indices; `eyes-nebula-labs/facts.md` section 1b), caps in the lab: KBUDGET 420 000 kernels; GPU upload as textures or instance buffers; plus the sort/order buffer if emission is order-dependent and any OIT accumulation targets.
|
||||
- [abs] Authoring model. Marched / span framework: authored field is a function (brushes, ramp, warp chain with parameter block) evaluated per point or span; edits are parameter changes with no rebuild; bounds must be derivable for each stage or the span evaluator is blind (Part A). Kernel cascade: authored as generator parameters (levels, children, keep, asp, rot, seed) that rebuild the kernel set on CPU on change ("Cascade items trigger rebuild", Kernel Lab SPEC); the set is explicit data, so the same data can be exported and no per-pixel bound derivation is needed, but fibrous look is the cascade's own ("fibrous look, cannot be marched" per the question text) and a field defined by a function (e.g. a warp chain) has no kernel representation in these sources.
|
||||
- [abs] Transmittance and emission under additive blending (reasoning, not tested). For absorption only, T = exp(-sum_k tau_k), where tau_k is the closed-form line integral of kernel k along the pixel's ray; a sum is order independent, so additive blending into a float or fixed-point target gives T exactly up to precision (B1 notes: 32F blend needs EXT_float_blend, which is absent on some iOS devices; 16F blending is not addressed by the two Khronos pages read; RGBA8 fixed-point packing overflows after about 16 overlaps in Esri's scheme). Emission integral of e(t) T(t) along the ray depends on T at each depth, so ordered compositing is needed (sorted splats, or OIT approximations: WBOIT, moment OIT, depth peeling at N passes) or a single-scatter approximation that drops the T(t) factor. EVER (B2) is the cited exact treatment of this ordering problem, by per-ray sorted traversal with RT cores.
|
||||
- [abs] Neither approach in B2 sources has been run in WebGL2 in this pass. No fps number in this file comes from a WebGL2 run.
|
||||
|
||||
## Gaps
|
||||
|
||||
- Taylor models: no primary Makino-Berz composition theorem text read (only Neumaier's review, search summaries of Makino's slides and the Caltech convergence paper); no remainder-growth-per-stage numbers for a chain of noise-like functions found; no graphics paper using Taylor models for ray marching found.
|
||||
- Knoll Table 1 row mapping not re-derived; Galin et al. per-operator Lipschitz rules and query counts not read (PDF behind login to curl); Fidget/MPR chain composition covered only by the committed doc; no source found that composes bounds across warp stages of noise.
|
||||
- WebGL2: no device-share numbers for EXT_float_blend, EXT_color_buffer_float or 3D texture size; no WebGL2 GPU radix sort source; no WebGL2 depth-peeling or WBOIT measurement; 16F blending support not confirmed from a spec page; 3DGRT and EVER read through summarisers (EVER and 3DGRT HTML not read to the end).
|
||||
- No WebGL2 measurement of the Kernel Lab or the Span Lab on a phone exists in the sources read; comparison part B3 is [abs] plus the CPU ratios in the committed probe doc.
|
||||
82
docs/research/tape-register-model.md
Normal file
82
docs/research/tape-register-model.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Facts for `cultmath-tapes:question:tape-register-model` (2026-10-14)
|
||||
|
||||
Eyes, read only. Tags: [src] source read, [doc] vendor/library documentation, [web] web page or search summary (second-hand where noted), [abs] derived by reasoning or a synthetic probe, not a measurement of any real GPU. Raw downloads: `raw/` beside this file (Fidget files at `main` on 2026-10-14; MPR paper text). Probe: `probe2.py` beside this file.
|
||||
|
||||
## 1. Fidget and MPR
|
||||
|
||||
### 1a. Fidget tape, allocator, bytecode (`github.com/mkeeter/fidget`, raw at `raw.githubusercontent.com/mkeeter/fidget/main/`)
|
||||
- [src] The allocator is `fidget-core/src/compiler/alloc.rs`, `RegisterAllocator<const N: usize>`, doc "Cheap and cheerful single-pass register allocation" (`:11`). `assert!(N <= u8::MAX)` (`:51`). It keeps a least-recently-used list of registers (`register_lru`) and the output tape is "assembled in reverse order". When no register is free it spills to a memory slot (`Allocation::Memory(u32)`, `RegOp::Load/Store`); memory is "treated as unlimited" (`get_memory`, `:106-123`). So the allocator is not a cap-and-refuse design: the CPU/JIT target spills past N. (I did not read the walk direction beyond the "reverse order" comment; "linear scan from the tape end" is consistent with it, not confirmed line by line.)
|
||||
- [src] Bytecode, `fidget-bytecode/src/lib.rs:1-45`: little-endian `u32` words, two words per op (fixed length so it can be walked forwards and backwards). Word 0 bytes: opcode, output register, input register 1, input register 2; input byte `0xFF` means "use word 1 as an f32 immediate". Tape starts `0xFFFFFFFF 0x00000000`, ends `0xFFFFFFFF 0xFFFFFFFF`. `Mem` op (Load/Store) uses word 1 as a memory slot. Register 255 is reserved (`ReservedRegister` error), so at most 255 registers in bytecode. The format is stated "not stable". Registers are renumbered by frequency of use (most used = 0). `Bytecode` records `reg_count` and `mem_count`.
|
||||
- [src] `fidget-wgpu/src/shaders/tape_interpreter.wgsl` (115 lines): `fn run_tape(...)` declares `var reg: array<Value, REG_COUNT>;` as a function-scope (private, per-invocation) array, indexed at runtime by bytes from the op word (`reg[lhs_i]`, `reg[op[1]] = tmp`). Not workgroup memory. `REG_COUNT` is a WGSL `const` the host prepends per pipeline (`fidget-wgpu/src/pixel/mod.rs:37,54,71`: `const REG_COUNT: u32 = {reg_count};`).
|
||||
- [src] `fidget-wgpu/src/lib.rs:529-545`: `REG_PIPELINE_SIZES = [8, 16, 32, 64, 128, 192, 255]`; `RegPipeline::get(reg_count)` compiles (lazily, cached) the first pipeline whose size >= the tape's register count and panics above 255. So the GPU interpreter specialises the shader on register-array length in seven buckets, so small tapes do not pay for a 255-element array.
|
||||
- [src] The same interpreter does NOT support spills: `case OP_MEM: { // Not implemented! return out; }` (op switch in `tape_interpreter.wgsl`). So on the GPU the effective cap is 255 registers and a tape needing memory slots does not run correctly. Related capacity: `TAPE_DATA_CAPACITY` = 8M words, 64 MiB (`lib.rs:268`).
|
||||
- [src] Registers hold `Value` (shared across interval, float and gradient evaluation). Pixel workgroups are `@workgroup_size(8, 8)` (`pixel/shaders/pixel_tiles.wgsl:7`, `interval_tiles.wgsl:13`). No `var<workgroup>` register store in those two shaders (grep found none).
|
||||
- [src] A second per-thread private structure: the min/max choice `Stack` is `array<u32, 32>` per invocation, 2 bits per choice, 512 choices (`shaders/stack.wgsl:1-12`).
|
||||
- [web] I found no published performance numbers for `fidget-wgpu` (its README is 9 lines: "implements 3D rendering of Fidget tapes using WebGPU"). CultLib's own note: `F:\Projects\CultLib\docs\cultmath-tape-target.md:25-40` (Fidget 0.5.1; tape 254 clauses shrinks to about 20 at pixel level, Fidget's example).
|
||||
|
||||
### 1b. MPR (Keeter, ACM TOG 39(4), SIGGRAPH 2020; `https://www.mattkeeter.com/research/mpr/keeter_mpr20.pdf`, text in `raw/mpr.txt`)
|
||||
- [src] Section 2: each thread "has exclusive access to a block of memory"; clause arguments and outputs are indexes into it, "slots", "equivalent to machine registers". Slot allocation is "a simple greedy algorithm": a slot is free outside its value's live range; if an argument is the last use, its slot is freed before the output is assigned (a clause may read and write the same slot). Register allocation with reordering (Rawat et al. 2018) was "deemed outside the scope".
|
||||
- [src] Section 4 implementation list: clause = 8 bytes (opcode 1 B, output slot 1 B, two input slots 1 B each, or one slot + 4 B immediate); "The storage slots are per-thread arrays of 128 values. For simplicity, this is hard-coded, but could be dynamically sized with the addition of load/store opcodes." The choices stack is a per-thread array too (2 bits per choice, 4096 choices in 256 `uint32_t`). Implementation was CUDA; tape in global memory.
|
||||
- [src] Table 2 (clauses, slots, ratio): "Hello, world" 328 / 26 / 12.6; bear head 541 / 22 / 24.6; architectural model 961 / 75 / 12.8; 2D text benchmark 6056 / 84 / 72.1. The paper hypothesises the high text ratio is because each character is an independent CSG primitive.
|
||||
- [src] Interpreter overhead (section 2.1, GTX 1080 Ti): brute-force interpreter about 58 ms per megapixel versus about 3 ms/MP for the same expression pasted into a CUDA function and compiled with nvcc (about 19x), on the 6056-clause text model; nvcc compile took 3.45 s. The full algorithm (interval pruning, tape shortening) beats the compiled kernel above about 1536^2 pixels and stays below 8 ms/frame at 4096^2. The paper reports no separate measurement of the 128-slot array's local-memory cost.
|
||||
- [src] Tape generation on CPU: 7 ms for the text model, 1-2 ms for the others.
|
||||
|
||||
## 2. GPU cost of dynamically indexed locals, groupshared, unrolled registers
|
||||
|
||||
- [doc] NVIDIA CUDA Programming Guide (`https://docs.nvidia.com/cuda/cuda-programming-guide/02-basics/writing-cuda-kernels.html`, via search summary): the compiler is likely to place arrays in local memory when it cannot prove constant indices; local memory physically lives in global memory space. The Nsight Compute profiling guide says the same.
|
||||
- [web] NVIDIA developer blog "Fast Dynamic Indexing of Private Arrays in CUDA" (`https://developer.nvidia.com/blog/fast-dynamic-indexing-private-arrays-cuda/`, fetched): constant indices keep the array in registers; dynamic indexing forces local memory, cached in L1/L2. Measured on Tesla K20, 32-element float private array: non-uniform indexing in local memory 24x slower than uniform indexing in local memory; non-uniform indexing in shared memory 1.34x faster than uniform local. On Maxwell: local non-uniform vs uniform gap about 14x; the shared-memory version 4x faster than uniform local. Replays per warp: 0 if all lanes use the same index, about 2.5 average for uniformly random indices over 0..31, up to 31 if every lane differs. Suggests a math-to-memory ratio of roughly 4:1 to 8:1 to hide local-memory cost. Shared layout: element i of thread t at `i * BLOCK + t` (one bank per thread); block size a multiple of 32; at most 48 KB shared per block; technique suited to private arrays of about 30-50 32-bit elements, larger arrays reduce occupancy.
|
||||
- [web] AMD (search summary of ROCm docs and a 2012 GCN thread): stack arrays live in scratch memory by default and the compiler may promote them to registers; GCN VGPRs can be indexed but one contributor reported about 50% speedup moving to static registers in some kernels (anecdote, old arch: `https://www.openwall.com/lists/john-dev/2012/12/09/7`). LLVM has a flag `-amdgpu-use-divergent-register-indexing`, so divergent VGPR indexing is a compiler mode. Register budget vs occupancy: RDNA SIMD register file 128 KB (GCN 64 KB); RDNA 4 desktop 192 KB per SIMD, full thread slots at 96 or fewer vector registers (`https://chipsandcheese.com/p/dynamic-register-allocation-on-amds`, `https://rocm.blogs.amd.com/software-tools-optimization/register-pressure/README.html`).
|
||||
- [web] Intel oneAPI GPU optimization guide, "Optimizing register spills" (`https://www.intel.com/content/www/us/en/docs/oneapi/optimization-guide-gpu/2024-2/optimizing-register-spills.html`): spills are likelier on Intel than NVIDIA because the GRF is smaller (A100 up to 256 32-bit registers per work-item vs 64 by default on Data Center GPU Max 1550); advice to avoid big private arrays and use shared local memory. A 2013 Intel SDK note says indexed private memory is slow because each work-item touches a unique cache line (old, OpenCL).
|
||||
- [web] Mesa/NIR driver commits (search summary): drivers choose by a size threshold between lowering indirect array access to conditional register selects and lowering to scratch. Intel commit "intel/fs: Lower large local arrays to scratch": on Kaby Lake, helped 6, hurt 2, helped programs were Aztec Ruins compute shaders, overall confidence interval for cycles included zero. An AMD MR found a 16-scalar-element threshold caused more scratch use. V3DV keeps arrays larger than 2 elements in scratch. SPIR-V allows dynamic indexing of arrays in every storage class except PushConstant (Khronos slide deck). Links: `https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/1097`, `https://gitlab.neroreflex.duckdns.org/NeroReflex/mesa/commit/69244fc7`.
|
||||
- [doc] D3D: `dcl_indexableTemp x0[23], 2` declares an indexable temp array; element count of all temp registers (indexable and not) cannot exceed 4096 (`https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dcl-indexabletemp`). FXC names a locally indexed array `x#`, each its own namespace (`https://list.winehq.org/hyperkitty/list/wine-gitlab@list.winehq.org/message/KNVE3CV4O2D464TKNCRIMZA6B7QG54NP/`). I found no measured cost of `x#` access on any GPU; whether a driver puts an `x#` array in registers or scratch is the driver's choice (vendor behaviour above).
|
||||
- [web] FXC limitation: writing to a dynamically indexed fixed-size array inside a struct fails (X3500 "array reference cannot be used as an l-value; not natively addressable"); Tint copies the array to a local, writes, copies back (`https://dawn.googlesource.com/tint/+/refs/heads/chromium/4765%5E%21/`). Relevant if the register array sits in a struct.
|
||||
- [doc] HLSL `groupshared`: compute shaders only; D3D10 limit 16 KB, D3D11 limit 32 KB total per thread group (`https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-variable-syntax`). [abs] 32 KB / (N registers x 4 B) = threads per group: N=64 gives 128 threads; N=128 gives 64; N=255 gives 32 (a float2 interval register halves each).
|
||||
- [abs] Static indexing needs the register number to be a compile-time constant, which in an interpreter loop it is not. A fully unrolled generated kernel has constant registers; that is MPR's "compiled expression" case (3 ms/MP vs 58 ms/MP interpreted, 1080 Ti).
|
||||
|
||||
## 3. Other GPU interpreters and expression VMs
|
||||
|
||||
- [web] Unity Shader Graph: the node graph is emitted as HLSL; the Custom Function node injects HLSL text or an include (`https://docs.unity3d.com/Packages/com.unity.shadergraph@17.2/manual/Custom-Function-Node.html`). The docs I read do not describe a runtime interpreter; the internals of the generator are not stated there, so "no interpreter" is consistent with the docs, not confirmed by them. Built-in pipeline support in Shader Graph is "compatibility only" since 2021.2 and, in the 17.5 docs, deprecated, supported through the Unity 6.7 LTS lifecycle (`https://docs.unity3d.com/Packages/com.unity.shadergraph@17.5/manual/Graph-Target.html`). Shader Graph docs found say nothing about compute shaders.
|
||||
- [web] Dreams (Media Molecule): Alex Evans, SIGGRAPH 2015 "Learning from Failure" (`https://advances.realtimerendering.com/s2015/AlexEvans_SIGGRAPH-2015-sml.pdf`, `https://www.mediamolecule.com/blog/article/siggraph_2015`). Secondary summary: edit lists of SDF primitives evaluated hierarchically into cached 8x8x8 bricks. The slides exceeded the fetch size cap (10 MB), so not read; no source found for a bytecode interpreter or its register storage in Dreams. GAP.
|
||||
- [web] vyre crate (`https://docs.rs/crate/vyre/0.3.0`): compiles boolean rule conditions to bytecode (49 opcodes) run on compute shaders, one rule per thread; no register layout described.
|
||||
- [src] Per `F:\Projects\CultLib\docs\cultmath-interval-ground-cut.md:1727-1731`, `F:\Projects\aetheria-material-path-prior-art.md` section 4 holds Dolphin ubershaders and Laine et al. on megakernel register pressure; "no measured interpreter-versus-kernel timing was found". I did not re-read that file.
|
||||
- [web] Divergence: an interpreter loop dispatches on data-dependent opcodes; Mesa ACO notes divergent control flow executes both paths with lanes masked (`https://android.googlesource.com/platform/external/mesa3d/+/b2fa2d99ae2/src/amd/compiler/README.md`). MPR makes every thread of a warp evaluate the same shortened tape (MPR section 4). [abs] A fixed trophic graph evaluated per point has the same property: all lanes run the same tape.
|
||||
- Gaps: no hit for libfive GPU work other than MPR itself; no IQ note on bytecode interpreters; no published shadertoy-style VM register layout; no shipped-game example of a bytecode interpreter in a shader found.
|
||||
|
||||
## 4. Register counts for the trophic graph
|
||||
|
||||
- [src] Aetheria `4354ef2c` (`docs/aetheria-fauna-map.md` B28-B36; `docs/research/2026-10-14-trophic-dag-probe/README.md`) gives layers and costs but no node or register count. B36: "each ring-stencil level multiplies the cost by 9" (krill .26 ms, minnows 3.19 ms = 9 krill, sharks 29.3 ms = 81 krill, Python single evaluation); stencils at most two levels deep (target invariant `trophic-dag-pointwise`). Probe layers: L1-glim, L2-krill (Skein x Holling of glim), L2-minnows (Holling of krill over the point + 8 taps on a 48 u ring), L3-sharks (minnows over a 160 u ring x band), L3-lanterns (slack x krill over a 120 u ring). No tape exists yet.
|
||||
- [src] `F:\Projects\CultLib\docs\cultmath-interval-ground-cut.md` Pass 7, T7 (`:1677`): in a graph a stencil is "input substitution" (the prey subgraph instantiated per tap with the position input replaced); the builder hash-conses identical nodes, so the 9 taps do not share nodes (their inputs differ). T7(b): site-span ops read an array of wells (per-batch buffer input). T7(c): the lattice weight is evaluated at the site, not the query point. Multi-result ops (cellular) are read through `Pick(node,k)`.
|
||||
- [src] CultMath already carries a multiplicatively weighted lattice fragment: `packages/cultmath/src/CultMath/Voronoi.cs:151-154` (`weight = o.x*0.5f+0.5f`, `d = max(d2.x,d2.y)*weight`) and an extern `cultmath_apollonian_voronoi_tones` (`:219`).
|
||||
- [abs] Synthetic probe `probe2.py` (Python; greedy MPR-style slot reuse, free at last use): a 9-tap mean-of-mean tree whose leaf is a 12-op chain with 2 live values. Slots depend on the schedule order, not on op count:
|
||||
|
||||
| stencil levels | ops | slots, depth-first with running accumulator | slots, level-order |
|
||||
|---|---|---|---|
|
||||
| 0 | 13 | 2 | 2 |
|
||||
| 1 | 125 | 3 | 18 |
|
||||
| 2 | 1,133 | 4 | 149 |
|
||||
| 3 | 10,205 | 5 | 1,230 |
|
||||
|
||||
A variant that computed all 9 child results before summing gave 10 / 18 / 26 slots at levels 1 / 2 / 3 (+8 per level; first probe script `probe.py`). The tree is a toy (no shared subexpressions, no span inputs, no wide intermediate such as cellular's multi-result values); real counts need the real graph. For scale, MPR's measured counts are 22 to 84 slots for 328 to 6056 clauses (ratio 12.6 to 72.1).
|
||||
- [abs] Fidget's frequency renumbering and the 255 cap mean the host knows `reg_count` before dispatch and can choose among pipelines (1a).
|
||||
|
||||
## 5. Scope addition: gravity bodies as Voronoi sites (operator sketch)
|
||||
|
||||
### 5a. Diagram variants over a small set of explicit weighted sites
|
||||
- [web] Wikipedia "Weighted Voronoi diagram" (`https://en.wikipedia.org/wiki/Weighted_Voronoi_diagram`, fetched): multiplicatively weighted = distance divided by a positive weight; boundaries circular arcs and straight segments in the plane; "A Voronoi cell may be non-convex, disconnected and may have holes". Additively weighted = weights subtracted from distance; boundaries hyperbola arcs and straight segments. Power diagram = weights subtracted from squared Euclidean distance. (The page does not state the Apollonius-circle name, convexity or the Delaunay relation; I did not source those.)
|
||||
- [abs] Equal-potential boundary: for point masses with potential `-G*M/r`, the locus where two bodies' potentials are equal has `r1/r2 = M1/M2`, a multiplicatively weighted Voronoi edge (weight = mass), an Apollonius circle in 2D. The probe's `slack` (weight = gravity radius, citing TD:20 Apollonius circles) is the same family. A sum of potentials `sum G*M_i/r_i` is not a Voronoi cell; its level sets and the points where pulls cancel are different objects (`slack` in the probe is "to first order" `(F2-F1)/|grad(F2-F1)|`). Equal-force (`M/r^2`) boundary: `r1/r2 = sqrt(M1/M2)`, multiplicative with weight `sqrt(M)`.
|
||||
- [src] Aetheria probe measurements (`docs/aetheria-fauna-map.md` B33-B34): the hard Gabriel graph over six bodies changes its edge set 29 times in 1,400 s (one gas-giant orbit), each a strand appearing or vanishing whole; the soft gate moves at most .00075 per 1/60 s. Largest presence step under the krill carriage: band .0035, slack .013, skein .054, lattice .124, skein-web .493.
|
||||
- [abs] Pop properties when a site moves: the distance field `F1` and the border measure `F2 - F1` are continuous in site position and weight; what jumps is the discrete label (which site is nearest) and adjacency (which cells touch). For multiplicatively weighted cells a cell can appear, vanish, split or become disconnected as weights change (Wikipedia above). With six bodies, a loop over all sites per point is exact; a lattice-based diagram needs its search window sized to the weight range (Aetheria B34: 5x5 sufficed for a 1.6x boost).
|
||||
- [web] Cosmic-web analogues: Hidding, van de Weygaert, Vegter, Jones, "Adhesion and the Geometry of the Cosmic Web" (arXiv 1211.5385) and van de Weygaert, "Tessellating the Universe: the Zel'dovich and Adhesion tiling of space" (arXiv 0912.4520, abstract read): the adhesion model (Zel'dovich approximation plus viscosity, Burgers' equation) yields in the zero-viscosity limit a "quasi-Voronoi" tessellation, with the Eulerian/Lagrangian relation tied to Voronoi/Delaunay duality. In that tessellation, 1-dimensional edges correspond to filaments (second most massive elements), vertices to clusters/nodes (most mass), faces/tiles to walls and voids. Voronoi foam kinematic model: matter condenses onto Voronoi walls, edges and nodes (search summary, arXiv 1611.01221 area). The abstract of 0912.4520 does not mention weighted or power diagrams or the distance metric; I found no source tying a weighted Voronoi to the cosmic web. Aetheria already cites a Zel'dovich warp (TD:39).
|
||||
- [abs] In 3D the Voronoi border set is faces (walls) meeting in edges (filament-like) at vertices; in 2D the borders are edges and the vertices are the node-like points. A rendered "filament" from a diagram's border in 3D is wall-like away from the edges.
|
||||
- [abs] Anisotropic/flow-stretched metric `d = |A(x-y)|` with constant per-site A elongates cells along A's long axis; a metric varying along the path is a geodesic with no closed form. Not sourced.
|
||||
|
||||
### 5b. Metric as a node parameter
|
||||
- [doc] Blender Voronoi Texture node (`https://docs.blender.org/manual/en/4.2/render/shader_nodes/textures/voronoi.html`, search summary): Distance metric Euclidean, Manhattan, Chebychev, Minkowski with an Exponent input; exponent 1 = Manhattan, 2 = Euclidean, infinite = Chebychev; manual examples use 0.5, 1, 2, 32 and treat 32 as an approximation of Chebychev; exponent below 1 gives star shapes. Features: F1, F2, Smooth F1, Distance to Edge, N-sphere Radius.
|
||||
- [src] Cycles implementation (`intern/cycles/kernel/svm/voronoi.h`, fetched from `raw.githubusercontent.com/blender/blender/main/`): `voronoi_distance(a,b,params)` is a template selecting with a chain of `if` on `params.metric`: Euclidean `distance`, Manhattan `reduce_add(fabs(a-b))`, Chebyshev `reduce_max(fabs(a-b))`, Minkowski `powf(reduce_add(power(fabs(a-b), exponent)), 1/exponent)`; unknown metric returns 0. `voronoi_distance_bound` is a cheaper variant that skips the square root (Euclidean) and the final root (Minkowski) for ordering inside the search loops; the true distance is computed once for output. Search windows: 2D 3x3 for F1/F2/edge/n-sphere, 5x5 for Smooth F1; 3D 3x3x3 (27 cells) and 5x5x5 (125) for smooth; 4D 81 and 625. A header comment records the non-smooth windows were reduced from -2..2 to -1..1 for performance.
|
||||
- [web] Houdini `mx_voronoi` VEX: integer `metric` argument, 0 Euclidean, 1 squared distance, 2 Manhattan (a mirror lists 3 Chebyshev) (`https://www.sidefx.com/docs/houdini/vex/functions/mx_voronoi.html`). Substance 3D Designer "Distance" node: Euclidean, Manhattan, Chebyshev (`https://experienceleague.adobe.com/en/docs/substance-3d-designer/using/substance-graphs/nodes-reference-for-substance-graphs/atomic-nodes/distance`); I did not find the Cells node's own metric options. 3Delight: Minkowski 1 = Manhattan, 2 = Euclidean, 4 rounded square, infinity Chebyshev (`https://documentation.3delightcloud.com/x/TgYuMg`).
|
||||
- [abs] Cost of a metric switch: Cycles' form is a runtime branch on a parameter uniform across a dispatch (all lanes take the same branch). A Minkowski exponent as a float input costs a `pow` per site pair plus one `pow` for the root; the bound variant avoids the root in loops. In a tape interpreter a constant metric can be chosen at lowering (ops in the graph) or dispatched at run time as one node.
|
||||
|
||||
### Gaps (section 5)
|
||||
- No source measured the per-evaluation cost of a metric switch; only the Cycles code structure is read.
|
||||
- No source found for weighted/power diagrams in cosmic-web modelling; the equal-potential = multiplicatively-weighted identity is derived here, not cited.
|
||||
- No numeric probe of cell geometry was run.
|
||||
52
docs/research/tape-register-model/probe2.py
Normal file
52
docs/research/tape-register-model/probe2.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Register need of a stencil tree under two schedules; MPR-style greedy slot reuse (free at last use).
|
||||
import sys
|
||||
LEAF=12 # ops in a shared-input leaf subgraph chain (krill-like): leaf = chain with 3 live temps
|
||||
def build(levels, taps=9):
|
||||
# node: ('leaf',) or ('mean', [children]) ; each tap child is an independent subgraph (offset input substituted)
|
||||
nodes=[] # (operands)
|
||||
def leaf():
|
||||
# leaf subgraph: LEAF ops, each uses previous two results (width 2 live) -> 2 live values
|
||||
base=len(nodes); nodes.append([]) # input
|
||||
prev=[base]
|
||||
for i in range(LEAF):
|
||||
ops=prev[-2:]
|
||||
nodes.append(ops); prev.append(len(nodes)-1)
|
||||
return prev[-1]
|
||||
def mean(l):
|
||||
if l==0: return leaf()
|
||||
acc=mean(l-1)
|
||||
for _ in range(taps-1):
|
||||
c=mean(l-1)
|
||||
nodes.append([acc,c]); acc=len(nodes)-1
|
||||
return acc
|
||||
root=mean(levels)
|
||||
return nodes,root
|
||||
def regs(nodes,order):
|
||||
pos={n:i for i,n in enumerate(order)}
|
||||
last={}
|
||||
for n in order:
|
||||
for o in nodes[n]: last[o]=max(last.get(o,-1),pos[n])
|
||||
live=0;peak=0;free=0;alloc=0
|
||||
for n in order:
|
||||
# free operands whose last use is here
|
||||
for o in set(nodes[n]):
|
||||
if last[o]==pos[n]: free+=1
|
||||
if free: free-=1
|
||||
else: alloc+=1
|
||||
if n not in last: free+=0
|
||||
return alloc
|
||||
def dfs_order(nodes):
|
||||
return list(range(len(nodes))) # construction order is postorder DFS
|
||||
def bfs_order(nodes,root):
|
||||
# level order by depth from root (reverse), a bad schedule
|
||||
depth={root:0}
|
||||
users={}
|
||||
for n,ops in enumerate(nodes):
|
||||
for o in ops: users.setdefault(o,[]).append(n)
|
||||
d=[0]*len(nodes)
|
||||
for n in range(len(nodes)-1,-1,-1):
|
||||
d[n]=max([d[u]+1 for u in users.get(n,[])],default=0)
|
||||
return sorted(range(len(nodes)),key=lambda n:(-d[n],n))
|
||||
for L in range(0,4):
|
||||
nodes,root=build(L)
|
||||
print(L,"ops",len(nodes),"slots_dfs",regs(nodes,dfs_order(nodes)),"slots_levelorder",regs(nodes,bfs_order(nodes,root)))
|
||||
Loading…
Reference in a new issue