Skip to content

Causal Dynamical Triangulations

Scrollable formula. Use Left and Right arrow keys to reveal hidden content.

Scrollable table. Use Left and Right arrow keys to reveal additional columns.

Scrollable figure. Use Left and Right arrow keys to inspect hidden content.

Causal dynamical triangulations define a Lorentzian sum over piecewise-flat histories with a preferred discrete proper-time slicing, fixed spatial topology, and no spatial branching. A configuration is a gluing of standard simplices, not a perturbation around one background metric. Four-dimensional simulations exhibit an extended phase with semiclassical large-scale observables, but a continuum theory requires a continuous transition, diverging physical correlation length in lattice units, finite-size scaling, and regulator-independent observables. The executable calculation below validates only the exact 1+1-dimensional transfer combinatorics used as a method control.

Helpful background. Flat-Spacetime Quantum Field Theory supplies lattice sums and critical phenomena; Einstein Equations supplies the continuum target; Quantum Information supplies transfer matrices and random walks; Quantum Gravity supplies evidence standards; Causality and Global Structure supplies foliations and topology; and Computational Relativity supplies finite-size, autocorrelation, and convergence methods.

Fix spatial topology and integer proper time. Spacelike edges have squared length a2a^2, timelike edges have squared length −αa2-\alpha a^2, and allowed four-simplices connect adjacent slices without branching the spatial hypersurface. The causal gluing rules admit a configuration-wise analytic continuation to a Euclideanized ensemble for the permitted range of α\alpha Ambjørn, Jurkiewicz, and Loll 2001, §§2–4.

After continuation, a commonly used four-dimensional Regge action is

SE=−(κ0+6Δ)N0+κ4(N4(4,1)+N4(3,2))+Δ(2N4(4,1)+N4(3,2)),S_E=-(\kappa_0+6\Delta)N_0 +\kappa_4\left(N_4^{(4,1)}+N_4^{(3,2)}\right) +\Delta\left(2N_4^{(4,1)}+N_4^{(3,2)}\right),

where both time orientations are included in each simplex count. The bare inverse Newton coupling is encoded in κ0\kappa_0, κ4\kappa_4 controls four-volume, and Δ\Delta records the spacelike–timelike asymmetry. Simulations sample

ZE=∑T1CTe−SE[T]Z_E=\sum_T\frac{1}{C_T}e^{-S_E[T]}

with local ergodic moves, automorphism factor CTC_T, and usually a declared volume-fixing term. Acceptance rule, thermalization, integrated autocorrelation time, move balance, topology, and finite-volume window are part of the result.

The extended CdSC_{\mathrm{dS}} phase has an ensemble-average spatial-volume profile compatible, after finite-size rescaling, with a Euclidean de Sitter solution and a macroscopic Hausdorff dimension near four. Diffusion observables give a scale-dependent spectral dimension,

dS(σ)=−2dlog⁡P(σ)dlog⁡σ,d_S(\sigma)=-2\frac{d\log P(\sigma)}{d\log\sigma},

where P(σ)P(\sigma) is the ensemble-averaged return probability after diffusion time σ\sigma. The short-distance reduction first reported in four-dimensional CDT is a numerical observable, not a proof that topology or metric dimension literally changes Ambjørn, Jurkiewicz, and Loll 2005, Eqs. (1)–(5).

A viable continuum limit would require a higher-order transition bordering an extended phase, with a correlation length ξ/a→∞\xi/a\to\infty as bare couplings approach criticality. At increasing four-volume, susceptibility peaks should scale consistently, pseudocritical couplings should approach a limit, hysteresis should disappear for a continuous transition, and renormalized observables should become insensitive to volume fixing, move set, and lattice details. The phase diagram contains candidate higher-order behavior, especially near the bifurcation and collapsed phases, but the identification of a unique ultraviolet fixed point and a complete set of continuum observables remains open Loll 2020, §§4–6.

For the method check, specialize to one 1+1-dimensional cylindrical strip. The entrance and exit loops have positive integer lengths ℓ\ell and mm. There are ℓ\ell future-pointing and mm past-pointing triangles, spatial topology is periodic, topology change is forbidden, and one entrance position is marked. Reading from that root gives a word whose first symbol is UU; choosing the remaining ℓ−1\ell-1 up-triangle positions yields

C(ℓ,m)=(ℓ+m−1ℓ−1).C(\ell,m)={\ell+m-1\choose\ell-1}.

The asymmetry C(2,3)=4C(2,3)=4 but C(3,2)=6C(3,2)=6 is intentional: only the entrance is marked, and mC(ℓ,m)=ℓC(m,ℓ)mC(\ell,m)=\ell C(m,\ell). This is the convention behind the marked transfer function in Ambjørn and Loll 1998, §2, Eqs. (9)–(17), pp. 5–6.

