diff options
| author | Federico Igne <federico.igne@cs.ox.ac.uk> | 2022-05-11 12:35:56 +0100 |
|---|---|---|
| committer | Federico Igne <federico.igne@cs.ox.ac.uk> | 2022-05-11 12:35:56 +0100 |
| commit | 32fe8f94255b913c570275043a3c056eaa4ec07b (patch) | |
| tree | 2a370c3998db1eab758f214f16f2cebc3fd2ef34 /src/main/scala/uk/ac/ox/cs/acqua | |
| parent | eaa8ed73aec7ca0ba02eb3e1c954388b8dbfc2cd (diff) | |
| download | ACQuA-32fe8f94255b913c570275043a3c056eaa4ec07b.tar.gz ACQuA-32fe8f94255b913c570275043a3c056eaa4ec07b.zip | |
Implement stub for query answering procedure
Diffstat (limited to 'src/main/scala/uk/ac/ox/cs/acqua')
4 files changed, 383 insertions, 19 deletions
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 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2021,2022 KRR Oxford | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 1 | package uk.ac.ox.cs.acqua | 17 | package uk.ac.ox.cs.acqua |
| 2 | 18 | ||
| 19 | import uk.ac.ox.cs.rsacomb.converter.Normalizer | ||
| 3 | import uk.ac.ox.cs.rsacomb.ontology.Ontology | 20 | import uk.ac.ox.cs.rsacomb.ontology.Ontology |
| 21 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil,RSA} | ||
| 4 | 22 | ||
| 5 | import uk.ac.ox.cs.pagoda.owl.OWLHelper | 23 | import uk.ac.ox.cs.pagoda.owl.OWLHelper |
| 6 | import uk.ac.ox.cs.pagoda.reasoner.{ELHOQueryReasoner,QueryReasoner,RLQueryReasoner} | 24 | import uk.ac.ox.cs.pagoda.reasoner.{ELHOQueryReasoner,MyQueryReasoner,QueryReasoner,RLQueryReasoner} |
| 7 | // import uk.ac.ox.cs.pagoda.Pagoda | 25 | import uk.ac.ox.cs.pagoda.util.PagodaProperties; |
| 8 | // import uk.ac.ox.cs.pagoda.util.PagodaProperties; | 26 | import uk.ac.ox.cs.pagoda.util.Utility; |
| 27 | |||
| 28 | import uk.ac.ox.cs.acqua.reasoner.RSAQueryReasoner | ||
| 29 | import uk.ac.ox.cs.acqua.util.AcquaConfig | ||
| 9 | 30 | ||
| 10 | object Acqua extends App { | 31 | object Acqua extends App { |
| 32 | val config = AcquaConfig.parse(args.toList) | ||
| 33 | AcquaConfig describe config | ||
| 11 | 34 | ||
| 12 | val ontopath = os.Path("tests/lubm/univ-bench.owl", base = os.pwd) | 35 | val ontopath = os.Path("tests/lubm/univ-bench.owl", base = os.pwd) |
| 13 | val ontology = Ontology(ontopath, List.empty) | 36 | val ontology = Ontology(ontopath, List.empty).normalize(new Normalizer) |
| 37 | |||
| 38 | val properties = new PagodaProperties() | ||
| 14 | 39 | ||
| 15 | val performMultiStages = true | 40 | val performMultiStages = true |
| 16 | val considerEqualities = true | 41 | val considerEqualities = true |
| @@ -20,22 +45,41 @@ object Acqua extends App { | |||
| 20 | } else if (OWLHelper.isInELHO(ontology.origin)) { | 45 | } else if (OWLHelper.isInELHO(ontology.origin)) { |
| 21 | new ELHOQueryReasoner(); | 46 | new ELHOQueryReasoner(); |
| 22 | } else if (ontology.isRSA) { | 47 | } else if (ontology.isRSA) { |
| 23 | // Use combined approach for RSA | 48 | new RSAQueryReasoner(ontology) |
| 24 | ??? | ||
| 25 | } else { | 49 | } else { |
| 26 | new MyQueryReasoner(performMultiStages, considerEqualities); | 50 | // Return ACQuA reasoner |
| 51 | // new MyQueryReasoner(performMultiStages, considerEqualities); | ||
| 52 | ??? | ||
| 53 | } | ||
| 54 | |||
| 55 | /* Preprocessing */ | ||
| 56 | reasoner.setProperties(properties) | ||
| 57 | reasoner.loadOntology(ontology.origin) | ||
| 58 | reasoner.importData(properties.getDataPath()) | ||
| 59 | if (reasoner.preprocess()) { | ||
| 60 | Utility.logInfo("The ontology is consistent!"); | ||
| 61 | } | ||
| 62 | else { | ||
| 63 | Utility.logInfo("The ontology is inconsistent!"); | ||
| 64 | reasoner.dispose(); | ||
| 27 | } | 65 | } |
| 28 | // else | ||
| 29 | // switch(type) { | ||
| 30 | // case RLU: | ||
| 31 | // reasoner = new RLUQueryReasoner(performMultiStages, considerEqualities); | ||
| 32 | // break; | ||
| 33 | // case ELHOU: | ||
| 34 | // reasoner = new ELHOUQueryReasoner(performMultiStages, considerEqualities); | ||
| 35 | // break; | ||
| 36 | // default: | ||
| 37 | // reasoner = new MyQueryReasoner(performMultiStages, considerEqualities); | ||
| 38 | // } | ||
| 39 | // return reasoner; | ||
| 40 | 66 | ||
| 67 | if (config contains 'queries) { | ||
| 68 | val queries = | ||
| 69 | RDFoxUtil.loadQueriesFromFiles( | ||
| 70 | config('queries).get[List[os.Path]], | ||
| 71 | RSA.Prefixes | ||
| 72 | ) | ||
| 73 | // for(String queryFile : properties.getQueryPath().split(";")) { | ||
| 74 | // Collection<QueryRecord> queryRecords = pagoda.getQueryManager().collectQueryRecords(queryFile); | ||
| 75 | // pagoda.evaluate(queryRecords); | ||
| 76 | } | ||
| 41 | } | 77 | } |
| 78 | |||
| 79 | |||
| 80 | // /* Perform query answering */ | ||
| 81 | // val answers = rsa ask queries | ||
| 82 | |||
| 83 | // /* Perform logging */ | ||
| 84 | // Logger write answers | ||
| 85 | // 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 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2021,2022 KRR Oxford | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package uk.ac.ox.cs.acqua.implicits | ||
| 18 | |||
| 19 | import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery | ||
| 20 | import uk.ac.ox.cs.pagoda.query.QueryRecord | ||
| 21 | |||
| 22 | object PagodaConverters { | ||
| 23 | |||
| 24 | implicit def queryRecord2conjuctiveQuery(q: QueryRecord): ConjunctiveQuery = ??? | ||
| 25 | |||
| 26 | implicit def conjunctiveQuery2queryRecord(q: ConjunctiveQuery): QueryRecord = ??? | ||
| 27 | |||
| 28 | } | ||
| 29 | |||
| 30 | |||
| 31 | |||
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 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2021,2022 KRR Oxford | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package uk.ac.ox.cs.acqua.reasoner | ||
| 18 | |||
| 19 | import org.semanticweb.owlapi.model.OWLOntology | ||
| 20 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
| 21 | import uk.ac.ox.cs.rsacomb.approximation.{Approximation,Lowerbound} | ||
| 22 | import uk.ac.ox.cs.rsacomb.ontology.Ontology | ||
| 23 | import uk.ac.ox.cs.pagoda.query.QueryRecord | ||
| 24 | import uk.ac.ox.cs.pagoda.reasoner.QueryReasoner | ||
| 25 | |||
| 26 | class RSAQueryReasoner(val origin: Ontology) extends QueryReasoner { | ||
| 27 | |||
| 28 | /* Implicit compatibility between PAGOdA and RSAComb types */ | ||
| 29 | import uk.ac.ox.cs.acqua.implicits.PagodaConverters._ | ||
| 30 | |||
| 31 | /** This class is instantiated when the input ontology is RSA. | ||
| 32 | * Approximation (via any algorithm with RSAOntology as target) | ||
| 33 | * doesn't perform anything, but is useful to turn a generic | ||
| 34 | * [[uk.ac.ox.cs.rsacomb.ontology.Ontology]] into an | ||
| 35 | * [[uk.ac.ox.cs.rsacomb.RSAOntology]]. | ||
| 36 | */ | ||
| 37 | private val toRSA: Approximation[RSAOntology] = new Lowerbound | ||
| 38 | val rsa: RSAOntology = origin approximate toRSA | ||
| 39 | |||
| 40 | /** Doesn't perform any action. | ||
| 41 | * | ||
| 42 | * @note Implemented for compatibility with other reasoners. | ||
| 43 | */ | ||
| 44 | def loadOntology(ontology: OWLOntology): Unit = { | ||
| 45 | /* Nothing to do */ | ||
| 46 | } | ||
| 47 | |||
| 48 | /** Check consistency and returns whether the ontology is RSA. | ||
| 49 | * | ||
| 50 | * Preprocessing is performed on instance creation, so no actual work | ||
| 51 | * is being done here. | ||
| 52 | * | ||
| 53 | * @note Implemented for compatibility with other reasoners. | ||
| 54 | */ | ||
| 55 | def preprocess(): Boolean = { | ||
| 56 | origin.isRSA | ||
| 57 | } | ||
| 58 | |||
| 59 | /** Check consistency and returns whether the ontology is RSA. | ||
| 60 | * | ||
| 61 | * Preprocessing is performed on instance creation, along with | ||
| 62 | * consistency checking, so no actual work is being done here. | ||
| 63 | * | ||
| 64 | * @note Implemented for compatibility with other reasoners. | ||
| 65 | */ | ||
| 66 | def isConsistent(): Boolean = { | ||
| 67 | origin.isRSA | ||
| 68 | } | ||
| 69 | |||
| 70 | // TODO: probably need to override `evaluate` on multiple queries | ||
| 71 | def evaluate(query: QueryRecord): Unit = { | ||
| 72 | rsa ask query | ||
| 73 | } | ||
| 74 | |||
| 75 | def evaluateUpper(record: QueryRecord): Unit= { | ||
| 76 | ??? | ||
| 77 | } | ||
| 78 | } | ||
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 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2021,2022 KRR Oxford | ||
| 3 | * | ||
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | * you may not use this file except in compliance with the License. | ||
| 6 | * You may obtain a copy of the License at | ||
| 7 | * | ||
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | * | ||
| 10 | * Unless required by applicable law or agreed to in writing, software | ||
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | * See the License for the specific language governing permissions and | ||
| 14 | * limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | package uk.ac.ox.cs.acqua.util | ||
| 18 | |||
| 19 | import scala.collection.mutable.Map | ||
| 20 | |||
| 21 | //import util.Logger | ||
| 22 | |||
| 23 | case class AcquaOption[+T](opt: T) { | ||
| 24 | def get[T]: T = opt.asInstanceOf[T] | ||
| 25 | } | ||
| 26 | |||
| 27 | object AcquaConfig { | ||
| 28 | type Config = Map[Symbol, AcquaOption[Any]] | ||
| 29 | |||
| 30 | private implicit def toRSAOption[T](opt: T) = AcquaOption[T](opt) | ||
| 31 | |||
| 32 | /** Help message */ | ||
| 33 | private val help: String = """ | ||
| 34 | |||
| 35 | rsacomb - combined approach for CQ answering for RSA ontologies. | ||
| 36 | |||
| 37 | USAGE | ||
| 38 | rsacomb [OPTIONS] <ontology> [<data> ...] | ||
| 39 | |||
| 40 | -h | -? | --help | ||
| 41 | print this help message | ||
| 42 | |||
| 43 | -l | --logger <level> | ||
| 44 | specify the logger verbosity. Values are: quiet, normal (default), | ||
| 45 | debug, verbose. | ||
| 46 | |||
| 47 | -a | --answers <file> | ||
| 48 | path to the output file for the answers to the query (in JSON | ||
| 49 | format) | ||
| 50 | |||
| 51 | -q | --queries <file> | ||
| 52 | path to a file containing a single SPARQL query. If no query | ||
| 53 | is provided, only the approximation to RSA will be performed. | ||
| 54 | |||
| 55 | -o | --ontology <file> | ||
| 56 | ontology file in OWL format. | ||
| 57 | |||
| 58 | -d | --data <file> | ||
| 59 | data files to be used alongside the ontology file. If a | ||
| 60 | directory is provided, all files in the directory (recursively) | ||
| 61 | will be considered. | ||
| 62 | |||
| 63 | -x | --approximation <string> | ||
| 64 | values available are "lowerupper" or "upperbound" corresponding | ||
| 65 | to the two algorithms for ontology approximation shipping by | ||
| 66 | default with RSAComb. You will need to change the source code to | ||
| 67 | expose custom approximation modules through the CLI. | ||
| 68 | |||
| 69 | -t | --transitive | ||
| 70 | "upperbound" approximation specific option. Include property chain | ||
| 71 | axioms (and hence the more common transitive properties) when | ||
| 72 | computing the canonical model. | ||
| 73 | |||
| 74 | """ | ||
| 75 | |||
| 76 | /** Default config values */ | ||
| 77 | private val default: Config = Map( | ||
| 78 | 'transitive -> false, | ||
| 79 | 'data -> List.empty[os.Path], | ||
| 80 | 'approximation -> 'lowerbound | ||
| 81 | ) | ||
| 82 | |||
| 83 | /** Parse a string into a path. | ||
| 84 | * | ||
| 85 | * @throws an [[IllegalArgumentException]] on malformed path. | ||
| 86 | */ | ||
| 87 | private def getPath(str: String): os.Path = | ||
| 88 | try { | ||
| 89 | os.Path(str, base = os.pwd) | ||
| 90 | } catch { | ||
| 91 | case e: IllegalArgumentException => | ||
| 92 | exit(s"'$str' is not a well formed path.") | ||
| 93 | } | ||
| 94 | |||
| 95 | /** Utility to exit the program with a custom message on stderr. | ||
| 96 | * | ||
| 97 | * The program will exit with error after printing the help message. | ||
| 98 | * | ||
| 99 | * @param msg message printed to stderr. | ||
| 100 | * @param errno error code number (defaults to 1) | ||
| 101 | */ | ||
| 102 | private def exit(msg: String, errno: Int = 1): Nothing = { | ||
| 103 | System.err.println(msg) | ||
| 104 | System.err.println(help) | ||
| 105 | sys.exit(errno) | ||
| 106 | } | ||
| 107 | |||
| 108 | /** Parse arguments with default options. | ||
| 109 | * | ||
| 110 | * @param args arguments list | ||
| 111 | * @return map of config options | ||
| 112 | */ | ||
| 113 | def parse(args: List[String]): Config = parse(args, default) | ||
| 114 | |||
| 115 | /** Parse arguments. | ||
| 116 | * | ||
| 117 | * @param args arguments list | ||
| 118 | * @param config default configuration | ||
| 119 | * @return map of config options | ||
| 120 | */ | ||
| 121 | def parse(args: List[String], config: Config): Config = { | ||
| 122 | args match { | ||
| 123 | case Nil => finalise(config) | ||
| 124 | case flag @ ("-h" | "-?" | "--help") :: _ => { | ||
| 125 | println(help) | ||
| 126 | sys.exit(0) | ||
| 127 | } | ||
| 128 | case flag @ ("-l" | "--logger") :: _level :: tail => { | ||
| 129 | // val level = _level match { | ||
| 130 | // case "quiet" => Logger.QUIET | ||
| 131 | // case "debug" => Logger.DEBUG | ||
| 132 | // case "verbose" => Logger.VERBOSE | ||
| 133 | // case _ => Logger.NORMAL | ||
| 134 | // } | ||
| 135 | parse(tail, config += ('logger -> _level)) | ||
| 136 | } | ||
| 137 | case flag @ ("-a" | "--answers") :: answers :: tail => | ||
| 138 | parse(tail, config += ('answers -> getPath(answers))) | ||
| 139 | case flag @ ("-x" | "--approximation") :: approx :: tail => { | ||
| 140 | parse(tail, config += ('approximation -> Symbol(approx))) | ||
| 141 | } | ||
| 142 | case flag @ ("-t" | "--transitive") :: tail => | ||
| 143 | parse(tail, config += ('transitive -> true)) | ||
| 144 | case flag @ ("-q" | "--queries") :: _query :: tail => { | ||
| 145 | val query = getPath(_query) | ||
| 146 | val files = | ||
| 147 | if (os.isFile(query)) | ||
| 148 | List(query) | ||
| 149 | else if (os.isDir(query)) | ||
| 150 | os.walk(query).filter(os.isFile).toList | ||
| 151 | else | ||
| 152 | exit(s"'${_query}' is not a valid path.") | ||
| 153 | parse(tail, config += ('queries -> files)) | ||
| 154 | } | ||
| 155 | case flag @ ("-o" | "--ontology") :: _ontology :: tail => { | ||
| 156 | val ontology = getPath(_ontology) | ||
| 157 | if (!os.isFile(ontology)) | ||
| 158 | exit(s"'${_ontology}' is not a valid filename.") | ||
| 159 | parse(tail, config += ('ontology -> ontology)) | ||
| 160 | } | ||
| 161 | case flag @ ("-d" | "--data") :: _data :: tail => { | ||
| 162 | val data = getPath(_data) | ||
| 163 | val files = | ||
| 164 | if (os.isFile(data)) | ||
| 165 | List(data) | ||
| 166 | else if (os.isDir(data)) { | ||
| 167 | os.walk(data).filter(os.isFile).toList | ||
| 168 | }else | ||
| 169 | exit(s"'${_data}' is not a valid path.") | ||
| 170 | parse(tail, config += ('data -> files)) | ||
| 171 | } | ||
| 172 | case a => exit(s"Invalid sequence of arguments '${a.mkString(" ")}'.") | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | /** Perform final checks on parsed options. | ||
| 177 | * | ||
| 178 | * @param config a parsed configuration | ||
| 179 | * @returns the input configuration, unchanged | ||
| 180 | */ | ||
| 181 | private def finalise(config: Config): Config = { | ||
| 182 | if (!config.contains('ontology)) | ||
| 183 | exit("The following flag is mandatory: '-o' or '--ontology'.") | ||
| 184 | config | ||
| 185 | } | ||
| 186 | |||
| 187 | /** Generate summary of a config object suitable for printing | ||
| 188 | * | ||
| 189 | * @param config a parsed configuration | ||
| 190 | * @returns a string describing the configuration | ||
| 191 | */ | ||
| 192 | def describe(config: Config): Unit = { | ||
| 193 | // config foreach { case (k,v) => k match { | ||
| 194 | // case 'logger => Logger print s"Logger level: ${v.get[Logger.Level]}" | ||
| 195 | // case 'ontology => Logger print s"Ontology file: ${v.get[os.Path]}" | ||
| 196 | // case 'data => { | ||
| 197 | // val paths = v.get[List[os.Path]] | ||
| 198 | // val ellipsis = if (paths.length > 1) " [...]" else "" | ||
| 199 | // Logger print s"Data files: ${paths.head}$ellipsis" | ||
| 200 | // } | ||
| 201 | // case 'queries => { | ||
| 202 | // val paths = v.get[List[os.Path]] | ||
| 203 | // val ellipsis = if (paths.length > 1) " [...]" else "" | ||
| 204 | // Logger print s"Query files: ${paths.head}$ellipsis" | ||
| 205 | // } | ||
| 206 | // case 'answers => Logger print s"Path to answers: ${v.get[os.Path]}" | ||
| 207 | // case 'approximation => Logger print s"Applied approximation: ${v.get[Symbol].name}" | ||
| 208 | // case 'transitive => Logger print s"Include property chain axioms: ${v.get[Boolean]}" | ||
| 209 | // }} | ||
| 210 | } | ||
| 211 | } | ||
