Previous Contents Next

Chapter 2   Building Blocks

2.1   Domains

Finite domains of integers are created, accessed and handled with functions of module Domain (described exhaustively in section 4.4). They are represented as functional objects of (abstract) type Domain.t and can therefore be shared. Domains are build with different functions according to the domain property: Domains can be conveniently printed on an output channel with Domain.fprint and are displayed as lists of non-overlapping intervals and single integers [inf1-sup1;val2;inf3-sup3;...] in increasing order:
 let discontinuous = Domain.create [4;7;2;4;-1;3];;
val discontinuous : Facile.Domain.t = <abstr>

 Domain.fprint stdout discontinuous;;
[-1;2-4;7]- : unit = ()

 let range = Domain.interval 4 12;;
val range : Facile.Domain.t = <abstr>

 Domain.fprint stdout range;;
[4-12]- : unit = ()


Various functions allow access to properties of domains like, among others (see 4.4), Domain.is_empty, Domain.min, Domain.max whose names are self-explanatory:
 Domain.is_empty range;;
- : bool = false

 Domain.max range;;
- : int = 12

 Domain.member 3 discontinuous;;
- : bool = true

 Domain.values range;;
- : int list = [4; 5; 6; 7; 8; 9; 10; 11; 12]


Operators are provided as well to handle domains and perform easily set operations like Domain.intersection, Domain.union, Domain.difference and domain reduction like Domain.remove, Domain.remove_up, Domain.remove_low etc (see 4.4):
 Domain.fprint stdout (Domain.intersection discontinuous range);;
[4;7]- : unit = ()

 Domain.fprint stdout (Domain.union discontinuous range);;
[-1;2-12]- : unit = ()

 Domain.fprint stdout (Domain.remove_up 3 discontinuous);;
[-1;2-3]- : unit = ()

 Domain.fprint stdout (Domain.remove_closed_inter 7 10 range);;
[4-6;11-12]- : unit = ()


2.2   Variables

FaCiLe variables are attributed objects[3] which maintain their current domain and can be backtracked during execution of search goals.

Creation

FaCiLe finite domain constrained variables are build and handled by functions of module Var.Fd (described exhaustively in section 4.13). Variables are objects of type Fd.t created by a call to one of the following functions of module Var.Fd: The omnipresent fprint function writes a variable on an output channel:
 let vd = Fd.create discontinuous;;
val vd : Facile.Var.Fd.t = <abstr>

 Fd.fprint stdout vd;;
_4{[-1;2-4;7]}- : unit = ()


Attribute

A FaCiLe variable can be regarded as either in one of the two following states: So an unbound variable is associated with an attribute of type Var.Attr.t holding its current domain, a unique integer identifier and various data useless for the end-user. Functions to access attributes data are gathered in module Var.Attr: Although variables are of abstract type Fd.t, function Fd.value v returns a concrete view of type Var.concrete_fd = Unk of Attr.t | Val of int 1 of a variable v, such that a control structure that depends on the instantiation of a variable will typically look like:
match Fd.value v with
    Val n -> f_bound n
  | Unk attr -> f_unbound attr
An alternative boolean function Fd.is_var returns the current state of a variable, sparing the ``match'' construct.
 let v1 = Fd.create (Domain.create [1]) (* equivalent to Fd.int 1 *);;
val v1 : Facile.Var.Fd.t = <abstr>

 Fd.is_var v1;;
- : bool = false

 Fd.fprint stdout v1;;
1- : unit = ()


Domain reduction

Module Fd provides three functions to perform backtrackable domain reductions on variables, typically used within instantiation goals and filtering of user-defined constraints:
 Fd.fprint stdout vd;;
_4{[-1;2-4;7]}- : unit = ()

 match Fd.value vd with
     Val n -> () (* Do nothing *)
   | Unk attr -> (* Remove every value > 2 *)
       let new_dom = Domain.remove_up 2 (Var.Attr.dom attr) in
       Fd.refine vd new_dom;;
- : unit = ()

 Fd.fprint stdout vd;;
