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.
Files changed (117) hide show
  1. typedlogic/__init__.py +53 -0
  2. typedlogic/builtins.py +48 -0
  3. typedlogic/cli.py +412 -0
  4. typedlogic/compiler.py +173 -0
  5. typedlogic/compilers/__init__.py +0 -0
  6. typedlogic/compilers/fol_compiler.py +44 -0
  7. typedlogic/compilers/prolog_compiler.py +69 -0
  8. typedlogic/compilers/prover9_compiler.py +42 -0
  9. typedlogic/compilers/sexpr_compiler.py +99 -0
  10. typedlogic/compilers/tptp_compiler.py +49 -0
  11. typedlogic/compilers/yaml_compiler.py +75 -0
  12. typedlogic/datamodel.py +1230 -0
  13. typedlogic/datamodels/__init__.py +0 -0
  14. typedlogic/datamodels/typesystem.py +181 -0
  15. typedlogic/decorators.py +148 -0
  16. typedlogic/evaluation.py +207 -0
  17. typedlogic/extensions/__init__.py +0 -0
  18. typedlogic/extensions/probabilistic.py +56 -0
  19. typedlogic/generators.py +95 -0
  20. typedlogic/integrations/__init__.py +0 -0
  21. typedlogic/integrations/data/__init__.py +0 -0
  22. typedlogic/integrations/data/dask/__init__.py +0 -0
  23. typedlogic/integrations/data/ibis/__init__.py +0 -0
  24. typedlogic/integrations/data/pandas/__init__.py +0 -0
  25. typedlogic/integrations/data/polars/__init__.py +0 -0
  26. typedlogic/integrations/data/sqlmodel/__init__.py +0 -0
  27. typedlogic/integrations/frameworks/__init__.py +0 -0
  28. typedlogic/integrations/frameworks/hornedowl/__init__.py +0 -0
  29. typedlogic/integrations/frameworks/hornedowl/horned_owl_bridge.py +448 -0
  30. typedlogic/integrations/frameworks/hornedowl/owl_compiler.py +38 -0
  31. typedlogic/integrations/frameworks/hornedowl/owl_parser.py +68 -0
  32. typedlogic/integrations/frameworks/linkml/__init__.py +1 -0
  33. typedlogic/integrations/frameworks/linkml/instance.py +260 -0
  34. typedlogic/integrations/frameworks/linkml/linkml_parser.py +28 -0
  35. typedlogic/integrations/frameworks/linkml/loader.py +399 -0
  36. typedlogic/integrations/frameworks/linkml/meta.py +117 -0
  37. typedlogic/integrations/frameworks/linkml/meta_axioms.py +87 -0
  38. typedlogic/integrations/frameworks/linkml/validator.py +24 -0
  39. typedlogic/integrations/frameworks/owldl/__init__.py +54 -0
  40. typedlogic/integrations/frameworks/owldl/ontology_generators.py +0 -0
  41. typedlogic/integrations/frameworks/owldl/owl_from_fol.py +89 -0
  42. typedlogic/integrations/frameworks/owldl/owlpy_compiler.py +89 -0
  43. typedlogic/integrations/frameworks/owldl/owlpy_parser.py +193 -0
  44. typedlogic/integrations/frameworks/owldl/owltop.py +2397 -0
  45. typedlogic/integrations/frameworks/owldl/reasoner.py +190 -0
  46. typedlogic/integrations/frameworks/pandas/__init__.py +0 -0
  47. typedlogic/integrations/frameworks/pandas/pandas_utils.py +72 -0
  48. typedlogic/integrations/frameworks/pandera/__init__.py +5 -0
  49. typedlogic/integrations/frameworks/pandera/pandera_bridge.py +37 -0
  50. typedlogic/integrations/frameworks/prolog/__init__.py +0 -0
  51. typedlogic/integrations/frameworks/prolog/basic_prolog.py +434 -0
  52. typedlogic/integrations/frameworks/prolog/prolog_parser.py +72 -0
  53. typedlogic/integrations/frameworks/pydantic/__init__.py +5 -0
  54. typedlogic/integrations/frameworks/pydantic/pydantic_bridge.py +57 -0
  55. typedlogic/integrations/frameworks/rdflib/__init__.py +3 -0
  56. typedlogic/integrations/frameworks/rdflib/owlfull.py +93 -0
  57. typedlogic/integrations/frameworks/rdflib/rdf.py +70 -0
  58. typedlogic/integrations/frameworks/rdflib/rdf_parser.py +84 -0
  59. typedlogic/integrations/frameworks/rdflib/rdfs.py +136 -0
  60. typedlogic/integrations/frameworks/sqlmodel/__init__.py +0 -0
  61. typedlogic/integrations/solvers/__init__.py +0 -0
  62. typedlogic/integrations/solvers/clingo/__init__.py +5 -0
  63. typedlogic/integrations/solvers/clingo/clingo_solver.py +132 -0
  64. typedlogic/integrations/solvers/clorm/__init__.py +0 -0
  65. typedlogic/integrations/solvers/formulog/__init__.py +0 -0
  66. typedlogic/integrations/solvers/llm/__init__.py +0 -0
  67. typedlogic/integrations/solvers/llm/llm_solver.py +114 -0
  68. typedlogic/integrations/solvers/problog/__init__.py +7 -0
  69. typedlogic/integrations/solvers/problog/problog_compiler.py +170 -0
  70. typedlogic/integrations/solvers/problog/problog_solver.py +105 -0
  71. typedlogic/integrations/solvers/prover9/__init__.py +3 -0
  72. typedlogic/integrations/solvers/prover9/prover9_solver.py +82 -0
  73. typedlogic/integrations/solvers/pyprover/__init__.py +0 -0
  74. typedlogic/integrations/solvers/snakelog/__init__.py +3 -0
  75. typedlogic/integrations/solvers/snakelog/snakelog_solver.py +239 -0
  76. typedlogic/integrations/solvers/souffle/__init__.py +3 -0
  77. typedlogic/integrations/solvers/souffle/souffle_compiler.py +100 -0
  78. typedlogic/integrations/solvers/souffle/souffle_solver.py +118 -0
  79. typedlogic/integrations/solvers/z3/__init__.py +3 -0
  80. typedlogic/integrations/solvers/z3/z3_compiler.py +45 -0
  81. typedlogic/integrations/solvers/z3/z3_solver.py +343 -0
  82. typedlogic/integrations/solvers/z3/z3_utils.py +0 -0
  83. typedlogic/integrations/variadic_generics.py +18 -0
  84. typedlogic/parser.py +139 -0
  85. typedlogic/parsers/__init__.py +0 -0
  86. typedlogic/parsers/catalog_parser.py +502 -0
  87. typedlogic/parsers/dataframe_parser.py +249 -0
  88. typedlogic/parsers/pyparser/__init__.py +5 -0
  89. typedlogic/parsers/pyparser/introspection.py +357 -0
  90. typedlogic/parsers/pyparser/python_ast_utils.py +403 -0
  91. typedlogic/parsers/pyparser/python_parser.py +201 -0
  92. typedlogic/parsers/yaml_parser.py +69 -0
  93. typedlogic/profiles.py +321 -0
  94. typedlogic/py.typed +0 -0
  95. typedlogic/pybridge.py +134 -0
  96. typedlogic/registry.py +170 -0
  97. typedlogic/solver.py +319 -0
  98. typedlogic/theories/__init__.py +0 -0
  99. typedlogic/theories/bfo/__init__.py +0 -0
  100. typedlogic/theories/jsonlog/__init__.py +0 -0
  101. typedlogic/theories/jsonlog/jsonlog.py +207 -0
  102. typedlogic/theories/jsonlog/jsonlog_axioms.py +37 -0
  103. typedlogic/theories/jsonlog/jsonlog_parser.py +56 -0
  104. typedlogic/theories/jsonlog/loader.py +139 -0
  105. typedlogic/transformations.py +1793 -0
  106. typedlogic/typechecking.py +0 -0
  107. typedlogic/utils/__init__.py +0 -0
  108. typedlogic/utils/detect_stratified_negation.py +149 -0
  109. typedlogic/utils/graph_utils.py +73 -0
  110. typedlogic/utils/import_closure.py +47 -0
  111. typedlogic/utils/ipython_utils.py +5 -0
  112. typedlogic/utils/term_maker.py +34 -0
  113. typedlogic-0.0.0.dist-info/METADATA +190 -0
  114. typedlogic-0.0.0.dist-info/RECORD +117 -0
  115. typedlogic-0.0.0.dist-info/WHEEL +4 -0
  116. typedlogic-0.0.0.dist-info/entry_points.txt +3 -0
  117. 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