☰

Constraint Logic Programming

Computational Logic


Constraints

  • Constraint: conditions that a solution must satisfy
    • is true
    • The third field of the data structure is greater that the second
    • The murderer is one of those who had met the cabaret entertainer
  • CLP: LP plus the ability to compute with some form of constraints
    (which are solved by the system during computation)
  • (Additional) features of a CLP system:
    • Domain of computation (reals, rationals, integers, booleans, structures, …)
    • Expressions that can be built ()
    • Constraints allowed: equations, inequations, inequations, etc. ()
    • Constraint solving algorithms: simplex, gauss, etc.
  • Solutions: assignments to variables, or new constraints among variables.

A comparison with classic LP (I)

  • A) Equality constraints on Herbrand terms (unifications):

    :- module(_,_).
    %% * CLP: a first comparison with classic Logic Programming
    
    :- use_package(clpr).
    % You can also try: :- use_package(clpq).
    
    % Classic LP
    hp(X,Y,Z) :- Z = f(X,Y).
    
    % CLP: Prolog+constraints (same in this case)
    hc(X,Y,Z) :- Z = f(X,Y).
  • B) Equality constraints on arithmetic expressions:

    % Classic Prolog using is/2
    ap(X,Y,Z) :- Z is X + Y. 
    
    % CLP: Prolog+constraints (note use of .=.)
    ac(X,Y,Z) :- Z .=. X+Y. 
  • Query examples:

    • A) Equality constraints on Herbrand terms: fully reversible in both cases:

      ?- hp(3, 4, Z).
      ?- hp(X, Y, f(3,4)).
      ?- hp(X, Y, Z).
      ?- hc(3, 4, Z).
      ?- hc(X, Y, f(3,4)).
      ?- hc(X, Y, Z).
    • B) Equality constraints on arithmetic expressions:

      Not reversible with is/2:

      ?- ap(3, 4, Z).
      ?- ap(X, 4, 7).
      Fully reversible with constraints:

      ?- ac(3, 4, Z).
      ?- ac(X, 4, 7).
      ?- ac(X, Y, 7).
      In the last case we get a constraint back.

A Comparison with classic LP (II)

  • Example (CLP(R) package):

    :- use_package(clpr).
    p(X, Y, Z) :-  Z .=. X + Y.
    ?- p(3, 4, Z).
    Z .=. 7
    
    ?- p(X, 4, 7).
    X .=. 3
    
    4 ?- p(X, Y, 7).
    X .=. 7 - Y           ← with clpr arithmetic is reversible!

A Comparison with classic LP (III)

  • Advantages:
    • Helps making programs expressive and flexible.
    • May save much coding.
    • In some cases, more efficient than classic LP programs due to solvers typically being very efficiently implemented.
    • Also, efficiency due to search space reduction:
      • LP: generate-and-test.
      • CLP: constrain-and-generate.
  • Possible disadvantages:
    • Solvers can be complex to implement (incremental simplex, gauss, etc).
      • Not a problem for the user of course.
    • Using constraints can be slower than traditional arithmetic.
      • However, in modern Prolog systems constraints are optional, so you only pay for their use if you use them.

Constraint Systems: CLP()

  • The semantics is parameterized by the constraint domain :
    CLP(X), where :
    • Σ: set of predicate and function symbols, together with their arity
    • –formulae: constraints
    • D: the set of actual elements in the constraint domain
    • : meaning of predicate and function symbols (and hence, constraints).
    • : a first–order theory (axiomatizes some properties of )
  • ) is a constraint domain
  • Assumptions:
    • built upon a first–order language
    • and is identity in
    • There are identically false and identically true constraints in
    • is closed w.r.t. renaming, conjunction, and existential quantification

Constraint Domains (I)

  • , D = R (the reals), interprets Σ as usual,
    • → Arithmetic over the reals ("" domain).
    • Eg.:
    • Question: is needed? How can it be represented?

  • ,
    • → Linear arithmetic ("" domain)
    • Eg.:

  • ,
    • → Linear equations ("" domain)
    • Eg.:
  • A corresponding set of domains can be defined on the rationals ("" domain)

Constraint Domains (II)

  • A very special domain:
    • constant and function symbols
    • D = { *finite trees* }
    • interprets Σ as tree constructors
      • Each with arity maps trees to a tree with root labeled and whose subtrees are the arguments of the mapping
    • Constraints: syntactic tree equality
    • → Equality constraints over the Herbrand domain ( domain)
    • Eg.:
  • LP ≡ CLP()

    • I.e., classical LP can be viewed as constraint logic programming over Herbrand terms with a single constraint predicate symbol: =.

Constraint Domains (III)

  • constants
  • D = { finite strings of constants }
    interprets . as string concatenation,
    as string length
    • → Equations over strings of constants ( domain)
    • Eg.:

  • D =
  • interprets symbols in Σ as boolean functions
    • → Boolean constraints ( domain)
    • Eg.:
  • CLP(X) Programs

    • Recall that:
      • Σ is a set of predicate and function symbols
      • –formulae are the constraints
    • : set of predicate symbols definable by a program
      • Atom: , where and are terms
      • Primitive constraint: , where
        are terms and is a predicate symbol
      • Constraint: (first–order) formula built from primitive constraints
    • The class of constraints will vary (generally only a subset of formulas are considered constraints)
    • A CLP program is a collection of rules of the form where is an atom and the 's are atoms or constraints
    • A fact is a rule where is a constraint
    • A goal (or query) is a conjunction of constraints and atoms

    A case study: CLP(R)

    • CLP(R): language based on Prolog + constraint solving over the reals ()
      • Same execution strategy as standard Prolog (depth–first, left–to–right)
      • Allows linear equations and inequations over the reals
      • Linear constraints are solved;
        non-linear constraints are passive: delayed until linear or simple checks:
        • X*Y = 7 becomes linear when X is assigned a definite value
        • X*X+2*X+1 = 0 becomes a check when X is assigned a definite value
      • Prolog arithmetic is subsumed by constraint solving
    • Note: CLP(R) is really CLP(()) — is often omitted.
    • Supported in modern Prologs coexisting with the ISO primitives is/2, >/2 etc.
    • In Ciao, via the clpr package:

      • Uses .=., .>., etc.  to distinguish the clpr constraints from the ISO-Prolog arithmetic primitives.
      • I.e.,   X .=. Y + 5, Y .>. 1 vs. X is Y + 5, Y > 1
      exercise! ▶

    Linear Equations (CLP(R) package)

    • Scalar (dot) product and systems of equations:

      (x₁,x₂, …, xn) . (y₁, y₂, …, yn) = x₁ . y₁ + x₂ . y₂ + … + xn . yn

      We represent vectors as lists of numbers:

      :- module(_,_).
      :- use_package(clpr).
      % You can also try: :- use_package(clpq).
      % -------------------------------------------------------------------
      
      prod([], [], Result) :- 
            Result .=. 0.
      prod([X|Xs], [Y|Ys], Result) :-
            Result .=. X * Y + Rest,
            prod(Xs, Ys, Rest).
      Unification and constraint solving combined!

      ?- prod([2, 3], [4, 5], K).
      ?- prod([2, 3], [4, Y2], 23).
      ?- prod([2, 7, 3], [Vx, Vy, Vz], 0).
      -> Any computed answer is, in general, a constraint over the variables in the query. Try also with clpq!

      ?- prod([Y,2,3],[4,X,6],32).
      ?- prod([3,1], [X,Y], 5), prod([1,8], [X,Y], 3).
      Equivalent to: 3x + y = 5 'and' x + 8y = 3. -> Systems of linear equations!
    • Solving systems of linear equations with arbitrary numbers of variables and equations:

      system([], _Vars, []). 
      system([Co|Coefs], Vars, [Ind|Indeps]) :- 
          prod(Co, Vars, Ind),
          system(Coefs, Vars, Indeps).
      E.g., solving the previous system (3x + y = 5, x + 8y = 3):

      ?- system([[3, 1],[1, 8]], [X, Y], [5, 3]).
      Note that we are using both Herbrand terms and constraints.
    • Inequations are solved using a modified, incremental Simplex:

      ?- X + Y .=<. 4, Y .>=. 4, X .>=. 0.
    • Non-linear equations are delayed:

      ?- sin(X) .=. cos(X).
      ?- X*X + 2*X + 1 .=. 0.
      Reason: no general solving technique is known. CLPR solves only linear (dis)equations.
    • Once equations become linear, they are handled properly:

      ?- X .=. cos(sin(Y)), Y .=. 2+Y*3.

    Systems of Linear Equations (CLP(R))

    • Can we solve systems of equations? E.g.,

    • Write them down at the top level prompt:

      ?- prod([3, 1], [X, Y], 5), prod([1, 8], [X, Y], 3).
      X .=. 1.6087, Y .=. 0.173913
    • A more general predicate can be built mimicking the mathematical vector notation :

      system([], _Vars, []). 
      system([Co|Coefs], Vars, [Ind|Indeps]) :- 
           prod(Co, Vars, Ind),
          system(Coefs, Vars, Indeps).
    • We can now express (and solve) equation systems:

      ?- system([[3, 1],[1, 8]], [X, Y], [5, 3]). 
      X .=. 1.6087, Y .=. 0.173913
      exercise! ▶

    Non–linear Equations (CLP(R))

    • Non–linear equations are delayed

      ?- sin(X) .=. cos(X).
      sin(X) .=. cos(X)
    • This can sometimes be the case even if a procedure exists to solve them:

      ?- X*X + 2*X + 1 .=. 0.
      -2*X - 1 .=. X * X
    • Reason: no general solving technique is known. CLP(R) solves only linear (dis)equations.
    • Once equations become linear, they are handled properly:

      ?- X .=. cos(sin(Y)), Y .=. 2+Y*3.
      Y .=. -1, X .=. 0.666367
    • Inequations are solved using a modified, incremental Simplex:

      ?- X + Y .=<. 4, Y .>=. 4, X .>=. 0.
      Y .=. 4, X .=. 0

    Fibonacci Revisited (Standard Prolog)

    • Fibonacci numbers:

    • Fibonacci in standard Prolog:

      :- module(_,_).
      :- use_package(clpq).
      % -------------------------------------------------------------------
      % Fibonacci revisited 
      
      fib_pl(0,0).
      fib_pl(1,1).
      fib_pl(N,R) :-
          N > 1, 
          N1 is N-1,
          N2 is N-2,
          fib_pl(N1,F1),
          fib_pl(N2,F2),
          R is F1+F2.
    • Fibonacci in CLP:

      fib_clp(N,N) :- N .=. 0.
      fib_clp(N,N) :- N .=. 1.
      fib_clp(N,R) :- 
          N .>. 1,
          F1 .>=. 0, 
          F2 .>=. 0, 
          N1 .=. N - 1,
          N2 .=. N - 2,
          fib_clp(N1,F1),
          fib_clp(N2,F2),
          R .=. F1 + F2.
    • Try:

      ?- fib_pl(11,Y).
      ?- fib_clp(11,Y).
      ?- fib_pl(X,89).
      -> Error

      ?- fib_clp(X,89).
      -> Works...

      ?- fib_pl(X,Y).
      -> Error (at third solution)

      ?- fib_clp(X,Y).
      -> Works!

    Fibonacci Revisited (CLP(R))

    • CLP(R) package version: syntactically similar to the previous one:

      :- use_package(clpr).
      fib(N,N) :- N .=. 0.
      fib(N,N) :- N .=. 1.
      fib(N,R) :- N .>. 1, F1 .>=. 0, F2 .>=. 0, 
                  N1 .=. N - 1, N2 .=. N - 2,
                  fib(N1,F1), fib(N2,F2),
                  R .=. F1 + F2.
    • Note all constraints included in program (F1 >= 0, F2 >= 0) – good practice!
    • Only real numbers and equations used (no data structures, no other constraint system): "pure CLP(R)"
    • Semantics greatly enhanced! E.g.:

      ?- fib(N, F).
      F .=. 0, N .=. 0  ;
      F .=. 1, N .=. 1  ;
      F .=. 1, N .=. 2  ;
      F .=. 2, N .=. 3  ;
      exercise! ▶

    Mortgage Calculation (CLP(R))

    • The classic CLP(R) mortgage calculation example, where:

      • P: Principal, i.e., the balance at the beginning
      • T: Term, i.e., the number of interest periods (e.g., years)
      • I: Interest rate, where, e.g., 0.1 means 10% by interest period
      • B: Balance at the end of the period
      • MP: Monthly Payment amount for each interest period
      The relation between them is defined by mg(P, T, I, B, MP):

      :- module(_,_).
      :- use_package(clpr).
      % Or, we can use clpq for exact calculations
      % :- use_package(clpq).
      
      mg(P, T, I, B, MP):-
          T .=. 1,
          B + MP .=. P * (1 + I).
      mg(P, T, I, B, MP):-
          T .>. 1,
          P1 .=. P * (1 + I) - MP,
          T1 .=. T - 1,
          mg(P1, T1, I, B, MP).
    • A simple query to calculate what the payments will be for a $1000 loan, 30 periods, at 3%, with balance 0 at the end:

      ?- mg(1000, 30, 0.03, 0, MP).
    • What loan amount can we afford if we can pay $20 per period?

      ?- mg(P, 30, 0.03, 0, 20).
    • If we see it instead as an investment, calculate the balance gained at the end for a given interest rate:

      ?- mg(1000, 30, 0.03, B, 0).
    • Same, but making payments of $50 per period:

      ?- mg(1000, 30, 0.03, B, -50).

    Mortgage Calculation (CLP(R)) (II)

    Some queries
    mg(Principal, Term, Interest, Balance, MPayment):
    • A simple query to calculate what the payments will be for a $1000 loan, 30 periods, at 3%, with balance 0 at the end:

      ?- mg(1000, 30, 0.03, 0, MP).

    • What loan amount can we afford if we can pay $20 per period?

      ?- mg(P, 30, 0.03, 0, 20).

    • If we see it instead as an investment, calculate the balance gained at the end for a given interest rate:

      ?- mg(1000, 30, 0.03, B, 0).

    • Same, but making payments of $50 per period:

      ?- mg(1000, 30, 0.03, B, -50).

    exercise! ▶

    Analog RLC circuits (CLP(R))

    • Analysis and synthesis of analog circuits
    • RLC network in steady state
    • Each circuit is composed either of:
      • A simple component, or
      • A connection of simpler circuits
    • For simplicity, we will suppose subnetworks connected only in parallel and series ⟶ Ohm's laws will suffice (other networks need global, i.e., Kirchoff's laws)
    • We want to relate the current (I), voltage (V) and frequency (W) in steady state
    • Entry point: circuit(C, V, I, W) states that:
      across the network C, the voltage is V, the current is I and the frequency is W
    • V and I must be modeled as complex numbers (the imaginary part takes into account the angular frequency)
    • Note that Herbrand terms are used to provide data structures

    Analog RLC circuits (CLP(R))

    • We represent complex numbers as a pair c( <Real_part>, <Imaginary_part> ):

      • Complex number addition:

        :- module(_,_).
        :- use_package(clpr).
        %----------------------------------------
        % RLC Analog Circuits
        
        c_add(c(Re1,Im1), c(Re2,Im2), c(Re12,Im12)) :- 
            Re12 .=. Re1+Re2,
            Im12 .=. Im1+Im2.
      • Complex number multiplication:

        c_mult(c(Re1, Im1), c(Re2, Im2), c(Re3, Im3)) :-
              Re3 .=. Re1 * Re2 - Im1 * Im2,
              Im3 .=. Re1 * Im2 + Re2 * Im1.
      (Equality, c_equal(c(R, I), c(R, I)), can be left to [extended] unification.)
    • Describing the relation of voltage (V), current (I), and frequency (W) in resistors, inductors, and capacitors:

      circuit(resistor(R), V, I, _W) :- 
          c_mult(I, c(R, 0), V).
      
      circuit(inductor(L), V, I, W) :- 
          Im .=. W * L,
          c_mult(I, c(0, Im), V).
      
      circuit(capacitor(C), V, I, W) :- 
          Im .=. -1 / (W * C),
          c_mult(I, c(0, Im), V).
    • Describing the relation of voltage (V), current (I), and frequency (W) when circuits connected in series or in parallel. We put these after the components, so that synthesis is directed.

      % When connecting in parallel, the voltage is the same, the
      % currents are added.
      circuit(parallel(N1, N2), V, I, W) :-
             c_add(I1, I2, I),
             circuit(N1, V, I1, W),
             circuit(N2, V, I2, W).
      
      % When connecting in series the current is the same, the
      % voltages are added.
      circuit(series(N1, N2), V, I, W) :-
             c_add(V1, V2, V),
             circuit(N1, V1, I, W),
             circuit(N2, V2, I, W).
    • Calculating the C and R values needed in the given circuit to get the given voltage and current at the given frequency:

      ?- circuit(parallel(inductor(0.073), series(capacitor(C), resistor(R))), c(4.5, 0), c(0.65, 0), 2400).

    Analog RLC circuits (CLP(R))

    • Circuits in series:

      circuit(series(N1, N2), V, I, W) :-
             c_add(V1, V2, V),
             circuit(N1, V1, I, W),
             circuit(N2, V2, I, W).
    • Circuits in parallel:

      circuit(parallel(N1, N2), V, I, W) :-
             c_add(I1, I2, I),
             circuit(N1, V, I1, W),
             circuit(N2, V, I2, W).

    Analog RLC circuits (CLP(R))

    Each basic component can be modeled as a separate unit:

    • Resistor:

      circuit(resistor(R), V, I, _W) :- 
          c_mult(I, c(R, 0), V).
    • Inductor:

      circuit(inductor(L), V, I, W) :- 
          Im .=. W * L,
          c_mult(I, c(0, Im), V).
    • Capacitor:

      circuit(capacitor(C), V, I, W) :- 
          Im .=. -1 / (W * C),
          c_mult(I, c(0, Im), V).

    Analog RLC circuits (CLP(R))

    • Example:

      [h]

      ?- circuit(parallel(inductor(0.073), 
                          series(capacitor(C), resistor(R))), 
                 c(4.5, 0), c(0.65, 0), 2400).
      
      R .=. 6.91229, C .=. 0.00152546
      
      ?- circuit(C, c(4.5, 0), c(0.65, 0), 2400).

    The N Queens Problem

    • Problem:
      place N chess queens in a N × N board such that they do not attack each other
    • Data structure: a list holding the column position for each row
    • The final solution is a permutation of the list [1, 2, ..., N]

      is represented as [2, 4, 1, 3]

    • General idea:
      • Start with partial solution
      • Nondeterministically select new queen
      • Check safety of new queen against those already placed
      • Add new queen to partial solution if compatible; start again with new partial solution

    The N Queens Problem in Prolog

    queens(N, Qs) :- queens_list(N, Ns),  % E.g., Ns=[4,3,2,1]
                     queens(Ns, [], Qs).
    queens([], Qs, Qs).                   % All queens placed!
    queens(Unplaced, Placed, Qs) :-
        select(Unplaced, Q, NewUnplaced), % E.g. Q=4, NewU=[3,2,1]
        no_attack(Placed, Q, 1),            % Fail if attack
        queens(NewUnplaced, [Q|Placed], Qs).% OK->Choose next q
    no_attack([], _Queen, _Nb).
    no_attack([Y|Ys], Queen, Nb) :-
        Queen =\=  Y + Nb,  Queen  =\= Y - Nb,
        Nb1 is Nb + 1, no_attack(Ys, Queen, Nb1).
    select([X|Ys], X, Ys).
    select([Y|Ys], X, [Y|Zs]) :- select(Ys, X, Zs).
    queens_list(0, []).
    queens_list(N, [N|Ns]) :-
        N > 0, N1 is N - 1, queens_list(N1, Ns).

    The N Queens Problem in CLP(R)  (in Ciao clpr syntax)

    • We represent the state as a list holding in which column the queen is placed for each row. E.g., for 4 queens:

      -----------------
      |   | Q |   |   |
      -----------------
      |   |   |   | Q |
      -----------------
      | Q |   |   |   |
      -----------------
      |   |   | Q |   |
      -----------------
      is represented as [2, 4, 1, 3].
    • General idea: from a partial solution, non-deterministically select a new queen, check safety of new queen against those already placed, if OK add new queen to partial solution and continue, otherwise backtrack to the selection and choose another possible queen.
    • Standard Prolog:

      :- module(_,_).
      :-use_package(clpr).
      %----------------------------------------
      % N Queens 
      
      queens_pl(N, Qs) :- 
          queens_list_pl(N, Ns),   % E.g., Ns=[4,3,2,1]
          queens_pl_(Ns, [], Qs).
      
      queens_pl_([], Qs, Qs).      % All queens placed!
      queens_pl_(Unplaced, Placed, Qs) :-
          selectq(Unplaced, Q, NewUnplaced), % E.g., Q=4, NewU=[3,2,1]
          no_attack_pl(Placed, Q, 1),        % Fail if Q attacked
          queens_pl_(NewUnplaced, [Q|Placed], Qs).  % OK -> Choose next Q
      
      no_attack_pl([], _Queen, _Nb).
      no_attack_pl([Y|Ys], Queen, Nb) :-
          Queen =\= Y + Nb, 
          Queen =\= Y - Nb, 
          Nb1 is Nb + 1,
          no_attack_pl(Ys, Queen, Nb1).
      
      selectq([X|Ys], X, Ys).
      selectq([Y|Ys], X, [Y|Zs]) :-
          selectq(Ys, X, Zs).
      
      queens_list_pl(0, []).
      queens_list_pl(N, [N|Ns]) :-
          N > 0, N1 is N - 1, queens_list_pl(N1, Ns).
      Try, e.g., the following queries to better understand the algorithm (also, run on the debugger):

      ?- queens_list_pl(4,L).
      ?- selectq([4,3,2,1], Q, NewUnplaced).
      Try different solutions

      Solving for N queens (can ask for more solutions):

      ?- queens_pl(4,L).
      ?- queens_pl(20,L).
      ?- queens_pl(27,L).
      ?- queens_pl(30,L).
    • CLP(R) version:

      queens(N, Qs) :- 
          constrain_values(N, N, Qs),
          place_queens(N, Qs).
      
      constrain_values(0, _N, []).
      constrain_values(I, N, [X|Xs]) :-
          I .>. 0,
          X .>. 0, X .=<. N, % All queens between 0 and N
          I1 .=. I - 1,
          constrain_values(I1, N, Xs), no_attack(Xs, X, 1).
      
      no_attack([], _Queen, _Nb).
      no_attack([Y|Ys], Queen, Nb) :-
          Queen .<>. Y + Nb,
          Queen .<>. Y - Nb,
          Nb1 .=. Nb + 1,
          no_attack(Ys, Queen, Nb1).
      
      place_queens(0, _).
      place_queens(N, Q) :- 
          N .>. 0, 
          memberq(N, Q), 
          N1 .=. N - 1, 
          place_queens(N1, Q).
      
      memberq(X, [X|_]).
      memberq(X, [_|Xs]) :- memberq(X, Xs).
      Solving for N queens (can ask for more solutions):

      ?- queens(4,Qs).
      ?- queens(20,Qs).
      -> This CLP(R) solution does not speed up w.r.t. the standard Prolog one. -> This problem is more suited to finite domain constraints.

      Try, e.g., the following queries to better understand the code:

      ?- queens(4, Qs).
      ?- memberq(4, [A, B, C, D]).
      ?- place_queens(0, L).
      ?- constrain_values(4, 4, Qs).
      ?- constrain_values(4, 4, Qs), Qs = [3,1|OQs].
      Bad state rejected using constraint (in)consistency:

      ?- constrain_values(4, 4, Qs), Qs = [3,2|OQs].

    The N Queens Problem in CLP(R)

    • This last program can attack the problem in its most general instance:

      ?- queens(N,L).
      L = [], N .=. 0 ;
      L = [1], N .=. 1 ;
      L = [2, 4, 1, 3], N .=. 4 ;
      L = [3, 1, 4, 2], N .=. 4 ;
      L = [5, 2, 4, 1, 3], N .=. 5 ;
      L = [5, 3, 1, 4, 2], N .=. 5 ;
      L = [3, 5, 2, 4, 1], N .=. 5 ;
      L = [2, 5, 3, 1, 4], N .=. 5
      ...
    • Remark: Herbrand terms used to build the data structures
    • But also used as constraints (e.g., length of already built list Xs in no_attack(Xs, X, 1))
    • Note that in fact we are using both and

    The N Queens Problem in CLP(R)

    • CLP(R) generates internally a set of equations for each board size

      ?- constrain_values(4, 4, Qs).
      Qs = [_A,_B,_C,_D],
      nonzero(_E),   _A.=<.4.0,    _E.=.3.0+_A-_D,     
      nonzero(_F),   _A.>.0,       _F.=. -3.0+_A-_D,    
      nonzero(_G),   _B.=<.4.0,    _G.=.2.0+_A-_C,      
      nonzero(_H),   _B.>.0,       _H.=. -2.0+_A-_C,    
      nonzero(_I),   _C.=<.4.0,    _I.=.1+_A-_B,        
      nonzero(_J),   _C.>.0,       _J.=. -1+_A-_B,      
      nonzero(_K),   _D.=<.4.0,    _K.=.2.0+_B-_D,      
      nonzero(_L),   _D.>.0,       _L.=. -2.0+_B-_D,    
      nonzero(_M),                 _M.=.1+_B-_C,       
      nonzero(_N),                 _N.=. -1+_B-_C,     
      nonzero(_O),                 _O.=.1+_C-_D,       
      nonzero(_P),                 _P.=. -1+_C-_D      ?
    • place_queens(4,[_A,_B,_C,_D]) adds all possible queens in [_A,_B,_C,_D].

    The N Queens Problem in CLP(R)

    • Constraints are (incrementally) simplified as new queens are added

      ?- constrain_values(4, 4, Qs), Qs = [3,1|_].
      Qs = [_A,_B,_C,_D],
      nonzero(_E),    _A.=.3.0,     _E.=.6.0-_D,   
      nonzero(_F),    _B.=.1.0,     _F.=. -_D,     
      nonzero(_G),    _C.=<.4.0,    _G.=.5.0-_C,     
      nonzero(_H),    _C.>.0,       _H.=.1.0-_C,     
      nonzero(_I),    _D.=<.4.0,    _I.=.3.0-_D,     
      nonzero(_J),    _D.>.0,       _J.=. -1.0-_D,   
      nonzero(_K),                  _K.=.2.0-_C,     
      nonzero(_L),                  _L.=. -_C,       
      nonzero(_M),                  _M.=.1+_C-_D,    
      nonzero(_N),                  _N.=. -1+_C-_D ? 
    • Bad choices are rejected using constraint consistency:

      ?- constrain_values(4, 4, Qs), Qs = [3,2|_].
      no

    Finite Domains (I)

    • A finite domain constraint solver associates each variable with a finite subset of
    • Example:
      Can be represented as, e.g., E :: [-123, -10..4, 10] [Eclipse notation]
      or as E in -123\/ (-10..4)\/ 10 [Ciao notation]
    • We can:
      • Establish the domain of a variable (in).
      • Perform arithmetic operations (+, -, *, /) on the variables
      • Establish linear relationships among arithmetic expressions (#=, #<, #=<)
    • These operations / relationships narrow the domains of the variables
    • Note: In Ciao this functionality is loaded with a
      :- use_package(clpfd).
      directive in the source code –or, equivalently, adding in the module declaration:
      :- module(_, ... , [clpfd]).

    Finite Domains (II)

    Finite domains: some first queries

    • First, we load the finite domains package:

      ?- use_package(clpfd).
    • Then:

      ?- X #= A + B, A in 1..3, B in  3..7.
      -> The respective minimums and maximums are added -> There is no unique solution
    • Similarly:

      ?- X #= A - B, A in 1..3, B in 3..7.
      -> The min value of X is the min value of A minus the max value of B -> (Similar for the maximum values)
    • Putting more constraints results in a unique solution:

      ?- X #= A - B, A in 1..3, B in 3..7, X #>= 0.
    • We can use domain/3 to define the (finite) domains of variables:

      ?- domain([X, Y, Z],1,1000), X*X+Y*Y #= Z*Z, X #>= Y.
    • Labeling can be used to get the concrete values that correspond within those ranges:

      ?- domain([X, Y, Z],1,1000), X*X+Y*Y #= Z*Z, X #>= Y, labeling([],[X,Y,Z]).
    • And minimize/2 can be used to find the values that minimize some variable:

      ?- domain([X, Y, Z],1,100), X*X+Y*Y #= Z*Z, X #>= Y, minimize(labeling([],[X,Y,Z]), Y).

    Finite Domains (III)

    Some useful primitives in finite domains:

    • domain(Variables, Min, Max): A shorthand for several in constraints
    • labeling(Options, VarList):
      • instantiates variables in VarList to values in their domains
      • Options dictates the enumeration/search order
      ?- domain([X, Y, Z],1,1000), X*X+Y*Y #= Z*Z, X #>= Y, 
         labeling([],[X,Y,Z]).
      X = 4, Y = 3, Z = 5,
      X = 8, Y = 6, Z = 10, 
      X = 12, Y = 5, Z = 13, 
      ...
    • minimize(G, X): solve G minimizing the value of variable X
    • This can be used to minimize (c.f., maximize) a solution
    • Also some more complex constraints available (e.g., all_different/1).

    A classic example: SEND    MORE    MONEY

    Find what decimal digit (i.e., 0 to 9) each letter stands for so that the addition is correct:

      S E N D
    + M O R E
    _________
    M O N E Y
    • Each letter must be a different digit
    • No 0's at the left (i.e., not S or M)
    • A first version:

      :- module(_,_).
      :- use_package(clpfd).
      % -------------------------------------------------------------------
      % The classic SEND MORE MONEY example
      
      smm_no_labeling(Vars) :-
          Vars = [S,E,N,D,M,O,R,Y], 
          domain(Vars, 0, 9),  % All digits 0..9
          0 #< S, 0 #< M,      % No leftmost zeros
          all_different(Vars), % All digits different
          % And the arithmetic constraints: 
                    S*1000 + E*100 + N*10 + D +
                    M*1000 + O*100 + R*10 + E #=
          M*10000 + O*1000 + N*100 + E*10 + Y.
      Try:

      ?- smm_no_labeling([S,E,N,D,M,O,R,Y]).
      -> We need labeling!
    • Version adding labeling (and a pretty printer):

      smm(Vars) :-
          Vars = [S,E,N,D,M,O,R,Y], 
          domain(Vars, 0, 9),
          0 #< S, 0 #< M,
          all_different(Vars),
          S*1000 + E*100 + N*10 + D +
          M*1000 + O*100 + R*10 + E #=
          M*10000 + O*1000 + N*100 + E*10 + Y,
          labeling([], Vars), 
          pp_smm(Vars).
      Try:

      ?- smm([S,E,N,D,M,O,R,Y]).
      % A simple pretty printer: 
      pp_smm([S,E,N,D,M,O,R,Y]) :-
          format("~n   ~w ~w ~w ~w~n",  [S,E,N,D]),
          format(  " + ~w ~w ~w ~w~n",  [M,O,R,E]),
          format(  " ---------~n",             []),
          format( " ~w ~w ~w ~w ~w~n",[M,O,N,E,Y]).
    exercise! ▶

    A Project Management Problem (I)

    • The job whose dependencies and
      task lengths are given by this graph...
    • ... should be finished in 10 time units or less.

    The job dependencies and task lengths are given by this graph:

        0*G
        ^ ^
       /  |
      /   |
    4*E  1*F
     ^^   ^^
     | \  | \
     |  \ |  \
    1*B  2*C  3*D
     ^    ^    ^
      \   |   /
       \  |  /
         0*A
    Plus, the whole job should be finished in 10 time units or less.

    :- module(_,_).
    :- use_package(clpfd).
    % -------------------------------------------------------------------
    % A project management problem (1)
    
    % Constraints:
    pn1(A,B,C,D,E,F,G) :- 
       domain([A,B,C,D,E,F,G], 0, 10), 
        A #>= 0, G #=< 10, 
        B #>= A, C #>= A, D #>= A, 
        E #>= B + 1, E #>= C + 2, 
        F #>= C + 2, F #>= D + 3, 
        G #>= E + 4, G #>= F + 1.
    • Queries:

      ?- use_package(clpfd).
      ?- pn1(A,B,C,D,E,F,G).
      -> Tasks have slack, let's minimize the final time (G):

      ?- minimize(pn1(A,B,C,D,E,F,G),G).
      -> We identify the critical path, some slack elsewhere.

      And we can minimize more variables:

      ?- minimize(pn1(A,B,C,D,E,F,G),G), minimize(true, F), minimize(true,B).

    A Project Management Problem (II)

    • Query:

      ?- pn1(A,B,C,D,E,F,G). 
      A in 0..4, B in 0..5, C in 0..4, 
      D in 0..6, E in 2..6, F in 3..9, G in 6..10.
    • Note the slack of the variables
    • Some additional constraints must be respected as well, but are not shown by default
    • Minimize the total project time:

      ?- minimize(pn1(A,B,C,D,E,F,G), G).
          A = 0, B in 0..1, C = 0, D in 0..2, 
          E = 2, F in 3..5, G = 6
    • Variables without slack represent critical tasks

    A Project Management Problem (III)

    An alternative setting:

    • Assume we can accelerate or slow down task F:
      (we make the length of F a variable X)

    The job dependencies and task lengths are now given by this graph, where we assume we can accelerate or slow down task F (we make the length of F a variable X )

        0*G
        ^ ^
       /  |
      /   |
    4*E  X*F
     ^^   ^^
     | \  | \
     |  \ |  \
    1*B  2*C  3*D
     ^    ^    ^
      \   |   /
       \  |  /
         0*A
    Still, the whole job should be finished in 10 time units or less.

    :- module(_,_).
    :- use_package(clpfd).
    % -------------------------------------------------------------------
    % A project management problem (2)
    
    pn2(A, B, C, D, E, F, G, X) :- 
        domain([A,B,C,D,E,F,G,X], 0, 10),
        A #>= 0, G #=< 10, 
        B #>= A, C #>= A, D #>= A, 
        E #>= B + 1, E #>= C + 2, 
        F #>= C + 2, F #>= D + 3, 
        G #>= E + 4, G #>= F + X.
    But going faster on F has a cost, so we do not want to accelerate it more than needed! –> minimize G and maximize X:

    • Queries:

      ?- use_package(clpfd).
      ?- pn2(A, B, C, D, E, F, G, X).
      ?- minimize(pn2(A,B,C,D,E,F,G,X), G), maximize(true,X).

    A Project Management Problem (IV)

    • Now two independent tasks B and D whose lengths X and Y are not fixed:

    • We can finish any of B or D in 2 time units at best → X #>= 2, Y #>= 2
    • Plus, some shared resource disallows finishing both tasks in 2 time units: they will instead take 6 time units → X #>= 2, Y #>= 2, X +  Y #= 6

    A Project Management Problem (V)

    • Constraints describing the net:

      The job dependencies and task lengths are now given by this graph, where we now have two independent tasks B and D whose lengths X and Y are not fixed:

          0*G
          ^ ^
         /  |
        /   |
      4*E  1*F
       ^^   ^^
       | \  | \
       |  \ |  \
      X*B  2*C  Y*D
       ^    ^    ^
        \   |   /
         \  |  /
           0*A
      Still, the whole job should be finished in 10 time units or less.

      But we can finish any of B or D in 2 time units at best -> X #>=2, Y #>=2

      Plus, some shared resource disallows finishing both tasks in 2 time units: they will instead take 6 time units -> X #>=2, Y #>=2, X + Y #= 6

      :- module(_,_).
      :- use_package(clpfd).
      % -------------------------------------------------------------------
      % A project management problem (3)
      
      pn3(A,B,C,D,E,F,G,X,Y) :- 
          domain([A,B,C,D,E,F,G,X,Y], 0, 10),
          A #>= 0, G #=< 10, 
          X #>= 2, Y #>= 2, X + Y #= 6, 
          B #>= A, C #>= A, D #>= A, 
          E #>= B + X, E #>= C + 2, 
          F #>= C + 2, F #>= D + Y, 
          G #>= E + 4, G #>= F + 1.
      • Queries:

        ?- use_package(clpfd).
        ?- minimize(pn3(A,B,C,D,E,F,G,X,Y), G).
        -> We must devote more resources to task B. -> All tasks but F and D are critical now.

        We can use labeling for D and F:

        ?- minimize(pn3(A,B,C,D,E,F,G,X,Y),G), labeling([],[D,F]).
        Also:

        ?- pn3(A,B,C,D,E,F,G,X,Y), minimize(labeling([],[A,B,C,D,E,F,G,X,Y]),G).
    • I.e., we must devote more resources to task B exercise! ▶
    • All tasks but F and D are critical now
    • Sometimes, minimize/2 not enough to provide best solution (pending constr.):

      ?- minimize(pn3(A,B,C,D,E,F,G,X,Y),G), labeling([],[D,F]).

    The N-Queens Problem Using Finite Domains   (in Ciao clpfd syntax)

    • We represent the state as a list holding in which column the queen is placed for each row. E.g., for 4 queens:

      -----------------
      |   | Q |   |   |
      -----------------
      |   |   |   | Q |
      -----------------
      | Q |   |   |   |
      -----------------
      |   |   | Q |   |
      -----------------
      is represented as [2, 4, 1, 3].
    • General idea: from a partial solution, non-deterministically select a new queen, check safety of new queen against those already placed, if OK add new queen to partial solution and continue, otherwise backtrack to the selection and choose another possible queen.
    • CLP(fd) version:

      :- module(_,_).
      :- use_package(clpfd).
      % -------------------------------------------------------------------
      % N Queens in CLP(fd)
      
      queens_fd(N, Qs, Type) :-    % Type is the labeling strategy
              constrain_values(N, N, Qs),
              all_different(Qs),  % built-in constraint
              labeling(Type,Qs).
      
      constrain_values(0, _N, []).
      constrain_values(N, Range, [X|Xs]) :-
              N > 0, N1 is N - 1, X in 1 .. Range,
              constrain_values(N1, Range, Xs), no_attack(Xs, X, 1).
      
      no_attack([], _Queen, _Nb).
      no_attack([Y|Ys], Queen, Nb) :-
              Queen #\= Y + Nb, Queen #\= Y - Nb, Nb1 is Nb + 1,
              no_attack(Ys, Queen, Nb1).
      Queries:

      ?- time(queens_fd(20, Q, [ff])).
      ?- time(queens_pl(20,Q)).
      ?- time(queens_fd(100, Q, [ff])).
      ?- time(queens_pl(100,Q)).
      (Takes a long time...) -> CLP(fd) very good at finding a solution!
    • What about finding all solutions?

      fd_all(N,L) :- findall(X, queens_fd(N,X,[ff]),S), length(S,L).
      pl_all(N,L) :- findall(X, queens_pl(N,X),S),      length(S,L).
      ?- time(pl_all(11, L)).
      ?- time(fd_all(11, L)).
      ?- time(pl_all(12, L)).
      ?- time(fd_all(12, L)).
      ?- time(pl_all(13, L)).
      ?- time(fd_all(13, L)).
      -> CLP(fd) (at least this program) not so good for all solutions!
    • The standard Prolog version:

      queens_pl(N, Qs) :- 
            queens_pl_list(N, Ns), 
            queens_pl_(Ns, [], Qs).
      
      queens_pl_([], Qs, Qs).
      queens_pl_(Unplaced, Placed, Qs) :-
          selectq(Unplaced, Q, NewUnplaced), 
          pl_no_attack(Placed, Q, 1),
          queens_pl_(NewUnplaced, [Q|Placed], Qs).
      
      pl_no_attack([], _Queen, _Nb).
      pl_no_attack([Y|Ys], Queen, Nb) :-
          Queen =\= Y + Nb, Queen =\= Y - Nb, Nb1 is Nb + 1,
          pl_no_attack(Ys, Queen, Nb1).
      
      selectq([X|Ys], X, Ys).
      selectq([Y|Ys], X, [Y|Zs]) :- selectq(Ys, X, Zs).
      
      queens_pl_list(0, []).
      queens_pl_list(N, [N|Ns]) :- N > 0, N1 is N - 1, queens_pl_list(N1, Ns).
    exercise! ▶

    CLP() (a.k.a. Logic Programming)

    • Equations over Finite Trees
    • Check that two trees are isomorphic (same elements in each level)

      iso(Tree, Tree). 
      iso(t(R, I1, D1), t(R, I2, D2)) :- 
          iso(I1, D2), 
          iso(D1, I2). 
      
      ?- iso(t(a, b, t(X, Y, Z)), t(a, t(u, v, W), L)). 
      L=b, X=u, Y=v, Z=W ? ; 
      L=b, X=u, Y=W, Z=v ? ; 
      L=b, W=t(_C,_B,_A), X=u, Y=t(_C,_A,_B), Z=v ? ; 
      L=b, W=t(_E,t(_D,_C,_B),_A), X=u, Y=t(_E,_A,t(_D,_B,_C)), Z=v ?  

    CLP()

    • Equations over finite strings
    • Primitive constraints: concatenation (.), string length (::)
    • Find strings meeting some property:

      ?- "123".Z = Z."231", Z::0.    ?- "123".Z = Z."231", Z::3.  
      no                             no                           
                                                                  
      ?- "123".Z = Z."231", Z::1.    ?- "123".Z = Z."231", Z::4.  
      Z = "1"                        Z = "1231"                   
      
      ?- "123".Z = Z."231", Z::2.
      no
    • These constraint solvers are very complex
    • Often incomplete algorithms are used

    CLP(())

    • Word equations plus arithmetic over (rational numbers)
    • Prove that the sequence has a period of length 9 (for any starting )
    • Strategy: describe the sequence, try to find a subsequence such that the period condition is violated
    • Sequence description (syntax is Prolog III slightly modified):

      seq(<Y, X>).                          abs(Y, Y) :- Y >= 0.
      seq(<Y1 - X, Y, X>.U) :-              abs(Y,-Y) :- Y < 0.
          seq(<Y, X>.U) 
          abs(Y, Y1).
    • Query: Is there any 11–element sequence such that the 2–tuple initial seed is different from the 2–tuple final subsequence (the seed of the rest of the sequence)?

      ?- seq(U.V.W), U::2, V::7, W::2, U#W.
      fail

    Summarizing

    • In general:
      • Data structures (Herbrand terms) for free
      • Each logical variable may have constraints associated with it (and with other variables)
    • Problem modeling :
      • Rules represent the problem at a high level
        • Program structure, modularity
        • Recursion used to set up constraints
      • Constraints encode problem conditions
      • Solutions also expressed as constraints
    • Combinatorial search problems:
      • CLP languages provide backtracking: enumeration is easy
      • Constraints keep the search space manageable
    • Tackling a problem:
      • Keep an open mind: often new approaches possible

    Complex Constraints

    • Systems offer also complex constraints:
      • Often allow expressing compactly many simpler constraints
    • May be operationally treated as passive constraints
    • E.g.: cardinality operator     meaning that the number of true constraints lies between and (which can be variables themselves)

      • If , all constraints must hold
      • If , one and only one constraint must be true
      • Constraining , we force the conjunction of the negations to be true
      • Constraining , the disjunction of the constraints is specified
    • Disjunctive constructive constraint:
      • If properly handled, avoids search and backtracking
      • E.g.:

    Other Primitives

    • CLP(X) systems usually provide additional primitives
    • E.g.:
      • enum(X)   enumerates X inside its current domain
      • maximize(X) (c.f. minimize(X))   works out maximum (minimum value) for X under the active constraints
      • delay Goal until Condition    specifies when the variables are instantiated enough so that Goal can be effectively executed
        • Its use needs deep knowledge of the constraint system
        • Also widely available in Prolog systems
        • Not really a constraint: control primitive

    Implementation Issues: Satisfiability

    • Algorithms must be incremental in order to be practical
    • Incrementality refers to the performance of the algorithm
    • It is important that algorithms to decide satisfiability have a good average case behavior
    • Common technique: use a solved form representation for satisfiable constraints
    • Not possible in every domain
    • E.g. in constraints are represented in the form , where
      • each denotes a term structure containing variables from
      • no variable appears in
      (i.e., idempotent substitutions, guaranteed by the unification algorithm)

    Implementation Issues: Backtracking in CLP(X)

    • Implementation of backtracking more complex than in Prolog
    • Need to record changes to constraints
    • Constraints typically stored as an association of variable to expression
    • Trailing expressions is, in general, costly: cannot be stored at every change
    • Avoid trailing when there is no choice point between two successive changes
    • A standard technique: use time stamps to compare the age of the choice point with the age of the variable at the time of last trailing

    Implementation Issues: Extensibility

    • Constraint domains often implemented now in Prolog-based systems using:
      • Attributed variables [Neumerkel,Holzbaur]:
        • Provide a hook into unification.
        • Allow attaching an attribute to a variable.
        • When unification with that variable occurs, user-defined code is called.
        • Used to implement constraint solvers (and other applications, e.g., distributed execution).
      • Constraint handling rules (CHRs):
        • Higher-level abstraction.
        • Allows defining propagation algorithms (e.g., constraint solvers) in a high-level way.
        • Often translated to attributed variable-based low-level code.

    Attributed Variables Example: Freeze

    • Primitives:

      • attach_attribute(X,C)
      • get_attribute(X,C)
      • detach_attribute(X)
      • update_attribute(X,C)
      • verify_attribute(C,T)
      • combine_attributes(C1,C2)
    • Example: Freeze

      freeze( X, Goal) :-
        attach_attribute( V, frozen(V,Goal)),
        X = V.
      
      verify_attribute( frozen(Var,Goal), Value) :-
        detach_attribute( Var),
        Var = Value, 
        call(Goal).
      
      combine_attributes( frozen(V1,G1), frozen(V2,G2)) :-
        detach_attribute( V1),
        detach_attribute( V2),
        V1 = V2,
        attach_attribute( V1, frozen(V1,(G1,G2))).

    Programming Tips

    • Over-constraining:

      • Seems to be against general advice "do not perform extra work", but can actually cut more search space
      • Specially useful if infer is weak
      • Or else, if constraints outside the domain are being used
    • Use control primitives (e.g., cut) very sparingly and carefully
    • Determinacy is more subtle (partially due to constraints in non–solved form)
    • Choosing a clause does not preclude trying other exclusive clauses
      (as with Prolog and plain unification)
    • Compare:

      max(X,Y,X) :- X .>. Y.              ?- max(X, Y, Z).  
      max(X,Y,Y) :- X .=<. Y.             Z .=. X, Y .<. X ;    
      with

      max(X,Y,X) :- X .>. Y, !.           ?- max(X, Y, Z).  
      max(X,Y,Y) :- X .=<. Y.             Z .=. X, Y .<. X      

    CLP Systems

    • As mentioned before, CLP defines a class of languages obtained by

      • Specifying the particular constraint system(s)
      • Specifying the Computation and Selection rules
    • Most practical systems include also the Herbrand domain with "=", but then add different domains and/or solver algorithms
    • Most use the Computation and Selection rules of Prolog

    Some of the Original CLP Systems

    • CLP():

      • Linear arithmetic over reals () – CLP(R)
        Incremental Gaussian elimination and incremental Simplex
    • PrologIII:

      • CLP(R)
      • Boolean (), 2-valued Boolean Algebra – CLP(B)
      • Infinite (rational) trees ()
      • Equations over finite strings – CLP(WE)
    • CHIP (and its successor: the ILOG library):

      • CLP(FD), CLP(B), CLP(Q).
      • User–defined constraints and solver algorithms
    • BNR-Prolog / CLP(BNR):

      • Arithmetic over reals (closed intervals); CLP(FD), CLP(B).
    • RISC–CLP:

      • Arithmetic constraints over reals, also non-linear
        (using Presburger arithmetic)

    Some Current CLP Systems

    • → Most Prolog systems now support constraints!
    • clp(FD)/gprolog:

      • CLP(FD).
    • SICStus:

      • CLP(R), CLP(Q), CLP(FD).
      • Attributed variables and CHR for adding domains.
    • ECLPS:

      • CLP(R), CLP(Q), CLP(FD).
    • SWI:

      • CLP(R), CLP(Q), CLP(FD), CLP(B).
      • Attributed variables and CHR for additional domains.
    • Ciao:

      • CLP(R), CLP(Q), CLP(FD).
      • Attributed variables and CHR for additional domains.
      • Different domains can be activated on a per-module basis (packages).

    Some origins and other instances

    • Ancestors:
      • SKETCHPAD (1963), Waltz's algorithm (1965?), THINGLAB (1981), MACSYMA (1983), ...
    • Constraints in logic languages: – the origin of "constraint programming":
      • General theory developed (Jaffar and Lassez '87).
      • First, standalone systems developed: clpr, CHIP, ...
      • Later, included in mainstream Prolog implementations.
      • Has given rise to a whole research area!
    • Constraints in imperative languages:
      • Equation solving libraries (ILOG, GECODE, ...)
      • Timestamping of variables: {`x := x + 1` }
        (similar to iterative methods in numerical analysis)
    • Constraints in functional languages, via extensions:
      • Evaluation of expressions including free variables.
      • Absolute Set Abstraction.