Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/dl-query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# DL-Query

## Contents

1. [Overview](#overview)
2. [Query types](#query-types)

## Overview

ROBOT can execute DL queries against an ontology. The functionality closely mimics the functionality of the <a href="https://protegewiki.stanford.edu/wiki/DLQueryTab" target="_blank">DL Query Tab</a> in Protege.

The `dl-query` command can be used to query for ancestors, descendants, instances and other relatives of an OWL Class Expression that is provided in Manchester syntax.

The output is always a list of Entity IRIs. Multiple queries and output files can be supplied. For example:

robot query --input uberon_module.owl \
--query "'part_of' some 'subdivision of trunk'" part_of_subdiv_trunk.txt \
--query "'part_of' some 'nervous system'" part_of_nervous_system.txt

## Query Types

The following query types are currently supported:

- equivalents: Classes that are exactly equivalent to the supplied class expression
- parents: Direct parents (superclasses) of the class expression provided
- children: Direct children (subclasses) of the class expression provided
- descendants (default): All subclasses of the class expression provided
- ancestors: All superclasses of the class expression provided
- instances: All named individuals that are instances of the class expression provided
174 changes: 174 additions & 0 deletions robot-command/src/main/java/org/obolibrary/robot/DLQueryCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package org.obolibrary.robot;

import java.io.File;
import java.io.IOException;
import java.util.*;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.io.FileUtils;
import org.obolibrary.robot.exceptions.InconsistentOntologyException;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.*;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Handles inputs and outputs for the {@link DLQueryOperation}.
*
* @author <a href="mailto:[email protected]">Nicolas Matentzoglu</a>
*/
public class DLQueryCommand implements Command {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command is defined but never added to the CommandManager.

/** Logger. */
private static final Logger logger = LoggerFactory.getLogger(DLQueryCommand.class);

private static final String NS = "dl-query#";

private static final List<String> LEGAL_RELATIONS =
Arrays.asList("equivalents", "ancestors", "descendants", "instances", "parents", "children");
OWLDataFactory df = OWLManager.getOWLDataFactory();

private static final String maxTypeError = NS + "MAX TYPE ERROR --max ('%s') must be an integer";
private static final String illegalRelationError =
NS + "ILLEGAL RELATION ERROR: %s. Must be one of " + String.join(" ", LEGAL_RELATIONS) + ".";
private static final String missingQueryArgumentError =
NS + "MISSING QUERY ARGUMENT ERROR: must have a valid --query.";

/** Error message when --query does not have two arguments. */
private static final String missingOutputError =
NS + "MISSING OUTPUT ERROR --%s requires two arguments: query and output";

/** Error message when a query is not provided */
private static final String missingQueryError =
NS + "MISSING QUERY ERROR at least one query must be provided";

/** Store the command-line options for the command. */
private Options options;

public DLQueryCommand() {
Options o = CommandLineHelper.getCommonOptions();
o.addOption("i", "input", true, "load ontology from a file");
o.addOption("I", "input-iri", true, "load ontology from an IRI");
o.addOption("r", "reasoner", true, "reasoner to use: ELK, HermiT, JFact");

Option opt = new Option("q", "query", true, "the DL query to run");
opt.setArgs(2);
o.addOption(opt);

o.addOption(
"s",
"select",
true,
"select what relations to query: equivalents, parents, children, ancestors, descendants, instances");
o.addOption("o", "output", true, "save ontology containing only explanation axioms to a file");
options = o;
}

@Override
public String getName() {
return "dl-query";
}

@Override
public String getDescription() {
return "query the ontology with the given class expression";
}

@Override
public String getUsage() {
return "robot dl-query --input <file> --query <expression> --output <output>";
}

@Override
public Options getOptions() {
return options;
}

/**
* Handle the command-line and file operations for the DLQueryOperation.
*
* @param args strings to use as arguments
*/
@Override
public void main(String[] args) {
try {
execute(null, args);
} catch (Exception e) {
CommandLineHelper.handleException(e);
}
}

@Override
public CommandState execute(CommandState state, String[] args) throws Exception {
CommandLine line = CommandLineHelper.getCommandLine(getUsage(), getOptions(), args);
if (line == null) {
return null;
}
if (state == null) {
state = new CommandState();
}
IOHelper ioHelper = CommandLineHelper.getIOHelper(line);
state = CommandLineHelper.updateInputOntology(ioHelper, state, line);
OWLOntology ontology = state.getOntology();

OWLReasonerFactory reasonerFactory = CommandLineHelper.getReasonerFactory(line, true);
List<String> selects = CommandLineHelper.getOptionalValues(line, "select");

List<List<String>> queries = getQueries(line);
for (List<String> q : queries) {
queryOntology(q, ontology, reasonerFactory, selects);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As implemented, it is very inefficient to run several queries against the same ontology, because there is no shared state between each query (in particular, no shared reasoner, even though the ontology cannot possibly change between each query, so initialising a brand new reasoner instance for each query is pointless).

From some tests I did with Uberon:

  • running a single query takes ~11s;
  • running 2 queries (--query "query 1" output1.txt --query "query 2" output2.txt) takes ~19s;
  • running 3 queries takes ~27s;
  • and so on.

By contrast, with a modified version that uses a shared state:

  • running a single query takes ~11s;
  • running 2 queries takes ~11.2s;
  • running 3 queries takes ~11.4s;
  • and so on.

}

state.setOntology(ontology);
CommandLineHelper.maybeSaveOutput(line, ontology);
return state;
}

private void queryOntology(
List<String> q,
OWLOntology ontology,
OWLReasonerFactory reasonerFactory,
List<String> selects)
throws InconsistentOntologyException, IOException {
OWLReasoner r = reasonerFactory.createReasoner(ontology);
String query = q.get(0);
File output = new File(q.get(1));
OWLClassExpression classExpression = DLQueryOperation.parseOWLClassExpression(query, ontology);
if (r.isConsistent()) {
List<OWLEntity> entities = DLQueryOperation.query(classExpression, r, selects);
writeQueryResultsToFile(output, entities);
} else {
throw new InconsistentOntologyException();
}
}

/**
* Given a command line, get a list of queries.
*
* @param line CommandLine with options
* @return List of queries
*/
private static List<List<String>> getQueries(CommandLine line) {
// Collect all queries as (queryPath, outputPath) pairs.
List<List<String>> queries = new ArrayList<>();
List<String> qs = CommandLineHelper.getOptionalValues(line, "query");
for (int i = 0; i < qs.size(); i += 2) {
try {
queries.add(qs.subList(i, i + 2));
} catch (IndexOutOfBoundsException e) {
throw new IllegalArgumentException(String.format(missingOutputError, "query"));
}
}
if (queries.isEmpty()) {
throw new IllegalArgumentException(missingQueryError);
}
return queries;
}

private void writeQueryResultsToFile(File output, List<OWLEntity> results) throws IOException {
Collections.sort(results);
FileUtils.writeLines(output, results);
}
}
30 changes: 27 additions & 3 deletions robot-command/src/main/java/org/obolibrary/robot/QueryCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import org.apache.jena.query.Dataset;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.tdb.TDBFactory;
import org.phenoscape.owlet.Owlet;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -239,14 +242,27 @@ private static void executeInMemory(
CommandLine line, OWLOntology inputOntology, List<List<String>> queries) throws Exception {
boolean useGraphs = CommandLineHelper.getBooleanValue(line, "use-graphs", false);
Dataset dataset = QueryOperation.loadOntologyAsDataset(inputOntology, useGraphs);
boolean useOwlet = CommandLineHelper.getBooleanValue(line, "owlet", false);

Owlet owlet = getOwlet(line, inputOntology, useOwlet);
try {
runQueries(line, dataset, queries);
runQueries(line, dataset, queries, owlet);
} finally {
dataset.close();
// TDBFactory.release(dataset);
}
}

private static Owlet getOwlet(CommandLine line, OWLOntology inputOntology, boolean useOwlet) {
Owlet owlet = null;
if (useOwlet) {
OWLReasonerFactory rf = CommandLineHelper.getReasonerFactory(line);
OWLReasoner r = rf.createReasoner(inputOntology);
owlet = new Owlet(r);
}
return owlet;
}

/**
* Given a command line and a list of queries, execute 'query' using TDB and writing mappings to
* disk.
Expand All @@ -260,8 +276,9 @@ private static void executeOnDisk(CommandLine line, List<List<String>> queries)
Dataset dataset = createTDBDataset(line);
boolean keepMappings = CommandLineHelper.getBooleanValue(line, "keep-tdb-mappings", false);
String tdbDir = CommandLineHelper.getDefaultValue(line, "tdb-directory", ".tdb");

try {
runQueries(line, dataset, queries);
runQueries(line, dataset, queries, null);
} finally {
dataset.close();
TDBFactory.release(dataset);
Expand Down Expand Up @@ -396,7 +413,8 @@ private static List<List<String>> getQueries(CommandLine line) {
* @param queries List of queries
* @throws IOException on issue reading or writing files
*/
private static void runQueries(CommandLine line, Dataset dataset, List<List<String>> queries)
private static void runQueries(
CommandLine line, Dataset dataset, List<List<String>> queries, Owlet owlet)
throws IOException {
String format = CommandLineHelper.getOptionalValue(line, "format");
String outputDir = CommandLineHelper.getDefaultValue(line, "output-dir", "");
Expand All @@ -407,6 +425,12 @@ private static void runQueries(CommandLine line, Dataset dataset, List<List<Stri

String query = FileUtils.readFileToString(new File(queryPath), Charset.defaultCharset());

if (owlet != null) {
// the second parameter (true) refers to an older implementation of SPARQL and should always
// be true
query = owlet.expandQueryString(query, true);
}

String formatName = format;
if (formatName == null) {
if (outputPath == null) {
Expand Down
20 changes: 18 additions & 2 deletions robot-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-arq</artifactId>
<version>3.17.0</version>
<version>4.9.0</version>
<exclusions>
<exclusion>
<groupId>com.github.jsonld-java</groupId>
Expand All @@ -134,7 +134,7 @@
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-tdb</artifactId>
<version>3.17.0</version>
<version>4.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
Expand Down Expand Up @@ -232,6 +232,11 @@
<artifactId>jackson-annotations</artifactId>
<version>2.12.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.2</version>
</dependency>
<dependency>
<groupId>net.sourceforge.owlapi</groupId>
<artifactId>owlexplanation</artifactId>
Expand Down Expand Up @@ -288,6 +293,17 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.phenoscape</groupId>
<artifactId>owlet_${scala.version}</artifactId>
<version>2.0.0</version>
<exclusions>
<exclusion>
<groupId>net.sourceforge.owlapi</groupId>
<artifactId>owlapi-distribution</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
Expand Down
Loading