Dec 20, 1995 - Keywords: constraint satisfaction problems, nite domain constraints, constraint solving ..... and name some areas that need further attention.
Uppsala Master's Theses in Computing Science 85 Examensarbete DV3 1995-12-20 ISSN 1100{1836
An Extension of Erlang with Finite Domain Constraints Greger Ottosson
Computing Science Department Uppsala University Box 311 S-751 05 Uppsala Sweden
ERICSSON Utvecklings AB Armborstvagen 14 Box 1505 S-125 25 A lvsjo Sweden
Supervisor: Bjorn Carlson, Mats Carlsson and Bogumil Hausman Examiner: Mats Carlsson
Abstract This report describes the design and implementation of a nite domain constraint solver for Erlang. The constraint solver handles linear arithmetic constraints over natural numbers, which are approximated using arc and interval consistency. We use a scheme with functional rules for constraint propagation and consistency maintainance, and plain backtracking programmed in Erlang to search for solutions. Keywords: constraint satisfaction problems, nite domain constraints, constraint solving, concurrent functional languages.
Contents 1 Introduction 1.1 1.2 1.3 1.4 1.5
Background . . . CLP(X ) . . . . . Erlang(FD) . . . Summary . . . . Report Overview
1 . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
2 Preliminaries
1 2 2 3 3
5
2.1 Finite Domain Constraints . . . . . . . . . . . 2.1.1 Arithmetic Finite Domain Constraints 2.1.2 Constraints and Constraint Stores . . 2.1.3 Ask & Tell . . . . . . . . . . . . . . . 2.1.4 Arc-Consistency . . . . . . . . . . . . 2.1.5 Interval-Consistency . . . . . . . . . . 2.1.6 Indexicals . . . . . . . . . . . . . . . . 2.2 Erlang . . . . . . . . . . . . . . . . . . . . . . 2.2.1 History and Language Characteristics 2.2.2 Data Objects . . . . . . . . . . . . . . 2.2.3 Source Code . . . . . . . . . . . . . . 2.2.4 Pattern Matching . . . . . . . . . . . . 2.2.5 Control Structures . . . . . . . . . . . 2.2.6 Built-In Functions . . . . . . . . . . .
i
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
5 5 6 7 7 9 10 12 12 12 13 14 14 15
2.2.7 Concurrency . . . . . . . . . 2.2.8 Distribution . . . . . . . . . . 2.2.9 Dynamic Code Management . 2.3 The BEAM Erlang implementation .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
15 16 16 16
3 Design
17
4 Implementation
21
3.1 Erlang(FD) Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2 Erlang(FD) Emulator Interface . . . . . . . . . . . . . . . . . . . . . . 19
4.1 Compilation of Arithmetic Constraints . . . . . . 4.1.1 Inlined Indexicals . . . . . . . . . . . . . . 4.1.2 Library Constraints . . . . . . . . . . . . 4.1.3 Inlined Indexicals vs Library Constraints 4.2 Equivalent Indexicals . . . . . . . . . . . . . . . . 4.3 The Propagation Algorithm . . . . . . . . . . . . 4.3.1 Propagation Optimizations . . . . . . . . 4.3.2 The Optimized Propagation Algorithm . . 4.4 Compiling and Evaluating Ranges . . . . . . . . 4.4.1 The Emulator . . . . . . . . . . . . . . . . 4.4.2 Compiling to Byte Code . . . . . . . . . . 4.4.3 Determining Monotonicity of Indexicals . 4.5 Searching for Solutions . . . . . . . . . . . . . . . 4.5.1 A Backtracking Scheme . . . . . . . . . . 4.5.2 Variable Ordering . . . . . . . . . . . . . 4.6 Data Structures . . . . . . . . . . . . . . . . . . . 4.6.1 Finite Domain Variables . . . . . . . . . . 4.6.2 Finite Domains . . . . . . . . . . . . . . . 4.6.3 Finite Domain Indexicals . . . . . . . . .
ii
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
21 21 22 24 25 25 27 29 29 29 32 32 34 34 36 36 37 37 38
5 Evaluation 5.1 5.2 5.3 5.4 5.5 5.6
Benchmark Programs . . . . . . . . . Performance Figures . . . . . . . . . . Discussion . . . . . . . . . . . . . . . . Dierences Between The Solvers . . . Pro ling Erlang(FD) . . . . . . . . . . Summary and Possible Improvements
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
6 Conclusion
39 39 40 41 44 44 46
49
6.1 Summary . . . . . . . . . . . . . . . . . . . . . 6.2 Future Work . . . . . . . . . . . . . . . . . . . 6.2.1 Future Development of Erlang(FD) . . 6.2.2 Ongoing Work with Constraint Solvers .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
49 50 50 50
Acknowledgements
53
Bibliography
55
A Erlang(FD) BIF:s A.1 A.2 A.3 A.4
Compile-Time BIF:s Emulator BIF:s . . . Debugging BIF:s . . Misc BIF:s . . . . .
57 . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
B User Manual B.1 B.2 B.3 B.4
57 57 58 58
59
Introduction . . . . . . . . . . . . . . Initialization . . . . . . . . . . . . . Allocating Variables . . . . . . . . . Telling Constraints . . . . . . . . . . B.4.1 Using Arithmetic Constraints B.4.2 Using Indexicals . . . . . . .
iii
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
59 59 60 60 60 60
B.5 Generating Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 B.6 User Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
C Example Programs C.1 C.2 C.3 C.4 C.5 C.6 C.7 C.8
Send More Money Queens . . . . . . . Alpha . . . . . . . 10 Equations . . . 20 Equations . . . Five Houses . . . . Magic Series . . . . Suudoku . . . . . .
63 . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
D Libraries
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
63 65 67 69 71 74 77 79
83
D.1 User Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 D.2 Variable Ordering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 D.3 Library Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
iv
List of Figures 2.1 Syntax of indexicals . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.1 Design of Erlang(FD) . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.1 Trail and choice point stack in the backtracking scheme . . . . . . . . 35
v
vi
List of Tables 4.1 4.2 4.3 4.4 4.5 4.6
Library constraints . . . . . . . . . . . . . . . Combination of prunings . . . . . . . . . . . . Byte code instructions . . . . . . . . . . . . . Translation of ranges and terms to byte code Monotonicity of term expressions . . . . . . . Monotonicity of range expressions . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
22 29 30 32 33 33
5.1 5.2 5.3 5.4
Erlang(FD) execution speed . . . . . . . . . . . . . . SICStus(FD) execution speed . . . . . . . . . . . . . AKL(FD) execution speed . . . . . . . . . . . . . . The most time consuming functions in Erlang(FD).
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
41 42 43 45
vii
. . . . . .
. . . . . .
. . . . . .
viii
List of Algorithms 2.1 4.1 4.2 4.3
AC-3: An arc-consistency algorithm A simplistic propagation algorithm . An optimized propagation algorithm The monotonicity algorithm . . . . .
ix
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. 8 . 26 . 31 . 34
x
Chapter 1
Introduction This chapter gives some background of the eld, introduces our work in that context and presents our aim and goals.
1.1 Background Many computational problems can be stated as constraints over variables of some abstract domain. Packing, scheduling and allocation are problems which can often be described using nite domain constraints. Basically, a Constraint Satisfaction Problem (CSP) is composed of a nite set of variables of nite domains and a set of constraints that restrict the values the variables can simultaneously take. The task is to assign a value to all variables satisfying the constraints. Solving a CSP can be divided into two steps; (1) problem reduction, which involves reducing the variable domains by removing redundant values and tightening the constraints, and (2) searching for a valid solution through enumeration. CSPs has been the subject of growing interest and research for the last ten years, and many dierent approaches have been made. Both special-purpose programs with domain-speci c knowledge and generic solvers for classes of problems have emerged. To the latter one may count the extension to logic programming, forming Constraint Logic Programming, CLP. From a CSP perspective, constraint solvers in CLP are an intertwined mixture of the two phases problem reduction and search mentioned above. The CLP community have approached the CSP by using dierent techniques for dierent domains, and thus there are CLP(B), CLP(FD) and CLP(R) for solving boolean, nite and rational domain problems respectively. Boolean constraint problems can for example be solved using boolean uni cation while problems with rational
1
2
CHAPTER 1. INTRODUCTION
numbers are commonly dealt with using some derivative of the simplex -algorithm. In this report, we will describe one approach for solving nite domain problems.
1.2 CLP(X ) The theory behind the family of CLP-languages, CLP(X ), is based on logic programming where uni cation is replaced with constraint handling in a constraint system [Hentenryck, 1991]. Hentenryck's CLP scheme de nes a class of languages, of which an instance can be obtained by considering a speci c constraint system (a computation domain) and for that provide a constraint solver. The output from a CLP(X )-language is an answer substitution (values for the variables) or a conjunction of answer constraints. The CLP framework has later been extended by the research on Concurrent Constraints Programming. The original notion of adding/posting a constraint c to a constraint store , i.e 0 = [ c, is called telling. The term ask is introduced, with which we mean to check if a constraint c is entailed in the constraint store , i.e. (8)( =) c).
1.3 Erlang(FD) This work aims at embedding nite domain constraints within the concurrent functional language Erlang. The object is to make the language more versatile and
exible, and to extend the class of target problems. Why adding the notion of constraints to a language like Erlang? Well, the highlevel avor of Erlang and its concurrency makes it good for reactive and distributed systems, but it lacks support for programming complex scheduling, planning and control. The embedding of constraints within Erlang would make up for this, and make it feasible to program reactive constraint solving and distributed constraint systems. A constraint system used in a reactive environment needs special properties, such as the ability to suspend (during say, logging or backup procedures) and the ability to interact with processes. In Erlang, an interfaceable constraint store from which processes could extract and subscribe to information and events would be valuable. With this feature at hand the provider of information, say a sensor, a user interface or a telephone switch, would not have to care about what information is used and by what process. Posting the information as entailed constraints in a store would be sucient since processes from there could extract the information in an intelligent manner.
1.4. SUMMARY
3
In this rst attempt we do as much as possible inside Erlang, reusing its memory management, process scheduling, control structures and syntax. The bulk of our implementation is written in Erlang itself. This makes our solver preemptive, and ful ls our need for a suspending solver. We further aim at making it possible for processes in Erlang to interface to our constraints, in a manner similar to agents in Concurrent Constraint Programming. This ask primitive, if optionally asynchronous, would make it possible for processes to subscribe to events. Our approach is based on the work of Bjorn Carlson with AKL(FD) [Carlson, 1995]. Dierences between AKL and Erlang as well as our aim to use Erlang as the primary implementation language has forced us to make dierent design decisions, especially concerning the search and the interface to Erlang. However, those familiar with Carlson's work will recognize the similarities. Our main focus is thus on implementation aspects and design issues regarding the interface to Erlang. Our work has been mainly practical, and this report is therefore mainly a description of what has been done.
1.4 Summary Summarizing, these are the goals of this work:
to investigate the feasibility of embedding nite domain constraints within Er-
lang, to determine the appropriate user interface of such an extension, to implement the notion of ask, in a way that makes it useful in Erlang programming, and to evaluate the performance and re ect upon the design decisions and the implementation.
1.5 Report Overview This report is structured as follows:
Chapter 1
Report introduction and overview.
4
CHAPTER 1. INTRODUCTION
Chapter 2
Covers background knowledge and introduces concepts needed in later chapters. Topics are: nite domain constraints, constraint stores, satis ability, consistency and indexicals. We also give an introduction to Erlang and one of its implementations | BEAM.
Chapter 3
The design of Erlang(FD). We show the structure and present our design decisions.
Chapter 4
A detailed chapter about the implementation of Erlang(FD). Knowledge of the topics covered in Chapter 2 is needed.
Chapter 5
An evaluation of Erlang(FD) with respect to performance. We compare our work to SICStus(FD) and AKL(FD ).
Chapter 6
Conclusions and future work. We summarize what have been done in this work, and name some areas that need further attention.
Appendices
A user manual, some example programs in Erlang(FD) and various libraries for those with interest in the implementation.
Chapter 2
Preliminaries This chapter introduces some basic concepts used in CSPs and constraint solvers. Knowledge about these concepts is needed in subsequent chapters. We describe the notions of constraints and constraint stores, satis ability, consistency and indexicals. We give an introduction to Erlang and one of its implementations | BEAM.
2.1 Finite Domain Constraints The idea of programming with constraints is based on the notion of constraints and a constraint store. A constraint intuitively expresses some relation on a set of variables. A constraint store is simply a set of constraints. In this section we consider our constraints (Section 2.1.1) and the collection of them in a constraint store (Section 2.1.2 on the next page). We also de ne consistency and an algorithm for achieving consistency (Section 2.1.4 on page 7). Finally, we describe the indexicals (Section 2.1.6 on page 10) which are the functional rules we have chosen to achieve and maintain consistency.
2.1.1 Arithmetic Finite Domain Constraints Since we are dealing with nite domain constraints, our constraints are expressed as arithmetic expressions over subset of the natural numbers, N0 . An arithmetic constraint consists of two linear terms1 together with a relation.
n1 x1 + : : : + nk xk nk+1 xk+1 + : : : + nnxn where 2 f6=; =; ; ; g. We only consider linear terms, non-linear terms can be treated [Carlson, 1995], but have not been implemented in our work. 1
5
6
CHAPTER 2. PRELIMINARIES
2.1.2 Constraints and Constraint Stores A constraint is a relation over one or more variables, e.g. X Y + 2. A domain constraint is a constraint X 2 fn1 ; : : : ; nk g, where ni 2 N0 . A constraint store is a set of domain constraints. The value of x in a store , x , is thereby N0 \ fd : (x 2 d) 2 g. For example, x , where = fx 2 f1; 2; 3g; x 2 f3; 4gg, equals f3g and for any unconstrained y, y equals N0 . Let be a constraint store; the following is de ned
satis ability I
is said to be satis able i no x is empty, for any x.
extension
0 is an extension of i x0 x for any x. Put dierently, 0 is an extension of i 0 =) . That is, 0 is a strengthening of the domain constraints in .
entailment
A constraint c is said to be entailed in i c is true in all satis able extensions of . Put dierently, c will always be true whatever strengthening of constraints is done in .
satis ability II
A constraint c is satis able in i it is entailed by some extension of . That is, a constraint is satis able as long as it has the possibility of being entailed.
(in)consistency w.r.t. property
Consistency is a concept with which one may exercise problem reduction. A constraint store is said to be consistent with respect to certain properties i the properties hold. Maintaining consistency thus implies removing values and tightening constraints to maintain the properties. A constraint store is inconsistent if it is not consistent, i.e. it does not satisfy the properties. Consistency with a certain property is not a sucient condition for a problem to be solvable. A problem nay be consistent but unsatis able. Dierent algorithms achieve dierent levels of consistency, satis ability being the best. An algorithm guaranteeing satis ability is often called complete. When talking about consistency we annotate the term with the properties we maintain. For example, Section 2.1.4 on the facing page and 2.1.5 on page 9 will de ne two common consistency levels, arc- and interval-consistency.2
2
Confused? Do not worry, most of this terminology will become apparent by example.
2.1. FINITE DOMAIN CONSTRAINTS
7
For a through examination of satis ability and consistency from a viewpoint of constraint satisfaction problems, see [Tsang, 1993, Mackworth, 1977].
Example 2.1
Given a constraint store fX; Y 2 f0; : : : ; 10gg. Then is satis able (no x = ; for any x) fX 2 f2; : : : ; 6g; Y 2 f0; : : : ; 10gg is an extension of X 4 is satis able in X = 14 is not satis able in X 11 is entailed in
For the rest of the report, the following notation will be used:
A constraint store. c A constraint. c(v1 ; : : : ; vn ) An \instantiated" n-ary constraint. Can be true (satis ed) or false. X; Y Constraint variable or integer. V Value (integer). X The domain of variable X in . X The constraints on X in . (X; V ) The assignment of value V to variable X .
2.1.3 Ask & Tell The concepts of ask and tell originated in the area of Concurrent Concurrent Programming [Saraswat, 1989] and was adopted in CLP [Hentenryck, 1991]. By tell we mean to add a constraint c in a constraint store , i.e 0 = [ c. The term ask means checking if a constraint c is entailed a the constraint store i.e. ( =) c). We will use this terminology throughout the report.
2.1.4 Arc-Consistency The traditional technique for achieving and maintaining consistency in nite domain constraint solvers has been arc-consistency, see e.g. [Mackworth, 1992, Tsang, 1993]. Intuitively, the consistency property is that all constraints should be individually satis able. Arc-consistency for n-ary constraints can be de ned more precisely as follows.
8
CHAPTER 2. PRELIMINARIES
De nition 2.1
A constraint c is arc-consistent i for each variable xi with domain Di and value vi 2 Di , there exists values v1 ; : : : ; vi?1 ; vi+1 ; : : : ; vn in D1 ; : : : ; Di?1 ; Di+1 ; : : : ; Dn such that c(v1 ; : : : ; vn ) is satis ed. A constraint store is arcconsistent i all constraints in are arc-consistent.
Arc-consistency is achieved by eliminating values from variable domains that are inconsistent with any (single) constraint3 . An arc-consistency algorithm (for binary constraints only) called AC-3 is presented as Algorithm 2.1.
Algorithm 2.1 AC-3: An arc-consistency algorithm
NC-1( ) % Node consistency handling BEGIN FOR each variable DO FOR each DO IF NOT ( ) satisfies X THEN REMOVE from the domain of END
X 2 V 2 X X; V V
unary constraints
X
AC-3( ) % Arc consistency algorithm NC-1( ); BEGIN := X;Y WHILE = DO DELETE any element from FOR each IF there exists no such that ( ) and ( ) satisfies THEN REMOVE from the domain of := = Z;X END
Q
fX ! Y j g Q 6 ; a 2 X
X; a Q
X! Y
Q
b2 Y
Y; b X! Y a X Q [ fZ ! X j ^ Z 6
X ^ Z 6= Y g
What the algorithm does is to check all constraints and remove any values from the corresponding target domain that do not satisfy the constraints on the variable. If values are removed from a variable domain, constraints that may be aected by this are re-queued. This is repeated until it stabilizes (i.e. the queue of constraints is empty). There are some remarks that need to be made about this approach and this algorithm. In the previous section we represented variable domains as domain constraints. To remove a value from a domain in this context would be to add a new domain constraint with a smaller domain. 3
2.1. FINITE DOMAIN CONSTRAINTS
9
Eciency
The algorithm is not naive with respect to which constraints it checks. Whereas a naive algorithm (referred to as AC-1 in the literature, see e.g. [Tsang, 1993]) would consider and check all constraints repeatedly, AC-3 avoids this by maintaining a queue of constraints that need to be checked. Constraints are only added to this queue if there is reason to believe that they need to be checked, i.e. a variable domain of the constraint has been reduced. Although not naive, AC-3 is not optimal. With respect to constraint checks there exists an optimal algorithm called AC-4, see e.g. [Tsang, 1993]. This algorithm has better time-complexity but has worse space-complexity, i.e. it requires a larger amount of space to maintain arc-consistency for a problem. It is not commonly used in constraint solvers, and we will not consider it further.
Constraint arity
As pointed out above, AC-3 only deals with unary (through NC-1) and binary constraints. For most purposes (such as ours) this is not enough. There exists an extension of AC-3 called HYPER AC-3, which deals with constraints with higher arity, but since we use a slightly dierent approach of handling constraints4 that solves this problem, we do not consider it here.
Consistency
It is important to (once again) point out that the consistency maintained by AC-3 is not equivalent to satis ability. To illustrate this, let us consider two examples.
Example 2.2
Given constraint x < y and domains x 2 f1; 2; 3g; y 2 f1; 2; 3; 4g, one would be able to remove 1 from the domain of y since there is no value in the domain of x that satis es the constraint when Y = 1. An arcconsistency algorithm such as AC-3 will accomplish this.
Example 2.3
A constraint x 6= y 6= z 6= x (all dierent) and domains x; y; z 2 f0; 1g, is not satis able since there are three variables and only two values. But since the arc-consistency technique does not consider the constraint as a whole, but as binary constraints, it will not discover the unsatis ability. In other words, the constraints are only arc-consistent, not satis able.
2.1.5 Interval-Consistency In De nition 2.1.4 on the facing page we used domain reasoning about constraints to achieve arc-consistency. This can sometimes be inconvenient or too expensive to carry out. Therefore, we sometimes use interval reasoning instead. 4
We use indexicals, see Section 2.1.6 on the next page.
10
CHAPTER 2. PRELIMINARIES
De nition 2.2
Let Dj = fmin(Dj ); max(Dj )g. A constraint c is interval-consistent i for each variable xi with domain Di and value vi 2 Di , there exist values v1 ; : : : ; vi?1 ; vi+1 ; : : : ; vn in D1 ; : : : ; Di?1 ; Di+1 ; : : : ; Dn such that c(v1 ; : : : ; vn ) is satis ed. A constraint store is interval-consistent i all constraints in are interval-consistent.
Let sup (x) be supremum, and inf (x) in mum of an expression x. Then intervalconsistency is achieved using min and max reasoning, as in the following example.
Example 2.4
A constraint x > y can be interpreted as \x should at least be greater than the smallest possible value of y", implying that
x 2 fa : a > inf(y)g As you see, we do not check that for every single element from the domain of x there is a satis able element from the domain of y. Instead we ensure that the lower bound of x's interval is greater than the lower bound of y's interval. As you will see later in Section 4.3 on page 25, our implementation technique is based on an arc-consistency algorithm, operating on constraints with any arity. We use indexicals (introduced in Section 2.1.6) as our \programming language" to achieve consistency, and the consistency maintained is most often arc-consistency or intervalconsistency
2.1.6 Indexicals An indexical has the form X in r , where X is a variable and r is a range. The range is an expression that evaluates to a set, and the indexical as a whole can be seen as a functional rule from a set of variables (the variables in the range expression) to the variable X. Figure 2.1 on the next page de nes the syntax of indexicals. When an indexical is evaluated in a constraint store, it evaluates to a new domain constraint X 2 r \ N , where r is the value of the set expression r in the constraint store . Thus, the evaluation of an indexical results in an extension of the constraint store, see Section 2.1.2 on page 6 for a de nition of extension. In the rest of this report, we will say that we propagate the indexicals when we evaluate (a series of) indexicals, and that we prune variable domains when we add new domain constraints.
2.1. FINITE DOMAIN CONSTRAINTS
I ::= X in R X ::= x;
a variable
R ::= T .. T; j R /\ R; j R \/ R; j R \ R; j R1 => R2; j R + T; j R - T; j R mod N; j dom(X ); j diffdom(X )
range intersection union set dierence if R1 is nonempty then R2 else empty range point-wise addition point-wise subtraction point-wise modulo the domain of a variable as a range, i.e. X same as above, but indexical is not suspended on X (Suspension is explained in Section 4.3 on page 25)
N ::= X j i;
j inf
T ::= N jT +T jT -T jT *T j T divd T; j T divu T; j T mod N j min(X ) j max(X ) j size(X )
an integer
division rounded down division rounded up minimum value of domain of X maximum value of domain of X domain size of X Figure 2.1: Syntax of indexicals
11
12
CHAPTER 2. PRELIMINARIES
Monotonicity of Indexicals An indexical range is considered monotone in if its value (a set) shrinks when is extended. If it grows, it is considered antimonotone. An indexical is considered (anti)monotone if its range is (anti)monotone. If its range is both monotone and antimonotone, it is considered constant, thus it will neither shrink nor grow. The monotonicity of an indexicals is computed at compile-time, see Section 4.4.3 on page 32, and is used by the propagation algorithm in the solver (Section 4.3 on page 25).
2.2 Erlang In this section we will give a short introduction to the programming language Erlang. For a complete guide to the language, we refer to Concurrent Programming in Erlang [Armstrong et al., 1993].
2.2.1 History and Language Characteristics Erlang is a symbolic, high level, functional and concurrent programming language developed at Ericsson Computer Science Laboratory. It is aimed for implementing soft real-time fault tolerant systems, mainly for telecom applications. Language features are:
declarative syntax, functional semantics largely free from side eects, concurrency based on light-weight processes, robust error handling, dynamic code management, allowing code to be replaced in running systems, language built-in memory management, and features for distribution over physical machines.
2.2.2 Data Objects Erlang supports constants, variables and compound terms. Valid constants are integers, oats, atoms and process identi ers 5 . A compound term is either a list or 5
Normally referred to as Pids.
2.2. ERLANG
13
a tuple ; lists are dynamically growing structures while tuples are of xed size. The following example shows some compound objects.
Example 2.5 List: Tuple:
[1,2,3] {1,2,3}
Memory for data structures are automatically allocated and deallocated when no longer used. Variables are single-assignment, i.e. they are instantiated on creation and do not change their value. Variables are untyped and need not be declared.
2.2.3 Source Code Erlang source code are divided into modules, which in turn consist of functions. Functions are by default local to a module, and must be explicitly exported to be seen outside the module. Each function consists of one or more clauses. The following example shows a module de ning two not entirely uncommon operations on lists.
Example 2.6 -module(lists). -export([member/2, append/2]). member(X, [X|_]) -> true; member(X, [_|Y]) -> member(X, Y); member(X, []) -> false. append([H|T], Z) -> [H|append(T, Z)]; append([], X) -> X.
External calls to a module are done by naming the module explicitly as in lists:append([1,2,3],[4,5])
14
CHAPTER 2. PRELIMINARIES
2.2.4 Pattern Matching Pattern matching is the fundamental concept used in Erlang to choose clauses and assign variables. The following example shows its use in assignment.
Example 2.7
A = 12 [B|_] = [foo,1,2,3] {C,C} = {12,[1,2]}
Succeeds, A is bound to integer 12. Succeeds, B is bound to atom foo. Fails, since C cannot be bound to both the integer 12 and the list [1,2].
The next section will show the use of pattern matching in control structures.
2.2.5 Control Structures As we mentioned earlier, pattern matching is used to choose the clause within a function. Pattern matching is also used for choosing execution path inside the body of clauses, i.e. with if and case statements. case Expr of Pattern1 [when Guard1] -> Seq1; Pattern2 [when Guard2] -> Seq2; ... PatternN [when GuardN] -> SeqN end if Guard1 -> Seq1; Guard2 -> Seq2; ... GuardN -> SeqN end
The case clause evaluates Expr and matches the result against the patterns. The corresponding sequence to the rst succeeding match is chosen. Pattern can be any data structure, with variables. Anonymous variables ' ' can be used, and if used in single (i.e. case A of _ -> ...) it will be a catch all, matching anything. Guards are used to extend the pattern matching, and consist of one ore more special tests on the variables in the pattern. E.g., one can check if a variable is an atom or if it is a list and has a certain length. User de ned guards, i.e. deep guards, are not allowed.
2.2. ERLANG
15
The if clause executes guards and evaluates the sequence corresponding to the rst guard that succeeds. In both case and if clauses, it is considered an error of no pattern/guard matches/succeeds.
2.2.6 Built-In Functions Erlang has a number of built-in functions (BIF:s), needed and used for tasks that are not possible to program in Erlang itself. Conversion between data types and process creation (see the following section) are examples of tasks that can be done using BIF:s. See [Armstrong et al., 1993] for a complete listing.
2.2.7 Concurrency Erlang processes are created dynamically using the built-in function spawn. Process communication is done by asynchronous message passing using the '!' operator. The following example shows how these features can be combined to start two processes who send ping messages to each other.
Example 2.8 -module(ping). -export([start/0,loop/0]). start() -> Pid1 = spawn(ping,loop,[]), Pid2 = spawn(ping,loop,[]), Pid1 ! {ping, Pid2}. loop() -> receive {What,Who} -> Who ! {What,self()}, loop() end.
Messages, which can be any Erlang terms, are received selectively by a process using the receive clause. This construct suspends the receiving process until a message arrives that matches some pattern. receive Pattern1 [when Guard1] -> Sequence1;
16
CHAPTER 2. PRELIMINARIES Pattern2 [when Guard2] -> Sequence2; ... PatternN [when GuardN] -> SequenceN [after Time -> TimeOutSequence] end
Messages to a process are queued up in a mailbox. When the process enters the receive clause the messages are matched in \ rst come rst served" order, and the Sequence corresponding to the matching Pattern is executed. If a message in the mailbox do not match any pattern, it is left in the mailbox and the next message is checked. If no message has arrived in Time milliseconds, the TimeOutSequence is executed.
2.2.8 Distribution An Erlang system running one or more Erlang processes, is called a node. Several Erlang nodes can co-exist and communicate on a single machine or on several machines in a network. The distribution facilities of Erlang ensure transparency | even between dierent architectures. This means that processes can be spawned \remote", code management can be \global" and error handling can be distributed.
2.2.9 Dynamic Code Management Erlang source code is compiled and loaded on need in a per-module basis. Code can be re-compiled and re-loaded during execution of the system. Processes doing function calls with explicit modules, e.g. lists:append([1,2],[3]) will automatically use the latest loaded module. Other processes will continue to use the old code. Thus, there may simultaneously exist more than one code version of a single module in the system.
2.3 The BEAM Erlang implementation BEAM is one of the currently available implementations of Erlang. It provides a runtime environment and a compiler. Erlang source code can be compiled in two ways; to C code (and then by a C compiler to object code) or to threaded code. These schemes can be mixed in the runtime environment (remember that modules are compiled separately). We have used the threaded code compiler and emulator for development and debugging, and the compilation to C code for nal execution. For details about the BEAM Erlang implementation, see [Hausman, 1994].
Chapter 3
Design This chapter describes the design of Erlang(FD); how it is structured and how we have interfaced with Erlang.
3.1 Erlang(FD) Structure The central block of Erlang(FD) is the constraint solver, which is located in a separate Constraint User process. Allocation of variables and telling1 Solver of constraints are done by sending messages Process Process to the solver process, although this is not visible by the programmer. The execution of the user process is thus separated (i.e. preemptively scheduled by Erlang) from the solver which has both negative and positive eects:
hijk onml
/
o
xyz{ ~}|
+ Robustness. The user process is not dependent on the solver and may run concurrently. + Easier to maintain a state of the solver algorithm. + Easier to distribute the solver. { Message sending overhead since all communication with the solver uses messages. Figure 3.1 on the following page shows the structure or Erlang(FD). The programmer's source code contains constraints | either arithmetic or directly stated as indexicals. When the program is compiled, the arithmetic constraints are transformed into library constraints (Section 4.1.2 on page 22), which in turn consist of indexicals. 1
Tell is explained in Section 2.1.3 on page 7.
17
18
CHAPTER 3. DESIGN
Compile Time
Run Time User Process Variables
Source Program
Variables Indexicals
Indexicals
Solver Process
Arithmetic Constraints Library Constraints
Indexicals
Variables
Byte code Emulator
Figure 3.1: Design of Erlang(FD)
3.2. ERLANG(FD ) EMULATOR INTERFACE
19
Consequently, when a program has been compiled its constraints consist solely of indexicals. These indexicals are at run-time sent to the constraint solver process, along with the nite domain variables the program allocates. The constraint solver process, running a propagation algorithm, evaluates the indexicals as they come in and prunes the domain of the variables. See Section 4.3 on page 25. The propagation algorithm makes calls to a byte code emulator to evaluate the ranges of the indexicals based on the current values of the nite domain variables. This emulator is described in more detail in Section 4.4.1 on page 29. The interface to the emulator is based on BIF:s and is described in Section 3.2. When asked for a solution | a set of values for the nite domain variables | the constraint solver nds a solution using a standard backtracking scheme. Since Erlang does not provide means for search | neither backtracking nor otherwise | this had to be implemented in the solver. See Section 4.5 on page 34 for details.
3.2 Erlang(FD) Emulator Interface To get an ecient interface to the emulator, which is written in C, we chose to extend Erlang with a few BIF:s gaining access to the emulator which was linked in with the Erlang runtime system. These BIF:s are described in Appendix A on page 57.
20
CHAPTER 3. DESIGN
Chapter 4
Implementation 4.1 Compilation of Arithmetic Constraints We will now consider two approaches to handle ( nite domain) arithmetic constraints. The rst is based on compiling directly to indexicals. The second, which uses an intermediate representation called library constraints, is the one used in our implementation.
4.1.1 Inlined Indexicals The straight-forward way to compile a constraint of, say 2x + y = z is to extract each variable in turn, and for each create an inlined indexical which maintains the relation from right to left only. This technique has previously been used in [Diaz and Codognet, 1993, Carlson and Carlsson, 1993]. Our example can be expressed as three indexicals
X in (min(z) ? max(y)) divu 2 :: (max(z) ? min(x)) divd 2 Y in min(z) ? 2 max(x) :: max(z) ? 2 min(x) Z in 2 min(x) + min(y) :: 2 max(x) + max(y) maintaining interval-consistency, see Section 2.1.5 on page 9, of the constraint. This indexical approach has one major drawback; the code size for the required indexicals grows quadratically. The amount of indexicals required is the amount of variables (n) and the size of the indexicals almost the same (n ? 1), making the growth O(n2 ).
21
22
CHAPTER 4. IMPLEMENTATION
This fact makes this way of expressing constraints unattractive for constraints with high arity.
4.1.2 Library Constraints The problem with translating high arity arithmetic constraints directly to indexicals comes from the fact that code is very much duplicated. Each part of a term will occur (almost) identically in n ? 1 of n indexicals. This can be avoided by introducing intermediate variables, a technique which is exploited in this approach. The following translation is done partly compile-time and partly run-time. We introduce a middle-step between arithmetic constraints and indexicals called library constraints, which de nes the temporary variables in terms of subexpressions of the arithmetic constraint. The method is de ned and used in [Diaz and Codognet, 1993, Carlson and Carlsson, 1993]. arithmetic expressions
compile-time /
library constraints
run-time /
indexicals
The library constraints are in fact ordinary Erlang functions, consisting solely of indexicals. These indexicals maintain interval-consistency on the arguments to the function. Table 4.1 shows, in an abstract manner, the library constraints we have used. x, y and t are variables, a, b and c are constants. Appendix D.3 on page 84 includes their implementation.
x t x c ax = t x+y =t ax = c t x+c t+x c ax + by = t
where 2 f=; 6=; ; g where 2 f=; 6=; ; g where 2 f=; 6=; ; g where 2 f=; 6=; ; g
Table 4.1: Library constraints Choosing which library constraints to use is a balance between introducing a lot of indexicals or introducing a lot of intermediate variables. Simple library constraints reduce the number of indexicals created, but in turn the composition of these library constraints requires more intermediate variables. Hypothetically speaking, using arbitrarily complex library constraints means avoiding composition, which is equivalent to compiling directly to indexicals, as in Section 4.1.1 on the preceding page.
4.1. COMPILATION OF ARITHMETIC CONSTRAINTS
23
Compiling arithmetic expression into library constraints is here done in three steps, as shown below: 1. Strict arithmetic relations (< and >) are eliminated from the arithmetic expression. This is done by replacing expressions A < B with A B ? 1 and A > B with A B + 1. 2. The expression is normalized. A normalized expression is on the form S+j T +i where S = n1 x1 + : : : + nk xk , T = nk+1xk+1 + : : : + nnxn , 2 f; ; 6=; =g, and either j or i is zero and the other is 0. 3. The normalized expression is nally decomposed into the appropriate library constraints. The nal step is worth some investigating. Decomposing a normalized expression can either be done using linear- or logarithmic decomposition. A simple example will illustrate these techniques, which are due to [Carlson and Carlsson, 1993].
Example 4.1
The term a = x1 + x2 + : : : + x6 can either be decomposed linearly as x1 + x2 = t1 t1 + x3 = t2 t2 + x4 = t3 t3 + x5 = t4 t4 + x6 = a or logarithmically as x1 + x2 = t1 x3 + x4 = t2 x5 + x6 = t3 t1 + t 2 = t4 t3 + t 4 = a
Both generate the same amount of library constraints; the dierence shows during execution. A change made to variable x1 propagates through the library constraints to aect a. Using the linear decomposition this will re-evaluate all ve constraints. This is not the case with the logarithmic decomposition which will only re-evaluate three. This technique is similar to using trees instead of lists in ordinary data structures.
24
CHAPTER 4. IMPLEMENTATION
4.1.3 Inlined Indexicals vs Library Constraints The choice between using inlined indexicals or library constraints is not obvious. Our implementation is based solely on library constraints, but probably a mixture would be preferable. Arithmetic constraints with small arity can be compiled as inlined indexicals, while constraints with high arity can be compiled as library constraints. This would not introduce intermediate variable when unnecessary. Another question when using library constraints is that of consistency. We know that individual library constraints ensures interval-consistency, but is this property preserved when library constraints are composed using intermediate variables?
Proposition 4.1
Compilation of linear arithmetic expressions ensures interval-consistency for these constraints, regardless if compiled to inline indexicals or to library constraints.
Consider a small example of propagation, comparing inlined indexicals and library constraints.
Example 4.2
Consider a constraint X = x1 +x2 +x3 +x4 , x1 ; x2 ; x3 2 f1; 2g; x4 2 f2; 3; 4g and X unde ned. The following propagation is achieved using inlined indexicals and library constraints respectively. The expression is logarithmically decomposed as library constraints t1 = x1 + x2 , t2 = x3 + x4 and X = t1 + t2 . Value of X x2
X
2 f1g added 2 f9g added
Inlined Indexical Library Constraints
X
X
x1 x3
2 f5 10g 2 f5 9g 2 f2g 2 f1g 2 f2g 2 f4g ;::: ;
;::: ;
; x2
; x4
;
t1 X t1 X t1 x1 x3
By noting that
2 f2 4g 2 f3 2 f5 10g 2 f2 3g 2 f3 2 f5 9g 2 f3g 2 f6g ) 2 f2g 2 f1g 2 f2g 2 f4g ;::: ;
; t2
;::: ;
; t2
;::: ;
;::: ;
;::: ;
6g ) 6g )
;::: ; ; t2
; x2
;
; x4
( (e1 )::sup(e2 )) = inf(e1 )
inf inf
and that
( (e1 )::sup(e2 )) = sup(e2 ) we see that upper and lower bounds (intervals) propagate correctly through intermediate variables, and thus, library constraints preserve the interval-consistency property when composed. sup inf
4.2. EQUIVALENT INDEXICALS
25
4.2 Equivalent Indexicals Two indexicals are considered equivalent i one is entailed whenever the other is. This is often the case in library constraints. For example, the library constraint 'x=y'(X,Y) -> fd:tell( X in min(Y)..max(Y), Y in min(X)..max(X) ).
contains two indexicals which are equivalent. Erlang(FD) treats two indexicals as equivalent if they occur in the same fd:tell()-statement in the source code, see the User Manual in Appendix B on page 59. The equivalence property is exploited in an optimization, see Section 4.3.1 on page 27.
4.3 The Propagation Algorithm The core of the constraint solver is the propagation algorithm. It ensures (some level of)1 consistency in the constraint solver by evaluating indexicals and pruning variables. The basic idea is as follows: Variables reside in the constraint solver connected to each other by constraints. The constraints are expressed as indexicals. The domains of variables are pruned by the evaluation of indexicals, and this triggers other indexicals which prune some variables, etc. As you might have noticed by the description, this is in many ways similar to the AC-3 (Algorithm 2.1 on page 8). The dierence is that AC-3 propagates and queues directed binary constraints while we use indexicals. A naive algorithm is presented in Algorithm 4.1 on the following page. Q is a queue of pruned variables. Some notes on the algorithm:
The algorithm terminates when the queue of pruned variables is empty. Vari-
ables are queued when they are pruned. Variables have indexicals suspended on them, and it is these suspended indexicals that are re-evaluated when a variable is taken from the queue. For a variable x, every indexical X in r where x occurs in r, is suspended on x. The motivation: these indexicals may need to be re-evaluated if x is pruned.
By this remark we simply mean that it does not ensure satis ability or even arc-consistency always. The level of consistency achieved depends on the indexicals and how well they propagate constraints between variables. 1
26
CHAPTER 4. IMPLEMENTATION
Algorithm 4.1 A simplistic propagation algorithm Q
prop( , ) BEGIN WHILE not empty DO := ; := suspended indexicals on ; FOR each X r in DO := ; CASE of : IF monotone in THEN fail ELSE continue : IF antimonotone in THEN mark the indexical entailed and continue otherwise: IF not monotone in THEN continue ; := ; := ; END
Q
Q S
Q n fyg
in x \ r
I
;
y
S
I
r
x
Q
r
r
[ fx 2 I g Q [ x
Remember that x is a notation for the domain of x in and r is the range r
evaluated in . If a monotone indexical evaluates to the empty set, the algorithm returns with failure. An empty domain of a variable implies that there is no solution to the constraint problem. An antimonotone indexical (one whose range grows) is simply ignored if it evaluates to the empty set. An antimonotone indexical whose variable domain becomes a subset of its range is marked entailed and will never be evaluated again. Since its range only grows, it will be satis ed in any extension of . When the domain of a variable x has been pruned in , x is queued.
In Section 2.1.4 on page 7 we presented AC-3, an arc-consistency algorithm. What we have described in this section is something similar, but not identical, in aim, context and performance.
4.3. THE PROPAGATION ALGORITHM
27
Context
AC-3 deals with arcs, i.e. directed binary constraints, and checks that single values are consistent with those constraints. Our propagation algorithm is more speci c and has coded the constraints as indexicals; instead of checking constraints (as in AC-3) we evaluate indexicals. Instead of removing single values, we prune variable domains.
Aim
AC-3 ensures arc-consistency. Our propagation algorithm does not. The level of consistency achieved here depends on the indexicals stated and when they are evaluated (in turn depending on their monotonicity property).
Eciency
In similarity to AC-3, our propagation algorithm does not re-evaluate all constraints (indexicals) but only those that have a chance to perform some pruning (some variable domain in the range expression of the indexical have changed). Even more can be done, as we shall see in Section 4.3.1.
Constraint arity
Since we are now coding constraints as indexicals, we are able to handle constraints of greater arity than two.
The next section introduces the optimizations in the algorithm.
4.3.1 Propagation Optimizations Here we describe the optimizations implemented.
Variable Dependencies Variable dependencies are an optimization exploited in this implementation of the indexical scheme. It reduces the amount of indexicals evaluated by the pruning of a variable. Each variable has a list of indexicals suspended on it. These are evaluated each time a pruning has been made to the domain of the variable. Many of the indexical evaluations will not have any eect. To reduce this waste of time, we attach a dependency -atom with each suspended indexical. Furthermore, each time a variable is pruned, a pruning -atom is calculated. These dependency -atoms and pruning -atoms are one of the following:
28
Atom Meaning as indexical dependency
CHAPTER 4. IMPLEMENTATION
Meaning as pruning
Indexical not aected of variable No pruning was made to the (not used, since such indexicals variable. are not even attached) int Indexical aected when variable Variable was pruned to a single becomes determined (Pruned integer (determined). int). min Indexical aected when variable is Variable was pruned of one or pruned min, minmax or int. more of its lower values. max Indexical aected when variable is Variable was pruned of one or pruned max, minmax or int. more of its upper values. minmax Indexical aected when variable is Variable was pruned of both lower pruned min, max, minmax or int. and upper values. dom Indexical aected when variable is Variable was pruned of values pruned anything but none. \inside" its domain. According to the rules in the above table, an indexical is aected (and thus reevaluated) only when the pruning \triggers" the dependency atom. Note that the dependencies min,max and minmax are triggered by an int-pruning, i.e. a variable becomes determined. This is due to the fact that these indexicals may become entailed, and that should be checked for, since we then may trigger the next optimization. none
Entailed Marking Equivalent indexicals (Section 4.2 on page 25) share a common entailed ag, as shown in Section 4.6.3 on page 38. Using this avoids the overhead of evaluating indexicals which are already entailed. When one of two or more equivalent indexicals is entailed, none of them will be evaluated again.
Variable Queue treated as a Set When a variable's domain is pruned, it is to be queued by the propagation algorithm. There is, however, no need to queue the variable if it is already in the queue. Therefore we check if the variable is in the queue by examining a boolean ag each variable has, see Section 4.6 on page 36 on data structures. If it already is in the queue, we only combine its old pruning information with the new pruning since they may dier. Table 4.2 on the facing page shows how the updating procedure is done between an old pruning P and a new pruning P 0 . It preserves the property that all indexicals for either pruning (old and new) are to be triggered by the combined pruning. That is, the combined pruning is in some sense the \strongest" of the two prunings.
4.4. COMPILING AND EVALUATING RANGES
Result
P'=NONE P'=DOM P'=MIN P'=MAX P'=MINMAX P'=INT
P=NONE NONE DOM MIN MAX MINMAX INT
P=DOM DOM DOM MIN MAX MINMAX INT
P=MIN MIN MIN MIN MINMAX MINMAX INT
P=MAX MAX MAX MINMAX MAX MINMAX INT
29 P=MINMAX MINMAX MINMAX MINMAX MINMAX MINMAX INT
P=INT INT INT INT INT INT INT
Table 4.2: Combination of prunings
4.3.2 The Optimized Propagation Algorithm We now present our propagation algorithm as it looks with the optimizations described in Section 4.3.1 on page 27 added. In Algorithm 4.2 on page 31, Q is a queue of pruned variables and is our constraint store.
4.4 Compiling and Evaluating Ranges In the propagation algorithm described earlier in Algorithm 4.2 on page 31, evaluation of a range r in is referred to as r . In this section we describe how this evaluation is performed. The evaluation of ranges in our solver is prepared for at compile time by compiling the range expression of the indexical to byte code for an abstract machine. Upon evaluation, an emulator for this abstract machine executes the byte code. Section 4.4.1 is an overview of the emulator, while the compilation to byte code is described in 4.4.2 on page 32. How the monotonicity information is extracted is described in 4.4.3 on page 32.
4.4.1 The Emulator The range emulator in our implementation originates in the AKL(FD)-work carried out by Bjorn Carlson for his PhD thesis [Carlson, 1995]. The source code has been somewhat modi ed and adapted to t into the run-time environment of Erlang. The interface to the emulator in through BIF:s, and they are described in Appendix A on page 57. The FD emulator evaluates ranges in a store , i.e. given a range r it computes r . Furthermore, it performs the intersection x \ r as shown in Algorithm 4.2 on page 31. We use a simple-minded abstract machine, where the emulator is stackbased. Each FD operator is interpreted by a corresponding instruction, which takes its arguments from the stack.
30
CHAPTER 4. IMPLEMENTATION
ci
const val dom dom min dom max range set add set sub union inter diff check set mod add sub mult divu divd mod min max size halt
Action v[j++] = ci+1 ; i = i + 2; k = ci+1 ; v[j++] = ak ; i = i + 2; k = ci+1 ; v[j++] = dom(ak ); i = i + 2; k = ci+1 ; v[j++] = min(ak ); i = i + 2; k = ci+1 ; v[j++] = max(ak ); i = i + 2; n = v[--j ]; m = v[--j ]; v[j++] = n :: m; i = i + 1; d = v[--j ]; n = v[--j ]; v[j++] = d + n; i = i + 1; d = v[--j ]; n = v[--j ]; v[j++] = d ? n; i = i + 1; d = v[--j ]; d0 = v[--j ]; v[j++] = d [ d0 ; i = i + 1; d = v[--j ]; d0 = v[--j ]; v[j++] = d \ d0 ; i = i + 1; d = v[--j ]; d0 = v[--j ]; v[j++] = d n d0 ; i = i + 1; if v[--j ] equals ; then j = j + 1; i = ci+1 else i = i + 2; d = v[--j ]; m = v[--j ]; v[j++] = d mod m; i = i + 1; n = v[--j ]; m = v[--j ]; v[j++] = n + m; i = i + 1; n = v[--j ]; m = v[--j ]; v[j++] = n ? m; i = i + 1; n = v[--j ]; m = v[--j ]; v[j++] = n m; i = i + 1; n = v[--j ]; m = v[--j ]; v[j++] = dn=me; i = i + 1; n = v[--j ]; m = v[--j ]; v[j++] = bn=mc; i = i + 1; n = v[--j ]; m = v[--j ]; v[j++] = n mod m; i = i + 1; d = v[--j ]; v[j++] = min(d); i = i + 1; d = v[--j ]; v[j++] = max(d); i = i + 1; d = v[--j ]; v[j++] = size(d); i = i + 1; return; Table 4.3: Byte code instructions
4.4. COMPILING AND EVALUATING RANGES
31
Algorithm 4.2 An optimized propagation algorithm Q
prop( , ) BEGIN WHILE not empty DO := ; := suspended indexicals on ; FOR each X r in DO IF is marked entailed THEN continue IF not affected by the pruning of THEN continue := ; CASE of : IF monotone in THEN fail ELSE continue : IF antimonotone in THEN mark the indexical entailed and continue otherwise: IF not monotone in THEN continue ; := ; IF queued THEN update pruning of ELSE := ; set pruning of ; IF constant in THEN mark as entailed ; END
Q Q n fyg
Q S
in
f
f
S
f
I
;
y
y
x \ r I r
x
r
r
x
Q
r
[ fx 2 I g
x
Q [ fxg
f
x
The emulator is provided with the byte code and the call frame a for the code, represented by a vector of (domain) variables. Furthermore, the emulator operates on three data areas; the code area c, the value stack v containing integers or references to domains, and the domain heap d containing all intervals and sets constructed in the emulation. At each invocation of the emulator, d is assumed empty. Hence, all memory in d is automatically reclaimed at each invocation.
32
CHAPTER 4. IMPLEMENTATION
Let i be the current code index, and j the current top of the value stack. The instructions are straightforward and de ned in Table 4.3 on page 30, where ci is the current instruction dispatched on, d and d0 denote domains, m and n denote integers, and the arithmetic operators are applied point-wise where appropriate. Besides computing the set and the intersection, our emulator calculates the pruning made to the target variable. This information is used as an optimization in our propagation algorithm, see Section 4.3.2 on page 29.
4.4.2 Compiling to Byte Code The translation of a range r into byte code is straightforward, and is de ned recursively by Table 4.4. Basically, a post x Polish notation is used, where we assume
r t1 :: t2 dom(x) r1 + r2 r1 ? r2 r1 _ r2 r1 ^ r2 r1 n r2 r1 ) r2 r0 mod x
Tr Tt2 Tt1 range dom Ax Tr2 Tr1 set add Tr2 Tr1 set sub Tr2 Tr1 union Tr2 Tr1 inter Tr2 Tr1 diff Tr1 check l Tr2 l: Tx Tr0 set mod
t n x t1 + t 2 t1 ? t 2 t1 t 2 t1 =t2 t0 mod x min(x) max(x) min(r) max(r) size(r)
Tt
n
const val x
A Tt2 Tt1 add Tt2 Tt1 sub Tt2 Tt1 mult Tt2 Tt1 div Ax Tt0 mod dom min Ax dom max Ax Tr min Tr max Tr size
Table 4.4: Translation of ranges and terms to byte code that an argument mapping Ax is given, such that Ax equals the position of x in the argument frame. The byte code for a range r is always terminated with a halt instruction.
4.4.3 Determining Monotonicity of Indexicals When evaluating an indexical, we must know whether it is monotone or antimonotone (Section 2.1.6 on page 12). The propagation algorithm in Section 4.3 on page 25 uses the monotonicity information. To make the monotonicity check easier at run-time, we prepare at compile-time by identifying those variables in the range that need to be determined for the range to be monotone. This is done recursively as indicated by the following tables.
4.4. COMPILING AND EVALUATING RANGES
33
Table 4.5 de nes two sets St /Gt containing the variables that must be determined for a linear term to decrease/increase with extensions of . (x) is a function whose value is ; if x is a number and fxg if x is a variable.
t x t1 + t2 t1 ? t2 xt (?x) t t=x t=?x t mod x min(x) max(x) size(x) min(r) max(r) size(r)
St
(x)
Gt
St1 [ St2 St1 [ Gt2 St [ (x) Gt [ (x) St [ (x) Gt [ (x) St [ (x) (x)
; ; Mr ; ;
(x)
Gt1 [ Gt2 Gt1 [ St2 Gt [ (x) St [ (x) Gt [ (x) St [ (x) Gt [ (x) ; (x) (x)
; Ar Ar
Table 4.5: Monotonicity of term expressions Table 4.6 builds on the previous de nition of shrinking/growing variable sets for terms, and de nes the monotonicity property for ranges. For example, a range t1 ::t2 is monotone if t1 grows and t2 shrinks, and thus is monotone if Gt1 [ St2 is determined.
r t1 :: t2 dom(x) r1 r2 ( 2 f_; ^; ); +; ?g) r1 n r2 r x ( 2 f; =; modg)
Mr Gt1 [ St2 ; M r1 [ M r2 Mr1 [ Ar2 Mr [ (x)
Ar St1 [ Gt2
(x)
Ar1 [ Ar2 Ar1 [ Mr2 Ar [ (x)
Table 4.6: Monotonicity of range expressions The computation outlined by the recursive rules above is performed at compile-time, and is annotated to the indexicals set at run-time, see Chapter 3 on page 17 for context. Using these Mr and Ar sets, we can at run-time invoke a simple and ecient algorithm for determining the monotonicity, see Algorithm 4.3 on the next page. Note that when all variables in Mr (Ar ) are determined in , M (A ) will be ; in and all extensions of .
34
CHAPTER 4. IMPLEMENTATION
Algorithm 4.3 The monotonicity algorithm monotonicity( Mr , Ar , ) M := Mr n fx : x determined in g; A := Ar n fx : x determined in g; IF M = A = ; THEN RETURN constant IF THEN = RETURN monotone IF THEN = RETURN antimonotone
M
;
A
;
4.5 Searching for Solutions The propagation algorithm previously described in Section 4.3 on page 25 achieves and maintains consistency. This is well, but not enough. Not only do we need a consistent constraint store which propagates constraints and prunes variable domains, we also need to nd speci c solution(s) to our stated constraint problem.
4.5.1 A Backtracking Scheme The state of the constraint store is that we have some variables with more or less pruned domains, e.g. X 2 f2; 3g and Y 2 f2; 3; 4g. Along with them there are a bunch of constraints (as indexicals). Remember that since our solver is not complete, i.e. the propagation algorithm does not ensure satis ability (not always arc-consistency either), not all combinations of values from the variable domains are solutions. To nd those combinations which are true solutions, we use search. We pick a variable and instantiate it to some value from its domain. Then we propagate this, and maybe get some pruning on other domains. We pick another, etc, and repeat this until we have determined all variables, and we have a solution. Inherent in this scheme is the choice of variable to instantiate, and to what value. These choices create choice points in our backtracking based search. Figure 4.1 on the next page shows the entities used to implement the backtracking. A choice point is created on the choice point stack each time we pick a variable and a value. During the propagation, we save data of variables and indexicals on the trail stack. This is depicted as in the gure. The data on the trail stack is separated by marks choice to indicate where each new choice point begins. The dashed arrows indicate that these marks implicitly refer to choice points. Using the data on the trail stack we may later undo the changes made to variables and indexicals, i.e. back up to the previous choice point. This is necessary when we
4.5. SEARCHING FOR SOLUTIONS Choice Point Stack
35 Trail Stack choice
{Var1, NextVal, RestOfVars} {Var2, NextVal, RestOfVars} choice choice
Figure 4.1: Trail and choice point stack in the backtracking scheme nd that the current instantiation of variables is not a solution or when we want more than one solution to our problem. The on the trail stack is either data from a variable or an indexical. The following table shows which parts that are trailed. For more details on what they represent, see Section 4.6 on the following page on data structures.
Variable f
Var, Queued flag, Domain, Trailed flag
g
Erlang pointer to the variable.
36
CHAPTER 4. IMPLEMENTATION
Indexical f
Ind, Entailed flag, ,
Erlang pointer to the indexical.
M A g
The Trailed- ag is true for a variable if the variable has been saved on the trail stack since the last choice point. This is used as a time/space optimization; a variable is never trailed twice for a given choice point.
4.5.2 Variable Ordering Our implementation has no special heuristic for choosing the value for variable, but we have made use of two standard techniques from CSP (see e.g. [Tsang, 1993] for an overview of such heuristics) to choose the variable to instantiate; naive and rst-fail.
Naive
Naive labeling chooses the rst variable at hand, thus making the choice more or less random.
First-Fail
First-fail labeling chooses the variable with the smallest domain or the largest amount of constraints suspended on it. This heuristic is dynamic (check for smallest domain is made at run-time) and suggests that the task which is most likely to fail should be performed rst. Search can be avoided by recognizing dead-ends as soon as possible. This technique is useful only when nding one or a few solutions to a problem; when searching for all solutions we have to traverse the entire search space, and it will not matter in which order we do it.
The performance dierence between these heuristics are all but negligible. Some timings for actual problems can be found in Section 5.1 on page 39. The implementation of these heuristics can be found in Appendix D.2 on page 83.
4.6 Data Structures This section presents the representation of our data structures; variables, domains and indexicals.
4.6. DATA STRUCTURES
37
4.6.1 Finite Domain Variables There are actually two representations of a nite domain variable|the representation in the user process and the representation in the solver process. The nite domain variable is represented as a tuple, consisting of the following parts:
Solver: f
Atom identifying type of structure. Unique structure identifying this instance of type nite domain variable. Common to the user and solver representation of the variable, and thus common to the user process and solver process. Used when transferring variables between user/solver processes. List of suspended indexicals. Boolean ag indicating if variable has been trailed since the last choice point. The domain of the variable. See Section 4.6.2. Boolean ag true if variable is queued in the propagation algorithm, see Algorithm 4.2 on page 31. One of fnone,int,min,max,minmax,domg. Tells the type of the last pruning.
fd var, Reference,
Indexicals, Trailed, Domain, Queued-flag, Pruning,
g User: f
fd client var, Reference,
g
Atom identifying type of structure. Unique structure identifying this instance. Same as in the solver representation above.
4.6.2 Finite Domains A nite domain in Erlang(FD) is either an Erlang integer or a tuple of the following format.
f
fd dom, Type, Lower, Upper, Bit-vector
g
Atom indicating that this tuple represents a nite domain. Either interval or set. Lower bound (de ned if interval or set). Upper bound (de ned if interval or set). Bit-vector (de ned if set only).
38
CHAPTER 4. IMPLEMENTATION
4.6.3 Finite Domain Indexicals The representation is a tuple:
f
fd ind, Reference, X, Args, (tuple) Deps, (tuple) Byte Code,
fEntailedg, M , A , g
Atom indicating that this tuple represents an indexical. Unique identi er. Target variable of the indexical. Argument variables in the indexical range. Dependencies (see Section 4.3.1 on page 27) for the argument variables. Byte code for evaluating the range expression of the indexical in the emulator. Flag set if indexical is entailed. This ag is shared between several (equivalent) indexicals, thus residing inside a tuple, allowing one to preserve the sharedness using destructive update. Monotonicity set Mr. Anti-Monotonicity set Ar.
Chapter 5
Evaluation In the following we evaluate the execution speed of Erlang(FD) compared to other constraint solvers, namely SICStus(FD) and AKL(FD). Our benchmark programs are a suite of more or less arti cial programs, but in some respect representative for the class of problems that nite domains constraint solvers are aimed for.
5.1 Benchmark Programs The programs are the following (a more complete description and a solution in Erlang(FD) can be found in Appendix C on page 63).
Queens
The problem of placing n queens on a n n chess board such that no two queens threaten each other. Consists solely of indexicals expressing dierence between variables.
Send More Money
The task is to assign numbers to letters, such that the equation S + E + N + D + M + O + R + E = M + O + N + E + Y holds. Consists of library constraints and all different/1 in Erlang(FD) and AKL(FD ). For SICStus(FD) we show both this approach and the library constraints replaced with inlined indexicals.
Five Houses
A logic puzzle. Consists of library constraints, all different/1 and indexicals in Erlang(FD). AKL(FD) uses inlined indexicals and all different/1, as do SICStus(FD). To express the disjunction, Erlang(FD) and AKL(FD) uses indexicals while SICStus(FD) make use of cardinality reasoning.
39
40
CHAPTER 5. EVALUATION
Ten equations
A system of linear arithmetic equations with a single solution. Library constraints are used in Erlang(FD) and AKL(FD). For SICStus(FD) we show inlined indexicals too.
Twenty equations
A system of linear arithmetic equations with a single solution. Library constraints are used in Erlang(FD) and AKL(FD). For SICStus(FD) we show inlined indexicals too.
Alpha
Assigning numbers to letters. Consists of library constraints and all different/1 in Erlang(FD ). AKL(FD ) has all different/1 plus a mixture of indexicals and library constraints. Both library constraints and inlined indexicals are shown for SICStus(FD).
Magic Series
Computing magic series. Consists of library constraints in all three solvers.
Suudoku
Assigning distinct numbers to squares in a grid. Solely all different/1 for all solvers.
5.2 Performance Figures The problem name is annotated with the amount of solutions requested (one or all), the variable ordering (see Section 4.5.2 on page 36), naive or (First-Fail), inl if compilation to inlined indexicals are performed, and nally ind if special purpose indexicals are used. The columns are as follows:
Fails
The number of times indexicals failed (evaluated to the empty set), and the search had to backtrack.
Pruned
The number of times the evaluation of an indexical pruned the domain of a variable.
Useless
The number of times the evaluation of an indexical resulted in neither pruning of a variable domain nor failure. These indexical evaluations are useless.
5.3. DISCUSSION
41
Time
The execution time of the problem milliseconds; this gure have been taken from the execution on a SPARCstation 4 with 32 Megabytes of RAM1 .
P/T
The ratio pruned domains per millisecond. In some sense the speed of the propagation algorithm. Not comparable if dierent compilation methods for arithmetics are used, i.e. inlined indexicals and library constraints.
Problem
Queens-8,one,naive,ind Queens-8,one,,ind Queens-8,all,naive,ind Queens-8,all,,ind Queens-16,one,naive,ind Queens-16,one,,ind SendMoreMoney,one,naive Five Houses,one,naive,ind Five Houses,one,,ind 10 Equations,one,naive 10 Equations,one, 20 Equations,one,naive 20 Equations,one, Alpha,one, Magic-4,one,naive Magic-7,one,naive Magic-4,one, Magic-7,one, Suudoku-6,one,naive Suudoku-6,one,
Fails Pruned Useless Time P/T 24 15 324 298 1833 72 1 5 2 49 35 49 36 17 2 8 0 0 4 6
185 138 3046 2944 20985 909 132 162 144 6637 6751 15272 14248 5066 102 743 100 324 581 693
28 25 594 580 8333 377 211 76 74 15239 16184 34174 31770 13287 224 1351 262 865 792 960
160 110 1520 1530 11430 720 130 160 170 3890 4020 8640 7690 4560 170 650 170 490 1650 1750
1.1 1.2 2.0 1.9 1.8 1.2 1.0 1.0 0.8 1.7 1.6 1.7 1.8 1.1 0.5 1.1 0.5 0.6 0.3 0.3
Table 5.1: Erlang(FD) execution speed
5.3 Discussion We will here give some analyzing comments to the gures given in the previous section. As you may have noticed we presented no gures regarding the eect of the various optimizations described in Section 4.3.1 on page 27; such an analysis can be found in [Carlson, 1995]. We will start with some questions and answers. The gures regarding AKL(FD) are not executed on the same machine but are scaled appropriately. 1
42
CHAPTER 5. EVALUATION
Problem
Queens-8,one,naive,ind Queens-8,one,,ind Queens-8,all,naive,ind Queens-8,all,,ind Queens-16,one,naive,ind Queens-16,one,,ind SendMoreMoney,one,naive SendMoreMoney,one,naive,inl Five Houses,one,naive Five Houses,one, 10 Equations,one,naive 10 Equations,one,naive,inl 10 Equations,one, 10 Equations,one,,inl 20 Equations,one,naive 20 Equations,one,naive,inl 20 Equations,one, 20 Equations,one,,inl Alpha,one, Alpha,one,,inl Magic-4,one,naive Magic-7,one,naive Magic-4,one, Magic-7,one, Suudoku-6,one,naive Suudoku-6,one,
Fails Pruned Useless Time P/T 24 23 324 292 1833 7 1 1 0 2 49 49 12 35 49 49 59 36 14 14 2 8 0 0 4 2
199 190 3032 2830 21519 180 106 60 84 123 5033 487 2084 505 9935 673 14400 490 3514 1576 116 601 115 378 611 533
23 18 504 466 8148 46 106 40 18 24 10218 3619 4769 4230 19602 5440 30685 4920 5947 3461 265 1314 261 866 744 649
30 40 350 390 2760 50 20 20 20 30 810 440 350 500 1570 680 2310 630 490 340 30 140 40 100 180 180
6.6 4.7 8.6 7.2 7.7 3.6 5.2 3.0 4.2 4.0 6.2 1.1 5.9 1.0 6.3 0.9 6.2 0.7 7.1 4.6 3.8 4.2 2.8 3.7 3.3 2.9
Table 5.2: SICStus(FD) execution speed
Why do fails dier between naive and rst-fail labeling?
As we pointed out in Section 4.5.2 on page 36, the choice of variable ordering is crucial for the eciency when nding a single solution, but almost negligible when we are traversing the entire search space. This is clear by the timing of Queens 8 and Queens 16 above. Why do fails dier between rst-fail labeling in dierent solvers? Their rst-fail algorithm is not identical, only similar. A small change of strategy in variable choice can have big impact on certain problems. Why do fails dier between naive labeling in dierent solvers? This happens if the problem formulations are dierent, making the programs express dierent levels of consistency. For example, take Five Houses where SICStus(FD) uses a dierent strategy for expressing disjunction compared to AKL(FD) and Erlang(FD).
5.3. DISCUSSION
Problem
43
Queens-8,one,naive,ind Queens-8,one,,ind Queens-8,all,naive,ind Queens-8,all,,ind Queens-16,one,naive,ind Queens-16,one,,ind SendMoreMoney,one,naive,ind Five Houses,one,naive,inl,ind Five Houses,one,,inl,ind 10 Equations,one,naive 10 Equations,one, 20 Equations,one,naive 20 Equations,one, Alpha,one, Magic-4,one,naive Magic-7,one,naive Magic-4,one, Magic-7,one, Suudoku-6,one,naive Suudoku-6,one,
Fails Pruned Useless Time P/T 24 23 324 290 1833 7 1 5 2 49 35 49 36 11 2 8 0 0 4 5
229 225 3740 3548 24032 199 54 158 136 6470 7356 12286 12081 2269 65 445 54 140 583 620
28 26 582 500 8165 45 50 63 56 12533 15589 22413 23865 3900 156 782 171 480 1312 1477
27 27 236 222 1268 40 27 27 22 331 381 613 609 172 27 72 31 45 168 172
8.4 8.3 15.8 15.9 18.9 4.9 2.0 5.8 6.1 19.5 19.3 20.0 19.8 13.1 2.4 6.1 1.7 3.1 3.4 3.6
Table 5.3: AKL(FD) execution speed
Why do pruned domains dier between inlined indexicals and library constraints ?
Obviously the number and the type of indexicals dier in the two approaches, which has an eect on the number of prunings performed (though it does not have any eect on the number of fails, since both approaches maintain intervalconsistency, see Section 4.1.3 on page 24). As we mentioned previously, using library constraints solely (see Section 4.1.3 on page 24) it is far from optimal, and a mixture is preferable as in AKL(FD) [Carlson, 1995].
Before looking at the dierent gures for fails and prunings, you probably noticed that Erlang(FD) is much slower compared to AKL(FD) and SICStus(FD). The performance gap to SICStus(FD) is a factor ranging from 2:75 to 14:4 depending on the problem, with an average value of 6:42 . The factor to AKL(FD) ranges from 4:0 to 26:4 with an average of 9:7. In Section 5.4 on the following page we describe the dierences between the solvers, and in Section 5.5 on the next page we show in what sections the time is spent in Erlang(FD). The execution times used for SICStus(FD) are those using library constraints. If instead choosing the gures for the inlined indexicals version, the average value is about 8 0. 2
:
44
CHAPTER 5. EVALUATION
5.4 Dierences Between The Solvers We here give a short listing of the dierences between the nite domain constraint solvers compared; Erlang(FD), SICStus(FD) and AKL(FD). Erlang(FD) SICStus(FD) AKL(FD) Inlined Uses library Uses either, Uses a mixture, indexicals vs. constraints solely. con gurable by depending on the library the user when arity of the constraints compiling constraint. Implementation Range emulator Range emulator Entirely in C. language in C, propagation in C, propagation Trailed-based algorithm in algorithm mainly backtracking Erlang. in Prolog but search in C, not Trailed-based with some more using backtracking expensive copying-based search in Erlang. functions coded in search a la AKL. A total of 2400 C. Prolog's lines of Erlang standard code and 2100 trailed-based lines of C code. backtracking search is used. A total of 2300 lines of Prolog code and 2700 lines of C code. Propagation Variable As Erlang(FD) + As Erlang(FD) + optimizations dependencies, unique suspension time stamps for entailed ag, lists for each indexicals. variable queue dependency and with unique variable + variables. maintains a queue of indexicals instead of variables. Summarizing, the basis (propagation of indexicals) are alike, but the implementation technique and implementation level dier somewhat.
5.5 Pro ling Erlang(FD) In this section we do a pro led execution of Erlang(FD), running the entire suite of benchmarks. There is a tool eprof for Erlang used to pro le the execution of Erlang
5.5. PROFILING ERLANG(FD )
45
code, but unfortunately it requires an Erlang 4.3 implementation and Erlang(FD) is implemented using Erlang BEAM 4.23 We then use gprof, and get a view of the time spent from C level. Table 5.4 shows the most time consuming functions. The rst column 'Time' is in percent of the total execution time, the second column is the number of calls made and the third column denotes the average time spent per call in milliseconds. The three rightmost columns
Time 9.48 5.82 3.72 3.41 3.29 3.06 3.01 2.93 2.68 2.61 2.19 1.71 1.60 1.46 1.40 1.23 1.18
Calls ms/Calls Name
2876 ? ? ? 3239282 ? 229408 4714768 ? 8690 ? 253 ? 1809081 1537450 229408 30587
2.90 5120.00 3270.00 3000.00 0.00 2690.00 0.01 0.00 2360.00 0.26 1930.00 5.93 1410.00 0.00 0.00 0.00 0.03
gc indexical propagate fd mon monotonicity fd dyntup element do mixed comp fd mon remove determined2 evaluate range element fd trail trail ind schedule lookup var change heap fd trail trail var size 1 eq intersect fd copy struct
RT p E C p p p p p p p p p p p p p p p p
Table 5.4: The most time consuming functions in Erlang(FD). mark if the function is executed within Erlang's run-time system, Erlang(FD) code written in Erlang4 or Erlang(FD) code written in C. The last column is thus the functions evaluate range and intersect fd which belong to the range emulator of Erlang(FD). By summing up the two rightmost columns, one may notice that 84% of the time spent executing Erlang(FD) is spent on running code written in Erlang. Thus, the time spent executing Erlang(FD) code written in C is less than 1=5 of the total time. This gure, if considering all functions, not just the most time consuming which is shown here, shrinks even more | to about 1=10 of the total time. 3 Of course, Erlang(FD ) runs on JAM 4.3 too, but is about 4{5 times slower, and thus the pro ling information is not reliable regarding the ratio between time spent in C and Erlang. 4 Including BIFs called from within Erlang code.
46
CHAPTER 5. EVALUATION
5.6 Summary and Possible Improvements We have developed a constraint solver with poor performance gures compared to existing solvers in AKL and SICStus Prolog. The majority of the execution time is spent on Erlang-level. Time spent on C-level is almost negligible. The time spent running Erlang code is fairly well spread over the entire propagation algorithm; there are no single functions/tasks responsible for more than 10%. What changes should be made to improve performance? Well, there are quite a few implementation choices and constraints5 that aect the performance. Here we will consider implementation facts/improvements related to the current indexical scheme, see Section 6.2 on page 50 for ideas in general to improve the eciency of solving nite domain constraints.
Representation of abstract data types
Finite domain variables and indexicals are represented as tuples. This is natural and ecient, but the interface to these structures could be improved. As of now, every access to a nite domain or indexical triggers a function call. Small tests have indicated that replacing these functions with macros to avoid the function call overhead could improve overall performance with 10{30%. Another choice would be to use records, tuples with named elds, which are present in the JAM 4.3 Erlang implementation and which we believe are to be introduced in forthcoming versions of the Erlang language.
Cyclic structures
The implementations of Erlang we have used (BEAM 4.2 and JAM 4.3) does not support cyclic data structures. This is unfortunate since the variables and indexicals naturally form a cyclic graph in the memory. The lack of cyclic structures forces us to use a clumsy and somewhat inecient variable database, where lookups for variables are done when evaluating the range of an indexical. This is expensive, as can be seen in the table above. The function fd dyntup element 2 is the one used to make lookups in this database (which is implemented as a dynamically growing tuple).
Backtracking
Backtracking with choice-point stack and trail would not ordinarily be implemented in a high-level language. The reason for coding the search in Erlang was twofold. To avoid problem with the garbage collection mechanism and to make it easier to experiment with dierent search strategies.
Implementation language
We believe that our choice of Erlang as main implementation language counts
Constraints imposed by the Erlang language and it's implementation, we are not referring to nite domain constraints. Nice for a change :-) 5
:::
5.6. SUMMARY AND POSSIBLE IMPROVEMENTS
47
for a large part of the performance dierence compared to AKL(FD) (entirely in C) and SICStus(FD) (most of the more expensive functions in C). With a larger part of the Erlang(FD) code written in C, performance could be improved generally. To make it easier to use C for extensions like Erlang(FD), Erlang should better support interfacing to the scheduler and the garbage collector (as AKL does). With such primitives, an ecient search could be implemented in C. Along with implementing the propagation algorithm in C, Erlang(FD) would be approximately as fast as AKL(FD )| provided that the overhead caused by lack of cyclic structures could be eliminated.
Propagation optimizations
The are a number of small tricks that can be applied to the propagation of indexicals that we have not made use of. Some of them are exploited in SICStus(FD). Before queueing a variable with pruning P we could check that there an indexical suspended that will be triggered by P . If not, there is no need to queue the variable. Variables can have several suspension lists, one for each dependency type. This would make the \aected" calculation more ecient.
48
CHAPTER 5. EVALUATION
Chapter 6
Conclusion We end this report by summarizing our work and peek forward to see what can and what will be done in the future.
6.1 Summary This report have described the work of embedding nite domain constraint into the functional programming language Erlang. We have extended the expressiveness of the language by allowing arithmetic expressions to be stated as facts, and developed a solver for generating solutions satisfying these facts (as constraints). Our performance evaluation and comparison to other constraint solvers shows that Erlang(FD) lacks in performance. With respect to the fact that the gap is quite large, we have pointed out possible weaknesses in the implementation and suggested solutions. Of our goals with this work, we succeeded with all but one. There was not time to implement the ask -primitive, and thus our vision of processes interacting with the constraint store like agents in concurrent constraint programming was not ful lled. We still think that this is possible and that the expressiveness of Erlang would gain from it. The following section is concerned with the further developments of constraints in Erlang and constraint solvers in general.
49
50
CHAPTER 6. CONCLUSION
6.2 Future Work With the history of Constraint Satisfaction Problems and the evolution of Constraint Logic Programming in mind, we will here try to point at some possible uses and approaches regarding constraint solvers in general and constraints in Erlang especially.
6.2.1 Future Development of Erlang(FD) First, one of the major tasks with further development on Erlang(FD) would be to gain performance. Although this is not the single key to make constraints in Erlang useful, one must show reasonable eciency. Being approximately a magnitude slower than comparable solvers is not acceptable for real-world programs. Secondly, the ask-primitive should be designed and implemented. Its semantics is not as obvious as in CLP, since there is no search in Erlang to interact with as it does in logic programming. Finally, the aim of embedded constraints in a language like Erlang should be to blend and help with the kind of tasks that Erlang are used to solve. We imagine the primary use of constraints in Erlang would be of semantics, not on raw speed. In e.g. soft real-time systems like telecommunication and simulation, there are other demands. What is almost entirely lacking in solvers of today are interactivity, user control, execution time estimates, preemptive search and reactivity. Processes interacting with the solver should be able to both add and remove constraints, control the search and through the constraint store aect other processes. These are all features that would distinguish a constraint solver in Erlang from others, and at the same time blend nicely with other properties of the language.
6.2.2 Ongoing Work with Constraint Solvers We are currently beginning work on constraints maintaining a greater level of consistency than arc- and interval-consistency. Consider for example the all different/1 constraint. This is a typical case where one, by using a higher consistency, would be able to prune domains:
Example 6.1 di([X1 ; : : : ; Xn ]) Xi 6= Xj ; i 6= j
6.2. FUTURE WORK
51 di([X1 ; X2 ; X3 ]) X1 2 f1; 2g X2 2 f1; 2g X3 2 f1; 2; 3g )X3 = 3
The pruning of X3 is not achieved when all different/1 is expressed using binary relations as in Erlang(FD) (see code in Appendix D.1 on page 83). [Regin, 1993] shows how to express the above dierence relation to achieve the desired pruning. It is based on expressing variables and values as sides in a bipartite graph and use graph matching to eliminate edges that represent the domains of the variables. A constraint like all different in that shape (considering all variables at once) could be called a global constraint. CHIP [Dincbas et al., 1988], a now commercial constraint solver, use this kind of constraints heavily. Maybe this is the way to real constraint solving performance. There are de nitely more to be done, despite the following words that | allegedly | have been uttered once: \Everything that can be invented has been invented". Charles H. Duell, Commissioner, U.S. Oce of Patents, 1899.
52
CHAPTER 6. CONCLUSION
Acknowledgements Before anything else, I owe a lot to Bjorn Carlson. He inspired me to this work, he provided the framework and guided me through. This would not have been possible without his advises, enthusiasm and support. I would also like to thank Bogumil Hausman for sharing the secrets of BEAM with me, and Mats Carlsson for his advices and thorough reading of this report. I would also like to mention Martin Wikborg, since [Wikborg, 1995] inspired me when I wrote my introduction to Erlang, and Mikael Sjodin and Kristina Sirhuber for reading and commenting on this report. My personal gratitude to Kiina for being there for me.
53
54
CHAPTER 6. CONCLUSION
Bibliography [Armstrong et al., 1993] J. Armstrong, R. Virding, and M. Williams. Concurrent Programming in ERLANG. Prentice Hall, 1993. [Carlson and Carlsson, 1993] B. Carlson and M. Carlsson. Constraint Solving and Entailment Algorithms for cc(FD). ESPRIT Project # 7195 (ACCLAIM) deliverable, SICS, 1993. [Carlson, 1995] Bjorn Carlson. Compiling and Executing Finite Domain Constraints. Uppsala theses in computing science 21, Uppsala University, May 1995. [Diaz and Codognet, 1993] D. Diaz and P. Codognet. Compiling constraints in clp(FD). Research report, INRIA, 1993. [Dincbas et al., 1988] M. Dincbas, P. Van Hentenryck, H. Simonis, A. Aggoun, T. Graf, and F. Berthier. The Constraint Logic Programming Language CHIP. In Proceedings of the International Conference on Fifth Generation Computer Systems, 1988. [Hausman, 1994] B. Hausman. Turbo Erlang: Approaching the Speed of C. In Evan Tick and Giancarlo Succi, editors, Implementations of Logic Programming Systems, pages 119{135. Kluwer Academic Publishers, 1994. [Hentenryck, 1991] Pascal Van Hentenryck. Constraint logic programming. The Knowledge Engineering Review, 6(3):151{194, 1991. [Mackworth, 1977] Alan Mackworth. Consistency in networks of relations. Arti cial Intelligence, 8(1):99{118, 1977. [Mackworth, 1992] Alan K. Mackworth. Constraint satisfaction. In Encyclopedia of Arti cial Intelligence, volume 1, pages 285{293, 1992. [Regin, 1993] J-C Regin. A ltering algorithm for constraints of dierence in CSPs. Technical Report R.R.LIRMM 93-068, LIRM, Dec 1993. [Saraswat, 1989] V. A. Saraswat. Concurrent Constraint Programming Languages. PhD thesis, Carnegie-Mellon University, January 1989.
55
56
BIBLIOGRAPHY
[Tsang, 1993] Edward Tsang. Foundations of Constraint Satisfaction. Academic Press, 1993. [Wikborg, 1995] Martin Wikborg. Comparing erlang and SDL/SDT for software development. Master's thesis, Dept. of Computer Systems, Uppsala University, 1995.
Appendix A
Erlang(FD) BIF:s Here we list all BIF:s that have been added to Erlang.
A.1 Compile-Time BIF:s fd map op(Op)
Given an atom representing an emulator instruction, this BIF maps this atom to the corresponding emulator instruction number.
A.2 Emulator BIF:s fd init emulator()
Sets up necessary data structures in the emulator.
fd evaluate indexical(Indexical, Variable Domain, Args)
This the main BIF for accessing the emulator. Given an indexical (the bytecode is the one thing used), the domain of the target variable and the argument variables, it calculates what in Algorithm 4.2 on page 31 is denoted as I := x \ r . I.e. it calculates the set value of the indexical range. Returned is the pruning made to the target variable X . The new domain of X is not returned but is accessible through the next BIF.
fd get intersection(Variable Domain)
Given a domain it lls in the resulting intersected domain from the last invocation of the previous BIF fd evaluate indexical.
57
APPENDIX A. ERLANG(FD ) BIF:S
58
A.3 Debugging BIF:s fd print bitv(Bitvector)
Given a bitvector (from a domain) as a binery this BIF returns a readable string representation of the bitvector (as a binary)
A.4 Misc BIF:s fd size(Variable)
Returns the size (regarded as set) of the domain of the argument.
fd in domain(Integer, Variable Domain)
Returns true if Integer is in the domain of Variable, false otherwise.
fd setelement(Index, Tuple, Value)
Returns Tuple with Value replacing index Index. Like the standard BIF setelement [Armstrong et al., 1993], but with destructive update and without type checking (unless during debugging).
fd element(Index, Tuple)
Returns the Index'ed element of Tuple. Like the standard BIF element, but without type checking (unless during debugging).
Appendix B
User Manual This document describes how to use Erlang(FD), which is an extension of Erlang with a solver for nite domain constraints. When reading this manual, you might nd it handly to simultaneously glance at Appendix C on page 63 with some example programs.
B.1 Introduction A constraint program in general consists of three phases; (1) allocating variables, (2) telling constraints, and (3) solving for a solution. See e.g. the program Send More Money (Appendix C.1 on page 63). Before doing any of these in Erlang(FD) though, just must initialize the solver process. The following sections address (in order) these subjects.
B.2 Initialization Before allocating variables and telling constraints just must initialize the solver process, and when you are done, you may kill the solver. This is done through the following functions: fd:init()
Initializes the solver process, and thus enables Erlang(FD).
fd:close()
Stops the solver process, and thus exits Erlang(FD).
59
60
APPENDIX B. USER MANUAL
B.3 Allocating Variables Finite domain variables must be explicitly allocated in Erlang(FD). This is done using either of fd:range([Lower,Upper])
)
Variable
Returns a nite domain variable with (optional) interval bounds Lower and Upper.
fd:range list(N,[Lower,Upper])
)
[Variables,...]
Returns a list of N nite domain variables with (optional) interval bounds Lower and Upper.
If not speci ed, lower bound equals 0 and upper bound equals 16777216 (224 ).
B.4 Telling Constraints Constraints can be set using two dierent approaches; arithmetic constraints or indexicals. In both cases, the function to use is fd:tell().
B.4.1 Using Arithmetic Constraints You can state your linear arithmetic constraints directly, e.g. as in fd:tell(2*X = 48)
There is nothing more to it, actually.
B.4.2 Using Indexicals In addition to the arithmetic constraints, you can state your indexicals directly, see e.g. the program Queens (Appendix C.2 on page 65): constrain(X,Y,N) -> fd:tell(X in diffdom(X) \ (dom(Y) \/ dom(Y)-N \/ dom(Y)+N), Y in diffdom(Y) \ (dom(X) \/ dom(X)-N \/ dom(X)+N)).
We will not dwelve into the science of indexicals here, but just note a few things;
B.5. GENERATING SOLUTIONS
61
syntax & semantics
In Section 2.1.6 on page 10 you nd a description of how indexicals look and behave.
equivalent indexicals
Bevare that indexicals inside the same fd:tell are treated as equivalent, see Section 4.2 on page 25.
B.5 Generating Solutions Solutions (as values to your variables) can be requested using the following functions: fd:solve(ListOfVariables,[Module,Function])
)
[Values,...]
Given a list of (previously allocated) nite domain variables, this function returns a solution, i.e. a list with values for your variables. This can be used repeatedly, getting one solution at a time. If fModule,Functiong is speci ed, it is used as the variable selection function in the solver. Pre-de ned are ffd lib,firstg and ffd lib,ffg, giving left-right ordering and rst-fail ordering, respectively. Left-right ordering is default.
fd:solve all(ListOfVariables)
)
[[Values,...],...]
As above, but returns all solutions at once.
If no (more) solution can be found, fail is returned.
B.6 User Library In addition to the built-in functions described above, there exists some high-level library functions. They reside in the same module as the library constraints, see Appendix D.3 on page 84. all different([Vars,...])
Constrains the variables to all have dierent values.
eq iff(X,V)
)
B
Library ( constraint B = eq iff(X; V ) expressing B = 1 if X equals V, 0 otherwise. See Program Magic Series (Appendix C.7 on page 77) for an example of use.
62
APPENDIX B. USER MANUAL
Appendix C
Example Programs C.1 Send More Money The problem amounts to computing an assignment of numbers from 0 to 9 to the letters S, E, N, D, M, O, R, and Y, such that adding the words \SEND" and \MORE" equals \MONEY". Furthermore, S and M cannot be assigned 0, and no two dierent letters can be assigned the same number. The solution to this problem, using nite domain constraints, comes out as follows. Let xs , xe , xn , xd , xm , xo , xr , and xy be variables such that each one of them is contained in the set f0; : : : ; 9g. The constraints modeling the problem are thus:
xs 6= 0 xm 6= 0
xs; xe; xn ; xd ; xm ; xo ; xr ; xy ]) 1000xs + 100xe + 10xn + xd + 1000xm + 100xo + 10xr + xe = 10000xm + 1000xo + 100xn + 10xe + xy where all different([x1 ; : : : ; xk ]) is true i xi 6= xj is true, where i 6= j and 1 i; j k. Since, xs and xm must be dierent from 0, it follows that xs; xm 2 f1; : : : ; 9g. The only solution to this problem is 9567 + 1085 = 10652, i.e. xs = 9; xe = 5; xn = 6; xd = 7; xm = 1; xo = 0; xr = 8, and xy = 2. all different([
send() -> fd:init(), [S,M] = fd:range_list(2,1,9), [E,N,D,O,R,Y] = fd:range_list(6,0,9), fd_lib:all_different([S,E,N,D,M,O,R,Y]), fd:tell(1000*S+100*E+10*N+D+1000*M+100*O+10*R+E =
63
64
APPENDIX C. EXAMPLE PROGRAMS 10000*M+1000*O+100*N+10*E+Y), Sol = fd:solve([S,E,N,D,M,O,R,Y]), fd:close(), Sol.
C.2. QUEENS
65
C.2 Queens The N-queens problem needs very little introduction. Given a chess board of size n n, place n queens such that no two queens threaten each other. This is formulated by associating one variable xi per queen, in the range between 1 and n, stating that no two queens can share column, row, or diagonal. Assume that variable xi is assigned to row i, hence we only need to worry about the columns and the diagonals. The constraints which maintain the \no-threat" relation are thus as follows: xi 6= xj ; if i 6= j; (dierent columns) xi 6= xi+1 + 1 6= xi+1 ? 1 6= xi; 1 i n ? 1; (dierent diagonals) xi 6= xi+2 + 2 6= xi+2 ? 2 6= xi; 1 i n ? 2; .. . x1 6= xn?1 + n ? 1 6= xn?1 ? (n ? 1) 6= x1 queens(N,Type) -> queens(N,Type,{fd_lib,naive}). queens(N,Type,F) -> fd:init(), L=domain_list(N,N), constrain_list(L), case Type of all -> Sol = fd:solve_all(L,F); _ -> Sol = fd:solve(L,F) end, fd:close(), Sol. domain_list(0,N) -> []; domain_list(M,N) -> [fd:range(1,N)|domain_list(M-1,N)]. constrain_list([]) -> true; constrain_list([X|L]) -> constrain_each(X,L,1), constrain_list(L). constrain_each(X,[],N) -> true; constrain_each(X,[Y|L],N) -> constrain(X,Y,N), constrain_each(X,L,N+1).
66
APPENDIX C. EXAMPLE PROGRAMS
constrain(X,Y,N) -> fd:tell(X in diffdom(X) \ (dom(Y) \/ dom(Y)-N \/ dom(Y)+N), Y in diffdom(Y) \ (dom(X) \/ dom(X)-N \/ dom(X)+N)).
C.3. ALPHA
67
C.3 Alpha The numbers 1 - 26 have been randomly assigned to the letters of the alphabet. The numbers beside each word are the total of the values assigned to the letters in the word. e.g for LYRE L,Y,R,E might equal 5,9,20 and 13 respectively or any other combination that add up to 47. The problem - What is the value of D ? BALLET 45 POLKA 59 CELLO 43 QUARTET 50 CONCERT 74 SAXOPHONE 134 FLUTE 30 SCALE 51 FUGUE 50 SOLO 37 GLEE 66 SONG 61 JAZZ 58 SOPRANO 82 LYRE 47 THEME 72 OBOE 53 VIOLIN 100 OPERA 65 WALTZ 34
Solution:
[A,B, C,D, E, F,G, H, I, J, K, L,M,N, O, P, Q,R, S, T,U,V, W,X, Y, Z] [5,13,9,16,20,4,24,21,25,17,23,2,8,12,10,19,7,11,15,3,1,26,6,22,14,18] alpha() -> alpha({fd_lib,naive}). alpha(Lab) -> fd:init(), LD = fd:range_list(26,1,26), [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z] = LD, fd_lib:all_different(LD), fd:tell( B + A + L + L + E + T = 45, C + E + L + L + O = 43, C + O + N + C + E + R + T = 74, F + L + U + T + E = 30, F + U + G + U + E = 50, G + L + E + E = 66, J + A + Z + Z = 58, L + Y + R + E = 47, O + B + O + E = 53, O + P + E + R + A = 65, P + O + L + K + A = 59, Q + U + A + R + T + E + T = 50, S + A + X + O + P + H + O + N + E = 134, S + C + A + L + E = 51,
68
APPENDIX C. EXAMPLE PROGRAMS S + O + L + O S + O + N + G S + O + P + R + T + H + E + M + V + I + O + L + W + A + L + T + ), Sol = fd:solve(LD,Lab), fd:close(), Sol.
A + N + O E I + N Z
= = = = = =
37, 61, 82, 72, 100, 34
C.4. 10 EQUATIONS
69
C.4 10 Equations The following is a simple system of linear equations over the natural numbers, where the variables range between 0 and 10, with exactly one solution. The problem is thus to compute this solution. 98527x1 + 34588x2 + 5872x3 + 59422x5 + 65159x7 = 1547604 + 30704x4 + 29649x6 ; 98957x2 + 83634x3 + 69966x4 + 62038x5 + 37164x6 + 85413x7 = 1823553 + 93989x1 ; 900032 + 10949x1 + 77761x2 + 67052x5 = 80197x3 + 61944x4 + 92964x6 + 44550x7 ; 73947x1 + 84391x3 + 81310x5 = 1164380 + 96253x2 + 44247x4 + 70582x6 + 33054x7 ; 13057x3 + 42253x4 + 77527x5 + 96552x7 = 1185471 + 60152x1 + 21103x2 + 97932x6 ; 1394152 + 66920x1 + 55679x4 = 64234x2 + 65337x3 + 45581x5 + 67707x6 + 98038x7 ; 68550x1 + 27886x2 + 31716x3 + 73597x4 + 38835x7 = 279091 + 88963x5 + 76391x6 ; 76132x2 + 71860x3 + 22770x4 + 68211x5 + 78587x6 = 480923 + 48224x1 + 82817x7 ; 519878 + 94198x2 + 87234x3 + 37498x4 = 71583x1 + 25728x5 + 25495x6 + 70023x7 ; 361921 + 78693x1 + 38592x5 + 38478x6 = 94129x2 + 43188x3 + 82528x4 + 69025x7 The above equations can be stated directly as nite domain constraints, adding the domain constraints xi 2 f0; : : : ; 10g, where 1 i 7. eq10() -> eq10(one, {fd_lib,naive}). eq10(Type,Lab) -> fd:init(), [X1,X2,X3,X4,X5,X6,X7] = fd:range_list(7,0,10), fd:tell( 0+98527*X1+34588*X2+5872*X3+59422*X5+65159*X7 = 1547604+30704*X4+29649*X6, 0+98957*X2+83634*X3+69966*X4+62038*X5+37164*X6+85413*X7 = 1823553+93989*X1,
70
APPENDIX C. EXAMPLE PROGRAMS
900032+10949*X1+77761*X2+67052*X5 = 0+80197*X3+61944*X4+92964*X6+44550*X7, 0+73947*X1+84391*X3+81310*X5 = 1164380+96253*X2+44247*X4+70582*X6+33054*X7, 0+13057*X3+42253*X4+77527*X5+96552*X7 = 1185471+60152*X1+21103*X2+97932*X6, 1394152+66920*X1+55679*X4 = 0+64234*X2+65337*X3+45581*X5+67707*X6+98038*X7, 0+68550*X1+27886*X2+31716*X3+73597*X4+38835*X7 = 279091+88963*X5+76391*X6, 0+76132*X2+71860*X3+22770*X4+68211*X5+78587*X6 = 480923+48224*X1+82817*X7, 519878+94198*X2+87234*X3+37498*X4 = 0+71583*X1+25728*X5+25495*X6+70023*X7, 361921+78693*X1+38592*X5+38478*X6 = 0+94129*X2+43188*X3+82528*X4+69025*X7 ), case Type of all -> Sol = fd:solve_all([X1,X2,X3,X4,X5,X6,X7], Lab); _ -> Sol = fd:solve([X1,X2,X3,X4,X5,X6,X7], Lab) end, fd:close(), Sol.
C.5. 20 EQUATIONS
71
C.5 20 Equations The following is a simple system of linear equations over the natural numbers, where the variables range between 0 and 10, with exactly one solution. The problem is thus to compute this solution. 876370 + 16105x1 + 6704x3 + 68610x6 = 62397x2 + 43340x4 + 95100x5 + 58301x7 ; 533909 + 96722x5 = 51637x1 + 67761x2 + 95951x3 + 3834x4 + 59190x6 + 15280x7 ; 915683 + 34121x2 + 33488x7 = 1671x1 + 10763x3 + 80609x4 + 42532x5 + 93520x6 ; 129768 + 11119x2 + 38875x4 + 14413x5 + 29234x6 = 71202x1 + 73017x3 + 72370x7 ; 752447 + 58412x2 = 8874x1 + 73947x3 + 17147x4 + 62335x5 + 16005x6 + 8632x7 ; 90614 + 18810x3 + 48219x4 + 79785x7 = 85268x1 + 54180x2 + 6013x5 + 78169x6 ; 1198280 + 45086x1 + 4578x3 = 51830x2 + 96120x4 + 21231x5 + 97919x6 + 65651x7 ; 18465 + 64919x1 + 59624x4 + 75542x5 + 47935x7 = 80460x2 + 90840x3 + 25145x6 ; 43525x2 + 92298x3 + 58630x4 + 92590x5 = 1503588 + 43277x1 + 9372x6 + 60227x7 ; 47385x2 + 97715x3 + 69028x5 + 76212x6 = 1244857 + 16835x1 + 12640x4 + 81102x7 ; 31227x2 + 93951x3 + 73889x4 + 81526x5 + 68026x7 = 1410723 + 60301x1 + 72702x6 ; 94016x1 + 35961x3 + 66597x4 = 25334 + 82071x2 + 30705x5 + 44404x6 + 38304x7 ; 84750x2 + 21239x4 + 81675x5 = 277271 + 67456x1 + 51553x3 + 99395x6 + 4254x7 ; 29958x2 + 57308x3 + 48789x4 + 4657x6 + 34539x7 = 249912 + 85698x1 + 78219x5 ; 85176x1 + 57898x4 + 15883x5 + 50547x6 + 83287x7 = 373854 + 95332x2 + 1268x3 ; 87758x2 + 19346x4 + 70072x5 + 44529x7 =
72
APPENDIX C. EXAMPLE PROGRAMS
740061 + 10343x1 + 11782x3 + 36991x6 ; 49149x1 + 52871x2 + 56728x4 = 146074 + 7132x3 + 33576x5 + 49530x6 + 62089x7 ; 29475x2 + 34421x3 + 62646x5 + 29278x6 = 251591 + 60113x1 + 76870x4 + 15212x7 ; 22167 + 29101x2 + 5513x3 + 21219x4 = 87059x1 + 22128x5 + 7276x6 + 57308x7 ; 821228 + 76706x1 + 48614x6 + 41906x7 = 98205x2 + 23445x3 + 67921x4 + 24111x5 The above equations can be stated directly as nite domain constraints, adding the domain constraints xi 2 f0; : : : ; 10g, where 1 i 7. eq20() -> eq20({fd_lib,naive}). eq20(Lab) -> fd:init(), [X1,X2,X3,X4,X5,X6,X7] = fd:range_list(7,0,10), fd:tell( 876370+16105*X1+6704*X3+68610*X6 = 0+62397*X2+43340*X4+95100*X5+58301*X7, 533909+96722*X5 = 0+51637*X1+67761*X2+95951*X3+3834*X4+59190*X6+15280*X7, 915683+34121*X2+33488*X7 = 0+1671*X1+10763*X3+80609*X4+42532*X5+93520*X6, 129768+11119*X2+38875*X4+14413*X5+29234*X6 = 0+71202*X1+73017*X3+72370*X7, 752447+58412*X2 = 0+8874*X1+73947*X3+17147*X4+62335*X5+16005*X6+8632*X7, 90614+18810*X3+48219*X4+79785*X7 = 0+85268*X1+54180*X2+6013*X5+78169*X6, 1198280+45086*X1+4578*X3 = 0+51830*X2+96120*X4+21231*X5+97919*X6+65651*X7, 18465+64919*X1+59624*X4+75542*X5+47935*X7 = 0+80460*X2+90840*X3+25145*X6, 0+43525*X2+92298*X3+58630*X4+92590*X5
C.5. 20 EQUATIONS =
1503588+43277*X1+9372*X6+60227*X7,
0+47385*X2+97715*X3+69028*X5+76212*X6 = 1244857+16835*X1+12640*X4+81102*X7, 0+31227*X2+93951*X3+73889*X4+81526*X5+68026*X7 = 1410723+60301*X1+72702*X6, 0+94016*X1+35961*X3+66597*X4 = 25334+82071*X2+30705*X5+44404*X6+38304*X7, 0+84750*X2+21239*X4+81675*X5 = 277271+67456*X1+51553*X3+99395*X6+4254*X7, 0+29958*X2+57308*X3+48789*X4+4657*X6+34539*X7 = 249912+85698*X1+78219*X5, 0+85176*X1+57898*X4+15883*X5+50547*X6+83287*X7 = 373854+95332*X2+1268*X3, 0+87758*X2+19346*X4+70072*X5+44529*X7 = 740061+10343*X1+11782*X3+36991*X6, 0+49149*X1+52871*X2+56728*X4 = 146074+7132*X3+33576*X5+49530*X6+62089*X7, 0+29475*X2+34421*X3+62646*X5+29278*X6 = 251591+60113*X1+76870*X4+15212*X7, 22167+29101*X2+5513*X3+21219*X4 = 0+87059*X1+22128*X5+7276*X6+57308*X7, 821228+76706*X1+48614*X6+41906*X7 = 0+98205*X2+23445*X3+67921*X4+24111*X5 ), Sol = fd:solve_all([X1,X2,X3,X4,X5,X6,X7], Lab), fd:close(), Sol.
73
74
APPENDIX C. EXAMPLE PROGRAMS
C.6 Five Houses The ve house problem is a logical puzzle which is naturally formulated as a nite domain problem. Five men of dierent nationality (England, Spain, Japan, Italy, Norway) live in the rst ve houses on a street. They all each have a profession (painter, diplomat, violinist, doctor, sculptor), one animal (dog, zebra, fox, snail, horse), and one favorite drink (juice, water, tea, coee, milk), all dierent from the others. Each of the houses is painted in a color dierent from all the others (green, red, yellow, blue, white). Furthermore:
The Englishman lives in the red house. The Spaniard owns the dog. The Japanese is the painter. The Italian likes tea. The Norwegian lives in the leftmost house. The owner of the green house likes coee. The green house is to the right of the white one. The sculptor breeds snails. The diplomat lives in the yellow house. Milk is drunk in the third house. The Norwegian's house is next to the blue one. The violinist likes juice. The fox is in the house next to the doctor's house. The horse is in the house next to the diplomat's.
The problem is thus to infer who owns the zebra and who drinks water. Let ni , ci , pi , ai and di range between 1 and 5, where i range between 1 and 5 denoting the corresponding house. Let ni represent the nationalities, e.g. n1 denotes the nationality of the person in house 1, ci represent the colors, pi represent the professions, ai represent the animals, and di the drinks. Hence, it must hold that ni 6= nj , where i 6= j , and similarly for ci , pi , ai and di . Furthermore, the following must be satis ed:
C.6. FIVE HOUSES
75
n1 n2 n3 n4 n5 c1
c5 + 1 p5 p2 d5 (n5 + 1 = c4 p3 (a3 + 1 = p4 (a5 + 1 = p2
= = = = = = = = = =
_
=
_ _
c2 ; a1 ; p1 ; d3 ; 1; d4 ; c1 ; a4 ; c3 ; 3; c4 + 1 = n 5 ) d1 ; p4 + 1 = a3); p2 + 1 = a 5 )
The above constraints have only one solution, thus determining the Japanese as the owner of the zebra, and the Norwegian as the one who drinks water. five() -> five({fd_lib,naive}). five(Lab) -> fd:init(), L = fd:range_list(25,1,5), [N1,N2,N3,N4,N5, C1,C2,C3,C4,C5, P1,P2,P3,P4,P5, A1,A2,A3,A4,A5, D1,D2,D3,D4,D5] = L, fd:tell(N5=1), fd:tell(D5=3), fd_lib:all_different([C1,C2,C3,C4,C5]), fd_lib:all_different([P1,P2,P3,P4,P5]), fd_lib:all_different([N1,N2,N3,N4,N5]), fd_lib:all_different([A1,A2,A3,A4,A5]), fd_lib:all_different([D1,D2,D3,D4,D5]), fd:tell( N1 = C2, N2 = A1, N3 = P1,
76
APPENDIX C. EXAMPLE PROGRAMS N4 = D3, P3 = D1, C1 = D4, P5 = A4, P2 = C3, C1 = C5+1 ), p_or_m(A3,P4,1), p_or_m(A5,P2,1), p_or_m(N5,C4,1), Sol = fd:solve([C1,C2,C3,C4,C5, P1,P2,P3,P4,P5, N1,N2,N3,N4,N5, A1,A2,A3,A4,A5, D1,D2,D3,D4,D5], Lab), fd:close(), Sol.
The following function expresses \plus or minus" on X , Y and Z , i.e. either X = Y +Z or X = Y ? Z is true. p_or_m(X,Y,Z) -> fd:tell( X in (min(Y)+min(Z)..max(Y)+max(Z)) \/ (min(Y)-max(Z)..max(Y)-min(Z)), Y in (min(X)+min(Z)..max(X)+max(Z)) \/ (min(X)-max(Z)..max(X)-min(Z)), Z in (min(Y)-max(X)..max(Y)-min(X)) \/ (min(X)-max(Y)..max(X)-min(Y))).
C.7. MAGIC SERIES
77
C.7 Magic Series A magic serie is a sequence x0 ; x1 ; : P : : ; xn?1 such that each xi is the number of occurrences of i in the serie; i.e. xi = bji where bji is 1 if xj = i and 0 if xj 6= i. Two redundant constraints are used: nX ?1 i=0
and
nX ?1 i=0
i=n
i xi = n
Solutions: N
Solution 1; 2; 3 and 6 none 4 [1,2,1,0] and [2,0,2,0] 5 [2,1,2,0,0] 7 [3,2,1,1,0,0,0] 7 [N ? 4,2,1,N ? 7 0's,1,0,0,0]
magic(N) -> magic(N,{fd_lib,naive}). magic(N,F) -> fd:init(), L = fd:range_list(N,0,N-1), case catch constraints(L,L,0,N,N) of fail -> fd:close(), fail; _ -> Sol = fd:solve(L,F), fd:close(), Sol end. constraints([],_,_,S0,S1) -> fd_lib:'x=y'(S0,0), fd_lib:'x=y'(S1,0); constraints([X|Xs],L,I,S,S2) -> Occ = sum(L,I), fd_lib:'x=y'(X,Occ), S1 = fd:range(), S3 = fd:range(), fd_lib:'x+y=t'(S1,X,S), c_0(I, X, S2, S3),
%% redundant constraint 1
78
APPENDIX C. EXAMPLE PROGRAMS constraints(Xs,L,I+1,S1,S3).
c_0(0, _, S0, S1) -> fd_lib:'x=y'(S0,S1); c_0(I, X, S0, S1) -> fd_lib:'ax+y=t'(I,X,S1,S0). sum([],_) -> 0; sum([X|Xs],I) -> S1 = sum(Xs,I), B = fd_lib:eq_iff(X,I), S = fd:range(), fd_lib:'x+y=t'(B,S1,S), S.
%% redundant constraint 2
C.8. SUUDOKU
79
C.8 Suudoku The problem is to ll partially lled 9x9 squares of 81 squares such that each row and column are permutations of [1,...,9], and each 3x3 square, where the leftmost column modulo 3 is 0, is a permutation of [1,...,9]. suudoku(P) -> fd:init(), InitialProblem = problem(P), io:format("\nInitial:\n~s\n\n", [p(InitialProblem)]), Problem = domain_problem(InitialProblem), row_constraint(Problem), column_constraint(Problem), block_constraint(Problem), Vars = lists:append(Problem), Sol = fd:solve(Vars), case Sol of fail -> io:format("fail\n", []); _ -> io:format("Solution:\n~s\n\n", [p(make_suu(Sol))]) end, done. p([]) -> ""; p([Row|Rows]) -> lists:append([io_lib:format(" ", []),p_row(Row), p(Rows)]). p_row([]) -> "\n"; p_row([V|R]) when integer(V) -> lists:append(io_lib:format("~w ",[V]), p_row(R)); p_row([V|R]) when V==v -> lists:append("_ ", p_row(R)). make_suu([]) -> []; make_suu([C1,C2,C3,C4,C5,C6,C7,C8,C9|Cs]) -> [[C1,C2,C3,C4,C5,C6,C7,C8,C9] | make_suu(Cs)]. domain_problem([]) -> []; domain_problem([Row|Rows]) -> [domain_problem_row(Row) | domain_problem(Rows)]. domain_problem_row([]) -> []; domain_problem_row([V|R]) when integer(V) -> [V | domain_problem_row(R)]; domain_problem_row([V|R]) when V==v -> [fd:range(1, 9) | domain_problem_row(R)].
80
APPENDIX C. EXAMPLE PROGRAMS
row_constraint([]) -> true; row_constraint([R|Rt]) -> fd_lib:all_different(R), row_constraint(Rt). column_constraint([C1,C2,C3,C4,C5,C6,C7,C8,C9]) -> column_constraint(C1,C2,C3,C4,C5,C6,C7,C8,C9). column_constraint([],[],[],[],[],[],[],[],[]) -> true; column_constraint([C1|C1t],[C2|C2t],[C3|C3t],[C4|C4t], [C5|C5t],[C6|C6t],[C7|C7t],[C8|C8t],[C9|C9t]) -> fd_lib:all_different([C1,C2,C3,C4,C5,C6,C7,C8,C9]), column_constraint(C1t,C2t,C3t,C4t,C5t,C6t,C7t,C8t,C9t). block_constraint([C1,C2,C3,C4,C5,C6,C7,C8,C9]) -> block_constraint(C1,C2,C3), block_constraint(C4,C5,C6), block_constraint(C7,C8,C9). block_constraint([],[],[]) -> true; block_constraint([C1,C2,C3|C1t],[C4,C5,C6|C2t],[C7,C8,C9|C3t]) -> fd_lib:all_different([C1,C2,C3,C4,C5,C6,C7,C8,C9]), block_constraint(C1t,C2t,C3t). problem(1) -> % shokyuu [[1,v,v,8,v,4,v,v,v], [v,2,v,v,v,v,4,5,6], [v,v,3,2,v,5,v,v,v], [v,v,v,4,v,v,8,v,5], [7,8,9,v,5,v,v,v,v], [v,v,v,v,v,6,2,v,3], [8,v,1,v,v,v,7,v,v], [v,v,v,1,2,3,v,8,v], [2,v,5,v,v,v,v,v,9]]; problem(2) -> % shokyuu [[v,v,2,v,3,v,1,v,v], [v,4,v,v,v,v,v,3,v], [1,v,5,v,v,v,v,8,2], [v,v,v,2,v,v,6,5,v], [9,v,v,v,8,7,v,v,3], [v,v,v,v,4,v,v,v,v], [8,v,v,v,7,v,v,v,4], [v,9,3,1,v,v,v,6,v],
C.8. SUUDOKU [v,v,7,v,6,v,5,v,v]]; problem(3) -> % chuukyuu [[v,v,v,v,v,v,3,v,v], [v,v,v,8,5,v,v,1,v], [v,v,2,v,v,4,v,v,9], [v,3,v,v,v,2,v,v,4], [8,v,v,v,6,v,v,v,1], [7,v,v,9,v,v,v,5,v], [1,v,v,6,v,v,7,v,v], [v,9,v,v,2,3,v,v,v], [v,v,4,v,v,v,v,v,v]]; problem(4) -> % joukyuu [[v,7,9,v,v,v,v,v,1], [6,v,v,v,v,v,3,8,v], [v,v,v,v,4,2,v,v,v], [v,v,3,9,v,v,v,v,v], [7,8,v,v,v,v,v,2,5], [v,v,v,v,v,4,8,v,v], [v,v,v,3,1,v,v,v,v], [v,5,6,v,v,v,v,v,7], [2,v,v,v,v,v,4,3,v]]; problem(5) -> % shokyuu; from Mr. Horai [[v,5,v,7,v,1,v,4,v], [7,v,3,v,v,v,1,v,2], [v,8,v,4,v,6,v,9,v], [9,v,4,v,6,v,8,v,3], [v,v,v,8,v,7,v,v,v], [1,v,8,v,5,v,6,v,9], [v,1,v,6,v,3,v,8,v], [5,v,6,v,v,v,7,v,1], [v,3,v,5,v,9,v,2,v]]; problem(6) -> % Hard: suudoku2 99 (1989) P=[[8,v,v,v,v,5,v,v,v], [v,1,2,3,v,v,6,v,v], [v,4,5,6,v,v,v,2,v], [v,7,8,v,v,v,v,v,1], [v,v,v,v,9,v,v,v,v], [9,v,v,v,v,v,8,7,v], [v,2,v,v,v,6,5,4,v], [v,v,4,v,v,3,2,1,v],
81
82
APPENDIX C. EXAMPLE PROGRAMS [v,v,v,1,v,v,v,v,9]].
Appendix D
Libraries D.1 User Library all_different([]) -> []; all_different([E|L]) -> different(E,L), all_different(L). different(X,[]) -> X; different(X,[E|L]) -> fd:tell(X in diffdom(X) \ dom(E), E in diffdom(E) \ dom(X)), different(X,L).
D.2 Variable Ordering naive(L) -> {hd(L), tl(L)}. ff([E|L]) -> ff1(L, {E,fd_size(E)}, [E|L]). ff1([], {SmallestE,SmallestSize}, Orig) -> {SmallestE, delete(SmallestE,Orig)}; ff1([E|L], {SmallestE,SmallestSize}, Orig) -> NewSize = fd_size(E), if NewSize < SmallestSize -> ff1(L, {E, NewSize}, Orig); true ->
83
84
APPENDIX D. LIBRARIES ff1(L, {SmallestE, SmallestSize}, Orig) end.
delete(Item, [Item|Rest]) -> Rest; delete(Item, [H|Rest]) -> [H|delete(Item, Rest)]; delete(Item, []) -> [].
D.3 Library Constraints Library constraints on form x t: 'x=y'(X,Y) -> fd:tell( X in min(Y)..max(Y), Y in min(X)..max(X) ). 'x/=y'(X,Y) -> fd:tell( X in diffdom(X) \ (min(Y)..max(Y)), Y in diffdom(Y) \ (min(X)..max(X)) ). 'x>=y'(X,Y) -> fd:tell( X in min(Y)..inf, Y in 0..max(X) ). 'x==c'(T,C) 't==u+c'(X,Y,C) -> fd:tell( X in min(Y) + C..inf, Y in 0..max(X) - C ). 't==c'(X,Y,C) -> fd:tell( X in C - max(Y)..inf, Y in C - max(X)..inf ). 't+u=