clingexplaid 1.2.0__tar.gz → 1.2.2__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 (113) hide show
  1. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/PKG-INFO +1 -1
  2. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/mus/core_computer.py +63 -16
  3. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/transformer_rule_id.py +1 -1
  4. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid.egg-info/PKG-INFO +1 -1
  5. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_rule_ids.lp +1 -1
  6. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/test_muc.py +30 -8
  7. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/.envrc +0 -0
  8. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/.github/workflows/deploy.yml +0 -0
  9. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/.github/workflows/test.yml +0 -0
  10. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/.gitignore +0 -0
  11. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/.pre-commit-config.yaml +0 -0
  12. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/.readthedocs.yaml +0 -0
  13. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/CHANGES.md +0 -0
  14. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/CONTRIBUTING.md +0 -0
  15. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/DEPLOYMENT.md +0 -0
  16. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/DEVELOPMENT.md +0 -0
  17. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/LICENSE +0 -0
  18. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/LIMITATIONS.md +0 -0
  19. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/README.md +0 -0
  20. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/TODO.md +0 -0
  21. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/Makefile +0 -0
  22. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/_static/css/custom.css +0 -0
  23. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/_static/logo-dark-mode.png +0 -0
  24. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/_static/logo-light-mode.png +0 -0
  25. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/conf.py +0 -0
  26. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/content/encodings/index.md +0 -0
  27. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/content/encodings/instance.md +0 -0
  28. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/content/installation.md +0 -0
  29. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/content/quickstart.md +0 -0
  30. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/doc/index.md +0 -0
  31. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/graph_coloring/README.md +0 -0
  32. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/graph_coloring/encoding.lp +0 -0
  33. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/graph_coloring/graph_coloring_example.svg +0 -0
  34. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/graph_coloring/instance.lp +0 -0
  35. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/bad_mucs.lp +0 -0
  36. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/sat.lp +0 -0
  37. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/sat_simple.lp +0 -0
  38. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/simple.lp +0 -0
  39. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/soup.lp +0 -0
  40. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/sudoku_4x4.lp +0 -0
  41. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/sudoku_encoding.lp +0 -0
  42. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/sudoku_encoding_2.lp +0 -0
  43. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/test/blob.lp +0 -0
  44. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/test.lp +0 -0
  45. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/test2.lp +0 -0
  46. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/test3.lp +0 -0
  47. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/test4.lp +0 -0
  48. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/test_inert.lp +0 -0
  49. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/x.lp +0 -0
  50. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/misc/zero_arity_assumptions.lp +0 -0
  51. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/queens/README.md +0 -0
  52. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/queens/encoding.lp +0 -0
  53. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/queens/instance.lp +0 -0
  54. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/queens/queens_example.svg +0 -0
  55. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/sudoku/README.md +0 -0
  56. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/sudoku/encoding.lp +0 -0
  57. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/sudoku/instance.lp +0 -0
  58. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/examples/sudoku/sudoku_example.svg +0 -0
  59. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/noxfile.py +0 -0
  60. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/pyproject.toml +0 -0
  61. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/setup.cfg +0 -0
  62. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/__init__.py +0 -0
  63. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/mus/__init__.py +0 -0
  64. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/propagators/__init__.py +0 -0
  65. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/propagators/constants.py +0 -0
  66. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/propagators/propagator_solver_decisions.py +0 -0
  67. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/py.typed +0 -0
  68. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/__init__.py +0 -0
  69. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/constants.py +0 -0
  70. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/exceptions.py +0 -0
  71. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/transformer_assumption.py +0 -0
  72. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/transformer_constraint.py +0 -0
  73. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/transformer_fact.py +0 -0
  74. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/transformer_optimization_remover.py +0 -0
  75. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/transformers/transformer_rule_splitter.py +0 -0
  76. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/unsat_constraints/__init__.py +0 -0
  77. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/unsat_constraints/constants.py +0 -0
  78. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/unsat_constraints/unsat_constraint_computer.py +0 -0
  79. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/utils/__init__.py +0 -0
  80. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/utils/logging.py +0 -0
  81. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/utils/match.py +0 -0
  82. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/utils/parser.py +0 -0
  83. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/utils/symbols.py +0 -0
  84. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid/utils/types.py +0 -0
  85. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid.egg-info/SOURCES.txt +0 -0
  86. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid.egg-info/dependency_links.txt +0 -0
  87. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid.egg-info/requires.txt +0 -0
  88. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/src/clingexplaid.egg-info/top_level.txt +0 -0
  89. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/__init__.py +0 -0
  90. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/__init__.py +0 -0
  91. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program.lp +0 -0
  92. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program_constants.lp +0 -0
  93. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program_constraints.lp +0 -0
  94. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program_decision_order.lp +0 -0
  95. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program_multi_mus.lp +0 -0
  96. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program_optimization.lp +0 -0
  97. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program_pattern.lp +0 -0
  98. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program_rules.lp +0 -0
  99. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/test_program_unsat_constraints.lp +0 -0
  100. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_assumptions_all.lp +0 -0
  101. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_assumptions_certain_signatures.lp +0 -0
  102. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_constraints.lp +0 -0
  103. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_constraints_id.lp +0 -0
  104. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_facts.lp +0 -0
  105. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_optimization.lp +0 -0
  106. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_pattern.lp +0 -0
  107. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/res/transformed_program_rules_split.lp +0 -0
  108. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/test_main.py +0 -0
  109. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/test_transformers.py +0 -0
  110. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/test_unsat_constraints.py +0 -0
  111. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/clingexplaid/test_utils.py +0 -0
  112. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/py.typed +0 -0
  113. {clingexplaid-1.2.0 → clingexplaid-1.2.2}/tests/test_main.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clingexplaid
