unicode-logic-kit 0.31.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. unicode_logic_kit/__init__.py +385 -0
  2. unicode_logic_kit/__main__.py +520 -0
  3. unicode_logic_kit/_deadline.py +219 -0
  4. unicode_logic_kit/ace/__init__.py +126 -0
  5. unicode_logic_kit/ace/_align.py +135 -0
  6. unicode_logic_kit/ace/chem_lexicon.py +128 -0
  7. unicode_logic_kit/ace/drs_reader.py +570 -0
  8. unicode_logic_kit/ace/mapping.py +666 -0
  9. unicode_logic_kit/ace/reverse_modal.py +138 -0
  10. unicode_logic_kit/ace/runner.py +551 -0
  11. unicode_logic_kit/ace/translate.py +452 -0
  12. unicode_logic_kit/ace/verbalize.py +1070 -0
  13. unicode_logic_kit/api.py +1284 -0
  14. unicode_logic_kit/atp/__init__.py +177 -0
  15. unicode_logic_kit/atp/_ascii_names.py +113 -0
  16. unicode_logic_kit/atp/_html.py +72 -0
  17. unicode_logic_kit/atp/_substructural_input.py +228 -0
  18. unicode_logic_kit/atp/_tff_problem.py +715 -0
  19. unicode_logic_kit/atp/_tptp_problem.py +1111 -0
  20. unicode_logic_kit/atp/_writer_support.py +289 -0
  21. unicode_logic_kit/atp/clingo_backend.py +1180 -0
  22. unicode_logic_kit/atp/cvc5_backend.py +1385 -0
  23. unicode_logic_kit/atp/eprover_backend.py +732 -0
  24. unicode_logic_kit/atp/finite_domain.py +1055 -0
  25. unicode_logic_kit/atp/fitch.py +1547 -0
  26. unicode_logic_kit/atp/fitch_search.py +551 -0
  27. unicode_logic_kit/atp/hets_backend.py +339 -0
  28. unicode_logic_kit/atp/hybrid_down.py +120 -0
  29. unicode_logic_kit/atp/incremental.py +250 -0
  30. unicode_logic_kit/atp/kripke_enum.py +741 -0
  31. unicode_logic_kit/atp/lambek.py +436 -0
  32. unicode_logic_kit/atp/leo3_backend.py +332 -0
  33. unicode_logic_kit/atp/linear.py +738 -0
  34. unicode_logic_kit/atp/lj.py +705 -0
  35. unicode_logic_kit/atp/logic_backends.py +566 -0
  36. unicode_logic_kit/atp/ltl_tableau.py +1084 -0
  37. unicode_logic_kit/atp/minizinc_backend.py +1402 -0
  38. unicode_logic_kit/atp/modal_tableau.py +1382 -0
  39. unicode_logic_kit/atp/nanocop_backend.py +410 -0
  40. unicode_logic_kit/atp/portfolio.py +489 -0
  41. unicode_logic_kit/atp/protocol.py +1803 -0
  42. unicode_logic_kit/atp/prover9_entailment.py +1153 -0
  43. unicode_logic_kit/atp/resolution.py +1376 -0
  44. unicode_logic_kit/atp/resolution_check.py +1114 -0
  45. unicode_logic_kit/atp/sequent.py +1050 -0
  46. unicode_logic_kit/atp/tableau.py +921 -0
  47. unicode_logic_kit/atp/tableau_check.py +543 -0
  48. unicode_logic_kit/atp/tptp_ncl.py +811 -0
  49. unicode_logic_kit/atp/tptp_tff.py +1546 -0
  50. unicode_logic_kit/atp/tstp.py +1333 -0
  51. unicode_logic_kit/atp/tstp_check.py +1096 -0
  52. unicode_logic_kit/atp/twee_backend.py +236 -0
  53. unicode_logic_kit/atp/twee_check.py +711 -0
  54. unicode_logic_kit/atp/twee_entailment.py +953 -0
  55. unicode_logic_kit/atp/vampire_entailment.py +540 -0
  56. unicode_logic_kit/atp/z3_arith.py +470 -0
  57. unicode_logic_kit/atp/z3_equivalence.py +36 -0
  58. unicode_logic_kit/atp/z3_fuzzy.py +362 -0
  59. unicode_logic_kit/atp/z3_input.py +500 -0
  60. unicode_logic_kit/atp/z3_models.py +208 -0
  61. unicode_logic_kit/chem/__init__.py +88 -0
  62. unicode_logic_kit/chem/_naming.py +284 -0
  63. unicode_logic_kit/chem/cache.py +185 -0
  64. unicode_logic_kit/chem/interop.py +244 -0
  65. unicode_logic_kit/chem/mol.py +525 -0
  66. unicode_logic_kit/chem/signature.py +112 -0
  67. unicode_logic_kit/comorphism.py +497 -0
  68. unicode_logic_kit/dl/__init__.py +384 -0
  69. unicode_logic_kit/dl/classification.py +227 -0
  70. unicode_logic_kit/dl/concepts.py +632 -0
  71. unicode_logic_kit/dl/datatypes.py +818 -0
  72. unicode_logic_kit/dl/owl_functional.py +2433 -0
  73. unicode_logic_kit/dl/owl_manchester.py +1637 -0
  74. unicode_logic_kit/dl/owl_reasoner.py +790 -0
  75. unicode_logic_kit/dl/parser.py +391 -0
  76. unicode_logic_kit/dl/tableau.py +4048 -0
  77. unicode_logic_kit/dl/translate.py +2704 -0
  78. unicode_logic_kit/drt/__init__.py +94 -0
  79. unicode_logic_kit/drt/export.py +179 -0
  80. unicode_logic_kit/drt/nodes.py +506 -0
  81. unicode_logic_kit/drt/parser.py +965 -0
  82. unicode_logic_kit/drt/resolve.py +195 -0
  83. unicode_logic_kit/drt/reverse.py +175 -0
  84. unicode_logic_kit/eval/__init__.py +106 -0
  85. unicode_logic_kit/eval/batch.py +382 -0
  86. unicode_logic_kit/eval/canonical.py +663 -0
  87. unicode_logic_kit/eval/chem_batch.py +606 -0
  88. unicode_logic_kit/eval/converses.py +200 -0
  89. unicode_logic_kit/eval/datasets/__init__.py +136 -0
  90. unicode_logic_kit/eval/datasets/_base.py +263 -0
  91. unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
  92. unicode_logic_kit/eval/datasets/c3po.py +678 -0
  93. unicode_logic_kit/eval/datasets/folio.py +158 -0
  94. unicode_logic_kit/eval/datasets/fracas.py +418 -0
  95. unicode_logic_kit/eval/datasets/groves.py +191 -0
  96. unicode_logic_kit/eval/datasets/logicbench.py +467 -0
  97. unicode_logic_kit/eval/datasets/logicnli.py +303 -0
  98. unicode_logic_kit/eval/datasets/malls.py +133 -0
  99. unicode_logic_kit/eval/datasets/pfolio.py +594 -0
  100. unicode_logic_kit/eval/datasets/pmb.py +242 -0
  101. unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
  102. unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
  103. unicode_logic_kit/eval/datasets/proverqa.py +674 -0
  104. unicode_logic_kit/eval/datasets/willow.py +478 -0
  105. unicode_logic_kit/eval/equivalence.py +466 -0
  106. unicode_logic_kit/eval/exercise_gen.py +533 -0
  107. unicode_logic_kit/eval/explain.py +791 -0
  108. unicode_logic_kit/eval/generality.py +750 -0
  109. unicode_logic_kit/eval/metric_hf.py +458 -0
  110. unicode_logic_kit/eval/predicate_match.py +343 -0
  111. unicode_logic_kit/eval/theory_check.py +1170 -0
  112. unicode_logic_kit/eval/validate.py +306 -0
  113. unicode_logic_kit/fol/__init__.py +177 -0
  114. unicode_logic_kit/fol/_atom_keys.py +510 -0
  115. unicode_logic_kit/fol/_fol_nodes.py +3586 -0
  116. unicode_logic_kit/fol/_free_parameters.py +105 -0
  117. unicode_logic_kit/fol/_ho_nodes.py +448 -0
  118. unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
  119. unicode_logic_kit/fol/_identifiers.py +1091 -0
  120. unicode_logic_kit/fol/_lambek_nodes.py +112 -0
  121. unicode_logic_kit/fol/_linear_nodes.py +352 -0
  122. unicode_logic_kit/fol/_modal_nodes.py +1467 -0
  123. unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
  124. unicode_logic_kit/fol/_numeral_symbols.py +231 -0
  125. unicode_logic_kit/fol/_so_nodes.py +200 -0
  126. unicode_logic_kit/fol/_symbol_names.py +81 -0
  127. unicode_logic_kit/fol/_team_nodes.py +181 -0
  128. unicode_logic_kit/fol/_tptp_symbols.py +551 -0
  129. unicode_logic_kit/fol/_truth_constants.py +117 -0
  130. unicode_logic_kit/fol/casl_export.py +1135 -0
  131. unicode_logic_kit/fol/casl_import.py +929 -0
  132. unicode_logic_kit/fol/derivation.py +367 -0
  133. unicode_logic_kit/fol/dialect_detect.py +70 -0
  134. unicode_logic_kit/fol/dialect_repair.py +537 -0
  135. unicode_logic_kit/fol/frames.py +637 -0
  136. unicode_logic_kit/fol/grammars/terminals.lark +31 -0
  137. unicode_logic_kit/fol/lambda_tools.py +297 -0
  138. unicode_logic_kit/fol/latex_input.py +429 -0
  139. unicode_logic_kit/fol/modal_translation.py +944 -0
  140. unicode_logic_kit/fol/msflparser.py +1033 -0
  141. unicode_logic_kit/fol/naming.py +422 -0
  142. unicode_logic_kit/fol/nodes.py +241 -0
  143. unicode_logic_kit/fol/normalforms.py +492 -0
  144. unicode_logic_kit/fol/pal.py +287 -0
  145. unicode_logic_kit/fol/prolog_export.py +566 -0
  146. unicode_logic_kit/fol/prolog_input.py +505 -0
  147. unicode_logic_kit/fol/prover9_input.py +1325 -0
  148. unicode_logic_kit/fol/qml.py +1760 -0
  149. unicode_logic_kit/fol/qmltp_input.py +525 -0
  150. unicode_logic_kit/fol/sanitize.py +221 -0
  151. unicode_logic_kit/fol/serialize.py +79 -0
  152. unicode_logic_kit/fol/signature.py +1290 -0
  153. unicode_logic_kit/fol/simplify_check.py +544 -0
  154. unicode_logic_kit/fol/spans.py +594 -0
  155. unicode_logic_kit/fol/tptp_input.py +1503 -0
  156. unicode_logic_kit/fol/tptp_repair.py +941 -0
  157. unicode_logic_kit/fol/unification.py +157 -0
  158. unicode_logic_kit/fol/verbalize.py +263 -0
  159. unicode_logic_kit/hets/__init__.py +163 -0
  160. unicode_logic_kit/hets/bridge.py +142 -0
  161. unicode_logic_kit/hets/client.py +748 -0
  162. unicode_logic_kit/hets/docker.py +420 -0
  163. unicode_logic_kit/hets/dol.py +712 -0
  164. unicode_logic_kit/hets/haskell_json.py +355 -0
  165. unicode_logic_kit/hets/owl_backend.py +794 -0
  166. unicode_logic_kit/hets/owl_cli.py +598 -0
  167. unicode_logic_kit/hets/symbols.py +512 -0
  168. unicode_logic_kit/hol/__init__.py +140 -0
  169. unicode_logic_kit/hol/_ho_common.py +323 -0
  170. unicode_logic_kit/hol/_isabelle_binders.py +125 -0
  171. unicode_logic_kit/hol/classical.py +812 -0
  172. unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
  173. unicode_logic_kit/hol/deepshallow/_common.py +177 -0
  174. unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
  175. unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
  176. unicode_logic_kit/hol/deepshallow/modal.py +217 -0
  177. unicode_logic_kit/hol/deepshallow/qml.py +406 -0
  178. unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
  179. unicode_logic_kit/hol/free.py +753 -0
  180. unicode_logic_kit/hol/goedel.py +336 -0
  181. unicode_logic_kit/hol/ho_modal.py +1743 -0
  182. unicode_logic_kit/hol/intuitionistic.py +403 -0
  183. unicode_logic_kit/hol/isabelle_conditional.py +593 -0
  184. unicode_logic_kit/hol/isabelle_modal.py +1908 -0
  185. unicode_logic_kit/hol/isabelle_relevant.py +412 -0
  186. unicode_logic_kit/hol/isabelle_runner.py +1147 -0
  187. unicode_logic_kit/hol/isabelle_substructural.py +884 -0
  188. unicode_logic_kit/hol/lean.py +1018 -0
  189. unicode_logic_kit/hol/manyvalued.py +921 -0
  190. unicode_logic_kit/hol/secondorder.py +687 -0
  191. unicode_logic_kit/hol/thf_modal.py +941 -0
  192. unicode_logic_kit/hol/thirdorder.py +397 -0
  193. unicode_logic_kit/ilp/__init__.py +89 -0
  194. unicode_logic_kit/ilp/readback.py +389 -0
  195. unicode_logic_kit/ilp/separation.py +153 -0
  196. unicode_logic_kit/ilp/task.py +730 -0
  197. unicode_logic_kit/logic.py +163 -0
  198. unicode_logic_kit/mcp/__init__.py +28 -0
  199. unicode_logic_kit/mcp/__main__.py +5 -0
  200. unicode_logic_kit/mcp/chem_tools.py +1031 -0
  201. unicode_logic_kit/mcp/server.py +2453 -0
  202. unicode_logic_kit/mcp/syntax_spec.py +681 -0
  203. unicode_logic_kit/prob/__init__.py +53 -0
  204. unicode_logic_kit/prob/_bdd.py +225 -0
  205. unicode_logic_kit/prob/_column_gen.py +668 -0
  206. unicode_logic_kit/prob/distribution.py +686 -0
  207. unicode_logic_kit/prob/nilsson.py +470 -0
  208. unicode_logic_kit/py.typed +0 -0
  209. unicode_logic_kit/semantics/__init__.py +137 -0
  210. unicode_logic_kit/semantics/_modal_reject.py +156 -0
  211. unicode_logic_kit/semantics/action_models.py +466 -0
  212. unicode_logic_kit/semantics/asp_models.py +1200 -0
  213. unicode_logic_kit/semantics/conditional.py +580 -0
  214. unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
  215. unicode_logic_kit/semantics/free_logic.py +913 -0
  216. unicode_logic_kit/semantics/fuzzy.py +384 -0
  217. unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
  218. unicode_logic_kit/semantics/intuitionistic.py +581 -0
  219. unicode_logic_kit/semantics/kripke.py +1139 -0
  220. unicode_logic_kit/semantics/manyvalued.py +580 -0
  221. unicode_logic_kit/semantics/matrix.py +342 -0
  222. unicode_logic_kit/semantics/model_eval.py +1135 -0
  223. unicode_logic_kit/semantics/modelfinder.py +1036 -0
  224. unicode_logic_kit/semantics/nonmonotonic.py +372 -0
  225. unicode_logic_kit/semantics/relevant.py +331 -0
  226. unicode_logic_kit/semantics/secondorder.py +657 -0
  227. unicode_logic_kit/semantics/structures.py +352 -0
  228. unicode_logic_kit/semantics/tarski.py +975 -0
  229. unicode_logic_kit/semantics/team.py +315 -0
  230. unicode_logic_kit/semantics/team_translation.py +416 -0
  231. unicode_logic_kit/semantics/thirdorder.py +358 -0
  232. unicode_logic_kit/semantics/tnorm.py +85 -0
  233. unicode_logic_kit/semantics/truthtable.py +201 -0
  234. unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
  235. unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
  236. unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
  237. unicode_logic_kit-0.31.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,500 @@
