GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/infer_proof_cons.cpp Lines: 451 537 84.0 %
Date: 2021-05-22 Branches: 891 2182 40.8 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Gereon Kremer, Aina Niemetz
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 inference to proof conversion.
14
 */
15
16
#include "theory/strings/infer_proof_cons.h"
17
18
#include "expr/proof_node_manager.h"
19
#include "expr/skolem_manager.h"
20
#include "options/smt_options.h"
21
#include "options/strings_options.h"
22
#include "theory/builtin/proof_checker.h"
23
#include "theory/rewriter.h"
24
#include "theory/strings/regexp_operation.h"
25
#include "theory/strings/theory_strings_utils.h"
26
#include "util/statistics_registry.h"
27
28
using namespace cvc5::kind;
29
30
namespace cvc5 {
31
namespace theory {
32
namespace strings {
33
34
1192
InferProofCons::InferProofCons(context::Context* c,
35
                               ProofNodeManager* pnm,
36
1192
                               SequencesStatistics& statistics)
37
1192
    : d_pnm(pnm), d_lazyFactMap(c), d_statistics(statistics)
38
{
39
1192
  Assert(d_pnm != nullptr);
40
1192
}
41
42
1887
void InferProofCons::notifyFact(const InferInfo& ii)
43
{
44
3746
  Node fact = ii.d_conc;
45
3774
  Trace("strings-ipc-debug")
46
1887
      << "InferProofCons::notifyFact: " << ii << std::endl;
47
1887
  if (d_lazyFactMap.find(fact) != d_lazyFactMap.end())
48
  {
49
28
    Trace("strings-ipc-debug") << "...duplicate!" << std::endl;
50
28
    return;
51
  }
52
3718
  Node symFact = CDProof::getSymmFact(fact);
53
1859
  if (!symFact.isNull() && d_lazyFactMap.find(symFact) != d_lazyFactMap.end())
54
  {
55
    Trace("strings-ipc-debug") << "...duplicate (sym)!" << std::endl;
56
    return;
57
  }
58
3718
  std::shared_ptr<InferInfo> iic = std::make_shared<InferInfo>(ii);
59
1859
  d_lazyFactMap.insert(ii.d_conc, iic);
60
}
61
62
1442
void InferProofCons::convert(InferenceId infer,
63
                             bool isRev,
64
                             Node conc,
65
                             const std::vector<Node>& exp,
66
                             ProofStep& ps,
67
                             TheoryProofStepBuffer& psb,
68
                             bool& useBuffer)
69
{
70
  // by default, don't use the buffer
71
1442
  useBuffer = false;
72
  // Must flatten children with respect to AND to be ready to explain.
73
  // We store the index where each flattened vector begins, since some
74
  // explanations are grouped together using AND.
75
2884
  std::vector<size_t> startExpIndex;
76
4085
  for (const Node& ec : exp)
77
  {
78
    // store the index in the flattened vector
79
2643
    startExpIndex.push_back(ps.d_children.size());
80
2643
    utils::flattenOp(AND, ec, ps.d_children);
81
  }
82
  // debug print
83
1442
  if (Trace.isOn("strings-ipc-debug"))
84
  {
85
    Trace("strings-ipc-debug") << "InferProofCons::convert: " << infer
86
                               << (isRev ? " :rev " : " ") << conc << std::endl;
87
    for (const Node& ec : exp)
88
    {
89
      Trace("strings-ipc-debug") << "    e: " << ec << std::endl;
90
    }
91
  }
92
  // try to find a set of proof steps to incorporate into the buffer
93
1442
  psb.clear();
94
1442
  NodeManager* nm = NodeManager::currentNM();
95
2884
  Node nodeIsRev = nm->mkConst(isRev);
96
1442
  switch (infer)
97
  {
98
    // ========================== equal by substitution+rewriting
99
387
    case InferenceId::STRINGS_I_NORM_S:
100
    case InferenceId::STRINGS_I_CONST_MERGE:
101
    case InferenceId::STRINGS_I_NORM:
102
    case InferenceId::STRINGS_LEN_NORM:
103
    case InferenceId::STRINGS_NORMAL_FORM:
104
    case InferenceId::STRINGS_CODE_PROXY:
105
    {
106
387
      ps.d_args.push_back(conc);
107
      // will attempt this rule
108
387
      ps.d_rule = PfRule::MACRO_SR_PRED_INTRO;
109
    }
110
387
    break;
111
    // ========================== substitution + rewriting
112
187
    case InferenceId::STRINGS_RE_NF_CONFLICT:
113
    case InferenceId::STRINGS_EXTF:
114
    case InferenceId::STRINGS_EXTF_N:
115
    case InferenceId::STRINGS_EXTF_D:
116
    case InferenceId::STRINGS_EXTF_D_N:
117
    case InferenceId::STRINGS_I_CONST_CONFLICT:
118
    case InferenceId::STRINGS_UNIT_CONST_CONFLICT:
119
    {
120
187
      if (!ps.d_children.empty())
121
      {
122
192
        std::vector<Node> exps(ps.d_children.begin(), ps.d_children.end() - 1);
123
192
        Node src = ps.d_children[ps.d_children.size() - 1];
124
96
        if (psb.applyPredTransform(src, conc, exps))
125
        {
126
64
          useBuffer = true;
127
        }
128
      }
129
187
      if (!useBuffer)
130
      {
131
        // use the predicate version?
132
123
        ps.d_args.push_back(conc);
133
123
        ps.d_rule = PfRule::MACRO_SR_PRED_INTRO;
134
      }
135
    }
136
187
    break;
137
    // ========================== rewrite pred
138
22
    case InferenceId::STRINGS_EXTF_EQ_REW:
139
    {
140
      // the last child is the predicate we are operating on, move to front
141
35
      Node src = ps.d_children[ps.d_children.size() - 1];
142
35
      std::vector<Node> expe(ps.d_children.begin(), ps.d_children.end() - 1);
143
      // start with a default rewrite
144
35
      Node mainEqSRew = psb.applyPredElim(src, expe);
145
22
      if (mainEqSRew == conc)
146
      {
147
        useBuffer = true;
148
        break;
149
      }
150
      // may need the "extended equality rewrite"
151
      Node mainEqSRew2 = psb.applyPredElim(mainEqSRew,
152
                                           {},
153
                                           MethodId::SB_DEFAULT,
154
                                           MethodId::SBA_SEQUENTIAL,
155
35
                                           MethodId::RW_REWRITE_EQ_EXT);
156
22
      if (mainEqSRew2 == conc)
157
      {
158
9
        useBuffer = true;
159
9
        break;
160
      }
161
      // rewrite again with default rewriter
162
26
      Node mainEqSRew3 = psb.applyPredElim(mainEqSRew2, {});
163
26
      useBuffer = (mainEqSRew3 == conc);
164
    }
165
13
    break;
166
    // ========================== substitution+rewriting, CONCAT_EQ, ...
167
420
    case InferenceId::STRINGS_F_CONST:
168
    case InferenceId::STRINGS_F_UNIFY:
169
    case InferenceId::STRINGS_F_ENDPOINT_EMP:
170
    case InferenceId::STRINGS_F_ENDPOINT_EQ:
171
    case InferenceId::STRINGS_F_NCTN:
172
    case InferenceId::STRINGS_N_EQ_CONF:
173
    case InferenceId::STRINGS_N_CONST:
174
    case InferenceId::STRINGS_N_UNIFY:
175
    case InferenceId::STRINGS_N_ENDPOINT_EMP:
176
    case InferenceId::STRINGS_N_ENDPOINT_EQ:
177
    case InferenceId::STRINGS_N_NCTN:
178
    case InferenceId::STRINGS_SSPLIT_CST_PROP:
179
    case InferenceId::STRINGS_SSPLIT_VAR_PROP:
180
    case InferenceId::STRINGS_SSPLIT_CST:
181
    case InferenceId::STRINGS_SSPLIT_VAR:
182
    {
183
840
      Trace("strings-ipc-core") << "Generate core rule for " << infer
184
420
                                << " (rev=" << isRev << ")" << std::endl;
185
      // All of the above inferences have the form:
186
      //   (explanation for why t and s have the same prefix/suffix) ^
187
      //   t = s ^
188
      //   (length constraint)?
189
      // We call t=s the "main equality" below. The length constraint is
190
      // optional, which we split on below.
191
420
      size_t nchild = ps.d_children.size();
192
420
      size_t mainEqIndex = 0;
193
420
      bool mainEqIndexSet = false;
194
      // the length constraint
195
766
      std::vector<Node> lenConstraint;
196
      // these inferences have a length constraint as the last explain
197
420
      if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY
198
287
          || infer == InferenceId::STRINGS_SSPLIT_CST || infer == InferenceId::STRINGS_SSPLIT_VAR
199
226
          || infer == InferenceId::STRINGS_SSPLIT_VAR_PROP
200
219
          || infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
201
      {
202
402
        if (exp.size() >= 2)
203
        {
204
201
          Assert(exp.size() <= startExpIndex.size());
205
          // The index of the "main" equality is the last equality before
206
          // the length explanation.
207
201
          mainEqIndex = startExpIndex[exp.size() - 1] - 1;
208
201
          mainEqIndexSet = true;
209
          // the remainder is the length constraint
210
603
          lenConstraint.insert(lenConstraint.end(),
211
402
                               ps.d_children.begin() + mainEqIndex + 1,
212
804
                               ps.d_children.end());
213
        }
214
      }
215
219
      else if (nchild >= 1)
216
      {
217
        // The index of the main equality is the last child.
218
219
        mainEqIndex = nchild - 1;
219
219
        mainEqIndexSet = true;
220
      }
221
766
      Node mainEq;
222
420
      if (mainEqIndexSet)
223
      {
224
420
        mainEq = ps.d_children[mainEqIndex];
225
840
        Trace("strings-ipc-core") << "Main equality " << mainEq << " at index "
226
420
                                  << mainEqIndex << std::endl;
227
      }
228
420
      if (mainEq.isNull() || mainEq.getKind() != EQUAL)
229
      {
230
        Trace("strings-ipc-core")
231
            << "...failed to find main equality" << std::endl;
232
        break;
233
      }
234
      // apply MACRO_SR_PRED_ELIM using equalities up to the main eq
235
766
      std::vector<Node> childrenSRew;
236
420
      childrenSRew.push_back(mainEq);
237
1260
      childrenSRew.insert(childrenSRew.end(),
238
                          ps.d_children.begin(),
239
1680
                          ps.d_children.begin() + mainEqIndex);
240
      Node mainEqSRew =
241
766
          psb.tryStep(PfRule::MACRO_SR_PRED_ELIM, childrenSRew, {});
242
420
      if (CDProof::isSame(mainEqSRew, mainEq))
243
      {
244
67
        Trace("strings-ipc-core") << "...undo step" << std::endl;
245
        // the rule added above was not necessary
246
67
        psb.popStep();
247
      }
248
353
      else if (mainEqSRew == conc)
249
      {
250
73
        Trace("strings-ipc-core") << "...success after rewrite!" << std::endl;
251
73
        useBuffer = true;
252
73
        break;
253
      }
254
694
      Trace("strings-ipc-core")
255
347
          << "Main equality after subs+rewrite " << mainEqSRew << std::endl;
256
      // now, apply CONCAT_EQ to get the remainder
257
693
      std::vector<Node> childrenCeq;
258
347
      childrenCeq.push_back(mainEqSRew);
259
693
      std::vector<Node> argsCeq;
260
347
      argsCeq.push_back(nodeIsRev);
261
693
      Node mainEqCeq = psb.tryStep(PfRule::CONCAT_EQ, childrenCeq, argsCeq);
262
694
      Trace("strings-ipc-core")
263
347
          << "Main equality after CONCAT_EQ " << mainEqCeq << std::endl;
264
347
      if (mainEqCeq.isNull() || mainEqCeq.getKind() != EQUAL)
265
      {
266
        // fail
267
        break;
268
      }
269
347
      else if (mainEqCeq == mainEqSRew)
270
      {
271
91
        Trace("strings-ipc-core") << "...undo step" << std::endl;
272
        // not necessary, probably first component of equality
273
91
        psb.popStep();
274
      }
275
      // Now, mainEqCeq is an equality t ++ ... == s ++ ... where the
276
      // inference involved t and s.
277
347
      if (infer == InferenceId::STRINGS_N_ENDPOINT_EQ
278
260
          || infer == InferenceId::STRINGS_N_ENDPOINT_EMP
279
260
          || infer == InferenceId::STRINGS_F_ENDPOINT_EQ
280
201
          || infer == InferenceId::STRINGS_F_ENDPOINT_EMP)
281
      {
282
        // Should be equal to conclusion already, or rewrite to it.
283
        // Notice that this step is necessary to handle the "rproc"
284
        // optimization in processSimpleNEq. Alternatively, this could
285
        // possibly be done by CONCAT_EQ with !isRev.
286
292
        std::vector<Node> cexp;
287
146
        if (psb.applyPredTransform(mainEqCeq,
288
                                   conc,
289
                                   cexp,
290
                                   MethodId::SB_DEFAULT,
291
                                   MethodId::SBA_SEQUENTIAL,
292
                                   MethodId::RW_REWRITE_EQ_EXT))
293
        {
294
286
          Trace("strings-ipc-core") << "Transformed to " << conc
295
143
                                    << " via pred transform" << std::endl;
296
          // success
297
143
          useBuffer = true;
298
143
          Trace("strings-ipc-core") << "...success!" << std::endl;
299
146
        }
300
        // Otherwise, note that EMP rules conclude ti = "" where
301
        // t1 ++ ... ++ tn == "". However, these are very rarely applied, let
302
        // alone for 2+ children. This case is intentionally unhandled here.
303
      }
304
201
      else if (infer == InferenceId::STRINGS_N_CONST || infer == InferenceId::STRINGS_F_CONST
305
201
               || infer == InferenceId::STRINGS_N_EQ_CONF)
306
      {
307
        // should be a constant conflict
308
        std::vector<Node> childrenC;
309
        childrenC.push_back(mainEqCeq);
310
        std::vector<Node> argsC;
311
        argsC.push_back(nodeIsRev);
312
        Node mainEqC = psb.tryStep(PfRule::CONCAT_CONFLICT, childrenC, argsC);
313
        if (mainEqC == conc)
314
        {
315
          useBuffer = true;
316
          Trace("strings-ipc-core") << "...success!" << std::endl;
317
        }
318
      }
319
      else
320
      {
321
401
        std::vector<Node> tvec;
322
401
        std::vector<Node> svec;
323
201
        utils::getConcat(mainEqCeq[0], tvec);
324
201
        utils::getConcat(mainEqCeq[1], svec);
325
401
        Node t0 = tvec[isRev ? tvec.size() - 1 : 0];
326
401
        Node s0 = svec[isRev ? svec.size() - 1 : 0];
327
201
        bool applySym = false;
328
        // may need to apply symmetry
329
402
        if ((infer == InferenceId::STRINGS_SSPLIT_CST
330
141
             || infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
331
261
            && t0.isConst())
332
        {
333
26
          Assert(!s0.isConst());
334
26
          applySym = true;
335
26
          std::swap(t0, s0);
336
        }
337
201
        if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY)
338
        {
339
133
          if (conc.getKind() != EQUAL)
340
          {
341
            break;
342
          }
343
          // one side should match, the other side could be a split constant
344
133
          if (conc[0] != t0 && conc[1] != s0)
345
          {
346
49
            applySym = true;
347
49
            std::swap(t0, s0);
348
          }
349
133
          Assert(conc[0].isConst() == t0.isConst());
350
133
          Assert(conc[1].isConst() == s0.isConst());
351
        }
352
201
        PfRule rule = PfRule::UNKNOWN;
353
        // the form of the required length constraint expected by the proof
354
401
        Node lenReq;
355
201
        bool lenSuccess = false;
356
201
        if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY)
357
        {
358
          // the required premise for unify is always len(x) = len(y),
359
          // however the explanation may not be literally this. Thus, we
360
          // need to reconstruct a proof from the given explanation.
361
          // it should be the case that lenConstraint => lenReq.
362
          // We use terms in the conclusion equality, not t0, s0 here.
363
399
          lenReq = nm->mkNode(STRING_LENGTH, conc[0])
364
266
                       .eqNode(nm->mkNode(STRING_LENGTH, conc[1]));
365
133
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
366
133
          rule = PfRule::CONCAT_UNIFY;
367
        }
368
68
        else if (infer == InferenceId::STRINGS_SSPLIT_VAR)
369
        {
370
          // it should be the case that lenConstraint => lenReq
371
3
          lenReq = nm->mkNode(STRING_LENGTH, t0)
372
2
                       .eqNode(nm->mkNode(STRING_LENGTH, s0))
373
2
                       .notNode();
374
1
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
375
1
          rule = PfRule::CONCAT_SPLIT;
376
        }
377
67
        else if (infer == InferenceId::STRINGS_SSPLIT_CST)
378
        {
379
          // it should be the case that lenConstraint => lenReq
380
180
          lenReq = nm->mkNode(STRING_LENGTH, t0)
381
120
                       .eqNode(nm->mkConst(Rational(0)))
382
120
                       .notNode();
383
60
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
384
60
          rule = PfRule::CONCAT_CSPLIT;
385
        }
386
7
        else if (infer == InferenceId::STRINGS_SSPLIT_VAR_PROP)
387
        {
388
          // it should be the case that lenConstraint => lenReq
389
11
          for (unsigned r = 0; r < 2; r++)
390
          {
391
30
            lenReq = nm->mkNode(GT,
392
20
                                nm->mkNode(STRING_LENGTH, t0),
393
20
                                nm->mkNode(STRING_LENGTH, s0));
394
10
            if (convertLengthPf(lenReq, lenConstraint, psb))
395
            {
396
6
              lenSuccess = true;
397
6
              break;
398
            }
399
4
            if (r == 0)
400
            {
401
              // may be the other direction
402
3
              applySym = true;
403
3
              std::swap(t0, s0);
404
            }
405
          }
406
7
          rule = PfRule::CONCAT_LPROP;
407
        }
408
        else if (infer == InferenceId::STRINGS_SSPLIT_CST_PROP)
409
        {
410
          // it should be the case that lenConstraint => lenReq
411
          lenReq = nm->mkNode(STRING_LENGTH, t0)
412
                       .eqNode(nm->mkConst(Rational(0)))
413
                       .notNode();
414
          lenSuccess = convertLengthPf(lenReq, lenConstraint, psb);
415
          rule = PfRule::CONCAT_CPROP;
416
        }
417
201
        if (!lenSuccess)
418
        {
419
2
          Trace("strings-ipc-core")
420
1
              << "...failed due to length constraint" << std::endl;
421
1
          break;
422
        }
423
        // apply symmetry if necessary
424
200
        if (applySym)
425
        {
426
154
          std::vector<Node> childrenSymm;
427
77
          childrenSymm.push_back(mainEqCeq);
428
          // note this explicit step may not be necessary
429
77
          mainEqCeq = psb.tryStep(PfRule::SYMM, childrenSymm, {});
430
154
          Trace("strings-ipc-core")
431
77
              << "Main equality after SYMM " << mainEqCeq << std::endl;
432
        }
433
200
        if (rule != PfRule::UNKNOWN)
434
        {
435
400
          Trace("strings-ipc-core")
436
200
              << "Core rule length requirement is " << lenReq << std::endl;
437
          // apply the given rule
438
400
          std::vector<Node> childrenMain;
439
200
          childrenMain.push_back(mainEqCeq);
440
200
          childrenMain.push_back(lenReq);
441
400
          std::vector<Node> argsMain;
442
200
          argsMain.push_back(nodeIsRev);
443
400
          Node mainEqMain = psb.tryStep(rule, childrenMain, argsMain);
444
400
          Trace("strings-ipc-core") << "Main equality after " << rule << " "
445
200
                                    << mainEqMain << std::endl;
446
200
          if (mainEqMain == mainEqCeq)
447
          {
448
6
            Trace("strings-ipc-core") << "...undo step" << std::endl;
449
            // not necessary, probably first component of equality
450
6
            psb.popStep();
451
          }
452
          // either equal or rewrites to it
453
400
          std::vector<Node> cexp;
454
200
          if (psb.applyPredTransform(mainEqMain, conc, cexp))
455
          {
456
            // requires that length success is also true
457
198
            useBuffer = true;
458
198
            Trace("strings-ipc-core") << "...success" << std::endl;
459
          }
460
          else
461
          {
462
2
            Trace("strings-ipc-core") << "...fail" << std::endl;
463
          }
464
        }
465
        else
466
        {
467
          // should always have given a rule to try above
468
          Assert(false) << "No reconstruction rule given for " << infer;
469
        }
470
346
      }
471
    }
472
346
    break;
473
    // ========================== Disequalities
474
2
    case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_STRING_SPLIT:
475
    case InferenceId::STRINGS_DEQ_DISL_STRINGS_SPLIT:
476
    {
477
6
      if (conc.getKind() != AND || conc.getNumChildren() != 2
478
4
          || conc[0].getKind() != EQUAL || !conc[0][0].getType().isStringLike()
479
4
          || conc[1].getKind() != EQUAL
480
6
          || conc[1][0].getKind() != STRING_LENGTH)
481
      {
482
        Trace("strings-ipc-deq") << "malformed application" << std::endl;
483
        Assert(false) << "unexpected conclusion " << conc << " for " << infer;
484
      }
485
      else
486
      {
487
        Node lenReq =
488
4
            nm->mkNode(GEQ, nm->mkNode(STRING_LENGTH, conc[0][0]), conc[1][1]);
489
4
        Trace("strings-ipc-deq")
490
2
            << "length requirement is " << lenReq << std::endl;
491
2
        if (convertLengthPf(lenReq, ps.d_children, psb))
492
        {
493
2
          Trace("strings-ipc-deq") << "...success length" << std::endl;
494
          // make the proof
495
4
          std::vector<Node> childrenMain;
496
2
          childrenMain.push_back(lenReq);
497
4
          std::vector<Node> argsMain;
498
2
          argsMain.push_back(nodeIsRev);
499
          Node mainConc =
500
4
              psb.tryStep(PfRule::STRING_DECOMPOSE, childrenMain, argsMain);
501
4
          Trace("strings-ipc-deq")
502
2
              << "...main conclusion is " << mainConc << std::endl;
503
2
          useBuffer = (mainConc == conc);
504
4
          Trace("strings-ipc-deq")
505
2
              << "...success is " << useBuffer << std::endl;
506
        }
507
        else
508
        {
509
          Trace("strings-ipc-deq") << "...fail length" << std::endl;
510
        }
511
      }
512
    }
513
2
    break;
514
    // ========================== Boolean split
515
62
    case InferenceId::STRINGS_CARD_SP:
516
    case InferenceId::STRINGS_LEN_SPLIT:
517
    case InferenceId::STRINGS_LEN_SPLIT_EMP:
518
    case InferenceId::STRINGS_DEQ_DISL_EMP_SPLIT:
519
    case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_EQ_SPLIT:
520
    case InferenceId::STRINGS_DEQ_STRINGS_EQ:
521
    case InferenceId::STRINGS_DEQ_LENS_EQ:
522
    case InferenceId::STRINGS_DEQ_LENGTH_SP:
523
    {
524
62
      if (conc.getKind() != OR)
525
      {
526
        // This should never happen. If it does, we resort to using
527
        // STRING_TRUST below (in production mode).
528
        Assert(false) << "Expected OR conclusion for " << infer;
529
      }
530
      else
531
      {
532
62
        ps.d_rule = PfRule::SPLIT;
533
62
        Assert(ps.d_children.empty());
534
62
        ps.d_args.push_back(conc[0]);
535
      }
536
    }
537
62
    break;
538
    // ========================== Regular expression unfolding
539
38
    case InferenceId::STRINGS_RE_UNFOLD_POS:
540
    case InferenceId::STRINGS_RE_UNFOLD_NEG:
541
    {
542
38
      Assert (!ps.d_children.empty());
543
38
      size_t nchild = ps.d_children.size();
544
76
      Node mem = ps.d_children[nchild-1];
545
38
      if (nchild>1)
546
      {
547
        // if more than one, apply MACRO_SR_PRED_ELIM
548
        std::vector<Node> tcs;
549
        tcs.insert(tcs.end(),
550
                          ps.d_children.begin(),
551
                          ps.d_children.begin() + (nchild-1));
552
        mem = psb.applyPredElim(mem, tcs);
553
        useBuffer = true;
554
      }
555
38
      PfRule r = PfRule::UNKNOWN;
556
38
      if (mem.isNull())
557
      {
558
        // failed to eliminate above
559
        Assert(false) << "Failed to apply MACRO_SR_PRED_ELIM for RE unfold";
560
        useBuffer = false;
561
      }
562
38
      else if (infer == InferenceId::STRINGS_RE_UNFOLD_POS)
563
      {
564
30
        r = PfRule::RE_UNFOLD_POS;
565
      }
566
      else
567
      {
568
8
        r = PfRule::RE_UNFOLD_NEG;
569
        // it may be an optimized form of concat simplification
570
8
        Assert(mem.getKind() == NOT && mem[0].getKind() == STRING_IN_REGEXP);
571
8
        if (mem[0][1].getKind() == REGEXP_CONCAT)
572
        {
573
          size_t index;
574
12
          Node reLen = RegExpOpr::getRegExpConcatFixed(mem[0][1], index);
575
          // if we can find a fixed length for a component, use the optimized
576
          // version
577
6
          if (!reLen.isNull())
578
          {
579
4
            r = PfRule::RE_UNFOLD_NEG_CONCAT_FIXED;
580
          }
581
        }
582
      }
583
38
      if (useBuffer)
584
      {
585
        mem = psb.tryStep(r, {mem}, {});
586
        // should match the conclusion
587
        useBuffer = (mem==conc);
588
      }
589
      else
590
      {
591
38
        ps.d_rule = r;
592
38
      }
593
    }
594
38
    break;
595
    // ========================== Reduction
596
81
    case InferenceId::STRINGS_CTN_POS:
597
    case InferenceId::STRINGS_CTN_NEG_EQUAL:
598
    {
599
81
      if (ps.d_children.size() != 1)
600
      {
601
2
        break;
602
      }
603
80
      bool polarity = ps.d_children[0].getKind() != NOT;
604
160
      Node atom = polarity ? ps.d_children[0] : ps.d_children[0][0];
605
160
      std::vector<Node> args;
606
80
      args.push_back(atom);
607
160
      Node res = psb.tryStep(PfRule::STRING_EAGER_REDUCTION, {}, args);
608
80
      if (res.isNull())
609
      {
610
        break;
611
      }
612
      // ite( contains(x,t), x = k1 ++ t ++ k2, x != t )
613
160
      std::vector<Node> tiChildren;
614
80
      tiChildren.push_back(ps.d_children[0]);
615
      Node ctnt = psb.tryStep(
616
160
          polarity ? PfRule::TRUE_INTRO : PfRule::FALSE_INTRO, tiChildren, {});
617
80
      if (ctnt.isNull() || ctnt.getKind() != EQUAL)
618
      {
619
        break;
620
      }
621
160
      std::vector<Node> tchildren;
622
80
      tchildren.push_back(ctnt);
623
      // apply substitution { contains(x,t) -> true|false } and rewrite to get
624
      // conclusion x = k1 ++ t ++ k2 or x != t.
625
80
      if (psb.applyPredTransform(res, conc, tchildren))
626
      {
627
80
        useBuffer = true;
628
81
      }
629
    }
630
80
    break;
631
632
137
    case InferenceId::STRINGS_REDUCTION:
633
    {
634
137
      size_t nchild = conc.getNumChildren();
635
274
      Node mainEq;
636
137
      if (conc.getKind() == EQUAL)
637
      {
638
3
        mainEq = conc;
639
      }
640
134
      else if (conc.getKind() == AND && conc[nchild - 1].getKind() == EQUAL)
641
      {
642
134
        mainEq = conc[nchild - 1];
643
      }
644
137
      if (mainEq.isNull())
645
      {
646
        Trace("strings-ipc-red") << "Bad Reduction: " << conc << std::endl;
647
        Assert(false) << "Unexpected reduction " << conc;
648
        break;
649
      }
650
274
      std::vector<Node> argsRed;
651
      // the left hand side of the last conjunct is the term we are reducing
652
137
      argsRed.push_back(mainEq[0]);
653
274
      Node red = psb.tryStep(PfRule::STRING_REDUCTION, {}, argsRed);
654
137
      Trace("strings-ipc-red") << "Reduction : " << red << std::endl;
655
137
      if (!red.isNull())
656
      {
657
        // either equal or rewrites to it
658
274
        std::vector<Node> cexp;
659
137
        if (psb.applyPredTransform(red, conc, cexp))
660
        {
661
130
          Trace("strings-ipc-red") << "...success!" << std::endl;
662
130
          useBuffer = true;
663
        }
664
        else
665
        {
666
7
          Trace("strings-ipc-red") << "...failed to reduce" << std::endl;
667
        }
668
137
      }
669
    }
670
137
    break;
671
    // ========================== code injectivity
672
55
    case InferenceId::STRINGS_CODE_INJ:
673
    {
674
55
      ps.d_rule = PfRule::STRING_CODE_INJ;
675
55
      Assert(conc.getKind() == OR && conc.getNumChildren() == 3
676
             && conc[2].getKind() == EQUAL);
677
55
      ps.d_args.push_back(conc[2][0]);
678
55
      ps.d_args.push_back(conc[2][1]);
679
    }
680
55
    break;
681
    // ========================== unit injectivity
682
10
    case InferenceId::STRINGS_UNIT_INJ:
683
    {
684
10
      ps.d_rule = PfRule::STRING_SEQ_UNIT_INJ;
685
    }
686
10
    break;
687
    // ========================== prefix conflict
688
32
    case InferenceId::STRINGS_PREFIX_CONFLICT:
689
    {
690
32
      Trace("strings-ipc-prefix") << "Prefix conflict..." << std::endl;
691
64
      std::vector<Node> eqs;
692
74
      for (const Node& e : ps.d_children)
693
      {
694
42
        Kind ek = e.getKind();
695
42
        if (ek == EQUAL)
696
        {
697
32
          Trace("strings-ipc-prefix") << "- equality : " << e << std::endl;
698
32
          eqs.push_back(e);
699
        }
700
10
        else if (ek == STRING_IN_REGEXP)
701
        {
702
          // unfold it and extract the equality
703
20
          std::vector<Node> children;
704
10
          children.push_back(e);
705
20
          std::vector<Node> args;
706
20
          Node eunf = psb.tryStep(PfRule::RE_UNFOLD_POS, children, args);
707
20
          Trace("strings-ipc-prefix")
708
10
              << "--- " << e << " unfolds to " << eunf << std::endl;
709
10
          if (eunf.isNull())
710
          {
711
            continue;
712
          }
713
10
          else if (eunf.getKind() == AND)
714
          {
715
            // equality is the last conjunct
716
20
            std::vector<Node> childrenAE;
717
10
            childrenAE.push_back(eunf);
718
20
            std::vector<Node> argsAE;
719
10
            argsAE.push_back(nm->mkConst(Rational(eunf.getNumChildren() - 1)));
720
20
            Node eunfAE = psb.tryStep(PfRule::AND_ELIM, childrenAE, argsAE);
721
20
            Trace("strings-ipc-prefix")
722
10
                << "--- and elim to " << eunfAE << std::endl;
723
10
            if (eunfAE.isNull() || eunfAE.getKind() != EQUAL)
724
            {
725
              Assert(false)
726
                  << "Unexpected unfolded premise " << eunf << " for " << infer;
727
              continue;
728
            }
729
20
            Trace("strings-ipc-prefix")
730
10
                << "- equality : " << eunfAE << std::endl;
731
10
            eqs.push_back(eunfAE);
732
          }
733
          else if (eunf.getKind() == EQUAL)
734
          {
735
            Trace("strings-ipc-prefix") << "- equality : " << eunf << std::endl;
736
            eqs.push_back(eunf);
737
          }
738
        }
739
        else
740
        {
741
          // not sure how to use this assumption
742
          Assert(false) << "Unexpected premise " << e << " for " << infer;
743
        }
744
      }
745
32
      if (eqs.empty())
746
      {
747
        break;
748
      }
749
      // connect via transitivity
750
64
      Node curr = eqs[0];
751
42
      for (size_t i = 1, esize = eqs.size(); i < esize; i++)
752
      {
753
20
        Node prev = curr;
754
10
        curr = convertTrans(curr, eqs[1], psb);
755
10
        if (curr.isNull())
756
        {
757
          break;
758
        }
759
10
        Trace("strings-ipc-prefix") << "- Via trans: " << curr << std::endl;
760
      }
761
32
      if (curr.isNull())
762
      {
763
        break;
764
      }
765
64
      Trace("strings-ipc-prefix")
766
32
          << "- Possible conflicting equality : " << curr << std::endl;
767
64
      std::vector<Node> emp;
768
64
      Node concE = psb.applyPredElim(curr, emp);
769
64
      Trace("strings-ipc-prefix")
770
32
          << "- After pred elim: " << concE << std::endl;
771
32
      if (concE == conc)
772
      {
773
32
        Trace("strings-ipc-prefix") << "...success!" << std::endl;
774
32
        useBuffer = true;
775
32
      }
776
    }
777
32
    break;
778
    // ========================== regular expressions
779
7
    case InferenceId::STRINGS_RE_INTER_INCLUDE:
780
    case InferenceId::STRINGS_RE_INTER_CONF:
781
    case InferenceId::STRINGS_RE_INTER_INFER:
782
    {
783
14
      std::vector<Node> reiExp;
784
14
      std::vector<Node> reis;
785
14
      std::vector<Node> reiChildren;
786
14
      std::vector<Node> reiChildrenOrig;
787
14
      Node x;
788
      // make the regular expression intersection that summarizes all
789
      // memberships in the explanation
790
23
      for (const Node& c : ps.d_children)
791
      {
792
16
        bool polarity = c.getKind() != NOT;
793
30
        Node catom = polarity ? c : c[0];
794
18
        if (catom.getKind() != STRING_IN_REGEXP)
795
        {
796
2
          Assert(c.getKind() == EQUAL);
797
2
          if (c.getKind() == EQUAL)
798
          {
799
2
            reiExp.push_back(c);
800
          }
801
2
          continue;
802
        }
803
14
        if (x.isNull())
804
        {
805
          // just take the first LHS; others should be equated to it by exp
806
7
          x = catom[0];
807
        }
808
        Node rcurr =
809
28
            polarity ? catom[1] : nm->mkNode(REGEXP_COMPLEMENT, catom[1]);
810
14
        reis.push_back(rcurr);
811
28
        Node mem = nm->mkNode(STRING_IN_REGEXP, catom[0], rcurr);
812
14
        reiChildren.push_back(mem);
813
14
        reiChildrenOrig.push_back(c);
814
      }
815
      // go back and justify each premise
816
7
      bool successChildren = true;
817
21
      for (size_t i = 0, nchild = reiChildren.size(); i < nchild; i++)
818
      {
819
14
        if (!psb.applyPredTransform(reiChildrenOrig[i], reiChildren[i], reiExp))
820
        {
821
          Trace("strings-ipc-re")
822
              << "... failed to justify child " << reiChildren[i] << " from "
823
              << reiChildrenOrig[i] << std::endl;
824
          successChildren = false;
825
          break;
826
        }
827
      }
828
7
      if (!successChildren)
829
      {
830
        break;
831
      }
832
14
      Node mem = psb.tryStep(PfRule::RE_INTER, reiChildren, {});
833
14
      Trace("strings-ipc-re")
834
7
          << "Regular expression summary: " << mem << std::endl;
835
      // the conclusion is rewritable to the premises via rewriting?
836
7
      if (psb.applyPredTransform(mem, conc, {}))
837
      {
838
2
        Trace("strings-ipc-re") << "... success!" << std::endl;
839
2
        useBuffer = true;
840
      }
841
      else
842
      {
843
10
        Trace("strings-ipc-re")
844
5
            << "...failed to rewrite to conclusion" << std::endl;
845
7
      }
846
    }
847
7
    break;
848
    // ========================== unknown and currently unsupported
849
2
    case InferenceId::STRINGS_CARDINALITY:
850
    case InferenceId::STRINGS_I_CYCLE_E:
851
    case InferenceId::STRINGS_I_CYCLE:
852
    case InferenceId::STRINGS_INFER_EMP:
853
    case InferenceId::STRINGS_RE_DELTA:
854
    case InferenceId::STRINGS_RE_DELTA_CONF:
855
    case InferenceId::STRINGS_RE_DERIVE:
856
    case InferenceId::STRINGS_FLOOP:
857
    case InferenceId::STRINGS_FLOOP_CONFLICT:
858
    case InferenceId::STRINGS_DEQ_NORM_EMP:
859
    case InferenceId::STRINGS_CTN_TRANS:
860
    case InferenceId::STRINGS_CTN_DECOMPOSE:
861
    default:
862
      // do nothing, these will be converted to STRING_TRUST below since the
863
      // rule is unknown.
864
2
      break;
865
  }
866
867
  // now see if we would succeed with the checker-to-try
868
1442
  bool success = false;
869
1442
  if (ps.d_rule != PfRule::UNKNOWN)
870
  {
871
1350
    Trace("strings-ipc") << "For " << infer << ", try proof rule " << ps.d_rule
872
675
                         << "...";
873
675
    Assert(ps.d_rule != PfRule::UNKNOWN);
874
1350
    Node pconc = psb.tryStep(ps.d_rule, ps.d_children, ps.d_args);
875
675
    if (pconc.isNull() || pconc != conc)
876
    {
877
36
      Trace("strings-ipc") << "failed, pconc is " << pconc << " (expected "
878
18
                           << conc << ")" << std::endl;
879
18
      ps.d_rule = PfRule::UNKNOWN;
880
    }
881
    else
882
    {
883
      // successfully set up a single step proof in ps
884
657
      success = true;
885
657
      Trace("strings-ipc") << "success!" << std::endl;
886
    }
887
  }
888
767
  else if (useBuffer)
889
  {
890
    // successfully set up a multi step proof in psb
891
738
    success = true;
892
  }
893
  else
894
  {
895
58
    Trace("strings-ipc") << "For " << infer << " " << conc
896
29
                         << ", no proof rule, failed" << std::endl;
897
  }
898
1442
  if (!success)
899
  {
900
    // debug print
901
47
    if (Trace.isOn("strings-ipc-fail"))
902
    {
903
      Trace("strings-ipc-fail")
904
          << "InferProofCons::convert: Failed " << infer
905
          << (isRev ? " :rev " : " ") << conc << std::endl;
906
      for (const Node& ec : exp)
907
      {
908
        Trace("strings-ipc-fail") << "    e: " << ec << std::endl;
909
      }
910
    }
911
    // untrustworthy conversion, the argument of STRING_TRUST is its conclusion
912
47
    ps.d_args.clear();
913
47
    ps.d_args.push_back(conc);
914
    // use the trust rule
915
47
    ps.d_rule = PfRule::STRING_TRUST;
916
    // add to stats
917
47
    d_statistics.d_inferencesNoPf << infer;
918
  }
919
1442
  if (Trace.isOn("strings-ipc-debug"))
920
  {
921
    if (useBuffer)
922
    {
923
      Trace("strings-ipc-debug")
924
          << "InferProofCons::convert returned buffer with "
925
          << psb.getNumSteps() << " steps:" << std::endl;
926
      const std::vector<std::pair<Node, ProofStep>>& steps = psb.getSteps();
927
      for (const std::pair<Node, ProofStep>& step : steps)
928
      {
929
        Trace("strings-ipc-debug")
930
            << "- " << step.first << " via " << step.second << std::endl;
931
      }
932
    }
933
    else
934
    {
935
      Trace("strings-ipc-debug")
936
          << "InferProofCons::convert returned " << ps << std::endl;
937
    }
938
  }
939
1442
}
940
941
206
bool InferProofCons::convertLengthPf(Node lenReq,
942
                                     const std::vector<Node>& lenExp,
943
                                     TheoryProofStepBuffer& psb)
944
{
945
340
  for (const Node& le : lenExp)
946
  {
947
226
    if (lenReq == le)
948
    {
949
92
      return true;
950
    }
951
  }
952
228
  Trace("strings-ipc-len") << "Must explain " << lenReq << " by " << lenExp
953
114
                           << std::endl;
954
126
  for (const Node& le : lenExp)
955
  {
956
    // probably rewrites to it?
957
134
    std::vector<Node> exp;
958
122
    if (psb.applyPredTransform(le, lenReq, exp))
959
    {
960
75
      Trace("strings-ipc-len") << "...success by rewrite" << std::endl;
961
75
      return true;
962
    }
963
    // maybe x != "" => len(x) != 0
964
59
    std::vector<Node> children;
965
47
    children.push_back(le);
966
59
    std::vector<Node> args;
967
59
    Node res = psb.tryStep(PfRule::STRING_LENGTH_NON_EMPTY, children, args);
968
47
    if (res == lenReq)
969
    {
970
35
      Trace("strings-ipc-len") << "...success by LENGTH_NON_EMPTY" << std::endl;
971
35
      return true;
972
    }
973
  }
974
4
  Trace("strings-ipc-len") << "...failed" << std::endl;
975
4
  return false;
976
}
977
978
10
Node InferProofCons::convertTrans(Node eqa,
979
                                  Node eqb,
980
                                  TheoryProofStepBuffer& psb)
981
{
982
10
  if (eqa.getKind() != EQUAL || eqb.getKind() != EQUAL)
983
  {
984
    return Node::null();
985
  }
986
10
  for (uint32_t i = 0; i < 2; i++)
987
  {
988
10
    Node eqaSym = i == 0 ? eqa[1].eqNode(eqa[0]) : eqa;
989
10
    for (uint32_t j = 0; j < 2; j++)
990
    {
991
10
      Node eqbSym = j == 0 ? eqb : eqb[1].eqNode(eqb[1]);
992
10
      if (eqa[i] == eqb[j])
993
      {
994
20
        std::vector<Node> children;
995
10
        children.push_back(eqaSym);
996
10
        children.push_back(eqbSym);
997
10
        return psb.tryStep(PfRule::TRANS, children, {});
998
      }
999
    }
1000
  }
1001
  return Node::null();
1002
}
1003
1004
1442
std::shared_ptr<ProofNode> InferProofCons::getProofFor(Node fact)
1005
{
1006
  // temporary proof
1007
2884
  CDProof pf(d_pnm);
1008
  // get the inference
1009
1442
  NodeInferInfoMap::iterator it = d_lazyFactMap.find(fact);
1010
1442
  if (it == d_lazyFactMap.end())
1011
  {
1012
    Node factSym = CDProof::getSymmFact(fact);
1013
    if (!factSym.isNull())
1014
    {
1015
      // Use the symmetric fact. There is no need to explictly make a
1016
      // SYMM proof, as this is handled by CDProof::getProofFor below.
1017
      it = d_lazyFactMap.find(factSym);
1018
    }
1019
  }
1020
1442
  AlwaysAssert(it != d_lazyFactMap.end());
1021
  // now go back and convert it to proof steps and add to proof
1022
1442
  bool useBuffer = false;
1023
2884
  ProofStep ps;
1024
2884
  TheoryProofStepBuffer psb(d_pnm->getChecker());
1025
2884
  std::shared_ptr<InferInfo> ii = (*it).second;
1026
  // run the conversion
1027
1442
  convert(ii->getId(), ii->d_idRev, ii->d_conc, ii->d_premises, ps, psb, useBuffer);
1028
  // make the proof based on the step or the buffer
1029
1442
  if (useBuffer)
1030
  {
1031
738
    if (!pf.addSteps(psb))
1032
    {
1033
      return nullptr;
1034
    }
1035
  }
1036
  else
1037
  {
1038
704
    if (!pf.addStep(fact, ps))
1039
    {
1040
      return nullptr;
1041
    }
1042
  }
1043
1442
  return pf.getProofFor(fact);
1044
}
1045
1046
3292
std::string InferProofCons::identify() const
1047
{
1048
3292
  return "strings::InferProofCons";
1049
}
1050
1051
}  // namespace strings
1052
}  // namespace theory
1053
28194
}  // namespace cvc5