Assign weight gℓ+mg^{\ell+m} with g=2/5g=2/5. The program enumerates every rooted word and compares it with an independent math.comb implementation for all 1≤ℓ,m≤81\le\ell,m\le8. It builds both count and exactly rational weighted transfer matrices, then compares formula-based matrix multiplication with a separate intermediate-length sum assembled from the enumerated one-step counts. It also evaluates a normalized ℓ=1\ell=1 outgoing-row mean at cutoffs 44, 66, and 88.

Because C(1,m)=1C(1,m)=1, normalizing this row over m≥1m\ge1 gives the geometric distribution pm=(1−g)gm−1p_m=(1-g)g^{m-1}. This is a deliberately normalized diagnostic, not a claim that the full transfer matrix is stochastic. Its infinite-cutoff mean and exact cutoff errors are

⟨m⟩∞=11−g,Pr⁡(m>K)=gK,⟨m⟩∞−⟨m⟩K=KgK1−gK.\langle m\rangle_\infty=\frac{1}{1-g},\qquad \Pr(m\gt K)=g^K,\qquad \langle m\rangle_\infty-\langle m\rangle_K =\frac{K g^K}{1-g^K}.

These follow by differentiating the finite geometric sum ∑m=1Kgm=g(1−gK)/(1−g)\sum_{m=1}^K g^m=g(1-g^K)/(1-g). They independently predict every row of the convergence table, including the limiting mean 5/35/3.

The program supports CPython 3.11 or later and was rerun with CPython 3.12.14. It uses only the standard library, with deterministic exhaustive enumeration and no random seed. Integer counts remain exact; rational weights use fractions.Fraction; conversion to binary64 occurs only for printed diagnostics after a 2532^{53} safety check. Final tolerances are 6×10−36\times10^{-3} for the mean error and 10−310^{-3} for discarded boundary weight. Save the following program as cdt_rooted_strip_benchmark.py. The script emits count, composition, convergence, failure, and scope diagnostics.

