GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/fp/theory_fp.cpp Lines: 424 449 94.4 %
Date: 2021-11-07 Branches: 977 2468 39.6 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Martin Brain, Andrew Reynolds, 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
 * Theory of floating-point arithmetic.
14
 */
15
16
#include "theory/fp/theory_fp.h"
17
18
#include <set>
19
#include <stack>
20
#include <unordered_set>
21
#include <vector>
22
23
#include "base/configuration.h"
24
#include "expr/skolem_manager.h"
25
#include "options/fp_options.h"
26
#include "smt/logic_exception.h"
27
#include "theory/fp/fp_word_blaster.h"
28
#include "theory/fp/theory_fp_rewriter.h"
29
#include "theory/output_channel.h"
30
#include "theory/theory_model.h"
31
#include "util/floatingpoint.h"
32
33
using namespace std;
34
35
namespace cvc5 {
36
namespace theory {
37
namespace fp {
38
39
namespace helper {
40
14
Node buildConjunct(const std::vector<TNode> &assumptions) {
41
14
  if (assumptions.size() == 0) {
42
    return NodeManager::currentNM()->mkConst<bool>(true);
43
44
14
  } else if (assumptions.size() == 1) {
45
    return assumptions[0];
46
47
  } else {
48
    // \todo see bv::utils::flattenAnd
49
50
28
    NodeBuilder conjunction(kind::AND);
51
48
    for (std::vector<TNode>::const_iterator it = assumptions.begin();
52
48
         it != assumptions.end(); ++it) {
53
34
      conjunction << *it;
54
    }
55
56
14
    return conjunction;
57
  }
58
}
59
}  // namespace helper
60
61
/** Constructs a new instance of TheoryFp w.r.t. the provided contexts. */
62
15273
TheoryFp::TheoryFp(Env& env, OutputChannel& out, Valuation valuation)
63
    : Theory(THEORY_FP, env, out, valuation),
64
      d_notification(*this),
65
15273
      d_registeredTerms(userContext()),
66
15273
      d_wordBlaster(new FpWordBlaster(userContext())),
67
      d_expansionRequested(false),
68
15273
      d_abstractionMap(userContext()),
69
      d_rewriter(userContext()),
70
      d_state(env, valuation),
71
      d_im(env, *this, d_state, "theory::fp::", true),
72
15273
      d_wbFactsCache(userContext()),
73
76365
      d_true(d_env.getNodeManager()->mkConst(true))
74
{
75
  // indicate we are using the default theory state and inference manager
76
15273
  d_theoryState = &d_state;
77
15273
  d_inferManager = &d_im;
78
15273
}
79
80
15273
TheoryRewriter* TheoryFp::getTheoryRewriter() { return &d_rewriter; }
81
82
7989
ProofRuleChecker* TheoryFp::getProofChecker() { return nullptr; }
83
84
15273
bool TheoryFp::needsEqualityEngine(EeSetupInfo& esi)
85
{
86
15273
  esi.d_notify = &d_notification;
87
15273
  esi.d_name = "theory::fp::ee";
88
15273
  return true;
89
}
90
91
15273
void TheoryFp::finishInit()
92
{
93
15273
  Assert(d_equalityEngine != nullptr);
94
  // Kinds that are to be handled in the congruence closure
95
96
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ABS);
97
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_NEG);
98
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ADD);
99
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_SUB); // Removed
100
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_MULT);
101
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_DIV);
102
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_FMA);
103
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_SQRT);
104
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_REM);
105
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_RTI);
106
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_MIN); // Removed
107
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_MAX); // Removed
108
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_MIN_TOTAL);
109
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_MAX_TOTAL);
110
111
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_EQ); // Removed
112
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_LEQ);
113
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_LT);
114
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_GEQ); // Removed
115
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_GT); // Removed
116
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ISN);
117
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ISSN);
118
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ISZ);
119
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ISINF);
120
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ISNAN);
121
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ISNEG);
122
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_ISPOS);
123
124
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_FP_IEEE_BITVECTOR);
125
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_FP_FLOATINGPOINT);
126
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_FP_REAL);
127
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_FP_SIGNED_BITVECTOR);
128
15273
  d_equalityEngine->addFunctionKind(
129
      kind::FLOATINGPOINT_TO_FP_UNSIGNED_BITVECTOR);
130
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_FP_GENERIC); //
131
  // Needed in parsing, should be rewritten away
132
133
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_UBV); // Removed
134
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_SBV); // Removed
135
  // d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_REAL); // Removed
136
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_UBV_TOTAL);
137
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_SBV_TOTAL);
138
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_TO_REAL_TOTAL);
139
140
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_COMPONENT_NAN);
141
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_COMPONENT_INF);
142
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_COMPONENT_ZERO);
143
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_COMPONENT_SIGN);
144
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_COMPONENT_EXPONENT);
145
15273
  d_equalityEngine->addFunctionKind(kind::FLOATINGPOINT_COMPONENT_SIGNIFICAND);
