Next: General Guidelines for the
Up: IC: A Hybrid Finite
Previous: Introduction
  Index
Subsections
- Vars :: Domain
- Constrains Vars to take only integer or real values from the domain
specified by Domain. Vars may be a variable, a list, or a submatrix (e.g. M[1..4, 3..6]); for a list or a submatrix, the domain is applied recursively
so that one can apply a domain to, for instance, a list of lists of
variables. Domain can be specified as a simple range Lo .. Hi, or as a list
of subranges and/or individual elements (integer variables only). The type
of the bounds determines the type of the variable (real or integer). Also
allowed are the (untyped) symbolic bound values inf, +inf and
-inf.
- reals(Vars)
- Equivalent to Vars :: -inf..inf (i.e. simply declares that the
variable is an IC variable).
- integers(Vars)
- Constrains the given variables to take integer values only.
Note that the integer forms of the constraints are essentially the same as
the general forms, except that they check that all constants are integers
and constrain all variables to be integral. Thus if the constants are
integers and the variables already integral, the two forms may be used
interchangeably.
- ic:(ExprX =:= ExprY)
- ExprX is equal to ExprY. ExprX and ExprY are general expressions.
- ic:(ExprX >= ExprY)
- ExprX is greater than or equal to ExprY. ExprX and ExprY are general
expressions.
- ic:(ExprX =< ExprY)
- ExprX is less than or equal to ExprY. ExprX and ExprY are general expressions.
- ic:(ExprX =
\= ExprY)
-
ExprX is not equal to ExprY. ExprX and ExprY are general expressions.
- ExprX #= ExprY
- ExprX is equal to ExprY. ExprX and ExprY are integer expressions,
and the variables are constrained to be integers.
- ExprX #>= ExprY
- ExprX is greater than or equal to ExprY. ExprX and ExprY are integer
expressions, and the variables are constrained to be integers.
- ExprX #=< ExprY
- ExprX is less than or equal to ExprY. ExprX and ExprY are integer
expressions, and the variables are constrained to be integers.
- ExprX #> ExprY
- ExprX is greater than ExprY. ExprX and ExprY are integer expressions,
and the variables are constrained to be integers.
- ExprX #< ExprY
- ExprX is less than ExprY. ExprX and ExprY are integer expressions, and the
variables are constrained to be integers.
- ic:(ExprX #
\= ExprY)
-
ExprX is not equal to ExprY. ExprX and ExprY are integer
expressions, and the variables are constrained to be integers.
The comparison constraints =:=/2, >=/2, =</2 and
=\=/2 have the same syntax as the standard ECLiPSe built-in
comparison operators (and those of other constraint solvers). By default,
the ECLiPSe built-ins are used. This choice of default can be changed
by explicitly importing the desired version before its first use:
:- import (=:=) / 2, (>=) / 2, (=<) / 2, (=\=) / 2 from ic.
foo(X, Y) :-
X >= Y. % this will use ic's >=/2
In any event, the default can be overridden on any given call by explicitly
qualifying which is desired, for example ic:(A =:= B) or
eclipse_language:(A =:= B).
As mentioned earlier, when constraints appear in an expression
context, then they evaluate to their reified truth value. Practically
this means that the constraints are posted in a passive check but
do not propagate mode, whereby no variable domains are modified but
checks are made to see if the constraint has become entailed
(necessarily true) or dis-entailed (necessarily false).
The simplest and arguably most natural way to reify a constraint is to
place it in an expression context (i.e. on either side of a =:=,
>=, #=, etc.) and assign its truth value to a variable.
For example:
ic:(TruthValue =:= (X > 4)).
It is also possible to use the 3 argument form of the constraint predicates
where the third argument is the reified truth value, for example:
ic:(>(X, 4, TruthValue)).
But in general the previous form is recommended as it can be easily
extended to handle the truth values of a combination of constraints, by
using the infix operators and (logical conjunction), or
(logical disjunction) and => (logical implication) or the prefix
operator neg (logical negation). e.g.:
ic:(TruthValue =:= (X > 4) and (Y < 6)).
The logical truth value of a constraint, when reified, can be used to
enforce the constraint (or its negation) during search.
The following three examples are equivalent:
ic:(X > 4).
ic:(B =:= (X > 4)), B=1.
ic:(B =:= (X =< 4)), B=0.
By unifying the value of the reified truth value, the constraint
changes from being passive to being active. Once actively
true (or actively false) the constraint will prune domains as though
it had been posted as a simple non-reified constraint.
Reified constraints are implemented using the the 3 argument form of
the constraint predicate if it exists (and it does exist for the
arithmetic relation constraints).
User defined constraints will be treated as reifiable if they appear
in an expression context and as such should provide three argument
forms where the third argument is the reified truth value reflected
into a variable.
The user defined constraint should behave as follows depending on the
state of the reified variable.
When the reified variable is unbound, the constraint should not perform any domain reduction on its arguments, but should check to see if the constraint has become entailed or dis-entailed, setting the reified variable to 1 or 0 respectively.
In the event that the reified variable becomes bound to 0 then the constraint should actively propagate its negation.
In the event that the reified variable becomes bound to 1 then the constraint should actively propagate its normal semantics.
- alldifferent(Vars)
- Constrains all elements of a list to be different from all other
elements of the list.
These predicates can be used to enumerate solutions to a set of constraints
over integer variables. What's available currently is very primitive.
Eventually something along the lines of the fd_search library will be
provided.
- indomain(Var)
- Instantiates an integer IC variable to an element of its domain.
- labeling(Vars)
- Instantiates all IC variables in a list to elements of their domains.
These predicates can be used to locate real solutions to a set of
constraints. They're essentially the same as those available in RIA; more
details of the algorithms used can be found in the RIA chapter of this
manual.
- locate(Vars, Precision)
- Locate solution intervals for Vars by splitting and search. Precision
indicates how accurate the intervals have to be before splitting stops.
- locate(Vars, Precision, LinLog)
- As per locate/2, but LinLog specifies wither linear (lin) or
logarithmic (log) splitting should be used. (ic:locate/2 is
equivalent to calling ic:locate/3 with log as the third
argument.)
- locate(LocateVars, SquashVars, Precision, LinLog)
- As per ic:locate/3, but also applies the squashing algorithm to
SquashVars both before splitting commences, and then again after each split.
- squash(Vars, Precision, LinLog)
- Refine the intervals of Vars by the squashing algorithm.
These predicates allow one to retrieve various properties of an IC variable.
- is_ic_var(Var)
- Succeeds if an only if Var is an IC variable.
- get_var_type(Var, Type)
- Returns whether Var is an integer variable or a real variable.
- get_ic_bounds(Var, Lo, Hi)
- Returns the current bounds of Var.
- get_ic_domain_size(Var, Size)
- Returns the number of elements in the IC domain for Var. Currently Var
needs to be of type integer.
- ic_constraints_number(Var, N)
- Returns the number of delayed goals suspended on the IC attribute. This
approximates the number of IC constraints that Var is involved in.
- median(Var, Median)
- Returns the median of the interval of Var.
- delta(Var, Delta)
- Returns the width of the interval of Var.
With interval constraint propagation, it is sometimes useful to limit
propagation for efficiency reasons. In IC, this is controlled by the
propagation threshold. The way it works is that for non-integer variables,
bounds are only changed if the absolute and relative changes of the bound
exceed this threshold (i.e. small changes are suppressed). This means that
constraints over real variables are only guaranteed to be consistent up to
the current threshold (over and above any normal widening which occurs).
Note that a higher threshold speeds up computations, but reduces precision
and may in the extreme case prevent the system from being able to locate
individual solutions.
The default threshold is 1e-8.
- get_threshold(Threshold)
- Returns the current propagation threshold.
- set_threshold(Threshold)
- Sets the propagation threshold. Note that if the threshold is reduced using
this predicate (requiring a higher level of precision), the current state of
the system may not be consistent with respect to the new threshold. If it
is important that the new level of precision be realised for all or part of
the system before computation proceeds, set_threshold/2 should be used
instead.
- set_threshold(Threshold, WakeVars)
- Sets the propagation threshold, with re-computation. If the threshold has
been reduced, all constraints suspended on the bounds of the variables in
the list WakeVars are woken.
Solving by Interval Propagation
Some problems can be solved just by interval propagation, for example:
[eclipse 9]: X :: 0.0..100.0, ic:(sqr(X) =:= 7-X).
X = X{2.1925824014821349 .. 2.1925824127108311}
Delayed goals:
...
yes.
There are two things to note here:
- The solver never instantiates real-variables. They only get
reduced to narrow ranges.
- In general, many delayed goals remain at the end of propagation.
This reflects the fact that the variable's ranges could possibly
be further reduced later on during the computation.
It also reflects he fact that
- the solver does not guarantee the existence of solutions in the
computed ranges. However, it guarantees that there are no solutions
outside these ranges.
Note that, since variables by default range from minus to plus infinity,
we could have written the above example as:
[eclipse 2]: ic:(sqr(X) =:= 7-X), ic:(X >= 0).
X = X{2.1925824014821349 .. 2.1925824127108311}
Delayed goals:
...
yes.
If too little information is given, the interval propagation may not
be able to infer any interesting bounds:
[eclipse 2]: ic:(sqr(X) =:= 7-X).
X = X{-1.0Inf .. 7.0000000000000009}
Delayed goals:
...
yes.
There are two methods for further domain reduction. They both rely on
search and splitting the domains. There are 2 parameters to specify how
domains are to be split.
The Precision parameter is used to specify the minimum required
precision, i.e. the maximum size of the resulting intervals.
Note that the
arc-propagation threshold
needs to be one or
several orders of magnitude smaller than precision, otherwise
the solver may not be able to achieve the required precision.
The lin/log parameter guides the way domains are split.
If it is set to lin then the split is in the arithmetic middle.
If it is set to log, the split is such as to have the
same number of floats to either side of the split. This is to take
the logarithmic distribution of the floats into account.
If the ranges of variables at the start of the squashing algorithm are
known not to span several orders of magnitude (
|max| < 10 * |min|) the
somewhat cheaper linear splitting may be used. In general, log splitting
is recommended.
- locate(+Vars, +Precision)
- locate(+Vars, +Precision, +lin/log)
-
Locate solution intervals for the given variables with the required
precision. This works well if the problem has a finite number of solutions.
locate/2,3 work by nondeterministically splitting the ranges of the variables
until they are narrower than Precision.
- squash(+Vars, +Precision, +lin/log)
-
Use the
squash algorithm
on these variables.
This is a deterministic reduction of the ranges of variables, done by
searching for domain restrictions which cause failure, and then reducing
the domain to the complement of that which caused the failure.
This algorithm is appropriate when the problem has continuous solution ranges
(where locate would return many adjacent solutions).
- locate(+LocateVars,+SquashVars,+Precision,+lin/log)
-
A variant of locate/2,3 with interleaved squashing:
The
squash algorithm
is applied once to the SquashVars initially,
and then again after each splitting step,
ie. each time one of the LocateVars has been split nondeterministically.
A variable may occur both in LocateVars and SquashVars.
Squash algorithm
A stronger propagation algorithm is also included. This is built upon the
normal bound consistency. It guarantees that, if you take any variable
and restrict its range to a small domain near one of its bounds,
the original bound consistency solver will not find any constraint
unsatisfied.
Figure 5.1:
Propagation with Squash algorithm (example)
 |
All points (X,Y) Y >= X, lying within the intersection of 2 circles with
radius 2, one centred at (0,0) the other at (1,1).
[eclipse 29]: ic:(4 >= X^2 + Y^2),ic:(4 >= (X-1)^2+(Y-1)^2), ic:(Y >= X).
Y = Y{-1.000000000000002 .. 2.0000000000000013}
X = X{-1.000000000000002 .. 2.0000000000000013}
yes.
The bound-consistency solution does not take into account the X >= Y
constraint. Intuitively this is because it passes through the corners
of the box denoting the solution to the problem of simply intersecting
the two circles.
[eclipse 29]: ic:(4 >= X^2 + Y^2), ic:(4 >= (X-1)^2+(Y-1)^2),
ic:(Y >= X), squash([X,Y],1e-5,lin).
X = X{-1.000000000000002 .. 1.4142135999632603}
Y = Y{-0.41421359996326107 .. 2.0000000000000013}
yes.
Often it is difficult to know where the solver spends its time.
The library has built-in counters which keep track of
- Propagation steps (prop)
- Domain splits in locate/2,3,4 (split)
- Attempts to bound reduction in squash/3 or locate/4 (squash)
- A number of constraint activity metrics (creation, reduction,
propagation, entailment) for each of the 1, 2 and n-variable
linear constraints.
- ic_1v_lin_create
- The number of 1 variable linear constraints created.
- ic_2v_lin_create
- The number of 2 variable linear constraints created.
- ic_nv_lin_create
- The number of n variable linear constraints created.
- ic_1v_lin_prop
- The number of times a 1 variable linear constraint propagated.
- ic_2v_lin_prop
- The number of times a 2 variable linear constraint propagated.
- ic_nv_lin_prop
- The number of times an n variable linear constraint propagated.
- ic_1v_lin_ent
- The number of times a 1 variable linear constraint was entailed.
- ic_2v_lin_ent
- The number of times a 2 variable linear constraint was entailed.
- ic_nv_lin_ent
- The number of times an n variable linear constraint was entailed.
- ic_1v_lin_red
- The number of times a 1 variable linear constraint was reduced to a 0 variable constraint.
- ic_2v_lin_red
- The number of times a 2 variable linear constraint was reduced to a 1 variable constraint.
- ic_nv_lin_red
- The number of times an n variable linear constraint was reduced to a 2 or 1 variable constraint.
Users who wish to track activity within their own programs may (if the
wish) use the same mechanism. New event types can be registered (see
list below) and actions recorded by calling the
ic_event(Event)
predicate.
The counters are controlled using the primitives:
- ic_stat(on)
- ic_stat(off)
- Enables/disable collection of statistics. Default is off.
- ic_stat(reset)
- Reset statistics counters.
- ic_stat(print)
- Print statistics counters to the standard output stream.
- ic_stat_get(-Stat)
- Returns a list of CounterName=CounterValue pairs, summarising the
computation since the last reset.
- ic_event(+Name)
- Records the fact that the named event has happened.
- ic_register_event(+Name,+Description)
- Registers a new event type and set the counter to 0. This allows user
defined predicates to record their own events within the same
framework.
Next: General Guidelines for the
Up: IC: A Hybrid Finite
Previous: Introduction
  Index
Warwick Harvey
2002-05-15