#!/usr/bin/env python3
from fractions import Fraction
from itertools import combinations
from math import comb, isfinite
import platform
import sys
MINIMUM_MAJOR_MINOR = (3, 11)
MAX_LENGTH = 8
LEVELS = (4, 6, 8)
G = Fraction(2, 5)
MEAN_ERROR_TOL = Fraction(3, 500)
BOUNDARY_TOL = Fraction(1, 1000)
FLOAT_SAFE_INTEGER = 2**53
def stop(message):
raise SystemExit("FAIL: " + message)
def validate_lengths(entrance, exit_):
if not isinstance(entrance, int) or not isinstance(exit_, int):
stop("boundary lengths must be integers")
if entrance < 1 or exit_ < 1:
stop("boundary lengths must be positive")
def rooted_words(entrance, exit_):
"""Rooted entrance: position zero is an up triangle."""
validate_lengths(entrance, exit_)
total = entrance + exit_
words = set()
for extra_up in combinations(range(1, total), entrance - 1):
up_positions = set(extra_up)
word = "U" + "".join(
"U" if position in up_positions else "D"
for position in range(1, total)
)
if (
len(word) != total
or word[0] != "U"
or word.count("U") != entrance
or word.count("D") != exit_
or set(word) != {"U", "D"}
):
stop("illegal rooted-strip word")
if word in words:
stop("duplicate cyclic word after fixing the root")
words.add(word)
return words
def brute_count(entrance, exit_):
return len(rooted_words(entrance, exit_))
def binomial_count(entrance, exit_):
validate_lengths(entrance, exit_)
return comb(entrance + exit_ - 1, entrance - 1)
def matmul(left, right):
rows = len(left)
inner = len(right)
columns = len(right[0])
if any(len(row) != inner for row in left):
stop("matrix shape mismatch")
return [
[
sum(left[i][k] * right[k][j] for k in range(inner))
for j in range(columns)
]
for i in range(rows)
]
def exact_controls():
maximum = 0
enumerated_count_matrix = []
for entrance in range(1, MAX_LENGTH + 1):
enumerated_row = []
for exit_ in range(1, MAX_LENGTH + 1):
brute = brute_count(entrance, exit_)
formula = binomial_count(entrance, exit_)
if brute != formula:
stop(f"count mismatch at ({entrance}, {exit_})")
reverse = binomial_count(exit_, entrance)
if exit_ * formula != entrance * reverse:
stop("marked-boundary relation failed")
maximum = max(maximum, formula)
enumerated_row.append(brute)
enumerated_count_matrix.append(enumerated_row)
if binomial_count(2, 3) != 4 or binomial_count(3, 2) != 6:
stop("entrance marking was accidentally symmetrized")
if maximum > FLOAT_SAFE_INTEGER:
stop("integer-to-float conversion would lose count information")
count_matrix = [
[
binomial_count(entrance, exit_)
for exit_ in range(1, MAX_LENGTH + 1)
]
for entrance in range(1, MAX_LENGTH + 1)
]
squared = matmul(count_matrix, count_matrix)
direct = [
[
sum(
enumerated_count_matrix[entrance - 1][middle - 1]
* enumerated_count_matrix[middle - 1][exit_ - 1]
for middle in range(1, MAX_LENGTH + 1)
)
for exit_ in range(1, MAX_LENGTH + 1)
]
for entrance in range(1, MAX_LENGTH + 1)
]
gap = max(
abs(squared[i][j] - direct[i][j])
for i in range(MAX_LENGTH)
for j in range(MAX_LENGTH)
)
if gap != 0:
stop("two-step transfer-matrix composition failed")
weighted_matrix = [
[
Fraction(binomial_count(entrance, exit_))
* G ** (entrance + exit_)
for exit_ in range(1, MAX_LENGTH + 1)
]
for entrance in range(1, MAX_LENGTH + 1)
]
weighted_squared = matmul(weighted_matrix, weighted_matrix)
weighted_direct = [
[
sum(
Fraction(enumerated_count_matrix[entrance - 1][middle - 1])
* G ** (entrance + middle)
* Fraction(enumerated_count_matrix[middle - 1][exit_ - 1])
* G ** (middle + exit_)
for middle in range(1, MAX_LENGTH + 1)
)
for exit_ in range(1, MAX_LENGTH + 1)
]
for entrance in range(1, MAX_LENGTH + 1)
]
weighted_gap = max(
abs(weighted_squared[i][j] - weighted_direct[i][j])
for i in range(MAX_LENGTH)
for j in range(MAX_LENGTH)
)
if weighted_gap != 0:
stop("weighted transfer-matrix composition failed")
return (
maximum,
gap,
squared[0][0],
weighted_gap,
weighted_squared[0][0],
)
def normalized_row_observable(cutoff):
weights = [
Fraction(binomial_count(1, exit_)) * G ** (1 + exit_)
for exit_ in range(1, cutoff + 1)
]
mass = sum(weights, Fraction(0))
mean_exit = sum(
Fraction(exit_) * weight
for exit_, weight in enumerate(weights, start=1)
) / mass
infinite_mean = 1 / (1 - G)
mean_error = abs(infinite_mean - mean_exit)
discarded_boundary_weight = G**cutoff
return mean_exit, mean_error, discarded_boundary_weight
def main():
if (
sys.implementation.name != "cpython"
or sys.version_info[:2] < MINIMUM_MAJOR_MINOR
):
stop("requires CPython 3.11 or later; found " + platform.python_version())
maximum, gap, sample, weighted_gap, weighted_sample = exact_controls()
rows = []
previous_error = None
previous_boundary = None
for cutoff in LEVELS:
row = normalized_row_observable(cutoff)
mean_exit, mean_error, boundary_weight = row
if not all(isfinite(float(value)) for value in row):
stop("non-finite normalized observable")
if previous_error is not None and mean_error >= previous_error:
stop("mean-exit cutoff error did not decrease")
if previous_boundary is not None and boundary_weight >= previous_boundary:
stop("discarded boundary weight did not decrease")
previous_error = mean_error
previous_boundary = boundary_weight
rows.append((cutoff, mean_exit, mean_error, boundary_weight))
if rows[-1][2] > MEAN_ERROR_TOL:
stop("final mean-exit cutoff error exceeded tolerance")
if rows[-1][3] > BOUNDARY_TOL:
stop("upper cutoff still dominates the normalized row")
print("CPython", platform.python_version(), "standard-library-only")
print("convention rooted-entrance periodic-strip no-branching")
print(
"count_pairs", MAX_LENGTH**2,
"max_count", maximum,
"marking_check PASS",
)
print(
"composition_cutoff", MAX_LENGTH,
"max_exact_gap", gap,
"T2_1_1", sample,
)
print(
"weighted_exact_gap", weighted_gap,
"weighted_T2_1_1", f"{float(weighted_sample):.12e}",
)
print(
"cutoff mean_exit_length mean_error "
"discarded_boundary_weight"
)
for cutoff, mean_exit, mean_error, boundary_weight in rows:
print(
f"{cutoff:d} {float(mean_exit):.12f} "
f"{float(mean_error):.12e} "
f"{float(boundary_weight):.12e}"
)
print(
"PASS: counts, marking, composition, cutoff convergence, and scope"
)
print("scope: exact finite 1+1 control; no four-dimensional inference")
if __name__ == "__main__":
main()

