uk.ac.manchester.cs.owlapi.modularity.SyntacticLocalityModuleExtractor Maven / Gradle / Ivy
The newest version!
/* This file is part of the OWL API.
* The contents of this file are subject to the LGPL License, Version 3.0.
* Copyright 2014, The University of Manchester
*
* 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, see http://www.gnu.org/licenses/.
*
* Alternatively, the contents of this file may be used under the terms of the Apache License, Version 2.0 in which case, the provisions of the Apache License Version 2.0 are applicable instead of those above.
* 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 uk.ac.manchester.cs.owlapi.modularity;
import static org.semanticweb.owlapi.util.OWLAPIPreconditions.checkNotNull;
import static org.semanticweb.owlapi.util.OWLAPIStreamUtils.add;
import static org.semanticweb.owlapi.util.OWLAPIStreamUtils.asList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.semanticweb.owlapi.model.AxiomType;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDeclarationAxiom;
import org.semanticweb.owlapi.model.OWLDifferentIndividualsAxiom;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyLoaderConfiguration;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLRuntimeException;
import org.semanticweb.owlapi.model.OWLSameIndividualAxiom;
import org.semanticweb.owlapi.model.parameters.Imports;
import org.semanticweb.owlapi.modularity.OntologySegmenter;
import org.semanticweb.owlapi.reasoner.NodeSet;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.clarkparsia.owlapi.modularity.locality.LocalityClass;
import com.clarkparsia.owlapi.modularity.locality.SyntacticLocalityEvaluator;
/**
* Implementation of module extraction based on syntactic locality.
*
* @author Thomas Schneider, School of Computer Science, University of Manchester
*/
public class SyntacticLocalityModuleExtractor implements OntologySegmenter {
private static final Logger LOGGER =
LoggerFactory.getLogger(SyntacticLocalityModuleExtractor.class);
/**
* Represents the associated ontology.
*/
private final OntologyAxiomSet ontologyAxiomSet;
private final OWLOntology ontology;
/**
* Represents the manager for the associated ontology.
*/
private final OWLOntologyManager manager;
/**
* Type of module.
*/
private ModuleType moduleType;
private OWLOntologyLoaderConfiguration config;
/**
* Creates a new module extractor for a subset of a given ontology, its manager, and a specified
* type of locality.
*
* @param man the manager for the associated ontology
* @param axs the subset of the ontology as a set of axioms
* @param moduleType the type of module this extractor will construct
*/
public SyntacticLocalityModuleExtractor(OWLOntologyManager man, Stream axs,
ModuleType moduleType) {
this(man, man.getOntologyLoaderConfiguration(), axs, moduleType, false);
}
/**
* Creates a new module extractor for a subset of a given ontology, its manager, and a specified
* type of locality.
*
* @param man the manager for the associated ontology
* @param axs the subset of the ontology as a set of axioms
* @param moduleType the type of module this extractor will construct
* @param excludeAssertions true if assertions should be excluded
*/
public SyntacticLocalityModuleExtractor(OWLOntologyManager man, Stream axs,
ModuleType moduleType, boolean excludeAssertions) {
this(man, man.getOntologyLoaderConfiguration(), axs, moduleType, excludeAssertions);
}
/**
* Creates a new module extractor for a subset of a given ontology, its manager, and a specified
* type of locality.
*
* @param man the manager for the associated ontology
* @param config access to configuration
* @param axs the subset of the ontology as a set of axioms
* @param moduleType the type of module this extractor will construct
* @param excludeAssertions true if assertions should be excluded
*/
public SyntacticLocalityModuleExtractor(OWLOntologyManager man,
OWLOntologyLoaderConfiguration config, Stream axs, ModuleType moduleType,
boolean excludeAssertions) {
this.config = config;
this.moduleType = checkNotNull(moduleType, "moduleType cannot be null");
manager = checkNotNull(man, "man cannot be null");
Predicate filter =
ax -> excludeAssertions ? !AxiomType.ABoxAxiomTypes.contains(ax.getAxiomType()) : true;
List collect = asList(axs.filter(filter));
ontologyAxiomSet = new OntologyAxiomSet(collect);
try {
ontology = checkNotNull(man.createOntology(collect));
} catch (OWLOntologyCreationException e) {
throw new OWLRuntimeException(e);
}
}
/**
* Creates a new module extractor for a given ontology, its manager, and a specified type of
* locality.
*
* @param man the manager for the associated ontology
* @param ont the associated ontology
* @param moduleType the type of module this extractor will construct
*/
public SyntacticLocalityModuleExtractor(OWLOntologyManager man, OWLOntology ont,
ModuleType moduleType) {
this(man, man.getOntologyLoaderConfiguration(), asAxiomSet(ont), moduleType, false);
}
private static Stream asAxiomSet(OWLOntology ont) {
return ont.axioms(Imports.INCLUDED);
}
/**
* Super or sub classes.
*
* @param superOrSubClassLevel the super or sub class level
* @param superVsSub the super vs sub
* @param reasoner the reasoner
* @param classesInSig the classes in sig
* @return the sets the
*/
static Set superOrSubClasses(int superOrSubClassLevel, boolean superVsSub,
@Nullable OWLReasoner reasoner, Set classesInSig) {
checkNotNull(reasoner);
assert reasoner != null;
Set superOrSubClasses = new HashSet<>();
if (superOrSubClassLevel < 0) {
negativeLevel(superVsSub, reasoner, classesInSig, superOrSubClasses);
} else if (superOrSubClassLevel > 0) {
positiveLevel(superOrSubClassLevel, superVsSub, reasoner, classesInSig,
superOrSubClasses);
}
return superOrSubClasses;
}
protected static void positiveLevel(int superOrSubClassLevel, boolean superVsSub,
OWLReasoner reasoner, Set classesInSig, Set superOrSubClasses) {
Queue toBeSuClassedNow;
Queue toBeSuClassedNext = new LinkedList<>(classesInSig);
Queue suClassesToBeAdded = new LinkedList<>();
for (int i = 0; i < superOrSubClassLevel; i++) {
toBeSuClassedNow = toBeSuClassedNext;
toBeSuClassedNext = new LinkedList<>();
processLayer(superVsSub, reasoner, classesInSig, toBeSuClassedNow, toBeSuClassedNext,
suClassesToBeAdded);
}
superOrSubClasses.addAll(suClassesToBeAdded);
}
protected static void processLayer(boolean superVsSub, OWLReasoner reasoner,
Set classesInSig, Queue toBeSuClassedNow,
Queue toBeSuClassedNext, Queue suClassesToBeAdded) {
for (OWLClassExpression ce : toBeSuClassedNow) {
Stream suClasses;
if (superVsSub) {
suClasses = reasoner.getSuperClasses(ce, true).entities();
} else {
suClasses = reasoner.getSubClasses(ce, true).entities();
}
suClasses.filter(c -> !classesInSig.contains(c) && suClassesToBeAdded.add(c))
.forEach(toBeSuClassedNext::add);
}
}
protected static void negativeLevel(boolean superVsSub, OWLReasoner reasoner,
Set classesInSig, Set superOrSubClasses) {
for (OWLClassExpression ent : classesInSig) {
NodeSet nodes;
if (superVsSub) {
nodes = reasoner.getSuperClasses(ent, false);
} else {
nodes = reasoner.getSubClasses(ent, false);
}
add(superOrSubClasses, nodes.entities());
}
}
/**
* Returns the module type for this extractor.
*
* @return module type for this extractor
*/
public ModuleType getModuleType() {
return moduleType;
}
/**
* Changes the module type for this extractor without deleting the stored referenced entities.
*
* @param moduleType the new type of module
*/
public void setModuleType(ModuleType moduleType) {
this.moduleType = checkNotNull(moduleType, "moduleType cannot be null");
}
/**
* This auxiliary method extracts a module from a given sub-ontology of the associated ontology
* for a given signature and locality type. The module will contain only logical axioms, no
* annotation or declaration axioms. The sub-ontology and module are represented as arrays of
* Booleans.
*
* This method is (if necessary, iteratively) called by the public method extract.
*
* @param subOnt an array of Booleans representing the sub-ontology
* @param signature the seed signature (set of entities) for the module; on return of the
* method, this will contain the signature of the module
* @param localityClass the type of locality
* @return an array of Booleans representing the module
*/
boolean[] extractLogicalAxioms(boolean[] subOnt, Set signature,
LocalityClass localityClass) {
boolean[] mod = ontologyAxiomSet.getSubset(false);
boolean[] q2 = ontologyAxiomSet.cloneSubset(subOnt);
SyntacticLocalityEvaluator sle = new SyntacticLocalityEvaluator(localityClass);
boolean change = true;
int loopNumber = 0;
while (change) {
change = false;
loopNumber++;
LOGGER.info(" Loop {}", Integer.valueOf(loopNumber));
for (int i = 0; i < q2.length; i++) {
OWLAxiom axiom = ontologyAxiomSet.getAxiom(i);
if (q2[i]) {
if (!sle.isLocal(axiom, signature)) {
LOGGER.info(" Non-local axiom: {}", axiom);
mod[i] = true;
q2[i] = false;
int oldSize = signature.size();
add(signature, axiom.signature());
// only triggering a change when the signature has
// changed doesn't improve performance
if (signature.size() > oldSize) {
change = true;
LOGGER.info(" New signature: {}", signature);
}
} else {
LOGGER.info(" Local axiom: {}", axiom);
}
}
}
}
return mod;
}
/**
* This method extracts a module from a given sub-ontology of the associated ontology for a
* given signature and locality type. The module will only contain logical axioms, no annotation
* or declaration axioms. The sub-ontology and module are represented as sets of axioms.
*
* @param subOnt a set of axioms representing the sub-ontology
* @param signature the seed signature (set of entities) for the module; on return of the
* method, this will contain the signature of the module
* @param localityClass the type of locality
* @return a set of axioms representing the module
*/
Set extract(Set subOnt, Set signature,
LocalityClass localityClass) {
HashSet mod = new HashSet<>();
HashSet q2 = new HashSet<>(subOnt);
SyntacticLocalityEvaluator sle = new SyntacticLocalityEvaluator(localityClass);
boolean change = true;
int loopNumber = 0;
while (change) {
change = false;
loopNumber++;
LOGGER.info(" Loop {}", Integer.valueOf(loopNumber));
HashSet q2remove = new HashSet<>();
for (OWLAxiom ax : q2) {
if (!sle.isLocal(ax, signature)) {
LOGGER.info(" Non-local axiom: {}", ax);
mod.add(ax);
q2remove.add(ax);
int oldSize = signature.size();
add(signature, ax.signature());
// only triggering a change when the signature has changed
// doesn't improve performance
if (signature.size() > oldSize) {
change = true;
LOGGER.info(" New signature: {}", signature);
}
} else {
LOGGER.info(" Local axiom: {}", ax);
}
}
q2.removeAll(q2remove);
}
return mod;
}
/**
* This method enriches a module that contains only logical axioms with all necessary entity
* declaration axioms, entity annotation axioms, and axiom annotation axioms. The module and
* enriched module are represented as sets of axioms.
*
* @param module a set of axioms representing the original module
* @param sig a set of entities representing the signature of the original module
* @return a set of axioms representing the enriched module
*/
Set enrich(Set module, Set sig) {
if (config.shouldSkipModuleAnnotations()) {
return module;
}
Set enrichedModule = new HashSet<>(module);
LOGGER.info(
"\nEnriching with declaration axioms, annotation axioms, same/different individual axioms ...");
// Adding all entity declaration axioms
// Adding all entity annotation axioms
for (OWLEntity entity : sig) {
List declarationAxioms =
asList(ontology.declarationAxioms(entity));
enrichedModule.addAll(declarationAxioms);
if (LOGGER.isInfoEnabled()) {
declarationAxioms
.forEach(a -> LOGGER.info(" Added entity declaration axiom: {}", a));
}
}
Set iris = new HashSet<>(sig.size());
for (OWLEntity i : sig) {
iris.add(i.getIRI());
}
ontology.axioms(AxiomType.ANNOTATION_ASSERTION).forEach(annotation -> {
if (iris.contains(annotation.getSubject())) {
enrichedModule.add(annotation);
LOGGER.info(" Added entity annotation axiom: {}", annotation);
}
});
// Adding all same-individuals axioms
// Adding all different-individuals axioms
for (OWLEntity entity : sig) {
if (entity.isOWLNamedIndividual()) {
List sameIndividualAxioms =
asList(ontology.sameIndividualAxioms(entity.asOWLNamedIndividual()));
enrichedModule.addAll(sameIndividualAxioms);
if (LOGGER.isInfoEnabled()) {
sameIndividualAxioms
.forEach(i -> LOGGER.info(" Added same individual axiom: {}", i));
}
List differentIndividualAxioms =
asList(ontology.differentIndividualAxioms(entity.asOWLNamedIndividual()));
enrichedModule.addAll(differentIndividualAxioms);
if (LOGGER.isInfoEnabled()) {
differentIndividualAxioms
.forEach(a -> LOGGER.info(" Added different individual axiom: {}", a));
}
}
}
return enrichedModule;
}
/**
* Output signature.
*
* @param preamble the preamble
* @param sig the sig
*/
void outputSignature(String preamble, Set sig) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info(preamble);
sig.forEach(e -> LOGGER.info(" {}", e));
}
}
/**
* Extract unnested module.
*
* @param sig the signature
* @param cls the locality class
* @return the sets the
*/
Set extractUnnestedModule(Set sig, LocalityClass cls) {
outputSignature("\nExtracting " + cls + " module for the following seed signature ... ",
sig);
boolean[] subOnt = ontologyAxiomSet.getSubset(true);
Set signature = new HashSet<>(sig);
boolean[] module = extractLogicalAxioms(subOnt, signature, cls);
Set moduleAsSet = ontologyAxiomSet.toSet(module);
return enrich(moduleAsSet, signature);
}
/**
* Enrich signature.
*
* @param sig the signature
* @param superClassLevel the super class level
* @param subClassLevel the sub class level
* @param reasoner the reasoner
* @return the sets the
*/
Set enrichSignature(Set sig, int superClassLevel, int subClassLevel,
@Nullable OWLReasoner reasoner) {
Set enrichedSig = new HashSet<>(sig);
Set classesInSig = new HashSet<>();
for (OWLEntity ent : sig) {
if (ent.isOWLClass()) {
classesInSig.add(ent.asOWLClass());
}
}
if (superClassLevel != 0) {
enrichedSig.addAll(superOrSubClasses(superClassLevel, true, reasoner, classesInSig));
}
if (subClassLevel != 0) {
enrichedSig.addAll(superOrSubClasses(subClassLevel, false, reasoner, classesInSig));
}
return enrichedSig;
}
@Override
public Set extract(Set signature) {
return extract(signature, 0, 0, null);
}
/**
* Extracts a module from the associated ontology for a given signature and the associated
* module type, and returns the module as a set of axioms. The seed signature (set of entities)
* which determines the module is the specified signature plus possibly all superclasses and/or
* subclasses of the classes therein. Sub-/superclasses are determined using the specified
* reasoner. The module will include annotation and declaration axioms for all entities and
* axioms in it.
*
* @param sig the seed signature (set of entities) for the module
* @param superClassLevel determines whether superclasses are added to the signature before
* segment extraction, see below for admissible values
* @param subClassLevel determines whether subclasses are added to the signature before
* segment extraction
* Admissible values for superClassLevel (analogously for subClassLevel):
*
* - If superClassLevel greater than 0, then all classes C are included
* for which the class hierarchy computed by the reasoner contains a path
* of length at most superClassLevel downwards from C to some class from
* the signature.
* - If superClassLevel = 0, then no super-/subclasses are added.
* - If superClassLevel lesser than 0, then all direct and indirect
* super-/subclasses of any class in the signature are added.
*
* @param reasoner the reasoner to determine super-/subclasses. This can be an arbitrary
* reasoner, including a ToldClassHierarchyReasoner. It must have loaded
* the ontology. Can be null if superClassLevel and subClassLevel are 0.
* @return the module
*/
@Override
public Set extract(Set sig, int superClassLevel, int subClassLevel,
@Nullable OWLReasoner reasoner) {
Set enrichedSig = enrichSignature(sig, superClassLevel, subClassLevel, reasoner);
switch (moduleType) {
case TOP:
return extractUnnestedModule(enrichedSig, LocalityClass.TOP_TOP);
case BOT:
return extractUnnestedModule(enrichedSig, LocalityClass.BOTTOM_BOTTOM);
case STAR:
return extractStar(enrichedSig);
default:
throw new OWLRuntimeException("Unsupported module type: " + moduleType);
}
}
protected Set extractStar(Set enrichedSig) {
boolean[] subOnt = ontologyAxiomSet.getSubset(true);
boolean nextStepNecessary = true;
boolean inFirstStep = true;
LocalityClass localityClass = LocalityClass.BOTTOM_BOTTOM;
Set seedSig = new HashSet<>(enrichedSig);
while (nextStepNecessary) {
outputSignature(
"\nExtracting " + localityClass + " module for the following seed signature: ",
enrichedSig);
int previousModuleSize = ontologyAxiomSet.subsetCardinality(subOnt);
seedSig = new HashSet<>(enrichedSig);
subOnt = extractLogicalAxioms(subOnt, seedSig, localityClass);
if (ontologyAxiomSet.subsetCardinality(subOnt) == previousModuleSize && !inFirstStep) {
nextStepNecessary = false;
}
inFirstStep = false;
if (localityClass == LocalityClass.BOTTOM_BOTTOM) {
localityClass = LocalityClass.TOP_TOP;
} else {
localityClass = LocalityClass.BOTTOM_BOTTOM;
}
}
Set moduleAsSet = ontologyAxiomSet.toSet(subOnt);
return enrich(moduleAsSet, seedSig);
}
@Override
public OWLOntology extractAsOntology(Set signature, IRI iri)
throws OWLOntologyCreationException {
return extractAsOntology(signature, iri, 0, 0, null);
}
@Override
public OWLOntology extractAsOntology(Set signature, IRI iri, int superClassLevel,
int subClassLevel, @Nullable OWLReasoner reasoner) throws OWLOntologyCreationException {
return manager.createOntology(extract(signature, superClassLevel, subClassLevel, reasoner),
iri);
}
/**
* Auxiliary inner class for the representation of the associated ontology and all its
* sub-ontologies as arrays of axioms. Advantages: (1) quicker set manipulation operations; (2)
* storage of all referenced entities of an axiom once this axiom is dealt with.
*
* @author Thomas Schneider
* @author School of Computer Science
* @author University of Manchester
*/
static class OntologyAxiomSet {
/**
* Array representing all axioms of the associated ontology.
*/
final OWLAxiom[] ax;
/**
* Creates a new OntologyAxiomSet from a given set of axioms without looking up the
* referenced entities.
*
* @param axs the set of axioms representing the ontology
*/
OntologyAxiomSet(List axs) {
ax = axs.toArray(new OWLAxiom[axs.size()]);
}
/**
* Returns the number of axioms in this set.
*
* @return the number of axioms in this set
*/
public int size() {
return ax.length;
}
/**
* Returns some axiom from this set.
*
* @param i a number for an axiom
* @return the i-th axiom in this set
*/
public OWLAxiom getAxiom(int i) {
return ax[i];
}
/**
* Constructs an array of Booleans that represents a subset of this set. The subset either
* equals this set (if init==true) or is the empty set (if init==false).
*
* @param init determines the initial value of the subset
* @return array of Booleans representing the specified subset
*/
public boolean[] getSubset(boolean init) {
boolean[] subset = new boolean[ax.length];
Arrays.fill(subset, init);
return subset;
}
/**
* Clones an array of Booleans that represents a subset of this set.
*
* @param oldSubset an array representing the original subset
* @return an array representing the new subset
*/
public boolean[] cloneSubset(boolean[] oldSubset) {
boolean[] newSubset = new boolean[ax.length];
System.arraycopy(oldSubset, 0, newSubset, 0, ax.length);
return newSubset;
}
/**
* Subset cardinality.
*
* @param subset the subset
* @return the int
*/
public int subsetCardinality(boolean[] subset) {
int card = 0;
for (int i = 0; i < ax.length; i++) {
if (subset[i]) {
card++;
}
}
return card;
}
/**
* Transforms a subset of this set (represented by an array of Booleans) into a set of
* axioms.
*
* @param subset an array representing the subset
* @return a set of axioms
*/
public Set toSet(boolean[] subset) {
HashSet axs = new HashSet<>();
for (int i = 0; i < ax.length; i++) {
if (subset[i]) {
axs.add(ax[i]);
}
}
return axs;
}
}
}