Skip to content

Conversation

@gordonbrander
Copy link
Contributor

@gordonbrander gordonbrander commented Jul 28, 2024

Following Radul, “Propagation Networks: A Flexible and Expressive Substrate for Computation”, Ch. 5.5, this PR explores state updates / resolving the diamond problem via tracking the causes of state changes using a vector clock that lives on the mergeable data structure.

TODO:

  • Notes on changes
  • Wrap Cell<State<T>> in a facade that facilitates this use case a bit more ergonomically
    • Plan: CellView proxy that makes this all look like ordinary property mutation
  • Tests for graph cycles

Approach

  • TODO outline approach from paper

Benefits

  • Glitch-free state updates
  • Resolves diamond problem via vector clock information recorded on data structure vs through batching or topological sorting in the scheduler
    • works with cycles (unlike batching)
  • Uses .merge() method mechanism that already exists on propagators
  • Allows for multiple update semantics using the same cell type, including last-write wins for state, or "never miss an update" events.

Changes

  • Tasks are now modeled as any object that implements .poll().
    • Previously we were modeling them as closures
    • Modeling propagators as instances with poll allows us to attach metadata to propagator instances, such as the cells they are attached to, etc. This will allow us to do things like serializing the graph later.
  • Tasks are given to a scheduler to be run
    • This "flattens" call stack generated by cell notifications so it traverses the cell graph breadth-first, rather than depth-first.
  • Put debug logs behind if(debug()) statements for efficiency

@seefeldb
Copy link
Contributor

seefeldb commented Jul 29, 2024

Nice! Can you spell out the loop semantics that are now supported?

E g. is a propagator that writes into what it reads called again immediately until it reaches a fix point? What if the merge result is different from what it wrote?

E.g. if there are two interlocked loops, but both get fed from outside as well, do computations first finish within before external data comes in, or is that undefined? (IOW, is there a sense of concentric loops, and what happens when they aren't clearly concentric?)

FWIW, my previous assumptions were:

  • queue events up until network seems quiesced down (so all loops reached a fix point)
  • assume that a proper propagator network will reach a fix point eventually and all we do is try to optimize computation cost (so define max iterations and stop after that, since this might be violated)
  • don't right away call a propagator again when they read their own output (experimental/unsure; mostly because i wanted to support r/w cells and because i felt if looping was the goal it could run the loop inside of it)

#113 implements this with a topological sort like so:

  • topological sort with pending inputs and following dependencies (so not just the graph!)
  • when encountering cycles prioritize nodes with small in-degree
  • repeat until fix point is reached or max iterations elapsed

for nice concentric loops this should behave the same way, since they are themselves topologically sorted. when that's not the case, extra runs can occur, but i don't know how else it would be well-defined. i figure it practice those are cases that look like complex loops in the graph, but only one leg runs at a time anyway.

(this isn't meant as a critique of the approach, just trying to compare notes)

Also:
- Change Cell.get() to Cell.value
- Change State.next() to State.step()
- Don't return new state from step if values are equal
@gordonbrander
Copy link
Contributor Author

gordonbrander commented Jul 30, 2024

Nice! Can you spell out the loop semantics that are now supported?

An example of a graph cycle can be found in the tests: https://github.com/commontoolsinc/labs/pull/121/files#diff-1c61eca3f1104743ce34c8d18d757588bf67002cdde58742ed908ab27ebed257R94

The implementation is based on the notes in Radul Ch. 5.5, and works like this:

  • Cells may contain a data structure called State
    • State contains a value, a time and a causes
      • Causes is a Record<string, number> (ID to time) which functions as a vector clock.
      • A state is always its own cause. In addition, if the state is derived from other states, it carries the causes of those states forward.
    • When a state advances, it increments its time
    • When a state update is attempted (via state.merge(other)), the merge function checks that the causes are not stale. If they are stale, the state update is ignored. Incoming states will be ignored until their causes are fresh.
  • When a cell has a "diamond problem" (multiple upstream dependencies that update from the same source), we make no attempt to de-dupe the notifications from the upstream dependencies. Instead, the cell is called upon to update multiple times, once per upstream dependency update. However, this does not cause a glitch because if the state merge sees that some of the causes are stale, it will not update. The cell's state will only be updated when all of the causes are fresh, e.g. only once, at the time that the relevant causal updates have propagated through the graph. Downstream dependencies will only be notified once, at the time the state is updated.
  • Cycles work with this approach because it's based on waiting for the vector clock times quiesce before updating the cell value. The cycle will loop back around with a new value at t=t+1, and so will update until the value quiesces.
    • This is in keeping with the approach propagators take to cycles in general: they're allowed to propagate until they quiesce.

This makes state keyable... step one in making Cell<State<T>> pathable.
@jsantell
Copy link
Collaborator

jsantell commented Mar 5, 2025

Closing out stale PRs, please reopen if this is relevant

@jsantell jsantell closed this Mar 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants