clingexplaid 1.2.2__tar.gz → 1.3.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.
Files changed (119) hide show
  1. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/PKG-INFO +37 -18
  2. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/README.md +36 -17
  3. clingexplaid-1.3.0/src/clingexplaid/cli/style/mus.py +5 -0
  4. clingexplaid-1.3.0/src/clingexplaid/exceptions/__init__.py +8 -0
  5. clingexplaid-1.3.0/src/clingexplaid/preprocessors/__init__.py +5 -0
  6. clingexplaid-1.3.0/src/clingexplaid/preprocessors/preprocessor_assumption.py +210 -0
  7. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/transformers/__init__.py +0 -2
  8. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid.egg-info/PKG-INFO +37 -18
  9. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid.egg-info/SOURCES.txt +6 -2
  10. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_assumptions_all.lp +1 -1
  11. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_assumptions_certain_signatures.lp +1 -1
  12. clingexplaid-1.3.0/tests/clingexplaid/res/transformed_program_nothing_transformed.lp +13 -0
  13. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/test_muc.py +13 -14
  14. clingexplaid-1.3.0/tests/clingexplaid/test_preprocessors.py +88 -0
  15. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/test_transformers.py +0 -69
  16. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/test_utils.py +27 -0
  17. clingexplaid-1.2.2/src/clingexplaid/transformers/exceptions.py +0 -15
  18. clingexplaid-1.2.2/src/clingexplaid/transformers/transformer_assumption.py +0 -151
  19. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/.envrc +0 -0
  20. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/.github/workflows/deploy.yml +0 -0
  21. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/.github/workflows/test.yml +0 -0
  22. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/.gitignore +0 -0
  23. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/.pre-commit-config.yaml +0 -0
  24. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/.readthedocs.yaml +0 -0
  25. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/CHANGES.md +0 -0
  26. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/CONTRIBUTING.md +0 -0
  27. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/DEPLOYMENT.md +0 -0
  28. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/DEVELOPMENT.md +0 -0
  29. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/LICENSE +0 -0
  30. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/LIMITATIONS.md +0 -0
  31. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/TODO.md +0 -0
  32. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/Makefile +0 -0
  33. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/_static/css/custom.css +0 -0
  34. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/_static/logo-dark-mode.png +0 -0
  35. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/_static/logo-light-mode.png +0 -0
  36. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/conf.py +0 -0
  37. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/content/encodings/index.md +0 -0
  38. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/content/encodings/instance.md +0 -0
  39. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/content/installation.md +0 -0
  40. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/content/quickstart.md +0 -0
  41. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/doc/index.md +0 -0
  42. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/graph_coloring/README.md +0 -0
  43. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/graph_coloring/encoding.lp +0 -0
  44. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/graph_coloring/graph_coloring_example.svg +0 -0
  45. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/graph_coloring/instance.lp +0 -0
  46. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/bad_mucs.lp +0 -0
  47. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/sat.lp +0 -0
  48. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/sat_simple.lp +0 -0
  49. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/simple.lp +0 -0
  50. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/soup.lp +0 -0
  51. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/sudoku_4x4.lp +0 -0
  52. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/sudoku_encoding.lp +0 -0
  53. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/sudoku_encoding_2.lp +0 -0
  54. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/test/blob.lp +0 -0
  55. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/test.lp +0 -0
  56. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/test2.lp +0 -0
  57. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/test3.lp +0 -0
  58. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/test4.lp +0 -0
  59. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/test_inert.lp +0 -0
  60. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/x.lp +0 -0
  61. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/misc/zero_arity_assumptions.lp +0 -0
  62. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/queens/README.md +0 -0
  63. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/queens/encoding.lp +0 -0
  64. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/queens/instance.lp +0 -0
  65. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/queens/queens_example.svg +0 -0
  66. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/sudoku/README.md +0 -0
  67. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/sudoku/encoding.lp +0 -0
  68. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/sudoku/instance.lp +0 -0
  69. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/examples/sudoku/sudoku_example.svg +0 -0
  70. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/noxfile.py +0 -0
  71. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/pyproject.toml +0 -0
  72. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/setup.cfg +0 -0
  73. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/__init__.py +0 -0
  74. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/mus/__init__.py +0 -0
  75. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/mus/core_computer.py +0 -0
  76. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/propagators/__init__.py +0 -0
  77. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/propagators/constants.py +0 -0
  78. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/propagators/propagator_solver_decisions.py +0 -0
  79. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/py.typed +0 -0
  80. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/transformers/constants.py +0 -0
  81. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/transformers/transformer_constraint.py +0 -0
  82. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/transformers/transformer_fact.py +0 -0
  83. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/transformers/transformer_optimization_remover.py +0 -0
  84. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/transformers/transformer_rule_id.py +0 -0
  85. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/transformers/transformer_rule_splitter.py +0 -0
  86. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/unsat_constraints/__init__.py +0 -0
  87. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/unsat_constraints/constants.py +0 -0
  88. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/unsat_constraints/unsat_constraint_computer.py +0 -0
  89. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/utils/__init__.py +0 -0
  90. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/utils/logging.py +0 -0
  91. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/utils/match.py +0 -0
  92. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/utils/parser.py +0 -0
  93. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/utils/symbols.py +0 -0
  94. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid/utils/types.py +0 -0
  95. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid.egg-info/dependency_links.txt +0 -0
  96. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid.egg-info/requires.txt +0 -0
  97. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/src/clingexplaid.egg-info/top_level.txt +0 -0
  98. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/__init__.py +0 -0
  99. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/__init__.py +0 -0
  100. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program.lp +0 -0
  101. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program_constants.lp +0 -0
  102. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program_constraints.lp +0 -0
  103. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program_decision_order.lp +0 -0
  104. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program_multi_mus.lp +0 -0
  105. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program_optimization.lp +0 -0
  106. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program_pattern.lp +0 -0
  107. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program_rules.lp +0 -0
  108. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/test_program_unsat_constraints.lp +0 -0
  109. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_constraints.lp +0 -0
  110. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_constraints_id.lp +0 -0
  111. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_facts.lp +0 -0
  112. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_optimization.lp +0 -0
  113. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_pattern.lp +0 -0
  114. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_rule_ids.lp +0 -0
  115. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/res/transformed_program_rules_split.lp +0 -0
  116. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/test_main.py +0 -0
  117. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/clingexplaid/test_unsat_constraints.py +0 -0
  118. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/py.typed +0 -0
  119. {clingexplaid-1.2.2 → clingexplaid-1.3.0}/tests/test_main.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clingexplaid
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Tools to aid the development of explanation systems using clingo
5
5
  Author-email: Hannes Weichelt <hweichelt@uni-potsdam.de>, Susana Hahn <susuhahnml@yahoo.com.mx>
