Closed cgd8d closed 8 years ago
It's related to the previous problem. In short, we need to do pre-processing and only handle a conjunction of theory literals inside of find_CE_via_underapprox
.
Thanks. I'm sorry if I keep filing different forms of the same issue, but I'm not sure I understand in this case what the preprocessing should generate. In #272 you mentioned problems should be provided in CNF form; I think this problem is already in CNF, since it consists of only disjunctions.
I believe I’ve found a patch which addresses this issue, though it’s probably not the best one. It does two things.
First, flattens long sequences of ORs, so that handle_disjunction is not presented with an enode which is itself an OR. (This can happen even for expressions which are already in CNF form, as I believe the current example shows.)
Second, for this input ibex’s ibwd operation returns an interval which is larger than the input argument, which I think is unintended behavior from ibex. The patch works around that by explicitly enforcing the behavior that in find_CE_via_underapprox, ibwd should never enlarge counterexample.
The patch I’ve written is:
diff --git a/src/contractor/contractor_generic_forall.cpp b/src/contractor/contractor_generic_forall.cpp
index e6db4dc..13e559b 100644
--- a/src/contractor/contractor_generic_forall.cpp
+++ b/src/contractor/contractor_generic_forall.cpp
@@ -383,6 +383,7 @@ box find_CE_via_underapprox(box const & b, unordered_set<Enode*> const & forall_
}
auto numctr = ctr.get_numctr();
// Construct iv from box b
+ ibex::IntervalVector iv_copy = iv;
if (numctr->op == ibex::CmpOp::GT || numctr->op == ibex::CmpOp::GEQ) {
numctr->f.ibwd(ibex::Interval(0.0, POS_INFINITY), iv);
} else if (numctr->op == ibex::CmpOp::LT || numctr->op == ibex::CmpOp::LEQ) {
@@ -394,6 +395,7 @@ box find_CE_via_underapprox(box const & b, unordered_set<Enode*> const & forall_
ss << "find_CE_via_underapprox: unknown constraint type, " << *numctr;
throw runtime_error(ss.str());
}
+ iv &= iv_copy;
if (iv.is_empty()) {
// stop when counterexample is already empty;
return counterexample;
@@ -458,6 +460,23 @@ void contractor_generic_forall::handle_disjunction(contractor_status & cs, vecto
forall_vars.insert(vars.begin(), vars.end());
}
+ // If any of the enodes in vec is an OR, break it apart and call this function recursively.
+ for(size_t i = 0; i < vec.size(); i++) {
+ if(vec.at(i)->isOr()) {
+ assert(!vec.at(i)->getCdr()->isEnil());
+ assert(!vec.at(i)->getCdr()->getCdr()->isEnil());
+ assert( vec.at(i)->getCdr()->getCdr()->getCdr()->isEnil()); // Verify vec.at(i) has exactly two arguments to OR.
+ Enode* e1 = vec.at(i)->getCdr()->getCar();
+ Enode* e2 = vec.at(i)->getCdr()->getCdr()->getCar();
+ std::vector<Enode*> local_vec = vec;
+ local_vec[i] = e1;
+ local_vec.push_back(e2);
+ handle_disjunction(cs, local_vec, p);
+ return;
+ }
+ }
+
+ // vec does not contain any nested OR terms; continue.
unordered_map<Enode*, ibex::Interval> subst;
if (!forall_vars.empty()) {
// Step 2. Find a counter-example
Does this seem like the right approach to patching this issue?
@cgd8d, thanks for the patches. I'll take a closer look at them and merge them into the repository.
Could you give us a small example where ibex contractor violates monotonicity property? I can take a look and possibly fix the problem on ibex side (or report the problem to the team).
Applying the patch so that nested ORs are traversed (the part of the diff which is applied to handle_disjunction), I found that the input at the top of the thread did exactly that. Maybe this could be confirmed by replacing "iv &= iv_copy" in the patch with an assertion that iv is a subset of iv_copy?
@cgd8d, sorry for the delay. I'll check it soon.
@cgd8d, I'll do the flattening at the parsing time so that the rest do not need to worry about it.
Closed by 4ac1aa86048d144c488aa9cdd2c001a465ac1d5d. Related Issue: #284.
@cgd8d, please check out the latest source code and try the example at the top. When I tested it, it returned unsat
which is the correct answer.
I still have a concern with your comment on IBEX:
Second, for this input ibex’s ibwd operation returns an interval which is larger than the input argument, which I think is unintended behavior from ibex.
If this is still the case, I'd like to fix it. Could you give me a reproducible example? For instance, with a box B1
and a constraint C1
, IBEX returns B2
which is not included in B1
.
@soonho-tri , thanks for tackling the flattening issue! Unfortunately unsat
is not the right answer -- the problem is satisfied, for example, by x = 1.5
.
To trigger an appropriate assertion, try applying the following diff (tested against 5ba4d8a):
diff --git a/src/contractor/contractor_generic_forall.cpp b/src/contractor/contractor_generic_forall.cpp
index e6db4dc..9720a6b 100644
--- a/src/contractor/contractor_generic_forall.cpp
+++ b/src/contractor/contractor_generic_forall.cpp
@@ -384,7 +384,9 @@ box find_CE_via_underapprox(box const & b, unordered_set<Enode*> const & forall_
auto numctr = ctr.get_numctr();
// Construct iv from box b
if (numctr->op == ibex::CmpOp::GT || numctr->op == ibex::CmpOp::GEQ) {
+ ibex::IntervalVector iv_copy = iv;
numctr->f.ibwd(ibex::Interval(0.0, POS_INFINITY), iv);
+ assert(iv.is_subset(iv_copy));
} else if (numctr->op == ibex::CmpOp::LT || numctr->op == ibex::CmpOp::LEQ) {
numctr->f.ibwd(ibex::Interval(NEG_INFINITY, 0.0), iv);
} else if (numctr->op == ibex::CmpOp::EQ) {
Run on the testcase above, this triggers an assertion. Is this enough to reproduce?
Oh, I see. I'll check that.
I think that the current behavior of IBEX is correct according to the documentation http://www.ibex-lib.org/doc/interval.html?highlight=backward%20operator#backward-operators. It's just the case that we need to take an intersection between the result and the input. I just added a patch aee385f73a02e252112fe2c305d1e38192699930. After applying the patch, it should return delta-sat for the example.
OK, that's great -- thanks!
Input:
Output:
This result is incorrect (eg. by selecting y = 0.75); x should be in the range [1, 2] for the problem to be satisfied.
I think the reason is partly because of the nested ORs, which the function “find_CE_via_underapprox” doesn’t traverse. The following patch should fix that part of the issue, but is not a complete fix; it changes the result from an incorrect solution to unsat, which is still wrong. The partial patch I’ve written is:
I’m unsure why the code, patched this way, still reports unsat rather than a correct solution.