Skip to content

Write UberGraph concords in a deterministic order#901

Draft
gaurav wants to merge 1 commit into
mainfrom
deterministic-concord-order
Draft

Write UberGraph concords in a deterministic order#901
gaurav wants to merge 1 commit into
mainfrom
deterministic-concord-order

Conversation

@gaurav

@gaurav gaurav commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Draft: the one-line change is here, but it has not been measured yet. See "What still needs doing".

Fixes #902.

The bug

src/ubergraph.py:build_sets() writes each subject's xref targets by iterating a Python set:

v = set([norm(x, other_prefixes) for x in v])
for x in v:                       # <- randomized string-hash order, per process
    ...
    concordfiles[p].write(f"{k}\t{types2relations[set_type]}\t{x}\n")

Set-of-strings iteration order is randomized per process, so the same UberGraph data produces differently-ordered concord files on every pull. This PR sorts before writing.

Why order changes cliques

glom() resolves a unique_prefixes conflict by keeping whichever bridge it processes first and silently dropping the other (src/babel_utils.py, the if not setok: continue branch). When a clique has two competing bridges to the same unique prefix, concord line order picks the winner.

Worked example from #886: MONDO:0003425 "ophthalmoplegia" reaches two different HP terms, and DISEASE_UNIQUE_PREFIXES admits only one HP:

Different pulls of the identical MONDO concord put a different bridge first, and the clique flips. Confirmed by swapping just those two lines in the concord and re-glomming.

The blast radius is not small: 33 of the 93 rows in #886's clique diff were this noise rather than the change under test. Re-glomming that branch's own inputs with the MP source fully excluded reproduced all 33.

Scope: this fixes reproducibility, not correctness

#894 originally recorded this behavior as "deterministic given identical inputs ... not Python hash-seed nondeterminism" and "independent of concord order". Both were wrong, and that issue has been corrected and rescoped; the nondeterminism is now stated properly in #902, which this PR closes.

What #894 still tracks, and what this PR does not do, is make the tie-break correct. Sorting only makes an arbitrary rule reproducible: the winner becomes "alphabetically smallest target CURIE", which has nothing to do with evidence strength. In the example above SNOMEDCT:78097002 sorts before UMLS:C0029089, so after this PR the wrong HP wins by default and it takes a hand-curated input_data/mondo_badxrefs.txt entry (#900) to correct that one clique. Awarding contested cross-references by exactMatch > closeMatch > hasDbXref is the real fix.

That is an argument for landing #894's fix too, not for holding this one: today the winner is both arbitrary and random, and randomness is the part that corrupts impact reports.

What still needs doing before this leaves draft

  • Rebuild disease/phenotype before and after this change and run babel-clique-diff; commit the artifacts under docs/pipelines/.
  • Size the churn. Sorting picks a winner wherever a unique_prefixes conflict exists today, and per the above it may pick a worse one than the last pull happened to. Worth counting how many contested cliques exist and spot-checking whether the alphabetical winner is systematically wrong.
  • Decide whether to land glom: unique_prefixes tie-break for a contested cross-reference ignores evidence strength #894's evidence-strength tie-break on top of this, or instead of it.
  • Check the other build_sets() callers (anatomy, chemicals, gene/protein) for the same sensitivity.
  • Consider logging glom()'s merge refusals, so contested bridges stop being invisible.

🤖 Generated with Claude Code

build_sets() emitted each subject's xref targets by iterating a Python set, whose
string-hash order is randomized per process. The same UberGraph data therefore
produced differently-ordered concord files on every pull.

That ordering is not cosmetic. glom() resolves a unique_prefixes conflict by
keeping whichever bridge it processes first and silently dropping the other, so
when a clique has two competing bridges to the same unique prefix, the concord
line order picks the winner. Two builds from the same source data could disagree.

Observed in PR #886: MONDO:0003425 "ophthalmoplegia" has bridges to both
HP:0000602 "Ophthalmoplegia" (via UMLS:C0029089) and HP:0007824 "Total
ophthalmoplegia" (via SNOMEDCT:78097002). Different pulls of the identical MONDO
concord put a different one first, and the clique flipped accordingly. 33 of the
93 rows in that PR's clique diff turned out to be this noise rather than the
change under test.

Note this contradicts two claims in #894, which recorded the behavior as
"deterministic given identical inputs, not Python hash-seed nondeterminism" and
"independent of concord order". Both are wrong: it is exactly hash-seed
nondeterminism, expressed through concord order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-project-automation github-project-automation Bot moved this to Backlog in Babel sprints Jul 9, 2026
gaurav added a commit that referenced this pull request Jul 9, 2026
…ter build

Replace the main-vs-branch clique-diff (which mixed in UberGraph concord-order
noise, per #894/#901) with a diff built from one fixed pull of
data/branch-mp/intermediate/disease: split_mutually_exclusive_cliques()
disabled (before) vs enabled (after). Both builds share identical glom state
up to the split, so the diff cannot contain ordering noise by construction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
gaurav added a commit that referenced this pull request Jul 9, 2026
…line

Explain why the baseline changed from main-vs-branch to a same-concords
overlap-allowed-vs-disjoint diff, update the headline counts and examples to
match the regenerated CSV, and cross-link #894/#901 as the source of the
ordering-noise problem this sidesteps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gaurav gaurav modified the milestone: Babel v1.18 Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

build_sets() writes UberGraph concords in nondeterministic order, so glom's unique_prefixes tie-break flips between builds

1 participant