Merge pull request #1689 from triska/clpb_doc
DOC: preliminary CLP(B) documentation in DocLog format
This commit is contained in:
310
src/lib/clpb.pl
310
src/lib/clpb.pl
@@ -1,6 +1,6 @@
|
||||
/* CLP(B): Constraint Logic Programming over Boolean Variables
|
||||
|
||||
Copyright (C): 2019 Markus Triska
|
||||
Copyright (C): 2019-2023 Markus Triska
|
||||
All rights reserved.
|
||||
|
||||
E-mail: triska@metalevel.at
|
||||
@@ -105,60 +105,309 @@ goal_expansion(del_attr(Var, Module), (var(Var) -> put_atts(Var, -Access);true))
|
||||
Access =.. [Module,_].
|
||||
|
||||
|
||||
/**
|
||||
/** Constraint Logic Programming over Boolean variables
|
||||
|
||||
## Introduction
|
||||
|
||||
This library provides CLP(B), Constraint Logic Programming over
|
||||
Boolean variables. It can be used to model and solve combinatorial
|
||||
problems such as verification, allocation and covering tasks.
|
||||
|
||||
CLP(B) is an instance of the general CLP(_X_) scheme,
|
||||
extending logic programming with reasoning over specialised domains.
|
||||
|
||||
The implementation is based on reduced and ordered Binary Decision
|
||||
Diagrams (BDDs).
|
||||
|
||||
Benchmarks and usage examples of this library are available from:
|
||||
[*https://www.metalevel.at/clpb/*](https://www.metalevel.at/clpb/)
|
||||
|
||||
## Boolean expressions
|
||||
|
||||
A _Boolean expression_ is one of:
|
||||
|
||||
| `0` | false |
|
||||
| `1` | true |
|
||||
| _variable_ | unknown truth value |
|
||||
| _atom_ | universally quantified variable |
|
||||
| ~ _Expr_ | logical NOT |
|
||||
| _Expr_ + _Expr_ | logical OR |
|
||||
| _Expr_ * _Expr_ | logical AND |
|
||||
| _Expr_ # _Expr_ | exclusive OR |
|
||||
| _Var_ ^ _Expr_ | existential quantification |
|
||||
| _Expr_ =:= _Expr_ | equality |
|
||||
| _Expr_ =\= _Expr_ | disequality (same as #) |
|
||||
| _Expr_ =< _Expr_ | less or equal (implication) |
|
||||
| _Expr_ >= _Expr_ | greater or equal |
|
||||
| _Expr_ < _Expr_ | less than |
|
||||
| _Expr_ > _Expr_ | greater than |
|
||||
| card(Is,Exprs) | cardinality constraint (_see below_) |
|
||||
| `+(Exprs)` | n-fold disjunction (_see below_) |
|
||||
| `*(Exprs)` | n-fold conjunction (_see below_) |
|
||||
|
||||
where _Expr_ again denotes a Boolean expression.
|
||||
|
||||
The Boolean expression `card(Is,Exprs)` is true iff the number of true
|
||||
expressions in the list `Exprs` is a member of the list `Is` of
|
||||
integers and integer ranges of the form `From-To`. For example, to
|
||||
state that precisely two of the three variables `X`, `Y` and `Z` are
|
||||
`true`, you can use `sat(card([2],[X,Y,Z]))`.
|
||||
|
||||
`+(Exprs)` and `*(Exprs)` denote, respectively, the disjunction and
|
||||
conjunction of all elements in the list `Exprs` of Boolean
|
||||
expressions.
|
||||
|
||||
Atoms denote parametric values that are universally quantified. All
|
||||
universal quantifiers appear implicitly in front of the entire
|
||||
expression. In residual goals, universally quantified variables always
|
||||
appear on the right-hand side of equations. Therefore, they can be
|
||||
used to express functional dependencies on input variables.
|
||||
|
||||
## Interface predicates
|
||||
|
||||
The most frequently used CLP(B) predicates are:
|
||||
|
||||
* `sat(+Expr)`
|
||||
True iff the Boolean expression Expr is satisfiable.
|
||||
|
||||
* `taut(+Expr, -T)`
|
||||
If Expr is a tautology with respect to the posted constraints, succeeds
|
||||
with *T = 1*. If Expr cannot be satisfied, succeeds with *T = 0*.
|
||||
Otherwise, it fails.
|
||||
|
||||
* `labeling(+Vs)`
|
||||
Assigns truth values to the variables Vs such that all constraints
|
||||
are satisfied.
|
||||
|
||||
The unification of a CLP(B) variable _X_ with a term _T_ is equivalent
|
||||
to posting the constraint sat(X=:=T).
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example session with a few queries and their answers:
|
||||
|
||||
```
|
||||
?- use_module(library(clpb)).
|
||||
true.
|
||||
|
||||
?- sat(X*Y).
|
||||
X = 1, Y = 1.
|
||||
|
||||
?- sat(X * ~X).
|
||||
false.
|
||||
|
||||
?- taut(X * ~X, T).
|
||||
T = 0, clpb:sat(X=:=X).
|
||||
|
||||
?- sat(X^Y^(X+Y)).
|
||||
clpb:sat(X=:=X), clpb:sat(Y=:=Y).
|
||||
|
||||
?- sat(X*Y + X*Z), labeling([X,Y,Z]).
|
||||
X = 1, Y = 0, Z = 1
|
||||
; X = 1, Y = 1, Z = 0
|
||||
; X = 1, Y = 1, Z = 1.
|
||||
|
||||
?- sat(X =< Y), sat(Y =< Z), taut(X =< Z, T).
|
||||
T = 1, clpb:sat(X=:=X*Y), clpb:sat(Y=:=Y*Z).
|
||||
|
||||
?- sat(1#X#a#b).
|
||||
sat(X=:=a#b).
|
||||
```
|
||||
|
||||
The pending residual goals constrain remaining variables to Boolean
|
||||
expressions and are declaratively equivalent to the original query.
|
||||
The last example illustrates that when applicable, remaining variables
|
||||
are expressed as functions of universally quantified variables.
|
||||
|
||||
## Obtaining BDDs
|
||||
|
||||
By default, CLP(B) residual goals appear in (approximately) algebraic
|
||||
normal form (ANF). This projection is often computationally expensive.
|
||||
We can assert `clpb:clpb_residuals(bdd)` to see the BDD representation
|
||||
of all constraints. This results in faster projection to residual
|
||||
goals, and is also useful for learning more about BDDs. For example:
|
||||
|
||||
```
|
||||
?- asserta(clpb:clpb_residuals(bdd)).
|
||||
true.
|
||||
|
||||
?- sat(X#Y).
|
||||
node(3)- (v(X, 0)->node(2);node(1)),
|
||||
node(1)- (v(Y, 1)->true;false),
|
||||
node(2)- (v(Y, 1)->false;true).
|
||||
```
|
||||
|
||||
Note that this representation cannot be pasted back on the toplevel,
|
||||
and its details are subject to change. Use copy_term/3 to obtain
|
||||
such answers as Prolog terms.
|
||||
|
||||
The variable order of the BDD is determined by the order in which the
|
||||
variables first appear in constraints. To obtain different orders,
|
||||
we can for example use:
|
||||
|
||||
```
|
||||
?- sat(+[1,Y,X]), sat(X#Y).
|
||||
node(3)- (v(Y, 0)->node(2);node(1)),
|
||||
node(1)- (v(X, 1)->true;false),
|
||||
node(2)- (v(X, 1)->false;true).
|
||||
```
|
||||
|
||||
## Enabling monotonic CLP(B)
|
||||
|
||||
In the default execution mode, CLP(B) constraints are _not_ monotonic.
|
||||
This means that _adding_ constraints can yield new solutions. For
|
||||
example:
|
||||
|
||||
```
|
||||
?- sat(X=:=1), X = 1+0.
|
||||
false.
|
||||
|
||||
?- X = 1+0, sat(X=:=1), X = 1+0.
|
||||
X = 1+0.
|
||||
```
|
||||
|
||||
This behaviour is highly problematic from a logical point of view, and
|
||||
it may render [*declarative
|
||||
debugging*](https://www.metalevel.at/prolog/debugging)
|
||||
techniques inapplicable.
|
||||
|
||||
Assert `clpb:monotonic` to make CLP(B) *monotonic*. If this mode is
|
||||
enabled, then you must wrap CLP(B) variables with the functor
|
||||
`v/1`. For example:
|
||||
|
||||
```
|
||||
?- asserta(clpb:monotonic).
|
||||
true.
|
||||
|
||||
?- sat(v(X)=:=1#1).
|
||||
X = 0.
|
||||
```
|
||||
|
||||
## Example: Pigeons
|
||||
|
||||
In this example, we are attempting to place _I_ pigeons into _J_ holes
|
||||
in such a way that each hole contains at most one pigeon. One
|
||||
interesting property of this task is that it can be formulated using
|
||||
only _cardinality constraints_ (`card/2`). Another interesting aspect
|
||||
is that this task has no short resolution refutations in general.
|
||||
|
||||
In the following, we use [*Prolog DCG
|
||||
notation*](https://www.metalevel.at/prolog/dcg) to describe a
|
||||
list `Cs` of CLP(B) constraints that must all be satisfied.
|
||||
|
||||
```
|
||||
:- use_module(library(clpb)).
|
||||
:- use_module(library(clpz)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
pigeon(I, J, Rows, Cs) :-
|
||||
length(Rows, I), length(Row, J),
|
||||
maplist(same_length(Row), Rows),
|
||||
transpose(Rows, TRows),
|
||||
phrase((all_cards(Rows,[1]),all_cards(TRows,[0,1])), Cs).
|
||||
|
||||
all_cards([], _) --> [].
|
||||
all_cards([Ls|Lss], Cs) --> [card(Cs,Ls)], all_cards(Lss, Cs).
|
||||
```
|
||||
|
||||
Example queries:
|
||||
|
||||
```
|
||||
?- pigeon(9, 8, Rows, Cs), sat(*(Cs)).
|
||||
false.
|
||||
|
||||
?- pigeon(2, 3, Rows, Cs), sat(*(Cs)),
|
||||
append(Rows, Vs), labeling(Vs),
|
||||
maplist(portray_clause, Rows).
|
||||
[0,0,1].
|
||||
[0,1,0].
|
||||
etc.
|
||||
```
|
||||
|
||||
## Example: Boolean circuit
|
||||
|
||||
Consider a Boolean circuit that express the Boolean function =|XOR|=
|
||||
with 4 =|NAND|= gates. We can model such a circuit with CLP(B)
|
||||
constraints as follows:
|
||||
|
||||
```
|
||||
:- use_module(library(clpb)).
|
||||
|
||||
nand_gate(X, Y, Z) :- sat(Z =:= ~(X*Y)).
|
||||
|
||||
xor(X, Y, Z) :-
|
||||
nand_gate(X, Y, T1),
|
||||
nand_gate(X, T1, T2),
|
||||
nand_gate(Y, T1, T3),
|
||||
nand_gate(T2, T3, Z).
|
||||
```
|
||||
|
||||
Using universally quantified variables, we can show that the circuit
|
||||
does compute =|XOR|= as intended:
|
||||
|
||||
```
|
||||
?- xor(x, y, Z).
|
||||
sat(Z=:=x#y).
|
||||
```
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
The interface predicates of this library follow the example of
|
||||
[*SICStus Prolog*](https://sicstus.sics.se).
|
||||
|
||||
Use SICStus Prolog for higher performance in many cases.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Each CLP(B) variable belongs to exactly one BDD. Each CLP(B)
|
||||
variable gets an attribute (in module "clpb") of the form:
|
||||
|
||||
```
|
||||
index_root(Index,Root)
|
||||
```
|
||||
index_root(Index,Root)
|
||||
|
||||
where Index is the variable's unique integer index, and Root is the
|
||||
root of the BDD that the variable belongs to.
|
||||
|
||||
Each CLP(B) variable also gets an attribute in module `clpb_hash`: an
|
||||
Each CLP(B) variable also gets an attribute in module clpb_hash: an
|
||||
association table node(LID,HID) -> Node, to keep the BDD reduced.
|
||||
The association table of each variable must be rebuilt on occasion
|
||||
to remove nodes that are no longer reachable. We rebuild the
|
||||
association tables of involved variables after BDDs are merged to
|
||||
build a new root. This only serves to reclaim memory: Keeping a
|
||||
node in a local table even when it no longer occurs in any BDD does
|
||||
not affect the solver's correctness. However, `apply_shortcut/4`
|
||||
not affect the solver's correctness. However, apply_shortcut/4
|
||||
relies on the invariant that every node that occurs in the relevant
|
||||
BDDs is also registered in the table of its branching variable.
|
||||
|
||||
A root is a logical variable with a single attribute ("clpb\_bdd")
|
||||
A root is a logical variable with a single attribute ("clpb_bdd")
|
||||
of the form:
|
||||
|
||||
```
|
||||
Sat-BDD
|
||||
```
|
||||
Sat-BDD
|
||||
|
||||
where Sat is the SAT formula (in original form) that corresponds to
|
||||
BDD. Sat is necessary to rebuild the BDD after variable aliasing,
|
||||
and to project all remaining constraints to a list of `sat/1` goals.
|
||||
and to project all remaining constraints to a list of sat/1 goals.
|
||||
|
||||
Finally, a BDD is either:
|
||||
|
||||
* The integers 0 or 1, denoting false and true, respectively, or
|
||||
* A node of the form
|
||||
*) The integers 0 or 1, denoting false and true, respectively, or
|
||||
*) A node of the form
|
||||
|
||||
```
|
||||
node(ID, Var, Low, High, Aux)
|
||||
```
|
||||
|
||||
Where ID is the node's unique integer ID, Var is the
|
||||
node's branching variable, and Low and High are the
|
||||
node's low (Var = 0) and high (Var = 1) children. Aux
|
||||
is a free variable, one for each node, that can be used
|
||||
to attach attributes and store intermediate results.
|
||||
node(ID, Var, Low, High, Aux)
|
||||
Where ID is the node's unique integer ID, Var is the
|
||||
node's branching variable, and Low and High are the
|
||||
node's low (Var = 0) and high (Var = 1) children. Aux
|
||||
is a free variable, one for each node, that can be used
|
||||
to attach attributes and store intermediate results.
|
||||
|
||||
Variable aliasing is treated as a conjunction of corresponding SAT
|
||||
formulae.
|
||||
|
||||
You should think of CLP(B) as a potentially vast collection of BDDs
|
||||
that can range from small to gigantic in size, and which can merge.
|
||||
*/
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Type checking.
|
||||
@@ -1117,16 +1366,14 @@ indomain(1).
|
||||
%
|
||||
% ```
|
||||
% ?- sat(A =< B), Vs = [A,B], sat_count(+[1|Vs], Count).
|
||||
% Vs = [A, B],
|
||||
% Count = 3,
|
||||
% sat(A=:=A*B).
|
||||
% Vs = [A,B], Count = 3, clpb:sat(A=:=A*B).
|
||||
%
|
||||
% ?- length(Vs, 120),
|
||||
% sat_count(+Vs, CountOr),
|
||||
% sat_count(*(Vs), CountAnd).
|
||||
% Vs = [...],
|
||||
% CountOr = 1329227995784915872903807060280344575,
|
||||
% CountAnd = 1.
|
||||
% Vs = [...],
|
||||
% CountOr = 1329227995784915872903807060280344575,
|
||||
% CountAnd = 1.
|
||||
% ```
|
||||
|
||||
|
||||
@@ -1266,7 +1513,8 @@ random_bindings(VNum, Node) -->
|
||||
%
|
||||
% ```
|
||||
% ?- sat(A#B), weighted_maximum([1,2,1], [A,B,C], Maximum).
|
||||
% A = 0, B = 1, C = 1, Maximum = 3.
|
||||
% A = 0, B = 1, C = 1, Maximum = 3
|
||||
% ; false.
|
||||
% ```
|
||||
|
||||
weighted_maximum(Ws, Vars, Max) :-
|
||||
|
||||
@@ -5842,6 +5842,10 @@ run_propagator(preified_slash(X, Y, D, R), MState) -->
|
||||
( Y == 0 ->
|
||||
kill(MState),
|
||||
D = 0
|
||||
; Y == 1 ->
|
||||
kill(MState),
|
||||
D = 1,
|
||||
R = X
|
||||
; nonvar(X),
|
||||
nonvar(Y) ->
|
||||
kill(MState),
|
||||
|
||||
Reference in New Issue
Block a user