asplain 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 (58) hide show
  1. asplain/__init__.py +287 -0
  2. asplain/__main__.py +24 -0
  3. asplain/app.py +485 -0
  4. asplain/encodings/construct-foil.lp +30 -0
  5. asplain/encodings/costs/docs.lp +10 -0
  6. asplain/encodings/costs/model-difference.lp +5 -0
  7. asplain/encodings/costs/penalize-added.lp +2 -0
  8. asplain/encodings/costs/penalize-non-assumptions-removed.lp +2 -0
  9. asplain/encodings/costs/penalize-non-constraint-removed.lp +2 -0
  10. asplain/encodings/costs/penalize-non-facts-removed.lp +2 -0
  11. asplain/encodings/costs/penalize-removed.lp +2 -0
  12. asplain/encodings/costs/program-difference.lp +5 -0
  13. asplain/encodings/dynamic-tags/removable-assumptions.lp +2 -0
  14. asplain/encodings/force-model.lp +5 -0
  15. asplain/encodings/model-subgraph.lp +17 -0
  16. asplain/encodings/pruning/change.lp +2 -0
  17. asplain/encodings/pruning/changes.lp +12 -0
  18. asplain/encodings/pruning/inclusion_filter.lp +26 -0
  19. asplain/encodings/pruning/orphans.lp +11 -0
  20. asplain/encodings/pruning/path_test.lp +20 -0
  21. asplain/encodings/pruning/paths.lp +38 -0
  22. asplain/encodings/pruning/paths_undirected.lp +37 -0
  23. asplain/encodings/reify-to-pg.lp +75 -0
  24. asplain/encodings/show.lp +23 -0
  25. asplain/encodings/ui.lp +639 -0
  26. asplain/encodings/utils-tags.lp +19 -0
  27. asplain/encodings/viz-pg.lp +108 -0
  28. asplain/llm/__init__.py +0 -0
  29. asplain/llm/models/__init__.py +7 -0
  30. asplain/llm/models/base.py +32 -0
  31. asplain/llm/models/google.py +42 -0
  32. asplain/llm/models/openai.py +60 -0
  33. asplain/llm/models/tags.py +27 -0
  34. asplain/llm/templates/__init__.py +8 -0
  35. asplain/llm/templates/base.py +15 -0
  36. asplain/llm/templates/explain.py +36 -0
  37. asplain/llm/templates/prompt_templates/explain_input.txt +4 -0
  38. asplain/llm/templates/prompt_templates/explain_instructions.txt +294 -0
  39. asplain/llm/utils/__init__.py +8 -0
  40. asplain/llm/utils/graph.py +216 -0
  41. asplain/llm/utils/parsing.py +21 -0
  42. asplain/llm/utils/predicates.py +106 -0
  43. asplain/pruning/__init__.py +0 -0
  44. asplain/pruning/pruners.py +127 -0
  45. asplain/py.typed +0 -0
  46. asplain/ui/backend.py +416 -0
  47. asplain/utils/__init__.py +3 -0
  48. asplain/utils/clingo.py +166 -0
  49. asplain/utils/logging.py +135 -0
  50. asplain/utils/node-count.lp +10 -0
  51. asplain/utils/parser.py +53 -0
  52. asplain/utils/viz.py +48 -0
  53. asplain-0.0.0.dist-info/METADATA +125 -0
  54. asplain-0.0.0.dist-info/RECORD +58 -0
  55. asplain-0.0.0.dist-info/WHEEL +5 -0
  56. asplain-0.0.0.dist-info/entry_points.txt +2 -0
  57. asplain-0.0.0.dist-info/licenses/LICENSE +21 -0
  58. asplain-0.0.0.dist-info/top_level.txt +1 -0
