Skip to content

Posets

A Poset (partially ordered set) is a directed acyclic graph (DAG) where nodes are Events and edges represent causal relationships. If event A caused event B, there is an edge from A to B. Events with no causal connection are considered independent, meaning they may have happened concurrently or in any relative order.

Building a Causal Graph

Create a Poset, add events, and declare which events caused which. The caused_by parameter accepts a list of predecessor events.

poset_example.py python
1from pyrapide import Event, Poset
2
3poset = Poset()
4
5request = Event(name="api.request", payload={"endpoint": "/data"})
6poset.add(request)
7
8db_query = Event(name="db.query", payload={"sql": "SELECT *"})
9poset.add(db_query, caused_by=[request])
10
11cache_check = Event(name="cache.check", payload={"key": "data"})
12poset.add(cache_check, caused_by=[request])
13
14assert poset.are_independent(db_query, cache_check)
15assert poset.is_ancestor(request, db_query)

In this example, the request event causes both db_query and cache_check. Because neither the database query nor the cache check caused the other, they are independent; they could have executed concurrently.

Querying Relationships

The Poset API provides several methods for exploring causal structure:

  • is_ancestor(a, b): returns True if a causally precedes b (directly or transitively).
  • is_descendant(a, b): returns True if a is causally after b.
  • are_independent(a, b): returns True if neither event is an ancestor of the other.
  • predecessors(e): returns the set of events that directly caused e.
  • successors(e): returns the set of events directly caused by e.
  • ancestors(e): returns all transitive ancestors of e.
  • descendants(e): returns all transitive descendants of e.

Cycle Prevention

Warning
Causality cannot be circular. If you attempt to create a cycle (for example, declaring that A caused B and B caused A), PyRapide raises a CausalCycleError immediately.

The Poset enforces the DAG invariant on every add() call. This means you can always trust that the causal graph is well-formed, regardless of the order in which events arrive.

cycle_error.py python
from pyrapide import Event, Poset, CausalCycleError

poset = Poset()
a = Event(name="a", payload={})
b = Event(name="b", payload={})

poset.add(a)
poset.add(b, caused_by=[a])

try:
    poset.add(a, caused_by=[b])   # Would create a cycle
except CausalCycleError as e:
    print(f"Cycle detected: {e}")

Partial Order Semantics

The "partial" in partially ordered set is key. Unlike a total order (where every pair of elements is comparable), a Poset only orders events that are causally related. Two independent events have no defined order, and that is by design. This faithfully models the reality of distributed systems where concurrent operations have no inherent ordering.

💡 Tip
Think of a Poset as a precise record of what you know about ordering. If two events are independent, it means there is genuinely no information linking their relative timing.

For higher-level management of events including clock tracking and query utilities, see Computations.