GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/arith/nl/icp/icp_solver.cpp Lines: 171 203 84.2 %
Date: 2021-11-07 Branches: 268 613 43.7 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Gereon Kremer, 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
 * Implements a ICP-based solver for nonlinear arithmetic.
14
 */
15
16
#include "theory/arith/nl/icp/icp_solver.h"
17
18
#include <iostream>
19
20
#include "base/check.h"
21
#include "base/output.h"
22
#include "expr/node_algorithm.h"
23
#include "theory/arith/arith_msum.h"
24
#include "theory/arith/inference_manager.h"
25
#include "theory/arith/nl/poly_conversion.h"
26
#include "theory/arith/normal_form.h"
27
#include "theory/rewriter.h"
28
#include "util/poly_util.h"
29
30
namespace cvc5 {
31
namespace theory {
32
namespace arith {
33
namespace nl {
34
namespace icp {
35
36
#ifdef CVC5_POLY_IMP
37
38
namespace {
39
/** A simple wrapper to nicely print an interval assignment. */
40
struct IAWrapper
41
{
42
  const poly::IntervalAssignment& ia;
43
  const VariableMapper& vm;
44
};
45
inline std::ostream& operator<<(std::ostream& os, const IAWrapper& iaw)
46
{
47
  os << "{ ";
48
  bool first = true;
49
  for (const auto& v : iaw.vm.mVarpolyCVC)
50
  {
51
    if (iaw.ia.has(v.first))
52
    {
53
      if (first)
54
      {
55
        first = false;
56
      }
57
      else
58
      {
59
        os << ", ";
60
      }
61
      os << v.first << " -> " << iaw.ia.get(v.first);
62
    }
63
  }
64
  return os << " }";
65
}
66
}  // namespace
67
68
9696
ICPSolver::ICPSolver(Env& env, InferenceManager& im)
69
9696
    : EnvObj(env), d_im(im), d_state(d_mapper)
70
{
71
9696
}
72
73
30
std::vector<Node> ICPSolver::collectVariables(const Node& n) const
74
{
75
60
  std::unordered_set<TNode> tmp;
76
30
  expr::getVariables(n, tmp);
77
30
  std::vector<Node> res;
78
62
  for (const auto& t : tmp)
79
  {
80
32
    res.emplace_back(t);
81
  }
82
60
  return res;
83
}
84
85
92
std::vector<Candidate> ICPSolver::constructCandidates(const Node& n)
86
{
87
184
  Node tmp = rewrite(n);
88
92
  if (tmp.isConst())
89
  {
90
    return {};
91
  }
92
184
  auto comp = Comparison::parseNormalForm(tmp).decompose(false);
93
92
  Kind k = std::get<1>(comp);
94
92
  if (k == Kind::DISTINCT)
95
  {
96
54
    return {};
97
  }
98
76
  auto poly = std::get<0>(comp);
99
100
76
  std::vector<Candidate> result;
101
76
  std::unordered_set<TNode> vars;
102
38
  expr::getVariables(n, vars);
103
104
  for (const auto& v : vars)
104
  {
105
66
    Trace("nl-icp") << "\tChecking " << n << " for " << v << std::endl;
106
107
132
    std::map<Node, Node> msum;
108
66
    ArithMSum::getMonomialSum(poly.getNode(), msum);
109
110
132
    Node veq_c;
111
132
    Node val;
112
113
66
    int isolated = ArithMSum::isolate(v, msum, veq_c, val, k);
114
66
    if (isolated == 1)
115
    {
116
20
      poly::Variable lhs = d_mapper(v);
117
20
      poly::SignCondition rel = poly::SignCondition::EQ;
118
20
      switch (k)
119
      {
120
6
        case Kind::LT: rel = poly::SignCondition::LT; break;
121
        case Kind::LEQ: rel = poly::SignCondition::LE; break;
122
14
        case Kind::EQUAL: rel = poly::SignCondition::EQ; break;
123
        case Kind::DISTINCT: rel = poly::SignCondition::NE; break;
124
        case Kind::GT: rel = poly::SignCondition::GT; break;
125
        case Kind::GEQ: rel = poly::SignCondition::GE; break;
126
        default: Assert(false) << "Unexpected kind: " << k;
127
      }
128
40
      poly::Rational rhsmult;
129
40
      poly::Polynomial rhs = as_poly_polynomial(val, d_mapper, rhsmult);
130
20
      rhsmult = poly::Rational(1) / rhsmult;
131
      // only correct up to a constant (denominator is thrown away!)
132
20
      if (!veq_c.isNull())
133
      {
134
        rhsmult = poly_utils::toRational(veq_c.getConst<Rational>());
135
      }
136
40
      Candidate res{lhs, rel, rhs, rhsmult, n, collectVariables(val)};
137
20
      Trace("nl-icp") << "\tAdded " << res << " from " << n << std::endl;
138
20
      result.emplace_back(res);
139
    }
140
46
    else if (isolated == -1)
141
    {
142
10
      poly::Variable lhs = d_mapper(v);
143
10
      poly::SignCondition rel = poly::SignCondition::EQ;
144
10
      switch (k)
145
      {
146
10
        case Kind::LT: rel = poly::SignCondition::GT; break;
147
        case Kind::LEQ: rel = poly::SignCondition::GE; break;
148
        case Kind::EQUAL: rel = poly::SignCondition::EQ; break;
149
        case Kind::DISTINCT: rel = poly::SignCondition::NE; break;
150
        case Kind::GT: rel = poly::SignCondition::LT; break;
151
        case Kind::GEQ: rel = poly::SignCondition::LE; break;
152
        default: Assert(false) << "Unexpected kind: " << k;
153
      }
154
20
      poly::Rational rhsmult;
155
20
      poly::Polynomial rhs = as_poly_polynomial(val, d_mapper, rhsmult);
156
10
      rhsmult = poly::Rational(1) / rhsmult;
157
10
      if (!veq_c.isNull())
158
      {
159
        rhsmult = poly_utils::toRational(veq_c.getConst<Rational>());
160
      }
161
20
      Candidate res{lhs, rel, rhs, rhsmult, n, collectVariables(val)};
162
10
      Trace("nl-icp") << "\tAdded " << res << " from " << n << std::endl;
163
10
      result.emplace_back(res);
164
    }
165
  }
166
38
  return result;
167
}
168
169
848
void ICPSolver::addCandidate(const Node& n)
170
{
171
848
  auto it = d_candidateCache.find(n);
172
848
  if (it != d_candidateCache.end())
173
  {
174
884
    for (const auto& c : it->second)
175
    {
176
128
      d_state.d_candidates.emplace_back(c);
177
    }
178
  }
179
  else
180
  {
181
184
    auto cands = constructCandidates(n);
182
92
    d_candidateCache.emplace(n, cands);
183
122
    for (const auto& c : cands)
184
    {
185
30
      d_state.d_candidates.emplace_back(c);
186
60
      Trace("nl-icp") << "Bumping budget because of the new candidate"
187
30
                      << std::endl;
188
30
      d_budget += d_budgetIncrement;
189
    }
190
  }
191
848
}
192
193
48
void ICPSolver::initOrigins()
194
{
195
140
  for (const auto& vars : d_state.d_bounds.get())
196
  {
197
92
    const Bounds& i = vars.second;
198
184
    Trace("nl-icp") << "Adding initial " << vars.first << " -> " << i
199
92
                    << std::endl;
200
92
    if (!i.lower_origin.isNull())
201
    {
202
80
      Trace("nl-icp") << "\tAdding lower " << i.lower_origin << std::endl;
203
80
      d_state.d_origins.add(vars.first, i.lower_origin, {});
204
    }
205
92
    if (!i.upper_origin.isNull())
206
    {
207
72
      Trace("nl-icp") << "\tAdding upper " << i.upper_origin << std::endl;
208
72
      d_state.d_origins.add(vars.first, i.upper_origin, {});
209
    }
210
  }
211
48
}
212
213
52
PropagationResult ICPSolver::doPropagationRound()
214
{
215
52
  if (d_budget <= 0)
216
  {
217
    Trace("nl-icp") << "ICP budget exceeded" << std::endl;
218
    return PropagationResult::NOT_CHANGED;
219
  }
220
52
  d_state.d_conflict.clear();
221
104
  Trace("nl-icp") << "Starting propagation with "
222
52
                  << IAWrapper{d_state.d_assignment, d_mapper} << std::endl;
223
52
  Trace("nl-icp") << "Current budget: " << d_budget << std::endl;
224
52
  PropagationResult res = PropagationResult::NOT_CHANGED;
225
216
  for (const auto& c : d_state.d_candidates)
226
  {
227
166
    --d_budget;
228
166
    PropagationResult cres = c.propagate(d_state.d_assignment, 100);
229
166
    switch (cres)
230
    {
231
160
      case PropagationResult::NOT_CHANGED: break;
232
      case PropagationResult::CONTRACTED:
233
      case PropagationResult::CONTRACTED_STRONGLY:
234
        d_state.d_origins.add(d_mapper(c.lhs), c.origin, c.rhsVariables);
235
        res = PropagationResult::CONTRACTED;
236
        break;
237
4
      case PropagationResult::CONTRACTED_WITHOUT_CURRENT:
238
      case PropagationResult::CONTRACTED_STRONGLY_WITHOUT_CURRENT:
239
4
        d_state.d_origins.add(d_mapper(c.lhs), c.origin, c.rhsVariables, false);
240
4
        res = PropagationResult::CONTRACTED;
241
4
        break;
242
2
      case PropagationResult::CONFLICT:
243
2
        d_state.d_origins.add(d_mapper(c.lhs), c.origin, c.rhsVariables);
244
2
        d_state.d_conflict = d_state.d_origins.getOrigins(d_mapper(c.lhs));
245
2
        return PropagationResult::CONFLICT;
246
    }
247
164
    switch (cres)
248
    {
249
4
      case PropagationResult::CONTRACTED_STRONGLY:
250
      case PropagationResult::CONTRACTED_STRONGLY_WITHOUT_CURRENT:
251
8
        Trace("nl-icp") << "Bumping budget because of a strong contraction"
252
4
                        << std::endl;
253
4
        d_budget += d_budgetIncrement;
254
4
        break;
255
160
      default: break;
256
    }
257
  }
258
50
  return res;
259
}
260
261
6
std::vector<Node> ICPSolver::generateLemmas() const
262
{
263
6
  auto nm = NodeManager::currentNM();
264
6
  std::vector<Node> lemmas;
265
266
18
  for (const auto& vars : d_mapper.mVarCVCpoly)
267
  {
268
12
    if (!d_state.d_assignment.has(vars.second)) continue;
269
24
    Node v = vars.first;
270
24
    poly::Interval i = d_state.d_assignment.get(vars.second);
271
12
    if (!is_minus_infinity(get_lower(i)))
272
    {
273
12
      Kind rel = get_lower_open(i) ? Kind::GT : Kind::GEQ;
274
24
      Node c = nm->mkNode(rel, v, value_to_node(get_lower(i), v));
275
12
      if (!d_state.d_origins.isInOrigins(v, c))
276
      {
277
24
        Node premise = nm->mkAnd(d_state.d_origins.getOrigins(v));
278
12
        Trace("nl-icp") << premise << " => " << c << std::endl;
279
24
        Node lemma = rewrite(nm->mkNode(Kind::IMPLIES, premise, c));
280
12
        if (lemma.isConst())
281
        {
282
          Assert(lemma == nm->mkConst<bool>(true));
283
        }
284
        else
285
        {
286
12
          Trace("nl-icp") << "Adding lemma " << lemma << std::endl;
287
12
          lemmas.emplace_back(lemma);
288
        }
289
      }
290
    }
291
12
    if (!is_plus_infinity(get_upper(i)))
292
    {
293
10
      Kind rel = get_upper_open(i) ? Kind::LT : Kind::LEQ;
294
20
      Node c = nm->mkNode(rel, v, value_to_node(get_upper(i), v));
295
10
      if (!d_state.d_origins.isInOrigins(v, c))
296
      {
297
20
        Node premise = nm->mkAnd(d_state.d_origins.getOrigins(v));
298
10
        Trace("nl-icp") << premise << " => " << c << std::endl;
299
20
        Node lemma = rewrite(nm->mkNode(Kind::IMPLIES, premise, c));
300
10
        if (lemma.isConst())
301
        {
302
          Assert(lemma == nm->mkConst<bool>(true));
303
        }
304
        else
305
        {
306
10
          Trace("nl-icp") << "Adding lemma " << lemma << std::endl;
307
10
          lemmas.emplace_back(lemma);
308
        }
309
      }
310
    }
311
  }
312
6
  return lemmas;
313
}
314
315
48
void ICPSolver::reset(const std::vector<Node>& assertions)
316
{
317
48
  d_state.reset();
318
1038
  for (const auto& n : assertions)
319
  {
320
990
    Trace("nl-icp") << "Adding " << n << std::endl;
321
990
    if (n.getKind() != Kind::CONST_BOOLEAN)
322
    {
323
990
      if (!d_state.d_bounds.add(n))
324
      {
325
848
        addCandidate(n);
326
      }
327
    }
328
  }
329
48
}
330
331
48
void ICPSolver::check()
332
{
333
48
  initOrigins();
334
48
  d_state.d_assignment = getBounds(d_mapper, d_state.d_bounds);
335
48
  bool did_progress = false;
336
48
  bool progress = false;
337
52
  do
338
  {
339
52
    switch (doPropagationRound())
340
    {
341
46
      case icp::PropagationResult::NOT_CHANGED: progress = false; break;
342
4
      case icp::PropagationResult::CONTRACTED:
343
      case icp::PropagationResult::CONTRACTED_STRONGLY:
344
      case icp::PropagationResult::CONTRACTED_WITHOUT_CURRENT:
345
      case icp::PropagationResult::CONTRACTED_STRONGLY_WITHOUT_CURRENT:
346
4
        did_progress = true;
347
4
        progress = true;
348
4
        break;
349
2
      case icp::PropagationResult::CONFLICT:
350
4
        Trace("nl-icp") << "Found a conflict: " << d_state.d_conflict
351
2
                        << std::endl;
352
353
4
        std::vector<Node> mis;
354
8
        for (const auto& n : d_state.d_conflict)
355
        {
356
6
          mis.emplace_back(n.negate());
357
        }
358
2
        d_im.addPendingLemma(NodeManager::currentNM()->mkOr(mis),
359
                             InferenceId::ARITH_NL_ICP_CONFLICT);
360
2
        did_progress = true;
361
2
        progress = false;
362
2
        break;
363
    }
364
  } while (progress);
365
48
  if (did_progress)
366
  {
367
12
    std::vector<Node> lemmas = generateLemmas();
368
28
    for (const auto& l : lemmas)
369
    {
370
22
      d_im.addPendingLemma(l, InferenceId::ARITH_NL_ICP_PROPAGATION);
371
    }
372
  }
373
48
}
374
375
#else /* CVC5_POLY_IMP */
376
377
void ICPSolver::reset(const std::vector<Node>& assertions)
378
{
379
  Unimplemented() << "ICPSolver requires cvc5 to be configured with LibPoly";
380
}
381
382
void ICPSolver::check()
383
{
384
  Unimplemented() << "ICPSolver requires cvc5 to be configured with LibPoly";
385
}
386
387
#endif /* CVC5_POLY_IMP */
388
389
}  // namespace icp
390
}  // namespace nl
391
}  // namespace arith
392
}  // namespace theory
393
31137
}  // namespace cvc5