Try CodeDelta
A human programmer and a robot each draw code onto the curved fabric of space-time around a black hole, beneath the Riemann curvature equation and the Schwarzschild radius r = 2GM/c squared. Human labels: hack for codeclimate, library design, modular functions, 33 functions. AI labels: formula in the docs, symmetry shortcut, nested loops, 15 functions.
Article · AI vs human coding

Ai v Human Coding

28 October 2026 · both programs run and measured with CodeDelta 2.6.4; every study linked below

Is an AI, a large language model (LLM), a better programmer than an experienced human? We gave both the same job, a function at the heart of Einstein’s general relativity, then tested and measured the results and set them beside the published research.

The job

The Riemann curvature tensor measures how space-time is curved: zero in empty, flat space; strong near a black hole. Given a description of a space-time (its metric) it is computed in two steps, first the Christoffel symbols and then the curvature, and the arithmetic is long enough that physicists hand it to a computer. It is a good test because the right answers are known. Each program was run on three cases: the space-time around a black hole (Schwarzschild), flat space, and the surface of a sphere.

The two programmers

The humans. EinsteinPy is an open-source Python library for general relativity, MIT-licensed. Its curvature code was written by named developers from June 2019; the version used here is the one that stood on 28 August 2020 (commit 966c94ef). [1]

The AI. Claude Opus 5.5, an LLM made by Anthropic, was given one sentence on 28 October 2026: “Write a Python function that, given a spacetime metric as a 4x4 SymPy matrix and the list of coordinate symbols, returns the Riemann curvature tensor.” It wrote the code below without seeing EinsteinPy’s.

The AI again, with the same scope. A single function is not a fair match for a library, so Claude Opus 5.5 was then asked for the same scope as EinsteinPy: “Write the Riemann curvature tensor as part of a Python library: classes for the metric, the Christoffel symbols and the Riemann tensor, with documentation, conversion between upper and lower index positions, and Lorentz transformations.” This second version is not independent of the human code: by then the AI had read EinsteinPy’s files in the same session.

The code

Human: EinsteinPy (2020), the two calculation loops

dims = metric.dimstmplist = np.zeros((dims, dims, dims), dtype=int).tolist()mat, syms = metric.lower_config().tensor(), metric.symbols()matinv = sympy.Matrix(mat.tolist()).inv()for t in range(dims ** 3):    # i,j,k each goes from 0 to (dims-1)    # hack for codeclimate. Could be done with 3 nested for loops    k = t % dims    j = (int(t / dims)) % (dims)    i = (int(t / (dims ** 2))) % (dims)    if k <= j:        tmpvar = 0        for n in range(dims):            tmpvar += (matinv[i, n] / 2) * (                sympy.diff(mat[n, j], syms[k])                + sympy.diff(mat[n, k], syms[j])                - sympy.diff(mat[j, k], syms[n])            )        tmplist[i][j][k] = tmplist[i][k][j] = tmpvarreturn cls(tmplist, syms, config="ull", parent_metric=metric)
arr, syms = chris.tensor(), chris.symbols()dims = len(syms)riemann_list = (np.zeros(shape=(dims, dims, dims, dims), dtype=int)).tolist()for i in range(dims ** 4):    # t,s,r,n each goes from 0 to (dims-1)    # hack for codeclimate. Could be done with 4 nested for loops    n = i % dims    r = (int(i / dims)) % (dims)    s = (int(i / (dims ** 2))) % (dims)    t = (int(i / (dims ** 3))) % (dims)    temp = sympy.diff(arr[t, s, n], syms[r]) - sympy.diff(arr[t, r, n], syms[s])    for p in range(dims):        temp += arr[p, s, n] * arr[t, p, r] - arr[p, r, n] * arr[t, p, s]    riemann_list[t][s][r][n] = sympy.simplify(temp)

Excerpt from einsteinpy/symbolic/christoffel.py and riemann.py at commit 966c94ef. Copyright (c) 2019-2020 The EinsteinPy Project, MIT licence. The full files also hold the classes, documentation, index conversions and Lorentz transforms around these loops.

AI: Claude Opus 5.5 (2026), complete

