@@ -23,7 +23,9 @@ meta def nat.to_pexpr : ℕ → pexpr
23
23
| 1 := ``(1 )
24
24
| n := if n % 2 = 0 then ``(bit0 %%(nat.to_pexpr (n/2 ))) else ``(bit1 %%(nat.to_pexpr (n/2 )))
25
25
26
+
26
27
open native
28
+
27
29
namespace linarith
28
30
29
31
section lemmas
@@ -177,6 +179,10 @@ def cmp : linexp → linexp → ordering
177
179
else if z2 < z1 then ordering.gt
178
180
else cmp t1 t2
179
181
182
+ /-- `l.vars` returns the list of variables that occur in `l`. -/
183
+ def vars (l : linexp) : list ℕ :=
184
+ l.map prod.fst
185
+
180
186
end linexp
181
187
182
188
section datatypes
@@ -216,11 +222,16 @@ The represented term is `coeffs.sum (λ ⟨k, v⟩, v * Var[k])`.
216
222
str determines the direction of the comparison -- is it < 0, ≤ 0, or = 0?
217
223
-/
218
224
@[derive inhabited]
219
- meta structure comp : Type :=
225
+ structure comp : Type :=
220
226
(str : ineq)
221
227
(coeffs : linexp)
222
228
223
- meta inductive comp_source : Type
229
+ /-- `c.vars` returns the list of variables that appear in the linear expression contained in `c`. -/
230
+ def comp.vars : comp → list ℕ :=
231
+ linexp.vars ∘ comp.coeffs
232
+
233
+ @[derive inhabited]
234
+ inductive comp_source : Type
224
235
| assump : ℕ → comp_source
225
236
| add : comp_source → comp_source → comp_source
226
237
| scale : ℕ → comp_source → comp_source
@@ -230,17 +241,76 @@ meta def comp_source.flatten : comp_source → rb_map ℕ ℕ
230
241
| (comp_source.add c1 c2) := (comp_source.flatten c1).add (comp_source.flatten c2)
231
242
| (comp_source.scale n c) := (comp_source.flatten c).map (λ v, v * n)
232
243
233
- meta def comp_source.to_string : comp_source → string
244
+ def comp_source.to_string : comp_source → string
234
245
| (comp_source.assump e) := to_string e
235
246
| (comp_source.add c1 c2) := comp_source.to_string c1 ++ " + " ++ comp_source.to_string c2
236
247
| (comp_source.scale n c) := to_string n ++ " * " ++ comp_source.to_string c
237
248
238
249
meta instance comp_source.has_to_format : has_to_format comp_source :=
239
250
⟨λ a, comp_source.to_string a⟩
240
251
241
- meta structure pcomp :=
252
+ /--
253
+ A `pcomp` stores a linear comparison `Σ cᵢ*xᵢ R 0`,
254
+ along with information about how this comparison was derived.
255
+
256
+ The original expressions fed into `linarith` are each assigned a unique natural number label.
257
+ The *historical set* `pcomp.history` stores the labels of expressions
258
+ that were used in deriving the current `pcomp`.
259
+
260
+ Variables are also indexed by natural numbers. The sets `pcomp.effective`, `pcomp.implicit`,
261
+ and `pcomp.vars` contain variable indices.
262
+
263
+ * `pcomp.vars` contains the variables that appear in `pcomp.c`. We store them in `pcomp` to
264
+ avoid recomputing the set, which requires folding over a list. (TODO: is this really needed?)
265
+ * `pcomp.effective` contains the variables that have been effectively eliminated from `pcomp`.
266
+ A variable `n` is said to be *effectively eliminated* in `pcomp` if the elimination of `n`
267
+ produced at least one of the ancestors of `pcomp`.
268
+ * `pcomp.implicit` contains the variables that have been implicitly eliminated from `pcomp`.
269
+ A variable `n` is said to be *implicitly eliminated* in `pcomp` if it satisfies the following
270
+ properties:
271
+ - There is some `ancestor` of `pcomp` such that `n` appears in `ancestor.vars`.
272
+ - `n` does not appear in `pcomp.vars`.
273
+ - `n` was not effectively eliminated.
274
+
275
+ We track these sets in order to compute whether the history of a `pcomp` is *minimal* .
276
+ Checking this directly is expensive, but effective approximations can be defined in terms of these
277
+ sets. During the variable elimination process, a `pcomp` with non-minimal history can be discarded.
278
+ -/
279
+ meta structure pcomp : Type :=
242
280
(c : comp)
243
281
(src : comp_source)
282
+ (history : rb_set ℕ)
283
+ (effective : rb_set ℕ)
284
+ (implicit : rb_set ℕ)
285
+ (vars : rb_set ℕ)
286
+
287
+ /--
288
+ Any comparison whose history is not minimal is redundant,
289
+ and need not be included in the new set of comparisons.
290
+
291
+ `elimed_ge : ℕ` is a natural number such that all variables with index ≥ `elimed_ge` have been
292
+ removed from the system.
293
+
294
+ This test is an overapproximation to minimality. It gives necessary but not sufficient conditions.
295
+ If the history of `c` is minimal, then `c.maybe_minimal` is true,
296
+ but `c.maybe_minimal` may also be true for some `c` with minimal history.
297
+ Thus, if `c.maybe_minimal` is false, `c` is known not to be minimal and must be redundant.
298
+
299
+ See http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.51.493&rep=rep1&type=pdf p.13
300
+ (Theorem 7).
301
+
302
+ The condition described there considers only implicitly eliminated variables that have been
303
+ officially eliminated from the system. This is not the case for every implicitly eliminated variable.
304
+ Consider eliminating `z` from `{x + y + z < 0, x - y - z < 0}`. The result is the set
305
+ `{2*x < 0}`; `y` is implicitly but not officially eliminated.
306
+
307
+ This implementation of Fourier-Motzkin elimination processes variables in decreasing order of
308
+ indices. Immediately after a step that eliminates variable `k`, variable `k'` has been eliminated
309
+ iff `k' ≥ k`. Thus we can compute the intersection of officially and implicitly eliminated variables
310
+ by taking the set of implicitly eliminated variables with indices ≥ `elimed_ge`.
311
+ -/
312
+ meta def pcomp.maybe_minimal (c : pcomp) (elimed_ge : ℕ) : bool :=
313
+ c.history.size ≤ 1 + ((c.implicit.filter (≥ elimed_ge)).union c.effective).size
244
314
245
315
/-- `comp` has a lex order. First the `ineq`s are compared, then the `coeff`s. -/
246
316
meta def comp.cmp : comp → comp → ordering
@@ -268,10 +338,47 @@ meta def comp.add (c1 c2 : comp) : comp :=
268
338
⟨c1.str.max c2.str, c1.coeffs.add c2.coeffs⟩
269
339
270
340
meta def pcomp.scale (c : pcomp) (n : ℕ) : pcomp :=
271
- ⟨c .c.scale n, comp_source.scale n c.src⟩
341
+ {c with c := c .c.scale n, src := c.src.scale n}
272
342
273
- meta def pcomp.add (c1 c2 : pcomp) : pcomp :=
274
- ⟨c1.c.add c2.c, comp_source.add c1.src c2.src⟩
343
+ /--
344
+ `pcomp.add c1 c2 elim_var` creates the result of summing the linear comparisons `c1` and `c2`,
345
+ during the process of eliminating the variable `elim_var`.
346
+ The computation assumes, but does not enforce, that `elim_var` appears in both `c1` and `c2`
347
+ and does not appear in the sum.
348
+
349
+ Computing the sum of the two comparisons is easy; the complicated details lie in tracking the
350
+ additional fields of `pcomp`.
351
+
352
+ * The historical set `pcomp.history` of `c1 + c2` is the union of the two historical sets.
353
+ * We recompute the variables that appear in `c1 + c2` from the newly created `linexp`,
354
+ since some may have been implicitly eliminated.
355
+ * The effectively eliminated variables of `c1 + c2` are the union of the two effective sets,
356
+ with `elim_var` inserted.
357
+ * The implicitly eliminated variables of `c1 + c2` are those that appear in at least one of
358
+ `c1.vars` and `c2.vars` but not in `(c1 + c2).vars`, excluding `elim_var`.
359
+ -/
360
+ meta def pcomp.add (c1 c2 : pcomp) (elim_var : ℕ) : pcomp :=
361
+ let c := c1.c.add c2.c,
362
+ src := c1.src.add c2.src,
363
+ history := c1.history.union c2.history,
364
+ vars := native.rb_set.of_list c.vars,
365
+ effective := (c1.effective.union c2.effective).insert elim_var,
366
+ implicit := ((c1.vars.union c2.vars).sdiff vars).erase elim_var in
367
+ ⟨c, src, history, effective, implicit, vars⟩
368
+
369
+ /--
370
+ `pcomp.assump c n` creates a `pcomp` whose comparison is `c` and whose source is
371
+ `comp_source.assump n`, that is, `c` is derived from the `n`th hypothesis.
372
+ The history is the singleton set `{n}`.
373
+ No variables have been eliminated (effectively or implicitly).
374
+ -/
375
+ meta def pcomp.assump (c : comp) (n : ℕ) : pcomp :=
376
+ { c := c,
377
+ src := comp_source.assump n,
378
+ history := mk_rb_set.insert n,
379
+ effective := mk_rb_set,
380
+ implicit := mk_rb_set,
381
+ vars := rb_set.of_list c.vars }
275
382
276
383
meta instance pcomp.to_format : has_to_format pcomp :=
277
384
⟨λ p, to_fmt p.c.coeffs ++ to_string p.c.str ++ " 0" ⟩
@@ -288,42 +395,36 @@ end datatypes
288
395
section fm_elim
289
396
290
397
/-- If `c1` and `c2` both contain variable `a` with opposite coefficients,
291
- produces `v1`, `v2`, and `c` such that `a` has been cancelled in `c := v1*c1 + v2*c2`. -/
292
- meta def elim_var (c1 c2 : comp) (a : ℕ) : option (ℕ × ℕ × comp ) :=
398
+ produces `v1`, `v2` such that `a` has been cancelled in `v1*c1 + v2*c2`. -/
399
+ meta def elim_var (c1 c2 : comp) (a : ℕ) : option (ℕ × ℕ) :=
293
400
let v1 := c1.coeff_of a,
294
401
v2 := c2.coeff_of a in
295
402
if v1 * v2 < 0 then
296
403
let vlcm := nat.lcm v1.nat_abs v2.nat_abs,
297
404
v1' := vlcm / v1.nat_abs,
298
405
v2' := vlcm / v2.nat_abs in
299
- some ⟨v1', v2', comp.add (c1.scale v1') (c2.scale v2') ⟩
406
+ some ⟨v1', v2'⟩
300
407
else none
301
408
302
409
meta def pelim_var (p1 p2 : pcomp) (a : ℕ) : option pcomp :=
303
- do (n1, n2, c ) ← elim_var p1.c p2.c a,
304
- return ⟨c, comp_source.add (p1.src. scale n1) (p2.src. scale n2)⟩
410
+ do (n1, n2) ← elim_var p1.c p2.c a,
411
+ return $ (p1.scale n1).add (p2.scale n2) a
305
412
306
413
meta def comp.is_contr (c : comp) : bool := c.coeffs.empty ∧ c.str = ineq.lt
307
414
308
415
meta def pcomp.is_contr (p : pcomp) : bool := p.c.is_contr
309
416
310
417
meta def elim_with_set (a : ℕ) (p : pcomp) (comps : rb_set pcomp) : rb_set pcomp :=
311
- if ¬ p.c.coeffs.contains a then mk_pcomp_set.insert p else
312
418
comps.fold mk_pcomp_set $ λ pc s,
313
419
match pelim_var p pc a with
314
- | some pc := s.insert pc
420
+ | some pc := if pc.maybe_minimal a then s.insert pc else s
315
421
| none := s
316
422
end
317
423
318
424
/--
319
425
The state for the elimination monad.
320
426
* `vars`: the set of variables present in `comps`
321
427
* `comps`: a set of comparisons
322
- * `inputs`: a set of pairs of exprs `(t, pf)`, where `t` is a term and `pf` is a proof that
323
- `t {<, ≤, =} 0`, indexed by `ℕ`.
324
- * `has_false`: stores a `pcomp` of `0 < 0` if one has been found
325
-
326
- TODO: is it more efficient to store comps as a list, to avoid comparisons?
327
428
-/
328
429
meta structure linarith_structure :=
329
430
(vars : rb_set ℕ)
@@ -351,13 +452,34 @@ end
351
452
meta def update (vars : rb_set ℕ) (comps : rb_set pcomp) : linarith_monad unit :=
352
453
state_t.put ⟨vars, comps⟩ >> validate
353
454
455
+ /--
456
+ `split_set_by_var_sign a comps` partitions the set `comps` into three parts.
457
+ * `pos` contains the elements of `comps` in which `a` has a positive coefficient.
458
+ * `neg` contains the elements of `comps` in which `a` has a negative coefficient.
459
+ * `not_present` contains the elements of `comps` in which `a` has coefficient 0.
460
+
461
+ Returns `(pos, neg, not_present)`.
462
+ -/
463
+ meta def split_set_by_var_sign (a : ℕ) (comps : rb_set pcomp) :
464
+ rb_set pcomp × rb_set pcomp × rb_set pcomp :=
465
+ comps.fold ⟨mk_pcomp_set, mk_pcomp_set, mk_pcomp_set⟩ $ λ pc ⟨pos, neg, not_present⟩,
466
+ let n := pc.c.coeff_of a in
467
+ if n > 0 then ⟨pos.insert pc, neg, not_present⟩
468
+ else if n < 0 then ⟨pos, neg.insert pc, not_present⟩
469
+ else ⟨pos, neg, not_present.insert pc⟩
470
+
471
+ /--
472
+ `monad.elim_var a` performs one round of Fourier-Motzkin elimination, eliminating the variable `a`
473
+ from the `linarith` state.
474
+ -/
354
475
meta def monad.elim_var (a : ℕ) : linarith_monad unit :=
355
476
do vs ← get_vars,
356
477
when (vs.contains a) $
357
- do comps ← get_comps,
358
- let cs' := comps .fold mk_pcomp_set (λ p s, s.union (elim_with_set a p comps )),
478
+ do ⟨pos, neg, not_present⟩ ← split_set_by_var_sign a <$> get_comps,
479
+ let cs' := pos .fold not_present (λ p s, s.union (elim_with_set a p neg )),
359
480
update (vs.erase a) cs'
360
481
482
+
361
483
meta def elim_all_vars : linarith_monad unit :=
362
484
get_var_list >>= list.mmap' monad.elim_var
363
485
@@ -516,8 +638,8 @@ do pftps ← l.mmap infer_type,
516
638
let prmap := rb_map.of_list $ lz.map (λ ⟨n, x⟩, (n, x.1 )),
517
639
let vars : rb_set ℕ := rb_map.set_of_list $ list.range map.size,
518
640
let pc : rb_set pcomp :=
519
- rb_set.of_list_core mk_pcomp_set $ lz.map (λ ⟨n, x⟩, ⟨ x.2 , comp_source.assump n⟩ ),
520
- return (⟨ vars, pc⟩ , prmap)
641
+ rb_set.of_list_core mk_pcomp_set $ lz.map (λ ⟨n, x⟩, pcomp.assump x.2 n ),
642
+ return ({ vars := vars, comps := pc} , prmap)
521
643
522
644
meta def linarith_monad.run (red : transparency) {α} (tac : linarith_monad α) (l : list expr) :
523
645
tactic ((pcomp ⊕ α) × rb_map ℕ (expr × expr)) :=
0 commit comments