1 /********************* */
2 /*! \file miplib_trick.cpp
4 ** Top contributors (to current version):
5 ** Mathias Preiner, Andrew Reynolds, Morgan Deters
6 ** This file is part of the CVC4 project.
7 ** Copyright (c) 2009-2020 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.\endverbatim
12 ** \brief The MIPLIB trick preprocessing pass
16 #include "preprocessing/passes/miplib_trick.h"
20 #include "expr/node_self_iterator.h"
21 #include "options/arith_options.h"
22 #include "options/smt_options.h"
23 #include "smt/smt_statistics_registry.h"
24 #include "smt_util/boolean_simplification.h"
25 #include "theory/booleans/circuit_propagator.h"
26 #include "theory/theory_model.h"
27 #include "theory/trust_substitutions.h"
30 namespace preprocessing
{
33 using namespace CVC4::theory
;
38 * Remove conjuncts in toRemove from conjunction n. Return # of removed
41 size_t removeFromConjunction(Node
& n
,
42 const std::unordered_set
<unsigned long>& toRemove
)
44 Assert(n
.getKind() == kind::AND
);
45 Node trueNode
= NodeManager::currentNM()->mkConst(true);
47 for (Node::iterator j
= n
.begin(); j
!= n
.end(); ++j
)
49 size_t subremovals
= 0;
51 if (toRemove
.find(sub
.getId()) != toRemove
.end()
52 || (sub
.getKind() == kind::AND
53 && (subremovals
= removeFromConjunction(sub
, toRemove
)) > 0))
55 NodeBuilder
<> b(kind::AND
);
56 b
.append(n
.begin(), j
);
59 removals
+= subremovals
;
66 for (++j
; j
!= n
.end(); ++j
)
68 if (toRemove
.find((*j
).getId()) != toRemove
.end())
72 else if ((*j
).getKind() == kind::AND
)
75 if ((subremovals
= removeFromConjunction(sub
, toRemove
)) > 0)
77 removals
+= subremovals
;
90 if (b
.getNumChildren() == 0)
95 else if (b
.getNumChildren() == 1)
104 n
= Rewriter::rewrite(n
);
109 Assert(removals
== 0);
114 * Trace nodes back to their assertions using CircuitPropagator's
117 void traceBackToAssertions(booleans::CircuitPropagator
* propagator
,
118 const std::vector
<Node
>& nodes
,
119 std::vector
<TNode
>& assertions
)
121 const booleans::CircuitPropagator::BackEdgesMap
& backEdges
=
122 propagator
->getBackEdges();
123 for (vector
<Node
>::const_iterator i
= nodes
.begin(); i
!= nodes
.end(); ++i
)
125 booleans::CircuitPropagator::BackEdgesMap::const_iterator j
=
127 // term must appear in map, otherwise how did we get here?!
128 Assert(j
!= backEdges
.end());
129 // if term maps to empty, that means it's a top-level assertion
130 if (!(*j
).second
.empty())
132 traceBackToAssertions(propagator
, (*j
).second
, assertions
);
136 assertions
.push_back(*i
);
143 MipLibTrick::MipLibTrick(PreprocessingPassContext
* preprocContext
)
144 : PreprocessingPass(preprocContext
, "miplib-trick")
146 if (!options::incrementalSolving())
148 NodeManager::currentNM()->subscribeEvents(this);
152 MipLibTrick::~MipLibTrick()
154 if (!options::incrementalSolving())
156 NodeManager::currentNM()->unsubscribeEvents(this);
160 void MipLibTrick::nmNotifyNewVar(TNode n
)
162 if (n
.getType().isBoolean())
164 d_boolVars
.push_back(n
);
168 void MipLibTrick::nmNotifyNewSkolem(TNode n
,
169 const std::string
& comment
,
172 if (n
.getType().isBoolean())
174 d_boolVars
.push_back(n
);
178 PreprocessingPassResult
MipLibTrick::applyInternal(
179 AssertionPipeline
* assertionsToPreprocess
)
181 Assert(assertionsToPreprocess
->getRealAssertionsEnd()
182 == assertionsToPreprocess
->size());
183 Assert(!options::incrementalSolving());
184 Assert(!options::unsatCores());
186 context::Context fakeContext
;
187 TheoryEngine
* te
= d_preprocContext
->getTheoryEngine();
188 booleans::CircuitPropagator
* propagator
=
189 d_preprocContext
->getCircuitPropagator();
190 const booleans::CircuitPropagator::BackEdgesMap
& backEdges
=
191 propagator
->getBackEdges();
192 unordered_set
<unsigned long> removeAssertions
;
194 theory::TrustSubstitutionMap
& tlsm
=
195 d_preprocContext
->getTopLevelSubstitutions();
196 SubstitutionMap
& top_level_substs
= tlsm
.get();
198 NodeManager
* nm
= NodeManager::currentNM();
199 Node zero
= nm
->mkConst(Rational(0)), one
= nm
->mkConst(Rational(1));
200 Node trueNode
= nm
->mkConst(true);
202 unordered_map
<TNode
, Node
, TNodeHashFunction
> intVars
;
203 for (TNode v0
: d_boolVars
)
205 if (propagator
->isAssigned(v0
))
207 Debug("miplib") << "ineligible: " << v0
<< " because assigned "
208 << propagator
->getAssignment(v0
) << endl
;
212 vector
<TNode
> assertions
;
213 booleans::CircuitPropagator::BackEdgesMap::const_iterator j0
=
215 // if not in back edges map, the bool var is unconstrained, showing up in no
216 // assertions. if maps to an empty vector, that means the bool var was
218 if (j0
!= backEdges
.end())
220 if (!(*j0
).second
.empty())
222 traceBackToAssertions(propagator
, (*j0
).second
, assertions
);
226 assertions
.push_back(v0
);
229 Debug("miplib") << "for " << v0
<< endl
;
230 bool eligible
= true;
231 map
<pair
<Node
, Node
>, uint64_t> marks
;
232 map
<pair
<Node
, Node
>, vector
<Rational
> > coef
;
233 map
<pair
<Node
, Node
>, vector
<Rational
> > checks
;
234 map
<pair
<Node
, Node
>, vector
<TNode
> > asserts
;
235 for (vector
<TNode
>::const_iterator j1
= assertions
.begin();
236 j1
!= assertions
.end();
239 Debug("miplib") << " found: " << *j1
<< endl
;
240 if ((*j1
).getKind() != kind::IMPLIES
)
243 Debug("miplib") << " -- INELIGIBLE -- (not =>)" << endl
;
246 Node conj
= BooleanSimplification::simplify((*j1
)[0]);
247 if (conj
.getKind() == kind::AND
&& conj
.getNumChildren() > 6)
250 Debug("miplib") << " -- INELIGIBLE -- (N-ary /\\ too big)" << endl
;
253 if (conj
.getKind() != kind::AND
&& !conj
.isVar()
254 && !(conj
.getKind() == kind::NOT
&& conj
[0].isVar()))
257 Debug("miplib") << " -- INELIGIBLE -- (not /\\ or literal)" << endl
;
260 if ((*j1
)[1].getKind() != kind::EQUAL
261 || !(((*j1
)[1][0].isVar()
262 && (*j1
)[1][1].getKind() == kind::CONST_RATIONAL
)
263 || ((*j1
)[1][0].getKind() == kind::CONST_RATIONAL
264 && (*j1
)[1][1].isVar())))
267 Debug("miplib") << " -- INELIGIBLE -- (=> (and X X) X)" << endl
;
270 if (conj
.getKind() == kind::AND
)
273 bool found_x
= false;
274 map
<TNode
, bool> neg
;
275 for (Node::iterator ii
= conj
.begin(); ii
!= conj
.end(); ++ii
)
281 found_x
= found_x
|| v0
== *ii
;
283 else if ((*ii
).getKind() == kind::NOT
&& (*ii
)[0].isVar())
285 posv
.push_back((*ii
)[0]);
286 neg
[(*ii
)[0]] = true;
287 found_x
= found_x
|| v0
== (*ii
)[0];
293 << " -- INELIGIBLE -- (non-var: " << *ii
<< ")" << endl
;
296 if (propagator
->isAssigned(posv
.back()))
299 Debug("miplib") << " -- INELIGIBLE -- (" << posv
.back()
300 << " asserted)" << endl
;
311 Debug("miplib") << " --INELIGIBLE -- (couldn't find " << v0
312 << " in conjunction)" << endl
;
315 sort(posv
.begin(), posv
.end());
316 const Node pos
= NodeManager::currentNM()->mkNode(kind::AND
, posv
);
317 const TNode var
= ((*j1
)[1][0].getKind() == kind::CONST_RATIONAL
)
320 const pair
<Node
, Node
> pos_var(pos
, var
);
321 const Rational
& constant
=
322 ((*j1
)[1][0].getKind() == kind::CONST_RATIONAL
)
323 ? (*j1
)[1][0].getConst
<Rational
>()
324 : (*j1
)[1][1].getConst
<Rational
>();
326 unsigned countneg
= 0, thepos
= 0;
327 for (unsigned ii
= 0; ii
< pos
.getNumChildren(); ++ii
)
339 if ((marks
[pos_var
] & (1lu << mark
)) != 0)
342 Debug("miplib") << " -- INELIGIBLE -- (remarked)" << endl
;
345 Debug("miplib") << "mark is " << mark
<< " -- " << (1lu << mark
)
347 marks
[pos_var
] |= (1lu << mark
);
348 Debug("miplib") << "marks[" << pos
<< "," << var
<< "] now "
349 << marks
[pos_var
] << endl
;
350 if (countneg
== pos
.getNumChildren())
355 Debug("miplib") << " -- INELIGIBLE -- (nonzero constant)" << endl
;
359 else if (countneg
== pos
.getNumChildren() - 1)
361 Assert(coef
[pos_var
].size() <= 6 && thepos
< 6);
362 if (coef
[pos_var
].size() <= thepos
)
364 coef
[pos_var
].resize(thepos
+ 1);
366 coef
[pos_var
][thepos
] = constant
;
370 if (checks
[pos_var
].size() <= mark
)
372 checks
[pos_var
].resize(mark
+ 1);
374 checks
[pos_var
][mark
] = constant
;
376 asserts
[pos_var
].push_back(*j1
);
381 if (x
!= v0
&& x
!= (v0
).notNode())
385 << " -- INELIGIBLE -- (x not present where I expect it)" << endl
;
388 const bool xneg
= (x
.getKind() == kind::NOT
);
390 Debug("miplib") << " x:" << x
<< " " << xneg
<< endl
;
391 const TNode var
= ((*j1
)[1][0].getKind() == kind::CONST_RATIONAL
)
394 const pair
<Node
, Node
> x_var(x
, var
);
395 const Rational
& constant
=
396 ((*j1
)[1][0].getKind() == kind::CONST_RATIONAL
)
397 ? (*j1
)[1][0].getConst
<Rational
>()
398 : (*j1
)[1][1].getConst
<Rational
>();
399 unsigned mark
= (xneg
? 0 : 1);
400 if ((marks
[x_var
] & (1u << mark
)) != 0)
403 Debug("miplib") << " -- INELIGIBLE -- (remarked)" << endl
;
406 marks
[x_var
] |= (1u << mark
);
412 Debug("miplib") << " -- INELIGIBLE -- (nonzero constant)" << endl
;
418 Assert(coef
[x_var
].size() <= 6);
419 coef
[x_var
].resize(6);
420 coef
[x_var
][0] = constant
;
422 asserts
[x_var
].push_back(*j1
);
427 for (map
<pair
<Node
, Node
>, uint64_t>::const_iterator j
= marks
.begin();
431 const TNode pos
= (*j
).first
.first
;
432 const TNode var
= (*j
).first
.second
;
433 const pair
<Node
, Node
>& pos_var
= (*j
).first
;
434 const uint64_t mark
= (*j
).second
;
435 const unsigned numVars
=
436 pos
.getKind() == kind::AND
? pos
.getNumChildren() : 1;
437 uint64_t expected
= (uint64_t(1) << (1 << numVars
)) - 1;
438 expected
= (expected
== 0) ? -1 : expected
; // fix for overflow
439 Debug("miplib") << "[" << pos
<< "] => " << hex
<< mark
<< " expect "
440 << expected
<< dec
<< endl
;
441 Assert(pos
.getKind() == kind::AND
|| pos
.isVar());
442 if (mark
!= expected
)
444 Debug("miplib") << " -- INELIGIBLE " << pos
445 << " -- (insufficiently marked, got " << mark
446 << " for " << numVars
<< " vars, expected "
452 { // exclude single-var case; nothing to check there
453 uint64_t sz
= (uint64_t(1) << checks
[pos_var
].size()) - 1;
454 sz
= (sz
== 0) ? -1 : sz
; // fix for overflow
455 Assert(sz
== mark
) << "expected size " << sz
<< " == mark " << mark
;
456 for (size_t k
= 0; k
< checks
[pos_var
].size(); ++k
)
458 if ((k
& (k
- 1)) != 0)
461 Debug("miplib") << k
<< " => " << checks
[pos_var
][k
] << endl
;
462 for (size_t v1
= 1, kk
= k
; kk
!= 0; ++v1
, kk
>>= 1)
466 Assert(pos
.getKind() == kind::AND
);
468 << "var " << v1
<< " : " << pos
[v1
- 1]
469 << " coef:" << coef
[pos_var
][v1
- 1] << endl
;
470 sum
+= coef
[pos_var
][v1
- 1];
473 Debug("miplib") << "checkSum is " << sum
<< " input says "
474 << checks
[pos_var
][k
] << endl
;
475 if (sum
!= checks
[pos_var
][k
])
478 Debug("miplib") << " -- INELIGIBLE " << pos
479 << " -- (nonlinear combination)" << endl
;
485 Assert(checks
[pos_var
][k
] == 0)
486 << "checks[(" << pos
<< "," << var
<< ")][" << k
487 << "] should be 0, but it's "
489 [k
]; // we never set for single-positive-var
495 eligible
= true; // next is still eligible
499 Debug("miplib") << " -- ELIGIBLE " << v0
<< " , " << pos
<< " --"
501 vector
<Node
> newVars
;
502 expr::NodeSelfIterator ii
, iiend
;
503 if (pos
.getKind() == kind::AND
)
510 ii
= expr::NodeSelfIterator::self(pos
);
511 iiend
= expr::NodeSelfIterator::selfEnd(pos
);
513 for (; ii
!= iiend
; ++ii
)
515 Node
& varRef
= intVars
[*ii
];
519 ss
<< "mipvar_" << *ii
;
520 Node newVar
= nm
->mkSkolem(
523 "a variable introduced due to scrubbing a miplib encoding",
524 NodeManager::SKOLEM_EXACT_NAME
);
525 Node geq
= Rewriter::rewrite(nm
->mkNode(kind::GEQ
, newVar
, zero
));
526 Node leq
= Rewriter::rewrite(nm
->mkNode(kind::LEQ
, newVar
, one
));
527 TrustNode tgeq
= TrustNode::mkTrustLemma(geq
, nullptr);
528 TrustNode tleq
= TrustNode::mkTrustLemma(leq
, nullptr);
530 Node n
= Rewriter::rewrite(geq
.andNode(leq
));
531 assertionsToPreprocess
->push_back(n
);
532 TrustSubstitutionMap
tnullMap(&fakeContext
, nullptr);
533 CVC4_UNUSED SubstitutionMap
& nullMap
= tnullMap
.get();
534 Theory::PPAssertStatus status CVC4_UNUSED
; // just for assertions
535 status
= te
->solve(tgeq
, tnullMap
);
536 Assert(status
== Theory::PP_ASSERT_STATUS_UNSOLVED
)
537 << "unexpected solution from arith's ppAssert()";
538 Assert(nullMap
.empty())
539 << "unexpected substitution from arith's ppAssert()";
540 status
= te
->solve(tleq
, tnullMap
);
541 Assert(status
== Theory::PP_ASSERT_STATUS_UNSOLVED
)
542 << "unexpected solution from arith's ppAssert()";
543 Assert(nullMap
.empty())
544 << "unexpected substitution from arith's ppAssert()";
545 te
->getModel()->addSubstitution(*ii
, newVar
.eqNode(one
));
546 newVars
.push_back(newVar
);
551 newVars
.push_back(varRef
);
553 d_preprocContext
->enableIntegers();
556 if (pos
.getKind() == kind::AND
)
558 NodeBuilder
<> sumb(kind::PLUS
);
559 for (size_t jj
= 0; jj
< pos
.getNumChildren(); ++jj
)
562 kind::MULT
, nm
->mkConst(coef
[pos_var
][jj
]), newVars
[jj
]);
569 kind::MULT
, nm
->mkConst(coef
[pos_var
][0]), newVars
[0]);
571 Debug("miplib") << "vars[] " << var
<< endl
572 << " eq " << Rewriter::rewrite(sum
) << endl
;
573 Node newAssertion
= var
.eqNode(Rewriter::rewrite(sum
));
574 if (top_level_substs
.hasSubstitution(newAssertion
[0]))
576 // Warning() << "RE-SUBSTITUTION " << newAssertion[0] << endl;
577 // Warning() << "REPLACE " << newAssertion[1] << endl;
578 // Warning() << "ORIG " <<
579 // top_level_substs.getSubstitution(newAssertion[0]) << endl;
580 Assert(top_level_substs
.getSubstitution(newAssertion
[0])
583 else if (pos
.getNumChildren() <= options::arithMLTrickSubstitutions())
585 top_level_substs
.addSubstitution(newAssertion
[0], newAssertion
[1]);
586 Debug("miplib") << "addSubs: " << newAssertion
[0] << " to "
587 << newAssertion
[1] << endl
;
592 << "skipSubs: " << newAssertion
[0] << " to " << newAssertion
[1]
593 << " (threshold is " << options::arithMLTrickSubstitutions()
596 newAssertion
= Rewriter::rewrite(newAssertion
);
597 Debug("miplib") << " " << newAssertion
<< endl
;
599 assertionsToPreprocess
->push_back(newAssertion
);
600 Debug("miplib") << " assertions to remove: " << endl
;
601 for (vector
<TNode
>::const_iterator k
= asserts
[pos_var
].begin(),
602 k_end
= asserts
[pos_var
].end();
606 Debug("miplib") << " " << *k
<< endl
;
607 removeAssertions
.insert((*k
).getId());
613 if (!removeAssertions
.empty())
615 Debug("miplib") << " scrubbing miplib encoding..." << endl
;
616 for (size_t i
= 0, size
= assertionsToPreprocess
->getRealAssertionsEnd();
620 Node assertion
= (*assertionsToPreprocess
)[i
];
621 if (removeAssertions
.find(assertion
.getId()) != removeAssertions
.end())
623 Debug("miplib") << " - removing " << assertion
<< endl
;
624 assertionsToPreprocess
->replace(i
, trueNode
);
625 ++d_statistics
.d_numMiplibAssertionsRemoved
;
627 else if (assertion
.getKind() == kind::AND
)
629 size_t removals
= removeFromConjunction(assertion
, removeAssertions
);
632 Debug("miplib") << " - reduced " << assertion
<< endl
;
633 Debug("miplib") << " - by " << removals
<< " conjuncts" << endl
;
634 d_statistics
.d_numMiplibAssertionsRemoved
+= removals
;
637 Debug("miplib") << "had: " << assertion
[i
] << endl
;
638 assertionsToPreprocess
->replace(
639 i
, Rewriter::rewrite(top_level_substs
.apply(assertion
)));
640 Debug("miplib") << "now: " << assertion
<< endl
;
645 Debug("miplib") << " miplib pass found nothing." << endl
;
647 assertionsToPreprocess
->updateRealAssertionsEnd();
648 return PreprocessingPassResult::NO_CONFLICT
;
651 MipLibTrick::Statistics::Statistics()
652 : d_numMiplibAssertionsRemoved(
653 "preprocessing::passes::MipLibTrick::numMiplibAssertionsRemoved", 0)
655 smtStatisticsRegistry()->registerStat(&d_numMiplibAssertionsRemoved
);
658 MipLibTrick::Statistics::~Statistics()
660 smtStatisticsRegistry()->unregisterStat(&d_numMiplibAssertionsRemoved
);
664 } // namespace passes
665 } // namespace preprocessing