def christoffel_symbols(metric, coords):    """Christoffel symbols of the second kind, Gamma^l_{mn}, as a nested list."""    n = len(coords)    inverse = metric.inv()    gamma = [[[0] * n for _ in range(n)] for _ in range(n)]    for l in range(n):        for m in range(n):            for k in range(m, n):  # symmetric in the lower indices                value = sum(                    inverse[l, s] * (                        sp.diff(metric[s, m], coords[k])                        + sp.diff(metric[s, k], coords[m])                        - sp.diff(metric[m, k], coords[s])                    )                    for s in range(n)                ) / 2                value = sp.simplify(value)                gamma[l][m][k] = value                gamma[l][k][m] = value    return gammadef riemann_tensor(metric, coords):    """Riemann tensor R^rho_{sigma mu nu}, indexed as R[rho][sigma][mu][nu].    R^rho_{sigma mu nu} = d_mu Gamma^rho_{nu sigma} - d_nu Gamma^rho_{mu sigma}                        + Gamma^rho_{mu lam} Gamma^lam_{nu sigma}                        - Gamma^rho_{nu lam} Gamma^lam_{mu sigma}    """    metric = sp.Matrix(metric)    n = len(coords)    if metric.shape != (n, n):        raise ValueError("metric must be square and match the number of coordinates")    gamma = christoffel_symbols(metric, coords)    riemann = [[[[0] * n for _ in range(n)] for _ in range(n)] for _ in range(n)]    for rho in range(n):        for sigma in range(n):            for mu in range(n):                for nu in range(mu + 1, n):  # antisymmetric in the last two indices                    value = (                        sp.diff(gamma[rho][nu][sigma], coords[mu])                        - sp.diff(gamma[rho][mu][sigma], coords[nu])                        + sum(                            gamma[rho][mu][lam] * gamma[lam][nu][sigma]                            - gamma[rho][nu][lam] * gamma[lam][mu][sigma]                            for lam in range(n)                        )                    )                    value = sp.simplify(value)                    riemann[rho][sigma][mu][nu] = value                    riemann[rho][sigma][nu][mu] = -value    return riemann

AI: Claude Opus 5.5 (2026), as a library, the two calculation classes

class ChristoffelSymbols(Tensor):    """Christoffel symbols of the second kind, Gamma^a_{bc} ('ull')."""    @classmethod    def from_metric(cls, metric):        """Compute Gamma^a_{bc} = 1/2 g^{ad} (d_b g_{dc} + d_c g_{db} - d_d g_{bc})."""        n, g, ginv, x = metric.dims, metric.lower, metric.inverse, metric.coords        gamma = sp.MutableDenseNDimArray.zeros(n, n, n)        for a, b in product(range(n), repeat=2):            for c in range(b, n):  # symmetric in the lower indices                value = sum(                    ginv[a, d] * (sp.diff(g[d, c], x[b]) + sp.diff(g[d, b], x[c]) - sp.diff(g[b, c], x[d]))                    for d in range(n)                ) / 2                gamma[a, b, c] = gamma[a, c, b] = sp.simplify(value)        return cls(gamma, x, "ull", metric)class RiemannTensor(Tensor):    """The Riemann curvature tensor R^rho_{sigma mu nu} ('ulll')."""    @classmethod    def from_metric(cls, metric):        """Compute the Riemann tensor of a metric."""        return cls.from_christoffels(ChristoffelSymbols.from_metric(metric))    @classmethod    def from_christoffels(cls, gamma):        """Compute the Riemann tensor from Christoffel symbols Gamma^a_{bc}."""        n, x = gamma.dims, gamma.coords        riemann = sp.MutableDenseNDimArray.zeros(n, n, n, n)        for rho, sigma, mu in product(range(n), repeat=3):            for nu in range(mu + 1, n):  # antisymmetric in the last two indices                value = (                    sp.diff(gamma[rho, nu, sigma], x[mu])                    - sp.diff(gamma[rho, mu, sigma], x[nu])                    + sum(                        gamma[rho, mu, lam] * gamma[lam, nu, sigma]                        - gamma[rho, nu, lam] * gamma[lam, mu, sigma]                        for lam in range(n)                    )                )                value = sp.simplify(value)                riemann[rho, sigma, mu, nu] = value                riemann[rho, sigma, nu, mu] = -value        return cls(riemann, x, "ulll", gamma.metric)

Excerpt: the Christoffel and Riemann classes, 46 of the library’s 206 lines. The full file, with the tensor and metric classes, index conversion, Lorentz transformation and documentation: curvature.py.

The results

TestKnown answerHumanAI functionAI library
Black hole (Schwarzschild)Kretschmann scalar 48M²/r⁶correctcorrectcorrect
Flat spacezero everywherecorrectcorrectcorrect
Surface of a unit sphereKretschmann scalar 4correctcorrectcorrect

Both programs agree on every component in all three tests, once one difference in how they order the tensor’s indices is allowed for. Each took under a second per case; across repeated runs neither was consistently faster. One small difference shows: for the sphere, the human code returns the textbook sin²θ while the AI’s returns sin(2θ)/(2 tan θ) − cos(2θ), which is the same value written the long way.

