What We Don’t Support
In constructing ASPAlchemy, we tried to make sure that some of the rough edges of ASP were sanded down and hidden from users. These largely fall into two camps. The first consists of features that we actively chose not to support, while the second consists of clingo syntax that is valid but misleading, and typically leads to unexpected behavior or correctness issues. There’s also one big feature we just haven’t built yet, multi-shot solving. This page lists them all, and our reasoning for each.
Most things refused here can still be written as plain clingo text with raw_asp(), see
Escape Hatches.
Excluded by philosophy
In this section, we discuss the features that have been left out on purpose.
Disjunctive heads (a ; b :- c.). While this looks like a simple way to say “this or that”, it
actually means something subtler (the smallest set of atoms that works), and can make your program
much harder to solve. Choice usually says what you meant: Choice(...) >= 1 for at least one, or
== 1 for exactly one. If you know that you really need a disjunctive head, then raw_asp() covers
you.
Anonymous tuples (p((1, 2))). These are valid clingo, but nothing says what the positions in a
tuple mean, so there’s no way to give them types. Using one gives an error, whether you write it
yourself or it comes back from a raw block, suggesting you use a named predicate instead
(pair(1, 2) rather than (1, 2)). A nested predicate field produces the same term, but with
types and readable output; see Declaring predicates.
Theory atoms (&diff { ... }). These belong to clingo’s extensions, like clingo-dl and
clingcon, which each have their own syntax and solving machinery. ASPAlchemy doesn’t model any of
that. You can still get at the underlying clingo.Control through grounded.control; see
Solver options and the raw Control.
#include, #program and #external. You don’t need #include, because your program is
Python: imports, functions and loops are how you organize it. #program and #external belong to
clingo’s incremental solving, which isn’t supported
yet. All three are refused inside raw_asp() too
(Blocked directives).
Scripting and @-functions aren’t refused, but they aren’t part of the typed layer either. You
can use @f(X) terms and #script blocks inside raw_asp() text, and ground(context=...)
supplies the Python object that the calls go to. See
Calling Python during grounding.
Deliberate strictness
Everything in this section is valid clingo, but we refuse it anyway, because it’s almost always a mistake. Each error tells you what to write instead.
Negated heads. not p :- body. doesn’t derive anything; clingo turns it into the constraint
:- body, p. It looks like it derives something, so ASPAlchemy refuses it and asks you to write
forbid(*body, p) instead.
_X variables. Clingo accepts variable names that start with an underscore, usually to mark a
variable you don’t care about. ASPAlchemy has one way to say that: ANY, which renders as _. Any
other variable name has to start with a capital letter.
Singleton variables. A variable that’s only used once in a rule is usually a typo, so it’s an
error here (even though clingo doesn’t say anything). You can turn this check off with
ASPProgram(allow_singletons=True).
A body variable that’s only bound inside a choice’s condition. In
{ h(W) : p(W) } :- W > 1., the W in the body isn’t bound by anything in the body: p(W) only
binds W inside its own element. Clingo happens to accept this particular rule, but rejects it as
soon as you add a bound or a second element. It’s much better to just put the filter in the
element’s condition instead: Choice(H(a=W), condition=[P(a=W), W > 1]).
Negated pool comparisons. not X = (2; 3) doesn’t mean “not in”. A pool comparison is true if
any member matches, so the negated form is true whenever X differs from any member, which is
always. The error tells you what to write instead:
>>> from aspalchemy import Not, Variable
>>> X = Variable("X")
>>> Not(X.in_((2, 3)))
Traceback (most recent call last):
...
ValueError: Negating a pool comparison does not mean 'not in': pools expand disjunctively, ... Write separate conditions instead, e.g. X != 2, X != 3
For the same reason, require() refuses pool comparisons: require() works by negating its
condition, and a pool comparison can’t be negated.
Equations with a variable on both sides. X = -X and X * 3 = X + 10 are refused as unsafe.
An equality can bind a variable on one side once the other side is known, but ASPAlchemy doesn’t
solve equations. Clingo can solve some of these, but the program is clearer if you just write down
the answer (X = 0, X = 5).
Braces in rule bodies. 2 { p(X) } 4 in a body is a cardinality test, not a choice; it looks
the same, but means something different. Write tests as Count comparisons, which ground the same
way. There’s an example in
Cardinality tests are not choices.
Plain solve() on a program with an objective. This is refused, rather than listing answer sets
that the objective was supposed to rank. Use optimize() to find the best answer set, pass
cost_bound= to list every answer set within a cost, or pass ignore_optimization=True to
explicitly ignore the objective. See Optimization.
Multi-shot solving: a future design project
Clingo can solve a program incrementally: ground some #program parts, solve, ground some more onto
the same solver, and solve again. This is common in planning problems. ASPAlchemy doesn’t support
this yet; it always grounds the whole program at once, and raw_asp() refuses #program.
Supporting it properly is a big design job. A GroundedProgram is currently a snapshot that always
solves the same program, and incremental solving would change that. There are also open questions
about how segments map to parts, what part parameters could look like in Python, and how to handle
external atoms.
In the meantime, ground() plus assumptions lets you ask one
program lots of questions cheaply, which covers a good part of what people use incremental solving
for. And segment groups already split a program into separately
rendered files, which is the shape #program parts would need.