Write UberGraph concords in a deterministic order#901
Draft
gaurav wants to merge 1 commit into
Draft
Conversation
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 Pythonset: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 aunique_prefixesconflict by keeping whichever bridge it processes first and silently dropping the other (src/babel_utils.py, theif not setok: continuebranch). 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, andDISEASE_UNIQUE_PREFIXESadmits only one HP:MONDO:0003425 --exactMatch--> UMLS:C0029089 --eq--> HP:0000602"Ophthalmoplegia" (correct)MONDO:0003425 --exactMatch--> SNOMEDCT:78097002 --xref--> HP:0007824"Total ophthalmoplegia" (a subtype; bad MONDO xref, worked around in Report MONDO:0003425 -> SNOMEDCT:78097002 bad exactMatch upstream to MONDO #900)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:78097002sorts beforeUMLS:C0029089, so after this PR the wrong HP wins by default and it takes a hand-curatedinput_data/mondo_badxrefs.txtentry (#900) to correct that one clique. Awarding contested cross-references byexactMatch>closeMatch>hasDbXrefis 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
babel-clique-diff; commit the artifacts underdocs/pipelines/.unique_prefixesconflict 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.build_sets()callers (anatomy, chemicals, gene/protein) for the same sensitivity.glom()'s merge refusals, so contested bridges stop being invisible.🤖 Generated with Claude Code