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.
- unicode_logic_kit/__init__.py +385 -0
- unicode_logic_kit/__main__.py +520 -0
- unicode_logic_kit/_deadline.py +219 -0
- unicode_logic_kit/ace/__init__.py +126 -0
- unicode_logic_kit/ace/_align.py +135 -0
- unicode_logic_kit/ace/chem_lexicon.py +128 -0
- unicode_logic_kit/ace/drs_reader.py +570 -0
- unicode_logic_kit/ace/mapping.py +666 -0
- unicode_logic_kit/ace/reverse_modal.py +138 -0
- unicode_logic_kit/ace/runner.py +551 -0
- unicode_logic_kit/ace/translate.py +452 -0
- unicode_logic_kit/ace/verbalize.py +1070 -0
- unicode_logic_kit/api.py +1284 -0
- unicode_logic_kit/atp/__init__.py +177 -0
- unicode_logic_kit/atp/_ascii_names.py +113 -0
- unicode_logic_kit/atp/_html.py +72 -0
- unicode_logic_kit/atp/_substructural_input.py +228 -0
- unicode_logic_kit/atp/_tff_problem.py +715 -0
- unicode_logic_kit/atp/_tptp_problem.py +1111 -0
- unicode_logic_kit/atp/_writer_support.py +289 -0
- unicode_logic_kit/atp/clingo_backend.py +1180 -0
- unicode_logic_kit/atp/cvc5_backend.py +1385 -0
- unicode_logic_kit/atp/eprover_backend.py +732 -0
- unicode_logic_kit/atp/finite_domain.py +1055 -0
- unicode_logic_kit/atp/fitch.py +1547 -0
- unicode_logic_kit/atp/fitch_search.py +551 -0
- unicode_logic_kit/atp/hets_backend.py +339 -0
- unicode_logic_kit/atp/hybrid_down.py +120 -0
- unicode_logic_kit/atp/incremental.py +250 -0
- unicode_logic_kit/atp/kripke_enum.py +741 -0
- unicode_logic_kit/atp/lambek.py +436 -0
- unicode_logic_kit/atp/leo3_backend.py +332 -0
- unicode_logic_kit/atp/linear.py +738 -0
- unicode_logic_kit/atp/lj.py +705 -0
- unicode_logic_kit/atp/logic_backends.py +566 -0
- unicode_logic_kit/atp/ltl_tableau.py +1084 -0
- unicode_logic_kit/atp/minizinc_backend.py +1402 -0
- unicode_logic_kit/atp/modal_tableau.py +1382 -0
- unicode_logic_kit/atp/nanocop_backend.py +410 -0
- unicode_logic_kit/atp/portfolio.py +489 -0
- unicode_logic_kit/atp/protocol.py +1803 -0
- unicode_logic_kit/atp/prover9_entailment.py +1153 -0
- unicode_logic_kit/atp/resolution.py +1376 -0
- unicode_logic_kit/atp/resolution_check.py +1114 -0
- unicode_logic_kit/atp/sequent.py +1050 -0
- unicode_logic_kit/atp/tableau.py +921 -0
- unicode_logic_kit/atp/tableau_check.py +543 -0
- unicode_logic_kit/atp/tptp_ncl.py +811 -0
- unicode_logic_kit/atp/tptp_tff.py +1546 -0
- unicode_logic_kit/atp/tstp.py +1333 -0
- unicode_logic_kit/atp/tstp_check.py +1096 -0
- unicode_logic_kit/atp/twee_backend.py +236 -0
- unicode_logic_kit/atp/twee_check.py +711 -0
- unicode_logic_kit/atp/twee_entailment.py +953 -0
- unicode_logic_kit/atp/vampire_entailment.py +540 -0
- unicode_logic_kit/atp/z3_arith.py +470 -0
- unicode_logic_kit/atp/z3_equivalence.py +36 -0
- unicode_logic_kit/atp/z3_fuzzy.py +362 -0
- unicode_logic_kit/atp/z3_input.py +500 -0
- unicode_logic_kit/atp/z3_models.py +208 -0
- unicode_logic_kit/chem/__init__.py +88 -0
- unicode_logic_kit/chem/_naming.py +284 -0
- unicode_logic_kit/chem/cache.py +185 -0
- unicode_logic_kit/chem/interop.py +244 -0
- unicode_logic_kit/chem/mol.py +525 -0
- unicode_logic_kit/chem/signature.py +112 -0
- unicode_logic_kit/comorphism.py +497 -0
- unicode_logic_kit/dl/__init__.py +384 -0
- unicode_logic_kit/dl/classification.py +227 -0
- unicode_logic_kit/dl/concepts.py +632 -0
- unicode_logic_kit/dl/datatypes.py +818 -0
- unicode_logic_kit/dl/owl_functional.py +2433 -0
- unicode_logic_kit/dl/owl_manchester.py +1637 -0
- unicode_logic_kit/dl/owl_reasoner.py +790 -0
- unicode_logic_kit/dl/parser.py +391 -0
- unicode_logic_kit/dl/tableau.py +4048 -0
- unicode_logic_kit/dl/translate.py +2704 -0
- unicode_logic_kit/drt/__init__.py +94 -0
- unicode_logic_kit/drt/export.py +179 -0
- unicode_logic_kit/drt/nodes.py +506 -0
- unicode_logic_kit/drt/parser.py +965 -0
- unicode_logic_kit/drt/resolve.py +195 -0
- unicode_logic_kit/drt/reverse.py +175 -0
- unicode_logic_kit/eval/__init__.py +106 -0
- unicode_logic_kit/eval/batch.py +382 -0
- unicode_logic_kit/eval/canonical.py +663 -0
- unicode_logic_kit/eval/chem_batch.py +606 -0
- unicode_logic_kit/eval/converses.py +200 -0
- unicode_logic_kit/eval/datasets/__init__.py +136 -0
- unicode_logic_kit/eval/datasets/_base.py +263 -0
- unicode_logic_kit/eval/datasets/_proofwriter_proof.py +422 -0
- unicode_logic_kit/eval/datasets/c3po.py +678 -0
- unicode_logic_kit/eval/datasets/folio.py +158 -0
- unicode_logic_kit/eval/datasets/fracas.py +418 -0
- unicode_logic_kit/eval/datasets/groves.py +191 -0
- unicode_logic_kit/eval/datasets/logicbench.py +467 -0
- unicode_logic_kit/eval/datasets/logicnli.py +303 -0
- unicode_logic_kit/eval/datasets/malls.py +133 -0
- unicode_logic_kit/eval/datasets/pfolio.py +594 -0
- unicode_logic_kit/eval/datasets/pmb.py +242 -0
- unicode_logic_kit/eval/datasets/prontoqa.py +611 -0
- unicode_logic_kit/eval/datasets/proofwriter.py +1431 -0
- unicode_logic_kit/eval/datasets/proverqa.py +674 -0
- unicode_logic_kit/eval/datasets/willow.py +478 -0
- unicode_logic_kit/eval/equivalence.py +466 -0
- unicode_logic_kit/eval/exercise_gen.py +533 -0
- unicode_logic_kit/eval/explain.py +791 -0
- unicode_logic_kit/eval/generality.py +750 -0
- unicode_logic_kit/eval/metric_hf.py +458 -0
- unicode_logic_kit/eval/predicate_match.py +343 -0
- unicode_logic_kit/eval/theory_check.py +1170 -0
- unicode_logic_kit/eval/validate.py +306 -0
- unicode_logic_kit/fol/__init__.py +177 -0
- unicode_logic_kit/fol/_atom_keys.py +510 -0
- unicode_logic_kit/fol/_fol_nodes.py +3586 -0
- unicode_logic_kit/fol/_free_parameters.py +105 -0
- unicode_logic_kit/fol/_ho_nodes.py +448 -0
- unicode_logic_kit/fol/_hybrid_nodes.py +308 -0
- unicode_logic_kit/fol/_identifiers.py +1091 -0
- unicode_logic_kit/fol/_lambek_nodes.py +112 -0
- unicode_logic_kit/fol/_linear_nodes.py +352 -0
- unicode_logic_kit/fol/_modal_nodes.py +1467 -0
- unicode_logic_kit/fol/_msfl_nodes.py +2196 -0
- unicode_logic_kit/fol/_numeral_symbols.py +231 -0
- unicode_logic_kit/fol/_so_nodes.py +200 -0
- unicode_logic_kit/fol/_symbol_names.py +81 -0
- unicode_logic_kit/fol/_team_nodes.py +181 -0
- unicode_logic_kit/fol/_tptp_symbols.py +551 -0
- unicode_logic_kit/fol/_truth_constants.py +117 -0
- unicode_logic_kit/fol/casl_export.py +1135 -0
- unicode_logic_kit/fol/casl_import.py +929 -0
- unicode_logic_kit/fol/derivation.py +367 -0
- unicode_logic_kit/fol/dialect_detect.py +70 -0
- unicode_logic_kit/fol/dialect_repair.py +537 -0
- unicode_logic_kit/fol/frames.py +637 -0
- unicode_logic_kit/fol/grammars/terminals.lark +31 -0
- unicode_logic_kit/fol/lambda_tools.py +297 -0
- unicode_logic_kit/fol/latex_input.py +429 -0
- unicode_logic_kit/fol/modal_translation.py +944 -0
- unicode_logic_kit/fol/msflparser.py +1033 -0
- unicode_logic_kit/fol/naming.py +422 -0
- unicode_logic_kit/fol/nodes.py +241 -0
- unicode_logic_kit/fol/normalforms.py +492 -0
- unicode_logic_kit/fol/pal.py +287 -0
- unicode_logic_kit/fol/prolog_export.py +566 -0
- unicode_logic_kit/fol/prolog_input.py +505 -0
- unicode_logic_kit/fol/prover9_input.py +1325 -0
- unicode_logic_kit/fol/qml.py +1760 -0
- unicode_logic_kit/fol/qmltp_input.py +525 -0
- unicode_logic_kit/fol/sanitize.py +221 -0
- unicode_logic_kit/fol/serialize.py +79 -0
- unicode_logic_kit/fol/signature.py +1290 -0
- unicode_logic_kit/fol/simplify_check.py +544 -0
- unicode_logic_kit/fol/spans.py +594 -0
- unicode_logic_kit/fol/tptp_input.py +1503 -0
- unicode_logic_kit/fol/tptp_repair.py +941 -0
- unicode_logic_kit/fol/unification.py +157 -0
- unicode_logic_kit/fol/verbalize.py +263 -0
- unicode_logic_kit/hets/__init__.py +163 -0
- unicode_logic_kit/hets/bridge.py +142 -0
- unicode_logic_kit/hets/client.py +748 -0
- unicode_logic_kit/hets/docker.py +420 -0
- unicode_logic_kit/hets/dol.py +712 -0
- unicode_logic_kit/hets/haskell_json.py +355 -0
- unicode_logic_kit/hets/owl_backend.py +794 -0
- unicode_logic_kit/hets/owl_cli.py +598 -0
- unicode_logic_kit/hets/symbols.py +512 -0
- unicode_logic_kit/hol/__init__.py +140 -0
- unicode_logic_kit/hol/_ho_common.py +323 -0
- unicode_logic_kit/hol/_isabelle_binders.py +125 -0
- unicode_logic_kit/hol/classical.py +812 -0
- unicode_logic_kit/hol/deepshallow/__init__.py +45 -0
- unicode_logic_kit/hol/deepshallow/_common.py +177 -0
- unicode_logic_kit/hol/deepshallow/conditional.py +225 -0
- unicode_logic_kit/hol/deepshallow/intuitionistic.py +181 -0
- unicode_logic_kit/hol/deepshallow/modal.py +217 -0
- unicode_logic_kit/hol/deepshallow/qml.py +406 -0
- unicode_logic_kit/hol/deepshallow/relevant.py +206 -0
- unicode_logic_kit/hol/free.py +753 -0
- unicode_logic_kit/hol/goedel.py +336 -0
- unicode_logic_kit/hol/ho_modal.py +1743 -0
- unicode_logic_kit/hol/intuitionistic.py +403 -0
- unicode_logic_kit/hol/isabelle_conditional.py +593 -0
- unicode_logic_kit/hol/isabelle_modal.py +1908 -0
- unicode_logic_kit/hol/isabelle_relevant.py +412 -0
- unicode_logic_kit/hol/isabelle_runner.py +1147 -0
- unicode_logic_kit/hol/isabelle_substructural.py +884 -0
- unicode_logic_kit/hol/lean.py +1018 -0
- unicode_logic_kit/hol/manyvalued.py +921 -0
- unicode_logic_kit/hol/secondorder.py +687 -0
- unicode_logic_kit/hol/thf_modal.py +941 -0
- unicode_logic_kit/hol/thirdorder.py +397 -0
- unicode_logic_kit/ilp/__init__.py +89 -0
- unicode_logic_kit/ilp/readback.py +389 -0
- unicode_logic_kit/ilp/separation.py +153 -0
- unicode_logic_kit/ilp/task.py +730 -0
- unicode_logic_kit/logic.py +163 -0
- unicode_logic_kit/mcp/__init__.py +28 -0
- unicode_logic_kit/mcp/__main__.py +5 -0
- unicode_logic_kit/mcp/chem_tools.py +1031 -0
- unicode_logic_kit/mcp/server.py +2453 -0
- unicode_logic_kit/mcp/syntax_spec.py +681 -0
- unicode_logic_kit/prob/__init__.py +53 -0
- unicode_logic_kit/prob/_bdd.py +225 -0
- unicode_logic_kit/prob/_column_gen.py +668 -0
- unicode_logic_kit/prob/distribution.py +686 -0
- unicode_logic_kit/prob/nilsson.py +470 -0
- unicode_logic_kit/py.typed +0 -0
- unicode_logic_kit/semantics/__init__.py +137 -0
- unicode_logic_kit/semantics/_modal_reject.py +156 -0
- unicode_logic_kit/semantics/action_models.py +466 -0
- unicode_logic_kit/semantics/asp_models.py +1200 -0
- unicode_logic_kit/semantics/conditional.py +580 -0
- unicode_logic_kit/semantics/dynamic_epistemic.py +95 -0
- unicode_logic_kit/semantics/free_logic.py +913 -0
- unicode_logic_kit/semantics/fuzzy.py +384 -0
- unicode_logic_kit/semantics/fuzzy_kripke.py +442 -0
- unicode_logic_kit/semantics/intuitionistic.py +581 -0
- unicode_logic_kit/semantics/kripke.py +1139 -0
- unicode_logic_kit/semantics/manyvalued.py +580 -0
- unicode_logic_kit/semantics/matrix.py +342 -0
- unicode_logic_kit/semantics/model_eval.py +1135 -0
- unicode_logic_kit/semantics/modelfinder.py +1036 -0
- unicode_logic_kit/semantics/nonmonotonic.py +372 -0
- unicode_logic_kit/semantics/relevant.py +331 -0
- unicode_logic_kit/semantics/secondorder.py +657 -0
- unicode_logic_kit/semantics/structures.py +352 -0
- unicode_logic_kit/semantics/tarski.py +975 -0
- unicode_logic_kit/semantics/team.py +315 -0
- unicode_logic_kit/semantics/team_translation.py +416 -0
- unicode_logic_kit/semantics/thirdorder.py +358 -0
- unicode_logic_kit/semantics/tnorm.py +85 -0
- unicode_logic_kit/semantics/truthtable.py +201 -0
- unicode_logic_kit-0.31.0.dist-info/METADATA +333 -0
- unicode_logic_kit-0.31.0.dist-info/RECORD +237 -0
- unicode_logic_kit-0.31.0.dist-info/WHEEL +4 -0
- unicode_logic_kit-0.31.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
r"""Classical third-order logic → HOL/THF (predicates of properties, mapped natively).
|
|
2
|
+
|
|
3
|
+
Second-order logic quantifies over predicates; :mod:`unicode_logic_kit.hol.secondorder`
|
|
4
|
+
exports that, and a HOL prover takes it directly because a predicate variable is
|
|
5
|
+
just a variable at a function type. Third-order logic is the next step and it is
|
|
6
|
+
a step in a different direction: a predicate whose ARGUMENT is a predicate —
|
|
7
|
+
``Positive(G)``, ``Essence(G, x)``, ``Positive(λx. ¬G(x))``. HOL takes that
|
|
8
|
+
directly too, for the same reason, one type higher::
|
|
9
|
+
|
|
10
|
+
x : i an individual
|
|
11
|
+
G : i => bool a property
|
|
12
|
+
Positive : (i => bool) => bool a predicate OF properties
|
|
13
|
+
Essence : (i => bool) => i => bool a relation between a property and an individual
|
|
14
|
+
|
|
15
|
+
so the embedding is again a translation and not a simulation: an object
|
|
16
|
+
quantifier is a HOL ``∀`` at type ``i``, a property quantifier a HOL ``∀`` at
|
|
17
|
+
``i => bool``, and an application is an application.
|
|
18
|
+
|
|
19
|
+
**Where the types come from.** They are not written in the surface syntax. What
|
|
20
|
+
each argument slot holds is inferred from the formulas TOGETHER by
|
|
21
|
+
:func:`unicode_logic_kit.fol.nodes.analyse_signatures` — ``Positive(G)`` in one
|
|
22
|
+
formula and ``G(x)`` in another jointly say that ``Positive`` takes a property
|
|
23
|
+
of arity 1 — with bound predicate variables renamed apart first so two local
|
|
24
|
+
bindings named ``P`` are never mistaken for one symbol. A property slot that
|
|
25
|
+
nothing in the input constrains defaults to arity 1 and is reported, not
|
|
26
|
+
silently assumed.
|
|
27
|
+
|
|
28
|
+
**Standard semantics, as in the second-order module.** The quantifiers range
|
|
29
|
+
over the prover's full function space, i.e. standard (full) semantics. Validity
|
|
30
|
+
at these orders is not semi-decidable, so an external prover may fail on a valid
|
|
31
|
+
conjecture; the kit emits the problem and does not run one. For Henkin
|
|
32
|
+
(general-models) semantics, add comprehension axioms yourself — this module does
|
|
33
|
+
not do it for you.
|
|
34
|
+
|
|
35
|
+
**Equality** follows the kit's HOL convention: ``=`` / ``≠`` are the
|
|
36
|
+
uninterpreted relations ``feq`` / ``fneq``, not primitive HOL identity.
|
|
37
|
+
|
|
38
|
+
**Constants and free variables.** A constant is a particular individual that its name
|
|
39
|
+
stands for, and a free variable is a parameter of the problem: one unknown element, the
|
|
40
|
+
same in every formula (see :mod:`unicode_logic_kit.fol._free_parameters`). The two are
|
|
41
|
+
different symbols even when they are spelled alike, so ``P(x)`` with a free ``x`` and
|
|
42
|
+
``P('x')`` are two statements, and each is declared under a name of its own: the second
|
|
43
|
+
``x`` is written ``x_2``. A constant is never looked up among the bound variables, so no
|
|
44
|
+
binder, of any name, captures it: ``∀x P(x, 'x')`` is written with the bound variable
|
|
45
|
+
``X_V`` and the constant ``x`` in THF and with ``x_2`` and ``x`` in Isabelle. A constant of
|
|
46
|
+
any name has a legal identifier in the target: a name that is an Isabelle identifier is
|
|
47
|
+
written as it is, and any other (``John Doe``, ``G-910``, ``θ``) under the kit's ASCII
|
|
48
|
+
stem of it; in THF every name goes through that stem. The identifiers are made unique over
|
|
49
|
+
every kind of symbol of the problem, so two names that share a stem (``a b`` and ``a_b``)
|
|
50
|
+
are still two symbols.
|
|
51
|
+
|
|
52
|
+
Public API: :func:`to_thf_to`, :func:`to_isabelle_to`.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from typing import Dict, Optional, Sequence
|
|
56
|
+
|
|
57
|
+
from ..fol.nodes import (
|
|
58
|
+
Node, Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
|
|
59
|
+
SecondOrderQuantifier, PredicateTerm,
|
|
60
|
+
Variable, Constant, Function, LambdaVar, Lambda,
|
|
61
|
+
analyse_signatures,
|
|
62
|
+
)
|
|
63
|
+
from ..fol._ho_nodes import INDIVIDUAL
|
|
64
|
+
from ..fol._truth_constants import truth_value
|
|
65
|
+
from ._ho_common import (
|
|
66
|
+
UnsupportedHigherOrderNode, EQUALITY, FREE_VARIABLE, CONSTANT, ISABELLE_BUILT_IN,
|
|
67
|
+
peel_lambdas, rename_apart, bound_pred_names, atom_predicates,
|
|
68
|
+
function_symbols, individual_symbols, ThfNames, IsabelleNames, bound_token,
|
|
69
|
+
)
|
|
70
|
+
from ._isabelle_binders import (
|
|
71
|
+
PREDICATE, VARIABLE, BinderScope, binder_tokens, collect_binders, declared_names,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
_ALL = r"\<forall>"
|
|
75
|
+
_EX = r"\<exists>"
|
|
76
|
+
_AND = r"\<and>"
|
|
77
|
+
_OR = r"\<or>"
|
|
78
|
+
_IMP = r"\<longrightarrow>"
|
|
79
|
+
_IFF = r"\<longleftrightarrow>"
|
|
80
|
+
_NOT = r"\<not>"
|
|
81
|
+
_LAM = r"\<lambda>"
|
|
82
|
+
_FUN = r"\<Rightarrow>"
|
|
83
|
+
_NEQ = r"\<noteq>"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _isa_prop_type(arity: int) -> str:
|
|
87
|
+
"""Isabelle type of a property of ``arity`` arguments: ``i => … => bool``."""
|
|
88
|
+
return "".join(f"i {_FUN} " for _ in range(arity)) + "bool"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _isa_slot_type(kind) -> str:
|
|
92
|
+
"""Isabelle type of one argument slot: ``i``, or a parenthesised property type."""
|
|
93
|
+
return "i" if kind == INDIVIDUAL else f"({_isa_prop_type(kind[1])})"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _thf_prop_type(arity: int) -> str:
|
|
97
|
+
"""THF type of a property of ``arity`` arguments: ``$i > … > $o``."""
|
|
98
|
+
return " > ".join(["$i"] * arity + ["$o"])
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _thf_slot_type(kind) -> str:
|
|
102
|
+
"""THF type of one argument slot."""
|
|
103
|
+
return "$i" if kind == INDIVIDUAL else f"( {_thf_prop_type(kind[1])} )"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# --------------------------------------------------------------------------
|
|
107
|
+
# Isabelle
|
|
108
|
+
# --------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
_BINARY_ISA = {And: _AND, Or: _OR, Implies: _IMP, Iff: _IFF}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _isa_arg(node: Node, arity: Dict[str, int], display: Dict[str, str],
|
|
114
|
+
scope: Optional[BinderScope] = None,
|
|
115
|
+
symbols: Optional[IsabelleNames] = None) -> str:
|
|
116
|
+
"""Render a node in ARGUMENT position — an individual term or a property.
|
|
117
|
+
|
|
118
|
+
``scope`` holds the binders that enclose the node and the names they are printed under: a
|
|
119
|
+
variable or a lambda variable bound by one of them is printed under its binder's name. A
|
|
120
|
+
free variable and a constant are not bound by any binder, whatever their spelling: each is
|
|
121
|
+
printed under the name ``symbols`` gives it, and the two have two names. Without a ``scope``
|
|
122
|
+
every binder is printed under its own name.
|
|
123
|
+
"""
|
|
124
|
+
if scope is None:
|
|
125
|
+
scope = BinderScope({})
|
|
126
|
+
if symbols is None:
|
|
127
|
+
symbols = IsabelleNames(ISABELLE_BUILT_IN)
|
|
128
|
+
if isinstance(node, (Variable, LambdaVar)):
|
|
129
|
+
# a binder's token is never empty, so "" means: no binder of this name encloses the node
|
|
130
|
+
return scope.token(VARIABLE, node.name, default="") or symbols.symbol(FREE_VARIABLE, node.name)
|
|
131
|
+
if isinstance(node, Constant):
|
|
132
|
+
return symbols.symbol(CONSTANT, node.name)
|
|
133
|
+
if isinstance(node, PredicateTerm):
|
|
134
|
+
return display.get(node.name) or symbols.symbol("predicate", node.name)
|
|
135
|
+
if isinstance(node, Lambda):
|
|
136
|
+
names, body = peel_lambdas(node)
|
|
137
|
+
binders = []
|
|
138
|
+
for name in names:
|
|
139
|
+
token, scope = scope.enter(VARIABLE, name)
|
|
140
|
+
binders.append(f"{_LAM}{token}::i.")
|
|
141
|
+
return f"({' '.join(binders)} {_isa(body, arity, display, scope, symbols)})"
|
|
142
|
+
if isinstance(node, Function):
|
|
143
|
+
head = symbols.symbol("function", node.name)
|
|
144
|
+
args = " ".join(_isa_arg(a, arity, display, scope, symbols) for a in node.args)
|
|
145
|
+
return f"({head} {args})" if args else head
|
|
146
|
+
raise UnsupportedHigherOrderNode(
|
|
147
|
+
f"thirdorder: {type(node).__name__} cannot stand in argument position; an "
|
|
148
|
+
f"argument is an individual term, a predicate name, or a λ-abstraction.")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _isa(node: Node, arity: Dict[str, int], display: Dict[str, str],
|
|
152
|
+
scope: Optional[BinderScope] = None,
|
|
153
|
+
symbols: Optional[IsabelleNames] = None) -> str:
|
|
154
|
+
"""Render ``node`` as an Isabelle/HOL formula.
|
|
155
|
+
|
|
156
|
+
``display`` gives each bound predicate variable the name it is printed under, ``scope`` each
|
|
157
|
+
enclosing object binder (see :mod:`unicode_logic_kit.hol._isabelle_binders`); without a
|
|
158
|
+
``scope`` every binder is printed under its own name. ``symbols`` names the free symbols
|
|
159
|
+
(predicates, functions, free variables, constants) and is the one the declarations were
|
|
160
|
+
written under.
|
|
161
|
+
"""
|
|
162
|
+
if scope is None:
|
|
163
|
+
scope = BinderScope({})
|
|
164
|
+
if symbols is None:
|
|
165
|
+
symbols = IsabelleNames(ISABELLE_BUILT_IN)
|
|
166
|
+
if isinstance(node, Not):
|
|
167
|
+
return f"({_NOT} {_isa(node.formula, arity, display, scope, symbols)})"
|
|
168
|
+
glyph = _BINARY_ISA.get(type(node))
|
|
169
|
+
if glyph is not None:
|
|
170
|
+
left = _isa(node.left, arity, display, scope, symbols)
|
|
171
|
+
right = _isa(node.right, arity, display, scope, symbols)
|
|
172
|
+
return f"({left} {glyph} {right})"
|
|
173
|
+
if isinstance(node, Xor):
|
|
174
|
+
left = _isa(node.left, arity, display, scope, symbols)
|
|
175
|
+
right = _isa(node.right, arity, display, scope, symbols)
|
|
176
|
+
return f"({left} {_NEQ} {right})"
|
|
177
|
+
if isinstance(node, Quantifier):
|
|
178
|
+
binder = _ALL if node.type == "∀" else _EX
|
|
179
|
+
token, inner = scope.enter(VARIABLE, node.variable.name)
|
|
180
|
+
body = _isa(node.formula, arity, display, inner, symbols)
|
|
181
|
+
return f"({binder}{token}::i. {body})"
|
|
182
|
+
if isinstance(node, SecondOrderQuantifier):
|
|
183
|
+
binder = _ALL if node.type == "∀" else _EX
|
|
184
|
+
k = arity.get(node.predicate, node.arity)
|
|
185
|
+
name = display.get(node.predicate, node.predicate)
|
|
186
|
+
body = _isa(node.formula, arity, display, scope, symbols)
|
|
187
|
+
return f"({binder}{name}::{_isa_prop_type(k)}. {body})"
|
|
188
|
+
if isinstance(node, Atom):
|
|
189
|
+
if truth_value(node) is not None:
|
|
190
|
+
return "True" if truth_value(node) else "False"
|
|
191
|
+
if node.predicate in EQUALITY:
|
|
192
|
+
name = EQUALITY[node.predicate]
|
|
193
|
+
else:
|
|
194
|
+
name = display.get(node.predicate) or symbols.symbol("predicate", node.predicate)
|
|
195
|
+
if not node.args:
|
|
196
|
+
return name
|
|
197
|
+
args = " ".join(_isa_arg(a, arity, display, scope, symbols) for a in node.args)
|
|
198
|
+
return f"({name} {args})"
|
|
199
|
+
raise UnsupportedHigherOrderNode(
|
|
200
|
+
f"thirdorder: no reading for {type(node).__name__}. This export covers "
|
|
201
|
+
f"CLASSICAL third-order syntax; modal operators belong to "
|
|
202
|
+
f"hol.ho_modal.to_isabelle_ho_modal.")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def to_isabelle_to(formula: Node, name: str = "TO_Goal",
|
|
206
|
+
assumptions: Sequence[Node] = (),
|
|
207
|
+
proof: str = None) -> str:
|
|
208
|
+
"""Emit a self-contained Isabelle/HOL theory for a classical third-order formula.
|
|
209
|
+
|
|
210
|
+
``assumptions`` are asserted with ``axiomatization`` and are analysed
|
|
211
|
+
together with ``formula``, so a symbol whose argument types only the
|
|
212
|
+
assumptions determine is still typed correctly. Without a ``proof`` the goal
|
|
213
|
+
is left ``oops`` — the kit states the problem; it does not invent a proof.
|
|
214
|
+
|
|
215
|
+
``name`` becomes the theory name and must be a legal Isabelle identifier.
|
|
216
|
+
"""
|
|
217
|
+
formulas = list(assumptions) + [formula]
|
|
218
|
+
apart, display = rename_apart(formulas)
|
|
219
|
+
signatures = analyse_signatures(apart)
|
|
220
|
+
bound = set()
|
|
221
|
+
for f in apart:
|
|
222
|
+
bound |= bound_pred_names(f)
|
|
223
|
+
|
|
224
|
+
lines = [
|
|
225
|
+
"(* Classical third-order logic -> HOL (predicates of properties are native). *)",
|
|
226
|
+
"(* Standard (full) semantics; validity at this order is NOT semi-decidable, *)",
|
|
227
|
+
"(* so a sound prover may fail to close a valid goal. *)",
|
|
228
|
+
f"theory {name}",
|
|
229
|
+
" imports Main",
|
|
230
|
+
"begin",
|
|
231
|
+
"",
|
|
232
|
+
"typedecl i \\<comment> \\<open>individuals\\<close>",
|
|
233
|
+
"",
|
|
234
|
+
]
|
|
235
|
+
predicates = [pred for pred in sorted(signatures.slots)
|
|
236
|
+
if not (pred in bound or pred in EQUALITY)]
|
|
237
|
+
individuals = individual_symbols(apart)
|
|
238
|
+
aliases = sorted({EQUALITY[p] for f in apart for p in atom_predicates(f) if p in EQUALITY})
|
|
239
|
+
functions = sorted(function_symbols(apart).items())
|
|
240
|
+
# One pool of identifiers for every symbol the theory declares: a constant is never
|
|
241
|
+
# declared under the name of a free variable, a predicate or a function, nor of an alias
|
|
242
|
+
# of the embedding, and a name that is no Isabelle identifier gets one.
|
|
243
|
+
symbols = IsabelleNames(ISABELLE_BUILT_IN | set(aliases))
|
|
244
|
+
symbols.claim([("predicate", pred) for pred in predicates]
|
|
245
|
+
+ [("function", symbol) for symbol, _ in functions] + individuals)
|
|
246
|
+
for pred in predicates:
|
|
247
|
+
arrow = "".join(f"{_isa_slot_type(k)} {_FUN} " for k in signatures.slots[pred])
|
|
248
|
+
lines.append(f'consts {symbols.symbol("predicate", pred)} :: "{arrow}bool"')
|
|
249
|
+
for kind, name in individuals:
|
|
250
|
+
lines.append(f'consts {symbols.symbol(kind, name)} :: "i"')
|
|
251
|
+
for symbol in aliases:
|
|
252
|
+
lines.append(f'consts {symbol} :: "i {_FUN} i {_FUN} bool"')
|
|
253
|
+
for symbol, k in functions:
|
|
254
|
+
arrow = "".join(f"i {_FUN} " for _ in range(k))
|
|
255
|
+
lines.append(f'consts {symbols.symbol("function", symbol)} :: "{arrow}i"')
|
|
256
|
+
lines.append("")
|
|
257
|
+
# A binder shadows a constant of its own spelling inside its scope: every binder (object
|
|
258
|
+
# quantifier, lambda parameter, bound predicate variable) is printed under a name that no
|
|
259
|
+
# declared symbol has. ``display`` gives each renamed-apart predicate variable that name.
|
|
260
|
+
tokens = binder_tokens(collect_binders(formulas), declared_names(lines))
|
|
261
|
+
display = {fresh: tokens[(PREDICATE, original)] for fresh, original in display.items()}
|
|
262
|
+
scope = BinderScope(tokens)
|
|
263
|
+
if signatures.defaulted:
|
|
264
|
+
pairs = ", ".join(f"{p}[{i}]" for p, i in sorted(signatures.defaulted))
|
|
265
|
+
lines.append(f"\\<comment> \\<open>arity defaulted to 1 (nothing in the "
|
|
266
|
+
f"input fixes it): {pairs}\\<close>")
|
|
267
|
+
lines.append("")
|
|
268
|
+
for index, assumption in enumerate(apart[:-1], start=1):
|
|
269
|
+
lines.append(f'axiomatization where assumption{index}: '
|
|
270
|
+
f'"{_isa(assumption, signatures.arity, display, scope, symbols)}"')
|
|
271
|
+
if len(apart) > 1:
|
|
272
|
+
lines.append("")
|
|
273
|
+
lines.append(f'lemma "{_isa(apart[-1], signatures.arity, display, scope, symbols)}"')
|
|
274
|
+
lines.append(f" {proof}" if proof else
|
|
275
|
+
" oops \\<comment> \\<open>try: by auto / by blast / sledgehammer\\<close>")
|
|
276
|
+
lines.append("")
|
|
277
|
+
lines.append("end")
|
|
278
|
+
return "\n".join(lines) + "\n"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# --------------------------------------------------------------------------
|
|
282
|
+
# THF
|
|
283
|
+
# --------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
_BINARY_THF = {And: "&", Or: "|", Implies: "=>", Iff: "<=>", Xor: "<~>"}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _thf_arg(node: Node, upper: Dict[str, str], display: Dict[str, str],
|
|
289
|
+
names: ThfNames) -> str:
|
|
290
|
+
"""Render a node in ARGUMENT position in THF.
|
|
291
|
+
|
|
292
|
+
``upper`` maps each source name bound in scope to its THF variable token;
|
|
293
|
+
a variable that is not in it is free and spelled through ``names``. A constant
|
|
294
|
+
is never looked up in ``upper``: no binder captures it, whatever its spelling.
|
|
295
|
+
"""
|
|
296
|
+
if isinstance(node, (Variable, LambdaVar)):
|
|
297
|
+
return upper.get(node.name) or names.functor(FREE_VARIABLE, node.name)
|
|
298
|
+
if isinstance(node, Constant):
|
|
299
|
+
return names.functor(CONSTANT, node.name)
|
|
300
|
+
if isinstance(node, PredicateTerm):
|
|
301
|
+
return upper.get(node.name) or names.functor("predicate", node.name)
|
|
302
|
+
if isinstance(node, Lambda):
|
|
303
|
+
params, body = peel_lambdas(node)
|
|
304
|
+
fresh = dict(upper)
|
|
305
|
+
for n in params:
|
|
306
|
+
fresh[n] = bound_token(n, "_V", fresh)
|
|
307
|
+
binders = ", ".join(f"{fresh[n]}: $i" for n in params)
|
|
308
|
+
return f"( ^ [{binders}] : {_thf(body, fresh, display, names)} )"
|
|
309
|
+
if isinstance(node, Function):
|
|
310
|
+
functor = names.functor("function", node.name)
|
|
311
|
+
args = " @ ".join(_thf_arg(a, upper, display, names) for a in node.args)
|
|
312
|
+
return f"( {functor} @ {args} )" if node.args else functor
|
|
313
|
+
raise UnsupportedHigherOrderNode(
|
|
314
|
+
f"thirdorder: {type(node).__name__} cannot stand in argument position.")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _thf(node: Node, upper: Dict[str, str], display: Dict[str, str],
|
|
318
|
+
names: ThfNames) -> str:
|
|
319
|
+
"""Render ``node`` as a THF (TH0) formula."""
|
|
320
|
+
if isinstance(node, Not):
|
|
321
|
+
return f"( ~ {_thf(node.formula, upper, display, names)} )"
|
|
322
|
+
glyph = _BINARY_THF.get(type(node))
|
|
323
|
+
if glyph is not None:
|
|
324
|
+
return (f"( {_thf(node.left, upper, display, names)} {glyph} "
|
|
325
|
+
f"{_thf(node.right, upper, display, names)} )")
|
|
326
|
+
if isinstance(node, Quantifier):
|
|
327
|
+
var = bound_token(node.variable.name, "_V", upper)
|
|
328
|
+
fresh = dict(upper, **{node.variable.name: var})
|
|
329
|
+
quant = "!" if node.type == "∀" else "?"
|
|
330
|
+
return f"( {quant} [{var}: $i] : {_thf(node.formula, fresh, display, names)} )"
|
|
331
|
+
if isinstance(node, SecondOrderQuantifier):
|
|
332
|
+
var = bound_token(display.get(node.predicate, node.predicate), "_P", upper)
|
|
333
|
+
fresh = dict(upper, **{node.predicate: var})
|
|
334
|
+
quant = "!" if node.type == "∀" else "?"
|
|
335
|
+
return (f"( {quant} [{var}: {_thf_prop_type(node.arity)}] : "
|
|
336
|
+
f"{_thf(node.formula, fresh, display, names)} )")
|
|
337
|
+
if isinstance(node, Atom):
|
|
338
|
+
if truth_value(node) is not None:
|
|
339
|
+
return "$true" if truth_value(node) else "$false"
|
|
340
|
+
if node.predicate in EQUALITY:
|
|
341
|
+
name = EQUALITY[node.predicate]
|
|
342
|
+
else:
|
|
343
|
+
name = upper.get(node.predicate) or names.functor("predicate", node.predicate)
|
|
344
|
+
if not node.args:
|
|
345
|
+
return name
|
|
346
|
+
args = " @ ".join(_thf_arg(a, upper, display, names) for a in node.args)
|
|
347
|
+
return f"( {name} @ {args} )"
|
|
348
|
+
raise UnsupportedHigherOrderNode(
|
|
349
|
+
f"thirdorder: no THF reading for {type(node).__name__}; this export "
|
|
350
|
+
f"covers classical third-order syntax.")
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def to_thf_to(formula: Node, assumptions: Sequence[Node] = (),
|
|
354
|
+
conjecture: bool = True) -> str:
|
|
355
|
+
"""Emit a THF (TH0) problem for a classical third-order formula.
|
|
356
|
+
|
|
357
|
+
``assumptions`` become ``axiom`` formulas and are typed together with the
|
|
358
|
+
goal. With ``conjecture=False`` the formula is emitted as an ``axiom``
|
|
359
|
+
instead — the form to hand a model finder when the question is
|
|
360
|
+
satisfiability rather than validity.
|
|
361
|
+
"""
|
|
362
|
+
formulas = list(assumptions) + [formula]
|
|
363
|
+
apart, display = rename_apart(formulas)
|
|
364
|
+
signatures = analyse_signatures(apart)
|
|
365
|
+
bound = set()
|
|
366
|
+
for f in apart:
|
|
367
|
+
bound |= bound_pred_names(f)
|
|
368
|
+
|
|
369
|
+
lines = [
|
|
370
|
+
"% Classical third-order logic -> THF (predicates of properties are native).",
|
|
371
|
+
"% Standard (full) semantics; validity at this order is NOT semi-decidable,",
|
|
372
|
+
"% so a sound prover may fail to close a valid goal.",
|
|
373
|
+
]
|
|
374
|
+
names = ThfNames(reserved=EQUALITY.values())
|
|
375
|
+
for pred in sorted(signatures.slots):
|
|
376
|
+
if pred in bound or pred in EQUALITY:
|
|
377
|
+
continue
|
|
378
|
+
parts = [_thf_slot_type(k) for k in signatures.slots[pred]]
|
|
379
|
+
thf_type = " > ".join(parts + ["$o"]) if parts else "$o"
|
|
380
|
+
functor = names.functor("predicate", pred)
|
|
381
|
+
lines.append(f"thf({functor}_type, type, ( {functor} : {thf_type} )).")
|
|
382
|
+
for kind, symbol in individual_symbols(apart):
|
|
383
|
+
functor = names.functor(kind, symbol)
|
|
384
|
+
lines.append(f"thf({functor}_type, type, ( {functor} : $i )).")
|
|
385
|
+
for symbol in sorted({EQUALITY[p] for f in apart for p in atom_predicates(f)
|
|
386
|
+
if p in EQUALITY}):
|
|
387
|
+
lines.append(f"thf({symbol}_type, type, ( {symbol} : $i > $i > $o )).")
|
|
388
|
+
for symbol, k in sorted(function_symbols(apart).items()):
|
|
389
|
+
functor = names.functor("function", symbol)
|
|
390
|
+
lines.append(f"thf({functor}_type, type, ( {functor} : "
|
|
391
|
+
f"{' > '.join(['$i'] * (k + 1))} )).")
|
|
392
|
+
for index, assumption in enumerate(apart[:-1], start=1):
|
|
393
|
+
lines.append(f"thf(assumption{index}, axiom, "
|
|
394
|
+
f"( {_thf(assumption, {}, display, names)} )).")
|
|
395
|
+
role = "conjecture" if conjecture else "axiom"
|
|
396
|
+
lines.append(f"thf(goal, {role}, ( {_thf(apart[-1], {}, display, names)} )).")
|
|
397
|
+
return "\n".join(lines) + "\n"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Inductive logic programming: structures in, a learning task out.
|
|
2
|
+
|
|
3
|
+
The other end of :mod:`unicode_logic_kit.fol.prolog_input`. That module reads a
|
|
4
|
+
learner's answer; this one writes the learner's question — the background
|
|
5
|
+
knowledge, examples and language bias an ILP system (Popper, Aleph, Metagol)
|
|
6
|
+
needs, built from the very same
|
|
7
|
+
:class:`~unicode_logic_kit.semantics.structures.FiniteStructure` objects the
|
|
8
|
+
kit's model checker evaluates against. The loop closes:
|
|
9
|
+
|
|
10
|
+
structure --IlpTask--> Prolog task --learner--> clause
|
|
11
|
+
--clause_to_formula--> kit formula --model checker--> verdicts
|
|
12
|
+
|
|
13
|
+
Why a module and not a script
|
|
14
|
+
-----------------------------
|
|
15
|
+
Because two encoding mistakes are easy to make, impossible to see in the
|
|
16
|
+
output, and both produce a hypothesis that scores **precision 1.00 and means
|
|
17
|
+
nothing**. Both were made while building this kit's own pre-trial, and both
|
|
18
|
+
are now structurally impossible here rather than written down as advice:
|
|
19
|
+
|
|
20
|
+
**1. Example-local individual names.** A structure names its individuals
|
|
21
|
+
locally — every molecule has a ``c1``. Emit those raw and the same constant
|
|
22
|
+
denotes a different thing in each example, so a learner can join *across*
|
|
23
|
+
examples. It will:
|
|
24
|
+
|
|
25
|
+
.. code-block:: prolog
|
|
26
|
+
|
|
27
|
+
amide(A) :- bSINGLE(A,B,C), n(D,B), bDOUBLE(D,E,C), bDOUBLE(A,C,E).
|
|
28
|
+
|
|
29
|
+
scored perfectly by hopping between molecules through a shared atom name.
|
|
30
|
+
:class:`IlpTask` prefixes every individual with its example and **refuses the
|
|
31
|
+
task** if two ``(example, individual)`` pairs would still collide — the check
|
|
32
|
+
is in the constructor, so a colliding task cannot be written at all.
|
|
33
|
+
|
|
34
|
+
**2. The example argument on every predicate.** Carry the example everywhere
|
|
35
|
+
(``c(M,X)``, ``bond(M,X,Y)``) and the learner can introduce a *second* example
|
|
36
|
+
variable and connect through it. The resulting clause is not a statement about
|
|
37
|
+
one structure, and cannot be translated into one. Here the example argument
|
|
38
|
+
exists on the membership predicate **alone** — the emitter has no way to put
|
|
39
|
+
it anywhere else — and :func:`clause_to_formula` refuses, on the way back, any
|
|
40
|
+
clause in which the example variable survives dropping the membership atoms.
|
|
41
|
+
|
|
42
|
+
A third guard falls out of the same reasoning: a **0-ary** background
|
|
43
|
+
predicate cannot be attached to an example at all, so it would hold globally
|
|
44
|
+
across every example. It is excluded from an inferred vocabulary (visibly, via
|
|
45
|
+
:attr:`IlpTask.excluded_predicates` and a comment in the emitted file) and
|
|
46
|
+
refused if named explicitly.
|
|
47
|
+
|
|
48
|
+
What this module does NOT do
|
|
49
|
+
----------------------------
|
|
50
|
+
It does not run a learner. Popper needs SWI-Prolog and, from v4, ``janus_swi``
|
|
51
|
+
— a dependency the kit does not take on for a file format. It writes the three
|
|
52
|
+
files Popper reads (``bk.pl``, ``exs.pl``, ``bias.pl``) via
|
|
53
|
+
:meth:`IlpTask.write`, and Aleph's genuinely different three-file layout
|
|
54
|
+
(``<stem>.b``/``.f``/``.n`` — ``modeh``/``modeb``/``determination`` mode
|
|
55
|
+
declarations rather than ``head_pred``/``body_pred``, bare-atom examples split
|
|
56
|
+
by label rather than one ``pos``/``neg``-wrapped file) via
|
|
57
|
+
:meth:`IlpTask.write_aleph`, and reads back the text either learner prints.
|
|
58
|
+
Everything in between is the learner's business.
|
|
59
|
+
|
|
60
|
+
It also does not decide whether your task is a *good* one. It does offer the
|
|
61
|
+
one check that has to happen first: :func:`check_separation` asks whether a
|
|
62
|
+
reference formula actually separates the two example sets under the kit's own
|
|
63
|
+
model checker. If it does not, the task is broken and no answer from any
|
|
64
|
+
learner would have meant anything.
|
|
65
|
+
|
|
66
|
+
>>> from unicode_logic_kit.semantics import FiniteStructure
|
|
67
|
+
>>> from unicode_logic_kit.ilp import IlpTask, Example
|
|
68
|
+
>>> yes = FiniteStructure(domain=("a1",), extensions={("p", 1): [("a1",)]})
|
|
69
|
+
>>> no = FiniteStructure(domain=("a1",), extensions={("p", 1): []})
|
|
70
|
+
>>> task = IlpTask("target", [Example("e1", yes, True),
|
|
71
|
+
... Example("e2", no, False)])
|
|
72
|
+
>>> print(task.examples_text())
|
|
73
|
+
pos(target(e1)).
|
|
74
|
+
neg(target(e2)).
|
|
75
|
+
<BLANKLINE>
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
from .task import (
|
|
79
|
+
Example, IlpTask, IlpEncodingError, task_from_structures, to_prolog_atom,
|
|
80
|
+
)
|
|
81
|
+
from .readback import clause_to_formula, hypothesis_to_formulas
|
|
82
|
+
from .separation import SeparationReport, check_separation
|
|
83
|
+
|
|
84
|
+
__all__ = [
|
|
85
|
+
"Example", "IlpTask", "IlpEncodingError", "task_from_structures",
|
|
86
|
+
"to_prolog_atom",
|
|
87
|
+
"clause_to_formula", "hypothesis_to_formulas",
|
|
88
|
+
"SeparationReport", "check_separation",
|
|
89
|
+
]
|