hylogic 1.0.0__tar.gz
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.
- hylogic-1.0.0/LICENSE +21 -0
- hylogic-1.0.0/PKG-INFO +352 -0
- hylogic-1.0.0/README.md +318 -0
- hylogic-1.0.0/hylogic/__init__.py +42 -0
- hylogic-1.0.0/hylogic/argument.hy +124 -0
- hylogic-1.0.0/hylogic/connectives.hy +70 -0
- hylogic-1.0.0/hylogic/evaluate.hy +71 -0
- hylogic-1.0.0/hylogic/exercise_sets.hy +95 -0
- hylogic-1.0.0/hylogic/exercises.hy +96 -0
- hylogic-1.0.0/hylogic/export.hy +143 -0
- hylogic-1.0.0/hylogic/formulas.hy +129 -0
- hylogic-1.0.0/hylogic/ipython.py +44 -0
- hylogic-1.0.0/hylogic/mcp_server.py +118 -0
- hylogic-1.0.0/hylogic/models.hy +122 -0
- hylogic-1.0.0/hylogic/problems.hy +98 -0
- hylogic-1.0.0/hylogic/quantifiers.hy +22 -0
- hylogic-1.0.0/hylogic/resolution.hy +287 -0
- hylogic-1.0.0/hylogic/rewrite.hy +92 -0
- hylogic-1.0.0/hylogic/sat.hy +141 -0
- hylogic-1.0.0/hylogic/session.py +31 -0
- hylogic-1.0.0/hylogic/terms.hy +43 -0
- hylogic-1.0.0/hylogic/truth.hy +106 -0
- hylogic-1.0.0/hylogic/unify.hy +84 -0
- hylogic-1.0.0/hylogic.egg-info/PKG-INFO +352 -0
- hylogic-1.0.0/hylogic.egg-info/SOURCES.txt +31 -0
- hylogic-1.0.0/hylogic.egg-info/dependency_links.txt +1 -0
- hylogic-1.0.0/hylogic.egg-info/entry_points.txt +2 -0
- hylogic-1.0.0/hylogic.egg-info/requires.txt +20 -0
- hylogic-1.0.0/hylogic.egg-info/top_level.txt +1 -0
- hylogic-1.0.0/pyproject.toml +49 -0
- hylogic-1.0.0/setup.cfg +4 -0
- hylogic-1.0.0/test/test_mcp.py +48 -0
- hylogic-1.0.0/test/test_notebook.py +21 -0
hylogic-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Marko Manninen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hylogic-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hylogic
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Propositional and first-order logic in Hy: truth tables, arguments, models and a resolution prover
|
|
5
|
+
Author-email: Marko Manninen <elonmedia@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/markomanninen/hylogic
|
|
8
|
+
Keywords: hylang,lisp,logic,propositional logic,first-order logic,truth table,theorem prover,resolution,unification
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Education
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Lisp
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: hy>=1.0
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
22
|
+
Requires-Dist: ipython; extra == "test"
|
|
23
|
+
Requires-Dist: ipykernel; extra == "test"
|
|
24
|
+
Requires-Dist: nbclient; extra == "test"
|
|
25
|
+
Requires-Dist: nbformat; extra == "test"
|
|
26
|
+
Requires-Dist: mcp<2,>=1.2; python_version >= "3.10" and extra == "test"
|
|
27
|
+
Provides-Extra: notebook
|
|
28
|
+
Requires-Dist: ipython; extra == "notebook"
|
|
29
|
+
Provides-Extra: mcp
|
|
30
|
+
Requires-Dist: mcp<2,>=1.2; extra == "mcp"
|
|
31
|
+
Provides-Extra: z3
|
|
32
|
+
Requires-Dist: z3-solver; extra == "z3"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# Hylogic
|
|
36
|
+
|
|
37
|
+
Propositional and first-order logic in [Hy](https://hylang.org), a Lisp that
|
|
38
|
+
compiles to Python. Write formulas as Lisp code with the usual logical
|
|
39
|
+
symbols, then check them with truth tables, evaluate them in a finite model,
|
|
40
|
+
test whether an argument is valid, or prove them with a small resolution
|
|
41
|
+
prover that prints its proofs.
|
|
42
|
+
|
|
43
|
+
```hy
|
|
44
|
+
(import hylogic [truth-table prove])
|
|
45
|
+
|
|
46
|
+
(print (truth-table '(→ (∧ P (→ P Q)) Q)))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
P │ Q │ (P ∧ (P → Q)) → Q
|
|
51
|
+
──┼───┼──────────────────
|
|
52
|
+
⊤ │ ⊤ │ ⊤
|
|
53
|
+
⊤ │ ⊥ │ ⊤
|
|
54
|
+
⊥ │ ⊤ │ ⊤
|
|
55
|
+
⊥ │ ⊥ │ ⊤
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Hylogic is meant for learning and experimenting: the code is short enough to
|
|
59
|
+
read, and every step of a proof can be printed. For serious proofs, use
|
|
60
|
+
[Lean](https://lean-lang.org/) or an automated prover such as Vampire or E.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install hylogic
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Hylogic needs Python 3.9 or newer and Hy 1.0 or newer. To run the tests and
|
|
69
|
+
the tour from a clone:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install -e ".[test]"
|
|
73
|
+
pytest
|
|
74
|
+
hy examples/tour.hy
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Formulas
|
|
78
|
+
|
|
79
|
+
A formula is quoted Hy code. Atoms are propositions (`P`) or predicates
|
|
80
|
+
applied to terms (`(Loves x (mother x))`). Every connective has a symbol and
|
|
81
|
+
an ASCII name, and both can be mixed freely:
|
|
82
|
+
|
|
83
|
+
| Symbol | ASCII | Name | Arguments |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `¬` | `not` | Negation | 1 |
|
|
86
|
+
| `∧` | `and` | Conjunction | any number |
|
|
87
|
+
| `∨` | `or` | Disjunction | any number |
|
|
88
|
+
| `↑` | `nand` | Nonconjunction | any number |
|
|
89
|
+
| `↓` | `nor` | Nondisjunction | any number |
|
|
90
|
+
| `⊕` `↮` | `xor` | Exclusive or, true for an odd number of true arguments | any number |
|
|
91
|
+
| `≡` | `eqv` | Equivalence, true when all arguments agree | any number |
|
|
92
|
+
| `≢` | `neqv` | Nonequivalence | any number |
|
|
93
|
+
| `↔` | `<=>` | Biconditional | 2 |
|
|
94
|
+
| `→` | `=>` | Material implication | 2 |
|
|
95
|
+
| `←` | `<=` | Converse implication | 2 |
|
|
96
|
+
| `↛` | `nimp` | Material nonimplication | 2 |
|
|
97
|
+
| `↚` | `cnimp` | Converse nonimplication | 2 |
|
|
98
|
+
| `∀` | `forall` | Universal quantifier, `(∀ [x y] body)` | |
|
|
99
|
+
| `∃` | `exists` | Existential quantifier | |
|
|
100
|
+
| `⊤` `⊥` | `true` `false` | The constants | |
|
|
101
|
+
|
|
102
|
+
`pretty` prints a formula in the usual notation:
|
|
103
|
+
|
|
104
|
+
```hy
|
|
105
|
+
(import hylogic [pretty])
|
|
106
|
+
(pretty '(forall [x] (=> (Man x) (Mortal x)))) ; "∀x ((Man x) → (Mortal x))"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Truth tables
|
|
110
|
+
|
|
111
|
+
```hy
|
|
112
|
+
(import hylogic.truth [truth-table tautology? satisfiable? equivalent? entails? counterexample])
|
|
113
|
+
|
|
114
|
+
(tautology? '(=> (=> (=> p q) p) p)) ; Peirce's law: True
|
|
115
|
+
(equivalent? '(¬ (∧ P Q)) '(∨ (¬ P) (¬ Q))) ; De Morgan: True
|
|
116
|
+
(entails? ['(→ P Q) '(¬ Q)] '(¬ P)) ; modus tollens: True
|
|
117
|
+
(counterexample ['(→ P Q) 'Q] 'P) ; {"P" False "Q" True}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`truth-table` takes several formulas and shows them side by side. In a
|
|
121
|
+
Jupyter notebook it renders as an HTML table. `(print (connective-tables))`
|
|
122
|
+
prints the tables of all thirteen connectives.
|
|
123
|
+
|
|
124
|
+
## Arguments
|
|
125
|
+
|
|
126
|
+
`defargument` takes premises and a conclusion as written, without quoting,
|
|
127
|
+
and decides validity: by truth table when the argument is propositional, and
|
|
128
|
+
with the prover when it has quantifiers or predicates.
|
|
129
|
+
|
|
130
|
+
```hy
|
|
131
|
+
(require hylogic.argument [defargument])
|
|
132
|
+
|
|
133
|
+
(defargument modus-tollens
|
|
134
|
+
:premises [(→ P Q) (¬ Q)]
|
|
135
|
+
:conclusion (¬ P)
|
|
136
|
+
:meanings {P "It rains" Q "The street is wet"})
|
|
137
|
+
|
|
138
|
+
(print (.explain modus-tollens))
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
P → Q
|
|
143
|
+
¬Q
|
|
144
|
+
───────
|
|
145
|
+
∴ ¬P
|
|
146
|
+
|
|
147
|
+
P: It rains
|
|
148
|
+
Q: The street is wet
|
|
149
|
+
|
|
150
|
+
Valid, by truth table.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
An invalid argument comes with a counterexample, and `Argument` builds the
|
|
154
|
+
same object from quoted formulas:
|
|
155
|
+
|
|
156
|
+
```hy
|
|
157
|
+
(import hylogic [Argument])
|
|
158
|
+
(.explain (Argument ['(→ P Q) 'Q] 'P))
|
|
159
|
+
; Invalid, by truth table.
|
|
160
|
+
; Counterexample: P = ⊥, Q = ⊤
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Models
|
|
164
|
+
|
|
165
|
+
`evaluate` finds the truth value of a formula in a model. Predicates are
|
|
166
|
+
Python functions or collections of the tuples they hold for, and quantifiers
|
|
167
|
+
range over a finite domain:
|
|
168
|
+
|
|
169
|
+
```hy
|
|
170
|
+
(import hylogic [evaluate])
|
|
171
|
+
|
|
172
|
+
(setv model {"Man" #{"socrates" "plato"}
|
|
173
|
+
"Mortal" #{"socrates" "plato" "fido"}})
|
|
174
|
+
(evaluate '(∀ [x] (→ (Man x) (Mortal x))) model ["socrates" "plato" "fido"]) ; True
|
|
175
|
+
(evaluate '(∀ [x] (→ (Mortal x) (Man x))) model ["socrates" "plato" "fido"]) ; False
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
For quantifiers over ordinary Python data, there are macros:
|
|
179
|
+
|
|
180
|
+
```hy
|
|
181
|
+
(require hylogic.quantifiers [∀ ∃])
|
|
182
|
+
(∀ [x (range 1 10)] (> x 0)) ; True
|
|
183
|
+
(∃ [x [1 2] y [-1 -2]] (= (+ x y) 0)) ; True, with x = 1, y = -1
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Proofs
|
|
187
|
+
|
|
188
|
+
`prove` refutes the negated goal together with the axioms by resolution, and
|
|
189
|
+
returns a result whose `proof` lists every step:
|
|
190
|
+
|
|
191
|
+
```hy
|
|
192
|
+
(import hylogic [prove])
|
|
193
|
+
|
|
194
|
+
(setv result (prove '(Kills curiosity tuna)
|
|
195
|
+
['(∃ [x] (∧ (Dog x) (Owns jack x)))
|
|
196
|
+
'(∀ [x] (→ (∃ [y] (∧ (Dog y) (Owns x y))) (AnimalLover x)))
|
|
197
|
+
'(∀ [x] (→ (AnimalLover x) (∀ [y] (→ (Animal y) (¬ (Kills x y))))))
|
|
198
|
+
'(∨ (Kills jack tuna) (Kills curiosity tuna))
|
|
199
|
+
'(Cat tuna)
|
|
200
|
+
'(∀ [x] (→ (Cat x) (Animal x)))]))
|
|
201
|
+
(for [line (.proof result)] (print line))
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
1. (Dog sk1) axiom
|
|
206
|
+
...
|
|
207
|
+
8. (not (Kills curiosity tuna)) negated goal
|
|
208
|
+
10. (Kills jack tuna) resolve 5, 8
|
|
209
|
+
13. (AnimalLover jack) resolve 12, 1
|
|
210
|
+
21. (not (AnimalLover jack)) resolve 16, 9
|
|
211
|
+
26. □ resolve 21, 13
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`result.status` is `proved`, `saturated` (the goal does not follow) or
|
|
215
|
+
`gave-up` (the clause limit, `:limit 5000` by default, was reached). The
|
|
216
|
+
prover proves Pelletier's problems 1 to 24 in milliseconds and problem 34
|
|
217
|
+
(Andrews' challenge) in about five seconds. [doc/prover.md](doc/prover.md)
|
|
218
|
+
explains how it works, step by step, with exercises.
|
|
219
|
+
|
|
220
|
+
Term rewriting is included too:
|
|
221
|
+
|
|
222
|
+
```hy
|
|
223
|
+
(require hylogic.rewrite [defrules])
|
|
224
|
+
(import hylogic [normalize])
|
|
225
|
+
|
|
226
|
+
(defrules PEANO
|
|
227
|
+
(+ 0 ?y) -> ?y
|
|
228
|
+
(+ (s ?x) ?y) -> (s (+ ?x ?y)))
|
|
229
|
+
|
|
230
|
+
(get (normalize '(+ (s (s 0)) (s 0)) PEANO) 0) ; #("s" #("s" #("s" 0)))
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Teaching and exercises
|
|
234
|
+
|
|
235
|
+
`hylogic.exercises` checks the two classic kinds of exercise and explains a
|
|
236
|
+
wrong answer in the words of the exercise. A formalization is correct when it
|
|
237
|
+
is logically equivalent to the reference, however it is written. A wrong one
|
|
238
|
+
gets a situation where the two come apart:
|
|
239
|
+
|
|
240
|
+
```hy
|
|
241
|
+
(import hylogic.exercise-sets [check-answer])
|
|
242
|
+
|
|
243
|
+
(check-answer "F2" '(→ P Q)) ; "The street is wet only if it rains."
|
|
244
|
+
; Not quite. Suppose “it rains” is false and “the street is wet” is true.
|
|
245
|
+
; Then your formula is true, but the sentence is false.
|
|
246
|
+
|
|
247
|
+
(check-answer "V9" True) ; "Everybody loves somebody. So there is somebody whom everybody loves."
|
|
248
|
+
; Not quite. The argument is invalid. In this world the premises are true and the conclusion is false:
|
|
249
|
+
; Domain: d1, d2
|
|
250
|
+
; Loves holds for: (d1 d1), (d2 d2)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The exercise set has 16 sentences to formalize, propositional and
|
|
254
|
+
first-order, and 10 arguments to judge. `check-formalization` and
|
|
255
|
+
`check-validity-claim` work with your own exercises too, and a claim that an
|
|
256
|
+
argument is invalid can come with a counterexample, which is checked.
|
|
257
|
+
|
|
258
|
+
[Hylogic tutorial.ipynb](Hylogic%20tutorial.ipynb) walks through truth
|
|
259
|
+
tables, arguments, exercises, models and proofs. It runs on the ordinary
|
|
260
|
+
Python kernel with `%load_ext hylogic.ipython`, which adds a `%%hy` cell
|
|
261
|
+
magic.
|
|
262
|
+
|
|
263
|
+
## Larger problems, countermodels and other solvers
|
|
264
|
+
|
|
265
|
+
Truth tables double with every proposition. For larger propositional
|
|
266
|
+
problems, `satisfiable` and `valid?` in `hylogic.sat` use the Tseitin
|
|
267
|
+
transformation and the DPLL algorithm, which stay in milliseconds up to about
|
|
268
|
+
40 variables. Arguments switch to it on their own above 10 propositions.
|
|
269
|
+
|
|
270
|
+
```hy
|
|
271
|
+
(import hylogic.sat [satisfiable valid?])
|
|
272
|
+
(satisfiable '(∧ (∨ P Q) (¬ P))) ; {"P" False "Q" True}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
When a first-order argument is invalid, `countermodel` looks for a small
|
|
276
|
+
world that shows it, by grounding the formulas over a domain of up to four
|
|
277
|
+
elements and solving the result with DPLL:
|
|
278
|
+
|
|
279
|
+
```hy
|
|
280
|
+
(import hylogic [countermodel])
|
|
281
|
+
(print (countermodel ['(∀ [x] (∃ [y] (Loves x y)))] '(∃ [y] (∀ [x] (Loves x y)))))
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
Domain: d1, d2
|
|
286
|
+
Loves holds for: (d1 d1), (d2 d2)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Everybody loves somebody, here themselves, but nobody is loved by everybody.
|
|
290
|
+
|
|
291
|
+
For problems beyond hylogic's reach, `to-smtlib` and `to-tptp` write the same
|
|
292
|
+
question for real solvers such as Z3 and cvc5, or provers such as Vampire and
|
|
293
|
+
E, and `to-dimacs` writes the SAT clauses for any SAT solver. With
|
|
294
|
+
`pip install "hylogic[z3]"`, `check-with-z3` runs Z3 directly.
|
|
295
|
+
[benchmarks/](benchmarks/) compares the four methods on the same problems.
|
|
296
|
+
|
|
297
|
+
## For AI agents
|
|
298
|
+
|
|
299
|
+
`hylogic-mcp` is a [Model Context Protocol](https://modelcontextprotocol.io)
|
|
300
|
+
server with one tool, `check_argument`. An agent formalizes an argument,
|
|
301
|
+
calls the tool, and gets the verdict with a counterexample or countermodel
|
|
302
|
+
when the argument is invalid. Formulas can be s-expression strings or JSON
|
|
303
|
+
arrays, since models write JSON most reliably.
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
pip install "hylogic[mcp]"
|
|
307
|
+
claude mcp add hylogic -- hylogic-mcp
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
check_argument(premises=["(=> P Q)", "Q"], conclusion="P",
|
|
312
|
+
meanings={"P": "it rains", "Q": "the street is wet"})
|
|
313
|
+
|
|
314
|
+
INVALID (checked by truth table)
|
|
315
|
+
Read as: P → Q; Q ⊢ P
|
|
316
|
+
Counterexample: “it rains” is false and “the street is wet” is true. Then every premise is true and the conclusion is false.
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
[benchmarks/field_test/](benchmarks/field_test/) measures whether the tool
|
|
320
|
+
helps agents judge arguments, and what it costs.
|
|
321
|
+
|
|
322
|
+
## From Python
|
|
323
|
+
|
|
324
|
+
Formulas are nested tuples of strings, and the Hy names that end in `?` have
|
|
325
|
+
Python aliases:
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
import hylogic
|
|
329
|
+
|
|
330
|
+
hylogic.is_tautology(("or", "P", ("not", "P"))) # True
|
|
331
|
+
hylogic.entails([("=>", "P", "Q"), "P"], "Q") # True
|
|
332
|
+
hylogic.prove(("=>", ("forall", ("x",), ("P", "x")), ("P", "a"))).proved # True
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Tests
|
|
336
|
+
|
|
337
|
+
The tests check every connective against its definition, compare the
|
|
338
|
+
extended connectives with their rewritten forms on every assignment, and run
|
|
339
|
+
the prover on 300 random propositional formulas, where it must agree with
|
|
340
|
+
the truth tables. The first-order problems were also checked with the Z3
|
|
341
|
+
solver during development.
|
|
342
|
+
|
|
343
|
+
## History
|
|
344
|
+
|
|
345
|
+
Hylogic began in 2017 as a propositional logic evaluator for Hy 0.12 with an
|
|
346
|
+
infix notation. Version 1.0 is a rewrite for Hy 1.x: the infix notation is
|
|
347
|
+
gone, formulas are data, and the prover, models and arguments are new. The
|
|
348
|
+
2017 version is tagged `v0.1.0`.
|
|
349
|
+
|
|
350
|
+
## License
|
|
351
|
+
|
|
352
|
+
MIT
|
hylogic-1.0.0/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Hylogic
|
|
2
|
+
|
|
3
|
+
Propositional and first-order logic in [Hy](https://hylang.org), a Lisp that
|
|
4
|
+
compiles to Python. Write formulas as Lisp code with the usual logical
|
|
5
|
+
symbols, then check them with truth tables, evaluate them in a finite model,
|
|
6
|
+
test whether an argument is valid, or prove them with a small resolution
|
|
7
|
+
prover that prints its proofs.
|
|
8
|
+
|
|
9
|
+
```hy
|
|
10
|
+
(import hylogic [truth-table prove])
|
|
11
|
+
|
|
12
|
+
(print (truth-table '(→ (∧ P (→ P Q)) Q)))
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
P │ Q │ (P ∧ (P → Q)) → Q
|
|
17
|
+
──┼───┼──────────────────
|
|
18
|
+
⊤ │ ⊤ │ ⊤
|
|
19
|
+
⊤ │ ⊥ │ ⊤
|
|
20
|
+
⊥ │ ⊤ │ ⊤
|
|
21
|
+
⊥ │ ⊥ │ ⊤
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Hylogic is meant for learning and experimenting: the code is short enough to
|
|
25
|
+
read, and every step of a proof can be printed. For serious proofs, use
|
|
26
|
+
[Lean](https://lean-lang.org/) or an automated prover such as Vampire or E.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install hylogic
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Hylogic needs Python 3.9 or newer and Hy 1.0 or newer. To run the tests and
|
|
35
|
+
the tour from a clone:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install -e ".[test]"
|
|
39
|
+
pytest
|
|
40
|
+
hy examples/tour.hy
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Formulas
|
|
44
|
+
|
|
45
|
+
A formula is quoted Hy code. Atoms are propositions (`P`) or predicates
|
|
46
|
+
applied to terms (`(Loves x (mother x))`). Every connective has a symbol and
|
|
47
|
+
an ASCII name, and both can be mixed freely:
|
|
48
|
+
|
|
49
|
+
| Symbol | ASCII | Name | Arguments |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `¬` | `not` | Negation | 1 |
|
|
52
|
+
| `∧` | `and` | Conjunction | any number |
|
|
53
|
+
| `∨` | `or` | Disjunction | any number |
|
|
54
|
+
| `↑` | `nand` | Nonconjunction | any number |
|
|
55
|
+
| `↓` | `nor` | Nondisjunction | any number |
|
|
56
|
+
| `⊕` `↮` | `xor` | Exclusive or, true for an odd number of true arguments | any number |
|
|
57
|
+
| `≡` | `eqv` | Equivalence, true when all arguments agree | any number |
|
|
58
|
+
| `≢` | `neqv` | Nonequivalence | any number |
|
|
59
|
+
| `↔` | `<=>` | Biconditional | 2 |
|
|
60
|
+
| `→` | `=>` | Material implication | 2 |
|
|
61
|
+
| `←` | `<=` | Converse implication | 2 |
|
|
62
|
+
| `↛` | `nimp` | Material nonimplication | 2 |
|
|
63
|
+
| `↚` | `cnimp` | Converse nonimplication | 2 |
|
|
64
|
+
| `∀` | `forall` | Universal quantifier, `(∀ [x y] body)` | |
|
|
65
|
+
| `∃` | `exists` | Existential quantifier | |
|
|
66
|
+
| `⊤` `⊥` | `true` `false` | The constants | |
|
|
67
|
+
|
|
68
|
+
`pretty` prints a formula in the usual notation:
|
|
69
|
+
|
|
70
|
+
```hy
|
|
71
|
+
(import hylogic [pretty])
|
|
72
|
+
(pretty '(forall [x] (=> (Man x) (Mortal x)))) ; "∀x ((Man x) → (Mortal x))"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Truth tables
|
|
76
|
+
|
|
77
|
+
```hy
|
|
78
|
+
(import hylogic.truth [truth-table tautology? satisfiable? equivalent? entails? counterexample])
|
|
79
|
+
|
|
80
|
+
(tautology? '(=> (=> (=> p q) p) p)) ; Peirce's law: True
|
|
81
|
+
(equivalent? '(¬ (∧ P Q)) '(∨ (¬ P) (¬ Q))) ; De Morgan: True
|
|
82
|
+
(entails? ['(→ P Q) '(¬ Q)] '(¬ P)) ; modus tollens: True
|
|
83
|
+
(counterexample ['(→ P Q) 'Q] 'P) ; {"P" False "Q" True}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`truth-table` takes several formulas and shows them side by side. In a
|
|
87
|
+
Jupyter notebook it renders as an HTML table. `(print (connective-tables))`
|
|
88
|
+
prints the tables of all thirteen connectives.
|
|
89
|
+
|
|
90
|
+
## Arguments
|
|
91
|
+
|
|
92
|
+
`defargument` takes premises and a conclusion as written, without quoting,
|
|
93
|
+
and decides validity: by truth table when the argument is propositional, and
|
|
94
|
+
with the prover when it has quantifiers or predicates.
|
|
95
|
+
|
|
96
|
+
```hy
|
|
97
|
+
(require hylogic.argument [defargument])
|
|
98
|
+
|
|
99
|
+
(defargument modus-tollens
|
|
100
|
+
:premises [(→ P Q) (¬ Q)]
|
|
101
|
+
:conclusion (¬ P)
|
|
102
|
+
:meanings {P "It rains" Q "The street is wet"})
|
|
103
|
+
|
|
104
|
+
(print (.explain modus-tollens))
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
P → Q
|
|
109
|
+
¬Q
|
|
110
|
+
───────
|
|
111
|
+
∴ ¬P
|
|
112
|
+
|
|
113
|
+
P: It rains
|
|
114
|
+
Q: The street is wet
|
|
115
|
+
|
|
116
|
+
Valid, by truth table.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
An invalid argument comes with a counterexample, and `Argument` builds the
|
|
120
|
+
same object from quoted formulas:
|
|
121
|
+
|
|
122
|
+
```hy
|
|
123
|
+
(import hylogic [Argument])
|
|
124
|
+
(.explain (Argument ['(→ P Q) 'Q] 'P))
|
|
125
|
+
; Invalid, by truth table.
|
|
126
|
+
; Counterexample: P = ⊥, Q = ⊤
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Models
|
|
130
|
+
|
|
131
|
+
`evaluate` finds the truth value of a formula in a model. Predicates are
|
|
132
|
+
Python functions or collections of the tuples they hold for, and quantifiers
|
|
133
|
+
range over a finite domain:
|
|
134
|
+
|
|
135
|
+
```hy
|
|
136
|
+
(import hylogic [evaluate])
|
|
137
|
+
|
|
138
|
+
(setv model {"Man" #{"socrates" "plato"}
|
|
139
|
+
"Mortal" #{"socrates" "plato" "fido"}})
|
|
140
|
+
(evaluate '(∀ [x] (→ (Man x) (Mortal x))) model ["socrates" "plato" "fido"]) ; True
|
|
141
|
+
(evaluate '(∀ [x] (→ (Mortal x) (Man x))) model ["socrates" "plato" "fido"]) ; False
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
For quantifiers over ordinary Python data, there are macros:
|
|
145
|
+
|
|
146
|
+
```hy
|
|
147
|
+
(require hylogic.quantifiers [∀ ∃])
|
|
148
|
+
(∀ [x (range 1 10)] (> x 0)) ; True
|
|
149
|
+
(∃ [x [1 2] y [-1 -2]] (= (+ x y) 0)) ; True, with x = 1, y = -1
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Proofs
|
|
153
|
+
|
|
154
|
+
`prove` refutes the negated goal together with the axioms by resolution, and
|
|
155
|
+
returns a result whose `proof` lists every step:
|
|
156
|
+
|
|
157
|
+
```hy
|
|
158
|
+
(import hylogic [prove])
|
|
159
|
+
|
|
160
|
+
(setv result (prove '(Kills curiosity tuna)
|
|
161
|
+
['(∃ [x] (∧ (Dog x) (Owns jack x)))
|
|
162
|
+
'(∀ [x] (→ (∃ [y] (∧ (Dog y) (Owns x y))) (AnimalLover x)))
|
|
163
|
+
'(∀ [x] (→ (AnimalLover x) (∀ [y] (→ (Animal y) (¬ (Kills x y))))))
|
|
164
|
+
'(∨ (Kills jack tuna) (Kills curiosity tuna))
|
|
165
|
+
'(Cat tuna)
|
|
166
|
+
'(∀ [x] (→ (Cat x) (Animal x)))]))
|
|
167
|
+
(for [line (.proof result)] (print line))
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
1. (Dog sk1) axiom
|
|
172
|
+
...
|
|
173
|
+
8. (not (Kills curiosity tuna)) negated goal
|
|
174
|
+
10. (Kills jack tuna) resolve 5, 8
|
|
175
|
+
13. (AnimalLover jack) resolve 12, 1
|
|
176
|
+
21. (not (AnimalLover jack)) resolve 16, 9
|
|
177
|
+
26. □ resolve 21, 13
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`result.status` is `proved`, `saturated` (the goal does not follow) or
|
|
181
|
+
`gave-up` (the clause limit, `:limit 5000` by default, was reached). The
|
|
182
|
+
prover proves Pelletier's problems 1 to 24 in milliseconds and problem 34
|
|
183
|
+
(Andrews' challenge) in about five seconds. [doc/prover.md](doc/prover.md)
|
|
184
|
+
explains how it works, step by step, with exercises.
|
|
185
|
+
|
|
186
|
+
Term rewriting is included too:
|
|
187
|
+
|
|
188
|
+
```hy
|
|
189
|
+
(require hylogic.rewrite [defrules])
|
|
190
|
+
(import hylogic [normalize])
|
|
191
|
+
|
|
192
|
+
(defrules PEANO
|
|
193
|
+
(+ 0 ?y) -> ?y
|
|
194
|
+
(+ (s ?x) ?y) -> (s (+ ?x ?y)))
|
|
195
|
+
|
|
196
|
+
(get (normalize '(+ (s (s 0)) (s 0)) PEANO) 0) ; #("s" #("s" #("s" 0)))
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Teaching and exercises
|
|
200
|
+
|
|
201
|
+
`hylogic.exercises` checks the two classic kinds of exercise and explains a
|
|
202
|
+
wrong answer in the words of the exercise. A formalization is correct when it
|
|
203
|
+
is logically equivalent to the reference, however it is written. A wrong one
|
|
204
|
+
gets a situation where the two come apart:
|
|
205
|
+
|
|
206
|
+
```hy
|
|
207
|
+
(import hylogic.exercise-sets [check-answer])
|
|
208
|
+
|
|
209
|
+
(check-answer "F2" '(→ P Q)) ; "The street is wet only if it rains."
|
|
210
|
+
; Not quite. Suppose “it rains” is false and “the street is wet” is true.
|
|
211
|
+
; Then your formula is true, but the sentence is false.
|
|
212
|
+
|
|
213
|
+
(check-answer "V9" True) ; "Everybody loves somebody. So there is somebody whom everybody loves."
|
|
214
|
+
; Not quite. The argument is invalid. In this world the premises are true and the conclusion is false:
|
|
215
|
+
; Domain: d1, d2
|
|
216
|
+
; Loves holds for: (d1 d1), (d2 d2)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The exercise set has 16 sentences to formalize, propositional and
|
|
220
|
+
first-order, and 10 arguments to judge. `check-formalization` and
|
|
221
|
+
`check-validity-claim` work with your own exercises too, and a claim that an
|
|
222
|
+
argument is invalid can come with a counterexample, which is checked.
|
|
223
|
+
|
|
224
|
+
[Hylogic tutorial.ipynb](Hylogic%20tutorial.ipynb) walks through truth
|
|
225
|
+
tables, arguments, exercises, models and proofs. It runs on the ordinary
|
|
226
|
+
Python kernel with `%load_ext hylogic.ipython`, which adds a `%%hy` cell
|
|
227
|
+
magic.
|
|
228
|
+
|
|
229
|
+
## Larger problems, countermodels and other solvers
|
|
230
|
+
|
|
231
|
+
Truth tables double with every proposition. For larger propositional
|
|
232
|
+
problems, `satisfiable` and `valid?` in `hylogic.sat` use the Tseitin
|
|
233
|
+
transformation and the DPLL algorithm, which stay in milliseconds up to about
|
|
234
|
+
40 variables. Arguments switch to it on their own above 10 propositions.
|
|
235
|
+
|
|
236
|
+
```hy
|
|
237
|
+
(import hylogic.sat [satisfiable valid?])
|
|
238
|
+
(satisfiable '(∧ (∨ P Q) (¬ P))) ; {"P" False "Q" True}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
When a first-order argument is invalid, `countermodel` looks for a small
|
|
242
|
+
world that shows it, by grounding the formulas over a domain of up to four
|
|
243
|
+
elements and solving the result with DPLL:
|
|
244
|
+
|
|
245
|
+
```hy
|
|
246
|
+
(import hylogic [countermodel])
|
|
247
|
+
(print (countermodel ['(∀ [x] (∃ [y] (Loves x y)))] '(∃ [y] (∀ [x] (Loves x y)))))
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
Domain: d1, d2
|
|
252
|
+
Loves holds for: (d1 d1), (d2 d2)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Everybody loves somebody, here themselves, but nobody is loved by everybody.
|
|
256
|
+
|
|
257
|
+
For problems beyond hylogic's reach, `to-smtlib` and `to-tptp` write the same
|
|
258
|
+
question for real solvers such as Z3 and cvc5, or provers such as Vampire and
|
|
259
|
+
E, and `to-dimacs` writes the SAT clauses for any SAT solver. With
|
|
260
|
+
`pip install "hylogic[z3]"`, `check-with-z3` runs Z3 directly.
|
|
261
|
+
[benchmarks/](benchmarks/) compares the four methods on the same problems.
|
|
262
|
+
|
|
263
|
+
## For AI agents
|
|
264
|
+
|
|
265
|
+
`hylogic-mcp` is a [Model Context Protocol](https://modelcontextprotocol.io)
|
|
266
|
+
server with one tool, `check_argument`. An agent formalizes an argument,
|
|
267
|
+
calls the tool, and gets the verdict with a counterexample or countermodel
|
|
268
|
+
when the argument is invalid. Formulas can be s-expression strings or JSON
|
|
269
|
+
arrays, since models write JSON most reliably.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
pip install "hylogic[mcp]"
|
|
273
|
+
claude mcp add hylogic -- hylogic-mcp
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
check_argument(premises=["(=> P Q)", "Q"], conclusion="P",
|
|
278
|
+
meanings={"P": "it rains", "Q": "the street is wet"})
|
|
279
|
+
|
|
280
|
+
INVALID (checked by truth table)
|
|
281
|
+
Read as: P → Q; Q ⊢ P
|
|
282
|
+
Counterexample: “it rains” is false and “the street is wet” is true. Then every premise is true and the conclusion is false.
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
[benchmarks/field_test/](benchmarks/field_test/) measures whether the tool
|
|
286
|
+
helps agents judge arguments, and what it costs.
|
|
287
|
+
|
|
288
|
+
## From Python
|
|
289
|
+
|
|
290
|
+
Formulas are nested tuples of strings, and the Hy names that end in `?` have
|
|
291
|
+
Python aliases:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
import hylogic
|
|
295
|
+
|
|
296
|
+
hylogic.is_tautology(("or", "P", ("not", "P"))) # True
|
|
297
|
+
hylogic.entails([("=>", "P", "Q"), "P"], "Q") # True
|
|
298
|
+
hylogic.prove(("=>", ("forall", ("x",), ("P", "x")), ("P", "a"))).proved # True
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Tests
|
|
302
|
+
|
|
303
|
+
The tests check every connective against its definition, compare the
|
|
304
|
+
extended connectives with their rewritten forms on every assignment, and run
|
|
305
|
+
the prover on 300 random propositional formulas, where it must agree with
|
|
306
|
+
the truth tables. The first-order problems were also checked with the Z3
|
|
307
|
+
solver during development.
|
|
308
|
+
|
|
309
|
+
## History
|
|
310
|
+
|
|
311
|
+
Hylogic began in 2017 as a propositional logic evaluator for Hy 0.12 with an
|
|
312
|
+
infix notation. Version 1.0 is a rewrite for Hy 1.x: the infix notation is
|
|
313
|
+
gone, formulas are data, and the prover, models and arguments are new. The
|
|
314
|
+
2017 version is tagged `v0.1.0`.
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
MIT
|