Wolfram Model: Causal Networks and Multiway Evolution

Copy Markdown View Source
Mix.install([
  {:jason, "~> 1.4"},
  {:kino, "~> 0.12"},
  {:kino_vega_lite, "~> 0.1"},
  {:vega_lite, "~> 0.1"},
  {:wolfram_model, path: __DIR__}
])

Universe

steps = 30

initial =
  Hypergraph.new()
  |> Hypergraph.add_hyperedge([1, 2])
  |> Hypergraph.add_hyperedge([2, 3])
  |> Hypergraph.add_hyperedge([3, 4])
  |> Hypergraph.add_hyperedge([4, 5])
  |> Hypergraph.add_hyperedge([5, 6])
  |> Hypergraph.add_hyperedge([6, 7])
  |> Hypergraph.add_hyperedge([7, 8])
  |> Hypergraph.add_hyperedge([8, 9])
  |> WolframModel.new(WolframModel.RuleSet.rule_set(:growth))

evolved = WolframModel.evolve_steps(initial, steps)

Hypergraph.stats(evolved.hypergraph)
WolframModel.HypergraphSVG.to_svg(evolved.hypergraph)
|> Kino.HTML.new()
WolframModel.GeodesicPlotSVG.to_svg(evolved.hypergraph)
|> Kino.HTML.new()
WolframModel.HypergraphSVG.evolution_to_svg(evolved)
|> Kino.HTML.new()

Causal Network

This shows how evolution events are causally connected through time:

WolframModel.causal_network_data(evolved)
|> WolframModel.CausalGraphSVG.to_svg()
|> Kino.HTML.new()

Foliations (Spacelike Slices)

Foliations partition causal events into layers where each layer contains only events whose parents all belong to earlier layers — the hypergraph analogue of constant-time slices in relativity.

layers = WolframModel.foliations(evolved)

foliation_data =
  layers
  |> Enum.with_index()
  |> Enum.flat_map(fn {events, layer} ->
    Enum.map(events, fn e ->
      %{layer: layer, event_id: e.id, rule: e.rule.name, parents: length(e.parent_ids)}
    end)
  end)

VegaLite.new(width: 600, height: 300, title: "Causal Foliations (Spacelike Layers)")
|> VegaLite.data_from_values(foliation_data)
|> VegaLite.mark(:circle, size: 120)
|> VegaLite.encode_field(:x, "event_id", type: :quantitative, axis: [title: "Event ID"])
|> VegaLite.encode_field(:y, "layer", type: :quantitative, axis: [title: "Foliation Layer"])
|> VegaLite.encode_field(:color, "rule", type: :nominal, legend: [title: "Rule"])
|> VegaLite.encode(:tooltip, [
  [field: "layer", type: :quantitative, title: "Layer"],
  [field: "event_id", type: :quantitative, title: "Event ID"],
  [field: "rule", type: :nominal, title: "Rule"],
  [field: "parents", type: :quantitative, title: "# Parents"]
])

Branchial Graph

The branchial graph connects rule matches that overlap (conflict) at the current state — these are the branching points in multiway evolution. Two nodes are adjacent when their matched hyperedges share a vertex.

WolframModel.branchial_graph(evolved)
|> WolframModel.BranchialGraphSVG.to_svg()
|> Kino.HTML.new()

Causal Invariance

Check whether all pairs of non-overlapping rule applications at the initial state commute (a necessary condition for relativistic invariance).

WolframModel.causally_invariant?(initial)

Event Graph Export

Export the full event DAG as nodes + edges for external tooling:

event_graph = WolframModel.export_event_graph(evolved)

IO.puts("Events: #{length(event_graph.nodes)}")
IO.puts("Causal edges: #{length(event_graph.edges)}")

event_graph

Effective Spatial Dimension

Estimate the emergent spatial dimension of the evolved hypergraph using geodesic ball growth (fits $V(r) \sim r^d$ via log-log regression).

Track how dimension evolves across generations:

dim_data =
  evolved.evolution_history
  |> Enum.reverse()
  |> Enum.with_index()
  |> Enum.map(fn {hg, gen} ->
    %{generation: gen, dimension: WolframModel.Analytics.estimate_dimension(hg)}
  end)

VegaLite.new(width: 600, height: 300, title: "Emergent Spatial Dimension Over Time")
|> VegaLite.data_from_values(dim_data)
|> VegaLite.mark(:line, point: true, strokeWidth: 2)
|> VegaLite.encode_field(:x, "generation", type: :quantitative, axis: [title: "Generation"])
|> VegaLite.encode_field(:y, "dimension", type: :quantitative, axis: [title: "Est. Dimension"])
|> VegaLite.encode(:tooltip, [
  [field: "generation", type: :quantitative],
  [field: "dimension", type: :quantitative, format: ".3f"]
])

Rule Analysis

Inspect the structural properties of the built-in rule sets:

alias WolframModel.RuleAnalysis

WolframModel.RuleSet.basic_rules()
|> Enum.map(fn rule ->
  {pat, rep} = RuleAnalysis.arity(rule)

  %{
    name: rule.name,
    reversible: RuleAnalysis.reversible?(rule),
    self_complementary: RuleAnalysis.self_complementary?(rule),
    introduces_new_vertices: RuleAnalysis.introduces_new_vertices?(rule),
    hyperedge_delta: RuleAnalysis.hyperedge_delta(rule),
    pattern_arities: inspect(pat),
    replacement_arities: inspect(rep)
  }
end)
|> Kino.DataTable.new()

Multiway Evolution

WolframModel.multiway_explore_dag(evolved, 2)
|> WolframModel.MultiwayGraphSVG.to_svg()
|> Kino.HTML.new()