deterministic_turtle is not idempotent on at least one real graph: canonicalizing its own
output produces a different file, and the process does not converge.
Symptom
pass 0 (as written) sha=8a4bcbd8c0de4252 229326 bytes 5115 triples
pass 1 sha=f1eddc962da07919 230308 bytes 5267 triples
pass 2 sha=97f31502b3284172 231294 bytes 5419 triples
pass 3 sha=cecca06d8634020e 232274 bytes 5571 triples
pass 4 sha=4ba887b9604efdd2 233254 bytes 5723 triples
+152 triples per pass, indefinitely. Comparing predicate histograms between passes, only
collection plumbing changes:
| predicate |
pass 1 |
pass 2 |
rdf:first |
352 |
428 |
rdf:rest |
352 |
428 |
Every other predicate is unchanged, so no statement is gained or lost — the collections are
being duplicated on each round trip.
Why it matters
This breaks the property the library exists to provide. A consumer that canonicalizes generated
RDF and commits it cannot verify the result: a CI check of the form "re-canonicalize and compare
bytes" fails on a file that was just canonicalized. That is exactly how we use it — see
ASCS-eV/asam-openx-standards, scripts/check_canonical.py.
Reproduction
The graph is a SHACL shapes file produced by shacl-play owl2shacl, attached below. It contains
81 sh:in constraints referencing 51 distinct collection nodes — 16 of those are referenced by
more than one property shape, with up to 5 references each.
from rdflib import Graph
from diffable_rdf import deterministic_turtle
cur = open("repro-shacl.ttl", "rb").read()
for i in range(5):
g = Graph().parse(data=cur, format="turtle")
print(i, len(g))
nxt = deterministic_turtle(g).encode()
if nxt == cur:
print("fixed point")
break
cur = nxt
What I could not narrow down
Shared collections alone are not sufficient to trigger it. One collection node shared by
two, three, four or five subjects converges after a single pass:
N=2: converges triples per pass: [6, 6]
N=3: converges triples per pass: [7, 7]
N=4: converges triples per pass: [8, 8]
N=5: converges triples per pass: [9, 9]
So does the case of several subjects each owning an identical-content collection. The trigger is
therefore narrower than plain sharing — plausibly an interaction between the
Weisfeiler-Lehman blank-node hashing and collections whose cells are structurally
indistinguishable, but I did not isolate it. The attached file reproduces it reliably.
Environment
diffable-rdf 0.0.1, rdflib 7.6.0, pyoxigraph 0.5.9, Python 3.12.
Suggestion
Whatever the cause, a regression test asserting deterministic_turtle reaches a fixed point in
one pass would be worth having for any graph containing rdf:Lists — that is the invariant the
library promises and the one that broke.
deterministic_turtleis not idempotent on at least one real graph: canonicalizing its ownoutput produces a different file, and the process does not converge.
Symptom
+152triples per pass, indefinitely. Comparing predicate histograms between passes, onlycollection plumbing changes:
rdf:firstrdf:restEvery other predicate is unchanged, so no statement is gained or lost — the collections are
being duplicated on each round trip.
Why it matters
This breaks the property the library exists to provide. A consumer that canonicalizes generated
RDF and commits it cannot verify the result: a CI check of the form "re-canonicalize and compare
bytes" fails on a file that was just canonicalized. That is exactly how we use it — see
ASCS-eV/asam-openx-standards,scripts/check_canonical.py.Reproduction
The graph is a SHACL shapes file produced by
shacl-play owl2shacl, attached below. It contains81
sh:inconstraints referencing 51 distinct collection nodes — 16 of those are referenced bymore than one property shape, with up to 5 references each.
What I could not narrow down
Shared collections alone are not sufficient to trigger it. One collection node shared by
two, three, four or five subjects converges after a single pass:
So does the case of several subjects each owning an identical-content collection. The trigger is
therefore narrower than plain sharing — plausibly an interaction between the
Weisfeiler-Lehman blank-node hashing and collections whose cells are structurally
indistinguishable, but I did not isolate it. The attached file reproduces it reliably.
Environment
diffable-rdf0.0.1,rdflib7.6.0,pyoxigraph0.5.9, Python 3.12.Suggestion
Whatever the cause, a regression test asserting
deterministic_turtlereaches a fixed point inone pass would be worth having for any graph containing
rdf:Lists — that is the invariant thelibrary promises and the one that broke.