forked from logic-and-learning-lab/Popper
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprobpopper.py
More file actions
198 lines (158 loc) · 7.26 KB
/
probpopper.py
File metadata and controls
198 lines (158 loc) · 7.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/usr/bin/env python3
import logging
import sys
from popper.util import Settings, Stats, timeout, parse_settings, format_program
from popper.asp import ClingoGrounder, ClingoSolver
from popper.tester import Tester
from popper.constrain import Constrain
from popper.generate import generate_program
from popper.core import Grounding, Clause
class Outcome:
ALL = 'all'
SOME = 'some'
NONE = 'none'
class Con:
GENERALISATION = 'generalisation'
SPECIALISATION = 'specialisation'
REDUNDANCY = 'redundancy'
BANISH = 'banish'
OUTCOME_TO_CONSTRAINTS = {
(Outcome.ALL, Outcome.NONE) : (Con.BANISH,),
(Outcome.ALL, Outcome.SOME) : (Con.GENERALISATION,),
(Outcome.SOME, Outcome.NONE) : (Con.SPECIALISATION,),
(Outcome.SOME, Outcome.SOME) : (Con.SPECIALISATION, Con.GENERALISATION),
(Outcome.NONE, Outcome.NONE) : (Con.SPECIALISATION, Con.REDUNDANCY),
(Outcome.NONE, Outcome.SOME) : (Con.SPECIALISATION, Con.REDUNDANCY, Con.GENERALISATION)
}
def ground_rules(stats, grounder, max_clauses, max_vars, clauses):
out = set()
for clause in clauses:
head, body = clause
# find bindings for variables in the constraint
assignments = grounder.find_bindings(clause, max_clauses, max_vars)
# keep only standard literals
body = tuple(literal for literal in body if not literal.meta)
# ground the clause for each variable assignment
for assignment in assignments:
out.add(Grounding.ground_clause((head, body), assignment))
stats.register_ground_rules(out)
return out
def decide_outcome(conf_matrix, eps):
tp, fn, tn, fp = conf_matrix
if fn < eps:
positive_outcome = Outcome.ALL # complete
elif tp < eps and fn > 1 - eps:
positive_outcome = Outcome.NONE # totally incomplete
else:
positive_outcome = Outcome.SOME # incomplete
if fp < eps:
negative_outcome = Outcome.NONE # consistent
# elif FP == self.num_neg: # AC: this line may not work with minimal testing
# negative_outcome = Outcome.ALL # totally inconsistent
else:
negative_outcome = Outcome.SOME # inconsistent
return (positive_outcome, negative_outcome)
def pi_enabled(settings):
if "enable_pi" in settings.bias_string:
return True
else:
return False
def build_rules(settings, stats, constrainer, tester, program, before, min_clause, outcome):
(positive_outcome, negative_outcome) = outcome
# RM: If you don't use these two lines you need another three entries in the OUTCOME_TO_CONSTRAINTS table (one for every positive outcome combined with negative outcome ALL).
if negative_outcome == Outcome.ALL:
negative_outcome = Outcome.SOME
rules = set()
for constraint_type in OUTCOME_TO_CONSTRAINTS[(positive_outcome, negative_outcome)]:
if constraint_type == Con.GENERALISATION:
rules.update(constrainer.generalisation_constraint(program, before, min_clause))
elif constraint_type == Con.SPECIALISATION:
rules.update(constrainer.specialisation_constraint(program, before, min_clause))
elif constraint_type == Con.REDUNDANCY:
rules.update(constrainer.redundancy_constraint(program, before, min_clause))
elif constraint_type == Con.BANISH:
rules.update(constrainer.banish_constraint(program, before, min_clause))
if settings.functional_test and tester.is_non_functional(program):
rules.update(constrainer.generalisation_constraint(program, before, min_clause))
# eliminate generalisations of clauses that contain redundant literals
for rule in tester.check_redundant_literal(program):
rules.update(constrainer.redundant_literal_constraint(rule, before, min_clause))
# eliminate generalisations of programs that contain redundant clauses
if tester.check_redundant_clause(program):
rules.update(constrainer.generalisation_constraint(program, before, min_clause))
# pi interferes with checking clauses singularly in this implementation
if len(program) > 1 and not pi_enabled(settings):
# evaluate inconsistent sub-clauses
for rule in program:
if Clause.is_separable(rule) and tester.is_inconsistent(rule):
for x in constrainer.generalisation_constraint([rule], before, min_clause):
rules.add(x)
# eliminate totally incomplete rules
if all(Clause.is_separable(rule) for rule in program):
for rule in program:
if tester.is_totally_incomplete(rule):
for x in constrainer.redundancy_constraint([rule], before, min_clause):
rules.add(x)
stats.register_rules(rules)
return rules
PROG_KEY = 'prog'
def calc_score(conf_matrix):
tp, fn, tn, fp = conf_matrix
return tp + tn
def popper(settings, stats):
solver = ClingoSolver(settings)
tester = Tester(settings)
grounder = ClingoGrounder()
constrainer = Constrain()
best_score = None
for size in range(1, settings.max_literals + 1):
stats.update_num_literals(size)
solver.update_number_of_literals(size)
while True:
# GENERATE HYPOTHESIS
with stats.duration('generate'):
model = solver.get_model()
if not model:
break
(program, before, min_clause) = generate_program(model)
# TEST HYPOTHESIS
with stats.duration('test'):
conf_matrix = tester.test(program)
outcome = decide_outcome(conf_matrix, settings.eps)
score = calc_score(conf_matrix)
stats.register_program(program, conf_matrix)
# UPDATE BEST PROGRAM
if best_score == None or score > best_score:
best_score = score
if outcome == (Outcome.ALL, Outcome.NONE):
stats.register_solution(program, conf_matrix)
return stats.solution.code
stats.register_best_program(program, conf_matrix)
# BUILD RULES
with stats.duration('build'):
rules = build_rules(settings, stats, constrainer, tester, program, before, min_clause, outcome)
# GROUND RULES
with stats.duration('ground'):
rules = ground_rules(stats, grounder, solver.max_clauses, solver.max_vars, rules)
# UPDATE SOLVER
with stats.duration('add'):
solver.add_ground_clauses(rules)
stats.register_completion()
return stats.best_program.code if stats.best_program else None
def show_hspace(settings):
f = lambda i, m: print(f'% program {i}\n{format_program(generate_program(m)[0])}')
ClingoSolver.get_hspace(settings, f)
def learn_solution(settings):
log_level = logging.DEBUG if settings.debug else logging.INFO
logging.basicConfig(level=log_level, stream=sys.stderr, format='%(message)s')
stats = Stats(log_best_programs=settings.info)
timeout(popper, (settings, stats), timeout_duration=int(settings.timeout))
stats.log_final_result()
if settings.stats:
stats.show()
if __name__ == '__main__':
settings = parse_settings()
if settings.hspace:
show_hspace(settings)
else:
learn_solution(settings)