Zulip Chat Archive

Stream: lean4

Topic: Proving properties on Array


Muqsit Azeem (Feb 02 2024 at 18:36):

I am a newbie in Lean community and trying to prove a theorem in Lean4. I seek help with making progress towards that.
The current status of proof is below. In particular, I have 5 assumptions and I am trying to use the assumption 3. i.e., sizeGTZeroDST

  1. src dst: Array Nat
  2. sizeGTZeroSRC: 0 < Array.size src
  3. sizeGTZeroDST: 0 < Array.size dst
  4. srcInRange: Nat.zero < Array.size src
  5. dstInRange: Nat.zero < Array.size dst
 (if h :
      Nat.zero <
        Array.size
          (copyElements src
            (if h : 0 < Array.size dst then
              {
                data :=
                  List.set dst.data ({ val := 0, isLt := h })
                    (if h : 0 < Array.size src then List.get src.data { val := 0, isLt := h } else default) }
            else dst)
            1) then
    List.get
      (copyElements src
          (if h : 0 < Array.size dst then
            {
              data :=
                List.set dst.data ({ val := 0, isLt := h })
                  (if h : 0 < Array.size src then List.get src.data { val := 0, isLt := h } else default) }
          else dst)
          1).data
      { val := Nat.zero, isLt := h }
  else default) =   if h : Nat.zero < Array.size src then List.get src.data { val := Nat.zero, isLt := h } else default

I tried a few tactics such as rw [sizeGTZeroDST] , however I failed to apply this assumption in the fifth line of the goal with the following error:
"tactic 'rewrite' failed, equality or iff proof expected
0 < Array.size dst".
The lean code in posted in the comment.

Muqsit Azeem (Feb 02 2024 at 18:36):

import Auto
import Std.Tactic.Omega

set_option auto.smt true
set_option auto.smt.trust true


def copyElements (src: Array Nat) (dst: Array Nat) (index: Nat) : Array Nat :=
  if index < src.size then
    if index < dst.size then
      let updatedDst := dst.set! index (src.get! index)
      copyElements src updatedDst (index + 1)
    else
      dst
  else
    dst
termination_by _ => src.size - index

def copy (src dst: Array Nat) : Array Nat :=
  copyElements src dst 0



#eval copy #[1, 2, 3] #[7, 5]
#eval copy #[1, 2, 3] #[7, 2, 6]
#eval copy #[1, 2] #[7, 2, 6]

theorem SrcDstEqualOnIndices (src : Array Nat) (dst : Array Nat) (index : Nat) :
 (index < src.size  index < dst.size)  (copy src dst).get! index = src.get! index := by
    intro indexInRange
    unfold copy
    unfold copyElements
    split
    case inl sizeGTZeroSRC =>
      let srcInRange, dstInRange := indexInRange
      simp_all
      split
      case inl sizeGTZeroDST =>
        induction index
        case zero =>
          --
          unfold Array.get!
          unfold Array.set!
          unfold Array.getD
          unfold Array.setD
          unfold Array.get
          unfold Array.set
          rw [sizeGTZeroDST]
          -- unfold copyElements
          simp_all
          split
          case inl =>
            simp_all
            unfold List.get
            split
            -- unfold copyElements
            case h_1 =>
              rewrite [dstInRange]
            omega
            rewrite [dstInRange]

          --simp [dstInRange]

          -- simp_arith
          sorry
        case succ ih =>
          unfold Array.get!
          unfold Array.set!
          unfold Array.getD
          unfold Array.setD
          unfold Array.get
          unfold Array.set
          simp_all
          split

          case inl =>
           sorry
          case inr =>

          simp_all
          sorry


      case inr sizeNOTGTZeroDST =>
        unfold Array.size at *
        simp_all
        contradiction

    case inr sizeNotGTZeroSRC =>
      let srcInRange, dstInRange := indexInRange
      unfold Array.size at *
      simp_all
      contradiction

Kevin Buzzard (Feb 02 2024 at 18:57):

The rw tactic will only consume hypotheses of the form A=B or A<->B, you can't rewrite arbitrary facts

James Gallicchio (Feb 02 2024 at 19:02):

I would recommend looking at core/std library facts about arrays to see how they are proven. Generally you want to provide a mathematically nice definition of what your function (in this case copy) does to the array.

Here maybe you want to give a description of copy in terms of Array.ofFn, something like copy A B = Array.ofFn (fun i => if i < A.size then A[i] else B[i]).

You then can prove that by setting up the right induction invariant, again see examples in core/std. And then most of the theorems you want about the operation just follow straightforwardly from this characterization of copy in terms of Array.ofFn.

Muqsit Azeem (Feb 02 2024 at 19:27):

Thanks @Kevin Buzzard : Indeed, it did not work and, therefore, I am wondering if there is a way to reduce the following two

if cond then E1 else E2

cond = true

to

E1

Kevin Buzzard (Feb 02 2024 at 19:27):

you can use docs#if_pos

pandaman (Feb 03 2024 at 13:10):

As far as I know, we need to perform an induction in the same direction as the function is defined when we want to prove these kinds of properties. For example, we can prove that copy does not change the size (which we'll use proving the final result) as follows. Please note that copy_size.go calls itself with i + 1 as we define copy.go.

def copy (src dst : Array α) : Array α :=
  go src dst 0
where
  go (src dst : Array α) (i : Nat) : Array α :=
    if h₁ : i < src.size then
      if h₂ : i < dst.size then
        go src (dst.set i, h₂ (src.get i, h₁⟩)) (i + 1)
      else
        dst
    else
      dst
termination_by go _ => src.size - i

theorem copy_size {src dst : Array α} : (copy src dst).size = dst.size :=
  go src dst 0
where
  go (src dst : Array α) (i : Nat) : (copy.go src dst i).size = dst.size := by
    unfold copy.go
    split <;> try rfl
    case inl h₁ =>
      split <;> try rfl
      case inl h₂ =>
        rw [go src (dst.set i, h₂ (src.get i, h₁⟩)) (i + 1)]
        simp
termination_by go _ => src.size - i

Proving the equality is trickier because we'll need to strengthen the induction hypothesis to prove properties in the base cases. This is done by adding preconditions to the go definition, and the added preconditions are called loop invariants. Finding the right loop invariants requires a bit of art, but usually you can find them by observing the arguments of the recursive calls and thinking about relationships between them.

Once you find the right loop invariants, you can write a proof like this. We'll also need some tricks to satisfy about the details, but AFAIK the best way to learn these tricks is to looking at the standard libraries, as suggested.


Last updated: May 02 2025 at 03:31 UTC