Library Kleene

Require Export Basic.
Require Export Vector.
Export VectorNotations.

Definitions

Our class of partial recursive functions, together with their semantics.
Section Definitions.

Inductive PRFunction : nat -> Set :=
  | Zero : PRFunction 1
  | Successor : PRFunction 1
  | Projection : forall {m k:nat}, k < m -> PRFunction m
  | Composition : forall {k m:nat} (g:PRFunction m) (fs:t (PRFunction k) m), PRFunction k
  | Recursion : forall {k:nat} (g:PRFunction k) (h:PRFunction (2+k)), PRFunction (1+k)
  | Minimization : forall {k:nat} (h:PRFunction (1+k)), PRFunction k
.

Kleene is imprecise about how e.g. composition should behave on undefined arguments: does should (Composition g fs) be undefined if one of the fs is undefined, even if its value is never used? We believe the second approach to be the case (it is what is assumed in typical proofs of Turing completeness).
Fixpoint all_defined {n} (v:t (option nat) n) : bool :=
  match v with
  | [] => true
  | (Some _) :: v' => all_defined v'
  | None :: _ => false
end.

Auxiliary function for minimization: we need to specify how many computation steps we want to allow, as all Coq functions must be total. We do a &dlquote;diagonal&urquote; search.
Fixpoint find_zero_from {k} (f:t (option nat) (1+k) -> option nat) (ns:t (option nat) k) (init:nat) (steps:nat) : option nat :=
  match steps with
  | O => None
  | S m => match f (shiftin (Some init) ns) with
           | None => None
           | Some O => Some init
           | Some (S _) => find_zero_from f ns (S init) m
end end.

We'd love a readable definition of evaluation, but the dependent type of f makes it tricky to write. So... First we define the fixpoint construction over the option type, to cater for undefinedness.
Fixpoint eval_opt {m} (f:PRFunction m) : forall (steps:nat) (ns:t (option nat) m), option nat.

This is the evaluation function we actually want to use.
Definition eval {m} (f:PRFunction m) (steps:nat) (ns:t nat m) : option nat := eval_opt f steps (map Some ns).

End Definitions.

Sanity checks

Our first goal is to prove that evaluation works as expected. This requires some preliminary properties about the auxiliary functions.
Section Auxiliary_Lemmas.

Predicate all_defined works as expected.
Lemma all_defined_map_Some : forall n v, all_defined (n:=n) (map Some v) = true.

Lemma all_defined_false : forall n v, all_defined (n:=n) v = false -> exists Hi, v[@Hi] = None.

Lemma all_defined_true : forall n v, all_defined (n:=n) v = true -> forall Hi, v[@Hi] <> None.

Lemma all_defined_false' : forall n v Hi, v[@Hi] = None -> all_defined (n:=n) v = false.

If find_zero_from returns a value (Some n), then we know that f(n)=0, but also that f is positive for all values between the initial one and n. Furthermore, the number of values we tested is bounded by the first parameter.
For convenience, we split these properties in separate lemmas.
Furthermore, if f has other zeros, they must lie outside the range we tested.
Lemma find_zero_from_min : forall steps {k} f (ns:t (option nat) k) init m, find_zero_from f ns init steps = Some m ->
  forall n, f (shiftin (Some n) ns) = Some 0 -> n < init \/ m <= n.

The negative counterpart: if find_zero returns None, then f is either undefined at least once in the range tested (for the given number of steps), or it is always positive.
Lemma find_zero_from_None : forall steps {k} f (ns:t (option nat) k) init,
  find_zero_from f ns init steps = None ->
  { exists n, init <= n < init + steps /\ f (shiftin (Some n) ns) = None /\
              forall k, init <= k < n -> exists val, f (shiftin (Some k) ns) = Some (S val)} +
  { forall n, init <= n < init + steps -> exists val, f (shiftin (Some n) ns) = Some (S val) }.

Monotonicity.
A useful characterization result.
Since our definition of eval is very indirect, we now prove that it behaves as expected.
Tactics for dealing with proofs involving composition.

Ltac prove_composition_1 := intros;
                            do 2 rewrite <- nth_map';
                            do 2 rewrite <- nth_map_inv';
                            apply vector_1_equal;
                            auto.

Ltac prove_composition_2 := intros;
                            do 2 rewrite <- nth_map';
                            do 2 rewrite <- nth_map_inv';
                            apply vector_2_equal;
                            auto.

