Clingo to ASPAlchemy

Translation table: every clingo construct → its spelling here or its refusal link; CI-pinned by a trailing block of verified transcripts.

You already write clingo. This page answers, construct by construct, how each thing you write is spelled in ASPAlchemy — or that it is deliberately refused, with a link to why. Every supported row links the guide page that owns it, and the Receipts section shows the load-bearing renderings as executed transcripts, so if a table entry drifts from the library, CI breaks before you do. For clingo’s own semantics, the Potassco guide remains the reference — this page only maps spellings.

Refused constructs are marked refused and never re-argued here: What We Don’t Support is the single home for the reasoning, and Escape Hatches covers raw_asp() when you need a construct anyway.

Terms and literals

clingo ASPAlchemy details
X, Node (variables) Variable("X"), or V.X for inline use Variables
_ (don’t-care) ANY Variables
_X (named don’t-warn variable) refused — use ANY Deliberate strictness
42 Python int, auto-coerced (Number is the explicit form) Constants and extremes
"hello" Python str, auto-coerced — a str always means a quoted ASP string One text type
n (symbolic constant / bare atom) 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)) — also accepts a Python range, list, or tuple Pools and ranges
X = Y + 1 (binding assignment in a body) X == Y + 1 — a comparison; the equality binds the variable, so it satisfies the safety check Comparisons
p(X) : q(X) (conditional literal in a body) ConditionalLiteral(P(x=X), Q(x=X)) Conditional literals
#sup / #inf SUP / INF Constants and extremes
#const n = 5. program.define_constant("n", 5) — returns the DefinedConstant term to use in rules Constants and extremes
p(f(X, Y)) (nested function terms) predicate-valued fields: Field[F] — nested atoms travel as typed compound terms Declaring predicates
p((1, 2)) (anonymous tuples) refused (teaching error) — wrap in a named predicate: pair(1, 2), i.e. a nested Field[Pair] Excluded by philosophy

Facts, rules, constraints

clingo ASPAlchemy details
p(3). program.fact(P(x=3)) — ground atoms only, any number per call The verbs
p(a). (symbolic-constant argument) the argument is a zero-arity atom: fact(P(x=a())) with a = Predicate.define("a", []) — a Python str would render the quoted string "a", a different value Bare atoms
h :- b1, b2. program.when(b1, b2).derive(h) The verbs
:- b1, b2. program.forbid(b1, b2) The verbs
a comparison or atom that must hold program.require(cmp) / program.require(p) — the constraint forbidding the opposite; require(p) renders :- not p 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) — preserved, not collapsed (stable-model semantics): default negation is no involution, unlike arithmetic’s -(-x) Default negation
not X != Y (negated plain comparison) ~(X != Y) — normalized to the complement X = Y at construction, gringo’s own rewrite done visibly Default negation
-p(X) (classical negation) -P(x=X) — unary minus on the atom; the sign is part of the atom Classical negation
-p in output declarations -P on the class (a NegatedSignature), e.g. in raw_asp(predicates=[P, -P]) The predicates= seatbelt
not p :- b. (negated head) refused (teaching error) — gringo rewrites it into a constraint anyway; spell forbid(b, p) Deliberate strictness
not X = (2; 3) (negated pool comparison) refused (teaching error) — pools expand disjunctively; spell the exclusion as separate conditions: X != 2, X != 3 (X.in_(...) covers the positive case) Better errors

Choices and cardinality

clingo ASPAlchemy details
{ h : c } = 1 :- b. program.when(b).derive(Choice(h, condition=c).exactly(1)) Choice rules
2 { h : c } 4 (head bounds) Choice(h, condition=c).at_least(2).at_most(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 (cardinality test) refused (teaching error) — a body-brace test is not a choice; spell it as Count comparisons, which ground identically Cardinality tests are not choices

Aggregates

clingo ASPAlchemy details
#count{...} / #sum{...} / #sum+{...} / #min{...} / #max{...} Count / Sum / SumPlus / Min / Max — tuple terms and conditions as arguments, more elements via add() Aggregates
#count{...} > 3 (one-sided guard) one comparison: Count(...) > 3 Guards done right
2 <= #count{...} <= 4 (two-sided guard) two comparisons over the same aggregate: Count(...) >= 2, Count(...) <= 4 — gringo grounds the shared aggregate once Guards done right
aggregates on both sides of one comparison refused — a clingo syntax error anyway; bind one to a variable in a separate rule Better errors

Arithmetic

Same tree, different spellings — Python builds the expression with Python operators, and the renderer emits clingo’s. Semantics (division sign quirks, precedence, overflow) live in 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)~ itself is reserved for default negation Arithmetic
\|x\| (absolute value) abs(x) Arithmetic
x - 1 x + (-1) — a negative right operand of +/- is normalized into the operator at construction, gringo’s own tidy done visibly (x - 1 also spells itself) Negative operands
-(-x), ~(~x), \|\|x\|\| -(-x), Compl(Compl(x)), abs(abs(x)) — a doubled unary operator collapses at construction (the two are involutions, abs is idempotent), so -(-x) IS x, not an expression over it. Contrast not not p, which is preserved Doubled unary operators