146
15273
  d_equalityEngine->addFunctionKind(kind::ROUNDINGMODE_BITBLAST);
147
15273
}
148
149
2393
TrustNode TheoryFp::ppRewrite(TNode node, std::vector<SkolemLemma>& lems)
150
{
151
2393
  Trace("fp-ppRewrite") << "TheoryFp::ppRewrite(): " << node << std::endl;
152
  // first, see if we need to expand definitions
153
4786
  TrustNode texp = d_rewriter.expandDefinition(node);
154
2393
  if (!texp.isNull())
155
  {
156
12
    return texp;
157
  }
158
159
4762
  Node res = node;
160
161
162
2381
  if (res != node)
163
  {
164
    Trace("fp-ppRewrite") << "TheoryFp::ppRewrite(): node " << node
165
                          << " rewritten to " << res << std::endl;
166
    return TrustNode::mkTrustRewrite(node, res, nullptr);
167
  }
168
169
2381
  return TrustNode::null();
170
}
171
172
51
bool TheoryFp::refineAbstraction(TheoryModel *m, TNode abstract, TNode concrete)
173
{
174
102
  Trace("fp-refineAbstraction") << "TheoryFp::refineAbstraction(): " << abstract
175
51
                                << " vs. " << concrete << std::endl;
176
51
  Kind k = concrete.getKind();
177
51
  if (k == kind::FLOATINGPOINT_TO_REAL_TOTAL)
178
  {
179
    // Get the values
180
12
    Assert(m->hasTerm(abstract));
181
12
    Assert(m->hasTerm(concrete[0]));
182
12
    Assert(m->hasTerm(concrete[1]));
183
184
24
    Node abstractValue = m->getValue(abstract);
185
24
    Node floatValue = m->getValue(concrete[0]);
186
24
    Node undefValue = m->getValue(concrete[1]);
187
188
12
    Assert(!abstractValue.isNull());
189
12
    Assert(!floatValue.isNull());
190
12
    Assert(!undefValue.isNull());
191
12
    Assert(abstractValue.isConst());
192
12
    Assert(floatValue.isConst());
193
12
    Assert(undefValue.isConst());
194
195
    // Work out the actual value for those args
196
12
    NodeManager *nm = NodeManager::currentNM();
197
198
    Node evaluate =
199
24
        nm->mkNode(kind::FLOATINGPOINT_TO_REAL_TOTAL, floatValue, undefValue);
200
24
    Node concreteValue = rewrite(evaluate);
201
12
    Assert(concreteValue.isConst());
202
203
24
    Trace("fp-refineAbstraction")
204
24
        << "TheoryFp::refineAbstraction(): " << concrete[0] << " = "
205
12
        << floatValue << std::endl
206
24
        << "TheoryFp::refineAbstraction(): " << concrete[1] << " = "
207
12
        << undefValue << std::endl
208
12
        << "TheoryFp::refineAbstraction(): " << abstract << " = "
209
12
        << abstractValue << std::endl
210
12
        << "TheoryFp::refineAbstraction(): " << concrete << " = "
211
12
        << concreteValue << std::endl;
212
213
12
    if (abstractValue != concreteValue)
214
    {
215
      // Need refinement lemmas
216
      // only in the normal and subnormal case
217
2
      Assert(floatValue.getConst<FloatingPoint>().isNormal()
218
             || floatValue.getConst<FloatingPoint>().isSubnormal());
219
220
      Node defined = nm->mkNode(
221
          kind::AND,
222
4
          nm->mkNode(kind::NOT,
223
4
                     nm->mkNode(kind::FLOATINGPOINT_ISNAN, concrete[0])),
224
4
          nm->mkNode(kind::NOT,
225
12
                     nm->mkNode(kind::FLOATINGPOINT_ISINF, concrete[0])));
226
      // First the "forward" constraints
227
      Node fg = nm->mkNode(
228
          kind::IMPLIES,
229
          defined,
230
8
          nm->mkNode(
231
              kind::EQUAL,
232
4
              nm->mkNode(kind::FLOATINGPOINT_GEQ, concrete[0], floatValue),
233
8
              nm->mkNode(kind::GEQ, abstract, concreteValue)));
234
2
      handleLemma(fg, InferenceId::FP_PREPROCESS);
235
236
      Node fl = nm->mkNode(
237
          kind::IMPLIES,
238
          defined,
239
8
          nm->mkNode(
240
              kind::EQUAL,
241
4
              nm->mkNode(kind::FLOATINGPOINT_LEQ, concrete[0], floatValue),
242
8
              nm->mkNode(kind::LEQ, abstract, concreteValue)));
243
2
      handleLemma(fl, InferenceId::FP_PREPROCESS);
244
245
      // Then the backwards constraints
246
      Node floatAboveAbstract = rewrite(
247
12
          nm->mkNode(kind::FLOATINGPOINT_TO_FP_REAL,
248
4
                     nm->mkConst(FloatingPointToFPReal(
249
4
                         concrete[0].getType().getConst<FloatingPointSize>())),
250
4
                     nm->mkConst(RoundingMode::ROUND_TOWARD_POSITIVE),
251
6
                     abstractValue));
252
253
      Node bg = nm->mkNode(
254
          kind::IMPLIES,
255
          defined,
256
8
          nm->mkNode(
257
              kind::EQUAL,
258
4
              nm->mkNode(
259
                  kind::FLOATINGPOINT_GEQ, concrete[0], floatAboveAbstract),
260
8
              nm->mkNode(kind::GEQ, abstract, abstractValue)));
261
2
      handleLemma(bg, InferenceId::FP_PREPROCESS);
262
263
      Node floatBelowAbstract = rewrite(
264
12
          nm->mkNode(kind::FLOATINGPOINT_TO_FP_REAL,
265
4
                     nm->mkConst(FloatingPointToFPReal(
266
4
                         concrete[0].getType().getConst<FloatingPointSize>())),
267
4
                     nm->mkConst(RoundingMode::ROUND_TOWARD_NEGATIVE),
268
6
                     abstractValue));
269
270
      Node bl = nm->mkNode(
271
          kind::IMPLIES,
272
          defined,
273
8
          nm->mkNode(
274
              kind::EQUAL,
275
4
              nm->mkNode(
276
                  kind::FLOATINGPOINT_LEQ, concrete[0], floatBelowAbstract),
277
8
              nm->mkNode(kind::LEQ, abstract, abstractValue)));
278
2
      handleLemma(bl, InferenceId::FP_PREPROCESS);
279
      // TODO : see if the overflow conditions could be improved #1914
280
281
2
      return true;
282
    }
283
    else
284
    {
285
      // No refinement needed
286
10
      return false;
287
    }
288
  }
289
39
  else if (k == kind::FLOATINGPOINT_TO_FP_REAL)
290
  {
291
    // Get the values
292
39
    Assert(m->hasTerm(abstract)) << "Term " << abstract << " not in model";
293
78
    Assert(m->hasTerm(concrete[0]))
294
39
        << "Term " << concrete[0] << " not in model";
295
    // Note: while the value for concrete[1] that we get from the model has to
296
    // be const, it is not necessarily the case that `m->hasTerm(concrete[1])`.
297
    // The arithmetic solver computes values for the variables in shared terms
298
    // but does not necessarily add the shared terms themselves.
299
300
78
    Node abstractValue = m->getValue(abstract);
301
78
    Node rmValue = m->getValue(concrete[0]);
302
78
    Node realValue = m->getValue(concrete[1]);
303
304
39
    Assert(!abstractValue.isNull());
305
39
    Assert(!rmValue.isNull());
306
39
    Assert(!realValue.isNull());
307
39
    Assert(abstractValue.isConst());
308
39
    Assert(rmValue.isConst());
309
39
    Assert(realValue.isConst());
310
311
    // Work out the actual value for those args
312
39
    NodeManager *nm = NodeManager::currentNM();
313
314
    Node evaluate =
315
        nm->mkNode(kind::FLOATINGPOINT_TO_FP_REAL,
316
78
                   nm->mkConst(FloatingPointToFPReal(
317
78
                       concrete.getType().getConst<FloatingPointSize>())),
318
                   rmValue,
319
156
                   realValue);
320
78
    Node concreteValue = rewrite(evaluate);
321
39
    Assert(concreteValue.isConst());
322
323
78
    Trace("fp-refineAbstraction")
324
78
        << "TheoryFp::refineAbstraction(): " << concrete[0] << " = " << rmValue
325
39
        << std::endl
326
78
        << "TheoryFp::refineAbstraction(): " << concrete[1] << " = "
327
39
        << realValue << std::endl
328
39
        << "TheoryFp::refineAbstraction(): " << abstract << " = "
329
39
        << abstractValue << std::endl
330
39
        << "TheoryFp::refineAbstraction(): " << concrete << " = "
331
39
        << concreteValue << std::endl;
332
333
39
    if (abstractValue != concreteValue)
334
    {
335
31
      Assert(!abstractValue.getConst<FloatingPoint>().isNaN());
336
31
      Assert(!concreteValue.getConst<FloatingPoint>().isNaN());
337
338
62
      Node correctRoundingMode = nm->mkNode(kind::EQUAL, concrete[0], rmValue);
339
      // TODO : Generalise to all rounding modes  #1914
340
341
      // First the "forward" constraints
342
      Node fg = nm->mkNode(
343
          kind::IMPLIES,
344
          correctRoundingMode,
345
124
          nm->mkNode(
346
              kind::EQUAL,
347
62
              nm->mkNode(kind::GEQ, concrete[1], realValue),
348
124
              nm->mkNode(kind::FLOATINGPOINT_GEQ, abstract, concreteValue)));
349
31
      handleLemma(fg, InferenceId::FP_PREPROCESS);
350
351
      Node fl = nm->mkNode(
352
          kind::IMPLIES,
353
          correctRoundingMode,
354
124
          nm->mkNode(
355
              kind::EQUAL,
356
62
              nm->mkNode(kind::LEQ, concrete[1], realValue),
357
124
              nm->mkNode(kind::FLOATINGPOINT_LEQ, abstract, concreteValue)));
358
31
      handleLemma(fl, InferenceId::FP_PREPROCESS);
359
360
      // Then the backwards constraints
361
31
      if (!abstractValue.getConst<FloatingPoint>().isInfinite())
362
      {
363
        Node realValueOfAbstract =
364
62
            rewrite(nm->mkNode(kind::FLOATINGPOINT_TO_REAL_TOTAL,
365
                               abstractValue,
366
124
                               nm->mkConst(Rational(0U))));
367
368
        Node bg = nm->mkNode(
369
            kind::IMPLIES,
370
            correctRoundingMode,
371
124
            nm->mkNode(
372
                kind::EQUAL,
373
62
                nm->mkNode(kind::GEQ, concrete[1], realValueOfAbstract),
374
124
                nm->mkNode(kind::FLOATINGPOINT_GEQ, abstract, abstractValue)));
375
31
        handleLemma(bg, InferenceId::FP_PREPROCESS);
376
377
        Node bl = nm->mkNode(
378
            kind::IMPLIES,
379
            correctRoundingMode,
380
124
            nm->mkNode(
381
                kind::EQUAL,
382
62
                nm->mkNode(kind::LEQ, concrete[1], realValueOfAbstract),
383
124
                nm->mkNode(kind::FLOATINGPOINT_LEQ, abstract, abstractValue)));
384
31
        handleLemma(bl, InferenceId::FP_PREPROCESS);
385
      }
386
387
31
      return true;
388
    }
389
    else
390
    {
391
      // No refinement needed
392
8
      return false;
393
    }
394
  }
395
  else
396
  {
397
    Unreachable() << "Unknown abstraction";
398
  }
399
400
  return false;
401
}
402
403
3080
void TheoryFp::wordBlastAndEquateTerm(TNode node)
404
{
405
6160
  Trace("fp-wordBlastTerm")
406
3080
      << "TheoryFp::wordBlastTerm(): " << node << std::endl;
407
408
3080
  size_t oldSize = d_wordBlaster->d_additionalAssertions.size();
409
410
6160
  Node wordBlasted(d_wordBlaster->wordBlast(node));
411
412
3080
  size_t newSize = d_wordBlaster->d_additionalAssertions.size();
413
414
3080
  if (wordBlasted != node)
415
  {
416
1628
    Debug("fp-wordBlastTerm")
417
814
        << "TheoryFp::wordBlastTerm(): before " << node << std::endl;
418
1628
    Debug("fp-wordBlastTerm")
419
814
        << "TheoryFp::wordBlastTerm(): after  " << wordBlasted << std::endl;
420
  }
421
422
3080
  Assert(oldSize <= newSize);
423
424
3468
  while (oldSize < newSize)
425
  {
426
388
    Node addA = d_wordBlaster->d_additionalAssertions[oldSize];
427
428
388
    Debug("fp-wordBlastTerm")
429
194
        << "TheoryFp::wordBlastTerm(): additional assertion  " << addA
430
194
        << std::endl;
431
432
194
    NodeManager* nm = NodeManager::currentNM();
433
434
194
    handleLemma(
435
388
        nm->mkNode(kind::EQUAL, addA, nm->mkConst(::cvc5::BitVector(1U, 1U))),
436
        InferenceId::FP_EQUATE_TERM);
437
438
194
    ++oldSize;
439
  }
440
441
  // Equate the floating-point atom and the wordBlasted one.
442
  // Also adds the bit-vectors to the bit-vector solver.
443
3080
  if (node.getType().isBoolean())
444
  {
445
1275
    if (wordBlasted != node)
446
    {
447
810
      Assert(wordBlasted.getType().isBitVector());
448
449
810
      NodeManager* nm = NodeManager::currentNM();
450
451
810
      handleLemma(
452
1620
          nm->mkNode(kind::EQUAL,
453
                     node,
454
1620
                     nm->mkNode(kind::EQUAL,
455
                                wordBlasted,
456
1620
                                nm->mkConst(::cvc5::BitVector(1U, 1U)))),
457
          InferenceId::FP_EQUATE_TERM);
458
    }
459
    else
460
    {
461
465
      Assert((node.getKind() == kind::EQUAL));
462
    }
463
  }
464
1805
  else if (node.getType().isBitVector())
465
  {
466
1097
    if (wordBlasted != node)
467
    {
468
4
      Assert(wordBlasted.getType().isBitVector());
469
470
4
      handleLemma(
471
8
          NodeManager::currentNM()->mkNode(kind::EQUAL, node, wordBlasted),
472
          InferenceId::FP_EQUATE_TERM);
473
    }
474
  }
475
476
6160
  return;
477
}
478
479
10334
void TheoryFp::registerTerm(TNode node)
480
{
481
10334
  Trace("fp-registerTerm") << "TheoryFp::registerTerm(): " << node << std::endl;
482
483
10334
  Kind k = node.getKind();
484
10334
  Assert(k != kind::FLOATINGPOINT_TO_FP_GENERIC && k != kind::FLOATINGPOINT_SUB
485
         && k != kind::FLOATINGPOINT_EQ && k != kind::FLOATINGPOINT_GEQ
486
         && k != kind::FLOATINGPOINT_GT);
487
488
  // Add to the equality engine, always. This is required to ensure
489
  // getEqualityStatus works as expected when theory combination is enabled.
490
10334
  if (k == kind::EQUAL)
491
  {
492
7102
    d_equalityEngine->addTriggerPredicate(node);
493
  }
494
  else
495
  {
496
3232
    d_equalityEngine->addTerm(node);
497
  }
498
499
  // if not registered in this user context
500
10334
  if (isRegistered(node))
501
  {
502
14469
    return;
503
  }
504
505
3107
  CVC5_UNUSED bool success = d_registeredTerms.insert(node);
506
3107
  Assert(success);
507
508
  // Give the expansion of classifications in terms of equalities
509
  // This should make equality reasoning slightly more powerful.
510
3107
  if ((k == kind::FLOATINGPOINT_ISNAN) || (k == kind::FLOATINGPOINT_ISZ)
511
3064
      || (k == kind::FLOATINGPOINT_ISINF))
512
  {
513
102
    NodeManager* nm = NodeManager::currentNM();
514
102
    FloatingPointSize s = node[0].getType().getConst<FloatingPointSize>();
515
204
    Node equalityAlias = Node::null();
516
517
102
    if (k == kind::FLOATINGPOINT_ISNAN)
518
    {
519
34
      equalityAlias = nm->mkNode(
520
68
          kind::EQUAL, node[0], nm->mkConst(FloatingPoint::makeNaN(s)));
521
    }
522
68
    else if (k == kind::FLOATINGPOINT_ISZ)
523
    {
524
27
      equalityAlias = nm->mkNode(
525
          kind::OR,
526
18
          nm->mkNode(kind::EQUAL,
527
                     node[0],
528
18
                     nm->mkConst(FloatingPoint::makeZero(s, true))),
529
18
          nm->mkNode(kind::EQUAL,
530
                     node[0],
531
18
                     nm->mkConst(FloatingPoint::makeZero(s, false))));
532
    }
533
59
    else if (k == kind::FLOATINGPOINT_ISINF)
534
    {
535
59
      equalityAlias =
536
236
          nm->mkNode(kind::OR,
537
118
                     nm->mkNode(kind::EQUAL,
538
                                node[0],
539
118
                                nm->mkConst(FloatingPoint::makeInf(s, true))),
540
118
                     nm->mkNode(kind::EQUAL,
541
                                node[0],
542
118
                                nm->mkConst(FloatingPoint::makeInf(s, false))));
543
    }
544
    else
545
    {
546
      Unreachable() << "Only isNaN, isInf and isZero have aliases";
547
    }
548
549
102
    handleLemma(nm->mkNode(kind::EQUAL, node, equalityAlias),
550
102
                InferenceId::FP_REGISTER_TERM);
551
  }
552
3005
  else if (k == kind::FLOATINGPOINT_TO_REAL_TOTAL)
553
  {
554
    // Purify (fp.to_real x)
555
8
    NodeManager* nm = NodeManager::currentNM();
556
8
    SkolemManager* sm = nm->getSkolemManager();
557
16
    Node sk = sm->mkPurifySkolem(node, "to_real", "fp purify skolem");
558
8
    handleLemma(node.eqNode(sk), InferenceId::FP_REGISTER_TERM);
559
8
    d_abstractionMap.insert(sk, node);
560
561
    Node pd =
562
        nm->mkNode(kind::IMPLIES,
563
32
                   nm->mkNode(kind::OR,
564
16
                              nm->mkNode(kind::FLOATINGPOINT_ISNAN, node[0]),
565
16
                              nm->mkNode(kind::FLOATINGPOINT_ISINF, node[0])),
566
32
                   nm->mkNode(kind::EQUAL, node, node[1]));
567
8
    handleLemma(pd, InferenceId::FP_REGISTER_TERM);
568
569
    Node z =
570
        nm->mkNode(kind::IMPLIES,
571
16
                   nm->mkNode(kind::FLOATINGPOINT_ISZ, node[0]),
572
32
                   nm->mkNode(kind::EQUAL, node, nm->mkConst(Rational(0U))));
573
8
    handleLemma(z, InferenceId::FP_REGISTER_TERM);
574
8
    return;
575
576
    // TODO : bounds on the output from largest floats, #1914
577
  }
578
2997
  else if (k == kind::FLOATINGPOINT_TO_FP_REAL)
579
  {
580
    // Purify ((_ to_fp eb sb) rm x)
581
7
    NodeManager* nm = NodeManager::currentNM();
582
7
    SkolemManager* sm = nm->getSkolemManager();
583
14
    Node sk = sm->mkPurifySkolem(node, "to_real_fp", "fp purify skolem");
584
7
    handleLemma(node.eqNode(sk), InferenceId::FP_REGISTER_TERM);
585
7
    d_abstractionMap.insert(sk, node);
586
587
    Node nnan =
588
14
        nm->mkNode(kind::NOT, nm->mkNode(kind::FLOATINGPOINT_ISNAN, node));
589
7
    handleLemma(nnan, InferenceId::FP_REGISTER_TERM);
590
591
    Node z = nm->mkNode(
592
        kind::IMPLIES,
593
14
        nm->mkNode(kind::EQUAL, node[1], nm->mkConst(Rational(0U))),
594
14
        nm->mkNode(kind::EQUAL,
595
                   node,
596
14
                   nm->mkConst(FloatingPoint::makeZero(
597
42
                       node.getType().getConst<FloatingPointSize>(), false))));
598
7
    handleLemma(z, InferenceId::FP_REGISTER_TERM);
599
7
    return;
600
601
    // TODO : rounding-mode specific bounds on floats that don't give infinity
602
    // BEWARE of directed rounding!   #1914
603
  }
604
605
  /* When not word-blasting lazier, we word-blast every term on
606
   * registration. */
607
3092
  if (!options().fp.fpLazyWb)
608
  {
609
3052
    wordBlastAndEquateTerm(node);
610
  }
611
}
612
613
23696
bool TheoryFp::isRegistered(TNode node)
614
{
615
23696
  return d_registeredTerms.find(node) != d_registeredTerms.end();
616
}
617
618
4023
void TheoryFp::preRegisterTerm(TNode node)
619
{
620
4023
  if (!options().fp.fpExp)
621
  {
622
2734
    TypeNode tn = node.getType();
623
1367
    if (tn.isFloatingPoint())
624
    {
625
388
      unsigned exp_sz = tn.getFloatingPointExponentSize();
626
388
      unsigned sig_sz = tn.getFloatingPointSignificandSize();
627
388
      if (!((exp_sz == 8 && sig_sz == 24) || (exp_sz == 11 && sig_sz == 53)))
628
      {
629
        std::stringstream ss;
630
        ss << "FP term " << node << " with type whose size is " << exp_sz << "/"
631
           << sig_sz
632
           << " is not supported, only Float32 (8/24) or Float64 (11/53) types "
633
              "are supported in default mode. Try the experimental solver via "
634
              "--fp-exp. Note: There are known issues with the experimental "
635
              "solver, use at your own risk.";
636
        throw LogicException(ss.str());
637
      }
638
    }
639
  }
640
8046
  Trace("fp-preRegisterTerm")
641
4023
      << "TheoryFp::preRegisterTerm(): " << node << std::endl;
642
4023
  registerTerm(node);
643
4023
  return;
644
}
645
646
1287
void TheoryFp::handleLemma(Node node, InferenceId id)
647
{
648
1287
  Trace("fp") << "TheoryFp::handleLemma(): asserting " << node << std::endl;
649
1287
  if (rewrite(node) != d_true)
650
  {
651
    /* We only send non-trivial lemmas. */
652
1285
    d_im.lemma(node, id);
653
  }
654
1287
}
655
656
8749
bool TheoryFp::propagateLit(TNode node)
657
{
658
8749
  Trace("fp") << "TheoryFp::propagateLit(): propagate " << node << std::endl;
659
8749
  return d_im.propagateLit(node);
660
}
661
662
20
void TheoryFp::conflictEqConstantMerge(TNode t1, TNode t2)
663
{
664
40
  Trace("fp") << "TheoryFp::conflictEqConstantMerge(): conflict detected"
665
20
              << std::endl;
666
20
  d_im.conflictEqConstantMerge(t1, t2);
667
20
}
668
669
8303
bool TheoryFp::needsCheckLastEffort()
670
{
671
  // only need to check if we have added to the abstraction map, otherwise
672
  // postCheck below is a no-op.
673
8303
  return !d_abstractionMap.empty();
674
}
675
676
32051
void TheoryFp::postCheck(Effort level)
677
{
678
  /* Resolve the abstractions for the conversion lemmas */
679
32051
  if (level == EFFORT_LAST_CALL)
680
  {
681
78
    Trace("fp-abstraction")
682
39
        << "TheoryFp::check(): checking abstractions" << std::endl;
683
39
    TheoryModel* m = getValuation().getModel();
684
39
    bool lemmaAdded = false;
685
686
90
    for (const auto& [abstract, concrete] : d_abstractionMap)
687
    {
688
102
      Trace("fp-abstraction")
689
51
          << "TheoryFp::check(): Abstraction: " << abstract << std::endl;
690
51
      if (m->hasTerm(abstract))
691
      {  // Is actually used in the model
692
102
        Trace("fp-abstraction")
693
51
            << "TheoryFp::check(): ... relevant" << std::endl;
694
51
        lemmaAdded |= refineAbstraction(m, abstract, concrete);
695
      }
696
      else
697
      {
698
        Trace("fp-abstraction")
699
            << "TheoryFp::check(): ... not relevant" << std::endl;
700
      }
701
    }
702
  }
703
704
32051
  Trace("fp") << "TheoryFp::check(): completed" << std::endl;
705
  /* Checking should be handled by the bit-vector engine */
706
32051
}
707
708
9037
bool TheoryFp::preNotifyFact(
709
    TNode atom, bool pol, TNode fact, bool isPrereg, bool isInternal)
