typedlogic 0.0.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.
- typedlogic/__init__.py +53 -0
- typedlogic/builtins.py +48 -0
- typedlogic/cli.py +412 -0
- typedlogic/compiler.py +173 -0
- typedlogic/compilers/__init__.py +0 -0
- typedlogic/compilers/fol_compiler.py +44 -0
- typedlogic/compilers/prolog_compiler.py +69 -0
- typedlogic/compilers/prover9_compiler.py +42 -0
- typedlogic/compilers/sexpr_compiler.py +99 -0
- typedlogic/compilers/tptp_compiler.py +49 -0
- typedlogic/compilers/yaml_compiler.py +75 -0
- typedlogic/datamodel.py +1230 -0
- typedlogic/datamodels/__init__.py +0 -0
- typedlogic/datamodels/typesystem.py +181 -0
- typedlogic/decorators.py +148 -0
- typedlogic/evaluation.py +207 -0
- typedlogic/extensions/__init__.py +0 -0
- typedlogic/extensions/probabilistic.py +56 -0
- typedlogic/generators.py +95 -0
- typedlogic/integrations/__init__.py +0 -0
- typedlogic/integrations/data/__init__.py +0 -0
- typedlogic/integrations/data/dask/__init__.py +0 -0
- typedlogic/integrations/data/ibis/__init__.py +0 -0
- typedlogic/integrations/data/pandas/__init__.py +0 -0
- typedlogic/integrations/data/polars/__init__.py +0 -0
- typedlogic/integrations/data/sqlmodel/__init__.py +0 -0
- typedlogic/integrations/frameworks/__init__.py +0 -0
- typedlogic/integrations/frameworks/hornedowl/__init__.py +0 -0
- typedlogic/integrations/frameworks/hornedowl/horned_owl_bridge.py +448 -0
- typedlogic/integrations/frameworks/hornedowl/owl_compiler.py +38 -0
- typedlogic/integrations/frameworks/hornedowl/owl_parser.py +68 -0
- typedlogic/integrations/frameworks/linkml/__init__.py +1 -0
- typedlogic/integrations/frameworks/linkml/instance.py +260 -0
- typedlogic/integrations/frameworks/linkml/linkml_parser.py +28 -0
- typedlogic/integrations/frameworks/linkml/loader.py +399 -0
- typedlogic/integrations/frameworks/linkml/meta.py +117 -0
- typedlogic/integrations/frameworks/linkml/meta_axioms.py +87 -0
- typedlogic/integrations/frameworks/linkml/validator.py +24 -0
- typedlogic/integrations/frameworks/owldl/__init__.py +54 -0
- typedlogic/integrations/frameworks/owldl/ontology_generators.py +0 -0
- typedlogic/integrations/frameworks/owldl/owl_from_fol.py +89 -0
- typedlogic/integrations/frameworks/owldl/owlpy_compiler.py +89 -0
- typedlogic/integrations/frameworks/owldl/owlpy_parser.py +193 -0
- typedlogic/integrations/frameworks/owldl/owltop.py +2397 -0
- typedlogic/integrations/frameworks/owldl/reasoner.py +190 -0
- typedlogic/integrations/frameworks/pandas/__init__.py +0 -0
- typedlogic/integrations/frameworks/pandas/pandas_utils.py +72 -0
- typedlogic/integrations/frameworks/pandera/__init__.py +5 -0
- typedlogic/integrations/frameworks/pandera/pandera_bridge.py +37 -0
- typedlogic/integrations/frameworks/prolog/__init__.py +0 -0
- typedlogic/integrations/frameworks/prolog/basic_prolog.py +434 -0
- typedlogic/integrations/frameworks/prolog/prolog_parser.py +72 -0
- typedlogic/integrations/frameworks/pydantic/__init__.py +5 -0
- typedlogic/integrations/frameworks/pydantic/pydantic_bridge.py +57 -0
- typedlogic/integrations/frameworks/rdflib/__init__.py +3 -0
- typedlogic/integrations/frameworks/rdflib/owlfull.py +93 -0
- typedlogic/integrations/frameworks/rdflib/rdf.py +70 -0
- typedlogic/integrations/frameworks/rdflib/rdf_parser.py +84 -0
- typedlogic/integrations/frameworks/rdflib/rdfs.py +136 -0
- typedlogic/integrations/frameworks/sqlmodel/__init__.py +0 -0
- typedlogic/integrations/solvers/__init__.py +0 -0
- typedlogic/integrations/solvers/clingo/__init__.py +5 -0
- typedlogic/integrations/solvers/clingo/clingo_solver.py +132 -0
- typedlogic/integrations/solvers/clorm/__init__.py +0 -0
- typedlogic/integrations/solvers/formulog/__init__.py +0 -0
- typedlogic/integrations/solvers/llm/__init__.py +0 -0
- typedlogic/integrations/solvers/llm/llm_solver.py +114 -0
- typedlogic/integrations/solvers/problog/__init__.py +7 -0
- typedlogic/integrations/solvers/problog/problog_compiler.py +170 -0
- typedlogic/integrations/solvers/problog/problog_solver.py +105 -0
- typedlogic/integrations/solvers/prover9/__init__.py +3 -0
- typedlogic/integrations/solvers/prover9/prover9_solver.py +82 -0
- typedlogic/integrations/solvers/pyprover/__init__.py +0 -0
- typedlogic/integrations/solvers/snakelog/__init__.py +3 -0
- typedlogic/integrations/solvers/snakelog/snakelog_solver.py +239 -0
- typedlogic/integrations/solvers/souffle/__init__.py +3 -0
- typedlogic/integrations/solvers/souffle/souffle_compiler.py +100 -0
- typedlogic/integrations/solvers/souffle/souffle_solver.py +118 -0
- typedlogic/integrations/solvers/z3/__init__.py +3 -0
- typedlogic/integrations/solvers/z3/z3_compiler.py +45 -0
- typedlogic/integrations/solvers/z3/z3_solver.py +343 -0
- typedlogic/integrations/solvers/z3/z3_utils.py +0 -0
- typedlogic/integrations/variadic_generics.py +18 -0
- typedlogic/parser.py +139 -0
- typedlogic/parsers/__init__.py +0 -0
- typedlogic/parsers/catalog_parser.py +502 -0
- typedlogic/parsers/dataframe_parser.py +249 -0
- typedlogic/parsers/pyparser/__init__.py +5 -0
- typedlogic/parsers/pyparser/introspection.py +357 -0
- typedlogic/parsers/pyparser/python_ast_utils.py +403 -0
- typedlogic/parsers/pyparser/python_parser.py +201 -0
- typedlogic/parsers/yaml_parser.py +69 -0
- typedlogic/profiles.py +321 -0
- typedlogic/py.typed +0 -0
- typedlogic/pybridge.py +134 -0
- typedlogic/registry.py +170 -0
- typedlogic/solver.py +319 -0
- typedlogic/theories/__init__.py +0 -0
- typedlogic/theories/bfo/__init__.py +0 -0
- typedlogic/theories/jsonlog/__init__.py +0 -0
- typedlogic/theories/jsonlog/jsonlog.py +207 -0
- typedlogic/theories/jsonlog/jsonlog_axioms.py +37 -0
- typedlogic/theories/jsonlog/jsonlog_parser.py +56 -0
- typedlogic/theories/jsonlog/loader.py +139 -0
- typedlogic/transformations.py +1793 -0
- typedlogic/typechecking.py +0 -0
- typedlogic/utils/__init__.py +0 -0
- typedlogic/utils/detect_stratified_negation.py +149 -0
- typedlogic/utils/graph_utils.py +73 -0
- typedlogic/utils/import_closure.py +47 -0
- typedlogic/utils/ipython_utils.py +5 -0
- typedlogic/utils/term_maker.py +34 -0
- typedlogic-0.0.0.dist-info/METADATA +190 -0
- typedlogic-0.0.0.dist-info/RECORD +117 -0
- typedlogic-0.0.0.dist-info/WHEEL +4 -0
- typedlogic-0.0.0.dist-info/entry_points.txt +3 -0
- typedlogic-0.0.0.dist-info/licenses/LICENSE +22 -0
typedlogic/__init__.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""typedlogic package."""
|
|
2
|
+
from typedlogic.datamodel import (
|
|
3
|
+
BooleanSentence,
|
|
4
|
+
And,
|
|
5
|
+
Or,
|
|
6
|
+
Not,
|
|
7
|
+
Implies,
|
|
8
|
+
Forall,
|
|
9
|
+
Exists,
|
|
10
|
+
Term,
|
|
11
|
+
NegationAsFailure,
|
|
12
|
+
not_provable,
|
|
13
|
+
Xor,
|
|
14
|
+
Implied,
|
|
15
|
+
Iff,
|
|
16
|
+
ExactlyOne,
|
|
17
|
+
Theory,
|
|
18
|
+
Sentence,
|
|
19
|
+
PredicateDefinition,
|
|
20
|
+
SentenceGroup,
|
|
21
|
+
Variable,
|
|
22
|
+
)
|
|
23
|
+
from typedlogic.pybridge import FactMixin, Fact
|
|
24
|
+
from typedlogic.generators import gen, gen1, gen2, gen3
|
|
25
|
+
from typedlogic.decorators import axiom, goal
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"BooleanSentence",
|
|
29
|
+
"And",
|
|
30
|
+
"Or",
|
|
31
|
+
"Not",
|
|
32
|
+
"Implies",
|
|
33
|
+
"Iff",
|
|
34
|
+
"Forall",
|
|
35
|
+
"Exists",
|
|
36
|
+
"Term",
|
|
37
|
+
"NegationAsFailure",
|
|
38
|
+
"not_provable",
|
|
39
|
+
"Xor",
|
|
40
|
+
"ExactlyOne",
|
|
41
|
+
"Implied",
|
|
42
|
+
"Theory",
|
|
43
|
+
"Sentence",
|
|
44
|
+
"Variable",
|
|
45
|
+
"PredicateDefinition",
|
|
46
|
+
"SentenceGroup",
|
|
47
|
+
"Fact",
|
|
48
|
+
"FactMixin",
|
|
49
|
+
"gen",
|
|
50
|
+
"gen1",
|
|
51
|
+
"gen2",
|
|
52
|
+
"gen3",
|
|
53
|
+
]
|
typedlogic/builtins.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mappings for builtin functions and datatypes.
|
|
3
|
+
"""
|
|
4
|
+
import operator as op
|
|
5
|
+
from typing import Callable, Mapping
|
|
6
|
+
|
|
7
|
+
NUMERIC_BUILTINS: Mapping[str, Callable] = {
|
|
8
|
+
"ge": op.ge,
|
|
9
|
+
"gt": op.gt,
|
|
10
|
+
"le": op.le,
|
|
11
|
+
"lt": op.lt,
|
|
12
|
+
"eq": op.eq,
|
|
13
|
+
"ne": op.ne,
|
|
14
|
+
"add": op.add,
|
|
15
|
+
"sub": op.sub,
|
|
16
|
+
"mul": op.mul,
|
|
17
|
+
"truediv": op.truediv,
|
|
18
|
+
"pow": op.pow,
|
|
19
|
+
"xor": op.xor,
|
|
20
|
+
"neg": op.neg,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
NAME_TO_INFIX_OP: Mapping[str, str] = {
|
|
24
|
+
"add": "+",
|
|
25
|
+
"sub": "-",
|
|
26
|
+
"mul": "*",
|
|
27
|
+
"truediv": "/",
|
|
28
|
+
"floordiv": "//",
|
|
29
|
+
"mod": "%",
|
|
30
|
+
"pow": "**",
|
|
31
|
+
"lshift": "<<",
|
|
32
|
+
"rshift": ">>",
|
|
33
|
+
"or": "|",
|
|
34
|
+
"xor": "^",
|
|
35
|
+
"and": "&",
|
|
36
|
+
"matmul": "@",
|
|
37
|
+
# Comparison operators
|
|
38
|
+
"eq": "==",
|
|
39
|
+
"ne": "!=",
|
|
40
|
+
"lt": "<",
|
|
41
|
+
"le": "<=",
|
|
42
|
+
"gt": ">",
|
|
43
|
+
"ge": ">=",
|
|
44
|
+
"is": "is",
|
|
45
|
+
"is_not": "is not",
|
|
46
|
+
"in": "in",
|
|
47
|
+
"not_in": "not in",
|
|
48
|
+
}
|
typedlogic/cli.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface (CLI) for typedlogic.
|
|
3
|
+
|
|
4
|
+
To get a list of all commands:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
typedlogic --help
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Note that an easy way to use the CLI is via pipx:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pipx run typedlogic --help
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Typically you will need one or more extras:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pipx run "typedlogic[pydantic,clingo]" --help
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Annotated, List, Optional
|
|
25
|
+
|
|
26
|
+
import click
|
|
27
|
+
import typer
|
|
28
|
+
from typer.main import get_command
|
|
29
|
+
|
|
30
|
+
from typedlogic.registry import get_compiler, get_parser, get_solver, all_parser_classes, all_compiler_classes, all_solver_classes
|
|
31
|
+
|
|
32
|
+
app = typer.Typer()
|
|
33
|
+
|
|
34
|
+
input_format_option = typer.Option(
|
|
35
|
+
"python", "--input-format", "-f", help="Input format. Currently supported: python, yaml, owlpy"
|
|
36
|
+
)
|
|
37
|
+
output_format_option = typer.Option(None, "--output-format", "-t", help="Output format")
|
|
38
|
+
|
|
39
|
+
output_file_option = typer.Option(None, "--output-file", "-o", help="Output file path")
|
|
40
|
+
|
|
41
|
+
@app.command()
|
|
42
|
+
def list_parsers():
|
|
43
|
+
"""
|
|
44
|
+
Lists all parsers.
|
|
45
|
+
|
|
46
|
+
Anything here should be usable as an input format.
|
|
47
|
+
"""
|
|
48
|
+
for name, cls in all_parser_classes().items():
|
|
49
|
+
print(name, cls)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@app.command()
|
|
53
|
+
def list_compilers():
|
|
54
|
+
"""
|
|
55
|
+
Lists all compilers.
|
|
56
|
+
|
|
57
|
+
Anything here should be usable as an output format.
|
|
58
|
+
"""
|
|
59
|
+
for name, cls in all_compiler_classes().items():
|
|
60
|
+
print(name, cls)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.command()
|
|
64
|
+
def list_solvers():
|
|
65
|
+
"""
|
|
66
|
+
Lists all solvers.
|
|
67
|
+
|
|
68
|
+
Anything here should be usable as a solver.
|
|
69
|
+
"""
|
|
70
|
+
for name, cls in all_solver_classes().items():
|
|
71
|
+
print(name, cls)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command()
|
|
75
|
+
def convert(
|
|
76
|
+
theory_files: List[Path] = typer.Argument(..., exists=True, dir_okay=False, readable=True),
|
|
77
|
+
input_format: str = input_format_option,
|
|
78
|
+
output_format: str = output_format_option,
|
|
79
|
+
output_file: Optional[Path] = output_file_option,
|
|
80
|
+
validate_types: bool = typer.Option(
|
|
81
|
+
True, "--validate-types/--no-validate-types", help="Use mypy to validate types"
|
|
82
|
+
),
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Convert from one logic form to another.
|
|
86
|
+
|
|
87
|
+
For a list of supported parsers and compilers,
|
|
88
|
+
see https://py-typedlogic.github.io/py-typedlogic/
|
|
89
|
+
|
|
90
|
+
Note that some conversions may be lossy. Currently no warnings are issued for such cases.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
-------
|
|
94
|
+
```bash
|
|
95
|
+
typedlogic convert my_theory.py -t fol
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
parser = get_parser(input_format)
|
|
100
|
+
if validate_types:
|
|
101
|
+
for p in theory_files:
|
|
102
|
+
errs = parser.validate(p)
|
|
103
|
+
if errs:
|
|
104
|
+
for err in errs:
|
|
105
|
+
click.echo(str(err))
|
|
106
|
+
raise ValueError("Errors in file")
|
|
107
|
+
theory = parser.parse(theory_files[0])
|
|
108
|
+
if len(theory_files) > 1:
|
|
109
|
+
for input_file in theory_files[1:]:
|
|
110
|
+
facts_theory = parser.parse(input_file)
|
|
111
|
+
for s in facts_theory.sentences:
|
|
112
|
+
theory.add(s)
|
|
113
|
+
|
|
114
|
+
compiler = get_compiler(output_format or "z3sexpr")
|
|
115
|
+
result = compiler.compile(theory)
|
|
116
|
+
|
|
117
|
+
if output_file:
|
|
118
|
+
with open(output_file, "w") as f:
|
|
119
|
+
f.write(result)
|
|
120
|
+
typer.echo(f"Conversion result written to {output_file}")
|
|
121
|
+
else:
|
|
122
|
+
typer.echo(result)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _guess_format(data_file: Path) -> str:
|
|
126
|
+
# Check for catalog files first
|
|
127
|
+
if data_file.name.endswith('.catalog.yaml') or data_file.name.endswith('.catalog.yml'):
|
|
128
|
+
return "catalog"
|
|
129
|
+
|
|
130
|
+
suffix = data_file.suffix[1:]
|
|
131
|
+
if suffix == "py":
|
|
132
|
+
return "python"
|
|
133
|
+
elif suffix in ["csv", "tsv", "xlsx", "xls", "tab"]:
|
|
134
|
+
return "dataframe"
|
|
135
|
+
return suffix
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _combine_input_files(input_files: List[Path], validate_types: bool = True):
|
|
139
|
+
"""
|
|
140
|
+
Combine multiple input files into a single theory.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
----
|
|
144
|
+
input_files: List of file paths to combine
|
|
145
|
+
validate_types: Whether to validate Python files with mypy
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
-------
|
|
149
|
+
Combined theory object
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
------
|
|
153
|
+
ValueError: If validation fails or no valid theories found
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
combined_theory = None
|
|
157
|
+
|
|
158
|
+
for input_file in input_files:
|
|
159
|
+
file_format = _guess_format(input_file)
|
|
160
|
+
parser = get_parser(file_format)
|
|
161
|
+
|
|
162
|
+
if validate_types and file_format == "python":
|
|
163
|
+
errs = parser.validate(input_file)
|
|
164
|
+
if errs:
|
|
165
|
+
for err in errs:
|
|
166
|
+
click.echo(f"Validation error in {input_file}: {err}")
|
|
167
|
+
raise ValueError(f"Type validation errors in {input_file}")
|
|
168
|
+
|
|
169
|
+
# Parse the file - try theory first, then ground terms
|
|
170
|
+
if combined_theory is None:
|
|
171
|
+
try:
|
|
172
|
+
parsed_result = parser.parse(input_file)
|
|
173
|
+
# Check if result is a proper Theory object
|
|
174
|
+
if hasattr(parsed_result, 'sentences') or hasattr(parsed_result, 'predicate_definitions'):
|
|
175
|
+
combined_theory = parsed_result
|
|
176
|
+
else:
|
|
177
|
+
# Result is raw data, treat as ground terms
|
|
178
|
+
from typedlogic import Theory
|
|
179
|
+
combined_theory = Theory()
|
|
180
|
+
ground_terms = parser.parse_ground_terms(input_file)
|
|
181
|
+
combined_theory.ground_terms = ground_terms
|
|
182
|
+
except Exception:
|
|
183
|
+
# If parsing as theory fails, try as ground terms only
|
|
184
|
+
from typedlogic import Theory
|
|
185
|
+
combined_theory = Theory()
|
|
186
|
+
ground_terms = parser.parse_ground_terms(input_file)
|
|
187
|
+
combined_theory.ground_terms = ground_terms
|
|
188
|
+
else:
|
|
189
|
+
# Try to parse as theory first
|
|
190
|
+
try:
|
|
191
|
+
parsed_result = parser.parse(input_file)
|
|
192
|
+
# Check if result is a proper Theory object
|
|
193
|
+
if hasattr(parsed_result, 'sentences') or hasattr(parsed_result, 'predicate_definitions'):
|
|
194
|
+
file_theory = parsed_result
|
|
195
|
+
|
|
196
|
+
# Merge sentences (axioms)
|
|
197
|
+
if hasattr(file_theory, 'sentences') and file_theory.sentences:
|
|
198
|
+
if combined_theory.sentences is None:
|
|
199
|
+
combined_theory.sentences = []
|
|
200
|
+
combined_theory.sentences.extend(file_theory.sentences)
|
|
201
|
+
|
|
202
|
+
# Merge ground terms (facts)
|
|
203
|
+
if hasattr(file_theory, 'ground_terms') and file_theory.ground_terms:
|
|
204
|
+
if combined_theory.ground_terms is None:
|
|
205
|
+
combined_theory.ground_terms = []
|
|
206
|
+
combined_theory.ground_terms.extend(file_theory.ground_terms)
|
|
207
|
+
|
|
208
|
+
# Merge predicate definitions
|
|
209
|
+
if hasattr(file_theory, 'predicate_definitions') and file_theory.predicate_definitions:
|
|
210
|
+
if combined_theory.predicate_definitions is None:
|
|
211
|
+
combined_theory.predicate_definitions = []
|
|
212
|
+
# Avoid duplicates by name
|
|
213
|
+
existing_names = {pd.predicate for pd in combined_theory.predicate_definitions}
|
|
214
|
+
for pd in file_theory.predicate_definitions:
|
|
215
|
+
if pd.predicate not in existing_names:
|
|
216
|
+
combined_theory.predicate_definitions.append(pd)
|
|
217
|
+
else:
|
|
218
|
+
# Result is raw data, treat as ground terms
|
|
219
|
+
ground_terms = parser.parse_ground_terms(input_file)
|
|
220
|
+
if combined_theory.ground_terms is None:
|
|
221
|
+
combined_theory.ground_terms = []
|
|
222
|
+
combined_theory.ground_terms.extend(ground_terms)
|
|
223
|
+
except Exception:
|
|
224
|
+
# If parsing as theory fails, try as ground terms
|
|
225
|
+
ground_terms = parser.parse_ground_terms(input_file)
|
|
226
|
+
if combined_theory.ground_terms is None:
|
|
227
|
+
combined_theory.ground_terms = []
|
|
228
|
+
combined_theory.ground_terms.extend(ground_terms)
|
|
229
|
+
|
|
230
|
+
if combined_theory is None:
|
|
231
|
+
raise ValueError("No valid theories found in input files")
|
|
232
|
+
|
|
233
|
+
return combined_theory
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@app.command()
|
|
237
|
+
def solve(
|
|
238
|
+
theory_file: Path = typer.Argument(..., exists=True, dir_okay=False, readable=True),
|
|
239
|
+
solver: str = typer.Option("souffle", "--solver", "-s", help="Solver to use (z3, souffle, clingo, etc.)"),
|
|
240
|
+
check_only: bool = typer.Option(False, "--check-only", "-c", help="Check satisfiability only, do not enumerate models"),
|
|
241
|
+
validate_types: bool = typer.Option(
|
|
242
|
+
True, "--validate-types/--no-validate-types", help="Use mypy to validate types"
|
|
243
|
+
),
|
|
244
|
+
input_format: str = input_format_option,
|
|
245
|
+
data_input_format: str = typer.Option(None, "--data-input-format", "-d", help="Format for ground terms"),
|
|
246
|
+
output_format: str = output_format_option,
|
|
247
|
+
output_file: Optional[Path] = output_file_option,
|
|
248
|
+
data_files: Annotated[Optional[List[Path]], typer.Argument()] = None,
|
|
249
|
+
):
|
|
250
|
+
"""
|
|
251
|
+
Solve logical theories with facts using the specified solver.
|
|
252
|
+
|
|
253
|
+
Accepts a theory file and optional data files containing facts.
|
|
254
|
+
Files can be Python (.py) or YAML (.yaml) format - format is auto-detected.
|
|
255
|
+
|
|
256
|
+
First checks satisfiability, then enumerates all models if satisfiable.
|
|
257
|
+
|
|
258
|
+
Examples
|
|
259
|
+
--------
|
|
260
|
+
```bash
|
|
261
|
+
# Solve with theory and facts
|
|
262
|
+
typedlogic solve theory.py facts.yaml --solver z3
|
|
263
|
+
|
|
264
|
+
# Check satisfiability only
|
|
265
|
+
typedlogic solve theory.py --check-only
|
|
266
|
+
|
|
267
|
+
# Solve with multiple data files
|
|
268
|
+
typedlogic solve theory.py data1.yaml data2.yaml --solver z3
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
"""
|
|
272
|
+
# Parse theory file
|
|
273
|
+
parser = get_parser(input_format or "python")
|
|
274
|
+
if validate_types:
|
|
275
|
+
errs = parser.validate(theory_file)
|
|
276
|
+
if errs:
|
|
277
|
+
for err in errs:
|
|
278
|
+
click.echo(f"Validation error in {theory_file}: {err}")
|
|
279
|
+
raise typer.Exit(1)
|
|
280
|
+
|
|
281
|
+
combined_theory = parser.parse(theory_file)
|
|
282
|
+
|
|
283
|
+
# Parse data files and add ground terms
|
|
284
|
+
if data_files:
|
|
285
|
+
if combined_theory.ground_terms is None:
|
|
286
|
+
combined_theory.ground_terms = []
|
|
287
|
+
for data_file in data_files:
|
|
288
|
+
data_parser = get_parser(data_input_format or _guess_format(data_file))
|
|
289
|
+
terms = data_parser.parse_ground_terms(data_file)
|
|
290
|
+
combined_theory.ground_terms.extend(terms)
|
|
291
|
+
|
|
292
|
+
# Initialize solver
|
|
293
|
+
try:
|
|
294
|
+
solver_instance = get_solver(solver or "souffle")
|
|
295
|
+
except ValueError as e:
|
|
296
|
+
click.echo(f"Error: {e}")
|
|
297
|
+
raise typer.Exit(1)
|
|
298
|
+
|
|
299
|
+
# Load theory into solver
|
|
300
|
+
solver_instance.add(combined_theory)
|
|
301
|
+
|
|
302
|
+
# Check satisfiability first
|
|
303
|
+
click.echo("Checking satisfiability...")
|
|
304
|
+
solution = solver_instance.check()
|
|
305
|
+
|
|
306
|
+
result = f"Satisfiable: {solution.satisfiable}\n"
|
|
307
|
+
|
|
308
|
+
if solution.satisfiable is False:
|
|
309
|
+
click.echo("UNSATISFIABLE: The theory has no valid models.")
|
|
310
|
+
result += "No models exist.\n"
|
|
311
|
+
else:
|
|
312
|
+
if solution.satisfiable is True:
|
|
313
|
+
click.echo("SATISFIABLE: The theory has valid models.")
|
|
314
|
+
else:
|
|
315
|
+
click.echo("UNKNOWN: Satisfiability could not be determined.")
|
|
316
|
+
if not check_only:
|
|
317
|
+
click.echo("Enumerating all models...")
|
|
318
|
+
model_count = 0
|
|
319
|
+
for model in solver_instance.models():
|
|
320
|
+
result += f"\n=== Model {model_count + 1} ===\n"
|
|
321
|
+
if model.ground_terms:
|
|
322
|
+
for fact in model.ground_terms:
|
|
323
|
+
result += f"{fact}\n"
|
|
324
|
+
else:
|
|
325
|
+
result += "(empty model)\n"
|
|
326
|
+
model_count += 1
|
|
327
|
+
|
|
328
|
+
if model_count == 0:
|
|
329
|
+
result += "No models generated (solver may not support model enumeration).\n"
|
|
330
|
+
else:
|
|
331
|
+
result += f"\nTotal models found: {model_count}\n"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# Output results
|
|
335
|
+
if output_file:
|
|
336
|
+
with open(output_file, "w") as f:
|
|
337
|
+
f.write(result)
|
|
338
|
+
click.echo(f"Solution written to {output_file}")
|
|
339
|
+
else:
|
|
340
|
+
click.echo("\n" + "="*50)
|
|
341
|
+
click.echo(result.rstrip())
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@app.command()
|
|
345
|
+
def dump(
|
|
346
|
+
input_files: List[Path] = typer.Argument(..., exists=True, dir_okay=False, readable=True),
|
|
347
|
+
output_format: str = typer.Option("yaml", "--output-format", "-t", help="Output format (fol, yaml, prolog, etc.)"),
|
|
348
|
+
output_file: Optional[Path] = output_file_option,
|
|
349
|
+
validate_types: bool = typer.Option(
|
|
350
|
+
True, "--validate-types/--no-validate-types", help="Use mypy to validate types"
|
|
351
|
+
),
|
|
352
|
+
):
|
|
353
|
+
"""
|
|
354
|
+
Parse and combine multiple input files, then export to specified format without solving.
|
|
355
|
+
|
|
356
|
+
This command is useful for preprocessing, format conversion, and inspecting
|
|
357
|
+
the combined logical theory before solving.
|
|
358
|
+
|
|
359
|
+
Files can be Python (.py) or YAML (.yaml) format - format is auto-detected.
|
|
360
|
+
|
|
361
|
+
Examples
|
|
362
|
+
--------
|
|
363
|
+
```bash
|
|
364
|
+
# Combine and export to first-order logic
|
|
365
|
+
typedlogic dump theory.py facts.yaml -t fol -o combined.fol
|
|
366
|
+
|
|
367
|
+
# Export to YAML format
|
|
368
|
+
typedlogic dump axioms.py data.yaml -t yaml
|
|
369
|
+
|
|
370
|
+
# Combine multiple files and view as Prolog
|
|
371
|
+
typedlogic dump theory1.py theory2.py facts.yaml -t prolog
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
"""
|
|
375
|
+
# Combine all input files into a single theory
|
|
376
|
+
try:
|
|
377
|
+
combined_theory = _combine_input_files(input_files, validate_types)
|
|
378
|
+
except ValueError as e:
|
|
379
|
+
click.echo(f"Error: {e}")
|
|
380
|
+
raise typer.Exit(1)
|
|
381
|
+
|
|
382
|
+
# Get compiler for output format
|
|
383
|
+
try:
|
|
384
|
+
compiler = get_compiler(output_format)
|
|
385
|
+
except ValueError as e:
|
|
386
|
+
click.echo(f"Error: {e}")
|
|
387
|
+
raise typer.Exit(1)
|
|
388
|
+
|
|
389
|
+
# Compile the combined theory
|
|
390
|
+
try:
|
|
391
|
+
result = compiler.compile(combined_theory)
|
|
392
|
+
except Exception as e:
|
|
393
|
+
click.echo(f"Error compiling theory: {e}")
|
|
394
|
+
raise typer.Exit(1)
|
|
395
|
+
|
|
396
|
+
# Output results
|
|
397
|
+
if output_file:
|
|
398
|
+
with open(output_file, "w") as f:
|
|
399
|
+
f.write(result)
|
|
400
|
+
click.echo(f"Combined theory exported to {output_file}")
|
|
401
|
+
else:
|
|
402
|
+
click.echo(result)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# DO NOT REMOVE THIS LINE
|
|
406
|
+
# added this for mkdocstrings to work
|
|
407
|
+
# see https://github.com/bruce-szalwinski/mkdocs-typer/issues/18
|
|
408
|
+
click_app = get_command(app)
|
|
409
|
+
click_app.name = "typedlogic"
|
|
410
|
+
|
|
411
|
+
if __name__ == "__main__":
|
|
412
|
+
app()
|
typedlogic/compiler.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Framework for compiling a theory to an external format (e.g., FOL, CL, TPTP, Prolog, etc.)
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import ClassVar, Optional, Type, Union, TextIO, Iterable, List
|
|
9
|
+
|
|
10
|
+
from mypyc.ir.ops import SetAttr
|
|
11
|
+
|
|
12
|
+
from typedlogic import Sentence, Theory
|
|
13
|
+
from typedlogic.parser import Parser
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def compile_sentences(sentences: Iterable[Sentence], syntax="fol") -> List[str]:
|
|
17
|
+
from typedlogic.registry import get_compiler
|
|
18
|
+
compiler = get_compiler(syntax)
|
|
19
|
+
return [compiler.compile_sentence(s) for s in sentences]
|
|
20
|
+
|
|
21
|
+
def write_sentences(sentences: Iterable[Sentence], syntax="fol"):
|
|
22
|
+
for s in compile_sentences(sentences, syntax=syntax):
|
|
23
|
+
print(s)
|
|
24
|
+
|
|
25
|
+
class ModelSyntax(str, Enum):
|
|
26
|
+
"""
|
|
27
|
+
Enum for model syntax types.
|
|
28
|
+
|
|
29
|
+
TODO: consider deprecated
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
UNKNOWN = "unknown"
|
|
33
|
+
SEXPR = "sexpr"
|
|
34
|
+
FUNCTIONAL = "functional"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Compiler(ABC):
|
|
39
|
+
"""
|
|
40
|
+
An engine for compiling from the internal logical model representation to an external format.
|
|
41
|
+
|
|
42
|
+
Note: one of the main use cases for compiling a theory is to generate an input for a solver.
|
|
43
|
+
For many use cases, it's possible to just use a Solver directly, and let the system
|
|
44
|
+
take care of compilation to an intermediate form.
|
|
45
|
+
|
|
46
|
+
You can use the registry `get_compiler` method to get a compiler for a particular syntax:
|
|
47
|
+
|
|
48
|
+
>>> from typedlogic.registry import get_compiler
|
|
49
|
+
>>> compiler = get_compiler("fol")
|
|
50
|
+
|
|
51
|
+
Next we will transpile from Python to FOL syntax. We will use a `Parser` object:
|
|
52
|
+
|
|
53
|
+
>>> from typedlogic.registry import get_parser
|
|
54
|
+
>>> parser = get_parser("python")
|
|
55
|
+
>>> theory = parser.parse_file("tests/theorems/animals.py")
|
|
56
|
+
|
|
57
|
+
Now we will compile the theory to FOL syntax:
|
|
58
|
+
|
|
59
|
+
>>> print(compiler.compile(theory))
|
|
60
|
+
Person('Fred')
|
|
61
|
+
Person('Jie')
|
|
62
|
+
Animal('corky', 'cat')
|
|
63
|
+
Animal('fido', 'dog')
|
|
64
|
+
∀[x:Thing species:Thing]. Animal(x, species) → Likes(x, 'Fred')
|
|
65
|
+
∀[x:Thing species:Thing]. Animal(x, 'cat') → Likes(x, 'Jie')
|
|
66
|
+
∀[x:Thing species:Thing]. Animal(x, 'dog') → ¬Likes('Fred', x)
|
|
67
|
+
|
|
68
|
+
Another useful syntax is TPTP, which is accepted by many theorom provers:
|
|
69
|
+
|
|
70
|
+
>>> compiler = get_compiler("tptp")
|
|
71
|
+
>>> print(compiler.compile(theory))
|
|
72
|
+
% Problem: animals
|
|
73
|
+
fof(axiom1, axiom, person('Fred')).
|
|
74
|
+
fof(axiom2, axiom, person('Jie')).
|
|
75
|
+
fof(axiom3, axiom, animal('corky', 'cat')).
|
|
76
|
+
fof(axiom4, axiom, animal('fido', 'dog')).
|
|
77
|
+
fof(axiom5, axiom, ! [X, Species] : (animal(X, Species) => likes(X, 'Fred'))).
|
|
78
|
+
fof(axiom6, axiom, ! [X, Species] : (animal(X, 'cat') => likes(X, 'Jie'))).
|
|
79
|
+
fof(axiom7, axiom, ! [X, Species] : (animal(X, 'dog') => ~likes('Fred', X))).
|
|
80
|
+
|
|
81
|
+
Another common syntax is Prolog syntax, and its variants. These are often used by Datalog solvers:
|
|
82
|
+
|
|
83
|
+
>>> compiler = get_compiler("prolog")
|
|
84
|
+
>>> print(compiler.compile(theory))
|
|
85
|
+
%% Predicate Definitions
|
|
86
|
+
% Likes(subject: str, object: str)
|
|
87
|
+
% Person(name: str)
|
|
88
|
+
% Animal(name: str, species: str)
|
|
89
|
+
<BLANKLINE>
|
|
90
|
+
%% persons
|
|
91
|
+
<BLANKLINE>
|
|
92
|
+
person('Fred').
|
|
93
|
+
person('Jie').
|
|
94
|
+
<BLANKLINE>
|
|
95
|
+
%% animals
|
|
96
|
+
<BLANKLINE>
|
|
97
|
+
animal('corky', 'cat').
|
|
98
|
+
animal('fido', 'dog').
|
|
99
|
+
<BLANKLINE>
|
|
100
|
+
%% animal_preferences
|
|
101
|
+
<BLANKLINE>
|
|
102
|
+
likes(X, 'Fred') :- animal(X, Species).
|
|
103
|
+
likes(X, 'Jie') :- animal(X, 'cat').
|
|
104
|
+
<BLANKLINE>
|
|
105
|
+
|
|
106
|
+
There are multiple variants of Prolog syntax, the `PrologConfig` object can be used to control the output.
|
|
107
|
+
Config arguments can be passed as kwargs to the `compile` method.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
default_suffix: ClassVar[str] = "txt"
|
|
111
|
+
parser_class: ClassVar[Optional[Type[Parser]]] = None
|
|
112
|
+
strict: Optional[bool] = None
|
|
113
|
+
|
|
114
|
+
@abstractmethod
|
|
115
|
+
def compile(self, theory: Theory, syntax: Optional[Union[str, ModelSyntax]] = None, **kwargs) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Compile a theory into an external representation.
|
|
118
|
+
|
|
119
|
+
:param theory:
|
|
120
|
+
:param syntax:
|
|
121
|
+
:param kwargs:
|
|
122
|
+
:return: string representation of the compiled artefact
|
|
123
|
+
"""
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
def compile_to_target(
|
|
127
|
+
self,
|
|
128
|
+
theory: Theory,
|
|
129
|
+
target: Union[str, Path, TextIO],
|
|
130
|
+
syntax: Optional[Union[str, ModelSyntax]] = None,
|
|
131
|
+
**kwargs,
|
|
132
|
+
):
|
|
133
|
+
"""
|
|
134
|
+
Compile a theory to a file or stream.
|
|
135
|
+
|
|
136
|
+
:param theory:
|
|
137
|
+
:param target:
|
|
138
|
+
:param syntax:
|
|
139
|
+
:param kwargs:
|
|
140
|
+
:return:
|
|
141
|
+
"""
|
|
142
|
+
if isinstance(target, str):
|
|
143
|
+
target = Path(target)
|
|
144
|
+
if isinstance(target, Path):
|
|
145
|
+
with target.open("w") as f:
|
|
146
|
+
f.write(self.compile(theory, syntax=syntax, **kwargs))
|
|
147
|
+
else:
|
|
148
|
+
target.write(self.compile(theory, syntax=syntax, **kwargs))
|
|
149
|
+
|
|
150
|
+
def compile_sentence(self, sentence: Sentence, syntax: Optional[Union[str, ModelSyntax]] = None, **kwargs) -> str:
|
|
151
|
+
"""
|
|
152
|
+
Compiles an individual sentence
|
|
153
|
+
|
|
154
|
+
:param sentence:
|
|
155
|
+
:param syntax:
|
|
156
|
+
:param kwargs:
|
|
157
|
+
:return:
|
|
158
|
+
"""
|
|
159
|
+
theory = Theory()
|
|
160
|
+
theory.add(sentence)
|
|
161
|
+
return self.compile(theory, syntax=syntax, **kwargs)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def suffix(self) -> str:
|
|
165
|
+
"""
|
|
166
|
+
Get the suffix for the compiled output.
|
|
167
|
+
|
|
168
|
+
:return:
|
|
169
|
+
"""
|
|
170
|
+
return self.default_suffix
|
|
171
|
+
|
|
172
|
+
def _add_untranslatable(self, sentence: Sentence):
|
|
173
|
+
pass
|
|
File without changes
|