_4{[-1;2]}- : unit = ()
Whenever the domain of a variable becomes empty, a failure occurs (see 2.5 for more explanations about failure):
 match Fd.value vd with
     Val n -> () (* Do nothing *)
   | Unk attr -> (* Remove every value < 4 *)
       let new_dom = Domain.remove_low 4 (Var.Attr.dom attr) in
       Fd.refine vd new_dom;;
Uncaught exception: Fcl_stak.Fail("Var.Fd.refine").


Access

Besides Fd.value and Fd.is_var which access the state of a variable, module Fd provides the mapping of module Domain functions like Fd.size, Fd.min, Fd.max, Fd.values, Fd.iter and Fd.member, and they return meaningful values whatever the state (bound or unbound) of the variable may be:
 let vr = Fd.interval 5 8;;
val vr : Facile.Var.Fd.t = <abstr>

 Fd.size vr;;
- : int = 4

 let v12 = Fd.int 12;;
val v12 : Facile.Var.Fd.t = <abstr>

 Fd.member v12 12;;
- : bool = true
Function Fd.id, unlike the previous ones, returns a unique identifier for the variable only if it is uninstantiated, otherwise an exception is raised. An order based on these identifiers is defined by function Fd.compare2 as well as an equality function Fd.equal, observing the two following rules:
  1. bound variables are smaller than unbound variables;
  2. unbound variables are compared according to their identifiers.
 Fd.id vr;;
- : int = 6

 Fd.id v12;;
Uncaught exception: Failure "Fatal error: Fd.id: bound variable".

 Fd.compare v12 (Fd.int 11);;
- : int = 1

 Fd.compare vr v12;;
- : int = 1

 Fd.id vd;;
- : int = 4

 Fd.compare vd vr;;
- : int = -1


2.3   Arithmetic expressions

Arithmetic expressions and constraints over finite domain variables are build with functions and operators of module Arith (see 4.2).

Creation and access

Arithmetic expressions are objects of abstract type Arith.t which contain a representation of an arithmetic term over finite domain variables. An expression is ground when all the variables used to build it are bound; in such a state an expression can be ``evaluated'' with function Arith.eval which returns its unique integral value. A call to Arith.eval with an expression that is not ground raises the exception Invalid_argument. However, any expression can be printed on an output channel with function Arith.fprint.

A variable of type Fd.t or an OCaml integer of type int are not arithmetic expressions and cannot therefore be mixed up with the latter. ``Conversion'' functions are provided by module Arith to build an expression from variables and integers : Handily enough, opening module Easy allows direct access to most useful functions of module Arith, including i2e and fd2e:

 let v1 = Fd.interval 2 5;;
val v1 : Facile.Var.Fd.t = <abstr>

 let exp1 = fd2e v1;;
val exp1 : Facile.Arith.t = <abstr>

 Arith.fprint stdout exp1;;
_7{[2-5]}- : unit = ()

 Arith.eval exp1;;
Uncaught exception: Invalid_argument "Arith.eval: not ground".

 Fd.unify v1 4;;
- : unit = ()

 Arith.eval exp1;;
- : int = 4

 Arith.fprint stdout (i2e 2);;
2- : unit = ()


Maximal and minimal values of expressions can be accessed by functions Arith.max_of_expr and Arith.min_of_expr:
 let exp2 = fd2e (Fd.interval (-3) 12);;
val exp2 : Facile.Arith.t = <abstr>

 Arith.min_of_expr exp2;;
- : int = -3

 Arith.max_of_expr exp2;;
- : int = 12


An arithmetic expression can also be transformed into a variable thanks to function Arith.e2fd which creates a new variable constrained to be equal to its argument (see 2.4.2).

Operators

Module Arith provides classic linear and non-linear arithmetic operators to build complex expressions. They can be directly accessed through the opening of module Easy, which considerably ligthen the writting of equation, especially for binary infix ones.
 let vx = Fd.interval 3 6 and vy = Fd.interval 4 12;;
 let exp1 = i2e 2 *~ fd2e vx -~ fd2e vy +~ i2e 3;;