+, -, *, **, &, ^, and all six comparison operators are spelled as in Python and render as themselves — the only exceptions being the two normalizations in the last two rows, both cosmetic and value-preserving.

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) — also a when() closer Optimization
@p priority tiers priority= on any of the above Optimization
optimization run program.optimize()Optimum Optimization
--opt-mode=ignore / plain solving of an optimizing program solve(ignore_optimization=True) — plain solve() refuses with a teaching error Optimization
--opt-mode=enum,N (cost-budget enumeration) refused — single tier is a solve() with a hard Sum constraint Cost-budget enumeration

Directives

clingo ASPAlchemy details
#show p/2. shown by default; show=False on the class hides, program.show() / hide() override per class Names and visibility
#show p(X) : cond. (conditional show) program.show_when(ConditionalLiteral(head, condition)) Names and visibility
#const define_constant() — refused inside raw blocks, same redirect Constants and extremes
#project / #heuristic via raw_asp() — silently inert until the matching grounded.control knob is set Solver options
#script (python) ... #end. supported inside ONE self-contained raw_asp() block Lexical rules
@f(X) terms supported through the hatch: raw_asp() text + ground(context=obj)@stone(...) calls context.stone(...) Calling Python during grounding
#defined p/1. supported via raw_asp("#defined p/1.") — the default stop_on_log_level=INFO turns gringo’s body-only-atom info into a loud GroundingError, and #defined is the preferred fix Clingo’s messages
#include refused, in raw blocks too — your program is Python, so imports and functions are the modularity story Excluded by philosophy
#program / #external refused, in raw blocks too — multi-shot is honestly unmodeled Multi-shot solving
&diff{...} (theory atoms) unmodeled in the typed layer; grounded.control exposes clingo’s internals (configuration, externals, theory atoms, observers) at your own risk Excluded by philosophy

Solving modes

clingo ASPAlchemy details
--models 0 (enumerate all) iterate solve() — the stream is lazy and unbounded; take what you need The model stream
--enum-mode=brave / --enum-mode=cautious brave() / cautious() — note the one-sided partial-certification contract Brave and cautious
optimization run optimize(); stepwise via optimize_iter() on a ground() handle Optimization
assumptions solve(assumptions=[...]) — on the program directly, or on a ground() handle when the same program answers many questions Ground once, solve many
--mode=gringo --text ground().ground_text() Seeing the ground program
--mode=gringo (aspif) ground().aspif() — byte-for-byte what clasp receives Seeing the ground program
--time-limit solve(timeout=) / optimize(timeout=) The model stream
-t 4, --seed, --configuration=jumpy, other clasp flags grounded.control.configuration, set between ground() and the solve Solver options

Symbols in and out

Solutions come back as typed atoms, so most programs never touch a clingo.Symbol. When you drive the clingo API directly — assumptions built elsewhere, externals or observers through grounded.controlconvert_predicate_to_symbol and convert_symbol_to_predicate cross the boundary in both directions, recursively, classical-negation sign included. See Clingo symbol interop.

Receipts

The tables above are claims about what the library renders. These blocks are the receipts: executed by the test suite in CI, so a spelling that drifts breaks the build, not you. Every transcript below is the library’s real output. First, the pieces the receipts are built from:

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()  # equality binds X: safe
'X = Y + 1'
>>> SUP.render(), INF.render()
('#sup', '#inf')
>>> ConditionalLiteral(P(x=X), Q(x=X)).render()
'p(X) : q(X)'

Arithmetic spellings — Python operator in, clingo operator out:

>>> (X // 2).render()
'X / 2'
>>> (X % 2).render()
'X \\ 2'
>>> (Number(1) | 2).render()
'1 ? 2'
>>> Compl(X).render()
'~X'
>>> abs(X).render()
'|X|'
>>> (X + Number(-1)).render()  # the negative addend folds into the operator
'X - 1'
>>> (-(-X)).render()  # a doubled involution collapses: the term itself comes back
'X'
>>> Compl(Compl(X)).render()  # likewise for the complement
'X'
>>> abs(abs(X)).render()  # abs is idempotent: one node, not two
'|X|'

Negation, in all four of its flavors:

>>> Not(P(x=X)).render()
'not p(X)'
>>> (~~P(x=X)).render()  # preserved, not collapsed
'not not p(X)'
>>> (~(X != 3)).render()  # complement, not a "not" wrapper
'X = 3'
>>> (-P(x=3)).render()  # classical: the sign is on the atom
'-p(3)'

Choices, aggregate guards, and bare atoms:

>>> Choice(P(x=X), condition=Q(x=X)).exactly(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 the full statement forms, 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)).exactly(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

ASPAlchemy types the program: rules, choices, and constraints are validated Python objects before clingo ever parses a line. clorm types the data boundary — facts in, models out, with a relational query layer over solutions — and by design leaves the rules themselves as ASP text. The two tools solve different problems: if you want to keep writing rules in .lp and query solutions relationally, clorm is the right tool; if you want the program itself to be typed Python, you are in the right docs. Solution handling here is deliberately minimal — typed atoms, loud failures (Solving and Results) — and the two compose: ASPAlchemy to define the program, clorm to handle the data.