Clingo to ASPAlchemy

If you already know clingo, this page shows how each thing you’d write in clingo is written in ASPAlchemy, or that it isn’t supported, with a link to why. Each row links to the page that covers it properly. The Verified against clingo section at the end runs the important examples, so this page can’t quietly fall out of date. For what clingo’s constructs actually mean, the Potassco guide is the place to go; this page is only about how to spell them.

Unsupported constructs are marked refused. What We Don’t Support explains why, and Escape Hatches shows how to use them anyway with raw_asp().

Terms and literals

clingo ASPAlchemy details
X, Node (variables) Variable("X"), or V.X inline Variables
_ (don’t care) ANY Variables
_X (named don’t-warn variable) refused: use ANY Deliberate strictness
42 a Python int (Number(42) if you need it explicitly) Constants and extremes
"hello" a Python str, which always means a quoted ASP string One text type
n (symbolic constant) a zero-arity predicate: Predicate.define("n", [])() Bare atoms
1..5 RangePool(1, 5) or pool(range(1, 6)) Pools and ranges
(a; b; c) ExplicitPool([...]) or pool([...]) Pools and ranges
X = 1..5 in a body X.in_(RangePool(1, 5)), which also takes a Python range, list or tuple Pools and ranges
X = Y + 1 in a body X == Y + 1 Comparisons
p(X) : q(X) (conditional literal) ConditionalLiteral(P(x=X), condition=Q(x=X)) Conditional literals
#sup / #inf SUP / INF Constants and extremes
#const n = 5. program.define_constant("n", 5), which returns the constant to use in rules Constants and extremes
p(f(X, Y)) (nested terms) a field whose type is another predicate (inner: F) Declaring predicates
p((1, 2)) (anonymous tuples) refused: use a named predicate instead, like pair(1, 2) Excluded by philosophy

Facts, rules, constraints

clingo ASPAlchemy details
p(3). program.fact(P(x=3)), with as many facts per call as you like The verbs
p(a). (symbolic constant argument) fact(P(x=a())) with a = Predicate.define("a", []); the Python string "a" would be the quoted string "a", which is a different value Bare atoms
h :- b1, b2. program.when(b1, b2).derive(h) The verbs
:- b1, b2. program.forbid(b1, b2) The verbs
:- not p., :- X > 3. program.require(p), program.require(X <= 3) The verbs
% comment program.comment(...), section(...), blank_line() Organizing output
a; b :- c. (disjunctive heads) refused Excluded by philosophy

Negation

clingo ASPAlchemy details
not p(X) Not(P(x=X)) or ~P(x=X) Default negation
not not p(X) ~~P(x=X), which stays as written Default negation
not X != Y ~(X != Y), which is written out as X = Y Default negation
-p(X) (classical negation) -P(x=X) Classical negation
-p in #show -P on the class, as in raw_asp(predicates=[P, -P]) The predicates= seatbelt
not p :- b. (negated head) refused: use forbid(b, p) Deliberate strictness
not X = (2; 3) (negated pool comparison) refused: it doesn’t mean “not in”; write X != 2, X != 3 Deliberate strictness

Choices and cardinality

clingo ASPAlchemy details
{ h : c } = 1 :- b. program.when(b).derive(Choice(h, condition=c) == 1) Choice rules
2 { h : c } 4 (bounds) 2 <= Choice(h, condition=c) <= 4 Choice rules
{ p(X) : q(X) }. (bare choice) program.choose(Choice(P(x=X), condition=Q(x=X))) Choice rules
2 { p(X) } 4 in a body refused: write it as a Count comparison Cardinality tests are not choices

Aggregates

clingo ASPAlchemy details
#count / #sum / #sum+ / #min / #max Count / Sum / SumPlus / Min / Max, with more elements via add() Aggregates
#count{...} > 3 Count(...) > 3 Aggregates
2 <= #count{...} <= 4 2 <= Count(...) <= 4 Banded aggregates
aggregates on both sides of a comparison refused (clingo rejects it too): compute one into a variable in a separate rule  

Arithmetic

You write arithmetic with Python’s operators, and ASPAlchemy writes out clingo’s. +, -, *, **, &, ^ and all six comparisons are the same in both. The ones that differ are below; where the meaning differs (rounding, overflow, precedence), see Arithmetic.

clingo ASPAlchemy details
x / y (integer division) x // y Arithmetic
x \ y (modulo) x % y Arithmetic
x ? y (bitwise or) x \| y Arithmetic
~x (bitwise complement) Compl(x), because ~ means default negation Arithmetic
\|x\| (absolute value) abs(x) Arithmetic

Optimization

clingo ASPAlchemy details
#minimize{ w@p, t : c }. program.minimize(w, *terms, condition=c, priority=p) Optimization
#maximize{ w@p, t : c }. program.maximize(w, *terms, condition=c, priority=p) Optimization
:~ b1, b2. [w@p, t] (weak constraint) program.penalize(b1, b2, weight=w, terms=[...], priority=p) Optimization
optimizing program.optimize(), which returns an Optimum Optimization
--opt-mode=ignore solve(ignore_optimization=True) Optimization
--opt-mode=enum with a bound solve(cost_bound=...) Optimization