6
6
  Maintainer-email: Hannes Weichelt <hweichelt@uni-potsdam.de>
@@ -96,12 +96,37 @@ Here are two example for using `clingexplaid`'s API.
96
96
 
97
97
  ### Minimal Unsatisfiable Subsets (MUS)
98
98
 
99
+ Transforming facts to Assumptions (necessary pre-processing step):
100
+
101
+ ```python
102
+ from clingexplaid.preprocessors import AssumptionPreprocessor
103
+ from clingexplaid.preprocessors import (
104
+ FilterSignature,
105
+ FilterPattern,
106
+ )
107
+
108
+ PROGRAM = """
109
+ a(book;magazine;video).
110
+ b(test).
111
+ c(1..10).
112
+ d(1..3).
113
+ """
114
+
115
+ ap = AssumptionPreprocessor(filters=[
116
+ FilterSignature("a", 1),
117
+ FilterPattern("d(2)")
118
+ ])
119
+ result = ap.process(PROGRAM)
120
+ # You can either use the return value of `ap.process`
121
+ print(result)
122
+ # Or use `ap.control` with your transformed program already added
123
+ print(ap.control)
124
+ ```
125
+
99
126
  Getting a single MUS:
100
127
 
101
128
  ```python
102
- import clingo
103
- from clingexplaid.transformers import AssumptionTransformer
104
- from clingexplaid.transformers.transformer_assumption import FilterSignature
129
+ from clingexplaid.preprocessors import AssumptionPreprocessor, FilterSignature
105
130
  from clingexplaid.mus import CoreComputer
106
131
 
107
132
  PROGRAM = """
@@ -113,25 +138,19 @@ a(X) :- b(X).
113
138
  :- a(X), X>=3.
114
139
  """
115
140
 
116
- control = clingo.Control()
117
- at = AssumptionTransformer(filters={FilterSignature("a", 1)})
118
-
119
- transformed_program = at.parse_string(PROGRAM)
120
-
121
- control.add("base", [], transformed_program)
122
- control.ground([("base", [])])
123
-
124
- assumptions = at.get_assumption_literals(control)
125
-
126
- cc = CoreComputer(control, assumptions)
127
-
141
+ ap = AssumptionPreprocessor(filters={FilterSignature("a", 1)})
142
+ ap.process(PROGRAM)
143
+ ap.control.ground([("base", [])])
144
+ cc = CoreComputer(ap.control, ap.assumptions)
128
145
 
129
146
  def shrink_on_core(core) -> None:
130
147
  mus_literals = cc.shrink(core)
131
148
  print("MUS:", cc.mus_to_string(mus_literals))
132
149
 
133
-
134
- control.solve(assumptions=list(assumptions), on_core=shrink_on_core)
150
+ ap.control.solve(
151
+ assumptions=list(ap.assumptions),
152
+ on_core=shrink_on_core
153
+ )
135
154
  ```
