GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/query_generator.cpp Lines: 2 223 0.9 %
Date: 2021-09-29 Branches: 2 674 0.3 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Mathias Preiner, Gereon Kremer
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 a class for mining interesting satisfiability
14
 * queries from a stream of generated expressions.
15
 */
16
17
#include "theory/quantifiers/query_generator.h"
18
19
#include <fstream>
20
#include <sstream>
21
22
#include "options/quantifiers_options.h"
23
#include "util/random.h"
24
25
using namespace std;
26
using namespace cvc5::kind;
27
28
namespace cvc5 {
29
namespace theory {
30
namespace quantifiers {
31
32
9
QueryGenerator::QueryGenerator(Env& env) : ExprMiner(env), d_queryCount(0) {}
33
void QueryGenerator::initialize(const std::vector<Node>& vars, SygusSampler* ss)
34
{
35
  Assert(ss != nullptr);
36
  d_queryCount = 0;
37
  ExprMiner::initialize(vars, ss);
38
}
39
40
void QueryGenerator::setThreshold(unsigned deqThresh)
41
{
42
  d_deqThresh = deqThresh;
43
}
44
45
bool QueryGenerator::addTerm(Node n, std::ostream& out)
46
{
47
  Node nn = n.getKind() == NOT ? n[0] : n;
48
  if (d_terms.find(nn) != d_terms.end())
49
  {
50
    return false;
51
  }
52
  d_terms.insert(nn);
53
54
  Trace("sygus-qgen") << "QueryGenerator::addTerm : " << n << std::endl;
55
  unsigned npts = d_sampler->getNumSamplePoints();
56
  TypeNode tn = n.getType();
57
  // TODO : as an optimization, use a shared lazy trie?
58
59
  // the queries we generate on this round
60
  std::vector<Node> queries;
61
  // For each query in the above vector, this stores the indices of the points
62
  // for which that query evaluated to true on.
63
  std::vector<std::vector<unsigned>> queriesPtTrue;
64
  // the sample point indices for which the above queries are true
65
  std::unordered_set<unsigned> indices;
66
67
  // collect predicate queries (if n is Boolean)
68
  if (tn.isBoolean())
69
  {
70
    std::map<Node, std::vector<unsigned>> ev_to_pt;
71
    unsigned index = 0;
72
    unsigned threshCount = 0;
73
    while (index < npts && threshCount < 2)
74
    {
75
      Node v = d_sampler->evaluate(nn, index);
76
      ev_to_pt[v].push_back(index);
77
      if (ev_to_pt[v].size() == d_deqThresh + 1)
78
      {
79
        threshCount++;
80
      }
81
      index++;
82
    }
83
    if (threshCount < 2)
84
    {
85
      for (const auto& etp : ev_to_pt)
86
      {
87
        if (etp.second.size() < d_deqThresh)
88
        {
89
          indices.insert(etp.second.begin(), etp.second.end());
90
          Node qy = nn;
91
          Assert(etp.first.isConst());
92
          if (!etp.first.getConst<bool>())
93
          {
94
            qy = qy.negate();
95
          }
96
          queries.push_back(qy);
97
          queriesPtTrue.push_back(etp.second);
98
        }
99
      }
100
    }
101
  }
102
103
  // collect equality queries
104
  findQueries(nn, queries, queriesPtTrue);
105
  Assert(queries.size() == queriesPtTrue.size());
106
  if (queries.empty())
107
  {
108
    return true;
109
  }
110
  Trace("sygus-qgen-debug")
111
      << "query: Check " << queries.size() << " queries..." << std::endl;
112
  // literal queries
113
  for (unsigned i = 0, nqueries = queries.size(); i < nqueries; i++)
114
  {
115
    Node qy = queries[i];
116
    std::vector<unsigned>& tIndices = queriesPtTrue[i];
117
    // we have an interesting query
118
    out << "(query " << qy << ")  ; " << tIndices.size() << "/" << npts
119
        << std::endl;
120
    AlwaysAssert(!tIndices.empty());
121
    checkQuery(qy, tIndices[0]);
122
    // add information
123
    for (unsigned& ti : tIndices)
124
    {
125
      d_ptToQueries[ti].push_back(qy);
126
      d_qysToPoints[qy].push_back(ti);
127
      indices.insert(ti);
128
    }
129
  }
130
  // for each new index, we may have a new conjunctive query
131
  NodeManager* nm = NodeManager::currentNM();
132
  for (const unsigned& i : indices)
133
  {
134
    std::vector<Node>& qsi = d_ptToQueries[i];
135
    if (qsi.size() > 1)
136
    {
137
      // take two random queries
138
      std::shuffle(qsi.begin(), qsi.end(), Random::getRandom());
139
      Node qy = nm->mkNode(AND, qsi[0], qsi[1]);
140
      checkQuery(qy, i);
141
    }
142
  }
143
  Trace("sygus-qgen-check") << "...finished." << std::endl;
144
  return true;
145
}
146
147
void QueryGenerator::checkQuery(Node qy, unsigned spIndex)
148
{
149
  // external query
150
  if (options::sygusQueryGenDumpFiles()
151
      == options::SygusQueryDumpFilesMode::ALL)
152
  {
153
    dumpQuery(qy, spIndex);
154
  }
155
156
  if (options::sygusQueryGenCheck())
157
  {
158
    Trace("sygus-qgen-check") << "  query: check " << qy << "..." << std::endl;
159
    // make the satisfiability query
160
    std::unique_ptr<SmtEngine> queryChecker;
161
    initializeChecker(queryChecker, qy);
162
    Result r = queryChecker->checkSat();
163
    Trace("sygus-qgen-check") << "  query: ...got : " << r << std::endl;
164
    if (r.asSatisfiabilityResult().isSat() == Result::UNSAT)
165
    {
166
      std::stringstream ss;
167
      ss << "--sygus-rr-query-gen detected unsoundness in cvc5 on input " << qy
168
         << "!" << std::endl;
169
      ss << "This query has a model : " << std::endl;
170
      std::vector<Node> pt;
171
      d_sampler->getSamplePoint(spIndex, pt);
172
      Assert(pt.size() == d_vars.size());
173
      for (unsigned i = 0, size = pt.size(); i < size; i++)
174
      {
175
        ss << "  " << d_vars[i] << " -> " << pt[i] << std::endl;
176
      }
177
      ss << "but cvc5 answered unsat!" << std::endl;
178
      AlwaysAssert(false) << ss.str();
179
    }
180
    if (options::sygusQueryGenDumpFiles()
181
        == options::SygusQueryDumpFilesMode::UNSOLVED)
182
    {
183
      if (r.asSatisfiabilityResult().isSat() != Result::SAT)
184
      {
185
        dumpQuery(qy, spIndex);
186
      }
187
    }
188
  }
189
190
  d_queryCount++;
191
}
192
193
void QueryGenerator::dumpQuery(Node qy, unsigned spIndex)
194
{
195
  // Print the query and the query + its model (commented) to queryN.smt2
196
  std::vector<Node> pt;
197
  d_sampler->getSamplePoint(spIndex, pt);
198
  size_t nvars = d_vars.size();
199
  AlwaysAssert(pt.size() == d_vars.size());
200
  std::stringstream fname;
201
  fname << "query" << d_queryCount << ".smt2";
202
  std::ofstream fs(fname.str(), std::ofstream::out);
203
  fs << "(set-logic ALL)" << std::endl;
204
  for (unsigned i = 0; i < 2; i++)
205
  {
206
    for (size_t j = 0; j < nvars; j++)
207
    {
208
      Node x = d_vars[j];
209
      if (i == 0)
210
      {
211
        fs << "(declare-fun " << x << " () " << x.getType() << ")";
212
      }
213
      else
214
      {
215
        fs << ";(define-fun " << x << " () " << x.getType() << " " << pt[j]
216
           << ")";
217
      }
218
      fs << std::endl;
219
    }
220
  }
221
  fs << "(assert " << qy << ")" << std::endl;
222
  fs << "(check-sat)" << std::endl;
223
  fs.close();
224
}
225
226
void QueryGenerator::findQueries(
227
    Node n,
228
    std::vector<Node>& queries,
229
    std::vector<std::vector<unsigned>>& queriesPtTrue)
230
{
231
  // At a high level, this method traverses the LazyTrie for the type of n
232
  // and tries to find paths to leafs that contain terms n' such that n = n'
233
  // or n != n' is an interesting query, i.e. satisfied for a small number of
234
  // points.
235
  TypeNode tn = n.getType();
236
  LazyTrie* lt = &d_qgtTrie[tn];
237
  // These vectors are the set of indices of sample points for which the current
238
  // node we are considering are { equal, disequal } from n.
239
  std::vector<unsigned> eqIndex[2];
240
  Trace("sygus-qgen-debug") << "Compute queries for " << n << "...\n";
241
242
  LazyTrieEvaluator* ev = d_sampler;
243
  unsigned ntotal = d_sampler->getNumSamplePoints();
244
  unsigned index = 0;
245
  bool exact = true;
246
  bool pushEq[2] = {false, false};
247
  bool pre = true;
248
  // The following parallel vectors describe the state of the locations in the
249
  // trie we are currently visiting.
250
  // Reference to the location in the trie
251
  std::vector<LazyTrie*> visitTr;
252
  // The index of the sample point we are testing
253
  std::vector<unsigned> currIndex;
254
  // Whether the path to this location exactly matches the evaluation of n
255
  std::vector<bool> currExact;
256
  // Whether we are adding to the points that are { equal, disequal } by
257
  // traversing to this location.
258
  std::vector<bool> pushIndex[2];
259
  // Whether we are in a pre-traversal for this location.
260
  std::vector<bool> preVisit;
261
  visitTr.push_back(lt);
262
  currIndex.push_back(0);
263
  currExact.push_back(true);
264
  pushIndex[0].push_back(false);
265
  pushIndex[1].push_back(false);
266
  preVisit.push_back(true);
267
  do
268
  {
269
    lt = visitTr.back();
270
    index = currIndex.back();
271
    exact = currExact.back();
272
    for (unsigned r = 0; r < 2; r++)
273
    {
274
      pushEq[r] = pushIndex[r].back();
275
    }
276
    pre = preVisit.back();
277
    if (!pre)
278
    {
279
      visitTr.pop_back();
280
      currIndex.pop_back();
281
      currExact.pop_back();
282
      preVisit.pop_back();
283
      // clean up the indices of points that are { equal, disequal }
284
      for (unsigned r = 0; r < 2; r++)
285
      {
286
        if (pushEq[r])
287
        {
288
          eqIndex[r].pop_back();
289
        }
290
        pushIndex[r].pop_back();
291
      }
292
    }
293
    else
294
    {
295
      preVisit[preVisit.size() - 1] = false;
296
      // add to the indices of points that are { equal, disequal }
297
      for (unsigned r = 0; r < 2; r++)
298
      {
299
        if (pushEq[r])
300
        {
301
          eqIndex[r].push_back(index - 1);
302
        }
303
      }
304
      int eqAllow = d_deqThresh - eqIndex[0].size();
305
      int deqAllow = d_deqThresh - eqIndex[1].size();
306
      Trace("sygus-qgen-debug")
307
          << "Find queries " << n << " " << index << "/" << ntotal
308
          << ", deq/eq allow = " << deqAllow << "/" << eqAllow
309
          << ", exact = " << exact << std::endl;
310
      if (index == ntotal)
311
      {
312
        if (exact)
313
        {
314
          // add to the trie
315
          lt->d_lazy_child = n;
316
        }
317
        else
318
        {
319
          Node nAlmostEq = lt->d_lazy_child;
320
          // if made it here, we still should have either a equality or
321
          // a disequality that is allowed.
322
          Assert(deqAllow >= 0 || eqAllow >= 0);
323
          Node query = n.eqNode(nAlmostEq);
324
          std::vector<unsigned> tIndices;
325
          if (eqAllow >= 0)
326
          {
327
            tIndices.insert(
328
                tIndices.end(), eqIndex[0].begin(), eqIndex[0].end());
329
          }
330
          else if (deqAllow >= 0)
331
          {
332
            query = query.negate();
333
            tIndices.insert(
334
                tIndices.end(), eqIndex[1].begin(), eqIndex[1].end());
335
          }
336
          AlwaysAssert(tIndices.size() <= d_deqThresh);
337
          if (!tIndices.empty())
338
          {
339
            queries.push_back(query);
340
            queriesPtTrue.push_back(tIndices);
341
          }
342
        }
343
      }
344
      else
345
      {
346
        if (!lt->d_lazy_child.isNull())
347
        {
348
          // if there is a lazy child here, push
349
          Node e_lc = ev->evaluate(lt->d_lazy_child, index);
350
          // store at next level
351
          lt->d_children[e_lc].d_lazy_child = lt->d_lazy_child;
352
          // replace
353
          lt->d_lazy_child = Node::null();
354
        }
355
        // compute
356
        Node e_this = ev->evaluate(n, index);
357
358
        if (deqAllow >= 0)
359
        {
360
          // recursing on disequal points
361
          deqAllow--;
362
          // if there is use continuing
363
          if (deqAllow >= 0 || eqAllow >= 0)
364
          {
365
            for (std::pair<const Node, LazyTrie>& ltc : lt->d_children)
366
            {
367
              if (ltc.first != e_this)
368
              {
369
                visitTr.push_back(&ltc.second);
370
                currIndex.push_back(index + 1);
371
                currExact.push_back(false);
372
                pushIndex[0].push_back(false);
373
                pushIndex[1].push_back(true);
374
                preVisit.push_back(true);
375
              }
376
            }
377
          }
378
          deqAllow++;
379
        }
380
        bool pushEqNext = false;
381
        if (eqAllow >= 0)
382
        {
383
          // below, we try recursing (if at all) on equal nodes.
384
          eqAllow--;
385
          pushEqNext = true;
386
        }
387
        // if we are on the exact path of n
388
        if (exact)
389
        {
390
          if (lt->d_children.empty())
391
          {
392
            // if no one has been here before, we are done
393
            lt->d_lazy_child = n;
394
          }
395
          else
396
          {
397
            // otherwise, we recurse on the equal point
398
            visitTr.push_back(&(lt->d_children[e_this]));
399
            currIndex.push_back(index + 1);
400
            currExact.push_back(true);
401
            pushIndex[0].push_back(pushEqNext);
402
            pushIndex[1].push_back(false);
403
            preVisit.push_back(true);
404
          }
405
        }
406
        else if (deqAllow >= 0 || eqAllow >= 0)
407
        {
408
          // recurse on the equal point if it exists
409
          std::map<Node, LazyTrie>::iterator iteq = lt->d_children.find(e_this);
410
          if (iteq != lt->d_children.end())
411
          {
412
            visitTr.push_back(&(iteq->second));
413
            currIndex.push_back(index + 1);
414
            currExact.push_back(false);
415
            pushIndex[0].push_back(pushEqNext);
416
            pushIndex[1].push_back(false);
417
            preVisit.push_back(true);
418
          }
419
        }
420
      }
421
    }
422
  } while (!visitTr.empty());
423
}
424
425
}  // namespace quantifiers
426
}  // namespace theory
427
22746
}  // namespace cvc5