val exp1 : Facile.Arith.t = <abstr>

 Arith.fprint stdout exp1;;
(((2*_9{[3-6]})+-(_10{[4-12]}))+3)- : unit = ()

 Arith.min_of_expr exp1;;
- : int = -3

 Arith.max_of_expr exp1;;
- : int = 11


Global arithmetic operators working on array of expressions are provided as well: Their variable counterparts where the array of expressions is replaced by an array of variables are defined as well: Arith.sum_fd, Arith.scalprod_fd, Arith.prod_fd.

 let size = 5;;
val size : int = 5

 let coefs = Array.init size ~f:(fun i -> i+1);;
val coefs : int array = [|1; 2; 3; 4; 5|]

 let vars = Fd.array size 0 9;;
val vars : Facile.Var.Fd.t array =
  [|<abstr>; <abstr>; <abstr>; <abstr>; <abstr>|]

 let pscal_exp = Arith.scalprod_fd coefs vars;;
val pscal_exp : Facile.Arith.t = <abstr>

 Arith.fprint stdout pscal_exp;;
(((((0+(_11{[0-9]}*1))+(_12{[0-9]}*2))+(_13{[0-9]}*3))+(_14{[0-9]}*4))+(_15{[0-9
]}*5))- : unit = ()

 Arith.min_of_expr pscal_exp;;
- : int = 0

 Arith.max_of_expr pscal_exp;;
- : int = 135


2.4   Constraints

2.4.1   Creation and Use

A constraint in FaCiLe is a value of type Cstr.t. It can be created by a built-in function (arithmetic, global constraints) or user-defined (see 3.3). A constraint must be posted with the function Cstr.post to be taken into account, i.e. added to the constraints store.

When a constraint is posted, it is attached to the involved variables and activated: propagation occurs as soon as the constraint is posted. The constraint is also stored in a global state accessible by the Cstr.active_store function which returns the list of all constraints still ``unsolved'', i.e. not yet globally consistent.

Constraints basically perform domain reductions on their involved variables, first when posted and then each time that a particular ``event'' occurs on their variables. An event corresponds to a domain reduction on a variable: the minimal or maximal value has changed, the size of the domain has decreased or the variable has been bound. To all these kinds of reduction are associated different events that will trigger the ``awakening'' of the appropriate constraints. See 3.2.1 for a more precise description of this event-driven mechanism.

Constraints can also be printed on an output channel with function Cstr.fprint which usually yields useful information about the variables involved and/or the name of the constraint.

2.4.2   Arithmetic Constraints

Simplest and standard constraints are relations on arithmetic expressions (c.f. 2.3): FaCiLe provides them as infix operators suffixed with the ~ character, similarly to expression operators. These operators are declared in the Easy module and don't need module prefix notation whenever Easy is opened. The small example below uses the equality operator =~ and points out the effect on the variables domains of posting the constraint equation:
 (* 0<=x<=10, 0<=y<=10, 0<=z<=10 *)
 let x = Fd.interval 0 10 and y = Fd.interval 0 10 and z = Fd.interval 0 10;;
 let equation = (* x*y - 2*z >= 90 *)
 fd2e x *~ fd2e y -~ i2e 2 *~ fd2e z >=~ i2e 90;;
val equation : Facile.Cstr.t = <abstr>

 (* before propagation has occured *)
 Cstr.fprint stdout equation;;
 +2._18{[0-10]} -1._19{[0-100]} <= -90- : unit = ()

 Cstr.post equation;;
- : unit = ()

 (* after propagation has occured *)
 Cstr.fprint stdout equation;;
 +2._18{[0-5]} -1._19{[90-100]} <= -90- : unit = ()
Notice that the output of the Cstr.fprint function does not look exactly like the stated inequation but shows how the two operands of the main sum are internally reduced into new single variables constrained to be equal to the latters. This mechanism is of course hidden to the user and is only unfolded when calling Cstr.fprint.