136
155
 
137
156
  Getting multiple MUS:
@@ -27,12 +27,37 @@ Here are two example for using `clingexplaid`'s API.
27
27
 
28
28
  ### Minimal Unsatisfiable Subsets (MUS)
29
29
 
30
+ Transforming facts to Assumptions (necessary pre-processing step):
31
+
32
+ ```python
33
+ from clingexplaid.preprocessors import AssumptionPreprocessor
34
+ from clingexplaid.preprocessors import (
35
+ FilterSignature,
36
+ FilterPattern,
37
+ )
38
+
39
+ PROGRAM = """
40
+ a(book;magazine;video).
41
+ b(test).
42
+ c(1..10).
43
+ d(1..3).
44
+ """
45
+
46
+ ap = AssumptionPreprocessor(filters=[
47
+ FilterSignature("a", 1),
48
+ FilterPattern("d(2)")
49
+ ])
50
+ result = ap.process(PROGRAM)
51
+ # You can either use the return value of `ap.process`
52
+ print(result)
53
+ # Or use `ap.control` with your transformed program already added
54
+ print(ap.control)
55
+ ```
56
+
30
57
  Getting a single MUS:
31
58
 
32
59
  ```python
33
- import clingo
34
- from clingexplaid.transformers import AssumptionTransformer
35
- from clingexplaid.transformers.transformer_assumption import FilterSignature
60
+ from clingexplaid.preprocessors import AssumptionPreprocessor, FilterSignature
36
61
  from clingexplaid.mus import CoreComputer
37
62
 
38
63
  PROGRAM = """
@@ -44,25 +69,19 @@ a(X) :- b(X).
44
69
  :- a(X), X>=3.
45
70
  """
46
71
 
47
- control = clingo.Control()
48
- at = AssumptionTransformer(filters={FilterSignature("a", 1)})
49
-
50
- transformed_program = at.parse_string(PROGRAM)
51
-
52
- control.add("base", [], transformed_program)
53
- control.ground([("base", [])])
54
-
55
- assumptions = at.get_assumption_literals(control)
56
-
57
- cc = CoreComputer(control, assumptions)
58
-
72
+ ap = AssumptionPreprocessor(filters={FilterSignature("a", 1)})
73
+ ap.process(PROGRAM)
74
+ ap.control.ground([("base", [])])
75
+ cc = CoreComputer(ap.control, ap.assumptions)
59
76
 
60
77
  def shrink_on_core(core) -> None:
61
78
  mus_literals = cc.shrink(core)
62
79
  print("MUS:", cc.mus_to_string(mus_literals))
63
80
 
64
-
65
- control.solve(assumptions=list(assumptions), on_core=shrink_on_core)
81
+ ap.control.solve(
82
+ assumptions=list(ap.assumptions),
83
+ on_core=shrink_on_core
84
+ )
66
85
  ```
67
86
 
68
87
  Getting multiple MUS:
@@ -0,0 +1,5 @@
1
+ MODE_MUS_STYLE = """
2
+ #content.mode-mus{
3
+
4
+ }
5
+ """
@@ -0,0 +1,8 @@
1
+ """Exceptions for ClingExplaid"""
2
+
3
+
4
+ class UnprocessedException(Exception):
5
+ """
6
+ Exception raised if the assumptions property of an AssumptionPreprocessor is called before it is used to
7
+ preprocess a program.
8
+ """
@@ -0,0 +1,5 @@
1
+ """Preprocessors for Clingexplaid"""
2
+
3
+ from .preprocessor_assumption import AssumptionPreprocessor, FilterPattern, FilterSignature
4
+
5
+ __all__ = ["AssumptionPreprocessor", "FilterPattern", "FilterSignature"]
@@ -0,0 +1,210 @@
1
+ """
2
+ Transformer Module: Assumption Transformer for converting facts to choices that can be assumed
3
+ """
4
+
5
+ import warnings
6
+ from dataclasses import dataclass
7
+ from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
8
+
9
+ import clingo
10
+ from clingo.ast import ProgramBuilder, parse_string
11
+
12
+ from ..exceptions import UnprocessedException
13
+ from ..utils.match import match
14
+
15
+
16
+ @dataclass
17
+ class FilterSignature:
18
+ """Filters the facts converted to assumptions by signature"""
19
+
20
+ name: str
21
+ arity: int
22
+
23
+ def __hash__(self) -> int:
24
+ return hash((self.name, self.arity))
25
+
26
+
27
+ @dataclass
28
+ class FilterPattern:
29
+ """Filters the facts converted to assumptions by pattern"""
30
+
31
+ pattern: str
32
+
33
+ def __hash__(self) -> int:
34
+ return hash(self.pattern)
35
+
36
+
37
+ class AssumptionPreprocessor:
38
+ """
39
+ A transformer that transforms facts that match with one of the signatures provided (no signatures means all facts)
40
+ into choice rules and also provides the according assumptions for them.
41
+ """
42
+
43
+ # pylint: disable=too-many-instance-attributes
44
+
45
+ def __init__(
46
+ self,
47
+ filters: Optional[Iterable[Union[FilterPattern, FilterSignature]]] = None,
48
+ control: Optional[clingo.Control] = None,
49
+ fail_on_unprocessed: bool = True,
50
+ ):
51
+ self.control = control if control is not None else clingo.Control()
52
+ self.filters: Set[Union[FilterPattern, FilterSignature]] = set(filters) if filters is not None else set()
53
+ self._filters_convert_nothing = bool(filters is not None and len(list(filters)) == 0)
54
+ self._processed = False
55
+ self._fail_on_unprocessed = fail_on_unprocessed
56
+ self._parsed_rules: List[str] = []
57
+ self._constants: Dict[str, clingo.Symbol] = {}
58
+ self._assumptions: Set[Tuple[clingo.Symbol, bool]] = set()
59
+
60
+ if self._filters_convert_nothing:
61
+ warnings.warn("When an empty list of filters is provided, no facts will be transformed to assumptions")
62
+
63
+ @staticmethod
64
+ def _to_ast(symbol: Union[str, clingo.Symbol]) -> clingo.ast.AST:
65
+ parsed_ast: List[clingo.ast.AST] = []
66
+ parse_string(f"{symbol}.", parsed_ast.append)
67
+ ast_symbol = parsed_ast[1] # return AST symbol (parsed_ast[0] = '#program base.')
68
+ return ast_symbol
69
+
70
+ def _add_assumption(self, symbol: clingo.Symbol, positive: bool) -> None:
71
+ self._assumptions.add((symbol, positive))
72
+
73
+ def _add_assumption_string(self, string: str, positive: bool) -> None:
74
+ self._add_assumption(clingo.parse_term(string), positive)
75
+
76
+ def _add_constant(self, name: str, symbol: clingo.Symbol) -> None:
77
+ self._constants[name] = symbol
78
+
79
+ def _any_filters_apply(self, symbol: clingo.Symbol) -> bool:
80
+ if self._filters_convert_nothing:
81
+ # Convert nothing
82
+ return False
83
+ if not self._filters_convert_nothing and len(self.filters) == 0:
84
+ # Convert everything
85
+ return True
86
+ applies = False
87
+ for symbol_filter in self.filters:
88
+ match symbol_filter:
89
+ case FilterPattern(pattern=pattern):
90
+ applies |= bool(match(pattern, symbol))
91
+ case FilterSignature(name=name, arity=arity):
92
+ applies |= len(symbol.arguments) == arity and symbol.name == name
93
+ return applies
94
+
95
+ @staticmethod
96
+ def _unpool(ast_symbol: clingo.ast.ASTType.SymbolicAtom) -> Set[clingo.Symbol]:
97
+ if ".." in str(ast_symbol):
98
+ # Case range in ast symbol (i.e. 1..10)
99
+ # Solved using grounding, but if possible, I'd rather avoid this if possible
100
+ unpool_control = clingo.Control()
101
+ unpool_control.add(f"{ast_symbol}.")
102
+ unpool_control.ground([("base", [])])
103
+ with unpool_control.solve(yield_=True) as solve_handle:
104
+ result = solve_handle.get()
105
+ if result.satisfiable:
106
+ model = solve_handle.model()
107
+ return set(model.symbols(atoms=True))
108
+ raise ValueError("Provided AST symbol does not follow valid ASP syntax") # nocoverage
109
+ # Case default
110
+ atoms_unpooled = ast_symbol.unpool()
111
+ return {clingo.parse_term(str(a)) for a in atoms_unpooled}
112
+
113
+ def _transform_rule(self, rule: clingo.ast.ASTType.Rule) -> List[clingo.ast.ASTType.Rule]:
114
+ if rule.head.ast_type != clingo.ast.ASTType.Literal:
115
+ return [rule]
116
+ if rule.body:
117
+ return [rule]
118
+
119
+ atoms_unpooled = AssumptionPreprocessor._unpool(rule.head)
120
+ atoms_choice: Set[clingo.ast.AST] = set()
121
+ atoms_retained: Set[clingo.Symbol] = set()
122
+ for atom in atoms_unpooled:
123
+ filters_apply = self._any_filters_apply(atom)
124
+ # if filters are defined, only transform facts that match them, else transform all facts
125
+ if not filters_apply:
126
+ atoms_retained.add(atom)
127
+ continue
128
+ ast = AssumptionPreprocessor._to_ast(atom)
129
+ ast_choice_literal = clingo.ast.ConditionalLiteral(location=rule.location, literal=ast.head, condition=[])
130
+ atoms_choice.add(ast_choice_literal)
131
+ atoms_retained_ast: Set[clingo.ast.AST] = set()
132
+ for atom in atoms_retained:
133
+ atoms_retained_ast.add(AssumptionPreprocessor._to_ast(atom))
134
+
135
+ if len(atoms_choice) > 0:
136
+ # Add all choice atoms to the assumption list
137
+ for a_choice in atoms_choice:
138
+ self._add_assumption_string(str(a_choice), True)
139
+
140
+ choice_rule = clingo.ast.Rule(
141
+ location=rule.location,
142
+ head=clingo.ast.Aggregate(
143
+ location=rule.location,
144
+ left_guard=None,
145
+ elements=list(sorted(atoms_choice, key=str)),
146
+ right_guard=None,
147
+ ),
148
+ body=[],
149
+ )
150
+ return [choice_rule, *sorted(atoms_retained_ast, key=str)]
151
+ return list(sorted(atoms_retained_ast, key=str))
152
+
153
+ def register_ast(self, ast: clingo.ast.AST, builder: clingo.ast.ProgramBuilder) -> None:
154
+ """Registers the provided AST to the builder and the parsed rules list"""
155
+ if ast.ast_type == clingo.ast.ASTType.Definition:
156
+ self._add_constant(str(ast.name), ast.value.symbol)
157
+ self._parsed_rules.append(str(ast))
158
+ warnings.warn(
159
+ "Currently the ProgramBuilder does not fill the control. Please add the processed programm manually."
160
+ )
161
+ builder.add(ast)
162
+
163
+ def process(self, program_string: str) -> str:
164
+ """Processes the provided program string and returns the transformed program string (control is also updated)"""
165
+ control = clingo.Control("0")
166
+ ast_list: List[clingo.ast.AST] = []
167
+ with ProgramBuilder(control) as builder:
168
+ parse_string(program_string, ast_list.append)
169
+ for ast in ast_list:
170
+ if ast.ast_type == clingo.ast.ASTType.Rule:
171
+ for new_ast in self._transform_rule(ast):
172
+ if new_ast.ast_type != clingo.ast.ASTType.Rule: # nocoverage
173
+ new_rule = clingo.ast.Rule(location=ast.location, head=new_ast, body=[])
174
+ self.register_ast(new_rule, builder)
175
+ else:
176
+ self.register_ast(new_ast, builder)
177
+ elif ast.ast_type == clingo.ast.ASTType.Definition:
178
+ self.register_ast(ast, builder)
179
+ else:
180
+ self.register_ast(ast, builder)
181
+ self._processed = True
182
+ return "\n".join(self._parsed_rules)
183
+
184
+ @property
185
+ def assumptions(self) -> Set[Tuple[clingo.Symbol, bool]]:
186
+ """Property that returns the assumptions generated by the preprocessor after calling `process`"""
187
+ if not self._processed:
188
+ if self._fail_on_unprocessed:
189
+ raise UnprocessedException(
190
+ "It is impossible to retrieve assumptions without invoking the `process` function first"
191
+ )
192
+ warnings.warn(
193
+ "Unprocessed Error: It is impossible to retrieve assumptions without invoking the "
194
+ "`process` function first"
195
+ ) # nocoverage
196
+ return set(self._assumptions)
197
+
198
+ @property
199
+ def constants(self) -> Dict[str, clingo.Symbol]:
200
+ """Property that returns the constants generated by the preprocessor after calling `process`"""
201
+ if not self._processed: # nocoverage
202
+ if self._fail_on_unprocessed:
203
+ raise UnprocessedException(
204
+ "It is impossible to retrieve constants without invoking the `process` function first"
205
+ )
206
+ warnings.warn(
207
+ "Unprocessed Error: It is impossible to retrieve constants without invoking the "
208
+ "`process function first`"
209
+ )
210
+ return self._constants
@@ -2,7 +2,6 @@
2
2
  Transformers for Explanation
