package facile

  1. Overview
  2. Docs

Source file fcl_linear.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
(***********************************************************************)
(*                                                                     *)
(*                           FaCiLe                                    *)
(*                 A Functional Constraint Library                     *)
(*                                                                     *)
(*            Nicolas Barnier, Pascal Brisset, LOG, CENA               *)
(*                                                                     *)
(* Copyright 2004 CENA. All rights reserved. This file is distributed  *)
(* under the terms of the GNU Lesser General Public License.           *)
(***********************************************************************)
open Fcl_var
open Fcl_misc.Operators
open Printf
module C = Fcl_cstr

type operator = LessThan | Equal | Diff

let string_of_op = function Equal -> "=" | LessThan -> "<=" | Diff -> "<>"

let min_max_plus_inter a b c d = (a + c, b + d)

let min_max_minus_inter a b c d = (a - d, b - c)

(* x1 <= x2 + d *)
(* specialized binary leq constraint *)
let rec less_than x1 x2 d =
  let name = "less_than" in
  let update _ =
    match Fd.value x1, Fd.value x2 with
      Unk a1, Unk a2 ->
	let max1 = Attr.max a1
	and max2d = Attr.max a2 + d in
	if max1 > max2d then
	  Fd.refine x1 (Fcl_domain.remove_up max2d (Attr.dom a1));
	let min2 = Attr.min a2
	and min1d = Attr.min a1 - d in
	if min1d > min2 then
	  Fd.refine x2 (Fcl_domain.remove_low min1d (Attr.dom a2));
	Fd.max x1 <= Fd.min x2 + d
    | Val v1, Unk a2 ->
	let min2 = Attr.min a2 in
	if min2 < v1 - d then
	  Fd.refine x2 (Fcl_domain.remove_low (v1-d) (Attr.dom a2));
	true
    | Unk a1, Val v2 ->
	let max1 = Attr.max a1 in
	if max1 > v2+d then
	  Fd.refine x1 (Fcl_domain.remove_up (v2+d) (Attr.dom a1));
	true
    | Val v1, Val v2 ->
	v1 <= v2+d || Fcl_stak.fail name
  and check () =
    let min1, max1 = Fd.min_max x1
    and min2, max2 = Fd.min_max x2 in
    max1 <= min2 + d || (if min1 > max2 + d then false else raise C.DontKnow)
  and not () = less_than x2 x1 (-1-d)
  and delay ct =
    delay [Fd.on_min] x1 ct;
    delay [Fd.on_max] x2 ct
  and fprint f =
    Printf.fprintf f "%a <= %a + %d" Fd.fprint x1 Fd.fprint x2 d
  in
  C.create ~not ~check ~name ~fprint update delay

(* x1 = k*x2 *)
(* specialized binary eq and diff constraint *)
(* propagations on domain bounds: less powerful than linear when k = 1 *)
let rec equalc x1 k x2 =
  let bounds () = (Fd.min_max x1, Fd.min_max x2) in
  let name = "equalc" in
  let update _ =
    Fcl_debug.call 'a' (fun s -> fprintf s "equalc - before update: %a = %d * %a\n" Fd.fprint x1 k Fd.fprint x2);
    if k = 0 then begin Fd.unify x1 0; true end else
    let rec loop () =
      match Fd.value x1, Fd.value x2 with
	Unk _, Unk _ ->
	  let cbounds = bounds () in
	  let (c, d) = Fd.min_max x2 in
	  let (kc, kd) = if k > 0 then (k*c, k*d) else (k*d, k*c) in
	  Fd.refine_low_up x1 kc kd;
	  let (a, b) = Fd.min_max x1 in
	  let (ak, bk) = if k > 0 then (a /+ k, b /- k) else (b /+ k, a /- k) in
	  Fd.refine_low_up x2 ak bk;
	  if cbounds <> bounds () then loop () else false
      | Val v1, Unk _a2 -> Fd.unify x2 (v1/k); true
      | Unk _a1, Val v2 -> Fd.unify x1 (k*v2); true
      | Val v1, Val v2 -> v1 = k*v2 || Fcl_stak.fail name in
    loop ()

  and check () =
    match Fd.value x1, Fd.value x2 with
      Unk _, Unk _ ->
	let (a, b) = Fd.min_max x1 in
	let (c, d) = Fd.min_max x2 in
	let (kc, kd) = if k > 0 then (k*c, k*d) else (k*d, k*c) in
	if a > kd || b < kc then false else raise C.DontKnow
    | Val v1, Unk a2 ->
	if not (Fcl_domain.member (v1/k) (Attr.dom a2)) then
	  false
	else raise C.DontKnow
    | Unk a1, Val v2 ->
	if not (Fcl_domain.member (k*v2) (Attr.dom a1)) then
	  false
	else raise C.DontKnow
    | Val v1, Val v2 ->	v1 = k*v2

  and not () = diffc x1 k x2
  and delay ct =
    delay [Fd.on_min; Fd.on_max] x1 ct;
    delay [Fd.on_min; Fd.on_max] x2 ct
  and fprint f =
    Printf.fprintf f "%a = %d * %a" Fd.fprint x1 k Fd.fprint x2 in
  C.create ~name ~fprint ~not ~check update delay

(* x1 <> k*x2 *)
and diffc x1 k x2 =
  let name = "diffc" in
  let update _ =
    if k = 0 then
      match Fd.value x1 with
	Unk a1 -> Fd.refine x1 (Fcl_domain.remove 0 (Attr.dom a1)); true
      |	Val v1 -> v1 <> 0 || Fcl_stak.fail name
    else
      match Fd.value x1, Fd.value x2 with
	Unk _, Unk _ ->
	  let (c, d) = Fd.min_max x2 in
	  let (kc, kd) = if k > 0 then (k*c, k*d) else (k*d, k*c) in
	  let (a, b) = Fd.min_max x1 in
	  (a > kd || b < kc)
      | Val v1, Unk a2 ->
	  v1 mod k <> 0 || not (Fcl_domain.member (v1/k) (Attr.dom a2))
      | Unk a1, Val v2 -> not (Fcl_domain.member (k*v2) (Attr.dom a1))
      | Val v1, Val v2 -> v1 <> k*v2 || Fcl_stak.fail name
	  
  and check () =
    match Fd.value x1, Fd.value x2 with
      Unk _, Unk _ ->
	let (a, b) = Fd.min_max x1 in
	let (c, d) = Fd.min_max x2 in
	let (kc, kd) = if k > 0 then (k*c, k*d) else (k*d, k*c) in
	(a > kd || b < kc) || raise C.DontKnow
    | Val v1, Unk a2 ->
	not (Fcl_domain.member (v1/k) (Attr.dom a2)) || raise C.DontKnow
    | Unk a1, Val v2 ->
	not (Fcl_domain.member (k*v2) (Attr.dom a1)) || raise C.DontKnow
    | Val v1, Val v2 ->	v1 <> k*v2

  and not () = equalc x1 k x2
  and delay ct =
    delay [Fd.on_min; Fd.on_max] x1 ct;
    delay [Fd.on_min; Fd.on_max] x2 ct
  and fprint f =
    Printf.fprintf f "%a <> %d * %a" Fd.fprint x1 k Fd.fprint x2 in
  C.create ~name ~fprint ~not ~check update delay


let remove_constants terms =
  let modif = ref false in
  let r =
    List.fold_left
      (fun (cst, vars) ((a, x) as ax) ->
	match Fd.value x with
	  Unk _ -> (cst, ax::vars)
	| Val v -> modif := true; (cst+a*v, vars))
      (0, [])
      terms in
  if !modif then r else raise Not_found

let compute_inf_sup pos_terms neg_terms =
  let neg_inf_sum, neg_sup_sum =
    List.fold_left
      (fun (inf,sup) (c,x) ->
	let mi, ma = Fd.min_max x in (inf+c*ma, sup+c*mi))
      (0, 0)
      neg_terms in
  List.fold_left
    (fun (inf,sup) (c,x) ->
      let mi,ma = Fd.min_max x in (inf+c*mi, sup+c*ma))
    (neg_inf_sum, neg_sup_sum)
    pos_terms

let part_pos_neg l =
  let rec loop pos neg = function
      [] -> (pos, neg)
    | (0, _) :: axs -> loop pos neg axs
    | (a, _x) as ax :: axs ->
	if a > 0 then loop (ax :: pos) neg axs
	else loop pos (ax :: neg) axs in
  loop [] [] l

(* terms = c *)
let linear_aux terms c =
  let (pos, neg) = part_pos_neg terms in
  let (a, b) = compute_inf_sup pos neg in
  let (ac, bc) = (a - c, b - c) in
  Fd.interval ac bc

(* In case all terms are positive (resp. negative), one can try to refine
   individually each term before computing the expression bounds
   (e.g. avoids an integer overflow in huge magic sequences caused) *)
let basic_refinements pos_terms neg_terms d op =
  if op <> Diff then
    if List.for_all (fun (_a, x) -> Fd.min x >= 0 ) pos_terms &&
      List.for_all (fun (_a, x) -> Fd.max x <= 0 ) neg_terms then begin
	List.iter (fun (a, x) -> Fd.refine_up x (d/a)) pos_terms;
	List.iter (fun (a, x) -> Fd.refine_low x (d/a)) neg_terms end
    else if op = Equal &&
      List.for_all (fun (_a, x) -> Fd.max x <= 0 ) pos_terms &&
      List.for_all (fun (_a, x) -> Fd.min x >= 0 ) neg_terms then begin
	List.iter (fun (a, x) -> Fd.refine_low x (d/a)) pos_terms;
	List.iter (fun (a, x) -> Fd.refine_up x (d/a)) neg_terms end

(* linear constraint a1*x1+...+an*xn=d *)
let rec linear_cstr terms pos_terms neg_terms op d =
  let pos_terms = Fcl_stak.ref pos_terms
  and neg_terms = Fcl_stak.ref neg_terms
  and d = Fcl_stak.ref d in

  let name = "linear" in
  let delay c =
    List.iter
      (fun (a, x) ->
	match op with
	  Diff -> delay [Fd.on_subst] x c
	| Equal -> delay [Fd.on_refine] x c
	| LessThan -> delay [if a > 0 then Fd.on_min else Fd.on_max] x c)
      terms in
  let fprint c =
    List.iter (fun (a,x) -> Printf.fprintf c " +%d.%a" a Fd.fprint x)
      (Fcl_stak.get pos_terms);
    List.iter (fun (a,x) -> Printf.fprintf c " %d.%a" a Fd.fprint x)
      (Fcl_stak.get neg_terms);
    Printf.fprintf c " %s %d" (string_of_op op) (Fcl_stak.get d);
    flush c in

  let not () =
    let terms = Fcl_stak.get pos_terms @ Fcl_stak.get neg_terms
    and d = Fcl_stak.get d in
    match op with
      Equal -> cstr terms Diff d
    | Diff -> cstr terms Equal d
    | LessThan ->
	cstr (List.map (fun (a,x) -> (-a, x)) terms) LessThan (-1 - d)

  and check () =
    let inf_sum, sup_sum =
      compute_inf_sup (Fcl_stak.get pos_terms) (Fcl_stak.get neg_terms) in
    let d = Fcl_stak.get d in
    match op with
      Equal ->
	if inf_sum = sup_sum then inf_sum = d else
	if sup_sum < d || inf_sum > d then false else
	raise C.DontKnow
    | Diff ->
 	if inf_sum = sup_sum then inf_sum <> d else
	sup_sum < d || inf_sum > d || raise C.DontKnow
    | LessThan ->
	sup_sum <= d ||
	(if inf_sum > d then false else begin
	  Fcl_debug.call 'a' (fun s -> fprintf s "Dont know\n");
	  raise C.DontKnow end)

  and update i =
    Fcl_debug.call 'a' (fun s -> fprintf s "linear - before update:"; fprint s; Printf.fprintf s "\n");
    assert(i = 0);
    begin try
      let (cst, new_pos_terms) = remove_constants (Fcl_stak.get pos_terms) in
      if cst <> 0 then Fcl_stak.set d (Fcl_stak.get d - cst);
      Fcl_stak.set pos_terms new_pos_terms
    with Not_found -> () end; (* No new constant positive term *)
    begin try
      let (cst, new_neg_terms) = remove_constants (Fcl_stak.get neg_terms) in
      if cst <> 0 then Fcl_stak.set d (Fcl_stak.get d - cst);
      Fcl_stak.set neg_terms new_neg_terms
    with Not_found -> () end; (* No new constant negative term *)

    let d = Fcl_stak.get d in
    match Fcl_stak.get pos_terms, Fcl_stak.get neg_terms with
      ([], []) -> begin
	match op with
	  Diff when d <> 0 -> true
	| Equal when d = 0 -> true
	| LessThan when d >= 0 -> true
	| _ -> Fcl_stak.fail "linear[]" end
    | ([(a, x)],[]) when op = Equal ->
	if d mod a = 0 then
	  begin Fd.subst x (d/a); true end
	else Fcl_stak.fail "linear[(a,x)]"
    | ([(a, x)],[]) when op = Diff ->
	if d mod a <> 0 then true else
	begin match Fd.value x with
	  Unk attr ->
	    Fd.refine x (Fcl_domain.remove (d/a) (Attr.dom attr)); true
	| Val _ ->
	    Fcl_debug.internal_error
	      "linear#update ([(a, x)],[]) when op = Diff" end
    | ([],[(a, x)]) when op = Equal ->
	if d mod (-a) = 0 then
	  begin Fd.subst x (d/a); true end
	else Fcl_stak.fail "linear[(a,x)]"
    (* propagation on domains *)
    | ([(1, x1)], [(-1,x2)]) when op = Equal -> (* x1 = x2 + d *)
	(*** Printf.printf "x1 = x2 + d"; ***)
	begin match Fd.value x1, Fd.value x2 with
	  Unk a1, Unk a2 ->
	    let d1 = Attr.dom a1 and d2 = Attr.dom a2 in
	    Fd.refine x1 (Fcl_domain.intersection d1 (Fcl_domain.plus d2 d));
	    Fd.refine x2 (Fcl_domain.intersection d2 (Fcl_domain.plus d1 (-d)))
	| _ -> Fcl_debug.internal_error "Arith 1 -1" end;
	false
    | ([(1, x1)], [(-1,x2)]) when op = LessThan -> (* x1 <= x2 + d *)
	begin match Fd.value x1, Fd.value x2 with
	  Unk a1, Unk a2 ->
	    let max1 = Attr.max a1 and min2 = Attr.min a2
	    and max2 = Attr.max a2 and min1 = Attr.min a1 in
	    if max1 > max2 + d then
	      Fd.refine x1 (Fcl_domain.remove_up (max2+d) (Attr.dom a1));
	    if min1 > min2 + d then
	      Fd.refine x2 (Fcl_domain.remove_low (min1-d) (Attr.dom a2));
	    (Fd.max x1 <= Fd.min x2 + d)
	| _ -> Fcl_debug.internal_error "Arith 1 -1" end
    | ([], [(a, x)]) when op = Diff ->
	if d mod (-a) <> 0 then true else begin
	  match Fd.value x with
	    Unk attr ->
	      Fd.refine x (Fcl_domain.remove (d/a) (Attr.dom attr)); true
	  | Val _ ->
	      Fcl_debug.internal_error
		"linear#update ([], [(a, x)]) when op = Diff" end
    | _ ->
	if op = Diff then false	else begin (* waiting for instantiation *)
	  let modif = ref true (* Only for Equal *)
	  and instantiated = ref true
	  and solved = ref false in
	  while !modif || !instantiated do
	    let last_modif = !modif in
	    modif := false;
	    instantiated := false;
	    let inf_sum, sup_sum =
	      compute_inf_sup
		(Fcl_stak.get pos_terms) (Fcl_stak.get neg_terms) in
	    let d_inf_sum = d - inf_sum and d_sup_sum = d - sup_sum in
	    if (op = LessThan && 0 <= d_sup_sum) ||
	    (d_sup_sum = 0 && d_inf_sum = 0) then solved := true
	    else if 0 > d_inf_sum || (op = Equal && 0 < d_sup_sum) then
	      Fcl_stak.fail "linear d_inf_sum"
            (* We stop here if only instantiated *)
	    else if last_modif then begin
	      (* Let's update bounds of variables *)
	      let update_pos (a, x) =
		match Fd.value x with
		  Unk ax ->
		    let domx = Attr.dom ax in
		    let mi = Fcl_domain.min domx and
			ma = Fcl_domain.max domx in
		    let new_sup = min ma ((d_inf_sum + a*mi)/a) in
		    if op = Equal then begin
		      let new_inf = max mi ((d_sup_sum + a*ma) /+ a) in
		      if new_sup < ma || new_inf > mi then begin
			modif:=true;
			Fd.refine_low_up x new_inf new_sup end end
		    else begin
		      if new_sup < ma then begin
			Fd.refine x (Fcl_domain.remove_up new_sup domx);
			if not (Fd.is_var x) then instantiated := true end
		    end
                (* because it's maybe the last variable of the expression and
		   the constraint is now solved *)
		| Val vx ->
		    let new_sup = ((d_inf_sum + a*vx)/a) in
		    if new_sup < vx then Fcl_stak.fail "Arith.update_pos sup";
		    if op = Equal then
		      let new_inf = (d_sup_sum + a*vx) /+ a in
		      if new_inf > vx then
			Fcl_stak.fail "Arith.update_pos inf" in
	      List.iter update_pos (Fcl_stak.get pos_terms);
	      
	      let update_neg (a, x) =
		match Fd.value x with
		  Unk ax ->
		    let domx = Attr.dom ax in
		    let mi = Fcl_domain.min domx
		    and ma = Fcl_domain.max domx in
		    let new_inf = max mi ((d_inf_sum + a*ma) /+ a) in
		    if op = Equal then begin
		      let new_sup = min ma ((d_sup_sum + a*mi)/a) in
		      if new_sup < ma || new_inf > mi then begin
			modif:=true;
			Fd.refine_low_up x new_inf new_sup end end
		    else begin
		      if new_inf > mi then begin
			Fcl_debug.call 'a'
			  (fun s -> fprintf s
			      "linear#update, refine_low %d\n" new_inf);
			Fd.refine x (Fcl_domain.remove_low new_inf domx);
			if not (Fd.is_var x) then instantiated := true end end
               (* because it's maybe the last variable of the expression and
		  the constraint is now solved *)	
		| Val vx ->
		    let new_inf = (d_inf_sum + a*vx) /+ a in
		    if new_inf > vx then Fcl_stak.fail "Arith.update_neg inf";
		    if op = Equal then
		      let new_sup = ((d_sup_sum + a*vx)/a) in
		      if new_sup < vx then
			Fcl_stak.fail "Arith.update_neg sup" in
	      List.iter update_neg (Fcl_stak.get neg_terms)
	    end
	  done;
	  !solved
	end in
  let init () =
    basic_refinements
      (Fcl_stak.get pos_terms) (Fcl_stak.get neg_terms) (Fcl_stak.get d) op;
    ignore (update 0) in

  C.create ~init ~name ~fprint ~check ~not update delay

and cstr (terms : (int*Fd.t) list) op dd =
  let pos_terms, neg_terms = part_pos_neg terms in
  match pos_terms, neg_terms, op, dd with
    [1, x1], [-1, x2], LessThan, d -> (* x1 <= x2 + d *)
      less_than x1 x2 d
  | [1, x1], [k, x2], Equal, 0 when k <> -1 -> (* x1 = -k * x2 *)
      equalc x1 (0 - k) x2
  | [k, x1], [-1, x2], Equal, 0 when k <> 1 -> (* k * x1 = x2 *)
      equalc x2 k x1
  | [1, x1], [k, x2], Diff, 0 when k <> -1 -> (* x1 <> -k * x2 *)
      diffc x1 (0 - k) x2
  | [k, x1], [-1, x2], Diff, 0 when k <> 1 -> (* k * x1 <> x2 *)
      diffc x2 k x1
  | _ -> linear_cstr terms pos_terms neg_terms op dd


(*** Automatic handling of boolean sub-expressions ***)

let boolsum_threshold = ref 5
let get_boolsum_threshold () = !boolsum_threshold
let set_boolsum_threshold x = boolsum_threshold := x

let is_boolean x =
  let min_x, max_x = Fcl_var.Fd.min_max x in min_x = 0 && max_x = 1

(* flatten : ('a * 'b) list -> ('a * 'b list) list -> ('a * 'b) list *)
let rec flatten rest = function
    [] -> rest
  | (a, l) :: ls ->
      List.fold_left (fun r x -> (a,x)::r) (flatten rest ls) l

(* Optimized boolean sums are used for boolean subexprs larger than
   boolsum only *)
let cstr ?(boolsum = !boolsum_threshold) terms op d =
  let (bools, others) = List.partition (fun (_a, x) -> is_boolean x) terms in
  (* partition of bools by coefficient *)
  let h = Hashtbl.create 17 in
  let add (a, x) =
    try
      let refxs = Hashtbl.find h a in refxs := x :: !refxs
    with Not_found -> Hashtbl.add h a (ref [x]) in
  List.iter add bools;
  (* coefficients with less than boolsum_threshold variables
     are discarded, otherwise an optimized boolean sum is build
     and substituted to the whole term *)
  let bool_sums = ref [] and short_bools = ref [] in
  Hashtbl.iter
    (fun a refxs ->
      if List.length !refxs >= boolsum then begin
	let bools = Array.of_list !refxs in
	Fcl_debug.call 'a' (fun c -> Printf.fprintf c "boolean sum (size %d) optimized\n" (Array.length bools));
	let sumxs = Fcl_boolean.sum bools in
	bool_sums := (a, sumxs) :: !bool_sums end
      else short_bools := (a, !refxs) :: !short_bools)
    h;
  let short_bools = flatten [] !short_bools in
  cstr (!bool_sums @ short_bools @ others) op d


(* x1 = x2 + d *)
let shift_cstr x1 x2 d =
  let update _ =
    match Fd.value x1, Fd.value x2 with
      Unk a1, Unk a2 ->
	let max1 = Attr.max a1
	and max2d = Attr.max a2 + d in
	if max1 > max2d then
	  Fd.refine x1 (Fcl_domain.remove_up max2d (Attr.dom a1))
	else if max1 < max2d then
	  Fd.refine x2 (Fcl_domain.remove_up (max1-d) (Attr.dom a2));
	let min2 = Attr.min a2
	and min1d = Attr.min a1 - d in
	if min1d < min2 then
	  Fd.refine x1 (Fcl_domain.remove_low (min2+d) (Attr.dom a1))
	else if min1d > min2 then
	  Fd.refine x2 (Fcl_domain.remove_low min1d (Attr.dom a2));
	max1 <= min2 + d
    | Val v1, Unk _ ->
	Fd.unify x2 (v1-d);
	true
    | Unk _, Val v2 ->
	Fd.unify x1 (v2+d);
	true
    | Val v1, Val v2 ->
	if v1 <> v2+d then Fcl_stak.fail "shift";
	true
  and delay ct =
    delay [Fd.on_min; Fd.on_max] x1 ct;
    delay [Fd.on_max; Fd.on_min] x2 ct
  and fprint f =
    Printf.fprintf f "%a = %a + %d" Fd.fprint x1 Fd.fprint x2 d in
  C.create ~name:"shift" ~fprint update delay
OCaml

Innovation. Community. Security.