FaCiLe compiles and simplifies (``normalizes'') arithmetic constraints as much as possible such that variables and integers may be scattered inside an expression with no loss of efficiency. Therefore the constraint ineq1:
 let x = Fd.interval (-2) 6 and y = Fd.interval 4 12;;
val x : Facile.Var.Fd.t = <abstr>
val y : Facile.Var.Fd.t = <abstr>

 let xe = fd2e x and ye = fd2e y;;
val xe : Facile.Arith.t = <abstr>
val ye : Facile.Arith.t = <abstr>

 let ineq1 = i2e 3 *~ ye +~ i2e 2 *~ xe *~ ye *~ i2e 5 *~ xe +~ ye >=~ i2e 4300;;
val ineq1 : Facile.Cstr.t = <abstr>

 Cstr.fprint stdout ineq1;;
 -10._26{[0-432]} -4._23{[4-12]} <= -4300- : unit = ()
which ensures 3y+(2xy×5x)+y ³ 4300, i.e. 10x2y+4y ³ 4300, is equivalent to ineq2:
 let ineq2 = i2e 10 *~ (xe **~ 2) *~ ye +~ i2e 4 *~ ye >=~ i2e 4300;;
val ineq2 : Facile.Cstr.t = <abstr>

 Cstr.fprint stdout ineq2;;
 -10._31{[0-432]} -4._23{[4-12]} <= -4300- : unit = ()


Once posted, ineq1 or ineq2 incidentally yield a single solution:
 Printf.printf "x=%a y=%a\\n" Fd.fprint x Fd.fprint y;;
x=_22{[-2-6]} y=_23{[4-12]}
- : unit = ()

 Cstr.post ineq1;;
- : unit = ()

 Printf.printf "x=%a y=%a\\n" Fd.fprint x Fd.fprint y;;
x=6 y=12
- : unit = ()


It is also worthy to mention that arithmetic constraints involving (large enough) sums of boolean variables are automatically detected by FaCiLe and handled internally by a specific efficient mechanism. The user may thus be willing to benefit from these features by choosing a suitable problem modeling.

Note on precision and overflow

Users should be carefull when expecting the arithmetic solver to compute bounds from variables with very large domain, that means with values close to max_int or min_int (depending of the system and architecture). Especially with exponentiation and multiplication, an integer overflow may occur which will yield an exception if compiled in byte code and a wrong calculation if compiled in native code. An unexpected result when performing such operations in native code should thus always be checked against the byte code version.

