Skip to content

Feat multispec - #251

Open
fbastian wants to merge 40 commits into
developfrom
feat_multispec
Open

Feat multispec#251
fbastian wants to merge 40 commits into
developfrom
feat_multispec

Conversation

@fbastian

Copy link
Copy Markdown
Member

PR solely for being able to comment in diff

@fbastian fbastian self-assigned this Jun 19, 2026
* @return The {@code Stream} of {@code SimilarityExpressionCall}s that are
* the similar expression calls for the requested parameters.
*/
public Stream<SimilarityExpressionCall> loadSimilarityExpressionCalls(int taxonId,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed this method should work as for org.bgee.model.expressiondata.call.ExpressionCallLoader and org.bgee.model.expressiondata.call.ExpressionCallService?

Meaning, this method should accept a org.bgee.model.expressiondata.call.CallFilter.ExpressionCallFilter2, or a new class extending org.bgee.model.expressiondata.call.CallFilter to accept multiple GeneFilters?

Then under the hood, the code could use the ExpressionCallLoader or ExpressionCallService directly

Set<AnatEntitySimilarity> anatEntitySimilaritiesFromCellTypeFilter =
new HashSet<>(anatEntitySimilaritiesFromAnatFilter);

// Extract anat entity and cell type IDs from conditionFilters for filtering similarities

@fbastian fbastian Jun 19, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic should go in a separate function probably, returning the Map<ConditionParameter<?, ?>, ComposedFilterIds<String>> condParamToFilter

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, don't separate anatEntities and CellTypes work, do everything at once, and separate at the end using methd isCellType() for line 1107

}
filterAnatEntityIds.addAll(expandedAnatIds);
}
if (cellIds != null) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor to avoid code duplication (same as for anatIds)
Probably it's more optimal to instantiate the ontology once for both anat and cell type at the same time