1
+ """Z3 input/output: turn a Z3 expression (or an SMT-LIB2 string) into the AST,
2
+ and a toolkit :class:`Node` back into SMT-LIB2 text.
3
+
4
+ Two reverse (read) directions for the Z3 back-end:
5
+
6
+ - :func:`from_z3` — convert a ``z3.ExprRef`` into a toolkit :class:`Node` (the inverse
7
+ of :meth:`Node.to_z3`).
8
+ - :func:`parse_smtlib` / :func:`load_smtlib` — parse an SMT-LIB2 string/file (via Z3's
9
+ own parser) and convert every assertion with :func:`from_z3`.
10
+
11
+ And one forward (write) direction:
12
+
13
+ - :func:`to_smtlib` — the general inverse of :func:`parse_smtlib`: render a
14
+ :class:`Node` (plus optional premises) as a standalone, sanitisation-correct
15
+ SMT-LIB2 problem. See its own docstring for what "sanitisation-correct" buys
16
+ over a naive ``to_z3()`` + ``z3.Solver.to_smt2()`` combination.
17
+
18
+ Lossiness (inherent to Z3): :meth:`Node.to_z3` maps :class:`Variable`, :class:`Constant`
19
+ and :class:`Number` all onto symbols of one uninterpreted sort ``S``. A *bound* variable
20
+ (a Z3 de-Bruijn ``Var`` node) comes back as a :class:`Variable`; a *free* symbol comes
21
+ back as a :class:`Constant`, except for the symbol ``x!v`` that ``to_z3`` writes for a
22
+ free :class:`Variable` ``x`` (see :class:`~unicode_logic_kit.fol.nodes.Z3Env`), which comes
23
+ back as that variable, and the symbol ``x!v!c`` or ``x!c!c`` it writes for a constant named
24
+ ``x!v`` or ``x!c``, which comes back as that constant. A symbol is a :class:`Number` only
25
+ when its name IS the text a numeral is written as (ASCII digits, a minus sign, a point:
26
+ ``5``, ``-3``, ``2.5``, ``1e-07``); ``inf``, ``nan``, ``+5`` and a name in other digits
27
+ are constants. The reading of a marked name is injective on the names of one text: a
28
+ bound or free ``x!v`` is read as the variable ``x`` unless ``x`` is itself a variable of the
29
+ text, and then as the variable ``x!v`` (a text that binds ``a!v`` and ``a`` binds two
30
+ variables); a symbol ``a!c`` that no writer produces is the constant ``a!c``, and the symbol
31
+ ``a!c!c`` that the writer gives that constant is read as ``a!c`` unless the text holds
32
+ ``a!c`` too, and then as ``a!c!c`` (a text with both holds two constants). A numeral is
33
+ the constant of its VALUE, so ``1.0`` and
34
+ ``1`` come back as one :class:`Number`. The conversion is therefore **meaning-preserving**,
35
+ not necessarily structure-preserving. ``A == B`` is read as :class:`Iff` when the operands are Boolean
36
+ and as an ``=`` :class:`Atom` when they are individuals; ``distinct`` becomes ``≠``;
37
+ ``xor`` becomes :class:`Xor`; an uninterpreted application returning ``Bool`` becomes an
38
+ :class:`Atom`, one returning ``S`` a :class:`Function`; the arithmetic operators /
39
+ relations and numerals are recognised too (so SMT-LIB with ``Int``/``Real`` imports).
40
+ This same lossiness applies to a :func:`to_smtlib` round trip: it goes through
41
+ ``Node.to_z3``/``from_z3`` on the way back in, so it inherits exactly this table.
42
+
43
+ Public API: :func:`from_z3`, :func:`parse_smtlib`, :func:`load_smtlib`, :func:`to_smtlib`.
44
+ """
45
+
46
+ import math
47
+ from dataclasses import dataclass, field
48
+ from typing import Dict, FrozenSet, Mapping, Sequence, Set
49
+
50
+ import z3
51
+
52
+ from ..fol.nodes import (
53
+ Node, Variable, Constant, Number, Function,
54
+ Atom, Not, And, Or, Xor, Implies, Iff,
55
+ )
56
+ from ..fol._fol_nodes import Quantifier, Z3Env, kit_name_of_z3_symbol, numeral_key
57
+
58
+
59
+ # Arithmetic operator / relation kinds → toolkit symbol names.
60
+ _ARITH_FUNC = {
61
+ z3.Z3_OP_ADD: "+", z3.Z3_OP_SUB: "-", z3.Z3_OP_MUL: "*",
62
+ z3.Z3_OP_DIV: "/", z3.Z3_OP_IDIV: "/",
63
+ }
64
+ _ARITH_PRED = {
65
+ z3.Z3_OP_LT: "<", z3.Z3_OP_LE: "≤", z3.Z3_OP_GT: ">", z3.Z3_OP_GE: "≥",
66
+ }
67
+
68
+
69
+ def _fold(cls, parts):
70
+ """Right-fold a list of >=1 formulas into nested binary ``cls`` nodes."""
71
+ result = parts[-1]
72
+ for part in reversed(parts[:-1]):
73
+ result = cls(part, result)
74
+ return result
75
+
76
+
77
+ def _const_or_number(name: str) -> Node:
78
+ """A 0-ary symbol of an uninterpreted sort: a :class:`Number` if its name is a numeral as the kit writes it, else Constant.
79
+
80
+ A numeral is written as :func:`~unicode_logic_kit.fol._fol_nodes.numeral_key` of its value,
81
+ so a symbol is read back as the number exactly when its name IS that text of a finite
82
+ number: ASCII digits, a minus sign, a point, an exponent. Python's own ``int`` and
83
+ ``float`` accept more (``inf``, ``nan``, ``+5``, ``1_000``, ``5.``, a number with
84
+ spaces around it, digits of other scripts), and each of those is a CONSTANT of that
85
+ name here: reading two of them as one number would merge distinct constants of the text.
86
+
87
+ Only a symbol of an uninterpreted sort is read this way (the caller sees to it): that
88
+ is where the kit's writer puts a numeral. A symbol of an arithmetic sort is a symbol
89
+ whatever its name, because a text over ``Int`` or ``Real`` has numerals of its own:
90
+ in ``(declare-fun |1| () Int) (assert (not (= |1| 1)))`` the symbol ``|1|`` and the
91
+ numeral ``1`` are two terms (the text is satisfiable), and reading both as the number
92
+ would make the assertion ``¬ 1 = 1``.
93
+ """
94
+ try:
95
+ integer = int(name)
96
+ except ValueError:
97
+ pass
98
+ else:
99
+ return Number(integer) if numeral_key(integer) == name else Constant(name)
100
+ try:
101
+ real = float(name)
102
+ except ValueError:
103
+ return Constant(name)
104
+ return Number(real) if math.isfinite(real) and numeral_key(real) == name else Constant(name)
105
+
106
+
107
+ def _numeral_of_rational(numerator: int, denominator: int) -> float:
108
+ """The value of the Z3 numeral ``numerator / denominator`` (lowest terms) as a :class:`Number` holds it.
109
+
110
+ That is an ``int`` for a whole numeral and a ``float`` for any other. A numeral is read exactly
111
+ or refused, as a decimal text is
112
+ (:func:`~unicode_logic_kit.fol._fol_nodes._numeral_from_text`): a whole value is the ``int``, and
113
+ a fraction is the decimal text it spells, when it spells one (the denominator has no prime
114
+ factor but 2 and 5), read by that same function, so ``1/10`` is ``0.1``, a decimal of more than
115
+ 15 significant digits is refused (Z3 reads ``0.30000000000000004`` and ``0.30000000000000005``
116
+ as two different rationals, and one float is the nearest to both), and so is a fraction that
117
+ has no decimal text (``1/3``: no float is that number, and the nearest one would be another
118
+ numeral).
119
+
120
+ Raises:
121
+ ValueError: the numeral is not a whole number and has no decimal text of at most 15
122
+ significant digits (see above).
123
+ """
124
+ from ..fol._fol_nodes import _numeral_from_text
125
+
126
+ if denominator == 1:
127
+ return numerator
128
+ remaining, twos, fives = denominator, 0, 0
129
+ while remaining % 2 == 0:
130
+ remaining //= 2
131
+ twos += 1
132
+ while remaining % 5 == 0:
133
+ remaining //= 5
134
+ fives += 1
135
+ if remaining != 1:
136
+ raise ValueError(
137
+ f"the numeral {numerator}/{denominator} has no decimal text (its denominator has a prime "
138
+ f"factor other than 2 and 5), so no floating-point number is that numeral, and reading "
139
+ f"the nearest one would make it another numeral than the one written. Keep it as the "
140
+ f"quotient of two integers, or give a decimal with at most 15 significant digits "
141
+ f"instead")
142
+ places = max(twos, fives)
143
+ scaled = str(abs(numerator) * 10 ** places // denominator).rjust(places + 1, "0")
144
+ text = f"{'-' if numerator < 0 else ''}{scaled[:-places]}.{scaled[-places:]}"
145
+ return _numeral_from_text(text)
146
+
147
+
148
+ @dataclass(frozen=True)
149
+ class _TextNames:
150
+ """The names of one text (one or several Z3 expressions) that decide how a marked symbol is read.
151
+
152
+ Fields:
153
+
154
+ * ``variables``: every name, as Z3 holds it, that is read back as a variable: the names of all
155
+ the bound variables of all the quantifiers, and the free symbols of the sort ``S`` whose name
156
+ ends in ``!v`` (the mark of :func:`~unicode_logic_kit.fol._fol_nodes.z3_variable_name`). See
157
+ :func:`_variable_name`.
158
+ * ``constants``: the kit's name of every free symbol of no arguments that is not a Boolean and
159
+ is read back as a constant, by the name Z3 holds it under. See :func:`_constant_readings`.
160
+ """
161
+
162
+ variables: FrozenSet[str] = frozenset()
163
+ constants: Mapping[str, str] = field(default_factory=dict)
164
+
165
+
166
+ def _text_names(exprs) -> _TextNames:
167
+ """The :class:`_TextNames` of ``exprs``: every name the reading of one text has to keep apart."""
168
+ variables: Set[str] = set()
169
+ constants: Set[str] = set()
170
+ seen: Set[int] = set()
171
+ stack = list(exprs)
172
+ while stack:
173
+ node = stack.pop()
174
+ key = node.get_id()
175
+ if key in seen:
176
+ continue
177
+ seen.add(key)
178
+ if z3.is_quantifier(node):
179
+ variables.update(str(node.var_name(i)) for i in range(node.num_vars()))
180
+ stack.append(node.body())
181
+ elif z3.is_app(node):
182
+ if (node.num_args() == 0 and not _is_bool(node)
183
+ and node.decl().kind() == z3.Z3_OP_UNINTERPRETED):
184
+ name = str(node.decl().name())
185
+ (variables if kit_name_of_z3_symbol(name)[1] else constants).add(name)
186
+ stack.extend(node.children())
187
+ return _TextNames(frozenset(variables), _constant_readings(constants))
188
+
189
+
190
+ def _constant_readings(names) -> Dict[str, str]:
191
+ """The kit's name of each constant that a text holds under the Z3 names ``names``: two symbols, two names.
192
+
193
+ The writer W (:func:`~unicode_logic_kit.fol._fol_nodes.z3_constant_name`) gives the constant ``n``
194
+ the symbol ``n``, or ``n!c`` when ``n`` ends in a mark (``!v`` or ``!c``). Its inverse R
195
+ (:func:`~unicode_logic_kit.fol._fol_nodes.kit_name_of_z3_symbol`) strips a ``!c`` that follows
196
+ a mark and keeps every other name. R alone does not keep two symbols of a text that a writer did
197
+ not write apart: ``a!c`` is no writer's symbol (the constant ``a`` is written ``a``) and R keeps
198
+ it, while ``a!c!c`` is the symbol of the constant ``a!c`` and R gives that name too. So the
199
+ symbols are read shortest first, each as R of it unless an earlier symbol has that reading
200
+ already, and then as written: a text that holds both is read as two constants, ``a!c`` and
201
+ ``a!c!c``; a symbol that no writer writes is read as itself, whatever else the text holds.
202
+ """
203
+ # (1) R(W(n)) = n for every name n, so R is injective on the image of W; (2) R(s) = s unless s = W(R(s)), so a symbol
204
+ # outside the image is read as itself; (3) shortest first, s is read as R(s) or as s, and no earlier reading is s: an
205
+ # earlier s' is no longer than s and R(s') = s needs s' = s or len(s') = len(s) + 2, so no two readings are equal.
206
+ readings: Dict[str, str] = {}
207
+ taken: Set[str] = set()
208
+ for raw in sorted(names, key=lambda name: (len(name), name)):
209
+ kit_name = kit_name_of_z3_symbol(raw)[0]
210
+ if kit_name in taken:
211
+ kit_name = raw
212
+ taken.add(kit_name)
213
+ readings[raw] = kit_name
214
+ return readings
215
+
216
+
217
+ def _variable_name(name, taken: FrozenSet[str]) -> str:
218
+ """The kit's name of a variable that Z3 holds as ``name`` (a bound name, or a free symbol ``x!v``).
219
+
220
+ ``x!v`` is what :meth:`Node.to_z3` writes for the variable ``x``, so it is read back as ``x``
221
+ -- unless ``x`` is itself the name of a variable of the text (``taken``, the ``variables`` of
222
+ :func:`_text_names`): a text that binds ``a!v`` and ``a`` in one quantifier names TWO
223
+ variables, and stripping the mark would make them one (``∀a ∀a (P(a, a) → Q)`` is a weaker
224
+ sentence than ``∀a!v ∀a (P(a!v, a) → Q)``), or would let a bound ``a`` capture a free symbol
225
+ ``a!v``. A name whose stripped form is taken is read as it is written. So the reading is
226
+ injective on the names of one text: two names read alike only when they are one.
227
+ """
228
+ raw = str(name)
229
+ kit_name, is_variable = kit_name_of_z3_symbol(raw)
230
+ return kit_name if is_variable and kit_name not in taken else raw
231
+
232
+
233
+ def _is_bool(expr) -> bool:
234
+ """True iff the Z3 expression has Boolean sort."""
235
+ return expr.sort_kind() == z3.Z3_BOOL_SORT
236
+
237
+
238
+ def from_z3(expr, _scope=None, _taken=None) -> Node:
239
+ """Convert a Z3 expression (``z3.ExprRef``) into a toolkit :class:`Node`.
240
+
241
+ The inverse of :meth:`Node.to_z3`; meaning-preserving (see the module docstring
242
+ for the inherent Z3 lossiness around free variables / constants / numbers).
243
+
244
+ Raises:
245
+ TypeError: on a Z3 construct with no first-order toolkit counterpart.
246
+ ValueError: a rational numeral that no :class:`Number` holds exactly: a fraction with
247
+ more than 15 significant digits or no decimal text at all (the rule is that of a
248
+ decimal text in every other reader). It is refused, never read as the nearest float.
249
+ """
250
+ if _scope is None:
251
+ _scope = []
252
+ if _taken is None:
253
+ _taken = _text_names([expr])
254
+
255
+ # --- quantifiers (Z3 stores the body with de-Bruijn Var nodes) ---
256
+ if z3.is_quantifier(expr):
257
+ if expr.is_lambda():
258
+ raise TypeError("from_z3: Z3 lambda terms have no first-order AST counterpart.")
259
+ qtype = "∀" if expr.is_forall() else "∃"
260
+ names = [_variable_name(expr.var_name(i), _taken.variables) for i in range(expr.num_vars())]
261
+ # In the body, Var(0) is the LAST bound name; append in order so that
262
+ # Var(idx) -> scope[len-1-idx].
263
+ body = from_z3(expr.body(), _scope + names, _taken)
264
+ for name in reversed(names):
265
+ body = Quantifier(qtype, Variable(name), body)
266
+ return body
267
+
268
+ if z3.is_var(expr):
269
+ idx = z3.get_var_index(expr)
270
+ return Variable(str(_scope[len(_scope) - 1 - idx]))
271
+
272
+ # --- logical connectives ---
273
+ if z3.is_not(expr):
274
+ return Not(from_z3(expr.arg(0), _scope, _taken))
275
+ if z3.is_and(expr):
276
+ return _fold(And, [from_z3(expr.arg(i), _scope, _taken) for i in range(expr.num_args())])
277
+ if z3.is_or(expr):
278
+ return _fold(Or, [from_z3(expr.arg(i), _scope, _taken) for i in range(expr.num_args())])
279
+ if z3.is_implies(expr):
280
+ return Implies(from_z3(expr.arg(0), _scope, _taken), from_z3(expr.arg(1), _scope, _taken))
281
+ if z3.is_true(expr):
282
+ return Atom("$true", [])
283
+ if z3.is_false(expr):
284
+ return Atom("$false", [])
285
+
286
+ if z3.is_app(expr):
287
+ kind = expr.decl().kind()
288
+
289
+ if z3.is_eq(expr):
290
+ left, right = expr.arg(0), expr.arg(1)
291
+ sub = [from_z3(left, _scope, _taken), from_z3(right, _scope, _taken)]
292
+ return Iff(*sub) if _is_bool(left) else Atom("=", sub)
293
+ if kind == z3.Z3_OP_DISTINCT:
294
+ args = [from_z3(expr.arg(i), _scope, _taken) for i in range(expr.num_args())]
295
+ if len(args) == 2:
296
+ return Atom("≠", args)
297
+ # n-ary distinct = conjunction of pairwise disequalities.
298
+ pairs = [Atom("≠", [args[i], args[j]])
299
+ for i in range(len(args)) for j in range(i + 1, len(args))]
300
+ return _fold(And, pairs)
301
+ if kind == z3.Z3_OP_XOR:
302
+ return Xor(from_z3(expr.arg(0), _scope, _taken), from_z3(expr.arg(1), _scope, _taken))
303
+ if kind == z3.Z3_OP_IFF:
304
+ return Iff(from_z3(expr.arg(0), _scope, _taken), from_z3(expr.arg(1), _scope, _taken))
305
+
306
+ # numerals
307
+ if z3.is_int_value(expr):
308
+ return Number(expr.as_long())
309
+ if z3.is_rational_value(expr):
310
+ return Number(_numeral_of_rational(expr.numerator_as_long(), expr.denominator_as_long()))
311
+
312
+ # arithmetic
313
+ if kind in _ARITH_PRED:
314
+ return Atom(_ARITH_PRED[kind],
315
+ [from_z3(expr.arg(i), _scope, _taken) for i in range(expr.num_args())])
316
+ if kind in _ARITH_FUNC and expr.num_args() == 2:
317
+ return Function(_ARITH_FUNC[kind],
318
+ [from_z3(expr.arg(0), _scope, _taken), from_z3(expr.arg(1), _scope, _taken)])
319
+ if kind == z3.Z3_OP_UMINUS:
320
+ return Function("-", [from_z3(expr.arg(0), _scope, _taken)])
321
+
322
+ # generic (uninterpreted) application, constant, or predicate
323
+ name = expr.decl().name()
324
+ nargs = expr.num_args()
325
+ args = [from_z3(expr.arg(i), _scope, _taken) for i in range(nargs)]
326
+ if nargs == 0:
327
+ if _is_bool(expr):
328
+ return Atom(name, [])
329
+ if kit_name_of_z3_symbol(name)[1]:
330
+ return Variable(_variable_name(name, _taken.variables))
331
+ reading = _taken.constants.get(name, kit_name_of_z3_symbol(name)[0])
332
+ if expr.sort().kind() != z3.Z3_UNINTERPRETED_SORT:
333
+ return Constant(reading)
334
+ return _const_or_number(reading)
335
+ return Atom(name, args) if _is_bool(expr) else Function(name, args)
336
+
337
+ raise TypeError(
338
+ f"from_z3: unsupported Z3 expression {expr!r} (no first-order AST counterpart)."
339
+ )
340
+
341
+
342
+ def parse_smtlib(text: str) -> list:
343
+ """Parse an SMT-LIB2 string and return its assertions as toolkit :class:`Node`\\ s.
344
+
345
+ Uses Z3's own SMT-LIB2 parser (``z3.parse_smt2_string``) to read the assertions,
346
+ then converts each with :func:`from_z3`.
347
+
348
+ Args:
349
+ text: SMT-LIB2 source (``declare-fun`` / ``assert`` … ).
350
+
351
+ Returns:
352
+ A list of :class:`Node`, one per top-level ``assert``.
353
+ """
354
+ return _from_assertions(z3.parse_smt2_string(text))
355
+
356
+
357
+ def load_smtlib(path: str) -> list:
358
+ """Read an SMT-LIB2 (``.smt2``) file and :func:`parse_smtlib` its contents."""
359
+ return _from_assertions(z3.parse_smt2_file(path))
360
+
361
+
362
+ def _from_assertions(vector) -> list:
363
+ """:func:`from_z3` of every assertion of ``vector``, naming one symbol alike in all of them.
364
+
365
+ The variables and the constants of the whole text are looked at together
366
+ (:func:`_text_names`), so that a free symbol ``x!v`` is read under one name wherever it
367
+ occurs, whichever of the assertions bind a variable ``x``, and two symbols of the text are
368
+ never read as one name (:func:`_constant_readings`).
369
+ """
370
+ assertions = list(vector)
371
+ taken = _text_names(assertions)
372
+ return [from_z3(assertion, None, taken) for assertion in assertions]
373
+
374
+
375
+ def to_smtlib(formula: Node, premises: Sequence[Node] = (), *, logic: str = "ALL") -> str:
376
+ """Render ``premises`` and ``formula`` as one standalone SMT-LIB2 problem.
377
+
378
+ The general inverse of :func:`parse_smtlib`: each of ``premises`` then
379
+ ``formula`` becomes its OWN top-level ``(assert ...)`` command (not one
380
+ folded ``(∧ premises) → formula`` implication) — that is what
381
+ :func:`parse_smtlib` reads back (one :class:`Node` per top-level
382
+ ``assert``), and it is the more idiomatic reading of a standalone
383
+ SMT-LIB2 problem: a list of asserted facts, not a single implication
384
+ that happens to encode an entailment question. Logically nothing is
385
+ lost either way (an SMT solver conjoins every assertion regardless of
386
+ how many ``(assert ...)`` commands they arrived in) — see
387
+ :func:`~atp.cvc5_backend._sanitize_many_for_smtlib`'s docstring for the
388
+ sibling design decision this mirrors.
389
+
390
+ This is deliberately NOT built as ``to_z3()`` + ``z3.Solver.to_smt2()``
391
+ alone: that combination is unsound for a symbol name that is pure ASCII
392
+ and digit-leading (``2008SummerOlympics``) or that IS one of SMT-LIB2's
393
+ own reserved words (``let``, ``forall``, ...) — Z3's own serialiser
394
+ quotes every OTHER illegal name correctly (including any non-ASCII one)
395
+ but silently emits these two kinds unquoted, producing ``.smt2`` text
396
+ that fails to parse back (reproduced live; see
397
+ :mod:`atp.cvc5_backend`'s module docstring). So every name across
398
+ ``premises`` and ``formula`` is first sanitised against ONE SHARED name
399
+ map — :func:`~atp.cvc5_backend._sanitize_many_for_smtlib`, promoted from
400
+ :class:`Cvc5Backend`'s own already-proven translation rather than
401
+ reimplemented here — so a symbol used in more than one formula renames
402
+ consistently, before any of them reaches ``to_z3()``. A counting quantifier is
403
+ expanded before the renaming (the witnesses are then names of the problem like any
404
+ other, and are never spelled like a predicate, a function, a constant or a sort of
405
+ it: in this text a bound variable and the symbol it shadows are one identifier).
406
+
407
+ Args:
408
+ formula: the goal (or the only formula, when ``premises`` is empty).
409
+ premises: additional formulas, each asserted on its own line ahead
410
+ of ``formula``'s.
411
+ logic: the SMT-LIB2 logic string emitted on the leading
412
+ ``(set-logic ...)`` command (default ``"ALL"`` — safe for
413
+ anything ``to_z3()`` can produce: one uninterpreted sort with
414
+ equality plus uninterpreted functions/predicates, never genuine
415
+ interpreted arithmetic).
416
+
417
+ Returns:
418
+ SMT-LIB2 source text: ``(set-logic ...)``, the ``declare-sort``/
419
+ ``declare-fun`` preamble Z3's own ``Solver.to_smt2()`` derives from
420
+ the declarations actually used, one ``(assert ...)`` per premise
421
+ then the goal, and a trailing ``(check-sat)``.
422
+
423
+ **Symbols.** A symbol is a predicate, a function or a variable at one arity, so
424
+ ``P(a)`` and ``P(a, b)``, a predicate and a function of one name, a variable and a
425
+ constant of one name, are written under different names (the second and every later
426
+ one gets a token such as ``P_2``; two declarations of one name are an error in
427
+ SMT-LIB2). A name that is a symbol of an SMT-LIB theory (``+``, ``select``,
428
+ ``str.len``, ...) is renamed too, so the text is the same whatever the logic of the
429
+ reading solver, and so is a constant or a variable whose name ends in ``!v`` or ``!c``
430
+ (the reader decodes such a symbol as a variable or as another constant), and so is a
431
+ symbol spelled like a name that Z3's printer mints for a shared sub-term (``$x24``,
432
+ ``?x10``: the printer writes ``(let (($x24 ...)) ...)`` without looking at the symbols the
433
+ text declares, and would shadow a declared symbol of that spelling inside the ``let``). A
434
+ symbol that is renamed is written under its token and read back under it. A numeral is
435
+ the constant of its value: ``1`` and ``1.0`` are one. Every formula is one
436
+ ``(assert ...)``, a truth constant included.
437
+
438
+ Raises:
439
+ NotImplementedError: ``formula`` or a premise uses a construct with
440
+ no first-order SMT-LIB2 encoding (second/third-order
441
+ quantification, modal/hybrid/linear/Lambek/team operators, ...)
442
+ — the exact refusal :meth:`Node.to_z3` itself raises for it,
443
+ reused rather than reimplemented (see the ``to_z3`` overrides in
444
+ ``fol/_so_nodes.py``, ``fol/_modal_nodes.py``, etc.), with one
445
+ sentence appended naming this entry point; or the problem holds a
446
+ numeral and a constant of the same text (``Number(1)`` and
447
+ ``Constant('1')``), which are one symbol; or a counting quantifier has a bound
448
+ above 500, which :meth:`~unicode_logic_kit.fol.nodes.Count._expand` refuses by
449
+ naming the bound (a bound up to 500 is written: its expansion is that many
450
+ nested quantifiers, and nothing here recurses on it).
451
+ """
452
+ # Lazy import: atp/__init__.py imports this module before cvc5_backend
453
+ # (which itself imports atp.protocol, which imports cvc5_backend again at
454
+ # its own bottom to register it) — importing cvc5_backend at THIS
455
+ # module's top level would run into that chain mid-load and fail with a
456
+ # partial-module ImportError. By the time to_smtlib() is actually
457
+ # called, package import has long finished, so this is safe.
458
+ from .cvc5_backend import _lower_counting_for_smtlib, _sanitize_many_for_smtlib
459
+
460
+ # The counting quantifiers are expanded BEFORE the sanitiser renames the symbols: their
461
+ # witnesses are then names of the problem like any other and are kept apart from every
462
+ # predicate, function, constant and sort (one namespace in the text).
463
+ all_nodes, _ = _lower_counting_for_smtlib(list(premises) + [formula])
464
+ sanitised_nodes, _name_map = _sanitize_many_for_smtlib(all_nodes)
465
+ # The sanitiser has given every variable and every constant a name of its own, so the
466
+ # environment writes a variable as it is named and the text holds those names.
467
+ env = Z3Env(variables_apart=False)
468
+ try:
469
+ z3_exprs = [node.to_z3(env) for node in sanitised_nodes]
470
+ except NotImplementedError as exc:
471
+ raise NotImplementedError(
472
+ f"{exc} SMT-LIB2 export is first-order only."
473
+ ) from exc
474
+
475
+ solver = z3.Solver()
476
+ for expr in z3_exprs:
477
+ solver.add(expr)
478
+ text = solver.to_smt2()
479
+ if z3_exprs and z3.is_true(z3_exprs[-1]):
480
+ # ``Solver.to_smt2`` prints its LAST assertion as the formula of a benchmark, and the
481
+ # printer leaves out a formula that is ``true``: the text would have no ``(assert true)``
482
+ # for a problem that ends in a truth constant (a lone ``⊤`` has no assertion at all), and
483
+ # would read back as fewer formulas than were written.
484
+ text = _benchmark_text(z3_exprs)
485
+ return f"(set-logic {logic})\n{text}"
486
+
487
+
488
+ def _benchmark_text(exprs) -> str:
489
+ """The SMT-LIB2 text of ``exprs``, one ``(assert ...)`` each, a ``true`` among them too.
490
+
491
+ What ``Solver.to_smt2`` prints, with every expression handed over as an assumption of the
492
+ benchmark and none as its formula, so that the printer writes all of them.
493
+ """
494
+ context = z3.main_ctx()
495
+ assumptions = (z3.Ast * len(exprs))()
496
+ for i, expr in enumerate(exprs):
497
+ assumptions[i] = expr.as_ast()
498
+ return z3.Z3_benchmark_to_smtlib_string(
499
+ context.ref(), "benchmark generated from python API", "", "unknown", "",
500
+ len(exprs), assumptions, z3.BoolVal(True, context).as_ast())