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 87c997b1a8c..cc8843f1cd6 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 00000000000..71077893500
--- /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 d56bb344667..65446bf1b4f 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 08e36a23572..a176c3ea06b 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);