An Introductory Course on Constraint Logic Programming Coordination: With input from: Manuel Carro Manuel Hermenegildo Francisco Bueno Daniel Cabeza Ma Jose Garca Pedro Lopez German Puebla Computer Science School Technical University of Madrid, UPM VOCAL ESPRIT Project P23182 Contents 1 What is Constraint (Logic) Programming? 1.1 1.2 1.3 1.4 1.5 1.6 1.7 Introduction . . . . . . . . . . . . . . . . . . . . Typical Applications and Approaches . . . . . Constraints: Representation and Solving . . . . Constraints as (Extended) Equations . . . . . . Why Constraints and Programming? . . . . . . Constraint{Programming Language Interfaces . An Example: SEND + MORE = MONEY . . 1.7.1 Prolog: Generate and Test . . . . . . . 1.7.2 ILOG Solver (C++ Version) . . . . . . 1.7.3 ILOG Solver (Le Lisp Version) . . . . . 1.7.4 Eclipse Version . . . . . . . . . . . . . . 1.8 Use Prolog as Host Language? . . . . . . . . . 1.9 How Does a CLP System Work? . . . . . . . . 1.9.1 Modeling the Problem . . . . . . . . . . 1.9.2 Be a Solver . . . . . . . . . . . . . . . . 1.9.3 Don't Be a Solver! . . . . . . . . . . . . 2 A Basic Language 2.1 A Basic Constraint Language . . . . . . . . 2.1.1 Clauses . . . . . . . . . . . . . . . . 2.1.2 Implicit Equality . . . . . . . . . . . 2.1.3 Facts . . . . . . . . . . . . . . . . . . 2.1.4 Predicates . . . . . . . . . . . . . . . 2.1.5 Programs and Queries . . . . . . . . 2.2 Searching . . . . . . . . . . . . . . . . . . . 2.3 Logical Variables . . . . . . . . . . . . . . . 2.4 The Execution Mechanism . . . . . . . . . . 2.5 Database Programming . . . . . . . . . . . 2.6 Datalog and the Relational Database Model 3 Adding Computation Domains 3.1 3.2 3.3 3.4 Domains . . . . . . . . . . . . . Linear (Dis)Equations . . . . . Linear Problems with Prolog IV Fibonacci Numbers . . . . . . . . . . . . . . . . . . . . . . . i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 4 6 7 7 8 9 10 10 11 12 13 14 14 15 17 19 19 20 21 21 22 22 24 25 27 28 29 33 33 34 36 39 3.5 Non-Linear Solver: Intervals . . . . . . . 3.6 Some Useful Primitives . . . . . . . . . . 3.6.1 The Bounds of a Variable . . . . 3.6.2 Enumerating Variables . . . . . . 3.7 A Project Management Problem . . . . 3.8 Other Constraints and Operations . . . 3.9 Herbrand Terms . . . . . . . . . . . . . 3.10 Herbrand Terms: Syntactic Equality . . 3.11 Structured Data and Data Abstraction . 3.12 Structuring Old Problems . . . . . . . . 3.13 Constructing Recursive Data Structures 3.14 Recursive Programming: Lists . . . . . . 3.15 Trees . . . . . . . . . . . . . . . . . . . . 3.16 Data Structures in General . . . . . . . 3.17 Putting Everything Together . . . . . . 3.17.1 Systems of Linear Equations . . 3.17.2 Analog RLC circuits . . . . . . . 3.18 Summarizing . . . . . . . . . . . . . . . 4 The Prolog Language 4.1 Prolog . . . . . . . . . . . . . . 4.2 Control Annotation . . . . . . . 4.2.1 Goal Ordering . . . . . 4.2.2 Clause Ordering . . . . 4.3 Arithmetic . . . . . . . . . . . . 4.4 Type Predicates . . . . . . . . 4.5 Structure Inspection . . . . . . 4.6 Input/Output . . . . . . . . . . 4.7 Pruning Operators: Cut . . . . 4.8 Meta-Logical Predicates . . . . 4.9 Meta-calls (Higher Order) . . . 4.10 Negation as Failure . . . . . . . 4.11 Dynamic Program Modication 4.12 Foreign Language Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 42 42 43 43 47 47 48 49 52 52 55 60 62 64 64 65 67 69 69 69 70 70 71 73 74 77 78 81 83 85 86 87 5 Pragmatics 89 6 Conclusions and Further Reading 7 Small Projects 93 95 5.1 Programming Tips . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Controlling the Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Complex Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1 7.2 7.3 7.4 The Blocks World . . . . . . . . . . . . . . . . . . . A Discussion on DONALD + GERALD = ROBERT Ordinary Dierential Equations . . . . . . . . . . . . A Scheduling Program . . . . . . . . . . . . . . . . . ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 90 91 . 96 . 100 . 103 . 105 A Solutions to Proposed Problems 111 iii iv List of Figures 1.1 External programming library . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Language with extended semantics . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Precendence net . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 9 15 2.1 2.2 2.3 2.4 A tree . . . . . . . . . . . . . . . . . . . . . Traversing an execution tree . . . . . . . . . An electronic circuit . . . . . . . . . . . . . Two tables in the relational database model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 28 30 30 3.1 3.2 3.3 3.4 3.5 Project 2: F can be speeded up! Two tasks with length not xed . A tree corresponding to a term . A tree . . . . . . . . . . . . . . . Modeling a circuit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 45 48 60 67 4.1 Eects of cut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 7.1 A scenario in the blocks world . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 . . . . . . . . . . . . . . . v . . . . . . . . . . . . . . . vi List of Tables 1.1 Being a solver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.1 Intervals and their representation in Prolog IV . . . . . . . . . . . . . . . . . . 3.2 Correspondence between keywords for the linear and non-linear solvers . . . . 3.3 Syntaxes for lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 41 56 4.1 4.2 4.3 4.4 4.5 4.6 72 72 73 78 81 82 Some arithmetic-related terms . . . . . . . . Some arithmetic-related builtins . . . . . . Predicates checking types of terms . . . . . DEC-10 I/O predicates . . . . . . . . . . . Some meta-logical Prolog predicates . . . . Predicates which implement standard order vii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viii Acknowledgments Many persons have contributed to the contents of this course. Special thanks are due to the members of the CLIP laboratory (http://www.clip.dia.fi.upm.es) at the Technical University of Madrid. They not only provide a delightful environment to work in, but their always insightful remarks and comments foster a continuous desire to do a better job. Many thanks are also due to people who have produced invaluable seminal work in the topics of logic and constraint logic programming, and that we have worked with and learnt from over the years. Listing all the names is an impossible job, but those of D.H.D. Warren, Joxan Jaar, A. Colmerauer (and all his colleagues), Michael Maher, Peter Stuckey, and Kim Marriot cannot be left out of that list. The preparation of this course was supported in part by Esprit project P23182, \VOCAL". We wish to thank also all the partners in the project for their feedback on earlier versions of the course. ) ix x Introduction The purpose of this document is to serve as the printed material for the seminar \An Introductory Course on Constraint Logic Programming". The intended audience of this seminar are industrial programmers with a degree in Computer Science but little previous experience with constraint programming. The seminar itself has been eld tested, prior to the writing of this document, with a group of the application programmers of Esprit project P23182, \VOCAL", aimed at developing an application in scheduling of eld maintenance tasks in the context of an electric utility company. The contents of this paper follow essentially the ow of the seminar slides. However, there are some dierences. These dierences stem from our perception from the experience of teaching the seminar, that the technical aspects are the ones which need more attention and clearer explanations in the written version. Thus, this document includes more examples than those in the slides, more exercises (and the solutions to them), as well as four additional programming projects, with which we hope the reader will obtain a clearer view of the process of development and tuning of programs using CLP. On the other hand, several parts of the seminar have been taken out: those related with the account of elds and applications in which C(L)P is useful, and the enumerations of C(L)P tools available. We feel that the slides are clear enough, and that for more information on available tools, the interested reader will nd more up-to-date information by browsing the Web or asking the vendors directly. More details in this direction will actually boil down to summarizing a user manual, which is not the aim of this document. ) 1 2 Chapter 1 What is Constraint (Logic) Programming? In this chapter we will give an introduction to Constraint (Logic) Programming. We will briey review the types of applications for which C(L)P is well suited, and we will give examples of the solution for a problem using dierent C(L)P languages. We will also compare the C(L)P programming paradigm approach to other related approaches. 1.1 Introduction The C(L)P programming paradigm has some resemblance to traditional Operations Research (OR) approach, in that the general path to a solution is: 1. Analyzing the problem to solve, in order to understand clearly which are its parts 2. determining which conditions/relationships hold among those parts: these relationships and conditions are key to the solving, for they will be used to model the problem 3. stating such conditions/relationships as equations to achieve this step not only the right variables and relationships must be chosen: as we will see, C(L)P usually oers a series of dierent constraint systems, some of which are better suited than others for a given task 4. setting up these equations and solving them to produce a solution this is usually transparent to the user, because the language itself has built-in solvers. There are, however, notable dierences with OR, mainly in the possibility of selecting dierent domains of constraints, and in the dynamic, generation of those constraints. This seamless combination of programming and equation solving accounts for some of the unique components of Constraint Programming : the use of sound mathematical methods: well-known and proved algorithms are provided as intrinsic, builtin components of C(L)P languages and tools the provision of means to perform programmed search, especially in CLP (were search is implicit in language itself) 3 4 CHAPTER 1. WHAT IS CONSTRAINT (LOGIC) PROGRAMMING? the possibility of developing modular, hybrid models, when necessary: many C(L)P systems oer dierent constraint systems, which can be combined to model the various parts of the problem using the tool more adequate for them the exibility provided by the programming language used, which allows the programmer to create the equations to be solved dynamically, possibly depending on the input data. 1.2 Typical Applications and Approaches As with any other computational approach, all problems are amenable to be tackled with C(L)P notwithstanding, there are some types of problems which can be solved with comparatively little eort using C(L)P based tools. Those applications share some general characteristics: No general, ecient algorithms exist (NP-completeness): specic techniques / heuristics must be used. These are usually problems with a heavy combinatorial part, and enumerating solutions is often impractical altogether. A fast program using usual programming paradigms is often too hard and complicated to produce, and normally it is so tied to the particular problem that adapting it to a related problem is not easy. The problem specication has a dynamic component: it should be easy to change programs rapidly to adapt. This has points in common with the previous item: C(L)P tools have builtin algorithms which have been tuned to show good behavior in a variety of scenarios, so updating the program to new conditions amounts to changing the setting up of the equations. Decision support required: either automatically in the program or in cooperation with the user. Many decisions can be encoded in mathematical formulae, which appear as rules and which are handled by the internal solvers, so (although, of course, not always) there is no need to program explicit decision trees. Among the applications with these characteristics, the following may be cited: planning, scheduling, resource allocation, logistics, circuit design and verication, nite state machines, nancial decision making, transportation, spatial databases, etc. ) Let us review some approaches to solving problems with the aforementioned characteristics: Operations Research systems, and also genetic algorithms, simulated annealing, etc., have a medium development eort, since most of the core technique (e.g., the solving algorithms themselves) are already coded an optimized, so the problem has only to be modeled and fed into the system. They have the drawback of being not exible (equations cannot be updated dynamically), and heuristic search of solutions is not always easy to include in the problem, or the modication according to the desires of the user. 1.2. TYPICAL APPLICATIONS AND APPROACHES 5 Conventional programs can potentially give the most ecient solution, but this eciency comes at a high cost: reaching a solution needs a uphill development phase, in which all solving|not only the particular problem conditions|has to be explicitly described usually the solving/search part of the problem is tailored for the particular application (which accounts for the high performance of the program), which in turn makes the program not amenable to be adapted to other scenarios, even related ones. Success in this approach also requires a deep knowledge of constraint solving algorithms, which in CLP systems is built in. Rule-based systems receive a good rate in heuristic possibilities, but on the other hand they lack constraint solving capabilities, and an algorithmic style is dicult to embed. Constraint-based approaches especially when combined with Logic Programming, try to combine the best of all the previous points. Not only constraint solving is included as a part of the systems, but algorithmic components are provided for being used when needed (e.g., in the cases in which parts of a problem can be worked out more advantageously using an explicit algorithm). Also, this algorithmic part interacts with the constraint solving part by creating dynamically the equations to be solved, and communicating the solutions by means of the variables of the language. Also, rules as means of expressing heuristics are available when using logic programming-based constraint tools. ) Since usual programming techniques are commonly well understood, we will review the tradeos between using Operation Research and Constraint Programming approaches: The OR Edge OR is a good approach when the problems to be solved have some specic characteristics: A good degree of staticity in the problem to be solved: the only dierences among runs of the program are some coecients which can be easily changed or tuned, and that in no way aect the modeling of the problem (which is the most dicult part to change). Can be expressed using classical, well-known OR models. This makes good, ecient algorithms available, and guidelines and examples for modeling the problem clear and well understood. The size of the problem (usually measured in the number of variables needed) is very large. If well suited OR methods are available, then probably they will be highly optimized, and then large problems could be solved within a reasonable amount of time. The CP Edge CP has short development time, exibility, and good eciency as main advantages: Fast prototyping is easy with CP preliminary models of the problem, often working correctly as reduced versions of the nal program are fast to build. The program evolves through successive renements, in which experiments to nd the best approach can be performed. CHAPTER 1. WHAT IS CONSTRAINT (LOGIC) PROGRAMMING? 6 Flexibility, adaptability, and maintainability are also strong points in favor of constraint programming. Due to the dynamic equation set up, the code tends to be adaptable, easy to change, and to maintain: conditions are not encoded directly in the equations, but rather in the way they are created. The performance of CP systems is good: in fact, internal solving algorithms are usually very optimized (in most cases they are inherited from O.R.), and can deal with sizeable problems exhibiting a reasonably good performance. The fact that prototyping is fast also adds to the global performance of the approach, and since successive renements are used to reach to a solution, there is no need to perform a complete rewriting of the code to obtain a \robust" production program. 1.3 Constraints: Representation and Solving The idea underlying in Constraint Programming is that constraints can be used to represent a problem, to solve it, and to represent a solution|which, in fact, is no more than a simplication of the initial constraints, arrived at by following deduction rules hardwired in the solver.1 We will give an example of how a problem can be represented by using constraints: let us think of a puzzle such as those commonly found in magazines: The murderer is older than Joe The man in yellow does not have green eyes .. . This puzzle can be viewed as constraints expressed in a language which has some primitive constraints (such as \is older than"), which relate elements pertaining to the domain of the constraint system (such as the actors and their characteristics: \the man in yellow", \Joe", \green eyes"). Some of the actors are denitely identied (\Joe"), and some others are represented by an identier, or a characteristic which does not allow its identication them (yet): \the murderer". A solution is an assignment of domain values to those actors not completely identied which agrees with all the initial constraints: Murderer: Lopez, green eyes, Magnum gun Sometimes a single solution cannot be reached. This can be due to the way in which the solver works (incomplete solver), or due to a lack of initial constraints which dene completely the problem (underconstrained problem|probably not correctly modeled) or just because there are many dierent solutions for that particular problem. In that case the initial constraint system cannot be completely reduced, and the nal answer is a constraint itself, such as: The murderer is older than the man in yellow 1 Although some CLP systems allow the user to de ne their own constraint domains and solvers. 1.4. CONSTRAINTS AS (EXTENDED) EQUATIONS 7 Note that it is often possible to perform an enumeration (search) through all the individuals in our initial problem to check which ones meet this nal constraint. This path could have been followed right from the beginning (try all the combinations of possible actors and domain values, and check which ones meet all the constraints), but a (partial) solving of the constraints can sometimes solve the problem, and, in any case, the number of equations and domain values to try is greatly reduced. 1.4 Constraints as (Extended) Equations Constraints can be actually viewed as equations: in both cases, variables are related by properties, and solving a set of equations amounts to nding which assignment of values to variables meets all the equations. Mathematical equations can be solved if appropriate methods are known, and the same happens with constraints. But constraint tools usually provide domains which are not commonly treated by classical mathematics or, at least, constraint systems for which solving methods are not a central point of the usual mathematical background. Using the appropriate domain for each problem is essential: constraint domains have specic characteristics and solving methods which make them more appropriate than others for some problems. Fortunately, deciding which constraint system has to be used is often not dicult: in most cases the problem itself strongly suggests which constraint system to use. In general, the process of solving a problem is a combination of propagation (a general term to refer to equation solving) and search, when an incomplete solution is found. But looking at constraints as a kind of extended equations does not allow the perception of the whole scenario: equations (even in their extended constraint-like version) suer from the same drawbacks as OR: lack of modularity (the whole problem is a big set of interrelated equations), lack of dynamic creation of equations, sometimes lack of power to solve completely the equation system proposed, or the solution, as returned by the solver (assignments of values to variables) not coming out in the appropriate format (which, for example, might have to be shared with other tools). Solutions to these problems can be worked out by coupling constraints and programming. 1.5 Why Constraints and Programming? There are some practical problems when using constraints (viewed as extended equations) alone to solve some real-life problems. As the set of equations is commonly static, it must be dened once for every problem. Usually there are decisions to be made while solving the problem, and those decisions can be dynamic in that they are not known beforehand they have to be somehow anticipated for every set of initial data. Even if those decisions can be encoded as formulae (using special variables) the resulting mathematical model is often unnatural and dicult to solve. C(L)P addresses this problem with a series of programming facilities as, for example, search. Sometimes there is a hierarchy of preferences which denes mandatory constraints, or imposes a penalty for constraint violation. Sometimes these penalties are not easy to determine (because, for example, the user has only some limited knowledge about the relative importance). Sometimes the penalties might change dynamically and be dierent for every problem instance. A programming-based approach tackles this by, for example, placing 8 CHAPTER 1. WHAT IS CONSTRAINT (LOGIC) PROGRAMMING? some rules before others, or incorporating some heuristics to the program which sets up the constraints. It is not uncommon that large problems can be split into smaller, easier to work out tasks, such that solving and combining their results is cheaper than solving the whole problem at once. While solving equations is normally a process which takes into account all the available data at the same time, divide-and-conquer is a widely used programming technique which can as well be used to set up constraints | and to help solve them faster. Constraint-enriched languages inherit very interesting capabilities: they oer for free data abstraction, with which modules aimed at solving well-dened problems (which, in this scenario, involves setting up constraints among variables) can be written. Also, dedicated algorithms can be coded when an ecient way to solving the task at hand is known. Dynamic setting up of constraints has already been mentioned: what a C(L)P program does can be viewed as dening a skeleton of the equations needed to solve a class of problems, the particular instance being generated from the input data. And, last, a program-based approach allows runtime external communication (with the user, with other programs, with databases), and reacting adequately to the conditions of the environment. The actual constraint solver in the program is a black box (with, possibly, some switches which can be adjusted by the user) as in a OR tool. 1.6 Constraint{Programming Language Interfaces There are two basic ways of using constraints from inside a programming language. One is providing a library with data structures and classes which implements objects such as variables, equations, etc., and methods to combine formulae using mathematical (or other) operations to give more formulae, combining formulae using mathematical relations to give equations, putting together equations in sets, testing their solvability (and trying to solve them), etc. This is exemplied in Figure 1.1. Constraints Constraints Library Host Language Answers (values/constraints) Figure 1.1: External programming library As good as it can be, it will not integrate seamlessly with the semantics of the host language, for the constrained variables are not language variables, and the same happens with the equations, relationships: the do not belong to the language. For that, an alternative path to coupling constraints and programming is making the language semantics richer by adding high-level mathematical properties to the basic building blocks of the language: variables can now be related to other variables, and can hold non-denite values. Constraint solving is performed automatically as program execution progresses, since the constraint solver is part of the runtime system. This is depicted in Figure 1.2. It is not surprising that functional and logic languages (specially the latter ones, because 1.7. AN EXAMPLE: SEND + MORE = MONEY 9 Constraint Programming Language Programming Language Constraint Solver Figure 1.2: Language with extended semantics they already provide logical variables and implicit search) are the ones more amenable to this approach: their mathematical foundation and independence from the machine oer leeway to for adapting their semantics self-congruently. Regardless of the approach taken towards the construction of a constraint language, there are some essential services that such a language must provide: A solver, which solves equations or communicates their non-solvability (the way this is done depends on the actual interface with the host language). Means to express constraints, formulas, etc. from the language. An interface to the solver, which allows constraints to be passed to it, and, upon successful constraint solving, asking for the values assigned to the constraint variables. 1.7 An Example: SEND + MORE = MONEY the variables S E N D M O R Y represent digits between 0 and 9, and the task is nding values for then such that the following arithmetic operation is correct: SEND + MORE = MONEY is a classical \crypto-arithmetic" puzzle: + M S M O E O N N R E D E Y Moreover, all variables must take unique values, and all the numbers must be well-formed (which implies that M > 0 and S > 0. Conventional programming needs to express an explicit search in general (though in this particular case nested loops can be used). Logic languages, such as Prolog, will use directly a built-in search: the programming is easy, but it might not be highly ecient (of course, rened programs can achieve good performance, but advanced skills and an eort in time is needed to write them). This is, in fact, a typical problem for nite domains: all variables take values from a nite set of numbers, the constraints to satisfy can be easily expressed, and there is some amount of search to perform. Finite domain variables always have as values a set of integers, taken CHAPTER 1. WHAT IS CONSTRAINT (LOGIC) PROGRAMMING? 10 from a nite number of possible initial values. For example, it is natural to use program variables in our problem to represent the dierent digits. In that case, every variable (say, the one corresponding to the digit D) can take values in the set f1 2 3 4 5 6 7 8 9g. The nal solution must be an assignment of singleton sets to every variable in the problem, and we take the unique value in this set as the denite value for that variable. If, at any point in the execution of the program, the domain of a variable happens to become the empty set, a failure is caused, and the program backtracks to the nearest (in time) choice point created2 . 1.7.1 Prolog: Generate and Test The Prolog solution below (one among several possibilities) is typical of the Generate and Test paradigm: variables from a list are assigned values from another list after this assignment is done, the list of variables is checked for compliance with the constraints of the problem. If any of the constraints fail, the system backtracks to nd another assignment for the variables. smm :X = S,E,N,D,M,O,R,Y], Digits = 0,1,2,3,4,5,6,7,8,9], assign_digits(X, Digits), M > 0, S > 0, 1000*S + 100*E + 10*N + D + 1000*M + 100*O + 10*R + E =:= 10000*M + 1000*O + 100*N + 10*E + Y, write(X). select(X, X|R], R). select(X, Y|Xs], Y|Ys]):- select(X, Xs, Ys). assign_digits(], _List). assign_digits(D|Ds], List):select(D, List, NewList), assign_digits(Ds, NewList). Unsurprisingly, the program is not very ecient: there are 10!2 possibilities for the assignment of values to digits, Better programs are not dicult to write, but the one above is possibly the non totally nave one which most directly expresses the problem, and whose algorithm is more natural to write and understand by the average programmer. Improvements include not taking into account the value 0 for M and S explicitly (which can arguably be viewed as a divide-and-conquer approach), or other techniques which may include using explicitly an internal carry (see Section 7.2) or automatic delays (Section 5.2). 1.7.2 ILOG Solver (C++ Version) The ILOG () Solver version is a proper constraint program, based on the Finite Domains paradigm. The program has to be linked against the appropriate ILOG libraries, in order As we will see later, these choice points are created every time there is an alternative in the program, and these alternatives appear almost inevitably even if the program do not explicitly create them. 2 1.7. AN EXAMPLE: SEND + MORE = MONEY 11 for the FD routines to be available. The basic structure of the program (which is actually shared, with minor changes, by the rest of the implementations of this example) is as follows: 1. The library is initialized, 2. The FD variables are declared, and initial bounds to them assigned (note the special bounds for the variables M and S), 3. An array packing all FD variables is created, 4. The rest of the constraints are generated (all variables must be dierent, and the equality dening the arithmetic operation must hold), 5. A call to the solver is made, to search for values and assign them to the variables, and 6. The nal solution is printed #include <ilsolver/ctint.h> CtInt dummy = CtInit() CtIntVar S(1, 9), E(0, 9), N(0, 9), D(0, 9), M(1, 9), O(0, 9), R(0, 9), Y(0, 9) CtIntVar* AllVars]= {&S, &E, &N, &D, &M, &O, &R, &Y} int main(int, char**) { CtAllNeq(8, AllVars) CtEq( 1000*S + 1000*M 10000*M + 1000*O CtSolve(CtGenerate(8, + 100*E + 10*N + D + 100*O + 10*R + E, + 100*N + 10*E + Y) AllVars)) PrintSol(CtInt, AllVars) CtEnd() return 0 } Since the FD variables are special objects not belonging to the C++ language itself, but dened as part of a class, they cannot be treated in the program in the same way as primary C++ objects: for example, printing them or accessing their values has to be done with special methods provided by the class. 1.7.3 ILOG Solver (Le Lisp Version) The Lisp version is actually very similar to the C++ one: this is not surprising, since the underlying engine is basically the same. The same comments as for the C++ version apply here. Only some additional remarks are needed: 12 CHAPTER 1. WHAT IS CONSTRAINT (LOGIC) PROGRAMMING? There is no need to initialize the library. Since the constraints library is part of the Lisp runtime system, the initialization takes place automatically. The constraints for M and S appear explicitly, instead of being given when the variables are declared. Since the Lisp variables have in fact a complex internal structures (tags, pointers etc.), they can be hooked by the implementation so that a more direct access from the language is possible. For example, printing and accessing their values can be made using standard Lisp functions. (defun smm () (ct-let-vars ((S E N D M O R Y) (ct-fix-range-var 0 9) l-var) (ct-neq M 0) (ct-neq S 0) (ct-all-neq S E N D M O R Y) (ct-eq (ct-add (ct-add (ct-add (ct-add (ct-mul 1000 S) (ct-mul 100 E)) (ct-mul 10 N)) D) (ct-add (ct-add (ct-add (ct-mul 1000 M) (ct-mul 100 O)) (ct-mul 10 R)) E)) (ct-add (ct-add (ct-add (ct-add (ct-mul 10000 M) (ct-mul 1000 O)) (ct-mul 100 N)) (ct-mul 10 E)) Y)) (ct-solve (ct-generate l-var () ())) (print S E N D M O R Y))) Unfortunately, the Lisp syntax is arguably not the best to write equations clearly. 1.7.4 Eclipse Version ECLi PSe is a programming system initially developed at ECRC, and now maintained at IC-Park, which combines Logic Programming with constraint solving capabilities. Having explained the previous examples, the program should be pretty obvious. Only some remarks concerning the program below: All variables are rst objects of the language: they can be manipulated and accessed using the same primitives as for non-FD variables. The results of this manipulation, of course might not be the same, since we are treating objects with dierent semantics, but the program syntax is homogeneous. Declaring the list of variables X is actually not needed, but it is convenient since it is used elsewhere in the program. 1.8. USE PROLOG AS HOST LANGUAGE? 13 The versions for other CLP languages (for example, Prolog IV and CHIP) may dier in the syntax, but the structure and programming is basically the same, and even the syntax changes are recognizable without any eort. smm :X X M S = S,E,N,D,M,O,R,Y], :: 0 .. 9, #> 0, #> 0, 1000*S + 100*E + 10*N + D + 1000*M + 100*O + 10*R + E #= 10000*M + 1000*O + 100*N + 10*E + Y, alldistinct(X), labeling(X), write(X). This program has the combined advantage of being at the same time a direct encoding of the problem and a highly ecient solution. 1.8 Use Prolog as Host Language? The last example shows that Prolog syntax (and semantics) and nite domains go quite well together. Actually, it is more than that: due to the incremental nature of constraint programming (prototyping and building an application incrementally is easy and natural), the availability of interactive interpreters for CLP languages (inherited from Prolog) is a plus, as experimentation and debugging are parts inherent to the development of a program. Also, the built-in backtracking of logic programming allows the easy customization of search procedures for the cases in which standard CLP procedures are not good enough: this may happen when there are hints as to what is the best direction to search in. Small examples might not show that, due to small search times, but large examples often make the dierence apparent. Some interesting characteristics from Prolog are also inherited, which are not found in other languages: A built-in database, which can be used (with caution) to implement global variables, but whose main strength is in saving intermediate results which do not need to be recomputed (lemmas ) and, in the extreme, to generate and change program code dynamically. Meta-programming facilities, which allow the program to be managed as if it were data, examine its code while running, calling goals and collecting the solutions produced on backtracking, and other goods which are only available to logic programming. Easy denition of meta-languages and easy developing of interpreters for those languages. This allows the user to create a high-level language suited for her/his needs, with which developing the nal application will be easier, and to code an interpreter for such a language. CHAPTER 1. WHAT IS CONSTRAINT (LOGIC) PROGRAMMING? 14 Of course, there are some disadvantages in using Prolog as host language, mainly concerned with the dierence of the logic programming paradigm with respect to other paradigms: It might be not as well accepted as other languages: Prolog is sometimes not part of standard curriculum in Computer Science, and therefore some training is usually needed, and some programmers might be reluctant to undertake learning a new paradigm. There are notable dierences with respect to conventional languages in the way data structures are handled: but these dierences, in the end, favor the programmer, for they turn out to be easier to work with and to dene, and more secure in what respect errors caused by illegal memory accesses, etc. Control is also quite dierent: the embedded search, once understood, is a very powerful way of programming. Last, but not least, there are dierent products which implement the CLP paradigm. Depending on the problem some of them might be more adequate than others. But very probably the nal application in an industrial environment will have to interact with other programs, so the possibility of having an interface (other than a raw text le) is a point to take into account. Fortunately this is the case for all commercially available Prolog and CLP systems. 1.9 How Does a CLP System Work? The reader might wonder how a CLP system actually works and solves equations. Equation solving in general might be radically dierent from the well-known methods for solving linear arithmetic equations. CLP programs set up equations just by expressing them these equations, in an internally coded form, are communicated to an internal solver in which values for the variables are worked out. We will not be concerned with way these equations are encoded, but, for the sake of having more knowledge (which will help us in a future to write better CLP programs), we will become solvers of nite domain equations for a while. 1.9.1 Modeling the Problem Suppose we have the precedence net (for example, for a project) and the task lengths3 in Figure 1.3. Usual O.R. methods to nd out critical tasks, the slacks in the tasks, the earliest nish time, etc. include the PERT and CPM algorithms. We will show how a simple, general nite domains algorithm performs the same task as those methods, and can even tackle more dicult problems within the same setting. Supposing that a hard limit for the length of the project is 10 time units, and that we choose each FD variable to represent the time in which the corresponding task can start, a model of the problem can be the following: abcdefg a 3 2 f0 : : : 10g bcd We will use nodes to represent tasks the problem is the same where nodes or edges are used to that end. 1.9. HOW DOES A CLP SYSTEM WORK? 15 0 G 4 E 1 F 1 B 2 C 0 A 3 D Figure 1.3: Precendence net b+1 c+2 c+2 d+3 e+4 f +1 e e f f g g The value of each variable (which is a set, initialized to f0 : : : 10g) represents the moments in time the corresponding task can start. This equation cannot be solved using linear arithmetic methods, because the values of the variables are not real numbers, but rather sets of integers. Of course, it might reformulated in this particular, linear, case to use real numbers, but in general nite domains can always nd a solution, because enumeration is possible, as we will see later. 1.9.2 Be a Solver We will set up a tableau (Table 1.1) in which current domains for the variables will be stored at each moment. At the beginning, all variables will have the initial domain, and we will iterate using the following strategy: Choose one equation analyze the values of the variables related by that equation. Sometimes the maximum / minimum values of the variables can be updated to make the equation hold. This causes the domain of the variable to be narrowed. Finish when no equation gives raise to a variable updating. For example, in step 1, we have selected equation b + 1 e. Since previously b 2 f0::10g and e 2 f0::10g, it is not dicult to deduce that b can be, at most, 9, and that e can be, at least, 1. So this step updates the domain of b and e to be, respectively, f0::9g and f1::10g. The rest of the steps perform similar operations, selecting other equations and rening values of variables until a xpoint, in which no further changes can be made, is reached. CHAPTER 1. WHAT IS CONSTRAINT (LOGIC) PROGRAMMING? 16 Step 0 1 2 3 4 5 6 7 8 9 10 11 Final domains a Variables and Domains b c d e f g 0..10 0..10 0..10 0..10 0..10 0..10 0..10 0..9 1..10 0..8 2..10 2..10 0..7 3..10 2..6 6..10 3..9 0..7 0..5 0..4 0..6 0..4 0..4 0..5 0..4 0..6 2..6 3..9 6..10 Table 1.1: Being a solver Although the general idea behind nite domain solver is as shown above, actual algorithms are much more complicated, and take into account issues like inequality constraints, global constraints, heuristics, enumeration, etc. More complex constraint solvers make a series of decisions (such as which equation is to be to chosen next) and, even if those do not aect correctness, performance depends heavily on them. What are in this cases the dierences with respect to classical methods, such as CPM? A central point is that this is just a particular application of nite domains, and not an algorithm dedicated to project scheduling. In fact, it gives more information than CPM, and can, using the same ideas, be used for more advanced tasks. For example, exact slacks of tasks in the critical path can be found just by setting g = 6 (which is just a way of writing g 2 f6g, another equation similar to those we already have) and repeating the process. In fact, restart the solving from scratch is not necessary: as an example of the dynamic, incremental nature of CLP, we only need to add to our initial set of constraints the aforementioned equation, update the domain of g and then continue the process where it stopped before: more updates are now possible. The result will give us slacks for the variables and the start time for every task so that the project is nished in the shortest time possible. Problem 1.1 Solve the problem with the added constraint that the nal task must end at time 6. Modeling other relationships without resorting to very dierent algorithms is also possible: for example, Two tasks do not depend on each other, but they cannot start at the same time: b 6= c. A resource r, of which there is a limited amount, is needed by two tasks b and d, and allocating more resource to one of these tasks speeds up its completion: b + rb e 1.9. HOW DOES A CLP SYSTEM WORK? 17 d + rd f rb + rd = 6 1.9.3 Don't Be a Solver! But the programmer using CLP tools does not need to build tableaus, keep track of communicating with the solver when a new constraint is added, or jump back to a point where a selection was previously done. CLP languages take care of all of these tasks by themselves, transparently to the programmer. Built-in solvers are provided for several constraint domains, FD being just one of them. Others, which we will talk about later, include linear equations, non-linear equations with intervals, boolean equations, etc. In the next chapter we will have a look at a basic language, based on concepts taken from Logic Programming, and we will introduce the concepts of logical variables and backtracking. We will add constraint solving capabilities upon this simple language, and we will see how non-trivial problems are easy to express. We have chosen to use a logic programming basic language because, although some initial acquaintance with its peculiarities is needed, once this is mastered, the resulting language and syntax merges much better than other approaches with the idea of programming using constraints. ) 18 CHAPTER 1. WHAT IS CONSTRAINT (LOGIC) PROGRAMMING? Chapter 2 A Basic Language In this chapter we will dene a basic language based on rst order logic, but which will not have the full capabilities of Prolog: it will be pure, in the sense that no side eects of metaprogramming facilities are available, and it will not have data structures. But we will add to it some symbols (like predened numbers and operators for common arithmetical operations) which are needed to write constraints. 2.1 A Basic Constraint Language We will dene the skeleton of a constraint language, without many interesting capabilities, but which will be enough to understand the principles of constraint programming without the burden of having to cope with unneeded details. The basic components of our language are the following: Variables which hold values throughout the execution. Dierently from other languages, variables do not need to be typed or declared anywhere, and so they are distinguished from other elements by their syntax. Variables will always be written starting with an uppercase character: X , Y , Speed. Constants which are immutable values. Usual languages can use only numbers as constants, or, at most, a set of predened strings which make up an enumerated or cardinal type| in fact, this is just another way of assigning names to numbers. Constants are either numbers, including oating point numbers, or names starting with a lowercase character: 87, ;45:87, bogus. Underscores are allowed either in the names of variables or non-numerical constants to improve readability: Second Task, a dog. Atoms which will play a syntactic r^ole similar to procedure denitions and procedure calls. Atoms have the form p(X1 : : : Xn ), where p is the name of a procedure or, more strictly, a predicate. X1 to Xn are the arguments of the atom, and the number of arguments n is termed the arity of p. This is commonly written p=n. Examples of atoms are hates(dog cat) predates(big fish small fish) 19 CHAPTER 2. A BASIC LANGUAGE 20 Constraints which allow writing equations relating variables and constants in the program are written. For now we will use only the constraint = of arity 2, which will denote syntactic equality. We will give examples of their use. Although constraint languages include builtin atoms which can be used in programs to perform several tasks (e.g., opening and writing to les), this small language will not have them: all the atoms which appear in bodies must be dened by the user somewhere in the program|although they will not always appear explicitly dened in the examples. Conversely, some constraint languages allow the user to dene and augment the constraints available, besides those already available in the system, but we will not allow that either at this point. 2.1.1 Clauses A clause represents a way of achieving a goal. Clauses have the form p b1 : : : bn : (2.1) where p is an atom, as dened in the previous section, and b1 : : : bn are either atoms or constraints. In this expression, p is commonly called the head of the clause, and b1 : : : bn is called the body. The symbol (which, for typographical convenience, is often written as :-) is called the neck, for it connects the body and the head. Example 2.1 The following are syntactically correct clauses, as usually written in a computer: animal(X):- dog = X. likes(C, F):- C = cat, F = fish. bigger(M1, M2):- M1 = men, M2 = mice. In this example, animal/1, dog/1, likes/2, and bigger/2 are atoms. X, C, F, M1, M2 are variables, and cat, fish, men, and mice are non-numerical constants. Note that variables and constants can be written on both sides of the equality symbol|it does not matter in which side they appear. The program has no meaning in itself as it is written, in the same sense that writing x = 3 + y in a conventional language has no meaning other than a mathematical operation whose purpose in the program we do not know. The only a priori possible interpretation comes from the semantics of rst order logic: a expression such as that in (2.1) is to be read as for p to be true, b1 : : : bn have to be true. Then, under an interpretation directed by the names in the code, the example 2.1 can be interpreted as expressing the following: X is an animal if X equals \dog" , or \dog" is an animal \cat" likes \sh" M1 is bigger than M2 if M1 equals \men" and M2 equals \mice" , or \men" are bigger than \mice" 2.1. A BASIC CONSTRAINT LANGUAGE 21 These clauses contained only calls to constraints. Clauses can also refer to other clauses written by the programmer (atoms). The variables in the clauses are used to pass arguments to the atoms in the body (and constants can be passed as well, of course). Example 2.2 The following clauses have atoms dened by the user in the body: eats(X, Y):- bigger(X, Y). pet(X):- animal(X), sound(X,Y), Y=bark. Their reading depends on the interpretation of the user atoms, but a likely meaning of them is: The big eat the small, or If some X is bigger than some Y, then X eats Y For X to be a pet, it must be an animal and the sound it produces must be a bark , or If X is and animal and X barks, then X is a pet, or An animal which barks is a pet Of course, the nal answer to the real meaning of this piece of code is what the programmer actually had in mind when writing animal/a, sound/2, and bigger/2. 2.1.2 Implicit Equality Equality is a very common constraint in all domains, and so it is customary to write it in a shorter form: the clause p(X):- X = something. can also be written, with exactly the same meaning as p(something). i.e., every time a variable of a clause appears anywhere within a clause, the atom (or variable) this variable is equated to can replace every appearance of that variable. Example 2.3 The clauses in Example 2.1 can also be written as follows: bigger(men, mice). pet(X):- animal(X), sound(X, bark). and their meaning and behavior is exactly the same as in the original example. 2.1.3 Facts The previous section introduced a new type of clause, which is actually a shorthand expression for clauses we already know how to write: the expression p: where p is an atom, is called a fact. The rst clause in Example 2.3 is a fact, which appears because an equality constraint has been implicitly moved to the head of the clause. 22 CHAPTER 2. A BASIC LANGUAGE Example 2.4 The rst and second clauses in Example 2.1 can also be written as facts: animal(dog). likes(cat, fish). 2.1.4 Predicates A predicate is simply a collection of clauses which have the same head name and arity. Recall that the constraints and atoms in the body of a clause represent conditions to be fullled in order to achieve a goal|the head|, so they logically represent a conjunction of goals. Dierent clauses, in turn, represent a disjunction: alternative possibilities to accomplish a target. From a more logical point of view, dierent clauses of a predicate oer alternative possibilities for the predicate to be true. Example 2.5 The following predicate expands our idea of what a pet can look like: pet(X):- animal(X), sound(X, bark). pet(X):- animal(X), sound(X, bubbles). What is the meaning of this example? In addition to the rst, already known clause, which casted animals which bark into the category of pets, we are not including animals whose sound is bubbles (probably shes) into the very same category. So, in a more colloquial form, the example above can be read as Animals which bark and animals which make bubbles are pets Note that when we describe the predicate in a goal-oriented form, the description must take a disjunctive form, closer to the logical meaning of the predicate, but less natural from the point of view of the human language: For something to be a pet, it must either be an animal and bark, or else be an animal and make bubbles. Note also that the same variable X appears in both clauses: the names of the variables in a clause are local to that clause, very much like local variables in procedural languages have an scope limited to the procedure/function they are dened in. 2.1.5 Programs and Queries We are now ready to write programs in our constraint language. A program is simply a collection of predicates, much in the same way that a program in other languages is a collection of procedures or functions. Example 2.6 The following code implements a program which has knowledge about what is a pet, and, using a database of facts dening some animals and characteristics, infers which animals are (to its knowledge), pets. 2.1. A BASIC CONSTRAINT LANGUAGE 23 pet(X):- animal(X), sound(X, bark). pet(X):- animal(X), sound(X, bubbles). animal(spot). animal(barry). animal(hobbes). sound(spot, bark). sound(barry, bubbles). sound(hobbes, roar). Since most CLP systems provide an interactive shell for the interpreter / compiler, the user can usually issue commands to load the program, call predicates in it, change the program, and load it again. Calling a predicate from the interpreter yields the same results as calling it from inside a program. A query issued by the user is just a conjunction of atoms, and has exactly the same form and meaning as the body of a clause. The answer to a query is a set of bindings for the variables which make the query true with respect to the program. Since some predicates may have several clauses which hold for a given query, multiple solutions are possible. Example 2.7 We will give an example of a possible session with a CLP system. The prompt of the system will be shown as ?-. We will use the program in Example 2.6. Load the le where the program is stored ?- consult(pet). Make queries! ?- sound(spot, X). X = bark ?- sound(A, roar). A = hobbes ?- animal(barry). yes ?- animal(X). X = spot X = barry X = hobbes Problem 2.1 What will be the answer(s) to the query ?- sound(A, S). CHAPTER 2. A BASIC LANGUAGE 24 2.2 Searching The query ?- pet(X). returns the following answers: X = spot X = barry How is this achieved? The CLP system performs a search using all the possibilities oered by having several clauses for the predicates. This is best depicted by a search tree which represents all possible paths in the program. Without entering into details, every time a predicate with more than a clause is called, a choice point is made at that execution point: this choice points keeps information about the state of the execution at that moment, so that, if more solutions are needed, the engine can backtrack up to that point, and resume the search with the next untried clause of that predicate. pet(X) animal(X), sound(X, bark) animal(X), sound(X, bubbles) animal(spok) animal(barry) animal(hobbes) sound(spok, bark) animal(spok) animal(barry) animal(hobbes) sound(barry, bubbles) Figure 2.1: A tree The search process, automatically triggered by a failure in the resolution, allows logic programming based languages to return all possible solutions to a query: after having reached a solution, if the user requests for more answers, the toplevel just causes a failure and the backtracking process is (re)started1 . The order of backtracking is as follows: Clauses within a predicate are tried from top to bottom backtracking on a predicate will cause the next untried clause to be executed. The order in which clauses are executed is dened by the search rule. Atoms within a clause body are executed from left to right, and so backtracking is attempted right to left. This is called the selection rule. There are also special all-solutions predicates which encapsulate a search in a single objective and return all possible solutions for a given query. 1 2.3. LOGICAL VARIABLES 25 Other strategies to select which clause and which atom to try are possible, and those dierent search and selection rules give raise to dierent operational semantics for logic languages. Example 2.8 The following query has been executed using the program in Example 2.6: ?- pet(X), animal(Y). X = spot, Y = spot X = spot, Y = barry X = spot, Y = hobbes X = barry, Y = spot X = barry, Y = barry X = barry, Y = hobbes Solutions for the clauses of animal/1 are generated rst, in the order in which the clauses are written. After that, a new solution for pet/1 is generated, following the rules for atoms and clauses stated above. 2.3 Logical Variables Variables in CLP languages are termed logical variables. The adjective logical stems from a unique character not present in other languages: these variables do not necessarily hold values|and yet they are completely legal, and run-time access exception errors are not generated by accessing them2 |, and they can be assigned (or, better, bound ) to other uninitialized variables. The value of an uninitialized variable is not NULL or other esoteric, special value: that variable, simply, has no value at all yet. Logical variable assignment is monotonic, which means that a logical variable cannot mutate its value within a search path. Example 2.9 The variable X can take the value a: ?- X = a. X = a But it cannot take the value a and then change it to b ?- X = a, X = b. no Problem 2.2 Then, how is it possible that the following queries work perfectly? In fact, the kind of fatal errors which are raised in some languages because of the dereferencing of uninitialized pointers, or because or arithmetical operations with numbers holding senseless values, cannot appear in CLP systems (and, if they do, it is the system's, not the programmer's, fault) and, at most, a runtime error is returned, which usually can be caught and recovered from. This results in an easier construction and management of complex data structures, as we will see. 2 CHAPTER 2. A BASIC LANGUAGE 26 ?- X X ?- X X = = = = a. a b. b Hint: state. the toplevel interpreter backtracks between goals, in order to recover the initial The constraint =/2 we have introduced before not only assigns values to variables (or, better, binds variables to values), but it can also bind free variables, constraining them to have the same value. Example 2.10 Variables can be bound one to each other, constraining them to take the same value, and this constraint is taken into account during the rest of the execution: ?- X = Y, X = a. X = a, Y = a. ?- X = Y, pet(X). X = spot, Y = spot X = barry, Y = barry Problem 2.3 Explain the following behavior: why the query has no solutions? ?- X = Y, pet(X), sound(Y, roar). no Problem 2.4 Given the following program, which is intended to model kinship in a family: father_of(juan, pedro). father_of(juan, maria). father_of(pedro, miguel). mother_of(maria, david). grandfather_of(L,M):father_of(L,N), father_of(N,M). grandfather_of(X,Y):father_of(X,Z), mother_of(Z,Y). answer the queries: ?- father_of(juan, pedro). ?- father_of(juan, david). ?- father_of(juan, X). 2.4. THE EXECUTION MECHANISM ????- 27 grandfather_of(X, miguel). grandfather_of(X, Y). X = Y, grandfather\_of(X, Y). grandfather_of(X, Y), X = Y. Problem 2.5 Augment the code in Problem 2.4 to contain rules for the relationship grandmother of(X, Y), following the spirit of the program. 2.4 The Execution Mechanism Execution of CLP languages can be seen as a tree traversal, where the nodes of the tree are conjunctions of atoms to be proved (similar to bodies of clauses, which are also conjunctions of atoms). The root of the tree is the initial query posed by the user, and there might be one or several branches starting at every node, each branch corresponding to the clauses with matching heads for the rst (leftmost) goal in the conjunction. The tree is explored by selecting the leftmost goal in a conjunction, and the leftmost untried branch (clause) for that goal. The tree can be explored partially or totally in the latter case, all solutions to the initial query are returned. Figure 2.2 shows how the execution tree is traversed for the following program and the query ?- grandparent(charles,X). grandparent(C,G):parent(C,P), parent(P,G). parent(C,P):- father(C,P). parent(C,P):- mother(C,P). father(charles,philip). father(ana,george). mother(charles,ana). Execution starts at the toplevel query grandparent(charles, X), which is equated to the rst clause of the program. Variables in the body of the clause are substituted by the constants in the query, and the body (with some constants in place of the textual variables) is left to be solved as a conjunction of goals. The execution continues by selecting the rst goal in the body (parent(C, P), now rewritten at runtime to parent(charles, P)), and the process continues. There are two matching clauses for parent(charles, P), and the two are tried in textual order: that is the reason why two dierent subtrees are rooted at this node. The execution proceeds until a node with no atoms to solve is obtained (this is possible because a resolution against a fact, which has no body, removes an atom from the node). The nal result of the query, X = george, is obtained in the leaf labeled (precisely) X = george. This binding for X can be seen as propagated upwards in the tree and communicated CHAPTER 2. A BASIC LANGUAGE 28 to the variable present in the toplevel query, but, in fact, the variable this binding is made to, is the same one which was present in the toplevel query: as atoms were reduced in the execution process, variables in the same position in atoms and clause heads were unied, i.e., equated. grandparent(charles,X) parent(charles,P),parent(P,X) father(charles,P),parent(P,X) mother(charles.P),parent(P,X) parent(philip,X) parent(ana,X) father(philip,X) mother(philip,X) failure failure father(ana,X) X = george mother(ana,X) failure Figure 2.2: Traversing an execution tree ) Knowing the operational behavior of the language is necessary for larger programs (especially because it is instrumental for achieving better performance), but for the time being, it is not essential: understanding the declarative semantics (i.e., the grandfather of someone is the father of his/her mother or the father or his father) is far more important at this stage. 2.5 Database Programming The code in Example 2.4 is a case of the so called database programming : it acts as a database, where facts store the basic relationships among data (in much the same way as in relational databases), and the rules express new relationships among data, based on the ones we already have: in other words, they provide views to the database, but everything is seen, together, as a program which can answer to queries. This language, although limited (for example, no data structures are used), can model quite sophisticated relationships, and answer queries which are not trivial. Example 2.11 A logical circuit. Figure 2.3 depicts an electronic circuit implementing logic gates. Some parts of it (resistors and transistors) are labeled. We will use facts to construct a small database stating which components connect the dierent points highlighted in the circuit: resistor(power,n1). resistor(power,n2). transistor(n2,ground,n1). transistor(n3,n4,n2). 2.6. DATALOG AND THE RELATIONAL DATABASE MODEL 29 transistor(n5,ground,n4). Note that current direction is meaningless in resistors, but for simplicity we have chosen to use the rst argument of the facts dening resistors to denote the end connected to the power. Some knowledge of electronic gates tells us that an inverter can be built of a transistor appropriately connected to a resistor. The needed connections are reected in this rule: inverter(Input,Output):transistor(Input,ground,Output), resistor(power,Output). Similar rules can be written for nand and and gates: nand_gate(Input1,Input2,Output):transistor(Input1,X,Output), transistor(Input2,ground,X), resistor(power,Output). and_gate(Input1,Input2,Output):nand_gate(Input1,Input2,X), inverter(X, Output). The following query and answer demonstrate the knowledge of the problem about our circuit: ?- and_gate(In1,In2,Out). In1=n3, In2=n5, Out=n1} Similarly, queries could be made to nd out the connection points of inverters and and gates. Problem 2.6 In Example 2.11, how could the code be modied so that it does not matter whether the resistors are dened as having power in the rst or in the second argument? In other words, change and/or augment the rules for the circuit components so that whoever denes resistor/2 does not have to know about the dierences between the rst and the second argument. 2.6 Datalog and the Relational Database Model The language we have seen so far, having (logical) variables, constants, user-dened predicates (which can be assimilated to program procedures), and the equality constraint =/2 is a constraint language. This language is, however, severely impeded by the lack of data structures and arithmetical operations, and we will introduce them later. In fact, its power is equivalent to that of propositional logic (i.e., logic without variables), because every program in our rst language can be rewritten to a semantically equivalent propositional program, and any propositional program is, directly, correct in our language. CHAPTER 2. A BASIC LANGUAGE 30 Power r1 n1 t1 r2 n2 t2 n3 n4 n5 t3 Figure 2.3: An electronic circuit Notwithstanding, augmenting this language with numbers and arithmetical operations, and (for the sake of practicality) other facilities (such as negation), produces a far superior language, termed Datalog, which is often used in advanced databases. Without adding anything to our language, we will show how it can be directly used to model common operations in relational databases. Basic structural components of relational databases are tables, which are collections of tuples (rows) having the same number of components in each tuple. Each component of every row has a type, such a string, number, date, etc., usually from a set of predened types available in the database we will not deal with such types at the moment. The arguments in the same position of all the rows in each table belong to the same column, and every column has an attribute, which usually names that column. Figure 2.4 shows two tables which can be part of a database which collects information about persons and cities where they have lived. Name Age Sex Brown Jones Smith 20 21 36 Person M F M Name Town Brown London Brown York Jones Paris Smith Brussels Smith Santander Years 15 5 21 15 5 Lived{in Figure 2.4: Two tables in the relational database model The order of rows in immaterial, since they are not accessed and retrieved by number, but according to the matching of the arguments. Similarly, the order of columns is not important either, since they are labeled with attributes but it will be important for our translation to a logic language. It is important to note that duplicate rows are not allowed, or, rather, that they are meaningless, since duplicated solutions are not taken into account at all. 2.6. DATALOG AND THE RELATIONAL DATABASE MODEL 31 A translation to our logic language takes every part of the database and casts it into the component of the constraint language following the paths below: Relat. Database ! Logic Program Relation Name Relation Tuple Attribute ! ! ! ! Predicate symbol Predicate consisting of ground facts (facts without variables) Ground fact Argument of predicate It is important to note that, since in our language, arguments of an atom cannot receive a name (but other logic languages allow it), the correspondence attribute name ! argument position must be respected in the whole translation. The fragment of database in Figure 2.4 can be translated to the set of facts below: person(brown,20,male). person(jones,21,female). person(smith,36,male). lived_in(brown,london,15). lived_in(brown,york,5). lived_in(jones,paris,21). lived_in(smith,brussels,15). lived_in(smith,santander,5). Using this translation scheme, which uses a set of facts to model a static database, the usual operations on relational databases can be easily dened, an implemented using clauses. As mentioned before, the result is that not only the database, but also the dierent queries, views, etc. can be programmed using the same language. S Union: two clauses dene that a table r s is constructed by taking elements which belong either to table s or to table r. Extending it to more than two tables is straightforward: X : : :,Xn ) X : : :,Xn ) r union s( 1 , r union s( 1 , r(X1 ,: : :,Xn ). s(X1 ,: : :,Xn ). Set Dierence: tuples belonging to one table, but not to the other. The implementation of Set Dierence needs negation, which we have not discussed yet: we will come back to it later. For now, it will suce to know that a general and proper implementation of negation in logic languages is very dicult, and usually only a restricted version of the full logical negation is available. Fortunately, for the purpose at hand (relational databases), implementing a sound logical negation is possible, since the tables are always nite and there are no data structures which can construct innite objects. X : : :,Xn) r(X1 ,: : :,Xn ), X : : :,Xn) s(X1 ,: : :,Xn ), r diff s( 1 , r diff s( 1 , not s(X1,: : :,Xn). not r(X1,: : :,Xn). CHAPTER 2. A BASIC LANGUAGE 32 We will later discuss negation more in depth. Cartesian Product: X ::: X X X ::: X : : : Xm+n) Xm+1 ,: : :,Xm+n ). r X s( 1 , , m , m+1 , , r( 1 , , m ), s( Projection: X X X X X r13( 1 , 3 ) r( 1 , 2 , 3 ). Selection: the selection criteria is just another predicate which can fail or have success for a tuple of data. In general it could be any user predicate, but in this case we will use the arithmetical predicate , which we assume is already dened by the system. X X X X X X X X r selected( 1 , 2 , 3 ) r( 1 , 2 , 3 ), ( 2 , 3 ). Some operations can be expressed as derivatives from the above ones, but they can also be expressed more directly in CLP: Intersection : tuples which are in r and s at the same time: X : : :,Xn) r meet s( 1 , r(X1 ,: : :,Xn ), X : : :,Xn). s( 1 , Join : tuples which have an element in common in two tables: X ::: X X X X : : : Xn ), X X X : : : Xn ). r joinX2 s( 1 , , n ) r( 1 , 2 , 3 , , s( 10 , 2 , 30 , , 0 The appearance of duplicate answers, even if there are no duplicates in the original table (e.g., projecting the table lived-in on its rst argument) is not a theoretical problem, since they are simply ignored, but it can be a practical problem. Database implementations automatically discard repeated tuples. Similarly, CLP languages have built-in primitives which allow the gathering of all answers to a query and remmoving duplicates. ) The so-called deductive databases are relational databases which use heavily concepts from rst-order logic to implement (actually, to program) explicitly deduction and coherence rules. They use commonly a language similar to the one we have just developed, plus some extended facilities. This language is usually a subset of a logic-based full-edged language. It is language of this kind, even augmented with constraint solving capabilities, which we are aiming at now. ) Chapter 3 Adding Computation Domains In this chapter we will add dierent constraint domains to our language, and we will see how they greatly expand its usefulness. Several examples, which could not have been realised before, will be developed here. 3.1 Domains A constraint domain introduces new symbols and their associated semantics in the language. This gives the language an ability to express computations which go well beyond what was available until this moment. Example 3.1 In a language like C or Pascal, integer numbers and real numbers are provided by default. Think of an application which needs to deal with complex numbers. Two paths are possible: Writing some libraries which create, access the real and imaginary parts of, and make arithmetical operations with such numbers, or p Augment the language with a new, primitive data type complex, a new symbol for ;1 (usually written as { or |), and an expanded meaning for the usual arithmetical operators. While both approaches are equally valid, if the embedding is correctly made, and ts nicely with the rest of the language, the second is probably more elegant and leads to languages easier to understand. We will see that using constraints together with logic programming is actually a natural step towards a more powerful language. There is a variety of constraint domains to choose, and CLP languages choose which one to implement (several at once, in some cases). The reason for having them is that dierent problems call for dierent constraint domains (due to its nature), and nding the right constraint domain is usually not dicult. But, in any case, the rst step is deciding a modelization of the problem. Choosing a constraint domain has another impact: the capabilities of the solvers for that domain. Not all constraint domains are equally solvable: some have algorithms which generate a solution (e.g., linear equations), some need enumeration and trial and error (e.g., 33 34 CHAPTER 3. ADDING COMPUTATION DOMAINS nite domains), and some need an iterative xpoint-based algorithm which approximates a solution (e.g., non-linear equations). Having dierent constraint systems available is, actually, an advantage, in that it allows the programmer to model the problem freely, and then try to adapt (if needed) that model to the constraint system which more closely resembles the modelization. We will present some constraint domains which will allow us to perceive the dierences among them, and, at the same time, to understand how all of them blend easily with the underlying LP machinery. 3.2 Linear (Dis)Equations Linear (dis)equations were, in practice, pioneered by the CLP(<) system, which oered a Prolog-like interface, where arithmetical symbols were enriched to express constraints. Other implementations (namely, Prolog IV, CHIP, SICStus . . . ) have chosen to implement this constraint system as well, as it has a well-known solving procedure, and is useful in a range of applications. We will use Prolog IV in the examples, as it is a quite reasonably known logic programming system, and it has several constraint systems available. A note on syntax: CLP systems tend to have some variations in the syntax of similar operations. This may be slightly confusing at rst, but it is not a real problem: dierent syntaxes are easily understood, once the underlying language design principles are known. We will augment the language with the following components: Numbers, both integers and oating point numbers (which aproximate real numbers), written as usual. Addiotinally, expressions like 3.7e5 represent the number 3:7 105 . Arithmetic operators (+, ;, *, /) , written in the usual inx form. They allow us to construct arithmetic terms, using numbers and variables: 3 + 4, X + 3 (6 ; Y ). Those arithmetic terms stand for the corresponding arithmetic expressions. The constraint =/2, which now stands for arithmetical equality : two expressions are now said to be equal if they can be arithmetically reduced to the same expression. More arithmetical constraints, which act as the corresponding arithmetical relational operators: Prolog IV name gelin(X, gtlin(X, lelin(X, ltlin(X, Y) Y) Y) Y) Arithmetical meaning X Y X >Y X Y X <Y Note that all of them have the sux lin, which stands for linear: the reason for that will become clear later. 3.2. LINEAR (DIS)EQUATIONS 35 Prolog IV (and other CLP languages) can solve equations directly typed in the top-level prompt. These examples are taken from a Prolog IV session: ?- 4 Y ?- X Y = = = Y = 3. 1 3*Y - X/2, X+Y = Y - 4*X + 7. 7/10, X = 7/5. (The prompt of the Prolog IV interpreter is >>, but we will be using ?- throughout this paper). By default, answers are returned as fractions because Prolog IV uses innite precision arithmetic when possible. The equations above had a unique solution, but equations may have no solutions: ?- X = 3*Y - X/2, X+Y = Y - 4*X + 7, lelin(X, Y). false. And sometimes there exists an innite number of solutions: ?- X = Y + 3. X ~ real, Y ~ real which means that X and Y are just real numbers. The syntax needs further explanation, but we will delay it until later: it will suce by now to understand it as meaning \belongs to the class of"|in this case, \X belongs to the class of the real numbers". Prolog IV does not output complex relationships, although it is of course aware of them. In the previous example, Yreal, Xreal is actually a weak answer, for the constraint X + Y = 3 is known by the system. A clearer example is the one below: Example 3.2 The following query represents the constraint X X = Y can be deduced: YY X, from which ?- gelin(X, Y), gelin(Y, X). Y ~ real, X ~ real No output of constraints is given, but the solver is internally aware of the constraint X =Y. ?- gelin(X, Y), gelin(Y, X), X = 4. Y = 4, X = 4 There is a problem in generating output for complex answer constraints: the constraint system, after simplication, has to be projected onto the query variables (because these are the ones the user knows about), and this is not easy (or even feasible) in some constraint domains. CHAPTER 3. ADDING COMPUTATION DOMAINS 36 3.3 Linear Problems with Prolog IV Let us give a couple of examples of using linear constraints. We will rst dene a predicate which can generate (and test) natural numbers. nat(0). nat(N):gtlin(N, 0), nat(N-1). This can be read as \zero is a natural number, and any number greater than zero is a natural number, provided that is predecessor is also a natural number". Note that we are using the actual number zero to represent zero. This is an example of a recursive denition, in which a problem (in this case, knowing when something is a natural number) is solved by reducing the initial problem to a similar, but in some sense smaller or simpler task. After loading the above code, Prolog IV returns a series of integer numbers: ?- nat(X). X = 0 X = 1 X = 2 .. . And it can also test whether a given number is or not natural: ?- nat(3.4). false ?- nat(-8). false This is one of the most important properties of CLP languages: as logical properties are written without paying attention1 to the internal operations of the language, the code can be used in various modes : it can either generate or test, or perform a mixture of both: ?- gelin(3, X), nat(X). X = 0 X = 1 X = 2 X = 3. ?- gelin(3, X), nat(X+5). X = -5 X = -4 X = -3 This can be assumed at this moment. Later we will see that a better knowledge of how the system actually works is needed for writing certain programs. 1 3.3. LINEAR PROBLEMS WITH PROLOG IV X X X X X X 37 = -2 = -1 = 0 = 1 = 2 = 3. The denition above can also be written as nat(0). nat(N + 1):gelin(N, 0), nat(N). and it works exactly in the same way. Problem 3.1 How, and why, will the following code nt(0). nt(N):nt(N-1). behave if queried ?- nt(3.4). (???) ?- nt(-8). (???) In a similar way to the natural numbers, even numbers can be dened as even(0). even(N+2):gtlin(N+2, 0), even(N). which is to be read as \zero is an even number, and any even number plus two is also an even number". Problem 3.2 Write code, similar to the one for and test for oddity. Call the predicate odd/1. even/1, which can generate odd numbers Problem 3.3 Write an alternative denition for odd/1 which uses the denition of but which is not based on it. It should not contain any recursive call. Problem 3.4 Write a predicate multiple of another number B. multiple(A, B) which tests if a number A even/1, is an integer CHAPTER 3. ADDING COMPUTATION DOMAINS 38 Problem 3.5 It should be trivial to give denitions for odd/1 and even/1 using multiple/2. Problem 3.6 Find whether a number N is congruent modulo K to some other number M (M N mod K ). This means that the remainder of the integer division of M by K is the same as the remainder of the integer division of N by K: 12 7 mod 5, 32 2 mod 2, 32 2 mod 30, . . . . Call the predicate congruent(M, N, K). ) A more involved example is generating the value of e based on a (slowly) convergent series: 1 1 1 1 = 4 1 ; 2 + 3 ; 4 + . The predicate which implements this is called is E(N, E), where N is the number of terms to add, and E is the value of e. We will make this toplevel call an interface to a predicate which will actually perform the calculation: e is_E(N, E4*4):- is_E(N, 1, 1, E4). is_E(0, Mult, Sign, 0). is_E(N, Mult, Sign, Sign/Mult + RestE):gtlin(N, 0), is_E(N-1, Mult+1, -Sign, RestE). The bulk of the work is performed by is E/4, which has in its rst argument the number of elements in the series remaining to be added, in the second argument the denominator for each element of the series, in the third argument the sign (+1 or ;1) to be used, and in the last argument the result of adding the rest of the series. Operationally, the predicate works by nding out the rst element of the series (which is Sign/Mult), and stating (in the head of the clause) that the whole series is to be calculated by adding this rst element with the rest of the series then, a recursive call works out the value of this rest of the series. All mathematical operations are solved when possible (i.e., when a sucient number of variables have been reduced to denite values). Finding an approximation of e is as easy as writing: ?- is_E(10, E). E = 1627/630. The problem, in that case, is that we do not have any idea of the accuracy of this approximation. A better control can be obtained by forcing two successive approximations of e to dier by a small amount2 . So, an easy possibility is writing: ?- lelin(abs(E1 - E2), 0.1), is_E(N, E1), is_E(N+1, E2), !. N = 39, E2 = 3637485804655193/1335732864265800, E1 = 3771059091081773/1335732864265800. Mathematically speaking, this is not a good idea: the error of the approximation in a series is, in general, an expression which is to be calculated separately, and usually working out this expression is not as straightforward as testing the value of an element of that series. 2 3.4. FIBONACCI NUMBERS 39 Thirtynine elements may seem a lot for an accuracy of only 0.1, but if one thinks carefully, the 40th element is 401 , and since the series itself returns 4e , the dierence among the 39th and the 40th element is just 0.1 concerning the accuracy of the approximation, it is not really meaningful, and should be looked mainly as an exercise of using CLP in innovative ways, not possible in other languages. Some primitives not yet explained are used in this example: abs/1 returns the absolute value of a number. The ! sign forces Prolog IV (and any other Prolog or CLP language) to stop after nding the rst answer to a query. We will return to them (especially to !) later. Problem 3.7 There is some redundancy in this example. Two of the arguments perform similar roles: the rst one counts the number of elements in the series still to be worked out, and it thus goes from N to 0 the second one has the denominator of the fractions, and it goes from 1 to N. The only reason for doing that is automatically calculating whether to add or substract each element in the series by reversing the sign of the third argument. CLP can help to have a simpler program: we can start at N1 and progress towards 11 , leaving undetermined the sign of every element of the series, but reversing its sign, until the rst element is reached. Write this program. 3.4 Fibonacci Numbers The Fibonacci series is a classical problem in mathematics and computer science. It is usually dened as: F0 = 0 F1 = 1 Fn+2 = Fn+1 + Fn I.e., the numbers of Fibonacci are 0, 1, 1, 2, 3, 5, 8, 13. . . It is easy to straightforwardly translate it to a logical denition, mimicking each equation with a clause. The rst argument of the predicate is the index of the Fibonacci number, and the second argument is the Fibonacci number itself. Note that there is an explicit check of the index in the last clause: fib(0, 0). fib(1, 1). fib(N, F1+F2):gelin(N, 2), fib(N-1, F1), fib(N-2, F2). There are two recursive calls in this case. This will be important later. As usual, queries can be issued to nd out which number corresponds to a given index: ?- fib(10, F). F = 55. But, as in previous examples, other calls are possible, as, for example, nding out the index of a Fibonacci number: CHAPTER 3. ADDING COMPUTATION DOMAINS 40 ?- fib(N, 8). N = 6 Other interesting queries are possible, as, for example, nding which are the xpoints of the Fibonacci series (i.e., which numbers are equal to their indexes): ?- fib(N, N). N = 0 N = 1 N = 5 But the above program, having two recursive calls, needs too much memory because of repeated calls: F100 needs F99 and F98 , but F99 itself needs F98 again, so not only many calls are needed: they are repeated, too. The program can use up the memory allocated for the process in medium sized computations: ?- fib(100, F). error: too_many_scols Increasing the memory allocated by the process only pushes the problem a little bit forward: it is much better to reformulate the program (which, in this case is quite easy) to make another faster (and cheaper in terms of memory) implementation. Problem 3.8 Write a simply recursive program which denes the Fibonacci series. Hint: use two arguments to store the current Fibonacci number, and the previous one. Find the 1000th Fibonacci number (the last four digits are 8875). 3.5 Non-Linear Solver: Intervals The linear equations solver we have been seeing has the attractive of being complete (i.e., it always nds a correct solution if it exists, and says that no solution exists only when this is the case). But, on the other hand, it can solve only linear equations. Prolog IV implements a second numerical constraint system which is based on intervals: variables take values in intervals (and combinations thereof) of real numbers, which in fact associates a (potentially) innite number of points to each variable. Intervals have a special syntax in Prolog IV, and the usual mathematical combinations of open / closed interval are available. Table 3.1 shows the four dierent types of intervals and their syntax. Interval #X Y ] (X Y ] #X Y ) (X Y ) From X (included) X (excluded) X (included) X (excluded) Y Y Y Y To (included) (included) (excluded) (excluded) Prolog IV cc(X, oc(X, co(X, oo(X, Y) Y) Y) Y) Table 3.1: Intervals and their representation in Prolog IV Using intervals brings advantages in some cases. Non-linear equations can be approximately solved, and intervals can also be used to simulate easily nite domains (by forcing 3.5. NON-LINEAR SOLVER: INTERVALS 41 the interval to contain only integer values). On the other hand, it is not sure that a solution will be found for non-linear problems. For this reasons, it is convenient to know when the linear solver has to be used, and when the non-linear solver is the correct choice. Thus, it is necessary to be able to tell Prolog IV which solver we want to use in every particular case. This is done by using dierent keywords to express operations in the linear and non-linear solver the two versions are shown in Table 3.2. The sux lin is removed from the relational constraints, and dots are added in before and after the arithmetical operators. The equality constraint =/2 remains the same. But the \compatible with" operator, , particular to Prolog IV, is to be used to bind variables to intervals: ?- X = A .+. B, A ~ cc(1, 3), B ~ cc(3, 7). B ~ cc(3,7), A ~ cc(1,3), X ~ cc(4,10). ?- X = A .-. B, A ~ cc(1, 3), B ~ cc(3, 7). B ~ cc(3,7), A ~ cc(1,3), X ~ cc(-6,0). In their basic version, operations on intervals just add, subtract, etc. the maxima and minima of the ranges of the variables, which are updated to make the operations true| always in the direction of narrowing the intervals. In more complex problems, the intervals are successively narrowed using an iterative procedure until a xpoint is reached: ?- X = A .-. B, A ~ cc(1,3), ge(X,0), B ~ cc(3,7). B = 3, A = 3, X = 0. Linear version Non-linear (intervals) version + * / gtlin( gelin( ltlin( lelin( , , , , ) ) ) ) .+. .-. .*. ./. gt( , ge( , lt( , le( , ) ) ) ) Table 3.2: Correspondence between keywords for the linear and non-linear solvers A note on Prolog IV and The use of in Prolog IV is a shorthand for writing more complex relations: an expression like A cc(1, 3) is a shortened form of cc(A, 1, 3) where cc/3 is a relation which states that A can take values in the interval #1 3]. Similarly, the expression gt(A, 0), which stands for A > 0, can also be written A gt(0), and other constructions as for example .*. are abbreviated forms for relations: writing X = A .*. B is akin to writing times(X, A, B). CHAPTER 3. ADDING COMPUTATION DOMAINS 42 3.6 Some Useful Primitives Besides those already mentioned, it is usual that additional primitives are provided in CLP systems. We will mention some primitives which are available in Prolog IV the reader is advised to refer to the manual of the CLP system being used. 3.6.1 The Bounds of a Variable Sometimes accessing the bounds of a variable is useful. Could this be made using the interval constructors to access the bounds of the variables? ?- A U L A ~ ~ ~ ~ co(1, 3), A ~ co(L, U). gt(1), lt(3), co(1,3). This does not work as expected: the lower and upper bounds are not returned as simple numbers, but rather as (innite) sets of numbers. Additionally, the intervals might not be as desired if we do not access the variable using the same combination of open/closed interval it has at that moment: ?- A U L A ~ ~ ~ ~ co(1, 3), A ~ oc(L, U). ge(1), lt(3), co(1,3). What happens here is that we are not accessing the bounds of the variable A, but rather constraining the variables L and U. When L is constrained to be a lower bound of the interval #1 3), then L can take any value from 3 (excluded) downwards (i.e., the range (;1 3)). There are specialized primitives which access the greatest lower bound of an interval variable (glb(A, L)), the lowest upper bound (lub(A, U)) and both at the same time: bounds(A, L, U): ?- A ~ co(1, 3), glb(A, L). L = 1, A ~ co(1,3). ?- A ~ co(1, 3), lub(A, U). U = 3, A ~ co(1,3). ?- A ~ co(1, 3), bounds(A, L, U). U = 3, L = 1, A ~ co(1,3). This can be used, in certain cases, to force a maximization/minimization of the solution of a problem: ?- X = A .-. B, A ~ cc(1,3), B ~ cc(3,7), glb(X,X). B = 7, A = 1, X = -6. 3.7. A PROJECT MANAGEMENT PROBLEM 43 3.6.2 Enumerating Variables Very commonly the problem constraints do not suce to give denite values to the variables. In this case obtaining solutions must be made resorting to an enumeration of the remaining values in the domain of each (or some) variables. With nite domain variables this enumeration poses no problem other than performance, since the number of possible values in the domain is nite. With interval variables the situation is more awkward, because the set of points in the interval of the variable is, in principle, innite. To help in this case, the primitive enum/1 instantiates its argument, which must be an interval variable, to the integer values in its domain. Example 3.3 This piece of code decomposes a number (1001, in this case) into two factors: ?- Num = 1001, Num = A .*. enum(B), enum(A). B = 1, A = 1001, Num = B = 7, A = 143, Num = B = 11, A = 91, Num = B = 13, A = 77, Num = B, A ~ cc(1,Num), B ~ (1,A), 1001 1001 1001 1001. The key point for the solution is the enumeration: it generates integer numbers in the domain of the variables, which are automatically tested against the constraints. If those numbers are not generated, the system does not have any way of factoring Num: ?- Num = 1001, Num = A .*. B, A ~ cc(1,Num), B ~ cc(1,A). Num = 1001, B ~ cc(1,1001), A ~ cc(1,1001). Problem 3.9 Use the code in Example 3.3 to factor bigger numbers. Try interchanging the order of the enumeration. Is there any dierence? Why? Prolog IV provides a number of enumeration and splitting primitives, which are useful in a variety of contexts. 3.7 A Project Management Problem In this section we will address the same problem we did in Section 1.9.1, but we will leave the task of solving the resulting equations to a CLP system. Recall the precedence net in Figure 1.3, and suppose that we want the whole job to be nished in 10 units of time or less. In that example we used nite domain variables, and in this programming example we will use interval variables to simulate FD variables. Recall that the constraints for the precedence net were abcdefg a 2 f0 : : : 10g bcd 44 CHAPTER 3. ADDING COMPUTATION DOMAINS b+1 c+2 c+2 d+3 e+4 f +1 e e f f g g These can be translated into Prolog IV using the following clause: pn1(A, B, C, D, E, F, G):ge(A, 0), le(G, 10), ge(B, A), ge(C, A), ge(D, A), ge(E, B .+. 1), ge(E, C .+. 2), ge(F, C .+. 2), ge(F, D .+. 3), ge(G, E .+. 4), ge(G, F .+. 1). where variables in the head of the clause correspond to nodes in the graph, and the maximum and minimum starting times for the corresponding job are precisely the bounds of the variables. Time constraints are directly encoded as Prolog IV constraints. After loading this simple program in the system, making a query yields the following result: ?- pn1(A,B,C,D,E,F,G). G ~ cc(6,10), F ~ cc(3,9), E ~ cc(2,6), D ~ cc(0,6), C ~ cc(0,4), B ~ cc(0,5), A ~ cc(0,4). The allowed range for each variable represents the slack in the start time for the corresponding task. We can minimize the total time of the project by setting the time of the end task to its lower bound: ?- pn1(A,B,C,D,E,F,G), glb(G, G). G = 6, E = 2, C = 0, A = 0, F ~ cc(3,5), D ~ cc(0,2), B ~ cc(0,1). As expected, some variables do not have slack: those are the ones corresponding to critical tasks, whose delay would imply a delay of the whole project. A variant of the project is presented in Figure 3.1. In that gure, there is a task (F) whose duration we can change. Speeding it up will cost more resources, slowing it down will make it cheaper. We want to know what is the minimum resource consumption so that the project is not delayed. We can model this by using the following clause: pn2(A, B, C, D, E, F, G, X):ge(A, 0), le(G, 10), ge(B, A), ge(C, A), ge(D, A), ge(E, B .+. 1), ge(E, C .+. 2), ge(F, C .+. 2), ge(F, D .+. 3), ge(G, E .+. 4), ge(G, F .+. X). 3.7. A PROJECT MANAGEMENT PROBLEM 45 0 G 4 E X F 1 B 2 C 0 A 3 D Figure 3.1: Project 2: F can be speeded up! X, the length of task F, is added to the variables in the head, since we want to access it. A possible query which minimizes project time and maximizes F's duration is: ?- pn2(A, B, C, D, E, F, G, X), glb(G,G), lub(X,X). X = 3, G = 6, F = 3, E = 2, D = 0, C = 0, A = 0, B ~ cc(0,1). Problem 3.10 What happens if the primitives glb/2 and lub/2 are called in reverse order in the example above? Why? 0 G E 1 F X B 2 C 0 A 4 Y D Figure 3.2: Two tasks with length not xed The last variant of this problem is depicted in Figure 3.2. Two tasks, B and D have a length which is not xed, but there are some additional constraints which relate their lengths: any CHAPTER 3. ADDING COMPUTATION DOMAINS 46 of them can be nished in 2 units of time at best, but shared resources disallow nishing both tasks in the minimum time possible: the addition of the duration of both tasks is always 6 units of time. The constraints which describe the net, again in Prolog IV syntax, are expressed in the following clause: pn3(A, B, C, D, E, F, G, X, Y):ge(A, 0), le(G, 10), ge(X, 2), ge(Y, 2), X .+. Y = 6, ge(B, A), ge(C, A), ge(D, A), ge(E, B .+. X), ge(E, C .+. 2), ge(F, C .+. 2), ge(F, D .+. Y), ge(G, E .+. 4), ge(G, F .+. 1). The query to ask for a solution, and the answer returned, is as follows: ?- pn3(A,B,C,D,E,F,G,X,Y), glb(G,G). Y = 4, X = 2, G = 6, E = 2, C = 0, B = 0, A = 0, F ~ cc(4,5), D ~ cc(0,1). which means that, since X has the minimum possible value, task B is the one to be accelerated. Also, all tasks but F and D are critical now. A note on minimization (and maximization): The approach we have followed to max- imize / minimize a constraint problem is a very nave one: taking the maximum value of a variable and sticking to it. This does not always work because, since the non-linear solver (as nite domain solvers) is not complete, and there are often values in the range of a variable which are not actually compatible with the problem constraints: ?- X ~ cc(-2, 2), X .*. X X ~ cc(-2, 2). = 1. The solver did not work out a solution for this quadratic equation (e.g., X = 1 and X = and the initial interval for the variable remains unchanged. If one adds the additional constraint that the solution must be smaller than 1: -1), ?- X ~ cc(-2, 2), X .*. X X ~ cc(-2,-0.5). = 1, X ~ lt(1). the answer approximates better the solution. But since there is no algorithm for solving non-linear equations, these are left as constraints. The only way to reach a solution to those problems is enumerating, or waiting for variables in the problem to become ground (or, at least, more constrained) so that the solver can decide if the values are compatible with the constraints. For specialized cases (such as maximization or minimization) the solvers include builtin strategies (e.g., branch and bound) which converge to a solution faster than blind enumeration. These strategies are accessible by calling ad-hoc predicates: ?- X ~ cc(-2, 2), X .*. X X = -1. = 1, min(X, 0, X), realsplit(X]). 3.8. OTHER CONSTRAINTS AND OPERATIONS 47 3.8 Other Constraints and Operations What we have sketched here is just a brief look at the possibilities available in CLP programming systems: most of them oer a whole gamut of primitive predicates and operations, which implement useful goodies and specialized complex constraints found to be interesting in practical cases. In particular, as an example, Prolog IV has also: Complementary intervals, which implement the exclusion of an interval. Boolean operations and constraints on them. Extended real operations, such as trigonometric operations, logarithms and other transcendental operations. Constraints on lists (about which we will see an example later). Constraints on integers, which force interval variables to take only integer values (or to exclude them), thus allowing the interval solver to be used with problems modeled using nite domains. 3.9 Herbrand Terms Herbrand terms are a representation of nite trees. Technically, they are non-interpreted function symbols of rst-order logic, but their main use, and the approach we will follow in presenting them, is as constructors of data structures. They are interesting because they are present in all the CLP languages, which means that data structuring and abstraction is handled uniformly in all CLP languages. Moreover, Herbrand terms themselves can be viewed as a constraint system itself, where the only constraint allowed is the syntactic equality (very similar to the one in the rst language we presented|actually, the data in that language was a simplication of Herbrand terms). A formal denition of a Herbrand term is: A variable is a Herbrand term A constant is a Herbrand term A function symbol f with arity n applied to n terms is a Herbrand term: f (t1 t2 : : : tn ) For example, following the syntax for variables and constants presented in Section 2.1, the following are examples of Herbrand terms (which we will call henceforth only \terms"): X mum 45 identity(Name, Number) task(Start, End, window(front), needs_carpenter) f(a, X, g(Y, t)) CHAPTER 3. ADDING COMPUTATION DOMAINS 48 Terms represent trees in a attened, text-only form. Figure 3.3 shows a tree-like depiction for the last term in the previous list. The function symbols in the written form correspond to nodes of the tree their arity correspond to the number of subtrees rooted at that node. From a data structures point of view, the atoms correspond to closed nodes, where the tree cannot grow, and the variables represent open nodes, which can be further instantiated (i.e., bound to another term) to produce a larger tree. f a g X Y t Figure 3.3: A tree corresponding to a term 3.10 Herbrand Terms: Syntactic Equality Terms can only be equated between them. The only constraint allowed is =/2, representing syntactic equality. Two terms are equated by binding variables to subterms so that the initial terms become equal. The formal algorithm which does that is: An equation f (t1 : : : tn ) = f (u1 : : : un ) is solved by solving t1 = u1 : : : tn = un . An equation v = t, where v is a variable and t is a term which does not contain v is solved with the binding v=t (in other words, v = t is already solved, since this represents a variable which is equated to a term not containing that variable). Equations like t = t can be deleted. If none of the above can be applied, the initial terms cannot be unied. The algorithm is written in terms of equality equations to emphasize the fact that Herbrand terms are just another kind of constraint system. Below are some examples of equating terms those examples have been directly taken from the toplevel of a CLP interpreter: ?- X = f(a, b). X = f(a, b) Equating X with f(a,b) is made by just binding X (a previously free variable) to f(a,b). ?- X = f(T, a), T = b. T = b, X = f(b,a) 3.11. STRUCTURED DATA AND DATA ABSTRACTION X. 49 In this case T is bound to b in turn T appears in the term f(T, a), which is equated to After performing all possible substitutions of variables, X becomes bound to f(b, a) ?- f(X, g(X, Y), Y) = f(a, T, b). X = a, Y = b, T = g(a, b) In this example we try to equate two terms. Both have the same toplevel functor (f/3), so that the subterms in corresponding argument positions are compared one by one, and all the resulting equations solved. First, X = a because of the rst argument. Then, T = g(X, Y), but, since X = a previously, in fact the equation generated is T = g(a, Y). In the last argument, Y = b. As Y appeared in a previous equation, this one is rewritten to T = g(a, b). The nal result is a system of equations in solved form : there are only variables at the left hand side of the equations, and none of them appear at the right hand side of the equations. This can be viewed as a binding of variables to terms. Note: choosing left hand side or right hand side is not important: the equations can be rotated. 3.11 Structured Data and Data Abstraction How can terms be used to construct data structures? A rst step is observing that terms can be as a whole bound to variables, and thus they can be passed to predicates as arguments. For example, a fact in a database of teacher, subjects, hours and classes, could be written as follows: course(comp logic, mond, 19, 21, 'Manuel', 'Hermenegildo', building 3, 1302). A call to this predicate is: ?- course(comp logic, Day, Start, End, C, D, E, F). in which we are only really interested in the day, start, and end hours of the course. Some arguments can be easily put together to make up more structured data: for example, name and surname, date, location. . . The clause can then be rearranged as follows: course(comp_logic, Time, Lecturer, Location):Time = time(mond, 19, 21), Lecturer = lecturer('Manuel', 'Hermenegildo'), Location = location(new, 1302). The constraints have been taken out of the head (remember Section 2.1.3) for the sake of clarity. A query to nd out the date, start, and end of the lecture, would be: ?- course(comp logic, Time, , ). (using the anonymous variable \ " to denote variables whose value we are not interested in, and thus they are not displayed at all) and the answer: CHAPTER 3. ADDING COMPUTATION DOMAINS 50 Time = time(mond, 19, 21) ) The previous examples use terms to implement records. This is one of the main (but not the only) ways of using terms to build data structures. We will develop a larger example of using terms to structure and hide data. We will start with a facts database about people and friendship relations among them: friends(peter, mark). friends(anna, marcia). friends(anna, luca). Some queries to this program be: ?- friends(anna, X). X = marcia X = luca ?- friends(X, anna). no The last answer is correct: although we intuitively think that if Anna and, say, Marcia are friends, then Anna is a friend of Marcia and Marcia is a friend of Anna, the program has no way of knowing this unless explicitly told|argument position matters. So we have to write another predicate which implements the symmetry of the friendship: are_friends(A, B):- friends(A, B). are_friends(A, B):- friends(B, A). Note that there are no constants in this predicate: only variable passing. Everything works as expected now: ?- are_friends(anna, X). X = marcia X = luca ?- are_friends(X, anna). X = marcia X = luca Some of the people in the friends database are married: married(couple(peter, anna)). married(couple(mark, kathleen)). married(couple(alvin, marcia)). Note that we are putting together the couple in a data structure: denes couples of persons: married/1 actually 3.11. STRUCTURED DATA AND DATA ABSTRACTION 51 ?- married(A). A = couple(peter,anna) A = couple(mark,kathleen) A = couple(alvin,marcia) Then, as before, we might want to know who is married to who: ?- married(couple(peter, S)). S = anna ?- married(couple(marcia, S)). no And we have a similar problem: couple/2 also keeps an order on the marriage. A possible solution is using a predicate which constructs / deconstructs couples : spouse(couple(A, B), A). spouse(couple(A, B), B). which says that \A is one of the spouses in the couple formed by A and B, and B is also one of the spouses in the same couple.". Then we can ask for marriages given only one of the spouses, and regardless of the order in which it appears in the denition of the couples: ?- spouse(C, peter), married(C). C = couple(peter,anna) ?- married(C), spouse(C, marcia). C = couple(alvin,marcia) ?- spouse(C, luca), married(C). no Last, we will dene conditions for going out to have dinner: two couples will have dinner together if spouses in the two couples are friends: go_out_for_dinner(Ma, Mb):married(Ma), married(Mb), spouse(Ma, A), spouse(Mb, B), are_friends(A, B). ?- go_out_for_dinner(A, B). A=couple(peter,anna), B=couple(mark,kathleen) A=couple(peter,anna), B=couple(alvin,marcia) A=couple(mark,kathleen), B=couple(peter,anna) A=couple(alvin,marcia), B=couple(peter,anna) Problem 3.11 Do repeated solutions appear? Why? CHAPTER 3. ADDING COMPUTATION DOMAINS 52 3.12 Structuring Old Problems Some examples already seen can be rewritten using data structures to increase modularity and to oer more information to the user. We will show an alternative implementation of the program which nds out inputs and outputs of electronic logic gates. The main dierence is that we will augment the program to keep track of the structure of the gate also. This structure will be returned to the user so that the basic components of every gate, and not only its connections, are known. Recalling Figure 2.3, we will add the names of the transistors and resistors to the database: resistor(r1, power,n1). resistor(r2, power,n2). transistor(t2, n3, n4, n2). transistor(t1, n2, ground, n1). transistor(t3, n5, ground, n4). We can now know what are the connections of every component. The rest of the program clauses relate the structure of gates with their inputs and outputs: inverter(inv(T, R), Input, Output):transistor(T, Input, ground, Output), resistor(R, power, Output). nand_gate(nand(T1, T2, R), Input1, Input2, Output):transistor(T1, Input, X, Output), transistor(T2, Input2, ground, X), resistor(R, power, Output). and_gate(and(N, I), Input1, Input2, Output):nand_gate(N, Input1, Input2, X), inverter(I, X). Queries can now return also the components of the gates: ?- and_gate(G, In1, In2, Out). G=and(nand(t2, t3, r2), inv(t1, r1)), In1=n3, In2=n5, Out=n1 3.13 Constructing Recursive Data Structures Terms can be used to construct data structures more complex than those we have been using so far. A (simply) recursive data structure is a data structure which has a eld which has a structure similar to the initial data structure. The simplest recursive data structure is the so-called Peano numbers. Peano numbers allow the modellization of natural numbers in a simple, homogeneous way, without actually dening dierent symbols for the digits. Peano numbers are constructed using these two rules: z is a Peano number (meaning zero, 0) 3.13. CONSTRUCTING RECURSIVE DATA STRUCTURES s(N) is a 53 Peano number if N is a Peano number (meaning the successor of N , i.e., N +1) The following Peano numbers symbolize what is usually written 0, 1, 2, 3, 4. . . : z, s(z), numbers can very easily be dened using terms, as every Peano number is, directly, a rst-order term. This code characterizes Peano numbers: s(s(z)), s(s(s(z))), s(s(s(s(z)))).. . Peano natural(z). natural(s(N)):- natural(N). It is interesting to note that this denition is, actually, very similar to the second one given in Section 3.3. Testing for \zero" and \greater than zero" is automatically made through matching (z does not match s(z), and the same happens the other way around). Subtracting one to continue the recursion is also made implicitly, since when the argument of the predicate matches s(N), N is, by the denition of Peano numbers, the predecessor of s(N). As usual, this allows us to make queries to test and also to generate numbers: ?- natural(z). yes ?- natural(potato). no ?- natural(s(s(s(z)))). yes ?- natural(X). X = z X = s(z) X = s(s(z)) .. . ?- natural(s(s(X))). X = z X = s(z) X = s(s(z)) .. . All usual integer arithmetic operations can be dened using Peano numbers. For example, below we dene the addition of Peano numbers: plus(A, B, C) is true if A plus B equals C, and the three arguments are Peano numbers: plus(z, X, X):- natural(X). plus(s(N), X, s(Y)):- plus(N, X, Y). CHAPTER 3. ADDING COMPUTATION DOMAINS 54 Note the call to natural/1 in the rst clause, to ensure that in fact, the second and third arguments are Peano numbers. plus/3 implements the following two equations: 0+x = x (1 + y) + z = 1 + (y + z ) Adding one / subtracting one to a Peano number amounts to putting a functor s/1 around it, or to equate it with s(X), where X is the variable which will be bound to the number minus one. Since this is a logical denition, it can be used with dierent call modes: it can add, subtract, and decompose a number: ?- plus(s(s(z)), s(z), R). R = s(s(s(z))) ?- plus(s(s(s(z))), T, s(s(s(s(s(z))))). T = s(z) ?- plus(s(s(s(s(z)))), T, s(s(s(z)))). no ?- plus(X, Y, s(s(z))). X = z, Y = s(s(z)) X = s(z), Y = s(z) X = s(s(z)), Y = z Problem 3.12 Recall the even/1 program of Section 3.3. Write a version which uses Peano arithmetic. Problem 3.13 Dene the following predicates. All of them should use Peano arithmetic. which is true if X Y = Z . exp(N, X, Y), which is true if Y = X N . factorial(N, F), which is true if F = N !, i.e., F = 1 2 3 N . Note that factorial is commonly dened so that 0! = 1: respect this. minimum(A, B, M), which is true if M is the minimum of A and B. ltn(X, Y), which is true if X < Y . times(X, Y, Z), ) Commonly, predicates can be dened in several ways, all of them logically equivalent, but which may dier greatly in their performance. For example, let us have a look at a couple of denitions of X mod Y , dened as follows: rem(X, Y, Z) is true if there is an integer Q (for quotient) such that X = Y Q + Z and Z < Y , i.e., Z is the remainder of the integer division of X by Y . A straightforward translation is as follows: 3.14. RECURSIVE PROGRAMMING: LISTS 55 rem(X, Y, Z):- ltn(Z, Y), times(Y, Q, W), plus(W, Z, X). which actually works|but quite ineciently. A typical call with X and Y instantiated to Peano numbers rst generates Zs less than Y, and then pairs of numbers Q, W such that Y * Q = W, and after that, it is checked that W + Z = X. A much better (but less direct) implementation is the one below: rem(X,Y,Z):plus(X1,Y,X), rem(X1,Y,Z). rem(X,Y,X):- ltn(X, Y). The idea is subtracting Y from X until X is less than Y3 then X will be the remainder sought for. Note that plus/3 is used to perform a subtraction, and that both clauses are mutually exclusive: if X < Y, then it is not possible that X1 + Y = X (remember that we are dealing with natural numbers). This implementation is much more ecient than the rst one, as it does not perform a generate-and-test procedure: it goes straight down to the solution. Also, it does not suer from the problem of looping in the case of choosing the wrong branch to search, as it was the case of the rst implementation for certain calls. 3.14 Recursive Programming: Lists Lists are one of the most useful data structures. They are present as primitive constructs in many languages (e.g., virtually all functional and logic languages) and available as libraries in many others. Lists can be dened by the user as any other structure in CLP languages, but they appear so often that there is a special syntax for them. Formally, a list of elements is either the empty list (usually called nil and written ]), or an element consed (\put as head of") with another list. Thus a list is always either an empty list or a head followed by a tail. This is modeled using a functor of arity 2, called cons. The name of the functor is usually `.'. For example the list composed by the elements a, b and c is formally written .(a, .(b, .(c, ]))). The rst argument of each of the cons functor is the head of the list the second is the tail of the list. A list term is logically dened (and recognized) by the predicate is list/2, dened as follows: is_list(]). is_list(.(Head, Tail)):- is_list(Tail). This syntax for lists reects the logical idea, but it is not very readable nor descriptive for an intuitive use of lists. Furthermore, the dot is overloaded by its use as clause terminator, and should be written quoted. It is customary to use a combination of square brackets and the inx operator `j' to write lists. To make life easier, there is a special syntax for writing lists without having to separate explicitly the head(s) and tail(s). Table 3.3 shows examples of the three syntaxes. List matching behaves as in any other structure. Some remarks will help to understand the element syntax: 3 We are obviously abusing the notation for variables: each variable is dierent in dierent iterations. CHAPTER 3. ADDING COMPUTATION DOMAINS 56 Formal object .(a,# ]) .(a,.(b,# ])) .(a,.(b,.(c,# ]))) .(a,X) .(a,.(b,X)) Cons pair syntax \Element" syntax #aj# ]] a] #aj#bj# ]]] a,b] #aj#bj#cj# ]]]] a,b,c] #ajX] ajX] #aj#bjX]] a,bjX] Table 3.3: Syntaxes for lists and ajX] unify with X = b] (X is the tail of a list|another list itself). a] and ajX] unify with X = ] (the tail of the singleton list is always the empty list). a] and a,bjX] do not unify (since the rst list has one element, and the second one has, at least, two). ] and X] do not unify. a,b] With this notation, the denition of lists can be expressed with the following predicate: is_list(]). is_list(Head|Tail]):- is_list(Tail). ) A common operation is checking for membership in a list. The member/2 predicate is true if the rst argument is an element of the list which appears as second argument: member(Element, Element|List]). member(Element, AnElement|RestList]):- member(Element, RestList). And, as in other cases, it can be used to check membership, to return on backtracking all elements which are members of a list, or to force a list to have an element as member: ?- member(b, a, b, c]). yes ?- member(plof, a, b, c]). no ?- member(X, a, b, c]). X = a ? X = b ? X = c ? ?- member(a, a, X, c]). true X = a 3.14. RECURSIVE PROGRAMMING: LISTS Problem 3.14 What does the query member(gogo, Another useful predicate is append/3: append(A, by concatenating the lists A and B. The denition is: append(], X, X). append(X|Xs], Ys, X|Zs]):- 57 return? Why? B, C) is true if C is the list constructed L) append(Xs, Ys, Zs). and it can be used in multiple ways: ?- append(1, 2, 3], g, h, t], L). L = 1, 2, 3, g, h, t]. ?- append(T, g, h, t], 0, m, g, X, Y]). Y = t, X = h, T = 0, m]. ?- append(X, Y, f, p, r]). Y = f, p, r], X = ] Y = p, r], X = f] Y = r], X = f, p] Y = ], X = f, p, r]. A note on Prolog IV lists Prolog IV lists, apart from the usual behavior we have just sketched, can be constrained using special primitives: size/2, which relates a list with the number of its elements, o/3, which relates two lists with their concatenation, and index/3, which relates a list and a number with the element which is placed in the list at the position given by that number. o/3 is, in some sense, similar to append/3, but the crucial dierence is that, similarly to other constraints, it does not enumerate, but instead leaves a constraint among lists. o/3 can also be written using the notation. The examples below (specially the last one) will make this clearer: ?- Z = 1, 2, 3] o 4, 5, 6]. Z = 1, 2, 3, 4, 5, 6]. ?- 1, 2, 3, 4, 5, 6] = X o 4, 5, 6]. X = 1, 2, 3]. o/3 does not enumerate, though: ?- 1, 2, 3, 4, 5, 6] = X o Y. Y ~ list, X ~ list. But o/3 does constrain (and size/2 does, too): ?- 1|Xs] = Xs o \_], 4 ~ size(Xs). Xs = 1, 1, 1, 1]. Problem 3.15 Use append/3 to make the last query. Could you explain how the answer is reached in the constraints case? Try to reason without thinking of solvers: act as a solver, and perform a step by step reasoning. CHAPTER 3. ADDING COMPUTATION DOMAINS 58 ) We will look at another example of a useful predicate and two feasible implementations of it. Sometimes the order in a list is important (although the list could not be called ordered in the sense of the word sorted : its order may derive from other considerations, such as the order of words in a le), and a utility predicate in many cases is reverse/2, which relates a list with the result of traversing it from the last element to the rst. A rst possibility is reasoning that, if we have an empty list, the empty list is its own reversed list, and, if we have a nonempty list and take apart head and tail, reverse the tail (which is a simpler problem), and append the head at the end (for which we can use the append/3 predicate), then the original list will be reversed. Putting it in code: reverse(],]). reverse(X|Xs],Ys ):reverse(Xs,Zs), append(Zs,X],Ys). This is a correct denition, but it is very inecient: for every element in the list, the predicate has to reverse the corresponding tail, and then put that element at the end, which needs traversing the reversed list completely again. This makes this predicate to be quadratic with respect to the length of the rst argument. A better strategy is using a common technique called accumulation parameter : an extra parameter is internally used, in which the nal result is constructed. The original list is traversed and each element is pushed onto the argument which will be returned as nal solution: reverse(Xs, Ys):- reverse(Xs, ], Ys). reverse(], Ys, Ys). reverse(X|Xs], Acc, Ys):- reverse(Xs, X|Acc], Ys). Do not be ba&ed by the presence of reverse/2 and reverse/3: dierent arities dene dierent predicates. reverse/3 could have been called with a completely dierent name, but it is just not necessary. The second argument of reverse/3 is called with an empty list, and, at every recursion step, the rst element in the list to be reversed is pushed as rst element of that second argument. The result is that, when recursion nishes, the second argument contains the initial list, but reversed. It is then unied with the third argument, which holds the result and which is the same variable as the result variable in the toplevel call. Problem 3.16 What is the eciency, in time, of this second implementation, with respect to the length of the rst list? Lists are, without any doubt, the most useful data structure in CLP, and thus it is worth knowing how to use them, even if some of this knowledge might not always be necessary. Problem 3.17 Write denitions for the following predicates (previously dened predicates may be freely used): len(L, N): N is the length (using Peano arithmetic) of the list L 3.14. RECURSIVE PROGRAMMING: LISTS suffix(S, L): S is a sux of the list L prefix(P, L): P is a prex of the list L sublist(S, L): S last(E, L): E 59 is a sublist of the list L is the last element of the list L palindrome(L): the list L is a palindrome evenodd(L, E, O): for any list L, E is the list of the elements in even position (i.e., the 2nd, 4th, etc.), and O is the list of the elements in odd position (i.e., the 1st, 3rd, etc.) select(E, L1, L2): L2 is the ?- select(X, a,c,n], L). L = c ,n], X = a L = a, n], X = c L = a, c], X = n list L1 without one (any one) of its elements, E, e.g., Try to give as many solutions as you can, and pay attention to the dierences in performance. ) In many cases keeping items ordered in a list can be advantageous, because search time can be reduced insertion time, on the other hand, is slower, because the right place to insert an element must be found, while in an unordered list, a new list with an additional element can be constructed in constant time just by consing the new head (the element) with the tail (the previous list). We will assume that there is a generic predicate precedes/2 such that precedes(A, B) is true if A precede B in the desired order. For numeric elements, this amounts to an arithmetical comparison, but in arbitrary pieces of information it can require a more complicated implementation. The code for inserting a piece of information in an ordered list without repetitions is: insert_ordlist(Element, ], Element]). insert_ordlist(Element, This|Rest], This, Element|Rest]):precedes(This, Element). insert_ordlist(Element, Element|Rest], Element|Rest]). insert_ordlist(Element, This|Rest], This|NewList]):precedes(Element, This), insert_ordlist(Element, Rest, NewList). Problem 3.18 Write a variant in which repetitions are allowed. The code for searching an element in an ordered list can stop the search before going past the last item: when we nd an item which should be placed after the element we are looking for, we know that the sought for term is actually not present in the list. 60 CHAPTER 3. ADDING COMPUTATION DOMAINS search_ordlist(Element, Element|Rest]). search_ordlist(Element, This|Rest]):precedes(This, Element), search_ordlist(Element, Rest). 3.15 Trees We will now turn to a more sophisticated data structure: trees. We will actually only deal with binary trees, because other trees are easily derived. Binary trees are either an empty node, or a node which contains a piece of information, and from which two subtrees are hanging. At the moment we will not suppose any order in the elements of the tree. We will also use trees to exemplify the construction of data structures in CLP languages. In general, complex data structures are built using functors|much like records are used in C, C++, or Pascal. A signicant dierence is that there is no need to declare a type, since the structure of the data and the access to their elements is automatically performed by matching and unication. We will use the functor tree/3 as the basic structure for a nonempty node, and the constant void to denote empty nodes. The rst argument of tree/3 will be the element in the node, and the second and third ones will be the left and right subtrees, respectively. Thus, an expression like tree(hen, tree(cow, void, void), void) represents the tree depicted in Figure 3.4. hen cow Figure 3.4: A tree As we did with lists, we can write a predicate which is true if its argument is a tree, as we have agreed to represent it we do not pay any attention the the elements actually stored in the tree: is_tree(void). is_tree(tree(Info, Left, Right):is_tree(Left), is_tree(Right). Checking whether an element is stored or not in a tree is slightly more involved than in the case of the lists, since that element might be present in any of the two subtrees: dierent clauses are used for elements present in the root of the tree, in the left child, or in the right child: 3.15. TREES 61 tree_member(Element, tree(Element, L, R)). tree_member(Element, tree(E, L, R)):- tree_member(Element, L). tree_member(Element, tree(E, L, R)):- tree_member(Element, R). Note that there is no clause for the case of a void tree: if the tree is void, then the call to the predicate must fail, because there is no information in it. Not having a case for a void tree makes the call fail in this case. Updating a tree is also an interesting task: we want to dene a predicate update tree(OldElem, NewElem, OldTree, NewTree), whose meaning is quite obvious. The implementation resembles the code for tree member/2: update_tree(Old, New, tree(Old, R, L), tree(New, R, L)). update_tree(Old, New, tree(Other, R, L), tree(Other, NR, L)):update_tree(Old, New, R, NR). update_tree(Old, New, tree(Other, R, L), tree(Other, R, NL)):update_tree(Old, New, L, NL). Trees can be traversed, and the contents of their nodes stored in lists. We give below a predicate which relates a tree with a list which stores the items in this tree in the order in which they are found when the tree is traversed in preorder (root rst, then left child, then right child): pre_order(void, ]). pre_order(tree(X, Left, Right), X|Order]):pre_order(Left, OrdLft), pre_order(Right, OrdRght), append(OrdLft, OrdRght, Order). Problem 3.19 Dene, similarly to the example before, in_order(Tree, Order) post_order(Tree, Order) Problem 3.20 Make versions of the predicates in Problem 3.19 using Prolog IV's o/3 constraint. ) One of the good things about binary trees is that if they are kept ordered, searching is relatively cheap. In a well-balanced binary tree, where an order relation is dened among the items it contains (suppose a precedes/2 predicate, as in the lists case), searching for an item can be made in O(log n), where n is the number of nodes in the tree. This search procedure can be implemented as follows: 62 CHAPTER 3. ADDING COMPUTATION DOMAINS search_ordtree(Element, tree(Element, L, R)). search_ordtree(Element, tree(This, L, R)):precedes(Element, This), search_ordtree(Element, L). search_ordtree(Element, tree(This, L, R)):precedes(This, Element), search_ordtree(Element, R). We are assuming that items which precede a given element are stored in the left subtree. Conversely, the construction of the tree must respect this order, so, as it happened with lists, a new ordered tree cannot be built just by putting together two sons and a new piece of information: we want the resulting tree to be ordered, and without repetitions. The code for insert ordtree(El, Tree, NewTree) is: insert_ordtree(Element, void, tree(Element, void, void)). insert_ordtree(Element, tree(Element, L, R), tree(Element, L, R)). insert_ordtree(Element, tree(This, L, R), tree(This, NL, R)):precedes(Element, This), insert_ordtree(Element, L, NL). insert_ordtree(Element, tree(This, L, R), tree(This, N, NR)):precedes(This, Element), insert_ordtree(Element, R, NR). 3.16 Data Structures in General In general, all data structures (queues, stacks, n-ary trees, etc.) can be implemented using the ideas presented. Moreover, as all data is dynamic (in fact, there is no such concept as \static variable"), and the user does not need to worry about memory management, dangling pointers, and the like, more sophisticated data structures can be used. Once familiar with the language, the programmer is able to focus on using the best data for the application at hand. Garbage collection is also automatic, so no memory leaks are possible (and, if they happen, they are the language implementor's fault, and not the programmer's). More rened data structures are possible, using the advantage of having free variables inside the data: this leaves the interesting possibility of incrementally adding more items to the structure, and can be seen as \open pointers". Probably using them is not often necessary in CLP, where the focus is more in constraint solving than in building intricate data structure and coding rened algorithms|the solver already contains such algorithms. Notwithstanding, we will give two examples of using open data structures for problems already seen. Example 3.4 We have shown how to insert pieces of information in a sorted binary tree. This takes three arguments, the rst for the element to insert, the second for the old tree, and the third for the tree after insertion. Empty trees appeared as the constant void. We can use only two arguments by having free variables in the leaves, instead of void: when a leaf is reached, the variable is just further instantiated. insert_ordtree(Element, tree(Element, L, R)):- !. insert_ordtree(Element, tree(This, L, R)):- 3.16. DATA STRUCTURES IN GENERAL 63 precedes(Element, This), insert_ordtree(Element, L). insert_ordtree(Element, tree(This, L, R)):precedes(This, Element), insert_ordtree(Element, R). There is a trick and something not yet known. The `!' sign is a control primitive, which in this case means that \after trying this clause, do not try the others below: commit to the decision you have made, even if you fail in the program afterwards", and is called a cut. The trick is that, as the tree is traversed down, going left or right, using the second and third clauses, there must be a point in which the rst clause matches: either we have found the element we want to insert (and there is no need to insert it, then) or we have reached a leaf, which is a free variable, and then it is instantiated and matches the rst clause. This variable is bound to a tree/3 structure, with fresh, unbound variables for the right and left sons. Here is an example of using it: ?- insert_ordtree(k, X), insert_ordtree(l, X), insert_ordtree(a, X), insert_ordtree(f, X), insert_ordtree(z, X). X = tree(k,tree(a,_,tree(f,_,_)),tree(l,_,tree(z,_,_))) Example 3.5 One of the best uses of open data structures is called dierence lists. A difference list is a list which is expressed as the dierence between two lists. We will use the construction Prefix-Suffix to denote a dierence list with this in mind, a,b,c,d,e,f]-d,e,f] is representing the list a,b,c]. The interesting case is when the Suffix is a free variable, and this free variable can be instantiated to make the list grow at the tail without the need of appending. In fact, this can be seen in most cases as an constant-time append which does not need to traverse the whole list. If we have the construction a,b,c|X]-X, and we instantiate X to be d,e], then the Prefix will be instantiated to a,b,c,d,e] in constant time. We will use dierence lists to rewrite the pre order/2 program without the need of append at the end: pre_order(Tree, List):- pre_order_open(Tree, List-]). pre_order_open(void, List-List). pre_order_open(tree(X, Left, Right), X|PrefLeft]-FinalRest):pre_order_open(Left, PrefLeft-Pref), pre_order_open(Right, Pref-FinalRest). Some hints to understand this code: L-L is the dierence between two is ] in the closed lists case. identical lists: thus it represents the empty list, which The sux of the list in the rst recursive call to pre order open/2 is handed down to the second recursive call, in order for it to be instantiated. CHAPTER 3. ADDING COMPUTATION DOMAINS 64 We want the toplevel call to return a closed list (but we may choose not to do so, actually). For this, we specify that the result of calling pre order open/2 should have ] as sux. In real applications, and when this technique is fully understood, the prex and sux of the lists are usually passed as separate arguments. 3.17 Putting Everything Together We will now develop a couple of small examples in which we will mix constraint handling and the use of data structures. This is a combination available in all CLP systems, and increases the constraints part of the language with the possibility of structuring data at the same time, it allows the setting up of constraints among components of data structures. 3.17.1 Systems of Linear Equations Our rst example will use lists to construct a very simple interface to the constraint solver. In fact, this will not add anything to the capabilities of the solver: linear systems can be solved just by writing them in the prompt, like in ?- 3 * X + Y = 5, X + 8 * Y = 3. Y = 4/23, X = 37/23 But in a program we will probably want to manipulate the coecients and the solutions as a whole. Data structures will allow us to dene systems of equations in a simple way, and manage them as a data structure which can be handled, transformed, and accessed at will. We will rst code a procedure for making dot product of vectors, mathematically dened as : <n <n ;! < (x1 x2 : : : xn ) (y1 y2 : : : yn ) = x1 y1 + + xn yn The rst problem is how to represent vectors. A customary and easy representation uses lists, where each element is one of the coordinates of the vector. Therefore we can write our code as follows: prod(], ], 0). prod(X|Xs], Y|Ys], X * Y + Rest):prod(Xs, Ys, Rest). We are adopting the convention (very convenient, for our case) that multiplying two vectors of zero dimensions yields zero as result. The product of two vectors is then worked out by multiplying elements pairwise and adding this to the result of multiplying the elements in the rest of the vector. The code behaves as expected: ?- prod(2, 3], 4, 5], K). K = 23 3.17. PUTTING EVERYTHING TOGETHER 65 ?- prod(2, 3], 5, X2], 22). X2 = 4 Note that it multiplies, but it also nds values of coordinates which satisfy a multiplication. This can be directly used to solve equation systems: ?- prod(3,1], X,Y], 5), prod(1,8], X,Y], 3). Y = 4/23, X = 37/23. but it is not yet what is needed: processing each equation is done with a separate call, and free coecients are not grouped together. Another predicate, which takes a matrix of factors, a vector of variables, and a vector of free coecients will do the job: system(_Vars, ], ]). system(Vars, Co|Coefs], Ind|Indeps]):prod(Vars, Co, Ind), system(Vars, Coefs, Indeps). Matrices are expressed as lists of vectors, and vectors as lists of numbers. Calls to the predicate can be now made, and all the needed data is packed in separate data structures: ?- system(X,Y], 3,1],1,8]], 5,3]). Y = 4/23, X = 37/23. 3.17.2 Analog RLC circuits We will develop a simple program which receives a data structure (which we will write down directly, but which could be constructed from reading a description le) and information about the voltage, current, and frequency. The program will try to nd out values for the variables so that all these parameters match together. We will suppose the circuit is in steady state, so that transients have not to be considered. We will also consider that elements are connected either in series or in parallel, so that Ohm laws will suce|no Kircho analysis is needed. If you do not know what all this means, do not be intimidated: it is pretty easy. The entry point will be the predicate circuit(C, V, I, W), which states that across the network C (this is where the data structure goes), the voltage is V, the current is I and the frequency is W. Voltage and current are complex numbers: this is needed because we will be dealing with inductors and capacitors, which react dierently with dierent frequencies, and the frequency will be kept in the imaginary part. We will model complex numbers using the c/2 structure the number X + Y { will be represented as c(X, Y), and we will implement the addition and multiplication (which, since we are using constraints, implicitly perform subtraction and division) as c_add(c(Re1,Im1), c(Re2,Im2), R):R = c(Re1+Re2,Im1+Im2). c_mult(c(Re1,Im1),c(Re2,Im2),c(Re3,Im3)):Re3 = Re1 * Re2 - Im1 * Im2, Im3 = Re1 * Im2 + Re2 * Im1. 66 CHAPTER 3. ADDING COMPUTATION DOMAINS We will now have a look at composing the parts of the circuit. On one hand we have individual components which behave according to certain laws relating voltage, current, and frequency these components can be connected among them to build larger units, and these units can again be connected to make bigger circuits. We will use functors for each simple components, which will give their characteristics, as well as for grouping these elements together. The Ohm laws state that two circuits in series have the same current running through them, they have the same frequency, but the voltage in the endpoints is the sum of the voltages at the endpoints of both circuits: circuit(series(N1, N2), V, I, W):c_add(V1, V2, V), circuit(N1, V1, I, W), circuit(N2, V2, I, W). The situation for circuits in parallel is the complementary: the voltage at the endpoints is the same in both, and the same as in their connection in parallel, but the total current of the circuit is divided between them. The frequency is the same in both sub circuits: circuit(parallel(N1, N2), V, I, W):c_add(I1, I2, I), circuit(N1, V, I1, W), circuit(N2, V, I2, W). We now have to nd out how simpler components react to the conditions they are subject to. We will consider resistors, capacitors, and inductors: A resistor obeys the Ohm law V = I (R + 0i), where V is the voltage, I is the current, R is its resistance, and the frequency is not important. We will model a resistor with the structure resistor(R): circuit(resistor(R), V, I, W):c_mult(I, c(R, 0), V). Inductors follow the law V = I (0+ WLi), where W is the frequency and L the inductor's inductance. Similarly to resistors, they are modeled as circuit(inductor(L), V, I, W):c_mult(I, c(0, W * L), V). 1 And nally, capacitors meet the equation V = I (0 ; WC i), where C is the capacitance. The corresponding clause is circuit(capacitor(C), V, I, W):c_mult(I, c(0, -1 / (W * C)), V). Figure 3.5 shows a circuit, where \?" denotes unknown values. A query which models the circuit in a data structure and automatically nds out the unknown values is as follows: 3.18. SUMMARIZING 67 R=? C=? V = 4.5 ω = 2400 I = 0.065 L = 0.073 Figure 3.5: Modeling a circuit ?- circuit(parallel(inductor(0.073), series(capacitor(C), resistor(R))), c(4.5, 0), c(0.65, 0), 2400). R = 24939720/3608029, C = 3608029/2365200000. This solution is possible, of course, because the execution converts the traversal of the data structure into a set of equations which are linear and which can be solved directly. If the equation system were not linear, a more sophisticated solving method (for example, a nonlinear solver, probably with the help of some search method to isolate a solution) would be needed. The equations generated depend on the description of the circuit, which is input data. 3.18 Summarizing There is an obvious relation between C(L)P languages and Operations Research: results and algorithms from O.R. are vital, and are found at the heart of C(L)P languages, because the solving methods for constraint systems are most times taken from O.R. But there is also a good deal of implementation techniques (which we have not even mentioned, and which we will not study) which come from Computer Science, and do not make sense in a setting of static equations: incremental addition of constraints, propagation of values, and dierent solving heuristics based on the problem under consideration. The use of a programming language oers many advantages: explicit algorithms can be programmed if needed, equations can be set up (and changed) dynamically, the solvability of the constraints forces backtracking (and, thus, the removal of some constraints and the addition of some others) by failing when a non-consistent state is reached, and there is always the possibility of performing search among the possible values in the domain of the variables. This is the only way to reach a denite solution when the problem is underconstrained, or the constraint solver is too weak to solve them directly, or, simply, there is no known algorithm to work out a solution to the generated constraints. The use of the data structures of the language favors a more modular, portable way to attack problems, and assimilating language variables to constraint variables results in a clean semantics and in clear programs. Also, the rule-based programming in CLP allow the expression of algorithms in a declarative way which is often more compact, easier to understand, debug (because of, for example, the implicit memory management), maintain, and update. ) 68 CHAPTER 3. ADDING COMPUTATION DOMAINS The next chapter will deal with Prolog. Prolog is a constraint language over the domain of Herbrand terms, and since most CLP languages are based on extending Prolog, they inherit lots of builtin predicates and control expressions from Prolog: we will review them. And, nally, Prolog itself is a nice language for writing many applications. ) Chapter 4 The Prolog Language 4.1 Prolog Prolog is a logic language based on a subset of rst order logic. Some constructions of rst order logic have been removed to allow performance to be competitive, and some extralogical features (mainly related to ow control, input/output, and meta-programming) have been added. High performance Prolog compilers are available, and integration with other languages is not dicult. One of the main dierences with Logic Programming is the restriction of formulae to a special subclass, called Horn clauses, for which fast resolution procedures are known. Also, the way programs are executed has been xed by a rule establishing a left-to-right, depth-rst search. This has the drawback of being incomplete (there might be correct problem models which do not lead to solutions, but there are always alternative models which do result in the nding of a solution), but in turn allows ecient implementations. Most CLP systems are built extending Prolog, and their internal machinery is full of implementation techniques developed for Prolog. Thus it is not strange that there is a good deal of programming techniques, builtins, and miscellaneous facilities which are shared among Prolog and other CLP languages. Prolog IV itself can be put in a \ISO Prolog mode", in which only Prolog programs are accepted and executed|no constraints are available. This chapter will give some hints about Prolog programming and its general philosophy, plus a general discussion on several well-known Prolog builtin predicates. The reader is referred to a Prolog manual for the system of choice for a more detailed listing of the facilities available. 4.2 Control Annotation Control annotation allows the programmer to have some command on the execution ow of the program. There are three ways in which a given execution path can be forced by the programmer: Ordering of goals in a clause. Ordering of clauses in a predicate. Pruning operators. 69 CHAPTER 4. THE PROLOG LANGUAGE 70 We will describe the use of pruning operators later, as they are really additions external to a logical language, while the ordering of goals and clauses stem from decisions regarding mainly performance matters, and which happen to be also useful for controlling the execution ow. 4.2.1 Goal Ordering Execution of goals is always1 performed left-to-right. This allows the programmer to know the behavior of the program and the order in which variables will be instantiated. It also impacts the performance of the program|precisely by instantiating some variables to some values after or before some points. Consider the following piece of code: p(a):- <something really big>. p(b). q(b). which is called using these two dierent queries: ?- p(X), q(X). %% (1) ?- q(X), p(X). %% (2) (1) will execute the big chunk of code in the rst clause of p/1 to fail afterwards, and succeed with the second clause of q/1. (2) will instantiate X to b, and will not even try to execute the rst clause of p/1|so the eect is more profound than just reorganizing the order of the clauses of p/1. Moreover, the optimal ordering of goals depends ultimately on the query mode, i.e., the values the variables have at runtime. 4.2.2 Clause Ordering The order of clauses in a predicate determine the order in which alternative branches for a computation are taken. Therefore, in the case of several solutions, it determines the order in which these solutions are returned. Compare the code p(a). p(b). p(c). with the code p(b). p(a). p(c). Not always: most Prolog and some CLP systems have means to declare predicates to be concurrent: calls to them are delayed until some conditions are met, and they are resumed when these conditions hold. 1 4.3. ARITHMETIC 71 In the former case, a query ?- p(X). will return the solutions X = a, X = b, X = c, and in the latter, the solutions will be returned as X = b, X = a, X = c. Therefore, putting the clauses more likely to lead to a solution in the rst place is sensible, because this would shorten the computation needed to reach this solution. In fact, a wrong order of clauses can lead to non termination: the following code n(s(X)):- n(X). n(z). loops ad innitum when the query ?- p(X). is issued. Switching the clauses n(z). n(s(X)):- n(X). returns an innite number of solutions (of course, there is an innite number of solutions). When all solutions are required, the whole search tree is explored, so if it is innite, the search will never end, yielding either an innite number of solutions, or falling into the socalled \innite failure": a branch which does not lead to a solution, but with a pattern which repeats itself. Pruning operators, to be discussed later, will help us in achieving more control on these cases. 4.3 Arithmetic Arithmetic in Prolog is, at most, pale in comparison with what is available in CLP systems, and resembles more what is available in common languages. It was designed not for constraint programming (which was not coupled with Prolog then), but rather for ease of implementation. The interface between arithmetics and the rest of the Prolog machinery is the evaluation of arithmetic terms. Certain terms (those constructed with designed functors, such as +/2, */2, etc., variables, and numeric constants), can be evaluated as arithmetic expression by some builtins, thus providing arithmetical operations. Table 4.1 shows some usually available functor names which are used to perform arithmetical operations, and Table 4.2 has some common builtins related to arithmetic. Note that functors are dened as operators, so that they can also be used inx / prex. Examples of correct arithmetical terms are 1 + 2, (56 // 4) mod (3 + 1), 45 / (X + 8) (if X is instantiated to a number at runtime otherwise an error is raised). Syntactically incorrect arithmetical terms are, for example a + 3 (since a is not an arithmetical term), or X + (1 / (3 * f(o, H))). The evaluation of arithmetical terms is performed via the predicate is/2: Z is T evaluates the arithmetical term T and the result is unied with the variable Z. If the unication fails, the predicate fails, and backtracking is forced. The following are examples of queries (which may be part of bodies of clauses): ?- X is 3 + 5. X = 8 ? yes CHAPTER 4. THE PROLOG LANGUAGE 72 Functor Meaning +/2 +/1 -/2 -/1 / / 2 // / 2 mod/2 log/1 Addition Positive prex (usually unneeded) Subtraction Negative prex Division Integer division Module Logarithm Table 4.1: Some arithmetic-related terms Functor is/2 =:= =n= <, >, =<, >= R^ole Evaluation Arithmetic equality Arithmetic inequality Order relationship Table 4.2: Some arithmetic-related builtins ?- X is 3 + 5, Y is X * X mod (X + 1). X = 8, Y = 1 ? yes ?- X is 3 + 5, Y is X * (X mod (X + 1)). X = 8, Y = 64 ? yes ?- X is 5 * (9 // 2), Y is (X * 2) // 3, X > Y. X = 20, Y = 13 ? yes ?- X is 5 * (9 // 2), Y is (X * 2) // 3, X =< Y. no ?- X is 5 * (9 // g). {ERROR: illegal arithmetic expression} ?- X is 32, X / 4 =:= (X // 3) -2. X = 32 ? yes When an error is found, the system usually aborts execution, instead of failing. 4.4. TYPE PREDICATES 73 4.4 Type Predicates The introduction of extra-logical predicates (such as, for example, the arithmetical ones just presented, and others we will see later), causes the need of testing the type of terms at runtime: we may want to check whether an argument is a number or not, to react accordingly at runtime. This is a feature not taken into account in formal logic, since the type of an object has, actually, no sense at all: all data in formal logic are Herbrand terms, and have no specic meaning per se. But, for practical reasons, it is often advantageous knowing when a variable has been bound to a number, or to a constant, or to a complex structure, or when it is still free. There are a number of unary predicates which deal with the types of terms Name integer(X) float(X) number(X) atom(X) atomic(X) Meaning X X X X X is an integer is a oating point number is a number is a constant other than a number is a constant Table 4.3: Predicates checking types of terms These predicates behave approximately as if they where dened via an innite set of facts. The dierence is that they do not enumerate, as facts would have done: when handed down a free variable, they fail (as they should, because a free variable is not a constant): ?- integer(3). yes ?- float(3). no ?- float(3.0). yes ?- atom(3). no ?- atom(logo). yes ?- atomic(logo). yes ?- atom(X). no ?- atomic(X). 74 CHAPTER 4. THE PROLOG LANGUAGE no These predicates can fail, but they cannot produce an error. In fact they are intended to be used before calling certain builtins so that no errors are raised. Also, they do not constrain, since they succeed only when the argument is instantiated to the type expressed by the predicate: they will fail when called with a free variable, or when a variable instantiated to a term not in such type. They can be used, for example, to restore some of the lost exibility to arithmetical predicates: plus(X, Y, Z):number(X), number(Y), Z is X + Y. plus(X, Y, Z):number(X), number(Z), Y is Z - X. plus(X, Y, Z):number(Y), number(Z), X is Z - Y. This predicate will succeed whenever called with two arguments instantiated to a number and the third being a free variable. It can fail if the three arguments are numbers, but the rst and the second do not add up the third and nally, it will not generate errors, and will not split a number in other two: ?- plus(4, 5, K). K = 9 ? yes ?- plus(X, 7, 3). X = -4 ? yes ?- plus(8, 7, 3). no ?- plus(8, must, must). no ?- plus(X, Y, 10). no 4.5 Structure Inspection Part of Prolog builtins are related to structure (functors) inspection: variables bound to structures can be accessed to nd out the functor name, its arity, a given argument, etc. The two basic predicates for doing that are functor/3 and arg/3. functor(F, N, A) succeeds when F is a complex structure whose arity is N and whose arity is A. It can be used to build new functors with fresh variables, or to obtain the name and arity of already built functors: 4.5. STRUCTURE INSPECTION 75 ?- X = corn(loki, K, straight), functor(X, N, A). A = 3, N = corn, X = corn(loki,K,straight) yes ?- functor(X, steam, 10). X = steam(_,_,_,_,_,_,_,_,_,_) arg(F, N, Arg) numbering at 1: succeeds when Arg is the N-th argument of functor F. Arguments start ?- arg(1, corn(loki, K, straight), A). A = loki ? yes ?- arg(2, corn(loki, K, straight), A). K = A ? yes ?- arg(0, corn(loki, K, straight), A). no ?- functor(X, steam, 10), arg(8, X, engine). X = steam(_,_,_,_,_,_,_,engine,_,_) ? yes Example 4.1 The above builtins can be used to test whether subterm contained in Term: SubTerm can be unied with a subterm(Term, Term). subterm(Sub,Term):functor(Term,F,N), subterm(N,Sub,Term). subterm(N,Sub,Term):arg(N,Term,Arg), subterm(Sub,Arg). subterm(N,Sub,Term):N>1, N1 is N-1, subterm(N1,Sub,Term). % N > 0 is traversed, element by element. If an argument of Term unies with SubTerm, then is already contained in Term (possibly after unifying one of its variables). Otherwise, the argument at hand is recursively traversed: Term SubTerm ?- subterm(f(g), h(11, oc, f(g)], loc)). yes 76 CHAPTER 4. THE PROLOG LANGUAGE ?- subterm(f(T), h(11, oc, f(g)], loc)). T = g ? no ?- subterm(f(T), h(11, oc, f(g)], I)). I = f(T) ? T = g ? no Problem 4.1 The above example instantiates variables, either in the containing or in the contained term, in order to satisfy the requirement of being contained. Where exactly in the code is this performed? We will see later a way to work around this, if it is not desired. ) Another example of the application of structure-inspecting primitives is to use them to implement arrays. Arrays themselves are not available as a Prolog datatype, with associated operations, but they are easily simulated with structures. Any element of a structure can be accessed using its position. The name of the functor does not matter, actually. As an example, we will implement the predicate add arrays/3 which will add the arrays passed in the rst and second argument, and will leave the result in the third argument. The functor name we have chosen for the arrays is array/3: add_arrays(A1,A2,A3):functor(A1,array,N), functor(A2,array,N), functor(A3,array,N), add_elements(N,A1,A2,A3). add_elements(0,_A1,_A2,_A3). add_elements(I,A1,A2,A3):arg(I,A1,X1), arg(I,A2,X2), arg(I,A3,X3), X3 is X1 + X2, I1 is I - 1, add_elements(I1,A1,A2,A3). %% Equal length %% I > 0 The code rst checks that the three arguments have the same functor name and arity then the arrays are traversed from the end to the beginning (to use only one index, stopping at 0), and the corresponding elements in the arrays are added. Note: some Prolog (and CLP) systems have a maximum xed arity. Other implementation schemes (lists, for example, as done in the CLP arrays multiplication in Section 3.17.1) should be used for simulating larger arrays. 4.6. INPUT/OUTPUT 77 Problem 4.2 Write an add matrices/3 predicate which can add matrices of arbitrary dimensions. Matrices will be represented using the functor mat/3, and are implemented by allowing the arguments of mat/3 to be themselves matrices, and not only numbers. For example, the structure mat(mat(1, 2, 3), mat(4, 5, 6), mat(7, 8, 9)) would represent the matrix 01 @4 2 3 5 6 7 8 9 1 A I.e., its behavior should be as follows: ?- add_matrices(mat(mat(1, 2), mat(4, 3)), mat(mat(7, -2), mat(10, 4)), X). X = mat(mat(8, 0), mat(14, 7)) It should fail if the matrices to add do not have the same dimensions or the proper functor name. For simplicity, a number itself can be considered a matrix, so the query and answer ?- add_matrices(7, 4, X). X = 11 are both legal. ) There is also a utility predicate which converts (in a quite bizarre way) lists into structures and vice versa. The \conversion" is done as follows: the name of the structure is the rst atom in the list, and the rest of elements of the list are the arguments of the structure. It is called univ, and its predicate name is =.., which is also dened as an inx operator: ?- date(9,february,1947) =.. L. L = date,9,february,1947]. ?- X =.. +,a,b]. X = a + b. This builtin should be avoided unless really necessary: it is expensive in time and memory, and most time using it is a last resort for badly designed data structures and/or programs. 4.6 Input/Output The easiest way of doing input/output in Prolog is using the so-called DEC-10 predicates. They are based on the idea of having a current input and output, which can be redirected to write to and read from les. The basic DEC-10 I/O predicates are shown in Table 4.4. There are more sophisticated I/O predicates based on opening and closing streams explicitly: handles to the les are returned, which can be passed to the I/O predicates. The CHAPTER 4. THE PROLOG LANGUAGE 78 Predicate write(X) nl read(X) put(N) get(N) see(File) seeing(File) seen tell(File) telling(File) told Explanation Write the term X on the current output stream. Start a new line on the current output stream. Read a term (nished by a full stop) from the current input stream and unify it with X. Write the ASCII character code N. N can be a string of length one. Read the next character code and unify its ASCII code with N. File becomes the current input stream. The current input stream is File. Close the current input stream. File becomes the current output stream. The current output stream is File. Close the current output stream. Table 4.4: DEC-10 I/O predicates interface is similar to what is provided by most operating systems, and available in many programming languages. All I/O predicates perform side-eects : they change the state of the world (changing the contents of the screen or a disk le, in this case broadcasting messages over the net, if writing / reading is made on a socket stream) in such a way that persists even after backtracking. Side-eects predicates are not easily formalized from a logical point of view, because the state of the whole world has to be taken into account. 4.7 Pruning Operators: Cut The cut is one of the Prolog operators related to the program control ow. It can be placed anywhere a goal can, and it is written as the predicate !/0. Technically, the cut commits the execution to all the choices made since the parent goal was unied with the head of the clause in which the cut appears. This means that all clauses below the one with the cut are discarded, as if they did not exist for this particular call (so they are not considered if the execution of the current clause fails, in which case the call to the predicate fails), and all the alternatives left by the execution of the goals at the left of the cut in that clause are also discarded. The goals at the right of the cut are executed normally (i.e., they can backtrack). Figure 4.1 shows the eect of the cut in the code below, with the query ?- s(A), p(B, C).: s(a). s(b). r(a). r(b). p(X,Y):- l(X). p(X,Y):- r(X),!,..... p(X,Y):- m(X)........ (note the cut in the second clause of p/2). The parts of the tree outlined with a dashed loop are not explored. After traversing the subtree generated by the rst clause of p/2 (regardless 4.7. PRUNING OPERATORS: CUT 79 s(A),p(B,C) A/b A/a p(B,C) l(B) r(B),!,.... B/a B/b !,..... !,.... p(B,C) m(B) r(B),!,.... l(B) B/a !,... m(B) B/b !,.... Figure 4.1: Eects of cut it has solutions or not, but supposing backtracking is made into the second clause of that predicate), a solution for the call to r/1 is found. Then the cut is executed, which has two eects: The third clause for p/2 is not taken into account. The second clause of r/1 is not taken into account (for this call). If the predicates after the cut fail, the whole call to p/2 will fail, because the last clause will not be taken into account, nor the second clause of r/1. ) Cuts are meta-logical predicates, which have a non-local eect on the computation: in the previous example, the call to r/1 did not respect the usual backtracking semantics, because only one solution is returned, but other calls outside the scope of a cut in the same program would have had a normal behavior: thus, the cut aects the behavior of predicates whose implementation does not imply that. Cuts, according to the way they aect the program execution, can be divided in several types, regarding their logical safeness, i.e., how much they change (if at all) the logical reading of the program. White cuts are those which do not discard solutions. They improve performance because they avoid backtracking (which should fail, anyway), and they, in some Prolog implementations, avoid creating choicepoints at all. Their use in CLP is not always as clear, though. An example of white cut is: max(X,Y,X):- X > Y,!. max(X,Y,Y):- X =< Y. The two tests are mutually exclusive: since (because of the way arithmetic works in Prolog) both X and Y must be instantiated to numbers, if the rst clause succeeds (which will 80 CHAPTER 4. THE PROLOG LANGUAGE happen if the cut is reached), then the second will not conversely, if the second clause is to succeed, then the rst one could not have succeeded, and the cut in it would not have been reached. In a CLP language, however, since instantiation of variables is not necessary (the predicate can just constrain and backtrack upon failure), the cut would break the declarative semantics. Green cuts are those which discard correct solutions which are not needed. Sometimes a predicate yields several solutions, but one is enough for the purposes of the program|or one is preferred over the others. Green cuts discard solutions not wanted, but all solutions returned are correct. For example, if we had a database of addresses of people and their workplaces, and we wanted to know the address of a person, we might prefer his/her home address, and if not found, we should resort to the business address. This predicate implements this query: address(X, Add):- home_address(X, Add), !. address(X, Add):- business_address(X, Add). Another useful example is checking if an element is member of a list, without neither enumerating (on backtracking) all the elements of a list nor instantiating on backtracking possible variables in the list. The membercheck/2 predicate does precisely this: when the element sought for is found, the alternative clause which searches in the rest of the list is not taken into account: membercheck(X, X|Xs]):- !. membercheck(X, Y|Xs]):- membercheck(X, Xs). Again, it might be interesting in some situations, mainly because of the savings in memory and time it helps to achieve. But it should be used with caution, ensuring that it does not remove solutions which are needed. Red cuts, nally, both discard correct solutions not needed, and can introduce wrong solutions, depending on the call mode. This causes predicates to be wrong according to almost any sensible meaning. For example, if we wanted to know how many days there are in a year, taking into account leap years, we might use the following predicate: days_in_year(X, 366):- number(X), leap_year(X), !. days_in_year(X, 365). The idea behind is: \if X is a number and a leap year, then we succeed, and do not need to go to the second clause. Otherwise, it is not a leap year". But the query ?- leap year(z, D) succeeds (with D = 365), because the predicate does not take into account that, in any case, a year must be a number. It is arguable that this predicate would behave correctly if it is always called with X instantiated to a number, but the check number(X) would not be needed, and correctness of the predicate will then be completely dependent on the way it is called|which is not a good way of writing predicates. Another example is the following implementation of the max/3 predicate which works out the maximum of two numbers: 4.8. META-LOGICAL PREDICATES 81 max(X, Y, X):- X > Y, !. max(X, Y, Y). The idea is: if X > Y, then there is no need to check whether X =< Y or not, hence the cut. And, if the rst clause failed, then clearly the case is that X =< Y. But there are two serious counterexamples to this: the rst is the query ?- max(5, X, X)., which succeeds binding nothing (instead of failing or giving an error, which would in any case be a better behavior, at least indicating that there has been a call with a wrong instantiation mode). A possible argument against of this counterexample is that it is violates the supposedly allowed \call modes" (i.e., trying to nd the maximum of two numbers, one of which is not instantiated), but good programs (logic programs or not) should exhibit a sensible behavior no matter what input is received. In any case, the second counterexample does not violate any sensible assumption: the call ?- max(5, 2, 2). succeeds instead of failing, because the rst head unication fails and the second succeeds! What happens here is a case of the so-called \output unication": there are unications made before the cut, which means that data is changed prior to the tests which determine if the (rst, in this case) clause is the right one or not. Changing the program to max(X, Y, Z):- X > Y, !, X = Z. max(X, Y, Y). will make the predicate behave correctly in both counterexamples (giving an error in the rst, failing in the second). 4.8 Meta-Logical Predicates Prolog includes some meta-logical predicates (predicates which cannot be modeled in rstorder logic) because they make programming simpler, and they allow the users to have more control on the program executions, controlling clause execution and restoring exibility to programs using certain builtins. We are listing some of them in Table 4.5. Name Meaning var(X) nonvar(X) ground(X) X X X is currently a free variable is not a free variable is a term not containing variables Table 4.5: Some meta-logical Prolog predicates They never cause error, or instantiate variables, but the state of variables can be inspected safely. They do not have a rst-order reading, since the ordering of the goals matters for them: ?- var(X), X = 3. yes ?- X = 3, var(X). no CHAPTER 4. THE PROLOG LANGUAGE 82 ) Although programs usually sort numbers, or strings, or similar entities, Prolog has a notion of a so-called standard order among all terms. This means that, apart from the arithmetical order among numbers, any two terms (being them atoms, structures, variables, numbers, etc.), can be compared for equality, disequality, and precedence. Of course this order is somewhat arbitrary, but it is usually adequate for most applications|in fact, since we are imposing an ordering among heterogeneous entities, either this ordering is highly application-dependent, or it is used just for the sake of keeping those items sorted somehow. Standard order checking primitives are shown in Table 4.6. It is interesting to note that the identity comparison == / 2 compares variables without binding them. In fact, it does not report two variables being equal unless they are the same: ?- X == Y. no ?- X = Y, X == Y. Y = X ? yes Example 4.2 The following chunk of code can maintain an ordered list of terms, possibly including variables, numbers, atoms, etc. insert(List, Term, NewList) adds Term to List (ordered and without repetitions) to obtain NewList, also ordered without repetitions. insert(], It, It]). insert(H|T], It, H|T]):- H == It. insert(H|T], It, It, H|T]):- H @> It. insert(H|T], It, H|NewT]) :H @< It, insert(T, It, NewT). Note the use of == instantiating them. / 2 to check for identity, so that variables can be added without further Example 4.3 In Sections 3.14 and 3.15 we assumed a precedes/2 implementation-dependent predicate. For a general sorted tree implementation, the standard order predicate be used. Name == / 2 n== / 2 @> / 2, @>= / 2, @< / 2, @=< / 2 Meaning Identity of terms Nonidentity of terms Precedence comparison Table 4.6: Predicates which implement standard order The order among terms is the following: @< / 2 can 4.9. META-CALLS (HIGHER ORDER) 83 Variables, oldest rst. The order is not related to the names of variables. Floats, in numeric order. Integers, in numeric order. Atoms, in alphabetical order. Structures, ordered rst by arity, then by the name of the principal functor, then by the arguments left-to-right. Example 4.4 In Example 4.1 we saw how to check a term for uniability of one of its subterms with another given term. We might not want to instantiate any term. This is achieved by changing the two clauses of subterm/2 to: 1. 2. 3. 4. 5. subterm(Sub,Term):- Sub == Term. subterm(Sub,Term):nonvar(Term), functor(Term,F,N), subterm(N,Sub,Term). 4.9 Meta-calls (Higher Order) Meta-calls allow performing \on the y" calls to terms which, if they correspond literally to the name of a program predicate, will be executed as program code. The basic metacall predicate is call/1, which accepts a term and calls it as if it appeared in the program text. Thus, call(p(X)) is equivalent to the appearance of p(X) in the program text. The argument X of call(X) (and this is really where the power of meta-calls is) does not need to be explicitly present in the source code, but only correctly instantiated at run-time. Example 4.5 The following code implements a nave apply/2 which takes as argument an atom (which should be a predicate name) and a list of terms (which are intended to be the arguments of the predicate) and makes the corresponding call (i.e., apply(foo, 1, X, best])) makes the call foo(1, X, best): apply(Atom, ListArgs):Term =.. Atom|ListArgs], call(Term). (Incidentally, this is one of the few cases where the use of =.. / 2 is justied). This code allows constructing calls to predicates which are not known at compile time. The more important use of meta-calls is to implement general predicates which perform tasks using the result of calls as data. The best known of them are the all-solutions predicates and the negation. ) \All solutions" predicates gather in a list all the solutions to a query. The more relevant are findall/3, bagof/3, and setof/3. CHAPTER 4. THE PROLOG LANGUAGE 84 leaves in List an instance of We will use the following program as example: findall(Term, Goal, List) p(a, p(b, p(1, p(2, p(3, Term for each success of Goal. a). a). 1). b). 1). The following queries collect all solutions for calls to p/2. Note that duplicates appear in the solutions list: ?- findall(A, p(A, B), L). L = a,b,1,2,3] ? yes ?- findall(B, p(A, B), L). L = a,a,1,b,1] ? yes ?- findall(A, p(A, 1), L). L = 1,3] ? yes ?- findall(example(A, B), p(A, B), L). L = example(a,a),example(b,a),example(1,1),example(2,b),example(3,1)] ? yes will return the empty list if no solutions are found for the goal. It ignores all variables in the query which do not appear in the term whose instances are collected (i.e., solutions are gathered for all bindings of these variables). The predicate bagof/3 allows (selective) backtracking on the free variables of the goal: findall/3 ?- bagof(A, B = 1, L B = a, L B = b, L p(A, B), L). = 1,3] ? = a,b] ? = 2] ? no ?- bagof(A, B^p(A, B), L). L = a,b,1,2,3] ? In the second case we are explicitly signaling that we do not want to take into account backtracking for dierent bindings of B. Also, bagof/3 will fail (instead of reporting an empty list) if the called predicate fails. Last, the meta-predicate setof/3 behaves as bagof/3, but in addition the returned list is sorted and has no repetitions. Note that no bindings are returned for the variables appearing in the rst argument of all three predicates. 4.10. NEGATION AS FAILURE 85 4.10 Negation as Failure Negation in Prolog is implemented based on the use of cut. Actually, negation in Prolog is the so-called negation as failure, which means that to negate p one tries to prove p (just executing it), and if p is proved, then its negation, not(p), fails. Conversely, if p fails during execution, then not(p) will succeed. The implementation of not/1 is as follows: not(Goal) :- call(Goal), !, fail. not(Goal). (fail/0 is a builtin predicate which always fails. It can be trivially dened as fail:- a = b.) not/1 is usually available as the (prex) predicate n+ / 1 in most Prolog systems. I.e., not(p) would be written n+ p . Since not(p) will try to execute p, if the execution of p does not terminate, the execution of not(p) will not terminate, either. Also, since not(p) succeeds if and only if p failed, not(p) will not instantiate any variable which could appear in p. This is not a logically sound behavior, since, from a formal point of view, not(p) should succeed and instantiate variables for each term for which p is false. The problem is that this will very likely lead to an innite number of solutions. But using negation with ground goals (or, at least with calls to goals which do not further instantiate free variables which are passed to them) is safe, and the programmer should ensure this to hold. Otherwise, unwanted results may show up: unmarried_student(X):not(married(X)), student(X). student(joe). married(john). This program seems to suggest that joe is an unmarried student, and that joe is not an unmarried student, and indeed: ?- unmarried_student(joe). yes ?- unmarried_student(john). no But, for logical consistence, asking for unmarried students should return joe as answer, and this is not what happens: ?- unmarried_student(X). no The reason for this is that the call to not(married(X)) is not returning the students which are not married: it is just failing because there is at least a married student. CHAPTER 4. THE PROLOG LANGUAGE 86 Problem 4.3 Change the three queries shown above. unmarried student/1 predicate so that it works correctly in the ) The use of cut and a fail in a clause forces the failure of the whole predicate, and is a technique termed cut-fail. It is useful to make a predicate fail when a condition (which may be a call to an arbitrary predicate) succeeds. An example of cut-fail combinations is implementing in Prolog the predicate ground/1, which succeeds if no variables are found in a term, and fails otherwise. The technique is recursively traversing the whole term, and forcing a failure as soon as a variable is found: ground(Term):- var(Term), !, fail. ground(Term):nonvar(Term), functor(Term,F,N), ground(N,Term). ground(0,T). ground(N,T):arg(N,T,Arg), ground(Arg), N1 is N-1, ground(N1,T). 4.11 Dynamic Program Modication Prolog programs can modify themselves while running: clauses can be added and removed at runtime. Normally this is not allowed, and clauses which will be changed must be marked specically, in order for the system to compile them in a special way. This very powerful feature must be used very carefully, as reasoning about a program which changes while it is running is not easy at all. Sometimes this is quite useful, but most of the times this is a mistake: the code becomes hard to maintain, and, since (a part of) the compiler has to be invoked, it is quite slow. Furthermore, the standard semantics for program modication states that no modication to a predicate becomes active until the calls to that predicate have nitely failed. Program modications can be justied, however, when used as a global switch: for example, asserting a fact which drives some options in a program, and which is consulted scarcely at some points in the program. There is also a logical justication (which might be used sometimes) to self-modication of code: when the clauses asserted are logical consequences from the program (this is called memoization ), or when the clauses retracted are redundant. The two main predicates (but there are more) for assertion and retraction are called assert/1 and retract/1. Their use is exemplied in the code below: :- dynamic related/2. relate_terms(X, Y):- 4.12. FOREIGN LANGUAGE INTERFACE 87 assert(related(X, Y)). unrelate_terms(X, Y):retract(related(X, Y)). The rst clause is called with two terms as arguments, and it simply adds the fact where X and Y are the terms. The second clause just retrieves the fact: related(X, Y), ?- related(X, Y). no ?- relate_terms(a, b). yes ?- related(X, Y). X = a, Y = b ? yes ?- unrelate_terms(a, b). yes ?- related(X, Y). no ?- relate_terms(a, b). yes ?- relate_terms(c, f). yes ?- related(X, Y). X = a, Y = b ? X = c, Y = f ? no Rules can also be asserted and retracted in this case, for syntactical reasons, they must be surrounded with parentheses, e.g., assert((a:- b, c)). Asserted code is usually slower than normal, compiled code. 4.12 Foreign Language Interface Interfaces to other languages are available for virtually all commercial Prolog systems. They dier on the implementation, but the idea is shared among them: linking an object le to the executable of the Prolog engine, and then using an internal convention to call from Prolog and to pass and retrieve data. Calling Prolog from other languages is also possible: the Prolog engine is stored as a library which is linked against the program which will use it. As an 88 CHAPTER 4. THE PROLOG LANGUAGE example, we will make a low level, complete developing of an example of accessing to a UNIX standard library. The library we will use is the so-called curses library, which allows cursor control in a variety of terminals, independently of the actual terminal used. There is a lot of functionality in that library, including making text-based subwindows, etc. But, in order to keep the example short, we will make interfaces only to two functions: initsrc(), which initializes the library, and move(), which positions the cursor at the coordinates given. We will access those C functions using the Prolog predicates init term/0 and tmove/2. The rst step is writing some C glue code: #include <curses.h> static WINDOW * prolog_window void init_term() { prolog_window = initscr() } void tmove(h, v) long h, v { move((int)h, (int)v) These C functions will be accessed from Prolog. init term() just calls initsrc() and saves the returned WINDOW * structure in a variable. This variable is not used anywhere else, but it could be needed if other library functions are accessed. This code is compiled to an object le (for example, term control.o), and that is all what is needed to do in the C side of the project. On the Prolog side we have to declare some things: which object le we want to link, which additional libraries are needed by that object le, if any (as in our case), which functions need to be called when the corresponding predicates are accessed, and how the data is to be converted (because Prolog data is usually stored dierently from data in other languages). Fortunately, almost all the low-level details are taken care of by Prolog. The necessary declarations are: foreign_file('term_control.o', init_term,tmove]). foreign(init_term, init_term). foreign(tmove, tmove(+integer, +integer)). The rst fact says that the object le term control.o has the C functions init term and tmove. foreign/2 keeps information about which C function should be called for every Prolog predicate, and which arguments the C functions expect to receive. All that is needed now is to call a builtin predicate which makes the linkage at runtime, and installs Prolog entries for these C predicates. This is done by calling ?- load_foreign_files('term_control.o'], '-lcurses', '-ltermcap']). either at the prompt or somewhere in the program. Note that it also provides names of libraries which are needed by the C code we have just written. ) Chapter 5 Pragmatics In this chapter we will briey discuss some programming tips and advanced features which are treated more in depth in specialized literature on constraint logic programming. Our aim is mainly to draw attention on their existence, because sometimes using them can make the dierence among a complicated, sluggish system, and a clean, neat one. 5.1 Programming Tips Control primitives should be used carefully, at least for a rst implementation: they can lead to incorrect programs in CLP more easily than in Prolog. This is so because some implicit assumptions on the classication of cuts for Prolog, which was based on the behavior of some builtins, cannot be extended to CLP. The Prolog-safe code for max/3 in Section 4.7, translated below to Prolog IV, is not safe any more: max(X,Y,X):- gtlin(X, Y),!. max(X,Y,Y):- lelin(X, Y). The fact is that the comparison performed by gtlin/2 can now succeed on two free variables, so on backtracking lelin/2 might be called as well|and this is disallowed by the cut. The following call exhibits a wrong behavior: ?- max(5, X, Y), X = 8. false. since the correct answer would have been X = 8, Y = 8. The programmer has to ensure that the proper instantiation mode is used when calling such predicates (which in fact breaks their declarative transparency), or be aware that answers can be lost, depending on the constraint system supported by the language. ) One of the initial tasks in CLP is making up a correct model of the problem. When coming to a neat model, people naturally try to be frugal in the use of relationships, and not to set up too many equations. This is a sensible advice in general, but for some cases putting redundant constraints is advantageous: the reason is that it shortens communication paths inside the solver, so that faster reductions are possible. As an example, if we have 89 90 CHAPTER 5. PRAGMATICS the constraints X > Y, Y > 0, X + Y = Z, then the constraint Z > 0 is implied by all of them but trying to assign a negative number to Z and failing takes some propagation steps which would not be needed if the (redundant) constraint Z > 0 were directly added to the system: this is called overconstraining. Its impact in the execution time depends heavily on the actual language and its implementation, but in general it can be tried if the problem to be solved is very complex, or if the constraint solver is weak. Excessive overconstraining can be negative, though: too much equations, many of which are redundant, add up to the amount of information to be processed. 5.2 Controlling the Control Very often the programmer knows quite a bit about when parts of a program can be executed, due to the state of instantiation of data. Some CLP (and Prolog) systems include concurrencyrelated primitives which allow delaying the execution of user predicates until certain conditions (usually related to the instantiation state of the variables) hold: block and wait declarations are particular cases of a more general :- delay Goal until Condition. declaration. The allowed Conditions dier between systems. With such a declaration, a predicate like plus/3 in Section 4.4 could be put anywhere, provided that a condition stating that at least two of its three arguments must be numbers. This allows a simulation of constraint solving. In the same way, constraint languages can be augmented with concurrency for predicates involving operations which are not part of the constraint system, and which need a given call mode. In any case, having the possibility of some kind of concurrency is interesting, because concurrent predicates can act as daemons waiting for a condition on the variables to be triggered their use can range from I/O communication to error raising. ) Enumeration also impacts the performance of constraint programs. The order in which variables are enumerated, and which values in the variable are selected rst, impact greatly the amount of work (and, therefore, the time and memory spent) used to gure out a solution. Constraint languages usually allow the user to specify heuristics for these two possibilities. On one hand, it is important to cut search paths as fast as possible (this is a general rule in writing search procedures, and is termed the rst-fail principle). This is aected by the selection of the variable to enumerate, as setting value(s) for a variable simplies the constraint system and removes values from the other variable's domains. Selecting the most constrained variables rst for enumeration is a simple, sensible heuristic: these variables are more likely to aect the domains of other variables. Also, when selecting values for a variable, there are two general possibilities: enumerating values (for example, minimum to maximum, or the other way around), or setting a constraint (associated to a choicepoint) which splits the domain of the variable in two halves. The selection of the heuristics depends greatly on the problem and the relationships among the variables. 5.3. COMPLEX CONSTRAINTS 91 5.3 Complex Constraints Some languages include specialized primitives which set up complex systems of equations for solving problems which appear often (for example, scheduling tasks subject to a maximum instantaneous availability of resources). These primitives may also perform enumeration, often taking into account how the constraints have been set up, and trying to work out a solution as eciently as possible. Other complex constraints allow expressing simpler constraints as a special case. They allow also setting up in a simple, compact expression, constraints which would otherwise be verbose to construct. For example, the complex constraint #(L, c1 , ..., cn ], U), called cardinality operator, states that the number of true constraints in c1 , ..., cn is, at least L, and, a most U. The numbers L and U act as parameters which change the eect of the constraint: If L and U are equal to n, then all constraints must be true: this boils down to the conjunction of c1 : : : cn . If L and U are equal to one, then only one of the constraints can (and must) be true. if L and U are both zero, then none of the constraints can be true: this is tantamount to requiring the conjunction of the negated constraints to be true. A specially interesting constraint is the so-called disjunctive constraint: c1 _ c2 expresses that at least one of these constraints (c1 or c2 ) is true this is a particular case of the cardinality operator, when L is equal to one and U is equal to 2. It can also be written using a predicate with several clauses. However, disjunctive constraints may sometimes have advantages. The following predicate expresses that a number does not belong to the interval -1, 1]: n1(X):- X > 1. n1(X):- X < -1. If this predicate is called with its argument not denitely inside or outside that interval, and due to the constraint semantics, the execution does not actually select among the dierent clauses immediately, but a choicepoint is set instead, a constraint added, and the execution is continued. Backtracking may happen later if the alternative chosen was not the right one. A disjunctive constraint such as n1(X):- X > 1 _ X < -1. will add the disjunctive constraint, and execution will continue as long as any of the disjuncted constraints hold. In this case, a possibly expensive backtracking would have been avoided. ) 92 CHAPTER 5. PRAGMATICS Chapter 6 Conclusions and Further Reading C(L)P, as programming paradigm, is relatively novel, but it has its roots in a combination of programming concepts with the AI techniques of constraint solving and others from Operations Research. This provides mathematical tools of proven soundness, the possibility of programming (which oers modularity, encapsulation of data, and use of algorithms when available/advantageous), and built-in search if required. A variety of tools for C(L)P exist. Some have been described in part in the text, and the reader is referred to the seminar slides for an account of others. Also, many industrial applications developed using constraints technology. Being a new technology, there is learning curve involved with C(L)P: the techniques and approach to using constraint programming is dierent from, for example, procedural or object-oriented programming. However, in C(L)P the analysis-design-implementation process is supported in a much more exible way, and the nal products can evolve as the evolution of a series of prototypes, each one being more complete and robust than those preceding it. CLP oers clear advantages in many elds: the use of symbolic knowledge representation, possibility of writing rules and performing automatic search, together with the expressiveness of constraints and their automatic solving, allows the programmer to focus on the core of the programming task, leaving many tedious details to the implementation of the language. Quite importantly, the use of data structures in CLP languages relieves the programmer from dealing with pointers, indexes, etc., while at the same time allowing a clear view of the construction of data. ) Further information can be obtained in the following articles, books, and WWW repositories: Overview of the eld in the 10th Anniversary Special Issue of the \Journal of Logic Programming", May-July 1994, North-Holland #JM94]. Issues of the \Constraints Journal", published by Kluwer Academic. Review of the eld and future directions in ACM Computing Surveys #HSB+ 96]. Articles in IEEE Computer, Byte (February 1995), ... Documentation of ILOG Solver / COSYTEC / PrologIA / ... systems. 93 94 CHAPTER 6. CONCLUSIONS AND FURTHER READING COSYTEC (H. Simonis) ICLP'95 (MIT Press) / PAP (Royal Society of Arts) Tutorials. Series on \The Practical Application of Constraint Technology" (The Practical Application Company) #PAC]. Pascal Van Hentenryck's book #Van89]. Newsgroups comp.lang.prolog and comp.constraints. Several WWW sites related to constraints: { The page on constraints at the Oregon University (http://www.cirl.uoregon.edu/constraints/), where papers and pointers to many other pages are stored. { The Prolog Resource Guide at Carnegie Mellon (http://www.cs.cmu.edu/Web/Groups/AI/html/faqs/lang/prolog/prg/top.html) Look also in the bibliography section of this document. ) Chapter 7 Small Projects In this section we will develop four small projects: The blocks world: a program of relations in a world made up of blocks, using the simple constraint language presented in Section 2.1. DONALD + GERALD = ROBERT has the same idea as SEND + MORE = MONEY, which we have already seen, but it takes longer to solve: we will see how dierent constraint setups and enumeration heuristics aect the performance of the program. A very simple program to solve numerically dierential equations. A program to schedule a project, given a generic description of the tasks, their precedence, and their length. 95 CHAPTER 7. SMALL PROJECTS 96 7.1 The Blocks World g b a h c e i d f j Figure 7.1: A scenario in the blocks world Problem: For this project we will only need a language having the = constraint, meaning syntactic equality. Consider a world with blocks having the setup shown in Figure 7.1. We will identify blocks with the names appearing in the picture. The following predicates will model the world: on floor(B): B is leaning on the oor. on(B1, B2): block B1 is put directly on block B2. at left(B1, B2): blocks B1 and B2 are put on the oor, and block B1 is directly at the left of B2. The task to do is: 1. Write a database of facts which models the world in the picture. 2. Based exclusively on these facts, write the following predicates: base(B1, B2): B2 is the base of the pile containing B1. base at right(B1, B2): B1 and B2 are on the oor, and b2 is at the right (but perhaps not directly) of B1. object at right(B1, B2): B1 is in a pile which is at the right (but perhaps not directly) of B1. The above predicates must work for any world dened using the facts on floor/1, on/2, and at left/2. 3. There are several types of blocks, which can be piled or not on each other, depending on their physical shape. The following types of objects can appear: cubes, spheres, pyramids and toruses. We want to know if a conguration is physically stable using certain rules. A torus can be piled on any object, and, in fact, it is the only object which can be piled on a pyramid in that case, the top of the pyramid will stick out from the torus, and the only object which can be piled on that torus is another torus. Any object can then be piled on that second torus. In particular, a torus is the only object on which a sphere can be piled (oor included). Write a predicate which relates every object with its type, as shown below: / 2 7.1. THE BLOCKS WORLD 97 Object a b c d e f g h i j Type pyramid torus cube sphere cube torus torus sphere torus pyramid Using the type of each object and the predicates related to the position of objects in the world, write the following predicate: : the object O is placed unstably on its base in the conguration known by the program. unstable(O) Solution: The predicates which model the basic relationships of the world are easy to work out: on_floor(a). on_floor(d). on_floor(f). on_floor(j). on(c, on(b, on(e, on(i, on(h, on(g, d). c). f). j). i). h). at_left(a, d). at_left(d, f). at_left(f, j). The predicate base/2 traverses down the pile until the block directly on the oor is found: base(X, X):on_floor(X). base(X, Y):on(X, Z), base(Z, Y). base at right/2 follows the same idea as base/2, but it traverses the oor in search of contiguous blocks. We use the at left/2 predicate (which ensures that both blocks are on the oor) and the recursion stops when both blocks are directly one at the left of the other. CHAPTER 7. SMALL PROJECTS 98 base_at_right(X, Y):at_left(X, Y). base_at_right(X, Y):at_left(X, Z), base_at_right(Z, Y). Identifying X and Y such that the pile of X is to the right of that of Y is done by nding which object is at the bottom of each pile, and then ensuring that the rst base (Xb) is at the right of the second one (Yb). object_at_right(X, Y):base(X, Xb), base(Y, Yb), base_at_right(Xb, Yb). Relating the object with its type boils down to applying the techniques discussed in Section 2.6. Using a set of predicates of the form pyramid(a)., torus(b)., and so on, could have been possible, but in that case querying for the type of a block would not have been easy. type_of(a, type_of(b, type_of(c, type_of(d, type_of(e, type_of(f, type_of(g, type_of(h, type_of(i, type_of(j, pyramid). torus). cube). sphere). cube). torus). torus). sphere). torus). pyramid). The predicate for instability is the less straightforward one, due to the number of possible cases. We will divide the unstable objects in three cases, which summarize the possible non-stable congurations: 1. A sphere standing directly on the oor. 2. A sphere standing on a cube. 3. An object which is not a torus, and which is standing on a conguration which is convex we dene a conguration as being convex if there is no at surface at its top on which a non-torus can stand still (i.e., pyramids, spheres, and pyramids which have only one torus on top of them). no_torus(O):- type_of(O, pyramid). no_torus(O):- type_of(O, cube). no_torus(O):- type_of(O, sphere). convex(O):- type_of(O, pyramid). 7.1. THE BLOCKS WORLD convex(O):- type_of(O, sphere). convex(O):type_of(O, torus), on(O, O1), type_of(O1, pyramid). unstable(O):type_of(O, sphere), on_floor(O). unstable(O):type_of(O, sphere), on(O, O1), type_of(O1, cube). unstable(O):no_torus(O), on(O, O1), convex(O1). 99 CHAPTER 7. SMALL PROJECTS 100 7.2 A Discussion on DONALD + GERALD = ROBERT This puzzle, similar to SEND + MORE = MONEY, consists of trying to nd out integer values between 0 and 9 so that the arithmetical operation + D G R O E O N R B A A E L L R D D T makes sense. We will follow an approach similar to that of SEND + MORE = MONEY, but we will use Prolog IV, which will allow us showing how enumeration predicates can be built upon simpler ones. This will also show us how to use interval arithmetic in order to simulate nite domains, using the appropriate primitive constraints. We will as well explore how choosing the order of variables and the right primitive to enumerate will aect the total time of the search. Our rst program is as follows: dgr(X):X = D,O,N,A,L,G,E,R,B,T], allintin(X, 0, 9), gt(D, 0), gt(G, 0), all_diff(X), 100000.*.D .+. 10000.*.O .+. 1000.*.N .+. 100.*.A .+. 10.*.L .+. D .+. 100000.*.G .+. 10000.*.E .+. 1000.*.R .+. 100.*.A .+. 10.*.L .+. D = 100000.*.R .+. 10000.*.O .+. 1000.*.B .+. 100.*.E .+. 10.*.R .+. T, enumlist(X). Recall the SEND + MORE = MONEY program in Section 1.7, and pay attention to the similarities. Since we want to simulate FD with interval arithmetic, we are using the non-linear, intervals version of the arithmetic operations. Some predicates called in the code before have to be dened by the user (they are not directly available in Prolog IV, but their denition is not dicult): expresses that all variables in the list of the rst argument have integer values which are between a minimum and a maximum (the second and third arguments): allintin/3 allintin(], _Min, _Max). allintin(X|Xs], Min, Max):int(X), ge(X, Min), ge(Max, X), allintin(Xs, Min, Max). imposes the constraint that all elements in the list must be dierent. This is programmed using the dif/2 builtin which forces two terms to be dierent: all diff/1 7.2. A DISCUSSION ON DONALD + GERALD = ROBERT 101 all_diff(]). all_diff(X|Xs]):diffs(X,Xs), all_diff(Xs). diffs(_X, ]). diffs(X, Y|Ys]):dif(X,Y), diffs(X, Ys). performs a enumeration of the elements in the list by enumerating the variables in the order of the list. enumlist/1 enumlist(]). enumlist(X|Xs]):enum(X), enumlist(Xs). This program takes 134.3 seconds to solve the problem.1 Of course, this is quite high| but, on the other hand, the program is really simple. We may try to improve the performance of the program by making a smarter selection of order of enumeration of the variables: a feasible heuristic, as mentioned in Section 5.2, is enumerating rst the most constrained variables. Simply counting how many times a variable appears in the main equation of the problem allows us to sort the list of variables in this order: D, R, O, A, L, E, N, G, B, T], where variables which appear more often go rst. Setting X = D, R, O, A, L, E, N, G, B, T] at the beginning of the program cuts the execution time down to 28.2 seconds. Reversing this order (T, B, G, N, E, L, A, O, R, D]) increases the execution time to 36.8 seconds, which suggests that the most-constrained ordering of variables is not necessarily the winner, since the less-constrained order is not as bad as the quasi-random one we chose rst. Trying new orderings, and seeing which ones make sense, would need an auxiliary tool to help understand how constraint solving behaves these tools, usually graphical displays of the constraint solving, exist, but it is not a task of this introductory paper dealing with them. We will try two new variable orderings, in the hope that some light is shed on the direction of the optimal search path. First we will try the order D, T, L, R, A, E, N, B, O, G], i.e., enumerating the variables as they appear in a right-to-left column-by-column traversal of the operation (as it is done when making the addition by hand). The result is all but encouraging: this time nding the solution takes 369 seconds. The reverse ordering, (G, O, B, N, E, A, R, L, T, D]), is surprisingly good: the puzzle is solved in 15.8 seconds. A feasible explanation is that, since the leftmost digits are the ones which have more height in the whole operation, a wrong selection will be detected before. This may be true, but it is only partially exemplied in the ordering selected: due to the removing of duplicates in the list, the order of \less signicant digits before" reversed is not \most signicant digits before", but \less signicant digits after". The \most signicant digits before" is actually D, G, R, O, E, N, B, A, All the times reported in this section will refer to the nding of the rst solution, not to traversal of the whole search tree. Also, all programs were run in a SUN Sparc 10 with SunOS 4.1.3 and v1.0.1. 1 Prolog IV CHAPTER 7. SMALL PROJECTS 102 L, T], and using this enumeration order the execution time is lowered to a better mark of 7.1 seconds. Usually other enumeration primitives are available. In Prolog IV the builtin intsplit/1,2,3] performs a dynamic, intelligent (and, if desired, user-programmed) selection of the variable to be enumerated next. Using it does not help to achieve better results with the last ordering, which seems to be quite good, but it does help with other orderings: the results with the third ordering proposed (T, B, G, N, E, L, A, O, R, D]) results in an execution time of 1.1 seconds, although the time to explore the whole execution tree is quite high. ) It is possible to write an alternative set of constraints for the problem: instead of coding directly the desired equation, the manual carry-based algorithm can be coded as a set of equations. Carry is generated for each column, and added from the previous column: dgr(X):X = D, T, L, R, A, E, N, B, O, G], Carry = C1, C2, C3, C4, C5], allintin(X, 0, 9), gt(M, 0), gt(S, 0), allintin(Carry, 0, 1), all_diff(X), add_carry(0, D, D, T, C1), add_carry(C1, L, L, R, C2), add_carry(C2, A, A, E, C3), add_carry(C3, N, R, B, C4), add_carry(C4, O, E, O, C5), add_carry(C5, D, G, R, 0), enumlist(X), enumlist(Carry). add_carry(Ci, S1, S2, R, Co):- Ci .+. S1 .+. S2 = 10 .*. Co .+. R . The results with this coding are extremely good | much better than with the previous coding. In fact, the code above, with the rest of the program unchanged, runs in just 0.5 seconds. 7.3. ORDINARY DIFFERENTIAL EQUATIONS 103 7.3 Ordinary Dierential Equations Problem: Solve an ordinary dierential equation: y = f (x y) 0 using a numerical (e.g., trapezoidal) method. Is should be understood that there is a mathematical relationship between x and y, e.g., x = g(y), and this relationship is what we want to nd out numerically. Solution: Numerical methods return functions as an explicit set of pairs (x y) instead of as an analytic expression. We need to supply the boundaries x0 and xn 1 of the interval in which the function is to be calculated, and the number of points n to be taken into account in that interval. Thus, n is the number of integration steps, x0 and xn 1 the integration limits, and h = xn;n1 x0 the integration step|the distance between two consecutive sampling points. Then the equation yi+1 = yi + h f (xi yi ) + f2(xi+1 yi+1) can be used to numerically approximate the function y = g(x) (this is the expression of the trapezoidal method of integration). Note that the expression for yi+1 involves yi+1 itself. The eect of the above formula, applied to all the points in the integration segment, is to originate a set of equations for them to be solved we need at least one yi to be dened. The top level call gives us access both to y0 and yn 1 , but setting any yi will actually suce. Modelling the recurrence equation: ; ; ; ; yn1(H, Xi, Yi, Xi1, Yi1):Yi1 = Yi + H*(Fi+Fi1)/2, Xi1 = Xi + H, f(Xi, Yi, Fi), f(Xi1, Yi1, Fi1). The recurrent loop relates points of yi = g(xi ) using the previous predicate: loop(_H, Xn, Yn, Xn, Yn, Xn], Yn]). loop( H, Xi, Yi, Xn, Yn, Xi|Xs], Yi|Ys]):lelin(Xi, Xn), yn1(H, Xi, Yi, Xi1, Yi1), loop(H, Xi1, Yi1, Xn, Yn, Xs, Ys). In this predicate the rst clause implements the stop condition of the loop, and the recursive clause extends the recurrence equation to the selected interval: Stop condition: when the current pair (xi yi ) equals the boundary condition (xn yn ). If the constraint solver is not accurate enough in the mathematical operations, this stop condition could fail: adding xn n x0 with itself n times may not come out with the result xn ; x0 , due to approximation problems. This can be worked around by stopping the recurrence when the current x coordinate has gone beyond the upper boundary. ; CHAPTER 7. SMALL PROJECTS 104 The recursive clause relates (xi yi ) with (xi+1 yi+1 ) using the recurrence equation. The top level call computes the integration step, the list of x coordinates and the corresponding y values, and writes the result. We could return the results as two lists of x coordinates and y values, but we will do a little of formatting: sv(X0, Y0, Xn, Yn, N):H = (Xn - X0)/N, loop(H, X0, Y0, Xn, Yn, Xs, Ys), write_res(Xs, Ys). write_res(], ]). write_res(X|Xs], Y|Ys]):write(y(X) = Y), nl, write_res(Xs, Ys). To solve, for example, y = ;2xy, we just need to dene the relationship among x, y and f (x y): 0 f(X, Y, -2*X*Y). (the program above will work for other dierential equations just changing this clause). An example call, where the integration bounds are -3 and 3, twenty sampling points where used, 1 and g(;3) = 2000 is the following: ?- sv(-3, 1/2000, 3, Yn, 20). y(-3)=1/2000 y(-27/10)=1/200 y(-12/5)=181/5600 y(-21/10)=7783/51800 y(-9/5)=1268629/2382800 y(-3/2)=1268629/851000 y(-6/5)=36790241/10892800 y(-9/10)=625434097/99396800 y(-3/5)=79430130319/8150537600 y(-3/10)=4686377688821/370849460800 y(0)=510815168081489/37084946080000 y(3/10)=4686377688821/370849460800 y(3/5)=79430130319/8150537600 y(9/10)=625434097/99396800 y(6/5)=36790241/10892800 y(3/2)=1268629/851000 y(9/5)=1268629/2382800 y(21/10)=7783/51800 y(12/5)=181/5600 y(27/10)=1/200 y(3)=1/2000 Yn = 1/2000. 7.4. A SCHEDULING PROGRAM 105 7.4 A Scheduling Program We want to develop a program to schedule a project, decomposed in a set of tasks. Each task has successor tasks and a length. A high level view of the whole program has this layout: Construct the data structure dening the project this could be made reading from a external le, but for the sake of simplicity, we will store it as a fact in the program. The rest of the program works exactly in the same way if this structure is built from a external source. Some checks are made regarding the integrity of the data|i.e., we want the data describing the project to make sense. After that, the project data is used to generate the constraints which model the relationships among the tasks. Finally, the total time in the project is minimized, and the results are written to standard output. sched(P):project(P, Td), check_data(Td), build_constraints(Td, FinalTask, Dict), minimize(FinalTask, Dict), close_structure(Dict), write_results(Dict). The denition of the project is (for this example) just a fact with relates a project name (the non-imaginative a) with a list of tasks, which dene the initial and final tasks, and for each task, its name (an atom), its length, and the tasks which depend on it (atoms, again). This list will be used to built the constraint network and a dictionary where information about the tasks will be stored. The nal task has, associated to it, an absolute limit for the project span. project(a, initial(a), task(a,0,b,c,d]), task(b,1,e]), task(c,2,e,f]), task(d,3,f]), task(e,4,g]), task(f,1,g]), final(g,10), task(g,0,])]). Checking the correctness of the data is one of the less elegant parts of the program. Each element in the list is checked to make sure that it denes the initial task, the nal task, or an intermediate task. For each of them, we will also check that atoms appear where are expected, and that numbers appear where task lengths are expected. check_data(]). check_data(T|Ts]):check_datum(T), check_data(Ts). check_datum(Task):- 106 CHAPTER 7. SMALL PROJECTS Task = task(Name, Dur, Foll), check_atoms(Name], Task), check_number(Dur, Task), check_atoms(Foll, Task), !. check_datum(Initial):Initial = initial(Name), check_atoms(Name], Initial), !. check_datum(Final):Final = final(Name, Limit), check_atoms(Name], Final), check_number(Limit, Final), !. check_datum(What):write_atoms('Found ' ,What, ' (unknown).']). check_atoms(], _Where). check_atoms(A|As], Where):check_atom(A, Where), check_atoms(As, Where). check_atom(A, _Where):- atom(A), !. check_atom(A, Where):write_atoms('Found ', A, ' in ', Where, ', expecting atom.']). check_number(N, _Where):- number(N), !. check_number(N, Where):write_atoms('Found ', N, ' in ', Where, ', expecting number.']). write_atoms(]):- nl. write_atoms(A|As]):write(A), write_atoms(As). The process of building the constraints actually makes two things: it sets up the constraints themselves, but it also constructs a dictionary which relates the task (the Key of each dictionary entry) with the task's< Start and Length (the Value associated to the Key). This is implemented using an open list (a list whose tail ends in a free variable), so that only one argument has to be used for the dictionary. In the case of a larger project, it might be advantageous replacing it by a binary sorted tree. The predicate lookup/4 is the only entry point for the dictionary: it retrieves and, in case of non-existence, adds new items. lookup(Task, Start, Len, Dict):insert(Task, data(Start, Len), Dict). insert(Key, Value, pair(Key, ThisValue)|_Rest]):- !, Value = ThisValue. insert(Key, Value, _OtherPair|Rest]):- insert(Key, Value, Rest). 7.4. A SCHEDULING PROGRAM 107 As a utility predicate, and to make clearer the nal printing of the list of tasks, close structure/1 closes the dictionary, i.e., it will make the nal variable of the list a ]. close_structure(]):- !. close_structure(_|R]):- close_structure(R). The core of the program is the constraint generation. For each item in the project definition we add the corresponding constraint. Tasks are related one to each other through constraints which are actually put on the variables associated to the tasks names in the dictionary. The name of the nal task is returned, so that the minimization predicate can use it to reduce the length of the project as much as possible. The actions taken for creating the constraints are: The initial task is searched for in the dictionary, and simultaneously the associated Start time is set to zero. The nal task is looked up in the dictionary, and its end time is bounded using the limit which was associated to the project. For every other task (among which the rst and last task can also appear), the successor tasks are scheduled to be started after the current task is nished. Their names are looked up in the dictionary, and their start time are forced to be greater than the current tasks' nish time. build_constraints(], _FinalTask, Dict). build_constraints(Task|Tasks], FinalTask, Dict):add_constraint(Task, FinalTask, Dict), build_constraints(Tasks, FinalTask, Dict). add_constraint(task(Name, Len, Succ), _Final, Dict):lookup(Name, Start, Len, Dict), End = Start .+. Len, previous(Succ, End, Dict). add_constraint(initial(Name), _Final, Dict):lookup(Name, 0, _Len, Dict). add_constraint(final(Name, Limit), Name, Dict):le(End, Limit), End = Start .+. Len, lookup(Name, Start, Len, Dict). previous(], _End, Dict). previous(NextTask|Tasks], EndThisTask, Dict):lookup(NextTask, StartNextTask, _Len, Dict), ge(StartNextTask, EndThisTask), previous(Tasks, EndThisTask, Dict). Minimizing is made navely, which is enough for this application: the start of the last task (which has length zero) is forced to be at its minimum. In other cases special builtin predicates will have to be used. CHAPTER 7. SMALL PROJECTS 108 minimize(FinalTask, Dict):lookup(FinalTask, Start, _Len, Dict), glb(Start, Start). Finally, writing the results takes advantage of the structure of the dictionary, and dumps it in a more readable form: write_results(]). write_results(pair(TaskName, TaskData)|Ps]):TaskData = data(TaskStart, TaskLen), bounds(TaskStart, Lbound, Ubound), write_bounds(TaskName, TaskLen, Lbound, Ubound), write_results(Ps). write_bounds(Task, Le, L, L):write_atoms('Task ', Task, ' with length ', Le, ' starts at ', L, '.']). write_bounds(Task, Le, L, U):lt(L, U), write_atoms('Task ', Task, ' with length ', Le, ' can start from ', L, ' to ', U, '.']). And a query, with the results, is: ?- sched(a). Task Task Task Task Task Task Task a b c d e f g with with with with with with with length length length length length length length 0 1 2 3 4 1 0 starts at can start starts at can start starts at can start starts at 0. from 0 to 1. 0. from 0 to 2. 2. from 3 to 5. 6. ) Bibliography #Col87] #Col90] #COS96] #DEDC96] #Fer81] #Her97] #HSB+ 96] #JM94] #MDV90] #MS98] #PAC] #Pro] A. Colmerauer. Opening the Prolog-III Universe. In BYTE Magazine, August 1987. A. Colmerauer. An Introduction to Prolog III. Communications of the ACM, 28(4):412{418, 1990. COSYTEC, Parc Club Orsay Universit(e, 4 Rue Jean Rostand, 91893 Orsay Cedex, France. CHIP V5 System Docmentation, April 1996. P. Deransart, A. Ed-Dbali, and L. Cervoni. Prolog: The Standard. SpringerVerlag, 1996. R. Ferguson. Prolog: A step towards the ultimate computer language. Byte, November 1981. M. Hermenegildo. Some Challenges for Constraint Programming. The Constraints Journal, 2(1):63{69, 1997. Special issue on strategic directions in constraint programming. P. Van Hentenryck, V. Saraswat, A. Borning, A. Brodski, P. Codognet, R. Dechter, M. Dincbas, E. Freuder, M. Hermenegildo, J. Jaar, S. Kasif, J.-L. Lassez, D. McAllester, Ken McAloon, A. Macworth, U. Montanari, W. Older, J.-F. Puget, R. Ramakrishnan, F. Rossi, G. Smolka, and R. Wachter. Strategic Directions in Constraint Programming. ACM Computing Surveys, 28(4):701{726, 1996. 50th Anniversary Issue on Strategic Directions in Computer Research. J. Jaar and M.J. Maher. Constraint Logic Programming: A Survey. Journal of Logic Programming, 19/20:503{581, 1994. H. Simonis M. Dincbas and P. Van Hentenryck. Solving Large Combinatorial Problems in Logic Programming. Journal of Logic Programming, 8(1 & 2):72{93, 1990. Kim Marriot and Peter Stuckey. Programming with Constraints: An Introduction. The MIT Press, 1998. The practical application of constraint technology conference series. The Practical Application Company, 54 Knowle Avenue, Blackpool, Lancs FY2 9UD, U.K. PrologIA, Parc Technologique de Luminy - Case 919, 13288 Marseille cedex 09, France. Prolog IV Manual. 109 110 #SS86] #Swe95] #Van89] #VD92] BIBLIOGRAPHY L. Sterling and E.Y. Shapiro. The Art of Prolog. MIT Press, Cambridge MA, 1986. Swedish Institute of Computer Science, P.O. Box 1263, S-16313 Spanga, Sweden. Sicstus Prolog V3.0 User's Manual, 1995. P. Van Hentenryck. Constraint Satisfaction in Logic Programming. MIT Press, 1989. P. Van Roy and A.M. Despain. High-Performace Logic Programming with the Aquarius Prolog Compiler. IEEE Computer Magazine, pages 54{68, January 1992. Appendix A Solutions to Proposed Problems Problem 1.1 (page 16): The tableau continues the one in Table 1.1 like this: Variables and Domains Step 12 13 14 15 16 a b c d 0..3 0..2 0..1 0 0 Final domains 0 0..1 0 0..2 e f g 2 3..5 2 3..5 6 6 Tasks a and c must start at the beginning of the project. Task e has to start at time 2, and cannot be delayed. Tasks b, d, and f have a slack in their start time. Problem 2.1 (page 23): ?- sound(A, S). A = spot, S = bark ? A = barry, S = bubbles ? A = hobbes, S = roar ? no Problem 2.2 (page 25): Both queries are independent. The rst one binds X to a then the toplevel backtracks and clears the bindings made so far, thus reverting to the state previous to the query. That is why the second query, binding X to a dierent value, succeeded: at this point the state of the interpreter is the same as if the rst query had never been issued. Problem 2.3 (page 26): The answer to ?- pet(X), sound(Y, roar). is X = spot, Y = and X = barry, Y = hobbes. No solution has the same value for an animal which is pet and for an animal with roars (in other words, no pet roars). When both are forced to be the same (using the constraint X = Y), the query fails. hobbes Problem 2.4 (page 26): 111 112 APPENDIX A. SOLUTIONS TO PROPOSED PROBLEMS ?- father_of(juan, pedro). yes ?- father_of(juan, david). no ?- father_of(juan, X). X = pedro ? X = maria ? no ?- grandfather_of(X, miguel). X = juan ? no ?- grandfather_of(X, Y). X = juan, Y = miguel ? X = juan, Y = david ? no ?- X = Y, grandfather_of(X, Y). no ?- grandfather_of(X, Y), X = Y. no Problem 2.5 (page 27): grandmother_of(L,M):mother_of(L,N), father_of(N,M). grandmother_of(X,Y):mother_of(X,Z), mother_of(Z,Y). Problem 2.6 (page 29): Symmetry of relationships can, of course, be achieved by dupli- cating the predicates (facts, in this case) which express this relationship, i.e., adding the following two facts: resistor(n1, power). resistor(n2, power). But this is not a good solution: changes to the database will have to be duplicated. Writing a bridge predicate is much better. In this case, and in order not to change the program dealing with the circuits, we will rewrite the denition of resistor/2 (and the part of the database which stores the information about resistors) to become 113 resistor(A, B):- rst(A, B). resistor(A, B):- rst(B, A). rst(power, n1). rst(power, n2). Problem 3.1 (page 37): Both queries loop forever, and neither solution nor failure is reached. The cause is the absence of a test for non-negativity of the argument. Thus, the call ?- nt(3.4). has, in successive invocations, the arguments 2.4, 1.4, 0.4, -1.4, -2.4, etc., and the recursion never stops. The call ?- nt(-8). starts directly in the negative case. Problem 3.2 (page 37): odd(1). odd(N+2):gtlin(N+2, 1), odd(N). Problem 3.3 (page 37): odd(N):- even(N+1). Problem 3.4 (page 37): multiple(0, B). multiple(A, B):gtlin(A, 0), multiple(A - B, B). Problem 3.5 (page 38): even(X):- multiple(X, 2). odd(X):- multiple(X + 1, 2). Problem 3.6 (page 38): congruent(M, N, K):int(M), int(N), int(K), remainder(M, K, R), remainder(N, K, R). remainder(X, Y, X):- ltlin(X, Y). remainder(X, Y, Z):gelin(X, Y), remainder(X -Y, Y, Z). APPENDIX A. SOLUTIONS TO PROPOSED PROBLEMS 114 Problem 3.7 (page 39): better_E(N, E4*4):- better_E(N, Sign, E4). better_E(1, 1, 1). better_E(N, Sign, Sign/N + RestE):gtlin(N, 1), better_E(N-1, -Sign, RestE). Problem 3.8 (page 40): fib(N, F):- fib(N, 0, 1, F). fib(0, 0, 1, 0). fib(1, _Prev, F, F). fib(N, Prev, Curr, Final):gtlin(N, 1), fib(N - 1, Curr, Prev + Curr, Final). The proposed query and its answer are: ?- fib(1000, F). F = 434665576869374564356885276750406258025646605173717804024817290895365554 179490518904038798400792551692959225930803226347752096896232398733224711 61642996440906533187938298969649928516003704476137795166849228875. Problem 3.10 (page 45): The duration of the project is not actually minimized. The queries in the example minimize the resource consumption after minimizing the project length|thus giving priority to nishing the project as soon as possible. Reversing the calls will make resource consumption as small as possible, and then try to make the task length as short as it can. Problem 3.11 (page 51): Yes, there are repeated solutions|or not? From the point of view of the user, if A has dinner with B, then it is clear the B is having dinner with A. But it can be argued they are not repeated: they bind dierent variables. We say they are repeated only because of our perception dictates that going for dinner is symmetrical, and does not need to be repeated. Which is actually the way around we thought of being friends. Everything depends on whether we are consulting or retrieving data. \Why" is easier. Duplicated or too much solutions are always produced by alternative clauses giving several paths for the solution, or too few constraints leaving too much freedom to the variables. In this case it is spouse/2 which causes the \excess" of answers. Problem 3.12 (page 54): even(z). even(s(s(E))):- even(E). Problem 3.13 (page 54): 115 times(z, _, z). times(s(X), Y, Z):plus(Y, Z1, Z), times(X, Y, Z1). exp(z, _, s(z)). exp(s(N), Y, Z):exp(N, Y, Z1), times(Y, Z1, Z). factorial(z, s(z)). factorial(s(N), F):factorial(N, F1), times(s(N), F1, F). minimum(z, s(_), z). minimum(s(_), z, z). minimum(z, z, z). minimum(s(A), s(B), s(M)):minimum(A, B, M). ltn(z, s(_)). ltn(s(A), s(B)):- ltn(A, B). Problem 3.14 (page 57): The query behaves as follows: ?- member(gogo, L). L = gogo|_] ? L = _,gogo|_] ? L = _,_,gogo|_] ? L = _,_,_,gogo|_] ? L = _,_,_,_,gogo|_] ? . . . The answers returned are, in fact, the most general possible. gogo is member of the list L either being in the rst, second, etc. position in that list, which continues indenitely. The rst answer is returned by the rst clause (the fact) of member/2 on backtracking, alternative solutions are returned by prepending free variables to the list. Problem 3.15 (page 57): A possible solution is the following query: ?- append(Xs, _], 1|Xs]), Xs = X1, X2, X3, X4]. The size/1 constraint of Prolog IV is simulated by explicitly writing a list of four variables. This query solves the problem, but falls into an innite failure (i.e., an endless loop trying to nd more solutions), due to backtracking generating lists of 1s. Putting at the beginning the APPENDIX A. SOLUTIONS TO PROPOSED PROBLEMS 116 generation of the list Xs solves this problem (remember that a property of constraints, unlike algorithmic solving, is the independence or the order in which they are generated): ?- Xs = X1, X2, X3, x4], append(Xs, _], 1|Xs]). The query is automatically solved as follows: suppose we are dealing with a list Xs of length 4 let us denote its elements (free variables, at the beginning) as Xs = X1, X2, X3, X4]. Then 1|Xs] is 1, X1, X2, X3, X4], and Xs o ] is X1, X2, X3, X4, ]. Equating these two lists generates the following: 1 X1 X2 X3 X4 = = = = = X1 X2 X3 X4 from which every element of the list is 1. Problem 3.16 (page 58): The second implementation runs in time linear with the length of the list to be reversed. This is so because that list is traversed downwards only once, and when the end of the list is found, the answer (in the second argument) is unied with the output argument (the third one). Problem 3.17 (page 58): len(], z). len(X|Xs], s(L)):- len(Xs, L). suffix(S, L):- append(_, S, L). prefix(P, L):- append(P, _, L). Alternative solution: suffix(X, X). suffix(S, X|Xs]):- suffix(S, Xs). prefix(], Xs). prefix(X|P], X|Xs]):- prefix(P, Xs). sublist(S, L):prefix(P, L), suffix(S, P). palindrome(L):- reverse(L, L). evenodd(], ], ]). evenodd(E1], ], E1]). evenodd(E1, E2|Rest], E2|Evens], E1|Odds]):evenodd(Rest, Evens, Odds). 117 Alternative solution: evenodd(], ], ]). evenodd(E|Rest], Evens, E|Odds]):evenodd(Rest, Odds, Evens). select(E, L1, L2):append(Head, E|Rest], L1), append(Head, Rest, L2). Alternative solution: select(X, X|Xs], Xs). select(X, Y|Xs], Y|Ys]):- select(X, Xs, Ys). Problem 3.18 (page 59): Simply duplicate the element when it is found in the list (third clause): insert_ordlist(Element, ], Element]). insert_ordlist(Element, This|Rest], This, Element|Rest]):precedes(This, Element). insert_ordlist(Element, Element|Rest], Element, Element|Rest]). insert_ordlist(Element, This|Rest], This|NewList]):precedes(Element, This), insert_ordlist(Element, Rest, NewList). Problem 3.19 (page 61): Note that the item of the current non-empty node is consed to the list coming from the right subtree before appending the left and right lists, in order to make it appear in between them. in_order(void, ]). in_order(tree(X, Left, Right), InOrder):in_order(Left, OrdLft), in_order(Right, OrdRght), append(OrdLft, X|OrdRght], InOrder). The need of placing an element at the end of a list makes necessary the use of two appends. The item of information in the current non-empty node is appended to the list from the traversal of the rightmost tree to minimize the work. post_order(void, ]). post_order(tree(X, Left, Right), Order):post_order(Left, OrdLft), post_order(Right, OrdRght), append(OrdRght, X], OrderMid), append(OrdLft, OrderMid, Order). 118 APPENDIX A. SOLUTIONS TO PROPOSED PROBLEMS Problem 4.1 (page 76): The unication (both in the containing and in the contained term) is made in the very rst clause, which reads subterm(Term, Term). Problem 4.2 (page 77): The same predicate numbers: there is a clause for each case. add matrices/3 is used for matrices and add_matrices(M1, M2, M3):number(M1), number(M2), M3 is M1 + M2. add_matrices(M1, M2, M3):functor(M1, mat, N), N > 0, functor(M2, mat, N), functor(M3, mat, N), add_matrices(N, M1, M2, M3). add_matrices(0, _, _, _). add_matrices(N, M1, M2, M3):N > 0, arg(N, M1, A1), arg(N, M2, A2), arg(N, M3, A3), add_matrices(A1, A2, A3), N1 is N - 1, add_matrices(N1, M1, M2, M3). Problem 4.3 (page 86): Ensure that the goal in not/1 is ground: unmarried_student(X):student(X), not(married(X)). student(joe). married(john). )

Download PDF

advertisement