Ltac prove_composition_3 := intros;
                            do 2 rewrite <- nth_map';
                            do 2 rewrite <- nth_map_inv';
                            apply vector_3_equal;
                            auto.

Examples

We now recover the examples from the submitted journal paper.
Section Examples.

These lemmas are used to define projections. Their names seem inconsistent, but they refer to the usual convention for naming projections.
Lemma aux11 : 0 < 1.

Lemma aux12 : 0 < 2.

Lemma aux22 : 1 < 2.

Lemma aux13 : 0 < 3.

Lemma aux23 : 1 < 3.

Lemma aux33 : 2 < 3.

Addition.

Multiplication.

Sign function.

Predecessor.

Natural (total) subtraction.

Greater than.

Less or equal.

Equality.

Inequality.

Subtraction.

(Finally.)
By the way, there is a typo in the definition of sub in the paper...

Evaluation

Here we include several lemmas about evaluation.
We first prove the induction schema for partial recursive functions, which requires induction on the depth of the construction of the function.
Fixpoint depth {m} (f:PRFunction m) : nat :=
  match f with
  | Zero => 0
  | Successor => 0
  | Projection _ => 0
  | Composition g fs => 1 + Nat.max (depth g) (vmax (map depth fs))
  | Recursion g h => 1 + Nat.max (depth g) (depth h)
  | Minimization h => 1 + depth h
end.

Theorem PRFunction_induction : forall (P:forall (n:nat) (f:PRFunction n), Prop),
  P _ Zero -> P _ Successor ->
  (forall i j (Hp:i<j), P _ (Projection Hp)) ->
  (forall m k g fs, (forall H, P m fs[@H]) -> P k g -> P _ (Composition g fs)) ->
  (forall k g h, P _ g -> P _ h -> P (1+k) (Recursion g h)) ->
  (forall k h, P _ h -> P k (Minimization h)) ->
  forall n f, P n f.

Definition PRFunction_recursion : forall (F:forall (n:nat) (f:PRFunction n), Type),
  F _ Zero -> F _ Successor ->
  (forall i j (Hp:i<j), F _ (Projection Hp)) ->
  (forall m k g fs, (forall H, F m fs[@H]) -> F k g -> F _ (Composition g fs)) ->
  (forall k g h, F _ g -> F _ h -> F (1+k) (Recursion g h)) ->
  (forall k h, F _ h -> F k (Minimization h)) ->
  forall n f, F n f.

Properties:
  • evaluation is injective (it cannot return two different values)
  • if evaluation returns a value, this is preserved when the maximum number of steps is increased
  • if evaluation does not return a value (yet), then neither does it for less steps.
Lemma eval_opt_inj : forall n (f:PRFunction n) s s' ns m m',
  eval_opt f s ns = Some m -> eval_opt f s' ns = Some m' -> m = m'.

Lemma eval_opt_mon : forall m (f : PRFunction m) s ns k,
    eval_opt f s ns = Some k -> forall s', s' >= s -> eval_opt f s' ns = Some k.

Lemma eval_opt_mon' : forall m (f:PRFunction m) s ns,
  eval_opt f s ns = None -> forall s', s' <= s -> eval_opt f s' ns = None.

Lemma eval_mon : forall m (f:PRFunction m) steps ns k, eval f steps ns = (Some k) ->
  forall s', s' >= steps -> eval f s' ns = (Some k).

Lemma eval_inj_Some : forall m (f:PRFunction m) s s' ns m m', eval f s ns = Some m -> eval f s' ns = Some m' -> m = m'.

Lemma eval_inj_None : forall m (f:PRFunction m) s ns, eval f s ns = None -> forall s', s'<s -> eval f s' ns = None.

End Evaluation.

Computation

Finally we can define the computed value :-)
Section Convergence.

Definition converges {k} (f:PRFunction k) ns y := exists steps, eval f steps ns = Some y.

Definition diverges {k} (f:PRFunction k) ns := forall steps, eval f steps ns = None.

Lemma converges_inj : forall {k} f ns y y', converges (k:=k) f ns y -> converges f ns y' -> y = y'.

Lemma converges_diverges : forall {k} f ns, (diverges (k:=k) f ns <-> forall y, ~converges f ns y).

Results for recursively proving convergence.
Inversion results about convergence using each constructor.
Lemmas about divergence - currently unused.