if (!filterAnatEntityIds.isEmpty()) {
anatEntitySimilaritiesFromAnatFilter = anatEntitySimilaritiesFromAnatFilter.stream()
.filter(s -> s.getSourceAnatEntities().stream()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it use getAllAnatEntities?

// Build maps for grouping (same logic as loadSimilarityExpressionCalls)
Map<AnatEntity, Set<AnatEntitySimilarity>> similaritiesByAnatEntityFromAnatFilter =
anatEntitySimilaritiesFromAnatFilter.stream()
.flatMap(sim -> sim.getSourceAnatEntities().stream()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAllAnatEntities

.collect(Collectors.toList());

if (this.requestParameters.isGetResultCount()) {
count = (long) allCalls.size();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, so here it shows that the code hasn't be written as the ExpressionCallLoader, and all results are always all in memory. Is that OK?

if (offset < 0) {
throw log.throwing(new InvalidRequestException("Offset cannot be less than 0."));
}
calls = allCalls.stream()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

.collect(Collectors.toList());
}
if (this.requestParameters.isGetFilters()) {
// Multi-species endpoint does not support post-filters; ignore.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw InvalidRequestException then

log.traceExit();
}

private MultispecExprCallItem toMultispecExprCallItem(SimilarityExpressionCall2 sc) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel lazy to read, explain to me why it's needed when we meet :p

Comment thread pom.xml Outdated
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>1.17-SNAPSHOT</version>
<version>1.16</version>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether this should be merge back

@fbastian
fbastian requested a review from hdetering June 19, 2026 14:28
* @return The {@code Stream} of {@code SimilarityExpressionCall2}s that are
* the similar expression calls for the requested parameters.
*/
public Stream<SimilarityExpressionCall2> loadSimilarityExpressionCalls2(int taxonId,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can extend CallFilter to create a MultiSpeciesExpressionCallFilter accepting multiple GeneFilters, taxonId, and the boolean onlyTrusted

throw log.throwing(new IllegalArgumentException("No gene filter should be null"));
}

Set<GeneFilter> clnGeneFilters = Collections.unmodifiableSet(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set clnGeneFilters = geneFilters == null || geneFilters.isEmpty() ?Collections.unmodifiableSet(this.getServiceFactory().getSpeciesService()
.loadSpeciesByTaxonIds(Collections.singleton(taxonId), false)
.stream().map(s -> new GeneFilter(s.getId()))
.collect(Collectors.toSet())) :
geneFilters;

Set<AnatEntitySimilarity> anatEntitySimilaritiesFromCellTypeFilter =
new HashSet<>(anatEntitySimilaritiesFromAnatFilter);

// Extract anat entity and cell type IDs from conditionFilters for filtering similarities

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, don't separate anatEntities and CellTypes work, do everything at once, and separate at the end using methd isCellType() for line 1107


// Load ExpressionCall2 for each species and merge (ExpressionCallFilter2 has single GeneFilter)
List<ExpressionCall2> allCalls = new ArrayList<>();
for (GeneFilter gf : clnGeneFilters) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a solution not to put everything in memory at once for a given GeneFilter, and still retrieving all data at once for `AnatEntitySimilarity, would be to have 2 interleaved loops:

for (AnatEntitySimilarity sim: sims) {
    for (GeneFilter gf : clnGeneFilters) {
      call ExpressionCallLoader
      for (Gene gene in returned ExpressionCalls) {
          => produce ONE SimilarityExpressionCall
      }
   }
}

Should we put all orthologous genes in a SimilarityExpressionCall?
Should we provide "pagination" on number of AnatEntitySimilaritys iterated? Or on number of SimilarityExpressionCalls returned? But then, complicated to know which AnatEntitySimilarity and GeneFilter to restart the procedure from

* Extracts the anatomical entity from a {@code Condition2} for ANAT_ENTITY_CELL_TYPE.
* ComposedEntity order: cellType at index 0, anatEntity at index 1 when both present.
*/
private static AnatEntity getAnatEntityFromCondition2(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See contructor of Condition2

Harald Detering added 10 commits July 15, 2026 14:51
Make tree node collections immutable and add equals/hashCode; pass raw arguments to traceEntry and clarify Javadoc for speciesById.
Restore develop's dependency version; 1.17-SNAPSHOT is not on Maven Central and must be
built from brunorozendo/log4jdbc-log4j2. Document one-time setup in m2_local_repository/README.txt.
Unify anat/cell similarity handling, extract condParamToFilter helpers, use
getAllAnatEntities for filtering and maps, deduplicate ontology expansion, and
classify anat vs cell terms with isCellType when building filters and reading
Condition2 values.
Move expressed/not-expressed aggregation from MultiSpeciesCallService into
SimilarityExpressionCall2 when built from supporting ExpressionCall2s.
…Loader.

Add a CallFilter-based multispec filter and a loader that paginates similarity
calls using AnatEntitySimilarity-driven ExpressionCallLoader iteration instead
of loading all expression calls into memory.
Use loadData and loadDataCount instead of collecting all similarity calls
in memory, and reject get_filters with InvalidRequestException. Added unit tests.
…all2 in API response.

MultispecExprCallResponse now mirrors ExpressionCallResponse by holding model
calls directly; JSON field derivation moved to MultispecExprCallResponseTypeAdapter.
Harald Detering added 14 commits July 15, 2026 14:52
Mirror the single-gene endpoint (loadExprCallCount / loadExprCallResults):
wrap loadMultispecExprCallCount and loadMultispecExprCallResults with
cacheService.useCacheNonAtomic, keyed by the SimilarityExpressionCallFilter
(count) and by (filter, offset, limit) (result page).
loadDataCount() and stream() previously each rebuilt the entire result set
from the data source, and loadData() re-scanned from the start on every page,
so a request asking for both count and results scanned twice.

Build the complete ordered list once in getAllCalls() and memoize it. Once
populated (by loadDataCount() or stream()), loadData() serves pages from the
cached list. When only results are requested, loadData() keeps the lazy
early-exit path and does not build the full set, so peak memory is unchanged
for that case. Replaces the count-only cachedDataCount field.
…ss ExpressionCall); correct toString label.
…uality monotonicity, lazy-vs-memoized determinism); skip integration tests when no DB configured; add quiet log4j2 config for integration runs.
…rameter names in endpoint docs (anat_entity_id, cell_type_id).
… CommandData; loader prep no longer runs on warm multispec requests.
…ache, combined vs split count/results, gene-list and limit scaling).
Finish applying the homology-resolution work onto the loader-based
multispec path so non-homologous structures are split or dropped
instead of pooled, and expose the homology taxon scope in the JSON.
…s anat filters.

Mirror single-species CallServiceUtils so discarded organs and their
descendants are removed from homology matching and expression-call loading.
The frontend sends several types in one parameter; the previous length limit rejected that form.
… expression queries.

Load the anatomy ontology once, keep organ and cell-type homology filters independent, skip inverted SUMMARY root expansion, and fetch expression calls once per species.
A later multi-species call whose seeds are already in a cached subgraph reuses that ontology instead of hitting the DAO again.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant