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..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 @@ -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,23 @@ 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())); + } + + private Iterable membersFromUnionGroup(final NormalFormUnionGroup unionGroup, final int groupNumber) { + 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);