3
- Version: 1.2.0
3
+ Version: 1.2.2
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>
@@ -2,9 +2,11 @@
2
2
  MUS Module: Core Computer to get Minimal Unsatisfiable Subsets
3
3
  """
4
4
 
5
+ import asyncio
5
6
  import warnings
7
+ from dataclasses import dataclass
6
8
  from itertools import chain, combinations
7
- from typing import Dict, Generator, List, Optional, Set, Tuple
9
+ from typing import Dict, Generator, Iterable, Iterator, List, Optional, Set, Tuple, Union
8
10
 
9
11
  import clingo
10
12
 
@@ -12,6 +14,17 @@ from ..utils import get_solver_literal_lookup
12
14
  from ..utils.types import Assumption, AssumptionSet, SymbolSet
13
15
 
14
16
 
17
+ @dataclass
18
+ class UnsatisfiableSubset:
19
+ """Container class for unsatisfiable subsets"""
20
+
21
+ assumptions: Set[Assumption]
22
+ minimal: bool = False
23
+
24
+ def __iter__(self) -> Iterator[Union[tuple[clingo.Symbol, bool], int]]:
25
+ return self.assumptions.__iter__()
26
+
27
+
15
28
  class CoreComputer:
16
29
  """
17
30
  A container class that allows for a passed program_string and assumption_set to compute a minimal unsatisfiable
@@ -22,7 +35,9 @@ class CoreComputer:
22
35
  self.control = control
23
36
  self.assumption_set = assumption_set
24
37
  self.literal_lookup = get_solver_literal_lookup(control=self.control)
25
- self.minimal: Optional[AssumptionSet] = None
38
+ self.minimal: Optional[UnsatisfiableSubset] = None
39
+ self._assumptions_minimal: Set[Assumption] = set()
40
+ self._assumptions_removed: Set[Assumption] = set()
26
41
 
27
42
  def _solve(self, assumptions: Optional[AssumptionSet] = None) -> Tuple[bool, SymbolSet, SymbolSet]:
28
43
  """
@@ -38,7 +53,7 @@ class CoreComputer:
38
53
 
39
54
  return satisfiable, set(model), core
40
55
 
41
- def _compute_single_minimal(self, assumptions: Optional[AssumptionSet] = None) -> AssumptionSet:
56
+ async def _compute_single_minimal(self, assumptions: Optional[AssumptionSet] = None) -> UnsatisfiableSubset:
42
57
  """
