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.
- asplain/__init__.py +287 -0
- asplain/__main__.py +24 -0
- asplain/app.py +485 -0
- asplain/encodings/construct-foil.lp +30 -0
- asplain/encodings/costs/docs.lp +10 -0
- asplain/encodings/costs/model-difference.lp +5 -0
- asplain/encodings/costs/penalize-added.lp +2 -0
- asplain/encodings/costs/penalize-non-assumptions-removed.lp +2 -0
- asplain/encodings/costs/penalize-non-constraint-removed.lp +2 -0
- asplain/encodings/costs/penalize-non-facts-removed.lp +2 -0
- asplain/encodings/costs/penalize-removed.lp +2 -0
- asplain/encodings/costs/program-difference.lp +5 -0
- asplain/encodings/dynamic-tags/removable-assumptions.lp +2 -0
- asplain/encodings/force-model.lp +5 -0
- asplain/encodings/model-subgraph.lp +17 -0
- asplain/encodings/pruning/change.lp +2 -0
- asplain/encodings/pruning/changes.lp +12 -0
- asplain/encodings/pruning/inclusion_filter.lp +26 -0
- asplain/encodings/pruning/orphans.lp +11 -0
- asplain/encodings/pruning/path_test.lp +20 -0
- asplain/encodings/pruning/paths.lp +38 -0
- asplain/encodings/pruning/paths_undirected.lp +37 -0
- asplain/encodings/reify-to-pg.lp +75 -0
- asplain/encodings/show.lp +23 -0
- asplain/encodings/ui.lp +639 -0
- asplain/encodings/utils-tags.lp +19 -0
- asplain/encodings/viz-pg.lp +108 -0
- asplain/llm/__init__.py +0 -0
- asplain/llm/models/__init__.py +7 -0
- asplain/llm/models/base.py +32 -0
- asplain/llm/models/google.py +42 -0
- asplain/llm/models/openai.py +60 -0
- asplain/llm/models/tags.py +27 -0
- asplain/llm/templates/__init__.py +8 -0
- asplain/llm/templates/base.py +15 -0
- asplain/llm/templates/explain.py +36 -0
- asplain/llm/templates/prompt_templates/explain_input.txt +4 -0
- asplain/llm/templates/prompt_templates/explain_instructions.txt +294 -0
- asplain/llm/utils/__init__.py +8 -0
- asplain/llm/utils/graph.py +216 -0
- asplain/llm/utils/parsing.py +21 -0
- asplain/llm/utils/predicates.py +106 -0
- asplain/pruning/__init__.py +0 -0
- asplain/pruning/pruners.py +127 -0
- asplain/py.typed +0 -0
- asplain/ui/backend.py +416 -0
- asplain/utils/__init__.py +3 -0
- asplain/utils/clingo.py +166 -0
- asplain/utils/logging.py +135 -0
- asplain/utils/node-count.lp +10 -0
- asplain/utils/parser.py +53 -0
- asplain/utils/viz.py +48 -0
- asplain-0.0.0.dist-info/METADATA +125 -0
- asplain-0.0.0.dist-info/RECORD +58 -0
- asplain-0.0.0.dist-info/WHEEL +5 -0
- asplain-0.0.0.dist-info/entry_points.txt +2 -0
- asplain-0.0.0.dist-info/licenses/LICENSE +21 -0
- 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()
|