I found the answer in the paper "A Tutorial on Hybrid Answer Set Solving with clingo" by Kaminski et al. (2017) [Archive here].
Short answer
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:
- Note that facts are put in the
base
subprogram. And the base program is explicitly called before any other subprogram to ensure facts used in witness
are grounded.
- This implies that subprograms must be independent. Here, both of our rules are independent. If we had
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.
Long answer
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 facts a(1)
and a(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
parameter k
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 into b(42)
, no
ground rule is obtained from c(X,k) :- a(X)
due to lacking instances
of a(X)
, and the solve command [...] yields a stable model
consisting of b(42)
only.
Note that ground instructions apply to the subprograms given as
arguments, while solve triggers reasoning w.r.t. all accumulated
ground rules.