3
3
  """
4
4
 
5
- from .transformer_assumption import AssumptionTransformer
6
5
  from .transformer_constraint import ConstraintTransformer
7
6
  from .transformer_fact import FactTransformer
8
7
  from .transformer_optimization_remover import OptimizationRemover
@@ -10,7 +9,6 @@ from .transformer_rule_id import RuleIDTransformer
10
9
  from .transformer_rule_splitter import RuleSplitter
11
10
 
12
11
  __all__ = [
13
- "AssumptionTransformer",
14
12
  "ConstraintTransformer",
15
13
  "FactTransformer",
16
14
  "OptimizationRemover",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clingexplaid
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Tools to aid the development of explanation systems using clingo
5
5
  Author-email: Hannes Weichelt <hweichelt@uni-potsdam.de>, Susana Hahn <susuhahnml@yahoo.com.mx>
6
6
  Maintainer-email: Hannes Weichelt <hweichelt@uni-potsdam.de>
@@ -96,12 +96,37 @@ Here are two example for using `clingexplaid`'s API.
96
96
 
97
97
  ### Minimal Unsatisfiable Subsets (MUS)
98
98
 
99
+ Transforming facts to Assumptions (necessary pre-processing step):
100
+
101
+ ```python
102
+ from clingexplaid.preprocessors import AssumptionPreprocessor
103
+ from clingexplaid.preprocessors import (
104
+ FilterSignature,
105
+ FilterPattern,
106
+ )
107
+
108
+ PROGRAM = """
109
+ a(book;magazine;video).
110
+ b(test).
111
+ c(1..10).
112
+ d(1..3).
113
+ """
114
+
115
+ ap = AssumptionPreprocessor(filters=[
116
+ FilterSignature("a", 1),
117
+ FilterPattern("d(2)")
118
+ ])
119
+ result = ap.process(PROGRAM)
120
+ # You can either use the return value of `ap.process`
121
+ print(result)
122
+ # Or use `ap.control` with your transformed program already added
123
+ print(ap.control)
124
+ ```
125
+
99
126
  Getting a single MUS:
100
127
 
101
128
  ```python
102
- import clingo
103
- from clingexplaid.transformers import AssumptionTransformer
104
- from clingexplaid.transformers.transformer_assumption import FilterSignature
129
+ from clingexplaid.preprocessors import AssumptionPreprocessor, FilterSignature
105
130
  from clingexplaid.mus import CoreComputer
106
131
 
107
132
  PROGRAM = """
