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.