1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds, Tianyi Liang, Andres Noetzli |
4 |
|
* |
5 |
|
* This file is part of the cvc5 project. |
6 |
|
* |
7 |
|
* Copyright (c) 2009-2021 by the authors listed in the file AUTHORS |
8 |
|
* in the top-level source directory and their institutional affiliations. |
9 |
|
* All rights reserved. See the file COPYING in the top-level source |
10 |
|
* directory for licensing information. |
11 |
|
* **************************************************************************** |
12 |
|
* |
13 |
|
* Implementation of the theory of strings. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "theory/strings/theory_strings.h" |
17 |
|
|
18 |
|
#include "expr/attribute.h" |
19 |
|
#include "expr/bound_var_manager.h" |
20 |
|
#include "expr/kind.h" |
21 |
|
#include "expr/sequence.h" |
22 |
|
#include "options/smt_options.h" |
23 |
|
#include "options/strings_options.h" |
24 |
|
#include "options/theory_options.h" |
25 |
|
#include "smt/logic_exception.h" |
26 |
|
#include "theory/decision_manager.h" |
27 |
|
#include "theory/ext_theory.h" |
28 |
|
#include "theory/rewriter.h" |
29 |
|
#include "theory/strings/theory_strings_utils.h" |
30 |
|
#include "theory/strings/type_enumerator.h" |
31 |
|
#include "theory/strings/word.h" |
32 |
|
#include "theory/theory_model.h" |
33 |
|
#include "theory/valuation.h" |
34 |
|
|
35 |
|
using namespace std; |
36 |
|
using namespace cvc5::context; |
37 |
|
using namespace cvc5::kind; |
38 |
|
|
39 |
|
namespace cvc5 { |
40 |
|
namespace theory { |
41 |
|
namespace strings { |
42 |
|
|
43 |
|
/** |
44 |
|
* Attribute used for making unique (bound variables) which correspond to |
45 |
|
* unique element values used in sequence models. See use in collectModelValues |
46 |
|
* below. |
47 |
|
*/ |
48 |
|
struct SeqModelVarAttributeId |
49 |
|
{ |
50 |
|
}; |
51 |
|
using SeqModelVarAttribute = expr::Attribute<SeqModelVarAttributeId, Node>; |
52 |
|
|
53 |
9943 |
TheoryStrings::TheoryStrings(Env& env, OutputChannel& out, Valuation valuation) |
54 |
|
: Theory(THEORY_STRINGS, env, out, valuation), |
55 |
|
d_notify(*this), |
56 |
|
d_statistics(), |
57 |
|
d_state(env, d_valuation), |
58 |
|
d_eagerSolver(d_state), |
59 |
|
d_termReg(env, d_state, d_statistics, d_pnm), |
60 |
|
d_extTheoryCb(), |
61 |
|
d_im(env, *this, d_state, d_termReg, d_extTheory, d_statistics, d_pnm), |
62 |
|
d_extTheory(d_extTheoryCb, context(), userContext(), d_im), |
63 |
|
d_rewriter(&d_statistics.d_rewrites), |
64 |
|
d_bsolver(env, d_state, d_im), |
65 |
|
d_csolver(env, d_state, d_im, d_termReg, d_bsolver), |
66 |
|
d_esolver(env, |
67 |
|
d_state, |
68 |
|
d_im, |
69 |
|
d_termReg, |
70 |
|
d_rewriter, |
71 |
|
d_bsolver, |
72 |
|
d_csolver, |
73 |
|
d_extTheory, |
74 |
|
d_statistics), |
75 |
|
d_rsolver(env, |
76 |
|
d_state, |
77 |
|
d_im, |
78 |
|
d_termReg.getSkolemCache(), |
79 |
|
d_csolver, |
80 |
|
d_esolver, |
81 |
|
d_statistics), |
82 |
19886 |
d_regexp_elim(options::regExpElimAgg(), d_pnm, userContext()), |
83 |
29829 |
d_stringsFmf(env, valuation, d_termReg) |
84 |
|
{ |
85 |
9943 |
d_termReg.finishInit(&d_im); |
86 |
|
|
87 |
9943 |
d_zero = NodeManager::currentNM()->mkConst( Rational( 0 ) ); |
88 |
9943 |
d_one = NodeManager::currentNM()->mkConst( Rational( 1 ) ); |
89 |
9943 |
d_neg_one = NodeManager::currentNM()->mkConst(Rational(-1)); |
90 |
9943 |
d_true = NodeManager::currentNM()->mkConst( true ); |
91 |
9943 |
d_false = NodeManager::currentNM()->mkConst( false ); |
92 |
|
|
93 |
9943 |
d_cardSize = utils::getAlphabetCardinality(); |
94 |
|
|
95 |
|
// set up the extended function callback |
96 |
9943 |
d_extTheoryCb.d_esolver = &d_esolver; |
97 |
|
|
98 |
|
// use the state object as the official theory state |
99 |
9943 |
d_theoryState = &d_state; |
100 |
|
// use the inference manager as the official inference manager |
101 |
9943 |
d_inferManager = &d_im; |
102 |
9943 |
} |
103 |
|
|
104 |
19880 |
TheoryStrings::~TheoryStrings() { |
105 |
|
|
106 |
19880 |
} |
107 |
|
|
108 |
9943 |
TheoryRewriter* TheoryStrings::getTheoryRewriter() { return &d_rewriter; } |
109 |
|
|
110 |
3794 |
ProofRuleChecker* TheoryStrings::getProofChecker() { return &d_checker; } |
111 |
|
|
112 |
9943 |
bool TheoryStrings::needsEqualityEngine(EeSetupInfo& esi) |
113 |
|
{ |
114 |
9943 |
esi.d_notify = &d_notify; |
115 |
9943 |
esi.d_name = "theory::strings::ee"; |
116 |
9943 |
esi.d_notifyNewClass = true; |
117 |
9943 |
esi.d_notifyMerge = true; |
118 |
9943 |
esi.d_notifyDisequal = true; |
119 |
9943 |
return true; |
120 |
|
} |
121 |
|
|
122 |
9943 |
void TheoryStrings::finishInit() |
123 |
|
{ |
124 |
9943 |
Assert(d_equalityEngine != nullptr); |
125 |
|
|
126 |
|
// witness is used to eliminate str.from_code |
127 |
9943 |
d_valuation.setUnevaluatedKind(WITNESS); |
128 |
|
|
129 |
9943 |
bool eagerEval = options::stringEagerEval(); |
130 |
|
// The kinds we are treating as function application in congruence |
131 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_LENGTH, eagerEval); |
132 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_CONCAT, eagerEval); |
133 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_IN_REGEXP, eagerEval); |
134 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_TO_CODE, eagerEval); |
135 |
9943 |
d_equalityEngine->addFunctionKind(kind::SEQ_UNIT, eagerEval); |
136 |
|
// `seq.nth` is not always defined, and so we do not evaluate it eagerly. |
137 |
9943 |
d_equalityEngine->addFunctionKind(kind::SEQ_NTH, false); |
138 |
|
// extended functions |
139 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_CONTAINS, eagerEval); |
140 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_LEQ, eagerEval); |
141 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_SUBSTR, eagerEval); |
142 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_UPDATE, eagerEval); |
143 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_ITOS, eagerEval); |
144 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_STOI, eagerEval); |
145 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_INDEXOF, eagerEval); |
146 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_INDEXOF_RE, eagerEval); |
147 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_REPLACE, eagerEval); |
148 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_REPLACE_ALL, eagerEval); |
149 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_REPLACE_RE, eagerEval); |
150 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_REPLACE_RE_ALL, eagerEval); |
151 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_REPLACE_ALL, eagerEval); |
152 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_TOLOWER, eagerEval); |
153 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_TOUPPER, eagerEval); |
154 |
9943 |
d_equalityEngine->addFunctionKind(kind::STRING_REV, eagerEval); |
155 |
9943 |
} |
156 |
|
|
157 |
|
std::string TheoryStrings::identify() const |
158 |
|
{ |
159 |
|
return std::string("TheoryStrings"); |
160 |
|
} |
161 |
|
|
162 |
7129 |
bool TheoryStrings::areCareDisequal( TNode x, TNode y ) { |
163 |
7129 |
Assert(d_equalityEngine->hasTerm(x)); |
164 |
7129 |
Assert(d_equalityEngine->hasTerm(y)); |
165 |
21387 |
if (d_equalityEngine->isTriggerTerm(x, THEORY_STRINGS) |
166 |
21387 |
&& d_equalityEngine->isTriggerTerm(y, THEORY_STRINGS)) |
167 |
|
{ |
168 |
|
TNode x_shared = |
169 |
8579 |
d_equalityEngine->getTriggerTermRepresentative(x, THEORY_STRINGS); |
170 |
|
TNode y_shared = |
171 |
8579 |
d_equalityEngine->getTriggerTermRepresentative(y, THEORY_STRINGS); |
172 |
5723 |
EqualityStatus eqStatus = d_valuation.getEqualityStatus(x_shared, y_shared); |
173 |
5723 |
if( eqStatus==EQUALITY_FALSE_AND_PROPAGATED || eqStatus==EQUALITY_FALSE || eqStatus==EQUALITY_FALSE_IN_MODEL ){ |
174 |
2867 |
return true; |
175 |
|
} |
176 |
|
} |
177 |
4262 |
return false; |
178 |
|
} |
179 |
|
|
180 |
1073722 |
bool TheoryStrings::propagateLit(TNode literal) |
181 |
|
{ |
182 |
1073722 |
return d_im.propagateLit(literal); |
183 |
|
} |
184 |
|
|
185 |
47099 |
TrustNode TheoryStrings::explain(TNode literal) |
186 |
|
{ |
187 |
47099 |
Debug("strings-explain") << "explain called on " << literal << std::endl; |
188 |
47099 |
return d_im.explainLit(literal); |
189 |
|
} |
190 |
|
|
191 |
15265 |
void TheoryStrings::presolve() { |
192 |
15265 |
Debug("strings-presolve") << "TheoryStrings::Presolving : get fmf options " << (options::stringFMF() ? "true" : "false") << std::endl; |
193 |
15265 |
d_strat.initializeStrategy(); |
194 |
|
|
195 |
|
// if strings fmf is enabled, register the strategy |
196 |
15265 |
if (options::stringFMF()) |
197 |
|
{ |
198 |
78 |
d_stringsFmf.presolve(); |
199 |
|
// This strategy is local to a check-sat call, since we refresh the strategy |
200 |
|
// on every call to presolve. |
201 |
78 |
d_im.getDecisionManager()->registerStrategy( |
202 |
|
DecisionManager::STRAT_STRINGS_SUM_LENGTHS, |
203 |
|
d_stringsFmf.getDecisionStrategy(), |
204 |
|
DecisionManager::STRAT_SCOPE_LOCAL_SOLVE); |
205 |
|
} |
206 |
15265 |
Debug("strings-presolve") << "Finished presolve" << std::endl; |
207 |
15265 |
} |
208 |
|
|
209 |
|
|
210 |
|
///////////////////////////////////////////////////////////////////////////// |
211 |
|
// MODEL GENERATION |
212 |
|
///////////////////////////////////////////////////////////////////////////// |
213 |
|
|
214 |
5257 |
bool TheoryStrings::collectModelValues(TheoryModel* m, |
215 |
|
const std::set<Node>& termSet) |
216 |
|
{ |
217 |
5257 |
if (Trace.isOn("strings-debug-model")) |
218 |
|
{ |
219 |
|
Trace("strings-debug-model") |
220 |
|
<< "TheoryStrings::collectModelValues" << std::endl; |
221 |
|
Trace("strings-debug-model") << "Equivalence classes are:" << std::endl; |
222 |
|
Trace("strings-debug-model") << debugPrintStringsEqc() << std::endl; |
223 |
|
Trace("strings-debug-model") << "Extended functions are:" << std::endl; |
224 |
|
Trace("strings-debug-model") << d_esolver.debugPrintModel() << std::endl; |
225 |
|
} |
226 |
5257 |
Trace("strings-model") << "TheoryStrings::collectModelValues" << std::endl; |
227 |
|
// Collects representatives by types and orders sequence types by how nested |
228 |
|
// they are |
229 |
10514 |
std::map<TypeNode, std::unordered_set<Node> > repSet; |
230 |
10514 |
std::unordered_set<TypeNode> toProcess; |
231 |
|
// Generate model |
232 |
|
// get the relevant string equivalence classes |
233 |
98559 |
for (const Node& s : termSet) |
234 |
|
{ |
235 |
186604 |
TypeNode tn = s.getType(); |
236 |
93302 |
if (tn.isStringLike()) |
237 |
|
{ |
238 |
91290 |
Node r = d_state.getRepresentative(s); |
239 |
45645 |
repSet[tn].insert(r); |
240 |
45645 |
toProcess.insert(tn); |
241 |
|
} |
242 |
|
} |
243 |
|
|
244 |
7057 |
while (!toProcess.empty()) |
245 |
|
{ |
246 |
|
// Pick one of the remaining types to collect model values for |
247 |
1802 |
TypeNode tn = *toProcess.begin(); |
248 |
902 |
if (!collectModelInfoType(tn, toProcess, repSet, m)) |
249 |
|
{ |
250 |
|
return false; |
251 |
|
} |
252 |
|
} |
253 |
|
|
254 |
5256 |
return true; |
255 |
|
} |
256 |
|
|
257 |
901 |
bool TheoryStrings::collectModelInfoType( |
258 |
|
TypeNode tn, |
259 |
|
std::unordered_set<TypeNode>& toProcess, |
260 |
|
const std::map<TypeNode, std::unordered_set<Node> >& repSet, |
261 |
|
TheoryModel* m) |
262 |
|
{ |
263 |
|
// Make sure that the model values for the element type of sequences are |
264 |
|
// computed first |
265 |
901 |
if (tn.isSequence() && tn.getSequenceElementType().isSequence()) |
266 |
|
{ |
267 |
6 |
TypeNode tnElem = tn.getSequenceElementType(); |
268 |
6 |
if (toProcess.find(tnElem) != toProcess.end() |
269 |
6 |
&& !collectModelInfoType(tnElem, toProcess, repSet, m)) |
270 |
|
{ |
271 |
|
return false; |
272 |
|
} |
273 |
|
} |
274 |
901 |
toProcess.erase(tn); |
275 |
|
|
276 |
|
// get partition of strings of equal lengths for the representatives of the |
277 |
|
// current type |
278 |
1802 |
std::map<TypeNode, std::vector<std::vector<Node> > > colT; |
279 |
1802 |
std::map<TypeNode, std::vector<Node> > ltsT; |
280 |
1802 |
const std::vector<Node> repVec(repSet.at(tn).begin(), repSet.at(tn).end()); |
281 |
901 |
d_state.separateByLength(repVec, colT, ltsT); |
282 |
901 |
const std::vector<std::vector<Node> >& col = colT[tn]; |
283 |
901 |
const std::vector<Node>& lts = ltsT[tn]; |
284 |
|
|
285 |
901 |
NodeManager* nm = NodeManager::currentNM(); |
286 |
1802 |
std::map< Node, Node > processed; |
287 |
|
//step 1 : get all values for known lengths |
288 |
1802 |
std::vector< Node > lts_values; |
289 |
1802 |
std::map<std::size_t, Node> values_used; |
290 |
1802 |
std::vector<Node> len_splits; |
291 |
6233 |
for( unsigned i=0; i<col.size(); i++ ) { |
292 |
5333 |
Trace("strings-model") << "Checking length for {"; |
293 |
14451 |
for( unsigned j=0; j<col[i].size(); j++ ) { |
294 |
9118 |
if( j>0 ) { |
295 |
3785 |
Trace("strings-model") << ", "; |
296 |
|
} |
297 |
9118 |
Trace("strings-model") << col[i][j]; |
298 |
|
} |
299 |
5333 |
Trace("strings-model") << " } (length is " << lts[i] << ")" << std::endl; |
300 |
10666 |
Node len_value; |
301 |
5333 |
if( lts[i].isConst() ) { |
302 |
3751 |
len_value = lts[i]; |
303 |
|
} |
304 |
1582 |
else if (!lts[i].isNull()) |
305 |
|
{ |
306 |
|
// get the model value for lts[i] |
307 |
1582 |
len_value = d_valuation.getModelValue(lts[i]); |
308 |
|
} |
309 |
5333 |
if (len_value.isNull()) |
310 |
|
{ |
311 |
|
lts_values.push_back(Node::null()); |
312 |
|
} |
313 |
|
else |
314 |
|
{ |
315 |
|
// must throw logic exception if we cannot construct the string |
316 |
5333 |
if (len_value.getConst<Rational>() > String::maxSize()) |
317 |
|
{ |
318 |
2 |
std::stringstream ss; |
319 |
2 |
ss << "The model was computed to have strings of length " << len_value |
320 |
1 |
<< ". We only allow strings up to length " << String::maxSize(); |
321 |
1 |
throw LogicException(ss.str()); |
322 |
|
} |
323 |
|
std::size_t lvalue = |
324 |
5332 |
len_value.getConst<Rational>().getNumerator().toUnsignedInt(); |
325 |
5332 |
auto itvu = values_used.find(lvalue); |
326 |
5332 |
if (itvu == values_used.end()) |
327 |
|
{ |
328 |
4201 |
values_used[lvalue] = lts[i]; |
329 |
|
} |
330 |
|
else |
331 |
|
{ |
332 |
1131 |
len_splits.push_back(lts[i].eqNode(itvu->second)); |
333 |
|
} |
334 |
5332 |
lts_values.push_back(len_value); |
335 |
|
} |
336 |
|
} |
337 |
|
////step 2 : assign arbitrary values for unknown lengths? |
338 |
|
// confirmed by calculus invariant, see paper |
339 |
900 |
Trace("strings-model") << "Assign to equivalence classes..." << std::endl; |
340 |
1800 |
std::map<Node, Node> pure_eq_assign; |
341 |
|
//step 3 : assign values to equivalence classes that are pure variables |
342 |
6231 |
for( unsigned i=0; i<col.size(); i++ ){ |
343 |
10662 |
std::vector< Node > pure_eq; |
344 |
10662 |
Trace("strings-model") << "The (" << col[i].size() |
345 |
5331 |
<< ") equivalence classes "; |
346 |
14447 |
for (const Node& eqc : col[i]) |
347 |
|
{ |
348 |
9116 |
Trace("strings-model") << eqc << " "; |
349 |
|
//check if col[i][j] has only variables |
350 |
9116 |
if (!eqc.isConst()) |
351 |
|
{ |
352 |
3122 |
NormalForm& nfe = d_csolver.getNormalForm(eqc); |
353 |
3122 |
if (nfe.d_nf.size() == 1) |
354 |
|
{ |
355 |
|
// is it an equivalence class with a seq.unit term? |
356 |
999 |
if (nfe.d_nf[0].getKind() == SEQ_UNIT) |
357 |
|
{ |
358 |
28 |
Node argVal; |
359 |
14 |
if (nfe.d_nf[0][0].getType().isStringLike()) |
360 |
|
{ |
361 |
|
// By this point, we should have assigned model values for the |
362 |
|
// elements of this sequence type because of the check in the |
363 |
|
// beginning of this method |
364 |
3 |
argVal = m->getRepresentative(nfe.d_nf[0][0]); |
365 |
|
} |
366 |
|
else |
367 |
|
{ |
368 |
|
// Otherwise, we use the term itself. We cannot get the model |
369 |
|
// value of this term, since it might not be available yet, as |
370 |
|
// it may belong to a theory that has not built its model yet. |
371 |
|
// Hence, we assign a (non-constant) skeleton (seq.unit argVal). |
372 |
11 |
argVal = nfe.d_nf[0][0]; |
373 |
|
} |
374 |
14 |
Assert(!argVal.isNull()) << "No value for " << nfe.d_nf[0][0]; |
375 |
28 |
Node c = rewrite(nm->mkNode(SEQ_UNIT, argVal)); |
376 |
14 |
pure_eq_assign[eqc] = c; |
377 |
14 |
Trace("strings-model") << "(unit: " << nfe.d_nf[0] << ") "; |
378 |
14 |
m->getEqualityEngine()->addTerm(c); |
379 |
|
} |
380 |
|
// does it have a code and the length of these equivalence classes are |
381 |
|
// one? |
382 |
985 |
else if (d_termReg.hasStringCode() && lts_values[i] == d_one) |
383 |
|
{ |
384 |
325 |
EqcInfo* eip = d_state.getOrMakeEqcInfo(eqc, false); |
385 |
325 |
if (eip && !eip->d_codeTerm.get().isNull()) |
386 |
|
{ |
387 |
|
// its value must be equal to its code |
388 |
372 |
Node ct = nm->mkNode(kind::STRING_TO_CODE, eip->d_codeTerm.get()); |
389 |
372 |
Node ctv = d_valuation.getModelValue(ct); |
390 |
|
unsigned cvalue = |
391 |
186 |
ctv.getConst<Rational>().getNumerator().toUnsignedInt(); |
392 |
186 |
Trace("strings-model") << "(code: " << cvalue << ") "; |
393 |
372 |
std::vector<unsigned> vec; |
394 |
186 |
vec.push_back(cvalue); |
395 |
372 |
Node mv = nm->mkConst(String(vec)); |
396 |
186 |
pure_eq_assign[eqc] = mv; |
397 |
186 |
m->getEqualityEngine()->addTerm(mv); |
398 |
|
} |
399 |
|
} |
400 |
999 |
pure_eq.push_back(eqc); |
401 |
|
} |
402 |
|
} |
403 |
|
else |
404 |
|
{ |
405 |
5994 |
processed[eqc] = eqc; |
406 |
|
// Make sure that constants are asserted to the theory model that we |
407 |
|
// are building. It is possible that new constants were introduced by |
408 |
|
// the eager evaluation in the equality engine. These terms are missing |
409 |
|
// in the term set and, as a result, are skipped when the equality |
410 |
|
// engine is asserted to the theory model. |
411 |
5994 |
m->getEqualityEngine()->addTerm(eqc); |
412 |
|
} |
413 |
|
} |
414 |
5331 |
Trace("strings-model") << "have length " << lts_values[i] << std::endl; |
415 |
|
|
416 |
|
//assign a new length if necessary |
417 |
5331 |
if( !pure_eq.empty() ){ |
418 |
703 |
if( lts_values[i].isNull() ){ |
419 |
|
// start with length two (other lengths have special precendence) |
420 |
|
std::size_t lvalue = 2; |
421 |
|
while( values_used.find( lvalue )!=values_used.end() ){ |
422 |
|
lvalue++; |
423 |
|
} |
424 |
|
Trace("strings-model") << "*** Decide to make length of " << lvalue << std::endl; |
425 |
|
lts_values[i] = nm->mkConst(Rational(lvalue)); |
426 |
|
values_used[lvalue] = Node::null(); |
427 |
|
} |
428 |
703 |
Trace("strings-model") << "Need to assign values of length " << lts_values[i] << " to equivalence classes "; |
429 |
1702 |
for( unsigned j=0; j<pure_eq.size(); j++ ){ |
430 |
999 |
Trace("strings-model") << pure_eq[j] << " "; |
431 |
|
} |
432 |
703 |
Trace("strings-model") << std::endl; |
433 |
|
|
434 |
|
//use type enumerator |
435 |
703 |
Assert(lts_values[i].getConst<Rational>() <= Rational(String::maxSize())) |
436 |
|
<< "Exceeded UINT32_MAX in string model"; |
437 |
|
uint32_t currLen = |
438 |
703 |
lts_values[i].getConst<Rational>().getNumerator().toUnsignedInt(); |
439 |
1406 |
std::unique_ptr<SEnumLen> sel; |
440 |
1406 |
Trace("strings-model") << "Cardinality of alphabet is " |
441 |
703 |
<< utils::getAlphabetCardinality() << std::endl; |
442 |
703 |
if (tn.isString()) // string-only |
443 |
|
{ |
444 |
683 |
sel.reset(new StringEnumLen( |
445 |
683 |
currLen, currLen, utils::getAlphabetCardinality())); |
446 |
|
} |
447 |
|
else |
448 |
|
{ |
449 |
20 |
sel.reset(new SeqEnumLen(tn, nullptr, currLen, currLen)); |
450 |
|
} |
451 |
1702 |
for (const Node& eqc : pure_eq) |
452 |
|
{ |
453 |
1998 |
Node c; |
454 |
999 |
std::map<Node, Node>::iterator itp = pure_eq_assign.find(eqc); |
455 |
999 |
if (itp == pure_eq_assign.end()) |
456 |
|
{ |
457 |
649 |
do |
458 |
|
{ |
459 |
1448 |
if (sel->isFinished()) |
460 |
|
{ |
461 |
|
// We are in a case where model construction is impossible due to |
462 |
|
// an insufficient number of constants of a given length. |
463 |
|
|
464 |
|
// Consider an integer equivalence class E whose value is assigned |
465 |
|
// n in the model. Let { S_1, ..., S_m } be the set of string |
466 |
|
// equivalence classes such that len( x ) is a member of E for |
467 |
|
// some member x of each class S1, ...,Sm. Since our calculus is |
468 |
|
// saturated with respect to cardinality inference (see Liang |
469 |
|
// et al, Figure 6, CAV 2014), we have that m <= A^n, where A is |
470 |
|
// the cardinality of our alphabet. |
471 |
|
|
472 |
|
// Now, consider the case where there exists two integer |
473 |
|
// equivalence classes E1 and E2 that are assigned n, and moreover |
474 |
|
// we did not received notification from arithmetic that E1 = E2. |
475 |
|
// This typically should never happen, but assume in the following |
476 |
|
// that it does. |
477 |
|
|
478 |
|
// Now, it may be the case that there are string equivalence |
479 |
|
// classes { S_1, ..., S_m1 } whose lengths are in E1, |
480 |
|
// and classes { S'_1, ..., S'_m2 } whose lengths are in E2, where |
481 |
|
// m1 + m2 > A^n. In this case, we have insufficient strings to |
482 |
|
// assign to { S_1, ..., S_m1, S'_1, ..., S'_m2 }. If this |
483 |
|
// happens, we add a split on len( u1 ) = len( u2 ) for some |
484 |
|
// len( u1 ) in E1, len( u2 ) in E2. We do this for each pair of |
485 |
|
// integer equivalence classes that are assigned to the same value |
486 |
|
// in the model. |
487 |
|
AlwaysAssert(!len_splits.empty()); |
488 |
|
for (const Node& sl : len_splits) |
489 |
|
{ |
490 |
|
Node spl = nm->mkNode(OR, sl, sl.negate()); |
491 |
|
d_im.lemma(spl, InferenceId::STRINGS_CMI_SPLIT); |
492 |
|
Trace("strings-lemma") |
493 |
|
<< "Strings::CollectModelInfoSplit: " << spl << std::endl; |
494 |
|
} |
495 |
|
// we added a lemma, so can return here |
496 |
|
return false; |
497 |
|
} |
498 |
1448 |
c = sel->getCurrent(); |
499 |
|
// if we are a sequence with infinite element type |
500 |
2896 |
if (tn.isSequence() |
501 |
2896 |
&& !d_state.isFiniteType(tn.getSequenceElementType())) |
502 |
|
{ |
503 |
|
// Make a skeleton instead. In particular, this means that |
504 |
|
// a value: |
505 |
|
// (seq.++ (seq.unit 0) (seq.unit 1) (seq.unit 2)) |
506 |
|
// becomes: |
507 |
|
// (seq.++ (seq.unit k_0) (seq.unit k_1) (seq.unit k_2)) |
508 |
|
// where k_0, k_1, k_2 are fresh integer variables. These |
509 |
|
// variables will be assigned values in the standard way by the |
510 |
|
// model. This construction is necessary since the strings solver |
511 |
|
// must constrain the length of the model of an equivalence class |
512 |
|
// (e.g. in this case to length 3); moreover we cannot assign a |
513 |
|
// concrete value since it may conflict with other skeletons we |
514 |
|
// have assigned, e.g. for the case of (seq.unit argVal) above. |
515 |
11 |
SkolemManager* sm = nm->getSkolemManager(); |
516 |
11 |
BoundVarManager* bvm = nm->getBoundVarManager(); |
517 |
11 |
Assert(c.getKind() == CONST_SEQUENCE); |
518 |
11 |
const Sequence& sn = c.getConst<Sequence>(); |
519 |
11 |
const std::vector<Node>& snvec = sn.getVec(); |
520 |
22 |
std::vector<Node> skChildren; |
521 |
37 |
for (const Node& snv : snvec) |
522 |
|
{ |
523 |
52 |
TypeNode etn = snv.getType(); |
524 |
52 |
Node v = bvm->mkBoundVar<SeqModelVarAttribute>(snv, etn); |
525 |
|
// use a skolem, not a bound variable |
526 |
52 |
Node kv = sm->mkPurifySkolem(v, "smv"); |
527 |
26 |
skChildren.push_back(nm->mkNode(SEQ_UNIT, kv)); |
528 |
|
} |
529 |
11 |
c = utils::mkConcat(skChildren, tn); |
530 |
|
} |
531 |
|
// increment |
532 |
1448 |
sel->increment(); |
533 |
1448 |
} while (m->hasTerm(c)); |
534 |
|
} |
535 |
|
else |
536 |
|
{ |
537 |
200 |
c = itp->second; |
538 |
|
} |
539 |
1998 |
Trace("strings-model") << "*** Assigned constant " << c << " for " |
540 |
999 |
<< eqc << std::endl; |
541 |
999 |
processed[eqc] = c; |
542 |
999 |
if (!m->assertEquality(eqc, c, true)) |
543 |
|
{ |
544 |
|
// this should never happen due to the model soundness argument |
545 |
|
// for strings |
546 |
|
Unreachable() |
547 |
|
<< "TheoryStrings::collectModelInfoType: Inconsistent equality" |
548 |
|
<< std::endl; |
549 |
|
return false; |
550 |
|
} |
551 |
|
} |
552 |
|
} |
553 |
|
} |
554 |
900 |
Trace("strings-model") << "String Model : Pure Assigned." << std::endl; |
555 |
|
//step 4 : assign constants to all other equivalence classes |
556 |
10016 |
for (const Node& rn : repVec) |
557 |
|
{ |
558 |
9116 |
if (processed.find(rn) == processed.end()) |
559 |
|
{ |
560 |
2123 |
NormalForm& nf = d_csolver.getNormalForm(rn); |
561 |
2123 |
if (Trace.isOn("strings-model")) |
562 |
|
{ |
563 |
|
Trace("strings-model") |
564 |
|
<< "Construct model for " << rn << " based on normal form "; |
565 |
|
for (unsigned j = 0, size = nf.d_nf.size(); j < size; j++) |
566 |
|
{ |
567 |
|
Node n = nf.d_nf[j]; |
568 |
|
if (j > 0) |
569 |
|
{ |
570 |
|
Trace("strings-model") << " ++ "; |
571 |
|
} |
572 |
|
Trace("strings-model") << n; |
573 |
|
Node r = d_state.getRepresentative(n); |
574 |
|
if (!r.isConst() && processed.find(r) == processed.end()) |
575 |
|
{ |
576 |
|
Trace("strings-model") << "(UNPROCESSED)"; |
577 |
|
} |
578 |
|
} |
579 |
|
} |
580 |
2123 |
Trace("strings-model") << std::endl; |
581 |
4246 |
std::vector< Node > nc; |
582 |
10000 |
for (const Node& n : nf.d_nf) |
583 |
|
{ |
584 |
15754 |
Node r = d_state.getRepresentative(n); |
585 |
7877 |
Assert(r.isConst() || processed.find(r) != processed.end()); |
586 |
7877 |
nc.push_back(r.isConst() ? r : processed[r]); |
587 |
|
} |
588 |
4246 |
Node cc = utils::mkNConcat(nc, tn); |
589 |
4246 |
Trace("strings-model") |
590 |
2123 |
<< "*** Determined constant " << cc << " for " << rn << std::endl; |
591 |
2123 |
processed[rn] = cc; |
592 |
2123 |
if (!m->assertEquality(rn, cc, true)) |
593 |
|
{ |
594 |
|
// this should never happen due to the model soundness argument |
595 |
|
// for strings |
596 |
|
Unreachable() << "TheoryStrings::collectModelInfoType: " |
597 |
|
"Inconsistent equality (unprocessed eqc)" |
598 |
|
<< std::endl; |
599 |
|
return false; |
600 |
|
} |
601 |
2123 |
else if (!cc.isConst()) |
602 |
|
{ |
603 |
|
// the value may be specified by seq.unit components, ensure this |
604 |
|
// is marked as the skeleton for constructing values in this class. |
605 |
3 |
m->assertSkeleton(cc); |
606 |
|
} |
607 |
|
} |
608 |
|
} |
609 |
|
//Trace("strings-model") << "String Model : Assigned." << std::endl; |
610 |
900 |
Trace("strings-model") << "String Model : Finished." << std::endl; |
611 |
900 |
return true; |
612 |
|
} |
613 |
|
|
614 |
|
///////////////////////////////////////////////////////////////////////////// |
615 |
|
// MAIN SOLVER |
616 |
|
///////////////////////////////////////////////////////////////////////////// |
617 |
|
|
618 |
157519 |
void TheoryStrings::preRegisterTerm(TNode n) |
619 |
|
{ |
620 |
315038 |
Trace("strings-preregister") |
621 |
157519 |
<< "TheoryStrings::preRegisterTerm: " << n << std::endl; |
622 |
157519 |
d_termReg.preRegisterTerm(n); |
623 |
|
// Register the term with the extended theory. Notice we do not recurse on |
624 |
|
// this term here since preRegisterTerm is already called recursively on all |
625 |
|
// subterms in preregistered literals. |
626 |
157519 |
d_extTheory.registerTerm(n); |
627 |
157519 |
} |
628 |
|
|
629 |
1073118 |
bool TheoryStrings::preNotifyFact( |
630 |
|
TNode atom, bool pol, TNode fact, bool isPrereg, bool isInternal) |
631 |
|
{ |
632 |
|
// this is only required for internal facts, others are already registered |
633 |
1073118 |
if (isInternal && atom.getKind() == EQUAL) |
634 |
|
{ |
635 |
|
// We must ensure these terms are registered. We register eagerly here for |
636 |
|
// performance reasons. Alternatively, terms could be registered at full |
637 |
|
// effort in e.g. BaseSolver::init. |
638 |
112773 |
for (const Node& t : atom) |
639 |
|
{ |
640 |
75182 |
d_termReg.registerTerm(t, 0); |
641 |
|
} |
642 |
|
} |
643 |
1073118 |
return false; |
644 |
|
} |
645 |
|
|
646 |
1073118 |
void TheoryStrings::notifyFact(TNode atom, |
647 |
|
bool polarity, |
648 |
|
TNode fact, |
649 |
|
bool isInternal) |
650 |
|
{ |
651 |
1073118 |
d_eagerSolver.notifyFact(atom, polarity, fact, isInternal); |
652 |
|
// process pending conflicts due to reasoning about endpoints |
653 |
1073118 |
if (!d_state.isInConflict() && d_state.hasPendingConflict()) |
654 |
|
{ |
655 |
886 |
InferInfo iiPendingConf(InferenceId::UNKNOWN); |
656 |
443 |
d_state.getPendingConflict(iiPendingConf); |
657 |
886 |
Trace("strings-pending") |
658 |
443 |
<< "Process pending conflict " << iiPendingConf.d_premises << std::endl; |
659 |
886 |
Trace("strings-conflict") |
660 |
443 |
<< "CONFLICT: Eager : " << iiPendingConf.d_premises << std::endl; |
661 |
443 |
++(d_statistics.d_conflictsEager); |
662 |
|
// call the inference manager to send the conflict |
663 |
443 |
d_im.processConflict(iiPendingConf); |
664 |
443 |
return; |
665 |
|
} |
666 |
1072675 |
Trace("strings-pending-debug") << " Now collect terms" << std::endl; |
667 |
1072675 |
Trace("strings-pending-debug") << " Finished collect terms" << std::endl; |
668 |
|
} |
669 |
|
|
670 |
450763 |
void TheoryStrings::postCheck(Effort e) |
671 |
|
{ |
672 |
450763 |
d_im.doPendingFacts(); |
673 |
|
|
674 |
450763 |
Assert(d_strat.isStrategyInit()); |
675 |
1350097 |
if (!d_state.isInConflict() && !d_valuation.needCheck() |
676 |
560143 |
&& d_strat.hasStrategyEffort(e)) |
677 |
|
{ |
678 |
52238 |
Trace("strings-check-debug") |
679 |
26119 |
<< "Theory of strings " << e << " effort check " << std::endl; |
680 |
26119 |
if (Trace.isOn("strings-eqc")) |
681 |
|
{ |
682 |
|
Trace("strings-eqc") << debugPrintStringsEqc() << std::endl; |
683 |
|
} |
684 |
26119 |
++(d_statistics.d_checkRuns); |
685 |
26119 |
bool sentLemma = false; |
686 |
26119 |
bool hadPending = false; |
687 |
26119 |
Trace("strings-check") << "Full effort check..." << std::endl; |
688 |
15574 |
do{ |
689 |
41693 |
d_im.reset(); |
690 |
41693 |
++(d_statistics.d_strategyRuns); |
691 |
41693 |
Trace("strings-check") << " * Run strategy..." << std::endl; |
692 |
41693 |
runStrategy(e); |
693 |
|
// remember if we had pending facts or lemmas |
694 |
41693 |
hadPending = d_im.hasPending(); |
695 |
|
// Send the facts *and* the lemmas. We send lemmas regardless of whether |
696 |
|
// we send facts since some lemmas cannot be dropped. Other lemmas are |
697 |
|
// otherwise avoided by aborting the strategy when a fact is ready. |
698 |
41693 |
d_im.doPending(); |
699 |
|
// Did we successfully send a lemma? Notice that if hasPending = true |
700 |
|
// and sentLemma = false, then the above call may have: |
701 |
|
// (1) had no pending lemmas, but successfully processed pending facts, |
702 |
|
// (2) unsuccessfully processed pending lemmas. |
703 |
|
// In either case, we repeat the strategy if we are not in conflict. |
704 |
41693 |
sentLemma = d_im.hasSentLemma(); |
705 |
41693 |
if (Trace.isOn("strings-check")) |
706 |
|
{ |
707 |
|
Trace("strings-check") << " ...finish run strategy: "; |
708 |
|
Trace("strings-check") << (hadPending ? "hadPending " : ""); |
709 |
|
Trace("strings-check") << (sentLemma ? "sentLemma " : ""); |
710 |
|
Trace("strings-check") << (d_state.isInConflict() ? "conflict " : ""); |
711 |
|
if (!hadPending && !sentLemma && !d_state.isInConflict()) |
712 |
|
{ |
713 |
|
Trace("strings-check") << "(none)"; |
714 |
|
} |
715 |
|
Trace("strings-check") << std::endl; |
716 |
|
} |
717 |
|
// repeat if we did not add a lemma or conflict, and we had pending |
718 |
|
// facts or lemmas. |
719 |
41693 |
} while (!d_state.isInConflict() && !sentLemma && hadPending); |
720 |
|
} |
721 |
450763 |
Trace("strings-check") << "Theory of strings, done check : " << e << std::endl; |
722 |
450763 |
Assert(!d_im.hasPendingFact()); |
723 |
450763 |
Assert(!d_im.hasPendingLemma()); |
724 |
450763 |
} |
725 |
|
|
726 |
6408 |
bool TheoryStrings::needsCheckLastEffort() { |
727 |
6408 |
if( options::stringGuessModel() ){ |
728 |
|
return d_esolver.hasExtendedFunctions(); |
729 |
|
} |
730 |
6408 |
return false; |
731 |
|
} |
732 |
|
|
733 |
|
/** Conflict when merging two constants */ |
734 |
1135 |
void TheoryStrings::conflict(TNode a, TNode b){ |
735 |
1135 |
if (d_state.isInConflict()) |
736 |
|
{ |
737 |
|
// already in conflict |
738 |
|
return; |
739 |
|
} |
740 |
1135 |
d_im.conflictEqConstantMerge(a, b); |
741 |
1135 |
Trace("strings-conflict") << "CONFLICT: Eq engine conflict" << std::endl; |
742 |
1135 |
++(d_statistics.d_conflictsEqEngine); |
743 |
|
} |
744 |
|
|
745 |
124678 |
void TheoryStrings::eqNotifyNewClass(TNode t){ |
746 |
124678 |
Kind k = t.getKind(); |
747 |
124678 |
if (k == STRING_LENGTH || k == STRING_TO_CODE) |
748 |
|
{ |
749 |
36954 |
Trace("strings-debug") << "New length eqc : " << t << std::endl; |
750 |
|
//we care about the length of this string |
751 |
36954 |
d_termReg.registerTerm(t[0], 1); |
752 |
|
} |
753 |
124678 |
d_eagerSolver.eqNotifyNewClass(t); |
754 |
124678 |
} |
755 |
|
|
756 |
13570 |
void TheoryStrings::addCarePairs(TNodeTrie* t1, |
757 |
|
TNodeTrie* t2, |
758 |
|
unsigned arity, |
759 |
|
unsigned depth) |
760 |
|
{ |
761 |
13570 |
if( depth==arity ){ |
762 |
1243 |
if( t2!=NULL ){ |
763 |
2486 |
Node f1 = t1->getData(); |
764 |
2486 |
Node f2 = t2->getData(); |
765 |
1243 |
if (!d_equalityEngine->areEqual(f1, f2)) |
766 |
|
{ |
767 |
493 |
Trace("strings-cg-debug") << "TheoryStrings::computeCareGraph(): checking function " << f1 << " and " << f2 << std::endl; |
768 |
986 |
vector< pair<TNode, TNode> > currentPairs; |
769 |
1525 |
for (unsigned k = 0; k < f1.getNumChildren(); ++ k) { |
770 |
2064 |
TNode x = f1[k]; |
771 |
2064 |
TNode y = f2[k]; |
772 |
1032 |
Assert(d_equalityEngine->hasTerm(x)); |
773 |
1032 |
Assert(d_equalityEngine->hasTerm(y)); |
774 |
1032 |
Assert(!d_equalityEngine->areDisequal(x, y, false)); |
775 |
1032 |
Assert(!areCareDisequal(x, y)); |
776 |
1032 |
if (!d_equalityEngine->areEqual(x, y)) |
777 |
|
{ |
778 |
1533 |
if (d_equalityEngine->isTriggerTerm(x, THEORY_STRINGS) |
779 |
1533 |
&& d_equalityEngine->isTriggerTerm(y, THEORY_STRINGS)) |
780 |
|
{ |
781 |
192 |
TNode x_shared = d_equalityEngine->getTriggerTermRepresentative( |
782 |
384 |
x, THEORY_STRINGS); |
783 |
192 |
TNode y_shared = d_equalityEngine->getTriggerTermRepresentative( |
784 |
384 |
y, THEORY_STRINGS); |
785 |
192 |
currentPairs.push_back(make_pair(x_shared, y_shared)); |
786 |
|
} |
787 |
|
} |
788 |
|
} |
789 |
685 |
for (unsigned c = 0; c < currentPairs.size(); ++ c) { |
790 |
192 |
Trace("strings-cg-pair") << "TheoryStrings::computeCareGraph(): pair : " << currentPairs[c].first << " " << currentPairs[c].second << std::endl; |
791 |
192 |
addCarePair(currentPairs[c].first, currentPairs[c].second); |
792 |
|
} |
793 |
|
} |
794 |
|
} |
795 |
|
}else{ |
796 |
12327 |
if( t2==NULL ){ |
797 |
10340 |
if( depth<(arity-1) ){ |
798 |
|
//add care pairs internal to each child |
799 |
10845 |
for (std::pair<const TNode, TNodeTrie>& tt : t1->d_data) |
800 |
|
{ |
801 |
7117 |
addCarePairs(&tt.second, nullptr, arity, depth + 1); |
802 |
|
} |
803 |
|
} |
804 |
|
//add care pairs based on each pair of non-disequal arguments |
805 |
33551 |
for (std::map<TNode, TNodeTrie>::iterator it = t1->d_data.begin(); |
806 |
33551 |
it != t1->d_data.end(); |
807 |
|
++it) |
808 |
|
{ |
809 |
23211 |
std::map<TNode, TNodeTrie>::iterator it2 = it; |
810 |
23211 |
++it2; |
811 |
200541 |
for( ; it2 != t1->d_data.end(); ++it2 ){ |
812 |
88665 |
if (!d_equalityEngine->areDisequal(it->first, it2->first, false)) |
813 |
|
{ |
814 |
2890 |
if( !areCareDisequal(it->first, it2->first) ){ |
815 |
1591 |
addCarePairs( &it->second, &it2->second, arity, depth+1 ); |
816 |
|
} |
817 |
|
} |
818 |
|
} |
819 |
|
} |
820 |
|
}else{ |
821 |
|
//add care pairs based on product of indices, non-disequal arguments |
822 |
5051 |
for (std::pair<const TNode, TNodeTrie>& tt1 : t1->d_data) |
823 |
|
{ |
824 |
8794 |
for (std::pair<const TNode, TNodeTrie>& tt2 : t2->d_data) |
825 |
|
{ |
826 |
5730 |
if (!d_equalityEngine->areDisequal(tt1.first, tt2.first, false)) |
827 |
|
{ |
828 |
3207 |
if (!areCareDisequal(tt1.first, tt2.first)) |
829 |
|
{ |
830 |
1639 |
addCarePairs(&tt1.second, &tt2.second, arity, depth + 1); |
831 |
|
} |
832 |
|
} |
833 |
|
} |
834 |
|
} |
835 |
|
} |
836 |
|
} |
837 |
13570 |
} |
838 |
|
|
839 |
6990 |
void TheoryStrings::computeCareGraph(){ |
840 |
|
//computing the care graph here is probably still necessary, due to operators that take non-string arguments TODO: verify |
841 |
6990 |
Trace("strings-cg") << "TheoryStrings::computeCareGraph(): Build term indices..." << std::endl; |
842 |
|
// Term index for each (type, operator) pair. We require the operator here |
843 |
|
// since operators are polymorphic, taking strings/sequences. |
844 |
13980 |
std::map<std::pair<TypeNode, Node>, TNodeTrie> index; |
845 |
13980 |
std::map< Node, unsigned > arity; |
846 |
6990 |
const context::CDList<TNode>& fterms = d_termReg.getFunctionTerms(); |
847 |
6990 |
size_t functionTerms = fterms.size(); |
848 |
56325 |
for (unsigned i = 0; i < functionTerms; ++ i) { |
849 |
98670 |
TNode f1 = fterms[i]; |
850 |
49335 |
Trace("strings-cg") << "...build for " << f1 << std::endl; |
851 |
98670 |
Node op = f1.getOperator(); |
852 |
98670 |
std::vector< TNode > reps; |
853 |
49335 |
bool has_trigger_arg = false; |
854 |
113331 |
for( unsigned j=0; j<f1.getNumChildren(); j++ ){ |
855 |
63996 |
reps.push_back(d_equalityEngine->getRepresentative(f1[j])); |
856 |
63996 |
if (d_equalityEngine->isTriggerTerm(f1[j], THEORY_STRINGS)) |
857 |
|
{ |
858 |
47252 |
has_trigger_arg = true; |
859 |
|
} |
860 |
|
} |
861 |
49335 |
if( has_trigger_arg ){ |
862 |
73428 |
TypeNode ft = utils::getOwnerStringType(f1); |
863 |
73428 |
std::pair<TypeNode, Node> ikey = std::pair<TypeNode, Node>(ft, op); |
864 |
36714 |
index[ikey].addTerm(f1, reps); |
865 |
36714 |
arity[op] = reps.size(); |
866 |
|
} |
867 |
|
} |
868 |
|
//for each index |
869 |
10213 |
for (std::pair<const std::pair<TypeNode, Node>, TNodeTrie>& ti : index) |
870 |
|
{ |
871 |
6446 |
Trace("strings-cg") << "TheoryStrings::computeCareGraph(): Process index " |
872 |
3223 |
<< ti.first << "..." << std::endl; |
873 |
6446 |
Node op = ti.first.second; |
874 |
3223 |
addCarePairs(&ti.second, nullptr, arity[op], 0); |
875 |
|
} |
876 |
6990 |
} |
877 |
|
|
878 |
|
void TheoryStrings::checkRegisterTermsPreNormalForm() |
879 |
|
{ |
880 |
|
const std::vector<Node>& seqc = d_bsolver.getStringEqc(); |
881 |
|
for (const Node& eqc : seqc) |
882 |
|
{ |
883 |
|
eq::EqClassIterator eqc_i = eq::EqClassIterator(eqc, d_equalityEngine); |
884 |
|
while (!eqc_i.isFinished()) |
885 |
|
{ |
886 |
|
Node n = (*eqc_i); |
887 |
|
if (!d_bsolver.isCongruent(n)) |
888 |
|
{ |
889 |
|
d_termReg.registerTerm(n, 2); |
890 |
|
} |
891 |
|
++eqc_i; |
892 |
|
} |
893 |
|
} |
894 |
|
} |
895 |
|
|
896 |
10942 |
void TheoryStrings::checkCodes() |
897 |
|
{ |
898 |
|
// ensure that lemmas regarding str.code been added for each constant string |
899 |
|
// of length one |
900 |
10942 |
if (d_termReg.hasStringCode()) |
901 |
|
{ |
902 |
1634 |
NodeManager* nm = NodeManager::currentNM(); |
903 |
|
// str.code applied to the code term for each equivalence class that has a |
904 |
|
// code term but is not a constant |
905 |
3052 |
std::vector<Node> nconst_codes; |
906 |
|
// str.code applied to the proxy variables for each equivalence classes that |
907 |
|
// are constants of size one |
908 |
3052 |
std::vector<Node> const_codes; |
909 |
1634 |
const std::vector<Node>& seqc = d_bsolver.getStringEqc(); |
910 |
22614 |
for (const Node& eqc : seqc) |
911 |
|
{ |
912 |
20980 |
NormalForm& nfe = d_csolver.getNormalForm(eqc); |
913 |
20980 |
if (nfe.d_nf.size() == 1 && nfe.d_nf[0].isConst()) |
914 |
|
{ |
915 |
15104 |
Node c = nfe.d_nf[0]; |
916 |
15104 |
Trace("strings-code-debug") << "Get proxy variable for " << c |
917 |
7552 |
<< std::endl; |
918 |
15104 |
Node cc = nm->mkNode(kind::STRING_TO_CODE, c); |
919 |
7552 |
cc = rewrite(cc); |
920 |
7552 |
Assert(cc.isConst()); |
921 |
15104 |
Node cp = d_termReg.ensureProxyVariableFor(c); |
922 |
15104 |
Node vc = nm->mkNode(STRING_TO_CODE, cp); |
923 |
7552 |
if (!d_state.areEqual(cc, vc)) |
924 |
|
{ |
925 |
1328 |
std::vector<Node> emptyVec; |
926 |
664 |
d_im.sendInference(emptyVec, cc.eqNode(vc), InferenceId::STRINGS_CODE_PROXY); |
927 |
|
} |
928 |
7552 |
const_codes.push_back(vc); |
929 |
|
} |
930 |
|
else |
931 |
|
{ |
932 |
13428 |
EqcInfo* ei = d_state.getOrMakeEqcInfo(eqc, false); |
933 |
13428 |
if (ei && !ei->d_codeTerm.get().isNull()) |
934 |
|
{ |
935 |
7132 |
Node vc = nm->mkNode(kind::STRING_TO_CODE, ei->d_codeTerm.get()); |
936 |
3566 |
nconst_codes.push_back(vc); |
937 |
|
} |
938 |
|
} |
939 |
|
} |
940 |
1634 |
if (d_im.hasProcessed()) |
941 |
|
{ |
942 |
216 |
return; |
943 |
|
} |
944 |
|
// now, ensure that str.code is injective |
945 |
2836 |
std::vector<Node> cmps; |
946 |
1418 |
cmps.insert(cmps.end(), const_codes.rbegin(), const_codes.rend()); |
947 |
1418 |
cmps.insert(cmps.end(), nconst_codes.rbegin(), nconst_codes.rend()); |
948 |
4590 |
for (unsigned i = 0, num_ncc = nconst_codes.size(); i < num_ncc; i++) |
949 |
|
{ |
950 |
6344 |
Node c1 = nconst_codes[i]; |
951 |
3172 |
cmps.pop_back(); |
952 |
19975 |
for (const Node& c2 : cmps) |
953 |
|
{ |
954 |
33606 |
Trace("strings-code-debug") |
955 |
16803 |
<< "Compare codes : " << c1 << " " << c2 << std::endl; |
956 |
16803 |
if (!d_state.areDisequal(c1, c2) && !d_state.areEqual(c1, d_neg_one)) |
957 |
|
{ |
958 |
2532 |
Node eq_no = c1.eqNode(d_neg_one); |
959 |
2532 |
Node deq = c1.eqNode(c2).negate(); |
960 |
2532 |
Node eqn = c1[0].eqNode(c2[0]); |
961 |
|
// str.code(x)==-1 V str.code(x)!=str.code(y) V x==y |
962 |
2532 |
Node inj_lem = nm->mkNode(kind::OR, eq_no, deq, eqn); |
963 |
1266 |
deq = rewrite(deq); |
964 |
1266 |
d_im.addPendingPhaseRequirement(deq, false); |
965 |
2532 |
std::vector<Node> emptyVec; |
966 |
1266 |
d_im.sendInference(emptyVec, inj_lem, InferenceId::STRINGS_CODE_INJ); |
967 |
|
} |
968 |
|
} |
969 |
|
} |
970 |
|
} |
971 |
|
} |
972 |
|
|
973 |
|
void TheoryStrings::checkRegisterTermsNormalForms() |
974 |
|
{ |
975 |
|
const std::vector<Node>& seqc = d_bsolver.getStringEqc(); |
976 |
|
for (const Node& eqc : seqc) |
977 |
|
{ |
978 |
|
NormalForm& nfi = d_csolver.getNormalForm(eqc); |
979 |
|
// check if there is a length term for this equivalence class |
980 |
|
EqcInfo* ei = d_state.getOrMakeEqcInfo(eqc, false); |
981 |
|
Node lt = ei ? ei->d_lengthTerm : Node::null(); |
982 |
|
if (lt.isNull()) |
983 |
|
{ |
984 |
|
Node c = utils::mkNConcat(nfi.d_nf, eqc.getType()); |
985 |
|
d_termReg.registerTerm(c, 3); |
986 |
|
} |
987 |
|
} |
988 |
|
} |
989 |
|
|
990 |
125355 |
TrustNode TheoryStrings::ppRewrite(TNode atom, std::vector<SkolemLemma>& lems) |
991 |
|
{ |
992 |
125355 |
Trace("strings-ppr") << "TheoryStrings::ppRewrite " << atom << std::endl; |
993 |
125355 |
if (atom.getKind() == EQUAL) |
994 |
|
{ |
995 |
|
// always apply aggressive equality rewrites here |
996 |
3889 |
Node ret = d_rewriter.rewriteEqualityExt(atom); |
997 |
2008 |
if (ret != atom) |
998 |
|
{ |
999 |
127 |
return TrustNode::mkTrustRewrite(atom, ret, nullptr); |
1000 |
|
} |
1001 |
|
} |
1002 |
125228 |
if (atom.getKind() == STRING_FROM_CODE) |
1003 |
|
{ |
1004 |
|
// str.from_code(t) ---> |
1005 |
|
// witness k. ite(0 <= t < |A|, t = str.to_code(k), k = "") |
1006 |
63 |
NodeManager* nm = NodeManager::currentNM(); |
1007 |
126 |
Node t = atom[0]; |
1008 |
126 |
Node card = nm->mkConst(Rational(utils::getAlphabetCardinality())); |
1009 |
|
Node cond = |
1010 |
126 |
nm->mkNode(AND, nm->mkNode(LEQ, d_zero, t), nm->mkNode(LT, t, card)); |
1011 |
126 |
Node v = nm->mkBoundVar(nm->stringType()); |
1012 |
126 |
Node emp = Word::mkEmptyWord(atom.getType()); |
1013 |
|
Node pred = nm->mkNode( |
1014 |
126 |
ITE, cond, t.eqNode(nm->mkNode(STRING_TO_CODE, v)), v.eqNode(emp)); |
1015 |
63 |
SkolemManager* sm = nm->getSkolemManager(); |
1016 |
126 |
Node ret = sm->mkSkolem(v, pred, "kFromCode"); |
1017 |
63 |
lems.push_back(SkolemLemma(ret, nullptr)); |
1018 |
63 |
return TrustNode::mkTrustRewrite(atom, ret, nullptr); |
1019 |
|
} |
1020 |
250330 |
TrustNode ret; |
1021 |
250330 |
Node atomRet = atom; |
1022 |
125165 |
if (options::regExpElim() && atom.getKind() == STRING_IN_REGEXP) |
1023 |
|
{ |
1024 |
|
// aggressive elimination of regular expression membership |
1025 |
134 |
ret = d_regexp_elim.eliminateTrusted(atomRet); |
1026 |
134 |
if (!ret.isNull()) |
1027 |
|
{ |
1028 |
184 |
Trace("strings-ppr") << " rewrote " << atom << " -> " << ret.getNode() |
1029 |
92 |
<< " via regular expression elimination." |
1030 |
92 |
<< std::endl; |
1031 |
92 |
atomRet = ret.getNode(); |
1032 |
|
} |
1033 |
|
} |
1034 |
125165 |
return ret; |
1035 |
|
} |
1036 |
|
|
1037 |
|
/** run the given inference step */ |
1038 |
287661 |
void TheoryStrings::runInferStep(InferStep s, int effort) |
1039 |
|
{ |
1040 |
287661 |
Trace("strings-process") << "Run " << s; |
1041 |
287661 |
if (effort > 0) |
1042 |
|
{ |
1043 |
41265 |
Trace("strings-process") << ", effort = " << effort; |
1044 |
|
} |
1045 |
287661 |
Trace("strings-process") << "..." << std::endl; |
1046 |
287661 |
switch (s) |
1047 |
|
{ |
1048 |
41693 |
case CHECK_INIT: d_bsolver.checkInit(); break; |
1049 |
34822 |
case CHECK_CONST_EQC: d_bsolver.checkConstantEquivalenceClasses(); break; |
1050 |
46753 |
case CHECK_EXTF_EVAL: d_esolver.checkExtfEval(effort); break; |
1051 |
32651 |
case CHECK_CYCLES: d_csolver.checkCycles(); break; |
1052 |
32647 |
case CHECK_FLAT_FORMS: d_csolver.checkFlatForms(); break; |
1053 |
|
case CHECK_REGISTER_TERMS_PRE_NF: checkRegisterTermsPreNormalForm(); break; |
1054 |
20928 |
case CHECK_NORMAL_FORMS_EQ: d_csolver.checkNormalFormsEq(); break; |
1055 |
11423 |
case CHECK_NORMAL_FORMS_DEQ: d_csolver.checkNormalFormsDeq(); break; |
1056 |
10942 |
case CHECK_CODES: checkCodes(); break; |
1057 |
10518 |
case CHECK_LENGTH_EQC: d_csolver.checkLengthsEqc(); break; |
1058 |
|
case CHECK_REGISTER_TERMS_NF: checkRegisterTermsNormalForms(); break; |
1059 |
29334 |
case CHECK_EXTF_REDUCTION: d_esolver.checkExtfReductions(effort); break; |
1060 |
8182 |
case CHECK_MEMBERSHIP: d_rsolver.checkMemberships(); break; |
1061 |
7768 |
case CHECK_CARDINALITY: d_bsolver.checkCardinality(); break; |
1062 |
|
default: Unreachable(); break; |
1063 |
|
} |
1064 |
575322 |
Trace("strings-process") << "Done " << s |
1065 |
575322 |
<< ", addedFact = " << d_im.hasPendingFact() |
1066 |
575322 |
<< ", addedLemma = " << d_im.hasPendingLemma() |
1067 |
575322 |
<< ", conflict = " << d_state.isInConflict() |
1068 |
287661 |
<< std::endl; |
1069 |
287661 |
} |
1070 |
|
|
1071 |
41693 |
void TheoryStrings::runStrategy(Theory::Effort e) |
1072 |
|
{ |
1073 |
41693 |
std::vector<std::pair<InferStep, int> >::iterator it = d_strat.stepBegin(e); |
1074 |
|
std::vector<std::pair<InferStep, int> >::iterator stepEnd = |
1075 |
41693 |
d_strat.stepEnd(e); |
1076 |
|
|
1077 |
41693 |
Trace("strings-process") << "----check, next round---" << std::endl; |
1078 |
1106163 |
while (it != stepEnd) |
1079 |
|
{ |
1080 |
566148 |
InferStep curr = it->first; |
1081 |
566148 |
if (curr == BREAK) |
1082 |
|
{ |
1083 |
278487 |
if (d_im.hasProcessed()) |
1084 |
|
{ |
1085 |
32519 |
break; |
1086 |
|
} |
1087 |
|
} |
1088 |
|
else |
1089 |
|
{ |
1090 |
287661 |
runInferStep(curr, it->second); |
1091 |
287661 |
if (d_state.isInConflict()) |
1092 |
|
{ |
1093 |
1394 |
break; |
1094 |
|
} |
1095 |
|
} |
1096 |
532235 |
++it; |
1097 |
|
} |
1098 |
41693 |
Trace("strings-process") << "----finished round---" << std::endl; |
1099 |
41693 |
} |
1100 |
|
|
1101 |
|
std::string TheoryStrings::debugPrintStringsEqc() |
1102 |
|
{ |
1103 |
|
std::stringstream ss; |
1104 |
|
for (unsigned t = 0; t < 2; t++) |
1105 |
|
{ |
1106 |
|
eq::EqClassesIterator eqcs2_i = eq::EqClassesIterator(d_equalityEngine); |
1107 |
|
ss << (t == 0 ? "STRINGS:" : "OTHER:") << std::endl; |
1108 |
|
while (!eqcs2_i.isFinished()) |
1109 |
|
{ |
1110 |
|
Node eqc = (*eqcs2_i); |
1111 |
|
bool print = (t == 0 && eqc.getType().isStringLike()) |
1112 |
|
|| (t == 1 && !eqc.getType().isStringLike()); |
1113 |
|
if (print) |
1114 |
|
{ |
1115 |
|
eq::EqClassIterator eqc2_i = eq::EqClassIterator(eqc, d_equalityEngine); |
1116 |
|
ss << "Eqc( " << eqc << " ) : { "; |
1117 |
|
while (!eqc2_i.isFinished()) |
1118 |
|
{ |
1119 |
|
if ((*eqc2_i) != eqc && (*eqc2_i).getKind() != kind::EQUAL) |
1120 |
|
{ |
1121 |
|
ss << (*eqc2_i) << " "; |
1122 |
|
} |
1123 |
|
++eqc2_i; |
1124 |
|
} |
1125 |
|
ss << " } " << std::endl; |
1126 |
|
EqcInfo* ei = d_state.getOrMakeEqcInfo(eqc, false); |
1127 |
|
if (ei) |
1128 |
|
{ |
1129 |
|
Trace("strings-eqc-debug") |
1130 |
|
<< "* Length term : " << ei->d_lengthTerm.get() << std::endl; |
1131 |
|
Trace("strings-eqc-debug") |
1132 |
|
<< "* Cardinality lemma k : " << ei->d_cardinalityLemK.get() |
1133 |
|
<< std::endl; |
1134 |
|
Trace("strings-eqc-debug") |
1135 |
|
<< "* Normalization length lemma : " |
1136 |
|
<< ei->d_normalizedLength.get() << std::endl; |
1137 |
|
} |
1138 |
|
} |
1139 |
|
++eqcs2_i; |
1140 |
|
} |
1141 |
|
ss << std::endl; |
1142 |
|
} |
1143 |
|
ss << std::endl; |
1144 |
|
return ss.str(); |
1145 |
|
} |
1146 |
|
|
1147 |
|
} // namespace strings |
1148 |
|
} // namespace theory |
1149 |
29589 |
} // namespace cvc5 |