4

I am currently writing a solver for a floor planning problem in Prolog and have some issues with the labeling part.

The current problem is my constraints are posted but when I launch the labeling, it takes forever to find a solution. I would like to bring in some heuristics.

My question is, how do I manually label my variables ? I am afraid that after defining a clpfd variable like this :

X in Xinf..Xsup

and constraining it, If I do something like :

    fd_sup(X, Xmax),
    X = Xmax,
...

in my custom label, I won't be using the backtrack ability of Prolog to test the other values of X's domain. Am I wrong ?

Also, is there a smarter way to label my variables than writing custom labeling procedures ? My idea of heuristics would consist in trying extrema of a variable domain alternatively (like max(X), min(X), max(X-1), min(X-1) etc...)

Hope you can help me :)

jschimpf
  • 4,904
  • 11
  • 24
Manfred
  • 75
  • 6
  • 1
    manually label ? just add actual selected values to constraint store... – CapelliC Mar 28 '16 at 10:37
  • I am sorry I am a beginner. By adding the values to the contraint store, do you mean writing " X #= Xmax" ? If so, this wouldn't work, what I want is to alternatively try the max and min of a variable's domain and deleting these values from the domain if they don't work This would be kind of a labeling([min(X), max(X)], [X]) except I don't know if this would work, and i have a whole list of variables for which i want to do the same – Manfred Mar 28 '16 at 10:51
  • 1
    To get best advice you really should show more of your specific problem. – false Mar 28 '16 at 11:05

5 Answers5

6

It is not difficult to write a custom labeling procedure, and with most real problems you will eventually need one anyway in order to incorporate problem-specific heuristics.

The two main components of a labeling procedure are

  1. variable selection: from all the remaining (i.e. not yet instantiated) problem variables, pick one to consider next.
  2. value selection or branching: explore, via backtracking, two or more alternative sub-problems by reducing the chosen variable's domain in (usually) complementary ways.

Using this scheme, the default labeling procedure can be written as

label(Xs) :-
    ( select_variable(X, Xs, Xs1) ->
         branch(X),
         label(Xs1)
    ;
         true    % done, no variables left
    ).

select_variable(X, [X|Xs], Xs).     % 'leftmost' strategy

branch(X) :- indomain(X).

You can now redefine select_variable/3 to implement techniques such as "first-fail", and redefine branch/1 to try domain values in different orders. As long as you make sure that branch/1 enumerates all of X's domain values on backtracking, your search remains complete.

Sometimes you want to try just one domain value first (say, one suggested by a heuristics), but, if it is no good, not commit to another value immediately. Let's say that, as in your example, you want to try the maximum domain value first. You could write this as

branch(X) :-
    fd_sup(X, Xmax),
    (
         X = Xmax           % try the maximum
    ;
         X #\= Xmax         % otherwise exclude the maximum
    ).

Because the two cases are complementary and cover all possible values for X, your search is still complete. However, because of the second alternative, branch/1 can now succeed with an uninstantiated X, which means you must make sure in the labeling procedure that you don't lose this variable from your list. One possibility would be:

label(Xs) :-
    ( select_variable(X, Xs, Xs1) ->
         branch(X),
         ( var(X) -> append(Xs1, [X], Xs2) ; Xs2=Xs1 ),
         label(Xs2)
    ;
         true    % done, no variables left
    ).
jschimpf
  • 4,904
  • 11
  • 24
  • Thanks for the detailed explanation. I understand that the branching needs to cover all the values of the domain, but does the label procedure need to treat every variables ? I mean, can I use a custom labeling procedure for some variables and the usual one for the others ? I'd say no but would like to be sure – Manfred Mar 28 '16 at 22:36
  • 2
    Yes, you can use a conjunction of labeling routines like `labeling(Xs), labeling(Ys)`. But you should be aware that the Xs are then always at the top of the search tree, and the Ys at the bottom. You can also have `my_labeling(Xs), labeling(Xs)` and let `my_labeling` treat only some of the variables, so that the remaining ones are taken care of by `labeling`. Labeling routines should be robust and ignore variables that are already instantiated. – jschimpf Mar 28 '16 at 23:09
4

First, always try built-in heuristics. ff is often a good strategy.

For custom labeling strategies, it is often easiest to first convert the domain to a list, then reorder the list, and then simply use member/2 to assign the values of the domain using the new order.

A good building black is dom_integers/2, relating a finite CLP(FD) domain to a list of integers:

:- use_module(library(clpfd)).

dom_integers(D, Is) :- phrase(dom_integers_(D), Is).

dom_integers_(I)      --> { integer(I) }, [I].
dom_integers_(L..U)   --> { numlist(L, U, Is) }, Is.
dom_integers_(D1\/D2) --> dom_integers_(D1), dom_integers_(D2).

Your specific strategy is easily expressed on a list of such ordered integers, relating these integers to a second list where the values occur in the order you describe:

outside_in([]) --> [].
outside_in([I]) --> [I].
outside_in([First|Rest0]) --> [First,Last],
        { append(Rest, [Last], Rest0) },
        outside_in(Rest).

Sample query and result:

?- phrase(outside_in([1,2,3,4]), Is).
Is = [1, 4, 2, 3] ;
false.

Combining this with fd_dom/2 and dom_integers/2, we get (bindings for variables other than X omitted):

?- X in 10..20,
   fd_dom(X, Dom),
   dom_integers(Dom, Is0),
   phrase(outside_in(Is0), Is),
   member(X, Is).
X = 10 ;
X = 20 ;
X = 11 ;
X = 19 ;
X = 12 ;
X = 18 ;
etc.

Nondeterminism is preserved by member/2.

mat
  • 40,498
  • 3
  • 51
  • 78
  • Thanks for the answer ! So you would assign values through the member/2 procedure but I don't quite understand the "outside_in' you provide, especially the syntax, do we use "{ }" in swi prolog ?? Otherwise it seems like it does exactly what I'm looking for ! – Manfred Mar 28 '16 at 21:34
  • 1
    Yes, this is doing precisely what you are asking for, and the other answers I gave complement this answer with other aspects you definitely also should take into account, and which address issues that are somewhat implicit in your question. See [tag:dcg] for the syntax I am using here to describe lists. You use the `phrase/2` interface predicate to invoke DCGs. Also make sure to check out the answer by @jschimpf: Instead of simply using `member/2`, it is often better to post a disjunction consisting of `(#=)/2` and `(\#=)/2` in the two branches, to enhance propagation in the alternative. – mat Mar 28 '16 at 22:11
4

Make sure to distinguish labeling strategies from additional propagation. These two aspects are currently a bit mixed in your question.

In SWI-Prolog, there is a predicate called clpfd:contracting/1. It does what you describe: It tries values from the domain boundaries, and removes values that can be seen as inconsistent, i.e., for which it is known that no solution exists.

Therefore, if you have a list of variables Vs, you can try: clpfd:contracting(Vs), and see if this helps.

Note that this can also significantly slow down the search, though on the other hand, also help significantly to reduce the search space before even trying any labeling!

mat
  • 40,498
  • 3
  • 51
  • 78
  • Thanks for the precision ! Does the contraction happen dynamically at every assignment of value for a variable or once in a static way at the very beginning ? Because the way I build my domains there should be no inconsistent values the static way. I'm going to try this anyway on an older version of the solver, with the ff strategy without any custom labeling ! I will keep you up to date ! – Manfred Mar 28 '16 at 21:41
  • 1
    The contraction only happens exactly at the time you invoke it, but you can of course also invoke it multiple times, for example, invoke `clpfd:contracting/1` for certain variables *during* a custom labeling. – mat Mar 28 '16 at 22:07
  • But will the effect of contracting disappear with the backtracking ? I mean, will it restore the inconsistent values for a Variable if another one's value changes ? – Manfred Mar 28 '16 at 22:32
  • 1
    Please open a new question for this. We need to discuss the arising issues separately. – mat Mar 28 '16 at 22:33
  • Ok no problem, you can see it here : http://stackoverflow.com/questions/36272417/prolog-contracting-1-used-dynamically-restoring-deleted-inconsistent-values – Manfred Mar 28 '16 at 22:40
3

To complement the other answers (one contrasting labeling and propagation, one showing a dedicated labeling method), I now tackle a further very important aspect of this question:

Very often, when beginners complain about the speed of their code, it turns out that their code in fact doesn't even terminate! More efficiency would not help in that case.

Hence, this answer points you towards first ensuring actual termination of your relation.

The best way to ensure termination of CLP(FD) programs is to separate them into 2 parts:

  1. the first, called the core relation, simply posts all constraints.
  2. the second uses labeling/2 to perform the actual search.

Have you done this in your program? If not, please do. When this is done, make sure that the core relation, say solution/2 (where the arguments are: a term denoting the task instance, and the list of variables to be labeled) terminates universally by querying:

?- solution(Instance, Vs), false.

If this terminates, then the following also terminates:

?- solution(Instance, Vs), label(Vs), false.

Of course, in larger tasks, you have no chance to actually witness the termination of the latter query, but a good chance to witness the termination of the first query, because setting up the constraints is often much faster than actually obtaining even a a single solution.

Therefore, test whether your core relation terminates!

mat
  • 40,498
  • 3
  • 51
  • 78
  • 1
    Only in SWI's `clpfd, labeling is always terminating – false Mar 28 '16 at 11:49
  • Yes ! The way my program is built, I have a main procedure calling others to post constraints and then a labeling procedure. I tried solving small instances of problems and it printed a solution, I guess I should not have core relation termination issues should I ? – Manfred Mar 28 '16 at 21:46
  • 1
    It seems you are not having any issues with this, so that is OK. You can make sure by simply querying like `?- core_relation, false.`, i.e., append a `, false` at the end of the query, so that only the termination property remains observable. In SWI and Yap, if *that* terminates, then so will this plus labeling. – mat Mar 28 '16 at 22:06
  • well, if I call my main procedure like this : `main(X), false.` I have the same issues of "infinite" labeling but with small instances I am sure it ends as I have writeln everywhere in my code ;) – Manfred Mar 28 '16 at 22:29
  • 1
    Do not use `main/1` for this test, but only the core relation, i.e., just stating the constraints, **without** labeling! If you have any questions about this strategy, please open a new question for this. – mat Mar 28 '16 at 22:33
  • s(X) for disseminating best practices. – repeat Apr 02 '16 at 00:30
1

This follows up on this previous answer by @mat.

If you have got some more CPU cycles to burn, try shave_zs/1 as defined in this previous answer.

shave_zs/1 kind of works like the auxiliary library predicate clpfd:contracting/1. Unlike contracting/1, however, all values are "up for grabs"—not just the ones at the boundary. YMMV!

Community
  • 1
  • 1
repeat
  • 18,496
  • 4
  • 54
  • 166