asplain/__init__.py ADDED
@@ -0,0 +1,287 @@
1
+ """
2
+ The asplain project.
3
+ """
4
+
5
+ import logging
6
+ from typing import Any, List, Optional, Tuple
7
+
8
+ from clingo import Control, SolveHandle, Symbol
9
+ from meta_tools import classic_reify, extend_reification, transform
10
+ from meta_tools.extensions import ShowExtension, TagExtension
11
+ from meta_tools.utils.theory import extend_with_theory_symbols
12
+
13
+ from asplain.llm.utils.graph import Graph
14
+ from asplain.utils.clingo import (
15
+ assert_no_errors,
16
+ assumptions_as_ic,
17
+ constants_to_args,
18
+ load_encoding,
19
+ symbols_to_prg,
20
+ )
21
+ from asplain.utils.logging import colored, save_out
22
+
23
+ log = logging.getLogger(__name__)
24
+
25
+
26
+ def reify_program(
27
+ file_paths: List[str],
28
+ prg: str = "",
29
+ constants: Optional[dict[str, str]] = None,
30
+ ) -> str:
31
+ """Reifies a program.
32
+ Args:
33
+ file_paths: List of file paths to load.
34
+ prg: Additional program string to include.
35
+ constants: Constants to pass to clingo.
36
+ Returns:
37
+ The reified program as a string.
38
+ """
39
+ constants = constants or {}
40
+ extensions = [
41
+ TagExtension(include_program=True, include_loc=True, include_id=True),
42
+ ShowExtension(),
43
+ ]
44
+ program_str = transform(file_paths, prg, extensions)
45
+ log.debug("Transformed program:\n%s", program_str)
46
+ rsymbols = classic_reify(
47
+ constants_to_args(constants) + ["--preserve-facts=symtab"],
48
+ program_str,
49
+ programs=[("base", []), ("asplain", [])],
50
+ )
51
+ extend_with_theory_symbols(rsymbols)
52
+ reified_prg = "\n".join([f"{str(s)}." for s in rsymbols])
53
+ reified_prg = extend_reification(reified_out_prg=reified_prg, extensions=extensions, clean_output=True)
54
+ save_out("reference_reified.lp", reified_prg)
55
+ return reified_prg
56
+
57
+
58
+ # pylint: disable=too-many-arguments
59
+ # pylint: disable=too-many-positional-arguments
60
+ def construct_program_graph(
61
+ file_paths: List[str],
62
+ prg: str = "",
63
+ constants: Optional[dict[str, str]] = None,
64
+ assumptions: Optional[List[Tuple[str, bool]]] = None,
65
+ dynamic_tags_prg: Optional[str] = None,
66
+ dynamic_tags_files: Optional[List[str]] = None,
67
+ ) -> str:
68
+ """Constructs a program graph
69
+ Args:
70
+ file_paths: List of file paths to load.
71
+ prg: Additional program string to include.
72
+ constants: Constants to pass to clingo.
73
+ assumptions: Assumptions to include as integrity constraints.
74
+ dynamic_tags_prg: Dynamic tags program string to generate tags.
75
+ dynamic_tags_files: List of files to load for dynamic tags.
76
+ Returns:
77
+ The program graph as a string.
78
+ """
79
+ constants = constants or {}
80
+
81
+ if assumptions is not None:
82
+ prg = prg + assumptions_as_ic(assumptions)
83
+ log.info(
84
+ "Reifying program %s with constants %s and assumptions %s",
85
+ file_paths,
86
+ constants,
87
+ assumptions,
88
+ )
89
+ reified_prg = reify_program(file_paths, prg, constants)
90
+ ctl = Control()
91
+ ctl.add("base", [], reified_prg)
92
+ if dynamic_tags_prg:
93
+ ctl.add("base", [], dynamic_tags_prg)
94
+ if dynamic_tags_files:
95
+ for file in dynamic_tags_files:
96
+ log.info("Loading dynamic tags file: %s", file)
97
+ ctl.load(file)
98
+ load_encoding(ctl, "reify-to-pg.lp")
99
+ ctl.ground([("base", [])])
100
+ with ctl.solve(yield_=True) as handle:
101
+ model = handle.model()
102
+ if model is None:
103
+ raise RuntimeError("No model found when constructing program graph.")
104
+ model_symbols = model.symbols(shown=True)
105
+ assert_no_errors(list(model_symbols))
106
+ return symbols_to_prg(list(model_symbols))
107
+
108
+
109
+ def set_model_subgraphs_ctl(
110
+ pg: str, ctl: Optional[Control] = None, model_symbols: Optional[List[str]] = None
111
+ ) -> Control:
112
+ """
113
+ Sets the control object for computing model subgraphs.
114
+ Args:
115
+ pg: The program graph string.
116
+ ctl: Optional existing Control object.
117
+ model_symbols: Optional list of model symbols.
118
+ Returns:
119
+ The Control object with the model subgraph encoding loaded and grounded
120
+ """
121
+ ctl = ctl or Control(["0", "-c graph=ref"])
122
+ ctl.add("base", [], pg)
123
+ if model_symbols is not None:
124
+ log.debug("Setting model symbols: %s", model_symbols)
125
+ model_prg = "\n".join([f"model({str(s)})." for s in model_symbols])
126
+ ctl.add("base", [], model_prg)
127
+ load_encoding(ctl, "force-model.lp")
128
+
129
+ load_encoding(ctl, "model-subgraph.lp")
130
+ ctl.ground([("base", [])])
131
+ return ctl
132
+
133
+
134
+ def set_foil_ctl(
135
+ pg: str,
136
+ query_prg: Optional[str] = None,
137
+ cost_prg: Optional[str] = None,
138
+ number_of_foils: int = 1,
139
+ ) -> Control:
140
+ """Constructs a foil to explain a query.
141
+ Args:
142
+ pg: The reference program graph string which might include facts for the reference model graph.
143
+ query_prg: The query program string.
144
+ cost_prg: The distance program string.
145
+ number_of_foils: The number of foils to construct.
146
+ """
147
+ log.debug("Query program : %s", query_prg or "<none>")
148
+ log.debug("Cost program : %s", cost_prg or "<none>")
149
+ log.debug("Program graph: %s", pg)
150
+ ctl = Control([str(number_of_foils), "-c graph=foil", "--opt-mode=optN"])
151
+ ctl.add("base", [], pg)
152
+ ctl.add("base", [], query_prg or "")
153
+ ctl.add("base", [], cost_prg or "")
154
+ load_encoding(ctl, "construct-foil.lp")
155
+ load_encoding(ctl, "model-subgraph.lp")
156
+ ctl.ground([("base", [])])
157
+ return ctl
158
+
159
+
160
+ def construct_contrastive(
161
+ pg: str,
162
+ query_prg: Optional[str],
163
+ ) -> str:
164
+ """Constructs a contrastive explanation.
165
+ Args:
166
+ pg: The set of facts defining the reference program graph,
167
+ foil program graph, foil model graph and optionally the reference model graph.
168
+ query_prg: The query program string defined via query/2 facts.
169
+ Returns:
170
+ The contrastive explanation program graph as a string,
171
+ which includes the facts for the input graphs in pg.
172
+ """
173
+ ctl = Control()
174
+ ctl.add("base", [], pg)
175
+ ctl.add("base", [], query_prg or "")
176
+ ctl.ground([("base", [])])
177
+ with ctl.solve(yield_=True) as handle:
178
+ model = handle.model()
179
+ if model is None:
180
+ raise RuntimeError("No contrastive explanation could be constructed.")
181
+ model_symbols = model.symbols(shown=True)
182
+ return symbols_to_prg(list(model_symbols))
183
+
184
+ raise RuntimeError("No contrastive explanation could be constructed.")
185
+
186
+
187
+ class Foil:
188
+ """
189
+ Class to represent a foil, including the atoms in the foil model,
190
+ the added and removed rules, and the reference atoms.
191
+ Intended to save the result of obtaining foils
192
+ """
193
+
194
+ def __init__(
195
+ self,
196
+ foil_atoms: list[str],
197
+ added_rules: list[str],
198
+ removed_rules: list[str],
199
+ reference_atoms: list[str],
200
+ explanation_graph_facts: str = "",
201
+ ) -> None:
202
+ self.foil_atoms = set(foil_atoms)
203
+ self.added_rules = set(added_rules)
204
+ self.removed_rules = set(removed_rules)
205
+ self.reference_atoms = set(reference_atoms)
206
+ self.explanation_graph_facts = explanation_graph_facts
207
+
208
+ @classmethod
209
+ def from_explanation_graph(cls, foil_pg: str) -> "Foil":
210
+ """
211
+ Inspect the foil program graph to extract the foil model, added and removed rules.
212
+
213
+ Args:
214
+ foil_pg: The program graph of the foil model as a string of facts.
215
+
216
+ Returns:
217
+ A tuple containing three lists:
218
+ - foil_atoms: The atoms in the foil model.
219
+ - added_rules: The rules added in the foil model.
220
+ - removed_rules: The rules removed in the foil model.
221
+ """
222
+ ctl = Control()
223
+ ctl.add("base", [], foil_pg)
224
+ ctl.ground([("base", [])])
225
+ with ctl.solve(yield_=True) as handle:
226
+ model = handle.model()
227
+ log.debug("Inspecting foil model")
228
+ graph = Graph("".join([str(s) + "." for s in model.symbols(shown=True)]))
229
+ log.debug("Constructed graph")
230
+ added_rules = []
231
+ removed_rules = []
232
+ foil_atoms = []
233
+ reference_atoms = []
234
+ for node in graph._nodes.values(): # pylint: disable=protected-access
235
+ if node.type == "atom" and "foil" in node.models:
236
+ foil_atoms.append(node.id)
237
+ if node.type == "atom" and "ref" in node.models:
238
+ reference_atoms.append(node.id)
239
+ if node.programs == set(["ref"]):
240
+ removed_rules.append(node.tags["first_order"])
241
+ if node.programs == set(["foil"]):
242
+ added_rules.append(node.tags["first_order"])
243
+ return cls(foil_atoms, added_rules, removed_rules, reference_atoms, foil_pg) # type: ignore
244
+
245
+ def __eq__(self, other: object) -> bool:
246
+ if not isinstance(other, Foil):
247
+ return NotImplemented
248
+ return (
249
+ self.foil_atoms == other.foil_atoms
250
+ and self.added_rules == other.added_rules
251
+ and self.removed_rules == other.removed_rules
252
+ and self.reference_atoms == other.reference_atoms
253
+ )
254
+
255
+ def __ne__(self, other: object) -> bool:
256
+ return not self.__eq__(other)
257
+
258
+ def __getitem__(self, key: str) -> Any:
259
+ return getattr(self, key)
260
+
261
+ def __hash__(self) -> int:
262
+ return hash(
263
+ (
264
+ frozenset(self.foil_atoms),
265
+ frozenset(self.added_rules),
266
+ frozenset(self.removed_rules),
267
+ frozenset(self.reference_atoms),
268
+ )
269
+ )
270
+
271
+ def __repr__(self) -> str:
272
+ return (
273
+ f"Foil(reference_atoms={self.reference_atoms}, "
274
+ f"foil_atoms={self.foil_atoms}, "
275
+ f"added_rules={self.added_rules}, "
276
+ f"removed_rules={self.removed_rules})"
277
+ )
278
+
279
+ def print(self) -> None:
280
+ """
281
+ Print the foil model, added and removed rules.
282
+ """
283
+ print(colored("blue", "Foil model: " + " ".join([str(s) for s in self.foil_atoms])))
284
+ if len(self.removed_rules) > 0:
285
+ print(colored("red", " Removed: " + "\t".join([str(s) for s in self.removed_rules])))
286
+ if len(self.added_rules) > 0:
287
+ print(colored("green", " Added: " + "\t".join([str(s) for s in self.added_rules])))
asplain/__main__.py ADDED
@@ -0,0 +1,24 @@
1
+ """
2
+ The main entry point for the application.
3
+ """
4
+
5
+ import sys
6
+
7
+ from clingo import clingo_main
8
+
9
+ from asplain.app import AsplainApp
10
+
11
+ from .utils.clingo import parse_constants
12
+
13
+
14
+ def main() -> None:
15
+ """
16
+ Run the main function.
17
+ """
18
+ constants = parse_constants(sys.argv[2:])
19
+ clingo_main(AsplainApp(sys.argv[0], constants=constants), sys.argv[1:])
20
+ sys.exit()
21
+
22
+
23
+ if __name__ == "__main__":
24
+ main()