=Paper= {{Paper |id=Vol-1335/wlp2014_paper2 |storemode=property |title=Describing and Measuring the Complexity of SAT encodings for Constraint Programs |pdfUrl=https://ceur-ws.org/Vol-1335/wlp2014_paper2.pdf |volume=Vol-1335 |dblpUrl=https://dblp.org/rec/conf/wlp/BauW14 }} ==Describing and Measuring the Complexity of SAT encodings for Constraint Programs== https://ceur-ws.org/Vol-1335/wlp2014_paper2.pdf
    Describing and Measuring the Complexity of
      SAT encodings for Constraint Programs

                      Alexander Bau? and Johannes Waldmann

                HTWK Leipzig, Fakultät IMN, 04277 Leipzig, Germany
                     abau|waldmann@imn.htwk-leipzig.de



        Abstract. The CO4 language is a Haskell-like language for specifying
        constraint systems over structured finite domains. A CO4 constraint sys-
        tem is solved by an automatic transformation into a satisfiability problem
        in propositional logic that is handed to an external SAT solver.
        We investigate the problem of predicting the size of formulas produced by
        the CO4 compiler. The goal is to help the programmer in understanding
        the resource consumption of CO4 on his program. We present a basic cost
        model, with some experimental data, and discuss ongoing work towards
        static analysis. It turns out that analysis steps will use constraint systems
        as well.


1     Introduction
CO4 is a constraint programming language that allows to write a constraint
problem as declarative specification. The CO4 compiler solves it by transforming
the constraint to a propositional satisfiability problem, so that a SAT solver
can be applied. Syntactically, the language is a subset of the purely functional
programming language Haskell [3] that includes user-defined algebraic data types
and recursive functions defined by pattern matching, as well as higher-order
polymorphic types.
    In CO4 , a constraint system over elements of set U is specified by a parametrized
predicate constraint : P × U → Bool, where P denotes the parameter domain.
Thus, constraint does not denote a single constraint, but a family of con-
straints. For a given constraint and parameter p ∈ P , u ∈ U is a solution if
constraint (p, u) = True.
    For the CO4 compiler to generate a propositional encoding, the input constraint
is transformed into an abstract program constraint’ that operates on abstract
values. An abstract value represents an undetermined value of the input program
by encoding the constructor’s index using propositional formulas. Evaluating the
abstract program generates the final formula that is passed to the external SAT
solver.
    It is desirable to predict the runtime of the SAT solver for a generated propo-
sitional encoding. Such a prediction is hard because as it depends on a lot of
design and implementation decisions of the SAT solver. Therefore we take the
?
    This author is supported by ESF grant 100088525
    size of the SAT encoding as a reasonable indicator for its hardness. To esti-
    mate the size of the encoding, we introduce a cost model for abstract values
    and abstract programs. This cost model captures two important facts: the size
    of intermediate abstract values and the costs to evaluate them. Especially the
    evaluation of case distinctions on abstract values is not obvious, and often they
    cannot be evaluated in a straightforward manner.
        This paper has three parts. The first part illustrates the syntax and seman-
    tics of CO4 (Section 2) and gives an overview on of the propositional encoding
    (Section 3). This is a summary of material that has already been published[1].
    The second part presents current work on cost analysis: in Section 4 we present
    our cost model, and in Section 5 we analyze the cost of the merge operation,
    which is a basic operator in our translation scheme. Section 6 illustrates how the
    current CO4 implementation measures concrete costs of SAT-compiled function
    calls. The third part outlines future work in static analysis of CO4 programs.
    Section 7 describes moded types and their inference, which will allow a more effi-
    cient propositional encoding of case distinctions. Section 8 describes an approach
    to bound function costs by resource types.


    2     Syntax and Semantics of CO4

    Syntactically, CO4 is a subset of Haskell. Domains are specified by algebraic data
    types (ADT), where constructors enumerate the values of the type.
1       data Bool       = False | True
2       data Color      = Red   | Green | Blue
3       data Monochrome = Black | White
4       data Pixel      = Colored Color | Background Monochrome

        CO4 supports recursive ADTs as well, but recursions must be restricted while
    generating a propositional encoding. We do not deal with recursions in the scope
    of this paper.
        A constructor may be parametrized either by types or type variables.
1       data Pair   a b = Pair a b
2       data Either a b = Left a | Right b

        Inspecting the constructor of a value d of some ADT is done by a case
    distinction on d (the discriminant of the case distinction):
1    case color of Blue      -> True
2                  otherwise -> False

       Case distinctions provides conditional branching of the control-flow. Other
    kinds of expressions in CO4 are constructor calls, applications, abstractions and
    local bindings. CO4 provides restricted support of higher-order, polymorphic
    functions. Besides type definitions, constraint systems in CO4 contain global
    function bindings with constraint being the top-level function:
1        data Bool       = False | True
2        data Color      = Red   | Green | Blue
3        data Monochrome = Black | White
4        data Pixel      = Colored Color | Background Monochrome
5

6        constraint :: Bool -> Pixel -> Bool
7        constraint p u = case p of
8          False -> case u of Background m -> True
9                             otherwise    -> False
10         True -> isBlue u
11

12       isBlue :: Pixel -> Bool
13       isBlue u = case u of
14         Colored color -> case color of Blue      -> True
15                                        otherwise -> False
16         Background m -> False
                          Listing 1.1. A trivial constraint over pixels

         Semantically, a constraint system in CO4 over elements of set U is a binary
     predicate constraint : P × U → Bool on U and some parameter domain P . In
     Listing 1.1, P = Bool and U = Pixel.
         For a given parameter p ∈ P , u ∈ U is a solution if constraint (p, u) = True.
     One advantage of specifying constraint systems in a functional language like
     CO4 is that a solution can be tested against the constraint simply by evaluat-
     ing constraint (p, u). Note that CO4 expressions are evaluated strictly, while
     Haskell features a non-strict evaluation strategy.


     3     Propositional Encoding of CO4 constraints
     In the following, we call the source constraint a concrete program. Concrete
     programs operate on concrete values, e.g., the concrete program in Listing 1.1
     operates on concrete values like False, White or Colored Red.
        To find a solution u ∈ U for a constraint constraint : P × U → Bool and a
     parameter p ∈ P , CO4 performs the following steps:
      1. The concrete program is transformed into an abstract program. An abstract
         program doesn’t operate on concrete values, but on abstract values.
      2. Evaluating the abstract program for an abstract value that represents pa-
         rameter p gives a formula f ∈ F in propositional logic.
      3. An external SAT solver is called to find a satisfying assignment σ ∈ Σ for f .
      4. If there is a satisfying assignment, the solution u ∈ U is constructed from
         σ. Optionally, testing whether constraint p u = True ensures that there
         are no implementation errors. This check must always succeed if there is a
         solution.
        In the following we briefly illustrate the first two steps of this process. Firstly,
     an abstract program is generated from a given concrete program. This transfor-
     mation not only modifies the program structure, the domain is changed as well.
Data Transformation An abstract program is an untyped, first-order and im-
perative program on abstract values.

Definition 1. Assume F being the set of propositional formulas. Then, the set
of abstract values A is the smallest set with A = F∗ × A∗ where F∗ denotes the
                                                                           →
                                                                           − −
set of sequences with elements from F. An abstract value a ∈ A is a tuple ( f , →
                                                                                a)
         →
         −                  →
                            −
of flags f and arguments a .

    An abstract value a ∈ A represents a (maybe unknown) value of a concrete
type T . The flags of an abstract value a ∈ A encode the indices of T ’s constructors
in binary code using propositional formulas.

Example 1. For an abstract value a1 ∈ A to represent a value of the ADT data
Color = Red | Green | Blue | Purple it must contain two flags f1 , f2 ∈ F
because Color has four constructors. Thus, a1 = ((f1 , f2 ), ()). a1 has no argu-
ments because none of Color’s constructors has any arguments.
     Consider an ADT data Maybe a = Nothing | Just a and an abstract value
a2 ∈ A that is supposed to represent a value of type Maybe Color. As Maybe con-
sists of two constructors, one flag f3 ∈ F is needed to discriminate both. Thus,
(f3 , a1 ) is a proper value for a2 . Note that a2 has a single argument a1 that
encodes the constructor argument of type Color of Maybe’s Just constructor.

    As the flags of an abstract value a ∈ A may contain propositional variables,
a can be decoded to different values according to the Boolean values that are
assigned to these variables. By decodeT : A ×Σ → T we denote a mapping from
abstract values and propositional assignments Σ to concrete values.
    If the flags of an abstract value a ∈ A don’t contain propositional variables,
then the flags of a index a particular constructor and a can only be decoded to a
single concrete value. By encodeT : T → A we denote a mapping from concrete
values to abstract values that represent a fixed value.

Example 2. Recall the ADTs defined in Example 1 and assume the flags of an
abstract value reference a constructor’s index using binary code where the first
flag encodes the most significant bit. Then:

                      encodeColor (Blue) = ((True, False), ())
          encodeMaybe Color (Just Blue) = (True, ((True, False), ()))


    As we’ve omitted details about abstract values we don’t provide definitions
for encode and decode.

Program Transformation The program structure of abstract programs resem-
bles the structure of their concrete counterparts. The most important difference
concerns case distinctions: while concrete values may be examined by matching
on their constructor, this is often not possible for abstract values. That’s be-
cause an abstract value’s flags may contain propositional variables. Therefore, it
is undetermined which constructor is indexed by the flags and there is no way
to known which branch to evaluate. Thus, all branches must be evaluated and
their result is merged according to the discriminant of the case distinction.
Example 3. The following case distinction matches on a Boolean value x in a
concrete program:
                   r = case x of { False -> g ; True -> h }

In the abstract counterpart of this expression, the abstract values g 0 and h0 of
both branches are evaluated and merged according to x
                   r0 = let _1 = g 0
                            _2 = h0
                          in mergex0 (_1,_2)

where r0 (resp. x0 , g 0 , h0 ) denote the abstract counterpart of r (resp. x, g, h).
    The function mergex : A∗ → A encodes a case distinction on a value x ∈ A
using the flags of x and the abstract values of all evaluated branches. We don’t
give a definition for merge, but illustrate its semantics by the following example.
Example 4. Recall the case distinction in Example 3 and assume r0 (resp. x0 , g 0 , h0 )
denotes the abstract counterpart of r (resp. x, g, h). The following two clauses
are emitted when evaluating mergex0 (g 0 , h0 ):

                        (x0 ≡ encodeBool (False) =⇒ r0 ≡ g 0 )
                      ∧ (x0 ≡ encodeBool (True) =⇒ r0 ≡ h0 )

    Informally, both clauses encode the semantics of the original case distinction
in terms of abstract values: r0 equals g 0 if x0 equals encodeBool (False), otherwise
r0 equals h0 .
   However, if none of the flags of an abstract value contain any propositional
variables, then the constructor that is indexed by these flags can be determined
and the associated branch can be evaluated. In this case it is not necessary to
evaluate the other branches.

Evaluation of Abstract Programs The constraint : P × U → Bool function in
a concrete program has a counterpart constraint’ : A × A → A in the abstract
program of the same arity. Evaluating constraint’ p0 u0 on
 – p0 = encodeP (p) for some parameter p ∈ P , and
 – u0 ∈ A, which represents a undetermined value in U ,
    gives a value a ∈ A that represents a Boolean value, i.e., a contains a single
flag f ∈ F. Solving f using an external SAT solver gives a satisfying assignment
σ ∈ Σ for all variables in f if there is such an assignment. The final solution
u ∈ U can be constructed by decodeU (u0 , σ).
    We refer to [1] for more technical details on the transformation process.
4   Cost Model

We illustrate an approach for formalizing the costs associated with a function
in a CO4 program. For readability we stick to unary functions and omit details
about functions of higher arities.
    We measure the cost of a function f : A → B in a concrete program by
analyzing its counter part f 0 : A → A in the corresponding abstract program.
The costs of f 0 depend on the size of its argument. Thus, we introduce a function
size : A → N to measure the size of an abstract value.

Example 5. There are at least two naive definitions for size: one that counts the
number of nested abstract values
                                                              n
                             →
                             −                              X
                      size1 ( f , (a1 , . . . , an )) = 1 +   size1 (ai )
                                                            i=1

and one that counts the number of flags in an abstract value
                                                                      n
                                                                      X
               size2 ((f1 , . . . , fm ), (a1 , . . . , an )) = m +         size2 (ai )
                                                                      i=1


     Fixing a particular implementation for size, the cost of the abstract function
f 0 is described by a pair of functions sf , cf : N → N.

Definition 2. sf (n) gives the maximal output size for all arguments of f 0 with
size n or smaller:

      sf (n) = max{size(f (encodeA (x))) | x ∈ A ∧ size(encodeA (x)) ≤ n}

    Whereas sf quantifies the size of a function’s result, cf measures the evalu-
ation costs of f 0 .

Definition 3. cf (n) gives the evaluation costs for all arguments of f 0 with size
n or smaller

      cf (n) = max{work(f, encodeA (x)) | x ∈ A ∧ size(encodeA (x)) ≤ n}

where work(f, x) equals the cost of evaluating f 0 (encodeA (x)) in the abstract
program.

    We can instantiate this scheme in several ways: for example, work(f, x) could
give the number of propositional variables or clauses that are allocated while
computing the abstract value f 0 (encodeA (x)). Other techniques may include ad-
ditional characteristics about the propositional encoding, like the number of
literals or the depth of the formula.
5    Cost of merge

Example 4 illustrated the semantics of the merge operation on abstract values.
Now we quantify the cost of merge in terms of the cost model in Section 4.
   Assume the following case distinction with n branches b1 , . . . , bn in a function
f:
                          f (x) = case x of C1 -> b1
                                              ...
                                            Cn -> bn
                   Listing 1.2. A case distinction over n branches

where f 0 : A → A denotes the abstract counterpart of f . In order to evalu-
ate f 0 (x0 ) for some abstract argument x0 ∈ A we need to evaluate all abstract
branches b0i ∈ A for i ∈ [1, n] and merge the results by mergex0 (b01 , . . . , b0n ). We
denote the result of this merge by r0 ∈ A.
   A first cost measure determines the numbers of variables that are needed to
represent the result of an application of merge (variable-cost).

Definition 4. workV (f, x) denotes the variable-cost of function f in Listing 1.2
and equals the maximum number of flags in the branches, i.e., if mi denotes the
number of flags in branch b0i for i ∈ [1, n], then

                        workV (f, x) = max{mi | 1 ≤ i ≤ n}

    As the result of f 0 (x) must equal one of the branches b0i (c.f. Example 4) it
is reasonable for workV to assume that r0 must consist of the maximum number
of flags that are present in the abstract branches b0i ∈ A for 1 ≤ i ≤ n.
    Furthermore, we define the clause-cost of an application of merge. Example 4
illustrated that the flags in a result of merge encode the case distinction in
terms of abstract values. The clause-costs represent the number of clauses in a
propositional formula that are needed to encode a case distinction.

Definition 5. workC (f, x) denotes the clause-cost of function f in Listing 1.2
where n denotes the number of branches:

                          workC (f, x) = 2 ∗ workV (f, x) ∗ n

    workC is reasonable because two clauses are emitted for each of the workV (f, x)
flags in r0 and each of the n branches.


6    Profiling CO4

In the following we compare the profiling output of CO4 for some examples and
show the relation to the previously defined cost-model.
    The first example illustrates the difference between the cost of evaluating a
concrete program and an abstract program.
1      data Bool = False | True deriving Show
2      data T    = T1 | T2 | T3 deriving Show
3

4      g :: T -> Bool
5      g t = case t of T1 -> True
6                      T2 -> False
7                      T3 -> False
8

9      f1 :: Bool -> Bool
10     f1 b = case b of False -> g T1
11                      True -> g T2
12

13     f2 :: Bool -> Bool
14     f2 b = g (case b of False -> T1
15                         True -> T2)
                 Listing 1.3. Profiling two semantically equivalent functions

        Listing 1.3 defines two functions f1,f2 with the same concrete semantics.
     Assume f1’ (resp. f2’,g’) being the abstract counterpart of f1 (resp. f2,g).
     Further assume that b ∈ A is an abstract value that represents an undetermined
     value of type Bool. Then, evaluating f1’ b gives
       ("f1’", {numCalls = 1, numVariables = 1, numClauses = 4})
       ("g’", {numCalls = 2, numVariables = 0, numClauses = 0})

        g’ does not allocate any variables nor clauses as its argument is constant in
     both calls g T1 and g T2 in the concrete program. Thus, the case distinction in
     g’ can be evaluated straightforwardly without applying merge.
        f1’ is called once and allocates one variable (resp. four clauses). That matches
     the workV (resp. workC ) cost function, because

      – workV (f1, b) = max{1, 1} = 1 as each branch in f1’ is represented by an
        abstract value with one flag (because Bool has two constructors)
      – workC (f1, b) = 2 ∗ workV (f1, b) ∗ 2 = 4 as there are n = 2 branches in f1’

        On the other hand, evaluating f2’ b gives
       ("f2’", {numCalls = 1, numVariables = 2, numClauses = 8})
       ("g’", {numCalls = 1, numVariables = 1, numClauses = 6})

        Again, the profiling information matches with the cost functions workV and
     workC defined in Section 5, because

      – workV (f2, b) = max{2, 2} = 2 as each branch in f2’ is represented by an
        abstract value with two flags (because T has three constructors)
      – workC (f2, b) = 2 ∗ workV (f2, b) ∗ 2 = 8 as there are n = 2 branches in f2’
      – workV (g, t) = max{1, 1, 1} = 1 as each branch in g’ is represented by an
        abstract value with one flag (because Bool has two constructors)
      – workC (g, t) = 2 ∗ workV (g, t) ∗ 3 = 6 as there are n = 3 branches in g’
    Note that f2’ allocates more variables than f1’ because it merges branches
of type T, which has more constructors than Bool. In the second case, g’ is only
called once, but this time with an unknown argument: its argument indirectly
depends on the unknown b. Thus, g’ allocates variables and emits clauses.
    We give a more complex example: CO4 has been applied to problems of
termination analysis of term rewriting systems. One exemplary problem is the
specification of a lexicographic path order (LPO) that proves the termination of
a given term rewriting system1 .
    Profiling (inner-under):
    ("constraint’", {numCalls = 1, numVariables = 160, numClauses = 514})
    ("allHOInst’", {numCalls = 1, numVariables = 160, numClauses = 514})
    ("mapHOInst’", {numCalls = 4, numVariables = 157, numClauses = 506})
    ("globalLam’", {numCalls = 3, numVariables = 157, numClauses = 506})
    ("globalLamSat’", {numCalls = 3, numVariables = 157, numClauses = 506})
    ("lpo’", {numCalls = 41, numVariables = 154, numClauses = 500})
    ...
                     Listing 1.4. Exemplary inner-under-profiling

    Listing 1.4 shows the inner-under-profiling for a LPO constraint. For each
function f in the abstract program, inner-under-profiling associates the number
of variables and clauses to f that has been allocated by f and by all func-
tions transitively called in f . Unsurprisingly, constraint’ allocates the most
resources according to inner-under-profiling as it is the top-level function in ev-
ery abstract program.
    Profiling (inner):
    ("gtNat’", {numCalls = 9, numVariables = 36, numClauses = 171})
    ("lpo’", {numCalls = 41, numVariables = 30, numClauses = 92})
    ("ordNat’", {numCalls = 9, numVariables = 27, numClauses = 63})
    ("eqNat’", {numCalls = 19, numVariables = 27, numClauses = 99})
    ("and2’", {numCalls = 26, numVariables = 20, numClauses = 44})
    ("eqOrder’", {numCalls = 31, numVariables = 18, numClauses = 40})
    ...
                       Listing 1.5. Exemplary under-profiling

    Listing 1.5 shows the under-profiling for a LPO constraint. For each function
f in the abstract program, under-profiling only associates the number of variables
and clauses to f that has been allocated by f . Listing 1.5 shows that for the
LPO constraint the abstract function gtNat’ allocates the most propositional
variables.
    CO4 also provides information about the number of variables and clauses
allocated in the abstract program as a whole:
            #variables: 167, #clauses: 517, #literals: 1365


1
    available   at     https://github.com/apunktbau/co4/blob/master/test/CO4/
    Example/LPO.hs
   We give one more example: Listing 1.6 shows the profiling data for a CO4
specification of the n-queens problem (with n = 8)2 .
    Profiling (inner-under):
    ("constraint’", {numCalls = 1, numVariables = 2324, numClauses = 6447})
    ("allSafe’", {numCalls = 9, numVariables = 2251, numClauses = 6237})
    ("safe’", {numCalls = 36, numVariables = 2244, numClauses = 6217})
    ("noAttack’", {numCalls = 28, numVariables = 2216, numClauses = 6140})
    ("equal’", {numCalls = 717, numVariables = 1724, numClauses = 5100})
    ("noDiagon’", {numCalls = 28, numVariables = 1488, numClauses = 4012})
    ("noStraight’", {numCalls = 28, numVariables = 700, numClauses = 2044})
    ...

    Profiling (inner):
    ("equal’", {numCalls = 717, numVariables = 1724, numClauses = 5100})
    ("add’", {numCalls = 359, numVariables = 352, numClauses = 704})
    ("and2’", {numCalls = 101, numVariables = 100, numClauses = 291})
    ("not’", {numCalls = 84, numVariables = 84, numClauses = 168})
    ("less’", {numCalls = 65, numVariables = 64, numClauses = 184})

    #variables: 2397, #clauses: 6522, #literals: 16697
       Listing 1.6. Exemplary profiling for the n-queens problem (with n = 8)

   Here, inner-profiling reveals that the equal’ function allocates the most re-
sources. This is reasonable because the n-queens constraint pair-wisely compares
the position of all queens in order to exclude all possibilities for two queens to
attack each other.


7     Moded Types and Mode Inference

For the future work on CO4 , we plan to develop a mode inference system that
allows the generation of propositional encodings with fewer variables and clauses.
That is desirable as smaller formulas are often solved in less time by a SAT solver.
     Moded types allow the differentiation between expressions that are constant
during abstract evaluation and expressions that are not. This information would
allow the CO4 compiler to determine case distinctions that have a constant
discriminant, i.e., that can be evaluated during abstract evaluation without al-
locating any propositional variables.
     In this context, a mode is either ! or ?. Mode ! states that the constructor
of a value is known during abstract evaluation, while mode ? states that the
constructor of a value is not known during abstract evaluation. A moded type is
a type that has been annotated by modes. For example, List! Bool? denotes a
list type, where each of list constructor is known, but each element of type Bool
has an unknown constructor. Thus, such a type encodes a list of known length
with unknown Boolean elements.
2
    available   at   https://github.com/apunktbau/co4/blob/master/test/CO4/
    Example/QueensSelfContained.hs
    We consider a moded program to be a typed program where each type is
annotated by modes. For a moded program to be dynamically well-moded, it
is required that the constructor of all case distinctions’ discriminants must be
constant that have mode !, i.e., their flags are constant.
    We plan to develop a static mode analysis as a safe approximation for dy-
namically well-moded programs. One possible approach for a mode inference
algorithm is the construction of a Boolean constraint (because there are two
different modes) that can be solved by a SAT solver.
    A similar approach has been successfully applied to infer modes in for the
Mercury language[4].


8   Resource Types and Resource Inference

Mode analysis allows a more strict analysis on the estimated cost for a CO4
constraint system.
     A possible approach to predict the resource cost is to annotate each function
in a CO4 constraint with a resource type, where a resource type for function
f according to the cost model introduced in Section 4 is a pair of functions
sf , cf : N → N.
     A dynamically well-resource-typed program is a program where each function
f has a resource type annotation, so that for each call of f with argument x the
actual cost work(f, encode(x)) is less or equal to cf (size(encode(x))) for some
cost function work (see Section 4).
     A resource-typed program is considered statically well-typed, if all resource
annotations are consistent with some sound set of rules for cost of case distinc-
tions, merge operations and function compositions.
     These rules should guarantee that the static resource type is a safe approxi-
mation for actual costs. We are especially interested in polynomial upper bounds.
     Related work consists of amortized resource analysis in Resource Aware ML
(RAML)[2], where polynomial potential functions are used as costs functions.
The coefficients for these polynomial are determined by a constraint system.
     We want to emphasize again that this approach is ongoing work and there
are currently no results nor experimental data to verify it. We plan to extend
this approach into a reasonable formalism to capture the resource constraints of
CO4 programs in order to estimate the size of the propositional encodings.


References

1. Alexander Bau and Johannes Waldmann. Propositional Encoding of Constraints
   over Tree-Shaped Data. In 22nd International Workshop on Functional and (Con-
   straint) Logic Programming, 2013.
2. Jan Hoffmann, Klaus Aehlig, and Martin Hofmann. Multivariate Amortized Re-
   source Analysis. ACM Trans. Program. Lang. Syst., 34(3):14:1–14:62, November
   2012.
3. Simon Peyton Jones, editor. Haskell 98 Language and Libraries, The Revised Report.
   Cambridge University Press, 2003.
4. David Overton, Zoltan Somogyi, and Peter J. Stuckey. Constraint-based mode
   analysis of mercury. In PPDP, pages 109–120, 2002.