/* * 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. * * Contributions from 2013-2017 where performed either by US government * employees, or under US Veterans Health Administration contracts. * * US Veterans Health Administration contributions by government employees * are work of the U.S. Government and are not subject to copyright * protection in the United States. Portions contributed by government * employees are USGovWork (17USC ยง105). Not subject to copyright. * * Contribution by contractors to the US Veterans Health Administration * during this period are contractually contributed under the * Apache License, Version 2.0. * * See: https://www.usa.gov/government-works * * Contributions prior to 2013: * * Copyright (C) International Health Terminology Standards Development Organisation. * Licensed under the Apache License, Version 2.0. * */ /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package sh.isaac.provider.logic.csiro.axioms; //~--- JDK imports ------------------------------------------------------------ import java.util.Calendar; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; //~--- non-JDK imports -------------------------------------------------------- //TODO move to CSIRO specific module import au.csiro.ontology.Factory; import au.csiro.ontology.model.Axiom; import au.csiro.ontology.model.Concept; import au.csiro.ontology.model.ConceptInclusion; import au.csiro.ontology.model.Feature; import au.csiro.ontology.model.Literal; import au.csiro.ontology.model.Operator; import au.csiro.ontology.model.Role; import sh.isaac.api.DataSource; import sh.isaac.api.Get; import sh.isaac.api.collections.ConceptSequenceSet; import sh.isaac.api.collections.ConcurrentSequenceObjectMap; import sh.isaac.api.component.sememe.version.LogicGraphSememe; import sh.isaac.api.logic.LogicNode; import sh.isaac.model.logic.LogicalExpressionOchreImpl; import sh.isaac.model.logic.node.AndNode; import sh.isaac.model.logic.node.LiteralNodeBoolean; import sh.isaac.model.logic.node.LiteralNodeFloat; import sh.isaac.model.logic.node.LiteralNodeInstant; import sh.isaac.model.logic.node.LiteralNodeInteger; import sh.isaac.model.logic.node.LiteralNodeString; import sh.isaac.model.logic.node.NecessarySetNode; import sh.isaac.model.logic.node.RootNode; import sh.isaac.model.logic.node.SufficientSetNode; import sh.isaac.model.logic.node.internal.ConceptNodeWithSequences; import sh.isaac.model.logic.node.internal.FeatureNodeWithSequences; import sh.isaac.model.logic.node.internal.RoleNodeSomeWithSequences; //~--- classes ---------------------------------------------------------------- /** * The Class GraphToAxiomTranslator. * * @author kec */ public class GraphToAxiomTranslator { /** The axioms. */ Set<Axiom> axioms = new ConcurrentSkipListSet<>(); /** The sequence logic concept map. */ ConcurrentSequenceObjectMap<Concept> sequenceLogicConceptMap = new ConcurrentSequenceObjectMap<>(); /** The sequence logic role map. */ ConcurrentHashMap<Integer, Role> sequenceLogicRoleMap = new ConcurrentHashMap<>(); /** The sequence logic feature map. */ ConcurrentHashMap<Integer, Feature> sequenceLogicFeatureMap = new ConcurrentHashMap<>(); /** The loaded concepts. */ ConcurrentSkipListSet<Integer> loadedConcepts = new ConcurrentSkipListSet<>(); /** The f. */ Factory f = new Factory(); //~--- methods ------------------------------------------------------------- /** * Clear. */ public void clear() { this.axioms.clear(); this.sequenceLogicRoleMap.clear(); this.sequenceLogicFeatureMap.clear(); this.sequenceLogicConceptMap.clear(); this.loadedConcepts.clear(); } /** * Translates the logicGraphSememe into a set of axioms, and adds those axioms * to the internal set of axioms. * * @param logicGraphSememe the logic graph sememe */ public void convertToAxiomsAndAdd(LogicGraphSememe logicGraphSememe) { this.loadedConcepts.add(logicGraphSememe.getReferencedComponentNid()); final LogicalExpressionOchreImpl logicGraph = new LogicalExpressionOchreImpl(logicGraphSememe.getGraphData(), DataSource.INTERNAL); generateAxioms(logicGraph.getRoot(), logicGraphSememe.getReferencedComponentNid(), logicGraph); } /** * To string. * * @return the string */ @Override public String toString() { return "GraphToAxiomTranslator{" + "axioms=" + this.axioms.size() + ", sequenceLogicConceptMap=" + this.sequenceLogicConceptMap.getSequences().count() + ", sequenceLogicRoleMap=" + this.sequenceLogicRoleMap.size() + ", sequenceLogicFeatureMap=" + this.sequenceLogicFeatureMap.size() + '}'; } /** * Generate axioms. * * @param logicNode the logic node * @param conceptNid the concept nid * @param logicGraph the logic graph * @return the optional */ private Optional<Concept> generateAxioms(LogicNode logicNode, int conceptNid, LogicalExpressionOchreImpl logicGraph) { switch (logicNode.getNodeSemantic()) { case AND: return processAnd((AndNode) logicNode, conceptNid, logicGraph); case CONCEPT: final ConceptNodeWithSequences conceptNode = (ConceptNodeWithSequences) logicNode; return Optional.of(getConcept(conceptNode.getConceptSequence())); case DEFINITION_ROOT: processRoot(logicNode, conceptNid, logicGraph); break; case DISJOINT_WITH: throw new UnsupportedOperationException("Not supported by SnoRocket/EL++."); case FEATURE: return processFeatureNode((FeatureNodeWithSequences) logicNode, conceptNid, logicGraph); case NECESSARY_SET: processNecessarySet((NecessarySetNode) logicNode, conceptNid, logicGraph); break; case OR: throw new UnsupportedOperationException("Not supported by SnoRocket/EL++."); case ROLE_ALL: throw new UnsupportedOperationException("Not supported by SnoRocket/EL++."); case ROLE_SOME: return processRoleNodeSome((RoleNodeSomeWithSequences) logicNode, conceptNid, logicGraph); case SUBSTITUTION_BOOLEAN: throw new UnsupportedOperationException("Supported, but not yet implemented."); case SUBSTITUTION_CONCEPT: throw new UnsupportedOperationException("Supported, but not yet implemented."); case SUBSTITUTION_FLOAT: throw new UnsupportedOperationException("Supported, but not yet implemented."); case SUBSTITUTION_INSTANT: throw new UnsupportedOperationException("Supported, but not yet implemented."); case SUBSTITUTION_INTEGER: throw new UnsupportedOperationException("Supported, but not yet implemented."); case SUBSTITUTION_STRING: throw new UnsupportedOperationException("Supported, but not yet implemented."); case SUFFICIENT_SET: processSufficientSet((SufficientSetNode) logicNode, conceptNid, logicGraph); break; case TEMPLATE: throw new UnsupportedOperationException("Supported, but not yet implemented."); case LITERAL_BOOLEAN: case LITERAL_FLOAT: case LITERAL_INSTANT: case LITERAL_INTEGER: case LITERAL_STRING: throw new UnsupportedOperationException("Expected concept logicNode, found literal logicNode: " + logicNode + " Concept: " + conceptNid + " graph: " + logicGraph); default: throw new UnsupportedOperationException("Can't handle: " + logicNode.getNodeSemantic()); } return Optional.empty(); } /** * Generate literals. * * @param logicNode the logic node * @param c the c * @param logicGraph the logic graph * @return the optional */ private Optional<Literal> generateLiterals(LogicNode logicNode, Concept c, LogicalExpressionOchreImpl logicGraph) { switch (logicNode.getNodeSemantic()) { case LITERAL_BOOLEAN: final LiteralNodeBoolean literalNodeBoolean = (LiteralNodeBoolean) logicNode; return Optional.of(Factory.createBooleanLiteral(literalNodeBoolean.getLiteralValue())); case LITERAL_FLOAT: final LiteralNodeFloat literalNodeFloat = (LiteralNodeFloat) logicNode; return Optional.of(Factory.createFloatLiteral(literalNodeFloat.getLiteralValue())); case LITERAL_INSTANT: final LiteralNodeInstant literalNodeInstant = (LiteralNodeInstant) logicNode; final Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(literalNodeInstant.getLiteralValue() .toEpochMilli()); return Optional.of(Factory.createDateLiteral(calendar)); case LITERAL_INTEGER: final LiteralNodeInteger literalNodeInteger = (LiteralNodeInteger) logicNode; return Optional.of(Factory.createIntegerLiteral(literalNodeInteger.getLiteralValue())); case LITERAL_STRING: final LiteralNodeString literalNodeString = (LiteralNodeString) logicNode; return Optional.of(Factory.createStringLiteral(literalNodeString.getLiteralValue())); default: throw new UnsupportedOperationException("Expected literal logicNode, found: " + logicNode + " Concept: " + c + " graph: " + logicGraph); } } /** * Process and. * * @param andNode the and node * @param conceptNid the concept nid * @param logicGraph the logic graph * @return the optional */ private Optional<Concept> processAnd(AndNode andNode, int conceptNid, LogicalExpressionOchreImpl logicGraph) { final LogicNode[] childrenLogicNodes = andNode.getChildren(); final Concept[] conjunctionConcepts = new Concept[childrenLogicNodes.length]; for (int i = 0; i < childrenLogicNodes.length; i++) { conjunctionConcepts[i] = generateAxioms(childrenLogicNodes[i], conceptNid, logicGraph).get(); } return Optional.of(Factory.createConjunction(conjunctionConcepts)); } /** * Process feature node. * * @param featureNode the feature node * @param conceptNid the concept nid * @param logicGraph the logic graph * @return the optional */ private Optional<Concept> processFeatureNode(FeatureNodeWithSequences featureNode, int conceptNid, LogicalExpressionOchreImpl logicGraph) { final Feature theFeature = getFeature(featureNode.getTypeConceptSequence()); final LogicNode[] children = featureNode.getChildren(); if (children.length != 1) { throw new IllegalStateException("FeatureNode can only have one child. Concept: " + conceptNid + " graph: " + logicGraph); } final Optional<Literal> 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); } /** * Process necessary set. * * @param necessarySetNode the necessary set node * @param conceptNid the concept nid * @param logicGraph the logic graph */ private void processNecessarySet(NecessarySetNode necessarySetNode, int conceptNid, LogicalExpressionOchreImpl logicGraph) { final LogicNode[] children = necessarySetNode.getChildren(); if (children.length != 1) { throw new IllegalStateException("necessarySetNode can only have one child. Concept: " + conceptNid + " graph: " + logicGraph); } if (!(children[0] instanceof AndNode)) { throw new IllegalStateException("necessarySetNode can only have AND for a child. Concept: " + conceptNid + " graph: " + logicGraph); } final Optional<Concept> conjunctionConcept = generateAxioms(children[0], conceptNid, logicGraph); if (conjunctionConcept.isPresent()) { this.axioms.add(new ConceptInclusion(getConcept(conceptNid), conjunctionConcept.get())); } else { throw new IllegalStateException("Child node must return a conjunction concept. Concept: " + conceptNid + " graph: " + logicGraph); } } /** * Process role node some. * * @param roleNodeSome the role node some * @param conceptNid the concept nid * @param logicGraph the logic graph * @return the optional */ private Optional<Concept> processRoleNodeSome(RoleNodeSomeWithSequences roleNodeSome, int conceptNid, LogicalExpressionOchreImpl logicGraph) { final Role theRole = getRole(roleNodeSome.getTypeConceptSequence()); final LogicNode[] children = roleNodeSome.getChildren(); if (children.length != 1) { throw new IllegalStateException("RoleNodeSome can only have one child. Concept: " + conceptNid + " graph: " + logicGraph); } final Optional<Concept> restrictionConcept = generateAxioms(children[0], conceptNid, logicGraph); if (restrictionConcept.isPresent()) { return Optional.of(Factory.createExistential(theRole, restrictionConcept.get())); } throw new UnsupportedOperationException("Child of role node can not return null concept. Concept: " + conceptNid + " graph: " + logicGraph); } /** * Process root. * * @param logicNode the logic node * @param conceptNid the concept nid * @param logicGraph the logic graph * @throws IllegalStateException the illegal state exception */ private void processRoot(LogicNode logicNode, int conceptNid, LogicalExpressionOchreImpl logicGraph) throws IllegalStateException { final RootNode rootNode = (RootNode) logicNode; for (final LogicNode child: rootNode.getChildren()) { final Optional<Concept> axiom = generateAxioms(child, conceptNid, logicGraph); if (axiom.isPresent()) { throw new IllegalStateException("Children of root logicNode should not return axioms. Concept: " + conceptNid + " graph: " + logicGraph); } } } /** * Process sufficient set. * * @param sufficientSetNode the sufficient set node * @param conceptNid the concept nid * @param logicGraph the logic graph */ private void processSufficientSet(SufficientSetNode sufficientSetNode, int conceptNid, LogicalExpressionOchreImpl logicGraph) { final LogicNode[] children = sufficientSetNode.getChildren(); if (children.length != 1) { throw new IllegalStateException("SufficientSetNode can only have one child. Concept: " + conceptNid + " graph: " + logicGraph); } if (!(children[0] instanceof AndNode)) { throw new IllegalStateException("SufficientSetNode can only have AND for a child. Concept: " + conceptNid + " graph: " + logicGraph); } final Optional<Concept> conjunctionConcept = generateAxioms(children[0], conceptNid, logicGraph); if (conjunctionConcept.isPresent()) { this.axioms.add(new ConceptInclusion(getConcept(conceptNid), conjunctionConcept.get())); this.axioms.add(new ConceptInclusion(conjunctionConcept.get(), getConcept(conceptNid))); } else { throw new IllegalStateException("Child node must return a conjunction concept. Concept: " + conceptNid + " graph: " + logicGraph); } } //~--- get methods --------------------------------------------------------- /** * Gets the axioms. * * @return the axioms */ public Set<Axiom> getAxioms() { return this.axioms; } /** * Gets the concept. * * @param name the name * @return the concept */ private Concept getConcept(int name) { if (name < 0) { name = Get.identifierService() .getConceptSequence(name); } final Optional<Concept> optionalConcept = this.sequenceLogicConceptMap.get(name); if (optionalConcept.isPresent()) { return optionalConcept.get(); } return this.sequenceLogicConceptMap.put(name, Factory.createNamedConcept(Integer.toString(name))); } /** * Gets the concept from sequence. * * @param sequence the sequence * @return the concept from sequence */ public Optional<Concept> getConceptFromSequence(int sequence) { return this.sequenceLogicConceptMap.get(sequence); } /** * Gets the feature. * * @param name the name * @return the feature */ private Feature getFeature(int name) { if (name < 0) { name = Get.identifierService() .getConceptSequence(name); } final Feature feature = this.sequenceLogicFeatureMap.get(name); if (feature != null) { return feature; } this.sequenceLogicFeatureMap.putIfAbsent(name, Factory.createNamedFeature(Integer.toString(name))); return this.sequenceLogicFeatureMap.get(name); } /** * Gets the loaded concepts. * * @return the loaded concepts */ public ConceptSequenceSet getLoadedConcepts() { return ConceptSequenceSet.of(this.loadedConcepts); } /** * Gets the role. * * @param name the name * @return the role */ private Role getRole(int name) { if (name < 0) { name = Get.identifierService() .getConceptSequence(name); } final Role role = this.sequenceLogicRoleMap.get(name); if (role != null) { return role; } this.sequenceLogicRoleMap.putIfAbsent(name, Factory.createNamedRole(Integer.toString(name))); return this.sequenceLogicRoleMap.get(name); } }