Core Syntax¶
This page documents the core syntax and fundamental building blocks of the constraint handler.
Notation¶
In the following sections, we will introduce the notation used throughout the documentation to describe the various elements of the constraint handler.
While in the previous Language Concepts page we used an abstract syntax to introduce general concepts such as Expressions and Declarations, this page mainly focuses on the specific syntax used by the constraint handler to represent these concepts in ASP.
However, for documentation purposes, we will often use more meaningful names for arguments instead of the generic ones introduced previously and will refer to their respective concepts accordingly.
For example, we will use the term Name to refer to unique names assigned to variables.
Syntax¶
The base syntax follows standard ASP predicates and function symbols.
Simple¶
A simple Declaration with a fixed number of arguments could be represented as follows:
some_predicate(Term, Term)
| Name | Description |
|---|---|
some_predicate |
The identifier of the predicate. |
Term |
Some argument of the predicate. |
Example
A simple declaration some_predicate with the unique identifier my_predicate and three terms:
some_predicate(term_1, term_2, term_3)
List¶
For arguments with varying numbers of elements, the constraint handler uses a list syntax.
Lists are represented as recursive tuples. More precisely, a list is either the empty tuple () or a tuple of the form (Head, Tail), where Head is the first element of the list and Tail is another list representing the rest of the arguments. A list has to be terminated by the empty tuple.
Example
Given some predicate with a definition like:
some_predicate(Terms)
where Terms represents a list of terms, one could represent a list with three terms as follows:
some_predicate((term_1, (term_2, (term_3, ()))))
Labels¶
Declarations marked with the banner Label Support support an optional leading argument called a Label.
- If you omit the label, the system will use an anonymous label internally.
- If you provide a label, it can be used for engine selection via requestEngine and can identify the source of warnings that refer to declaration labels (see warning).
In other words: if you do not use requestEngine/2 and you do not rely on label provenance in warnings, you can omit labels on declarations that support them for shorter encodings.
Example
A variable definition without a label:
variable_define(x, val(int, 42)).
The equivalent form with an explicit label:
variable_define(my_label, x, val(int, 42)).
Value¶
Values represent concrete instances of data of some Type or Collection used in rules and constraints.
To work with a value directly, the constraint handler uses the val/2 function symbol.
val(Type, Term)
| Name | Description |
|---|---|
Type |
The data type of the value. This should correspond to one of the supported types in the constraint handler, such as int, bool, etc. |
Term |
The actual value, which should correspond to the specified type. |
Example
This represents the integer value 42. It can be used in expressions such as operations or assignments.
val(int, 42)
Variable¶
Variables represent references to values that can be reused throughout the program. The constraint handler provides multiple ways of assigning values to variables.
Output¶
When a variable is assigned a value, an atom of the value/2 predicate is added to the model.
The same predicate is also used by some higher-level features, such as labeled optimization declarations, to expose aggregated results in the final model.
value(Name, Value)
| Name | Description |
|---|---|
Name |
The unique identifier for the variable associated with the value. |
Value |
The actual value assigned to the variable using the val/2 predicate. |
Example
If the integer value 42 was assigned to the variable x the following atom would be added to the model:
value(x, val(int, 42)).
This is exactly what the test example from the Getting Started guide does.
Define¶
The simplest way to create variables is to use the variable_define/2 predicate to define them with a specific value.
variable_define(Name, Expression).
| Name | Description |
|---|---|
Name |
A unique identifier for the variable. |
Expression |
An Expression to be associated with the variable. |
This assigns a specific value to the variable Name based on the Valuation of Expression.
The result is a single value/2 atom in the model.
Declare¶
A more advanced technique is to declare variables using the variable_declare/2 predicate. Instead of creating a single variable with a specific value, this declares possible values from a given set of possible values (domain).
Note
While Define creates a single value/2 atom in all models. The Declare approach creates multiple models with different value/2 atoms based on the domain.
variable_declare(Name, Domain).
| Name | Description |
|---|---|
Name |
A unique identifier for the variable. |
Domain |
An Expression that evaluates to a domain of possible values. |
Example
Declaring a variable x that can take the boolean values true or false:
variable_declare(x, boolDomain).
This creates models for both possible assignments:
value(x, val(bool, true)).
value(x, val(bool, false)).
Domain¶
While the constraint handler provides a shortcut for boolean domains, users can also define custom domains.
From List¶
An easy way to define a domain is to use the fromList function symbol together with a list of Expressions defining the possible values.
fromList(Values)
| Name | Description |
|---|---|
Values |
A List of Expressions representing the Values of the domain. |
Example
Creating a variable y that can take the integer values 1, 2, or 3:
variable_declare(y, fromList((val(int,1), (val(int,2), (val(int,3), ()))))).
This creates models for each possible assignment:
value(y, val(int, 1))
value(y, val(int, 2))
value(y, val(int, 3))
From Facts¶
Another way to define a domain is to use the fromFacts/0 function symbol. However, this additionally requires the use of the variable_domain/2 predicate to extract the possible values from existing facts.
variable_domain(Name, Domain).
| Name | Description |
|---|---|
Name |
A unique identifier for the variable. |
Domain |
The facts representing the domain of possible values. |
Example
Creating a variable y that can take the integer values 1, 2, or 3:
variable_declare(y, fromFacts).
variable_domain(y, val(int,(1;2;3))).
This creates models for each possible assignment:
value(y, val(int, 1))
value(y, val(int, 2))
value(y, val(int, 3))
Optional¶
Variables that are declared using a domain can also be marked as optional. This means that the variable may also not be assigned any value at all.
variable_declareOptional(Name).
| Name | Description |
|---|---|
Name |
A unique identifier for the variable. |
Example
Marking the variable y from the previous example as optional:
variable_declareOptional(y).
This creates models for each possible assignment as before, but also an additional model with the value:
value(y, val(none, none)).
Usage¶
While it is technically possible to use the value/2 Result to work with the value of a variable, it is not recommended for defining logic. Instead, users are advised to use the variable/1 function symbol within their Expressions.
This function symbol retrieves the value stored in the specified variable.
variable(Name)
| Name | Description |
|---|---|
Name |
A unique identifier for the variable. |
Example
Getting the value assigned to variable x and assign it to variable y:
variable_define(x, val(int,42)).
variable_define(y, variable(x)).
Operation¶
Operations are the key aspect of the constraint handler that allow expressing arbitrary computations. To achieve this, the constraint handler uses the operation/2 function symbol together with a collection of operators.
operation(Operator, Terms).
| Name | Description |
|---|---|
Operator |
The operator to be applied. For a full list of supported operators by specific types, please refer to the respective pages in the reference. |
Terms |
A list of arguments on which the operator will be applied. Terms can be Values, Variables, or even other operations. |
Example
Adding two variables x and y and assigning the result to variable z
variable_define(x, val(int,5)).
variable_define(y, val(int,7)).
variable_define(z, operation(add, (variable(x), (variable(y),())))).
While simple operations may be sufficient for many use cases, more complex programs often require combining multiple operations together. For this reason, the constraint handler fully supports nesting operations within each other.
In this case, one or more elements of the argument list will be entire operation terms rather than simple values or variables.
Example
Consider the expressions a+x and b+c:
operation(add, (variable(a), (variable(x),())))
operation(add, (variable(b), (variable(c),())))
Graphically, they can be represented like this:
flowchart LR
subgraph S1 ["Expression 1: a + x"]
direction TB
Op1[add] --> Va[variable a]
Op1 --> Vx[variable x]
end
subgraph S2 ["Expression 2: b + c"]
direction TB
Op2[add] --> Vb[variable b]
Op2 --> Vc[variable c]
end
S1 ~~~ S2
If we now wanted to represent a + b + c directly, we can imagine to simply move the entire graph of Expression 2 into the slot of variable x like so:
graph TD
subgraph S1 ["Expression 3: a + b + c"]
direction TB
Op1[add] --> A[variable a]
Op1 --> Op2[add]
Op2 --> B[variable b]
Op2 --> C[variable c]
end
In ASP, we do this by replacing the term variable(x) in the first operation by the entirety of the second operation:
operation(add, (variable(a), (operation(add, (variable(b), (variable(c),()))),())))
Ensure¶
Ensures allow users to specify conditions that must hold true in the model. For this, the constraint handler provides the ensure/1 predicate.
ensure(Condition).
| Name | Description |
|---|---|
Condition |
The condition that must be satisfied in the model. |
Condition¶
Conditions can be any expression with a Bool result. If the condition evaluates to false, the model is considered invalid.
Strict Evaluation
The constraint handler is strict. The condition must explicitly evaluate to true. If a condition cannot be evaluated (e.g., because it references a variable that was never assigned), the constraint is considered violated.
If a variable itself is of type Bool, it can be used directly as a condition.
Example
Ensure that a variable x is true:
ensure(variable(x)).
Conditions can also be more complex expressions, such as comparisons or operations that yield a boolean result.
Example
Ensure the variable x has a greater value than the variable y.
ensure(operation(gt, (variable(x),(variable(y),())))).
Because all ensures must hold true for the model to be valid, they can be used to enforce multiple conditions at the same time. It is recommended to use this feature to break down complex constraints into smaller, more manageable parts.
Example
To ensure that variable x is greater than y and that variable z is true, one could write:
ensure(operation(conj, (operation(gt, (variable(x),(variable(y),()))),(variable(z),())))).
While this works, it gets harder to read the more conditions are added. A better approach is to use multiple ensure/1 atoms:
ensure(operation(gt, (variable(x),(variable(y),())))).
ensure(variable(z)).
This way, each condition is clearly separated and easier to understand and debug.