m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

428 lines
11 KiB

6 months ago
  1. """Logic expressions handling
  2. NOTE
  3. ----
  4. at present this is mainly needed for facts.py, feel free however to improve
  5. this stuff for general purpose.
  6. """
  7. from typing import Dict as tDict, Type, Union as tUnion
  8. # Type of a fuzzy bool
  9. FuzzyBool = tUnion[bool, None]
  10. def _torf(args):
  11. """Return True if all args are True, False if they
  12. are all False, else None.
  13. >>> from sympy.core.logic import _torf
  14. >>> _torf((True, True))
  15. True
  16. >>> _torf((False, False))
  17. False
  18. >>> _torf((True, False))
  19. """
  20. sawT = sawF = False
  21. for a in args:
  22. if a is True:
  23. if sawF:
  24. return
  25. sawT = True
  26. elif a is False:
  27. if sawT:
  28. return
  29. sawF = True
  30. else:
  31. return
  32. return sawT
  33. def _fuzzy_group(args, quick_exit=False):
  34. """Return True if all args are True, None if there is any None else False
  35. unless ``quick_exit`` is True (then return None as soon as a second False
  36. is seen.
  37. ``_fuzzy_group`` is like ``fuzzy_and`` except that it is more
  38. conservative in returning a False, waiting to make sure that all
  39. arguments are True or False and returning None if any arguments are
  40. None. It also has the capability of permiting only a single False and
  41. returning None if more than one is seen. For example, the presence of a
  42. single transcendental amongst rationals would indicate that the group is
  43. no longer rational; but a second transcendental in the group would make the
  44. determination impossible.
  45. Examples
  46. ========
  47. >>> from sympy.core.logic import _fuzzy_group
  48. By default, multiple Falses mean the group is broken:
  49. >>> _fuzzy_group([False, False, True])
  50. False
  51. If multiple Falses mean the group status is unknown then set
  52. `quick_exit` to True so None can be returned when the 2nd False is seen:
  53. >>> _fuzzy_group([False, False, True], quick_exit=True)
  54. But if only a single False is seen then the group is known to
  55. be broken:
  56. >>> _fuzzy_group([False, True, True], quick_exit=True)
  57. False
  58. """
  59. saw_other = False
  60. for a in args:
  61. if a is True:
  62. continue
  63. if a is None:
  64. return
  65. if quick_exit and saw_other:
  66. return
  67. saw_other = True
  68. return not saw_other
  69. def fuzzy_bool(x):
  70. """Return True, False or None according to x.
  71. Whereas bool(x) returns True or False, fuzzy_bool allows
  72. for the None value and non-false values (which become None), too.
  73. Examples
  74. ========
  75. >>> from sympy.core.logic import fuzzy_bool
  76. >>> from sympy.abc import x
  77. >>> fuzzy_bool(x), fuzzy_bool(None)
  78. (None, None)
  79. >>> bool(x), bool(None)
  80. (True, False)
  81. """
  82. if x is None:
  83. return None
  84. if x in (True, False):
  85. return bool(x)
  86. def fuzzy_and(args):
  87. """Return True (all True), False (any False) or None.
  88. Examples
  89. ========
  90. >>> from sympy.core.logic import fuzzy_and
  91. >>> from sympy import Dummy
  92. If you had a list of objects to test the commutivity of
  93. and you want the fuzzy_and logic applied, passing an
  94. iterator will allow the commutativity to only be computed
  95. as many times as necessary. With this list, False can be
  96. returned after analyzing the first symbol:
  97. >>> syms = [Dummy(commutative=False), Dummy()]
  98. >>> fuzzy_and(s.is_commutative for s in syms)
  99. False
  100. That False would require less work than if a list of pre-computed
  101. items was sent:
  102. >>> fuzzy_and([s.is_commutative for s in syms])
  103. False
  104. """
  105. rv = True
  106. for ai in args:
  107. ai = fuzzy_bool(ai)
  108. if ai is False:
  109. return False
  110. if rv: # this will stop updating if a None is ever trapped
  111. rv = ai
  112. return rv
  113. def fuzzy_not(v):
  114. """
  115. Not in fuzzy logic
  116. Return None if `v` is None else `not v`.
  117. Examples
  118. ========
  119. >>> from sympy.core.logic import fuzzy_not
  120. >>> fuzzy_not(True)
  121. False
  122. >>> fuzzy_not(None)
  123. >>> fuzzy_not(False)
  124. True
  125. """
  126. if v is None:
  127. return v
  128. else:
  129. return not v
  130. def fuzzy_or(args):
  131. """
  132. Or in fuzzy logic. Returns True (any True), False (all False), or None
  133. See the docstrings of fuzzy_and and fuzzy_not for more info. fuzzy_or is
  134. related to the two by the standard De Morgan's law.
  135. >>> from sympy.core.logic import fuzzy_or
  136. >>> fuzzy_or([True, False])
  137. True
  138. >>> fuzzy_or([True, None])
  139. True
  140. >>> fuzzy_or([False, False])
  141. False
  142. >>> print(fuzzy_or([False, None]))
  143. None
  144. """
  145. rv = False
  146. for ai in args:
  147. ai = fuzzy_bool(ai)
  148. if ai is True:
  149. return True
  150. if rv is False: # this will stop updating if a None is ever trapped
  151. rv = ai
  152. return rv
  153. def fuzzy_xor(args):
  154. """Return None if any element of args is not True or False, else
  155. True (if there are an odd number of True elements), else False."""
  156. t = f = 0
  157. for a in args:
  158. ai = fuzzy_bool(a)
  159. if ai:
  160. t += 1
  161. elif ai is False:
  162. f += 1
  163. else:
  164. return
  165. return t % 2 == 1
  166. def fuzzy_nand(args):
  167. """Return False if all args are True, True if they are all False,
  168. else None."""
  169. return fuzzy_not(fuzzy_and(args))
  170. class Logic:
  171. """Logical expression"""
  172. # {} 'op' -> LogicClass
  173. op_2class = {} # type: tDict[str, Type[Logic]]
  174. def __new__(cls, *args):
  175. obj = object.__new__(cls)
  176. obj.args = args
  177. return obj
  178. def __getnewargs__(self):
  179. return self.args
  180. def __hash__(self):
  181. return hash((type(self).__name__,) + tuple(self.args))
  182. def __eq__(a, b):
  183. if not isinstance(b, type(a)):
  184. return False
  185. else:
  186. return a.args == b.args
  187. def __ne__(a, b):
  188. if not isinstance(b, type(a)):
  189. return True
  190. else:
  191. return a.args != b.args
  192. def __lt__(self, other):
  193. if self.__cmp__(other) == -1:
  194. return True
  195. return False
  196. def __cmp__(self, other):
  197. if type(self) is not type(other):
  198. a = str(type(self))
  199. b = str(type(other))
  200. else:
  201. a = self.args
  202. b = other.args
  203. return (a > b) - (a < b)
  204. def __str__(self):
  205. return '%s(%s)' % (self.__class__.__name__,
  206. ', '.join(str(a) for a in self.args))
  207. __repr__ = __str__
  208. @staticmethod
  209. def fromstring(text):
  210. """Logic from string with space around & and | but none after !.
  211. e.g.
  212. !a & b | c
  213. """
  214. lexpr = None # current logical expression
  215. schedop = None # scheduled operation
  216. for term in text.split():
  217. # operation symbol
  218. if term in '&|':
  219. if schedop is not None:
  220. raise ValueError(
  221. 'double op forbidden: "%s %s"' % (term, schedop))
  222. if lexpr is None:
  223. raise ValueError(
  224. '%s cannot be in the beginning of expression' % term)
  225. schedop = term
  226. continue
  227. if '&' in term or '|' in term:
  228. raise ValueError('& and | must have space around them')
  229. if term[0] == '!':
  230. if len(term) == 1:
  231. raise ValueError('do not include space after "!"')
  232. term = Not(term[1:])
  233. # already scheduled operation, e.g. '&'
  234. if schedop:
  235. lexpr = Logic.op_2class[schedop](lexpr, term)
  236. schedop = None
  237. continue
  238. # this should be atom
  239. if lexpr is not None:
  240. raise ValueError(
  241. 'missing op between "%s" and "%s"' % (lexpr, term))
  242. lexpr = term
  243. # let's check that we ended up in correct state
  244. if schedop is not None:
  245. raise ValueError('premature end-of-expression in "%s"' % text)
  246. if lexpr is None:
  247. raise ValueError('"%s" is empty' % text)
  248. # everything looks good now
  249. return lexpr
  250. class AndOr_Base(Logic):
  251. def __new__(cls, *args):
  252. bargs = []
  253. for a in args:
  254. if a == cls.op_x_notx:
  255. return a
  256. elif a == (not cls.op_x_notx):
  257. continue # skip this argument
  258. bargs.append(a)
  259. args = sorted(set(cls.flatten(bargs)), key=hash)
  260. for a in args:
  261. if Not(a) in args:
  262. return cls.op_x_notx
  263. if len(args) == 1:
  264. return args.pop()
  265. elif len(args) == 0:
  266. return not cls.op_x_notx
  267. return Logic.__new__(cls, *args)
  268. @classmethod
  269. def flatten(cls, args):
  270. # quick-n-dirty flattening for And and Or
  271. args_queue = list(args)
  272. res = []
  273. while True:
  274. try:
  275. arg = args_queue.pop(0)
  276. except IndexError:
  277. break
  278. if isinstance(arg, Logic):
  279. if isinstance(arg, cls):
  280. args_queue.extend(arg.args)
  281. continue
  282. res.append(arg)
  283. args = tuple(res)
  284. return args
  285. class And(AndOr_Base):
  286. op_x_notx = False
  287. def _eval_propagate_not(self):
  288. # !(a&b&c ...) == !a | !b | !c ...
  289. return Or(*[Not(a) for a in self.args])
  290. # (a|b|...) & c == (a&c) | (b&c) | ...
  291. def expand(self):
  292. # first locate Or
  293. for i in range(len(self.args)):
  294. arg = self.args[i]
  295. if isinstance(arg, Or):
  296. arest = self.args[:i] + self.args[i + 1:]
  297. orterms = [And(*(arest + (a,))) for a in arg.args]
  298. for j in range(len(orterms)):
  299. if isinstance(orterms[j], Logic):
  300. orterms[j] = orterms[j].expand()
  301. res = Or(*orterms)
  302. return res
  303. return self
  304. class Or(AndOr_Base):
  305. op_x_notx = True
  306. def _eval_propagate_not(self):
  307. # !(a|b|c ...) == !a & !b & !c ...
  308. return And(*[Not(a) for a in self.args])
  309. class Not(Logic):
  310. def __new__(cls, arg):
  311. if isinstance(arg, str):
  312. return Logic.__new__(cls, arg)
  313. elif isinstance(arg, bool):
  314. return not arg
  315. elif isinstance(arg, Not):
  316. return arg.args[0]
  317. elif isinstance(arg, Logic):
  318. # XXX this is a hack to expand right from the beginning
  319. arg = arg._eval_propagate_not()
  320. return arg
  321. else:
  322. raise ValueError('Not: unknown argument %r' % (arg,))
  323. @property
  324. def arg(self):
  325. return self.args[0]
  326. Logic.op_2class['&'] = And
  327. Logic.op_2class['|'] = Or
  328. Logic.op_2class['!'] = Not