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
- src dst: Array Nat
- sizeGTZeroSRC: 0 < Array.size src
- sizeGTZeroDST: 0 < Array.size dst
- srcInRange: Nat.zero < Array.size src
- 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