710
{
711
  /* Word-blast lazier if configured. */
712
18074
  if (options().fp.fpLazyWb
713
18074
      && d_wbFactsCache.find(atom) == d_wbFactsCache.end())
714
  {
715
8
    d_wbFactsCache.insert(atom);
716
8
    wordBlastAndEquateTerm(atom);
717
  }
718
719
9037
  if (atom.getKind() == kind::EQUAL)
720
  {
721
6311
    Assert(!(atom[0].getType().isFloatingPoint()
722
             || atom[0].getType().isRoundingMode())
723
           || isRegistered(atom[0]));
724
6311
    Assert(!(atom[1].getType().isFloatingPoint()
725
             || atom[1].getType().isRoundingMode())
726
           || isRegistered(atom[1]));
727
6311
    registerTerm(atom);  // Needed for float equalities
728
  }
729
  else
730
  {
731
    // A system-wide invariant; predicates are registered before they are
732
    // asserted
733
2726
    Assert(isRegistered(atom));
734
735
2726
    if (!d_equalityEngine->isFunctionKind(atom.getKind()))
736
    {
737
      return true;
738
    }
739
  }
740
9037
  return false;
741
}
742
743
1141
void TheoryFp::notifySharedTerm(TNode n)
744
{
745
  /* Word-blast lazier if configured. */
746
1141
  if (options().fp.fpLazyWb && d_wbFactsCache.find(n) == d_wbFactsCache.end())
747
  {
748
20
    d_wbFactsCache.insert(n);
749
20
    wordBlastAndEquateTerm(n);
750
  }
751
1141
}
752
753
14
TrustNode TheoryFp::explain(TNode n)
754
{
755
14
  Trace("fp") << "TheoryFp::explain(): explain " << n << std::endl;
756
757
  // All things we assert directly (and not via bit-vector) should
758
  // come from the equality engine so this should be sufficient...
759
28
  std::vector<TNode> assumptions;
760
761
14
  bool polarity = n.getKind() != kind::NOT;
762
28
  TNode atom = polarity ? n : n[0];
763
14
  if (atom.getKind() == kind::EQUAL) {
764
14
    d_equalityEngine->explainEquality(atom[0], atom[1], polarity, assumptions);
765
  } else {
766
    d_equalityEngine->explainPredicate(atom, polarity, assumptions);
767
  }
768
769
28
  Node exp = helper::buildConjunct(assumptions);
770
28
  return TrustNode::mkTrustPropExp(n, exp, nullptr);
771
}
772
773
Node TheoryFp::getModelValue(TNode var) {
774
  return d_wordBlaster->getValue(d_valuation, var);
775
}
776
777
7252
bool TheoryFp::collectModelInfo(TheoryModel* m,
778
                                const std::set<Node>& relevantTerms)
