Source file solveBounds.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
let src =
Logs.Src.create "OcplibSimplex.SolveBounds"
~doc:"A module providing arithmetical utilities for solving the simplex"
module type S = sig
module Core : CoreSig.S
val solve : Core.t -> Core.t
val maximize : Core.t -> Core.P.t -> Core.t * (Core.P.t * bool) option
end
module Make(Core : CoreSig.S) : S with module Core = Core = struct
module Core = Core
module Result = Result.Make(Core)
open Core
let gauss_pivot s p x c =
let p, _ = P.replace s R.m_one (P.remove x p) in
let c = R.div R.m_one c in
if R.is_one c then p
else P.fold (fun y d q -> fst (P.replace y (R.mult c d) q)) p P.empty
exception Out of Var.t * R.t * var_info * SX.t
let look_for_next_pivot si pi non_basic =
let status = si.vstatus in
let is_lower =
match status with
| ValueOK -> assert false | LowerKO -> 1 | UpperKO -> -1
in
try
P.iter
(fun x coef ->
let xi,use = try MX.find x non_basic with Not_found -> assert false in
let c = is_lower * R.sign coef in
assert (c <> 0);
if c > 0 && not (equals_optimum xi.value xi.maxi) then
raise (Out (x, coef, xi, use));
if c < 0 && not (equals_optimum xi.value xi.mini) then
raise (Out (x, coef, xi, use));
)pi;
None
with Out (x, c, xi, use) -> Some (x, c, xi, use)
let adapt_valuation_of_newly_basic old_si new_si old_xi c_x =
let diff = R2.div_by_const c_x (R2.sub new_si.value old_si.value) in
{ old_xi with value = R2.add diff old_xi.value }
let rec solve_rec env round =
Core.debug (Format.sprintf "[solve] round %d" round) env (Result.get None);
Core.check_invariants env (Result.get None);
if SX.is_empty env.fixme then {env with status = SAT}
else
let s = SX.choose env.fixme in
let fixme = SX.remove s env.fixme in
let si, p = try MX.find s env.basic with Not_found -> assert false in
match look_for_next_pivot si p env.non_basic with
| None -> {env with fixme = SX.empty; status = UNSAT s}
| Some(x, c, xi, use_x) ->
Logs.debug ~src (fun p ->
p ~header:"[solve_rec]"
"pivot basic %a and non-basic %a@."
Var.print s Var.print x
);
let basic = MX.remove s env.basic in
let non_basic = MX.remove x env.non_basic in
let q = gauss_pivot s p x c in
assert (SX.mem s use_x);
let use_x = SX.remove s use_x in
let old_si = si in
let si, changed = Core.ajust_value_of_non_basic si in
assert (changed);
let old_xi = xi in
let xi = adapt_valuation_of_newly_basic old_si si xi c in
let xi = ajust_status_of_basic xi in
let diff_xi_val = R2.sub xi.value old_xi.value in
let fixme =
if xi.vstatus == ValueOK then fixme
else SX.add x fixme
in
let non_basic =
P.fold
(fun y _ non_basic ->
let yi, use_y =
try MX.find y non_basic with Not_found -> assert false in
MX.add y (yi, SX.add x (SX.remove s use_y)) non_basic
)(P.remove s q) non_basic
in
let non_basic = MX.add s (si, SX.add x use_x) non_basic in
let basic, non_basic, fixme =
SX.fold
(fun t (basic, non_basic, fixme) ->
let ti0, r = try MX.find t basic with Not_found -> assert false in
let cx = try P.find x r with Not_found -> assert false in
let diff_cx = R2.mult_by_const cx diff_xi_val in
let ti = {ti0 with value = R2.add ti0.value diff_cx} in
let ti = ajust_status_of_basic ti in
let r', changed = P.subst x q r in
let non_basic =
List.fold_left
(fun non_basic (z, vstt) ->
match vstt with
| P.Exists -> non_basic
| P.New ->
let zi, use_z =
try MX.find z non_basic with Not_found -> assert false
in
MX.add z (zi, SX.add t use_z) non_basic
| P.Removed ->
if Var.compare z x = 0 then non_basic
else
let zi, use_z =
try MX.find z non_basic with Not_found -> assert false
in
MX.add z (zi, SX.remove t use_z) non_basic
)non_basic changed
in
let basic = MX.add t (ti, r') basic in
let fixme =
if ti.vstatus == ValueOK then
if ti0.vstatus == ValueOK then fixme
else SX.remove t fixme
else SX.add t fixme
in
basic, non_basic, fixme
)use_x (basic, non_basic, fixme)
in
let basic = MX.add x (xi, q) basic in
let env = {env with fixme; basic; non_basic} in
env.nb_pivots := !(env.nb_pivots) + 1;
solve_rec env (round + 1)
let solve env =
Core.debug "[entry of solve]" env (Result.get None);
Core.check_invariants env (Result.get None);
let env =
match env.Core.status with
| Core.UNSAT _ | Core.SAT -> env
| Core.UNK -> solve_rec env 1
in
Core.debug "[exit of solve]" env (Result.get None);
Core.check_invariants env (Result.get None);
env
let non_basic_to_maximize {non_basic=n_b; _} opt =
let acc = ref None in
try
P.iter
(fun x c ->
let xi, use = try MX.find x n_b with Not_found -> assert false in
let sg = R.sign c in
if sg > 0 && not (equals_optimum xi.value xi.maxi) ||
sg < 0 && not (equals_optimum xi.value xi.mini) then begin
acc := Some (x, c, xi, use, sg > 0);
raise Exit
end
)opt;
!acc
with Exit -> !acc
type 'a maximiza_basic =
| Free
| Stuck
| Progress of 'a
let basic_var_to_pivot_for_maximization =
let choose_best_pivot acc s si p c_px bnd_opt is_min =
match bnd_opt with
| None ->
if !acc = Stuck then acc := Free
| Some {bvalue = bnd; _} ->
let tmp = if is_min then R2.sub si.value bnd else R2.sub bnd si.value in
let ratio = R2.div_by_const (R.abs c_px) tmp in
begin
match !acc with
| Free | Stuck ->
acc := Progress (ratio, s, si, p, c_px, bnd, is_min)
| Progress (r_old,_,_,_,_,_,_) ->
if R2.compare r_old ratio > 0 then
acc := Progress (ratio, s, si, p, c_px, bnd, is_min)
end;
if R2.is_zero ratio then raise Exit
in
fun {basic; _} x use_x should_incr_x ->
let acc = ref (if SX.is_empty use_x then Free else Stuck) in
try
SX.iter
(fun s ->
let si, p = try MX.find s basic with Not_found -> assert false in
let c_px = try P.find x p with Not_found -> assert false in
let sg = R.sign c_px in
assert (sg <> 0);
match should_incr_x, sg > 0, si.mini, si.maxi with
| true , true , _, mx_opt ->
choose_best_pivot acc s si p c_px mx_opt false
| true , false, mn_opt, _ ->
choose_best_pivot acc s si p c_px mn_opt true
| false, true , mn_opt, _ ->
choose_best_pivot acc s si p c_px mn_opt true
| false, false, _, mx_opt ->
choose_best_pivot acc s si p c_px mx_opt false
)use_x;
!acc
with Exit -> !acc
let can_fix_valuation_without_pivot should_incr xi ratio_opt =
if should_incr then
match xi.maxi, ratio_opt with
| None, _ -> None
| Some {bvalue = bnd; _}, Some ratio ->
let diff = R2.sub bnd xi.value in
if R2.compare diff ratio < 0 then Some ({xi with value = bnd}, diff)
else None
| Some {bvalue = bnd; _}, None ->
let diff = R2.sub bnd xi.value in
Some ({xi with value = bnd}, diff)
else
match xi.mini, ratio_opt with
| None, _ -> None
| Some {bvalue = bnd; _}, Some ratio ->
let diff = R2.sub xi.value bnd in
if R2.compare diff ratio < 0 then Some ({xi with value = bnd}, diff)
else None
| Some {bvalue = bnd; _}, None ->
let diff = R2.sub xi.value bnd in
Some ({xi with value = bnd}, diff)
let update_valuation_without_pivot
({basic; non_basic; _ } as env) x use_x new_xi diff _should_incr =
let non_basic = MX.add x (new_xi, use_x) non_basic in
let diff = if _should_incr then diff else R2.minus diff in
let basic =
SX.fold
(fun s basic ->
let si, p = try MX.find s basic with Not_found -> assert false in
let cx = try P.find x p with Not_found -> assert false in
assert (not (R.is_zero cx));
let delta = R2.mult_by_const cx diff in
let si = {si with value = R2.add si.value delta} in
MX.add s (si, p) basic
)use_x basic
in
{env with basic; non_basic}
let rec maximize_rec env opt rnd =
Logs.debug ~src (fun p -> p "[maximize_rec] round %d // OPT = %a@." rnd P.print opt);
Core.debug
(Format.sprintf "[maximize_rec] round %d" rnd) env (Result.get None);
Core.check_invariants env (Result.get None);
match non_basic_to_maximize env opt with
| None -> Logs.debug (fun p -> p "max reached@.");
rnd, env, Some (opt, true)
| Some (_x, _c, _xi, _use_x, _should_incr) ->
Logs.debug (fun p -> p "pivot non basic var %a ?@." Var.print _x);
match basic_var_to_pivot_for_maximization env _x _use_x _should_incr with
| Free ->
Logs.debug (fun p -> p "non basic %a not constrained by basic vars: Set it to max@."
Var.print _x);
begin
match can_fix_valuation_without_pivot _should_incr _xi None with
| Some (new_xi, diff) ->
Logs.debug (fun p -> p
"No --> I can set value of %a to min/max WO pivot@."
Var.print _x);
let env, opt =
update_valuation_without_pivot
env _x _use_x new_xi diff _should_incr, opt
in
maximize_rec env opt (rnd + 1)
| None ->
Logs.debug (fun p -> p
"no pivot finally(no upper bnd), pb unbounded@.");
rnd, env, Some (opt, false)
end
| Stuck ->
Logs.debug (fun p -> p "no pivot finally, pb unbounded@.");
rnd, env, Some (opt, false)
| Progress (ratio, s, si, p, c_px, bnd, _is_min) ->
Logs.debug (fun p -> p "pivot with basic var %a ?@." Var.print s);
let env, opt =
match
can_fix_valuation_without_pivot _should_incr _xi (Some ratio) with
| Some (new_xi, diff) ->
Logs.debug (fun p -> p
"No --> I can set value of %a to min/max WO pivot@."
Var.print _x);
update_valuation_without_pivot
env _x _use_x new_xi diff _should_incr, opt
| None ->
let x = _x in
let c = c_px in
let use_x = _use_x in
let xi = _xi in
Logs.debug (fun p -> p
"[maximize_rec] pivot basic %a and non-basic %a@."
Var.print s Var.print x);
let basic = MX.remove s env.basic in
let non_basic = MX.remove x env.non_basic in
let q = gauss_pivot s p x c in
assert (SX.mem s use_x);
let use_x = SX.remove s use_x in
let old_si = si in
let si = {si with value = bnd} in
let old_xi = xi in
let xi = adapt_valuation_of_newly_basic old_si si xi c in
let xi = ajust_status_of_basic xi in
let diff_xi_val = R2.sub xi.value old_xi.value in
assert(xi.vstatus == ValueOK);
let non_basic =
P.fold
(fun y _ non_basic ->
let yi, use_y =
try MX.find y non_basic with Not_found -> assert false in
MX.add y (yi, SX.add x (SX.remove s use_y)) non_basic
)(P.remove s q) non_basic
in
let non_basic = MX.add s (si, SX.add x use_x) non_basic in
let basic, non_basic =
SX.fold
(fun t (basic, non_basic) ->
let ti0, r =
try MX.find t basic with Not_found -> assert false in
let cx = try P.find x r with Not_found -> assert false in
let diff_cx = R2.mult_by_const cx diff_xi_val in
let ti = {ti0 with value = R2.add ti0.value diff_cx} in
let ti = ajust_status_of_basic ti in
let r', changed = P.subst x q r in
let non_basic =
List.fold_left
(fun non_basic (z, vstt) ->
match vstt with
| P.Exists -> non_basic
| P.New ->
let zi, use_z =
try MX.find z non_basic
with Not_found -> assert false
in
MX.add z (zi, SX.add t use_z) non_basic
| P.Removed ->
if Var.compare z x = 0 then non_basic
else
let zi, use_z =
try MX.find z non_basic
with Not_found -> assert false
in
MX.add z (zi, SX.remove t use_z) non_basic
)non_basic changed
in
let basic = MX.add t (ti, r') basic in
assert(ti.vstatus == ValueOK);
basic, non_basic
)use_x (basic, non_basic)
in
let basic = MX.add x (xi, q) basic in
{env with basic; non_basic}, (fst (P.subst x q opt))
in
env.nb_pivots := !(env.nb_pivots) + 1;
maximize_rec env opt (rnd + 1)
let maximize env opt0 =
let env = solve env in
match env.status with
| UNK -> assert false
| UNSAT _ -> env, None
| SAT ->
Logs.debug (fun p -> p "[maximize] pb SAT! try to maximize %a@." P.print opt0);
let {basic; non_basic; _} = env in
let unbnd = ref false in
let opt =
P.fold
(fun x c acc ->
if MX.mem x non_basic then fst (P.accumulate x c acc)
else
try fst (P.append acc c (snd (MX.find x basic)))
with Not_found ->
unbnd := true;
fst (P.accumulate x c acc)
)opt0 P.empty
in
if !unbnd then env, Some (opt, false)
else
begin
Logs.debug (fun p -> p "start maximization@.");
let rnd, env, is_max = maximize_rec env opt 1 in
Core.check_invariants env (Result.get is_max);
Logs.debug (fun p -> p "[maximize] pb SAT! Max found ? %b for %a == %a@."
(is_max != None) P.print opt0 P.print opt);
Logs.debug (fun p -> p "maximization done after %d steps@." rnd);
env, is_max
end
end