@@ -35,27 +35,21 @@ about the deliberately matching semantics:
3535 discriminator, and ` tests/test_ndsel_tensorstore.py ` loads our bodies into
3636 ` tensorstore.IndexTransform(json=...) ` and round-trips them back through our
3737 engine layer.
38-
39- The representations differ in one place: index arrays. Both models want an index
40- array at the transform's full input rank, with singleton axes for the dimensions
41- a map does not vary over. TensorStore enforces it — its JSON parser rejects a
42- rank-1 array over a rank-2 domain outright, with `Index array for output
43- dimension 0 has rank 1 but must have rank 2` (checked against tensorstore
44- 0.1.84) — while our loader is the more permissive of the two and also accepts a
45- lower-rank array that broadcasts against the input domain. That is a
46- compatibility affordance, not a difference in the model: ndsel leaves index-array
47- rank to [ the engine layer] ( ndsel.md#lowering-to-a-transform ) , and everything the
48- algebra builds itself is at full rank.
49-
50- The reason full rank matters here is that we * derive* meaning from those
51- singletons rather than merely tolerating them: an array full-sized on one axis
52- and singleton elsewhere is orthogonal, and one varying over several shared axes
53- is vectorized, so the distinction is readable off the shape — and the shape is
54- the * only* place it lives. An earlier ` ArrayMap.input_dimension ` field pinned
55- the orthogonal axis redundantly and was retired: the one shape it disambiguated
56- (a single-coordinate array, all axes singleton) is now normalized away at
57- construction, collapsed to the ` ConstantMap ` it equals, exactly as
58- [ the serializer] ( api/json.md ) has always collapsed it on the wire.
38+ - ** Chunk partitioning.** Both factor a transform over a grid before visiting
39+ any cell, rather than intersecting the whole transform with each chunk.
40+ TensorStore's ` IndexTransformGridPartition ` holds strided sets and index
41+ array sets and derives a per-cell transform on iteration;
42+ [ ` GridPartition ` ] ( api/chunk_resolution.md ) also partitions index-array
43+ dependency components. Its ` StridedSet ` is per storage axis, where TensorStore's
44+ is per input dimension and spans every grid axis reading it (which is how
45+ TensorStore factors diagonals). ` joint_sets ` holds one ` JointSet ` per
46+ connected index-array component, including independent single-array
47+ components within a mixed request. Both derive the per-chunk transforms
48+ from the partition ([ the guide] ( guide/index.md#a-plan-is-a-product-of-per-axis-tables )
49+ shows the tables). TensorStore keeps strided sets implicit, while this
50+ library materializes their per-axis rows for vectorized consumers. Diagonals are rejected here; supporting them needs a strided set per
51+ * input* dimension spanning every storage axis that reads it, TensorStore's
52+ representation.
5953
6054Four deliberate differences:
6155
@@ -85,11 +79,14 @@ safe, `partial` proves it is not, and `unknown` conservatively covers fancy
8579selections whose duplicates would require additional work to classify.
8680
8781The comparison also runs the other way. TensorStore is a mature, heavily
88- optimized C++ system whose performance this library cannot approach: resolution
89- here is Python-level bookkeeping over NumPy, and the per-part overhead is
90- significant. This library is small and depends on nothing beyond NumPy, so the
91- algebra can be adopted by a Python project that wants the model without the C++
92- runtime.
82+ optimized C++ system whose performance this library cannot approach. Independent strided planning
83+ here is per axis, but each materialized ` ChunkProjection ` is
84+ Python-level bookkeeping over NumPy — two domains, two transforms and the
85+ projection itself — so the per-part overhead of the object view is
86+ significant; a consumer that reads the partition's tables directly pays no
87+ per-chunk object construction. This library is small and depends on nothing beyond
88+ NumPy, so the algebra can be adopted by a Python project that wants the model
89+ without the C++ runtime.
9390
9491## Bounding-box selections vs query selections
9592
@@ -277,15 +274,20 @@ rewritten in place. Resolution classifies the result by structure
277274(` index_array_structure ` ): pure per-axis outer products keep the orthogonal
278275resolvers, and everything else — correlated maps, mixtures, index arrays
279276sharing an input axis (a diagonal gather, reachable only by hand-building a
280- transform) — takes the pointwise path that collapses the joint block.
277+ transform) — takes the general reader/intersection path. Chunk planning
278+ factors index arrays into connected dependency components before flattening,
279+ so independent groups do not expand one another. Vectorized selection preserves
280+ broadcast singletons to retain those dependencies.
281281
282282Three limits remain, all intentional and all expected to be lifted:
283283
284- - ** Affine diagonals.** A hand-built transform in which an * index array* and a
285- * slice map* bind the same input dimension, or two slice maps share one, is
286- rejected at resolution with ` NotImplementedError ` . No selection dialect
287- produces one; supporting them means lowering the slice maps into the joint
288- block too. * Planned.*
284+ - ** Affine diagonals.** A hand-built transform in which two output maps read
285+ one input dimension — two slice maps, or a slice map and an orthogonal index
286+ array — is rejected at planning with ` ValueError ` ; a correlated index array
287+ varying over a dimension a slice map also reads is rejected with
288+ ` NotImplementedError ` . No selection dialect produces either. Supporting them
289+ needs a strided set per * input* dimension spanning all dependent storage
290+ axes, TensorStore's connected-component representation. * Planned.*
289291- ** Finite explicit bounds only.** ` IndexDomain ` has no implicit or unbounded
290292 dimensions; the message layer will normalize a body with ` "-inf" ` /` "+inf" `
291293 bounds, but the engine layer refuses to lower one into a transform.
@@ -294,3 +296,8 @@ Three limits remain, all intentional and all expected to be lifted:
294296 dimension labels and the wire format round-trips them, but indexing
295297 operations build new domains without them, so a label does not survive a
296298 slice. * Planned.*
299+
300+ ## Selection to chunk operations
301+
302+ [ The selection-flow guide] ( guide/selection-flow.md ) traces normalization,
303+ partitioning, and paired chunk-local/request projections with executable examples.
0 commit comments