Open soonhokong opened 8 years ago
FYI, I added ctest label for --multiheuristic
and --multiprune
options. For example, try ctest -R nra_multiheuristic --output-on-failure
in your build directory after building dReal. More information about ctest options is available at https://cmake.org/cmake/help/v3.0/manual/ctest.1.html.
So I've tried a couple things, and I've been able to reproduce the issue, even when there's no data being shared between the threads (i.e. the common hull is not accessed or modified by either thread); essentially, the threads are just running independently (see clhuang/dreal3:bugfix), up until the point that one finishes.
Despite the apparent independence of the threads, it does seem like their behavior is linked somehow--if two different heuristics are used, the bug is reproducible; if the SizeBrancher is used for both, the bug does not appear. Or if there's only one heuristic used, the bug does not appear.
My only guess would be that there is some state in the contractor, which is shared between the heuristics, that cause some issues when doing subsequent contractions...Is there any way to completely copy a contractor, to see if not sharing a contractor makes a difference?
My only guess would be that there is some state in the contractor, which is shared between the heuristics, that cause some issues when doing subsequent contractions...Is there any way to completely copy a contractor, to see if not sharing a contractor makes a difference?
You're right, contractor
class has a state which can be changed by calling prune
method. Internally (in contractor_cell
), I have a way to save/restore a state. I'll expose them as public API of contractor
class so that you can use it. I'll ping you when it's ready.
I'm pretty sure it's a contractor issue at this point. I've tried it on 13.smt, which has a solution at X=2.40, Y=0.675, and when printing out the X and Y ranges (BEFORE/AFTER refer to the state of the box before and after the contractor call, and the following number is the thread number):
BEFORE0 X: [2.20008, 2.51301] Y: [0.632766, 0.697291] AFTER0 X: [2.36998, 2.45647] Y: [0.66861, 0.686114] BEFORE1 X: [2.20008, 2.51301] Y: [0.632766, 0.697291] AFTER1 X: [2.36998, 2.45647] Y: [0.66861, 0.686114] BEFORE0 X: [2.36998, 2.41323] Y: [0.66861, 0.686114] AFTER0 X: [nan, nan] Y: [0.66861, 0.686114] BEFORE0X : [2.41323, 2.45647] Y: [0.66861, 0.686114] AFTER0 X: [nan, nan] Y: [0.66861, 0.686114] NO SOLUTION FOUND 0 BEFORE1 X: [2.36998, 2.41323] Y: [0.66861, 0.686114] AFTER1 X: [2.38546, 2.40926] Y: [0.671778, 0.676619] unsat
and in the bolded portion, the contractor eliminates the solution, leading to an erroneous unsat.
To avoid this issue by saving/restoring contractor state, that would mean we'd have to save/restore contractor state every time it's used, is that practical?
To avoid this issue by saving/restoring contractor state, that would mean we'd have to save/restore contractor state every time it's used, is that practical?
It's not ideal, but the size of this state is quite small. Two bit-vectors of size N and one set of pointers. Let's see how big the performance penalty this change can make.
@clhuang, I added another function to src/icp/icp.cpp
in cf6c419:
// Prune a given box b using ctc, but keep the old state of ctc void test_prune(box & b, contractor & ctc, SMTConfig & config)
It's a derivation of the function that you used to call, void prune(box & b, contractor & ctc, SMTConfig & config, std::unordered_set<std::shared_ptr<constraint>> & used_constraints)
. The difference is that 1) test_prune
doesn't take std::unordered_set<std::shared_ptr<constraint>> &
anymore, and 2) it keeps the internal state of a given argument contractor & ctc
.
Please try this one, and let me know if you find further problem or need something else. Thank you.
Also, please feel free to add more assert
in the code, or testcases under src/tests
.
It still returns unsat I think there's some more state in contractor, beyond the output bit vector and the used constraints, because if you look at contractor_common.h it seems that the bitvector and the used_constraints get cleared no matter what. Is it possible there's more state involved, say in the sub-contractors of a contractor_seq for example?
@clhuang, you're right. I think I need to introduce test_prune
to all the subclasses of contractor_cell
. I'll do that soon.
@soonhokong Rather than having a state in the contractor, would it make more sense to make the contractor as pure as possible, i.e., modulo IBEX, and carry any state used by the contractor as part of a state monad (or something along those lines) that is explicitly passed as argument. The SMTConfig object is already carried around. Could we generalize that to into an object that carries any state needed by the contractor. It seems that we need such an object. The clause manager for the learning, or keeping track of the proof would also benefit from it.
@dzufferey, I see. I agree that this is better and fits better with what we've discussed before (clause manager, proof objects).
@soonhokong, @dzufferey, are there any current efforts to refactor the contractor?
Not right now.. I'll do it after Thursday.
I'm on it now. For now, contractor_status
will include b : Box
, output : ibex:BitSet
, and used_constraints : set of constraints
. Later, I'll add clause_manager
and proof_manager
to integrate learning and proof generation.
@clhuang and @dzufferey:
I've just pushed refactored contractor code to the master
branch. Here are some comments:
--multiprune
is working but --multiheuristic
is not working yet. (anyway, we need to solve the thread-safety issue first). For now, I've commented out the implementation of --multiheuristic
.gradout
is set to be an empty interval. This case was not covered previously. For now, this case adds 0
to the score. If you have a better patch, please feel free to add that.first
or second
is empty when we add them to the stack. But I think we can have a better version. If you have an idea, please share it (or fix it).@scungao: I'll rebase dev
branch tomorrow. It seems that there are more things to do for that.
Thanks @soonhokong
When I have some spare cycles, I'll start looking into how to adapt the proof/interpolation infrastructure to the new contractors.
About the multiprune
, checking before pushing on the stack avoids putting empty boxes on the stack that should make it a little bit faster. For the loop doing the branches, it should be correct even if the boxes are empty. hull
and intersect
are defined on empty boxes.
Looking again at the code, I can think of two additional optimizations:
score
is 0.0
, i.e., first and second are both empty. Right now, it continues to try the other branches even if it is already empty.on line 162, instead of bisecting original, we can bisect the hull of first
and second
. The code would look something like:
original = first;
original.hull(second);
tuple<int, box, box> const splits = original.bisect_at(dim);
then we can remove the hull operation on line 176.
@dzufferey, thanks for the comment. I'll make a patch, open a pull-request. Please review the PR when it's ready.
how to adapt the proof/interpolation infrastructure to the new contractors.
Great. I'll make a proof-object class and re-introduce what we had in dReal2. We can start from there and improve things.
@clhuang and @dzufferey, could you take a look at this problem when you have some time?
I have been testing
--multiheuristic
option with hard instances. I found that using the option changes the return value (delta-sat/unsat) from a run to another run. Here is an example:Another symptom is that sometimes dReal returns delta-sat with a witness box (B). However, the width of the box |B| is larger than the given precision. Here is an example:
Here the variable
ball_diameter
has quite a large interval [0.15, 0.22330625] while 0.00001 was given as a precision. Here isinv_pend.smt2
file that I used:My environment is: