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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/*
* This file is part of the source code of the software program
* Vampire. It is protected by applicable
* copyright laws.
*
* This source code is distributed under the licence found here
* https://vprover.github.io/license.html
* and in the source directory
*/
/**
* @file InterpolantMinimizer.cpp
* Implements class InterpolantMinimizer.
* @author Bernhard Gleiss
*/
#if VZ3
#include "InterpolantMinimizer.hpp"
#include "Lib/VirtualIterator.hpp"
#include "Kernel/Unit.hpp"
#include <memory>
#include <unordered_set> // TODO: remove this after benchmarking
#include "z3++.h"
namespace Shell
{
using namespace std;
using namespace Kernel;
std::unordered_map<Kernel::Unit*, Kernel::Color> InterpolantMinimizer::computeSplittingFunction(Kernel::Unit* refutation, UnitWeight weightFunction)
{
using namespace z3;
context c;
optimize solver(c);
std::unordered_map<Unit*, std::unique_ptr<expr>> unitsToExpressions; // needed in order to map the result of the optimisation-query back to the inferences.
std::unordered_map<Unit*, std::unique_ptr<expr>> unitsToPenalties;
int i = 0; // counter needed for unique names
// note: idea from the thesis: we use x_i to denote whether inference i is assigned to the A-part.
ProofIteratorPostOrder it(refutation);
while (it.hasNext()) // traverse the proof in depth-first post order
{
Unit* current = it.next();
// construct a new expression representing the current inference and
// remember that the current inference is encoded by that expression
std::unique_ptr<expr> x_i_ptr (new expr(c));
*x_i_ptr = c.bool_const(("x_" + std::to_string(i)).c_str()); // ugly but necessary due to z3-API
unitsToExpressions.emplace(std::make_pair(current, std::move(x_i_ptr)));
// construct a new expression representing the current penalty and remember it
std::unique_ptr<expr> p_i_ptr (new expr(c));
*p_i_ptr = c.bool_const(("p_" + std::to_string(i)).c_str()); // ugly but necessary due to z3-API
unitsToPenalties.emplace(std::make_pair(current, std::move(p_i_ptr)));
// increase i in order to get unique names
i++;
expr& x_i = *unitsToExpressions.at(current);
// if inference is an axiom we need to assign it to the corresponding partition
if (current->inheritedColor() == COLOR_LEFT)
{
solver.add(x_i);
}
else if (current->inheritedColor() == COLOR_RIGHT)
{
solver.add(!x_i);
}
// if the conclusion contains a colored symbol, we need to assign the inference to the corresponding partition
if (current->getColor() == COLOR_LEFT)
{
solver.add(x_i);
}
else if (current->getColor() == COLOR_RIGHT)
{
solver.add(!x_i);
}
// if any parent contains a colored symbol, we need to assign the inference to the corresponding partition
VirtualIterator<Unit*> parents = current->getParents();
while (parents.hasNext())
{
Unit* premise= parents.next();
if (premise->getColor() == COLOR_LEFT)
{
solver.add(x_i);
}
else if (premise->getColor() == COLOR_RIGHT)
{
solver.add(!x_i);
}
}
// TODO: the above may be adding the same constraint more than once
// now add the main constraints: the conclusion of a parent-inference is included in the interpolant iff the
// the parent inference is assigned a different partition than the current inference
parents = current->getParents();
while (parents.hasNext())
{
Unit* premise= parents.next();
expr& x_j = *unitsToExpressions.at(premise);
expr& p_j = *unitsToPenalties.at(premise);
solver.add(!(x_i != x_j) || p_j);
}
}
// add the function we want to minimise to the solver
/*
* Note: we want to use the weighted sum of literals as measurement,
* but this doesn't exactly correspond to the size of the interpolant,
* since we are not counting the connectives.
* we can now use the fact that each included literal except the first one
* introduces exactly one connective (from an NNF-perspective), so we therefore
* add 1 to each weight. Afterwards we need to subtract 1, if there is at least one literal
* in the interpolant, since the first element doesn't introduce a connective.
*/
expr penaltyFunction = c.real_val(0);
for (const auto& keyValuePair : unitsToPenalties)
{
expr& p_i = *keyValuePair.second;
double weight = weightForUnit(keyValuePair.first, weightFunction) + 1;
expr weightExpression = c.real_val(std::to_string(weight).c_str());
penaltyFunction = penaltyFunction + ite(p_i, weightExpression, c.real_val(0));
}
solver.minimize(penaltyFunction);
// set a time limit for z3 call in order to being able to fallback to heuristic splitting function for very big proofs
/* ":timeout" seems not to by supported by (the current version of) Z3 in this context
params p(c);
p.set(":timeout", static_cast<unsigned>(60000)); // in milliseconds, i.e. 1 minute
solver.set(p);
*/
// we are now finished with adding constraints, so use z3 to compute an optimal model
check_result result = solver.check();
// if the optimization procedure finds any model
// Note: doesn't have to be the optimal one (Assumption: a non-optimal model still produces a better splitting function than the heuristic approach)
if (result == check_result::sat)
{
bool containsLeftInference = false; // needed for weight prediction
bool containsRightInference = false; // needed for weight prediction
// convert computed model to splitting function
model m = solver.get_model();
std::unordered_map<Kernel::Unit*, Kernel::Color> splittingFunction;
for (const auto& keyValuePair : unitsToExpressions)
{
Unit* current = keyValuePair.first;
expr evaluation = m.eval(*unitsToExpressions[current]);
if (Z3_get_bool_value(c,evaluation) == Z3_L_TRUE)
{
splittingFunction[current] = COLOR_LEFT;
containsLeftInference = true;
}
else
{
splittingFunction[current] = COLOR_RIGHT;
containsRightInference = true;
}
}
// print weight prediction
if (containsLeftInference && containsRightInference)
{
cout << "expecting interpolant weight " << m.eval(penaltyFunction - c.real_val(1)) << endl;
}
else
{
cout << "expecting interpolant weight " << m.eval(penaltyFunction) << endl;
}
return splittingFunction;
}
// otherwise use heuristic approach as fallback
else
{
return Interpolants::computeSplittingFunction(refutation, weightFunction);
}
}
void InterpolantMinimizer::analyzeLocalProof(Kernel::Unit *refutation)
{
// print statistics on grey area
analyzeGreyAreas(refutation);
// compute number of red subproofs
const std::unordered_map<Unit*, Color> splittingFunction = computeSplittingFunction(refutation, UnitWeight::VAMPIRE);
const std::unordered_map<Unit*, Unit*> unitsToRepresentative = computeSubproofs(refutation, splittingFunction);
std::unordered_set<Unit*> representatives;
for (const auto& keyValuePair : unitsToRepresentative)
{
representatives.insert(keyValuePair.second);
}
cout << "Number of red subproofs: " << representatives.size() << endl;
// compute number of alternations between red and blue subproofs
/*
* Def.: if we have a refutation
* A
* -----
* B
* -----
* A
* we count it as 2 alternations (although one could argue for 3 alternations)
*/
std::unordered_map<Unit*, int> alternations;
ProofIteratorPostOrder it(refutation);
while (it.hasNext()) // traverse the proof in depth-first post order
{
Unit* current = it.next();
// if current is an axiom
if (!current->getParents().hasNext())
{
alternations[current] = 0;
}
else
{
int alternations_max = 0;
VirtualIterator<Unit*> parents = current->getParents();
while (parents.hasNext())
{
Unit* premise= parents.next();
if (splittingFunction.at(premise) != splittingFunction.at(current))
{
alternations_max = std::max(alternations.at(premise) + 1, alternations_max);
}
else
{
alternations_max = std::max(alternations.at(premise), alternations_max);
}
}
alternations[current] = alternations_max;
}
}
cout << "Number of alternations: " << alternations.at(refutation) << endl;
}
/*
* compute both number of inferences which are necessarily assigned to red and to blue, and number of inferences which can be assigned arbitrarily
* computes percentage grey / (red + blue + grey)
*/
void InterpolantMinimizer::analyzeGreyAreas(Kernel::Unit* refutation)
{
int number_red = 0;
int number_blue = 0;
int number_grey = 0;
/*
* note: reuses code from heuristic splitting function
*/
ProofIteratorPostOrder it(refutation);
while (it.hasNext()) // traverse the proof in depth-first post order
{
Unit* current = it.next();
ASS((!current->getParents().hasNext() && (current->inheritedColor() == COLOR_LEFT || current->inheritedColor() == COLOR_RIGHT)) || (current->getParents().hasNext() && current->inheritedColor() == COLOR_INVALID));
// if the inference is an axiom, assign it to the corresponding partition
if (!current->getParents().hasNext())
{
if (current->inheritedColor() == COLOR_LEFT)
{
number_red++;
}
else
{
number_blue++;
}
continue;
}
// if the inference contains a colored symbol, assign it to the corresponding partition (this
// ensures requirement of a LOCAL splitting function in the words of the thesis):
// - this is the case if either the conclusion contains a colored symbol
if (current->getColor() == COLOR_LEFT)
{
number_red++;
continue;
}
else if (current->getColor() == COLOR_RIGHT)
{
number_blue++;
continue;
}
// - or if any premise contains a colored symbol
Color containedColor = COLOR_TRANSPARENT;
VirtualIterator<Unit*> parents = current->getParents();
while (parents.hasNext())
{
Unit* premise= parents.next();
if (premise->getColor() == COLOR_LEFT || premise->getColor() == COLOR_RIGHT)
{
containedColor = premise->getColor();
break;
}
}
if (containedColor == COLOR_LEFT)
{
number_red++;
continue;
}
else if (containedColor == COLOR_RIGHT)
{
number_blue++;
continue;
}
number_grey++;
}
cout << "Proof contains " << number_red << " / " << number_blue << " / " << number_grey << " inferences (red/blue/grey)" << endl;
cout << "Percentage of grey inferences: " << static_cast<double>(number_grey) / (number_red + number_blue + number_grey) << endl;
}
}
#endif // VZ3