Require Import ZArith.
Require Import Omega.
Require Import List.
Require Import FunctionalExtensionality.

Import ListNotations.

Require Import listutils.
Require Import rollingsum.
Require Import seqi.

Definition fv (l : list Z) (i : nat) : Z :=
  Z.abs(sum (firstn i l) - sum (skipn i l)).

Open Scope Z_scope.

Lemma fv_step : forall x xs n,
  fv (x::xs) (S n) = Z.abs (x + sum (firstn n xs) - sum (skipn n xs)).
Proof.
intros.
unfold fv.
auto.
Qed.

Theorem fv_nil: forall n, fv [] n = 0.
Proof.
unfold fv. intros.
rewrite firstn_nil.
rewrite skipn_nil.
auto.
Qed.

Theorem fv_0: forall l, fv l 0 = Z.abs (sum l).
Proof.
intros.
unfold fv. simpl. rewrite Z.abs_opp. auto.
Qed.

Fixpoint fulcrum_inside (curl : Z) (curr : Z) (curn : nat)
                        (bestindex : nat) (bestdiff : Z)
                        (remaining : list Z) : (nat*Z) :=
  match remaining with
  | nil => (bestindex, bestdiff)
  | x::xs =>
     let newl := curl + x in
     let newr := curr - x in
     let curdiff := Z.abs (newl - newr) in
     let (nbestn, nbestd) := if curdiff <? bestdiff then (S curn, curdiff) else (bestindex, bestdiff) in
       fulcrum_inside newl newr (S curn) nbestn nbestd xs
  end.

Definition fulcrum (l : list Z) : nat * Z :=
  fulcrum_inside 0 (sum l) (0%nat) (0%nat) (Z.abs (sum l)) l.

Theorem fulcrum_inside_invariants:
  forall remaining curl curr curn bestindex bestdiff n v,
    fulcrum_inside curl curr curn bestindex bestdiff remaining = (n,v) ->
      (bestindex <= curn)%nat ->
      bestdiff <= Z.abs (curl - curr) ->
      (n >= bestindex)%nat /\ v <= bestdiff.
Proof.
induction remaining;intros.
* simpl in *. inversion H. subst. split;omega.
* simpl in H.
  destruct (Z.abs (curl + a - (curr - a)) <? bestdiff) eqn:F.
  + pose proof (IHremaining _ _ _ _ _ _ _ H).
    destruct H2; try omega.
    apply Z.ltb_lt in F.
    split; try omega.
  + pose proof (IHremaining _ _ _ _ _ _ _ H).
    destruct H2; try omega.
    apply Z.ltb_ge in F.
    omega.
Qed.

Inductive Model : list Z -> nat -> nat -> nat -> Z -> Prop :=
 | SimpleEnd : forall lst curbest v curn,
     v = fv lst curbest ->
     curn = length lst ->
     (curbest <= curn)%nat ->
     Model lst curn curbest curbest v
 | SimpleChange : forall lst n curbest best v,
     (n < length lst)%nat ->
     fv lst (S n) < fv lst curbest ->
     (curbest <= n)%nat ->
     Model lst (S n) (S n) best v -> Model lst n curbest best v
 | SimpleStay : forall lst n curbest best v,
     (n < length lst)%nat ->
     fv lst (S n) >= fv lst curbest ->
     (curbest <= n)%nat ->
     Model lst (S n) curbest best v -> Model lst n curbest best v
 .

Example test_hypo2:
  let lst := [3;7;-4;8;2] in
    forall n v,
    fulcrum_inside 0 (sum lst) 0 0 (Z.abs (sum lst)) lst = (n,v) <->
      Model lst 0 0 n v.
Proof.
simpl.
split;intros.
* inversion H. subst; clear H.
  apply SimpleChange;simpl;compute; try omega;auto.
  apply SimpleChange;simpl;compute; try omega;auto.
  apply SimpleStay;unfold fv;simpl;try omega.
  apply SimpleStay;unfold fv;simpl;try omega.
  apply SimpleStay;unfold fv;simpl;try omega.
  apply SimpleEnd; compute; auto.
* inversion H;subst. 1: { compute in H1. inversion H1. } 2: { compute in H1. contradiction. }
  inversion H3;subst. 1: { compute in H5. inversion H5. } 2: { compute in H5. contradiction. }
  inversion H7;subst. compute in H9. inversion H9. compute in H9. inversion H9.
  inversion H11;subst. compute in H13. inversion H13. compute in H13. inversion H13.
  inversion H15;subst. compute in H17. inversion H17. compute in H17. inversion H17.
  inversion H19;subst.
  + compute. auto.
  + simpl in H20. omega.
  + simpl in H20. omega.
Qed.

Lemma sum_fv_artifacts: forall s l,
  s + sum l - s = sum l.
Proof. intros. omega. Qed.

Lemma skipnS {A} : forall lst (x : A) xs curn, x :: xs = skipn curn lst -> xs = skipn (S curn) lst.
Proof.
intros.
replace (S curn) with (1+curn)%nat by omega.
rewrite <- skipn_comp.
rewrite <- H.
auto.
Qed.

Lemma uncons_eq {A} : forall (x : A) xs ys,
   x::xs = x::ys <-> xs=ys.
Proof.
split;intros.
* inversion H. auto.
* f_equal. auto.
Qed.

Lemma firstnS {A} : forall lst (x : A) xs curn, x :: xs = skipn curn lst ->
     firstn (S curn) lst = firstn curn lst ++ [x].
Proof.
induction lst; intros.
* rewrite skipn_nil in H. inversion H.
* simpl. destruct curn.
  + simpl. simpl in H. inversion H. auto.
  + simpl firstn at 2. replace ((a :: firstn curn lst) ++ [x]) with (a:: (firstn curn lst ++ [x])) by auto.
    erewrite <- IHlst. auto.
    simpl in H. apply H.
Qed.

Theorem Model_equivalent :
  forall lst n v curn curbest,
    (curn <= length lst)%nat ->
    (n <= length lst)%nat ->
    (curbest <= curn)%nat ->
    fulcrum_inside (sum (firstn curn lst)) (sum (skipn curn lst)) curn curbest (fv lst curbest) (skipn curn lst) = (n,v) <->
      Model lst curn curbest n v.
