User-defined, or ‘conceptual’ constraints can easily be defined as conjunctions of primitive constraints. For example, let us consider a set of products and the specification that allows them to be colocated in a warehouse. This should be done in such a way as to propagate possible changes in the domains as soon as this becomes possible.

Let us assume we have a symmetric relation that defines which product can be colocated with another and that products are distinguished by numeric product identifiers:

colocate(100, 101). colocate(100, 102). colocate(101, 100). colocate(102, 100). colocate(103, 104). colocate(104, 103). |

Suppose we define a constraint `colocate_product_pair(X, Y)`

such
that any change of the possible values of X or Y is propagated to the
other variable. There are many ways in which this pairing
can be defined in ECL^{i}PS^{e}. They are different solutions with different
properties, but they yield the same results.

We can encode directly the relations between elements in the domains of the two variables:

colocate_product_pair(A, B) :- cpp(A, B), cpp(B, A). cpp(A, B) :- [A,B] :: [100, 101, 102, 103, 104], A #= 100 => B :: [101, 102], A #= 101 => B #= 100, A #= 102 => B #= 100, A #= 103 => B #= 104, A #= 104 => B #= 103. |

This method is quite simple and does not need any special analysis; on the other hand it potentially creates a huge number of auxiliary constraints and variables.

By far the simplest mechanism, that avoids this potential creation of large
numbers of auxiliary constraints and variables, is to load the Generalised
Propagation library (*propia*) and use arc-consistency (*ac*)
propagation, viz:

?- colocate(X,Y) infers ac

- ⊙
- Additional information on
*propia*can be found in section 15.3, section 15 and the ECL^{i}PS^{e}Constraint Library Manual.

In this case we use the `element/3`

predicate,
that states in a list of integers that the element at
an index is equal to a value. Every time the index or the value is updated,
the constraint is activated and the domain of the other variable is updated
accordingly.

relates(X, Xs, Y, Ys) :- element(I, Xs, X), element(I, Ys, Y). |

We define a generic predicate, `relates/4`

, that associates the
corresponding elements at a specific index of two lists, with one
another. The variable I is an index into the lists,
Xs and Ys, to yield the elements at this index,
in variables X and Y.

colocate_product_pair(A, B) :- relates(A, [100, 100, 101, 102, 103, 104], B, [101, 102, 100, 100, 104, 103]). |

The `colocate_product_pair`

predicate simply calls `relates/4`

passing a list containing the product identifiers in the first argument
of `colocate/2`

as Xs and a list containing product identifiers
from the second argument of `colocate/2`

as Ys.

Behind the scenes, this is exactly the implementation used for arc-consistency propagation by the Generalised Propagation library.

Because of the specific and efficient algorithm implementing the
`element/3`

constraint, it is usually faster than the first
approach, using reified constraints.