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,478 @@
|
|
|
1
|
+
"""Adapter for the WillowNLtoFOL dataset (Deveci, "A Diagnostic Framework for
|
|
2
|
+
Compositional Generalization via NL-to-FOL Translation", JLLI 2026; earlier
|
|
3
|
+
described in Deveci's 2024 METU master's thesis) — local JSONL only, no
|
|
4
|
+
network access.
|
|
5
|
+
|
|
6
|
+
Source and verified schema
|
|
7
|
+
---------------------------
|
|
8
|
+
Source: https://huggingface.co/datasets/iedeveci/WillowNLtoFOL (unauthenticated;
|
|
9
|
+
verified 2026-08-12 directly via the Hugging Face datasets-server ``splits``,
|
|
10
|
+
``first-rows``, and ``rows`` APIs, and by fetching the dataset's raw
|
|
11
|
+
``README.md`` — https://huggingface.co/datasets/iedeveci/WillowNLtoFOL/raw/main/README.md
|
|
12
|
+
— for its YAML metadata and licensing prose). The ``splits`` endpoint reports
|
|
13
|
+
exactly two splits under config ``"default"``: ``"train"`` (16014 rows) and
|
|
14
|
+
``"test"`` (90 rows). NOTE: the dataset card's prose additionally describes a
|
|
15
|
+
"``WillowNLtoFOL_extended``" 90-pair out-of-distribution stress-test subset —
|
|
16
|
+
that appears to BE the ``"test"`` split under a different name in the card's
|
|
17
|
+
prose than in the actual data (the ``splits``/``info`` APIs show no subset or
|
|
18
|
+
config named ``extended``); this loader reads whatever split you point it at
|
|
19
|
+
and does not depend on which of the two names is "correct".
|
|
20
|
+
|
|
21
|
+
Each verified row (``first-rows``/``rows`` APIs, both splits) is a flat JSON
|
|
22
|
+
object with EXACTLY two keys:
|
|
23
|
+
|
|
24
|
+
* ``"NL_sentence"`` — ``str``, one natural-language sentence.
|
|
25
|
+
* ``"FOL_expression"`` — ``str``, its FOL translation, written DIRECTLY in
|
|
26
|
+
this kit's own unicode surface syntax (``∀``/``∃``/``∧``/``∨``/``¬``/``→``/
|
|
27
|
+
``↔``/``⊕`` all appear across the verified sample rows and the ``"fol"``
|
|
28
|
+
dialect is what ``api.parse_any`` selects for every one that parses at
|
|
29
|
+
all — no ASCII/LaTeX fallback notation was observed anywhere in the sample).
|
|
30
|
+
|
|
31
|
+
Like MALLS, WillowNLtoFOL has NO id field, NO premises/conclusion structure,
|
|
32
|
+
and NO entailment label — it is a flat bag of (sentence, formula) pairs. This
|
|
33
|
+
adapter maps that onto :class:`~unicode_logic_kit.eval.datasets.DatasetExample`
|
|
34
|
+
with the SAME convention :mod:`~unicode_logic_kit.eval.datasets.malls` uses for
|
|
35
|
+
MALLS (an equally-shaped translation-pair dataset):
|
|
36
|
+
|
|
37
|
+
* ``nl_conclusion`` / ``fol_conclusion`` carry ``"NL_sentence"`` /
|
|
38
|
+
``"FOL_expression"``.
|
|
39
|
+
* ``nl_premises`` / ``fol_premises`` are always ``()``.
|
|
40
|
+
* ``label`` is always ``None``.
|
|
41
|
+
|
|
42
|
+
Does the gold notation actually parse under this kit? — an honest, measured
|
|
43
|
+
answer, not a guess
|
|
44
|
+
--------------------------------------------------------------------------
|
|
45
|
+
The dataset card states the FOL annotations were "aggressively filtered using
|
|
46
|
+
``nltk.sem.logic``" to reject unparseable/unbound-variable formulas — but
|
|
47
|
+
``nltk.sem.logic`` accepts several things this kit's ``fol`` grammar
|
|
48
|
+
deliberately does not, so "filtered by nltk" does not imply "parses here".
|
|
49
|
+
To answer this honestly rather than assume it, a STRATIFIED sample of 2100 of
|
|
50
|
+
the 16014 real ``train`` rows (row offsets 0, 100, ..., 900, then 1500, 3000,
|
|
51
|
+
..., 15000, 15900 — spanning the whole file, fetched 2026-08-12 via the
|
|
52
|
+
``rows`` API) was run through ``unicode_logic_kit.api.parse_any`` and, for
|
|
53
|
+
whatever parsed, ``unicode_logic_kit.api.check``:
|
|
54
|
+
|
|
55
|
+
* 2075 / 2100 (98.8%) parse AND pass ``check()`` (closed, arity-consistent,
|
|
56
|
+
lambda-free) outright.
|
|
57
|
+
* 25 / 2100 (1.2%) FAIL TO PARSE under every dialect ``parse_any`` tries. Two
|
|
58
|
+
recurring, real causes accounted for all 25:
|
|
59
|
+
|
|
60
|
+
1. (~21/25) Mixing ``∧``/``∨`` (or ``∧``/``⊕``) at the same nesting depth
|
|
61
|
+
without disambiguating parentheses, e.g. row 148's
|
|
62
|
+
``"∀y (Bird(y) ∧ Predator(y) ↔ (Hunt(y) ∨ EatMeat(y) ∧ ¬EatSeeds(y)))"``
|
|
63
|
+
(the ``Hunt(y) ∨ EatMeat(y) ∧ ¬EatSeeds(y)`` clause). This kit's ``fol``
|
|
64
|
+
grammar deliberately refuses to guess a precedence between ``∧``, ``∨``,
|
|
65
|
+
and ``⊕`` rather than silently pick one (see the parser's own
|
|
66
|
+
``SYNTAX_ERROR`` hint text) — a real, structural difference from
|
|
67
|
+
``nltk.sem.logic``'s (and most textbooks') convention of ``∧`` binding
|
|
68
|
+
tighter than ``∨``.
|
|
69
|
+
2. (remaining few) A predicate identifier that does not start with an
|
|
70
|
+
uppercase ASCII letter, which this kit's ``fol`` grammar requires
|
|
71
|
+
(predicate names match ``[A-Z][a-zA-Z0-9]*``; a lower-initial identifier
|
|
72
|
+
is parsed as a TERM, not a predicate, so e.g. row 1554's
|
|
73
|
+
``"∀x (Device(x) → (Android(x) ∨ iOS(x)))"`` fails because ``iOS`` starts
|
|
74
|
+
with a lowercase letter, and row 15071's ``"...∃y (Café(y) ∧ ...)"``
|
|
75
|
+
fails because ``é`` is outside the grammar's ASCII letter class for
|
|
76
|
+
predicate names.
|
|
77
|
+
|
|
78
|
+
* 1 / 2100 (0.05%) PARSES but fails ``check()``: row 15947,
|
|
79
|
+
``"∃x (Chef(x) ∧ ∃y (Cook(y) ∧ ∀z (Meal(z) ∧ CookedBy(z, y) → Cook(x, z))))"``
|
|
80
|
+
uses the predicate ``Cook`` at TWO different arities in the same formula
|
|
81
|
+
(``Cook(y)`` unary, ``Cook(x, z)`` binary) — a genuine annotation defect,
|
|
82
|
+
not a kit limitation; ``check()`` reports it as an ``arity_conflict``.
|
|
83
|
+
|
|
84
|
+
None of this is hidden: the fixture below (``tests/fixtures/willow_mini.jsonl``)
|
|
85
|
+
deliberately includes real rows from BOTH failure classes above plus the one
|
|
86
|
+
``check()``-only failure, and ``tests/test_datasets_willow.py`` asserts
|
|
87
|
+
``audit_examples`` finds exactly them — so a caller loading the real 16014-row
|
|
88
|
+
file and running :func:`~unicode_logic_kit.eval.datasets.audit_examples` should
|
|
89
|
+
expect roughly this same ~98.8% clean rate, not 100%.
|
|
90
|
+
|
|
91
|
+
License — a discrepancy flagged, not silently resolved
|
|
92
|
+
--------------------------------------------------------
|
|
93
|
+
The iedeveci/WillowNLtoFOL Hugging Face card states, both in its YAML
|
|
94
|
+
frontmatter (``license: cc-by-4.0``) and in its "Licensing Information" prose
|
|
95
|
+
section: **CC-BY-4.0** ("You are free to share and adapt the material for any
|
|
96
|
+
purpose, even commercially, provided you give appropriate credit..."),
|
|
97
|
+
verified directly against the raw README.md on 2026-08-12.
|
|
98
|
+
|
|
99
|
+
This conflicts with a DIFFERENT, related dataset's card: fvossel/groves
|
|
100
|
+
(GROVES, which uses WillowNLtoFOL as one of two NL sources) states in its own
|
|
101
|
+
"Licensing" section that "WillowNLtoFOL ... originally licensed under
|
|
102
|
+
**CC BY-NC-ND 4.0**" (also verified 2026-08-12). This loader reads
|
|
103
|
+
iedeveci/WillowNLtoFOL directly, so :data:`~unicode_logic_kit.eval.datasets.DATASET_INFO`
|
|
104
|
+
below records what is VERIFIED AT THAT PRIMARY SOURCE TODAY (CC-BY-4.0) — not
|
|
105
|
+
the GROVES card's claim about an earlier state of it. The discrepancy (a more
|
|
106
|
+
restrictive license claimed elsewhere for what may be an earlier version of
|
|
107
|
+
the same data) is flagged here rather than silently resolved either way.
|
|
108
|
+
ADDITIONALLY, per this repository's author (stated 2026-08-12): explicit
|
|
109
|
+
usage permission for the GROVES/unicode-fol-kit use of WillowNLtoFOL (this
|
|
110
|
+
kit, under the name it had until 0.30.0) was
|
|
111
|
+
obtained from the Willow authors — so whichever of the two license readings
|
|
112
|
+
applies, this repository's own use (including the real-rows test fixture
|
|
113
|
+
below) is covered by that direct permission. Third parties redistributing
|
|
114
|
+
Willow data should still confirm the applicable license themselves.
|
|
115
|
+
|
|
116
|
+
This module never downloads anything — obtain the data yourself (e.g. via the
|
|
117
|
+
``datasets`` library, ``load_dataset("iedeveci/WillowNLtoFOL", split="train").to_json("willow_train.jsonl")``,
|
|
118
|
+
which yields the one-JSON-object-per-line format this loader expects) and
|
|
119
|
+
pass its local JSONL path to :func:`load_willow`.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
import json
|
|
123
|
+
import re
|
|
124
|
+
import unicodedata
|
|
125
|
+
from pathlib import Path
|
|
126
|
+
from typing import Dict, FrozenSet, Iterator, Optional, Tuple, Union
|
|
127
|
+
|
|
128
|
+
from lark import Lark, Transformer
|
|
129
|
+
from lark.exceptions import LarkError, VisitError
|
|
130
|
+
|
|
131
|
+
from ...fol.nodes import (
|
|
132
|
+
Node, Atom, Not, And, Or, Xor, Implies, Iff, Quantifier,
|
|
133
|
+
Variable, Constant, Function, free_variables,
|
|
134
|
+
)
|
|
135
|
+
from ._base import DatasetExample, _register_dataset_info
|
|
136
|
+
|
|
137
|
+
__all__ = ["load_willow", "repair_willow_formula"]
|
|
138
|
+
|
|
139
|
+
_register_dataset_info(
|
|
140
|
+
"willow",
|
|
141
|
+
license=(
|
|
142
|
+
"CC-BY-4.0, per the iedeveci/WillowNLtoFOL Hugging Face card (raw "
|
|
143
|
+
"README.md frontmatter `license: cc-by-4.0` plus its 'Licensing "
|
|
144
|
+
"Information' section, verified 2026-08-12). NOTE: the fvossel/groves "
|
|
145
|
+
"dataset card (GROVES, built in part from WillowNLtoFOL) states this "
|
|
146
|
+
"same source was 'originally licensed under CC BY-NC-ND 4.0' -- a "
|
|
147
|
+
"more restrictive claim that conflicts with what iedeveci/WillowNLtoFOL "
|
|
148
|
+
"publishes today; this record reflects what is verified AT THAT "
|
|
149
|
+
"PRIMARY SOURCE, not the GROVES card's claim about an earlier state "
|
|
150
|
+
"of it. Explicit usage permission for the GROVES/unicode-fol-kit use "
|
|
151
|
+
"(this kit, under the name it had until 0.30.0) "
|
|
152
|
+
"was additionally obtained from the Willow authors (per this "
|
|
153
|
+
"repository's author, 2026-08-12). Third parties should confirm the "
|
|
154
|
+
"applicable license themselves before redistributing."
|
|
155
|
+
),
|
|
156
|
+
source_url="https://huggingface.co/datasets/iedeveci/WillowNLtoFOL",
|
|
157
|
+
citation_hint=(
|
|
158
|
+
"Deveci, İbrahim Ethem. \"A Diagnostic Framework for "
|
|
159
|
+
"Compositional Generalization via NL-to-FOL Translation.\" Journal "
|
|
160
|
+
"of Logic, Language and Information (2026). "
|
|
161
|
+
"https://doi.org/10.1007/s10849-026-09480-0"
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# --------------------------------------------------------------------------- #
|
|
167
|
+
# The Willow repair dialect: an import-time grammar for the ~1.2% tail
|
|
168
|
+
#
|
|
169
|
+
# ~98.8% of WillowNLtoFOL's gold formulas already parse under this kit's own
|
|
170
|
+
# grammar (measured — see the module docstring), so the loader touches
|
|
171
|
+
# nothing for those. The measured failure tail has exactly two structural
|
|
172
|
+
# causes, and this dedicated grammar exists to read PRECISELY those:
|
|
173
|
+
#
|
|
174
|
+
# 1. ∧/∨/⊕ mixed at one nesting depth without parentheses. The kit's own
|
|
175
|
+
# grammar refuses to guess a precedence; Willow's formulas were filtered
|
|
176
|
+
# through ``nltk.sem.logic``, whose convention is that ∧ binds tighter
|
|
177
|
+
# than ∨ — so THIS grammar adopts the textbook/NLTK precedence
|
|
178
|
+
# ¬ > ∧ > ∨ > ⊕ > → (right-assoc) > ↔, i.e. the reading the dataset's
|
|
179
|
+
# own pipeline accepted. (∨-vs-⊕ mixing without parens never occurred
|
|
180
|
+
# in the measured sample; the relative order chosen here is documented,
|
|
181
|
+
# not observed.)
|
|
182
|
+
# 2. Predicate identifiers outside the kit's ``[A-Z][a-zA-Z0-9]*`` class
|
|
183
|
+
# (``iOS``, ``Café``). The converter renames minimally: NFKD
|
|
184
|
+
# transliteration for non-ASCII letters (``Café`` → ``Cafe``), then the
|
|
185
|
+
# first letter upcased (``iOS`` → ``IOS``) — injectively per namespace,
|
|
186
|
+
# with the mapping recorded in ``meta``.
|
|
187
|
+
#
|
|
188
|
+
# The loader (``convert_fol=True``, the default) first tries ``api.parse_any``
|
|
189
|
+
# — a formula the kit already accepts stays byte-for-byte verbatim — and only
|
|
190
|
+
# a formula the kit refuses goes through this repair grammar; whatever fails
|
|
191
|
+
# BOTH keeps its verbatim string plus a recorded ``fol_conversion_error``
|
|
192
|
+
# (e.g. a genuinely malformed formula), never a crash or a silent drop.
|
|
193
|
+
# --------------------------------------------------------------------------- #
|
|
194
|
+
|
|
195
|
+
_WILLOW_GRAMMAR = r"""
|
|
196
|
+
?start: formula
|
|
197
|
+
?formula: iff
|
|
198
|
+
?iff: implies ("↔" implies)*
|
|
199
|
+
?implies: xor "→" implies -> implies
|
|
200
|
+
| xor
|
|
201
|
+
?xor: disj ("⊕" disj)*
|
|
202
|
+
?disj: conj ("∨" conj)*
|
|
203
|
+
?conj: unary ("∧" unary)*
|
|
204
|
+
?unary: "¬" unary -> neg
|
|
205
|
+
| "∀" IDENT unary -> forall
|
|
206
|
+
| "∃" IDENT unary -> exists
|
|
207
|
+
| atom
|
|
208
|
+
| "(" formula ")"
|
|
209
|
+
atom: IDENT "(" term ("," term)* ")"
|
|
210
|
+
?term: IDENT "(" term ("," term)* ")" -> func
|
|
211
|
+
| IDENT -> term_ident
|
|
212
|
+
IDENT: /[^\W\d]\w*/
|
|
213
|
+
%import common.WS
|
|
214
|
+
%ignore WS
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
_KIT_PREDICATE_RE = re.compile(r"[A-Z][a-zA-Z0-9]*\Z")
|
|
218
|
+
_KIT_CONSTANT_RE = re.compile(r"[a-z][a-zA-Z0-9]*[a-zA-Z][a-zA-Z0-9]*\Z")
|
|
219
|
+
_KIT_VARIABLE_RE = re.compile(r"[a-z][0-9]*\Z")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _ascii_fold(name: str) -> str:
|
|
223
|
+
"""NFKD-transliterate ``name`` to its ASCII-alnum core (``Café``→``Cafe``).
|
|
224
|
+
|
|
225
|
+
Raises ``ValueError`` when nothing usable survives — a name with no Latin
|
|
226
|
+
core has no honest ASCII rendering, and inventing one would make the
|
|
227
|
+
recorded mapping unreadable.
|
|
228
|
+
"""
|
|
229
|
+
folded = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode()
|
|
230
|
+
folded = re.sub(r"[^A-Za-z0-9]", "", folded)
|
|
231
|
+
if not folded or folded[0].isdigit():
|
|
232
|
+
raise ValueError(
|
|
233
|
+
f"willow: identifier {name!r} has no usable ASCII-letter core "
|
|
234
|
+
"to transliterate to — refusing to invent a name.")
|
|
235
|
+
return folded
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class _WillowNameConverter:
|
|
239
|
+
"""Minimal, injective renaming into kit conventions (see dialect block)."""
|
|
240
|
+
|
|
241
|
+
def __init__(self) -> None:
|
|
242
|
+
self.pred_map: Dict[str, str] = {}
|
|
243
|
+
self.term_map: Dict[str, str] = {}
|
|
244
|
+
self._final: Dict[str, Dict[str, str]] = {"predicate": {}, "term": {}}
|
|
245
|
+
|
|
246
|
+
def _claim(self, namespace: str, source: str, target: str) -> str:
|
|
247
|
+
owner = self._final[namespace].setdefault(target, source)
|
|
248
|
+
if owner != source:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"willow: {namespace} names {owner!r} and {source!r} would "
|
|
251
|
+
f"both end up as {target!r} — the renaming must stay "
|
|
252
|
+
"injective, so this formula cannot be repaired.")
|
|
253
|
+
return target
|
|
254
|
+
|
|
255
|
+
def predicate(self, name: str) -> str:
|
|
256
|
+
if _KIT_PREDICATE_RE.match(name):
|
|
257
|
+
return self._claim("predicate", name, name)
|
|
258
|
+
folded = _ascii_fold(name)
|
|
259
|
+
target = folded[0].upper() + folded[1:]
|
|
260
|
+
if not _KIT_PREDICATE_RE.match(target):
|
|
261
|
+
raise ValueError(
|
|
262
|
+
f"willow: predicate {name!r} does not repair to a legal kit "
|
|
263
|
+
f"predicate (got {target!r}).")
|
|
264
|
+
self._claim("predicate", name, target)
|
|
265
|
+
self.pred_map[name] = target
|
|
266
|
+
return target
|
|
267
|
+
|
|
268
|
+
def term_name(self, name: str) -> str:
|
|
269
|
+
"""A constant or function name (both live in the kit's NAME space)."""
|
|
270
|
+
if _KIT_CONSTANT_RE.match(name) or _KIT_VARIABLE_RE.match(name):
|
|
271
|
+
return name
|
|
272
|
+
folded = _ascii_fold(name)
|
|
273
|
+
target = folded[0].lower() + folded[1:]
|
|
274
|
+
if _KIT_VARIABLE_RE.match(target):
|
|
275
|
+
raise ValueError(
|
|
276
|
+
f"willow: term name {name!r} would repair to {target!r}, "
|
|
277
|
+
"which the kit lexes as a VARIABLE — refusing the silent "
|
|
278
|
+
"semantics change.")
|
|
279
|
+
if not _KIT_CONSTANT_RE.match(target):
|
|
280
|
+
raise ValueError(
|
|
281
|
+
f"willow: term name {name!r} does not repair to a legal kit "
|
|
282
|
+
f"constant (got {target!r}).")
|
|
283
|
+
self._claim("term", name, target)
|
|
284
|
+
self.term_map[name] = target
|
|
285
|
+
return target
|
|
286
|
+
|
|
287
|
+
def mapping(self) -> Dict[str, Dict[str, str]]:
|
|
288
|
+
return {"predicates": dict(self.pred_map), "terms": dict(self.term_map)}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class _WillowTransformer(Transformer):
|
|
292
|
+
"""Lark tree → kit :class:`~unicode_logic_kit.fol.nodes.Node`."""
|
|
293
|
+
|
|
294
|
+
def __init__(self, converter: _WillowNameConverter) -> None:
|
|
295
|
+
super().__init__()
|
|
296
|
+
self._conv = converter
|
|
297
|
+
|
|
298
|
+
def term_ident(self, children) -> Node:
|
|
299
|
+
name = str(children[0])
|
|
300
|
+
if _KIT_VARIABLE_RE.match(name):
|
|
301
|
+
return Variable(name)
|
|
302
|
+
return Constant(self._conv.term_name(name))
|
|
303
|
+
|
|
304
|
+
def func(self, children) -> Node:
|
|
305
|
+
name = self._conv.term_name(str(children[0]))
|
|
306
|
+
return Function(name, tuple(children[1:]))
|
|
307
|
+
|
|
308
|
+
def atom(self, children) -> Node:
|
|
309
|
+
pred = self._conv.predicate(str(children[0]))
|
|
310
|
+
return Atom(pred, tuple(children[1:]))
|
|
311
|
+
|
|
312
|
+
def neg(self, children) -> Node:
|
|
313
|
+
return Not(children[0])
|
|
314
|
+
|
|
315
|
+
def _binder(self, token) -> Variable:
|
|
316
|
+
name = str(token)
|
|
317
|
+
if not _KIT_VARIABLE_RE.match(name):
|
|
318
|
+
raise ValueError(
|
|
319
|
+
f"willow: quantified variable {name!r} is not a kit variable "
|
|
320
|
+
"token ([a-z][0-9]*) — refusing to rename a BOUND name.")
|
|
321
|
+
return Variable(name)
|
|
322
|
+
|
|
323
|
+
def forall(self, children) -> Node:
|
|
324
|
+
return Quantifier("∀", self._binder(children[0]), children[1])
|
|
325
|
+
|
|
326
|
+
def exists(self, children) -> Node:
|
|
327
|
+
return Quantifier("∃", self._binder(children[0]), children[1])
|
|
328
|
+
|
|
329
|
+
def _fold(self, children, cls) -> Node:
|
|
330
|
+
node = children[0]
|
|
331
|
+
for right in children[1:]:
|
|
332
|
+
node = cls(node, right)
|
|
333
|
+
return node
|
|
334
|
+
|
|
335
|
+
def conj(self, children) -> Node:
|
|
336
|
+
return self._fold(children, And)
|
|
337
|
+
|
|
338
|
+
def disj(self, children) -> Node:
|
|
339
|
+
return self._fold(children, Or)
|
|
340
|
+
|
|
341
|
+
def xor(self, children) -> Node:
|
|
342
|
+
return self._fold(children, Xor)
|
|
343
|
+
|
|
344
|
+
def implies(self, children) -> Node:
|
|
345
|
+
if len(children) == 1:
|
|
346
|
+
return children[0]
|
|
347
|
+
return Implies(children[0], children[1])
|
|
348
|
+
|
|
349
|
+
def iff(self, children) -> Node:
|
|
350
|
+
return self._fold(children, Iff)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
_willow_parser = Lark(_WILLOW_GRAMMAR, parser="lalr")
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def repair_willow_formula(text: str) -> Tuple[Node, Dict[str, Dict[str, str]]]:
|
|
357
|
+
"""Parse one kit-refused Willow formula with the repair dialect grammar.
|
|
358
|
+
|
|
359
|
+
Returns ``(node, mapping)`` — the kit AST (NLTK-precedence reading, names
|
|
360
|
+
repaired into kit conventions) and the changed-names-only mapping. The
|
|
361
|
+
node is guaranteed CLOSED; a free variable raises.
|
|
362
|
+
|
|
363
|
+
Raises:
|
|
364
|
+
lark.exceptions.LarkError: ``text`` is outside even the repair
|
|
365
|
+
dialect (genuinely malformed).
|
|
366
|
+
ValueError: an identifier cannot be repaired without a collision or
|
|
367
|
+
a constant→variable semantics change, or the result is not
|
|
368
|
+
closed.
|
|
369
|
+
"""
|
|
370
|
+
conv = _WillowNameConverter()
|
|
371
|
+
tree = _willow_parser.parse(text)
|
|
372
|
+
try:
|
|
373
|
+
node = _WillowTransformer(conv).transform(tree)
|
|
374
|
+
except VisitError as exc:
|
|
375
|
+
if isinstance(exc.orig_exc, ValueError):
|
|
376
|
+
raise exc.orig_exc from None
|
|
377
|
+
raise
|
|
378
|
+
free = free_variables(node)
|
|
379
|
+
if free:
|
|
380
|
+
raise ValueError(
|
|
381
|
+
f"willow: repaired formula has free variable(s) {sorted(free)!r} "
|
|
382
|
+
f"— refusing (source: {text!r}).")
|
|
383
|
+
return node, conv.mapping()
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _example_from_record(record: dict, line_no: int,
|
|
387
|
+
known_bad_ids: FrozenSet[str],
|
|
388
|
+
convert_fol: bool) -> DatasetExample:
|
|
389
|
+
nl = record.get("NL_sentence")
|
|
390
|
+
fol = record.get("FOL_expression")
|
|
391
|
+
# WillowNLtoFOL has no native id field (see module docstring); some
|
|
392
|
+
# downstream re-export might add one, so honour it opportunistically
|
|
393
|
+
# before falling back to a positional id (same convention as malls.py).
|
|
394
|
+
raw_id = record.get("id")
|
|
395
|
+
example_id = str(raw_id) if raw_id is not None else f"willow:{line_no}"
|
|
396
|
+
|
|
397
|
+
meta = {k: v for k, v in record.items()
|
|
398
|
+
if k not in ("NL_sentence", "FOL_expression", "id")}
|
|
399
|
+
meta["line_no"] = line_no
|
|
400
|
+
|
|
401
|
+
if convert_fol and fol is not None:
|
|
402
|
+
from ... import api # lazy: same cost/cycle rationale as _base
|
|
403
|
+
|
|
404
|
+
if not api.parse_any(fol).ok:
|
|
405
|
+
try:
|
|
406
|
+
node, mapping = repair_willow_formula(fol)
|
|
407
|
+
except (LarkError, ValueError) as exc:
|
|
408
|
+
# Honest per-example fallback: verbatim string kept, failure
|
|
409
|
+
# recorded — never swallowed, never a crash of the load.
|
|
410
|
+
meta["fol_conversion_error"] = f"{type(exc).__name__}: {exc}"
|
|
411
|
+
else:
|
|
412
|
+
meta["original_fol_conclusion"] = fol
|
|
413
|
+
meta["fol_name_mapping"] = mapping
|
|
414
|
+
fol = node.to_unicode_str()
|
|
415
|
+
|
|
416
|
+
return DatasetExample(
|
|
417
|
+
id=example_id,
|
|
418
|
+
nl_premises=(),
|
|
419
|
+
fol_premises=(),
|
|
420
|
+
nl_conclusion=nl,
|
|
421
|
+
fol_conclusion=fol,
|
|
422
|
+
label=None,
|
|
423
|
+
known_bad=example_id in known_bad_ids,
|
|
424
|
+
meta=meta,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def load_willow(path: Union[str, Path], *,
|
|
429
|
+
known_bad_ids: FrozenSet[str] = frozenset(),
|
|
430
|
+
convert_fol: bool = True) -> Iterator[DatasetExample]:
|
|
431
|
+
"""Stream :class:`~unicode_logic_kit.eval.datasets.DatasetExample` from a
|
|
432
|
+
local WillowNLtoFOL JSONL file.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
path: path to a local ``.jsonl`` file — one
|
|
436
|
+
``{"NL_sentence": ..., "FOL_expression": ...}`` object per
|
|
437
|
+
non-blank line (see module docstring for converting the upstream
|
|
438
|
+
Hugging Face parquet distribution to this format). NEVER
|
|
439
|
+
downloaded by this function.
|
|
440
|
+
known_bad_ids: ids (the record's own ``"id"`` if present, else the
|
|
441
|
+
positional fallback ``f"willow:{line_no}"``) whose
|
|
442
|
+
``"FOL_expression"`` translation is known to be broken. Every
|
|
443
|
+
yielded example with a matching id gets ``known_bad=True``.
|
|
444
|
+
Defaults to an empty set.
|
|
445
|
+
convert_fol: with the default ``True``, a formula the kit's own
|
|
446
|
+
grammar already accepts stays byte-for-byte verbatim, and ONLY a
|
|
447
|
+
kit-refused formula goes through the repair dialect grammar
|
|
448
|
+
(NLTK-precedence reading of unparenthesised ∧/∨/⊕ mixes, minimal
|
|
449
|
+
injective renaming of out-of-class identifiers — see the dialect
|
|
450
|
+
comment block); a repaired example records the verbatim original
|
|
451
|
+
in ``meta["original_fol_conclusion"]`` and the changed names in
|
|
452
|
+
``meta["fol_name_mapping"]``, and a formula neither route can
|
|
453
|
+
read keeps its verbatim string plus
|
|
454
|
+
``meta["fol_conversion_error"]``. ``False`` restores the raw
|
|
455
|
+
pass-through (~98.8% kit-parse rate, see module docstring).
|
|
456
|
+
|
|
457
|
+
Yields:
|
|
458
|
+
One :class:`~unicode_logic_kit.eval.datasets.DatasetExample` per
|
|
459
|
+
non-blank JSONL line, in file order, with ``nl_conclusion``/
|
|
460
|
+
``fol_conclusion`` set from ``"NL_sentence"``/``"FOL_expression"`` and
|
|
461
|
+
``nl_premises``/``fol_premises`` empty (see module docstring for why
|
|
462
|
+
this dataset maps onto the "conclusion" slot, matching
|
|
463
|
+
:func:`~unicode_logic_kit.eval.datasets.malls.load_malls`).
|
|
464
|
+
|
|
465
|
+
Raises:
|
|
466
|
+
FileNotFoundError: ``path`` does not exist.
|
|
467
|
+
json.JSONDecodeError: a non-blank line is not valid JSON — raised,
|
|
468
|
+
not swallowed (a malformed dataset file must fail loudly).
|
|
469
|
+
"""
|
|
470
|
+
path = Path(path)
|
|
471
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
472
|
+
for line_no, raw_line in enumerate(fh):
|
|
473
|
+
line = raw_line.strip()
|
|
474
|
+
if not line:
|
|
475
|
+
continue
|
|
476
|
+
record = json.loads(line)
|
|
477
|
+
yield _example_from_record(record, line_no, known_bad_ids,
|
|
478
|
+
convert_fol)
|