Proof with auto.
split.
{
  (* induction is performed on [skipn curn lst] so that [curn] and [lst] advance in lockstep *)
  remember (skipn curn lst) as remaining.
  generalize dependent curn. generalize dependent v.
  generalize dependent n. generalize dependent lst.
  generalize dependent curbest.
  induction remaining as [|x xs];intros;simpl in H0.
  * (* empty remaining list, we are in the SimpleEnd case *)
    inversion H2. symmetry in Heqremaining.
    apply skipn_all_2 in Heqremaining.
    assert(curn = length lst). omega.
    apply SimpleEnd;auto.
    omega.
  * (* some data is remaining, we are in a normal step *)
    simpl in H2.
    rewrite sum_fv_artifacts in H2.
    (* either the current diff is lower than the best diff, or it is not *)
    destruct (Z.abs (sum (firstn curn lst) + x - sum xs) <? fv lst curbest) eqn:F.
    + (* SimpleChange situation *)
      apply Z.ltb_lt in F.
      (* if curn = length lst, it means we are at the final step, which is not possible, so we prove otherwise *)
      destruct (curn =? length lst)%nat eqn:CN.
      - apply Nat.eqb_eq in CN.
        rewrite CN in Heqremaining.
        rewrite skipn_all in Heqremaining.
        inversion Heqremaining.
      - apply Nat.eqb_neq in CN.
        apply SimpleChange;try omega;auto.
        unfold fv at 1.
        rewrite (firstnS lst x xs);auto.
        rewrite <- (skipnS lst x xs);auto.
        rewrite <- sum_distributive.
        simpl. rewrite Z.add_0_r...
        { apply IHxs;auto;try omega.
          * apply (skipnS _ _ _ _ Heqremaining).
          * rewrite (firstnS lst x xs);auto.
            rewrite <- sum_distributive. simpl. rewrite Z.add_0_r.
            replace (fv lst (S curn)) with (Z.abs (sum (firstn curn lst) + x - sum xs));auto.
            unfold fv.
            rewrite (firstnS lst x xs);auto.
            rewrite <- (skipnS lst x xs);auto.
            rewrite <- sum_distributive.
            f_equal.
            simpl.
            omega.
        }
   + (* SimpleStay situation *)
      apply Z.ltb_ge in F.
      (* if curn = length lst, it means we are at the final step, which is not possible, so we prove otherwise *)
      destruct (curn =? length lst)%nat eqn:CN.
      - apply Nat.eqb_eq in CN.
        rewrite CN in Heqremaining.
        rewrite skipn_all in Heqremaining.
        inversion Heqremaining.
      - apply Nat.eqb_neq in CN.
        apply SimpleStay;try omega;auto.
        unfold fv at 1.
        rewrite (firstnS lst x xs);auto.
        rewrite <- (skipnS lst x xs);auto.
        rewrite <- sum_distributive.
        simpl. rewrite Z.add_0_r. omega.
        { apply IHxs;auto;try omega.
          * apply (skipnS _ _ _ _ Heqremaining).
          * rewrite (firstnS lst x xs);auto.
            rewrite <- sum_distributive. simpl. rewrite Z.add_0_r.
            replace (fv lst (S curn)) with (Z.abs (sum (firstn curn lst) + x - sum xs));auto.
            unfold fv.
            rewrite (firstnS lst x xs);auto.
            rewrite <- (skipnS lst x xs);auto.
            rewrite <- sum_distributive.
            f_equal.
            simpl.
            omega.
        }
} {

remember (skipn curn lst) as remaining eqn:REM.
remember (firstn curn lst) as processed eqn:PRO.
(*assert(PROREM: processed ++ remaining = lst). rewrite REM. rewrite PRO. apply firstn_skipn.*)

generalize dependent n.
generalize dependent v.
generalize dependent curbest.
generalize dependent curn.
generalize dependent lst.
generalize processed.
induction remaining;intros.
* simpl.
  symmetry in REM.
  apply skipn_all_2 in REM.
  assert(F: curn = length lst). omega.
  rewrite F in H2.
  inversion H2;subst;auto;omega.
* clear processed.
  destruct curn.
  + (* curn = 0 *)
    replace curbest with O in * by omega.
    simpl.
    rewrite sum_fv_artifacts.
    inversion H2;subst.
    - simpl in REM. rewrite <- REM in H4.
      inversion H4.
    - (* SimpleChange *)
      simpl in *.
      apply Z.ltb_lt in H4.
      replace (Z.abs (a - sum remaining)) with (fv lst 1).
      rewrite H4.
      replace a with (sum [a]) at 1 by (simpl;omega).
      apply IHremaining;auto;try (rewrite <- REM; auto).
      rewrite <- REM.
      rewrite fv_step. simpl. f_equal. omega.
    - (* SimpleKeep *)
      simpl in *.
      Search ( _ < _ )%Z.
      apply Z.ge_le in H4.
      apply Z.ltb_ge in H4.
      replace (Z.abs (a - sum remaining)) with (fv lst 1).
      rewrite H4.
      replace a with (sum [a]) at 1 by (simpl;omega).
      apply IHremaining;auto;try (rewrite <- REM; auto).
      rewrite <- REM.
      rewrite fv_step. simpl. f_equal. omega.
  + (* curn > 0 *)
    pose proof (firstn_skipn (S curn) lst) as APP.
    rewrite <- REM in APP.
    inversion H2;subst.
    - rewrite H4 in REM.
      rewrite skipn_all in REM.
      inversion REM.
    - (* SimpleChange *)
      unfold fulcrum_inside.
      fold fulcrum_inside.
      replace (Z.abs (sum (firstn (S curn) lst) + a - (sum (a::remaining) - a))) with (fv ((firstn (S curn) lst) ++ a :: remaining) (S (S curn))).
      rewrite APP.
      apply Z.ltb_lt in H4.
      rewrite H4.
      replace (sum (a::remaining) - a) with (sum remaining) by (simpl;omega).
      replace (sum (firstn (S curn) lst) + a) with (sum ((firstn (S curn) lst) ++ [a])).
      apply IHremaining;auto.
      eapply skipnS. apply REM.
      erewrite <- firstnS. auto. apply REM.
      rewrite <- sum_distributive. simpl sum at 2. omega.
      remember (firstn (S curn) lst) as processed eqn:PRO.
      assert(length processed = S curn).
      rewrite PRO.
      rewrite firstn_length.
      Search(Init.Nat.min).
      apply Nat.min_l. auto.
      (* :( *)
      unfold fv.
      erewrite firstnS. 2: { rewrite APP. apply REM. }
      erewrite <- skipnS. 2: {rewrite APP. apply REM. }
      rewrite firstn_app.
      rewrite <- H7 at 1.
      rewrite firstn_all.
      rewrite H7.
      rewrite Nat.sub_diag. simpl.
      f_equal.
      rewrite <- sum_distributive.
      rewrite <- sum_distributive.
      simpl.
      omega.
    - (* SimpleStay *)
      unfold fulcrum_inside.
      fold fulcrum_inside.
      replace (Z.abs (sum (firstn (S curn) lst) + a - (sum (a::remaining) - a))) with (fv ((firstn (S curn) lst) ++ a :: remaining) (S (S curn))).
      rewrite APP.
      apply Z.ge_le in H4.
      apply Z.ltb_ge in H4.
      rewrite H4.
      replace (sum (a::remaining) - a) with (sum remaining) by (simpl;omega).
      replace (sum (firstn (S curn) lst) + a) with (sum ((firstn (S curn) lst) ++ [a])).
      apply IHremaining;auto.
      eapply skipnS. apply REM.
      erewrite <- firstnS. auto. apply REM.
      rewrite <- sum_distributive. simpl sum at 2. omega.
      remember (firstn (S curn) lst) as processed eqn:PRO.
      assert(length processed = S curn).
      rewrite PRO.
      rewrite firstn_length.
      Search(Init.Nat.min).
      apply Nat.min_l. auto.
      (* :( *)
      unfold fv.
      erewrite firstnS. 2: { rewrite APP. apply REM. }
      erewrite <- skipnS. 2: {rewrite APP. apply REM. }
      rewrite firstn_app.
      rewrite <- H7 at 1.
      rewrite firstn_all.
      rewrite H7.
      rewrite Nat.sub_diag. simpl.
      f_equal.
      rewrite <- sum_distributive.
      rewrite <- sum_distributive.
      simpl.
      omega.
}
Qed.

Theorem fulcrum_correct : forall i j l v,
   (j < length l)%nat -> fulcrum l = (i, v) -> fv l j >= fv l i.
Proof.

Qed.


Require Import Reals Coquelicot.Coquelicot.
Require Import Streams.

Require Import Lra Omega.

Require Import Utils.

Set Bullet Behavior "Strict Subproofs".

(* main results:
  Definition Standard_Gaussian_PDF (t:R) := (/ (sqrt (2*PI))) * exp (-t^2/2).

  Lemma Standard_Gaussian_PDF_normed :
     is_RInt_gen Standard_Gaussian_PDF (Rbar_locally m_infty) (Rbar_locally p_infty) 1.

  Definition Standard_Gaussian_CDF (t:Rbar) :=
      RInt_gen Standard_Gaussian_PDF (Rbar_locally m_infty) (at_point t).

  Lemma std_CDF_from_erf :
     forall x:R, Standard_Gaussian_CDF x = (/ 2) + (/2)*erf (x/sqrt 2).

  Lemma mean_standard_gaussian :
     is_RInt_gen (fun t => t*Standard_Gaussian_PDF t)
           (Rbar_locally m_infty) (Rbar_locally p_infty) 0.

  Lemma variance_standard_gaussian :
     is_RInt_gen (fun t => t^2*Standard_Gaussian_PDF t)
           (Rbar_locally m_infty) (Rbar_locally p_infty) 1.

  CoFixpoint mkGaussianStream (uniformStream : Stream R) : Stream R

  Lemma General_Gaussian_PDF_normed (mu sigma:R) :
     sigma>0 ->
     ex_RInt_gen Standard_Gaussian_PDF (Rbar_locally' m_infty) (at_point (- mu / sigma)) ->
     ex_RInt_gen Standard_Gaussian_PDF (at_point (- mu / sigma)) (Rbar_locally' p_infty) ->
     is_RInt_gen (General_Gaussian_PDF mu sigma) (Rbar_locally' m_infty) (Rbar_locally' p_infty) 1.

  Lemma mean_general_gaussian (mu sigma:R) :
    sigma > 0 ->
    ex_RInt_gen Standard_Gaussian_PDF (Rbar_locally' m_infty) (at_point (- mu / sigma)) ->
    ex_RInt_gen Standard_Gaussian_PDF (at_point (- mu / sigma)) (Rbar_locally' p_infty) ->

    is_RInt_gen (fun t => t*General_Gaussian_PDF mu sigma t)
                           (Rbar_locally' m_infty) (Rbar_locally' p_infty) mu.

  Lemma variance_general_gaussian (mu sigma : R) :
    sigma > 0 ->
    ex_RInt_gen (fun t : R => sigma ^ 2 * t ^ 2 * Standard_Gaussian_PDF t)
              (Rbar_locally' m_infty) (at_point (- mu / sigma)) ->
    ex_RInt_gen (fun t : R => sigma ^ 2 * t ^ 2 * Standard_Gaussian_PDF t)
              (at_point (- mu / sigma)) (Rbar_locally' p_infty) ->
    is_RInt_gen (fun t => (t-mu)^2*General_Gaussian_PDF mu sigma t)
              (Rbar_locally' m_infty) (Rbar_locally' p_infty) (sigma^2).

  Lemma Uniform_normed (a b:R) :
    a < b -> is_RInt_gen (Uniform_PDF a b) (Rbar_locally' m_infty) (Rbar_locally' p_infty) 1.

  Lemma Uniform_mean (a b:R) :
    a < b -> is_RInt_gen (fun t => t*(Uniform_PDF a b t)) (Rbar_locally' m_infty) (Rbar_locally' p_infty) ((b+a)/2).

  Lemma Uniform_variance (a b:R) :
    a < b -> is_RInt_gen (fun t => (t-(b+a)/2)^2*(Uniform_PDF a b t)) (Rbar_locally' m_infty) (Rbar_locally' p_infty) ((b-a)^2/12).

*)

Local Open Scope R_scope.
Implicit Type f : R -> R.

Section Gaussian_defs.

Definition erf' (x:R) := (2 / sqrt PI) * exp(-x^2).
Definition erf (x:R) := RInt erf' 0 x.

(* following is standard normal density, i.e. has mean 0 and std=1 *)
(* CDF(x) = RInt_gen Standard_Gaussian_PDF (Rbar_locally m_infty) (Rbar_locally x) *)
Definition Standard_Gaussian_PDF (t:R) := (/ (sqrt (2*PI))) * exp (-t^2/2).

(* general gaussian density with mean = mu and std = sigma *)
Definition General_Gaussian_PDF (mu sigma t : R) :=
   (/ (sigma * (sqrt (2*PI)))) * exp (- (t - mu)^2/(2*sigma^2)).

Definition Standard_Gaussian_CDF (t:R) :=
  RInt_gen Standard_Gaussian_PDF (Rbar_locally m_infty) (at_point t).

Definition General_Gaussian_CDF (mu sigma : R) (t:R) :=
  RInt_gen (General_Gaussian_PDF mu sigma) (Rbar_locally m_infty) (at_point t).

End Gaussian_defs.

Lemma continuous_Standard_Gaussian_PDF :
  forall (x:R), continuous Standard_Gaussian_PDF x.
Proof.
  intros.
  unfold Standard_Gaussian_PDF.
  apply (@ex_derive_continuous).
  now auto_derive.
Qed.

Ltac solve_derive := try solve [auto_derive; trivial | lra].
Ltac equation_simplifier := apply Rminus_diag_uniq; unfold Rsqr; simpl; field_simplify; try lra; auto with Rarith.

Ltac cont_helper
  :=
    repeat (match goal with
            | [|- continuous (fun _ => _ * _ ) _ ] => apply @continuous_scal
            | [|- continuous (fun _ => _ ^ 2 ) _ ] => apply @continuous_scal
            | [|- continuous (fun _ => Hierarchy.mult _ _ ) _ ] => apply @continuous_mult
            | [|- continuous (fun _ => _ + _ ) _ ] => apply @continuous_plus
            | [|- continuous (fun _ => _ - _ ) _ ] => apply @continuous_minus
            end
            || apply continuous_id
            || apply continuous_const
            || apply continuous_Standard_Gaussian_PDF).

Section Uniform_Distribution.

Definition Indicator (a b t:R) :=
  (if Rlt_dec t a then 0 else
     (if Rgt_dec t b then 0 else 1)).

Definition Uniform_PDF (a b t:R) :=
  (/ (b-a)) * Indicator a b t.

Lemma Uniform_normed0 (a b:R) :
  a < b -> is_RInt (fun t => (/ (b-a))) a b 1.
Proof.
  intros.
  replace (1) with (scal (b-a) (/ (b-a))).
  apply (@is_RInt_const).
  compute; field_simplify; lra.
Qed.

Lemma Indicator_left (a b:R) (f : R -> R) :
  a < b -> is_RInt_gen (fun t => (f t) * (Indicator a b t)) (Rbar_locally' m_infty) (at_point a) 0.
Proof.
    intros.
    unfold Indicator.
    apply (is_RInt_gen_ext (fun _ =>  0)).
    + exists (fun y => y<a) (fun x => x=a).
      unfold Rbar_locally'.
      exists (a).
      intros; assumption.
      now unfold at_point.
      intros x y H0 H1.
      unfold fst, snd.
      subst.
      rewrite Rmin_left; try lra.
      rewrite Rmax_right; try lra.
      intros.
      destruct (Rlt_dec x0 a); try lra.
    + apply (is_RInt_gen_ext (Derive (fun _ => 0))).
      * apply filter_forall.
        intros.
        apply Derive_const.
      * replace (0) with (0 - 0) at 1 by lra.
        apply is_RInt_gen_Derive with (f0 := fun _ => 0) (la := 0) (lb := 0).
        - apply filter_forall.
           intros.
           apply ex_derive_const.
        - apply filter_forall.
           intros.
           apply continuous_const.
        - apply filterlim_const.
        - apply filterlim_const.
Qed.

Lemma Indicator_right (a b:R) (f : R -> R) :
  a < b -> is_RInt_gen (fun t => (f t) * (Indicator a b t)) (at_point b) (Rbar_locally' p_infty)  0.
Proof.
    intros.
    unfold Indicator.
    apply (is_RInt_gen_ext (fun _ =>  0)).
    + exists (fun x => x=b) (fun y => b<y).
      now unfold at_point.
      unfold Rbar_locally'.
      exists (b).
      intros; trivial.
      intros x y H0 H1.
      unfold fst, snd.
      subst.
      rewrite Rmin_left; try lra.
      rewrite Rmax_right; try lra.
      intros.
      destruct (Rlt_dec x a); try lra.
      destruct (Rgt_dec x b); try lra.
    + apply (is_RInt_gen_ext (Derive (fun _ => 0))).
      apply filter_forall.
      intros.
      apply Derive_const.
      replace (0) with (0 - 0) at 1 by lra.
      apply is_RInt_gen_Derive with (f0 := fun _ => 0) (la := 0) (lb := 0).
      * apply filter_forall.
        intros.
        apply ex_derive_const.
      * apply filter_forall.
        intros.
        apply continuous_const.
      * apply filterlim_const.
      * apply filterlim_const.
Qed.

Lemma Indicator_full (a b:R) (f : R -> R) (l:R):
  a < b -> is_RInt f a b l -> is_RInt_gen (fun t => (f t) * (Indicator a b t)) (Rbar_locally' m_infty) (Rbar_locally' p_infty) l.
Proof.
    intros.
    replace (l) with (0 + l) by lra.
    apply (@is_RInt_gen_Chasles) with (b:=a).
    + apply Rbar_locally'_filter.
    + apply Rbar_locally'_filter.
    + apply (is_RInt_gen_ext (fun t => (f t) * (Indicator a b t))).
      * apply filter_forall.
        intros.
        now apply Rmult_eq_compat_l.
      * now apply Indicator_left with (f := f).
    + replace (l) with (l + 0) by lra.
      apply (@is_RInt_gen_Chasles) with (b:=b).
      * apply at_point_filter.
      * apply Rbar_locally'_filter.
      * apply is_RInt_gen_at_point.
        apply (is_RInt_ext f).
        rewrite Rmin_left; try lra.
        rewrite Rmax_right; try lra.
        intros.
        - unfold Indicator.
           destruct (Rlt_dec x a).
           lra.
           destruct (Rgt_dec x b).
           lra.
           lra.
        - trivial.
      * now apply Indicator_right.
Qed.

Lemma Uniform_full (a b:R) (f : R -> R) (l:R):
  a < b -> is_RInt (fun t => (f t)*(/ (b-a))) a b l -> is_RInt_gen (fun t => (f t) * (Uniform_PDF a b t)) (Rbar_locally' m_infty) (Rbar_locally' p_infty) l.
Proof.
  intros.
  apply (is_RInt_gen_ext (fun t => (f t) * (/ (b-a)) * Indicator a b t)).
  apply filter_forall.
  intros.
  unfold Uniform_PDF.
  now ring_simplify.
  now apply Indicator_full with (f := fun t => f t * (/ (b - a))).
Qed.

Lemma Uniform_PDF_non_neg (a b t :R) :
  a < b -> Uniform_PDF a b t >= 0.
Proof.
  intros.
  unfold Uniform_PDF.
  replace (0) with ((/ (b-a)) * 0) by lra.
  apply Rmult_ge_compat_l with (r2 := 0).
  left.
  apply Rinv_0_lt_compat; lra.
  unfold Indicator.
  destruct (Rlt_dec t a); try lra.
  destruct (Rgt_dec t b); try lra.
Qed.

Lemma Uniform_normed (a b:R) :
  a < b -> is_RInt_gen (Uniform_PDF a b) (Rbar_locally' m_infty) (Rbar_locally' p_infty) 1.
Proof.
  intros.
  apply (is_RInt_gen_ext (fun t => 1 * (Uniform_PDF a b t))).
  apply filter_forall.
  intros.
  lra.
  apply Uniform_full with (f := fun _ => 1) (l := 1); try lra.
  apply (is_RInt_ext (fun t => (/ (b-a)))).
  intros.
  now ring_simplify.
  now apply Uniform_normed0.
Qed.

Lemma Uniform_mean0 (a b:R) :
  a < b -> is_RInt (fun t => t*(/ (b-a))) a b ((b+a)/2).
Proof.
    intros.
    replace ((b+a)/2) with  (/(b-a)*(b^2/2) - (/(b-a)*(a^2/2))).
    + apply (@is_RInt_derive) with (f := fun t => (/(b-a))*(t^2/2)).
      rewrite Rmin_left; try lra.
      rewrite Rmax_right; try lra.
      intros.
      replace (x * (/ (b-a))) with (/(b-a) * x) by lra.
      apply is_derive_scal with (k := /(b-a)) (f:= (fun t => t^2/2)).
      apply (is_derive_ext (fun t => t * ((/2) * t))).
      intros.
      now field_simplify.
      replace (x) with (1 * (/2 * x) + x * /2) at 2 by lra.
      apply (@is_derive_mult) with (f := id) (g:= fun t => (/2) * t).
      apply (@is_derive_id).
      replace (/2) with (/2 * 1) at 1 by lra.
      apply (@is_derive_scal).
      apply (@is_derive_id).
      intros.
      apply Rmult_comm.
      intros.
      apply (@continuous_scal_l).
      apply continuous_id.
    + equation_simplifier.
Qed.

Lemma Uniform_mean (a b:R) :
  a < b -> is_RInt_gen (fun t => t*(Uniform_PDF a b t)) (Rbar_locally' m_infty) (Rbar_locally' p_infty) ((b+a)/2).
Proof.
  intros.
  apply Uniform_full with (f := fun t => t); trivial.
  now apply Uniform_mean0.
Qed.

Lemma Uniform_variance0 (a b:R) :
  a < b -> is_RInt (fun t => (/ (b-a)) * (t-(b+a)/2)^2) a b ((b-a)^2/12).
Proof.
    intros.
    replace ((b-a)^2/12) with (scal (/(b-a)) ((b-a)^3/12)).
    + apply (@is_RInt_scal) with (k := /(b-a)) (f := fun t => (t - (b+a)/2)^2) (If := (b-a)^3/12).
      apply (is_RInt_ext (fun t => t^2 - (b+a)*t + (b+a)^2/4)).
      * intros.
        now field_simplify.
      * replace ((b-a)^3/12) with ((a-b)*(b^2+4*a*b+a^2)/6 + ((b+a)^2/4)*(b-a)).
        apply is_RInt_plus with (f:= fun t=> t^2 - (b+a)*t) (g := fun t=> (b+a)^2/4).
        -  replace ((a - b) * (b ^ 2 + 4 * a * b + a ^ 2) / 6) with ((b^3/3-a^3/3) - (b-a)*(b+a)^2/2).
           apply is_RInt_minus with (f := fun t => t^2) (g := fun t => (b+a)*t).
           apply (@is_RInt_derive) with (f := fun t => t^3/3).
           ++ intros.
              apply (is_derive_ext (fun t => (/3) * t^3)).
              intros; now field_simplify.
              replace (x^2) with (/3 * (INR(3%nat) * 1 * x^2)).
              apply is_derive_scal.
              apply is_derive_pow with (f:=id) (n := 3%nat) (l:=1).
              apply (@is_derive_id).
              simpl; now field_simplify.
           ++ rewrite Rmax_right; try lra.
              rewrite Rmin_left; try lra.
              intros.
              cont_helper.
           ++ replace ((b - a) * (b + a) ^ 2 / 2) with ((b+a)*((b^2/2-a^2/2))).
              apply (@is_RInt_scal).
              apply is_RInt_derive with (f:=fun x => x^2/2).
              rewrite Rmax_right; try lra.
              rewrite Rmin_left; try lra.
              ** intros.
                 apply (is_derive_ext (fun t => (/2) * t^2)).
                 intros.
                 now field_simplify.
                 replace (x) with (/2 * (2 * x)) at 2 by lra.
                 apply is_derive_scal.
                 replace (2 * x) with (INR(2%nat) * 1 * x^1) by
                 (simpl; field_simplify; lra).
                 apply is_derive_pow with (f:=id) (n := 2%nat) (l:=1).
                 apply (@is_derive_id).
              ** rewrite Rmax_right; try lra.
                 rewrite Rmin_left; try lra.
                 intros.
                 apply continuous_id.
              ** now field_simplify.
           ++
              equation_simplifier.
        -  replace ((b + a) ^ 2 / 4 * (b - a)) with (scal (b-a) ((b+a)^2/4)).
           apply (@is_RInt_const).
           compute; now field_simplify.
        -  equation_simplifier.
    + compute; field_simplify; lra.
Qed.

Lemma Uniform_variance (a b:R) :
  a < b -> is_RInt_gen (fun t => (t-(b+a)/2)^2*(Uniform_PDF a b t)) (Rbar_locally' m_infty) (Rbar_locally' p_infty) ((b-a)^2/12).
Proof.
  intros.
  apply Uniform_full with (f := (fun t => (t-(b+a)/2)^2)); trivial.
  apply (is_RInt_ext (fun t => (/ (b-a)) * (t-(b+a)/2)^2)).
  intros.
  now ring_simplify.
  now apply Uniform_variance0.
Qed.

End Uniform_Distribution.

Section Gaussian_Distribution.
(* standard normal distribution *)

Definition inf_sign (r:Rbar) :=
  match r with
  | p_infty => 1
  | m_infty => -1
  | _ => 0
  end.

Lemma ex_RInt_Standard_Gaussian_PDF (a b:R) :
  ex_RInt Standard_Gaussian_PDF a b.
Proof.
  intros.
  apply (@ex_RInt_continuous).
  intros.
  apply continuous_Standard_Gaussian_PDF.
Qed.

Lemma Derive_exp g z :
  ex_derive g z -> Derive (fun x => exp (g x)) z = Derive g z * exp (g z).
Proof.
  intros.
  rewrite Derive_comp; solve_derive; trivial.
  rewrite <- Derive_Reals with (pr := derivable_pt_exp (g z)).
  now rewrite derive_pt_exp.
Qed.

Lemma Derive_atan g z :
  ex_derive g z -> Derive (fun x => atan (g x)) z = Derive g z / (1 + Rsqr (g z)).
Proof.
  intros.
  rewrite Derive_comp; solve_derive; trivial.
  rewrite <- Derive_Reals with (pr := derivable_pt_atan (g z)).
  rewrite derive_pt_atan.
  unfold Rdiv.
  now ring_simplify.
Qed.

Hint Resolve Rgt_not_eq : Rarith.

(*                     || (rewrite Derive_div; [ | solve[solve_derive]..])*)

Ltac Derive_helper
  := intros; repeat ((rewrite Derive_mult; [ | solve[solve_derive]..])
                     || (rewrite Derive_div; solve_derive)
                     || (rewrite Derive_plus; [ | solve[solve_derive]..])
                     || (rewrite Derive_minus; [ | solve[solve_derive]..])
                     || (rewrite Derive_pow; [ | solve[solve_derive]..])
                     || rewrite Derive_id
                     || rewrite Derive_const
                     || (rewrite Derive_exp; [ | solve[solve_derive]..])
                     || (rewrite Derive_atan; [ | solve[solve_derive]..])
                     || rewrite Derive_opp
                    )
     ; try equation_simplifier; auto 3 with Rarith.

Lemma derive_xover_sqrt2 (x:R):
  Derive (fun x => x/sqrt 2) x = /sqrt 2.
Proof.
  generalize sqrt2_neq0; intros.
  unfold Rdiv.
  Derive_helper.
Qed.

Lemma continuous_erf' :
  forall (x:R), continuous erf' x.
Proof.
  intros.
  unfold erf'.
  apply (@ex_derive_continuous).
  now auto_derive.
Qed.

Lemma std_pdf_from_erf' (x:R):
  Standard_Gaussian_PDF x = / (2*sqrt 2) * (erf' (x / sqrt 2)).
Proof.
  unfold Standard_Gaussian_PDF.
  unfold erf'.
  field_simplify; auto with Rarith.
  rewrite sqrt_mult_alt; trivial; try lra.
  unfold Rdiv.
  apply Rmult_eq_compat_r.
  f_equal; field_simplify; auto with Rarith.
  replace (sqrt 2 ^ 2) with (2); trivial.
  rewrite <- Rsqr_pow2.
  rewrite -> Rsqr_sqrt with (x:=2); trivial; lra.
Qed.

Lemma std_pdf_from_erf (x:R):
  Derive (fun t=> erf (t/sqrt 2)) x = 2 * Standard_Gaussian_PDF x.
Proof.
  unfold erf.
  assert (forall y:R, ex_RInt erf' 0 y).
  intros.
  apply (@ex_RInt_continuous).
  intros.
  apply continuous_erf'.
  rewrite Derive_comp; solve_derive.
  rewrite Derive_RInt.
  rewrite derive_xover_sqrt2.
  rewrite std_pdf_from_erf'.
  equation_simplifier.
  apply locally_open with (D:=fun _ => True); trivial.
  apply open_true.
  apply continuous_erf'.
  unfold ex_derive.
  exists (erf' (x / sqrt 2)).
  apply is_derive_RInt with (a:=0).
  apply locally_open with (D:=fun _ => True); trivial.
  apply open_true.
  intros.
  now apply RInt_correct with (f:=erf') (a:=0) (b:=x0).
  apply continuous_erf'.
Qed.

Hint Resolve Rlt_sqrt2_0 sqrt2_neq0 Rinv_pos : Rarith.

Lemma std_from_erf0 (x:R) :
  RInt Standard_Gaussian_PDF 0 x = / 2 * erf(x/sqrt 2).
Proof.
  unfold erf.
  replace (/ 2 * RInt erf' 0 (x / sqrt 2)) with (scal (/ 2) (RInt erf' 0 (x / sqrt 2))).
  - rewrite <- RInt_scal with (l := /2) (f:=erf') (a := 0) (b := x / sqrt 2).
    + replace (0) with (/ sqrt 2 * 0 + 0) at 2 by lra.
      replace (x / sqrt 2) with (/ sqrt 2 * x + 0) by lra.
      rewrite <- RInt_comp_lin with (v:=0) (u:=/sqrt 2) (a:=0) (b:=x).
      * apply RInt_ext.
        intros.
        rewrite std_pdf_from_erf'.
        replace (/ sqrt 2 * x0 + 0) with  (x0/sqrt 2) by lra.
        rewrite scal_assoc.
        apply Rmult_eq_compat_r.
        rewrite Rinv_mult_distr; auto with Rarith.
        now rewrite Rmult_comm.
      * apply ex_RInt_scal with (f := erf').
        field_simplify; auto with Rarith.
        apply (@ex_RInt_continuous).
        intros.
        apply continuous_erf'.
    + apply (@ex_RInt_continuous).
      intros.
      apply continuous_erf'.
  - reflexivity.
Qed.

(* generates 2 gaussian samples from 2 uniform samples *)
(* with mean 0 and variance 1 *)
Definition Box_Muller (uniform1 uniform2: R) : (R * R) :=
  let r := sqrt (-2 * (ln uniform1)) in
  let theta := 2 * PI * uniform2 in
  (r * cos theta, r * sin theta).

CoFixpoint mkGaussianStream (uniformStream : Stream R) : Stream R :=
  let u1 := hd uniformStream in
  let ust2 := tl uniformStream in
  let u2 := hd ust2 in
  let ust3 := tl ust2 in
  let '(g1,g2) := Box_Muller u1 u2 in
  Cons g1 (Cons g2 (mkGaussianStream ust3)).

Lemma ex_RInt_Standard_Gaussian_mean_PDF (a b:R) :
    ex_RInt (fun t => t * (Standard_Gaussian_PDF t)) a b.
Proof.
  intros.
  apply (@ex_RInt_continuous).
  intros.
  cont_helper.
Qed.

Lemma ex_RInt_Standard_Gaussian_variance_PDF (a b:R) :
    ex_RInt (fun t => t^2 * (Standard_Gaussian_PDF t)) a b.
Proof.
  intros.
  apply (@ex_RInt_continuous).
  intros.
  cont_helper.
Qed.

Lemma variance_exint0 (a b:Rbar) :
  ex_RInt (fun t => (t^2-1)*Standard_Gaussian_PDF t) a b.
Proof.
  intros.
  assert (ex_RInt (fun t => t^2*Standard_Gaussian_PDF t - Standard_Gaussian_PDF t) a b).
  apply ex_RInt_minus with (f := fun t=> t^2*Standard_Gaussian_PDF t)
                           (g := Standard_Gaussian_PDF).
  now apply ex_RInt_Standard_Gaussian_variance_PDF.
  now apply ex_RInt_Standard_Gaussian_PDF.
  apply ex_RInt_ext with (f := (fun t : R => t ^ 2 * Standard_Gaussian_PDF t - Standard_Gaussian_PDF t)) (g := (fun t : R => (t ^ 2 - 1) * Standard_Gaussian_PDF t)).
  intros.
  lra.
  assumption.
Qed.

Lemma variance_int0 (a b:Rbar) :
  RInt (fun t => t^2*Standard_Gaussian_PDF t) a b =
  RInt (fun t => (t^2-1)*Standard_Gaussian_PDF t) a b +
  RInt (fun t => Standard_Gaussian_PDF t) a b.
Proof.
  intros.
  replace (RInt (fun t : R => t ^ 2 * Standard_Gaussian_PDF t) a b) with
      (RInt (fun t : R => (t^2-1)*Standard_Gaussian_PDF t + Standard_Gaussian_PDF t) a b).
  apply RInt_plus with (f := (fun t => (t^2-1)*Standard_Gaussian_PDF t))
                       (g := (fun t => Standard_Gaussian_PDF t)).
  apply variance_exint0; trivial.
  now apply ex_RInt_Standard_Gaussian_PDF.
  apply RInt_ext.
  intros.
  lra.
Qed.

Lemma variance_derive (x:R) :
      Derive (fun t => -t*Standard_Gaussian_PDF t) x = (x^2-1)*Standard_Gaussian_PDF x.
Proof.
  unfold Standard_Gaussian_PDF.
  Derive_helper.
Qed.

Lemma limxexp_inv_inf : is_lim (fun t => exp(t^2/2) / t) p_infty p_infty.
Proof.
  eapply is_lim_le_p_loc; [idtac | apply is_lim_div_exp_p].
  unfold Rbar_locally'.
  exists 3; intros.
  apply Rmult_le_compat_r.
  - left.
    apply Rinv_0_lt_compat; lra.
  - left.
    apply exp_increasing.
    simpl.
    replace x with (x*1) at 1 by lra.
    unfold Rdiv.
    repeat rewrite Rmult_assoc.
    apply Rmult_lt_compat_l; lra.
Qed.

Lemma limxexp_inf : is_lim (fun t => t*exp(-t^2/2)) p_infty 0.
Proof.
  generalize (limxexp_inv_inf); intros HH.
  apply is_lim_inv in HH; try discriminate.
  simpl in HH.
  eapply is_lim_ext_loc; try apply HH.
  intros.
  simpl.
  exists 0.
  intros x xpos.
  replace (- (x * (x * 1)) / 2) with (- ((x * (x * 1)) / 2)) by lra.
  rewrite exp_Ropp; field.
  split; try lra.
  generalize (exp_pos (x * (x * 1) / 2)); lra.
Qed.

Lemma limxexp_minf : is_lim (fun t => t*exp(-t^2/2)) m_infty 0.
Proof.
  generalize limxexp_inf; intros HH.
  generalize (is_lim_comp (fun t => t*exp(-t^2/2)) Ropp m_infty 0 p_infty HH); intros HH2.
  cut_to HH2.
  - apply is_lim_opp in HH2.
    simpl in HH2.
    replace (- 0) with 0 in HH2 by lra.
    eapply is_lim_ext; try eapply HH2.
    intros; simpl; field_simplify.
    do 3 f_equal.
    lra.
  - generalize (is_lim_id m_infty); intros HH3.
    apply is_lim_opp in HH3.
    simpl in HH3.
    apply HH3.
  - simpl.
    exists 0; intros; discriminate.
Qed.

Lemma continuous_derive_gaussian_opp_mean x :
  continuous (Derive (fun t : R => - t * Standard_Gaussian_PDF t)) x.
Proof.
  apply (continuous_ext (fun t => (t^2-1)*Standard_Gaussian_PDF t)).
  intros.
  now rewrite variance_derive.
  cont_helper.
Qed.

Lemma plim_gaussian_opp_mean : is_lim (fun t => - t*(Standard_Gaussian_PDF t)) p_infty 0.
Proof.
  replace (0) with ((- / sqrt (2*PI)) * 0) by lra.
  unfold Standard_Gaussian_PDF.
  apply (is_lim_ext (fun t : R => (- / sqrt (2 * PI)) * (t * exp (- t ^ 2 / 2)))).
  intros.
  equation_simplifier.
  apply is_lim_scal_l with (a:=- / sqrt (2 * PI)) (l := 0).
  apply limxexp_inf.
Qed.

Lemma mlim_gaussian_opp_mean : is_lim (fun t => - t*(Standard_Gaussian_PDF t)) m_infty 0.
Proof.
  replace (0) with ((- / sqrt (2*PI)) * 0) by lra.
  unfold Standard_Gaussian_PDF.
  apply (is_lim_ext (fun t : R => (- / sqrt (2 * PI)) * (t * exp (- t ^ 2 / 2)))).
  intros.
  equation_simplifier.
  apply is_lim_scal_l with (a:=- / sqrt (2 * PI)) (l := 0).
  apply limxexp_minf.
Qed.

Lemma variance_int1_middle :
  is_RInt_gen (fun t => (t^2-1)*Standard_Gaussian_PDF t) (Rbar_locally m_infty) (Rbar_locally p_infty) 0.
Proof.
  apply (is_RInt_gen_ext (Derive (fun t : R => - t * Standard_Gaussian_PDF t))).
  - simpl.
    eapply (Filter_prod _ _ _ (fun _ => True) (fun _ => True))
    ; simpl; eauto.
    intros.
    now rewrite variance_derive.
  - replace 0 with (0 - 0) by lra.
    apply is_RInt_gen_Derive.
    + eapply (Filter_prod _ _ _ (fun _ => True) (fun _ => True))
      ; simpl; eauto.
      intros; simpl.
      unfold Standard_Gaussian_PDF.
      now auto_derive.
    + eapply (Filter_prod _ _ _ (fun _ => True) (fun _ => True))
      ; simpl; eauto.
      intros; simpl.
      apply continuous_derive_gaussian_opp_mean.
    + apply mlim_gaussian_opp_mean.
    + apply plim_gaussian_opp_mean.
  Unshelve.
  exact 0.
  exact 0.
  exact 0.
  exact 0.
  exact 0.
  exact 0.
Qed.

Lemma limexp_inf : is_lim (fun t => exp(t^2/2)) p_infty p_infty.
Proof.
  eapply is_lim_le_p_loc; [idtac | apply is_lim_exp_p].
  unfold Rbar_locally'.
  exists 2; intros.
  left.
  apply exp_increasing.
  simpl.
  replace (x) with (x*1) at 1 by lra.
  replace (x * (x * 1) / 2) with (x * (x / 2)) by lra.
  apply Rmult_lt_compat_l; lra.
Qed.

Lemma limexp_neg_inf : is_lim (fun t => exp(-t^2/2)) p_infty 0.
Proof.
  apply (is_lim_ext (fun t => / exp(t^2/2))).
  intros.
  symmetry.
  replace (- y^2/2) with (- (y^2/2)).
  apply exp_Ropp with (x:=y^2/2).
  lra.
  replace (Finite 0) with (Rbar_inv p_infty).
  apply is_lim_inv.
  apply limexp_inf.
  discriminate.
  now compute.
Qed.

Lemma limexp_neg_minf : is_lim (fun t => exp(-t^2/2)) m_infty 0.
Proof.
  replace (0) with ((-1) * 0 + 0).
  apply (is_lim_ext (fun t => exp(-(-1*t+0)^2/2))).
  intros.
  f_equal; now field_simplify.
  apply is_lim_comp_lin with (a := -1) (b := 0) (f := fun t => exp(-t^2/2)).
  replace (Rbar_plus (Rbar_mult (-1) m_infty) 0) with (p_infty).
  replace (-1 * 0 + 0) with (0) by lra.
  apply limexp_neg_inf.
  rewrite Rbar_plus_0_r.
  symmetry.
  rewrite Rbar_mult_comm.
  apply is_Rbar_mult_unique.
  apply is_Rbar_mult_m_infty_neg.
  compute; lra.
  apply Rlt_not_eq; lra.
  lra.
Qed.

Lemma Derive_opp_Standard_Gaussian_PDF (x:R):
  Derive (fun t => - Standard_Gaussian_PDF t) x = x*Standard_Gaussian_PDF x.
Proof.
  unfold Standard_Gaussian_PDF.
  Derive_helper.
Qed.

Lemma ex_derive_opp_Standard_Gaussian_PDF (x:R):
  ex_derive (fun t => - Standard_Gaussian_PDF t) x.
Proof.
  unfold Standard_Gaussian_PDF.
  now auto_derive.
Qed.

Lemma continuous_Derive_opp_Standard_Gaussian_PDF (x:R):
  continuous (Derive (fun t => - Standard_Gaussian_PDF t)) x.
Proof.
  apply continuous_ext with (f:=fun t => t*Standard_Gaussian_PDF t).
  symmetry.
  apply Derive_opp_Standard_Gaussian_PDF.
  cont_helper.
Qed.

Lemma mean_standard_gaussian :
  is_RInt_gen (fun t => t*Standard_Gaussian_PDF t)
           (Rbar_locally m_infty) (Rbar_locally p_infty) 0.
Proof.
  replace (0) with (0 - 0) by lra.
  apply (is_RInt_gen_ext (Derive (fun t => - Standard_Gaussian_PDF t))).
  apply filter_forall.
  intros; trivial.
  apply Derive_opp_Standard_Gaussian_PDF.
  apply is_RInt_gen_Derive with (f := fun t => - Standard_Gaussian_PDF t) (la := 0) (lb := 0).
  apply filter_forall.
  intros; trivial.
  apply ex_derive_opp_Standard_Gaussian_PDF.
  apply filter_forall.
  intros; trivial.
  apply continuous_Derive_opp_Standard_Gaussian_PDF.
  replace (filterlim (fun t : R => - Standard_Gaussian_PDF t) (Rbar_locally m_infty) (locally 0)) with (is_lim (fun t : R => - Standard_Gaussian_PDF t) m_infty 0).
  unfold Standard_Gaussian_PDF.
  apply (is_lim_ext (fun t : R => (- / sqrt (2 * PI)) *  exp (- t ^ 2 / 2))).
  intros.
  now ring_simplify.
  replace (0) with ((- / sqrt (2*PI)) * 0) by lra.
  apply is_lim_scal_l with (a:=- / sqrt (2 * PI)) (l := 0).
  apply limexp_neg_minf.
  now unfold is_lim.
  replace (filterlim (fun t : R => - Standard_Gaussian_PDF t) (Rbar_locally p_infty) (locally 0)) with (is_lim (fun t : R => - Standard_Gaussian_PDF t) p_infty 0).
  unfold Standard_Gaussian_PDF.
  apply (is_lim_ext (fun t : R => (- / sqrt (2 * PI)) *  exp (- t ^ 2 / 2))).
  intros.
  now ring_simplify.
  replace (0) with ((- / sqrt (2*PI)) * 0) by lra.
  apply is_lim_scal_l with (a:=- / sqrt (2 * PI)) (l := 0).
  apply limexp_neg_inf.
  now unfold is_lim.
Qed.

Lemma gen_from_std (mu sigma : R) :
   sigma > 0 -> forall x:R,  General_Gaussian_PDF mu sigma x =
                             / sigma * Standard_Gaussian_PDF ((x-mu)/sigma).
Proof.
  intros.
  assert (sigma <> 0).
  now apply Rgt_not_eq.
  generalize sqrt_2PI_nzero; intros.

  unfold General_Gaussian_PDF.
  unfold Standard_Gaussian_PDF.
  field_simplify.
  unfold Rdiv.
  apply Rmult_eq_compat_r.
  f_equal; now field_simplify.
  lra.
  lra.
Qed.

Lemma Positive_General_Gaussian_PDF (mu sigma x : R):
  sigma > 0 -> General_Gaussian_PDF mu sigma x > 0.
Proof.
  intros.
  unfold General_Gaussian_PDF.
  apply Rmult_gt_0_compat.
  apply Rinv_0_lt_compat.
  apply Rmult_gt_0_compat; trivial.
  apply sqrt_lt_R0.
  apply Rmult_gt_0_compat.
  apply Rle_lt_0_plus_1; lra.
  apply PI_RGT_0.
  apply exp_pos.
Qed.

Lemma Derive_General_Gaussian_PDF (mu sigma x:R):
  sigma > 0 -> Derive (General_Gaussian_PDF mu sigma) x = / (sigma^2)*(mu-x)*General_Gaussian_PDF mu sigma x.
Proof.
  unfold General_Gaussian_PDF.
  Derive_helper.
  apply Rgt_not_eq.
  apply Rmult_gt_0_compat; try lra.
  simpl.
  apply Rmult_gt_0_compat; try lra.
Qed.

Lemma ex_derive_General_Gaussian_PDF (mu sigma:R) (x:R):
  sigma > 0 -> ex_derive (General_Gaussian_PDF mu sigma) x.
Proof.
  intros.
  unfold General_Gaussian_PDF.
  now auto_derive.
Qed.

Lemma continuous_Derive_General_Gaussian_PDF (mu sigma x:R):
  sigma > 0 -> continuous (Derive (General_Gaussian_PDF mu sigma)) x.
Proof.
  intros.
  apply (continuous_ext (fun x => / (sigma^2)*((mu-x)*General_Gaussian_PDF mu sigma x))).
  intros.
  rewrite Derive_General_Gaussian_PDF.
  now rewrite Rmult_assoc.
  trivial.
  unfold General_Gaussian_PDF.
  apply (@ex_derive_continuous).
  now auto_derive.
Qed.

Axiom Fubini:
  forall (a b c d: R) (f: R -> R -> R) (x y: R),
    a < x < b -> c < y < d ->
    continuity_2d_pt f x y ->
    RInt (fun u => RInt (fun v => f u v) a b) c d =
    RInt (fun v => RInt (fun u => f u v) c d) a b.

(* the iterated integrals below are equal in the sense either they are both infinite, or they are both finite with the same value if you omit the ex_RInt_gen conditions
 as written they are both finite, and can be generalized to non-positive functions by requiring them to be absoluately integrable *)

Axiom Fubini_gen :
  forall (Fa Fb Fc Fd: (R -> Prop) -> Prop)
         (f: R -> R -> R) ,
  filter_prod Fa Fb
    (fun ab => forall (x : R), Rmin (fst ab) (snd ab) <= x <= Rmax (fst ab) (snd ab) ->
           filter_prod Fc Fd
                       (fun bc => forall (y : R),
                            Rmin (fst bc) (snd bc) <= y <= Rmax (fst bc) (snd bc) ->
                            continuity_2d_pt f x y)) ->
  filter_prod Fa Fb
    (fun ab => forall (x : R),
         Rmin (fst ab) (snd ab) <= x <= Rmax (fst ab) (snd ab) ->
         filter_prod Fc Fd
                     (fun bc => forall (y : R),
                          Rmin (fst bc) (snd bc) <= y <= Rmax (fst bc) (snd bc) ->
                          f x y >= 0)) ->
  ex_RInt_gen (fun u => RInt_gen (fun v => f u v) Fa Fb) Fc Fd ->
  ex_RInt_gen (fun v => RInt_gen (fun u => f u v) Fc Fd) Fa Fb ->
  RInt_gen (fun u => RInt_gen (fun v => f u v) Fa Fb) Fc Fd =
    RInt_gen (fun v => RInt_gen (fun u => f u v) Fc Fd) Fa Fb.

Lemma sqr_plus1_gt (x:R):
  x^2 + 1 > 0.
Proof.
  intros.
  apply Rplus_le_lt_0_compat.
  apply pow2_ge_0.
  lra.
Qed.

Lemma sqr_plus1_neq (x:R):
  x^2 + 1 <> 0.
Proof.
  apply Rgt_not_eq.
  apply sqr_plus1_gt.
Qed.

Lemma deriv_erf00 (x0 x2:R) :
  Derive (fun u : R => - / (2 * x0 ^ 2 + 2) * exp (- (u ^ 2 + (u * x0) ^ 2))) x2 =
    x2 * exp (- (x2 ^ 2 + (x2 * x0) ^ 2)).
Proof.
  Derive_helper.

  replace (2*(x0 * x0) + 2) with (2*(x0^2 + 1)) by lra.
  apply Rmult_integral_contrapositive_currified; try lra.
  apply sqr_plus1_neq.
Qed.

Lemma atan_tan_inv (x:R) :
  -PI/2 < x < PI/2 -> atan (tan x) = x.
Proof.
  intros.
  unfold atan.
  destruct (pre_atan (tan x)) as [y [yrang yinv]].
  now apply tan_is_inj in yinv.
Qed.

Lemma lim_atan_inf:
  is_lim atan p_infty (PI/2).
Proof.
  apply is_lim_spec.
  unfold is_lim'.
  intros.
  assert(atan_upper:forall x, atan x < PI/2)
    by apply atan_bound.
  unfold Rbar_locally'.
  destruct (Rlt_dec (PI/2-eps) 0).
  - exists (0).
    intros.
    rewrite Rabs_left.
    + assert (atan 0 < atan x)
             by now apply atan_increasing.
      rewrite atan_0 in H0; try lra.
    + now apply Rlt_minus.
  - exists (tan (PI/2 - eps)).
    assert (eps > 0)
      by now destruct eps.
    intros.
    rewrite Rabs_left; try lra.
    + assert (atan (tan (PI/2 - eps)) < atan x)
       by now apply atan_increasing.
      rewrite atan_tan_inv in H1; try lra.
    + now apply Rlt_minus.
Qed.

Lemma erf_atan:
  is_RInt_gen (fun s : R => / (2 * s ^ 2 + 2)) (at_point 0)
              (Rbar_locally' p_infty) (PI / 4).
Proof.
    + apply (is_RInt_gen_ext (Derive (fun s => /2 * atan s))).
      * apply filter_forall.
        intros.
        replace (/ (2 * x0^2 + 2)) with ( (/2) * (/ (x0^2+1))).
        rewrite Derive_scal.
        apply Rmult_eq_compat_l.
        rewrite <- Derive_Reals with (pr := derivable_pt_atan x0).
        rewrite derive_pt_atan.
        unfold Rsqr.
        replace (1+x0*x0) with (x0^2+1) by lra.
        unfold Rdiv.
        lra.
        field.
        split.
        replace (2*x0^2 + 2) with (2*(x0^2+1)) by lra.
        apply Rmult_integral_contrapositive.
        split.
        lra.
        apply sqr_plus1_neq.
        apply sqr_plus1_neq.
      * replace (PI/4) with (PI/4 - 0) by lra.
        apply is_RInt_gen_Derive.
        - apply filter_forall.
           intros.
           apply ex_derive_scal.
           apply ex_derive_Reals_1.
           apply derivable_pt_atan.
        - apply filter_forall.
           intros.
           apply (continuous_ext (fun s => /2 * /(s^2+1))).
           intros.
           symmetry.
           replace (/ (x1^2+1)) with (Derive atan x1).
           apply Derive_scal with (f := atan ) (k := /2) (x := x1).
           rewrite <- Derive_Reals with (pr := derivable_pt_atan x1).
           rewrite derive_pt_atan.
           unfold Rsqr.
           replace (1 + x1*x1) with (x1^2 + 1) by lra; lra.
           apply (@ex_derive_continuous).
           auto_derive.
           apply sqr_plus1_neq.
        - unfold filterlim, filter_le.
          intros.
          unfold filtermap, at_point.
          replace (/2 * atan 0) with (0).
          now apply locally_singleton.
          rewrite atan_0; lra.
        - replace (filterlim (fun s : R => / 2 * atan s) (Rbar_locally' p_infty) (locally (PI / 4))) with (is_lim (fun s : R => / 2 * atan s) p_infty (Rbar_mult (/2) (PI/2))).
           apply is_lim_scal_l with (a := /2) (f := atan).
           apply lim_atan_inf.
           replace (Rbar_mult (/2) (PI/2)) with (Finite (PI/4)).
           now unfold is_lim.
           f_equal; simpl; f_equal; field.
Qed.

Lemma erf_exp0 (x0:R) :
   is_RInt_gen (fun u : R => u * exp (- (u ^ 2 + (u * x0) ^ 2)))
               (at_point 0) (Rbar_locally' p_infty) (/ (2 * x0 ^ 2 + 2)).
Proof.
      replace (/ (2*x0^2+2)) with (0 - (- / (2*x0^2+2))).
      * apply (is_RInt_gen_ext (Derive (fun u => -/(2*x0^2+2) * exp(-(u^2+(u*x0)^2))))).
        -- apply filter_forall.
           intros.
           apply deriv_erf00.
        -- apply is_RInt_gen_Derive with (f := fun u => -/(2*x0^2+2)* exp(-(u^2+(u*x0)^2))) (lb:=0) (la := - / (2*x0^2+2)).
           ++ apply filter_forall.
              intros.
              solve_derive.
           ++ apply filter_forall.
              intros.
              apply (continuous_ext (fun x2 => x2 * exp (- (x2 ^ 2 + (x2 * x0) ^ 2)))).
              ** intros.
                 symmetry.
                 apply deriv_erf00.
              ** apply (@ex_derive_continuous).
                 solve_derive.
           ++ unfold filterlim, filter_le.
              intros.
              unfold filtermap, at_point.
              replace (- / (2 * x0 ^ 2 + 2) * exp (- (0 ^ 2 + (0 * x0) ^ 2))) with (- / (2 * x0 ^ 2 + 2)).
              now apply locally_singleton.
              replace (- (0 ^ 2 + (0 * x0) ^ 2)) with (0) by lra.
              rewrite exp_0; lra.
           ++ replace (filterlim (fun u : R => - / (2 * x0 ^ 2 + 2) * exp (- (u ^ 2 + (u * x0) ^ 2)))
                                 (Rbar_locally' p_infty) (locally 0)) with
                  (is_lim (fun u : R => - / (2 * x0 ^ 2 + 2) * exp (- (u ^ 2 + (u * x0) ^ 2))) p_infty 0).
              ** replace  (Finite 0) with (Rbar_mult  (- / (2 * x0 ^ 2 + 2)) 0).
                 apply is_lim_scal_l with (a := - / (2 * x0 ^ 2 + 2) ).
                 apply is_lim_comp with (l:=m_infty).
                 apply is_lim_exp_m.
                 replace (m_infty) with (Rbar_opp p_infty).
                 apply is_lim_opp.
                 apply (is_lim_ext (fun y => y * y * (1 + Rsqr x0))).
                 intros.
                 unfold Rsqr.
                 now ring_simplify.
                 replace (p_infty) with (Rbar_mult (Rbar_mult p_infty p_infty) (1 + Rsqr x0)) at 2.
                 apply is_lim_mult.
                 apply is_lim_mult.
                 apply is_lim_id.
                 apply is_lim_id.
                 now compute.
                 apply is_lim_const.
                 compute.
                 replace (x0 * x0) with (x0^2) by lra.
                 rewrite Rplus_comm.
                 apply sqr_plus1_neq.
                 replace (Rbar_mult p_infty p_infty) with (p_infty).
                 apply is_Rbar_mult_unique.
                 apply is_Rbar_mult_p_infty_pos.
                 simpl.
                 rewrite Rsqr_pow2.
                 rewrite Rplus_comm.
                 apply sqr_plus1_gt.
                 now compute.
                 now compute.
                 unfold Rbar_locally'.
                 exists x0.
                 intros.
                 discriminate.
                 simpl; f_equal; ring.
              ** now unfold is_lim.
      * now ring_simplify.
Qed.

Lemma erf_int00 :
  is_RInt_gen (fun s => RInt_gen (fun u => u*exp(-(u^2+(u*s)^2))) (at_point 0)  (Rbar_locally' p_infty)) (at_point 0) (Rbar_locally' p_infty) (PI / 4).
Proof.
    apply (is_RInt_gen_ext (fun s => / (2*s^2+2))).
    apply filter_forall.
    + intros.
      symmetry.
      apply is_RInt_gen_unique.
      apply erf_exp0.
    + apply erf_atan.
Qed.

Lemma erf_ex_RInt0 (x0:R):
  ex_RInt_gen
   (fun v : R =>
    (if  (Rlt_dec v 1) then 1 else v * exp (- v ^ 2)) * exp (- x0 ^ 2))
   (at_point 0) (Rbar_locally' p_infty).
Proof.
  unfold ex_RInt_gen.
  exists (exp(-x0^2) + / (2* exp 1) * exp(-x0^2)).
  apply is_RInt_gen_Chasles with (b:=1) (l1 := exp(-x0^2)) (l2 := / (2 * exp 1) * exp(-x0^2))
                                 (f :=    (fun v : R =>
    (if (Rlt_dec v 1) then 1 else v * exp (- v ^ 2)) * exp (- x0 ^ 2))).
  apply is_RInt_gen_at_point.
  apply (is_RInt_ext (fun v => exp(-x0^2))).
  intros.
  rewrite Rmin_left in H; try lra.
  rewrite Rmax_right in H; try lra.
  destruct (Rlt_dec x 1); lra.
  replace (exp(-x0^2)) with ((1 - 0) * (exp(-x0^2))) at 1.
  apply (@is_RInt_const).
  lra.
  apply (is_RInt_gen_ext (fun v => v* exp(-v^2)*exp(-x0^2))).
  exists (fun a => a = 1) (fun b => b>1000).
  now unfold at_point.
  unfold Rbar_locally'; now exists 1000.
  intros.
  subst.
  unfold fst, snd in H1.
  rewrite Rmin_left in H1; try lra.
  rewrite Rmax_right in H1; try lra.
  destruct (Rlt_dec x1 1); try lra.
  apply (is_RInt_gen_ext (Derive (fun v => -(/2)*exp(-x0^2) * exp(-v^2)))).
  apply filter_forall.
  Derive_helper.

  replace  (/ (2 * exp 1) * exp (- x0 ^ 2)) with (0 - -  (/ (2 * exp 1) * exp (- x0 ^ 2))) by lra.
  apply is_RInt_gen_Derive.
  apply filter_forall.
  intros.
  now auto_derive.
  apply filter_forall.
  intros.
  apply (continuous_ext (fun v => exp(-x0^2)*v*exp(-v^2))).
  Derive_helper.

  apply (@ex_derive_continuous).
  now auto_derive.
  unfold filterlim, filter_le.
  intros.
  unfold filtermap, at_point.
  apply locally_singleton.
  replace (- / 2 * exp (- x0 ^ 2) * exp (- 1 ^ 2)) with (- (/ (2 * exp 1) * exp (- x0 ^ 2))); trivial.
  replace (exp(-1^2)) with (/ exp(1)).
  equation_simplifier.
  apply Rgt_not_eq.
  apply exp_pos.
  replace (-1^2) with (-1) by lra.
  symmetry; apply exp_Ropp.
  replace (filterlim (fun v : R => - / 2 * exp (- x0 ^ 2) * exp (- v ^ 2))
    (Rbar_locally' p_infty) (locally 0)) with (is_lim (fun v : R => - / 2 * exp (- x0 ^ 2) * exp (- v ^ 2)) p_infty 0).
  replace (Finite 0) with (Rbar_mult (-/2 * exp(-x0^2)) 0).
  apply is_lim_scal_l.
  apply (is_lim_ext (fun t => / exp(t^2))).
  intros.
  symmetry; apply exp_Ropp.
  replace (Finite 0) with (Rbar_inv p_infty).
  apply is_lim_inv.
  eapply is_lim_le_p_loc; [idtac | apply is_lim_exp_p].
  unfold Rbar_locally'.
  exists 2; intros.
  left.
  apply exp_increasing.
  replace (x) with (x*1) at 1 by lra.
  replace (x^2) with (x*x) by lra.
  apply Rmult_lt_compat_l; lra.
  discriminate.
  now compute.
  apply Rbar_mult_0_r.
  now unfold is_lim.
Qed.

Lemma erf_ex_RInt1 (x0:R):
  ex_RInt_gen
   (fun v : R => exp(-(x0^2 + v^2)))
   (at_point 0) (Rbar_locally' p_infty).
Proof.
    apply (ex_RInt_gen_ext (fun v => exp(-(x0^2))*exp(-v^2))).
    apply filter_forall.
    intros.
    replace (-(x0^2 + x1^2)) with ((-x0^2) + (-x1^2)) by lra.
    symmetry.
    apply exp_plus.
    apply ex_RInt_gen_bound with (g := fun v => (if (Rlt_dec v 1) then 1 else v*exp(-v^2))*exp(-x0^2)).
    apply at_point_filter.
    apply Rbar_locally'_filter.
    unfold filter_Rlt.
    exists 1.
    exists (fun x => x=0) (fun y => y>1000).
    now unfold at_point.
    unfold Rbar_locally'; now exists 1000.
    intros.
    subst.
    unfold fst, snd.
    lra.
    apply erf_ex_RInt0.
    exists (fun x => x=0) (fun y => y>1000).
    now unfold at_point.
    unfold Rbar_locally'; now exists 1000.
    intros.
    subst.
    unfold fst, snd.
    split.
    intros.
    split.
    rewrite <- exp_plus.
    left.
    apply exp_pos.
    rewrite Rmult_comm.
    apply Rmult_le_compat_r.
    left.
    apply exp_pos.
    destruct (Rlt_dec x 1).
    rewrite exp_Ropp.
    replace (1) with (/ 1) by lra.
    apply Rinv_le_contravar; try lra.
    replace (1) with (exp 0).
    left.
    apply exp_increasing.
    rewrite <- Rsqr_pow2.
    apply Rlt_0_sqr; lra.
    apply exp_0.
    replace (exp(-x^2)) with (1 * exp(-x^2)) at 1 by lra.
    apply  Rmult_le_compat_r.
    left.
    apply exp_pos.
    lra.
    apply (@ex_RInt_continuous).
    intros.
    apply (@ex_derive_continuous).
    now auto_derive.
Qed.

Lemma erf_ex_RInt2:
  ex_RInt_gen
   (fun v : R => exp(-v^2))
   (at_point 0) (Rbar_locally' p_infty).
Proof.
  apply (ex_RInt_gen_ext (fun v => exp(-(0^2 + v^2)))).
  apply filter_forall.
  intros.
  f_equal; lra.
  apply erf_ex_RInt1.
Qed.

Lemma erf_ex_bound1 (u:R) :
  u>=0 -> exp (- (u ^ 2)) <= /(1+u^2).
Proof.
  intros.
  rewrite exp_Ropp.
  destruct H.
  left.
  apply Rinv_1_lt_contravar.
  apply Rplus_le_reg_l with (r:=-1).
  replace (-1 + 1) with (0) by lra.
  replace (-1 + (1 + u^2)) with (u^2) by lra.
  apply pow2_ge_0.
  apply exp_ineq1.
  apply pow2_gt_0; lra.
  subst.
  replace (0^2) with (0) by lra.
  replace (1 + 0) with (1) by lra.
  rewrite exp_0; lra.
Qed.

Lemma erf_ex_bound2 (u v:R) :
  u*v>=0 -> exp (- ((u*v) ^ 2)) <= /(1+(u*v)^2).
Proof.
  intros.
  now apply erf_ex_bound1 with (u:=u*v).
Qed.

Lemma erf_ex_bound3 (u v:R) :
  u>=0 -> v>=0 -> u * exp (- (u ^ 2 + (u * v) ^ 2)) <= u * ( / (1+u^2) * /(1 + (u*v)^2)).
Proof.
  intros.
  apply Rmult_le_compat_l; try lra.
  replace (exp(-(u^2+(u*v)^2))) with (exp(-u^2)*exp(-(u*v)^2)).
  apply Rmult_le_compat.
  left.
  apply exp_pos.
  left.
  apply exp_pos.
  now apply erf_ex_bound1.
  apply erf_ex_bound2.
  replace (0) with (u * 0) by lra.
  now apply Rmult_ge_compat_l.
  rewrite <- exp_plus.
  f_equal; ring.
Qed.

Lemma int_bound3 (u:R) :
  u>0 -> is_RInt_gen (fun v : R =>   u / (1+u^2) * /(1 + (u*v)^2))
                      (at_point 0) (Rbar_locally' p_infty) (PI/(2*(u^2+1))).
Proof.
  intros.
  assert (forall x1 : R_UniformSpace,
      / (1 + (u * x1) ^ 2) = Derive (fun y : R => atan (u * y) / u) x1).
  intros.
  symmetry.
  Derive_helper.
  split.
  replace (u*x1*(u*x1)) with ((u*x1)^2).
  apply Rgt_not_eq.
  apply plus_Rsqr_gt_0.
  now ring_simplify.
  lra.
  replace (PI/(2*(u^2+1))) with ((u/(1+u^2)) * (PI/(2*u))).
  apply (@is_RInt_gen_scal).
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply (is_RInt_gen_ext (Derive (fun y => atan(u*y)/u))).
  exists (fun a => a=0) (fun b => b>1000).
  now unfold at_point.
  unfold Rbar_locally'; now exists 1000.
  intros.
  symmetry.
  apply H0.
  replace (PI / (2*u)) with ((PI/(2*u)) - 0) by lra.
  apply is_RInt_gen_Derive.
  apply filter_forall.
  intros.
  solve_derive.
  apply filter_forall.
  intros.
  apply (continuous_ext (fun y =>  / (1 + (u * y) ^ 2))).
  apply H0.
  apply (@ex_derive_continuous).
  auto_derive.
  replace (u*x0*(u*x0*1)) with ((u*x0)^2).
  apply Rgt_not_eq.
  apply plus_Rsqr_gt_0.
  now ring_simplify.
  unfold filterlim, filter_le.
  intros.
  unfold filtermap, at_point.
  replace (u * 0) with (0) by lra.
  rewrite atan_0.
  replace (0 / u) with (0) by lra.
  now apply locally_singleton.
  replace (filterlim (fun y : R => atan (u * y) / u) (Rbar_locally' p_infty)
                     (locally (PI / (2 * u)))) with
          (is_lim (fun y : R => atan (u * y) / u) p_infty (PI / (2 * u))).
  apply (is_lim_ext (fun y => /u * atan(u*y+0))).
  intros.
  unfold Rdiv.
  replace (u*y+0) with (u*y) by lra.
  now rewrite Rmult_comm.
  replace (Finite (PI / (2*u))) with (Rbar_mult (/u) (PI/2)).
  apply (@is_lim_scal_l).
  apply is_lim_comp_lin .
  rewrite Rbar_plus_0_r.
  rewrite Rbar_mult_comm.
  rewrite Rbar_mult_p_infty_pos.
  apply lim_atan_inf.
  lra.
  now apply Rgt_not_eq.
  simpl.
  f_equal; equation_simplifier.
  now unfold is_lim.
  equation_simplifier.
  split.
  replace (u * u) with (u^2) by lra.
  now apply sqr_plus1_neq.
  now apply Rgt_not_eq.
Qed.

Lemma erf_ex_RInt33 (u:R) :
  u >= 0 -> ex_RInt_gen (fun v : R => u * exp (- (u ^ 2 + (u * v) ^ 2))) (at_point 0)
                       (Rbar_locally' p_infty).
Proof.
  intros.
  destruct H.
  apply ex_RInt_gen_bound with (g:=(fun v : R =>   u / (1+u^2) * /(1 + (u*v)^2))).
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply filter_Rlt_at_point_p_infty.
  unfold ex_RInt_gen.
  exists (PI/(2*(u^2+1))).
  now apply int_bound3.
  exists (fun a => a=0) (fun b => b>1000).
  now unfold at_point.
  unfold Rbar_locally'; now exists 1000.
  intros.
  split.
  intros.
  split.
  replace (0) with (u * 0) by lra.
  apply Rmult_le_compat_l; try lra.
  left; apply exp_pos.
  unfold Rdiv.
  rewrite Rmult_assoc.
  apply erf_ex_bound3; try lra.
  unfold fst in H2; lra.
  unfold fst, snd.
  apply (@ex_RInt_continuous).
  intros.
  apply (@ex_derive_continuous).
  solve_derive.
  subst.
  apply (ex_RInt_gen_ext (Derive (fun v => 0))).
  apply filter_forall.
  intros.
  rewrite Derive_const; lra.
  unfold ex_RInt_gen.
  exists (0 - 0).
  apply is_RInt_gen_Derive.
  apply filter_forall.
  intros.
  solve_derive.
  apply filter_forall.
  intros.
  apply (continuous_ext (fun _ => 0)).
  intros.
  now rewrite Derive_const.
  apply (@ex_derive_continuous).
  solve_derive.
  unfold filterlim, filter_le.
  intros.
  unfold filtermap, at_point.
  now apply locally_singleton.
  unfold filterlim, filter_le.
  intros.
  unfold filtermap, Rbar_locally'.
  exists 0.
  intros.
  now apply locally_singleton.
Qed.

Lemma erf_decr_RInt30 (b:R):
   b >= 0 ->
   interval_decreasing (fun u : R => RInt_gen (fun v : R => exp (- (u ^ 2 + v ^ 2))) (at_point 0) (Rbar_locally' p_infty)) 0 b.
Proof.
  intros.
  unfold interval_decreasing.
  intros.
  apply is_RInt_gen_Rle with (fl := RInt_gen (fun v : R => exp (- (y ^ 2 + v ^ 2)))
                                             (at_point 0) (Rbar_locally' p_infty))
                             (gl := RInt_gen (fun v : R => exp (- (x ^ 2 + v ^ 2)))
                                             (at_point 0) (Rbar_locally' p_infty))
                             (f := (fun v : R => exp (- (y ^ 2 + v ^ 2))))
                             (g := (fun v : R => exp (- (x ^ 2 + v ^ 2))))
                             (F:= at_point 0) (G:= Rbar_locally' p_infty).
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply filter_Rlt_at_point_p_infty.
  apply (@RInt_gen_correct).
  apply Proper_StrongProper, at_point_filter.
  apply Proper_StrongProper, Rbar_locally'_filter.
  apply erf_ex_RInt1.
  apply (@RInt_gen_correct).
  apply Proper_StrongProper, at_point_filter.
  apply Proper_StrongProper, Rbar_locally'_filter.
  apply erf_ex_RInt1.
  exists (fun a => a=0) (fun b => b>1000).
  now unfold at_point.
  unfold Rbar_locally'; now exists 1000.
  intros.
  destruct H2.
  left.
  apply exp_increasing.
  apply Ropp_gt_lt_contravar.
  apply Rplus_lt_compat_r.
  rewrite <- Rsqr_pow2; rewrite <- Rsqr_pow2.
  apply Rsqr_incrst_1; lra; lra.
  subst; lra.
Qed.

Lemma erf_ex_RInt3_bound (u:R) :
  u > 0 ->
  ex_RInt_gen (fun v : R => u * exp (- (u ^ 2 + (u * v) ^ 2)))
              (at_point 0) (Rbar_locally' p_infty)  ->
  RInt_gen (fun v : R => u * exp (- (u ^ 2 + (u * v) ^ 2)))
           (at_point 0) (Rbar_locally' p_infty) <= (PI/(2*(u^2+1))).
Proof.
  intros.
  apply is_RInt_gen_Rle with (fl := RInt_gen (fun v : R => u * exp (- (u ^ 2 + (u * v) ^ 2)))
                                             (at_point 0) (Rbar_locally' p_infty))
                             (gl := (PI/(2*(u^2+1))))
                             (f := (fun v : R => u * exp (- (u ^ 2 + (u * v) ^ 2))))
                             (g := (fun v =>   u / (1+u^2) * /(1 + (u*v)^2)) )
                             (F:= at_point 0) (G:= Rbar_locally' p_infty).
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply filter_Rlt_at_point_p_infty.
  now apply int_bound3.
  apply (@RInt_gen_correct).
  apply Proper_StrongProper, at_point_filter.
  apply Proper_StrongProper, Rbar_locally'_filter.
  trivial.
  exists (fun a => a = 0) (fun b => b>1000).
  now unfold at_point.
  unfold Rbar_locally'; now exists 1000.
  intros.
  subst.
  unfold Rdiv.
  rewrite Rmult_assoc.
  apply erf_ex_bound3; try lra.
  unfold fst in H3; try lra.
Qed.

Lemma erf_ex_RInt3_bound_int :
  is_RInt_gen (fun u : R => PI/(2*(u^2+1))) (at_point 0) (Rbar_locally' p_infty) (PI^2/4).
Proof.
  apply (is_RInt_gen_ext (Derive (fun u => (PI/2) * atan u))).
  apply filter_forall.
  intros.
  rewrite Derive_scal.
  rewrite <- Derive_Reals with (pr := derivable_pt_atan (x0)).
  rewrite derive_pt_atan.
  unfold Rsqr.
  field_simplify; trivial.
  apply Rgt_not_eq.
  now apply sqr_plus1_gt.
  replace (1 + x0*x0) with (x0^2+1).
  now apply sqr_plus1_neq.
  ring.
  replace (PI^2/4) with (PI^2/4 - 0).
  apply is_RInt_gen_Derive.
  apply filter_forall.
  intros.
  solve_derive.
  apply filter_forall.
  intros.
  apply (continuous_ext (fun u => (PI/(2*(u^2+1))))).
  intros.
  rewrite Derive_scal.
  rewrite <- Derive_Reals with (pr := derivable_pt_atan (x1)).
  rewrite derive_pt_atan.
  rewrite Rsqr_pow2.
  field_simplify; trivial.
  rewrite Rplus_comm.
  now apply sqr_plus1_neq.
  now apply sqr_plus1_neq.
  apply (@ex_derive_continuous).
  auto_derive.
  apply Rgt_not_eq.
  ring_simplify.
  replace (2 * x0 ^ 2 + 2) with (2*(x0^2+1)) by lra.
  apply Rmult_gt_0_compat; try lra.
  now apply sqr_plus1_gt.
  unfold filterlim, filter_le.
  intros.
  unfold filtermap, at_point.
  replace (PI/2 * atan 0) with (0).
  now apply locally_singleton.
  rewrite atan_0; lra.
  replace (filterlim (fun u : R => PI / 2 * atan u) (Rbar_locally' p_infty) (locally (PI ^ 2 / 4))
) with (is_lim (fun u : R => PI / 2 * atan u) p_infty (PI^2/4)).
  replace (Finite (PI^2/4)) with (Rbar_mult (PI/2) (PI/2)).
  apply is_lim_scal_l.
  apply lim_atan_inf.
  simpl.
  f_equal; now field_simplify.
  now unfold is_lim.
  now field_simplify.
Qed.

Lemma erf_ex_RInt3 :
  ex_RInt_gen
    (fun u : R =>
     RInt_gen (fun v : R => u * exp (- (u ^ 2 + (u * v) ^ 2)))
              (at_point 0) (Rbar_locally' p_infty)) (at_point 0) (Rbar_locally' p_infty).
Proof.
  apply ex_RInt_gen_bound with (g:=(fun u : R => (PI/(2*(u^2+1))))).
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply filter_Rlt_at_point_p_infty.
  unfold ex_RInt_gen.
  exists (PI^2/4).
  apply erf_ex_RInt3_bound_int.
  exists (fun a => a=0) (fun b => b>1000).
  now unfold at_point.
  unfold Rbar_locally'; now exists 1000.
  intros.
  split.
  intros.
  split.
  apply RInt_gen_Rle0.
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply filter_Rlt_at_point_p_infty.
  apply erf_ex_RInt33.
  unfold fst in H1; try lra.
  exists (fun a => a=0) (fun b => b>1000).
  now unfold at_point.
  unfold Rbar_locally'; now exists 1000.
  intros.
  unfold fst in H1.
  left.
  apply Rmult_lt_0_compat; try lra.
  apply exp_pos.
  apply erf_ex_RInt3_bound.
  unfold fst in H1; try lra.
  apply erf_ex_RInt33.
  unfold fst in H1; try lra.
  unfold fst, snd.
  subst.
  apply (ex_RInt_ext (fun u : R =>
     RInt_gen (fun w : R => exp (- (u ^ 2 + w ^ 2))) (at_point 0)
       (Rbar_locally' p_infty))).
  intros.
  rewrite Rmin_left in H; try lra.
  rewrite Rmax_right in H; try lra.
  symmetry.
  apply is_RInt_gen_unique.
  apply is_RInt_gen_comp_lin_point_0 with (u := x) (f := fun v => exp(-(x^2+v^2))).
  lra.
  intros.
  apply (@ex_RInt_continuous).
  intros.
  apply (@ex_derive_continuous).
  solve_derive.
  replace (x*0) with (0) by lra.
  replace (Rbar_mult x p_infty) with (p_infty).
  apply (@RInt_gen_correct).
  apply Proper_StrongProper, at_point_filter.
  apply Proper_StrongProper, Rbar_locally'_filter.
  apply erf_ex_RInt1.
  symmetry.
  apply Rbar_mult_p_infty_pos; try lra.
  apply ex_RInt_Reals_1.
  apply RiemannInt_decreasing; try lra.
  apply erf_decr_RInt30; try lra.
Qed.

Lemma erf_int1  :
  is_RInt_gen (fun u => RInt_gen (fun v => exp(-(u^2+v^2))) (at_point 0)  (Rbar_locally' p_infty)) (at_point 0) (Rbar_locally' p_infty) (PI / 4).
Proof.
  apply (is_RInt_gen_ext (fun u => RInt_gen (fun v => u*exp(-(u^2+(u*v)^2))) (at_point 0) (Rbar_locally' p_infty))).
  - exists (fun x => x=0) (fun y => y>1000).
    + now unfold at_point.
    + unfold Rbar_locally'; now exists 1000.
    + intros.
      unfold fst, snd in H1.
      subst.
      rewrite Rmin_left in H1; try lra.
      rewrite Rmax_right in H1; try lra.
      apply is_RInt_gen_unique.
      apply is_RInt_gen_comp_lin_point_0 with (f := fun v => exp(-(x0^2 + v^2))); try lra.
      * intros.
        apply (@ex_RInt_continuous).
        intros.
        apply (@ex_derive_continuous).
        now auto_derive.
      * replace (x0*0) with (0) by lra.
        replace (Rbar_mult x0 p_infty) with (p_infty).
        ++ apply (@RInt_gen_correct).
           apply Proper_StrongProper, at_point_filter.
           apply Proper_StrongProper, Rbar_locally'_filter.
           apply erf_ex_RInt1.
        ++ rewrite Rbar_mult_comm.
           now rewrite Rbar_mult_p_infty_pos.
  - replace (PI/4) with
        (RInt_gen
           (fun u : R =>
              RInt_gen (fun v : R => u * exp (- (u ^ 2 + (u * v) ^ 2)))
                       (at_point 0) (Rbar_locally' p_infty)) (at_point 0) (Rbar_locally' p_infty)).
    + apply RInt_gen_correct.
      apply erf_ex_RInt3.
    + rewrite -> Fubini_gen with (Fa := at_point 0) (Fc := at_point 0) (Fb := Rbar_locally' p_infty) (Fd := Rbar_locally' p_infty) (f := fun u => fun v => u* exp (- (u^2 + (u*v) ^ 2))).
      * apply is_RInt_gen_unique.
        apply erf_int00.
      * apply filter_forall.
        intros.
        apply filter_forall.
        intros.
        apply continuity_2d_pt_mult.
        apply continuity_2d_pt_id1.
        apply continuity_1d_2d_pt_comp.
        apply derivable_continuous_pt.
        apply derivable_pt_exp.
        repeat first [
               apply continuity_2d_pt_opp
             | apply continuity_2d_pt_plus
             | apply continuity_2d_pt_mult
             | apply continuity_2d_pt_id1
             | apply continuity_2d_pt_id2
             | apply continuity_2d_pt_const
             ].
      * eapply Filter_prod with (Q:=(eq 0)) (R:=fun x => x > 1000).
        -- now red.
        -- simpl;
             now exists 1000.
        -- intros.
           simpl in *.
           subst.
           eapply Filter_prod with (Q:=(eq 0)) (R:=fun x => x > 1000).
           now red.
           simpl;
             now exists 1000.
           intros.
           simpl in *.
           subst.
           rewrite Rmin_left in H1; try lra.
           apply Rle_ge.
           apply Rmult_le_pos; try lra.
           left.
           apply exp_pos.
      * apply erf_ex_RInt3.
      * unfold ex_RInt_gen.
        exists (PI/4).
        apply erf_int00.
Qed.

Lemma erf_int_sq :
  Rsqr (RInt_gen (fun u => exp(-(u^2))) (at_point 0) (Rbar_locally' p_infty)) = PI/4.
Proof.
  unfold Rsqr.
  rewrite <- RInt_gen_scal.
  rewrite <- (RInt_gen_ext (fun u => RInt_gen (fun v => exp(-(u^2+v^2))) (at_point 0)  (Rbar_locally' p_infty)) ).
  apply is_RInt_gen_unique.
  apply erf_int1.
  apply filter_forall.
  intros.
  rewrite Rmult_comm.
  rewrite <- RInt_gen_scal.
  symmetry.
  rewrite <- (RInt_gen_ext (fun y => exp (-(x0^2 + y^2)))).
  reflexivity.
  apply filter_forall.
  intros.
  replace (-(x0^2 + x2^2)) with ((-x0^2)+(-x2^2)).
  apply exp_plus.
  lra.
  apply erf_ex_RInt1.
  apply erf_ex_RInt2.
  unfold ex_RInt_gen.
  exists (PI/4).
  apply erf_int1.
  apply erf_ex_RInt2.
Qed.

Lemma erf_int2:
  RInt_gen (fun u => exp(-(u^2))) (at_point 0) (Rbar_locally' p_infty) = (sqrt PI/2).
Proof.
  replace (sqrt PI/2) with (Rabs (sqrt PI/2)).
  replace ( RInt_gen (fun u => exp(-(u^2))) (at_point 0) (Rbar_locally' p_infty)) with
      (Rabs ( RInt_gen (fun u => exp(-(u^2))) (at_point 0) (Rbar_locally' p_infty))).
  apply Rsqr_eq_abs_0.
  replace (Rsqr (sqrt PI/2)) with (PI/4).
  apply erf_int_sq.
  rewrite Rsqr_div.
  rewrite Rsqr_sqrt.
  unfold Rsqr; lra.
  left; apply PI_RGT_0.
  lra.
  rewrite Rabs_pos_eq; trivial.
  apply RInt_gen_Rle0.
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply filter_Rlt_at_point_p_infty.
  apply erf_ex_RInt2.
  apply filter_forall.
  intros.
  left; apply exp_pos.
  rewrite Rabs_pos_eq; trivial.
  apply Rle_div_r; try lra.
  replace (0 * 2) with (0) by lra.
  left; apply sqrt_lt_R0.
  apply PI_RGT_0.
Qed.

Lemma erf_int21:
  is_RInt_gen (fun u => exp(-(u^2))) (at_point 0) (Rbar_locally' p_infty) (sqrt PI/2).
Proof.
  replace (sqrt PI/2) with (RInt_gen (fun u => exp(-(u^2))) (at_point 0) (Rbar_locally' p_infty)).
  apply (@RInt_gen_correct).
  apply Proper_StrongProper, at_point_filter.
  apply Proper_StrongProper, Rbar_locally'_filter.
  apply erf_ex_RInt2.
  apply erf_int2.
Qed.

Lemma erf_int31:
  is_RInt_gen (fun u => exp(-(u^2))) (at_point 0) (Rbar_locally' m_infty) ( -sqrt PI/2).
Proof.
  apply (is_RInt_gen_ext (fun y => (-1) * ((-1) * (exp (-(-1*y)^2))))).
  apply filter_forall.
  intros.
  replace ((-1*x0)^2) with (x0^2).
  now ring_simplify.
  now ring_simplify.
  replace (-sqrt PI/2) with (scal  (-1) (sqrt PI/2)).
  apply (@is_RInt_gen_scal).
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply is_RInt_gen_comp_lin_point_0 with (f := fun y => exp(-y^2)).
  lra.
  intros.
  apply (@ex_RInt_continuous).
  intros.
  apply (@ex_derive_continuous).
  now auto_derive.
  replace (-1*0) with (0) by lra.
  replace (Rbar_mult (-1) m_infty) with (p_infty).
  apply erf_int21.
  symmetry.
  rewrite Rbar_mult_comm.
  apply Rbar_mult_m_infty_neg; lra.
  replace (- sqrt PI/2) with ((-1)*(sqrt PI/2)).
  reflexivity.
  lra.
Qed.

Lemma erf_int3 (r:Rbar):
  r = p_infty \/ r= m_infty ->
  is_RInt_gen (fun u => exp(-(u^2))) (at_point 0) (Rbar_locally' r) ( inf_sign r * sqrt PI/2).
Proof.
  intros.
  destruct H.
  subst.
  unfold inf_sign.
  replace (1 * sqrt PI /2) with (sqrt PI / 2) by lra.
  apply erf_int21.
  subst.
  unfold inf_sign.
  replace (-1 * sqrt PI /2) with (-sqrt PI / 2) by lra.
  apply erf_int31.
Qed.

Lemma erf_p_infty :
  is_RInt_gen erf' (at_point 0) (Rbar_locally' p_infty) 1.
Proof.
  unfold erf'.
  replace (1) with ((2 / sqrt PI)*(sqrt PI/2)).
  apply (@is_RInt_gen_scal).
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply erf_int21.
  equation_simplifier.
Qed.

Lemma erf_m_infty :
  is_RInt_gen erf' (at_point 0) (Rbar_locally' m_infty) (-1).
Proof.
  unfold erf'.
  replace (-1) with ((2 / sqrt PI)*(-sqrt PI/2)).
  apply (@is_RInt_gen_scal).
  apply at_point_filter.
  apply Rbar_locally'_filter.
  apply erf_int31.
  equation_simplifier.
Qed.

Lemma Standard_Gaussian_PDF_int1_pm (r : Rbar):
  r = p_infty \/ r = m_infty -> is_RInt_gen Standard_Gaussian_PDF (at_point 0) (Rbar_locally' r)  (inf_sign r * /2).
Proof.
  intros.
  unfold Standard_Gaussian_PDF.
  apply (is_RInt_gen_ext (fun y => /(sqrt 2) * ( (/sqrt PI) * exp (- ((/sqrt 2)*y)^2)))).
  - apply filter_forall.
    intros.
    replace (/sqrt(2*PI)) with (/sqrt(2)*/sqrt(PI)).
    + replace (-(/ sqrt 2 * x0) ^ 2) with (-x0^2/2).
      equation_simplifier.
      rewrite Rpow_mult_distr.
      replace ((/ sqrt 2)^2) with (/2).
      now field_simplify.
      rewrite <- Rsqr_pow2.
      rewrite Rsqr_inv.
      rewrite Rsqr_sqrt; try easy.
      lra.
      auto with Rarith.
    + rewrite sqrt_mult_alt; try lra.
      symmetry; apply Rinv_mult_distr.
      auto with Rarith.
      auto with Rarith.
  - apply is_RInt_gen_comp_lin_point_0 with (f := fun y => (/ sqrt PI) * exp (- y^2)).
    + apply Rinv_neq_0_compat.
      apply sqrt2_neq0.
    + intros.
      apply (@ex_RInt_continuous).
      intros.
      apply (@ex_derive_continuous).
      now auto_derive.
    + replace (/sqrt 2 * 0) with (0) by lra.
      * replace (Rbar_mult (/ sqrt 2) r) with (r).
        -- replace (inf_sign r * /2) with (/ sqrt PI * (inf_sign r * sqrt PI/2)).
           ++ apply (@is_RInt_gen_scal).
              apply at_point_filter.
              apply Rbar_locally'_filter.
              now apply erf_int3.
           ++ equation_simplifier.
        -- symmetry.
           rewrite Rbar_mult_comm.
           assert (0 < / sqrt 2).
           apply Rgt_lt.
           apply Rinv_0_lt_compat.
           apply sqrt_lt_R0; lra.
           destruct H.
           subst.
           now apply Rbar_mult_p_infty_pos.
           subst.
           now apply Rbar_mult_m_infty_pos.
Qed.

Lemma std_CDF_from_erf0 :
  forall x:R, is_RInt_gen Standard_Gaussian_PDF (Rbar_locally' m_infty) (at_point x)  ((/ 2) + (/2)*erf (x/sqrt 2)).
Proof.
  intros.
  apply (@is_RInt_gen_Chasles) with (b := 0) (l1 := /2) (l2 := /2 * erf (x / sqrt 2)).
  apply Rbar_locally'_filter.
  apply at_point_filter.
  replace (/2) with (opp (- /2)).
  apply (@is_RInt_gen_swap).
  apply Rbar_locally'_filter.
  apply at_point_filter.
  replace (- / 2) with (inf_sign m_infty * /2).
  apply Standard_Gaussian_PDF_int1_pm.
  now right.
  compute; lra.
  compute; lra.
  rewrite is_RInt_gen_at_point.
  replace (/ 2 * erf (x / sqrt 2)) with (RInt Standard_Gaussian_PDF 0 x).
  apply (@RInt_correct).
  apply ex_RInt_Standard_Gaussian_PDF.
  apply std_from_erf0.
Qed.

Lemma std_CDF_from_erf :
  forall x:R, Standard_Gaussian_CDF x =  (/ 2) + (/2)*erf (x/sqrt 2).
Proof.
  intros.
  unfold Standard_Gaussian_CDF.
  apply is_RInt_gen_unique.
  apply std_CDF_from_erf0.
Qed.

Lemma Standard_Gaussian_PDF_normed :
  is_RInt_gen Standard_Gaussian_PDF (Rbar_locally m_infty) (Rbar_locally p_infty) 1.
Proof.
  replace (1) with (plus (/ 2) (/ 2)).
  apply (@is_RInt_gen_Chasles) with (b := 0) (l1 := /2) (l2 := /2).
  apply Rbar_locally_filter.
  apply Rbar_locally_filter.
  replace (/ 2) with (opp (opp (/2))).
  apply (@is_RInt_gen_swap) with (l := (opp (/2))).
  apply Rbar_locally_filter.
  apply at_point_filter.
  replace (opp (/ 2)) with (inf_sign m_infty * /2).
  apply Standard_Gaussian_PDF_int1_pm.
  now right.
  compute; lra.
  apply opp_opp.
  replace (/ 2) with (inf_sign p_infty * /2).
  apply Standard_Gaussian_PDF_int1_pm.
  now left.
  compute; lra.
  compute; lra.
Qed.

Lemma variance_standard_gaussian0 :
  is_RInt_gen (fun t => (t^2-1)*Standard_Gaussian_PDF t + Standard_Gaussian_PDF t) (Rbar_locally m_infty) (Rbar_locally p_infty) 1.
Proof.
  intros.
  replace (1) with (0 + 1) at 1 by lra.
  apply is_RInt_gen_plus with
      (f:=(fun t => (t^2-1)*Standard_Gaussian_PDF t)) (lf :=0)
      (g:=(fun t => Standard_Gaussian_PDF t)) (lg := 1).
  apply variance_int1_middle.
  apply Standard_Gaussian_PDF_normed.
Qed.

Lemma variance_standard_gaussian :
  is_RInt_gen (fun t => t^2*Standard_Gaussian_PDF t)
           (Rbar_locally m_infty) (Rbar_locally p_infty) 1.
Proof.
  eapply is_RInt_gen_ext; try eapply variance_standard_gaussian0.
  eapply (Filter_prod _ _ _ (fun _ => True) (fun _ => True))
  ; simpl; eauto.
  intros; simpl.
  unfold Standard_Gaussian_PDF; lra.
  Unshelve.
  exact 0.
  exact 0.
Qed.

Lemma variance_general_gaussian (mu sigma : R) :
  sigma > 0 ->
  ex_RInt_gen (fun t : R => sigma ^ 2 * t ^ 2 * Standard_Gaussian_PDF t)
              (Rbar_locally' m_infty) (at_point (- mu / sigma)) ->
  ex_RInt_gen (fun t : R => sigma ^ 2 * t ^ 2 * Standard_Gaussian_PDF t)
              (at_point (- mu / sigma)) (Rbar_locally' p_infty) ->
  is_RInt_gen (fun t => (t-mu)^2*General_Gaussian_PDF mu sigma t)
              (Rbar_locally' m_infty) (Rbar_locally' p_infty) (sigma^2).
Proof.
  intros.
  assert (sigma <> 0).
  now apply Rgt_not_eq.
  assert (Rbar_plus (Rbar_mult (/ sigma) m_infty) (- mu / sigma) = m_infty).
  rewrite Rbar_mult_comm.
  rewrite Rbar_mult_m_infty_pos.
  now compute.
  apply Rinv_0_lt_compat; lra.
  assert (Rbar_plus (Rbar_mult (/ sigma) p_infty) (- mu / sigma) = p_infty).
  rewrite Rbar_mult_comm.
  rewrite Rbar_mult_p_infty_pos.
  now compute.
  apply Rinv_0_lt_compat; lra.
  apply (is_RInt_gen_ext (fun t => /sigma * (sigma^2 * (/sigma * t + (-mu/sigma))^2 * Standard_Gaussian_PDF(/sigma*t + (-mu/sigma))))).
  apply filter_forall.
  intros.
  rewrite gen_from_std.
  replace (/sigma * x0 + - mu/sigma) with ((x0-mu)/sigma) by lra.
  equation_simplifier.
  lra.
  apply is_RInt_gen_comp_lin with (u := /sigma) (v := -mu/sigma)
                                  (f:=fun t => sigma^2 * t^2 * Standard_Gaussian_PDF t).
  now apply Rinv_neq_0_compat.
  replace (Rbar_plus (Rbar_mult (/ sigma) m_infty) (- mu / sigma)) with
          (m_infty).
  replace (/ sigma * 0 + - mu / sigma) with (-mu/sigma) by lra.
  assumption.
  replace (/ sigma * 0 + - mu / sigma) with (-mu/sigma) by lra.
  replace (Rbar_plus (Rbar_mult (/ sigma) p_infty) (- mu / sigma)) with
          (p_infty).
  assumption.
  intros.
  apply (ex_RInt_ext (fun t => sigma^2 * (t^2 * Standard_Gaussian_PDF t))).
  intros.
  lra.
  apply (@ex_RInt_scal) with (k := sigma^2) (f := fun t => t^2 * Standard_Gaussian_PDF t).
  apply ex_RInt_Standard_Gaussian_variance_PDF.
  replace (sigma^2) with (sigma^2 * 1) at 1 by lra.
  apply (is_RInt_gen_ext (fun t=>sigma^2 * (t^2 * Standard_Gaussian_PDF t))).
  apply filter_forall.
  intros.
  lra.
  apply (@is_RInt_gen_scal).
  apply Rbar_locally'_filter.
  apply Rbar_locally'_filter.
  replace (Rbar_plus (Rbar_mult (/ sigma) m_infty) (- mu / sigma)) with
      (m_infty).
  replace (Rbar_plus (Rbar_mult (/ sigma) p_infty) (- mu / sigma)) with
      (p_infty).
  apply variance_standard_gaussian.
Qed.

Lemma General_Gaussian_PDF_normed (mu sigma:R) :
  sigma>0 ->
  ex_RInt_gen Standard_Gaussian_PDF (Rbar_locally' m_infty) (at_point (- mu / sigma)) ->
  ex_RInt_gen Standard_Gaussian_PDF (at_point (- mu / sigma)) (Rbar_locally' p_infty) ->
  is_RInt_gen (General_Gaussian_PDF mu sigma) (Rbar_locally' m_infty) (Rbar_locally' p_infty) 1.
Proof.
  intros.
  assert (Rbar_plus (Rbar_mult (/ sigma) m_infty) (- mu / sigma) = m_infty).
  rewrite Rbar_mult_comm.
  rewrite Rbar_mult_m_infty_pos.
  now compute.
  apply Rinv_0_lt_compat; lra.
  assert (Rbar_plus (Rbar_mult (/ sigma) p_infty) (- mu / sigma) = p_infty).
  rewrite Rbar_mult_comm.
  rewrite Rbar_mult_p_infty_pos.
  now compute.
  apply Rinv_0_lt_compat; lra.

  apply (is_RInt_gen_ext (fun x =>  / sigma * Standard_Gaussian_PDF (/sigma *x + (-mu/sigma)))).
  apply filter_forall.
  intros.
  rewrite gen_from_std.
  apply Rmult_eq_compat_l.
  f_equal; lra.
  trivial.
  apply is_RInt_gen_comp_lin with (u := /sigma) (v := -mu/sigma)
                                  (f:=Standard_Gaussian_PDF).
  apply Rinv_neq_0_compat.
  now apply Rgt_not_eq.
  replace (Rbar_plus (Rbar_mult (/ sigma) m_infty) (- mu / sigma)) with
          (m_infty).
  replace (/ sigma * 0 + - mu / sigma) with (-mu/sigma) by lra.
  trivial.
  replace (/ sigma * 0 + - mu / sigma) with (-mu/sigma) by lra.
  replace (Rbar_plus (Rbar_mult (/ sigma) p_infty) (- mu / sigma)) with
          (p_infty).
  trivial.
  apply ex_RInt_Standard_Gaussian_PDF.
  replace (Rbar_plus (Rbar_mult (/ sigma) m_infty) (- mu / sigma)) with
          (m_infty).
  replace (Rbar_plus (Rbar_mult (/ sigma) p_infty) (- mu / sigma)) with
          (p_infty).
  apply Standard_Gaussian_PDF_normed.
Qed.

Lemma mean_general_gaussian (mu sigma:R) :
  sigma > 0 ->
    ex_RInt_gen Standard_Gaussian_PDF (Rbar_locally' m_infty) (at_point (- mu / sigma)) ->
    ex_RInt_gen Standard_Gaussian_PDF (at_point (- mu / sigma)) (Rbar_locally' p_infty) ->

    is_RInt_gen (fun t => t*General_Gaussian_PDF mu sigma t)
                           (Rbar_locally' m_infty) (Rbar_locally' p_infty) mu.
Proof.
  intros.
  assert (sigma <> 0).
  now apply Rgt_not_eq.
  apply (is_RInt_gen_ext (fun t => mu*General_Gaussian_PDF mu sigma t - (mu-t)*General_Gaussian_PDF mu sigma t)).
  apply filter_forall.
  intros.
  apply Rminus_diag_uniq; lra.
  replace (mu) with (mu*1 - 0) at 1 by lra.
  apply (@is_RInt_gen_minus).
  apply Rbar_locally'_filter.
  apply Rbar_locally'_filter.
  apply (@is_RInt_gen_scal).
  apply Rbar_locally'_filter.
  apply Rbar_locally'_filter.
  now apply General_Gaussian_PDF_normed.
  apply (is_RInt_gen_ext (fun t => sigma^2 * Derive (General_Gaussian_PDF mu sigma) t)).
  apply filter_forall.
  intros.
  rewrite Derive_General_Gaussian_PDF.
  equation_simplifier.
  assumption.
  replace (0) with (sigma^2 * 0) by lra.
  apply (@is_RInt_gen_scal).
  apply Rbar_locally'_filter.
  apply Rbar_locally'_filter.
  replace (0) with (0 - 0) by lra.
  apply is_RInt_gen_Derive.
  apply filter_forall.
  intros.
  now apply ex_derive_General_Gaussian_PDF.
  apply filter_forall.
  intros.
  apply continuous_Derive_General_Gaussian_PDF.
  now unfold General_Gaussian_PDF.
  replace (0) with ( / (sigma * sqrt (2*PI)) * 0) by lra.
  apply is_lim_scal_l with (a:= / (sigma * sqrt (2 * PI))) (l := 0).
  apply (is_lim_ext (fun t => exp(-((/sigma * t)+ (-mu/sigma))^2/2))).
  intros.
  f_equal; now field_simplify.
  apply is_lim_comp_lin with (f := fun t => exp(-t^2/2)) (a := /sigma) (b:= -mu/sigma).
  replace (Rbar_plus (Rbar_mult (/ sigma) m_infty) (- mu / sigma)) with (m_infty).
  apply limexp_neg_minf.
  rewrite Rbar_mult_comm.
  rewrite Rbar_mult_m_infty_pos.
  now compute.
  apply Rinv_0_lt_compat; lra.
  now apply Rinv_neq_0_compat.
  unfold General_Gaussian_PDF.
  replace (0) with ( / (sigma * sqrt (2*PI)) * 0) by lra.
  apply is_lim_scal_l with (a:= / (sigma * sqrt (2 * PI))) (l := 0).
  apply (is_lim_ext (fun t => exp(-((/sigma * t)+ (-mu/sigma))^2/2))).
  intros.
  f_equal; now field_simplify.
  apply is_lim_comp_lin with (f := fun t => exp(-t^2/2)) (a := /sigma) (b:= -mu/sigma).
  replace (Rbar_plus (Rbar_mult (/ sigma) p_infty) (- mu / sigma)) with (p_infty).
  apply limexp_neg_inf.
  rewrite Rbar_mult_comm.
  rewrite Rbar_mult_p_infty_pos.
  now compute.
  apply Rinv_0_lt_compat; lra.
  now apply Rinv_neq_0_compat.
Qed.

End Gaussian_Distribution.


Require Import Reals.
Require Import Interval.Tactic.

Local Open Scope R_scope.

(*
Example taken from:
John Harrison, Verifying the Accuracy of Polynomial Approximations in HOL.
In TPHOLs, pages 137-152, 1997.
*)

Goal
  forall x : R,
    (-10831/1000000 <= x /\ x <= 10831/1000000) ->
    Rabs ((exp x - 1) - (x + (8388676/2^24) * x^2 + (11184876/2^26) * x^3))
    <= (23/27) / (2^33).
Proof.
intros x H.
(*
Time interval with (i_bisect x, i_autodiff x, i_prec 50, i_depth 16). (* 22 s *)
*)
Time interval with (i_bisect x, i_taylor x, i_degree 3, i_prec 50). (* 0.12 s *)
Qed.

(* The timings above were obtained using Coq 8.9.1 *)


Require Export Reals Qreals.
Require Import Lra.
Open Scope R_scope.

Local Lemma archimed_1
     : forall r : R, r < IZR (up r).
Proof. apply archimed. Qed.

Local Hint Resolve archimed_1 : real.

Lemma inverses_of_nats_approach_0:
  forall eps:R, eps > 0 -> exists n:nat, (n > 0)%nat /\
                                         / (INR n) < eps.
Proof.
  intros.
  assert (exists n:Z, (n>0)%Z /\ / (IZR n) < eps).
  {
    exists (up (/ eps)).
    split.
    - assert (IZR (up (/ eps)) > 0).
      {
        eapply Rgt_trans;
          auto with real rorders.
      }
      apply lt_0_IZR in H0.
      now apply Z.lt_gt.
    - pattern eps at 2.
      rewrite <- Rinv_involutive; auto with real.
      apply Rinv_lt_contravar; auto with real.
      apply Rmult_lt_0_compat; auto with real.
      apply Rlt_trans with (/ eps); auto with real.
  }
  destruct H0 as [[ | p | p ]];
    destruct H0;
    try inversion H0.
  exists (nat_of_P p).
  unfold IZR in H1.
  rewrite <- INR_IPR in H1.
  auto with real.
Qed.

Lemma Z_interpolation: forall x y:R, y > x+1 ->
  exists n:Z, x < IZR n < y.
Proof.
intros.
exists (up x).
split; auto with real.
eapply Rle_lt_trans;
    [ | eassumption ].
destruct (archimed x).
lra.
Qed.

Local Notation " ' x " := (Zpos x) (at level 20, no associativity) : Z_scope.

Lemma rational_interpolation: forall (x y:R) (n:positive),
  x<y -> IZR (' n) > 1/(y-x) ->
  exists m:Z, x < Q2R (m # n) < y.
Proof.
intros.
assert (0 < / IZR (' n)) by
  auto with real.
destruct (Z_interpolation (IZR (' n) * x)
  (IZR (' n) * y)) as [m].
- apply Rgt_minus in H.
  assert (IZR (' n) * (y - x) > 1).
  {
    replace 1 with ((1 / (y-x)) * (y-x)) by
      (field; auto with real).
    now apply Rmult_gt_compat_r.
  }
  apply Rgt_minus in H2.
  apply Rminus_gt.
  match goal with H2: ?a > 0 |- ?b > 0 => replace b with a end;
    lra.
- exists m.
  unfold Q2R.
  simpl.
  replace x with ((IZR (' n) * x) / IZR (' n)) by
    (field; auto with real).
  replace y with ((IZR (' n) * y) / IZR (' n)) by
    (field; auto with real).
  destruct H2.
  split;
    now apply Rmult_lt_compat_r.
Qed.

Lemma rationals_dense_in_reals: forall x y:R, x<y ->
  exists q:Q, x < Q2R q < y.
Proof.
intros.
pose (d := up (/ (y - x))).
assert (0 < d)%Z.
{
  apply lt_IZR, Rlt_trans with (/ (y - x)).
  - apply Rgt_minus in H.
    auto with real.
  - apply archimed.
}
assert (/ (y - x) < IZR d) by apply archimed.
destruct d as [|d|]; try discriminate H0.
destruct (rational_interpolation x y d) as [n]; trivial.
- replace (1 / (y-x)) with (/(y-x)); lra.
- now exists (n # d).
Qed.


(*
 * Hat problem.
 * A group of people are all given either a red or blue hat. They cannot communicate
 * with each other and they cannot see their own hat. They are eached asked to guess
 * what color hat they have and if any is wrong then they all lose. What strategy
 * allows them to win exactly 50% of the time?
 *)

From Coq Require Import
  PeanoNat.
From FunProofs.Lib Require Import
  Enumerate
  Util.
Import EqDecNotations.

Module Hat.
  Inductive Hat := Red | Blue.

  #[export] Instance Hat_eq_dec : EqDec Hat.
  Proof. constructor; decide equality. Defined.

  Definition opp h := match h with Red => Blue | Blue => Red end.

  Lemma opp_involutive : forall h, opp (opp h) = h.
  Proof. now destruct h. Qed.

  Lemma opp_neq : forall h, h <> opp h.
  Proof. now destruct h. Qed.
End Hat.

Notation Hat := Hat.Hat.
Notation Red := Hat.Red.
Notation Blue := Hat.Blue.
#[local] Existing Instance Hat.Hat_eq_dec.

(* A strategy guesses a hat color given the visible hats. *)
Definition strategy := list Hat -> Hat.

(* Each person guesses by applying the strategy to all hats but their own. *)
Definition guess (s : strategy) (hats : list Hat) :=
  mapIdx (fun i => s (remove_nth i hats)) hats.

Definition count_hats color (hats : list Hat) :=
  length (filter (fun h => h ==? color) hats).

(* The optimal strategy guesses based on the number of red (or blue equivalently)
   hats it can see. *)
Definition optimal : strategy := fun hats =>
  if Nat.even (count_hats Red hats) then Blue else Red.

(* A strategy wins if every person guesses correctly. *)
Definition wins (s : strategy) (hats : list Hat) : bool :=
  (guess s hats) ==? hats.

Lemma count_hats_same hats color :
  count_hats color (color :: hats) = S (count_hats color hats).
Proof. unfold count_hats; intros; cbn; simplify; auto. Qed.

Definition count_hats_opp hats color ocolor :
  ocolor = Hat.opp color -> count_hats color (ocolor :: hats) = count_hats color hats.
Proof.
  intros; subst; cbn; rewrite eqb_false; auto using Hat.opp_neq.
Qed.

Lemma guess_blue hats : optimal (Blue :: hats) = optimal hats.
Proof. unfold optimal; intros; rewrite count_hats_opp; auto. Qed.

Lemma guess_red hats : optimal (Red :: hats) = Hat.opp (optimal hats).
Proof.
  unfold optimal; intros; cbn.
  unfold count_hats.
  destruct (length _); cbn -[Nat.even]; auto.
  rewrite Nat.even_succ, <- Nat.negb_even.
  destruct (Nat.even _); auto.
Qed.

Lemma apply_guess_blue hats :
  guess optimal (Blue :: hats) = optimal hats :: guess optimal hats.
Proof.
  unfold guess, mapIdx; intros; cbn.
  rewrite <- seq_shift, map_map.
  erewrite map_ext; auto.
Qed.

Lemma apply_guess_red hats :
  guess optimal (Red :: hats) = optimal hats :: map Hat.opp (guess optimal hats).
Proof.
  unfold guess, mapIdx; intros; cbn.
  rewrite <- seq_shift, !map_map.
  erewrite map_ext; auto.
  intros; cbn; rewrite guess_red; auto.
Qed.

(* For any set of n hats, the number with an even number of red hats is 2^(n-1). *)
Lemma enumerate_hats_half_even' n
    (all := enumerate [Red; Blue] n)
    (evens := filter (fun hats => Nat.even (count_hats Red hats)) all) :
  length evens = 2 ^ (n - 1).
Proof.
  induction n; cbn; auto.
  cbn in IHn.
  rewrite flat_map_concat_map, <- concat_filter_map, map_map.
  erewrite map_ext with (g := fun x => if Nat.even (count_hats Red x) then _ else _).
  2:{
    intros; cbn [filter].
    rewrite count_hats_same, count_hats_opp by auto.
    rewrite Nat.even_succ, <- Nat.negb_even.
    destruct (Nat.even _); cbn; auto.
  }
  erewrite (concat_length _ 1).
  - rewrite map_length, enumerate_length; cbn.
    rewrite Nat.sub_0_r, Nat.add_0_r; auto.
  - rewrite Forall_forall; intros *.
    rewrite in_map_iff.
    intros (? & ? & ?); subst.
    destruct (Nat.even _); cbn; auto.
Qed.

(* For any set of n hats, exactly half have an even number of red hats. *)
Corollary enumerate_hats_half_even n
    (all := enumerate [Red; Blue] n)
    (evens := filter (fun hats => Nat.even (count_hats Red hats)) all) :
  0 < n -> 2 * length evens = length all.
Proof.
  intros; subst all evens.
  rewrite enumerate_hats_half_even', enumerate_length; cbn [length].
  change 2 with (2 ^ 1) at 1.
  rewrite <- Nat.pow_add_r.
  replace (1 + (n - 1)) with n; lia.
Qed.

(* If the number of red hats is even then the optimal strategy guarantees every
   guess to be correct. Otherwise every guess is wrong. *)
Theorem guess_wins_even hats :
  guess optimal hats = if Nat.even (count_hats Red hats) then hats else map Hat.opp hats.
Proof.
  induction hats as [| h hats]; intros; [cbn; auto |].
  destruct h.
  - cbn -[guess Nat.even].
    rewrite Nat.even_succ, <- Nat.negb_even.
    fold (count_hats Red hats).
    rewrite apply_guess_red, IHhats; unfold optimal.
    destruct (Nat.even _); cbn; auto.
    erewrite map_map, map_ext, map_id; auto.
    intros; rewrite Hat.opp_involutive; auto.
  - cbn -[guess Nat.even].
    fold (count_hats Red hats).
    rewrite apply_guess_blue.
    rewrite IHhats; unfold optimal.
    destruct (Nat.even _); auto.
Qed.

(* For any set of n hats, the optimal strategy guarantees a win for exactly half. *)
Theorem guess_wins_half n
    (all := enumerate [Red; Blue] n)
    (win := filter (wins optimal) all) :
  0 < n -> 2 * length win = length all.
Proof.
  intros; subst all win; unfold wins.
  rewrite <- enumerate_hats_half_even; auto.
  erewrite filter_ext with (g := fun hats => Nat.even (count_hats Red hats)); auto.
  intros hats; rewrite guess_wins_even.
  destruct hats; auto.
  destruct (Nat.even _); simplify; auto.
  pose proof Hat.opp_neq.
  cbn; rewrite eqb_false; congruence.
Qed.

(* enumerate contains all sets of hats. *)
Corollary enumerate_hats hats : In hats (enumerate [Red; Blue] (length hats)).
Proof.
  intros; apply enumerate_finite.
  intros []; cbn; auto.
Qed.


(* Software Foundations *)
(* Exercice 2 stars, advanced currying *)

Definition prod_curry{X Y Z: Type}
      (f: X*Y -> Z)(x: X)(y: Y): Z := f(x,y).

Definition prod_uncurry{X Y Z: Type}
      (f: X -> Y -> Z)(p: X*Y): Z := match p with (x,y) => f x y end.

Definition prod_uncurry'{X Y Z: Type}
      (f: X -> Y -> Z)(p: X*Y): Z := f (fst p) (snd p).

Check @prod_curry.
Check @prod_uncurry.

Theorem uncurry_curry: forall (X Y Z: Type)(f: X -> Y -> Z)(x: X)(y: Y),
      prod_curry (prod_uncurry f) x y = f x y.
Proof.
    intros. compute. reflexivity.
Qed.

Theorem curry_uncurry: forall (X Y Z: Type)(f: X*Y -> Z)(p: X*Y),
      prod_uncurry (prod_curry f) p = f p.
Proof.
    intros. compute. destruct p. reflexivity.
Qed.