All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.ikm.tinkar.reasoner.elkowl.ElkOwlDataBuilder Maven / Gradle / Ivy

/*
 * Copyright © 2015 Integrated Knowledge Management ([email protected])
 *
 * 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 dev.ikm.tinkar.reasoner.elkowl;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.impl.factory.primitive.IntLists;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
import org.semanticweb.owlapi.model.OWLObjectIntersectionOf;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
import org.semanticweb.owlapi.model.OWLSubPropertyChainOfAxiom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dev.ikm.elk.snomed.owl.SnomedOwlOntology;
import dev.ikm.tinkar.common.alert.AlertStreams;
import dev.ikm.tinkar.common.id.IntIdList;
import dev.ikm.tinkar.common.service.PrimitiveData;
import dev.ikm.tinkar.common.service.TrackingCallable;
import dev.ikm.tinkar.common.sets.ConcurrentHashSet;
import dev.ikm.tinkar.coordinate.logic.LogicCoordinateRecord;
import dev.ikm.tinkar.coordinate.view.calculator.ViewCalculator;
import dev.ikm.tinkar.entity.graph.DiTreeEntity;
import dev.ikm.tinkar.entity.graph.EntityVertex;
import dev.ikm.tinkar.entity.graph.adaptor.axiom.LogicalAxiomSemantic;
import dev.ikm.tinkar.terms.ConceptFacade;
import dev.ikm.tinkar.terms.PatternFacade;
import dev.ikm.tinkar.terms.TinkarTerm;

public class ElkOwlDataBuilder {

	private static final Logger LOG = LoggerFactory.getLogger(ElkOwlDataBuilder.class);

	private final ViewCalculator viewCalculator;

	private final PatternFacade statedAxiomPattern;

	private final ElkOwlData axiomData;

	private OWLDataFactory owlDataFactory;

	private TrackingCallable progressUpdater = null;

	public ElkOwlDataBuilder(ViewCalculator viewCalculator, PatternFacade statedAxiomPattern, ElkOwlData axiomData,
			OWLDataFactory owlDataFactory) {
		super();
		this.viewCalculator = viewCalculator;
		this.statedAxiomPattern = statedAxiomPattern;
		this.axiomData = axiomData;
		this.owlDataFactory = owlDataFactory;
	}

	public void setProgressUpdater(TrackingCallable progressUpdater) {
		this.progressUpdater = progressUpdater;
	}

	private void updateProgress(int count, int total) {
		if (progressUpdater != null)
			progressUpdater.updateProgress(count, total);
	}

	private void alert(Exception ex) {
		try {
			AlertStreams.dispatchToRoot(ex);
		} catch (Exception e) {
			LOG.error("AlertStreams.dispatchToRoot failed");
			LOG.error("Alert ex", ex.getMessage());
		}
	}

	public static class IncrementalChanges {

		private ImmutableList additions;

		private ImmutableList deletions;

		public IncrementalChanges(ImmutableList additions, ImmutableList deletions) {
			super();
			this.additions = additions;
			this.deletions = deletions;
		}

		public ImmutableList getAdditions() {
			return additions;
		}

		public ImmutableList getDeletions() {
			return deletions;
		}

	}

	public void build() throws Exception {
//		AtomicInteger processedSemanticsCounter = axiomData.processedSemantics;
		AtomicInteger totalCounter = new AtomicInteger();
		PrimitiveData.get().forEachSemanticNidOfPattern(statedAxiomPattern.nid(), i -> totalCounter.incrementAndGet());
		final int totalCount = totalCounter.get();
		LOG.info("Total axioms: " + totalCount);
		updateProgress(0, totalCount);
		LogicCoordinateRecord logicCoordinate = viewCalculator.logicCalculator().logicCoordinateRecord();
		axiomData.processedSemantics.set(0);
		// TODO get a native concurrent collector for roaring
		// https://stackoverflow.com/questions/29916881/how-to-implement-a-thread-safe-collector
		ConcurrentHashSet includedConceptNids = new ConcurrentHashSet<>(totalCount);
		AtomicInteger ex_cnt = new AtomicInteger();
		viewCalculator.forEachSemanticVersionOfPatternParallel(logicCoordinate.statedAxiomsPatternNid(),
				(semanticEntityVersion, patternEntityVersion) -> {
					try {
						int conceptNid = semanticEntityVersion.referencedComponentNid();
						// TODO: In some cases, may wish to classify axioms from inactive concepts. Put
						// in logic coordinate?
						if (viewCalculator.latestIsActive(conceptNid)) {
							// For now, only classify active until we get snomed data issues worked out
							includedConceptNids.add(conceptNid);
//						OWLClass concept = getConcept(conceptNid);
							DiTreeEntity definition = (DiTreeEntity) semanticEntityVersion.fieldValues().get(0);
							ImmutableList axiomsForDefinition = processDefinition(definition, conceptNid);
//						LOG.info(axiomsForDefinition.size() + " " + axiomsForDefinition);
							if (axiomData.nidAxiomsMap.compareAndSet(conceptNid, null, axiomsForDefinition)) {
								axiomData.axiomsSet.addAll(axiomsForDefinition.castToList());
							} else {
								alert(new IllegalStateException("Definition for " + conceptNid + " already exists"));
							}
							axiomData.incrementActiveConceptCount();
						} else {
							axiomData.incrementInactiveConceptCount();
						}
						int processedCount = axiomData.processedSemantics.incrementAndGet();
						if (processedCount % 100 == 0) {
							updateProgress(processedCount, totalCount);
						}
//					if (axiomCounter.get() < 5) {
//						LOG.info("Axiom: \n" + semanticEntityVersion);
//					}
					} catch (Exception ex) {
						if (ex_cnt.incrementAndGet() < 10) {
							LOG.error(ex.getMessage());
							LOG.error("", ex);
						}
					}
				});
		int[] includedConceptNidArray = includedConceptNids.stream().mapToInt(boxedInt -> (int) boxedInt).toArray();
		Arrays.sort(includedConceptNidArray);
		axiomData.classificationConceptSet = IntLists.immutable.of(includedConceptNidArray);
		for (OWLClass con : axiomData.nidConceptMap.values()) {
			int nid = (int) SnomedOwlOntology.getId(con);
			if (axiomData.nidAxiomsMap.get(nid) == null)
				LOG.warn("No axioms for: " + nid + " " + PrimitiveData.text(nid));
		}
		updateProgress(totalCount, totalCount);
//		updateMessage("Extract in " + durationString());
//		LOG.info("Extract in " + durationString());
		LOG.info("Total axioms: " + totalCount + " " + axiomData.processedSemantics.get() + " "
				+ axiomData.axiomsSet.size());
		LOG.info("Active concepts: " + axiomData.getActiveConceptCount());
		LOG.info("Inactive concepts: " + axiomData.getInactiveConceptCount());
		if (ex_cnt.get() != 0) {
			String msg = "Exceptions: " + ex_cnt.get();
			LOG.error(msg);
			throw new Exception(msg);
		}
	}

	public IncrementalChanges processIncremental(DiTreeEntity definition, int conceptNid) {
		ImmutableList additions = processDefinition(definition, conceptNid);
		ImmutableList deletions = axiomData.nidAxiomsMap.get(conceptNid);
		if (deletions == null)
			throw new RuntimeException(conceptNid + " " + PrimitiveData.text(conceptNid));
		axiomData.nidAxiomsMap.put(conceptNid, additions);
		deletions.forEach(axiomData.axiomsSet::remove);
//		axiomData.axiomsSet.removeAll(deletions.castToList());
		// TODO update active concept count etc. ??
		return new IncrementalChanges(additions, deletions);
	}

	private ImmutableList processDefinition(DiTreeEntity definition, int conceptNid) {
		return processRoot(definition.root(), conceptNid, definition, Lists.mutable.empty());
	}

	private ImmutableList processRoot(EntityVertex rootVertex, int conceptNid, DiTreeEntity definition,
			MutableList axioms) throws IllegalStateException {
		for (final EntityVertex childVertex : definition.successors(rootVertex)) {
//			if (PrimitiveData.text(conceptNid).equals("Transitive Feature"))
//				LOG.info("Transitive: " + PrimitiveData.text(conceptNid) + " " + childVertex);
			switch (LogicalAxiomSemantic.get(childVertex.getMeaningNid())) {
			case SUFFICIENT_SET -> {
				processSufficientSet(childVertex, conceptNid, definition, axioms);
			}
			case NECESSARY_SET -> {
				processNecessarySet(childVertex, conceptNid, definition, axioms);
			}
			case INCLUSION_SET -> {
				processInclusionSet(childVertex, conceptNid, definition, axioms);
			}
			case PROPERTY_SET -> {
				processPropertySet(childVertex, conceptNid, definition, axioms);
			}
			default ->
				throw new IllegalStateException("Unexpected value: " + PrimitiveData.text(childVertex.getMeaningNid()));
			}
		}
		return axioms.toImmutable();
	}

	private void processNecessarySet(EntityVertex sufficientSetVertex, int conceptNid, DiTreeEntity definition,
			MutableList axioms) {
		final ImmutableList childVertexList = definition.successors(sufficientSetVertex);
		if (childVertexList.size() == 1) {
			final Optional conjunctionConcept = generateAxioms(childVertexList.get(0), conceptNid,
					definition, axioms);
			if (conjunctionConcept.isPresent()) {
				OWLSubClassOfAxiom axiom = owlDataFactory.getOWLSubClassOfAxiom(axiomData.getConcept(conceptNid),
						conjunctionConcept.get());
				axioms.add(axiom);
			} else {
				throw new IllegalStateException("Child node must return a conjunction concept. Concept: " + conceptNid
						+ " definition: " + definition);
			}
		} else {
			throw new IllegalStateException("Necessary sets require a single AND child... " + childVertexList);
		}
	}

	private void processSufficientSet(EntityVertex necessarySetVertex, int conceptNid, DiTreeEntity definition,
			MutableList axioms) {
		final ImmutableList childVertexList = definition.successors(necessarySetVertex);
		if (childVertexList.size() == 1) {
			final Optional conjunctionConcept = generateAxioms(childVertexList.get(0), conceptNid,
					definition, axioms);
			if (conjunctionConcept.isPresent()) {
				OWLEquivalentClassesAxiom axiom = owlDataFactory
						.getOWLEquivalentClassesAxiom(axiomData.getConcept(conceptNid), conjunctionConcept.get());
				axioms.add(axiom);
			} else {
				throw new IllegalStateException("Child node must return a conjunction concept. Concept: " + conceptNid
						+ " definition: " + definition);
			}
		} else {
			throw new IllegalStateException("Sufficient sets require a single AND child... " + childVertexList);
		}
	}

	private void processInclusionSet(EntityVertex inclusionSetVertex, int conceptNid, DiTreeEntity definition,
			MutableList axioms) {
		final ImmutableList childVertexList = definition.successors(inclusionSetVertex);
		if (childVertexList.size() == 1) {
			final Optional conjunctionConcept = generateAxioms(childVertexList.get(0), conceptNid,
					definition, axioms);
			if (conjunctionConcept.isPresent()) {
				OWLSubClassOfAxiom axiom = owlDataFactory.getOWLSubClassOfAxiom(conjunctionConcept.get(),
						axiomData.getConcept(conceptNid));
				axioms.add(axiom);
//				LOG.info("Inclusion set: " + PrimitiveData.text(conceptNid) + "\n" + definition + "\n" + axioms);
			} else {
				throw new IllegalStateException("Child node must return a conjunction concept. Concept: " + conceptNid
						+ " definition: " + definition);
			}
		} else {
			throw new IllegalStateException("Inclusion sets require a single AND child... " + childVertexList);
		}
	}

	/**
	 * Generate axioms.
	 *
	 * @param logicVertex the logic node
	 * @param conceptNid  the concept nid
	 * @param definition  the logical definition
	 * @return the optional
	 */
	private Optional generateAxioms(EntityVertex logicVertex, int conceptNid,
			DiTreeEntity definition, MutableList axioms) {
		switch (LogicalAxiomSemantic.get(logicVertex.getMeaningNid())) {
		case AND:
			return processAnd(logicVertex, conceptNid, definition, axioms);

		case CONCEPT:
			final ConceptFacade concept = logicVertex.propertyFast(TinkarTerm.CONCEPT_REFERENCE);
//			if (PrimitiveData.text(concept.nid()).equals("Transitive Feature"))
//				LOG.info("Transitive parent: " + PrimitiveData.text(conceptNid) + " " + definition);
			return Optional.of(axiomData.getConcept(concept.nid()));

		case DEFINITION_ROOT:
			processRoot(logicVertex, conceptNid, definition, axioms);
			break;

		case DISJOINT_WITH:
			throw new UnsupportedOperationException("Not supported");

		case FEATURE:
			return processFeatureNode(logicVertex, conceptNid, definition, axioms);

		case PROPERTY_SET:
			processPropertySet(logicVertex, conceptNid, definition, axioms);
			break;

		case OR:
			throw new UnsupportedOperationException("Not supported");

		case ROLE:
			ConceptFacade roleOperator = logicVertex.propertyFast(TinkarTerm.ROLE_OPERATOR);
			if (roleOperator.nid() == TinkarTerm.EXISTENTIAL_RESTRICTION.nid()) {
				return processRoleNodeSome(logicVertex, conceptNid, definition, axioms);
			} else {
				throw new UnsupportedOperationException(
						"Role: " + PrimitiveData.text(roleOperator.nid()) + " not supported. ");
			}

//		case LITERAL_BOOLEAN:
//		case LITERAL_FLOAT:
//		case LITERAL_INSTANT:
//		case LITERAL_INTEGER:
//		case LITERAL_STRING:
//			throw new UnsupportedOperationException("Expected concept logicNode, found literal logicNode: "
//					+ logicVertex + " Concept: " + conceptNid + " definition: " + definition);

		case SUFFICIENT_SET:
		case NECESSARY_SET:
		case INCLUSION_SET:
			throw new UnsupportedOperationException("Not expected here: " + logicVertex);
		case PROPERTY_PATTERN_IMPLICATION:
			throw new UnsupportedOperationException();
		}

		return Optional.empty();
	}

	private void processPropertySet(EntityVertex propertySetNode, int conceptNid, DiTreeEntity definition,
			MutableList axioms) {
		final ImmutableList children = definition.successors(propertySetNode);
		if (children.size() != 1) {
			throw new IllegalStateException(
					"PropertySetNode can only have one child. Concept: " + conceptNid + " definition: " + definition);
		}
		if (!(children.get(0).getMeaningNid() == TinkarTerm.AND.nid())) {
			throw new IllegalStateException("PropertySetNode can only have AND for a child. Concept: " + conceptNid
					+ " definition: " + definition);
		}
		for (EntityVertex node : definition.successors(children.get(0))) {
			switch (LogicalAxiomSemantic.get(node.getMeaningNid())) {
			case CONCEPT:
				final ConceptFacade successorConcept = node.propertyFast(TinkarTerm.CONCEPT_REFERENCE);
				if (PrimitiveData.text(successorConcept.nid()).equals("Transitive Feature")) {
//					LOG.info("Transitive property parent: " + conceptNid + " " + PrimitiveData.text(conceptNid) + "\n"
//							+ definition);
					axioms.add(owlDataFactory.getOWLTransitiveObjectPropertyAxiom(axiomData.getRole(conceptNid)));
				} else if (PrimitiveData.text(successorConcept.nid()).equals("Reflexive Feature")) {
//					LOG.info("Reflexive property parent: " + conceptNid + " " + PrimitiveData.text(conceptNid) + "\n"
//							+ definition);
					axioms.add(owlDataFactory.getOWLReflexiveObjectPropertyAxiom(axiomData.getRole(conceptNid)));
				} else {
					axioms.add(owlDataFactory.getOWLSubObjectPropertyOfAxiom(axiomData.getRole(conceptNid),
							axiomData.getRole(successorConcept.nid())));
				}
				break;
			case PROPERTY_PATTERN_IMPLICATION:
//				LOG.info("PPI: " + PrimitiveData.text(conceptNid) + " " + definition);
				final ConceptFacade pi = node.propertyFast(TinkarTerm.PROPERTY_PATTERN_IMPLICATION);
				final IntIdList ps = node.propertyFast(TinkarTerm.PROPERTY_SET);
				List chain = ps.intStream().mapToObj(x -> axiomData.getRole(x)).toList();
				OWLSubPropertyChainOfAxiom axiom = owlDataFactory.getOWLSubPropertyChainOfAxiom(chain,
						axiomData.getRole(pi.nid()));
				axioms.add(axiom);
				break;
			default:
				throw new UnsupportedOperationException("Can't handle: " + node + " in: " + definition);
			}
		}
	}

	/**
	 * Process and.
	 *
	 * @param andNode    the and node
	 * @param conceptNid the concept nid
	 * @param definition the logical definition
	 * @return the optional
	 */
	private Optional processAnd(EntityVertex andNode, int conceptNid, DiTreeEntity definition,
			MutableList axioms) {
		final ImmutableList childrenLogicNodes = definition.successors(andNode);
		final OWLClassExpression[] conjunctionConcepts = new OWLClassExpression[childrenLogicNodes.size()];
		for (int i = 0; i < childrenLogicNodes.size(); i++) {
			conjunctionConcepts[i] = generateAxioms(childrenLogicNodes.get(i), conceptNid, definition, axioms).get();
		}
		if (conjunctionConcepts.length == 1)
			return Optional.of(conjunctionConcepts[0]);
		OWLObjectIntersectionOf expr = owlDataFactory.getOWLObjectIntersectionOf(conjunctionConcepts);
		return Optional.of(expr);
	}

	/**
	 * Process role node some.
	 *
	 * @param roleNodeSome the role node some
	 * @param conceptNid   the concept nid
	 * @param definition   the logical definition
	 * @return the optional
	 */
	private Optional processRoleNodeSome(EntityVertex roleNodeSome, int conceptNid,
			DiTreeEntity definition, MutableList axioms) {
		ConceptFacade roleType = roleNodeSome.propertyFast(TinkarTerm.ROLE_TYPE);
		final OWLObjectProperty theRole = axiomData.getRole(roleType.nid());
		final ImmutableList children = definition.successors(roleNodeSome);
		if (children.size() != 1) {
			throw new IllegalStateException(
					"RoleNodeSome can only have one child. Concept: " + conceptNid + " definition: " + definition);
		}
		final Optional restrictionConcept = generateAxioms(children.get(0), conceptNid, definition,
				axioms);
		if (restrictionConcept.isPresent()) {
			return Optional.of(owlDataFactory.getOWLObjectSomeValuesFrom(theRole, restrictionConcept.get()));
		}
		throw new UnsupportedOperationException("Child of role node can not return null concept. Concept: " + conceptNid
				+ " definition: " + definition);
	}

	/**
	 * Process feature node.
	 *
	 * @param featureNode the feature node
	 * @param conceptNid  the concept nid
	 * @param definition  the logical definition
	 * @return the optional
	 */
	private Optional processFeatureNode(EntityVertex featureNode, int conceptNid,
			DiTreeEntity definition, MutableList axioms) {
//		EntityFacade featureFacade = featureNode.propertyFast(TinkarTerm.FEATURE);
//		final Feature theFeature = getFeature(featureFacade.nid());
		throw new UnsupportedOperationException();
		/*
		 * final ImmutableList children =
		 * logicGraph.successors(featureNode);
		 * 
		 * if (children.size() != 1) { throw new
		 * IllegalStateException("FeatureNode can only have one child. Concept: " +
		 * conceptNid + " graph: " + logicGraph); }
		 * 
		 * final Optional optionalLiteral = generateLiterals(children[0],
		 * getConcept(conceptNid), logicGraph);
		 * 
		 * if (optionalLiteral.isPresent()) { switch (featureNode.getOperator()) { case
		 * EQUALS: return Optional.of(Factory.createDatatype(theFeature,
		 * Operator.EQUALS, optionalLiteral.get()));
		 * 
		 * case GREATER_THAN: return Optional.of(Factory.createDatatype(theFeature,
		 * Operator.GREATER_THAN, optionalLiteral.get()));
		 * 
		 * case GREATER_THAN_EQUALS: return
		 * Optional.of(Factory.createDatatype(theFeature, Operator.GREATER_THAN_EQUALS,
		 * optionalLiteral.get()));
		 * 
		 * case LESS_THAN: return Optional.of(Factory.createDatatype(theFeature,
		 * Operator.LESS_THAN, optionalLiteral.get()));
		 * 
		 * case LESS_THAN_EQUALS: return Optional.of(Factory.createDatatype(theFeature,
		 * Operator.LESS_THAN_EQUALS, optionalLiteral.get()));
		 * 
		 * default: throw new
		 * UnsupportedOperationException(featureNode.getOperator().toString()); } }
		 * 
		 * throw new
		 * UnsupportedOperationException("Child of FeatureNode node cannot return null concept. Concept: "
		 * + conceptNid + " graph: " + logicGraph);
		 * 
		 */
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy