Merge pull request #1689 from triska/clpb_doc
DOC: preliminary CLP(B) documentation in DocLog format
This commit is contained in:
288
src/lib/clpb.pl
288
src/lib/clpb.pl
@@ -1,6 +1,6 @@
|
|||||||
/* CLP(B): Constraint Logic Programming over Boolean Variables
|
/* CLP(B): Constraint Logic Programming over Boolean Variables
|
||||||
|
|
||||||
Copyright (C): 2019 Markus Triska
|
Copyright (C): 2019-2023 Markus Triska
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
E-mail: triska@metalevel.at
|
E-mail: triska@metalevel.at
|
||||||
@@ -105,48 +105,297 @@ goal_expansion(del_attr(Var, Module), (var(Var) -> put_atts(Var, -Access);true))
|
|||||||
Access =.. [Module,_].
|
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)
|
Each CLP(B) variable belongs to exactly one BDD. Each CLP(B)
|
||||||
variable gets an attribute (in module "clpb") of the form:
|
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
|
where Index is the variable's unique integer index, and Root is the
|
||||||
root of the BDD that the variable belongs to.
|
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.
|
association table node(LID,HID) -> Node, to keep the BDD reduced.
|
||||||
The association table of each variable must be rebuilt on occasion
|
The association table of each variable must be rebuilt on occasion
|
||||||
to remove nodes that are no longer reachable. We rebuild the
|
to remove nodes that are no longer reachable. We rebuild the
|
||||||
association tables of involved variables after BDDs are merged to
|
association tables of involved variables after BDDs are merged to
|
||||||
build a new root. This only serves to reclaim memory: Keeping a
|
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
|
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
|
relies on the invariant that every node that occurs in the relevant
|
||||||
BDDs is also registered in the table of its branching variable.
|
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:
|
of the form:
|
||||||
|
|
||||||
```
|
|
||||||
Sat-BDD
|
Sat-BDD
|
||||||
```
|
|
||||||
|
|
||||||
where Sat is the SAT formula (in original form) that corresponds to
|
where Sat is the SAT formula (in original form) that corresponds to
|
||||||
BDD. Sat is necessary to rebuild the BDD after variable aliasing,
|
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:
|
Finally, a BDD is either:
|
||||||
|
|
||||||
* The integers 0 or 1, denoting false and true, respectively, or
|
*) The integers 0 or 1, denoting false and true, respectively, or
|
||||||
* A node of the form
|
*) A node of the form
|
||||||
|
|
||||||
```
|
|
||||||
node(ID, Var, Low, High, Aux)
|
node(ID, Var, Low, High, Aux)
|
||||||
```
|
|
||||||
|
|
||||||
Where ID is the node's unique integer ID, Var is the
|
Where ID is the node's unique integer ID, Var is the
|
||||||
node's branching variable, and Low and High are the
|
node's branching variable, and Low and High are the
|
||||||
node's low (Var = 0) and high (Var = 1) children. Aux
|
node's low (Var = 0) and high (Var = 1) children. Aux
|
||||||
@@ -158,7 +407,7 @@ goal_expansion(del_attr(Var, Module), (var(Var) -> put_atts(Var, -Access);true))
|
|||||||
|
|
||||||
You should think of CLP(B) as a potentially vast collection of BDDs
|
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.
|
that can range from small to gigantic in size, and which can merge.
|
||||||
*/
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
Type checking.
|
Type checking.
|
||||||
@@ -1117,9 +1366,7 @@ indomain(1).
|
|||||||
%
|
%
|
||||||
% ```
|
% ```
|
||||||
% ?- sat(A =< B), Vs = [A,B], sat_count(+[1|Vs], Count).
|
% ?- sat(A =< B), Vs = [A,B], sat_count(+[1|Vs], Count).
|
||||||
% Vs = [A, B],
|
% Vs = [A,B], Count = 3, clpb:sat(A=:=A*B).
|
||||||
% Count = 3,
|
|
||||||
% sat(A=:=A*B).
|
|
||||||
%
|
%
|
||||||
% ?- length(Vs, 120),
|
% ?- length(Vs, 120),
|
||||||
% sat_count(+Vs, CountOr),
|
% sat_count(+Vs, CountOr),
|
||||||
@@ -1266,7 +1513,8 @@ random_bindings(VNum, Node) -->
|
|||||||
%
|
%
|
||||||
% ```
|
% ```
|
||||||
% ?- sat(A#B), weighted_maximum([1,2,1], [A,B,C], Maximum).
|
% ?- 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) :-
|
weighted_maximum(Ws, Vars, Max) :-
|
||||||
|
|||||||
@@ -5842,6 +5842,10 @@ run_propagator(preified_slash(X, Y, D, R), MState) -->
|
|||||||
( Y == 0 ->
|
( Y == 0 ->
|
||||||
kill(MState),
|
kill(MState),
|
||||||
D = 0
|
D = 0
|
||||||
|
; Y == 1 ->
|
||||||
|
kill(MState),
|
||||||
|
D = 1,
|
||||||
|
R = X
|
||||||
; nonvar(X),
|
; nonvar(X),
|
||||||
nonvar(Y) ->
|
nonvar(Y) ->
|
||||||
kill(MState),
|
kill(MState),
|
||||||
|
|||||||
Reference in New Issue
Block a user