/* * DisjointWithQueries.java * * Created on Oct 28, 2010, 11:35:42 PM * * Description: Provides disjointWith inference queries into the the knowledge base specified by a given RDF entity manager. * * Copyright (C) Oct 28, 2010, Stephen L. Reed. * * This program is free software; you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.texai.subsumptionReasoner; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import net.jcip.annotations.NotThreadSafe; import org.apache.log4j.Logger; import org.openrdf.OpenRDFException; import org.openrdf.model.URI; import org.openrdf.query.BindingSet; import org.openrdf.query.MalformedQueryException; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.QueryLanguage; import org.openrdf.query.TupleQuery; import org.openrdf.query.TupleQueryResult; import org.openrdf.repository.RepositoryConnection; import org.texai.kb.Constants; import org.texai.kb.persistence.RDFEntityManager; import org.texai.kb.persistence.RDFUtility; import org.texai.subsumptionGraph.CachedSubsumptionGraph; import org.texai.util.LRUMap; import org.texai.util.TexaiException; /** Provides disjointWith inference queries into the the knowledge base specified by a given RDF entity manager. * * @author reed */ @NotThreadSafe public class DisjointWithQueries { /** the logger */ private static final Logger LOGGER = Logger.getLogger(DisjointWithQueries.class); /** the disjointWith memoized cache */ private final DisjointWithMemoizationDictionary disjointWithMemoizationDictionary = new DisjointWithMemoizationDictionary(); /** the disjointWith query string */ private static final String DISJOINT_WITH_QUERY_STRING = "SELECT s, o FROM {s} <" + Constants.OWL_NAMESPACE + "disjointWith> {o}"; /** the SubClassOfQueries object */ private final SubClassOfQueries subClassOfQueries; /** the RDF entity manager */ private final RDFEntityManager rdfEntityManager; /** the tuple query */ private final TupleQuery disjointWithQuery; /** the cached subsumption graph */ private final CachedSubsumptionGraph cachedSubsumptionGraph; /** Constructs a new DisjointWithQueries instance. * * @param rdfEntityManager the RDF entity manager */ public DisjointWithQueries(final RDFEntityManager rdfEntityManager) { //Preconditions assert rdfEntityManager != null : "kbEntityManager must not be null"; this.rdfEntityManager = rdfEntityManager; subClassOfQueries = new SubClassOfQueries(rdfEntityManager); cachedSubsumptionGraph = CachedSubsumptionGraph.getInstance(); final RepositoryConnection repositoryConnection = rdfEntityManager.getConnectionToNamedRepository("OpenCyc"); assert repositoryConnection != null; try { disjointWithQuery = repositoryConnection.prepareTupleQuery( QueryLanguage.SERQL, DISJOINT_WITH_QUERY_STRING); } catch (final MalformedQueryException ex) { throw new TexaiException(ex); } catch (final OpenRDFException ex) { throw new TexaiException(ex); } } /** Returns whether the two given terms are disjoint classes. * * @param term1 the first given term * @param term2 the second given term * @return whether the two given terms are disjoint classes. */ public boolean areDisjoint( final URI term1, final URI term2) { //preconditions assert term1 != null : "term1 must not be null"; assert term2 != null : "term2 must not be null"; final List<URI> termPair = orderTermPair(term1, term2); Boolean areDisjoint; areDisjoint = disjointWithMemoizationDictionary.get().get(termPair); if (areDisjoint == null) { areDisjoint = areDisjointInternal(term1, term2); disjointWithMemoizationDictionary.get().put(termPair, areDisjoint); } return areDisjoint; } /** Adds the given term pair to the memoized disjoint-with dictionary. * * @param term1 the first given term * @param term2 the second given term */ public void addMemoizedDisjointWith(final URI term1, final URI term2) { //preconditions assert term1 != null : "term1 must not be null"; assert term2 != null : "term2 must not be null"; final List<URI> termPair = orderTermPair(term1, term2); disjointWithMemoizationDictionary.get().put(termPair, true); } /** Returns whether the two given terms are disjoint classes, without memoizing the results. This algorithm works by * spreading activation from the two given terms, through successive superclass expansions, until either Thing is reached * for all branches, or a disjoint link has been detected. * * @param term1 the first given term * @param term2 the second given term * @return whether the two given terms are disjoint classes. */ private boolean areDisjointInternal( final URI term1, final URI term2) { //preconditions assert term1 != null : "term1 must not be null"; assert term2 != null : "term2 must not be null"; // the dictionaries of self classes and classes discovered to be superclasses of the respective terms so far, // class --> superclass path from respective term final Map<URI, List<URI>> classDictionary1 = new HashMap<>(); final Map<URI, List<URI>> classDictionary2 = new HashMap<>(); // the dictionaries of the the classes discovered to be disjoint with the respective terms so far, // disjoint class --> other disjoint class final Map<URI, URI> disjointClassDictionary1 = new HashMap<>(); final Map<URI, URI> disjointClassDictionary2 = new HashMap<>(); // the superclasses of the respective terms which were discovered in the most recent spreading activation iteration final Set<URI> queue1 = new HashSet<>(); final Set<URI> queue2 = new HashSet<>(); // the superclasses of the respective terms which will be processed in the next spreading activation iteration final Set<URI> nextQueue1 = new HashSet<>(); final Set<URI> nextQueue2 = new HashSet<>(); // add self classes to the class dictionaries final List<URI> superClassPath1 = new ArrayList<>(); superClassPath1.add(term1); classDictionary1.put(term1, superClassPath1); final List<URI> superClassPath2 = new ArrayList<>(); superClassPath2.add(term2); classDictionary2.put(term2, superClassPath2); // add disjoint terms for (final URI disjointWithTerm : getDirectDisjointWiths(term1)) { disjointClassDictionary1.put(disjointWithTerm, term1); } for (final URI disjointWithTerm : getDirectDisjointWiths(term2)) { disjointClassDictionary2.put(disjointWithTerm, term2); } // detect disjoint relationship for (final URI disjointWithTerm : disjointClassDictionary1.keySet()) { if (classDictionary2.containsKey(disjointWithTerm)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("explanation why " + RDFUtility.formatResource(term1) + " is disjoint with " + RDFUtility.formatResource(term2) + " ..."); LOGGER.debug(" " + RDFUtility.formatResources(classDictionary2.get(disjointWithTerm))); LOGGER.debug(" " + RDFUtility.formatResources(classDictionary1.get(disjointClassDictionary1.get(disjointWithTerm)))); return true; } } } for (final URI disjointWithTerm : disjointClassDictionary2.keySet()) { if (classDictionary1.containsKey(disjointWithTerm)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("explanation why " + RDFUtility.formatResource(term1) + " is disjoint with " + RDFUtility.formatResource(term2) + " ..."); LOGGER.debug(" " + RDFUtility.formatResources(classDictionary2.get(disjointWithTerm))); LOGGER.debug(" " + RDFUtility.formatResources(classDictionary1.get(disjointClassDictionary2.get(disjointWithTerm)))); return true; } } } // add self classes to the spreading activation queues queue1.add(term1); queue2.add(term2); while (true) { // if (LOGGER.isDebugEnabled()) { // LOGGER.info("spreading activation ..."); // LOGGER.info(" queue1: " + RDFUtility.formatResources(queue1)); // LOGGER.info(" queue2: " + RDFUtility.formatResources(queue2)); // } // spread activitation by adding direct superclasses for (final URI term : queue1) { final Collection<URI> superClasses = subClassOfQueries.getDirectSuperClasses("OpenCyc", term); for (final URI superClass : superClasses) { if (!classDictionary1.containsKey(superClass)) { // add superclass to the class dictionary final List<URI> superClassPath = classDictionary1.get(term); assert superClassPath != null; final List<URI> superClassPathClone = new ArrayList<>(superClassPath); superClassPathClone.add(superClass); classDictionary1.put(superClass, superClassPathClone); nextQueue1.add(superClass); } } } for (final URI term : queue2) { final Collection<URI> superClasses = subClassOfQueries.getDirectSuperClasses("OpenCyc", term); for (final URI superClass : superClasses) { if (!classDictionary2.containsKey(superClass)) { // add superclass to the class dictionary final List<URI> superClassPath = classDictionary2.get(term); assert superClassPath != null; final List<URI> superClassPathClone = new ArrayList<>(superClassPath); superClassPathClone.add(superClass); classDictionary2.put(superClass, superClassPathClone); nextQueue2.add(superClass); } } } if (nextQueue1.isEmpty() && nextQueue2.isEmpty()) { return false; } // add disjoint terms with respect to the added direct superclasses for (final URI term : nextQueue1) { for (final URI disjointWithTerm : getDirectDisjointWiths(term)) { disjointClassDictionary1.put(disjointWithTerm, term); } } for (final URI term : nextQueue2) { for (final URI disjointWithTerm : getDirectDisjointWiths(term)) { disjointClassDictionary2.put(disjointWithTerm, term); } } // detect disjoint relationship for (final URI disjointWithTerm : disjointClassDictionary1.keySet()) { if (classDictionary2.containsKey(disjointWithTerm)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("explanation why " + RDFUtility.formatResource(term1) + " is disjoint with " + RDFUtility.formatResource(term2) + " ..."); LOGGER.debug(" " + RDFUtility.formatResources(classDictionary2.get(disjointWithTerm))); LOGGER.debug(" " + RDFUtility.formatResources(classDictionary1.get(disjointClassDictionary1.get(disjointWithTerm)))); return true; } } } for (final URI disjointWithTerm : disjointClassDictionary2.keySet()) { if (classDictionary1.containsKey(disjointWithTerm)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("explanation why " + RDFUtility.formatResource(term1) + " is disjoint with " + RDFUtility.formatResource(term2) + " ..."); LOGGER.debug(" " + RDFUtility.formatResources(classDictionary2.get(disjointWithTerm))); LOGGER.debug(" " + RDFUtility.formatResources(classDictionary1.get(disjointClassDictionary2.get(disjointWithTerm)))); return true; } } } // migrate the queue items for the next spreading activation iteration queue1.clear(); queue1.addAll(nextQueue1); nextQueue1.clear(); queue2.clear(); queue2.addAll(nextQueue2); nextQueue2.clear(); } } /** Returns whether the given terms are disjoint. * * @param termPair the two ordered terms * @return whether the given terms are disjoint */ private boolean areDisjoint(final URI[] termPair) { //preconditions assert termPair != null : "termPair must not be null"; disjointWithQuery.setBinding("s", termPair[0]); disjointWithQuery.setBinding("o", termPair[1]); boolean areDisjoint = false; try { TupleQueryResult tupleQueryResult = disjointWithQuery.evaluate(); areDisjoint = tupleQueryResult.hasNext(); tupleQueryResult.close(); if (!areDisjoint) { disjointWithQuery.setBinding("s", termPair[1]); disjointWithQuery.setBinding("o", termPair[0]); tupleQueryResult = disjointWithQuery.evaluate(); areDisjoint = tupleQueryResult.hasNext(); tupleQueryResult.close(); } } catch (final QueryEvaluationException ex) { throw new TexaiException(ex); } return areDisjoint; } /** Returns the class terms that are directly asserted to disjoint with the given class term. * * @param term the given class term * @return the class terms that are directly asserted to disjoint with the given class term */ public Collection<URI> getDirectDisjointWiths(final URI term) { //preconditions assert term != null : "term must not be null"; if (cachedSubsumptionGraph == null) { final Set<URI> disjointWithTerms = new HashSet<>(); disjointWithQuery.setBinding("s", term); disjointWithQuery.removeBinding("o"); try { // term owl:disjointWith ? TupleQueryResult tupleQueryResult = disjointWithQuery.evaluate(); while (tupleQueryResult.hasNext()) { final BindingSet bindingSet = tupleQueryResult.next(); disjointWithTerms.add((URI) bindingSet.getBinding("o").getValue()); } tupleQueryResult.close(); // ? owl:disjointWith term disjointWithQuery.removeBinding("s"); disjointWithQuery.setBinding("o", term); tupleQueryResult = disjointWithQuery.evaluate(); while (tupleQueryResult.hasNext()) { final BindingSet bindingSet = tupleQueryResult.next(); disjointWithTerms.add((URI) bindingSet.getBinding("s").getValue()); } tupleQueryResult.close(); } catch (final QueryEvaluationException ex) { throw new TexaiException(ex); } return disjointWithTerms; } else { return cachedSubsumptionGraph.getDirectDisjointWiths(term); } } /** Returns an ordered pair consisting of the given terms. * * @param term1 the first given term * @param term2 the second given term * @return an ordered pair consisting of the given terms */ private static List<URI> orderTermPair(final URI term1, final URI term2) { //preconditions assert term1 != null : "term1 must not be null"; assert term2 != null : "term2 must not be null"; final List<URI> termPair = new ArrayList<>(); if (term1.toString().compareTo(term2.toString()) < 1) { termPair.add(term1); termPair.add(term2); } else { termPair.add(term2); termPair.add(term1); } return termPair; } /** Provides the disjointWith memoized cache. */ class DisjointWithMemoizationDictionary extends ThreadLocal<Map<List<URI>, Boolean>> { /** Returns the current thread's "initial value" for this thread-local variable. * * @return the current thread's "initial value */ @Override protected Map<List<URI>, Boolean> initialValue() { return new LRUMap<>( 100, // initialCapacity 10000); // maximumCapacity } } }