|
|
|||||||||||||||||||||||||||
|
|
Start of topic | Skip to actions
617S07 Homeworks for Cherif AndraosBraun Trees (Continued)Functions (Continued)Now that we have a new release of Concoqtion, we can easily write the remove function which required a nested match statement. Also the unjustified warning described in the fold function for dependently-typed Braun trees is now removed.RemoveUnsizedexception TreeException;; let rec remove_last_bt bt = match bt with |Leaf -> raise TreeException |Parent(r,Leaf,Leaf) -> Leaf |Parent(r,bt1,bt2) -> Parent (r, bt2, (remove_last_bt bt1));; Sized
let rec remove_last_dbt .|n:'(nat)| (dbt: ('(S n),'a) dbtree) : ('(n),'a) dbtree =
match dbt in ('(n),'a) dbtree with
| DParent1 .|m:'(nat)| (r, dbt1, dbt2) ->
(match dbt2 in ('(n),'a) dbtree with
| DLeaf -> DLeaf
| DParent1 .|l:'(nat)| (r2, dbt3, dbt4) -> DParent2 .|'(l*2)| (r,dbt1, remove_last_dbt .|'(l*2)| dbt2)
| DParent2 .|l:'(nat)| (r2, dbt3, dbt4) -> DParent2 .|'(S (l*2))| (r,dbt1, remove_last_dbt .|'(S (l*2))| dbt2) )
| DParent2 .|m:'(nat)| (r, dbt1, dbt2) -> DParent1 .|'(m)| (r, remove_last_dbt .|'(m)| dbt1, dbt2)
Note that in the unsized version we have to raise an exception if the tree is already empty, this case can't happen in the sized version because the type of input tree is a tree of size successor of n which implies that it can't be empty. Even if we try to add a branch in the match that matches the input tree against an empty tree as shown below, here is what happens: Definition:
let rec remove_last_dbt .|n:'(nat)| (dbt: ('(S n),'a) dbtree) : ('(n),'a) dbtree =
match dbt in ('(n),'a) dbtree with
| DLeaf -> raise TreeException
| DParent1 .|m:'(nat)| (r, dbt1, dbt2) ->
(match dbt2 in ('(n),'a) dbtree with
| DLeaf -> DLeaf
| DParent1 .|l:'(nat)| (r2, dbt3, dbt4) -> DParent2 .|'(l*2)| (r,dbt1, remove_last_dbt .|'(l*2)| dbt2)
| DParent2 .|l:'(nat)| (r2, dbt3, dbt4) -> DParent2 .|'(S (l*2))| (r,dbt1, remove_last_dbt .|'(S (l*2))| dbt2) )
| DParent2 .|m:'(nat)| (r, dbt1, dbt2) -> DParent1 .|'(m)| (r, remove_last_dbt .|'(m)| dbt1, dbt2)
Output:
File "bt.ml", line 112, characters 4-9:
Error while unifying the type of the argument of match
('(S n), 'b) dbtree and pattern type ('(0), 'c) dbtree:
This pattern matches values of type '(
S _UNBOUND_REL_1) but is here used to match values of type '(0)
The type checker catches the fact that this is not possible and saves us from doing this unnecessary check. Small TestCode: let t1 = insert_dbt1 .|'(0)| 0 DLeaf ;; let t2 = insert_dbt1 .|'(1)| 10 t1;; let t3 = insert_dbt1 .|'(2)| 20 t2;; let t4 = insert_dbt1 .|'(3)| 30 t3;; let t5 = insert_dbt1 .|'(4)| 40 t4;; let t6 = insert_dbt1 .|'(5)| 50 t5;; let t7 = insert_dbt1 .|'(6)| 60 t6;; let t8 = insert_dbt1 .|'(7)| 70 t7;; let t9 = insert_dbt1 .|'(8)| 80 t8;; let add a b = a+b;; let n = fold_dbt .|'(9)| 0 add t9;; let t8_1 = remove_last_dbt .|'(8)| t9;; let n_1 = fold_dbt .|'(8)| 0 add t8_1;; Output:
val t1 : ('(1), int) dbtree = DParent1 (0, DLeaf, DLeaf)
val t2 : ('(2), int) dbtree =
DParent2 (0, DParent1 (10, DLeaf, DLeaf), DLeaf)
val t3 : ('(3), int) dbtree =
DParent1 (0, DParent1 (10, DLeaf, DLeaf), DParent1 (20, DLeaf, DLeaf))
.
.
.
.
val t9 : ('(9), int) dbtree =
DParent1 (0,
DParent2 (10, DParent2 (30, DParent1 (70, DLeaf, DLeaf), DLeaf),
DParent1 (50, DLeaf, DLeaf)),
DParent2 (20, DParent2 (40, DParent1 (80, DLeaf, DLeaf), DLeaf),
DParent1 (60, DLeaf, DLeaf)))
val add : int -> int -> int = <fun>
val n : int = 360
val t8_1 : ('(8), int) dbtree =
DParent2 (0,
DParent2 (10, DParent2 (30, DParent1 (70, DLeaf, DLeaf), DLeaf),
DParent1 (50, DLeaf, DLeaf)),
DParent1 (20, DParent1 (40, DLeaf, DLeaf), DParent1 (60, DLeaf, DLeaf)))
val n_1 : int = 280
Project report (2007-04-09(M))
OverviewDiscrete Fourier Transformation (DFT) is widely used in digital signal processing applications, such as linear filtering, correlation analysis, and spectrum analysis. Its efficient computation is a topic that has been greatly studied in both academia and industry. The algorithm to perform DFT is called Fast Fourier Transform (FFT). Basically, computing the DFT can be seen as computing a sequence {X(k)} of N complex-valued numbers given another sequence of data {x(n)} of the same length N. We are aiming at implement FFT algorithm, specifically the Cooley-Turkey's recursive algorithm (CT_FFT), using index types. The implementation in "A Methodology for Generating Verified Combinatorial Circuits" requires that the input to the algorithm must be a list whose length is power of 2. But this must be checked at run time.Our idea is that by encoding the length of FFT input and output, we can statically guarantee that the CT_FFT algorithm behaves well on its input. ++ Basic functions CT_FFT algorithm computes the DFT of a list of size N by first split the input list into two sub-lists, one has all odd index elements and another has all even index elements. It then computes the DFT of two sub-lists and uses the result to compute DFT the original list. If N = 2^n, this can be applied recursively and thus the basic functions in the implementation includes:
let rec split l =
match l with
[] -> ([], [])
| x::y::xs -> let (a,b) = split xs in (x::a, y::b)
let rec merge .|n:'(nat)| (dir: float) (l1: ('(n), 'a) slist) (l2: ('(n), 'a) slist): ('(n*2), 'a) slist =
let len = fftlength .|'(n)| l1 in
let rec mg .|m:'(nat)| (ll1: ('(m), 'a) slist) (ll2: ('(m), 'a) slist) j =
match (ll1, ll2) in ('(n*2), 'a) slist with
| Cons .|m1:'(nat)| (x, xs), Cons .|m2:'(nat)| (y, ys) ->
let z1 = mult (w dir len j, y) in
let zx = add (x, z1) in
let zy = sub (x, z1) in
let (a,b) = (mg .|'(m1)| xs ys (j+1)) in (Cons .|'(m1)| (zx, a), Cons .|'(m1)| (zy, b))
| Nil, Nil -> Nil, Nil in
let (c, d) = mg .|'(n)| l1 l2 0 in
appends .|'(n), '(n)| c d
let rec fft dir l =
if (List.length l = 1) then
l
else
let (e,o) = split l in
let y0 = fft dir e in
let y1 = fft dir o in
merge dir y0 y1
Lessons in implementationTypes of functions "split", "merge" and "FFT" (First Step):We first tried to get the length of list recorded in the list and the three element functions:
type ('n :'(nat), 'a) slist =
| Nil : ('(0), 'a) slist
| Cons of l et 'm:'(nat) in 'a * ('(m), 'a) slist: ('(S m), 'a) sl ist
val split :
(forall n:'(nat) .
('(n), 'a) slist -> ('(div2 n), 'a) slist * ('(div2 n), 'a) slist) =
<forall>
val merge :
(forall m:'(nat) .
float ->
('(m), float * float) slist ->
('(m), float * float) slist -> ('(m + m), float * float) slist) =
<forall>
val fft :
(forall n:'(nat) .
float -> ('(n), float * float) slist -> ('(n), float * float) slist) =
<forall>
This is exactly what we want w.r.t. to the length of lists we manipulated. If the input to the "fft" function is exactly of length 2^n, we have no problem. However, if the input list has a length like "3, 6, 10,..." which are not a power of 2, the program will complain and report a mis-matching.
Types of functions "split", "merge" and "FFT" (Second Step):Now the problem is clear: how to guarantee the index type .|n| is a power of 2? So that further in the split we know it will always get a list of even length.To achieve this, in the index type of the input list, beside the length "n", we need a property or predicate to say that "n" is a power of 2. So we first define the property in Coq like follows:
Inductive isPower2: nat -> Prop :=
| Power_0 : isPower2 1
| Power_S : forall n, isPower2 n -> isPower2 (double n).
And now the fft function looks like this:
let rec fft .|n:'(nat), p:'(isPower2 n)| (dir: float) (l: ('(n), 'a) slist) : (('(n), 'a) slist) =
if (fftlength .|'(n)| l = 1) then l
else
let (e,o) = split .|'(n)| l in
let y0 = fft .|'(div2 n),'(isPower2 (div2 n))| dir e in
let y1 = fft .|'(div2 n),'(isPower2 (div2 n))| dir o in
aux .|'a, '(n)| (merge .|'(div2 n)| dir y0 y1);;
However, this will not work since we should not pass .|'(isPower2 (div2 n))|directly, as expected, we get the error:
Kind error while checking type application:
Type '(isPower2 (div2 n)) is expected to be of kind '(isPower2 (div2 _UNBOUND_REL_2)) but got '(Prop)
To pass a correct property to say n/2 is also of power 2, we need the following Lemma: Lemma div_power2: forall n, gt n 1 -> isPower2 n -> isPower2 (div2 n).In the next, we show the difficulties to proof and use this Lemma. "gt n 1" and "gt n/2 1" ---- function "destr_gt"One pre-condition of lemma "div_power2" is to guarantee that n>1, because n=1 is a power of 2, but 1/2=0 is not. Although in the fft function, if n=1 we return "l" directly, and the recursive call to fft only happens when n>1, but it is not easy to pass this information around.
*We define another natural number type where: If n<=1, it is encoded as 'No0' and 'No1'; If n>1, it is encoded as 'Yes' and a proof of n>1: type 'n:'(nat) gt_one = | No0: '(0) gt_one | No1: '(S 0) gt_one | Yes of let 'm:'(nat) 'p:'(gt m 1) in unit : '(m) gt_one
let rec safelength .|n:'(nat)| (l: ('(n), 'a) slist) : '(n) gt_one =
match l in '(n) gt_one with
| Nil -> No0
| Cons .|m1:'(nat)| (x, xs) ->
(match xs in '(n) gt_one with
| Nil -> No1
| Cons .|m2:'(nat)| (y, ys) -> Yes .|'(n), '(ss_gt_1 m2)| ())
Emir:" give me a return type r and a '(n) gt_one, and two functions (one for each case). If it is greater than the one, I'll invoke the first function and pass the prof to it. Otherwise, I'll invoke the other function and pass the proof to it. Both functions must return the result type 'r which you instantiate for every particular use. Note that this is just a function-based way of writing the case. BUT, it allows us to avoid nested matches etc. ......destr_gt which is an "elimination scheme" for the gt type which acts very much like the match statement, but can be used in place of the match statement. Its type is as follows: "
val destr_gt :
(forall n:'(nat) , r:'(OCamlType) .
'(n) gt_one ->
(forall p:'(n > 1) . unit -> '(r)) ->
(forall p:'(n <= 1) . unit -> '(r)) -> '(r))
and the "safefft" function now is implemented using the above auxiliary function:
let rec safefft .|n:'(nat),p:'(isPower2 n)| (dir:float) (l:('(n),'a) slist) : ('(n),'a) slist =
let case_less .|p:'(n<=1)| () : ('(n),'a) slist = l in
let case_greater .|q:'(n>1)| () : ('(n),'a) slist =
let (e,o) = split .|'(n)| l in
let y0 = safefft .|'(div2 n),'(div_power2 n q p)| dir e in
let y1 = safefft .|'(div2 n),'(div_power2 n q p)| dir o in
let rr = aux .|'a, '(n)| (merge .|'(div2 n)| dir y0 y1) in rr in
let condition = destr_gt .|'(n), ('(n),float * float) slist| (safelength .|'(n)| l) in
condition case_greater case_less
Proofs of Lemma in CoqWe use several Lemma in coq environment to guarantee the desired properties on the lenth of fft lists. Basically, we need the following definitions and lemmas:
Inductive isPower2: nat -> Prop :=
| Power_0 : isPower2 1
| Power_S : forall n, isPower2 n -> isPower2 (double n).
Lemma ss_gt_1: forall n, gt (S (S n)) 1.
Lemma div2_double : forall n:nat, div2 (2*n) = n.
Lemma div_power2: forall n, gt n 1 -> isPower2 n -> isPower2 (div2 n).
Lemma div2_plus_div2 : forall n:nat, gt n 1 -> isPower2 n -> div2 n + div2 n = n.
Lemma list_cast : forall e, forall m n:nat, (m=n) -> (OCaml_slist m e) = (OCaml_slist n e).
During this project, we learned how to write proofs for these lemma. Detailed proof please refer to the appendix. A view on "safe cast"Cast function is used in our implementation to convert the type of "merge" output, which is '(n/2 + n/2) slist, to the type '(n) slist, for "fft" function. Notice that this is only true when n is even. This is guaranteed if the input to "fft" function is a list of length 2^n. We achieve this by passing the proof around in the index type.However cast function withOUT "Obj.magic" is a little different from what we've seen in the type checker. In "type_checker.ml" we do the cast by first construction a type called "eq_typ" which can help us to get an indexed type to another indexed type (computational cast). However, this is not applicable here since we only have original coq type.
Following is what worded for our implementation w.r.t. "cast" issue:
external castFun:'a -> 'b = "%identity";;
let cast .|a,b,p:'(a=b)| (x:'(a)) : '(b) = castFun x;;
let aux .|a, m:'(nat), p:'(gt m 1), q:'(isPower2 m)| (x: ('((div2 (m))+(div2 (m))), 'a) slist) =
cast .|'(OCaml_slist ((div2 (m))+(div2 (m))) a),
'(OCaml_slist (m) a),
'(list_cast a ((div2 (m))+(div2 (m))) (m) (div2_plus_div2 m p q))| x
Concoqtion bug reportingBefore trying to proof the fft function will always get a list of length 2^n, we tried something on "split" function first to guarantee it will always get a input list of even length.We failed because concoqtion's problem on nested matching, and reported in our email. Emir affirmed that this is a bug and the problem is specified as follows:
let rec split .|n:'(nat), p:'(even n)| (l: ('(n), 'a) slist): (('(div2 n), 'a) slist) * (('(div2 n), 'a) slist) =
match l in (('(div2 n), 'a) slist) * (('(div2 n), 'a) slist) with
| Nil -> Nil, Nil
| Cons .|m1:'(nat)| (x, xt) ->
match xt in (('(div2 n), 'a) slist) * (('(div2 n), 'a) slist) with
| Cons .|m2: '(nat)| (y, xs) ->
let (xxs,yys) = (split .|'(m2),'(even_minus_two m2 p)| xs) in Cons .|'(div2 m2)| (x, xxs), Cons .|'(div2 m2)| (y, yys) ;;
*************************************************************************
File "fft1.ml", line 113, characters 35-57:
Coq error in escaped type:
At location 19-20:
Error:
In environment
m2 : nat
m1 := S m2 : nat
n := S _UNBOUND_REL_0 : nat
p : even n
The term "p" has type "even n" while it is expected to have type
"even (S (S m2))"
*************************************************************************
Clearly, n = S m1 and m1 = S m2, but the pattern matching lost this information in the nested match statement. Test and evaluationThe test cases we used is at the end of the attached source code in the "Appendix".Appendix: Full implementation in concoqtion
(***********************************************
* Angela Yun Zhu *
* Cherif Salama *
* RAP, CS Dept, Rice Univ. *
* 2007, April 8th *
* Seminar 617 Homework *
* Concoqtion implementation of FFT *
************************************************)
(* Pi value *)
let pi = 4.0 *. atan(1.0)
(* W = exp(-2PI dir/N)^j. Note that dir=1.0 for the forward transform *)
let w dir n j =
let theta = dir *. ((float_of_int (-2 * j)) *. pi) /. (float_of_int n) in
((cos theta), (sin theta))
(* Complex number addition. Complex numbers are represented as float tuples *)
let add ((r1,i1), (r2, i2)) = ((r1 +. r2), (i1 +. i2))
(* Complex number addition. Complex numbers are represented as float tuples *)
let sub ((r1,i1), (r2, i2)) = ((r1 -. r2), (i1 -. i2))
(* Complex number addition. Complex numbers are represented as float tuples *)
let mult ((r1, i1), (r2, i2)) =
let rp = (r1 *. r2) -. (i1 *. i2) in
let ip = (r1 *. i2) +. (r2 *. i1) in
(rp, ip)
(* Sized lists data type as defined before. Use to guarantee
that the list returned from the fft function is of
the correct size which is equal to the returned size *)
type ('n:'(nat), 'a) slist =
| Nil : ('(0), 'a) slist
| Cons of let 'm:'(nat) in 'a * ('(m), 'a) slist: ('(S m), 'a) slist
;;
(* Some coq definitions and lemmas *)
coq
(* Required Libraries *)
Require Import Div2.
Require Import Arith.Gt.
(* Inductive definition of isPower2, if n=1 than it is a
power of 2 and if n=double n0 where n0 is a power of2
than n also is a power of 2 *)
Inductive isPower2: nat -> Prop :=
| Power_0 : isPower2 1
| Power_S : forall n, isPower2 n -> isPower2 (double n).
(* A successor of a successor is always greater than 1.
Used for the safe length function *)
Lemma ss_gt_1: forall n, gt (S (S n)) 1.
auto with arith.
Qed.
(* Half double n equals n. Used in the proof of the following
2 lemmas *)
Lemma div2_double : forall n:nat, div2 (2*n) = n.
induction n.
simpl; auto.
simpl.
replace (n + S (n + 0)) with (S (2 * n)).
rewrite IHn.
auto.
simpl.
auto with arith.
Qed.
(* If n > 1 and n is a power of 2 than dividing n by 2 gives
a power of 2 also. Used in safefft to prove that after spliting
a sized list whose length is a power of 2 we get 2 sized lists
whose lengths are also powers of 2 *)
Lemma div_power2: forall n, gt n 1 -> isPower2 n -> isPower2 (div2 n).
intros.
inversion H0.
subst.
absurd (1 > 1).
apply gt_irrefl.
auto.
replace (div2 (double n0)) with n0.
auto.
replace (double n0) with (2 * n0).
rewrite div2_double.
auto.
replace (double n0) with (n0 + n0).
simpl.
auto.
auto.
Qed.
(* If n > 1 and n is a power of 2 than dividing n by 2 and adding the
result to itself gives n again. Used in safefft to prove that after
and merging we get a list of the same size as of the original list *)
Lemma div2_plus_div2 : forall n:nat, gt n 1 -> isPower2 n -> div2 n + div2 n = n.
intros.
inversion H0.
subst.
absurd (1 > 1).
apply gt_irrefl.
auto.
replace (div2 (double n0)) with n0.
auto.
replace (double n0) with (2 * n0).
rewrite div2_double.
auto.
replace (double n0) with (n0 + n0).
simpl.
auto.
auto.
Qed.
(* if m=n than a sized list of size m has a size equal to a sized list
of size n. Used in aux function to help achieving a safe cast *)
Lemma list_cast : forall e, forall m n:nat, (m=n) ->
(OCaml_slist m e) = (OCaml_slist n e).
intro; intro; intro.
intro H.
rewrite -> H.
trivial.
Qed.
end;;
(* external castFun : 'a -> 'b = "%identity" *)
external castFun:'a -> 'b = "%identity";;
(* val cast : (forall a , b , p:'(a = b) . '(a) -> '(b)) = <forall> *)
let cast .|a,b,p:'(a=b)| (x:'(a)) : '(b) = castFun x;;
let aux .|a, m:'(nat), p:'(gt m 1), q:'(isPower2 m)| (x: ('((div2 (m))+(div2 (m))), 'a) slist) =
cast .|'(OCaml_slist ((div2 (m))+(div2 (m))) a),
'(OCaml_slist (m) a),
'(list_cast a ((div2 (m))+(div2 (m))) (m) (div2_plus_div2 m p q))| x
(* splits a lists into 2 lists of equal size, one containing the elements
originally in odd positions and one containing the elements originally
in even positions *)
let rec split .|n:'(nat)| (l: ('(n), 'a) slist): (('(div2 n), 'a) slist) * (('(div2 n), 'a) slist)=
match l in (('(div2 n), 'a) slist) * (('(div2 n), 'a) slist) with
| Nil -> Nil, Nil
| Cons .|m1:'(nat)| (x, xs) ->
(match xs in (('(div2 n), 'a) slist) * (('(div2 n), 'a) slist) with
| Cons .|m2:'(nat)| (y, ys) ->
let (xxs, yys) = split .|'(m2)| ys in
Cons .|'(div2 m2)| (x, xxs), Cons .|'(div2 m2)| (y, yys))
(* The following 4 function are needed to implement the original merge function
as appeared in the paper *)
(* Function that takes a sized list and returns its length as an OCaml integer.
Used in merge function *)
let rec fftlength .|n:'(nat)| (l: ('(n), 'a) slist) : int =
match l in int with
| Nil -> 0
| Cons .|m1:'(nat)| (x, xs) -> 1 + fftlength .|'(m1)| xs;;
(* Function that takes 2 sized lists and append them. Used in merge function *)
let rec appends .|m:'(nat), n:'(nat)| (l1 : ('(m), 'a) slist) (l2 : ('(n), 'a) slist): ('(m+n), 'a) slist =
(match l1 as ('i:'(nat),'t) slist in ('(i+n),'t) slist with
| Nil -> l2
| Cons .| m1:'(nat)| (x,xs) -> Cons .| '(m1+n) | (x, appends .|'(m1),'(n)| xs l2));;
(* Helper function for merge *)
let rec mg .|n:'(nat)| (l1: ('(n), 'a) slist) (l2: ('(n), 'a) slist) (dir: float) (n: int) (j: int): (('(n), 'a) slist) * (('(n), 'a) slist) =
match (l1, l2) in (('(n), 'a) slist) * (('(n), 'a) slist) with
| (Cons .|m1:'(nat)| (x, xs), Cons .|m2:'(nat)| (y, ys)) ->
let z1 = mult (w dir n j, y) in
let zx = add (x, z1) in
let zy = sub (x, z1) in
let (a,b) = (mg .|'(m1)| xs ys dir n (j+1)) in (Cons .|'(m1)| (zx,a), Cons .|'(m1)| (zy,b))
| (Nil, Nil) -> (Nil, Nil);;
(* Merge function. Used in safefft *)
let merge .|m:'(nat)| (dir: float) (l1: ('(m), 'a) slist) (l2: ('(m), 'a) slist): (('(m+m), 'a) slist) =
match l1 in (('(m+m), 'a) slist) with
| Cons .|m1:'(nat)| (x, xs) ->
let n = 2 * fftlength .|'(S m1)| l1 in
let (a,b) = mg .|'(S m1)| l1 l2 dir n 0 in appends .|'(S m1),'(S m1)| a b;;
(* The following type and 3 functions are needed to implement the original fft function
as appeared in the paper *)
(* This type is required to be able to easily differentiate between numbers that are
greater than 1 and those that are not while storing their values. This can be
thought of a way of encoding natural numbers while keeping the fact of either
they are greater than 1 or not explicit in the encoding itself *)
type 'n:'(nat) gt_one =
| No0: '(0) gt_one
| No1: '(S 0) gt_one
| Yes of let 'm:'(nat) 'p:'(gt m 1) in unit : '(m) gt_one
;;
(* This function is passed a sized list and returns a gt_one encoded number representing
its length *)
let rec safelength .|n:'(nat)| (l: ('(n), 'a) slist) : '(n) gt_one =
match l in '(n) gt_one with
| Nil -> No0
| Cons .|m1:'(nat)| (x, xs) ->
(match xs in '(n) gt_one with
| Nil -> No1
| Cons .|m2:'(nat)| (y, ys) -> Yes .|'(n), '(ss_gt_1 m2)| ())
(* This function is passed a gt_one encoded number and returns a function that work as follows:
If given a function that returns something of type r if this number is greater than 1
and another function that returns something of type r if this number is not greater than r
it returns something of type r *)
let rec destr_gt .|n:'(nat),r:'(OCamlType)| (cond:'(n) gt_one) : (forall p:'(n>1). unit -> '(r)) -> (forall p:'(n<=1). unit -> '(r)) -> '(r) =
match cond as ('q:'(nat)) gt_one in (forall p:'(q>1). unit -> '(r)) -> (forall p:'(q<=1). unit -> '(r)) -> '(r) with
| Yes .|m:'(nat),p:'(m>1)| () -> fun f g -> f .|'(p)| ()
| No0 -> fun f g -> g .|'(le_S 0 0 (le_n 0))| ()
| No1 -> fun f g -> g .| '(le_n 1)| ()
(* Safe FFT function that takes a sized list of size n which is guaranteed to be a power of 2
and returns the FFT as a list of the same size *)
let rec safefft .|n:'(nat),p:'(isPower2 n)| (dir:float) (l:('(n),'a) slist) : ('(n),'a) slist =
let case_less .|q1:'(n<=1)| () : ('(n),'a) slist = l in
let case_greater .|q2:'(n>1)| () : ('(n),'a) slist =
let (e,o) = split .|'(n)| l in
let y0 = safefft .|'(div2 n),'(div_power2 n q2 p)| dir e in
let y1 = safefft .|'(div2 n),'(div_power2 n q2 p)| dir o in
let rr = aux .|'a, '(n), '(q2), '(p)| (merge .|'(div2 n)| dir y0 y1) in rr in
let condition = destr_gt .|'(n), ('(n),float * float) slist| (safelength .|'(n)| l) in
condition case_greater case_less
;;
(* The following function is the standard fft implementation from the paper *)
let pi0 = 4.0 *. atan(1.0);;
let w0 dir n j =
let theta = dir *. ((float_of_int (-2 * j)) *. pi0) /. (float_of_int n) in
((cos theta), (sin theta)) ;;
let add0 ((r1,i1), (r2, i2)) = ((r1 +. r2), (i1 +. i2));;
let sub0 ((r1,i1), (r2, i2)) = ((r1 -. r2), (i1 -. i2));;
let mult0 ((r1, i1), (r2, i2)) =
let rp = (r1 *. r2) -. (i1 *. i2) in
let ip = (r1 *. i2) +. (r2 *. i1) in
(rp, ip);;
let rec split0 l =
match l with
[] -> ([], [])
| x::y::xs ->
let (a,b) = split0 xs in (x::a, y::b);;
let merge0 dir l1 l2 =
let n = 2 * List.length l1 in
let rec mg0 l1 l2 j =
match (l1, l2) with
(x::xs, y::ys) ->
let z1 = mult0 (w0 dir n j, y) in
let zx = add0 (x, z1) in
let zy = sub0 (x, z1) in
let (a,b) = (mg0 xs ys (j+1)) in (zx::a, zy::b)
| _ -> ([], []) in
let (a,b) = mg0 l1 l2 0 in (a @ b);;
let rec fft0 dir l =
if (List.length l = 1) then
l
else
let (e,o) = split0 l in
let y0 = fft0 dir e in
let y1 = fft0 dir o in
merge0 dir y0 y1;;
(* Tests *)
(* Regular fft *)
let l10 = [(1.1,2.2);(3.3,4.4);(5.5,6.6);(7.7,8.8)];;
fft0 1.0 l10;;
(* Safe fft *)
let l1 = (Cons .|'(3)|((1.1,2.2),Cons .|'(2)|((3.3,4.4),Cons .|'(1)|((5.5,6.6),Cons .|'(0)|((7.7,8.8),Nil)))));;
safefft .|'(4),'(Power_S 2 (Power_S 1 Power_0))| 1.0 l1;;
(* Performance comparison *)
Trxtime.init_times ();;
Trxtime.timenew "Regular fft" (fun _ -> fft0 1.0 l10);;
Trxtime.timenew "Safe fft" (fun _ -> (safefft .|'(4),'(Power_S 2 (Power_S 1 Power_0))| 1.0 l1));;
Trxtime.print_times ();;
(* Results running the above code using concoqtion interpreter *)
(*
__ Regular fft _____________ 65536x avg= 1.793996E-02 msec
__ Safe fft ________________ 65536x avg= 1.936152E-02 msec
*)
(* Results running the above code using concoqtion compiler *)
(*
__ Regular fft _____________ 65536x avg= 2.199154E-02 msec
__ Safe fft ________________ 65536x avg= 2.548305E-02 msec
*)
(* In both cases Regular fft is about 10% faster than Safe fft *)
Project proposal: FFT using indexed typesProject Overview
MotivationDiscrete Fourier Transformation (DFT) is widely used in digital signal processing applications, such as linear filtering, correlation analysis, and spectrum analysis. Its efficient computation is a topic that has been greatly studied in both academia and industry. The algorithm to perform DFT is called Fast Fourier Transform (FFT). Basically, computing the DFT can be seen as computing a sequence {X(k)} of N complex-valued numbers given another sequence of data {x(n)} of the same length N.We propose an index-typed implementation of FFT based on Cooley-Turkey's recursive algorithm. Using that index-type, we get the following benefits:
We also want to check the following:
Project phases
Other project ideas
Braun treesTypesRegular Braun Tree TypeDefinition:
type 'a btree =
|Leaf
|Parent of 'a * 'a btree * 'a btree;;
Dependently-Typed Braun Tree TypeOld Definition: This definition causes matching problems in the insert function and needs a lemma to be added to prove that Type S (S (m + m)) is equal to (S m + S m). An alternative way to avoid this is using Dan's definition using multiplication.
type ('n:'(nat),'a) dbtree =
|DLeaf :('(0),'a) dbtree
|DParent1 of let 'n:'(nat) in 'a * ('(n),'a) dbtree *('(n),'a) dbtree : ('(S (n+n)),'a) dbtree
|DParent2 of let 'n:'(nat) in 'a * ('(S n),'a) dbtree *('(n),'a) dbtree: ('(S (S (n+n))),'a) dbtree;;
New Definition: This definition is based on Dan's definition:
type ('n:'(nat),'a) dbtree =
|DLeaf :('(0),'a) dbtree
|DParent1 of let 'n:'(nat) in 'a * ('(n),'a) dbtree *('(n),'a) dbtree : ('(S (n*2)),'a) dbtree
|DParent2 of let 'n:'(nat) in 'a * ('(S n),'a) dbtree *('(n),'a) dbtree: ('(S (S (n*2))),'a) dbtree;;
FunctionsMap FunctionUnsized
let rec map_bt f bt =
match bt with
|Leaf -> Leaf
|Parent(x,bt1,bt2) -> Parent(f x, map_bt f bt1, map_bt f bt2);;
Sized
let rec map_dbt .|n:'(nat)| (f:'a ->'b) (dbt:('(n),'a) dbtree): ('(n),'b) dbtree =
match dbt in ('(n),'t) dbtree with
|DLeaf -> DLeaf
|DParent1 .|m:'(nat)| (x,dbt1,dbt2) -> DParent1 .|'(m)| ((f x), (map_dbt .|'(m)| f dbt1), (map_dbt .|'(m)| f dbt2))
|DParent2 .|m:'(nat)| (x,dbt1,dbt2) -> DParent2 .|'(m)| ((f x), (map_dbt .|'(S m)| f dbt1), (map_dbt .|'(m)| f dbt2));;
Get_size and Diff FunctionsUnsizedThis version runs in O(n)
let rec getSize_bt1 bt =
match bt with
|Leaf -> 0
|Parent(x,bt1,bt2) -> (getSize_bt1 bt1) + (getSize_bt1 bt2) + 1;;
Another Definition for Regular Braun Trees: This version makes use of the diff function and runs in O(log^2 n)
let rec getSize_bt2 bt =
let rec diff bt m =
(match bt with
| Leaf -> 0
| Parent(x,bt1,bt2) ->
if m = 0 then 1
else
(if m mod 2 = 1 then
diff bt1 (m-1)/2
else
diff bt2 (m-2)/2))
in match bt with
|Leaf -> 0
|Parent(x,bt1,bt2) -> let n = (getSize_bt2 bt2) in 1+2*n+(diff bt1 n);;
SizedRuns in O(log n) without even needing the diff function
let rec getSize_dbt .|n:'(nat)| (dbt:('(n),'a) dbtree): int =
match dbt in int with
|DLeaf -> 0
|DParent1 .|m:'(nat)| (x,dbt1,dbt2) -> 1 + (2 * (getSize_dbt .|'(m)| dbt2))
|DParent2 .|m:'(nat)| (x,dbt1,dbt2) -> 2 + (2 * (getSize_dbt .|'(m)| dbt2));;
Insert FunctionUnsized
let rec insert_bt1 x bt =
match bt with
|Leaf -> Parent(x,Leaf,Leaf)
|Parent(r,bt1,bt2) ->
if (getSize_bt2 bt1) > (getSize_bt2 bt2) then
Parent(r,bt1,(insert_bt1 x bt2))
else
Parent(r,(insert_bt1 x bt1),bt2);;
Another Definition for Regular Braun Trees: This definition is based on the standard cons for braun trees. It is easier to implement since it always insert the new element as the root and adds the element that was initially the root to the right tree and then swaps the left and right branches.
let rec insert_bt2 x bt =
match bt with
|Leaf -> Parent(x,Leaf,Leaf)
|Parent(r,bt1,bt2) -> Parent(x,(insert_bt2 r bt2),bt1);;
Sized
let rec insert_dbt1 .|n:'(nat)| (x:'a) (dbt:('(n),'a) dbtree): ('(S n),'a) dbtree =
match dbt in ('(S n),'a) dbtree with
|DLeaf -> DParent1 .|'(0)| (x,DLeaf,DLeaf)
|DParent1 .|m:'(nat)| (r,dbt1,dbt2) -> DParent2 .|'(m)| (r, (insert_dbt1 .|'(m)| x dbt1), dbt2)
|DParent2 .|m:'(nat)| (r,dbt1,dbt2) -> DParent1 .|'(S m)| (r, dbt1, (insert_dbt1 .|'(m)| x dbt2));;
Another Definition for Dependtly-typed Braun Trees: Based on swaping the left and right branches
let rec insert_dbt2 .|n:'(nat)| (x:'a) (dbt:('(n),'a) dbtree): ('(S n),'a) dbtree =
match dbt in ('(S n),'a) dbtree with
|DLeaf -> DParent1 .|'(0)| (x,DLeaf,DLeaf)
|DParent1 .|m:'(nat)| (r,dbt1,dbt2) -> DParent2 .|'(m)| (x, (insert_dbt2 .|'(m)| r dbt2), dbt1)
|DParent2 .|m:'(nat)| (r,dbt1,dbt2) -> DParent1 .|'(S m)| (x, (insert_dbt2 .|'(m)| r dbt2), dbt1);;
Note both versions for dependly typed braun trees work correctly now after changing the dependtly-typed braun tree definition to use multiplication, before that change both versions returned the following error: Output:
File "bt.ml", line 42, characters 41-98:
Type [(m : nat) (n := S (S (m + m)) : nat)] |-
('(S n), 'e1) dbtree was expected but
('(S (S m + S m)), 'e1) dbtree was inferred for the branch expression.
Fold FunctionUnsized
let rec fold_bt i step bt =
match bt with
|Leaf -> i
|Parent(r,bt1,bt2) -> step r (fold_bt (fold_bt i step bt2) step bt1);;
Sized
let rec fold_dbt .|n:'(nat)| (i:'a) (step:'b ->'a -> 'a) (dbt:('(n),'b) dbtree): 'a =
match dbt in 'a with
|DLeaf -> i
|DParent1 .|m:'(nat)| (r,dbt1,dbt2) -> step r (fold_dbt .|'(m)| (fold_dbt .|'(m)| i step dbt2) step dbt1)
|DParent2 .|m:'(nat)| (r,dbt1,dbt2) -> step r (fold_dbt .|'(S m)| (fold_dbt .|'(m)| i step dbt2) step dbt1);;
Note this function compiles and works correctly but it gives the following unjustified warning: Output:
File "bt.ml", line 81, characters 16-18:
Warning: Unspecified return type for the extended match expression.
val fold_dbt :
(forall n:'(nat) . 'a -> ('b -> 'a -> 'a) -> ('(n), 'b) dbtree -> 'a) =
<forall>
ListsWalid: This section looks quite good! Here are a few suggestions that would improve it:
TypesSized List TypeThis was straight forward but using Definition:
type ('n:'(nat),'a) slist =
| Nil : ('(0),'a) slist
| Cons of let 'm:'(nat) in 'a * ('(m),'a) slist : ('(S m),'a) slist;;
Output:
type ('a:'(nat), 'b:'(OCamlType)) slist =
Nil:('(0), 'b) slist
| Cons of let 'm:'(nat) in 'b * ('(m), 'b) slist : ('(S m), 'b) slist
Option TypeThis type is used to represent an optional return value of a certain type. Definition: type 'a option = |None |Some of 'a;;Output: type 'a:'(OCamlType) option = None:'a option | Some of 'a : 'a option Existsize TypeThis type is used to represent the existential quantifier Definition:
type 'a existsize = E of let 'n:'(nat) in ('(n),'a) slist;;
Output:
type 'a:'(OCamlType) existsize =
E of let 'n:'(nat) in ('(n), 'a) slist : 'a existsize
FunctionsMap FunctionBoth versions for Unsized and Sized lists look very similar and were easy to write. Unsized
let rec mapn f l =
match l with
| []->[]
| h::t -> (f h)::(mapn f t);;
Output:
val mapn : ('a -> 'b) -> 'a list -> 'b list = <fun>
Sized
let rec maps .|m:'(nat)| (f:'a ->'b) (la:('(m),'a) slist): ('(m),'b) slist =
(match la in ('(m),'t) slist with
|Nil -> Nil
|Cons .|n:'(nat)| (lah,lat) -> Cons .|'(n)| ((f lah), (maps .|'(n)| f lat)));;
Output:
val maps :
(forall m:'(nat) . ('a -> 'b) -> ('(m), 'a) slist -> ('(m), 'b) slist) =
<forall>
Zip FunctionThe sized version has the nice property that we are sure from the typing information that both lists are equal. Therefore we only needed two branches in the match statement, while in the unsized version we need three at least. Also in the unsized version, there are different ways to handle the case where both lists are not equal. The one I picked is just to trim the resulting list to be of length equal to the minimum of input lists lengths ignoring the remaining elements of the longer list. Other approaches would have been to raise an error or to try to append the shorter list with dummy values. Note that all these problems are not encountered in case of the sized version because of the static guarantees on the list sizes. Unsized
let rec zipn l1 l2 =
match (l1,l2) with
|(_,[])->[]
|([],_)->[]
|(h1::t1,h2::t2) -> (h1,h2)::(zipn t1 t2);;
Output:
val zipn : 'a list -> 'b list -> ('a * 'b) list = <fun>
Sized
let rec zips .|m:'(nat)| (la:('(m),'a) slist) (lb:('(m),'b) slist): ('(m),('a*'b)) slist=
(match (la,lb) in ('(m),('t1*'t2)) slist with
|(Nil,Nil) -> Nil
|(Cons .|n:'(nat)| (lah,lat),Cons .|n:'(nat)| (lbh,lbt)) -> Cons .|'(n)| ((lah,lbh),(zips .|'(n)| lat lbt)));;
Output:
val zips :
(forall m:'(nat) .
('(m), 'a) slist -> ('(m), 'b) slist -> ('(m), 'a * 'b) slist) =
<forall>
Add_twice FunctionThis is a very simple function that takes an element and a list and inserts that element twice in the head of the list. Both the sized and unsized versions are similar. Unsizedlet add_twicen x xs = x::(x::xs);;Output: val add_twicen : 'a -> 'a list -> 'a list = <fun> Sizedlet add_twices .|m:'(nat)| x xs = Cons .|'(S m)| (x, Cons .|'(m)| (x, xs));;Output:
val add_twices :
(forall m:'(nat) . 'a -> ('(m), 'a) slist -> ('(S (S m)), 'a) slist) =
<forall>
Append FunctionThe standard append function, implemented in a similar fashion for both sized and unsized lists. Unsized
let rec appendn l1 l2 =
match l1 with
| []-> l2
| h::t -> h::(appendn t l2);;
Output:
val appendn : 'a list -> 'a list -> 'a list = <fun> Sized
let rec appends .|m:'(nat), n:'(nat)| (l1 : ('(m), 'a) slist) (l2 : ('(n), 'a) slist): ('(m+n), 'a) slist =
(match l1 as ('i:'(nat),'t) slist in ('(i+n),'t) slist with
| Nil -> l2
| Cons .| m1:'(nat)| (x,xs) -> Cons .| '(m1+n) | (x, appends .|'(m1),'(n)| xs l2));;
Output:
val appends :
(forall m:'(nat) , n:'(nat) .
('(m), 'a) slist -> ('(n), 'a) slist -> ('(m + n), 'a) slist) =
<forall>
Generate FunctionTo be able to test functions with long lists, I wrote this generate function that generates a list of integers given a start value, and end value and a step. The sized version was a little more complex and required the use of existential type. It's use in a recursive call required unwrapping the E constructer and wrapping again. Unsized
let rec generaten st en stp =
if (en<st) then [] else
st::(generaten (st+stp) en stp);;
Output:
val generaten : int -> int -> int -> int list = <fun> Sized
let rec generates (st:int) (en:int) (stp:int): 'a existsize =
if (en<st) then (E .|'(0)| Nil) else
(match (generates (st+stp) en stp) in 'c existsize with
| E .|n:'(nat)| sl -> E .|'(1+n)| (Cons .|'(n)| (st,sl)) ) ;;
Output:
val generates : int -> int -> int -> int existsize = <fun> Size_It FunctionThe size_it function that takes a list and returns an existential type encapsulating a sized list. So we could think of it as a function that converts an unsized list into a sized one. Definition:
let rec size_it (la: 'a list): 'a existsize =
(match la in 'b existsize with
|[] -> E .|'(0)| Nil
|h::t -> (match (size_it t) in 'c existsize with
| E .|n:'(nat)| sl -> E .|'(1+n)| (Cons .|'(n)| (h,sl))));;
Output:
val size_it : 'a list -> 'a existsize = <fun> Unsize_It FunctionThe unsize_it should do the inverse of what the size_it function does. It should be able to convert a sized list into an unsized one. I have tried two versions of it, only the first one is working. The first one takes an slist and returns a list, while the second one take an existsize and returns a list. The second is a more accurate inverse to the size_it function. Definition 1 (working):
let rec unsize_it1 .|m:'(nat)| (la:('(m),'a) slist): 'a list =
(match la in 't list with
|Nil -> []
|Cons .|n:'(nat)| (lah,lat) -> lah::(unsize_it1 .|'(n)| lat));;
Output:
val unsize_it1 : (forall m:'(nat) . ('(m), 'a) slist -> 'a list) = <forall>
Definition 2 (potential bug):
let rec unsize_it2 (ela:'a existsize): 'a list =
(match ela as 'a existsize in 'a list with
|E .|n:'(nat)| slist ->
(match slist in 'a list with
|Nil -> []
|Cons .|n:'(nat)| (lah,lat) -> lah::unsize_it2 (E .|'(n)| lat)));;
Output:
Error while checking body of the context-refined branch:
(n : nat) (a : OCamlType) (n := S n : nat)
This expression has type ('(_UNBOUND_REL_3), 'a) slist
but is here used with type ('(_UNBOUND_REL_1), 'b) slist
Sample TestsThe following is a list of sample interactions with concoqtion interpreter after loading the above definitions except for unsize_it2 that is not working:let g n = if n then 0 else 1;; val g : bool -> int = <fun> let h x = (x mod 2) = 0;; val h : int -> bool = <fun>
let l1 = Cons .|'(2)|(true, Cons .|'(1)| (false, Cons .|'(0)| (false, Nil)));;
val l1 : ('(3), bool) slist = Cons (true, Cons (false, Cons (false, Nil)))
let l1'= unsize_it1 .|'(3)| l1;; val l1' : bool list = [true; false; false]
let l2 = maps .|'(3)| g l1;;
val l2 : ('(3), int) slist = Cons (0, Cons (1, Cons (1, Nil)))
let l2'= unsize_it1 .|'(3)| l2;; val l2' : int list = [0; 1; 1]
let l3 = zips .|'(3)| l1 l2;;
val l3 : ('(3), bool * int) slist =
Cons ((true, 0), Cons ((false, 1), Cons ((false, 1), Nil)))
let l3'= unsize_it1 .|'(3)| l3;; val l3' : (bool * int) list = [(true, 0); (false, 1); (false, 1)]
let l4 = add_twices .|'(3)| true l1;;
val l4 : ('(5), bool) slist =
Cons (true, Cons (true, Cons (true, Cons (false, Cons (false, Nil)))))
let l4'= unsize_it1 .|'(5)| l4;; val l4' : bool list = [true; true; true; false; false]
let l5 = appends .|'(3),'(5)| l1 l4;;
val l5 : ('(3 + 5), bool) slist =
Cons (true, Cons (false, Cons (false, Cons (true, Cons (true, Cons (true, Cons (false, Cons (false, Nil))))))))
let l5'= unsize_it1 .|'(8)| l5;; val l5' : bool list = [true; false; false; true; true; true; false; false] let l6 = generates 20 45 6;; val l6 : int existsize = E (Cons (20, Cons (26, Cons (32, Cons (38, Cons (44, Nil)))))) let l7 = size_it [1;5;6;70];; val l7 : int existsize = E (Cons (1, Cons (5, Cons (6, Cons (70, Nil)))))
let bigl = generates 1 10000 1;;
val bigl : int existsize =
E
(Cons (1,
Cons (2,
Cons (3,
Cons (4,
Cons (5,
Cons (6,
Cons (7, ..)))))))
let bigl' = generaten 1 10000 1;; val bigl' : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20; 21; 22; 23; 24; ...] Timing
Trxtime.init_times ();;
Trxtime.timenew "Regular zip" (fun _ -> zipn l2' l2');;
Trxtime.timenew "Sized zip" (fun _ -> (zips .|'(3)| l2 l2));;
Trxtime.timenew "Regular map" (fun _ -> mapn g l1');;
Trxtime.timenew "Sized map" (fun _ -> (maps .|'(3)| g l1));;
Trxtime.timenew "Regular append" (fun _ -> appendn l2' l2');;
Trxtime.timenew "Sized append" (fun _ -> (appends .|'(3),'(3)| l2 l2));;
Trxtime.timenew "Regular zip (big)" (fun _ -> zipn bigl' bigl');;
let _ =match bigl as 'a existsize in unit with
| E .|n:'(nat)| sl->
let _ = Trxtime.timenew
"Sized zip (big)" (fun _ -> zips .|'(n)| sl sl) in ();;
Trxtime.timenew "Regular map (big)" (fun _ -> mapn h bigl');;
let _ =match bigl as 'a existsize in unit with
| E .|n:'(nat)| sl->
let _ = Trxtime.timenew
"Sized map (big)" (fun _ -> maps .|'(n)| h sl) in ();;
Trxtime.timenew "Regular append (big)" (fun _ -> appendn bigl' bigl');;
let _ =match bigl as 'a existsize in unit with
| E .|n:'(nat)| sl->
let _ = Trxtime.timenew
"Sized append (big)" (fun _ -> appends .|'(n),'(n)| sl sl) in ();;
Trxtime.print_times ();;
I have run the timing test 4 times and I got very similar results each time. The following table summarizes the obtained results. It can be seen that in all cases (for small and big lists and for all tested functions) Sizing does not add any overhead but it does not save time neither. The difference in timing between both of them is very close to zero and should be ignored.
Topic Actions: Edit | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r24 < r23 < r22 < r21 < r20 | More topic actions
Webs: Main | TWiki | Africa | EmbeddedSystems | Gpce | Houston | International | K12 | MetaOCaml | MulticoreOCR | ProgrammingLanguages | RAP | RIDL | Sandbox | SpeechClub | Teaching | Texbot | WG211 Web Actions: |
||||||||||||||||||||||||||
This work is licensed under a Creative Commons Attribution 2.5 License. Please follow our citation guidelines.