evaluate.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. # Natural Language Toolkit: Models for first-order languages with lambda
  2. #
  3. # Copyright (C) 2001-2020 NLTK Project
  4. # Author: Ewan Klein <ewan@inf.ed.ac.uk>,
  5. # URL: <http://nltk.sourceforge.net>
  6. # For license information, see LICENSE.TXT
  7. # TODO:
  8. # - fix tracing
  9. # - fix iterator-based approach to existentials
  10. """
  11. This module provides data structures for representing first-order
  12. models.
  13. """
  14. from pprint import pformat
  15. import inspect
  16. import textwrap
  17. import re
  18. import sys
  19. from nltk.decorators import decorator # this used in code that is commented out
  20. from nltk.sem.logic import (
  21. AbstractVariableExpression,
  22. AllExpression,
  23. Expression,
  24. AndExpression,
  25. ApplicationExpression,
  26. EqualityExpression,
  27. ExistsExpression,
  28. IffExpression,
  29. ImpExpression,
  30. IndividualVariableExpression,
  31. LambdaExpression,
  32. NegatedExpression,
  33. OrExpression,
  34. Variable,
  35. is_indvar,
  36. )
  37. class Error(Exception):
  38. pass
  39. class Undefined(Error):
  40. pass
  41. def trace(f, *args, **kw):
  42. argspec = inspect.getfullargspec(f)
  43. d = dict(zip(argspec[0], args))
  44. if d.pop("trace", None):
  45. print()
  46. for item in d.items():
  47. print("%s => %s" % item)
  48. return f(*args, **kw)
  49. def is_rel(s):
  50. """
  51. Check whether a set represents a relation (of any arity).
  52. :param s: a set containing tuples of str elements
  53. :type s: set
  54. :rtype: bool
  55. """
  56. # we have the empty relation, i.e. set()
  57. if len(s) == 0:
  58. return True
  59. # all the elements are tuples of the same length
  60. elif all(isinstance(el, tuple) for el in s) and len(max(s)) == len(min(s)):
  61. return True
  62. else:
  63. raise ValueError("Set %r contains sequences of different lengths" % s)
  64. def set2rel(s):
  65. """
  66. Convert a set containing individuals (strings or numbers) into a set of
  67. unary tuples. Any tuples of strings already in the set are passed through
  68. unchanged.
  69. For example:
  70. - set(['a', 'b']) => set([('a',), ('b',)])
  71. - set([3, 27]) => set([('3',), ('27',)])
  72. :type s: set
  73. :rtype: set of tuple of str
  74. """
  75. new = set()
  76. for elem in s:
  77. if isinstance(elem, str):
  78. new.add((elem,))
  79. elif isinstance(elem, int):
  80. new.add((str(elem)))
  81. else:
  82. new.add(elem)
  83. return new
  84. def arity(rel):
  85. """
  86. Check the arity of a relation.
  87. :type rel: set of tuples
  88. :rtype: int of tuple of str
  89. """
  90. if len(rel) == 0:
  91. return 0
  92. return len(list(rel)[0])
  93. class Valuation(dict):
  94. """
  95. A dictionary which represents a model-theoretic Valuation of non-logical constants.
  96. Keys are strings representing the constants to be interpreted, and values correspond
  97. to individuals (represented as strings) and n-ary relations (represented as sets of tuples
  98. of strings).
  99. An instance of ``Valuation`` will raise a KeyError exception (i.e.,
  100. just behave like a standard dictionary) if indexed with an expression that
  101. is not in its list of symbols.
  102. """
  103. def __init__(self, xs):
  104. """
  105. :param xs: a list of (symbol, value) pairs.
  106. """
  107. super(Valuation, self).__init__()
  108. for (sym, val) in xs:
  109. if isinstance(val, str) or isinstance(val, bool):
  110. self[sym] = val
  111. elif isinstance(val, set):
  112. self[sym] = set2rel(val)
  113. else:
  114. msg = textwrap.fill(
  115. "Error in initializing Valuation. "
  116. "Unrecognized value for symbol '%s':\n%s" % (sym, val),
  117. width=66,
  118. )
  119. raise ValueError(msg)
  120. def __getitem__(self, key):
  121. if key in self:
  122. return dict.__getitem__(self, key)
  123. else:
  124. raise Undefined("Unknown expression: '%s'" % key)
  125. def __str__(self):
  126. return pformat(self)
  127. @property
  128. def domain(self):
  129. """Set-theoretic domain of the value-space of a Valuation."""
  130. dom = []
  131. for val in self.values():
  132. if isinstance(val, str):
  133. dom.append(val)
  134. elif not isinstance(val, bool):
  135. dom.extend(
  136. [elem for tuple_ in val for elem in tuple_ if elem is not None]
  137. )
  138. return set(dom)
  139. @property
  140. def symbols(self):
  141. """The non-logical constants which the Valuation recognizes."""
  142. return sorted(self.keys())
  143. @classmethod
  144. def fromstring(cls, s):
  145. return read_valuation(s)
  146. ##########################################
  147. # REs used by the _read_valuation function
  148. ##########################################
  149. _VAL_SPLIT_RE = re.compile(r"\s*=+>\s*")
  150. _ELEMENT_SPLIT_RE = re.compile(r"\s*,\s*")
  151. _TUPLES_RE = re.compile(
  152. r"""\s*
  153. (\([^)]+\)) # tuple-expression
  154. \s*""",
  155. re.VERBOSE,
  156. )
  157. def _read_valuation_line(s):
  158. """
  159. Read a line in a valuation file.
  160. Lines are expected to be of the form::
  161. noosa => n
  162. girl => {g1, g2}
  163. chase => {(b1, g1), (b2, g1), (g1, d1), (g2, d2)}
  164. :param s: input line
  165. :type s: str
  166. :return: a pair (symbol, value)
  167. :rtype: tuple
  168. """
  169. pieces = _VAL_SPLIT_RE.split(s)
  170. symbol = pieces[0]
  171. value = pieces[1]
  172. # check whether the value is meant to be a set
  173. if value.startswith("{"):
  174. value = value[1:-1]
  175. tuple_strings = _TUPLES_RE.findall(value)
  176. # are the set elements tuples?
  177. if tuple_strings:
  178. set_elements = []
  179. for ts in tuple_strings:
  180. ts = ts[1:-1]
  181. element = tuple(_ELEMENT_SPLIT_RE.split(ts))
  182. set_elements.append(element)
  183. else:
  184. set_elements = _ELEMENT_SPLIT_RE.split(value)
  185. value = set(set_elements)
  186. return symbol, value
  187. def read_valuation(s, encoding=None):
  188. """
  189. Convert a valuation string into a valuation.
  190. :param s: a valuation string
  191. :type s: str
  192. :param encoding: the encoding of the input string, if it is binary
  193. :type encoding: str
  194. :return: a ``nltk.sem`` valuation
  195. :rtype: Valuation
  196. """
  197. if encoding is not None:
  198. s = s.decode(encoding)
  199. statements = []
  200. for linenum, line in enumerate(s.splitlines()):
  201. line = line.strip()
  202. if line.startswith("#") or line == "":
  203. continue
  204. try:
  205. statements.append(_read_valuation_line(line))
  206. except ValueError:
  207. raise ValueError("Unable to parse line %s: %s" % (linenum, line))
  208. return Valuation(statements)
  209. class Assignment(dict):
  210. """
  211. A dictionary which represents an assignment of values to variables.
  212. An assigment can only assign values from its domain.
  213. If an unknown expression *a* is passed to a model *M*\ 's
  214. interpretation function *i*, *i* will first check whether *M*\ 's
  215. valuation assigns an interpretation to *a* as a constant, and if
  216. this fails, *i* will delegate the interpretation of *a* to
  217. *g*. *g* only assigns values to individual variables (i.e.,
  218. members of the class ``IndividualVariableExpression`` in the ``logic``
  219. module. If a variable is not assigned a value by *g*, it will raise
  220. an ``Undefined`` exception.
  221. A variable *Assignment* is a mapping from individual variables to
  222. entities in the domain. Individual variables are usually indicated
  223. with the letters ``'x'``, ``'y'``, ``'w'`` and ``'z'``, optionally
  224. followed by an integer (e.g., ``'x0'``, ``'y332'``). Assignments are
  225. created using the ``Assignment`` constructor, which also takes the
  226. domain as a parameter.
  227. >>> from nltk.sem.evaluate import Assignment
  228. >>> dom = set(['u1', 'u2', 'u3', 'u4'])
  229. >>> g3 = Assignment(dom, [('x', 'u1'), ('y', 'u2')])
  230. >>> g3 == {'x': 'u1', 'y': 'u2'}
  231. True
  232. There is also a ``print`` format for assignments which uses a notation
  233. closer to that in logic textbooks:
  234. >>> print(g3)
  235. g[u1/x][u2/y]
  236. It is also possible to update an assignment using the ``add`` method:
  237. >>> dom = set(['u1', 'u2', 'u3', 'u4'])
  238. >>> g4 = Assignment(dom)
  239. >>> g4.add('x', 'u1')
  240. {'x': 'u1'}
  241. With no arguments, ``purge()`` is equivalent to ``clear()`` on a dictionary:
  242. >>> g4.purge()
  243. >>> g4
  244. {}
  245. :param domain: the domain of discourse
  246. :type domain: set
  247. :param assign: a list of (varname, value) associations
  248. :type assign: list
  249. """
  250. def __init__(self, domain, assign=None):
  251. super(Assignment, self).__init__()
  252. self.domain = domain
  253. if assign:
  254. for (var, val) in assign:
  255. assert val in self.domain, "'%s' is not in the domain: %s" % (
  256. val,
  257. self.domain,
  258. )
  259. assert is_indvar(var), (
  260. "Wrong format for an Individual Variable: '%s'" % var
  261. )
  262. self[var] = val
  263. self.variant = None
  264. self._addvariant()
  265. def __getitem__(self, key):
  266. if key in self:
  267. return dict.__getitem__(self, key)
  268. else:
  269. raise Undefined("Not recognized as a variable: '%s'" % key)
  270. def copy(self):
  271. new = Assignment(self.domain)
  272. new.update(self)
  273. return new
  274. def purge(self, var=None):
  275. """
  276. Remove one or all keys (i.e. logic variables) from an
  277. assignment, and update ``self.variant``.
  278. :param var: a Variable acting as a key for the assignment.
  279. """
  280. if var:
  281. del self[var]
  282. else:
  283. self.clear()
  284. self._addvariant()
  285. return None
  286. def __str__(self):
  287. """
  288. Pretty printing for assignments. {'x', 'u'} appears as 'g[u/x]'
  289. """
  290. gstring = "g"
  291. # Deterministic output for unit testing.
  292. variant = sorted(self.variant)
  293. for (val, var) in variant:
  294. gstring += "[%s/%s]" % (val, var)
  295. return gstring
  296. def _addvariant(self):
  297. """
  298. Create a more pretty-printable version of the assignment.
  299. """
  300. list_ = []
  301. for item in self.items():
  302. pair = (item[1], item[0])
  303. list_.append(pair)
  304. self.variant = list_
  305. return None
  306. def add(self, var, val):
  307. """
  308. Add a new variable-value pair to the assignment, and update
  309. ``self.variant``.
  310. """
  311. assert val in self.domain, "%s is not in the domain %s" % (val, self.domain)
  312. assert is_indvar(var), "Wrong format for an Individual Variable: '%s'" % var
  313. self[var] = val
  314. self._addvariant()
  315. return self
  316. class Model(object):
  317. """
  318. A first order model is a domain *D* of discourse and a valuation *V*.
  319. A domain *D* is a set, and a valuation *V* is a map that associates
  320. expressions with values in the model.
  321. The domain of *V* should be a subset of *D*.
  322. Construct a new ``Model``.
  323. :type domain: set
  324. :param domain: A set of entities representing the domain of discourse of the model.
  325. :type valuation: Valuation
  326. :param valuation: the valuation of the model.
  327. :param prop: If this is set, then we are building a propositional\
  328. model and don't require the domain of *V* to be subset of *D*.
  329. """
  330. def __init__(self, domain, valuation):
  331. assert isinstance(domain, set)
  332. self.domain = domain
  333. self.valuation = valuation
  334. if not domain.issuperset(valuation.domain):
  335. raise Error(
  336. "The valuation domain, %s, must be a subset of the model's domain, %s"
  337. % (valuation.domain, domain)
  338. )
  339. def __repr__(self):
  340. return "(%r, %r)" % (self.domain, self.valuation)
  341. def __str__(self):
  342. return "Domain = %s,\nValuation = \n%s" % (self.domain, self.valuation)
  343. def evaluate(self, expr, g, trace=None):
  344. """
  345. Read input expressions, and provide a handler for ``satisfy``
  346. that blocks further propagation of the ``Undefined`` error.
  347. :param expr: An ``Expression`` of ``logic``.
  348. :type g: Assignment
  349. :param g: an assignment to individual variables.
  350. :rtype: bool or 'Undefined'
  351. """
  352. try:
  353. parsed = Expression.fromstring(expr)
  354. value = self.satisfy(parsed, g, trace=trace)
  355. if trace:
  356. print()
  357. print("'%s' evaluates to %s under M, %s" % (expr, value, g))
  358. return value
  359. except Undefined:
  360. if trace:
  361. print()
  362. print("'%s' is undefined under M, %s" % (expr, g))
  363. return "Undefined"
  364. def satisfy(self, parsed, g, trace=None):
  365. """
  366. Recursive interpretation function for a formula of first-order logic.
  367. Raises an ``Undefined`` error when ``parsed`` is an atomic string
  368. but is not a symbol or an individual variable.
  369. :return: Returns a truth value or ``Undefined`` if ``parsed`` is\
  370. complex, and calls the interpretation function ``i`` if ``parsed``\
  371. is atomic.
  372. :param parsed: An expression of ``logic``.
  373. :type g: Assignment
  374. :param g: an assignment to individual variables.
  375. """
  376. if isinstance(parsed, ApplicationExpression):
  377. function, arguments = parsed.uncurry()
  378. if isinstance(function, AbstractVariableExpression):
  379. # It's a predicate expression ("P(x,y)"), so used uncurried arguments
  380. funval = self.satisfy(function, g)
  381. argvals = tuple(self.satisfy(arg, g) for arg in arguments)
  382. return argvals in funval
  383. else:
  384. # It must be a lambda expression, so use curried form
  385. funval = self.satisfy(parsed.function, g)
  386. argval = self.satisfy(parsed.argument, g)
  387. return funval[argval]
  388. elif isinstance(parsed, NegatedExpression):
  389. return not self.satisfy(parsed.term, g)
  390. elif isinstance(parsed, AndExpression):
  391. return self.satisfy(parsed.first, g) and self.satisfy(parsed.second, g)
  392. elif isinstance(parsed, OrExpression):
  393. return self.satisfy(parsed.first, g) or self.satisfy(parsed.second, g)
  394. elif isinstance(parsed, ImpExpression):
  395. return (not self.satisfy(parsed.first, g)) or self.satisfy(parsed.second, g)
  396. elif isinstance(parsed, IffExpression):
  397. return self.satisfy(parsed.first, g) == self.satisfy(parsed.second, g)
  398. elif isinstance(parsed, EqualityExpression):
  399. return self.satisfy(parsed.first, g) == self.satisfy(parsed.second, g)
  400. elif isinstance(parsed, AllExpression):
  401. new_g = g.copy()
  402. for u in self.domain:
  403. new_g.add(parsed.variable.name, u)
  404. if not self.satisfy(parsed.term, new_g):
  405. return False
  406. return True
  407. elif isinstance(parsed, ExistsExpression):
  408. new_g = g.copy()
  409. for u in self.domain:
  410. new_g.add(parsed.variable.name, u)
  411. if self.satisfy(parsed.term, new_g):
  412. return True
  413. return False
  414. elif isinstance(parsed, LambdaExpression):
  415. cf = {}
  416. var = parsed.variable.name
  417. for u in self.domain:
  418. val = self.satisfy(parsed.term, g.add(var, u))
  419. # NB the dict would be a lot smaller if we do this:
  420. # if val: cf[u] = val
  421. # But then need to deal with cases where f(a) should yield
  422. # a function rather than just False.
  423. cf[u] = val
  424. return cf
  425. else:
  426. return self.i(parsed, g, trace)
  427. # @decorator(trace_eval)
  428. def i(self, parsed, g, trace=False):
  429. """
  430. An interpretation function.
  431. Assuming that ``parsed`` is atomic:
  432. - if ``parsed`` is a non-logical constant, calls the valuation *V*
  433. - else if ``parsed`` is an individual variable, calls assignment *g*
  434. - else returns ``Undefined``.
  435. :param parsed: an ``Expression`` of ``logic``.
  436. :type g: Assignment
  437. :param g: an assignment to individual variables.
  438. :return: a semantic value
  439. """
  440. # If parsed is a propositional letter 'p', 'q', etc, it could be in valuation.symbols
  441. # and also be an IndividualVariableExpression. We want to catch this first case.
  442. # So there is a procedural consequence to the ordering of clauses here:
  443. if parsed.variable.name in self.valuation.symbols:
  444. return self.valuation[parsed.variable.name]
  445. elif isinstance(parsed, IndividualVariableExpression):
  446. return g[parsed.variable.name]
  447. else:
  448. raise Undefined("Can't find a value for %s" % parsed)
  449. def satisfiers(self, parsed, varex, g, trace=None, nesting=0):
  450. """
  451. Generate the entities from the model's domain that satisfy an open formula.
  452. :param parsed: an open formula
  453. :type parsed: Expression
  454. :param varex: the relevant free individual variable in ``parsed``.
  455. :type varex: VariableExpression or str
  456. :param g: a variable assignment
  457. :type g: Assignment
  458. :return: a set of the entities that satisfy ``parsed``.
  459. """
  460. spacer = " "
  461. indent = spacer + (spacer * nesting)
  462. candidates = []
  463. if isinstance(varex, str):
  464. var = Variable(varex)
  465. else:
  466. var = varex
  467. if var in parsed.free():
  468. if trace:
  469. print()
  470. print(
  471. (spacer * nesting)
  472. + "Open formula is '%s' with assignment %s" % (parsed, g)
  473. )
  474. for u in self.domain:
  475. new_g = g.copy()
  476. new_g.add(var.name, u)
  477. if trace and trace > 1:
  478. lowtrace = trace - 1
  479. else:
  480. lowtrace = 0
  481. value = self.satisfy(parsed, new_g, lowtrace)
  482. if trace:
  483. print(indent + "(trying assignment %s)" % new_g)
  484. # parsed == False under g[u/var]?
  485. if value == False:
  486. if trace:
  487. print(
  488. indent + "value of '%s' under %s is False" % (parsed, new_g)
  489. )
  490. # so g[u/var] is a satisfying assignment
  491. else:
  492. candidates.append(u)
  493. if trace:
  494. print(
  495. indent
  496. + "value of '%s' under %s is %s" % (parsed, new_g, value)
  497. )
  498. result = set(c for c in candidates)
  499. # var isn't free in parsed
  500. else:
  501. raise Undefined("%s is not free in %s" % (var.name, parsed))
  502. return result
  503. # //////////////////////////////////////////////////////////////////////
  504. # Demo..
  505. # //////////////////////////////////////////////////////////////////////
  506. # number of spacer chars
  507. mult = 30
  508. # Demo 1: Propositional Logic
  509. #################
  510. def propdemo(trace=None):
  511. """Example of a propositional model."""
  512. global val1, dom1, m1, g1
  513. val1 = Valuation([("P", True), ("Q", True), ("R", False)])
  514. dom1 = set([])
  515. m1 = Model(dom1, val1)
  516. g1 = Assignment(dom1)
  517. print()
  518. print("*" * mult)
  519. print("Propositional Formulas Demo")
  520. print("*" * mult)
  521. print("(Propositional constants treated as nullary predicates)")
  522. print()
  523. print("Model m1:\n", m1)
  524. print("*" * mult)
  525. sentences = [
  526. "(P & Q)",
  527. "(P & R)",
  528. "- P",
  529. "- R",
  530. "- - P",
  531. "- (P & R)",
  532. "(P | R)",
  533. "(R | P)",
  534. "(R | R)",
  535. "(- P | R)",
  536. "(P | - P)",
  537. "(P -> Q)",
  538. "(P -> R)",
  539. "(R -> P)",
  540. "(P <-> P)",
  541. "(R <-> R)",
  542. "(P <-> R)",
  543. ]
  544. for sent in sentences:
  545. if trace:
  546. print()
  547. m1.evaluate(sent, g1, trace)
  548. else:
  549. print("The value of '%s' is: %s" % (sent, m1.evaluate(sent, g1)))
  550. # Demo 2: FOL Model
  551. #############
  552. def folmodel(quiet=False, trace=None):
  553. """Example of a first-order model."""
  554. global val2, v2, dom2, m2, g2
  555. v2 = [
  556. ("adam", "b1"),
  557. ("betty", "g1"),
  558. ("fido", "d1"),
  559. ("girl", set(["g1", "g2"])),
  560. ("boy", set(["b1", "b2"])),
  561. ("dog", set(["d1"])),
  562. ("love", set([("b1", "g1"), ("b2", "g2"), ("g1", "b1"), ("g2", "b1")])),
  563. ]
  564. val2 = Valuation(v2)
  565. dom2 = val2.domain
  566. m2 = Model(dom2, val2)
  567. g2 = Assignment(dom2, [("x", "b1"), ("y", "g2")])
  568. if not quiet:
  569. print()
  570. print("*" * mult)
  571. print("Models Demo")
  572. print("*" * mult)
  573. print("Model m2:\n", "-" * 14, "\n", m2)
  574. print("Variable assignment = ", g2)
  575. exprs = ["adam", "boy", "love", "walks", "x", "y", "z"]
  576. parsed_exprs = [Expression.fromstring(e) for e in exprs]
  577. print()
  578. for parsed in parsed_exprs:
  579. try:
  580. print(
  581. "The interpretation of '%s' in m2 is %s"
  582. % (parsed, m2.i(parsed, g2))
  583. )
  584. except Undefined:
  585. print("The interpretation of '%s' in m2 is Undefined" % parsed)
  586. applications = [
  587. ("boy", ("adam")),
  588. ("walks", ("adam",)),
  589. ("love", ("adam", "y")),
  590. ("love", ("y", "adam")),
  591. ]
  592. for (fun, args) in applications:
  593. try:
  594. funval = m2.i(Expression.fromstring(fun), g2)
  595. argsval = tuple(m2.i(Expression.fromstring(arg), g2) for arg in args)
  596. print("%s(%s) evaluates to %s" % (fun, args, argsval in funval))
  597. except Undefined:
  598. print("%s(%s) evaluates to Undefined" % (fun, args))
  599. # Demo 3: FOL
  600. #########
  601. def foldemo(trace=None):
  602. """
  603. Interpretation of closed expressions in a first-order model.
  604. """
  605. folmodel(quiet=True)
  606. print()
  607. print("*" * mult)
  608. print("FOL Formulas Demo")
  609. print("*" * mult)
  610. formulas = [
  611. "love (adam, betty)",
  612. "(adam = mia)",
  613. "\\x. (boy(x) | girl(x))",
  614. "\\x. boy(x)(adam)",
  615. "\\x y. love(x, y)",
  616. "\\x y. love(x, y)(adam)(betty)",
  617. "\\x y. love(x, y)(adam, betty)",
  618. "\\x y. (boy(x) & love(x, y))",
  619. "\\x. exists y. (boy(x) & love(x, y))",
  620. "exists z1. boy(z1)",
  621. "exists x. (boy(x) & -(x = adam))",
  622. "exists x. (boy(x) & all y. love(y, x))",
  623. "all x. (boy(x) | girl(x))",
  624. "all x. (girl(x) -> exists y. boy(y) & love(x, y))", # Every girl loves exists boy.
  625. "exists x. (boy(x) & all y. (girl(y) -> love(y, x)))", # There is exists boy that every girl loves.
  626. "exists x. (boy(x) & all y. (girl(y) -> love(x, y)))", # exists boy loves every girl.
  627. "all x. (dog(x) -> - girl(x))",
  628. "exists x. exists y. (love(x, y) & love(x, y))",
  629. ]
  630. for fmla in formulas:
  631. g2.purge()
  632. if trace:
  633. m2.evaluate(fmla, g2, trace)
  634. else:
  635. print("The value of '%s' is: %s" % (fmla, m2.evaluate(fmla, g2)))
  636. # Demo 3: Satisfaction
  637. #############
  638. def satdemo(trace=None):
  639. """Satisfiers of an open formula in a first order model."""
  640. print()
  641. print("*" * mult)
  642. print("Satisfiers Demo")
  643. print("*" * mult)
  644. folmodel(quiet=True)
  645. formulas = [
  646. "boy(x)",
  647. "(x = x)",
  648. "(boy(x) | girl(x))",
  649. "(boy(x) & girl(x))",
  650. "love(adam, x)",
  651. "love(x, adam)",
  652. "-(x = adam)",
  653. "exists z22. love(x, z22)",
  654. "exists y. love(y, x)",
  655. "all y. (girl(y) -> love(x, y))",
  656. "all y. (girl(y) -> love(y, x))",
  657. "all y. (girl(y) -> (boy(x) & love(y, x)))",
  658. "(boy(x) & all y. (girl(y) -> love(x, y)))",
  659. "(boy(x) & all y. (girl(y) -> love(y, x)))",
  660. "(boy(x) & exists y. (girl(y) & love(y, x)))",
  661. "(girl(x) -> dog(x))",
  662. "all y. (dog(y) -> (x = y))",
  663. "exists y. love(y, x)",
  664. "exists y. (love(adam, y) & love(y, x))",
  665. ]
  666. if trace:
  667. print(m2)
  668. for fmla in formulas:
  669. print(fmla)
  670. Expression.fromstring(fmla)
  671. parsed = [Expression.fromstring(fmla) for fmla in formulas]
  672. for p in parsed:
  673. g2.purge()
  674. print("The satisfiers of '%s' are: %s" % (p, m2.satisfiers(p, "x", g2, trace)))
  675. def demo(num=0, trace=None):
  676. """
  677. Run exists demos.
  678. - num = 1: propositional logic demo
  679. - num = 2: first order model demo (only if trace is set)
  680. - num = 3: first order sentences demo
  681. - num = 4: satisfaction of open formulas demo
  682. - any other value: run all the demos
  683. :param trace: trace = 1, or trace = 2 for more verbose tracing
  684. """
  685. demos = {1: propdemo, 2: folmodel, 3: foldemo, 4: satdemo}
  686. try:
  687. demos[num](trace=trace)
  688. except KeyError:
  689. for num in demos:
  690. demos[num](trace=trace)
  691. if __name__ == "__main__":
  692. demo(2, trace=0)