Directives

clingo ASPAlchemy details
#show p/2. predicates are shown by default; show=False on the class hides one, and program.show() / hide() override that Names and visibility
#show p(X) : p(X), cond. program.show_when(ConditionalLiteral(head, condition)), which adds the head to the condition for you Names and visibility
#const define_constant() (refused inside raw_asp()) Constants and extremes
#project / #heuristic raw_asp(), plus a solver option to turn them on Solver options
#script (python) ... #end. inside a single raw_asp() call Lexical rules
@f(X) in raw_asp() text, with ground(context=obj); @f(X) calls obj.f(X) Calling Python during grounding
#defined p/1. raw_asp("#defined p/1.") Clingo’s messages
#include, or several .lp files refused: use Python to organize your program; to write it out as several files, use segment groups Segment groups
#program / #external refused, including in raw_asp() Multi-shot solving
&diff{...} (theory atoms) not supported; grounded.control gives you the underlying clingo Control Excluded by philosophy

Solving modes

clingo ASPAlchemy details
--models 0 (all models) iterate over solve(), which computes models as you ask for them The model stream
--enum-mode=brave / cautious brave() / cautious() Brave and cautious
optimizing optimize(), or optimize_iter() on a grounding Optimization
assumptions solve(assumptions=[...]) on the program, or on a grounding Ground once, solve many
--mode=gringo --text ground().ground_text() Seeing the ground program
--mode=gringo (aspif) ground().aspif() Seeing the ground program
--time-limit timeout= on solve() and the other verbs The model stream
-t 4, --seed, --configuration=jumpy, and other solver options grounded.control.configuration, set after ground() and before solving Solver options

Symbols in and out

Answer sets come back as typed atoms, so most programs never need a clingo.Symbol. If you use the clingo API directly, convert_predicate_to_symbol and convert_symbol_to_predicate convert between the two. See Clingo symbol interop.

Verified against clingo

The examples below are run as part of the test suite, so if ASPAlchemy’s output ever changes, this page has to change with it. First, the pieces they use:

from aspalchemy import (
    ANY,
    ASPProgram,
    Choice,
    Compl,
    ConditionalLiteral,
    Count,
    Field,
    INF,
    Not,
    Number,
    Predicate,
    RangePool,
    SUP,
    V,
    pool,
)

X, Y = V.X, V.Y

class P(Predicate):
    x: Field[int]

class Q(Predicate):
    x: Field[int]

Terms and literals:

>>> ANY.render()
'_'
>>> pool(range(1, 6)).render()
'1..5'
>>> pool([1, 3, 5]).render()
'(1; 3; 5)'
>>> X.in_(RangePool(1, 5)).render()
'X = 1..5'
>>> (X == Y + 1).render()
'X = Y + 1'
>>> SUP.render(), INF.render()
('#sup', '#inf')
>>> ConditionalLiteral(P(x=X), condition=Q(x=X)).render()
'p(X) : q(X)'

Arithmetic:

>>> (X // 2).render()
'X / 2'
>>> (X % 2).render()
'X \\ 2'
>>> (Number(1) | 2).render()
'1 ? 2'
>>> Compl(X).render()
'~X'
>>> abs(X).render()
'|X|'

Negation:

>>> Not(P(x=X)).render()
'not p(X)'
>>> (~~P(x=X)).render()
'not not p(X)'
>>> (~(X != 3)).render()
'X = 3'
>>> (-P(x=3)).render()
'-p(3)'

Choices, aggregates and bare atoms:

>>> (Choice(P(x=X), condition=Q(x=X)) == 1).render()
'{ p(X) : q(X) } = 1'
>>> (Count(X, condition=P(x=X)) > 3).render()
'#count{ X : p(X) } > 3'
>>> (Count(X, condition=P(x=X)) >= 2).render()
'#count{ X : p(X) } >= 2'
>>> n = Predicate.define("n", [], show=False)  # a bare atom is a zero-arity predicate
>>> n().render()
'n'

And whole statements, through a program:

program = ASPProgram()
width = program.define_constant("width", 9)
program.fact(P(x=width))
program.when(Q(x=X)).derive(P(x=X))
program.forbid(P(x=X), Q(x=X))
program.when(Q(x=X)).derive(Choice(P(x=X), condition=Q(x=X)) == 1)
>>> print(program.render())
% Generated by aspalchemy ...
#const width = 9.
p(width).
p(X) :- q(X).
:- p(X), q(X).
{ p(X) : q(X) } = 1 :- q(X).

#show.
#show p/1.
#show q/1.

Positioning

If you’ve used clorm, here’s how the two compare. clorm gives you typed data: facts going in, and models coming out, with a query layer for searching solutions. The rules themselves stay as ASP text. ASPAlchemy gives you a typed program: your rules, choices and constraints are Python objects, checked before clingo ever sees them. If you want to keep writing rules in .lp files and query the results, clorm is the right tool. If you want to write the program itself in Python, you’re in the right place. The two also work together: ASPAlchemy to build the program, and clorm to handle the data.