-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multithreading Propagators #166
Comments
Looks like a problem with interface and implementation. With the current implementation, you should not add literals during initialization. On the other hand, the interface does not allow you to detect that the system is currently initializing. Right now, it should be safe to add literals when propagate is called above the root level. Some possible fixes:
@BenKaufmann what do you think? |
@rkaminsk |
I put some code below where I think it should be okay to add variables. Maybe @MaxOstrowski can already work with this? void Propagator::propagate(PropagateControl &ctl, LiteralSpan span) {
auto a = ctl.assignment();
if (a.decision_level() > a.root_level()) {
// here it should be safe to add variables
}
else if (a.decision_level() > 0) {
// is it safe to add variables here?
}
else {
// here it is not safe to add variables
// because level 0 might be propagated during initialization
}
} |
@rkaminsk Your |
Thanks. I am still a bit puzzled about the whole business of adding variables because I never wrote a propagator using this interface. Adding variables on level zero sounds like something one would want to do in a preprocessing step. Maybe adding a method to add a (static) variable in PS: Since we have the interface, it would be great to also be able to add volatile variables during propagate on level 0. But I am just saying this for the sake of having an easy to use interface; I don't have actual use cases in mind. |
I added a commit to clasp's dev branch that hopefully fixes this issue. To this end, I slightly postpone the first propagate/check call until after the problem is actually frozen. This way, propagators can now safely add aux variables even on decision level 0 and these aux variables are correctly treated as volatile and solver-local. |
I updated clasp in clingo's wip branch. Up-to-date binaries are available on anaconda in potassco's dev channel and on our university computers. @MaxOstrowski please test. I was thinking to also add an add_literal to clingo's |
@rkaminsk Nope. You cannot use To add a new static variable, you would have to call |
Can
|
Yes, |
That should be easy enough to implement. The only annoying thing is that Edit: Or I add all clauses later just as you are suggesting and return simply return true. The return value can be removed in the next minor release. This has the advantage that the assignment does not change during propagator initialization. As a result, the interface will be easier to use. |
@BenKaufmann: I updated the interface in 45327d5. Can you please have a look if this makes sense? |
Hi guys, thanks a lot for your efforts. I will test this. I'm not quite sure clingcon could avoid adding variables on decision level 0.
contains only facts and therefore the assignment is complete. To enumerate solutions, clingcon introduced e.g. "x<=5" as a free variable to find an assignment for x during the "check" callback. There exist also other cases, like propagation that adds clauses on not yet existing variables.
If preprocessing makes "a" true, the initial propagation should add a clause |
OK, I tried this new commit and my system is broken with the new behaviour even with one thread as I relied on some assumptions I think. What triggers my assertions is the following scenario: |
@MaxOstrowski ideally you would provide a small example to reproduce. Per se the behavior does not sound wrong. If you add a unit clause, the solver might have to backtrack to actually incorporate it. Also note that "no further calls on a control object or functions on the assignment should be called when the result of propagate is false." I did not explicitly write it, but the same should hold true if add_clause returns false. |
Sorry, my bad. I meant a clause with exactly one free literal. All other literals are false. The decision level (of everything) is 0. I will work on an example and get back to you. Thanks. And yes, neither propagate nor add_clause returned false of course. |
In this case it really depends on what is implemented in clasp. I could imagine that it is possible to delay propagation until the check call has returned. You could try with a small example yourself but I think we also need some info from @BenKaufmann. |
Here is my guess what happens: The clauses you add are volatile. This means that clasp has to tag them with the step literal Ts for the current step s. A unit clause {x} therefore becomes {x, ~Ts} behind the scenes. Since we are logically still in "preparation mode" (i.e. solving of the current step has not yet started), Ts is not yet assumed to be true. Hence, {x ~Ts} is (not yet) a unit-resulting clause. This used to "work" (for some broken definition of work) because previously Ts was still equal to the unique True literal at this point, meaning that your volatile clause actually became static. |
I just finished my example test propagator for this. And this is exactly the behaviour. One additional literal (2) is introduced but not yet assigned. This is probably the step literal. |
To me it makes total sense to not imply volatile literals on level zero because these literals are retracted at the end of a solving step. It sounds like you want to add literals that are not volatile so that you can add static clauses over them. You can do this during initialization in |
Exactly this would be my Plan B 🥈 I came up with a little test propagator and found a weird behaviour. After adding a variable in the "init" callback i do get an error when trying to retrieve its truth value. A unit propagation method during the init would be awesome 👍 , as much more knowledge could be made static. Especially if you combine several propagators that only do a translational approach. |
With the current implementation it is the right thing to happen. Let's see. If it is possible to do a unit propagation, then you could access its truth value after propagation. |
|
I rather think it's about documenting what is to happen.
I think that the answer is to delay propagation until after initialization. That's why I said that post propagation should not be called. Just unit propagation. This will keep things simple. |
|
Sorry for taking so long. After fixing some raid conditions I can approve that my tests now also run in multi-threading mode. Thanks. |
Thanks. Let's wait for @BenKaufmann feedback about the propagation before we close this. And no hurry. 😄 I am a bit skeptical because we want to do something here clasp has not been designed for. We want to have volatile variables as in propagation during the initialization phase; with the difference that those variables should become static in the end. |
Could you please summarize which solution you would prefer in the end? From the clasp-side of things, I guess it would be relatively straightforward to postpone calls to Anyhow, let's first clearly specify what is needed/wanted. |
I think @MaxOstrowski (correct me if I am wrong/or refine) want's to propagate the clauses he is adding during initialization right away. These clauses might refer to newly introduced variables. Each propagation might reduce the size of clingcon's domains reducing the number of order variables that have to be introduced while going on. Here some pseudo-code: def init(self, init):
for atom in init.theory_atoms:
# how many clauses and literals are added depends on the assignment
# and internal state of the propagator
self.add_literals_clauses_and_watches(init, atom)
# could propagate anything as long as it does not call back into this propagator
# I was suggesting clasp's unit propagation
init.propagate() |
@rkaminsk |
The backend cannot be used during initialization, you are stuck with clauses (clasp's weight constraints could also be made available). If you want to use the backend, then we would need incremental propagation on the logic program. And this is a whole other topic. |
Actually the backend is not needed but adding weight constraints and atoms to the minimize statement would be all I need. Shall I will open a feature request? |
It should also be possible to add literals to the minimize constraint - during initialization but not while solving. I'll open another issue to track the implementation progress once we came to a conclusion what to actually implement. |
Perfect! Btw: would it make sense to automagically call ctl.cleanup() on every ground() call ? This way gringo can profit without any user interaction. Or is there any reason not to do so. |
To me it looks like you can already get started by using the backend. It allow you to add clauses, weight constraints, and minimize constraints. It just offers less preprocessing. I am saying this because it will probably take some time to implement the above. It should be possible to write the code so that it can be easily ported.
It could be called after every solve call. It should be reasonably fast in practice (linear in the size of gringo's atom base). It is not called be automatically to leave it up to the user whether to simplify or not. |
Currently (the old clingcon version and still the current wip) does exactly this. It uses the backend to do all preprocessing, domain shrinking etc.. |
You might get facts. There is just no way to determine this with the current interface (without using a program observer). I can check if it is possible to add an |
If weight constraints and minimize atoms work during init i'm fine. |
Yes, assignments on level 0 cannot be undone, and literals and constraints added during init are static. |
👯♀️ |
@rkaminsk I slightly extended the capability of This should allow you to immediately add all clauses (thereby simplifying 45327d5) and also to add a Finally, please note that while the new |
OK, i found out that I only postponed the problem with the current fix. My new question is now regarding multi-shot solving. Hope that makes any sense ? |
This issue is already too convoluted. Please open a separate issue for unrelated questions. The development of the PropagateInit interface will be tracked in #183. |
I have a problem running my propagator with several threads.
During ClingoControl::prepare my propagator's ::propagate and ::check methods are called.
In some examples I do use PropagateControl::add_literal to add new literals.
As far as I understand, these literals are thread specific. Since prepare is only executed on the master solver only there these literals are added. So it would be bad if these literals are thread specific and not copied to the other solvers. (We would have clauses talking over literals that are not in the new threads)
When starting the multi-threaded solve operation (ParallelSolve::solveParallel) the threads are copied, but it seems that the variables added via PropagateControl::add_literal are not copied.
While doing SharedContext::attach the master trail is forced on the new solver and it tries to assign these illegal variables (resulting in an array out of bounds exception (solver_types.h ll. 674)).
PS: Still need to work on a showcase example, but maybe you already have some thoughts.
The text was updated successfully, but these errors were encountered: