Repairing Conflicts

## 13.3 Repairing Conflicts

- Combining Repair with IC Propagation

If all the constraints of a problem are monitored for conflicts, then the problem can be solved by:

- Finding an initial assignment of tentative values for all the problem variables
- Finding a constraint in conflict, and labelling a variable in this constraint
- Instantiating the remaining variables to their tentative values, when there are no more constraints in conflict

Consider a satisfiability problem with each clause represented by an ic constraint, whose form is illustrated by the following example: `(X1 or neg X2 or X3 $= 1`. This represents the clause X1 ∨ ¬ X2 ∨ X3.

To apply conflict minimisation to this problem use the predicate:

- `tent_init` to find an initial solution
- `conflict_constraints` and `term_variables` to find a variable to label
- `set_to_tent` to set the remaining variables to their tentative values

The code is as follows:

>     prop_sat_1(Vars) :-
>         Vars = [X1,X2,X3],
>         tent_init(Vars),
>         (X1 or neg X2 or X3 $= 1) r_conflict cs,
>         (neg X1 or neg X2 $= 1) r_conflict cs,
>         (X2 or neg X3 $= 1) r_conflict cs,
>         min_conflicts(Vars).

>     tent_init(List) :-
>         ( foreach(Var,List) do Var tent_set 1 ).

>     min_conflicts(Vars) :-
>         conflict_constraints(cs,List),
>         ( List = [] -> set_to_tent(Vars) ;
>           List = [Constraint|_] ->
>             term_variables(Constraint,[Var|_]),
>             guess(Var),
>             min_conflicts(Vars)
>         ).

>     guess(0).
>     guess(1).

>     set_to_tent(Term) :-
>        Term tent_get Tent,
>        Term = Tent.

The value choice predicate `guess` is naive. Since the variable occurs in a conflict constraint it would arguably be better to label it to another value. This would be implemented as follows:

>     guess(Var) :-
>         Var tent_get Value,
>         ( Value = 0 -> (Var=1 ; Var=0)
>         ; Value = 1 -> (Var=0 ; Var=1)
>         ).

### 13.3.1 Combining Repair with IC Propagation

To illustrate a combination of repair with ic propagation we tackle a scheduling example. The problem involves tasks with unknown start times, and known durations, which are related by a variety of temporal constraints. These temporal constraints are handled, for the purposes of this example, by ic. The temporal constraints are encoded thus:

>     before(TimePoint1,Interval,TimePoint2) :-
>         TimePoint1+Interval #=< TimePoint2.

`TimePoint1` and `TimePoint2` are variables (or numbers), but we assume, for this example, that the `Interval` is a number. This constraint can enforce a minimum separation between start times, or a maximum separation (if the `Interval` is negative). It can also enforce constraints between end times, by adjusting the `Interval` to account for the task durations.

Additionally we assume that certain tasks require the same resource and cannot therefore proceed at the same time. The resource constraint is encoded thus:

>     noclash(Start1,Duration1,Start2,_) :-
>         Start2 #>= Start1+Duration1.
>     noclash(Start1,_,Start2,Duration2) :-
>         Start1 #>= Start2+Duration2.

Suppose the requirement is to complete the schedule as early as possible. To express this we introduce a last time point `End` which is constrained to come after all the tasks. Ignoring the resource constraints, the temporal constraints are easily handled by ic. The optimal solution is obtained simply by posting the temporal constraints and then instantiating each start time to the lowest value in its domain.

To deal with the resource constraints conflict minimisation is used. The least (i.e. optimal) value in the domain of each variable is chosen as its tentative value, at each node of the search tree.

To fix a constraint in conflict, we simply invoke its nondetermistic definition, and ECLiPSe then unfolds the first clause and sends the new temporal constraint `Start2 #>= Start1+Duration1` to ic. On backtracking, the second clause will be unfolded instead.

After fixing a resource constraint, and posting a new temporal constraint, ic propagation takes place, and then the tentative values are changed to the new ic lower bounds.

The code is simply this:

>     :- lib(ic), lib(repair), lib(branch_and_bound).
>     schedule(Starts,End) :-
>         Starts = [S1,S2,...,End],
>         Starts :: 0..1000,
>         before(S2,5,S1),
>         before(S1,8,End),
>         ...
>         noclash(S1,4,S2,8) r_conflict resource_cons,
>         ...
>         minimize(repair_ic(Starts),End).

>     repair_ic(Starts) :-
>         set_tent_to_min(Starts),
>         conflict_constraints(resource_cons,List),
>         ( List = [] ->
>             set_to_tent(Starts)
>         ; List = [Constraint|_] ->
>             call(Constraint),
>             repair_ic(Starts)
>         ).

>     set_tent_to_min(Vars) :-
>         (  foreach(Var,Vars)
>         do
>              get_min(Var,Min),
>              Var tent_set Min
>         ).

This code is much more robust than the traditional code for solving the bridge scheduling example from [27]. The code is in the examples directory file `bridge_repair.pl`.

This algorithm uses the ic solver to:

- Enforce the consistency of the temporal constraints
- Set the tentative values to an optimal solution (of this relaxation of the original problem)

This technique is called probing. The use of the eplex solver, instead of ic for probing is described in chapter 17 below.

> Repair naturally supports conflict minimisation. This algorithm can be combined with other solvers, such as ic, and with optimization.

> Figure 13.3: Conflict Minimisation