From 32fe8f94255b913c570275043a3c056eaa4ec07b Mon Sep 17 00:00:00 2001 From: Federico Igne Date: Wed, 11 May 2022 12:35:56 +0100 Subject: Implement stub for query answering procedure --- src/main/scala/uk/ac/ox/cs/acqua/Main.scala | 82 ++++++-- .../ox/cs/acqua/implicits/PagodaConverters.scala | 31 +++ .../ac/ox/cs/acqua/reasoner/RSAQueryReasoner.scala | 78 ++++++++ .../scala/uk/ac/ox/cs/acqua/util/AcquaConfig.scala | 211 +++++++++++++++++++++ 4 files changed, 383 insertions(+), 19 deletions(-) create mode 100644 src/main/scala/uk/ac/ox/cs/acqua/implicits/PagodaConverters.scala create mode 100644 src/main/scala/uk/ac/ox/cs/acqua/reasoner/RSAQueryReasoner.scala create mode 100644 src/main/scala/uk/ac/ox/cs/acqua/util/AcquaConfig.scala (limited to 'src') diff --git a/src/main/scala/uk/ac/ox/cs/acqua/Main.scala b/src/main/scala/uk/ac/ox/cs/acqua/Main.scala index 749a492..e8cfeb7 100644 --- a/src/main/scala/uk/ac/ox/cs/acqua/Main.scala +++ b/src/main/scala/uk/ac/ox/cs/acqua/Main.scala @@ -1,16 +1,41 @@ +/* + * Copyright 2021,2022 KRR Oxford + * + * 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.ox.cs.acqua +import uk.ac.ox.cs.rsacomb.converter.Normalizer import uk.ac.ox.cs.rsacomb.ontology.Ontology +import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil,RSA} import uk.ac.ox.cs.pagoda.owl.OWLHelper -import uk.ac.ox.cs.pagoda.reasoner.{ELHOQueryReasoner,QueryReasoner,RLQueryReasoner} -// import uk.ac.ox.cs.pagoda.Pagoda -// import uk.ac.ox.cs.pagoda.util.PagodaProperties; +import uk.ac.ox.cs.pagoda.reasoner.{ELHOQueryReasoner,MyQueryReasoner,QueryReasoner,RLQueryReasoner} +import uk.ac.ox.cs.pagoda.util.PagodaProperties; +import uk.ac.ox.cs.pagoda.util.Utility; + +import uk.ac.ox.cs.acqua.reasoner.RSAQueryReasoner +import uk.ac.ox.cs.acqua.util.AcquaConfig object Acqua extends App { + val config = AcquaConfig.parse(args.toList) + AcquaConfig describe config val ontopath = os.Path("tests/lubm/univ-bench.owl", base = os.pwd) - val ontology = Ontology(ontopath, List.empty) + val ontology = Ontology(ontopath, List.empty).normalize(new Normalizer) + + val properties = new PagodaProperties() val performMultiStages = true val considerEqualities = true @@ -20,22 +45,41 @@ object Acqua extends App { } else if (OWLHelper.isInELHO(ontology.origin)) { new ELHOQueryReasoner(); } else if (ontology.isRSA) { - // Use combined approach for RSA - ??? + new RSAQueryReasoner(ontology) } else { - new MyQueryReasoner(performMultiStages, considerEqualities); + // Return ACQuA reasoner + // new MyQueryReasoner(performMultiStages, considerEqualities); + ??? + } + + /* Preprocessing */ + reasoner.setProperties(properties) + reasoner.loadOntology(ontology.origin) + reasoner.importData(properties.getDataPath()) + if (reasoner.preprocess()) { + Utility.logInfo("The ontology is consistent!"); + } + else { + Utility.logInfo("The ontology is inconsistent!"); + reasoner.dispose(); } - // else - // switch(type) { - // case RLU: - // reasoner = new RLUQueryReasoner(performMultiStages, considerEqualities); - // break; - // case ELHOU: - // reasoner = new ELHOUQueryReasoner(performMultiStages, considerEqualities); - // break; - // default: - // reasoner = new MyQueryReasoner(performMultiStages, considerEqualities); - // } - // return reasoner; + if (config contains 'queries) { + val queries = + RDFoxUtil.loadQueriesFromFiles( + config('queries).get[List[os.Path]], + RSA.Prefixes + ) + // for(String queryFile : properties.getQueryPath().split(";")) { + // Collection queryRecords = pagoda.getQueryManager().collectQueryRecords(queryFile); + // pagoda.evaluate(queryRecords); + } } + + + // /* Perform query answering */ + // val answers = rsa ask queries + + // /* Perform logging */ + // Logger write answers + // Logger.generateSimulationScripts(datapath, queries) diff --git a/src/main/scala/uk/ac/ox/cs/acqua/implicits/PagodaConverters.scala b/src/main/scala/uk/ac/ox/cs/acqua/implicits/PagodaConverters.scala new file mode 100644 index 0000000..c7c582d --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/acqua/implicits/PagodaConverters.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2021,2022 KRR Oxford + * + * 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.ox.cs.acqua.implicits + +import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery +import uk.ac.ox.cs.pagoda.query.QueryRecord + +object PagodaConverters { + + implicit def queryRecord2conjuctiveQuery(q: QueryRecord): ConjunctiveQuery = ??? + + implicit def conjunctiveQuery2queryRecord(q: ConjunctiveQuery): QueryRecord = ??? + +} + + + diff --git a/src/main/scala/uk/ac/ox/cs/acqua/reasoner/RSAQueryReasoner.scala b/src/main/scala/uk/ac/ox/cs/acqua/reasoner/RSAQueryReasoner.scala new file mode 100644 index 0000000..6b98d79 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/acqua/reasoner/RSAQueryReasoner.scala @@ -0,0 +1,78 @@ +/* + * Copyright 2021,2022 KRR Oxford + * + * 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.ox.cs.acqua.reasoner + +import org.semanticweb.owlapi.model.OWLOntology +import uk.ac.ox.cs.rsacomb.RSAOntology +import uk.ac.ox.cs.rsacomb.approximation.{Approximation,Lowerbound} +import uk.ac.ox.cs.rsacomb.ontology.Ontology +import uk.ac.ox.cs.pagoda.query.QueryRecord +import uk.ac.ox.cs.pagoda.reasoner.QueryReasoner + +class RSAQueryReasoner(val origin: Ontology) extends QueryReasoner { + + /* Implicit compatibility between PAGOdA and RSAComb types */ + import uk.ac.ox.cs.acqua.implicits.PagodaConverters._ + + /** This class is instantiated when the input ontology is RSA. + * Approximation (via any algorithm with RSAOntology as target) + * doesn't perform anything, but is useful to turn a generic + * [[uk.ac.ox.cs.rsacomb.ontology.Ontology]] into an + * [[uk.ac.ox.cs.rsacomb.RSAOntology]]. + */ + private val toRSA: Approximation[RSAOntology] = new Lowerbound + val rsa: RSAOntology = origin approximate toRSA + + /** Doesn't perform any action. + * + * @note Implemented for compatibility with other reasoners. + */ + def loadOntology(ontology: OWLOntology): Unit = { + /* Nothing to do */ + } + + /** Check consistency and returns whether the ontology is RSA. + * + * Preprocessing is performed on instance creation, so no actual work + * is being done here. + * + * @note Implemented for compatibility with other reasoners. + */ + def preprocess(): Boolean = { + origin.isRSA + } + + /** Check consistency and returns whether the ontology is RSA. + * + * Preprocessing is performed on instance creation, along with + * consistency checking, so no actual work is being done here. + * + * @note Implemented for compatibility with other reasoners. + */ + def isConsistent(): Boolean = { + origin.isRSA + } + + // TODO: probably need to override `evaluate` on multiple queries + def evaluate(query: QueryRecord): Unit = { + rsa ask query + } + + def evaluateUpper(record: QueryRecord): Unit= { + ??? + } +} diff --git a/src/main/scala/uk/ac/ox/cs/acqua/util/AcquaConfig.scala b/src/main/scala/uk/ac/ox/cs/acqua/util/AcquaConfig.scala new file mode 100644 index 0000000..675a592 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/acqua/util/AcquaConfig.scala @@ -0,0 +1,211 @@ +/* + * Copyright 2021,2022 KRR Oxford + * + * 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.ox.cs.acqua.util + +import scala.collection.mutable.Map + +//import util.Logger + +case class AcquaOption[+T](opt: T) { + def get[T]: T = opt.asInstanceOf[T] +} + +object AcquaConfig { + type Config = Map[Symbol, AcquaOption[Any]] + + private implicit def toRSAOption[T](opt: T) = AcquaOption[T](opt) + + /** Help message */ + private val help: String = """ + + rsacomb - combined approach for CQ answering for RSA ontologies. + + USAGE + rsacomb [OPTIONS] [ ...] + + -h | -? | --help + print this help message + + -l | --logger + specify the logger verbosity. Values are: quiet, normal (default), + debug, verbose. + + -a | --answers + path to the output file for the answers to the query (in JSON + format) + + -q | --queries + path to a file containing a single SPARQL query. If no query + is provided, only the approximation to RSA will be performed. + + -o | --ontology + ontology file in OWL format. + + -d | --data + data files to be used alongside the ontology file. If a + directory is provided, all files in the directory (recursively) + will be considered. + + -x | --approximation + values available are "lowerupper" or "upperbound" corresponding + to the two algorithms for ontology approximation shipping by + default with RSAComb. You will need to change the source code to + expose custom approximation modules through the CLI. + + -t | --transitive + "upperbound" approximation specific option. Include property chain + axioms (and hence the more common transitive properties) when + computing the canonical model. + + """ + + /** Default config values */ + private val default: Config = Map( + 'transitive -> false, + 'data -> List.empty[os.Path], + 'approximation -> 'lowerbound + ) + + /** Parse a string into a path. + * + * @throws an [[IllegalArgumentException]] on malformed path. + */ + private def getPath(str: String): os.Path = + try { + os.Path(str, base = os.pwd) + } catch { + case e: IllegalArgumentException => + exit(s"'$str' is not a well formed path.") + } + + /** Utility to exit the program with a custom message on stderr. + * + * The program will exit with error after printing the help message. + * + * @param msg message printed to stderr. + * @param errno error code number (defaults to 1) + */ + private def exit(msg: String, errno: Int = 1): Nothing = { + System.err.println(msg) + System.err.println(help) + sys.exit(errno) + } + + /** Parse arguments with default options. + * + * @param args arguments list + * @return map of config options + */ + def parse(args: List[String]): Config = parse(args, default) + + /** Parse arguments. + * + * @param args arguments list + * @param config default configuration + * @return map of config options + */ + def parse(args: List[String], config: Config): Config = { + args match { + case Nil => finalise(config) + case flag @ ("-h" | "-?" | "--help") :: _ => { + println(help) + sys.exit(0) + } + case flag @ ("-l" | "--logger") :: _level :: tail => { + // val level = _level match { + // case "quiet" => Logger.QUIET + // case "debug" => Logger.DEBUG + // case "verbose" => Logger.VERBOSE + // case _ => Logger.NORMAL + // } + parse(tail, config += ('logger -> _level)) + } + case flag @ ("-a" | "--answers") :: answers :: tail => + parse(tail, config += ('answers -> getPath(answers))) + case flag @ ("-x" | "--approximation") :: approx :: tail => { + parse(tail, config += ('approximation -> Symbol(approx))) + } + case flag @ ("-t" | "--transitive") :: tail => + parse(tail, config += ('transitive -> true)) + case flag @ ("-q" | "--queries") :: _query :: tail => { + val query = getPath(_query) + val files = + if (os.isFile(query)) + List(query) + else if (os.isDir(query)) + os.walk(query).filter(os.isFile).toList + else + exit(s"'${_query}' is not a valid path.") + parse(tail, config += ('queries -> files)) + } + case flag @ ("-o" | "--ontology") :: _ontology :: tail => { + val ontology = getPath(_ontology) + if (!os.isFile(ontology)) + exit(s"'${_ontology}' is not a valid filename.") + parse(tail, config += ('ontology -> ontology)) + } + case flag @ ("-d" | "--data") :: _data :: tail => { + val data = getPath(_data) + val files = + if (os.isFile(data)) + List(data) + else if (os.isDir(data)) { + os.walk(data).filter(os.isFile).toList + }else + exit(s"'${_data}' is not a valid path.") + parse(tail, config += ('data -> files)) + } + case a => exit(s"Invalid sequence of arguments '${a.mkString(" ")}'.") + } + } + + /** Perform final checks on parsed options. + * + * @param config a parsed configuration + * @returns the input configuration, unchanged + */ + private def finalise(config: Config): Config = { + if (!config.contains('ontology)) + exit("The following flag is mandatory: '-o' or '--ontology'.") + config + } + + /** Generate summary of a config object suitable for printing + * + * @param config a parsed configuration + * @returns a string describing the configuration + */ + def describe(config: Config): Unit = { + // config foreach { case (k,v) => k match { + // case 'logger => Logger print s"Logger level: ${v.get[Logger.Level]}" + // case 'ontology => Logger print s"Ontology file: ${v.get[os.Path]}" + // case 'data => { + // val paths = v.get[List[os.Path]] + // val ellipsis = if (paths.length > 1) " [...]" else "" + // Logger print s"Data files: ${paths.head}$ellipsis" + // } + // case 'queries => { + // val paths = v.get[List[os.Path]] + // val ellipsis = if (paths.length > 1) " [...]" else "" + // Logger print s"Query files: ${paths.head}$ellipsis" + // } + // case 'answers => Logger print s"Path to answers: ${v.get[os.Path]}" + // case 'approximation => Logger print s"Applied approximation: ${v.get[Symbol].name}" + // case 'transitive => Logger print s"Include property chain axioms: ${v.get[Boolean]}" + // }} + } +} -- cgit v1.2.3