#!/usr/bin/env python
# coding: utf-8
#
#
#
#
# # MQL versus TF-Query
#
# See [TF versus MQL](tfVersusMql.ipynb) for an introduction.
# # Loading
#
# We load the Text-Fabric program and the BHSA data.
# In[1]:
get_ipython().run_line_magic('load_ext', 'autoreload')
get_ipython().run_line_magic('autoreload', '2')
# In[2]:
from tf.app import use
from util import getTfVerses, getShebanqData, compareResults, MQL_RESULTS
# In[3]:
VERSION = "2017"
# A = use('ETCBC/bhsa', hoist=globals(), version=VERSION)
A = use("ETCBC/bhsa:clone", checkout="clone", hoist=globals(), version=VERSION)
# # Example 9
#
# [Oliver Glanz: DHQ article: discourse pattern deviation](https://shebanq.ancient-data.org/hebrew/query?version=2017&id=491)
#
# ```
# [[clause domain = "N"
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ..
# [phrase FOCUS function = Subj
# [word AS samesubject]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word AS samecomplement]
# ]
# ]
# [clause domain = "N"]* {0-1}
# [clause domain = "Q"]* {1-50}
# [clause domain = "N"
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ..
# [phrase FOCUS function = Subj
# [word lex = samesubject.lex]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word lex = samecomplement.lex]
# ]
# ]]
# OR
# [[clause domain = "N"
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word AS samecomplement2]
# ]
# ..
# [phrase FOCUS function = Subj
# [word AS samesubject2]
# ]
# ]
# [clause domain = "N"]* {0-1}
# [clause domain = "Q"]* {1-50}
# [clause domain = "N"
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word lex = samecomplement2.lex]
# ]
# ..
# [phrase FOCUS function = Subj
# [word lex = samesubject2.lex]
# ]
# ]]
# OR
# [[clause domain = "N"
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ..
# [phrase FOCUS function = Subj
# [word AS samesubject3]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word AS samecomplement3]
# ]
# ]
# [clause domain = "N"]* {0-1}
# [clause domain = "Q"]* {1-50}
# [clause domain = "N"
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word lex = samecomplement3.lex]
# ]
# ..
# [phrase FOCUS function = Subj
# [word lex = samesubject3.lex]
# ]
# ]]
# OR
# [[clause domain = "N"
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word AS samecomplement4]
# ]
# ..
# [phrase FOCUS function = Subj
# [word AS samesubject4]
# ]
# ]
# [clause domain = "N"]* {0-1}
# [clause domain = "Q"]* {1-50}
# [clause domain = "N"
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ..
# [phrase FOCUS function = Subj
# [word lex = samesubject4.lex]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word lex = samecomplement4.lex]
# ]
# ]]
# ```
# In[4]:
(verses, words) = getShebanqData(A, MQL_RESULTS, 9)
# This is a complex query. Let's make it simpler first.
#
# We see some recurring objects:
#
#
# **`speakPhrase`**
#
# ```
# [phrase function = Pred
# [word
# [word lex = "DBR["]
# OR
# [word lex = ">MR["]
# OR
# [word lex = "QR>["]
# ]
# ]
# ```
#
# **`subjPhrase`**
#
# ```
# [phrase FOCUS function = Subj
# [word AS samesubject]
# ]
# ```
#
# **`cmplPhrase`**
#
# ```
# [phrase FOCUS function = Cmpl
# [word AS samecomplement]
# ]
# ```
#
# **`clauses`**
#
# ```
# [clause domain = "N"]* {0-1}
# [clause domain = "Q"]* {1-50}
# ```
# The structure of the whole query is then, in pseudo TF-query terms:
#
# ```
# clause domain=N
# speakPhrase
# < s:subjPhrase
# < c:cmplPhrase
# clauses
# clause domain=N
# speakPhrase
# < subjPhrase (.lex. s)
# < cmplPhrase (.lex. c)
# ```
#
# OR
#
# ```
# clause domain=N
# speakPhrase
# < c:cmplPhrase
# < s:subjPhrase
# clauses
# clause domain=N
# speakPhrase
# < cmplPhrase (.lex. c)
# < subjPhrase (.lex. s)
# ```
#
# OR
#
# ```
# clause domain=N
# speakPhrase
# < s:subjPhrase
# < c:cmplPhrase
# clauses
# clause domain=N
# speakPhrase
# < cmplPhrase (.lex. c)
# < subjPhrase (.lex. s)
# ```
#
# OR
#
# ```
# clause domain=N
# speakPhrase
# < c:cmplPhrase
# < s:subjPhrase
# clauses
# clause domain=N
# speakPhrase
# < subjPhrase (.lex. s)
# < cmplPhrase (.lex. c)
# ```
# The `OR` is only used to enumerate the four different orders between **`subjPhrase`** and **`cmplPhrase`**.
# So we can simplify greatly!
#
# ```
# clause domain=N
# sp1:speakPhrase
# < s1:subjPhrase
# c1:cmplPhrase
# < clauses
# < clause domain=N
# sp2:speakPhrase
# < s2:subjPhrase (.lex. s1)
# c2:cmplPhrase (.lex. c1)
#
# sp1 < c1
# sp2 < c2
# s1 .lex. s2
# c1 .lex. c2
# ```
# There is a problem with translating the **`clauses`** bit:
#
# ```
# [clause domain = "N"]* {0-1}
# [clause domain = "Q"]* {1-50}
# ```
#
# The operator `* {n-m}` means: repeat the previous block `n` to `m` times.
# In TF-Query there is no such operator.
#
# We will mimic this query by means of a mixture of TF-Query and hand-coding.
#
# We examine the results of searching for
#
# ```
# clause domain=N
# sp: speakPhrase
# < subjPhrase
# c:cmplPhrase
#
# sp < c
# ```
#
# By means of hand coding we walk through the results of this query:
#
# 1. suppose we are at a query result
# 1. walk through the following clauses as long as they match **`clauses`**
# 1. see if the next clause is a result of the query
# 1. check whether this clause and the one we started at in 1.
# agree lexically in their **`subjPhrase`** and **`cmplPhrase`**
# 1. for each such pair of results we add the combination to the result set
#
# We take care to deliver the results in the same way as a TF-query would do.
#
# **N.B.** What does *agree* mean in 4? According to the MQL query:
#
# ```
# [phrase FOCUS function = Subj
# [word AS samesubject]
# ]
# ..
# [phrase FOCUS function = Cmpl
# [word AS samecomplement]
# ]
# ```
#
# That means that two phrases *agree* if they both have a word which are an occurrence of the same lexeme.
# In short: they share a lexeme.
#
# That is a fairly relaxed condition: if both phrases have the article, or the same preposition, the condition
# is met. BUt that is what the query says, and we stick to that.
# In[5]:
query = """
clause domain=N
sp:phrase function=Pred
word lex=DBR[|>MR[|QR>[
< phrase function=Subj
c:phrase function=Cmpl
sp < c
"""
# In[6]:
speakResults = A.search(query)
# This is our starting point.
#
# We are going to weave these results together.
#
# The following function does that.
# It has to find up to 50 intervening clauses with `domain=Q` between
# two clauses with a speech verb.
#
# Let's parametrize this number 50, so that we can play with it later on.
# In[12]:
speakResultsIndex = {sr[0]: sr for sr in speakResults}
def weave(qLimit):
results = []
for speakResult in speakResults:
(clause, speakPhrase, speakWord, subjPhrase, cmplPhrase) = speakResult
nextClause = L.n(clause, otype="clause")
if not nextClause:
continue
nextClause = nextClause[0]
domain = F.domain.v(nextClause)
qSeen = domain == "Q"
if not qSeen and domain != "N":
continue
if not qSeen:
nextClause = L.n(nextClause, otype="clause")
if not nextClause:
continue
nextClause = nextClause[0]
domain = F.domain.v(nextClause)
qSeen = domain == "Q"
if not qSeen:
continue
qs = 1
while qs <= qLimit:
nextClause = L.n(nextClause, otype="clause")
if not nextClause:
break
nextClause = nextClause[0]
domain = F.domain.v(nextClause)
if domain != "Q":
break
qs += 1
if not nextClause:
continue
if domain != "N":
continue
if nextClause not in speakResultsIndex:
continue
nextSpeakResult = speakResultsIndex[nextClause]
(
nextClause,
nextSpeakPhrase,
nextSpeakWord,
nextSubjPhrase,
nextCmplPhrase,
) = nextSpeakResult
# here we implement the "agree" bit. Note that & means: set intersection.
if (
{F.lex.v(w) for w in L.d(subjPhrase, otype="word")}
& {F.lex.v(w) for w in L.d(nextSubjPhrase, otype="word")}
) and (
{F.lex.v(w) for w in L.d(cmplPhrase, otype="word")}
& {F.lex.v(w) for w in L.d(nextCmplPhrase, otype="word")}
):
# note that we add the number of Q-clauses at the end of each result tuple
results.append(
(
clause,
speakPhrase,
subjPhrase,
cmplPhrase,
nextSubjPhrase,
nextCmplPhrase,
qs,
)
)
print(f"qLimit={qLimit}: {len(results)} results")
return results
# In[9]:
results = weave(50)
# In[10]:
(tfVerses, tfWords) = getTfVerses(A, results, (2, 3, 4, 5))
# In[11]:
compareResults(A, verses, words, tfVerses, tfWords)
# What if we allowed only strings of 49 Q-clauses?
# Would that matter?
#
# It would be nice if we could see the number of Q-clauses in each result.
# Well, we have sneaked that in already!
# It is added at the end of each result tuple.
#
# Here are the minimum and the maximum that we encountered:
# In[13]:
print(f"minimum number of Q-clauses: {min(r[-1] for r in results):>2}")
print(f"maximum number of Q-clauses: {max(r[-1] for r in results):>2}")
# So we expect that it does matter if we go from 50 to 49.
# Before we test that, let us show all Q-lengths:
# In[14]:
for r in results:
startPhrase = min(r[2], r[3])
startVerse = T.sectionFromNode(L.u(startPhrase, otype="verse")[0])
startString = "{} {}:{}".format(*startVerse)
endPhrase = min(r[4], r[5])
endVerse = T.sectionFromNode(L.u(endPhrase, otype="verse")[0])
endString = "{} {}:{}".format(*endVerse)
qs = r[-1]
print(f"{startString:<20} == {qs:>2} Q-clauses ==> {endString}")
# Let's double-check: if we allow only strings of Q-clauses up to length 49, the result in Leviticus 21:1
# should be gone.
# In[16]:
results = weave(49)
# In[17]:
(tfVerses, tfWords) = getTfVerses(A, results, (2, 3, 4, 5))
# In[18]:
compareResults(A, verses, words, tfVerses, tfWords)
# And so it is!
# **Conclusion**
#
# Instead of running a query and obtaining a list of results,
# we did a bit of programming and we can get much more than just the results.
#
# That is the power of programming.
# But programming is difficult, and mistakes will be made.
#
# TF-Query helps you to find a sweet spot between crafting queries
# and writing code.