coq / coq

Coq is a formal proof management system. It provides a formal language to write mathematical definitions, executable algorithms and theorems together with an environment for semi-interactive development of machine-checked proofs.
https://coq.inria.fr/
GNU Lesser General Public License v2.1
4.86k stars 650 forks source link

Folds/iterators for primitive arrays #17067

Open palmskog opened 1 year ago

palmskog commented 1 year ago

Although Coq has primitive arrays, it currently provides no folds/iterators for such arrays.

One possible application of folds over primitive arrays is in refinement of abstract matrices to arrays of arrays (https://github.com/coq-community/coqeal/issues/72).

@maximedenes @strub

zhengpushi commented 1 year ago

It's an interesting problem. I have studied several formal matrix models ago, and focused on the properties of the matrix operations such as "mapping, fold, addition, subtraction, scalar multiplication, transposition, multiplication". Meanwhile, all of them could be used to compute with concrete types such as Z, Q, Qc, R.

The effecient in Coq (and extracted code in OCaml) is very different in these models. For example, matrix multiplication with the element of R type (extracted to float type in OCaml), and matrix size from 20x20 to 211x211, the running time (second) in OCaml is approximately below:

size DL DP DR NF FF
20 0.00 0.00 0.00 0.00 3.50
50 0.26 0.02 0.01 0.37  ?
60 0.52 0.04 0.02 0.76  ?
72 1.04 0.06 0.02 1.58  ? 
86 2.06 0.10 0.04 3.22   ?
103 4.18 0.18 0.07 6.52   ?
123 8.42 0.30 0.12 13.11   ?
147 17.26 0.51 0.19 26.63   ?
176 34.46 0.86 0.33 54.43   ?
211 72.90 1.48 0.55 111.90   ?

DL : dependent list, (Coq.Vectors.Vector.t) DP : dependent pair, (a fixpoint function) DR : dependent record, (inner is a list of list) NF : function with natrual indexing (function style) FF : function with fin indexing (from MathComp)


From above, the DR model is the fastest. Multiply two 211x211 float matrices, need 0.55 seconds. But, this is still too slow than primitive array.

Conclusion: I think the fasted way to compute matrix operation in Coq directly is primitive array, and worthwhile to study the properties of matrix operation in this model.

The OCaml source file at https://github.com/zhengpushi/coq-matrix/blob/main/MatrixComparison/src/Matrix/OCamlExtractionComparison/test.ml (Note that, you may need to compile these project to get the extracted ocaml file from Coq. and the compile is easy with make.) The detail could be checked at https://github.com/zhengpushi/coq-matrix/blob/main/MatrixComparison/doc/ocaml-extraction-comparison.md The proejct could be checked at https://github.com/zhengpushi/coq-matrix/ The comparison of these models could be found in https://link.springer.com/chapter/10.1007/978-3-031-21213-0_11 or the draft here: https://github.com/zhengpushi/coq-matrix/blob/main/MatrixComparison/doc/matrix-coq-setta2022.pdf

Note that, this project is written with Chinese mixed with English, not very friendly for English users. I will correct it gradually

SkySkimmer commented 1 year ago

What kind of API would this be? Is it enough to have interruptible finite loops over ints ie

Parameter for_loop : forall {A B}, int -> int -> (A -> B) -> (int -> A -> A + B) -> A -> B

which in ocaml would be

let rec loop start end_ finish f x =
  if start = end_ then finish x
  else match f start x with
    | Inl x -> loop (start+1) end_ finish f x
    | Inr x -> x

let for_loop start end_ finish f x = if start <= end then loop start end_ finish f x else finish x

? Then eg

Definition array_fold_left {A B} (f : A -> B -> A) (acc : A) (a : array B) : A
  := for_loop 0 (Array.length a) id (fun i acc => inl (f acc (Array.get a i))) acc.

and

Definition array_exists {A} (p : A -> bool) (a : array A) : bool
  := for_loop 0 (Array.length a) (fun _ => false) (fun i _ => if p (Array.get a i) then inr true else inl tt) tt.

??

zhengpushi commented 1 year ago

This demo program maybe work well in OCaml. @SkySkimmer . Looks very well.

I have writen some code of PArray.array, which can be evaluated in Coq directly. But it is not so efficient, which use nat type to write a fixpoint function, and with the help of "n2i" to translate "int" to "nat" type.


(*
  remark    :
  1. A loop need to trace the index of int type, but int is axiomized, 
     havn't a inductive structure, hard to write fixpoint function.
     (1). we can use nat, the structure is easy, but max-size-of-nat is 5000
     (2). we can use Z, it is a bit complicate, and need to solve negative.
     (3). we can use positive, but also complicate.
 *)

Require Import Coq.Numbers.Cyclic.Int63.Uint63. (* native int *)
Require Import PArray.          (* native array *)

Module parray_loop_with_nat.

  (* int <-> nat *)
  Definition i2n : int -> nat := fun i => BinInt.Z.to_nat (to_Z i).
  Definition n2i : nat -> int := fun n : nat => of_Z (BinInt.Z.of_nat n).
  Compute i2n 2. (* = 2 : nat *)
  Compute n2i 2. (* = 2%uint63 : int *)

  (** loop : init + a[i] + a[i+1] + ... *)
  Fixpoint loop {A:Type} (arr : array A) (cnt : nat) (i : int) (f : A -> A -> A)
    (init : A) : A :=
    match cnt with
    | O => init
    | S cnt' => loop arr cnt' (i + 1) f (f init arr.[i])
    end.

  Definition data1 := make 50 1.
  Compute data1.
  (* = [| 
     1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 
     1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 
     1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 
     1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 
     1; 1; 1; 1; 1; 1; 1; 1; 1; 1 | 1 : nat |]
     : array nat *)

  Variable f : nat -> nat -> nat.
  Compute loop data1 10 0 f 0. (*
     = f (f (f (f (f (f (f (f (f (f 0 1) 1) 1) 1) 1) 1) 1) 1) 1) 1
     : nat *)
  Compute loop data1 10 0 (Nat.add) 0. (*  = 10 
                                           : nat *)
End parray_loop_with_nat.