From bc37ee9293d8a4098edce2a77db6efa3d87b6dd2 Mon Sep 17 00:00:00 2001 From: Federico Igne Date: Thu, 30 Sep 2021 18:10:01 +0100 Subject: Make canonical model generation parametric over named graph --- .../scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala | 35 +++++++----- .../scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | 64 +++++++++++++++------- .../ox/cs/rsacomb/converter/RDFoxConverter.scala | 21 ++++--- .../uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala | 22 +++----- .../scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala | 29 ++++++++-- src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala | 53 ++++++++++-------- 6 files changed, 141 insertions(+), 83 deletions(-) (limited to 'src/main/scala/uk/ac') diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala index 3467d3c..a39b9c0 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala @@ -31,7 +31,8 @@ import tech.oxfordsemantic.jrdfox.logic.datalog.{ BodyFormula, Negation, Rule, - TupleTableAtom + TupleTableAtom, + TupleTableName } import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term, Variable} @@ -48,8 +49,9 @@ import uk.ac.ox.cs.rsacomb.util.{DataFactory, RSA} * (via materialization). * * @param ontology the RSA ontology the canonical model is targeting. + * @param graph the graph the canonical model will be generated into. */ -class CanonicalModel(val ontology: RSAOntology) { +class CanonicalModel(val ontology: RSAOntology, val graph: IRI) { /** Simplify conversion between OWLAPI and RDFox concepts */ import implicits.RDFox._ @@ -65,6 +67,7 @@ class CanonicalModel(val ontology: RSAOntology) { * versions need to be explicitly stated in terms of logic rules. */ val rolesAdditionalRules: List[Rule] = { + val tt = TupleTableName.create(graph.getIRI) ontology.roles .collect { case prop: OWLObjectProperty => prop } .flatMap((pred) => { @@ -83,8 +86,8 @@ class CanonicalModel(val ontology: RSAOntology) { ) ) yield Rule.create( - TupleTableAtom.rdf(varX, iri :: hSuffix, varY), - TupleTableAtom.rdf(varX, iri :: bSuffix, varY) + TupleTableAtom.create(tt, varX, iri :: hSuffix, varY), + TupleTableAtom.create(tt, varX, iri :: bSuffix, varY) ) }) } @@ -108,6 +111,8 @@ class CanonicalModel(val ontology: RSAOntology) { object CanonicalModelConverter extends RDFoxConverter { + override val graph = TupleTableName.create(CanonicalModel.this.graph.getIRI) + private def rules1( axiom: OWLSubClassOfAxiom ): Result = { @@ -115,11 +120,10 @@ class CanonicalModel(val ontology: RSAOntology) { // Fresh Variables val v0 = RSA("v0_" ++ axiom.hashed) val varX = Variable.create("X") - implicit val unfoldTerm = RSA(unfold.hashCode.toString) // TODO: use axiom.toTriple instead val atomA: TupleTableAtom = { val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI - TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) + TupleTableAtom.create(graph, varX, IRI.RDF_TYPE, cls) } val roleRf: TupleTableAtom = { val prop = @@ -132,12 +136,15 @@ class CanonicalModel(val ontology: RSAOntology) { .getFiller .asInstanceOf[OWLClass] .getIRI - TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls) + TupleTableAtom.create(graph, v0, IRI.RDF_TYPE, cls) } - val facts = unfold map RSA.In + val unfoldSet = RSA(unfold.hashCode.toString) + val facts = unfold.map(TupleTableAtom.create(graph, _, RSA.IN, unfoldSet)) + val notInX = + Negation.create(TupleTableAtom.create(graph, varX, RSA.IN, unfoldSet)) val rules = List( - Rule.create(roleRf, atomA, RSA.NotIn(varX)), - Rule.create(atomB, atomA, RSA.NotIn(varX)) + Rule.create(roleRf, atomA, notInX), + Rule.create(atomB, atomA, notInX) ) (facts, rules) } @@ -155,7 +162,7 @@ class CanonicalModel(val ontology: RSAOntology) { // Predicates def atomA(t: Term): TupleTableAtom = { val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI - TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) + TupleTableAtom.create(graph, t, IRI.RDF_TYPE, cls) } def roleRf(t1: Term, t2: Term): TupleTableAtom = super.convert(roleR, t1, t2, Forward) @@ -165,7 +172,7 @@ class CanonicalModel(val ontology: RSAOntology) { .getFiller .asInstanceOf[OWLClass] .getIRI - TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) + TupleTableAtom.create(graph, t, IRI.RDF_TYPE, cls) } //Rules List( @@ -190,7 +197,7 @@ class CanonicalModel(val ontology: RSAOntology) { // Predicates def atomA(t: Term): TupleTableAtom = { val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI - TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) + TupleTableAtom.create(graph, t, IRI.RDF_TYPE, cls) } def roleRf(t: Term): TupleTableAtom = super.convert(roleR, t, v1, Forward) @@ -200,7 +207,7 @@ class CanonicalModel(val ontology: RSAOntology) { .getFiller .asInstanceOf[OWLClass] .getIRI - TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls) + TupleTableAtom.create(graph, v1, IRI.RDF_TYPE, cls) } cycle.flatMap { x => List( diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala index 6e9a119..5a89bf9 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala @@ -48,6 +48,7 @@ import tech.oxfordsemantic.jrdfox.Prefixes import tech.oxfordsemantic.jrdfox.logic.datalog.{ Rule, TupleTableAtom, + TupleTableName, Negation, BodyFormula } @@ -91,6 +92,20 @@ object RSAOntology { /** Name of the RDFox data store used for CQ answering */ private val DataStore = "answer_computation" + /** Canonical model named graph */ + private val CanonGraph: IRI = + RDFoxUtil.getNamedGraph(DataStore, "CanonicalModel") + + /** Filtering program named graph + * + * @param query query associated with the returned named graph. + * + * @return named graph for the filtering program associated with the + * input query. + */ + private def FilterGraph(query: ConjunctiveQuery): IRI = + RDFoxUtil.getNamedGraph(DataStore, s"Filter${query.id}") + /** Filtering program for a given query * * @param query the query to derive the filtering program @@ -343,11 +358,12 @@ class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File]) private val topAxioms: List[Rule] = { val varX = Variable.create("X") val varY = Variable.create("Y") + val graph = TupleTableName.create(RSAOntology.CanonGraph.getIRI) concepts .map(c => { Rule.create( - RSA.Thing(varX), - TupleTableAtom.rdf(varX, IRI.RDF_TYPE, c.getIRI) + TupleTableAtom.create(graph, varX, IRI.RDF_TYPE, IRI.THING), + TupleTableAtom.create(graph, varX, IRI.RDF_TYPE, c.getIRI) ) }) ++ roles.map(r => { val name = r match { @@ -356,8 +372,11 @@ class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File]) x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse } Rule.create( - List(RSA.Thing(varX), RSA.Thing(varY)), - List(TupleTableAtom.rdf(varX, name, varY)) + List( + TupleTableAtom.create(graph, varX, IRI.RDF_TYPE, IRI.THING), + TupleTableAtom.create(graph, varY, IRI.RDF_TYPE, IRI.THING) + ), + List(TupleTableAtom.create(graph, varX, name, varY)) ) }) } @@ -382,23 +401,31 @@ class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File]) val varX = Variable.create("X") val varY = Variable.create("Y") val varZ = Variable.create("Z") - List( + val graph = TupleTableName.create(RSAOntology.CanonGraph.getIRI) + // Equality properties + val properties = List( // Reflexivity - Rule.create(RSA.Congruent(varX, varX), RSA.Thing(varX)), + Rule.create( + TupleTableAtom.create(graph, varX, RSA.CONGRUENT, varX), + TupleTableAtom.create(graph, varX, IRI.RDF_TYPE, IRI.THING) + ), // Simmetry - Rule.create(RSA.Congruent(varY, varX), RSA.Congruent(varX, varY)), + Rule.create( + TupleTableAtom.create(graph, varY, RSA.CONGRUENT, varX), + TupleTableAtom.create(graph, varX, RSA.CONGRUENT, varY) + ), // Transitivity Rule.create( - RSA.Congruent(varX, varZ), - RSA.Congruent(varX, varY), - RSA.Congruent(varY, varZ) + TupleTableAtom.create(graph, varX, RSA.CONGRUENT, varZ), + TupleTableAtom.create(graph, varX, RSA.CONGRUENT, varY), + TupleTableAtom.create(graph, varY, RSA.CONGRUENT, varZ) ) ) } /** Canonical model of the ontology */ lazy val canonicalModel = Logger.timed( - new CanonicalModel(this), + new CanonicalModel(this, RSAOntology.CanonGraph), "Generating canonical model program", Logger.DEBUG ) @@ -505,19 +532,18 @@ class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File]) * @return a collection of answers for each query. */ def ask(queries: Seq[ConjunctiveQuery]): Seq[ConjunctiveQueryAnswers] = { + /* Open connection with RDFox server */ val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) - val canonNamedGraph = "http://cs.ox.ac.uk/isg/RSAComb#CanonicalModel" - // Create a new NamedGraph for the canonical model - data.createTupleTable(canonNamedGraph, Map("type" -> "named-graph").asJava) /* Upload data from data file */ - RDFoxUtil.addData(canonNamedGraph, data, datafiles: _*) + RDFoxUtil.addData(data, RSAOntology.CanonGraph, datafiles: _*) /* Top / equality axiomatization */ RDFoxUtil.addRules(data, topAxioms ++ equalityAxioms) /* Generate `named` predicates */ + // TODO: do I need both to generate all NAMED atoms? RDFoxUtil.addFacts( - canonNamedGraph, data, + RSAOntology.CanonGraph, (individuals ++ literals) map RSA.Named ) data.evaluateUpdate( @@ -525,9 +551,9 @@ class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File]) RSA.Prefixes, s""" INSERT { - GRAPH <$canonNamedGraph> { ?X a rsa:Named } + GRAPH ${RSAOntology.CanonGraph} { ?X a ${RSA.NAMED} } } WHERE { - GRAPH <$canonNamedGraph> { ?X a owl:Thing } + GRAPH ${RSAOntology.CanonGraph} { ?X a ${IRI.THING} } } """, new java.util.HashMap[String, String] @@ -538,7 +564,7 @@ class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File]) RDFoxUtil.addRules(data, this.canonicalModel.rules) Logger print s"Canonical model facts: ${this.canonicalModel.facts.length}" - RDFoxUtil.addFacts(canonNamedGraph, data, this.canonicalModel.facts) + RDFoxUtil.addFacts(data, RSAOntology.CanonGraph, this.canonicalModel.facts) queries map { query => { diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala index 276ee1a..2f48798 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala @@ -24,7 +24,8 @@ import tech.oxfordsemantic.jrdfox.logic.datalog.{ BindAtom, BodyFormula, Rule, - TupleTableAtom + TupleTableAtom, + TupleTableName } import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, FunctionCall} import uk.ac.ox.cs.rsacomb.RSAOntology @@ -59,6 +60,10 @@ trait RDFoxConverter { private val manager = OWLManager.createOWLOntologyManager() private val factory = manager.getOWLDataFactory() + /** Default named graph to be used when generating new atoms */ + val graph: TupleTableName = + TupleTableName.create("http://oxfordsemantic.tech/RDFox#DefaultTriples") + /** Represents the result of the conversion of a * [[org.semanticweb.owlapi.model.OWLClassExpression OWLClassExpression]]. * @@ -193,7 +198,8 @@ trait RDFoxConverter { .flatMap((cls) => convert(cls, term, unsafe, NoSkolem, suffix)(fresh)._1 ) - val bottom = TupleTableAtom.rdf(term, IRI.RDF_TYPE, IRI.NOTHING) + val bottom = + TupleTableAtom.create(graph, term, IRI.RDF_TYPE, IRI.NOTHING) ResultR(List(Rule.create(bottom, body: _*))) } @@ -310,7 +316,7 @@ trait RDFoxConverter { */ case e: OWLClass => { val iri: IRI = if (e.isTopEntity()) IRI.THING else e.getIRI - val atom = TupleTableAtom.rdf(term, IRI.RDF_TYPE, iri) + val atom = TupleTableAtom.create(graph, term, IRI.RDF_TYPE, iri) (List(atom), List()) } @@ -340,7 +346,8 @@ trait RDFoxConverter { .collect { case x: OWLNamedIndividual => x } if (named.length != 1) throw new RuntimeException(s"Class expression '$e' has arity != 1.") - val atom = RSA.Congruent(term, named.head.getIRI) + val atom = + TupleTableAtom.create(graph, term, RSA.CONGRUENT, named.head.getIRI) (List(atom), List()) } @@ -412,7 +419,7 @@ trait RDFoxConverter { val (res, ext) = vars.map(convert(cls, _, unsafe, skolem, suffix)(fresh)).unzip val props = vars.map(convert(role, term, _, suffix)(fresh)) - val eq = RSA.Congruent(y, z) + val eq = TupleTableAtom.create(graph, y, RSA.CONGRUENT, z) (List(eq), res.flatten ++ props) } @@ -515,7 +522,7 @@ trait RDFoxConverter { */ case e: OWLObjectProperty => { val role = IRI.create(e.getIRI.getIRIString :: suffix) - TupleTableAtom.rdf(term1, role, term2) + TupleTableAtom.create(graph, term1, role, term2) } /** Inverse of a named role/property @@ -555,7 +562,7 @@ trait RDFoxConverter { */ case e: OWLDataProperty => { val role = IRI.create(e.getIRI.getIRIString :: suffix) - TupleTableAtom.rdf(term1, role, term2) + TupleTableAtom.create(graph, term1, role, term2) } /** The infamous impossible case. diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala index 795e039..ff48f1f 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala @@ -51,19 +51,14 @@ object RSAAtom { import RDFox._ import JavaCollections._ - val name: String = atom.getTupleTableName.getName + val tt: TupleTableName = atom.getTupleTableName val args: List[Term] = atom.getArguments - val isRDF: Boolean = - name == "http://oxfordsemantic.tech/RDFox#DefaultTriples" + val isRDF: Boolean = atom.getArguments.length == 3 - val isClassAssertion: Boolean = { - isRDF && { - val pred = atom.getArguments.get(1) - pred == IRI.RDF_TYPE - } - } + val isClassAssertion: Boolean = + isRDF && atom.getArguments.get(1) == IRI.RDF_TYPE val isRoleAssertion: Boolean = isRDF && !isClassAssertion @@ -77,18 +72,15 @@ object RSAAtom { case iri: IRI => IRI.create(iri.getIRI :: suffix) case other => other } - TupleTableAtom.rdf(subj, pred, obj1) + TupleTableAtom.create(tt, subj, pred, obj1) } else { val pred1 = pred match { case iri: IRI => IRI.create(iri.getIRI :: suffix) case other => other } - TupleTableAtom.rdf(subj, pred1, obj) + TupleTableAtom.create(tt, subj, pred1, obj) } - } else { - val ttname = TupleTableName.create(name :: suffix) - TupleTableAtom.create(ttname, atom.getArguments()) - } + } else atom def reified(implicit fresh: DataFactory diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala index 6f5ff31..568858c 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala @@ -17,6 +17,7 @@ package uk.ac.ox.cs.rsacomb.util import java.io.{OutputStream, File, StringReader} +import scala.collection.JavaConverters._ import tech.oxfordsemantic.jrdfox.Prefixes import tech.oxfordsemantic.jrdfox.client.{ ComponentInfo, @@ -38,7 +39,8 @@ import tech.oxfordsemantic.jrdfox.logic.expression.{ Literal, Resource, Variable, - Term + Term, + IRI } import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery @@ -92,6 +94,22 @@ object RDFoxUtil { (server, data) } + /** Get the IRI of a named graph (creating it if necessary) + * + * @param datastore name of the datastore to perform the action in. + * @param name name of the named graph. + * + * @return the full IRI for the (new) named graph. + */ + def getNamedGraph(datastore: String, name: String): IRI = { + val graph = RSA(name) + val (server, data) = openConnection(datastore) + if (!data.containsTupleTable(graph.getIRI)) + data.createTupleTable(graph.getIRI, Map("type" -> "named-graph").asJava) + RDFoxUtil.closeConnection(server, data) + return graph + } + /** Create a built-in `rdfox:SKOLEM` TupleTableAtom. */ def skolem(name: String, terms: Term*): TupleTableAtom = TupleTableAtom.create( @@ -143,14 +161,14 @@ object RDFoxUtil { * @param facts collection of facts to be added to the data store */ def addFacts( - graph: String, data: DataStoreConnection, + graph: IRI, facts: Seq[TupleTableAtom] ): Unit = Logger.timed( if (facts.length > 0) { data.importData( - graph, + graph.getIRI, UpdateType.ADDITION, RSA.Prefixes, facts @@ -165,12 +183,13 @@ object RDFoxUtil { /** Imports a sequence of files directly into a datastore. * * @param data datastore connection. + * @param graph named graph where the data should be uploaded * @param files sequence of files to upload. */ - def addData(graph: String, data: DataStoreConnection, files: File*): Unit = + def addData(data: DataStoreConnection, graph: IRI, files: File*): Unit = Logger.timed( files.foreach { - data.importData(graph, UpdateType.ADDITION, RSA.Prefixes, _) + data.importData(graph.getIRI, UpdateType.ADDITION, RSA.Prefixes, _) }, "Loading data files", Logger.DEBUG diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala index 8b341ba..96d3aa8 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala @@ -42,32 +42,29 @@ import scala.collection.JavaConverters._ object RSA { + /** Set of default prefixes to be included in all datastore operations */ val Prefixes: Prefixes = new Prefixes() - Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/") + Prefixes.declarePrefix("rsacomb:", "http://www.cs.ox.ac.uk/isg/RSAComb#") + Prefixes.declarePrefix("rdfox:", "http://oxfordsemantic.tech/RDFox#") Prefixes.declarePrefix("owl:", "http://www.w3.org/2002/07/owl#") - val CONGRUENT = RSA("congruent") - val NAMED = RSA("Named") - - private def atom(name: IRI, vars: List[Term]): TupleTableAtom = - TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*) - - def E(t1: Term, t2: Term) = - TupleTableAtom.rdf(t1, RSA("E"), t2) - - def PE(t1: Term, t2: Term) = - TupleTableAtom.rdf(t1, RSA("PE"), t2) + /** Creates a `rsacomb:` IRI */ + def apply(name: Any): IRI = + IRI.create( + Prefixes.getPrefixIRIsByPrefixName.get("rsacomb:").getIRI + name.toString + ) - def U(t: Term) = - TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("U")) + val NAMED = RSA("Named") + val CONGRUENT = RSA("congruent") + val IN = RSA("In") - def In(t: Term)(implicit set: Term) = - TupleTableAtom.rdf(t, RSA("In"), set) + // def In(t: Term)(implicit set: Term) = + // TupleTableAtom.rdf(t, RSA("In"), set) - def NotIn(t: Term)(implicit set: Term) = Negation.create(In(t)(set)) + // def NotIn(t: Term)(implicit set: Term) = Negation.create(In(t)(set)) - def Congruent(t1: Term, t2: Term) = - TupleTableAtom.rdf(t1, RSA("congruent"), t2) + // def Congruent(t1: Term, t2: Term) = + // TupleTableAtom.rdf(t1, RSA("congruent"), t2) def QM(implicit q: ConjunctiveQuery) = atom(RSA("QM"), q.answer ::: q.bounded) @@ -104,8 +101,18 @@ object RSA { atom(RSA("Ans"), q.answer) } - def apply(name: Any): IRI = - IRI.create( - Prefixes.getPrefixIRIsByPrefixName.get("rsa:").getIRI + name.toString - ) + /* TODO: review after reworking the dependency graph construction */ + + // private def atom(name: IRI, vars: List[Term]): TupleTableAtom = + // TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*) + + def E(t1: Term, t2: Term) = + TupleTableAtom.rdf(t1, RSA("E"), t2) + + def PE(t1: Term, t2: Term) = + TupleTableAtom.rdf(t1, RSA("PE"), t2) + + def U(t: Term) = + TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("U")) + } -- cgit v1.2.3