Skip to content

Design: expand/restrict diagram-traversal algebra (2.4) - #1524

Draft
dimitri-yatsenko wants to merge 10 commits into
masterfrom
design/expand-restrict
Draft

Design: expand/restrict diagram-traversal algebra (2.4)#1524
dimitri-yatsenko wants to merge 10 commits into
masterfrom
design/expand-restrict

Conversation

@dimitri-yatsenko

Copy link
Copy Markdown
Member

What

Proposed 2.4 design (docs-only) deriving a small, composable algebra for diagram traversal that collapses Diagram.cascade / Diagram.trace / Diagram.restrict into two operations. Adds DESIGN-expand-restrict.md; no code changes.

The design

  • One primitive — propagate a restriction across a foreign key by semijoin, either direction.
  • R1 (edge rule) — the semijoin, with an identity fast-path when the FK is the whole primary key and unrenamed. Absorbs the former forward F1–3 / upward U1–3 rules into one.
  • R2 (group rule) — a restriction touching any part of a master's group brings the whole group (existential lift part→master, re-expand to all parts).
  • Diagram.expand(seed, direction="down"|"up"|"both") — additive constructor; grows a related sub-diagram from a single seed. cascade = expand("down") (delete blast radius) and trace = expand("up") (valid make() sources) are retained as aliases; self.upstream = expand(self & key, "up").
  • diagram.restrict(*conditions, direction=...) — subtractive progressive carving on any diagram (including one from expand): each table ends restricted by the conjunction of conditions reaching it. Chainable, order-independent, monotone.
  • Composition — both are transforms over one representation (union to grow, intersection to carve), so they chain freely; the current "cascade and restrict are mutually exclusive" restriction goes away.

Includes the "all data for mouse_id=5 and method_id=5" counterexample showing restrict is not reducible to combinations of expand.

Scope / relationship to 2.3.3

This is a 2.4 API evolution, not part of the 2.3.3 bug-fix line. The compositional-integrity issues #1496 / #1501 / #1481 fold into this redesign rather than shipping as standalone 2.3.3 patches. (The multi-FK-path walk + alias-docstring scrub already landed separately on the 2.3.3 branch.)

Open for review

  • restrict direction default and whether a descendant condition also trims ancestors (both) for a fully-consistent export slice.
  • Diagram.trace: descend through master-part boundaries (Merge-table upstream tracing) #1481expand("up") applies R2, so trace descends from an ancestor master into its parts (reproducibility-contract grounds); this flips the currently-pinned test_trace_stops_at_master_no_part_down_collection.
  • Platform behavior (Pipeline Navigator progressive-vs-batch carving) is a product decision; the library guarantees only the algebra.

Draft — opening for design review before any code or spec refactor.

Proposed 2.4 design that collapses Diagram.cascade / Diagram.trace /
Diagram.restrict into a small composable algebra, derived from first principles:

- One primitive: propagate a restriction across a foreign key by semijoin,
  either direction.
- R1 (edge rule): the semijoin, with identity fast-path for primary,
  non-renamed FKs; absorbs the former F1-3 / U1-3 rules.
- R2 (group rule): a restriction touching any part of a master group brings the
  whole group (existential lift + re-expand).
- Two operations: expand(seed, direction) — additive constructor (cascade=down,
  trace=up retained as aliases); and diagram.restrict(*conditions, direction) —
  subtractive progressive carving. They compose freely on one representation
  (union to grow, intersection to carve), removing the current cascade-vs-restrict
  mutual exclusivity.

Includes derivation, the mouse/method counterexample showing restrict is not
reducible to expand, and open follow-ups (restrict direction default, #1481
master->parts trace descent, release scoping).
@dimitri-yatsenko dimitri-yatsenko added the documentation Issues related to documentation label Jul 30, 2026
Align to DataJoint's canonical operator names (& = restriction). These
foreign-key-matched narrowings are restrictions, not joins.
Replace primed set-variables and union/intersection symbols with DataJoint
expressions (`&`, restrict-by-list for OR, chained `&` for AND).
Adds section 6 deriving how expand(A & r) propagates across renamed foreign keys,
distinguishing the dict-key seed (relabel keys through the edge, values unchanged)
from the general-condition seed (restrict the neighbor by the renamed projection).
Written to stand alone, in DataJoint operators.
…ed FKs

Restructure section 6 around the three kinds of seed restriction — materialized
(dict / sequence of dicts, e.g. A.keys()), subquery, and string query — and how
each crosses a renamed foreign key. Add the traversal rule for materialized keys:
relabel the carried attributes and drop any key field the neighbor lacks (which
is what makes the upstream walk yield exactly the parent's key). Note the drop is
exact for identity fields and, for extra constraints, resolved by materializing
first; unify live restrictions to the materialized path via (A & r).keys().
Clarify that expand(A & key) uses the relabel fast-path only when key contains
solely primary-key attributes of A. A secondary attribute is referenced by no
foreign key, so it cannot propagate — a key mixing one in is really an opaque
A & cond (Kind 2/3), reducible to Kind 1 via (A & cond).keys(). Per Dimitri's
correction.
…xpand/restrict fallback

Replace the PK-only framing with the per-edge principle: an attribute propagates
across an edge iff that edge carries it (so a secondary FK attribute propagates
along its edge; a data attribute never does). Derive the up/down rule from the FK
being a function: down always suffices with a parent key; up needs the parent's
full primary key. Add the confirmed additive/subtractive fallback: when the up
key is insufficient, expand must materialize (exactness) while restrict may skip
(conservative carve). Same fast-path condition; only the fallback differs.
Derive how a materialized key crosses a master<->part boundary: R2 needs no new
key machinery. The existential lift (part->master) is relabel-drop, with sibling
part keys collapsing onto one master key by dedup; the atomic re-expansion
(master->all parts) is a fresh downstream relabel from the recovered master key
that widens by dropping the part-specific constraint. The round trip is lossy on
the part-specific attribute, and that loss is compositional atomicity in key
terms. Corollaries: materialized seeds stay materialized (delete-safe for free);
one mechanism serves cascade/trace/restrict.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Issues related to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant