What are attributes and coordinates, really? A field/signature model (and maybe a unified store) #1000
d-chambers
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Posting this as a possible direction to kick around, not a plan. It came out of trying to sharpen what attributes and coordinates actually are, since a lot of recent work (#757, #983, #987, #988) has been legislating the boundary between them case by case.
The itch
An attribute looks a lot like a coordinate that happens to be constant.
quality="good"says something about every sample in the patch, the same way thetimecoordinate says something about every sample in a row. Yet the two live in different containers with different rules, and every combination-policy question (kind, conflict, missing values, promotion) has needed its own debate.A model
Take the single sample as the atomic unit. Then most patch metadata is a field over the sample set, classified by which dims its values vary along — call that its signature.
timehas signature("time",), a per-channel depth has("distance",), andquality="good"has signature(): constant, broadcasting over the whole array. In this reading an attribute is just the()corner of a lattice whose top isdimsitself, and "attr" vs "coord" stops being a difference in kind.Promotion and demotion are movement along the lattice: concatenate patches that disagree on an attr and it becomes a coordinate along the joined dim (#987); collapse a dim and a coordinate can become an attr. The round trip should be lossless.
But not everything in
attrsis a field. Classify each item by how it responds to sample operations (select, chunk, concatenate — they change which samples you have) versus value operations (multiply, envelope — they change what the numbers are):acquisition_key, kind attrs)data_units,data_type)3.2 m/sis the value, andm/sisn't a label about the sample. That's why squaring a patch squares the units but touches no label, and why the only correct resolution for differing units is conversion —keep_firston units turned out to mean splicing m- and km-scaled samples into one unlabeled array (caught twice independently in the Make a missing attr value a value where patches are partitioned #988 review).history, the ids) is in neither cell: it isn't state at all but a record of the path. Two patches with different data and the same route share aprocessing_id; two with equal data and different routes don't. That's why provenance is (and should stay) exempt from every comparison rule.A third layer: values vs references. Some attrs are plain values — nothing to know about
"good"beyond its spelling. Others are references to entities that exist outside any patch:acquisition_keypoints at an Inventory node that owns its own facts (geometry, instrument, coupling). The model's placement rule: facts about an entity belong on the entity and get projected onto patches, never copied as per-patch truth. Disagreement between copies ofgauge_lengthacross one acquisition's patches is data corruption, not a "conflict" for a merge policy to arbitrate.The tutorial-sized version of all of this: coordinates say where a sample is, attributes are coordinates that happen to be constant, units say what the numbers mean, the inventory says what the data is of, and history says what was done to it.
Recent work already points this way
conflict="promote", which is the lattice's up-move.Each of these was argued independently; the model above predicts all of them, which is some evidence it's the right one.
A possible direction for the data model
If we took the model seriously, the containers could follow it:
CoordManagerthat allows signature().coordsandattrsbecome views:coords= entries with non-empty signature;attrs= the()entries plus data description and provenance, presenting exactly today's interface. One store means name collisions are unrepresentable and promotion is a signature change both views see instantly, rather than a hop between containers.PatchAttrsstops being state and keeps its three real jobs: schema declaration (per-name types/validators/roles, enforced when a field is written to the store), boundary DTO (FiberIO readers and the index keep their current contract), and the materializedattrsview (built without re-validation).None/NaN/""sentinel conventions and theexclude_unsettracking disappear, because "not stated" is simply "no entry."patch.meta(name to bikeshed) as the uniform access point, andselectworking over any named field — selecting on a()-field is the natural keep-all-or-none filter.Backward compatibility looks surprisingly good: the constructor keeps accepting
coords=/attrs=and decomposes them, readers and writers go through the views, andpatch.attrs.data_unitsstill works. The churn is internal (store, fingerprints, serialization) — large but mechanical.What this would not change
data_unitsstays factored out (it's a compressed representation, not an annotation).dimsstays structural — it's the domain itself (signatures are subsets of it), not metadata in any container.Questions
meta?fields? Something else?gauge_lengthlive long term — patch attrs (denormalized) or inventory-only with projection?Curious what people think, especially about failure modes of the model — places where a real workflow doesn't fit it.
All reactions