Ph/pr50 retold - #52
Draft
hansenp wants to merge 11 commits into
Draft
Conversation
OntologyTraverser.initLayer named the BOQA concept it served rather than the operation it performs: expanding a set of HPO terms with their ancestors. The new name says what the method returns, which matters because the same call expands both the patient layer and the disease layer. Renames the method and its three call sites in BoqaSetCounter and BlendedCounter, and the corresponding test method. No behavior change. Two usage examples in the class javadoc still name initLayer; they are left as they are on the source branch.
Renames five test classes: DiseaseDataParseIngestTest, DiseaseDataPhenolIngestTest, DiseaseDataCmpParsePhenolIngestTest, BlendedDiseaseDataTest and DiseaseGeneAssociationsTest, each to a Phenotype* name, together with their self-references. The names anticipate renaming DiseaseData to PhenotypeData, which holds phenotype annotations keyed by disease rather than diseases. That rename does not happen here, so the tests name classes that do not exist yet. No test is added, removed or changed otherwise. A javadoc reference in DiseaseDataParser still names the old test and is left as it is on the source branch.
These two records were the Exomiser-facing input and output types introduced in #45. #47 superseded them with diseases.TargetDisease and CandidateResult but left the files in place, unreferenced by any main source. Deleting them leaves BlendedResultTest referring to classes that no longer exist, so test sources stop compiling and the whole suite becomes unrunnable. The test is not touched here, matching the source branch. BoqaBlendedExomiserAnalyzerTest refers to BlendedResult too but still compiles, because #47 left its body inside a block comment.
TargetDisease becomes a sealed interface with two variants: Phenotype carries a disease with its HPO terms, Gene adds the gene that made the disease a candidate. Both carry observedHpoIds, so a target disease now arrives with its own annotations rather than being looked up in HpoDiseases by the code that scores it. CandidateDisease follows: Single and Blended drop their separate observed sets and read them off the target disease, and getMelded unions the components' terms instead of querying HpoDiseases. Call sites in BlendedCounter and BoqaBlendedExomiserAnalyser are updated to the new types. The wrapper keeps its current structure here; it is rewritten once the new pipeline exists.
BlendedCounter no longer holds diseases. It keeps only the ontology and gets both layers as term sets from its caller, through the new Counter.computeBoqaCountsFromDisease. BoqaCountsNew carries the four counts without a disease id or label, since a counter that is handed two sets does not know whose they are. Both layers are now expanded with getObservedWithAncestors. The disease layer was previously stored unexpanded, which made a perfect match score 335 false positives on OMIM:620851; the counter now agrees with BoqaSetCounter exactly on that pair. The old computeBoqaCounts is left returning a constant and getDiseaseIds returning an empty set, and the interface default of computeBoqaCountsFromDisease throws, so neither implementation of Counter satisfies the whole interface. The HP:0000118 filter is not carried over: isPhenotypicFeature is added but never called.
CandidateDiseaseNew forms the candidate list from TargetDisease.Gene alone: every anchor as a Single, every gene-disjoint pair as a Blended. Since commit "Split TargetDisease into Phenotype and Gene" put the HPO terms on the target disease, no HpoDiseases lookup is needed and none is done. A Blended holds its components as a set and no longer carries a melded disease. The concatenated OMIM:a-OMIM:b identifier that CandidateDisease built is dropped, along with the melded label, gene id and gene symbol. Nothing calls this class yet; CandidateDisease still serves the wrapper.
CandidateResult gains score() and counts() and becomes Comparable, ordering results by score descending with NaN last, so callers can sort without unwrapping the variants. DiseaseComponent moves from BoqaCounts to BoqaCountsNew. Blended changes from (components, finalDiseaseModel) to (components, counts, score). A blended result no longer names the melded disease, matching CandidateDiseaseNew, where a blend is its component set. The check that a Blended has at least two components is commented out rather than removed, so an empty blend is now constructible. BoqaBlendedExomiserAnalyser converts its counts to BoqaCountsNew to keep compiling; both the conversion and the surrounding loop go away when the wrapper moves to the new pipeline.
BoqaPatientAnalyzerNew takes a patient, a counter and a candidate list and returns ranked CandidateResults: raw log scores, softmax normalization, conversion to CandidateResult, the improvement filter for blends, sorting and the results limit. BoqaResultNew pairs BoqaCountsNew with a score and the candidate it belongs to, so the analyzer can reattach identity to counts that no longer carry it. This moves the analysis out of the Exomiser wrapper, which so far did the scoring itself, into the core module. Normalization runs over singles and blends together, so scores are spread across n + n(n-1)/2 candidates and do not sum to 1 once blends are filtered. computeBoqaResults sorts the list returned by computeBoqaResultsRawLog, which is unmodifiable, so the method throws UnsupportedOperationException for any non-empty candidate list. Util gains a rescaling variant for BoqaResultNew that nothing calls.
computeBlendedBoqaResults now builds a BlendedCounter and a CandidateDiseaseNew list and hands both to BoqaPatientAnalyzerNew. The scoring loop it used to run, including the per-candidate counts, the component rescoring and the improvement filter, is gone; all of it lives in the core module now. CandidateDisease and its melded TargetDisease are left behind unreferenced. The constructor still ingests HpoDiseases into a DiseaseData field that nothing reads, and the results limit is hard-coded to 100000.
Adds plantuml-generator-maven-plugin, scanning org.p2gx.boqa.core and writing boqa-core.puml with fields and methods hidden, to get a picture of the type graph the blended refactor has produced. The execution is bound to the compile phase, so it runs on every build rather than on request, and it writes into the project root, where the generated file is neither tracked nor ignored. The extra plugin also has to be downloaded before an offline build works.
TargetDisease.Phenotype and Gene become PhenotypeOnly and PhenotypeAndGene, naming the contrast rather than implying that the second variant is a gene. CandidateDiseaseNew and CandidateResult rename their variants likewise, and CandidateResult moves from the diseases package to analysis, next to the analyzer that builds it. The disease-data tests take a PhenotypeOnly prefix. CandidateResult also drops counts(): BlendedResult no longer carries the counts of the blend, which toCandidateResult now discards, while SingleResult still reaches its own through its DiseaseComponent. CandidateDisease is renamed along with the rest although nothing has referenced it since the wrapper moved to the new pipeline. That's the last commit. Once you've made it, I'll walk the eleven as a reviewer would and draft the PR description — the branch is currently 11 commits ahead of ph/pr47-retold and exactly matches PR50's head.
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.
Sealed BOQA-blended refactor, in reviewable commits
This is the code of #50, unchanged, split into eleven commits with messages that
say what each one does. Nothing is fixed, added or left out: the tree at the tip
of this branch is byte-identical to
sealed. The goal is to make #50 reviewable,and to record what reading it turned up.
It is stacked on #51, which does the same for #47, because #50 is based on
bboqa. If #50 is merged instead, this PR can be closed — the value here is thecommit messages and the findings below, not the code.
sealedmoved while this branch was being written. #50 had four commits when Istarted and six when I finished;
b17bae9and6e53bdaarrived in between, thesecond on the morning of the meeting. Commits 1–9 here retell the first four,
commits 10 and 11 the two that followed, so the boundary stays visible.
What the change does
One decision drives the whole PR:
Everything else follows from it:
TargetDiseasebecomes a sealed interface,PhenotypeOnly/PhenotypeAndGene,both carrying their HPO terms.
BlendedCounterkeeps only the ontology.Counter.computeBoqaCountsFromDiseasetakes two term sets and counts one against the other, with no notion of which
disease it is looking at;
BoqaCountsNewtherefore drops the disease id and label.CandidateDiseaseNewbuilds the candidate list from target diseases alone, withno
HpoDiseaseslookup. A blend is its set of components; the meldedOMIM:a-OMIM:bdisease is gone.BoqaPatientAnalyzerNewtakes a patient, a counter and a candidate list and doesthe whole analysis — raw log scores, softmax normalization, conversion to
CandidateResult, the improvement filter, sorting, limiting.BoqaBlendedExomiserAnalysershrinks from a forty-line scoring loop to afour-line delegation.
Also included: a PlantUML class-diagram plugin bound to the
compilephase.What this changes relative to #47
#47's question 1 is answered "yes, and further". I asked on #51 whether the
blended path should stop going through
DiseaseData. #47 hadBlendedCounterreading
HpoDiseasesdirectly; #50 removes even that. Core now holds nodisease-annotation source on the blended path at all.
The counter bug from #51 is fixed. #47's
BlendedCounterstored rawpresentAnnotations()as its disease layer while expanding the patient side, so aperfect match scored hundreds of false positives.
computeBoqaCountsFromDiseaseexpands both sides. Scoring a disease against its own annotations:
Exact agreement with plain BOQA on that pair.
The
analysisleftovers from #45 are deleted.analysis.TargetDiseaseandanalysis.BlendedResultare gone, answering #51's question 5.The scoring moves into core. #47 scored inside the Exomiser wrapper; #50 puts
it in
BoqaPatientAnalyzerNew, which is the shape the comment in the wrapper sayswas agreed.
Not changed from #47: gene-disjointness still compares gene symbols while the
CLI compares gene IDs; alpha and beta are still fixed to
defaultParams();the improvement filter is still a bare
>rather than a Bayes factor. #51'squestions 2, 3 and 4 are all still open.
Findings
Review notes; none are fixed on this branch.
The blended path cannot run. Two independent crashes, both verified against
the compiled classes at this tip.
CandidateDiseaseNew.createCandidateDiseasesbuilds its list fromcreateSingleDiseaseCandidates, which ends inStream.toList()— unmodifiable —then calls
.add()on it (CandidateDiseaseNew.java:92). Calling the wrapper theway Exomiser would, with two gene-disjoint anchors:
It is masked only when there is nothing to blend: fewer than two anchors, or every
pair sharing a gene.
Behind it,
BoqaPatientAnalyzerNew.computeBoqaResultssorts the list returned bycomputeBoqaResultsRawLog, also fromStream.toList()(
BoqaPatientAnalyzerNew.java:121). Handing it a candidate list built by hand:Fixing either one still leaves the other. The second is worth fixing by deletion:
everything after that
sortis order-independent —maxLogPcomes from.max(),the sum from a full reduce, and the method ends with
.sorted().limit(...)on theCandidateResults — so removing the line fixes the crash and changes no output.Set.of(a, b)inmakeAllowedCombinationsrejects duplicates, so the same diseaseanchored twice throws
IllegalArgumentException: duplicate elementrather thanbeing skipped the way a same-gene pair is.
The test suite cannot run.
./mvnw testfails attestCompile, so zero of the~3266 tests execute.
BlendedResultTestis a live test ofanalysis.BlendedResult,which commit 3 deletes without touching the test; today's commit renames the file to
BlendedDiseaseNewDiseaseResultResultTest.javarather than deleting it. Nothingcovers any of the new code. Note that
mvn test-compilereports BUILD SUCCESS on awarm
target/— Maven does not recompile an untouched test when a main classdisappears — so this is invisible locally until
clean. No CI runs onsealed(
gh pr checks 50: no checks reported), so nothing caught it.Counterno longer means anything.BlendedCounter.computeBoqaCountsreturns ahard-coded constant and
getDiseaseIds()returns an empty set:while the interface default of
computeBoqaCountsFromDiseasethrowsRuntimeException("... needs refactor"), which is whatBoqaSetCounterinherits.Neither implementation satisfies the interface: one lies, the other throws. A caller
holding a
Countergets plausible-looking garbage rather than an error.Blended results lost their counts. Today's commit removes
counts()fromCandidateResultand the field fromBlendedResult, sotoCandidateResultcomputes the blend's counts and discards them, flagged with
// TODO actually make sure BlendedResults have also counts and score. The loss isasymmetric: a
SingleResultstill reaches its counts through itsDiseaseComponent.Since a blend's counts are the only evidence separating "this blend explains more
phenotype" from "this blend has a bigger layer", this removes the reason to trust
the ranking. #45's
BlendedResultcarriedcomponentCountsandblendedCountsforexactly this.
Normalization spans singles and blends. The softmax runs over the whole
candidate list, so with n anchors the denominator has n + n(n−1)/2 terms. Single
diseases are diluted by the presence of blends, which makes these scores
incomparable with plain BOQA over the same anchors; and because the improvement
filter runs after normalization, what is returned does not sum to 1 either.
The
HP:0000118filter is still missing.isPhenotypicFeatureis written intoBlendedCounterand never called, so nothing restricts a caller's terms tophenotypic abnormalities the way
BoqaSetCounterdoes on ingest. With annotationsnow supplied by the caller, this matters more than in #47, not less. The agreement
in the table above holds because ReNU's annotations happen to be all phenotypic
abnormalities.
Guards removed alongside their tests.
CandidateResult.Blended's"at least two components" check is commented out, not deleted, in the same PR that
deletes the only test of that invariant. An empty blend is now constructible, and
naiveImprovementends.max().orElse(0.0): with raw log scores that sentinelmeans "reject", with normalized probabilities it means "accept". The same line
changes meaning between commits 7 and 8.
Four parallel type families, and dead code being maintained.
BoqaCounts/BoqaCountsNew,CandidateDisease/CandidateDiseaseNew,BoqaResult/BoqaResultNew,BoqaPatientAnalyzer/BoqaPatientAnalyzerNew, withno removal path stated.
CandidateDiseasehas been unreferenced since commit 9 —an unused import in
BlendedCounterand a stale comment are all that name it — yettoday's commit renamed it throughout, ~43 lines of edits to a class nothing
constructs.
CandidateDiseaseNew's logger is declared asgetLogger("org.p2gx.boqa.core.diseases.CandidateDisease"), so it logs under thedead class's name.
Smaller things. The wrapper's constructor still runs
DiseaseDataPhenolIngest.ofover all diseases into a
DiseaseDatafield nothing reads — worse here than in #47,since the design now says annotations come from the caller.
resultsLimitishard-coded to 100000 with a TODO.
Util.reScaledRawLogBoqaExomiserScoresNewandcomputeBoqaResultsRescaledare dead, and the rescale still gives the bestcandidate 1.0 however poorly it fits and yields
NaNwhen scores are equal.BoqaCountsNew's guard rejects negatives with the message "must be greater thanzero".
BoqaPatientAnalyzerNew's javadoc promises aBoqaAnalysisResultand pointsat
BoqaSetCounter, neither of which it uses. Renames left dangling references: theinitLayerexamples inOntologyTraverser's javadoc, andDiseaseDataCmpParsePhenolIngestTestinDiseaseDataParser's. The PlantUML pluginis bound to
compile, so it runs on every build and writes a 17.9 KB generated fileinto the repo root that is neither tracked nor ignored; a clean checkout now needs
network for its first build.
The naming is not settled. These types have been renamed three times in three
weeks:
TargetDisease.Disease/Gene→Phenotype/Gene→PhenotypeOnly/PhenotypeAndGene; the disease-data tests twice, ending atPhenotypeOnlyPhenotypeAndGeneAssociationsTest— a class testing gene-to-diseaseassociations — and
BlendedDiseaseNewDiseaseResultResultTest, which has "Result"twice and still tests a deleted class.
PhenotypeAndGeneis a genuine improvement;SingleDiseaseNewis not.How this branch was built
Each commit was produced by checking the relevant paths out of
sealedat thecommit being retold, then verified: all eleven compile main sources on their own,
and
git diff 6e53bdaat the tip is empty.Test sources compile through commit 2 and stop at commit 3, and never recover. That
matches the endpoint; it is not an artifact of the split.
Three commits pass through states that existed on no machine, because #50 delivers
them as blobs. Commit 1 updates the
initLayercall site inBlendedCounterthatcommit 5 then deletes. Commit 4 updates two record-deconstruction patterns and one
factory call in the wrapper, replaced in commit 9. Commit 7 adds a four-line
asCountsNewbridge in the wrapper, also removed in commit 9. The endpoint isunaffected.
Questions
BlendedResultget its counts back before this merges? Without them ablend's ranking cannot be checked by a reader, and the asymmetry with
SingleResultmeans the two variants report different things.comparable with plain BOQA — or is a distribution over the joint candidate space
what is wanted, given the candidates are not mutually exclusive?
OMIM:a-OMIM:b, Blended BOQA Exomiser integration #47 built it, this removes it. If Exomiser maps components backonto its own per-gene results it may not need one — but
componentsis aSet,so a caller rebuilding a label gets no stable order.
HP:0000118filter now that the caller supplies annotations,and should
TargetDiseasesay so?Counterinterface with two half-implementations the intended end state?If it is scaffolding,
UnsupportedOperationExceptionon both sides would behonest where a hard-coded
BoqaCountsis not.*Newpairs? A reviewer cannot currently tellwhich of each pair to build on, and dead code is being kept in sync by hand.
target/, and shouldit be its own PR? It is the only commit here that touches no Java.
CLI and this path share the rule? Should the improvement filter be a Bayes factor
threshold, and optional? Should alpha and beta stay fixed in the wrapper?