From 21f54923081b82ede075bda04cfbf0a361a8d1c0 Mon Sep 17 00:00:00 2001 From: Mark Czotter Date: Wed, 26 Jun 2024 12:36:33 +0200 Subject: [PATCH 1/2] feat(classification): resurrect cd member inference Older systems with member based concrete domain support (via the concreteDomainSupport configuration setting) should be able to keep their inferred concrete data values in cd members (as opposed to moving them to the new relationship value model which has limited support for data types). --- .../datastore/ConcreteDomainFragment.java | 13 +- .../NormalFormConcreteDomainMemberValue.java | 121 ++++++++++++++++++ .../normalform/NormalFormGenerator.java | 40 ++++-- .../request/ClassificationJobRequest.java | 2 +- 4 files changed, 162 insertions(+), 14 deletions(-) create mode 100644 snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormConcreteDomainMemberValue.java diff --git a/snomed/com.b2international.snowowl.snomed.datastore/src/com/b2international/snowowl/snomed/datastore/ConcreteDomainFragment.java b/snomed/com.b2international.snowowl.snomed.datastore/src/com/b2international/snowowl/snomed/datastore/ConcreteDomainFragment.java index 87c997b1a8..cc8843f1cd 100644 --- a/snomed/com.b2international.snowowl.snomed.datastore/src/com/b2international/snowowl/snomed/datastore/ConcreteDomainFragment.java +++ b/snomed/com.b2international.snowowl.snomed.datastore/src/com/b2international/snowowl/snomed/datastore/ConcreteDomainFragment.java @@ -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. @@ -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() + ); + } } diff --git a/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormConcreteDomainMemberValue.java b/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormConcreteDomainMemberValue.java new file mode 100644 index 0000000000..7107789350 --- /dev/null +++ b/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormConcreteDomainMemberValue.java @@ -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 null) + * @param reasonerTaxonomy + * + * @throws NullPointerException if the given concrete domain member is null + */ + 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); + } + +} \ No newline at end of file diff --git a/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormGenerator.java b/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormGenerator.java index d56bb34466..6d8bd79043 100644 --- a/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormGenerator.java +++ b/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormGenerator.java @@ -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 @@ -77,6 +72,7 @@ public final class NormalFormGenerator implements INormalFormGenerator { private final LongKeyMap> statementCache = PrimitiveMaps.newLongKeyOpenHashMap(); private final LongKeyMap> concreteDomainCache = PrimitiveMaps.newLongKeyOpenHashMap(); private final Map transitiveNodeGraphs = newHashMap(); + private final boolean inferConcreteDomainRefsetMembers; /** * Creates a new distribution normal form generator instance. @@ -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 @@ -402,8 +399,13 @@ private Iterable 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(); @@ -531,11 +533,25 @@ private Iterable relationshipsFromUnionGroup(final NormalForm }); } - @Deprecated private Iterable 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 membersFromGroup(final NormalFormGroup group) { + return FluentIterable + .from(group.getUnionGroups()) + .transformAndConcat(unionGroup -> membersFromUnionGroup(unionGroup, + group.getGroupNumber(), + unionGroup.getUnionGroupNumber())); + } + + private Iterable membersFromUnionGroup(final NormalFormUnionGroup unionGroup, final int groupNumber, final int unionGroupNumber) { + return FluentIterable + .from(unionGroup.getProperties()) + .filter(NormalFormConcreteDomainMemberValue.class) + .transform(property -> property.getFragment().withGroupNumber(groupNumber)); + } private Collection getTargetRelationships(final long conceptId) { final Iterable targetIsARelationships = getTargetIsARelationships(conceptId); diff --git a/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/request/ClassificationJobRequest.java b/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/request/ClassificationJobRequest.java index 08e36a2357..a176c3ea06 100644 --- a/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/request/ClassificationJobRequest.java +++ b/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/request/ClassificationJobRequest.java @@ -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); From d36182150b49cb0cc7b6ec12301361c6bedff7cf Mon Sep 17 00:00:00 2001 From: Mark Czotter Date: Wed, 26 Jun 2024 13:06:58 +0200 Subject: [PATCH 2/2] chore(classification): remove unnecesary unused unionGroupNumber arg --- .../snomed/reasoner/normalform/NormalFormGenerator.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormGenerator.java b/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormGenerator.java index 6d8bd79043..65446bf1b4 100644 --- a/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormGenerator.java +++ b/snomed/com.b2international.snowowl.snomed.reasoner/src/com/b2international/snowowl/snomed/reasoner/normalform/NormalFormGenerator.java @@ -541,12 +541,10 @@ private Iterable membersFromGroupSet(final NormalFormGro private Iterable membersFromGroup(final NormalFormGroup group) { return FluentIterable .from(group.getUnionGroups()) - .transformAndConcat(unionGroup -> membersFromUnionGroup(unionGroup, - group.getGroupNumber(), - unionGroup.getUnionGroupNumber())); + .transformAndConcat(unionGroup -> membersFromUnionGroup(unionGroup, group.getGroupNumber())); } - private Iterable membersFromUnionGroup(final NormalFormUnionGroup unionGroup, final int groupNumber, final int unionGroupNumber) { + private Iterable membersFromUnionGroup(final NormalFormUnionGroup unionGroup, final int groupNumber) { return FluentIterable .from(unionGroup.getProperties()) .filter(NormalFormConcreteDomainMemberValue.class)