@@ -113,25 +138,19 @@ a(X) :- b(X).
113
138
  :- a(X), X>=3.
114
139
  """
115
140
 
116
- control = clingo.Control()
117
- at = AssumptionTransformer(filters={FilterSignature("a", 1)})
118
-
119
- transformed_program = at.parse_string(PROGRAM)
120
-
121
- control.add("base", [], transformed_program)
122
- control.ground([("base", [])])
123
-
124
- assumptions = at.get_assumption_literals(control)
125
-
126
- cc = CoreComputer(control, assumptions)
127
-
141
+ ap = AssumptionPreprocessor(filters={FilterSignature("a", 1)})
142
+ ap.process(PROGRAM)
143
+ ap.control.ground([("base", [])])
144
+ cc = CoreComputer(ap.control, ap.assumptions)
128
145
 
129
146
  def shrink_on_core(core) -> None:
130
147
  mus_literals = cc.shrink(core)
131
148
  print("MUS:", cc.mus_to_string(mus_literals))
132
149
 
133
-
134
- control.solve(assumptions=list(assumptions), on_core=shrink_on_core)
150
+ ap.control.solve(
151
+ assumptions=list(ap.assumptions),
152
+ on_core=shrink_on_core
153
+ )
135
154
  ```
136
155
 
137
156
  Getting multiple MUS:
@@ -59,15 +59,17 @@ src/clingexplaid.egg-info/SOURCES.txt
59
59
  src/clingexplaid.egg-info/dependency_links.txt
60
60
  src/clingexplaid.egg-info/requires.txt
61
61
  src/clingexplaid.egg-info/top_level.txt
62
+ src/clingexplaid/cli/style/mus.py
63
+ src/clingexplaid/exceptions/__init__.py
62
64
  src/clingexplaid/mus/__init__.py
63
65
  src/clingexplaid/mus/core_computer.py
66
+ src/clingexplaid/preprocessors/__init__.py
67
+ src/clingexplaid/preprocessors/preprocessor_assumption.py
64
68
  src/clingexplaid/propagators/__init__.py
65
69
  src/clingexplaid/propagators/constants.py
66
70
  src/clingexplaid/propagators/propagator_solver_decisions.py
67
71
  src/clingexplaid/transformers/__init__.py
68
72
  src/clingexplaid/transformers/constants.py
69
- src/clingexplaid/transformers/exceptions.py
70
- src/clingexplaid/transformers/transformer_assumption.py
71
73
  src/clingexplaid/transformers/transformer_constraint.py
72
74
  src/clingexplaid/transformers/transformer_fact.py
73
75
  src/clingexplaid/transformers/transformer_optimization_remover.py
@@ -88,6 +90,7 @@ tests/test_main.py
88
90
  tests/clingexplaid/__init__.py
89
91
  tests/clingexplaid/test_main.py
90
92
  tests/clingexplaid/test_muc.py
93
+ tests/clingexplaid/test_preprocessors.py
91
94
  tests/clingexplaid/test_transformers.py
92
95
  tests/clingexplaid/test_unsat_constraints.py
93
96
  tests/clingexplaid/test_utils.py
@@ -105,6 +108,7 @@ tests/clingexplaid/res/transformed_program_assumptions_certain_signatures.lp
105
108
  tests/clingexplaid/res/transformed_program_constraints.lp
106
109
  tests/clingexplaid/res/transformed_program_constraints_id.lp
107
110
  tests/clingexplaid/res/transformed_program_facts.lp
111
+ tests/clingexplaid/res/transformed_program_nothing_transformed.lp
108
112
  tests/clingexplaid/res/transformed_program_optimization.lp
109
113
  tests/clingexplaid/res/transformed_program_pattern.lp
110
114
  tests/clingexplaid/res/transformed_program_rule_ids.lp
@@ -2,7 +2,7 @@
2
2
  { a(1) }.
3
3
  b(2) :- x.
4
4
  c(3); c(4) :- x.
5
- { d((10..15)) }.
5
+ { d(10); d(11); d(12); d(13); d(14); d(15) }.
6
6
  { e(16) }.
7
7
  f(17); f(18) :- e(16).
8
8
  { x(19) }.
@@ -2,7 +2,7 @@
2
2
  { a(1) }.
3
3
  b(2) :- x.
4
4
  c(3); c(4) :- x.
5
- { d((10..15)) }.
5
+ { d(10); d(11); d(12); d(13); d(14); d(15) }.
6
6
  { e(16) }.
7
7
  f(17); f(18) :- e(16).
8
8
  x(19).
@@ -0,0 +1,13 @@
1
+ #program base.
2
+ a(1).
3
+ b(2) :- x.
4
+ c(3); c(4) :- x.
5
+ d(10).
6
+ d(11).
7
+ d(12).
8
+ d(13).
9
+ d(14).
10
+ d(15).
11
+ e(16).
12
+ f(17); f(18) :- e(16).
13
+ x(19).
@@ -11,8 +11,7 @@ import clingo
11
11
 
12
12
  from clingexplaid.mus import CoreComputer
13
13
  from clingexplaid.mus.core_computer import UnsatisfiableSubset
14
- from clingexplaid.transformers import AssumptionTransformer
15
- from clingexplaid.transformers.transformer_assumption import FilterPattern, FilterSignature
14
+ from clingexplaid.preprocessors import AssumptionPreprocessor, FilterPattern, FilterSignature
16
15
 
17
16
  from .test_main import TEST_DIR
18
17
 
@@ -34,20 +33,18 @@ def get_mus_of_program(
34
33
 
35
34
  ctl = control if control is not None else clingo.Control()
36
35
 
37
- at = AssumptionTransformer(filters=assumption_filters)
38
- transformed_program = at.parse_string(program_string)
36
+ ap = AssumptionPreprocessor(filters=assumption_filters)
37
+ transformed_program = ap.process(program_string)
39
38
 
40
39
  ctl.add("base", [], transformed_program)
41
40
  ctl.ground([("base", [])])
42
41
 
43
- assumptions = at.get_assumption_literals(ctl)
44
-
45
- cc = CoreComputer(ctl, assumptions)
42
+ cc = CoreComputer(ctl, ap.assumptions)
46
43
 
47
44
  def shrink_on_model(core: Sequence[int]) -> None:
48
45
  _ = cc.shrink(core, timeout=timeout)
49
46
 
50
- ctl.solve(assumptions=list(assumptions), on_core=shrink_on_model)
47
+ ctl.solve(assumptions=list(ap.assumptions), on_core=shrink_on_model)
51
48
 
52
49
  # if the instance was satisfiable and the on_core function wasn't called an empty set is returned, else the mus.
53
50
  result = cc.minimal if cc.minimal is not None else UnsatisfiableSubset(set())
@@ -223,11 +220,12 @@ class TestMUS(TestCase):
223
220
  ctl = clingo.Control()
224
221
 
225
222
  program_path = TEST_DIR.joinpath("res/test_program_multi_mus.lp")
226
- at = AssumptionTransformer(filters={FilterSignature("a", 1)})
227
- parsed = at.parse_files([program_path])
223
+ ap = AssumptionPreprocessor(filters={FilterSignature("a", 1)})
224
+ with open(program_path, "r", encoding="utf-8") as file:
225
+ parsed = ap.process(file.read())
228
226
  ctl.add("base", [], parsed)
229
227
  ctl.ground([("base", [])])
230
- cc = CoreComputer(ctl, at.get_assumption_literals(ctl))
228
+ cc = CoreComputer(ctl, ap.assumptions)
231
229
 
232
230
  mus_generator = cc.get_multiple_minimal()
233
231
 
@@ -246,11 +244,12 @@ class TestMUS(TestCase):
246
244
  ctl = clingo.Control()
247
245
 
248
246
  program_path = TEST_DIR.joinpath("res/test_program_multi_mus.lp")
249
- at = AssumptionTransformer(filters={FilterSignature("a", 1)})
250
- parsed = at.parse_files([program_path])
247
+ ap = AssumptionPreprocessor(filters={FilterSignature("a", 1)})
248
+ with open(program_path, "r", encoding="utf-8") as file:
249
+ parsed = ap.process(file.read())
251
250
  ctl.add("base", [], parsed)
252
251
  ctl.ground([("base", [])])
253
- cc = CoreComputer(ctl, at.get_assumption_literals(ctl))
252
+ cc = CoreComputer(ctl, ap.assumptions)
254
253
 
255
254
  mus_generator = cc.get_multiple_minimal(max_mus=2)
256
255