Warning: ASP newbie.
Let's assume we have this simple program:
% crime scene
% Facts
present(harry). % was present on the crime scene
present(sally).
present(mary).
motive(harry). % has a motive
motive(sally).
guilty(harry).
% encoding / rules
innocent(Suspect) :- motive(Suspect), not guilty(Suspect).
witness(Suspect) :- present(Suspect), not motive(Suspect), not guilty(Suspect).
The output, as expected, is:
present(harry) present(sally) present(mary)
motive(harry) motive(sally) guilty(harry)
innocent(sally) witness(mary)
Is there a way to only compute a specific rule out of all the defined ones? I'm not talking about hiding an atom with the #show
statement but really telling the solver to only compute the witness(Suspect)
rule for example.
I assume writing two different programs is an answer but is there anything else?
For context, I use the Clingo Python API to execute an ASP program that contains dozens of rules. Those (independent) rules correspond to questions the user might want an answer to. For now, all of them are computed when executing the program and I filter the output to only process the atoms of interest once an answer is found.
I found the answer in the paper "A Tutorial on Hybrid Answer Set Solving with clingo" by Kaminski et al. (2017) [Archive here].
Use the #program
directive to partition a program into subprograms (blocks of rules). Then use scripting to only execute the subprogram you want.
Concretely:
% crime scene
% Facts
#program base. % NEW
present(harry).
present(sally).
present(mary).
motive(harry).
motive(sally).
guilty(harry).
% encoding / rules
#program groundInnocent. % NEW
innocent(Suspect) :- motive(Suspect), not guilty(Suspect).
#program groundWitness. % NEW
witness(Suspect) :- present(Suspect), not motive(Suspect), not guilty(Suspect).
Then, you can either include a script in the ASP code:
#script(python)
def main(prg):
prg.ground([("base", []), ("groundWitness", [])])
prg.solve()
#end
Or use the Clingo Python API:
from clingo.control import Control
def on_model(model):
print ("result = ", model)
if __name__ == '__main__':
ctl = Control()
ctl.configuration.solve.models = 1
ctl.load("path_to_asp_file")
ctl.ground([("base", []), ("groundWitness", [])])
ctl.solve(on_model=on_model)
Important:
base
subprogram. And the base program is explicitly called before any other subprogram to ensure facts used in witness
are grounded.witness(Suspect) :- present(Suspect), not innocent(Suspect).
, the rule would not be grounded since innocent
is outside the scope of the groundWitness
subprogram and is not part of base
either.See long answer below to understand why.
Direct citation from section 3.1 ("A gentle introduction") of the paper mentioned above:
[...] a program can be partitioned into several subprograms by means of the directive
#program
; it comes with a name and an optional list of parameters. [...] As an example, two subprograms base and acid(k) can be specified as follows:a(1). #program acid(k). b(k). c(X,k) :- a(X). #program base. a(2).
Note that
base
is a dedicated subprogram (with an empty parameter list): in addition to the rules in its scope, it gathers all rules not preceded by any#program
directive. Hence, in the above example, the base subprogram includes the factsa(1)
anda(2)
, although, only the latter is in the actual scope of the directive [...].Without further control instructions (see below), clingo grounds and solves the base subprogram only [...]. The processing of other subprograms such as
acid(k)
is subject to scripting control.For customized control over grounding and solving, a main routine (taking a control object representing the state of clingo as argument) can be supplied.
#script(python) def main(prg): prg.ground([("base",[])]) prg.solve() #end.
While the above control program matches the default behavior of clingo, the one below ignores all rules in the base program but rather contains a ground instruction for
acid(k)
[...], where the parameterk
is to be instantiated with the term 42.#script(python) def main(prg): prg.ground([("acid",[42])]) prg.solve() #end.
Accordingly, the schematic fact
b(k)
is turned intob(42)
, no ground rule is obtained fromc(X,k) :- a(X)
due to lacking instances ofa(X)
, and the solve command [...] yields a stable model consisting ofb(42)
only.Note that ground instructions apply to the subprograms given as arguments, while solve triggers reasoning w.r.t. all accumulated ground rules.