aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2022-05-18 15:31:40 +0100
committerFederico Igne <federico.igne@cs.ox.ac.uk>2022-05-18 15:41:21 +0100
commite3d640788cf5804d3f0d5213f52f0dc8b78df1ca (patch)
tree1d31aaa79dc324da53b353a83d644316f2f92900
parent08f6576178fafe7a24215efb2575d4a8701ec6ef (diff)
downloadACQuA-e3d640788cf5804d3f0d5213f52f0dc8b78df1ca.tar.gz
ACQuA-e3d640788cf5804d3f0d5213f52f0dc8b78df1ca.zip
feat: add consistency check for ACQuA query reasoner
-rw-r--r--src/main/scala/uk/ac/ox/cs/acqua/reasoner/AcquaQueryReasoner.scala23
-rw-r--r--src/main/scala/uk/ac/ox/cs/acqua/reasoner/ConsistencyManager.scala336
2 files changed, 347 insertions, 12 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/acqua/reasoner/AcquaQueryReasoner.scala b/src/main/scala/uk/ac/ox/cs/acqua/reasoner/AcquaQueryReasoner.scala
index de6e87c..2ee0f64 100644
--- a/src/main/scala/uk/ac/ox/cs/acqua/reasoner/AcquaQueryReasoner.scala
+++ b/src/main/scala/uk/ac/ox/cs/acqua/reasoner/AcquaQueryReasoner.scala
@@ -33,7 +33,6 @@ import uk.ac.ox.cs.pagoda.query.{
33} 33}
34import uk.ac.ox.cs.pagoda.query.QueryRecord.Step 34import uk.ac.ox.cs.pagoda.query.QueryRecord.Step
35import uk.ac.ox.cs.pagoda.reasoner.{ 35import uk.ac.ox.cs.pagoda.reasoner.{
36 ConsistencyManager,
37 MyQueryReasoner, 36 MyQueryReasoner,
38 QueryReasoner 37 QueryReasoner
39} 38}
@@ -62,7 +61,7 @@ class AcquaQueryReasoner(val ontology: Ontology)
62 /** Compatibility convertions between PAGOdA and RSAComb */ 61 /** Compatibility convertions between PAGOdA and RSAComb */
63 import uk.ac.ox.cs.acqua.implicits.PagodaConverters._ 62 import uk.ac.ox.cs.acqua.implicits.PagodaConverters._
64 63
65 private var encoder: Option[TrackingRuleEncoder] = None 64 var encoder: Option[TrackingRuleEncoder] = None
66 private var lazyUpperStore: Option[MultiStageQueryEngine] = None; 65 private var lazyUpperStore: Option[MultiStageQueryEngine] = None;
67 66
68 private val timer: Timer = new Timer(); 67 private val timer: Timer = new Timer();
@@ -70,25 +69,25 @@ class AcquaQueryReasoner(val ontology: Ontology)
70 private var _isConsistent: ConsistencyStatus = StatusUnchecked 69 private var _isConsistent: ConsistencyStatus = StatusUnchecked
71 // TODO: explicit casting to MyQueryReasoner makes no sense. Find 70 // TODO: explicit casting to MyQueryReasoner makes no sense. Find
72 // another solution. Probably requires changing PAGOdA source code. 71 // another solution. Probably requires changing PAGOdA source code.
73 private val consistencyManager: ConsistencyManager = new ConsistencyManager(this.asInstanceOf[MyQueryReasoner]) 72 private val consistencyManager: ConsistencyManager = new ConsistencyManager(this)
74 73
75 private val rlLowerStore: BasicQueryEngine = new BasicQueryEngine("rl-lower-bound") 74 val rlLowerStore: BasicQueryEngine = new BasicQueryEngine("rl-lower-bound")
76 private val elLowerStore: KarmaQueryEngine = new KarmaQueryEngine("elho-lower-bound") 75 val elLowerStore: KarmaQueryEngine = new KarmaQueryEngine("elho-lower-bound")
77 private lazy val lowerRSAEngine = new RSACombQueryReasoner(ontology, new Lowerbound) 76 private lazy val lowerRSAEngine = new RSACombQueryReasoner(ontology, new Lowerbound)
78 private lazy val upperRSAEngine = new RSACombQueryReasoner(ontology, new Upperbound) 77 private lazy val upperRSAEngine = new RSACombQueryReasoner(ontology, new Upperbound)
79 78
80 private val trackingStore = new MultiStageQueryEngine("tracking", false); 79 val trackingStore = new MultiStageQueryEngine("tracking", false);
81 80
82 var predicatesWithGap: Seq[String] = Seq.empty 81 var predicatesWithGap: Seq[String] = Seq.empty
83 82
84 /* Load ontology into PAGOdA */ 83 /* Load ontology into PAGOdA */
85 private val datalog = new DatalogProgram(ontology.origin); 84 val datalog = new DatalogProgram(ontology.origin);
86 //datalog.getGeneral().save(); 85 //datalog.getGeneral().save();
87 if (!datalog.getGeneral().isHorn()) 86 if (!datalog.getGeneral().isHorn())
88 lazyUpperStore = Some(new MultiStageQueryEngine("lazy-upper-bound", true)) 87 lazyUpperStore = Some(new MultiStageQueryEngine("lazy-upper-bound", true))
89 importData(datalog.getAdditionalDataFile()); 88 importData(datalog.getAdditionalDataFile())
90 private val elhoOntology: OWLOntology = new ELHOProfile().getFragment(ontology.origin); 89 private val elhoOntology: OWLOntology = new ELHOProfile().getFragment(ontology.origin);
91 elLowerStore.processOntology(elhoOntology); 90 elLowerStore processOntology elhoOntology
92 91
93 92
94 /** Performs nothing. 93 /** Performs nothing.
@@ -121,7 +120,7 @@ class AcquaQueryReasoner(val ontology: Ontology)
121 /* RL lower-bound check */ 120 /* RL lower-bound check */
122 rlLowerStore.importRDFData(name, datafile); 121 rlLowerStore.importRDFData(name, datafile);
123 rlLowerStore.materialise("lower program", datalog.getLower.toString); 122 rlLowerStore.materialise("lower program", datalog.getLower.toString);
124 if (!consistencyManager.checkRLLowerBound()) { 123 if (!consistencyManager.checkRLLowerBound) {
125 Utility logDebug s"time for satisfiability checking: ${timer.duration()}" 124 Utility logDebug s"time for satisfiability checking: ${timer.duration()}"
126 _isConsistent = StatusInconsistent 125 _isConsistent = StatusInconsistent
127 return false 126 return false
@@ -134,7 +133,7 @@ class AcquaQueryReasoner(val ontology: Ontology)
134 elLowerStore.materialise("saturate named individuals", originalMarkProgram); 133 elLowerStore.materialise("saturate named individuals", originalMarkProgram);
135 elLowerStore.materialise("lower program", datalog.getLower.toString); 134 elLowerStore.materialise("lower program", datalog.getLower.toString);
136 elLowerStore.initialiseKarma(); 135 elLowerStore.initialiseKarma();
137 if (!consistencyManager.checkELLowerBound()) { 136 if (!consistencyManager.checkELLowerBound) {
138 Utility logDebug s"time for satisfiability checking: ${timer.duration()}" 137 Utility logDebug s"time for satisfiability checking: ${timer.duration()}"
139 _isConsistent = StatusInconsistent 138 _isConsistent = StatusInconsistent
140 return false 139 return false
@@ -187,7 +186,7 @@ class AcquaQueryReasoner(val ontology: Ontology)
187 */ 186 */
188 def isConsistent(): Boolean = { 187 def isConsistent(): Boolean = {
189 if (_isConsistent == StatusUnchecked) { 188 if (_isConsistent == StatusUnchecked) {
190 _isConsistent = consistencyManager.check() 189 _isConsistent = consistencyManager.check
191 Utility logDebug s"time for satisfiability checking: ${timer.duration()}" 190 Utility logDebug s"time for satisfiability checking: ${timer.duration()}"
192 } 191 }
193 Utility logInfo s"The ontology is ${_isConsistent}!" 192 Utility logInfo s"The ontology is ${_isConsistent}!"
diff --git a/src/main/scala/uk/ac/ox/cs/acqua/reasoner/ConsistencyManager.scala b/src/main/scala/uk/ac/ox/cs/acqua/reasoner/ConsistencyManager.scala
new file mode 100644
index 0000000..4b73f88
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/acqua/reasoner/ConsistencyManager.scala
@@ -0,0 +1,336 @@
1package uk.ac.ox.cs.acqua.reasoner
2
3import java.util.LinkedList
4import scala.collection.JavaConverters._
5
6import org.semanticweb.HermiT.model.{
7 Atom,
8 AtomicConcept,
9 DLClause,
10 Variable
11}
12import org.semanticweb.owlapi.model.{
13 OWLOntology,
14 OWLOntologyCreationException,
15 OWLOntologyManager
16}
17import uk.ac.ox.cs.JRDFox.JRDFStoreException
18// import uk.ac.ox.cs.JRDFox.store.DataStore;
19import uk.ac.ox.cs.JRDFox.store.DataStore.UpdateType
20import uk.ac.ox.cs.pagoda.hermit.DLClauseHelper
21import uk.ac.ox.cs.pagoda.query.{
22 AnswerTuples,
23 QueryRecord
24}
25// import uk.ac.ox.cs.pagoda.query.QueryManager;
26import uk.ac.ox.cs.pagoda.reasoner.full.Checker
27import uk.ac.ox.cs.pagoda.reasoner.light.BasicQueryEngine
28// import uk.ac.ox.cs.pagoda.rules.UpperDatalogProgram;
29import uk.ac.ox.cs.pagoda.summary.HermitSummaryFilter
30import uk.ac.ox.cs.pagoda.tracking.QueryTracker
31// import uk.ac.ox.cs.pagoda.tracking.TrackingRuleEncoder;
32import uk.ac.ox.cs.pagoda.util.{
33 Timer,
34 Utility
35}
36import uk.ac.ox.cs.pagoda.util.disposable.{
37 Disposable,
38 DisposedException
39}
40// import uk.ac.ox.cs.pagoda.util.disposable.DisposedException;
41
42/** Consistency checker inspired by [[uk.ac.ox.cs.pagoda.reaseoner.ConsistencyManager]].
43 *
44 * @param reasoner an [[AcquaQueryReasoner]] instance
45 *
46 * TODO: document public methods and rework the code to be more
47 * Scala-esque.
48 */
49class ConsistencyManager(
50 protected val reasoner: AcquaQueryReasoner
51) extends Disposable {
52
53 protected val queryManager = reasoner.getQueryManager
54 private val timer = new Timer()
55 private var fragmentExtracted = false
56
57 private var fullQueryRecord: Option[QueryRecord] = None
58 private var botQueryRecords = Array.empty[QueryRecord]
59 private var toAddClauses = new LinkedList[DLClause]()
60
61
62 override def dispose(): Unit = {
63 super.dispose()
64 fullQueryRecord.map(_.dispose())
65 }
66
67 /**
68 *
69 */
70 def extractBottomFragment(): Unit = {
71 if (isDisposed) throw new DisposedException
72 if (!fragmentExtracted) {
73 fragmentExtracted = true
74
75 val upperProgram = reasoner.datalog.getUpper
76 val number = upperProgram.getBottomNumber
77
78 if (number <= 1) {
79 botQueryRecords = Array[QueryRecord](fullQueryRecord.get)
80 } else {
81 var record: QueryRecord = null
82 var tempQueryRecords = new Array[QueryRecord](number-1)
83 for(i <- 0 until (number-1)) {
84 record = queryManager.create(QueryRecord.botQueryText.replace("Nothing", s"Nothing${i+1}"), 0, i + 1)
85 tempQueryRecords(i) = record
86 var iter: AnswerTuples = null
87 try {
88 iter = reasoner.trackingStore.evaluate(record.getQueryText, record.getAnswerVariables)
89 record updateUpperBoundAnswers iter
90 } finally {
91 if (iter != null) iter.dispose()
92 }
93 }
94
95 var bottomNumber = 0;
96 val group = (0 until (number-1)).toArray
97 for(i <- 0 until (number-1))
98 if (tempQueryRecords(i).isProcessed)
99 tempQueryRecords(i).dispose()
100 else if(group(i) == i) {
101 bottomNumber += 1
102 record = tempQueryRecords(i)
103 for (j <- i until (number-1))
104 if (record hasSameGapAnswers tempQueryRecords(j))
105 group(j) = i
106 }
107
108 Utility logInfo s"There are $bottomNumber different bottom fragments."
109 toAddClauses = new LinkedList[DLClause]()
110 var bottomCounter = 0
111 botQueryRecords = new Array[QueryRecord](bottomNumber)
112 val X = Variable.create("X")
113 for(i <- 0 until (number-1)) {
114 if (!tempQueryRecords(i).isDisposed() && !tempQueryRecords(i).isProcessed()) {
115 if (group(i) == i) {
116 record = tempQueryRecords(i)
117 botQueryRecords(bottomCounter) = record
118 bottomCounter += 1
119 group(i) = bottomCounter
120 record.resetInfo(
121 QueryRecord.botQueryText.replace(
122 "Nothing",
123 s"Nothing_final$bottomCounter"
124 ), 0, bottomCounter)
125 toAddClauses.add(
126 DLClause.create(
127 Array[Atom](Atom.create(AtomicConcept.create(s"${AtomicConcept.NOTHING.getIRI}_final$bottomCounter"), X)),
128 Array[Atom](Atom.create(AtomicConcept.create(s"${AtomicConcept.NOTHING.getIRI}${i+1}"), X))
129 )
130 )
131 } else {
132 toAddClauses.add(
133 DLClause.create(
134 Array[Atom](Atom.create(AtomicConcept.create(s"${AtomicConcept.NOTHING.getIRI}_final${group(group(i))}"), X)),
135 Array[Atom](Atom.create(AtomicConcept.create(s"${AtomicConcept.NOTHING.getIRI}${i+1}"), X))
136 )
137 )
138 tempQueryRecords(i).dispose()
139 }
140 }
141 }
142 upperProgram updateDependencyGraph toAddClauses
143 }
144
145 val programs: Array[String] = collectTrackingProgramAndImport()
146 if (programs.length > 0) {
147 val datastore = reasoner.trackingStore.getDataStore
148 var oldTripleCount: Long = 0
149 var tripleCount: Long = 0
150 try {
151 val t1 = new Timer();
152 oldTripleCount = datastore.getTriplesCount
153 for(program <- programs)
154 datastore.importRules(program, UpdateType.ScheduleForAddition)
155 datastore applyReasoning true
156 tripleCount = datastore.getTriplesCount
157
158 Utility logDebug s"tracking store after materialising tracking program: $tripleCount (${tripleCount - oldTripleCount} new)"
159 Utility logDebug s"tracking store finished the materialisation of tracking program in ${t1.duration()} seconds."
160
161 extractAxioms()
162 datastore.clearRulesAndMakeFactsExplicit()
163 } catch {
164 case e: JRDFStoreException => e.printStackTrace()
165 case e: OWLOntologyCreationException => e.printStackTrace()
166 }
167 }
168 }
169 }
170
171 /**
172 *
173 * @note provided for compatibility reasons
174 */
175 val getQueryRecords: Array[QueryRecord] = botQueryRecords
176
177 /** RL lower bound check for satisfiability. */
178 lazy val checkRLLowerBound: Boolean = {
179 if (isDisposed) throw new DisposedException
180 val record: QueryRecord = queryManager.create(QueryRecord.botQueryText, 0)
181 fullQueryRecord = Some(record)
182
183 var iter: AnswerTuples = null
184 try {
185 iter = reasoner.rlLowerStore.evaluate(record.getQueryText, record.getAnswerVariables)
186 record updateLowerBoundAnswers iter
187 } finally {
188 iter.dispose()
189 }
190
191 if (record.getNoOfSoundAnswers > 0) {
192 Utility logInfo s"Answers to bottom in the lower bound: ${record.outputSoundAnswerTuple}"
193 }
194 record.getNoOfSoundAnswers <= 0
195 }
196
197 /** ELHO lower bound check for satisfiability */
198 lazy val checkELLowerBound: Boolean = {
199 if (isDisposed) throw new DisposedException
200 val record: QueryRecord = fullQueryRecord.get
201
202 val answers: AnswerTuples =
203 reasoner.elLowerStore.evaluate(
204 record.getQueryText,
205 record.getAnswerVariables
206 )
207 record updateLowerBoundAnswers answers
208
209 if (record.getNoOfSoundAnswers > 0) {
210 Utility logInfo s"Answers to bottom in the lower bound: ${record.outputSoundAnswerTuple}"
211 }
212 record.getNoOfSoundAnswers <= 0
213 }
214
215 /**
216 *
217 */
218 def checkUpper(upperStore: BasicQueryEngine): Boolean = {
219 if (isDisposed) throw new DisposedException
220 val record = fullQueryRecord.get
221
222 if (upperStore != null) {
223 var tuples: AnswerTuples = null
224 try {
225 tuples = upperStore.evaluate(record.getQueryText, record.getAnswerVariables)
226 if (!tuples.isValid) {
227 Utility logInfo s"There are no contradictions derived in ${upperStore.getName} materialisation."
228 Utility logDebug "The ontology and dataset is satisfiable."
229 return true
230 }
231 } finally {
232 if (tuples != null) tuples.dispose()
233 }
234 }
235 false
236 }
237
238 /** True if the KB associate with the [[reasoner]] is consistent.
239 *
240 * This is the main entry point of the consistency checker.
241 */
242 lazy val check: Boolean = {
243 if (isDisposed) throw new DisposedException
244 val record = fullQueryRecord.get
245 var tuples: AnswerTuples = null
246
247 try {
248 tuples = reasoner.trackingStore.evaluate(
249 record.getQueryText,
250 record.getAnswerVariables
251 )
252 record updateUpperBoundAnswers tuples
253 } finally {
254 if (tuples != null) tuples.dispose()
255 }
256
257 var satisfiability = true
258 if (record.getNoOfCompleteAnswers != 0) {
259
260 extractBottomFragment()
261
262 try {
263 extractAxioms4Full()
264 } catch {
265 case e: OWLOntologyCreationException => e.printStackTrace()
266 }
267
268 var checker: Checker = null
269 for (r <- getQueryRecords) {
270 checker = new HermitSummaryFilter(r, true)
271 satisfiability = checker.isConsistent()
272 checker.dispose()
273 }
274 }
275 satisfiability
276 }
277
278 /**
279 *
280 */
281 private def extractAxioms4Full(): Unit = {
282 val manager: OWLOntologyManager =
283 reasoner.encoder.get
284 .getProgram
285 .getOntology
286 .getOWLOntologyManager
287 val fullOntology: OWLOntology = manager.createOntology()
288 for (record <- botQueryRecords) {
289 for (clause <- record.getRelevantClauses.asScala) {
290 fullQueryRecord.get addRelevantClauses clause
291 }
292 manager.addAxioms(
293 fullOntology,
294 record.getRelevantOntology.getAxioms()
295 )
296 }
297 fullQueryRecord.get.setRelevantOntology(fullOntology)
298 }
299
300 /**
301 *
302 */
303 private def extractAxioms(): Unit = {
304 val manager: OWLOntologyManager =
305 reasoner.encoder.get
306 .getProgram
307 .getOntology
308 .getOWLOntologyManager
309 for (record <- botQueryRecords) {
310 record setRelevantOntology manager.createOntology()
311 val tracker: QueryTracker = new QueryTracker(reasoner.encoder.get, reasoner.rlLowerStore, record)
312 reasoner.encoder.get setCurrentQuery record
313 tracker extractAxioms reasoner.trackingStore
314 Utility logInfo s"finish extracting axioms for bottom ${record.getQueryID}"
315 }
316 }
317
318 /**
319 *
320 */
321 private def collectTrackingProgramAndImport(): Array[String] = {
322 val programs = new Array[String](botQueryRecords.length)
323 val encoder = reasoner.encoder.get
324 val currentClauses: LinkedList[DLClause] = new LinkedList[DLClause]()
325
326 botQueryRecords.zipWithIndex map { case (record,i) => {
327 encoder setCurrentQuery record
328 val builder = new StringBuilder(encoder.getTrackingProgram)
329 val currentClauses = toAddClauses.asScala.filter{clause =>
330 clause.getHeadAtom(0).getDLPredicate.toString().contains(s"_final${i + 1}")
331 }.asJava
332 builder append (DLClauseHelper toString currentClauses)
333 builder.toString
334 }}
335 }
336}