Another possible source of miscalculation is the rounding performed when computing bounds of expressions involving exponentiation and multiplication or division: float operations are possibly performed with a loss of accuracy which is most of the time corrected by rounding, but errors might still occur for large numbers for which nth root or division requires a high precision (let's say 10-6) not provided by available float operators. Please send a bug report if you think such a case occured (see page ??).

2.4.3   Global Constraints

Beside arithmetic constraints, FaCiLe provides so-called ``global constraints'' which express a relation on a set of variables. They are defined in separate modules in which a function (and possibly several variants) usually named cstr yields the constraint; these functions takes an array of variables as their main argument.

The most famous one is probably the ``all different'' constraint which expresses that all the elements of an array of variables must take different values. This constraint is invoked by the function Alldiff.cstr ?algo vars where vars is an array of variables and algo an optional argument that controls the efficiency of the constraint (see 4.1):
 let vars = Fd.array 5 0 4;;
val vars : Facile.Var.Fd.t array =
  [|<abstr>; <abstr>; <abstr>; <abstr>; <abstr>|]

 let ct = Alldiff.cstr vars;;
val ct : Facile.Cstr.t = <abstr>

 Fd.fprint_array stdout vars;;
[|_35{[0-4]}; _36{[0-4]}; _37{[0-4]}; _38{[0-4]}; _39{[0-4]}|]- : unit = ()

 Cstr.post ct; Fd.unify vars.(0) 3;;
- : unit = ()

 Fd.fprint_array stdout vars;;
[|3; _36{[0-2;4]}; _37{[0-2;4]}; _38{[0-2;4]}; _39{[0-2;4]}|]- : unit = ()


Module FdArray provides the ``element'' constraint named FdArray.get which allows to index an array of variables by a variable, and the min (and max) constraint which returns a variable constrained to be equal to the variable that will instantiate to the minimal (respectively maximal) value among the variables of an array:
 let vars = [|Fd.interval 7 12; Fd.interval 2 5; Fd.interval 4 8|];;
val vars : Facile.Var.Fd.t array = [|<abstr>; <abstr>; <abstr>|]

 let index = Fd.interval (-10) 10;;
val index : Facile.Var.Fd.t = <abstr>

 let vars_index = FdArray.get vars index;;
val vars_index : Facile.Var.Fd.t = <abstr>

 Fd.fprint stdout index;;
_66{[0-2]}- : unit = ()

 Fd.fprint stdout vars_index;;
_67{[2-12]}- : unit = ()

 let mini = FdArray.min vars;;
val mini : Facile.Var.Fd.t = <abstr>

 Fd.fprint stdout mini;;
_69{[2-5]}- : unit = ()
FdArray.get and FdArray.min which produce a new variable (and thus hide an underlying constraint) have also their ``constraint'' counterpart FdArray.get_cstr and FdArray.min_cstr which take an extra variable as argument and return a constraint of type Cstr.t that must be posted to be effective: FdArray.min_cstr vars mini is therefore equivalent to the constraint
fd2e (FdArray.min vars) =~ fd2e mini,
and FdArray.get_cstr vars index v to
fd2e (FdArray.get vars index) =~ fd2e v.
More sophisticated global constraints are available as well as FaCiLe built-in constraints:

2.4.4   Reification

FaCiLe constraints can be ``reified'' thanks to the Reify module and its function Reify.boolean (see 4.10) which takes an argument of type Cstr.t and returns a new boolean variable. This boolean variable is interpreted as the truth value of the relation expressed by the constraint and the following equivalences hold: otherwise, i.e. it is not yet known if the constraint is satisfied or violated and the boolean variable is not instantiated, the reification of a constraint do not perform any domain reduction on the variables involved.

In the following example, the boolean variable is_x_less_than_y is constrained to the truth value of the inequation constraint x < y:
 let x = Fd.interval 3 6 and y = Fd.interval 5 8;;
val x : Facile.Var.Fd.t = <abstr>
val y : Facile.Var.Fd.t = <abstr>

 let x_less_than_y = Reify.boolean (fd2e x <~ fd2e y);;
val x_less_than_y : Facile.Var.Fd.t = <abstr>

 Fd.fprint stdout x_less_than_y;;
_74{[0-1]}- : unit = ()

 Cstr.post (fd2e y >=~ i2e 7);;
- : unit = ()

 Fd.fprint stdout x_less_than_y;;
1- : unit = ()

 Fd.fprint stdout (Reify.boolean (fd2e x =~ fd2e y));;
0- : unit = ()


When posted, the reification of a constraint calls the check function (see 3.3) of the constraint, which verifies whether it is satisfied or violated (without performing domain reduction). If it is violated, the negation of the constraint is posted with a call to another function of the constraint dedicated to reification, namely not (see 3.3). Both functions are always defined for all constraints but their default behaviour is merely exception raising (Failure "Fatal error: ...") which means that the constraint is actually not reifiable - as specified in the documentation of the relevant constraints in the reference manual. Mainly arithmetic constraints are reifiable (as well as the ``interval'' constraint of module Interval, see 4.8) and others (global ones) are not.

Reified constraint are by default waked up with the events triggering its standard awakening (i.e. when directly posted, not reified) and those of its negation. This behaviour might possibly be too time costly (for some specific problem) and the call to Reify.boolean with its optional argument delay_on_negation (see 4.10) set to false disables it, i.e. the events associated with the negation of the constraint are ignored.

Module Reify also provides standard logic (infix) operators over constraints: These operators can be directly accessed through the opening of module Easy except Reify.not (for obvious reasons). They can be combined to yield new logic operators like the ``exclusive or'' for example:
 let x = Fd.interval 3 5 and y = Fd.interval 5 7;;
val x : Facile.Var.Fd.t = <abstr>
val y : Facile.Var.Fd.t = <abstr>

 let xor ct1 ct2 = Reify.not (ct1 <=>~~ ct2) in
 let xor_cstr = xor (fd2e x =~ i2e 5) (fd2e y =~ i2e 5) in
 Cstr.post (xor_cstr);
 Cstr.post (fd2e x <=~ i2e 4);
 Printf.printf "x=%a y=%a\\n" Fd.fprint x Fd.fprint y;;
x=_82{[3-4]} y=5
- : unit = ()


Furthermore, module Arith contains convenient shortcuts to reify its basic arithmetic constraints:
=~~, <>~~, <=~~, >=~~, <~~, >~~
These operators stand for the reification (and transformation into arithmetic expression) of their basic counterparts, i.e. they take two arithmetic expressions as operands and yield a new arithmetic expression being the boolean variable related to the truth value of the arithmetic constraint. e1 =~~ e2 is therefore equivalent to
fd2e (Reify.boolean (e1 =~ e2))
These operators can also be directly accessed through the opening of module Easy. In the following example, the constraint stating that at least two of the three variables contained in array vs must be greater than 5 is expressed with the reified greater or equal >=~~:
 let vs = Fd.array 3 0 10;;
val vs : Facile.Var.Fd.t array = [|<abstr>; <abstr>; <abstr>|]

 Cstr.post (Arith.sum (Array.map ~f:(fun v -> fd2e v >~~ i2e 5) vs) >=~ i2e 2);
 Fd.fprint_array stdout vs;;
[|_91{[0-10]}; _92{[0-10]}; _93{[0-10]}|]- : unit = ()
If vs.(1) is forced to be less than 5, the two other variables become greater than 5:
 Cstr.post (fd2e vs.(1) <=~ i2e 5);
 Fd.fprint_array stdout vs;;
[|_91{[6-10]}; _92{[0-5]}; _93{[6-10]}|]- : unit = ()


2.5   Solving

Most constraint models are not tight enough to yield directly a single solution, such that search (and/or optimization) is necessary to find appropriate ones. FaCiLe uses goals to search for solutions. All built-in goals and functions to create and combine goals are gathered in module Goals (see 4.7). This section only introduces ``ready-to-use'' goals intended to implement basic search strategies, but more experimented users shall refer to sections 3.1.2 and 3.4 where combining goals with iterators, and building of goals from scratch are explained.

FaCiLe's most standard labeling goals is Goals.indomain which instantiates non-deterministically a single variable by disjunctively trying each value still in its domain in increasing order. To be executed, a goal must then be passed as argument of function Goals.solve which returns true if the goal succeeds, and false if it fails.
 let x = Fd.create (Domain.create [-4;2;12]);;
val x : Facile.Var.Fd.t = <abstr>

 Goals.solve (Goals.indomain x);;
- : bool = true

 Fd.fprint stdout x;;
-4- : unit = ()
So the first attempt to instantiate x (to -4) obviously succeeds.

The values of the domain of x can be enumerated with a slightly more sophisticated goal which fails just after Goals.indomain. Module Goals provides Goals.fails, which is a goal that fails immediately, and conjunction and disjunction operators, respectively &&~ and ||~ (which can be directly accessed when module Easy is open), to combine simple goals. Hence such an enumeration goal would look like:
Goals.indomain x &&~ Goals.fail
But the result of such a goal will be failure and the state of the system (variable x not instantiated) will not be restored. A simple disjunction of this goal with the goal that succeeds immediately, Goals.success, yields the desirable behaviour :
Goals.indomain x &&~ Goals.fail ||~ Goals.success
In order to display the execution of this goal, a printing goal gprint_fd which prints a variable on the standard output (but will not be detailed in this section, see 3.4.1) can eventually be inserted (conjunctively) between indomain and fail:
 let x = Fd.create (Domain.create [-4;2;12]);;
val x : Facile.Var.Fd.t = <abstr>

 let goal = Goals.indomain x &&~ gprint_fd x &&~ Goals.fail ||~ Goals.success;;
val goal : Facile.Goals.t = <abstr>

 Goals.solve goal;;
-4 2 12 - : bool = true
Note that the logic operators have standard priorities.

Module Goals provides as well the function Goals.instantiate that allows to specify the odering strategy of the labeling. Goals.instantiate takes as first argument a function which is given the current domain of the variable (as single argument) and should return an integer candidate for instantiation. Labeling of variable x in decreasing order is then merely:
 let label_and_print labeling v =
   labeling v &&~ gprint_fd v &&~ Goals.fail ||~ Goals.success;;
val label_and_print :
  (Facile.Var.Fd.t -> Facile.Goals.t) -> Facile.Var.Fd.t -> Facile.Goals.t =
  <fun>

 Goals.solve (label_and_print (Goals.instantiate Domain.max) x);;
12 2 -4 - : bool = true
Function label_and_print is defined here to lighten the writting of enumeration goals (it takes only the instantiation goal and the variable as arguments). In the example below, variable x is labeled in increasing order of the absolute value of its values. Function Domain.choose allows to specify only the relevant order:
 let goal =
   label_and_print
     (Goals.instantiate (Domain.choose (fun v1 v2 -> abs v1 < abs v2))) x;;
val goal : Facile.Goals.t = <abstr>

 Goals.solve goal;;
2 -4 12 - : bool = true


Beside non-deterministic instantiation, FaCiLe provides also Goals.unify to enforce the instantiation of a variable (which might be already bound) to a given integer value:
 Goals.solve (Goals.unify x 2);;
- : bool = true

 Fd.fprint stdout x;;
2- : unit = ()

 Goals.solve (Goals.unify x 12);;
- : bool = false

 Goals.solve (Goals.unify (Fd.int 0) 0);;
- : bool = true


2.6   Optimization

Classic Branch & Bound search is provided by the module Opti with the function minimize. It allows to solve a specified goal (g) while minimizing a cost defined by a finite domain variable (c):
  1. Goal g is solved and its associated cost c (minimum of variable c) is computed;
  2. a new constraint stating c < c is added;
  3. the process loops until goal fails.
The third argument of Opti.minimize is a function f called each time a solution is found. The argument of the function is the current cost (minimum of variable c).

Opti.minimize returns the result of the last call to function f. Because the function f may never be called, this result is packed with an option type. So if goal does not have any solution, returned value is None. If goal does have solutions and the search is not interrupted, the returned value is (Some x) where x is the value returned by function f when the last solution was found.

The following example solves the minimization of x2+y2 while x+y=10 :
 let x = Fd.interval 0 10 and y = Fd.interval 0 10 in
 Cstr.post (fd2e x +~ fd2e y =~ i2e 10);
 let c = Arith.e2fd (fd2e x **~ 2 +~ fd2e y **~ 2) in
 let optimum =
   Opti.minimize (Goals.indomain x &&~ Goals.indomain y) c
     (fun c' -> Printf.printf "Found %d\\n" c'; (Fd.min x, Fd.min y)) in
 match optimum with
     None -> Printf.printf "No solution found\\n"
   | Some (x, y) ->
       Printf.printf "Optimal solution: cost=%d x=%d y=%d\\n" (Fd.min c) x y;;
Found 100
Found 82
Found 68
Found 58
Found 52
Found 50
Optimal solution: cost=50 x=5 y=5
- : unit = ()



1
Type Var.concrete_fd constructors Unk and Val stand respectively for ``Unknown'' (unbound) and ``Value'' (bound).
2
Comparison functions return 0 if both arguments are equal, a positive integer if the first is greater than the second and a negative one otherwise (like specified in the OCaml standard library).
3
This one is obviously not infix.

Previous Contents Next