779
{
780
  // this override behavior to not assert equality engine
781
7252
  return collectModelValues(m, relevantTerms);
782
}
783
784
7252
bool TheoryFp::collectModelValues(TheoryModel* m,
785
                                  const std::set<Node>& relevantTerms)
786
{
787
14504
  Trace("fp-collectModelInfo")
788
7252
      << "TheoryFp::collectModelInfo(): begin" << std::endl;
789
7252
  if (Trace.isOn("fp-collectModelInfo")) {
790
    for (std::set<Node>::const_iterator i(relevantTerms.begin());
791
         i != relevantTerms.end(); ++i) {
792
      Trace("fp-collectModelInfo")
793
          << "TheoryFp::collectModelInfo(): relevantTerms " << *i << std::endl;
794
    }
795
  }
796
797
14504
  std::unordered_set<TNode> visited;
798
14504
  std::vector<TNode> working;
799
14504
  std::set<TNode> relevantVariables;
800
18915
  for (const Node& n : relevantTerms)
801
  {
802
11663
    working.emplace_back(n);
803
  }
804
805
50342
  while (!working.empty()) {
806
33480
    TNode current = working.back();
807
21545
    working.pop_back();
808
809
21545
    if (visited.find(current) != visited.end())
810
    {
811
      // Ignore things that have already been explored
812
9610
      continue;
813
    }
814
11935
    visited.insert(current);
815
816
23870
    TypeNode t = current.getType();
817
818
11935
    if ((t.isRoundingMode() || t.isFloatingPoint()) && this->isLeaf(current))
819
    {
820
4319
      relevantVariables.insert(current);
821
    }
822
823
11935
    working.insert(working.end(), current.begin(), current.end());
824
  }
825
826
11571
  for (const TNode& node : relevantVariables)
827
  {
828
8638
    Trace("fp-collectModelInfo")
829
4319
        << "TheoryFp::collectModelInfo(): relevantVariable " << node
830
4319
        << std::endl;
831
832
8638
    Node wordBlasted = d_wordBlaster->getValue(d_valuation, node);
833
    // We only assign the value if the FpWordBlaster actually has one, that is,
834
    // if FpWordBlaster::getValue() does not return a null node.
835
4319
    if (!wordBlasted.isNull() && !m->assertEquality(node, wordBlasted, true))
836
    {
837
      Trace("fp-collectModelInfo")
838
          << "TheoryFp::collectModelInfo(): ... not converted" << std::endl;
839
      return false;
840
    }
841
842
12957
    if (Configuration::isAssertionBuild() && isLeaf(node) && !node.isConst()
843
9387
        && node.getType().isFloatingPoint())
844
    {
845
      // Check that the equality engine has asssigned values to all the
846
      // components of `node` except `(sign node)` (the sign component is
847
      // assignable, meaning that the model builder can pick an arbitrary value
848
      // for it if it hasn't been assigned in the equality engine).
849
742
      NodeManager* nm = NodeManager::currentNM();
850
1484
      Node compNaN = nm->mkNode(kind::FLOATINGPOINT_COMPONENT_NAN, node);
851
1484
      Node compInf = nm->mkNode(kind::FLOATINGPOINT_COMPONENT_INF, node);
852
1484
      Node compZero = nm->mkNode(kind::FLOATINGPOINT_COMPONENT_ZERO, node);
853
      Node compExponent =
854
1484
          nm->mkNode(kind::FLOATINGPOINT_COMPONENT_EXPONENT, node);
855
      Node compSignificand =
856
1484
          nm->mkNode(kind::FLOATINGPOINT_COMPONENT_SIGNIFICAND, node);
857
858
742
      eq::EqualityEngine* ee = m->getEqualityEngine();
859
742
      Assert(ee->hasTerm(compNaN) && ee->getRepresentative(compNaN).isConst());
860
742
      Assert(ee->hasTerm(compInf) && ee->getRepresentative(compInf).isConst());
861
742
      Assert(ee->hasTerm(compZero)
862
             && ee->getRepresentative(compZero).isConst());
863
742
      Assert(ee->hasTerm(compExponent)
864
             && ee->getRepresentative(compExponent).isConst());
865
742
      Assert(ee->hasTerm(compSignificand));
866
742
      Assert(ee->getRepresentative(compSignificand).isConst());
867
868
      // At most one of the flags (NaN, inf, zero) can be set
869
1484
      Node one = nm->mkConst(BitVector(1U, 1U));
870
742
      size_t numFlags = 0;
871
742
      numFlags += ee->getRepresentative(compNaN) == one ? 1 : 0;
872
742
      numFlags += ee->getRepresentative(compInf) == one ? 1 : 0;
873
742
      numFlags += ee->getRepresentative(compZero) == one ? 1 : 0;
874
742
      Assert(numFlags <= 1);
875
    }
876
  }
877
878
7252
  return true;
879
}
880
881
7514
bool TheoryFp::NotifyClass::eqNotifyTriggerPredicate(TNode predicate,
882
                                                     bool value) {
883
15028
  Debug("fp-eq")
884
7514
      << "TheoryFp::eqNotifyTriggerPredicate(): call back as predicate "
885
7514
      << predicate << " is " << value << std::endl;
886
887
7514
  if (value) {
888
1749
    return d_theorySolver.propagateLit(predicate);
889
  }
890
5765
  return d_theorySolver.propagateLit(predicate.notNode());
891
}
892
893
1235
bool TheoryFp::NotifyClass::eqNotifyTriggerTermEquality(TheoryId tag, TNode t1,
894
                                                        TNode t2, bool value) {
895
2470
  Debug("fp-eq") << "TheoryFp::eqNotifyTriggerTermEquality(): call back as "
896
1235
                 << t1 << (value ? " = " : " != ") << t2 << std::endl;
897
898
1235
  if (value) {
899
1033
    return d_theorySolver.propagateLit(t1.eqNode(t2));
900
  }
901
202
  return d_theorySolver.propagateLit(t1.eqNode(t2).notNode());
902
}
903
904
20
void TheoryFp::NotifyClass::eqNotifyConstantTermMerge(TNode t1, TNode t2) {
905
40
  Debug("fp-eq") << "TheoryFp::eqNotifyConstantTermMerge(): call back as " << t1
906
20
                 << " = " << t2 << std::endl;
907
20
  d_theorySolver.conflictEqConstantMerge(t1, t2);
908
20
}
909
910
}  // namespace fp
911
}  // namespace theory
912
31137
}  // namespace cvc5