PageRenderTime 67ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/query.py

https://bitbucket.org/DanC/swap
Python | 2128 lines | 1942 code | 82 blank | 104 comment | 154 complexity | 3acbc0823a159cb16d6c9a1d3d046c1a MD5 | raw file
Possible License(s): CC-BY-SA-3.0

Large files files are truncated, but you can click here to view the full file

  1. """ Query for cwm architecture
  2. 2003-09-07 split off from llyn.py
  3. """
  4. QL_NS = "http://www.w3.org/2004/ql#"
  5. from sparql2cwm import SPARQL_NS
  6. from set_importer import Set, ImmutableSet, sorted
  7. from RDFSink import Logic_NS, RDFSink, forSomeSym, forAllSym
  8. from RDFSink import CONTEXT, PRED, SUBJ, OBJ, PARTS, ALL4
  9. from RDFSink import N3_nil, N3_first, N3_rest, OWL_NS, N3_Empty, N3_List, List_NS
  10. from RDFSink import RDF_NS_URI
  11. from OrderedSequence import intersection, minus, indentString
  12. import diag
  13. from diag import chatty_flag, tracking, progress
  14. from term import BuiltIn, LightBuiltIn, RDFBuiltIn, ArgumentNotLiteral, \
  15. HeavyBuiltIn, Function, ReverseFunction, MultipleFunction, \
  16. MultipleReverseFunction, UnknownType, Env, unify, \
  17. Literal, Symbol, Fragment, FragmentNil, Term, \
  18. CompoundTerm, List, EmptyList, NonEmptyList, ErrorFlag
  19. from formula import StoredStatement, Formula
  20. from why import Because, BecauseBuiltIn, BecauseOfRule, \
  21. BecauseOfExperience, becauseSubexpression, Reason, \
  22. BecauseSupports, BecauseMerge ,report, Premise, newTopLevelFormula, isTopLevel
  23. BuiltinFeedError = (ArgumentNotLiteral, UnknownType)
  24. import types
  25. import sys
  26. import weakref
  27. # from sets import Set # only in python 2.3 and following
  28. # set_importer does a cleaner job
  29. INFINITY = 1000000000 # @@ larger than any number occurences
  30. # State values as follows, high value=try first:
  31. S_UNKNOWN = 99 # State unknown - to be [re]calculated by setup.
  32. S_DONE = 80 # Exhausted all possible ways to saitsfy this. return now.
  33. S_LIGHT_UNS_READY= 70 # Light, not searched yet, but can run
  34. S_LIGHT_GO = 65 # Light, can run Do this!
  35. S_NOT_LIGHT = 60 # Not a light built-in, haven't searched yet.
  36. S_LIGHT_EARLY= 50 # Light built-in, not ready to calculate, not searched yet.
  37. S_NEED_DEEP= 45 # Can't search because of unbound compound term,
  38. # could do recursive unification
  39. S_HEAVY_READY= 40 # Heavy built-in, search done,
  40. # but formula now has no vars left. Ready to run.
  41. S_LIGHT_WAIT= 30 # Light built-in, not enough constants to calculate, search done.
  42. S_HEAVY_WAIT= 20 # Heavy built-in, too many variables in args to calculate, search done.
  43. S_REMOTE = 10 # Waiting for local query to be resolved as much as possible
  44. #S_SATISFIED = 0 # Item has been staisfied, and is no longer a constraint, continue with others
  45. stateName = {
  46. S_UNKNOWN : "????",
  47. S_DONE : "DONE",
  48. S_LIGHT_UNS_READY : "LtUsGo",
  49. S_LIGHT_GO : "LtGo",
  50. S_NOT_LIGHT : "NotLt",
  51. S_LIGHT_EARLY : "LtEarly",
  52. S_NEED_DEEP : "Deep",
  53. S_HEAVY_READY : "HvGo",
  54. S_LIGHT_WAIT : "LtWait",
  55. S_HEAVY_WAIT : "HvWait",
  56. S_REMOTE : "Remote"}
  57. # S_SATISFIED: "Satis" }
  58. def think(knowledgeBase, ruleFormula=None, mode="", why=None):
  59. """Forward-chaining inference
  60. In the case in which rules are added back into the
  61. store. The store is used for read (normally canonical) and write
  62. (normally open) at the samne time. It in fact has to be open.
  63. """
  64. if ruleFormula == None:
  65. ruleFormula = knowledgeBase
  66. assert knowledgeBase.canonical == None , "Must be open to add stuff:"+ `knowledgeBase `
  67. if diag.chatty_flag > 45: progress("think: rules from %s added to %s" %(
  68. knowledgeBase, ruleFormula))
  69. return InferenceTask(knowledgeBase, ruleFormula, mode=mode, why=why, repeat=1).run()
  70. def applyRules(
  71. workingContext, # Data we assume
  72. ruleFormula = None, # Where to find the rules
  73. targetContext = None): # Where to put the conclusions
  74. """Once"""
  75. t = InferenceTask(workingContext, ruleFormula, targetContext)
  76. result = t.run()
  77. del(t)
  78. return result
  79. def applyQueries(
  80. workingContext, # Data we assume
  81. ruleFormula = None, # Where to find the rules
  82. targetContext = None): # Where to put the conclusions
  83. """Once, nothing recusive, for a N3QL query"""
  84. t = InferenceTask(workingContext, ruleFormula, targetContext)
  85. t.gatherQueries(t.ruleFormula)
  86. result = t.run()
  87. del(t)
  88. return result
  89. def applySparqlQueries(
  90. workingContext, # Data we assume
  91. ruleFormula = None, # Where to find the rules
  92. targetContext = None): # Where to put the conclusions
  93. """Once, nothing recusive, for a N3QL query"""
  94. t = InferenceTask(workingContext, ruleFormula, targetContext, mode="q")
  95. t.gatherSparqlQueries(t.ruleFormula)
  96. result = t.run()
  97. del(t)
  98. return result
  99. class InferenceTask:
  100. """A task of applying rules or filters to information"""
  101. def __init__(self,
  102. workingContext, # Data we assume
  103. ruleFormula = None, # Where to find the rules
  104. targetContext = None, # Where to put the conclusions
  105. universals = Set(), # Inherited from higher contexts
  106. mode="", # modus operandi
  107. why=None, # Trace reason for all this
  108. repeat = 0): # do it until finished
  109. """ Apply rules in one context to the same or another
  110. A rule here is defined by log:implies, which associates the template
  111. (aka premise, precondidtion, antecedent, body) to the conclusion
  112. (aka postcondition, head).
  113. """
  114. if diag.chatty_flag >20:
  115. progress("New Inference task, rules from %s" % ruleFormula)
  116. if targetContext is None: targetContext = workingContext # return new data to store
  117. if ruleFormula is None: self.ruleFormula = workingContext # apply own rules
  118. else: self.ruleFormula = ruleFormula
  119. self.ruleFor = {}
  120. self.hasMetaRule = 0
  121. self.scheduler = Scheduler()
  122. self.workingContext, self.targetContext, self.mode, self.repeat = \
  123. workingContext, targetContext, mode, repeat
  124. self.store = self.workingContext.store
  125. def scheduleAttachRule(task, statement, formula, variables):
  126. formula = statement.context()
  127. subj, pred, obj = statement.spo()
  128. variables = variables | formula.universals()
  129. def addRule():
  130. if not formula.contains(subj=subj, pred=formula.store.implies, obj=obj):
  131. return 0 # The triple is no longer there
  132. return Rule(task, subj, obj, statement, variables).once()
  133. task.schedule(addRule)
  134. def scheduleAttachQuery(task, subj, obj, statement, variables):
  135. formula = statement.context()
  136. variables = variables | formula.universals()
  137. def addRule():
  138. if not formula.contains(subj=statement.subject(), pred=statement.predicate(), obj=statement.object()):
  139. return 0 # The triple is no longer there
  140. r = Rule(task, subj, obj, statement, variables).once()
  141. if (diag.chatty_flag >30):
  142. progress( "Found rule %r for statement %s " % (r, statement))
  143. return r
  144. task.schedule(addRule)
  145. def schedule(self, thunk):
  146. if self.scheduler is not None:
  147. self.scheduler.add(thunk)
  148. def run(self):
  149. """Perform task.
  150. Return number of new facts"""
  151. return self.runBrilliant()
  152. def runBrilliant(self):
  153. """Perform task.
  154. Return number of new facts.
  155. Start again if new rule mayhave been generated.
  156. This should be much faster than even runSmart,
  157. despite being much simpler"""
  158. if self.repeat and self.targetContext is self.workingContext:
  159. # We can loop
  160. canLoop = True
  161. else:
  162. canLoop = False
  163. universals = Set() # self.universals???
  164. if "q" not in self.mode:
  165. self.gatherRules(self.ruleFormula)
  166. scheduler = self.scheduler
  167. if not canLoop:
  168. self.scheduler = None
  169. total = scheduler.run(int.__add__)
  170. self.scheduler = scheduler
  171. return total
  172. def gatherRules(self, ruleFormula):
  173. universals = Set() # @@ self.universals??
  174. v2 = universals
  175. RuleInstaller(self, ruleFormula, v2).think()
  176. for F in ruleFormula.each(pred=self.store.type, obj=self.store.Truth): #@@ take out when --closure=T ??
  177. self.gatherRules(F) #See test/rules13.n3, test/schema-rules.n3 etc
  178. def gatherQueries(self, ruleFormula):
  179. "Find a set of rules in N3QL"
  180. universals = Set() # @@ self.universals??
  181. ql_select = self.store.newSymbol(QL_NS + "select")
  182. ql_where = self.store.newSymbol(QL_NS + "where")
  183. for s in ruleFormula.statementsMatching(pred=ql_select):
  184. r = self.ruleFor.get(s, None)
  185. if r != None: continue
  186. con, pred, query, selectClause = s.quad
  187. whereClause= ruleFormula.the(subj=query, pred=ql_where)
  188. if whereClause == None: continue # ignore (warning?)
  189. if (isinstance(selectClause, Formula)
  190. and isinstance(whereClause, Formula)):
  191. v2 = universals | ruleFormula.universals() # Note new variables can be generated
  192. self.scheduleAttachQuery(whereClause, selectClause, s, v2)
  193. def gatherSparqlQueries(self, ruleFormula):
  194. "Find the rules in SPARQL"
  195. store = self.store
  196. sparql = store.newSymbol(SPARQL_NS)
  197. for from_statement in ruleFormula.statementsMatching(pred=sparql['data']):
  198. working_context_stand_in = from_statement.object()
  199. ruleFormula = ruleFormula.substitution({working_context_stand_in: self.workingContext})
  200. query_root = ruleFormula.any(pred=store.type, obj=sparql['ConstructQuery'])
  201. if not query_root:
  202. # This is wrong
  203. query_root = ruleFormula.any(pred=store.type, obj=sparql['SelectQuery'])
  204. if not query_root:
  205. query_root = ruleFormula.any(pred=store.type, obj=sparql['AskQuery'])
  206. # query_root is a very boring bNode
  207. if query_root:
  208. #construct query
  209. for where_triple in ruleFormula.statementsMatching(subj=query_root, pred=sparql['where']):
  210. where_clause = where_triple.object()
  211. #where_clause is the tail of the rule
  212. implies_clause = ruleFormula.the(subj=where_clause, pred=store.implies)
  213. assert implies_clause is not None, ("where=%s, f=%s" % (where_clause.debugString(), ruleFormula.debugString()))
  214. #implies_clause is the head of the rule
  215. v2 = ruleFormula.universals().copy()
  216. self.scheduleAttachQuery(where_clause, implies_clause, where_triple, v2)
  217. #### TOPO
  218. #def partialOrdered(cy1, pool):
  219. # """Return sequence conforming to the partially order in a set of cyclic subsystems
  220. #
  221. # Basially, we find the dependencies of a node and remove them from the pool.
  222. # Then, any node in the pool can be done earlier, because has no depndency from those done.
  223. # """
  224. # seq = []
  225. # for r1 in cy1: # @@ did just chose first rule cy[0], but didn't get all
  226. # for r2 in r1.affects:
  227. # if r2 not in cy1: # An external dependency
  228. # cy2 = r2.cycle
  229. # if cy2 in pool:
  230. # seq = partialOrdered(cy2, pool) + seq
  231. # pool.remove(cy1)
  232. # if diag.chatty_flag > 90: progress("partial topo: %s" % `[cy1] + seq`)
  233. # return [cy1] + seq
  234. #
  235. #class CyclicSetOfRules:
  236. # """A set of rules which are connected
  237. # """
  238. # def __init__(self, rules):
  239. # self.rules = rules
  240. # for r1 in rules:
  241. # r1.cycle = self
  242. #
  243. # def __getitem__(self, i):
  244. # return self.rules[i]
  245. #
  246. # def __repr__(self):
  247. # return `self.rules`
  248. #
  249. # def run(self):
  250. # "Run a cyclic subset of the rules"
  251. # if diag.chatty_flag > 20:
  252. # progress()
  253. # progress("Running cyclic system %s" % (self))
  254. # if len(self.rules) == 1:
  255. # rule = self.rules[0]
  256. # if not rule.affects.get(rule, 0):
  257. ## rule.already = None # Suppress recording of previous answers
  258. # # - no, needed to remove dup bnodes as in test/includes/quant-implies.n3 --think
  259. # # When Rule.once is smarter about not iterating over things not mentioned elsewhere,
  260. # # can remove this.
  261. # return rule.once()
  262. #
  263. # agenda = self.rules[:]
  264. # total = 0
  265. # for r1 in self.rules:
  266. # af = r1.affects.keys()
  267. # af.sort()
  268. # r1.affectsInCyclic = intersection(self.rules, af)
  269. # while agenda:
  270. # rule = agenda[0]
  271. # agenda = agenda[1:]
  272. # found = rule.once()
  273. # if diag.chatty_flag > 20: progress("Rule %s gave %i. Affects:%s." %(
  274. # rule, found, rule.affectsInCyclic))
  275. # if found:
  276. # total = total + found
  277. # for r2 in rule.affectsInCyclic:
  278. # if r2 not in agenda:
  279. # if diag.chatty_flag > 30: progress("...rescheduling", r2)
  280. # agenda.append(r2)
  281. # if diag.chatty_flag > 20: progress("Cyclic subsystem exhausted")
  282. # return total
  283. def buildPattern(workingContext, template):
  284. """Make a list of unmatched statements including special
  285. builtins to check something is universally quantified"""
  286. unmatched = template.statements[:]
  287. for v in template.occurringIn(template.universals()):
  288. if diag.chatty_flag > 100: progress(
  289. "Template %s has universalVariableName %s, formula is %s" % (template, v, template.debugString()))
  290. unmatched.append(StoredStatement((workingContext,
  291. template.store.universalVariableName,
  292. workingContext,
  293. v
  294. # workingContext.store.newLiteral(v.uriref())
  295. )))
  296. return unmatched
  297. def buildStrictPattern(workingContext, template):
  298. unmatched = buildPattern(workingContext, template)
  299. for v in template.occurringIn(template.existentials()):
  300. if diag.chatty_flag > 100: progress(
  301. "Tempate %s has existentialVariableName %s, formula is %s" % (template, v, template.debugString()))
  302. unmatched.append(StoredStatement((workingContext,
  303. template.store.existentialVariableName,
  304. workingContext,
  305. v
  306. # workingContext.store.newLiteral(v.uriref())
  307. )))
  308. ## for v in template.variables():
  309. ## if diag.chatty_flag > 100: progress(
  310. ## "Tempate %s has enforceUniqueBinding %s, formula is %s" % (template, v, template.debugString()))
  311. ## unmatched.append(StoredStatement((workingContext,
  312. ## template.store.enforceUniqueBinding,
  313. ## v,
  314. ## workingContext.store.newLiteral(v.uriref())
  315. ## )))
  316. return unmatched
  317. nextRule = 0
  318. class Rule:
  319. def __init__(self, task, antecedent, consequent, statement, variables):
  320. """Try a rule
  321. Beware lists are corrupted. Already list is updated if present.
  322. The idea is that, for a rule which may be tried many times, the constant
  323. processing is done in this rather than in Query().
  324. The already dictionary is used to track bindings.
  325. This less useful when not repeating (as in --filter), but as in fact
  326. there may be several ways in which one cane get the same bindings,
  327. even without a repeat.
  328. """
  329. global nextRule
  330. self.task = task
  331. self.template = antecedent
  332. self.conclusion = consequent
  333. self.store = self.template.store
  334. self.statement = statement # original statement
  335. self.number = nextRule = nextRule+1
  336. self.meta = self.conclusion.contains(pred=self.conclusion.store.implies) #generate rules?
  337. # if task.repeat: self.already = [] # No neat to track dups if not
  338. # else: self.already = None
  339. self.already = []
  340. self.affects = {}
  341. self.indirectlyAffects = []
  342. self.indirectlyAffectedBy = []
  343. self.affectsInCyclic = []
  344. self.cycle = None
  345. # When the template refers to itself, the thing we are
  346. # are looking for will refer to the context we are searching
  347. # Similarly, references to the working context have to be moved into the
  348. # target context when the conclusion is drawn.
  349. # if self.template.universals() != Set():
  350. # raise RuntimeError("""Cannot query for universally quantified things.
  351. # As of 2003/07/28 forAll x ...x cannot be on left hand side of rule.
  352. # This/these were: %s\n""" % self.template.universals())
  353. self.unmatched = buildPattern(task.workingContext, self.template)
  354. self.templateExistentials = self.template.existentials().copy()
  355. _substitute({self.template: task.workingContext}, self.unmatched)
  356. variablesMentioned = self.template.occurringIn(variables)
  357. self.variablesUsed = self.conclusion.occurringIn(variablesMentioned)
  358. for x in variablesMentioned:
  359. if x not in self.variablesUsed:
  360. self.templateExistentials.add(x)
  361. allVariablesMentioned = self.templateExistentials | self.variablesUsed
  362. self.patternsToUnmatched = {}
  363. for p in self.unmatched:
  364. def replaceWithNil(x):
  365. if isinstance(x, Formula) or \
  366. (isinstance(x, List) and hasFormula(x)) or \
  367. x.occurringIn(allVariablesMentioned):
  368. return None
  369. return x
  370. patternTuple = tuple(replaceWithNil(x) for x in (p[1],
  371. p[2],
  372. p[3]))
  373. ## print 'patternTuple is %s, p is %s, vars=%s' % (patternTuple, p[1:], variables)
  374. primaryAlpha = p[0].statementsMatching(*patternTuple)
  375. primaryAlpha.addConsumer(self)
  376. self.patternsToUnmatched.setdefault(primaryAlpha.identity, []).append(p)
  377. if diag.chatty_flag >20:
  378. progress("New Rule %s ============ looking for:" % `self` )
  379. for s in self.template.statements: progress(" ", `s`)
  380. progress("=>")
  381. for s in self.conclusion.statements: progress(" ", `s`)
  382. progress("Universals declared in outer " + seqToString(variables))
  383. progress(" mentioned in template " + seqToString(variablesMentioned))
  384. progress(" also used in conclusion " + seqToString(self.variablesUsed))
  385. progress("Existentials in template " + seqToString(self.templateExistentials))
  386. return
  387. def once(self):
  388. if diag.chatty_flag >20:
  389. progress("Trying rule %s ===================" % self )
  390. progress( setToString(self.unmatched))
  391. task = self.task
  392. query = Query(self.store,
  393. unmatched = self.unmatched[:],
  394. template = self.template,
  395. variables = self.variablesUsed.copy(),
  396. existentials = self.templateExistentials.copy(),
  397. workingContext = task.workingContext,
  398. conclusion = self.conclusion,
  399. targetContext = task.targetContext,
  400. already = self.already,
  401. ###
  402. rule = self.statement,
  403. ###
  404. interpretBuiltins = 1, # (...)
  405. meta = task.workingContext,
  406. mode = task.mode)
  407. Formula.resetRenames()
  408. total = query.resolve()
  409. Formula.resetRenames(False)
  410. if diag.chatty_flag > 20:
  411. progress("Rule try generated %i new statements" % total)
  412. return total
  413. def scheduleAddTriple(rule, key, triple):
  414. def fireRuleWithTriple():
  415. if not triple.context().contains(subj=triple.subject(), pred=triple.predicate(), obj=triple.object()):
  416. return 0 # The triple is no longer there
  417. return rule.addTriple(key, triple)
  418. rule.task.schedule(fireRuleWithTriple)
  419. def addTriple(self, key, triple):
  420. """One triple was added to the store. Run the rule on it
  421. """
  422. possiblesInUnmatched = self.patternsToUnmatched[key]
  423. total = 0
  424. for pattern in possiblesInUnmatched:
  425. for env1, env2 in unify(pattern[1:], triple.quad[1:], vars=self.variablesUsed | self.templateExistentials):
  426. if diag.chatty_flag >20:
  427. progress("Trying rule %s on pattern %s, triple %s, env=%s===================" %
  428. (self, pattern, triple.quad, env1) )
  429. progress( setToString(self.unmatched))
  430. task = self.task
  431. query = Query(self.store,
  432. unmatched = [x for x in self.unmatched if x is not pattern],
  433. template = self.template,
  434. variables = self.variablesUsed.copy(),
  435. existentials = self.templateExistentials.copy(),
  436. workingContext = task.workingContext,
  437. conclusion = self.conclusion,
  438. targetContext = task.targetContext,
  439. already = self.already,
  440. ###
  441. rule = self.statement,
  442. ###
  443. interpretBuiltins = 1, # (...)
  444. meta = task.workingContext,
  445. mode = task.mode)
  446. Formula.resetRenames()
  447. subtotal = query.resolve((env1, [triple]))
  448. total += subtotal
  449. Formula.resetRenames(False)
  450. if diag.chatty_flag > 20:
  451. progress("Rule try generated %i new statements" % subtotal)
  452. return total
  453. def __repr__(self):
  454. if self in self.affects: return "R"+`self.number`+ "*"
  455. return "R"+`self.number`
  456. def compareByAffects(other):
  457. if other in self.indirectlyAffects: return -1 # Do me earlier
  458. if other in self.indirectlyAffectedBy: return 1
  459. return 0
  460. def traceForward(self, r1):
  461. for r2 in r1.affects:
  462. if r2 not in self.indirectlyAffects:
  463. self.indirectlyAffects.append(r2)
  464. r2.indirectlyAffectedBy.append(self)
  465. self.traceForward(r2)
  466. # else:
  467. # self.__setattr__("leadsToCycle", 1)
  468. def testIncludes(f, g, _variables=Set(), bindings={}, interpretBuiltins = 0):
  469. """Return whether or nor f contains a top-level formula equvalent to g.
  470. Just a test: no bindings returned."""
  471. if diag.chatty_flag >30: progress("testIncludes ============\nseeing if %s entails %s" % (f, g))
  472. # raise RuntimeError()
  473. if not(isinstance(f, Formula) and isinstance(g, Formula)): return 0
  474. assert f.canonical is f, f.debugString()
  475. assert g.canonical is g
  476. m = diag.chatty_flag
  477. diag.chatty_flag = 0
  478. if m > 60: progress("Before rename: ", f.debugString())
  479. f = f.renameVars()
  480. if m > 60: progress("After rename: ", f.debugString())
  481. diag.chatty_flag = m
  482. if diag.chatty_flag >100: progress("Formula we are searching in is\n%s" % g.debugString())
  483. unmatched = buildPattern(f, g)
  484. templateExistentials = g.existentials()
  485. more_variables = g.universals().copy()
  486. _substitute({g: f}, unmatched)
  487. # if g.universals() != Set():
  488. # raise RuntimeError("""Cannot query for universally quantified things.
  489. # As of 2003/07/28 forAll x ...x cannot be on left hand side of rule.
  490. # This/these were: %s\n""" % g.universals())
  491. # if bindings != {}: _substitute(bindings, unmatched)
  492. if diag.chatty_flag > 20:
  493. progress( "# testIncludes BUILTIN, %i terms in template %s, %i unmatched, %i template variables" % (
  494. len(g.statements),
  495. `g`[-8:], len(unmatched), len(templateExistentials)))
  496. if diag.chatty_flag > 80:
  497. for v in _variables:
  498. progress( " Variable: " + `v`[-8:])
  499. result = Query(f.store,
  500. unmatched=unmatched,
  501. template = g,
  502. variables=Set(),
  503. interpretBuiltins = interpretBuiltins,
  504. existentials=_variables | templateExistentials | more_variables,
  505. justOne=1, mode="").resolve()
  506. if diag.chatty_flag >30: progress("=================== end testIncludes =" + `result`)
  507. return result
  508. def n3Equivalent(g, f, env1, env2, vars=Set([]),
  509. universals=Set(), existentials=Set([]),
  510. n1Source=42, n2Source=42):
  511. """Return whether or nor f contains a top-level formula equvalent to g.
  512. Just a test: no bindings returned."""
  513. if diag.chatty_flag >30: progress("Query.py n3Equivalent ============\nseeing if %s equals %s" % (f, g))
  514. # raise RuntimeError()
  515. if not(isinstance(f, Formula) and isinstance(g, Formula)): pass
  516. elif f is g:
  517. yield env1, env2
  518. elif len(f) > len(g):
  519. pass
  520. else:
  521. assert f.canonical is f, "%s, %s" % (f.debugString(), f.canonical.debugString())
  522. assert g.canonical is g, "%s, %s" % (g.debugString(), g.canonical.debugString())
  523. m = diag.chatty_flag
  524. diag.chatty_flag = 0
  525. if m > 60: progress("Before rename: ", f.debugString())
  526. f = f.renameVars()
  527. if m > 60: progress("After rename: ", f.debugString())
  528. diag.chatty_flag = m
  529. unmatched = buildStrictPattern(f, g)
  530. templateExistentials = g.existentials() | g.universals() | existentials
  531. more_variables = Set(vars)
  532. _substitute({g: f}, unmatched)
  533. if env1: _substitute(env1.asDict(), unmatched)
  534. if diag.chatty_flag > 20:
  535. progress( "# testEqual BUILTIN, %i terms in template %s, %i unmatched, %i template variables" % (
  536. len(g.statements),
  537. `g`[-8:], len(unmatched), len(templateExistentials)))
  538. if diag.chatty_flag > 80:
  539. for v in vars:
  540. progress( " Variable: " + `v`[-8:])
  541. result = Query(f.store,
  542. unmatched=unmatched,
  543. template = g,
  544. variables=more_variables,
  545. workingContext = f,
  546. interpretBuiltins = False,
  547. existentials= templateExistentials ,
  548. justReturn=1, mode="").resolve()
  549. if diag.chatty_flag >30: progress("=================== end n3Equivalent =" + `result`)
  550. if not result: result = []
  551. for x in result:
  552. for k, (v, source) in x.items():
  553. env1 = env1.bind(k, (v, env2.id))
  554. yield env1, env2
  555. ## return [(x, None) for x in result]
  556. ############################################################## Query engine
  557. #
  558. # Template matching in a graph
  559. #
  560. # Optimizations we have NOT done:
  561. # - storing the tree of bindings so that we don't have to duplicate them another time
  562. # - using that tree to check only whether new data would extend it (very cool - memory?)
  563. # (this links to dynamic data, live variables.)
  564. # - recognising in advance disjoint graph templates, doing cross product of separate searches
  565. #
  566. # Built-Ins:
  567. # The trick seems to be to figure out which built-ins are going to be faster to
  568. # calculate, and so should be resolved before query terms involving a search, and
  569. # which, like those involving recursive queries or net access, will be slower than a query term,
  570. # and so should be left till last.
  571. # I feel that it should be possible to argue about built-ins just like anything else,
  572. # so we do not exclude these things from the query system. We therefore may have both light and
  573. # heavy built-ins which still have too many variables to calculate at this stage.
  574. # When we do the variable substitution for new bindings, these can be reconsidered.
  575. class Queue([].__class__):
  576. __slots__ = ['statements', 'bNodes']
  577. list = [].__class__
  578. def __init__(self, other=[], metaSource = None):
  579. self.list.__init__(self, other)
  580. if isinstance(metaSource, Queue):
  581. for k in self.__slots__:
  582. setattr(self, k, getattr(metaSource, k).copy())
  583. else:
  584. self.statements = Set()
  585. self.bNodes = Set()
  586. pass #fill in slots here
  587. def popBest(self):
  588. best = len(self) -1 # , say...
  589. i = best - 1
  590. while i >=0:
  591. if (self[i].state > self[best].state
  592. or (self[i].state == self[best].state
  593. and self[i].short < self[best].short)): best=i
  594. i = i - 1
  595. item = self[best]
  596. self.remove(item)
  597. return item
  598. def __repr__(self):
  599. return 'Queue(%s, bNodes=%s)' % (list.__repr__(self), self.bNodes)
  600. #Queue = [].__class__
  601. class Chain_Step(object):
  602. def __init__(self, vars, existentials, queue, env, parent=None, evidence=[]):
  603. self.vars = vars
  604. self.existentials = existentials
  605. self.lines = queue
  606. self.env = env
  607. assert parent is None
  608. self.parent = parent
  609. self.evidence = evidence
  610. def popBest(self):
  611. return self.lines.popBest()
  612. def copy(self):
  613. retVal = self.__class__(self.vars, self.existentials, self.lines, self.env, self.parent, self.evidence)
  614. return retVal
  615. def done(self):
  616. return not self.lines
  617. def __cmp__(self, other):
  618. return cmp(len(other.lines), len(self.lines))
  619. def __repr__(self):
  620. return "%s(lines=%r,\n\tenv=%r,\n\tparent=%r,\n\tevidence=%r)" % (self.__class__.__name__, self.lines, self.env, self.parent, self.evidence)
  621. def returnWrapper(f):
  622. def g(*args, **keywords):
  623. retVal = f(*args, **keywords)
  624. progress('%s() returns %s' % (f.func_name, retVal))
  625. return retVal
  626. return g
  627. class Query(Formula):
  628. """A query holds a hypothesis/antecedent/template which is being matched aginst (unified with)
  629. the knowledge base."""
  630. def __init__(self,
  631. store,
  632. unmatched=[], # Tuple of interned quads we are trying to match CORRUPTED
  633. template = None, # Actually, must have one
  634. variables=Set(), # List of variables to match and return CORRUPTED
  635. existentials=Set(), # List of variables to match to anything
  636. # Existentials or any kind of variable in subexpression
  637. workingContext = None,
  638. conclusion = None,
  639. targetContext = None,
  640. already = None, # Dictionary of matches already found
  641. rule = None, # The rule statement
  642. interpretBuiltins = 0, # List of contexts in which to use builtins
  643. justOne = 0, # Flag: Stop when you find the first one
  644. justReturn = 0, # Flag: Return bindings, don't conclude
  645. mode = "", # Character flags modifying modus operandi
  646. meta = None): # Context to check for useful info eg remote stuff
  647. if diag.chatty_flag > 50:
  648. progress( "Query: created with %i terms. (justone=%i, wc=%s)" %
  649. (len(unmatched), justOne, workingContext))
  650. if diag.chatty_flag > 80: progress( setToString(unmatched))
  651. if diag.chatty_flag > 90 and interpretBuiltins: progress(
  652. "iBuiltIns=1 ")
  653. Formula.__init__(self, store)
  654. self.statements = Queue() # Unmatched with more info
  655. # self.store = store # Initialized by Formula
  656. self.variables = variables
  657. self._existentialVariables = existentials
  658. self.workingContext = workingContext
  659. self.conclusion = conclusion
  660. self.targetContext = targetContext
  661. self.justOne = justOne
  662. self.already = already
  663. self.rule = rule
  664. self.template = template # For looking for lists
  665. self.meta = meta
  666. self.mode = mode
  667. self.lastCheckedNumberOfRedirections = 0
  668. self.bindingList = []
  669. self.justReturn = justReturn
  670. realMatchCount = 0
  671. if justReturn and not variables:
  672. self.justOne = True
  673. for quad in unmatched:
  674. item = QueryItem(self, quad)
  675. if not item.setup(allvars=variables|existentials, unmatched=unmatched,
  676. interpretBuiltins=interpretBuiltins, mode=mode):
  677. if diag.chatty_flag > 80: progress(
  678. "match: abandoned, no way for "+`item`)
  679. self.noWay = 1
  680. return # save time
  681. if not item.builtIn:
  682. realMatchCount += 1
  683. self.statements.append(item)
  684. if justReturn and realMatchCount > len(workingContext):
  685. self.noWay = 1
  686. return
  687. return
  688. def resolve(self, alreadyBound=None):
  689. if hasattr(self, "noWay"): return 0
  690. if alreadyBound is not None:
  691. env, evidence = alreadyBound
  692. k = self.matchFormula(self.statements, self.variables, self._existentialVariables, env, evidence=evidence)
  693. else:
  694. k = self.matchFormula(self.statements, self.variables, self._existentialVariables)
  695. if self.justReturn:
  696. return self.bindingList
  697. return k
  698. def checkRedirectsInAlready(self):
  699. """Kludge"""
  700. n = len(self.targetContext._redirections)
  701. if n > self.lastCheckedNumberOfRedirections:
  702. self.lastCheckedNumberOfRedirections = n
  703. self.redirect(self.targetContext._redirections)
  704. def redirect(self, redirections):
  705. for bindings in self.already:
  706. for var, value in bindings.items():
  707. try:
  708. x = redirections[value]
  709. except:
  710. pass
  711. else:
  712. if diag.chatty_flag>29: progress("Redirecting binding %r to %r" % (value, x))
  713. bindings[var] = x
  714. def conclude(self, bindings, evidence = [], extraBNodes = Set(), allBindings=None):
  715. """When a match found in a query, add conclusions to target formula.
  716. Returns the number of statements added."""
  717. if self.justOne:
  718. self.bindingList = [{}]
  719. return 1 # If only a test needed
  720. if self.justReturn:
  721. if bindings not in self.bindingList:
  722. # progress('CONCLUDE bindings = %s' % bindings)
  723. self.bindingList.append(bindings)
  724. return 1
  725. if diag.chatty_flag >60:
  726. progress( "Concluding tentatively...%r" % bindings)
  727. if self.already != None:
  728. self.checkRedirectsInAlready() # @@@ KLUDGE - use delegation and notification systme instead
  729. if bindings in self.already:
  730. if diag.chatty_flag > 30:
  731. progress("@@ Duplicate result: %r is in %r" % (bindings, self.already))
  732. return 0
  733. if diag.chatty_flag > 30: progress("Not duplicate: %r" % bindings)
  734. self.already.append(bindings)
  735. else:
  736. if diag.chatty_flag >60:
  737. progress( "No duplication check")
  738. if diag.tracking:
  739. if allBindings is None:
  740. allBindings = bindings
  741. for loc in xrange(len(evidence)):
  742. r = evidence[loc]
  743. if isinstance(r, BecauseSupportsWill):
  744. evidence[loc] = BecauseSupports(*([smarterSubstitution(k, allBindings,
  745. r.args[1], why=Because("I support it: "), exception=[r.args[2]]) for k in r.args] +
  746. [[k for k in evidence if isinstance(k, (StoredStatement, Reason))]]))
  747. if isinstance(r, BecauseBuiltInWill):
  748. evidence[loc] = BecauseBuiltIn(*[smarterSubstitution(k, allBindings,
  749. r.args[0], why=Because("I include it: " + k.debugString() + `allBindings`)) for k in r.args[1:]])
  750. reason = BecauseOfRule(self.rule, bindings=allBindings, knownExistentials = extraBNodes,
  751. evidence=evidence, kb=self.workingContext)
  752. # progress("We have a reason for %s of %s with bindings %s" % (self.rule, reason, alBindings))
  753. else:
  754. reason = None
  755. es, exout = (self.workingContext.existentials() | extraBNodes), Set() #self.workingContext.existentials() |
  756. for var, (val, source) in bindings.items():
  757. if isinstance(val, Exception):
  758. if "q" in self.mode: # How nice are we?
  759. raise ValueError(val)
  760. return 0
  761. intersection = val.occurringIn(es) # Take time for large number of bnodes?
  762. if intersection:
  763. exout.update(intersection)
  764. if diag.chatty_flag > 25: progress(
  765. "Match found to that which is only an existential: %s -> %s" %
  766. (var, val))
  767. for val2 in intersection:
  768. if val not in self.targetContext.existentials():
  769. if self.conclusion.occurringIn([var]):
  770. self.targetContext.declareExistential(val2)
  771. # Variable renaming
  772. b2 = bindings.asDict()
  773. # b2[self.conclusion] = self.targetContext # What does this mean?
  774. ok = self.targetContext.universals()
  775. # It is actually ok to share universal variables with other stuff
  776. poss = self.conclusion.universals().copy()
  777. for x in poss.copy():
  778. if x in ok: poss.remove(x)
  779. poss_sorted = list(poss)
  780. poss_sorted.sort(Term.compareAnyTerm)
  781. #progress(poss)
  782. # vars = self.conclusion.existentials() + poss # Terms with arbitrary identifiers
  783. # clashes = self.occurringIn(targetContext, vars) Too slow to do every time; play safe
  784. if diag.chatty_flag > 25:
  785. s=""
  786. for v in poss_sorted:
  787. v2 = self.targetContext.newUniversal()
  788. b2[v] =v2 # Regenerate names to avoid clash
  789. if diag.chatty_flag > 25: s = s + ",uni %s -> %s" %(v, v2)
  790. for v in sorted(list(self.conclusion.existentials()), Term.compareAnyTerm):
  791. if v not in exout:
  792. v2 = self.targetContext.newBlankNode()
  793. b2[v] =v2 # Regenerate names to avoid clash
  794. if diag.chatty_flag > 25: s = s + ",exi %s -> %s" %(v, v2)
  795. else:
  796. if diag.chatty_flag > 25: s = s + (", (%s is existential in kb)"%v)
  797. if diag.chatty_flag > 25:
  798. progress("Variables regenerated: universal " + `poss`
  799. + " existential: " +`self.conclusion.existentials()` + s)
  800. if diag.chatty_flag>19:
  801. progress("Concluding DEFINITELY" + bindingsToString(b2) )
  802. before = self.store.size
  803. _, delta = self.targetContext.loadFormulaWithSubstitution(
  804. self.conclusion, b2, why=reason, cannon=True)
  805. if diag.chatty_flag>29 and delta:
  806. progress(" --- because of: %s => %s, with bindings %s" % (self.template.debugString(),
  807. self.conclusion.debugString(),
  808. b2))
  809. if diag.chatty_flag> 40:
  810. progress("Added %i, nominal size of store changed from %i to %i."%(delta, before, self.store.size))
  811. return delta # self.store.size - before
  812. ##################################################################################
  813. def matchFormula(query, queue, variables, existentials, env=Env(), evidence=[]):
  814. total = 0
  815. if env:
  816. for i in queue:
  817. i.bindNew(env)
  818. stack = [Chain_Step(variables, existentials, queue, env, evidence=evidence)]
  819. while stack:
  820. if diag.chatty_flag > 150:
  821. progress(stack)
  822. workingStep = stack.pop()
  823. if not workingStep.done():
  824. queue = workingStep.lines
  825. evidence = workingStep.evidence
  826. bindings = workingStep.env
  827. variables = workingStep.vars
  828. existentials = workingStep.existentials
  829. item = workingStep.popBest()
  830. con, pred, subj, obj = item.quad
  831. state = item.state
  832. if state == S_DONE: # After bindNew, could be undoable.
  833. nbs = []
  834. elif state == S_LIGHT_UNS_READY: # Search then
  835. nbs = item.tryBuiltin(queue, bindings, evidence=evidence)
  836. item.state = S_LIGHT_EARLY # Unsearched, try builtin @@@@@@@@@ <== need new state here
  837. elif state == S_LIGHT_GO:
  838. nbs = item.tryBuiltin(queue, bindings, evidence=evidence)
  839. item.state = S_DONE # Searched.
  840. elif (state == S_LIGHT_EARLY or state == S_NOT_LIGHT or
  841. state == S_NEED_DEEP): # Not searched yet
  842. nbs = item.tryDeepSearch(queue, bindings)
  843. elif state == S_HEAVY_READY: # not light, may be heavy; or heavy ready to run
  844. if pred is query.store.includes: # and not diag.tracking: # don't optimize when tracking?
  845. variables = variables.copy()
  846. existentials = existentials.copy()
  847. nbs = item.doIncludes(queue, existentials, variables, bindings)
  848. elif pred is query.store.supports:
  849. variables = variables.copy()
  850. existentials = existentials.copy()
  851. nbs = item.doSupports(queue, existentials, variables, bindings)
  852. else:
  853. item.state = S_HEAVY_WAIT # Assume can't resolve
  854. nbs = item.tryBuiltin(queue, bindings, evidence=evidence)
  855. item.state = S_DONE
  856. elif state == S_REMOTE: # Remote query -- need to find all of them for the same service
  857. items = [item]
  858. for i in queue[:]:
  859. if i.state == S_REMOTE and i.service is item.service: #@@ optimize which group is done first!
  860. items.append(i)
  861. queue.remove(i)
  862. nbs = query.remoteQuery(items)
  863. item.state = S_DONE # do not put back on list
  864. elif state ==S_HEAVY_WAIT or state == S_LIGHT_WAIT:
  865. if item.quad[PRED] is query.store.universalVariableName or \
  866. item.quad[PRED] is query.store.existentialVariableName:
  867. ### We will never bind this variable in the first place
  868. item.state = S_DONE
  869. nbs = []
  870. else:
  871. if diag.chatty_flag > 20 :
  872. progress("@@@@ Warning: query can't find term which will work.")
  873. progress( " state is %s, queue length %i" % (state, len(queue)+1))
  874. progress("@@ Current item: %s" % `item`)
  875. progress(queueToString(queue))
  876. continue # Forget it
  877. else:
  878. raise RuntimeError, "Unknown state " + `state`
  879. stack_extent = []
  880. for nb, reason in nbs:
  881. assert isinstance(nb, dict), nb
  882. q2 = Queue([], queue)
  883. if query.justReturn:
  884. ### What does the following do?
  885. ### If we are doing a 1::1 match, record everything we have matched
  886. if isinstance(reason, StoredStatement):
  887. if reason not in q2.statements and \
  888. reason[CONTEXT] is query.workingContext:
  889. q2.statements.add(reason)
  890. else:
  891. continue
  892. if isinstance(reason, StoredStatement):
  893. if True or reason[CONTEXT] is not query.workingContext:
  894. for m in nb.values():
  895. if isinstance(m, tuple):
  896. m = m[0]
  897. if m in reason[CONTEXT].existentials():
  898. q2.bNodes.add(m)
  899. if diag.chatty_flag > 80:
  900. ### These get picked up from log:includes
  901. ### {[:b :c]} log:includes {?X :b :c} ...
  902. progress('Adding bNode %s, now %s' % (m, q2.bNodes))
  903. new_thing = False
  904. try:
  905. new_env = bindings.flatten(nb)
  906. except ValueError:
  907. pass
  908. else:
  909. new_thing = True
  910. for i in queue:
  911. newItem = i.clone()
  912. newItem.bindNew(new_env) ## Is this right? I was hoping to avoid this
  913. q2.append(newItem) #@@@@@@@@@@ If exactly 1 binding, loop (tail recurse)
  914. if new_thing:
  915. if diag.chatty_flag > 70:
  916. progress("query.py bindings nb new_entry:", bindings, nb, new_env)
  917. new_step = Chain_Step(variables, existentials, q2, new_env, workingStep.parent, workingStep.evidence + [reason])
  918. stack_extent.append(new_step)
  919. if item.state != S_DONE:
  920. queue.append(item)
  921. new_step = workingStep.copy()
  922. stack_extent.append(new_step)
  923. stack.extend(stack_extent)
  924. else:
  925. if workingStep.parent is not None:
  926. raise RuntimeError("We are not chaining yet.\n How did I get here?")
  927. else:
  928. total = query.conclude(workingStep.env.filter(workingStep.vars), allBindings=workingStep.env
  929. , evidence=workingStep.evidence, extraBNodes = workingStep.lines.bNodes) + total # No terms left .. success!
  930. #raise RuntimeError("I need to conclude here, workingStep=%s" % workingStep)
  931. pass
  932. return total
  933. def matchFormula2(query,
  934. queue, # Set of items we are trying to match CORRUPTED
  935. variables, # List of variables to match and return CORRUPTED
  936. existentials, # List of variables to match to anything
  937. # Existentials or any kind of variable in subexpression
  938. bindings = Env(), # Bindings discovered so far
  939. newBindings = Env(), # New bindings not yet incorporated
  940. evidence = []): # List of statements supporting the bindings so far
  941. """ Iterate on the remaining query items
  942. bindings collected matches already found
  943. newBindings matches found and not yet applied - used in recursion
  944. You probably really need the state diagram to understand this
  945. http://www.w3.org/2000/10/swap/doc/states.svg
  946. even if it is a bit out of date.
  947. """
  948. total = 0
  949. assert isinstance(bindings, Env)
  950. assert isinstance(newBindings, Env), 'env is an %s, not an %s' % (newBindings.__class__, Env)
  951. if diag.chatty_flag > 59:
  952. progress( "QUERY2: called %i terms, %i bindings %s, (new: %s)" %
  953. (len(queue), len(bindings), `bindings`,
  954. `newBindings`))
  955. if diag.chatty_flag > 90: progress( queueToString(queue))
  956. newBindingItems = newBindings.items()
  957. while newBindingItems: # Take care of business left over from recursive call
  958. pair = newBindingItems.pop(0)
  959. if isinstance(pair[1], tuple):
  960. pair = (pair[0], pair[1][0])
  961. else:
  962. raise RuntimeError
  963. if diag.chatty_flag>95: progress(" new binding: %s -> %s" % (`pair[0]`, `pair[1]`))
  964. if pair[0] in variables:
  965. variables.remove(pair[0])
  966. bindings = bindings.newBinding(pair[0], (pair[1], None))
  967. else: # Formulae aren't needed as existentials, unlike lists. hmm.
  968. ### bindings.update({pair[0]: pair[1]}) # remove me!!!!!
  969. # if diag.tracking: raise RuntimeError(pair[0], pair[1])
  970. #bindings.update({pair[0]: pair[1]}) # Record for proof only
  971. if pair[0] not in existentials:
  972. if isinstance(pair[0], List):
  973. # pair[0] should be a variable, can't be a list, surely

Large files files are truncated, but you can click here to view the full file