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,500 @@
|
|
|
1
|
+
"""Z3 input/output: turn a Z3 expression (or an SMT-LIB2 string) into the AST,
|
|
2
|
+
and a toolkit :class:`Node` back into SMT-LIB2 text.
|
|
3
|
+
|
|
4
|
+
Two reverse (read) directions for the Z3 back-end:
|
|
5
|
+
|
|
6
|
+
- :func:`from_z3` — convert a ``z3.ExprRef`` into a toolkit :class:`Node` (the inverse
|
|
7
|
+
of :meth:`Node.to_z3`).
|
|
8
|
+
- :func:`parse_smtlib` / :func:`load_smtlib` — parse an SMT-LIB2 string/file (via Z3's
|
|
9
|
+
own parser) and convert every assertion with :func:`from_z3`.
|
|
10
|
+
|
|
11
|
+
And one forward (write) direction:
|
|
12
|
+
|
|
13
|
+
- :func:`to_smtlib` — the general inverse of :func:`parse_smtlib`: render a
|
|
14
|
+
:class:`Node` (plus optional premises) as a standalone, sanitisation-correct
|
|
15
|
+
SMT-LIB2 problem. See its own docstring for what "sanitisation-correct" buys
|
|
16
|
+
over a naive ``to_z3()`` + ``z3.Solver.to_smt2()`` combination.
|
|
17
|
+
|
|
18
|
+
Lossiness (inherent to Z3): :meth:`Node.to_z3` maps :class:`Variable`, :class:`Constant`
|
|
19
|
+
and :class:`Number` all onto symbols of one uninterpreted sort ``S``. A *bound* variable
|
|
20
|
+
(a Z3 de-Bruijn ``Var`` node) comes back as a :class:`Variable`; a *free* symbol comes
|
|
21
|
+
back as a :class:`Constant`, except for the symbol ``x!v`` that ``to_z3`` writes for a
|
|
22
|
+
free :class:`Variable` ``x`` (see :class:`~unicode_logic_kit.fol.nodes.Z3Env`), which comes
|
|
23
|
+
back as that variable, and the symbol ``x!v!c`` or ``x!c!c`` it writes for a constant named
|
|
24
|
+
``x!v`` or ``x!c``, which comes back as that constant. A symbol is a :class:`Number` only
|
|
25
|
+
when its name IS the text a numeral is written as (ASCII digits, a minus sign, a point:
|
|
26
|
+
``5``, ``-3``, ``2.5``, ``1e-07``); ``inf``, ``nan``, ``+5`` and a name in other digits
|
|
27
|
+
are constants. The reading of a marked name is injective on the names of one text: a
|
|
28
|
+
bound or free ``x!v`` is read as the variable ``x`` unless ``x`` is itself a variable of the
|
|
29
|
+
text, and then as the variable ``x!v`` (a text that binds ``a!v`` and ``a`` binds two
|
|
30
|
+
variables); a symbol ``a!c`` that no writer produces is the constant ``a!c``, and the symbol
|
|
31
|
+
``a!c!c`` that the writer gives that constant is read as ``a!c`` unless the text holds
|
|
32
|
+
``a!c`` too, and then as ``a!c!c`` (a text with both holds two constants). A numeral is
|
|
33
|
+
the constant of its VALUE, so ``1.0`` and
|
|
34
|
+
``1`` come back as one :class:`Number`. The conversion is therefore **meaning-preserving**,
|
|
35
|
+
not necessarily structure-preserving. ``A == B`` is read as :class:`Iff` when the operands are Boolean
|
|
36
|
+
and as an ``=`` :class:`Atom` when they are individuals; ``distinct`` becomes ``≠``;
|
|
37
|
+
``xor`` becomes :class:`Xor`; an uninterpreted application returning ``Bool`` becomes an
|
|
38
|
+
:class:`Atom`, one returning ``S`` a :class:`Function`; the arithmetic operators /
|
|
39
|
+
relations and numerals are recognised too (so SMT-LIB with ``Int``/``Real`` imports).
|
|
40
|
+
This same lossiness applies to a :func:`to_smtlib` round trip: it goes through
|
|
41
|
+
``Node.to_z3``/``from_z3`` on the way back in, so it inherits exactly this table.
|
|
42
|
+
|
|
43
|
+
Public API: :func:`from_z3`, :func:`parse_smtlib`, :func:`load_smtlib`, :func:`to_smtlib`.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
import math
|
|
47
|
+
from dataclasses import dataclass, field
|
|
48
|
+
from typing import Dict, FrozenSet, Mapping, Sequence, Set
|
|
49
|
+
|
|
50
|
+
import z3
|
|
51
|
+
|
|
52
|
+
from ..fol.nodes import (
|
|
53
|
+
Node, Variable, Constant, Number, Function,
|
|
54
|
+
Atom, Not, And, Or, Xor, Implies, Iff,
|
|
55
|
+
)
|
|
56
|
+
from ..fol._fol_nodes import Quantifier, Z3Env, kit_name_of_z3_symbol, numeral_key
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Arithmetic operator / relation kinds → toolkit symbol names.
|
|
60
|
+
_ARITH_FUNC = {
|
|
61
|
+
z3.Z3_OP_ADD: "+", z3.Z3_OP_SUB: "-", z3.Z3_OP_MUL: "*",
|
|
62
|
+
z3.Z3_OP_DIV: "/", z3.Z3_OP_IDIV: "/",
|
|
63
|
+
}
|
|
64
|
+
_ARITH_PRED = {
|
|
65
|
+
z3.Z3_OP_LT: "<", z3.Z3_OP_LE: "≤", z3.Z3_OP_GT: ">", z3.Z3_OP_GE: "≥",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _fold(cls, parts):
|
|
70
|
+
"""Right-fold a list of >=1 formulas into nested binary ``cls`` nodes."""
|
|
71
|
+
result = parts[-1]
|
|
72
|
+
for part in reversed(parts[:-1]):
|
|
73
|
+
result = cls(part, result)
|
|
74
|
+
return result
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _const_or_number(name: str) -> Node:
|
|
78
|
+
"""A 0-ary symbol of an uninterpreted sort: a :class:`Number` if its name is a numeral as the kit writes it, else Constant.
|
|
79
|
+
|
|
80
|
+
A numeral is written as :func:`~unicode_logic_kit.fol._fol_nodes.numeral_key` of its value,
|
|
81
|
+
so a symbol is read back as the number exactly when its name IS that text of a finite
|
|
82
|
+
number: ASCII digits, a minus sign, a point, an exponent. Python's own ``int`` and
|
|
83
|
+
``float`` accept more (``inf``, ``nan``, ``+5``, ``1_000``, ``5.``, a number with
|
|
84
|
+
spaces around it, digits of other scripts), and each of those is a CONSTANT of that
|
|
85
|
+
name here: reading two of them as one number would merge distinct constants of the text.
|
|
86
|
+
|
|
87
|
+
Only a symbol of an uninterpreted sort is read this way (the caller sees to it): that
|
|
88
|
+
is where the kit's writer puts a numeral. A symbol of an arithmetic sort is a symbol
|
|
89
|
+
whatever its name, because a text over ``Int`` or ``Real`` has numerals of its own:
|
|
90
|
+
in ``(declare-fun |1| () Int) (assert (not (= |1| 1)))`` the symbol ``|1|`` and the
|
|
91
|
+
numeral ``1`` are two terms (the text is satisfiable), and reading both as the number
|
|
92
|
+
would make the assertion ``¬ 1 = 1``.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
integer = int(name)
|
|
96
|
+
except ValueError:
|
|
97
|
+
pass
|
|
98
|
+
else:
|
|
99
|
+
return Number(integer) if numeral_key(integer) == name else Constant(name)
|
|
100
|
+
try:
|
|
101
|
+
real = float(name)
|
|
102
|
+
except ValueError:
|
|
103
|
+
return Constant(name)
|
|
104
|
+
return Number(real) if math.isfinite(real) and numeral_key(real) == name else Constant(name)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _numeral_of_rational(numerator: int, denominator: int) -> float:
|
|
108
|
+
"""The value of the Z3 numeral ``numerator / denominator`` (lowest terms) as a :class:`Number` holds it.
|
|
109
|
+
|
|
110
|
+
That is an ``int`` for a whole numeral and a ``float`` for any other. A numeral is read exactly
|
|
111
|
+
or refused, as a decimal text is
|
|
112
|
+
(:func:`~unicode_logic_kit.fol._fol_nodes._numeral_from_text`): a whole value is the ``int``, and
|
|
113
|
+
a fraction is the decimal text it spells, when it spells one (the denominator has no prime
|
|
114
|
+
factor but 2 and 5), read by that same function, so ``1/10`` is ``0.1``, a decimal of more than
|
|
115
|
+
15 significant digits is refused (Z3 reads ``0.30000000000000004`` and ``0.30000000000000005``
|
|
116
|
+
as two different rationals, and one float is the nearest to both), and so is a fraction that
|
|
117
|
+
has no decimal text (``1/3``: no float is that number, and the nearest one would be another
|
|
118
|
+
numeral).
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: the numeral is not a whole number and has no decimal text of at most 15
|
|
122
|
+
significant digits (see above).
|
|
123
|
+
"""
|
|
124
|
+
from ..fol._fol_nodes import _numeral_from_text
|
|
125
|
+
|
|
126
|
+
if denominator == 1:
|
|
127
|
+
return numerator
|
|
128
|
+
remaining, twos, fives = denominator, 0, 0
|
|
129
|
+
while remaining % 2 == 0:
|
|
130
|
+
remaining //= 2
|
|
131
|
+
twos += 1
|
|
132
|
+
while remaining % 5 == 0:
|
|
133
|
+
remaining //= 5
|
|
134
|
+
fives += 1
|
|
135
|
+
if remaining != 1:
|
|
136
|
+
raise ValueError(
|
|
137
|
+
f"the numeral {numerator}/{denominator} has no decimal text (its denominator has a prime "
|
|
138
|
+
f"factor other than 2 and 5), so no floating-point number is that numeral, and reading "
|
|
139
|
+
f"the nearest one would make it another numeral than the one written. Keep it as the "
|
|
140
|
+
f"quotient of two integers, or give a decimal with at most 15 significant digits "
|
|
141
|
+
f"instead")
|
|
142
|
+
places = max(twos, fives)
|
|
143
|
+
scaled = str(abs(numerator) * 10 ** places // denominator).rjust(places + 1, "0")
|
|
144
|
+
text = f"{'-' if numerator < 0 else ''}{scaled[:-places]}.{scaled[-places:]}"
|
|
145
|
+
return _numeral_from_text(text)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True)
|
|
149
|
+
class _TextNames:
|
|
150
|
+
"""The names of one text (one or several Z3 expressions) that decide how a marked symbol is read.
|
|
151
|
+
|
|
152
|
+
Fields:
|
|
153
|
+
|
|
154
|
+
* ``variables``: every name, as Z3 holds it, that is read back as a variable: the names of all
|
|
155
|
+
the bound variables of all the quantifiers, and the free symbols of the sort ``S`` whose name
|
|
156
|
+
ends in ``!v`` (the mark of :func:`~unicode_logic_kit.fol._fol_nodes.z3_variable_name`). See
|
|
157
|
+
:func:`_variable_name`.
|
|
158
|
+
* ``constants``: the kit's name of every free symbol of no arguments that is not a Boolean and
|
|
159
|
+
is read back as a constant, by the name Z3 holds it under. See :func:`_constant_readings`.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
variables: FrozenSet[str] = frozenset()
|
|
163
|
+
constants: Mapping[str, str] = field(default_factory=dict)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _text_names(exprs) -> _TextNames:
|
|
167
|
+
"""The :class:`_TextNames` of ``exprs``: every name the reading of one text has to keep apart."""
|
|
168
|
+
variables: Set[str] = set()
|
|
169
|
+
constants: Set[str] = set()
|
|
170
|
+
seen: Set[int] = set()
|
|
171
|
+
stack = list(exprs)
|
|
172
|
+
while stack:
|
|
173
|
+
node = stack.pop()
|
|
174
|
+
key = node.get_id()
|
|
175
|
+
if key in seen:
|
|
176
|
+
continue
|
|
177
|
+
seen.add(key)
|
|
178
|
+
if z3.is_quantifier(node):
|
|
179
|
+
variables.update(str(node.var_name(i)) for i in range(node.num_vars()))
|
|
180
|
+
stack.append(node.body())
|
|
181
|
+
elif z3.is_app(node):
|
|
182
|
+
if (node.num_args() == 0 and not _is_bool(node)
|
|
183
|
+
and node.decl().kind() == z3.Z3_OP_UNINTERPRETED):
|
|
184
|
+
name = str(node.decl().name())
|
|
185
|
+
(variables if kit_name_of_z3_symbol(name)[1] else constants).add(name)
|
|
186
|
+
stack.extend(node.children())
|
|
187
|
+
return _TextNames(frozenset(variables), _constant_readings(constants))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _constant_readings(names) -> Dict[str, str]:
|
|
191
|
+
"""The kit's name of each constant that a text holds under the Z3 names ``names``: two symbols, two names.
|
|
192
|
+
|
|
193
|
+
The writer W (:func:`~unicode_logic_kit.fol._fol_nodes.z3_constant_name`) gives the constant ``n``
|
|
194
|
+
the symbol ``n``, or ``n!c`` when ``n`` ends in a mark (``!v`` or ``!c``). Its inverse R
|
|
195
|
+
(:func:`~unicode_logic_kit.fol._fol_nodes.kit_name_of_z3_symbol`) strips a ``!c`` that follows
|
|
196
|
+
a mark and keeps every other name. R alone does not keep two symbols of a text that a writer did
|
|
197
|
+
not write apart: ``a!c`` is no writer's symbol (the constant ``a`` is written ``a``) and R keeps
|
|
198
|
+
it, while ``a!c!c`` is the symbol of the constant ``a!c`` and R gives that name too. So the
|
|
199
|
+
symbols are read shortest first, each as R of it unless an earlier symbol has that reading
|
|
200
|
+
already, and then as written: a text that holds both is read as two constants, ``a!c`` and
|
|
201
|
+
``a!c!c``; a symbol that no writer writes is read as itself, whatever else the text holds.
|
|
202
|
+
"""
|
|
203
|
+
# (1) R(W(n)) = n for every name n, so R is injective on the image of W; (2) R(s) = s unless s = W(R(s)), so a symbol
|
|
204
|
+
# outside the image is read as itself; (3) shortest first, s is read as R(s) or as s, and no earlier reading is s: an
|
|
205
|
+
# earlier s' is no longer than s and R(s') = s needs s' = s or len(s') = len(s) + 2, so no two readings are equal.
|
|
206
|
+
readings: Dict[str, str] = {}
|
|
207
|
+
taken: Set[str] = set()
|
|
208
|
+
for raw in sorted(names, key=lambda name: (len(name), name)):
|
|
209
|
+
kit_name = kit_name_of_z3_symbol(raw)[0]
|
|
210
|
+
if kit_name in taken:
|
|
211
|
+
kit_name = raw
|
|
212
|
+
taken.add(kit_name)
|
|
213
|
+
readings[raw] = kit_name
|
|
214
|
+
return readings
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _variable_name(name, taken: FrozenSet[str]) -> str:
|
|
218
|
+
"""The kit's name of a variable that Z3 holds as ``name`` (a bound name, or a free symbol ``x!v``).
|
|
219
|
+
|
|
220
|
+
``x!v`` is what :meth:`Node.to_z3` writes for the variable ``x``, so it is read back as ``x``
|
|
221
|
+
-- unless ``x`` is itself the name of a variable of the text (``taken``, the ``variables`` of
|
|
222
|
+
:func:`_text_names`): a text that binds ``a!v`` and ``a`` in one quantifier names TWO
|
|
223
|
+
variables, and stripping the mark would make them one (``∀a ∀a (P(a, a) → Q)`` is a weaker
|
|
224
|
+
sentence than ``∀a!v ∀a (P(a!v, a) → Q)``), or would let a bound ``a`` capture a free symbol
|
|
225
|
+
``a!v``. A name whose stripped form is taken is read as it is written. So the reading is
|
|
226
|
+
injective on the names of one text: two names read alike only when they are one.
|
|
227
|
+
"""
|
|
228
|
+
raw = str(name)
|
|
229
|
+
kit_name, is_variable = kit_name_of_z3_symbol(raw)
|
|
230
|
+
return kit_name if is_variable and kit_name not in taken else raw
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _is_bool(expr) -> bool:
|
|
234
|
+
"""True iff the Z3 expression has Boolean sort."""
|
|
235
|
+
return expr.sort_kind() == z3.Z3_BOOL_SORT
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def from_z3(expr, _scope=None, _taken=None) -> Node:
|
|
239
|
+
"""Convert a Z3 expression (``z3.ExprRef``) into a toolkit :class:`Node`.
|
|
240
|
+
|
|
241
|
+
The inverse of :meth:`Node.to_z3`; meaning-preserving (see the module docstring
|
|
242
|
+
for the inherent Z3 lossiness around free variables / constants / numbers).
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
TypeError: on a Z3 construct with no first-order toolkit counterpart.
|
|
246
|
+
ValueError: a rational numeral that no :class:`Number` holds exactly: a fraction with
|
|
247
|
+
more than 15 significant digits or no decimal text at all (the rule is that of a
|
|
248
|
+
decimal text in every other reader). It is refused, never read as the nearest float.
|
|
249
|
+
"""
|
|
250
|
+
if _scope is None:
|
|
251
|
+
_scope = []
|
|
252
|
+
if _taken is None:
|
|
253
|
+
_taken = _text_names([expr])
|
|
254
|
+
|
|
255
|
+
# --- quantifiers (Z3 stores the body with de-Bruijn Var nodes) ---
|
|
256
|
+
if z3.is_quantifier(expr):
|
|
257
|
+
if expr.is_lambda():
|
|
258
|
+
raise TypeError("from_z3: Z3 lambda terms have no first-order AST counterpart.")
|
|
259
|
+
qtype = "∀" if expr.is_forall() else "∃"
|
|
260
|
+
names = [_variable_name(expr.var_name(i), _taken.variables) for i in range(expr.num_vars())]
|
|
261
|
+
# In the body, Var(0) is the LAST bound name; append in order so that
|
|
262
|
+
# Var(idx) -> scope[len-1-idx].
|
|
263
|
+
body = from_z3(expr.body(), _scope + names, _taken)
|
|
264
|
+
for name in reversed(names):
|
|
265
|
+
body = Quantifier(qtype, Variable(name), body)
|
|
266
|
+
return body
|
|
267
|
+
|
|
268
|
+
if z3.is_var(expr):
|
|
269
|
+
idx = z3.get_var_index(expr)
|
|
270
|
+
return Variable(str(_scope[len(_scope) - 1 - idx]))
|
|
271
|
+
|
|
272
|
+
# --- logical connectives ---
|
|
273
|
+
if z3.is_not(expr):
|
|
274
|
+
return Not(from_z3(expr.arg(0), _scope, _taken))
|
|
275
|
+
if z3.is_and(expr):
|
|
276
|
+
return _fold(And, [from_z3(expr.arg(i), _scope, _taken) for i in range(expr.num_args())])
|
|
277
|
+
if z3.is_or(expr):
|
|
278
|
+
return _fold(Or, [from_z3(expr.arg(i), _scope, _taken) for i in range(expr.num_args())])
|
|
279
|
+
if z3.is_implies(expr):
|
|
280
|
+
return Implies(from_z3(expr.arg(0), _scope, _taken), from_z3(expr.arg(1), _scope, _taken))
|
|
281
|
+
if z3.is_true(expr):
|
|
282
|
+
return Atom("$true", [])
|
|
283
|
+
if z3.is_false(expr):
|
|
284
|
+
return Atom("$false", [])
|
|
285
|
+
|
|
286
|
+
if z3.is_app(expr):
|
|
287
|
+
kind = expr.decl().kind()
|
|
288
|
+
|
|
289
|
+
if z3.is_eq(expr):
|
|
290
|
+
left, right = expr.arg(0), expr.arg(1)
|
|
291
|
+
sub = [from_z3(left, _scope, _taken), from_z3(right, _scope, _taken)]
|
|
292
|
+
return Iff(*sub) if _is_bool(left) else Atom("=", sub)
|
|
293
|
+
if kind == z3.Z3_OP_DISTINCT:
|
|
294
|
+
args = [from_z3(expr.arg(i), _scope, _taken) for i in range(expr.num_args())]
|
|
295
|
+
if len(args) == 2:
|
|
296
|
+
return Atom("≠", args)
|
|
297
|
+
# n-ary distinct = conjunction of pairwise disequalities.
|
|
298
|
+
pairs = [Atom("≠", [args[i], args[j]])
|
|
299
|
+
for i in range(len(args)) for j in range(i + 1, len(args))]
|
|
300
|
+
return _fold(And, pairs)
|
|
301
|
+
if kind == z3.Z3_OP_XOR:
|
|
302
|
+
return Xor(from_z3(expr.arg(0), _scope, _taken), from_z3(expr.arg(1), _scope, _taken))
|
|
303
|
+
if kind == z3.Z3_OP_IFF:
|
|
304
|
+
return Iff(from_z3(expr.arg(0), _scope, _taken), from_z3(expr.arg(1), _scope, _taken))
|
|
305
|
+
|
|
306
|
+
# numerals
|
|
307
|
+
if z3.is_int_value(expr):
|
|
308
|
+
return Number(expr.as_long())
|
|
309
|
+
if z3.is_rational_value(expr):
|
|
310
|
+
return Number(_numeral_of_rational(expr.numerator_as_long(), expr.denominator_as_long()))
|
|
311
|
+
|
|
312
|
+
# arithmetic
|
|
313
|
+
if kind in _ARITH_PRED:
|
|
314
|
+
return Atom(_ARITH_PRED[kind],
|
|
315
|
+
[from_z3(expr.arg(i), _scope, _taken) for i in range(expr.num_args())])
|
|
316
|
+
if kind in _ARITH_FUNC and expr.num_args() == 2:
|
|
317
|
+
return Function(_ARITH_FUNC[kind],
|
|
318
|
+
[from_z3(expr.arg(0), _scope, _taken), from_z3(expr.arg(1), _scope, _taken)])
|
|
319
|
+
if kind == z3.Z3_OP_UMINUS:
|
|
320
|
+
return Function("-", [from_z3(expr.arg(0), _scope, _taken)])
|
|
321
|
+
|
|
322
|
+
# generic (uninterpreted) application, constant, or predicate
|
|
323
|
+
name = expr.decl().name()
|
|
324
|
+
nargs = expr.num_args()
|
|
325
|
+
args = [from_z3(expr.arg(i), _scope, _taken) for i in range(nargs)]
|
|
326
|
+
if nargs == 0:
|
|
327
|
+
if _is_bool(expr):
|
|
328
|
+
return Atom(name, [])
|
|
329
|
+
if kit_name_of_z3_symbol(name)[1]:
|
|
330
|
+
return Variable(_variable_name(name, _taken.variables))
|
|
331
|
+
reading = _taken.constants.get(name, kit_name_of_z3_symbol(name)[0])
|
|
332
|
+
if expr.sort().kind() != z3.Z3_UNINTERPRETED_SORT:
|
|
333
|
+
return Constant(reading)
|
|
334
|
+
return _const_or_number(reading)
|
|
335
|
+
return Atom(name, args) if _is_bool(expr) else Function(name, args)
|
|
336
|
+
|
|
337
|
+
raise TypeError(
|
|
338
|
+
f"from_z3: unsupported Z3 expression {expr!r} (no first-order AST counterpart)."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def parse_smtlib(text: str) -> list:
|
|
343
|
+
"""Parse an SMT-LIB2 string and return its assertions as toolkit :class:`Node`\\ s.
|
|
344
|
+
|
|
345
|
+
Uses Z3's own SMT-LIB2 parser (``z3.parse_smt2_string``) to read the assertions,
|
|
346
|
+
then converts each with :func:`from_z3`.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
text: SMT-LIB2 source (``declare-fun`` / ``assert`` … ).
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
A list of :class:`Node`, one per top-level ``assert``.
|
|
353
|
+
"""
|
|
354
|
+
return _from_assertions(z3.parse_smt2_string(text))
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def load_smtlib(path: str) -> list:
|
|
358
|
+
"""Read an SMT-LIB2 (``.smt2``) file and :func:`parse_smtlib` its contents."""
|
|
359
|
+
return _from_assertions(z3.parse_smt2_file(path))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _from_assertions(vector) -> list:
|
|
363
|
+
""":func:`from_z3` of every assertion of ``vector``, naming one symbol alike in all of them.
|
|
364
|
+
|
|
365
|
+
The variables and the constants of the whole text are looked at together
|
|
366
|
+
(:func:`_text_names`), so that a free symbol ``x!v`` is read under one name wherever it
|
|
367
|
+
occurs, whichever of the assertions bind a variable ``x``, and two symbols of the text are
|
|
368
|
+
never read as one name (:func:`_constant_readings`).
|
|
369
|
+
"""
|
|
370
|
+
assertions = list(vector)
|
|
371
|
+
taken = _text_names(assertions)
|
|
372
|
+
return [from_z3(assertion, None, taken) for assertion in assertions]
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def to_smtlib(formula: Node, premises: Sequence[Node] = (), *, logic: str = "ALL") -> str:
|
|
376
|
+
"""Render ``premises`` and ``formula`` as one standalone SMT-LIB2 problem.
|
|
377
|
+
|
|
378
|
+
The general inverse of :func:`parse_smtlib`: each of ``premises`` then
|
|
379
|
+
``formula`` becomes its OWN top-level ``(assert ...)`` command (not one
|
|
380
|
+
folded ``(∧ premises) → formula`` implication) — that is what
|
|
381
|
+
:func:`parse_smtlib` reads back (one :class:`Node` per top-level
|
|
382
|
+
``assert``), and it is the more idiomatic reading of a standalone
|
|
383
|
+
SMT-LIB2 problem: a list of asserted facts, not a single implication
|
|
384
|
+
that happens to encode an entailment question. Logically nothing is
|
|
385
|
+
lost either way (an SMT solver conjoins every assertion regardless of
|
|
386
|
+
how many ``(assert ...)`` commands they arrived in) — see
|
|
387
|
+
:func:`~atp.cvc5_backend._sanitize_many_for_smtlib`'s docstring for the
|
|
388
|
+
sibling design decision this mirrors.
|
|
389
|
+
|
|
390
|
+
This is deliberately NOT built as ``to_z3()`` + ``z3.Solver.to_smt2()``
|
|
391
|
+
alone: that combination is unsound for a symbol name that is pure ASCII
|
|
392
|
+
and digit-leading (``2008SummerOlympics``) or that IS one of SMT-LIB2's
|
|
393
|
+
own reserved words (``let``, ``forall``, ...) — Z3's own serialiser
|
|
394
|
+
quotes every OTHER illegal name correctly (including any non-ASCII one)
|
|
395
|
+
but silently emits these two kinds unquoted, producing ``.smt2`` text
|
|
396
|
+
that fails to parse back (reproduced live; see
|
|
397
|
+
:mod:`atp.cvc5_backend`'s module docstring). So every name across
|
|
398
|
+
``premises`` and ``formula`` is first sanitised against ONE SHARED name
|
|
399
|
+
map — :func:`~atp.cvc5_backend._sanitize_many_for_smtlib`, promoted from
|
|
400
|
+
:class:`Cvc5Backend`'s own already-proven translation rather than
|
|
401
|
+
reimplemented here — so a symbol used in more than one formula renames
|
|
402
|
+
consistently, before any of them reaches ``to_z3()``. A counting quantifier is
|
|
403
|
+
expanded before the renaming (the witnesses are then names of the problem like any
|
|
404
|
+
other, and are never spelled like a predicate, a function, a constant or a sort of
|
|
405
|
+
it: in this text a bound variable and the symbol it shadows are one identifier).
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
formula: the goal (or the only formula, when ``premises`` is empty).
|
|
409
|
+
premises: additional formulas, each asserted on its own line ahead
|
|
410
|
+
of ``formula``'s.
|
|
411
|
+
logic: the SMT-LIB2 logic string emitted on the leading
|
|
412
|
+
``(set-logic ...)`` command (default ``"ALL"`` — safe for
|
|
413
|
+
anything ``to_z3()`` can produce: one uninterpreted sort with
|
|
414
|
+
equality plus uninterpreted functions/predicates, never genuine
|
|
415
|
+
interpreted arithmetic).
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
SMT-LIB2 source text: ``(set-logic ...)``, the ``declare-sort``/
|
|
419
|
+
``declare-fun`` preamble Z3's own ``Solver.to_smt2()`` derives from
|
|
420
|
+
the declarations actually used, one ``(assert ...)`` per premise
|
|
421
|
+
then the goal, and a trailing ``(check-sat)``.
|
|
422
|
+
|
|
423
|
+
**Symbols.** A symbol is a predicate, a function or a variable at one arity, so
|
|
424
|
+
``P(a)`` and ``P(a, b)``, a predicate and a function of one name, a variable and a
|
|
425
|
+
constant of one name, are written under different names (the second and every later
|
|
426
|
+
one gets a token such as ``P_2``; two declarations of one name are an error in
|
|
427
|
+
SMT-LIB2). A name that is a symbol of an SMT-LIB theory (``+``, ``select``,
|
|
428
|
+
``str.len``, ...) is renamed too, so the text is the same whatever the logic of the
|
|
429
|
+
reading solver, and so is a constant or a variable whose name ends in ``!v`` or ``!c``
|
|
430
|
+
(the reader decodes such a symbol as a variable or as another constant), and so is a
|
|
431
|
+
symbol spelled like a name that Z3's printer mints for a shared sub-term (``$x24``,
|
|
432
|
+
``?x10``: the printer writes ``(let (($x24 ...)) ...)`` without looking at the symbols the
|
|
433
|
+
text declares, and would shadow a declared symbol of that spelling inside the ``let``). A
|
|
434
|
+
symbol that is renamed is written under its token and read back under it. A numeral is
|
|
435
|
+
the constant of its value: ``1`` and ``1.0`` are one. Every formula is one
|
|
436
|
+
``(assert ...)``, a truth constant included.
|
|
437
|
+
|
|
438
|
+
Raises:
|
|
439
|
+
NotImplementedError: ``formula`` or a premise uses a construct with
|
|
440
|
+
no first-order SMT-LIB2 encoding (second/third-order
|
|
441
|
+
quantification, modal/hybrid/linear/Lambek/team operators, ...)
|
|
442
|
+
— the exact refusal :meth:`Node.to_z3` itself raises for it,
|
|
443
|
+
reused rather than reimplemented (see the ``to_z3`` overrides in
|
|
444
|
+
``fol/_so_nodes.py``, ``fol/_modal_nodes.py``, etc.), with one
|
|
445
|
+
sentence appended naming this entry point; or the problem holds a
|
|
446
|
+
numeral and a constant of the same text (``Number(1)`` and
|
|
447
|
+
``Constant('1')``), which are one symbol; or a counting quantifier has a bound
|
|
448
|
+
above 500, which :meth:`~unicode_logic_kit.fol.nodes.Count._expand` refuses by
|
|
449
|
+
naming the bound (a bound up to 500 is written: its expansion is that many
|
|
450
|
+
nested quantifiers, and nothing here recurses on it).
|
|
451
|
+
"""
|
|
452
|
+
# Lazy import: atp/__init__.py imports this module before cvc5_backend
|
|
453
|
+
# (which itself imports atp.protocol, which imports cvc5_backend again at
|
|
454
|
+
# its own bottom to register it) — importing cvc5_backend at THIS
|
|
455
|
+
# module's top level would run into that chain mid-load and fail with a
|
|
456
|
+
# partial-module ImportError. By the time to_smtlib() is actually
|
|
457
|
+
# called, package import has long finished, so this is safe.
|
|
458
|
+
from .cvc5_backend import _lower_counting_for_smtlib, _sanitize_many_for_smtlib
|
|
459
|
+
|
|
460
|
+
# The counting quantifiers are expanded BEFORE the sanitiser renames the symbols: their
|
|
461
|
+
# witnesses are then names of the problem like any other and are kept apart from every
|
|
462
|
+
# predicate, function, constant and sort (one namespace in the text).
|
|
463
|
+
all_nodes, _ = _lower_counting_for_smtlib(list(premises) + [formula])
|
|
464
|
+
sanitised_nodes, _name_map = _sanitize_many_for_smtlib(all_nodes)
|
|
465
|
+
# The sanitiser has given every variable and every constant a name of its own, so the
|
|
466
|
+
# environment writes a variable as it is named and the text holds those names.
|
|
467
|
+
env = Z3Env(variables_apart=False)
|
|
468
|
+
try:
|
|
469
|
+
z3_exprs = [node.to_z3(env) for node in sanitised_nodes]
|
|
470
|
+
except NotImplementedError as exc:
|
|
471
|
+
raise NotImplementedError(
|
|
472
|
+
f"{exc} SMT-LIB2 export is first-order only."
|
|
473
|
+
) from exc
|
|
474
|
+
|
|
475
|
+
solver = z3.Solver()
|
|
476
|
+
for expr in z3_exprs:
|
|
477
|
+
solver.add(expr)
|
|
478
|
+
text = solver.to_smt2()
|
|
479
|
+
if z3_exprs and z3.is_true(z3_exprs[-1]):
|
|
480
|
+
# ``Solver.to_smt2`` prints its LAST assertion as the formula of a benchmark, and the
|
|
481
|
+
# printer leaves out a formula that is ``true``: the text would have no ``(assert true)``
|
|
482
|
+
# for a problem that ends in a truth constant (a lone ``⊤`` has no assertion at all), and
|
|
483
|
+
# would read back as fewer formulas than were written.
|
|
484
|
+
text = _benchmark_text(z3_exprs)
|
|
485
|
+
return f"(set-logic {logic})\n{text}"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _benchmark_text(exprs) -> str:
|
|
489
|
+
"""The SMT-LIB2 text of ``exprs``, one ``(assert ...)`` each, a ``true`` among them too.
|
|
490
|
+
|
|
491
|
+
What ``Solver.to_smt2`` prints, with every expression handed over as an assumption of the
|
|
492
|
+
benchmark and none as its formula, so that the printer writes all of them.
|
|
493
|
+
"""
|
|
494
|
+
context = z3.main_ctx()
|
|
495
|
+
assumptions = (z3.Ast * len(exprs))()
|
|
496
|
+
for i, expr in enumerate(exprs):
|
|
497
|
+
assumptions[i] = expr.as_ast()
|
|
498
|
+
return z3.Z3_benchmark_to_smtlib_string(
|
|
499
|
+
context.ref(), "benchmark generated from python API", "", "unknown", "",
|
|
500
|
+
len(exprs), assumptions, z3.BoolVal(True, context).as_ast())
|