Use this exact save, compile, and run recipe. The final line is the clean rerun command.

Terminal window
python3 --version
python3 -m py_compile cdt_rooted_strip_benchmark.py
python3 cdt_rooted_strip_benchmark.py

The source above was compiled and executed with CPython 3.12.14 during the 2026-09-27 check, with no third-party package. It enumerated 64 boundary pairs, found maximum count 6435, obtained zero discrepancy between the formula-matrix and enumerated-composition routes for both exact transfers, and ended with PASS. The values agree with the earlier 2026-08-20 calculation on CPython 3.11.4. No random seed or external data enters.

Maximum length cutoffMean exit-length observableAbsolute mean errorDiscarded boundary weight
41.5615763546801.050903119869e-012.560000000000e-02
61.6419895893582.467707730866e-024.096000000000e-03
81.6614203484405.246318227113e-036.553600000000e-04

The mean approaches 5/35/3, its error decreases at every refinement, and the final boundary weight is below 10−310^{-3}. The composition control compares matrix multiplication of the binomial formula with direct composition of enumerated one-step counts; it gives (C2)11=36(C^2)_{11}=36 at cutoff eight and zero discrepancy for every entry. The weighted rational routes also agree exactly; the displayed (T2)11=3.628105884028×10−2(T^2)_{11}=3.628105884028\times10^{-2} is only a checksum.

The program exits nonzero for a nonpositive length, brute-force/binomial mismatch, duplicate rooted word, accidental symmetrization of the marked convention, unsafe integer conversion, matrix-composition mismatch, nonfinite observable, nonmonotone cutoff error, or excessive final boundary weight. The U/DU/D word representation fixes a cylindrical strip and excludes branching and topology change by construction. Changing to unmarked loops requires new symmetry factors rather than silently dividing this matrix.

This is an exact finite 1+1-dimensional control of enumeration, normalization, marking, and composition. It does not sample four-dimensional triangulations, locate a phase transition, estimate a four-dimensional spectral dimension, or demonstrate an extended continuum geometry. In production CDT, spectral-dimension analysis must first be validated on known finite graphs, then tested across diffusion windows, volumes, couplings, walk conventions, and autocorrelation bins. Likewise, a de Sitter volume profile at one bare point cannot replace finite-size critical scaling.

The direct answer to the governing question is therefore conditional: a continuous transition adjacent to an extended four-dimensional phase, accompanied by a diverging correlation length and stable renormalized observables, would support a continuum limit. Existing numerical evidence motivates that search but does not yet complete it.

Derive C(ℓ,m)C(\ell,m) for a rooted strip. Then compute C(2,3)C(2,3), C(3,2)C(3,2), and the cutoff-KK two-step count from length one back to length one.

Solution

The root fixes the first of the ℓ\ell up triangles. Among the remaining ℓ+m−1\ell+m-1 cyclic positions, choose ℓ−1\ell-1 for the other up triangles, giving

C(ℓ,m)=(ℓ+m−1ℓ−1).C(\ell,m)={\ell+m-1\choose\ell-1}.

Hence C(2,3)=(41)=4C(2,3)={4\choose1}=4 and C(3,2)=(42)=6C(3,2)={4\choose2}=6; the mismatch records which boundary is marked. Since C(1,k)=1C(1,k)=1 and C(k,1)=kC(k,1)=k,

(C2)11(K)=∑k=1KC(1,k)C(k,1)=∑k=1Kk=K(K+1)2.(C^2)_{11}^{(K)} =\sum_{k=1}^{K}C(1,k)C(k,1) =\sum_{k=1}^{K}k=\frac{K(K+1)}2.

At K=8K=8 this is 3636, matching the independent matrix product. This proves the finite transfer-composition identity under the stated convention, not any four-dimensional continuum claim.

  • Ambjørn, Jan, and Renate Loll. “Non-Perturbative Lorentzian Quantum Gravity, Causality and Topology Change.” Nuclear Physics B 536 (1998): 407–434. Open manuscript.
  • Ambjørn, Jan, Jerzy Jurkiewicz, and Renate Loll. “Dynamically Triangulating Lorentzian Quantum Gravity.” Nuclear Physics B 610 (2001): 347–382. Open manuscript.
  • Ambjørn, Jan, Jerzy Jurkiewicz, and Renate Loll. “Spectral Dimension of the Universe.” Physical Review Letters 95 (2005): 171301. Open manuscript.
  • Loll, Renate. “Quantum Gravity from Causal Dynamical Triangulations: A Review.” Classical and Quantum Gravity 37 (2020): 013002. Open review.