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,921 @@
1
+ """Finite many-valued propositional logic -> HOL export (K3 / LP / FDE / any TruthMatrix).
2
+
3
+ A many-valued *shallow semantical embedding* (SSE) in the Benzmueller/Steen
4
+ style, generalised over the toolkit's finite-matrix layer
5
+ :mod:`unicode_logic_kit.semantics.matrix`: any :class:`~unicode_logic_kit.semantics.matrix.TruthMatrix`
6
+ (the three-valued ``K3_MATRIX`` / ``LP_MATRIX``, the four-valued Belnap-Dunn
7
+ ``FDE_MATRIX``, or a custom matrix built with ``TruthMatrix.from_functions``)
8
+ can be reified as an N-element HOL type, with the connectives defined as total
9
+ functions over that type matching the matrix's own operation tables, and
10
+ *validity* read as "the formula's value is **designated** under every
11
+ valuation". Concretely, for a matrix with values ``v1, ..., vN``:
12
+
13
+ * ``N`` distinct, pairwise-**distinct** and jointly **exhaustive** constants
14
+ reify the truth values (so an ATP/Isabelle can finitely case-split);
15
+ * one **ground equation per truth-table cell** pins each connective the
16
+ matrix actually tabulates (arity-1 for negation, arity-2 for the rest;
17
+ only over connectives with a *non-empty* table -- a formula using a
18
+ connective the matrix has no table for is rejected up front, not emitted
19
+ as a dangling symbol);
20
+ * a **designated** predicate ``des`` picks out the matrix's designated set;
21
+ * the conjecture/lemma is ``! [valuation] : des @ eval(formula)``, a
22
+ **Theorem** exactly when ``formula`` is valid in the matrix.
23
+
24
+ This is the data-driven core: :func:`to_thf_matrix` / :func:`to_isabelle_matrix`
25
+ (and their ``*_entailment`` counterparts) take **any** finite
26
+ :class:`~unicode_logic_kit.semantics.matrix.TruthMatrix` and need nothing hardcoded
27
+ about K3/LP/FDE. The legacy K3/LP-only entry points --
28
+ :func:`to_thf_k3lp`, :func:`to_isabelle_k3lp`, :func:`to_thf_k3lp_entailment`,
29
+ :func:`to_isabelle_k3lp_entailment` -- are now thin wrappers that look up
30
+ ``K3_MATRIX`` / ``LP_MATRIX`` and delegate, passing the historical constant/
31
+ function names (``tT``/``tB``/``tF``, ``kneg``/``kand``/``kor``/``kimp``/
32
+ ``kiff``/``kxor``) so their emitted text keeps its shape; this is a
33
+ compatibility convenience, not a promise about a specific matrix's naming.
34
+
35
+ Truth-value reification and naming. Each matrix value gets a constant name:
36
+ a caller may pin exact names via ``value_names`` (a ``{value: name}`` dict --
37
+ used by the K3/LP wrappers to reproduce ``tT``/``tB``/``tF``), and any value
38
+ left unnamed gets the default ``"t" + sanitised(str(value))`` (e.g. FDE's
39
+ ``"T"`` -> ``"tT"``, K3's ``0.5`` -> ``"t0_5"``), de-collided exactly like the
40
+ module's atom-variable naming (:func:`_assign_var_names`). Connective function/
41
+ predicate symbols are similarly overridable via ``conn_names`` (default: the
42
+ :class:`TruthMatrix` field name itself -- ``neg``/``conj``/``disj``/``impl``/
43
+ ``iff``/``xor``).
44
+
45
+ Honesty / scope. The toolkit *emits* a problem/theory; it does **not** run
46
+ Leo-III, Satallax, Isabelle, or Sledgehammer. Any *finite* matrix's validity is
47
+ **decidable** (finite truth tables), so the emitted problem is in principle
48
+ fully dischargeable by any sound+complete HOL prover -- and for the Isabelle
49
+ export the emit-time verdict (and, when invalid/non-entailing, the witness
50
+ used in the proof) comes from the toolkit's own oracle,
51
+ :func:`unicode_logic_kit.semantics.matrix.matrix_is_valid` /
52
+ :func:`~unicode_logic_kit.semantics.matrix.matrix_entails`, so the emitted lemma
53
+ cannot drift from the decision procedure it is meant to certify. This module
54
+ covers the **propositional** fragment only: quantifiers and all modal /
55
+ Lukasiewicz / sorted / lambda nodes are out of scope and raise
56
+ ``NotImplementedError`` -- exactly the nodes the toolkit's evaluators reject,
57
+ plus ``Quantifier`` (a first-order many-valued validity problem is not a
58
+ finite truth table and is not handled by this propositional export).
59
+
60
+ Public API: :func:`to_thf_matrix`, :func:`to_isabelle_matrix`,
61
+ :func:`to_thf_matrix_entailment`, :func:`to_isabelle_matrix_entailment`, and
62
+ the K3/LP-specific :func:`to_thf_k3lp`, :func:`to_isabelle_k3lp`,
63
+ :func:`to_thf_k3lp_entailment`, :func:`to_isabelle_k3lp_entailment`, and the
64
+ constant :data:`SYSTEMS`.
65
+ """
66
+
67
+ from itertools import product
68
+ from typing import Dict, List, Optional, Sequence, Type
69
+
70
+ from ..fol.nodes import (
71
+ Node, Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
72
+ Variable, Constant, Number, Function,
73
+ )
74
+ from ..fol._atom_keys import AtomKeys
75
+ from ..fol._msfl_nodes import key_text
76
+ from ..fol._truth_constants import truth_value as _truth_value
77
+ from ..semantics.matrix import (
78
+ TruthMatrix, Value, matrix_value, matrix_is_valid, matrix_entails,
79
+ K3_MATRIX, LP_MATRIX,
80
+ )
81
+
82
+ # The two legacy systems and their designated-value sets, kept in lock-step
83
+ # with unicode_logic_kit.semantics.manyvalued.DESIGNATED (K3 designates only
84
+ # "true"; LP designates "true" and "both") -- and, since 0.17, re-expressed as
85
+ # TruthMatrix instances (semantics.matrix.K3_MATRIX / LP_MATRIX).
86
+ SYSTEMS = ("K3", "LP")
87
+
88
+ _K3LP_MATRICES = {"K3": K3_MATRIX, "LP": LP_MATRIX}
89
+
90
+
91
+ def _check_system(system: str) -> str:
92
+ if system not in SYSTEMS:
93
+ raise ValueError(
94
+ f"Unknown three-valued system {system!r}; choose one of {list(SYSTEMS)} "
95
+ "(K3 designates {T}; LP designates {T, B}). For any other finite "
96
+ "logical matrix (e.g. FDE), use to_thf_matrix / to_isabelle_matrix "
97
+ "with a unicode_logic_kit.semantics.matrix.TruthMatrix directly."
98
+ )
99
+ return system
100
+
101
+
102
+ def _check_propositional(formula: Node) -> None:
103
+ """Reject any node outside the propositional {neg,and,or,xor,imp,iff} fragment.
104
+
105
+ Recurses the *connective* structure only, treating an ``Atom`` as a leaf:
106
+ an atom's term arguments (Variable/Constant/Number/Function -- e.g. the ``a``
107
+ in ``P(a)``) are legitimate and are not inspected; an atom is just a key into
108
+ the valuation.
109
+
110
+ Shared by every export in this module (K3/LP and the generic matrix
111
+ exporters alike): a quantified or modal/Lukasiewicz/sorted/lambda node has
112
+ no many-valued propositional reading here, and this export's validity is a
113
+ finite truth table, so ``Quantifier`` is out of scope too.
114
+ """
115
+ if isinstance(formula, Atom):
116
+ return
117
+ if isinstance(formula, Not):
118
+ _check_propositional(formula.formula)
119
+ return
120
+ if isinstance(formula, (And, Or, Xor, Implies, Iff)):
121
+ _check_propositional(formula.left)
122
+ _check_propositional(formula.right)
123
+ return
124
+ if isinstance(formula, Quantifier):
125
+ raise NotImplementedError(
126
+ "many-valued HOL export: Quantifier is outside the propositional "
127
+ "fragment; first-order many-valued validity is not a finite truth "
128
+ "table. Use unicode_logic_kit.semantics.manyvalued.is_valid or "
129
+ "semantics.matrix.matrix_is_valid with a domain instead."
130
+ )
131
+ if isinstance(formula, (Variable, Constant, Number, Function)):
132
+ raise NotImplementedError(
133
+ f"many-valued HOL export: {type(formula).__name__} is a term, not "
134
+ "a propositional formula."
135
+ )
136
+ raise NotImplementedError(
137
+ f"many-valued HOL export: {type(formula).__name__} is not in the "
138
+ "propositional fragment {not, and, or, xor, implies, iff}; this "
139
+ "export has no many-valued reading for it."
140
+ )
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # Atom keys -> THF / Isabelle valuation-variable names
145
+ # ---------------------------------------------------------------------------
146
+ #
147
+ # An *atom* of the propositional formula is keyed by its canonical
148
+ # rendering with every constant written by its bare name (``key_text``) -- EXACTLY
149
+ # the key the toolkit's evaluators use (e.g. ``valuation[key_text(atom)]`` in
150
+ # matrix_value), and not the text of the formula, which writes a constant such as
151
+ # ``k2`` in quotes. Distinct atoms become distinct universally-quantified
152
+ # valuation variables.
153
+
154
+ def _atom_keys(*formulas: Node) -> List[str]:
155
+ """Distinct atom keys (canonical ``key_text``) in first-seen order, over all
156
+ ``formulas`` of one problem.
157
+
158
+ ``$true`` / ``$false`` are the matrix's top / bottom value, not letters. Two different atoms
159
+ that print alike (the numeral ``1`` and a constant named ``1``) would be ONE valuation
160
+ variable and the exported problem another one, and a sorted constant has no reading in a
161
+ matrix, so both are refused by name with ``NotImplementedError``.
162
+ """
163
+ return AtomKeys("many-valued HOL export", "refuse").letters(formulas)
164
+
165
+
166
+ def _safe_name(key: str) -> str:
167
+ """Turn an arbitrary key into a safe alnum/underscore suffix for a name."""
168
+ return "".join(c if (c.isalnum() or c == "_") else "_" for c in key)
169
+
170
+
171
+ def _assign_var_names(keys: List[str]) -> dict:
172
+ """Map each atom key to a distinct uppercase THF/Isabelle variable name.
173
+
174
+ ``P(a)`` -> ``V_P_A`` etc.; on a collision after sanitisation a numeric
175
+ suffix is appended so distinct atoms never share a valuation variable.
176
+ """
177
+ names_by_key = {}
178
+ used = set()
179
+ for key in keys:
180
+ base = "V_" + _safe_name(key)
181
+ base = base.upper()
182
+ name = base
183
+ i = 0
184
+ while name in used:
185
+ i += 1
186
+ name = f"{base}_{i}"
187
+ used.add(name)
188
+ names_by_key[key] = name
189
+ return names_by_key
190
+
191
+
192
+ # ===========================================================================
193
+ # Generic matrix machinery: naming, op-availability, and the emit-time oracle.
194
+ # ===========================================================================
195
+ #
196
+ # Canonical (node class, TruthMatrix field name, arity) triples, in the fixed
197
+ # order every export below declares/axiomatises them -- same order the K3/LP
198
+ # export always used (neg, conj, disj, impl, iff, xor).
199
+ _CANONICAL_OPS = [
200
+ (Not, "neg", 1), (And, "conj", 2), (Or, "disj", 2),
201
+ (Implies, "impl", 2), (Iff, "iff", 2), (Xor, "xor", 2),
202
+ ]
203
+ _CANONICAL_OP_INFO = {cls: (attr, arity) for cls, attr, arity in _CANONICAL_OPS}
204
+ # Default connective symbol names are the TruthMatrix field name prefixed with
205
+ # "mv_" (many-valued) -- NOT the bare field name. Isabelle/HOL's prelude
206
+ # (`imports Main`) already binds `conj`/`disj` as the underlying constants of
207
+ # `\<and>`/`\<or>`; a bare `fun conj` or `fun disj` would collide with those.
208
+ # The legacy K3/LP export already dodged exactly this with its "k" prefix
209
+ # (kneg/kand/kor/kimp/kiff/kxor); "mv_" generalises the same precaution to any
210
+ # matrix's default names. A caller may still override via ``conn_names``.
211
+ _DEFAULT_CONN_NAMES = {cls: f"mv_{attr}" for cls, attr, _ in _CANONICAL_OPS}
212
+
213
+
214
+ def _matrix_defined_ops(matrix: TruthMatrix):
215
+ """The canonical ``(node class, table attr, arity)`` triples ``matrix`` actually
216
+ tabulates (a non-empty dict for that connective), in the fixed declaration order."""
217
+ return [(cls, attr, arity) for cls, attr, arity in _CANONICAL_OPS if getattr(matrix, attr)]
218
+
219
+
220
+ def _collect_used_ops(node: Node, acc: set) -> None:
221
+ """Record which connective *classes* appear in the propositional structure of ``node``."""
222
+ if isinstance(node, Atom):
223
+ return
224
+ if isinstance(node, Not):
225
+ acc.add(Not)
226
+ _collect_used_ops(node.formula, acc)
227
+ return
228
+ if isinstance(node, (And, Or, Xor, Implies, Iff)):
229
+ acc.add(type(node))
230
+ _collect_used_ops(node.left, acc)
231
+ _collect_used_ops(node.right, acc)
232
+ return
233
+ # Any other node class is already rejected by _check_propositional.
234
+
235
+
236
+ def _check_matrix_supports(formulas: Sequence[Node], matrix: TruthMatrix) -> None:
237
+ """Raise ``ValueError`` if some formula uses a connective ``matrix`` has no table for.
238
+
239
+ ``matrix``'s ``neg``/``conj``/``disj``/``impl``/``iff``/``xor`` fields are
240
+ plain dicts; an empty dict means "this matrix does not define this
241
+ connective" (``TruthMatrix.from_functions`` always fills all six, but a
242
+ hand-built matrix may legitimately omit one). Emitting a THF/Isabelle
243
+ symbol with no axioms would be unsound (an uninterpreted function the
244
+ prover could give any meaning), so this is checked eagerly instead.
245
+ """
246
+ used: set = set()
247
+ for f in formulas:
248
+ _collect_used_ops(f, used)
249
+ for cls in used:
250
+ attr, _ = _CANONICAL_OP_INFO[cls]
251
+ if not getattr(matrix, attr):
252
+ raise ValueError(
253
+ f"TruthMatrix {matrix.name!r} does not define {attr!r} (its "
254
+ f"table is empty), but the formula uses {cls.__name__}. This "
255
+ "export only reifies connectives the matrix actually "
256
+ f"tabulates -- supply a matrix with a non-empty {attr!r} "
257
+ f"table, or remove {cls.__name__} from the formula."
258
+ )
259
+
260
+
261
+ def _default_value_name(value: Value, used: set) -> str:
262
+ """Default constant name for a matrix value: ``"t" + sanitised(str(value))``,
263
+ de-collided with a numeric suffix (mirrors :func:`_assign_var_names`)."""
264
+ base = "t" + _safe_name(str(value))
265
+ name = base
266
+ i = 0
267
+ while name in used:
268
+ i += 1
269
+ name = f"{base}_{i}"
270
+ return name
271
+
272
+
273
+ def _resolve_value_names(matrix: TruthMatrix,
274
+ value_names: Optional[Dict[Value, str]]) -> Dict[Value, str]:
275
+ """Merge caller-supplied constant names with defaults for the rest.
276
+
277
+ The returned dict's iteration order is significant: it is the order
278
+ constants/constructors are declared in the emitted text. It is the
279
+ caller's ``value_names`` keys, in the order given (letting a caller pin a
280
+ specific declaration order), followed by any remaining ``matrix.values``
281
+ in the matrix's own order. Every value resolves to a distinct name.
282
+ """
283
+ provided = dict(value_names or {})
284
+ for v in provided:
285
+ if v not in matrix.values:
286
+ raise ValueError(
287
+ f"value_names key {v!r} is not one of matrix {matrix.name!r}'s "
288
+ f"values {matrix.values!r}.")
289
+ resolved: Dict[Value, str] = {}
290
+ used = set()
291
+ for v, nm in provided.items():
292
+ resolved[v] = nm
293
+ used.add(nm)
294
+ for v in matrix.values:
295
+ if v in resolved:
296
+ continue
297
+ name = _default_value_name(v, used)
298
+ resolved[v] = name
299
+ used.add(name)
300
+ return resolved
301
+
302
+
303
+ def _resolve_conn_names(conn_names: Optional[Dict[Type[Node], str]]) -> Dict[Type[Node], str]:
304
+ """Merge caller-supplied connective symbol names with the field-name defaults."""
305
+ resolved = dict(_DEFAULT_CONN_NAMES)
306
+ if conn_names:
307
+ resolved.update(conn_names)
308
+ return resolved
309
+
310
+
311
+ def _constant_names(matrix: TruthMatrix, names: Dict[Value, str]) -> Dict[bool, Optional[str]]:
312
+ """The value constants that stand for the truth constants ``$true`` / ``$false``.
313
+
314
+ ``True`` maps to the name of ``matrix.top`` and ``False`` to the name of
315
+ ``matrix.bottom``; ``None`` for a value the matrix does not declare.
316
+ """
317
+ return {True: names[matrix.top] if matrix.top is not None else None,
318
+ False: names[matrix.bottom] if matrix.bottom is not None else None}
319
+
320
+
321
+ def _constant_term(constants: Optional[Dict[bool, Optional[str]]], truth: bool,
322
+ node: Atom, route: str) -> str:
323
+ """The value constant for the truth constant ``node``, or a refusal by name."""
324
+ name = (constants or {}).get(truth)
325
+ if name is None:
326
+ raise NotImplementedError(
327
+ f"{route}: the matrix declares no {'top' if truth else 'bottom'} value, so "
328
+ f"it has no reading of the truth constant {node.predicate}; declare one "
329
+ "with TruthMatrix.from_functions(..., top=..., bottom=...), or write the "
330
+ "formula without the constant.")
331
+ return name
332
+
333
+
334
+ def _matrix_falsifying_assignment(formula: Node, matrix: TruthMatrix, keys: List[str]):
335
+ """A ``matrix``-value assignment (in ``keys`` order) making ``formula`` non-designated.
336
+
337
+ Only called after :func:`~unicode_logic_kit.semantics.matrix.matrix_is_valid`
338
+ has already reported ``formula`` invalid (which enumerates the identical
339
+ assignment space and bounds it by ``MAX_MODELS``), so a witness is
340
+ guaranteed to exist and this re-enumeration is safe.
341
+ """
342
+ for combo in product(matrix.values, repeat=len(keys)):
343
+ asg = dict(zip(keys, combo))
344
+ if matrix_value(formula, asg, matrix) not in matrix.designated:
345
+ return combo
346
+ return None
347
+
348
+
349
+ def _matrix_entailment_countermodel(premises: Sequence[Node], conclusion: Node,
350
+ matrix: TruthMatrix, keys: List[str]):
351
+ """A ``matrix``-value assignment designating every premise but not the conclusion.
352
+
353
+ Only called after :func:`~unicode_logic_kit.semantics.matrix.matrix_entails`
354
+ has already reported non-entailment (same enumeration, same bound), so a
355
+ countermodel is guaranteed to exist.
356
+ """
357
+ for combo in product(matrix.values, repeat=len(keys)):
358
+ asg = dict(zip(keys, combo))
359
+ if (all(matrix_value(p, asg, matrix) in matrix.designated for p in premises)
360
+ and matrix_value(conclusion, asg, matrix) not in matrix.designated):
361
+ return combo
362
+ return None
363
+
364
+
365
+ # ===========================================================================
366
+ # (A) THF export
367
+ # ===========================================================================
368
+ #
369
+ # Type ``tv`` with |values| exhaustive, pairwise-distinct constants; connective
370
+ # functions matching the matrix's own tables, one ground equation per cell.
371
+
372
+
373
+ def _thf_preamble(matrix: TruthMatrix, names: Dict[Value, str], conns: Dict[Type[Node], str],
374
+ type_name: str, predicate_name: str) -> List[str]:
375
+ """Type/constant/connective *declarations* plus distinctness + exhaustiveness."""
376
+ ordered = list(names.keys())
377
+ lines = [f"thf({type_name}_type, type, ( {type_name} : $tType ))."]
378
+ for v in ordered:
379
+ cname = names[v]
380
+ lines.append(f"thf({cname}_decl, type, ( {cname} : {type_name} )).")
381
+ lines.append(
382
+ f"thf({predicate_name}_decl, type, ( {predicate_name} : ( {type_name} > $o ) )).")
383
+ for cls, attr, arity in _matrix_defined_ops(matrix):
384
+ cname = conns[cls]
385
+ ty = (f"( {type_name} > {type_name} )" if arity == 1
386
+ else f"( {type_name} > {type_name} > {type_name} )")
387
+ lines.append(f"thf({cname}_decl, type, ( {cname} : {ty} )).")
388
+ if len(ordered) > 1:
389
+ pairs = [f"( {names[a]} != {names[b]} )"
390
+ for i, a in enumerate(ordered) for b in ordered[i + 1:]]
391
+ lines.append(f"thf({type_name}_distinct, axiom, ( " + " & ".join(pairs) + " )).")
392
+ disj = " | ".join(f"( X = {names[v]} )" for v in ordered)
393
+ lines.append(
394
+ f"thf({type_name}_exhaust, axiom, ( ! [X: {type_name}] : ( {disj} ) )).")
395
+ return lines
396
+
397
+
398
+ def _thf_op_axioms(matrix: TruthMatrix, names: Dict[Value, str],
399
+ conns: Dict[Type[Node], str]) -> List[str]:
400
+ """One THF ground equation per truth-table cell, for every op ``matrix`` defines."""
401
+ ordered = list(names.keys())
402
+ lines: List[str] = []
403
+ for cls, attr, arity in _matrix_defined_ops(matrix):
404
+ cname = conns[cls]
405
+ table = getattr(matrix, attr)
406
+ if arity == 1:
407
+ for v in ordered:
408
+ out = names[table[v]]
409
+ lines.append(
410
+ f"thf({cname}_{names[v]}, axiom, "
411
+ f"( ( {cname} @ {names[v]} ) = {out} )).")
412
+ else:
413
+ for a in ordered:
414
+ for b in ordered:
415
+ out = names[table[(a, b)]]
416
+ lines.append(
417
+ f"thf({cname}_{names[a]}_{names[b]}, axiom, "
418
+ f"( ( {cname} @ {names[a]} @ {names[b]} ) = {out} )).")
419
+ return lines
420
+
421
+
422
+ def _thf_designated_predicate(matrix: TruthMatrix, names: Dict[Value, str],
423
+ type_name: str, predicate_name: str) -> str:
424
+ """The ``des : tv > $o`` predicate, true exactly of the matrix's designated values."""
425
+ ordered = list(names.keys())
426
+ terms = [f"( D = {names[v]} )" for v in ordered if v in matrix.designated]
427
+ if not terms:
428
+ raise ValueError(f"TruthMatrix {matrix.name!r} has an empty designated set.")
429
+ body = terms[0] if len(terms) == 1 else "( " + " | ".join(terms) + " )"
430
+ return (f"thf({predicate_name}_def, definition, "
431
+ f"( {predicate_name} = ( ^ [D: {type_name}] : {body} ) )).")
432
+
433
+
434
+ def _thf_eval_matrix(node: Node, names_by_key: dict, conns: Dict[Type[Node], str],
435
+ constants: Optional[Dict[bool, Optional[str]]] = None) -> str:
436
+ """Render the formula as a truth-value-typed THF term using the connective fns.
437
+
438
+ ``constants`` names the value constants of the truth constants ``$true`` /
439
+ ``$false`` (see :func:`_constant_names`).
440
+ """
441
+ if isinstance(node, Atom):
442
+ truth = _truth_value(node)
443
+ if truth is not None:
444
+ return _constant_term(constants, truth, node, "to_thf_matrix")
445
+ return names_by_key[key_text(node)]
446
+ if isinstance(node, Not):
447
+ return f"( {conns[Not]} @ {_thf_eval_matrix(node.formula, names_by_key, conns, constants)} )"
448
+ for cls in (And, Or, Xor, Implies, Iff):
449
+ if isinstance(node, cls):
450
+ return (f"( {conns[cls]} @ {_thf_eval_matrix(node.left, names_by_key, conns, constants)} "
451
+ f"@ {_thf_eval_matrix(node.right, names_by_key, conns, constants)} )")
452
+ raise NotImplementedError(
453
+ f"to_thf_matrix: unsupported node {type(node).__name__}.")
454
+
455
+
456
+ def to_thf_matrix(formula: Node, matrix: TruthMatrix, *,
457
+ value_names: Optional[Dict[Value, str]] = None,
458
+ conn_names: Optional[Dict[Type[Node], str]] = None,
459
+ type_name: str = "tv",
460
+ predicate_name: str = "des") -> str:
461
+ """Emit a complete THF problem whose theorem-hood is ``matrix``-validity of ``formula``.
462
+
463
+ Builds a self-contained TPTP **THF** problem: an ``|matrix.values|``-element
464
+ truth-value type (one constant per value, distinctness + exhaustiveness
465
+ axioms), the connective functions ``matrix`` tabulates -- defined by full
466
+ case analysis, one ground equation per cell -- the designated predicate
467
+ ``des`` (true of exactly ``matrix.designated``), and the conjecture
468
+ ``! [valuation] : des @ <formula>`` where each propositional atom is a
469
+ universally-quantified valuation variable. The conjecture is a **Theorem**
470
+ iff ``formula`` is valid in ``matrix`` (:func:`~unicode_logic_kit.semantics.matrix.matrix_is_valid`).
471
+
472
+ Propositional fragment only (``not/and/or/xor/implies/iff`` over atoms); a
473
+ ``Quantifier`` or any modal/Lukasiewicz/sorted/lambda node raises
474
+ ``NotImplementedError``. A formula using a connective ``matrix`` has no
475
+ table for raises ``ValueError`` (see :func:`_check_matrix_supports`).
476
+
477
+ ``value_names`` / ``conn_names`` let a caller pin exact constant/function
478
+ names (and, via a dict's key order, the declaration order); anything
479
+ unnamed gets a data-driven default. ``type_name`` / ``predicate_name``
480
+ rename the truth-value type and the designated predicate.
481
+
482
+ Note: the toolkit emits this problem; it does **not** run a prover. A
483
+ finite matrix's validity is decidable, so a sound+complete HOL ATP
484
+ (Leo-III, Satallax) can in principle discharge it.
485
+ """
486
+ _check_propositional(formula)
487
+ _check_matrix_supports([formula], matrix)
488
+ names = _resolve_value_names(matrix, value_names)
489
+ conns = _resolve_conn_names(conn_names)
490
+ keys = _atom_keys(formula)
491
+ names_by_key = _assign_var_names(keys)
492
+
493
+ designated_names = [names[v] for v in names if v in matrix.designated]
494
+ lines = [
495
+ f"% {matrix.name} many-valued shallow embedding of a propositional formula.",
496
+ f"% Values: {[names[v] for v in names]}; designated: {designated_names}. "
497
+ "Conjecture is 'Theorem' iff the formula is valid in this matrix.",
498
+ f"% Formula: {formula.to_unicode_str()}",
499
+ ]
500
+ lines.extend(_thf_preamble(matrix, names, conns, type_name, predicate_name))
501
+ lines.extend(_thf_op_axioms(matrix, names, conns))
502
+ lines.append(_thf_designated_predicate(matrix, names, type_name, predicate_name))
503
+
504
+ body = _thf_eval_matrix(formula, names_by_key, conns, _constant_names(matrix, names))
505
+ if keys:
506
+ binder = ", ".join(f"{names_by_key[k]}: {type_name}" for k in keys)
507
+ conj = f"! [{binder}] : ( {predicate_name} @ {body} )"
508
+ else:
509
+ conj = f"( {predicate_name} @ {body} )"
510
+ lines.append(f"thf(goal, conjecture, ( {conj} )).")
511
+ return "\n".join(lines) + "\n"
512
+
513
+
514
+ def to_thf_matrix_entailment(premises: Sequence[Node], conclusion: Node, matrix: TruthMatrix, *,
515
+ value_names: Optional[Dict[Value, str]] = None,
516
+ conn_names: Optional[Dict[Type[Node], str]] = None,
517
+ type_name: str = "tv",
518
+ predicate_name: str = "des") -> str:
519
+ """Emit a THF problem whose theorem-hood is ``matrix``-entailment of ``conclusion`` by ``premises``.
520
+
521
+ Designation-preserving entailment: ``premises |= conclusion`` iff every
522
+ valuation that designates **all** premises also designates the
523
+ conclusion. The conjecture is
524
+ ``! [valuation] : ( (des p1 & ... & des pn) => des c )``, matching
525
+ :func:`~unicode_logic_kit.semantics.matrix.matrix_entails`.
526
+
527
+ Same propositional fragment, rejection surface, and naming knobs as
528
+ :func:`to_thf_matrix`.
529
+ """
530
+ premises = list(premises)
531
+ for f in premises:
532
+ _check_propositional(f)
533
+ _check_propositional(conclusion)
534
+ _check_matrix_supports([*premises, conclusion], matrix)
535
+ names = _resolve_value_names(matrix, value_names)
536
+ conns = _resolve_conn_names(conn_names)
537
+
538
+ keys = _atom_keys(*premises, conclusion)
539
+ names_by_key = _assign_var_names(keys)
540
+
541
+ lines = [
542
+ f"% {matrix.name} many-valued entailment.",
543
+ "% Conjecture is 'Theorem' iff the premises entail the conclusion.",
544
+ "% Premises: " + ("; ".join(p.to_unicode_str() for p in premises) or "(none)"),
545
+ f"% Conclusion: {conclusion.to_unicode_str()}",
546
+ ]
547
+ lines.extend(_thf_preamble(matrix, names, conns, type_name, predicate_name))
548
+ lines.extend(_thf_op_axioms(matrix, names, conns))
549
+ lines.append(_thf_designated_predicate(matrix, names, type_name, predicate_name))
550
+
551
+ consts = _constant_names(matrix, names)
552
+ concl = f"( {predicate_name} @ {_thf_eval_matrix(conclusion, names_by_key, conns, consts)} )"
553
+ if premises:
554
+ prem = " & ".join(
555
+ f"( {predicate_name} @ {_thf_eval_matrix(p, names_by_key, conns, consts)} )"
556
+ for p in premises)
557
+ goal_body = f"( ( {prem} ) => {concl} )"
558
+ else:
559
+ goal_body = concl
560
+ if keys:
561
+ binder = ", ".join(f"{names_by_key[k]}: {type_name}" for k in keys)
562
+ conj = f"! [{binder}] : {goal_body}"
563
+ else:
564
+ conj = goal_body
565
+ lines.append(f"thf(goal, conjecture, ( {conj} )).")
566
+ return "\n".join(lines) + "\n"
567
+
568
+
569
+ # ===========================================================================
570
+ # (B) Isabelle/HOL export
571
+ # ===========================================================================
572
+ #
573
+ # A self-contained, loadable theory: a datatype with |values| constructors,
574
+ # fun definitions for the connectives matching the matrix's tables, the
575
+ # designated predicate, and a lemma provable iff the formula is matrix-valid.
576
+ # Discharge with `by eval` / `by (cases ...)` / Sledgehammer.
577
+
578
+
579
+ def _isa_datatype_line(matrix: TruthMatrix, names: Dict[Value, str], type_name: str) -> str:
580
+ ordered = list(names.keys())
581
+ return f"datatype {type_name} = " + " | ".join(names[v] for v in ordered)
582
+
583
+
584
+ def _isa_neg_def_matrix(matrix: TruthMatrix, names: Dict[Value, str],
585
+ conns: Dict[Type[Node], str], type_name: str) -> str:
586
+ cname = conns[Not]
587
+ ordered = list(names.keys())
588
+ eqs = [f' "{cname} {names[v]} = {names[matrix.neg[v]]}"' for v in ordered]
589
+ return (f'fun {cname} :: "{type_name} \\<Rightarrow> {type_name}" where\n'
590
+ + " |\n".join(eqs))
591
+
592
+
593
+ def _isa_binop_def_matrix(matrix: TruthMatrix, names: Dict[Value, str], conns: Dict[Type[Node], str],
594
+ cls: type, attr: str, type_name: str) -> str:
595
+ cname = conns[cls]
596
+ table = getattr(matrix, attr)
597
+ ordered = list(names.keys())
598
+ eqs = []
599
+ for a in ordered:
600
+ for b in ordered:
601
+ out = names[table[(a, b)]]
602
+ eqs.append(f' "{cname} {names[a]} {names[b]} = {out}"')
603
+ return (f'fun {cname} :: "{type_name} \\<Rightarrow> {type_name} \\<Rightarrow> {type_name}" where\n'
604
+ + " |\n".join(eqs))
605
+
606
+
607
+ def _isa_designated_def_matrix(matrix: TruthMatrix, names: Dict[Value, str],
608
+ predicate_name: str, type_name: str) -> str:
609
+ ordered = list(names.keys())
610
+ ds = [names[v] for v in ordered if v in matrix.designated]
611
+ if not ds:
612
+ raise ValueError(f"TruthMatrix {matrix.name!r} has an empty designated set.")
613
+ rhs = f"(d = {ds[0]})" if len(ds) == 1 else "(" + " \\<or> ".join(f"d = {n}" for n in ds) + ")"
614
+ return (f'definition {predicate_name} :: "{type_name} \\<Rightarrow> bool" where\n'
615
+ f' "{predicate_name} d \\<equiv> {rhs}"')
616
+
617
+
618
+ def _isa_matrix_prelude(theory_name: str, matrix: TruthMatrix, names: Dict[Value, str],
619
+ conns: Dict[Type[Node], str], type_name: str, predicate_name: str,
620
+ header_comments: List[str]) -> List[str]:
621
+ """The shared theory header: datatype, connective funs, designated-predicate definition.
622
+
623
+ Returns the lines from ``theory ... begin`` down to (and including) the
624
+ designated-predicate definition; the caller appends its lemma(s) and the
625
+ final ``end``.
626
+ """
627
+ parts = [
628
+ f"theory {theory_name}",
629
+ " imports Main",
630
+ "begin",
631
+ "",
632
+ ]
633
+ parts += header_comments
634
+ parts += ["", _isa_datatype_line(matrix, names, type_name), ""]
635
+ for cls, attr, arity in _matrix_defined_ops(matrix):
636
+ if arity == 1:
637
+ parts.append(_isa_neg_def_matrix(matrix, names, conns, type_name))
638
+ else:
639
+ parts.append(_isa_binop_def_matrix(matrix, names, conns, cls, attr, type_name))
640
+ parts.append("")
641
+ parts.append(_isa_designated_def_matrix(matrix, names, predicate_name, type_name))
642
+ parts.append("")
643
+ return parts
644
+
645
+
646
+ def _isa_eval_matrix(node: Node, names_by_key: dict, conns: Dict[Type[Node], str],
647
+ constants: Optional[Dict[bool, Optional[str]]] = None) -> str:
648
+ if isinstance(node, Atom):
649
+ truth = _truth_value(node)
650
+ if truth is not None:
651
+ return _constant_term(constants, truth, node, "to_isabelle_matrix")
652
+ return names_by_key[key_text(node)].lower()
653
+ if isinstance(node, Not):
654
+ return f"({conns[Not]} {_isa_eval_matrix(node.formula, names_by_key, conns, constants)})"
655
+ for cls in (And, Or, Xor, Implies, Iff):
656
+ if isinstance(node, cls):
657
+ return (f"({conns[cls]} {_isa_eval_matrix(node.left, names_by_key, conns, constants)} "
658
+ f"{_isa_eval_matrix(node.right, names_by_key, conns, constants)})")
659
+ raise NotImplementedError(
660
+ f"to_isabelle_matrix: unsupported node {type(node).__name__}.")
661
+
662
+
663
+ def _isa_forall_proof(isa_vars: List[str], predicate_name: str = "des") -> str:
664
+ """Discharge ``\\<forall>vars. des (...)`` by exhausting the finite value-type cases.
665
+
666
+ ``simp`` cannot reduce ``fn v`` while ``v`` is an abstract variable (e.g.
667
+ on the LP-valid ``p \\<or> \\<not>p``), so a bare
668
+ ``by (simp add: <predicate>_def)`` fails. Splitting every quantified
669
+ variable into its constructors first makes each leaf ground, and
670
+ ``simp_all`` then evaluates the ``fun`` tables.
671
+ """
672
+ if not isa_vars:
673
+ return f" by (simp add: {predicate_name}_def)"
674
+ cases = "; ".join(f"case_tac {v}" for v in isa_vars)
675
+ return (" apply (intro allI)\n"
676
+ f" apply ({cases})\n"
677
+ f" by (simp_all add: {predicate_name}_def)")
678
+
679
+
680
+ def _isa_exists_proof(witness: List[str], predicate_name: str = "des") -> str:
681
+ """Discharge ``\\<exists>vars. \\<not> des (...)`` by supplying the witness.
682
+
683
+ ``witness`` is the counter-valuation (a value-type constructor per
684
+ quantified variable, in binder order) computed at emit time; each
685
+ ``rule exI`` instantiates one existential, then ``simp`` evaluates the
686
+ now-ground body.
687
+ """
688
+ if not witness:
689
+ return f" by (simp add: {predicate_name}_def)"
690
+ exs = ", ".join(f"rule exI[where x={w}]" for w in witness)
691
+ return f" by ({exs}, simp add: {predicate_name}_def)"
692
+
693
+
694
+ def to_isabelle_matrix(formula: Node, matrix: TruthMatrix, *,
695
+ name: str = "Matrix_Validity",
696
+ lemma_name: str = "matrix_validity",
697
+ value_names: Optional[Dict[Value, str]] = None,
698
+ conn_names: Optional[Dict[Type[Node], str]] = None,
699
+ type_name: str = "tv",
700
+ predicate_name: str = "des") -> str:
701
+ """Emit a complete, loadable Isabelle/HOL theory text for ``matrix``-validity of ``formula``.
702
+
703
+ Produces a ``theory <name> ... begin ... end`` snippet: an
704
+ ``|matrix.values|``-constructor ``datatype``, ``fun`` definitions of every
705
+ connective ``matrix`` tabulates, the designated predicate (true of exactly
706
+ ``matrix.designated``), and a ``lemma`` that **always loads**:
707
+
708
+ * if ``formula`` is valid in ``matrix``
709
+ (:func:`~unicode_logic_kit.semantics.matrix.matrix_is_valid`), the
710
+ lemma is ``\\<forall> valuation. des (<formula>)``;
711
+ * if it is **invalid**, the lemma is the *refutation*
712
+ ``\\<exists> valuation. \\<not> des (<formula>)`` (which then holds).
713
+
714
+ Either way the lemma encodes the validity verdict and is **discharged by a
715
+ real proof** Isabelle accepts: the ``\\<forall>`` form by exhausting each
716
+ quantified variable's constructors (``case_tac`` + ``simp_all``); the
717
+ ``\\<exists>`` refutation form by supplying the counter-valuation
718
+ (computed by :func:`~unicode_logic_kit.semantics.matrix.matrix_is_valid`'s
719
+ sibling enumeration) as ``rule exI`` witnesses, then ``simp``. A leading
720
+ comment states the verdict so the reader knows which form was emitted.
721
+
722
+ Same propositional fragment, rejection surface, and naming knobs
723
+ (``value_names``/``conn_names``/``type_name``/``predicate_name``) as
724
+ :func:`to_thf_matrix`; ``name`` is the Isabelle theory name and
725
+ ``lemma_name`` the lemma's label.
726
+
727
+ Note: the toolkit emits the theory; it does **not** run Isabelle. The
728
+ verdict and witness come from the toolkit's own decision procedure
729
+ (:mod:`unicode_logic_kit.semantics.matrix`), cross-checked in the tests.
730
+ """
731
+ _check_propositional(formula)
732
+ _check_matrix_supports([formula], matrix)
733
+ names = _resolve_value_names(matrix, value_names)
734
+ conns = _resolve_conn_names(conn_names)
735
+ keys = _atom_keys(formula)
736
+ names_by_key = _assign_var_names(keys)
737
+ isa_vars = [names_by_key[k].lower() for k in keys]
738
+
739
+ valid = matrix_is_valid(formula, matrix)
740
+ body = _isa_eval_matrix(formula, names_by_key, conns, _constant_names(matrix, names))
741
+ if keys:
742
+ binder = " ".join(isa_vars)
743
+ if valid:
744
+ lemma = f" \"\\<forall>{binder}. {predicate_name} ({body})\""
745
+ proof = _isa_forall_proof(isa_vars, predicate_name)
746
+ else:
747
+ lemma = f" \"\\<exists>{binder}. \\<not> {predicate_name} ({body})\""
748
+ witness_values = _matrix_falsifying_assignment(formula, matrix, keys)
749
+ proof = _isa_exists_proof([names[v] for v in witness_values], predicate_name)
750
+ else:
751
+ lemma = (f" \"{predicate_name} ({body})\"" if valid
752
+ else f" \"\\<not> {predicate_name} ({body})\"")
753
+ proof = f" by (simp add: {predicate_name}_def)"
754
+
755
+ verdict = "VALID" if valid else "INVALID (lemma is the refutation)"
756
+ designated_names = [names[v] for v in names if v in matrix.designated]
757
+ header = [
758
+ f"(* {matrix.name} many-valued shallow embedding of a propositional formula. *)",
759
+ f"(* Designated: {designated_names}. Verdict: {verdict}. *)",
760
+ f"(* Formula: {formula.to_unicode_str()} *)",
761
+ ]
762
+ parts = _isa_matrix_prelude(name, matrix, names, conns, type_name, predicate_name, header)
763
+ parts.append(f"lemma {lemma_name}:")
764
+ parts.append(lemma)
765
+ parts.append(proof)
766
+ parts.append("")
767
+ parts.append("end")
768
+ return "\n".join(parts) + "\n"
769
+
770
+
771
+ def to_isabelle_matrix_entailment(premises: Sequence[Node], conclusion: Node, matrix: TruthMatrix, *,
772
+ name: str = "Matrix_Entailment",
773
+ lemma_name: str = "matrix_entailment",
774
+ value_names: Optional[Dict[Value, str]] = None,
775
+ conn_names: Optional[Dict[Type[Node], str]] = None,
776
+ type_name: str = "tv",
777
+ predicate_name: str = "des") -> str:
778
+ """Emit a loadable Isabelle/HOL theory whose lemma is ``matrix``-entailment.
779
+
780
+ Emits a lemma that **always loads**, mirroring
781
+ :func:`~unicode_logic_kit.semantics.matrix.matrix_entails`:
782
+
783
+ * if ``premises |= conclusion`` in ``matrix``, the lemma is
784
+ ``\\<forall> valuation. (des p1 \\<and> ... ) \\<longrightarrow> des c``;
785
+ * otherwise it is the countermodel statement
786
+ ``\\<exists> valuation. (des p1 \\<and> ... ) \\<and> \\<not> des c``.
787
+
788
+ The verdict and (when non-entailing) the countermodel witness come from
789
+ :func:`~unicode_logic_kit.semantics.matrix.matrix_entails`'s enumeration.
790
+
791
+ Same propositional fragment, rejection surface, and naming knobs as
792
+ :func:`to_isabelle_matrix`.
793
+ """
794
+ premises = list(premises)
795
+ for f in premises:
796
+ _check_propositional(f)
797
+ _check_propositional(conclusion)
798
+ _check_matrix_supports([*premises, conclusion], matrix)
799
+ names = _resolve_value_names(matrix, value_names)
800
+ conns = _resolve_conn_names(conn_names)
801
+
802
+ keys = _atom_keys(*premises, conclusion)
803
+ names_by_key = _assign_var_names(keys)
804
+ isa_vars = [names_by_key[k].lower() for k in keys]
805
+
806
+ valid = matrix_entails(premises, conclusion, matrix)
807
+ consts = _constant_names(matrix, names)
808
+ concl = f"{predicate_name} ({_isa_eval_matrix(conclusion, names_by_key, conns, consts)})"
809
+ if premises:
810
+ prem = " \\<and> ".join(
811
+ f"{predicate_name} ({_isa_eval_matrix(p, names_by_key, conns, consts)})"
812
+ for p in premises)
813
+ if valid:
814
+ body = f"({prem}) \\<longrightarrow> {concl}"
815
+ else:
816
+ body = f"({prem}) \\<and> \\<not> {concl}"
817
+ else:
818
+ body = concl if valid else f"\\<not> {concl}"
819
+ if keys:
820
+ binder = " ".join(isa_vars)
821
+ q = "\\<forall>" if valid else "\\<exists>"
822
+ lemma = f" \"{q}{binder}. {body}\""
823
+ if valid:
824
+ proof = _isa_forall_proof(isa_vars, predicate_name)
825
+ else:
826
+ witness_values = _matrix_entailment_countermodel(premises, conclusion, matrix, keys)
827
+ proof = _isa_exists_proof([names[v] for v in witness_values], predicate_name)
828
+ else:
829
+ lemma = f" \"{body}\""
830
+ proof = f" by (simp add: {predicate_name}_def)"
831
+
832
+ verdict = "ENTAILS" if valid else "DOES NOT ENTAIL (lemma is a countermodel)"
833
+ header = [
834
+ f"(* {matrix.name} many-valued entailment. *)",
835
+ f"(* Verdict: {verdict}. *)",
836
+ "(* Premises: " + ("; ".join(p.to_unicode_str() for p in premises) or "(none)") + " *)",
837
+ f"(* Conclusion: {conclusion.to_unicode_str()} *)",
838
+ ]
839
+ parts = _isa_matrix_prelude(name, matrix, names, conns, type_name, predicate_name, header)
840
+ parts.append(f"lemma {lemma_name}:")
841
+ parts.append(lemma)
842
+ parts.append(proof)
843
+ parts.append("")
844
+ parts.append("end")
845
+ return "\n".join(parts) + "\n"
846
+
847
+
848
+ # ===========================================================================
849
+ # (C) Legacy K3/LP-only entry points: thin delegation to the generic exports.
850
+ # ===========================================================================
851
+ #
852
+ # K3_MATRIX / LP_MATRIX (semantics.matrix) reproduce the strong-Kleene tables
853
+ # exactly (cross-checked in tests/test_matrix.py); these wrappers just pin the
854
+ # historical constant/function names so the emitted text keeps its old shape.
855
+
856
+ _K3LP_VALUE_NAMES: Dict[Value, str] = {1.0: "tT", 0.5: "tB", 0.0: "tF"}
857
+ _K3LP_CONN_NAMES = {
858
+ Not: "kneg", And: "kand", Or: "kor", Implies: "kimp", Iff: "kiff", Xor: "kxor",
859
+ }
860
+
861
+
862
+ def to_thf_k3lp(formula: Node, system: str = "K3") -> str:
863
+ """Emit a complete THF problem whose theorem-hood is K3/LP validity of ``formula``.
864
+
865
+ A thin wrapper around :func:`to_thf_matrix` with ``system``'s matrix
866
+ (``K3_MATRIX`` / ``LP_MATRIX`` from :mod:`unicode_logic_kit.semantics.matrix`)
867
+ and the historical constant/function names
868
+ (``tT``/``tB``/``tF``, ``kneg``/``kand``/``kor``/``kimp``/``kiff``/``kxor``).
869
+ See :func:`to_thf_matrix` for the full contract; ``system`` is ``"K3"``
870
+ (designates {T}) or ``"LP"`` (designates {T, B}).
871
+ """
872
+ _check_system(system)
873
+ return to_thf_matrix(formula, _K3LP_MATRICES[system],
874
+ value_names=_K3LP_VALUE_NAMES, conn_names=_K3LP_CONN_NAMES)
875
+
876
+
877
+ def to_thf_k3lp_entailment(premises, conclusion, system: str = "K3") -> str:
878
+ """Emit a THF problem whose theorem-hood is the K3/LP *entailment* of ``conclusion`` by ``premises``.
879
+
880
+ A thin wrapper around :func:`to_thf_matrix_entailment` with ``system``'s
881
+ matrix and the historical K3/LP constant/function names. This is where K3
882
+ and LP diverge: the explosion ``[P, ~P] |= Q`` is a **Theorem** under K3
883
+ (vacuously -- P and ~P are never jointly designated) but **not** under LP
884
+ (at P = B the premises are designated yet Q may be F), so LP is
885
+ paraconsistent. Mirrors :func:`unicode_logic_kit.semantics.manyvalued.entails`.
886
+ """
887
+ _check_system(system)
888
+ return to_thf_matrix_entailment(premises, conclusion, _K3LP_MATRICES[system],
889
+ value_names=_K3LP_VALUE_NAMES, conn_names=_K3LP_CONN_NAMES)
890
+
891
+
892
+ def to_isabelle_k3lp(formula: Node, system: str = "K3") -> str:
893
+ """Emit a complete, loadable Isabelle/HOL theory text for K3/LP validity.
894
+
895
+ A thin wrapper around :func:`to_isabelle_matrix` with ``system``'s matrix,
896
+ the historical K3/LP constant/function names, and the historical theory/
897
+ lemma names (``K3LP_Validity`` / ``k3lp_validity``). See
898
+ :func:`to_isabelle_matrix` for the full contract (verdict-dependent lemma
899
+ shape, proof-by-case-split/witness, honesty note).
900
+ """
901
+ _check_system(system)
902
+ return to_isabelle_matrix(formula, _K3LP_MATRICES[system],
903
+ name="K3LP_Validity", lemma_name="k3lp_validity",
904
+ value_names=_K3LP_VALUE_NAMES, conn_names=_K3LP_CONN_NAMES)
905
+
906
+
907
+ def to_isabelle_k3lp_entailment(premises, conclusion, system: str = "K3") -> str:
908
+ """Emit a loadable Isabelle/HOL theory whose lemma is the K3/LP entailment.
909
+
910
+ A thin wrapper around :func:`to_isabelle_matrix_entailment` with
911
+ ``system``'s matrix, the historical K3/LP constant/function names, and the
912
+ historical theory/lemma names (``K3LP_Entailment`` / ``k3lp_entailment``).
913
+ The K3/LP split shows up here -- explosion ``[P, ~P] |= Q`` is valid under
914
+ K3 (the ``\\<forall>``/``\\<longrightarrow>`` form) but invalid under LP
915
+ (the ``\\<exists>`` countermodel form).
916
+ """
917
+ _check_system(system)
918
+ return to_isabelle_matrix_entailment(
919
+ premises, conclusion, _K3LP_MATRICES[system],
920
+ name="K3LP_Entailment", lemma_name="k3lp_entailment",
921
+ value_names=_K3LP_VALUE_NAMES, conn_names=_K3LP_CONN_NAMES)