43
58
  Function to compute a single minimal unsatisfiable subset from the passed set of assumptions and the program of
44
59
  the CoreComputer. If there is no minimal unsatisfiable subset, since for example the program with assumptions
@@ -48,6 +63,9 @@ class CoreComputer:
48
63
  if assumptions is None:
49
64
  assumptions = self.assumption_set
50
65
 
66
+ # Reset progress set
67
+ self._assumptions_minimal = set()
68
+
51
69
  # check that the assumption set isn't empty
52
70
  if not assumptions:
53
71
  warnings.warn("A minimal unsatisfiable subset cannot be computed on an empty assumption set")
@@ -56,37 +74,62 @@ class CoreComputer:
56
74
  # rest of the algorithm and return an empty set.
57
75
  satisfiable, _, _ = self._solve(assumptions=assumptions)
58
76
  if satisfiable:
59
- return set()
77
+ return UnsatisfiableSubset(set())
60
78
 
61
- mus_members: Set[Assumption] = set()
62
79
  working_set = set(assumptions)
63
80
 
64
81
  for assumption in assumptions:
65
82
  # remove the current assumption from the working set
66
83
  working_set.remove(assumption)
67
84
 
68
- satisfiable, _, _ = self._solve(assumptions=working_set.union(mus_members))
85
+ satisfiable, _, _ = self._solve(assumptions=working_set.union(self._assumptions_minimal))
69
86
  # if the working set now becomes satisfiable without the assumption it is added to the mus_members
70
87
  if satisfiable:
71
- mus_members.add(assumption)
88
+ self._assumptions_minimal.add(assumption)
72
89
  # every time we discover a new mus member we also check if all currently found mus members already
73
90
  # suffice to make the instance unsatisfiable. If so we can stop the search sice we found our mus.
74
- if not self._solve(assumptions=mus_members)[0]:
91
+ if not self._solve(assumptions=self._assumptions_minimal)[0]:
75
92
  break
76
-
77
- return mus_members
78
-
79
- def shrink(self, assumptions: Optional[AssumptionSet] = None) -> AssumptionSet:
93
+ else:
94
+ # Remove the current assumption since it's not part of the mus
95
+ self._assumptions_removed.add(assumption)
96
+
97
+ # Do a very short wait every time to allow for async timeout interruption
98
+ await asyncio.sleep(0.0000001)
99
+
100
+ return UnsatisfiableSubset(self._assumptions_minimal, minimal=True)
101
+
102
+ async def _shrink_unsatisfiable_subset(
103
+ self, assumptions: Optional[AssumptionSet] = None, timeout: Optional[float] = None
104
+ ) -> UnsatisfiableSubset:
105
+ timeout_occurred = False
106
+ try:
107
+ await asyncio.wait_for(self._compute_single_minimal(assumptions=assumptions), timeout=timeout)
108
+ unsat_subset = self._assumptions_minimal
109
+ except TimeoutError:
110
+ timeout_occurred = True
111
+ warnings.warn(
112
+ "Timeout encountered while computing unsatisfiable subset, "
113
+ "intermediate unsatisfiable subset is returned"
114
+ )
115
+ # Remove the already excluded assumptions from the original set and return them
116
+ provided_assumptions = set(assumptions) if assumptions is not None else set(self.assumption_set)
117
+ unsat_subset = provided_assumptions.difference(self._assumptions_removed)
118
+ return UnsatisfiableSubset(unsat_subset, minimal=not timeout_occurred)
119
+
120
+ def shrink(
121
+ self, assumptions: Optional[AssumptionSet] = None, timeout: Optional[float] = None
122
+ ) -> UnsatisfiableSubset:
80
123
  """
81
124
  This function applies the unsatisfiable subset minimization (`self._compute_single_minimal`) on the assumptions
82
125
  set `assumptions` and stores the resulting MUS inside `self.minimal`.
83
126
 
84
127
  Returns the MUS as a set of assumptions.
85
128
  """
