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,231 @@
1
+ """A numeral is a constant: the one name a writer gives it, and the check that keeps it apart.
2
+
3
+ On every route that was not asked for arithmetic, a :class:`~unicode_logic_kit.fol.nodes.Number`
4
+ is a CONSTANT symbol identified by its VALUE: ``Number(1)``, ``Number(1.0)`` and the
5
+ numeral written ``01`` are one constant (one node: a ``Number`` stores a float with a
6
+ whole value as the integer it equals), and
7
+ nothing else is known about it, so two numerals of different value may denote the same
8
+ element, like any two constants. The operators ``+ - * /`` are uninterpreted function
9
+ symbols and ``< > ≤ ≥`` uninterpreted predicates. A problem writer for a language whose
10
+ own numbers are arithmetic (TPTP's ``1`` is an ``$int``, ``$sum`` is addition) must not
11
+ hand a numeral to the prover as a number: Vampire and E read the bare ``1`` as an
12
+ integer, and the text ``p(1)`` is then a type error for a predicate over individuals,
13
+ while ``1 != 2`` and ``$sum(1,1) = 2`` are theorems of the prover's arithmetic that the
14
+ kit's reading does not have.
15
+
16
+ This module is what such a writer shares:
17
+
18
+ * :func:`numeral_name` -- the name a numeral is written under, one per VALUE: the text of
19
+ the number, with an integral float spelled as the integer (``1.0`` is ``1``). The writer
20
+ then treats it as a constant of that name, so the writer's ordinary renaming (a name
21
+ that is no word in the target language is replaced by a legal, collision-free token,
22
+ and the replacement is recorded) gives it its word, and the writer's name map reads it
23
+ back. :func:`numeral_value` is the inverse.
24
+ * :func:`numerals_as_constants` -- rewrite every numeral in term position as that
25
+ constant, and refuse by name a constant, sorted constant or function that is spelled
26
+ like a numeral of the problem (``Number(1)`` and ``Constant('1')``): they would be one
27
+ symbol under one name, and the kit keeps a numeral and a constant apart.
28
+
29
+ A ``Number`` that is the bound of a counting quantifier (``Count.n``,
30
+ ``SortedCount.n``) is not a term and is left as it is. A writer whose target states a
31
+ comparison with a cardinality arithmetically (``|{x : P(x)}| ≥ 2`` is HOL's ``card {x. P x}
32
+ ≥ 2`` over the naturals) asks for ``counting_comparisons=True``: a ``Number`` operand of
33
+ such a comparison is then the number of the comparison and not a constant, and is left as
34
+ it is too.
35
+ """
36
+
37
+ from dataclasses import replace
38
+ from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Sequence, Tuple, Union
39
+
40
+ from ._fol_nodes import _number_text
41
+ from .nodes import (
42
+ Atom, Cardinality, Constant, Count, Function, Node, Number, SortedCardinality,
43
+ SortedConstant, SortedCount,
44
+ )
45
+
46
+ __all__ = ["numeral_name", "prefixed_numeral_name", "numeral_value", "term_numerals",
47
+ "numerals_as_constants"]
48
+
49
+ _COUNT_NODES = (Count, SortedCount)
50
+
51
+ #: The comparisons whose operands are numbers when one of them is a cardinality.
52
+ _COMPARISONS = frozenset({"=", "≠", "<", ">", "≤", "≥"})
53
+
54
+
55
+ def _plain(value: Union[int, float]) -> Union[int, float]:
56
+ """``value`` as the ``int`` or ``float`` it is; a ``bool`` is the integer it equals."""
57
+ if isinstance(value, bool):
58
+ return int(value)
59
+ if isinstance(value, (int, float)):
60
+ return value
61
+ raise NotImplementedError(
62
+ f"Number({value!r}) holds a {type(value).__name__}, and only an int or a float is a "
63
+ "numeral the kit's writers can name; convert the value first.")
64
+
65
+
66
+ def numeral_name(value: Union[int, float]) -> str:
67
+ """The name a numeral of value ``value`` is written under: ONE name per value.
68
+
69
+ The text of the number as the kit prints it everywhere (digits, an optional fractional
70
+ part, a leading ``-``, no exponent), except that a float with an integral value is
71
+ spelled as that integer, so ``1`` and ``1.0`` are one name
72
+ (``numeral_name(1.0) == numeral_name(1) == '1'``) while ``2.5`` stays ``'2.5'``. A
73
+ :class:`~unicode_logic_kit.fol.nodes.Number` already stores such a float as the integer, so for
74
+ the value of a node this is simply its printed text; the function takes a raw value too.
75
+
76
+ Raises:
77
+ ValueError: ``value`` is ``inf``, ``-inf`` or ``nan``: no syntax of the kit has a
78
+ literal for it.
79
+ NotImplementedError: ``value`` is neither an ``int`` nor a ``float``.
80
+ """
81
+ value = _plain(value)
82
+ if isinstance(value, float) and value.is_integer():
83
+ value = int(value)
84
+ return _number_text(value)
85
+
86
+
87
+ def prefixed_numeral_name(value: Union[int, float]) -> str:
88
+ """:func:`numeral_name` with an ``n`` in front (``n1``, ``n2.5``, ``n-1``): the spelling
89
+ the higher-order writers (THF, Isabelle/HOL) give a numeral, so that the identifier
90
+ their own sanitiser makes of it starts with a lower-case letter (``n1``, ``n2_5``,
91
+ ``n_1``). One name per value, like :func:`numeral_name`."""
92
+ return "n" + numeral_name(value)
93
+
94
+
95
+ def numeral_value(name: str) -> Union[int, float]:
96
+ """The value of the numeral that :func:`numeral_name` named ``name``: an ``int`` when the
97
+ name has no ``.``, a ``float`` otherwise.
98
+
99
+ The exact inverse of :func:`numeral_name` and of nothing else: the name of a float is the
100
+ shortest text that reads back as that float, so reading it back gives the float that was
101
+ named. A text a person typed is read by ``_numeral_from_text`` (at most 15 significant digits),
102
+ never by this function, and a name that :func:`numeral_name` does not produce is refused, so
103
+ two different names are never read as one value.
104
+
105
+ Raises:
106
+ ValueError: ``name`` is not a name :func:`numeral_name` produces (``1.0``, ``007``, ``1e3``
107
+ and ``0.30000000000000005``, whose nearest float is named ``0.30000000000000004``,
108
+ are not).
109
+ """
110
+ value = float(name) if "." in name else int(name)
111
+ if numeral_name(value) != name:
112
+ raise ValueError(
113
+ f"{name!r} is not a name numeral_name produces (the numeral of that value is named "
114
+ f"{numeral_name(value)!r}), so it is not read as that value")
115
+ return value
116
+
117
+
118
+ def _is_counting_comparison(atom: Atom) -> bool:
119
+ """Whether ``atom`` compares a cardinality (``|{x : φ}|``, sorted or not) with
120
+ something, by ``= ≠ < > ≤ ≥``: its ``Number`` operands are numbers, not individuals."""
121
+ return (atom.predicate in _COMPARISONS and len(atom.args) == 2
122
+ and any(isinstance(a, (Cardinality, SortedCardinality)) for a in atom.args))
123
+
124
+
125
+ def _term_children(node: Node, counting_comparisons: bool = False) -> List[Node]:
126
+ """The children of ``node`` that can hold a term: the bound of a counting quantifier
127
+ is not a term, and (with ``counting_comparisons``) neither is a number compared with a
128
+ cardinality; everything else is read as it is."""
129
+ if isinstance(node, _COUNT_NODES):
130
+ return [node.variable, node.formula]
131
+ if counting_comparisons and isinstance(node, Atom) and _is_counting_comparison(node):
132
+ return [a for a in node.args if not isinstance(a, Number)]
133
+ return node._child_nodes()
134
+
135
+
136
+ def term_numerals(formula: Node, *, counting_comparisons: bool = False) -> Iterator[Number]:
137
+ """Every :class:`~unicode_logic_kit.fol.nodes.Number` of ``formula`` that stands in term
138
+ position, in pre-order: not the bound ``n`` of a ``Count`` / ``SortedCount`` and, with
139
+ ``counting_comparisons=True``, not a ``Number`` compared with a cardinality."""
140
+ stack: List[Node] = [formula]
141
+ while stack:
142
+ node = stack.pop()
143
+ if isinstance(node, Number):
144
+ yield node
145
+ continue
146
+ stack.extend(reversed(_term_children(node, counting_comparisons)))
147
+
148
+
149
+ def _spellings(numeral: Number, spell: Callable[[Union[int, float]], str]) -> Tuple[str, ...]:
150
+ """The texts a numeral is written as: the name ``spell`` gives it and its canonical name.
151
+
152
+ A value has one spelling (a :class:`~unicode_logic_kit.fol.nodes.Number` stores a float with a
153
+ whole value as the integer it equals), so the text the kit prints for the node is the
154
+ canonical name and there is no third text."""
155
+ value = _plain(numeral.value)
156
+ return tuple(dict.fromkeys((spell(value), numeral_name(value))))
157
+
158
+
159
+ def _clash(where: str, numeral: Number, spelling: str, kind: str, name: str) -> NotImplementedError:
160
+ return NotImplementedError(
161
+ f"{where}: the number {_number_text(_plain(numeral.value))} and the {kind} {name!r} are spelled "
162
+ f"alike ({spelling!r}), so they would be written under one name and be ONE symbol. "
163
+ "A numeral is a constant of its own, identified by its value (1 and 1.0 are one "
164
+ "constant), and it is not the constant of the same spelling -- the kit refuses to "
165
+ f"merge the two. Rename the {kind}.")
166
+
167
+
168
+ def _check_names(formulas: Sequence[Node], spelled: Dict[str, Number], where: str) -> None:
169
+ for formula in formulas:
170
+ for node in formula.walk():
171
+ if isinstance(node, (Constant, SortedConstant)):
172
+ kind = "constant"
173
+ elif isinstance(node, Function):
174
+ kind = "function"
175
+ else:
176
+ continue
177
+ numeral = spelled.get(node.name)
178
+ if numeral is not None:
179
+ raise _clash(where, numeral, node.name, kind, node.name)
180
+
181
+
182
+ def _rewrite(node: Node, spell: Callable[[Union[int, float]], str],
183
+ counting_comparisons: bool = False) -> Node:
184
+ if isinstance(node, Number):
185
+ return Constant(spell(_plain(node.value)))
186
+ if isinstance(node, _COUNT_NODES):
187
+ return replace(node, formula=_rewrite(node.formula, spell, counting_comparisons)) # the bound variable holds no numeral
188
+ if counting_comparisons and isinstance(node, Atom) and _is_counting_comparison(node):
189
+ return Atom(node.predicate, tuple(a if isinstance(a, Number) else _rewrite(a, spell, True)
190
+ for a in node.args))
191
+ return node.map_children(lambda child: _rewrite(child, spell, counting_comparisons))
192
+
193
+
194
+ def numerals_as_constants(formulas: Iterable[Node], *, where: str,
195
+ spell: Callable[[Union[int, float]], str] = numeral_name,
196
+ counting_comparisons: bool = False
197
+ ) -> Tuple[List[Node], FrozenSet[str]]:
198
+ """``formulas`` with every numeral in term position written as a constant, and the set
199
+ of the names of those constants.
200
+
201
+ The constant of a numeral is named ``spell(value)``: :func:`numeral_name` unless a writer
202
+ has its own spelling (the HOL writers spell a numeral ``n1``, ``n2.5``), which is
203
+ one name per value either way. A formula without a numeral comes back as the very same
204
+ object. ``where`` is the name of the writer that is calling, which a refusal opens with.
205
+ With ``counting_comparisons=True`` a ``Number`` that is an operand of a comparison with a
206
+ cardinality (``|{x : P(x)}| ≥ 2``) is not in term position: it is the number the target
207
+ compares the count with, and stays a ``Number``.
208
+
209
+ Raises:
210
+ NotImplementedError: a ``Constant``, ``SortedConstant`` or ``Function`` of the
211
+ formulas is spelled like a numeral of the formulas (``Number(1)`` next to
212
+ ``Constant('1')``, or ``Number(2.5)`` next to ``Constant('2.5')``; ``Number(1.0)``
213
+ is ``Number(1)``, so ``Constant('1.0')`` is spelled like no numeral), or a
214
+ ``Number`` holds a value that is neither an ``int`` nor a ``float``.
215
+ ValueError: a numeral is ``inf``, ``-inf`` or ``nan``.
216
+ """
217
+ formulas = list(formulas)
218
+ spelled: Dict[str, Number] = {}
219
+ numerals_of: List[List[Number]] = []
220
+ for formula in formulas:
221
+ found = list(term_numerals(formula, counting_comparisons=counting_comparisons))
222
+ numerals_of.append(found)
223
+ for numeral in found:
224
+ for spelling in _spellings(numeral, spell):
225
+ spelled.setdefault(spelling, numeral)
226
+ if not spelled:
227
+ return formulas, frozenset()
228
+ _check_names(formulas, spelled, where)
229
+ names = frozenset(spell(_plain(n.value)) for found in numerals_of for n in found)
230
+ return [_rewrite(f, spell, counting_comparisons) if found else f
231
+ for f, found in zip(formulas, numerals_of)], names
@@ -0,0 +1,200 @@
1
+ """Second-order quantifier AST node: ∀P / ∃P binding a predicate variable.
2
+
3
+ This node extends the classical FOL AST with second-order quantification over a
4
+ predicate variable. It is purely syntactic in this phase: there is no semantics
5
+ yet, and it rejects the first-order export back-ends (to_z3/to_prover9/to_tptp) —
6
+ second-order quantification is not first-order and is not SMT-expressible, so a
7
+ formula must first be evaluated by the finite-model evaluator (a later phase).
8
+
9
+ SecondOrderQuantifier is a frozen dataclass (hashable) subclassing Node. The
10
+ bound symbol is a PREDICATE name (uppercase, e.g. ``P``), not an object variable,
11
+ so binding a predicate name leaves the object-level free variables untouched:
12
+ free_variables walks _child_nodes() (only the ``formula`` child) and the scalar
13
+ ``predicate``/``arity`` fields are invisible to it. Structural methods
14
+ (to_msfol, _relativize, traversal) are inherited unchanged because
15
+ Node.map_children / Node._child_nodes treat ``formula`` as the single Node child
16
+ and copy the scalar ``type``/``predicate``/``arity`` fields verbatim. The Unicode
17
+ and LaTeX renderers live in _msfl_nodes.py and dispatch by class name;
18
+ serialisation, the tree label, and export-rejection live here.
19
+
20
+ The bound predicate's ``arity`` is INFERRED from its applications in the body at
21
+ parse time (see ``_second_order_quantifier_transform`` / ``_infer_so_arity`` below);
22
+ it is recorded on the node for the semantics phase but is NOT printed by the
23
+ renderers, because it is
24
+ re-inferred on re-parse — keeping the parse → to_unicode_str → parse round-trip
25
+ stable.
26
+ """
27
+
28
+ from dataclasses import dataclass
29
+
30
+ from ._fol_nodes import Node, Z3Env, NODE_CLASSES, Atom, register_parser_op
31
+ from .naming import ParsingError
32
+
33
+
34
+ # Shared message for the export back-ends: second-order quantification has no
35
+ # first-order / SMT encoding.
36
+ _NO_EXPORT = (
37
+ "Second-order quantification is not first-order and is not SMT-expressible; "
38
+ "it has no direct to_z3/to_prover9/to_tptp export. Evaluate the formula with "
39
+ "the finite-model evaluator instead."
40
+ )
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class SecondOrderQuantifier(Node):
45
+ """A second-order quantifier ∀P / ∃P binding a predicate variable.
46
+
47
+ type is "∀" (universal) or "∃" (existential). predicate is the bound
48
+ predicate-variable NAME (uppercase, as it appears applied in the body).
49
+ arity is that predicate variable's arity, inferred from its applications in
50
+ the body (0 for a never-applied / propositional predicate variable).
51
+ formula is the body.
52
+
53
+ Semantics: ∀P φ asserts φ holds for every interpretation of the
54
+ arity-``arity`` predicate P over the domain; ∃P φ for some interpretation.
55
+ Evaluation is a later (finite-model) phase.
56
+ """
57
+
58
+ type: str
59
+ predicate: str
60
+ arity: int
61
+ formula: Node
62
+
63
+ def _tree_parts(self):
64
+ """Return the ``∀ P/2`` style label and the single subformula child."""
65
+ return f"{self.type} {self.predicate}/{self.arity}", [self.formula]
66
+
67
+ def to_dict(self):
68
+ """Serialise to dict with type tag, quantifier type, bound predicate, arity, and body."""
69
+ return {
70
+ "_type": "SecondOrderQuantifier",
71
+ "type": self.type,
72
+ "predicate": self.predicate,
73
+ "arity": self.arity,
74
+ "formula": self.formula.to_dict(),
75
+ }
76
+
77
+ @staticmethod
78
+ def from_dict(d):
79
+ """Deserialise a SecondOrderQuantifier from a dict produced by to_dict."""
80
+ return SecondOrderQuantifier(
81
+ d["type"],
82
+ d["predicate"],
83
+ d["arity"],
84
+ Node.from_dict(d["formula"]),
85
+ )
86
+
87
+ def to_z3(self, env: Z3Env = None):
88
+ """Reject Z3 export: second-order quantification is not SMT-expressible."""
89
+ raise NotImplementedError(_NO_EXPORT)
90
+
91
+ def to_prover9(self) -> str:
92
+ """Reject Prover9 export: second-order quantification is not first-order."""
93
+ raise NotImplementedError(_NO_EXPORT)
94
+
95
+ def to_tptp(self) -> str:
96
+ """Reject TPTP export: second-order quantification is not first-order."""
97
+ raise NotImplementedError(_NO_EXPORT)
98
+
99
+
100
+ # =========================
101
+ # Registry extension
102
+ # =========================
103
+
104
+ NODE_CLASSES.update({
105
+ "SecondOrderQuantifier": SecondOrderQuantifier,
106
+ })
107
+
108
+
109
+ # =========================
110
+ # Arity inference + parser registration (second-order mode)
111
+ # =========================
112
+ #
113
+ # The bound predicate variable's arity is not in the surface syntax; it is
114
+ # inferred from the predicate's applications in the body at parse time. These two
115
+ # names (the inference helper and its error) live here, next to the node, and are
116
+ # re-exported by msflparser for backward compatibility.
117
+
118
+
119
+ class ConflictingArityError(ParsingError):
120
+ """Raised when a second-order-bound predicate is applied at conflicting arities.
121
+
122
+ Subclasses ParsingError so callers catching the parser's error type also
123
+ catch this inference-level failure. It is constructed directly (not from a
124
+ Lark exception), so it bypasses ParsingError.__init__ and sets its own
125
+ message.
126
+ """
127
+
128
+ def __init__(self, predname: str, arities):
129
+ arities_str = ", ".join(str(a) for a in arities)
130
+ message = (
131
+ f"SYNTAX_ERROR: Second-order predicate variable '{predname}' is "
132
+ f"applied at conflicting arities ({arities_str}); a bound predicate "
133
+ f"variable must be used at a single, consistent arity."
134
+ )
135
+ self.args = (message,)
136
+
137
+ def __str__(self):
138
+ return self.args[0]
139
+
140
+
141
+ def _infer_so_arity(body: Node, predname: str) -> int:
142
+ """Infer the arity of a second-order-bound predicate from its uses in body.
143
+
144
+ Scans ``body`` for ``Atom(predname, args)`` occurrences and returns the
145
+ common application arity (``len(args)``). All applications of ``predname``
146
+ must share the same arity, else a ConflictingArityError is raised. A predicate
147
+ that is never applied has arity 0 (a propositional / Boolean predicate
148
+ variable).
149
+
150
+ Shadowing: an inner ``SecondOrderQuantifier`` that rebinds the SAME predicate
151
+ name introduces a fresh binding, so its body is NOT scanned for the outer
152
+ binder's arity — the descent stops at such a rebinder. (An inner binder over a
153
+ DIFFERENT name is descended into normally.)
154
+ """
155
+ arities: set = set()
156
+
157
+ def visit(node: Node) -> None:
158
+ if isinstance(node, SecondOrderQuantifier) and node.predicate == predname:
159
+ return # inner binder shadows predname; do not descend into its scope
160
+ if isinstance(node, Atom) and node.predicate == predname:
161
+ arities.add(len(node.args))
162
+ for child in node._child_nodes():
163
+ visit(child)
164
+
165
+ visit(body)
166
+
167
+ if len(arities) > 1:
168
+ raise ConflictingArityError(predname, sorted(arities))
169
+ return arities.pop() if arities else 0
170
+
171
+
172
+ def _second_order_quantifier_transform(items):
173
+ """Build a SecondOrderQuantifier from [FORALL/EXISTS, PREDICATE, body].
174
+
175
+ The PREDICATE token has no terminal handler (raw Token), the body is already a
176
+ built AST node (Lark transforms bottom-up), and the bound predicate's arity is
177
+ inferred from its applications in the body.
178
+ """
179
+ quant = str(items[0])
180
+ predname = str(items[1])
181
+ body = items[2]
182
+ # Imported at call time, not at module scope: _ho_nodes is defined ON TOP of
183
+ # this module (it imports SecondOrderQuantifier and _infer_so_arity), so the
184
+ # dependency only closes once both are loaded — which, by the time any
185
+ # formula is parsed, they are. In a second-order body infer_bound_arity IS
186
+ # _infer_so_arity; it differs only where a property actually stands in an
187
+ # argument slot, which the second-order grammar cannot even express.
188
+ from ._ho_nodes import infer_bound_arity
189
+ arity = infer_bound_arity(body, predname)
190
+ return SecondOrderQuantifier(quant, predname, arity, body)
191
+
192
+
193
+ # ∀P / ∃P over a PREDICATE variable, at the quantifier level. The classical
194
+ # first-order quantifier (∀x / ∃x -> quantifier_) is registered for the
195
+ # "second_order" mode in _fol_nodes.py; this adds the second alternative (the
196
+ # grammar's two-alternative quantifier rule).
197
+ register_parser_op(SecondOrderQuantifier, "second_order", "quantifier",
198
+ "second_order_quantifier_",
199
+ "(FORALL | EXISTS) PREDICATE prefix",
200
+ _second_order_quantifier_transform)
@@ -0,0 +1,81 @@
1
+ """Shared symbol-name de-collision for the HOL / THF / Isabelle exporters.
2
+
3
+ Every exporter that sanitises predicate / constant / function names into a target
4
+ syntax must guarantee that **distinct source symbols never collapse to one emitted
5
+ name** — otherwise a non-valid formula could be emitted as a tautology (e.g. ``□Ab →
6
+ □ab`` collapsing to ``□ab → □ab``) or produce duplicate / ill-typed declarations. This
7
+ module factors out the two pieces that were duplicated across those exporters:
8
+
9
+ - :func:`dedupe` — assign a unique identifier by suffixing ``_2``, ``_3``, …;
10
+ - :class:`SymbolNames` — collect every ``(kind, name, arity)`` symbol of a formula and
11
+ map each to a unique functor, keyed so a predicate used at two arities is two symbols.
12
+ Parameterised by the target's base sanitiser and its alias map, so the THF and
13
+ Isabelle resolvers are one shared implementation.
14
+ """
15
+
16
+ from typing import Callable
17
+
18
+ from .nodes import Atom, Constant, Number, Function, Node
19
+ from ._numeral_symbols import prefixed_numeral_name
20
+ from ._truth_constants import truth_value
21
+
22
+
23
+ def dedupe(base: str, used: set) -> str:
24
+ """Return ``base`` (or ``base_2``, ``base_3``, …) not already in ``used``; mark it used."""
25
+ name, i = base, 2
26
+ while name in used:
27
+ name, i = f"{base}_{i}", i + 1
28
+ used.add(name)
29
+ return name
30
+
31
+
32
+ class SymbolNames:
33
+ """Map each distinct ``(kind, name, arity)`` symbol of ``formula`` to a unique name.
34
+
35
+ ``sanitize`` turns a raw name into a (possibly non-injective) target identifier;
36
+ ``alias`` is the set/mapping of names that should claim their natural sanitised form
37
+ first (e.g. ``=`` → ``feq``), so a user symbol that sanitises to the same stem is
38
+ pushed off it rather than the other way round. Predicates are keyed by
39
+ ``(name, arity)`` so a predicate used at two arities yields two distinct symbols.
40
+
41
+ ``reserved`` pre-claims the target's OWN fixed identifiers (built-in functors,
42
+ relation names, defined operators), so a user symbol that sanitises onto one of
43
+ them is pushed to a suffixed variant instead of silently colliding with — and
44
+ re-typing — a built-in (e.g. a user predicate named ``r`` colliding with the THF
45
+ accessibility relation ``r``, which would emit two conflicting declarations).
46
+ """
47
+
48
+ def __init__(self, formula: Node, sanitize: Callable[[str], str], alias=frozenset(),
49
+ reserved=frozenset()):
50
+ preds, consts, funcs = set(), set(), set()
51
+ for n in formula.walk():
52
+ if isinstance(n, Atom):
53
+ if truth_value(n) is not None:
54
+ continue # `$true` / `$false` are written as the target's own constants
55
+ preds.add((n.predicate, len(n.args)))
56
+ elif isinstance(n, Constant):
57
+ consts.add(n.name)
58
+ elif isinstance(n, Number):
59
+ # one constant per VALUE (``1`` and ``1.0`` are ``n1``); a writer that must also
60
+ # refuse a constant spelled ``n1`` rewrites its numerals first
61
+ # (:func:`~unicode_logic_kit.fol._numeral_symbols.numerals_as_constants`)
62
+ consts.add(prefixed_numeral_name(n.value))
63
+ elif isinstance(n, Function):
64
+ funcs.add((n.name, len(n.args)))
65
+ used: set = set(reserved)
66
+ self.pred, self.const, self.func = {}, {}, {}
67
+ for key in sorted(preds, key=lambda k: (k[0] not in alias, k)):
68
+ self.pred[key] = dedupe(sanitize(key[0]), used)
69
+ for name in sorted(consts):
70
+ self.const[name] = dedupe(sanitize(name), used)
71
+ for key in sorted(funcs):
72
+ self.func[key] = dedupe(sanitize(key[0]), used)
73
+
74
+ def atom(self, node: Atom) -> str:
75
+ return self.pred[(node.predicate, len(node.args))]
76
+
77
+ def constant(self, name: str) -> str:
78
+ return self.const[name]
79
+
80
+ def function(self, node) -> str:
81
+ return self.func[(node.name, len(node.args))]