Zulip Chat Archive

Stream: mathlib4

Topic: simp and vector notation


Michael Stoll (Jun 05 2024 at 18:52):

would it be possible to make (say)

import Mathlib

example {F} [Field F] : (![(0 : Int), 1, 0, -1, 0, -1, 0, 1] 6 : F) = 0 := by
  simp

work?

(Replacing 6 by 4, it does work because of docs#Matrix.cons_val_four, but there is docs#Matrix.cons_val_five etc. Without the coercion, it's rfl.)

Is this something a simproc could do?

Eric Wieser (Jun 05 2024 at 18:56):

Yes, this is a good area for a simproc

Eric Wieser (Jun 05 2024 at 18:57):

Things like docs#FinVec.map_eq are also waiting for a simproc

Eric Wieser (Jun 05 2024 at 18:57):

Things like docs#FinVec.map_eq are also waiting for a simproc

Michael Stoll (Jun 05 2024 at 18:58):

(This may be a regression from lean3.)

Eric Wieser (Jun 05 2024 at 19:46):

In lean3 we had a clever trick that exploited the binary representation of numbers

Eric Wieser (Jun 05 2024 at 19:46):

But a simproc makes more sense here

Michael Stoll (Jun 05 2024 at 20:03):

Anybody wants to write it? :smile:

Kim Morrison (Jun 05 2024 at 21:16):

(deleted)

Michael Stoll (Jun 06 2024 at 08:34):

(Vector notation is defined in Mathlib, I think, that's why I was asking in this channel. Not sure if the question is relevant outside Mathlib.)

Eric Wieser (Jun 06 2024 at 09:13):

I suspect Kim was thinking of the analogous array / list versions?

Eric Wieser (Jun 06 2024 at 10:38):

Here's a prototype:

import Mathlib.Data.Fin.VecNotation
import Mathlib.Util.Qq


open Lean Qq

partial def matchVecApp {u : Level} {α : Q(Type u)} (x : Q($α)) :
  MetaM <| Option (Σ' (n : Q()) (f : Q(Fin $n  $α)) (i : Q(Fin $n)), $x =Q $f $i) := do
    Meta.withNewMCtxDepth do
      let n : Q()  mkFreshExprMVarQ q()
      let f : Q(Fin $n  $α)  mkFreshExprMVarQ q(Fin $n  $α)
      let i : Q(Fin $n)  mkFreshExprMVarQ q(Fin $n)
      let .defEq _h  isDefEqQ q($x) q($f $i) | pure none
      return some ⟨← instantiateMVars n,  instantiateMVars f,  instantiateMVars i, ⟨⟩⟩

partial def matchVecCons {u : Level} {α : Q(Type u)}
    {n : Q()} (f : Q(Fin ($n + 1)  $α)) :
  MetaM <| Option (Σ' (a : Q($α)) (f' : Q(Fin $n  $α)), $f =Q Matrix.vecCons (α := $α) (n := $n) $a $f') := do
    Meta.withNewMCtxDepth do
      let a : Q($α)  mkFreshExprMVarQ q($α)
      let f' : Q(Fin $n  $α)  mkFreshExprMVarQ q(Fin $n  $α)
      let .defEq _h  isDefEqQ (u := u.succ) (α := q(Fin ($n + 1)  $α)) q($f) q(Matrix.vecCons (α := $α) (n := $n) $a $f') | pure none
      return some ⟨← instantiateMVars a,  instantiateMVars f', ⟨⟩⟩


partial def reduce {u : Level} {α : Q(Type u)}
    {n : Q()} (f : Q(Fin $n  $α)) (i : Q(Fin $n)) :
    MetaM (Option (Σ a : Q($α), Q($f $i = $a))) := do
  let .some iv := i.int? | pure none
  let .some nv := n.nat? | pure none
  let .succ nv' := nv | pure none
  have n' : Q() := mkNatLit nv'
  have _hn : $n =Q ($n' + 1) := ⟨⟩
  let .some a, f', _h  matchVecCons (n := n') (α := α) f | pure none
  match (iv % (nv' + 1)).toNat with
  | 0 =>
    have _hi : $i =Q 0 := ⟨⟩
    return some a, q(Matrix.cons_val_zero _ _)
  | iv' + 1 =>
    have ilit : Q() := mkRawNatLit iv'
    have : Q(NeZero $n') := q(sorry) -- TODO
    have i' : Q(Fin $n') := q(OfNat.ofNat $ilit)
    have _hi : $i =Q (Fin.succ $i') := ⟨⟩
    let .some x, pf  reduce (u := u) (α := α) (n := n') q($f') i' | pure none
    return some x, q((Matrix.cons_val_succ $x $f' $i').trans $pf)

simproc Matrix.cons_val (Matrix.vecCons _ _ _) := fun e => do
  let ⟨_u, _α, x  inferTypeQ' e
  let .some ⟨_n, f, i, _h  matchVecApp x | failure
  let .some e, pf  reduce f i | failure
  return .continue (some { expr := e, proof? := pf})

lemma foo {a b c d : } : ![a, b, c, d, a, b, c, d, a, b, c, d] 33 = b := by simp
lemma bar {a b c d : } : ![a, b, c, d, a, b, c, d, a, b, c, d] (-1) = d := by simp

Eric Wieser (Jun 06 2024 at 19:05):

I now realize the above is more complicated than it needs to be: the proof is always rfl, so I can avoid all the effort to track the proof

Eric Wieser (Jun 06 2024 at 21:23):

I've made a more polished attempt in #13578

Eric Wieser (Jun 06 2024 at 21:26):

What I can't work out is how to obtain the length of the vector; seemingly .nat? fails

Eric Wieser (Jun 06 2024 at 21:26):

I suppose repeatedly unifying with Nat.succ is probably better

Heather Macbeth (Jun 06 2024 at 21:43):

I've wanted things like this several times, and indeed I've tried to arrange things so that the matrix indices which are produced in the earlier part of the work are all iterated .succs. This works:

example {F} [Field F] :
    (![(0 : Int), 1, 0, -1, 0, -1, 0, 1] (0 : Fin 2).succ.succ.succ.succ.succ.succ : F) = 0 := by
  simp

Kyle Miller (Jun 06 2024 at 22:43):

I'm not sure about all the isDefEq in your simproc @Eric Wieser. It seems like this should be more syntactically driven, only working for things that are obviously vector literals.

Here's a short implementation using core style. The expr matches handle mdata and annotation cleanup.

import Mathlib.Data.Fin.VecNotation

open Lean

partial def matchVecLit (e : Expr) : Option (List Expr) :=
  match_expr e with
  | Matrix.vecEmpty _ => some []
  | Matrix.vecCons _ _ x xs => (x :: ·) <$> matchVecLit xs
  | _ => none

dsimproc Matrix.cons_val (Matrix.vecCons _ _ _) := fun e => do
  let_expr Matrix.vecCons _ _ x xs' n := e | return .continue
  let some n := n.int? | return .continue -- The docstring claims only nat or int, but works fine for Fin
  let some xs' := matchVecLit xs' | return .continue
  let xs := x :: xs'
  let n' := (n % xs.length).toNat
  return .continue (xs.get! n')

lemma foo {a b c d : } : ![a, b, c, d, a, b, c, d, a, b, c, d] 33 = b := by dsimp
lemma bar {a b c d : } : ![a, b, c, d, a, b, c, d, a, b, c, d] (-1) = d := by simp

Eric Wieser (Jun 06 2024 at 22:47):

One difference is that I think my version works for vecCons a (vecCons b a_three_tuple) 6 = b

Eric Wieser (Jun 06 2024 at 22:47):

I'm not sure I agree re defeq; shouldn't _everything_ see through reducible defeq? (Probably I forgot to make it reducible)

Kyle Miller (Jun 06 2024 at 23:18):

If you imagine that the simproc is supposed to be looking for a dynamic discrimination tree key, simulating an infinite collection of simp lemmas, then maybe it shouldn't see through reducible defeq when matching?

It's not clear to me that this simproc should handle "improper" vecCons expressions (those that don't terminate in vecNil) since these aren't vec literals (they don't use the ![...] notation), but in any case it's not hard to add support, at the cost of putting the length of the array into WHNF at default transparency.

import Mathlib.Data.Fin.VecNotation

open Lean

partial def matchVecLitPrefix (e : Expr) : List Expr :=
  match_expr e with
  | Matrix.vecCons _ _ x xs => x :: matchVecLitPrefix xs
  | _ => []

dsimproc Matrix.cons_val (Matrix.vecCons _ _ _) := fun e => do
  let_expr Matrix.vecCons _ _ x xs' n := e | return .continue
  let some n' := n.int? | return .continue -- The docstring claims only nat or int, but works fine for Fin
  let_expr Fin length := ( instantiateMVars ( Meta.inferType n)) | return .continue
  let Expr.lit (.natVal length)  Meta.whnfD length | return .continue
  let xs := x :: matchVecLitPrefix xs'
  let n' := (n' % length).toNat
  guard <| n' < xs.length
  return .continue (xs.get! n')

lemma foo {a b c d : } : ![a, b, c, d, a, b, c, d, a, b, c, d] 33 = b := by dsimp
lemma bar {a b c d : } : ![a, b, c, d, a, b, c, d, a, b, c, d] (-1) = d := by simp
example {a b c d e : Int} : ![a, b, c, d, e] 123456789 = e := by simp

Eric Wieser (Jun 07 2024 at 07:12):

I was under the impression that simp saw through abbrev. Is that the case, and is that also true of ler_expr?

Eric Wieser (Jun 07 2024 at 07:27):

I just checked; it sounds like my concern is real, but can be fixed by inserting ← Meta.whnfRs before the match_expr / let_expr

Eric Wieser (Jun 07 2024 at 07:45):

Here's a revised version; I think I agree that using Qq is not really appropriate here:

import Mathlib.Data.Fin.VecNotation

open Lean Qq

/-- Extracts a chain of vecCons calls -/
partial def matchVecConsPrefix (e : Expr) : MetaM <| List Expr × Expr := do
  match_expr  Meta.whnfR e with
  | Matrix.vecCons _ _ x xs => do
    let (elems, tail)  matchVecConsPrefix xs
    return (x :: elems, tail)
  | _ => return ([], e)

dsimproc Matrix.cons_val (Matrix.vecCons _ _ _) := fun e => do
  let_expr Matrix.vecCons _ _ x xs' n :=  Meta.whnfR e | return .continue
  let some n' := n.int? | return .continue -- The docstring claims only nat or int, but works fine for Fin
  let_expr Fin length := ( instantiateMVars ( Meta.inferType n)) | return .continue
  let Expr.lit (.natVal length)  Meta.whnfD length | return .continue
  let (xs, tail)  matchVecConsPrefix xs'
  let xs := x :: xs
  let n' := (n' % length).toNat
  if n' < xs.length then
    return .continue (xs.get! n')
  else if 0 < xs.length then
    let newn := unsafe toExpr (n' - xs.length, lcProof : Fin (length - xs.length))
    return .continue (.some <| .app tail <| newn)
  else
    return .continue

abbrev id' (x : α) := x

open Matrix

attribute [-simp] Matrix.cons_val_four

example {a b c d : } : id' ![a, b, c, d, a, b, c, d, a, b, c, d] 8 = a := by dsimp
example {a b c : } {f : Fin 3  } : vecCons a (id' <| vecCons b (vecCons c f)) 7 = b := by dsimp
example {a b c : } {f : Fin 3  } : vecCons a (vecCons b (vecCons c f)) 4 = f 1 := by dsimp
example {a b c d : } : ![a, b, c, d, a, b, c, d, a, b, c, d] (-1) = d := by dsimp
example {a b c d e : Int} : ![a, b, c, d, e] 123456789 = e := by simp

Eric Wieser (Jun 07 2024 at 07:46):

Separately, I think having ![a, b, c, ..f] as notation for that third case might be reasonable; do you think core would accept anything similar for List?

Yaël Dillies (Jun 07 2024 at 08:23):

... and ![f.., a, b c] for the other direction?

Yaël Dillies (Jun 07 2024 at 08:24):

Note that #11142 is trying to add notation for vecCons

Kyle Miller (Jun 07 2024 at 17:46):

@Eric Wieser ![a, b, c, ..f] seems reasonable to me.

For List, an immediate question would be whether it would pretty print, and whether .. is accepted elsewhere. I think there's a path forward here, but I think we'll have to wait for some time before it will be acceptable in core.

Kyle Miller (Jun 07 2024 at 17:47):

Why does this line need to be unsafe?

let newn := unsafe toExpr (n' - xs.length, lcProof : Fin (length - xs.length))

Is there another way to construct that?

Eric Wieser (Jun 07 2024 at 18:02):

It's unsafe because of the use of lcProof, and because there's not much point providing the proof when it's thrown away by toExpr anyway

Eric Wieser (Jun 07 2024 at 18:03):

Of course we could inline the implement of Fin.toExpr there and the unsafe would go away

Eric Wieser (Jun 07 2024 at 18:03):

I supposed I could also just use elabTerm to build the fin literal, which I now do on the other branch anyway

Eric Wieser (Apr 13 2025 at 12:55):

I've updated #13578 to avoid both lcProof and elabTerm


Last updated: May 02 2025 at 03:31 UTC