86
- self.minimal = self._compute_single_minimal(assumptions=assumptions)
129
+ self.minimal = asyncio.run(self._shrink_unsatisfiable_subset(assumptions=assumptions, timeout=timeout))
87
130
  return self.minimal
88
131
 
89
- def get_multiple_minimal(self, max_mus: Optional[int] = None) -> Generator[AssumptionSet, None, None]:
132
+ def get_multiple_minimal(self, max_mus: Optional[int] = None) -> Generator[UnsatisfiableSubset, None, None]:
90
133
  """
91
134
  This function generates all minimal unsatisfiable subsets of the provided assumption set. It implements the
92
135
  generator pattern since finding all mus of an assumption set is exponential in nature and the search might not
@@ -112,7 +155,7 @@ class CoreComputer:
112
155
  if any(set(muc).issubset(current_subset) for muc in found_mucs):
113
156
  continue
114
157
 
115
- muc = self._compute_single_minimal(assumptions=current_subset)
158
+ muc = asyncio.run(self._compute_single_minimal(assumptions=current_subset))
116
159
 
117
160
  # if the current subset wasn't unsatisfiable store this info and continue
118
161
  if len(list(muc)) == 0:
@@ -127,7 +170,11 @@ class CoreComputer:
127
170
  if max_mus is not None and len(found_mucs) == max_mus:
128
171
  break
129
172
 
130
- def mus_to_string(self, muc: AssumptionSet, literal_lookup: Optional[Dict[int, clingo.Symbol]] = None) -> Set[str]:
173
+ def mus_to_string(
174
+ self,
175
+ muc: Iterable[Union[Tuple[clingo.Symbol, bool], int]],
176
+ literal_lookup: Optional[Dict[int, clingo.Symbol]] = None,
177
+ ) -> Set[str]:
131
178
  """
132
179
  Converts a MUS into a set containing the string representations of the contained assumptions
133
180
  """
@@ -55,7 +55,7 @@ class RuleIDTransformer(_ast.Transformer):
55
55
  out = []
56
56
  _ast.parse_string(string, lambda stm: out.append((str(self(stm)))))
57
57
  out.append(
58
- f"{{_rule(1..{self._get_number_of_rules()})}}"
58
+ f"{{_rule(1..{self._get_number_of_rules()})}}."
59
59
  f" % Choice rule to allow all _rule atoms to become assumptions"
60
60
  )
61
61
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clingexplaid
3
- Version: 1.2.0
3
+ Version: 1.2.2
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>
@@ -6,4 +6,4 @@ d((10..15)) :- _rule(4).
6
6
  e(16) :- _rule(5).
7
7
  f(17); f(18) :- e(16); _rule(6).
8
8
  x(19) :- _rule(7).
9
- {_rule(1..7)} % Choice rule to allow all _rule atoms to become assumptions
9
+ {_rule(1..7)}. % Choice rule to allow all _rule atoms to become assumptions
@@ -2,6 +2,7 @@
2
2
  Tests for the mus package
3
3
  """
4
4
 
5
+ import asyncio
5
6
  import random
6
7
  from typing import Iterable, List, Optional, Sequence, Set, Tuple, Union
7
8
  from unittest import TestCase
@@ -9,9 +10,9 @@ from unittest import TestCase
9
10
  import clingo
10
11
 
11
12
  from clingexplaid.mus import CoreComputer
13
+ from clingexplaid.mus.core_computer import UnsatisfiableSubset
12
14
  from clingexplaid.transformers import AssumptionTransformer
13
15
  from clingexplaid.transformers.transformer_assumption import FilterPattern, FilterSignature
14
- from clingexplaid.utils.types import AssumptionSet
15
16
 
16
17
  from .test_main import TEST_DIR
17
18
 