The AI library was also checked on what a library adds. Lowering all four indices and raising them back returns the original tensor; the lowered tensor has the symmetries every textbook Riemann tensor must have; and after a Lorentz transformation at three-fifths of the speed of light the individual components change while the curvature scalar of the black hole stays at 48M²/r⁶, as it must. Every check passed on the first run.

Measured with CodeDelta 2.6.4LinesStatements (LLOC)Comment lines
Human: christoffel.py and riemann.py305934
AI: one function63322
Human: the four library files (metric, tensor, Christoffel, Riemann)89629314
AI: the library, one file2061273

The first pair is not like for like: the human files belong to a library, while the AI wrote one bare function, which is all it was asked for. The second pair is closer. Asked for the same scope, the AI wrote 127 statements against EinsteinPy’s 293, but EinsteinPy’s files still do more: they substitute values into a tensor, turn results into fast numeric functions and print themselves readably. Comment lines count only # comments; both libraries document themselves mainly in docstrings.

The differences you can see

The output: curvature you can see

What both programs produce is a description of how space-time bends. For the black hole, the measure physicists use, the Kretschmann scalar, falls with the sixth power of distance: at twice the distance the curvature is 64 times weaker. The chart plots it from each program’s own output at 73 distances; the dots (AI) sit exactly on the line (human), because the two programs return the same formula, 48M²/r⁶.

10-710-610-510-410-310-210-11002468101214161820distance from the centre (in units of the black hole’s mass, M)curvature (Kretschmann scalar)event horizon (r = 2M)r = 2M: AI 0.75, human 0.75r = 3M: AI 0.0658, human 0.0658r = 4M: AI 0.0117, human 0.0117r = 5M: AI 0.00307, human 0.00307r = 6M: AI 0.00103, human 0.00103r = 7M: AI 0.000408, human 0.000408r = 8M: AI 0.000183, human 0.000183r = 9M: AI 9.03e-05, human 9.03e-05r = 10M: AI 4.8e-05, human 4.8e-05r = 11M: AI 2.71e-05, human 2.71e-05r = 12M: AI 1.61e-05, human 1.61e-05r = 13M: AI 9.94e-06, human 9.94e-06r = 14M: AI 6.37e-06, human 6.37e-06r = 15M: AI 4.21e-06, human 4.21e-06r = 16M: AI 2.86e-06, human 2.86e-06r = 17M: AI 1.99e-06, human 1.99e-06r = 18M: AI 1.41e-06, human 1.41e-06r = 19M: AI 1.02e-06, human 1.02e-06r = 20M: AI 7.5e-07, human 7.5e-07human code (EinsteinPy)AI code (Claude Opus 5.5)
Curvature against distance for a black hole of mass M, from each program’s output; vertical scale logarithmic. Hover a dot for both values.

The second picture is the classic way of showing curved space: a slice through the space around the black hole drawn as a surface, the shape known as Flamm’s paraboloid, which is worked out from the black hole’s metric. Each ring is coloured by the curvature the programs computed at that distance, darker where space bends more sharply.

r = 12M, curvature 1.61e-05r = 11.5M, curvature 2.08e-05r = 11M, curvature 2.71e-05r = 10.5M, curvature 3.58e-05r = 10M, curvature 4.8e-05r = 9.5M, curvature 6.53e-05r = 9M, curvature 9.03e-05r = 8.5M, curvature 0.000127r = 8M, curvature 0.000183r = 7.5M, curvature 0.00027r = 7M, curvature 0.000408r = 6.5M, curvature 0.000636r = 6M, curvature 0.00103r = 5.5M, curvature 0.00173r = 5M, curvature 0.00307r = 4.5M, curvature 0.00578r = 4M, curvature 0.0117r = 3.5M, curvature 0.0261r = 3M, curvature 0.0658r = 2.5M, curvature 0.197r = 2M, curvature 0.75event horizon, r = 2M: strongest curvaturerim, r = 12M: curvature 46,656 times weaker
Flamm’s paraboloid, z = 2√(2M(r − 2M)), from r = 2M to 12M; ring colour from the computed Kretschmann scalar. Hover a ring for its value.

Where is the Hawking radiation? Not in this picture, and it could not be. Both programs compute Einstein’s general relativity, in which nothing ever leaves the event horizon. Hawking radiation, which Stephen Hawking described in 1974, appears only when quantum physics is added to the curved space at the horizon, and neither program does that. His formula gives a black hole the mass of the Sun a temperature of about 0.00000006 degrees above absolute zero (6.17 × 10−8 K), far colder than the 2.726 K afterglow of the Big Bang that fills space, so in practice it cannot be seen. [8]

Side by side, measured

The figures below were measured, not judged by eye: the work counts by running each version on the black-hole case, the rest by parsing the code.

Human libraryAI libraryAI function
Simplification steps (black hole)256136136
Derivatives taken (black hole)992672672
Classes / functions5 / 334 / 150 / 2
Classes and functions with documentation33 of 3815 of 192 of 2
Decision points (ifs, loops)462615
Deepest nesting335
Single-letter variable names21%14%38%
Error types raisedValueError, TypeError, ExceptionValueErrorValueError
Libraries neededSymPy, NumPySymPySymPy

Alike

Different

On a well-defined mathematical task, good human code and AI code converge: the substance is the same, and the differences are in habits, tools and age.

What the research says

Speed, for experienced developers. In a 2025 randomised trial by METR, 16 experienced open-source developers did 246 tasks on projects they knew well. With AI tools allowed they took 19% longer, though beforehand they expected to be 24% faster and afterwards believed they had been 20% faster. [2]

Speed, on a simple task. In a 2023 experiment, developers asked to write an HTTP server in JavaScript finished 55.8% faster with GitHub Copilot than without. [3]

Security. A 2022 study found that people with an AI assistant wrote significantly less secure code than those without, and were more likely to believe it was secure. [4] A 2021 study prompted GitHub Copilot with 89 security-sensitive scenarios; of the 1,689 programs it produced, about 40% were vulnerable. [5]

So who is the better programmer?

On this job, neither lost. The AI produced correct, readable code from one sentence in seconds, and a tested library from a second; the humans’ code is just as correct and carries decisions the AI was never asked to make. The research says the same thing from both sides: AI is fast at well-specified work and less reliable where the context is large, the conventions are local or security matters. A single file rarely shows who wrote it. What changes is the pattern over time: CodeDelta’s own measurements found an agent-built codebase editing about one statement in five hundred in place, where established human-maintained projects edit about one in six. [6]

Questions worth asking

See also: Can you tell AI code from human code, and does it matter?, on what can and cannot be detected in AI-written code.

Measure any codebase the same way

Churn, REWORK and the Agent Scan run on any two snapshots of a repository, on macOS, Linux and Windows. Download CodeDelta →

Disclaimer. One task is not a general verdict on human or AI programmers. The human code is EinsteinPy’s, used under its MIT licence; EinsteinPy’s developers did not take part in, endorse or review this article. Study findings are as published by their authors. Corrections are welcome and will be published with a dated revision note.

Appendix: fun facts!

Computed from r = 2GM/c² with G = 6.67430 × 10−11 m³ kg−1 s−2, c = 299,792,458 m/s, a solar mass of 1.98847 × 1030 kg and an Earth mass of 5.9722 × 1024 kg.

Sources and references

  1. EinsteinPy: github.com/einsteinpy/einsteinpy, MIT licence; files einsteinpy/symbolic/christoffel.py and riemann.py at commit 966c94ef (28 August 2020); riemann.py first committed 21 June 2019.
  2. Becker, Rush, Barnes, Rein, Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity, METR, July 2025.
  3. Peng, Kalliamvakou, Cihon, Demirer, The Impact of AI on Developer Productivity: Evidence from GitHub Copilot, February 2023.
  4. Perry, Srivastava, Kumar, Boneh, Do Users Write More Insecure Code with AI Assistants?, November 2022.
  5. Pearce, Ahmad, Tan, Dolan-Gavitt, Karri, Asleep at the Keyboard? Assessing the Security of GitHub Copilot’s Code Contributions, August 2021.
  6. REWORK measurements: paper C.
  7. Method: both programs run with SymPy 1.14.0 on 28 October 2026 on the three metrics named above; components compared after mapping EinsteinPy’s index order [t][s][r][n] to the standard Rρσμν; Kretschmann scalar computed from each. AI library also checked for the llll–ulll round trip, the antisymmetry and pair symmetry of Rabcd, and a Lorentz boost at v = 3/5 c with the metric transformed alongside. Simplification and derivative counts by counting each top-level call while running the black-hole case; class, function, documentation, branch, nesting, naming and error counts by parsing each file with Python’s ast module. Line, statement and comment counts from the CodeDelta 2.6.4 engine; the human library measured as einsteinpy/symbolic/metric.py, tensor.py, christoffel.py and riemann.py at commit 966c94ef.
  8. Hawking radiation: S. W. Hawking, Black hole explosions?, Nature 248, 30–31 (1974). Temperature from T = ℏc³/(8πGMkB) with CODATA constants and a solar mass of 1.98847 × 1030 kg. Background temperature: D. J. Fixsen, The Temperature of the Cosmic Microwave Background (2009), 2.7260 ± 0.0013 K.
  9. Liu, Lin, Hewitt, Paranjape et al., Lost in the Middle: How Language Models Use Long Contexts, July 2023.