evaluate.py 25 KB

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