Branch data Line data Source code
1 : : /******************************************************************************
2 : : * This file is part of the cvc5 project.
3 : : *
4 : : * Copyright (c) 2009-2026 by the authors listed in the file AUTHORS
5 : : * in the top-level source directory and their institutional affiliations.
6 : : * All rights reserved. See the file COPYING in the top-level source
7 : : * directory for licensing information.
8 : : * ****************************************************************************
9 : : *
10 : : * Congruence manager, the interface to the equality engine from the
11 : : * linear arithmetic solver
12 : : */
13 : :
14 : : #include "theory/arith/linear/congruence_manager.h"
15 : :
16 : : #include "base/output.h"
17 : : #include "options/arith_options.h"
18 : : #include "proof/conv_proof_generator.h"
19 : : #include "proof/proof_checker.h"
20 : : #include "proof/proof_node.h"
21 : : #include "proof/proof_node_manager.h"
22 : : #include "smt/env.h"
23 : : #include "theory/arith/arith_poly_norm.h"
24 : : #include "theory/arith/arith_proof_utilities.h"
25 : : #include "theory/arith/arith_subs.h"
26 : : #include "theory/arith/arith_utilities.h"
27 : : #include "theory/arith/linear/constraint.h"
28 : : #include "theory/arith/linear/partial_model.h"
29 : : #include "theory/ee_setup_info.h"
30 : : #include "theory/rewriter.h"
31 : : #include "theory/uf/equality_engine.h"
32 : : #include "theory/uf/proof_equality_engine.h"
33 : :
34 : : using namespace cvc5::internal::kind;
35 : :
36 : : namespace cvc5::internal {
37 : : namespace theory {
38 : : namespace arith::linear {
39 : :
40 : 435 : std::vector<Node> andComponents(NodeManager* nm, TNode an)
41 : : {
42 [ - + ]: 435 : if (an == nm->mkConst(true))
43 : : {
44 : 0 : return {};
45 : : }
46 [ - + ]: 435 : else if (an.getKind() != Kind::AND)
47 : : {
48 : 0 : return {an};
49 : : }
50 : 435 : std::vector<Node> a{};
51 : 435 : a.reserve(an.getNumChildren());
52 : 435 : a.insert(a.end(), an.begin(), an.end());
53 : 435 : return a;
54 : 435 : }
55 : :
56 : 51016 : ArithCongruenceManager::ArithCongruenceManager(
57 : : Env& env,
58 : : ConstraintDatabase& cd,
59 : : SetupLiteralCallBack setup,
60 : : const ArithVariables& avars,
61 : 51016 : RaiseEqualityEngineConflict raiseConflict)
62 : : : EnvObj(env),
63 : 51016 : d_inConflict(context()),
64 : 51016 : d_raiseConflict(raiseConflict),
65 : 51016 : d_keepAlive(context()),
66 : 51016 : d_propagatations(context()),
67 : 51016 : d_explanationMap(context()),
68 : 51016 : d_constraintDatabase(cd),
69 : 51016 : d_setupLiteral(setup),
70 : 51016 : d_avariables(avars),
71 : 51016 : d_ee(nullptr),
72 [ + + ]: 51016 : d_pnm(d_env.isTheoryProofProducing() ? d_env.getProofNodeManager()
73 : : : nullptr),
74 : : // Construct d_pfGenEe with the SAT context, since its proof include
75 : : // unclosed assumptions of theory literals.
76 : 102032 : d_pfGenEe(new EagerProofGenerator(
77 : 102032 : d_env, context(), "ArithCongruenceManager::pfGenEe")),
78 : : // Construct d_pfGenEe with the USER context, since its proofs are closed.
79 : 102032 : d_pfGenExplain(new EagerProofGenerator(
80 : 102032 : d_env, userContext(), "ArithCongruenceManager::pfGenExplain")),
81 : 51016 : d_pfee(nullptr),
82 : 153048 : d_statistics(statisticsRegistry())
83 : : {
84 : 51016 : }
85 : :
86 : 50668 : ArithCongruenceManager::~ArithCongruenceManager() {}
87 : :
88 : 50944 : void ArithCongruenceManager::finishInit(eq::EqualityEngine* ee)
89 : : {
90 [ - + ][ - + ]: 50944 : Assert(ee != nullptr);
[ - - ]
91 : : // otherwise, we use the official one
92 : 50944 : d_ee = ee;
93 : : // the congruence kinds are already set up
94 : : // the proof equality engine is the one from the equality engine
95 : 50944 : d_pfee = d_ee->getProofEqualityEngine();
96 : : // have proof equality engine only if proofs are enabled
97 [ - + ][ - + ]: 50944 : Assert(isProofEnabled() == (d_pfee != nullptr));
[ - - ]
98 : 50944 : }
99 : :
100 : 51016 : ArithCongruenceManager::Statistics::Statistics(StatisticsRegistry& sr)
101 : : : d_watchedVariables(
102 : 51016 : sr.registerInt("theory::arith::congruence::watchedVariables")),
103 : : d_watchedVariableIsZero(
104 : 51016 : sr.registerInt("theory::arith::congruence::watchedVariableIsZero")),
105 : 51016 : d_watchedVariableIsNotZero(sr.registerInt(
106 : : "theory::arith::congruence::watchedVariableIsNotZero")),
107 : : d_equalsConstantCalls(
108 : 51016 : sr.registerInt("theory::arith::congruence::equalsConstantCalls")),
109 : 51016 : d_propagations(sr.registerInt("theory::arith::congruence::propagations")),
110 : : d_propagateConstraints(
111 : 51016 : sr.registerInt("theory::arith::congruence::propagateConstraints")),
112 : 51016 : d_conflicts(sr.registerInt("theory::arith::congruence::conflicts"))
113 : : {
114 : 51016 : }
115 : :
116 : 4041 : void ArithCongruenceManager::raiseConflict(Node conflict,
117 : : std::shared_ptr<ProofNode> pf)
118 : : {
119 [ - + ][ - + ]: 4041 : Assert(!inConflict());
[ - - ]
120 [ + - ]: 8082 : Trace("arith::conflict") << "difference manager conflict " << conflict
121 : 4041 : << std::endl;
122 : 4041 : d_inConflict.raise();
123 : 4041 : d_raiseConflict.raiseEEConflict(conflict, pf);
124 : 4041 : }
125 : 1738395 : bool ArithCongruenceManager::inConflict() const
126 : : {
127 : 1738395 : return d_inConflict.isRaised();
128 : : }
129 : :
130 : 8021848 : bool ArithCongruenceManager::hasMorePropagations() const
131 : : {
132 : 8021848 : return !d_propagatations.empty();
133 : : }
134 : 1234787 : const Node ArithCongruenceManager::getNextPropagation()
135 : : {
136 [ - + ][ - + ]: 1234787 : Assert(hasMorePropagations());
[ - - ]
137 : 1234787 : Node prop = d_propagatations.front();
138 : 1234787 : d_propagatations.dequeue();
139 : 1234787 : return prop;
140 : 0 : }
141 : :
142 : 109298 : bool ArithCongruenceManager::canExplain(TNode n) const
143 : : {
144 : 109298 : return d_explanationMap.find(n) != d_explanationMap.end();
145 : : }
146 : :
147 : 37543 : Node ArithCongruenceManager::externalToInternal(TNode n) const
148 : : {
149 [ - + ][ - + ]: 37543 : Assert(canExplain(n));
[ - - ]
150 : 37543 : ExplainMap::const_iterator iter = d_explanationMap.find(n);
151 : 37543 : size_t pos = (*iter).second;
152 : 75086 : return d_propagatations[pos];
153 : : }
154 : :
155 : 1083581 : void ArithCongruenceManager::pushBack(TNode n)
156 : : {
157 : 1083581 : d_explanationMap.insert(n, d_propagatations.size());
158 : 1083581 : d_propagatations.enqueue(n);
159 : :
160 : 1083581 : ++(d_statistics.d_propagations);
161 : 1083581 : }
162 : 209208 : void ArithCongruenceManager::pushBack(TNode n, TNode r)
163 : : {
164 : 209208 : d_explanationMap.insert(r, d_propagatations.size());
165 : 209208 : d_explanationMap.insert(n, d_propagatations.size());
166 : 209208 : d_propagatations.enqueue(n);
167 : :
168 : 209208 : ++(d_statistics.d_propagations);
169 : 209208 : }
170 : 0 : void ArithCongruenceManager::pushBack(TNode n, TNode r, TNode w)
171 : : {
172 : 0 : d_explanationMap.insert(w, d_propagatations.size());
173 : 0 : d_explanationMap.insert(r, d_propagatations.size());
174 : 0 : d_explanationMap.insert(n, d_propagatations.size());
175 : 0 : d_propagatations.enqueue(n);
176 : :
177 : 0 : ++(d_statistics.d_propagations);
178 : 0 : }
179 : :
180 : 672636 : void ArithCongruenceManager::watchedVariableIsZero(ConstraintCP lb,
181 : : ConstraintCP ub)
182 : : {
183 [ - + ][ - + ]: 672636 : Assert(lb->isLowerBound());
[ - - ]
184 [ - + ][ - + ]: 672636 : Assert(ub->isUpperBound());
[ - - ]
185 [ - + ][ - + ]: 672636 : Assert(lb->getVariable() == ub->getVariable());
[ - - ]
186 [ - + ][ - + ]: 672636 : Assert(lb->getValue().sgn() == 0);
[ - - ]
187 [ - + ][ - + ]: 672636 : Assert(ub->getValue().sgn() == 0);
[ - - ]
188 : :
189 : 672636 : ++(d_statistics.d_watchedVariableIsZero);
190 : :
191 : 672636 : ArithVar s = lb->getVariable();
192 : 672636 : TNode eq = d_watchedEqualities[s];
193 : 672636 : ConstraintCP eqC = d_constraintDatabase.getConstraint(
194 : : s, ConstraintType::Equality, lb->getValue());
195 : 672636 : NodeBuilder reasonBuilder(nodeManager(), Kind::AND);
196 : 672636 : auto pfLb = lb->externalExplainByAssertions(reasonBuilder);
197 : 672636 : auto pfUb = ub->externalExplainByAssertions(reasonBuilder);
198 : 672636 : Node reason = mkAndFromBuilder(nodeManager(), reasonBuilder);
199 : 672636 : std::shared_ptr<ProofNode> pf{};
200 [ + + ]: 672636 : if (isProofEnabled())
201 : : {
202 [ + + ][ - - ]: 1305288 : pf = d_pnm->mkNode(
203 : 978966 : ProofRule::ARITH_TRICHOTOMY, {pfLb, pfUb}, {}, eqC->getProofLiteral());
204 : 326322 : pf = ensurePredTransform(d_pnm, pf, eq);
205 : : }
206 : :
207 : 672636 : d_keepAlive.push_back(reason);
208 [ + - ]: 1345272 : Trace("arith-ee") << "Asserting an equality on " << s << ", on trichotomy"
209 : 672636 : << std::endl;
210 [ + - ]: 672636 : Trace("arith-ee") << " based on " << lb << std::endl;
211 [ + - ]: 672636 : Trace("arith-ee") << " based on " << ub << std::endl;
212 : 672636 : assertionToEqualityEngine(true, s, reason, pf);
213 : 672636 : }
214 : :
215 : 535651 : void ArithCongruenceManager::watchedVariableIsZero(ConstraintCP eq)
216 : : {
217 [ + - ]: 535651 : Trace("arith::cong") << "Cong::watchedVariableIsZero: " << *eq << std::endl;
218 : :
219 [ - + ][ - + ]: 535651 : Assert(eq->isEquality());
[ - - ]
220 [ - + ][ - + ]: 535651 : Assert(eq->getValue().sgn() == 0);
[ - - ]
221 : :
222 : 535651 : ++(d_statistics.d_watchedVariableIsZero);
223 : :
224 : 535651 : ArithVar s = eq->getVariable();
225 : :
226 : : // Explain for conflict is correct as these proofs are generated
227 : : // and stored eagerly
228 : : // These will be safe for propagation later as well
229 : 535651 : NodeBuilder nb(nodeManager(), Kind::AND);
230 : : // An open proof of eq from literals now in reason.
231 [ - + ]: 535651 : if (TraceIsOn("arith::cong"))
232 : : {
233 [ - - ]: 0 : eq->printProofTree(Trace("arith::cong"));
234 : : }
235 : 535651 : auto pf = eq->externalExplainByAssertions(nb);
236 [ + + ]: 535651 : if (isProofEnabled())
237 : : {
238 : 203239 : pf = ensurePredTransform(d_pnm, pf, d_watchedEqualities[s]);
239 : : }
240 : 535651 : Node reason = mkAndFromBuilder(nodeManager(), nb);
241 : :
242 : 535651 : d_keepAlive.push_back(reason);
243 : 535651 : assertionToEqualityEngine(true, s, reason, pf);
244 : 535651 : }
245 : :
246 : 1442545 : void ArithCongruenceManager::watchedVariableCannotBeZero(ConstraintCP c)
247 : : {
248 [ + - ]: 2885090 : Trace("arith::cong::notzero")
249 : 1442545 : << "Cong::watchedVariableCannotBeZero " << *c << std::endl;
250 : 1442545 : ++(d_statistics.d_watchedVariableIsNotZero);
251 : :
252 : 1442545 : ArithVar s = c->getVariable();
253 : 1442545 : Node disEq = d_watchedEqualities[s].negate();
254 : :
255 : : // Explain for conflict is correct as these proofs are generated and stored
256 : : // eagerly These will be safe for propagation later as well
257 : 1442545 : NodeBuilder nb(nodeManager(), Kind::AND);
258 : : // An open proof of eq from literals now in reason.
259 : 1442545 : auto pf = c->externalExplainByAssertions(nb);
260 [ - + ]: 1442545 : if (TraceIsOn("arith::cong::notzero"))
261 : : {
262 [ - - ]: 0 : Trace("arith::cong::notzero") << " original proof ";
263 [ - - ]: 0 : pf->printDebug(Trace("arith::cong::notzero"));
264 [ - - ]: 0 : Trace("arith::cong::notzero") << std::endl;
265 : : }
266 : 1442545 : Node reason = mkAndFromBuilder(nodeManager(), nb);
267 [ + + ]: 1442545 : if (isProofEnabled())
268 : : {
269 [ + + ]: 653234 : if (c->getType() == ConstraintType::Disequality)
270 : : {
271 [ - + ][ - + ]: 169432 : Assert(c->getLiteral() == d_watchedEqualities[s].negate());
[ - - ]
272 : : // We have to prove equivalence to the watched disequality.
273 : 169432 : pf = ensurePredTransform(d_pnm, pf, disEq);
274 : : }
275 : : else
276 : : {
277 [ + - ]: 967604 : Trace("arith::cong::notzero")
278 : 483802 : << " proof modification needed" << std::endl;
279 : :
280 : : // Four cases:
281 : : // c has form x_i = d, d > 0 => multiply c by -1 in Farkas proof
282 : : // c has form x_i = d, d > 0 => multiply c by 1 in Farkas proof
283 : : // c has form x_i <= d, d < 0 => multiply c by 1 in Farkas proof
284 : : // c has form x_i >= d, d > 0 => multiply c by -1 in Farkas proof
285 : 483802 : const bool scaleCNegatively = c->getType() == ConstraintType::LowerBound
286 [ + + ][ + + ]: 502582 : || (c->getType() == ConstraintType::Equality
287 [ + + ]: 18780 : && c->getValue().sgn() > 0);
288 [ + + ]: 483802 : const int cSign = scaleCNegatively ? -1 : 1;
289 : 483802 : TNode isZero = d_watchedEqualities[s];
290 : 483802 : TypeNode type = isZero[0].getType();
291 : 483802 : const auto isZeroPf = d_pnm->mkAssume(isZero);
292 : 483802 : const auto nm = nodeManager();
293 : 1935208 : std::vector<std::shared_ptr<ProofNode>> pfs{isZeroPf, pf};
294 : : // Trick for getting correct, opposing signs.
295 : 0 : std::vector<Node> coeff{nm->mkConstInt(Rational(-1 * cSign)),
296 : 2419010 : nm->mkConstInt(Rational(cSign))};
297 : 483802 : std::vector<Node> coeffUse = getMacroSumUbCoeff(nm, pfs, coeff);
298 : : auto sumPf =
299 : 483802 : d_pnm->mkNode(ProofRule::MACRO_ARITH_SCALE_SUM_UB, pfs, coeffUse);
300 : 483802 : Node fn = nm->mkConst(false);
301 : 483802 : const auto botPf = ensurePredTransform(d_pnm, sumPf, fn);
302 : 1451406 : std::vector<Node> assumption = {isZero};
303 : 483802 : pf = d_pnm->mkScope(botPf, assumption, false);
304 [ - + ]: 483802 : if (TraceIsOn("arith::cong::notzero"))
305 : : {
306 [ - - ]: 0 : Trace("arith::cong::notzero") << " new proof ";
307 [ - - ]: 0 : pf->printDebug(Trace("arith::cong::notzero"));
308 [ - - ]: 0 : Trace("arith::cong::notzero") << std::endl;
309 : : }
310 : 483802 : }
311 [ - + ][ - + ]: 653234 : Assert(pf->getResult() == disEq);
[ - - ]
312 : : }
313 : 1442545 : d_keepAlive.push_back(reason);
314 : 1442545 : assertionToEqualityEngine(false, s, reason, pf);
315 : 1442545 : }
316 : :
317 : 1734354 : bool ArithCongruenceManager::propagate(TNode x)
318 : : {
319 [ + - ]: 3468708 : Trace("arith::congruenceManager")
320 : 1734354 : << "ArithCongruenceManager::propagate(" << x << ")" << std::endl;
321 [ - + ]: 1734354 : if (inConflict())
322 : : {
323 : 0 : return true;
324 : : }
325 : :
326 : 1734354 : Node rewritten = rewrite(x);
327 : :
328 : : // Need to still propagate this!
329 [ + + ]: 1734354 : if (rewritten.getKind() == Kind::CONST_BOOLEAN)
330 : : {
331 : 3559 : pushBack(x);
332 : :
333 [ - + ]: 3559 : if (rewritten.getConst<bool>())
334 : : {
335 : 0 : return true;
336 : : }
337 : : else
338 : : {
339 : : // x rewrites to false.
340 : 3559 : ++(d_statistics.d_conflicts);
341 : 3559 : TrustNode trn = explainInternal(x);
342 : 3559 : Node conf = flattenAnd(trn.getNode());
343 [ + - ]: 7118 : Trace("arith::congruenceManager")
344 : 0 : << "rewritten to false " << x << " with explanation " << conf
345 : 3559 : << std::endl;
346 [ + + ]: 3559 : if (isProofEnabled())
347 : : {
348 : 1823 : auto pf = trn.getGenerator()->getProofFor(trn.getProven());
349 : 1823 : auto confPf = ensurePredTransform(d_pnm, pf, conf.negate());
350 : 1823 : raiseConflict(conf, confPf);
351 : 1823 : }
352 : : else
353 : : {
354 : 1736 : raiseConflict(conf);
355 : : }
356 : 3559 : return false;
357 : 3559 : }
358 : : }
359 : :
360 [ - + ][ - + ]: 1730795 : Assert(rewritten.getKind() != Kind::CONST_BOOLEAN);
[ - - ]
361 : :
362 : 1730795 : ConstraintP c = d_constraintDatabase.lookup(rewritten);
363 [ + + ]: 1730795 : if (c == NullConstraint)
364 : : {
365 : : // using setup as there may not be a corresponding congruence literal yet
366 : 41116 : d_setupLiteral(rewritten);
367 : 41116 : c = d_constraintDatabase.lookup(rewritten);
368 [ - + ][ - + ]: 41116 : Assert(c != NullConstraint);
[ - - ]
369 : : }
370 : :
371 [ + - ]: 3461590 : Trace("arith::congruenceManager")
372 : 0 : << "x is " << c->hasProof() << " " << (x == rewritten) << " "
373 : 1730795 : << c->canBePropagated() << " " << c->negationHasProof() << std::endl;
374 : :
375 [ + + ]: 1730795 : if (c->negationHasProof())
376 : : {
377 : 482 : TrustNode texpC = explainInternal(x);
378 : 482 : Node expC = texpC.getNode();
379 : 482 : ConstraintCP negC = c->getNegation();
380 : 964 : Node neg = Constraint::externalExplainByAssertions(nodeManager(), {negC});
381 : 482 : Node conf = expC.andNode(neg);
382 : 482 : Node finalPf = flattenAnd(conf);
383 : :
384 : 482 : ++(d_statistics.d_conflicts);
385 [ + + ]: 482 : if (isProofEnabled())
386 : : {
387 [ + - ]: 262 : Trace("arith-cm-proof") << "Handle conflict " << finalPf << std::endl;
388 : : // we have a proof of (=> C L1) and need a proof of
389 : : // (not (and C L2)), where L1 and L2 are contradictory literals,
390 : : // stored in proven[1] and neg respectively below.
391 : 262 : NodeManager* nm = nodeManager();
392 : 262 : std::vector<Node> conj(finalPf.begin(), finalPf.end());
393 : 524 : CDProof cdp(d_env);
394 : 262 : Node falsen = nm->mkConst(false);
395 : 262 : Node finalPfNeg = finalPf.notNode();
396 : 262 : cdp.addProof(texpC.toProofNode());
397 : 262 : Node proven = texpC.getProven();
398 [ + - ]: 262 : Trace("arith-cm-proof") << "Proven was " << proven << std::endl;
399 : 262 : Node antec = proven[0];
400 : 262 : std::vector<Node> antecc;
401 [ + - ]: 262 : if (antec.getKind() == Kind::AND)
402 : : {
403 : 262 : antecc.insert(antecc.end(), antec.begin(), antec.end());
404 : 262 : cdp.addStep(antec, ProofRule::AND_INTRO, antecc, {});
405 : : }
406 : : else
407 : : {
408 : 0 : antecc.push_back(antec);
409 : : }
410 [ + + ][ - - ]: 786 : cdp.addStep(proven[1], ProofRule::MODUS_PONENS, {antec, proven}, {});
411 : 262 : std::shared_ptr<ProofNode> pf;
412 : 262 : bool success = false;
413 [ + + ]: 338 : for (size_t i = 0; i < 2; i++)
414 : : {
415 [ + + ]: 301 : Node lit1 = i == 0 ? neg : proven[1];
416 [ + + ]: 301 : Node lit2 = i == 0 ? proven[1] : neg;
417 [ + - ]: 301 : Trace("arith-cm-proof") << "same " << lit1 << " " << lit2 << std::endl;
418 : 301 : Rational rx, ry;
419 : : // We are robust to cases where proven[1] and neg[0] are equivalent via
420 : : // arith poly norm here, where in most cases neg[0] is proven[1]
421 : 903 : if (lit1.getKind() == Kind::NOT
422 : 301 : && PolyNorm::isArithPolyNormRel(lit2, lit1[0], rx, ry))
423 : : {
424 [ + + ]: 225 : if (lit1[0] != lit2)
425 : : {
426 : 30 : Node eqa = lit2.eqNode(lit1[0]);
427 : : Node premise =
428 : 60 : PolyNorm::getArithPolyNormRelPremise(lit2, lit1[0], rx, ry);
429 : 60 : cdp.addStep(premise, ProofRule::ARITH_POLY_NORM, {}, {premise});
430 : 90 : cdp.addStep(eqa, ProofRule::ARITH_POLY_NORM_REL, {premise}, {eqa});
431 [ + + ][ - - ]: 90 : cdp.addStep(lit1[0], ProofRule::EQ_RESOLVE, {lit2, eqa}, {});
432 : 30 : }
433 : : // L1 and L2 are negation of one another, just use CONTRA
434 [ + + ][ - - ]: 675 : cdp.addStep(falsen, ProofRule::CONTRA, {lit1[0], lit1}, {});
435 : 225 : success = true;
436 : 225 : break;
437 : : }
438 [ + + ][ + + ]: 976 : }
[ + + ][ + + ]
439 [ + + ][ + - ]: 262 : if (!success && proven[1].getKind() == Kind::EQUAL)
[ + + ][ + + ]
[ - - ]
440 : : {
441 : : // otherwise typically proven[1] is of the form (= t c) or (= c t) where
442 : : // neg is the (negation of) a relation involving t.
443 : 74 : Node peq = proven[1][0].isConst() ? proven[1][1].eqNode(proven[1][0])
444 : 73 : : proven[1];
445 [ - + ][ - + ]: 37 : Assert(peq.getKind() == Kind::EQUAL);
[ - - ]
446 [ + + ]: 37 : if (peq[0].getKind() == Kind::TO_REAL)
447 : : {
448 : : // if we have (= (to_real t) c) where c is a rational, we do:
449 : : // -------------------------- ARITH_POLY_NORM_REL
450 : : // (= (to_real t) c) (= (= (to_real t) c) (= t c'))
451 : : // ------------------------------------------------- EQ_RESOLVE
452 : : // (= t c')
453 : : // where c' is integer equivalent of c.
454 : 4 : Assert(peq[1].isConst() && peq[1].getConst<Rational>().isIntegral());
455 : 4 : Node ic = nm->mkConstInt(peq[1].getConst<Rational>());
456 : 8 : Node peqi = peq[0][0].eqNode(ic);
457 : 4 : Node equiv = peq.eqNode(peqi);
458 : 4 : Rational cx, cy;
459 : : Node premise =
460 : 8 : PolyNorm::getArithPolyNormRelPremise(peq, peqi, cx, cy);
461 : 8 : cdp.addStep(premise, ProofRule::ARITH_POLY_NORM, {}, {premise});
462 : 12 : cdp.addStep(
463 : : equiv, ProofRule::ARITH_POLY_NORM_REL, {premise}, {equiv});
464 [ + + ][ - - ]: 12 : cdp.addStep(peqi, ProofRule::EQ_RESOLVE, {peq, equiv}, {});
465 : 4 : peq = peqi;
466 : 4 : }
467 : 37 : ProofChecker* pc = d_env.getProofNodeManager()->getChecker();
468 : : // We substitute t -> c within the arithmetic context of neg.
469 : : // In particular using an arithmetic context ensures that this rewrite
470 : : // should be locally handled as an ARITH_POLY_NORM step.
471 : : // Otherwise, we may require the full rewriter. For example:
472 : : // (= x f(x)) => (not (>= (+ x (* -1 f(x))) 0)) would otherwise fail if
473 : : // we applied at general substitution
474 : : // (not (>= (+ f(x) (* -1 f(f(x)))) 0)),
475 : : // whereas since x in f(x) is not in an arithmetic context, we want
476 : : // (not (>= (+ f(x) (* -1 f(x))) 0)).
477 : : // Furthermore note that we should not traverse non-linear
478 : : // multiplication here, as this inference was inferred via linear
479 : : // arithmetic which treats non-linear arithmetic as an abstraction.
480 : 37 : ArithSubsTermContext astc(false);
481 : : TConvProofGenerator tcnv(d_env,
482 : : nullptr,
483 : : TConvPolicy::FIXPOINT,
484 : : TConvCachePolicy::NEVER,
485 : : "ArithRConsTConv",
486 : 74 : &astc);
487 : 74 : Trace("arith-cm-proof") << "add step " << peq[0] << " -> " << peq[1]
488 : 37 : << ", rewrite " << neg << std::endl;
489 : 37 : tcnv.addRewriteStep(peq[0], peq[1], &cdp);
490 : 37 : std::shared_ptr<ProofNode> pfna = tcnv.getProofForRewriting(neg);
491 : 37 : Node negr = pfna->getResult()[1];
492 : 74 : Node res = pc->checkDebug(
493 : 148 : ProofRule::MACRO_SR_PRED_TRANSFORM, {negr}, {falsen}, falsen);
494 [ - + ][ - + ]: 37 : Assert(!res.isNull());
[ - - ]
495 [ + - ]: 37 : if (!res.isNull())
496 : : {
497 : 111 : cdp.addStep(
498 : : falsen, ProofRule::MACRO_SR_PRED_TRANSFORM, {negr}, {falsen});
499 : 37 : success = true;
500 [ + - ]: 37 : if (negr != neg)
501 : : {
502 : 37 : cdp.addProof(pfna);
503 [ + + ][ - - ]: 111 : cdp.addStep(
504 : : negr, ProofRule::EQ_RESOLVE, {neg, pfna->getResult()}, {});
505 : : }
506 : : }
507 : 37 : }
508 [ + - ]: 262 : if (success)
509 : : {
510 : 524 : cdp.addStep(finalPfNeg, ProofRule::SCOPE, {falsen}, conj);
511 : 262 : pf = cdp.getProofFor(finalPfNeg);
512 : : }
513 : 262 : Assert(pf != nullptr) << "Failed from " << neg << " " << proven[1];
514 : 262 : raiseConflict(finalPf, pf);
515 : 262 : }
516 : : else
517 : : {
518 : 220 : raiseConflict(finalPf);
519 : : }
520 [ + - ]: 964 : Trace("arith::congruenceManager")
521 : 482 : << "congruenceManager found a conflict " << finalPf << std::endl;
522 : 482 : return false;
523 : 482 : }
524 : :
525 : : // Cases for propagation
526 : : // C : c has a proof
527 : : // S : x == rewritten
528 : : // P : c can be propagated
529 : : //
530 : : // CSP
531 : : // 000 : propagate x, and mark C it as being explained
532 : : // 001 : propagate x, and propagate c after marking it as being explained
533 : : // 01* : propagate x, mark c but do not propagate c
534 : : // 10* : propagate x, do not mark c and do not propagate c
535 : : // 11* : drop the constraint, do not propagate x or c
536 : :
537 [ + + ][ + + ]: 1730313 : if (!c->hasProof() && x != rewritten)
[ + + ]
538 : : {
539 [ - + ]: 209208 : if (c->assertedToTheTheory())
540 : : {
541 : 0 : pushBack(x, rewritten, c->getWitness());
542 : : }
543 : : else
544 : : {
545 : 209208 : pushBack(x, rewritten);
546 : : }
547 : :
548 : 209208 : c->setEqualityEngineProof();
549 [ + + ][ + - ]: 209208 : if (c->canBePropagated() && !c->assertedToTheTheory())
[ + + ]
550 : : {
551 : 32982 : ++(d_statistics.d_propagateConstraints);
552 : 32982 : c->propagate();
553 : : }
554 : : }
555 [ + + ][ + - ]: 1521105 : else if (!c->hasProof() && x == rewritten)
[ + + ]
556 : : {
557 [ - + ]: 163556 : if (c->assertedToTheTheory())
558 : : {
559 : 0 : pushBack(x, c->getWitness());
560 : : }
561 : : else
562 : : {
563 : 163556 : pushBack(x);
564 : : }
565 : 163556 : c->setEqualityEngineProof();
566 : : }
567 [ + - ][ + + ]: 1357549 : else if (c->hasProof() && x != rewritten)
[ + + ]
568 : : {
569 [ + + ]: 916466 : if (c->assertedToTheTheory())
570 : : {
571 : 914866 : pushBack(x);
572 : : }
573 : : else
574 : : {
575 : 1600 : pushBack(x);
576 : : }
577 : : }
578 : : else
579 : : {
580 [ + - ][ + - ]: 441083 : Assert(c->hasProof() && x == rewritten);
[ - + ][ - + ]
[ - - ]
581 : : }
582 : 1730313 : return true;
583 : 1734354 : }
584 : :
585 : 41584 : TrustNode ArithCongruenceManager::explainInternal(TNode internal)
586 : : {
587 [ + + ]: 41584 : if (isProofEnabled())
588 : : {
589 : 20522 : return d_pfee->explain(internal);
590 : : }
591 : : // otherwise, explain without proof generator
592 : 21062 : Node exp = d_ee->mkExplainLit(internal);
593 : 21062 : return TrustNode::mkTrustPropExp(internal, exp, nullptr);
594 : 21062 : }
595 : :
596 : 37543 : TrustNode ArithCongruenceManager::explain(TNode external)
597 : : {
598 [ + - ]: 37543 : Trace("arith-ee") << "Ask for explanation of " << external << std::endl;
599 : 37543 : Node internal = externalToInternal(external);
600 [ + - ]: 37543 : Trace("arith-ee") << "...internal = " << internal << std::endl;
601 : 37543 : TrustNode trn = explainInternal(internal);
602 : 37543 : if (isProofEnabled() && trn.getProven()[1] != external)
603 : : {
604 [ - + ][ - + ]: 435 : Assert(trn.getKind() == TrustNodeKind::PROP_EXP);
[ - - ]
605 [ - + ][ - + ]: 435 : Assert(trn.getProven().getKind() == Kind::IMPLIES);
[ - - ]
606 [ - + ][ - + ]: 435 : Assert(trn.getGenerator() != nullptr);
[ - - ]
607 [ + - ]: 870 : Trace("arith-ee") << "tweaking proof to prove " << external << " not "
608 : 435 : << trn.getProven()[1] << std::endl;
609 : 435 : std::vector<std::shared_ptr<ProofNode>> assumptionPfs;
610 : 870 : std::vector<Node> assumptions = andComponents(nodeManager(), trn.getNode());
611 : 435 : assumptionPfs.push_back(trn.toProofNode());
612 [ + + ]: 1496 : for (const auto& a : assumptions)
613 : : {
614 : 1061 : assumptionPfs.push_back(
615 : 3183 : d_pnm->mkNode(ProofRule::TRUE_INTRO, {d_pnm->mkAssume(a)}, {}));
616 : : }
617 : : // uses substitution to true
618 : 435 : auto litPf = d_pnm->mkNode(ProofRule::MACRO_SR_PRED_TRANSFORM,
619 : : {assumptionPfs},
620 : : {external},
621 : 1305 : external);
622 : 870 : auto extPf = d_pnm->mkScope(litPf, assumptions);
623 : 435 : return d_pfGenExplain->mkTrustedPropagation(external, trn.getNode(), extPf);
624 : 435 : }
625 : 37108 : return trn;
626 : 37543 : }
627 : :
628 : 135421 : void ArithCongruenceManager::addWatchedPair(ArithVar s, TNode x, TNode y)
629 : : {
630 [ - + ][ - + ]: 135421 : Assert(!isWatchedVariable(s));
[ - - ]
631 : :
632 [ + - ]: 270842 : Trace("arith::congruenceManager")
633 : 135421 : << "addWatchedPair(" << s << ", " << x << ", " << y << ")" << std::endl;
634 : :
635 : 135421 : ++(d_statistics.d_watchedVariables);
636 : :
637 : 135421 : d_watchedVariables.add(s);
638 : : // must ensure types are correct, thus, add TO_REAL if necessary here
639 : 270842 : std::pair<Node, Node> p = mkSameType(x, y);
640 : 135421 : Node eq = p.first.eqNode(p.second);
641 : 135421 : d_watchedEqualities.set(s, eq);
642 : 135421 : }
643 : :
644 : 4297398 : void ArithCongruenceManager::assertLitToEqualityEngine(
645 : : Node lit, TNode reason, std::shared_ptr<ProofNode> pf)
646 : : {
647 : 4297398 : bool isEquality = lit.getKind() != Kind::NOT;
648 [ + + ]: 4297398 : Node eq = isEquality ? lit : lit[0];
649 [ - + ][ - + ]: 4297398 : Assert(eq.getKind() == Kind::EQUAL);
[ - - ]
650 : :
651 [ + - ]: 8594796 : Trace("arith-ee") << "Assert to Eq " << lit << ", reason " << reason
652 : 4297398 : << std::endl;
653 [ + + ]: 4297398 : if (isProofEnabled())
654 : : {
655 [ + + ]: 1791796 : if (CDProof::isSame(lit, reason))
656 : : {
657 [ + - ]: 767288 : Trace("arith-pfee") << "Asserting only, b/c implied by symm" << std::endl;
658 : : // The equality engine doesn't ref-count for us...
659 : 767288 : d_keepAlive.push_back(eq);
660 : 767288 : d_keepAlive.push_back(reason);
661 : 767288 : d_ee->assertEquality(eq, isEquality, reason);
662 : : }
663 [ + + ]: 1024508 : else if (hasProofFor(lit))
664 : : {
665 [ + - ]: 25694 : Trace("arith-pfee") << "Skipping b/c already done" << std::endl;
666 : : }
667 : : else
668 : : {
669 : 998814 : setProofFor(lit, pf);
670 [ + - ]: 998814 : Trace("arith-pfee") << "Actually asserting" << std::endl;
671 [ - + ]: 998814 : if (TraceIsOn("arith-pfee"))
672 : : {
673 [ - - ]: 0 : Trace("arith-pfee") << "Proof: ";
674 [ - - ]: 0 : pf->printDebug(Trace("arith-pfee"));
675 [ - - ]: 0 : Trace("arith-pfee") << std::endl;
676 : : }
677 : : // The proof equality engine *does* ref-count for us...
678 [ + - ]: 998814 : d_pfee->assertFact(lit, reason, d_pfGenEe.get());
679 : : }
680 : : }
681 : : else
682 : : {
683 : : // The equality engine doesn't ref-count for us...
684 : 2505602 : d_keepAlive.push_back(eq);
685 : 2505602 : d_keepAlive.push_back(reason);
686 : 2505602 : d_ee->assertEquality(eq, isEquality, reason);
687 : : }
688 : 4297398 : }
689 : :
690 : 2650832 : void ArithCongruenceManager::assertionToEqualityEngine(
691 : : bool isEquality, ArithVar s, TNode reason, std::shared_ptr<ProofNode> pf)
692 : : {
693 [ - + ][ - + ]: 2650832 : Assert(isWatchedVariable(s));
[ - - ]
694 : :
695 : 2650832 : TNode eq = d_watchedEqualities[s];
696 [ - + ][ - + ]: 2650832 : Assert(eq.getKind() == Kind::EQUAL);
[ - - ]
697 : :
698 [ + + ]: 2650832 : Node lit = isEquality ? Node(eq) : eq.notNode();
699 [ + - ]: 5301664 : Trace("arith-ee") << "Assert to Eq " << eq << ", pol " << isEquality
700 : 2650832 : << ", reason " << reason << std::endl;
701 : 2650832 : assertLitToEqualityEngine(lit, reason, pf);
702 : 2650832 : }
703 : :
704 : 2023322 : bool ArithCongruenceManager::hasProofFor(TNode f) const
705 : : {
706 [ - + ][ - + ]: 2023322 : Assert(isProofEnabled());
[ - - ]
707 [ + + ]: 2023322 : if (d_pfGenEe->hasProofFor(f))
708 : : {
709 : 25694 : return true;
710 : : }
711 : 1997628 : Node sym = CDProof::getSymmFact(f);
712 [ - + ][ - + ]: 1997628 : Assert(!sym.isNull());
[ - - ]
713 : 1997628 : return d_pfGenEe->hasProofFor(sym);
714 : 1997628 : }
715 : :
716 : 998814 : void ArithCongruenceManager::setProofFor(TNode f,
717 : : std::shared_ptr<ProofNode> pf) const
718 : : {
719 [ - + ][ - + ]: 998814 : Assert(!hasProofFor(f));
[ - - ]
720 : 998814 : d_pfGenEe->mkTrustNode(f, pf);
721 : 998814 : Node symF = CDProof::getSymmFact(f);
722 : 3995256 : auto symPf = d_pnm->mkNode(ProofRule::SYMM, {pf}, {});
723 : 998814 : d_pfGenEe->mkTrustNode(symF, symPf);
724 : 998814 : }
725 : :
726 : 1325768 : void ArithCongruenceManager::equalsConstant(ConstraintCP c)
727 : : {
728 [ - + ][ - + ]: 1325768 : Assert(c->isEquality());
[ - - ]
729 : :
730 : 1325768 : ++(d_statistics.d_equalsConstantCalls);
731 [ + - ]: 1325768 : Trace("equalsConstant") << "equals constant " << c << std::endl;
732 : :
733 : 1325768 : ArithVar x = c->getVariable();
734 : 1325768 : Node xAsNode = d_avariables.asNode(x);
735 : 1325768 : NodeManager* nm = nodeManager();
736 : : Node asRational = nm->mkConstRealOrInt(
737 : 1325768 : xAsNode.getType(), c->getValue().getNoninfinitesimalPart());
738 : :
739 : : // No guarentee this is in normal form!
740 : : // Note though, that it happens to be in proof normal form!
741 : 1325768 : Node eq = xAsNode.eqNode(asRational);
742 : 1325768 : d_keepAlive.push_back(eq);
743 : :
744 : 1325768 : NodeBuilder nb(nodeManager(), Kind::AND);
745 : 1325768 : auto pf = c->externalExplainByAssertions(nb);
746 : 1325768 : Node reason = mkAndFromBuilder(nodeManager(), nb);
747 : 1325768 : d_keepAlive.push_back(reason);
748 : :
749 [ + - ]: 2651536 : Trace("arith-ee") << "Assert equalsConstant " << eq << ", reason " << reason
750 : 1325768 : << std::endl;
751 : 1325768 : assertLitToEqualityEngine(eq, reason, pf);
752 : 1325768 : }
753 : :
754 : 320798 : void ArithCongruenceManager::equalsConstant(ConstraintCP lb, ConstraintCP ub)
755 : : {
756 [ - + ][ - + ]: 320798 : Assert(lb->isLowerBound());
[ - - ]
757 [ - + ][ - + ]: 320798 : Assert(ub->isUpperBound());
[ - - ]
758 [ - + ][ - + ]: 320798 : Assert(lb->getVariable() == ub->getVariable());
[ - - ]
759 : :
760 : 320798 : ++(d_statistics.d_equalsConstantCalls);
761 [ + - ]: 641596 : Trace("equalsConstant") << "equals constant " << lb << std::endl
762 : 320798 : << ub << std::endl;
763 : :
764 : 320798 : ArithVar x = lb->getVariable();
765 : 320798 : NodeManager* nm = nodeManager();
766 : 320798 : NodeBuilder nb(nm, Kind::AND);
767 : 320798 : auto pfLb = lb->externalExplainByAssertions(nb);
768 : 320798 : auto pfUb = ub->externalExplainByAssertions(nb);
769 : 320798 : Node reason = mkAndFromBuilder(nodeManager(), nb);
770 : :
771 : 320798 : Node xAsNode = d_avariables.asNode(x);
772 : : Node asRational = nm->mkConstRealOrInt(
773 : 320798 : xAsNode.getType(), lb->getValue().getNoninfinitesimalPart());
774 : :
775 : : // No guarentee this is in normal form!
776 : : // Note though, that it happens to be in proof normal form!
777 : 320798 : Node eq = xAsNode.eqNode(asRational);
778 : 320798 : std::shared_ptr<ProofNode> pf;
779 [ + + ]: 320798 : if (isProofEnabled())
780 : : {
781 [ + + ][ - - ]: 413613 : pf = d_pnm->mkNode(ProofRule::ARITH_TRICHOTOMY, {pfLb, pfUb}, {}, eq);
782 : : }
783 : 320798 : d_keepAlive.push_back(eq);
784 : 320798 : d_keepAlive.push_back(reason);
785 : :
786 [ + - ]: 641596 : Trace("arith-ee") << "Assert equalsConstant2 " << eq << ", reason " << reason
787 : 320798 : << std::endl;
788 : :
789 : 320798 : assertLitToEqualityEngine(eq, reason, pf);
790 : 320798 : }
791 : :
792 : 9426462 : bool ArithCongruenceManager::isProofEnabled() const { return d_pnm != nullptr; }
793 : :
794 : : } // namespace arith::linear
795 : : } // namespace theory
796 : : } // namespace cvc5::internal
|