@@ -20,7 +21,8 @@ def get_mus_of_program(
20
21
  program_string: str,
21
22
  assumption_filters: Optional[Iterable[Union[FilterPattern, FilterSignature]]] = None,
22
23
  control: Optional[clingo.Control] = None,
23
- ) -> Tuple[AssumptionSet, CoreComputer]:
24
+ timeout: Optional[float] = None,
25
+ ) -> Tuple[UnsatisfiableSubset, CoreComputer]:
24
26
  """
25
27
  Helper function to directly get the MUS of a given program string.
26
28
  """
@@ -43,12 +45,12 @@ def get_mus_of_program(
43
45
  cc = CoreComputer(ctl, assumptions)
44
46
 
45
47
  def shrink_on_model(core: Sequence[int]) -> None:
46
- _ = cc.shrink(core)
48
+ _ = cc.shrink(core, timeout=timeout)
47
49
 
48
50
  ctl.solve(assumptions=list(assumptions), on_core=shrink_on_model)
49
51
 
50
52
  # if the instance was satisfiable and the on_core function wasn't called an empty set is returned, else the mus.
51
- result = cc.minimal if cc.minimal is not None else set()
53
+ result = cc.minimal if cc.minimal is not None else UnsatisfiableSubset(set())
52
54
 
53
55
  return result, cc
54
56
 
@@ -178,6 +180,25 @@ class TestMUS(TestCase):
178
180
  self.fail()
179
181
  self._assert_mus(cc.mus_to_string(mus), [{f"a({i})" for i in random_core}])
180
182
 
183
+ def test_core_computer_shrink_timeout(self) -> None:
184
+ """
185
+ Test the CoreComputer's `shrink` function with a satisfiable assumption set.
186
+ """
187
+
188
+ ctl = clingo.Control()
189
+
190
+ n_assumptions = 3000
191
+ random_core = random.choices(range(1, n_assumptions), k=500)
192
+ program = f"""
193
+ a(1..{n_assumptions}).
194
+ :- {', '.join([f"a({i})" for i in random_core])}.
195
+ """
196
+ filters = {FilterSignature("a", 1)}
197
+
198
+ mus, _ = get_mus_of_program(program_string=program, assumption_filters=filters, control=ctl, timeout=1)
199
+
200
+ self.assertEqual(mus.minimal, False)
201
+
181
202
  def test_core_computer_shrink_satisfiable(self) -> None:
182
203
  """
183
204
  Test the CoreComputer's `shrink` function with a satisfiable assumption set.
@@ -192,7 +213,7 @@ class TestMUS(TestCase):
192
213
 
193
214
  mus, _ = get_mus_of_program(program_string=program, assumption_filters=filters, control=ctl)
194
215
 
195
- self.assertEqual(mus, set())
216
+ self.assertEqual(mus, UnsatisfiableSubset(set()))
196
217
 
197
218
  def test_core_computer_get_multiple_minimal(self) -> None:
198
219
  """
@@ -265,8 +286,8 @@ class TestMUS(TestCase):
265
286
  control.ground([("base", [])])
266
287
  assumptions = {(clingo.parse_term(c), True) for c in "abc"}
267
288
  cc = CoreComputer(control, assumptions)
268
- mus = cc._compute_single_minimal() # pylint: disable=W0212
269
- self.assertEqual(mus, set())
289
+ mus = asyncio.run(cc._compute_single_minimal()) # pylint: disable=W0212
290
+ self.assertEqual(mus, UnsatisfiableSubset(set()))
270
291
 
271
292
  def test_core_computer_internal_compute_single_minimal_no_assumptions(self) -> None:
272
293
  """
@@ -276,7 +297,8 @@ class TestMUS(TestCase):
276
297
  control = clingo.Control()
277
298
  cc = CoreComputer(control, set())
278
299
  # Disabled exception assertion due to change in error handling
279
- # self.assertRaises(ValueError, cc._compute_single_minimal) # pylint: disable=W0212
300
+ mus = asyncio.run(cc._compute_single_minimal(assumptions=None)) # pylint: disable=W0212
301
+ self.assertEqual(mus, UnsatisfiableSubset(set()))
280
302
  cc.shrink([])
281
303
 
282
304
  def test_core_computer_mus_to_string(self) -> None:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes