# 6.1 Updating Events and Causal Dependence

Consider the rule:

{{x, y}, {x, z}} -> {{x, y}, {x, w}, {y, w}, {z, w}}
RulePlot[ResourceFunction[ "WolframModel"][{{x, y}, {x, z}} -> {{x, y}, {x, w}, {y, w}, {z, w}}]]

When we discussed this rule previously, we showed the first few steps in its evolution as:

ResourceFunction[ "WolframModel"][{{x, y}, {x, z}} -> {{x, z}, {x, w}, {y, w}, {z, w}}, {{1, 1}, {1, 1}}, 4]["StatesPlotsList", "MaxImageSize" -> 100]

But to understand the updating process in our models in more detail, it is helpful to “look inside” these steps, and see the individual updating events of which they are comprised:

With[{eo = ResourceFunction[ "WolframModel"][{{x, y}, {x, z}} -> {{x, z}, {x, w}, {y, w}, {z, w}}, {{0, 0}, {0, 0}}, 4]}, TakeList[eo["EventsStatesPlotsList", ImageSize -> Tiny, VertexLabels -> Automatic], eo["GenerationEventsCountList", "IncludeBoundaryEvents" -> "Initial"]]]

The 22 42 signature of the rule means that in each updating event, two relations are destroyed, and four new ones are created. In the pictures above, new relations from each event are shown in red; the ones that will disappear in the next event are shown dotted. The elements are numbers in the sequence they are created.

There are in general many possible sequences of updating events that are consistent with the rule. But in making the pictures above (and in much of our discussion in previous sections), we have used our “standard updating order”, in which each step in the overall evolution in effect includes as many non-overlapping updates as will “fit”. (In more detail, what is done is that in each overall step, relations are scanned from oldest to newest, in each case using them in an update event so long as this can be done without using any relation that has already been updated in this overall step.)

Our modelsand the hypergraphs on which they operateare in many ways more difficult to handle than the string-based systems we discussed in the previous section. But one way in which they are simpler is that they more directly expose causal relationships between events. To see if an event B depends on an event A, all we need do is to see whether elements that were involved in A are also involved in B.

Looking at the sequence of updates above, therefore, we can immediately construct a causal graph:

getWolframModelMultiwayEventRenderer[instantiatedRule_] := Module[{events}, events = ResourceFunction["WolframModel"][<| "PatternRules" -> instantiatedRule|>, instantiatedRule[], <|"MaxEvents" -> 1|>][ "EventsStatesPlotsList", VertexLabels -> Automatic] /. Text[content_, args__] :> Text[Style[content, 7], args]; Graphics[{Inset[First[events], {0, 0}, Right, Scaled[3.5]], Inset[Graphics[{Thickness[0.011494], FaceForm[{RGBColor[0.5, 0.5, 0.5], Opacity}], FilledCurve[{{{0, 2, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}}}, {{{66.24, 72.75}, {28.6875, 14.39}, {14.99, 14.39}, {52.61, 72.75}, {14.99, 131.03}, {28.69, 131.03}, {66.24, 72.75}}}]}], {1, 0}, Center, Scaled], Inset[Last[events], {2, 0}, Left, Scaled[3.5]]}]] CloudGet["https://wolfr.am/LmHho8Tr"]; newgraph[ With[{evolutionObject = ResourceFunction[ "WolframModel"][{{{x, y}, {x, z}} -> {{x, y}, {x, w}, {y, w}, {z, w}}}, {{0, 0}, {0, 0}}, 3]}, evolutionObject["CausalGraph", VertexSize -> 1.3, VertexShapeFunction -> (With[{inOutEdges = Rule @@ (evolutionObject["AllEventsEdgesList"][[#]] & /@ evolutionObject["EventsList"][[#2, 2]])}, Inset[Framed[getWolframModelMultiwayEventRenderer@inOutEdges, FrameStyle -> Directive[Hue[0.09, 1, 0.91], Opacity[0.7]], Background -> Directive[{Hue[0.14, 0.34, 1], Opacity[0.2]}]], #1, Center, #3]] &), PerformanceGoal -> "Quality"]]]

Another feature of our models is that every element can be thought of as having a unique “lineage”, in that it was created by a particular updating event, which in turn was the result of some other updating event, and so on. When we introduced our models in section 2, we just said that any element created by applying a rule should be new and distinct from all others. If we were implementing the model, this might then make us imagine that the element would have a name based on some global counter, or a UUID.

But there is another, more deterministic (as well as more local and distributed) alternative: think of each new element as being a kind of encapsulation of its lineage (analogous to a chain of pointers, or to a hash like in blockchains  or Git). In the evolution above, for example, we could describe element 10 by just saying it was created as part of the relation {2,10} from the relations {{2,4},{2,5}} (as the second part of the output of an update that uses them)but then we could say that these relations were in turn created from earlier relations, and so on recursively, all the way back to the initial state of the system:

CloudGet["https://wolfr.am/Lc04uSZO"]; Column[ Most[ElementsBacktrace[ ResourceFunction[ "WolframModel"][{{x, y}, {x, z}} -> {{x, z}, {x, w}, {y, w}, {z, w}}, {{0, 0}, {0, 0}}, 4], 40]], Frame -> All, FrameStyle -> Gray, BaseStyle -> "Text"]

The final expression here can also be written as:

frameit[{x__}] := Row[{x}, Frame -> True, FrameMargins -> 2, RoundingRadius -> 3, FrameStyle -> Lighter[Orange, .5]]; frameit := Style["\[Bullet]", Small, Gray]; frameit[x_] /; Head[x] =!= List := x; MapAll[frameit, {{{{{{0, 0}, {0, 0}} \[RightTriangle] 1, {{0, 0}, {0, 0}} \[RightTriangle] 2} \[RightTriangle] 1, {{{0, 0}, {0, 0}} \[RightTriangle] 1, {{0, 0}, {0, 0}} \[RightTriangle] 2} \[RightTriangle] 2} \[RightTriangle] 4, {{{{0, 0}, {0, 0}} \[RightTriangle] 1, {{0, 0}, {0, 0}} \[RightTriangle] 2} \[RightTriangle] 3, {{{0, 0}, {0, 0}} \[RightTriangle] 3, {{0, 0}, {0, 0}} \[RightTriangle] 4} \[RightTriangle] 1} \[RightTriangle] 3} \[RightTriangle] 2}] /. RightTriangle :> (Subscript[#1, Style[#2, Lighter[Blue, .4]]] &)

Roughly what this is doing is specifying an element (in this case the one we originally labeled simply as 10) by giving a symbolic representation of the path in the causal graph that led to its creation. And we can then use this to create a unique symbolic name for the element. But while this may be structurally interesting, when it comes to actually using an element as a node in a hypergraph, the name we choose to use for the element is irrelevant; all that matters is what elements are the same, and what are different.