Skip to content

Commit

Permalink
Merge pull request #1302 from b2ihealthcare/issue/support-cd-member-i…
Browse files Browse the repository at this point in the history
…nference

feat(classification): resurrect cd member inference
  • Loading branch information
cmark committed Jun 26, 2024
2 parents f049071 + d361821 commit 1abea87
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2019 B2i Healthcare, https://b2ihealthcare.com
* Copyright 2011-2024 B2i Healthcare, https://b2ihealthcare.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -115,4 +115,15 @@ public String toString() {
builder.append("]");
return builder.toString();
}

public ConcreteDomainFragment withGroupNumber(int groupNumber) {
return new ConcreteDomainFragment(
getMemberId(),
getRefSetId(),
groupNumber,
getSerializedValue(),
getTypeId(),
isReleased()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2024 B2i Healthcare, https://b2ihealthcare.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.b2international.snowowl.snomed.reasoner.normalform;

import static com.google.common.base.Preconditions.checkNotNull;

import java.text.MessageFormat;
import java.util.Objects;

import com.b2international.snowowl.snomed.core.domain.refset.DataType;
import com.b2international.snowowl.snomed.datastore.ConcreteDomainFragment;
import com.b2international.snowowl.snomed.datastore.SnomedRefSetUtil;
import com.b2international.snowowl.snomed.datastore.index.taxonomy.ReasonerTaxonomy;

/**
* Wraps concept concrete domain members, used in the normal form generation process.
*/
final class NormalFormConcreteDomainMemberValue implements NormalFormProperty {

private final ConcreteDomainFragment fragment;
private final ReasonerTaxonomy reasonerTaxonomy;

/**
* Creates a new instance from the specified concrete domain member.
*
* @param fragment the concrete domain fragment to wrap (may not be <code>null</code>)
* @param reasonerTaxonomy
*
* @throws NullPointerException if the given concrete domain member is <code>null</code>
*/
public NormalFormConcreteDomainMemberValue(final ConcreteDomainFragment fragment, final ReasonerTaxonomy reasonerTaxonomy) {
this.fragment = checkNotNull(fragment, "fragment");
this.reasonerTaxonomy = checkNotNull(reasonerTaxonomy, "reasonerTaxonomy");
}

public ConcreteDomainFragment getFragment() {
return fragment;
}

public String getSerializedValue() {
return fragment.getSerializedValue();
}

public long getTypeId() {
return fragment.getTypeId();
}

public long getRefSetId() {
return fragment.getRefSetId();
}

public String getMemberId() {
return fragment.getMemberId();
}

public boolean isReleased() {
return fragment.isReleased();
}

@Override
public boolean isSameOrStrongerThan(final NormalFormProperty property) {
if (this == property) { return true; }
if (!(property instanceof NormalFormConcreteDomainMemberValue)) { return false; }

final NormalFormConcreteDomainMemberValue other = (NormalFormConcreteDomainMemberValue) property;

// Check type SCTID subsumption, data type (reference set SCTID) and value equality
return true
&& getRefSetId() == other.getRefSetId()
&& closureContains(getTypeId(), other.getTypeId())
&& getSerializedValue().equals(other.getSerializedValue());
}

private boolean ancestorsContains(final long conceptId1, final long conceptId2) {
return reasonerTaxonomy.getInferredAncestors().getDestinations(conceptId1, false).contains(conceptId2);
}

private boolean closureContains(final long conceptId1, final long conceptId2) {
return (conceptId1 == conceptId2) || ancestorsContains(conceptId1, conceptId2);
}

@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (!(obj instanceof NormalFormConcreteDomainMemberValue)) { return false; }

final NormalFormConcreteDomainMemberValue other = (NormalFormConcreteDomainMemberValue) obj;

if (getRefSetId() != other.getRefSetId()) { return false; }
if (getTypeId() != other.getTypeId()) { return false; }
if (!getSerializedValue().equals(other.getSerializedValue())) { return false; }

return true;
}

@Override
public int hashCode() {
return Objects.hash(getSerializedValue(), getRefSetId(), getTypeId());
}

@Override
public String toString() {
final String refSetId = Long.toString(getRefSetId());
final DataType dataType = SnomedRefSetUtil.getDataType(refSetId);
return MessageFormat.format("{0,number,#} : {1} [{2}]", getTypeId(), getSerializedValue(), dataType);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,7 @@
import com.b2international.snowowl.snomed.reasoner.diff.relationship.StatementFragmentOrdering;
import com.google.common.base.Predicates;
import com.google.common.base.Stopwatch;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.collect.*;

/**
* Transforms a subsumption hierarchy and a set of non-ISA relationships into
Expand All @@ -77,6 +72,7 @@ public final class NormalFormGenerator implements INormalFormGenerator {
private final LongKeyMap<Collection<StatementFragment>> statementCache = PrimitiveMaps.newLongKeyOpenHashMap();
private final LongKeyMap<Collection<ConcreteDomainFragment>> concreteDomainCache = PrimitiveMaps.newLongKeyOpenHashMap();
private final Map<Long, NodeGraph> transitiveNodeGraphs = newHashMap();
private final boolean inferConcreteDomainRefsetMembers;

/**
* Creates a new distribution normal form generator instance.
Expand All @@ -85,8 +81,9 @@ public final class NormalFormGenerator implements INormalFormGenerator {
* the reasoner, as well as the pre-classification
* contents of the branch (may not be {@code null})
*/
public NormalFormGenerator(final ReasonerTaxonomy reasonerTaxonomy) {
public NormalFormGenerator(final ReasonerTaxonomy reasonerTaxonomy, final boolean inferConcreteDomainRefsetMembers) {
this.reasonerTaxonomy = reasonerTaxonomy;
this.inferConcreteDomainRefsetMembers = inferConcreteDomainRefsetMembers;
}

@Override
Expand Down Expand Up @@ -402,8 +399,13 @@ private Iterable<NormalFormUnionGroup> toZeroUnionGroups(
}

for (final ConcreteDomainFragment unionGroupMember : unionGroupMembers) {
final NormalFormValue normalFormValue = new NormalFormValue(unionGroupMember, reasonerTaxonomy);
zeroUnionGroups.add(new NormalFormUnionGroup(normalFormValue));
if (inferConcreteDomainRefsetMembers) {
final NormalFormConcreteDomainMemberValue normalFormValue = new NormalFormConcreteDomainMemberValue(unionGroupMember, reasonerTaxonomy);
zeroUnionGroups.add(new NormalFormUnionGroup(normalFormValue));
} else {
final NormalFormValue normalFormValue = new NormalFormValue(unionGroupMember, reasonerTaxonomy);
zeroUnionGroups.add(new NormalFormUnionGroup(normalFormValue));
}
}

return zeroUnionGroups.build();
Expand Down Expand Up @@ -531,11 +533,23 @@ private Iterable<StatementFragment> relationshipsFromUnionGroup(final NormalForm
});
}

@Deprecated
private Iterable<ConcreteDomainFragment> membersFromGroupSet(final NormalFormGroupSet targetGroupSet) {
// We will consume CD member fragments, but no longer suggest to create new ones.
return List.of();
// We will consume CD member fragments, but no longer suggest to create new ones, unless explicitly requested via inferConcreteDomainRefsetMembers option
return inferConcreteDomainRefsetMembers ? FluentIterable.from(targetGroupSet).transformAndConcat(this::membersFromGroup) : List.of();
}

private Iterable<ConcreteDomainFragment> membersFromGroup(final NormalFormGroup group) {
return FluentIterable
.from(group.getUnionGroups())
.transformAndConcat(unionGroup -> membersFromUnionGroup(unionGroup, group.getGroupNumber()));
}

private Iterable<ConcreteDomainFragment> membersFromUnionGroup(final NormalFormUnionGroup unionGroup, final int groupNumber) {
return FluentIterable
.from(unionGroup.getProperties())
.filter(NormalFormConcreteDomainMemberValue.class)
.transform(property -> property.getFragment().withGroupNumber(groupNumber));
}

private Collection<StatementFragment> getTargetRelationships(final long conceptId) {
final Iterable<StatementFragment> targetIsARelationships = getTargetIsARelationships(conceptId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private void executeClassification(final BranchContext context,
final DelegateOntology ontology = (DelegateOntology) ontologyManager.createOntology(ontologyIRI);
final ReasonerTaxonomyInferrer inferrer = new ReasonerTaxonomyInferrer(reasonerId, ontology, context);
final ReasonerTaxonomy inferredTaxonomy = inferrer.addInferences(taxonomy);
final NormalFormGenerator normalFormGenerator = new NormalFormGenerator(inferredTaxonomy);
final NormalFormGenerator normalFormGenerator = new NormalFormGenerator(inferredTaxonomy, concreteDomainSupported);

tracker.classificationCompleted(classificationId, inferredTaxonomy, normalFormGenerator);

Expand Down

0 comments on commit 1abea87

Please sign in to comment.