# 6.7 Typical Causal Graphs

As we discussed in 5.12, causal graphs for string substitution systems tend to have fairly simple structures. Causal graphs for our models tend to be considerably more complicated, and even among 22 32 rules considerable diversity is observed. A typical random sample of different forms is:

CloudGet["https://wolfr.am/LlK870RS"]; distincts = First /@ GatherBy[ ResourceFunction[ "ParallelMapMonitored"][(WolframModelTest[#, Automatic][ "EvolutionObject"]["CausalGraph"]) -> # &, Import["https://www.wolframcloud.com/obj/wolframphysics/Data/\ RuleEnumerations/22-32c.wxf"]], First]; SeedRandom[2426]; GraphicsGrid[ Partition[ Show[First[#]] /. Arrowheads[Medium] -> Arrowheads[0.1] & /@ RandomSample[distincts, 8*6], 8], ImageSize -> Full]

Even rules whose states seem quite simple can produce quite complex causal graphs:

trints = {{{{1, 2}, {2, 3}} -> {{3, 2}, {3, 2}, {2, 4}}, {{0, 0}, {0, 0}}, 50}, {{{1, 1}, {1, 2}} -> {{2, 2}, {3, 2}, {1, 3}}, {{0, 0}, {0, 0}}, 130}, {{{1, 2}, {1, 3}} -> {{1, 2}, {2, 3}, {3, 4}}, {{0, 0}, {0, 0}}, 17}, {{{1, 2}, {1, 3}} -> {{1, 2}, {1, 2}, {2, 4}}, {{0, 0}, {0, 0}}, 15}, {{{1, 2}, {2, 3}} -> {{1, 3}, {3, 2}, {2, 4}}, {{0, 0}, {0, 0}}, 16}, {{{1, 2}, {1, 3}} -> {{2, 3}, {3, 1}, {1, 4}}, {{0, 0}, {0, 0}}, 22}, {{{1, 2}, {1, 3}} -> {{2, 3}, {2, 4}, {1, 3}}, {{0, 0}, {0, 0}}, 15}, {{{1, 2}, {2, 3}} -> {{1, 1}, {1, 4}, {4, 3}}, {{0, 0}, {0, 0}}, 13}, {{{1, 2}, {1, 3}} -> {{2, 3}, {3, 4}, {4, 1}}, {{0, 0}, {0, 0}}, 22}, {{{1, 2}, {1, 3}} -> {{1, 4}, {1, 2}, {3, 4}}, {{0, 0}, {0, 0}}, 14}, {{{1, 2}, {1, 3}} -> {{2, 4}, {2, 1}, {4, 1}}, {{0, 0}, {0, 0}}, 130}, {{{1, 2}, {1, 3}} -> {{2, 4}, {4, 3}, {3, 1}}, {{0, 0}, {0, 0}}, 26}, {{{1, 2}, {2, 3}} -> {{1, 4}, {1, 2}, {4, 3}}, {{0, 0}, {0, 0}}, 16}, {{{1, 2}, {1, 3}} -> {{4, 4}, {2, 4}, {3, 4}}, {{0, 0}, {0, 0}}, 17}, {{{1, 2}, {3, 2}} -> {{1, 4}, {1, 3}, {4, 2}}, {{0, 0}, {0, 0}}, 130}, {{{1, 2}, {3, 2}} -> {{2, 4}, {2, 1}, {4, 3}}, {{0, 0}, {0, 0}}, 30}}; Partition[ ResourceFunction["ParallelMapMonitored"][ With[{w = (ResourceFunction["WolframModel"] @@ #)}, GraphicsRow[{ResourceFunction["WolframModelPlot"][w["FinalState"], ImageSize -> 50], Show[w["CausalGraph"], ImageSize -> 100]}]] &, trints], 4]

As a first example, consider the rule:

{{x, x}, {x, y}} -> {{y, y}, {z, y}, {x, z}}

At each step, the self-loop just adds a relation, and effectively moves around the growing loop:

ResourceFunction[ "WolframModel"][ {{x, x}, {x, y}} -> {{y, y}, {z, y}, {x, z}}, {{0, 0}, {0, 0}}, 15]["StatesPlotsList", "MaxImageSize" -> 50]

The causal graph captures the causal connections created by the self-loop encountering the same relations again after it goes all the way around:

ResourceFunction[ "WolframModel"][ {{x, x}, {x, y}} -> {{y, y}, {z, y}, {x, z}}, {{0, 0}, {0, 0}}, 15, "CausalGraph"]

The structure gets progressively more complicated:

ResourceFunction[ "WolframModel"][ {{x, x}, {x, y}} -> {{y, y}, {z, y}, {x, z}}, {{0, 0}, {0, 0}}, 50, "CausalGraph"] // LayeredGraphPlot

Re-rendering this gives

ResourceFunction[ "WolframModel"][ {{x, x}, {x, y}} -> {{y, y}, {z, y}, {x, z}}, {{0, 0}, {0, 0}}, 50, "CausalGraph"]

or after 500 steps:

ResourceFunction[ "WolframModel"][ {{x, x}, {x, y}} -> {{y, y}, {z, y}, {x, z}}, {{0, 0}, {0, 0}}, 500, "CausalGraph"]

As another example, consider the rule:

{{x, y}, {x, z}} -> {{y, w}, {y, x}, {w, x}}

Here are the first 25 steps in its evolution (using our standard updating order):

ResourceFunction[ "WolframModel"][ {{x, y}, {x, z}} -> {{y, w}, {y, x}, {w, x}}, {{0, 0}, {0, 0}}, 25]["StatesPlotsList", "MaxImageSize" -> 50]

After a few steps all that happens is that there is a small structure that successively moves around the loop creating new “hairs”. The causal graph (here shown after 25 steps) captures this process:

ResourceFunction[ "WolframModel"][ {{x, y}, {x, z}} -> {{y, w}, {y, x}, {w, x}}, {{0, 0}, {0, 0}}, 25, "CausalGraph"]

An alternative rendering shows that a grid structure emerges:

With[{w = ResourceFunction[ "WolframModel"][ {{x, y}, {x, z}} -> {{y, w}, {y, x}, {w, x}}, {{0, 0}, {0, 0}}, 25, "CausalGraph"]}, Graph[w, VertexCoordinates -> (RotationMatrix[48 Degree] . # & /@ GraphEmbedding[w, "SpringElectricalEmbedding"])]]

Here are the corresponding results after 100 steps:

{LayeredGraphPlot[#, AspectRatio -> 3], Rotate[#, 115 Degree]} &@ ResourceFunction[ "WolframModel"][ {{x, y}, {x, z}} -> {{y, w}, {y, x}, {w, x}}, {{0, 0}, {0, 0}}, 100, "CausalGraph"]

As a somewhat different example, consider the rule:

{{x, y}, {y, z}} -> {{x, w}, {x, y}, {w, z}}
ResourceFunction[ "WolframModel"][{{x, y}, {y, z}} -> {{x, w}, {x, y}, {w, z}}, {{0, 0}, {0, 0}}, 15]["StatesPlotsList", "MaxImageSize" -> 70]

After the same number of steps, one can effectively see the separate trees in the causal graph:

ResourceFunction[ "WolframModel"][{{x, y}, {y, z}} -> {{x, w}, {x, y}, {w, z}}, {{0, 0}, {0, 0}}, 15, "LayeredCausalGraph"]

Re-rendering the causal graph, it has a structure that is quite similar to the actual state of the system:

ResourceFunction[ "WolframModel"][{{x, y}, {y, z}} -> {{x, w}, {x, y}, {w, z}}, {{0, 0}, {0, 0}}, 15, "CausalGraph"]

Continuing for a few more steps, a definite tree structure emerges:

ResourceFunction[ "WolframModel"][{{x, y}, {y, z}} -> {{x, w}, {x, y}, {w, z}}, {{0, 0}, {0, 0}}, 20, "CausalGraph"]

It is not uncommon for a causal graph to “look like” the actual hypergraph generated by one of our models. For example, rules that produce globular structures tend to produce similar “globular” causal graphs (here shown for three 22 42 rules from section 3):

ResourceFunction["ParallelMapMonitored"][ With[{w = ResourceFunction["WolframModel"][#, {{0, 0}, {0, 0}}, 12]}, {ResourceFunction["WolframModelPlot"][w["FinalState"]], LayeredGraphPlot[w["CausalGraph"], AspectRatio -> 1/3], w["CausalGraph"]}] &, {{{x, y}, {x, z}} -> {{y, z}, {y, w}, {z, w}, {w, x}}, {{x, y}, {y, z}} -> {{x, y}, {y, x}, {w, x}, {w, z}}, {{x, y}, {y, z}} -> {{w, y}, {y, z}, {z, w}, {x, w}}}]

Rules that exhibit slow growth often yield either grid-like or “hyperbolic” causal graphs (here shown for some 23 33 rules from section 3):

ResourceFunction["ParallelMapMonitored"][ With[{w = ResourceFunction["WolframModel"][#, {{0, 0, 0}, {0, 0, 0}}, 400]}, {ResourceFunction["WolframModelPlot"][w["FinalState"]], LayeredGraphPlot[w["CausalGraph"], AspectRatio -> 1/3], w["CausalGraph"]}] &, {{{x, y, y}, {z, x, u}} -> {{y, v, y}, {y, z, v}, {u, v, v}}, {{x, y, z}, {x, u, v}} -> {{z, z, w}, {w, w, v}, {u, v, w}}, {{1, 1, 2}, {1, 3, 4}} -> {{4, 4, 3}, {2, 5, 3}, {2, 5, 3}}, {{1, 1, 2}, {1, 3, 4}} -> {{4, 4, 5}, {5, 4, 2}, {3, 2, 5}}, {{1, 2, 2}, {1, 3, 4}} -> {{4, 5, 5}, {5, 3, 2}, {1, 2, 5}}, {{x, y, z}, {u, y, v}} -> {{w, z, x}, {z, w, u}, {x, y, w}}, {{1, 2, 2}, {3, 2, 4}} -> {{5, 3, 5}, {5, 4, 4}, {4, 5, 1}}, {{1, 2, 2}, {3, 1, 4}} -> {{2, 3, 2}, {3, 4, 4}, {2, 4, 5}}, {{x, x, y}, {z, u, x}} -> {{u, u, z}, {v, u, v}, {v, y, x}}, {{1, 2, 1}, {1, 3, 4}} -> {{4, 5, 4}, {5, 4, 3}, {1, 2, 5}}, {{x, y, z}, {x, u, v}} -> {{x, w, u}, {v, w, y}, {w, y, z}}}]

A typical source of grid-like causal graphs [1:p489] is rules where in a sense only one thing ever happens at a time, or, in effect, the rules operate like a mobile automaton [1:3.3] or a Turing machine, with a single active element. As an example, consider the rule (see 3.10):

{{x, y, y}, {z, x, u}} -> {{y, v, y}, {y, z, v}, {u, v, v}}
ResourceFunction[ "WolframModel"][{{x, y, y}, {z, x, u}} -> {{y, v, y}, {y, z, v}, {u, v, v}}, {{0, 0, 0}, {0, 0, 0}}, 30]["StatesPlotsList", "MaxImageSize" -> 60]

Updates can only occur at the position of the self-loop, which progressively “moves around”, “knitting” a grid pattern. The causal graph captures the fact that “only one thing happens at a time”:

Graph[ResourceFunction[ "WolframModel"][{{x, y, y}, {z, x, u}} -> {{y, v, y}, {y, z, v}, {u, v, v}}, {{0, 0, 0}, {0, 0, 0}}, 30, "CausalGraph"], VertexLabels -> Automatic]

But what is notable is that if we ask about the overall causal relationships between events, we realize that even events that happened many steps apart in the evolution as shown here are actually directly causally connected, because in a sense “nothing else happened in between”. Re-rendering the causal graph illustrates this, and shows how a grid is built up:

Graph[ResourceFunction[ "WolframModel"][{{x, y, y}, {z, x, u}} -> {{y, v, y}, {y, z, v}, {u, v, v}}, {{0, 0, 0}, {0, 0, 0}}, 30, "CausalGraph"], GraphLayout -> "SpringElectricalEmbedding", VertexLabels -> Automatic]

Sometimes the actual growth process can be more complicated, as in the case of the rule

{{x, y, y}, {z, y, u}} -> {{v, z, v}, {v, u, u}, {u, v, x}}
ResourceFunction["WolframModelPlot"][#, "MaxImageSize" -> 60] & /@ ResourceFunction[ "WolframModel"][ {{x, y, y}, {z, y, u}} -> {{v, z, v}, {v, u, u}, {u, v, x}}, {{0, 0, 0}, {0, 0, 0}}, 30, "StatesList"]

After 200 steps this yields:

ResourceFunction[ "WolframModel"][ {{x, y, y}, {z, y, u}} -> {{v, z, v}, {v, u, u}, {u, v, x}}, {{0, 0, 0}, {0, 0, 0}}, 200, "FinalStatePlot"]

And after 1000 steps it gives:

ResourceFunction[ "WolframModel"][ {{x, y, y}, {z, y, u}} -> {{v, z, v}, {v, u, u}, {u, v, x}}, {{0, 0, 0}, {0, 0, 0}}, 1000, "FinalStatePlot"]

But despite this elaborate structure, the causal graph is very simple:

ResourceFunction[ "WolframModel"][ {{x, y, y}, {z, y, u}} -> {{v, z, v}, {v, u, u}, {u, v, x}}, {{0, 0, 0}, {0, 0, 0}}, 30, "CausalGraph"]

After 200 steps, the grid structure is clear:

ResourceFunction[ "WolframModel"][ {{x, y, y}, {z, y, u}} -> {{v, z, v}, {v, u, u}, {u, v, x}}, {{0, 0, 0}, {0, 0, 0}}, 200, "CausalGraph"]

Sometimes the causal graph can locally be like a grid, while having a more complicated overall topological structure. Consider for example the rule:

{{x, y, y}, {z, x, u}} -> {{y, z, y}, {z, u, u}, {y, u, v}}

After 200 steps this gives:

ResourceFunction[ "WolframModel"][ {{x, y, y}, {z, x, u}} -> {{y, z, y}, {z, u, u}, {y, u, v}}, {{0, 0, 0}, {0, 0, 0}}, 200, "FinalStatePlot"]

The corresponding causal graph is:

ResourceFunction[ "WolframModel"][ {{x, y, y}, {z, x, u}} -> {{y, z, y}, {z, u, u}, {y, u, v}}, {{0, 0, 0}, {0, 0, 0}}, 200, "CausalGraph"]

After 1000 steps with surface reconstruction this gives:

CausalGraphReconstructedSurface[w_List, delta_ : 2.5, opts : OptionsPattern[GraphPlot3D]] := With[{gr = ResourceFunction["HypergraphToGraph"][w]}, Show[Graphics3D[{Hue[0.11, 1, 0.97], Style[ResourceFunction["NonConvexHullMesh"][ GraphEmbedding[gr, "SpringElectricalEmbedding", 3], delta], Directive[EdgeForm[], Opacity[.1]]]}, FilterRules[{opts}, Options[Graphics3D]], Boxed -> False, BoxStyle -> Gray, Method -> {"ShrinkWrap" -> True}, Lighting -> "Neutral"], GraphPlot3D[gr, FilterRules[{opts}, Options[GraphPlot3D]], EdgeStyle -> { Hue[0, 1, 0.8300000000000001]}, VertexSize -> 2, FormatType -> TraditionalForm, ImageSize -> {473.34765625`, Automatic}, VertexStyle -> {Directive[Hue[0.11, 1, 0.97], Opacity[.6], EdgeForm[None]]}]]]; CausalGraphReconstructedSurface[ List @@@ EdgeList@ ResourceFunction[ "WolframModel"][ {{x, y, y}, {z, x, u}} -> {{y, z, y}, {z, u, u}, {y, u, v}}, {{0, 0, 0}, {0, 0, 0}}, 1000, "CausalGraph"] ]

Rules (such as those with signature 22 22) that cannot exhibit growth inevitably terminate or repeat, thus leading to causal graphs that are either finite or repetitivebut may still have fairly complex structure. Consider for example the rule (compare 3.15):

{{x, y}, {y, z}} -> {{z, x}, {z, y}}

Evolution from a chain of 9 relations leads to a 31-step transient, then a 9-step cycle:

ResourceFunction["WolframModel"][{{1, 2}, {2, 3}} -> {{3, 1}, {3, 2}}, Table[{i, i + 1}, {i, 9}], 30]["StatesPlotsList", "MaxImageSize" -> 60]

The first 30 layers in the causal graph are:

ResourceFunction["WolframModel"][{{1, 2}, {2, 3}} -> {{3, 1}, {3, 2}}, Table[{i, i + 1}, {i, 9}], 30, "CausalGraph"] // LayeredGraphPlot

In an alternative rendering, the graph is:

ResourceFunction["WolframModel"][{{1, 2}, {2, 3}} -> {{3, 1}, {3, 2}}, Table[{i, i + 1}, {i, 9}], 30, "CausalGraph"]

After 50 more steps, the repetitive structure becomes clear:

ResourceFunction["WolframModel"][{{1, 2}, {2, 3}} -> {{3, 1}, {3, 2}}, Table[{i, i + 1}, {i, 9}], 80, "CausalGraph"]

Sometimes the structure of the causal graph may be very much a reflection of the updating order used. Consider for example the rather trivial “identity” rule:

{{x, y}, {y, z}} -> {{x, y}, {y, z}}

Starting with a chain of 3 relations, this shows update events according to our standard updating order (note that the same relation can be both created and destroyed at a particular step):

ResourceFunction["WolframModel"][{{x, y}, {y, z}} -> {{x, y}, {y, z}}, Table[{i, i + 1}, {i, 3}], 8, "EventsStatesPlotsList"]

The corresponding causal graph is:

ResourceFunction["WolframModel"][{{x, y}, {y, z}} -> {{x, y}, {y, z}}, Table[{i, i + 1}, {i, 3}], 8, "CausalGraph"]

For a chain of length of 21 the causal graph consists largely of independent regionsexcept for the connection created by updates fitting differently at different steps:

ResourceFunction["WolframModel"][{{x, y}, {y, z}} -> {{x, y}, {y, z}}, Table[{i, i + 1}, {i, 0, 20}], 30, "LayeredCausalGraph"]

Re-rendering this gives a seemingly elaborate structure:

ResourceFunction["WolframModel"][{{x, y}, {y, z}} -> {{x, y}, {y, z}}, Table[{i, i + 1}, {i, 0, 20}], 30, "CausalGraph"]

After 100 steps, though, its repetitive character becomes clear:

ResourceFunction["WolframModel"][{{x, y}, {y, z}} -> {{x, y}, {y, z}}, Table[{i, i + 1}, {i, 0, 20}], 100, "CausalGraph"]

Note that if the initial condition is a ring rather than a chain, one gets

ResourceFunction["WolframModel"][{{x, y}, {y, z}} -> {{x, y}, {y, z}}, Table[{i, Mod[i + 1, 21]}, {i, 0, 20}], 30, "CausalGraph"] // LayeredGraphPlot

together with the tube-like structure:

ResourceFunction["WolframModel"][{{x, y}, {y, z}} -> {{x, y}, {y, z}}, Table[{i, Mod[i + 1, 21]}, {i, 0, 20}], 30, "CausalGraph"]