PageRenderTime 39ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/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
  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
  974. del newBindings[pair[0]]
  975. #We can accidently bind a list using (1 2 3) rdf:rest (?x 3).
  976. #This finds the true binding
  977. reallyNewBindingsList = pair[0].unify(
  978. pair[1], bindings, Env(), variables | existentials)
  979. ## I'm a bit parenoid. If we did not find a binding ...
  980. if not reallyNewBindingsList or not hasattr(
  981. reallyNewBindingsList, '__iter__'):
  982. return 0
  983. reallyNewBindingsList = [x for x in reallyNewBindingsList]
  984. try:
  985. reallyNewBindings = reallyNewBindingsList[0][0] #We don't deal
  986. # with multiple ways to bind
  987. except:
  988. print 'we lost'
  989. print pair[0], pair[1]
  990. a = pair[0].unify(
  991. pair[1], variables, existentials, bindings)
  992. print a
  993. print a[0]
  994. print a[0][0]
  995. raise
  996. newBindingItems.extend(reallyNewBindings.items())
  997. newBindings = newBindings.update2(reallyNewBindings)
  998. else:
  999. if diag.chatty_flag > 40: # Reasonable
  1000. progress("Not in existentials or variables but now bound:", `pair[0]`)
  1001. elif diag.tracking: bindings = bindings.newBinding(pair[0], (pair[1], None))
  1002. if not isinstance(pair[0], CompoundTerm) and ( # Hack - else rules13.n3 fails @@
  1003. pair[0] in existentials): # Hack ... could be bnding from nested expression
  1004. existentials.remove(pair[0]) # Can't match anything anymore, need exact match
  1005. # Perform the substitution, noting where lists become boundLists.
  1006. # We do this carefully, messing up the order only of things we have already processed.
  1007. if newBindings != {}:
  1008. for item in queue:
  1009. if item.bindNew(newBindings) == 0: return 0
  1010. while len(queue) > 0:
  1011. if (diag.chatty_flag > 90):
  1012. progress( "query iterating with %i terms, %i bindings: %s; %i new bindings: %s ." %
  1013. (len(queue),
  1014. len(bindings),bindingsToString(bindings),
  1015. len(newBindings),bindingsToString(newBindings)))
  1016. progress ( queueToString(queue))
  1017. # Take best. (Design choice: search here or keep queue in order)
  1018. # item = queue.pop()
  1019. item = queue.popBest()
  1020. if diag.chatty_flag>49:
  1021. progress( "Looking at " + `item`)
  1022. progress( "...with vars("+seqToString(variables)+")"
  1023. + " ExQuVars:("+seqToString(existentials)+")")
  1024. con, pred, subj, obj = item.quad
  1025. state = item.state
  1026. if state == S_DONE: # After bindNew, could be undoable.
  1027. return total # Forget it -- must be impossible
  1028. if state == S_LIGHT_UNS_READY: # Search then
  1029. nbs = item.tryBuiltin(queue, bindings, evidence=evidence)
  1030. item.state = S_LIGHT_EARLY # Unsearched, try builtin @@@@@@@@@ <== need new state here
  1031. elif state == S_LIGHT_GO:
  1032. nbs = item.tryBuiltin(queue, bindings, evidence=evidence)
  1033. item.state = S_DONE # Searched.
  1034. elif (state == S_LIGHT_EARLY or state == S_NOT_LIGHT or
  1035. state == S_NEED_DEEP): # Not searched yet
  1036. nbs = item.tryDeepSearch(queue, Env())
  1037. elif state == S_HEAVY_READY: # not light, may be heavy; or heavy ready to run
  1038. if pred is query.store.includes: # and not diag.tracking: # don't optimize when tracking?
  1039. nbs = item.doIncludes(queue, existentials, variables, bindings)
  1040. elif pred is query.store.supports:
  1041. nbs = item.doSupports(queue, existentials, variables, bindings)
  1042. else:
  1043. item.state = S_HEAVY_WAIT # Assume can't resolve
  1044. nbs = item.tryBuiltin(queue, bindings, evidence=evidence)
  1045. item.state = S_DONE
  1046. elif state == S_REMOTE: # Remote query -- need to find all of them for the same service
  1047. items = [item]
  1048. for i in queue[:]:
  1049. if i.state == S_REMOTE and i.service is item.service: #@@ optimize which group is done first!
  1050. items.append(i)
  1051. queue.remove(i)
  1052. nbs = query.remoteQuery(items)
  1053. item.state = S_DONE # do not put back on list
  1054. elif state ==S_HEAVY_WAIT or state == S_LIGHT_WAIT:
  1055. if item.quad[PRED] is query.store.universalVariableName or \
  1056. item.quad[PRED] is query.store.existentialVariableName:
  1057. ### We will never bind this variable in the first place
  1058. item.state = S_DONE
  1059. nbs = []
  1060. else:
  1061. if diag.chatty_flag > 20 :
  1062. progress("@@@@ Warning: query can't find term which will work.")
  1063. progress( " state is %s, queue length %i" % (state, len(queue)+1))
  1064. progress("@@ Current item: %s" % `item`)
  1065. progress(queueToString(queue))
  1066. return total # Forget it
  1067. else:
  1068. raise RuntimeError, "Unknown state " + `state`
  1069. if diag.chatty_flag > 90: progress("nbs=" + `nbs`)
  1070. # query.py Removed as I added this and wasn't sure whether it works with justReturn change below -tbl
  1071. # # Optimization when sucess but no bindings
  1072. # if (len(nbs) == 1 and nbs[0][0] == {} and nbs[0][1] is None and # if nbs == [({}, None)] and
  1073. # state == S_DONE):
  1074. # if diag.chatty_flag>90: progress("LOOP to next, state="+`state`)
  1075. # continue # Loop around and do the next one. optimization.
  1076. for nb, reason in nbs:
  1077. assert isinstance(nb,types.DictType), nb
  1078. q2 = Queue([], queue)
  1079. if query.justReturn:
  1080. ### What does the following do?
  1081. ### If we are doing a 1::1 match, record everything we have matched
  1082. if isinstance(reason, StoredStatement):
  1083. if reason not in q2.statements and \
  1084. reason[CONTEXT] is query.workingContext:
  1085. q2.statements.add(reason)
  1086. else:
  1087. continue
  1088. if isinstance(reason, StoredStatement):
  1089. if True or reason[CONTEXT] is not query.workingContext:
  1090. for m in nb.values():
  1091. if isinstance(m, tuple):
  1092. m = m[0]
  1093. if m in reason[CONTEXT].existentials():
  1094. q2.bNodes.add(m)
  1095. if diag.chatty_flag > 80:
  1096. ### These get picked up from log:includes
  1097. ### {[:b :c] log:includes {?X :b :c} ...
  1098. progress('Adding bNode %s, now %s' % (m, q2.bNodes))
  1099. for i in queue:
  1100. newItem = i.clone()
  1101. q2.append(newItem) #@@@@@@@@@@ If exactly 1 binding, loop (tail recurse)
  1102. found = query.matchFormula(q2, variables.copy(), existentials.copy(),
  1103. bindings.copy(), nb, evidence = evidence + [reason])
  1104. if diag.chatty_flag > 91: progress(
  1105. "Nested query returns %i (nb= %r)" % (found, nb))
  1106. total = total + found
  1107. if query.justOne and total:
  1108. return total
  1109. # NO - more to do return total # The called recursive calls above will have generated the output @@@@ <====XXXX
  1110. if diag.chatty_flag > 80: progress("Item state %i, returning total %i" % (item.state, total))
  1111. if (item.state == S_DONE):
  1112. return total
  1113. queue.append(item)
  1114. # And loop back to take the next item
  1115. if diag.chatty_flag>50: progress("QUERY MATCH COMPLETE with bindings: " + `bindings`)
  1116. if query.justReturn:
  1117. try:
  1118. len(queue.statements)
  1119. len(query.workingContext)
  1120. except:
  1121. print type(queue.statements)
  1122. print type(query.workingContext)
  1123. raise
  1124. if len(queue.statements) != len(query.workingContext):
  1125. return total
  1126. newTotal = query.conclude(bindings, evidence=evidence, extraBNodes = queue.bNodes, allBindings=bindings) + total # No terms left .. success!
  1127. if total:
  1128. progress('newTotal=%s, total=%s' % (newTotal, total))
  1129. raise RuntimeError('How did I get here?')
  1130. return newTotal
  1131. def remoteQuery(query, items):
  1132. """Perform remote query as client on remote store
  1133. Currently this only goes to an SQL store, but should later use SPARQL etc
  1134. in remote HTTP/SOAP call."""
  1135. if diag.chatty_flag > 90:
  1136. progress(" Remote service %s" % (items))
  1137. serviceURI = items[0].service.uri
  1138. if serviceURI.startswith("http:"):
  1139. from sparql.sparqlClient import SparqlQuery
  1140. return SparqlQuery(query, items, serviceURI)
  1141. elif not serviceURI.startswith("mysql:"):
  1142. raise ValueError("Unknown URI scheme for remote query service: %s" % serviceURI)
  1143. import dbork.SqlDB
  1144. from dbork.SqlDB import ResultSet, SqlDBAlgae, ShowStatement
  1145. # SqlDB stores results in a ResultSet.
  1146. rs = ResultSet()
  1147. # QueryPiece qp stores query tree.
  1148. qp = rs.buildQuerySetsFromCwm(items, query.variables, query._existentialVariables)
  1149. # Extract access info from the first item.
  1150. if diag.chatty_flag > 90:
  1151. progress(" Remote service %s" %items[0].service.uri)
  1152. (user, password, host, database) = re.match(
  1153. "^mysql://(?:([^@:]+)(?::([^@]+))?)@?([^/]+)/([^/]+)/$",
  1154. items[0].service.uri).groups()
  1155. # Look for one of a set of pre-compiled rdb schemas.
  1156. HostDB2SchemeMapping = { "mysql://root@localhost/w3c" : "AclSqlObjects" }
  1157. if (HostDB2SchemeMapping.has_key(items[0].service.uri)):
  1158. cachedSchema = HostDB2SchemeMapping.get(items[0].service.uri)
  1159. else:
  1160. cachedSchema = None
  1161. # The SqlDBAlgae object knows how to compile SQL query from query tree qp.
  1162. a = SqlDBAlgae(query.store.symbol(items[0].service.uri), cachedSchema,
  1163. user, password, host, database, query.meta, query.store.pointsAt,
  1164. query.store)
  1165. # Execute the query.
  1166. messages = []
  1167. nextResults, nextStatements = a._processRow([], [], qp, rs, messages, {})
  1168. # rs.results = nextResults # Store results as initial state for next use of rs.
  1169. if diag.chatty_flag > 90: progress(string.join(messages, "\n"))
  1170. if diag.chatty_flag > 90: progress("query matrix \"\"\""+
  1171. rs.toString({'dataFilter' : None})+"\"\"\" .\n")
  1172. nbs = []
  1173. reason = Because("Remote query") # could be messages[0] which is the query
  1174. # Transform nextResults to format cwm expects.
  1175. for resultsRow in nextResults:
  1176. boundRow = {}
  1177. for i in range(len(query.variables)):
  1178. v = query.variables[i]
  1179. index = rs.getVarIndex(v)
  1180. interned = resultsRow[index]
  1181. boundRow[v] = interned # bindings
  1182. nbs.append((boundRow, reason))
  1183. if diag.chatty_flag > 10: progress("====> bindings from remote query:"+`nbs`)
  1184. return nbs # No bindings for testing
  1185. class BetterNone(object):
  1186. __slots__ = []
  1187. def __new__(cls):
  1188. try:
  1189. return cls.__val__
  1190. except:
  1191. cls.__val__ = object.__new__(cls)
  1192. return cls.__val__
  1193. def __hash__(self):
  1194. raise TypeError
  1195. def __str__(self):
  1196. raise NotImplementedError
  1197. def __eq__(self, other):
  1198. raise NotImplementedError
  1199. __neq__ = __eq__
  1200. __lt__ = __gt__ = __leq__ = __geq__ = __eq__
  1201. BNone = BetterNone()
  1202. class QueryItem(StoredStatement): # Why inherit? Could be useful, and is logical...
  1203. """One line in a query being resolved.
  1204. To a large extent, each line can keep track of its own state.
  1205. When a remote query is done, query lines to the same server have to be coordinated again.
  1206. """
  1207. def __init__(self, query, quad):
  1208. quad = lookupQuad(quad[CONTEXT]._redirections, quad)
  1209. #print quad[CONTEXT]._redirections
  1210. self.quad = quad
  1211. self.query = query
  1212. self.searchPattern = None # Will be list of variables
  1213. self.store = query.store
  1214. self.state = S_UNKNOWN # Invalid
  1215. self.short = INFINITY
  1216. self.neededToRun = None # see setup()
  1217. self.myIndex = None # will be list of satistfying statements
  1218. self.service = None # Remote database server for this predicate?
  1219. self.builtIn = False
  1220. return
  1221. def clone(self):
  1222. """Take a copy when for iterating on a query"""
  1223. x = QueryItem(self.query, self.quad)
  1224. x.state = self.state
  1225. x.short = self.short
  1226. x.neededToRun = []
  1227. x.searchPattern = self.searchPattern[:]
  1228. for p in ALL4: # Deep copy! Prevent crosstalk
  1229. x.neededToRun.append(self.neededToRun[p].copy())
  1230. x.myIndex = self.myIndex
  1231. try:
  1232. x.interpretBuiltins = self.interpretBuiltins
  1233. except AttributeError:
  1234. pass
  1235. return x
  1236. def setup(self, allvars, unmatched, interpretBuiltins=[], mode=""):
  1237. """Check how many variables in this term,
  1238. and how long it would take to search
  1239. Returns, true normally or false if there is no way this query will work.
  1240. Only called on virgin query item.
  1241. The mode is a set of character flags about how we think."""
  1242. con, pred, subj, obj = self.quad
  1243. self.interpretBuiltins = interpretBuiltins
  1244. self.service = None
  1245. if diag.chatty_flag > 800: progress("setup:" + `allvars`)
  1246. if "r" in mode:
  1247. schema = None
  1248. if "s" in mode:
  1249. schema = pred.dereference(mode, self.query.workingContext)
  1250. if schema != None:
  1251. if "a" in mode:
  1252. if diag.chatty_flag > 95:
  1253. progress("Axiom processing for %s" % (pred))
  1254. ns = pred.resource
  1255. rules = schema.any(subj=ns, pred=self.store.docRules)
  1256. rulefile = rulefile.dereference("m", self.query.workingContext)
  1257. self.service = schema.any(pred=self.store.definitiveService, subj=pred)
  1258. if self.service == None and self.query.meta != None:
  1259. self.service = self.query.meta.any(pred=self.store.definitiveService, subj=pred)
  1260. if self.service == None:
  1261. uri = pred.uriref()
  1262. if uri[:4] == "mysql:":
  1263. j = uri.rfind("/")
  1264. if j>0: self.service = meta.newSymbol(uri[:j])
  1265. if diag.chatty_flag > 90 and self.service:
  1266. progress("We have a Remote service %s for %s." %(self.service, pred))
  1267. if not self.service:
  1268. authDoc = None
  1269. if schema != None:
  1270. authDoc = schema.any(pred=self.store.definitiveDocument, subj=pred)
  1271. if authDoc == None and self.query.meta != None:
  1272. authDoc = self.query.meta.any(pred=self.store.definitiveDocument, subj=pred)
  1273. if authDoc != None:
  1274. if diag.chatty_flag > 90:
  1275. progress("We have a definitive document %s for %s." %(authDoc, pred))
  1276. authFormula = authDoc.dereference(mode, self.query.workingContext)
  1277. if authFormula != None:
  1278. self.quad = (authFormula, pred, subj, obj)
  1279. con = authFormula
  1280. self.neededToRun = [ Set(), Set(), Set(), Set() ] # for each part of speech
  1281. self.searchPattern = [con, pred, subj, obj] # What do we search for?
  1282. hasUnboundCoumpundTerm = 0
  1283. for p in PRED, SUBJ, OBJ :
  1284. x = self.quad[p]
  1285. if x in allvars: # Variable
  1286. self.neededToRun[p] = Set([x])
  1287. self.searchPattern[p] = None # can bind this
  1288. elif isinstance(x, Formula) or isinstance(x, List): # expr @@ Set @@@@@@@@@@ Check and CompundTerm>???
  1289. ur = x.occurringIn(allvars)
  1290. self.neededToRun[p] = ur
  1291. if ur != Set() or isinstance(x, Formula) or (isinstance(x, List) and hasFormula(x)):
  1292. hasUnboundCoumpundTerm = 1 # Can't search directly
  1293. self.searchPattern[p] = None # can bind this if we recurse
  1294. if diag.chatty_flag > 98: progress(" %s needs to run: %s"%(`x`, `self.neededToRun[p]`))
  1295. self.updateMyIndex(con)
  1296. if isinstance(pred, RDFBuiltIn) or (
  1297. interpretBuiltins and isinstance(pred, BuiltIn)):
  1298. self.builtIn = True
  1299. if isinstance(pred, RDFBuiltIn) or (
  1300. interpretBuiltins and isinstance(pred, LightBuiltIn)):
  1301. if self.canRun(): self.state = S_LIGHT_UNS_READY # Can't do it here
  1302. else: self.state = S_LIGHT_EARLY # Light built-in, can't run yet, not searched
  1303. elif self.short == 0: # Skip search if no possibilities!
  1304. self.searchDone()
  1305. elif hasUnboundCoumpundTerm:
  1306. self.state = S_NEED_DEEP # Put off till later than non-deep ones
  1307. else:
  1308. self.state = S_NOT_LIGHT # Not a light built in, not searched.
  1309. if diag.chatty_flag > 80: progress("setup:" + `self`)
  1310. if self.state == S_DONE: return False
  1311. return True
  1312. def doIncludes(item, queue, existentials, variables, bindings):
  1313. """Implement log:includes by adding all the statements on the LHS
  1314. to the query items. The plan is that this should"""
  1315. con, pred, subj, obj = item.quad
  1316. state = item.state
  1317. nbs = [] # Failure
  1318. if (isinstance(subj, Formula)
  1319. and isinstance(obj, Formula)):
  1320. oldsubj = subj
  1321. subj = subj.renameVars()
  1322. more_unmatched = buildPattern(subj, obj)
  1323. more_variables = obj.variables().copy()
  1324. _substitute({obj: subj}, more_unmatched)
  1325. _substitute(bindings, more_unmatched)
  1326. existentials = existentials | more_variables
  1327. allvars = variables | existentials
  1328. for quad in more_unmatched:
  1329. newItem = QueryItem(item.query, quad)
  1330. queue.append(newItem)
  1331. if not newItem.setup(allvars, interpretBuiltins = 0,
  1332. unmatched=more_unmatched, mode=item.query.mode):
  1333. return []
  1334. if diag.chatty_flag > 40:
  1335. progress("log:Includes: Adding %i new terms and %s as new existentials."%
  1336. (len(more_unmatched),
  1337. seqToString(more_variables)))
  1338. rea = BecauseBuiltInWill(subj, con, oldsubj, pred, obj)
  1339. ## nbs = [({oldsubj: subj}, rea)]
  1340. nbs = [(Env(), rea)]
  1341. else:
  1342. # if isinstance(subj, Formula): subj = subj.n3String()
  1343. # if isinstance(obj, Formula): obj = obj.n3String()
  1344. #raise RuntimeError("Cannot do {%s} log:includes {%s} " % (subj, obj))
  1345. progress("""Warning: Type error ignored on builtin:
  1346. log:includes only on formulae """+`item`)
  1347. #@@ was RuntimeError exception
  1348. item.state = S_DONE
  1349. return nbs
  1350. def doSupports(item, queue, existentials, variables, bindings):
  1351. """Real implementation of log:supports. Have fun.
  1352. """
  1353. con, pred, subj, obj = item.quad
  1354. state = item.state
  1355. store = con.store
  1356. #statementObject = iter(con.statementsMatching(subj=subj, pred=pred, obj=obj)).next()
  1357. nbs = [] # Failure
  1358. if (isinstance(subj, Formula)
  1359. and isinstance(obj, Formula)):
  1360. assert subj.canonical != None
  1361. F = store.any((store._experience, store.cufi, subj, None)) # Cached value?
  1362. if F != None:
  1363. if diag.chatty_flag > 10: progress("Bultin: " + `subj`+ " cached log:conclusion " + `F`)
  1364. else:
  1365. oldSubj = subj
  1366. newSubj = subj.renameVars()
  1367. F = subj.newFormula()
  1368. newTopLevelFormula(F)
  1369. if diag.tracking:
  1370. reason = Premise("Assumption of builtin", item)
  1371. #reason = BecauseMerge(F, subj)
  1372. # F.collector = reason
  1373. # proof.append(reason)
  1374. else: reason = None
  1375. if diag.chatty_flag > 10: progress("Bultin: " + `subj`+ " log:conclusion " + `F`)
  1376. F.loadFormulaWithSubstitution(subj, why=reason, cannon=True) # leave open
  1377. think(F)
  1378. F4 = F.close()
  1379. if F4 is not F:
  1380. raise RuntimeError
  1381. ### oh no! we've hit something old!
  1382. if F is subj:
  1383. ### absolute worst --- cannot be allowed!
  1384. F.add(F.newBlankNode(), F.newBlankNode(), F.newBlankNode())
  1385. F4 = F.close()
  1386. F = F4
  1387. assert subj.canonical != None
  1388. store.storeQuad((store._experience, store.cufi, oldSubj, F),
  1389. why=BecauseOfExperience("conclusion")) # Cache for later
  1390. more_unmatched = buildPattern(F, obj)
  1391. more_variables = obj.variables().copy()
  1392. _substitute({obj: F}, more_unmatched)
  1393. _substitute(bindings, more_unmatched)
  1394. existentials = existentials | more_variables
  1395. allvars = variables | existentials
  1396. for quad in more_unmatched:
  1397. newItem = QueryItem(item.query, quad)
  1398. queue.append(newItem)
  1399. if not newItem.setup(allvars, interpretBuiltins = 0,
  1400. unmatched=more_unmatched, mode=item.query.mode):
  1401. return []
  1402. if diag.chatty_flag > 40:
  1403. progress("log:Includes: Adding %i new terms and %s as new existentials."%
  1404. (len(more_unmatched),
  1405. seqToString(more_variables)))
  1406. rea = BecauseSupportsWill(con, subj, F, pred, obj)
  1407. ## nbs = [({oldsubj: subj}, rea)]
  1408. nbs = [(Env(), rea)]
  1409. else:
  1410. if isinstance(subj, Formula): subj = subj.n3String()
  1411. if isinstance(obj, Formula): obj = obj.n3String()
  1412. #raise RuntimeError("Cannot do {%s} log:includes {%s} " % (subj, obj))
  1413. progress("""Warning: Type error ignored on builtin:
  1414. log:include only on formulae """+`item`)
  1415. #@@ was RuntimeError exception
  1416. item.state = S_DONE
  1417. return nbs
  1418. def tryBuiltin(self, queue, bindings, evidence):
  1419. """Check for built-in functions to see whether it will resolve.
  1420. Return codes: 0 - give up;
  1421. [] - success, no new bindings, (action has been called)
  1422. [...] list of binding lists, each a pair of bindings and reason."""
  1423. con, pred, subj, obj = self.quad
  1424. proof = [] # place for built-in to hang a justification
  1425. rea = None # Reason for believing this item is true
  1426. if "q" in self.query.mode:
  1427. caughtErrors = (TypeError, ValueError, AttributeError, AssertionError, ArgumentNotLiteral, UnknownType)
  1428. else:
  1429. caughtErrors = ()
  1430. try:
  1431. if self.neededToRun[SUBJ] == Set():
  1432. if self.neededToRun[OBJ] == Set(): # bound expression - we can evaluate it
  1433. try:
  1434. # if pred.eval(subj, obj, queue, bindings.copy(), proof, self.query):
  1435. if pred.eval(subj, obj, BNone, BNone, proof, BNone):
  1436. if diag.chatty_flag > 80: progress(
  1437. "Builtin buinary relation operator succeeds")
  1438. if diag.tracking:
  1439. rea = BecauseBuiltIn(con, subj, pred, obj)
  1440. return [(Env(), rea)] # Involves extra recursion just to track reason
  1441. return [(Env(), None)] # No new bindings but success in logical operator
  1442. else: return [] # We absoluteley know this won't match with this in it
  1443. except caughtErrors:
  1444. progress(
  1445. "Warning: Built-in %s %s %s failed because:\n %s: %s"
  1446. % (`subj`, `pred`, `obj`, sys.exc_info()[0].__name__ ,
  1447. sys.exc_info()[1].__str__() ))
  1448. if "h" in self.query.mode:
  1449. raise
  1450. return []
  1451. except BuiltinFeedError:
  1452. return []
  1453. else:
  1454. if isinstance(pred, Function):
  1455. if diag.chatty_flag > 97:
  1456. progress("Builtin function call %s(%s)"%(pred, subj))
  1457. try:
  1458. # result = pred.evalObj(subj, queue, bindings.copy(), proof, self.query)
  1459. result = pred.evalObj(subj, BNone, BNone, proof, BNone)
  1460. except caughtErrors:
  1461. errVal = (
  1462. "Warning: Built-in %s!%s failed because:\n %s: %s"
  1463. % (`pred`, `subj`, sys.exc_info()[0].__name__ ,
  1464. sys.exc_info()[1].__str__() ))
  1465. progress(errVal)
  1466. if "h" in self.query.mode:
  1467. raise
  1468. if isinstance(pred, MultipleFunction):
  1469. result = [ErrorFlag(errVal)]
  1470. else:
  1471. result = ErrorFlag(errVal)
  1472. except BuiltinFeedError:
  1473. return []
  1474. if result != None:
  1475. self.state = S_DONE
  1476. rea=None
  1477. if isinstance(result, Formula):
  1478. if self.quad[OBJ] in self.query.variables:
  1479. result.reopen()
  1480. result = result.canonicalize(cannon=True)
  1481. assert result.canonical is result
  1482. if diag.tracking:
  1483. if isinstance(result, Formula):
  1484. result2 = result.renameVars()
  1485. assert result.canonical is result, result.debugString()
  1486. else:
  1487. result2 = result
  1488. if isinstance(pred, MultipleFunction):
  1489. return [(Env({obj:(x, None)}),
  1490. diag.tracking and BecauseBuiltIn(con, subj, pred, x)
  1491. ) for x in result]
  1492. else:
  1493. return [(nb1, diag.tracking and BecauseBuiltIn(con, subj, pred, result))
  1494. for nb1, env3 in obj.unify(result, Env(), Env(), self.neededToRun[OBJ])]
  1495. ## return [(Env({obj: (result, None)}),
  1496. ## diag.tracking and BecauseBuiltIn(con, subj, pred, result))]
  1497. else:
  1498. return []
  1499. else:
  1500. if (self.neededToRun[OBJ] == Set()):
  1501. if isinstance(pred, ReverseFunction):
  1502. if diag.chatty_flag > 97: progress("Builtin Rev function call %s(%s)"%(pred, obj))
  1503. try:
  1504. # result = pred.evalSubj(obj, queue, bindings.copy(), proof, self.query)
  1505. result = pred.evalSubj(obj, BNone, BNone, proof, BNone)
  1506. except caughtErrors:
  1507. errVal = (
  1508. "Warning: Built-in %s^%s failed because:\n %s: %s"
  1509. % (`pred`, `obj`, sys.exc_info()[0].__name__ ,
  1510. sys.exc_info()[1].__str__() ))
  1511. progress(errVal)
  1512. if "h" in self.query.mode:
  1513. raise
  1514. if isinstance(pred, MultipleReverseFunction):
  1515. result = [ErrorFlag(errVal)]
  1516. else:
  1517. result = ErrorFlag(errVal)
  1518. except BuiltinFeedError:
  1519. return []
  1520. if result != None:
  1521. self.state = S_DONE
  1522. if isinstance(pred, MultipleReverseFunction):
  1523. return [(Env({subj:(x,None)}),
  1524. diag.tracking and \
  1525. BecauseBuiltIn(con, x, pred, obj)) \
  1526. for x in result]
  1527. else:
  1528. return [(nb1, diag.tracking and BecauseBuiltIn(con, result, pred, obj))
  1529. for nb1, env3 in subj.unify(result, Env(), Env(), self.neededToRun[SUBJ])]
  1530. ## return [(Env({subj: (result,None)}),
  1531. ## diag.tracking and \
  1532. ## BecauseBuiltIn(con, result, pred, obj)
  1533. ## )]
  1534. else:
  1535. return []
  1536. else:
  1537. if isinstance(pred, FiniteProperty):
  1538. result = pred.ennumerate()
  1539. if result != 0:
  1540. self.state = S_DONE
  1541. return result # Generates its own list of (list of bindings, reason)s
  1542. else:
  1543. return []
  1544. if diag.chatty_flag > 30:
  1545. progress("Builtin could not give result"+`self`)
  1546. return [] # no solution
  1547. # @@@ remove dependency on 'heavy' above and remove heavy as param
  1548. except (IOError, SyntaxError):
  1549. raise BuiltInFailed(sys.exc_info(), self, pred ),None
  1550. def tryDeepSearch(self, queue, oldBindings=Env()):
  1551. """Search the store, unifying nested compound structures
  1552. Returns lists of list of bindings, attempting if necessary to unify
  1553. any nested compound terms. Added 20030810, not sure whether it is worth the
  1554. execution time in practice. It could be left to a special magic built-in like "matches"
  1555. or something if it is only occasionnaly used.
  1556. Used in state S_NEED_DEEP
  1557. """
  1558. nbs = [] # Assume failure
  1559. if self.short == INFINITY:
  1560. if diag.chatty_flag > 36:
  1561. progress( "Can't deep search for %s" % `self`)
  1562. else:
  1563. if diag.chatty_flag > 36:
  1564. progress( "Searching (S=%i) %i for %s" %(self.state, self.short, `self`))
  1565. try:
  1566. for s in self.myIndex:
  1567. pass
  1568. except:
  1569. print self.myIndex, self
  1570. raise
  1571. for s in self.myIndex : # for everything matching what we know,
  1572. if self.query.justReturn and s in queue.statements:
  1573. continue
  1574. env_queue = [(oldBindings, Env())]
  1575. ### Why not just unify here?
  1576. if diag.chatty_flag > 106: progress("...checking %r" % s)
  1577. for p in PRED, SUBJ, OBJ:
  1578. if self.searchPattern[p] == None: # Need to check
  1579. eq = env_queue
  1580. env_queue = []
  1581. for nb, env2 in eq:
  1582. x = self.quad[p]
  1583. if self.neededToRun[p] == Set([x]): # a term with no variables
  1584. if nb.canBind(x, s.quad[p]):
  1585. nb = nb.bind(x, (s.quad[p], env2))
  1586. env_queue.append((nb, env2))
  1587. else: # Deep case
  1588. if diag.chatty_flag > 70:
  1589. progress( "Deep: Unify %s with %s vars=%s; ee=%s" %
  1590. (x, s.quad[p], `self.query.variables`[4:-1],
  1591. `self.query._existentialVariables`[4:-1]))
  1592. for nb1, env3 in x.unify(s.quad[p], nb, env2, self.neededToRun[p]):
  1593. env_queue.append((nb1, env3))
  1594. ## nbs1 = x.unify(s.quad[p], self.neededToRun[p] & self.query.variables,
  1595. ## self.neededToRun[p] & self.query._existentialVariables, {}) # Bindings have all been bound
  1596. for nb, _ in env_queue:
  1597. nbs.append((nb, s)) # Add the new bindings into the set
  1598. self.searchDone() # State transitions
  1599. return nbs # End try deep search
  1600. def searchDone(self):
  1601. """Search has been done: figure out next state."""
  1602. con, pred, subj, obj = self.quad
  1603. if self.state == S_LIGHT_EARLY: # Light, can't run yet.
  1604. self.state = S_LIGHT_WAIT # Search done, can't run
  1605. elif self.state == S_LIGHT_UNS_READY: # Still have to run this light one
  1606. return
  1607. elif self.service:
  1608. self.state = S_REMOTE # Search done, need to synchronize with other items
  1609. elif not isinstance(pred, HeavyBuiltIn) or not self.interpretBuiltins:
  1610. self.state = S_DONE # Done with this one: Do new bindings & stop
  1611. elif self.canRun():
  1612. self.state = S_HEAVY_READY
  1613. else:
  1614. self.state = S_HEAVY_WAIT
  1615. if diag.chatty_flag > 90:
  1616. progress("...searchDone, now ",self)
  1617. return
  1618. def canRun(self):
  1619. "Is this built-in ready to run?"
  1620. if (self.neededToRun[SUBJ] == Set()):
  1621. if (self.neededToRun[OBJ] == Set()): return 1
  1622. else:
  1623. pred = self.quad[PRED]
  1624. return (isinstance(pred, Function)
  1625. or pred is self.store.includes
  1626. or pred is self.store.supports) # Can use variables
  1627. else:
  1628. if (self.neededToRun[OBJ] == Set()):
  1629. return isinstance(self.quad[PRED], ReverseFunction)
  1630. def bindNew(self, newBindings):
  1631. """Take into account new bindings from query processing to date
  1632. The search may get easier, and builtins may become ready to run.
  1633. Lists may either be matched against store by searching,
  1634. and/or may turn out to be bound and therefore ready to run.
  1635. Return:
  1636. 0 No way can this item ever complete.
  1637. None Binding done, see item.state
  1638. """
  1639. con, pred, subj, obj = self.quad
  1640. if diag.chatty_flag > 90:
  1641. progress(" binding ", `self` + " with "+ `newBindings`)
  1642. q=[con, pred, subj, obj]
  1643. for p in ALL4:
  1644. changed = 0
  1645. for var, val in newBindings.items():
  1646. if var in self.neededToRun[p]:
  1647. self.neededToRun[p].remove(var)
  1648. changed = 1
  1649. if q[p] is var and self.searchPattern[p]==None:
  1650. self.searchPattern[p] = val # const now
  1651. changed = 1
  1652. self.neededToRun[p].clear() # Now it is definitely all bound
  1653. if changed:
  1654. q[p] = q[p].substitution(newBindings.asDict(), why=becauseSubexpression) # possibly expensive
  1655. if self.searchPattern[p] != None: self.searchPattern[p] = q[p]
  1656. self.quad = q[0], q[1], q[2], q[3] # yuk
  1657. if self.state in [S_NOT_LIGHT, S_LIGHT_EARLY, S_NEED_DEEP, S_LIGHT_UNS_READY]: # Not searched yet
  1658. hasUnboundCoumpundTerm = 0
  1659. for p in PRED, SUBJ, OBJ :
  1660. x = self.quad[p]
  1661. if hasFormula(x): # expr @@ Set @@@@@@@@@@ Check and CompundTerm>???
  1662. # ur = x.freeVariables()
  1663. ur = x.occurringIn(self.neededToRun[p])
  1664. self.neededToRun[p] = ur
  1665. hasUnboundCoumpundTerm = 1 # Can't search directly
  1666. self.searchPattern[p] = None # can bind this if we recurse
  1667. ## for p in PRED, SUBJ, OBJ:
  1668. ## x = self.quad[p]
  1669. ## if isinstance(x, Formula):
  1670. ## if self.neededToRun[p]!= Set():
  1671. ## self.short = INFINITY # Forget it
  1672. ## break
  1673. else:
  1674. self.updateMyIndex(con)
  1675. if self.short == 0:
  1676. self.searchDone()
  1677. else:
  1678. self.short = 7700+self.state # Should not be used
  1679. self.myIndex = None
  1680. if isinstance(self.quad[PRED], BuiltIn):
  1681. if self.canRun():
  1682. if self.state == S_LIGHT_EARLY: self.state = S_LIGHT_UNS_READY
  1683. elif self.state == S_LIGHT_WAIT: self.state = S_LIGHT_GO
  1684. elif self.state == S_HEAVY_WAIT: self.state = S_HEAVY_READY
  1685. if diag.chatty_flag > 90:
  1686. progress("...bound becomes ", `self`)
  1687. if self.state == S_DONE: return []
  1688. return [({}, None)] # continue
  1689. def updateMyIndex(self, formula):
  1690. self.short, self.myIndex = formula.searchable(
  1691. self.searchPattern[SUBJ],
  1692. self.searchPattern[PRED],
  1693. self.searchPattern[OBJ])
  1694. if isinstance(self.quad[SUBJ], Formula) and self.short:
  1695. self.short = self.short * 3 + len(self.quad[SUBJ]) * 100
  1696. if isinstance(self.quad[OBJ], Formula) and self.short:
  1697. self.short = self.short * 3 + len(self.quad[OBJ]) * 100
  1698. for p in SUBJ, OBJ :
  1699. if isinstance(self.quad[p], Formula) and not self.neededToRun[p]:
  1700. newIndex = []
  1701. for triple in self.myIndex:
  1702. if isinstance(triple[p], Formula) and len(triple[p]) == len(self.quad[p]):
  1703. newIndex.append(triple)
  1704. self.myIndex = newIndex
  1705. ## self.myIndex = []
  1706. ## for triple in myIndex:
  1707. ## for loc in SUBJ, PRED, OBJ:
  1708. ## node = triple[loc]
  1709. ## if node in formula.variables() and self.searchPattern[loc] is not None:
  1710. ## break
  1711. ## else:
  1712. ## self.myIndex.append(triple)
  1713. def __repr__(self):
  1714. """Diagnostic string only"""
  1715. s = "%3i) short=%i, %s" % (
  1716. self.state, self.short,
  1717. quadToString(self.quad, self.neededToRun, self.searchPattern))
  1718. if self.service: s += (", servce=%s " % self.service)
  1719. return s
  1720. #### The new scheduler to run rules
  1721. class Event(object):
  1722. def __init__(self, parent, thunk):
  1723. self.thunk = thunk
  1724. if parent is not None:
  1725. parent.child = self
  1726. self.child = None
  1727. def run(self):
  1728. return thunk()
  1729. class Scheduler(object):
  1730. def __init__(self):
  1731. self.current = None
  1732. self.last = None
  1733. def add(self, thunk):
  1734. self.last = Event(self.last, thunk)
  1735. if self.current is None:
  1736. self.current = self.last
  1737. def __len__(self):
  1738. """We just want to know if we are empty"""
  1739. if self.current is None:
  1740. return 0
  1741. return 1
  1742. def next(self):
  1743. retVal = self.current
  1744. self.current = self.current.child
  1745. return retVal.thunk()
  1746. def run(self, func=None):
  1747. retVal = None
  1748. while self:
  1749. if func is None or retVal is None:
  1750. retVal = self.next()
  1751. else:
  1752. retVal = func(retVal, self.next())
  1753. return retVal
  1754. # This should probably be properties and methods of IndexedFormula
  1755. class RuleInstaller(object):
  1756. def __init__(self, task, ruleSource, variables):
  1757. self.task = task
  1758. self.ruleSource = ruleSource
  1759. self.variables = variables
  1760. def think(self):
  1761. rules = self.ruleSource.statementsMatching(pred=self.ruleSource.store.implies)
  1762. rules.addConsumer(self)
  1763. for rule in rules:
  1764. self.task.scheduleAttachRule(rule, self.ruleSource, self.variables)
  1765. def scheduleAddTriple(self, ruleList, rule):
  1766. self.task.scheduleAttachRule(rule, self.ruleSource, self.variables)
  1767. #### TOPO
  1768. ##############
  1769. # Compare two cyclic subsystem sto see which should be done first
  1770. #
  1771. #def compareCyclics(self,other):
  1772. # """Note the set indirectly affected is the same for any
  1773. # member of a cyclic subsystem"""
  1774. # if other[0] in self[0].indirectlyAffects:
  1775. # return -1 # Do me earlier
  1776. # if other[0] in self[0].indirectlyAffectedBy:
  1777. #
  1778. # return 0
  1779. #
  1780. ############# Substitution functions
  1781. def _substitute(bindings, list):
  1782. """Subsitustes IN-LINE into a list of quads"""
  1783. for i in range(len(list)):
  1784. q = list[i]
  1785. list[i] = lookupQuad(bindings, q)
  1786. def lookupQuad(bindings, q):
  1787. "Return a subsituted quad"
  1788. context, pred, subj, obj = q
  1789. return (bindings.get(context, context),
  1790. bindings.get(pred, pred),
  1791. bindings.get(subj, subj),
  1792. bindings.get(obj, obj))
  1793. def lookupQuadRecursive(bindings, q, why=None):
  1794. context, pred, subj, obj = q
  1795. if diag.chatty_flag > 99: progress("\tlookupQuadRecursive:", q)
  1796. return (
  1797. context.substitution(bindings),
  1798. pred.substitution(bindings),
  1799. subj.substitution(bindings),
  1800. obj.substitution(bindings) )
  1801. # DIAGNOSTIC STRING OUTPUT
  1802. #
  1803. def queueToString(queue):
  1804. str = ""
  1805. for item in queue:
  1806. str = str + `item` + "\n"
  1807. return str
  1808. def setToString(set):
  1809. str = ""
  1810. for q in set:
  1811. str = str+ " " + quadToString(q) + "\n"
  1812. return str
  1813. def quadToString(q, neededToRun=[[],[],[],[]], pattern=[1,1,1,1]):
  1814. qm=[" "," "," "," "]
  1815. for p in ALL4:
  1816. n = neededToRun[p]
  1817. if n == []: qm[p]=""
  1818. else: qm[p] = "(" + `n`[5:-2] + ")" # Set([...]) -> (...)
  1819. if pattern[p]==None: qm[p]=qm[p]+"?"
  1820. return "%s%s :: %8s%s %8s%s %8s%s." %(`q[CONTEXT]`, qm[CONTEXT],
  1821. `q[SUBJ]`,qm[SUBJ],
  1822. `q[PRED]`,qm[PRED],
  1823. `q[OBJ]`,qm[OBJ])
  1824. def seqToString(set):
  1825. # return `set`
  1826. set = list(set)
  1827. str = ""
  1828. for x in set[:-1]:
  1829. str = str + `x` + ","
  1830. for x in set[-1:]:
  1831. str = str+ `x`
  1832. return str
  1833. def bindingsToString(bindings):
  1834. # return `bindings`
  1835. str = ""
  1836. for x, y in bindings.items():
  1837. str = str + (" %s->%s " % ( `x`, `y`))
  1838. return str
  1839. class BecauseBuiltInWill(object):
  1840. def __init__(self, *args):
  1841. self.args = args
  1842. class BecauseSupportsWill(object):
  1843. def __init__(self, *args):
  1844. self.args = args
  1845. class BuiltInFailed(Exception):
  1846. def __init__(self, info, item, pred):
  1847. progress("BuiltIn %s FAILED" % pred, `info`)
  1848. self._item = item
  1849. self._info = info
  1850. self._pred = pred
  1851. def __str__(self):
  1852. reason = indentString(self._info[1].__str__())
  1853. # return "reason=" + reason
  1854. return ("Error during built-in operation\n%s\nbecause:\n%s" % (
  1855. `self._item`,
  1856. # `self._info`))
  1857. `reason`))
  1858. def hasFormula(l):
  1859. if not isinstance(l, (List, Formula)):
  1860. return False
  1861. if isinstance(l, Formula):
  1862. return True
  1863. for x in l:
  1864. if hasFormula(x):
  1865. return True
  1866. return False
  1867. from term import AnonymousNode, CompoundTerm
  1868. def smarterSubstitution(f, bindings, source, why=None, exception=[]):
  1869. bindings = bindings.asDict()
  1870. if f in exception:
  1871. return f
  1872. if isinstance(f, Formula):
  1873. f2 = f.newFormula()
  1874. newBindings, _ = f2.loadFormulaWithSubstitution(f, bindings, why=Because("I said so #2", why))
  1875. if f is not source:
  1876. newExistentials = f2.occurringIn(source.existentials().intersection(Set(bindings.values())))
  1877. for n in newExistentials:
  1878. f2.declareExistential(n)
  1879. ## for k in bindings.values():
  1880. ## if k not in newExistentials and isinstance(k, AnonymousNode) and not isinstance(k,CompoundTerm) and f2.occurringIn(Set([k])) and len(f2) < 20:
  1881. ## raise RuntimeError('How did I get here? newBindings=%s, bindings=%s, k=%s, f=%s, source=%s' % (newBindings, bindings, k, f2.debugString(), source.debugString()))
  1882. rr = f2.close()
  1883. return rr
  1884. rr = f.substitution(bindings, why=Because("I said so #3", why))
  1885. return rr
  1886. # ends